@astryxdesign/core 0.1.4-canary.37c19d3 → 0.1.4-canary.39ffd21

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 (226) hide show
  1. package/dist/Banner/Banner.d.ts.map +1 -1
  2. package/dist/Banner/Banner.js +1 -8
  3. package/dist/Calendar/Calendar.d.ts.map +1 -1
  4. package/dist/Calendar/Calendar.js +11 -31
  5. package/dist/Calendar/styles.d.ts +2 -2
  6. package/dist/Calendar/styles.d.ts.map +1 -1
  7. package/dist/Calendar/styles.js +2 -2
  8. package/dist/Carousel/Carousel.d.ts.map +1 -1
  9. package/dist/Carousel/Carousel.js +0 -1
  10. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  11. package/dist/Chat/ChatToolCalls.js +1 -8
  12. package/dist/CheckboxInput/CheckboxInput.d.ts +1 -1
  13. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  14. package/dist/CheckboxInput/CheckboxInput.js +1 -3
  15. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  16. package/dist/ClickableCard/ClickableCard.js +3 -26
  17. package/dist/CodeBlock/CodeBlock.d.ts +1 -10
  18. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  19. package/dist/CodeBlock/CodeBlock.js +5 -23
  20. package/dist/Collapsible/Collapsible.d.ts +3 -9
  21. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  22. package/dist/Collapsible/Collapsible.js +13 -101
  23. package/dist/Collapsible/CollapsibleGroup.d.ts +7 -39
  24. package/dist/Collapsible/CollapsibleGroup.d.ts.map +1 -1
  25. package/dist/Collapsible/CollapsibleGroup.js +9 -70
  26. package/dist/Collapsible/CollapsibleGroupContext.d.ts +0 -31
  27. package/dist/Collapsible/CollapsibleGroupContext.d.ts.map +1 -1
  28. package/dist/Collapsible/CollapsibleGroupContext.js +3 -33
  29. package/dist/Collapsible/index.d.ts +0 -1
  30. package/dist/Collapsible/index.d.ts.map +1 -1
  31. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  32. package/dist/ContextMenu/ContextMenu.js +0 -2
  33. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  34. package/dist/DateTimeInput/DateTimeInput.js +0 -2
  35. package/dist/Grid/Grid.d.ts +3 -4
  36. package/dist/Grid/Grid.d.ts.map +1 -1
  37. package/dist/Grid/Grid.js +9 -26
  38. package/dist/Icon/Icon.d.ts.map +1 -1
  39. package/dist/Icon/Icon.js +1 -5
  40. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  41. package/dist/Lightbox/Lightbox.js +2 -26
  42. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  43. package/dist/NumberInput/NumberInput.js +16 -32
  44. package/dist/Pagination/Pagination.d.ts.map +1 -1
  45. package/dist/Pagination/Pagination.js +36 -87
  46. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  47. package/dist/PowerSearch/PowerSearch.js +21 -39
  48. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  49. package/dist/ProgressBar/ProgressBar.js +1 -2
  50. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  51. package/dist/Resizable/ResizeHandle.js +2 -10
  52. package/dist/SideNav/SideNav.d.ts.map +1 -1
  53. package/dist/SideNav/SideNav.js +2 -6
  54. package/dist/TabList/TabMenu.d.ts.map +1 -1
  55. package/dist/TabList/TabMenu.js +7 -34
  56. package/dist/Table/BaseTable.d.ts.map +1 -1
  57. package/dist/Table/BaseTable.js +2 -12
  58. package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
  59. package/dist/Table/plugins/columnResize/useTableColumnResize.js +2 -5
  60. package/dist/Table/plugins/pagination/paginateData.d.ts.map +1 -1
  61. package/dist/Table/plugins/pagination/paginateData.js +1 -5
  62. package/dist/Table/plugins/selection/useTableSelectionState.d.ts.map +1 -1
  63. package/dist/Table/plugins/selection/useTableSelectionState.js +1 -11
  64. package/dist/Table/plugins/sortable/useTableSortableState.d.ts.map +1 -1
  65. package/dist/Table/plugins/sortable/useTableSortableState.js +4 -8
  66. package/dist/Thumbnail/Thumbnail.d.ts +2 -3
  67. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  68. package/dist/Thumbnail/Thumbnail.js +0 -1
  69. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  70. package/dist/TimeInput/TimeInput.js +1 -6
  71. package/dist/Toast/Toast.d.ts.map +1 -1
  72. package/dist/Toast/Toast.js +3 -11
  73. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  74. package/dist/Toast/ToastViewport.js +0 -12
  75. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  76. package/dist/Typeahead/BaseTypeahead.js +2 -5
  77. package/dist/astryx.css +0 -7
  78. package/dist/astryx.umd.js +48 -48
  79. package/dist/astryx.umd.js.map +4 -4
  80. package/dist/hooks/focusableSelector.d.ts +1 -1
  81. package/dist/hooks/focusableSelector.d.ts.map +1 -1
  82. package/dist/hooks/focusableSelector.js +1 -1
  83. package/dist/hooks/useFocusTrap.d.ts +0 -3
  84. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  85. package/dist/hooks/useFocusTrap.js +1 -38
  86. package/dist/theme/defineTheme.d.ts +1 -1
  87. package/dist/theme/defineTheme.d.ts.map +1 -1
  88. package/dist/theme/expandColorScale.d.ts.map +1 -1
  89. package/dist/theme/expandColorScale.js +3 -10
  90. package/dist/theme/hct.d.ts +15 -0
  91. package/dist/theme/hct.d.ts.map +1 -1
  92. package/dist/theme/hct.js +9 -9
  93. package/dist/theme/tokens.d.ts +2 -10
  94. package/dist/theme/tokens.d.ts.map +1 -1
  95. package/dist/theme/tokens.js +4 -220
  96. package/dist/utils/index.d.ts +0 -2
  97. package/dist/utils/index.d.ts.map +1 -1
  98. package/dist/utils/index.js +1 -2
  99. package/dist/utils/timeParser.d.ts.map +1 -1
  100. package/dist/utils/timeParser.js +5 -8
  101. package/package.json +1 -1
  102. package/src/Avatar/Avatar.doc.mjs +0 -11
  103. package/src/Banner/Banner.doc.mjs +0 -8
  104. package/src/Banner/Banner.test.tsx +0 -38
  105. package/src/Banner/Banner.tsx +1 -8
  106. package/src/Calendar/Calendar.doc.mjs +1 -1
  107. package/src/Calendar/Calendar.test.tsx +4 -112
  108. package/src/Calendar/Calendar.tsx +5 -26
  109. package/src/Calendar/styles.ts +2 -12
  110. package/src/Carousel/Carousel.doc.mjs +12 -12
  111. package/src/Carousel/Carousel.test.tsx +0 -13
  112. package/src/Carousel/Carousel.tsx +0 -1
  113. package/src/Chat/Chat.doc.mjs +1 -1
  114. package/src/Chat/ChatToolCalls.test.tsx +0 -55
  115. package/src/Chat/ChatToolCalls.tsx +12 -10
  116. package/src/CheckboxInput/CheckboxInput.doc.mjs +3 -3
  117. package/src/CheckboxInput/CheckboxInput.test.tsx +4 -71
  118. package/src/CheckboxInput/CheckboxInput.tsx +0 -2
  119. package/src/Citation/Citation.doc.mjs +0 -5
  120. package/src/ClickableCard/ClickableCard.tsx +2 -45
  121. package/src/CodeBlock/CodeBlock.doc.mjs +2 -7
  122. package/src/CodeBlock/CodeBlock.test.tsx +3 -87
  123. package/src/CodeBlock/CodeBlock.tsx +3 -43
  124. package/src/Collapsible/Collapsible.doc.mjs +4 -8
  125. package/src/Collapsible/Collapsible.tsx +10 -86
  126. package/src/Collapsible/CollapsibleGroup.doc.mjs +3 -27
  127. package/src/Collapsible/CollapsibleGroup.test.tsx +0 -288
  128. package/src/Collapsible/CollapsibleGroup.tsx +9 -107
  129. package/src/Collapsible/CollapsibleGroupContext.tsx +2 -42
  130. package/src/Collapsible/index.ts +0 -4
  131. package/src/CommandPalette/CommandPaletteEmpty.doc.mjs +0 -5
  132. package/src/CommandPalette/CommandPaletteGroup.doc.mjs +0 -10
  133. package/src/CommandPalette/CommandPaletteInput.doc.mjs +0 -6
  134. package/src/CommandPalette/CommandPaletteItem.doc.mjs +0 -6
  135. package/src/CommandPalette/CommandPaletteList.doc.mjs +0 -9
  136. package/src/ContextMenu/ContextMenu.doc.mjs +0 -15
  137. package/src/ContextMenu/ContextMenu.test.tsx +0 -15
  138. package/src/ContextMenu/ContextMenu.tsx +0 -2
  139. package/src/ContextMenu/ContextMenuItem.doc.mjs +0 -6
  140. package/src/DateTimeInput/DateTimeInput.test.tsx +0 -70
  141. package/src/DateTimeInput/DateTimeInput.tsx +0 -2
  142. package/src/Dialog/DialogHeader.doc.mjs +0 -45
  143. package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -1
  144. package/src/Field/FieldLabel.test.tsx +2 -2
  145. package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +3 -0
  146. package/src/Grid/Grid.test.tsx +18 -43
  147. package/src/Grid/Grid.tsx +9 -26
  148. package/src/Icon/Icon.test.tsx +29 -41
  149. package/src/Icon/Icon.tsx +2 -6
  150. package/src/Layout/LayoutContent.doc.mjs +0 -12
  151. package/src/Layout/LayoutPanel.doc.mjs +0 -40
  152. package/src/Lightbox/Lightbox.doc.mjs +0 -5
  153. package/src/Lightbox/Lightbox.test.tsx +2 -123
  154. package/src/Lightbox/Lightbox.tsx +1 -29
  155. package/src/NavMenu/NavMenu.doc.mjs +0 -15
  156. package/src/NumberInput/NumberInput.test.tsx +3 -48
  157. package/src/NumberInput/NumberInput.tsx +15 -32
  158. package/src/Overlay/Overlay.doc.mjs +0 -6
  159. package/src/Pagination/Pagination.test.tsx +0 -143
  160. package/src/Pagination/Pagination.tsx +23 -75
  161. package/src/Popover/Popover.test.tsx +0 -65
  162. package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
  163. package/src/PowerSearch/PowerSearch.test.tsx +1 -109
  164. package/src/PowerSearch/PowerSearch.tsx +16 -37
  165. package/src/ProgressBar/ProgressBar.test.tsx +0 -10
  166. package/src/ProgressBar/ProgressBar.tsx +1 -2
  167. package/src/Resizable/ResizeHandle.tsx +2 -10
  168. package/src/SegmentedControl/SegmentedControl.doc.mjs +1 -1
  169. package/src/SelectableCard/SelectableCard.doc.mjs +1 -1
  170. package/src/SideNav/SideNav.doc.mjs +1 -7
  171. package/src/SideNav/SideNav.test.tsx +0 -20
  172. package/src/SideNav/SideNav.tsx +2 -6
  173. package/src/TabList/TabList.test.tsx +1 -185
  174. package/src/TabList/TabMenu.tsx +9 -45
  175. package/src/Table/BaseTable.tsx +1 -11
  176. package/src/Table/Table.test.tsx +0 -51
  177. package/src/Table/plugins/columnResize/useTableColumnResize.test.tsx +0 -13
  178. package/src/Table/plugins/columnResize/useTableColumnResize.tsx +1 -3
  179. package/src/Table/plugins/pagination/paginateData.ts +1 -5
  180. package/src/Table/plugins/pagination/useTablePagination.test.tsx +0 -13
  181. package/src/Table/plugins/selection/useTableSelectionState.test.tsx +0 -23
  182. package/src/Table/plugins/selection/useTableSelectionState.tsx +6 -11
  183. package/src/Table/plugins/sortable/useTableSortableState.test.tsx +1 -35
  184. package/src/Table/plugins/sortable/useTableSortableState.tsx +4 -8
  185. package/src/TextArea/TextArea.test.tsx +3 -3
  186. package/src/TextInput/TextInput.test.tsx +3 -3
  187. package/src/Thumbnail/Thumbnail.test.tsx +2 -36
  188. package/src/Thumbnail/Thumbnail.tsx +2 -4
  189. package/src/TimeInput/TimeInput.test.tsx +0 -37
  190. package/src/TimeInput/TimeInput.tsx +1 -11
  191. package/src/Timestamp/Timestamp.doc.mjs +1 -1
  192. package/src/Toast/Toast.tsx +3 -11
  193. package/src/Toast/ToastViewport.test.tsx +0 -64
  194. package/src/Toast/ToastViewport.tsx +0 -12
  195. package/src/ToggleButton/ToggleButton.doc.mjs +0 -1
  196. package/src/Tokenizer/Tokenizer.doc.mjs +2 -2
  197. package/src/TopNav/TopNav.doc.mjs +1 -1
  198. package/src/TreeList/TreeList.doc.mjs +2 -2
  199. package/src/Typeahead/BaseTypeahead.doc.mjs +0 -19
  200. package/src/Typeahead/BaseTypeahead.tsx +2 -5
  201. package/src/Typeahead/Typeahead.doc.mjs +1 -1
  202. package/src/Typeahead/Typeahead.test.tsx +1 -45
  203. package/src/hooks/focusableSelector.ts +1 -1
  204. package/src/hooks/useFocusTrap.doc.mjs +2 -4
  205. package/src/hooks/useFocusTrap.test.tsx +1 -149
  206. package/src/hooks/useFocusTrap.ts +1 -50
  207. package/src/theme/SyntaxTheme.doc.mjs +4 -4
  208. package/src/theme/defineTheme.ts +13 -4
  209. package/src/theme/derivedVarRegistry.test.ts +2 -2
  210. package/src/theme/expandColorScale.test.ts +0 -29
  211. package/src/theme/expandColorScale.ts +6 -11
  212. package/src/theme/hct.ts +17 -9
  213. package/src/theme/tokens.test.ts +9 -182
  214. package/src/theme/tokens.ts +4 -239
  215. package/src/theme/useTheme.test.tsx +0 -20
  216. package/src/utils/index.ts +0 -3
  217. package/src/utils/timeParser.test.ts +0 -13
  218. package/src/utils/timeParser.ts +5 -8
  219. package/dist/utils/color.d.ts +0 -42
  220. package/dist/utils/color.d.ts.map +0 -1
  221. package/dist/utils/color.js +0 -150
  222. package/src/Resizable/ResizeHandle.test.tsx +0 -180
  223. package/src/__tests__/TestIcon.tsx +0 -26
  224. package/src/docPropReferences.test.ts +0 -178
  225. package/src/utils/color.test.ts +0 -133
  226. package/src/utils/color.ts +0 -148
