gridsmith-ui 0.19.0 → 0.20.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,7 +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, useOverlayMark, useDirtyGuard, OverlayMarkCtx, OverlayCloseCtx, DiscardConfirm, Spinner, DrawerFooterCtx, useCompact, useElementWidth, Sheet, useCloseSettle, Drawer, useRememberedWidth, pxOf, ResizeEdge, defaultMaxWidth } from './chunk-6MXHGH3D.js';
2
+ export { AppShell, Button, ButtonSizeContext, ControlSizeContext, Drawer, Sheet, SkipLink, Spinner, useAppShell, useUnsavedChanges } from './chunk-6MXHGH3D.js';
3
3
  import { Skeleton, TITLE_TEXT } from './chunk-NCIZQYXQ.js';
4
4
  export { Skeleton } from './chunk-NCIZQYXQ.js';
5
+ import { useReportedChange, useOptionalFormContext, FormChangeCtx, FormCtx } from './chunk-XVB55M3N.js';
6
+ export { useFormContext } from './chunk-XVB55M3N.js';
5
7
  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
8
  export { useGridKeyboard } from './chunk-JXANCXK6.js';
7
9
  import { Link } from './chunk-EQDR57AM.js';
@@ -115,6 +117,7 @@ function DropdownMenu({ trigger, items, align = "end", className, container }) {
115
117
  return /* @__PURE__ */ jsxs(
116
118
  "button",
117
119
  {
120
+ type: "button",
118
121
  "data-index": i,
119
122
  role: "menuitem",
120
123
  disabled: item.disabled,
@@ -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,11 @@ function BulkActionBar({ selectedCount, actions, onClearSelection, className })
204
208
  }
205
209
  );
206
210
  }
