motionary 0.0.0-stage → 6.2.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/CHANGELOG.md +803 -0
- package/LICENSE +21 -0
- package/README.md +218 -2
- package/README_ja.md +219 -0
- package/README_zh.md +219 -0
- package/bin/usa-codemod-5.mjs +79 -0
- package/bin/usa-codemod-6.mjs +146 -0
- package/dist/chunks/base-BaQV-2ha.cjs +436 -0
- package/dist/chunks/base-BaQV-2ha.cjs.map +1 -0
- package/dist/chunks/base-C_3cAoRz.js +404 -0
- package/dist/chunks/base-C_3cAoRz.js.map +1 -0
- package/dist/chunks/bind-B_CTL6Qn.js +22 -0
- package/dist/chunks/bind-B_CTL6Qn.js.map +1 -0
- package/dist/chunks/bind-Ui43-n2d.cjs +25 -0
- package/dist/chunks/bind-Ui43-n2d.cjs.map +1 -0
- package/dist/chunks/core-B5T0dhFH.cjs +920 -0
- package/dist/chunks/core-B5T0dhFH.cjs.map +1 -0
- package/dist/chunks/core-BGAyaY6L.cjs +265 -0
- package/dist/chunks/core-BGAyaY6L.cjs.map +1 -0
- package/dist/chunks/core-D7cVumUu.js +909 -0
- package/dist/chunks/core-D7cVumUu.js.map +1 -0
- package/dist/chunks/core-VEBw36QK.js +260 -0
- package/dist/chunks/core-VEBw36QK.js.map +1 -0
- package/dist/chunks/core-ps8uvYuN.js +181 -0
- package/dist/chunks/core-ps8uvYuN.js.map +1 -0
- package/dist/chunks/core-zq17EeCI.cjs +185 -0
- package/dist/chunks/core-zq17EeCI.cjs.map +1 -0
- package/dist/chunks/fx-Bs6IrV4Q.js +109 -0
- package/dist/chunks/fx-Bs6IrV4Q.js.map +1 -0
- package/dist/chunks/fx-lBGVtQO1.cjs +114 -0
- package/dist/chunks/fx-lBGVtQO1.cjs.map +1 -0
- package/dist/chunks/generative-D2YyhaeO.js +336 -0
- package/dist/chunks/generative-D2YyhaeO.js.map +1 -0
- package/dist/chunks/generative-DzIZq-_g.cjs +348 -0
- package/dist/chunks/generative-DzIZq-_g.cjs.map +1 -0
- package/dist/chunks/gpu-Bu7qwugO.js +380 -0
- package/dist/chunks/gpu-Bu7qwugO.js.map +1 -0
- package/dist/chunks/gpu-CenK2l6b.cjs +387 -0
- package/dist/chunks/gpu-CenK2l6b.cjs.map +1 -0
- package/dist/chunks/index-tags-DucKMQr_.js +26 -0
- package/dist/chunks/index-tags-DucKMQr_.js.map +1 -0
- package/dist/chunks/index-tags-wv0R_vkO.cjs +28 -0
- package/dist/chunks/index-tags-wv0R_vkO.cjs.map +1 -0
- package/dist/chunks/presets-BYBVJVeP.js +226 -0
- package/dist/chunks/presets-BYBVJVeP.js.map +1 -0
- package/dist/chunks/presets-CUHys3sK.cjs +233 -0
- package/dist/chunks/presets-CUHys3sK.cjs.map +1 -0
- package/dist/chunks/registry-CKNLQpwd.js +121 -0
- package/dist/chunks/registry-CKNLQpwd.js.map +1 -0
- package/dist/chunks/registry-DehBVRDV.cjs +131 -0
- package/dist/chunks/registry-DehBVRDV.cjs.map +1 -0
- package/dist/chunks/spring-CbvHfVtP.js +219 -0
- package/dist/chunks/spring-CbvHfVtP.js.map +1 -0
- package/dist/chunks/spring-Dgx187Vh.cjs +232 -0
- package/dist/chunks/spring-Dgx187Vh.cjs.map +1 -0
- package/dist/chunks/stagger-CCFyhzSw.cjs +86 -0
- package/dist/chunks/stagger-CCFyhzSw.cjs.map +1 -0
- package/dist/chunks/stagger-DtMKo2SK.js +84 -0
- package/dist/chunks/stagger-DtMKo2SK.js.map +1 -0
- package/dist/chunks/variants-BhjyddG8.cjs +39 -0
- package/dist/chunks/variants-BhjyddG8.cjs.map +1 -0
- package/dist/chunks/variants-DRlKVHvu.js +35 -0
- package/dist/chunks/variants-DRlKVHvu.js.map +1 -0
- package/dist/components/a11y.cjs +276 -0
- package/dist/components/a11y.cjs.map +1 -0
- package/dist/components/a11y.d.cts +131 -0
- package/dist/components/a11y.d.ts +131 -0
- package/dist/components/a11y.js +259 -0
- package/dist/components/a11y.js.map +1 -0
- package/dist/components/angular.cjs +76 -0
- package/dist/components/angular.cjs.map +1 -0
- package/dist/components/angular.d.cts +1734 -0
- package/dist/components/angular.d.ts +1734 -0
- package/dist/components/angular.js +70 -0
- package/dist/components/angular.js.map +1 -0
- package/dist/components/background.cjs +659 -0
- package/dist/components/background.cjs.map +1 -0
- package/dist/components/background.css +9 -0
- package/dist/components/background.d.cts +166 -0
- package/dist/components/background.d.ts +166 -0
- package/dist/components/background.js +647 -0
- package/dist/components/background.js.map +1 -0
- package/dist/components/bridge.cjs +154 -0
- package/dist/components/bridge.cjs.map +1 -0
- package/dist/components/bridge.d.cts +63 -0
- package/dist/components/bridge.d.ts +63 -0
- package/dist/components/bridge.js +147 -0
- package/dist/components/bridge.js.map +1 -0
- package/dist/components/cards.cjs +561 -0
- package/dist/components/cards.cjs.map +1 -0
- package/dist/components/cards.css +6 -0
- package/dist/components/cards.d.cts +115 -0
- package/dist/components/cards.d.ts +115 -0
- package/dist/components/cards.js +554 -0
- package/dist/components/cards.js.map +1 -0
- package/dist/components/click.cjs +877 -0
- package/dist/components/click.cjs.map +1 -0
- package/dist/components/click.css +9 -0
- package/dist/components/click.d.cts +218 -0
- package/dist/components/click.d.ts +218 -0
- package/dist/components/click.js +863 -0
- package/dist/components/click.js.map +1 -0
- package/dist/components/depth.cjs +250 -0
- package/dist/components/depth.cjs.map +1 -0
- package/dist/components/depth.css +3 -0
- package/dist/components/depth.d.cts +86 -0
- package/dist/components/depth.d.ts +86 -0
- package/dist/components/depth.js +242 -0
- package/dist/components/depth.js.map +1 -0
- package/dist/components/effects.cjs +2258 -0
- package/dist/components/effects.cjs.map +1 -0
- package/dist/components/effects.d.cts +545 -0
- package/dist/components/effects.d.ts +545 -0
- package/dist/components/effects.js +2200 -0
- package/dist/components/effects.js.map +1 -0
- package/dist/components/feedback.cjs +442 -0
- package/dist/components/feedback.cjs.map +1 -0
- package/dist/components/feedback.css +7 -0
- package/dist/components/feedback.d.cts +144 -0
- package/dist/components/feedback.d.ts +144 -0
- package/dist/components/feedback.js +433 -0
- package/dist/components/feedback.js.map +1 -0
- package/dist/components/fx-gpu.cjs +12 -0
- package/dist/components/fx-gpu.cjs.map +1 -0
- package/dist/components/fx-gpu.d.cts +63 -0
- package/dist/components/fx-gpu.d.ts +63 -0
- package/dist/components/fx-gpu.js +5 -0
- package/dist/components/fx-gpu.js.map +1 -0
- package/dist/components/fx.cjs +169 -0
- package/dist/components/fx.cjs.map +1 -0
- package/dist/components/fx.css +3 -0
- package/dist/components/fx.d.cts +114 -0
- package/dist/components/fx.d.ts +114 -0
- package/dist/components/fx.js +156 -0
- package/dist/components/fx.js.map +1 -0
- package/dist/components/fx2.cjs +25 -0
- package/dist/components/fx2.cjs.map +1 -0
- package/dist/components/fx2.d.cts +136 -0
- package/dist/components/fx2.d.ts +136 -0
- package/dist/components/fx2.js +17 -0
- package/dist/components/fx2.js.map +1 -0
- package/dist/components/gesture.cjs +209 -0
- package/dist/components/gesture.cjs.map +1 -0
- package/dist/components/gesture.css +3 -0
- package/dist/components/gesture.d.cts +134 -0
- package/dist/components/gesture.d.ts +134 -0
- package/dist/components/gesture.js +203 -0
- package/dist/components/gesture.js.map +1 -0
- package/dist/components/interaction.cjs +424 -0
- package/dist/components/interaction.cjs.map +1 -0
- package/dist/components/interaction.css +8 -0
- package/dist/components/interaction.d.cts +115 -0
- package/dist/components/interaction.d.ts +115 -0
- package/dist/components/interaction.js +416 -0
- package/dist/components/interaction.js.map +1 -0
- package/dist/components/jsx.cjs +3 -0
- package/dist/components/jsx.cjs.map +1 -0
- package/dist/components/jsx.d.cts +53 -0
- package/dist/components/jsx.d.ts +53 -0
- package/dist/components/jsx.js +2 -0
- package/dist/components/jsx.js.map +1 -0
- package/dist/components/layout.cjs +257 -0
- package/dist/components/layout.cjs.map +1 -0
- package/dist/components/layout.css +3 -0
- package/dist/components/layout.d.cts +99 -0
- package/dist/components/layout.d.ts +99 -0
- package/dist/components/layout.js +249 -0
- package/dist/components/layout.js.map +1 -0
- package/dist/components/lazy.cjs +92 -0
- package/dist/components/lazy.cjs.map +1 -0
- package/dist/components/lazy.d.cts +48 -0
- package/dist/components/lazy.d.ts +48 -0
- package/dist/components/lazy.js +87 -0
- package/dist/components/lazy.js.map +1 -0
- package/dist/components/lite.cjs +11095 -0
- package/dist/components/lite.cjs.map +1 -0
- package/dist/components/lite.d.cts +4 -0
- package/dist/components/lite.d.ts +4 -0
- package/dist/components/lite.js +10827 -0
- package/dist/components/lite.js.map +1 -0
- package/dist/components/packs.cjs +192 -0
- package/dist/components/packs.cjs.map +1 -0
- package/dist/components/packs.css +3 -0
- package/dist/components/packs.d.cts +72 -0
- package/dist/components/packs.d.ts +72 -0
- package/dist/components/packs.js +184 -0
- package/dist/components/packs.js.map +1 -0
- package/dist/components/page.cjs +824 -0
- package/dist/components/page.cjs.map +1 -0
- package/dist/components/page.css +11 -0
- package/dist/components/page.d.cts +249 -0
- package/dist/components/page.d.ts +249 -0
- package/dist/components/page.js +799 -0
- package/dist/components/page.js.map +1 -0
- package/dist/components/perf.cjs +120 -0
- package/dist/components/perf.cjs.map +1 -0
- package/dist/components/perf.d.cts +98 -0
- package/dist/components/perf.d.ts +98 -0
- package/dist/components/perf.js +110 -0
- package/dist/components/perf.js.map +1 -0
- package/dist/components/physics.cjs +448 -0
- package/dist/components/physics.cjs.map +1 -0
- package/dist/components/physics.css +5 -0
- package/dist/components/physics.d.cts +189 -0
- package/dist/components/physics.d.ts +189 -0
- package/dist/components/physics.js +430 -0
- package/dist/components/physics.js.map +1 -0
- package/dist/components/react.cjs +96 -0
- package/dist/components/react.cjs.map +1 -0
- package/dist/components/react.d.cts +18 -0
- package/dist/components/react.d.ts +18 -0
- package/dist/components/react.js +91 -0
- package/dist/components/react.js.map +1 -0
- package/dist/components/reveal.cjs +362 -0
- package/dist/components/reveal.cjs.map +1 -0
- package/dist/components/reveal.css +5 -0
- package/dist/components/reveal.d.cts +105 -0
- package/dist/components/reveal.d.ts +105 -0
- package/dist/components/reveal.js +353 -0
- package/dist/components/reveal.js.map +1 -0
- package/dist/components/solid.cjs +86 -0
- package/dist/components/solid.cjs.map +1 -0
- package/dist/components/solid.d.cts +1751 -0
- package/dist/components/solid.d.ts +1751 -0
- package/dist/components/solid.js +82 -0
- package/dist/components/solid.js.map +1 -0
- package/dist/components/svelte.cjs +68 -0
- package/dist/components/svelte.cjs.map +1 -0
- package/dist/components/svelte.d.cts +1733 -0
- package/dist/components/svelte.d.ts +1733 -0
- package/dist/components/svelte.js +64 -0
- package/dist/components/svelte.js.map +1 -0
- package/dist/components/svg.cjs +359 -0
- package/dist/components/svg.cjs.map +1 -0
- package/dist/components/svg.css +3 -0
- package/dist/components/svg.d.cts +113 -0
- package/dist/components/svg.d.ts +113 -0
- package/dist/components/svg.js +347 -0
- package/dist/components/svg.js.map +1 -0
- package/dist/components/text.cjs +968 -0
- package/dist/components/text.cjs.map +1 -0
- package/dist/components/text.css +8 -0
- package/dist/components/text.d.cts +342 -0
- package/dist/components/text.d.ts +342 -0
- package/dist/components/text.js +947 -0
- package/dist/components/text.js.map +1 -0
- package/dist/components/timeline.cjs +109 -0
- package/dist/components/timeline.cjs.map +1 -0
- package/dist/components/timeline.css +3 -0
- package/dist/components/timeline.d.cts +155 -0
- package/dist/components/timeline.d.ts +155 -0
- package/dist/components/timeline.js +103 -0
- package/dist/components/timeline.js.map +1 -0
- package/dist/components/tokens.cjs +226 -0
- package/dist/components/tokens.cjs.map +1 -0
- package/dist/components/tokens.d.cts +74 -0
- package/dist/components/tokens.d.ts +74 -0
- package/dist/components/tokens.js +211 -0
- package/dist/components/tokens.js.map +1 -0
- package/dist/components/transitions.cjs +430 -0
- package/dist/components/transitions.cjs.map +1 -0
- package/dist/components/transitions.css +5 -0
- package/dist/components/transitions.d.cts +126 -0
- package/dist/components/transitions.d.ts +126 -0
- package/dist/components/transitions.js +423 -0
- package/dist/components/transitions.js.map +1 -0
- package/dist/components/ui.cjs +1050 -0
- package/dist/components/ui.cjs.map +1 -0
- package/dist/components/ui.css +14 -0
- package/dist/components/ui.d.cts +224 -0
- package/dist/components/ui.d.ts +224 -0
- package/dist/components/ui.js +1034 -0
- package/dist/components/ui.js.map +1 -0
- package/dist/components/vue.cjs +67 -0
- package/dist/components/vue.cjs.map +1 -0
- package/dist/components/vue.d.cts +1722 -0
- package/dist/components/vue.d.ts +1722 -0
- package/dist/components/vue.js +64 -0
- package/dist/components/vue.js.map +1 -0
- package/dist/components/webgl.cjs +459 -0
- package/dist/components/webgl.cjs.map +1 -0
- package/dist/components/webgl.css +3 -0
- package/dist/components/webgl.d.cts +133 -0
- package/dist/components/webgl.d.ts +133 -0
- package/dist/components/webgl.js +442 -0
- package/dist/components/webgl.js.map +1 -0
- package/dist/components/widgets.cjs +591 -0
- package/dist/components/widgets.cjs.map +1 -0
- package/dist/components/widgets.d.cts +110 -0
- package/dist/components/widgets.d.ts +110 -0
- package/dist/components/widgets.js +581 -0
- package/dist/components/widgets.js.map +1 -0
- package/dist/components.cjs +351 -0
- package/dist/components.cjs.map +1 -0
- package/dist/components.css +75 -0
- package/dist/components.d.cts +2997 -0
- package/dist/components.d.ts +2997 -0
- package/dist/components.js +106 -0
- package/dist/components.js.map +1 -0
- package/dist/components.umd.js +23 -0
- package/dist/components.umd.js.map +1 -0
- package/dist/element.cjs +97 -0
- package/dist/element.cjs.map +1 -0
- package/dist/element.d.cts +215 -0
- package/dist/element.d.ts +215 -0
- package/dist/element.js +95 -0
- package/dist/element.js.map +1 -0
- package/dist/element.umd.js +2 -0
- package/dist/element.umd.js.map +1 -0
- package/dist/index.cjs +149 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +425 -0
- package/dist/index.d.ts +425 -0
- package/dist/index.js +131 -0
- package/dist/index.js.map +1 -0
- package/dist/index.umd.js +15 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/presets/extended.cjs +243 -0
- package/dist/presets/extended.cjs.map +1 -0
- package/dist/presets/extended.d.cts +37 -0
- package/dist/presets/extended.d.ts +37 -0
- package/dist/presets/extended.js +239 -0
- package/dist/presets/extended.js.map +1 -0
- package/dist/presets-extended.umd.js +2 -0
- package/dist/presets-extended.umd.js.map +1 -0
- package/dist/react.cjs +68 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +158 -0
- package/dist/react.d.ts +158 -0
- package/dist/react.js +65 -0
- package/dist/react.js.map +1 -0
- package/dist/solid.cjs +93 -0
- package/dist/solid.cjs.map +1 -0
- package/dist/solid.d.cts +249 -0
- package/dist/solid.d.ts +249 -0
- package/dist/solid.js +89 -0
- package/dist/solid.js.map +1 -0
- package/dist/svelte.cjs +66 -0
- package/dist/svelte.cjs.map +1 -0
- package/dist/svelte.d.cts +243 -0
- package/dist/svelte.d.ts +243 -0
- package/dist/svelte.js +63 -0
- package/dist/svelte.js.map +1 -0
- package/dist/vue.cjs +62 -0
- package/dist/vue.cjs.map +1 -0
- package/dist/vue.d.cts +158 -0
- package/dist/vue.d.ts +158 -0
- package/dist/vue.js +60 -0
- package/dist/vue.js.map +1 -0
- package/dist/widgets.umd.js +2 -0
- package/dist/widgets.umd.js.map +1 -0
- package/docs/API.md +147 -0
- package/docs/ROADMAP.md +14 -0
- package/docs/accessibility.md +60 -0
- package/docs/components.md +564 -0
- package/docs/deprecations.md +30 -0
- package/docs/frameworks-ssr.md +100 -0
- package/docs/hybrid-apps.md +145 -0
- package/docs/images/showcase-detail.png +0 -0
- package/docs/images/showcase-grid.png +0 -0
- package/docs/images/showcase-mobile.png +0 -0
- package/docs/migration-from-aos.md +67 -0
- package/docs/migration-from-gsap-scrolltrigger.md +80 -0
- package/docs/motion-tokens.css +31 -0
- package/docs/motion-tokens.md +46 -0
- package/docs/motion.tokens.json +132 -0
- package/docs/performance.md +24 -0
- package/docs/presets.md +310 -0
- package/docs/upgrading-3.md +21 -0
- package/docs/upgrading-4.md +65 -0
- package/docs/upgrading-5.md +31 -0
- package/docs/upgrading-6.md +32 -0
- package/docs/windows-apps.md +119 -0
- package/package.json +562 -4
|
@@ -0,0 +1,920 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var presets = require('./presets-CUHys3sK.cjs');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* motionary - Core Implementation
|
|
7
|
+
* Uses IntersectionObserver + Web Animations API for zero-dependency,
|
|
8
|
+
* high-performance scroll-triggered animations.
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_CONFIG = {
|
|
11
|
+
defaultAnimation: 'fade-in-up',
|
|
12
|
+
defaultDuration: 600,
|
|
13
|
+
defaultDelay: 0,
|
|
14
|
+
defaultEasing: 'ease',
|
|
15
|
+
defaultThreshold: 0.1,
|
|
16
|
+
defaultRootMargin: '0px',
|
|
17
|
+
defaultRepeat: false,
|
|
18
|
+
defaultOnce: true,
|
|
19
|
+
defaultOffset: 0,
|
|
20
|
+
hiddenClass: 'sa-hidden',
|
|
21
|
+
visibleClass: 'sa-visible',
|
|
22
|
+
useClassNames: false,
|
|
23
|
+
disabled: false,
|
|
24
|
+
root: null,
|
|
25
|
+
autoUnregister: true,
|
|
26
|
+
defaultEngine: 'auto',
|
|
27
|
+
};
|
|
28
|
+
const noop = () => undefined;
|
|
29
|
+
/* ------------------------------------------------------------------ */
|
|
30
|
+
/* Environment helpers (all SSR-safe: never touch globals at import) */
|
|
31
|
+
/* ------------------------------------------------------------------ */
|
|
32
|
+
const hasDOM = () => typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
33
|
+
/** @internal */
|
|
34
|
+
const supportsObserver = () => hasDOM() && typeof IntersectionObserver !== 'undefined';
|
|
35
|
+
let reducedMotionQuery;
|
|
36
|
+
/** @internal Whether the user asked the OS/browser to reduce motion. */
|
|
37
|
+
function prefersReducedMotion() {
|
|
38
|
+
if (!hasDOM())
|
|
39
|
+
return false;
|
|
40
|
+
if (reducedMotionQuery === undefined) {
|
|
41
|
+
reducedMotionQuery =
|
|
42
|
+
typeof window.matchMedia === 'function' ? window.matchMedia('(prefers-reduced-motion: reduce)') : null;
|
|
43
|
+
}
|
|
44
|
+
return !!reducedMotionQuery && reducedMotionQuery.matches;
|
|
45
|
+
}
|
|
46
|
+
let scrollTimelineSupported;
|
|
47
|
+
/**
|
|
48
|
+
* Whether the browser can run presets on a native scroll-driven timeline:
|
|
49
|
+
* `CSS.supports('animation-timeline: view()')` plus the `ViewTimeline`
|
|
50
|
+
* constructor used to attach it from JavaScript. Cached. SSR-safe.
|
|
51
|
+
*/
|
|
52
|
+
function supportsScrollTimeline() {
|
|
53
|
+
if (scrollTimelineSupported === undefined) {
|
|
54
|
+
scrollTimelineSupported =
|
|
55
|
+
hasDOM() &&
|
|
56
|
+
typeof CSS !== 'undefined' &&
|
|
57
|
+
typeof CSS.supports === 'function' &&
|
|
58
|
+
CSS.supports('animation-timeline: view()') &&
|
|
59
|
+
typeof globalThis.ViewTimeline === 'function';
|
|
60
|
+
}
|
|
61
|
+
return scrollTimelineSupported;
|
|
62
|
+
}
|
|
63
|
+
/* ------------------------------------------------------------------ */
|
|
64
|
+
/* Option parsing */
|
|
65
|
+
/* ------------------------------------------------------------------ */
|
|
66
|
+
function num(value) {
|
|
67
|
+
if (value === undefined || value.trim() === '')
|
|
68
|
+
return undefined;
|
|
69
|
+
const n = parseFloat(value);
|
|
70
|
+
return Number.isFinite(n) ? n : undefined;
|
|
71
|
+
}
|
|
72
|
+
const DEFAULT_PROGRESS_VAR = '--sa-progress';
|
|
73
|
+
const DEFAULT_VIEW_RANGE = ['entry 0%', 'entry 100%'];
|
|
74
|
+
/** `''` (bare attribute) -> default name; `sa-progress` -> `--sa-progress`. */
|
|
75
|
+
function normalizeVar(name) {
|
|
76
|
+
const v = name.trim();
|
|
77
|
+
if (!v)
|
|
78
|
+
return DEFAULT_PROGRESS_VAR;
|
|
79
|
+
return v.startsWith('--') ? v : `--${v}`;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* @internal Read options from string attributes. `get(name)` returns the raw
|
|
83
|
+
* value of the attribute `name` (kebab-case, e.g. `root-margin`), `''` for a
|
|
84
|
+
* bare attribute and `undefined` when absent. Shared by `data-sa-*` and the
|
|
85
|
+
* `<scroll-animate>` element.
|
|
86
|
+
*/
|
|
87
|
+
function readOptions(get) {
|
|
88
|
+
const opts = {};
|
|
89
|
+
const anim = get('animation');
|
|
90
|
+
if (anim) {
|
|
91
|
+
opts.animation = (anim.includes(',') ? anim.split(',').map((s) => s.trim()) : anim.trim());
|
|
92
|
+
}
|
|
93
|
+
opts.duration = num(get('duration'));
|
|
94
|
+
opts.delay = num(get('delay'));
|
|
95
|
+
const easing = get('easing');
|
|
96
|
+
if (easing) {
|
|
97
|
+
const e = easing.trim();
|
|
98
|
+
if (e.startsWith('[')) {
|
|
99
|
+
try {
|
|
100
|
+
opts.easing = JSON.parse(e);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Malformed JSON: fall back to the default easing instead of throwing
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
opts.easing = e;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const t = get('threshold');
|
|
111
|
+
if (t) {
|
|
112
|
+
opts.threshold = t.includes(',') ? t.split(',').map(parseFloat).filter(Number.isFinite) : num(t);
|
|
113
|
+
}
|
|
114
|
+
const rootMargin = get('root-margin');
|
|
115
|
+
if (rootMargin)
|
|
116
|
+
opts.rootMargin = rootMargin;
|
|
117
|
+
const repeat = get('repeat');
|
|
118
|
+
if (repeat !== undefined)
|
|
119
|
+
opts.repeat = repeat !== 'false';
|
|
120
|
+
const once = get('once');
|
|
121
|
+
if (once !== undefined)
|
|
122
|
+
opts.once = once !== 'false';
|
|
123
|
+
opts.offset = num(get('offset'));
|
|
124
|
+
opts.stagger = num(get('stagger'));
|
|
125
|
+
const progressVar = get('progress-var');
|
|
126
|
+
if (progressVar !== undefined)
|
|
127
|
+
opts.progressVar = normalizeVar(progressVar);
|
|
128
|
+
const engine = get('engine')?.trim();
|
|
129
|
+
if (engine === 'auto' || engine === 'js' || engine === 'css')
|
|
130
|
+
opts.engine = engine;
|
|
131
|
+
const range = get('view-range');
|
|
132
|
+
if (range) {
|
|
133
|
+
const [start, end] = range.split(',').map((s) => s.trim());
|
|
134
|
+
if (start && end)
|
|
135
|
+
opts.viewRange = [start, end];
|
|
136
|
+
}
|
|
137
|
+
const exit = get('exit');
|
|
138
|
+
if (exit !== undefined) {
|
|
139
|
+
const e = exit.trim();
|
|
140
|
+
opts.exit = e === '' || e === 'true' ? true : e === 'false' ? false : (e.includes(',') ? e.split(',').map((s) => s.trim()) : e);
|
|
141
|
+
}
|
|
142
|
+
const progress = get('progress');
|
|
143
|
+
if (progress)
|
|
144
|
+
opts.progressMode = progress.trim() === 'scroll' ? 'scroll' : 'ratio';
|
|
145
|
+
// Drop keys that were not set, so they don't override defaults when spread.
|
|
146
|
+
Object.keys(opts).forEach((k) => opts[k] === undefined && delete opts[k]);
|
|
147
|
+
return opts;
|
|
148
|
+
}
|
|
149
|
+
function parseDataAttributes(el, config) {
|
|
150
|
+
return mergeOptions(readOptions((name) => {
|
|
151
|
+
const v = el.getAttribute(`data-sa-${name}`);
|
|
152
|
+
return v === null ? undefined : v;
|
|
153
|
+
}), config);
|
|
154
|
+
}
|
|
155
|
+
function mergeOptions(opts, config) {
|
|
156
|
+
let engine = opts.engine ?? config.defaultEngine;
|
|
157
|
+
// 'auto' keeps time-based semantics when the element asks for them explicitly.
|
|
158
|
+
if (engine === 'auto' && (opts.duration !== undefined || opts.delay !== undefined || opts.offset !== undefined || (opts.stagger ?? 0) > 0)) {
|
|
159
|
+
engine = 'js';
|
|
160
|
+
}
|
|
161
|
+
const exit = opts.exit ?? false;
|
|
162
|
+
const repeat = opts.repeat ?? (exit ? true : config.defaultRepeat);
|
|
163
|
+
const threshold = opts.threshold ?? config.defaultThreshold;
|
|
164
|
+
return {
|
|
165
|
+
animation: opts.animation ?? config.defaultAnimation,
|
|
166
|
+
duration: opts.duration ?? config.defaultDuration,
|
|
167
|
+
delay: opts.delay ?? config.defaultDelay,
|
|
168
|
+
easing: opts.easing ?? config.defaultEasing,
|
|
169
|
+
threshold: Array.isArray(threshold) && threshold.length === 0 ? config.defaultThreshold : threshold,
|
|
170
|
+
rootMargin: opts.rootMargin ?? config.defaultRootMargin,
|
|
171
|
+
repeat,
|
|
172
|
+
once: opts.once ?? (repeat ? false : config.defaultOnce),
|
|
173
|
+
offset: opts.offset ?? config.defaultOffset,
|
|
174
|
+
stagger: opts.stagger ?? 0,
|
|
175
|
+
onStart: opts.onStart ?? noop,
|
|
176
|
+
onComplete: opts.onComplete ?? noop,
|
|
177
|
+
onEnter: opts.onEnter ?? noop,
|
|
178
|
+
onLeave: opts.onLeave ?? noop,
|
|
179
|
+
onProgress: opts.onProgress ?? noop,
|
|
180
|
+
progressMode: opts.progressMode ?? 'ratio',
|
|
181
|
+
progressVar: opts.progressVar ? normalizeVar(opts.progressVar) : '',
|
|
182
|
+
engine,
|
|
183
|
+
viewRange: opts.viewRange ?? DEFAULT_VIEW_RANGE,
|
|
184
|
+
exit,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function resolveTargets(target) {
|
|
188
|
+
if (!target || !hasDOM())
|
|
189
|
+
return [];
|
|
190
|
+
if (typeof target === 'string')
|
|
191
|
+
return Array.from(document.querySelectorAll(target));
|
|
192
|
+
if (target instanceof Element)
|
|
193
|
+
return [target];
|
|
194
|
+
if (Array.isArray(target) || target instanceof NodeList) {
|
|
195
|
+
return Array.from(target).filter((el) => el instanceof Element);
|
|
196
|
+
}
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Apply `offset` to the bottom edge of a rootMargin, preserving the other
|
|
201
|
+
* three sides. `offset: 100` means "trigger 100px later" (bottom -100px).
|
|
202
|
+
* @internal
|
|
203
|
+
*/
|
|
204
|
+
function applyOffset(rootMargin, offset) {
|
|
205
|
+
if (!offset)
|
|
206
|
+
return rootMargin;
|
|
207
|
+
const parts = (rootMargin || '0px').trim().split(/\s+/);
|
|
208
|
+
const top = parts[0];
|
|
209
|
+
const right = parts[1] ?? top;
|
|
210
|
+
const bottom = parts[2] ?? top;
|
|
211
|
+
const left = parts[3] ?? right;
|
|
212
|
+
const m = /^(-?\d*\.?\d+)(px)?$/.exec(bottom);
|
|
213
|
+
const base = m ? parseFloat(m[1]) : 0; // non-px bottoms (e.g. %) cannot be combined; offset wins
|
|
214
|
+
return `${top} ${right} ${base - offset}px ${left}`;
|
|
215
|
+
}
|
|
216
|
+
function needsProgress(opts) {
|
|
217
|
+
return opts.onProgress !== noop || !!opts.progressVar;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* True scroll progress of `el` through the viewport (or `root`): 0 when its top
|
|
221
|
+
* edge reaches the bottom of the viewport, 1 when its bottom edge passes the top.
|
|
222
|
+
* Works for elements taller than the viewport. Returns 0 without a DOM.
|
|
223
|
+
*/
|
|
224
|
+
function getScrollProgress(el, root) {
|
|
225
|
+
if (!hasDOM())
|
|
226
|
+
return 0;
|
|
227
|
+
const rect = el.getBoundingClientRect();
|
|
228
|
+
let top = 0;
|
|
229
|
+
let height = window.innerHeight || document.documentElement.clientHeight || 0;
|
|
230
|
+
if (root) {
|
|
231
|
+
const r = root.getBoundingClientRect();
|
|
232
|
+
top = r.top;
|
|
233
|
+
height = r.height;
|
|
234
|
+
}
|
|
235
|
+
const total = height + rect.height;
|
|
236
|
+
if (total <= 0)
|
|
237
|
+
return 0;
|
|
238
|
+
const p = (top + height - rect.top) / total;
|
|
239
|
+
return p < 0 ? 0 : p > 1 ? 1 : p;
|
|
240
|
+
}
|
|
241
|
+
/* ------------------------------------------------------------------ */
|
|
242
|
+
/* Animation */
|
|
243
|
+
/* ------------------------------------------------------------------ */
|
|
244
|
+
/** The animation currently running on an element, so it can be cancelled/replaced. */
|
|
245
|
+
const running = new WeakMap();
|
|
246
|
+
/** Native engine: the scroll-linked exit animation, next to the entrance in `running`. */
|
|
247
|
+
const exits = new WeakMap();
|
|
248
|
+
const timers = new WeakMap();
|
|
249
|
+
function cancelRunning(el) {
|
|
250
|
+
const exitAnim = exits.get(el);
|
|
251
|
+
if (exitAnim) {
|
|
252
|
+
exits.delete(el);
|
|
253
|
+
exitAnim.cancel();
|
|
254
|
+
}
|
|
255
|
+
const anim = running.get(el);
|
|
256
|
+
if (anim) {
|
|
257
|
+
running.delete(el);
|
|
258
|
+
anim.onfinish = null;
|
|
259
|
+
anim.cancel();
|
|
260
|
+
}
|
|
261
|
+
const timer = timers.get(el);
|
|
262
|
+
if (timer !== undefined) {
|
|
263
|
+
clearTimeout(timer);
|
|
264
|
+
timers.delete(el);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function setStyles(el, styles) {
|
|
268
|
+
const style = el.style;
|
|
269
|
+
if (!style)
|
|
270
|
+
return;
|
|
271
|
+
Object.keys(styles).forEach((prop) => {
|
|
272
|
+
style[prop] = String(styles[prop]);
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
const NUM_UNIT = /(-?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?)([a-z%]*)/gi;
|
|
276
|
+
/**
|
|
277
|
+
* Interpolate two CSS values at `t`. Numbers interpolate directly; strings
|
|
278
|
+
* interpolate when they share the same structure (e.g. `translateY(40px)` ->
|
|
279
|
+
* `translateY(0px)`, or `scale(0.8)` -> `scale(1)`). Otherwise snaps at 0.5.
|
|
280
|
+
* @internal
|
|
281
|
+
*/
|
|
282
|
+
function interpolateValue(from, to, t) {
|
|
283
|
+
if (typeof from === 'number' && typeof to === 'number')
|
|
284
|
+
return from + (to - from) * t;
|
|
285
|
+
const a = String(from);
|
|
286
|
+
const b = String(to);
|
|
287
|
+
const ta = [];
|
|
288
|
+
const tb = [];
|
|
289
|
+
const skA = a.replace(NUM_UNIT, (_, n, u) => (ta.push([parseFloat(n), u]), '#'));
|
|
290
|
+
const skB = b.replace(NUM_UNIT, (_, n, u) => (tb.push([parseFloat(n), u]), '#'));
|
|
291
|
+
if (skA !== skB || ta.length !== tb.length)
|
|
292
|
+
return t < 0.5 ? from : to;
|
|
293
|
+
let i = 0;
|
|
294
|
+
let ok = true;
|
|
295
|
+
const out = skA.replace(/#/g, () => {
|
|
296
|
+
const [na, ua] = ta[i];
|
|
297
|
+
const [nb, ub] = tb[i++];
|
|
298
|
+
if (ua !== ub && na !== 0 && nb !== 0 && ua && ub)
|
|
299
|
+
ok = false;
|
|
300
|
+
const unit = ua || ub;
|
|
301
|
+
return `${+(na + (nb - na) * t).toFixed(4)}${unit}`;
|
|
302
|
+
});
|
|
303
|
+
return ok ? out : t < 0.5 ? from : to;
|
|
304
|
+
}
|
|
305
|
+
const EASING_SAMPLES = 30;
|
|
306
|
+
let linearSupported;
|
|
307
|
+
function supportsLinearEasing() {
|
|
308
|
+
if (linearSupported === undefined) {
|
|
309
|
+
linearSupported =
|
|
310
|
+
typeof CSS !== 'undefined' &&
|
|
311
|
+
typeof CSS.supports === 'function' &&
|
|
312
|
+
CSS.supports('animation-timing-function', 'linear(0, 1)');
|
|
313
|
+
}
|
|
314
|
+
return linearSupported;
|
|
315
|
+
}
|
|
316
|
+
function sampleEasing(fn) {
|
|
317
|
+
const values = [];
|
|
318
|
+
for (let i = 0; i <= EASING_SAMPLES; i++) {
|
|
319
|
+
const v = fn(i / EASING_SAMPLES);
|
|
320
|
+
values.push(Number.isFinite(v) ? +v.toFixed(4) : i / EASING_SAMPLES);
|
|
321
|
+
}
|
|
322
|
+
return values;
|
|
323
|
+
}
|
|
324
|
+
function buildAnimation(preset, easing) {
|
|
325
|
+
// `frames`: intermediate keyframes (overshoot, bounce, glitch …) between from and to
|
|
326
|
+
const list = [preset.from, ...(preset.frames || []), preset.to];
|
|
327
|
+
if (typeof easing !== 'function') {
|
|
328
|
+
return { keyframes: list, easing: presets.resolveEasing(easing) };
|
|
329
|
+
}
|
|
330
|
+
const samples = sampleEasing(easing);
|
|
331
|
+
// Modern browsers: an exact, property-agnostic `linear()` easing curve.
|
|
332
|
+
if (supportsLinearEasing() || preset.frames) {
|
|
333
|
+
return { keyframes: list, easing: supportsLinearEasing() ? `linear(${samples.join(', ')})` : 'ease' };
|
|
334
|
+
}
|
|
335
|
+
// Fallback: approximate the curve with interpolated keyframes.
|
|
336
|
+
const props = Object.keys(preset.from).filter((p) => p in preset.to);
|
|
337
|
+
const keyframes = samples.map((eased, i) => {
|
|
338
|
+
const frame = { offset: i / EASING_SAMPLES };
|
|
339
|
+
props.forEach((prop) => {
|
|
340
|
+
frame[prop] = interpolateValue(preset.from[prop], preset.to[prop], eased);
|
|
341
|
+
});
|
|
342
|
+
return frame;
|
|
343
|
+
});
|
|
344
|
+
return { keyframes, easing: 'linear' };
|
|
345
|
+
}
|
|
346
|
+
/** Make an element visible without animating (reduced motion / disabled / no WAAPI). */
|
|
347
|
+
function reveal(el, config) {
|
|
348
|
+
cancelRunning(el);
|
|
349
|
+
if (config.useClassNames) {
|
|
350
|
+
el.classList.remove(config.hiddenClass);
|
|
351
|
+
el.classList.add(config.visibleClass);
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
const style = el.style;
|
|
355
|
+
if (style)
|
|
356
|
+
style.opacity = '';
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/** @internal Cancel a running/pending animation and make the element visible. */
|
|
360
|
+
function stopAnimation(el, config = {}) {
|
|
361
|
+
reveal(el, { ...DEFAULT_CONFIG, ...config });
|
|
362
|
+
}
|
|
363
|
+
/** @internal Whether animations should be skipped entirely. */
|
|
364
|
+
function motionDisabled(config) {
|
|
365
|
+
return !!config.disabled || prefersReducedMotion();
|
|
366
|
+
}
|
|
367
|
+
function runAnimation(el, opts, config, staggerIndex = 0, pending) {
|
|
368
|
+
const { duration, delay, stagger, onStart, onComplete } = opts;
|
|
369
|
+
const totalDelay = Math.max(0, delay + staggerIndex * stagger);
|
|
370
|
+
cancelRunning(el);
|
|
371
|
+
if (motionDisabled(config)) {
|
|
372
|
+
reveal(el, config);
|
|
373
|
+
onStart(el);
|
|
374
|
+
onComplete(el);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (config.useClassNames) {
|
|
378
|
+
if (totalDelay > 0)
|
|
379
|
+
el.style.animationDelay = `${totalDelay}ms`;
|
|
380
|
+
el.classList.remove(config.hiddenClass);
|
|
381
|
+
el.classList.add(config.visibleClass);
|
|
382
|
+
onStart(el);
|
|
383
|
+
pending?.add(el);
|
|
384
|
+
timers.set(el, setTimeout(() => {
|
|
385
|
+
timers.delete(el);
|
|
386
|
+
pending?.delete(el);
|
|
387
|
+
onComplete(el);
|
|
388
|
+
}, duration + totalDelay));
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const preset = presets.resolvePreset(opts.animation);
|
|
392
|
+
// Presets that don't animate opacity (slide-*, clip-*, scale-x, ...) would
|
|
393
|
+
// otherwise stay at the `opacity: 0` applied while waiting to enter.
|
|
394
|
+
if (!('opacity' in preset.to)) {
|
|
395
|
+
const style = el.style;
|
|
396
|
+
if (style && style.opacity === '0')
|
|
397
|
+
style.opacity = '';
|
|
398
|
+
}
|
|
399
|
+
if (typeof el.animate !== 'function') {
|
|
400
|
+
setStyles(el, preset.to);
|
|
401
|
+
onStart(el);
|
|
402
|
+
onComplete(el);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const built = buildAnimation(preset, opts.easing);
|
|
406
|
+
const timing = { duration, delay: totalDelay, easing: built.easing, fill: 'both' };
|
|
407
|
+
let anim;
|
|
408
|
+
try {
|
|
409
|
+
anim = el.animate(built.keyframes, timing);
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
// Invalid user easing string (WAAPI throws a TypeError): fall back to 'ease'
|
|
413
|
+
anim = el.animate(built.keyframes, { ...timing, easing: 'ease' });
|
|
414
|
+
}
|
|
415
|
+
running.set(el, anim);
|
|
416
|
+
onStart(el);
|
|
417
|
+
anim.onfinish = () => {
|
|
418
|
+
if (running.get(el) === anim) {
|
|
419
|
+
running.delete(el);
|
|
420
|
+
// Persist the end state inline and drop the filling animation. This frees
|
|
421
|
+
// the Animation object and lets later inline styles (parallax, user code)
|
|
422
|
+
// take effect instead of being masked by `fill: forwards`.
|
|
423
|
+
try {
|
|
424
|
+
anim.commitStyles();
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
setStyles(el, preset.to);
|
|
428
|
+
}
|
|
429
|
+
anim.cancel();
|
|
430
|
+
}
|
|
431
|
+
onComplete(el);
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Native engine: attach the preset to a `ViewTimeline` of the element, so the
|
|
436
|
+
* browser drives it from scroll position. Returns `null` when that fails
|
|
437
|
+
* (caller falls back to the JS engine).
|
|
438
|
+
*/
|
|
439
|
+
function startNative(el, opts, onFrozen) {
|
|
440
|
+
if (typeof el.animate !== 'function')
|
|
441
|
+
return null;
|
|
442
|
+
const preset = presets.resolvePreset(opts.animation);
|
|
443
|
+
const built = buildAnimation(preset, opts.easing);
|
|
444
|
+
let anim;
|
|
445
|
+
try {
|
|
446
|
+
const timeline = new globalThis.ViewTimeline({ subject: el, axis: 'block' });
|
|
447
|
+
const timing = {
|
|
448
|
+
fill: 'both',
|
|
449
|
+
easing: built.easing,
|
|
450
|
+
timeline,
|
|
451
|
+
rangeStart: opts.viewRange[0],
|
|
452
|
+
rangeEnd: opts.viewRange[1],
|
|
453
|
+
};
|
|
454
|
+
try {
|
|
455
|
+
anim = el.animate(built.keyframes, timing);
|
|
456
|
+
}
|
|
457
|
+
catch {
|
|
458
|
+
anim = el.animate(built.keyframes, { ...timing, easing: 'linear' });
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
running.set(el, anim);
|
|
465
|
+
if (opts.exit) {
|
|
466
|
+
// Reverse keyframes over the `exit` range; `fill: 'forwards'` so it has
|
|
467
|
+
// no effect before the element starts leaving.
|
|
468
|
+
const leave = presets.resolvePreset(opts.exit === true ? opts.animation : opts.exit);
|
|
469
|
+
const out = buildAnimation(presets.reversePreset(leave), opts.easing);
|
|
470
|
+
try {
|
|
471
|
+
const timeline = new globalThis.ViewTimeline({ subject: el, axis: 'block' });
|
|
472
|
+
const timing = { fill: 'forwards', easing: out.easing, timeline, rangeStart: 'exit 0%', rangeEnd: 'exit 100%' };
|
|
473
|
+
exits.set(el, el.animate(out.keyframes, timing));
|
|
474
|
+
}
|
|
475
|
+
catch {
|
|
476
|
+
// Exit is cosmetic: ignore if unsupported.
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
const keep = opts.repeat || !opts.once;
|
|
480
|
+
anim.onfinish = () => {
|
|
481
|
+
// `once`: freeze the end state, so scrolling back up does not reverse it.
|
|
482
|
+
if (!keep && running.get(el) === anim) {
|
|
483
|
+
running.delete(el);
|
|
484
|
+
try {
|
|
485
|
+
anim.commitStyles();
|
|
486
|
+
}
|
|
487
|
+
catch {
|
|
488
|
+
setStyles(el, preset.to);
|
|
489
|
+
}
|
|
490
|
+
anim.cancel();
|
|
491
|
+
onFrozen?.();
|
|
492
|
+
}
|
|
493
|
+
opts.onComplete(el);
|
|
494
|
+
};
|
|
495
|
+
return anim;
|
|
496
|
+
}
|
|
497
|
+
/** Play the exit animation (entrance or `exit` preset, reversed), ending hidden. */
|
|
498
|
+
function runExit(el, opts, config) {
|
|
499
|
+
if (config.useClassNames || typeof el.animate !== 'function') {
|
|
500
|
+
hideElement(el, config);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
cancelRunning(el);
|
|
504
|
+
const preset = presets.resolvePreset(opts.exit === true ? opts.animation : opts.exit);
|
|
505
|
+
const built = buildAnimation(presets.reversePreset(preset), opts.easing);
|
|
506
|
+
const timing = { duration: opts.duration, easing: built.easing, fill: 'forwards' };
|
|
507
|
+
let anim;
|
|
508
|
+
try {
|
|
509
|
+
anim = el.animate(built.keyframes, timing);
|
|
510
|
+
}
|
|
511
|
+
catch {
|
|
512
|
+
anim = el.animate(built.keyframes, { ...timing, easing: 'ease' });
|
|
513
|
+
}
|
|
514
|
+
// Kept (filling) until the next entrance cancels it.
|
|
515
|
+
running.set(el, anim);
|
|
516
|
+
}
|
|
517
|
+
function hideElement(el, config) {
|
|
518
|
+
cancelRunning(el);
|
|
519
|
+
if (config.useClassNames) {
|
|
520
|
+
el.classList.add(config.hiddenClass);
|
|
521
|
+
el.classList.remove(config.visibleClass);
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
const style = el.style;
|
|
525
|
+
if (style)
|
|
526
|
+
style.opacity = '0';
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
/** @internal Hide an element before its entrance animation, unless motion is off. */
|
|
530
|
+
function prepareElement(el, config = {}) {
|
|
531
|
+
const full = { ...DEFAULT_CONFIG, ...config };
|
|
532
|
+
if (!motionDisabled(full))
|
|
533
|
+
hideElement(el, full);
|
|
534
|
+
}
|
|
535
|
+
let progressThresholds;
|
|
536
|
+
function getProgressThresholds() {
|
|
537
|
+
if (!progressThresholds) {
|
|
538
|
+
progressThresholds = [];
|
|
539
|
+
for (let i = 0; i <= 100; i++)
|
|
540
|
+
progressThresholds.push(i / 100);
|
|
541
|
+
}
|
|
542
|
+
return progressThresholds;
|
|
543
|
+
}
|
|
544
|
+
const PASSIVE = { passive: true };
|
|
545
|
+
/* ------------------------------------------------------------------ */
|
|
546
|
+
/* Instance */
|
|
547
|
+
/* ------------------------------------------------------------------ */
|
|
548
|
+
function createScrollAnimate(userConfig = {}) {
|
|
549
|
+
let config = { ...DEFAULT_CONFIG, ...userConfig };
|
|
550
|
+
const registry = new Map();
|
|
551
|
+
// `once` elements that finished and were dropped from the registry (autoUnregister).
|
|
552
|
+
let finished = new WeakSet();
|
|
553
|
+
// Elements in `progressMode: 'scroll'` that are currently inside the viewport.
|
|
554
|
+
const scrolling = new Set();
|
|
555
|
+
let frame = 0;
|
|
556
|
+
let listening = null;
|
|
557
|
+
// Active watch() MutationObservers, disconnected by destroy().
|
|
558
|
+
const watchers = new Set();
|
|
559
|
+
// Elements with a pending class-name completion timer started by this instance.
|
|
560
|
+
const pending = new Set();
|
|
561
|
+
// Elements whose entrance runs on a native scroll-driven timeline.
|
|
562
|
+
const natives = new Set();
|
|
563
|
+
// Observers are shared between elements with the same root/threshold/rootMargin,
|
|
564
|
+
// instead of one (or two) IntersectionObservers per element.
|
|
565
|
+
const pools = new Map();
|
|
566
|
+
function pooled(root, key, create) {
|
|
567
|
+
let pool = pools.get(root);
|
|
568
|
+
if (!pool)
|
|
569
|
+
pools.set(root, (pool = new Map()));
|
|
570
|
+
let io = pool.get(key);
|
|
571
|
+
if (!io)
|
|
572
|
+
pool.set(key, (io = create()));
|
|
573
|
+
return io;
|
|
574
|
+
}
|
|
575
|
+
function teardown(el, restore) {
|
|
576
|
+
const record = registry.get(el);
|
|
577
|
+
if (!record)
|
|
578
|
+
return;
|
|
579
|
+
record.observer.unobserve(el);
|
|
580
|
+
record.progressObserver?.unobserve(el);
|
|
581
|
+
registry.delete(el);
|
|
582
|
+
untrack(el);
|
|
583
|
+
if (record.engine === 'css') {
|
|
584
|
+
// A scroll-linked animation would keep following the scroll: stop it and
|
|
585
|
+
// show the element (unless it finished and is just being released).
|
|
586
|
+
if (restore) {
|
|
587
|
+
natives.delete(el);
|
|
588
|
+
reveal(el, config);
|
|
589
|
+
}
|
|
590
|
+
else if (el.isConnected === false) {
|
|
591
|
+
// Left the DOM: drop it too, or the instance keeps it alive (and
|
|
592
|
+
// destroy() touches it) for its whole lifetime.
|
|
593
|
+
natives.delete(el);
|
|
594
|
+
}
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
// An element that never animated would otherwise stay invisible forever.
|
|
598
|
+
if (restore && !record.animated)
|
|
599
|
+
reveal(el, config);
|
|
600
|
+
}
|
|
601
|
+
function pruneDetached() {
|
|
602
|
+
registry.forEach((record, el) => {
|
|
603
|
+
if (el.isConnected === false)
|
|
604
|
+
teardown(el, false);
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
function emitProgress(el, record, progress) {
|
|
608
|
+
const opts = record.options;
|
|
609
|
+
opts.onProgress(el, progress);
|
|
610
|
+
if (opts.progressVar) {
|
|
611
|
+
const style = el.style;
|
|
612
|
+
if (style)
|
|
613
|
+
style.setProperty(opts.progressVar, String(+progress.toFixed(4)));
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
function update() {
|
|
617
|
+
frame = 0;
|
|
618
|
+
scrolling.forEach((el) => {
|
|
619
|
+
const record = registry.get(el);
|
|
620
|
+
if (record)
|
|
621
|
+
emitProgress(el, record, getScrollProgress(el, config.root));
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
// rAF-throttled; IntersectionObserver-capable browsers all have rAF.
|
|
625
|
+
const schedule = () => frame || (frame = requestAnimationFrame(update));
|
|
626
|
+
function listen(on) {
|
|
627
|
+
if (on === !!listening)
|
|
628
|
+
return;
|
|
629
|
+
const method = on ? 'addEventListener' : 'removeEventListener';
|
|
630
|
+
const target = listening || config.root || window;
|
|
631
|
+
target[method]('scroll', schedule, PASSIVE);
|
|
632
|
+
window[method]('resize', schedule, PASSIVE);
|
|
633
|
+
listening = on ? target : null;
|
|
634
|
+
if (!on && frame) {
|
|
635
|
+
cancelAnimationFrame(frame);
|
|
636
|
+
frame = 0;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
function untrack(el) {
|
|
640
|
+
if (scrolling.delete(el) && !scrolling.size)
|
|
641
|
+
listen(false);
|
|
642
|
+
}
|
|
643
|
+
function onScrollIntersect(entries) {
|
|
644
|
+
entries.forEach((entry) => {
|
|
645
|
+
const el = entry.target;
|
|
646
|
+
const record = registry.get(el);
|
|
647
|
+
if (!record)
|
|
648
|
+
return;
|
|
649
|
+
if (entry.isIntersecting) {
|
|
650
|
+
scrolling.add(el);
|
|
651
|
+
listen(true);
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
untrack(el);
|
|
655
|
+
}
|
|
656
|
+
// Emit right away so the edges (0 / 1) are reported even on fast scrolls.
|
|
657
|
+
emitProgress(el, record, getScrollProgress(el, config.root));
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
function onIntersect(entries) {
|
|
661
|
+
const staggerCounts = new Map();
|
|
662
|
+
entries.forEach((entry) => {
|
|
663
|
+
const el = entry.target;
|
|
664
|
+
const record = registry.get(el);
|
|
665
|
+
if (!record)
|
|
666
|
+
return;
|
|
667
|
+
const opts = record.options;
|
|
668
|
+
if (el.isConnected === false) {
|
|
669
|
+
teardown(el, false);
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
if (record.engine === 'css') {
|
|
673
|
+
// The browser drives the animation; only lifecycle bookkeeping here.
|
|
674
|
+
if (entry.isIntersecting) {
|
|
675
|
+
opts.onEnter(el);
|
|
676
|
+
if (!record.animated) {
|
|
677
|
+
record.animated = true;
|
|
678
|
+
opts.onStart(el);
|
|
679
|
+
}
|
|
680
|
+
if (opts.once && !opts.repeat) {
|
|
681
|
+
record.observer.unobserve(el);
|
|
682
|
+
if (config.autoUnregister && !record.progressObserver) {
|
|
683
|
+
finished.add(el);
|
|
684
|
+
teardown(el, false);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
opts.onLeave(el);
|
|
690
|
+
if (opts.repeat)
|
|
691
|
+
record.animated = false;
|
|
692
|
+
}
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
if (entry.isIntersecting) {
|
|
696
|
+
opts.onEnter(el);
|
|
697
|
+
if (record.animated && !opts.repeat)
|
|
698
|
+
return;
|
|
699
|
+
// Stagger relative to siblings revealed in the same batch, so elements
|
|
700
|
+
// scrolled into view later don't inherit an ever-growing delay.
|
|
701
|
+
let staggerIndex = 0;
|
|
702
|
+
if (opts.stagger > 0) {
|
|
703
|
+
const parent = el.parentElement;
|
|
704
|
+
staggerIndex = staggerCounts.get(parent) ?? 0;
|
|
705
|
+
staggerCounts.set(parent, staggerIndex + 1);
|
|
706
|
+
}
|
|
707
|
+
runAnimation(el, opts, config, staggerIndex, pending);
|
|
708
|
+
record.animated = true;
|
|
709
|
+
if (opts.once && !opts.repeat) {
|
|
710
|
+
// Keep the progress observer: parallax/onProgress must keep working.
|
|
711
|
+
record.observer.unobserve(el);
|
|
712
|
+
if (config.autoUnregister && !record.progressObserver) {
|
|
713
|
+
// Nothing left to watch: free the record (the running animation
|
|
714
|
+
// keeps its own reference until it finishes).
|
|
715
|
+
finished.add(el);
|
|
716
|
+
teardown(el, false);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
else {
|
|
721
|
+
opts.onLeave(el);
|
|
722
|
+
if (opts.repeat && record.animated) {
|
|
723
|
+
if (!motionDisabled(config)) {
|
|
724
|
+
if (opts.exit)
|
|
725
|
+
runExit(el, opts, config);
|
|
726
|
+
else
|
|
727
|
+
hideElement(el, config);
|
|
728
|
+
}
|
|
729
|
+
record.animated = false;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
function onProgress(entries) {
|
|
735
|
+
entries.forEach((entry) => {
|
|
736
|
+
const record = registry.get(entry.target);
|
|
737
|
+
if (!record)
|
|
738
|
+
return;
|
|
739
|
+
emitProgress(entry.target, record, entry.intersectionRatio);
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
function getObserver(opts) {
|
|
743
|
+
const rootMargin = applyOffset(opts.rootMargin, opts.offset);
|
|
744
|
+
const threshold = opts.threshold;
|
|
745
|
+
const root = config.root;
|
|
746
|
+
return pooled(root, `m|${rootMargin}|${String(threshold)}`, () => new IntersectionObserver(onIntersect, { threshold, rootMargin, root: root }));
|
|
747
|
+
}
|
|
748
|
+
function getProgressObserver(opts) {
|
|
749
|
+
const root = config.root;
|
|
750
|
+
if (opts.progressMode === 'scroll') {
|
|
751
|
+
// Only used to know when to start/stop measuring; progress itself comes
|
|
752
|
+
// from a single shared, rAF-throttled passive scroll listener.
|
|
753
|
+
return pooled(root, `s|${opts.rootMargin}`, () => new IntersectionObserver(onScrollIntersect, { threshold: 0, rootMargin: opts.rootMargin, root: root }));
|
|
754
|
+
}
|
|
755
|
+
return pooled(root, `p|${opts.rootMargin}`, () => new IntersectionObserver(onProgress, {
|
|
756
|
+
threshold: getProgressThresholds(),
|
|
757
|
+
rootMargin: opts.rootMargin,
|
|
758
|
+
root: root,
|
|
759
|
+
}));
|
|
760
|
+
}
|
|
761
|
+
function attach(el, record, observeMain) {
|
|
762
|
+
record.observer = getObserver(record.options);
|
|
763
|
+
if (observeMain)
|
|
764
|
+
record.observer.observe(el);
|
|
765
|
+
record.progressObserver = needsProgress(record.options) ? getProgressObserver(record.options) : undefined;
|
|
766
|
+
record.progressObserver?.observe(el);
|
|
767
|
+
}
|
|
768
|
+
function wantsNative(opts) {
|
|
769
|
+
return opts.engine !== 'js' && !config.useClassNames && !motionDisabled(config) && supportsScrollTimeline();
|
|
770
|
+
}
|
|
771
|
+
function observeElement(el, opts) {
|
|
772
|
+
if (registry.has(el) || finished.has(el))
|
|
773
|
+
return;
|
|
774
|
+
if (!supportsObserver()) {
|
|
775
|
+
// No IntersectionObserver (very old browser): never leave content hidden.
|
|
776
|
+
reveal(el, config);
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
const record = { element: el, options: opts, animated: false, engine: 'js' };
|
|
780
|
+
if (wantsNative(opts)) {
|
|
781
|
+
cancelRunning(el);
|
|
782
|
+
if (startNative(el, opts, () => natives.delete(el))) {
|
|
783
|
+
record.engine = 'css';
|
|
784
|
+
natives.add(el);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
if (record.engine === 'js' && !motionDisabled(config))
|
|
788
|
+
hideElement(el, config);
|
|
789
|
+
registry.set(el, record);
|
|
790
|
+
attach(el, record, true);
|
|
791
|
+
}
|
|
792
|
+
function observeDataElement(el) {
|
|
793
|
+
if (!registry.has(el))
|
|
794
|
+
observeElement(el, parseDataAttributes(el, config));
|
|
795
|
+
}
|
|
796
|
+
const instance = {
|
|
797
|
+
observe(target, options = {}) {
|
|
798
|
+
pruneDetached();
|
|
799
|
+
const opts = mergeOptions(options, config);
|
|
800
|
+
resolveTargets(target).forEach((el) => observeElement(el, opts));
|
|
801
|
+
},
|
|
802
|
+
unobserve(target) {
|
|
803
|
+
resolveTargets(target).forEach((el) => {
|
|
804
|
+
finished.delete(el);
|
|
805
|
+
teardown(el, true);
|
|
806
|
+
});
|
|
807
|
+
},
|
|
808
|
+
init(rootElement) {
|
|
809
|
+
const scope = rootElement ?? (hasDOM() ? document : null);
|
|
810
|
+
if (!scope)
|
|
811
|
+
return;
|
|
812
|
+
pruneDetached();
|
|
813
|
+
scope.querySelectorAll('[data-sa]').forEach(observeDataElement);
|
|
814
|
+
},
|
|
815
|
+
watch(rootElement) {
|
|
816
|
+
const scope = rootElement ?? (hasDOM() ? document : null);
|
|
817
|
+
if (!scope || typeof MutationObserver === 'undefined')
|
|
818
|
+
return noop;
|
|
819
|
+
instance.init(scope);
|
|
820
|
+
const observeTree = (node) => {
|
|
821
|
+
if (node.hasAttribute('data-sa'))
|
|
822
|
+
observeDataElement(node);
|
|
823
|
+
node.querySelectorAll('[data-sa]').forEach(observeDataElement);
|
|
824
|
+
};
|
|
825
|
+
const mo = new MutationObserver((records) => {
|
|
826
|
+
let removed = false;
|
|
827
|
+
records.forEach((record) => {
|
|
828
|
+
if (record.type === 'attributes') {
|
|
829
|
+
const target = record.target;
|
|
830
|
+
if (target.isConnected !== false)
|
|
831
|
+
observeTree(target);
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
record.addedNodes.forEach((node) => {
|
|
835
|
+
if (node instanceof Element && node.isConnected !== false)
|
|
836
|
+
observeTree(node);
|
|
837
|
+
});
|
|
838
|
+
if (record.removedNodes.length)
|
|
839
|
+
removed = true;
|
|
840
|
+
});
|
|
841
|
+
// Free elements that left the DOM (they can't animate any more).
|
|
842
|
+
if (removed)
|
|
843
|
+
pruneDetached();
|
|
844
|
+
});
|
|
845
|
+
mo.observe(scope, { childList: true, subtree: true, attributes: true, attributeFilter: ['data-sa'] });
|
|
846
|
+
watchers.add(mo);
|
|
847
|
+
return () => {
|
|
848
|
+
mo.disconnect();
|
|
849
|
+
watchers.delete(mo);
|
|
850
|
+
};
|
|
851
|
+
},
|
|
852
|
+
destroy() {
|
|
853
|
+
watchers.forEach((mo) => mo.disconnect());
|
|
854
|
+
watchers.clear();
|
|
855
|
+
// Class-name mode: drop completion timers so onComplete never fires after destroy().
|
|
856
|
+
pending.forEach((el) => {
|
|
857
|
+
const timer = timers.get(el);
|
|
858
|
+
if (timer !== undefined) {
|
|
859
|
+
clearTimeout(timer);
|
|
860
|
+
timers.delete(el);
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
pending.clear();
|
|
864
|
+
registry.forEach((record, el) => {
|
|
865
|
+
if (!record.animated && record.engine !== 'css')
|
|
866
|
+
reveal(el, config);
|
|
867
|
+
});
|
|
868
|
+
// Native scroll-linked animations still running: stop them, show content.
|
|
869
|
+
natives.forEach((el) => {
|
|
870
|
+
if (running.has(el))
|
|
871
|
+
reveal(el, config);
|
|
872
|
+
});
|
|
873
|
+
natives.clear();
|
|
874
|
+
pools.forEach((pool) => pool.forEach((io) => io.disconnect()));
|
|
875
|
+
pools.clear();
|
|
876
|
+
registry.clear();
|
|
877
|
+
scrolling.clear();
|
|
878
|
+
listen(false);
|
|
879
|
+
finished = new WeakSet();
|
|
880
|
+
},
|
|
881
|
+
refresh() {
|
|
882
|
+
// Rebuild observers (e.g. after configure({ root })) without re-hiding or
|
|
883
|
+
// replaying elements that have already animated.
|
|
884
|
+
pools.forEach((pool) => pool.forEach((io) => io.disconnect()));
|
|
885
|
+
pools.clear();
|
|
886
|
+
scrolling.clear();
|
|
887
|
+
listen(false);
|
|
888
|
+
pruneDetached();
|
|
889
|
+
if (!supportsObserver())
|
|
890
|
+
return;
|
|
891
|
+
registry.forEach((record, el) => {
|
|
892
|
+
const done = record.animated && record.options.once && !record.options.repeat;
|
|
893
|
+
attach(el, record, !done);
|
|
894
|
+
});
|
|
895
|
+
},
|
|
896
|
+
animate(target, options = {}) {
|
|
897
|
+
const opts = mergeOptions(options, config);
|
|
898
|
+
resolveTargets(target).forEach((el) => runAnimation(el, opts, config, 0, pending));
|
|
899
|
+
},
|
|
900
|
+
getObservedElements() {
|
|
901
|
+
return Array.from(registry.values());
|
|
902
|
+
},
|
|
903
|
+
configure(newConfig) {
|
|
904
|
+
config = { ...config, ...newConfig };
|
|
905
|
+
},
|
|
906
|
+
};
|
|
907
|
+
return instance;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
exports.createScrollAnimate = createScrollAnimate;
|
|
911
|
+
exports.getScrollProgress = getScrollProgress;
|
|
912
|
+
exports.hasDOM = hasDOM;
|
|
913
|
+
exports.prefersReducedMotion = prefersReducedMotion;
|
|
914
|
+
exports.prepareElement = prepareElement;
|
|
915
|
+
exports.readOptions = readOptions;
|
|
916
|
+
exports.resolveTargets = resolveTargets;
|
|
917
|
+
exports.stopAnimation = stopAnimation;
|
|
918
|
+
exports.supportsObserver = supportsObserver;
|
|
919
|
+
exports.supportsScrollTimeline = supportsScrollTimeline;
|
|
920
|
+
//# sourceMappingURL=core-B5T0dhFH.cjs.map
|