refactor: use custom properties in shadow host

This commit is contained in:
one 2025-08-15 16:18:32 +08:00
parent e801b9d298
commit 4c81443930
6 changed files with 31 additions and 41 deletions

View File

@ -1,10 +1,9 @@
import { AsyncInitializer } from '@renderer/utils/asyncInitializer' import { AsyncInitializer } from '@renderer/utils/asyncInitializer'
import React, { memo, useCallback } from 'react' import React, { memo, useCallback } from 'react'
import styled from 'styled-components'
import { useDebouncedRender } from './hooks/useDebouncedRender' import { useDebouncedRender } from './hooks/useDebouncedRender'
import ImagePreviewLayout from './ImagePreviewLayout' import ImagePreviewLayout from './ImagePreviewLayout'
import { PreviewHostCssWhite } from './styles' import { ShadowWhiteContainer } from './styles'
import { BasicPreviewHandles, BasicPreviewProps } from './types' import { BasicPreviewHandles, BasicPreviewProps } from './types'
import { renderSvgInShadowHost } from './utils' import { renderSvgInShadowHost } from './utils'
@ -28,7 +27,7 @@ const GraphvizPreview = ({
const renderGraphviz = useCallback(async (content: string, container: HTMLDivElement) => { const renderGraphviz = useCallback(async (content: string, container: HTMLDivElement) => {
const viz = await vizInitializer.get() const viz = await vizInitializer.get()
const svg = viz.renderString(content, { format: 'svg' }) const svg = viz.renderString(content, { format: 'svg' })
renderSvgInShadowHost(svg, container, { customCss: PreviewHostCssWhite }) renderSvgInShadowHost(svg, container)
}, []) }, [])
// 使用预览渲染器 hook // 使用预览渲染器 hook
@ -44,16 +43,9 @@ const GraphvizPreview = ({
ref={ref} ref={ref}
imageRef={containerRef} imageRef={containerRef}
source="graphviz"> source="graphviz">
<StyledGraphviz ref={containerRef} className="graphviz special-preview" /> <ShadowWhiteContainer ref={containerRef} className="graphviz special-preview" />
</ImagePreviewLayout> </ImagePreviewLayout>
) )
} }
const StyledGraphviz = styled.div`
overflow: auto;
position: relative;
width: 100%;
height: 100%;
`
export default memo(GraphvizPreview) export default memo(GraphvizPreview)

View File

@ -1,10 +1,10 @@
import { nanoid } from '@reduxjs/toolkit' import { nanoid } from '@reduxjs/toolkit'
import { useMermaid } from '@renderer/hooks/useMermaid' import { useMermaid } from '@renderer/hooks/useMermaid'
import React, { memo, useCallback, useEffect, useRef, useState } from 'react' import React, { memo, useCallback, useEffect, useRef, useState } from 'react'
import styled from 'styled-components'
import { useDebouncedRender } from './hooks/useDebouncedRender' import { useDebouncedRender } from './hooks/useDebouncedRender'
import ImagePreviewLayout from './ImagePreviewLayout' import ImagePreviewLayout from './ImagePreviewLayout'
import { ShadowTransparentContainer } from './styles'
import { BasicPreviewHandles, BasicPreviewProps } from './types' import { BasicPreviewHandles, BasicPreviewProps } from './types'
import { renderSvgInShadowHost } from './utils' import { renderSvgInShadowHost } from './utils'
@ -129,16 +129,9 @@ const MermaidPreview = ({
ref={ref} ref={ref}
imageRef={containerRef} imageRef={containerRef}
source="mermaid"> source="mermaid">
<StyledMermaid ref={containerRef} className="mermaid special-preview" /> <ShadowTransparentContainer ref={containerRef} className="mermaid special-preview" />
</ImagePreviewLayout> </ImagePreviewLayout>
) )
} }
const StyledMermaid = styled.div`
overflow: auto;
position: relative;
width: 100%;
height: 100%;
`
export default memo(MermaidPreview) export default memo(MermaidPreview)

View File

@ -4,7 +4,7 @@ import React, { memo, useCallback, useEffect } from 'react'
import { useDebouncedRender } from './hooks/useDebouncedRender' import { useDebouncedRender } from './hooks/useDebouncedRender'
import ImagePreviewLayout from './ImagePreviewLayout' import ImagePreviewLayout from './ImagePreviewLayout'
import { PreviewHostCssWhite } from './styles' import { ShadowWhiteContainer } from './styles'
import { BasicPreviewHandles, BasicPreviewProps } from './types' import { BasicPreviewHandles, BasicPreviewProps } from './types'
import { renderSvgInShadowHost } from './utils' import { renderSvgInShadowHost } from './utils'
@ -104,7 +104,7 @@ const PlantUmlPreview = ({
} }
const text = await response.text() const text = await response.text()
renderSvgInShadowHost(text, container, { customCss: PreviewHostCssWhite }) renderSvgInShadowHost(text, container)
}, []) }, [])
// 使用预览渲染器 hook // 使用预览渲染器 hook
@ -129,7 +129,7 @@ const PlantUmlPreview = ({
ref={ref} ref={ref}
imageRef={containerRef} imageRef={containerRef}
source="plantuml"> source="plantuml">
<div ref={containerRef} className="plantuml-preview special-preview" /> <ShadowWhiteContainer ref={containerRef} className="plantuml-preview special-preview" />
</ImagePreviewLayout> </ImagePreviewLayout>
) )
} }

