@astryxdesign/core 0.4.1 → 0.4.2-canary.356d2f9

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 (201) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/dist/Avatar/Avatar.d.ts +4 -1
  3. package/dist/Avatar/Avatar.d.ts.map +1 -1
  4. package/dist/Avatar/Avatar.js +19 -23
  5. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  6. package/dist/AvatarGroup/AvatarGroupOverflow.js +7 -2
  7. package/dist/Button/Button.d.ts.map +1 -1
  8. package/dist/Button/Button.js +9 -7
  9. package/dist/Chat/ChatMessage.d.ts +7 -0
  10. package/dist/Chat/ChatMessage.d.ts.map +1 -1
  11. package/dist/Chat/ChatMessageBubble.d.ts +13 -1
  12. package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
  13. package/dist/Chat/ChatMessageBubble.js +19 -1
  14. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  15. package/dist/CheckboxInput/CheckboxInput.js +5 -0
  16. package/dist/CommandPalette/CommandPaletteFooter.d.ts.map +1 -1
  17. package/dist/CommandPalette/CommandPaletteFooter.js +5 -3
  18. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  19. package/dist/DateTimeInput/DateTimeInput.js +2 -2
  20. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  21. package/dist/DropdownMenu/DropdownMenuSubMenu.js +19 -15
  22. package/dist/HoverCard/HoverCard.d.ts +1 -1
  23. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  24. package/dist/HoverCard/HoverCard.js +5 -13
  25. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  26. package/dist/HoverCard/useHoverCard.js +6 -5
  27. package/dist/InputGroup/groupStyles.d.ts.map +1 -1
  28. package/dist/InputGroup/groupStyles.js +7 -2
  29. package/dist/Layer/layerHost.d.ts +24 -0
  30. package/dist/Layer/layerHost.d.ts.map +1 -0
  31. package/dist/Layer/layerHost.js +79 -0
  32. package/dist/Layer/useLayer.d.ts +14 -6
  33. package/dist/Layer/useLayer.d.ts.map +1 -1
  34. package/dist/Layer/useLayer.js +158 -30
  35. package/dist/NavItem/navItemStyles.stylex.d.ts +17 -5
  36. package/dist/NavItem/navItemStyles.stylex.d.ts.map +1 -1
  37. package/dist/NavItem/navItemStyles.stylex.js +11 -5
  38. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  39. package/dist/RadioList/RadioListItem.js +5 -0
  40. package/dist/SideNav/SideNav.d.ts +7 -9
  41. package/dist/SideNav/SideNav.d.ts.map +1 -1
  42. package/dist/SideNav/SideNav.js +32 -5
  43. package/dist/SideNav/SideNavCollapseButton.d.ts +25 -9
  44. package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
  45. package/dist/SideNav/SideNavCollapseButton.js +39 -15
  46. package/dist/SideNav/SideNavCollapseContext.d.ts +21 -0
  47. package/dist/SideNav/SideNavCollapseContext.d.ts.map +1 -1
  48. package/dist/SideNav/SideNavCollapseContext.js +16 -2
  49. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  50. package/dist/SideNav/SideNavHeading.js +89 -32
  51. package/dist/SideNav/SideNavItem.d.ts +7 -2
  52. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  53. package/dist/SideNav/SideNavItem.js +119 -75
  54. package/dist/SideNav/SideNavSection.d.ts.map +1 -1
  55. package/dist/SideNav/SideNavSection.js +7 -16
  56. package/dist/SideNav/index.d.ts +1 -1
  57. package/dist/SideNav/index.d.ts.map +1 -1
  58. package/dist/Slider/Slider.d.ts.map +1 -1
  59. package/dist/Slider/Slider.js +56 -15
  60. package/dist/Switch/Switch.d.ts.map +1 -1
  61. package/dist/Switch/Switch.js +5 -0
  62. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  63. package/dist/Thumbnail/Thumbnail.js +5 -0
  64. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  65. package/dist/TopNav/TopNavHeading.js +14 -6
  66. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  67. package/dist/TopNav/TopNavMegaMenu.js +25 -93
  68. package/dist/TopNav/TopNavMegaMenuItem.js +1 -1
  69. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  70. package/dist/TopNav/TopNavMenu.js +10 -5
  71. package/dist/astryx.css +40 -8
  72. package/dist/astryx.umd.js +50 -50
  73. package/dist/astryx.umd.js.map +4 -4
  74. package/dist/hooks/containerReveal.stylex.d.ts +71 -4
  75. package/dist/hooks/containerReveal.stylex.d.ts.map +1 -1
  76. package/dist/hooks/containerReveal.stylex.js +57 -5
  77. package/dist/hooks/index.d.ts +2 -2
  78. package/dist/hooks/index.d.ts.map +1 -1
  79. package/dist/hooks/index.js +1 -1
  80. package/dist/hooks/useContainerReveal.d.ts +60 -3
  81. package/dist/hooks/useContainerReveal.d.ts.map +1 -1
  82. package/dist/hooks/useContainerReveal.js +27 -5
  83. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  84. package/dist/hooks/useFocusTrap.js +16 -2
  85. package/dist/hooks/useMenuHover.d.ts +50 -5
  86. package/dist/hooks/useMenuHover.d.ts.map +1 -1
  87. package/dist/hooks/useMenuHover.js +171 -51
  88. package/dist/theme/defineTheme.d.ts +16 -5
  89. package/dist/theme/defineTheme.d.ts.map +1 -1
  90. package/dist/theme/defineTheme.js +44 -48
  91. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  92. package/dist/theme/derivedVarRegistry.js +7 -0
  93. package/dist/theme/expandColorScale.d.ts.map +1 -1
  94. package/dist/theme/expandColorScale.js +1 -0
  95. package/dist/theme/expandMotionScale.d.ts +1 -0
  96. package/dist/theme/expandMotionScale.d.ts.map +1 -1
  97. package/dist/theme/expandMotionScale.js +1 -0
  98. package/dist/theme/expandRadiusScale.d.ts +1 -0
  99. package/dist/theme/expandRadiusScale.d.ts.map +1 -1
  100. package/dist/theme/expandRadiusScale.js +1 -0
  101. package/dist/theme/expandTypeScale.d.ts +1 -0
  102. package/dist/theme/expandTypeScale.d.ts.map +1 -1
  103. package/dist/theme/expandTypeScale.js +1 -0
  104. package/dist/theme/mergeComponents.d.ts +20 -0
  105. package/dist/theme/mergeComponents.d.ts.map +1 -0
  106. package/dist/theme/mergeComponents.js +56 -0
  107. package/dist/theme/onMediaTokens.d.ts +6 -1
  108. package/dist/theme/onMediaTokens.d.ts.map +1 -1
  109. package/dist/theme/onMediaTokens.js +11 -3
  110. package/dist/theme/tokens.stylex.d.ts +3 -1
  111. package/dist/theme/tokens.stylex.d.ts.map +1 -1
  112. package/dist/theme/tokens.stylex.js +3 -1
  113. package/locales/en.json +84 -0
  114. package/locales/pseudo.json +63 -0
  115. package/package.json +3 -3
  116. package/src/Avatar/Avatar.doc.mjs +5 -2
  117. package/src/Avatar/Avatar.test.tsx +51 -0
  118. package/src/Avatar/Avatar.tsx +30 -20
  119. package/src/AvatarGroup/AvatarGroup.doc.mjs +1 -1
  120. package/src/AvatarGroup/AvatarGroup.test.tsx +14 -0
  121. package/src/AvatarGroup/AvatarGroupOverflow.tsx +3 -2
  122. package/src/Button/Button.tsx +5 -3
  123. package/src/ButtonGroup/ButtonGroup.test.tsx +7 -4
  124. package/src/Card/Card.doc.mjs +2 -0
  125. package/src/Chat/Chat.doc.mjs +4 -1
  126. package/src/Chat/ChatMessage.doc.mjs +3 -3
  127. package/src/Chat/ChatMessage.tsx +7 -0
  128. package/src/Chat/ChatMessageBubble.doc.mjs +7 -0
  129. package/src/Chat/ChatMessageBubble.test.tsx +95 -6
  130. package/src/Chat/ChatMessageBubble.tsx +25 -0
  131. package/src/CheckboxInput/CheckboxInput.tsx +20 -0
  132. package/src/CodeBlock/CodeBlock.doc.mjs +3 -0
  133. package/src/CommandPalette/CommandPaletteFooter.tsx +6 -3
  134. package/src/DateTimeInput/DateTimeInput.tsx +4 -2
  135. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +72 -1
  136. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +24 -20
  137. package/src/HoverCard/HoverCard.doc.mjs +3 -3
  138. package/src/HoverCard/HoverCard.test.tsx +283 -55
  139. package/src/HoverCard/HoverCard.tsx +5 -13
  140. package/src/HoverCard/useHoverCard.tsx +8 -11
  141. package/src/InputGroup/groupStyles.ts +7 -8
  142. package/src/Item/Item.doc.mjs +4 -0
  143. package/src/Layer/layerHost.test.ts +99 -0
  144. package/src/Layer/layerHost.ts +141 -0
  145. package/src/Layer/useLayer.doc.mjs +14 -4
  146. package/src/Layer/useLayer.test.tsx +332 -7
  147. package/src/Layer/useLayer.tsx +235 -36
  148. package/src/MobileNav/MobileNav.doc.mjs +7 -0
  149. package/src/NavItem/navItemStyles.stylex.ts +31 -5
  150. package/src/RadioList/RadioListItem.tsx +20 -0
  151. package/src/SideNav/SideNav.doc.mjs +13 -4
  152. package/src/SideNav/SideNav.test.tsx +858 -2
  153. package/src/SideNav/SideNav.tsx +37 -16
  154. package/src/SideNav/SideNavCollapseButton.doc.mjs +28 -6
  155. package/src/SideNav/SideNavCollapseButton.tsx +67 -15
  156. package/src/SideNav/SideNavCollapseContext.ts +29 -9
  157. package/src/SideNav/SideNavHeading.tsx +53 -25
  158. package/src/SideNav/SideNavItem.doc.mjs +13 -0
  159. package/src/SideNav/SideNavItem.tsx +97 -84
  160. package/src/SideNav/SideNavSection.tsx +6 -20
  161. package/src/SideNav/index.ts +2 -0
  162. package/src/Slider/Slider.test.tsx +69 -33
  163. package/src/Slider/Slider.tsx +65 -13
  164. package/src/Switch/Switch.tsx +20 -0
  165. package/src/TabList/TabList.doc.mjs +3 -0
  166. package/src/Text/Text.doc.mjs +1 -1
  167. package/src/Thumbnail/Thumbnail.doc.mjs +3 -0
  168. package/src/Thumbnail/Thumbnail.tsx +10 -0
  169. package/src/Timestamp/Timestamp.test.tsx +40 -22
  170. package/src/Toolbar/Toolbar.test.tsx +14 -14
  171. package/src/TopNav/TopNav.test.tsx +86 -1
  172. package/src/TopNav/TopNavHeading.tsx +21 -14
  173. package/src/TopNav/TopNavMegaMenu.test.tsx +43 -0
  174. package/src/TopNav/TopNavMegaMenu.tsx +29 -105
  175. package/src/TopNav/TopNavMegaMenuItem.tsx +1 -1
  176. package/src/TopNav/TopNavMenu.test.tsx +41 -0
  177. package/src/TopNav/TopNavMenu.tsx +14 -4
  178. package/src/TreeList/TreeList.doc.mjs +1 -0
  179. package/src/hooks/containerReveal.stylex.ts +140 -9
  180. package/src/hooks/index.ts +6 -1
  181. package/src/hooks/useContainerReveal.doc.mjs +11 -5
  182. package/src/hooks/useContainerReveal.test.tsx +70 -3
  183. package/src/hooks/useContainerReveal.ts +86 -7
  184. package/src/hooks/useFocusTrap.test.tsx +16 -0
  185. package/src/hooks/useFocusTrap.ts +20 -2
  186. package/src/hooks/useMenuHover.test.tsx +364 -0
  187. package/src/hooks/useMenuHover.ts +243 -47
  188. package/src/theme/defineTheme.test.ts +127 -0
  189. package/src/theme/defineTheme.ts +57 -51
  190. package/src/theme/derivedVarRegistry.test.ts +78 -12
  191. package/src/theme/derivedVarRegistry.ts +4 -0
  192. package/src/theme/expandColorScale.ts +1 -0
  193. package/src/theme/expandMotionScale.ts +1 -0
  194. package/src/theme/expandRadiusScale.ts +1 -0
  195. package/src/theme/expandTypeScale.ts +1 -0
  196. package/src/theme/mergeComponents.ts +59 -0
  197. package/src/theme/onMediaTokens.ts +9 -2
  198. package/src/theme/themingTargets.test.ts +152 -26
  199. package/src/theme/tokens.stylex.ts +3 -1
  200. package/src/theme/tokens.test.ts +12 -0
  201. package/src/theme/useTheme.test.tsx +18 -0
