@t007/utils 0.0.36 → 0.0.38

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.
@@ -1,4 +1,4 @@
1
- import { K as KeyEvent, C as Config } from './scrollAssist-BNvPnsJq.js';
1
+ import { K as KeyEvent, C as Config } from './scrollAssist-CGZBNw6D.js';
2
2
 
3
3
  type ArrowNavigationHandle = {
4
4
  /** Current computed horizontal grid size. */
@@ -1,4 +1,4 @@
1
- import { K as KeyEvent, C as Config } from './scrollAssist-BNvPnsJq.cjs';
1
+ import { K as KeyEvent, C as Config } from './scrollAssist-CGZBNw6D.cjs';
2
2
 
3
3
  type ArrowNavigationHandle = {
4
4
  /** Current computed horizontal grid size. */
@@ -4,7 +4,7 @@ import {
4
4
  createEl,
5
5
  getActiveEl,
6
6
  isInteractive
7
- } from "./chunk-ZOFWWRKV.js";
7
+ } from "./chunk-ZFW6HNGO.js";
8
8
 
9
9
  // src/ts/hooks/vanilla/outsideClick.ts
10
10
  import { NIL, NOOP } from "sia-reactor";
@@ -31,20 +31,20 @@ var removeOutsideClick = (el) => t007._outsiders?.get(el)?.();
31
31
 
32
32
  // src/ts/hooks/vanilla/focusTrap.ts
33
33
  import { NIL as NIL2 } from "sia-reactor";
34
- function initFocusTrap(el, { enabled = false, initialSelector = "[data-autofocus]", ringClassName = "focus-outline", root = window, scoped = true, capture = true } = NIL2) {
34
+ function initFocusTrap(el, { enabled = false, initialSelector = "[data-autofocus]", ringClassName = "focus-outline", root = window, scoped = true, capture = true, focusOptions } = NIL2) {
35
35
  const stack = t007._ftrappers_stack ??= [], existing = (t007._ftrappers ??= /* @__PURE__ */ new WeakMap()).get(el);
36
36
  if (!enabled || existing) return existing ? existing : void 0;
37
37
  scoped = scoped && root instanceof HTMLElement, root = scoped ? root : root === document ? document : window;
38
38
  let recovering = false;
39
- const focused = document.querySelector(":focus"), initial = el.querySelector(initialSelector), first = createEl("span", { tabIndex: 0 }, { focusGuard: "start" }, { position: "absolute", width: "0", height: "0", pointerEvents: "none" }), last = 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) => {
39
+ const focused = document.querySelector(":focus"), initial = el.querySelector(initialSelector), first = createEl("span", { tabIndex: 0 }, { focusGuard: "start" }, { position: "absolute", width: "0", height: "0", pointerEvents: "none" }), last = 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(focusOptions) : (!el.hasAttribute("tabindex") && (el.tabIndex = -1), el.focus(focusOptions)), recovering = false), edgeFocus = (pre = false, rt = root) => {
40
40
  if (!scoped) return resetFocus(pre ? -1 : 0);
41
- if (rt.hasAttribute("tabindex")) return rt.focus();
41
+ if (rt.hasAttribute("tabindex")) return rt.focus(focusOptions);
42
42
  const items = getFocusable();
43
43
  if (!items.length) return resetFocus(0, null);
44
44
  const ceil = document.fullscreenElement || document.querySelector("dialog:modal") || document.body;
45
45
  let p = rt.parentElement || ceil, all = getFocusable(p);
46
46
  while (p !== ceil && (!all.length || (pre ? rt.contains(all[0]) : rt.contains(all.at(-1))))) all = getFocusable(p = p.parentElement || ceil);
47
- 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();
47
+ 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(focusOptions);
48
48
  (pre ? first : last).blur();
49
49
  }, handleFocusIn = (e) => {
50
50
  if (document.querySelector("dialog:modal") && !el.matches("dialog:modal")) return;
@@ -53,9 +53,9 @@ function initFocusTrap(el, { enabled = false, initialSelector = "[data-autofocus
53
53
  first.addEventListener("focus", (e) => el.contains(e.relatedTarget) ? edgeFocus(true) : resetFocus(), capture), el.prepend(first);
54
54
  last.addEventListener("focus", (e) => el.contains(e.relatedTarget) ? edgeFocus() : resetFocus(-1), capture), el.append(last);
55
55
  root.addEventListener("focusin", handleFocusIn, capture);
56
- if (initial || !el.contains(focused)) !initial ? setTimeout(resetFocus) : setTimeout(() => (initial.classList.add(ringClassName), initial.focus(), initial.addEventListener("blur", handleInitialBlur, capture)));
56
+ if (initial || !el.contains(focused)) !initial ? setTimeout(resetFocus) : setTimeout(() => (initial.classList.add(ringClassName), initial.focus(focusOptions), initial.addEventListener("blur", handleInitialBlur, capture)));
57
57
  const destroy = () => {
58
- focused?.isConnected && focused.focus(), first.remove(), last.remove();
58
+ focused?.isConnected && focused.focus(focusOptions), first.remove(), last.remove();
59
59
  root.removeEventListener("focusin", handleFocusIn, capture);
60
60
  initial?.removeEventListener("blur", handleInitialBlur, capture);
61
61
  t007._ftrappers.delete(el), stack.splice(stack.indexOf(el), 1);
@@ -186,16 +186,11 @@ function throttle(key, fn, delay = 30, strict = true, signal, win) {
186
186
  const id = strict === false ? setTimeout2(() => throttleMap.delete(key), delay, signal, win) : strict(() => throttleMap.delete(key));
187
187
  return throttleMap.set(key, id), fn();
188
188
  }
189
- function debounce(key, fn, delay = 30, strict = true, signal, win) {
190
- const debounceMap = t007._debouncers ??= /* @__PURE__ */ new Map();
191
- if (strict) {
192
- const now = performance.now(), prev = debounceMap.get(key) ?? 0;
193
- return debounceMap.set(key, now), now - prev < delay ? void 0 : fn();
194
- }
195
- const prevId = debounceMap.get(key);
189
+ function debounce(key, fn, delay = 30, strict = false, signal, win) {
190
+ const debounceMap = t007._debouncers ??= /* @__PURE__ */ new Map(), prevId = debounceMap.get(key), callNow = strict && prevId === void 0;
196
191
  prevId !== void 0 && (win ?? window).clearTimeout(prevId);
197
- const id = setTimeout2(() => (debounceMap.delete(key), fn()), delay, signal, win);
198
- return void debounceMap.set(key, id);
192
+ const id = setTimeout2(() => (debounceMap.delete(key), !strict && fn()), delay, signal, win);
193
+ return debounceMap.set(key, id), callNow ? fn() : void 0;
199
194
  }
200
195
  function RAFLoop(key, fn, signal, win) {
201
196
  const rafLoopMap = t007._RAFLoopers ??= /* @__PURE__ */ new Map();
@@ -206,10 +201,10 @@ function RAFLoop(key, fn, signal, win) {
206
201
  }
207
202
  var cancelRAFLoop = (key) => t007._RAFLoopers ? t007._RAFLoopers.delete(key) : false;
208
203
  function limited(FN_KEY, fn, opts = {}) {
209
- let count = 0, { key, maxTimes: max = 1, oncePerSession = true } = isStr(opts) ? { key: opts } : opts;
204
+ let count = 0, { key, maxTimes: max = 1, perSession = key != null } = isStr(opts) ? { key: opts } : opts;
210
205
  const getReg = () => JSON.parse(localStorage.getItem(FN_KEY) || "{}"), setReg = (r) => localStorage.setItem(FN_KEY, JSON.stringify(r));
211
206
  const handle = (...args) => {
212
- if (oncePerSession && count > 0) return void 0;
207
+ if (perSession && count >= +perSession) return;
213
208
  if (!key) return count++ < max ? fn(...args) : void 0;
214
209
  const r = getReg(), c = r[key] || 0;
215
210
  return c < max ? (count++, r[key] = c + 1, setReg(r), fn(...args)) : void 0;
@@ -384,20 +384,20 @@ var import_react4 = require("react");
384
384
 
385
385
  // src/ts/hooks/vanilla/focusTrap.ts
386
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) {
387
+ function initFocusTrap(el, { enabled = false, initialSelector = "[data-autofocus]", ringClassName = "focus-outline", root = window, scoped = true, capture = true, focusOptions } = import_sia_reactor6.NIL) {
388
388
  const stack = t007._ftrappers_stack ??= [], existing = (t007._ftrappers ??= /* @__PURE__ */ new WeakMap()).get(el);
389
389
  if (!enabled || existing) return existing ? existing : void 0;
390
390
  scoped = scoped && root instanceof HTMLElement, root = scoped ? root : root === document ? document : window;
391
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) => {
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(focusOptions) : (!el.hasAttribute("tabindex") && (el.tabIndex = -1), el.focus(focusOptions)), recovering = false), edgeFocus = (pre = false, rt = root) => {
393
393
  if (!scoped) return resetFocus(pre ? -1 : 0);
394
- if (rt.hasAttribute("tabindex")) return rt.focus();
394
+ if (rt.hasAttribute("tabindex")) return rt.focus(focusOptions);
395
395
  const items = getFocusable();
396
396
  if (!items.length) return resetFocus(0, null);
397
397
  const ceil = document.fullscreenElement || document.querySelector("dialog:modal") || document.body;
398
398
  let p = rt.parentElement || ceil, all = getFocusable(p);
399
399
  while (p !== ceil && (!all.length || (pre ? rt.contains(all[0]) : rt.contains(all.at(-1))))) all = getFocusable(p = p.parentElement || ceil);
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();
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(focusOptions);
401
401
  (pre ? first : last).blur();
402
402
  }, handleFocusIn = (e) => {
403
403
  if (document.querySelector("dialog:modal") && !el.matches("dialog:modal")) return;
@@ -406,9 +406,9 @@ function initFocusTrap(el, { enabled = false, initialSelector = "[data-autofocus
406
406
  first.addEventListener("focus", (e) => el.contains(e.relatedTarget) ? edgeFocus(true) : resetFocus(), capture), el.prepend(first);
407
407
  last.addEventListener("focus", (e) => el.contains(e.relatedTarget) ? edgeFocus() : resetFocus(-1), capture), el.append(last);
408
408
  root.addEventListener("focusin", handleFocusIn, capture);
409
- if (initial || !el.contains(focused)) !initial ? setTimeout(resetFocus) : setTimeout(() => (initial.classList.add(ringClassName), initial.focus(), initial.addEventListener("blur", handleInitialBlur, capture)));
409
+ if (initial || !el.contains(focused)) !initial ? setTimeout(resetFocus) : setTimeout(() => (initial.classList.add(ringClassName), initial.focus(focusOptions), initial.addEventListener("blur", handleInitialBlur, capture)));
410
410
  const destroy = () => {
411
- focused?.isConnected && focused.focus(), first.remove(), last.remove();
411
+ focused?.isConnected && focused.focus(focusOptions), first.remove(), last.remove();
412
412
  root.removeEventListener("focusin", handleFocusIn, capture);
413
413
  initial?.removeEventListener("blur", handleInitialBlur, capture);
414
414
  t007._ftrappers.delete(el), stack.splice(stack.indexOf(el), 1);
@@ -421,10 +421,7 @@ function initFocusTrap(el, { enabled = false, initialSelector = "[data-autofocus
421
421
  var import_sia_reactor7 = require("sia-reactor");
422
422
  function useFocusTrap(ref, config = import_sia_reactor7.NIL) {
423
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]);
424
+ (0, import_react4.useEffect)(() => (!ref.current ? void 0 : handle.current = initFocusTrap(ref.current, config), () => handle.current?.destroy()), [ref, config.enabled, config.initialSelector, config.ringClassName, config.root, config.scoped, config.capture, config.focusOptions]);
428
425
  return { sync: () => handle.current?.sync() };
429
426
  }
430
427
 
@@ -1,5 +1,5 @@
1
1
  import { RefObject } from 'react';
2
- import { a as ScrollAssistConfig, C as Config, K as KeyEvent, b as FocusTrapConfig } from '../scrollAssist-BNvPnsJq.cjs';
2
+ import { a as ScrollAssistConfig, C as Config, K as KeyEvent, b as FocusTrapConfig } from '../scrollAssist-CGZBNw6D.cjs';
3
3
  import { O as OutsideClickConfig, R as RippleConfig } from '../ripple-D1kGErTn.cjs';
4
4
  export { H as HighlightOptions, u as useHighlight } from '../useHighlight-DYc_GF0R.cjs';
5
5
 
@@ -1,5 +1,5 @@
1
1
  import { RefObject } from 'react';
2
- import { a as ScrollAssistConfig, C as Config, K as KeyEvent, b as FocusTrapConfig } from '../scrollAssist-BNvPnsJq.js';
2
+ import { a as ScrollAssistConfig, C as Config, K as KeyEvent, b as FocusTrapConfig } from '../scrollAssist-CGZBNw6D.js';
3
3
  import { O as OutsideClickConfig, R as RippleConfig } from '../ripple-D1kGErTn.js';
4
4
  export { H as HighlightOptions, u as useHighlight } from '../useHighlight-DYc_GF0R.js';
5
5
 
@@ -1,6 +1,3 @@
1
- import {
2
- useHighlight
3
- } from "../chunk-RXMKMD3E.js";
4
1
  import {
5
2
  DEFAULT_CONFIG,
6
3
  H_NAV_KEYS,
@@ -11,11 +8,14 @@ import {
11
8
  initFocusTrap,
12
9
  initOutsideClick,
13
10
  rippleHandler
14
- } from "../chunk-DSDFIB5F.js";
11
+ } from "../chunk-GCJ5LPEB.js";
15
12
  import {
16
13
  INTERACTIVE_SELECTOR,
17
14
  getActiveEl
18
- } from "../chunk-ZOFWWRKV.js";
15
+ } from "../chunk-ZFW6HNGO.js";
16
+ import {
17
+ useHighlight
18
+ } from "../chunk-RXMKMD3E.js";
19
19
 
20
20
  // src/ts/hooks/react/useScrollAssist.ts
21
21
  import { useEffect, useRef, useCallback } from "react";
@@ -264,10 +264,7 @@ import { useEffect as useEffect4, useRef as useRef3 } from "react";
264
264
  import { NIL as NIL3 } from "sia-reactor";
265
265
  function useFocusTrap(ref, config = NIL3) {
266
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]);
267
+ useEffect4(() => (!ref.current ? void 0 : handle.current = initFocusTrap(ref.current, config), () => handle.current?.destroy()), [ref, config.enabled, config.initialSelector, config.ringClassName, config.root, config.scoped, config.capture, config.focusOptions]);
271
268
  return { sync: () => handle.current?.sync() };
272
269
  }
273
270
 
@@ -162,20 +162,20 @@ var removeOutsideClick = (el) => t007._outsiders?.get(el)?.();
162
162
 
163
163
  // src/ts/hooks/vanilla/focusTrap.ts
164
164
  var import_sia_reactor5 = require("sia-reactor");
165
- function initFocusTrap(el, { enabled = false, initialSelector = "[data-autofocus]", ringClassName = "focus-outline", root = window, scoped = true, capture = true } = import_sia_reactor5.NIL) {
165
+ function initFocusTrap(el, { enabled = false, initialSelector = "[data-autofocus]", ringClassName = "focus-outline", root = window, scoped = true, capture = true, focusOptions } = import_sia_reactor5.NIL) {
166
166
  const stack = t007._ftrappers_stack ??= [], existing = (t007._ftrappers ??= /* @__PURE__ */ new WeakMap()).get(el);
167
167
  if (!enabled || existing) return existing ? existing : void 0;
168
168
  scoped = scoped && root instanceof HTMLElement, root = scoped ? root : root === document ? document : window;
169
169
  let recovering = false;
170
- const focused = document.querySelector(":focus"), initial = el.querySelector(initialSelector), first = (0, import_utils.createEl)("span", { tabIndex: 0 }, { focusGuard: "start" }, { position: "absolute", width: "0", height: "0", pointerEvents: "none" }), last = (0, import_utils.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) => {
170
+ const focused = document.querySelector(":focus"), initial = el.querySelector(initialSelector), first = (0, import_utils.createEl)("span", { tabIndex: 0 }, { focusGuard: "start" }, { position: "absolute", width: "0", height: "0", pointerEvents: "none" }), last = (0, import_utils.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(focusOptions) : (!el.hasAttribute("tabindex") && (el.tabIndex = -1), el.focus(focusOptions)), recovering = false), edgeFocus = (pre = false, rt = root) => {
171
171
  if (!scoped) return resetFocus(pre ? -1 : 0);
172
- if (rt.hasAttribute("tabindex")) return rt.focus();
172
+ if (rt.hasAttribute("tabindex")) return rt.focus(focusOptions);
173
173
  const items = getFocusable();
174
174
  if (!items.length) return resetFocus(0, null);
175
175
  const ceil = document.fullscreenElement || document.querySelector("dialog:modal") || document.body;
176
176
  let p = rt.parentElement || ceil, all = getFocusable(p);
177
177
  while (p !== ceil && (!all.length || (pre ? rt.contains(all[0]) : rt.contains(all.at(-1))))) all = getFocusable(p = p.parentElement || ceil);
178
- 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();
178
+ 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(focusOptions);
179
179
  (pre ? first : last).blur();
180
180
  }, handleFocusIn = (e) => {
181
181
  if (document.querySelector("dialog:modal") && !el.matches("dialog:modal")) return;
@@ -184,9 +184,9 @@ function initFocusTrap(el, { enabled = false, initialSelector = "[data-autofocus
184
184
  first.addEventListener("focus", (e) => el.contains(e.relatedTarget) ? edgeFocus(true) : resetFocus(), capture), el.prepend(first);
185
185
  last.addEventListener("focus", (e) => el.contains(e.relatedTarget) ? edgeFocus() : resetFocus(-1), capture), el.append(last);
186
186
  root.addEventListener("focusin", handleFocusIn, capture);
187
- if (initial || !el.contains(focused)) !initial ? setTimeout(resetFocus) : setTimeout(() => (initial.classList.add(ringClassName), initial.focus(), initial.addEventListener("blur", handleInitialBlur, capture)));
187
+ if (initial || !el.contains(focused)) !initial ? setTimeout(resetFocus) : setTimeout(() => (initial.classList.add(ringClassName), initial.focus(focusOptions), initial.addEventListener("blur", handleInitialBlur, capture)));
188
188
  const destroy = () => {
189
- focused?.isConnected && focused.focus(), first.remove(), last.remove();
189
+ focused?.isConnected && focused.focus(focusOptions), first.remove(), last.remove();
190
190
  root.removeEventListener("focusin", handleFocusIn, capture);
191
191
  initial?.removeEventListener("blur", handleInitialBlur, capture);
192
192
  t007._ftrappers.delete(el), stack.splice(stack.indexOf(el), 1);
@@ -1,6 +1,6 @@
1
- export { b as FocusTrapConfig, F as FocusTrapHandle, a as ScrollAssistConfig, S as ScrollAssistHandle, c as ScrollDir, i as initFocusTrap, d as initScrollAssist, r as removeFocusTrap, e as removeScrollAssist, s as syncFocusTrap } from '../scrollAssist-BNvPnsJq.cjs';
1
+ export { b as FocusTrapConfig, F as FocusTrapHandle, a as ScrollAssistConfig, S as ScrollAssistHandle, c as ScrollDir, i as initFocusTrap, d as initScrollAssist, r as removeFocusTrap, e as removeScrollAssist, s as syncFocusTrap } from '../scrollAssist-CGZBNw6D.cjs';
2
2
  export { O as OutsideClickConfig, R as RippleConfig, i as initOutsideClick, r as removeOutsideClick, a as rippleHandler } from '../ripple-D1kGErTn.cjs';
3
- export { A as ArrowNavigationHandle, i as initArrowNavigation, r as removeArrowNavigation, s as syncArrowNavigation } from '../arrowNavigation-zEJbo8Er.cjs';
3
+ export { A as ArrowNavigationHandle, i as initArrowNavigation, r as removeArrowNavigation, s as syncArrowNavigation } from '../arrowNavigation-PxYXKmCY.cjs';
4
4
 
5
5
  /** Configuration for the vertical edge-scrolling helper. */
6
6
  interface ScrolleratorOptions {
@@ -1,6 +1,6 @@
1
- export { b as FocusTrapConfig, F as FocusTrapHandle, a as ScrollAssistConfig, S as ScrollAssistHandle, c as ScrollDir, i as initFocusTrap, d as initScrollAssist, r as removeFocusTrap, e as removeScrollAssist, s as syncFocusTrap } from '../scrollAssist-BNvPnsJq.js';
1
+ export { b as FocusTrapConfig, F as FocusTrapHandle, a as ScrollAssistConfig, S as ScrollAssistHandle, c as ScrollDir, i as initFocusTrap, d as initScrollAssist, r as removeFocusTrap, e as removeScrollAssist, s as syncFocusTrap } from '../scrollAssist-CGZBNw6D.js';
2
2
  export { O as OutsideClickConfig, R as RippleConfig, i as initOutsideClick, r as removeOutsideClick, a as rippleHandler } from '../ripple-D1kGErTn.js';
3
- export { A as ArrowNavigationHandle, i as initArrowNavigation, r as removeArrowNavigation, s as syncArrowNavigation } from '../arrowNavigation-B9bEdoqR.js';
3
+ export { A as ArrowNavigationHandle, i as initArrowNavigation, r as removeArrowNavigation, s as syncArrowNavigation } from '../arrowNavigation-C5asxLP0.js';
4
4
 
5
5
  /** Configuration for the vertical edge-scrolling helper. */
6
6
  interface ScrolleratorOptions {
@@ -11,12 +11,12 @@ import {
11
11
  removeOutsideClick,
12
12
  rippleHandler,
13
13
  syncFocusTrap
14
- } from "../chunk-DSDFIB5F.js";
14
+ } from "../chunk-GCJ5LPEB.js";
15
15
  import {
16
16
  INTERACTIVE_SELECTOR,
17
17
  createEl,
18
18
  getActiveEl
19
- } from "../chunk-ZOFWWRKV.js";
19
+ } from "../chunk-ZFW6HNGO.js";
20
20
 
21
21
  // src/ts/hooks/vanilla/scrollAssist.ts
22
22
  import { NIL } from "sia-reactor";
package/dist/index.cjs CHANGED
@@ -272,16 +272,11 @@ function throttle(key, fn, delay = 30, strict = true, signal, win) {
272
272
  const id = strict === false ? (0, import_utils5.setTimeout)(() => throttleMap.delete(key), delay, signal, win) : strict(() => throttleMap.delete(key));
273
273
  return throttleMap.set(key, id), fn();
274
274
  }
275
- function debounce(key, fn, delay = 30, strict = true, signal, win) {
276
- const debounceMap = t007._debouncers ??= /* @__PURE__ */ new Map();
277
- if (strict) {
278
- const now = performance.now(), prev = debounceMap.get(key) ?? 0;
279
- return debounceMap.set(key, now), now - prev < delay ? void 0 : fn();
280
- }
281
- const prevId = debounceMap.get(key);
275
+ function debounce(key, fn, delay = 30, strict = false, signal, win) {
276
+ const debounceMap = t007._debouncers ??= /* @__PURE__ */ new Map(), prevId = debounceMap.get(key), callNow = strict && prevId === void 0;
282
277
  prevId !== void 0 && (win ?? window).clearTimeout(prevId);
283
- const id = (0, import_utils5.setTimeout)(() => (debounceMap.delete(key), fn()), delay, signal, win);
284
- return void debounceMap.set(key, id);
278
+ const id = (0, import_utils5.setTimeout)(() => (debounceMap.delete(key), !strict && fn()), delay, signal, win);
279
+ return debounceMap.set(key, id), callNow ? fn() : void 0;
285
280
  }
286
281
  function RAFLoop(key, fn, signal, win) {
287
282
  const rafLoopMap = t007._RAFLoopers ??= /* @__PURE__ */ new Map();
@@ -292,10 +287,10 @@ function RAFLoop(key, fn, signal, win) {
292
287
  }
293
288
  var cancelRAFLoop = (key) => t007._RAFLoopers ? t007._RAFLoopers.delete(key) : false;
294
289
  function limited(FN_KEY, fn, opts = {}) {
295
- let count = 0, { key, maxTimes: max = 1, oncePerSession = true } = isStr(opts) ? { key: opts } : opts;
290
+ let count = 0, { key, maxTimes: max = 1, perSession = key != null } = isStr(opts) ? { key: opts } : opts;
296
291
  const getReg = () => JSON.parse(localStorage.getItem(FN_KEY) || "{}"), setReg = (r) => localStorage.setItem(FN_KEY, JSON.stringify(r));
297
292
  const handle = (...args) => {
298
- if (oncePerSession && count > 0) return void 0;
293
+ if (perSession && count >= +perSession) return;
299
294
  if (!key) return count++ < max ? fn(...args) : void 0;
300
295
  const r = getReg(), c = r[key] || 0;
301
296
  return c < max ? (count++, r[key] = c + 1, setReg(r), fn(...args)) : void 0;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as ArrowNavigationHandle } from './arrowNavigation-zEJbo8Er.cjs';
2
- import { F as FocusTrapHandle, S as ScrollAssistHandle } from './scrollAssist-BNvPnsJq.cjs';
1
+ import { A as ArrowNavigationHandle } from './arrowNavigation-PxYXKmCY.cjs';
2
+ import { F as FocusTrapHandle, S as ScrollAssistHandle } from './scrollAssist-CGZBNw6D.cjs';
3
3
  export { NIL, NOOP } from 'sia-reactor';
4
4
  export { KEYS_BLOCKS, KeyStruct, KeysSettings, assignEl, bindAllMethods, clamp, cleanKeyCombo, createEl, formatKeyForDisplay, formatKeyShortcutsForDisplay, getActiveEl, getTermsForKey, guardAllMethods, guardMethod, isObj, keyEventAllowed, matchKeys, onAllMethods, parseForARIAKS, parseKeyCombo, requestAnimationFrame, setInterval, setTimeout, stringifyKeyEvent } from 'sia-reactor/utils';
5
5
 
@@ -103,7 +103,7 @@ declare function camelize<T extends string>(str?: T, { source }?: RegExp, { pres
103
103
  }): CamelCase<T>;
104
104
  /** Convert a camelCase or PascalCase string to a separator-based format (e.g. "helloWorld" to "hello-world").
105
105
  * @param str The camelCase or PascalCase string to convert.
106
- * @param separator The string to insert between words. @default a hyphen ("-").
106
+ * @param separator The string to insert between words. @default " ".
107
107
  * @returns The uncamelized version of the input string with separators.
108
108
  * @example
109
109
  * uncamelize("helloWorld") // "hello-world"
@@ -158,7 +158,7 @@ declare function throttle(key: string, fn: Function, delay?: number, strict?: ((
158
158
  * @param key Unique identifier for the debounced function, used to track pending calls.
159
159
  * @param fn Function to be debounced.
160
160
  * @param delay Time in milliseconds to wait after the latest call before invoking the function. @default `30ms`.
161
- * @param strict If `true`, exact timestamp difference between calls will be used else pending timeout is reset each call for practical thread leniency. @default `true`.
161
+ * @param strict If `true`, the function will execute immediately on the leading edge of the delay period instead of the trailing edge. @default `false`.
162
162
  * @param signal Optional `AbortSignal` to automatically clear scheduled debounce execution when aborted.
163
163
  * @param win Optional `Window` object for scheduling/clearing the debounce timeout, useful for testing or if running in a non-browser environment.
164
164
  */
@@ -182,8 +182,8 @@ interface LimitedOptions {
182
182
  key?: string;
183
183
  /** Maximum number of allowed calls. @default 1 */
184
184
  maxTimes?: number;
185
- /** Only allow calling once per session, regardless of maxTimes. @default true */
186
- oncePerSession?: boolean;
185
+ /** Only allow calling x times per session, regardless of maxTimes. @default true */
186
+ perSession?: boolean | number;
187
187
  }
188
188
  interface LimitedHandle<T extends (...args: any[]) => any> {
189
189
  /** Call the wrapped function with the original arguments. */
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as ArrowNavigationHandle } from './arrowNavigation-B9bEdoqR.js';
2
- import { F as FocusTrapHandle, S as ScrollAssistHandle } from './scrollAssist-BNvPnsJq.js';
1
+ import { A as ArrowNavigationHandle } from './arrowNavigation-C5asxLP0.js';
2
+ import { F as FocusTrapHandle, S as ScrollAssistHandle } from './scrollAssist-CGZBNw6D.js';
3
3
  export { NIL, NOOP } from 'sia-reactor';
4
4
  export { KEYS_BLOCKS, KeyStruct, KeysSettings, assignEl, bindAllMethods, clamp, cleanKeyCombo, createEl, formatKeyForDisplay, formatKeyShortcutsForDisplay, getActiveEl, getTermsForKey, guardAllMethods, guardMethod, isObj, keyEventAllowed, matchKeys, onAllMethods, parseForARIAKS, parseKeyCombo, requestAnimationFrame, setInterval, setTimeout, stringifyKeyEvent } from 'sia-reactor/utils';
5
5
 
@@ -103,7 +103,7 @@ declare function camelize<T extends string>(str?: T, { source }?: RegExp, { pres
103
103
  }): CamelCase<T>;
104
104
  /** Convert a camelCase or PascalCase string to a separator-based format (e.g. "helloWorld" to "hello-world").
105
105
  * @param str The camelCase or PascalCase string to convert.
106
- * @param separator The string to insert between words. @default a hyphen ("-").
106
+ * @param separator The string to insert between words. @default " ".
107
107
  * @returns The uncamelized version of the input string with separators.
108
108
  * @example
109
109
  * uncamelize("helloWorld") // "hello-world"
@@ -158,7 +158,7 @@ declare function throttle(key: string, fn: Function, delay?: number, strict?: ((
158
158
  * @param key Unique identifier for the debounced function, used to track pending calls.
159
159
  * @param fn Function to be debounced.
160
160
  * @param delay Time in milliseconds to wait after the latest call before invoking the function. @default `30ms`.
161
- * @param strict If `true`, exact timestamp difference between calls will be used else pending timeout is reset each call for practical thread leniency. @default `true`.
161
+ * @param strict If `true`, the function will execute immediately on the leading edge of the delay period instead of the trailing edge. @default `false`.
162
162
  * @param signal Optional `AbortSignal` to automatically clear scheduled debounce execution when aborted.
163
163
  * @param win Optional `Window` object for scheduling/clearing the debounce timeout, useful for testing or if running in a non-browser environment.
164
164
  */
@@ -182,8 +182,8 @@ interface LimitedOptions {
182
182
  key?: string;
183
183
  /** Maximum number of allowed calls. @default 1 */
184
184
  maxTimes?: number;
185
- /** Only allow calling once per session, regardless of maxTimes. @default true */
186
- oncePerSession?: boolean;
185
+ /** Only allow calling x times per session, regardless of maxTimes. @default true */
186
+ perSession?: boolean | number;
187
187
  }
188
188
  interface LimitedHandle<T extends (...args: any[]) => any> {
189
189
  /** Call the wrapped function with the original arguments. */
package/dist/index.js CHANGED
@@ -60,7 +60,7 @@ import {
60
60
  throttle,
61
61
  uid,
62
62
  uncamelize
63
- } from "./chunk-ZOFWWRKV.js";
63
+ } from "./chunk-ZFW6HNGO.js";
64
64
  export {
65
65
  INTERACTIVE_SELECTOR,
66
66
  KEYS_BLOCKS,
@@ -51,13 +51,15 @@ interface FocusTrapConfig {
51
51
  scoped?: boolean;
52
52
  /** Passed down to all event listeners used. @default `true`. */
53
53
  capture?: boolean;
54
+ /** Focus behavior options used in triggering focus. */
55
+ focusOptions?: FocusOptions;
54
56
  }
55
57
  interface FocusTrapHandle {
56
58
  destroy: () => void;
57
59
  sync: () => void;
58
60
  }
59
61
  /** Hook to keep focus trapped inside an element until disabled. */
60
- declare function initFocusTrap(el: HTMLElement, { enabled, initialSelector, ringClassName, root, scoped, capture }?: FocusTrapConfig): FocusTrapHandle | void;
62
+ declare function initFocusTrap(el: HTMLElement, { enabled, initialSelector, ringClassName, root, scoped, capture, focusOptions }?: FocusTrapConfig): FocusTrapHandle | void;
61
63
  /** Remove the focus trap guard from an element. */
62
64
  declare const removeFocusTrap: (el: HTMLElement) => void | undefined;
63
65
  /** Synchronize the focus trap guards. */
@@ -51,13 +51,15 @@ interface FocusTrapConfig {
51
51
  scoped?: boolean;
52
52
  /** Passed down to all event listeners used. @default `true`. */
53
53
  capture?: boolean;
54
+ /** Focus behavior options used in triggering focus. */
55
+ focusOptions?: FocusOptions;
54
56
  }
55
57
  interface FocusTrapHandle {
56
58
  destroy: () => void;
57
59
  sync: () => void;
58
60
  }
59
61
  /** Hook to keep focus trapped inside an element until disabled. */
60
- declare function initFocusTrap(el: HTMLElement, { enabled, initialSelector, ringClassName, root, scoped, capture }?: FocusTrapConfig): FocusTrapHandle | void;
62
+ declare function initFocusTrap(el: HTMLElement, { enabled, initialSelector, ringClassName, root, scoped, capture, focusOptions }?: FocusTrapConfig): FocusTrapHandle | void;
61
63
  /** Remove the focus trap guard from an element. */
62
64
  declare const removeFocusTrap: (el: HTMLElement) => void | undefined;
63
65
  /** Synchronize the focus trap guards. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@t007/utils",
3
- "version": "0.0.36",
3
+ "version": "0.0.38",
4
4
  "description": "High-performance utilities for the t007 ecosystem.",
5
5
  "author": "Oketade Oluwatobiloba <tobioketade007@gmail.com>",
6
6
  "license": "MIT",
@@ -76,7 +76,7 @@
76
76
  "react-dom": "^18.3.1"
77
77
  },
78
78
  "dependencies": {
79
- "sia-reactor": "^0.0.37"
79
+ "sia-reactor": "^0.0.40"
80
80
  },
81
81
  "peerDependencies": {
82
82
  "react": "^18.0.0"