poveste 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/node/build.js +0 -3
- package/dist/node/builtin-plugins/tailwind-tokens.d.ts +6 -1
- package/dist/node/builtin-plugins/tailwind-tokens.d.ts.map +1 -1
- package/dist/node/builtin-plugins/tailwind-tokens.js +169 -33
- package/dist/node/builtin-plugins/vanilla-support/MountStory.d.ts +3 -3
- package/dist/node/builtin-plugins/vanilla-support/RenderStory.d.ts +5 -5
- package/dist/node/collect/index.d.ts.map +1 -1
- package/dist/node/collect/index.js +1 -3
- package/dist/node/config.d.ts +2 -18
- package/dist/node/config.d.ts.map +1 -1
- package/dist/node/config.js +3 -0
- package/dist/node/dom/env.d.ts.map +1 -1
- package/dist/node/dom/env.js +10 -6
- package/dist/node/index.d.ts +0 -7
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/markdown.d.ts.map +1 -1
- package/dist/node/markdown.js +21 -3
- package/dist/node/stories.js +1 -1
- package/dist/node/style-isolation/entry-css-merger.d.ts.map +1 -1
- package/dist/node/style-isolation/entry-css-merger.js +8 -7
- package/dist/node/style-isolation/transforms.js +32 -8
- package/dist/node/vite.js +1 -1
- package/package.json +10 -12
- package/src/node/__tests__/dom-env.spec.ts +19 -1
- package/src/node/build.ts +2 -6
- package/src/node/builtin-plugins/tailwind-tokens.ts +190 -34
- package/src/node/collect/index.ts +1 -3
- package/src/node/config.ts +3 -0
- package/src/node/dom/env.ts +10 -6
- package/src/node/index.ts +0 -8
- package/src/node/markdown.ts +25 -3
- package/src/node/stories.ts +1 -1
- package/src/node/style-isolation/__tests__/entry-css-merger.spec.ts +37 -18
- package/src/node/style-isolation/__tests__/transforms.spec.ts +46 -8
- package/src/node/style-isolation/entry-css-merger.ts +10 -9
- package/src/node/style-isolation/transforms.ts +36 -7
- package/src/node/vite.ts +4 -4
- package/tsconfig.json +1 -1
|
@@ -6,7 +6,7 @@ export function wrapUserCss(css, opts) {
|
|
|
6
6
|
return css;
|
|
7
7
|
}
|
|
8
8
|
const { hoisted, remainder } = extractHoistableAtRules(css);
|
|
9
|
-
const body = remainder
|
|
9
|
+
const body = hasRootSelector(remainder)
|
|
10
10
|
? rewriteRootToScope(remainder)
|
|
11
11
|
: remainder;
|
|
12
12
|
const hoistedBlock = hoisted.length > 0 ? `${hoisted.join('\n')}\n` : '';
|
|
@@ -18,10 +18,10 @@ export function wrapChromeCss(css, opts) {
|
|
|
18
18
|
return css;
|
|
19
19
|
}
|
|
20
20
|
const { hoisted, remainder } = extractHoistableAtRules(css);
|
|
21
|
-
// Rewrite
|
|
21
|
+
// Rewrite root selectors to `:scope` so source-level `@scope (:root) to (...)`
|
|
22
22
|
// rules nest correctly under the outer scope; without this, the inner
|
|
23
23
|
// `:root` resolves to the document root which is outside the outer scope.
|
|
24
|
-
const body = remainder
|
|
24
|
+
const body = hasRootSelector(remainder)
|
|
25
25
|
? rewriteRootToScope(remainder)
|
|
26
26
|
: remainder;
|
|
27
27
|
const hoistedBlock = hoisted.length > 0 ? `${hoisted.join('\n')}\n` : '';
|
|
@@ -34,20 +34,44 @@ export function isGlobalImport(id) {
|
|
|
34
34
|
const parts = id.slice(q + 1).split('&');
|
|
35
35
|
return parts.includes('global') || parts.some(p => p.startsWith('global='));
|
|
36
36
|
}
|
|
37
|
+
// `html` and `body` sit above the scoping root once the sheet is wrapped, so a
|
|
38
|
+
// rule targeting either can never match. Inside a story they mean the same
|
|
39
|
+
// thing `:root` does — the one root there is — which is what `:scope`
|
|
40
|
+
// resolves to. Leaving them out made `body { font-size: 14px }` inert with no
|
|
41
|
+
// error (#116); the chrome wrapper learned the same lesson in #102.
|
|
42
|
+
const ROOT_TYPE_SELECTORS = new Set(['html', 'body']);
|
|
43
|
+
// Only a gate on the lightningcss round-trip, so a false positive costs a
|
|
44
|
+
// reparse and nothing else. Word-bounded to skip `.sidebar-body` and friends.
|
|
45
|
+
const ROOT_SELECTOR_RE = /:root|(?:^|[\s,{}>+~])(?:html|body)\b/i;
|
|
46
|
+
function hasRootSelector(css) {
|
|
47
|
+
return ROOT_SELECTOR_RE.test(css);
|
|
48
|
+
}
|
|
49
|
+
// Type selectors are ASCII case-insensitive against HTML and lightningcss
|
|
50
|
+
// reports the name as authored, so `BODY { … }` arrives spelled `BODY`.
|
|
51
|
+
function isRootTypeSelector(name) {
|
|
52
|
+
return ROOT_TYPE_SELECTORS.has(name.toLowerCase());
|
|
53
|
+
}
|
|
54
|
+
// Top level of the selector only: lightningcss hands `:is()`/`:where()`/`:not()`
|
|
55
|
+
// over as one part with the inner selectors nested inside, so a root spelled
|
|
56
|
+
// `:is(html, body)` is left alone. Descending needs a decision on `:not()`,
|
|
57
|
+
// where rewriting would change what the rule matches rather than fix it (#124).
|
|
37
58
|
function rewriteRootToScope(css) {
|
|
59
|
+
const scope = { type: 'pseudo-class', kind: 'scope' };
|
|
38
60
|
const processed = lightningcssTransform({
|
|
39
61
|
filename: 'user.css',
|
|
40
62
|
code: Buffer.from(css, 'utf8'),
|
|
41
63
|
minify: false,
|
|
42
64
|
visitor: {
|
|
43
65
|
Selector(selector) {
|
|
44
|
-
|
|
66
|
+
return selector.map((part) => {
|
|
45
67
|
if (part.type === 'pseudo-class' && part.kind === 'root') {
|
|
46
|
-
;
|
|
47
|
-
|
|
68
|
+
return scope;
|
|
69
|
+
}
|
|
70
|
+
if (part.type === 'type' && isRootTypeSelector(part.name)) {
|
|
71
|
+
return scope;
|
|
48
72
|
}
|
|
49
|
-
|
|
50
|
-
|
|
73
|
+
return part;
|
|
74
|
+
});
|
|
51
75
|
},
|
|
52
76
|
},
|
|
53
77
|
});
|
package/dist/node/vite.js
CHANGED
|
@@ -283,7 +283,7 @@ export async function getViteConfigWithPlugins(isServer, ctx) {
|
|
|
283
283
|
name: 'poveste:flags',
|
|
284
284
|
enforce: 'pre',
|
|
285
285
|
transform(code, id) {
|
|
286
|
-
if (
|
|
286
|
+
if (/\.(?:vue|js)(?:$|\?)/.test(id)) {
|
|
287
287
|
const original = code;
|
|
288
288
|
for (const flag in flags) {
|
|
289
289
|
code = code.replace(new RegExp(flag, 'g'), flags[flag]);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poveste",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.1",
|
|
5
5
|
"description": "A fast story book powered by Vite",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Sorin Gitlan"
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"histoire": "./bin.mjs"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"vite": "^
|
|
41
|
+
"vite": "^8.0.0"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@akryum/tinypool": "^0.3.1",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"globby": "^14.0.2",
|
|
54
54
|
"gray-matter": "^4.0.3",
|
|
55
55
|
"jiti": "^2.4.1",
|
|
56
|
-
"jsdom": "^
|
|
56
|
+
"jsdom": "^30.0.1",
|
|
57
57
|
"lightningcss": "^1.32.0",
|
|
58
58
|
"markdown-it": "^14.1.0",
|
|
59
59
|
"markdown-it-anchor": "^9.2.0",
|
|
@@ -67,20 +67,18 @@
|
|
|
67
67
|
"shiki": "^3.0.0",
|
|
68
68
|
"sirv": "^3.0.0",
|
|
69
69
|
"unhead": "^3.1.0",
|
|
70
|
-
"vite-node": "
|
|
71
|
-
"@poveste/app": "^0.
|
|
72
|
-
"@poveste/controls": "^0.
|
|
73
|
-
"@poveste/
|
|
74
|
-
"@poveste/
|
|
70
|
+
"vite-node": "^6.0.0",
|
|
71
|
+
"@poveste/app": "^0.3.1",
|
|
72
|
+
"@poveste/controls": "^0.3.1",
|
|
73
|
+
"@poveste/vendors": "^0.3.1",
|
|
74
|
+
"@poveste/shared": "^0.3.1"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
77
|
"@types/fs-extra": "^11.0.4",
|
|
78
78
|
"@types/node": "^22.10.1",
|
|
79
|
-
"esbuild": "^0.27.2",
|
|
80
|
-
"rollup": "^4.28.0",
|
|
81
79
|
"typescript": "5.6.3",
|
|
82
|
-
"vite": "^
|
|
83
|
-
"vitest": "4.
|
|
80
|
+
"vite": "^8.2.0",
|
|
81
|
+
"vitest": "^4.1.10"
|
|
84
82
|
},
|
|
85
83
|
"scripts": {
|
|
86
84
|
"build": "rimraf dist && tsc",
|
|
@@ -10,8 +10,25 @@ describe('createDomEnv', () => {
|
|
|
10
10
|
errorSpy?.mockRestore()
|
|
11
11
|
})
|
|
12
12
|
|
|
13
|
-
describe('when a stylesheet contains
|
|
13
|
+
describe('when a stylesheet contains CSS jsdom cannot parse', () => {
|
|
14
|
+
// An unparseable selector is one of the few inputs jsdom 28+ still reports
|
|
15
|
+
// a css-parsing jsdomError for, so it is what keeps this guard honest.
|
|
16
|
+
// Remove the suppression in `env.ts` and this test fails.
|
|
14
17
|
it('does not log a css-parsing jsdomError', () => {
|
|
18
|
+
errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
19
|
+
env = createDomEnv()
|
|
20
|
+
const style = env.window.document.createElement('style')
|
|
21
|
+
style.textContent = '.btn[[[ { color: red }'
|
|
22
|
+
|
|
23
|
+
env.window.document.head.appendChild(style)
|
|
24
|
+
|
|
25
|
+
expect(errorSpy).not.toHaveBeenCalled()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// The symptom that motivated the suppression. jsdom now drops the unknown
|
|
29
|
+
// at-rule silently and keeps the rest of the sheet, so this passes on its
|
|
30
|
+
// own merits — it is here to catch a regression, not to cover the guard.
|
|
31
|
+
it('parses a Tailwind v4 @theme sheet without complaining', () => {
|
|
15
32
|
errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
16
33
|
env = createDomEnv()
|
|
17
34
|
const style = env.window.document.createElement('style')
|
|
@@ -20,6 +37,7 @@ describe('createDomEnv', () => {
|
|
|
20
37
|
env.window.document.head.appendChild(style)
|
|
21
38
|
|
|
22
39
|
expect(errorSpy).not.toHaveBeenCalled()
|
|
40
|
+
expect(env.window.document.styleSheets[0].cssRules).toHaveLength(1)
|
|
23
41
|
})
|
|
24
42
|
})
|
|
25
43
|
|
package/src/node/build.ts
CHANGED
|
@@ -3,8 +3,8 @@ import type {
|
|
|
3
3
|
ChangeViteConfigCallback,
|
|
4
4
|
PreviewStoryCallback,
|
|
5
5
|
} from '@poveste/shared'
|
|
6
|
-
import type { RollupOutput } from 'rollup'
|
|
7
6
|
import type {
|
|
7
|
+
Rolldown,
|
|
8
8
|
InlineConfig as ViteInlineConfig,
|
|
9
9
|
Plugin as VitePlugin,
|
|
10
10
|
} from 'vite'
|
|
@@ -197,7 +197,7 @@ export async function build(ctx: Context) {
|
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
const results = await viteBuild(buildViteConfig)
|
|
200
|
-
const result = Array.isArray(results) ? results[0] : results as
|
|
200
|
+
const result = Array.isArray(results) ? results[0] : results as Rolldown.RolldownOutput
|
|
201
201
|
|
|
202
202
|
function findEntryCss(entryName: string) {
|
|
203
203
|
return result.output.find(
|
|
@@ -303,7 +303,3 @@ async function writeFile(fileName: string, content: string, ctx: Context) {
|
|
|
303
303
|
function generateScriptLinks(prefetchScripts: string[], rel: string, ctx: Context) {
|
|
304
304
|
return prefetchScripts.map(s => `<link rel="${rel}" href="${ctx.resolvedViteConfig.base}${s}" as="script" crossOrigin="anonymous">`).join('')
|
|
305
305
|
}
|
|
306
|
-
|
|
307
|
-
function generateStyleLinks(styleFiles: string[], rel: string, ctx: Context) {
|
|
308
|
-
return styleFiles.map(s => `<link rel="${rel}" href="${ctx.resolvedViteConfig.base}${s}" as="style">`).join('')
|
|
309
|
-
}
|
|
@@ -3,20 +3,35 @@ import { findUp } from '../util/find-up.js'
|
|
|
3
3
|
import { getInjectedImport } from '../util/vendors.js'
|
|
4
4
|
|
|
5
5
|
export interface TailwindTokensOptions {
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Path to the CSS entrypoint that imports Tailwind and declares `@theme`.
|
|
8
|
+
* Tailwind v4 is CSS-first, so the design tokens are read from CSS rather
|
|
9
|
+
* than from a JS config. Auto-detected when omitted.
|
|
10
|
+
*/
|
|
11
|
+
cssFile?: string
|
|
7
12
|
}
|
|
8
13
|
|
|
14
|
+
const CSS_ENTRY_CANDIDATES = [
|
|
15
|
+
'src/style.css',
|
|
16
|
+
'src/styles.css',
|
|
17
|
+
'src/main.css',
|
|
18
|
+
'src/index.css',
|
|
19
|
+
'src/app.css',
|
|
20
|
+
'src/assets/main.css',
|
|
21
|
+
'src/assets/style.css',
|
|
22
|
+
'src/assets/css/main.css',
|
|
23
|
+
// Nuxt conventions
|
|
24
|
+
'app/assets/css/main.css',
|
|
25
|
+
'assets/css/main.css',
|
|
26
|
+
// Next.js conventions
|
|
27
|
+
'styles/globals.css',
|
|
28
|
+
'app/globals.css',
|
|
29
|
+
'style.css',
|
|
30
|
+
'styles.css',
|
|
31
|
+
]
|
|
32
|
+
|
|
9
33
|
export function tailwindTokens(options: TailwindTokensOptions = {}): Plugin {
|
|
10
|
-
const
|
|
11
|
-
'tailwind.config.js',
|
|
12
|
-
'tailwind.config.cjs',
|
|
13
|
-
'tailwind.config.mjs',
|
|
14
|
-
'tailwind.config.ts',
|
|
15
|
-
'tailwind-config.js',
|
|
16
|
-
'tailwind-config.cjs',
|
|
17
|
-
'tailwind-config.mjs',
|
|
18
|
-
'tailwind-config.ts',
|
|
19
|
-
])
|
|
34
|
+
const tailwindCssFile = options.cssFile ?? findUp(process.cwd(), CSS_ENTRY_CANDIDATES)
|
|
20
35
|
|
|
21
36
|
async function generate(api: PluginApiBase) {
|
|
22
37
|
try {
|
|
@@ -24,11 +39,9 @@ export function tailwindTokens(options: TailwindTokensOptions = {}): Plugin {
|
|
|
24
39
|
await api.fs.emptyDir(api.pluginTempDir)
|
|
25
40
|
api.moduleLoader.clearCache()
|
|
26
41
|
await api.fs.writeFile(api.path.resolve(api.pluginTempDir, 'style.css'), css)
|
|
27
|
-
const
|
|
28
|
-
const { default: resolveConfig } = await import('tailwindcss/resolveConfig.js')
|
|
29
|
-
const resolvedTailwindConfig = resolveConfig(tailwindConfig.default ?? tailwindConfig)
|
|
42
|
+
const theme = await loadTailwindTheme(api, tailwindCssFile)
|
|
30
43
|
const storyFile = api.path.resolve(api.pluginTempDir, 'Tailwind.story.js')
|
|
31
|
-
await api.fs.writeFile(storyFile, storyTemplate(
|
|
44
|
+
await api.fs.writeFile(storyFile, storyTemplate({ theme }))
|
|
32
45
|
api.addStoryFile(storyFile)
|
|
33
46
|
}
|
|
34
47
|
catch (e) {
|
|
@@ -40,7 +53,7 @@ export function tailwindTokens(options: TailwindTokensOptions = {}): Plugin {
|
|
|
40
53
|
name: 'builtin:tailwind-tokens',
|
|
41
54
|
|
|
42
55
|
config(config) {
|
|
43
|
-
if (
|
|
56
|
+
if (tailwindCssFile) {
|
|
44
57
|
// Add 'design-system' group
|
|
45
58
|
if (!config.tree) {
|
|
46
59
|
config.tree = {}
|
|
@@ -65,8 +78,8 @@ export function tailwindTokens(options: TailwindTokensOptions = {}): Plugin {
|
|
|
65
78
|
},
|
|
66
79
|
|
|
67
80
|
onDev(api, onCleanup) {
|
|
68
|
-
if (
|
|
69
|
-
const watcher = api.watcher.watch(
|
|
81
|
+
if (tailwindCssFile) {
|
|
82
|
+
const watcher = api.watcher.watch(tailwindCssFile)
|
|
70
83
|
.on('change', () => generate(api))
|
|
71
84
|
.on('add', () => generate(api))
|
|
72
85
|
onCleanup(() => {
|
|
@@ -76,13 +89,156 @@ export function tailwindTokens(options: TailwindTokensOptions = {}): Plugin {
|
|
|
76
89
|
},
|
|
77
90
|
|
|
78
91
|
async onBuild(api) {
|
|
79
|
-
if (
|
|
92
|
+
if (tailwindCssFile) {
|
|
80
93
|
await generate(api)
|
|
81
94
|
}
|
|
82
95
|
},
|
|
83
96
|
}
|
|
84
97
|
}
|
|
85
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Tailwind v4 keeps its theme as flat CSS custom properties rather than a
|
|
101
|
+
* resolved JS config (`resolveConfig` is gone). Load the design system from the
|
|
102
|
+
* project's CSS entrypoint and reshape its variables into the grouped structure
|
|
103
|
+
* the story template renders.
|
|
104
|
+
*/
|
|
105
|
+
/**
|
|
106
|
+
* Resolve a stylesheet referenced from CSS. A bare package specifier resolves
|
|
107
|
+
* to the JS entry, so its `style` field is used instead — that is how
|
|
108
|
+
* `@import 'tailwindcss'` reaches `tailwindcss/index.css`.
|
|
109
|
+
*/
|
|
110
|
+
async function resolveStylesheet(api: PluginApiBase, id: string, importBase: string) {
|
|
111
|
+
const { createRequire } = await import('node:module')
|
|
112
|
+
const require = createRequire(`${importBase}/`)
|
|
113
|
+
|
|
114
|
+
if (id.startsWith('.')) {
|
|
115
|
+
return api.path.resolve(importBase, id)
|
|
116
|
+
}
|
|
117
|
+
if (id.endsWith('.css')) {
|
|
118
|
+
return require.resolve(id)
|
|
119
|
+
}
|
|
120
|
+
const packageJsonPath = require.resolve(`${id}/package.json`)
|
|
121
|
+
const packageJson = JSON.parse(await api.fs.readFile(packageJsonPath, 'utf8'))
|
|
122
|
+
return api.path.resolve(api.path.dirname(packageJsonPath), packageJson.style ?? 'index.css')
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function loadTailwindTheme(api: PluginApiBase, cssFile: string) {
|
|
126
|
+
const { __unstable__loadDesignSystem } = await import('tailwindcss')
|
|
127
|
+
|
|
128
|
+
const base = api.path.dirname(cssFile)
|
|
129
|
+
const designSystem = await __unstable__loadDesignSystem(
|
|
130
|
+
await api.fs.readFile(cssFile, 'utf8'),
|
|
131
|
+
{
|
|
132
|
+
base,
|
|
133
|
+
loadStylesheet: async (id: string, importBase: string) => {
|
|
134
|
+
const resolved = await resolveStylesheet(api, id, importBase)
|
|
135
|
+
return {
|
|
136
|
+
path: resolved,
|
|
137
|
+
base: api.path.dirname(resolved),
|
|
138
|
+
content: await api.fs.readFile(resolved, 'utf8'),
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
const vars = new Map<string, string>(
|
|
145
|
+
[...designSystem.theme.entries()].map(([key, entry]: [string, any]) => [key, entry.value]),
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
/** All values in a `--namespace-*` group, keyed by their remainder. */
|
|
149
|
+
function group(namespace: string, exclude?: RegExp) {
|
|
150
|
+
const out: Record<string, string> = {}
|
|
151
|
+
const prefix = `${namespace}-`
|
|
152
|
+
for (const [key, value] of vars) {
|
|
153
|
+
if (!key.startsWith(prefix)) continue
|
|
154
|
+
if (exclude?.test(key)) continue
|
|
155
|
+
out[key.slice(prefix.length)] = value
|
|
156
|
+
}
|
|
157
|
+
return out
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Colours are rendered as shade sets: `red-500` -> `{ red: { 500: … } }`. */
|
|
161
|
+
function colors() {
|
|
162
|
+
const out: Record<string, any> = {}
|
|
163
|
+
for (const [key, value] of Object.entries(group('--color'))) {
|
|
164
|
+
const match = key.match(/^(.*)-(\d+)$/)
|
|
165
|
+
if (match) {
|
|
166
|
+
out[match[1]] ??= {}
|
|
167
|
+
out[match[1]][match[2]] = value
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
out[key] = value
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return out
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// v4 generates the spacing scale on demand from a single `--spacing` base,
|
|
177
|
+
// so the conventional steps are reconstructed here for display.
|
|
178
|
+
const SPACING_STEPS = [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 72, 80, 96]
|
|
179
|
+
function spacing() {
|
|
180
|
+
const unit = vars.get('--spacing') ?? '0.25rem'
|
|
181
|
+
// Multiply out rather than emitting `calc(0.25rem * 4)`, so the story shows
|
|
182
|
+
// the resolved length the way the v3 resolved config did.
|
|
183
|
+
const [, rawAmount, cssUnit] = unit.trim().match(/^([\d.]+)([a-z%]*)$/i) ?? []
|
|
184
|
+
const amount = Number(rawAmount)
|
|
185
|
+
const out: Record<string, string> = {}
|
|
186
|
+
for (const step of SPACING_STEPS) {
|
|
187
|
+
if (step === 0) {
|
|
188
|
+
out['0'] = '0px'
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
out[String(step)] = Number.isFinite(amount)
|
|
192
|
+
? `${Number((amount * step).toFixed(4))}${cssUnit}`
|
|
193
|
+
: `calc(${unit} * ${step})`
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return out
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Fractions and keywords have no theme variables in v4 — they are static
|
|
200
|
+
// utilities — but they belong in the sizing scales the story lists.
|
|
201
|
+
const SIZING_EXTRAS: Record<string, string> = {
|
|
202
|
+
'auto': 'auto',
|
|
203
|
+
'full': '100%',
|
|
204
|
+
'min': 'min-content',
|
|
205
|
+
'max': 'max-content',
|
|
206
|
+
'fit': 'fit-content',
|
|
207
|
+
'px': '1px',
|
|
208
|
+
'1/2': '50%',
|
|
209
|
+
'1/3': '33.333333%',
|
|
210
|
+
'2/3': '66.666667%',
|
|
211
|
+
'1/4': '25%',
|
|
212
|
+
'3/4': '75%',
|
|
213
|
+
'1/5': '20%',
|
|
214
|
+
'2/5': '40%',
|
|
215
|
+
'3/5': '60%',
|
|
216
|
+
'4/5': '80%',
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const palette = colors()
|
|
220
|
+
const scale = spacing()
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
backgroundColor: palette,
|
|
224
|
+
textColor: palette,
|
|
225
|
+
borderColor: palette,
|
|
226
|
+
padding: scale,
|
|
227
|
+
margin: scale,
|
|
228
|
+
width: { ...scale, ...SIZING_EXTRAS, screen: '100vw' },
|
|
229
|
+
height: { ...scale, ...SIZING_EXTRAS, screen: '100vh' },
|
|
230
|
+
fontSize: group('--text', /--line-height$|--letter-spacing$|--font-weight$/),
|
|
231
|
+
fontWeight: group('--font-weight'),
|
|
232
|
+
fontFamily: group('--font', /^--font-weight-/),
|
|
233
|
+
letterSpacing: group('--tracking'),
|
|
234
|
+
lineHeight: group('--leading'),
|
|
235
|
+
dropShadow: group('--drop-shadow'),
|
|
236
|
+
borderRadius: group('--radius'),
|
|
237
|
+
// v4 has no border-width namespace; these are the built-in static utilities.
|
|
238
|
+
borderWidth: { DEFAULT: '1px', 0: '0px', 2: '2px', 4: '4px', 8: '8px' },
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
86
242
|
function storyTemplate(tailwindConfig: any) {
|
|
87
243
|
return `
|
|
88
244
|
import 'poveste-style'
|
|
@@ -134,7 +290,7 @@ export default {
|
|
|
134
290
|
onMount: (api) => mountApp(api, () => Object.entries(config.theme.backgroundColor).map(([key, shades]) => h(HstColorShades, {
|
|
135
291
|
key,
|
|
136
292
|
shades: typeof shades === 'object' ? shades : { DEFAULT: shades },
|
|
137
|
-
getName: shade =>
|
|
293
|
+
getName: shade => '' + (shade === 'DEFAULT' ? \`bg-\${key}\` : \`bg-\${key}-\${shade}\`),
|
|
138
294
|
search: search.value,
|
|
139
295
|
}, ({ color}) => h('div', {
|
|
140
296
|
class: '__pvt-shade',
|
|
@@ -157,7 +313,7 @@ export default {
|
|
|
157
313
|
onMount: (api) => mountApp(api, () => Object.entries(config.theme.textColor).map(([key, shades]) => h(HstColorShades, {
|
|
158
314
|
key,
|
|
159
315
|
shades: typeof shades === 'object' ? shades : { DEFAULT: shades },
|
|
160
|
-
getName: shade =>
|
|
316
|
+
getName: shade => '' + (shade === 'DEFAULT' ? \`text-\${key}\` : \`text-\${key}-\${shade}\`),
|
|
161
317
|
search: search.value,
|
|
162
318
|
}, ({ color}) => h('div', {
|
|
163
319
|
class: '__pvt-shade __pvt-text',
|
|
@@ -180,7 +336,7 @@ export default {
|
|
|
180
336
|
onMount: (api) => mountApp(api, () => Object.entries(config.theme.borderColor).map(([key, shades]) => h(HstColorShades, {
|
|
181
337
|
key,
|
|
182
338
|
shades: typeof shades === 'object' ? shades : { DEFAULT: shades },
|
|
183
|
-
getName: shade =>
|
|
339
|
+
getName: shade => '' + (shade === 'DEFAULT' ? \`border-\${key}\` : \`border-\${key}-\${shade}\`),
|
|
184
340
|
search: search.value,
|
|
185
341
|
}, ({ color}) => h('div', {
|
|
186
342
|
class: '__pvt-shade __pvt-border',
|
|
@@ -202,7 +358,7 @@ export default {
|
|
|
202
358
|
icon: 'carbon:area',
|
|
203
359
|
onMount: (api) => mountApp(api, () => h(HstTokenList, {
|
|
204
360
|
tokens: config.theme.padding,
|
|
205
|
-
getName: key =>
|
|
361
|
+
getName: key => \`p-\${key}\`,
|
|
206
362
|
}, ({ token }) => h('div', {
|
|
207
363
|
class: '__pvt-padding',
|
|
208
364
|
style: {
|
|
@@ -220,7 +376,7 @@ export default {
|
|
|
220
376
|
icon: 'carbon:area',
|
|
221
377
|
onMount: (api) => mountApp(api, () => h(HstTokenList, {
|
|
222
378
|
tokens: config.theme.margin,
|
|
223
|
-
getName: key =>
|
|
379
|
+
getName: key => \`m-\${key}\`,
|
|
224
380
|
}, ({ token }) => h('div', {
|
|
225
381
|
class: '__pvt-margin',
|
|
226
382
|
}, [
|
|
@@ -238,7 +394,7 @@ export default {
|
|
|
238
394
|
icon: 'carbon:text-font',
|
|
239
395
|
onMount: (api) => mountApp(api, () => h(HstTokenList, {
|
|
240
396
|
tokens: config.theme.fontSize,
|
|
241
|
-
getName: key =>
|
|
397
|
+
getName: key => \`text-\${key}\`,
|
|
242
398
|
}, ({ token }) => h('div', {
|
|
243
399
|
class: '__pvt-truncate',
|
|
244
400
|
style: {
|
|
@@ -261,7 +417,7 @@ export default {
|
|
|
261
417
|
icon: 'carbon:text-font',
|
|
262
418
|
onMount: (api) => mountApp(api, () => h(HstTokenList, {
|
|
263
419
|
tokens: config.theme.fontWeight,
|
|
264
|
-
getName: key =>
|
|
420
|
+
getName: key => \`font-\${key}\`,
|
|
265
421
|
}, ({ token }) => h('div', {
|
|
266
422
|
class: '__pvt-truncate',
|
|
267
423
|
style: {
|
|
@@ -290,7 +446,7 @@ export default {
|
|
|
290
446
|
icon: 'carbon:text-font',
|
|
291
447
|
onMount: (api) => mountApp(api, () => h(HstTokenList, {
|
|
292
448
|
tokens: config.theme.fontFamily,
|
|
293
|
-
getName: key =>
|
|
449
|
+
getName: key => \`font-\${key}\`,
|
|
294
450
|
}, ({ token }) => h('div', {
|
|
295
451
|
class: '__pvt-truncate',
|
|
296
452
|
style: {
|
|
@@ -319,7 +475,7 @@ export default {
|
|
|
319
475
|
icon: 'carbon:text-font',
|
|
320
476
|
onMount: (api) => mountApp(api, () => h(HstTokenList, {
|
|
321
477
|
tokens: config.theme.letterSpacing,
|
|
322
|
-
getName: key =>
|
|
478
|
+
getName: key => \`tracking-\${key}\`,
|
|
323
479
|
}, ({ token }) => h('div', {
|
|
324
480
|
class: '__pvt-truncate',
|
|
325
481
|
style: {
|
|
@@ -348,7 +504,7 @@ export default {
|
|
|
348
504
|
icon: 'carbon:text-font',
|
|
349
505
|
onMount: (api) => mountApp(api, () => h(HstTokenList, {
|
|
350
506
|
tokens: config.theme.lineHeight,
|
|
351
|
-
getName: key =>
|
|
507
|
+
getName: key => \`leading-\${key}\`,
|
|
352
508
|
}, ({ token }) => h('div', {
|
|
353
509
|
style: {
|
|
354
510
|
lineHeight: token.value,
|
|
@@ -370,7 +526,7 @@ export default {
|
|
|
370
526
|
icon: 'carbon:shape-except',
|
|
371
527
|
onMount: (api) => mountApp(api, () => h(HstTokenGrid, {
|
|
372
528
|
tokens: config.theme.dropShadow,
|
|
373
|
-
getName: key =>
|
|
529
|
+
getName: key => '' + (key === 'DEFAULT' ? 'drop-shadow' : \`drop-shadow-\${key}\`),
|
|
374
530
|
colSize: 180,
|
|
375
531
|
}, ({ token }) => h('div', {
|
|
376
532
|
class: '__pvt-drop-shadow',
|
|
@@ -385,7 +541,7 @@ export default {
|
|
|
385
541
|
icon: 'carbon:condition-wait-point',
|
|
386
542
|
onMount: (api) => mountApp(api, () => h(HstTokenGrid, {
|
|
387
543
|
tokens: config.theme.borderRadius,
|
|
388
|
-
getName: key =>
|
|
544
|
+
getName: key => '' + (key === 'DEFAULT' ? 'rounded' : \`rounded-\${key}\`),
|
|
389
545
|
colSize: 180,
|
|
390
546
|
}, ({ token }) => h('div', {
|
|
391
547
|
class: '__pvt-border-radius',
|
|
@@ -400,7 +556,7 @@ export default {
|
|
|
400
556
|
icon: 'carbon:checkbox',
|
|
401
557
|
onMount: (api) => mountApp(api, () => h(HstTokenGrid, {
|
|
402
558
|
tokens: config.theme.borderWidth,
|
|
403
|
-
getName: key =>
|
|
559
|
+
getName: key => '' + (key === 'DEFAULT' ? 'border' : \`border-\${key}\`),
|
|
404
560
|
colSize: 180,
|
|
405
561
|
}, ({ token }) => h('div', {
|
|
406
562
|
class: '__pvt-border-width',
|
|
@@ -415,7 +571,7 @@ export default {
|
|
|
415
571
|
icon: 'carbon:pan-horizontal',
|
|
416
572
|
onMount: (api) => mountApp(api, () => h(HstTokenList, {
|
|
417
573
|
tokens: config.theme.width,
|
|
418
|
-
getName: key =>
|
|
574
|
+
getName: key => '' + (key === 'DEFAULT' ? 'w' : \`w-\${key}\`),
|
|
419
575
|
}, ({ token }) => h('div', {
|
|
420
576
|
class: '__pvt-width',
|
|
421
577
|
}, [
|
|
@@ -433,7 +589,7 @@ export default {
|
|
|
433
589
|
icon: 'carbon:pan-vertical',
|
|
434
590
|
onMount: (api) => mountApp(api, () => h(HstTokenList, {
|
|
435
591
|
tokens: config.theme.height,
|
|
436
|
-
getName: key =>
|
|
592
|
+
getName: key => '' + (key === 'DEFAULT' ? 'h' : \`h-\${key}\`),
|
|
437
593
|
}, ({ token }) => h('div', {
|
|
438
594
|
class: '__pvt-height',
|
|
439
595
|
style: {
|
|
@@ -127,9 +127,7 @@ export function useCollectStories(options: UseCollectStoriesOptions, ctx: Contex
|
|
|
127
127
|
// Default props
|
|
128
128
|
if (ctx.config.defaultStoryProps) {
|
|
129
129
|
for (const key in ctx.config.defaultStoryProps) {
|
|
130
|
-
|
|
131
|
-
finalData[key] = ctx.config.defaultStoryProps[key]
|
|
132
|
-
}
|
|
130
|
+
finalData[key] ??= ctx.config.defaultStoryProps[key]
|
|
133
131
|
}
|
|
134
132
|
}
|
|
135
133
|
|
package/src/node/config.ts
CHANGED
|
@@ -265,6 +265,9 @@ export async function resolveConfig(cwd: string = process.cwd(), mode: ConfigMod
|
|
|
265
265
|
result = await loadConfigFile(resolvedConfigFile)
|
|
266
266
|
}
|
|
267
267
|
const viteConfig = await resolveViteConfig({}, 'serve')
|
|
268
|
+
// The `histoire` key is our own deprecation, and reading it is the whole
|
|
269
|
+
// point — it is what makes poveste drop-in for an existing histoire config.
|
|
270
|
+
// eslint-disable-next-line ts/no-deprecated
|
|
268
271
|
const vitePovesteConfig = (viteConfig.poveste ?? viteConfig.histoire ?? {}) as PovesteConfig
|
|
269
272
|
|
|
270
273
|
const preUserConfig = mergeConfig(result, vitePovesteConfig)
|
package/src/node/dom/env.ts
CHANGED
|
@@ -8,12 +8,16 @@ import { populateGlobal } from './util.js'
|
|
|
8
8
|
const JSDOM_ERROR_CSS_PARSING = 'css-parsing'
|
|
9
9
|
const JSDOM_ERROR_UNHANDLED_EXCEPTION = 'unhandled-exception'
|
|
10
10
|
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
11
|
+
// CSS jsdom's parser can't make sense of makes it emit a jsdomError per
|
|
12
|
+
// stylesheet, so collecting stories from an app with such a stylesheet prints
|
|
13
|
+
// "Could not parse CSS stylesheet" over and over. The parser recovers on its
|
|
14
|
+
// own and keeps the valid rules, so the message is pure noise: drop
|
|
15
|
+
// css-parsing errors, mirror jsdom's default forwardTo for the rest.
|
|
16
|
+
//
|
|
17
|
+
// jsdom 28 moved to css-tree, which recovers browser-style and stays quiet for
|
|
18
|
+
// most of what jsdom 27 complained about — Tailwind v4's `@theme`, unknown
|
|
19
|
+
// at-rules, unterminated strings. It still reports some inputs (an
|
|
20
|
+
// unparseable selector, for one), so this stays.
|
|
17
21
|
function createVirtualConsole(): VirtualConsole | undefined {
|
|
18
22
|
if (!console || !globalThis.console) return undefined
|
|
19
23
|
const virtualConsole = new VirtualConsole().forwardTo(globalThis.console, { jsdomErrors: 'none' })
|
package/src/node/index.ts
CHANGED