gridsmith-ui 0.6.3 → 0.6.4

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,13 +1,13 @@
1
1
  import { Skeleton } from './chunk-QGBG72OB.js';
2
2
  export { Skeleton } from './chunk-QGBG72OB.js';
3
- import { Badge } from './chunk-TYU2LQGK.js';
4
- export { Badge } from './chunk-TYU2LQGK.js';
5
- import { useBreakpoint } from './chunk-DPF6776I.js';
6
- export { BreakpointWidthProvider, useBreakpoint } from './chunk-DPF6776I.js';
7
- import { Tooltip, useTextOverflow, usePosition, Slot } from './chunk-R5QGJURZ.js';
8
- export { Tooltip, useTextOverflow } from './chunk-R5QGJURZ.js';
3
+ import { Badge } from './chunk-KXNJJKIV.js';
4
+ export { Badge } from './chunk-KXNJJKIV.js';
5
+ import { useBreakpoint } from './chunk-EKQHHP72.js';
6
+ export { BreakpointWidthProvider, useBreakpoint } from './chunk-EKQHHP72.js';
7
+ import { Tooltip, useTextOverflow, usePosition, Slot, useLatest } from './chunk-DDKCQVKO.js';
8
+ export { Tooltip, useTextOverflow } from './chunk-DDKCQVKO.js';
9
9
  import { Icon } from './chunk-GX6O72IV.js';
10
- import { cn, GLASS_SURFACE, ACTIVE_RING, GLASS_BACKDROP, GLASS_DROPDOWN, lockBodyScroll, unlockBodyScroll, GLASS_OVERLAY, FOCUS_RING, HAS_FOCUS_RING, FOCUS_WITHIN_RING } from './chunk-TTZSZNJ6.js';
10
+ import { cn, GLASS_SURFACE, ACTIVE_RING, GLASS_BACKDROP, GLASS_DROPDOWN, FOCUS_RING, lockBodyScroll, unlockBodyScroll, GLASS_OVERLAY, HAS_FOCUS_RING, FOCUS_WITHIN_RING } from './chunk-TTZSZNJ6.js';
11
11
  import { createContext, forwardRef, useContext, useState, useRef, useEffect, useCallback, useId, isValidElement, cloneElement, Fragment as Fragment$1, useMemo, Children, useLayoutEffect } from 'react';
12
12
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
13
13
  import { createPortal } from 'react-dom';
@@ -149,11 +149,13 @@ function DropdownMenu({ trigger, items, align = "end", className, container }) {
149
149
  if (!isDivider(item) && !item.disabled) acc.push(i);
150
150
  return acc;
151
151
  }, []);
