@signal9/era-ui 32.0.0 → 33.0.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.
Files changed (145) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/dev/audit-overlay.svelte +2 -2
  3. package/dist/era-ui.css +1 -1
  4. package/dist/generated-docs/utilities.json +99 -99
  5. package/dist/os/pane.svelte +3 -13
  6. package/dist/styles/index.css +2 -956
  7. package/dist/styles/typography.css +1 -1
  8. package/dist/styles/utilities/geometry.css +188 -0
  9. package/dist/styles/utilities/interaction.css +334 -0
  10. package/dist/styles/utilities/materials.css +39 -0
  11. package/dist/styles/utilities/public.css +391 -0
  12. package/dist/styles/utilities.css +5 -0
  13. package/dist/ui/card/card.svelte +2 -52
  14. package/dist/ui/card/card.svelte.d.ts +0 -43
  15. package/dist/ui/card/variants.d.ts +44 -0
  16. package/dist/ui/card/variants.js +20 -0
  17. package/dist/ui/context-menu/context-menu-checkbox-item.svelte +2 -4
  18. package/dist/ui/context-menu/context-menu-group-heading.svelte +2 -5
  19. package/dist/ui/context-menu/context-menu-item.svelte +2 -8
  20. package/dist/ui/context-menu/context-menu-radio-item.svelte +2 -8
  21. package/dist/ui/context-menu/context-menu-separator.svelte +2 -5
  22. package/dist/ui/context-menu/context-menu-sub-trigger.svelte +2 -8
  23. package/dist/ui/dialog/dialog-header.svelte +1 -1
  24. package/dist/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +2 -4
  25. package/dist/ui/dropdown-menu/dropdown-menu-group-heading.svelte +2 -5
  26. package/dist/ui/dropdown-menu/dropdown-menu-item.svelte +2 -8
  27. package/dist/ui/dropdown-menu/dropdown-menu-radio-item.svelte +2 -8
  28. package/dist/ui/dropdown-menu/dropdown-menu-separator.svelte +2 -5
  29. package/dist/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +2 -8
  30. package/dist/ui/menu-recipes.d.ts +6 -0
  31. package/dist/ui/menu-recipes.js +6 -0
  32. package/dist/ui/menubar/menubar-checkbox-item.svelte +2 -9
  33. package/dist/ui/menubar/menubar-group-heading.svelte +2 -5
  34. package/dist/ui/menubar/menubar-item.svelte +2 -8
  35. package/dist/ui/menubar/menubar-radio-item.svelte +2 -8
  36. package/dist/ui/menubar/menubar-separator.svelte +2 -5
  37. package/dist/ui/menubar/menubar-sub-trigger.svelte +2 -8
  38. package/dist/ui/pane/pane-close.svelte +7 -26
  39. package/dist/ui/video-player/video-player.svelte +8 -8
  40. package/package.json +48 -12
  41. package/dist/docs/badge.md +0 -18
  42. package/dist/docs/bits-ui-audit.md +0 -58
  43. package/dist/docs/button.md +0 -19
  44. package/dist/docs/chip.md +0 -20
  45. package/dist/docs/component-docs-core.d.ts +0 -8
  46. package/dist/docs/component-docs-core.js +0 -73
  47. package/dist/docs/component-docs.d.ts +0 -2
  48. package/dist/docs/component-docs.js +0 -25
  49. package/dist/docs/component-source-scanners.d.ts +0 -18
  50. package/dist/docs/component-source-scanners.js +0 -46
  51. package/dist/docs/consumer-import.d.ts +0 -65
  52. package/dist/docs/consumer-import.js +0 -70
  53. package/dist/docs/css-utilities.d.ts +0 -58
  54. package/dist/docs/css-utilities.js +0 -317
  55. package/dist/docs/extract-props.d.ts +0 -13
  56. package/dist/docs/extract-props.js +0 -324
  57. package/dist/docs/generated-docs.d.ts +0 -6
  58. package/dist/docs/generated-docs.js +0 -30
  59. package/dist/docs/index.d.ts +0 -5
  60. package/dist/docs/index.js +0 -66
  61. package/dist/docs/notes.md +0 -83
  62. package/dist/docs/scroll-area.md +0 -7
  63. package/dist/docs/separator.md +0 -20
  64. package/dist/docs/table.md +0 -35
  65. package/dist/docs/utilities.d.ts +0 -4
  66. package/dist/docs/utilities.js +0 -15
  67. package/dist/generated-docs/accordion.md +0 -57
  68. package/dist/generated-docs/alert-dialog.md +0 -91
  69. package/dist/generated-docs/aspect-ratio.md +0 -17
  70. package/dist/generated-docs/avatar.md +0 -39
  71. package/dist/generated-docs/badge.md +0 -41
  72. package/dist/generated-docs/bar.md +0 -41
  73. package/dist/generated-docs/button-group.md +0 -19
  74. package/dist/generated-docs/button.md +0 -117
  75. package/dist/generated-docs/calendar.md +0 -126
  76. package/dist/generated-docs/card.md +0 -58
  77. package/dist/generated-docs/checkbox.md +0 -42
  78. package/dist/generated-docs/chip.md +0 -61
  79. package/dist/generated-docs/code-block.md +0 -20
  80. package/dist/generated-docs/collapsible.md +0 -30
  81. package/dist/generated-docs/combobox.md +0 -74
  82. package/dist/generated-docs/command-bar.md +0 -3
  83. package/dist/generated-docs/command.md +0 -126
  84. package/dist/generated-docs/context-menu.md +0 -104
  85. package/dist/generated-docs/copy-button.md +0 -29
  86. package/dist/generated-docs/cycle.md +0 -29
  87. package/dist/generated-docs/date-field.md +0 -49
  88. package/dist/generated-docs/date-picker.md +0 -180
  89. package/dist/generated-docs/date-range-field.md +0 -50
  90. package/dist/generated-docs/date-range-picker.md +0 -180
  91. package/dist/generated-docs/dialog.md +0 -94
  92. package/dist/generated-docs/draggable.md +0 -3
  93. package/dist/generated-docs/dropdown-menu.md +0 -104
  94. package/dist/generated-docs/file-upload.md +0 -18
  95. package/dist/generated-docs/getting-started.md +0 -3
  96. package/dist/generated-docs/input.md +0 -23
  97. package/dist/generated-docs/kv.md +0 -20
  98. package/dist/generated-docs/label.md +0 -17
  99. package/dist/generated-docs/link-preview.md +0 -40
  100. package/dist/generated-docs/llm-shell.md +0 -3
  101. package/dist/generated-docs/llms-full.txt +0 -5201
  102. package/dist/generated-docs/llms.txt +0 -138
  103. package/dist/generated-docs/logo.md +0 -24
  104. package/dist/generated-docs/manifest.json +0 -1685
  105. package/dist/generated-docs/measurements.md +0 -3
  106. package/dist/generated-docs/menu.md +0 -11
  107. package/dist/generated-docs/menubar.md +0 -104
  108. package/dist/generated-docs/meter.md +0 -101
  109. package/dist/generated-docs/mode.md +0 -24
  110. package/dist/generated-docs/nav.md +0 -73
  111. package/dist/generated-docs/navigation-menu.md +0 -11
  112. package/dist/generated-docs/notes.md +0 -87
  113. package/dist/generated-docs/os.md +0 -3
  114. package/dist/generated-docs/pagination.md +0 -55
  115. package/dist/generated-docs/pane.md +0 -34
  116. package/dist/generated-docs/pin-input.md +0 -31
  117. package/dist/generated-docs/popover.md +0 -43
  118. package/dist/generated-docs/progress.md +0 -17
  119. package/dist/generated-docs/radio-group.md +0 -31
  120. package/dist/generated-docs/range-calendar.md +0 -11
  121. package/dist/generated-docs/rating-group.md +0 -31
  122. package/dist/generated-docs/scroll-area.md +0 -65
  123. package/dist/generated-docs/select.md +0 -67
  124. package/dist/generated-docs/separator.md +0 -57
  125. package/dist/generated-docs/sheet.md +0 -11
  126. package/dist/generated-docs/skeleton.md +0 -18
  127. package/dist/generated-docs/slider.md +0 -71
  128. package/dist/generated-docs/spacing.md +0 -3
  129. package/dist/generated-docs/step.md +0 -51
  130. package/dist/generated-docs/surfaces.md +0 -3
  131. package/dist/generated-docs/switch.md +0 -18
  132. package/dist/generated-docs/table.md +0 -113
  133. package/dist/generated-docs/tabs.md +0 -48
  134. package/dist/generated-docs/text.md +0 -3
  135. package/dist/generated-docs/time-field.md +0 -49
  136. package/dist/generated-docs/time-range-field.md +0 -50
  137. package/dist/generated-docs/timeline.md +0 -32
  138. package/dist/generated-docs/todo.md +0 -3
  139. package/dist/generated-docs/toggle-group.md +0 -31
  140. package/dist/generated-docs/toggle.md +0 -20
  141. package/dist/generated-docs/toolbar.md +0 -11
  142. package/dist/generated-docs/tooltip.md +0 -40
  143. package/dist/generated-docs/tree.md +0 -36
  144. package/dist/generated-docs/utilities.md +0 -1236
  145. package/dist/generated-docs/video-player.md +0 -73
