@astryxdesign/lab 0.2.0-canary.fce8b64 → 0.3.0-canary.041a0ef

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 (109) hide show
  1. package/README.md +6 -0
  2. package/dist/BottomSheet/BottomSheet.d.ts +29 -7
  3. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  4. package/dist/BottomSheet/BottomSheet.js +73 -24
  5. package/dist/BottomSheet/snapOffsets.d.ts +13 -5
  6. package/dist/BottomSheet/snapOffsets.d.ts.map +1 -1
  7. package/dist/BottomSheet/snapOffsets.js +22 -9
  8. package/dist/BottomSheet/useSheetGestures.d.ts +1 -1
  9. package/dist/BottomSheet/useSheetGestures.js +1 -1
  10. package/dist/ChatReasoning/ChatReasoning.d.ts.map +1 -1
  11. package/dist/ChatReasoning/ChatReasoning.js +10 -1
  12. package/dist/ListInput/ListInput.d.ts +138 -0
  13. package/dist/ListInput/ListInput.d.ts.map +1 -0
  14. package/dist/ListInput/ListInput.js +1254 -0
  15. package/dist/ListInput/index.d.ts +11 -0
  16. package/dist/ListInput/index.d.ts.map +1 -0
  17. package/dist/ListInput/index.js +13 -0
  18. package/dist/LogStream/LogStream.d.ts +4 -0
  19. package/dist/LogStream/LogStream.d.ts.map +1 -1
  20. package/dist/LogStream/LogStream.js +12 -1
  21. package/dist/RichTextEditor/RichTextEditor.d.ts +12 -2
  22. package/dist/RichTextEditor/RichTextEditor.d.ts.map +1 -1
  23. package/dist/RichTextEditor/RichTextEditor.js +81 -7
  24. package/dist/SVGIcon/SVGIcon.d.ts.map +1 -1
  25. package/dist/SVGIcon/SVGIcon.js +14 -1
  26. package/dist/Stepper/Step.d.ts +5 -0
  27. package/dist/Stepper/Step.d.ts.map +1 -1
  28. package/dist/Stepper/Step.js +42 -15
  29. package/dist/Tour/Tour.d.ts +85 -0
  30. package/dist/Tour/Tour.d.ts.map +1 -0
  31. package/dist/Tour/Tour.js +112 -0
  32. package/dist/Tour/TourContext.d.ts +38 -0
  33. package/dist/Tour/TourContext.d.ts.map +1 -0
  34. package/dist/Tour/TourContext.js +36 -0
  35. package/dist/Tour/TourStep.d.ts +77 -0
  36. package/dist/Tour/TourStep.d.ts.map +1 -0
  37. package/dist/Tour/TourStep.js +307 -0
  38. package/dist/Tour/index.d.ts +16 -0
  39. package/dist/Tour/index.d.ts.map +1 -0
  40. package/dist/Tour/index.js +15 -0
  41. package/dist/Tour/useTour.d.ts +27 -0
  42. package/dist/Tour/useTour.d.ts.map +1 -0
  43. package/dist/Tour/useTour.js +41 -0
  44. package/dist/TransferList/TransferList.d.ts +90 -0
  45. package/dist/TransferList/TransferList.d.ts.map +1 -0
  46. package/dist/TransferList/TransferList.js +895 -0
  47. package/dist/TransferList/TransferListSelector.d.ts +57 -0
  48. package/dist/TransferList/TransferListSelector.d.ts.map +1 -0
  49. package/dist/TransferList/TransferListSelector.js +211 -0
  50. package/dist/TransferList/index.d.ts +10 -0
  51. package/dist/TransferList/index.d.ts.map +1 -0
  52. package/dist/TransferList/index.js +12 -0
  53. package/dist/TransferList/tokens.stylex.d.ts +15 -0
  54. package/dist/TransferList/tokens.stylex.d.ts.map +1 -0
  55. package/dist/TransferList/tokens.stylex.js +19 -0
  56. package/dist/index.d.ts +3 -1
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +9 -1
  59. package/dist/lab.css +97 -2
  60. package/dist/reorderStyles.d.ts +69 -0
  61. package/dist/reorderStyles.d.ts.map +1 -0
  62. package/dist/reorderStyles.js +82 -0
  63. package/package.json +3 -3
  64. package/src/BottomSheet/BottomSheet.doc.mjs +21 -2
  65. package/src/BottomSheet/BottomSheet.test.tsx +93 -0
  66. package/src/BottomSheet/BottomSheet.tsx +93 -22
  67. package/src/BottomSheet/snapOffsets.test.ts +11 -5
  68. package/src/BottomSheet/snapOffsets.ts +22 -9
  69. package/src/BottomSheet/useSheetGestures.doc.mjs +1 -1
  70. package/src/BottomSheet/useSheetGestures.test.ts +11 -10
  71. package/src/BottomSheet/useSheetGestures.ts +2 -2
  72. package/src/ChatReasoning/ChatReasoning.test.tsx +25 -0
  73. package/src/ChatReasoning/ChatReasoning.tsx +9 -1
  74. package/src/ListInput/ListInput.doc.mjs +375 -0
  75. package/src/ListInput/ListInput.test.tsx +1110 -0
  76. package/src/ListInput/ListInput.tsx +1800 -0
  77. package/src/ListInput/index.ts +21 -0
  78. package/src/LogStream/LogStream.doc.mjs +1 -1
  79. package/src/LogStream/LogStream.test.tsx +25 -0
  80. package/src/LogStream/LogStream.tsx +10 -0
  81. package/src/RichTextEditor/RichTextEditor.doc.mjs +9 -2
  82. package/src/RichTextEditor/RichTextEditor.test.tsx +139 -1
  83. package/src/RichTextEditor/RichTextEditor.tsx +126 -7
  84. package/src/SVGIcon/SVGIcon.test.tsx +40 -0
  85. package/src/SVGIcon/SVGIcon.tsx +14 -6
  86. package/src/Stepper/Step.tsx +51 -7
  87. package/src/Stepper/Stepper.doc.mjs +5 -3
  88. package/src/Stepper/Stepper.test.tsx +84 -3
  89. package/src/Tour/Tour.doc.mjs +106 -0
  90. package/src/Tour/Tour.test.tsx +261 -0
  91. package/src/Tour/Tour.tsx +171 -0
  92. package/src/Tour/TourContext.ts +59 -0
  93. package/src/Tour/TourStep.tsx +398 -0
  94. package/src/Tour/index.ts +23 -0
  95. package/src/Tour/useTour.ts +58 -0
  96. package/src/TransferList/TransferList.composition.test.tsx +288 -0
  97. package/src/TransferList/TransferList.doc.mjs +749 -0
  98. package/src/TransferList/TransferList.test.tsx +888 -0
  99. package/src/TransferList/TransferList.tsx +1323 -0
  100. package/src/TransferList/TransferListSelector.test.tsx +562 -0
  101. package/src/TransferList/TransferListSelector.tsx +315 -0
  102. package/src/TransferList/index.ts +20 -0
  103. package/src/TransferList/tokens.stylex.ts +18 -0
  104. package/src/index.ts +25 -1
  105. package/src/reorderStyles.ts +76 -0
  106. package/dist/CodeBlock/index.d.ts +0 -8
  107. package/dist/CodeBlock/index.d.ts.map +0 -1
  108. package/dist/CodeBlock/index.js +0 -9
  109. package/src/CodeBlock/index.ts +0 -10
