poveste 0.2.1 → 0.3.0
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/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/entry-css-merger.ts +10 -9
- package/src/node/vite.ts +4 -4
- package/tsconfig.json +1 -1
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.0",
|
|
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/shared": "^0.
|
|
74
|
-
"@poveste/vendors": "^0.
|
|
70
|
+
"vite-node": "^6.0.0",
|
|
71
|
+
"@poveste/app": "^0.3.0",
|
|
72
|
+
"@poveste/controls": "^0.3.0",
|
|
73
|
+
"@poveste/shared": "^0.3.0",
|
|
74
|
+
"@poveste/vendors": "^0.3.0"
|
|
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
package/src/node/markdown.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ServerMarkdownFile } from '@poveste/shared'
|
|
2
|
+
import type { Highlighter } from 'shiki'
|
|
2
3
|
import type { Plugin as VitePlugin } from 'vite'
|
|
3
4
|
import type { Context } from './context.js'
|
|
4
5
|
import { kebabCase } from 'change-case'
|
|
@@ -28,14 +29,35 @@ function notifyMarkdownListChange() {
|
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Shared between the markdown Vite plugin and the markdown file watcher, which
|
|
34
|
+
* both build a renderer — a `poveste build` reaches this three times. The
|
|
35
|
+
* themes and langs are constant, and each instance eagerly loads every bundled
|
|
36
|
+
* grammar, so the duplicates are pure waste.
|
|
37
|
+
*/
|
|
38
|
+
let highlighterPromise: Promise<Highlighter> | undefined
|
|
39
|
+
|
|
40
|
+
function getHighlighter() {
|
|
41
|
+
highlighterPromise ??= createHighlighter({
|
|
33
42
|
themes: ['github-dark'],
|
|
34
43
|
langs: Object.keys(bundledLanguages), // not ideal but markdown-it does not provide async highlight
|
|
35
44
|
})
|
|
36
45
|
|
|
46
|
+
return highlighterPromise
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function createMarkdownRenderer(ctx: Context) {
|
|
50
|
+
const highlighter = await getHighlighter()
|
|
51
|
+
|
|
37
52
|
const md = new MarkdownIt({
|
|
38
|
-
|
|
53
|
+
/*
|
|
54
|
+
* Unprefixed utilities: the `ptw-` prefix went away with Tailwind v4, and
|
|
55
|
+
* `not-prose` in particular is matched by the typography plugin's own
|
|
56
|
+
* `[class~="not-prose"]` selector, so a prefixed spelling silently opts the
|
|
57
|
+
* block back *into* prose. These classes are generated because main.pcss
|
|
58
|
+
* `@source`s this file — v4 auto-detection only scans the app package.
|
|
59
|
+
*/
|
|
60
|
+
highlight: (code, lang) => `<div class="relative not-prose __poveste-code __histoire-code"><div class="absolute top-0 right-0 text-xs text-white/40">${lang}</div>${highlighter.codeToHtml(code, { theme: 'github-dark', lang })}</div>`,
|
|
39
61
|
linkify: true,
|
|
40
62
|
html: true,
|
|
41
63
|
breaks: false,
|
package/src/node/stories.ts
CHANGED
|
@@ -76,7 +76,7 @@ export async function watchStories(newContext: Context) {
|
|
|
76
76
|
watcher
|
|
77
77
|
.on('add', (file) => {
|
|
78
78
|
const storyFile = addStory(file)
|
|
79
|
-
setTimeout(
|
|
79
|
+
setTimeout(notifyStoryChange, 100, storyFile) // Delay in case file renaming fired Add event before Unlink event
|
|
80
80
|
})
|
|
81
81
|
.on('unlink', (file) => {
|
|
82
82
|
removeStory(file)
|