152
+ const [prevOpen, setPrevOpen] = useState(open);
153
+ if (open !== prevOpen) {
154
+ setPrevOpen(open);
155
+ if (!open) setFocusedIdx(-1);
156
+ }
152
157
  useEffect(() => {
153
- if (!open) {
154
- setFocusedIdx(-1);
155
- return;
156
- }
158
+ if (!open) return;
157
159
  const onKeyDown = (e) => {
158
160
  if (e.key === "Escape") setOpen(false);
159
161
  };
@@ -534,13 +536,15 @@ function Dropdown({
534
536
  }
535
537
  setPos({ top, left, width });
536
538
  }, []);
539
+ const [prevOpen, setPrevOpen] = useState(open);
540
+ if (open !== prevOpen) {
541
+ setPrevOpen(open);
542
+ if (open) setSearch("");
543
+ else setPositioned(false);
544
+ }
537
545
  useEffect(() => {
538
- if (!open) {
539
- setPositioned(false);
540
- return;
541
- }
546
+ if (!open) return;
542
547
  updatePos();
543
- setSearch("");
544
548
  requestAnimationFrame(() => {
545
549
  if (searchRef.current) {
546
550
  searchRef.current.focus();
@@ -570,14 +574,14 @@ function Dropdown({
570
574
  return () => document.removeEventListener("mousedown", handler);
571
575
  }, [open]);
572
576
  const typeahead = useRef({ buffer: "", at: 0 });
573
- const optionButtons = () => Array.from(menuRef.current?.querySelectorAll('[role="option"]') ?? []);
574
- const focusOption = (idx) => {
575
- const els = optionButtons();
576
- if (els.length === 0) return;
577
- els[(idx % els.length + els.length) % els.length].focus();
578
- };
579
577
  useEffect(() => {
580
578
  if (!open) return;
579
+ const optionButtons = () => Array.from(menuRef.current?.querySelectorAll('[role="option"]') ?? []);
580
+ const focusOption = (idx) => {
581
+ const els = optionButtons();
582
+ if (els.length === 0) return;
583
+ els[(idx % els.length + els.length) % els.length].focus();
584
+ };
581
585
  const onKey = (e) => {
582
586
  const inMenu = menuRef.current?.contains(document.activeElement) || triggerRef.current === document.activeElement;
583
587
  if (!inMenu) return;
@@ -812,6 +816,7 @@ function Combobox({ value, onChange, options, placeholder = "Search\u2026", disa
812
816
  const [highlightIdx, setHighlightIdx] = useState(0);
813
817
  const inputRef = useRef(null);
814
818
  const triggerRef = useRef(null);
819
+ const [triggerWidth, setTriggerWidth] = useState(void 0);
815
820
  const listRef = useRef(null);
816
821
  const listboxId = useId();
817
822
  const pos = usePosition(triggerRef, listRef, open, { side: "bottom", align: "start", offset: 4, flip: true });
@@ -820,9 +825,11 @@ function Combobox({ value, onChange, options, placeholder = "Search\u2026", disa
820
825
  );
821
826
  const selectedLabel = options.find((o) => o.value === value)?.label ?? "";
822
827
  const { ref: overflowRef, isTruncated: labelTruncated } = useTextOverflow({ lines: 1, content: selectedLabel, disabled: open });
823
- useEffect(() => {
828
+ const [prevQuery, setPrevQuery] = useState(query);
829
+ if (query !== prevQuery) {
830
+ setPrevQuery(query);
824
831
  setHighlightIdx(0);
825
- }, [query]);
832
+ }
826
833
  useEffect(() => {
827
834
  if (!open) return;
828
835
  const handler = (e) => {
@@ -870,6 +877,7 @@ function Combobox({ value, onChange, options, placeholder = "Search\u2026", disa
870
877
  }, [overflowRef]);
871
878
  const triggerContent = /* @__PURE__ */ jsxs("div", { ref: (node) => {
872
879
  triggerRef.current = node;
880
+ setTriggerWidth(node?.offsetWidth);
873
881
  if (typeof ref === "function") ref(node);
874
882
  else if (ref) ref.current = node;
875
883
  }, className: cn("relative", className), children: [
@@ -950,7 +958,7 @@ function Combobox({ value, onChange, options, placeholder = "Search\u2026", disa
950
958
  "rounded-[var(--ds-radius-md)]",
951
959
  "shadow-[var(--ds-shadow-dropdown),var(--ds-glass-inset)]"
952
960
  ),
953
- style: { top: pos.top, left: pos.left, width: triggerRef.current?.offsetWidth },
961
+ style: { top: pos.top, left: pos.left, width: triggerWidth },
954
962
  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(
955
963
  "button",
956
964
  {
@@ -1491,7 +1499,7 @@ function getMonthGrid(year, month, weekStartsOn = 0) {
1491
1499
  const firstOfMonth = new Date(year, month, 1);
1492
1500
  const gridStart = startOfWeek(firstOfMonth, weekStartsOn);
1493
1501
  const weeks = [];
1494
- let current = new Date(gridStart);
1502
+ const current = new Date(gridStart);
1495
1503
  for (let w = 0; w < 6; w++) {
1496
1504
  const week = [];
1497
1505
  for (let d = 0; d < 7; d++) {
@@ -2292,11 +2300,11 @@ function NumberInput({
2292
2300
  const [focused, setFocused] = useState(false);
2293
2301
  const [draft, setDraft] = useState(String(value));
2294
2302
  const decimals = allowDecimals ?? step % 1 !== 0;
2295
- useEffect(() => {
2296
- if (!focused) {
2297
- setDraft(String(value));
2298
- }
2299
- }, [value, focused]);
2303
+ const [prevSync, setPrevSync] = useState({ value, focused });
2304
+ if (value !== prevSync.value || focused !== prevSync.focused) {
2305
+ setPrevSync({ value, focused });
2306
+ if (!focused) setDraft(String(value));
2307
+ }
2300
2308
  const clamp = useCallback(
2301
2309
  (v) => Math.min(max, Math.max(min, v)),
2302
2310
  [min, max]
@@ -2397,7 +2405,7 @@ function NumberInput({
2397
2405
  onMouseDown: (e) => {
2398
2406
  e.preventDefault();
2399
2407
  inputRef.current?.focus();
2400
- !disabled && !atMin && startRepeat(decrement);
2408
+ if (!disabled && !atMin) startRepeat(decrement);
2401
2409
  },
2402
2410
  onMouseUp: stopRepeat,
2403
2411
  onMouseLeave: stopRepeat,
@@ -2453,7 +2461,7 @@ function NumberInput({
2453
2461
  onMouseDown: (e) => {
2454
2462
  e.preventDefault();
2455
2463
  inputRef.current?.focus();
2456
- !disabled && !atMax && startRepeat(increment);
2464
+ if (!disabled && !atMax) startRepeat(increment);
2457
2465
  },
2458
2466
  onMouseUp: stopRepeat,
2459
2467
  onMouseLeave: stopRepeat,
@@ -3012,11 +3020,12 @@ function ColorPicker({
3012
3020
  const [hexInput, setHexInput] = useState(value);
3013
3021
  const satLightRef = useRef(null);
3014
3022
  const draggingRef = useRef(false);
3015
- useEffect(() => {
3016
- const parsed = hexToHsl(value);
3017
- setHsl(parsed);
3023
+ const [prevValue, setPrevValue] = useState(value);
3024
+ if (value !== prevValue) {
3025
+ setPrevValue(value);
3026
+ setHsl(hexToHsl(value));
3018
3027
  setHexInput(value);
3019
- }, [value]);
3028
+ }
3020
3029
  const emitChange = useCallback(
3021
3030
  (newHsl) => {
3022
3031
  setHsl(newHsl);
@@ -3250,17 +3259,15 @@ function useFormContext() {
3250
3259
  function FormWrapper({ onSubmit, validationMode = "onSubmit", children, className, ref }) {
3251
3260
  const [errors, setErrors] = useState({});
3252
3261
  const [submitted, setSubmitted] = useState(false);
3253
- const errorsRef = useRef(errors);
3254
- errorsRef.current = errors;
3262
+ const errorsRef = useLatest(errors);
3255
3263
  const pendingSubmit = useRef(false);
3256
- const onSubmitRef = useRef(onSubmit);
3257
- onSubmitRef.current = onSubmit;
3264
+ const onSubmitRef = useLatest(onSubmit);
3258
3265
  useEffect(() => {
3259
3266
  if (pendingSubmit.current && submitted) {
3260
3267
  pendingSubmit.current = false;
3261
3268
  onSubmitRef.current(errorsRef.current);
3262
3269
  }
3263
- }, [submitted, errors]);
3270
+ }, [submitted, errors, errorsRef, onSubmitRef]);
3264
3271
  const setError = useCallback((field, message) => {
3265
3272
  setErrors((prev) => ({ ...prev, [field]: message }));
3266
3273
  }, []);
@@ -5818,9 +5825,12 @@ function MonthDropdown({
5818
5825
  offset: 8,
5819
5826
  flip: true
5820
5827
  });
5821
- useEffect(() => {
5828
+ const browseSyncKey = open ? currentDate.getTime() : null;
5829
+ const [prevBrowseSyncKey, setPrevBrowseSyncKey] = useState(browseSyncKey);
5830
+ if (browseSyncKey !== prevBrowseSyncKey) {
5831
+ setPrevBrowseSyncKey(browseSyncKey);
5822
5832
  if (open) setBrowseYear(currentDate.getFullYear());
5823
- }, [open, currentDate]);
5833
+ }
5824
5834
  useEffect(() => {
5825
5835
  if (!open) return;
5826
5836
  const onKey = (e) => {
@@ -5938,9 +5948,12 @@ function YearDropdown({
5938
5948
  });
5939
5949
  const currentYear = currentDate.getFullYear();
5940
5950
  const [rangeStart, setRangeStart] = useState(() => currentYear - 4);
5941
- useEffect(() => {
5951
+ const rangeSyncKey = open ? currentYear : null;
5952
+ const [prevRangeSyncKey, setPrevRangeSyncKey] = useState(rangeSyncKey);
5953
+ if (rangeSyncKey !== prevRangeSyncKey) {
5954
+ setPrevRangeSyncKey(rangeSyncKey);
5942
5955
  if (open) setRangeStart(currentYear - 4);
5943
- }, [open, currentYear]);
5956
+ }
5944
5957
  useEffect(() => {
5945
5958
  if (!open) return;
5946
5959
  const onKey = (e) => {
@@ -6662,6 +6675,13 @@ function TruncLabel2({ children }) {
6662
6675
  if (!isTruncated) return label;
6663
6676
  return /* @__PURE__ */ jsx(Tooltip, { content: children, children: label });
6664
6677
  }
6678
+ function allDescendantIds(node) {
6679
+ const ids = [node.id];
6680
+ if (node.children) {
6681
+ for (const child of node.children) ids.push(...allDescendantIds(child));
6682
+ }
6683
+ return ids;
6684
+ }
6665
6685
  function TreeView({
6666
6686
  nodes,
6667
6687
  selectable,
@@ -6704,13 +6724,6 @@ function TreeView({
6704
6724
  },
6705
6725
  [selectable, multiSelect, onSelect]
6706
6726
  );
6707
- const allDescendantIds = useCallback((node) => {
6708
- const ids = [node.id];
6709
- if (node.children) {
6710
- for (const child of node.children) ids.push(...allDescendantIds(child));
6711
- }
6712
- return ids;
6713
- }, []);
6714
6727
  const handleCheck = useCallback(
6715
6728
  (node) => {
6716
6729
  if (!checkable) return;
@@ -6726,7 +6739,7 @@ function TreeView({
6726
6739
  return next;
6727
6740
  });
6728
6741
  },
6729
- [checkable, allDescendantIds, onCheck]
6742
+ [checkable, onCheck]
6730
6743
  );
6731
6744
  const getCheckState = useCallback(
6732
6745
  (node) => {
@@ -6737,7 +6750,7 @@ function TreeView({
6737
6750
  if (checkedCount > 0) return "indeterminate";
6738
6751
  return "unchecked";
6739
6752
  },
6740
- [checked, allDescendantIds]
6753
+ [checked]
6741
6754
  );
6742
6755
  const flatNodes = useCallback(() => {
6743
6756
  const result = [];
@@ -7845,6 +7858,11 @@ var Breadcrumbs = forwardRef(function Breadcrumbs2({
7845
7858
  function Popover({ open, onOpenChange, trigger, children, side = "bottom", align = "center", width, className, container }) {
7846
7859
  const triggerRef = useRef(null);
7847
7860
  const floatingRef = useRef(null);
7861
+ const [triggerWidth, setTriggerWidth] = useState(void 0);
7862
+ const setTriggerNode = useCallback((node) => {
7863
+ triggerRef.current = node;
7864
+ setTriggerWidth(node?.offsetWidth);
7865
+ }, []);
7848
7866
  const pos = usePosition(triggerRef, floatingRef, open, { side, align, offset: 8, flip: true });
7849
7867
  useEffect(() => {
7850
7868
  if (!open) return;
@@ -7870,8 +7888,11 @@ function Popover({ open, onOpenChange, trigger, children, side = "bottom", align
7870
7888
  /* @__PURE__ */ jsx(
7871
7889
  Slot,
7872
7890
  {
7873
- ref: triggerRef,
7874
- onClick: () => onOpenChange(!open),
7891
+ ref: setTriggerNode,
7892
+ onClick: () => {
7893
+ setTriggerWidth(triggerRef.current?.offsetWidth);
7894
+ onOpenChange(!open);
7895
+ },
7875
7896
  children: trigger
7876
7897
  }
7877
7898
  ),
@@ -7891,7 +7912,7 @@ function Popover({ open, onOpenChange, trigger, children, side = "bottom", align
7891
7912
  style: {
7892
7913
  top: pos.top,
7893
7914
  left: pos.left,
7894
- width: width === "trigger" ? triggerRef.current?.offsetWidth : width === "auto" ? void 0 : width
7915
+ width: width === "trigger" ? triggerWidth : width === "auto" ? void 0 : width
7895
7916
  },
7896
7917
  children
7897
7918
  }
@@ -8507,10 +8528,7 @@ function CompactFlyout({
8507
8528
  onMouseEnter,
8508
8529
  onMouseLeave
8509
8530
  }) {
8510
- const [pos, setPos] = useState(() => computeCompactFlyoutPos(anchorEl, items.length));
8511
- useLayoutEffect(() => {
8512
- setPos(computeCompactFlyoutPos(anchorEl, items.length));
8513
- }, [anchorEl, items.length]);
8531
+ const pos = useMemo(() => computeCompactFlyoutPos(anchorEl, items.length), [anchorEl, items.length]);
8514
8532
  useEffect(() => {
8515
8533
  function handler(e) {
8516
8534
  if (e.key === "Escape") onClose();
@@ -8900,7 +8918,7 @@ function CompactNavItem({
8900
8918
  expanded,
8901
8919
  onToggleExpand
8902
8920
  }) {
8903
- const triggerRef = useRef(null);
8921
+ const [triggerEl, setTriggerEl] = useState(null);
8904
8922
  const handleMouseEnter = useCallback(() => {
8905
8923
  onCancelClose();
8906
8924
  if (hasChildren && !isOpen) onOpen(flyoutKey);
@@ -8919,7 +8937,7 @@ function CompactNavItem({
8919
8937
  /* @__PURE__ */ jsxs(
8920
8938
  "button",
8921
8939
  {
8922
- ref: triggerRef,
8940
+ ref: setTriggerEl,
8923
8941
  onMouseEnter: !mobile && hasChildren ? handleMouseEnter : void 0,
8924
8942
  onMouseLeave: !mobile && hasChildren && isOpen ? handleMouseLeave : void 0,
8925
8943
  onClick: handleClick,
@@ -8953,12 +8971,12 @@ function CompactNavItem({
8953
8971
  ]
8954
8972
  }
8955
8973
  ),
8956
- !mobile && isOpen && hasChildren && item.children && triggerRef.current && /* @__PURE__ */ jsx(
8974
+ !mobile && isOpen && hasChildren && item.children && triggerEl && /* @__PURE__ */ jsx(
8957
8975
  CompactFlyout,
8958
8976
  {
8959
8977
  parentLabel: item.label,
8960
8978
  items: item.children,
8961
- anchorEl: triggerRef.current,
8979
+ anchorEl: triggerEl,
8962
8980
  portalContainer,
8963
8981
  onItemClick,
8964
8982
  onClose,
@@ -9948,6 +9966,187 @@ function Dock({
9948
9966
  }
9949
9967
  );
9950
9968
  }
9969
+ function Sheet({ open, onClose, title, children, footer, snapPoints = [0.5, 0.92], container, flush }) {
9970
+ const titleId = useId();
9971
+ const panelRef = useRef(null);
9972
+ const [mounted, setMounted] = useState(open);
9973
+ const [snapIdx, setSnapIdx] = useState(0);
9974
+ const [dragOffset, setDragOffset] = useState(0);
9975
+ const [dragging, setDragging] = useState(false);
9976
+ const dragStartY = useRef(0);
9977
+ const startHeight = useRef(0);
9978
+ const onCloseRef = useLatest(onClose);
9979
+ const [prevOpen, setPrevOpen] = useState(open);
9980
+ if (open !== prevOpen) {
9981
+ setPrevOpen(open);
9982
+ if (open) {
9983
+ setMounted(true);
9984
+ setSnapIdx(0);
9985
+ setDragOffset(0);
9986
+ }
9987
+ }
9988
+ const viewportHeight = useCallback(
9989
+ () => container instanceof HTMLElement ? container.clientHeight : window.innerHeight,
9990
+ [container]
9991
+ );
9992
+ function handleTransitionEnd() {
9993
+ if (!open) setMounted(false);
9994
+ }
9995
+ useReturnFocus(open);
9996
+ useEffect(() => {
9997
+ if (!open || !mounted) return;
9998
+ const raf = requestAnimationFrame(() => focusInitial(panelRef.current));
9999
+ return () => cancelAnimationFrame(raf);
10000
+ }, [open, mounted]);
10001
+ useEffect(() => {
10002
+ if (!open) return;
10003
+ const onKeyDown = (e) => {
10004
+ if (e.key === "Escape") onCloseRef.current();
10005
+ };
10006
+ document.addEventListener("keydown", onKeyDown);
10007
+ if (!container) lockBodyScroll();
10008
+ return () => {
10009
+ document.removeEventListener("keydown", onKeyDown);
10010
+ if (!container) unlockBodyScroll();
10011
+ };
10012
+ }, [open, container, onCloseRef]);
10013
+ const handlePointerDown = useCallback((e) => {
10014
+ setDragging(true);
10015
+ dragStartY.current = e.clientY;
10016
+ startHeight.current = (snapPoints[snapIdx] ?? snapPoints[0]) * viewportHeight();
10017
+ setDragOffset(0);
10018
+ e.target.setPointerCapture(e.pointerId);
10019
+ }, [snapIdx, snapPoints, viewportHeight]);
10020
+ const handlePointerMove = useCallback((e) => {
10021
+ if (!dragging) return;
10022
+ const delta = dragStartY.current - e.clientY;
10023
+ setDragOffset(delta);
10024
+ }, [dragging]);
10025
+ const handlePointerUp = useCallback(() => {
10026
+ if (!dragging) return;
10027
+ setDragging(false);
10028
+ const currentHeightPx = startHeight.current + dragOffset;
10029
+ const currentFraction = currentHeightPx / viewportHeight();
10030
+ if (currentFraction < snapPoints[0] * 0.6) {
10031
+ onClose();
10032
+ setDragOffset(0);
10033
+ return;
10034
+ }
10035
+ let bestIdx = 0;
10036
+ let bestDist = Math.abs(currentFraction - snapPoints[0]);
10037
+ for (let i = 1; i < snapPoints.length; i++) {
10038
+ const dist = Math.abs(currentFraction - snapPoints[i]);
10039
+ if (dist < bestDist) {
10040
+ bestDist = dist;
10041
+ bestIdx = i;
10042
+ }
10043
+ }
10044
+ setSnapIdx(bestIdx);
10045
+ setDragOffset(0);
10046
+ }, [dragging, dragOffset, snapPoints, onClose, viewportHeight]);
10047
+ if (!mounted) return null;
10048
+ const baseHeight = (snapPoints[snapIdx] ?? snapPoints[0]) * 100;
10049
+ const heightUnit = container ? "%" : "vh";
10050
+ const dragPx = dragging ? dragOffset : 0;
10051
+ const positionClass = container ? "absolute" : "fixed";
10052
+ return createPortal(
10053
+ /* @__PURE__ */ jsxs(
10054
+ "div",
10055
+ {
10056
+ className: cn(
10057
+ `${positionClass} inset-0 z-[var(--ds-z-modal)] transition-opacity duration-[var(--ds-transition-speed)]`,
10058
+ open ? "pointer-events-auto" : "pointer-events-none"
10059
+ ),
10060
+ onTransitionEnd: handleTransitionEnd,
10061
+ children: [
10062
+ /* @__PURE__ */ jsx(
10063
+ "div",
10064
+ {
10065
+ className: cn(
10066
+ "absolute inset-0 bg-black/50 transition-opacity duration-[var(--ds-transition-speed)]",
10067
+ open ? "opacity-100" : "opacity-0"
10068
+ ),
10069
+ onClick: onClose
10070
+ }
10071
+ ),
10072
+ /* @__PURE__ */ jsxs(
10073
+ "div",
10074
+ {
10075
+ ref: panelRef,
10076
+ role: "dialog",
10077
+ "aria-modal": "true",
10078
+ "aria-labelledby": title ? titleId : void 0,
10079
+ "data-ds-sheet": "",
10080
+ className: cn(
10081
+ `${positionClass} bottom-0 left-0 right-0 z-10 flex flex-col`,
10082
+ "bg-[var(--ds-bg-surface)] text-[var(--ds-text-primary)]",
10083
+ GLASS_OVERLAY,
10084
+ "border-t-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
10085
+ "rounded-t-[var(--ds-radius-xl)]",
10086
+ !dragging && "transition-[transform,height] duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
10087
+ open ? "translate-y-0" : "translate-y-full"
10088
+ ),
10089
+ style: {
10090
+ containerType: "inline-size",
10091
+ height: dragPx ? `calc(${baseHeight}${heightUnit} + ${dragPx}px)` : `${baseHeight}${heightUnit}`
10092
+ },
10093
+ children: [
10094
+ /* @__PURE__ */ jsx(
10095
+ "div",
10096
+ {
10097
+ className: "flex justify-center pt-ds-2 pb-ds-1 cursor-grab active:cursor-grabbing touch-none",
10098
+ onPointerDown: handlePointerDown,
10099
+ onPointerMove: handlePointerMove,
10100
+ onPointerUp: handlePointerUp,
10101
+ onPointerCancel: handlePointerUp,
10102
+ children: /* @__PURE__ */ jsx("div", { className: "w-10 h-1 rounded-[var(--ds-radius-full)] bg-[var(--ds-border-default)]" })
10103
+ }
10104
+ ),
10105
+ title && /* @__PURE__ */ jsxs(
10106
+ "div",
10107
+ {
10108
+ className: "flex items-center justify-between shrink-0 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
10109
+ style: { padding: "var(--ds-space-2) var(--ds-space-5)" },
10110
+ children: [
10111
+ /* @__PURE__ */ jsx("h2", { id: titleId, className: "font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] truncate", children: title }),
10112
+ /* @__PURE__ */ jsx(
10113
+ "button",
10114
+ {
10115
+ onClick: onClose,
10116
+ ...{ [OVERLAY_CLOSE_ATTR]: "" },
10117
+ className: "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer",
10118
+ "aria-label": "Close",
10119
+ children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 18 })
10120
+ }
10121
+ )
10122
+ ]
10123
+ }
10124
+ ),
10125
+ /* @__PURE__ */ jsx(
10126
+ "div",
10127
+ {
10128
+ className: "flex-1 overflow-y-auto",
10129
+ style: flush ? void 0 : { padding: "var(--ds-space-5)" },
10130
+ children
10131
+ }
10132
+ ),
10133
+ footer && /* @__PURE__ */ jsx(
10134
+ "div",
10135
+ {
10136
+ className: "shrink-0 flex items-center justify-end gap-ds-2 border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
10137
+ style: { padding: "var(--ds-space-3) var(--ds-space-5)" },
10138
+ children: footer
10139
+ }
10140
+ )
10141
+ ]
10142
+ }
10143
+ )
10144
+ ]
10145
+ }
10146
+ ),
10147
+ container ?? document.body
10148
+ );
10149
+ }
9951
10150
  var slideClasses = {
9952
10151
  right: {
9953
10152
  panel: "top-0 right-0 h-full",
@@ -9970,19 +10169,23 @@ var borderByPosition = {
9970
10169
  left: "border-r-[length:var(--ds-border-width)] border-r-[var(--ds-border-default)]",
9971
10170
  bottom: "border-t-[length:var(--ds-border-width)] border-t-[var(--ds-border-default)]"
9972
10171
  };
9973
- function Drawer({ open, onClose, position = "right", width = "400px", title, children, footer, container, flush, lightBackdrop, topOffset }) {
10172
+ function Drawer(props) {
10173
+ const { position = "right", mobile, snapPoints, open, onClose, title, children, footer, container, flush } = props;
10174
+ const isPhone = useBreakpoint("sm");
10175
+ const mobileMode = mobile ?? (position === "left" ? "panel" : "sheet");
10176
+ if (isPhone && mobileMode === "sheet") {
10177
+ return /* @__PURE__ */ jsx(Sheet, { open, onClose, title, footer, container, flush, snapPoints, children });
10178
+ }
10179
+ return /* @__PURE__ */ jsx(DrawerPanel, { ...props });
10180
+ }
10181
+ function DrawerPanel({ open, onClose, position = "right", width = "400px", title, children, footer, container, flush, lightBackdrop, topOffset }) {
9974
10182
  const titleId = useId();
9975
10183
  const { ref: titleOverflowRef, isTruncated: titleTruncated } = useTextOverflow({ lines: 1 });
9976
10184
  const isMobile = useBreakpoint("sm");
9977
10185
  const panelRef = useRef(null);
9978
- const [mounted, setMounted] = useState(false);
9979
- const onCloseRef = useRef(onClose);
9980
- onCloseRef.current = onClose;
9981
- useEffect(() => {
9982
- if (open) {
9983
- setMounted(true);
9984
- }
9985
- }, [open]);
10186
+ const [mounted, setMounted] = useState(open);
10187
+ const onCloseRef = useLatest(onClose);
10188
+ if (open && !mounted) setMounted(true);
9986
10189
  function handleTransitionEnd() {
9987
10190
  if (!open) setMounted(false);
9988
10191
  }
@@ -9997,7 +10200,7 @@ function Drawer({ open, onClose, position = "right", width = "400px", title, chi
9997
10200
  document.removeEventListener("keydown", onKeyDown);
9998
10201
  if (!container) unlockBodyScroll();
9999
10202
  };
10000
- }, [open, container]);
10203
+ }, [open, container, onCloseRef]);
10001
10204
  useReturnFocus(open);
10002
10205
  useEffect(() => {
10003
10206
  if (!open || !mounted) return;
@@ -10132,13 +10335,13 @@ var AppShell = forwardRef(function AppShell2({
10132
10335
  const hasMobileMenu = mobileMenu != null;
10133
10336
  const hasMobileNav = mobileNav != null;
10134
10337
  const showMobileDrawer = hasMobileMenu && !(isSmallMobile && hasMobileNav);
10135
- const shellRef = useRef(null);
10338
+ const [shellEl, setShellEl] = useState(null);
10136
10339
  return /* @__PURE__ */ jsx(
10137
10340
  AppShellContext,
10138
10341
  {
10139
10342
  value: { hasSidebar, hasMobileMenu, hasMobileNav, sidebarOpen, setSidebarOpen, isMobile, isTablet },
10140
10343
  children: /* @__PURE__ */ jsxs("div", { ref: (el) => {
10141
- shellRef.current = el;
10344
+ setShellEl(el);
10142
10345
  if (typeof ref === "function") ref(el);
10143
10346
  else if (ref) ref.current = el;
10144
10347
  }, className: cn("relative h-screen flex flex-col bg-[var(--ds-bg-canvas)] overflow-hidden", className), children: [
@@ -10170,7 +10373,7 @@ var AppShell = forwardRef(function AppShell2({
10170
10373
  onClose: () => setSidebarOpen(false),
10171
10374
  position: "left",
10172
10375
  width: mobileMenuWidth ?? "var(--ds-drawer-width)",
10173
- container: shellRef.current ?? void 0,
10376
+ container: shellEl ?? void 0,
10174
10377
  flush: mobileMenuFlush,
10175
10378
  lightBackdrop: mobileMenuLightBackdrop,
10176
10379
  topOffset: layout !== "stacked" && navbar != null ? "var(--ds-navbar-height)" : void 0,
@@ -10649,8 +10852,7 @@ function Dialog({ open, onClose, title, description, children, footer, variant =
10649
10852
  const overlayRef = useRef(null);
10650
10853
  const dialogRef = useRef(null);
10651
10854
  const triggerRef = useRef(null);
10652
- const onCloseRef = useRef(onClose);
10653
- onCloseRef.current = onClose;
10855
+ const onCloseRef = useLatest(onClose);
10654
10856
  const trapFocus = useCallback((e) => {
10655
10857
  if (e.key !== "Tab" || !dialogRef.current) return;
10656
10858
  const focusable = dialogRef.current.querySelectorAll(FOCUSABLE);
@@ -10686,7 +10888,7 @@ function Dialog({ open, onClose, title, description, children, footer, variant =
10686
10888
  return () => {
10687
10889
  document.removeEventListener("keydown", handler);
10688
10890
  };
10689
- }, [open, trapFocus, alert]);
10891
+ }, [open, trapFocus, alert, onCloseRef]);
10690
10892
  useEffect(() => {
10691
10893
  if (!open) return;
10692
10894
  lockBodyScroll();
@@ -10798,16 +11000,22 @@ function CommandPalette({ open, onOpenChange, commands, placeholder = "Type a co
10798
11000
  groups.get(g).push(cmd);
10799
11001
  }
10800
11002
  const flatFiltered = filtered;
10801
- useEffect(() => {
11003
+ const [prevOpen, setPrevOpen] = useState(open);
11004
+ if (open !== prevOpen) {
11005
+ setPrevOpen(open);
10802
11006
  if (open) {
10803
11007
  setQuery("");
10804
11008
  setSelectedIdx(0);
10805
- requestAnimationFrame(() => inputRef.current?.focus());
10806
11009
  }
10807
- }, [open]);
10808
- useEffect(() => {
11010
+ }
11011
+ const [prevQuery, setPrevQuery] = useState(query);
11012
+ if (query !== prevQuery) {
11013
+ setPrevQuery(query);
10809
11014
  setSelectedIdx(0);
10810
- }, [query]);
11015
+ }
11016
+ useEffect(() => {
11017
+ if (open) requestAnimationFrame(() => inputRef.current?.focus());
11018
+ }, [open]);
10811
11019
  useEffect(() => {
10812
11020
  if (!open) return;
10813
11021
  const handler = (e) => {
@@ -10823,23 +11031,20 @@ function CommandPalette({ open, onOpenChange, commands, placeholder = "Type a co
10823
11031
  },
10824
11032
  [onOpenChange]
10825
11033
  );
10826
- const handleKeyDown = useCallback(
10827
- (e) => {
10828
- if (e.key === "ArrowDown") {
10829
- e.preventDefault();
10830
- setSelectedIdx((prev) => Math.min(prev + 1, flatFiltered.length - 1));
10831
- listRef.current?.children[Math.min(selectedIdx + 1, flatFiltered.length - 1)]?.scrollIntoView({ block: "nearest" });
10832
- } else if (e.key === "ArrowUp") {
10833
- e.preventDefault();
10834
- setSelectedIdx((prev) => Math.max(prev - 1, 0));
10835
- listRef.current?.children[Math.max(selectedIdx - 1, 0)]?.scrollIntoView({ block: "nearest" });
10836
- } else if (e.key === "Enter" && flatFiltered[selectedIdx]) {
10837
- e.preventDefault();
10838
- handleSelect(flatFiltered[selectedIdx]);
10839
- }
10840
- },
10841
- [flatFiltered, selectedIdx, handleSelect]
10842
- );
11034
+ const handleKeyDown = (e) => {
11035
+ if (e.key === "ArrowDown") {
11036
+ e.preventDefault();
11037
+ setSelectedIdx((prev) => Math.min(prev + 1, flatFiltered.length - 1));
11038
+ listRef.current?.children[Math.min(selectedIdx + 1, flatFiltered.length - 1)]?.scrollIntoView({ block: "nearest" });
11039
+ } else if (e.key === "ArrowUp") {
11040
+ e.preventDefault();
11041
+ setSelectedIdx((prev) => Math.max(prev - 1, 0));
11042
+ listRef.current?.children[Math.max(selectedIdx - 1, 0)]?.scrollIntoView({ block: "nearest" });
11043
+ } else if (e.key === "Enter" && flatFiltered[selectedIdx]) {
11044
+ e.preventDefault();
11045
+ handleSelect(flatFiltered[selectedIdx]);
11046
+ }
11047
+ };
10843
11048
  if (!open) return null;
10844
11049
  let flatIdx = 0;
10845
11050
  return createPortal(
@@ -11132,179 +11337,6 @@ function ContextMenu({ items, children, className, ref, container }) {
11132
11337
  }
11133
11338
  );
11134
11339
  }
11135
- function Sheet({ open, onClose, title, children, footer, snapPoints = [0.5, 0.92] }) {
11136
- const titleId = useId();
11137
- const panelRef = useRef(null);
11138
- const [mounted, setMounted] = useState(false);
11139
- const [snapIdx, setSnapIdx] = useState(0);
11140
- const [dragOffset, setDragOffset] = useState(0);
11141
- const isDragging = useRef(false);
11142
- const dragStartY = useRef(0);
11143
- const startHeight = useRef(0);
11144
- const onCloseRef = useRef(onClose);
11145
- onCloseRef.current = onClose;
11146
- useEffect(() => {
11147
- if (open) {
11148
- setMounted(true);
11149
- setSnapIdx(0);
11150
- setDragOffset(0);
11151
- }
11152
- }, [open]);
11153
- function handleTransitionEnd() {
11154
- if (!open) setMounted(false);
11155
- }
11156
- useReturnFocus(open);
11157
- useEffect(() => {
11158
- if (!open || !mounted) return;
11159
- const raf = requestAnimationFrame(() => focusInitial(panelRef.current));
11160
- return () => cancelAnimationFrame(raf);
11161
- }, [open, mounted]);
11162
- useEffect(() => {
11163
- if (!open) return;
11164
- const onKeyDown = (e) => {
11165
- if (e.key === "Escape") onCloseRef.current();
11166
- };
11167
- document.addEventListener("keydown", onKeyDown);
11168
- lockBodyScroll();
11169
- return () => {
11170
- document.removeEventListener("keydown", onKeyDown);
11171
- unlockBodyScroll();
11172
- };
11173
- }, [open]);
11174
- const handlePointerDown = useCallback((e) => {
11175
- isDragging.current = true;
11176
- dragStartY.current = e.clientY;
11177
- startHeight.current = (snapPoints[snapIdx] ?? snapPoints[0]) * window.innerHeight;
11178
- setDragOffset(0);
11179
- e.target.setPointerCapture(e.pointerId);
11180
- }, [snapIdx, snapPoints]);
11181
- const handlePointerMove = useCallback((e) => {
11182
- if (!isDragging.current) return;
11183
- const delta = dragStartY.current - e.clientY;
11184
- setDragOffset(delta);
11185
- }, []);
11186
- const handlePointerUp = useCallback(() => {
11187
- if (!isDragging.current) return;
11188
- isDragging.current = false;
11189
- const currentHeightPx = startHeight.current + dragOffset;
11190
- const currentFraction = currentHeightPx / window.innerHeight;
11191
- if (currentFraction < snapPoints[0] * 0.6) {
11192
- onClose();
11193
- setDragOffset(0);
11194
- return;
11195
- }
11196
- let bestIdx = 0;
11197
- let bestDist = Math.abs(currentFraction - snapPoints[0]);
11198
- for (let i = 1; i < snapPoints.length; i++) {
11199
- const dist = Math.abs(currentFraction - snapPoints[i]);
11200
- if (dist < bestDist) {
11201
- bestDist = dist;
11202
- bestIdx = i;
11203
- }
11204
- }
11205
- setSnapIdx(bestIdx);
11206
- setDragOffset(0);
11207
- }, [dragOffset, snapPoints, onClose]);
11208
- if (!mounted) return null;
11209
- const baseHeightVh = (snapPoints[snapIdx] ?? snapPoints[0]) * 100;
11210
- const dragPx = isDragging.current ? dragOffset : 0;
11211
- return createPortal(
11212
- /* @__PURE__ */ jsxs(
11213
- "div",
11214
- {
11215
- className: cn(
11216
- "fixed inset-0 z-[var(--ds-z-modal)] transition-opacity duration-[var(--ds-transition-speed)]",
11217
- open ? "pointer-events-auto" : "pointer-events-none"
11218
- ),
11219
- onTransitionEnd: handleTransitionEnd,
11220
- children: [
11221
- /* @__PURE__ */ jsx(
11222
- "div",
11223
- {
11224
- className: cn(
11225
- "absolute inset-0 bg-black/50 transition-opacity duration-[var(--ds-transition-speed)]",
11226
- open ? "opacity-100" : "opacity-0"
11227
- ),
11228
- onClick: onClose
11229
- }
11230
- ),
11231
- /* @__PURE__ */ jsxs(
11232
- "div",
11233
- {
11234
- ref: panelRef,
11235
- role: "dialog",
11236
- "aria-modal": "true",
11237
- "aria-labelledby": title ? titleId : void 0,
11238
- className: cn(
11239
- "fixed bottom-0 left-0 right-0 z-10 flex flex-col",
11240
- "bg-[var(--ds-bg-surface)] text-[var(--ds-text-primary)]",
11241
- GLASS_OVERLAY,
11242
- "border-t-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
11243
- "rounded-t-[var(--ds-radius-xl)]",
11244
- !isDragging.current && "transition-[transform,height] duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
11245
- open ? "translate-y-0" : "translate-y-full"
11246
- ),
11247
- style: {
11248
- containerType: "inline-size",
11249
- height: dragPx ? `calc(${baseHeightVh}vh + ${dragPx}px)` : `${baseHeightVh}vh`
11250
- },
11251
- children: [
11252
- /* @__PURE__ */ jsx(
11253
- "div",
11254
- {
11255
- className: "flex justify-center pt-ds-2 pb-ds-1 cursor-grab active:cursor-grabbing touch-none",
11256
- onPointerDown: handlePointerDown,
11257
- onPointerMove: handlePointerMove,
11258
- onPointerUp: handlePointerUp,
11259
- onPointerCancel: handlePointerUp,
11260
- children: /* @__PURE__ */ jsx("div", { className: "w-10 h-1 rounded-[var(--ds-radius-full)] bg-[var(--ds-border-default)]" })
11261
- }
11262
- ),
11263
- title && /* @__PURE__ */ jsxs(
11264
- "div",
11265
- {
11266
- className: "flex items-center justify-between shrink-0 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
11267
- style: { padding: "var(--ds-space-2) var(--ds-space-5)" },
11268
- children: [
11269
- /* @__PURE__ */ jsx("h2", { id: titleId, className: "font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] truncate", children: title }),
11270
- /* @__PURE__ */ jsx(
11271
- "button",
11272
- {
11273
- onClick: onClose,
11274
- ...{ [OVERLAY_CLOSE_ATTR]: "" },
11275
- className: "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer",
11276
- "aria-label": "Close",
11277
- children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 18 })
11278
- }
11279
- )
11280
- ]
11281
- }
11282
- ),
11283
- /* @__PURE__ */ jsx(
11284
- "div",
11285
- {
11286
- className: "flex-1 overflow-y-auto",
11287
- style: { padding: "var(--ds-space-5)" },
11288
- children
11289
- }
11290
- ),
11291
- footer && /* @__PURE__ */ jsx(
11292
- "div",
11293
- {
11294
- className: "shrink-0 flex items-center justify-end gap-ds-2 border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
11295
- style: { padding: "var(--ds-space-3) var(--ds-space-5)" },
11296
- children: footer
11297
- }
11298
- )
11299
- ]
11300
- }
11301
- )
11302
- ]
11303
- }
11304
- ),
11305
- document.body
11306
- );
11307
- }
11308
11340
  function TruncTitle({ children }) {
11309
11341
  const { ref, isTruncated } = useTextOverflow({ lines: 1 });
11310
11342
  const el = /* @__PURE__ */ jsx("span", { ref, className: "truncate", children });
@@ -11335,7 +11367,10 @@ function NotificationCenter({
11335
11367
  const [internalFilter, setInternalFilter] = useState("all");
11336
11368
  const isControlled = controlledOpen !== void 0;
11337
11369
  const open = isControlled ? controlledOpen : internalOpen;
11338
- const setOpen = isControlled ? (v) => onOpenChange?.(v) : setInternalOpen;
11370
+ const setOpen = useCallback((v) => {
11371
+ if (isControlled) onOpenChange?.(v);
11372
+ else setInternalOpen(v);
11373
+ }, [isControlled, onOpenChange]);
11339
11374
  const triggerRef = useRef(null);
11340
11375
  const panelRef = useRef(null);
11341
11376
  const pos = usePosition(triggerRef, panelRef, open, { side: "bottom", align: "end", offset: 8, flip: true });
@@ -11359,7 +11394,7 @@ function NotificationCenter({
11359
11394
  document.removeEventListener("keydown", onKey);
11360
11395
  document.removeEventListener("mousedown", onClick);
11361
11396
  };
11362
- }, [open]);
11397
+ }, [open, setOpen]);
11363
11398
  const grouped = groupByDate(filteredNotifications);
11364
11399
  if (variant === "page") {
11365
11400
  return /* @__PURE__ */ jsxs("div", { ref, className: cn("w-full", className), children: [
@@ -11606,11 +11641,13 @@ function Tour({ steps, open, onClose, onComplete, container }) {
11606
11641
  if (el) setRect(el.getBoundingClientRect());
11607
11642
  else setRect(null);
11608
11643
  }, [step]);
11644
+ const [prevOpen, setPrevOpen] = useState(open);
11645
+ if (open !== prevOpen) {
11646
+ setPrevOpen(open);
11647
+ if (!open) setCurrent(0);
11648
+ }
11609
11649
  useEffect(() => {
11610
- if (!open) {
11611
- setCurrent(0);
11612
- return;
11613
- }
11650
+ if (!open) return;
11614
11651
  measureTarget();
11615
11652
  window.addEventListener("resize", measureTarget);
11616
11653
  window.addEventListener("scroll", measureTarget, true);
@@ -11619,16 +11656,6 @@ function Tour({ steps, open, onClose, onComplete, container }) {
11619
11656
  window.removeEventListener("scroll", measureTarget, true);
11620
11657
  };
11621
11658
  }, [open, current, measureTarget]);
11622
- useEffect(() => {
11623
- if (!open) return;
11624
- const onKey = (e) => {
11625
- if (e.key === "Escape") onClose();
11626
- else if (e.key === "ArrowRight") handleNext();
11627
- else if (e.key === "ArrowLeft") handlePrev();
11628
- };
11629
- document.addEventListener("keydown", onKey);
11630
- return () => document.removeEventListener("keydown", onKey);
11631
- });
11632
11659
  const handleNext = useCallback(() => {
11633
11660
  if (current < steps.length - 1) setCurrent((c) => c + 1);
11634
11661
  else {
@@ -11639,6 +11666,16 @@ function Tour({ steps, open, onClose, onComplete, container }) {
11639
11666
  const handlePrev = useCallback(() => {
11640
11667
  if (current > 0) setCurrent((c) => c - 1);
11641
11668
  }, [current]);
11669
+ useEffect(() => {
11670
+ if (!open) return;
11671
+ const onKey = (e) => {
11672
+ if (e.key === "Escape") onClose();
11673
+ else if (e.key === "ArrowRight") handleNext();
11674
+ else if (e.key === "ArrowLeft") handlePrev();
11675
+ };
11676
+ document.addEventListener("keydown", onKey);
11677
+ return () => document.removeEventListener("keydown", onKey);
11678
+ }, [open, onClose, handleNext, handlePrev]);
11642
11679
  if (!open || !step) return null;
11643
11680
  const padding = 8;
11644
11681
  const spotlightRadius = 8;
@@ -11942,15 +11979,14 @@ function Toast({ variant = "default", message, visible, onDismiss, duration = 3e
11942
11979
  const timerRef = useRef(void 0);
11943
11980
  const startedAtRef = useRef(0);
11944
11981
  const remainingRef = useRef(duration);
11945
- const onDismissRef = useRef(onDismiss);
11946
- onDismissRef.current = onDismiss;
11982
+ const onDismissRef = useLatest(onDismiss);
11947
11983
  const startTimer = useCallback((ms) => {
11948
11984
  const delay = ms ?? remainingRef.current;
11949
11985
  remainingRef.current = delay;
11950
11986
  startedAtRef.current = Date.now();
11951
11987
  if (!Number.isFinite(delay) || delay <= 0) return;
11952
11988
  timerRef.current = setTimeout(() => onDismissRef.current(), delay);
11953
- }, []);
11989
+ }, [onDismissRef]);
11954
11990
  const clearTimer = useCallback(() => {
11955
11991
  if (timerRef.current) {
11956
11992
  clearTimeout(timerRef.current);