@t007/utils 0.0.33 → 0.0.35

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.
@@ -38,12 +38,13 @@ var import_utils = require("sia-reactor/utils");
38
38
  // src/ts/core/dom.ts
39
39
  var import_utils2 = require("sia-reactor/utils");
40
40
  var import_utils3 = require("sia-reactor/utils");
41
- var INTERACTIVE_SELECTOR = ":is(button,[href],input:not([type='hidden']),select,textarea,details>summary,[contenteditable='true'],iframe,audio[controls],video[controls],[tabindex]):not([disabled],[tabindex='-1'],[data-focus-guard],[inert],[inert] *)";
41
+ var import_sia_reactor = require("sia-reactor");
42
+ var INTERACTIVE_SELECTOR = ":is(button,[href],input:not([type='hidden']),select,textarea,details>summary,[contenteditable],iframe,audio[controls],video[controls],[tabindex]):not([disabled],[tabindex='-1'],[data-focus-guard],[inert],[inert] *)";
42
43
  var isInteractive = (target) => target instanceof HTMLElement && target.matches(INTERACTIVE_SELECTOR);
43
44
 
44
45
  // src/ts/hooks/react/useScrollAssist.ts
45
- var import_sia_reactor = require("sia-reactor");
46
- function useScrollAssist(ref, { enabled = true, pxPerSecond = 80, assistClassName = "t007-scroll-assist", vertical = true, horizontal = true } = import_sia_reactor.NIL) {
46
+ var import_sia_reactor2 = require("sia-reactor");
47
+ function useScrollAssist(ref, { enabled = true, pxPerSecond = 80, assistClassName = "t007-scroll-assist", vertical = true, horizontal = true } = import_sia_reactor2.NIL) {
47
48
  const scrollId = (0, import_react.useRef)(null), last = (0, import_react.useRef)(performance.now()), assists = (0, import_react.useRef)({}), assistWidth = (0, import_react.useRef)(20), assistHeight = (0, import_react.useRef)(20);
48
49
  const update = (0, import_react.useCallback)(() => {
49
50
  const el = ref.current;
@@ -124,31 +125,30 @@ function useScrollAssist(ref, { enabled = true, pxPerSecond = 80, assistClassNam
124
125
  var import_react2 = require("react");
125
126
 
126
127
  // src/ts/hooks/vanilla/outsideClick.ts
127
- var import_sia_reactor2 = require("sia-reactor");
128
- function initOutsideClick(el, { enabled = false, onOutside = import_sia_reactor2.NOOP, outOnClick = true, outOnEscape = true, outOnFocusOut = false, allowBounds = false, allowInputs = false, root = window, scoped = true, capture = true } = import_sia_reactor2.NIL) {
129
- const stacks = t007._outsiders_stacks ??= /* @__PURE__ */ new WeakMap(), existing = (t007._outsiders ??= /* @__PURE__ */ new WeakMap()).get(el);
128
+ var import_sia_reactor3 = require("sia-reactor");
129
+ function initOutsideClick(el, { enabled = false, onOutside = import_sia_reactor3.NOOP, outOnClick = true, outOnEscape = true, outOnFocusOut = false, allowBounds = false, allowInputs = false, root = window, scoped = true, capture = true } = import_sia_reactor3.NIL) {
130
+ const stack = t007._outsiders_stack ??= [], existing = (t007._outsiders ??= /* @__PURE__ */ new WeakMap()).get(el);
130
131
  if (!enabled || existing) return existing ? existing : void 0;
131
132
  scoped = scoped && root instanceof HTMLElement, root = scoped ? root : root === document ? document : window;
132
- const stack = stacks.get(root) ?? [], onScopedOut = (e, t, p = e.touches?.[0] || e, rect = allowBounds ? el.getBoundingClientRect() : null) => {
133
+ const onScopedOut = (e, t, p = e.touches?.[0] || e, rect = allowBounds ? el.getBoundingClientRect() : null) => {
133
134
  if (stack.at(-1) !== el || (allowBounds ? p.clientX >= rect.left && p.clientX <= rect.right && p.clientY >= rect.top && p.clientY <= rect.bottom : el.contains(t))) return false;
134
135
  return (!scoped ? true : root.contains(t)) && onOutside(e);
135
136
  }, handleClick = ((e) => outOnClick && !(allowInputs && isInteractive(e.target)) && onScopedOut(e, e.target)), handleEscape = ((e) => outOnEscape && e.key === "Escape" && !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey && stack.at(-1) === el && onOutside(e)), handleFocusOut = (e) => outOnFocusOut && !el.contains(e.relatedTarget) && onScopedOut(e, e.relatedTarget);
136
137
  root.addEventListener("mousedown", handleClick, capture), root.addEventListener("touchstart", handleClick, { passive: true, capture });
137
138
  root.addEventListener("keydown", handleEscape, capture);
138
139
  el.addEventListener("focusout", handleFocusOut, capture);
139
- if (!stack.includes(el)) stack.push(el), stacks.set(root, stack);
140
140
  const destroy = () => {
141
141
  root.removeEventListener("mousedown", handleClick, capture), root.removeEventListener("touchstart", handleClick, capture);
142
142
  root.removeEventListener("keydown", handleEscape, capture);
143
143
  el.removeEventListener("focusout", handleFocusOut, capture);
144
144
  t007._outsiders.delete(el), stack.splice(stack.indexOf(el), 1);
145
145
  };
146
- return t007._outsiders.set(el, destroy), destroy;
146
+ return !stack.includes(el) && stack.push(el), t007._outsiders.set(el, destroy), destroy;
147
147
  }
148
148
 
149
149
  // src/ts/hooks/react/useOutsideClick.ts
150
- var import_sia_reactor3 = require("sia-reactor");
151
- function useOutsideClick(ref, config = import_sia_reactor3.NIL) {
150
+ var import_sia_reactor4 = require("sia-reactor");
151
+ function useOutsideClick(ref, config = import_sia_reactor4.NIL) {
152
152
  (0, import_react2.useEffect)(() => ref.current ? initOutsideClick(ref.current, config) : void 0, [ref, config.enabled, config.onOutside, config.outOnEscape, config.outOnClick, config.outOnFocusOut, config.allowBounds, config.allowInputs, config.root, config.scoped, config.capture]);
153
153
  }
154
154
 
@@ -200,7 +200,7 @@ var getGrid = (all, x = true, y = true, vY = true) => {
200
200
  };
201
201
 
202
202
  // src/ts/hooks/react/useArrowNavigation/consts.ts
203
- var import_sia_reactor4 = require("sia-reactor");
203
+ var import_sia_reactor5 = require("sia-reactor");
204
204
  var H_NAV_KEYS = ["ArrowRight", "ArrowLeft", "Home", "End"];
205
205
  var V_NAV_KEYS = ["ArrowUp", "ArrowDown", "PageDown", "PageUp"];
206
206
  var NAV_KEYS = [...H_NAV_KEYS, ...V_NAV_KEYS];
@@ -218,16 +218,16 @@ var DEFAULT_CONFIG = {
218
218
  rtl: null,
219
219
  grid: {},
220
220
  activeClass: "focus-outlined",
221
- inputSelector: "input,textarea,[contenteditable='true']",
221
+ inputSelector: "input,textarea,[contenteditable]",
222
222
  focusOptions: { preventScroll: false },
223
223
  scrollIntoView: { block: "nearest", inline: "nearest" },
224
- onSelect: import_sia_reactor4.NOOP,
225
- onFocusOut: import_sia_reactor4.NOOP
224
+ onSelect: import_sia_reactor5.NOOP,
225
+ onFocusOut: import_sia_reactor5.NOOP
226
226
  };
227
227
 
228
228
  // src/ts/hooks/react/useArrowNavigation/index.ts
229
229
  function useArrowNavigation(containerRef, config = {}) {
230
- const { enabled: isEnabled, selector, focusOnHover, loop, virtual, typeahead, rovingTab, resetMs, activeClass, inputSelector, defaultTabbableIndex, baseTabIndex, grid, rtl: isRtl, focusOptions, scrollIntoView, onSelect, onFocusOut } = { ...DEFAULT_CONFIG, ...config }, [gridX, setGridX] = (0, import_react3.useState)(grid.x || 1), [gridY, setGridY] = (0, import_react3.useState)(grid.y || 1), [vGridY, setVGridY] = (0, import_react3.useState)(grid.vY || 1), [activeIndex, setActiveIndex] = (0, import_react3.useState)(-1), buffer = (0, import_react3.useRef)(""), timeout = (0, import_react3.useRef)(null), itemsRef = (0, import_react3.useRef)([]), enabled = isEnabled ?? virtual, roving = rovingTab ?? !virtual, rtl = (0, import_react3.useMemo)(() => isRtl ?? ("undefined" === typeof document ? false : getComputedStyle(containerRef.current || document.body).direction === "rtl"), [containerRef, isRtl]), mutationObserverRef = (0, import_react3.useRef)(null), shouldSnub = (0, import_react3.useCallback)(() => !enabled || !containerRef.current, [enabled, containerRef]), isItemDisabled = (0, import_react3.useCallback)((el) => !el ? true : el.hasAttribute("disabled") || el.hasAttribute("aria-disabled"), []), getItems = (0, import_react3.useCallback)(() => itemsRef.current = Array.from(containerRef.current?.querySelectorAll(selector) || []), [containerRef, selector]);
230
+ const { enabled: isEnabled, selector, focusOnHover, loop, virtual, typeahead, rovingTab, resetMs, activeClass, inputSelector, defaultTabbableIndex, baseTabIndex, grid, rtl: isRtl, focusOptions, scrollIntoView, onSelect, onFocusOut } = { ...DEFAULT_CONFIG, ...config }, [gridX, setGridX] = (0, import_react3.useState)(grid.x || 1), [gridY, setGridY] = (0, import_react3.useState)(grid.y || 1), [vGridY, setVGridY] = (0, import_react3.useState)(grid.vY || 1), [activeIndex, setActiveIndex] = (0, import_react3.useState)(-1), buffer = (0, import_react3.useRef)(""), timeout = (0, import_react3.useRef)(null), itemsRef = (0, import_react3.useRef)([]), enabled = isEnabled ?? virtual, roving = rovingTab ?? !virtual, rtl = (0, import_react3.useMemo)(() => isRtl ?? ("undefined" === typeof document ? false : getComputedStyle(containerRef.current || document.body).direction === "rtl"), [containerRef, isRtl]), mutationObserverRef = (0, import_react3.useRef)(null), shouldSnub = (0, import_react3.useCallback)(() => !enabled || !containerRef.current, [enabled, containerRef]), isItemDisabled = (0, import_react3.useCallback)((el) => !el || el.matches('[disabled],[aria-disabled="true"],[inert],:disabled'), []), getItems = (0, import_react3.useCallback)(() => itemsRef.current = Array.from(containerRef.current?.querySelectorAll(selector) || []), [containerRef, selector]);
231
231
  const getAbleIndex = (0, import_react3.useCallback)(
232
232
  (targetIndex, e = { key: "ArrowRight", ctrlKey: false }) => {
233
233
  if (shouldSnub()) return;
@@ -301,8 +301,9 @@ function useArrowNavigation(containerRef, config = {}) {
301
301
  },
302
302
  [shouldSnub, virtual, activeIndex, gridX, gridY, vGridY, loop, rtl, goToIndex, typeahead, typeAhead]
303
303
  );
304
- const latest = (0, import_react3.useRef)({ getItems, updateDOM, activeIndex });
305
- (0, import_react3.useEffect)(() => void (latest.current = { getItems, updateDOM, activeIndex }), [getItems, updateDOM, activeIndex]);
304
+ const latest = (0, import_react3.useRef)({ getItems, updateDOM, activeIndex, calcGrid: () => {
305
+ } });
306
+ (0, import_react3.useEffect)(() => void (latest.current = { ...latest.current, getItems, updateDOM, activeIndex }), [getItems, updateDOM, activeIndex]);
306
307
  (0, import_react3.useEffect)(() => void getItems(), [getItems, enabled]);
307
308
  (0, import_react3.useEffect)(() => void (!enabled && setActiveIndex(-1)), [enabled]);
308
309
  (0, import_react3.useEffect)(() => updateDOM(), [activeIndex, updateDOM]);
@@ -331,8 +332,7 @@ function useArrowNavigation(containerRef, config = {}) {
331
332
  const all = itemsRef.current;
332
333
  const handleHover = (e) => {
333
334
  if (!focusOnHover) return;
334
- const el = e.currentTarget;
335
- const i = itemsRef.current?.indexOf(el);
335
+ const el = e.currentTarget, i = itemsRef.current?.indexOf(el);
336
336
  if (i !== -1) goToIndex(i);
337
337
  };
338
338
  for (const el of all) el.addEventListener("mouseenter", handleHover);
@@ -340,23 +340,23 @@ function useArrowNavigation(containerRef, config = {}) {
340
340
  for (const el of all) el.removeEventListener("mouseenter", handleHover);
341
341
  };
342
342
  }, [enabled, focusOnHover, goToIndex]);
343
+ const sync = (0, import_react3.useCallback)(() => {
344
+ const { getItems: getItems2, updateDOM: updateDOM2, activeIndex: activeIndex2 } = latest.current;
345
+ const oldEl = itemsRef.current[activeIndex2];
346
+ getItems2();
347
+ const newEl = itemsRef.current[activeIndex2];
348
+ updateDOM2();
349
+ if (oldEl && newEl && oldEl === newEl) return;
350
+ setActiveIndex(-1);
351
+ }, []);
343
352
  (0, import_react3.useEffect)(() => {
344
353
  if (shouldSnub()) return;
345
- const observer = mutationObserverRef.current = new MutationObserver(() => {
346
- const { getItems: getItems2, updateDOM: updateDOM2, activeIndex: activeIndex2 } = latest.current;
347
- const oldEl = itemsRef.current[activeIndex2];
348
- getItems2();
349
- const newEl = itemsRef.current[activeIndex2];
350
- updateDOM2();
351
- if (oldEl && newEl && oldEl === newEl) return;
352
- setActiveIndex(-1);
353
- });
354
+ const observer = mutationObserverRef.current = new MutationObserver(sync);
354
355
  observer.observe(containerRef.current, { childList: true, subtree: true });
355
356
  return () => observer.disconnect();
356
- }, [shouldSnub, containerRef]);
357
+ }, [shouldSnub, containerRef, sync]);
357
358
  (0, import_react3.useEffect)(() => {
358
359
  if (shouldSnub()) return;
359
- const all = itemsRef.current;
360
360
  const setGrid = (g) => {
361
361
  if (g.x) setGridX(g.x);
362
362
  if (g.y) setGridY(g.y);
@@ -364,26 +364,32 @@ function useArrowNavigation(containerRef, config = {}) {
364
364
  };
365
365
  setGrid(grid);
366
366
  if (grid.x && grid.y && grid.vY) return;
367
- const calcGrid = () => setGrid(getGrid(all, !grid.x, !grid.y, !grid.vY));
368
- calcGrid();
367
+ let ancestor = null;
369
368
  const resizeObserver = new ResizeObserver(() => calcGrid());
370
- resizeObserver.observe(getCommonAncestor(all[0], all[1]) ?? containerRef.current);
369
+ const calcGrid = () => {
370
+ setGrid(getGrid(itemsRef.current, !grid.x, !grid.y, !grid.vY));
371
+ const next = itemsRef.current.length > 1 ? getCommonAncestor(itemsRef.current[0], itemsRef.current[1]) : containerRef.current;
372
+ if (next && next !== ancestor) ancestor && resizeObserver.unobserve(ancestor), ancestor = next, resizeObserver.observe(ancestor);
373
+ };
374
+ latest.current.calcGrid = calcGrid;
375
+ calcGrid();
371
376
  return () => resizeObserver.disconnect();
372
377
  }, [shouldSnub, containerRef, grid]);
373
378
  (0, import_react3.useEffect)(() => void (timeout.current && clearTimeout(timeout.current)), []);
374
- return { gridX, gridY, vGridY, activeIndex, activeItem: (0, import_react3.useCallback)(() => itemsRef.current[activeIndex] ?? null, [activeIndex]), items: (0, import_react3.useCallback)(() => itemsRef.current, []), getAbleIndex, typeAhead, goToIndex, simulateKey };
379
+ return { gridX, gridY, vGridY, activeIndex, activeItem: (0, import_react3.useCallback)(() => itemsRef.current[activeIndex] ?? null, [activeIndex]), items: (0, import_react3.useCallback)(() => itemsRef.current, []), getAbleIndex, typeAhead, goToIndex, simulateKey, sync };
375
380
  }
376
381
 
377
382
  // src/ts/hooks/react/useFocusTrap.ts
378
383
  var import_react4 = require("react");
379
384
 
380
385
  // src/ts/hooks/vanilla/focusTrap.ts
381
- var import_sia_reactor5 = require("sia-reactor");
382
- function initFocusTrap(el, { enabled = false, initialSelector = "[data-autofocus]", ringClassName = "focus-outline", root = window, scoped = true, capture = true } = import_sia_reactor5.NIL) {
383
- const stacks = t007._ftrappers_stacks ??= /* @__PURE__ */ new WeakMap(), existing = (t007._ftrappers ??= /* @__PURE__ */ new WeakMap()).get(el);
386
+ var import_sia_reactor6 = require("sia-reactor");
387
+ function initFocusTrap(el, { enabled = false, initialSelector = "[data-autofocus]", ringClassName = "focus-outline", root = window, scoped = true, capture = true } = import_sia_reactor6.NIL) {
388
+ const stack = t007._ftrappers_stack ??= [], existing = (t007._ftrappers ??= /* @__PURE__ */ new WeakMap()).get(el);
384
389
  if (!enabled || existing) return existing ? existing : void 0;
385
390
  scoped = scoped && root instanceof HTMLElement, root = scoped ? root : root === document ? document : window;
386
- const stack = stacks.get(root) ?? [], focused = document.querySelector(":focus"), initial = el.querySelector(initialSelector), first = (0, import_utils2.createEl)("span", { tabIndex: 0 }, { focusGuard: "start" }, { position: "absolute", width: "0", height: "0", pointerEvents: "none" }), last = (0, import_utils2.createEl)("span", { tabIndex: 0 }, { focusGuard: "end" }, { position: "absolute", width: "0", height: "0", pointerEvents: "none" }), getFocusable = (c = el) => [...c.querySelectorAll(INTERACTIVE_SELECTOR)], resetFocus = (i = 0, els = getFocusable()) => els?.length ? els.at(i).focus() : (!el.hasAttribute("tabindex") && (el.tabIndex = -1), el.focus()), edgeFocus = (pre = false, rt = root) => {
391
+ let recovering = false;
392
+ const focused = document.querySelector(":focus"), initial = el.querySelector(initialSelector), first = (0, import_utils2.createEl)("span", { tabIndex: 0 }, { focusGuard: "start" }, { position: "absolute", width: "0", height: "0", pointerEvents: "none" }), last = (0, import_utils2.createEl)("span", { tabIndex: 0 }, { focusGuard: "end" }, { position: "absolute", width: "0", height: "0", pointerEvents: "none" }), getFocusable = (c = el) => [...c.querySelectorAll(INTERACTIVE_SELECTOR)], resetFocus = (i = 0, els = getFocusable()) => !recovering && (recovering = true, els?.length ? els.at(i).focus() : (!el.hasAttribute("tabindex") && (el.tabIndex = -1), el.focus()), recovering = false), edgeFocus = (pre = false, rt = root) => {
387
393
  if (!scoped) return resetFocus(pre ? -1 : 0);
388
394
  if (rt.hasAttribute("tabindex")) return rt.focus();
389
395
  const items = getFocusable();
@@ -393,28 +399,33 @@ function initFocusTrap(el, { enabled = false, initialSelector = "[data-autofocus
393
399
  while (p !== ceil && (!all.length || (pre ? rt.contains(all[0]) : rt.contains(all.at(-1))))) all = getFocusable(p = p.parentElement || ceil);
394
400
  for (let target, len = all.length, i = all.indexOf(items[pre ? 0 : items.length - 1]) + (pre ? -1 : 1); pre ? i >= 0 : i < len; pre ? i-- : i++) if (!rt.contains(target = all[i])) return target.focus();
395
401
  (pre ? first : last).blur();
396
- }, handleFocusIn = () => {
402
+ }, handleFocusIn = (e) => {
397
403
  if (document.querySelector("dialog:modal") && !el.matches("dialog:modal")) return;
398
- stack.at(-1) === el && (0, import_utils3.getActiveEl)(el.ownerDocument) !== root && !el.contains((0, import_utils3.getActiveEl)(el.ownerDocument)) && resetFocus();
404
+ stack.at(-1) === el && (0, import_utils3.getActiveEl)(el.ownerDocument) !== root && !el.contains((0, import_utils3.getActiveEl)(el.ownerDocument)) && !e.composedPath().includes(el) && resetFocus();
399
405
  }, handleInitialBlur = () => initial.classList.remove(ringClassName);
400
406
  first.addEventListener("focus", (e) => el.contains(e.relatedTarget) ? edgeFocus(true) : resetFocus(), capture), el.prepend(first);
401
407
  last.addEventListener("focus", (e) => el.contains(e.relatedTarget) ? edgeFocus() : resetFocus(-1), capture), el.append(last);
402
408
  root.addEventListener("focusin", handleFocusIn, capture);
403
409
  if (initial || !el.contains(focused)) !initial ? setTimeout(resetFocus) : setTimeout(() => (initial.classList.add(ringClassName), initial.focus(), initial.addEventListener("blur", handleInitialBlur, capture)));
404
- if (!stack.includes(el)) stack.push(el), stacks.set(root, stack);
405
410
  const destroy = () => {
406
411
  focused?.isConnected && focused.focus(), first.remove(), last.remove();
407
412
  root.removeEventListener("focusin", handleFocusIn, capture);
408
413
  initial?.removeEventListener("blur", handleInitialBlur, capture);
409
414
  t007._ftrappers.delete(el), stack.splice(stack.indexOf(el), 1);
410
415
  };
411
- return t007._ftrappers.set(el, destroy), destroy;
416
+ const handle = { destroy, sync: () => (el.prepend(first), el.append(last)) };
417
+ return !stack.includes(el) && stack.push(el), t007._ftrappers.set(el, handle), handle;
412
418
  }
413
419
 
414
420
  // src/ts/hooks/react/useFocusTrap.ts
415
- var import_sia_reactor6 = require("sia-reactor");
416
- function useFocusTrap(ref, config = import_sia_reactor6.NIL) {
417
- (0, import_react4.useEffect)(() => ref.current ? initFocusTrap(ref.current, config) : void 0, [ref, config.enabled, config.initialSelector, config.ringClassName, config.root, config.scoped, config.capture]);
421
+ var import_sia_reactor7 = require("sia-reactor");
422
+ function useFocusTrap(ref, config = import_sia_reactor7.NIL) {
423
+ const handle = (0, import_react4.useRef)(void 0);
424
+ (0, import_react4.useEffect)(() => {
425
+ if (!ref.current) return;
426
+ return handle.current = initFocusTrap(ref.current, config), () => handle.current?.destroy();
427
+ }, [ref, config.enabled, config.initialSelector, config.ringClassName, config.root, config.scoped, config.capture]);
428
+ return { sync: () => handle.current?.sync() };
418
429
  }
419
430
 
420
431
  // src/ts/hooks/react/useHighlight.ts
@@ -434,8 +445,8 @@ function useHighlight(text, query, ignoreCase = true, options) {
434
445
  var import_react6 = require("react");
435
446
 
436
447
  // src/ts/hooks/vanilla/ripple.ts
437
- var import_sia_reactor7 = require("sia-reactor");
438
- function rippleHandler(e, { target, forceCenter = false, wrapperClassName = "t007-ripple-wrapper", className = "t007-ripple", holdClassName = "t007-ripple-hold", fadeClassName = "t007-ripple-fade" } = import_sia_reactor7.NIL) {
448
+ var import_sia_reactor8 = require("sia-reactor");
449
+ function rippleHandler(e, { target, forceCenter = false, wrapperClassName = "t007-ripple-wrapper", className = "t007-ripple", holdClassName = "t007-ripple-hold", fadeClassName = "t007-ripple-fade", maxDuration = 1e3 } = import_sia_reactor8.NIL) {
439
450
  const el = target || e.currentTarget;
440
451
  if (!el || e.target !== e.currentTarget && isInteractive(e.target) || el.hasAttribute("disabled") || e.pointerType === "mouse" && e.button !== 0) return;
441
452
  e.stopPropagation?.();
@@ -443,19 +454,22 @@ function rippleHandler(e, { target, forceCenter = false, wrapperClassName = "t00
443
454
  let canRelease = false;
444
455
  ripple.addEventListener("animationend", () => canRelease = true, { once: true });
445
456
  el.append(wrapper.appendChild(ripple).parentElement);
446
- const release = () => {
447
- if (!canRelease) return ripple.addEventListener("animationend", release, { once: true });
457
+ let released = false;
458
+ const release = (force) => {
459
+ if (released) return;
460
+ if (!canRelease && force !== true) return ripple.addEventListener("animationend", release, { once: true }), void setTimeout(release, maxDuration, true);
461
+ released = true;
448
462
  ripple.classList.replace(holdClassName, fadeClassName);
449
- ripple.addEventListener("animationend", () => setTimeout(() => wrapper.remove()));
463
+ ripple.addEventListener("animationend", () => setTimeout(() => wrapper.remove())), setTimeout(() => wrapper.remove(), maxDuration);
450
464
  for (const evt of ["pointerup", "pointercancel"]) (el.ownerDocument?.defaultView || window).removeEventListener(evt, release);
451
465
  };
452
466
  for (const evt of ["pointerup", "pointercancel"]) (el.ownerDocument?.defaultView || window).addEventListener(evt, release);
453
467
  }
454
468
 
455
469
  // src/ts/hooks/react/useRipple.ts
456
- var import_sia_reactor8 = require("sia-reactor");
470
+ var import_sia_reactor9 = require("sia-reactor");
457
471
  function useRipple() {
458
- return (0, import_react6.useCallback)((e, config = import_sia_reactor8.NIL) => rippleHandler(e.nativeEvent, config), []);
472
+ return (0, import_react6.useCallback)((e, config = import_sia_reactor9.NIL) => rippleHandler(e.nativeEvent, config), []);
459
473
  }
460
474
  // Annotate the CommonJS export names for ESM import in node:
461
475
  0 && (module.exports = {
@@ -1,11 +1,11 @@
1
1
  import { RefObject } from 'react';
2
- import { a as ScrollAssistConfig, C as Config, K as KeyEvent } from '../scrollAssist-y9wFmYgt.cjs';
3
- import { O as OutsideClickConfig, F as FocusTrapConfig, R as RippleConfig } from '../ripple-C5MfEErC.cjs';
4
- export { H as HighlightOptions, u as useHighlight } from '../useHighlight-DMpDCILK.cjs';
2
+ import { a as ScrollAssistConfig, C as Config, K as KeyEvent, b as FocusTrapConfig } from '../scrollAssist-BNvPnsJq.cjs';
3
+ import { O as OutsideClickConfig, R as RippleConfig } from '../ripple-D1kGErTn.cjs';
4
+ export { H as HighlightOptions, u as useHighlight } from '../useHighlight-DYc_GF0R.cjs';
5
5
 
6
6
  /** Configuration options for the `useScrollAssist` hook. */
7
7
  interface UseScrollAssistConfig extends ScrollAssistConfig {
8
- /** Enables or disables the scroll assist. Defaults to `true`. */
8
+ /** Enables or disables the scroll assist. @default `true`. */
9
9
  enabled?: boolean;
10
10
  }
11
11
  /** React hook to add edge-scrolling assist to an element. It creates invisible "hot zones" at the edges of the element that, when hovered or dragged into, will scroll the element in that direction.
@@ -33,10 +33,13 @@ declare function useArrowNavigation(containerRef: React.RefObject<HTMLElement>,
33
33
  typeAhead: (key: string) => void;
34
34
  goToIndex: (targetIndex: number, e?: KeyEvent) => void;
35
35
  simulateKey: (e: KeyEvent) => void;
36
+ sync: () => void;
36
37
  };
37
38
 
38
- /** React hook to keep focus trapped inside an element until disabled. */
39
- declare function useFocusTrap(ref: RefObject<HTMLElement>, config?: FocusTrapConfig): void;
39
+ /** React hook to keep focus trapped inside an element until disabled. */
40
+ declare function useFocusTrap(ref: RefObject<HTMLElement>, config?: FocusTrapConfig): {
41
+ sync: () => void | undefined;
42
+ };
40
43
 
41
44
  /** React hook to render and control a material-style ripple animation on an element.
42
45
  * @returns Stable pointer handler for clickable UI surfaces.
@@ -1,11 +1,11 @@
1
1
  import { RefObject } from 'react';
2
- import { a as ScrollAssistConfig, C as Config, K as KeyEvent } from '../scrollAssist-y9wFmYgt.js';
3
- import { O as OutsideClickConfig, F as FocusTrapConfig, R as RippleConfig } from '../ripple-C5MfEErC.js';
4
- export { H as HighlightOptions, u as useHighlight } from '../useHighlight-DMpDCILK.js';
2
+ import { a as ScrollAssistConfig, C as Config, K as KeyEvent, b as FocusTrapConfig } from '../scrollAssist-BNvPnsJq.js';
3
+ import { O as OutsideClickConfig, R as RippleConfig } from '../ripple-D1kGErTn.js';
4
+ export { H as HighlightOptions, u as useHighlight } from '../useHighlight-DYc_GF0R.js';
5
5
 
6
6
  /** Configuration options for the `useScrollAssist` hook. */
7
7
  interface UseScrollAssistConfig extends ScrollAssistConfig {
8
- /** Enables or disables the scroll assist. Defaults to `true`. */
8
+ /** Enables or disables the scroll assist. @default `true`. */
9
9
  enabled?: boolean;
10
10
  }
11
11
  /** React hook to add edge-scrolling assist to an element. It creates invisible "hot zones" at the edges of the element that, when hovered or dragged into, will scroll the element in that direction.
@@ -33,10 +33,13 @@ declare function useArrowNavigation(containerRef: React.RefObject<HTMLElement>,
33
33
  typeAhead: (key: string) => void;
34
34
  goToIndex: (targetIndex: number, e?: KeyEvent) => void;
35
35
  simulateKey: (e: KeyEvent) => void;
36
+ sync: () => void;
36
37
  };
37
38
 
38
- /** React hook to keep focus trapped inside an element until disabled. */
39
- declare function useFocusTrap(ref: RefObject<HTMLElement>, config?: FocusTrapConfig): void;
39
+ /** React hook to keep focus trapped inside an element until disabled. */
40
+ declare function useFocusTrap(ref: RefObject<HTMLElement>, config?: FocusTrapConfig): {
41
+ sync: () => void | undefined;
42
+ };
40
43
 
41
44
  /** React hook to render and control a material-style ripple animation on an element.
42
45
  * @returns Stable pointer handler for clickable UI surfaces.
@@ -11,11 +11,11 @@ import {
11
11
  initFocusTrap,
12
12
  initOutsideClick,
13
13
  rippleHandler
14
- } from "../chunk-QJ72EQCJ.js";
14
+ } from "../chunk-NX2LWYD6.js";
15
15
  import {
16
16
  INTERACTIVE_SELECTOR,
17
17
  getActiveEl
18
- } from "../chunk-4O76EZJ4.js";
18
+ } from "../chunk-ZALPHCSJ.js";
19
19
 
20
20
  // src/ts/hooks/react/useScrollAssist.ts
21
21
  import { useEffect, useRef, useCallback } from "react";
@@ -107,7 +107,7 @@ function useOutsideClick(ref, config = NIL2) {
107
107
  // src/ts/hooks/react/useArrowNavigation/index.ts
108
108
  import { useEffect as useEffect3, useState, useCallback as useCallback2, useRef as useRef2, useMemo } from "react";
109
109
  function useArrowNavigation(containerRef, config = {}) {
110
- const { enabled: isEnabled, selector, focusOnHover, loop, virtual, typeahead, rovingTab, resetMs, activeClass, inputSelector, defaultTabbableIndex, baseTabIndex, grid, rtl: isRtl, focusOptions, scrollIntoView, onSelect, onFocusOut } = { ...DEFAULT_CONFIG, ...config }, [gridX, setGridX] = useState(grid.x || 1), [gridY, setGridY] = useState(grid.y || 1), [vGridY, setVGridY] = useState(grid.vY || 1), [activeIndex, setActiveIndex] = useState(-1), buffer = useRef2(""), timeout = useRef2(null), itemsRef = useRef2([]), enabled = isEnabled ?? virtual, roving = rovingTab ?? !virtual, rtl = useMemo(() => isRtl ?? ("undefined" === typeof document ? false : getComputedStyle(containerRef.current || document.body).direction === "rtl"), [containerRef, isRtl]), mutationObserverRef = useRef2(null), shouldSnub = useCallback2(() => !enabled || !containerRef.current, [enabled, containerRef]), isItemDisabled = useCallback2((el) => !el ? true : el.hasAttribute("disabled") || el.hasAttribute("aria-disabled"), []), getItems = useCallback2(() => itemsRef.current = Array.from(containerRef.current?.querySelectorAll(selector) || []), [containerRef, selector]);
110
+ const { enabled: isEnabled, selector, focusOnHover, loop, virtual, typeahead, rovingTab, resetMs, activeClass, inputSelector, defaultTabbableIndex, baseTabIndex, grid, rtl: isRtl, focusOptions, scrollIntoView, onSelect, onFocusOut } = { ...DEFAULT_CONFIG, ...config }, [gridX, setGridX] = useState(grid.x || 1), [gridY, setGridY] = useState(grid.y || 1), [vGridY, setVGridY] = useState(grid.vY || 1), [activeIndex, setActiveIndex] = useState(-1), buffer = useRef2(""), timeout = useRef2(null), itemsRef = useRef2([]), enabled = isEnabled ?? virtual, roving = rovingTab ?? !virtual, rtl = useMemo(() => isRtl ?? ("undefined" === typeof document ? false : getComputedStyle(containerRef.current || document.body).direction === "rtl"), [containerRef, isRtl]), mutationObserverRef = useRef2(null), shouldSnub = useCallback2(() => !enabled || !containerRef.current, [enabled, containerRef]), isItemDisabled = useCallback2((el) => !el || el.matches('[disabled],[aria-disabled="true"],[inert],:disabled'), []), getItems = useCallback2(() => itemsRef.current = Array.from(containerRef.current?.querySelectorAll(selector) || []), [containerRef, selector]);
111
111
  const getAbleIndex = useCallback2(
112
112
  (targetIndex, e = { key: "ArrowRight", ctrlKey: false }) => {
113
113
  if (shouldSnub()) return;
@@ -181,8 +181,9 @@ function useArrowNavigation(containerRef, config = {}) {
181
181
  },
182
182
  [shouldSnub, virtual, activeIndex, gridX, gridY, vGridY, loop, rtl, goToIndex, typeahead, typeAhead]
183
183
  );
184
- const latest = useRef2({ getItems, updateDOM, activeIndex });
185
- useEffect3(() => void (latest.current = { getItems, updateDOM, activeIndex }), [getItems, updateDOM, activeIndex]);
184
+ const latest = useRef2({ getItems, updateDOM, activeIndex, calcGrid: () => {
185
+ } });
186
+ useEffect3(() => void (latest.current = { ...latest.current, getItems, updateDOM, activeIndex }), [getItems, updateDOM, activeIndex]);
186
187
  useEffect3(() => void getItems(), [getItems, enabled]);
187
188
  useEffect3(() => void (!enabled && setActiveIndex(-1)), [enabled]);
188
189
  useEffect3(() => updateDOM(), [activeIndex, updateDOM]);
@@ -211,8 +212,7 @@ function useArrowNavigation(containerRef, config = {}) {
211
212
  const all = itemsRef.current;
212
213
  const handleHover = (e) => {
213
214
  if (!focusOnHover) return;
214
- const el = e.currentTarget;
215
- const i = itemsRef.current?.indexOf(el);
215
+ const el = e.currentTarget, i = itemsRef.current?.indexOf(el);
216
216
  if (i !== -1) goToIndex(i);
217
217
  };
218
218
  for (const el of all) el.addEventListener("mouseenter", handleHover);
@@ -220,23 +220,23 @@ function useArrowNavigation(containerRef, config = {}) {
220
220
  for (const el of all) el.removeEventListener("mouseenter", handleHover);
221
221
  };
222
222
  }, [enabled, focusOnHover, goToIndex]);
223
+ const sync = useCallback2(() => {
224
+ const { getItems: getItems2, updateDOM: updateDOM2, activeIndex: activeIndex2 } = latest.current;
225
+ const oldEl = itemsRef.current[activeIndex2];
226
+ getItems2();
227
+ const newEl = itemsRef.current[activeIndex2];
228
+ updateDOM2();
229
+ if (oldEl && newEl && oldEl === newEl) return;
230
+ setActiveIndex(-1);
231
+ }, []);
223
232
  useEffect3(() => {
224
233
  if (shouldSnub()) return;
225
- const observer = mutationObserverRef.current = new MutationObserver(() => {
226
- const { getItems: getItems2, updateDOM: updateDOM2, activeIndex: activeIndex2 } = latest.current;
227
- const oldEl = itemsRef.current[activeIndex2];
228
- getItems2();
229
- const newEl = itemsRef.current[activeIndex2];
230
- updateDOM2();
231
- if (oldEl && newEl && oldEl === newEl) return;
232
- setActiveIndex(-1);
233
- });
234
+ const observer = mutationObserverRef.current = new MutationObserver(sync);
234
235
  observer.observe(containerRef.current, { childList: true, subtree: true });
235
236
  return () => observer.disconnect();
236
- }, [shouldSnub, containerRef]);
237
+ }, [shouldSnub, containerRef, sync]);
237
238
  useEffect3(() => {
238
239
  if (shouldSnub()) return;
239
- const all = itemsRef.current;
240
240
  const setGrid = (g) => {
241
241
  if (g.x) setGridX(g.x);
242
242
  if (g.y) setGridY(g.y);
@@ -244,21 +244,31 @@ function useArrowNavigation(containerRef, config = {}) {
244
244
  };
245
245
  setGrid(grid);
246
246
  if (grid.x && grid.y && grid.vY) return;
247
- const calcGrid = () => setGrid(getGrid(all, !grid.x, !grid.y, !grid.vY));
248
- calcGrid();
247
+ let ancestor = null;
249
248
  const resizeObserver = new ResizeObserver(() => calcGrid());
250
- resizeObserver.observe(getCommonAncestor(all[0], all[1]) ?? containerRef.current);
249
+ const calcGrid = () => {
250
+ setGrid(getGrid(itemsRef.current, !grid.x, !grid.y, !grid.vY));
251
+ const next = itemsRef.current.length > 1 ? getCommonAncestor(itemsRef.current[0], itemsRef.current[1]) : containerRef.current;
252
+ if (next && next !== ancestor) ancestor && resizeObserver.unobserve(ancestor), ancestor = next, resizeObserver.observe(ancestor);
253
+ };
254
+ latest.current.calcGrid = calcGrid;
255
+ calcGrid();
251
256
  return () => resizeObserver.disconnect();
252
257
  }, [shouldSnub, containerRef, grid]);
253
258
  useEffect3(() => void (timeout.current && clearTimeout(timeout.current)), []);
254
- return { gridX, gridY, vGridY, activeIndex, activeItem: useCallback2(() => itemsRef.current[activeIndex] ?? null, [activeIndex]), items: useCallback2(() => itemsRef.current, []), getAbleIndex, typeAhead, goToIndex, simulateKey };
259
+ return { gridX, gridY, vGridY, activeIndex, activeItem: useCallback2(() => itemsRef.current[activeIndex] ?? null, [activeIndex]), items: useCallback2(() => itemsRef.current, []), getAbleIndex, typeAhead, goToIndex, simulateKey, sync };
255
260
  }
256
261
 
257
262
  // src/ts/hooks/react/useFocusTrap.ts
258
- import { useEffect as useEffect4 } from "react";
263
+ import { useEffect as useEffect4, useRef as useRef3 } from "react";
259
264
  import { NIL as NIL3 } from "sia-reactor";
260
265
  function useFocusTrap(ref, config = NIL3) {
261
- useEffect4(() => ref.current ? initFocusTrap(ref.current, config) : void 0, [ref, config.enabled, config.initialSelector, config.ringClassName, config.root, config.scoped, config.capture]);
266
+ const handle = useRef3(void 0);
267
+ useEffect4(() => {
268
+ if (!ref.current) return;
269
+ return handle.current = initFocusTrap(ref.current, config), () => handle.current?.destroy();
270
+ }, [ref, config.enabled, config.initialSelector, config.ringClassName, config.root, config.scoped, config.capture]);
271
+ return { sync: () => handle.current?.sync() };
262
272
  }
263
273
 
264
274
  // src/ts/hooks/react/useRipple.ts