@@ -26,7 +26,7 @@
26
26
 
27
27
  import {describe, it, expect} from 'vitest';
28
28
  import {readdirSync, readFileSync} from 'node:fs';
29
- import {join} from 'node:path';
29
+ import {join, relative} from 'node:path';
30
30
  import ts from 'typescript';
31
31
  import {stableClassName} from '../naming';
32
32
 
@@ -45,6 +45,61 @@ interface ThemeTargetSite {
45
45
  isOpaque: boolean;
46
46
  }
47
47
 
48
+ /**
49
+ * Keys of a spread whose operand is a conditional object literal:
50
+ * `...(cond && {a})`, `...(cond ? {a} : null)`, `...(cond || {a})`.
51
+ * Returns null when the operand is anything else (a bag like `...rest`, or an
52
+ * object with a computed key), which the caller treats as opaque.
53
+ */
54
+ function conditionalSpreadKeys(expression: ts.Expression): string[] | null {
55
+ const unwrap = (node: ts.Expression): ts.Expression =>
56
+ ts.isParenthesizedExpression(node) ? unwrap(node.expression) : node;
57
+
58
+ const expr = unwrap(expression);
59
+ const branches: ts.Expression[] = [];
60
+ if (
61
+ ts.isBinaryExpression(expr) &&
62
+ (expr.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ||
63
+ expr.operatorToken.kind === ts.SyntaxKind.BarBarToken)
64
+ ) {
65
+ branches.push(expr.right);
66
+ } else if (ts.isConditionalExpression(expr)) {
67
+ branches.push(expr.whenTrue, expr.whenFalse);
68
+ } else if (ts.isObjectLiteralExpression(expr)) {
69
+ branches.push(expr);
70
+ } else {
71
+ return null;
72
+ }
73
+
74
+ const keys: string[] = [];
75
+ for (const branch of branches) {
76
+ const object = unwrap(branch);
77
+ // A falsy filler arm (`null`, `undefined`) contributes no keys; anything
78
+ // else that is not an object literal hides its keys, so give up.
79
+ if (
80
+ object.kind === ts.SyntaxKind.NullKeyword ||
81
+ (ts.isIdentifier(object) && object.text === 'undefined')
82
+ ) {
83
+ continue;
84
+ }
85
+ if (!ts.isObjectLiteralExpression(object)) {
86
+ return null;
87
+ }
88
+ for (const prop of object.properties) {
89
+ const name = prop.name;
90
+ if (
91
+ name != null &&
92
+ (ts.isIdentifier(name) || ts.isStringLiteralLike(name))
93
+ ) {
94
+ keys.push(name.text);
95
+ } else {
96
+ return null;
97
+ }
98
+ }
99
+ }
100
+ return keys;
101
+ }
102
+
48
103
  /**
49
104
  * Extract every `themeProps('name', {...})` call from a source file.
50
105
  *
@@ -88,8 +143,16 @@ function extractThemeTargets(
88
143
  if (ts.isObjectLiteralExpression(propsArg)) {
89
144
  for (const prop of propsArg.properties) {
90
145
  if (ts.isSpreadAssignment(prop)) {
91
- // {...rest} — keys unknown at parse time.
92
- site.isOpaque = true;
146
+ // A conditional spread of an object literal — `...(type &&
147
+ // {type})` — has statically known keys. Treating it as opaque
148
+ // is how `astryx-heading`'s `type` stayed undocumented: the
149
+ // same drift this file exists to catch (#3652, #3680).
150
+ const spreadKeys = conditionalSpreadKeys(prop.expression);
151
+ if (spreadKeys == null) {
152
+ site.isOpaque = true;
153
+ } else {
154
+ site.propKeys.push(...spreadKeys);
155
+ }
93
156
  continue;
94
157
  }
95
158
  const name = prop.name;
@@ -194,12 +257,35 @@ describe('extractThemeTargets', () => {
194
257
  ]);
195
258
  });
196
259
 
260
+ it('reads the keys of a conditional spread', () => {
261
+ // Heading's real call site: `{level, color, ...(type && {type})}`.
262
+ const [site] = extractThemeTargets(
263
+ `themeProps('heading', {level, color, ...(type && {type})})`,
264
+ );
265
+ expect(site.propKeys).toEqual(['level', 'color', 'type']);
266
+ expect(site.isOpaque).toBe(false);
267
+ });
268
+
269
+ it('reads both arms of a ternary spread', () => {
270
+ const [site] = extractThemeTargets(
271
+ `themeProps('card', {...(isOpen ? {expanded} : {collapsed})})`,
272
+ );
273
+ expect(site.propKeys).toEqual(['expanded', 'collapsed']);
274
+ expect(site.isOpaque).toBe(false);
275
+ });
276
+
197
277
  it('marks a spread props bag opaque rather than guessing its keys', () => {
198
278
  const [site] = extractThemeTargets(`themeProps('card', {...rest})`);
199
279
  expect(site.isOpaque).toBe(true);
200
280
  expect(site.propKeys).toEqual([]);
201
281
  });
202
282
 
283
+ it('marks a conditional spread of a non-literal opaque', () => {
284
+ const [site] = extractThemeTargets(`themeProps('card', {...(on && rest)})`);
285
+ expect(site.isOpaque).toBe(true);
286
+ expect(site.propKeys).toEqual([]);
287
+ });
288
+
203
289
  it('marks a non-literal props bag opaque', () => {
204
290
  const [site] = extractThemeTargets(`themeProps('card', visualProps)`);
205
291
  expect(site.isOpaque).toBe(true);
@@ -231,7 +317,37 @@ interface ComponentInfo {
231
317
  dir: string;
232
318
  sites: ThemeTargetSite[];
233
319
  /** The doc blocks that carry theming.targets, by the key they live under. */
