@bquery/bquery 1.3.0 → 1.4.0
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 +527 -501
- package/dist/{batch-4LAvfLE7.js → batch-x7b2eZST.js} +2 -2
- package/dist/{batch-4LAvfLE7.js.map → batch-x7b2eZST.js.map} +1 -1
- package/dist/component.es.mjs +1 -1
- package/dist/core/collection.d.ts +19 -3
- package/dist/core/collection.d.ts.map +1 -1
- package/dist/core/element.d.ts +23 -4
- package/dist/core/element.d.ts.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/utils/function.d.ts +21 -4
- package/dist/core/utils/function.d.ts.map +1 -1
- package/dist/{core-COenAZjD.js → core-BhpuvPhy.js} +62 -37
- package/dist/core-BhpuvPhy.js.map +1 -0
- package/dist/core.es.mjs +174 -131
- package/dist/core.es.mjs.map +1 -1
- package/dist/full.es.mjs +7 -7
- package/dist/full.iife.js +2 -2
- package/dist/full.iife.js.map +1 -1
- package/dist/full.umd.js +2 -2
- package/dist/full.umd.js.map +1 -1
- package/dist/index.es.mjs +7 -7
- package/dist/motion.es.mjs.map +1 -1
- package/dist/{persisted-Dz_ryNuC.js → persisted-DHoi3uEs.js} +4 -4
- package/dist/{persisted-Dz_ryNuC.js.map → persisted-DHoi3uEs.js.map} +1 -1
- package/dist/platform/storage.d.ts.map +1 -1
- package/dist/platform.es.mjs +12 -7
- package/dist/platform.es.mjs.map +1 -1
- package/dist/reactive/core.d.ts +12 -0
- package/dist/reactive/core.d.ts.map +1 -1
- package/dist/reactive/effect.d.ts.map +1 -1
- package/dist/reactive/internals.d.ts +6 -0
- package/dist/reactive/internals.d.ts.map +1 -1
- package/dist/reactive.es.mjs +6 -6
- package/dist/router.es.mjs +1 -1
- package/dist/{sanitize-1FBEPAFH.js → sanitize-Cxvxa-DX.js} +50 -39
- package/dist/sanitize-Cxvxa-DX.js.map +1 -0
- package/dist/security/sanitize-core.d.ts.map +1 -1
- package/dist/security.es.mjs +2 -2
- package/dist/store.es.mjs +2 -2
- package/dist/type-guards-BdKlYYlS.js +32 -0
- package/dist/type-guards-BdKlYYlS.js.map +1 -0
- package/dist/untrack-DNnnqdlR.js +6 -0
- package/dist/{untrack-BuEQKH7_.js.map → untrack-DNnnqdlR.js.map} +1 -1
- package/dist/view/evaluate.d.ts.map +1 -1
- package/dist/view.es.mjs +157 -151
- package/dist/view.es.mjs.map +1 -1
- package/dist/{watch-CXyaBC_9.js → watch-DXXv3iAI.js} +3 -3
- package/dist/{watch-CXyaBC_9.js.map → watch-DXXv3iAI.js.map} +1 -1
- package/package.json +132 -132
- package/src/core/collection.ts +628 -588
- package/src/core/element.ts +774 -746
- package/src/core/index.ts +48 -47
- package/src/core/utils/function.ts +151 -110
- package/src/motion/animate.ts +113 -113
- package/src/motion/flip.ts +176 -176
- package/src/motion/scroll.ts +57 -57
- package/src/motion/spring.ts +150 -150
- package/src/motion/timeline.ts +246 -246
- package/src/motion/transition.ts +51 -51
- package/src/platform/storage.ts +215 -208
- package/src/reactive/core.ts +114 -93
- package/src/reactive/effect.ts +54 -43
- package/src/reactive/internals.ts +122 -105
- package/src/security/sanitize-core.ts +364 -343
- package/src/view/evaluate.ts +290 -274
- package/dist/core-COenAZjD.js.map +0 -1
- package/dist/sanitize-1FBEPAFH.js.map +0 -1
- package/dist/type-guards-DRma3-Kc.js +0 -16
- package/dist/type-guards-DRma3-Kc.js.map +0 -1
- package/dist/untrack-BuEQKH7_.js +0 -6
package/src/core/index.ts
CHANGED
|
@@ -1,47 +1,48 @@
|
|
|
1
|
-
export { BQueryCollection } from './collection';
|
|
2
|
-
export { BQueryElement } from './element';
|
|
3
|
-
export { $, $$ } from './selector';
|
|
4
|
-
// Re-export the utils namespace for backward compatibility
|
|
5
|
-
export { utils } from './utils';
|
|
6
|
-
// Export individual utilities (except internal helpers)
|
|
7
|
-
export {
|
|
8
|
-
chunk,
|
|
9
|
-
compact,
|
|
10
|
-
ensureArray,
|
|
11
|
-
flatten,
|
|
12
|
-
unique,
|
|
13
|
-
debounce,
|
|
14
|
-
noop,
|
|
15
|
-
once,
|
|
16
|
-
throttle,
|
|
17
|
-
isEmpty,
|
|
18
|
-
parseJson,
|
|
19
|
-
sleep,
|
|
20
|
-
uid,
|
|
21
|
-
clamp,
|
|
22
|
-
inRange,
|
|
23
|
-
randomInt,
|
|
24
|
-
toNumber,
|
|
25
|
-
clone,
|
|
26
|
-
hasOwn,
|
|
27
|
-
isPlainObject,
|
|
28
|
-
merge,
|
|
29
|
-
omit,
|
|
30
|
-
pick,
|
|
31
|
-
capitalize,
|
|
32
|
-
escapeRegExp,
|
|
33
|
-
slugify,
|
|
34
|
-
toCamelCase,
|
|
35
|
-
toKebabCase,
|
|
36
|
-
truncate,
|
|
37
|
-
isArray,
|
|
38
|
-
isBoolean,
|
|
39
|
-
isCollection,
|
|
40
|
-
isDate,
|
|
41
|
-
isElement,
|
|
42
|
-
isFunction,
|
|
43
|
-
isNumber,
|
|
44
|
-
isObject,
|
|
45
|
-
isPromise,
|
|
46
|
-
isString,
|
|
47
|
-
} from './utils';
|
|
1
|
+
export { BQueryCollection } from './collection';
|
|
2
|
+
export { BQueryElement } from './element';
|
|
3
|
+
export { $, $$ } from './selector';
|
|
4
|
+
// Re-export the utils namespace for backward compatibility
|
|
5
|
+
export { utils } from './utils';
|
|
6
|
+
// Export individual utilities (except internal helpers)
|
|
7
|
+
export {
|
|
8
|
+
chunk,
|
|
9
|
+
compact,
|
|
10
|
+
ensureArray,
|
|
11
|
+
flatten,
|
|
12
|
+
unique,
|
|
13
|
+
debounce,
|
|
14
|
+
noop,
|
|
15
|
+
once,
|
|
16
|
+
throttle,
|
|
17
|
+
isEmpty,
|
|
18
|
+
parseJson,
|
|
19
|
+
sleep,
|
|
20
|
+
uid,
|
|
21
|
+
clamp,
|
|
22
|
+
inRange,
|
|
23
|
+
randomInt,
|
|
24
|
+
toNumber,
|
|
25
|
+
clone,
|
|
26
|
+
hasOwn,
|
|
27
|
+
isPlainObject,
|
|
28
|
+
merge,
|
|
29
|
+
omit,
|
|
30
|
+
pick,
|
|
31
|
+
capitalize,
|
|
32
|
+
escapeRegExp,
|
|
33
|
+
slugify,
|
|
34
|
+
toCamelCase,
|
|
35
|
+
toKebabCase,
|
|
36
|
+
truncate,
|
|
37
|
+
isArray,
|
|
38
|
+
isBoolean,
|
|
39
|
+
isCollection,
|
|
40
|
+
isDate,
|
|
41
|
+
isElement,
|
|
42
|
+
isFunction,
|
|
43
|
+
isNumber,
|
|
44
|
+
isObject,
|
|
45
|
+
isPromise,
|
|
46
|
+
isString,
|
|
47
|
+
} from './utils';
|
|
48
|
+
export type { DebouncedFn, ThrottledFn } from './utils';
|
|
@@ -1,110 +1,151 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Function-focused utility helpers.
|
|
3
|
-
*
|
|
4
|
-
* @module bquery/core/utils/function
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* @template TArgs - The argument types of the function
|
|
75
|
-
* @
|
|
76
|
-
* @param
|
|
77
|
-
* @returns A function
|
|
78
|
-
*
|
|
79
|
-
* @example
|
|
80
|
-
* ```ts
|
|
81
|
-
* const
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Function-focused utility helpers.
|
|
3
|
+
*
|
|
4
|
+
* @module bquery/core/utils/function
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** A debounced function with a cancel method to clear the pending timeout. */
|
|
8
|
+
export interface DebouncedFn<TArgs extends unknown[]> {
|
|
9
|
+
(...args: TArgs): void;
|
|
10
|
+
/** Cancels the pending debounced invocation. */
|
|
11
|
+
cancel(): void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** A throttled function with a cancel method to reset the throttle timer. */
|
|
15
|
+
export interface ThrottledFn<TArgs extends unknown[]> {
|
|
16
|
+
(...args: TArgs): void;
|
|
17
|
+
/** Resets the throttle timer, allowing the next call to execute immediately. */
|
|
18
|
+
cancel(): void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a debounced function that delays execution until after
|
|
23
|
+
* the specified delay has elapsed since the last call.
|
|
24
|
+
*
|
|
25
|
+
* @template TArgs - The argument types of the function
|
|
26
|
+
* @param fn - The function to debounce
|
|
27
|
+
* @param delayMs - Delay in milliseconds
|
|
28
|
+
* @returns A debounced version of the function with a `cancel()` method
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const search = debounce((query: string) => {
|
|
33
|
+
* console.log('Searching:', query);
|
|
34
|
+
* }, 300);
|
|
35
|
+
*
|
|
36
|
+
* search('h');
|
|
37
|
+
* search('he');
|
|
38
|
+
* search('hello'); // Only this call executes after 300ms
|
|
39
|
+
*
|
|
40
|
+
* search('cancel me');
|
|
41
|
+
* search.cancel(); // Cancels the pending invocation
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export function debounce<TArgs extends unknown[]>(
|
|
45
|
+
fn: (...args: TArgs) => void,
|
|
46
|
+
delayMs: number
|
|
47
|
+
): DebouncedFn<TArgs> {
|
|
48
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
49
|
+
const debounced: DebouncedFn<TArgs> = Object.assign(
|
|
50
|
+
(...args: TArgs) => {
|
|
51
|
+
if (timeoutId !== undefined) {
|
|
52
|
+
clearTimeout(timeoutId);
|
|
53
|
+
}
|
|
54
|
+
timeoutId = setTimeout(() => {
|
|
55
|
+
timeoutId = undefined;
|
|
56
|
+
fn(...args);
|
|
57
|
+
}, delayMs);
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
cancel: () => {
|
|
61
|
+
if (timeoutId !== undefined) {
|
|
62
|
+
clearTimeout(timeoutId);
|
|
63
|
+
timeoutId = undefined;
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
return debounced;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Creates a throttled function that runs at most once per interval.
|
|
73
|
+
*
|
|
74
|
+
* @template TArgs - The argument types of the function
|
|
75
|
+
* @param fn - The function to throttle
|
|
76
|
+
* @param intervalMs - Minimum interval between calls in milliseconds
|
|
77
|
+
* @returns A throttled version of the function with a `cancel()` method
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* const handleScroll = throttle(() => {
|
|
82
|
+
* console.log('Scroll position:', window.scrollY);
|
|
83
|
+
* }, 100);
|
|
84
|
+
*
|
|
85
|
+
* window.addEventListener('scroll', handleScroll);
|
|
86
|
+
*
|
|
87
|
+
* handleScroll.cancel(); // Resets throttle, next call executes immediately
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export function throttle<TArgs extends unknown[]>(
|
|
91
|
+
fn: (...args: TArgs) => void,
|
|
92
|
+
intervalMs: number
|
|
93
|
+
): ThrottledFn<TArgs> {
|
|
94
|
+
let lastRun = 0;
|
|
95
|
+
const throttled: ThrottledFn<TArgs> = Object.assign(
|
|
96
|
+
(...args: TArgs) => {
|
|
97
|
+
const now = Date.now();
|
|
98
|
+
if (now - lastRun >= intervalMs) {
|
|
99
|
+
lastRun = now;
|
|
100
|
+
fn(...args);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
cancel: () => {
|
|
105
|
+
lastRun = 0;
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
return throttled;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Ensures a function only runs once. Subsequent calls return the first result.
|
|
114
|
+
*
|
|
115
|
+
* @template TArgs - The argument types of the function
|
|
116
|
+
* @template TResult - The return type of the function
|
|
117
|
+
* @param fn - The function to wrap
|
|
118
|
+
* @returns A function that only runs once
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```ts
|
|
122
|
+
* const init = once(() => ({ ready: true }));
|
|
123
|
+
* init();
|
|
124
|
+
* init(); // only runs once
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
export function once<TArgs extends unknown[], TResult>(
|
|
128
|
+
fn: (...args: TArgs) => TResult
|
|
129
|
+
): (...args: TArgs) => TResult {
|
|
130
|
+
let hasRun = false;
|
|
131
|
+
let result!: TResult;
|
|
132
|
+
return (...args: TArgs) => {
|
|
133
|
+
if (!hasRun) {
|
|
134
|
+
result = fn(...args);
|
|
135
|
+
hasRun = true;
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* A no-operation function.
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```ts
|
|
146
|
+
* noop();
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
export function noop(): void {
|
|
150
|
+
// Intentionally empty
|
|
151
|
+
}
|
package/src/motion/animate.ts
CHANGED
|
@@ -1,113 +1,113 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Web Animations helpers.
|
|
3
|
-
*
|
|
4
|
-
* @module bquery/motion
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { prefersReducedMotion } from './reduced-motion';
|
|
8
|
-
import type { AnimateOptions } from './types';
|
|
9
|
-
|
|
10
|
-
/** @internal */
|
|
11
|
-
const isStyleValue = (value: unknown): value is string | number =>
|
|
12
|
-
typeof value === 'string' || typeof value === 'number';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Convert camelCase property names to kebab-case for CSS.
|
|
16
|
-
* @internal
|
|
17
|
-
*/
|
|
18
|
-
const toKebabCase = (str: string): string => {
|
|
19
|
-
return str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
/** @internal */
|
|
23
|
-
export const applyFinalKeyframeStyles = (
|
|
24
|
-
element: Element,
|
|
25
|
-
keyframes: Keyframe[] | PropertyIndexedKeyframes
|
|
26
|
-
): void => {
|
|
27
|
-
const htmlElement = element as HTMLElement;
|
|
28
|
-
const style = htmlElement.style;
|
|
29
|
-
|
|
30
|
-
if (Array.isArray(keyframes)) {
|
|
31
|
-
const last = keyframes[keyframes.length - 1];
|
|
32
|
-
if (!last) return;
|
|
33
|
-
for (const [prop, value] of Object.entries(last)) {
|
|
34
|
-
if (prop === 'offset' || prop === 'easing' || prop === 'composite') continue;
|
|
35
|
-
if (isStyleValue(value)) {
|
|
36
|
-
// Convert camelCase to kebab-case for CSS properties
|
|
37
|
-
const cssProp = prop.startsWith('--') ? prop : toKebabCase(prop);
|
|
38
|
-
style.setProperty(cssProp, String(value));
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
for (const [prop, value] of Object.entries(keyframes)) {
|
|
45
|
-
if (prop === 'offset' || prop === 'easing' || prop === 'composite') continue;
|
|
46
|
-
const finalValue = Array.isArray(value) ? value[value.length - 1] : value;
|
|
47
|
-
if (isStyleValue(finalValue)) {
|
|
48
|
-
// Convert camelCase to kebab-case for CSS properties
|
|
49
|
-
const cssProp = prop.startsWith('--') ? prop : toKebabCase(prop);
|
|
50
|
-
style.setProperty(cssProp, String(finalValue));
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Animate an element using the Web Animations API with reduced-motion fallback.
|
|
57
|
-
*
|
|
58
|
-
* @param element - Element to animate
|
|
59
|
-
* @param config - Animation configuration
|
|
60
|
-
* @returns Promise that resolves when animation completes
|
|
61
|
-
*
|
|
62
|
-
* @example
|
|
63
|
-
* ```ts
|
|
64
|
-
* await animate(element, {
|
|
65
|
-
* keyframes: [{ opacity: 0 }, { opacity: 1 }],
|
|
66
|
-
* options: { duration: 200, easing: 'ease-out' },
|
|
67
|
-
* });
|
|
68
|
-
* ```
|
|
69
|
-
*/
|
|
70
|
-
export const animate = (element: Element, config: AnimateOptions): Promise<void> => {
|
|
71
|
-
const { keyframes, options, commitStyles = true, respectReducedMotion = true, onFinish } = config;
|
|
72
|
-
|
|
73
|
-
if (respectReducedMotion && prefersReducedMotion()) {
|
|
74
|
-
if (commitStyles) {
|
|
75
|
-
applyFinalKeyframeStyles(element, keyframes);
|
|
76
|
-
}
|
|
77
|
-
onFinish?.();
|
|
78
|
-
return Promise.resolve();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const htmlElement = element as HTMLElement;
|
|
82
|
-
if (typeof htmlElement.animate !== 'function') {
|
|
83
|
-
if (commitStyles) {
|
|
84
|
-
applyFinalKeyframeStyles(element, keyframes);
|
|
85
|
-
}
|
|
86
|
-
onFinish?.();
|
|
87
|
-
return Promise.resolve();
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return new Promise((resolve) => {
|
|
91
|
-
const animation = htmlElement.animate(keyframes, options);
|
|
92
|
-
let finalized = false;
|
|
93
|
-
const finalize = () => {
|
|
94
|
-
if (finalized) return;
|
|
95
|
-
finalized = true;
|
|
96
|
-
if (commitStyles) {
|
|
97
|
-
if (typeof animation.commitStyles === 'function') {
|
|
98
|
-
animation.commitStyles();
|
|
99
|
-
} else {
|
|
100
|
-
applyFinalKeyframeStyles(element, keyframes);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
animation.cancel();
|
|
104
|
-
onFinish?.();
|
|
105
|
-
resolve();
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
animation.onfinish = finalize;
|
|
109
|
-
if (animation.finished) {
|
|
110
|
-
animation.finished.then(finalize).catch(finalize);
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Web Animations helpers.
|
|
3
|
+
*
|
|
4
|
+
* @module bquery/motion
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { prefersReducedMotion } from './reduced-motion';
|
|
8
|
+
import type { AnimateOptions } from './types';
|
|
9
|
+
|
|
10
|
+
/** @internal */
|
|
11
|
+
const isStyleValue = (value: unknown): value is string | number =>
|
|
12
|
+
typeof value === 'string' || typeof value === 'number';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Convert camelCase property names to kebab-case for CSS.
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
const toKebabCase = (str: string): string => {
|
|
19
|
+
return str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** @internal */
|
|
23
|
+
export const applyFinalKeyframeStyles = (
|
|
24
|
+
element: Element,
|
|
25
|
+
keyframes: Keyframe[] | PropertyIndexedKeyframes
|
|
26
|
+
): void => {
|
|
27
|
+
const htmlElement = element as HTMLElement;
|
|
28
|
+
const style = htmlElement.style;
|
|
29
|
+
|
|
30
|
+
if (Array.isArray(keyframes)) {
|
|
31
|
+
const last = keyframes[keyframes.length - 1];
|
|
32
|
+
if (!last) return;
|
|
33
|
+
for (const [prop, value] of Object.entries(last)) {
|
|
34
|
+
if (prop === 'offset' || prop === 'easing' || prop === 'composite') continue;
|
|
35
|
+
if (isStyleValue(value)) {
|
|
36
|
+
// Convert camelCase to kebab-case for CSS properties
|
|
37
|
+
const cssProp = prop.startsWith('--') ? prop : toKebabCase(prop);
|
|
38
|
+
style.setProperty(cssProp, String(value));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (const [prop, value] of Object.entries(keyframes)) {
|
|
45
|
+
if (prop === 'offset' || prop === 'easing' || prop === 'composite') continue;
|
|
46
|
+
const finalValue = Array.isArray(value) ? value[value.length - 1] : value;
|
|
47
|
+
if (isStyleValue(finalValue)) {
|
|
48
|
+
// Convert camelCase to kebab-case for CSS properties
|
|
49
|
+
const cssProp = prop.startsWith('--') ? prop : toKebabCase(prop);
|
|
50
|
+
style.setProperty(cssProp, String(finalValue));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Animate an element using the Web Animations API with reduced-motion fallback.
|
|
57
|
+
*
|
|
58
|
+
* @param element - Element to animate
|
|
59
|
+
* @param config - Animation configuration
|
|
60
|
+
* @returns Promise that resolves when animation completes
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* await animate(element, {
|
|
65
|
+
* keyframes: [{ opacity: 0 }, { opacity: 1 }],
|
|
66
|
+
* options: { duration: 200, easing: 'ease-out' },
|
|
67
|
+
* });
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export const animate = (element: Element, config: AnimateOptions): Promise<void> => {
|
|
71
|
+
const { keyframes, options, commitStyles = true, respectReducedMotion = true, onFinish } = config;
|
|
72
|
+
|
|
73
|
+
if (respectReducedMotion && prefersReducedMotion()) {
|
|
74
|
+
if (commitStyles) {
|
|
75
|
+
applyFinalKeyframeStyles(element, keyframes);
|
|
76
|
+
}
|
|
77
|
+
onFinish?.();
|
|
78
|
+
return Promise.resolve();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const htmlElement = element as HTMLElement;
|
|
82
|
+
if (typeof htmlElement.animate !== 'function') {
|
|
83
|
+
if (commitStyles) {
|
|
84
|
+
applyFinalKeyframeStyles(element, keyframes);
|
|
85
|
+
}
|
|
86
|
+
onFinish?.();
|
|
87
|
+
return Promise.resolve();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return new Promise((resolve) => {
|
|
91
|
+
const animation = htmlElement.animate(keyframes, options);
|
|
92
|
+
let finalized = false;
|
|
93
|
+
const finalize = () => {
|
|
94
|
+
if (finalized) return;
|
|
95
|
+
finalized = true;
|
|
96
|
+
if (commitStyles) {
|
|
97
|
+
if (typeof animation.commitStyles === 'function') {
|
|
98
|
+
animation.commitStyles();
|
|
99
|
+
} else {
|
|
100
|
+
applyFinalKeyframeStyles(element, keyframes);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
animation.cancel();
|
|
104
|
+
onFinish?.();
|
|
105
|
+
resolve();
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
animation.onfinish = finalize;
|
|
109
|
+
if (animation.finished) {
|
|
110
|
+
animation.finished.then(finalize).catch(finalize);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
};
|