@astryxdesign/core 0.1.7-canary.e24b070 → 0.1.7-canary.eb8e07b

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 (93) 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/Outline/Outline.d.ts +3 -69
  6. package/dist/Outline/Outline.d.ts.map +1 -1
  7. package/dist/Outline/Outline.js +26 -129
  8. package/dist/Outline/useScrollSpy.d.ts +7 -27
  9. package/dist/Outline/useScrollSpy.d.ts.map +1 -1
  10. package/dist/Outline/useScrollSpy.js +70 -202
  11. package/dist/TabList/Tab.d.ts.map +1 -1
  12. package/dist/TabList/Tab.js +2 -2
  13. package/dist/TabList/TabList.d.ts.map +1 -1
  14. package/dist/TabList/TabList.js +0 -2
  15. package/dist/TabList/TabMenu.d.ts.map +1 -1
  16. package/dist/TabList/TabMenu.js +1 -1
  17. package/dist/Table/index.d.ts +0 -2
  18. package/dist/Table/index.d.ts.map +1 -1
  19. package/dist/Table/index.js +0 -1
  20. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  21. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +2 -24
  22. package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
  23. package/dist/Table/plugins/sortable/useTableSortable.js +7 -18
  24. package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
  25. package/dist/Table/useBaseTablePlugins.js +3 -6
  26. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  27. package/dist/Toolbar/Toolbar.js +1 -8
  28. package/dist/astryx.css +1 -3
  29. package/dist/astryx.umd.js +43 -43
  30. package/dist/astryx.umd.js.map +4 -4
  31. package/dist/theme/Theme.js +1 -1
  32. package/dist/theme/defineTheme.d.ts +1 -1
  33. package/dist/theme/defineTheme.js +1 -1
  34. package/dist/theme/hct.d.ts +1 -0
  35. package/dist/theme/hct.d.ts.map +1 -1
  36. package/dist/theme/hct.js +3 -0
  37. package/locales/en.json +0 -28
  38. package/locales/pseudo.json +0 -21
  39. package/package.json +1 -3
  40. package/src/CheckboxInput/CheckboxInput.doc.mjs +1 -1
  41. package/src/Collapsible/Collapsible.doc.mjs +0 -7
  42. package/src/Collapsible/Collapsible.test.tsx +0 -80
  43. package/src/Collapsible/Collapsible.tsx +5 -58
  44. package/src/DropdownMenu/DropdownMenu.doc.mjs +0 -13
  45. package/src/DropdownMenu/DropdownMenuItem.doc.mjs +1 -7
  46. package/src/Field/Field.doc.mjs +1 -1
  47. package/src/Field/FieldLabel.doc.mjs +1 -1
  48. package/src/NumberInput/NumberInput.doc.mjs +2 -2
  49. package/src/Outline/Outline.doc.mjs +3 -123
  50. package/src/Outline/Outline.test.tsx +0 -735
  51. package/src/Outline/Outline.tsx +33 -184
  52. package/src/Outline/useScrollSpy.ts +72 -248
  53. package/src/Selector/SelectorOption.doc.mjs +1 -1
  54. package/src/SideNav/SideNavItem.doc.mjs +2 -2
  55. package/src/Switch/Switch.doc.mjs +1 -1
  56. package/src/TabList/Tab.tsx +1 -5
  57. package/src/TabList/TabList.doc.mjs +3 -3
  58. package/src/TabList/TabList.test.tsx +0 -66
  59. package/src/TabList/TabList.tsx +0 -8
  60. package/src/TabList/TabMenu.tsx +1 -4
  61. package/src/Table/Table.doc.mjs +0 -2
  62. package/src/Table/index.ts +0 -7
  63. package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +1 -90
  64. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +5 -37
  65. package/src/Table/plugins/sortable/useTableSortable.test.tsx +9 -95
  66. package/src/Table/plugins/sortable/useTableSortable.tsx +7 -23
  67. package/src/Table/useBaseTablePlugins.ts +2 -6
  68. package/src/TextArea/TextArea.doc.mjs +1 -1
  69. package/src/TextInput/TextInput.doc.mjs +1 -1
  70. package/src/Toolbar/Toolbar.tsx +3 -10
  71. package/src/theme/Theme.doc.mjs +4 -4
  72. package/src/theme/Theme.tsx +1 -1
  73. package/src/theme/defineTheme.ts +1 -1
  74. package/src/theme/hct.ts +4 -0
  75. package/dist/Table/plugins/tree/index.d.ts +0 -5
  76. package/dist/Table/plugins/tree/index.d.ts.map +0 -1
  77. package/dist/Table/plugins/tree/index.js +0 -4
  78. package/dist/Table/plugins/tree/useTableTreeData.d.ts +0 -38
  79. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +0 -1
  80. package/dist/Table/plugins/tree/useTableTreeData.js +0 -358
  81. package/dist/Table/plugins/tree/useTableTreeState.d.ts +0 -51
  82. package/dist/Table/plugins/tree/useTableTreeState.d.ts.map +0 -1
  83. package/dist/Table/plugins/tree/useTableTreeState.js +0 -175
  84. package/scripts/postinstall.mjs +0 -93
  85. package/src/Table/plugins/tree/index.ts +0 -12
  86. package/src/Table/plugins/tree/useTableTreeData-perf.test.tsx +0 -163
  87. package/src/Table/plugins/tree/useTableTreeData.test.tsx +0 -594
  88. package/src/Table/plugins/tree/useTableTreeData.tsx +0 -495
  89. package/src/Table/plugins/tree/useTableTreeState.test.tsx +0 -636
  90. package/src/Table/plugins/tree/useTableTreeState.tsx +0 -253
  91. package/src/Table/useTableTreeData.doc.mjs +0 -75
  92. package/src/Table/useTableTreeState.doc.mjs +0 -98
  93. package/src/postinstall.test.mjs +0 -71