234
- docBlocks: {key: 'docs' | 'docsZh'; targets: DocTarget[]}[];
320
+ docBlocks: {key: 'docs' | 'docsZh'; file: string; targets: DocTarget[]}[];
321
+ }
322
+
323
+ /**
324
+ * Every `*.doc.mjs` under src that declares a theming target for one of
325
+ * `classNames`. Lets a component be checked against the doc that documents it,
326
+ * wherever that file lives.
327
+ *
328
+ * Reads the file as text rather than requiring it: this runs for directories
329
+ * that have no doc of their own, so most candidates are misses.
330
+ */
331
+ function docFilesDocumenting(classNames: Set<string>): string[] {
332
+ const matches: string[] = [];
333
+ const walk = (dir: string): void => {
334
+ for (const entry of readdirSync(dir, {withFileTypes: true})) {
335
+ const p = join(dir, entry.name);
336
+ if (entry.isDirectory()) {
337
+ walk(p);
338
+ } else if (entry.name.endsWith('.doc.mjs')) {
339
+ const source = readFileSync(p, 'utf-8');
340
+ for (const className of classNames) {
341
+ if (source.includes(`'${className}'`)) {
342
+ matches.push(p);
343
+ break;
344
+ }
345
+ }
346
+ }
347
+ }
348
+ };
349
+ walk(SRC_DIR);
350
+ return matches;
235
351
  }
