@t007/utils 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +230 -0
- package/dist/index.d.cts +68 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +191 -0
- package/package.json +42 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
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/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
assignEl: () => assignEl,
|
|
24
|
+
bindAllMethods: () => bindAllMethods,
|
|
25
|
+
clamp: () => clamp,
|
|
26
|
+
createEl: () => createEl,
|
|
27
|
+
guardAllMethods: () => guardAllMethods,
|
|
28
|
+
guardMethod: () => guardMethod,
|
|
29
|
+
initScrollAssist: () => initScrollAssist,
|
|
30
|
+
initVScrollerator: () => initVScrollerator,
|
|
31
|
+
isSameURL: () => isSameURL,
|
|
32
|
+
loadResource: () => loadResource,
|
|
33
|
+
onAllMethods: () => onAllMethods,
|
|
34
|
+
removeScrollAssist: () => removeScrollAssist,
|
|
35
|
+
uid: () => uid
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(index_exports);
|
|
38
|
+
|
|
39
|
+
// src/core/num.ts
|
|
40
|
+
function clamp(min = 0, val, max = Infinity) {
|
|
41
|
+
return Math.min(Math.max(val, min), max);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/core/str.ts
|
|
45
|
+
function uid(prefix = "") {
|
|
46
|
+
return prefix + Date.now().toString(36) + "_" + performance.now().toString(36).replace(".", "") + "_" + Math.random().toString(36).slice(2);
|
|
47
|
+
}
|
|
48
|
+
function isSameURL(src1, src2) {
|
|
49
|
+
if (typeof src1 !== "string" || typeof src2 !== "string" || !src1 || !src2) return false;
|
|
50
|
+
try {
|
|
51
|
+
const u1 = new URL(src1, window.location.href), u2 = new URL(src2, window.location.href);
|
|
52
|
+
return decodeURIComponent(u1.origin + u1.pathname) === decodeURIComponent(u2.origin + u2.pathname);
|
|
53
|
+
} catch {
|
|
54
|
+
return src1.replace(/\\/g, "/").split("?")[0].trim() === src2.replace(/\\/g, "/").split("?")[0].trim();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/core/dom.ts
|
|
59
|
+
function createEl(tag, props = {}, dataset = {}, styles = {}) {
|
|
60
|
+
return assignEl(tag ? document?.createElement(tag) : void 0, props, dataset, styles) ?? null;
|
|
61
|
+
}
|
|
62
|
+
function assignEl(el, props = {}, dataset = {}, styles = {}) {
|
|
63
|
+
if (!el) return;
|
|
64
|
+
for (const k of Object.keys(props)) if (props[k] !== void 0) el[k] = props[k];
|
|
65
|
+
for (const k of Object.keys(dataset)) if (dataset[k] !== void 0) el.dataset[k] = String(dataset[k]);
|
|
66
|
+
for (const k of Object.keys(styles)) if (styles[k] !== void 0) el.style[k] = styles[k];
|
|
67
|
+
}
|
|
68
|
+
function loadResource(src, type = "style", { module: module2, media, crossOrigin, integrity, referrerPolicy, nonce, fetchPriority, attempts = 3, retryKey = false } = {}, w = window) {
|
|
69
|
+
w.t007 ??= {}, w.t007._resourceCache ??= {};
|
|
70
|
+
if (w.t007._resourceCache[src]) return w.t007._resourceCache[src];
|
|
71
|
+
const existing = type === "script" ? Array.prototype.find.call(w.document.scripts, (s) => isSameURL(s.src, src)) : type === "style" ? Array.prototype.find.call(w.document.styleSheets, (s) => isSameURL(s.href, src)) : null;
|
|
72
|
+
if (existing) return w.t007._resourceCache[src] = Promise.resolve(existing);
|
|
73
|
+
w.t007._resourceCache[src] = new Promise((resolve, reject) => {
|
|
74
|
+
(function tryLoad(remaining, el) {
|
|
75
|
+
const onerror = () => {
|
|
76
|
+
el?.remove?.();
|
|
77
|
+
if (remaining > 1) {
|
|
78
|
+
setTimeout(tryLoad, 1e3, remaining - 1);
|
|
79
|
+
console.warn(`Retrying ${type} load (${attempts - remaining + 1}): ${src}...`);
|
|
80
|
+
} else {
|
|
81
|
+
delete w.t007._resourceCache[src];
|
|
82
|
+
reject(new Error(`${type} load failed after ${attempts} attempts: ${src}`));
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const url = retryKey && remaining < attempts ? `${src}${src.includes("?") ? "&" : "?"}_${retryKey}=${Date.now()}` : src;
|
|
86
|
+
if (type === "script") w.document.body.append(el = createEl("script", { src: url, type: module2 ? "module" : "text/javascript", crossOrigin, integrity, referrerPolicy, nonce, fetchPriority, onload: () => resolve(el), onerror }) || "");
|
|
87
|
+
else if (type === "style") w.document.head.append(el = createEl("link", { rel: "stylesheet", href: url, media, crossOrigin, integrity, referrerPolicy, nonce, fetchPriority, onload: () => resolve(el), onerror }) || "");
|
|
88
|
+
else reject(new Error(`Unsupported resource type: ${type}`));
|
|
89
|
+
})(attempts);
|
|
90
|
+
});
|
|
91
|
+
return w.t007._resourceCache[src];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/mixins/methodist.ts
|
|
95
|
+
function onAllMethods(owner, callback) {
|
|
96
|
+
let proto = owner;
|
|
97
|
+
while (proto && proto !== Object.prototype) {
|
|
98
|
+
for (const method of Object.getOwnPropertyNames(proto)) {
|
|
99
|
+
if (method === "constructor") continue;
|
|
100
|
+
if ("function" !== typeof Object.getOwnPropertyDescriptor(proto, method)?.value) continue;
|
|
101
|
+
callback(method, owner);
|
|
102
|
+
}
|
|
103
|
+
proto = Object.getPrototypeOf(proto);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function bindAllMethods(owner) {
|
|
107
|
+
onAllMethods(owner, (method, owner2) => {
|
|
108
|
+
owner2[method] = owner2[method].bind(owner2);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function guardAllMethods(owner, guardFn = guardMethod, bound = true) {
|
|
112
|
+
onAllMethods(owner, (method, owner2) => {
|
|
113
|
+
owner2[method] = guardFn(bound ? owner2[method].bind(owner2) : owner2[method]);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function guardMethod(fn, onError = (e) => console.error(e)) {
|
|
117
|
+
return ((...args) => {
|
|
118
|
+
try {
|
|
119
|
+
const result = fn(...args);
|
|
120
|
+
return result instanceof Promise ? result.catch((e) => onError(e)) : result;
|
|
121
|
+
} catch (e) {
|
|
122
|
+
onError(e);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/quirks/scroll.ts
|
|
128
|
+
function initVScrollerator({ baseSpeed = 3, maxSpeed = 10, stepDelay = 2e3, baseRate = 16, lineHeight = 80, margin = 80, car = window } = {}) {
|
|
129
|
+
let linesPerSec = baseSpeed, accelId = null, lastTime = null;
|
|
130
|
+
const drive = (clientY, brake = false, offsetY = 0) => {
|
|
131
|
+
if (car !== window) clientY -= offsetY;
|
|
132
|
+
const now = performance.now(), speed = linesPerSec * lineHeight * ((lastTime ? now - lastTime : baseRate) / 1e3);
|
|
133
|
+
if (!brake && (clientY < margin || clientY > (car.innerHeight ?? car.offsetHeight) - margin)) {
|
|
134
|
+
accelId === null ? accelId = setTimeout(() => linesPerSec += 1, stepDelay) : linesPerSec > baseSpeed && (linesPerSec = Math.min(linesPerSec + 1, maxSpeed));
|
|
135
|
+
car.scrollBy?.(0, clientY < margin ? -speed : speed);
|
|
136
|
+
} else reset();
|
|
137
|
+
return lastTime = !brake ? now : null, speed;
|
|
138
|
+
};
|
|
139
|
+
const reset = () => (accelId && clearTimeout(accelId), accelId = null, linesPerSec = baseSpeed, lastTime = null);
|
|
140
|
+
return { drive, reset };
|
|
141
|
+
}
|
|
142
|
+
var _SCROLLERS = /* @__PURE__ */ new WeakMap();
|
|
143
|
+
var _SCROLLER_R_OBSERVER = typeof window !== "undefined" ? new ResizeObserver((entries) => entries.forEach(({ target }) => _SCROLLERS.get(target)?.update())) : null;
|
|
144
|
+
var _SCROLLER_M_OBSERVER = typeof window !== "undefined" ? new MutationObserver((entries) => {
|
|
145
|
+
const els = /* @__PURE__ */ new Set();
|
|
146
|
+
for (const entry of entries) {
|
|
147
|
+
let node = entry.target instanceof Element ? entry.target : null;
|
|
148
|
+
while (node && !_SCROLLERS.has(node)) node = node.parentElement;
|
|
149
|
+
if (node) els.add(node);
|
|
150
|
+
}
|
|
151
|
+
for (const el of els) _SCROLLERS.get(el)?.update();
|
|
152
|
+
}) : null;
|
|
153
|
+
function initScrollAssist(el, { pxPerSecond = 80, assistClassName = "tmg-video-controls-scroll-assist", vertical = true, horizontal = true } = {}) {
|
|
154
|
+
const parent = el?.parentElement;
|
|
155
|
+
if (!parent || _SCROLLERS.has(el)) return;
|
|
156
|
+
const assist = {};
|
|
157
|
+
let scrollId = null, last = performance.now(), assistWidth = 20, assistHeight = 20;
|
|
158
|
+
const update = () => {
|
|
159
|
+
const hasInteractive = !!parent.querySelector('button, a[href], input, select, textarea, [contenteditable="true"], [tabindex]:not([tabindex="-1"])');
|
|
160
|
+
if (horizontal) {
|
|
161
|
+
const w = assist.left?.offsetWidth || assistWidth, check = hasInteractive ? el.clientWidth < w * 2 : false;
|
|
162
|
+
assist.left.style.display = check ? "none" : el.scrollLeft > 0 ? "block" : "none";
|
|
163
|
+
assist.right.style.display = check ? "none" : el.scrollLeft + el.clientWidth < el.scrollWidth - 1 ? "block" : "none";
|
|
164
|
+
assistWidth = w;
|
|
165
|
+
}
|
|
166
|
+
if (vertical) {
|
|
167
|
+
const h = assist.up?.offsetHeight || assistHeight, check = hasInteractive ? el.clientHeight < h * 2 : false;
|
|
168
|
+
assist.up.style.display = check ? "none" : el.scrollTop > 0 ? "block" : "none";
|
|
169
|
+
assist.down.style.display = check ? "none" : el.scrollTop + el.clientHeight < el.scrollHeight - 1 ? "block" : "none";
|
|
170
|
+
assistHeight = h;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
const scroll = (dir) => {
|
|
174
|
+
const frame = () => {
|
|
175
|
+
const now = performance.now(), dt = now - last;
|
|
176
|
+
last = now;
|
|
177
|
+
const d = pxPerSecond * dt / 1e3;
|
|
178
|
+
if (dir === "left") el.scrollLeft = Math.max(0, el.scrollLeft - d);
|
|
179
|
+
if (dir === "right") el.scrollLeft = Math.min(el.scrollWidth - el.clientWidth, el.scrollLeft + d);
|
|
180
|
+
if (dir === "up") el.scrollTop = Math.max(0, el.scrollTop - d);
|
|
181
|
+
if (dir === "down") el.scrollTop = Math.min(el.scrollHeight - el.clientHeight, el.scrollTop + d);
|
|
182
|
+
scrollId = requestAnimationFrame(frame);
|
|
183
|
+
};
|
|
184
|
+
last = performance.now();
|
|
185
|
+
frame();
|
|
186
|
+
};
|
|
187
|
+
const stop = () => (cancelAnimationFrame(scrollId ?? 0), scrollId = null);
|
|
188
|
+
const addAssist = (dir) => {
|
|
189
|
+
const div = createEl("div", { className: assistClassName }, { scrollDirection: dir }, { display: "none" });
|
|
190
|
+
if (!div) return;
|
|
191
|
+
["pointerenter", "dragenter"].forEach((evt) => div.addEventListener(evt, () => scroll(dir)));
|
|
192
|
+
["pointerleave", "pointerup", "pointercancel", "dragleave", "dragend"].forEach((evt) => div.addEventListener(evt, stop));
|
|
193
|
+
dir === "left" || dir === "up" ? parent.insertBefore(div, el) : parent.append(div);
|
|
194
|
+
assist[dir] = div;
|
|
195
|
+
};
|
|
196
|
+
if (horizontal) ["left", "right"].forEach(addAssist);
|
|
197
|
+
if (vertical) ["up", "down"].forEach(addAssist);
|
|
198
|
+
el.addEventListener("scroll", update);
|
|
199
|
+
_SCROLLER_R_OBSERVER.observe(el);
|
|
200
|
+
_SCROLLER_M_OBSERVER.observe(el, { childList: true, subtree: true, characterData: true });
|
|
201
|
+
_SCROLLERS.set(el, {
|
|
202
|
+
update,
|
|
203
|
+
destroy() {
|
|
204
|
+
stop();
|
|
205
|
+
el.removeEventListener("scroll", update);
|
|
206
|
+
_SCROLLER_R_OBSERVER.unobserve(el);
|
|
207
|
+
_SCROLLERS.delete(el);
|
|
208
|
+
Object.values(assist).forEach((a) => a.remove());
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
update();
|
|
212
|
+
return _SCROLLERS.get(el);
|
|
213
|
+
}
|
|
214
|
+
var removeScrollAssist = (el) => _SCROLLERS.get(el)?.destroy();
|
|
215
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
216
|
+
0 && (module.exports = {
|
|
217
|
+
assignEl,
|
|
218
|
+
bindAllMethods,
|
|
219
|
+
clamp,
|
|
220
|
+
createEl,
|
|
221
|
+
guardAllMethods,
|
|
222
|
+
guardMethod,
|
|
223
|
+
initScrollAssist,
|
|
224
|
+
initVScrollerator,
|
|
225
|
+
isSameURL,
|
|
226
|
+
loadResource,
|
|
227
|
+
onAllMethods,
|
|
228
|
+
removeScrollAssist,
|
|
229
|
+
uid
|
|
230
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
interface T007Namespace {
|
|
3
|
+
_resourceCache: Partial<Record<string, Promise<HTMLElement | void>>>;
|
|
4
|
+
}
|
|
5
|
+
interface Window {
|
|
6
|
+
t007: T007Namespace;
|
|
7
|
+
}
|
|
8
|
+
var t007: T007Namespace;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
declare function clamp(min: number | undefined, val: number, max?: number): number;
|
|
12
|
+
|
|
13
|
+
declare function uid(prefix?: string): string;
|
|
14
|
+
declare function isSameURL(src1: unknown, src2: unknown): boolean;
|
|
15
|
+
|
|
16
|
+
type Dataset = Record<string, string | number>;
|
|
17
|
+
type Style = Partial<CSSStyleDeclaration>;
|
|
18
|
+
type ResourceType = "style" | "script" | string;
|
|
19
|
+
type LoadResourceOptions = Partial<{
|
|
20
|
+
module: boolean;
|
|
21
|
+
media: string;
|
|
22
|
+
crossOrigin: "anonymous" | "use-credentials" | string | null;
|
|
23
|
+
integrity: string;
|
|
24
|
+
referrerPolicy: "no-referrer" | "origin" | "strict-origin-when-cross-origin" | string;
|
|
25
|
+
nonce: string;
|
|
26
|
+
fetchPriority: "high" | "low" | "auto";
|
|
27
|
+
attempts: number;
|
|
28
|
+
retryKey: boolean | string;
|
|
29
|
+
}>;
|
|
30
|
+
declare function createEl<K extends keyof HTMLElementTagNameMap>(tag: K, props?: Partial<HTMLElementTagNameMap[K]>, dataset?: Dataset, styles?: Style): HTMLElementTagNameMap[K];
|
|
31
|
+
declare function createEl(tag: string, props?: Partial<HTMLElement>, dataset?: Dataset, styles?: Style): HTMLElement | null;
|
|
32
|
+
declare function assignEl<K extends keyof HTMLElementTagNameMap>(el?: HTMLElementTagNameMap[K], props?: Partial<HTMLElementTagNameMap[K]>, dataset?: Dataset, styles?: Style): void;
|
|
33
|
+
declare function assignEl(el?: HTMLElement, props?: Partial<HTMLElement>, dataset?: Dataset, styles?: Style): void;
|
|
34
|
+
declare function loadResource(src: string, type?: ResourceType, { module, media, crossOrigin, integrity, referrerPolicy, nonce, fetchPriority, attempts, retryKey }?: LoadResourceOptions, w?: Window & typeof globalThis): Promise<HTMLElement | void>;
|
|
35
|
+
|
|
36
|
+
declare function onAllMethods(owner: any, callback: (method: string, owner: any) => void): void;
|
|
37
|
+
declare function bindAllMethods(owner: any): void;
|
|
38
|
+
declare function guardAllMethods(owner: any, guardFn?: (fn: Function) => Function, bound?: boolean): void;
|
|
39
|
+
declare function guardMethod<T extends Function>(fn: T, onError?: (e: any) => void): T;
|
|
40
|
+
|
|
41
|
+
interface ScrolleratorOptions {
|
|
42
|
+
baseSpeed?: number;
|
|
43
|
+
maxSpeed?: number;
|
|
44
|
+
stepDelay?: number;
|
|
45
|
+
baseRate?: number;
|
|
46
|
+
lineHeight?: number;
|
|
47
|
+
margin?: number;
|
|
48
|
+
car?: Window | HTMLElement;
|
|
49
|
+
}
|
|
50
|
+
interface Scrollerator {
|
|
51
|
+
drive: (clientY: number, brake?: boolean, offsetY?: number) => number;
|
|
52
|
+
reset: () => void;
|
|
53
|
+
}
|
|
54
|
+
declare function initVScrollerator({ baseSpeed, maxSpeed, stepDelay, baseRate, lineHeight, margin, car }?: ScrolleratorOptions): Scrollerator;
|
|
55
|
+
interface ScrollAssistControl {
|
|
56
|
+
update: () => void;
|
|
57
|
+
destroy: () => void;
|
|
58
|
+
}
|
|
59
|
+
interface ScrollAssistOptions {
|
|
60
|
+
pxPerSecond?: number;
|
|
61
|
+
assistClassName?: string;
|
|
62
|
+
vertical?: boolean;
|
|
63
|
+
horizontal?: boolean;
|
|
64
|
+
}
|
|
65
|
+
declare function initScrollAssist(el: HTMLElement, { pxPerSecond, assistClassName, vertical, horizontal }?: ScrollAssistOptions): ScrollAssistControl | void;
|
|
66
|
+
declare const removeScrollAssist: (el: HTMLElement) => void | undefined;
|
|
67
|
+
|
|
68
|
+
export { type Dataset, type LoadResourceOptions, type ResourceType, type Style, assignEl, bindAllMethods, clamp, createEl, guardAllMethods, guardMethod, initScrollAssist, initVScrollerator, isSameURL, loadResource, onAllMethods, removeScrollAssist, uid };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
interface T007Namespace {
|
|
3
|
+
_resourceCache: Partial<Record<string, Promise<HTMLElement | void>>>;
|
|
4
|
+
}
|
|
5
|
+
interface Window {
|
|
6
|
+
t007: T007Namespace;
|
|
7
|
+
}
|
|
8
|
+
var t007: T007Namespace;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
declare function clamp(min: number | undefined, val: number, max?: number): number;
|
|
12
|
+
|
|
13
|
+
declare function uid(prefix?: string): string;
|
|
14
|
+
declare function isSameURL(src1: unknown, src2: unknown): boolean;
|
|
15
|
+
|
|
16
|
+
type Dataset = Record<string, string | number>;
|
|
17
|
+
type Style = Partial<CSSStyleDeclaration>;
|
|
18
|
+
type ResourceType = "style" | "script" | string;
|
|
19
|
+
type LoadResourceOptions = Partial<{
|
|
20
|
+
module: boolean;
|
|
21
|
+
media: string;
|
|
22
|
+
crossOrigin: "anonymous" | "use-credentials" | string | null;
|
|
23
|
+
integrity: string;
|
|
24
|
+
referrerPolicy: "no-referrer" | "origin" | "strict-origin-when-cross-origin" | string;
|
|
25
|
+
nonce: string;
|
|
26
|
+
fetchPriority: "high" | "low" | "auto";
|
|
27
|
+
attempts: number;
|
|
28
|
+
retryKey: boolean | string;
|
|
29
|
+
}>;
|
|
30
|
+
declare function createEl<K extends keyof HTMLElementTagNameMap>(tag: K, props?: Partial<HTMLElementTagNameMap[K]>, dataset?: Dataset, styles?: Style): HTMLElementTagNameMap[K];
|
|
31
|
+
declare function createEl(tag: string, props?: Partial<HTMLElement>, dataset?: Dataset, styles?: Style): HTMLElement | null;
|
|
32
|
+
declare function assignEl<K extends keyof HTMLElementTagNameMap>(el?: HTMLElementTagNameMap[K], props?: Partial<HTMLElementTagNameMap[K]>, dataset?: Dataset, styles?: Style): void;
|
|
33
|
+
declare function assignEl(el?: HTMLElement, props?: Partial<HTMLElement>, dataset?: Dataset, styles?: Style): void;
|
|
34
|
+
declare function loadResource(src: string, type?: ResourceType, { module, media, crossOrigin, integrity, referrerPolicy, nonce, fetchPriority, attempts, retryKey }?: LoadResourceOptions, w?: Window & typeof globalThis): Promise<HTMLElement | void>;
|
|
35
|
+
|
|
36
|
+
declare function onAllMethods(owner: any, callback: (method: string, owner: any) => void): void;
|
|
37
|
+
declare function bindAllMethods(owner: any): void;
|
|
38
|
+
declare function guardAllMethods(owner: any, guardFn?: (fn: Function) => Function, bound?: boolean): void;
|
|
39
|
+
declare function guardMethod<T extends Function>(fn: T, onError?: (e: any) => void): T;
|
|
40
|
+
|
|
41
|
+
interface ScrolleratorOptions {
|
|
42
|
+
baseSpeed?: number;
|
|
43
|
+
maxSpeed?: number;
|
|
44
|
+
stepDelay?: number;
|
|
45
|
+
baseRate?: number;
|
|
46
|
+
lineHeight?: number;
|
|
47
|
+
margin?: number;
|
|
48
|
+
car?: Window | HTMLElement;
|
|
49
|
+
}
|
|
50
|
+
interface Scrollerator {
|
|
51
|
+
drive: (clientY: number, brake?: boolean, offsetY?: number) => number;
|
|
52
|
+
reset: () => void;
|
|
53
|
+
}
|
|
54
|
+
declare function initVScrollerator({ baseSpeed, maxSpeed, stepDelay, baseRate, lineHeight, margin, car }?: ScrolleratorOptions): Scrollerator;
|
|
55
|
+
interface ScrollAssistControl {
|
|
56
|
+
update: () => void;
|
|
57
|
+
destroy: () => void;
|
|
58
|
+
}
|
|
59
|
+
interface ScrollAssistOptions {
|
|
60
|
+
pxPerSecond?: number;
|
|
61
|
+
assistClassName?: string;
|
|
62
|
+
vertical?: boolean;
|
|
63
|
+
horizontal?: boolean;
|
|
64
|
+
}
|
|
65
|
+
declare function initScrollAssist(el: HTMLElement, { pxPerSecond, assistClassName, vertical, horizontal }?: ScrollAssistOptions): ScrollAssistControl | void;
|
|
66
|
+
declare const removeScrollAssist: (el: HTMLElement) => void | undefined;
|
|
67
|
+
|
|
68
|
+
export { type Dataset, type LoadResourceOptions, type ResourceType, type Style, assignEl, bindAllMethods, clamp, createEl, guardAllMethods, guardMethod, initScrollAssist, initVScrollerator, isSameURL, loadResource, onAllMethods, removeScrollAssist, uid };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// src/core/num.ts
|
|
2
|
+
function clamp(min = 0, val, max = Infinity) {
|
|
3
|
+
return Math.min(Math.max(val, min), max);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// src/core/str.ts
|
|
7
|
+
function uid(prefix = "") {
|
|
8
|
+
return prefix + Date.now().toString(36) + "_" + performance.now().toString(36).replace(".", "") + "_" + Math.random().toString(36).slice(2);
|
|
9
|
+
}
|
|
10
|
+
function isSameURL(src1, src2) {
|
|
11
|
+
if (typeof src1 !== "string" || typeof src2 !== "string" || !src1 || !src2) return false;
|
|
12
|
+
try {
|
|
13
|
+
const u1 = new URL(src1, window.location.href), u2 = new URL(src2, window.location.href);
|
|
14
|
+
return decodeURIComponent(u1.origin + u1.pathname) === decodeURIComponent(u2.origin + u2.pathname);
|
|
15
|
+
} catch {
|
|
16
|
+
return src1.replace(/\\/g, "/").split("?")[0].trim() === src2.replace(/\\/g, "/").split("?")[0].trim();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/core/dom.ts
|
|
21
|
+
function createEl(tag, props = {}, dataset = {}, styles = {}) {
|
|
22
|
+
return assignEl(tag ? document?.createElement(tag) : void 0, props, dataset, styles) ?? null;
|
|
23
|
+
}
|
|
24
|
+
function assignEl(el, props = {}, dataset = {}, styles = {}) {
|
|
25
|
+
if (!el) return;
|
|
26
|
+
for (const k of Object.keys(props)) if (props[k] !== void 0) el[k] = props[k];
|
|
27
|
+
for (const k of Object.keys(dataset)) if (dataset[k] !== void 0) el.dataset[k] = String(dataset[k]);
|
|
28
|
+
for (const k of Object.keys(styles)) if (styles[k] !== void 0) el.style[k] = styles[k];
|
|
29
|
+
}
|
|
30
|
+
function loadResource(src, type = "style", { module, media, crossOrigin, integrity, referrerPolicy, nonce, fetchPriority, attempts = 3, retryKey = false } = {}, w = window) {
|
|
31
|
+
w.t007 ??= {}, w.t007._resourceCache ??= {};
|
|
32
|
+
if (w.t007._resourceCache[src]) return w.t007._resourceCache[src];
|
|
33
|
+
const existing = type === "script" ? Array.prototype.find.call(w.document.scripts, (s) => isSameURL(s.src, src)) : type === "style" ? Array.prototype.find.call(w.document.styleSheets, (s) => isSameURL(s.href, src)) : null;
|
|
34
|
+
if (existing) return w.t007._resourceCache[src] = Promise.resolve(existing);
|
|
35
|
+
w.t007._resourceCache[src] = new Promise((resolve, reject) => {
|
|
36
|
+
(function tryLoad(remaining, el) {
|
|
37
|
+
const onerror = () => {
|
|
38
|
+
el?.remove?.();
|
|
39
|
+
if (remaining > 1) {
|
|
40
|
+
setTimeout(tryLoad, 1e3, remaining - 1);
|
|
41
|
+
console.warn(`Retrying ${type} load (${attempts - remaining + 1}): ${src}...`);
|
|
42
|
+
} else {
|
|
43
|
+
delete w.t007._resourceCache[src];
|
|
44
|
+
reject(new Error(`${type} load failed after ${attempts} attempts: ${src}`));
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const url = retryKey && remaining < attempts ? `${src}${src.includes("?") ? "&" : "?"}_${retryKey}=${Date.now()}` : src;
|
|
48
|
+
if (type === "script") w.document.body.append(el = createEl("script", { src: url, type: module ? "module" : "text/javascript", crossOrigin, integrity, referrerPolicy, nonce, fetchPriority, onload: () => resolve(el), onerror }) || "");
|
|
49
|
+
else if (type === "style") w.document.head.append(el = createEl("link", { rel: "stylesheet", href: url, media, crossOrigin, integrity, referrerPolicy, nonce, fetchPriority, onload: () => resolve(el), onerror }) || "");
|
|
50
|
+
else reject(new Error(`Unsupported resource type: ${type}`));
|
|
51
|
+
})(attempts);
|
|
52
|
+
});
|
|
53
|
+
return w.t007._resourceCache[src];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/mixins/methodist.ts
|
|
57
|
+
function onAllMethods(owner, callback) {
|
|
58
|
+
let proto = owner;
|
|
59
|
+
while (proto && proto !== Object.prototype) {
|
|
60
|
+
for (const method of Object.getOwnPropertyNames(proto)) {
|
|
61
|
+
if (method === "constructor") continue;
|
|
62
|
+
if ("function" !== typeof Object.getOwnPropertyDescriptor(proto, method)?.value) continue;
|
|
63
|
+
callback(method, owner);
|
|
64
|
+
}
|
|
65
|
+
proto = Object.getPrototypeOf(proto);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function bindAllMethods(owner) {
|
|
69
|
+
onAllMethods(owner, (method, owner2) => {
|
|
70
|
+
owner2[method] = owner2[method].bind(owner2);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function guardAllMethods(owner, guardFn = guardMethod, bound = true) {
|
|
74
|
+
onAllMethods(owner, (method, owner2) => {
|
|
75
|
+
owner2[method] = guardFn(bound ? owner2[method].bind(owner2) : owner2[method]);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function guardMethod(fn, onError = (e) => console.error(e)) {
|
|
79
|
+
return ((...args) => {
|
|
80
|
+
try {
|
|
81
|
+
const result = fn(...args);
|
|
82
|
+
return result instanceof Promise ? result.catch((e) => onError(e)) : result;
|
|
83
|
+
} catch (e) {
|
|
84
|
+
onError(e);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/quirks/scroll.ts
|
|
90
|
+
function initVScrollerator({ baseSpeed = 3, maxSpeed = 10, stepDelay = 2e3, baseRate = 16, lineHeight = 80, margin = 80, car = window } = {}) {
|
|
91
|
+
let linesPerSec = baseSpeed, accelId = null, lastTime = null;
|
|
92
|
+
const drive = (clientY, brake = false, offsetY = 0) => {
|
|
93
|
+
if (car !== window) clientY -= offsetY;
|
|
94
|
+
const now = performance.now(), speed = linesPerSec * lineHeight * ((lastTime ? now - lastTime : baseRate) / 1e3);
|
|
95
|
+
if (!brake && (clientY < margin || clientY > (car.innerHeight ?? car.offsetHeight) - margin)) {
|
|
96
|
+
accelId === null ? accelId = setTimeout(() => linesPerSec += 1, stepDelay) : linesPerSec > baseSpeed && (linesPerSec = Math.min(linesPerSec + 1, maxSpeed));
|
|
97
|
+
car.scrollBy?.(0, clientY < margin ? -speed : speed);
|
|
98
|
+
} else reset();
|
|
99
|
+
return lastTime = !brake ? now : null, speed;
|
|
100
|
+
};
|
|
101
|
+
const reset = () => (accelId && clearTimeout(accelId), accelId = null, linesPerSec = baseSpeed, lastTime = null);
|
|
102
|
+
return { drive, reset };
|
|
103
|
+
}
|
|
104
|
+
var _SCROLLERS = /* @__PURE__ */ new WeakMap();
|
|
105
|
+
var _SCROLLER_R_OBSERVER = typeof window !== "undefined" ? new ResizeObserver((entries) => entries.forEach(({ target }) => _SCROLLERS.get(target)?.update())) : null;
|
|
106
|
+
var _SCROLLER_M_OBSERVER = typeof window !== "undefined" ? new MutationObserver((entries) => {
|
|
107
|
+
const els = /* @__PURE__ */ new Set();
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
let node = entry.target instanceof Element ? entry.target : null;
|
|
110
|
+
while (node && !_SCROLLERS.has(node)) node = node.parentElement;
|
|
111
|
+
if (node) els.add(node);
|
|
112
|
+
}
|
|
113
|
+
for (const el of els) _SCROLLERS.get(el)?.update();
|
|
114
|
+
}) : null;
|
|
115
|
+
function initScrollAssist(el, { pxPerSecond = 80, assistClassName = "tmg-video-controls-scroll-assist", vertical = true, horizontal = true } = {}) {
|
|
116
|
+
const parent = el?.parentElement;
|
|
117
|
+
if (!parent || _SCROLLERS.has(el)) return;
|
|
118
|
+
const assist = {};
|
|
119
|
+
let scrollId = null, last = performance.now(), assistWidth = 20, assistHeight = 20;
|
|
120
|
+
const update = () => {
|
|
121
|
+
const hasInteractive = !!parent.querySelector('button, a[href], input, select, textarea, [contenteditable="true"], [tabindex]:not([tabindex="-1"])');
|
|
122
|
+
if (horizontal) {
|
|
123
|
+
const w = assist.left?.offsetWidth || assistWidth, check = hasInteractive ? el.clientWidth < w * 2 : false;
|
|
124
|
+
assist.left.style.display = check ? "none" : el.scrollLeft > 0 ? "block" : "none";
|
|
125
|
+
assist.right.style.display = check ? "none" : el.scrollLeft + el.clientWidth < el.scrollWidth - 1 ? "block" : "none";
|
|
126
|
+
assistWidth = w;
|
|
127
|
+
}
|
|
128
|
+
if (vertical) {
|
|
129
|
+
const h = assist.up?.offsetHeight || assistHeight, check = hasInteractive ? el.clientHeight < h * 2 : false;
|
|
130
|
+
assist.up.style.display = check ? "none" : el.scrollTop > 0 ? "block" : "none";
|
|
131
|
+
assist.down.style.display = check ? "none" : el.scrollTop + el.clientHeight < el.scrollHeight - 1 ? "block" : "none";
|
|
132
|
+
assistHeight = h;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
const scroll = (dir) => {
|
|
136
|
+
const frame = () => {
|
|
137
|
+
const now = performance.now(), dt = now - last;
|
|
138
|
+
last = now;
|
|
139
|
+
const d = pxPerSecond * dt / 1e3;
|
|
140
|
+
if (dir === "left") el.scrollLeft = Math.max(0, el.scrollLeft - d);
|
|
141
|
+
if (dir === "right") el.scrollLeft = Math.min(el.scrollWidth - el.clientWidth, el.scrollLeft + d);
|
|
142
|
+
if (dir === "up") el.scrollTop = Math.max(0, el.scrollTop - d);
|
|
143
|
+
if (dir === "down") el.scrollTop = Math.min(el.scrollHeight - el.clientHeight, el.scrollTop + d);
|
|
144
|
+
scrollId = requestAnimationFrame(frame);
|
|
145
|
+
};
|
|
146
|
+
last = performance.now();
|
|
147
|
+
frame();
|
|
148
|
+
};
|
|
149
|
+
const stop = () => (cancelAnimationFrame(scrollId ?? 0), scrollId = null);
|
|
150
|
+
const addAssist = (dir) => {
|
|
151
|
+
const div = createEl("div", { className: assistClassName }, { scrollDirection: dir }, { display: "none" });
|
|
152
|
+
if (!div) return;
|
|
153
|
+
["pointerenter", "dragenter"].forEach((evt) => div.addEventListener(evt, () => scroll(dir)));
|
|
154
|
+
["pointerleave", "pointerup", "pointercancel", "dragleave", "dragend"].forEach((evt) => div.addEventListener(evt, stop));
|
|
155
|
+
dir === "left" || dir === "up" ? parent.insertBefore(div, el) : parent.append(div);
|
|
156
|
+
assist[dir] = div;
|
|
157
|
+
};
|
|
158
|
+
if (horizontal) ["left", "right"].forEach(addAssist);
|
|
159
|
+
if (vertical) ["up", "down"].forEach(addAssist);
|
|
160
|
+
el.addEventListener("scroll", update);
|
|
161
|
+
_SCROLLER_R_OBSERVER.observe(el);
|
|
162
|
+
_SCROLLER_M_OBSERVER.observe(el, { childList: true, subtree: true, characterData: true });
|
|
163
|
+
_SCROLLERS.set(el, {
|
|
164
|
+
update,
|
|
165
|
+
destroy() {
|
|
166
|
+
stop();
|
|
167
|
+
el.removeEventListener("scroll", update);
|
|
168
|
+
_SCROLLER_R_OBSERVER.unobserve(el);
|
|
169
|
+
_SCROLLERS.delete(el);
|
|
170
|
+
Object.values(assist).forEach((a) => a.remove());
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
update();
|
|
174
|
+
return _SCROLLERS.get(el);
|
|
175
|
+
}
|
|
176
|
+
var removeScrollAssist = (el) => _SCROLLERS.get(el)?.destroy();
|
|
177
|
+
export {
|
|
178
|
+
assignEl,
|
|
179
|
+
bindAllMethods,
|
|
180
|
+
clamp,
|
|
181
|
+
createEl,
|
|
182
|
+
guardAllMethods,
|
|
183
|
+
guardMethod,
|
|
184
|
+
initScrollAssist,
|
|
185
|
+
initVScrollerator,
|
|
186
|
+
isSameURL,
|
|
187
|
+
loadResource,
|
|
188
|
+
onAllMethods,
|
|
189
|
+
removeScrollAssist,
|
|
190
|
+
uid
|
|
191
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@t007/utils",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "High-performance, zero-dependency utility functions for the t007 ecosystem.",
|
|
5
|
+
"author": "Oketade Oluwatobiloba <tobioketade007@gmail.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/tobi007-del/t007-tools.git",
|
|
10
|
+
"directory": "packages/utils"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/tobi007-del/t007-tools/tree/main/packages/utils#readme",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "./dist/index.cjs",
|
|
15
|
+
"module": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js",
|
|
22
|
+
"require": "./dist/index.cjs",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"keywords": [
|
|
33
|
+
"t007",
|
|
34
|
+
"utils",
|
|
35
|
+
"helpers",
|
|
36
|
+
"dom-manipulation",
|
|
37
|
+
"ecosystem",
|
|
38
|
+
"zero-dependency",
|
|
39
|
+
"performance",
|
|
40
|
+
"shared-logic"
|
|
41
|
+
]
|
|
42
|
+
}
|