View File

@ -2,7 +2,7 @@ import { memo, useCallback } from 'react'
import { useDebouncedRender } from './hooks/useDebouncedRender' import { useDebouncedRender } from './hooks/useDebouncedRender'
import ImagePreviewLayout from './ImagePreviewLayout' import ImagePreviewLayout from './ImagePreviewLayout'
import { PreviewHostCssWhite } from './styles' import { ShadowWhiteContainer } from './styles'
import { BasicPreviewHandles } from './types' import { BasicPreviewHandles } from './types'
import { renderSvgInShadowHost } from './utils' import { renderSvgInShadowHost } from './utils'
@ -19,7 +19,7 @@ interface SvgPreviewProps {
const SvgPreview = ({ children, enableToolbar = false, className, ref }: SvgPreviewProps) => { const SvgPreview = ({ children, enableToolbar = false, className, ref }: SvgPreviewProps) => {
// 定义渲染函数 // 定义渲染函数
const renderSvg = useCallback(async (content: string, container: HTMLDivElement) => { const renderSvg = useCallback(async (content: string, container: HTMLDivElement) => {
renderSvgInShadowHost(content, container, { customCss: PreviewHostCssWhite }) renderSvgInShadowHost(content, container)
}, []) }, [])
// 使用预览渲染器 hook // 使用预览渲染器 hook
@ -35,7 +35,7 @@ const SvgPreview = ({ children, enableToolbar = false, className, ref }: SvgPrev
ref={ref} ref={ref}
imageRef={containerRef} imageRef={containerRef}
source="svg"> source="svg">
<div ref={containerRef} className={className ?? 'svg-preview special-preview'}></div> <ShadowWhiteContainer ref={containerRef} className={className ?? 'svg-preview special-preview'} />
</ImagePreviewLayout> </ImagePreviewLayout>
) )
} }

View File

@ -34,10 +34,14 @@ export const PreviewContainer = styled(Flex).attrs({ role: 'alert' })`
} }
` `
export const PreviewHostCssWhite = ` export const ShadowWhiteContainer = styled.div`
:host { --shadow-host-background-color: white;
background-color: white; --shadow-host-border: 0.5px solid var(--color-code-background);
border: 0.5px solid var(--color-code-background); --shadow-host-border-radius: 8px;
border-radius: 8px; `
}
export const ShadowTransparentContainer = styled.div`
--shadow-host-background-color: transparent;
--shadow-host-border: unset;
--shadow-host-border-radius: unset;
` `

View File

@ -1,7 +1,3 @@
type ShadowHostOptions = {
customCss?: string // override styles
}
/** /**
* Renders an SVG string inside a host element's Shadow DOM to ensure style encapsulation. * Renders an SVG string inside a host element's Shadow DOM to ensure style encapsulation.
* This function handles creating the shadow root, injecting base styles for the host, * This function handles creating the shadow root, injecting base styles for the host,
@ -11,7 +7,7 @@ type ShadowHostOptions = {
* @param hostElement The container element that will host the Shadow DOM. * @param hostElement The container element that will host the Shadow DOM.
* @throws An error if the SVG content is invalid or cannot be parsed. * @throws An error if the SVG content is invalid or cannot be parsed.
*/ */
export function renderSvgInShadowHost(svgContent: string, hostElement: HTMLElement, options?: ShadowHostOptions): void { export function renderSvgInShadowHost(svgContent: string, hostElement: HTMLElement): void {
if (!hostElement) { if (!hostElement) {
throw new Error('Host element for SVG rendering is not available.') throw new Error('Host element for SVG rendering is not available.')
} }
@ -19,8 +15,16 @@ export function renderSvgInShadowHost(svgContent: string, hostElement: HTMLEleme
const shadowRoot = hostElement.shadowRoot || hostElement.attachShadow({ mode: 'open' }) const shadowRoot = hostElement.shadowRoot || hostElement.attachShadow({ mode: 'open' })
// Base styles for the host element // Base styles for the host element
const base = ` const style = document.createElement('style')
style.textContent = `
:host { :host {
--shadow-host-background-color: white;
--shadow-host-border: 0.5px solid var(--color-code-background);
--shadow-host-border-radius: 8px;
background-color: var(--shadow-host-background-color);
border: var(--shadow-host-border);
border-radius: var(--shadow-host-border-radius);
padding: 1em; padding: 1em;
overflow: auto; overflow: auto;
display: block; display: block;
@ -34,9 +38,6 @@ export function renderSvgInShadowHost(svgContent: string, hostElement: HTMLEleme
} }
` `
const style = document.createElement('style')
style.textContent = base + (options?.customCss ? `\n${options.customCss}\n` : '')
// Clear previous content and append new style and SVG // Clear previous content and append new style and SVG
shadowRoot.innerHTML = '' shadowRoot.innerHTML = ''
shadowRoot.appendChild(style) shadowRoot.appendChild(style)