gridsmith-ui 0.30.2 → 0.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/CHANGELOG.md +71 -1
  2. package/dist/charts.d.ts +114 -1
  3. package/dist/charts.js +1113 -206
  4. package/dist/charts.js.map +1 -1
  5. package/dist/chat.js +20 -12
  6. package/dist/chat.js.map +1 -1
  7. package/dist/{chunk-HVGHGBKV.js → chunk-2TCCSKL3.js} +6 -7
  8. package/dist/chunk-2TCCSKL3.js.map +1 -0
  9. package/dist/{chunk-L6HWACK7.js → chunk-53F7GVBQ.js} +6 -3
  10. package/dist/chunk-53F7GVBQ.js.map +1 -0
  11. package/dist/chunk-BHTAVJIG.js +285 -0
  12. package/dist/chunk-BHTAVJIG.js.map +1 -0
  13. package/dist/{chunk-WWN6CQKI.js → chunk-DR6WEI3A.js} +60 -7
  14. package/dist/chunk-DR6WEI3A.js.map +1 -0
  15. package/dist/{chunk-4HJDOZ2M.js → chunk-EZ3ZGFGA.js} +203 -34
  16. package/dist/chunk-EZ3ZGFGA.js.map +1 -0
  17. package/dist/{chunk-MIUSSYVE.js → chunk-HQFTP7Y5.js} +27 -52
  18. package/dist/chunk-HQFTP7Y5.js.map +1 -0
  19. package/dist/{chunk-SIWERRP2.js → chunk-I6JJ7KKP.js} +14 -15
  20. package/dist/chunk-I6JJ7KKP.js.map +1 -0
  21. package/dist/chunk-IOHGE4EW.js +755 -0
  22. package/dist/chunk-IOHGE4EW.js.map +1 -0
  23. package/dist/{chunk-USHW523V.js → chunk-IZW5XHKA.js} +73 -699
  24. package/dist/chunk-IZW5XHKA.js.map +1 -0
  25. package/dist/{chunk-ZASGFL5D.js → chunk-K3NAFQ4S.js} +3 -4
  26. package/dist/chunk-K3NAFQ4S.js.map +1 -0
  27. package/dist/{chunk-ACLKDE5W.js → chunk-KSJTMSYA.js} +23 -11
  28. package/dist/chunk-KSJTMSYA.js.map +1 -0
  29. package/dist/{chunk-SQHKC4HA.js → chunk-NDL2JGQ7.js} +8 -7
  30. package/dist/chunk-NDL2JGQ7.js.map +1 -0
  31. package/dist/{chunk-TYNDXMBV.js → chunk-QOMP4D6V.js} +3 -3
  32. package/dist/{chunk-TYNDXMBV.js.map → chunk-QOMP4D6V.js.map} +1 -1
  33. package/dist/{chunk-BZRZENBS.js → chunk-SLGGZ3MU.js} +6 -6
  34. package/dist/{chunk-BZRZENBS.js.map → chunk-SLGGZ3MU.js.map} +1 -1
  35. package/dist/{chunk-K6IDOCGE.js → chunk-TH272DIC.js} +10 -10
  36. package/dist/{chunk-K6IDOCGE.js.map → chunk-TH272DIC.js.map} +1 -1
  37. package/dist/chunk-V73LHNRL.js +51 -0
  38. package/dist/chunk-V73LHNRL.js.map +1 -0
  39. package/dist/{chunk-ZCDA74RQ.js → chunk-W3GY5ROH.js} +3 -3
  40. package/dist/{chunk-ZCDA74RQ.js.map → chunk-W3GY5ROH.js.map} +1 -1
  41. package/dist/data-grid.js +11 -12
  42. package/dist/data-grid.js.map +1 -1
  43. package/dist/editor-mentions.js +2 -3
  44. package/dist/editor-mentions.js.map +1 -1
  45. package/dist/editor.js +4 -6
  46. package/dist/editor.js.map +1 -1
  47. package/dist/icons.js +1 -1
  48. package/dist/index.d.ts +100 -16
  49. package/dist/index.js +1177 -660
  50. package/dist/index.js.map +1 -1
  51. package/dist/kanban.js +10 -11
  52. package/dist/kanban.js.map +1 -1
  53. package/dist/resizable.js +10 -11
  54. package/dist/resizable.js.map +1 -1
  55. package/dist/virtualized.js +11 -12
  56. package/dist/virtualized.js.map +1 -1
  57. package/package.json +1 -1
  58. package/dist/chunk-4HJDOZ2M.js.map +0 -1
  59. package/dist/chunk-6C2IUMY7.js +0 -46
  60. package/dist/chunk-6C2IUMY7.js.map +0 -1
  61. package/dist/chunk-7Q46JXSK.js +0 -51
  62. package/dist/chunk-7Q46JXSK.js.map +0 -1
  63. package/dist/chunk-ACLKDE5W.js.map +0 -1
  64. package/dist/chunk-HVGHGBKV.js.map +0 -1
  65. package/dist/chunk-L6HWACK7.js.map +0 -1
  66. package/dist/chunk-MIUSSYVE.js.map +0 -1
  67. package/dist/chunk-NKUCS67T.js +0 -13
  68. package/dist/chunk-NKUCS67T.js.map +0 -1
  69. package/dist/chunk-SIWERRP2.js.map +0 -1
  70. package/dist/chunk-SQHKC4HA.js.map +0 -1
  71. package/dist/chunk-USHW523V.js.map +0 -1
  72. package/dist/chunk-WWN6CQKI.js.map +0 -1
  73. package/dist/chunk-YL5KD5RC.js +0 -49
  74. package/dist/chunk-YL5KD5RC.js.map +0 -1
  75. package/dist/chunk-ZASGFL5D.js.map +0 -1
@@ -1,16 +1,13 @@
1
1
  "use client";
2
- import { useShellPreferences, PREFERENCE_HOUSE_DEFAULTS } from './chunk-TYNDXMBV.js';
2
+ import { useReportedChange, useOptionalFormContext } from './chunk-72SDAX54.js';
3
+ import { ControlSizeContext } from './chunk-HQFTP7Y5.js';
3
4
  import { useInputModality } from './chunk-7RKC5C7G.js';
