gridsmith-ui 0.19.0 → 0.21.0

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.
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { Button, useAppShell, REACT_VERSION_PROBLEM, ControlSizeContext, useDirtyGuard, OverlayCloseCtx, DiscardConfirm, Spinner, DrawerFooterCtx, useCompact, useElementWidth, Sheet, useCloseSettle, Drawer, useRememberedWidth, pxOf, ResizeEdge, defaultMaxWidth } from './chunk-5PDTHKW3.js';
2
- export { AppShell, Button, ButtonSizeContext, ControlSizeContext, Drawer, Sheet, SkipLink, Spinner, useAppShell, useUnsavedChanges } from './chunk-5PDTHKW3.js';
1
+ import { Button, useAppShell, REACT_VERSION_PROBLEM, ControlSizeContext, triggerType, useOverlayMark, useDirtyGuard, OverlayMarkScope, OverlayCloseCtx, DiscardConfirm, OVERLAY_ROOT_ATTR, Spinner, OverlayMarkCtx, DrawerFooterCtx, useCompact, useElementWidth, Sheet, useCloseSettle, Drawer, useRememberedWidth, pxOf, ResizeEdge, defaultMaxWidth } from './chunk-GGVASDXT.js';
2
+ export { AppShell, Button, ButtonSizeContext, ControlSizeContext, Drawer, Sheet, SkipLink, Spinner, useAppShell, useUnsavedChanges } from './chunk-GGVASDXT.js';
3
3
  import { Skeleton, TITLE_TEXT } from './chunk-NCIZQYXQ.js';
4
4
  export { Skeleton } from './chunk-NCIZQYXQ.js';
5
- import { FOCUSABLE, usePopupFocus, FIELD, ZOOM_KEYS, useOverlayLayer, useFocusTrap, useFocusBeforeOpen, rememberFocus, focusInitial, OVERLAY_CLOSE_ATTR, focusAfterRemoval, rovingTabStop, useGridKeyboard, useNonModalDialog, usePressMarks, useReturnFocus, useCloseOnFocusOut } from './chunk-JXANCXK6.js';
6
- export { useGridKeyboard } from './chunk-JXANCXK6.js';
5
+ import { FOCUSABLE, usePopupFocus, FIELD, ZOOM_KEYS, FormChangeCtx, useOverlayLayer, useFocusTrap, useFocusBeforeOpen, rememberFocus, focusInitial, OVERLAY_CLOSE_ATTR, useReportedChange, focusAfterRemoval, useOptionalFormContext, rovingTabStop, useGridKeyboard, FormCtx, useNonModalDialog, usePressMarks, useReturnFocus, useCloseOnFocusOut } from './chunk-RT7RYGC3.js';
6
+ export { useFormContext, useGridKeyboard } from './chunk-RT7RYGC3.js';
7
7
  import { Link } from './chunk-EQDR57AM.js';
8
8
  export { CodeBlock, Divider, Link } from './chunk-EQDR57AM.js';
9
9
  import { Badge } from './chunk-XYDIBW3J.js';
