@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,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stagger helpers.
|
|
3
|
+
*
|
|
4
|
+
* @module bquery/motion
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { StaggerFunction, StaggerOptions } from './types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create a staggered delay function for list animations.
|
|
11
|
+
*
|
|
12
|
+
* @param step - Delay between items in milliseconds
|
|
13
|
+
* @param options - Stagger configuration
|
|
14
|
+
* @returns Function that returns delay for a given index
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const delay = stagger(50, { from: 'center' });
|
|
19
|
+
* delay(0, 3); // 50
|
|
20
|
+
* delay(1, 3); // 0
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export const stagger = (step: number, options: StaggerOptions = {}): StaggerFunction => {
|
|
24
|
+
const { start = 0, from = 'start', easing } = options;
|
|
25
|
+
|
|
26
|
+
return (index: number, total = 0): number => {
|
|
27
|
+
const origin =
|
|
28
|
+
typeof from === 'number'
|
|
29
|
+
? from
|
|
30
|
+
: from === 'center'
|
|
31
|
+
? (total - 1) / 2
|
|
32
|
+
: from === 'end'
|
|
33
|
+
? total - 1
|
|
34
|
+
: 0;
|
|
35
|
+
|
|
36
|
+
const distance = Math.abs(index - origin);
|
|
37
|
+
const maxDistance = total > 1 ? Math.max(origin, total - 1 - origin) : 1;
|
|
38
|
+
const normalized = maxDistance === 0 ? 0 : distance / maxDistance;
|
|
39
|
+
const eased = easing ? easing(normalized) * maxDistance : distance;
|
|
40
|
+
|
|
41
|
+
return start + eased * step;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timeline and sequence helpers.
|
|
3
|
+
*
|
|
4
|
+
* @module bquery/motion
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { animate, applyFinalKeyframeStyles } from './animate';
|
|
8
|
+
import { prefersReducedMotion } from './reduced-motion';
|
|
9
|
+
import type {
|
|
10
|
+
SequenceOptions,
|
|
11
|
+
SequenceStep,
|
|
12
|
+
TimelineConfig,
|
|
13
|
+
TimelineControls,
|
|
14
|
+
TimelineStep,
|
|
15
|
+
} from './types';
|
|
16
|
+
|
|
17
|
+
const resolveTimeValue = (value?: number | string): number => {
|
|
18
|
+
if (typeof value === 'number') return value;
|
|
19
|
+
if (typeof value === 'string') {
|
|
20
|
+
const trimmed = value.trim();
|
|
21
|
+
if (trimmed.endsWith('ms')) {
|
|
22
|
+
const parsed = Number.parseFloat(trimmed.slice(0, -2));
|
|
23
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
24
|
+
}
|
|
25
|
+
if (trimmed.endsWith('s')) {
|
|
26
|
+
const parsed = Number.parseFloat(trimmed.slice(0, -1));
|
|
27
|
+
return Number.isFinite(parsed) ? parsed * 1000 : 0;
|
|
28
|
+
}
|
|
29
|
+
const parsed = Number.parseFloat(trimmed);
|
|
30
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
31
|
+
}
|
|
32
|
+
return 0;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const resolveAt = (at: TimelineStep['at'], previousEnd: number): number => {
|
|
36
|
+
if (typeof at === 'number') return at;
|
|
37
|
+
if (typeof at === 'string') {
|
|
38
|
+
const match = /^([+-])=(\d+(?:\.\d+)?)$/.exec(at);
|
|
39
|
+
if (match) {
|
|
40
|
+
const delta = Number.parseFloat(match[2]);
|
|
41
|
+
if (!Number.isFinite(delta)) return previousEnd;
|
|
42
|
+
return match[1] === '+' ? previousEnd + delta : previousEnd - delta;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return previousEnd;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const normalizeDuration = (options?: KeyframeAnimationOptions): number => {
|
|
49
|
+
const baseDuration = resolveTimeValue(options?.duration as number | string | undefined);
|
|
50
|
+
const endDelay = resolveTimeValue(options?.endDelay as number | string | undefined);
|
|
51
|
+
const rawIterations = options?.iterations ?? 1;
|
|
52
|
+
|
|
53
|
+
// Handle infinite iterations - treat as a special case with a very large duration
|
|
54
|
+
// In practice, infinite iterations shouldn't be used in timelines as they never end
|
|
55
|
+
if (rawIterations === Infinity) {
|
|
56
|
+
// Return a large sentinel value - timeline calculations will be incorrect,
|
|
57
|
+
// but this at least prevents NaN/Infinity from breaking scheduling
|
|
58
|
+
return Number.MAX_SAFE_INTEGER;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Per Web Animations spec, iterations must be a non-negative number
|
|
62
|
+
// Treat negative as 0 (only endDelay duration)
|
|
63
|
+
const iterations = Math.max(0, rawIterations);
|
|
64
|
+
|
|
65
|
+
// Total duration = (baseDuration * iterations) + endDelay
|
|
66
|
+
// Note: endDelay is applied once at the end, after all iterations
|
|
67
|
+
return baseDuration * iterations + endDelay;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const scheduleSteps = (steps: TimelineStep[]) => {
|
|
71
|
+
let previousEnd = 0;
|
|
72
|
+
return steps.map((step) => {
|
|
73
|
+
const baseStart = resolveAt(step.at, previousEnd);
|
|
74
|
+
const stepDelay = resolveTimeValue(step.options?.delay as number | string | undefined);
|
|
75
|
+
const start = Math.max(0, baseStart + stepDelay);
|
|
76
|
+
const duration = normalizeDuration(step.options);
|
|
77
|
+
const end = start + duration;
|
|
78
|
+
previousEnd = Math.max(previousEnd, end);
|
|
79
|
+
return { step, start, end, duration };
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Run a list of animations sequentially.
|
|
85
|
+
*
|
|
86
|
+
* @param steps - Steps to run in order
|
|
87
|
+
* @param options - Sequence configuration
|
|
88
|
+
*/
|
|
89
|
+
export const sequence = async (
|
|
90
|
+
steps: SequenceStep[],
|
|
91
|
+
options: SequenceOptions = {}
|
|
92
|
+
): Promise<void> => {
|
|
93
|
+
const { stagger, onFinish } = options;
|
|
94
|
+
const total = steps.length;
|
|
95
|
+
|
|
96
|
+
for (let index = 0; index < steps.length; index += 1) {
|
|
97
|
+
const step = steps[index];
|
|
98
|
+
const delay = stagger ? stagger(index, total) : 0;
|
|
99
|
+
if (delay > 0) {
|
|
100
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
101
|
+
}
|
|
102
|
+
await animate(step.target, step);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
onFinish?.();
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Create a timeline controller for multiple animations.
|
|
110
|
+
*
|
|
111
|
+
* @param initialSteps - Steps for the timeline
|
|
112
|
+
* @param config - Timeline configuration
|
|
113
|
+
*/
|
|
114
|
+
export const timeline = (
|
|
115
|
+
initialSteps: TimelineStep[] = [],
|
|
116
|
+
config: TimelineConfig = {}
|
|
117
|
+
): TimelineControls => {
|
|
118
|
+
const steps = [...initialSteps];
|
|
119
|
+
const listeners = new Set<() => void>();
|
|
120
|
+
let animations: Array<{ animation: Animation; step: TimelineStep; start: number }> = [];
|
|
121
|
+
let totalDuration = 0;
|
|
122
|
+
let reducedMotionApplied = false;
|
|
123
|
+
let finalized = false;
|
|
124
|
+
|
|
125
|
+
const { commitStyles = true, respectReducedMotion = true, onFinish } = config;
|
|
126
|
+
|
|
127
|
+
const finalize = () => {
|
|
128
|
+
if (finalized) return;
|
|
129
|
+
finalized = true;
|
|
130
|
+
|
|
131
|
+
if (commitStyles) {
|
|
132
|
+
for (const item of animations) {
|
|
133
|
+
const { animation, step } = item;
|
|
134
|
+
if (typeof animation.commitStyles === 'function') {
|
|
135
|
+
animation.commitStyles();
|
|
136
|
+
} else {
|
|
137
|
+
applyFinalKeyframeStyles(step.target, step.keyframes);
|
|
138
|
+
}
|
|
139
|
+
animation.cancel();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
listeners.forEach((listener) => listener());
|
|
144
|
+
onFinish?.();
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const buildAnimations = () => {
|
|
148
|
+
animations.forEach(({ animation }) => animation.cancel());
|
|
149
|
+
animations = [];
|
|
150
|
+
finalized = false;
|
|
151
|
+
|
|
152
|
+
const schedule = scheduleSteps(steps);
|
|
153
|
+
totalDuration = schedule.length ? Math.max(...schedule.map((item) => item.end)) : 0;
|
|
154
|
+
|
|
155
|
+
if (respectReducedMotion && prefersReducedMotion()) {
|
|
156
|
+
if (commitStyles) {
|
|
157
|
+
schedule.forEach(({ step }) => applyFinalKeyframeStyles(step.target, step.keyframes));
|
|
158
|
+
}
|
|
159
|
+
reducedMotionApplied = true;
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Check if Web Animations API is available on all targets
|
|
164
|
+
const animateUnavailable = schedule.some(
|
|
165
|
+
({ step }) => typeof (step.target as HTMLElement).animate !== 'function'
|
|
166
|
+
);
|
|
167
|
+
if (animateUnavailable) {
|
|
168
|
+
if (commitStyles) {
|
|
169
|
+
schedule.forEach(({ step }) => applyFinalKeyframeStyles(step.target, step.keyframes));
|
|
170
|
+
}
|
|
171
|
+
reducedMotionApplied = true;
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
reducedMotionApplied = false;
|
|
176
|
+
animations = schedule.map(({ step, start }) => {
|
|
177
|
+
const { delay: _delay, ...options } = step.options ?? {};
|
|
178
|
+
const animation = step.target.animate(step.keyframes, {
|
|
179
|
+
...options,
|
|
180
|
+
delay: start,
|
|
181
|
+
fill: options.fill ?? 'both',
|
|
182
|
+
});
|
|
183
|
+
return { animation, step, start };
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
add(step: TimelineStep): void {
|
|
189
|
+
steps.push(step);
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
duration(): number {
|
|
193
|
+
if (!steps.length) return 0;
|
|
194
|
+
if (!animations.length) {
|
|
195
|
+
const schedule = scheduleSteps(steps);
|
|
196
|
+
return Math.max(...schedule.map((item) => item.end));
|
|
197
|
+
}
|
|
198
|
+
return totalDuration;
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
async play(): Promise<void> {
|
|
202
|
+
buildAnimations();
|
|
203
|
+
|
|
204
|
+
if (reducedMotionApplied || animations.length === 0) {
|
|
205
|
+
finalize();
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const finishPromises = animations.map((item) =>
|
|
210
|
+
item.animation.finished.catch(() => undefined)
|
|
211
|
+
);
|
|
212
|
+
await Promise.all(finishPromises);
|
|
213
|
+
finalize();
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
pause(): void {
|
|
217
|
+
if (reducedMotionApplied) return;
|
|
218
|
+
animations.forEach(({ animation }) => animation.pause());
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
resume(): void {
|
|
222
|
+
if (reducedMotionApplied) return;
|
|
223
|
+
animations.forEach(({ animation }) => animation.play());
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
stop(): void {
|
|
227
|
+
animations.forEach(({ animation }) => animation.cancel());
|
|
228
|
+
animations = [];
|
|
229
|
+
reducedMotionApplied = false;
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
seek(time: number): void {
|
|
233
|
+
if (reducedMotionApplied) return;
|
|
234
|
+
animations.forEach(({ animation }) => {
|
|
235
|
+
// currentTime is measured from the beginning of the animation including delay,
|
|
236
|
+
// so we set it directly to the requested timeline time
|
|
237
|
+
animation.currentTime = time;
|
|
238
|
+
});
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
onFinish(callback: () => void): () => void {
|
|
242
|
+
listeners.add(callback);
|
|
243
|
+
return () => listeners.delete(callback);
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* View transition helpers.
|
|
3
|
+
*
|
|
4
|
+
* @module bquery/motion
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { TransitionOptions } from './types';
|
|
8
|
+
|
|
9
|
+
/** Extended document type with View Transitions API */
|
|
10
|
+
type DocumentWithTransition = Document & {
|
|
11
|
+
startViewTransition?: (callback: () => void) => {
|
|
12
|
+
finished: Promise<void>;
|
|
13
|
+
ready: Promise<void>;
|
|
14
|
+
updateCallbackDone: Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Execute a DOM update with view transition animation.
|
|
20
|
+
* Falls back to immediate update when View Transitions API is unavailable.
|
|
21
|
+
*
|
|
22
|
+
* @param updateOrOptions - Update function or options object
|
|
23
|
+
* @returns Promise that resolves when transition completes
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* await transition(() => {
|
|
28
|
+
* $('#content').text('Updated');
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export const transition = async (
|
|
33
|
+
updateOrOptions: (() => void) | TransitionOptions
|
|
34
|
+
): Promise<void> => {
|
|
35
|
+
const update = typeof updateOrOptions === 'function' ? updateOrOptions : updateOrOptions.update;
|
|
36
|
+
|
|
37
|
+
// SSR/non-DOM environment fallback
|
|
38
|
+
if (typeof document === 'undefined') {
|
|
39
|
+
update();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const doc = document as DocumentWithTransition;
|
|
44
|
+
|
|
45
|
+
if (doc.startViewTransition) {
|
|
46
|
+
await doc.startViewTransition(() => update()).finished;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
update();
|
|
51
|
+
};
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Motion module types.
|
|
3
|
+
*
|
|
4
|
+
* @module bquery/motion
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Options for view transitions.
|
|
9
|
+
*/
|
|
10
|
+
export interface TransitionOptions {
|
|
11
|
+
/** The DOM update function to execute during transition */
|
|
12
|
+
update: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Captured element bounds for FLIP animations.
|
|
17
|
+
*/
|
|
18
|
+
export interface ElementBounds {
|
|
19
|
+
top: number;
|
|
20
|
+
left: number;
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* FLIP animation configuration options.
|
|
27
|
+
*/
|
|
28
|
+
export interface FlipOptions {
|
|
29
|
+
/** Animation duration in milliseconds */
|
|
30
|
+
duration?: number;
|
|
31
|
+
/** CSS easing function */
|
|
32
|
+
easing?: string;
|
|
33
|
+
/** Callback when animation completes */
|
|
34
|
+
onComplete?: () => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Stagger delay function signature.
|
|
39
|
+
*/
|
|
40
|
+
export type StaggerFunction = (index: number, total: number) => number;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Extended options for group FLIP animations.
|
|
44
|
+
*/
|
|
45
|
+
export interface FlipGroupOptions extends FlipOptions {
|
|
46
|
+
/** Optional stagger delay function */
|
|
47
|
+
stagger?: StaggerFunction;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Spring physics configuration.
|
|
52
|
+
*/
|
|
53
|
+
export interface SpringConfig {
|
|
54
|
+
/** Spring stiffness (default: 100) */
|
|
55
|
+
stiffness?: number;
|
|
56
|
+
/** Damping coefficient (default: 10) */
|
|
57
|
+
damping?: number;
|
|
58
|
+
/** Mass of the object (default: 1) */
|
|
59
|
+
mass?: number;
|
|
60
|
+
/** Velocity threshold for completion (default: 0.01) */
|
|
61
|
+
precision?: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Spring instance for animating values.
|
|
66
|
+
*/
|
|
67
|
+
export interface Spring {
|
|
68
|
+
/** Start animating to target value */
|
|
69
|
+
to(target: number): Promise<void>;
|
|
70
|
+
/** Get current animated value */
|
|
71
|
+
current(): number;
|
|
72
|
+
/** Stop the animation */
|
|
73
|
+
stop(): void;
|
|
74
|
+
/** Subscribe to value changes */
|
|
75
|
+
onChange(callback: (value: number) => void): () => void;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Web Animations helper configuration.
|
|
80
|
+
*/
|
|
81
|
+
export interface AnimateOptions {
|
|
82
|
+
/** Keyframes to animate */
|
|
83
|
+
keyframes: Keyframe[] | PropertyIndexedKeyframes;
|
|
84
|
+
/** Animation options (duration, easing, etc.) */
|
|
85
|
+
options?: KeyframeAnimationOptions;
|
|
86
|
+
/** Commit final styles to the element (default: true) */
|
|
87
|
+
commitStyles?: boolean;
|
|
88
|
+
/** Respect prefers-reduced-motion (default: true) */
|
|
89
|
+
respectReducedMotion?: boolean;
|
|
90
|
+
/** Callback when animation completes */
|
|
91
|
+
onFinish?: () => void;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Stagger helper configuration.
|
|
96
|
+
*/
|
|
97
|
+
export interface StaggerOptions {
|
|
98
|
+
/** Start delay in milliseconds (default: 0) */
|
|
99
|
+
start?: number;
|
|
100
|
+
/** Origin index or keyword (default: 'start') */
|
|
101
|
+
from?: 'start' | 'center' | 'end' | number;
|
|
102
|
+
/** Optional easing function for normalized distance */
|
|
103
|
+
easing?: EasingFunction;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Easing function signature.
|
|
108
|
+
*/
|
|
109
|
+
export type EasingFunction = (t: number) => number;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Sequence step configuration.
|
|
113
|
+
*/
|
|
114
|
+
export interface SequenceStep extends AnimateOptions {
|
|
115
|
+
/** Target element to animate */
|
|
116
|
+
target: Element;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Sequence run configuration.
|
|
121
|
+
*/
|
|
122
|
+
export interface SequenceOptions {
|
|
123
|
+
/** Optional stagger delay between steps */
|
|
124
|
+
stagger?: StaggerFunction;
|
|
125
|
+
/** Callback when sequence completes */
|
|
126
|
+
onFinish?: () => void;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Timeline step configuration.
|
|
131
|
+
*/
|
|
132
|
+
export interface TimelineStep {
|
|
133
|
+
/** Target element to animate */
|
|
134
|
+
target: Element;
|
|
135
|
+
/** Keyframes to animate */
|
|
136
|
+
keyframes: Keyframe[] | PropertyIndexedKeyframes;
|
|
137
|
+
/** Animation options for this step */
|
|
138
|
+
options?: KeyframeAnimationOptions;
|
|
139
|
+
/** Absolute or relative start time in milliseconds */
|
|
140
|
+
at?: number | `+=${number}` | `-=${number}`;
|
|
141
|
+
/** Optional label for debugging */
|
|
142
|
+
label?: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Timeline configuration.
|
|
147
|
+
*/
|
|
148
|
+
export interface TimelineConfig {
|
|
149
|
+
/** Commit final styles when timeline completes (default: true) */
|
|
150
|
+
commitStyles?: boolean;
|
|
151
|
+
/** Respect prefers-reduced-motion (default: true) */
|
|
152
|
+
respectReducedMotion?: boolean;
|
|
153
|
+
/** Callback when timeline completes */
|
|
154
|
+
onFinish?: () => void;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Timeline controls.
|
|
159
|
+
*/
|
|
160
|
+
export interface TimelineControls {
|
|
161
|
+
/** Play all steps */
|
|
162
|
+
play(): Promise<void>;
|
|
163
|
+
/** Pause animations */
|
|
164
|
+
pause(): void;
|
|
165
|
+
/** Resume animations */
|
|
166
|
+
resume(): void;
|
|
167
|
+
/** Stop and cancel animations */
|
|
168
|
+
stop(): void;
|
|
169
|
+
/** Seek to a specific time in milliseconds */
|
|
170
|
+
seek(time: number): void;
|
|
171
|
+
/** Add a step to the timeline */
|
|
172
|
+
add(step: TimelineStep): void;
|
|
173
|
+
/** Total timeline duration in milliseconds */
|
|
174
|
+
duration(): number;
|
|
175
|
+
/** Subscribe to finish events */
|
|
176
|
+
onFinish(callback: () => void): () => void;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Scroll animation configuration.
|
|
181
|
+
*/
|
|
182
|
+
export interface ScrollAnimateOptions extends AnimateOptions {
|
|
183
|
+
/** IntersectionObserver root */
|
|
184
|
+
root?: Element | Document | null;
|
|
185
|
+
/** Root margin for observer */
|
|
186
|
+
rootMargin?: string;
|
|
187
|
+
/** Intersection thresholds */
|
|
188
|
+
threshold?: number | number[];
|
|
189
|
+
/** Trigger only once (default: true) */
|
|
190
|
+
once?: boolean;
|
|
191
|
+
/** Callback when element enters the viewport */
|
|
192
|
+
onEnter?: (element: Element) => void;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Cleanup function for scroll animations.
|
|
197
|
+
*/
|
|
198
|
+
export type ScrollAnimateCleanup = () => void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batched reactive updates.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { beginBatch, endBatch } from './internals';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Batches multiple signal updates into a single notification cycle.
|
|
9
|
+
*
|
|
10
|
+
* Updates made inside the batch function are deferred until the batch
|
|
11
|
+
* completes, preventing intermediate re-renders and improving performance.
|
|
12
|
+
*
|
|
13
|
+
* @param fn - Function containing multiple signal updates
|
|
14
|
+
*/
|
|
15
|
+
export const batch = (fn: () => void): void => {
|
|
16
|
+
beginBatch();
|
|
17
|
+
try {
|
|
18
|
+
fn();
|
|
19
|
+
} finally {
|
|
20
|
+
endBatch();
|
|
21
|
+
}
|
|
22
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Computed reactive values.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
clearDependencies,
|
|
7
|
+
getCurrentObserver,
|
|
8
|
+
registerDependency,
|
|
9
|
+
scheduleObserver,
|
|
10
|
+
track,
|
|
11
|
+
type ReactiveSource,
|
|
12
|
+
} from './internals';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A computed value that derives from other reactive sources.
|
|
16
|
+
*
|
|
17
|
+
* Computed values are lazily evaluated and cached. They only
|
|
18
|
+
* recompute when their dependencies change.
|
|
19
|
+
*
|
|
20
|
+
* @template T - The type of the computed value
|
|
21
|
+
*/
|
|
22
|
+
export class Computed<T> implements ReactiveSource {
|
|
23
|
+
private cachedValue!: T;
|
|
24
|
+
private dirty = true;
|
|
25
|
+
private subscribers = new Set<() => void>();
|
|
26
|
+
private readonly markDirty = () => {
|
|
27
|
+
this.dirty = true;
|
|
28
|
+
// Create snapshot to avoid issues with subscribers modifying the set during iteration
|
|
29
|
+
const subscribersSnapshot = Array.from(this.subscribers);
|
|
30
|
+
for (const subscriber of subscribersSnapshot) {
|
|
31
|
+
scheduleObserver(subscriber);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Creates a new computed value.
|
|
37
|
+
* @param compute - Function that computes the value
|
|
38
|
+
*/
|
|
39
|
+
constructor(private readonly compute: () => T) {}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Gets the computed value, recomputing if dependencies changed.
|
|
43
|
+
* During untrack calls, getCurrentObserver returns undefined, preventing dependency tracking.
|
|
44
|
+
*/
|
|
45
|
+
get value(): T {
|
|
46
|
+
const current = getCurrentObserver();
|
|
47
|
+
if (current) {
|
|
48
|
+
this.subscribers.add(current);
|
|
49
|
+
registerDependency(current, this);
|
|
50
|
+
}
|
|
51
|
+
if (this.dirty) {
|
|
52
|
+
this.dirty = false;
|
|
53
|
+
// Clear old dependencies before recomputing
|
|
54
|
+
clearDependencies(this.markDirty);
|
|
55
|
+
this.cachedValue = track(this.markDirty, this.compute);
|
|
56
|
+
}
|
|
57
|
+
return this.cachedValue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Reads the current computed value without tracking.
|
|
62
|
+
* Useful when you need the value but don't want to create a dependency.
|
|
63
|
+
*
|
|
64
|
+
* @returns The current cached value (recomputes if dirty)
|
|
65
|
+
*/
|
|
66
|
+
peek(): T {
|
|
67
|
+
if (this.dirty) {
|
|
68
|
+
this.dirty = false;
|
|
69
|
+
// Clear old dependencies before recomputing
|
|
70
|
+
clearDependencies(this.markDirty);
|
|
71
|
+
this.cachedValue = track(this.markDirty, this.compute);
|
|
72
|
+
}
|
|
73
|
+
return this.cachedValue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Removes an observer from this computed's subscriber set.
|
|
78
|
+
* @internal
|
|
79
|
+
*/
|
|
80
|
+
unsubscribe(observer: () => void): void {
|
|
81
|
+
this.subscribers.delete(observer);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Creates a new computed value.
|
|
87
|
+
*
|
|
88
|
+
* @template T - The type of the computed value
|
|
89
|
+
* @param fn - Function that computes the value from reactive sources
|
|
90
|
+
* @returns A new Computed instance
|
|
91
|
+
*/
|
|
92
|
+
export const computed = <T>(fn: () => T): Computed<T> => new Computed(fn);
|