4
- import { useReportedChange, useOptionalFormContext, FormChangeCtx } from './chunk-72SDAX54.js';
5
- import { ControlSizeContext, triggerType, RowsCtx, Button } from './chunk-MIUSSYVE.js';
6
- import { useTextOverflow } from './chunk-ZCDA74RQ.js';
7
- import { useRevealFocus, AutoTooltip } from './chunk-WWN6CQKI.js';
8
- import { Icon, warnOnce } from './chunk-L6HWACK7.js';
9
- import { Slot } from './chunk-7Q46JXSK.js';
10
- import { useLatest, useFocusLeave, usePopupFocus, focusAfterRemoval, usePosition, usePressMarks, focusInitial, focusAfterSelfRemoval, tabStops } from './chunk-4HJDOZ2M.js';
11
- import { cn, ERROR_HAS_FOCUS_RING, FIELD_HAS_FOCUS_RING, ERROR_FOCUS_RING, FIELD_FOCUS_RING, FOCUS_RING, ERROR_ACTIVE_RING, ACTIVE_RING, ERROR_FOCUS_WITHIN_RING, FIELD_FOCUS_WITHIN_RING, GLASS_DROPDOWN } from './chunk-YL5KD5RC.js';
12
- import { createContext, useContext, useRef, useCallback, useEffect, useId, useState, Children, isValidElement, useMemo, cloneElement, useLayoutEffect } from 'react';
13
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
5
+ import { useTextOverflow } from './chunk-W3GY5ROH.js';
6
+ import { useRevealFocus, AutoTooltip } from './chunk-DR6WEI3A.js';
7
+ import { warnNameLeftOut, Icon } from './chunk-53F7GVBQ.js';
8
+ import { cn, ERROR_HAS_FOCUS_RING, FIELD_HAS_FOCUS_RING, ERROR_FOCUS_RING, FIELD_FOCUS_RING, FOCUS_RING, useLatest, useFocusLeave, usePopupFocus, focusAfterRemoval, ERROR_ACTIVE_RING, ACTIVE_RING } from './chunk-EZ3ZGFGA.js';
9
+ import { createContext, useContext, useRef, useCallback, useEffect, useId, useState, Children, isValidElement, useLayoutEffect, useMemo, cloneElement } from 'react';
10
+ import { jsxs, jsx } from 'react/jsx-runtime';
14
11
  import { createPortal } from 'react-dom';
15
12
 
16
13
  // ../../showcase/src/lib/control-looks.ts
@@ -180,7 +177,14 @@ var TOGGLE_SIZES = {
180
177
  sm: { w: 32, h: 18, thumb: 12 },
181
178
  md: { w: 44, h: 24, thumb: 16 }
182
179
  };
