@t007/utils 0.0.24 → 0.0.26

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.
@@ -0,0 +1,232 @@
1
+ import {
2
+ DEFAULT_CONFIG,
3
+ H_NAV_KEYS,
4
+ NAV_KEYS,
5
+ getCommonAncestor,
6
+ getGrid,
7
+ getTargetIndex,
8
+ initFocusTrap,
9
+ initOutsideClick,
10
+ removeFocusTrap,
11
+ removeOutsideClick,
12
+ rippleHandler
13
+ } from "../chunk-AI5O3OGE.js";
14
+ import {
15
+ INTERACTIVE_SELECTOR,
16
+ createEl,
17
+ getActiveElement
18
+ } from "../chunk-XVFFZZJA.js";
19
+
20
+ // src/hooks/vanilla/scrollAssist.ts
21
+ import { NIL } from "sia-reactor";
22
+ function initScrollAssist(el, { pxPerSecond = 80, assistClassName = "t007-scroll-assist", vertical = true, horizontal = true } = NIL) {
23
+ const parent = el?.parentElement, existing = (t007._scrollers ??= /* @__PURE__ */ new WeakMap()).get(el);
24
+ if (!parent || existing) return existing ? existing : void 0;
25
+ t007._scroller_r_observer ??= new ResizeObserver((entries) => {
26
+ for (const { target } of entries) t007._scrollers.get(target)?.update();
27
+ });
28
+ t007._scroller_m_observer ??= new MutationObserver((entries) => {
29
+ const els = /* @__PURE__ */ new Set();
30
+ for (const entry of entries) {
31
+ let node = entry.target instanceof Element ? entry.target : null;
32
+ while (node && !t007._scrollers.has(node)) node = node.parentElement;
33
+ if (node) els.add(node);
34
+ }
35
+ for (const el2 of els) t007._scrollers.get(el2)?.update();
36
+ });
37
+ const assist = {};
38
+ let scrollId = null, last = performance.now(), assistWidth = 20, assistHeight = 20;
39
+ const update = () => {
40
+ const hasInteractive = !!parent.querySelector(INTERACTIVE_SELECTOR);
41
+ if (horizontal) {
42
+ const w = assist.left?.offsetWidth || assistWidth, check = hasInteractive ? el.clientWidth < w * 2 : false;
43
+ assist.left.style.display = check ? "none" : el.scrollLeft > 0 ? "block" : "none";
44
+ assist.right.style.display = check ? "none" : el.scrollLeft + el.clientWidth < el.scrollWidth - 1 ? "block" : "none";
45
+ assistWidth = w;
46
+ }
47
+ if (vertical) {
48
+ const h = assist.up?.offsetHeight || assistHeight, check = hasInteractive ? el.clientHeight < h * 2 : false;
49
+ assist.up.style.display = check ? "none" : el.scrollTop > 0 ? "block" : "none";
50
+ assist.down.style.display = check ? "none" : el.scrollTop + el.clientHeight < el.scrollHeight - 1 ? "block" : "none";
51
+ assistHeight = h;
52
+ }
53
+ };
54
+ const scroll = (dir) => {
55
+ const frame = () => {
56
+ const now = performance.now(), dt = now - last;
57
+ last = now;
58
+ const d = pxPerSecond * dt / 1e3;
59
+ if (dir === "left") el.scrollLeft = Math.max(0, el.scrollLeft - d);
60
+ if (dir === "right") el.scrollLeft = Math.min(el.scrollWidth - el.clientWidth, el.scrollLeft + d);
61
+ if (dir === "up") el.scrollTop = Math.max(0, el.scrollTop - d);
62
+ if (dir === "down") el.scrollTop = Math.min(el.scrollHeight - el.clientHeight, el.scrollTop + d);
63
+ scrollId = requestAnimationFrame(frame);
64
+ };
65
+ last = performance.now();
66
+ frame();
67
+ };
68
+ const stop = () => (cancelAnimationFrame(scrollId ?? 0), scrollId = null);
69
+ const addAssist = (dir) => {
70
+ const div = createEl("div", { className: assistClassName }, { scrollDirection: dir }, { display: "none" });
71
+ if (!div) return;
72
+ for (const evt of ["pointerenter", "dragenter"]) div.addEventListener(evt, () => scroll(dir));
73
+ for (const evt of ["pointerleave", "pointerup", "pointercancel", "dragleave", "dragend"]) div.addEventListener(evt, stop);
74
+ dir === "left" || dir === "up" ? parent.insertBefore(div, el) : parent.append(div);
75
+ assist[dir] = div;
76
+ };
77
+ if (horizontal) for (const dir of ["left", "right"]) addAssist(dir);
78
+ if (vertical) for (const dir of ["up", "down"]) addAssist(dir);
79
+ const handle = {
80
+ update,
81
+ destroy() {
82
+ stop(), el.removeEventListener("scroll", update);
83
+ t007._scroller_r_observer.unobserve(el), t007._scrollers.delete(el);
84
+ for (const a of Object.values(assist)) a.remove();
85
+ }
86
+ };
87
+ update(), el.addEventListener("scroll", update);
88
+ t007._scroller_r_observer.observe(el), t007._scroller_m_observer.observe(el, { childList: true, subtree: true, characterData: true });
89
+ return t007._scrollers.set(el, handle), handle;
90
+ }
91
+ var removeScrollAssist = (el) => t007._scrollers.get(el)?.destroy();
92
+
93
+ // src/hooks/vanilla/scrollerator.ts
94
+ import { NIL as NIL2 } from "sia-reactor";
95
+ function initVScrollerator({ baseSpeed = 3, maxSpeed = 10, stepDelay = 2e3, baseRate = 16, lineHeight = 80, margin = 80, car = window } = NIL2) {
96
+ let linesPerSec = baseSpeed, accelId = null, lastTime = null;
97
+ const drive = (clientY, brake = false, offsetY = 0) => {
98
+ if (car !== window) clientY -= offsetY;
99
+ const now = performance.now(), speed = linesPerSec * lineHeight * ((lastTime ? now - lastTime : baseRate) / 1e3);
100
+ if (!brake && (clientY < margin || clientY > (car.innerHeight ?? car.offsetHeight) - margin)) {
101
+ accelId === null ? accelId = setTimeout(() => linesPerSec += 1, stepDelay) : linesPerSec > baseSpeed && (linesPerSec = Math.min(linesPerSec + 1, maxSpeed));
102
+ car.scrollBy?.(0, clientY < margin ? -speed : speed);
103
+ } else reset();
104
+ return lastTime = !brake ? now : null, speed;
105
+ };
106
+ const reset = () => (accelId && clearTimeout(accelId), accelId = null, linesPerSec = baseSpeed, lastTime = null);
107
+ return { drive, reset };
108
+ }
109
+
110
+ // src/hooks/vanilla/arrowNavigation.ts
111
+ function initArrowNavigation(container, config = {}) {
112
+ const existing = (t007._ashooters ??= /* @__PURE__ */ new WeakMap()).get(container);
113
+ if (!config.enabled || existing) return existing ? existing : void 0;
114
+ const { enabled: isEnabled, selector, focusOnHover, loop, virtual, typeahead, resetMs, activeClass, inputSelector, defaultTabbableIndex, baseTabIndex, grid, rtl: isRtl, focusOptions, scrollIntoView, onSelect, onFocusOut, rovingTab } = { ...DEFAULT_CONFIG, ...config };
115
+ let gridX = grid.x || 1, gridY = grid.y || 1, vGridY = grid.vY || 1, activeIndex = -1, buffer = "", timeout = null, items = [];
116
+ const enabled = isEnabled ?? virtual, roving = rovingTab ?? !virtual, rtl = isRtl ?? getComputedStyle(container).direction === "rtl", shouldSnub = () => !enabled || !container, isItemDisabled = (el) => !el || el.hasAttribute("disabled") || el.hasAttribute("aria-disabled"), getItems = () => items = Array.from(container.querySelectorAll(selector));
117
+ const getAbleIndex = (targetIndex, e = { key: "ArrowRight", ctrlKey: false }) => {
118
+ if (shouldSnub() || !items.length) return null;
119
+ let index = targetIndex;
120
+ let attempts = 0;
121
+ while (attempts < items.length) {
122
+ if (!isItemDisabled(items[index])) return index;
123
+ index = getTargetIndex({ key: e.key, currIndex: index, gridX, gridY, vGridY, length: items.length, loop, ctrlKey: e.ctrlKey, rtl });
124
+ attempts++;
125
+ }
126
+ return null;
127
+ };
128
+ const goToIndex = (targetIndex, e = { key: "ArrowRight" }) => {
129
+ if (shouldSnub()) return;
130
+ const idx = getAbleIndex(targetIndex, e);
131
+ if (idx === null) return;
132
+ resetActiveIndex(idx);
133
+ onSelect?.(items[idx], e), updateDOM();
134
+ if (virtual) {
135
+ items[idx]?.scrollIntoView(scrollIntoView);
136
+ container.setAttribute("aria-activedescendant", items[idx].id || "");
137
+ } else items[idx]?.focus(focusOptions);
138
+ };
139
+ const updateDOM = () => {
140
+ if (shouldSnub() || !virtual && !roving || !items.length) return;
141
+ const hasDefaultTabbable = defaultTabbableIndex !== null && defaultTabbableIndex !== void 0, tabbableIndex = hasDefaultTabbable && !isItemDisabled(items[defaultTabbableIndex]) ? defaultTabbableIndex : getAbleIndex(0);
142
+ for (let i = 0, len = items.length; i < len; i++) {
143
+ const el = items[i], isActive = i === activeIndex;
144
+ if (roving) el.setAttribute("tabindex", i === activeIndex || activeIndex === -1 && i === tabbableIndex ? baseTabIndex : "-1");
145
+ else if (virtual && activeIndex > 0) el.setAttribute("tabindex", i > activeIndex ? baseTabIndex : "-1");
146
+ else el.setAttribute("tabindex", baseTabIndex);
147
+ if (virtual) el.setAttribute("aria-selected", String(isActive)), el.classList.toggle(activeClass, isActive);
148
+ }
149
+ };
150
+ const resetActiveIndex = (index = -1) => (activeIndex = index, updateDOM());
151
+ const typeAhead = (key) => {
152
+ if (shouldSnub() || !typeahead) return;
153
+ buffer += key.toLowerCase();
154
+ if (timeout) clearTimeout(timeout);
155
+ timeout = setTimeout(() => buffer = "", resetMs);
156
+ const start = activeIndex >= 0 ? activeIndex + 1 : 0;
157
+ for (let i = 0; i < items.length; i++) {
158
+ const idx = (start + i) % items.length, label = (items[idx].getAttribute("data-label") || items[idx].innerText || "").trim().toLowerCase();
159
+ if (label.startsWith(buffer)) return goToIndex(idx);
160
+ }
161
+ };
162
+ const simulateKey = (e) => {
163
+ if (shouldSnub() || getActiveElement()?.matches("option")) return;
164
+ const { key } = e;
165
+ if (!items.length) return;
166
+ if (virtual && (key === " " || key === "Enter")) return items[activeIndex]?.click();
167
+ if (e.target?.matches(DEFAULT_CONFIG.inputSelector) && !virtual) return;
168
+ if (typeahead && key.length === 1 && /^[a-z0-9]$/i.test(key)) return typeAhead(key);
169
+ if (!NAV_KEYS.includes(key)) return;
170
+ if (!(e.currentTarget?.matches(DEFAULT_CONFIG.inputSelector) && gridX <= 1 && H_NAV_KEYS.includes(key))) e.preventDefault?.(), e.stopPropagation?.();
171
+ const currIndex = virtual ? activeIndex : items.indexOf(getActiveElement()), targetIndex = getTargetIndex({ currIndex, gridX, gridY, vGridY, length: items.length, loop, rtl, key, ctrlKey: e.ctrlKey });
172
+ goToIndex(targetIndex, e);
173
+ };
174
+ getItems(), updateDOM();
175
+ const interactiveEls = !virtual ? [container] : [container.querySelector(inputSelector)];
176
+ for (const el of interactiveEls) el?.addEventListener("keydown", simulateKey);
177
+ const handleFocusOut = (e) => {
178
+ if (!container.contains(e.relatedTarget)) return resetActiveIndex(), updateDOM(), onFocusOut?.(e);
179
+ const among = items.includes(e.relatedTarget);
180
+ if (!among && (defaultTabbableIndex ?? -1) >= 0) return resetActiveIndex(), updateDOM();
181
+ if (virtual) resetActiveIndex(), updateDOM();
182
+ };
183
+ container.addEventListener("focusout", handleFocusOut);
184
+ const handleHover = (e) => {
185
+ if (!enabled || !focusOnHover) return;
186
+ const el = e.currentTarget, idx = items.indexOf(el);
187
+ if (idx !== -1) goToIndex(idx);
188
+ };
189
+ for (const el of items) el.addEventListener("mouseenter", handleHover);
190
+ const mutationObserver = new MutationObserver(() => {
191
+ const oldEl = items[activeIndex];
192
+ getItems();
193
+ const newEl = items[activeIndex];
194
+ updateDOM();
195
+ if (oldEl && newEl && oldEl === newEl) return;
196
+ resetActiveIndex();
197
+ updateDOM();
198
+ });
199
+ mutationObserver.observe(container, { childList: true, subtree: true });
200
+ const setGrid = (g) => {
201
+ if (g.x !== void 0) gridX = g.x;
202
+ if (g.y !== void 0) gridY = g.y;
203
+ if (g.vY !== void 0) vGridY = g.vY;
204
+ };
205
+ const calcGrid = () => setGrid(getGrid(items, !grid.x, !grid.y, !grid.vY));
206
+ setGrid(grid), calcGrid();
207
+ const ancestor = items.length > 1 ? getCommonAncestor(items[0], items[1]) : container;
208
+ const resizeObserver = new ResizeObserver(calcGrid);
209
+ if (ancestor) resizeObserver.observe(ancestor);
210
+ const destroy = () => {
211
+ for (const el of interactiveEls) el?.removeEventListener("keydown", simulateKey);
212
+ container.removeEventListener("focusout", handleFocusOut);
213
+ for (const el of items) el.removeEventListener("mouseenter", handleHover);
214
+ mutationObserver.disconnect(), resizeObserver.disconnect();
215
+ if (timeout) clearTimeout(timeout);
216
+ };
217
+ const handle = { gridX: () => gridX, gridY: () => gridY, vGridY: () => vGridY, items: () => items, activeIndex: () => activeIndex, activeItem: () => items[activeIndex] ?? null, getAbleIndex, typeAhead, goToIndex, simulateKey, destroy };
218
+ return t007._ashooters.set(container, handle), handle;
219
+ }
220
+ var removeArrowNavigation = (container) => t007._ashooters?.get(container)?.destroy();
221
+ export {
222
+ initArrowNavigation,
223
+ initFocusTrap,
224
+ initOutsideClick,
225
+ initScrollAssist,
226
+ initVScrollerator,
227
+ removeArrowNavigation,
228
+ removeFocusTrap,
229
+ removeOutsideClick,
230
+ removeScrollAssist,
231
+ rippleHandler
232
+ };
package/dist/index.cjs CHANGED
@@ -20,6 +20,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ INTERACTIVE_SELECTOR: () => INTERACTIVE_SELECTOR,
24
+ NIL: () => import_sia_reactor.NIL,
25
+ NOOP: () => import_sia_reactor.NOOP,
23
26
  VIRTUAL_RESOURCE: () => VIRTUAL_RESOURCE,
