Skip to content

Commit 64a21cb

Browse files
authored
fix(json-render-ui): restore shadow-root theming and scrollbars (#278)
1 parent 171b8bf commit 64a21cb

5 files changed

Lines changed: 80 additions & 26 deletions

File tree

design/build-shadow-css.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ export interface BuildShadowCssOptions {
3030
*/
3131
primaryRampPath: string
3232
/**
33-
* Absolute path to a hand-authored stylesheet run through the generator's
34-
* configured transformers (directives, variant groups) and merged in
35-
* right after the CSS reset. Omit for a package with no hand-written
36-
* styles.
33+
* One or more absolute paths to hand-authored stylesheets run through the
34+
* generator's configured transformers (directives, variant groups) and
35+
* merged in order right after the CSS reset. Omit for a package with no
36+
* hand-written styles.
3737
*/
38-
userStylePath?: string
38+
userStylePath?: string | readonly string[]
3939
/**
4040
* Prefix Wind's `--un-*` custom properties are renamed to (see
4141
* `namespaceShadowCssVars`) — unique per shadow-root surface so two
@@ -93,16 +93,16 @@ export async function buildShadowCss(options: BuildShadowCssOptions): Promise<Bu
9393
await generator.applyExtractors(content, file, tokens)
9494
}
9595

96-
// The hand-written stylesheet (if any) may use `--at-apply` — run it
97-
// through the configured transformers (directives, variant groups) before
98-
// merging.
99-
const userStyle = userStylePath
100-
? new MagicString(await fs.readFile(userStylePath, 'utf-8').catch(() => ''))
101-
: undefined
102-
if (userStyle) {
96+
// Hand-written stylesheets may use `--at-apply`. Run each through the
97+
// configured transformers before merging them in the caller's order.
98+
const userStylePaths = typeof userStylePath === 'string' ? [userStylePath] : (userStylePath ?? [])
99+
const userStyles: string[] = []
100+
for (const userStylePath of userStylePaths) {
101+
const userStyle = new MagicString(await fs.readFile(userStylePath, 'utf-8').catch(() => ''))
103102
for (const transformer of generator.config.transformers ?? []) {
104-
await transformer.transform(userStyle, userStylePath!, { uno: generator } as any)
103+
await transformer.transform(userStyle, userStylePath, { uno: generator } as any)
105104
}
105+
userStyles.push(userStyle.toString())
106106
}
107107

108108
const primaryRamp = await fs.readFile(primaryRampPath, 'utf-8')
@@ -126,7 +126,7 @@ export async function buildShadowCss(options: BuildShadowCssOptions): Promise<Bu
126126
// `namespaceShadowCssVars`).
127127
let css = [
128128
reset,
129-
userStyle?.toString(),
129+
...userStyles,
130130
unoCss,
131131
surfacesCss,
132132
primaryRamp,

packages/json-render-ui/scripts/build-css.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { createRequire } from 'node:module'
12
import { join } from 'node:path'
23
import { fileURLToPath } from 'node:url'
34
import { colors as c } from 'devframe/utils/colors'
@@ -12,12 +13,17 @@ import config from '../uno.config'
1213
// host page. See `design/build-shadow-css.ts` for the shared pipeline
1314
// (mirrored by `@devframes/hub-ui`'s `scripts/build-css.ts`).
1415
const SRC_DIR = join(fileURLToPath(new URL('..', import.meta.url)), 'src')
16+
const moduleRequire = createRequire(import.meta.url)
1517

1618
const { sourceCount, css } = await buildShadowCss({
1719
srcDir: SRC_DIR,
1820
globs: ['components/**/*.ts', 'renderer.ts', 'dock-renderer.ts', 'renderer-module/**/*.ts'],
1921
config,
2022
primaryRampPath: join(SRC_DIR, 'renderer-module/primary-ramp.css'),
23+
userStylePath: [
24+
moduleRequire.resolve('@antfu/design/styles/scrollbar.css'),
25+
join(SRC_DIR, 'renderer-module/style.css'),
26+
],
2127
varPrefix: '--un-jr-',
2228
})
2329
console.log(`${c.green('✓')} CSS built (${sourceCount} sources, ${(css.length / 1024).toFixed(1)} kB)`)

packages/json-render-ui/src/JsonRender.stories.ts

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import type { Spec } from '@devframes/json-render'
22
import type { Meta, StoryObj } from '@storybook/vue3-vite'
3-
import { h } from 'vue'
3+
import { h, onMounted, onUnmounted, useTemplateRef } from 'vue'
44
import { baseRegistry } from './registry'
55
import { JsonRenderView } from './renderer'
6+
import jsonRenderDockRenderer from './renderer-module'
67