@@ -82,6 +82,7 @@ function DropdownMenu({ trigger, items, align = "end", className, container }) {
82
82
  /* @__PURE__ */ jsx(
83
83
  Slot,
84
84
  {
85
+ ...triggerType(trigger),
85
86
  ref: triggerRef,
86
87
  onClick: () => {
87
88
  setOpen(!open);
@@ -115,6 +116,7 @@ function DropdownMenu({ trigger, items, align = "end", className, container }) {
115
116
  return /* @__PURE__ */ jsxs(
116
117
  "button",
117
118
  {
119
+ type: "button",
118
120
  "data-index": i,
119
121
  role: "menuitem",
120
122
  disabled: item.disabled,
@@ -177,13 +179,14 @@ function BulkActionBar({ selectedCount, actions, onClearSelection, className })
177
179
  isMobile ? /* @__PURE__ */ jsx(
178
180
  DropdownMenu,
179
181
  {
180
- trigger: /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", iconLeft: /* @__PURE__ */ jsx(Icon, { name: "more-vertical", size: 14 }), children: "Actions" }),
182
+ trigger: /* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", size: "sm", iconLeft: /* @__PURE__ */ jsx(Icon, { name: "more-vertical", size: 14 }), children: "Actions" }),
181
183
  items: dropdownItems,
182
184
  align: "start"
183
185
  }
184
186
  ) : /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-2", children: actions.map((action, i) => /* @__PURE__ */ jsx(
185
187
  Button,
186
188
  {
189
+ type: "button",
187
190
  variant: "secondary",
188
191
  size: "sm",
189
192
  onClick: action.onClick,
@@ -194,6 +197,7 @@ function BulkActionBar({ selectedCount, actions, onClearSelection, className })
194
197
  /* @__PURE__ */ jsx(
195
198
  "button",
196
199
  {
200
+ type: "button",
197
201
  onClick: onClearSelection,
198
202
  className: "ml-auto text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer",
199
203
  "aria-label": "Clear selection",
@@ -204,6 +208,12 @@ function BulkActionBar({ selectedCount, actions, onClearSelection, className })
204
208
  }
205
209
  );
206
210
  }
211
+ var UNCONTROLLED = /* @__PURE__ */ Symbol("uncontrolled");
212
+ var valueOf = (e) => {
213
+ const t = e.target;
214
+ if (!t) return void 0;
215
+ return t instanceof HTMLInputElement && (t.type === "checkbox" || t.type === "radio") ? t.checked : t.value;
216
+ };
207
217
  var INPUT_HEIGHT = {
208
218
  sm: "h-[var(--ds-control-height-sm)]",
209
219
  md: "h-[var(--ds-control-height-md)]",
@@ -217,6 +227,7 @@ var INPUT_TEXT = {
217
227
  function Input({ className, size: sizeProp, error = false, prefix, suffix, ...props }) {
218
228
  const contextSize = useContext(ControlSizeContext);
219
229
  const size = sizeProp ?? contextSize ?? "md";
230
+ const onChange = useReportedChange(props.onChange, props.value === void 0 ? UNCONTROLLED : props.value, valueOf);
220
231
  const borderColor = error ? "border-[var(--ds-status-error)]" : "border-[var(--ds-input-border)]";
221
232
  const hoverBorder = !error && !props.disabled && !props.readOnly ? "hover:border-[var(--ds-input-border-hover)]" : "";
222
233
  if (prefix || suffix) {
@@ -252,7 +263,8 @@ function Input({ className, size: sizeProp, error = false, prefix, suffix, ...pr
252
263
  "disabled:cursor-not-allowed",
253
264
  "read-only:text-[var(--ds-text-secondary)] read-only:cursor-default"
254
265
  ),
255
- ...props
266
+ ...props,
267
+ onChange: props.onChange || props.value === void 0 ? onChange : void 0
256
268
  }
257
269
  ),
258
270
  suffix && /* @__PURE__ */ jsx("span", { className: "shrink-0 pr-ds-3 text-[var(--ds-text-muted)] flex items-center", children: suffix })
@@ -281,11 +293,13 @@ function Input({ className, size: sizeProp, error = false, prefix, suffix, ...pr
281
293
  "transition-colors duration-[var(--ds-transition-speed)]",
282
294
  className
283
295
  ),
284
- ...props
296
+ ...props,
297
+ onChange: props.onChange || props.value === void 0 ? onChange : void 0
285
298
  }
286
299
  );
287
300
  }
288
301
  function Textarea({ className, autoResize, maxRows, error = false, ref, ...props }) {
302
+ const onChange = useReportedChange(props.onChange, props.value === void 0 ? UNCONTROLLED : props.value, valueOf);
289
303
  const internalRef = useRef(null);
290
304
  const textareaRef = ref ?? internalRef;
291
305
  const resize = useCallback(() => {
@@ -324,7 +338,8 @@ function Textarea({ className, autoResize, maxRows, error = false, ref, ...props
324
338
  className
325
339
  ),
326
340
  onInput: autoResize ? resize : void 0,
327
- ...props
341
+ ...props,
342
+ onChange: props.onChange || props.value === void 0 ? onChange : void 0
328
343
  }
329
344
  );
330
345
  }
@@ -347,18 +362,20 @@ var TOGGLE_SIZES = {
347
362
  md: { w: 44, h: 24, thumb: 16 }
348
363
  };
349
364
  function Toggle({ checked, onChange, label, disabled, error = false, layout = "full", size = "md", "aria-labelledby": ariaLabelledBy, ...rest }) {
365
+ const change = useReportedChange(onChange, checked);
350
366
  const dims = TOGGLE_SIZES[size];
351
367
  const travel = dims.w - dims.thumb - 3;
352
368
  const switchButton = /* @__PURE__ */ jsx(
353
369
  "button",
354
370
  {
371
+ type: "button",
355
372
  role: "switch",
356
373
  "aria-checked": checked,
357
374
  "aria-label": !label && !ariaLabelledBy ? "Toggle" : void 0,
358
375
  "aria-labelledby": ariaLabelledBy,
359
376
  "aria-invalid": error || void 0,
360
377
  disabled,
361
- onClick: () => !disabled && onChange(!checked),
378
+ onClick: () => !disabled && change(!checked),
362
379
  ...rest,
363
380
  className: cn(
364
381
  "relative inline-flex shrink-0 items-center overflow-hidden",
@@ -407,15 +424,6 @@ function Toggle({ checked, onChange, label, disabled, error = false, layout = "f
407
424
  }
408
425
  );
409
426
  }
410
- var FormCtx = createContext(null);
411
- function useFormContext() {
412
- const ctx = useContext(FormCtx);
413
- if (!ctx) throw new Error("useFormContext must be used within FormWrapper");
414
- return ctx;
415
- }
416
- function useOptionalFormContext() {
417
- return useContext(FormCtx);
418
- }
419
427
  function InlineFieldError({ message, id, className }) {
420
428
  return /* @__PURE__ */ jsxs(
421
429
  "p",
@@ -454,7 +462,9 @@ function useFieldControl(own, options = {}) {
454
462
  requiredNote: noteId ? /* @__PURE__ */ jsx("span", { id: noteId, hidden: true, children: "Required" }) : null
455
463
  };
456
464
  }
457
- function Field({ label, children, hint, error, required, labelAction, id: idProp, validate, className }) {
465
+ var AddAnotherRowCtx = createContext(false);
466
+ function Field({ label, children, hint, error, required, labelAction, hideLabel = false, id: idProp, validate, className }) {
467
+ const inGroupRow = useContext(AddAnotherRowCtx);
458
468
  const generated = useId();
459
469
  const id = idProp ?? `field-${generated}`;
460
470
  const hintId = `${id}-hint`;
@@ -533,6 +543,10 @@ function Field({ label, children, hint, error, required, labelAction, id: idProp
533
543
  } } : {};
534
544
  control = cloneElement(child, isNative ? ariaOnly : { ...ariaOnly, ...errorFlag ? { error: true } : {}, ...onChange });
535
545
  }
546
+ const labelNode = /* @__PURE__ */ jsxs(Label, { id: labelId, htmlFor: id, className: cn(hideLabel && "sr-only", hideLabel && inGroupRow && "@max-[30rem]:not-sr-only"), children: [
547
+ label,
548
+ required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ml-ds-0.5 text-[var(--ds-status-error)]", children: "*" })
549
+ ] });
536
550
  return (
537
551
  // In a settings panel a field keeps the narrow reading width however wide the page is (--ds-form-max, Sprint 25.9 A4)
538
552
  /* @__PURE__ */ jsxs(
@@ -541,11 +555,8 @@ function Field({ label, children, hint, error, required, labelAction, id: idProp
541
555
  className: cn("flex flex-col gap-ds-1.5 max-w-[var(--ds-form-max,none)]", className),
542
556
  ...validate ? { onFocusCapture, onBlurCapture, onInputCapture: recheck } : {},
543
557
  children: [
544
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-ds-2", children: [
545
- /* @__PURE__ */ jsxs(Label, { id: labelId, htmlFor: id, children: [
546
- label,
547
- required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ml-ds-0.5 text-[var(--ds-status-error)]", children: "*" })
548
- ] }),
558
+ hideLabel && !labelAction ? labelNode : /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-ds-2", children: [
559
+ labelNode,
549
560
  labelAction
550
561
  ] }),
551
562
  /* @__PURE__ */ jsx(FieldContext.Provider, { value: fieldContext, children: control }),
@@ -564,7 +575,7 @@ var DROPDOWN_HEIGHT = {
564
575
  function Dropdown({
565
576
  options,
566
577
  value,
567
- onChange,
578
+ onChange: onChangeProp,
568
579
  label,
569
580
  disabled,
570
581
  placeholder = "Select...",
@@ -579,6 +590,7 @@ function Dropdown({
579
590
  "aria-label": ariaLabel,
580
591
  ...wiring
581
592
  }) {
593
+ const onChange = useReportedChange(onChangeProp, value);
582
594
  const contextSize = useContext(ControlSizeContext);
583
595
  const generatedId = useId();
584
596
  const control = useFieldControl({ ...wiring, error: error || void 0 }, { requiredInDescription: true });
@@ -865,6 +877,7 @@ function Dropdown({
865
877
  /* @__PURE__ */ jsx(
866
878
  "button",
867
879
  {
880
+ type: "button",
868
881
  "data-chip-remove": "",
869
882
  onClick: () => {
870
883
  pendingChipFocus.current = { index: i, value: v };
@@ -932,7 +945,8 @@ var TEXT_SIZE = {
932
945
  md: "text-sm",
933
946
  lg: "text-base"
934
947
  };
935
- function Combobox({ value, onChange, options, placeholder = "Search\u2026", disabled, loading = false, className, ref, container, error = false, size: sizeProp, ...wiring }) {
948
+ function Combobox({ value, onChange: onChangeProp, options, placeholder = "Search\u2026", disabled, loading = false, className, ref, container, error = false, size: sizeProp, ...wiring }) {
949
+ const onChange = useReportedChange(onChangeProp, value);
936
950
  const contextSize = useContext(ControlSizeContext);
937
951
  const control = useFieldControl({ ...wiring, error: error || void 0 });
938
952
  const size = sizeProp ?? contextSize ?? "md";
@@ -1130,6 +1144,7 @@ function Combobox({ value, onChange, options, placeholder = "Search\u2026", disa
1130
1144
  children: /* @__PURE__ */ jsx("div", { style: { padding: "var(--ds-space-1)" }, children: loading ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-ds-4", children: /* @__PURE__ */ jsx(Spinner, { size: "sm", label: "Loading options" }) }) : filtered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-ds-2 py-ds-3 text-sm text-[var(--ds-text-muted)] text-center", children: "No results" }) : filtered.map((opt, i) => /* @__PURE__ */ jsxs(
1131
1145
  "button",
1132
1146
  {
1147
+ type: "button",
1133
1148
  id: `${listboxId}-opt-${i}`,
1134
1149
  role: "option",
1135
1150
  tabIndex: -1,
@@ -1161,9 +1176,11 @@ var CHECKBOX_SIZES = {
1161
1176
  sm: { box: "h-[16px] w-[16px]", glyph: 10, label: "text-xs" },
1162
1177
  md: { box: "h-[18px] w-[18px]", glyph: 12, label: "text-sm" }
1163
1178
  };
1164
- function Checkbox({ checked, onChange, label, size = "md", disabled = false, indeterminate = false, className, ref }) {
1179
+ function Checkbox({ checked, onChange: onChangeProp, label, size = "md", disabled = false, indeterminate = false, className, ref, ...wiring }) {
1180
+ const onChange = useReportedChange(onChangeProp, checked);
1165
1181
  const internalRef = useRef(null);
1166
1182
  const dims = CHECKBOX_SIZES[size];
1183
+ const control = useFieldControl(wiring);
1167
1184
  const mergedRef = useCallback(
1168
1185
  (node) => {
1169
1186
  internalRef.current = node;
@@ -1191,6 +1208,11 @@ function Checkbox({ checked, onChange, label, size = "md", disabled = false, ind
1191
1208
  {
1192
1209
  ref: mergedRef,
1193
1210
  type: "checkbox",
1211
+ id: control.id,
1212
+ "aria-labelledby": wiring["aria-labelledby"],
1213
+ "aria-describedby": control.describedBy,
1214
+ "aria-invalid": control.invalid || void 0,
1215
+ required: control.required || void 0,
1194
1216
  checked,
1195
1217
  disabled,
1196
1218
  onChange: (e) => onChange(e.target.checked),
@@ -1253,9 +1275,11 @@ function CheckboxGroupItem({ value, label, disabled, groupValue, onGroupChange }
1253
1275
  }
1254
1276
  );
1255
1277
  }
1256
- function RadioGroup({ value, onChange, options, orientation = "vertical", className, label }) {
1278
+ function RadioGroup({ value, onChange: onChangeProp, options, orientation = "vertical", className, label, ...wiring }) {
1279
+ const onChange = useReportedChange(onChangeProp, value);
1257
1280
  const groupRef = useRef(null);
1258
1281
  const groupId = useId();
1282
+ const control = useFieldControl(wiring);
1259
1283
  const enabledOptions = options.filter((o) => !o.disabled);
1260
1284
  const tabStop = rovingTabStop(options, (o) => o.value === value, (o) => !!o.disabled);
1261
1285
  function handleKeyDown(e) {
@@ -1285,7 +1309,11 @@ function RadioGroup({ value, onChange, options, orientation = "vertical", classN
1285
1309
  {
1286
1310
  ref: groupRef,
1287
1311
  role: "radiogroup",
1288
- "aria-labelledby": label ? groupId : void 0,
1312
+ id: control.id,
1313
+ "aria-labelledby": label ? groupId : control.labelId,
1314
+ "aria-describedby": control.describedBy,
1315
+ "aria-invalid": control.invalid || void 0,
1316
+ "aria-required": control.required || void 0,
1289
1317
  className: cn(
1290
1318
  "flex",
1291
1319
  orientation === "vertical" ? "flex-col gap-ds-2" : "flex-row flex-wrap gap-ds-3"
@@ -1478,7 +1506,8 @@ function parseTypedDate(text, dayFirst = localeDayFirst()) {
1478
1506
  }
1479
1507
  return null;
1480
1508
  }
1481
- function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, className, ref, container, error = false, label, id, size: sizeProp, min, max, range: range2, ...wiring }) {
1509
+ function DatePicker({ value, onChange: onChangeProp, placeholder = "Pick a date", disabled, className, ref, container, error = false, label, id, size: sizeProp, min, max, range: range2, ...wiring }) {
1510
+ const onChange = useReportedChange(onChangeProp, value);
1482
1511
  const contextSize = useContext(ControlSizeContext);
1483
1512
  const size = sizeProp ?? contextSize ?? "md";
1484
1513
  const [open, setOpen] = useState(false);
@@ -1779,13 +1808,13 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1779
1808
  className: "flex items-center justify-between bg-[var(--ds-bg-muted)] rounded-t-[var(--ds-radius-md)]",
1780
1809
  style: { padding: "var(--ds-space-2) var(--ds-space-3)" },
1781
1810
  children: [
1782
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: prevMonth, "aria-label": "Previous month", children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14, className: "rotate-180" }) }),
1811
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", onClick: prevMonth, "aria-label": "Previous month", children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14, className: "rotate-180" }) }),
1783
1812
  /* @__PURE__ */ jsxs("span", { className: "text-sm font-medium text-[var(--ds-text-primary)]", children: [
1784
1813
  MONTHS[month],
1785
1814
  " ",
1786
1815
  year
1787
1816
  ] }),
1788
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: nextMonth, "aria-label": "Next month", children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14 }) })
1817
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", onClick: nextMonth, "aria-label": "Next month", children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14 }) })
1789
1818
  ]
1790
1819
  }
1791
1820
  ),
@@ -1848,6 +1877,7 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1848
1877
  /* @__PURE__ */ jsx(
1849
1878
  Button,
1850
1879
  {
1880
+ type: "button",
1851
1881
  variant: "ghost",
1852
1882
  size: "sm",
1853
1883
  onClick: () => {
@@ -1861,6 +1891,7 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1861
1891
  value && /* @__PURE__ */ jsx(
1862
1892
  Button,
1863
1893
  {
1894
+ type: "button",
1864
1895
  variant: "ghost",
1865
1896
  size: "sm",
1866
1897
  onClick: () => choose(null),
@@ -2083,7 +2114,7 @@ var DEFAULT_DATE_RANGE_PRESETS = [
2083
2114
  ];
2084
2115
  function DateRangePicker({
2085
2116
  value,
2086
- onChange,
2117
+ onChange: onChangeProp,
2087
2118
  placeholder = "Pick a date range",
2088
2119
  disabled,
2089
2120
  error = false,
@@ -2099,6 +2130,7 @@ function DateRangePicker({
2099
2130
  size: sizeProp,
2100
2131
  ...wiring
2101
2132
  }) {
2133
+ const onChange = useReportedChange(onChangeProp, value);
2102
2134
  const contextSize = useContext(ControlSizeContext);
2103
2135
  const size = sizeProp ?? contextSize ?? "md";
2104
2136
  const [open, setOpen] = useState(false);
@@ -2246,7 +2278,7 @@ function DateRangePicker({
2246
2278
  ),
2247
2279
  style: { top: pos.top, left: pos.left },
2248
2280
  children: [
2249
- presets.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-ds-0.5 p-ds-2 border-r-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] min-w-[140px]", children: presets.map((p) => /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", className: "justify-start", onClick: () => {
2281
+ presets.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-ds-0.5 p-ds-2 border-r-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] min-w-[140px]", children: presets.map((p) => /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "justify-start", onClick: () => {
2250
2282
  const r = p.range();
2251
2283
  returnFocus();
2252
2284
  onChange(r);
@@ -2255,7 +2287,7 @@ function DateRangePicker({
2255
2287
  }, children: p.label }, p.label)) }),
2256
2288
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
2257
2289
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between bg-[var(--ds-bg-muted)] rounded-tr-[var(--ds-radius-md)] px-ds-3 py-ds-2", children: [
2258
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: () => pageMonths(-1), "aria-label": "Previous month", children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14, className: "rotate-180" }) }),
2290
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", onClick: () => pageMonths(-1), "aria-label": "Previous month", children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14, className: "rotate-180" }) }),
2259
2291
  /* @__PURE__ */ jsxs("span", { className: "text-sm font-medium text-[var(--ds-text-primary)]", children: [
2260
2292
  MONTHS2[months[0].getMonth()],
2261
2293
  " ",
@@ -2265,7 +2297,7 @@ function DateRangePicker({
2265
2297
  " ",
2266
2298
  months[1].getFullYear()
2267
2299
  ] }),
2268
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: () => pageMonths(1), "aria-label": "Next month", children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14 }) })
2300
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", onClick: () => pageMonths(1), "aria-label": "Next month", children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14 }) })
2269
2301
  ] }),
2270
2302
  /* @__PURE__ */ jsx("div", { ref: gridRef, onKeyDown: grid.onKeyDown, className: "flex gap-ds-4 px-ds-3 py-ds-2", onMouseLeave: () => setHover(null), children: months.map((m) => /* @__PURE__ */ jsxs("div", { role: "grid", "aria-label": `${MONTHS2[m.getMonth()]} ${m.getFullYear()}`, className: "w-[224px] flex flex-col gap-ds-0.5", children: [
2271
2303
  /* @__PURE__ */ jsx("div", { role: "row", className: "grid grid-cols-7 gap-ds-0.5 mb-ds-0.5", children: dayNames.map((d) => /* @__PURE__ */ jsx("div", { role: "columnheader", className: "text-center text-2xs font-medium text-[var(--ds-text-muted)] py-ds-1", children: d }, d)) }),
@@ -2306,7 +2338,7 @@ function DateRangePicker({
2306
2338
  ] }, `${m.getFullYear()}-${m.getMonth()}`)) }),
2307
2339
  /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center bg-[var(--ds-bg-muted)] border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] rounded-br-[var(--ds-radius-md)] px-ds-3 py-ds-2", children: [
2308
2340
  /* @__PURE__ */ jsx("span", { className: "text-xs text-[var(--ds-text-muted)]", children: value.start && !value.end ? "Now pick an end date" : summary ?? "Pick a start date" }),
2309
- (value.start || value.end) && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => {
2341
+ (value.start || value.end) && /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => {
2310
2342
  onChange({ start: null, end: null });
2311
2343
  setHover(null);
2312
2344
  }, children: "Clear" })
@@ -2456,28 +2488,50 @@ function Progress({ value, max = 100, variant = "default", size = "md", indeterm
2456
2488
  ] })
2457
2489
  ] });
2458
2490
  }
2491
+
2492
+ // ../../showcase/src/lib/dev-warn.ts
2493
+ var warned = /* @__PURE__ */ new Set();
2494
+ function warnOnce(component, message) {
2495
+ let dev = false;
2496
+ try {
2497
+ dev = process.env.NODE_ENV !== "production";
2498
+ } catch {
2499
+ dev = false;
2500
+ }
2501
+ const text = `${component}: ${message}`;
2502
+ if (!dev || warned.has(text)) return;
2503
+ warned.add(text);
2504
+ console.warn(text);
2505
+ }
2459
2506
  function TruncFileName({ children, className }) {
2460
2507
  const { ref, isTruncated } = useTextOverflow({ lines: 1 });
2461
2508
  const el = /* @__PURE__ */ jsx("p", { ref, className: cn("truncate", className), children });
2462
2509
  if (!isTruncated) return el;
2463
2510
  return /* @__PURE__ */ jsx(Tooltip, { content: children, children: el });
2464
2511
  }
2512
+ var PARALLEL = 3;
2465
2513
  function formatSize(bytes) {
2466
2514
  if (bytes < 1024) return `${bytes} B`;
2467
2515
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
2468
2516
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
2469
2517
  }
2470
- function FileUpload({ onFiles, accept, multiple = true, maxSize, className, error = false, label, id }) {
2471
- const [files, setFiles] = useState([]);
2518
+ var running = (item) => item.status === "queued" || item.status === "uploading";
2519
+ var failureOf = (err) => err instanceof Error && err.message ? err.message : "The upload didn't work.";
2520
+ var percentOf = (value) => typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.min(100, value)) : 0;
2521
+ var STILL_UPLOADING = "Wait for your files to finish uploading";
2522
+ function FileUpload({ onFiles, files, upload, onCancel, onRetry, onRemove, accept, multiple = true, maxSize, className, error = false, label, id }) {
2523
+ const controlled = files !== void 0;
2524
+ const [own, setOwn] = useState([]);
2525
+ const shown = controlled ? files : own;
2472
2526
  const [dragOver, setDragOver] = useState(false);
2473
2527
  const [rejected, setRejected] = useState(null);
2474
2528
  const inputRef = useRef(null);
2529
+ const listRef = useRef(null);
2475
2530
  const nextEntryId = useRef(0);
2476
- const timers = useRef(/* @__PURE__ */ new Map());
2477
- useEffect(() => {
2478
- const running = timers.current;
2479
- return () => running.forEach(clearInterval);
2480
- }, []);
2531
+ const controllers = useRef(/* @__PURE__ */ new Map());
2532
+ const uploadRef = useLatest(upload);
2533
+ const overlayMark = useContext(OverlayMarkCtx);
2534
+ const reportChange = useContext(FormChangeCtx);
2481
2535
  const field = useField();
2482
2536
  const ownId = useId();
2483
2537
  const inputId = id ?? `${ownId}-input`;
@@ -2485,39 +2539,141 @@ function FileUpload({ onFiles, accept, multiple = true, maxSize, className, erro
2485
2539
  const rejectedId = `${ownId}-rejected`;
2486
2540
  const showLabel = label != null && !field;
2487
2541
  const constraints = fileConstraints(accept, maxSize, multiple);
2488
- const invalid = error || rejected != null;
2489
- const describedBy = [constraints ? constraintsId : null, field?.describedBy, rejected ? rejectedId : null].filter(Boolean).join(" ") || void 0;
2490
- const stopTimer = (id2) => {
2491
- clearInterval(timers.current.get(id2));
2492
- timers.current.delete(id2);
2542
+ const patch = (itemId, change) => setOwn((prev) => prev.map((i) => i.id === itemId ? { ...i, ...change } : i));
2543
+ const stop = (itemId) => {
2544
+ controllers.current.get(itemId)?.abort();
2545
+ controllers.current.delete(itemId);
2546
+ };
2547
+ const start = (item) => {
2548
+ const run = uploadRef.current;
2549
+ if (!run || !item.file) return;
2550
+ const controller = new AbortController();
2551
+ controllers.current.set(item.id, controller);
2552
+ patch(item.id, { status: "uploading", progress: 0, error: void 0 });
2553
+ const settle = (change) => {
2554
+ if (controller.signal.aborted) return;
2555
+ controllers.current.delete(item.id);
2556
+ patch(item.id, change);
2557
+ };
2558
+ let attempt;
2559
+ try {
2560
+ attempt = Promise.resolve(run(item.file, {
2561
+ signal: controller.signal,
2562
+ onProgress: (percent) => {
2563
+ if (!controller.signal.aborted) patch(item.id, { progress: percentOf(percent) });
2564
+ }
2565
+ }));
2566
+ } catch (err) {
2567
+ attempt = Promise.reject(err);
2568
+ }
2569
+ attempt.then(() => settle({ status: "done", progress: 100 }), (err) => settle({ status: "error", error: failureOf(err) }));
2493
2570
  };
2571
+ const [hadUpload, setHadUpload] = useState(!!upload);
2572
+ if (!controlled && !!upload !== hadUpload) {
2573
+ setHadUpload(!!upload);
2574
+ setOwn((prev) => prev.map((i) => upload ? i.status === "added" && i.file ? { ...i, status: "queued" } : i : i.status === "queued" ? { ...i, status: "error", error: "The upload couldn't start." } : i));
2575
+ }
2576
+ useEffect(() => {
2577
+ if (controlled || !upload) return;
2578
+ let active = own.filter((i) => i.status === "uploading" && controllers.current.has(i.id)).length;
2579
+ for (const item of own) {
2580
+ if (active >= PARALLEL) break;
2581
+ if (item.status === "queued" || item.status === "uploading" && !controllers.current.has(item.id)) {
2582
+ start(item);
2583
+ active++;
2584
+ }
2585
+ }
2586
+ }, [own, controlled, upload]);
2587
+ const stopAll = useCallback(() => {
2588
+ controllers.current.forEach((c) => c.abort());
2589
+ controllers.current.clear();
2590
+ }, []);
2591
+ useEffect(() => stopAll, [stopAll]);
2592
+ const overlayOpen = overlayMark?.open ?? true;
2593
+ const [wasOpen, setWasOpen] = useState(overlayOpen);
2594
+ if (overlayOpen !== wasOpen) {
2595
+ setWasOpen(overlayOpen);
2596
+ if (!overlayOpen && !controlled) setOwn((prev) => prev.filter((i) => !running(i)));
2597
+ }
2598
+ useEffect(() => {
2599
+ if (!overlayOpen && !controlled) stopAll();
2600
+ }, [overlayOpen, controlled, stopAll]);
2601
+ const busy = shown.some((i) => i.status === "uploading" || i.status === "queued" && (controlled || !!upload));
2602
+ useEffect(() => {
2603
+ if (!busy || !overlayMark) return;
2604
+ return overlayMark.hold();
2605
+ }, [busy, overlayMark]);
2606
+ const form = useOptionalFormContext();
2607
+ const waitingKey = `${inputId}-uploads`;
2608
+ const waitingId = `${ownId}-waiting`;
2609
+ const busyRef = useLatest(busy);
2610
+ const registerCheck = form?.register;
2611
+ useEffect(() => registerCheck?.(waitingKey, () => busyRef.current ? STILL_UPLOADING : void 0), [registerCheck, waitingKey, busyRef]);
2612
+ const waiting = form?.errors[waitingKey];
2613
+ const clearWaiting = form?.setError;
2614
+ useEffect(() => {
2615
+ if (!busy && waiting) clearWaiting?.(waitingKey, void 0);
2616
+ }, [busy, waiting, clearWaiting, waitingKey]);
2617
+ useEffect(() => {
2618
+ const owner = inputRef.current?.form;
2619
+ if (!owner) return;
2620
+ const clear = () => setRejected(null);
2621
+ owner.addEventListener("submit", clear);
2622
+ return () => owner.removeEventListener("submit", clear);
2623
+ }, []);
2624
+ const invalid = error || rejected != null || !!waiting;
2625
+ const describedBy = [constraints ? constraintsId : null, field?.describedBy, rejected ? rejectedId : null, waiting ? waitingId : null].filter(Boolean).join(" ") || void 0;
2626
+ useEffect(() => {
2627
+ if (controlled && !onFiles) warnOnce("FileUpload", "`files` without `onFiles`: the page's list never gets the files the user picks. Add them in onFiles.");
2628
+ if (controlled && upload) warnOnce("FileUpload", "pass `files` or `upload`, not both: with `files` the page runs the uploads and FileUpload shows them.");
2629
+ if (!onRemove) warnOnce("FileUpload", "no `onRemove`: Remove shows only on a failed file, since the page would still have a file the user removed.");
2630
+ const ids = shown.map((i) => i.id);
2631
+ if (new Set(ids).size !== ids.length) warnOnce("FileUpload", "two files in the list share an id: give each file its own (the same file picked twice is two files).");
2632
+ }, [controlled, onFiles, upload, onRemove, shown]);
2633
+ const [heard, setHeard] = useState({ text: "", n: 0 });
2634
+ const signature2 = shown.map((i) => `${i.id}\0${i.status}`).join("");
2635
+ const [seen, setSeen] = useState(() => ({ signature: signature2, statuses: new Map(shown.map((i) => [i.id, i.status])) }));
2636
+ if (signature2 !== seen.signature) {
2637
+ const said = [];
2638
+ for (const item of shown) {
2639
+ const before = seen.statuses.get(item.id);
2640
+ if (!before || before === item.status) continue;
2641
+ if (item.status === "done") said.push(`${item.name} uploaded`);
2642
+ else if (item.status === "error") said.push(`${item.name} didn't upload: ${item.error ?? "the upload failed"}`);
2643
+ }
2644
+ setSeen({ signature: signature2, statuses: new Map(shown.map((i) => [i.id, i.status])) });
2645
+ if (said.length) setHeard((h) => ({ text: said.join(". "), n: h.n + 1 }));
2646
+ }
2647
+ const pendingFocus = useRef(null);
2648
+ const lastAction = useRef(null);
2649
+ useEffect(() => {
2650
+ const gone = pendingFocus.current;
2651
+ if (gone && !shown.some((i) => i.id === gone.id)) {
2652
+ pendingFocus.current = null;
2653
+ const rows = Array.from(listRef.current?.querySelectorAll(":scope > li") ?? []).map((row2) => row2.querySelector("button")).filter((b) => !!b);
2654
+ focusAfterRemoval(rows, gone.index, inputRef.current);
2655
+ return;
2656
+ }
2657
+ const last = lastAction.current;
2658
+ const active = document.activeElement;
2659
+ if (!last || last.el.isConnected || active && active !== document.body) return;
2660
+ const row = Array.from(listRef.current?.querySelectorAll(":scope > li") ?? []).find((li) => li.dataset.uploadId === last.id);
2661
+ row?.querySelector("button")?.focus();
2662
+ }, [shown]);
2494
2663
  const addFiles = useCallback(
2495
2664
  (fileList) => {
2496
2665
  const { accepted, message } = checkFiles(Array.from(fileList), { accept, maxSize, multiple });
2497
2666
  setRejected(message);
2498
2667
  if (accepted.length === 0) return;
2499
- const entries = accepted.map((file) => ({ id: nextEntryId.current++, file, progress: 0, status: "uploading" }));
2500
- if (!multiple) {
2501
- timers.current.forEach(clearInterval);
2502
- timers.current.clear();
2503
- }
2504
- setFiles((prev) => multiple ? [...prev, ...entries] : entries);
2505
- onFiles(accepted);
2506
- for (const { id: id2 } of entries) {
2507
- let progress = 0;
2508
- const timer = setInterval(() => {
2509
- progress = Math.min(100, progress + Math.random() * 30 + 10);
2510
- const done = progress >= 100;
2511
- if (done) {
2512
- clearInterval(timer);
2513
- timers.current.delete(id2);
2514
- }
2515
- setFiles((prev) => prev.map((f) => f.id === id2 ? { ...f, progress, status: done ? "complete" : "uploading" } : f));
2516
- }, 300);
2517
- timers.current.set(id2, timer);
2518
- }
2668
+ reportChange?.();
2669
+ onFiles?.(accepted);
2670
+ if (controlled) return;
2671
+ const status = uploadRef.current ? "queued" : "added";
2672
+ const entries = accepted.map((file) => ({ id: `${ownId}-file-${nextEntryId.current++}`, name: file.name, size: file.size, status, file }));
2673
+ if (!multiple) stopAll();
2674
+ setOwn((prev) => multiple ? [...prev, ...entries] : entries);
2519
2675
  },
2520
- [accept, maxSize, multiple, onFiles]
2676
+ [accept, maxSize, multiple, onFiles, controlled, ownId, reportChange, stopAll, uploadRef]
2521
2677
  );
2522
2678
  const handleDrop = useCallback(
2523
2679
  (e) => {
@@ -2527,10 +2683,31 @@ function FileUpload({ onFiles, accept, multiple = true, maxSize, className, erro
2527
2683
  },
2528
2684
  [addFiles]
2529
2685
  );
2530
- const removeFile = (id2) => {
2531
- stopTimer(id2);
2532
- setFiles((prev) => prev.filter((f) => f.id !== id2));
2686
+ const indexOf = (item) => shown.findIndex((i) => i.id === item.id);
2687
+ const cancel = (item) => {
2688
+ pendingFocus.current = { id: item.id, index: indexOf(item) };
2689
+ if (!controlled) {
2690
+ stop(item.id);
2691
+ setOwn((prev) => prev.filter((i) => i.id !== item.id));
2692
+ }
2693
+ onCancel?.(item);
2533
2694
  };
2695
+ const retry = (item) => {
2696
+ if (!controlled) patch(item.id, { status: "queued", progress: 0, error: void 0 });
2697
+ onRetry?.(item);
2698
+ };
2699
+ const remove = (item) => {
2700
+ pendingFocus.current = { id: item.id, index: indexOf(item) };
2701
+ reportChange?.();
2702
+ if (!controlled) {
2703
+ stop(item.id);
2704
+ setOwn((prev) => prev.filter((i) => i.id !== item.id));
2705
+ }
2706
+ onRemove?.(item);
2707
+ };
2708
+ const canCancel = !controlled || !!onCancel;
2709
+ const canRetry = !controlled || !!onRetry;
2710
+ const canRemove = (item) => !!onRemove || !controlled && item.status === "error";
2534
2711
  return /* @__PURE__ */ jsxs("div", { className: cn("space-y-ds-3", className), children: [
2535
2712
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-ds-1.5", children: [
2536
2713
  showLabel && /* @__PURE__ */ jsx(Label, { htmlFor: inputId, children: label }),
@@ -2584,35 +2761,51 @@ function FileUpload({ onFiles, accept, multiple = true, maxSize, className, erro
2584
2761
  ]
2585
2762
  }
2586
2763
  ),
2587
- rejected && /* @__PURE__ */ jsx(InlineFieldError, { id: rejectedId, message: rejected, className: "mt-0" })
2764
+ rejected && /* @__PURE__ */ jsx(InlineFieldError, { id: rejectedId, message: rejected, className: "mt-0" }),
2765
+ waiting && /* @__PURE__ */ jsx(InlineFieldError, { id: waitingId, message: waiting, className: "mt-0" })
2588
2766
  ] }),
2589
- files.length > 0 && /* @__PURE__ */ jsx("ul", { className: "space-y-ds-2", children: files.map((entry) => /* @__PURE__ */ jsxs(
2590
- "li",
2591
- {
2592
- className: cn(
2593
- "flex items-center gap-ds-3 text-sm",
2594
- "border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
2595
- "rounded-[var(--ds-radius-md)] bg-[var(--ds-bg-surface)]"
2596
- ),
2597
- style: { padding: "var(--ds-space-3)" },
2598
- children: [
2599
- /* @__PURE__ */ jsx(Icon, { name: "folder", size: 16, className: "text-[var(--ds-text-muted)] shrink-0" }),
2600
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
2601
- /* @__PURE__ */ jsx(TruncFileName, { className: "text-[var(--ds-text-primary)]", children: entry.file.name }),
2602
- /* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--ds-text-muted)]", children: formatSize(entry.file.size) }),
2603
- entry.status === "uploading" && /* @__PURE__ */ jsx(Progress, { value: entry.progress, size: "sm", className: "mt-ds-1" })
2604
- ] }),
2605
- entry.status === "complete" && /* @__PURE__ */ jsx(Icon, { name: "check", size: 16, className: "text-[var(--ds-status-success)] shrink-0" }),
2606
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: () => removeFile(entry.id), "aria-label": "Remove file", children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 14 }) })
2607
- ]
2608
- },
2609
- entry.id
2610
- )) })
2767
+ shown.length > 0 && /* @__PURE__ */ jsx("ul", { ref: listRef, className: "space-y-ds-2", children: shown.map((item) => {
2768
+ const progress = percentOf(item.progress);
2769
+ const words = item.status === "queued" ? "Waiting" : item.status === "uploading" ? `${Math.round(progress)}%` : item.status === "done" ? "Uploaded" : item.status === "error" ? "Failed" : null;
2770
+ return /* @__PURE__ */ jsxs(
2771
+ "li",
2772
+ {
2773
+ "data-upload-id": item.id,
2774
+ "data-upload-status": item.status,
2775
+ onFocus: (e) => {
2776
+ if (e.target instanceof HTMLButtonElement) lastAction.current = { id: item.id, el: e.target };
2777
+ },
2778
+ className: cn(
2779
+ "flex items-center gap-ds-3 text-sm",
2780
+ "border-[length:var(--ds-border-width)]",
2781
+ item.status === "error" ? "border-[var(--ds-status-error)]" : "border-[var(--ds-border-subtle)]",
2782
+ "rounded-[var(--ds-radius-md)] bg-[var(--ds-bg-surface)]"
2783
+ ),
2784
+ style: { padding: "var(--ds-space-3)" },
2785
+ children: [
2786
+ /* @__PURE__ */ jsx(Icon, { name: "folder", size: 16, className: "text-[var(--ds-text-muted)] shrink-0" }),
2787
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
2788
+ /* @__PURE__ */ jsx(TruncFileName, { className: "text-[var(--ds-text-primary)]", children: item.name }),
2789
+ (item.size != null || words) && /* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--ds-text-muted)]", children: [item.size != null ? formatSize(item.size) : null, words].filter(Boolean).join(" \xB7 ") }),
2790
+ item.status === "uploading" && /* @__PURE__ */ jsx(Progress, { value: progress, size: "sm", label: `Uploading ${item.name}`, className: "mt-ds-1" }),
2791
+ item.status === "error" && /* @__PURE__ */ jsx("p", { className: "mt-ds-1 text-xs text-[var(--ds-status-error)]", children: item.error ?? "The upload didn't work." })
2792
+ ] }),
2793
+ item.status === "done" && /* @__PURE__ */ jsx(Icon, { name: "check", size: 16, className: "text-[var(--ds-status-success)] shrink-0", "aria-hidden": "true" }),
2794
+ running(item) && canCancel && // the visible word starts the name, which says which file (WCAG 2.5.3)
2795
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => cancel(item), "aria-label": `Cancel ${item.name}`, children: "Cancel" }),
2796
+ item.status === "error" && canRetry && /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => retry(item), "aria-label": `Retry ${item.name}`, children: "Retry" }),
2797
+ !running(item) && canRemove(item) && /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", onClick: () => remove(item), "aria-label": `Remove ${item.name}`, children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 14 }) })
2798
+ ]
2799
+ },
2800
+ item.id
2801
+ );
2802
+ }) }),
2803
+ /* @__PURE__ */ jsx("div", { role: "status", className: "sr-only", children: /* @__PURE__ */ jsx("span", { children: heard.text }, heard.n) })
2611
2804
  ] });
2612
2805
  }
2613
2806
  function SliderRange({
2614
2807
  value,
2615
- onChange,
2808
+ onChange: onChangeProp,
2616
2809
  min = 0,
2617
2810
  max = 100,
2618
2811
  step = 1,
@@ -2627,6 +2820,7 @@ function SliderRange({
2627
2820
  trackStyle,
2628
2821
  formatValue
2629
2822
  }) {
2823
+ const onChange = useReportedChange(onChangeProp, value);
2630
2824
  const field = useField();
2631
2825
  const ownId = useId();
2632
2826
  const showLabel = label != null && !field;
@@ -2832,7 +3026,7 @@ function parseTime(value) {
2832
3026
  }
2833
3027
  function TimePicker({
2834
3028
  value,
2835
- onChange,
3029
+ onChange: onChangeProp,
2836
3030
  use24Hour = false,
2837
3031
  minuteStep = 15,
2838
3032
  disabled,
@@ -2844,6 +3038,7 @@ function TimePicker({
2844
3038
  size: sizeProp,
2845
3039
  ...wiring
2846
3040
  }) {
3041
+ const onChange = useReportedChange(onChangeProp, value);
2847
3042
  const contextSize = useContext(ControlSizeContext);
2848
3043
  const control = useFieldControl({ ...wiring, id, error: error || void 0 }, { requiredInDescription: true });
2849
3044
  const size = sizeProp ?? contextSize ?? "md";
@@ -3222,7 +3417,7 @@ var sizes = {
3222
3417
  };
3223
3418
  function NumberInput({
3224
3419
  value,
3225
- onChange,
3420
+ onChange: onChangeProp,
3226
3421
  min = -Infinity,
3227
3422
  max = Infinity,
3228
3423
  step = 1,
@@ -3236,6 +3431,7 @@ function NumberInput({
3236
3431
  allowDecimals,
3237
3432
  id
3238
3433
  }) {
3434
+ const onChange = useReportedChange(onChangeProp, value);
3239
3435
  const contextSize = useContext(ControlSizeContext);
3240
3436
  const size = sizeProp ?? contextSize ?? "md";
3241
3437
  const intervalRef = useRef(void 0);
@@ -3254,13 +3450,13 @@ function NumberInput({
3254
3450
  [min, max]
3255
3451
  );
3256
3452
  const valueRef = useLatest(value);
3257
- const commitValue = useCallback((v) => {
3453
+ const commitValue = useCallback((v, own = false) => {
3258
3454
  const clamped = clamp(v);
3259
3455
  valueRef.current = clamped;
3260
3456
  setDraft(String(clamped));
3261
- onChange(clamped);
3457
+ (own ? onChangeProp : onChange)(clamped);
3262
3458
  return clamped;
3263
- }, [onChange, clamp, valueRef]);
3459
+ }, [onChange, onChangeProp, clamp, valueRef]);
3264
3460
  const increment = useCallback(() => commitValue(valueRef.current + step), [commitValue, step, valueRef]);
3265
3461
  const decrement = useCallback(() => commitValue(valueRef.current - step), [commitValue, step, valueRef]);
3266
3462
  const startRepeat = useCallback(
@@ -3298,11 +3494,11 @@ function NumberInput({
3298
3494
  setFocused(false);
3299
3495
  const parsed = parseFloat(draft);
3300
3496
  if (!isNaN(parsed) && isFinite(parsed)) {
3301
- const committed = commitValue(parsed);
3497
+ const committed = commitValue(parsed, draft === String(value));
3302
3498
  setDraft(String(committed));
3303
3499
  } else {
3304
3500
  const fallback = isFinite(min) ? min : 0;
3305
- const committed = commitValue(value !== void 0 ? value : fallback);
3501
+ const committed = commitValue(value !== void 0 ? value : fallback, true);
3306
3502
  setDraft(String(committed));
3307
3503
  }
3308
3504
  }, [draft, commitValue, min, value]);
@@ -3543,7 +3739,7 @@ var Tag = forwardRef(function Tag2({
3543
3739
  });
3544
3740
  function TagInput({
3545
3741
  value,
3546
- onChange,
3742
+ onChange: onChangeProp,
3547
3743
  placeholder = "Add a tag...",
3548
3744
  maxTags = Infinity,
3549
3745
  disabled = false,
@@ -3552,6 +3748,7 @@ function TagInput({
3552
3748
  id,
3553
3749
  "aria-label": ariaLabel
3554
3750
  }) {
3751
+ const onChange = useReportedChange(onChangeProp, value);
3555
3752
  const [input, setInput] = useState("");
3556
3753
  const inputRef = useRef(null);
3557
3754
  const addTag = useCallback(
@@ -3677,7 +3874,7 @@ var sizes2 = {
3677
3874
  };
3678
3875
  function PinInput({
3679
3876
  value,
3680
- onChange,
3877
+ onChange: onChangeProp,
3681
3878
  length = 6,
3682
3879
  mask = false,
3683
3880
  disabled = false,
@@ -3686,6 +3883,7 @@ function PinInput({
3686
3883
  error = false,
3687
3884
  id
3688
3885
  }) {
3886
+ const onChange = useReportedChange(onChangeProp, value);
3689
3887
  const contextSize = useContext(ControlSizeContext);
3690
3888
  const size = sizeProp ?? contextSize ?? "md";
3691
3889
  const inputsRef = useRef([]);
@@ -3821,23 +4019,26 @@ function StarIcon({ filled, half, size }) {
3821
4019
  }
3822
4020
  function Rating({
3823
4021
  value,
3824
- onChange,
4022
+ onChange: onChangeProp,
3825
4023
  max = 5,
3826
4024
  allowHalf = false,
3827
4025
  readOnly = false,
3828
4026
  size = "md",
3829
4027
  className,
3830
- error = false
4028
+ error = false,
4029
+ ...wiring
3831
4030
  }) {
4031
+ const onChange = useReportedChange(onChangeProp, value);
3832
4032
  const [hoverValue, setHoverValue] = useState(null);
3833
4033
  const displayValue = hoverValue ?? value;
4034
+ const control = useFieldControl({ ...wiring, error: error || void 0 });
3834
4035
  const handleClick = useCallback(
3835
4036
  (starIdx, isLeftHalf) => {
3836
- if (readOnly || !onChange) return;
4037
+ if (readOnly || !onChangeProp) return;
3837
4038
  const newValue = allowHalf && isLeftHalf ? starIdx + 0.5 : starIdx + 1;
3838
4039
  onChange(newValue === value ? 0 : newValue);
3839
4040
  },
3840
- [readOnly, onChange, allowHalf, value]
4041
+ [readOnly, onChangeProp, onChange, allowHalf, value]
3841
4042
  );
3842
4043
  const handleMouseMove = useCallback(
3843
4044
  (starIdx, e) => {
@@ -3851,7 +4052,7 @@ function Rating({
3851
4052
  const iconSize = iconSizes[size];
3852
4053
  const step = allowHalf ? 0.5 : 1;
3853
4054
  const handleKeyDown = (e) => {
3854
- if (readOnly || !onChange) return;
4055
+ if (readOnly || !onChangeProp) return;
3855
4056
  let next = null;
3856
4057
  if (e.key === "ArrowRight" || e.key === "ArrowUp") next = Math.min(max, value + step);
3857
4058
  else if (e.key === "ArrowLeft" || e.key === "ArrowDown") next = Math.max(0, value - step);
@@ -3897,8 +4098,12 @@ function Rating({
3897
4098
  "div",
3898
4099
  {
3899
4100
  role: readOnly ? "img" : "radiogroup",
3900
- "aria-label": `Rating: ${value} out of ${max}`,
3901
- "aria-invalid": error || void 0,
4101
+ id: control.id,
4102
+ "aria-label": readOnly || !control.labelId ? `Rating: ${value} out of ${max}` : void 0,
4103
+ "aria-labelledby": readOnly ? void 0 : control.labelId,
4104
+ "aria-describedby": control.describedBy,
4105
+ "aria-invalid": readOnly ? void 0 : control.invalid || void 0,
4106
+ "aria-required": readOnly ? void 0 : control.required || void 0,
3902
4107
  onKeyDown: readOnly ? void 0 : handleKeyDown,
3903
4108
  className: cn(
3904
4109
  "inline-flex items-center gap-ds-0.5",
@@ -3975,13 +4180,14 @@ var DEFAULT_SWATCHES = [
3975
4180
  ];
3976
4181
  function ColorPicker({
3977
4182
  value,
3978
- onChange,
4183
+ onChange: onChangeProp,
3979
4184
  showAlpha = false,
3980
4185
  swatches = DEFAULT_SWATCHES,
3981
4186
  disabled = false,
3982
4187
  className,
3983
4188
  error = false
3984
4189
  }) {
4190
+ const onChange = useReportedChange(onChangeProp, value);
3985
4191
  const [hsl, setHsl] = useState(() => hexToHsl(value));
3986
4192
  const [hexInput, setHexInput] = useState(value);
3987
4193
  const satLightRef = useRef(null);
@@ -4222,6 +4428,7 @@ var Alert = forwardRef(function Alert2({
4222
4428
  trigger: /* @__PURE__ */ jsx(
4223
4429
  "button",
4224
4430
  {
4431
+ type: "button",
4225
4432
  className: cn(
4226
4433
  "shrink-0 text-xs font-medium px-ds-2.5 py-ds-1 rounded-[var(--ds-radius-sm)]",
4227
4434
  "transition-colors cursor-pointer",
@@ -4246,6 +4453,7 @@ var Alert = forwardRef(function Alert2({
4246
4453
  return /* @__PURE__ */ jsxs(
4247
4454
  "button",
4248
4455
  {
4456
+ type: "button",
4249
4457
  onClick: a.onClick,
4250
4458
  className: cn(
4251
4459
  "shrink-0 text-xs font-medium px-ds-2.5 py-ds-1 rounded-[var(--ds-radius-sm)]",
@@ -4266,6 +4474,7 @@ var Alert = forwardRef(function Alert2({
4266
4474
  /* @__PURE__ */ jsxs(
4267
4475
  "button",
4268
4476
  {
4477
+ type: "button",
4269
4478
  onClick: () => setDetailsExpanded(!detailsExpanded),
4270
4479
  "aria-expanded": detailsExpanded,
4271
4480
  className: cn(
@@ -4293,6 +4502,7 @@ var Alert = forwardRef(function Alert2({
4293
4502
  dismissible && /* @__PURE__ */ jsx(
4294
4503
  "button",
4295
4504
  {
4505
+ type: "button",
4296
4506
  onClick: onDismiss,
4297
4507
  className: "shrink-0 text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer",
4298
4508
  "aria-label": "Dismiss",
@@ -4303,6 +4513,7 @@ var Alert = forwardRef(function Alert2({
4303
4513
  }
4304
4514
  );
4305
4515
  });
4516
+ var isThenable = (value) => !!value && typeof value.then === "function";
4306
4517
  function stampFormId(node, formId) {
4307
4518
  return Children.map(node, (child) => {
4308
4519
  if (!isValidElement(child)) return child;
@@ -4359,12 +4570,19 @@ function FormWrapper({ onSubmit, validationMode: validationModeProp, children, a
4359
4570
  const pendingSubmit = useRef(false);
4360
4571
  const pendingFocus = useRef(false);
4361
4572
  const onSubmitRef = useLatest(onSubmit);
4573
+ const overlayMark = useContext(OverlayMarkCtx);
4574
+ const reportChange = useMemo(() => overlayMark ? () => overlayMark.report(formId) : null, [overlayMark, formId]);
4575
+ useEffect(() => overlayMark?.attach(formId), [overlayMark, formId]);
4362
4576
  useEffect(() => {
4363
4577
  if (pendingSubmit.current && submitted) {
4364
4578
  pendingSubmit.current = false;
4365
- onSubmitRef.current(errorsRef.current);
4579
+ const answer = overlayMark?.submit(formId, { element: () => formRef.current, failing: () => Object.keys(errorsRef.current).length > 0 });
4580
+ const result = onSubmitRef.current(errorsRef.current);
4581
+ if (!answer) return;
4582
+ if (isThenable(result)) void result.then(() => answer.settle("resolved"));
4583
+ else answer.settle("returned");
4366
4584
  }
4367
- }, [submitted, submitCount, errors, errorsRef, onSubmitRef]);
4585
+ }, [submitted, submitCount, errors, errorsRef, onSubmitRef, overlayMark, formId]);
4368
4586
  const submittedRef = useLatest(submitted);
4369
4587
  const errorSummaryRef = useLatest(errorSummary);
4370
4588
  const summaryRef = useRef(null);
@@ -4418,7 +4636,15 @@ function FormWrapper({ onSubmit, validationMode: validationModeProp, children, a
4418
4636
  pendingFocus.current = false;
4419
4637
  }, []);
4420
4638
  const setError = useCallback((field, message) => {
4421
- setErrors((prev) => prev[field] === message ? prev : { ...prev, [field]: message });
4639
+ setErrors((prev) => {
4640
+ if (!message) {
4641
+ if (!(field in prev)) return prev;
4642
+ const next = { ...prev };
4643
+ delete next[field];
4644
+ return next;
4645
+ }
4646
+ return prev[field] === message ? prev : { ...prev, [field]: message };
4647
+ });
4422
4648
  }, []);
4423
4649
  const checks = useRef(/* @__PURE__ */ new Map());
4424
4650
  const register = useCallback((field, check) => {
@@ -4441,11 +4667,20 @@ function FormWrapper({ onSubmit, validationMode: validationModeProp, children, a
4441
4667
  }, []);
4442
4668
  const handleSubmit = useCallback(
4443
4669
  (e) => {
4670
+ if (e.target !== e.currentTarget) return;
4444
4671
  e.preventDefault();
4672
+ e.stopPropagation();
4445
4673
  if (checks.current.size > 0) {
4446
- const found = {};
4447
- for (const [field, check] of checks.current) found[field] = check();
4448
- setErrors((prev) => ({ ...prev, ...found }));
4674
+ const found = /* @__PURE__ */ new Map();
4675
+ for (const [field, check] of checks.current) found.set(field, check());
4676
+ setErrors((prev) => {
4677
+ const next = { ...prev };
4678
+ for (const [field, message] of found) {
4679
+ if (message) next[field] = message;
4680
+ else delete next[field];
4681
+ }
4682
+ return next;
4683
+ });
4449
4684
  }
4450
4685
  pendingSubmit.current = true;
4451
4686
  pendingFocus.current = focusFirstError;
@@ -4468,22 +4703,79 @@ function FormWrapper({ onSubmit, validationMode: validationModeProp, children, a
4468
4703
  f.message
4469
4704
  ] }, f.id || f.label)) }) }) : null;
4470
4705
  const actionsNode = actions && !drawerFooter ? /* @__PURE__ */ jsx("div", { "data-form-actions": "", className: cn("flex flex-wrap items-center gap-ds-2 pt-ds-2", actionsAlign === "end" ? "justify-end" : "justify-start"), children: actions }) : null;
4471
- return /* @__PURE__ */ jsx(FormCtx.Provider, { value: { errors, setError, clearErrors, validationMode, submitted, register }, children: /* @__PURE__ */ jsxs("form", { ref: setRefs, id: drawerFooter ? formId : void 0, onSubmit: handleSubmit, onFocusCapture: userActed, onInputCapture: userActed, className: cn("flex flex-col gap-ds-5 max-w-[var(--ds-form-max,none)]", className), noValidate: true, children: [
4706
+ return /* @__PURE__ */ jsx(FormCtx.Provider, { value: { errors, setError, clearErrors, validationMode, submitted, register }, children: /* @__PURE__ */ jsx(FormChangeCtx.Provider, { value: reportChange, children: /* @__PURE__ */ jsxs("form", { ref: setRefs, id: drawerFooter ? formId : void 0, onSubmit: handleSubmit, onFocusCapture: userActed, onInputCapture: userActed, className: cn("flex flex-col gap-ds-5 max-w-[var(--ds-form-max,none)]", className), noValidate: true, children: [
4472
4707
  summaryNode && /* @__PURE__ */ jsx("div", { ref: summaryRef, "data-form-error-summary": "", tabIndex: -1, className: "rounded-[var(--ds-radius-md)] outline-none focus-visible:shadow-[var(--ds-shadow-focus)]", children: summaryNode }),
4473
4708
  children,
4474
4709
  actionsNode
4475
- ] }) });
4710
+ ] }) }) });
4711
+ }
4712
+ var FOCUSABLE2 = "input:not([disabled]):not([type='hidden']), select:not([disabled]), textarea:not([disabled]), [role='combobox'], [role='switch'], [role='radio'], [tabindex]:not([tabindex='-1'])";
4713
+ function AddAnother({ items, getId, itemName, columns, renderRow, onAdd, onRemove, addLabel = "Add another", label, minItems = 1, maxItems, className }) {
4714
+ const listRef = useRef(null);
4715
+ const addRef = useRef(null);
4716
+ const reportChange = useContext(FormChangeCtx);
4717
+ const getIdRef = useLatest(getId);
4718
+ const pending = useRef(null);
4719
+ useEffect(() => {
4720
+ const next = pending.current;
4721
+ if (!next) return;
4722
+ const rows = () => Array.from(listRef.current?.querySelectorAll(":scope > li") ?? []);
4723
+ const firstFields = () => rows().map((row) => row.querySelector(FOCUSABLE2)).filter((el) => !!el);
4724
+ const ids = items.map((item) => getIdRef.current(item));
4725
+ if (next.kind === "remove") {
4726
+ if (ids.includes(next.id)) return;
4727
+ pending.current = null;
4728
+ focusAfterRemoval(firstFields(), next.index, addRef.current);
4729
+ return;
4730
+ }
4731
+ const added = ids.findIndex((id) => !next.before.has(id));
4732
+ if (added === -1) return;
4733
+ pending.current = null;
4734
+ rows()[added]?.querySelector(FOCUSABLE2)?.focus();
4735
+ }, [items, getIdRef]);
4736
+ const canRemove = items.length > minItems;
4737
+ const canAdd = maxItems === void 0 || items.length < maxItems;
4738
+ const grid = { "--gs-add-another-cols": `repeat(${columns.length}, minmax(0, 1fr)) max-content` };
4739
+ const body = /* @__PURE__ */ jsxs("div", { "data-add-another": "", className: cn("@container flex flex-col gap-ds-3", className), style: grid, children: [
4740
+ /* @__PURE__ */ jsxs("div", { "aria-hidden": "true", className: "hidden gap-ds-3 text-sm font-medium text-[var(--ds-text-secondary)] @min-[30rem]:grid @min-[30rem]:grid-cols-[var(--gs-add-another-cols)]", children: [
4741
+ columns.map((column) => /* @__PURE__ */ jsx("span", { children: column }, column)),
4742
+ canRemove && /* @__PURE__ */ jsx("span", { className: "invisible inline-flex items-center whitespace-nowrap px-ds-4 text-xs font-medium", children: "Remove" })
4743
+ ] }),
4744
+ /* @__PURE__ */ jsx("ul", { ref: listRef, className: "flex flex-col gap-ds-4 @min-[30rem]:gap-ds-3", children: items.map((item, index) => {
4745
+ const id = getId(item);
4746
+ return /* @__PURE__ */ jsxs("li", { "data-add-another-row": "", className: "grid grid-cols-1 items-start gap-ds-3 @min-[30rem]:grid-cols-[var(--gs-add-another-cols)]", children: [
4747
+ /* @__PURE__ */ jsx(AddAnotherRowCtx.Provider, { value: true, children: renderRow(item, index, (column) => `${column}, ${itemName(index)}`) }),
4748
+ canRemove && // the visible "Remove" starts its name, which says whose row it removes (WCAG 2.5.3 label in name)
4749
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": `Remove ${itemName(index)}`, className: "justify-self-start @min-[30rem]:mt-[calc((var(--ds-control-height-md)-var(--ds-control-height-sm))/2)]", onClick: () => {
4750
+ pending.current = { kind: "remove", id, index };
4751
+ reportChange?.();
4752
+ onRemove(id);
4753
+ }, children: "Remove" })
4754
+ ] }, id);
4755
+ }) }),
4756
+ canAdd && /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, { ref: addRef, type: "button", variant: "secondary", onClick: () => {
4757
+ pending.current = { kind: "add", before: new Set(items.map(getId)) };
4758
+ reportChange?.();
4759
+ onAdd();
4760
+ }, children: addLabel }) })
4761
+ ] });
4762
+ if (!label) return body;
4763
+ return /* @__PURE__ */ jsxs("fieldset", { className: "min-w-0", children: [
4764
+ /* @__PURE__ */ jsx("legend", { className: "mb-ds-2 text-sm font-medium text-[var(--ds-text-primary)]", children: label }),
4765
+ body
4766
+ ] });
4476
4767
  }
4477
4768
  function TransferList({
4478
4769
  available,
4479
4770
  selected,
4480
- onChange,
4771
+ onChange: onChangeProp,
4481
4772
  availableTitle = "Available",
4482
4773
  selectedTitle = "Selected",
4483
4774
  searchable = true,
4484
4775
  className,
4485
4776
  ref
4486
4777
  }) {
4778
+ const onChange = useReportedChange(onChangeProp, selected, (_available, chosen) => chosen);
4487
4779
  const [checkedAvailable, setCheckedAvailable] = useState(/* @__PURE__ */ new Set());
4488
4780
  const [checkedSelected, setCheckedSelected] = useState(/* @__PURE__ */ new Set());
4489
4781
  const [searchLeft, setSearchLeft] = useState("");
@@ -4565,6 +4857,7 @@ function TransferList({
4565
4857
  /* @__PURE__ */ jsx(
4566
4858
  "button",
4567
4859
  {
4860
+ type: "button",
4568
4861
  onClick: moveAllRight,
4569
4862
  disabled: !available.filter((i) => !i.disabled).length,
4570
4863
  "aria-label": isStacked ? "Move all down" : "Move all right",
@@ -4575,6 +4868,7 @@ function TransferList({
4575
4868
  /* @__PURE__ */ jsx(
4576
4869
  "button",
4577
4870
  {
4871
+ type: "button",
4578
4872
  onClick: moveRight,
4579
4873
  disabled: !checkedAvailable.size,
4580
4874
  "aria-label": isStacked ? "Move selected down" : "Move selected right",
@@ -4585,6 +4879,7 @@ function TransferList({
4585
4879
  /* @__PURE__ */ jsx(
4586
4880
  "button",
4587
4881
  {
4882
+ type: "button",
4588
4883
  onClick: moveLeft,
4589
4884
  disabled: !checkedSelected.size,
4590
4885
  "aria-label": isStacked ? "Move selected up" : "Move selected left",
@@ -4595,6 +4890,7 @@ function TransferList({
4595
4890
  /* @__PURE__ */ jsx(
4596
4891
  "button",
4597
4892
  {
4893
+ type: "button",
4598
4894
  onClick: moveAllLeft,
4599
4895
  disabled: !selected.filter((i) => !i.disabled).length,
4600
4896
  "aria-label": isStacked ? "Move all up" : "Move all left",
@@ -4726,62 +5022,6 @@ function toInitials(value) {
4726
5022
  if (!/\s/.test(s) && Array.from(s).length <= 3) return s;
4727
5023
  return initialsOf(s);
4728
5024
  }
4729
- var variantColors = {
4730
- success: "bg-[var(--ds-status-success)]",
4731
- warning: "bg-[var(--ds-status-warning)]",
4732
- error: "bg-[var(--ds-status-error)]",
4733
- info: "bg-[var(--ds-accent-primary)]",
4734
- neutral: "bg-[var(--ds-text-muted)]",
4735
- offline: "bg-[var(--ds-text-muted)] opacity-disabled"
4736
- };
4737
- var pulseRingColors = {
4738
- success: "bg-[color-mix(in_oklch,var(--ds-status-success)_30%,transparent)]",
4739
- warning: "bg-[color-mix(in_oklch,var(--ds-status-warning)_30%,transparent)]",
4740
- error: "bg-[color-mix(in_oklch,var(--ds-status-error)_30%,transparent)]",
4741
- info: "bg-[var(--ds-accent-soft)]",
4742
- neutral: "bg-[color-mix(in_oklch,var(--ds-text-muted)_20%,transparent)]",
4743
- offline: "bg-[color-mix(in_oklch,var(--ds-text-muted)_10%,transparent)]"
4744
- };
4745
- var StatusDot = forwardRef(function StatusDot2({
4746
- variant = "neutral",
4747
- label,
4748
- pulse = false,
4749
- className
4750
- }, ref) {
4751
- return /* @__PURE__ */ jsxs(
4752
- "span",
4753
- {
4754
- ref,
4755
- className: cn("inline-flex items-center gap-ds-1.5", className),
4756
- role: "status",
4757
- "aria-label": label ?? variant,
4758
- children: [
4759
- /* @__PURE__ */ jsxs("span", { className: "relative inline-flex shrink-0", children: [
4760
- /* @__PURE__ */ jsx(
4761
- "span",
4762
- {
4763
- className: cn(
4764
- "rounded-[var(--ds-radius-full)] h-2.5 w-2.5",
4765
- variantColors[variant]
4766
- )
4767
- }
4768
- ),
4769
- pulse && variant !== "offline" && /* @__PURE__ */ jsx(
4770
- "span",
4771
- {
4772
- className: cn(
4773
- "absolute inset-0 rounded-[var(--ds-radius-full)]",
4774
- "animate-ping motion-reduce:animate-none",
4775
- pulseRingColors[variant]
4776
- )
4777
- }
4778
- )
4779
- ] }),
4780
- label && /* @__PURE__ */ jsx("span", { className: "font-normal text-[var(--ds-text-primary)] text-[length:var(--ds-font-size-xs)]", children: label })
4781
- ]
4782
- }
4783
- );
4784
- });
4785
5025
  var sizeStyles2 = {
4786
5026
  sm: "h-8 w-8 text-xs",
4787
5027
  md: "h-10 w-10 text-sm",
@@ -4804,13 +5044,23 @@ function colorStyles(color, seed) {
4804
5044
  if (color === "neutral") return "bg-[var(--ds-bg-muted)] text-[var(--ds-text-secondary)]";
4805
5045
  return paletteStyles[color === "auto" ? paletteFor(seed) : color];
4806
5046
  }
4807
- var statusVariantMap = {
4808
- online: "success",
4809
- away: "warning",
4810
- busy: "error",
4811
- offline: "offline"
4812
- };
5047
+ function PresenceMark({ status }) {
5048
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 10 10", width: "10", height: "10", "aria-hidden": "true", focusable: "false", "data-presence": status, className: "block", children: [
5049
+ status === "online" && /* @__PURE__ */ jsx("circle", { cx: "5", cy: "5", r: "5", className: "fill-[var(--ds-status-success)]" }),
5050
+ status === "away" && /* @__PURE__ */ jsx("circle", { cx: "5", cy: "5", r: "4", fill: "none", strokeWidth: "2", className: "stroke-[var(--ds-status-warning)]" }),
5051
+ status === "busy" && /* @__PURE__ */ jsxs(Fragment, { children: [
5052
+ /* @__PURE__ */ jsx("circle", { cx: "5", cy: "5", r: "5", className: "fill-[var(--ds-status-error)]" }),
5053
+ /* @__PURE__ */ jsx("rect", { x: "2.5", y: "4.1", width: "5", height: "1.8", rx: "0.9", className: "fill-[var(--ds-bg-canvas)]" })
5054
+ ] }),
5055
+ status === "offline" && /* @__PURE__ */ jsxs(Fragment, { children: [
5056
+ /* @__PURE__ */ jsx("circle", { cx: "5", cy: "5", r: "4", fill: "none", strokeWidth: "2", className: "stroke-[var(--ds-text-muted)]" }),
5057
+ /* @__PURE__ */ jsx("line", { x1: "3.1", y1: "6.9", x2: "6.9", y2: "3.1", strokeWidth: "1.5", strokeLinecap: "round", className: "stroke-[var(--ds-text-muted)]" })
5058
+ ] })
5059
+ ] });
5060
+ }
4813
5061
  function Avatar({ initials, src, alt, size = "md", status, color = "auto", className }) {
5062
+ const who = alt ?? initials ?? "";
5063
+ const named = status ? [who, status].filter(Boolean).join(", ") : void 0;
4814
5064
  const base = cn(
4815
5065
  "inline-flex items-center justify-center font-medium shrink-0",
4816
5066
  "rounded-[var(--ds-radius-full)]",
@@ -4822,13 +5072,15 @@ function Avatar({ initials, src, alt, size = "md", status, color = "auto", class
4822
5072
  {
4823
5073
  "data-avatar": "",
4824
5074
  src,
4825
- alt: alt ?? initials ?? "",
5075
+ alt: named ?? alt ?? initials ?? "",
4826
5076
  className: cn(base, "object-cover")
4827
5077
  }
4828
5078
  ) : /* @__PURE__ */ jsx(
4829
5079
  "div",
4830
5080
  {
4831
5081
  "data-avatar": "",
5082
+ role: named ? "img" : void 0,
5083
+ "aria-label": named,
4832
5084
  className: cn(
4833
5085
  base,
4834
5086
  colorStyles(color, alt ?? initials ?? ""),
@@ -4850,20 +5102,14 @@ function Avatar({ initials, src, alt, size = "md", status, color = "auto", class
4850
5102
  size === "lg" && "bottom-[0px] right-[0px]"
4851
5103
  ),
4852
5104
  "aria-hidden": "true",
4853
- children: /* @__PURE__ */ jsx(
4854
- StatusDot,
4855
- {
4856
- variant: statusVariantMap[status],
4857
- className: "rounded-[var(--ds-radius-full)] shadow-[0_0_0_2px_var(--ds-bg-canvas)]"
4858
- }
4859
- )
5105
+ children: /* @__PURE__ */ jsx("span", { className: "block rounded-[var(--ds-radius-full)] bg-[var(--ds-bg-canvas)] shadow-[0_0_0_2px_var(--ds-bg-canvas)]", children: /* @__PURE__ */ jsx(PresenceMark, { status }) })
4860
5106
  }
4861
5107
  )
4862
5108
  ] });
4863
5109
  }
4864
5110
  function MentionInput({
4865
5111
  value: controlledValue,
4866
- onChange,
5112
+ onChange: onChangeProp,
4867
5113
  suggestions,
4868
5114
  trigger = "@",
4869
5115
  placeholder,
@@ -4872,6 +5118,7 @@ function MentionInput({
4872
5118
  }) {
4873
5119
  const [internalValue, setInternalValue] = useState("");
4874
5120
  const value = controlledValue ?? internalValue;
5121
+ const onChange = useReportedChange(onChangeProp, value);
4875
5122
  const [query, setQuery] = useState("");
4876
5123
  const [showPopup, setShowPopup] = useState(false);
4877
5124
  const [activeIndex, setActiveIndex] = useState(0);
@@ -5777,6 +6024,57 @@ var DescriptionList = forwardRef(function DescriptionList2({
5777
6024
  }
5778
6025
  );
5779
6026
  });
6027
+ var variantColors = {
6028
+ success: "bg-[var(--ds-status-success)]",
6029
+ warning: "bg-[var(--ds-status-warning)]",
6030
+ error: "bg-[var(--ds-status-error)]",
6031
+ info: "bg-[var(--ds-accent-primary)]",
6032
+ neutral: "bg-[var(--ds-text-muted)]",
6033
+ offline: "bg-[var(--ds-text-muted)] opacity-disabled"
6034
+ };
6035
+ var pulseRingColors = {
6036
+ success: "bg-[color-mix(in_oklch,var(--ds-status-success)_30%,transparent)]",
6037
+ warning: "bg-[color-mix(in_oklch,var(--ds-status-warning)_30%,transparent)]",
6038
+ error: "bg-[color-mix(in_oklch,var(--ds-status-error)_30%,transparent)]",
6039
+ info: "bg-[var(--ds-accent-soft)]",
6040
+ neutral: "bg-[color-mix(in_oklch,var(--ds-text-muted)_20%,transparent)]",
6041
+ offline: "bg-[color-mix(in_oklch,var(--ds-text-muted)_10%,transparent)]"
6042
+ };
6043
+ var StatusDot = forwardRef(function StatusDot2({
6044
+ variant = "neutral",
6045
+ label,
6046
+ pulse = false,
6047
+ className
6048
+ }, ref) {
6049
+ return (
6050
+ // No live region (A48): the dot is read as its words where it sits; telling the user about a change as it happens
6051
+ // is the toasts' job (WCAG 4.1.3). The dot itself is hidden from assistive technology.
6052
+ /* @__PURE__ */ jsxs("span", { ref, "data-status-dot": variant, className: cn("inline-flex items-center gap-ds-1.5", className), children: [
6053
+ /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: "relative inline-flex shrink-0", children: [
6054
+ /* @__PURE__ */ jsx(
6055
+ "span",
6056
+ {
6057
+ className: cn(
6058
+ "rounded-[var(--ds-radius-full)] h-2.5 w-2.5",
6059
+ variantColors[variant]
6060
+ )
6061
+ }
6062
+ ),
6063
+ pulse && variant !== "offline" && /* @__PURE__ */ jsx(
6064
+ "span",
6065
+ {
6066
+ className: cn(
6067
+ "absolute inset-0 rounded-[var(--ds-radius-full)]",
6068
+ "animate-ping motion-reduce:animate-none",
6069
+ pulseRingColors[variant]
6070
+ )
6071
+ }
6072
+ )
6073
+ ] }),
6074
+ /* @__PURE__ */ jsx("span", { className: "font-normal text-[var(--ds-text-primary)] text-[length:var(--ds-font-size-xs)]", children: label })
6075
+ ] })
6076
+ );
6077
+ });
5780
6078
  function Accordion({ items, type = "single", defaultOpen = [], open: controlledOpen, onOpenChange, className }) {
5781
6079
  const [internalOpen, setInternalOpen] = useState(new Set(defaultOpen));
5782
6080
  const baseId = useId();
@@ -5811,6 +6109,7 @@ function Accordion({ items, type = "single", defaultOpen = [], open: controlledO
5811
6109
  /* @__PURE__ */ jsxs(
5812
6110
  "button",
5813
6111
  {
6112
+ type: "button",
5814
6113
  id: triggerId,
5815
6114
  "aria-expanded": isOpen,
5816
6115
  "aria-controls": panelId,
@@ -6196,6 +6495,7 @@ function Table({
6196
6495
  expandable && /* @__PURE__ */ jsx("td", { className: cn("w-8", density === "compact" ? "px-ds-1 py-ds-2" : "px-ds-2 py-ds-3"), children: /* @__PURE__ */ jsx(
6197
6496
  "button",
6198
6497
  {
6498
+ type: "button",
6199
6499
  onClick: () => toggleExpand(i),
6200
6500
  className: "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] cursor-pointer",
6201
6501
  "aria-expanded": isExpanded,
@@ -6312,7 +6612,7 @@ var indicatorInset = {
6312
6612
  };
6313
6613
  function SegmentedControl({
6314
6614
  value,
6315
- onChange,
6615
+ onChange: onChangeProp,
6316
6616
  options,
6317
6617
  size: sizeProp,
6318
6618
  variant = "default",
@@ -6323,6 +6623,7 @@ function SegmentedControl({
6323
6623
  maxOptions = 6,
6324
6624
  ...wiring
6325
6625
  }) {
6626
+ const onChange = useReportedChange(onChangeProp, value);
6326
6627
  const contextSize = useContext(ControlSizeContext);
6327
6628
  const control = useFieldControl(wiring);
6328
6629
  const named = !ariaLabel && control.labelId ? { "aria-labelledby": control.labelId } : {};
@@ -8212,74 +8513,78 @@ function TableToolbar({
8212
8513
  const paged = total !== void 0 && !!pagination && pagination.pageSize > 0 && total > pagination.pageSize;
8213
8514
  const countText = total === void 0 ? null : grows ? shown < total ? `Showing ${shown} of ${total} ${noun(total)}` : resultCount !== void 0 ? `${total} ${noun(total)}` : null : paged ? `Showing ${Math.min((pagination.page - 1) * pagination.pageSize + 1, total)}\u2013${Math.min(pagination.page * pagination.pageSize, total)} of ${total} ${noun(total)}` : resultCount !== void 0 ? `${total} ${noun(total)}` : null;
8214
8515
  if (!hasControls && countText === null && !hasChips) return null;
8215
- return /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: size, children: /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-ds-3 min-w-0 mb-ds-3 last:mb-0", className), "data-table-toolbar": "", children: [
8216
- hasControls && /* @__PURE__ */ jsxs("div", { ref: rowRef, className: "flex items-center gap-ds-2 flex-wrap", children: [
8217
- search && /* @__PURE__ */ jsx(
8218
- Input,
8219
- {
8220
- "aria-label": search.label,
8221
- placeholder: search.placeholder ?? search.label,
8222
- value: search.value,
8223
- size,
8224
- onChange: (e) => search.onChange(e.target.value),
8225
- className: cn("flex-1 min-w-[160px]", !searchFills && "max-w-[280px]")
8226
- }
8227
- ),
8228
- primaryFilter && (compact ? /* @__PURE__ */ jsx(
8229
- Dropdown,
8230
- {
8231
- "aria-label": primaryFilter.label,
8232
- options: primaryFilter.options.map((o) => ({ id: o.value, label: o.label })),
8233
- value: primaryFilter.value,
8234
- onChange: (v) => primaryFilter.onChange(v),
8235
- size,
8236
- className: "w-[150px]"
8237
- }
8238
- ) : /* @__PURE__ */ jsx(
8239
- SegmentedControl,
8240
- {
8241
- "aria-label": primaryFilter.label,
8242
- options: primaryFilter.options,
8243
- value: primaryFilter.value,
8244
- size,
8245
- onChange: primaryFilter.onChange
8246
- }
8247
- )),
8248
- (filters || actions) && /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-ds-2 justify-end", inPane ? "flex-none" : "flex-1"), children: [
8249
- filters && /* @__PURE__ */ jsxs(
8250
- Button,
8516
+ return (
8517
+ // a list's search and filters are how it's viewed, not a form's data: they never mark an overlay's form (0.21.0)
8518
+ /* @__PURE__ */ jsx(FormChangeCtx, { value: null, children: /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: size, children: /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-ds-3 min-w-0 mb-ds-3 last:mb-0", className), "data-table-toolbar": "", children: [
8519
+ hasControls && /* @__PURE__ */ jsxs("div", { ref: rowRef, className: "flex items-center gap-ds-2 flex-wrap", children: [
8520
+ search && /* @__PURE__ */ jsx(
8521
+ Input,
8251
8522
  {
8252
- variant: open ? "secondary" : "outline",
8523
+ "aria-label": search.label,
8524
+ placeholder: search.placeholder ?? search.label,
8525
+ value: search.value,
8253
8526
  size,
8254
- iconLeft: /* @__PURE__ */ jsx(Icon, { name: "filter", size: iconSize }),
8255
- onClick: () => setOpen(!open),
8256
- "aria-expanded": open,
8257
- "aria-controls": filterRowId,
8258
- className: "mr-auto",
8259
- children: [
8260
- "Filters",
8261
- count > 0 && !open && /* @__PURE__ */ jsx(Badge, { variant: "default", size: "xs", className: "ml-ds-1", children: count })
8262
- ]
8527
+ onChange: (e) => search.onChange(e.target.value),
8528
+ className: cn("flex-1 min-w-[160px]", !searchFills && "max-w-[280px]")
8263
8529
  }
8264
8530
  ),
8265
- actions
8266
- ] })
8267
- ] }),
8268
- filters && open && /* @__PURE__ */ jsx("div", { id: filterRowId, className: "flex items-center gap-ds-3 flex-wrap", children: filters }),
8269
- (countText !== null || hasChips) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2 flex-wrap", children: [
8270
- countText !== null && /* @__PURE__ */ jsx("span", { className: "text-xs text-[var(--ds-text-muted)]", "aria-live": "polite", "data-result-count": "", children: countText }),
8271
- activeFilters?.map((f) => /* @__PURE__ */ jsx(Tag, { size: "sm", variant: "subtle", onRemove: f.onRemove, children: f.label }, f.id)),
8272
- hasChips && onClearAll && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: onClearAll, children: "Clear all" })
8273
- ] })
8274
- ] }) });
8275
- }
8276
- var ICON_TEXT_OFFSET = "calc((2rem - 1.25rem) / 2)";
8277
- function Marker({ item, vertical }) {
8278
- if (item.icon) {
8279
- return /* @__PURE__ */ jsx("div", { "data-timeline-icon": "", className: "shrink-0 w-8 h-8 flex items-center justify-center *:max-w-8 *:max-h-8", children: item.icon });
8280
- }
8281
- return /* @__PURE__ */ jsx(
8282
- "div",
8531
+ primaryFilter && (compact ? /* @__PURE__ */ jsx(
8532
+ Dropdown,
8533
+ {
8534
+ "aria-label": primaryFilter.label,
8535
+ options: primaryFilter.options.map((o) => ({ id: o.value, label: o.label })),
8536
+ value: primaryFilter.value,
8537
+ onChange: (v) => primaryFilter.onChange(v),
8538
+ size,
8539
+ className: "w-[150px]"
8540
+ }
8541
+ ) : /* @__PURE__ */ jsx(
8542
+ SegmentedControl,
8543
+ {
8544
+ "aria-label": primaryFilter.label,
8545
+ options: primaryFilter.options,
8546
+ value: primaryFilter.value,
8547
+ size,
8548
+ onChange: primaryFilter.onChange
8549
+ }
8550
+ )),
8551
+ (filters || actions) && /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-ds-2 justify-end", inPane ? "flex-none" : "flex-1"), children: [
8552
+ filters && /* @__PURE__ */ jsxs(
8553
+ Button,
8554
+ {
8555
+ type: "button",
8556
+ variant: open ? "secondary" : "outline",
8557
+ size,
8558
+ iconLeft: /* @__PURE__ */ jsx(Icon, { name: "filter", size: iconSize }),
8559
+ onClick: () => setOpen(!open),
8560
+ "aria-expanded": open,
8561
+ "aria-controls": filterRowId,
8562
+ className: "mr-auto",
8563
+ children: [
8564
+ "Filters",
8565
+ count > 0 && !open && /* @__PURE__ */ jsx(Badge, { variant: "default", size: "xs", className: "ml-ds-1", children: count })
8566
+ ]
8567
+ }
8568
+ ),
8569
+ actions
8570
+ ] })
8571
+ ] }),
8572
+ filters && open && /* @__PURE__ */ jsx("div", { id: filterRowId, className: "flex items-center gap-ds-3 flex-wrap", children: filters }),
8573
+ (countText !== null || hasChips) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2 flex-wrap", children: [
8574
+ countText !== null && /* @__PURE__ */ jsx("span", { className: "text-xs text-[var(--ds-text-muted)]", "aria-live": "polite", "data-result-count": "", children: countText }),
8575
+ activeFilters?.map((f) => /* @__PURE__ */ jsx(Tag, { size: "sm", variant: "subtle", onRemove: f.onRemove, children: f.label }, f.id)),
8576
+ hasChips && onClearAll && /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: onClearAll, children: "Clear all" })
8577
+ ] })
8578
+ ] }) }) })
8579
+ );
8580
+ }
8581
+ var ICON_TEXT_OFFSET = "calc((2rem - 1.25rem) / 2)";
8582
+ function Marker({ item, vertical }) {
8583
+ if (item.icon) {
8584
+ return /* @__PURE__ */ jsx("div", { "data-timeline-icon": "", className: "shrink-0 w-8 h-8 flex items-center justify-center *:max-w-8 *:max-h-8", children: item.icon });
8585
+ }
8586
+ return /* @__PURE__ */ jsx(
8587
+ "div",
8283
8588
  {
8284
8589
  className: cn(
8285
8590
  "shrink-0 w-3 h-3 rounded-[var(--ds-radius-full)]",
@@ -8436,6 +8741,8 @@ function CollapsibleTrigger({ children, className }) {
8436
8741
  if (isValidElement(children)) {
8437
8742
  const child = children;
8438
8743
  return cloneElement(child, {
8744
+ // a Button trigger in a form toggles; it never submits the form (0.21.0)
8745
+ ...triggerType(child),
8439
8746
  "aria-expanded": isOpen,
8440
8747
  className: cn(child.props.className, className),
8441
8748
  onClick: (e) => {
@@ -8864,6 +9171,7 @@ function Carousel({
8864
9171
  /* @__PURE__ */ jsx(
8865
9172
  "button",
8866
9173
  {
9174
+ type: "button",
8867
9175
  onClick: prev,
8868
9176
  disabled: !canPrev,
8869
9177
  "aria-label": "Previous slide",
@@ -8883,6 +9191,7 @@ function Carousel({
8883
9191
  /* @__PURE__ */ jsx(
8884
9192
  "button",
8885
9193
  {
9194
+ type: "button",
8886
9195
  onClick: next,
8887
9196
  disabled: !canNext,
8888
9197
  "aria-label": "Next slide",
@@ -8903,6 +9212,7 @@ function Carousel({
8903
9212
  showIndicators && count > 1 && /* @__PURE__ */ jsx("div", { className: "absolute bottom-ds-3 left-1/2 -translate-x-1/2 flex gap-ds-1.5 z-10", children: children.map((_, i) => /* @__PURE__ */ jsx(
8904
9213
  "button",
8905
9214
  {
9215
+ type: "button",
8906
9216
  onClick: () => goTo(i),
8907
9217
  "aria-label": `Go to slide ${i + 1}`,
8908
9218
  className: cn(
@@ -8982,7 +9292,7 @@ function ActionsRow({ message, onAction }) {
8982
9292
  ),
8983
9293
  children: message.actions.map((a) => {
8984
9294
  const { id, label, icon } = resolveAction(a);
8985
- return /* @__PURE__ */ jsx(Tooltip, { content: label, children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-xs", "aria-label": label, onClick: () => run(id), children: /* @__PURE__ */ jsx(Icon, { name: icon, size: 14 }) }) }, id);
9295
+ return /* @__PURE__ */ jsx(Tooltip, { content: label, children: /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-xs", "aria-label": label, onClick: () => run(id), children: /* @__PURE__ */ jsx(Icon, { name: icon, size: 14 }) }) }, id);
8986
9296
  })
8987
9297
  }
8988
9298
  );
@@ -9119,12 +9429,13 @@ function ChatInputBar({ onSend, busy, onStop, onAttach, onVoice, placeholder })
9119
9429
  ),
9120
9430
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-ds-2 pb-ds-1.5", children: [
9121
9431
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-0.5", children: [
9122
- onAttach && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Add attachment", onClick: onAttach, className: compact, children: /* @__PURE__ */ jsx(Icon, { name: "paperclip", size: glyph }) }),
9123
- onVoice && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Voice message", onClick: onVoice, className: compact, children: /* @__PURE__ */ jsx(Icon, { name: "mic", size: glyph }) })
9432
+ onAttach && /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": "Add attachment", onClick: onAttach, className: compact, children: /* @__PURE__ */ jsx(Icon, { name: "paperclip", size: glyph }) }),
9433
+ onVoice && /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": "Voice message", onClick: onVoice, className: compact, children: /* @__PURE__ */ jsx(Icon, { name: "mic", size: glyph }) })
9124
9434
  ] }),
9125
- busy ? /* @__PURE__ */ jsx(Button, { variant: "tertiary", size: "icon-sm", "aria-label": "Stop generating", onClick: onStop, className: compact, children: /* @__PURE__ */ jsx(Icon, { name: "square", size: glyph }) }) : /* @__PURE__ */ jsx(
9435
+ busy ? /* @__PURE__ */ jsx(Button, { type: "button", variant: "tertiary", size: "icon-sm", "aria-label": "Stop generating", onClick: onStop, className: compact, children: /* @__PURE__ */ jsx(Icon, { name: "square", size: glyph }) }) : /* @__PURE__ */ jsx(
9126
9436
  Button,
9127
9437
  {
9438
+ type: "button",
9128
9439
  variant: text.trim() ? "default" : "ghost",
9129
9440
  size: "icon-sm",
9130
9441
  "aria-label": "Send message",
@@ -9238,7 +9549,7 @@ function ChatMessageList({
9238
9549
  }),
9239
9550
  typingUser && /* @__PURE__ */ jsx(TypingIndicator, { name: typingUser })
9240
9551
  ] }) }),
9241
- showJump && !empty && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-x-0 bottom-ds-2 flex justify-center", children: /* @__PURE__ */ jsxs(Button, { variant: "secondary", size: "xs", className: "pointer-events-auto rounded-[var(--ds-radius-full)]", onClick: scrollToBottom, children: [
9552
+ showJump && !empty && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-x-0 bottom-ds-2 flex justify-center", children: /* @__PURE__ */ jsxs(Button, { type: "button", variant: "secondary", size: "xs", className: "pointer-events-auto rounded-[var(--ds-radius-full)]", onClick: scrollToBottom, children: [
9242
9553
  /* @__PURE__ */ jsx(Icon, { name: "chevron-down", size: 14 }),
9243
9554
  "Jump to latest"
9244
9555
  ] }) })
@@ -9293,10 +9604,11 @@ function ChatPanel({
9293
9604
  ] }),
9294
9605
  /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-ds-1", children: [
9295
9606
  actions,
9296
- onNewConversation && /* @__PURE__ */ jsx(Tooltip, { content: "New conversation", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "New conversation", onClick: onNewConversation, children: /* @__PURE__ */ jsx(Icon, { name: "plus", size: 16 }) }) }),
9607
+ onNewConversation && /* @__PURE__ */ jsx(Tooltip, { content: "New conversation", children: /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": "New conversation", onClick: onNewConversation, children: /* @__PURE__ */ jsx(Icon, { name: "plus", size: 16 }) }) }),
9297
9608
  onExpandedChange && /* @__PURE__ */ jsx(Tooltip, { content: expanded ? "Collapse" : "Expand", children: /* @__PURE__ */ jsx(
9298
9609
  Button,
9299
9610
  {
9611
+ type: "button",
9300
9612
  variant: "ghost",
9301
9613
  size: "icon-sm",
9302
9614
  "aria-label": expanded ? "Collapse" : "Expand",
@@ -9305,7 +9617,7 @@ function ChatPanel({
9305
9617
  children: /* @__PURE__ */ jsx(Icon, { name: expanded ? "minimize-2" : "maximize-2", size: 16 })
9306
9618
  }
9307
9619
  ) }),
9308
- /* @__PURE__ */ jsx(Tooltip, { content: "Close", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Close", onClick: onClose, ...{ [OVERLAY_CLOSE_ATTR]: "" }, children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 16 }) }) })
9620
+ /* @__PURE__ */ jsx(Tooltip, { content: "Close", children: /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": "Close", onClick: onClose, ...{ [OVERLAY_CLOSE_ATTR]: "" }, children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 16 }) }) })
9309
9621
  ] })
9310
9622
  ]
9311
9623
  }
@@ -10542,15 +10854,15 @@ var TimeViewport = forwardRef(function TimeViewport2({
10542
10854
  const hasHeader = headerNode != null || labelHeader != null;
10543
10855
  const bar = toolbar === false ? null : /* @__PURE__ */ jsxs("div", { className: cn("flex shrink-0 flex-wrap items-center gap-ds-1 px-ds-2 py-ds-1.5 border-b-[length:var(--ds-border-width)]", border), children: [
10544
10856
  onZoomChange && levels.length > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
10545
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Zoom out", "aria-disabled": !canZoomOut || void 0, className: "aria-disabled:opacity-disabled aria-disabled:cursor-not-allowed", onClick: () => zoom(1), children: /* @__PURE__ */ jsx(Icon, { name: "minus", size: 14 }) }),
10857
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": "Zoom out", "aria-disabled": !canZoomOut || void 0, className: "aria-disabled:opacity-disabled aria-disabled:cursor-not-allowed", onClick: () => zoom(1), children: /* @__PURE__ */ jsx(Icon, { name: "minus", size: 14 }) }),
10546
10858
  /* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "min-w-[4.5rem] text-center text-xs text-[var(--ds-text-secondary)]", children: ZOOM_NAMES[scale.zoom] }),
10547
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Zoom in", "aria-disabled": !canZoomIn || void 0, className: "aria-disabled:opacity-disabled aria-disabled:cursor-not-allowed", onClick: () => zoom(-1), children: /* @__PURE__ */ jsx(Icon, { name: "plus", size: 14 }) })
10859
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": "Zoom in", "aria-disabled": !canZoomIn || void 0, className: "aria-disabled:opacity-disabled aria-disabled:cursor-not-allowed", onClick: () => zoom(-1), children: /* @__PURE__ */ jsx(Icon, { name: "plus", size: 14 }) })
10548
10860
  ] }),
10549
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Previous period", onClick: () => api.page(-1), children: /* @__PURE__ */ jsx(Icon, { name: "chevron-left", size: 14 }) }),
10550
- /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", disabled: todayAtX === null, onClick: () => {
10861
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": "Previous period", onClick: () => api.page(-1), children: /* @__PURE__ */ jsx(Icon, { name: "chevron-left", size: 14 }) }),
10862
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", size: "sm", disabled: todayAtX === null, onClick: () => {
10551
10863
  if (todayAtX !== null) setLeft(todayAtX - visibleWidth / 2);
10552
10864
  }, children: "Today" }),
10553
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Next period", onClick: () => api.page(1), children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14 }) }),
10865
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": "Next period", onClick: () => api.page(1), children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14 }) }),
10554
10866
  toolbar
10555
10867
  ] });
10556
10868
  return (
@@ -10807,16 +11119,16 @@ var SELECTED = "outline-solid outline-2 outline-offset-2 outline-[var(--ds-text-
10807
11119
  var UNDATED_INSET = 16;
10808
11120
  var NO_GROUPS = [];
10809
11121
  var NO_STATUSES = {};
10810
- var warned = /* @__PURE__ */ new Set();
10811
- function warnOnce(message) {
11122
+ var warned2 = /* @__PURE__ */ new Set();
11123
+ function warnOnce2(message) {
10812
11124
  let dev = false;
10813
11125
  try {
10814
11126
  dev = process.env.NODE_ENV !== "production";
10815
11127
  } catch {
10816
11128
  dev = false;
10817
11129
  }
10818
- if (!dev || warned.has(message)) return;
10819
- warned.add(message);
11130
+ if (!dev || warned2.has(message)) return;
11131
+ warned2.add(message);
10820
11132
  console.warn(`Gantt: ${message}`);
10821
11133
  }
10822
11134
  function GanttArrows({ arrows, width, height }) {
@@ -10898,7 +11210,7 @@ function Gantt({
10898
11210
  const selected = isControlled ? selectedId ?? null : selectedState;
10899
11211
  useEffect(() => {
10900
11212
  if (overridden !== null) {
10901
- warnOnce("selectedId was given a value after the Gantt had kept a selection of its own. To control the selection, pass selectedId from the first render (null for none); to let the Gantt keep it, leave selectedId out.");
11213
+ warnOnce2("selectedId was given a value after the Gantt had kept a selection of its own. To control the selection, pass selectedId from the first render (null for none); to let the Gantt keep it, leave selectedId out.");
10902
11214
  }
10903
11215
  }, [overridden]);
10904
11216
  const select = (item) => {
@@ -10910,8 +11222,8 @@ function Gantt({
10910
11222
  const phases = useMemo(() => uniqueById(groups), [groups]);
10911
11223
  useEffect(() => {
10912
11224
  const quoted = (ids) => ids.map((id) => `"${id}"`).join(", ");
10913
- if (plan.repeated.length) warnOnce(`more than one item has the id ${quoted(plan.repeated)}; only the first of each is shown.`);
10914
- if (phases.repeated.length) warnOnce(`more than one group has the id ${quoted(phases.repeated)}; only the first of each is shown.`);
11225
+ if (plan.repeated.length) warnOnce2(`more than one item has the id ${quoted(plan.repeated)}; only the first of each is shown.`);
11226
+ if (phases.repeated.length) warnOnce2(`more than one group has the id ${quoted(phases.repeated)}; only the first of each is shown.`);
10915
11227
  }, [plan, phases]);
10916
11228
  const tasks = plan.unique;
10917
11229
  const extent = useMemo(() => itemsExtent(tasks), [tasks]);
@@ -11651,6 +11963,7 @@ function TabButton({
11651
11963
  const btn2 = /* @__PURE__ */ jsxs(
11652
11964
  "button",
11653
11965
  {
11966
+ type: "button",
11654
11967
  id: tabId,
11655
11968
  role: "tab",
11656
11969
  "aria-selected": isActive,
@@ -11686,6 +11999,7 @@ function TabButton({
11686
11999
  const btn = /* @__PURE__ */ jsx(
11687
12000
  "button",
11688
12001
  {
12002
+ type: "button",
11689
12003
  id: tabId,
11690
12004
  role: "tab",
11691
12005
  "aria-selected": isActive,
@@ -11806,14 +12120,17 @@ function TabsRoot(props) {
11806
12120
  const { tabs, mobile = "scroll" } = props;
11807
12121
  const collapseToDropdown = isMobile && (orientation === "responsive" || mobile === "dropdown");
11808
12122
  if (collapseToDropdown) {
11809
- return /* @__PURE__ */ jsx(
11810
- Dropdown,
11811
- {
11812
- options: tabs.filter((t) => !t.disabled).map((t) => ({ id: t.id, label: t.label, group: t.group })),
11813
- value: activeTab,
11814
- onChange: (v) => onTabChange(v),
11815
- className
11816
- }
12123
+ return (
12124
+ // choosing a tab is navigation, never a change to a form around it (0.21.0)
12125
+ /* @__PURE__ */ jsx(FormChangeCtx, { value: null, children: /* @__PURE__ */ jsx(
12126
+ Dropdown,
12127
+ {
12128
+ options: tabs.filter((t) => !t.disabled).map((t) => ({ id: t.id, label: t.label, group: t.group })),
12129
+ value: activeTab,
12130
+ onChange: (v) => onTabChange(v),
12131
+ className
12132
+ }
12133
+ ) })
11817
12134
  );
11818
12135
  }
11819
12136
  if (effectiveOrientation === "vertical") {
@@ -11915,7 +12232,7 @@ function SettingsLayout({
11915
12232
  return (
11916
12233
  // gap-x-ds-8 is the rail's gap, unused in one column: the threshold reads the same gap as the row (useColumnGap)
11917
12234
  /* @__PURE__ */ jsxs("div", { ref: rootRef, "data-settings-layout": "sidebar", "data-collapsed": "true", className: cn("flex flex-col gap-y-ds-6 gap-x-ds-8", className), children: [
11918
- /* @__PURE__ */ jsx(
12235
+ /* @__PURE__ */ jsx(FormChangeCtx, { value: null, children: /* @__PURE__ */ jsx(
11919
12236
  Dropdown,
11920
12237
  {
11921
12238
  "aria-label": ariaLabel,
@@ -11923,7 +12240,7 @@ function SettingsLayout({
11923
12240
  value,
11924
12241
  onChange: (v) => onChange(v)
11925
12242
  }
11926
- ),
12243
+ ) }),
11927
12244
  /* @__PURE__ */ jsx("div", { "data-settings-panel": "", className: PANEL_CLASSES, style: PANEL_STYLE, children })
11928
12245
  ] })
11929
12246
  );
@@ -12059,6 +12376,7 @@ var Breadcrumbs = forwardRef(function Breadcrumbs2({
12059
12376
  /* @__PURE__ */ jsx(
12060
12377
  "button",
12061
12378
  {
12379
+ type: "button",
12062
12380
  ref: ellipsisBtnRef,
12063
12381
  onClick: () => {
12064
12382
  if (!dropdownOpen) setActiveIdx(0);
@@ -12109,6 +12427,7 @@ var Breadcrumbs = forwardRef(function Breadcrumbs2({
12109
12427
  children: collapsedItems.map((ci, idx) => /* @__PURE__ */ jsxs(
12110
12428
  "button",
12111
12429
  {
12430
+ type: "button",
12112
12431
  "data-index": idx,
12113
12432
  role: "menuitem",
12114
12433
  tabIndex: idx === activeIdx ? 0 : -1,
@@ -12207,6 +12526,7 @@ function Popover({ open, onOpenChange, trigger, children, side = "bottom", align
12207
12526
  /* @__PURE__ */ jsx(
12208
12527
  Slot,
12209
12528
  {
12529
+ ...triggerType(trigger),
12210
12530
  ref: setTriggerNode,
12211
12531
  onClick: () => {
12212
12532
  setTriggerWidth(triggerRef.current?.offsetWidth);
@@ -12279,7 +12599,7 @@ function LoadMore({ hasMore, onLoadMore, loadMoreLabel, currentPage, scroll, cla
12279
12599
  if (!hasMore) return null;
12280
12600
  return /* @__PURE__ */ jsxs("div", { "data-ds-pager": "", "data-pagination-mode": scroll ? "infinite-scroll" : "load-more", className: cn("flex flex-col items-center", className), children: [
12281
12601
  scroll && /* @__PURE__ */ jsx("div", { ref: marker, "data-load-more-trigger": "", "aria-hidden": "true", className: "h-px w-full" }),
12282
- /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onLoadMore, children: loadMoreLabel ?? "Load more" })
12602
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", onClick: onLoadMore, children: loadMoreLabel ?? "Load more" })
12283
12603
  ] });
12284
12604
  }
12285
12605
  function PageNumbers({ currentPage, totalPages, onPageChange, pageSize: pageSizeProp, pageSizeOptions, onPageSizeChange, className }) {
@@ -12289,7 +12609,7 @@ function PageNumbers({ currentPage, totalPages, onPageChange, pageSize: pageSize
12289
12609
  return /* @__PURE__ */ jsxs("nav", { "aria-label": "Pagination", "data-ds-pager": "", className: cn("flex flex-wrap items-center gap-ds-3", className), children: [
12290
12610
  pageSizeOptions && pageSize != null && onPageSizeChange && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2", children: [
12291
12611
  /* @__PURE__ */ jsx("span", { className: "text-sm text-[var(--ds-text-muted)] whitespace-nowrap", children: "Rows per page" }),
12292
- /* @__PURE__ */ jsx(
12612
+ /* @__PURE__ */ jsx(FormChangeCtx, { value: null, children: /* @__PURE__ */ jsx(
12293
12613
  Dropdown,
12294
12614
  {
12295
12615
  options: pageSizeOptions.map((n) => ({ id: String(n), label: String(n) })),
@@ -12298,12 +12618,13 @@ function PageNumbers({ currentPage, totalPages, onPageChange, pageSize: pageSize
12298
12618
  size: "sm",
12299
12619
  className: "w-[4.5rem]"
12300
12620
  }
12301
- )
12621
+ ) })
12302
12622
  ] }),
12303
12623
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-1", children: [
12304
12624
  /* @__PURE__ */ jsx(
12305
12625
  "button",
12306
12626
  {
12627
+ type: "button",
12307
12628
  onClick: () => onPageChange(currentPage - 1),
12308
12629
  disabled: currentPage <= 1,
12309
12630
  "aria-label": "Previous page",
@@ -12323,6 +12644,7 @@ function PageNumbers({ currentPage, totalPages, onPageChange, pageSize: pageSize
12323
12644
  (p, i) => p === "..." ? /* @__PURE__ */ jsx(EllipsisJumper, { totalPages, onPageChange }, `ellipsis-${i}`) : /* @__PURE__ */ jsx(
12324
12645
  "button",
12325
12646
  {
12647
+ type: "button",
12326
12648
  onClick: () => onPageChange(p),
12327
12649
  "aria-current": p === currentPage ? "page" : void 0,
12328
12650
  className: cn(
@@ -12340,6 +12662,7 @@ function PageNumbers({ currentPage, totalPages, onPageChange, pageSize: pageSize
12340
12662
  /* @__PURE__ */ jsx(
12341
12663
  "button",
12342
12664
  {
12665
+ type: "button",
12343
12666
  onClick: () => onPageChange(currentPage + 1),
12344
12667
  disabled: currentPage >= totalPages,
12345
12668
  "aria-label": "Next page",
@@ -12602,6 +12925,7 @@ function StepCircle({ status, index, onClick, clickable }) {
12602
12925
  return /* @__PURE__ */ jsx(
12603
12926
  "button",
12604
12927
  {
12928
+ type: "button",
12605
12929
  onClick,
12606
12930
  disabled: !clickable,
12607
12931
  "aria-current": status === "current" ? "step" : void 0,
@@ -12796,7 +13120,7 @@ var Sidebar = forwardRef(function Sidebar2({
12796
13120
  position = "flush",
12797
13121
  navbarFloating: navbarFloatingProp,
12798
13122
  navSize = "md",
12799
- fill = false,
13123
+ fill: fill2 = false,
12800
13124
  className
12801
13125
  }, ref) {
12802
13126
  const [internalCollapsed, setInternalCollapsed] = useState(defaultCollapsed ?? false);
@@ -12806,10 +13130,10 @@ var Sidebar = forwardRef(function Sidebar2({
12806
13130
  const appShell = useAppShell();
12807
13131
  const navbarFloating = navbarFloatingProp ?? appShell?.navbarFloating;
12808
13132
  useLayoutEffect(() => {
12809
- if (fill || !appShell) return;
13133
+ if (fill2 || !appShell) return;
12810
13134
  return appShell.registerChrome("sidebar", position);
12811
- }, [appShell?.registerChrome, fill, position]);
12812
- const usesShellState = appShell !== null && !isControlled && defaultCollapsed === void 0 && !fill && type === "traditional";
13135
+ }, [appShell?.registerChrome, fill2, position]);
13136
+ const usesShellState = appShell !== null && !isControlled && defaultCollapsed === void 0 && !fill2 && type === "traditional";
12813
13137
  const uncontrolledCollapsed = usesShellState ? appShell.sidebarCollapsed : internalCollapsed;
12814
13138
  const collapsed = mini || (isControlled ? controlledCollapsed : uncontrolledCollapsed);
12815
13139
  const handleCollapsedChange = (value) => {
@@ -12828,11 +13152,11 @@ var Sidebar = forwardRef(function Sidebar2({
12828
13152
  effectiveDarkMode = false;
12829
13153
  }
12830
13154
  const isDark = effectiveDarkMode ?? variant === "dark";
12831
- const showCollapse = type === "traditional" && !mini && !fill;
13155
+ const showCollapse = type === "traditional" && !mini && !fill2;
12832
13156
  const isMinimal = type === "minimal";
12833
13157
  const isCompact = type === "compact";
12834
- const isFloating = position === "floating" && !fill;
12835
- const sidebarWidth = fill ? "100%" : isCompact ? "var(--ds-sidebar-compact-width)" : collapsed ? "var(--ds-sidebar-collapsed-width)" : "var(--ds-sidebar-width)";
13158
+ const isFloating = position === "floating" && !fill2;
13159
+ const sidebarWidth = fill2 ? "100%" : isCompact ? "var(--ds-sidebar-compact-width)" : collapsed ? "var(--ds-sidebar-collapsed-width)" : "var(--ds-sidebar-width)";
12836
13160
  const aside = /* @__PURE__ */ jsxs(
12837
13161
  "aside",
12838
13162
  {
@@ -12870,6 +13194,7 @@ var Sidebar = forwardRef(function Sidebar2({
12870
13194
  const collapseButton = /* @__PURE__ */ jsxs(
12871
13195
  "button",
12872
13196
  {
13197
+ type: "button",
12873
13198
  onClick: () => handleCollapsedChange(!collapsed),
12874
13199
  "aria-expanded": !collapsed,
12875
13200
  "aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar",
@@ -13222,6 +13547,7 @@ function SidebarNav({
13222
13547
  /* @__PURE__ */ jsxs(
13223
13548
  "button",
13224
13549
  {
13550
+ type: "button",
13225
13551
  onClick: () => {
13226
13552
  toggleExpand(key);
13227
13553
  onItemClick?.(item, index);
@@ -13356,6 +13682,7 @@ function SidebarNav({
13356
13682
  collapsible && /* @__PURE__ */ jsxs(
13357
13683
  "button",
13358
13684
  {
13685
+ type: "button",
13359
13686
  onClick: () => onCollapsedChange(!collapsed),
13360
13687
  title: collapsed ? "Expand sidebar" : "Collapse sidebar",
13361
13688
  className: cn(
@@ -13422,6 +13749,7 @@ function CompactNavItem({
13422
13749
  /* @__PURE__ */ jsxs(
13423
13750
  "button",
13424
13751
  {
13752
+ type: "button",
13425
13753
  ref: setTriggerEl,
13426
13754
  onMouseEnter: !mobile && hasChildren ? handleMouseEnter : void 0,
13427
13755
  onMouseLeave: !mobile && hasChildren && isOpen ? handleMouseLeave : void 0,
@@ -13484,6 +13812,7 @@ function CompactNavItem({
13484
13812
  mobile && expanded && hasChildren && item.children && /* @__PURE__ */ jsx("div", { className: "w-full flex flex-col gap-ds-1 pb-ds-1", children: item.children.map((child, ci) => /* @__PURE__ */ jsxs(
13485
13813
  "button",
13486
13814
  {
13815
+ type: "button",
13487
13816
  onClick: () => onItemClick?.(child, ci),
13488
13817
  className: cn(
13489
13818
  "flex items-center gap-ds-2 px-ds-3 py-ds-1.5 text-[length:var(--ds-font-size-xs)] rounded-[var(--ds-radius-sm)] transition-colors cursor-pointer w-full",
@@ -13498,7 +13827,519 @@ function CompactNavItem({
13498
13827
  )) })
13499
13828
  ] });
13500
13829
  }
13830
+ var variantStyles4 = {
13831
+ default: "border-[var(--ds-border-default)] bg-[var(--ds-bg-surface)]",
13832
+ success: "border-[var(--ds-status-success)] bg-[var(--ds-status-success-bg)]",
13833
+ warning: "border-[var(--ds-status-warning)] bg-[var(--ds-status-warning-bg)]",
13834
+ error: "border-[var(--ds-status-error)] bg-[var(--ds-status-error-bg)]"
13835
+ };
13836
+ var variantIcons = {
13837
+ default: { name: "info", className: "text-[var(--ds-text-muted)]" },
13838
+ success: { name: "check", className: "text-[var(--ds-status-success)]" },
13839
+ warning: { name: "warning", className: "text-[var(--ds-status-warning)]" },
13840
+ error: { name: "error", className: "text-[var(--ds-status-error)]" }
13841
+ };
13842
+ var TOAST_POSITION_CLASSES = {
13843
+ "top-right": "top-[var(--ds-toast-inset-top)] right-[var(--ds-toast-inset-right)]",
13844
+ "top-left": "top-[var(--ds-toast-inset-top)] left-[var(--ds-toast-inset-left)]",
13845
+ "bottom-right": "bottom-[var(--ds-toast-inset-bottom-right)] right-[var(--ds-toast-inset-right)]",
13846
+ "bottom-left": "bottom-[var(--ds-toast-inset-bottom-left)] left-[var(--ds-toast-inset-left)]",
13847
+ "top-center": "top-[var(--ds-toast-inset-top)] left-1/2 -translate-x-1/2",
13848
+ "bottom-center": "bottom-ds-4 left-1/2 -translate-x-1/2"
13849
+ };
13850
+ var finite = (ms) => typeof ms === "number" && Number.isFinite(ms) && ms > 0;
13851
+ function Toast({ variant = "default", message, visible, onDismiss, duration = 5e3, pauseOnHover = false, action, actionWindow, position = "bottom-right", mode, container, inline = false, paused = false, onActionLapse, onAction, restartKey = 0 }) {
13852
+ const isMobile = useBreakpoint("sm");
13853
+ const isCentered = position === "top-center" || position === "bottom-center";
13854
+ const [hovered, setHovered] = useState(false);
13855
+ const onDismissRef = useLatest(onDismiss);
13856
+ const onLapseRef = useLatest(onActionLapse);
13857
+ const actionUsed = useRef(false);
13858
+ const phase = action ? finite(actionWindow) ? "window" : "none" : finite(duration) ? "plain" : "none";
13859
+ const total = phase === "window" ? actionWindow : phase === "plain" ? duration : 0;
13860
+ const timer = useRef(void 0);
13861
+ const remaining = useRef(total);
13862
+ const startedAt = useRef(0);
13863
+ const isPaused = paused || !inline && pauseOnHover && hovered;
13864
+ const stop = useCallback(() => {
13865
+ if (timer.current) {
13866
+ clearTimeout(timer.current);
13867
+ timer.current = void 0;
13868
+ remaining.current -= Date.now() - startedAt.current;
13869
+ }
13870
+ }, []);
13871
+ const start = useCallback(() => {
13872
+ if (phase === "none" || timer.current) return;
13873
+ startedAt.current = Date.now();
13874
+ const expire = phase === "window" ? () => {
13875
+ timer.current = void 0;
13876
+ if (onLapseRef.current) onLapseRef.current();
13877
+ else onDismissRef.current();
13878
+ } : () => {
13879
+ timer.current = void 0;
13880
+ onDismissRef.current();
13881
+ };
13882
+ timer.current = setTimeout(expire, Math.max(0, remaining.current));
13883
+ }, [phase, onDismissRef, onLapseRef]);
13884
+ useEffect(() => {
13885
+ if (!visible) return;
13886
+ remaining.current = total;
13887
+ if (timer.current) {
13888
+ clearTimeout(timer.current);
13889
+ timer.current = void 0;
13890
+ }
13891
+ if (!isPaused) start();
13892
+ return () => {
13893
+ if (timer.current) {
13894
+ clearTimeout(timer.current);
13895
+ timer.current = void 0;
13896
+ }
13897
+ };
13898
+ }, [visible, phase, total, restartKey]);
13899
+ useEffect(() => {
13900
+ if (!visible) return;
13901
+ if (isPaused) stop();
13902
+ else start();
13903
+ }, [isPaused, visible, start, stop]);
13904
+ useEffect(() => {
13905
+ actionUsed.current = false;
13906
+ }, [restartKey]);
13907
+ if (!visible) return null;
13908
+ const icon = variantIcons[variant];
13909
+ const pressAction = () => {
13910
+ if (!action || actionUsed.current) return;
13911
+ actionUsed.current = true;
13912
+ if (onAction) onAction();
13913
+ else {
13914
+ void action.onClick();
13915
+ onDismissRef.current();
13916
+ }
13917
+ };
13918
+ const card = /* @__PURE__ */ jsxs(
13919
+ "div",
13920
+ {
13921
+ "data-toast": "",
13922
+ "data-variant": variant,
13923
+ role: inline ? void 0 : variant === "error" ? "alert" : "status",
13924
+ "aria-live": inline ? void 0 : variant === "error" ? "assertive" : "polite",
13925
+ "data-mode": inline ? mode : void 0,
13926
+ className: cn(
13927
+ "flex items-center gap-ds-3 px-ds-4 py-ds-3",
13928
+ isCentered ? "min-w-[360px] max-w-[560px]" : "min-w-[280px] max-w-[420px]",
13929
+ "border-[length:var(--ds-border-width)]",
13930
+ GLASS_DROPDOWN,
13931
+ "rounded-[var(--ds-radius-md)]",
13932
+ "text-sm text-[var(--ds-text-primary)]",
13933
+ variantStyles4[variant]
13934
+ ),
13935
+ children: [
13936
+ /* @__PURE__ */ jsx(Icon, { name: icon.name, size: 18, className: cn("shrink-0", icon.className) }),
13937
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: message }),
13938
+ action && /* @__PURE__ */ jsx(
13939
+ "button",
13940
+ {
13941
+ type: "button",
13942
+ onClick: pressAction,
13943
+ className: "shrink-0 text-xs font-semibold text-[var(--ds-accent-primary)] hover:underline cursor-pointer",
13944
+ children: action.label
13945
+ }
13946
+ ),
13947
+ /* @__PURE__ */ jsx(
13948
+ "button",
13949
+ {
13950
+ type: "button",
13951
+ onClick: onDismiss,
13952
+ "aria-label": "Dismiss notification",
13953
+ className: "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer shrink-0",
13954
+ children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 16 })
13955
+ }
13956
+ )
13957
+ ]
13958
+ }
13959
+ );
13960
+ if (inline) return card;
13961
+ return createPortal(
13962
+ /* @__PURE__ */ jsx(
13963
+ "div",
13964
+ {
13965
+ "data-mode": mode,
13966
+ onMouseEnter: pauseOnHover ? () => setHovered(true) : void 0,
13967
+ onMouseLeave: pauseOnHover ? () => setHovered(false) : void 0,
13968
+ className: cn(
13969
+ "fixed z-[var(--ds-z-toast)]",
13970
+ isMobile ? "bottom-ds-4 left-ds-4 right-ds-4" : TOAST_POSITION_CLASSES[position]
13971
+ ),
13972
+ children: card
13973
+ }
13974
+ ),
13975
+ container ?? document.body
13976
+ );
13977
+ }
13978
+ var ToastContext = createContext(null);
13979
+ var ToastLedgerCtx = createContext(null);
13980
+ var BottomNavPresenceCtx = createContext(null);
13981
+ function useToast() {
13982
+ const ctx = useContext(ToastContext);
13983
+ if (!ctx) {
13984
+ throw new Error("useToast must be used within <ToastProvider>: the app shell's (get_app_shell writes it around AppShell). A page outside the shell (sign-in, a public page) shows its messages inline instead: an Alert above the form, and each field's error below the field.");
13985
+ }
13986
+ return ctx;
13987
+ }
13988
+ function useToastOptional() {
13989
+ return useContext(ToastContext);
13990
+ }
13991
+ var toastSeq = 0;
13992
+ var heardSeq = 0;
13993
+ var HEARD_MS = 7e3;
13994
+ var RUN_END_MS = 20;
13995
+ var isThenable2 = (value) => !!value && typeof value.then === "function";
13996
+ function textOf(node) {
13997
+ if (node == null || typeof node === "boolean") return "";
13998
+ if (typeof node === "string" || typeof node === "number") return String(node);
13999
+ if (Array.isArray(node)) return node.map(textOf).join("");
14000
+ if (typeof node === "object" && "props" in node) return textOf(node.props.children);
14001
+ return "";
14002
+ }
14003
+ var mergeable = (e) => !e.action && e.variant !== "error";
14004
+ function fill(entries, slots) {
14005
+ let shown = entries.filter((e) => e.shown).length;
14006
+ if (shown >= slots || shown === entries.length) return entries;
14007
+ const next = [...entries];
14008
+ while (shown < slots) {
14009
+ const urgent = next.findIndex((e) => !e.shown && !mergeable(e));
14010
+ const at = urgent !== -1 ? urgent : next.findIndex((e) => !e.shown);
14011
+ if (at === -1) break;
14012
+ next[at] = { ...next[at], shown: true };
14013
+ shown++;
14014
+ }
14015
+ return next;
14016
+ }
14017
+ function ToastProvider({ children, maxVisible = 3, position = "bottom-right", duration = 5e3, mode, pauseOnHover = true, coalesce = true }) {
14018
+ const slots = Math.max(1, Math.floor(Number.isFinite(maxVisible) ? maxVisible : 1));
14019
+ const [list, setList] = useState([]);
14020
+ const entries = useRef([]);
14021
+ const isMobile = useBreakpoint("sm");
14022
+ const shownDuringRuns = useRef([]);
14023
+ const showSeq = useRef(0);
14024
+ const runs = useRef(/* @__PURE__ */ new Map());
14025
+ const runSeq = useRef(0);
14026
+ const [heard, setHeard] = useState([]);
14027
+ const [hovered, setHovered] = useState(false);
14028
+ const [focusInside, setFocusInside] = useState(false);
14029
+ const paused = pauseOnHover && hovered || focusInside;
14030
+ const stackRef = useRef(null);
14031
+ const returnTo = useRef(null);
14032
+ const pressed = useRef(/* @__PURE__ */ new Set());
14033
+ const pointerAt = useRef(0);
14034
+ const viaKey = useRef(false);
14035
+ const refocus = useRef(false);
14036
+ const pendingHeard = useRef(/* @__PURE__ */ new Map());
14037
+ const heardTimer = useRef(void 0);
14038
+ const [bottomNavs, setBottomNavs] = useState(0);
14039
+ const registerBottomNav = useCallback(() => {
14040
+ setBottomNavs((n) => n + 1);
14041
+ let gone = false;
14042
+ return () => {
14043
+ if (gone) return;
14044
+ gone = true;
14045
+ setBottomNavs((n) => n - 1);
14046
+ };
14047
+ }, []);
14048
+ const commit = useCallback((next) => {
14049
+ entries.current = fill(next, slots);
14050
+ setList(entries.current);
14051
+ }, [slots]);
14052
+ useEffect(() => {
14053
+ let kept = 0;
14054
+ const reslotted = entries.current.map((e) => e.shown && ++kept > slots ? { ...e, shown: false } : e);
14055
+ const next = fill(reslotted, slots);
14056
+ if (next.length === entries.current.length && next.every((e, i) => e === entries.current[i])) return;
14057
+ commit(next);
14058
+ }, [slots, commit]);
14059
+ if (list.every((t) => !t.shown) && (hovered || focusInside)) {
14060
+ setHovered(false);
14061
+ setFocusInside(false);
14062
+ }
14063
+ const clearTimers = useRef(/* @__PURE__ */ new Set());
14064
+ const announce = useCallback((entry) => {
14065
+ pendingHeard.current.set(entry.id, entry);
14066
+ if (heardTimer.current) return;
14067
+ heardTimer.current = setTimeout(() => {
14068
+ heardTimer.current = void 0;
14069
+ const batch = [...pendingHeard.current.values()].map((e) => {
14070
+ const words = textOf(e.message).trim();
14071
+ const sentence = !words || /[.!?…]$/.test(words) ? words : `${words}.`;
14072
+ return { id: `heard-${++heardSeq}`, assertive: e.variant === "error", text: e.action ? `${sentence} Press F8 to reach ${e.action.label}.`.trim() : sentence };
14073
+ }).filter((h) => h.text);
14074
+ pendingHeard.current.clear();
14075
+ if (!batch.length) return;
14076
+ setHeard((list2) => [...list2, ...batch].slice(-5));
14077
+ const ids = new Set(batch.map((h) => h.id));
14078
+ const timer = setTimeout(() => {
14079
+ clearTimers.current.delete(timer);
14080
+ setHeard((list2) => list2.filter((h) => !ids.has(h.id)));
14081
+ }, HEARD_MS);
14082
+ clearTimers.current.add(timer);
14083
+ }, 0);
14084
+ }, []);
14085
+ useEffect(() => () => {
14086
+ if (heardTimer.current) clearTimeout(heardTimer.current);
14087
+ heardTimer.current = void 0;
14088
+ clearTimers.current.forEach(clearTimeout);
14089
+ clearTimers.current.clear();
14090
+ }, []);
14091
+ const leaving = useCallback((id) => {
14092
+ const stack = stackRef.current;
14093
+ const holder = id === void 0 ? stack : [...stack?.querySelectorAll("[data-toast-id]") ?? []].find((n) => n.getAttribute("data-toast-id") === id);
14094
+ if (!holder?.contains(document.activeElement)) return;
14095
+ refocus.current = true;
14096
+ setFocusInside(false);
14097
+ }, []);
14098
+ const remove = useCallback((id) => {
14099
+ leaving(id);
14100
+ commit(entries.current.filter((t) => t.id !== id));
14101
+ }, [commit, leaving]);
14102
+ const dismissAll = useCallback(() => {
14103
+ leaving();
14104
+ commit([]);
14105
+ }, [commit, leaving]);
14106
+ const noteShown = useCallback((id, variant, action, fresh) => {
14107
+ if (runs.current.size) shownDuringRuns.current.push({ seq: ++showSeq.current, at: Date.now(), id, variant, action, fresh });
14108
+ }, []);
14109
+ const update = useCallback((id, message, options = {}) => {
14110
+ const list2 = entries.current;
14111
+ const at = list2.findIndex((t2) => t2.id === id);
14112
+ if (at === -1) return;
14113
+ pressed.current.delete(id);
14114
+ const t = list2[at];
14115
+ const next = [...list2];
14116
+ next[at] = { ...t, message, variant: options.variant ?? t.variant, duration: options.duration ?? t.duration, action: options.action ?? t.action, actionWindow: options.actionWindow ?? t.actionWindow, key: options.key ?? t.key, version: t.version + 1 };
14117
+ commit(next);
14118
+ announce(next[at]);
14119
+ noteShown(id, next[at].variant, !!next[at].action, false);
14120
+ }, [commit, announce, noteShown]);
14121
+ const toast = useCallback((message, options = {}) => {
14122
+ const list2 = entries.current;
14123
+ const base = { message, variant: options.variant ?? "default", duration: options.duration ?? duration, action: options.action, actionWindow: options.actionWindow, key: options.key };
14124
+ if (options.id) {
14125
+ const at = list2.findIndex((t) => t.id === options.id);
14126
+ if (at !== -1) {
14127
+ const next = [...list2];
14128
+ next[at] = { ...list2[at], ...base, version: list2[at].version + 1 };
14129
+ pressed.current.delete(options.id);
14130
+ commit(next);
14131
+ announce(next[at]);
14132
+ noteShown(options.id, base.variant, !!base.action, false);
14133
+ return options.id;
14134
+ }
14135
+ }
14136
+ if (coalesce && base.key && mergeable(base)) {
14137
+ const at = list2.findIndex((t) => t.key === base.key && mergeable(t));
14138
+ if (at !== -1) {
14139
+ const next = [...list2];
14140
+ next[at] = { ...list2[at], ...base, version: list2[at].version + 1 };
14141
+ commit(next);
14142
+ announce(next[at]);
14143
+ noteShown(list2[at].id, base.variant, !!base.action, false);
14144
+ return list2[at].id;
14145
+ }
14146
+ }
14147
+ const id = options.id ?? `toast-${++toastSeq}`;
14148
+ const entry = { id, ...base, shown: false, version: 0 };
14149
+ commit([...list2, entry]);
14150
+ announce(entry);
14151
+ noteShown(id, base.variant, !!base.action, true);
14152
+ return id;
14153
+ }, [duration, coalesce, commit, announce, noteShown]);
14154
+ const api = useMemo(() => ({
14155
+ toast,
14156
+ success: (m, o) => toast(m, { ...o, variant: "success" }),
14157
+ warning: (m, o) => toast(m, { ...o, variant: "warning" }),
14158
+ error: (m, o) => toast(m, { ...o, variant: "error" }),
14159
+ update,
14160
+ dismiss: remove,
14161
+ dismissAll,
14162
+ promise: async (promise, messages, options = {}) => {
14163
+ const id = toast(messages.loading, { ...options, variant: "default", duration: Infinity });
14164
+ try {
14165
+ const value = await promise;
14166
+ update(id, typeof messages.success === "function" ? messages.success(value) : messages.success, { variant: "success", duration });
14167
+ return value;
14168
+ } catch (err) {
14169
+ update(id, typeof messages.error === "function" ? messages.error(err) : messages.error, { variant: "error", duration });
14170
+ throw err;
14171
+ }
14172
+ }
14173
+ }), [toast, update, remove, dismissAll, duration]);
14174
+ const ledger = useMemo(() => ({
14175
+ begin: () => {
14176
+ const run = ++runSeq.current;
14177
+ const overlapped = runs.current.size > 0;
14178
+ if (overlapped) runs.current.forEach((r) => {
14179
+ r.overlapped = true;
14180
+ });
14181
+ runs.current.set(run, { from: showSeq.current, overlapped });
14182
+ return run;
14183
+ },
14184
+ end: (run) => {
14185
+ const watched = runs.current.get(run);
14186
+ runs.current.delete(run);
14187
+ if (!watched) return { problem: false };
14188
+ const during = shownDuringRuns.current.filter((s) => s.seq > watched.from);
14189
+ const oldest = Math.min(...[...runs.current.values()].map((r) => r.from));
14190
+ shownDuringRuns.current = runs.current.size ? shownDuringRuns.current.filter((s) => s.seq > oldest) : [];
14191
+ const problem = during.some((s) => s.variant === "error" || s.variant === "warning");
14192
+ const endedAt = Date.now();
14193
+ const confirmations = during.filter((s) => s.fresh && !s.action && (s.variant === "success" || s.variant === "default"));
14194
+ if (watched.overlapped || confirmations.length !== 1 || endedAt - confirmations[0].at > RUN_END_MS) return { problem };
14195
+ const entry = entries.current.find((t) => t.id === confirmations[0].id);
14196
+ return { problem, own: entry && !entry.action ? { id: entry.id, message: entry.message, variant: entry.variant } : void 0 };
14197
+ },
14198
+ has: (id) => entries.current.some((t) => t.id === id)
14199
+ }), []);
14200
+ const pressAction = useCallback((id) => {
14201
+ const entry = entries.current.find((t) => t.id === id);
14202
+ if (!entry?.action) return;
14203
+ const run = entry.action.onClick;
14204
+ leaving(id);
14205
+ commit(entries.current.map((t) => t.id === id ? { ...t, action: void 0, actionWindow: void 0, version: t.version + 1 } : t));
14206
+ pressed.current.add(id);
14207
+ const done = () => {
14208
+ if (pressed.current.delete(id)) remove(id);
14209
+ };
14210
+ const failed = (err) => {
14211
+ done();
14212
+ console.error(err);
14213
+ };
14214
+ let result;
14215
+ try {
14216
+ result = run();
14217
+ } catch (err) {
14218
+ failed(err);
14219
+ return;
14220
+ }
14221
+ if (isThenable2(result)) result.then(done, failed);
14222
+ else done();
14223
+ }, [commit, leaving, remove]);
14224
+ const lapse = useCallback((id) => {
14225
+ const list2 = entries.current;
14226
+ const at = list2.findIndex((t) => t.id === id);
14227
+ if (at === -1) return;
14228
+ const next = [...list2];
14229
+ next[at] = { ...list2[at], action: void 0, actionWindow: void 0, version: list2[at].version + 1 };
14230
+ commit(next);
14231
+ }, [commit]);
14232
+ useEffect(() => {
14233
+ const onKey = (e) => {
14234
+ if (e.key !== "F8" || e.defaultPrevented) return;
14235
+ const stack = stackRef.current;
14236
+ if (!stack) return;
14237
+ e.preventDefault();
14238
+ const active = document.activeElement;
14239
+ if (!stack.contains(active)) {
14240
+ returnTo.current = active instanceof HTMLElement && active !== document.body ? active : null;
14241
+ viaKey.current = true;
14242
+ }
14243
+ (stack.querySelector("button") ?? stack).focus();
14244
+ viaKey.current = false;
14245
+ };
14246
+ document.addEventListener("keydown", onKey);
14247
+ return () => document.removeEventListener("keydown", onKey);
14248
+ }, []);
14249
+ useEffect(() => {
14250
+ if (!refocus.current) return;
14251
+ refocus.current = false;
14252
+ const back = returnTo.current;
14253
+ const next = stackRef.current?.querySelector("button");
14254
+ const main = document.querySelector("main");
14255
+ const byPointer = Date.now() - pointerAt.current < 1e3;
14256
+ if (back?.isConnected) back.focus();
14257
+ else if (byPointer) {
14258
+ if (stackRef.current?.contains(document.activeElement)) document.activeElement.blur();
14259
+ } else if (next) next.focus();
14260
+ else if (main) {
14261
+ if (!main.hasAttribute("tabindex")) {
14262
+ main.setAttribute("tabindex", "-1");
14263
+ main.addEventListener("blur", () => main.removeAttribute("tabindex"), { once: true });
14264
+ }
14265
+ main.focus();
14266
+ }
14267
+ }, [list]);
14268
+ const shown = list.filter((t) => t.shown);
14269
+ const waiting = list.length - shown.length;
14270
+ const isTop = position.startsWith("top");
14271
+ const hasBottomNav = isMobile && bottomNavs > 0;
14272
+ return /* @__PURE__ */ jsx(ToastContext.Provider, { value: api, children: /* @__PURE__ */ jsxs(ToastLedgerCtx.Provider, { value: ledger, children: [
14273
+ /* @__PURE__ */ jsx(BottomNavPresenceCtx.Provider, { value: registerBottomNav, children }),
14274
+ /* @__PURE__ */ jsx("div", { className: "sr-only", "aria-live": "polite", "data-toast-announcer": "polite", children: heard.filter((h) => !h.assertive).map((h) => /* @__PURE__ */ jsx("p", { children: h.text }, h.id)) }),
14275
+ /* @__PURE__ */ jsx("div", { className: "sr-only", "aria-live": "assertive", "data-toast-announcer": "assertive", children: heard.filter((h) => h.assertive).map((h) => /* @__PURE__ */ jsx("p", { children: h.text }, h.id)) }),
14276
+ shown.length > 0 && createPortal(
14277
+ /* @__PURE__ */ jsxs(
14278
+ "div",
14279
+ {
14280
+ ref: stackRef,
14281
+ role: "region",
14282
+ "aria-label": "Notifications",
14283
+ tabIndex: -1,
14284
+ "data-mode": mode,
14285
+ "data-toast-stack": "",
14286
+ onMouseEnter: () => setHovered(true),
14287
+ onMouseLeave: () => setHovered(false),
14288
+ onPointerDownCapture: () => {
14289
+ pointerAt.current = Date.now();
14290
+ },
14291
+ onFocusCapture: (e) => {
14292
+ setFocusInside(true);
14293
+ const from = e.relatedTarget;
14294
+ if (viaKey.current || from instanceof Node && e.currentTarget.contains(from)) return;
14295
+ returnTo.current = from instanceof HTMLElement ? from : null;
14296
+ },
14297
+ onBlurCapture: (e) => {
14298
+ if (!e.currentTarget.contains(e.relatedTarget)) setFocusInside(false);
14299
+ },
14300
+ className: cn(
14301
+ "fixed z-[var(--ds-z-toast)] flex gap-ds-2 pointer-events-none outline-none",
14302
+ isTop ? "flex-col" : "flex-col-reverse",
14303
+ isMobile ? cn("left-ds-4 right-ds-4", !hasBottomNav && "bottom-ds-4") : TOAST_POSITION_CLASSES[position]
14304
+ ),
14305
+ style: hasBottomNav ? { bottom: "calc(var(--ds-space-12) + var(--ds-space-2) + var(--ds-space-4) + env(safe-area-inset-bottom))" } : void 0,
14306
+ children: [
14307
+ shown.map((t) => /* @__PURE__ */ jsx("div", { "data-toast-id": t.id, className: "pointer-events-auto", children: /* @__PURE__ */ jsx(
14308
+ Toast,
14309
+ {
14310
+ inline: true,
14311
+ visible: true,
14312
+ variant: t.variant,
14313
+ message: t.message,
14314
+ duration: t.duration,
14315
+ action: t.action,
14316
+ actionWindow: t.actionWindow,
14317
+ mode,
14318
+ paused,
14319
+ restartKey: t.version,
14320
+ onAction: () => pressAction(t.id),
14321
+ onActionLapse: () => lapse(t.id),
14322
+ onDismiss: () => remove(t.id)
14323
+ }
14324
+ ) }, t.id)),
14325
+ (waiting > 0 || shown.length >= 2) && /* @__PURE__ */ jsxs("div", { className: "pointer-events-auto flex items-center justify-end gap-ds-2 text-xs", "data-toast-overflow": "", children: [
14326
+ waiting > 0 && /* @__PURE__ */ jsxs("span", { className: "rounded-[var(--ds-radius-full)] bg-[var(--ds-bg-muted)] px-ds-2 py-ds-0.5 font-medium text-[var(--ds-text-secondary)]", children: [
14327
+ "+",
14328
+ waiting,
14329
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: " more waiting" })
14330
+ ] }),
14331
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: dismissAll, className: "font-medium text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:underline cursor-pointer", children: "Clear all" })
14332
+ ] })
14333
+ ]
14334
+ }
14335
+ ),
14336
+ document.body
14337
+ )
14338
+ ] }) });
14339
+ }
13501
14340
  function BottomNav({ items, onItemClick, maxVisible = 5, className, ref }) {
14341
+ const registerWithToasts = useContext(BottomNavPresenceCtx);
14342
+ useEffect(() => registerWithToasts?.(), [registerWithToasts]);
13502
14343
  const needsOverflow = items.length > maxVisible;
13503
14344
  const visibleItems = needsOverflow ? items.slice(0, maxVisible - 1) : items;
13504
14345
  const overflowItems = needsOverflow ? items.slice(maxVisible - 1) : [];
@@ -13533,6 +14374,7 @@ function BottomNav({ items, onItemClick, maxVisible = 5, className, ref }) {
13533
14374
  /* @__PURE__ */ jsxs(
13534
14375
  "button",
13535
14376
  {
14377
+ type: "button",
13536
14378
  onClick: () => setOverflowOpen(!overflowOpen),
13537
14379
  "aria-expanded": overflowOpen,
13538
14380
  "aria-haspopup": "true",
@@ -13560,6 +14402,7 @@ function BottomNav({ items, onItemClick, maxVisible = 5, className, ref }) {
13560
14402
  children: overflowItems.map((item, i) => /* @__PURE__ */ jsxs(
13561
14403
  "button",
13562
14404
  {
14405
+ type: "button",
13563
14406
  role: "menuitem",
13564
14407
  onClick: () => {
13565
14408
  onItemClick?.(item, maxVisible - 1 + i);
@@ -13589,6 +14432,7 @@ function NavButton({ item, onClick }) {
13589
14432
  return /* @__PURE__ */ jsxs(
13590
14433
  "button",
13591
14434
  {
14435
+ type: "button",
13592
14436
  onClick,
13593
14437
  className: cn(
13594
14438
  "flex flex-col items-center justify-center gap-ds-0.5 flex-1 h-full",
@@ -14991,6 +15835,7 @@ function NavMenu({
14991
15835
  /* @__PURE__ */ jsxs(
14992
15836
  "button",
14993
15837
  {
15838
+ type: "button",
14994
15839
  ref: triggerRef,
14995
15840
  onClick: () => setOpen(!open),
14996
15841
  "aria-expanded": open,
@@ -15187,7 +16032,8 @@ var sizeClasses = {
15187
16032
  full: "max-w-[calc(100vw-2rem)]"
15188
16033
  };
15189
16034
  function Dialog({ open, onClose: onCloseProp, title, description, children, footer, variant = "default", size = "md", alert = false, container, onSubmit, dirty, onDiscard }) {
15190
- const guard = useDirtyGuard(dirty, onCloseProp, onDiscard);
16035
+ const { marked, mark } = useOverlayMark(open);
16036
+ const guard = useDirtyGuard(dirty ?? marked, onCloseProp, onDiscard);
15191
16037
  const onClose = guard.requestClose;
15192
16038
  const titleId = useId();
15193
16039
  const descriptionId = useId();
@@ -15255,7 +16101,9 @@ function Dialog({ open, onClose: onCloseProp, title, description, children, foot
15255
16101
  "form",
15256
16102
  {
15257
16103
  onSubmit: (e) => {
16104
+ if (e.target !== e.currentTarget) return;
15258
16105
  e.preventDefault();
16106
+ e.stopPropagation();
15259
16107
  if (variant === "destructive") return;
15260
16108
  const submitter = e.nativeEvent.submitter;
15261
16109
  const implicit = !submitter || submitter === defaultButtonRef.current;
@@ -15284,6 +16132,7 @@ function Dialog({ open, onClose: onCloseProp, title, description, children, foot
15284
16132
  {
15285
16133
  ref: dialogRef,
15286
16134
  role: alert ? "alertdialog" : "dialog",
16135
+ ...{ [OVERLAY_ROOT_ATTR]: "" },
15287
16136
  "aria-modal": "true",
15288
16137
  "data-gs": "dialog",
15289
16138
  "aria-labelledby": titleId,
@@ -15324,7 +16173,7 @@ function Dialog({ open, onClose: onCloseProp, title, description, children, foot
15324
16173
  }
15325
16174
  ),
15326
16175
  description && /* @__PURE__ */ jsx("p", { id: descriptionId, className: "mt-ds-2 text-sm text-[var(--ds-text-muted)]", children: description }),
15327
- children && /* @__PURE__ */ jsx("div", { className: "mt-ds-4", children: /* @__PURE__ */ jsx(OverlayCloseCtx, { value: onClose, children }) })
16176
+ children && /* @__PURE__ */ jsx("div", { className: "mt-ds-4", children: /* @__PURE__ */ jsx(OverlayMarkScope, { mark, children: /* @__PURE__ */ jsx(OverlayCloseCtx, { value: onClose, children }) }) })
15328
16177
  ] })
15329
16178
  ] }) }),
15330
16179
  /* @__PURE__ */ jsx(DiscardConfirm, { open: guard.confirming, onKeep: guard.keep, onDiscard: guard.discard }),
@@ -15350,6 +16199,7 @@ function HamburgerButton({ onClick, open, className: cls }) {
15350
16199
  return /* @__PURE__ */ jsx(
15351
16200
  "button",
15352
16201
  {
16202
+ type: "button",
15353
16203
  onClick,
15354
16204
  "aria-label": open ? "Close navigation menu" : "Open navigation menu",
15355
16205
  "aria-expanded": open ?? void 0,
@@ -15484,7 +16334,7 @@ var Navbar = forwardRef(function Navbar2({
15484
16334
  }
15485
16335
  focus.onExit?.();
15486
16336
  };
15487
- const exitControl = focus ? /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", onClick: requestExit, children: focus.exitLabel ?? (focus.kind === "before-app" ? "Save and log out" : "Exit") }) : null;
16337
+ const exitControl = focus ? /* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: requestExit, children: focus.exitLabel ?? (focus.kind === "before-app" ? "Save and log out" : "Exit") }) : null;
15488
16338
  const shownUserMenu = focus?.kind === "before-app" ? null : userMenu;
15489
16339
  const asking = confirmExit && focus?.kind === "in-app" && !!appShell?.hasUnsavedChanges;
15490
16340
  if (confirmExit && !asking) setConfirmExit(false);
@@ -15500,8 +16350,8 @@ var Navbar = forwardRef(function Navbar2({
15500
16350
  alert: true,
15501
16351
  container: shellEl ?? void 0,
15502
16352
  footer: /* @__PURE__ */ jsxs(Fragment, { children: [
15503
- /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => setConfirmExit(false), children: "Keep editing" }),
15504
- /* @__PURE__ */ jsx(Button, { variant: "destructive", onClick: () => {
16353
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", onClick: () => setConfirmExit(false), children: "Keep editing" }),
16354
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "destructive", onClick: () => {
15505
16355
  setConfirmExit(false);
15506
16356
  focus.onExit?.();
15507
16357
  }, children: "Discard changes" })
@@ -15652,6 +16502,7 @@ function MobileMenuItem({
15652
16502
  return /* @__PURE__ */ jsxs(
15653
16503
  "button",
15654
16504
  {
16505
+ type: "button",
15655
16506
  onClick,
15656
16507
  className: cn(
15657
16508
  "flex items-center gap-ds-3 px-ds-3 py-ds-3 text-sm relative cursor-pointer w-full text-left",
@@ -15676,6 +16527,7 @@ function MobileMenuItem({
15676
16527
  return /* @__PURE__ */ jsxs(
15677
16528
  "button",
15678
16529
  {
16530
+ type: "button",
15679
16531
  onClick,
15680
16532
  className: cn(
15681
16533
  "flex items-center gap-ds-3 px-ds-3 py-ds-2.5 text-sm cursor-pointer w-full text-left",
@@ -15693,6 +16545,7 @@ function MobileMenuItem({
15693
16545
  return /* @__PURE__ */ jsxs(
15694
16546
  "button",
15695
16547
  {
16548
+ type: "button",
15696
16549
  onClick,
15697
16550
  className: cn(
15698
16551
  "flex items-center gap-ds-3 px-ds-3 py-ds-2.5 text-sm rounded-[var(--ds-radius-md)] cursor-pointer w-full text-left",
@@ -15709,6 +16562,7 @@ function MobileMenuItem({
15709
16562
  return /* @__PURE__ */ jsxs(
15710
16563
  "button",
15711
16564
  {
16565
+ type: "button",
15712
16566
  onClick,
15713
16567
  className: cn(
15714
16568
  "flex items-center gap-ds-3 px-ds-3 py-ds-2.5 text-sm rounded-[var(--ds-radius-md)] cursor-pointer w-full text-left",
@@ -15870,6 +16724,7 @@ function CommandPalette({ open, onOpenChange, commands, placeholder = "Type a co
15870
16724
  return /* @__PURE__ */ jsxs(
15871
16725
  "button",
15872
16726
  {
16727
+ type: "button",
15873
16728
  id: `${listboxId}-opt-${myIdx}`,
15874
16729
  role: "option",
15875
16730
  tabIndex: -1,
@@ -16058,6 +16913,7 @@ function ContextMenu({ items, children, className, ref, container }) {
16058
16913
  return /* @__PURE__ */ jsxs(
16059
16914
  "button",
16060
16915
  {
16916
+ type: "button",
16061
16917
  role: "menuitem",
16062
16918
  disabled: item.disabled,
16063
16919
  onClick: () => {
@@ -16168,6 +17024,7 @@ function NotificationCenter({
16168
17024
  /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-3", children: unreadCount > 0 && /* @__PURE__ */ jsx(
16169
17025
  "button",
16170
17026
  {
17027
+ type: "button",
16171
17028
  onClick: onMarkAllRead,
16172
17029
  className: "text-sm text-[var(--ds-accent-primary)] hover:underline cursor-pointer",
16173
17030
  children: "Mark all read"
@@ -16178,6 +17035,7 @@ function NotificationCenter({
16178
17035
  /* @__PURE__ */ jsx(
16179
17036
  "button",
16180
17037
  {
17038
+ type: "button",
16181
17039
  onClick: () => setFilter("all"),
16182
17040
  className: cn(
16183
17041
  "px-ds-3 py-ds-1.5 text-sm rounded-[var(--ds-radius-md)] transition-colors cursor-pointer",
@@ -16189,6 +17047,7 @@ function NotificationCenter({
16189
17047
  /* @__PURE__ */ jsxs(
16190
17048
  "button",
16191
17049
  {
17050
+ type: "button",
16192
17051
  onClick: () => setFilter("unread"),
16193
17052
  className: cn(
16194
17053
  "px-ds-3 py-ds-1.5 text-sm rounded-[var(--ds-radius-md)] transition-colors cursor-pointer",
@@ -16238,6 +17097,7 @@ function NotificationCenter({
16238
17097
  !n.read && onMarkRead && /* @__PURE__ */ jsx(
16239
17098
  "button",
16240
17099
  {
17100
+ type: "button",
16241
17101
  onClick: () => onMarkRead(n.id),
16242
17102
  "aria-label": "Mark as read",
16243
17103
  className: "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer p-ds-0.5",
@@ -16247,6 +17107,7 @@ function NotificationCenter({
16247
17107
  onDismiss && /* @__PURE__ */ jsx(
16248
17108
  "button",
16249
17109
  {
17110
+ type: "button",
16250
17111
  onClick: () => onDismiss(n.id),
16251
17112
  "aria-label": "Dismiss",
16252
17113
  className: "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer p-ds-0.5",
@@ -16265,6 +17126,7 @@ function NotificationCenter({
16265
17126
  /* @__PURE__ */ jsxs(
16266
17127
  "button",
16267
17128
  {
17129
+ type: "button",
16268
17130
  ref: triggerRef,
16269
17131
  onClick: () => setOpen(!open),
16270
17132
  "aria-label": `Notifications${unreadCount ? ` (${unreadCount} unread)` : ""}`,
@@ -16304,6 +17166,7 @@ function NotificationCenter({
16304
17166
  unreadCount > 0 && /* @__PURE__ */ jsx(
16305
17167
  "button",
16306
17168
  {
17169
+ type: "button",
16307
17170
  onClick: onMarkAllRead,
16308
17171
  className: "text-xs text-[var(--ds-accent-primary)] hover:underline cursor-pointer",
16309
17172
  children: "Mark all read"
@@ -16348,6 +17211,7 @@ function NotificationCenter({
16348
17211
  !n.read && onMarkRead && /* @__PURE__ */ jsx(
16349
17212
  "button",
16350
17213
  {
17214
+ type: "button",
16351
17215
  onClick: () => onMarkRead(n.id),
16352
17216
  "aria-label": "Mark as read",
16353
17217
  className: "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer p-ds-0.5",
@@ -16357,6 +17221,7 @@ function NotificationCenter({
16357
17221
  onDismiss && /* @__PURE__ */ jsx(
16358
17222
  "button",
16359
17223
  {
17224
+ type: "button",
16360
17225
  onClick: () => onDismiss(n.id),
16361
17226
  "aria-label": "Dismiss",
16362
17227
  className: "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer p-ds-0.5",
@@ -16520,6 +17385,7 @@ function Tour({ steps, open, onClose, onComplete, container }) {
16520
17385
  /* @__PURE__ */ jsx(
16521
17386
  "button",
16522
17387
  {
17388
+ type: "button",
16523
17389
  onClick: onClose,
16524
17390
  className: "shrink-0 text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer -mt-ds-0.5",
16525
17391
  "aria-label": "Close tour",
@@ -16539,6 +17405,7 @@ function Tour({ steps, open, onClose, onComplete, container }) {
16539
17405
  current > 0 && /* @__PURE__ */ jsx(
16540
17406
  "button",
16541
17407
  {
17408
+ type: "button",
16542
17409
  onClick: handlePrev,
16543
17410
  className: cn(
16544
17411
  "px-ds-3 py-ds-1.5 text-xs font-medium rounded-[var(--ds-radius-sm)]",
@@ -16552,6 +17419,7 @@ function Tour({ steps, open, onClose, onComplete, container }) {
16552
17419
  /* @__PURE__ */ jsx(
16553
17420
  "button",
16554
17421
  {
17422
+ type: "button",
16555
17423
  ref: nextRef,
16556
17424
  onClick: handleNext,
16557
17425
  className: cn(
@@ -16600,223 +17468,6 @@ function getPopoverPosition(rect, placement, padding) {
16600
17468
  top = Math.max(8, Math.min(top, window.innerHeight - popH - 8));
16601
17469
  return { top, left };
16602
17470
  }
16603
- var variantStyles4 = {
16604
- default: "border-[var(--ds-border-default)] bg-[var(--ds-bg-surface)]",
16605
- success: "border-[var(--ds-status-success)] bg-[var(--ds-status-success-bg)]",
16606
- warning: "border-[var(--ds-status-warning)] bg-[var(--ds-status-warning-bg)]",
16607
- error: "border-[var(--ds-status-error)] bg-[var(--ds-status-error-bg)]"
16608
- };
16609
- var variantIcons = {
16610
- default: { name: "info", className: "text-[var(--ds-text-muted)]" },
16611
- success: { name: "check", className: "text-[var(--ds-status-success)]" },
16612
- warning: { name: "warning", className: "text-[var(--ds-status-warning)]" },
16613
- error: { name: "error", className: "text-[var(--ds-status-error)]" }
16614
- };
16615
- var TOAST_POSITION_CLASSES = {
16616
- "top-right": "top-[var(--ds-toast-inset-top)] right-[var(--ds-toast-inset-right)]",
16617
- "top-left": "top-[var(--ds-toast-inset-top)] left-[var(--ds-toast-inset-left)]",
16618
- "bottom-right": "bottom-[var(--ds-toast-inset-bottom-right)] right-[var(--ds-toast-inset-right)]",
16619
- "bottom-left": "bottom-[var(--ds-toast-inset-bottom-left)] left-[var(--ds-toast-inset-left)]",
16620
- "top-center": "top-[var(--ds-toast-inset-top)] left-1/2 -translate-x-1/2",
16621
- "bottom-center": "bottom-ds-4 left-1/2 -translate-x-1/2"
16622
- };
16623
- function Toast({ variant = "default", message, visible, onDismiss, duration = 5e3, pauseOnHover = false, action, position = "bottom-right", mode, container, inline = false }) {
16624
- const isMobile = useBreakpoint("sm");
16625
- const isCentered = position === "top-center" || position === "bottom-center";
16626
- const timerRef = useRef(void 0);
16627
- const startedAtRef = useRef(0);
16628
- const remainingRef = useRef(duration);
16629
- const onDismissRef = useLatest(onDismiss);
16630
- const startTimer = useCallback((ms) => {
16631
- const delay = ms ?? remainingRef.current;
16632
- remainingRef.current = delay;
16633
- startedAtRef.current = Date.now();
16634
- if (!Number.isFinite(delay) || delay <= 0) return;
16635
- timerRef.current = setTimeout(() => onDismissRef.current(), delay);
16636
- }, [onDismissRef]);
16637
- const clearTimer = useCallback(() => {
16638
- if (timerRef.current) {
16639
- clearTimeout(timerRef.current);
16640
- remainingRef.current -= Date.now() - startedAtRef.current;
16641
- }
16642
- }, []);
16643
- useEffect(() => {
16644
- if (!visible) return;
16645
- remainingRef.current = duration;
16646
- startTimer(duration);
16647
- return () => {
16648
- if (timerRef.current) clearTimeout(timerRef.current);
16649
- };
16650
- }, [visible, duration, startTimer]);
16651
- const handleMouseEnter = pauseOnHover ? () => {
16652
- clearTimer();
16653
- } : void 0;
16654
- const handleMouseLeave = pauseOnHover ? () => {
16655
- startTimer();
16656
- } : void 0;
16657
- if (!visible) return null;
16658
- const icon = variantIcons[variant];
16659
- const card = /* @__PURE__ */ jsxs(
16660
- "div",
16661
- {
16662
- role: variant === "error" ? "alert" : "status",
16663
- "aria-live": variant === "error" ? "assertive" : "polite",
16664
- "data-mode": inline ? mode : void 0,
16665
- onMouseEnter: inline ? handleMouseEnter : void 0,
16666
- onMouseLeave: inline ? handleMouseLeave : void 0,
16667
- className: cn(
16668
- "flex items-center gap-ds-3 px-ds-4 py-ds-3",
16669
- isCentered ? "min-w-[360px] max-w-[560px]" : "min-w-[280px] max-w-[420px]",
16670
- "border-[length:var(--ds-border-width)]",
16671
- GLASS_DROPDOWN,
16672
- "rounded-[var(--ds-radius-md)]",
16673
- "text-sm text-[var(--ds-text-primary)]",
16674
- variantStyles4[variant]
16675
- ),
16676
- children: [
16677
- /* @__PURE__ */ jsx(Icon, { name: icon.name, size: 18, className: cn("shrink-0", icon.className) }),
16678
- /* @__PURE__ */ jsx("span", { className: "flex-1", children: message }),
16679
- action && /* @__PURE__ */ jsx(
16680
- "button",
16681
- {
16682
- type: "button",
16683
- onClick: action.onClick,
16684
- className: "shrink-0 text-xs font-semibold text-[var(--ds-accent-primary)] hover:underline cursor-pointer",
16685
- children: action.label
16686
- }
16687
- ),
16688
- /* @__PURE__ */ jsx(
16689
- "button",
16690
- {
16691
- type: "button",
16692
- onClick: onDismiss,
16693
- "aria-label": "Dismiss notification",
16694
- className: "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer shrink-0",
16695
- children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 16 })
16696
- }
16697
- )
16698
- ]
16699
- }
16700
- );
16701
- if (inline) return card;
16702
- return createPortal(
16703
- /* @__PURE__ */ jsx(
16704
- "div",
16705
- {
16706
- "data-mode": mode,
16707
- onMouseEnter: handleMouseEnter,
16708
- onMouseLeave: handleMouseLeave,
16709
- className: cn(
16710
- "fixed z-[var(--ds-z-toast)]",
16711
- isMobile ? "bottom-ds-4 left-ds-4 right-ds-4" : TOAST_POSITION_CLASSES[position]
16712
- ),
16713
- children: card
16714
- }
16715
- ),
16716
- container ?? document.body
16717
- );
16718
- }
16719
- var ToastContext = createContext(null);
16720
- function useToast() {
16721
- const ctx = useContext(ToastContext);
16722
- if (!ctx) {
16723
- throw new Error("useToast must be used within <ToastProvider>: the app shell's (get_app_shell writes it around AppShell). A page outside the shell (sign-in, a public page) shows its messages inline instead: an Alert above the form, and each field's error below the field.");
16724
- }
16725
- return ctx;
16726
- }
16727
- function useToastOptional() {
16728
- return useContext(ToastContext);
16729
- }
16730
- var toastSeq = 0;
16731
- function ToastProvider({ children, maxVisible = 1, position = "bottom-right", duration = 5e3, mode, pauseOnHover = true, coalesce = true }) {
16732
- const [queue, setQueue] = useState([]);
16733
- const isMobile = useBreakpoint("sm");
16734
- const dismiss = useCallback((id) => {
16735
- setQueue((q) => q.filter((t) => t.id !== id));
16736
- }, []);
16737
- const dismissAll = useCallback(() => setQueue([]), []);
16738
- const update = useCallback((id, message, options = {}) => {
16739
- setQueue((q) => q.map((t) => t.id === id ? { ...t, message, variant: options.variant ?? t.variant, duration: options.duration ?? t.duration, action: options.action ?? t.action } : t));
16740
- }, []);
16741
- const toast = useCallback((message, options = {}) => {
16742
- const id = options.id ?? `toast-${++toastSeq}`;
16743
- const entry = {
16744
- id,
16745
- message,
16746
- variant: options.variant ?? "default",
16747
- duration: options.duration ?? duration,
16748
- action: options.action,
16749
- key: options.key ?? options.variant ?? "default"
16750
- };
16751
- const slots = Math.max(1, maxVisible);
16752
- setQueue((q) => {
16753
- if (q.some((t) => t.id === id)) return q.map((t) => t.id === id ? entry : t);
16754
- if (coalesce && q.length >= slots) {
16755
- const waiting = q.findIndex((t, i) => i >= slots && t.key === entry.key);
16756
- if (waiting >= 0) {
16757
- const next = [...q];
16758
- next[waiting] = entry;
16759
- return next;
16760
- }
16761
- }
16762
- return [...q, entry];
16763
- });
16764
- return id;
16765
- }, [duration, maxVisible, coalesce]);
16766
- const api = useMemo(() => ({
16767
- toast,
16768
- success: (m, o) => toast(m, { ...o, variant: "success" }),
16769
- warning: (m, o) => toast(m, { ...o, variant: "warning" }),
16770
- error: (m, o) => toast(m, { ...o, variant: "error" }),
16771
- update,
16772
- dismiss,
16773
- dismissAll,
16774
- promise: async (promise, messages, options = {}) => {
16775
- const id = toast(messages.loading, { ...options, variant: "default", duration: Infinity });
16776
- try {
16777
- const value = await promise;
16778
- update(id, typeof messages.success === "function" ? messages.success(value) : messages.success, { variant: "success", duration });
16779
- return value;
16780
- } catch (err) {
16781
- update(id, typeof messages.error === "function" ? messages.error(err) : messages.error, { variant: "error", duration });
16782
- throw err;
16783
- }
16784
- }
16785
- }), [toast, update, dismiss, dismissAll, duration]);
16786
- const visible = queue.slice(0, Math.max(1, maxVisible));
16787
- const isTop = position.startsWith("top");
16788
- return /* @__PURE__ */ jsxs(ToastContext.Provider, { value: api, children: [
16789
- children,
16790
- visible.length > 0 && createPortal(
16791
- /* @__PURE__ */ jsx(
16792
- "div",
16793
- {
16794
- "data-mode": mode,
16795
- className: cn(
16796
- "fixed z-[var(--ds-z-toast)] flex gap-ds-2 pointer-events-none",
16797
- isTop ? "flex-col" : "flex-col-reverse",
16798
- isMobile ? "bottom-ds-4 left-ds-4 right-ds-4" : TOAST_POSITION_CLASSES[position]
16799
- ),
16800
- children: visible.map((t) => /* @__PURE__ */ jsx("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx(
16801
- Toast,
16802
- {
16803
- inline: true,
16804
- visible: true,
16805
- variant: t.variant,
16806
- message: t.message,
16807
- duration: t.duration,
16808
- pauseOnHover,
16809
- action: t.action,
16810
- mode,
16811
- onDismiss: () => dismiss(t.id)
16812
- }
16813
- ) }, t.id))
16814
- }
16815
- ),
16816
- document.body
16817
- )
16818
- ] });
16819
- }
16820
17471
  function firstKey(item) {
16821
17472
  if (item !== null && typeof item === "object") return "id" in item ? item.id : void 0;
16822
17473
  return item;
@@ -16879,9 +17530,10 @@ function usePagination(list, options = {}) {
16879
17530
  };
16880
17531
  }
16881
17532
  function useDestructiveAction(options) {
16882
- const { action, title, description, confirmLabel, typeToConfirm, onConfirm, onUndo, undoMessage, severity } = options;
17533
+ const { action, title, description, confirmLabel, typeToConfirm, onConfirm, onUndo, doneMessage, undoMessage, restoredMessage, severity } = options;
16883
17534
  const preferences = useShellPreferences();
16884
17535
  const toast = useToastOptional();
17536
+ const ledger = useContext(ToastLedgerCtx);
16885
17537
  const [open, setOpen] = useState(false);
16886
17538
  const [typed, setTyped] = useState("");
16887
17539
  const [busy, setBusy] = useState(false);
@@ -16898,28 +17550,66 @@ function useDestructiveAction(options) {
16898
17550
  setTyped("");
16899
17551
  };
16900
17552
  const failureMessage = (err) => err instanceof Error && err.message ? err.message : `${title.replace(/\?$/, "")} failed. Try again.`;
17553
+ const done = doneMessage ?? undoMessage ?? pastTenseOf(label);
17554
+ const watch = async (step) => {
17555
+ const run2 = ledger?.begin();
17556
+ try {
17557
+ await step();
17558
+ } catch (err) {
17559
+ if (run2 !== void 0) ledger.end(run2);
17560
+ throw err;
17561
+ }
17562
+ return run2 !== void 0 ? ledger.end(run2) : { problem: false };
17563
+ };
16901
17564
  const confirm = async () => {
16902
17565
  setBusy(true);
16903
17566
  try {
16904
- await onConfirm();
17567
+ const seen = await watch(onConfirm);
16905
17568
  close();
17569
+ if (toast && !seen.problem && !seen.own) toast.success(done);
16906
17570
  } catch (err) {
16907
17571
  toast?.error(failureMessage(err));
16908
17572
  } finally {
16909
17573
  setBusy(false);
16910
17574
  }
16911
17575
  };
17576
+ const undo = async (id) => {
17577
+ const here = () => !!toast && (ledger?.has(id) ?? false);
17578
+ try {
17579
+ const seen = await watch(onUndo);
17580
+ if (seen.problem) {
17581
+ if (here()) toast.dismiss(id);
17582
+ } else if (seen.own) {
17583
+ if (here()) {
17584
+ toast.update(id, seen.own.message, { variant: seen.own.variant });
17585
+ toast.dismiss(seen.own.id);
17586
+ }
17587
+ } else if (here()) toast.update(id, restoredMessage ?? "Restored", { variant: "success" });
17588
+ else toast?.success(restoredMessage ?? "Restored");
17589
+ } catch (err) {
17590
+ const message = err instanceof Error && err.message ? err.message : "Undo didn't work. Try again.";
17591
+ if (here()) toast.update(id, message, { variant: "error" });
17592
+ else toast?.error(message);
17593
+ }
17594
+ };
16912
17595
  const run = () => {
16913
17596
  if (undoable) {
16914
17597
  void (async () => {
17598
+ let seen;
16915
17599
  try {
16916
- await onConfirm();
17600
+ seen = await watch(onConfirm);
16917
17601
  } catch (err) {
16918
17602
  toast.error(failureMessage(err));
16919
17603
  return;
16920
17604
  }
17605
+ if (seen.problem) return;
16921
17606
  const seconds = preferences.undoWindowSeconds ?? 5;
16922
- toast.success(undoMessage ?? `${title.replace(/\?$/, "")} done`, { duration: seconds * 1e3, action: { label: "Undo", onClick: onUndo } });
17607
+ let id = "";
17608
+ const undoAction = { label: "Undo", onClick: () => undo(id) };
17609
+ if (seen.own) {
17610
+ id = seen.own.id;
17611
+ toast.update(id, seen.own.message, { action: undoAction, actionWindow: seconds * 1e3 });
17612
+ } else id = toast.success(done, { action: undoAction, actionWindow: seconds * 1e3 });
16923
17613
  })();
16924
17614
  return;
16925
17615
  }
@@ -16935,8 +17625,8 @@ function useDestructiveAction(options) {
16935
17625
  description,
16936
17626
  variant: "destructive",
16937
17627
  footer: /* @__PURE__ */ jsxs(Fragment, { children: [
16938
- /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: close, disabled: busy, children: "Cancel" }),
16939
- /* @__PURE__ */ jsx(Button, { variant: "destructive", onClick: () => void confirm(), disabled: !matches2 || busy, loading: busy, children: label })
17628
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", onClick: close, disabled: busy, children: "Cancel" }),
17629
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "destructive", onClick: () => void confirm(), disabled: !matches2 || busy, loading: busy, children: label })
16940
17630
  ] }),
16941
17631
  children: typedRequired && /* @__PURE__ */ jsx(Field, { label: /* @__PURE__ */ jsxs(Fragment, { children: [
16942
17632
  "Type ",
@@ -16947,6 +17637,61 @@ function useDestructiveAction(options) {
16947
17637
  ) : null;
16948
17638
  return { run, element, open };
16949
17639
  }
17640
+ var PAST_TENSE = {
17641
+ delete: "Deleted",
17642
+ remove: "Removed",
17643
+ archive: "Archived",
17644
+ leave: "Left",
17645
+ revoke: "Revoked",
17646
+ disconnect: "Disconnected",
17647
+ cancel: "Cancelled",
17648
+ reset: "Reset",
17649
+ clear: "Cleared",
17650
+ discard: "Discarded",
17651
+ unpublish: "Unpublished",
17652
+ deactivate: "Deactivated",
17653
+ disable: "Disabled",
17654
+ uninstall: "Uninstalled",
17655
+ unsubscribe: "Unsubscribed",
17656
+ block: "Blocked",
17657
+ ban: "Banned",
17658
+ suspend: "Suspended",
17659
+ close: "Closed",
17660
+ end: "Ended",
17661
+ stop: "Stopped",
17662
+ "sign out": "Signed out",
17663
+ "log out": "Logged out",
17664
+ transfer: "Transferred",
17665
+ merge: "Merged",
17666
+ overwrite: "Overwritten",
17667
+ replace: "Replaced",
17668
+ erase: "Erased",
17669
+ purge: "Purged",
17670
+ wipe: "Wiped",
17671
+ drop: "Dropped",
17672
+ detach: "Detached",
17673
+ unlink: "Unlinked",
17674
+ expire: "Expired",
17675
+ reject: "Rejected",
17676
+ decline: "Declined"
17677
+ };
17678
+ var warned3 = /* @__PURE__ */ new Set();
17679
+ function pastTenseOf(label) {
17680
+ const words = label.trim().toLowerCase().split(/\s+/);
17681
+ const found = PAST_TENSE[words.slice(0, 2).join(" ")] ?? PAST_TENSE[words[0] ?? ""];
17682
+ if (found) return found;
17683
+ let dev = false;
17684
+ try {
17685
+ dev = process.env.NODE_ENV !== "production";
17686
+ } catch {
17687
+ dev = false;
17688
+ }
17689
+ if (dev && !warned3.has(label)) {
17690
+ warned3.add(label);
17691
+ console.warn(`useDestructiveAction: "${label}" isn't a verb the hook knows; pass doneMessage so the confirmation says what happened.`);
17692
+ }
17693
+ return "Done";
17694
+ }
16950
17695
  var SectionHeading = forwardRef(function SectionHeading2({
16951
17696
  title,
16952
17697
  description,
@@ -17087,6 +17832,6 @@ var TextOverflow = forwardRef(function TextOverflow2({
17087
17832
  return /* @__PURE__ */ jsx(Tooltip, { content: resolvedTooltipContent, children: content });
17088
17833
  });
17089
17834
 
17090
- export { Accordion, ActionPanel, ActionPanelGroup, Alert, AsidePanel, Avatar, AvatarGroup, BottomNav, Breadcrumbs, BulkActionBar, Calendar, Card, CardContent, CardDescription, CardFooter, CardGrid, CardHeader, CardTitle, Carousel, ChatMessageList, ChatPanel, Checkbox, CheckboxGroup, CheckboxGroupItem, Collapsible, ColorPicker, Combobox, CommandPalette, ContextMenu, CornerPanel, DEFAULT_DATE_RANGE_PRESETS, DataList, DatePicker, DateRangePicker, DescriptionList, Dialog, Dock, Dropdown, DropdownMenu, EmptyState, EntityCell, Field, FileUpload, FormWrapper, Gantt, HoverCard, InlineFieldError, Input, Kbd, Label, Launcher, MentionInput, MobileHeaderMenu, NavItem, NavMenu, Navbar, NotificationCenter, NumberInput, PageContainer, PageHeader, Pagination, PinInput, Popover, Progress, RadioGroup, Rating, ScrollArea, SectionHeading, SegmentedControl, SettingsLayout, Sidebar, SidebarNav, SliderRange, StackedList, StatsCard, StatusDot, Stepper, TIME_ZOOMS, Table, TableToolbar, Tabs, Tag, TagInput, TextOverflow, Textarea, TimeBar, TimePicker, TimeRow, TimeScaleHeader, TimeViewport, Timeline, Toast, ToastProvider, Toggle, Tour, TransferList, TreeView, dateOfDay, dayOf, endDayOf, fitLabel, fitLabels, formatDateRange, timeExtent, useContentWidth, useDestructiveAction, useFittedLabels, useFormContext, usePagination, useSidebar, useTextMeasure, useTimeScale, useTimeViewport, useTimeViewportLayout, useToast, useToastOptional };
17835
+ export { Accordion, ActionPanel, ActionPanelGroup, AddAnother, Alert, AsidePanel, Avatar, AvatarGroup, BottomNav, Breadcrumbs, BulkActionBar, Calendar, Card, CardContent, CardDescription, CardFooter, CardGrid, CardHeader, CardTitle, Carousel, ChatMessageList, ChatPanel, Checkbox, CheckboxGroup, CheckboxGroupItem, Collapsible, ColorPicker, Combobox, CommandPalette, ContextMenu, CornerPanel, DEFAULT_DATE_RANGE_PRESETS, DataList, DatePicker, DateRangePicker, DescriptionList, Dialog, Dock, Dropdown, DropdownMenu, EmptyState, EntityCell, Field, FileUpload, FormWrapper, Gantt, HoverCard, InlineFieldError, Input, Kbd, Label, Launcher, MentionInput, MobileHeaderMenu, NavItem, NavMenu, Navbar, NotificationCenter, NumberInput, PageContainer, PageHeader, Pagination, PinInput, Popover, Progress, RadioGroup, Rating, ScrollArea, SectionHeading, SegmentedControl, SettingsLayout, Sidebar, SidebarNav, SliderRange, StackedList, StatsCard, StatusDot, Stepper, TIME_ZOOMS, Table, TableToolbar, Tabs, Tag, TagInput, TextOverflow, Textarea, TimeBar, TimePicker, TimeRow, TimeScaleHeader, TimeViewport, Timeline, Toast, ToastProvider, Toggle, Tour, TransferList, TreeView, dateOfDay, dayOf, endDayOf, fitLabel, fitLabels, formatDateRange, timeExtent, useContentWidth, useDestructiveAction, useFittedLabels, usePagination, useSidebar, useTextMeasure, useTimeScale, useTimeViewport, useTimeViewportLayout, useToast, useToastOptional };
17091
17836
  //# sourceMappingURL=index.js.map
17092
17837
  //# sourceMappingURL=index.js.map