@astryxdesign/core 0.1.7-canary.fb9c68c → 0.1.7

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 (137) hide show
  1. package/README.md +15 -24
  2. package/dist/Collapsible/Collapsible.d.ts +1 -11
  3. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  4. package/dist/Collapsible/Collapsible.js +4 -36
  5. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  6. package/dist/ContextMenu/ContextMenu.js +3 -4
  7. package/dist/ContextMenu/index.d.ts +0 -3
  8. package/dist/ContextMenu/index.d.ts.map +1 -1
  9. package/dist/ContextMenu/index.js +1 -7
  10. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  11. package/dist/DropdownMenu/DropdownMenu.js +4 -8
  12. package/dist/DropdownMenu/DropdownMenuContext.d.ts +1 -17
  13. package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
  14. package/dist/DropdownMenu/DropdownMenuContext.js +0 -18
  15. package/dist/DropdownMenu/index.d.ts +1 -5
  16. package/dist/DropdownMenu/index.d.ts.map +1 -1
  17. package/dist/DropdownMenu/index.js +1 -10
  18. package/dist/Icon/Icon.d.ts +12 -17
  19. package/dist/Icon/Icon.d.ts.map +1 -1
  20. package/dist/Icon/Icon.js +20 -28
  21. package/dist/Outline/Outline.d.ts +3 -69
  22. package/dist/Outline/Outline.d.ts.map +1 -1
  23. package/dist/Outline/Outline.js +26 -129
  24. package/dist/Outline/useScrollSpy.d.ts +7 -27
  25. package/dist/Outline/useScrollSpy.d.ts.map +1 -1
  26. package/dist/Outline/useScrollSpy.js +70 -202
  27. package/dist/TabList/Tab.d.ts.map +1 -1
  28. package/dist/TabList/Tab.js +2 -2
  29. package/dist/TabList/TabList.d.ts.map +1 -1
  30. package/dist/TabList/TabList.js +0 -2
  31. package/dist/TabList/TabMenu.d.ts.map +1 -1
  32. package/dist/TabList/TabMenu.js +1 -1
  33. package/dist/Table/index.d.ts +0 -2
  34. package/dist/Table/index.d.ts.map +1 -1
  35. package/dist/Table/index.js +0 -1
  36. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  37. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +2 -24
  38. package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
  39. package/dist/Table/plugins/sortable/useTableSortable.js +7 -18
  40. package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
  41. package/dist/Table/useBaseTablePlugins.js +3 -6
  42. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  43. package/dist/Toolbar/Toolbar.js +1 -8
  44. package/dist/astryx.css +2 -18
  45. package/dist/astryx.umd.js +48 -48
  46. package/dist/astryx.umd.js.map +4 -4
  47. package/dist/theme/Theme.js +1 -1
  48. package/dist/theme/defineTheme.d.ts +1 -1
  49. package/dist/theme/defineTheme.js +1 -1
  50. package/dist/theme/hct.d.ts +1 -0
  51. package/dist/theme/hct.d.ts.map +1 -1
  52. package/dist/theme/hct.js +3 -0
  53. package/locales/en.json +0 -28
  54. package/locales/pseudo.json +0 -21
  55. package/package.json +1 -3
  56. package/src/CheckboxInput/CheckboxInput.doc.mjs +1 -1
  57. package/src/Collapsible/Collapsible.doc.mjs +0 -7
  58. package/src/Collapsible/Collapsible.test.tsx +0 -80
  59. package/src/Collapsible/Collapsible.tsx +5 -58
  60. package/src/ContextMenu/ContextMenu.test.tsx +1 -75
  61. package/src/ContextMenu/ContextMenu.tsx +5 -10
  62. package/src/ContextMenu/index.ts +0 -15
  63. package/src/DropdownMenu/DropdownMenu.doc.mjs +0 -23
  64. package/src/DropdownMenu/DropdownMenu.tsx +6 -11
  65. package/src/DropdownMenu/DropdownMenuContext.tsx +1 -29
  66. package/src/DropdownMenu/DropdownMenuItem.doc.mjs +1 -7
  67. package/src/DropdownMenu/index.ts +3 -23
  68. package/src/Field/Field.doc.mjs +1 -1
  69. package/src/Field/FieldLabel.doc.mjs +1 -1
  70. package/src/Icon/Icon.test.tsx +0 -46
  71. package/src/Icon/Icon.tsx +24 -37
  72. package/src/NumberInput/NumberInput.doc.mjs +2 -2
  73. package/src/Outline/Outline.doc.mjs +3 -123
  74. package/src/Outline/Outline.test.tsx +0 -735
  75. package/src/Outline/Outline.tsx +33 -184
  76. package/src/Outline/useScrollSpy.ts +72 -248
  77. package/src/Selector/SelectorOption.doc.mjs +1 -1
  78. package/src/SideNav/SideNavItem.doc.mjs +2 -2
  79. package/src/Switch/Switch.doc.mjs +1 -1
  80. package/src/TabList/Tab.tsx +1 -5
  81. package/src/TabList/TabList.doc.mjs +3 -3
  82. package/src/TabList/TabList.test.tsx +0 -66
  83. package/src/TabList/TabList.tsx +0 -8
  84. package/src/TabList/TabMenu.tsx +1 -4
  85. package/src/Table/Table.doc.mjs +0 -2
  86. package/src/Table/index.ts +0 -7
  87. package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +1 -90
  88. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +5 -37
  89. package/src/Table/plugins/sortable/useTableSortable.test.tsx +9 -95
  90. package/src/Table/plugins/sortable/useTableSortable.tsx +7 -23
  91. package/src/Table/useBaseTablePlugins.ts +2 -6
  92. package/src/TextArea/TextArea.doc.mjs +1 -1
  93. package/src/TextInput/TextInput.doc.mjs +1 -1
  94. package/src/Toolbar/Toolbar.tsx +3 -10
  95. package/src/theme/Theme.doc.mjs +4 -4
  96. package/src/theme/Theme.tsx +1 -1
  97. package/src/theme/defineTheme.ts +1 -1
  98. package/src/theme/hct.ts +4 -0
  99. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +0 -89
  100. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +0 -1
  101. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +0 -170
  102. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +0 -69
  103. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +0 -1
  104. package/dist/DropdownMenu/DropdownMenuRadioGroup.js +0 -70
  105. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +0 -69
  106. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +0 -1
  107. package/dist/DropdownMenu/DropdownMenuRadioItem.js +0 -178
  108. package/dist/DropdownMenu/menuItemRoles.d.ts +0 -13
  109. package/dist/DropdownMenu/menuItemRoles.d.ts.map +0 -1
  110. package/dist/DropdownMenu/menuItemRoles.js +0 -15
  111. package/dist/Table/plugins/tree/index.d.ts +0 -5
  112. package/dist/Table/plugins/tree/index.d.ts.map +0 -1
  113. package/dist/Table/plugins/tree/index.js +0 -4
  114. package/dist/Table/plugins/tree/useTableTreeData.d.ts +0 -38
  115. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +0 -1
  116. package/dist/Table/plugins/tree/useTableTreeData.js +0 -358
  117. package/dist/Table/plugins/tree/useTableTreeState.d.ts +0 -51
  118. package/dist/Table/plugins/tree/useTableTreeState.d.ts.map +0 -1
  119. package/dist/Table/plugins/tree/useTableTreeState.js +0 -175
  120. package/scripts/postinstall.mjs +0 -93
  121. package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +0 -95
  122. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +0 -237
  123. package/src/DropdownMenu/DropdownMenuRadioGroup.doc.mjs +0 -80
  124. package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +0 -119
  125. package/src/DropdownMenu/DropdownMenuRadioItem.doc.mjs +0 -80
  126. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +0 -232
  127. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +0 -163
  128. package/src/DropdownMenu/menuItemRoles.ts +0 -22
  129. package/src/Table/plugins/tree/index.ts +0 -12
  130. package/src/Table/plugins/tree/useTableTreeData-perf.test.tsx +0 -163
  131. package/src/Table/plugins/tree/useTableTreeData.test.tsx +0 -594
  132. package/src/Table/plugins/tree/useTableTreeData.tsx +0 -495
  133. package/src/Table/plugins/tree/useTableTreeState.test.tsx +0 -636
  134. package/src/Table/plugins/tree/useTableTreeState.tsx +0 -253
  135. package/src/Table/useTableTreeData.doc.mjs +0 -75
  136. package/src/Table/useTableTreeState.doc.mjs +0 -98
  137. package/src/postinstall.test.mjs +0 -71
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file Outline.tsx
7
- * @input Uses React, StyleX, Astryx link provider integration, Outline hooks/types, useListFocus
7
+ * @input Uses React, StyleX, Astryx link provider integration, Outline hooks/types
8
8
  * @output Exports Outline component and OutlineProps type