package/README.md CHANGED
@@ -20,6 +20,12 @@ See the **[Component Lifecycle](https://github.com/facebook/astryx/wiki/Componen
20
20
 
21
21
  **Core:** Full keyboard/a11y, hover guards, theming story, status states, spec compliance, vibe tested. Shipped to consumers on `latest`.
22
22
 
23
+ ## Promotion gate: accessibility
24
+
25
+ The [Accessibility Checklist](https://github.com/facebook/astryx/wiki/Accessibility-Checklist) is a hard requirement for graduating to core — every item, verified in the promotion PR, alongside the hardening bar linked above.
26
+
27
+ Lab components get the same scan coverage as core (the `pr-a11y` axe audit and the weekly full scan run against lab too), but a11y findings on a lab component do not block lab merges — they block **promotion**. Iterate freely in lab; clear the checklist to graduate.
28
+
23
29
  ## Usage
24
30
 
25
31
  Inside the monorepo (storybook/sandbox), imports resolve via pnpm workspaces:
@@ -5,7 +5,7 @@
5
5
  * @position Lab implementation; consumed by index.ts, tested by BottomSheet.test.tsx, demonstrated in Storybook
6
6
  *
7
7
  * A mobile touch surface that rises from the bottom edge: grab handle, snap
8
- * points, and swipe-to-dismiss. It owns a native modal `<dialog>` directly and
8
+ * points, and swipe-to-dismiss. It owns a native `<dialog>` directly and
9
9
  * composes core primitives (`useScrollLock`, `<dialog>.showModal()` for the
10
10
  * top layer + focus trap, a `::backdrop` scrim) rather than delegating to a
11
11
  * heavier overlay component — so it controls its own sizing and can render a
@@ -13,6 +13,12 @@
13
13
  * That shell is what lets the sheet translate freely (including a little past
14
14
  * fully-open) without clipping against a fixed dialog edge.
15
15
  *
16
+ * `hasScrim` picks the presentation: `true` (default) uses `showModal()` (top
17
+ * layer, focus trap, scrim, scroll lock, background inert); `false` uses
18
+ * `show()` for a non-modal sheet with no scrim, leaving the page behind
19
+ * interactive and scrollable (the transparent shell is pointer-events:none so
20
+ * taps pass through).
21
+ *
16
22
  * The drag/snap/dismiss machinery lives in `useSheetGestures`; the offset
17
23
  * geometry lives in the pure, tested `snapOffsets` module. Both are internal
18
24
  * to BottomSheet and not exported from the lab entry point.
@@ -71,16 +77,32 @@ export interface BottomSheetProps extends BaseProps<HTMLDialogElement> {
71
77
  * @default 'capped'
72
78
  */
73
79
  height?: BottomSheetHeight | number | string;
80
+ /**
81
+ * Whether to render a modal scrim behind the sheet.
82
+ * - `true` (default) — `showModal()`: renders in the top layer with a focus
83
+ * trap, a `::backdrop` scrim, body scroll lock, and tap-scrim-to-dismiss.
84
+ * The background is inert. Use for focused tasks (filters, forms).
85
+ * - `false` — `show()`: a non-modal sheet with **no scrim**. The page behind
86
+ * stays interactive and scrollable (like Material's *standard* bottom
87
+ * sheet, or an iOS undimmed detent). Escape still closes while focus is
88
+ * inside the sheet, and drag/flick-to-dismiss still work. Use for a peek
89
+ * surface that coexists with the page (e.g. a panel over a live map).
90
+ * @default true
91
+ */
92
+ hasScrim?: boolean;
74
93
  /** Test ID for the root element. */
75
94
  'data-testid'?: string;
76
95
  }
77
96
  /**
78
97
  * A mobile touch sheet that rises from the bottom edge, with a grab handle,
79
- * drag-to-resize snap points, and swipe-to-dismiss. It owns a native modal
80
- * `<dialog>` (top layer, focus trap, `::backdrop` scrim) and locks body scroll
81
- * while open; a slow drag settles to the nearest snap point, a flick down
82
- * dismisses, and Escape closes — so the swipe always has a keyboard
83
- * equivalent.
98
+ * drag-to-resize snap points, and swipe-to-dismiss. It owns a native
99
+ * `<dialog>` and, by default (`hasScrim`), enters the top layer with a focus
100
+ * trap + `::backdrop` scrim and locks body scroll; a slow drag settles to the
101
+ * nearest snap point, a flick down dismisses, and Escape closes — so the swipe
102
+ * always has a keyboard equivalent.
103
+ *
104
+ * With `hasScrim={false}` it opens non-modally (`show()`, no scrim), leaving
105
+ * the page behind interactive and scrollable.
84
106
  *
85
107
  * @example
86
108
  * ```
@@ -93,7 +115,7 @@ export interface BottomSheetProps extends BaseProps<HTMLDialogElement> {
93
115
  * </BottomSheet>
94
116
  * ```
95
117
  */
96
- export declare function BottomSheet({ ref, isOpen, onOpenChange, label, children, height, xstyle, ...props }: BottomSheetProps): import("react").JSX.Element;
118
+ export declare function BottomSheet({ ref, isOpen, onOpenChange, label, children, height, hasScrim, xstyle, ...props }: BottomSheetProps): import("react").JSX.Element;
97
119
  export declare namespace BottomSheet {
98
120
  var displayName: string;
99
121
  }
@@ -1 +1 @@
1
- {"version":3,"file":"BottomSheet.d.ts","sourceRoot":"","sources":["../../src/BottomSheet/BottomSheet.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAiC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAErE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAqBlD;;;;;;GAMG;AACH,QAAA,MAAM,cAAc;;;;CAIV,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,cAAc,CAAC;AAyJ5D,MAAM,WAAW,gBAAiB,SAAQ,SAAS,CAAC,iBAAiB,CAAC;IACpE,wDAAwD;IACxD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAEnC,8EAA8E;IAC9E,MAAM,EAAE,OAAO,CAAC;IAEhB;;;;OAIG;IACH,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAExC;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd,0EAA0E;IAC1E,QAAQ,EAAE,SAAS,CAAC;IAEpB;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,EAAE,iBAAiB,GAAG,MAAM,GAAG,MAAM,CAAC;IAE7C,oCAAoC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAAC,EAC1B,GAAG,EACH,MAAM,EACN,YAAY,EACZ,KAAK,EACL,QAAQ,EACR,MAAiB,EACjB,MAAM,EACN,GAAG,KAAK,EACT,EAAE,gBAAgB,+BAyKlB;yBAlLe,WAAW"}
1
+ {"version":3,"file":"BottomSheet.d.ts","sourceRoot":"","sources":["../../src/BottomSheet/BottomSheet.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAiC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAErE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AA2BlD;;;;;;GAMG;AACH,QAAA,MAAM,cAAc;;;;CAIV,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,cAAc,CAAC;AAiL5D,MAAM,WAAW,gBAAiB,SAAQ,SAAS,CAAC,iBAAiB,CAAC;IACpE,wDAAwD;IACxD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAEnC,8EAA8E;IAC9E,MAAM,EAAE,OAAO,CAAC;IAEhB;;;;OAIG;IACH,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAExC;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd,0EAA0E;IAC1E,QAAQ,EAAE,SAAS,CAAC;IAEpB;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,EAAE,iBAAiB,GAAG,MAAM,GAAG,MAAM,CAAC;IAE7C;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,oCAAoC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,WAAW,CAAC,EAC1B,GAAG,EACH,MAAM,EACN,YAAY,EACZ,KAAK,EACL,QAAQ,EACR,MAAiB,EACjB,QAAe,EACf,MAAM,EACN,GAAG,KAAK,EACT,EAAE,gBAAgB,+BA0LlB;yBApMe,WAAW"}
@@ -9,7 +9,7 @@
9
9
  * @position Lab implementation; consumed by index.ts, tested by BottomSheet.test.tsx, demonstrated in Storybook
10
10
  *
11
11
  * A mobile touch surface that rises from the bottom edge: grab handle, snap
12
- * points, and swipe-to-dismiss. It owns a native modal `<dialog>` directly and
12
+ * points, and swipe-to-dismiss. It owns a native `<dialog>` directly and
13
13
  * composes core primitives (`useScrollLock`, `<dialog>.showModal()` for the
14
14
  * top layer + focus trap, a `::backdrop` scrim) rather than delegating to a
15
15
  * heavier overlay component — so it controls its own sizing and can render a
@@ -17,6 +17,12 @@
17
17
  * That shell is what lets the sheet translate freely (including a little past
18
18
  * fully-open) without clipping against a fixed dialog edge.
19
19
  *
20
+ * `hasScrim` picks the presentation: `true` (default) uses `showModal()` (top
21
+ * layer, focus trap, scrim, scroll lock, background inert); `false` uses
22
+ * `show()` for a non-modal sheet with no scrim, leaving the page behind
23
+ * interactive and scrollable (the transparent shell is pointer-events:none so
24
+ * taps pass through).
25
+ *
20
26
  * The drag/snap/dismiss machinery lives in `useSheetGestures`; the offset
21
27
  * geometry lives in the pure, tested `snapOffsets` module. Both are internal
22
28
  * to BottomSheet and not exported from the lab entry point.
@@ -35,12 +41,18 @@ import { useDevWarning, useScrollLock } from '@astryxdesign/core/hooks';
35
41
  import { mergeProps, mergeRefs, themeProps } from '@astryxdesign/core/utils';
36
42
  import { useSheetGestures } from "./useSheetGestures.js";
37
43
 
44
+ // A non-modal sheet (hasScrim={false}) uses show() instead of showModal(), so
45
+ // it isn't in the native top layer and needs an explicit z-index to sit above
46
+ // page content. No z-index token exists in the theme; 1000 matches the Drawer
47
+ // / app-level overlay convention.
48
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
49
+ const NON_MODAL_Z = 1000;
50
+
38
51
  // Detent fractions of the viewport, ascending (peek / mid / full). Detents
39
52
  // that land within DETENT_DEDUP_PX of each other (e.g. a hug height next to a
40
53
  // snap) are de-duped in the gesture hook, and any taller than the sheet are
41
54
  // dropped, so a full set is safe to offer. The shortest is a glance "peek";
42
55
  // the scrim hides as the sheet collapses onto it (see onScrimOpacity).
43
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
44
56
  const SNAP_FRACTIONS = [0.14, 0.5, 0.92];
45
57
 
46
58
  /**
@@ -116,11 +128,14 @@ const styles = {
116
128
  };
117
129
  /**
118
130
  * A mobile touch sheet that rises from the bottom edge, with a grab handle,
119
- * drag-to-resize snap points, and swipe-to-dismiss. It owns a native modal
120
- * `<dialog>` (top layer, focus trap, `::backdrop` scrim) and locks body scroll
121
- * while open; a slow drag settles to the nearest snap point, a flick down
122
- * dismisses, and Escape closes — so the swipe always has a keyboard
123
- * equivalent.
131
+ * drag-to-resize snap points, and swipe-to-dismiss. It owns a native
132
+ * `<dialog>` and, by default (`hasScrim`), enters the top layer with a focus
133
+ * trap + `::backdrop` scrim and locks body scroll; a slow drag settles to the
134
+ * nearest snap point, a flick down dismisses, and Escape closes — so the swipe
135
+ * always has a keyboard equivalent.
136
+ *
137
+ * With `hasScrim={false}` it opens non-modally (`show()`, no scrim), leaving
138
+ * the page behind interactive and scrollable.
124
139
  *
125
140
  * @example
126
141
  * ```
@@ -140,6 +155,7 @@ export function BottomSheet({
140
155
  label,
141
156
  children,
142
157
  height = 'capped',
158
+ hasScrim = true,
143
159
  xstyle,
144
160
  ...props
145
161
  }) {
@@ -149,7 +165,7 @@ export function BottomSheet({
149
165
  const close = useCallback(() => onOpenChange(false), [onOpenChange]);
150
166
 
151
167
  // Set imperatively (not via state) so a 60fps drag doesn't re-render. The
152
- // hook reports the scrim opacity for the current detent (hidden at the peek).
168
+ // hook reports the scrim opacity for the current detent (a faint floor at the peek).
153
169
  const handleScrimOpacity = useCallback(opacity => {
154
170
  dialogRef.current?.style.setProperty('--_sheet-scrim-opacity', String(opacity));
155
171
  }, []);
@@ -165,24 +181,32 @@ export function BottomSheet({
165
181
  onScrimOpacity: handleScrimOpacity
166
182
  });
167
183
 
168
- // showModal() enters the top layer with a focus trap + ::backdrop; on close
169
- // we animate out (below) then restore focus to the opener.
184
+ // Modal: showModal() enters the top layer with a focus trap + ::backdrop and
185
+ // we restore focus to the opener on close. Standard: show() is non-modal, so
186
+ // we neither trap nor steal focus (the background stays interactive) — we
187
+ // only honor an explicit data-autofocus.
170
188
  useEffect(() => {
171
189
  const dialog = dialogRef.current;
172
190
  if (!dialog) {
173
191
  return;
174
192
  }
175
193
  if (isOpen) {
176
- triggerRef.current = document.activeElement;
177
194
  dialog.style.setProperty('--_sheet-scrim-opacity', '1');
178
195
  if (!dialog.open) {
179
- dialog.showModal();
180
- // Override showModal()'s default (first tabbable) to land on the panel,
181
- // so AT reads from the label — unless a descendant sets data-autofocus.
196
+ // Only remember/restore focus for modal sheets; a non-modal sheet must
197
+ // not yank focus back from whatever the user does in the live page.
198
+ if (hasScrim) {
199
+ triggerRef.current = document.activeElement;
200
+ dialog.showModal();
201
+ } else {
202
+ dialog.show();
203
+ }
204
+ // Land on the panel (so AT reads from the label) for modal sheets;
205
+ // otherwise only honor a descendant data-autofocus.
182
206
  const autofocus = dialog.querySelector('[data-autofocus]');
183
207
  if (autofocus) {
184
208
  autofocus.focus();
185
- } else {
209
+ } else if (hasScrim) {
186
210
  sheetNodeRef.current?.focus();
187
211
  }
188
212
  }
@@ -219,8 +243,11 @@ export function BottomSheet({
219
243
  sheet?.removeEventListener('transitionend', onEnd);
220
244
  };
221
245
  }
222
- }, [isOpen]);
223
- useScrollLock(isOpen);
246
+ }, [isOpen, hasScrim]);
247
+
248
+ // Only a modal sheet locks body scroll; a non-modal sheet leaves the page
249
+ // scrollable behind it.
250
+ useScrollLock(isOpen && hasScrim);
224
251
 
225
252
  // Enforce an accessible name: label is required by types, but a JS caller
226
253
  // can still pass an empty string, leaving the sheet unnamed.
@@ -230,33 +257,55 @@ export function BottomSheet({
230
257
  close();
231
258
  }, [close]);
232
259
  // Explicit Escape handler in addition to the native `cancel` event, for
233
- // environments that don't synthesize `cancel`.
260
+ // environments that don't synthesize `cancel` (and the show() path, which
261
+ // never fires `cancel`). Only fires when focus is inside the sheet.
234
262
  const handleKeyDown = useCallback(event => {
235
263
  if (event.key === 'Escape') {
236
264
  event.preventDefault();
237
265
  close();
238
266
  }
239
267
  }, [close]);
268
+ // Tap-outside-to-dismiss only applies to modal sheets (the scrim). A non-modal
269
+ // sheet has no scrim and its transparent shell is pointer-events:none, so taps
270
+ // pass through to the page rather than dismissing.
240
271
  const handleClick = useCallback(event => {
241
- if (event.target === event.currentTarget) {
272
+ if (hasScrim && event.target === event.currentTarget) {
242
273
  close();
243
274
  }
244
- }, [close]);
275
+ }, [close, hasScrim]);
245
276
  const isNamed = typeof height === 'string' && height in HEIGHT_BUDGETS;
246
277
  const budget = isNamed ? HEIGHT_BUDGETS[height] : typeof height === 'number' ? `${height}px` : height;
247
278
  const isHug = height === 'hug';
248
279
  return /*#__PURE__*/_jsx("dialog", {
249
280
  ...{
250
281
  0: {
251
- className: "xixxii4 x10a8y8t x1o6l61p xtdtrs8 x1x1rfll x7ab17h x1ghz6dp x1717udv xjbqb8w x1rea2x4 x1s85apg x1a2a7pz xnixb3f x1abwkk1 xni466t xxiuuzi xc0dz0a xft5bk6 x15h3t91 x142juwg x1viac0w"
282
+ className: "xixxii4 x10a8y8t x1o6l61p xtdtrs8 x1x1rfll x7ab17h x1ghz6dp x1717udv xjbqb8w x1rea2x4 x1s85apg x1a2a7pz"
283
+ },
284
+ 4: {
285
+ className: "xixxii4 x10a8y8t x1o6l61p xtdtrs8 x1x1rfll x7ab17h x1ghz6dp x1717udv xjbqb8w x1rea2x4 x1a2a7pz x1lliihq"
286
+ },
287
+ 2: {
288
+ className: "xixxii4 x10a8y8t x1o6l61p xtdtrs8 x1x1rfll x7ab17h x1ghz6dp x1717udv xjbqb8w x1rea2x4 x1s85apg x1a2a7pz xnixb3f xni466t xxiuuzi xc0dz0a xft5bk6 x15h3t91 x142juwg x1viac0w"
289
+ },
290
+ 6: {
291
+ className: "xixxii4 x10a8y8t x1o6l61p xtdtrs8 x1x1rfll x7ab17h x1ghz6dp x1717udv xjbqb8w x1rea2x4 x1a2a7pz x1lliihq xnixb3f xni466t xxiuuzi xc0dz0a xft5bk6 x15h3t91 x142juwg x1viac0w"
252
292
  },
253
293
  1: {
254
- className: "xixxii4 x10a8y8t x1o6l61p xtdtrs8 x1x1rfll x7ab17h x1ghz6dp x1717udv xjbqb8w x1rea2x4 x1a2a7pz x1lliihq xnixb3f x1abwkk1 xni466t xxiuuzi xc0dz0a xft5bk6 x15h3t91 x142juwg x1viac0w"
294
+ className: "xixxii4 x10a8y8t x1x1rfll x7ab17h x1ghz6dp x1717udv xjbqb8w x1rea2x4 x1s85apg x1a2a7pz x47corl xfo81ep xh8yej3 x5yr21d"
295
+ },
296
+ 5: {
297
+ className: "xixxii4 x10a8y8t x1x1rfll x7ab17h x1ghz6dp x1717udv xjbqb8w x1rea2x4 x1a2a7pz x1lliihq x47corl xfo81ep xh8yej3 x5yr21d"
298
+ },
299
+ 3: {
300
+ className: "xixxii4 x10a8y8t x1x1rfll x7ab17h x1ghz6dp x1717udv xjbqb8w x1rea2x4 x1s85apg x1a2a7pz xnixb3f xni466t xxiuuzi xc0dz0a xft5bk6 x15h3t91 x142juwg x1viac0w x47corl xfo81ep xh8yej3 x5yr21d"
301
+ },
302
+ 7: {
303
+ className: "xixxii4 x10a8y8t x1x1rfll x7ab17h x1ghz6dp x1717udv xjbqb8w x1rea2x4 x1a2a7pz x1lliihq xnixb3f xni466t xxiuuzi xc0dz0a xft5bk6 x15h3t91 x142juwg x1viac0w x47corl xfo81ep xh8yej3 x5yr21d"
255
304
  }
256
- }[!!isOpen << 0],
305
+ }[!!isOpen << 2 | !!hasScrim << 1 | !!!hasScrim << 0],
257
306
  ref: mergeRefs(ref, dialogRef),
258
307
  "aria-label": label,
259
- "aria-modal": "true",
308
+ "aria-modal": hasScrim ? 'true' : undefined,
260
309
  onCancel: handleCancel,
261
310
  onClick: handleClick,
262
311
  onKeyDown: handleKeyDown,
@@ -36,12 +36,20 @@ export declare function computeDetentOffsets(sheetHeight: number, detentHeights:
36
36
  /** Nearest value in `offsets` to `value` (offsets must be non-empty). */
37
37
  export declare function nearestOffset(value: number, offsets: readonly number[]): number;
38
38
  /**
39
- * Scrim opacity (1 = fully visible, 0 = hidden) for a drag/settle `offset`.
39
+ * Minimum scrim opacity at the peek detent. The scrim thins to a glance state
40
+ * but never fully vanishes, because a modal sheet keeps the background inert —
41
+ * a fully clear backdrop would read as "interactive" when it isn't. (For a
42
+ * genuinely interactive, undimmed peek, use a non-modal sheet, `hasScrim=false`.)
43
+ */
44
+ export declare const MIN_PEEK_SCRIM_OPACITY = 0.3;
45
+ /**
46
+ * Scrim opacity (1 = fully visible) for a drag/settle `offset`.
40
47
  * The scrim stays full while the sheet is at or above its second-shortest
41
- * detent, then fades to 0 as it collapses onto the shortest ("peek") detent —
42
- * a glance state that reveals the content behind — and stays hidden below it.
43
- * A single-detent sheet has no peek, so it instead fades across the dismiss
44
- * overshoot toward `dismissOffset`.
48
+ * detent, then fades as it collapses onto the shortest ("peek") detent — a
49
+ * glance state that thins the backdrop to `MIN_PEEK_SCRIM_OPACITY` (not fully
50
+ * gone, since the sheet is still modal) and holds there below it.
51
+ * A single-detent sheet has no peek, so it instead fades all the way to 0
52
+ * across the dismiss overshoot toward `dismissOffset` (the sheet is leaving).
45
53
  */
46
54
  export declare function scrimOpacityForOffset(offset: number, offsets: readonly number[], dismissOffset: number): number;
47
55
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"snapOffsets.d.ts","sourceRoot":"","sources":["../../src/BottomSheet/snapOffsets.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC,4EAA4E;AAC5E,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,UAAU,EAAE,MAAM,GACjB,MAAM,EAAE,CAEV;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,SAAS,MAAM,EAAE,EAChC,OAAO,GAAE,MAAwB,GAChC,MAAM,EAAE,CAcV;AAED,yEAAyE;AACzE,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,MAAM,EAAE,GACzB,MAAM,CAKR;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,aAAa,EAAE,MAAM,GACpB,MAAM,CAWR;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,GACjB,MAAM,CAcR"}
1
+ {"version":3,"file":"snapOffsets.d.ts","sourceRoot":"","sources":["../../src/BottomSheet/snapOffsets.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC,4EAA4E;AAC5E,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,UAAU,EAAE,MAAM,GACjB,MAAM,EAAE,CAEV;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,SAAS,MAAM,EAAE,EAChC,OAAO,GAAE,MAAwB,GAChC,MAAM,EAAE,CAcV;AAED,yEAAyE;AACzE,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,MAAM,EAAE,GACzB,MAAM,CAKR;AAED;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,aAAa,EAAE,MAAM,GACpB,MAAM,CAeR;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,GACjB,MAAM,CAcR"}
@@ -58,24 +58,37 @@ export function nearestOffset(value, offsets) {
58
58
  }
59
59
 
60
60
  /**
61
- * Scrim opacity (1 = fully visible, 0 = hidden) for a drag/settle `offset`.
61
+ * Minimum scrim opacity at the peek detent. The scrim thins to a glance state
62
+ * but never fully vanishes, because a modal sheet keeps the background inert —
63
+ * a fully clear backdrop would read as "interactive" when it isn't. (For a
64
+ * genuinely interactive, undimmed peek, use a non-modal sheet, `hasScrim=false`.)
65
+ */
66
+ export const MIN_PEEK_SCRIM_OPACITY = 0.3;
67
+
68
+ /**
69
+ * Scrim opacity (1 = fully visible) for a drag/settle `offset`.
62
70
  * The scrim stays full while the sheet is at or above its second-shortest
63
- * detent, then fades to 0 as it collapses onto the shortest ("peek") detent —
64
- * a glance state that reveals the content behind — and stays hidden below it.
65
- * A single-detent sheet has no peek, so it instead fades across the dismiss
66
- * overshoot toward `dismissOffset`.
71
+ * detent, then fades as it collapses onto the shortest ("peek") detent — a
72
+ * glance state that thins the backdrop to `MIN_PEEK_SCRIM_OPACITY` (not fully
73
+ * gone, since the sheet is still modal) and holds there below it.
74
+ * A single-detent sheet has no peek, so it instead fades all the way to 0
75
+ * across the dismiss overshoot toward `dismissOffset` (the sheet is leaving).
67
76
  */
68
77
  export function scrimOpacityForOffset(offset, offsets, dismissOffset) {
69
78
  const shortest = offsets[offsets.length - 1];
70
- const fadeStart = offsets.length >= 2 ? offsets[offsets.length - 2] : 0;
71
- const fadeEnd = offsets.length >= 2 ? shortest : dismissOffset;
79
+ const hasPeek = offsets.length >= 2;
80
+ const fadeStart = hasPeek ? offsets[offsets.length - 2] : 0;
81
+ const fadeEnd = hasPeek ? shortest : dismissOffset;
82
+ // Peek is a resting state on a still-modal sheet, so keep a floor; the
83
+ // dismiss overshoot is a sheet on its way out, so let it clear completely.
84
+ const floor = hasPeek ? MIN_PEEK_SCRIM_OPACITY : 0;
72
85
  if (offset <= fadeStart) {
73
86
  return 1;
74
87
  }
75
88
  if (offset >= fadeEnd) {
76
- return 0;
89
+ return floor;
77
90
  }
78
- return 1 - (offset - fadeStart) / (fadeEnd - fadeStart);
91
+ return 1 - (1 - floor) * ((offset - fadeStart) / (fadeEnd - fadeStart));
79
92
  }
80
93
 
81
94
  /**
@@ -43,7 +43,7 @@ export interface UseSheetGesturesOptions {
43
43
  * Called with the scrim opacity the sheet should show (1 = fully visible,
44
44
  * 0 = hidden) as the drag moves and on settle. Full while the sheet is at
45
45
  * or above its mid detent, fading to 0 as it collapses onto the shortest
46
- * "peek" detent — so a glance-height sheet reveals the content behind — and
46
+ * "peek" detent — thinning to a faint glance state without fully clearing — and
47
47
  * on the dismiss overshoot. Lets the owner mirror it onto the scrim.
48
48
  */
49
49
  onScrimOpacity?: (opacity: number) => void;
@@ -213,7 +213,7 @@ export function useSheetGestures({
213
213
  const target = resolveSettleOffset(offset, offsets, dir, baseOffset);
214
214
  setSettledOffset(target);
215
215
  onSnapRef.current?.(height - target);
216
- // Keep the scrim in sync with the resting detent (hidden at the peek).
216
+ // Keep the scrim in sync with the resting detent (faint floor at the peek).
217
217
  const dismissOffset = maxOffset + shortestDetentHeight * DISMISS_OVERSHOOT_RATIO;
218
218
  onScrimOpacityRef.current?.(scrimOpacityForOffset(target, offsets, dismissOffset));
219
219
  // Haptic "tick" when landing on a different detent (where supported).
@@ -1 +1 @@
1
- {"version":3,"file":"ChatReasoning.d.ts","sourceRoot":"","sources":["../../src/ChatReasoning/ChatReasoning.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAwB,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAC5D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAkBlD,MAAM,WAAW,kBAAmB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACnE,mFAAmF;IACnF,QAAQ,EAAE,SAAS,CAAC;IACpB,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,iCAAiC;IACjC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,4DAA4D;IAC5D,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,4CAA4C;IAC5C,gBAAgB,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,KAAK,IAAI,CAAC;CAClD;AAkKD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,+BA0FtD;yBA1Fe,aAAa"}
1
+ {"version":3,"file":"ChatReasoning.d.ts","sourceRoot":"","sources":["../../src/ChatReasoning/ChatReasoning.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,OAAO,EAA+B,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AACnE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAkBlD,MAAM,WAAW,kBAAmB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACnE,mFAAmF;IACnF,QAAQ,EAAE,SAAS,CAAC;IACpB,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,iCAAiC;IACjC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,4DAA4D;IAC5D,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,4CAA4C;IAC5C,gBAAgB,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,KAAK,IAAI,CAAC;CAClD;AAkKD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,+BAkGtD;yBAlGe,aAAa"}
@@ -14,7 +14,7 @@
14
14
  * Design: extremely compact by default. One line with ellipsis overflow.
15
15
  * Expandable on click.
16
16
  */
17
- import { useState, useCallback } from 'react';
17
+ import { useCallback, useId, useState } from 'react';
18
18
  import * as stylex from '@stylexjs/stylex';
19
19
  import '@astryxdesign/core/theme/tokens.stylex';
20
20
  import { colorVars, spacingVars, typeScaleVars, typographyVars, fontWeightVars, durationVars, easeVars } from '@astryxdesign/core/theme/tokens.stylex';
@@ -127,6 +127,7 @@ export function ChatReasoning(props) {
127
127
  const [internalExpanded, setInternalExpanded] = useState(defaultIsExpanded);
128
128
  const isControlled = controlledExpanded !== undefined;
129
129
  const isExpanded = isControlled ? controlledExpanded : internalExpanded;
130
+ const contentId = useId();
130
131
  const toggle = useCallback(() => {
131
132
  const next = !isExpanded;
132
133
  if (!isControlled) {
@@ -145,6 +146,7 @@ export function ChatReasoning(props) {
145
146
  role: "button",
146
147
  tabIndex: 0,
147
148
  "aria-expanded": isExpanded,
149
+ "aria-controls": contentId,
148
150
  onClick: toggle,
149
151
  onKeyDown: e => {
150
152
  if (e.key === 'Enter' || e.key === ' ') {
@@ -211,6 +213,13 @@ export function ChatReasoning(props) {
211
213
  children: /*#__PURE__*/_jsx(ChevronDownIcon, {})
212
214
  })]
213
215
  }), /*#__PURE__*/_jsx("div", {
216
+ id: contentId
217
+ // Collapsed content is only clipped visually (0fr grid row + overflow
218
+ // hidden), which leaves it in the accessibility tree. `inert` removes
219
+ // it from AT and the tab order while keeping the element rendered, so
220
+ // the grid-template-rows expand/collapse transition still runs.
221
+ ,
222
+ inert: !isExpanded,
214
223
  ...{
215
224
  0: {
216
225
  className: "xrvj5dj xihq33y xb0j27v"
@@ -0,0 +1,138 @@
1
+ /**
2
+ * @file ListInput.tsx
3
+ * @input React, StyleX, Astryx field/list/button/icon/layer/empty-state primitives, compact size context, browser scroll geometry, programmatic theme tokens, and shared Lab reorder styles
4
+ * @output Exports ListInput and its controlled data, column, renderer, and change types
5
+ * @position Lab experiment (RFC facebook/astryx#4531) for editing compact repeated records
6
+ *
7
+ * SYNC: When modified, update these files to stay in sync:
8
+ * - /packages/lab/src/ListInput/ListInput.doc.mjs
9
+ * - /packages/lab/src/ListInput/ListInput.test.tsx
10
+ * - /packages/lab/src/ListInput/index.ts
11
+ * - /apps/storybook/stories/ListInput.stories.tsx
12
+ */
13
+ import { type Key, type ReactNode } from 'react';
14
+ import type { BaseProps } from '@astryxdesign/core';
15
+ import { type InputStatus } from '@astryxdesign/core/Field';
16
+ import type { ColumnWidth } from '@astryxdesign/core/Table';
17
+ export type ListInputChange<T> = {
18
+ type: 'add';
19
+ item: T;
20
+ index: number;
21
+ } | {
22
+ type: 'update';
23
+ item: T;
24
+ previousItem: T;
25
+ index: number;
26
+ columnKey?: string;
27
+ } | {
28
+ type: 'remove';
29
+ item: T;
30
+ index: number;
31
+ } | {
32
+ type: 'reorder';
33
+ item: T;
34
+ fromIndex: number;
35
+ toIndex: number;
36
+ };
37
+ export interface ListInputValueContext<T> {
38
+ /** Current record. */
39
+ item: T;
40
+ /** Current visual position. */
41
+ index: number;
42
+ /** Accessible cell label, including the record position. */
43
+ label: string;
44
+ /** True because ListInput owns the visible column label. */
45
+ isLabelHidden: boolean;
46
+ }
47
+ export interface ListInputRenderContext<T> extends ListInputValueContext<T> {
48
+ /** Complete validation status scoped to this field. */
49
+ status?: InputStatus;
50
+ /** Forward to the rendered Astryx control so status uses its native tooltip. */
51
+ statusVariant: 'tooltip';
52
+ /** Whether the rendered control should be disabled. */
53
+ isDisabled: boolean;
54
+ /** Whether an asynchronous list operation is in progress. */
55
+ isLoading: boolean;
56
+ /** Replace this record in the controlled list. */
57
+ updateItem: (nextItem: T, columnKey?: string) => void;
58
+ }
59
+ export interface ListInputColumn<T> {
60
+ /** Stable column identifier, also passed to getFieldStatus. */
61
+ key: string;
62
+ /** Field label shown on the first record and repeated when rows stack. */
63
+ header: string;
64
+ /**
65
+ * Column sizing. Use proportional() or pixel() from @astryxdesign/core/Table
66
+ * to tune the width ratio. Defaults to a proportional column with a 140px floor.
67
+ */
68
+ width?: ColumnWidth;
69
+ /** Renders the editable control for one cell. */
70
+ renderInput: (context: ListInputRenderContext<T>) => ReactNode;
71
+ }
72
+ export interface ListInputProps<T> extends Omit<BaseProps<HTMLDivElement>, 'onChange'> {
73
+ /** Ref forwarded to the outer field element. */
74
+ ref?: React.Ref<HTMLDivElement>;
75
+ /** Visible and accessible label for the list. */
76
+ label: string;
77
+ /** Optional supporting text. */
78
+ description?: string;
79
+ /** Controlled records. */
80
+ value: T[];
81
+ /** Called for every record mutation with the next value and mutation detail. */
82
+ onChange: (nextValue: T[], change: ListInputChange<T>) => void;
83
+ /** Returns a stable key for focus preservation and reordering. */
84
+ getItemKey: (item: T) => Key;
85
+ /** Creates a new record when the Add action is used. */
86
+ createItem: () => T;
87
+ /** Column definitions and cell renderers. */
88
+ columns: ListInputColumn<T>[];
89
+ /** Singular name used in action labels and announcements. @default 'item' */
90
+ itemName?: string;
91
+ /** Validation status for the whole list. */
92
+ status?: InputStatus;
93
+ /** Returns a validation status displayed across one record. */
94
+ getItemStatus?: (item: T, index: number) => InputStatus | undefined;
95
+ /** Returns a validation status passed to one field renderer. */
96
+ getFieldStatus?: (item: T, columnKey: string, index: number) => InputStatus | undefined;
97
+ /** Enables handle-only pointer and keyboard reordering. @default false */
98
+ isReorderable?: boolean;
99
+ /** Disables fields and mutation controls. @default false */
100
+ isDisabled?: boolean;
101
+ /** Marks the list busy and prevents mutations. @default false */
102
+ isLoading?: boolean;
103
+ /** Maximum record count. Reaching it disables, but does not remove, Add. */
104
+ maxItems?: number;
105
+ /** Visually hides the list label while preserving its accessible name. */
106
+ isLabelHidden?: boolean;
107
+ /** Marks the list optional. */
108
+ isOptional?: boolean;
109
+ /** Marks the list required. */
110
+ isRequired?: boolean;
111
+ }
112
+ /**
113
+ * A compact editor for short collections of consistent, simple records.
114
+ *
115
+ * ListInput owns list semantics, add/remove controls, handle-only reordering,
116
+ * live collection motion, focus restoration, announcements, and list/item/
117
+ * field validation placement. Consumers keep ownership of the controlled data
118
+ * and render each field with standard Astryx inputs.
119
+ *
120
+ * @example
121
+ * ```
122
+ * <ListInput
123
+ * label="Guests"
124
+ * value={guests}
125
+ * onChange={setGuests}
126
+ * getItemKey={guest => guest.id}
127
+ * createItem={() => ({id: crypto.randomUUID(), name: '', email: ''})}
128
+ * columns={columns}
129
+ * itemName="guest"
130
+ * isReorderable
131
+ * />
132
+ * ```
133
+ */
134
+ export declare function ListInput<T>({ label, description, value, onChange, getItemKey, createItem, columns, itemName, status, getItemStatus, getFieldStatus, isReorderable, isDisabled, isLoading, maxItems, isLabelHidden, isOptional, isRequired, ref, ...rest }: ListInputProps<T>): ReactNode;
135
+ export declare namespace ListInput {
136
+ var displayName: string;
137
+ }
138
+ //# sourceMappingURL=ListInput.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ListInput.d.ts","sourceRoot":"","sources":["../../src/ListInput/ListInput.tsx"],"names":[],"mappings":"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"}