211
+ var valueOf = (e) => {
212
+ const t = e.target;
213
+ if (!t) return void 0;
214
+ return t instanceof HTMLInputElement && (t.type === "checkbox" || t.type === "radio") ? t.checked : t.value;
215
+ };
207
216
  var INPUT_HEIGHT = {
208
217
  sm: "h-[var(--ds-control-height-sm)]",
209
218
  md: "h-[var(--ds-control-height-md)]",
@@ -217,6 +226,7 @@ var INPUT_TEXT = {
217
226
  function Input({ className, size: sizeProp, error = false, prefix, suffix, ...props }) {
218
227
  const contextSize = useContext(ControlSizeContext);
219
228
  const size = sizeProp ?? contextSize ?? "md";
229
+ const onChange = useReportedChange(props.onChange, props.value, valueOf);
220
230
  const borderColor = error ? "border-[var(--ds-status-error)]" : "border-[var(--ds-input-border)]";
221
231
  const hoverBorder = !error && !props.disabled && !props.readOnly ? "hover:border-[var(--ds-input-border-hover)]" : "";
222
232
  if (prefix || suffix) {
@@ -252,7 +262,8 @@ function Input({ className, size: sizeProp, error = false, prefix, suffix, ...pr
252
262
  "disabled:cursor-not-allowed",
253
263
  "read-only:text-[var(--ds-text-secondary)] read-only:cursor-default"
254
264
  ),
255
- ...props
265
+ ...props,
266
+ onChange: props.onChange || props.value === void 0 ? onChange : void 0
256
267
  }
257
268
  ),
258
269
  suffix && /* @__PURE__ */ jsx("span", { className: "shrink-0 pr-ds-3 text-[var(--ds-text-muted)] flex items-center", children: suffix })
@@ -281,11 +292,13 @@ function Input({ className, size: sizeProp, error = false, prefix, suffix, ...pr
281
292
  "transition-colors duration-[var(--ds-transition-speed)]",
282
293
  className
283
294
  ),
284
- ...props
295
+ ...props,
296
+ onChange: props.onChange || props.value === void 0 ? onChange : void 0
285
297
  }
286
298
  );
287
299
  }
288
300
  function Textarea({ className, autoResize, maxRows, error = false, ref, ...props }) {
301
+ const onChange = useReportedChange(props.onChange, props.value, valueOf);
289
302
  const internalRef = useRef(null);
290
303
  const textareaRef = ref ?? internalRef;
291
304
  const resize = useCallback(() => {
@@ -324,7 +337,8 @@ function Textarea({ className, autoResize, maxRows, error = false, ref, ...props
324
337
  className
325
338
  ),
326
339
  onInput: autoResize ? resize : void 0,
327
- ...props
340
+ ...props,
341
+ onChange: props.onChange || props.value === void 0 ? onChange : void 0
328
342
  }
329
343
  );
330
344
  }
@@ -347,18 +361,20 @@ var TOGGLE_SIZES = {
347
361
  md: { w: 44, h: 24, thumb: 16 }
348
362
  };
349
363
  function Toggle({ checked, onChange, label, disabled, error = false, layout = "full", size = "md", "aria-labelledby": ariaLabelledBy, ...rest }) {
364
+ const change = useReportedChange(onChange, checked);
350
365
  const dims = TOGGLE_SIZES[size];
351
366
  const travel = dims.w - dims.thumb - 3;
352
367
  const switchButton = /* @__PURE__ */ jsx(
353
368
  "button",
354
369
  {
370
+ type: "button",
355
371
  role: "switch",
356
372
  "aria-checked": checked,
357
373
  "aria-label": !label && !ariaLabelledBy ? "Toggle" : void 0,
358
374
  "aria-labelledby": ariaLabelledBy,
359
375
  "aria-invalid": error || void 0,
360
376
  disabled,
361
- onClick: () => !disabled && onChange(!checked),
377
+ onClick: () => !disabled && change(!checked),
362
378
  ...rest,
363
379
  className: cn(
364
380
  "relative inline-flex shrink-0 items-center overflow-hidden",
@@ -407,15 +423,6 @@ function Toggle({ checked, onChange, label, disabled, error = false, layout = "f
407
423
  }
408
424
  );
409
425
  }
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
426
  function InlineFieldError({ message, id, className }) {
420
427
  return /* @__PURE__ */ jsxs(
421
428
  "p",
@@ -454,7 +461,9 @@ function useFieldControl(own, options = {}) {
454
461
  requiredNote: noteId ? /* @__PURE__ */ jsx("span", { id: noteId, hidden: true, children: "Required" }) : null
455
462
  };
456
463
  }
457
- function Field({ label, children, hint, error, required, labelAction, id: idProp, validate, className }) {
464
+ var AddAnotherRowCtx = createContext(false);
465
+ function Field({ label, children, hint, error, required, labelAction, hideLabel = false, id: idProp, validate, className }) {
466
+ const inGroupRow = useContext(AddAnotherRowCtx);
458
467
  const generated = useId();
459
468
  const id = idProp ?? `field-${generated}`;
460
469
  const hintId = `${id}-hint`;
@@ -533,6 +542,10 @@ function Field({ label, children, hint, error, required, labelAction, id: idProp
533
542
  } } : {};
534
543
  control = cloneElement(child, isNative ? ariaOnly : { ...ariaOnly, ...errorFlag ? { error: true } : {}, ...onChange });
535
544
  }
545
+ const labelNode = /* @__PURE__ */ jsxs(Label, { id: labelId, htmlFor: id, className: cn(hideLabel && "sr-only", hideLabel && inGroupRow && "@max-[30rem]:not-sr-only"), children: [
546
+ label,
547
+ required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ml-ds-0.5 text-[var(--ds-status-error)]", children: "*" })
548
+ ] });
536
549
  return (
537
550
  // In a settings panel a field keeps the narrow reading width however wide the page is (--ds-form-max, Sprint 25.9 A4)
538
551
  /* @__PURE__ */ jsxs(
@@ -541,11 +554,8 @@ function Field({ label, children, hint, error, required, labelAction, id: idProp
541
554
  className: cn("flex flex-col gap-ds-1.5 max-w-[var(--ds-form-max,none)]", className),
542
555
  ...validate ? { onFocusCapture, onBlurCapture, onInputCapture: recheck } : {},
543
556
  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
- ] }),
557
+ hideLabel && !labelAction ? labelNode : /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-ds-2", children: [
558
+ labelNode,
549
559
  labelAction
550
560
  ] }),
551
561
  /* @__PURE__ */ jsx(FieldContext.Provider, { value: fieldContext, children: control }),
@@ -564,7 +574,7 @@ var DROPDOWN_HEIGHT = {
564
574
  function Dropdown({
565
575
  options,
566
576
  value,
567
- onChange,
577
+ onChange: onChangeProp,
568
578
  label,
569
579
  disabled,
570
580
  placeholder = "Select...",
@@ -579,6 +589,7 @@ function Dropdown({
579
589
  "aria-label": ariaLabel,
580
590
  ...wiring
581
591
  }) {
592
+ const onChange = useReportedChange(onChangeProp, value);
582
593
  const contextSize = useContext(ControlSizeContext);
583
594
  const generatedId = useId();
584
595
  const control = useFieldControl({ ...wiring, error: error || void 0 }, { requiredInDescription: true });
@@ -865,6 +876,7 @@ function Dropdown({
865
876
  /* @__PURE__ */ jsx(
866
877
  "button",
867
878
  {
879
+ type: "button",
868
880
  "data-chip-remove": "",
869
881
  onClick: () => {
870
882
  pendingChipFocus.current = { index: i, value: v };
@@ -932,7 +944,8 @@ var TEXT_SIZE = {
932
944
  md: "text-sm",
933
945
  lg: "text-base"
934
946
  };
935
- function Combobox({ value, onChange, options, placeholder = "Search\u2026", disabled, loading = false, className, ref, container, error = false, size: sizeProp, ...wiring }) {
947
+ function Combobox({ value, onChange: onChangeProp, options, placeholder = "Search\u2026", disabled, loading = false, className, ref, container, error = false, size: sizeProp, ...wiring }) {
948
+ const onChange = useReportedChange(onChangeProp, value);
936
949
  const contextSize = useContext(ControlSizeContext);
937
950
  const control = useFieldControl({ ...wiring, error: error || void 0 });
938
951
  const size = sizeProp ?? contextSize ?? "md";
@@ -1130,6 +1143,7 @@ function Combobox({ value, onChange, options, placeholder = "Search\u2026", disa
1130
1143
  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
1144
  "button",
1132
1145
  {
1146
+ type: "button",
1133
1147
  id: `${listboxId}-opt-${i}`,
1134
1148
  role: "option",
1135
1149
  tabIndex: -1,
@@ -1161,7 +1175,8 @@ var CHECKBOX_SIZES = {
1161
1175
  sm: { box: "h-[16px] w-[16px]", glyph: 10, label: "text-xs" },
1162
1176
  md: { box: "h-[18px] w-[18px]", glyph: 12, label: "text-sm" }
1163
1177
  };
1164
- function Checkbox({ checked, onChange, label, size = "md", disabled = false, indeterminate = false, className, ref }) {
1178
+ function Checkbox({ checked, onChange: onChangeProp, label, size = "md", disabled = false, indeterminate = false, className, ref }) {
1179
+ const onChange = useReportedChange(onChangeProp, checked);
1165
1180
  const internalRef = useRef(null);
1166
1181
  const dims = CHECKBOX_SIZES[size];
1167
1182
  const mergedRef = useCallback(
@@ -1253,7 +1268,8 @@ function CheckboxGroupItem({ value, label, disabled, groupValue, onGroupChange }
1253
1268
  }
1254
1269
  );
1255
1270
  }
1256
- function RadioGroup({ value, onChange, options, orientation = "vertical", className, label }) {
1271
+ function RadioGroup({ value, onChange: onChangeProp, options, orientation = "vertical", className, label }) {
1272
+ const onChange = useReportedChange(onChangeProp, value);
1257
1273
  const groupRef = useRef(null);
1258
1274
  const groupId = useId();
1259
1275
  const enabledOptions = options.filter((o) => !o.disabled);
@@ -1478,7 +1494,8 @@ function parseTypedDate(text, dayFirst = localeDayFirst()) {
1478
1494
  }
1479
1495
  return null;
1480
1496
  }
1481
- function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, className, ref, container, error = false, label, id, size: sizeProp, min, max, range: range2, ...wiring }) {
1497
+ function DatePicker({ value, onChange: onChangeProp, placeholder = "Pick a date", disabled, className, ref, container, error = false, label, id, size: sizeProp, min, max, range: range2, ...wiring }) {
1498
+ const onChange = useReportedChange(onChangeProp, value);
1482
1499
  const contextSize = useContext(ControlSizeContext);
1483
1500
  const size = sizeProp ?? contextSize ?? "md";
1484
1501
  const [open, setOpen] = useState(false);
@@ -2083,7 +2100,7 @@ var DEFAULT_DATE_RANGE_PRESETS = [
2083
2100
  ];
2084
2101
  function DateRangePicker({
2085
2102
  value,
2086
- onChange,
2103
+ onChange: onChangeProp,
2087
2104
  placeholder = "Pick a date range",
2088
2105
  disabled,
2089
2106
  error = false,
@@ -2099,6 +2116,7 @@ function DateRangePicker({
2099
2116
  size: sizeProp,
2100
2117
  ...wiring
2101
2118
  }) {
2119
+ const onChange = useReportedChange(onChangeProp, value);
2102
2120
  const contextSize = useContext(ControlSizeContext);
2103
2121
  const size = sizeProp ?? contextSize ?? "md";
2104
2122
  const [open, setOpen] = useState(false);
@@ -2462,22 +2480,27 @@ function TruncFileName({ children, className }) {
2462
2480
  if (!isTruncated) return el;
2463
2481
  return /* @__PURE__ */ jsx(Tooltip, { content: children, children: el });
2464
2482
  }
2483
+ var PARALLEL = 3;
2465
2484
  function formatSize(bytes) {
2466
2485
  if (bytes < 1024) return `${bytes} B`;
2467
2486
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
2468
2487
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
2469
2488
  }
2470
- function FileUpload({ onFiles, accept, multiple = true, maxSize, className, error = false, label, id }) {
2471
- const [files, setFiles] = useState([]);
2489
+ var running = (item) => item.status === "queued" || item.status === "uploading";
2490
+ var failureOf = (err) => err instanceof Error && err.message ? err.message : "The upload didn't work.";
2491
+ function FileUpload({ onFiles, files, upload, onCancel, onRetry, onRemove, accept, multiple = true, maxSize, className, error = false, label, id }) {
2492
+ const controlled = files !== void 0;
2493
+ const [own, setOwn] = useState([]);
2494
+ const shown = controlled ? files : own;
2472
2495
  const [dragOver, setDragOver] = useState(false);
2473
2496
  const [rejected, setRejected] = useState(null);
2474
2497
  const inputRef = useRef(null);
2498
+ const listRef = useRef(null);
2475
2499
  const nextEntryId = useRef(0);
2476
- const timers = useRef(/* @__PURE__ */ new Map());
2477
- useEffect(() => {
2478
- const running = timers.current;
2479
- return () => running.forEach(clearInterval);
2480
- }, []);
2500
+ const controllers = useRef(/* @__PURE__ */ new Map());
2501
+ const uploadRef = useLatest(upload);
2502
+ const overlayMark = useContext(OverlayMarkCtx);
2503
+ const reportChange = useContext(FormChangeCtx);
2481
2504
  const field = useField();
2482
2505
  const ownId = useId();
2483
2506
  const inputId = id ?? `${ownId}-input`;
@@ -2487,37 +2510,100 @@ function FileUpload({ onFiles, accept, multiple = true, maxSize, className, erro
2487
2510
  const constraints = fileConstraints(accept, maxSize, multiple);
2488
2511
  const invalid = error || rejected != null;
2489
2512
  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);
2513
+ const patch = (itemId, change) => setOwn((prev) => prev.map((i) => i.id === itemId ? { ...i, ...change } : i));
2514
+ const stop = (itemId) => {
2515
+ controllers.current.get(itemId)?.abort();
2516
+ controllers.current.delete(itemId);
2517
+ };
2518
+ const start = (item) => {
2519
+ const run = uploadRef.current;
2520
+ if (!run || !item.file) return;
2521
+ const controller = new AbortController();
2522
+ controllers.current.set(item.id, controller);
2523
+ patch(item.id, { status: "uploading", progress: 0, error: void 0 });
2524
+ const settle = (change) => {
2525
+ if (controller.signal.aborted) return;
2526
+ controllers.current.delete(item.id);
2527
+ patch(item.id, change);
2528
+ };
2529
+ let attempt;
2530
+ try {
2531
+ attempt = Promise.resolve(run(item.file, {
2532
+ signal: controller.signal,
2533
+ onProgress: (percent) => {
2534
+ if (!controller.signal.aborted) patch(item.id, { progress: Math.max(0, Math.min(100, percent)) });
2535
+ }
2536
+ }));
2537
+ } catch (err) {
2538
+ attempt = Promise.reject(err);
2539
+ }
2540
+ attempt.then(() => settle({ status: "done", progress: 100 }), (err) => settle({ status: "error", error: failureOf(err) }));
2493
2541
  };
2542
+ useEffect(() => {
2543
+ if (controlled || !upload) return;
2544
+ let active = own.filter((i) => i.status === "uploading" && controllers.current.has(i.id)).length;
2545
+ for (const item of own) {
2546
+ if (active >= PARALLEL) break;
2547
+ if (item.status === "queued" || item.status === "uploading" && !controllers.current.has(item.id)) {
2548
+ start(item);
2549
+ active++;
2550
+ }
2551
+ }
2552
+ }, [own, controlled, upload]);
2553
+ const stopAll = useCallback(() => {
2554
+ controllers.current.forEach((c) => c.abort());
2555
+ controllers.current.clear();
2556
+ }, []);
2557
+ useEffect(() => stopAll, [stopAll]);
2558
+ const overlayOpen = overlayMark?.open ?? true;
2559
+ const [wasOpen, setWasOpen] = useState(overlayOpen);
2560
+ if (overlayOpen !== wasOpen) {
2561
+ setWasOpen(overlayOpen);
2562
+ if (!overlayOpen && !controlled) setOwn((prev) => prev.filter((i) => !running(i)));
2563
+ }
2564
+ useEffect(() => {
2565
+ if (!overlayOpen && !controlled) stopAll();
2566
+ }, [overlayOpen, controlled, stopAll]);
2567
+ const busy = shown.some(running);
2568
+ useEffect(() => {
2569
+ if (!busy || !overlayMark) return;
2570
+ return overlayMark.hold();
2571
+ }, [busy, overlayMark]);
2572
+ const [heard, setHeard] = useState("");
2573
+ const [seen, setSeen] = useState(() => new Map(shown.map((i) => [i.id, i.status])));
2574
+ if (shown.length !== seen.size || shown.some((i) => seen.get(i.id) !== i.status)) {
2575
+ const said = [];
2576
+ for (const item of shown) {
2577
+ const before = seen.get(item.id);
2578
+ if (!before || before === item.status) continue;
2579
+ if (item.status === "done") said.push(`${item.name} uploaded`);
2580
+ else if (item.status === "error") said.push(`${item.name} didn't upload: ${item.error ?? "the upload failed"}`);
2581
+ }
2582
+ setSeen(new Map(shown.map((i) => [i.id, i.status])));
2583
+ if (said.length) setHeard(said.join(". "));
2584
+ }
2585
+ const pendingFocus = useRef(null);
2586
+ useEffect(() => {
2587
+ const index = pendingFocus.current;
2588
+ if (index === null) return;
2589
+ pendingFocus.current = null;
2590
+ const rows = Array.from(listRef.current?.querySelectorAll(":scope > li") ?? []).map((row) => row.querySelector("button")).filter((b) => !!b);
2591
+ focusAfterRemoval(rows, index, inputRef.current);
2592
+ }, [shown]);
2494
2593
  const addFiles = useCallback(
2495
2594
  (fileList) => {
2496
2595
  const { accepted, message } = checkFiles(Array.from(fileList), { accept, maxSize, multiple });
2497
2596
  setRejected(message);
2498
2597
  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
- }
2598
+ reportChange?.();
2599
+ onFiles?.(accepted);
2600
+ if (controlled) return;
2601
+ const status = uploadRef.current ? "queued" : "added";
2602
+ const entries = accepted.map((file) => ({ id: `${ownId}-file-${nextEntryId.current++}`, name: file.name, size: file.size, status, file }));
2603
+ if (!multiple) stopAll();
2604
+ setOwn((prev) => multiple ? [...prev, ...entries] : entries);
2519
2605
  },
2520
- [accept, maxSize, multiple, onFiles]
2606
+ [accept, maxSize, multiple, onFiles, controlled, ownId, reportChange, stopAll, uploadRef]
2521
2607
  );
2522
2608
  const handleDrop = useCallback(
2523
2609
  (e) => {
@@ -2527,10 +2613,31 @@ function FileUpload({ onFiles, accept, multiple = true, maxSize, className, erro
2527
2613
  },
2528
2614
  [addFiles]
2529
2615
  );
2530
- const removeFile = (id2) => {
2531
- stopTimer(id2);
2532
- setFiles((prev) => prev.filter((f) => f.id !== id2));
2616
+ const indexOf = (item) => shown.findIndex((i) => i.id === item.id);
2617
+ const cancel = (item) => {
2618
+ pendingFocus.current = indexOf(item);
2619
+ if (!controlled) {
2620
+ stop(item.id);
2621
+ setOwn((prev) => prev.filter((i) => i.id !== item.id));
2622
+ }
2623
+ onCancel?.(item);
2533
2624
  };
2625
+ const retry = (item) => {
2626
+ if (!controlled) patch(item.id, { status: "queued", progress: 0, error: void 0 });
2627
+ onRetry?.(item);
2628
+ };
2629
+ const remove = (item) => {
2630
+ pendingFocus.current = indexOf(item);
2631
+ reportChange?.();
2632
+ if (!controlled) {
2633
+ stop(item.id);
2634
+ setOwn((prev) => prev.filter((i) => i.id !== item.id));
2635
+ }
2636
+ onRemove?.(item);
2637
+ };
2638
+ const canCancel = !controlled || !!onCancel;
2639
+ const canRetry = !controlled || !!onRetry;
2640
+ const canRemove = !controlled || !!onRemove;
2534
2641
  return /* @__PURE__ */ jsxs("div", { className: cn("space-y-ds-3", className), children: [
2535
2642
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-ds-1.5", children: [
2536
2643
  showLabel && /* @__PURE__ */ jsx(Label, { htmlFor: inputId, children: label }),
@@ -2586,33 +2693,43 @@ function FileUpload({ onFiles, accept, multiple = true, maxSize, className, erro
2586
2693
  ),
2587
2694
  rejected && /* @__PURE__ */ jsx(InlineFieldError, { id: rejectedId, message: rejected, className: "mt-0" })
2588
2695
  ] }),
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
- )) })
2696
+ shown.length > 0 && /* @__PURE__ */ jsx("ul", { ref: listRef, className: "space-y-ds-2", children: shown.map((item) => {
2697
+ const words = item.status === "queued" ? "Waiting" : item.status === "uploading" ? `${Math.round(item.progress ?? 0)}%` : item.status === "done" ? "Uploaded" : item.status === "error" ? "Failed" : null;
2698
+ return /* @__PURE__ */ jsxs(
2699
+ "li",
2700
+ {
2701
+ "data-upload-status": item.status,
2702
+ className: cn(
2703
+ "flex items-center gap-ds-3 text-sm",
2704
+ "border-[length:var(--ds-border-width)]",
2705
+ item.status === "error" ? "border-[var(--ds-status-error)]" : "border-[var(--ds-border-subtle)]",
2706
+ "rounded-[var(--ds-radius-md)] bg-[var(--ds-bg-surface)]"
2707
+ ),
2708
+ style: { padding: "var(--ds-space-3)" },
2709
+ children: [
2710
+ /* @__PURE__ */ jsx(Icon, { name: "folder", size: 16, className: "text-[var(--ds-text-muted)] shrink-0" }),
2711
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
2712
+ /* @__PURE__ */ jsx(TruncFileName, { className: "text-[var(--ds-text-primary)]", children: item.name }),
2713
+ (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 ") }),
2714
+ item.status === "uploading" && /* @__PURE__ */ jsx(Progress, { value: item.progress ?? 0, size: "sm", label: `Uploading ${item.name}`, className: "mt-ds-1" }),
2715
+ 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." })
2716
+ ] }),
2717
+ item.status === "done" && /* @__PURE__ */ jsx(Icon, { name: "check", size: 16, className: "text-[var(--ds-status-success)] shrink-0", "aria-hidden": "true" }),
2718
+ running(item) && canCancel && // the visible word starts the name, which says which file (WCAG 2.5.3)
2719
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => cancel(item), "aria-label": `Cancel ${item.name}`, children: "Cancel" }),
2720
+ item.status === "error" && canRetry && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => retry(item), "aria-label": `Retry ${item.name}`, children: "Retry" }),
2721
+ !running(item) && canRemove && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: () => remove(item), "aria-label": `Remove ${item.name}`, children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 14 }) })
2722
+ ]
2723
+ },
2724
+ item.id
2725
+ );
2726
+ }) }),
2727
+ /* @__PURE__ */ jsx("p", { role: "status", className: "sr-only", children: heard })
2611
2728
  ] });
2612
2729
  }
2613
2730
  function SliderRange({
2614
2731
  value,
2615
- onChange,
2732
+ onChange: onChangeProp,
2616
2733
  min = 0,
2617
2734
  max = 100,
2618
2735
  step = 1,
@@ -2627,6 +2744,7 @@ function SliderRange({
2627
2744
  trackStyle,
2628
2745
  formatValue
2629
2746
  }) {
2747
+ const onChange = useReportedChange(onChangeProp, value);
2630
2748
  const field = useField();
2631
2749
  const ownId = useId();
2632
2750
  const showLabel = label != null && !field;
@@ -2832,7 +2950,7 @@ function parseTime(value) {
2832
2950
  }
2833
2951
  function TimePicker({
2834
2952
  value,
2835
- onChange,
2953
+ onChange: onChangeProp,
2836
2954
  use24Hour = false,
2837
2955
  minuteStep = 15,
2838
2956
  disabled,
@@ -2844,6 +2962,7 @@ function TimePicker({
2844
2962
  size: sizeProp,
2845
2963
  ...wiring
2846
2964
  }) {
2965
+ const onChange = useReportedChange(onChangeProp, value);
2847
2966
  const contextSize = useContext(ControlSizeContext);
2848
2967
  const control = useFieldControl({ ...wiring, id, error: error || void 0 }, { requiredInDescription: true });
2849
2968
  const size = sizeProp ?? contextSize ?? "md";
@@ -3222,7 +3341,7 @@ var sizes = {
3222
3341
  };
3223
3342
  function NumberInput({
3224
3343
  value,
3225
- onChange,
3344
+ onChange: onChangeProp,
3226
3345
  min = -Infinity,
3227
3346
  max = Infinity,
3228
3347
  step = 1,
@@ -3236,6 +3355,7 @@ function NumberInput({
3236
3355
  allowDecimals,
3237
3356
  id
3238
3357
  }) {
3358
+ const onChange = useReportedChange(onChangeProp, value);
3239
3359
  const contextSize = useContext(ControlSizeContext);
3240
3360
  const size = sizeProp ?? contextSize ?? "md";
3241
3361
  const intervalRef = useRef(void 0);
@@ -3254,13 +3374,13 @@ function NumberInput({
3254
3374
  [min, max]
3255
3375
  );
3256
3376
  const valueRef = useLatest(value);
3257
- const commitValue = useCallback((v) => {
3377
+ const commitValue = useCallback((v, own = false) => {
3258
3378
  const clamped = clamp(v);
3259
3379
  valueRef.current = clamped;
3260
3380
  setDraft(String(clamped));
3261
- onChange(clamped);
3381
+ (own ? onChangeProp : onChange)(clamped);
3262
3382
  return clamped;
3263
- }, [onChange, clamp, valueRef]);
3383
+ }, [onChange, onChangeProp, clamp, valueRef]);
3264
3384
  const increment = useCallback(() => commitValue(valueRef.current + step), [commitValue, step, valueRef]);
3265
3385
  const decrement = useCallback(() => commitValue(valueRef.current - step), [commitValue, step, valueRef]);
3266
3386
  const startRepeat = useCallback(
@@ -3302,7 +3422,7 @@ function NumberInput({
3302
3422
  setDraft(String(committed));
3303
3423
  } else {
3304
3424
  const fallback = isFinite(min) ? min : 0;
3305
- const committed = commitValue(value !== void 0 ? value : fallback);
3425
+ const committed = commitValue(value !== void 0 ? value : fallback, true);
3306
3426
  setDraft(String(committed));
3307
3427
  }
3308
3428
  }, [draft, commitValue, min, value]);
@@ -3543,7 +3663,7 @@ var Tag = forwardRef(function Tag2({
3543
3663
  });
3544
3664
  function TagInput({
3545
3665
  value,
3546
- onChange,
3666
+ onChange: onChangeProp,
3547
3667
  placeholder = "Add a tag...",
3548
3668
  maxTags = Infinity,
3549
3669
  disabled = false,
@@ -3552,6 +3672,7 @@ function TagInput({
3552
3672
  id,
3553
3673
  "aria-label": ariaLabel
3554
3674
  }) {
3675
+ const onChange = useReportedChange(onChangeProp, value);
3555
3676
  const [input, setInput] = useState("");
3556
3677
  const inputRef = useRef(null);
3557
3678
  const addTag = useCallback(
@@ -3677,7 +3798,7 @@ var sizes2 = {
3677
3798
  };
3678
3799
  function PinInput({
3679
3800
  value,
3680
- onChange,
3801
+ onChange: onChangeProp,
3681
3802
  length = 6,
3682
3803
  mask = false,
3683
3804
  disabled = false,
@@ -3686,6 +3807,7 @@ function PinInput({
3686
3807
  error = false,
3687
3808
  id
3688
3809
  }) {
3810
+ const onChange = useReportedChange(onChangeProp, value);
3689
3811
  const contextSize = useContext(ControlSizeContext);
3690
3812
  const size = sizeProp ?? contextSize ?? "md";
3691
3813
  const inputsRef = useRef([]);
@@ -3821,7 +3943,7 @@ function StarIcon({ filled, half, size }) {
3821
3943
  }
3822
3944
  function Rating({
3823
3945
  value,
3824
- onChange,
3946
+ onChange: onChangeProp,
3825
3947
  max = 5,
3826
3948
  allowHalf = false,
3827
3949
  readOnly = false,
@@ -3829,15 +3951,16 @@ function Rating({
3829
3951
  className,
3830
3952
  error = false
3831
3953
  }) {
3954
+ const onChange = useReportedChange(onChangeProp, value);
3832
3955
  const [hoverValue, setHoverValue] = useState(null);
3833
3956
  const displayValue = hoverValue ?? value;
3834
3957
  const handleClick = useCallback(
3835
3958
  (starIdx, isLeftHalf) => {
3836
- if (readOnly || !onChange) return;
3959
+ if (readOnly || !onChangeProp) return;
3837
3960
  const newValue = allowHalf && isLeftHalf ? starIdx + 0.5 : starIdx + 1;
3838
3961
  onChange(newValue === value ? 0 : newValue);
3839
3962
  },
3840
- [readOnly, onChange, allowHalf, value]
3963
+ [readOnly, onChangeProp, onChange, allowHalf, value]
3841
3964
  );
3842
3965
  const handleMouseMove = useCallback(
3843
3966
  (starIdx, e) => {
@@ -3851,7 +3974,7 @@ function Rating({
3851
3974
  const iconSize = iconSizes[size];
3852
3975
  const step = allowHalf ? 0.5 : 1;
3853
3976
  const handleKeyDown = (e) => {
3854
- if (readOnly || !onChange) return;
3977
+ if (readOnly || !onChangeProp) return;
3855
3978
  let next = null;
3856
3979
  if (e.key === "ArrowRight" || e.key === "ArrowUp") next = Math.min(max, value + step);
3857
3980
  else if (e.key === "ArrowLeft" || e.key === "ArrowDown") next = Math.max(0, value - step);
@@ -3975,13 +4098,14 @@ var DEFAULT_SWATCHES = [
3975
4098
  ];
3976
4099
  function ColorPicker({
3977
4100
  value,
3978
- onChange,
4101
+ onChange: onChangeProp,
3979
4102
  showAlpha = false,
3980
4103
  swatches = DEFAULT_SWATCHES,
3981
4104
  disabled = false,
3982
4105
  className,
3983
4106
  error = false
3984
4107
  }) {
4108
+ const onChange = useReportedChange(onChangeProp, value);
3985
4109
  const [hsl, setHsl] = useState(() => hexToHsl(value));
3986
4110
  const [hexInput, setHexInput] = useState(value);
3987
4111
  const satLightRef = useRef(null);
@@ -4222,6 +4346,7 @@ var Alert = forwardRef(function Alert2({
4222
4346
  trigger: /* @__PURE__ */ jsx(
4223
4347
  "button",
4224
4348
  {
4349
+ type: "button",
4225
4350
  className: cn(
4226
4351
  "shrink-0 text-xs font-medium px-ds-2.5 py-ds-1 rounded-[var(--ds-radius-sm)]",
4227
4352
  "transition-colors cursor-pointer",
@@ -4246,6 +4371,7 @@ var Alert = forwardRef(function Alert2({
4246
4371
  return /* @__PURE__ */ jsxs(
4247
4372
  "button",
4248
4373
  {
4374
+ type: "button",
4249
4375
  onClick: a.onClick,
4250
4376
  className: cn(
4251
4377
  "shrink-0 text-xs font-medium px-ds-2.5 py-ds-1 rounded-[var(--ds-radius-sm)]",
@@ -4266,6 +4392,7 @@ var Alert = forwardRef(function Alert2({
4266
4392
  /* @__PURE__ */ jsxs(
4267
4393
  "button",
4268
4394
  {
4395
+ type: "button",
4269
4396
  onClick: () => setDetailsExpanded(!detailsExpanded),
4270
4397
  "aria-expanded": detailsExpanded,
4271
4398
  className: cn(
@@ -4293,6 +4420,7 @@ var Alert = forwardRef(function Alert2({
4293
4420
  dismissible && /* @__PURE__ */ jsx(
4294
4421
  "button",
4295
4422
  {
4423
+ type: "button",
4296
4424
  onClick: onDismiss,
4297
4425
  className: "shrink-0 text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer",
4298
4426
  "aria-label": "Dismiss",
@@ -4303,6 +4431,12 @@ var Alert = forwardRef(function Alert2({
4303
4431
  }
4304
4432
  );
4305
4433
  });
4434
+ function controlsOf(form) {
4435
+ if (!form) return "";
4436
+ const controls = form.querySelectorAll("input, select, textarea, [role='combobox'], [role='radiogroup'], [role='checkbox'], [role='switch'], [role='slider'], [role='spinbutton'], [contenteditable='true']");
4437
+ return Array.from(controls).map((el) => el.id || el.getAttribute("name") || el.getAttribute("role") || el.tagName).join("|");
4438
+ }
4439
+ var isThenable = (value) => !!value && typeof value.then === "function";
4306
4440
  function stampFormId(node, formId) {
4307
4441
  return Children.map(node, (child) => {
4308
4442
  if (!isValidElement(child)) return child;
@@ -4359,12 +4493,30 @@ function FormWrapper({ onSubmit, validationMode: validationModeProp, children, a
4359
4493
  const pendingSubmit = useRef(false);
4360
4494
  const pendingFocus = useRef(false);
4361
4495
  const onSubmitRef = useLatest(onSubmit);
4496
+ const overlayMark = useContext(OverlayMarkCtx);
4497
+ const reportChange = overlayMark ? overlayMark.report : null;
4498
+ const [savedCheck, setSavedCheck] = useState(null);
4499
+ useEffect(() => {
4500
+ if (!savedCheck) return;
4501
+ const form = formRef.current;
4502
+ if (form && !form.querySelector("[aria-invalid='true']") && controlsOf(form) === savedCheck.controls) overlayMark?.clear();
4503
+ }, [savedCheck, overlayMark]);
4362
4504
  useEffect(() => {
4363
4505
  if (pendingSubmit.current && submitted) {
4364
4506
  pendingSubmit.current = false;
4365
- onSubmitRef.current(errorsRef.current);
4507
+ const form = formRef.current;
4508
+ const passed = Object.keys(errorsRef.current).length === 0 && !form?.querySelector("[aria-invalid='true']");
4509
+ const controls = controlsOf(form);
4510
+ const result = onSubmitRef.current(errorsRef.current);
4511
+ if (!passed || !overlayMark) return;
4512
+ if (isThenable(result)) {
4513
+ result.then(() => overlayMark.clear(), () => {
4514
+ });
4515
+ } else {
4516
+ setSavedCheck({ controls });
4517
+ }
4366
4518
  }
4367
- }, [submitted, submitCount, errors, errorsRef, onSubmitRef]);
4519
+ }, [submitted, submitCount, errors, errorsRef, onSubmitRef, overlayMark]);
4368
4520
  const submittedRef = useLatest(submitted);
4369
4521
  const errorSummaryRef = useLatest(errorSummary);
4370
4522
  const summaryRef = useRef(null);
@@ -4418,7 +4570,15 @@ function FormWrapper({ onSubmit, validationMode: validationModeProp, children, a
4418
4570
  pendingFocus.current = false;
4419
4571
  }, []);
4420
4572
  const setError = useCallback((field, message) => {
4421
- setErrors((prev) => prev[field] === message ? prev : { ...prev, [field]: message });
4573
+ setErrors((prev) => {
4574
+ if (!message) {
4575
+ if (!(field in prev)) return prev;
4576
+ const next = { ...prev };
4577
+ delete next[field];
4578
+ return next;
4579
+ }
4580
+ return prev[field] === message ? prev : { ...prev, [field]: message };
4581
+ });
4422
4582
  }, []);
4423
4583
  const checks = useRef(/* @__PURE__ */ new Map());
4424
4584
  const register = useCallback((field, check) => {
@@ -4443,9 +4603,16 @@ function FormWrapper({ onSubmit, validationMode: validationModeProp, children, a
4443
4603
  (e) => {
4444
4604
  e.preventDefault();
4445
4605
  if (checks.current.size > 0) {
4446
- const found = {};
4447
- for (const [field, check] of checks.current) found[field] = check();
4448
- setErrors((prev) => ({ ...prev, ...found }));
4606
+ const found = /* @__PURE__ */ new Map();
4607
+ for (const [field, check] of checks.current) found.set(field, check());
4608
+ setErrors((prev) => {
4609
+ const next = { ...prev };
4610
+ for (const [field, message] of found) {
4611
+ if (message) next[field] = message;
4612
+ else delete next[field];
4613
+ }
4614
+ return next;
4615
+ });
4449
4616
  }
4450
4617
  pendingSubmit.current = true;
4451
4618
  pendingFocus.current = focusFirstError;
@@ -4468,22 +4635,67 @@ function FormWrapper({ onSubmit, validationMode: validationModeProp, children, a
4468
4635
  f.message
4469
4636
  ] }, f.id || f.label)) }) }) : null;
4470
4637
  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: [
4638
+ 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
4639
  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
4640
  children,
4474
4641
  actionsNode
4475
- ] }) });
4642
+ ] }) }) });
4643
+ }
4644
+ var FOCUSABLE2 = "input:not([disabled]):not([type='hidden']), select:not([disabled]), textarea:not([disabled]), [role='combobox'], [role='switch'], [role='radio'], [tabindex]:not([tabindex='-1'])";
4645
+ function AddAnother({ items, getId, itemName, columns, renderRow, onAdd, onRemove, addLabel = "Add another", label, minItems = 1, maxItems, className }) {
4646
+ const listRef = useRef(null);
4647
+ const addRef = useRef(null);
4648
+ const pending = useRef(null);
4649
+ const firstFields = () => Array.from(listRef.current?.querySelectorAll(":scope > li") ?? []).map((row) => row.querySelector(FOCUSABLE2)).filter((el) => !!el);
4650
+ useEffect(() => {
4651
+ const next = pending.current;
4652
+ if (!next) return;
4653
+ pending.current = null;
4654
+ const fields = firstFields();
4655
+ if (next.kind === "add") fields[fields.length - 1]?.focus();
4656
+ else focusAfterRemoval(fields, next.index, addRef.current);
4657
+ }, [items]);
4658
+ const canRemove = items.length > minItems;
4659
+ const canAdd = maxItems === void 0 || items.length < maxItems;
4660
+ const grid = { "--gs-add-another-cols": `repeat(${columns.length}, minmax(0, 1fr)) max-content` };
4661
+ const body = /* @__PURE__ */ jsxs("div", { "data-add-another": "", className: cn("@container flex flex-col gap-ds-3", className), style: grid, children: [
4662
+ /* @__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: [
4663
+ columns.map((column) => /* @__PURE__ */ jsx("span", { children: column }, column)),
4664
+ canRemove && /* @__PURE__ */ jsx("span", { className: "invisible inline-flex items-center whitespace-nowrap px-ds-4 text-xs font-medium", children: "Remove" })
4665
+ ] }),
4666
+ /* @__PURE__ */ jsx("ul", { ref: listRef, className: "flex flex-col gap-ds-4 @min-[30rem]:gap-ds-3", children: items.map((item, index) => {
4667
+ const id = getId(item);
4668
+ 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: [
4669
+ /* @__PURE__ */ jsx(AddAnotherRowCtx.Provider, { value: true, children: renderRow(item, index, (column) => `${column}, ${itemName(index)}`) }),
4670
+ canRemove && // the visible "Remove" starts its name, which says whose row it removes (WCAG 2.5.3 label in name)
4671
+ /* @__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: () => {
4672
+ pending.current = { kind: "remove", index };
4673
+ onRemove(id);
4674
+ }, children: "Remove" })
4675
+ ] }, id);
4676
+ }) }),
4677
+ canAdd && /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, { ref: addRef, type: "button", variant: "secondary", onClick: () => {
4678
+ pending.current = { kind: "add" };
4679
+ onAdd();
4680
+ }, children: addLabel }) })
4681
+ ] });
4682
+ if (!label) return body;
4683
+ return /* @__PURE__ */ jsxs("fieldset", { className: "min-w-0", children: [
4684
+ /* @__PURE__ */ jsx("legend", { className: "mb-ds-2 text-sm font-medium text-[var(--ds-text-primary)]", children: label }),
4685
+ body
4686
+ ] });
4476
4687
  }
4477
4688
  function TransferList({
4478
4689
  available,
4479
4690
  selected,
4480
- onChange,
4691
+ onChange: onChangeProp,
4481
4692
  availableTitle = "Available",
4482
4693
  selectedTitle = "Selected",
4483
4694
  searchable = true,
4484
4695
  className,
4485
4696
  ref
4486
4697
  }) {
4698
+ const onChange = useReportedChange(onChangeProp, selected);
4487
4699
  const [checkedAvailable, setCheckedAvailable] = useState(/* @__PURE__ */ new Set());
4488
4700
  const [checkedSelected, setCheckedSelected] = useState(/* @__PURE__ */ new Set());
4489
4701
  const [searchLeft, setSearchLeft] = useState("");
@@ -4565,6 +4777,7 @@ function TransferList({
4565
4777
  /* @__PURE__ */ jsx(
4566
4778
  "button",
4567
4779
  {
4780
+ type: "button",
4568
4781
  onClick: moveAllRight,
4569
4782
  disabled: !available.filter((i) => !i.disabled).length,
4570
4783
  "aria-label": isStacked ? "Move all down" : "Move all right",
@@ -4575,6 +4788,7 @@ function TransferList({
4575
4788
  /* @__PURE__ */ jsx(
4576
4789
  "button",
4577
4790
  {
4791
+ type: "button",
4578
4792
  onClick: moveRight,
4579
4793
  disabled: !checkedAvailable.size,
4580
4794
  "aria-label": isStacked ? "Move selected down" : "Move selected right",
@@ -4585,6 +4799,7 @@ function TransferList({
4585
4799
  /* @__PURE__ */ jsx(
4586
4800
  "button",
4587
4801
  {
4802
+ type: "button",
4588
4803
  onClick: moveLeft,
4589
4804
  disabled: !checkedSelected.size,
4590
4805
  "aria-label": isStacked ? "Move selected up" : "Move selected left",
@@ -4595,6 +4810,7 @@ function TransferList({
4595
4810
  /* @__PURE__ */ jsx(
4596
4811
  "button",
4597
4812
  {
4813
+ type: "button",
4598
4814
  onClick: moveAllLeft,
4599
4815
  disabled: !selected.filter((i) => !i.disabled).length,
4600
4816
  "aria-label": isStacked ? "Move all up" : "Move all left",
@@ -4726,62 +4942,6 @@ function toInitials(value) {
4726
4942
  if (!/\s/.test(s) && Array.from(s).length <= 3) return s;
4727
4943
  return initialsOf(s);
4728
4944
  }
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
4945
  var sizeStyles2 = {
4786
4946
  sm: "h-8 w-8 text-xs",
4787
4947
  md: "h-10 w-10 text-sm",
@@ -4804,13 +4964,23 @@ function colorStyles(color, seed) {
4804
4964
  if (color === "neutral") return "bg-[var(--ds-bg-muted)] text-[var(--ds-text-secondary)]";
4805
4965
  return paletteStyles[color === "auto" ? paletteFor(seed) : color];
4806
4966
  }
4807
- var statusVariantMap = {
4808
- online: "success",
4809
- away: "warning",
4810
- busy: "error",
4811
- offline: "offline"
4812
- };
4967
+ function PresenceMark({ status }) {
4968
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 10 10", width: "10", height: "10", "aria-hidden": "true", focusable: "false", "data-presence": status, className: "block", children: [
4969
+ status === "online" && /* @__PURE__ */ jsx("circle", { cx: "5", cy: "5", r: "5", className: "fill-[var(--ds-status-success)]" }),
4970
+ status === "away" && /* @__PURE__ */ jsx("circle", { cx: "5", cy: "5", r: "4", fill: "none", strokeWidth: "2", className: "stroke-[var(--ds-status-warning)]" }),
4971
+ status === "busy" && /* @__PURE__ */ jsxs(Fragment, { children: [
4972
+ /* @__PURE__ */ jsx("circle", { cx: "5", cy: "5", r: "5", className: "fill-[var(--ds-status-error)]" }),
4973
+ /* @__PURE__ */ jsx("rect", { x: "2.5", y: "4.1", width: "5", height: "1.8", rx: "0.9", className: "fill-[var(--ds-bg-canvas)]" })
4974
+ ] }),
4975
+ status === "offline" && /* @__PURE__ */ jsxs(Fragment, { children: [
4976
+ /* @__PURE__ */ jsx("circle", { cx: "5", cy: "5", r: "4", fill: "none", strokeWidth: "2", className: "stroke-[var(--ds-text-muted)]" }),
4977
+ /* @__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)]" })
4978
+ ] })
4979
+ ] });
4980
+ }
4813
4981
  function Avatar({ initials, src, alt, size = "md", status, color = "auto", className }) {
4982
+ const who = alt ?? initials ?? "";
4983
+ const named = status ? [who, status].filter(Boolean).join(", ") : void 0;
4814
4984
  const base = cn(
4815
4985
  "inline-flex items-center justify-center font-medium shrink-0",
4816
4986
  "rounded-[var(--ds-radius-full)]",
@@ -4822,13 +4992,15 @@ function Avatar({ initials, src, alt, size = "md", status, color = "auto", class
4822
4992
  {
4823
4993
  "data-avatar": "",
4824
4994
  src,
4825
- alt: alt ?? initials ?? "",
4995
+ alt: named ?? alt ?? initials ?? "",
4826
4996
  className: cn(base, "object-cover")
4827
4997
  }
4828
4998
  ) : /* @__PURE__ */ jsx(
4829
4999
  "div",
4830
5000
  {
4831
5001
  "data-avatar": "",
5002
+ role: named ? "img" : void 0,
5003
+ "aria-label": named,
4832
5004
  className: cn(
4833
5005
  base,
4834
5006
  colorStyles(color, alt ?? initials ?? ""),
@@ -4850,20 +5022,14 @@ function Avatar({ initials, src, alt, size = "md", status, color = "auto", class
4850
5022
  size === "lg" && "bottom-[0px] right-[0px]"
4851
5023
  ),
4852
5024
  "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
- )
5025
+ 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
5026
  }
4861
5027
  )
4862
5028
  ] });
4863
5029
  }
4864
5030
  function MentionInput({
4865
5031
  value: controlledValue,
4866
- onChange,
5032
+ onChange: onChangeProp,
4867
5033
  suggestions,
4868
5034
  trigger = "@",
4869
5035
  placeholder,
@@ -4872,6 +5038,7 @@ function MentionInput({
4872
5038
  }) {
4873
5039
  const [internalValue, setInternalValue] = useState("");
4874
5040
  const value = controlledValue ?? internalValue;
5041
+ const onChange = useReportedChange(onChangeProp, value);
4875
5042
  const [query, setQuery] = useState("");
4876
5043
  const [showPopup, setShowPopup] = useState(false);
4877
5044
  const [activeIndex, setActiveIndex] = useState(0);
@@ -5777,6 +5944,57 @@ var DescriptionList = forwardRef(function DescriptionList2({
5777
5944
  }
5778
5945
  );
5779
5946
  });
5947
+ var variantColors = {
5948
+ success: "bg-[var(--ds-status-success)]",
5949
+ warning: "bg-[var(--ds-status-warning)]",
5950
+ error: "bg-[var(--ds-status-error)]",
5951
+ info: "bg-[var(--ds-accent-primary)]",
5952
+ neutral: "bg-[var(--ds-text-muted)]",
5953
+ offline: "bg-[var(--ds-text-muted)] opacity-disabled"
5954
+ };
5955
+ var pulseRingColors = {
5956
+ success: "bg-[color-mix(in_oklch,var(--ds-status-success)_30%,transparent)]",
5957
+ warning: "bg-[color-mix(in_oklch,var(--ds-status-warning)_30%,transparent)]",
5958
+ error: "bg-[color-mix(in_oklch,var(--ds-status-error)_30%,transparent)]",
5959
+ info: "bg-[var(--ds-accent-soft)]",
5960
+ neutral: "bg-[color-mix(in_oklch,var(--ds-text-muted)_20%,transparent)]",
5961
+ offline: "bg-[color-mix(in_oklch,var(--ds-text-muted)_10%,transparent)]"
5962
+ };
5963
+ var StatusDot = forwardRef(function StatusDot2({
5964
+ variant = "neutral",
5965
+ label,
5966
+ pulse = false,
5967
+ className
5968
+ }, ref) {
5969
+ return (
5970
+ // No live region (A48): the dot is read as its words where it sits; telling the user about a change as it happens
5971
+ // is the toasts' job (WCAG 4.1.3). The dot itself is hidden from assistive technology.
5972
+ /* @__PURE__ */ jsxs("span", { ref, "data-status-dot": variant, className: cn("inline-flex items-center gap-ds-1.5", className), children: [
5973
+ /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: "relative inline-flex shrink-0", children: [
5974
+ /* @__PURE__ */ jsx(
5975
+ "span",
5976
+ {
5977
+ className: cn(
5978
+ "rounded-[var(--ds-radius-full)] h-2.5 w-2.5",
5979
+ variantColors[variant]
5980
+ )
5981
+ }
5982
+ ),
5983
+ pulse && variant !== "offline" && /* @__PURE__ */ jsx(
5984
+ "span",
5985
+ {
5986
+ className: cn(
5987
+ "absolute inset-0 rounded-[var(--ds-radius-full)]",
5988
+ "animate-ping motion-reduce:animate-none",
5989
+ pulseRingColors[variant]
5990
+ )
5991
+ }
5992
+ )
5993
+ ] }),
5994
+ /* @__PURE__ */ jsx("span", { className: "font-normal text-[var(--ds-text-primary)] text-[length:var(--ds-font-size-xs)]", children: label })
5995
+ ] })
5996
+ );
5997
+ });
5780
5998
  function Accordion({ items, type = "single", defaultOpen = [], open: controlledOpen, onOpenChange, className }) {
5781
5999
  const [internalOpen, setInternalOpen] = useState(new Set(defaultOpen));
5782
6000
  const baseId = useId();
@@ -5811,6 +6029,7 @@ function Accordion({ items, type = "single", defaultOpen = [], open: controlledO
5811
6029
  /* @__PURE__ */ jsxs(
5812
6030
  "button",
5813
6031
  {
6032
+ type: "button",
5814
6033
  id: triggerId,
5815
6034
  "aria-expanded": isOpen,
5816
6035
  "aria-controls": panelId,
@@ -6196,6 +6415,7 @@ function Table({
6196
6415
  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
6416
  "button",
6198
6417
  {
6418
+ type: "button",
6199
6419
  onClick: () => toggleExpand(i),
6200
6420
  className: "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] cursor-pointer",
6201
6421
  "aria-expanded": isExpanded,
@@ -6312,7 +6532,7 @@ var indicatorInset = {
6312
6532
  };
6313
6533
  function SegmentedControl({
6314
6534
  value,
6315
- onChange,
6535
+ onChange: onChangeProp,
6316
6536
  options,
6317
6537
  size: sizeProp,
6318
6538
  variant = "default",
@@ -6323,6 +6543,7 @@ function SegmentedControl({
6323
6543
  maxOptions = 6,
6324
6544
  ...wiring
6325
6545
  }) {
6546
+ const onChange = useReportedChange(onChangeProp, value);
6326
6547
  const contextSize = useContext(ControlSizeContext);
6327
6548
  const control = useFieldControl(wiring);
6328
6549
  const named = !ariaLabel && control.labelId ? { "aria-labelledby": control.labelId } : {};
@@ -8864,6 +9085,7 @@ function Carousel({
8864
9085
  /* @__PURE__ */ jsx(
8865
9086
  "button",
8866
9087
  {
9088
+ type: "button",
8867
9089
  onClick: prev,
8868
9090
  disabled: !canPrev,
8869
9091
  "aria-label": "Previous slide",
@@ -8883,6 +9105,7 @@ function Carousel({
8883
9105
  /* @__PURE__ */ jsx(
8884
9106
  "button",
8885
9107
  {
9108
+ type: "button",
8886
9109
  onClick: next,
8887
9110
  disabled: !canNext,
8888
9111
  "aria-label": "Next slide",
@@ -8903,6 +9126,7 @@ function Carousel({
8903
9126
  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
9127
  "button",
8905
9128
  {
9129
+ type: "button",
8906
9130
  onClick: () => goTo(i),
8907
9131
  "aria-label": `Go to slide ${i + 1}`,
8908
9132
  className: cn(
@@ -11651,6 +11875,7 @@ function TabButton({
11651
11875
  const btn2 = /* @__PURE__ */ jsxs(
11652
11876
  "button",
11653
11877
  {
11878
+ type: "button",
11654
11879
  id: tabId,
11655
11880
  role: "tab",
11656
11881
  "aria-selected": isActive,
@@ -11686,6 +11911,7 @@ function TabButton({
11686
11911
  const btn = /* @__PURE__ */ jsx(
11687
11912
  "button",
11688
11913
  {
11914
+ type: "button",
11689
11915
  id: tabId,
11690
11916
  role: "tab",
11691
11917
  "aria-selected": isActive,
@@ -12059,6 +12285,7 @@ var Breadcrumbs = forwardRef(function Breadcrumbs2({
12059
12285
  /* @__PURE__ */ jsx(
12060
12286
  "button",
12061
12287
  {
12288
+ type: "button",
12062
12289
  ref: ellipsisBtnRef,
12063
12290
  onClick: () => {
12064
12291
  if (!dropdownOpen) setActiveIdx(0);
@@ -12109,6 +12336,7 @@ var Breadcrumbs = forwardRef(function Breadcrumbs2({
12109
12336
  children: collapsedItems.map((ci, idx) => /* @__PURE__ */ jsxs(
12110
12337
  "button",
12111
12338
  {
12339
+ type: "button",
12112
12340
  "data-index": idx,
12113
12341
  role: "menuitem",
12114
12342
  tabIndex: idx === activeIdx ? 0 : -1,
@@ -12304,6 +12532,7 @@ function PageNumbers({ currentPage, totalPages, onPageChange, pageSize: pageSize
12304
12532
  /* @__PURE__ */ jsx(
12305
12533
  "button",
12306
12534
  {
12535
+ type: "button",
12307
12536
  onClick: () => onPageChange(currentPage - 1),
12308
12537
  disabled: currentPage <= 1,
12309
12538
  "aria-label": "Previous page",
@@ -12323,6 +12552,7 @@ function PageNumbers({ currentPage, totalPages, onPageChange, pageSize: pageSize
12323
12552
  (p, i) => p === "..." ? /* @__PURE__ */ jsx(EllipsisJumper, { totalPages, onPageChange }, `ellipsis-${i}`) : /* @__PURE__ */ jsx(
12324
12553
  "button",
12325
12554
  {
12555
+ type: "button",
12326
12556
  onClick: () => onPageChange(p),
12327
12557
  "aria-current": p === currentPage ? "page" : void 0,
12328
12558
  className: cn(
@@ -12340,6 +12570,7 @@ function PageNumbers({ currentPage, totalPages, onPageChange, pageSize: pageSize
12340
12570
  /* @__PURE__ */ jsx(
12341
12571
  "button",
12342
12572
  {
12573
+ type: "button",
12343
12574
  onClick: () => onPageChange(currentPage + 1),
12344
12575
  disabled: currentPage >= totalPages,
12345
12576
  "aria-label": "Next page",
@@ -12602,6 +12833,7 @@ function StepCircle({ status, index, onClick, clickable }) {
12602
12833
  return /* @__PURE__ */ jsx(
12603
12834
  "button",
12604
12835
  {
12836
+ type: "button",
12605
12837
  onClick,
12606
12838
  disabled: !clickable,
12607
12839
  "aria-current": status === "current" ? "step" : void 0,
@@ -12796,7 +13028,7 @@ var Sidebar = forwardRef(function Sidebar2({
12796
13028
  position = "flush",
12797
13029
  navbarFloating: navbarFloatingProp,
12798
13030
  navSize = "md",
12799
- fill = false,
13031
+ fill: fill2 = false,
12800
13032
  className
12801
13033
  }, ref) {
12802
13034
  const [internalCollapsed, setInternalCollapsed] = useState(defaultCollapsed ?? false);
@@ -12806,10 +13038,10 @@ var Sidebar = forwardRef(function Sidebar2({
12806
13038
  const appShell = useAppShell();
12807
13039
  const navbarFloating = navbarFloatingProp ?? appShell?.navbarFloating;
12808
13040
  useLayoutEffect(() => {
12809
- if (fill || !appShell) return;
13041
+ if (fill2 || !appShell) return;
12810
13042
  return appShell.registerChrome("sidebar", position);
12811
- }, [appShell?.registerChrome, fill, position]);
12812
- const usesShellState = appShell !== null && !isControlled && defaultCollapsed === void 0 && !fill && type === "traditional";
13043
+ }, [appShell?.registerChrome, fill2, position]);
13044
+ const usesShellState = appShell !== null && !isControlled && defaultCollapsed === void 0 && !fill2 && type === "traditional";
12813
13045
  const uncontrolledCollapsed = usesShellState ? appShell.sidebarCollapsed : internalCollapsed;
12814
13046
  const collapsed = mini || (isControlled ? controlledCollapsed : uncontrolledCollapsed);
12815
13047
  const handleCollapsedChange = (value) => {
@@ -12828,11 +13060,11 @@ var Sidebar = forwardRef(function Sidebar2({
12828
13060
  effectiveDarkMode = false;
12829
13061
  }
12830
13062
  const isDark = effectiveDarkMode ?? variant === "dark";
12831
- const showCollapse = type === "traditional" && !mini && !fill;
13063
+ const showCollapse = type === "traditional" && !mini && !fill2;
12832
13064
  const isMinimal = type === "minimal";
12833
13065
  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)";
13066
+ const isFloating = position === "floating" && !fill2;
13067
+ const sidebarWidth = fill2 ? "100%" : isCompact ? "var(--ds-sidebar-compact-width)" : collapsed ? "var(--ds-sidebar-collapsed-width)" : "var(--ds-sidebar-width)";
12836
13068
  const aside = /* @__PURE__ */ jsxs(
12837
13069
  "aside",
12838
13070
  {
@@ -12870,6 +13102,7 @@ var Sidebar = forwardRef(function Sidebar2({
12870
13102
  const collapseButton = /* @__PURE__ */ jsxs(
12871
13103
  "button",
12872
13104
  {
13105
+ type: "button",
12873
13106
  onClick: () => handleCollapsedChange(!collapsed),
12874
13107
  "aria-expanded": !collapsed,
12875
13108
  "aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar",
@@ -13222,6 +13455,7 @@ function SidebarNav({
13222
13455
  /* @__PURE__ */ jsxs(
13223
13456
  "button",
13224
13457
  {
13458
+ type: "button",
13225
13459
  onClick: () => {
13226
13460
  toggleExpand(key);
13227
13461
  onItemClick?.(item, index);
@@ -13356,6 +13590,7 @@ function SidebarNav({
13356
13590
  collapsible && /* @__PURE__ */ jsxs(
13357
13591
  "button",
13358
13592
  {
13593
+ type: "button",
13359
13594
  onClick: () => onCollapsedChange(!collapsed),
13360
13595
  title: collapsed ? "Expand sidebar" : "Collapse sidebar",
13361
13596
  className: cn(
@@ -13422,6 +13657,7 @@ function CompactNavItem({
13422
13657
  /* @__PURE__ */ jsxs(
13423
13658
  "button",
13424
13659
  {
13660
+ type: "button",
13425
13661
  ref: setTriggerEl,
13426
13662
  onMouseEnter: !mobile && hasChildren ? handleMouseEnter : void 0,
13427
13663
  onMouseLeave: !mobile && hasChildren && isOpen ? handleMouseLeave : void 0,
@@ -13484,6 +13720,7 @@ function CompactNavItem({
13484
13720
  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
13721
  "button",
13486
13722
  {
13723
+ type: "button",
13487
13724
  onClick: () => onItemClick?.(child, ci),
13488
13725
  className: cn(
13489
13726
  "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,20 +13735,486 @@ function CompactNavItem({
13498
13735
  )) })
13499
13736
  ] });
13500
13737
  }
13501
- function BottomNav({ items, onItemClick, maxVisible = 5, className, ref }) {
13502
- const needsOverflow = items.length > maxVisible;
13503
- const visibleItems = needsOverflow ? items.slice(0, maxVisible - 1) : items;
13504
- const overflowItems = needsOverflow ? items.slice(maxVisible - 1) : [];
13505
- const overflowHasActive = overflowItems.some((it) => it.active);
13506
- const [overflowOpen, setOverflowOpen] = useState(false);
13507
- const menuRef = useRef(null);
13508
- const closeOverflow = useCallback(() => setOverflowOpen(false), []);
13738
+ var variantStyles4 = {
13739
+ default: "border-[var(--ds-border-default)] bg-[var(--ds-bg-surface)]",
13740
+ success: "border-[var(--ds-status-success)] bg-[var(--ds-status-success-bg)]",
13741
+ warning: "border-[var(--ds-status-warning)] bg-[var(--ds-status-warning-bg)]",
13742
+ error: "border-[var(--ds-status-error)] bg-[var(--ds-status-error-bg)]"
13743
+ };
13744
+ var variantIcons = {
13745
+ default: { name: "info", className: "text-[var(--ds-text-muted)]" },
13746
+ success: { name: "check", className: "text-[var(--ds-status-success)]" },
13747
+ warning: { name: "warning", className: "text-[var(--ds-status-warning)]" },
13748
+ error: { name: "error", className: "text-[var(--ds-status-error)]" }
13749
+ };
13750
+ var TOAST_POSITION_CLASSES = {
13751
+ "top-right": "top-[var(--ds-toast-inset-top)] right-[var(--ds-toast-inset-right)]",
13752
+ "top-left": "top-[var(--ds-toast-inset-top)] left-[var(--ds-toast-inset-left)]",
13753
+ "bottom-right": "bottom-[var(--ds-toast-inset-bottom-right)] right-[var(--ds-toast-inset-right)]",
13754
+ "bottom-left": "bottom-[var(--ds-toast-inset-bottom-left)] left-[var(--ds-toast-inset-left)]",
13755
+ "top-center": "top-[var(--ds-toast-inset-top)] left-1/2 -translate-x-1/2",
13756
+ "bottom-center": "bottom-ds-4 left-1/2 -translate-x-1/2"
13757
+ };
13758
+ var finite = (ms) => typeof ms === "number" && Number.isFinite(ms) && ms > 0;
13759
+ 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 }) {
13760
+ const isMobile = useBreakpoint("sm");
13761
+ const isCentered = position === "top-center" || position === "bottom-center";
13762
+ const [hovered, setHovered] = useState(false);
13763
+ const onDismissRef = useLatest(onDismiss);
13764
+ const onLapseRef = useLatest(onActionLapse);
13765
+ const actionUsed = useRef(false);
13766
+ const phase = action ? finite(actionWindow) ? "window" : "none" : finite(duration) ? "plain" : "none";
13767
+ const total = phase === "window" ? actionWindow : phase === "plain" ? duration : 0;
13768
+ const timer = useRef(void 0);
13769
+ const remaining = useRef(total);
13770
+ const startedAt = useRef(0);
13771
+ const isPaused = paused || !inline && pauseOnHover && hovered;
13772
+ const stop = useCallback(() => {
13773
+ if (timer.current) {
13774
+ clearTimeout(timer.current);
13775
+ timer.current = void 0;
13776
+ remaining.current -= Date.now() - startedAt.current;
13777
+ }
13778
+ }, []);
13779
+ const start = useCallback(() => {
13780
+ if (phase === "none" || timer.current) return;
13781
+ startedAt.current = Date.now();
13782
+ const expire = phase === "window" ? () => {
13783
+ timer.current = void 0;
13784
+ if (onLapseRef.current) onLapseRef.current();
13785
+ else onDismissRef.current();
13786
+ } : () => {
13787
+ timer.current = void 0;
13788
+ onDismissRef.current();
13789
+ };
13790
+ timer.current = setTimeout(expire, Math.max(0, remaining.current));
13791
+ }, [phase, onDismissRef, onLapseRef]);
13509
13792
  useEffect(() => {
13510
- if (!overflowOpen) return;
13511
- function handleClick(e) {
13512
- if (menuRef.current && !menuRef.current.contains(e.target)) closeOverflow();
13793
+ if (!visible) return;
13794
+ remaining.current = total;
13795
+ if (timer.current) {
13796
+ clearTimeout(timer.current);
13797
+ timer.current = void 0;
13513
13798
  }
13514
- document.addEventListener("mousedown", handleClick);
13799
+ if (!isPaused) start();
13800
+ return () => {
13801
+ if (timer.current) {
13802
+ clearTimeout(timer.current);
13803
+ timer.current = void 0;
13804
+ }
13805
+ };
13806
+ }, [visible, phase, total, restartKey]);
13807
+ useEffect(() => {
13808
+ if (!visible) return;
13809
+ if (isPaused) stop();
13810
+ else start();
13811
+ }, [isPaused, visible, start, stop]);
13812
+ useEffect(() => {
13813
+ actionUsed.current = false;
13814
+ }, [restartKey]);
13815
+ if (!visible) return null;
13816
+ const icon = variantIcons[variant];
13817
+ const pressAction = () => {
13818
+ if (!action || actionUsed.current) return;
13819
+ actionUsed.current = true;
13820
+ if (onAction) onAction();
13821
+ else {
13822
+ void action.onClick();
13823
+ onDismissRef.current();
13824
+ }
13825
+ };
13826
+ const card = /* @__PURE__ */ jsxs(
13827
+ "div",
13828
+ {
13829
+ "data-toast": "",
13830
+ "data-variant": variant,
13831
+ role: inline ? void 0 : variant === "error" ? "alert" : "status",
13832
+ "aria-live": inline ? void 0 : variant === "error" ? "assertive" : "polite",
13833
+ "data-mode": inline ? mode : void 0,
13834
+ className: cn(
13835
+ "flex items-center gap-ds-3 px-ds-4 py-ds-3",
13836
+ isCentered ? "min-w-[360px] max-w-[560px]" : "min-w-[280px] max-w-[420px]",
13837
+ "border-[length:var(--ds-border-width)]",
13838
+ GLASS_DROPDOWN,
13839
+ "rounded-[var(--ds-radius-md)]",
13840
+ "text-sm text-[var(--ds-text-primary)]",
13841
+ variantStyles4[variant]
13842
+ ),
13843
+ children: [
13844
+ /* @__PURE__ */ jsx(Icon, { name: icon.name, size: 18, className: cn("shrink-0", icon.className) }),
13845
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: message }),
13846
+ action && /* @__PURE__ */ jsx(
13847
+ "button",
13848
+ {
13849
+ type: "button",
13850
+ onClick: pressAction,
13851
+ className: "shrink-0 text-xs font-semibold text-[var(--ds-accent-primary)] hover:underline cursor-pointer",
13852
+ children: action.label
13853
+ }
13854
+ ),
13855
+ /* @__PURE__ */ jsx(
13856
+ "button",
13857
+ {
13858
+ type: "button",
13859
+ onClick: onDismiss,
13860
+ "aria-label": "Dismiss notification",
13861
+ className: "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer shrink-0",
13862
+ children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 16 })
13863
+ }
13864
+ )
13865
+ ]
13866
+ }
13867
+ );
13868
+ if (inline) return card;
13869
+ return createPortal(
13870
+ /* @__PURE__ */ jsx(
13871
+ "div",
13872
+ {
13873
+ "data-mode": mode,
13874
+ onMouseEnter: pauseOnHover ? () => setHovered(true) : void 0,
13875
+ onMouseLeave: pauseOnHover ? () => setHovered(false) : void 0,
13876
+ className: cn(
13877
+ "fixed z-[var(--ds-z-toast)]",
13878
+ isMobile ? "bottom-ds-4 left-ds-4 right-ds-4" : TOAST_POSITION_CLASSES[position]
13879
+ ),
13880
+ children: card
13881
+ }
13882
+ ),
13883
+ container ?? document.body
13884
+ );
13885
+ }
13886
+ var ToastContext = createContext(null);
13887
+ var ToastLedgerCtx = createContext(null);
13888
+ var BottomNavPresenceCtx = createContext(null);
13889
+ function useToast() {
13890
+ const ctx = useContext(ToastContext);
13891
+ if (!ctx) {
13892
+ 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.");
13893
+ }
13894
+ return ctx;
13895
+ }
13896
+ function useToastOptional() {
13897
+ return useContext(ToastContext);
13898
+ }
13899
+ var toastSeq = 0;
13900
+ var heardSeq = 0;
13901
+ var mergeable = (e) => !e.action && e.variant !== "error";
13902
+ function fill(entries, slots) {
13903
+ let shown = entries.filter((e) => e.shown).length;
13904
+ if (shown >= slots || shown === entries.length) return entries;
13905
+ const next = [...entries];
13906
+ while (shown < slots) {
13907
+ const urgent = next.findIndex((e) => !e.shown && !mergeable(e));
13908
+ const at = urgent !== -1 ? urgent : next.findIndex((e) => !e.shown);
13909
+ if (at === -1) break;
13910
+ next[at] = { ...next[at], shown: true };
13911
+ shown++;
13912
+ }
13913
+ return next;
13914
+ }
13915
+ function ToastProvider({ children, maxVisible = 3, position = "bottom-right", duration = 5e3, mode, pauseOnHover = true, coalesce = true }) {
13916
+ const slots = Math.max(1, Math.floor(Number.isFinite(maxVisible) ? maxVisible : 1));
13917
+ const [list, setList] = useState([]);
13918
+ const entries = useRef([]);
13919
+ const isMobile = useBreakpoint("sm");
13920
+ const createdIds = useRef([]);
13921
+ const [heard, setHeard] = useState([]);
13922
+ const [hovered, setHovered] = useState(false);
13923
+ const [focusInside, setFocusInside] = useState(false);
13924
+ const paused = pauseOnHover && hovered || focusInside;
13925
+ const stackRef = useRef(null);
13926
+ const returnTo = useRef(null);
13927
+ const answering = useRef([]);
13928
+ const viaKey = useRef(false);
13929
+ const refocus = useRef(false);
13930
+ const pendingHeard = useRef(/* @__PURE__ */ new Map());
13931
+ const heardTimer = useRef(void 0);
13932
+ const [bottomNavs, setBottomNavs] = useState(0);
13933
+ const registerBottomNav = useCallback(() => {
13934
+ setBottomNavs((n) => n + 1);
13935
+ let gone = false;
13936
+ return () => {
13937
+ if (gone) return;
13938
+ gone = true;
13939
+ setBottomNavs((n) => n - 1);
13940
+ };
13941
+ }, []);
13942
+ const commit = useCallback((next) => {
13943
+ entries.current = fill(next, slots);
13944
+ setList(entries.current);
13945
+ }, [slots]);
13946
+ const [slotsSeen, setSlotsSeen] = useState(slots);
13947
+ if (slots !== slotsSeen) {
13948
+ setSlotsSeen(slots);
13949
+ let kept = 0;
13950
+ setList(fill(list.map((e) => e.shown && ++kept > slots ? { ...e, shown: false } : e), slots));
13951
+ }
13952
+ useEffect(() => {
13953
+ entries.current = list;
13954
+ }, [list]);
13955
+ const announce = useCallback((entry) => {
13956
+ pendingHeard.current.set(entry.id, entry);
13957
+ if (heardTimer.current) return;
13958
+ heardTimer.current = setTimeout(() => {
13959
+ heardTimer.current = void 0;
13960
+ const batch = [...pendingHeard.current.values()];
13961
+ pendingHeard.current.clear();
13962
+ setHeard((list2) => [...list2, ...batch.map((e) => ({
13963
+ id: `heard-${++heardSeq}`,
13964
+ assertive: e.variant === "error",
13965
+ node: /* @__PURE__ */ jsxs(Fragment, { children: [
13966
+ e.message,
13967
+ e.action ? ` Press F8 to reach ${e.action.label}.` : ""
13968
+ ] })
13969
+ }))].slice(-5));
13970
+ }, 0);
13971
+ }, []);
13972
+ useEffect(() => () => {
13973
+ if (heardTimer.current) clearTimeout(heardTimer.current);
13974
+ }, []);
13975
+ const leaving = useCallback(() => {
13976
+ if (!stackRef.current?.contains(document.activeElement)) return;
13977
+ refocus.current = true;
13978
+ setFocusInside(false);
13979
+ }, []);
13980
+ const remove = useCallback((id) => {
13981
+ leaving();
13982
+ commit(entries.current.filter((t) => t.id !== id));
13983
+ }, [commit, leaving]);
13984
+ const dismissAll = useCallback(() => {
13985
+ leaving();
13986
+ commit([]);
13987
+ }, [commit, leaving]);
13988
+ const update = useCallback((id, message, options = {}) => {
13989
+ const list2 = entries.current;
13990
+ const at = list2.findIndex((t2) => t2.id === id);
13991
+ if (at === -1) return;
13992
+ const t = list2[at];
13993
+ const next = [...list2];
13994
+ 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 };
13995
+ commit(next);
13996
+ announce(next[at]);
13997
+ }, [commit, announce]);
13998
+ const toast = useCallback((message, options = {}) => {
13999
+ const list2 = entries.current;
14000
+ const base = { message, variant: options.variant ?? "default", duration: options.duration ?? duration, action: options.action, actionWindow: options.actionWindow, key: options.key };
14001
+ if (options.id) {
14002
+ const at = list2.findIndex((t) => t.id === options.id);
14003
+ if (at !== -1) {
14004
+ const next = [...list2];
14005
+ next[at] = { ...list2[at], ...base, version: list2[at].version + 1 };
14006
+ createdIds.current.push(options.id);
14007
+ commit(next);
14008
+ announce(next[at]);
14009
+ return options.id;
14010
+ }
14011
+ }
14012
+ while (answering.current.length) {
14013
+ const answered = answering.current.shift();
14014
+ const at = list2.findIndex((t) => t.id === answered);
14015
+ if (at === -1) continue;
14016
+ const next = [...list2];
14017
+ next[at] = { ...list2[at], ...base, version: list2[at].version + 1 };
14018
+ createdIds.current.push(answered);
14019
+ commit(next);
14020
+ announce(next[at]);
14021
+ return answered;
14022
+ }
14023
+ if (coalesce && base.key && mergeable(base)) {
14024
+ const at = list2.findIndex((t) => t.key === base.key && mergeable(t));
14025
+ if (at !== -1) {
14026
+ const next = [...list2];
14027
+ next[at] = { ...list2[at], ...base, version: list2[at].version + 1 };
14028
+ createdIds.current.push(list2[at].id);
14029
+ commit(next);
14030
+ announce(next[at]);
14031
+ return list2[at].id;
14032
+ }
14033
+ }
14034
+ const id = options.id ?? `toast-${++toastSeq}`;
14035
+ const entry = { id, ...base, shown: false, version: 0 };
14036
+ createdIds.current.push(id);
14037
+ commit([...list2, entry]);
14038
+ announce(entry);
14039
+ return id;
14040
+ }, [duration, coalesce, commit, announce]);
14041
+ const api = useMemo(() => ({
14042
+ toast,
14043
+ success: (m, o) => toast(m, { ...o, variant: "success" }),
14044
+ warning: (m, o) => toast(m, { ...o, variant: "warning" }),
14045
+ error: (m, o) => toast(m, { ...o, variant: "error" }),
14046
+ update,
14047
+ dismiss: remove,
14048
+ dismissAll,
14049
+ promise: async (promise, messages, options = {}) => {
14050
+ const id = toast(messages.loading, { ...options, variant: "default", duration: Infinity });
14051
+ try {
14052
+ const value = await promise;
14053
+ update(id, typeof messages.success === "function" ? messages.success(value) : messages.success, { variant: "success", duration });
14054
+ return value;
14055
+ } catch (err) {
14056
+ update(id, typeof messages.error === "function" ? messages.error(err) : messages.error, { variant: "error", duration });
14057
+ throw err;
14058
+ }
14059
+ }
14060
+ }), [toast, update, remove, dismissAll, duration]);
14061
+ const ledger = useMemo(() => ({
14062
+ created: () => createdIds.current.length,
14063
+ latestSince: (count) => {
14064
+ if (createdIds.current.length <= count) return void 0;
14065
+ const id = createdIds.current[createdIds.current.length - 1];
14066
+ const entry = entries.current.find((t) => t.id === id);
14067
+ return entry ? { id, message: entry.message } : void 0;
14068
+ }
14069
+ }), []);
14070
+ const pressAction = useCallback((id) => {
14071
+ const entry = entries.current.find((t) => t.id === id);
14072
+ if (!entry?.action) return;
14073
+ const run = entry.action.onClick;
14074
+ leaving();
14075
+ commit(entries.current.map((t) => t.id === id ? { ...t, action: void 0, actionWindow: void 0, version: t.version + 1 } : t));
14076
+ answering.current.push(id);
14077
+ const done = () => {
14078
+ const waiting2 = answering.current.indexOf(id);
14079
+ if (waiting2 === -1) return;
14080
+ answering.current.splice(waiting2, 1);
14081
+ remove(id);
14082
+ };
14083
+ let result;
14084
+ try {
14085
+ result = run();
14086
+ } catch {
14087
+ result = void 0;
14088
+ }
14089
+ if (result && typeof result.then === "function") result.then(done, done);
14090
+ else done();
14091
+ }, [commit, leaving, remove]);
14092
+ const lapse = useCallback((id) => {
14093
+ const list2 = entries.current;
14094
+ const at = list2.findIndex((t) => t.id === id);
14095
+ if (at === -1) return;
14096
+ const next = [...list2];
14097
+ next[at] = { ...list2[at], action: void 0, actionWindow: void 0, version: list2[at].version + 1 };
14098
+ commit(next);
14099
+ }, [commit]);
14100
+ useEffect(() => {
14101
+ const onKey = (e) => {
14102
+ if (e.key !== "F8" || e.defaultPrevented) return;
14103
+ const stack = stackRef.current;
14104
+ if (!stack) return;
14105
+ e.preventDefault();
14106
+ const active = document.activeElement;
14107
+ if (!stack.contains(active)) {
14108
+ returnTo.current = active instanceof HTMLElement && active !== document.body ? active : null;
14109
+ viaKey.current = true;
14110
+ }
14111
+ (stack.querySelector("button") ?? stack).focus();
14112
+ viaKey.current = false;
14113
+ };
14114
+ document.addEventListener("keydown", onKey);
14115
+ return () => document.removeEventListener("keydown", onKey);
14116
+ }, []);
14117
+ useEffect(() => {
14118
+ if (!refocus.current) return;
14119
+ refocus.current = false;
14120
+ const back = returnTo.current;
14121
+ const next = stackRef.current?.querySelector("button");
14122
+ const main = document.querySelector("main");
14123
+ if (back?.isConnected) back.focus();
14124
+ else if (next) next.focus();
14125
+ else if (main) {
14126
+ if (!main.hasAttribute("tabindex")) {
14127
+ main.setAttribute("tabindex", "-1");
14128
+ main.addEventListener("blur", () => main.removeAttribute("tabindex"), { once: true });
14129
+ }
14130
+ main.focus();
14131
+ }
14132
+ }, [list]);
14133
+ const shown = list.filter((t) => t.shown);
14134
+ const waiting = list.length - shown.length;
14135
+ const isTop = position.startsWith("top");
14136
+ const hasBottomNav = isMobile && bottomNavs > 0;
14137
+ return /* @__PURE__ */ jsx(ToastContext.Provider, { value: api, children: /* @__PURE__ */ jsxs(ToastLedgerCtx.Provider, { value: ledger, children: [
14138
+ /* @__PURE__ */ jsx(BottomNavPresenceCtx.Provider, { value: registerBottomNav, children }),
14139
+ /* @__PURE__ */ jsx("div", { className: "sr-only", role: "status", "aria-live": "polite", "data-toast-announcer": "polite", children: heard.filter((h) => !h.assertive).map((h) => /* @__PURE__ */ jsx("p", { children: h.node }, h.id)) }),
14140
+ /* @__PURE__ */ jsx("div", { className: "sr-only", role: "alert", "aria-live": "assertive", "data-toast-announcer": "assertive", children: heard.filter((h) => h.assertive).map((h) => /* @__PURE__ */ jsx("p", { children: h.node }, h.id)) }),
14141
+ shown.length > 0 && createPortal(
14142
+ /* @__PURE__ */ jsxs(
14143
+ "div",
14144
+ {
14145
+ ref: stackRef,
14146
+ role: "region",
14147
+ "aria-label": "Notifications",
14148
+ tabIndex: -1,
14149
+ "data-mode": mode,
14150
+ "data-toast-stack": "",
14151
+ onMouseEnter: () => setHovered(true),
14152
+ onMouseLeave: () => setHovered(false),
14153
+ onFocusCapture: (e) => {
14154
+ setFocusInside(true);
14155
+ const from = e.relatedTarget;
14156
+ if (viaKey.current || from instanceof Node && e.currentTarget.contains(from)) return;
14157
+ returnTo.current = from instanceof HTMLElement ? from : null;
14158
+ },
14159
+ onBlurCapture: (e) => {
14160
+ if (!e.currentTarget.contains(e.relatedTarget)) setFocusInside(false);
14161
+ },
14162
+ className: cn(
14163
+ "fixed z-[var(--ds-z-toast)] flex gap-ds-2 pointer-events-none outline-none",
14164
+ isTop ? "flex-col" : "flex-col-reverse",
14165
+ isMobile ? cn("left-ds-4 right-ds-4", !hasBottomNav && "bottom-ds-4") : TOAST_POSITION_CLASSES[position]
14166
+ ),
14167
+ style: hasBottomNav ? { bottom: "calc(var(--ds-space-12) + var(--ds-space-2) + var(--ds-space-4) + env(safe-area-inset-bottom))" } : void 0,
14168
+ children: [
14169
+ shown.map((t) => /* @__PURE__ */ jsx("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx(
14170
+ Toast,
14171
+ {
14172
+ inline: true,
14173
+ visible: true,
14174
+ variant: t.variant,
14175
+ message: t.message,
14176
+ duration: t.duration,
14177
+ action: t.action,
14178
+ actionWindow: t.actionWindow,
14179
+ mode,
14180
+ paused,
14181
+ restartKey: t.version,
14182
+ onAction: () => pressAction(t.id),
14183
+ onActionLapse: () => lapse(t.id),
14184
+ onDismiss: () => remove(t.id)
14185
+ }
14186
+ ) }, t.id)),
14187
+ (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: [
14188
+ 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: [
14189
+ "+",
14190
+ waiting,
14191
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: " more waiting" })
14192
+ ] }),
14193
+ /* @__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" })
14194
+ ] })
14195
+ ]
14196
+ }
14197
+ ),
14198
+ document.body
14199
+ )
14200
+ ] }) });
14201
+ }
14202
+ function BottomNav({ items, onItemClick, maxVisible = 5, className, ref }) {
14203
+ const registerWithToasts = useContext(BottomNavPresenceCtx);
14204
+ useEffect(() => registerWithToasts?.(), [registerWithToasts]);
14205
+ const needsOverflow = items.length > maxVisible;
14206
+ const visibleItems = needsOverflow ? items.slice(0, maxVisible - 1) : items;
14207
+ const overflowItems = needsOverflow ? items.slice(maxVisible - 1) : [];
14208
+ const overflowHasActive = overflowItems.some((it) => it.active);
14209
+ const [overflowOpen, setOverflowOpen] = useState(false);
14210
+ const menuRef = useRef(null);
14211
+ const closeOverflow = useCallback(() => setOverflowOpen(false), []);
14212
+ useEffect(() => {
14213
+ if (!overflowOpen) return;
14214
+ function handleClick(e) {
14215
+ if (menuRef.current && !menuRef.current.contains(e.target)) closeOverflow();
14216
+ }
14217
+ document.addEventListener("mousedown", handleClick);
13515
14218
  return () => document.removeEventListener("mousedown", handleClick);
13516
14219
  }, [overflowOpen, closeOverflow]);
13517
14220
  return /* @__PURE__ */ jsxs(
@@ -13533,6 +14236,7 @@ function BottomNav({ items, onItemClick, maxVisible = 5, className, ref }) {
13533
14236
  /* @__PURE__ */ jsxs(
13534
14237
  "button",
13535
14238
  {
14239
+ type: "button",
13536
14240
  onClick: () => setOverflowOpen(!overflowOpen),
13537
14241
  "aria-expanded": overflowOpen,
13538
14242
  "aria-haspopup": "true",
@@ -13560,6 +14264,7 @@ function BottomNav({ items, onItemClick, maxVisible = 5, className, ref }) {
13560
14264
  children: overflowItems.map((item, i) => /* @__PURE__ */ jsxs(
13561
14265
  "button",
13562
14266
  {
14267
+ type: "button",
13563
14268
  role: "menuitem",
13564
14269
  onClick: () => {
13565
14270
  onItemClick?.(item, maxVisible - 1 + i);
@@ -13589,6 +14294,7 @@ function NavButton({ item, onClick }) {
13589
14294
  return /* @__PURE__ */ jsxs(
13590
14295
  "button",
13591
14296
  {
14297
+ type: "button",
13592
14298
  onClick,
13593
14299
  className: cn(
13594
14300
  "flex flex-col items-center justify-center gap-ds-0.5 flex-1 h-full",
@@ -14991,6 +15697,7 @@ function NavMenu({
14991
15697
  /* @__PURE__ */ jsxs(
14992
15698
  "button",
14993
15699
  {
15700
+ type: "button",
14994
15701
  ref: triggerRef,
14995
15702
  onClick: () => setOpen(!open),
14996
15703
  "aria-expanded": open,
@@ -15187,7 +15894,8 @@ var sizeClasses = {
15187
15894
  full: "max-w-[calc(100vw-2rem)]"
15188
15895
  };
15189
15896
  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);
15897
+ const { marked, mark } = useOverlayMark(open);
15898
+ const guard = useDirtyGuard(dirty ?? marked, onCloseProp, onDiscard);
15191
15899
  const onClose = guard.requestClose;
15192
15900
  const titleId = useId();
15193
15901
  const descriptionId = useId();
@@ -15324,7 +16032,7 @@ function Dialog({ open, onClose: onCloseProp, title, description, children, foot
15324
16032
  }
15325
16033
  ),
15326
16034
  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 }) })
16035
+ children && /* @__PURE__ */ jsx("div", { className: "mt-ds-4", children: /* @__PURE__ */ jsx(OverlayMarkCtx, { value: mark, children: /* @__PURE__ */ jsx(OverlayCloseCtx, { value: onClose, children }) }) })
15328
16036
  ] })
15329
16037
  ] }) }),
15330
16038
  /* @__PURE__ */ jsx(DiscardConfirm, { open: guard.confirming, onKeep: guard.keep, onDiscard: guard.discard }),
@@ -15350,6 +16058,7 @@ function HamburgerButton({ onClick, open, className: cls }) {
15350
16058
  return /* @__PURE__ */ jsx(
15351
16059
  "button",
15352
16060
  {
16061
+ type: "button",
15353
16062
  onClick,
15354
16063
  "aria-label": open ? "Close navigation menu" : "Open navigation menu",
15355
16064
  "aria-expanded": open ?? void 0,
@@ -15652,6 +16361,7 @@ function MobileMenuItem({
15652
16361
  return /* @__PURE__ */ jsxs(
15653
16362
  "button",
15654
16363
  {
16364
+ type: "button",
15655
16365
  onClick,
15656
16366
  className: cn(
15657
16367
  "flex items-center gap-ds-3 px-ds-3 py-ds-3 text-sm relative cursor-pointer w-full text-left",
@@ -15676,6 +16386,7 @@ function MobileMenuItem({
15676
16386
  return /* @__PURE__ */ jsxs(
15677
16387
  "button",
15678
16388
  {
16389
+ type: "button",
15679
16390
  onClick,
15680
16391
  className: cn(
15681
16392
  "flex items-center gap-ds-3 px-ds-3 py-ds-2.5 text-sm cursor-pointer w-full text-left",
@@ -15693,6 +16404,7 @@ function MobileMenuItem({
15693
16404
  return /* @__PURE__ */ jsxs(
15694
16405
  "button",
15695
16406
  {
16407
+ type: "button",
15696
16408
  onClick,
15697
16409
  className: cn(
15698
16410
  "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 +16421,7 @@ function MobileMenuItem({
15709
16421
  return /* @__PURE__ */ jsxs(
15710
16422
  "button",
15711
16423
  {
16424
+ type: "button",
15712
16425
  onClick,
15713
16426
  className: cn(
15714
16427
  "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 +16583,7 @@ function CommandPalette({ open, onOpenChange, commands, placeholder = "Type a co
15870
16583
  return /* @__PURE__ */ jsxs(
15871
16584
  "button",
15872
16585
  {
16586
+ type: "button",
15873
16587
  id: `${listboxId}-opt-${myIdx}`,
15874
16588
  role: "option",
15875
16589
  tabIndex: -1,
@@ -16058,6 +16772,7 @@ function ContextMenu({ items, children, className, ref, container }) {
16058
16772
  return /* @__PURE__ */ jsxs(
16059
16773
  "button",
16060
16774
  {
16775
+ type: "button",
16061
16776
  role: "menuitem",
16062
16777
  disabled: item.disabled,
16063
16778
  onClick: () => {
@@ -16168,6 +16883,7 @@ function NotificationCenter({
16168
16883
  /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-3", children: unreadCount > 0 && /* @__PURE__ */ jsx(
16169
16884
  "button",
16170
16885
  {
16886
+ type: "button",
16171
16887
  onClick: onMarkAllRead,
16172
16888
  className: "text-sm text-[var(--ds-accent-primary)] hover:underline cursor-pointer",
16173
16889
  children: "Mark all read"
@@ -16178,6 +16894,7 @@ function NotificationCenter({
16178
16894
  /* @__PURE__ */ jsx(
16179
16895
  "button",
16180
16896
  {
16897
+ type: "button",
16181
16898
  onClick: () => setFilter("all"),
16182
16899
  className: cn(
16183
16900
  "px-ds-3 py-ds-1.5 text-sm rounded-[var(--ds-radius-md)] transition-colors cursor-pointer",
@@ -16189,6 +16906,7 @@ function NotificationCenter({
16189
16906
  /* @__PURE__ */ jsxs(
16190
16907
  "button",
16191
16908
  {
16909
+ type: "button",
16192
16910
  onClick: () => setFilter("unread"),
16193
16911
  className: cn(
16194
16912
  "px-ds-3 py-ds-1.5 text-sm rounded-[var(--ds-radius-md)] transition-colors cursor-pointer",
@@ -16238,6 +16956,7 @@ function NotificationCenter({
16238
16956
  !n.read && onMarkRead && /* @__PURE__ */ jsx(
16239
16957
  "button",
16240
16958
  {
16959
+ type: "button",
16241
16960
  onClick: () => onMarkRead(n.id),
16242
16961
  "aria-label": "Mark as read",
16243
16962
  className: "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer p-ds-0.5",
@@ -16247,6 +16966,7 @@ function NotificationCenter({
16247
16966
  onDismiss && /* @__PURE__ */ jsx(
16248
16967
  "button",
16249
16968
  {
16969
+ type: "button",
16250
16970
  onClick: () => onDismiss(n.id),
16251
16971
  "aria-label": "Dismiss",
16252
16972
  className: "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer p-ds-0.5",
@@ -16265,6 +16985,7 @@ function NotificationCenter({
16265
16985
  /* @__PURE__ */ jsxs(
16266
16986
  "button",
16267
16987
  {
16988
+ type: "button",
16268
16989
  ref: triggerRef,
16269
16990
  onClick: () => setOpen(!open),
16270
16991
  "aria-label": `Notifications${unreadCount ? ` (${unreadCount} unread)` : ""}`,
@@ -16304,6 +17025,7 @@ function NotificationCenter({
16304
17025
  unreadCount > 0 && /* @__PURE__ */ jsx(
16305
17026
  "button",
16306
17027
  {
17028
+ type: "button",
16307
17029
  onClick: onMarkAllRead,
16308
17030
  className: "text-xs text-[var(--ds-accent-primary)] hover:underline cursor-pointer",
16309
17031
  children: "Mark all read"
@@ -16348,6 +17070,7 @@ function NotificationCenter({
16348
17070
  !n.read && onMarkRead && /* @__PURE__ */ jsx(
16349
17071
  "button",
16350
17072
  {
17073
+ type: "button",
16351
17074
  onClick: () => onMarkRead(n.id),
16352
17075
  "aria-label": "Mark as read",
16353
17076
  className: "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer p-ds-0.5",
@@ -16357,6 +17080,7 @@ function NotificationCenter({
16357
17080
  onDismiss && /* @__PURE__ */ jsx(
16358
17081
  "button",
16359
17082
  {
17083
+ type: "button",
16360
17084
  onClick: () => onDismiss(n.id),
16361
17085
  "aria-label": "Dismiss",
16362
17086
  className: "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer p-ds-0.5",
@@ -16520,6 +17244,7 @@ function Tour({ steps, open, onClose, onComplete, container }) {
16520
17244
  /* @__PURE__ */ jsx(
16521
17245
  "button",
16522
17246
  {
17247
+ type: "button",
16523
17248
  onClick: onClose,
16524
17249
  className: "shrink-0 text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer -mt-ds-0.5",
16525
17250
  "aria-label": "Close tour",
@@ -16539,6 +17264,7 @@ function Tour({ steps, open, onClose, onComplete, container }) {
16539
17264
  current > 0 && /* @__PURE__ */ jsx(
16540
17265
  "button",
16541
17266
  {
17267
+ type: "button",
16542
17268
  onClick: handlePrev,
16543
17269
  className: cn(
16544
17270
  "px-ds-3 py-ds-1.5 text-xs font-medium rounded-[var(--ds-radius-sm)]",
@@ -16552,6 +17278,7 @@ function Tour({ steps, open, onClose, onComplete, container }) {
16552
17278
  /* @__PURE__ */ jsx(
16553
17279
  "button",
16554
17280
  {
17281
+ type: "button",
16555
17282
  ref: nextRef,
16556
17283
  onClick: handleNext,
16557
17284
  className: cn(
@@ -16600,223 +17327,6 @@ function getPopoverPosition(rect, placement, padding) {
16600
17327
  top = Math.max(8, Math.min(top, window.innerHeight - popH - 8));
16601
17328
  return { top, left };
16602
17329
  }
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
17330
  function firstKey(item) {
16821
17331
  if (item !== null && typeof item === "object") return "id" in item ? item.id : void 0;
16822
17332
  return item;
@@ -16879,9 +17389,10 @@ function usePagination(list, options = {}) {
16879
17389
  };
16880
17390
  }
16881
17391
  function useDestructiveAction(options) {
16882
- const { action, title, description, confirmLabel, typeToConfirm, onConfirm, onUndo, undoMessage, severity } = options;
17392
+ const { action, title, description, confirmLabel, typeToConfirm, onConfirm, onUndo, doneMessage, undoMessage, restoredMessage, severity } = options;
16883
17393
  const preferences = useShellPreferences();
16884
17394
  const toast = useToastOptional();
17395
+ const ledger = useContext(ToastLedgerCtx);
16885
17396
  const [open, setOpen] = useState(false);
16886
17397
  const [typed, setTyped] = useState("");
16887
17398
  const [busy, setBusy] = useState(false);
@@ -16898,20 +17409,32 @@ function useDestructiveAction(options) {
16898
17409
  setTyped("");
16899
17410
  };
16900
17411
  const failureMessage = (err) => err instanceof Error && err.message ? err.message : `${title.replace(/\?$/, "")} failed. Try again.`;
17412
+ const done = doneMessage ?? undoMessage ?? pastTenseOf(label);
16901
17413
  const confirm = async () => {
16902
17414
  setBusy(true);
17415
+ const before = ledger?.created() ?? 0;
16903
17416
  try {
16904
17417
  await onConfirm();
16905
17418
  close();
17419
+ if (toast && (ledger?.created() ?? 0) === before) toast.success(done);
16906
17420
  } catch (err) {
16907
17421
  toast?.error(failureMessage(err));
16908
17422
  } finally {
16909
17423
  setBusy(false);
16910
17424
  }
16911
17425
  };
17426
+ const undo = async () => {
17427
+ try {
17428
+ await onUndo();
17429
+ toast?.success(restoredMessage ?? "Restored");
17430
+ } catch (err) {
17431
+ toast?.error(err instanceof Error && err.message ? err.message : "Undo didn't work. Try again.");
17432
+ }
17433
+ };
16912
17434
  const run = () => {
16913
17435
  if (undoable) {
16914
17436
  void (async () => {
17437
+ const before = ledger?.created() ?? 0;
16915
17438
  try {
16916
17439
  await onConfirm();
16917
17440
  } catch (err) {
@@ -16919,7 +17442,10 @@ function useDestructiveAction(options) {
16919
17442
  return;
16920
17443
  }
16921
17444
  const seconds = preferences.undoWindowSeconds ?? 5;
16922
- toast.success(undoMessage ?? `${title.replace(/\?$/, "")} done`, { duration: seconds * 1e3, action: { label: "Undo", onClick: onUndo } });
17445
+ const undoAction = { label: "Undo", onClick: () => undo() };
17446
+ const pages = (ledger?.created() ?? 0) > before ? ledger?.latestSince(before) : void 0;
17447
+ if (pages) toast.update(pages.id, pages.message, { variant: "success", action: undoAction, actionWindow: seconds * 1e3 });
17448
+ else toast.success(done, { action: undoAction, actionWindow: seconds * 1e3 });
16923
17449
  })();
16924
17450
  return;
16925
17451
  }
@@ -16947,6 +17473,61 @@ function useDestructiveAction(options) {
16947
17473
  ) : null;
16948
17474
  return { run, element, open };
16949
17475
  }
17476
+ var PAST_TENSE = {
17477
+ delete: "Deleted",
17478
+ remove: "Removed",
17479
+ archive: "Archived",
17480
+ leave: "Left",
17481
+ revoke: "Revoked",
17482
+ disconnect: "Disconnected",
17483
+ cancel: "Cancelled",
17484
+ reset: "Reset",
17485
+ clear: "Cleared",
17486
+ discard: "Discarded",
17487
+ unpublish: "Unpublished",
17488
+ deactivate: "Deactivated",
17489
+ disable: "Disabled",
17490
+ uninstall: "Uninstalled",
17491
+ unsubscribe: "Unsubscribed",
17492
+ block: "Blocked",
17493
+ ban: "Banned",
17494
+ suspend: "Suspended",
17495
+ close: "Closed",
17496
+ end: "Ended",
17497
+ stop: "Stopped",
17498
+ "sign out": "Signed out",
17499
+ "log out": "Logged out",
17500
+ transfer: "Transferred",
17501
+ merge: "Merged",
17502
+ overwrite: "Overwritten",
17503
+ replace: "Replaced",
17504
+ erase: "Erased",
17505
+ purge: "Purged",
17506
+ wipe: "Wiped",
17507
+ drop: "Dropped",
17508
+ detach: "Detached",
17509
+ unlink: "Unlinked",
17510
+ expire: "Expired",
17511
+ reject: "Rejected",
17512
+ decline: "Declined"
17513
+ };
17514
+ var warned2 = /* @__PURE__ */ new Set();
17515
+ function pastTenseOf(label) {
17516
+ const words = label.trim().toLowerCase().split(/\s+/);
17517
+ const found = PAST_TENSE[words.slice(0, 2).join(" ")] ?? PAST_TENSE[words[0] ?? ""];
17518
+ if (found) return found;
17519
+ let dev = false;
17520
+ try {
17521
+ dev = process.env.NODE_ENV !== "production";
17522
+ } catch {
17523
+ dev = false;
17524
+ }
17525
+ if (dev && !warned2.has(label)) {
17526
+ warned2.add(label);
17527
+ console.warn(`useDestructiveAction: "${label}" isn't a verb the hook knows; pass doneMessage so the confirmation says what happened.`);
17528
+ }
17529
+ return "Done";
17530
+ }
16950
17531
  var SectionHeading = forwardRef(function SectionHeading2({
16951
17532
  title,
16952
17533
  description,
@@ -17087,6 +17668,6 @@ var TextOverflow = forwardRef(function TextOverflow2({
17087
17668
  return /* @__PURE__ */ jsx(Tooltip, { content: resolvedTooltipContent, children: content });
17088
17669
  });
17089
17670
 
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 };
17671
+ 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
17672
  //# sourceMappingURL=index.js.map
17092
17673
  //# sourceMappingURL=index.js.map