9
9
  * @position Core implementation; consumed by index.ts
10
10
  *
@@ -12,9 +12,6 @@
12
12
  * - Sliding indicator track (vertical divider + animated active bar)
13
13
  * - Density variant (default/compact)
14
14
  * - Scroll-spy active state when uncontrolled
15
- * - Roving-tabindex keyboard navigation (Arrow/Home/End, Enter/Space activate)
16
- * - onNavigateStart / onNavigateEnd callbacks around the smooth scroll
17
- * - Scroll scoping (offset, scrollContainerRef, hasScrollOnClick)
18
15
  *
19
16
  * SYNC: When modified, update these files to stay in sync:
20
17
  * - /packages/core/src/Outline/Outline.doc.mjs
@@ -35,8 +32,6 @@ import {
35
32
  fontWeightVars,
36
33
  } from '../theme/tokens.stylex';
37
34
  import {useLinkComponent} from '../Link/useLinkComponent';
38
- import {useListFocus} from '../hooks/useListFocus';
39
- import {useIsomorphicLayoutEffect} from '../hooks/useIsomorphicLayoutEffect';
40
35
  import {mergeProps, mergeRefs} from '../utils';
41
36
  import type {BaseProps} from '../BaseProps';
42
37
  import {useScrollSpy} from './useScrollSpy';
