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