236
352
 
237
353
  function discoverComponents(): ComponentInfo[] {
@@ -262,27 +378,37 @@ function discoverComponents(): ComponentInfo[] {
262
378
  continue;
263
379
  }
264
380
 
265
- // Match the on-disk listing rather than existsSync: on case-insensitive
266
- // filesystems existsSync would match a differently-cased doc file that CI
267
- // never checks. (Same guard as derivedVarRegistry.test.ts.)
268
- if (!dirEntries.includes(`${dir}.doc.mjs`)) {
269
- continue;
270
- }
271
-
272
- let mod: ComponentDocModule;
273
- try {
274
- mod = require(join(dirPath, `${dir}.doc.mjs`)) as ComponentDocModule;
275
- } catch {
381
+ // A component's doc file usually sits beside its source, but not always:
382
+ // `Heading/Heading.tsx` is documented by `Text/Text.doc.mjs`. Requiring a
383
+ // same-directory doc silently exempted every such component — Heading
384
+ // among them. Fall back to whichever doc file documents the classes this
385
+ // directory renders.
386
+ //
387
+ // Both paths match the on-disk listing rather than existsSync: on
388
+ // case-insensitive filesystems existsSync would match a differently-cased
389
+ // doc file that CI never checks. (Same guard as derivedVarRegistry.test.ts.)
390
+ const docFiles = dirEntries.includes(`${dir}.doc.mjs`)
391
+ ? [join(dirPath, `${dir}.doc.mjs`)]
392
+ : docFilesDocumenting(new Set(sites.map(s => s.className)));
393
+ if (docFiles.length === 0) {
276
394
  continue;
277
395
  }
278
396
 
279
397
  const docBlocks: ComponentInfo['docBlocks'] = [];
280
- for (const key of ['docs', 'docsZh'] as const) {
281
- const targets = mod[key]?.theming?.targets;
282
- // Only blocks that already document a theming surface are held to it —
283
- // a doc with no theming block at all is a separate (documentation) gap.
284
- if (targets != null) {
285
- docBlocks.push({key, targets});
398
+ for (const docFile of docFiles) {
399
+ let mod: ComponentDocModule;
400
+ try {
401
+ mod = require(docFile) as ComponentDocModule;
402
+ } catch {
403
+ continue;
404
+ }
405
+ for (const key of ['docs', 'docsZh'] as const) {
406
+ const targets = mod[key]?.theming?.targets;
407
+ // Only blocks that already document a theming surface are held to it —
408
+ // a doc with no theming block at all is a separate (documentation) gap.
409
+ if (targets != null) {
410
+ docBlocks.push({key, file: relative(SRC_DIR, docFile), targets});
411
+ }
286
412
  }
287
413
  }
288
414
  if (docBlocks.length === 0) {
@@ -309,22 +435,22 @@ describe('theming.targets matches the themeProps() call sites', () => {
309
435
  for (const {dir, sites, docBlocks} of components) {
310
436
  const renderedClasses = [...new Set(sites.map(s => s.className))].sort();
311
437
 
312
- for (const {key, targets} of docBlocks) {
438
+ for (const {key, file, targets} of docBlocks) {
313
439
  const documented = new Set(targets.map(t => t.className));
314
440
 
315
- it(`${dir} (${key}): every rendered class is documented`, () => {
441
+ it(`${dir} (${file} ${key}): every rendered class is documented`, () => {
316
442
  const undocumented = renderedClasses.filter(c => !documented.has(c));
317
443
  expect(
318
444
  undocumented,
319
445
  `${dir} renders ${undocumented.length} astryx-* class(es) that ` +
320
- `${dir}.doc.mjs ${key}.theming.targets does not document: ` +
446
+ `${file} ${key}.theming.targets does not document: ` +
321
447
  `${undocumented.join(', ')}. An undocumented class is an ` +
322
448
  `unthemeable element — theme authors and codegen read targets[] ` +
323
449
  `to learn which selectors exist. Add {className: '...'} entries.`,
324
450
  ).toEqual([]);
325
451
  });
326
452
 
327
- it(`${dir} (${key}): every visual prop passed to themeProps is documented`, () => {
453
+ it(`${dir} (${file} ${key}): every visual prop passed to themeProps is documented`, () => {
328
454
  const missing: string[] = [];
329
455
  for (const site of sites) {
330
456
  const target = targets.find(t => t.className === site.className);
@@ -343,7 +469,7 @@ describe('theming.targets matches the themeProps() call sites', () => {
343
469
  }
344
470
  expect(
345
471
  [...new Set(missing)],
346
- `${dir} passes prop keys to themeProps() that ${dir}.doc.mjs ` +
472
+ `${dir} passes prop keys to themeProps() that ${file} ` +
347
473
  `${key}.theming.targets does not list under visualProps/states. ` +
348
474
  `Each one is a [data-*] selector consumers cannot discover.`,
349
475
  ).toEqual([]);
@@ -8,7 +8,9 @@
8
8
  * - *Vars: CSS custom properties that themes can override via createTheme
9
9
  *
10
10
  * SYNC: When modified, run `node scripts/generate-token-docs.mjs` to update docs.
11
- * CI checks for drift via --check flag.
11
+ * CI checks for drift via --check flag. Adding or removing a token FAMILY (a
12
+ * whole `*Defaults` group) also means updating:
13
+ * - /packages/cli/assets/theme.template.ts (its inventory of what exists)
12
14
  *
13
15
  * Domain tokens (syntax highlighting, data visualization) live separately in
14
16
  * /packages/core/src/theme/domainTokens/ — they're tree-shaken from core components.
@@ -40,6 +40,18 @@ describe('resolveThemeTokens', () => {
40
40
  const tokens = resolveThemeTokens(null, {mode: 'light'});
41
41
  expect(tokens['--color-text-primary']).toBe('#0A1317');
42
42
  expect(tokens['--spacing-1']).toBe('4px');
43
+ expect(tokens['--border-width']).toBe('1px');
44
+ });
45
+
46
+ it('resolves --border-width when overridden in defineTheme', () => {
47
+ const arcadeTheme = defineTheme({
48
+ name: 'arcade',
49
+ tokens: {
50
+ '--border-width': '2px',
51
+ },
52
+ });
53
+ const tokens = resolveThemeTokens(arcadeTheme, {mode: 'light'});
54
+ expect(tokens['--border-width']).toBe('2px');
43
55
  });
44
56
 
45
57
  it('resolves tuple overrides using original input tokens', () => {
@@ -80,6 +80,24 @@ describe('useTheme', () => {
80
80
  });
81
81
  // --spacing-1 is not overridden — should be the default '4px'
82
82
  expect(result.current.token('--spacing-1')).toBe('4px');
83
+ expect(result.current.token('--border-width')).toBe('1px');
84
+ });
85
+
86
+ it('resolves --border-width override when defined in theme', () => {
87
+ const arcadeTheme = defineTheme({
88
+ name: 'arcade',
89
+ tokens: {
90
+ '--border-width': '2px',
91
+ },
92
+ });
93
+ const {result} = renderHook(() => useTheme(), {
94
+ wrapper: ({children}) => (
95
+ <Theme theme={arcadeTheme} mode="light">
96
+ {children}
97
+ </Theme>
98
+ ),
99
+ });
100
+ expect(result.current.token('--border-width')).toBe('2px');
83
101
  });
84
102
 
85
103
  it('resolves default light-dark() string tokens for the mode', () => {