package/README.md CHANGED
@@ -2,14 +2,7 @@
2
2
 
3
3
  Core UI components, theme system, and utilities for the Astryx design system. For project setup, see [Quick Start](#quick-start) below.
4
4
 
5
- > **Building with an AI agent?** Add the CLI, then run `init`:
6
- >
7
- > ```bash
8
- > npm install -D @astryxdesign/cli # or: pnpm add -D / yarn add -D / bun add -d
9
- > npx astryx init # resolves to the CLI you just installed
10
- > ```
11
- >
12
- > `init` writes the Astryx component index into your `AGENTS.md`/`CLAUDE.md` so your agent discovers components, templates, and design tokens instead of guessing. Need a single command without installing? Use the scoped package directly — `npx @astryxdesign/cli <cmd>` (or `pnpm dlx`/`bunx @astryxdesign/cli`). Bare `npx astryx` only works once `@astryxdesign/cli` is a dependency; before that npm resolves it to an unrelated package. See [XDS CLI](#xds-cli).
5
+ > **Building with an AI agent?** Run **`npx astryx init`** first. It installs the Astryx CLI's component index into your `AGENTS.md`/`CLAUDE.md` so your agent discovers components, templates, and design tokens instead of guessing. Without it, agents can't see the CLI — see [XDS CLI](#xds-cli).
13
6
 
14
7
  ## Component Docs
15
8
 
@@ -32,9 +25,9 @@ global navigation.
32
25
  Requires `@astryxdesign/cli` (`npm install -D @astryxdesign/cli`):
33
26
 
34
27
  ```bash
35
- astryx template --list # browse all page and block templates
36
- astryx template dashboard # emit full page source
37
- astryx template settings --skeleton # layout skeleton with spatial annotations
28
+ npx astryx template --list # browse all page and block templates
29
+ npx astryx template dashboard # emit full page source
30
+ npx astryx template settings --skeleton # layout skeleton with spatial annotations
38
31
  ```
39
32
 
40
33
  ## Astryx CLI
@@ -42,21 +35,19 @@ astryx template settings --skeleton # layout skeleton with spatial annotations
42
35
  The CLI (`@astryxdesign/cli`) provides additional tooling:
43
36
 
44
37
  ```bash
45
- astryx --help # full listing of all commands
46
- astryx component Button # full docs + related block templates
47
- astryx docs # reference docs (principles, tokens, theming, styling)
48
- astryx docs theme # theming guide (Theme, defineTheme, light/dark)
49
- astryx docs tokens # spacing, color, radius, typography token reference
50
- astryx init # initialize Astryx in your project
51
- astryx theme build # build theme CSS for production
52
- astryx swizzle Button # eject component source for customization
53
- astryx upgrade --apply # run codemods to migrate between versions
54
- astryx discover # discover external Astryx packages
55
- astryx gap-report # report a missing capability
38
+ npx astryx --help # full listing of all commands
39
+ npx astryx component Button # full docs + related block templates
40
+ npx astryx docs # reference docs (principles, tokens, theming, styling)
41
+ npx astryx docs theme # theming guide (Theme, defineTheme, light/dark)
42
+ npx astryx docs tokens # spacing, color, radius, typography token reference
43
+ npx astryx init # initialize Astryx in your project
44
+ npx astryx theme build # build theme CSS for production
45
+ npx astryx swizzle Button # eject component source for customization
46
+ npx astryx upgrade --apply # run codemods to migrate between versions
47
+ npx astryx discover # discover external Astryx packages
48
+ npx astryx gap-report # report a missing capability
56
49
  ```
57
50
 
58
- > Prefix these with your runner: `npx astryx …` / `pnpm exec astryx …` once the CLI is installed, or `npx @astryxdesign/cli …` to run without installing.
59
-
60
51
  ## Related Packages
61
52
 
62
53
  | Package | Description |
@@ -45,16 +45,6 @@ export interface CollapsibleProps extends BaseProps {
45
45
  * Controlled open state. When provided, the component is fully controlled.
46
46
  */
47
47
  isOpen?: boolean;
48
- /**
49
- * Whether the collapsible is disabled. A disabled item can't be toggled —
50
- * its trigger is non-interactive and dimmed. Following the system-wide
51
- * disabled convention, the trigger uses `aria-disabled` (not the native
52
- * `disabled` attribute) and drops out of the tab order, staying perceivable
53
- * to assistive tech. The content stays in whatever open state it was;
54
- * disabling doesn't collapse an already-open item.
55
- * @default false
56
- */
57
- isDisabled?: boolean;
58
48
  /**
59
49
  * Callback when the open state changes.
60
50
  */
@@ -106,7 +96,7 @@ export interface CollapsibleProps extends BaseProps {
106
96
  * </CollapsibleGroup>
107
97
  * ```
108
98
  */
109
- export declare function Collapsible({ trigger, children, defaultIsOpen, isOpen: controlledIsOpen, isDisabled, onOpenChange, value, ref, xstyle, className, style, ...props }: CollapsibleProps): import("react").JSX.Element;
99
+ export declare function Collapsible({ trigger, children, defaultIsOpen, isOpen: controlledIsOpen, onOpenChange, value, ref, xstyle, className, style, ...props }: CollapsibleProps): import("react").JSX.Element;
110
100
  export declare namespace Collapsible {
111
101
  var displayName: string;
112
102
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Collapsible.d.ts","sourceRoot":"","sources":["../../src/Collapsible/Collapsible.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAa,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAiBjD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAkH5C,MAAM,WAAW,gBAAiB,SAAQ,SAAS;IACjD,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;OAGG;IACH,OAAO,EAAE,SAAS,CAAC;IAEnB;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IAErB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;OAEG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAEzC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,QAAQ,EACR,aAAa,EACb,MAAM,EAAE,gBAAgB,EACxB,UAAkB,EAClB,YAAY,EACZ,KAAK,EACL,GAAG,EACH,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,KAAK,EACT,EAAE,gBAAgB,+BA6FlB;yBA1Ge,WAAW"}
1
+ {"version":3,"file":"Collapsible.d.ts","sourceRoot":"","sources":["../../src/Collapsible/Collapsible.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAa,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAgBjD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAkG5C,MAAM,WAAW,gBAAiB,SAAQ,SAAS;IACjD,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;OAGG;IACH,OAAO,EAAE,SAAS,CAAC;IAEnB;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IAErB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;OAEG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAEzC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,QAAQ,EACR,aAAa,EACb,MAAM,EAAE,gBAAgB,EACxB,YAAY,EACZ,KAAK,EACL,GAAG,EACH,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,KAAK,EACT,EAAE,gBAAgB,+BAqElB;yBAjFe,WAAW"}
@@ -58,22 +58,12 @@ const styles = {
58
58
  kInvED: "x1wfwxd8 x7s97pk",
59
59
  $$css: true
60
60
  },
61
- triggerDisabled: {
62
- kkrTdU: "x1h6gzvc",
63
- kSiTet: "xbyyjgo",
64
- $$css: true
65
- },
66
61
  contentHidden: {
67
62
  k1xSpc: "x1s85apg",
68
63
  $$css: true
69
64
  },
70
65
  content: {
71
66
  kLKAdn: "xfsso4q",
72
- kMv6JI: "x9ynric",
73
- kGuDYH: "xjm74w1",
74
- k63SB2: "xxovm9e",
75
- kLWn49: "xw6l6zx",
76
- kMwMTN: "x1tgivj0",
77
67
  $$css: true
78
68
  },
79
69
  divided: {
@@ -165,7 +155,6 @@ export function Collapsible({
165
155
  children,
166
156
  defaultIsOpen,
167
157
  isOpen: controlledIsOpen,
168
- isDisabled = false,
169
158
  onOpenChange,
170
159
  value,
171
160
  ref,
@@ -189,17 +178,6 @@ export function Collapsible({
189
178
  isCollapsible: collapsibleConfig,
190
179
  value
191
180
  });
192
-
193
- // Activation is blocked by this guard rather than the native `disabled`
194
- // attribute, so the trigger keeps `aria-disabled` semantics and stays
195
- // discoverable. A native `disabled` button would silently swallow events
196
- // (e.g. a wrapping tooltip's hover) — the system-wide disabled convention.
197
- const handleToggle = () => {
198
- if (isDisabled) {
199
- return;
200
- }
201
- toggle();
202
- };
203
181
  const presentation = use(CollapsibleGroupPresentationContext);
204
182
  const isDivided = presentation?.hasDividers ?? false;
205
183
  const density = presentation?.density ?? null;
@@ -216,18 +194,10 @@ export function Collapsible({
216
194
  ...props,
217
195
  children: [/*#__PURE__*/_jsxs("button", {
218
196
  type: "button",
219
- onClick: handleToggle,
220
- "aria-disabled": isDisabled || undefined,
197
+ onClick: toggle,
221
198
  "aria-expanded": isOpen,
222
- "aria-controls": contentId
223
- // A disabled trigger drops out of the tab order so it isn't a silently
224
- // dead tab stop; activation stays blocked by the handleToggle guard,
225
- // and aria-disabled keeps the state perceivable to assistive tech —
226
- // the system-wide disabled convention (never native `disabled`, which
227
- // would swallow events like a wrapping tooltip's hover).
228
- ,
229
- tabIndex: isDisabled ? -1 : undefined,
230
- ...stylex.props(styles.trigger, density != null && triggerDensity[density], isDisabled && styles.triggerDisabled),
199
+ "aria-controls": contentId,
200
+ ...stylex.props(styles.trigger, density != null && triggerDensity[density]),
231
201
  children: [/*#__PURE__*/_jsx("span", {
232
202
  ...{
233
203
  className: "x1b2iylo xwgcxoh"
@@ -246,9 +216,7 @@ export function Collapsible({
246
216
  })]
247
217
  }), /*#__PURE__*/_jsx("div", {
248
218
  id: contentId,
249
- ...mergeProps(themeProps('collapsible-content', {
250
- density: density ?? undefined
251
- }), stylex.props(styles.content, density != null && contentDensity[density], !isOpen && styles.contentHidden)),
219
+ ...stylex.props(styles.content, density != null && contentDensity[density], !isOpen && styles.contentHidden),
252
220
  children: presentation != null ? /*#__PURE__*/_jsx(CollapsibleGroupPresentationContext, {
253
221
  value: null,
254
222
  children: children
@@ -19,50 +19,6 @@ export interface OutlineProps extends BaseProps<HTMLElement> {
19
19
  * @default 'default'
20
20
  */
21
21
  density?: 'default' | 'compact';
22
- /**
23
- * Called when navigation to an item begins, before the scroll starts.
24
- * Receives the item `id`. Pair with `onNavigateEnd` to drive an arrival
25
- * effect (flash, ring, pulse) on the target heading.
26
- */
27
- onNavigateStart?: (id: string) => void;
28
- /**
29
- * Called once when navigation to an item resolves — when the smooth scroll
30
- * settles, or immediately-ish when reduced motion turns it into a jump.
31
- *
32
- * Fires exactly once for every `onNavigateStart`, including when the user
33
- * interrupts the scroll by scrolling manually, so a "navigating" state can
34
- * never leak. It does not fire if the Outline unmounts mid-scroll.
35
- */
36
- onNavigateEnd?: (id: string) => void;
37
- /**
38
- * Height in px of a fixed header overlaying the top of the scroll root.
39
- *
40
- * Shifts both the activation line *and* the scroll landing by the same
41
- * amount, so a heading activates exactly where navigating to it puts it —
42
- * below the header rather than hidden underneath it.
43
- *
44
- * It composes with each heading's own `scroll-margin-top` (the header, then
45
- * the breathing room below it) rather than replacing it. When nothing
46
- * overlays the content, leave this at 0 and let `scroll-margin-top` do the
47
- * work — the browser already honors it.
48
- *
49
- * @default 0
50
- */
51
- offset?: number;
52
- /**
53
- * Scroll container to track, instead of auto-detecting the nearest scrollable
54
- * ancestor. Use this when the content scrolls inside a split pane, modal, or
55
- * dashboard panel rather than the viewport.
56
- */
57
- scrollContainerRef?: React.RefObject<HTMLElement | null>;
58
- /**
59
- * Whether activating an item smooth-scrolls to it. Set to false to own the
60
- * scrolling yourself (virtualized content, a router) — the Outline still
61
- * updates the active item, the hash, and the navigate callbacks, but performs
62
- * no scroll and suppresses the anchor's default jump.
63
- * @default true
64
- */
65
- hasScrollOnClick?: boolean;
66
22
  /** Test ID for testing frameworks. */
67
23
  'data-testid'?: string;
68
24
  }
@@ -74,15 +30,8 @@ export interface OutlineProps extends BaseProps<HTMLElement> {
74
30
  * track that animates to the active item.
75
31
  *
76
32
  * When `activeId` is omitted, it tracks scroll position and marks the last
77
- * heading whose top has passed its activation line — which is exactly where
78
- * navigating to that heading lands it: `offset` (a fixed header overlaying the
79
- * scroll root) plus the heading's own `scroll-margin-top`. It defaults to the
80
- * first item at the top and the last at the bottom.
81
- *
82
- * Keyboard: the list is a single tab stop (roving tabindex), seated on the
83
- * active heading. Arrow keys move between headings, Home/End jump to the ends,
84
- * and Enter/Space activate — so a long table of contents costs one Tab press,
85
- * not one per heading.
33
+ * heading whose top has passed its activation line (its scroll-margin-top)
34
+ * active — defaulting to the first item at the top and the last at the bottom.
86
35
  *
87
36
  * @example
88
37
  * ```
@@ -94,23 +43,8 @@ export interface OutlineProps extends BaseProps<HTMLElement> {
94
43
  * ]}
95
44
  * />
96
45
  * ```
97
- *
98
- * Scoped to a custom scroll container, under a fixed header:
99
- *
100
- * @example
101
- * ```
102
- * const scrollContainerRef = useRef<HTMLDivElement>(null);
103
- *
104
- * <div ref={scrollContainerRef} style={{overflowY: 'auto'}}>...</div>
105
- * <Outline
106
- * items={items}
107
- * scrollContainerRef={scrollContainerRef}
108
- * offset={64}
109
- * onNavigateEnd={id => flashHeading(id)}
110
- * />
111
- * ```
112
46
  */
113
- export declare function Outline({ items, activeId, onActiveIdChange, label: labelFromProps, density, onNavigateStart, onNavigateEnd, offset, scrollContainerRef, hasScrollOnClick, xstyle, className, style, ref, 'data-testid': testId, ...props }: OutlineProps): import("react").JSX.Element;
47
+ export declare function Outline({ items, activeId, onActiveIdChange, label: labelFromProps, density, xstyle, className, style, ref, 'data-testid': testId, ...props }: OutlineProps): import("react").JSX.Element;
114
48
  export declare namespace Outline {
115
49
  var displayName: string;
116
50
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Outline.d.ts","sourceRoot":"","sources":["../../src/Outline/Outline.tsx"],"names":[],"mappings":"AAwCA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAE5C,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,SAAS,CAAC;AAIzC,YAAY,EAAC,WAAW,EAAC,MAAM,SAAS,CAAC;AAEzC,MAAM,WAAW,YAAa,SAAQ,SAAS,CAAC,WAAW,CAAC;IAC1D,6CAA6C;IAC7C,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAE7B,+CAA+C;IAC/C,KAAK,EAAE,WAAW,EAAE,CAAC;IAErB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAExC,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;;OAKG;IACH,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAEhC;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAEvC;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAErC;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAEzD;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B,sCAAsC;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAgJD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAgB,OAAO,CAAC,EACtB,KAAK,EACL,QAAQ,EACR,gBAAgB,EAChB,KAAK,EAAE,cAAc,EACrB,OAAmB,EACnB,eAAe,EACf,aAAa,EACb,MAAU,EACV,kBAAkB,EAClB,gBAAuB,EACvB,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,aAAa,EAAE,MAAM,EACrB,GAAG,KAAK,EACT,EAAE,YAAY,+BAiLd;yBAlMe,OAAO"}
1
+ {"version":3,"file":"Outline.d.ts","sourceRoot":"","sources":["../../src/Outline/Outline.tsx"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAE5C,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,SAAS,CAAC;AAIzC,YAAY,EAAC,WAAW,EAAC,MAAM,SAAS,CAAC;AAEzC,MAAM,WAAW,YAAa,SAAQ,SAAS,CAAC,WAAW,CAAC;IAC1D,6CAA6C;IAC7C,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAE7B,+CAA+C;IAC/C,KAAK,EAAE,WAAW,EAAE,CAAC;IAErB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAExC,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;;OAKG;IACH,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAEhC,sCAAsC;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAgJD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,OAAO,CAAC,EACtB,KAAK,EACL,QAAQ,EACR,gBAAgB,EAChB,KAAK,EAAE,cAAc,EACrB,OAAmB,EACnB,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,aAAa,EAAE,MAAM,EACrB,GAAG,KAAK,EACT,EAAE,YAAY,+BA2Gd;yBAvHe,OAAO"}
@@ -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
@@ -27,8 +24,6 @@ import * as stylex from '@stylexjs/stylex';
27
24
  import "../theme/tokens.stylex.js";
28
25
  import { colorVars, durationVars, easeVars, radiusVars, spacingVars, typeScaleVars, fontWeightVars } from "../theme/tokens.stylex.js";
29
26
  import { useLinkComponent } from "../Link/useLinkComponent.js";
30
- import { useListFocus } from "../hooks/useListFocus.js";
31
- import { useIsomorphicLayoutEffect } from "../hooks/useIsomorphicLayoutEffect.js";
32
27
  import { mergeProps, mergeRefs } from "../utils/index.js";
33
28
  import { useScrollSpy } from "./useScrollSpy.js";
34
29
  import { themeProps } from "../utils/themeProps.js";
@@ -140,15 +135,8 @@ function getIndentStyle(level) {
140
135
  * track that animates to the active item.
141
136
  *
142
137
  * When `activeId` is omitted, it tracks scroll position and marks the last
143
- * heading whose top has passed its activation line — which is exactly where
144
- * navigating to that heading lands it: `offset` (a fixed header overlaying the
145
- * scroll root) plus the heading's own `scroll-margin-top`. It defaults to the
146
- * first item at the top and the last at the bottom.
147
- *
148
- * Keyboard: the list is a single tab stop (roving tabindex), seated on the
149
- * active heading. Arrow keys move between headings, Home/End jump to the ends,
150
- * and Enter/Space activate — so a long table of contents costs one Tab press,
151
- * not one per heading.
138
+ * heading whose top has passed its activation line (its scroll-margin-top)
139
+ * active — defaulting to the first item at the top and the last at the bottom.
152
140
  *
153
141
  * @example
154
142
  * ```
@@ -160,21 +148,6 @@ function getIndentStyle(level) {
160
148
  * ]}
161
149
  * />
162
150
  * ```
163
- *
164
- * Scoped to a custom scroll container, under a fixed header:
165
- *
166
- * @example
167
- * ```
168
- * const scrollContainerRef = useRef<HTMLDivElement>(null);
169
- *
170
- * <div ref={scrollContainerRef} style={{overflowY: 'auto'}}>...</div>
171
- * <Outline
172
- * items={items}
173
- * scrollContainerRef={scrollContainerRef}
174
- * offset={64}
175
- * onNavigateEnd={id => flashHeading(id)}
176
- * />
177
- * ```
178
151
  */
179
152
  export function Outline({
180
153
  items,
@@ -182,11 +155,6 @@ export function Outline({
182
155
  onActiveIdChange,
183
156
  label: labelFromProps,
184
157
  density = 'default',
185
- onNavigateStart,
186
- onNavigateEnd,
187
- offset = 0,
188
- scrollContainerRef,
189
- hasScrollOnClick = true,
190
158
  xstyle,
191
159
  className,
192
160
  style,
@@ -198,109 +166,42 @@ export function Outline({
198
166
  const label = labelFromProps ?? t('@astryx.outline.label');
199
167
  const rootRef = useRef(null);
200
168
  const LinkComponent = useLinkComponent();
169
+ const isControlled = activeId !== undefined;
201
170
  const {
202
171
  activeId: resolvedActiveId,
203
- scrollTo
172
+ setActiveId,
173
+ lockActiveId
204
174
  } = useScrollSpy({
205
175
  activeId,
206
176
  items,
207
177
  onActiveIdChange,
208
- rootRef,
209
- offset,
210
- scrollContainerRef,
211
- hasScrollOnClick,
212
- onNavigateStart,
213
- onNavigateEnd
178
+ rootRef
214
179
  });
215
-
216
- // Roving tabindex over the links: the whole outline is one tab stop, and
217
- // arrows move between headings. The hook owns `tabindex` on the items.
218
- const {
219
- listRef,
220
- handleKeyDown: handleListKeyDown,
221
- handleFocus
222
- } = useListFocus({
223
- itemSelector: 'a[href]',
224
- orientation: 'vertical',
225
- hasRovingTabIndex: true
226
- });
227
-
228
- // Seat the tab stop on the *active* heading. WAI-ARIA puts the single tab
229
- // stop on the current item, so tabbing into a table of contents while
230
- // reading section 7 lands on section 7 — not back at section 1, which is
231
- // where useListFocus would otherwise leave it (it promotes the first item on
232
- // mount and then keeps whichever item already holds the stop).
233
- //
234
- // Only while focus is outside the list: once the reader has arrowed to an
235
- // item, the stop is theirs and scroll-spy must not yank it away. Sets
236
- // `tabindex` only — it never steals focus. Registered after useListFocus so
237
- // it runs after the hook's own layout effect and wins; on later commits the
238
- // hook then finds this item already holding the stop and keeps it.
239
- useIsomorphicLayoutEffect(() => {
240
- const list = listRef.current;
241
- if (list == null || list.contains(document.activeElement)) {
242
- return;
243
- }
244
- const links = Array.from(list.querySelectorAll('a[href]'));
245
- const active = links.find(link => link.getAttribute('aria-current') === 'true');
246
- if (active == null) {
247
- return;
248
- }
249
- for (const link of links) {
250
- const tabIndex = link === active ? '0' : '-1';
251
- if (link.getAttribute('tabindex') !== tabIndex) {
252
- link.setAttribute('tabindex', tabIndex);
253
- }
254
- }
255
- });
256
-
257
- /**
258
- * The single navigation path, shared by click and keyboard activation:
259
- * push the hash, then hand off to the scroll-spy's `scrollTo`, which fires
260
- * the navigate callbacks and moves the indicator when the scroll settles.
261
- */
262
- const navigate = id => {
263
- if (scrollTo(id)) {
264
- window.history.pushState(null, '', `#${id}`);
265
- }
266
- };
267
-
268
- /** Whether an event carries a modifier chord we must leave to the browser. */
269
- const hasModifier = event => event.metaKey || event.altKey || event.ctrlKey || event.shiftKey;
270
180
  const handleClick = id => event => {
271
- // Let the browser handle modified clicks (open in new tab, etc.) without
272
- // touching the active state.
273
- if (event.defaultPrevented || hasModifier(event)) {
274
- return;
275
- }
181
+ const target = document.getElementById(id);
276
182
 
277
- // With no target in the DOM — lazily-rendered or virtualized content —
278
- // there is nothing to scroll to and nothing to make active. Leave the
279
- // anchor to the browser's native fragment navigation (which still
280
- // updates the URL) rather than swallowing the click into a dead link.
281
- if (document.getElementById(id) == null) {
183
+ // Let the browser handle modified clicks (open in new tab, etc.) and
184
+ // missing targets without touching the active state.
185
+ if (target == null || event.defaultPrevented || event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) {
282
186
  return;
283
187
  }
284
-
285
- // Suppress the anchor's default jump: we own the scroll (or, with
286
- // `hasScrollOnClick={false}`, deliberately leave it to the consumer).
287
188
  event.preventDefault();
288
- navigate(id);
289
- };
290
- const handleKeyDown = id => event => {
291
- // Enter is a link's native activation and already produces a click.
292
- // Space is not, so wire it up here to match the button-like affordance.
293
- if (event.key !== ' ' && event.key !== 'Spacebar') {
294
- return;
295
- }
296
- if (event.defaultPrevented || hasModifier(event)) {
297
- return;
298
- }
189
+ window.history.pushState(null, '', `#${id}`);
299
190
 
300
- // preventDefault does double duty: it stops the page from scrolling, and
301
- // it tells useScrollSpy this Space is an activation, not a manual scroll.
302
- event.preventDefault();
303
- navigate(id);
191
+ // Move the indicator to the clicked item in a single step. Controlled
192
+ // consumers own the active state (notify only); uncontrolled mode pins
193
+ // the active id and suppresses scroll-spy until the next manual scroll,
194
+ // so the click is honored — even for short/last sections — and the
195
+ // indicator doesn't chase the smooth scroll through other sections.
196
+ if (isControlled) {
197
+ setActiveId(id);
198
+ } else {
199
+ lockActiveId(id);
200
+ }
201
+ target.scrollIntoView({
202
+ behavior: 'smooth',
203
+ block: 'start'
204
+ });
304
205
  };
305
206
  return /*#__PURE__*/_jsxs("nav", {
306
207
  ...props,
@@ -314,10 +215,7 @@ export function Outline({
314
215
  ...{
315
216
  className: "x78zum5 xdt5ytf x1lsbc85 x1ghz6dp x1717udv xe8uvvx x98rzlu xeuugli"
316
217
  },
317
- ref: listRef,
318
218
  role: "list",
319
- onKeyDown: handleListKeyDown,
320
- onFocus: handleFocus,
321
219
  children: items.map(item => {
322
220
  const isActive = item.id === resolvedActiveId;
323
221
  return /*#__PURE__*/_jsx("li", {
@@ -329,7 +227,6 @@ export function Outline({
329
227
  href: `#${item.id}`,
330
228
  "aria-current": isActive ? 'true' : undefined,
331
229
  onClick: handleClick(item.id),
332
- onKeyDown: handleKeyDown(item.id),
333
230
  ...mergeProps(themeProps('outline-item', {
334
231
  active: isActive ? 'active' : null,
335
232
  level: item.level
@@ -4,40 +4,20 @@ interface UseScrollSpyOptions {
4
4
  items: OutlineItem[];
5
5
  onActiveIdChange?: (id: string) => void;
6
6
  rootRef: React.RefObject<HTMLElement | null>;
7
- /**
8
- * Height in px of a fixed header overlaying the top of the scroll root.
9
- * Shifts both the activation line and the scroll landing by the same amount,
10
- * on top of each heading's own `scroll-margin-top`.
11
- */
12
- offset?: number;
13
- /** Scroll container to track, instead of the nearest scrollable ancestor. */
14
- scrollContainerRef?: React.RefObject<HTMLElement | null>;
15
- /** Whether {@link UseScrollSpyResult.scrollTo} performs the smooth scroll. */
16
- hasScrollOnClick?: boolean;
17
- /** Called when a navigation begins, before the scroll starts. */
18
- onNavigateStart?: (id: string) => void;
19
- /** Called once per navigation, when the scroll settles or is interrupted. */
20
- onNavigateEnd?: (id: string) => void;
21
7
  }
22
8
  interface UseScrollSpyResult {
23
9
  activeId: string | undefined;
24
10
  /** Set the active id (notifies onActiveIdChange). For controlled consumers. */
25
11
  setActiveId: (id: string) => void;
26
12
  /**
27
- * Navigate to the item with id `id` — the single path shared by click and
28
- * keyboard activation.
29
- *
30
- * Fires `onNavigateStart(id)`, scrolls (unless `hasScrollOnClick` is false),
31
- * and fires `onNavigateEnd(id)` exactly once when the scroll settles or the
32
- * user interrupts it. While uncontrolled, scroll-spy is suppressed for the
33
- * duration so the indicator doesn't chase the scroll through intervening
34
- * sections; it lands on the target once the scroll settles, or resumes
35
- * position tracking if the user scrolls away mid-flight.
36
- *
37
- * Returns false (and does nothing) when no element with `id` exists.
13
+ * Handle a click on the outline item with id `id`. Delays moving the
14
+ * indicator: scroll-spy is suppressed during the programmatic smooth scroll
15
+ * so the indicator doesn't chase it, then the indicator moves once to the
16
+ * clicked item when the scroll settles. If the user scrolls manually mid-way,
17
+ * scroll-position tracking resumes immediately instead.
38
18
  */
39
- scrollTo: (id: string) => boolean;
19
+ lockActiveId: (id: string) => void;
40
20
  }
41
- export declare function useScrollSpy({ activeId, items, onActiveIdChange, rootRef, offset, scrollContainerRef, hasScrollOnClick, onNavigateStart, onNavigateEnd, }: UseScrollSpyOptions): UseScrollSpyResult;
21
+ export declare function useScrollSpy({ activeId, items, onActiveIdChange, rootRef, }: UseScrollSpyOptions): UseScrollSpyResult;
42
22
  export {};
43
23
  //# sourceMappingURL=useScrollSpy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useScrollSpy.d.ts","sourceRoot":"","sources":["../../src/Outline/useScrollSpy.ts"],"names":[],"mappings":"AAqCA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,SAAS,CAAC;AAsJzC,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,gBAAgB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC7C;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,kBAAkB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IACzD,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iEAAiE;IACjE,eAAe,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,6EAA6E;IAC7E,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,+EAA+E;IAC/E,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;;;;;;;;;;;OAYG;IACH,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;CACnC;AAED,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,KAAK,EACL,gBAAgB,EAChB,OAAO,EACP,MAAU,EACV,kBAAkB,EAClB,gBAAuB,EACvB,eAAe,EACf,aAAa,GACd,EAAE,mBAAmB,GAAG,kBAAkB,CAoN1C"}
1
+ {"version":3,"file":"useScrollSpy.d.ts","sourceRoot":"","sources":["../../src/Outline/useScrollSpy.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,SAAS,CAAC;AAuFzC,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,gBAAgB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CAC9C;AAED,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,+EAA+E;IAC/E,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;;;;;OAMG;IACH,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,KAAK,EACL,gBAAgB,EAChB,OAAO,GACR,EAAE,mBAAmB,GAAG,kBAAkB,CA2I1C"}