183
- function Toggle({ checked, onChange, label, disabled, error = false, layout = "full", size = "md", "aria-labelledby": ariaLabelledBy, ...rest }) {
180
+ function Toggle({ checked, onChange, label, disabled, error = false, layout = "full", size = "md", "aria-label": pageLabel, "aria-labelledby": pageLabelledBy, ...rest }) {
181
+ useEffect(() => {
182
+ if (!label) return;
183
+ if (pageLabel) warnNameLeftOut("Toggle", "aria-label", pageLabel, "its own label");
184
+ if (pageLabelledBy) warnNameLeftOut("Toggle", "aria-labelledby", pageLabelledBy, "its own label");
185
+ }, [label, pageLabel, pageLabelledBy]);
186
+ const ariaLabel = label ? void 0 : pageLabel;
187
+ const ariaLabelledBy = label ? void 0 : pageLabelledBy;
184
188
  const change = useReportedChange(onChange, checked);
185
189
  const dims = TOGGLE_SIZES[size];
186
190
  const travel = dims.w - dims.thumb - 3;
@@ -190,7 +194,7 @@ function Toggle({ checked, onChange, label, disabled, error = false, layout = "f
190
194
  type: "button",
191
195
  role: "switch",
192
196
  "aria-checked": checked,
193
- "aria-label": !label && !ariaLabelledBy ? "Toggle" : void 0,
197
+ "aria-label": ariaLabel ?? (!label && !ariaLabelledBy && !rest.id ? "Toggle" : void 0),
194
198
  "aria-labelledby": ariaLabelledBy,
195
199
  "aria-invalid": error || void 0,
196
200
  disabled,
@@ -277,10 +281,24 @@ function useFieldControl(own, options = {}) {
277
281
  const required = !!own.required || own["aria-required"] === true || own["aria-required"] === "true" || !!field?.required;
278
282
  const noteId = options.requiredInDescription && required && id ? `${id}-required` : void 0;
279
283
  const ids = [own["aria-describedby"], field?.describedBy, noteId].filter(Boolean).join(" ").split(/\s+/).filter(Boolean);
284
+ const { ownLabel, component } = options;
285
+ const fieldShown = !!field && !field.labelHidden;
286
+ const shown = !!ownLabel || fieldShown;
287
+ const pageLabel = own["aria-label"], pageLabelledBy = own["aria-labelledby"];
288
+ useEffect(() => {
289
+ if (!shown) return;
290
+ const whose = ownLabel ? "its own label" : "its Field's label";
291
+ for (const [attr, value] of [["aria-label", pageLabel], ["aria-labelledby", pageLabelledBy]]) {
292
+ if (value) warnNameLeftOut(component ?? "A Field's control", attr, value, whose);
293
+ }
294
+ }, [shown, ownLabel, component, pageLabel, pageLabelledBy]);
280
295
  return {
281
296
  id,
282
- label: own["aria-label"],
283
- labelId: own["aria-labelledby"] ?? field?.labelId,
297
+ label: shown ? void 0 : pageLabel,
298
+ // with none on screen, ARIA's own order: the page's aria-labelledby, its aria-label, then a Field label that isn't shown
299
+ // (the final review of 0.31.0, 3: a hidden "Select" had named every row's box, over the page's "Select Alice")
300
+ labelId: shown ? typeof ownLabel === "string" ? ownLabel : ownLabel ? void 0 : field?.labelId : pageLabelledBy ?? (pageLabel ? void 0 : field?.labelId),
301
+ shown,
284
302
  describedBy: [...new Set(ids)].join(" ") || void 0,
285
303
  invalid,
286
304
  required,
@@ -333,10 +351,32 @@ function Field({ label, children, hint, error, required, labelAction, hideLabel
333
351
  const hasError = typeof shownError === "string" && shownError.length > 0;
334
352
  const describedBy = [hint ? hintId : null, hasError ? errorId : null].filter(Boolean).join(" ") || void 0;
335
353
  const labelId = `${id}-label`;
336
- const fieldContext = useMemo(() => ({ controlId: id, labelId, describedBy, invalid: hasError, required: !!required }), [id, labelId, describedBy, hasError, required]);
354
+ const [rowShowsLabel, setRowShowsLabel] = useState(false);
355
+ useLayoutEffect(() => {
356
+ const el = document.getElementById(labelId);
357
+ if (!hideLabel || !inGroupRow || !el || typeof ResizeObserver === "undefined") return;
358
+ const ro = new ResizeObserver(() => {
359
+ const box = el.getBoundingClientRect();
360
+ setRowShowsLabel(box.width > 1 && box.height > 1);
361
+ });
362
+ ro.observe(el);
363
+ return () => ro.disconnect();
364
+ }, [hideLabel, inGroupRow, labelId]);
365
+ const labelShown = !hideLabel || inGroupRow && rowShowsLabel;
366
+ const fieldContext = useMemo(() => ({ controlId: id, labelId, describedBy, invalid: hasError, required: !!required, labelHidden: !labelShown }), [id, labelId, describedBy, hasError, required, labelShown]);
367
+ const childType = child && isValidElement(child) ? child.type : null;
368
+ const childName = typeof childType === "string" ? childType : childType ? childType.displayName ?? childType.name ?? "A Field's control" : "A Field's control";
369
+ const pageLabel = labelShown ? childProps?.["aria-label"] : void 0;
370
+ const pageLabelledBy = labelShown ? childProps?.["aria-labelledby"] : void 0;
371
+ useEffect(() => {
372
+ if (pageLabel) warnNameLeftOut(childName, "aria-label", pageLabel, "its Field's label");
373
+ if (pageLabelledBy) warnNameLeftOut(childName, "aria-labelledby", pageLabelledBy, "its Field's label");
374
+ }, [childName, pageLabel, pageLabelledBy]);
375
+ const ownDescribedBy = childProps?.["aria-describedby"];
376
+ const allDescribedBy = [...new Set([ownDescribedBy, describedBy].filter(Boolean).join(" ").split(/\s+/).filter(Boolean))].join(" ") || void 0;
337
377
  const controlProps = {
338
378
  id,
339
- "aria-describedby": describedBy,
379
+ "aria-describedby": allDescribedBy,
340
380
  ...hasError ? { "aria-invalid": true, error: true } : {},
341
381
  ...required ? { "aria-required": true, required: true } : {}
342
382
  };
@@ -352,7 +392,8 @@ function Field({ label, children, hint, error, required, labelAction, hideLabel
352
392
  ownOnChange?.(...args);
353
393
  recheck();
354
394
  } } : {};
355
- control = cloneElement(child, isNative ? ariaOnly : { ...ariaOnly, ...errorFlag ? { error: true } : {}, ...onChange });
395
+ const names = labelShown ? { "aria-label": void 0, "aria-labelledby": void 0 } : {};
396
+ control = cloneElement(child, isNative ? { ...ariaOnly, ...names } : { ...ariaOnly, ...names, ...errorFlag ? { error: true } : {}, ...onChange });
356
397
  }
357
398
  const labelNode = /* @__PURE__ */ jsxs(Label, { id: labelId, htmlFor: id, className: cn(hideLabel && "sr-only", hideLabel && inGroupRow && "@max-[30rem]:not-sr-only"), children: [
358
399
  label,
@@ -405,11 +446,11 @@ function Dropdown({
405
446
  useInputModality();
406
447
  const contextSize = useContext(ControlSizeContext);
407
448
  const generatedId = useId();
408
- const control = useFieldControl({ ...wiring, error: error || void 0 }, { requiredInDescription: true });
449
+ const labelSpanId = `${generatedId}-label`;
450
+ const nameSpanId = `${generatedId}-name`;
451
+ const control = useFieldControl({ ...wiring, "aria-label": ariaLabel, error: error || void 0 }, { requiredInDescription: true, component: "Dropdown", ownLabel: label ? labelSpanId : void 0 });
409
452
  const triggerId = control.id ?? generatedId;
410
- const labelSpanId = `${triggerId}-label`;
411
- const labelledBy = control.labelId ? `${control.labelId} ${triggerId}` : label && !ariaLabel ? `${labelSpanId} ${triggerId}` : void 0;
412
- const listLabelledBy = control.labelId ?? (label && !ariaLabel ? labelSpanId : void 0);
453
+ const listLabelledBy = control.labelId;
413
454
  const invalid = control.invalid;
414
455
  const size = sizeProp ?? contextSize ?? "md";
415
456
  const [open, setOpen] = useState(false);
@@ -581,9 +622,15 @@ function Dropdown({
581
622
  });
582
623
  const displayLabel = multiple ? selectedValues.length > 0 ? `${selectedValues.length} selected` : placeholder : options.find((o) => o.id === selectedValues[0])?.label ?? placeholder;
583
624
  const { ref: overflowRef, isTruncated } = useTextOverflow({ lines: 1, content: displayLabel });
625
+ const shows = String(displayLabel).trim();
626
+ const name = control.label?.trim() ?? "";
627
+ const repeats = !!name && new RegExp(`^${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?!\\p{L})`, "iu").test(shows);
628
+ const nameSpan = !control.labelId && !!control.label && !repeats;
629
+ const labelledBy = control.labelId ? `${control.labelId} ${triggerId}` : nameSpan ? `${nameSpanId} ${triggerId}` : void 0;
584
630
  return /* @__PURE__ */ jsxs("div", { ref, className: cn("relative", className), children: [
585
631
  label && /* @__PURE__ */ jsx("span", { id: labelSpanId, className: "block mb-ds-1.5 text-sm font-medium text-[var(--ds-text-secondary)]", children: label }),
586
632
  control.requiredNote,
633
+ nameSpan && /* @__PURE__ */ jsx("span", { id: nameSpanId, hidden: true, children: shows && !/[,.:;!?]$/.test(control.label.trim()) ? `${control.label},` : control.label }),
587
634
  /* @__PURE__ */ jsxs(
588
635
  "button",
589
636
  {
@@ -602,7 +649,6 @@ function Dropdown({
602
649
  "aria-invalid": invalid || void 0,
603
650
  "aria-haspopup": "listbox",
604
651
  "aria-expanded": open,
605
- "aria-label": labelledBy ? void 0 : ariaLabel,
606
652
  "aria-labelledby": labelledBy,
607
653
  "aria-describedby": control.describedBy,
608
654
  className: cn(
@@ -655,7 +701,7 @@ function Dropdown({
655
701
  className: cn("w-full bg-transparent text-[var(--ds-text-primary)] placeholder:text-[var(--ds-text-muted)] outline-none", size === "sm" ? "text-xs" : "text-sm")
656
702
  }
657
703
  ) }),
658
- /* @__PURE__ */ jsx("div", { ref: setListbox, role: "listbox", "aria-labelledby": listLabelledBy, "aria-label": listLabelledBy ? void 0 : ariaLabel, "aria-multiselectable": multiple || void 0, className: "overflow-y-auto scroll-p-[4px]", style: { padding: "max(var(--ds-space-1), 4px)", maxHeight: "var(--ds-dropdown-max-height)" }, children: filtered.length === 0 ? /* @__PURE__ */ jsx("p", { className: cn("text-[var(--ds-text-muted)] text-center py-ds-4", size === "sm" ? "text-xs" : "text-sm"), children: "No options found" }) : hasGroups ? Array.from(groups.entries()).map(([group, opts]) => /* @__PURE__ */ jsxs("div", { children: [
704
+ /* @__PURE__ */ jsx("div", { ref: setListbox, role: "listbox", "aria-labelledby": listLabelledBy, "aria-label": listLabelledBy ? void 0 : control.label, "aria-multiselectable": multiple || void 0, className: "overflow-y-auto scroll-p-[4px]", style: { padding: "max(var(--ds-space-1), 4px)", maxHeight: "var(--ds-dropdown-max-height)" }, children: filtered.length === 0 ? /* @__PURE__ */ jsx("p", { className: cn("text-[var(--ds-text-muted)] text-center py-ds-4", size === "sm" ? "text-xs" : "text-sm"), children: "No options found" }) : hasGroups ? Array.from(groups.entries()).map(([group, opts]) => /* @__PURE__ */ jsxs("div", { children: [
659
705
  group && /* @__PURE__ */ jsx("p", { className: "text-2xs font-medium uppercase tracking-wider text-[var(--ds-text-muted)] px-ds-2 pt-ds-2 pb-ds-1", children: group }),
660
706
  opts.map((opt) => /* @__PURE__ */ jsx(
661
707
  OptionButton,
@@ -757,679 +803,7 @@ function OptionButton({
757
803
  }
758
804
  );
759
805
  }
760
- var sizes = {
761
- sm: { box: "h-[var(--ds-control-height-sm)]", input: "text-xs self-stretch", btn: "w-7 text-xs" },
762
- md: { box: "h-[var(--ds-control-height-md)]", input: "text-sm self-stretch", btn: "w-9 text-sm" },
763
- lg: { box: "h-[var(--ds-control-height-lg)]", input: "text-base self-stretch", btn: "w-10 text-base" }
764
- };
765
- function NumberInput({
766
- value,
767
- onChange: onChangeProp,
768
- min = -Infinity,
769
- max = Infinity,
770
- step = 1,
771
- disabled = false,
772
- placeholder = "0",
773
- prefix,
774
- suffix,
775
- size: sizeProp,
776
- className,
777
- error = false,
778
- allowDecimals,
779
- id,
780
- "aria-label": ariaLabel
781
- }) {
782
- const onChange = useReportedChange(onChangeProp, value);
783
- useInputModality();
784
- const contextSize = useContext(ControlSizeContext);
785
- const size = sizeProp ?? contextSize ?? "md";
786
- const intervalRef = useRef(void 0);
787
- const timeoutRef = useRef(void 0);
788
- const inputRef = useRef(null);
789
- const [focused, setFocused] = useState(false);
790
- const [draft, setDraft] = useState(String(value));
791
- const decimals = allowDecimals ?? step % 1 !== 0;
792
- const [prevSync, setPrevSync] = useState({ value, focused });
793
- if (value !== prevSync.value || focused !== prevSync.focused) {
794
- setPrevSync({ value, focused });
795
- if (!focused) setDraft(String(value));
796
- }
797
- const clamp = useCallback(
798
- (v) => Math.min(max, Math.max(min, v)),
799
- [min, max]
800
- );
801
- const valueRef = useLatest(value);
802
- const commitValue = useCallback((v, own = false) => {
803
- const clamped = clamp(v);
804
- valueRef.current = clamped;
805
- setDraft(String(clamped));
806
- (own ? onChangeProp : onChange)(clamped);
807
- return clamped;
808
- }, [onChange, onChangeProp, clamp, valueRef]);
809
- const increment = useCallback(() => commitValue(valueRef.current + step), [commitValue, step, valueRef]);
810
- const decrement = useCallback(() => commitValue(valueRef.current - step), [commitValue, step, valueRef]);
811
- const startRepeat = useCallback(
812
- (fn) => {
813
- fn();
814
- timeoutRef.current = setTimeout(() => {
815
- intervalRef.current = setInterval(fn, 75);
816
- }, 400);
817
- },
818
- []
819
- );
820
- const stopRepeat = useCallback(() => {
821
- clearTimeout(timeoutRef.current);
822
- clearInterval(intervalRef.current);
823
- }, []);
824
- const handleInput = useCallback(
825
- (e) => {
826
- const raw = e.target.value;
827
- if (raw === "" || raw === "-") {
828
- setDraft(raw);
829
- return;
830
- }
831
- if (!decimals && raw.includes(".")) return;
832
- const validPattern = decimals ? /^-?\d*\.?\d*$/ : /^-?\d*$/;
833
- if (!validPattern.test(raw)) return;
834
- setDraft(raw);
835
- const parsed = parseFloat(raw);
836
- if (!isNaN(parsed) && isFinite(parsed)) {
837
- onChange(clamp(parsed));
838
- }
839
- },
840
- [onChange, clamp, decimals]
841
- );
842
- const handleBlur = useCallback(() => {
843
- setFocused(false);
844
- const parsed = parseFloat(draft);
845
- if (!isNaN(parsed) && isFinite(parsed)) {
846
- const committed = commitValue(parsed, draft === String(value));
847
- setDraft(String(committed));
848
- } else {
849
- const fallback = isFinite(min) ? min : 0;
850
- const committed = commitValue(value !== void 0 ? value : fallback, true);
851
- setDraft(String(committed));
852
- }
853
- }, [draft, commitValue, min, value]);
854
- const handleFocus = useCallback(() => {
855
- setFocused(true);
856
- }, []);
857
- const handleKeyDown = useCallback(
858
- (e) => {
859
- if (e.key === "ArrowUp") {
860
- e.preventDefault();
861
- increment();
862
- }
863
- if (e.key === "ArrowDown") {
864
- e.preventDefault();
865
- decrement();
866
- }
867
- const plain = !e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey;
868
- if (e.key === "Home" && plain && Number.isFinite(min)) {
869
- e.preventDefault();
870
- commitValue(min);
871
- }
872
- if (e.key === "End" && plain && Number.isFinite(max)) {
873
- e.preventDefault();
874
- commitValue(max);
875
- }
876
- },
877
- [increment, decrement, commitValue, min, max]
878
- );
879
- const s = sizes[size];
880
- const atMin = value <= min;
881
- const atMax = value >= max;
882
- const shown = focused ? draft : String(value);
883
- const holdsNumber = Number.isFinite(parseFloat(shown)) && Number.isFinite(value);
884
- return /* @__PURE__ */ jsxs(
885
- "div",
886
- {
887
- className: cn(
888
- "flex items-center w-full",
889
- s.box,
890
- "border-[length:var(--ds-border-width)]",
891
- error ? "border-[var(--ds-status-error)]" : "border-[var(--ds-input-border)]",
892
- "rounded-[var(--ds-radius-md)]",
893
- "shadow-[var(--ds-input-shadow)]",
894
- "bg-[var(--ds-input-bg)]",
895
- "transition-colors duration-[var(--ds-transition-speed)]",
896
- !error && !disabled && "hover:border-[var(--ds-input-border-hover)]",
897
- error ? ERROR_FOCUS_WITHIN_RING : FIELD_FOCUS_WITHIN_RING,
898
- disabled && "opacity-disabled cursor-not-allowed",
899
- className
900
- ),
901
- children: [
902
- /* @__PURE__ */ jsx(
903
- "button",
904
- {
905
- type: "button",
906
- tabIndex: -1,
907
- disabled: disabled || atMin,
908
- onMouseDown: (e) => {
909
- e.preventDefault();
910
- inputRef.current?.focus();
911
- if (!disabled && !atMin) startRepeat(decrement);
912
- },
913
- onMouseUp: stopRepeat,
914
- onMouseLeave: stopRepeat,
915
- className: cn(
916
- "inline-flex items-center justify-center shrink-0",
917
- "border-r-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
918
- "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]",
919
- "transition-colors cursor-pointer",
920
- "rounded-l-[var(--ds-radius-md)]",
921
- // kept out of the Tab order (the field's arrows step it) and never focused by a press: the outline all the same
922
- // if anything focuses it (Sprint 30)
923
- FOCUS_RING,
924
- s.btn,
925
- s.input,
926
- (disabled || atMin) && "opacity-disabled cursor-not-allowed hover:bg-transparent hover:text-[var(--ds-text-muted)]"
927
- ),
928
- "aria-label": "Decrease",
929
- children: "\u2212"
930
- }
931
- ),
932
- /* @__PURE__ */ jsxs("div", { className: cn("flex items-center flex-1 min-w-0 px-ds-2 gap-ds-1", s.input), children: [
933
- prefix && /* @__PURE__ */ jsx("span", { className: "text-[var(--ds-text-muted)] shrink-0 text-xs", children: prefix }),
934
- /* @__PURE__ */ jsx(
935
- "input",
936
- {
937
- ref: inputRef,
938
- id,
939
- type: "text",
940
- role: "spinbutton",
941
- "aria-label": ariaLabel,
942
- "aria-valuenow": holdsNumber ? value : void 0,
943
- "aria-valuemin": Number.isFinite(min) ? min : void 0,
944
- "aria-valuemax": Number.isFinite(max) ? max : void 0,
945
- inputMode: decimals ? "decimal" : "numeric",
946
- "aria-invalid": error || void 0,
947
- value: shown,
948
- onChange: handleInput,
949
- onFocus: handleFocus,
950
- onBlur: handleBlur,
951
- onKeyDown: handleKeyDown,
952
- disabled,
953
- placeholder,
954
- className: cn(
955
- "w-full min-w-0 bg-transparent text-center outline-none",
956
- "text-[var(--ds-text-primary)]",
957
- "placeholder:text-[var(--ds-text-muted)]",
958
- "font-[family-name:var(--ds-font-mono)]",
959
- s.input,
960
- disabled && "cursor-not-allowed"
961
- )
962
- }
963
- ),
964
- suffix && /* @__PURE__ */ jsx("span", { className: "text-[var(--ds-text-muted)] shrink-0 text-xs", children: suffix })
965
- ] }),
966
- /* @__PURE__ */ jsx(
967
- "button",
968
- {
969
- type: "button",
970
- tabIndex: -1,
971
- disabled: disabled || atMax,
972
- onMouseDown: (e) => {
973
- e.preventDefault();
974
- inputRef.current?.focus();
975
- if (!disabled && !atMax) startRepeat(increment);
976
- },
977
- onMouseUp: stopRepeat,
978
- onMouseLeave: stopRepeat,
979
- className: cn(
980
- "inline-flex items-center justify-center shrink-0",
981
- "border-l-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
982
- "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]",
983
- "transition-colors cursor-pointer",
984
- "rounded-r-[var(--ds-radius-md)]",
985
- FOCUS_RING,
986
- s.btn,
987
- s.input,
988
- (disabled || atMax) && "opacity-disabled cursor-not-allowed hover:bg-transparent hover:text-[var(--ds-text-muted)]"
989
- ),
990
- "aria-label": "Increase",
991
- children: "+"
992
- }
993
- )
994
- ]
995
- }
996
- );
997
- }
998
- function Popover({ open, onOpenChange, trigger, children, side = "bottom", align = "center", width, className, container }) {
999
- const triggerRef = useRef(null);
1000
- const floatingRef = useRef(null);
1001
- const [triggerWidth, setTriggerWidth] = useState(void 0);
1002
- const setTriggerNode = useCallback((node) => {
1003
- triggerRef.current = node;
1004
- setTriggerWidth(node?.offsetWidth);
1005
- }, []);
1006
- const pos = usePosition(triggerRef, floatingRef, open, { side, align, offset: 8, flip: true });
1007
- const onOpenChangeRef = useLatest(onOpenChange);
1008
- const { props: pressMarks, marked } = usePressMarks();
1009
- const { isTop: isTopLayer, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: floatingRef, close: () => onOpenChangeRef.current(false), tabExits: "edges", marked });
1010
- useEffect(() => {
1011
- if (!open) return;
1012
- const onKeyDown = (e) => {
1013
- if (e.key !== "Escape" || e.defaultPrevented || !isTopLayer()) return;
1014
- const active = document.activeElement;
1015
- if (active && active !== document.body && !triggerRef.current?.contains(active) && !floatingRef.current?.contains(active)) return;
1016
- e.preventDefault();
1017
- returnFocus();
1018
- onOpenChangeRef.current(false);
1019
- };
1020
- const onPress = (e) => {
1021
- const target = e.target;
1022
- if (marked(e) || triggerRef.current?.contains(target) || floatingRef.current?.contains(target)) return;
1023
- onOpenChangeRef.current(false);
1024
- };
1025
- document.addEventListener("keydown", onKeyDown);
1026
- document.addEventListener("mousedown", onPress);
1027
- return () => {
1028
- document.removeEventListener("keydown", onKeyDown);
1029
- document.removeEventListener("mousedown", onPress);
1030
- };
1031
- }, [open, onOpenChangeRef, isTopLayer, returnFocus, marked]);
1032
- useEffect(() => {
1033
- if (open) focusInitial(floatingRef.current);
1034
- }, [open]);
1035
- return /* @__PURE__ */ jsxs(Fragment, { children: [
1036
- /* @__PURE__ */ jsx(
1037
- Slot,
1038
- {
1039
- ...triggerType(trigger),
1040
- ref: setTriggerNode,
1041
- onClick: () => {
1042
- setTriggerWidth(triggerRef.current?.offsetWidth);
1043
- onOpenChange(!open);
1044
- },
1045
- children: trigger
1046
- }
1047
- ),
1048
- open && createPortal(
1049
- /* @__PURE__ */ jsx(
1050
- "div",
1051
- {
1052
- ref: floatingRef,
1053
- "data-gs": "popover",
1054
- ...pressMarks,
1055
- onClick: (e) => e.stopPropagation(),
1056
- className: cn(
1057
- "fixed z-[var(--ds-z-popover)]",
1058
- "bg-[var(--ds-bg-surface-elevated)]",
1059
- GLASS_DROPDOWN,
1060
- "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
1061
- "rounded-[var(--ds-radius-md)]",
1062
- className
1063
- ),
1064
- style: {
1065
- top: pos.top,
1066
- left: pos.left,
1067
- width: width === "trigger" ? triggerWidth : width === "auto" ? void 0 : width
1068
- },
1069
- children: /* @__PURE__ */ jsx(RowsCtx, { value: false, children })
1070
- }
1071
- ),
1072
- container ?? document.body
1073
- )
1074
- ] });
1075
- }
1076
- var controlsDrawn = /* @__PURE__ */ new WeakMap();
1077
- function Pagination(props) {
1078
- const { pagerToken } = props;
1079
- useEffect(() => {
1080
- if (!pagerToken) return;
1081
- const count = (controlsDrawn.get(pagerToken) ?? 0) + 1;
1082
- controlsDrawn.set(pagerToken, count);
1083
- if (count > 1) warnOnce("Pagination", "this list's pager already draws its control: a list given the pager (<Table pager={pager}>) draws it beneath the rows, so remove the separate <Pagination {...pager.props} />.");
1084
- return () => {
1085
- controlsDrawn.set(pagerToken, (controlsDrawn.get(pagerToken) ?? 1) - 1);
1086
- };
1087
- }, [pagerToken]);
1088
- if ((props.mode === "load-more" || props.mode === "infinite-scroll") && props.onLoadMore) return /* @__PURE__ */ jsx(LoadMore, { ...props, onLoadMore: props.onLoadMore, scroll: props.mode === "infinite-scroll" });
1089
- return /* @__PURE__ */ jsx(PageNumbers, { ...props });
1090
- }
1091
- function scrollerOf(el) {
1092
- for (let n = el.parentElement; n && n !== document.body && n !== document.documentElement; n = n.parentElement) {
1093
- const { overflowY } = getComputedStyle(n);
1094
- if ((overflowY === "auto" || overflowY === "scroll") && n.scrollHeight > n.clientHeight) return n;
1095
- }
1096
- return null;
1097
- }
1098
- function contentTop(el) {
1099
- let top = el.getBoundingClientRect().top + window.scrollY;
1100
- for (let n = el.parentElement; n && n !== document.documentElement; n = n.parentElement) top += n.scrollTop;
1101
- return top;
1102
- }
1103
- function LoadMore({ hasMore, onLoadMore, loadMoreLabel, currentPage, scroll, className }) {
1104
- const marker = useRef(null);
1105
- const button = useRef(null);
1106
- const pager = useRef(null);
1107
- const watching = useRef(null);
1108
- const press = () => {
1109
- const b = button.current, root = pager.current;
1110
- if (b && root && document.activeElement === b) {
1111
- const list = root.previousElementSibling;
1112
- const had = new Set(rowsOf(list));
1113
- watching.current?.();
1114
- watching.current = focusAfterSelfRemoval(b, {
1115
- item: root,
1116
- to: () => {
1117
- const fresh = rowsOf(list).find((r) => !had.has(r));
1118
- return fresh ? tabStops(fresh)[0] ?? fresh : null;
1119
- }
1120
- });
1121
- }
1122
- onLoadMore();
1123
- };
1124
- const load = useLatest(onLoadMore);
1125
- const loadedAt = useRef(null);
1126
- const [held, setHeld] = useState(false);
1127
- useEffect(() => {
1128
- const el = marker.current;
1129
- if (!scroll || !hasMore || !el || held || typeof IntersectionObserver === "undefined") return;
1130
- const observer = new IntersectionObserver((entries) => {
1131
- if (!entries.some((e) => e.isIntersecting)) return;
1132
- const at = contentTop(el);
1133
- if (loadedAt.current !== null && Math.abs(at - loadedAt.current) < 1) return;
1134
- loadedAt.current = at;
1135
- load.current();
1136
- }, { root: scrollerOf(el), rootMargin: "200px 0px" });
1137
- observer.observe(el);
1138
- const rearm = () => {
1139
- loadedAt.current = null;
1140
- };
1141
- window.addEventListener("scroll", rearm, { capture: true, passive: true });
1142
- return () => {
1143
- observer.disconnect();
1144
- window.removeEventListener("scroll", rearm, { capture: true });
1145
- };
1146
- }, [scroll, hasMore, currentPage, load, held]);
1147
- useEffect(() => {
1148
- const b = button.current;
1149
- if (b && document.activeElement === b) b.scrollIntoView?.({ block: "nearest" });
1150
- }, [currentPage]);
1151
- if (!hasMore) return null;
1152
- return /* @__PURE__ */ jsxs("div", { ref: pager, "data-ds-pager": "", "data-pagination-mode": scroll ? "infinite-scroll" : "load-more", className: cn("flex flex-col items-center", className), children: [
1153
- scroll && /* @__PURE__ */ jsx("div", { ref: marker, "data-load-more-trigger": "", "aria-hidden": "true", className: "h-px w-full" }),
1154
- /* @__PURE__ */ jsx(
1155
- Button,
1156
- {
1157
- ref: button,
1158
- type: "button",
1159
- variant: "secondary",
1160
- onClick: press,
1161
- onFocus: (e) => setHeld(scroll && byKeyboard(e.currentTarget)),
1162
- onBlur: () => setHeld(false),
1163
- children: loadMoreLabel ?? "Load more"
1164
- }
1165
- )
1166
- ] });
1167
- }
1168
- function rowsOf(list) {
1169
- if (!list) return [];
1170
- const marked = list.querySelectorAll("[data-row-key], [data-row-index]");
1171
- return Array.from(marked.length ? marked : list.querySelectorAll('tbody > tr, li, [role="row"], [data-card]'));
1172
- }
1173
- function byKeyboard(el) {
1174
- try {
1175
- return el.matches(":focus-visible");
1176
- } catch {
1177
- return true;
1178
- }
1179
- }
1180
- function PageNumbers({ currentPage, totalPages, onPageChange, pageSize: pageSizeProp, pageSizeOptions, onPageSizeChange, className }) {
1181
- const pages = buildPageRange(currentPage, totalPages);
1182
- const preferences = useShellPreferences();
1183
- const pageSize = pageSizeProp ?? preferences.itemsPerPage;
1184
- const sizeChoice = !!(pageSizeOptions && pageSize != null && onPageSizeChange);
1185
- if (totalPages <= 1 && !sizeChoice) return null;
1186
- return /* @__PURE__ */ jsxs("nav", { "aria-label": "Pagination", "data-ds-pager": "", className: cn("flex flex-wrap items-center gap-ds-3", className), children: [
1187
- sizeChoice && pageSizeOptions && pageSize != null && onPageSizeChange && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2", children: [
1188
- /* @__PURE__ */ jsx("span", { className: "text-sm text-[var(--ds-text-muted)] whitespace-nowrap", children: "Rows per page" }),
1189
- /* @__PURE__ */ jsx(FormChangeCtx, { value: null, children: /* @__PURE__ */ jsx(
1190
- Dropdown,
1191
- {
1192
- "aria-label": `Rows per page, ${pageSize}`,
1193
- options: pageSizeOptions.map((n) => ({ id: String(n), label: String(n) })),
1194
- value: String(pageSize),
1195
- onChange: (v) => onPageSizeChange(Number(v)),
1196
- size: "sm",
1197
- className: "w-[4.5rem]"
1198
- }
1199
- ) })
1200
- ] }),
1201
- totalPages > 1 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-1", children: [
1202
- /* @__PURE__ */ jsx(AutoTooltip, { content: "Previous page", children: /* @__PURE__ */ jsx(
1203
- "button",
1204
- {
1205
- type: "button",
1206
- onClick: (e) => {
1207
- focusAfterSelfRemoval(e.currentTarget);
1208
- onPageChange(currentPage - 1);
1209
- },
1210
- disabled: currentPage <= 1,
1211
- "aria-label": "Previous page",
1212
- className: cn(
1213
- "inline-flex items-center justify-center h-[var(--ds-control-height-sm)] w-[var(--ds-control-height-sm)] text-sm cursor-pointer",
1214
- "rounded-[var(--ds-radius-md)]",
1215
- "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
1216
- "bg-[var(--ds-bg-surface)] text-[var(--ds-text-secondary)]",
1217
- "transition-colors duration-[var(--ds-transition-speed)]",
1218
- "hover:bg-[var(--ds-bg-muted)] hover:text-[var(--ds-text-primary)]",
1219
- "disabled:opacity-disabled disabled:cursor-not-allowed",
1220
- FOCUS_RING
1221
- ),
1222
- children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 16, className: "rotate-180" })
1223
- }
1224
- ) }),
1225
- pages.map(
1226
- (p, i) => p === "..." ? /* @__PURE__ */ jsx(EllipsisJumper, { totalPages, onPageChange }, `ellipsis-${i}`) : /* @__PURE__ */ jsx(
1227
- "button",
1228
- {
1229
- type: "button",
1230
- onClick: () => onPageChange(p),
1231
- "aria-current": p === currentPage ? "page" : void 0,
1232
- className: cn(
1233
- "inline-flex items-center justify-center h-[var(--ds-control-height-sm)] w-[var(--ds-control-height-sm)] text-sm font-medium cursor-pointer",
1234
- "rounded-[var(--ds-radius-md)]",
1235
- "border-[length:var(--ds-border-width)]",
1236
- "transition-colors duration-[var(--ds-transition-speed)]",
1237
- FOCUS_RING,
1238
- p === currentPage ? "bg-[var(--ds-accent-primary)] [background-image:var(--ds-gradient)] text-[var(--ds-text-on-primary)] border-[var(--ds-accent-primary)]" : "bg-[var(--ds-bg-surface)] text-[var(--ds-text-secondary)] border-[var(--ds-border-default)] hover:bg-[var(--ds-bg-muted)] hover:text-[var(--ds-text-primary)]"
1239
- ),
1240
- children: p
1241
- },
1242
- p
1243
- )
1244
- ),
1245
- /* @__PURE__ */ jsx(AutoTooltip, { content: "Next page", children: /* @__PURE__ */ jsx(
1246
- "button",
1247
- {
1248
- type: "button",
1249
- onClick: (e) => {
1250
- focusAfterSelfRemoval(e.currentTarget);
1251
- onPageChange(currentPage + 1);
1252
- },
1253
- disabled: currentPage >= totalPages,
1254
- "aria-label": "Next page",
1255
- className: cn(
1256
- "inline-flex items-center justify-center h-[var(--ds-control-height-sm)] w-[var(--ds-control-height-sm)] text-sm cursor-pointer",
1257
- "rounded-[var(--ds-radius-md)]",
1258
- "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
1259
- "bg-[var(--ds-bg-surface)] text-[var(--ds-text-secondary)]",
1260
- "transition-colors duration-[var(--ds-transition-speed)]",
1261
- "hover:bg-[var(--ds-bg-muted)] hover:text-[var(--ds-text-primary)]",
1262
- "disabled:opacity-disabled disabled:cursor-not-allowed",
1263
- FOCUS_RING
1264
- ),
1265
- children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 16 })
1266
- }
1267
- ) })
1268
- ] })
1269
- ] });
1270
- }
1271
- function buildPageRange(current, total) {
1272
- if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1);
1273
- const pages = [1];
1274
- if (current > 3) pages.push("...");
1275
- const start = Math.max(2, current - 1);
1276
- const end = Math.min(total - 1, current + 1);
1277
- for (let i = start; i <= end; i++) pages.push(i);
1278
- if (current < total - 2) pages.push("...");
1279
- pages.push(total);
1280
- return pages;
1281
- }
1282
- function EllipsisJumper({ totalPages, onPageChange }) {
1283
- const [open, setOpen] = useState(false);
1284
- const [jumpPage, setJumpPage] = useState(1);
1285
- const handleOpenChange = (next) => {
1286
- if (next) setJumpPage(1);
1287
- setOpen(next);
1288
- };
1289
- const handleGo = () => {
1290
- const clamped = Math.min(totalPages, Math.max(1, jumpPage));
1291
- onPageChange(clamped);
1292
- setOpen(false);
1293
- };
1294
- return /* @__PURE__ */ jsx(
1295
- Popover,
1296
- {
1297
- open,
1298
- onOpenChange: handleOpenChange,
1299
- trigger: /* @__PURE__ */ jsx(
1300
- "button",
1301
- {
1302
- type: "button",
1303
- "aria-label": "Go to page",
1304
- onClick: () => handleOpenChange(true),
1305
- className: cn(
1306
- "inline-flex items-center justify-center h-[var(--ds-control-height-sm)] w-[var(--ds-control-height-sm)] text-sm cursor-pointer",
1307
- "rounded-[var(--ds-radius-md)]",
1308
- "text-[var(--ds-text-muted)]",
1309
- "transition-colors duration-[var(--ds-transition-speed)]",
1310
- "hover:bg-[var(--ds-bg-muted)] hover:text-[var(--ds-text-primary)]",
1311
- FOCUS_RING
1312
- ),
1313
- children: "\u2026"
1314
- }
1315
- ),
1316
- children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-ds-2 p-ds-3", children: [
1317
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-[var(--ds-text-secondary)]", children: "Go to page" }),
1318
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2", children: [
1319
- /* @__PURE__ */ jsx(
1320
- NumberInput,
1321
- {
1322
- value: jumpPage,
1323
- onChange: setJumpPage,
1324
- min: 1,
1325
- max: totalPages,
1326
- step: 1,
1327
- size: "sm",
1328
- className: "w-[5rem]"
1329
- }
1330
- ),
1331
- /* @__PURE__ */ jsx(
1332
- "button",
1333
- {
1334
- type: "button",
1335
- onClick: handleGo,
1336
- className: cn(
1337
- "px-ds-2.5 h-[var(--ds-control-height-sm)] text-xs font-medium cursor-pointer",
1338
- "rounded-[var(--ds-radius-md)]",
1339
- "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]",
1340
- "transition-colors duration-[var(--ds-transition-speed)]",
1341
- "hover:opacity-90",
1342
- FOCUS_RING
1343
- ),
1344
- children: "Go"
1345
- }
1346
- )
1347
- ] })
1348
- ] })
1349
- }
1350
- );
1351
- }
1352
- function firstKey(item) {
1353
- if (item !== null && typeof item === "object") return "id" in item ? item.id : void 0;
1354
- return item;
1355
- }
1356
- function resetKey(resetOn) {
1357
- try {
1358
- return JSON.stringify(resetOn ?? null);
1359
- } catch {
1360
- return String(resetOn);
1361
- }
1362
- }
1363
- function signature(list, resetOn) {
1364
- let first = "";
1365
- try {
1366
- first = JSON.stringify(firstKey(list[0]) ?? null);
1367
- } catch {
1368
- first = "";
1369
- }
1370
- return `${list.length}|${first}|${resetKey(resetOn)}`;
1371
- }
1372
- function grewAtAnEnd(seen, list, reset) {
1373
- if (reset !== seen.reset || list.length <= seen.length || seen.first === void 0 || seen.length === 0) return false;
1374
- return firstKey(list[0]) === seen.first || firstKey(list[list.length - seen.length]) === seen.first;
1375
- }
1376
- function usePagination(list, options = {}) {
1377
- const shell = useShellPreferences();
1378
- const style = options.style ?? shell.paginationStyle ?? PREFERENCE_HOUSE_DEFAULTS.paginationStyle;
1379
- const [chosenSize, setChosenSize] = useState(null);
1380
- const [pagerToken] = useState(() => ({}));
1381
- const pageSize = Math.max(1, Math.floor(chosenSize ?? options.pageSize ?? shell.itemsPerPage ?? PREFERENCE_HOUSE_DEFAULTS.itemsPerPage));
1382
- const [page, setPageState] = useState(1);
1383
- const sig = signature(list, options.resetOn);
1384
- const reset = resetKey(options.resetOn);
1385
- const [seen, setSeen] = useState(() => ({ sig, length: list.length, first: firstKey(list[0]), reset }));
1386
- const changed = seen.sig !== sig;
1387
- const startsAgain = changed && !grewAtAnEnd(seen, list, reset);
1388
- if (changed) {
1389
- setSeen({ sig, length: list.length, first: firstKey(list[0]), reset });
1390
- if (startsAgain && page !== 1) setPageState(1);
1391
- }
1392
- const totalItems = list.length;
1393
- const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
1394
- const current = Math.min(Math.max(1, startsAgain ? 1 : page), totalPages);
1395
- const grows = style !== "pagination";
1396
- const start = grows ? 0 : (current - 1) * pageSize;
1397
- const items = list.slice(start, current * pageSize);
1398
- const range = items.length === 0 ? [0, 0] : [start + 1, start + items.length];
1399
- const hasMore = current < totalPages;
1400
- const setPage = (next) => setPageState(Math.min(Math.max(1, Math.floor(next)), totalPages));
1401
- const loadMore = () => setPageState((p) => Math.min(p + 1, totalPages));
1402
- const setPageSize = (size) => {
1403
- setChosenSize(Math.max(1, Math.floor(size)));
1404
- setPageState(1);
1405
- };
1406
- return {
1407
- style,
1408
- items,
1409
- page: current,
1410
- pageSize,
1411
- totalItems,
1412
- totalPages,
1413
- range,
1414
- shown: items.length,
1415
- hasMore,
1416
- loadMore,
1417
- setPage,
1418
- setPageSize,
1419
- props: { currentPage: current, totalPages, onPageChange: setPage, pageSize, onPageSizeChange: setPageSize, mode: style, hasMore, onLoadMore: loadMore, pagerToken, ...options.pageSizeOptions ? { pageSizeOptions: options.pageSizeOptions } : {} }
1420
- };
1421
- }
1422
- function pagerPart(list, pager) {
1423
- if (!pager) return list;
1424
- return pager.range[0] === 0 ? [] : list.slice(pager.range[0] - 1, pager.range[1]);
1425
- }
1426
- function useWholeListCheck(component, length, pager) {
1427
- const total = pager?.totalItems;
1428
- useEffect(() => {
1429
- if (total !== void 0 && length !== total) warnOnce(component, `the pager counts ${total} items but the list holds ${length}: give the list and usePagination the same items, every item of the list, and the list draws the pager's part (Sprint 25.13 A3).`);
1430
- }, [component, length, total]);
1431
- }
1432
806
 
1433
- export { AddAnotherRowCtx, CHECK_FILL, Dropdown, Field, FilterRowContext, InlineFieldError, Input, Label, NumberInput, OutsideField, Pagination, Popover, SWITCH_KNOB_MOTION, SWITCH_KNOB_OFF, SWITCH_KNOB_ON, SWITCH_TRACK_OFF, SWITCH_TRACK_ON, Textarea, Toggle, pagerPart, useField, useFieldControl, usePagination, useWholeListCheck };
1434
- //# sourceMappingURL=chunk-USHW523V.js.map
1435
- //# sourceMappingURL=chunk-USHW523V.js.map
807
+ export { AddAnotherRowCtx, CHECK_FILL, Dropdown, Field, FilterRowContext, InlineFieldError, Input, Label, OutsideField, SWITCH_KNOB_MOTION, SWITCH_KNOB_OFF, SWITCH_KNOB_ON, SWITCH_TRACK_OFF, SWITCH_TRACK_ON, Textarea, Toggle, useField, useFieldControl };
808
+ //# sourceMappingURL=chunk-IZW5XHKA.js.map
809
+ //# sourceMappingURL=chunk-IZW5XHKA.js.map