@@ -70,55 +65,6 @@ export interface OutlineProps extends BaseProps<HTMLElement> {
70
65
  */
71
66
  density?: 'default' | 'compact';
72
67
 
73
- /**
74
- * Called when navigation to an item begins, before the scroll starts.
75
- * Receives the item `id`. Pair with `onNavigateEnd` to drive an arrival
76
- * effect (flash, ring, pulse) on the target heading.
77
- */
78
- onNavigateStart?: (id: string) => void;
79
-
80
- /**
81
- * Called once when navigation to an item resolves — when the smooth scroll
82
- * settles, or immediately-ish when reduced motion turns it into a jump.
83
- *
84
- * Fires exactly once for every `onNavigateStart`, including when the user
85
- * interrupts the scroll by scrolling manually, so a "navigating" state can
86
- * never leak. It does not fire if the Outline unmounts mid-scroll.
87
- */
88
- onNavigateEnd?: (id: string) => void;
89
-
90
- /**
91
- * Height in px of a fixed header overlaying the top of the scroll root.
92
- *
93
- * Shifts both the activation line *and* the scroll landing by the same
94
- * amount, so a heading activates exactly where navigating to it puts it —
95
- * below the header rather than hidden underneath it.
96
- *
97
- * It composes with each heading's own `scroll-margin-top` (the header, then
98
- * the breathing room below it) rather than replacing it. When nothing
99
- * overlays the content, leave this at 0 and let `scroll-margin-top` do the
100
- * work — the browser already honors it.
101
- *
102
- * @default 0
103
- */
104
- offset?: number;
105
-
106
- /**
107
- * Scroll container to track, instead of auto-detecting the nearest scrollable
108
- * ancestor. Use this when the content scrolls inside a split pane, modal, or
109
- * dashboard panel rather than the viewport.
110
- */
111
- scrollContainerRef?: React.RefObject<HTMLElement | null>;
112
-
113
- /**
114
- * Whether activating an item smooth-scrolls to it. Set to false to own the
115
- * scrolling yourself (virtualized content, a router) — the Outline still
116
- * updates the active item, the hash, and the navigate callbacks, but performs
117
- * no scroll and suppresses the anchor's default jump.
118
- * @default true
119
- */
120
- hasScrollOnClick?: boolean;
121
-
122
68
  /** Test ID for testing frameworks. */
123
69
  'data-testid'?: string;
124
70
  }