78
// A no-op RPC — stories don't dispatch real actions.
89
const rpc = { call: async () => undefined }
@@ -20,7 +21,7 @@ const meta: Meta = {
2021
}
2122
export default meta
2223

23-
export const Gallery = story({
24+
const gallerySpec: Spec = {
2425
root: 'root',
2526
elements: {
2627
root: { type: 'Stack', props: { gap: 12 }, children: ['title', 'row', 'card', 'progress', 'table', 'tree'] },
@@ -37,7 +38,9 @@ export const Gallery = story({
3738
table: { type: 'DataTable', props: { rows: [{ id: 1, name: 'a' }, { id: 2, name: 'b' }] }, children: [] },
3839
tree: { type: 'Tree', props: { data: { a: 1, b: [true, 'x'] } }, children: [] },
3940
},
40-
})
41+
}
42+
43+
export const Gallery = story(gallerySpec)
4144

4245
export const Controls = story({
4346
root: 'root',
@@ -119,3 +122,42 @@ export const SubsetRegistry: StoryObj = story(
119122
},
120123
{ registry: subsetRegistry },
121124
)
125+
126+
const dockRendererContext = {
127+
rpc: { call: rpc.call, connectionMeta: undefined },
128+
} as unknown as Parameters<typeof jsonRenderDockRenderer>[0]['context']
129+
130+
/** Mounts the shipped dock renderer so the story exercises its shadow root and adopted stylesheet. */
131+
export const InShadowRoot: StoryObj = {
132+
render: () => ({
133+
setup() {
134+
const host = useTemplateRef<HTMLDivElement>('host')
135+
let dispose: (() => void) | undefined
136+
let mountToken = 0
137+
onMounted(async () => {
138+
const token = ++mountToken
139+
const instance = await jsonRenderDockRenderer({
140+
entry: {
141+
id: 'story',
142+
title: 'Story',
143+
icon: 'ph:cube-duotone',
144+
type: 'json-render',
145+
view: { spec: gallerySpec },
146+
},
147+
container: host.value!,
148+
context: dockRendererContext,
149+
})
150+
if (token !== mountToken) {
151+
instance.dispose?.()
152+
return
153+
}
154+
dispose = instance.dispose
155+
})
156+
onUnmounted(() => {
157+
mountToken++
158+
dispose?.()
159+
})
160+
return () => h('div', { ref: 'host', class: 'w-full h-80 rounded-lg bg-grid' })
161+
},
162+
}),
163+
}

packages/json-render-ui/src/renderer-module/index.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,29 +46,32 @@ const jsonRenderDockRenderer: JsonRenderDockRenderer = async ({ entry, container
4646
shadow.append(style)
4747
}
4848

49-
// Carries the `.dark`/`.light` class that class-based utilities resolve
50-
// against (kept in sync with the viewer's container class), and the native
51-
// `color-scheme` for scrollbars and form controls.
49+
// Keep the scheme class on an ancestor. Wind3 emits descendant selectors
50+
// such as `.dark .bg-base`, which do not match an element carrying both
51+
// classes itself.
52+
const colorSchemeRoot = document.createElement('div')
53+
colorSchemeRoot.style.display = 'contents'
5254
const root = document.createElement('div')
53-
root.className = 'w-full h-full of-auto p4 bg-base color-base font-sans text-sm'
55+
root.className = 'devframes-json-render-scroll-root w-full h-full of-auto p4 color-base font-sans text-sm'
5456
const syncScheme = (): void => {
5557
const dark = isDarkFor(container)
56-
root.classList.toggle('dark', dark)
57-
root.classList.toggle('light', !dark)
58-
root.style.colorScheme = dark ? 'dark' : 'light'
58+
colorSchemeRoot.classList.toggle('dark', dark)
59+
colorSchemeRoot.classList.toggle('light', !dark)
60+
colorSchemeRoot.style.colorScheme = dark ? 'dark' : 'light'
5961
}
6062
syncScheme()
6163
const observer = new MutationObserver(syncScheme)
6264
observer.observe(container, { attributes: true, attributeFilter: ['class'] })
6365
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] })
64-
shadow.append(root)
66+
colorSchemeRoot.append(root)
67+
shadow.append(colorSchemeRoot)
6568

6669
const instance = await inner({ entry, container: root, context })
6770
return {
6871
dispose() {
6972
observer.disconnect()
7073
instance.dispose?.()
71-
root.remove()
74+
colorSchemeRoot.remove()
7275
},
7376
}
7477
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.devframes-json-render-scroll-root {
2+
scrollbar-gutter: stable;
3+
}

0 commit comments

Comments
 (0)