@@ -17,7 +17,7 @@ export const docs = {
17
17
  theming: {
18
18
  targets: [
19
19
  {className: 'astryx-top-nav', states: ['mode']},
20
- {className: 'astryx-top-nav-item', states: ['mode', 'selected']},
20
+ {className: 'astryx-top-nav-item', states: ['mode']},
21
21
  {className: 'astryx-top-nav-heading'},
22
22
  {className: 'astryx-top-nav-mega-menu', states: ['mode']},
23
23
  {className: 'astryx-top-nav-mega-menu-item', states: ['mode']},
@@ -25,7 +25,7 @@ export const docs = {
25
25
  theming: {
26
26
  targets: [
27
27
  {className: 'astryx-tree-list', visualProps: ['density']},
28
- {className: 'astryx-tree-list-item', visualProps: ['density'], states: ['selected', 'disabled']},
28
+ {className: 'astryx-tree-list-item', states: ['selected', 'disabled']},
29
29
  ],
30
30
  },
31
31
  components: [
@@ -89,7 +89,7 @@ export const docsZh = {
89
89
  theming: {
90
90
  targets: [
91
91
  {className: 'astryx-tree-list', visualProps: ['density']},
92
- {className: 'astryx-tree-list-item', visualProps: ['density'], states: ['selected', 'disabled']},
92
+ {className: 'astryx-tree-list-item', states: ['selected', 'disabled']},
93
93
  ],
94
94
  },
95
95
  components: [
@@ -8,16 +8,6 @@ export const docs = {
8
8
  displayName: 'Base Typeahead',
9
9
  isHiddenFromOverview: true,
10
10
  description: 'Unstyled combobox engine providing input, search, keyboard navigation, and dropdown. No wrapper div, no border styling, no token rendering. Used by Typeahead and Tokenizer for custom compositions.',
11
- usage: {
12
- description: 'Unstyled combobox engine providing input, search, keyboard navigation, and dropdown. No wrapper div, no border styling, no token rendering. Used by Typeahead and Tokenizer for custom compositions.',
13
- bestPractices: [
14
- { guidance: true, description: 'Use Typeahead or Tokenizer for standard fields; they wrap BaseTypeahead with the wrapper div, border styling, and token rendering it intentionally omits.' },
15
- { guidance: true, description: 'Provide your own wrapper div with border and layout when composing directly, since BaseTypeahead renders no visual chrome of its own.' },
16
- { guidance: true, description: 'Pass anchorRef pointing to your wrapper so the dropdown positions against your custom input chrome, not just the bare input element.' },
17
- { guidance: false, description: 'Expect a wrapper div, border, or token rendering. BaseTypeahead is an engine only; all visual chrome is the caller\'s responsibility.' },
18
- { guidance: false, description: 'Use BaseTypeahead when Typeahead or Tokenizer would suffice; the extra wrapper and styling work is only justified for truly custom compositions.' },
19
- ],
20
- },
21
11
  props: [
22
12
  {
23
13
  name: 'searchSource',
@@ -236,15 +226,6 @@ export const docsDense = {
236
226
  isHiddenFromOverview: true,
237
227
  displayName: 'Base Typeahead',
238
228
  description: 'Unstyled combobox engine; input+search+keyboard nav+dropdown. No wrapper/border/token. Used by Typeahead+Tokenizer.',
239
- usage: {
240
- bestPractices: [
241
- { guidance: true, description: 'Use Typeahead or Tokenizer for standard fields; they add the wrapper, border, and token rendering BaseTypeahead omits.' },
242
- { guidance: true, description: 'Provide your own wrapper div with border and layout when composing directly; BaseTypeahead renders no chrome.' },
243
- { guidance: true, description: 'Pass anchorRef to your wrapper so the dropdown positions against your input chrome, not the bare input.' },
244
- { guidance: false, description: 'Expect wrapper, border, or token rendering. Engine only; chrome is the caller\'s job.' },
245
- { guidance: false, description: 'Use BaseTypeahead when Typeahead or Tokenizer suffice; extra work only pays off for custom compositions.' },
246
- ],
247
- },
248
229
  propDescriptions: {
249
230
  searchSource: 'Data source w/ search+bootstrap methods.',
250
231
  value: 'Currently selected item.',
@@ -410,10 +410,7 @@ export const BaseTypeahead = function BaseTypeahead<T extends SearchableItem>({
410
410
  const performSearch = useCallback(
411
411
  async (searchQuery: string) => {
412
412
  searchSource.cancel?.();
413
- // Claim a new generation so overlapping searches can't race: an
414
- // in-flight response for an older query fails the gen check below
415
- // instead of overwriting the newer results.
416
- const gen = ++searchGenRef.current;
413
+ const gen = searchGenRef.current;
417
414
  setIsLoading(true);
418
415
  setHasSearched(true);
419
416
  try {
@@ -454,7 +451,7 @@ export const BaseTypeahead = function BaseTypeahead<T extends SearchableItem>({
454
451
 
455
452
  // Perform bootstrap
456
453
  const performBootstrap = useCallback(async () => {
457
- const gen = ++searchGenRef.current;
454
+ const gen = searchGenRef.current;
458
455
  setIsLoading(true);
459
456
  try {
460
457
  const bootstrapResults = await searchSource.bootstrap();
@@ -168,7 +168,7 @@ export const docs = {
168
168
  components: [{name: 'BaseTypeahead'}, {name: 'TypeaheadItem'}],
169
169
  theming: {
170
170
  targets: [
171
- {className: 'astryx-typeahead', visualProps: ['status', 'size']},
171
+ {className: 'astryx-typeahead', visualProps: ['status']},
172
172
  {className: 'astryx-typeahead-dropdown'},
173
173
  {className: 'astryx-typeahead-item'},
174
174
  ],
@@ -18,7 +18,7 @@ import {
18
18
  afterAll,
19
19
  beforeEach,
20
20
  } from 'vitest';
21
- import {render, screen, fireEvent, waitFor, act} from '@testing-library/react';
21
+ import {render, screen, fireEvent, waitFor} from '@testing-library/react';
22
22
  import userEvent from '@testing-library/user-event';
23
23
  import {Typeahead} from './Typeahead';
24
24
  import {BaseTypeahead} from './BaseTypeahead';
@@ -314,50 +314,6 @@ describe('BaseTypeahead focus-out', () => {
314
314
  });
315
315
 
316
316
  describe('Typeahead', () => {
317
- describe('out-of-order async results', () => {
318
- it('discards a stale response that resolves after a newer query', async () => {
319
- const resolvers = new Map<string, (items: SearchableItem[]) => void>();
320
- const rawSource: SearchSource = {
321
- search: async (query: string) =>
322
- new Promise<SearchableItem[]>(resolve => {
323
- resolvers.set(query, resolve);
324
- }),
325
- bootstrap: () => [],
326
- };
327
-
328
- render(
329
- <Typeahead
330
- label="Fruit"
331
- searchSource={rawSource}
332
- value={null}
333
- onChange={() => {}}
334
- debounceMs={0}
335
- />,
336
- );
337
-
338
- const input = screen.getByRole('combobox');
339
- fireEvent.change(input, {target: {value: 'a'}});
340
- fireEvent.change(input, {target: {value: 'ap'}});
341
-
342
- // The newer query resolves first…
343
- await act(async () => {
344
- resolvers.get('ap')!([{id: 'apple', label: 'Apple'}]);
345
- });
346
- expect(screen.getByText('Apple')).toBeInTheDocument();
347
-
348
- // …then the abandoned query's slow response arrives and must be
349
- // discarded rather than overwriting the current results.
350
- await act(async () => {
351
- resolvers.get('a')!([
352
- {id: 'avocado', label: 'Avocado'},
353
- {id: 'apricot', label: 'Apricot'},
354
- ]);
355
- });
356
- expect(screen.getByText('Apple')).toBeInTheDocument();
357
- expect(screen.queryByText('Avocado')).not.toBeInTheDocument();
358
- });
359
- });
360
-
361
317
  it('renders with label', () => {
362
318
  render(
363
319
  <Typeahead
@@ -22,4 +22,4 @@
22
22
  * re-declaring the string so behavior stays consistent across hooks.
23
23
  */
24
24
  export const FOCUSABLE_SELECTOR =
25
- 'button:not([disabled]), a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled]), [contenteditable]:not([contenteditable="false"]), audio[controls], video[controls], iframe, details > summary:first-child';
25
+ 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled]), [contenteditable]:not([contenteditable="false"]), audio[controls], video[controls], iframe, details > summary:first-child';
@@ -39,11 +39,10 @@ export const docs = {
39
39
  ],
40
40
  usage: {
41
41
  description:
42
- 'Traps focus within a container element following the WAI-ARIA dialog focus trap pattern. Listens to focus events on the document and redirects focus back into the container if it escapes via keyboard navigation. Handles both Tab and Shift+Tab wrapping. When the trap deactivates or unmounts, focus is restored to the element that was focused before activation, unless focus was already moved elsewhere. Mouse clicks outside the container are not intercepted; use a light-dismiss handler for that.',
42
+ 'Traps focus within a container element following the WAI-ARIA dialog focus trap pattern. Listens to focus events on the document and redirects focus back into the container if it escapes via keyboard navigation. Handles both Tab and Shift+Tab wrapping. Mouse clicks outside the container are not intercepted; use a light-dismiss handler for that.',
43
43
  bestPractices: [
44
44
  { guidance: true, description: 'Call focusFirst() when opening a dialog/modal to move focus into the trapped region.' },
45
45
  { guidance: true, description: 'Provide an onEscape callback to close the dialog when Escape is pressed.' },
46
- { guidance: true, description: 'Rely on the built-in focus restoration on close; only add your own onHide focus handling when you need to send focus somewhere other than the previously-focused element.' },
47
46
  { guidance: false, description: 'Use on non-modal content like tooltips or dropdowns; those need light-dismiss, not focus trapping.' },
48
47
  ],
49
48
  },
@@ -68,11 +67,10 @@ export const docsDense = {
68
67
  },
69
68
  usage: {
70
69
  description:
71
- 'Traps focus within container element following WAI-ARIA dialog focus trap pattern. Listens to document focus events + redirects focus back into container if it escapes via keyboard navigation. Handles both Tab + Shift+Tab wrapping. On deactivate/unmount, restores focus to the element focused before activation unless focus already moved elsewhere. Mouse clicks outside container not intercepted; use light-dismiss handler for that.',
70
+ 'Traps focus within container element following WAI-ARIA dialog focus trap pattern. Listens to document focus events + redirects focus back into container if it escapes via keyboard navigation. Handles both Tab + Shift+Tab wrapping. Mouse clicks outside container not intercepted; use light-dismiss handler for that.',
72
71
  bestPractices: [
73
72
  { guidance: true, description: 'Call focusFirst() when opening dialog/modal to move focus into trapped region.' },
74
73
  { guidance: true, description: 'Provide onEscape callback to close dialog when Escape pressed.' },
75
- { guidance: true, description: 'Rely on built-in focus restoration on close; add own onHide focus handling only to send focus somewhere other than previously-focused element.' },
76
74
  { guidance: false, description: 'Use on non-modal content like tooltips / dropdowns; those need light-dismiss, not focus trapping.' },
77
75
  ],
78
76
  },
@@ -11,7 +11,6 @@
11
11
 
12
12
  import {describe, it, expect, vi} from 'vitest';
13
13
  import {render, screen, fireEvent} from '@testing-library/react';
14
- import {FOCUSABLE_SELECTOR} from './focusableSelector';
15
14
  import {useFocusTrap} from './useFocusTrap';
16
15
 
17
16
  function Trap({children}: {children: React.ReactNode}) {
@@ -50,41 +49,6 @@ function EscapeTrap({
50
49
  );
51
50
  }
52
51
 
53
- function RestoreTrap({isActive}: {isActive: boolean}) {
54
- const {containerRef} = useFocusTrap<HTMLDivElement>({isActive});
55
- return (
56
- <div ref={containerRef} data-testid="restore-trap">
57
- <button type="button" data-testid="inside">
58
- Inside trap
59
- </button>
60
- </div>
61
- );
62
- }
63
-
64
- function RestoreFixture({
65
- isActive,
66
- showPrev = true,
67
- showTrap = true,
68
- }: {
69
- isActive: boolean;
70
- showPrev?: boolean;
71
- showTrap?: boolean;
72
- }) {
73
- return (
74
- <div>
75
- {showPrev && (
76
- <button type="button" data-testid="prev">
77
- Previously focused
78
- </button>
79
- )}
80
- <button type="button" data-testid="other">
81
- Other outside
82
- </button>
83
- {showTrap && <RestoreTrap isActive={isActive} />}
84
- </div>
85
- );
86
- }
87
-
88
52
  describe('useFocusTrap tabbable model (infra-8)', () => {
89
53
  it('treats a contenteditable as focusable (focusFirst lands on it)', () => {
90
54
  render(
@@ -120,48 +84,6 @@ describe('useFocusTrap tabbable model (infra-8)', () => {
120
84
  });
121
85
  });
122
86
 
123
- describe('FOCUSABLE_SELECTOR href matching', () => {
124
- // Only real links (<a href>/<area href>) are focusable via href. A bare
125
- // [href] term also matched non-focusable elements carrying href (e.g. a
126
- // <link> in the head, or a custom element), which useFocusTrap would then
127
- // treat as tab stops when computing trap boundaries.
128
- it('matches real links but not other elements carrying href', () => {
129
- const container = document.createElement('div');
130
- container.innerHTML =
131
- '<a href="#a" data-testid="anchor">Anchor</a>' +
132
- '<map name="m">' +
133
- '<area href="#area" shape="rect" coords="0,0,1,1" data-testid="area" />' +
134
- '</map>' +
135
- '<span href="#span" data-testid="span">Span</span>' +
136
- '<link href="#link" data-testid="link" />';
137
-
138
- const matches = Array.from(
139
- container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR),
140
- );
141
- const byTestId = (id: string) =>
142
- container.querySelector(`[data-testid="${id}"]`);
143
-
144
- // Real links are focusable via href.
145
- expect(matches).toContain(byTestId('anchor'));
146
- expect(matches).toContain(byTestId('area'));
147
- // Non-link elements carrying href are not focusable and must be excluded.
148
- expect(matches).not.toContain(byTestId('span'));
149
- expect(matches).not.toContain(byTestId('link'));
150
- });
151
-
152
- it('treats an <a href> inside a trap as focusable (focusFirst lands on it)', () => {
153
- render(
154
- <Trap>
155
- <a href="#link" data-testid="anchor">
156
- Link
157
- </a>
158
- </Trap>,
159
- );
160
- fireEvent.click(screen.getByTestId('focus-first'));
161
- expect(screen.getByTestId('anchor')).toHaveFocus();
162
- });
163
- });
164
-
165
87
  describe('useFocusTrap Escape coordination', () => {
166
88
  it('calls onEscape for a single active trap', () => {
167
89
  const onEscape = vi.fn();
@@ -203,78 +125,8 @@ describe('useFocusTrap Escape coordination', () => {
203
125
  const {rerender} = render(
204
126
  <EscapeTrap isActive onEscape={onEscape} label="toggle" />,
205
127
  );
206
- rerender(
207
- <EscapeTrap isActive={false} onEscape={onEscape} label="toggle" />,
208
- );
128
+ rerender(<EscapeTrap isActive={false} onEscape={onEscape} label="toggle" />);
209
129
  fireEvent.keyDown(document, {key: 'Escape'});
210
130
  expect(onEscape).not.toHaveBeenCalled();
211
131
  });
212
132
  });
213
-
214
- describe('useFocusTrap focus restoration', () => {
215
- it('restores focus to the previously-focused element when deactivated', () => {
216
- const {rerender} = render(<RestoreFixture isActive={false} />);
217
- const prev = screen.getByTestId('prev');
218
- prev.focus();
219
- expect(prev).toHaveFocus();
220
-
221
- // Activate the trap (captures `prev` as the restore target) and move focus
222
- // inside it, as auto-focus or a keyboard user would.
223
- rerender(<RestoreFixture isActive={true} />);
224
- screen.getByTestId('inside').focus();
225
- expect(screen.getByTestId('inside')).toHaveFocus();
226
-
227
- // Deactivating returns focus to where it was before the trap opened.
228
- rerender(<RestoreFixture isActive={false} />);
229
- expect(prev).toHaveFocus();
230
- });
231
-
232
- it('does not steal focus when it was moved elsewhere outside the trap', () => {
233
- const {rerender} = render(<RestoreFixture isActive={false} />);
234
- const prev = screen.getByTestId('prev');
235
- prev.focus();
236
-
237
- rerender(<RestoreFixture isActive={true} />);
238
- // The user (or a consumer that self-restores) moves focus to a different
239
- // outside control while the trap is open.
240
- const other = screen.getByTestId('other');
241
- other.focus();
242
-
243
- rerender(<RestoreFixture isActive={false} />);
244
- // Focus is left where the user put it — not yanked back to `prev`.
245
- expect(other).toHaveFocus();
246
- expect(prev).not.toHaveFocus();
247
- });
248
-
249
- it('does not crash or restore when the captured element was removed', () => {
250
- const {rerender} = render(<RestoreFixture isActive={false} />);
251
- const prev = screen.getByTestId('prev');
252
- prev.focus();
253
-
254
- rerender(<RestoreFixture isActive={true} />);
255
- screen.getByTestId('inside').focus();
256
-
257
- // Remove the captured element from the DOM before the trap deactivates.
258
- rerender(<RestoreFixture isActive={true} showPrev={false} />);
259
- expect(() =>
260
- rerender(<RestoreFixture isActive={false} showPrev={false} />),
261
- ).not.toThrow();
262
- });
263
-
264
- it('restores focus when the trap unmounts while active', () => {
265
- const {rerender} = render(
266
- <RestoreFixture isActive={true} showTrap={false} />,
267
- );
268
- const prev = screen.getByTestId('prev');
269
- prev.focus();
270
-
271
- // Mounting the trap active captures `prev`; then focus moves inside it.
272
- rerender(<RestoreFixture isActive={true} showTrap={true} />);
273
- screen.getByTestId('inside').focus();
274
- expect(screen.getByTestId('inside')).toHaveFocus();
275
-
276
- // Unmounting the trap (cleanup path, not an isActive flip) still restores.
277
- rerender(<RestoreFixture isActive={true} showTrap={false} />);
278
- expect(prev).toHaveFocus();
279
- });
280
- });
@@ -5,8 +5,7 @@
5
5
  /**
6
6
  * @file useFocusTrap.ts
7
7
  * @input Uses React useCallback, useEffect, useRef
8
- * @output Exports useFocusTrap hook for trapping focus within a container and
9
- * restoring focus to the previously-focused element on deactivation
8
+ * @output Exports useFocusTrap hook for trapping focus within a container
10
9
  * @position Core hook; used by dialogs, modals, date pickers
11
10
  *
12
11
  * Based on WAI-ARIA dialog pattern:
@@ -179,9 +178,6 @@ export interface UseFocusTrapReturn<T extends HTMLElement = HTMLElement> {
179
178
  * - Listens to focus events on the document
180
179
  * - Redirects focus back into the container if it escapes
181
180
  * - Handles both Tab and Shift+Tab navigation
182
- * - Restores focus to the element that was focused before activation when the
183
- * trap deactivates or unmounts, unless focus was already moved elsewhere
184
- * (so consumers that restore focus themselves are unaffected)
185
181
  *
186
182
  * @example
187
183
  * ```
@@ -221,51 +217,6 @@ export function useFocusTrap<T extends HTMLElement = HTMLElement>(
221
217
  }
222
218
  }, []);
223
219
 
224
- /**
225
- * Capture the element focused before the trap activated, and restore focus to
226
- * it when the trap deactivates (or the component unmounts). Overlays are
227
- * opened imperatively (e.g. `showPopover()`), so the browser's declarative
228
- * popover focus restoration does not apply — without this, closing a Popover
229
- * via Escape or light dismiss drops keyboard focus to `<body>`.
230
- *
231
- * The restore is guarded so it never steals focus a consumer moved on
232
- * purpose: it only runs when focus would otherwise be lost — i.e. the active
233
- * element is nothing, the document body/root, or still inside the (possibly
234
- * now-unmounted) trap container. If focus already moved to some other element
235
- * outside the trap (the user clicked elsewhere, or a consumer such as
236
- * DropdownMenu already refocused its trigger), the restore is a no-op.
237
- */
238
- useEffect(() => {
239
- if (!isActive) {
240
- return;
241
- }
242
-
243
- const previouslyFocused = document.activeElement as HTMLElement | null;
244
- // Snapshot the container now; by cleanup it may be detached or unmounted.
245
- const container = containerRef.current;
246
-
247
- return () => {
248
- const active = document.activeElement;
249
- const focusWasLost =
250
- active == null ||
251
- active === document.body ||
252
- active === document.documentElement ||
253
- (container != null && container.contains(active));
254
-
255
- if (!focusWasLost) {
256
- return;
257
- }
258
-
259
- if (
260
- previouslyFocused != null &&
261
- previouslyFocused.isConnected &&
262
- typeof previouslyFocused.focus === 'function'
263
- ) {
264
- previouslyFocused.focus();
265
- }
266
- };
267
- }, [isActive]);
268
-
269
220
  /**
270
221
  * Handle focus events - redirect focus back into container if it escapes.
271
222
  * Only redirects for keyboard navigation, not mouse clicks.
@@ -50,9 +50,9 @@ export const docs = {
50
50
  'Syntax themes support light-dark() tuples: each token can have different values for light and dark mode, resolved automatically by the color scheme.',
51
51
  },
52
52
  {
53
- guidance: true,
53
+ guidance: false,
54
54
  description:
55
- 'For a single CodeBlock, pass the syntaxTheme prop directly: it is shorthand for wrapping that block in SyntaxTheme. Use the SyntaxTheme wrapper when theming a whole region of code components.',
55
+ 'Wrap individual CodeBlock instances with SyntaxTheme: use the syntaxTheme prop on CodeBlock directly for per-instance overrides.',
56
56
  },
57
57
  ],
58
58
  },
@@ -96,9 +96,9 @@ export const docsDense = {
96
96
  'Syntax themes support light-dark() tuples: each token can have different values for light/dark mode, resolved automatically by color scheme.',
97
97
  },
98
98
  {
99
- guidance: true,
99
+ guidance: false,
100
100
  description:
101
- 'Single CodeBlock: pass syntaxTheme prop (shorthand for wrapping in SyntaxTheme). Whole region of code components: wrap w/ SyntaxTheme.',
101
+ 'Wrap individual CodeBlock instances w/ SyntaxTheme: use syntaxTheme prop on CodeBlock directly for per-instance overrides instead.',
102
102
  },
103
103
  ],
104
104
  },
@@ -56,8 +56,14 @@ import {
56
56
  generateTypeScaleComponents,
57
57
  type TypeScaleConfig,
58
58
  } from './expandTypeScale';
59
- import {expandMotionScale, type MotionScaleConfig} from './expandMotionScale';
60
- import {expandRadiusScale, type RadiusScaleConfig} from './expandRadiusScale';
59
+ import {
60
+ expandMotionScale,
61
+ type MotionScaleConfig,
62
+ } from './expandMotionScale';
63
+ import {
64
+ expandRadiusScale,
65
+ type RadiusScaleConfig,
66
+ } from './expandRadiusScale';
61
67
  import {expandColorScale, type ColorScaleConfig} from './expandColorScale';
62
68
 
63
69
  import type {DomainTokenName} from './domainTokens';
@@ -147,7 +153,10 @@ export type StyleOverrides = Record<string, string | Record<string, string>>;
147
153
  * }
148
154
  * ```
149
155
  */
150
- export type ComponentStyleMap = Record<string, Record<string, StyleOverrides>>;
156
+ export type ComponentStyleMap = Record<
157
+ string,
158
+ Record<string, StyleOverrides>
159
+ >;
151
160
 
152
161
  /** Input to defineTheme */
153
162
  export interface DefineThemeInput {
@@ -278,7 +287,7 @@ export interface DefineThemeInput {
278
287
  /**
279
288
  * Default syntax highlighting theme for code components.
280
289
  * Sets --color-syntax-* tokens at the theme root. Can be overridden
281
- * per-region (or per-instance) by wrapping in SyntaxTheme.
290
+ * per-region via SyntaxTheme or per-instance via syntaxTheme prop.
282
291
  *
283
292
  * @example
284
293
  * ```tsx
@@ -14,8 +14,8 @@
14
14
 
15
15
  import {describe, it, expect} from 'vitest';
16
16
  import {derivedVarRegistry, getDerivedVars} from './derivedVarRegistry';
17
- import {readdirSync, readFileSync} from 'node:fs';
18
- import {join} from 'node:path';
17
+ import {readdirSync, readFileSync} from 'fs';
18
+ import {join} from 'path';
19
19
 
20
20
  const SRC_DIR = join(__dirname, '..');
21
21
 
@@ -3,7 +3,6 @@
3
3
  import {describe, it, expect} from 'vitest';
4
4
  import {expandColorScale} from './expandColorScale';
5
5
  import {defineTheme} from './defineTheme';
6
- import {generateThemeRules} from './generateThemeRules';
7
6
 
8
7
  describe('expandColorScale', () => {
9
8
  it('produces all expected token keys', () => {
@@ -61,19 +60,6 @@ describe('expandColorScale', () => {
61
60
  expect(warm['--color-neutral']).not.toBe(neutral['--color-neutral']);
62
61
  });
63
62
 
64
- it('emits derived accent tokens as references to --color-accent', () => {
65
- const tokens = expandColorScale({accent: '#0064E0'});
66
- // Reference tokens follow a scoped --color-accent override at runtime.
67
- expect(tokens['--color-text-accent']).toBe('var(--color-accent)');
68
- expect(tokens['--color-icon-accent']).toBe('var(--color-accent)');
69
- expect(tokens['--color-accent-muted']).toBe(
70
- 'light-dark(color-mix(in srgb, var(--color-accent) 20%, transparent), color-mix(in srgb, var(--color-accent) 25%, transparent))',
71
- );
72
- // The base token and the contrast-computed on-accent stay resolved.
73
- expect(tokens['--color-accent']).toMatch(/^light-dark\(#/);
74
- expect(tokens['--color-on-accent']).toMatch(/^light-dark\(#/);
75
- });
76
-
77
63
  it('contrast high produces different --color-text-primary than standard', () => {
78
64
  const standard = expandColorScale({
79
65
  accent: '#0064E0',
@@ -95,19 +81,4 @@ describe('expandColorScale + defineTheme integration', () => {
95
81
  });
96
82
  expect(theme.tokens['--color-accent']).toBe('red');
97
83
  });
98
-
99
- it('generated theme CSS keeps the accent references (#3495)', () => {
100
- const theme = defineTheme({
101
- name: 'test-accent-refs',
102
- color: {accent: '#DC2626'},
103
- });
104
- const css = generateThemeRules(theme).join('\n');
105
- expect(css).toContain('--color-text-accent: var(--color-accent);');
106
- expect(css).toContain('--color-icon-accent: var(--color-accent);');
107
- expect(css).toContain(
108
- '--color-accent-muted: light-dark(color-mix(in srgb, var(--color-accent) 20%, transparent), color-mix(in srgb, var(--color-accent) 25%, transparent));',
109
- );
110
- // The base token itself stays a resolved color pair.
111
- expect(css).toMatch(/--color-accent: light-dark\(#/);
112
- });
113
84
  });
@@ -77,10 +77,6 @@ function ld(light: string, dark: string): string {
77
77
  return `light-dark(${light}, ${dark})`;
78
78
  }
79
79
 
80
- function accentWithAlpha(alpha: number): string {
81
- return `color-mix(in srgb, var(--color-accent) ${alpha * 100}%, transparent)`;
82
- }
83
-
84
80
  /**
85
81
  * Expand a color scale config into Astryx color token overrides.
86
82
  *
@@ -121,11 +117,10 @@ export function expandColorScale(
121
117
  return {
122
118
  // Core semantic
123
119
  '--color-accent': ld(P[40], P[80]),
124
- // Derived accent tokens reference --color-accent instead of baking its
125
- // resolved hex, so a scoped override of the base token re-accents the
126
- // whole subtree at runtime. --color-on-accent stays baked: it is a
127
- // contrast computation against the accent, which CSS cannot express.
128
- '--color-accent-muted': ld(accentWithAlpha(0.2), accentWithAlpha(0.25)),
120
+ '--color-accent-muted': ld(
121
+ hexWithAlpha(P[40], 0.2),
122
+ hexWithAlpha(P[80], 0.25),
123
+ ),
129
124
  '--color-on-accent': ld(P[100], P[20]),
130
125
  '--color-neutral': ld(hexWithAlpha(N[10], 0.1), hexWithAlpha(N[90], 0.2)),
131
126
  '--color-background-surface': ld(N[99], N[10]),
@@ -151,10 +146,10 @@ export function expandColorScale(
151
146
  NV[textSecondaryDarkTone],
152
147
  ),
153
148
  '--color-text-disabled': ld(NV[60], NV[40]),
154
- '--color-text-accent': 'var(--color-accent)',
149
+ '--color-text-accent': ld(P[30], P[80]),
155
150
 
156
151
  // Icon
157
- '--color-icon-accent': 'var(--color-accent)',
152
+ '--color-icon-accent': ld(P[40], P[80]),
158
153
  '--color-icon-primary': ld(N[textPrimaryLightTone], N[textPrimaryDarkTone]),
159
154
  '--color-icon-secondary': ld(
160
155
  NV[textSecondaryLightTone],
package/src/theme/hct.ts CHANGED
@@ -16,8 +16,6 @@
16
16
  * - Tone from L* (CIE Lightness, 0=black, 100=white)
17
17
  */
18
18
 
19
- import {parseHex, formatHex} from '../utils/color';
20
-
21
19
  // =============================================================================
22
20
  // Types
23
21
  // =============================================================================
@@ -123,11 +121,21 @@ export function yToTone(y: number): number {
123
121
  // =============================================================================
124
122
 
125
123
  function hexToRgb(hex: string): [number, number, number] {
126
- const parsed = parseHex(hex);
127
- if (parsed === null) {
128
- return [0, 0, 0];
129
- }
130
- return [parsed.r, parsed.g, parsed.b];
124
+ const h = hex.replace('#', '');
125
+ const full =
126
+ h.length === 3 ? h[0] + h[0] + h[1] + h[1] + h[2] + h[2] : h.slice(0, 6);
127
+ const n = parseInt(full, 16);
128
+ return [(n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff];
129
+ }
130
+
131
+ function rgbToHex(r: number, g: number, b: number): string {
132
+ return (
133
+ '#' +
134
+ [r, g, b]
135
+ .map(c => c.toString(16).padStart(2, '0'))
136
+ .join('')
137
+ .toUpperCase()
138
+ );
131
139
  }
132
140
 
133
141
  // =============================================================================
@@ -173,7 +181,7 @@ export function hctToHex(hct: HCT): string {
173
181
  }
174
182
  if (chroma < 0.5) {
175
183
  const gray = toneToGray(tone);
176
- return formatHex(gray, gray, gray);
184
+ return rgbToHex(gray, gray, gray);
177
185
  }
178
186
 
179
187
  let lo = 0;
@@ -233,7 +241,7 @@ function hctComponentToHex(
233
241
  return null;
234
242
  }
235
243
 
236
- return formatHex(r, g, bVal);
244
+ return rgbToHex(r, g, bVal);
237
245
  }
238
246
 
239
247
  // =============================================================================