From 849ee573acc3dae14c3ff164be949e5d770bfd1d Mon Sep 17 00:00:00 2001 From: dvcolomban Date: Fri, 21 Aug 2026 18:34:32 +0200 Subject: [PATCH] fix(json-render-ui): restore shadow-root theming and scrollbars --- design/build-shadow-css.ts | 28 +++++------ packages/json-render-ui/scripts/build-css.ts | 6 +++ .../json-render-ui/src/JsonRender.stories.ts | 48 +++++++++++++++++-- .../src/renderer-module/index.ts | 21 ++++---- .../src/renderer-module/style.css | 3 ++ 5 files changed, 80 insertions(+), 26 deletions(-) create mode 100644 packages/json-render-ui/src/renderer-module/style.css diff --git a/design/build-shadow-css.ts b/design/build-shadow-css.ts index a102dbd2..f364265f 100644 --- a/design/build-shadow-css.ts +++ b/design/build-shadow-css.ts @@ -30,12 +30,12 @@ export interface BuildShadowCssOptions { */ primaryRampPath: string /** - * Absolute path to a hand-authored stylesheet run through the generator's - * configured transformers (directives, variant groups) and merged in - * right after the CSS reset. Omit for a package with no hand-written - * styles. + * One or more absolute paths to hand-authored stylesheets run through the + * generator's configured transformers (directives, variant groups) and + * merged in order right after the CSS reset. Omit for a package with no + * hand-written styles. */ - userStylePath?: string + userStylePath?: string | readonly string[] /** * Prefix Wind's `--un-*` custom properties are renamed to (see * `namespaceShadowCssVars`) — unique per shadow-root surface so two @@ -93,16 +93,16 @@ export async function buildShadowCss(options: BuildShadowCssOptions): Promise '')) - : undefined - if (userStyle) { + // Hand-written stylesheets may use `--at-apply`. Run each through the + // configured transformers before merging them in the caller's order. + const userStylePaths = typeof userStylePath === 'string' ? [userStylePath] : (userStylePath ?? []) + const userStyles: string[] = [] + for (const userStylePath of userStylePaths) { + const userStyle = new MagicString(await fs.readFile(userStylePath, 'utf-8').catch(() => '')) for (const transformer of generator.config.transformers ?? []) { - await transformer.transform(userStyle, userStylePath!, { uno: generator } as any) + await transformer.transform(userStyle, userStylePath, { uno: generator } as any) } + userStyles.push(userStyle.toString()) } const primaryRamp = await fs.readFile(primaryRampPath, 'utf-8') @@ -126,7 +126,7 @@ export async function buildShadowCss(options: BuildShadowCssOptions): Promise undefined } @@ -20,7 +21,7 @@ const meta: Meta = { } export default meta -export const Gallery = story({ +const gallerySpec: Spec = { root: 'root', elements: { root: { type: 'Stack', props: { gap: 12 }, children: ['title', 'row', 'card', 'progress', 'table', 'tree'] }, @@ -35,7 +36,9 @@ export const Gallery = story({ table: { type: 'DataTable', props: { rows: [{ id: 1, name: 'a' }, { id: 2, name: 'b' }] }, children: [] }, tree: { type: 'Tree', props: { data: { a: 1, b: [true, 'x'] } }, children: [] }, }, -}) +} + +export const Gallery = story(gallerySpec) export const Controls = story({ root: 'root', @@ -103,3 +106,42 @@ export const SubsetRegistry: StoryObj = story( }, { registry: subsetRegistry }, ) + +const dockRendererContext = { + rpc: { call: rpc.call, connectionMeta: undefined }, +} as unknown as Parameters[0]['context'] + +/** Mounts the shipped dock renderer so the story exercises its shadow root and adopted stylesheet. */ +export const InShadowRoot: StoryObj = { + render: () => ({ + setup() { + const host = useTemplateRef('host') + let dispose: (() => void) | undefined + let mountToken = 0 + onMounted(async () => { + const token = ++mountToken + const instance = await jsonRenderDockRenderer({ + entry: { + id: 'story', + title: 'Story', + icon: 'ph:cube-duotone', + type: 'json-render', + view: { spec: gallerySpec }, + }, + container: host.value!, + context: dockRendererContext, + }) + if (token !== mountToken) { + instance.dispose?.() + return + } + dispose = instance.dispose + }) + onUnmounted(() => { + mountToken++ + dispose?.() + }) + return () => h('div', { ref: 'host', class: 'w-full h-80 rounded-lg bg-grid' }) + }, + }), +} diff --git a/packages/json-render-ui/src/renderer-module/index.ts b/packages/json-render-ui/src/renderer-module/index.ts index a3d2aa50..d6770b8d 100644 --- a/packages/json-render-ui/src/renderer-module/index.ts +++ b/packages/json-render-ui/src/renderer-module/index.ts @@ -46,29 +46,32 @@ const jsonRenderDockRenderer: JsonRenderDockRenderer = async ({ entry, container shadow.append(style) } - // Carries the `.dark`/`.light` class that class-based utilities resolve - // against (kept in sync with the viewer's container class), and the native - // `color-scheme` for scrollbars and form controls. + // Keep the scheme class on an ancestor. Wind3 emits descendant selectors + // such as `.dark .bg-base`, which do not match an element carrying both + // classes itself. + const colorSchemeRoot = document.createElement('div') + colorSchemeRoot.style.display = 'contents' const root = document.createElement('div') - root.className = 'w-full h-full of-auto p4 bg-base color-base font-sans text-sm' + root.className = 'devframes-json-render-scroll-root w-full h-full of-auto p4 color-base font-sans text-sm' const syncScheme = (): void => { const dark = isDarkFor(container) - root.classList.toggle('dark', dark) - root.classList.toggle('light', !dark) - root.style.colorScheme = dark ? 'dark' : 'light' + colorSchemeRoot.classList.toggle('dark', dark) + colorSchemeRoot.classList.toggle('light', !dark) + colorSchemeRoot.style.colorScheme = dark ? 'dark' : 'light' } syncScheme() const observer = new MutationObserver(syncScheme) observer.observe(container, { attributes: true, attributeFilter: ['class'] }) observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }) - shadow.append(root) + colorSchemeRoot.append(root) + shadow.append(colorSchemeRoot) const instance = await inner({ entry, container: root, context }) return { dispose() { observer.disconnect() instance.dispose?.() - root.remove() + colorSchemeRoot.remove() }, } } diff --git a/packages/json-render-ui/src/renderer-module/style.css b/packages/json-render-ui/src/renderer-module/style.css new file mode 100644 index 00000000..a8d6f973 --- /dev/null +++ b/packages/json-render-ui/src/renderer-module/style.css @@ -0,0 +1,3 @@ +.devframes-json-render-scroll-root { + scrollbar-gutter: stable; +}