@@ -1,317 +0,0 @@
1
- /*
2
- * Extracts era's Tailwind v4 `@utility` classes — era-link, era-text-trim,
3
- * era-shimmer, scrollbar-none, material-popover, … — from the stylesheet source.
4
- *
5
- * These are first-class API but they have no JS export and no .svelte file, so
6
- * nothing else in the doc pipeline can see them: the component barrel misses
7
- * them, the registry misses them, and a consumer (or an agent) reasoning from
8
- * "what does era expose for links?" never finds `era-link`. The doc comment
9
- * above each `@utility` is already thorough, so it — not a hand-maintained list
10
- * — is the source of truth. This parses it.
11
- *
12
- * The convention the parser leans on: an `@use ` line in the comment is the
13
- * one-line use case (what an agent searches), and the remaining prose is the
14
- * long description. A utility with no `@use` line still documents fine; it just
15
- * lands in the docs without a use-case summary.
16
- *
17
- * Pure (takes sources, returns data) so both the runtime glob loader and the
18
- * node build script can drive it — same split as component-source-scanners.ts.
19
- */
20
- /** Files whose utilities are library API. Order fixes the doc's order. */
21
- const FILE_ORDER = ['index.css'];
22
- /**
23
- * `@theme` namespace → the utility prefixes it generates.
24
- *
25
- * Half of era's public class API is registered this way rather than as
26
- * `@utility` blocks — `rounded-control` from `--radius-control`, `shadow-lg`,
27
- * `bg-highlight`, `text-body`, and the whole spacing scale. They are classes a
28
- * consumer writes exactly like the others, and until this table existed the
29
- * doc pipeline could not see any of them: `@utility` was the only thing it
30
- * parsed, so era's most-used names were also its least-documented.
31
- *
32
- * The list per namespace is the SPELLINGS WORTH DOCUMENTING, not everything
33
- * Tailwind derives — a spacing token really does generate `mt-`, `gap-x-`,
34
- * `inset-`, `translate-` and a dozen more, and printing all of them per name
35
- * would bury the point. The prose says the rest.
36
- */
37
- export const THEME_CLASS_PREFIXES = {
38
- radius: ['rounded'],
39
- shadow: ['shadow'],
40
- text: ['text'],
41
- leading: ['leading'],
42
- ease: ['ease'],
43
- color: ['bg', 'text', 'border'],
44
- spacing: ['p', 'px', 'py', 'gap']
45
- };
46
- /** Every prefix a namespace really generates — the map the codemod needs. */
47
- export const THEME_ALL_PREFIXES = {
48
- ...THEME_CLASS_PREFIXES,
49
- radius: [
50
- 'rounded',
51
- ...'t,r,b,l,tl,tr,bl,br,s,e,ss,se,es,ee'.split(',').map((s) => `rounded-${s}`)
52
- ],
53
- color: [
54
- 'bg',
55
- 'text',
56
- 'border',
57
- 'fill',
58
- 'stroke',
59
- 'ring',
60
- 'outline',
61
- 'divide',
62
- 'decoration',
63
- 'caret',
64
- 'accent',
65
- 'from',
66
- 'via',
67
- 'to'
68
- ],
69
- spacing: [
70
- 'p,px,py,pt,pr,pb,pl,ps,pe',
71
- 'm,mx,my,mt,mr,mb,ml,ms,me',
72
- 'gap,gap-x,gap-y',
73
- 'w,h,size,min-w,min-h,max-w,max-h',
74
- 'inset,inset-x,inset-y,top,right,bottom,left,start,end',
75
- 'translate,translate-x,translate-y,scroll-m,scroll-p,basis,indent'
76
- ]
77
- .join(',')
78
- .split(',')
79
- };
80
- function cleanComment(raw) {
81
- return raw
82
- .split('\n')
83
- .map((line) => line.replace(/^\s*\*\s?/, '').trimEnd())
84
- .join('\n')
85
- .trim();
86
- }
87
- /** Split the comment into its `@use` one-liner and the surrounding prose. */
88
- function splitUseCase(comment) {
89
- const lines = comment.split('\n');
90
- const start = lines.findIndex((line) => line.startsWith('@use '));
91
- if (start === -1)
92
- return { description: comment.trim() };
93
- let end = start + 1;
94
- while (end < lines.length && lines[end].trim() !== '')
95
- end++;
96
- const useCase = lines
97
- .slice(start, end)
98
- .join(' ')
99
- .replace(/^@use\s+/, '')
100
- .replace(/\s+/g, ' ')
101
- .trim();
102
- const description = [...lines.slice(0, start), ...lines.slice(end)].join('\n').trim();
103
- return { useCase, description };
104
- }
105
- /** Walk from the opening brace to its match so nested rules survive intact. */
106
- function blockEnd(source, openBrace) {
107
- let depth = 0;
108
- for (let i = openBrace; i < source.length; i++) {
109
- if (source[i] === '{')
110
- depth++;
111
- else if (source[i] === '}' && --depth === 0)
112
- return i;
113
- }
114
- return source.length - 1;
115
- }
116
- function parseFile(file, source) {
117
- const out = [];
118
- // The comment body may not contain a terminator — a lazy `[\s\S]*?` would
119
- // happily start at the first comment in the file and run through every `*/`
120
- // between there and the @utility.
121
- const pattern = /\/\*((?:(?!\*\/)[\s\S])*)\*\/\s*@utility\s+([\w-]+)\s*\{/g;
122
- for (const match of source.matchAll(pattern)) {
123
- const names = [match[2]];
124
- let end = blockEnd(source, match.index + match[0].length - 1);
125
- // One doc comment may govern a RUN of consecutive @utility blocks — a
126
- // ladder like h-icon…h-bar shares one story, and repeating it five times
127
- // would drown the docs. Attach every block that follows the previous one
128
- // with nothing but whitespace in between; the next commented block never
129
- // matches here because the outer pattern requires the comment.
130
- const next = /^\s*@utility\s+([\w-]+)\s*\{/;
131
- for (let m; (m = next.exec(source.slice(end + 1)));) {
132
- names.push(m[1]);
133
- end = blockEnd(source, end + 1 + m[0].length - 1);
134
- }
135
- const { useCase, description } = splitUseCase(cleanComment(match[1]));
136
- out.push({
137
- name: names[0],
138
- names,
139
- file,
140
- useCase,
141
- description,
142
- css: source.slice(source.indexOf('@utility', match.index), end + 1)
143
- });
144
- }
145
- return out;
146
- }
147
- /**
148
- * The classes one `@theme` key generates, each with the utility PREFIX that
149
- * generated it — `--spacing-inset-control` → `{ prefix: 'px', name:
150
- * 'px-inset-control' }`. The prefix cannot be recovered from the name (both
151
- * `px` and `px-inset` are hyphen-splits of `px-inset-control`), and the codemod
152
- * matches on it, so it is returned rather than re-derived.
153
- */
154
- export function themeKeyClassEntries(themeKey, prefixes = THEME_CLASS_PREFIXES) {
155
- const bare = themeKey.replace(/^--/, '');
156
- // Tailwind's metadata sub-keys (`--text-title--line-height`) configure a
157
- // scale entry; they are not classes of their own, and listing them as such
158
- // put `text-title--letter-spacing` in the reference as if a consumer could
159
- // write it.
160
- if (bare.includes('--'))
161
- return [];
162
- // `--shadow: …` IS the bare `shadow` class — a namespace with no suffix.
163
- if (prefixes[bare])
164
- return [{ prefix: bare, name: bare }];
165
- const cut = bare.indexOf('-');
166
- if (cut === -1)
167
- return [];
168
- const namespace = bare.slice(0, cut);
169
- const suffix = bare.slice(cut + 1);
170
- return (prefixes[namespace] ?? []).map((p) => ({ prefix: p, name: `${p}-${suffix}` }));
171
- }
172
- /** The class names one `@theme` key generates, e.g. `--radius-control` → `rounded-control`. */
173
- export function themeKeyClasses(themeKey, prefixes = THEME_CLASS_PREFIXES) {
174
- return themeKeyClassEntries(themeKey, prefixes).map((e) => e.name);
175
- }
176
- /**
177
- * Named theme scales — the other half of the public class API.
178
- *
179
- * Shape parsed: a doc comment followed by a RUN of `--key: var(…)` lines inside
180
- * `@theme`, exactly like the `@utility` ladder rule above. One comment governs
181
- * the whole run, so the radius ladder or the spacing scale documents once.
182
- */
183
- function parseTheme(file, source) {
184
- // Anchored to the start of a line: `@theme` also appears in PROSE (the layering
185
- // comment in index.css explains that @utility and @theme are Tailwind's to
186
- // place), and an unanchored match happily started inside that comment, ran to
187
- // the next `{` it found, and returned garbage — silently dropping every
188
- // theme-derived class from the docs.
189
- const theme = source.match(/^@theme[^{]*\{([\s\S]*?)\n\}/m);
190
- if (!theme)
191
- return [];
192
- const body = theme[1];
193
- const out = [];
194
- const pattern = /\/\*((?:(?!\*\/)[\s\S])*)\*\/\s*((?:\s*--[\w-]+\s*:[^;]+;\n?)+)/g;
195
- for (const match of body.matchAll(pattern)) {
196
- const decls = [...match[2].matchAll(/--[\w-]+\s*:[^;]+;/g)].map((m) => m[0]);
197
- const names = decls.flatMap((d) => themeKeyClasses(d.slice(0, d.indexOf(':')).trim()));
198
- // A comment governing only non-class keys (--default-transition-*) has
199
- // nothing to document — it is internal wiring, not API.
200
- if (!names.length)
201
- continue;
202
- const { useCase, description } = splitUseCase(cleanComment(match[1]));
203
- out.push({
204
- name: names[0],
205
- names,
206
- file,
207
- useCase,
208
- description,
209
- css: `@theme inline {\n${decls.map((d) => `\t${d}`).join('\n')}\n}`
210
- });
211
- }
212
- return out;
213
- }
214
- /**
215
- * @param sources stylesheet text keyed by path relative to src/lib/styles
216
- * (e.g. `index.css`, `surfaces/glass.css`).
217
- */
218
- export function parseUtilities(sources) {
219
- const rank = (file) => {
220
- const i = FILE_ORDER.indexOf(file);
221
- return i === -1 ? FILE_ORDER.length : i;
222
- };
223
- return Object.entries(sources)
224
- .sort(([a], [b]) => rank(a) - rank(b) || a.localeCompare(b))
225
- .flatMap(([file, source]) => [...parseFile(file, source), ...parseTheme(file, source)]);
226
- }
227
- const INTRO = [
228
- "era's styling API is pure class names — write `tier-control px-gutter shadow`,",
229
- 'never the `h-(--era-h-control)` var-class spelling. Everything below rides along',
230
- 'with the stylesheet and needs no import:',
231
- '',
232
- '```ts',
233
- 'import "@signal9/era-ui/css";',
234
- '```',
235
- '',
236
- '```svelte',
237
- '<button class="tier-control px-inset-control shadow text-body">era styled</button>',
238
- '```',
239
- '',
240
- 'Alongside the `@utility` classes referenced below, era registers named theme',
241
- 'scales that OVERLOAD the stock Tailwind names, so ordinary-looking markup',
242
- 'renders on-design and responds to every axis (density, surface, corners,',
243
- 'motion):',
244
- '',
245
- '| Stock-looking class | What it resolves to |',
246
- '|---|---|',
247
- '| `tier-icon/pill/chip/control/bar` | indivisible bounded height + concentric radius |',
248
- '| `rounded-icon/pill/chip/control/bar` | the concentric radius ladder (collapses on `data-corners="square"`) |',
249
- '| `rounded-menu-item` | menu-row radius, concentric inside a `rounded-control` panel |',
250
- '| `shadow` / `shadow-lg` | resting control edge / floating panel chrome, per surface |',
251
- '| `shadow-well` / `shadow-pressed` / `shadow-highlight` | recessed, pressed/latched, hover-material chrome |',
252
- '| `bg-well` / `bg-elevated` / `bg-highlight` / `bg-overlay` | surface fills: resting, raised, transient feedback, modal scrim |',
253
- '| `transition-*` (bare) | duration + easing default to the motion axis |',
254
- '| `ease-base` / `duration-base` | the motion axis, named explicitly |',
255
- '| `text-body` | density-derived body/control text size |',
256
- '| `p-gutter` / `gap-gutter` / `mt-gutter` / … | the spacing scale — EVERY spacing prefix works |',
257
- '',
258
- 'THE SPACING NAMES IN SIZE ORDER, smallest first, because the names do not',
259
- 'imply it and guessing gets it backwards:',
260
- '',
261
- ' gutter < panel < field < card < content',
262
- '',
263
- 'Measured at `data-mode="balanced"`: gutter 5px, field ~9px, card ~11px,',
264
- 'content ~22px. `gutter` is the TIGHT one — the gap between sibling controls',
265
- "and a rail's own padding. `field` is a text input's side padding, not the",
266
- 'label-to-input gap. Reading them the other way round produces a layout whose',
267
- 'hierarchy is inverted and therefore reads flat.',
268
- '',
269
- 'The spacing names (`gutter`, `card`, `panel`, `content`, `menu`, `field`, the',
270
- '`inset-*` and concentric `*-inset-*` insets, `glyph-*`) are registered as a',
271
- 'Tailwind spacing scale, so every prefix derives automatically: `p-`, `px-`,',
272
- '`pt-`, `mt-`, `gap-`, `gap-x-`, `inset-`, `translate-`, and the rest. The TIER',
273
- 'names (`icon`, `pill`, `chip`, `control`, `bar`) are dimensions, not spacing, so',
274
- 'they are registered per prefix instead — `h-`, `w-`, `min-h-`, `min-w-` and',
275
- '`size-` — and the two vocabularies deliberately share no word. Putting a tier',
276
- 'in the spacing scale would silently make `px-control` mean 24px of padding.',
277
- 'For chrome, use `tier-T`, which binds `h-T` and `rounded-T`; use the separate',
278
- 'dimension/radius names only for width, minimums, unbounded surfaces, or',
279
- 'asymmetric/docked corners.',
280
- '',
281
- 'The var form (`h-(--era-h-control)`) still works and is the escape hatch for a',
282
- 'token with no registered name — `--era-sp`, the press-sink offsets, anything',
283
- 'a component reaches for once. `scripts/check-canonical.ts` fails the build if',
284
- 'source uses it where a name exists.'
285
- ].join('\n');
286
- /**
287
- * `h-icon` for a single class, `h-icon · h-pill · … · h-bar` for a ladder.
288
- *
289
- * Capped, because a theme scale can govern a LOT of spellings: the colour block
290
- * generates bg-/text-/border- for every token, which is ~60 names and reads as
291
- * noise in a heading. The prose under each entry states the full rule.
292
- */
293
- const LABEL_CAP = 8;
294
- const label = (u) => u.names.length > LABEL_CAP
295
- ? `${u.names.slice(0, LABEL_CAP).join(' · ')} · … (${u.names.length} in all)`
296
- : u.names.join(' · ');
297
- /** Renders the utilities reference — the generated `utilities.md` body. */
298
- export function buildUtilitiesDoc(utilities) {
299
- const lines = ['## Overview', '', INTRO, '', '| Utility | Use it for |', '|---|---|'];
300
- for (const u of utilities) {
301
- lines.push(`| ${u.names.map((n) => `\`${n}\``).join(' ')} | ${u.useCase ?? '—'} |`);
302
- }
303
- lines.push('');
304
- for (const u of utilities) {
305
- lines.push(`## ${label(u)}`, '');
306
- if (u.useCase)
307
- lines.push(`**Use it for:** ${u.useCase}`, '');
308
- if (u.description)
309
- lines.push(u.description, '');
310
- lines.push(`Declared in \`${u.file}\`.`, '', '```css', u.css, '```', '');
311
- }
312
- return lines.join('\n');
313
- }
314
- /** One line per utility (or ladder) for llms.txt — plus its searchable use case. */
315
- export function buildUtilitiesIndex(utilities) {
316
- return utilities.map((u) => `- \`${label(u)}\` — ${u.useCase ?? u.description.split('\n')[0]}`);
317
- }
@@ -1,13 +0,0 @@
1
- export interface PropSpec {
2
- name: string;
3
- type: string;
4
- optional: boolean;
5
- default?: string;
6
- bindable?: boolean;
7
- description?: string;
8
- }
9
- export interface ComponentSpec {
10
- inherits: string[];
11
- props: PropSpec[];
12
- }
13
- export declare function extractProps(source: string): ComponentSpec | null;
@@ -1,324 +0,0 @@
1
- export function extractProps(source) {
2
- const script = extractScript(source);
3
- if (!script)
4
- return null;
5
- const propsIdx = script.indexOf('$props(');
6
- if (propsIdx === -1)
7
- return null;
8
- // Walk backwards through balanced brackets to locate `let { … }: … = $props()`.
9
- // We find the `=` directly preceding `$props(`, then the matching `:`, then
10
- // the destructure's closing `}`. Both the `=` and `:` must be at bracket
11
- // depth 0 so we skip past any `=`/`:` buried inside the destructure or
12
- // generic type arguments.
13
- const eqIdx = findPrevTopLevel(script, propsIdx - 1, '=');
14
- if (eqIdx === -1)
15
- return null;
16
- const typeEnd = eqIdx;
17
- const typeStart = findPrevTopLevel(script, typeEnd - 1, ':');
18
- if (typeStart === -1)
19
- return null;
20
- const typeExpr = script.slice(typeStart + 1, typeEnd).trim();
21
- // Before `:` should be the destructure's closing `}`.
22
- let braceEnd = typeStart - 1;
23
- while (braceEnd >= 0 && /\s/.test(script[braceEnd]))
24
- braceEnd--;
25
- if (script[braceEnd] !== '}')
26
- return null;
27
- const braceStart = findMatchingOpen(script, braceEnd);
28
- if (braceStart === -1)
29
- return null;
30
- const destructure = script.slice(braceStart + 1, braceEnd);
31
- const defaults = parseDestructure(destructure);
32
- const { inherits, literal } = parseTypeExpr(typeExpr);
33
- // Merge defaults onto literal members; anything declared as bindable in the
34
- // destructure is marked here too.
35
- for (const prop of literal) {
36
- const d = defaults[prop.name];
37
- if (!d)
38
- continue;
39
- if (d.default !== undefined)
40
- prop.default = d.default;
41
- if (d.bindable)
42
- prop.bindable = true;
43
- }
44
- // If the destructure introduces bindables not visible in the literal (a
45
- // forwarded prop like `value` on Slider.Root), surface them anyway so the
46
- // doc notes binding support.
47
- for (const [name, d] of Object.entries(defaults)) {
48
- if (literal.find((p) => p.name === name))
49
- continue;
50
- if (!d.bindable)
51
- continue;
52
- literal.push({
53
- name,
54
- type: '(forwarded)',
55
- optional: true,
56
- default: d.default,
57
- bindable: true
58
- });
59
- }
60
- return { inherits, props: literal };
61
- }
62
- function extractScript(source) {
63
- // A component may have both a `<script module>` (tv() variants, etc.) and
64
- // the instance `<script>`. $props() lives in the instance block, so scan
65
- // all blocks and prefer the one that actually contains $props(); fall back
66
- // to the last non-module block. Reading only the first block silently
67
- // missed props on module-first components (badge/chip/card/skeleton).
68
- const blocks = [...source.matchAll(/<script([^>]*)>([\s\S]*?)<\/script>/g)];
69
- if (blocks.length === 0)
70
- return null;
71
- const withProps = blocks.find((b) => b[2].includes('$props('));
72
- if (withProps)
73
- return withProps[2];
74
- const instance = blocks.filter((b) => !/\bmodule\b/.test(b[1])).at(-1);
75
- return (instance ?? blocks[0])[2];
76
- }
77
- // If `src[i]` starts a string, template literal, or comment, skip past it and
78
- // return the next index. Otherwise returns `i` unchanged so callers can decide
79
- // what to do with the character.
80
- function skipNonCode(src, i) {
81
- const c = src[i];
82
- // Arrow `=>` is neither a bracket nor an operator we track. Skipping it as a
83
- // unit keeps the lone `>` from being counted as a generic-bracket close
84
- // (which corrupted depth on arrow-typed props like `(d: E) => void`) and
85
- // keeps its `=` from being mistaken for the `= $props()` assignment.
86
- if (c === '=' && src[i + 1] === '>')
87
- return i + 2;
88
- if (c === '"' || c === "'") {
89
- const quote = c;
90
- i++;
91
- while (i < src.length && src[i] !== quote) {
92
- if (src[i] === '\\')
93
- i++;
94
- i++;
95
- }
96
- return i + 1;
97
- }
98
- if (c === '`') {
99
- i++;
100
- while (i < src.length && src[i] !== '`') {
101
- if (src[i] === '\\')
102
- i++;
103
- else if (src[i] === '$' && src[i + 1] === '{') {
104
- i = findMatchingClose(src, i + 1) + 1;
105
- continue;
106
- }
107
- i++;
108
- }
109
- return i + 1;
110
- }
111
- if (c === '/' && src[i + 1] === '/') {
112
- while (i < src.length && src[i] !== '\n')
113
- i++;
114
- return i;
115
- }
116
- if (c === '/' && src[i + 1] === '*') {
117
- i += 2;
118
- while (i < src.length - 1 && !(src[i] === '*' && src[i + 1] === '/'))
119
- i++;
120
- return i + 2;
121
- }
122
- return i;
123
- }
124
- function findMatchingClose(src, openIdx) {
125
- const open = src[openIdx];
126
- const close = { '{': '}', '(': ')', '[': ']', '<': '>' }[open];
127
- if (!close)
128
- return -1;
129
- let depth = 0;
130
- let i = openIdx;
131
- while (i < src.length) {
132
- const next = skipNonCode(src, i);
133
- if (next !== i) {
134
- i = next;
135
- continue;
136
- }
137
- const c = src[i];
138
- if (c === open)
139
- depth++;
140
- else if (c === close) {
141
- depth--;
142
- if (depth === 0)
143
- return i;
144
- }
145
- i++;
146
- }
147
- return -1;
148
- }
149
- function findMatchingOpen(src, closeIdx) {
150
- const close = src[closeIdx];
151
- const open = { '}': '{', ')': '(', ']': '[', '>': '<' }[close];
152
- if (!open)
153
- return -1;
154
- // Rescan forward tracking balance rather than scanning backwards — handling
155
- // string/comment boundaries in reverse is error-prone.
156
- const stack = [];
157
- let i = 0;
158
- while (i <= closeIdx) {
159
- const next = skipNonCode(src, i);
160
- if (next !== i) {
161
- i = next;
162
- continue;
163
- }
164
- const c = src[i];
165
- if (c === open)
166
- stack.push(i);
167
- else if (c === close) {
168
- const o = stack.pop();
169
- if (i === closeIdx)
170
- return o ?? -1;
171
- }
172
- i++;
173
- }
174
- return -1;
175
- }
176
- function findPrevTopLevel(src, end, token) {
177
- // Records only matches at bracket depth 0, skipping nested expressions.
178
- let found = -1;
179
- let depth = 0;
180
- let i = 0;
181
- while (i <= end) {
182
- const next = skipNonCode(src, i);
183
- if (next !== i) {
184
- i = next;
185
- continue;
186
- }
187
- const c = src[i];
188
- if (c === '{' || c === '(' || c === '[' || c === '<')
189
- depth++;
190
- else if (c === '}' || c === ')' || c === ']' || c === '>')
191
- depth--;
192
- else if (depth === 0 && c === token)
193
- found = i;
194
- i++;
195
- }
196
- return found;
197
- }
198
- function splitTopLevel(s, delim) {
199
- const out = [];
200
- let start = 0;
201
- let depth = 0;
202
- let i = 0;
203
- while (i < s.length) {
204
- const next = skipNonCode(s, i);
205
- if (next !== i) {
206
- i = next;
207
- continue;
208
- }
209
- const c = s[i];
210
- if (c === '{' || c === '(' || c === '[' || c === '<')
211
- depth++;
212
- else if (c === '}' || c === ')' || c === ']' || c === '>')
213
- depth--;
214
- else if (depth === 0 && c === delim) {
215
- out.push(s.slice(start, i));
216
- start = i + 1;
217
- }
218
- i++;
219
- }
220
- out.push(s.slice(start));
221
- return out;
222
- }
223
- function parseTypeExpr(typeExpr) {
224
- const branches = splitTopLevel(typeExpr, '&')
225
- .map((b) => b.trim())
226
- .filter(Boolean);
227
- const inherits = [];
228
- const literal = [];
229
- for (const branch of branches) {
230
- if (branch.startsWith('{')) {
231
- const close = findMatchingClose(branch, 0);
232
- if (close === -1)
233
- continue;
234
- literal.push(...parseLiteralMembers(branch.slice(1, close)));
235
- }
236
- else {
237
- inherits.push(branch);
238
- }
239
- }
240
- return { inherits, literal };
241
- }
242
- function parseLiteralMembers(body) {
243
- const entries = splitTopLevel(body, ';')
244
- .map((s) => s.trim())
245
- .filter(Boolean);
246
- const props = [];
247
- for (const entry of entries) {
248
- let description;
249
- let remainder = entry;
250
- const jsdoc = remainder.match(/^\/\*\*([\s\S]*?)\*\/\s*/);
251
- if (jsdoc) {
252
- description = jsdoc[1].replace(/^\s*\*\s?/gm, '').trim();
253
- remainder = remainder.slice(jsdoc[0].length);
254
- }
255
- // Strip any remaining leading line comments.
256
- while (remainder.startsWith('//')) {
257
- const nl = remainder.indexOf('\n');
258
- remainder = nl === -1 ? '' : remainder.slice(nl + 1).trimStart();
259
- }
260
- const m = remainder.match(/^([A-Za-z_$][\w$]*)(\??)\s*:\s*([\s\S]+)$/);
261
- if (!m)
262
- continue;
263
- props.push({
264
- name: m[1],
265
- optional: m[2] === '?',
266
- type: m[3].trim(),
267
- description
268
- });
269
- }
270
- return props;
271
- }
272
- function parseDestructure(body) {
273
- const entries = splitTopLevel(body, ',')
274
- .map((s) => s.trim())
275
- .filter(Boolean);
276
- const out = {};
277
- for (const entry of entries) {
278
- if (entry.startsWith('...'))
279
- continue;
280
- let left = entry;
281
- let defaultVal;
282
- const eq = findTopLevelChar(entry, '=');
283
- if (eq !== -1) {
284
- left = entry.slice(0, eq).trim();
285
- defaultVal = entry.slice(eq + 1).trim();
286
- }
287
- // Strip alias: `class: className` → name is `class`.
288
- const colon = findTopLevelChar(left, ':');
289
- if (colon !== -1)
290
- left = left.slice(0, colon).trim();
291
- const bindable = /^\$bindable\b/.test(defaultVal ?? '');
292
- let normalizedDefault = defaultVal;
293
- if (bindable && defaultVal) {
294
- // `$bindable(null)` → `null`; `$bindable<T>(x)` → `x`.
295
- const paren = defaultVal.indexOf('(');
296
- const close = paren === -1 ? -1 : findMatchingClose(defaultVal, paren);
297
- if (paren !== -1 && close !== -1) {
298
- normalizedDefault = defaultVal.slice(paren + 1, close).trim() || undefined;
299
- }
300
- }
301
- out[left] = { default: normalizedDefault, bindable };
302
- }
303
- return out;
304
- }
305
- function findTopLevelChar(s, ch) {
306
- let depth = 0;
307
- let i = 0;
308
- while (i < s.length) {
309
- const next = skipNonCode(s, i);
310
- if (next !== i) {
311
- i = next;
312
- continue;
313
- }
314
- const c = s[i];
315
- if (c === '{' || c === '(' || c === '[' || c === '<')
316
- depth++;
317
- else if (c === '}' || c === ')' || c === ']' || c === '>')
318
- depth--;
319
- else if (depth === 0 && c === ch)
320
- return i;
321
- i++;
322
- }
323
- return -1;
324
- }
@@ -1,6 +0,0 @@
1
- export declare const docsBySlug: Record<string, string>;
2
- export declare const knownSlugs: Set<string>;
3
- /** Served verbatim at /utilities.json so tooling can enumerate the CSS API. */
4
- export declare const utilitiesJson: string;
5
- export declare const llmsTxtTemplate: string;
6
- export declare const llmsFullTxt: string;
@@ -1,30 +0,0 @@
1
- const markdownFiles = import.meta.glob('../generated-docs/*.md', {
2
- query: '?raw',
3
- import: 'default',
4
- eager: true
5
- });
6
- const textFiles = import.meta.glob('../generated-docs/*.txt', {
7
- query: '?raw',
8
- import: 'default',
9
- eager: true
10
- });
11
- const manifestModules = import.meta.glob('../generated-docs/manifest.json', {
12
- import: 'default',
13
- eager: true
14
- });
15
- const utilitiesModules = import.meta.glob('../generated-docs/utilities.json', {
16
- query: '?raw',
17
- import: 'default',
18
- eager: true
19
- });
20
- function stem(path) {
21
- return path.replace(/^\.\.\/generated-docs\//, '').replace(/\.(md|txt)$/, '');
22
- }
23
- export const docsBySlug = Object.fromEntries(Object.entries(markdownFiles).map(([file, body]) => [stem(file), body]));
24
- const manifest = Object.values(manifestModules)[0];
25
- const manifestSlugs = manifest?.entries?.map((entry) => entry.slug) ?? [];
26
- export const knownSlugs = new Set(manifestSlugs.length ? manifestSlugs : Object.keys(docsBySlug));
27
- /** Served verbatim at /utilities.json so tooling can enumerate the CSS API. */
28
- export const utilitiesJson = utilitiesModules['../generated-docs/utilities.json'] ?? '';
29
- export const llmsTxtTemplate = textFiles['../generated-docs/llms.txt'] ?? '';
30
- export const llmsFullTxt = textFiles['../generated-docs/llms-full.txt'] ?? '';