@@ -273,15 +219,8 @@ function getIndentStyle(level: number) {
273
219
  * track that animates to the active item.
274
220
  *
275
221
  * When `activeId` is omitted, it tracks scroll position and marks the last
276
- * heading whose top has passed its activation line — which is exactly where
277
- * navigating to that heading lands it: `offset` (a fixed header overlaying the
278
- * scroll root) plus the heading's own `scroll-margin-top`. It defaults to the
279
- * first item at the top and the last at the bottom.
280
- *
281
- * Keyboard: the list is a single tab stop (roving tabindex), seated on the
282
- * active heading. Arrow keys move between headings, Home/End jump to the ends,
283
- * and Enter/Space activate — so a long table of contents costs one Tab press,
284
- * not one per heading.
222
+ * heading whose top has passed its activation line (its scroll-margin-top)
223
+ * active — defaulting to the first item at the top and the last at the bottom.
285
224
  *
286
225
  * @example
287
226
  * ```
@@ -293,21 +232,6 @@ function getIndentStyle(level: number) {
293
232
  * ]}
294
233
  * />
295
234
  * ```
296
- *
297
- * Scoped to a custom scroll container, under a fixed header:
298
- *
299
- * @example
300
- * ```
301
- * const scrollContainerRef = useRef<HTMLDivElement>(null);
302
- *
303
- * <div ref={scrollContainerRef} style={{overflowY: 'auto'}}>...</div>
304
- * <Outline
305
- * items={items}
306
- * scrollContainerRef={scrollContainerRef}
307
- * offset={64}
308
- * onNavigateEnd={id => flashHeading(id)}
309
- * />
310
- * ```
311
235
  */
312
236
  export function Outline({
313
237
  items,
@@ -315,11 +239,6 @@ export function Outline({
315
239
  onActiveIdChange,
316
240
  label: labelFromProps,
317
241
  density = 'default',
318
- onNavigateStart,
319
- onNavigateEnd,
320
- offset = 0,
321
- scrollContainerRef,
322
- hasScrollOnClick = true,
323
242
  xstyle,
324
243
  className,
325
244
  style,
@@ -331,114 +250,50 @@ export function Outline({
331
250
  const label = labelFromProps ?? t('@astryx.outline.label');
332
251
  const rootRef = useRef<HTMLElement | null>(null);
333
252
  const LinkComponent = useLinkComponent();
334
- const {activeId: resolvedActiveId, scrollTo} = useScrollSpy({
253
+ const isControlled = activeId !== undefined;
254
+ const {
255
+ activeId: resolvedActiveId,
256
+ setActiveId,
257
+ lockActiveId,
258
+ } = useScrollSpy({
335
259
  activeId,
336
260
  items,
337
261
  onActiveIdChange,
338
262
  rootRef,
339
- offset,
340
- scrollContainerRef,
341
- hasScrollOnClick,
342
- onNavigateStart,
343
- onNavigateEnd,
344
- });
345
-
346
- // Roving tabindex over the links: the whole outline is one tab stop, and
347
- // arrows move between headings. The hook owns `tabindex` on the items.
348
- const {
349
- listRef,
350
- handleKeyDown: handleListKeyDown,
351
- handleFocus,
352
- } = useListFocus<HTMLUListElement>({
353
- itemSelector: 'a[href]',
354
- orientation: 'vertical',
355
- hasRovingTabIndex: true,
356
263
  });
357
264
 
358
- // Seat the tab stop on the *active* heading. WAI-ARIA puts the single tab
359
- // stop on the current item, so tabbing into a table of contents while
360
- // reading section 7 lands on section 7 — not back at section 1, which is
361
- // where useListFocus would otherwise leave it (it promotes the first item on
362
- // mount and then keeps whichever item already holds the stop).
363
- //
364
- // Only while focus is outside the list: once the reader has arrowed to an
365
- // item, the stop is theirs and scroll-spy must not yank it away. Sets
366
- // `tabindex` only — it never steals focus. Registered after useListFocus so
367
- // it runs after the hook's own layout effect and wins; on later commits the
368
- // hook then finds this item already holding the stop and keeps it.
369
- useIsomorphicLayoutEffect(() => {
370
- const list = listRef.current;
371
- if (list == null || list.contains(document.activeElement)) {
372
- return;
373
- }
374
- const links = Array.from(list.querySelectorAll<HTMLElement>('a[href]'));
375
- const active = links.find(
376
- link => link.getAttribute('aria-current') === 'true',
377
- );
378
- if (active == null) {
379
- return;
380
- }
381
- for (const link of links) {
382
- const tabIndex = link === active ? '0' : '-1';
383
- if (link.getAttribute('tabindex') !== tabIndex) {
384
- link.setAttribute('tabindex', tabIndex);
385
- }
386
- }
387
- });
388
-
389
- /**
390
- * The single navigation path, shared by click and keyboard activation:
391
- * push the hash, then hand off to the scroll-spy's `scrollTo`, which fires
392
- * the navigate callbacks and moves the indicator when the scroll settles.
393
- */
394
- const navigate = (id: string) => {
395
- if (scrollTo(id)) {
396
- window.history.pushState(null, '', `#${id}`);
397
- }
398
- };
399
-
400
- /** Whether an event carries a modifier chord we must leave to the browser. */
401
- const hasModifier = (
402
- event: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
403
- ) => event.metaKey || event.altKey || event.ctrlKey || event.shiftKey;
404
-
405
265
  const handleClick =
406
266
  (id: string) => (event: React.MouseEvent<HTMLElement>) => {
407
- // Let the browser handle modified clicks (open in new tab, etc.) without
408
- // touching the active state.
409
- if (event.defaultPrevented || hasModifier(event)) {
410
- return;
411
- }
412
-
413
- // With no target in the DOM — lazily-rendered or virtualized content —
414
- // there is nothing to scroll to and nothing to make active. Leave the
415
- // anchor to the browser's native fragment navigation (which still
416
- // updates the URL) rather than swallowing the click into a dead link.
417
- if (document.getElementById(id) == null) {
267
+ const target = document.getElementById(id);
268
+
269
+ // Let the browser handle modified clicks (open in new tab, etc.) and
270
+ // missing targets without touching the active state.
271
+ if (
272
+ target == null ||
273
+ event.defaultPrevented ||
274
+ event.metaKey ||
275
+ event.altKey ||
276
+ event.ctrlKey ||
277
+ event.shiftKey
278
+ ) {
418
279
  return;
419
280
  }
420
281
 
421
- // Suppress the anchor's default jump: we own the scroll (or, with
422
- // `hasScrollOnClick={false}`, deliberately leave it to the consumer).
423
282
  event.preventDefault();
424
- navigate(id);
425
- };
283
+ window.history.pushState(null, '', `#${id}`);
426
284
 
427
- const handleKeyDown =
428
- (id: string) => (event: React.KeyboardEvent<HTMLElement>) => {
429
- // Enter is a link's native activation and already produces a click.
430
- // Space is not, so wire it up here to match the button-like affordance.
431
- if (event.key !== ' ' && event.key !== 'Spacebar') {
432
- return;
433
- }
434
- if (event.defaultPrevented || hasModifier(event)) {
435
- return;
285
+ // Move the indicator to the clicked item in a single step. Controlled
286
+ // consumers own the active state (notify only); uncontrolled mode pins
287
+ // the active id and suppresses scroll-spy until the next manual scroll,
288
+ // so the click is honored — even for short/last sections — and the
289
+ // indicator doesn't chase the smooth scroll through other sections.
290
+ if (isControlled) {
291
+ setActiveId(id);
292
+ } else {
293
+ lockActiveId(id);
436
294
  }
437
295
 
438
- // preventDefault does double duty: it stops the page from scrolling, and
439
- // it tells useScrollSpy this Space is an activation, not a manual scroll.
440
- event.preventDefault();
441
- navigate(id);
296
+ target.scrollIntoView({behavior: 'smooth', block: 'start'});
442
297
  };
443
298
 
444
299
  return (
@@ -453,12 +308,7 @@ export function Outline({
453
308
  className,
454
309
  style,
455
310
  )}>
456
- <ul
457
- {...stylex.props(styles.list)}
458
- ref={listRef}
459
- role="list"
460
- onKeyDown={handleListKeyDown}
461
- onFocus={handleFocus}>
311
+ <ul {...stylex.props(styles.list)} role="list">
462
312
  {items.map(item => {
463
313
  const isActive = item.id === resolvedActiveId;
464
314
 
@@ -468,7 +318,6 @@ export function Outline({
468
318
  href={`#${item.id}`}
469
319
  aria-current={isActive ? 'true' : undefined}
470
320
  onClick={handleClick(item.id)}
471
- onKeyDown={handleKeyDown(item.id)}
472
321
  {...mergeProps(
473
322
  themeProps('outline-item', {
474
323
  active: isActive ? 'active' : null,