@bquery/bquery 1.2.0 → 1.3.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 +501 -427
- package/dist/batch-4LAvfLE7.js +13 -0
- package/dist/batch-4LAvfLE7.js.map +1 -0
- package/dist/component/component.d.ts +69 -0
- package/dist/component/component.d.ts.map +1 -0
- package/dist/component/html.d.ts +35 -0
- package/dist/component/html.d.ts.map +1 -0
- package/dist/component/index.d.ts +3 -126
- package/dist/component/index.d.ts.map +1 -1
- package/dist/component/props.d.ts +18 -0
- package/dist/component/props.d.ts.map +1 -0
- package/dist/component/types.d.ts +77 -0
- package/dist/component/types.d.ts.map +1 -0
- package/dist/component.es.mjs +90 -59
- package/dist/component.es.mjs.map +1 -1
- package/dist/core/collection.d.ts +36 -0
- package/dist/core/collection.d.ts.map +1 -1
- package/dist/core/dom.d.ts +6 -0
- package/dist/core/dom.d.ts.map +1 -0
- package/dist/core/element.d.ts +8 -0
- 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/array.d.ts +74 -0
- package/dist/core/utils/array.d.ts.map +1 -0
- package/dist/core/utils/function.d.ts +70 -0
- package/dist/core/utils/function.d.ts.map +1 -0
- package/dist/core/utils/index.d.ts +70 -0
- package/dist/core/utils/index.d.ts.map +1 -0
- package/dist/core/utils/misc.d.ts +63 -0
- package/dist/core/utils/misc.d.ts.map +1 -0
- package/dist/core/utils/number.d.ts +65 -0
- package/dist/core/utils/number.d.ts.map +1 -0
- package/dist/core/utils/object.d.ts +133 -0
- package/dist/core/utils/object.d.ts.map +1 -0
- package/dist/core/utils/string.d.ts +80 -0
- package/dist/core/utils/string.d.ts.map +1 -0
- package/dist/core/utils/type-guards.d.ts +79 -0
- package/dist/core/utils/type-guards.d.ts.map +1 -0
- package/dist/core-COenAZjD.js +145 -0
- package/dist/core-COenAZjD.js.map +1 -0
- package/dist/core.es.mjs +411 -448
- package/dist/core.es.mjs.map +1 -1
- package/dist/full.d.ts +2 -2
- package/dist/full.d.ts.map +1 -1
- package/dist/full.es.mjs +87 -64
- package/dist/full.es.mjs.map +1 -1
- 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 +138 -68
- package/dist/index.es.mjs.map +1 -1
- package/dist/motion/animate.d.ts +25 -0
- package/dist/motion/animate.d.ts.map +1 -0
- package/dist/motion/easing.d.ts +30 -0
- package/dist/motion/easing.d.ts.map +1 -0
- package/dist/motion/flip.d.ts +55 -0
- package/dist/motion/flip.d.ts.map +1 -0
- package/dist/motion/index.d.ts +11 -138
- package/dist/motion/index.d.ts.map +1 -1
- package/dist/motion/keyframes.d.ts +21 -0
- package/dist/motion/keyframes.d.ts.map +1 -0
- package/dist/motion/reduced-motion.d.ts +12 -0
- package/dist/motion/reduced-motion.d.ts.map +1 -0
- package/dist/motion/scroll.d.ts +15 -0
- package/dist/motion/scroll.d.ts.map +1 -0
- package/dist/motion/spring.d.ts +42 -0
- package/dist/motion/spring.d.ts.map +1 -0
- package/dist/motion/stagger.d.ts +22 -0
- package/dist/motion/stagger.d.ts.map +1 -0
- package/dist/motion/timeline.d.ts +21 -0
- package/dist/motion/timeline.d.ts.map +1 -0
- package/dist/motion/transition.d.ts +22 -0
- package/dist/motion/transition.d.ts.map +1 -0
- package/dist/motion/types.d.ts +182 -0
- package/dist/motion/types.d.ts.map +1 -0
- package/dist/motion.es.mjs +320 -61
- package/dist/motion.es.mjs.map +1 -1
- package/dist/persisted-Dz_ryNuC.js +278 -0
- package/dist/persisted-Dz_ryNuC.js.map +1 -0
- package/dist/reactive/batch.d.ts +13 -0
- package/dist/reactive/batch.d.ts.map +1 -0
- package/dist/reactive/computed.d.ts +50 -0
- package/dist/reactive/computed.d.ts.map +1 -0
- package/dist/reactive/core.d.ts +60 -0
- package/dist/reactive/core.d.ts.map +1 -0
- package/dist/reactive/effect.d.ts +15 -0
- package/dist/reactive/effect.d.ts.map +1 -0
- package/dist/reactive/index.d.ts +2 -2
- package/dist/reactive/index.d.ts.map +1 -1
- package/dist/reactive/internals.d.ts +36 -0
- package/dist/reactive/internals.d.ts.map +1 -0
- package/dist/reactive/linked.d.ts +36 -0
- package/dist/reactive/linked.d.ts.map +1 -0
- package/dist/reactive/persisted.d.ts +14 -0
- package/dist/reactive/persisted.d.ts.map +1 -0
- package/dist/reactive/readonly.d.ts +26 -0
- package/dist/reactive/readonly.d.ts.map +1 -0
- package/dist/reactive/signal.d.ts +13 -312
- package/dist/reactive/signal.d.ts.map +1 -1
- package/dist/reactive/type-guards.d.ts +20 -0
- package/dist/reactive/type-guards.d.ts.map +1 -0
- package/dist/reactive/untrack.d.ts +29 -0
- package/dist/reactive/untrack.d.ts.map +1 -0
- package/dist/reactive/watch.d.ts +42 -0
- package/dist/reactive/watch.d.ts.map +1 -0
- package/dist/reactive.es.mjs +30 -163
- package/dist/reactive.es.mjs.map +1 -1
- package/dist/router/index.d.ts +6 -252
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/links.d.ts +44 -0
- package/dist/router/links.d.ts.map +1 -0
- package/dist/router/match.d.ts +20 -0
- package/dist/router/match.d.ts.map +1 -0
- package/dist/router/navigation.d.ts +45 -0
- package/dist/router/navigation.d.ts.map +1 -0
- package/dist/router/query.d.ts +16 -0
- package/dist/router/query.d.ts.map +1 -0
- package/dist/router/router.d.ts +34 -0
- package/dist/router/router.d.ts.map +1 -0
- package/dist/router/state.d.ts +27 -0
- package/dist/router/state.d.ts.map +1 -0
- package/dist/router/types.d.ts +88 -0
- package/dist/router/types.d.ts.map +1 -0
- package/dist/router/utils.d.ts +65 -0
- package/dist/router/utils.d.ts.map +1 -0
- package/dist/router.es.mjs +168 -132
- package/dist/router.es.mjs.map +1 -1
- package/dist/sanitize-1FBEPAFH.js +272 -0
- package/dist/sanitize-1FBEPAFH.js.map +1 -0
- package/dist/security/constants.d.ts +42 -0
- package/dist/security/constants.d.ts.map +1 -0
- package/dist/security/csp.d.ts +24 -0
- package/dist/security/csp.d.ts.map +1 -0
- package/dist/security/index.d.ts +4 -2
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/sanitize-core.d.ts +13 -0
- package/dist/security/sanitize-core.d.ts.map +1 -0
- package/dist/security/sanitize.d.ts +5 -57
- package/dist/security/sanitize.d.ts.map +1 -1
- package/dist/security/trusted-types.d.ts +25 -0
- package/dist/security/trusted-types.d.ts.map +1 -0
- package/dist/security/types.d.ts +36 -0
- package/dist/security/types.d.ts.map +1 -0
- package/dist/security.es.mjs +50 -277
- package/dist/security.es.mjs.map +1 -1
- package/dist/store/create-store.d.ts +15 -0
- package/dist/store/create-store.d.ts.map +1 -0
- package/dist/store/define-store.d.ts +28 -0
- package/dist/store/define-store.d.ts.map +1 -0
- package/dist/store/devtools.d.ts +22 -0
- package/dist/store/devtools.d.ts.map +1 -0
- package/dist/store/index.d.ts +10 -286
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/mapping.d.ts +28 -0
- package/dist/store/mapping.d.ts.map +1 -0
- package/dist/store/persisted.d.ts +13 -0
- package/dist/store/persisted.d.ts.map +1 -0
- package/dist/store/plugins.d.ts +13 -0
- package/dist/store/plugins.d.ts.map +1 -0
- package/dist/store/registry.d.ts +28 -0
- package/dist/store/registry.d.ts.map +1 -0
- package/dist/store/types.d.ts +71 -0
- package/dist/store/types.d.ts.map +1 -0
- package/dist/store/utils.d.ts +28 -0
- package/dist/store/utils.d.ts.map +1 -0
- package/dist/store/watch.d.ts +23 -0
- package/dist/store/watch.d.ts.map +1 -0
- package/dist/store.es.mjs +22 -224
- package/dist/store.es.mjs.map +1 -1
- package/dist/type-guards-DRma3-Kc.js +16 -0
- package/dist/type-guards-DRma3-Kc.js.map +1 -0
- package/dist/untrack-BuEQKH7_.js +6 -0
- package/dist/untrack-BuEQKH7_.js.map +1 -0
- package/dist/view/directives/bind.d.ts +7 -0
- package/dist/view/directives/bind.d.ts.map +1 -0
- package/dist/view/directives/class.d.ts +8 -0
- package/dist/view/directives/class.d.ts.map +1 -0
- package/dist/view/directives/for.d.ts +23 -0
- package/dist/view/directives/for.d.ts.map +1 -0
- package/dist/view/directives/html.d.ts +7 -0
- package/dist/view/directives/html.d.ts.map +1 -0
- package/dist/view/directives/if.d.ts +7 -0
- package/dist/view/directives/if.d.ts.map +1 -0
- package/dist/view/directives/index.d.ts +12 -0
- package/dist/view/directives/index.d.ts.map +1 -0
- package/dist/view/directives/model.d.ts +7 -0
- package/dist/view/directives/model.d.ts.map +1 -0
- package/dist/view/directives/on.d.ts +7 -0
- package/dist/view/directives/on.d.ts.map +1 -0
- package/dist/view/directives/ref.d.ts +7 -0
- package/dist/view/directives/ref.d.ts.map +1 -0
- package/dist/view/directives/show.d.ts +7 -0
- package/dist/view/directives/show.d.ts.map +1 -0
- package/dist/view/directives/style.d.ts +7 -0
- package/dist/view/directives/style.d.ts.map +1 -0
- package/dist/view/directives/text.d.ts +7 -0
- package/dist/view/directives/text.d.ts.map +1 -0
- package/dist/view/evaluate.d.ts +43 -0
- package/dist/view/evaluate.d.ts.map +1 -0
- package/dist/view/index.d.ts +3 -93
- package/dist/view/index.d.ts.map +1 -1
- package/dist/view/mount.d.ts +69 -0
- package/dist/view/mount.d.ts.map +1 -0
- package/dist/view/process.d.ts +26 -0
- package/dist/view/process.d.ts.map +1 -0
- package/dist/view/types.d.ts +36 -0
- package/dist/view/types.d.ts.map +1 -0
- package/dist/view.es.mjs +368 -267
- package/dist/view.es.mjs.map +1 -1
- package/dist/watch-CXyaBC_9.js +58 -0
- package/dist/watch-CXyaBC_9.js.map +1 -0
- package/package.json +132 -132
- package/src/component/component.ts +289 -0
- package/src/component/html.ts +53 -0
- package/src/component/index.ts +40 -414
- package/src/component/props.ts +116 -0
- package/src/component/types.ts +85 -0
- package/src/core/collection.ts +588 -454
- package/src/core/dom.ts +38 -0
- package/src/core/element.ts +746 -740
- package/src/core/index.ts +43 -0
- package/src/core/utils/array.ts +102 -0
- package/src/core/utils/function.ts +110 -0
- package/src/core/utils/index.ts +83 -0
- package/src/core/utils/misc.ts +82 -0
- package/src/core/utils/number.ts +78 -0
- package/src/core/utils/object.ts +206 -0
- package/src/core/utils/string.ts +112 -0
- package/src/core/utils/type-guards.ts +112 -0
- package/src/full.ts +187 -150
- package/src/index.ts +36 -36
- package/src/motion/animate.ts +113 -0
- package/src/motion/easing.ts +40 -0
- package/src/motion/flip.ts +176 -0
- package/src/motion/index.ts +41 -358
- package/src/motion/keyframes.ts +46 -0
- package/src/motion/reduced-motion.ts +17 -0
- package/src/motion/scroll.ts +57 -0
- package/src/motion/spring.ts +150 -0
- package/src/motion/stagger.ts +43 -0
- package/src/motion/timeline.ts +246 -0
- package/src/motion/transition.ts +51 -0
- package/src/motion/types.ts +198 -0
- package/src/reactive/batch.ts +22 -0
- package/src/reactive/computed.ts +92 -0
- package/src/reactive/core.ts +93 -0
- package/src/reactive/effect.ts +43 -0
- package/src/reactive/index.ts +23 -22
- package/src/reactive/internals.ts +105 -0
- package/src/reactive/linked.ts +56 -0
- package/src/reactive/persisted.ts +74 -0
- package/src/reactive/readonly.ts +35 -0
- package/src/reactive/signal.ts +20 -520
- package/src/reactive/type-guards.ts +22 -0
- package/src/reactive/untrack.ts +31 -0
- package/src/reactive/watch.ts +73 -0
- package/src/router/index.ts +41 -718
- package/src/router/links.ts +130 -0
- package/src/router/match.ts +106 -0
- package/src/router/navigation.ts +71 -0
- package/src/router/query.ts +35 -0
- package/src/router/router.ts +211 -0
- package/src/router/state.ts +46 -0
- package/src/router/types.ts +93 -0
- package/src/router/utils.ts +116 -0
- package/src/security/constants.ts +209 -0
- package/src/security/csp.ts +77 -0
- package/src/security/index.ts +4 -12
- package/src/security/sanitize-core.ts +343 -0
- package/src/security/sanitize.ts +66 -625
- package/src/security/trusted-types.ts +69 -0
- package/src/security/types.ts +40 -0
- package/src/store/create-store.ts +329 -0
- package/src/store/define-store.ts +48 -0
- package/src/store/devtools.ts +45 -0
- package/src/store/index.ts +22 -848
- package/src/store/mapping.ts +73 -0
- package/src/store/persisted.ts +61 -0
- package/src/store/plugins.ts +32 -0
- package/src/store/registry.ts +51 -0
- package/src/store/types.ts +94 -0
- package/src/store/utils.ts +141 -0
- package/src/store/watch.ts +52 -0
- package/src/view/directives/bind.ts +23 -0
- package/src/view/directives/class.ts +70 -0
- package/src/view/directives/for.ts +275 -0
- package/src/view/directives/html.ts +19 -0
- package/src/view/directives/if.ts +30 -0
- package/src/view/directives/index.ts +11 -0
- package/src/view/directives/model.ts +56 -0
- package/src/view/directives/on.ts +41 -0
- package/src/view/directives/ref.ts +41 -0
- package/src/view/directives/show.ts +26 -0
- package/src/view/directives/style.ts +47 -0
- package/src/view/directives/text.ts +15 -0
- package/src/view/evaluate.ts +274 -0
- package/src/view/index.ts +112 -1041
- package/src/view/mount.ts +200 -0
- package/src/view/process.ts +92 -0
- package/src/view/types.ts +44 -0
- package/dist/core/utils.d.ts +0 -313
- package/dist/core/utils.d.ts.map +0 -1
- package/src/core/utils.ts +0 -444
|
@@ -0,0 +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
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Easing helpers.
|
|
3
|
+
*
|
|
4
|
+
* @module bquery/motion
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { EasingFunction } from './types';
|
|
8
|
+
|
|
9
|
+
const clamp = (value: number) => Math.min(1, Math.max(0, value));
|
|
10
|
+
|
|
11
|
+
export const linear: EasingFunction = (t) => clamp(t);
|
|
12
|
+
export const easeInQuad: EasingFunction = (t) => clamp(t * t);
|
|
13
|
+
export const easeOutQuad: EasingFunction = (t) => clamp(1 - (1 - t) * (1 - t));
|
|
14
|
+
export const easeInOutQuad: EasingFunction = (t) =>
|
|
15
|
+
clamp(t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2);
|
|
16
|
+
export const easeInCubic: EasingFunction = (t) => clamp(t * t * t);
|
|
17
|
+
export const easeOutCubic: EasingFunction = (t) => clamp(1 - Math.pow(1 - t, 3));
|
|
18
|
+
export const easeInOutCubic: EasingFunction = (t) =>
|
|
19
|
+
clamp(t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2);
|
|
20
|
+
export const easeOutBack: EasingFunction = (t) => {
|
|
21
|
+
const c1 = 1.70158;
|
|
22
|
+
const c3 = c1 + 1;
|
|
23
|
+
return clamp(1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2));
|
|
24
|
+
};
|
|
25
|
+
export const easeOutExpo: EasingFunction = (t) => clamp(t === 1 ? 1 : 1 - Math.pow(2, -10 * t));
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Named easing presets.
|
|
29
|
+
*/
|
|
30
|
+
export const easingPresets = {
|
|
31
|
+
linear,
|
|
32
|
+
easeInQuad,
|
|
33
|
+
easeOutQuad,
|
|
34
|
+
easeInOutQuad,
|
|
35
|
+
easeInCubic,
|
|
36
|
+
easeOutCubic,
|
|
37
|
+
easeInOutCubic,
|
|
38
|
+
easeOutBack,
|
|
39
|
+
easeOutExpo,
|
|
40
|
+
};
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FLIP animation helpers.
|
|
3
|
+
*
|
|
4
|
+
* @module bquery/motion
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ElementBounds, FlipGroupOptions, FlipOptions } from './types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Capture the current bounds of an element for FLIP animation.
|
|
11
|
+
*
|
|
12
|
+
* @param element - The DOM element to measure
|
|
13
|
+
* @returns The element's current position and size
|
|
14
|
+
*/
|
|
15
|
+
export const capturePosition = (element: Element): ElementBounds => {
|
|
16
|
+
const rect = element.getBoundingClientRect();
|
|
17
|
+
return {
|
|
18
|
+
top: rect.top,
|
|
19
|
+
left: rect.left,
|
|
20
|
+
width: rect.width,
|
|
21
|
+
height: rect.height,
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Perform a FLIP (First, Last, Invert, Play) animation.
|
|
27
|
+
* Animates an element from its captured position to its current position.
|
|
28
|
+
*
|
|
29
|
+
* @param element - The element to animate
|
|
30
|
+
* @param firstBounds - The previously captured bounds
|
|
31
|
+
* @param options - Animation configuration
|
|
32
|
+
* @returns Promise that resolves when animation completes
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* const first = capturePosition(element);
|
|
37
|
+
* // ... DOM changes that move the element ...
|
|
38
|
+
* await flip(element, first, { duration: 300 });
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export const flip = (
|
|
42
|
+
element: Element,
|
|
43
|
+
firstBounds: ElementBounds,
|
|
44
|
+
options: FlipOptions = {}
|
|
45
|
+
): Promise<void> => {
|
|
46
|
+
const { duration = 300, easing = 'ease-out', onComplete } = options;
|
|
47
|
+
|
|
48
|
+
// Last: Get current position
|
|
49
|
+
const lastBounds = capturePosition(element);
|
|
50
|
+
|
|
51
|
+
// Skip animation if element has zero dimensions (avoid division by zero)
|
|
52
|
+
if (lastBounds.width === 0 || lastBounds.height === 0) {
|
|
53
|
+
onComplete?.();
|
|
54
|
+
return Promise.resolve();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Invert: Calculate the delta
|
|
58
|
+
const deltaX = firstBounds.left - lastBounds.left;
|
|
59
|
+
const deltaY = firstBounds.top - lastBounds.top;
|
|
60
|
+
const deltaW = firstBounds.width / lastBounds.width;
|
|
61
|
+
const deltaH = firstBounds.height / lastBounds.height;
|
|
62
|
+
|
|
63
|
+
// Skip animation if no change
|
|
64
|
+
if (deltaX === 0 && deltaY === 0 && deltaW === 1 && deltaH === 1) {
|
|
65
|
+
onComplete?.();
|
|
66
|
+
return Promise.resolve();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const htmlElement = element as HTMLElement;
|
|
70
|
+
|
|
71
|
+
// Feature check: fallback if Web Animations API is unavailable
|
|
72
|
+
if (typeof htmlElement.animate !== 'function') {
|
|
73
|
+
onComplete?.();
|
|
74
|
+
return Promise.resolve();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Apply inverted transform
|
|
78
|
+
htmlElement.style.transform = `translate(${deltaX}px, ${deltaY}px) scale(${deltaW}, ${deltaH})`;
|
|
79
|
+
htmlElement.style.transformOrigin = 'top left';
|
|
80
|
+
|
|
81
|
+
// Force reflow
|
|
82
|
+
void htmlElement.offsetHeight;
|
|
83
|
+
|
|
84
|
+
// Play: Animate back to current position
|
|
85
|
+
return new Promise((resolve) => {
|
|
86
|
+
const animation = htmlElement.animate(
|
|
87
|
+
[
|
|
88
|
+
{
|
|
89
|
+
transform: `translate(${deltaX}px, ${deltaY}px) scale(${deltaW}, ${deltaH})`,
|
|
90
|
+
},
|
|
91
|
+
{ transform: 'translate(0, 0) scale(1, 1)' },
|
|
92
|
+
],
|
|
93
|
+
{ duration, easing, fill: 'forwards' }
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
let finalized = false;
|
|
97
|
+
const finalize = () => {
|
|
98
|
+
if (finalized) return;
|
|
99
|
+
finalized = true;
|
|
100
|
+
htmlElement.style.transform = '';
|
|
101
|
+
htmlElement.style.transformOrigin = '';
|
|
102
|
+
onComplete?.();
|
|
103
|
+
resolve();
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
animation.onfinish = finalize;
|
|
107
|
+
// Handle cancel/rejection via the finished promise
|
|
108
|
+
if (animation.finished) {
|
|
109
|
+
animation.finished.then(finalize).catch(finalize);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* FLIP helper for animating a list of elements.
|
|
116
|
+
* Useful for reordering lists with smooth animations.
|
|
117
|
+
*
|
|
118
|
+
* @param elements - Array of elements to animate
|
|
119
|
+
* @param performUpdate - Function that performs the DOM update
|
|
120
|
+
* @param options - Animation configuration
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```ts
|
|
124
|
+
* await flipList(listItems, () => {
|
|
125
|
+
* container.appendChild(container.firstChild); // Move first to last
|
|
126
|
+
* });
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
export const flipList = async (
|
|
130
|
+
elements: Element[],
|
|
131
|
+
performUpdate: () => void,
|
|
132
|
+
options: FlipOptions = {}
|
|
133
|
+
): Promise<void> => {
|
|
134
|
+
await flipElements(elements, performUpdate, options);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* FLIP helper with optional stagger support.
|
|
139
|
+
*
|
|
140
|
+
* @param elements - Array of elements to animate
|
|
141
|
+
* @param performUpdate - Function that performs the DOM update
|
|
142
|
+
* @param options - Animation configuration
|
|
143
|
+
*/
|
|
144
|
+
export const flipElements = async (
|
|
145
|
+
elements: Element[],
|
|
146
|
+
performUpdate: () => void,
|
|
147
|
+
options: FlipGroupOptions = {}
|
|
148
|
+
): Promise<void> => {
|
|
149
|
+
const { stagger, ...flipOptions } = options;
|
|
150
|
+
|
|
151
|
+
// First: Capture all positions
|
|
152
|
+
const positions = new Map<Element, ElementBounds>();
|
|
153
|
+
for (const el of elements) {
|
|
154
|
+
positions.set(el, capturePosition(el));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Perform DOM update
|
|
158
|
+
performUpdate();
|
|
159
|
+
|
|
160
|
+
const total = elements.length;
|
|
161
|
+
|
|
162
|
+
// Animate each element
|
|
163
|
+
const animations = elements.map((el, index) => {
|
|
164
|
+
const first = positions.get(el);
|
|
165
|
+
if (!first) return Promise.resolve();
|
|
166
|
+
const delay = stagger ? stagger(index, total) : 0;
|
|
167
|
+
if (delay > 0) {
|
|
168
|
+
return new Promise((resolve) => setTimeout(resolve, delay)).then(() =>
|
|
169
|
+
flip(el, first, flipOptions)
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
return flip(el, first, flipOptions);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
await Promise.all(animations);
|
|
176
|
+
};
|