24
27
  assignEl: () => import_utils.assignEl,
25
28
  bindAllMethods: () => import_utils6.bindAllMethods,
@@ -31,16 +34,17 @@ __export(index_exports, {
31
34
  deepBreath: () => deepBreath,
32
35
  formatKeyForDisplay: () => import_utils5.formatKeyForDisplay,
33
36
  formatKeyShortcutsForDisplay: () => import_utils5.formatKeyShortcutsForDisplay,
37
+ formatSize: () => formatSize,
38
+ getActiveElement: () => getActiveElement,
34
39
  getTermsForKey: () => import_utils5.getTermsForKey,
35
40
  guardAllMethods: () => import_utils6.guardAllMethods,
36
41
  guardMethod: () => import_utils6.guardMethod,
37
42
  inBoolArrOpt: () => inBoolArrOpt,
38
- initScrollAssist: () => initScrollAssist,
39
- initVScrollerator: () => initVScrollerator,
40
43
  isArr: () => isArr,
41
44
  isBool: () => isBool,
42
45
  isDef: () => isDef,
43
46
  isFunc: () => isFunc,
47
+ isInteractive: () => isInteractive,
44
48
  isIter: () => isIter,
45
49
  isNum: () => isNum,
46
50
  isObj: () => import_utils2.isObj,
@@ -56,7 +60,6 @@ __export(index_exports, {
56
60
  onAllMethods: () => import_utils6.onAllMethods,
57
61
  parseForARIAKS: () => import_utils5.parseForARIAKS,
58
62
  parseKeyCombo: () => import_utils5.parseKeyCombo,
59
- removeScrollAssist: () => removeScrollAssist,
60
63
  requestAnimationFrame: () => import_utils4.requestAnimationFrame,
61
64
  setInterval: () => import_utils4.setInterval,
62
65
  setTimeout: () => import_utils4.setTimeout,
@@ -67,6 +70,8 @@ module.exports = __toCommonJS(index_exports);
67
70
 
68
71
  // src/core/dom.ts
69
72
  var import_utils = require("sia-reactor/utils");
73
+ var INTERACTIVE_SELECTOR = 'button,[href],input,label,select,textarea,details>summary,[contenteditable],iframe,audio[controls],video[controls],[tabindex]:not([tabindex="-1"])';
74
+ var isInteractive = (target) => target instanceof HTMLElement && target.matches(INTERACTIVE_SELECTOR);
70
75
  var VIRTUAL_RESOURCE = /* @__PURE__ */ Symbol.for("T007_VIRTUAL_RESOURCE");
71
76
  function loadResource(req, type = "style", { module: module2, media, crossOrigin, integrity, referrerPolicy, nonce, fetchPriority, attempts = 3, retryKey = false } = {}, w = window) {
72
77
  w.t007 ??= {}, w.t007._resourceCache ??= {};
@@ -95,6 +100,13 @@ function loadResource(req, type = "style", { module: module2, media, crossOrigin
95
100
  });
96
101
  return w.t007._resourceCache[src];
97
102
  }
103
+ function getActiveElement(root = document) {
104
+ const activeEl = root.activeElement;
105
+ return !activeEl ? null : activeEl.shadowRoot ? getActiveElement(activeEl.shadowRoot) : activeEl;
106
+ }
107
+
108
+ // src/index.ts
109
+ var import_sia_reactor = require("sia-reactor");
98
110
 
99
111
  // src/core/obj.ts
100
112
  var import_utils2 = require("sia-reactor/utils");
@@ -173,101 +185,19 @@ function bindCleanupToSignal(cleanup, signal) {
173
185
  // src/core/keys.ts
174
186
  var import_utils5 = require("sia-reactor/utils");
175
187
 
188
+ // src/core/file.ts
189
+ function formatSize(bytes, decimals = 3, base = 1e3) {
190
+ if (bytes < base) return `${bytes} byte${bytes == 1 ? "" : "s"}`;
191
+ const units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"], exponent = Math.min(Math.floor(Math.log(bytes) / Math.log(base)), units.length - 1);
192
+ return `${(bytes / Math.pow(base, exponent)).toFixed(decimals).replace(/\.0+$/, "")} ${units[exponent]}`;
193
+ }
194
+
176
195
  // src/mixins/methd.ts
177
196
  var import_utils6 = require("sia-reactor/utils");
178
197
 
179
- // src/quirks/scroll.ts
180
- function initVScrollerator({ baseSpeed = 3, maxSpeed = 10, stepDelay = 2e3, baseRate = 16, lineHeight = 80, margin = 80, car = window } = {}) {
181
- let linesPerSec = baseSpeed, accelId = null, lastTime = null;
182
- const drive = (clientY, brake = false, offsetY = 0) => {
183
- if (car !== window) clientY -= offsetY;
184
- const now = performance.now(), speed = linesPerSec * lineHeight * ((lastTime ? now - lastTime : baseRate) / 1e3);
185
- if (!brake && (clientY < margin || clientY > (car.innerHeight ?? car.offsetHeight) - margin)) {
186
- accelId === null ? accelId = setTimeout(() => linesPerSec += 1, stepDelay) : linesPerSec > baseSpeed && (linesPerSec = Math.min(linesPerSec + 1, maxSpeed));
187
- car.scrollBy?.(0, clientY < margin ? -speed : speed);
188
- } else reset();
189
- return lastTime = !brake ? now : null, speed;
190
- };
191
- const reset = () => (accelId && clearTimeout(accelId), accelId = null, linesPerSec = baseSpeed, lastTime = null);
192
- return { drive, reset };
193
- }
194
- function initScrollAssist(el, { pxPerSecond = 80, assistClassName = "tmg-video-controls-scroll-assist", vertical = true, horizontal = true } = {}) {
195
- t007._scrollers ??= /* @__PURE__ */ new WeakMap();
196
- t007._scroller_r_observer ??= new ResizeObserver((entries) => entries.forEach(({ target }) => t007._scrollers.get(target)?.update()));
197
- t007._scroller_m_observer ??= new MutationObserver((entries) => {
198
- const els = /* @__PURE__ */ new Set();
199
- for (const entry of entries) {
200
- let node = entry.target instanceof Element ? entry.target : null;
201
- while (node && !t007._scrollers.has(node)) node = node.parentElement;
202
- if (node) els.add(node);
203
- }
204
- for (const el2 of els) t007._scrollers.get(el2)?.update();
205
- });
206
- const parent = el?.parentElement;
207
- if (!parent || t007._scrollers.has(el)) return;
208
- const assist = {};
209
- let scrollId = null, last = performance.now(), assistWidth = 20, assistHeight = 20;
210
- const update = () => {
211
- const hasInteractive = !!parent.querySelector('button, a[href], input, select, textarea, [contenteditable="true"], [tabindex]:not([tabindex="-1"])');
212
- if (horizontal) {
213
- const w = assist.left?.offsetWidth || assistWidth, check = hasInteractive ? el.clientWidth < w * 2 : false;
214
- assist.left.style.display = check ? "none" : el.scrollLeft > 0 ? "block" : "none";
215
- assist.right.style.display = check ? "none" : el.scrollLeft + el.clientWidth < el.scrollWidth - 1 ? "block" : "none";
216
- assistWidth = w;
217
- }
218
- if (vertical) {
219
- const h = assist.up?.offsetHeight || assistHeight, check = hasInteractive ? el.clientHeight < h * 2 : false;
220
- assist.up.style.display = check ? "none" : el.scrollTop > 0 ? "block" : "none";
221
- assist.down.style.display = check ? "none" : el.scrollTop + el.clientHeight < el.scrollHeight - 1 ? "block" : "none";
222
- assistHeight = h;
223
- }
224
- };
225
- const scroll = (dir) => {
226
- const frame = () => {
227
- const now = performance.now(), dt = now - last;
228
- last = now;
229
- const d = pxPerSecond * dt / 1e3;
230
- if (dir === "left") el.scrollLeft = Math.max(0, el.scrollLeft - d);
231
- if (dir === "right") el.scrollLeft = Math.min(el.scrollWidth - el.clientWidth, el.scrollLeft + d);
232
- if (dir === "up") el.scrollTop = Math.max(0, el.scrollTop - d);
233
- if (dir === "down") el.scrollTop = Math.min(el.scrollHeight - el.clientHeight, el.scrollTop + d);
234
- scrollId = requestAnimationFrame(frame);
235
- };
236
- last = performance.now();
237
- frame();
238
- };
239
- const stop = () => (cancelAnimationFrame(scrollId ?? 0), scrollId = null);
240
- const addAssist = (dir) => {
241
- const div = (0, import_utils.createEl)("div", { className: assistClassName }, { scrollDirection: dir }, { display: "none" });
242
- if (!div) return;
243
- ["pointerenter", "dragenter"].forEach((evt) => div.addEventListener(evt, () => scroll(dir)));
244
- ["pointerleave", "pointerup", "pointercancel", "dragleave", "dragend"].forEach((evt) => div.addEventListener(evt, stop));
245
- dir === "left" || dir === "up" ? parent.insertBefore(div, el) : parent.append(div);
246
- assist[dir] = div;
247
- };
248
- if (horizontal) ["left", "right"].forEach(addAssist);
249
- if (vertical) ["up", "down"].forEach(addAssist);
250
- el.addEventListener("scroll", update);
251
- t007._scroller_r_observer.observe(el);
252
- t007._scroller_m_observer.observe(el, { childList: true, subtree: true, characterData: true });
253
- t007._scrollers.set(el, {
254
- update,
255
- destroy() {
256
- stop();
257
- el.removeEventListener("scroll", update);
258
- t007._scroller_r_observer.unobserve(el);
259
- t007._scrollers.delete(el);
260
- Object.values(assist).forEach((a) => a.remove());
261
- }
262
- });
263
- return update(), t007._scrollers.get(el);
264
- }
265
- var removeScrollAssist = (el) => t007._scrollers.get(el)?.destroy();
266
-
267
198
  // src/index.ts
268
199
  if ("undefined" !== typeof window) {
269
- window.t007 ??= {};
270
- t007.VIRTUAL_RESOURCE = VIRTUAL_RESOURCE;
200
+ (window.t007 ??= {}).VIRTUAL_RESOURCE = VIRTUAL_RESOURCE;
271
201
  window.T007_TOAST_JS_SRC ??= `https://cdn.jsdelivr.net/npm/@t007/toast@latest`;
272
202
  window.T007_INPUT_JS_SRC ??= `https://cdn.jsdelivr.net/npm/@t007/input@latest`;
273
203
  window.T007_DIALOG_JS_SRC ??= `https://cdn.jsdelivr.net/npm/@t007/dialog@latest`;
@@ -277,6 +207,9 @@ if ("undefined" !== typeof window) {
277
207
  }
278
208
  // Annotate the CommonJS export names for ESM import in node:
279
209
  0 && (module.exports = {
210
+ INTERACTIVE_SELECTOR,
211
+ NIL,
212
+ NOOP,
280
213
  VIRTUAL_RESOURCE,
281
214
  assignEl,
282
215
  bindAllMethods,
@@ -288,16 +221,17 @@ if ("undefined" !== typeof window) {
288
221
  deepBreath,
289
222
  formatKeyForDisplay,
290
223
  formatKeyShortcutsForDisplay,
224
+ formatSize,
225
+ getActiveElement,
291
226
  getTermsForKey,
292
227
  guardAllMethods,
293
228
  guardMethod,
294
229
  inBoolArrOpt,
295
- initScrollAssist,
296
- initVScrollerator,
297
230
  isArr,
298
231
  isBool,
299
232
  isDef,
300
233
  isFunc,
234
+ isInteractive,
301
235
  isIter,
302
236
  isNum,
303
237
  isObj,
@@ -313,7 +247,6 @@ if ("undefined" !== typeof window) {
313
247
  onAllMethods,
314
248
  parseForARIAKS,
315
249
  parseKeyCombo,
316
- removeScrollAssist,
317
250
  requestAnimationFrame,
318
251
  setInterval,
319
252
  setTimeout,
package/dist/index.d.cts CHANGED
@@ -1,74 +1,18 @@
1
+ import { A as ArrowNavigationHandle } from './arrowNavigation-DK8mqVOk.cjs';
2
+ import { S as ScrollAssistHandle } from './scrollAssist-y9wFmYgt.cjs';
3
+ export { NIL, NOOP } from 'sia-reactor';
1
4
  export { KeyStruct, assignEl, bindAllMethods, clamp, cleanKeyCombo, createEl, formatKeyForDisplay, formatKeyShortcutsForDisplay, getTermsForKey, guardAllMethods, guardMethod, isObj, keyEventAllowed, keysSettings, matchKeys, onAllMethods, parseForARIAKS, parseKeyCombo, requestAnimationFrame, setInterval, setTimeout, stringifyKeyEvent } from 'sia-reactor/utils';
2
5
 
3
- /** Configuration for the vertical edge-scrolling helper. */
4
- interface ScrolleratorOptions {
5
- /** Starting lines-per-second speed. */
6
- baseSpeed?: number;
7
- /** Maximum accelerated speed. */
8
- maxSpeed?: number;
9
- /** Delay before acceleration kicks in. */
10
- stepDelay?: number;
11
- /** Base frame rate used to estimate movement. */
12
- baseRate?: number;
13
- /** Approximate line height in pixels. */
14
- lineHeight?: number;
15
- /** Edge margin that triggers scrolling. */
16
- margin?: number;
17
- /** Scroll container or window target. */
18
- car?: Window | HTMLElement;
19
- }
20
- /** Scrolling controls returned by initVScrollerator. */
21
- interface Scrollerator {
22
- /** Trigger a scroll frame and return the computed distance. */
23
- drive: (clientY: number, brake?: boolean, offsetY?: number) => number;
24
- /** Reset speed and timers. */
25
- reset: () => void;
26
- }
27
- /** Create an edge-driven vertical scrolling controller.
28
- * @param options Scrollerator configuration.
29
- * @returns Drive and reset controls for the controller.
30
- */
31
- declare function initVScrollerator({ baseSpeed, maxSpeed, stepDelay, baseRate, lineHeight, margin, car }?: ScrolleratorOptions): Scrollerator;
32
- /** Scroll assist control object returned by initScrollAssist. */
33
- interface ScrollAssistControl {
34
- /** Recompute assist visibility. */
35
- update: () => void;
36
- /** Tear down observers and assist elements. */
37
- destroy: () => void;
38
- }
39
- /** Configuration for scroll assist overlays. */
40
- interface ScrollAssistOptions {
41
- /** Scroll speed in pixels per second. */
42
- pxPerSecond?: number;
43
- /** Class name applied to assist overlays. */
44
- assistClassName?: string;
45
- /** Enable vertical assist overlays. */
46
- vertical?: boolean;
47
- /** Enable horizontal assist overlays. */
48
- horizontal?: boolean;
49
- }
50
- /** Attach directional scroll assist overlays to an element.
51
- * @param el Scrollable element to enhance.
52
- * @param options Scroll assist configuration.
53
- * @returns Scroll assist controls or void when the element is already managed.
54
- */
55
- declare function initScrollAssist(el: HTMLElement, { pxPerSecond, assistClassName, vertical, horizontal }?: ScrollAssistOptions): ScrollAssistControl | void;
56
- /** Remove scroll assist from an element.
57
- * @param el Target element.
58
- */
59
- declare const removeScrollAssist: (el: HTMLElement) => void | undefined;
60
-
61
6
  declare global {
62
7
  interface T007Namespace {
63
8
  /** Symbol used to mark virtual resources that should not load a real asset. */
64
9
  VIRTUAL_RESOURCE: symbol;
65
- /** Cache used to deduplicate resource loading promises. */
66
10
  _resourceCache: Partial<Record<string, Promise<HTMLElement | void>>>;
67
- /** Active scroll assist controllers keyed by element. */
68
- _scrollers?: WeakMap<HTMLElement, ScrollAssistControl>;
69
- /** Resize observer used by scroll assist controllers. */
11
+ _ftrappers?: WeakMap<HTMLElement, () => void>;
12
+ _outsiders?: WeakMap<HTMLElement, () => void>;
13
+ _ashooters?: WeakMap<HTMLElement, ArrowNavigationHandle>;
14
+ _scrollers?: WeakMap<HTMLElement, ScrollAssistHandle>;
70
15
  _scroller_r_observer?: ResizeObserver;
71
- /** Mutation observer used by scroll assist controllers. */
72
16
  _scroller_m_observer?: MutationObserver;
73
17
  }
74
18
  interface Window {
@@ -162,6 +106,10 @@ declare const deepBreath: (w?: Window & typeof globalThis) => Promise<unknown>;
162
106
  */
163
107
  declare function bindCleanupToSignal<Cb extends () => any>(cleanup: Cb, signal?: AbortSignal): Cb;
164
108
 
109
+ /** Exhaustive Selector used for interactive, tabbable UI controls. */
110
+ declare const INTERACTIVE_SELECTOR = "button,[href],input,label,select,textarea,details>summary,[contenteditable],iframe,audio[controls],video[controls],[tabindex]:not([tabindex=\"-1\"])";
111
+ /** Check whether an event target points to an interactive element. */
112
+ declare const isInteractive: (target: EventTarget | null) => target is HTMLElement;
165
113
  /** Resource type accepted by loadResource. */
166
114
  type ResourceType = "style" | "script" | string;
167
115
  /** Options used when loading a script or stylesheet resource. */
@@ -185,7 +133,6 @@ type LoadResourceOptions = Partial<{
185
133
  /** Cache-busting retry token key. */
186
134
  retryKey: boolean | string;
187
135
  }>;
188
-
189
136
  /** Virtual resource marker used to skip real network loading. */
190
137
  declare const VIRTUAL_RESOURCE: symbol;
191
138
  /** Load a stylesheet or script into the current document with retry support.
@@ -196,5 +143,18 @@ declare const VIRTUAL_RESOURCE: symbol;
196
143
  * @returns Promise resolving to the created element or void.
197
144
  */
198
145
  declare function loadResource(req: string | symbol, type?: ResourceType, { module, media, crossOrigin, integrity, referrerPolicy, nonce, fetchPriority, attempts, retryKey }?: LoadResourceOptions, w?: Window & typeof globalThis): Promise<HTMLElement | void>;
146
+ /** Get the currently active element, traversing into shadow roots if necessary.
147
+ * @param root Root node to start searching from, defaults to the main document.
148
+ * @returns The active element or null if none found.
149
+ */
150
+ declare function getActiveElement(root?: Document | ShadowRoot): Element | null;
151
+
152
+ /** Format a file size for display.
153
+ * @param size Size in bytes.
154
+ * @param decimals Decimal precision.
155
+ * @param base Size base, usually 1000 or 1024.
156
+ * @returns Human-readable size string, i.e., "1.234 KB".
157
+ */
158
+ declare function formatSize(bytes: number, decimals?: number, base?: number): string;
199
159
 
200
- export { type LimitedHandle, type LimitedOptions, type LoadResourceOptions, type ResourceType, type ScrollAssistControl, VIRTUAL_RESOURCE, bindCleanupToSignal, breath, deepBreath, inBoolArrOpt, initScrollAssist, initVScrollerator, isArr, isBool, isDef, isFunc, isIter, isNum, isPOJO, isSameURL, isStr, isSym, limited, loadResource, mockAsync, removeScrollAssist, uid };
160
+ export { INTERACTIVE_SELECTOR, type LimitedHandle, type LimitedOptions, type LoadResourceOptions, type ResourceType, VIRTUAL_RESOURCE, bindCleanupToSignal, breath, deepBreath, formatSize, getActiveElement, inBoolArrOpt, isArr, isBool, isDef, isFunc, isInteractive, isIter, isNum, isPOJO, isSameURL, isStr, isSym, limited, loadResource, mockAsync, uid };