@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.
- package/README.md +91 -4
- package/dist/arrowNavigation-DK8mqVOk.d.cts +31 -0
- package/dist/arrowNavigation-VenvPI4H.d.ts +31 -0
- package/dist/chunk-AI5O3OGE.js +165 -0
- package/dist/chunk-OGFBI6PS.js +16 -0
- package/dist/chunk-XVFFZZJA.js +185 -0
- package/dist/components/react.cjs +51 -0
- package/dist/components/react.d.cts +17 -0
- package/dist/components/react.d.ts +17 -0
- package/dist/components/react.js +15 -0
- package/dist/hooks/react.cjs +466 -0
- package/dist/hooks/react.d.cts +46 -0
- package/dist/hooks/react.d.ts +46 -0
- package/dist/hooks/react.js +276 -0
- package/dist/hooks/vanilla.cjs +407 -0
- package/dist/hooks/vanilla.d.cts +35 -0
- package/dist/hooks/vanilla.d.ts +35 -0
- package/dist/hooks/vanilla.js +232 -0
- package/dist/index.cjs +29 -96
- package/dist/index.d.cts +25 -65
- package/dist/index.d.ts +25 -65
- package/dist/index.js +55 -215
- package/dist/ripple-DcMWw_AP.d.cts +71 -0
- package/dist/ripple-DcMWw_AP.d.ts +71 -0
- package/dist/scrollAssist-y9wFmYgt.d.cts +72 -0
- package/dist/scrollAssist-y9wFmYgt.d.ts +72 -0
- package/dist/styles/ripple.css +55 -0
- package/dist/styles/scroll-assist.css +44 -0
- package/dist/useHighlight-DMpDCILK.d.cts +19 -0
- package/dist/useHighlight-DMpDCILK.d.ts +19 -0
- package/package.json +42 -4
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/hooks/vanilla.ts
|
|
21
|
+
var vanilla_exports = {};
|
|
22
|
+
__export(vanilla_exports, {
|
|
23
|
+
initArrowNavigation: () => initArrowNavigation,
|
|
24
|
+
initFocusTrap: () => initFocusTrap,
|
|
25
|
+
initOutsideClick: () => initOutsideClick,
|
|
26
|
+
initScrollAssist: () => initScrollAssist,
|
|
27
|
+
initVScrollerator: () => initVScrollerator,
|
|
28
|
+
removeArrowNavigation: () => removeArrowNavigation,
|
|
29
|
+
removeFocusTrap: () => removeFocusTrap,
|
|
30
|
+
removeOutsideClick: () => removeOutsideClick,
|
|
31
|
+
removeScrollAssist: () => removeScrollAssist,
|
|
32
|
+
rippleHandler: () => rippleHandler
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(vanilla_exports);
|
|
35
|
+
|
|
36
|
+
// src/hooks/vanilla/scrollAssist.ts
|
|
37
|
+
var import_sia_reactor = require("sia-reactor");
|
|
38
|
+
|
|
39
|
+
// src/core/dom.ts
|
|
40
|
+
var import_utils = require("sia-reactor/utils");
|
|
41
|
+
var INTERACTIVE_SELECTOR = 'button,[href],input,label,select,textarea,details>summary,[contenteditable],iframe,audio[controls],video[controls],[tabindex]:not([tabindex="-1"])';
|
|
42
|
+
var isInteractive = (target) => target instanceof HTMLElement && target.matches(INTERACTIVE_SELECTOR);
|
|
43
|
+
function getActiveElement(root = document) {
|
|
44
|
+
const activeEl = root.activeElement;
|
|
45
|
+
return !activeEl ? null : activeEl.shadowRoot ? getActiveElement(activeEl.shadowRoot) : activeEl;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/core/num.ts
|
|
49
|
+
var import_utils2 = require("sia-reactor/utils");
|
|
50
|
+
|
|
51
|
+
// src/hooks/vanilla/scrollAssist.ts
|
|
52
|
+
function initScrollAssist(el, { pxPerSecond = 80, assistClassName = "t007-scroll-assist", vertical = true, horizontal = true } = import_sia_reactor.NIL) {
|
|
53
|
+
const parent = el?.parentElement, existing = (t007._scrollers ??= /* @__PURE__ */ new WeakMap()).get(el);
|
|
54
|
+
if (!parent || existing) return existing ? existing : void 0;
|
|
55
|
+
t007._scroller_r_observer ??= new ResizeObserver((entries) => {
|
|
56
|
+
for (const { target } of entries) t007._scrollers.get(target)?.update();
|
|
57
|
+
});
|
|
58
|
+
t007._scroller_m_observer ??= new MutationObserver((entries) => {
|
|
59
|
+
const els = /* @__PURE__ */ new Set();
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
let node = entry.target instanceof Element ? entry.target : null;
|
|
62
|
+
while (node && !t007._scrollers.has(node)) node = node.parentElement;
|
|
63
|
+
if (node) els.add(node);
|
|
64
|
+
}
|
|
65
|
+
for (const el2 of els) t007._scrollers.get(el2)?.update();
|
|
66
|
+
});
|
|
67
|
+
const assist = {};
|
|
68
|
+
let scrollId = null, last = performance.now(), assistWidth = 20, assistHeight = 20;
|
|
69
|
+
const update = () => {
|
|
70
|
+
const hasInteractive = !!parent.querySelector(INTERACTIVE_SELECTOR);
|
|
71
|
+
if (horizontal) {
|
|
72
|
+
const w = assist.left?.offsetWidth || assistWidth, check = hasInteractive ? el.clientWidth < w * 2 : false;
|
|
73
|
+
assist.left.style.display = check ? "none" : el.scrollLeft > 0 ? "block" : "none";
|
|
74
|
+
assist.right.style.display = check ? "none" : el.scrollLeft + el.clientWidth < el.scrollWidth - 1 ? "block" : "none";
|
|
75
|
+
assistWidth = w;
|
|
76
|
+
}
|
|
77
|
+
if (vertical) {
|
|
78
|
+
const h = assist.up?.offsetHeight || assistHeight, check = hasInteractive ? el.clientHeight < h * 2 : false;
|
|
79
|
+
assist.up.style.display = check ? "none" : el.scrollTop > 0 ? "block" : "none";
|
|
80
|
+
assist.down.style.display = check ? "none" : el.scrollTop + el.clientHeight < el.scrollHeight - 1 ? "block" : "none";
|
|
81
|
+
assistHeight = h;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
const scroll = (dir) => {
|
|
85
|
+
const frame = () => {
|
|
86
|
+
const now = performance.now(), dt = now - last;
|
|
87
|
+
last = now;
|
|
88
|
+
const d = pxPerSecond * dt / 1e3;
|
|
89
|
+
if (dir === "left") el.scrollLeft = Math.max(0, el.scrollLeft - d);
|
|
90
|
+
if (dir === "right") el.scrollLeft = Math.min(el.scrollWidth - el.clientWidth, el.scrollLeft + d);
|
|
91
|
+
if (dir === "up") el.scrollTop = Math.max(0, el.scrollTop - d);
|
|
92
|
+
if (dir === "down") el.scrollTop = Math.min(el.scrollHeight - el.clientHeight, el.scrollTop + d);
|
|
93
|
+
scrollId = requestAnimationFrame(frame);
|
|
94
|
+
};
|
|
95
|
+
last = performance.now();
|
|
96
|
+
frame();
|
|
97
|
+
};
|
|
98
|
+
const stop = () => (cancelAnimationFrame(scrollId ?? 0), scrollId = null);
|
|
99
|
+
const addAssist = (dir) => {
|
|
100
|
+
const div = (0, import_utils.createEl)("div", { className: assistClassName }, { scrollDirection: dir }, { display: "none" });
|
|
101
|
+
if (!div) return;
|
|
102
|
+
for (const evt of ["pointerenter", "dragenter"]) div.addEventListener(evt, () => scroll(dir));
|
|
103
|
+
for (const evt of ["pointerleave", "pointerup", "pointercancel", "dragleave", "dragend"]) div.addEventListener(evt, stop);
|
|
104
|
+
dir === "left" || dir === "up" ? parent.insertBefore(div, el) : parent.append(div);
|
|
105
|
+
assist[dir] = div;
|
|
106
|
+
};
|
|
107
|
+
if (horizontal) for (const dir of ["left", "right"]) addAssist(dir);
|
|
108
|
+
if (vertical) for (const dir of ["up", "down"]) addAssist(dir);
|
|
109
|
+
const handle = {
|
|
110
|
+
update,
|
|
111
|
+
destroy() {
|
|
112
|
+
stop(), el.removeEventListener("scroll", update);
|
|
113
|
+
t007._scroller_r_observer.unobserve(el), t007._scrollers.delete(el);
|
|
114
|
+
for (const a of Object.values(assist)) a.remove();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
update(), el.addEventListener("scroll", update);
|
|
118
|
+
t007._scroller_r_observer.observe(el), t007._scroller_m_observer.observe(el, { childList: true, subtree: true, characterData: true });
|
|
119
|
+
return t007._scrollers.set(el, handle), handle;
|
|
120
|
+
}
|
|
121
|
+
var removeScrollAssist = (el) => t007._scrollers.get(el)?.destroy();
|
|
122
|
+
|
|
123
|
+
// src/hooks/vanilla/scrollerator.ts
|
|
124
|
+
var import_sia_reactor2 = require("sia-reactor");
|
|
125
|
+
function initVScrollerator({ baseSpeed = 3, maxSpeed = 10, stepDelay = 2e3, baseRate = 16, lineHeight = 80, margin = 80, car = window } = import_sia_reactor2.NIL) {
|
|
126
|
+
let linesPerSec = baseSpeed, accelId = null, lastTime = null;
|
|
127
|
+
const drive = (clientY, brake = false, offsetY = 0) => {
|
|
128
|
+
if (car !== window) clientY -= offsetY;
|
|
129
|
+
const now = performance.now(), speed = linesPerSec * lineHeight * ((lastTime ? now - lastTime : baseRate) / 1e3);
|
|
130
|
+
if (!brake && (clientY < margin || clientY > (car.innerHeight ?? car.offsetHeight) - margin)) {
|
|
131
|
+
accelId === null ? accelId = setTimeout(() => linesPerSec += 1, stepDelay) : linesPerSec > baseSpeed && (linesPerSec = Math.min(linesPerSec + 1, maxSpeed));
|
|
132
|
+
car.scrollBy?.(0, clientY < margin ? -speed : speed);
|
|
133
|
+
} else reset();
|
|
134
|
+
return lastTime = !brake ? now : null, speed;
|
|
135
|
+
};
|
|
136
|
+
const reset = () => (accelId && clearTimeout(accelId), accelId = null, linesPerSec = baseSpeed, lastTime = null);
|
|
137
|
+
return { drive, reset };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/hooks/vanilla/outsideClick.ts
|
|
141
|
+
var import_sia_reactor3 = require("sia-reactor");
|
|
142
|
+
var stacks = /* @__PURE__ */ new WeakMap();
|
|
143
|
+
function initOutsideClick(el, { enabled = false, onOutsideClick = import_sia_reactor3.NOOP, clickOnClick = true, clickOnEscape = true, clickOnFocusOut = false, allowInputs = true, root = window, scoped = true, capture = true } = import_sia_reactor3.NIL) {
|
|
144
|
+
const existing = (t007._outsiders ??= /* @__PURE__ */ new WeakMap()).get(el);
|
|
145
|
+
if (!enabled || existing) return existing ? existing : void 0;
|
|
146
|
+
scoped = scoped && root instanceof HTMLElement, root = scoped ? root : root === document ? document : window;
|
|
147
|
+
const stack = stacks.get(root) ?? [], onScopedOut = (e, t, p = e.touches?.[0] || e, rect = el.getBoundingClientRect()) => {
|
|
148
|
+
if (stack.at(-1) !== el || p.clientX >= rect.left && p.clientX <= rect.right && p.clientY >= rect.top && p.clientY <= rect.bottom) return false;
|
|
149
|
+
return (!scoped ? true : root.contains(t)) && onOutsideClick(e);
|
|
150
|
+
}, handleClick = ((e) => clickOnClick && !(allowInputs && isInteractive(e.target)) && onScopedOut(e, e.target)), handleEscape = ((e) => clickOnEscape && e.key === "Escape" && !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey && stack.at(-1) === el && onOutsideClick(e)), handleFocusOut = (e) => clickOnFocusOut && onScopedOut(e, e.relatedTarget);
|
|
151
|
+
root.addEventListener("mousedown", handleClick, capture), root.addEventListener("touchstart", handleClick, { passive: true, capture });
|
|
152
|
+
root.addEventListener("keydown", handleEscape, capture), el.addEventListener("focusout", handleFocusOut, capture);
|
|
153
|
+
if (!stack.includes(el)) stack.push(el), stacks.set(root, stack);
|
|
154
|
+
const destroy = () => {
|
|
155
|
+
root.removeEventListener("mousedown", handleClick, capture), root.removeEventListener("touchstart", handleClick, capture);
|
|
156
|
+
root.removeEventListener("keydown", handleEscape, capture);
|
|
157
|
+
el.removeEventListener("focusout", handleFocusOut, capture);
|
|
158
|
+
t007._outsiders.delete(el), stack.splice(stack.indexOf(el), 1);
|
|
159
|
+
};
|
|
160
|
+
return t007._outsiders.set(el, destroy), destroy;
|
|
161
|
+
}
|
|
162
|
+
var removeOutsideClick = (el) => t007._outsiders?.get(el)?.();
|
|
163
|
+
|
|
164
|
+
// src/hooks/vanilla/focusTrap.ts
|
|
165
|
+
var import_sia_reactor4 = require("sia-reactor");
|
|
166
|
+
var stacks2 = /* @__PURE__ */ new WeakMap();
|
|
167
|
+
function initFocusTrap(el, { enabled = false, initialSelector = "[data-autofocus]", ringClassName = "focus-outline", root = window, scoped = true, capture = true } = import_sia_reactor4.NIL) {
|
|
168
|
+
const existing = (t007._ftrappers ??= /* @__PURE__ */ new WeakMap()).get(el);
|
|
169
|
+
if (!enabled || existing) return existing ? existing : void 0;
|
|
170
|
+
scoped = scoped && root instanceof HTMLElement, root = scoped ? root : root === document ? document : window;
|
|
171
|
+
const stack = stacks2.get(root) ?? [], 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) => Array.prototype.filter.call(c.querySelectorAll(INTERACTIVE_SELECTOR), (el2) => !el2.hasAttribute("disabled") && !el2.hasAttribute("aria-hidden") && !el2.hasAttribute("data-focus-guard")), resetFocus = (i = 0, els = getFocusable()) => els?.length ? els.at(i).focus() : (!el.hasAttribute("tabindex") && (el.tabIndex = -1), el.focus()), edgeFocus = (pre = false) => {
|
|
172
|
+
if (!scoped) return resetFocus(pre ? -1 : 0);
|
|
173
|
+
else if (root.hasAttribute("tabindex")) return root.focus();
|
|
174
|
+
const items = getFocusable();
|
|
175
|
+
if (!items.length) return resetFocus(0, null);
|
|
176
|
+
const all = getFocusable(root.parentElement?.closest(`:has(${INTERACTIVE_SELECTOR})`) || document.body);
|
|
177
|
+
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 (!root.contains(target = all[i])) return target.focus();
|
|
178
|
+
(pre ? first : last).blur();
|
|
179
|
+
}, handleFocusIn = () => stack.at(-1) === el && getActiveElement() !== root && !el.contains(getActiveElement()) && setTimeout(resetFocus, 0, 0), handleInitialBlur = () => initial.classList.remove(ringClassName);
|
|
180
|
+
first.addEventListener("focus", (e) => el.contains(e.relatedTarget) ? edgeFocus(true) : resetFocus(), capture), el.prepend(first);
|
|
181
|
+
last.addEventListener("focus", (e) => el.contains(e.relatedTarget) ? edgeFocus() : resetFocus(-1), capture), el.append(last);
|
|
182
|
+
root.addEventListener("focusin", handleFocusIn, capture);
|
|
183
|
+
if (!el.querySelector(":focus")) !initial ? resetFocus() : setTimeout(() => (initial.classList.add(ringClassName), initial.focus(), initial.addEventListener("blur", handleInitialBlur, capture)));
|
|
184
|
+
if (!stack.includes(el)) stack.push(el), stacks2.set(root, stack);
|
|
185
|
+
const destroy = () => {
|
|
186
|
+
focused?.isConnected && focused.focus(), first.remove(), last.remove();
|
|
187
|
+
root.removeEventListener("focusin", handleFocusIn, capture);
|
|
188
|
+
initial?.removeEventListener("blur", handleInitialBlur, capture);
|
|
189
|
+
t007._ftrappers.delete(el), stack.splice(stack.indexOf(el), 1);
|
|
190
|
+
};
|
|
191
|
+
return t007._ftrappers.set(el, destroy), destroy;
|
|
192
|
+
}
|
|
193
|
+
var removeFocusTrap = (el) => t007._ftrappers?.get(el)?.();
|
|
194
|
+
|
|
195
|
+
// src/hooks/react/useArrowNavigation/consts.ts
|
|
196
|
+
var import_sia_reactor5 = require("sia-reactor");
|
|
197
|
+
var H_NAV_KEYS = ["ArrowRight", "ArrowLeft", "Home", "End"];
|
|
198
|
+
var V_NAV_KEYS = ["ArrowUp", "ArrowDown", "PageDown", "PageUp"];
|
|
199
|
+
var NAV_KEYS = [...H_NAV_KEYS, ...V_NAV_KEYS];
|
|
200
|
+
var DEFAULT_CONFIG = {
|
|
201
|
+
enabled: null,
|
|
202
|
+
selector: "[data-arrow-item]",
|
|
203
|
+
focusOnHover: true,
|
|
204
|
+
loop: true,
|
|
205
|
+
virtual: false,
|
|
206
|
+
typeahead: false,
|
|
207
|
+
rovingTab: null,
|
|
208
|
+
defaultTabbableIndex: null,
|
|
209
|
+
baseTabIndex: "0",
|
|
210
|
+
resetMs: 500,
|
|
211
|
+
rtl: null,
|
|
212
|
+
grid: {},
|
|
213
|
+
activeClass: "focus-outlined",
|
|
214
|
+
inputSelector: "input,textarea,[contenteditable='true']",
|
|
215
|
+
focusOptions: { preventScroll: false },
|
|
216
|
+
scrollIntoView: { block: "nearest", inline: "nearest" },
|
|
217
|
+
onSelect: import_sia_reactor5.NOOP,
|
|
218
|
+
onFocusOut: import_sia_reactor5.NOOP
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// src/hooks/react/useArrowNavigation/utils.ts
|
|
222
|
+
var getTargetIndex = ({ key, currIndex, length, gridX, gridY, vGridY, loop, ctrlKey = false, rtl }) => {
|
|
223
|
+
const rowStart = currIndex - currIndex % gridX, rowEnd = Math.min(rowStart + gridX - 1, length - 1), colStart = currIndex % gridX, colEnd = Math.min(colStart + gridX * (gridY - 1), length - 1), canX = gridX > 1, canY = gridY > 1, horizontalMove = rtl ? { ArrowRight: canX ? -1 : 0, ArrowLeft: canX ? 1 : 0 } : { ArrowRight: canX ? 1 : 0, ArrowLeft: canX ? -1 : 0 }, move = { ...horizontalMove, ArrowDown: canY ? gridX : 0, ArrowUp: canY ? -gridX : 0, Home: ctrlKey ? 0 : rowStart, End: ctrlKey ? length - 1 : rowEnd, PageDown: (vGridY - 1) * gridX, PageUp: -(vGridY - 1) * gridX }[key] ?? 0;
|
|
224
|
+
let targetIndex = key === "Home" || key === "End" ? move : currIndex + move;
|
|
225
|
+
if (key === "ArrowDown") {
|
|
226
|
+
if (!loop && targetIndex >= length) targetIndex -= move;
|
|
227
|
+
} else if (key === "ArrowUp") {
|
|
228
|
+
if (!loop && targetIndex < 0) targetIndex += Math.abs(move);
|
|
229
|
+
} else if (key === "PageDown") {
|
|
230
|
+
if (!loop && targetIndex >= length) targetIndex = colEnd;
|
|
231
|
+
} else if (key === "PageUp") {
|
|
232
|
+
if (!loop && targetIndex < 0) targetIndex = colStart;
|
|
233
|
+
}
|
|
234
|
+
return loop ? (targetIndex + length) % length : (0, import_utils2.clamp)(0, targetIndex, length - 1);
|
|
235
|
+
};
|
|
236
|
+
var getCommonAncestor = (first, second) => {
|
|
237
|
+
if (!first) return null;
|
|
238
|
+
if (!second) return first.parentElement;
|
|
239
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
240
|
+
let current = first;
|
|
241
|
+
while (current) ancestors.add(current), current = current.parentElement;
|
|
242
|
+
current = second;
|
|
243
|
+
while (current) {
|
|
244
|
+
if (ancestors.has(current)) return current;
|
|
245
|
+
current = current.parentElement;
|
|
246
|
+
}
|
|
247
|
+
return null;
|
|
248
|
+
};
|
|
249
|
+
var getGrid = (all, x = true, y = true, vY = true) => {
|
|
250
|
+
const len = all.length, grid = {};
|
|
251
|
+
if (!len) return grid;
|
|
252
|
+
let cols = all.findIndex((el) => el.offsetTop !== all[0].offsetTop);
|
|
253
|
+
cols = cols > 0 ? cols : len;
|
|
254
|
+
if (x) grid.x = cols;
|
|
255
|
+
let rows = Math.ceil(len / cols);
|
|
256
|
+
if (y) grid.y = rows;
|
|
257
|
+
if (vY) {
|
|
258
|
+
const itemHeight = all[0].offsetHeight ?? 0, containerHeight = getCommonAncestor(all[0], all[1])?.clientHeight ?? 0;
|
|
259
|
+
rows = (0, import_utils2.clamp)(1, Math.floor(containerHeight / itemHeight), rows) || rows;
|
|
260
|
+
grid.vY = rows;
|
|
261
|
+
}
|
|
262
|
+
return grid;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// src/hooks/vanilla/arrowNavigation.ts
|
|
266
|
+
function initArrowNavigation(container, config = {}) {
|
|
267
|
+
const existing = (t007._ashooters ??= /* @__PURE__ */ new WeakMap()).get(container);
|
|
268
|
+
if (!config.enabled || existing) return existing ? existing : void 0;
|
|
269
|
+
const { enabled: isEnabled, selector, focusOnHover, loop, virtual, typeahead, resetMs, activeClass, inputSelector, defaultTabbableIndex, baseTabIndex, grid, rtl: isRtl, focusOptions, scrollIntoView, onSelect, onFocusOut, rovingTab } = { ...DEFAULT_CONFIG, ...config };
|
|
270
|
+
let gridX = grid.x || 1, gridY = grid.y || 1, vGridY = grid.vY || 1, activeIndex = -1, buffer = "", timeout = null, items = [];
|
|
271
|
+
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));
|
|
272
|
+
const getAbleIndex = (targetIndex, e = { key: "ArrowRight", ctrlKey: false }) => {
|
|
273
|
+
if (shouldSnub() || !items.length) return null;
|
|
274
|
+
let index = targetIndex;
|
|
275
|
+
let attempts = 0;
|
|
276
|
+
while (attempts < items.length) {
|
|
277
|
+
if (!isItemDisabled(items[index])) return index;
|
|
278
|
+
index = getTargetIndex({ key: e.key, currIndex: index, gridX, gridY, vGridY, length: items.length, loop, ctrlKey: e.ctrlKey, rtl });
|
|
279
|
+
attempts++;
|
|
280
|
+
}
|
|
281
|
+
return null;
|
|
282
|
+
};
|
|
283
|
+
const goToIndex = (targetIndex, e = { key: "ArrowRight" }) => {
|
|
284
|
+
if (shouldSnub()) return;
|
|
285
|
+
const idx = getAbleIndex(targetIndex, e);
|
|
286
|
+
if (idx === null) return;
|
|
287
|
+
resetActiveIndex(idx);
|
|
288
|
+
onSelect?.(items[idx], e), updateDOM();
|
|
289
|
+
if (virtual) {
|
|
290
|
+
items[idx]?.scrollIntoView(scrollIntoView);
|
|
291
|
+
container.setAttribute("aria-activedescendant", items[idx].id || "");
|
|
292
|
+
} else items[idx]?.focus(focusOptions);
|
|
293
|
+
};
|
|
294
|
+
const updateDOM = () => {
|
|
295
|
+
if (shouldSnub() || !virtual && !roving || !items.length) return;
|
|
296
|
+
const hasDefaultTabbable = defaultTabbableIndex !== null && defaultTabbableIndex !== void 0, tabbableIndex = hasDefaultTabbable && !isItemDisabled(items[defaultTabbableIndex]) ? defaultTabbableIndex : getAbleIndex(0);
|
|
297
|
+
for (let i = 0, len = items.length; i < len; i++) {
|
|
298
|
+
const el = items[i], isActive = i === activeIndex;
|
|
299
|
+
if (roving) el.setAttribute("tabindex", i === activeIndex || activeIndex === -1 && i === tabbableIndex ? baseTabIndex : "-1");
|
|
300
|
+
else if (virtual && activeIndex > 0) el.setAttribute("tabindex", i > activeIndex ? baseTabIndex : "-1");
|
|
301
|
+
else el.setAttribute("tabindex", baseTabIndex);
|
|
302
|
+
if (virtual) el.setAttribute("aria-selected", String(isActive)), el.classList.toggle(activeClass, isActive);
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
const resetActiveIndex = (index = -1) => (activeIndex = index, updateDOM());
|
|
306
|
+
const typeAhead = (key) => {
|
|
307
|
+
if (shouldSnub() || !typeahead) return;
|
|
308
|
+
buffer += key.toLowerCase();
|
|
309
|
+
if (timeout) clearTimeout(timeout);
|
|
310
|
+
timeout = setTimeout(() => buffer = "", resetMs);
|
|
311
|
+
const start = activeIndex >= 0 ? activeIndex + 1 : 0;
|
|
312
|
+
for (let i = 0; i < items.length; i++) {
|
|
313
|
+
const idx = (start + i) % items.length, label = (items[idx].getAttribute("data-label") || items[idx].innerText || "").trim().toLowerCase();
|
|
314
|
+
if (label.startsWith(buffer)) return goToIndex(idx);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
const simulateKey = (e) => {
|
|
318
|
+
if (shouldSnub() || getActiveElement()?.matches("option")) return;
|
|
319
|
+
const { key } = e;
|
|
320
|
+
if (!items.length) return;
|
|
321
|
+
if (virtual && (key === " " || key === "Enter")) return items[activeIndex]?.click();
|
|
322
|
+
if (e.target?.matches(DEFAULT_CONFIG.inputSelector) && !virtual) return;
|
|
323
|
+
if (typeahead && key.length === 1 && /^[a-z0-9]$/i.test(key)) return typeAhead(key);
|
|
324
|
+
if (!NAV_KEYS.includes(key)) return;
|
|
325
|
+
if (!(e.currentTarget?.matches(DEFAULT_CONFIG.inputSelector) && gridX <= 1 && H_NAV_KEYS.includes(key))) e.preventDefault?.(), e.stopPropagation?.();
|
|
326
|
+
const currIndex = virtual ? activeIndex : items.indexOf(getActiveElement()), targetIndex = getTargetIndex({ currIndex, gridX, gridY, vGridY, length: items.length, loop, rtl, key, ctrlKey: e.ctrlKey });
|
|
327
|
+
goToIndex(targetIndex, e);
|
|
328
|
+
};
|
|
329
|
+
getItems(), updateDOM();
|
|
330
|
+
const interactiveEls = !virtual ? [container] : [container.querySelector(inputSelector)];
|
|
331
|
+
for (const el of interactiveEls) el?.addEventListener("keydown", simulateKey);
|
|
332
|
+
const handleFocusOut = (e) => {
|
|
333
|
+
if (!container.contains(e.relatedTarget)) return resetActiveIndex(), updateDOM(), onFocusOut?.(e);
|
|
334
|
+
const among = items.includes(e.relatedTarget);
|
|
335
|
+
if (!among && (defaultTabbableIndex ?? -1) >= 0) return resetActiveIndex(), updateDOM();
|
|
336
|
+
if (virtual) resetActiveIndex(), updateDOM();
|
|
337
|
+
};
|
|
338
|
+
container.addEventListener("focusout", handleFocusOut);
|
|
339
|
+
const handleHover = (e) => {
|
|
340
|
+
if (!enabled || !focusOnHover) return;
|
|
341
|
+
const el = e.currentTarget, idx = items.indexOf(el);
|
|
342
|
+
if (idx !== -1) goToIndex(idx);
|
|
343
|
+
};
|
|
344
|
+
for (const el of items) el.addEventListener("mouseenter", handleHover);
|
|
345
|
+
const mutationObserver = new MutationObserver(() => {
|
|
346
|
+
const oldEl = items[activeIndex];
|
|
347
|
+
getItems();
|
|
348
|
+
const newEl = items[activeIndex];
|
|
349
|
+
updateDOM();
|
|
350
|
+
if (oldEl && newEl && oldEl === newEl) return;
|
|
351
|
+
resetActiveIndex();
|
|
352
|
+
updateDOM();
|
|
353
|
+
});
|
|
354
|
+
mutationObserver.observe(container, { childList: true, subtree: true });
|
|
355
|
+
const setGrid = (g) => {
|
|
356
|
+
if (g.x !== void 0) gridX = g.x;
|
|
357
|
+
if (g.y !== void 0) gridY = g.y;
|
|
358
|
+
if (g.vY !== void 0) vGridY = g.vY;
|
|
359
|
+
};
|
|
360
|
+
const calcGrid = () => setGrid(getGrid(items, !grid.x, !grid.y, !grid.vY));
|
|
361
|
+
setGrid(grid), calcGrid();
|
|
362
|
+
const ancestor = items.length > 1 ? getCommonAncestor(items[0], items[1]) : container;
|
|
363
|
+
const resizeObserver = new ResizeObserver(calcGrid);
|
|
364
|
+
if (ancestor) resizeObserver.observe(ancestor);
|
|
365
|
+
const destroy = () => {
|
|
366
|
+
for (const el of interactiveEls) el?.removeEventListener("keydown", simulateKey);
|
|
367
|
+
container.removeEventListener("focusout", handleFocusOut);
|
|
368
|
+
for (const el of items) el.removeEventListener("mouseenter", handleHover);
|
|
369
|
+
mutationObserver.disconnect(), resizeObserver.disconnect();
|
|
370
|
+
if (timeout) clearTimeout(timeout);
|
|
371
|
+
};
|
|
372
|
+
const handle = { gridX: () => gridX, gridY: () => gridY, vGridY: () => vGridY, items: () => items, activeIndex: () => activeIndex, activeItem: () => items[activeIndex] ?? null, getAbleIndex, typeAhead, goToIndex, simulateKey, destroy };
|
|
373
|
+
return t007._ashooters.set(container, handle), handle;
|
|
374
|
+
}
|
|
375
|
+
var removeArrowNavigation = (container) => t007._ashooters?.get(container)?.destroy();
|
|
376
|
+
|
|
377
|
+
// src/hooks/vanilla/ripple.ts
|
|
378
|
+
var import_sia_reactor6 = require("sia-reactor");
|
|
379
|
+
function rippleHandler(e, { target, forceCenter = false, wrapperClassName = "t007-ripple-wrapper", className = "t007-ripple", holdClassName = "t007-ripple-hold", fadeClassName = "t007-ripple-fade" } = import_sia_reactor6.NIL) {
|
|
380
|
+
const el = target || e.currentTarget;
|
|
381
|
+
if (!el || e.target !== e.currentTarget && isInteractive(e.target) || el.hasAttribute("disabled") || e.pointerType === "mouse" && e.button !== 0) return;
|
|
382
|
+
e.stopPropagation?.();
|
|
383
|
+
const { offsetWidth: rW, offsetHeight: rH } = el, { width: w, height: h, left: l, top: t } = el.getBoundingClientRect(), size = Math.max(rW, rH), x = forceCenter ? rW / 2 - size / 2 : (e.clientX - l) * rW / w - size / 2, y = forceCenter ? rH / 2 - size / 2 : (e.clientY - t) * rH / h - size / 2, wrapper = (0, import_utils.createEl)("span", { className: wrapperClassName }), ripple = (0, import_utils.createEl)("span", { className: className + " " + holdClassName }, {}, { cssText: `width:${size}px;height:${size}px;left:${x}px;top:${y}px;` });
|
|
384
|
+
let canRelease = false;
|
|
385
|
+
ripple.addEventListener("animationend", () => canRelease = true, { once: true });
|
|
386
|
+
el.append(wrapper.appendChild(ripple).parentElement);
|
|
387
|
+
const release = () => {
|
|
388
|
+
if (!canRelease) return ripple.addEventListener("animationend", release, { once: true });
|
|
389
|
+
ripple.classList.replace(holdClassName, fadeClassName);
|
|
390
|
+
ripple.addEventListener("animationend", () => setTimeout(() => wrapper.remove()));
|
|
391
|
+
for (const evt of ["pointerup", "pointercancel"]) (el.ownerDocument?.defaultView || window).removeEventListener(evt, release);
|
|
392
|
+
};
|
|
393
|
+
for (const evt of ["pointerup", "pointercancel"]) (el.ownerDocument?.defaultView || window).addEventListener(evt, release);
|
|
394
|
+
}
|
|
395
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
396
|
+
0 && (module.exports = {
|
|
397
|
+
initArrowNavigation,
|
|
398
|
+
initFocusTrap,
|
|
399
|
+
initOutsideClick,
|
|
400
|
+
initScrollAssist,
|
|
401
|
+
initVScrollerator,
|
|
402
|
+
removeArrowNavigation,
|
|
403
|
+
removeFocusTrap,
|
|
404
|
+
removeOutsideClick,
|
|
405
|
+
removeScrollAssist,
|
|
406
|
+
rippleHandler
|
|
407
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export { a as ScrollAssistConfig, S as ScrollAssistHandle, b as ScrollDir, i as initScrollAssist, r as removeScrollAssist } from '../scrollAssist-y9wFmYgt.cjs';
|
|
2
|
+
export { F as FocusTrapConfig, O as OutsideClickConfig, R as RippleConfig, i as initFocusTrap, a as initOutsideClick, r as removeFocusTrap, b as removeOutsideClick, c as rippleHandler } from '../ripple-DcMWw_AP.cjs';
|
|
3
|
+
export { A as ArrowNavigationHandle, i as initArrowNavigation, r as removeArrowNavigation } from '../arrowNavigation-DK8mqVOk.cjs';
|
|
4
|
+
|
|
5
|
+
/** Configuration for the vertical edge-scrolling helper. */
|
|
6
|
+
interface ScrolleratorOptions {
|
|
7
|
+
/** Starting lines-per-second speed. Defaults to `3`. */
|
|
8
|
+
baseSpeed?: number;
|
|
9
|
+
/** Maximum accelerated speed. Defaults to `10`. */
|
|
10
|
+
maxSpeed?: number;
|
|
11
|
+
/** Delay before acceleration kicks in. Defaults to `2000`. */
|
|
12
|
+
stepDelay?: number;
|
|
13
|
+
/** Base frame rate used to estimate movement. Defaults to `16`. */
|
|
14
|
+
baseRate?: number;
|
|
15
|
+
/** Approximate line height in pixels. Defaults to `80`. */
|
|
16
|
+
lineHeight?: number;
|
|
17
|
+
/** Edge margin that triggers scrolling. Defaults to `80`. */
|
|
18
|
+
margin?: number;
|
|
19
|
+
/** Scroll container or window target. Defaults to `window`. */
|
|
20
|
+
car?: Window | HTMLElement;
|
|
21
|
+
}
|
|
22
|
+
/** Scrolling controls returned by initVScrollerator. */
|
|
23
|
+
interface Scrollerator {
|
|
24
|
+
/** Trigger a scroll frame and return the computed distance. */
|
|
25
|
+
drive: (clientY: number, brake?: boolean, offsetY?: number) => number;
|
|
26
|
+
/** Reset speed and timers. */
|
|
27
|
+
reset: () => void;
|
|
28
|
+
}
|
|
29
|
+
/** Create an edge-driven vertical scrolling controller.
|
|
30
|
+
* @param options Scrollerator configuration.
|
|
31
|
+
* @returns Drive and reset controls for the controller.
|
|
32
|
+
*/
|
|
33
|
+
declare function initVScrollerator({ baseSpeed, maxSpeed, stepDelay, baseRate, lineHeight, margin, car }?: ScrolleratorOptions): Scrollerator;
|
|
34
|
+
|
|
35
|
+
export { initVScrollerator };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export { a as ScrollAssistConfig, S as ScrollAssistHandle, b as ScrollDir, i as initScrollAssist, r as removeScrollAssist } from '../scrollAssist-y9wFmYgt.js';
|
|
2
|
+
export { F as FocusTrapConfig, O as OutsideClickConfig, R as RippleConfig, i as initFocusTrap, a as initOutsideClick, r as removeFocusTrap, b as removeOutsideClick, c as rippleHandler } from '../ripple-DcMWw_AP.js';
|
|
3
|
+
export { A as ArrowNavigationHandle, i as initArrowNavigation, r as removeArrowNavigation } from '../arrowNavigation-VenvPI4H.js';
|
|
4
|
+
|
|
5
|
+
/** Configuration for the vertical edge-scrolling helper. */
|
|
6
|
+
interface ScrolleratorOptions {
|
|
7
|
+
/** Starting lines-per-second speed. Defaults to `3`. */
|
|
8
|
+
baseSpeed?: number;
|
|
9
|
+
/** Maximum accelerated speed. Defaults to `10`. */
|
|
10
|
+
maxSpeed?: number;
|
|
11
|
+
/** Delay before acceleration kicks in. Defaults to `2000`. */
|
|
12
|
+
stepDelay?: number;
|
|
13
|
+
/** Base frame rate used to estimate movement. Defaults to `16`. */
|
|
14
|
+
baseRate?: number;
|
|
15
|
+
/** Approximate line height in pixels. Defaults to `80`. */
|
|
16
|
+
lineHeight?: number;
|
|
17
|
+
/** Edge margin that triggers scrolling. Defaults to `80`. */
|
|
18
|
+
margin?: number;
|
|
19
|
+
/** Scroll container or window target. Defaults to `window`. */
|
|
20
|
+
car?: Window | HTMLElement;
|
|
21
|
+
}
|
|
22
|
+
/** Scrolling controls returned by initVScrollerator. */
|
|
23
|
+
interface Scrollerator {
|
|
24
|
+
/** Trigger a scroll frame and return the computed distance. */
|
|
25
|
+
drive: (clientY: number, brake?: boolean, offsetY?: number) => number;
|
|
26
|
+
/** Reset speed and timers. */
|
|
27
|
+
reset: () => void;
|
|
28
|
+
}
|
|
29
|
+
/** Create an edge-driven vertical scrolling controller.
|
|
30
|
+
* @param options Scrollerator configuration.
|
|
31
|
+
* @returns Drive and reset controls for the controller.
|
|
32
|
+
*/
|
|
33
|
+
declare function initVScrollerator({ baseSpeed, maxSpeed, stepDelay, baseRate, lineHeight, margin, car }?: ScrolleratorOptions): Scrollerator;
|
|
34
|
+
|
|
35
|
+
export { initVScrollerator };
|