animejs 4.2.2 → 4.3.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -28
- package/dist/bundles/anime.esm.js +2705 -1303
- package/dist/bundles/anime.esm.min.js +2 -2
- package/dist/bundles/anime.umd.js +2710 -1306
- package/dist/bundles/anime.umd.min.js +2 -2
- package/dist/modules/animatable/animatable.cjs +1 -1
- package/dist/modules/animatable/animatable.js +1 -1
- package/dist/modules/animatable/index.cjs +1 -1
- package/dist/modules/animatable/index.js +1 -1
- package/dist/modules/animation/additive.cjs +1 -1
- package/dist/modules/animation/additive.js +1 -1
- package/dist/modules/animation/animation.cjs +18 -9
- package/dist/modules/animation/animation.js +19 -10
- package/dist/modules/animation/composition.cjs +1 -1
- package/dist/modules/animation/composition.js +1 -1
- package/dist/modules/animation/index.cjs +1 -1
- package/dist/modules/animation/index.js +1 -1
- package/dist/modules/core/clock.cjs +10 -10
- package/dist/modules/core/clock.d.ts +1 -1
- package/dist/modules/core/clock.js +10 -10
- package/dist/modules/core/colors.cjs +1 -1
- package/dist/modules/core/colors.js +1 -1
- package/dist/modules/core/consts.cjs +6 -4
- package/dist/modules/core/consts.d.ts +13 -5
- package/dist/modules/core/consts.js +6 -4
- package/dist/modules/core/globals.cjs +5 -2
- package/dist/modules/core/globals.d.ts +1 -0
- package/dist/modules/core/globals.js +5 -3
- package/dist/modules/core/helpers.cjs +1 -1
- package/dist/modules/core/helpers.js +1 -1
- package/dist/modules/core/render.cjs +1 -1
- package/dist/modules/core/render.js +1 -1
- package/dist/modules/core/styles.cjs +1 -1
- package/dist/modules/core/styles.js +1 -1
- package/dist/modules/core/targets.cjs +1 -1
- package/dist/modules/core/targets.js +1 -1
- package/dist/modules/core/transforms.cjs +1 -1
- package/dist/modules/core/transforms.js +1 -1
- package/dist/modules/core/units.cjs +1 -1
- package/dist/modules/core/units.js +1 -1
- package/dist/modules/core/values.cjs +1 -1
- package/dist/modules/core/values.js +1 -1
- package/dist/modules/draggable/draggable.cjs +1 -1
- package/dist/modules/draggable/draggable.js +1 -1
- package/dist/modules/draggable/index.cjs +1 -1
- package/dist/modules/draggable/index.js +1 -1
- package/dist/modules/easings/cubic-bezier/index.cjs +1 -1
- package/dist/modules/easings/cubic-bezier/index.js +1 -1
- package/dist/modules/easings/eases/index.cjs +1 -1
- package/dist/modules/easings/eases/index.js +1 -1
- package/dist/modules/easings/eases/parser.cjs +1 -1
- package/dist/modules/easings/eases/parser.js +1 -1
- package/dist/modules/easings/index.cjs +1 -1
- package/dist/modules/easings/index.js +1 -1
- package/dist/modules/easings/irregular/index.cjs +1 -1
- package/dist/modules/easings/irregular/index.js +1 -1
- package/dist/modules/easings/linear/index.cjs +1 -1
- package/dist/modules/easings/linear/index.js +1 -1
- package/dist/modules/easings/none.cjs +1 -1
- package/dist/modules/easings/none.js +1 -1
- package/dist/modules/easings/spring/index.cjs +1 -1
- package/dist/modules/easings/spring/index.js +1 -1
- package/dist/modules/easings/steps/index.cjs +1 -1
- package/dist/modules/easings/steps/index.js +1 -1
- package/dist/modules/engine/engine.cjs +2 -2
- package/dist/modules/engine/engine.js +2 -2
- package/dist/modules/engine/index.cjs +1 -1
- package/dist/modules/engine/index.js +1 -1
- package/dist/modules/events/index.cjs +1 -1
- package/dist/modules/events/index.js +1 -1
- package/dist/modules/events/scroll.cjs +1 -1
- package/dist/modules/events/scroll.js +1 -1
- package/dist/modules/index.cjs +4 -1
- package/dist/modules/index.d.ts +1 -0
- package/dist/modules/index.js +2 -1
- package/dist/modules/layout/index.cjs +15 -0
- package/dist/modules/layout/index.d.ts +1 -0
- package/dist/modules/layout/index.js +8 -0
- package/dist/modules/layout/layout.cjs +1384 -0
- package/dist/modules/layout/layout.d.ts +211 -0
- package/dist/modules/layout/layout.js +1381 -0
- package/dist/modules/scope/index.cjs +1 -1
- package/dist/modules/scope/index.js +1 -1
- package/dist/modules/scope/scope.cjs +1 -1
- package/dist/modules/scope/scope.js +1 -1
- package/dist/modules/svg/drawable.cjs +1 -1
- package/dist/modules/svg/drawable.js +1 -1
- package/dist/modules/svg/helpers.cjs +1 -1
- package/dist/modules/svg/helpers.js +1 -1
- package/dist/modules/svg/index.cjs +1 -1
- package/dist/modules/svg/index.js +1 -1
- package/dist/modules/svg/morphto.cjs +1 -1
- package/dist/modules/svg/morphto.js +1 -1
- package/dist/modules/svg/motionpath.cjs +1 -1
- package/dist/modules/svg/motionpath.js +1 -1
- package/dist/modules/text/index.cjs +1 -1
- package/dist/modules/text/index.js +1 -1
- package/dist/modules/text/split.cjs +23 -9
- package/dist/modules/text/split.js +23 -9
- package/dist/modules/timeline/index.cjs +1 -1
- package/dist/modules/timeline/index.js +1 -1
- package/dist/modules/timeline/position.cjs +1 -1
- package/dist/modules/timeline/position.js +1 -1
- package/dist/modules/timeline/timeline.cjs +14 -6
- package/dist/modules/timeline/timeline.d.ts +2 -0
- package/dist/modules/timeline/timeline.js +15 -7
- package/dist/modules/timer/index.cjs +1 -1
- package/dist/modules/timer/index.js +1 -1
- package/dist/modules/timer/timer.cjs +26 -13
- package/dist/modules/timer/timer.d.ts +1 -0
- package/dist/modules/timer/timer.js +27 -14
- package/dist/modules/types/index.d.ts +3 -1
- package/dist/modules/utils/chainable.cjs +1 -1
- package/dist/modules/utils/chainable.js +1 -1
- package/dist/modules/utils/index.cjs +1 -1
- package/dist/modules/utils/index.js +1 -1
- package/dist/modules/utils/number.cjs +1 -1
- package/dist/modules/utils/number.js +1 -1
- package/dist/modules/utils/random.cjs +1 -1
- package/dist/modules/utils/random.js +1 -1
- package/dist/modules/utils/stagger.cjs +1 -1
- package/dist/modules/utils/stagger.js +1 -1
- package/dist/modules/utils/target.cjs +1 -1
- package/dist/modules/utils/target.js +1 -1
- package/dist/modules/utils/time.cjs +1 -1
- package/dist/modules/utils/time.js +1 -1
- package/dist/modules/waapi/composition.cjs +1 -1
- package/dist/modules/waapi/composition.js +1 -1
- package/dist/modules/waapi/index.cjs +1 -1
- package/dist/modules/waapi/index.js +1 -1
- package/dist/modules/waapi/waapi.cjs +15 -7
- package/dist/modules/waapi/waapi.js +16 -8
- package/package.json +8 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Anime.js - ESM bundle
|
|
3
|
-
* @version v4.
|
|
3
|
+
* @version v4.3.0-beta.1
|
|
4
4
|
* @license MIT
|
|
5
5
|
* @copyright 2025 - Julian Garnier
|
|
6
6
|
*/
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
/** @typedef {JSAnimation|Timeline} Renderable */
|
|
38
38
|
/** @typedef {Timer|Renderable} Tickable */
|
|
39
39
|
/** @typedef {Timer&JSAnimation&Timeline} CallbackArgument */
|
|
40
|
-
/** @typedef {Animatable|Tickable|WAAPIAnimation|Draggable|ScrollObserver|TextSplitter|Scope} Revertible */
|
|
40
|
+
/** @typedef {Animatable|Tickable|WAAPIAnimation|Draggable|ScrollObserver|TextSplitter|Scope|AutoLayout} Revertible */
|
|
41
41
|
|
|
42
42
|
// Stagger types
|
|
43
43
|
|
|
@@ -361,6 +361,7 @@
|
|
|
361
361
|
* @typedef {Object} TimelineOptions
|
|
362
362
|
* @property {DefaultsParams} [defaults]
|
|
363
363
|
* @property {EasingParam} [playbackEase]
|
|
364
|
+
* @property {Boolean} [composition]
|
|
364
365
|
*/
|
|
365
366
|
|
|
366
367
|
/**
|
|
@@ -638,8 +639,10 @@
|
|
|
638
639
|
// TODO: Do we need to check if we're running inside a worker ?
|
|
639
640
|
const isBrowser = typeof window !== 'undefined';
|
|
640
641
|
|
|
641
|
-
/** @
|
|
642
|
-
|
|
642
|
+
/** @typedef {Window & {AnimeJS: Array} & {AnimeJSDevTools: any}|null} AnimeJSWindow
|
|
643
|
+
|
|
644
|
+
/** @type {AnimeJSWindow} */
|
|
645
|
+
const win = isBrowser ? /** @type {AnimeJSWindow} */(/** @type {unknown} */(window)) : null;
|
|
643
646
|
|
|
644
647
|
/** @type {Document|null} */
|
|
645
648
|
const doc = isBrowser ? document : null;
|
|
@@ -691,7 +694,7 @@ const proxyTargetSymbol = Symbol();
|
|
|
691
694
|
const minValue = 1e-11;
|
|
692
695
|
const maxValue = 1e12;
|
|
693
696
|
const K = 1e3;
|
|
694
|
-
const maxFps =
|
|
697
|
+
const maxFps = 240;
|
|
695
698
|
|
|
696
699
|
// Strings
|
|
697
700
|
|
|
@@ -796,7 +799,9 @@ const globals = {
|
|
|
796
799
|
tickThreshold: 200,
|
|
797
800
|
};
|
|
798
801
|
|
|
799
|
-
const
|
|
802
|
+
const devTools = isBrowser && win.AnimeJSDevTools;
|
|
803
|
+
|
|
804
|
+
const globalVersions = { version: '4.3.0-beta.1', engine: null };
|
|
800
805
|
|
|
801
806
|
if (isBrowser) {
|
|
802
807
|
if (!win.AnimeJS) win.AnimeJS = [];
|
|
@@ -1826,7 +1831,7 @@ class Clock {
|
|
|
1826
1831
|
/** @type {Number} */
|
|
1827
1832
|
this._currentTime = initTime;
|
|
1828
1833
|
/** @type {Number} */
|
|
1829
|
-
this.
|
|
1834
|
+
this._lastTickTime = initTime;
|
|
1830
1835
|
/** @type {Number} */
|
|
1831
1836
|
this._startTime = initTime;
|
|
1832
1837
|
/** @type {Number} */
|
|
@@ -1834,7 +1839,7 @@ class Clock {
|
|
|
1834
1839
|
/** @type {Number} */
|
|
1835
1840
|
this._scheduledTime = 0;
|
|
1836
1841
|
/** @type {Number} */
|
|
1837
|
-
this._frameDuration =
|
|
1842
|
+
this._frameDuration = K / maxFps;
|
|
1838
1843
|
/** @type {Number} */
|
|
1839
1844
|
this._fps = maxFps;
|
|
1840
1845
|
/** @type {Number} */
|
|
@@ -1855,7 +1860,8 @@ class Clock {
|
|
|
1855
1860
|
const previousFrameDuration = this._frameDuration;
|
|
1856
1861
|
const fr = +frameRate;
|
|
1857
1862
|
const fps = fr < minValue ? minValue : fr;
|
|
1858
|
-
const frameDuration =
|
|
1863
|
+
const frameDuration = K / fps;
|
|
1864
|
+
if (fps > defaults.frameRate) defaults.frameRate = fps;
|
|
1859
1865
|
this._fps = fps;
|
|
1860
1866
|
this._frameDuration = frameDuration;
|
|
1861
1867
|
this._scheduledTime += frameDuration - previousFrameDuration;
|
|
@@ -1876,14 +1882,13 @@ class Clock {
|
|
|
1876
1882
|
*/
|
|
1877
1883
|
requestTick(time) {
|
|
1878
1884
|
const scheduledTime = this._scheduledTime;
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
// If the elapsed time is lower than the scheduled time
|
|
1885
|
+
this._lastTickTime = time;
|
|
1886
|
+
// If the current time is lower than the scheduled time
|
|
1882
1887
|
// this means not enough time has passed to hit one frameDuration
|
|
1883
1888
|
// so skip that frame
|
|
1884
|
-
if (
|
|
1889
|
+
if (time < scheduledTime) return tickModes.NONE;
|
|
1885
1890
|
const frameDuration = this._frameDuration;
|
|
1886
|
-
const frameDelta =
|
|
1891
|
+
const frameDelta = time - scheduledTime;
|
|
1887
1892
|
// Ensures that _scheduledTime progresses in steps of at least 1 frameDuration.
|
|
1888
1893
|
// Skips ahead if the actual elapsed time is higher.
|
|
1889
1894
|
this._scheduledTime += frameDelta < frameDuration ? frameDuration : frameDelta;
|
|
@@ -2020,7 +2025,7 @@ class Engine extends Clock {
|
|
|
2020
2025
|
|
|
2021
2026
|
wake() {
|
|
2022
2027
|
if (this.useDefaultMainLoop && !this.reqId) {
|
|
2023
|
-
// Imediatly request a tick to update engine.
|
|
2028
|
+
// Imediatly request a tick to update engine._lastTickTime and get accurate offsetPosition calculation in timer.js
|
|
2024
2029
|
this.requestTick(now());
|
|
2025
2030
|
this.reqId = engineTickMethod(tickEngine);
|
|
2026
2031
|
}
|
|
@@ -2508,6 +2513,8 @@ class Timer extends Clock {
|
|
|
2508
2513
|
|
|
2509
2514
|
super(0);
|
|
2510
2515
|
|
|
2516
|
+
++timerId;
|
|
2517
|
+
|
|
2511
2518
|
const {
|
|
2512
2519
|
id,
|
|
2513
2520
|
delay,
|
|
@@ -2529,31 +2536,42 @@ class Timer extends Clock {
|
|
|
2529
2536
|
|
|
2530
2537
|
if (scope.current) scope.current.register(this);
|
|
2531
2538
|
|
|
2532
|
-
const timerInitTime = parent ? 0 : engine.
|
|
2539
|
+
const timerInitTime = parent ? 0 : engine._lastTickTime;
|
|
2533
2540
|
const timerDefaults = parent ? parent.defaults : globals.defaults;
|
|
2534
2541
|
const timerDelay = /** @type {Number} */(isFnc(delay) || isUnd(delay) ? timerDefaults.delay : +delay);
|
|
2535
2542
|
const timerDuration = isFnc(duration) || isUnd(duration) ? Infinity : +duration;
|
|
2536
2543
|
const timerLoop = setValue(loop, timerDefaults.loop);
|
|
2537
2544
|
const timerLoopDelay = setValue(loopDelay, timerDefaults.loopDelay);
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2545
|
+
let timerIterationCount = timerLoop === true ||
|
|
2546
|
+
timerLoop === Infinity ||
|
|
2547
|
+
/** @type {Number} */(timerLoop) < 0 ? Infinity :
|
|
2548
|
+
/** @type {Number} */(timerLoop) + 1;
|
|
2549
|
+
|
|
2550
|
+
if (devTools) {
|
|
2551
|
+
const isInfinite = timerIterationCount === Infinity;
|
|
2552
|
+
const registered = devTools.register(this, parameters, isInfinite);
|
|
2553
|
+
if (registered && isInfinite) {
|
|
2554
|
+
const minIterations = alternate ? 2 : 1;
|
|
2555
|
+
const iterations = parent ? devTools.maxNestedInfiniteLoops : devTools.maxInfiniteLoops;
|
|
2556
|
+
timerIterationCount = Math.max(iterations, minIterations);
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2542
2559
|
|
|
2543
2560
|
let offsetPosition = 0;
|
|
2544
2561
|
|
|
2545
2562
|
if (parent) {
|
|
2546
2563
|
offsetPosition = parentPosition;
|
|
2547
2564
|
} else {
|
|
2548
|
-
// Make sure to tick the engine once if not currently running to get up to date engine.
|
|
2565
|
+
// Make sure to tick the engine once if not currently running to get up to date engine._lastTickTime
|
|
2549
2566
|
// to avoid big gaps with the following offsetPosition calculation
|
|
2550
2567
|
if (!engine.reqId) engine.requestTick(now());
|
|
2551
2568
|
// Make sure to scale the offset position with globals.timeScale to properly handle seconds unit
|
|
2552
|
-
offsetPosition = (engine.
|
|
2569
|
+
offsetPosition = (engine._lastTickTime - engine._startTime) * globals.timeScale;
|
|
2553
2570
|
}
|
|
2554
2571
|
|
|
2555
2572
|
// Timer's parameters
|
|
2556
|
-
|
|
2573
|
+
/** @type {String|Number} */
|
|
2574
|
+
this.id = !isUnd(id) ? id : timerId;
|
|
2557
2575
|
/** @type {Timeline} */
|
|
2558
2576
|
this.parent = parent;
|
|
2559
2577
|
// Total duration of the timer
|
|
@@ -2613,7 +2631,7 @@ class Timer extends Clock {
|
|
|
2613
2631
|
|
|
2614
2632
|
// Clock's parameters
|
|
2615
2633
|
/** @type {Number} */
|
|
2616
|
-
this.
|
|
2634
|
+
this._lastTickTime = timerInitTime;
|
|
2617
2635
|
/** @type {Number} */
|
|
2618
2636
|
this._startTime = timerInitTime;
|
|
2619
2637
|
/** @type {Number} */
|
|
@@ -2644,7 +2662,7 @@ class Timer extends Clock {
|
|
|
2644
2662
|
}
|
|
2645
2663
|
|
|
2646
2664
|
get iterationCurrentTime() {
|
|
2647
|
-
return round$1(this._iterationTime, globals.precision);
|
|
2665
|
+
return clamp$1(round$1(this._iterationTime, globals.precision), 0, this.iterationDuration);
|
|
2648
2666
|
}
|
|
2649
2667
|
|
|
2650
2668
|
set iterationCurrentTime(time) {
|
|
@@ -2742,9 +2760,9 @@ class Timer extends Clock {
|
|
|
2742
2760
|
/** @return {this} */
|
|
2743
2761
|
resetTime() {
|
|
2744
2762
|
const timeScale = 1 / (this._speed * engine._speed);
|
|
2745
|
-
// TODO: See if we can safely use engine.
|
|
2763
|
+
// TODO: See if we can safely use engine._lastTickTime here
|
|
2746
2764
|
// if (!engine.reqId) engine.requestTick(now())
|
|
2747
|
-
// this._startTime = engine.
|
|
2765
|
+
// this._startTime = engine._lastTickTime - (this._currentTime + this._delay) * timeScale;
|
|
2748
2766
|
this._startTime = now() - (this._currentTime + this._delay) * timeScale;
|
|
2749
2767
|
return this;
|
|
2750
2768
|
}
|
|
@@ -3258,6 +3276,7 @@ const fastSetValuesArray = [null, null];
|
|
|
3258
3276
|
const keyObjectTarget = { to: null };
|
|
3259
3277
|
|
|
3260
3278
|
let tweenId = 0;
|
|
3279
|
+
let JSAnimationId = 0;
|
|
3261
3280
|
let keyframes;
|
|
3262
3281
|
/** @type {TweenParamsOptions & TweenValues} */
|
|
3263
3282
|
let key;
|
|
@@ -3372,6 +3391,8 @@ class JSAnimation extends Timer {
|
|
|
3372
3391
|
|
|
3373
3392
|
super(/** @type {TimerParams & AnimationParams} */(parameters), parent, parentPosition);
|
|
3374
3393
|
|
|
3394
|
+
++JSAnimationId;
|
|
3395
|
+
|
|
3375
3396
|
const parsedTargets = registerTargets(targets);
|
|
3376
3397
|
const targetsLength = parsedTargets.length;
|
|
3377
3398
|
|
|
@@ -3381,6 +3402,7 @@ class JSAnimation extends Timer {
|
|
|
3381
3402
|
const params = /** @type {AnimationParams} */(kfParams ? mergeObjects(generateKeyframes(/** @type {DurationKeyframes} */(kfParams), parameters), parameters) : parameters);
|
|
3382
3403
|
|
|
3383
3404
|
const {
|
|
3405
|
+
id,
|
|
3384
3406
|
delay,
|
|
3385
3407
|
duration,
|
|
3386
3408
|
ease,
|
|
@@ -3391,11 +3413,12 @@ class JSAnimation extends Timer {
|
|
|
3391
3413
|
} = params;
|
|
3392
3414
|
|
|
3393
3415
|
const animDefaults = parent ? parent.defaults : globals.defaults;
|
|
3394
|
-
const
|
|
3395
|
-
const
|
|
3396
|
-
const
|
|
3397
|
-
const
|
|
3398
|
-
const
|
|
3416
|
+
const animEase = setValue(ease, animDefaults.ease);
|
|
3417
|
+
const animPlaybackEase = setValue(playbackEase, animDefaults.playbackEase);
|
|
3418
|
+
const parsedAnimPlaybackEase = animPlaybackEase ? parseEase(animPlaybackEase) : null;
|
|
3419
|
+
const hasSpring = !isUnd(/** @type {Spring} */(animEase).ease);
|
|
3420
|
+
const tEasing = hasSpring ? /** @type {Spring} */(animEase).ease : setValue(ease, parsedAnimPlaybackEase ? 'linear' : animDefaults.ease);
|
|
3421
|
+
const tDuration = hasSpring ? /** @type {Spring} */(animEase).settlingDuration : setValue(duration, animDefaults.duration);
|
|
3399
3422
|
const tDelay = setValue(delay, animDefaults.delay);
|
|
3400
3423
|
const tModifier = modifier || animDefaults.modifier;
|
|
3401
3424
|
// If no composition is defined and the targets length is high (>= 1000) set the composition to 'none' (0) for faster tween creation
|
|
@@ -3403,7 +3426,7 @@ class JSAnimation extends Timer {
|
|
|
3403
3426
|
// const absoluteOffsetTime = this._offset;
|
|
3404
3427
|
const absoluteOffsetTime = this._offset + (parent ? parent._offset : 0);
|
|
3405
3428
|
// This allows targeting the current animation in the spring onComplete callback
|
|
3406
|
-
if (hasSpring) /** @type {Spring} */(
|
|
3429
|
+
if (hasSpring) /** @type {Spring} */(animEase).parent = this;
|
|
3407
3430
|
|
|
3408
3431
|
let iterationDuration = NaN;
|
|
3409
3432
|
let iterationDelay = NaN;
|
|
@@ -3547,6 +3570,8 @@ class JSAnimation extends Timer {
|
|
|
3547
3570
|
if (isFromToValue) {
|
|
3548
3571
|
decomposeRawValue(isFromToArray ? getFunctionValue(tweenToValue[0], target, ti, tl) : tweenFromValue, fromTargetObject);
|
|
3549
3572
|
decomposeRawValue(isFromToArray ? getFunctionValue(tweenToValue[1], target, ti, tl, toFunctionStore) : tweenToValue, toTargetObject);
|
|
3573
|
+
// Needed to force an inline style registration
|
|
3574
|
+
const originalValue = getOriginalAnimatableValue(target, propName, tweenType, inlineStylesStore);
|
|
3550
3575
|
if (fromTargetObject.t === valueTypes.NUMBER) {
|
|
3551
3576
|
if (prevSibling) {
|
|
3552
3577
|
if (prevSibling._valueType === valueTypes.UNIT) {
|
|
@@ -3555,7 +3580,7 @@ class JSAnimation extends Timer {
|
|
|
3555
3580
|
}
|
|
3556
3581
|
} else {
|
|
3557
3582
|
decomposeRawValue(
|
|
3558
|
-
|
|
3583
|
+
originalValue,
|
|
3559
3584
|
decomposedOriginalValue
|
|
3560
3585
|
);
|
|
3561
3586
|
if (decomposedOriginalValue.t === valueTypes.UNIT) {
|
|
@@ -3774,12 +3799,14 @@ class JSAnimation extends Timer {
|
|
|
3774
3799
|
}
|
|
3775
3800
|
/** @type {TargetsArray} */
|
|
3776
3801
|
this.targets = parsedTargets;
|
|
3802
|
+
/** @type {String|Number} */
|
|
3803
|
+
this.id = !isUnd(id) ? id : JSAnimationId;
|
|
3777
3804
|
/** @type {Number} */
|
|
3778
3805
|
this.duration = iterationDuration === minValue ? minValue : clampInfinity(((iterationDuration + this._loopDelay) * this.iterationCount) - this._loopDelay) || minValue;
|
|
3779
3806
|
/** @type {Callback<this>} */
|
|
3780
3807
|
this.onRender = onRender || animDefaults.onRender;
|
|
3781
3808
|
/** @type {EasingFunction} */
|
|
3782
|
-
this._ease =
|
|
3809
|
+
this._ease = parsedAnimPlaybackEase;
|
|
3783
3810
|
/** @type {Number} */
|
|
3784
3811
|
this._delay = iterationDelay;
|
|
3785
3812
|
// NOTE: I'm keeping delay values separated from offsets in timelines because delays can override previous tweens and it could be confusing to debug a timeline with overridden tweens and no associated visible delays.
|
|
@@ -4117,11 +4144,11 @@ function addTlChild(childParams, tl, timePosition, targets, index, length) {
|
|
|
4117
4144
|
const isSetter = isNum(childParams.duration) && /** @type {Number} */(childParams.duration) <= minValue;
|
|
4118
4145
|
// Offset the tl position with -minValue for 0 duration animations or .set() calls in order to align their end value with the defined position
|
|
4119
4146
|
const adjustedPosition = isSetter ? timePosition - minValue : timePosition;
|
|
4120
|
-
tick(tl, adjustedPosition, 1, 1, tickModes.AUTO);
|
|
4147
|
+
if (tl.composition) tick(tl, adjustedPosition, 1, 1, tickModes.AUTO);
|
|
4121
4148
|
const tlChild = targets ?
|
|
4122
4149
|
new JSAnimation(targets,/** @type {AnimationParams} */(childParams), tl, adjustedPosition, false, index, length) :
|
|
4123
4150
|
new Timer(/** @type {TimerParams} */(childParams), tl, adjustedPosition);
|
|
4124
|
-
tlChild.init(true);
|
|
4151
|
+
if (tl.composition) tlChild.init(true);
|
|
4125
4152
|
// TODO: Might be better to insert at a position relative to startTime?
|
|
4126
4153
|
addChild(tl, tlChild);
|
|
4127
4154
|
forEachChildren(tl, (/** @type {Renderable} */child) => {
|
|
@@ -4133,6 +4160,8 @@ function addTlChild(childParams, tl, timePosition, targets, index, length) {
|
|
|
4133
4160
|
return tl;
|
|
4134
4161
|
}
|
|
4135
4162
|
|
|
4163
|
+
let TLId = 0;
|
|
4164
|
+
|
|
4136
4165
|
class Timeline extends Timer {
|
|
4137
4166
|
|
|
4138
4167
|
/**
|
|
@@ -4140,6 +4169,9 @@ class Timeline extends Timer {
|
|
|
4140
4169
|
*/
|
|
4141
4170
|
constructor(parameters = {}) {
|
|
4142
4171
|
super(/** @type {TimerParams&TimelineParams} */(parameters), null, 0);
|
|
4172
|
+
++TLId;
|
|
4173
|
+
/** @type {String|Number} */
|
|
4174
|
+
this.id = !isUnd(parameters.id) ? parameters.id : TLId;
|
|
4143
4175
|
/** @type {Number} */
|
|
4144
4176
|
this.duration = 0; // TL duration starts at 0 and grows when adding children
|
|
4145
4177
|
/** @type {Record<String, Number>} */
|
|
@@ -4148,6 +4180,8 @@ class Timeline extends Timer {
|
|
|
4148
4180
|
const globalDefaults = globals.defaults;
|
|
4149
4181
|
/** @type {DefaultsParams} */
|
|
4150
4182
|
this.defaults = defaultsParams ? mergeObjects(defaultsParams, globalDefaults) : globalDefaults;
|
|
4183
|
+
/** @type {Boolean} */
|
|
4184
|
+
this.composition = setValue(parameters.composition, true);
|
|
4151
4185
|
/** @type {Callback<this>} */
|
|
4152
4186
|
this.onRender = parameters.onRender || globalDefaults.onRender;
|
|
4153
4187
|
const tlPlaybackEase = setValue(parameters.playbackEase, globalDefaults.playbackEase);
|
|
@@ -4225,7 +4259,8 @@ class Timeline extends Timer {
|
|
|
4225
4259
|
parseTimelinePosition(this,a2),
|
|
4226
4260
|
);
|
|
4227
4261
|
}
|
|
4228
|
-
|
|
4262
|
+
if (this.composition) this.init(true);
|
|
4263
|
+
return this;
|
|
4229
4264
|
}
|
|
4230
4265
|
}
|
|
4231
4266
|
|
|
@@ -4252,7 +4287,7 @@ class Timeline extends Timer {
|
|
|
4252
4287
|
if (isUnd(synced) || synced && isUnd(synced.pause)) return this;
|
|
4253
4288
|
synced.pause();
|
|
4254
4289
|
const duration = +(/** @type {globalThis.Animation} */(synced).effect ? /** @type {globalThis.Animation} */(synced).effect.getTiming().duration : /** @type {Tickable} */(synced).duration);
|
|
4255
|
-
return this.add(synced, { currentTime: [0, duration], duration, ease: 'linear' }, position);
|
|
4290
|
+
return this.add(synced, { currentTime: [0, duration], duration, delay: 0, ease: 'linear', playbackEase: 'linear' }, position);
|
|
4256
4291
|
}
|
|
4257
4292
|
|
|
4258
4293
|
/**
|
|
@@ -4275,7 +4310,7 @@ class Timeline extends Timer {
|
|
|
4275
4310
|
*/
|
|
4276
4311
|
call(callback, position) {
|
|
4277
4312
|
if (isUnd(callback) || callback && !isFnc(callback)) return this;
|
|
4278
|
-
return this.add({ duration: 0, onComplete: () => callback(this) }, position);
|
|
4313
|
+
return this.add({ duration: 0, delay: 0, onComplete: () => callback(this) }, position);
|
|
4279
4314
|
}
|
|
4280
4315
|
|
|
4281
4316
|
/**
|
|
@@ -7281,1517 +7316,2884 @@ var index$3 = /*#__PURE__*/Object.freeze({
|
|
|
7281
7316
|
steps: steps
|
|
7282
7317
|
});
|
|
7283
7318
|
|
|
7284
|
-
// Chain-able utilities
|
|
7285
7319
|
|
|
7286
|
-
const numberUtils = numberImports; // Needed to keep the import when bundling
|
|
7287
7320
|
|
|
7288
|
-
const chainables = {};
|
|
7289
7321
|
|
|
7290
|
-
/**
|
|
7291
|
-
* @callback UtilityFunction
|
|
7292
|
-
* @param {...*} args
|
|
7293
|
-
* @return {Number|String}
|
|
7294
|
-
*
|
|
7295
|
-
* @param {UtilityFunction} fn
|
|
7296
|
-
* @param {Number} [last=0]
|
|
7297
|
-
* @return {function(...(Number|String)): function(Number|String): (Number|String)}
|
|
7298
|
-
*/
|
|
7299
|
-
const curry = (fn, last = 0) => (...args) => last ? v => fn(...args, v) : v => fn(v, ...args);
|
|
7300
7322
|
|
|
7301
|
-
/**
|
|
7302
|
-
* @param {Function} fn
|
|
7303
|
-
* @return {function(...(Number|String))}
|
|
7304
|
-
*/
|
|
7305
|
-
const chain = fn => {
|
|
7306
|
-
return (...args) => {
|
|
7307
|
-
const result = fn(...args);
|
|
7308
|
-
return new Proxy(noop, {
|
|
7309
|
-
apply: (_, __, [v]) => result(v),
|
|
7310
|
-
get: (_, prop) => chain(/**@param {...Number|String} nextArgs */(...nextArgs) => {
|
|
7311
|
-
const nextResult = chainables[prop](...nextArgs);
|
|
7312
|
-
return (/**@type {Number|String} */v) => nextResult(result(v));
|
|
7313
|
-
})
|
|
7314
|
-
});
|
|
7315
|
-
}
|
|
7316
|
-
};
|
|
7317
7323
|
|
|
7318
|
-
/**
|
|
7319
|
-
* @param {UtilityFunction} fn
|
|
7320
|
-
* @param {String} name
|
|
7321
|
-
* @param {Number} [right]
|
|
7322
|
-
* @return {function(...(Number|String)): UtilityFunction}
|
|
7323
|
-
*/
|
|
7324
|
-
const makeChainable = (name, fn, right = 0) => {
|
|
7325
|
-
const chained = (...args) => (args.length < fn.length ? chain(curry(fn, right)) : fn)(...args);
|
|
7326
|
-
if (!chainables[name]) chainables[name] = chained;
|
|
7327
|
-
return chained;
|
|
7328
|
-
};
|
|
7329
7324
|
|
|
7330
7325
|
/**
|
|
7331
|
-
*
|
|
7332
|
-
* @
|
|
7333
|
-
* @
|
|
7334
|
-
* @
|
|
7335
|
-
* @property {ChainedWrap} wrap
|
|
7336
|
-
* @property {ChainedLerp} lerp
|
|
7337
|
-
* @property {ChainedDamp} damp
|
|
7338
|
-
* @property {ChainedMapRange} mapRange
|
|
7339
|
-
* @property {ChainedRoundPad} roundPad
|
|
7340
|
-
* @property {ChainedPadStart} padStart
|
|
7341
|
-
* @property {ChainedPadEnd} padEnd
|
|
7342
|
-
* @property {ChainedDegToRad} degToRad
|
|
7343
|
-
* @property {ChainedRadToDeg} radToDeg
|
|
7326
|
+
* Converts an easing function into a valid CSS linear() timing function string
|
|
7327
|
+
* @param {EasingFunction} fn
|
|
7328
|
+
* @param {number} [samples=100]
|
|
7329
|
+
* @returns {string} CSS linear() timing function
|
|
7344
7330
|
*/
|
|
7331
|
+
const easingToLinear = (fn, samples = 100) => {
|
|
7332
|
+
const points = [];
|
|
7333
|
+
for (let i = 0; i <= samples; i++) points.push(round$1(fn(i / samples), 4));
|
|
7334
|
+
return `linear(${points.join(', ')})`;
|
|
7335
|
+
};
|
|
7345
7336
|
|
|
7346
|
-
|
|
7347
|
-
* @callback ChainedUtilsResult
|
|
7348
|
-
* @param {Number} value - The value to process through the chained operations
|
|
7349
|
-
* @return {Number} The processed result
|
|
7350
|
-
*/
|
|
7337
|
+
const WAAPIEasesLookups = {};
|
|
7351
7338
|
|
|
7352
7339
|
/**
|
|
7353
|
-
* @
|
|
7340
|
+
* @param {EasingParam} ease
|
|
7341
|
+
* @return {String}
|
|
7354
7342
|
*/
|
|
7343
|
+
const parseWAAPIEasing = (ease) => {
|
|
7344
|
+
let parsedEase = WAAPIEasesLookups[ease];
|
|
7345
|
+
if (parsedEase) return parsedEase;
|
|
7346
|
+
parsedEase = 'linear';
|
|
7347
|
+
if (isStr(ease)) {
|
|
7348
|
+
if (
|
|
7349
|
+
stringStartsWith(ease, 'linear') ||
|
|
7350
|
+
stringStartsWith(ease, 'cubic-') ||
|
|
7351
|
+
stringStartsWith(ease, 'steps') ||
|
|
7352
|
+
stringStartsWith(ease, 'ease')
|
|
7353
|
+
) {
|
|
7354
|
+
parsedEase = ease;
|
|
7355
|
+
} else if (stringStartsWith(ease, 'cubicB')) {
|
|
7356
|
+
parsedEase = toLowerCase(ease);
|
|
7357
|
+
} else {
|
|
7358
|
+
const parsed = parseEaseString(ease);
|
|
7359
|
+
if (isFnc(parsed)) parsedEase = parsed === none ? 'linear' : easingToLinear(parsed);
|
|
7360
|
+
}
|
|
7361
|
+
// Only cache string based easing name, otherwise function arguments get lost
|
|
7362
|
+
WAAPIEasesLookups[ease] = parsedEase;
|
|
7363
|
+
} else if (isFnc(ease)) {
|
|
7364
|
+
const easing = easingToLinear(ease);
|
|
7365
|
+
if (easing) parsedEase = easing;
|
|
7366
|
+
} else if (/** @type {Spring} */(ease).ease) {
|
|
7367
|
+
parsedEase = easingToLinear(/** @type {Spring} */(ease).ease);
|
|
7368
|
+
}
|
|
7369
|
+
return parsedEase;
|
|
7370
|
+
};
|
|
7355
7371
|
|
|
7356
|
-
|
|
7372
|
+
const transformsShorthands = ['x', 'y', 'z'];
|
|
7373
|
+
const commonDefaultPXProperties = [
|
|
7374
|
+
'perspective',
|
|
7375
|
+
'width',
|
|
7376
|
+
'height',
|
|
7377
|
+
'margin',
|
|
7378
|
+
'padding',
|
|
7379
|
+
'top',
|
|
7380
|
+
'right',
|
|
7381
|
+
'bottom',
|
|
7382
|
+
'left',
|
|
7383
|
+
'borderWidth',
|
|
7384
|
+
'fontSize',
|
|
7385
|
+
'borderRadius',
|
|
7386
|
+
...transformsShorthands
|
|
7387
|
+
];
|
|
7357
7388
|
|
|
7358
|
-
|
|
7359
|
-
* @callback ChainedRoundPad
|
|
7360
|
-
* @param {Number} decimalLength - Number of decimal places
|
|
7361
|
-
* @return {ChainableUtil}
|
|
7362
|
-
*/
|
|
7363
|
-
const roundPad = /** @type {typeof numberUtils.roundPad & ChainedRoundPad} */(makeChainable('roundPad', numberUtils.roundPad));
|
|
7389
|
+
const validIndividualTransforms = /*#__PURE__*/ (() => [...transformsShorthands, ...validTransforms.filter(t => ['X', 'Y', 'Z'].some(axis => t.endsWith(axis)))])();
|
|
7364
7390
|
|
|
7365
|
-
|
|
7366
|
-
* @callback ChainedPadStart
|
|
7367
|
-
* @param {Number} totalLength - Target length
|
|
7368
|
-
* @param {String} padString - String to pad with
|
|
7369
|
-
* @return {ChainableUtil}
|
|
7370
|
-
*/
|
|
7371
|
-
const padStart = /** @type {typeof numberUtils.padStart & ChainedPadStart} */(makeChainable('padStart', numberUtils.padStart));
|
|
7391
|
+
let transformsPropertiesRegistered = null;
|
|
7372
7392
|
|
|
7373
7393
|
/**
|
|
7374
|
-
* @
|
|
7375
|
-
* @param
|
|
7376
|
-
* @param
|
|
7377
|
-
* @
|
|
7394
|
+
* @param {String} propName
|
|
7395
|
+
* @param {WAAPIKeyframeValue} value
|
|
7396
|
+
* @param {DOMTarget} $el
|
|
7397
|
+
* @param {Number} i
|
|
7398
|
+
* @param {Number} targetsLength
|
|
7399
|
+
* @return {String}
|
|
7378
7400
|
*/
|
|
7379
|
-
const
|
|
7401
|
+
const normalizeTweenValue = (propName, value, $el, i, targetsLength) => {
|
|
7402
|
+
// Do not try to compute strings with getFunctionValue otherwise it will convert CSS variables
|
|
7403
|
+
let v = isStr(value) ? value : getFunctionValue(/** @type {any} */(value), $el, i, targetsLength);
|
|
7404
|
+
if (!isNum(v)) return v;
|
|
7405
|
+
if (commonDefaultPXProperties.includes(propName) || stringStartsWith(propName, 'translate')) return `${v}px`;
|
|
7406
|
+
if (stringStartsWith(propName, 'rotate') || stringStartsWith(propName, 'skew')) return `${v}deg`;
|
|
7407
|
+
return `${v}`;
|
|
7408
|
+
};
|
|
7380
7409
|
|
|
7381
7410
|
/**
|
|
7382
|
-
* @
|
|
7383
|
-
* @param
|
|
7384
|
-
* @param
|
|
7385
|
-
* @
|
|
7411
|
+
* @param {DOMTarget} $el
|
|
7412
|
+
* @param {String} propName
|
|
7413
|
+
* @param {WAAPIKeyframeValue} from
|
|
7414
|
+
* @param {WAAPIKeyframeValue} to
|
|
7415
|
+
* @param {Number} i
|
|
7416
|
+
* @param {Number} targetsLength
|
|
7417
|
+
* @return {WAAPITweenValue}
|
|
7386
7418
|
*/
|
|
7387
|
-
const
|
|
7419
|
+
const parseIndividualTweenValue = ($el, propName, from, to, i, targetsLength) => {
|
|
7420
|
+
/** @type {WAAPITweenValue} */
|
|
7421
|
+
let tweenValue = '0';
|
|
7422
|
+
const computedTo = !isUnd(to) ? normalizeTweenValue(propName, to, $el, i, targetsLength) : getComputedStyle($el)[propName];
|
|
7423
|
+
if (!isUnd(from)) {
|
|
7424
|
+
const computedFrom = normalizeTweenValue(propName, from, $el, i, targetsLength);
|
|
7425
|
+
tweenValue = [computedFrom, computedTo];
|
|
7426
|
+
} else {
|
|
7427
|
+
tweenValue = isArr(to) ? to.map((/** @type {any} */v) => normalizeTweenValue(propName, v, $el, i, targetsLength)) : computedTo;
|
|
7428
|
+
}
|
|
7429
|
+
return tweenValue;
|
|
7430
|
+
};
|
|
7388
7431
|
|
|
7432
|
+
class WAAPIAnimation {
|
|
7389
7433
|
/**
|
|
7390
|
-
* @
|
|
7391
|
-
* @param {
|
|
7392
|
-
* @param {Number} inHigh - Input range maximum
|
|
7393
|
-
* @param {Number} outLow - Output range minimum
|
|
7394
|
-
* @param {Number} outHigh - Output range maximum
|
|
7395
|
-
* @return {ChainableUtil}
|
|
7434
|
+
* @param {DOMTargetsParam} targets
|
|
7435
|
+
* @param {WAAPIAnimationParams} params
|
|
7396
7436
|
*/
|
|
7397
|
-
|
|
7437
|
+
constructor(targets, params) {
|
|
7398
7438
|
|
|
7399
|
-
|
|
7400
|
-
* @callback ChainedDegToRad
|
|
7401
|
-
* @return {ChainableUtil}
|
|
7402
|
-
*/
|
|
7403
|
-
const degToRad = /** @type {typeof numberUtils.degToRad & ChainedDegToRad} */(makeChainable('degToRad', numberUtils.degToRad));
|
|
7439
|
+
if (scope.current) scope.current.register(this);
|
|
7404
7440
|
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7441
|
+
// Skip the registration and fallback to no animation in case CSS.registerProperty is not supported
|
|
7442
|
+
if (isNil(transformsPropertiesRegistered)) {
|
|
7443
|
+
if (isBrowser && (isUnd(CSS) || !Object.hasOwnProperty.call(CSS, 'registerProperty'))) {
|
|
7444
|
+
transformsPropertiesRegistered = false;
|
|
7445
|
+
} else {
|
|
7446
|
+
validTransforms.forEach(t => {
|
|
7447
|
+
const isSkew = stringStartsWith(t, 'skew');
|
|
7448
|
+
const isScale = stringStartsWith(t, 'scale');
|
|
7449
|
+
const isRotate = stringStartsWith(t, 'rotate');
|
|
7450
|
+
const isTranslate = stringStartsWith(t, 'translate');
|
|
7451
|
+
const isAngle = isRotate || isSkew;
|
|
7452
|
+
const syntax = isAngle ? '<angle>' : isScale ? "<number>" : isTranslate ? "<length-percentage>" : "*";
|
|
7453
|
+
try {
|
|
7454
|
+
CSS.registerProperty({
|
|
7455
|
+
name: '--' + t,
|
|
7456
|
+
syntax,
|
|
7457
|
+
inherits: false,
|
|
7458
|
+
initialValue: isTranslate ? '0px' : isAngle ? '0deg' : isScale ? '1' : '0',
|
|
7459
|
+
});
|
|
7460
|
+
} catch {} });
|
|
7461
|
+
transformsPropertiesRegistered = true;
|
|
7462
|
+
}
|
|
7463
|
+
}
|
|
7410
7464
|
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
* @param {Number|Array<Number>} increment - Step size or array of snap points
|
|
7414
|
-
* @return {ChainableUtil}
|
|
7415
|
-
*/
|
|
7416
|
-
const snap = /** @type {typeof numberUtils.snap & ChainedSnap} */(makeChainable('snap', numberUtils.snap));
|
|
7465
|
+
const parsedTargets = registerTargets(targets);
|
|
7466
|
+
const targetsLength = parsedTargets.length;
|
|
7417
7467
|
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
* @param {Number} max - Maximum boundary
|
|
7422
|
-
* @return {ChainableUtil}
|
|
7423
|
-
*/
|
|
7424
|
-
const clamp = /** @type {typeof numberUtils.clamp & ChainedClamp} */(makeChainable('clamp', numberUtils.clamp));
|
|
7468
|
+
if (!targetsLength) {
|
|
7469
|
+
console.warn(`No target found. Make sure the element you're trying to animate is accessible before creating your animation.`);
|
|
7470
|
+
}
|
|
7425
7471
|
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
*/
|
|
7431
|
-
const
|
|
7472
|
+
const ease = setValue(params.ease, parseWAAPIEasing(globals.defaults.ease));
|
|
7473
|
+
const spring = /** @type {Spring} */(ease).ease && ease;
|
|
7474
|
+
const autoplay = setValue(params.autoplay, globals.defaults.autoplay);
|
|
7475
|
+
const scroll = autoplay && /** @type {ScrollObserver} */(autoplay).link ? autoplay : false;
|
|
7476
|
+
const alternate = params.alternate && /** @type {Boolean} */(params.alternate) === true;
|
|
7477
|
+
const reversed = params.reversed && /** @type {Boolean} */(params.reversed) === true;
|
|
7478
|
+
const loop = setValue(params.loop, globals.defaults.loop);
|
|
7479
|
+
const iterations = /** @type {Number} */((loop === true || loop === Infinity) ? Infinity : isNum(loop) ? loop + 1 : 1);
|
|
7480
|
+
/** @type {PlaybackDirection} */
|
|
7481
|
+
const direction = alternate ? reversed ? 'alternate-reverse' : 'alternate' : reversed ? 'reverse' : 'normal';
|
|
7482
|
+
/** @type {FillMode} */
|
|
7483
|
+
const fill = 'both'; // We use 'both' here because the animation can be reversed during playback
|
|
7484
|
+
/** @type {String} */
|
|
7485
|
+
const easing = parseWAAPIEasing(ease);
|
|
7486
|
+
const timeScale = (globals.timeScale === 1 ? 1 : K);
|
|
7432
7487
|
|
|
7433
|
-
/**
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7488
|
+
/** @type {DOMTargetsArray}] */
|
|
7489
|
+
this.targets = parsedTargets;
|
|
7490
|
+
/** @type {Array<globalThis.Animation>}] */
|
|
7491
|
+
this.animations = [];
|
|
7492
|
+
/** @type {globalThis.Animation}] */
|
|
7493
|
+
this.controlAnimation = null;
|
|
7494
|
+
/** @type {Callback<this>} */
|
|
7495
|
+
this.onComplete = params.onComplete || /** @type {Callback<WAAPIAnimation>} */(/** @type {unknown} */(globals.defaults.onComplete));
|
|
7496
|
+
/** @type {Number} */
|
|
7497
|
+
this.duration = 0;
|
|
7498
|
+
/** @type {Boolean} */
|
|
7499
|
+
this.muteCallbacks = false;
|
|
7500
|
+
/** @type {Boolean} */
|
|
7501
|
+
this.completed = false;
|
|
7502
|
+
/** @type {Boolean} */
|
|
7503
|
+
this.paused = !autoplay || scroll !== false;
|
|
7504
|
+
/** @type {Boolean} */
|
|
7505
|
+
this.reversed = reversed;
|
|
7506
|
+
/** @type {Boolean} */
|
|
7507
|
+
this.persist = setValue(params.persist, globals.defaults.persist);
|
|
7508
|
+
/** @type {Boolean|ScrollObserver} */
|
|
7509
|
+
this.autoplay = autoplay;
|
|
7510
|
+
/** @type {Number} */
|
|
7511
|
+
this._speed = setValue(params.playbackRate, globals.defaults.playbackRate);
|
|
7512
|
+
/** @type {Function} */
|
|
7513
|
+
this._resolve = noop; // Used by .then()
|
|
7514
|
+
/** @type {Number} */
|
|
7515
|
+
this._completed = 0;
|
|
7516
|
+
/** @type {Array.<Object>} */
|
|
7517
|
+
this._inlineStyles = [];
|
|
7440
7518
|
|
|
7441
|
-
|
|
7442
|
-
* @callback ChainedDamp
|
|
7443
|
-
* @param {Number} start - Starting value
|
|
7444
|
-
* @param {Number} end - Target value
|
|
7445
|
-
* @param {Number} deltaTime - Delta time in ms
|
|
7446
|
-
* @return {ChainableUtil}
|
|
7447
|
-
*/
|
|
7448
|
-
const damp = /** @type {typeof numberUtils.damp & ChainedDamp} */(makeChainable('damp', numberUtils.damp, 1));
|
|
7519
|
+
parsedTargets.forEach(($el, i) => {
|
|
7449
7520
|
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
* @param {Number} [min=0] - The minimum value (inclusive)
|
|
7455
|
-
* @param {Number} [max=1] - The maximum value (inclusive)
|
|
7456
|
-
* @param {Number} [decimalLength=0] - Number of decimal places to round to
|
|
7457
|
-
* @return {Number} A random number between min and max
|
|
7458
|
-
*/
|
|
7521
|
+
const cachedTransforms = $el[transformsSymbol];
|
|
7522
|
+
const hasIndividualTransforms = validIndividualTransforms.some(t => params.hasOwnProperty(t));
|
|
7523
|
+
const elStyle = $el.style;
|
|
7524
|
+
const inlineStyles = this._inlineStyles[i] = {};
|
|
7459
7525
|
|
|
7460
|
-
/**
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
*
|
|
7464
|
-
*/
|
|
7465
|
-
const
|
|
7466
|
-
const m = 10 ** decimalLength;
|
|
7467
|
-
return Math.floor((Math.random() * (max - min + (1 / m)) + min) * m) / m;
|
|
7468
|
-
};
|
|
7526
|
+
/** @type {Number} */
|
|
7527
|
+
const duration = (spring ? /** @type {Spring} */(spring).settlingDuration : getFunctionValue(setValue(params.duration, globals.defaults.duration), $el, i, targetsLength)) * timeScale;
|
|
7528
|
+
/** @type {Number} */
|
|
7529
|
+
const delay = getFunctionValue(setValue(params.delay, globals.defaults.delay), $el, i, targetsLength) * timeScale;
|
|
7530
|
+
/** @type {CompositeOperation} */
|
|
7531
|
+
const composite = /** @type {CompositeOperation} */(setValue(params.composition, 'replace'));
|
|
7469
7532
|
|
|
7470
|
-
let
|
|
7533
|
+
for (let name in params) {
|
|
7534
|
+
if (!isKey(name)) continue;
|
|
7535
|
+
/** @type {PropertyIndexedKeyframes} */
|
|
7536
|
+
const keyframes = {};
|
|
7537
|
+
/** @type {KeyframeAnimationOptions} */
|
|
7538
|
+
const tweenParams = { iterations, direction, fill, easing, duration, delay, composite };
|
|
7539
|
+
const propertyValue = params[name];
|
|
7540
|
+
const individualTransformProperty = hasIndividualTransforms ? validTransforms.includes(name) ? name : shortTransforms.get(name) : false;
|
|
7471
7541
|
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
const
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
7542
|
+
const styleName = individualTransformProperty ? 'transform' : name;
|
|
7543
|
+
if (!inlineStyles[styleName]) {
|
|
7544
|
+
inlineStyles[styleName] = elStyle[styleName];
|
|
7545
|
+
}
|
|
7546
|
+
|
|
7547
|
+
let parsedPropertyValue;
|
|
7548
|
+
if (isObj(propertyValue)) {
|
|
7549
|
+
const tweenOptions = /** @type {WAAPITweenOptions} */(propertyValue);
|
|
7550
|
+
const tweenOptionsEase = setValue(tweenOptions.ease, ease);
|
|
7551
|
+
const tweenOptionsSpring = /** @type {Spring} */(tweenOptionsEase).ease && tweenOptionsEase;
|
|
7552
|
+
const to = /** @type {WAAPITweenOptions} */(tweenOptions).to;
|
|
7553
|
+
const from = /** @type {WAAPITweenOptions} */(tweenOptions).from;
|
|
7554
|
+
/** @type {Number} */
|
|
7555
|
+
tweenParams.duration = (tweenOptionsSpring ? /** @type {Spring} */(tweenOptionsSpring).settlingDuration : getFunctionValue(setValue(tweenOptions.duration, duration), $el, i, targetsLength)) * timeScale;
|
|
7556
|
+
/** @type {Number} */
|
|
7557
|
+
tweenParams.delay = getFunctionValue(setValue(tweenOptions.delay, delay), $el, i, targetsLength) * timeScale;
|
|
7558
|
+
/** @type {CompositeOperation} */
|
|
7559
|
+
tweenParams.composite = /** @type {CompositeOperation} */(setValue(tweenOptions.composition, composite));
|
|
7560
|
+
/** @type {String} */
|
|
7561
|
+
tweenParams.easing = parseWAAPIEasing(tweenOptionsEase);
|
|
7562
|
+
parsedPropertyValue = parseIndividualTweenValue($el, name, from, to, i, targetsLength);
|
|
7563
|
+
if (individualTransformProperty) {
|
|
7564
|
+
keyframes[`--${individualTransformProperty}`] = parsedPropertyValue;
|
|
7565
|
+
cachedTransforms[individualTransformProperty] = parsedPropertyValue;
|
|
7566
|
+
} else {
|
|
7567
|
+
keyframes[name] = parseIndividualTweenValue($el, name, from, to, i, targetsLength);
|
|
7568
|
+
}
|
|
7569
|
+
addWAAPIAnimation(this, $el, name, keyframes, tweenParams);
|
|
7570
|
+
if (!isUnd(from)) {
|
|
7571
|
+
if (!individualTransformProperty) {
|
|
7572
|
+
elStyle[name] = keyframes[name][0];
|
|
7573
|
+
} else {
|
|
7574
|
+
const key = `--${individualTransformProperty}`;
|
|
7575
|
+
elStyle.setProperty(key, keyframes[key][0]);
|
|
7576
|
+
}
|
|
7577
|
+
}
|
|
7578
|
+
} else {
|
|
7579
|
+
parsedPropertyValue = isArr(propertyValue) ?
|
|
7580
|
+
propertyValue.map((/** @type {any} */v) => normalizeTweenValue(name, v, $el, i, targetsLength)) :
|
|
7581
|
+
normalizeTweenValue(name, /** @type {any} */(propertyValue), $el, i, targetsLength);
|
|
7582
|
+
if (individualTransformProperty) {
|
|
7583
|
+
keyframes[`--${individualTransformProperty}`] = parsedPropertyValue;
|
|
7584
|
+
cachedTransforms[individualTransformProperty] = parsedPropertyValue;
|
|
7585
|
+
} else {
|
|
7586
|
+
keyframes[name] = parsedPropertyValue;
|
|
7587
|
+
}
|
|
7588
|
+
addWAAPIAnimation(this, $el, name, keyframes, tweenParams);
|
|
7589
|
+
}
|
|
7590
|
+
}
|
|
7591
|
+
if (hasIndividualTransforms) {
|
|
7592
|
+
let transforms = emptyString;
|
|
7593
|
+
for (let t in cachedTransforms) {
|
|
7594
|
+
transforms += `${transformsFragmentStrings[t]}var(--${t})) `;
|
|
7595
|
+
}
|
|
7596
|
+
elStyle.transform = transforms;
|
|
7597
|
+
}
|
|
7598
|
+
});
|
|
7599
|
+
|
|
7600
|
+
if (scroll) {
|
|
7601
|
+
/** @type {ScrollObserver} */(this.autoplay).link(this);
|
|
7602
|
+
}
|
|
7489
7603
|
}
|
|
7490
|
-
};
|
|
7491
7604
|
|
|
7492
|
-
/**
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
* @param {String|Array<T>} items - The array or string to pick from
|
|
7497
|
-
* @return {String|T} A random element from the array or character from the string
|
|
7498
|
-
*/
|
|
7499
|
-
const randomPick = items => items[random(0, items.length - 1)];
|
|
7605
|
+
/**
|
|
7606
|
+
* @callback forEachCallback
|
|
7607
|
+
* @param {globalThis.Animation} animation
|
|
7608
|
+
*/
|
|
7500
7609
|
|
|
7501
|
-
/**
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
*/
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
return items;
|
|
7512
|
-
};
|
|
7610
|
+
/**
|
|
7611
|
+
* @param {forEachCallback|String} callback
|
|
7612
|
+
* @return {this}
|
|
7613
|
+
*/
|
|
7614
|
+
forEach(callback) {
|
|
7615
|
+
try {
|
|
7616
|
+
const cb = isStr(callback) ? (/** @type {globalThis.Animation} */a) => a[callback]() : callback;
|
|
7617
|
+
this.animations.forEach(cb);
|
|
7618
|
+
} catch {} return this;
|
|
7619
|
+
}
|
|
7513
7620
|
|
|
7621
|
+
get speed() {
|
|
7622
|
+
return this._speed;
|
|
7623
|
+
}
|
|
7514
7624
|
|
|
7625
|
+
set speed(speed) {
|
|
7626
|
+
this._speed = +speed;
|
|
7627
|
+
this.forEach(anim => anim.playbackRate = speed);
|
|
7628
|
+
}
|
|
7515
7629
|
|
|
7630
|
+
get currentTime() {
|
|
7631
|
+
const controlAnimation = this.controlAnimation;
|
|
7632
|
+
const timeScale = globals.timeScale;
|
|
7633
|
+
return this.completed ? this.duration : controlAnimation ? +controlAnimation.currentTime * (timeScale === 1 ? 1 : timeScale) : 0;
|
|
7634
|
+
}
|
|
7516
7635
|
|
|
7636
|
+
set currentTime(time) {
|
|
7637
|
+
const t = time * (globals.timeScale === 1 ? 1 : K);
|
|
7638
|
+
this.forEach(anim => {
|
|
7639
|
+
// Make sure the animation playState is not 'paused' in order to properly trigger an onfinish callback.
|
|
7640
|
+
// The "paused" play state supersedes the "finished" play state; if the animation is both paused and finished, the "paused" state is the one that will be reported.
|
|
7641
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Animation/finish_event
|
|
7642
|
+
// This is not needed for persisting animations since they never finish.
|
|
7643
|
+
if (!this.persist && t >= this.duration) anim.play();
|
|
7644
|
+
anim.currentTime = t;
|
|
7645
|
+
});
|
|
7646
|
+
}
|
|
7517
7647
|
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
7538
|
-
|
|
7539
|
-
|
|
7540
|
-
|
|
7541
|
-
|
|
7542
|
-
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
7568
|
-
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
7574
|
-
|
|
7575
|
-
|
|
7576
|
-
|
|
7577
|
-
|
|
7578
|
-
|
|
7579
|
-
|
|
7580
|
-
|
|
7648
|
+
get progress() {
|
|
7649
|
+
return this.currentTime / this.duration;
|
|
7650
|
+
}
|
|
7651
|
+
|
|
7652
|
+
set progress(progress) {
|
|
7653
|
+
this.forEach(anim => anim.currentTime = progress * this.duration || 0);
|
|
7654
|
+
}
|
|
7655
|
+
|
|
7656
|
+
resume() {
|
|
7657
|
+
if (!this.paused) return this;
|
|
7658
|
+
this.paused = false;
|
|
7659
|
+
// TODO: Store the current time, and seek back to the last position
|
|
7660
|
+
return this.forEach('play');
|
|
7661
|
+
}
|
|
7662
|
+
|
|
7663
|
+
pause() {
|
|
7664
|
+
if (this.paused) return this;
|
|
7665
|
+
this.paused = true;
|
|
7666
|
+
return this.forEach('pause');
|
|
7667
|
+
}
|
|
7668
|
+
|
|
7669
|
+
alternate() {
|
|
7670
|
+
this.reversed = !this.reversed;
|
|
7671
|
+
this.forEach('reverse');
|
|
7672
|
+
if (this.paused) this.forEach('pause');
|
|
7673
|
+
return this;
|
|
7674
|
+
}
|
|
7675
|
+
|
|
7676
|
+
play() {
|
|
7677
|
+
if (this.reversed) this.alternate();
|
|
7678
|
+
return this.resume();
|
|
7679
|
+
}
|
|
7680
|
+
|
|
7681
|
+
reverse() {
|
|
7682
|
+
if (!this.reversed) this.alternate();
|
|
7683
|
+
return this.resume();
|
|
7684
|
+
}
|
|
7685
|
+
|
|
7686
|
+
/**
|
|
7687
|
+
* @param {Number} time
|
|
7688
|
+
* @param {Boolean} muteCallbacks
|
|
7689
|
+
*/
|
|
7690
|
+
seek(time, muteCallbacks = false) {
|
|
7691
|
+
if (muteCallbacks) this.muteCallbacks = true;
|
|
7692
|
+
if (time < this.duration) this.completed = false;
|
|
7693
|
+
this.currentTime = time;
|
|
7694
|
+
this.muteCallbacks = false;
|
|
7695
|
+
if (this.paused) this.pause();
|
|
7696
|
+
return this;
|
|
7697
|
+
}
|
|
7698
|
+
|
|
7699
|
+
restart() {
|
|
7700
|
+
this.completed = false;
|
|
7701
|
+
return this.seek(0, true).resume();
|
|
7702
|
+
}
|
|
7703
|
+
|
|
7704
|
+
commitStyles() {
|
|
7705
|
+
return this.forEach('commitStyles');
|
|
7706
|
+
}
|
|
7707
|
+
|
|
7708
|
+
complete() {
|
|
7709
|
+
return this.seek(this.duration);
|
|
7710
|
+
}
|
|
7711
|
+
|
|
7712
|
+
cancel() {
|
|
7713
|
+
this.muteCallbacks = true; // This prevents triggering the onComplete callback and resolving the Promise
|
|
7714
|
+
this.commitStyles().forEach('cancel');
|
|
7715
|
+
this.animations.length = 0; // Needed to release all animations from memory
|
|
7716
|
+
requestAnimationFrame(() => {
|
|
7717
|
+
this.targets.forEach(($el) => { // Needed to avoid unecessary inline transorms
|
|
7718
|
+
if ($el.style.transform === 'none') $el.style.removeProperty('transform');
|
|
7719
|
+
});
|
|
7720
|
+
});
|
|
7721
|
+
return this;
|
|
7722
|
+
}
|
|
7723
|
+
|
|
7724
|
+
revert() {
|
|
7725
|
+
// NOTE: We need a better way to revert the transforms, since right now the entire transform property value is reverted,
|
|
7726
|
+
// This means if you have multiple animations animating different transforms on the same target,
|
|
7727
|
+
// reverting one of them will also override the transform property of the other animations.
|
|
7728
|
+
// A better approach would be to store the original custom property values if they exist instead of the entire transform value,
|
|
7729
|
+
// and update the CSS variables with the orignal value
|
|
7730
|
+
this.cancel().targets.forEach(($el, i) => {
|
|
7731
|
+
const targetStyle = $el.style;
|
|
7732
|
+
const targetInlineStyles = this._inlineStyles[i];
|
|
7733
|
+
for (let name in targetInlineStyles) {
|
|
7734
|
+
const originalInlinedValue = targetInlineStyles[name];
|
|
7735
|
+
if (isUnd(originalInlinedValue) || originalInlinedValue === emptyString) {
|
|
7736
|
+
targetStyle.removeProperty(toLowerCase(name));
|
|
7581
7737
|
} else {
|
|
7582
|
-
|
|
7583
|
-
const fromY = !fromCenter ? floor(fromIndex / grid[0]) : (grid[1] - 1) / 2;
|
|
7584
|
-
const toX = index % grid[0];
|
|
7585
|
-
const toY = floor(index / grid[0]);
|
|
7586
|
-
const distanceX = fromX - toX;
|
|
7587
|
-
const distanceY = fromY - toY;
|
|
7588
|
-
let value = sqrt(distanceX * distanceX + distanceY * distanceY);
|
|
7589
|
-
if (axis === 'x') value = -distanceX;
|
|
7590
|
-
if (axis === 'y') value = -distanceY;
|
|
7591
|
-
values.push(value);
|
|
7738
|
+
$el.style[name] = originalInlinedValue;
|
|
7592
7739
|
}
|
|
7593
|
-
maxValue = max(...values);
|
|
7594
7740
|
}
|
|
7595
|
-
|
|
7596
|
-
if (
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
|
|
7605
|
-
|
|
7741
|
+
// Remove style attribute if empty
|
|
7742
|
+
if ($el.getAttribute('style') === emptyString) $el.removeAttribute('style');
|
|
7743
|
+
});
|
|
7744
|
+
return this;
|
|
7745
|
+
}
|
|
7746
|
+
|
|
7747
|
+
/**
|
|
7748
|
+
* @typedef {this & {then: null}} ResolvedWAAPIAnimation
|
|
7749
|
+
*/
|
|
7750
|
+
|
|
7751
|
+
/**
|
|
7752
|
+
* @param {Callback<ResolvedWAAPIAnimation>} [callback]
|
|
7753
|
+
* @return Promise<this>
|
|
7754
|
+
*/
|
|
7755
|
+
then(callback = noop) {
|
|
7756
|
+
const then = this.then;
|
|
7757
|
+
const onResolve = () => {
|
|
7758
|
+
this.then = null;
|
|
7759
|
+
callback(/** @type {ResolvedWAAPIAnimation} */(this));
|
|
7760
|
+
this.then = then;
|
|
7761
|
+
this._resolve = noop;
|
|
7762
|
+
};
|
|
7763
|
+
return new Promise(r => {
|
|
7764
|
+
this._resolve = () => r(onResolve());
|
|
7765
|
+
if (this.completed) this._resolve();
|
|
7766
|
+
return this;
|
|
7767
|
+
});
|
|
7606
7768
|
}
|
|
7769
|
+
}
|
|
7770
|
+
|
|
7771
|
+
const waapi = {
|
|
7772
|
+
/**
|
|
7773
|
+
* @param {DOMTargetsParam} targets
|
|
7774
|
+
* @param {WAAPIAnimationParams} params
|
|
7775
|
+
* @return {WAAPIAnimation}
|
|
7776
|
+
*/
|
|
7777
|
+
animate: (targets, params) => new WAAPIAnimation(targets, params),
|
|
7778
|
+
convertEase: easingToLinear
|
|
7607
7779
|
};
|
|
7608
7780
|
|
|
7609
|
-
|
|
7610
|
-
|
|
7611
|
-
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
createSeededRandom: createSeededRandom,
|
|
7615
|
-
damp: damp,
|
|
7616
|
-
degToRad: degToRad,
|
|
7617
|
-
get: get,
|
|
7618
|
-
keepTime: keepTime,
|
|
7619
|
-
lerp: lerp,
|
|
7620
|
-
mapRange: mapRange,
|
|
7621
|
-
padEnd: padEnd,
|
|
7622
|
-
padStart: padStart,
|
|
7623
|
-
radToDeg: radToDeg,
|
|
7624
|
-
random: random,
|
|
7625
|
-
randomPick: randomPick,
|
|
7626
|
-
remove: remove,
|
|
7627
|
-
round: round,
|
|
7628
|
-
roundPad: roundPad,
|
|
7629
|
-
set: set,
|
|
7630
|
-
shuffle: shuffle,
|
|
7631
|
-
snap: snap,
|
|
7632
|
-
stagger: stagger,
|
|
7633
|
-
sync: sync,
|
|
7634
|
-
wrap: wrap
|
|
7635
|
-
});
|
|
7781
|
+
|
|
7782
|
+
|
|
7783
|
+
|
|
7784
|
+
|
|
7785
|
+
|
|
7636
7786
|
|
|
7637
7787
|
|
|
7638
7788
|
|
|
7639
7789
|
/**
|
|
7640
|
-
* @
|
|
7641
|
-
* @return {SVGGeometryElement|void}
|
|
7790
|
+
* @typedef {DOMTargetSelector|Array<DOMTargetSelector>} LayoutChildrenParam
|
|
7642
7791
|
*/
|
|
7643
|
-
const getPath = path => {
|
|
7644
|
-
const parsedTargets = parseTargets(path);
|
|
7645
|
-
const $parsedSvg = /** @type {SVGGeometryElement} */(parsedTargets[0]);
|
|
7646
|
-
if (!$parsedSvg || !isSvg($parsedSvg)) return console.warn(`${path} is not a valid SVGGeometryElement`);
|
|
7647
|
-
return $parsedSvg;
|
|
7648
|
-
};
|
|
7649
7792
|
|
|
7793
|
+
/**
|
|
7794
|
+
* @typedef {Record<String, Number|String>} LayoutStateParams
|
|
7795
|
+
*/
|
|
7650
7796
|
|
|
7797
|
+
/**
|
|
7798
|
+
* @typedef {Object} LayoutAnimationParams
|
|
7799
|
+
* @property {Number|FunctionValue} [delay]
|
|
7800
|
+
* @property {Number|FunctionValue} [duration]
|
|
7801
|
+
* @property {EasingParam} [ease]
|
|
7802
|
+
* @property {LayoutStateParams} [frozen]
|
|
7803
|
+
* @property {LayoutStateParams} [added]
|
|
7804
|
+
* @property {LayoutStateParams} [removed]
|
|
7805
|
+
* @property {Callback<AutoLayout>} [onComplete]
|
|
7806
|
+
*/
|
|
7651
7807
|
|
|
7652
|
-
|
|
7808
|
+
/**
|
|
7809
|
+
* @typedef {LayoutAnimationParams & {
|
|
7810
|
+
* children?: LayoutChildrenParam,
|
|
7811
|
+
* properties?: Array<String>,
|
|
7812
|
+
* }} AutoLayoutParams
|
|
7813
|
+
*/
|
|
7653
7814
|
|
|
7654
7815
|
/**
|
|
7655
|
-
* @
|
|
7656
|
-
*
|
|
7657
|
-
*
|
|
7658
|
-
*
|
|
7659
|
-
*
|
|
7660
|
-
*
|
|
7816
|
+
* @typedef {Record<String, Number|String> & {
|
|
7817
|
+
* transform: String,
|
|
7818
|
+
* x: Number,
|
|
7819
|
+
* y: Number,
|
|
7820
|
+
* left: Number,
|
|
7821
|
+
* top: Number,
|
|
7822
|
+
* clientLeft: Number,
|
|
7823
|
+
* clientTop: Number,
|
|
7824
|
+
* width: Number,
|
|
7825
|
+
* height: Number,
|
|
7826
|
+
* }} LayoutNodeProperties
|
|
7661
7827
|
*/
|
|
7662
|
-
const getPathPoint = ($path, totalLength, progress, lookup, shouldClamp) => {
|
|
7663
|
-
const point = progress + lookup;
|
|
7664
|
-
const pointOnPath = shouldClamp
|
|
7665
|
-
? Math.max(0, Math.min(point, totalLength)) // Clamp between 0 and totalLength
|
|
7666
|
-
: (point % totalLength + totalLength) % totalLength; // Wrap around
|
|
7667
|
-
return $path.getPointAtLength(pointOnPath);
|
|
7668
|
-
};
|
|
7669
7828
|
|
|
7670
7829
|
/**
|
|
7671
|
-
* @
|
|
7672
|
-
* @
|
|
7673
|
-
* @
|
|
7674
|
-
* @
|
|
7830
|
+
* @typedef {Object} LayoutNode
|
|
7831
|
+
* @property {String} id
|
|
7832
|
+
* @property {DOMTarget} $el
|
|
7833
|
+
* @property {Number} index
|
|
7834
|
+
* @property {Number} total
|
|
7835
|
+
* @property {Number} delay
|
|
7836
|
+
* @property {Number} duration
|
|
7837
|
+
* @property {DOMTarget} $measure
|
|
7838
|
+
* @property {LayoutSnapshot} state
|
|
7839
|
+
* @property {AutoLayout} layout
|
|
7840
|
+
* @property {LayoutNode|null} parentNode
|
|
7841
|
+
* @property {Boolean} isTarget
|
|
7842
|
+
* @property {Boolean} hasTransform
|
|
7843
|
+
* @property {Boolean} isAnimated
|
|
7844
|
+
* @property {Array<String>} inlineStyles
|
|
7845
|
+
* @property {String|null} inlineTransforms
|
|
7846
|
+
* @property {String|null} inlineTransition
|
|
7847
|
+
* @property {Boolean} branchAdded
|
|
7848
|
+
* @property {Boolean} branchRemoved
|
|
7849
|
+
* @property {Boolean} branchNotRendered
|
|
7850
|
+
* @property {Boolean} sizeChanged
|
|
7851
|
+
* @property {Boolean} isInlined
|
|
7852
|
+
* @property {Boolean} hasVisibilitySwap
|
|
7853
|
+
* @property {Boolean} hasDisplayNone
|
|
7854
|
+
* @property {Boolean} hasVisibilityHidden
|
|
7855
|
+
* @property {String|null} measuredInlineTransform
|
|
7856
|
+
* @property {String|null} measuredInlineTransition
|
|
7857
|
+
* @property {String|null} measuredDisplay
|
|
7858
|
+
* @property {String|null} measuredVisibility
|
|
7859
|
+
* @property {String|null} measuredPosition
|
|
7860
|
+
* @property {Boolean} measuredHasDisplayNone
|
|
7861
|
+
* @property {Boolean} measuredHasVisibilityHidden
|
|
7862
|
+
* @property {Boolean} measuredIsVisible
|
|
7863
|
+
* @property {Boolean} measuredIsRemoved
|
|
7864
|
+
* @property {Boolean} measuredIsInsideRoot
|
|
7865
|
+
* @property {LayoutNodeProperties} properties
|
|
7866
|
+
* @property {LayoutNode|null} _head
|
|
7867
|
+
* @property {LayoutNode|null} _tail
|
|
7868
|
+
* @property {LayoutNode|null} _prev
|
|
7869
|
+
* @property {LayoutNode|null} _next
|
|
7870
|
+
*/
|
|
7871
|
+
|
|
7872
|
+
/**
|
|
7873
|
+
* @callback LayoutNodeIterator
|
|
7874
|
+
* @param {LayoutNode} node
|
|
7875
|
+
* @param {Number} index
|
|
7876
|
+
* @return {void}
|
|
7675
7877
|
*/
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
7679
|
-
const inSvg = $el[isSvgSymbol];
|
|
7680
|
-
const ctm = $path.getCTM();
|
|
7681
|
-
const shouldClamp = offset === 0;
|
|
7682
|
-
/** @type {TweenObjectValue} */
|
|
7683
|
-
return {
|
|
7684
|
-
from: 0,
|
|
7685
|
-
to: totalLength,
|
|
7686
|
-
/** @type {TweenModifier} */
|
|
7687
|
-
modifier: progress => {
|
|
7688
|
-
const offsetLength = offset * totalLength;
|
|
7689
|
-
const newProgress = progress + offsetLength;
|
|
7690
|
-
if (pathProperty === 'a') {
|
|
7691
|
-
const p0 = getPathPoint($path, totalLength, newProgress, -1, shouldClamp);
|
|
7692
|
-
const p1 = getPathPoint($path, totalLength, newProgress, 1, shouldClamp);
|
|
7693
|
-
return atan2(p1.y - p0.y, p1.x - p0.x) * 180 / PI;
|
|
7694
|
-
} else {
|
|
7695
|
-
const p = getPathPoint($path, totalLength, newProgress, 0, shouldClamp);
|
|
7696
|
-
return pathProperty === 'x' ?
|
|
7697
|
-
inSvg || !ctm ? p.x : p.x * ctm.a + p.y * ctm.c + ctm.e :
|
|
7698
|
-
inSvg || !ctm ? p.y : p.x * ctm.b + p.y * ctm.d + ctm.f
|
|
7699
|
-
}
|
|
7700
|
-
}
|
|
7701
|
-
}
|
|
7702
|
-
}
|
|
7703
|
-
};
|
|
7878
|
+
|
|
7879
|
+
let layoutId = 0;
|
|
7880
|
+
let nodeId = 0;
|
|
7704
7881
|
|
|
7705
7882
|
/**
|
|
7706
|
-
* @param {
|
|
7707
|
-
* @param {
|
|
7883
|
+
* @param {DOMTarget} root
|
|
7884
|
+
* @param {DOMTarget} $el
|
|
7885
|
+
* @return {Boolean}
|
|
7708
7886
|
*/
|
|
7709
|
-
const
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
return {
|
|
7713
|
-
translateX: getPathProgess($path, 'x', offset),
|
|
7714
|
-
translateY: getPathProgess($path, 'y', offset),
|
|
7715
|
-
rotate: getPathProgess($path, 'a', offset),
|
|
7716
|
-
}
|
|
7887
|
+
const isElementInRoot = (root, $el) => {
|
|
7888
|
+
if (!root || !$el) return false;
|
|
7889
|
+
return root === $el || root.contains($el);
|
|
7717
7890
|
};
|
|
7718
7891
|
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
7892
|
/**
|
|
7722
|
-
* @param {
|
|
7723
|
-
* @
|
|
7893
|
+
* @param {Node} node
|
|
7894
|
+
* @param {'previousSibling'|'nextSibling'} direction
|
|
7895
|
+
* @return {Boolean}
|
|
7724
7896
|
*/
|
|
7725
|
-
const
|
|
7726
|
-
let
|
|
7727
|
-
|
|
7728
|
-
|
|
7729
|
-
if (ctm) {
|
|
7730
|
-
const scaleX = sqrt(ctm.a * ctm.a + ctm.b * ctm.b);
|
|
7731
|
-
const scaleY = sqrt(ctm.c * ctm.c + ctm.d * ctm.d);
|
|
7732
|
-
scaleFactor = (scaleX + scaleY) / 2;
|
|
7733
|
-
}
|
|
7897
|
+
const hasTextSibling = (node, direction) => {
|
|
7898
|
+
let sibling = node[direction];
|
|
7899
|
+
while (sibling && sibling.nodeType === Node.TEXT_NODE && !sibling.textContent.trim()) {
|
|
7900
|
+
sibling = sibling[direction];
|
|
7734
7901
|
}
|
|
7735
|
-
return
|
|
7902
|
+
return sibling && sibling.nodeType === Node.TEXT_NODE;
|
|
7736
7903
|
};
|
|
7737
7904
|
|
|
7738
7905
|
/**
|
|
7739
|
-
*
|
|
7740
|
-
* @
|
|
7741
|
-
* @param {number} start - Starting position (0-1)
|
|
7742
|
-
* @param {number} end - Ending position (0-1)
|
|
7743
|
-
* @return {DrawableSVGGeometry} - Returns a proxy that preserves the original element's type with additional 'draw' attribute functionality
|
|
7906
|
+
* @param {DOMTarget} $el
|
|
7907
|
+
* @return {Boolean}
|
|
7744
7908
|
*/
|
|
7745
|
-
const
|
|
7746
|
-
const pathLength = K;
|
|
7747
|
-
const computedStyles = getComputedStyle($el);
|
|
7748
|
-
const strokeLineCap = computedStyles.strokeLinecap;
|
|
7749
|
-
// @ts-ignore
|
|
7750
|
-
const $scalled = computedStyles.vectorEffect === 'non-scaling-stroke' ? $el : null;
|
|
7751
|
-
let currentCap = strokeLineCap;
|
|
7752
|
-
|
|
7753
|
-
const proxy = new Proxy($el, {
|
|
7754
|
-
get(target, property) {
|
|
7755
|
-
const value = target[property];
|
|
7756
|
-
if (property === proxyTargetSymbol) return target;
|
|
7757
|
-
if (property === 'setAttribute') {
|
|
7758
|
-
return (...args) => {
|
|
7759
|
-
if (args[0] === 'draw') {
|
|
7760
|
-
const value = args[1];
|
|
7761
|
-
const values = value.split(' ');
|
|
7762
|
-
const v1 = +values[0];
|
|
7763
|
-
const v2 = +values[1];
|
|
7764
|
-
// TOTO: Benchmark if performing two slices is more performant than one split
|
|
7765
|
-
// const spaceIndex = value.indexOf(' ');
|
|
7766
|
-
// const v1 = round(+value.slice(0, spaceIndex), precision);
|
|
7767
|
-
// const v2 = round(+value.slice(spaceIndex + 1), precision);
|
|
7768
|
-
const scaleFactor = getScaleFactor($scalled);
|
|
7769
|
-
const os = v1 * -pathLength * scaleFactor;
|
|
7770
|
-
const d1 = (v2 * pathLength * scaleFactor) + os;
|
|
7771
|
-
const d2 = (pathLength * scaleFactor +
|
|
7772
|
-
((v1 === 0 && v2 === 1) || (v1 === 1 && v2 === 0) ? 0 : 10 * scaleFactor) - d1);
|
|
7773
|
-
if (strokeLineCap !== 'butt') {
|
|
7774
|
-
const newCap = v1 === v2 ? 'butt' : strokeLineCap;
|
|
7775
|
-
if (currentCap !== newCap) {
|
|
7776
|
-
target.style.strokeLinecap = `${newCap}`;
|
|
7777
|
-
currentCap = newCap;
|
|
7778
|
-
}
|
|
7779
|
-
}
|
|
7780
|
-
target.setAttribute('stroke-dashoffset', `${os}`);
|
|
7781
|
-
target.setAttribute('stroke-dasharray', `${d1} ${d2}`);
|
|
7782
|
-
}
|
|
7783
|
-
return Reflect.apply(value, target, args);
|
|
7784
|
-
};
|
|
7785
|
-
}
|
|
7909
|
+
const isElementSurroundedByText = $el => hasTextSibling($el, 'previousSibling') || hasTextSibling($el, 'nextSibling');
|
|
7786
7910
|
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
|
|
7793
|
-
|
|
7911
|
+
/**
|
|
7912
|
+
* @param {DOMTarget|null} $el
|
|
7913
|
+
* @return {String|null}
|
|
7914
|
+
*/
|
|
7915
|
+
const muteElementTransition = $el => {
|
|
7916
|
+
if (!$el) return null;
|
|
7917
|
+
const style = $el.style;
|
|
7918
|
+
const transition = style.transition || '';
|
|
7919
|
+
style.setProperty('transition', 'none', 'important');
|
|
7920
|
+
return transition;
|
|
7921
|
+
};
|
|
7794
7922
|
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
|
|
7923
|
+
/**
|
|
7924
|
+
* @param {DOMTarget|null} $el
|
|
7925
|
+
* @param {String|null} transition
|
|
7926
|
+
*/
|
|
7927
|
+
const restoreElementTransition = ($el, transition) => {
|
|
7928
|
+
if (!$el) return;
|
|
7929
|
+
const style = $el.style;
|
|
7930
|
+
if (transition) {
|
|
7931
|
+
style.transition = transition;
|
|
7932
|
+
} else {
|
|
7933
|
+
style.removeProperty('transition');
|
|
7798
7934
|
}
|
|
7799
|
-
|
|
7800
|
-
return /** @type {DrawableSVGGeometry} */(proxy);
|
|
7801
7935
|
};
|
|
7802
7936
|
|
|
7803
7937
|
/**
|
|
7804
|
-
*
|
|
7805
|
-
* @param {TargetsParam} selector - CSS selector, SVG element, or array of elements and selectors
|
|
7806
|
-
* @param {number} [start=0] - Starting position (0-1)
|
|
7807
|
-
* @param {number} [end=0] - Ending position (0-1)
|
|
7808
|
-
* @return {Array<DrawableSVGGeometry>} - Array of proxied elements with drawing functionality
|
|
7938
|
+
* @param {LayoutNode} node
|
|
7809
7939
|
*/
|
|
7810
|
-
const
|
|
7811
|
-
const
|
|
7812
|
-
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
));
|
|
7940
|
+
const muteNodeTransition = node => {
|
|
7941
|
+
const store = node.layout.transitionMuteStore;
|
|
7942
|
+
const $el = node.$el;
|
|
7943
|
+
const $measure = node.$measure;
|
|
7944
|
+
if ($el && !store.has($el)) store.set($el, muteElementTransition($el));
|
|
7945
|
+
if ($measure && !store.has($measure)) store.set($measure, muteElementTransition($measure));
|
|
7817
7946
|
};
|
|
7818
7947
|
|
|
7948
|
+
/**
|
|
7949
|
+
* @param {Map<DOMTarget, String|null>} store
|
|
7950
|
+
*/
|
|
7951
|
+
const restoreLayoutTransition = store => {
|
|
7952
|
+
store.forEach((value, $el) => restoreElementTransition($el, value));
|
|
7953
|
+
store.clear();
|
|
7954
|
+
};
|
|
7819
7955
|
|
|
7956
|
+
const hiddenComputedStyle = /** @type {CSSStyleDeclaration} */({
|
|
7957
|
+
display: 'none',
|
|
7958
|
+
visibility: 'hidden',
|
|
7959
|
+
opacity: '0',
|
|
7960
|
+
transform: 'none',
|
|
7961
|
+
position: 'static',
|
|
7962
|
+
});
|
|
7820
7963
|
|
|
7821
7964
|
/**
|
|
7822
|
-
* @param
|
|
7823
|
-
* @param {Number} [precision]
|
|
7824
|
-
* @return {FunctionValue}
|
|
7965
|
+
* @param {LayoutNode|null} node
|
|
7825
7966
|
*/
|
|
7826
|
-
const
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
if (
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
}
|
|
7839
|
-
const isPath = $path1.tagName === 'path';
|
|
7840
|
-
const separator = isPath ? ' ' : ',';
|
|
7841
|
-
const previousPoints = $path1[morphPointsSymbol];
|
|
7842
|
-
if (previousPoints) $path1.setAttribute(isPath ? 'd' : 'points', previousPoints);
|
|
7967
|
+
const detachNode = node => {
|
|
7968
|
+
if (!node) return;
|
|
7969
|
+
const parent = node.parentNode;
|
|
7970
|
+
if (!parent) return;
|
|
7971
|
+
if (parent._head === node) parent._head = node._next;
|
|
7972
|
+
if (parent._tail === node) parent._tail = node._prev;
|
|
7973
|
+
if (node._prev) node._prev._next = node._next;
|
|
7974
|
+
if (node._next) node._next._prev = node._prev;
|
|
7975
|
+
node._prev = null;
|
|
7976
|
+
node._next = null;
|
|
7977
|
+
node.parentNode = null;
|
|
7978
|
+
};
|
|
7843
7979
|
|
|
7844
|
-
|
|
7980
|
+
/**
|
|
7981
|
+
* @param {DOMTarget} $el
|
|
7982
|
+
* @param {LayoutNode|null} parentNode
|
|
7983
|
+
* @param {LayoutSnapshot} state
|
|
7984
|
+
* @param {LayoutNode} [recycledNode]
|
|
7985
|
+
* @return {LayoutNode}
|
|
7986
|
+
*/
|
|
7987
|
+
const createNode = ($el, parentNode, state, recycledNode) => {
|
|
7988
|
+
let dataId = $el.dataset.layoutId;
|
|
7989
|
+
if (!dataId) dataId = $el.dataset.layoutId = `node-${nodeId++}`;
|
|
7990
|
+
const node = recycledNode ? recycledNode : /** @type {LayoutNode} */({});
|
|
7991
|
+
node.$el = $el;
|
|
7992
|
+
node.$measure = $el;
|
|
7993
|
+
node.id = dataId;
|
|
7994
|
+
node.index = 0;
|
|
7995
|
+
node.total = 1;
|
|
7996
|
+
node.delay = 0;
|
|
7997
|
+
node.duration = 0;
|
|
7998
|
+
node.state = state;
|
|
7999
|
+
node.layout = state.layout;
|
|
8000
|
+
node.parentNode = parentNode || null;
|
|
8001
|
+
node.isTarget = false;
|
|
8002
|
+
node.hasTransform = false;
|
|
8003
|
+
node.isAnimated = false;
|
|
8004
|
+
node.inlineStyles = [];
|
|
8005
|
+
node.inlineTransforms = null;
|
|
8006
|
+
node.inlineTransition = null;
|
|
8007
|
+
node.branchAdded = false;
|
|
8008
|
+
node.branchRemoved = false;
|
|
8009
|
+
node.branchNotRendered = false;
|
|
8010
|
+
node.sizeChanged = false;
|
|
8011
|
+
node.isInlined = false;
|
|
8012
|
+
node.hasVisibilitySwap = false;
|
|
8013
|
+
node.hasDisplayNone = false;
|
|
8014
|
+
node.hasVisibilityHidden = false;
|
|
8015
|
+
node.measuredInlineTransform = null;
|
|
8016
|
+
node.measuredInlineTransition = null;
|
|
8017
|
+
node.measuredDisplay = null;
|
|
8018
|
+
node.measuredVisibility = null;
|
|
8019
|
+
node.measuredPosition = null;
|
|
8020
|
+
node.measuredHasDisplayNone = false;
|
|
8021
|
+
node.measuredHasVisibilityHidden = false;
|
|
8022
|
+
node.measuredIsVisible = false;
|
|
8023
|
+
node.measuredIsRemoved = false;
|
|
8024
|
+
node.measuredIsInsideRoot = false;
|
|
8025
|
+
node.properties = /** @type {LayoutNodeProperties} */({
|
|
8026
|
+
transform: 'none',
|
|
8027
|
+
x: 0,
|
|
8028
|
+
y: 0,
|
|
8029
|
+
left: 0,
|
|
8030
|
+
top: 0,
|
|
8031
|
+
clientLeft: 0,
|
|
8032
|
+
clientTop: 0,
|
|
8033
|
+
width: 0,
|
|
8034
|
+
height: 0,
|
|
8035
|
+
});
|
|
8036
|
+
node.layout.properties.forEach(prop => node.properties[prop] = 0);
|
|
8037
|
+
node._head = null;
|
|
8038
|
+
node._tail = null;
|
|
8039
|
+
node._prev = null;
|
|
8040
|
+
node._next = null;
|
|
8041
|
+
return node;
|
|
8042
|
+
};
|
|
7845
8043
|
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
|
|
7852
|
-
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
8044
|
+
/**
|
|
8045
|
+
* @param {LayoutNode} node
|
|
8046
|
+
* @param {DOMTarget} $measure
|
|
8047
|
+
* @param {CSSStyleDeclaration} computedStyle
|
|
8048
|
+
* @param {Boolean} skipMeasurements
|
|
8049
|
+
* @return {LayoutNode}
|
|
8050
|
+
*/
|
|
8051
|
+
const recordNodeState = (node, $measure, computedStyle, skipMeasurements) => {
|
|
8052
|
+
const $el = node.$el;
|
|
8053
|
+
const root = node.layout.root;
|
|
8054
|
+
const isRoot = root === $el;
|
|
8055
|
+
const properties = node.properties;
|
|
8056
|
+
const rootNode = node.state.rootNode;
|
|
8057
|
+
const parentNode = node.parentNode;
|
|
8058
|
+
const computedTransforms = computedStyle.transform;
|
|
8059
|
+
const inlineTransforms = $el.style.transform;
|
|
8060
|
+
const parentNotRendered = parentNode ? parentNode.measuredIsRemoved : false;
|
|
8061
|
+
const position = computedStyle.position;
|
|
8062
|
+
if (isRoot) node.layout.absoluteCoords = position === 'fixed' || position === 'absolute';
|
|
8063
|
+
node.$measure = $measure;
|
|
8064
|
+
node.inlineTransforms = inlineTransforms;
|
|
8065
|
+
node.hasTransform = computedTransforms && computedTransforms !== 'none';
|
|
8066
|
+
node.measuredIsInsideRoot = isElementInRoot(root, $measure);
|
|
8067
|
+
node.measuredInlineTransform = null;
|
|
8068
|
+
node.measuredDisplay = computedStyle.display;
|
|
8069
|
+
node.measuredVisibility = computedStyle.visibility;
|
|
8070
|
+
node.measuredPosition = position;
|
|
8071
|
+
node.measuredHasDisplayNone = computedStyle.display === 'none';
|
|
8072
|
+
node.measuredHasVisibilityHidden = computedStyle.visibility === 'hidden';
|
|
8073
|
+
node.measuredIsVisible = !(node.measuredHasDisplayNone || node.measuredHasVisibilityHidden);
|
|
8074
|
+
node.measuredIsRemoved = node.measuredHasDisplayNone || node.measuredHasVisibilityHidden || parentNotRendered;
|
|
8075
|
+
node.isInlined = node.measuredDisplay.includes('inline') && isElementSurroundedByText($el);
|
|
8076
|
+
|
|
8077
|
+
// Mute transforms (and transition to avoid triggering an animation) before the position calculation
|
|
8078
|
+
if (node.hasTransform && !skipMeasurements) {
|
|
8079
|
+
const transitionMuteStore = node.layout.transitionMuteStore;
|
|
8080
|
+
if (!transitionMuteStore.get($el)) node.inlineTransition = muteElementTransition($el);
|
|
8081
|
+
if ($measure === $el) {
|
|
8082
|
+
$el.style.transform = 'none';
|
|
8083
|
+
} else {
|
|
8084
|
+
if (!transitionMuteStore.get($measure)) node.measuredInlineTransition = muteElementTransition($measure);
|
|
8085
|
+
node.measuredInlineTransform = $measure.style.transform;
|
|
8086
|
+
$measure.style.transform = 'none';
|
|
7860
8087
|
}
|
|
7861
8088
|
}
|
|
7862
8089
|
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
var index$1 = /*#__PURE__*/Object.freeze({
|
|
7869
|
-
__proto__: null,
|
|
7870
|
-
createDrawable: createDrawable,
|
|
7871
|
-
createMotionPath: createMotionPath,
|
|
7872
|
-
morphTo: morphTo
|
|
7873
|
-
});
|
|
7874
|
-
|
|
8090
|
+
let left = 0;
|
|
8091
|
+
let top = 0;
|
|
8092
|
+
let width = 0;
|
|
8093
|
+
let height = 0;
|
|
7875
8094
|
|
|
8095
|
+
if (!skipMeasurements) {
|
|
8096
|
+
const rect = $measure.getBoundingClientRect();
|
|
8097
|
+
left = rect.left;
|
|
8098
|
+
top = rect.top;
|
|
8099
|
+
width = rect.width;
|
|
8100
|
+
height = rect.height;
|
|
8101
|
+
}
|
|
7876
8102
|
|
|
7877
|
-
|
|
7878
|
-
const
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
const whiteSpaceRgx = /^\s+$/;
|
|
7882
|
-
const lineType = 'line';
|
|
7883
|
-
const wordType = 'word';
|
|
7884
|
-
const charType = 'char';
|
|
7885
|
-
const dataLine = `data-line`;
|
|
8103
|
+
for (let name in properties) {
|
|
8104
|
+
const computedProp = name === 'transform' ? computedTransforms : computedStyle[name] || (computedStyle.getPropertyValue && computedStyle.getPropertyValue(name));
|
|
8105
|
+
if (!isUnd(computedProp)) properties[name] = computedProp;
|
|
8106
|
+
}
|
|
7886
8107
|
|
|
7887
|
-
|
|
7888
|
-
|
|
7889
|
-
|
|
7890
|
-
|
|
7891
|
-
|
|
8108
|
+
properties.left = left;
|
|
8109
|
+
properties.top = top;
|
|
8110
|
+
properties.clientLeft = skipMeasurements ? 0 : $measure.clientLeft;
|
|
8111
|
+
properties.clientTop = skipMeasurements ? 0 : $measure.clientTop;
|
|
8112
|
+
// Compute local x/y relative to parent
|
|
8113
|
+
let absoluteLeft, absoluteTop;
|
|
8114
|
+
if (isRoot) {
|
|
8115
|
+
if (!node.layout.absoluteCoords) {
|
|
8116
|
+
absoluteLeft = 0;
|
|
8117
|
+
absoluteTop = 0;
|
|
8118
|
+
} else {
|
|
8119
|
+
absoluteLeft = left;
|
|
8120
|
+
absoluteTop = top;
|
|
8121
|
+
}
|
|
8122
|
+
} else {
|
|
8123
|
+
const p = parentNode || rootNode;
|
|
8124
|
+
const parentLeft = p.properties.left;
|
|
8125
|
+
const parentTop = p.properties.top;
|
|
8126
|
+
const borderLeft = p.properties.clientLeft;
|
|
8127
|
+
const borderTop = p.properties.clientTop;
|
|
8128
|
+
if (!node.layout.absoluteCoords) {
|
|
8129
|
+
if (p === rootNode) {
|
|
8130
|
+
const rootLeft = rootNode.properties.left;
|
|
8131
|
+
const rootTop = rootNode.properties.top;
|
|
8132
|
+
const rootBorderLeft = rootNode.properties.clientLeft;
|
|
8133
|
+
const rootBorderTop = rootNode.properties.clientTop;
|
|
8134
|
+
absoluteLeft = left - rootLeft - rootBorderLeft;
|
|
8135
|
+
absoluteTop = top - rootTop - rootBorderTop;
|
|
8136
|
+
} else {
|
|
8137
|
+
absoluteLeft = left - parentLeft - borderLeft;
|
|
8138
|
+
absoluteTop = top - parentTop - borderTop;
|
|
8139
|
+
}
|
|
8140
|
+
} else {
|
|
8141
|
+
absoluteLeft = left - parentLeft - borderLeft;
|
|
8142
|
+
absoluteTop = top - parentTop - borderTop;
|
|
8143
|
+
}
|
|
8144
|
+
}
|
|
8145
|
+
properties.x = absoluteLeft;
|
|
8146
|
+
properties.y = absoluteTop;
|
|
8147
|
+
properties.width = width;
|
|
8148
|
+
properties.height = height;
|
|
8149
|
+
return node;
|
|
8150
|
+
};
|
|
7892
8151
|
|
|
7893
8152
|
/**
|
|
7894
|
-
* @
|
|
7895
|
-
* @
|
|
8153
|
+
* @param {LayoutNode} node
|
|
8154
|
+
* @param {LayoutStateParams} [props]
|
|
7896
8155
|
*/
|
|
7897
|
-
|
|
7898
|
-
|
|
7899
|
-
let
|
|
7900
|
-
|
|
7901
|
-
|
|
7902
|
-
|
|
8156
|
+
const updateNodeProperties = (node, props) => {
|
|
8157
|
+
if (!props) return;
|
|
8158
|
+
for (let name in props) {
|
|
8159
|
+
node.properties[name] = props[name];
|
|
8160
|
+
}
|
|
8161
|
+
};
|
|
7903
8162
|
|
|
7904
8163
|
/**
|
|
7905
|
-
* @param
|
|
7906
|
-
* @return {Boolean}
|
|
8164
|
+
* @param {LayoutNode} node
|
|
7907
8165
|
*/
|
|
7908
|
-
const
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
8166
|
+
const recordNodeInlineStyles = node => {
|
|
8167
|
+
const style = node.$el.style;
|
|
8168
|
+
const stylesStore = node.inlineStyles;
|
|
8169
|
+
stylesStore.length = 0;
|
|
8170
|
+
node.layout.recordedProperties.forEach(prop => {
|
|
8171
|
+
stylesStore.push(prop, style[prop] || '');
|
|
8172
|
+
});
|
|
7912
8173
|
};
|
|
7913
8174
|
|
|
7914
8175
|
/**
|
|
7915
|
-
* @param {
|
|
8176
|
+
* @param {LayoutNode} node
|
|
7916
8177
|
*/
|
|
7917
|
-
const
|
|
8178
|
+
const restoreNodeInlineStyles = node => {
|
|
8179
|
+
const style = node.$el.style;
|
|
8180
|
+
const stylesStore = node.inlineStyles;
|
|
8181
|
+
for (let i = 0, l = stylesStore.length; i < l; i += 2) {
|
|
8182
|
+
const property = stylesStore[i];
|
|
8183
|
+
const styleValue = stylesStore[i + 1];
|
|
8184
|
+
if (styleValue && styleValue !== '') {
|
|
8185
|
+
style[property] = styleValue;
|
|
8186
|
+
} else {
|
|
8187
|
+
style[property] = '';
|
|
8188
|
+
style.removeProperty(property);
|
|
8189
|
+
}
|
|
8190
|
+
}
|
|
8191
|
+
};
|
|
7918
8192
|
|
|
7919
8193
|
/**
|
|
7920
|
-
* @param {
|
|
7921
|
-
* @param {String} type
|
|
7922
|
-
* @return {Array<HTMLElement>}
|
|
8194
|
+
* @param {LayoutNode} node
|
|
7923
8195
|
*/
|
|
7924
|
-
const
|
|
7925
|
-
|
|
7926
|
-
const
|
|
8196
|
+
const restoreNodeTransform = node => {
|
|
8197
|
+
const inlineTransforms = node.inlineTransforms;
|
|
8198
|
+
const nodeStyle = node.$el.style;
|
|
8199
|
+
if (!node.hasTransform || !inlineTransforms || (node.hasTransform && nodeStyle.transform === 'none') || (inlineTransforms && inlineTransforms === 'none')) {
|
|
8200
|
+
nodeStyle.removeProperty('transform');
|
|
8201
|
+
} else if (inlineTransforms) {
|
|
8202
|
+
nodeStyle.transform = inlineTransforms;
|
|
8203
|
+
}
|
|
8204
|
+
const $measure = node.$measure;
|
|
8205
|
+
if (node.hasTransform && $measure !== node.$el) {
|
|
8206
|
+
const measuredStyle = $measure.style;
|
|
8207
|
+
const measuredInline = node.measuredInlineTransform;
|
|
8208
|
+
if (measuredInline && measuredInline !== '') {
|
|
8209
|
+
measuredStyle.transform = measuredInline;
|
|
8210
|
+
} else {
|
|
8211
|
+
measuredStyle.removeProperty('transform');
|
|
8212
|
+
}
|
|
8213
|
+
}
|
|
8214
|
+
node.measuredInlineTransform = null;
|
|
8215
|
+
if (node.inlineTransition !== null) {
|
|
8216
|
+
restoreElementTransition(node.$el, node.inlineTransition);
|
|
8217
|
+
node.inlineTransition = null;
|
|
8218
|
+
}
|
|
8219
|
+
if ($measure !== node.$el && node.measuredInlineTransition !== null) {
|
|
8220
|
+
restoreElementTransition($measure, node.measuredInlineTransition);
|
|
8221
|
+
node.measuredInlineTransition = null;
|
|
8222
|
+
}
|
|
8223
|
+
};
|
|
7927
8224
|
|
|
7928
8225
|
/**
|
|
7929
|
-
* @param {
|
|
8226
|
+
* @param {LayoutNode} node
|
|
7930
8227
|
*/
|
|
7931
|
-
const
|
|
7932
|
-
if (
|
|
7933
|
-
|
|
7934
|
-
|
|
7935
|
-
if (
|
|
8228
|
+
const restoreNodeVisualState = node => {
|
|
8229
|
+
if (node.measuredIsRemoved || node.hasVisibilitySwap) {
|
|
8230
|
+
node.$el.style.removeProperty('display');
|
|
8231
|
+
node.$el.style.removeProperty('visibility');
|
|
8232
|
+
if (node.hasVisibilitySwap) {
|
|
8233
|
+
node.$measure.style.removeProperty('display');
|
|
8234
|
+
node.$measure.style.removeProperty('visibility');
|
|
8235
|
+
}
|
|
8236
|
+
}
|
|
8237
|
+
if (node.measuredIsRemoved) {
|
|
8238
|
+
node.layout.pendingRemoved.delete(node.$el);
|
|
7936
8239
|
}
|
|
7937
8240
|
};
|
|
7938
8241
|
|
|
7939
8242
|
/**
|
|
7940
|
-
* @param {
|
|
7941
|
-
* @param {
|
|
7942
|
-
* @param {
|
|
7943
|
-
* @
|
|
7944
|
-
*/
|
|
7945
|
-
const
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
|
|
7950
|
-
|
|
8243
|
+
* @param {LayoutNode} node
|
|
8244
|
+
* @param {LayoutNode} targetNode
|
|
8245
|
+
* @param {LayoutSnapshot} newState
|
|
8246
|
+
* @return {LayoutNode}
|
|
8247
|
+
*/
|
|
8248
|
+
const cloneNodeProperties = (node, targetNode, newState) => {
|
|
8249
|
+
targetNode.properties = /** @type {LayoutNodeProperties} */({ ...node.properties });
|
|
8250
|
+
targetNode.state = newState;
|
|
8251
|
+
targetNode.isTarget = node.isTarget;
|
|
8252
|
+
targetNode.hasTransform = node.hasTransform;
|
|
8253
|
+
targetNode.inlineTransforms = node.inlineTransforms;
|
|
8254
|
+
targetNode.measuredIsVisible = node.measuredIsVisible;
|
|
8255
|
+
targetNode.measuredDisplay = node.measuredDisplay;
|
|
8256
|
+
targetNode.measuredIsRemoved = node.measuredIsRemoved;
|
|
8257
|
+
targetNode.measuredHasDisplayNone = node.measuredHasDisplayNone;
|
|
8258
|
+
targetNode.measuredHasVisibilityHidden = node.measuredHasVisibilityHidden;
|
|
8259
|
+
targetNode.hasDisplayNone = node.hasDisplayNone;
|
|
8260
|
+
targetNode.isInlined = node.isInlined;
|
|
8261
|
+
targetNode.hasVisibilityHidden = node.hasVisibilityHidden;
|
|
8262
|
+
return targetNode;
|
|
7951
8263
|
};
|
|
7952
8264
|
|
|
7953
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
template += `<span inert style="position:absolute;top:${top};left:${left};white-space:nowrap;">{value}</span>`;
|
|
7971
|
-
} else {
|
|
7972
|
-
template += `{value}`;
|
|
8265
|
+
class LayoutSnapshot {
|
|
8266
|
+
/**
|
|
8267
|
+
* @param {AutoLayout} layout
|
|
8268
|
+
*/
|
|
8269
|
+
constructor(layout) {
|
|
8270
|
+
/** @type {AutoLayout} */
|
|
8271
|
+
this.layout = layout;
|
|
8272
|
+
/** @type {LayoutNode|null} */
|
|
8273
|
+
this.rootNode = null;
|
|
8274
|
+
/** @type {Set<LayoutNode>} */
|
|
8275
|
+
this.rootNodes = new Set();
|
|
8276
|
+
/** @type {Map<String, LayoutNode>} */
|
|
8277
|
+
this.nodes = new Map();
|
|
8278
|
+
/** @type {Number} */
|
|
8279
|
+
this.scrollX = 0;
|
|
8280
|
+
/** @type {Number} */
|
|
8281
|
+
this.scrollY = 0;
|
|
7973
8282
|
}
|
|
7974
|
-
template += `</span>`;
|
|
7975
|
-
if (wrapType) template += `</span>`;
|
|
7976
|
-
return template;
|
|
7977
|
-
};
|
|
7978
8283
|
|
|
7979
|
-
/**
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
|
|
7983
|
-
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
|
|
7989
|
-
|
|
7990
|
-
|
|
7991
|
-
|
|
7992
|
-
|
|
7993
|
-
|
|
7994
|
-
|
|
7995
|
-
|
|
7996
|
-
|
|
7997
|
-
$
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
const $split = /** @type {HTMLElement} */($content.querySelector(`[data-${type}]`)) || $highestParent;
|
|
8003
|
-
const $replacables = /** @type {NodeListOf<HTMLElement>} */($content.querySelectorAll(`i.${className}`));
|
|
8004
|
-
const replacablesLength = $replacables.length;
|
|
8005
|
-
if (replacablesLength) {
|
|
8006
|
-
$highestParent.style.display = displayStyle;
|
|
8007
|
-
$split.style.display = displayStyle;
|
|
8008
|
-
$split.setAttribute(dataLine, `${lineIndex}`);
|
|
8009
|
-
if (!isLine) {
|
|
8010
|
-
$split.setAttribute('data-word', `${wordIndex}`);
|
|
8011
|
-
if (isChar) $split.setAttribute('data-char', `${charIndex}`);
|
|
8284
|
+
/**
|
|
8285
|
+
* @return {this}
|
|
8286
|
+
*/
|
|
8287
|
+
revert() {
|
|
8288
|
+
this.forEachNode(node => {
|
|
8289
|
+
node.$el.removeAttribute('data-layout-id');
|
|
8290
|
+
node.$measure.removeAttribute('data-layout-id');
|
|
8291
|
+
});
|
|
8292
|
+
this.rootNode = null;
|
|
8293
|
+
this.rootNodes.clear();
|
|
8294
|
+
this.nodes.clear();
|
|
8295
|
+
return this;
|
|
8296
|
+
}
|
|
8297
|
+
|
|
8298
|
+
/**
|
|
8299
|
+
* @param {DOMTarget} $el
|
|
8300
|
+
* @return {LayoutNodeProperties|undefined}
|
|
8301
|
+
*/
|
|
8302
|
+
get($el) {
|
|
8303
|
+
const node = this.nodes.get($el.dataset.layoutId);
|
|
8304
|
+
if (!node) {
|
|
8305
|
+
console.warn(`No node found on state`);
|
|
8306
|
+
return;
|
|
8012
8307
|
}
|
|
8013
|
-
|
|
8014
|
-
|
|
8015
|
-
|
|
8016
|
-
|
|
8017
|
-
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8308
|
+
return node.properties;
|
|
8309
|
+
}
|
|
8310
|
+
|
|
8311
|
+
/**
|
|
8312
|
+
* @param {DOMTarget} $el
|
|
8313
|
+
* @param {String} prop
|
|
8314
|
+
* @return {Number|String|undefined}
|
|
8315
|
+
*/
|
|
8316
|
+
getValue($el, prop) {
|
|
8317
|
+
if (!$el || !$el.dataset) {
|
|
8318
|
+
console.warn(`No element found on state (${$el})`);
|
|
8319
|
+
return;
|
|
8023
8320
|
}
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8321
|
+
const node = this.nodes.get($el.dataset.layoutId);
|
|
8322
|
+
if (!node) {
|
|
8323
|
+
console.warn(`No node found on state`);
|
|
8324
|
+
return;
|
|
8325
|
+
}
|
|
8326
|
+
const value = node.properties[prop];
|
|
8327
|
+
if (!isUnd(value)) return getFunctionValue(value, $el, node.index, node.total);
|
|
8028
8328
|
}
|
|
8029
|
-
if (debug) $highestParent.style.outline = `1px dotted ${debugColors[type]}`;
|
|
8030
|
-
return $highestParent;
|
|
8031
|
-
};
|
|
8032
8329
|
|
|
8033
|
-
/**
|
|
8034
|
-
* A class that splits text into words and wraps them in span elements while preserving the original HTML structure.
|
|
8035
|
-
* @class
|
|
8036
|
-
*/
|
|
8037
|
-
class TextSplitter {
|
|
8038
8330
|
/**
|
|
8039
|
-
* @param
|
|
8040
|
-
* @param
|
|
8331
|
+
* @param {LayoutNode|null} rootNode
|
|
8332
|
+
* @param {LayoutNodeIterator} cb
|
|
8041
8333
|
*/
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
|
|
8045
|
-
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8334
|
+
forEach(rootNode, cb) {
|
|
8335
|
+
let node = rootNode;
|
|
8336
|
+
let i = 0;
|
|
8337
|
+
while (node) {
|
|
8338
|
+
cb(node, i++);
|
|
8339
|
+
if (node._head) {
|
|
8340
|
+
node = node._head;
|
|
8341
|
+
} else if (node._next) {
|
|
8342
|
+
node = node._next;
|
|
8343
|
+
} else {
|
|
8344
|
+
while (node && !node._next) {
|
|
8345
|
+
node = node.parentNode;
|
|
8054
8346
|
}
|
|
8055
|
-
|
|
8347
|
+
if (node) node = node._next;
|
|
8056
8348
|
}
|
|
8057
|
-
};
|
|
8058
|
-
if (!graphemeSegmenter) graphemeSegmenter = segmenter ? new segmenter([], { granularity: 'grapheme' }) : {
|
|
8059
|
-
segment: text => [...text].map(char => ({ segment: char }))
|
|
8060
|
-
};
|
|
8061
|
-
if (!$splitTemplate && isBrowser) $splitTemplate = doc.createElement('template');
|
|
8062
|
-
if (scope.current) scope.current.register(this);
|
|
8063
|
-
const { words, chars, lines, accessible, includeSpaces, debug } = parameters;
|
|
8064
|
-
const $target = /** @type {HTMLElement} */((target = isArr(target) ? target[0] : target) && /** @type {Node} */(target).nodeType ? target : (getNodeList(target) || [])[0]);
|
|
8065
|
-
const lineParams = lines === true ? {} : lines;
|
|
8066
|
-
const wordParams = words === true || isUnd(words) ? {} : words;
|
|
8067
|
-
const charParams = chars === true ? {} : chars;
|
|
8068
|
-
this.debug = setValue(debug, false);
|
|
8069
|
-
this.includeSpaces = setValue(includeSpaces, false);
|
|
8070
|
-
this.accessible = setValue(accessible, true);
|
|
8071
|
-
this.linesOnly = lineParams && (!wordParams && !charParams);
|
|
8072
|
-
/** @type {String|false|SplitFunctionValue} */
|
|
8073
|
-
this.lineTemplate = isObj(lineParams) ? generateTemplate(lineType, /** @type {SplitTemplateParams} */(lineParams)) : lineParams;
|
|
8074
|
-
/** @type {String|false|SplitFunctionValue} */
|
|
8075
|
-
this.wordTemplate = isObj(wordParams) || this.linesOnly ? generateTemplate(wordType, /** @type {SplitTemplateParams} */(wordParams)) : wordParams;
|
|
8076
|
-
/** @type {String|false|SplitFunctionValue} */
|
|
8077
|
-
this.charTemplate = isObj(charParams) ? generateTemplate(charType, /** @type {SplitTemplateParams} */(charParams)) : charParams;
|
|
8078
|
-
this.$target = $target;
|
|
8079
|
-
this.html = $target && $target.innerHTML;
|
|
8080
|
-
this.lines = [];
|
|
8081
|
-
this.words = [];
|
|
8082
|
-
this.chars = [];
|
|
8083
|
-
this.effects = [];
|
|
8084
|
-
this.effectsCleanups = [];
|
|
8085
|
-
this.cache = null;
|
|
8086
|
-
this.ready = false;
|
|
8087
|
-
this.width = 0;
|
|
8088
|
-
this.resizeTimeout = null;
|
|
8089
|
-
const handleSplit = () => this.html && (lineParams || wordParams || charParams) && this.split();
|
|
8090
|
-
// Make sure this is declared before calling handleSplit() in case revert() is called inside an effect callback
|
|
8091
|
-
this.resizeObserver = new ResizeObserver(() => {
|
|
8092
|
-
// Use a setTimeout instead of a Timer for better tree shaking
|
|
8093
|
-
clearTimeout(this.resizeTimeout);
|
|
8094
|
-
this.resizeTimeout = setTimeout(() => {
|
|
8095
|
-
const currentWidth = /** @type {HTMLElement} */($target).offsetWidth;
|
|
8096
|
-
if (currentWidth === this.width) return;
|
|
8097
|
-
this.width = currentWidth;
|
|
8098
|
-
handleSplit();
|
|
8099
|
-
}, 150);
|
|
8100
|
-
});
|
|
8101
|
-
// Only declare the font ready promise when splitting by lines and not alreay split
|
|
8102
|
-
if (this.lineTemplate && !this.ready) {
|
|
8103
|
-
doc.fonts.ready.then(handleSplit);
|
|
8104
|
-
} else {
|
|
8105
|
-
handleSplit();
|
|
8106
8349
|
}
|
|
8107
|
-
$target ? this.resizeObserver.observe($target) : console.warn('No Text Splitter target found.');
|
|
8108
8350
|
}
|
|
8109
8351
|
|
|
8110
8352
|
/**
|
|
8111
|
-
* @param
|
|
8112
|
-
* @return this
|
|
8353
|
+
* @param {LayoutNodeIterator} cb
|
|
8113
8354
|
*/
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
const refreshableEffect = keepTime(effect);
|
|
8117
|
-
this.effects.push(refreshableEffect);
|
|
8118
|
-
if (this.ready) this.effectsCleanups[this.effects.length - 1] = refreshableEffect(this);
|
|
8119
|
-
return this;
|
|
8355
|
+
forEachRootNode(cb) {
|
|
8356
|
+
this.forEach(this.rootNode, cb);
|
|
8120
8357
|
}
|
|
8121
8358
|
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
8126
|
-
|
|
8127
|
-
|
|
8128
|
-
|
|
8129
|
-
return this;
|
|
8359
|
+
/**
|
|
8360
|
+
* @param {LayoutNodeIterator} cb
|
|
8361
|
+
*/
|
|
8362
|
+
forEachNode(cb) {
|
|
8363
|
+
for (const rootNode of this.rootNodes) {
|
|
8364
|
+
this.forEach(rootNode, cb);
|
|
8365
|
+
}
|
|
8130
8366
|
}
|
|
8131
8367
|
|
|
8132
8368
|
/**
|
|
8133
|
-
*
|
|
8134
|
-
* @param {
|
|
8369
|
+
* @param {DOMTarget} $el
|
|
8370
|
+
* @param {LayoutNode|null} parentNode
|
|
8371
|
+
* @return {LayoutNode|null}
|
|
8135
8372
|
*/
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8373
|
+
registerElement($el, parentNode) {
|
|
8374
|
+
if (!$el || $el.nodeType !== 1) return null;
|
|
8375
|
+
|
|
8376
|
+
if (!this.layout.transitionMuteStore.has($el)) this.layout.transitionMuteStore.set($el, muteElementTransition($el));
|
|
8377
|
+
|
|
8378
|
+
/** @type {Array<DOMTarget|LayoutNode|null>} */
|
|
8379
|
+
const stack = [$el, parentNode];
|
|
8380
|
+
const root = this.layout.root;
|
|
8381
|
+
let firstNode = null;
|
|
8382
|
+
|
|
8383
|
+
while (stack.length) {
|
|
8384
|
+
/** @type {LayoutNode|null} */
|
|
8385
|
+
const $parent = /** @type {LayoutNode|null} */(stack.pop());
|
|
8386
|
+
/** @type {DOMTarget|null} */
|
|
8387
|
+
const $current = /** @type {DOMTarget|null} */(stack.pop());
|
|
8388
|
+
if (!$current || $current.nodeType !== 1 || isSvg($current)) continue;
|
|
8389
|
+
|
|
8390
|
+
const skipMeasurements = $parent ? $parent.measuredIsRemoved : false;
|
|
8391
|
+
|
|
8392
|
+
const computedStyle = skipMeasurements ? hiddenComputedStyle : getComputedStyle($current);
|
|
8393
|
+
const hasDisplayNone = skipMeasurements ? true : computedStyle.display === 'none';
|
|
8394
|
+
const hasVisibilityHidden = skipMeasurements ? true : computedStyle.visibility === 'hidden';
|
|
8395
|
+
const isVisible = !hasDisplayNone && !hasVisibilityHidden;
|
|
8396
|
+
const existingId = $current.dataset.layoutId;
|
|
8397
|
+
const isInsideRoot = isElementInRoot(root, $current);
|
|
8398
|
+
|
|
8399
|
+
let node = existingId ? this.nodes.get(existingId) : null;
|
|
8400
|
+
|
|
8401
|
+
if (node && node.$el !== $current) {
|
|
8402
|
+
const nodeInsideRoot = isElementInRoot(root, node.$el);
|
|
8403
|
+
const measuredVisible = node.measuredIsVisible;
|
|
8404
|
+
const shouldReassignNode = !nodeInsideRoot && (isInsideRoot || (!isInsideRoot && !measuredVisible && isVisible));
|
|
8405
|
+
const shouldReuseMeasurements = nodeInsideRoot && !measuredVisible && isVisible;
|
|
8406
|
+
// Rebind nodes that move into the root or whose detached twin just became visible
|
|
8407
|
+
if (shouldReassignNode) {
|
|
8408
|
+
detachNode(node);
|
|
8409
|
+
node = createNode($current, $parent, this, node);
|
|
8410
|
+
// for hidden element with in-root sibling, keep the hidden node but borrow measurements from its visible in-root twin element
|
|
8411
|
+
} else if (shouldReuseMeasurements) {
|
|
8412
|
+
recordNodeState(node, $current, computedStyle, skipMeasurements);
|
|
8413
|
+
let $child = $current.lastElementChild;
|
|
8414
|
+
while ($child) {
|
|
8415
|
+
stack.push(/** @type {DOMTarget} */($child), node);
|
|
8416
|
+
$child = $child.previousElementSibling;
|
|
8167
8417
|
}
|
|
8168
|
-
|
|
8418
|
+
if (!firstNode) firstNode = node;
|
|
8419
|
+
continue;
|
|
8420
|
+
// No reassignment needed so keep walking descendants under the current parent
|
|
8421
|
+
} else {
|
|
8422
|
+
let $child = $current.lastElementChild;
|
|
8423
|
+
while ($child) {
|
|
8424
|
+
stack.push(/** @type {DOMTarget} */($child), $parent);
|
|
8425
|
+
$child = $child.previousElementSibling;
|
|
8426
|
+
}
|
|
8427
|
+
if (!firstNode) firstNode = node;
|
|
8428
|
+
continue;
|
|
8169
8429
|
}
|
|
8430
|
+
} else {
|
|
8431
|
+
node = createNode($current, $parent, this, node);
|
|
8432
|
+
}
|
|
8170
8433
|
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
// Chars elements must be re-parsed in the split() method if both words and chars are parsed
|
|
8197
|
-
} else if (charTemplate) {
|
|
8198
|
-
$wordsFragment.appendChild($charsFragment);
|
|
8199
|
-
} else {
|
|
8200
|
-
$wordsFragment.appendChild(doc.createTextNode(word));
|
|
8201
|
-
}
|
|
8202
|
-
// Skip the next iteration if we included a space
|
|
8203
|
-
if (hasWordFollowingSpace) i++;
|
|
8204
|
-
}
|
|
8434
|
+
node.branchAdded = false;
|
|
8435
|
+
node.branchRemoved = false;
|
|
8436
|
+
node.branchNotRendered = false;
|
|
8437
|
+
node.isTarget = false;
|
|
8438
|
+
node.isAnimated = false;
|
|
8439
|
+
node.hasVisibilityHidden = hasVisibilityHidden;
|
|
8440
|
+
node.hasDisplayNone = hasDisplayNone;
|
|
8441
|
+
node.hasVisibilitySwap = (hasVisibilityHidden && !node.measuredHasVisibilityHidden) || (hasDisplayNone && !node.measuredHasDisplayNone);
|
|
8442
|
+
// node.hasVisibilitySwap = (hasVisibilityHidden !== node.measuredHasVisibilityHidden) || (hasDisplayNone !== node.measuredHasDisplayNone);
|
|
8443
|
+
|
|
8444
|
+
this.nodes.set(node.id, node);
|
|
8445
|
+
|
|
8446
|
+
node.parentNode = $parent || null;
|
|
8447
|
+
node._prev = null;
|
|
8448
|
+
node._next = null;
|
|
8449
|
+
|
|
8450
|
+
if ($parent) {
|
|
8451
|
+
this.rootNodes.delete(node);
|
|
8452
|
+
if (!$parent._head) {
|
|
8453
|
+
$parent._head = node;
|
|
8454
|
+
$parent._tail = node;
|
|
8455
|
+
} else {
|
|
8456
|
+
$parent._tail._next = node;
|
|
8457
|
+
node._prev = $parent._tail;
|
|
8458
|
+
$parent._tail = node;
|
|
8205
8459
|
}
|
|
8206
|
-
|
|
8460
|
+
} else {
|
|
8461
|
+
this.rootNodes.add(node);
|
|
8207
8462
|
}
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8463
|
+
|
|
8464
|
+
recordNodeState(node, node.$el, computedStyle, skipMeasurements);
|
|
8465
|
+
|
|
8466
|
+
let $child = $current.lastElementChild;
|
|
8467
|
+
while ($child) {
|
|
8468
|
+
stack.push(/** @type {DOMTarget} */($child), node);
|
|
8469
|
+
$child = $child.previousElementSibling;
|
|
8470
|
+
}
|
|
8471
|
+
|
|
8472
|
+
if (!firstNode) firstNode = node;
|
|
8212
8473
|
}
|
|
8474
|
+
|
|
8475
|
+
return firstNode;
|
|
8476
|
+
}
|
|
8477
|
+
|
|
8478
|
+
/**
|
|
8479
|
+
* @param {DOMTarget} $el
|
|
8480
|
+
* @param {Set<DOMTarget>} candidates
|
|
8481
|
+
* @return {LayoutNode|null}
|
|
8482
|
+
*/
|
|
8483
|
+
ensureDetachedNode($el, candidates) {
|
|
8484
|
+
if (!$el || $el === this.layout.root) return null;
|
|
8485
|
+
const existingId = $el.dataset.layoutId;
|
|
8486
|
+
const existingNode = existingId ? this.nodes.get(existingId) : null;
|
|
8487
|
+
if (existingNode && existingNode.$el === $el) return existingNode;
|
|
8488
|
+
let parentNode = null;
|
|
8489
|
+
let $ancestor = $el.parentElement;
|
|
8490
|
+
while ($ancestor && $ancestor !== this.layout.root) {
|
|
8491
|
+
if (candidates.has($ancestor)) {
|
|
8492
|
+
parentNode = this.ensureDetachedNode($ancestor, candidates);
|
|
8493
|
+
break;
|
|
8494
|
+
}
|
|
8495
|
+
$ancestor = $ancestor.parentElement;
|
|
8496
|
+
}
|
|
8497
|
+
return this.registerElement($el, parentNode);
|
|
8213
8498
|
}
|
|
8214
8499
|
|
|
8215
8500
|
/**
|
|
8216
|
-
* @param {Boolean} clearCache
|
|
8217
8501
|
* @return {this}
|
|
8218
8502
|
*/
|
|
8219
|
-
|
|
8220
|
-
const
|
|
8221
|
-
const
|
|
8222
|
-
const
|
|
8223
|
-
const
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
if (canSplitLines || clearCache) {
|
|
8229
|
-
// No need to revert effects animations here since it's already taken care by the refreshable
|
|
8230
|
-
this.effectsCleanups.forEach(cleanup => isFnc(cleanup) && cleanup(this));
|
|
8503
|
+
record() {
|
|
8504
|
+
const { children, root } = this.layout;
|
|
8505
|
+
const toParse = isArr(children) ? children : [children];
|
|
8506
|
+
const scoped = [];
|
|
8507
|
+
const scopeRoot = children === '*' ? root : scope.root;
|
|
8508
|
+
|
|
8509
|
+
for (let i = 0, l = toParse.length; i < l; i++) {
|
|
8510
|
+
const child = toParse[i];
|
|
8511
|
+
scoped[i] = isStr(child) ? scopeRoot.querySelectorAll(child) : child;
|
|
8231
8512
|
}
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8513
|
+
|
|
8514
|
+
const parsedChildren = registerTargets(scoped);
|
|
8515
|
+
|
|
8516
|
+
this.nodes.clear();
|
|
8517
|
+
this.rootNodes.clear();
|
|
8518
|
+
|
|
8519
|
+
const rootNode = this.registerElement(root, null);
|
|
8520
|
+
// Root node are always targets
|
|
8521
|
+
rootNode.isTarget = true;
|
|
8522
|
+
this.rootNode = rootNode;
|
|
8523
|
+
|
|
8524
|
+
// Track ids of nodes that belong to the current root to filter detached matches
|
|
8525
|
+
const inRootNodeIds = new Set();
|
|
8526
|
+
this.nodes.forEach((node, id) => {
|
|
8527
|
+
if (node && node.measuredIsInsideRoot) {
|
|
8528
|
+
inRootNodeIds.add(id);
|
|
8529
|
+
}
|
|
8530
|
+
});
|
|
8531
|
+
|
|
8532
|
+
// Elements with a layout id outside the root that match the children selector
|
|
8533
|
+
const detachedElementsLookup = new Set();
|
|
8534
|
+
const orderedDetachedElements = [];
|
|
8535
|
+
|
|
8536
|
+
for (let i = 0, l = parsedChildren.length; i < l; i++) {
|
|
8537
|
+
const $el = parsedChildren[i];
|
|
8538
|
+
if (!$el || $el.nodeType !== 1 || $el === root) continue;
|
|
8539
|
+
const insideRoot = isElementInRoot(root, $el);
|
|
8540
|
+
if (!insideRoot) {
|
|
8541
|
+
const layoutNodeId = $el.dataset.layoutId;
|
|
8542
|
+
if (!layoutNodeId || !inRootNodeIds.has(layoutNodeId)) continue;
|
|
8543
|
+
}
|
|
8544
|
+
if (!detachedElementsLookup.has($el)) {
|
|
8545
|
+
detachedElementsLookup.add($el);
|
|
8546
|
+
orderedDetachedElements.push($el);
|
|
8236
8547
|
}
|
|
8237
|
-
this.splitNode($el);
|
|
8238
|
-
this.cache = $el.innerHTML;
|
|
8239
|
-
}
|
|
8240
|
-
if (canSplitLines) {
|
|
8241
|
-
if (isCached) $el.innerHTML = this.cache;
|
|
8242
|
-
this.lines.length = 0;
|
|
8243
|
-
if (wordTemplate) this.words = getAllTopLevelElements($el, wordType);
|
|
8244
8548
|
}
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
this.
|
|
8549
|
+
|
|
8550
|
+
for (let i = 0, l = orderedDetachedElements.length; i < l; i++) {
|
|
8551
|
+
this.ensureDetachedNode(orderedDetachedElements[i], detachedElementsLookup);
|
|
8248
8552
|
}
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8553
|
+
|
|
8554
|
+
for (let i = 0, l = parsedChildren.length; i < l; i++) {
|
|
8555
|
+
const $el = parsedChildren[i];
|
|
8556
|
+
const node = this.nodes.get($el.dataset.layoutId);
|
|
8557
|
+
if (node) {
|
|
8558
|
+
let cur = node;
|
|
8559
|
+
while (cur) {
|
|
8560
|
+
if (cur.isTarget) break;
|
|
8561
|
+
cur.isTarget = true;
|
|
8562
|
+
cur = cur.parentNode;
|
|
8563
|
+
}
|
|
8564
|
+
}
|
|
8261
8565
|
}
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8271
|
-
|
|
8566
|
+
|
|
8567
|
+
this.scrollX = window.scrollX;
|
|
8568
|
+
this.scrollY = window.scrollY;
|
|
8569
|
+
|
|
8570
|
+
const total = this.nodes.size;
|
|
8571
|
+
|
|
8572
|
+
this.forEachNode(restoreNodeTransform);
|
|
8573
|
+
this.forEachNode((node, i) => {
|
|
8574
|
+
node.index = i;
|
|
8575
|
+
node.total = total;
|
|
8576
|
+
});
|
|
8577
|
+
|
|
8578
|
+
return this;
|
|
8579
|
+
}
|
|
8580
|
+
}
|
|
8581
|
+
|
|
8582
|
+
class AutoLayout {
|
|
8583
|
+
/**
|
|
8584
|
+
* @param {DOMTargetSelector} root
|
|
8585
|
+
* @param {AutoLayoutParams} [params]
|
|
8586
|
+
*/
|
|
8587
|
+
constructor(root, params = {}) {
|
|
8588
|
+
if (scope.current) scope.current.register(this);
|
|
8589
|
+
const frozenParams = params.frozen;
|
|
8590
|
+
const addedParams = params.added;
|
|
8591
|
+
const removedParams = params.removed;
|
|
8592
|
+
const propsParams = params.properties;
|
|
8593
|
+
/** @type {AutoLayoutParams} */
|
|
8594
|
+
this.params = params;
|
|
8595
|
+
/** @type {DOMTarget} */
|
|
8596
|
+
this.root = /** @type {DOMTarget} */(registerTargets(root)[0]);
|
|
8597
|
+
/** @type {Number} */
|
|
8598
|
+
this.id = layoutId++;
|
|
8599
|
+
/** @type {LayoutChildrenParam} */
|
|
8600
|
+
this.children = params.children || '*';
|
|
8601
|
+
/** @type {Boolean} */
|
|
8602
|
+
this.absoluteCoords = false;
|
|
8603
|
+
/** @type {Number|FunctionValue} */
|
|
8604
|
+
this.duration = setValue(params.duration, 500);
|
|
8605
|
+
/** @type {Number|FunctionValue} */
|
|
8606
|
+
this.delay = setValue(params.delay, 0);
|
|
8607
|
+
/** @type {EasingParam} */
|
|
8608
|
+
this.ease = setValue(params.ease, 'inOut(3.5)');
|
|
8609
|
+
/** @type {Callback<this>} */
|
|
8610
|
+
this.onComplete = setValue(params.onComplete, /** @type {Callback<this>} */(noop));
|
|
8611
|
+
/** @type {LayoutStateParams} */
|
|
8612
|
+
this.frozenParams = frozenParams || { opacity: 0 };
|
|
8613
|
+
/** @type {LayoutStateParams} */
|
|
8614
|
+
this.addedParams = addedParams || { opacity: 0 };
|
|
8615
|
+
/** @type {LayoutStateParams} */
|
|
8616
|
+
this.removedParams = removedParams || { opacity: 0 };
|
|
8617
|
+
/** @type {Set<String>} */
|
|
8618
|
+
this.properties = new Set([
|
|
8619
|
+
'opacity',
|
|
8620
|
+
'borderRadius',
|
|
8621
|
+
]);
|
|
8622
|
+
if (frozenParams) for (let name in frozenParams) this.properties.add(name);
|
|
8623
|
+
if (addedParams) for (let name in addedParams) this.properties.add(name);
|
|
8624
|
+
if (removedParams) for (let name in removedParams) this.properties.add(name);
|
|
8625
|
+
if (propsParams) for (let i = 0, l = propsParams.length; i < l; i++) this.properties.add(propsParams[i]);
|
|
8626
|
+
/** @type {Set<String>} */
|
|
8627
|
+
this.recordedProperties = new Set([
|
|
8628
|
+
'display',
|
|
8629
|
+
'visibility',
|
|
8630
|
+
'translate',
|
|
8631
|
+
'position',
|
|
8632
|
+
'left',
|
|
8633
|
+
'top',
|
|
8634
|
+
'marginLeft',
|
|
8635
|
+
'marginTop',
|
|
8636
|
+
'width',
|
|
8637
|
+
'height',
|
|
8638
|
+
'maxWidth',
|
|
8639
|
+
'maxHeight',
|
|
8640
|
+
'minWidth',
|
|
8641
|
+
'minHeight',
|
|
8642
|
+
]);
|
|
8643
|
+
this.properties.forEach(prop => this.recordedProperties.add(prop));
|
|
8644
|
+
/** @type {WeakSet<DOMTarget>} */
|
|
8645
|
+
this.pendingRemoved = new WeakSet();
|
|
8646
|
+
/** @type {Map<DOMTarget, String|null>} */
|
|
8647
|
+
this.transitionMuteStore = new Map();
|
|
8648
|
+
/** @type {LayoutSnapshot} */
|
|
8649
|
+
this.oldState = new LayoutSnapshot(this);
|
|
8650
|
+
/** @type {LayoutSnapshot} */
|
|
8651
|
+
this.newState = new LayoutSnapshot(this);
|
|
8652
|
+
/** @type {Timeline|null} */
|
|
8653
|
+
this.timeline = null;
|
|
8654
|
+
/** @type {WAAPIAnimation|null} */
|
|
8655
|
+
this.transformAnimation = null;
|
|
8656
|
+
/** @type {Array<DOMTarget>} */
|
|
8657
|
+
this.frozen = [];
|
|
8658
|
+
/** @type {Array<DOMTarget>} */
|
|
8659
|
+
this.removed = [];
|
|
8660
|
+
/** @type {Array<DOMTarget>} */
|
|
8661
|
+
this.added = [];
|
|
8662
|
+
// Record the current state as the old state to init the data attributes
|
|
8663
|
+
this.oldState.record();
|
|
8664
|
+
// And all layout transition muted during the record
|
|
8665
|
+
restoreLayoutTransition(this.transitionMuteStore);
|
|
8666
|
+
}
|
|
8667
|
+
|
|
8668
|
+
/**
|
|
8669
|
+
* @return {this}
|
|
8670
|
+
*/
|
|
8671
|
+
revert() {
|
|
8672
|
+
if (this.timeline) {
|
|
8673
|
+
this.timeline.complete();
|
|
8674
|
+
this.timeline = null;
|
|
8675
|
+
}
|
|
8676
|
+
if (this.transformAnimation) {
|
|
8677
|
+
this.transformAnimation.complete();
|
|
8678
|
+
this.transformAnimation = null;
|
|
8679
|
+
}
|
|
8680
|
+
this.root.classList.remove('is-animated');
|
|
8681
|
+
this.frozen.length = this.removed.length = this.added.length = 0;
|
|
8682
|
+
this.oldState.revert();
|
|
8683
|
+
this.newState.revert();
|
|
8684
|
+
requestAnimationFrame(() => restoreLayoutTransition(this.transitionMuteStore));
|
|
8685
|
+
return this;
|
|
8686
|
+
}
|
|
8687
|
+
|
|
8688
|
+
/**
|
|
8689
|
+
* @return {this}
|
|
8690
|
+
*/
|
|
8691
|
+
record() {
|
|
8692
|
+
// Commit transforms before measuring
|
|
8693
|
+
if (this.transformAnimation) {
|
|
8694
|
+
this.transformAnimation.cancel();
|
|
8695
|
+
this.transformAnimation = null;
|
|
8696
|
+
}
|
|
8697
|
+
// Record the old state
|
|
8698
|
+
this.oldState.record();
|
|
8699
|
+
// Cancel any running timeline
|
|
8700
|
+
if (this.timeline) {
|
|
8701
|
+
this.timeline.cancel();
|
|
8702
|
+
this.timeline = null;
|
|
8703
|
+
}
|
|
8704
|
+
// Restore previously captured inline styles
|
|
8705
|
+
this.newState.forEachRootNode(restoreNodeInlineStyles);
|
|
8706
|
+
return this;
|
|
8707
|
+
}
|
|
8708
|
+
|
|
8709
|
+
/**
|
|
8710
|
+
* @param {LayoutAnimationParams} [params]
|
|
8711
|
+
* @return {Timeline}
|
|
8712
|
+
*/
|
|
8713
|
+
animate(params = {}) {
|
|
8714
|
+
const delay = setValue(params.delay, this.delay);
|
|
8715
|
+
const duration = setValue(params.duration, this.duration);
|
|
8716
|
+
const onComplete = setValue(params.onComplete, this.onComplete);
|
|
8717
|
+
const frozenParams = params.frozen ? mergeObjects(params.frozen, this.frozenParams) : this.frozenParams;
|
|
8718
|
+
const addedParams = params.added ? mergeObjects(params.added, this.addedParams) : this.addedParams;
|
|
8719
|
+
const removedParams = params.removed ? mergeObjects(params.removed, this.removedParams) : this.removedParams;
|
|
8720
|
+
const oldState = this.oldState;
|
|
8721
|
+
const newState = this.newState;
|
|
8722
|
+
const added = this.added;
|
|
8723
|
+
const removed = this.removed;
|
|
8724
|
+
const frozen = this.frozen;
|
|
8725
|
+
const pendingRemoved = this.pendingRemoved;
|
|
8726
|
+
|
|
8727
|
+
added.length = removed.length = frozen.length = 0;
|
|
8728
|
+
|
|
8729
|
+
// Mute old state CSS transitions to prevent wrong properties calculation
|
|
8730
|
+
oldState.forEachRootNode(muteNodeTransition);
|
|
8731
|
+
// Capture the new state before animation
|
|
8732
|
+
newState.record();
|
|
8733
|
+
newState.forEachRootNode(recordNodeInlineStyles);
|
|
8734
|
+
|
|
8735
|
+
const targets = [];
|
|
8736
|
+
const animated = [];
|
|
8737
|
+
const transformed = [];
|
|
8738
|
+
const animatedFrozen = [];
|
|
8739
|
+
const root = newState.rootNode.$el;
|
|
8740
|
+
|
|
8741
|
+
newState.forEachRootNode(node => {
|
|
8742
|
+
const $el = node.$el;
|
|
8743
|
+
const id = node.id;
|
|
8744
|
+
const parent = node.parentNode;
|
|
8745
|
+
const parentAdded = parent ? parent.branchAdded : false;
|
|
8746
|
+
const parentRemoved = parent ? parent.branchRemoved : false;
|
|
8747
|
+
const parentNotRendered = parent ? parent.branchNotRendered : false;
|
|
8748
|
+
|
|
8749
|
+
// Delay and duration must be calculated in the animate() call to support delay override
|
|
8750
|
+
node.delay = +(isFnc(delay) ? delay($el, node.index, node.total) : delay);
|
|
8751
|
+
node.duration = +(isFnc(duration) ? duration($el, node.index, node.total) : duration);
|
|
8752
|
+
|
|
8753
|
+
let oldStateNode = oldState.nodes.get(id);
|
|
8754
|
+
|
|
8755
|
+
const hasNoOldState = !oldStateNode;
|
|
8756
|
+
|
|
8757
|
+
if (hasNoOldState) {
|
|
8758
|
+
oldStateNode = cloneNodeProperties(node, /** @type {LayoutNode} */({}), oldState);
|
|
8759
|
+
oldState.nodes.set(id, oldStateNode);
|
|
8760
|
+
oldStateNode.measuredIsRemoved = true;
|
|
8761
|
+
} else if (oldStateNode.measuredIsRemoved && !node.measuredIsRemoved) {
|
|
8762
|
+
cloneNodeProperties(node, oldStateNode, oldState);
|
|
8763
|
+
oldStateNode.measuredIsRemoved = true;
|
|
8764
|
+
}
|
|
8765
|
+
|
|
8766
|
+
const oldParentNode = oldStateNode.parentNode;
|
|
8767
|
+
const oldParentId = oldParentNode ? oldParentNode.id : null;
|
|
8768
|
+
const newParentId = parent ? parent.id : null;
|
|
8769
|
+
const parentChanged = oldParentId !== newParentId;
|
|
8770
|
+
const elementChanged = oldStateNode.$el !== node.$el;
|
|
8771
|
+
const wasRemovedBefore = oldStateNode.measuredIsRemoved;
|
|
8772
|
+
const isRemovedNow = node.measuredIsRemoved;
|
|
8773
|
+
|
|
8774
|
+
// Recalculate postion relative to their parent for elements that have been moved
|
|
8775
|
+
if (!oldStateNode.measuredIsRemoved && !isRemovedNow && !hasNoOldState && (parentChanged || elementChanged)) {
|
|
8776
|
+
let offsetX = 0;
|
|
8777
|
+
let offsetY = 0;
|
|
8778
|
+
let current = node.parentNode;
|
|
8779
|
+
while (current) {
|
|
8780
|
+
offsetX += current.properties.x || 0;
|
|
8781
|
+
offsetY += current.properties.y || 0;
|
|
8782
|
+
if (current.parentNode === newState.rootNode) break;
|
|
8783
|
+
current = current.parentNode;
|
|
8784
|
+
}
|
|
8785
|
+
let oldOffsetX = 0;
|
|
8786
|
+
let oldOffsetY = 0;
|
|
8787
|
+
let oldCurrent = oldStateNode.parentNode;
|
|
8788
|
+
while (oldCurrent) {
|
|
8789
|
+
oldOffsetX += oldCurrent.properties.x || 0;
|
|
8790
|
+
oldOffsetY += oldCurrent.properties.y || 0;
|
|
8791
|
+
if (oldCurrent.parentNode === oldState.rootNode) break;
|
|
8792
|
+
oldCurrent = oldCurrent.parentNode;
|
|
8793
|
+
}
|
|
8794
|
+
oldStateNode.properties.x += oldOffsetX - offsetX;
|
|
8795
|
+
oldStateNode.properties.y += oldOffsetY - offsetY;
|
|
8796
|
+
}
|
|
8797
|
+
|
|
8798
|
+
if (node.hasVisibilitySwap) {
|
|
8799
|
+
if (node.hasVisibilityHidden) {
|
|
8800
|
+
node.$el.style.visibility = 'visible';
|
|
8801
|
+
node.$measure.style.visibility = 'hidden';
|
|
8802
|
+
}
|
|
8803
|
+
if (node.hasDisplayNone) {
|
|
8804
|
+
node.$el.style.display = oldStateNode.measuredDisplay || node.measuredDisplay || '';
|
|
8805
|
+
// Setting visibility 'hidden' instead of display none to avoid calculation issues
|
|
8806
|
+
node.$measure.style.visibility = 'hidden';
|
|
8807
|
+
// @TODO: check why setting display here can cause calculation issues
|
|
8808
|
+
// node.$measure.style.display = 'none';
|
|
8809
|
+
}
|
|
8810
|
+
}
|
|
8811
|
+
|
|
8812
|
+
const wasPendingRemoval = pendingRemoved.has($el);
|
|
8813
|
+
const wasVisibleBefore = oldStateNode.measuredIsVisible;
|
|
8814
|
+
const isVisibleNow = node.measuredIsVisible;
|
|
8815
|
+
const becomeVisible = !wasVisibleBefore && isVisibleNow && !parentNotRendered;
|
|
8816
|
+
const topLevelAdded = !isRemovedNow && (wasRemovedBefore || wasPendingRemoval) && !parentAdded;
|
|
8817
|
+
const newlyRemoved = isRemovedNow && !wasRemovedBefore && !parentRemoved;
|
|
8818
|
+
const topLevelRemoved = newlyRemoved || isRemovedNow && wasPendingRemoval && !parentRemoved;
|
|
8819
|
+
|
|
8820
|
+
if (node.measuredIsRemoved && wasVisibleBefore) {
|
|
8821
|
+
node.$el.style.display = oldStateNode.measuredDisplay;
|
|
8822
|
+
node.$el.style.visibility = 'visible';
|
|
8823
|
+
cloneNodeProperties(oldStateNode, node, newState);
|
|
8824
|
+
}
|
|
8825
|
+
|
|
8826
|
+
if (newlyRemoved) {
|
|
8827
|
+
removed.push($el);
|
|
8828
|
+
pendingRemoved.add($el);
|
|
8829
|
+
} else if (!isRemovedNow && wasPendingRemoval) {
|
|
8830
|
+
pendingRemoved.delete($el);
|
|
8831
|
+
}
|
|
8832
|
+
|
|
8833
|
+
// Node is added
|
|
8834
|
+
if ((topLevelAdded && !parentNotRendered) || becomeVisible) {
|
|
8835
|
+
updateNodeProperties(oldStateNode, addedParams);
|
|
8836
|
+
added.push($el);
|
|
8837
|
+
// Node is removed
|
|
8838
|
+
} else if (topLevelRemoved && !parentNotRendered) {
|
|
8839
|
+
updateNodeProperties(node, removedParams);
|
|
8840
|
+
}
|
|
8841
|
+
|
|
8842
|
+
// Compute function based propety values before cheking for changes
|
|
8843
|
+
for (let name in node.properties) {
|
|
8844
|
+
node.properties[name] = newState.getValue(node.$el, name);
|
|
8845
|
+
// NOTE: I'm using node.$el to get the value of old state, make sure this is valid instead of oldStateNode.$el
|
|
8846
|
+
oldStateNode.properties[name] = oldState.getValue(node.$el, name);
|
|
8847
|
+
}
|
|
8848
|
+
|
|
8849
|
+
const hiddenStateChanged = (topLevelAdded || newlyRemoved) && wasRemovedBefore !== isRemovedNow;
|
|
8850
|
+
let propertyChanged = false;
|
|
8851
|
+
|
|
8852
|
+
|
|
8853
|
+
if (node.isTarget && (!node.measuredIsRemoved && wasVisibleBefore || node.measuredIsRemoved && isVisibleNow)) {
|
|
8854
|
+
if (!node.isInlined && (node.properties.transform !== 'none' || oldStateNode.properties.transform !== 'none')) {
|
|
8855
|
+
node.hasTransform = true;
|
|
8856
|
+
propertyChanged = true;
|
|
8857
|
+
transformed.push($el);
|
|
8858
|
+
}
|
|
8859
|
+
for (let name in node.properties) {
|
|
8860
|
+
if (name !== 'transform' && (node.properties[name] !== oldStateNode.properties[name] || hiddenStateChanged)) {
|
|
8861
|
+
propertyChanged = true;
|
|
8862
|
+
animated.push($el);
|
|
8863
|
+
break;
|
|
8864
|
+
}
|
|
8865
|
+
}
|
|
8866
|
+
}
|
|
8867
|
+
|
|
8868
|
+
const nodeHasChanged = (propertyChanged || topLevelAdded || topLevelRemoved || becomeVisible);
|
|
8869
|
+
const nodeIsAnimated = node.isTarget && nodeHasChanged;
|
|
8870
|
+
|
|
8871
|
+
node.isAnimated = nodeIsAnimated;
|
|
8872
|
+
node.branchAdded = parentAdded || topLevelAdded;
|
|
8873
|
+
node.branchRemoved = parentRemoved || topLevelRemoved;
|
|
8874
|
+
node.branchNotRendered = parentNotRendered || node.measuredIsRemoved;
|
|
8875
|
+
|
|
8876
|
+
const sizeTolerance = 1;
|
|
8877
|
+
const widthChanged = Math.abs(node.properties.width - oldStateNode.properties.width) > sizeTolerance;
|
|
8878
|
+
const heightChanged = Math.abs(node.properties.height - oldStateNode.properties.height) > sizeTolerance;
|
|
8879
|
+
|
|
8880
|
+
node.sizeChanged = (widthChanged || heightChanged);
|
|
8881
|
+
|
|
8882
|
+
targets.push($el);
|
|
8883
|
+
|
|
8884
|
+
if (!node.isTarget) {
|
|
8885
|
+
frozen.push($el);
|
|
8886
|
+
if ((nodeHasChanged || node.sizeChanged) && parent && parent.isTarget && parent.isAnimated && parent.sizeChanged) {
|
|
8887
|
+
animatedFrozen.push($el);
|
|
8888
|
+
}
|
|
8889
|
+
}
|
|
8890
|
+
});
|
|
8891
|
+
|
|
8892
|
+
const defaults = {
|
|
8893
|
+
ease: setValue(params.ease, this.ease),
|
|
8894
|
+
duration: (/** @type {HTMLElement} */$el) => newState.nodes.get($el.dataset.layoutId).duration,
|
|
8895
|
+
delay: (/** @type {HTMLElement} */$el) => newState.nodes.get($el.dataset.layoutId).delay,
|
|
8896
|
+
};
|
|
8897
|
+
|
|
8898
|
+
this.timeline = createTimeline({
|
|
8899
|
+
onComplete: () => {
|
|
8900
|
+
// Make sure to call .cancel() after restoreNodeInlineStyles(node); otehrwise the commited styles get reverted
|
|
8901
|
+
if (this.transformAnimation) this.transformAnimation.cancel();
|
|
8902
|
+
newState.forEachRootNode(node => {
|
|
8903
|
+
restoreNodeVisualState(node);
|
|
8904
|
+
restoreNodeInlineStyles(node);
|
|
8272
8905
|
});
|
|
8273
|
-
|
|
8906
|
+
for (let i = 0, l = transformed.length; i < l; i++) {
|
|
8907
|
+
const $el = transformed[i];
|
|
8908
|
+
$el.style.transform = newState.getValue($el, 'transform');
|
|
8909
|
+
}
|
|
8910
|
+
this.root.classList.remove('is-animated');
|
|
8911
|
+
if (onComplete) onComplete(this);
|
|
8912
|
+
// Avoid CSS transitions at the end of the animation by restoring them on the next frame
|
|
8913
|
+
requestAnimationFrame(() => {
|
|
8914
|
+
if (this.root.classList.contains('is-animated')) return;
|
|
8915
|
+
restoreLayoutTransition(this.transitionMuteStore);
|
|
8916
|
+
});
|
|
8917
|
+
},
|
|
8918
|
+
onPause: () => {
|
|
8919
|
+
if (this.transformAnimation) this.transformAnimation.cancel();
|
|
8920
|
+
newState.forEachRootNode(restoreNodeVisualState);
|
|
8921
|
+
this.root.classList.remove('is-animated');
|
|
8922
|
+
if (onComplete) onComplete(this);
|
|
8923
|
+
},
|
|
8924
|
+
composition: false,
|
|
8925
|
+
defaults,
|
|
8926
|
+
});
|
|
8927
|
+
|
|
8928
|
+
if (targets.length) {
|
|
8929
|
+
|
|
8930
|
+
this.root.classList.add('is-animated');
|
|
8931
|
+
|
|
8932
|
+
for (let i = 0, l = targets.length; i < l; i++) {
|
|
8933
|
+
const $el = targets[i];
|
|
8934
|
+
const id = $el.dataset.layoutId;
|
|
8935
|
+
const oldNode = oldState.nodes.get(id);
|
|
8936
|
+
const newNode = newState.nodes.get(id);
|
|
8937
|
+
const oldNodeState = oldNode.properties;
|
|
8938
|
+
|
|
8939
|
+
// Make sure to mute all CSS transition before applying the oldState styles back
|
|
8940
|
+
muteNodeTransition(newNode);
|
|
8941
|
+
|
|
8942
|
+
// Don't animate dimensions and positions of inlined elements
|
|
8943
|
+
if (!newNode.isInlined) {
|
|
8944
|
+
// Display grid can mess with the absolute positioning, so set it to block during transition
|
|
8945
|
+
// if (oldNode.measuredDisplay === 'grid' || newNode.measuredDisplay === 'grid') $el.style.display = 'block';
|
|
8946
|
+
$el.style.display = 'block';
|
|
8947
|
+
// All children must be in position absolue
|
|
8948
|
+
if ($el !== root || this.absoluteCoords) {
|
|
8949
|
+
$el.style.position = this.absoluteCoords ? 'fixed' : 'absolute';
|
|
8950
|
+
$el.style.left = '0px';
|
|
8951
|
+
$el.style.top = '0px';
|
|
8952
|
+
$el.style.marginLeft = '0px';
|
|
8953
|
+
$el.style.marginTop = '0px';
|
|
8954
|
+
$el.style.translate = `${oldNodeState.x}px ${oldNodeState.y}px`;
|
|
8955
|
+
}
|
|
8956
|
+
if ($el === root && newNode.measuredPosition === 'static') {
|
|
8957
|
+
$el.style.position = 'relative';
|
|
8958
|
+
// Cancel left / trop in case the static element had muted values now activated by potision relative
|
|
8959
|
+
$el.style.left = '0px';
|
|
8960
|
+
$el.style.top = '0px';
|
|
8961
|
+
}
|
|
8962
|
+
$el.style.width = `${oldNodeState.width}px`;
|
|
8963
|
+
$el.style.height = `${oldNodeState.height}px`;
|
|
8964
|
+
// Overrides user defined min and max to prevents width and height clamping
|
|
8965
|
+
$el.style.minWidth = `auto`;
|
|
8966
|
+
$el.style.minHeight = `auto`;
|
|
8967
|
+
$el.style.maxWidth = `none`;
|
|
8968
|
+
$el.style.maxHeight = `none`;
|
|
8969
|
+
}
|
|
8274
8970
|
}
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8971
|
+
|
|
8972
|
+
// Restore the scroll position if the oldState differs from the current state
|
|
8973
|
+
if (oldState.scrollX !== window.scrollX || oldState.scrollY !== window.scrollY) {
|
|
8974
|
+
// Restoring in the next frame avoids race conditions if for example a waapi animation commit styles that affect the root height
|
|
8975
|
+
requestAnimationFrame(() => {
|
|
8976
|
+
window.scrollTo(oldState.scrollX, oldState.scrollY);
|
|
8977
|
+
});
|
|
8278
8978
|
}
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8979
|
+
|
|
8980
|
+
for (let i = 0, l = animated.length; i < l; i++) {
|
|
8981
|
+
const $el = animated[i];
|
|
8982
|
+
const id = $el.dataset.layoutId;
|
|
8983
|
+
const oldNode = oldState.nodes.get(id);
|
|
8984
|
+
const newNode = newState.nodes.get(id);
|
|
8985
|
+
const oldNodeState = oldNode.properties;
|
|
8986
|
+
const newNodeState = newNode.properties;
|
|
8987
|
+
let hasChanged = false;
|
|
8988
|
+
const animatedProps = {
|
|
8989
|
+
composition: 'none',
|
|
8990
|
+
// delay: (/** @type {HTMLElement} */$el) => newState.nodes.get($el.dataset.layoutId).delay,
|
|
8991
|
+
};
|
|
8992
|
+
if (!newNode.isInlined) {
|
|
8993
|
+
if (oldNodeState.width !== newNodeState.width) {
|
|
8994
|
+
animatedProps.width = [oldNodeState.width, newNodeState.width];
|
|
8995
|
+
hasChanged = true;
|
|
8996
|
+
}
|
|
8997
|
+
if (oldNodeState.height !== newNodeState.height) {
|
|
8998
|
+
animatedProps.height = [oldNodeState.height, newNodeState.height];
|
|
8999
|
+
hasChanged = true;
|
|
9000
|
+
}
|
|
9001
|
+
// If the node has transforms we handle the translate animation in wappi otherwise translate and other transforms can be out of sync
|
|
9002
|
+
// Always animate translate
|
|
9003
|
+
if (!newNode.hasTransform) {
|
|
9004
|
+
animatedProps.translate = [`${oldNodeState.x}px ${oldNodeState.y}px`, `${newNodeState.x}px ${newNodeState.y}px`];
|
|
9005
|
+
hasChanged = true;
|
|
9006
|
+
}
|
|
9007
|
+
}
|
|
9008
|
+
this.properties.forEach(prop => {
|
|
9009
|
+
const oldVal = oldNodeState[prop];
|
|
9010
|
+
const newVal = newNodeState[prop];
|
|
9011
|
+
if (prop !== 'transform' && oldVal !== newVal) {
|
|
9012
|
+
animatedProps[prop] = [oldVal, newVal];
|
|
9013
|
+
hasChanged = true;
|
|
9014
|
+
}
|
|
9015
|
+
});
|
|
9016
|
+
if (hasChanged) {
|
|
9017
|
+
this.timeline.add($el, animatedProps, 0);
|
|
9018
|
+
}
|
|
8291
9019
|
}
|
|
8292
|
-
|
|
9020
|
+
|
|
8293
9021
|
}
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
9022
|
+
|
|
9023
|
+
if (frozen.length) {
|
|
9024
|
+
|
|
9025
|
+
for (let i = 0, l = frozen.length; i < l; i++) {
|
|
9026
|
+
const $el = frozen[i];
|
|
9027
|
+
const oldNode = oldState.nodes.get($el.dataset.layoutId);
|
|
9028
|
+
if (!oldNode.isInlined) {
|
|
9029
|
+
const oldNodeState = oldState.get($el);
|
|
9030
|
+
$el.style.width = `${oldNodeState.width}px`;
|
|
9031
|
+
$el.style.height = `${oldNodeState.height}px`;
|
|
9032
|
+
// Overrides user defined min and max to prevents width and height clamping
|
|
9033
|
+
$el.style.minWidth = `auto`;
|
|
9034
|
+
$el.style.minHeight = `auto`;
|
|
9035
|
+
$el.style.maxWidth = `none`;
|
|
9036
|
+
$el.style.maxHeight = `none`;
|
|
9037
|
+
$el.style.translate = `${oldNodeState.x}px ${oldNodeState.y}px`;
|
|
9038
|
+
}
|
|
9039
|
+
this.properties.forEach(prop => {
|
|
9040
|
+
if (prop !== 'transform') {
|
|
9041
|
+
$el.style[prop] = `${oldState.getValue($el, prop)}`;
|
|
9042
|
+
}
|
|
9043
|
+
});
|
|
9044
|
+
}
|
|
9045
|
+
|
|
9046
|
+
for (let i = 0, l = frozen.length; i < l; i++) {
|
|
9047
|
+
const $el = frozen[i];
|
|
9048
|
+
const newNode = newState.nodes.get($el.dataset.layoutId);
|
|
9049
|
+
const newNodeState = newState.get($el);
|
|
9050
|
+
this.timeline.call(() => {
|
|
9051
|
+
if (!newNode.isInlined) {
|
|
9052
|
+
$el.style.width = `${newNodeState.width}px`;
|
|
9053
|
+
$el.style.height = `${newNodeState.height}px`;
|
|
9054
|
+
// Overrides user defined min and max to prevents width and height clamping
|
|
9055
|
+
$el.style.minWidth = `auto`;
|
|
9056
|
+
$el.style.minHeight = `auto`;
|
|
9057
|
+
$el.style.maxWidth = `none`;
|
|
9058
|
+
$el.style.maxHeight = `none`;
|
|
9059
|
+
$el.style.translate = `${newNodeState.x}px ${newNodeState.y}px`;
|
|
9060
|
+
}
|
|
9061
|
+
this.properties.forEach(prop => {
|
|
9062
|
+
if (prop !== 'transform') {
|
|
9063
|
+
$el.style[prop] = `${newState.getValue($el, prop)}`;
|
|
9064
|
+
}
|
|
9065
|
+
});
|
|
9066
|
+
}, newNode.delay + newNode.duration / 2);
|
|
9067
|
+
}
|
|
9068
|
+
|
|
9069
|
+
if (animatedFrozen.length) {
|
|
9070
|
+
const animatedFrozenParams = /** @type {AnimationParams} */({});
|
|
9071
|
+
if (frozenParams) {
|
|
9072
|
+
for (let prop in frozenParams) {
|
|
9073
|
+
animatedFrozenParams[prop] = [
|
|
9074
|
+
{ from: (/** @type {HTMLElement} */$el) => oldState.getValue($el, prop), ease: 'in(1.75)', to: frozenParams[prop] },
|
|
9075
|
+
{ from: frozenParams[prop], to: (/** @type {HTMLElement} */$el) => newState.getValue($el, prop), ease: 'out(1.75)' }
|
|
9076
|
+
];
|
|
9077
|
+
}
|
|
9078
|
+
}
|
|
9079
|
+
this.timeline.add(animatedFrozen, animatedFrozenParams, 0);
|
|
9080
|
+
}
|
|
9081
|
+
|
|
8304
9082
|
}
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
9083
|
+
|
|
9084
|
+
const transformedLength = transformed.length;
|
|
9085
|
+
|
|
9086
|
+
if (transformedLength) {
|
|
9087
|
+
// We only need to set the transform property here since translate is alread defined the targets loop
|
|
9088
|
+
for (let i = 0; i < transformedLength; i++) {
|
|
9089
|
+
const $el = transformed[i];
|
|
9090
|
+
$el.style.translate = `${oldState.get($el).x}px ${oldState.get($el).y}px`,
|
|
9091
|
+
$el.style.transform = oldState.getValue($el, 'transform');
|
|
9092
|
+
}
|
|
9093
|
+
this.transformAnimation = waapi.animate(transformed, {
|
|
9094
|
+
translate: (/** @type {HTMLElement} */$el) => `${newState.get($el).x}px ${newState.get($el).y}px`,
|
|
9095
|
+
transform: (/** @type {HTMLElement} */$el) => newState.getValue($el, 'transform'),
|
|
9096
|
+
autoplay: false,
|
|
9097
|
+
persist: true,
|
|
9098
|
+
...defaults,
|
|
9099
|
+
});
|
|
9100
|
+
this.timeline.sync(this.transformAnimation, 0);
|
|
8308
9101
|
}
|
|
8309
|
-
|
|
9102
|
+
|
|
9103
|
+
return this.timeline.init();
|
|
8310
9104
|
}
|
|
8311
9105
|
|
|
8312
|
-
|
|
8313
|
-
|
|
9106
|
+
/**
|
|
9107
|
+
* @param {(layout: this) => void} callback
|
|
9108
|
+
* @param {LayoutAnimationParams} [params]
|
|
9109
|
+
* @return {this}
|
|
9110
|
+
*/
|
|
9111
|
+
update(callback, params = {}) {
|
|
9112
|
+
this.record();
|
|
9113
|
+
callback(this);
|
|
9114
|
+
this.animate(params);
|
|
9115
|
+
return this;
|
|
8314
9116
|
}
|
|
8315
9117
|
}
|
|
8316
9118
|
|
|
8317
9119
|
/**
|
|
8318
|
-
* @param
|
|
8319
|
-
* @param
|
|
8320
|
-
* @return {
|
|
9120
|
+
* @param {DOMTargetSelector} root
|
|
9121
|
+
* @param {AutoLayoutParams} [params]
|
|
9122
|
+
* @return {AutoLayout}
|
|
8321
9123
|
*/
|
|
8322
|
-
const
|
|
9124
|
+
const createLayout = (root, params) => new AutoLayout(root, params);
|
|
9125
|
+
|
|
9126
|
+
// Chain-able utilities
|
|
9127
|
+
|
|
9128
|
+
const numberUtils = numberImports; // Needed to keep the import when bundling
|
|
9129
|
+
|
|
9130
|
+
const chainables = {};
|
|
8323
9131
|
|
|
8324
9132
|
/**
|
|
8325
|
-
* @
|
|
9133
|
+
* @callback UtilityFunction
|
|
9134
|
+
* @param {...*} args
|
|
9135
|
+
* @return {Number|String}
|
|
8326
9136
|
*
|
|
8327
|
-
* @param
|
|
8328
|
-
* @param
|
|
8329
|
-
* @return {
|
|
8330
|
-
*/
|
|
8331
|
-
const
|
|
8332
|
-
|
|
8333
|
-
|
|
9137
|
+
* @param {UtilityFunction} fn
|
|
9138
|
+
* @param {Number} [last=0]
|
|
9139
|
+
* @return {function(...(Number|String)): function(Number|String): (Number|String)}
|
|
9140
|
+
*/
|
|
9141
|
+
const curry = (fn, last = 0) => (...args) => last ? v => fn(...args, v) : v => fn(v, ...args);
|
|
9142
|
+
|
|
9143
|
+
/**
|
|
9144
|
+
* @param {Function} fn
|
|
9145
|
+
* @return {function(...(Number|String))}
|
|
9146
|
+
*/
|
|
9147
|
+
const chain = fn => {
|
|
9148
|
+
return (...args) => {
|
|
9149
|
+
const result = fn(...args);
|
|
9150
|
+
return new Proxy(noop, {
|
|
9151
|
+
apply: (_, __, [v]) => result(v),
|
|
9152
|
+
get: (_, prop) => chain(/**@param {...Number|String} nextArgs */(...nextArgs) => {
|
|
9153
|
+
const nextResult = chainables[prop](...nextArgs);
|
|
9154
|
+
return (/**@type {Number|String} */v) => nextResult(result(v));
|
|
9155
|
+
})
|
|
9156
|
+
});
|
|
9157
|
+
}
|
|
8334
9158
|
};
|
|
8335
9159
|
|
|
8336
|
-
|
|
9160
|
+
/**
|
|
9161
|
+
* @param {UtilityFunction} fn
|
|
9162
|
+
* @param {String} name
|
|
9163
|
+
* @param {Number} [right]
|
|
9164
|
+
* @return {function(...(Number|String)): UtilityFunction}
|
|
9165
|
+
*/
|
|
9166
|
+
const makeChainable = (name, fn, right = 0) => {
|
|
9167
|
+
const chained = (...args) => (args.length < fn.length ? chain(curry(fn, right)) : fn)(...args);
|
|
9168
|
+
if (!chainables[name]) chainables[name] = chained;
|
|
9169
|
+
return chained;
|
|
9170
|
+
};
|
|
9171
|
+
|
|
9172
|
+
/**
|
|
9173
|
+
* @typedef {Object} ChainablesMap
|
|
9174
|
+
* @property {ChainedClamp} clamp
|
|
9175
|
+
* @property {ChainedRound} round
|
|
9176
|
+
* @property {ChainedSnap} snap
|
|
9177
|
+
* @property {ChainedWrap} wrap
|
|
9178
|
+
* @property {ChainedLerp} lerp
|
|
9179
|
+
* @property {ChainedDamp} damp
|
|
9180
|
+
* @property {ChainedMapRange} mapRange
|
|
9181
|
+
* @property {ChainedRoundPad} roundPad
|
|
9182
|
+
* @property {ChainedPadStart} padStart
|
|
9183
|
+
* @property {ChainedPadEnd} padEnd
|
|
9184
|
+
* @property {ChainedDegToRad} degToRad
|
|
9185
|
+
* @property {ChainedRadToDeg} radToDeg
|
|
9186
|
+
*/
|
|
9187
|
+
|
|
9188
|
+
/**
|
|
9189
|
+
* @callback ChainedUtilsResult
|
|
9190
|
+
* @param {Number} value - The value to process through the chained operations
|
|
9191
|
+
* @return {Number} The processed result
|
|
9192
|
+
*/
|
|
9193
|
+
|
|
9194
|
+
/**
|
|
9195
|
+
* @typedef {ChainablesMap & ChainedUtilsResult} ChainableUtil
|
|
9196
|
+
*/
|
|
9197
|
+
|
|
9198
|
+
// Chainable
|
|
9199
|
+
|
|
9200
|
+
/**
|
|
9201
|
+
* @callback ChainedRoundPad
|
|
9202
|
+
* @param {Number} decimalLength - Number of decimal places
|
|
9203
|
+
* @return {ChainableUtil}
|
|
9204
|
+
*/
|
|
9205
|
+
const roundPad = /** @type {typeof numberUtils.roundPad & ChainedRoundPad} */(makeChainable('roundPad', numberUtils.roundPad));
|
|
9206
|
+
|
|
9207
|
+
/**
|
|
9208
|
+
* @callback ChainedPadStart
|
|
9209
|
+
* @param {Number} totalLength - Target length
|
|
9210
|
+
* @param {String} padString - String to pad with
|
|
9211
|
+
* @return {ChainableUtil}
|
|
9212
|
+
*/
|
|
9213
|
+
const padStart = /** @type {typeof numberUtils.padStart & ChainedPadStart} */(makeChainable('padStart', numberUtils.padStart));
|
|
9214
|
+
|
|
9215
|
+
/**
|
|
9216
|
+
* @callback ChainedPadEnd
|
|
9217
|
+
* @param {Number} totalLength - Target length
|
|
9218
|
+
* @param {String} padString - String to pad with
|
|
9219
|
+
* @return {ChainableUtil}
|
|
9220
|
+
*/
|
|
9221
|
+
const padEnd = /** @type {typeof numberUtils.padEnd & ChainedPadEnd} */(makeChainable('padEnd', numberUtils.padEnd));
|
|
9222
|
+
|
|
9223
|
+
/**
|
|
9224
|
+
* @callback ChainedWrap
|
|
9225
|
+
* @param {Number} min - Minimum boundary
|
|
9226
|
+
* @param {Number} max - Maximum boundary
|
|
9227
|
+
* @return {ChainableUtil}
|
|
9228
|
+
*/
|
|
9229
|
+
const wrap = /** @type {typeof numberUtils.wrap & ChainedWrap} */(makeChainable('wrap', numberUtils.wrap));
|
|
9230
|
+
|
|
9231
|
+
/**
|
|
9232
|
+
* @callback ChainedMapRange
|
|
9233
|
+
* @param {Number} inLow - Input range minimum
|
|
9234
|
+
* @param {Number} inHigh - Input range maximum
|
|
9235
|
+
* @param {Number} outLow - Output range minimum
|
|
9236
|
+
* @param {Number} outHigh - Output range maximum
|
|
9237
|
+
* @return {ChainableUtil}
|
|
9238
|
+
*/
|
|
9239
|
+
const mapRange = /** @type {typeof numberUtils.mapRange & ChainedMapRange} */(makeChainable('mapRange', numberUtils.mapRange));
|
|
9240
|
+
|
|
9241
|
+
/**
|
|
9242
|
+
* @callback ChainedDegToRad
|
|
9243
|
+
* @return {ChainableUtil}
|
|
9244
|
+
*/
|
|
9245
|
+
const degToRad = /** @type {typeof numberUtils.degToRad & ChainedDegToRad} */(makeChainable('degToRad', numberUtils.degToRad));
|
|
9246
|
+
|
|
9247
|
+
/**
|
|
9248
|
+
* @callback ChainedRadToDeg
|
|
9249
|
+
* @return {ChainableUtil}
|
|
9250
|
+
*/
|
|
9251
|
+
const radToDeg = /** @type {typeof numberUtils.radToDeg & ChainedRadToDeg} */(makeChainable('radToDeg', numberUtils.radToDeg));
|
|
9252
|
+
|
|
9253
|
+
/**
|
|
9254
|
+
* @callback ChainedSnap
|
|
9255
|
+
* @param {Number|Array<Number>} increment - Step size or array of snap points
|
|
9256
|
+
* @return {ChainableUtil}
|
|
9257
|
+
*/
|
|
9258
|
+
const snap = /** @type {typeof numberUtils.snap & ChainedSnap} */(makeChainable('snap', numberUtils.snap));
|
|
9259
|
+
|
|
9260
|
+
/**
|
|
9261
|
+
* @callback ChainedClamp
|
|
9262
|
+
* @param {Number} min - Minimum boundary
|
|
9263
|
+
* @param {Number} max - Maximum boundary
|
|
9264
|
+
* @return {ChainableUtil}
|
|
9265
|
+
*/
|
|
9266
|
+
const clamp = /** @type {typeof numberUtils.clamp & ChainedClamp} */(makeChainable('clamp', numberUtils.clamp));
|
|
9267
|
+
|
|
9268
|
+
/**
|
|
9269
|
+
* @callback ChainedRound
|
|
9270
|
+
* @param {Number} decimalLength - Number of decimal places
|
|
9271
|
+
* @return {ChainableUtil}
|
|
9272
|
+
*/
|
|
9273
|
+
const round = /** @type {typeof numberUtils.round & ChainedRound} */(makeChainable('round', numberUtils.round));
|
|
9274
|
+
|
|
9275
|
+
/**
|
|
9276
|
+
* @callback ChainedLerp
|
|
9277
|
+
* @param {Number} start - Starting value
|
|
9278
|
+
* @param {Number} end - Ending value
|
|
9279
|
+
* @return {ChainableUtil}
|
|
9280
|
+
*/
|
|
9281
|
+
const lerp = /** @type {typeof numberUtils.lerp & ChainedLerp} */(makeChainable('lerp', numberUtils.lerp, 1));
|
|
9282
|
+
|
|
9283
|
+
/**
|
|
9284
|
+
* @callback ChainedDamp
|
|
9285
|
+
* @param {Number} start - Starting value
|
|
9286
|
+
* @param {Number} end - Target value
|
|
9287
|
+
* @param {Number} deltaTime - Delta time in ms
|
|
9288
|
+
* @return {ChainableUtil}
|
|
9289
|
+
*/
|
|
9290
|
+
const damp = /** @type {typeof numberUtils.damp & ChainedDamp} */(makeChainable('damp', numberUtils.damp, 1));
|
|
9291
|
+
|
|
9292
|
+
/**
|
|
9293
|
+
* Generate a random number between optional min and max (inclusive) and decimal precision
|
|
9294
|
+
*
|
|
9295
|
+
* @callback RandomNumberGenerator
|
|
9296
|
+
* @param {Number} [min=0] - The minimum value (inclusive)
|
|
9297
|
+
* @param {Number} [max=1] - The maximum value (inclusive)
|
|
9298
|
+
* @param {Number} [decimalLength=0] - Number of decimal places to round to
|
|
9299
|
+
* @return {Number} A random number between min and max
|
|
9300
|
+
*/
|
|
9301
|
+
|
|
9302
|
+
/**
|
|
9303
|
+
* Generates a random number between min and max (inclusive) with optional decimal precision
|
|
9304
|
+
*
|
|
9305
|
+
* @type {RandomNumberGenerator}
|
|
9306
|
+
*/
|
|
9307
|
+
const random = (min = 0, max = 1, decimalLength = 0) => {
|
|
9308
|
+
const m = 10 ** decimalLength;
|
|
9309
|
+
return Math.floor((Math.random() * (max - min + (1 / m)) + min) * m) / m;
|
|
9310
|
+
};
|
|
9311
|
+
|
|
9312
|
+
let _seed = 0;
|
|
9313
|
+
|
|
9314
|
+
/**
|
|
9315
|
+
* Creates a seeded pseudorandom number generator function
|
|
9316
|
+
*
|
|
9317
|
+
* @param {Number} [seed] - The seed value for the random number generator
|
|
9318
|
+
* @param {Number} [seededMin=0] - The minimum default value (inclusive) of the returned function
|
|
9319
|
+
* @param {Number} [seededMax=1] - The maximum default value (inclusive) of the returned function
|
|
9320
|
+
* @param {Number} [seededDecimalLength=0] - Default number of decimal places to round to of the returned function
|
|
9321
|
+
* @return {RandomNumberGenerator} A function to generate a random number between optional min and max (inclusive) and decimal precision
|
|
9322
|
+
*/
|
|
9323
|
+
const createSeededRandom = (seed, seededMin = 0, seededMax = 1, seededDecimalLength = 0) => {
|
|
9324
|
+
let t = seed === undefined ? _seed++ : seed;
|
|
9325
|
+
return (min = seededMin, max = seededMax, decimalLength = seededDecimalLength) => {
|
|
9326
|
+
t += 0x6D2B79F5;
|
|
9327
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
9328
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
9329
|
+
const m = 10 ** decimalLength;
|
|
9330
|
+
return Math.floor(((((t ^ t >>> 14) >>> 0) / 4294967296) * (max - min + (1 / m)) + min) * m) / m;
|
|
9331
|
+
}
|
|
9332
|
+
};
|
|
9333
|
+
|
|
9334
|
+
/**
|
|
9335
|
+
* Picks a random element from an array or a string
|
|
9336
|
+
*
|
|
9337
|
+
* @template T
|
|
9338
|
+
* @param {String|Array<T>} items - The array or string to pick from
|
|
9339
|
+
* @return {String|T} A random element from the array or character from the string
|
|
9340
|
+
*/
|
|
9341
|
+
const randomPick = items => items[random(0, items.length - 1)];
|
|
9342
|
+
|
|
9343
|
+
/**
|
|
9344
|
+
* Shuffles an array in-place using the Fisher-Yates algorithm
|
|
9345
|
+
* Adapted from https://bost.ocks.org/mike/shuffle/
|
|
9346
|
+
*
|
|
9347
|
+
* @param {Array} items - The array to shuffle (will be modified in-place)
|
|
9348
|
+
* @return {Array} The same array reference, now shuffled
|
|
9349
|
+
*/
|
|
9350
|
+
const shuffle = items => {
|
|
9351
|
+
let m = items.length, t, i;
|
|
9352
|
+
while (m) { i = random(0, --m); t = items[m]; items[m] = items[i]; items[i] = t; }
|
|
9353
|
+
return items;
|
|
9354
|
+
};
|
|
9355
|
+
|
|
9356
|
+
|
|
9357
|
+
|
|
9358
|
+
|
|
9359
|
+
|
|
9360
|
+
/**
|
|
9361
|
+
* @overload
|
|
9362
|
+
* @param {Number} val
|
|
9363
|
+
* @param {StaggerParams} [params]
|
|
9364
|
+
* @return {StaggerFunction<Number>}
|
|
9365
|
+
*/
|
|
9366
|
+
/**
|
|
9367
|
+
* @overload
|
|
9368
|
+
* @param {String} val
|
|
9369
|
+
* @param {StaggerParams} [params]
|
|
9370
|
+
* @return {StaggerFunction<String>}
|
|
9371
|
+
*/
|
|
9372
|
+
/**
|
|
9373
|
+
* @overload
|
|
9374
|
+
* @param {[Number, Number]} val
|
|
9375
|
+
* @param {StaggerParams} [params]
|
|
9376
|
+
* @return {StaggerFunction<Number>}
|
|
9377
|
+
*/
|
|
9378
|
+
/**
|
|
9379
|
+
* @overload
|
|
9380
|
+
* @param {[String, String]} val
|
|
9381
|
+
* @param {StaggerParams} [params]
|
|
9382
|
+
* @return {StaggerFunction<String>}
|
|
9383
|
+
*/
|
|
9384
|
+
/**
|
|
9385
|
+
* @param {Number|String|[Number, Number]|[String, String]} val The staggered value or range
|
|
9386
|
+
* @param {StaggerParams} [params] The stagger parameters
|
|
9387
|
+
* @return {StaggerFunction<Number|String>}
|
|
9388
|
+
*/
|
|
9389
|
+
const stagger = (val, params = {}) => {
|
|
9390
|
+
let values = [];
|
|
9391
|
+
let maxValue = 0;
|
|
9392
|
+
const from = params.from;
|
|
9393
|
+
const reversed = params.reversed;
|
|
9394
|
+
const ease = params.ease;
|
|
9395
|
+
const hasEasing = !isUnd(ease);
|
|
9396
|
+
const hasSpring = hasEasing && !isUnd(/** @type {Spring} */(ease).ease);
|
|
9397
|
+
const staggerEase = hasSpring ? /** @type {Spring} */(ease).ease : hasEasing ? parseEase(ease) : null;
|
|
9398
|
+
const grid = params.grid;
|
|
9399
|
+
const axis = params.axis;
|
|
9400
|
+
const customTotal = params.total;
|
|
9401
|
+
const fromFirst = isUnd(from) || from === 0 || from === 'first';
|
|
9402
|
+
const fromCenter = from === 'center';
|
|
9403
|
+
const fromLast = from === 'last';
|
|
9404
|
+
const fromRandom = from === 'random';
|
|
9405
|
+
const isRange = isArr(val);
|
|
9406
|
+
const useProp = params.use;
|
|
9407
|
+
const val1 = isRange ? parseNumber(val[0]) : parseNumber(val);
|
|
9408
|
+
const val2 = isRange ? parseNumber(val[1]) : 0;
|
|
9409
|
+
const unitMatch = unitsExecRgx.exec((isRange ? val[1] : val) + emptyString);
|
|
9410
|
+
const start = params.start || 0 + (isRange ? val1 : 0);
|
|
9411
|
+
let fromIndex = fromFirst ? 0 : isNum(from) ? from : 0;
|
|
9412
|
+
return (target, i, t, tl) => {
|
|
9413
|
+
const [ registeredTarget ] = registerTargets(target);
|
|
9414
|
+
const total = isUnd(customTotal) ? t : customTotal;
|
|
9415
|
+
const customIndex = !isUnd(useProp) ? isFnc(useProp) ? useProp(registeredTarget, i, total) : getOriginalAnimatableValue(registeredTarget, useProp) : false;
|
|
9416
|
+
const staggerIndex = isNum(customIndex) || isStr(customIndex) && isNum(+customIndex) ? +customIndex : i;
|
|
9417
|
+
if (fromCenter) fromIndex = (total - 1) / 2;
|
|
9418
|
+
if (fromLast) fromIndex = total - 1;
|
|
9419
|
+
if (!values.length) {
|
|
9420
|
+
for (let index = 0; index < total; index++) {
|
|
9421
|
+
if (!grid) {
|
|
9422
|
+
values.push(abs(fromIndex - index));
|
|
9423
|
+
} else {
|
|
9424
|
+
const fromX = !fromCenter ? fromIndex % grid[0] : (grid[0] - 1) / 2;
|
|
9425
|
+
const fromY = !fromCenter ? floor(fromIndex / grid[0]) : (grid[1] - 1) / 2;
|
|
9426
|
+
const toX = index % grid[0];
|
|
9427
|
+
const toY = floor(index / grid[0]);
|
|
9428
|
+
const distanceX = fromX - toX;
|
|
9429
|
+
const distanceY = fromY - toY;
|
|
9430
|
+
let value = sqrt(distanceX * distanceX + distanceY * distanceY);
|
|
9431
|
+
if (axis === 'x') value = -distanceX;
|
|
9432
|
+
if (axis === 'y') value = -distanceY;
|
|
9433
|
+
values.push(value);
|
|
9434
|
+
}
|
|
9435
|
+
maxValue = max(...values);
|
|
9436
|
+
}
|
|
9437
|
+
if (staggerEase) values = values.map(val => staggerEase(val / maxValue) * maxValue);
|
|
9438
|
+
if (reversed) values = values.map(val => axis ? (val < 0) ? val * -1 : -val : abs(maxValue - val));
|
|
9439
|
+
if (fromRandom) values = shuffle(values);
|
|
9440
|
+
}
|
|
9441
|
+
const spacing = isRange ? (val2 - val1) / maxValue : val1;
|
|
9442
|
+
const offset = tl ? parseTimelinePosition(tl, isUnd(params.start) ? tl.iterationDuration : start) : /** @type {Number} */(start);
|
|
9443
|
+
/** @type {String|Number} */
|
|
9444
|
+
let output = offset + ((spacing * round$1(values[staggerIndex], 2)) || 0);
|
|
9445
|
+
if (params.modifier) output = params.modifier(output);
|
|
9446
|
+
if (unitMatch) output = `${output}${unitMatch[2]}`;
|
|
9447
|
+
return output;
|
|
9448
|
+
}
|
|
9449
|
+
};
|
|
9450
|
+
|
|
9451
|
+
var index$2 = /*#__PURE__*/Object.freeze({
|
|
8337
9452
|
__proto__: null,
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
9453
|
+
$: registerTargets,
|
|
9454
|
+
clamp: clamp,
|
|
9455
|
+
cleanInlineStyles: cleanInlineStyles,
|
|
9456
|
+
createSeededRandom: createSeededRandom,
|
|
9457
|
+
damp: damp,
|
|
9458
|
+
degToRad: degToRad,
|
|
9459
|
+
get: get,
|
|
9460
|
+
keepTime: keepTime,
|
|
9461
|
+
lerp: lerp,
|
|
9462
|
+
mapRange: mapRange,
|
|
9463
|
+
padEnd: padEnd,
|
|
9464
|
+
padStart: padStart,
|
|
9465
|
+
radToDeg: radToDeg,
|
|
9466
|
+
random: random,
|
|
9467
|
+
randomPick: randomPick,
|
|
9468
|
+
remove: remove,
|
|
9469
|
+
round: round,
|
|
9470
|
+
roundPad: roundPad,
|
|
9471
|
+
set: set,
|
|
9472
|
+
shuffle: shuffle,
|
|
9473
|
+
snap: snap,
|
|
9474
|
+
stagger: stagger,
|
|
9475
|
+
sync: sync,
|
|
9476
|
+
wrap: wrap
|
|
8341
9477
|
});
|
|
8342
9478
|
|
|
8343
9479
|
|
|
8344
9480
|
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
|
|
8349
|
-
/**
|
|
8350
|
-
* Converts an easing function into a valid CSS linear() timing function string
|
|
8351
|
-
* @param {EasingFunction} fn
|
|
8352
|
-
* @param {number} [samples=100]
|
|
8353
|
-
* @returns {string} CSS linear() timing function
|
|
8354
|
-
*/
|
|
8355
|
-
const easingToLinear = (fn, samples = 100) => {
|
|
8356
|
-
const points = [];
|
|
8357
|
-
for (let i = 0; i <= samples; i++) points.push(round$1(fn(i / samples), 4));
|
|
8358
|
-
return `linear(${points.join(', ')})`;
|
|
8359
|
-
};
|
|
8360
|
-
|
|
8361
|
-
const WAAPIEasesLookups = {};
|
|
8362
|
-
|
|
8363
9481
|
/**
|
|
8364
|
-
* @param {
|
|
8365
|
-
* @return {
|
|
9482
|
+
* @param {TargetsParam} path
|
|
9483
|
+
* @return {SVGGeometryElement|void}
|
|
8366
9484
|
*/
|
|
8367
|
-
const
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
if (
|
|
8373
|
-
stringStartsWith(ease, 'linear') ||
|
|
8374
|
-
stringStartsWith(ease, 'cubic-') ||
|
|
8375
|
-
stringStartsWith(ease, 'steps') ||
|
|
8376
|
-
stringStartsWith(ease, 'ease')
|
|
8377
|
-
) {
|
|
8378
|
-
parsedEase = ease;
|
|
8379
|
-
} else if (stringStartsWith(ease, 'cubicB')) {
|
|
8380
|
-
parsedEase = toLowerCase(ease);
|
|
8381
|
-
} else {
|
|
8382
|
-
const parsed = parseEaseString(ease);
|
|
8383
|
-
if (isFnc(parsed)) parsedEase = parsed === none ? 'linear' : easingToLinear(parsed);
|
|
8384
|
-
}
|
|
8385
|
-
// Only cache string based easing name, otherwise function arguments get lost
|
|
8386
|
-
WAAPIEasesLookups[ease] = parsedEase;
|
|
8387
|
-
} else if (isFnc(ease)) {
|
|
8388
|
-
const easing = easingToLinear(ease);
|
|
8389
|
-
if (easing) parsedEase = easing;
|
|
8390
|
-
} else if (/** @type {Spring} */(ease).ease) {
|
|
8391
|
-
parsedEase = easingToLinear(/** @type {Spring} */(ease).ease);
|
|
8392
|
-
}
|
|
8393
|
-
return parsedEase;
|
|
9485
|
+
const getPath = path => {
|
|
9486
|
+
const parsedTargets = parseTargets(path);
|
|
9487
|
+
const $parsedSvg = /** @type {SVGGeometryElement} */(parsedTargets[0]);
|
|
9488
|
+
if (!$parsedSvg || !isSvg($parsedSvg)) return console.warn(`${path} is not a valid SVGGeometryElement`);
|
|
9489
|
+
return $parsedSvg;
|
|
8394
9490
|
};
|
|
8395
9491
|
|
|
8396
|
-
const transformsShorthands = ['x', 'y', 'z'];
|
|
8397
|
-
const commonDefaultPXProperties = [
|
|
8398
|
-
'perspective',
|
|
8399
|
-
'width',
|
|
8400
|
-
'height',
|
|
8401
|
-
'margin',
|
|
8402
|
-
'padding',
|
|
8403
|
-
'top',
|
|
8404
|
-
'right',
|
|
8405
|
-
'bottom',
|
|
8406
|
-
'left',
|
|
8407
|
-
'borderWidth',
|
|
8408
|
-
'fontSize',
|
|
8409
|
-
'borderRadius',
|
|
8410
|
-
...transformsShorthands
|
|
8411
|
-
];
|
|
8412
9492
|
|
|
8413
|
-
const validIndividualTransforms = /*#__PURE__*/ (() => [...transformsShorthands, ...validTransforms.filter(t => ['X', 'Y', 'Z'].some(axis => t.endsWith(axis)))])();
|
|
8414
9493
|
|
|
8415
|
-
|
|
9494
|
+
// Motion path animation
|
|
8416
9495
|
|
|
8417
9496
|
/**
|
|
8418
|
-
* @param
|
|
8419
|
-
* @param
|
|
8420
|
-
* @param
|
|
8421
|
-
* @param
|
|
8422
|
-
* @param
|
|
8423
|
-
* @return {
|
|
9497
|
+
* @param {SVGGeometryElement} $path
|
|
9498
|
+
* @param {Number} totalLength
|
|
9499
|
+
* @param {Number} progress
|
|
9500
|
+
* @param {Number} lookup
|
|
9501
|
+
* @param {Boolean} shouldClamp
|
|
9502
|
+
* @return {DOMPoint}
|
|
8424
9503
|
*/
|
|
8425
|
-
const
|
|
8426
|
-
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
return `${v}`;
|
|
9504
|
+
const getPathPoint = ($path, totalLength, progress, lookup, shouldClamp) => {
|
|
9505
|
+
const point = progress + lookup;
|
|
9506
|
+
const pointOnPath = shouldClamp
|
|
9507
|
+
? Math.max(0, Math.min(point, totalLength)) // Clamp between 0 and totalLength
|
|
9508
|
+
: (point % totalLength + totalLength) % totalLength; // Wrap around
|
|
9509
|
+
return $path.getPointAtLength(pointOnPath);
|
|
8432
9510
|
};
|
|
8433
9511
|
|
|
8434
9512
|
/**
|
|
8435
|
-
* @param
|
|
8436
|
-
* @param
|
|
8437
|
-
* @param
|
|
8438
|
-
* @
|
|
8439
|
-
* @param {Number} i
|
|
8440
|
-
* @param {Number} targetsLength
|
|
8441
|
-
* @return {WAAPITweenValue}
|
|
9513
|
+
* @param {SVGGeometryElement} $path
|
|
9514
|
+
* @param {String} pathProperty
|
|
9515
|
+
* @param {Number} [offset=0]
|
|
9516
|
+
* @return {FunctionValue}
|
|
8442
9517
|
*/
|
|
8443
|
-
const
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
const
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
|
|
9518
|
+
const getPathProgess = ($path, pathProperty, offset = 0) => {
|
|
9519
|
+
return $el => {
|
|
9520
|
+
const totalLength = +($path.getTotalLength());
|
|
9521
|
+
const inSvg = $el[isSvgSymbol];
|
|
9522
|
+
const ctm = $path.getCTM();
|
|
9523
|
+
const shouldClamp = offset === 0;
|
|
9524
|
+
/** @type {TweenObjectValue} */
|
|
9525
|
+
return {
|
|
9526
|
+
from: 0,
|
|
9527
|
+
to: totalLength,
|
|
9528
|
+
/** @type {TweenModifier} */
|
|
9529
|
+
modifier: progress => {
|
|
9530
|
+
const offsetLength = offset * totalLength;
|
|
9531
|
+
const newProgress = progress + offsetLength;
|
|
9532
|
+
if (pathProperty === 'a') {
|
|
9533
|
+
const p0 = getPathPoint($path, totalLength, newProgress, -1, shouldClamp);
|
|
9534
|
+
const p1 = getPathPoint($path, totalLength, newProgress, 1, shouldClamp);
|
|
9535
|
+
return atan2(p1.y - p0.y, p1.x - p0.x) * 180 / PI;
|
|
9536
|
+
} else {
|
|
9537
|
+
const p = getPathPoint($path, totalLength, newProgress, 0, shouldClamp);
|
|
9538
|
+
return pathProperty === 'x' ?
|
|
9539
|
+
inSvg || !ctm ? p.x : p.x * ctm.a + p.y * ctm.c + ctm.e :
|
|
9540
|
+
inSvg || !ctm ? p.y : p.x * ctm.b + p.y * ctm.d + ctm.f
|
|
9541
|
+
}
|
|
9542
|
+
}
|
|
9543
|
+
}
|
|
8452
9544
|
}
|
|
8453
|
-
return tweenValue;
|
|
8454
9545
|
};
|
|
8455
9546
|
|
|
8456
|
-
class WAAPIAnimation {
|
|
8457
9547
|
/**
|
|
8458
|
-
* @param {
|
|
8459
|
-
* @param {
|
|
9548
|
+
* @param {TargetsParam} path
|
|
9549
|
+
* @param {Number} [offset=0]
|
|
8460
9550
|
*/
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
|
|
8464
|
-
|
|
8465
|
-
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
validTransforms.forEach(t => {
|
|
8471
|
-
const isSkew = stringStartsWith(t, 'skew');
|
|
8472
|
-
const isScale = stringStartsWith(t, 'scale');
|
|
8473
|
-
const isRotate = stringStartsWith(t, 'rotate');
|
|
8474
|
-
const isTranslate = stringStartsWith(t, 'translate');
|
|
8475
|
-
const isAngle = isRotate || isSkew;
|
|
8476
|
-
const syntax = isAngle ? '<angle>' : isScale ? "<number>" : isTranslate ? "<length-percentage>" : "*";
|
|
8477
|
-
try {
|
|
8478
|
-
CSS.registerProperty({
|
|
8479
|
-
name: '--' + t,
|
|
8480
|
-
syntax,
|
|
8481
|
-
inherits: false,
|
|
8482
|
-
initialValue: isTranslate ? '0px' : isAngle ? '0deg' : isScale ? '1' : '0',
|
|
8483
|
-
});
|
|
8484
|
-
} catch {} });
|
|
8485
|
-
transformsPropertiesRegistered = true;
|
|
8486
|
-
}
|
|
8487
|
-
}
|
|
8488
|
-
|
|
8489
|
-
const parsedTargets = registerTargets(targets);
|
|
8490
|
-
const targetsLength = parsedTargets.length;
|
|
8491
|
-
|
|
8492
|
-
if (!targetsLength) {
|
|
8493
|
-
console.warn(`No target found. Make sure the element you're trying to animate is accessible before creating your animation.`);
|
|
8494
|
-
}
|
|
8495
|
-
|
|
8496
|
-
const ease = setValue(params.ease, parseWAAPIEasing(globals.defaults.ease));
|
|
8497
|
-
const spring = /** @type {Spring} */(ease).ease && ease;
|
|
8498
|
-
const autoplay = setValue(params.autoplay, globals.defaults.autoplay);
|
|
8499
|
-
const scroll = autoplay && /** @type {ScrollObserver} */(autoplay).link ? autoplay : false;
|
|
8500
|
-
const alternate = params.alternate && /** @type {Boolean} */(params.alternate) === true;
|
|
8501
|
-
const reversed = params.reversed && /** @type {Boolean} */(params.reversed) === true;
|
|
8502
|
-
const loop = setValue(params.loop, globals.defaults.loop);
|
|
8503
|
-
const iterations = /** @type {Number} */((loop === true || loop === Infinity) ? Infinity : isNum(loop) ? loop + 1 : 1);
|
|
8504
|
-
/** @type {PlaybackDirection} */
|
|
8505
|
-
const direction = alternate ? reversed ? 'alternate-reverse' : 'alternate' : reversed ? 'reverse' : 'normal';
|
|
8506
|
-
/** @type {FillMode} */
|
|
8507
|
-
const fill = 'both'; // We use 'both' here because the animation can be reversed during playback
|
|
8508
|
-
/** @type {String} */
|
|
8509
|
-
const easing = parseWAAPIEasing(ease);
|
|
8510
|
-
const timeScale = (globals.timeScale === 1 ? 1 : K);
|
|
8511
|
-
|
|
8512
|
-
/** @type {DOMTargetsArray}] */
|
|
8513
|
-
this.targets = parsedTargets;
|
|
8514
|
-
/** @type {Array<globalThis.Animation>}] */
|
|
8515
|
-
this.animations = [];
|
|
8516
|
-
/** @type {globalThis.Animation}] */
|
|
8517
|
-
this.controlAnimation = null;
|
|
8518
|
-
/** @type {Callback<this>} */
|
|
8519
|
-
this.onComplete = params.onComplete || /** @type {Callback<WAAPIAnimation>} */(/** @type {unknown} */(globals.defaults.onComplete));
|
|
8520
|
-
/** @type {Number} */
|
|
8521
|
-
this.duration = 0;
|
|
8522
|
-
/** @type {Boolean} */
|
|
8523
|
-
this.muteCallbacks = false;
|
|
8524
|
-
/** @type {Boolean} */
|
|
8525
|
-
this.completed = false;
|
|
8526
|
-
/** @type {Boolean} */
|
|
8527
|
-
this.paused = !autoplay || scroll !== false;
|
|
8528
|
-
/** @type {Boolean} */
|
|
8529
|
-
this.reversed = reversed;
|
|
8530
|
-
/** @type {Boolean} */
|
|
8531
|
-
this.persist = setValue(params.persist, globals.defaults.persist);
|
|
8532
|
-
/** @type {Boolean|ScrollObserver} */
|
|
8533
|
-
this.autoplay = autoplay;
|
|
8534
|
-
/** @type {Number} */
|
|
8535
|
-
this._speed = setValue(params.playbackRate, globals.defaults.playbackRate);
|
|
8536
|
-
/** @type {Function} */
|
|
8537
|
-
this._resolve = noop; // Used by .then()
|
|
8538
|
-
/** @type {Number} */
|
|
8539
|
-
this._completed = 0;
|
|
8540
|
-
/** @type {Array.<Object>} */
|
|
8541
|
-
this._inlineStyles = [];
|
|
8542
|
-
|
|
8543
|
-
parsedTargets.forEach(($el, i) => {
|
|
9551
|
+
const createMotionPath = (path, offset = 0) => {
|
|
9552
|
+
const $path = getPath(path);
|
|
9553
|
+
if (!$path) return;
|
|
9554
|
+
return {
|
|
9555
|
+
translateX: getPathProgess($path, 'x', offset),
|
|
9556
|
+
translateY: getPathProgess($path, 'y', offset),
|
|
9557
|
+
rotate: getPathProgess($path, 'a', offset),
|
|
9558
|
+
}
|
|
9559
|
+
};
|
|
8544
9560
|
|
|
8545
|
-
const cachedTransforms = $el[transformsSymbol];
|
|
8546
|
-
const hasIndividualTransforms = validIndividualTransforms.some(t => params.hasOwnProperty(t));
|
|
8547
|
-
const elStyle = $el.style;
|
|
8548
|
-
const inlineStyles = this._inlineStyles[i] = {};
|
|
8549
9561
|
|
|
8550
|
-
/** @type {Number} */
|
|
8551
|
-
const duration = (spring ? /** @type {Spring} */(spring).settlingDuration : getFunctionValue(setValue(params.duration, globals.defaults.duration), $el, i, targetsLength)) * timeScale;
|
|
8552
|
-
/** @type {Number} */
|
|
8553
|
-
const delay = getFunctionValue(setValue(params.delay, globals.defaults.delay), $el, i, targetsLength) * timeScale;
|
|
8554
|
-
/** @type {CompositeOperation} */
|
|
8555
|
-
const composite = /** @type {CompositeOperation} */(setValue(params.composition, 'replace'));
|
|
8556
9562
|
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
9563
|
+
/**
|
|
9564
|
+
* @param {SVGGeometryElement} [$el]
|
|
9565
|
+
* @return {Number}
|
|
9566
|
+
*/
|
|
9567
|
+
const getScaleFactor = $el => {
|
|
9568
|
+
let scaleFactor = 1;
|
|
9569
|
+
if ($el && $el.getCTM) {
|
|
9570
|
+
const ctm = $el.getCTM();
|
|
9571
|
+
if (ctm) {
|
|
9572
|
+
const scaleX = sqrt(ctm.a * ctm.a + ctm.b * ctm.b);
|
|
9573
|
+
const scaleY = sqrt(ctm.c * ctm.c + ctm.d * ctm.d);
|
|
9574
|
+
scaleFactor = (scaleX + scaleY) / 2;
|
|
9575
|
+
}
|
|
9576
|
+
}
|
|
9577
|
+
return scaleFactor;
|
|
9578
|
+
};
|
|
8565
9579
|
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
9580
|
+
/**
|
|
9581
|
+
* Creates a proxy that wraps an SVGGeometryElement and adds drawing functionality.
|
|
9582
|
+
* @param {SVGGeometryElement} $el - The SVG element to transform into a drawable
|
|
9583
|
+
* @param {number} start - Starting position (0-1)
|
|
9584
|
+
* @param {number} end - Ending position (0-1)
|
|
9585
|
+
* @return {DrawableSVGGeometry} - Returns a proxy that preserves the original element's type with additional 'draw' attribute functionality
|
|
9586
|
+
*/
|
|
9587
|
+
const createDrawableProxy = ($el, start, end) => {
|
|
9588
|
+
const pathLength = K;
|
|
9589
|
+
const computedStyles = getComputedStyle($el);
|
|
9590
|
+
const strokeLineCap = computedStyles.strokeLinecap;
|
|
9591
|
+
// @ts-ignore
|
|
9592
|
+
const $scalled = computedStyles.vectorEffect === 'non-scaling-stroke' ? $el : null;
|
|
9593
|
+
let currentCap = strokeLineCap;
|
|
8570
9594
|
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
} else {
|
|
8598
|
-
const key = `--${individualTransformProperty}`;
|
|
8599
|
-
elStyle.setProperty(key, keyframes[key][0]);
|
|
9595
|
+
const proxy = new Proxy($el, {
|
|
9596
|
+
get(target, property) {
|
|
9597
|
+
const value = target[property];
|
|
9598
|
+
if (property === proxyTargetSymbol) return target;
|
|
9599
|
+
if (property === 'setAttribute') {
|
|
9600
|
+
return (...args) => {
|
|
9601
|
+
if (args[0] === 'draw') {
|
|
9602
|
+
const value = args[1];
|
|
9603
|
+
const values = value.split(' ');
|
|
9604
|
+
const v1 = +values[0];
|
|
9605
|
+
const v2 = +values[1];
|
|
9606
|
+
// TOTO: Benchmark if performing two slices is more performant than one split
|
|
9607
|
+
// const spaceIndex = value.indexOf(' ');
|
|
9608
|
+
// const v1 = round(+value.slice(0, spaceIndex), precision);
|
|
9609
|
+
// const v2 = round(+value.slice(spaceIndex + 1), precision);
|
|
9610
|
+
const scaleFactor = getScaleFactor($scalled);
|
|
9611
|
+
const os = v1 * -pathLength * scaleFactor;
|
|
9612
|
+
const d1 = (v2 * pathLength * scaleFactor) + os;
|
|
9613
|
+
const d2 = (pathLength * scaleFactor +
|
|
9614
|
+
((v1 === 0 && v2 === 1) || (v1 === 1 && v2 === 0) ? 0 : 10 * scaleFactor) - d1);
|
|
9615
|
+
if (strokeLineCap !== 'butt') {
|
|
9616
|
+
const newCap = v1 === v2 ? 'butt' : strokeLineCap;
|
|
9617
|
+
if (currentCap !== newCap) {
|
|
9618
|
+
target.style.strokeLinecap = `${newCap}`;
|
|
9619
|
+
currentCap = newCap;
|
|
9620
|
+
}
|
|
8600
9621
|
}
|
|
9622
|
+
target.setAttribute('stroke-dashoffset', `${os}`);
|
|
9623
|
+
target.setAttribute('stroke-dasharray', `${d1} ${d2}`);
|
|
8601
9624
|
}
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
propertyValue.map((/** @type {any} */v) => normalizeTweenValue(name, v, $el, i, targetsLength)) :
|
|
8605
|
-
normalizeTweenValue(name, /** @type {any} */(propertyValue), $el, i, targetsLength);
|
|
8606
|
-
if (individualTransformProperty) {
|
|
8607
|
-
keyframes[`--${individualTransformProperty}`] = parsedPropertyValue;
|
|
8608
|
-
cachedTransforms[individualTransformProperty] = parsedPropertyValue;
|
|
8609
|
-
} else {
|
|
8610
|
-
keyframes[name] = parsedPropertyValue;
|
|
8611
|
-
}
|
|
8612
|
-
addWAAPIAnimation(this, $el, name, keyframes, tweenParams);
|
|
8613
|
-
}
|
|
8614
|
-
}
|
|
8615
|
-
if (hasIndividualTransforms) {
|
|
8616
|
-
let transforms = emptyString;
|
|
8617
|
-
for (let t in cachedTransforms) {
|
|
8618
|
-
transforms += `${transformsFragmentStrings[t]}var(--${t})) `;
|
|
8619
|
-
}
|
|
8620
|
-
elStyle.transform = transforms;
|
|
9625
|
+
return Reflect.apply(value, target, args);
|
|
9626
|
+
};
|
|
8621
9627
|
}
|
|
8622
|
-
});
|
|
8623
9628
|
|
|
8624
|
-
|
|
8625
|
-
|
|
9629
|
+
if (isFnc(value)) {
|
|
9630
|
+
return (...args) => Reflect.apply(value, target, args);
|
|
9631
|
+
} else {
|
|
9632
|
+
return value;
|
|
9633
|
+
}
|
|
8626
9634
|
}
|
|
9635
|
+
});
|
|
9636
|
+
|
|
9637
|
+
if ($el.getAttribute('pathLength') !== `${pathLength}`) {
|
|
9638
|
+
$el.setAttribute('pathLength', `${pathLength}`);
|
|
9639
|
+
proxy.setAttribute('draw', `${start} ${end}`);
|
|
8627
9640
|
}
|
|
8628
9641
|
|
|
8629
|
-
/**
|
|
8630
|
-
|
|
8631
|
-
* @param {globalThis.Animation} animation
|
|
8632
|
-
*/
|
|
9642
|
+
return /** @type {DrawableSVGGeometry} */(proxy);
|
|
9643
|
+
};
|
|
8633
9644
|
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
|
|
9645
|
+
/**
|
|
9646
|
+
* Creates drawable proxies for multiple SVG elements.
|
|
9647
|
+
* @param {TargetsParam} selector - CSS selector, SVG element, or array of elements and selectors
|
|
9648
|
+
* @param {number} [start=0] - Starting position (0-1)
|
|
9649
|
+
* @param {number} [end=0] - Ending position (0-1)
|
|
9650
|
+
* @return {Array<DrawableSVGGeometry>} - Array of proxied elements with drawing functionality
|
|
9651
|
+
*/
|
|
9652
|
+
const createDrawable = (selector, start = 0, end = 0) => {
|
|
9653
|
+
const els = parseTargets(selector);
|
|
9654
|
+
return els.map($el => createDrawableProxy(
|
|
9655
|
+
/** @type {SVGGeometryElement} */($el),
|
|
9656
|
+
start,
|
|
9657
|
+
end
|
|
9658
|
+
));
|
|
9659
|
+
};
|
|
8643
9660
|
|
|
8644
|
-
get speed() {
|
|
8645
|
-
return this._speed;
|
|
8646
|
-
}
|
|
8647
9661
|
|
|
8648
|
-
set speed(speed) {
|
|
8649
|
-
this._speed = +speed;
|
|
8650
|
-
this.forEach(anim => anim.playbackRate = speed);
|
|
8651
|
-
}
|
|
8652
9662
|
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
8656
|
-
|
|
9663
|
+
/**
|
|
9664
|
+
* @param {TargetsParam} path2
|
|
9665
|
+
* @param {Number} [precision]
|
|
9666
|
+
* @return {FunctionValue}
|
|
9667
|
+
*/
|
|
9668
|
+
const morphTo = (path2, precision = .33) => ($path1) => {
|
|
9669
|
+
const tagName1 = ($path1.tagName || '').toLowerCase();
|
|
9670
|
+
if (!tagName1.match(/^(path|polygon|polyline)$/)) {
|
|
9671
|
+
throw new Error(`Can't morph a <${$path1.tagName}> SVG element. Use <path>, <polygon> or <polyline>.`);
|
|
9672
|
+
}
|
|
9673
|
+
const $path2 = /** @type {SVGGeometryElement} */(getPath(path2));
|
|
9674
|
+
if (!$path2) {
|
|
9675
|
+
throw new Error("Can't morph to an invalid target. 'path2' must resolve to an existing <path>, <polygon> or <polyline> SVG element.");
|
|
9676
|
+
}
|
|
9677
|
+
const tagName2 = ($path2.tagName || '').toLowerCase();
|
|
9678
|
+
if (!tagName2.match(/^(path|polygon|polyline)$/)) {
|
|
9679
|
+
throw new Error(`Can't morph a <${$path2.tagName}> SVG element. Use <path>, <polygon> or <polyline>.`);
|
|
8657
9680
|
}
|
|
9681
|
+
const isPath = $path1.tagName === 'path';
|
|
9682
|
+
const separator = isPath ? ' ' : ',';
|
|
9683
|
+
const previousPoints = $path1[morphPointsSymbol];
|
|
9684
|
+
if (previousPoints) $path1.setAttribute(isPath ? 'd' : 'points', previousPoints);
|
|
8658
9685
|
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
9686
|
+
let v1 = '', v2 = '';
|
|
9687
|
+
|
|
9688
|
+
if (!precision) {
|
|
9689
|
+
v1 = $path1.getAttribute(isPath ? 'd' : 'points');
|
|
9690
|
+
v2 = $path2.getAttribute(isPath ? 'd' : 'points');
|
|
9691
|
+
} else {
|
|
9692
|
+
const length1 = /** @type {SVGGeometryElement} */($path1).getTotalLength();
|
|
9693
|
+
const length2 = $path2.getTotalLength();
|
|
9694
|
+
const maxPoints = Math.max(Math.ceil(length1 * precision), Math.ceil(length2 * precision));
|
|
9695
|
+
for (let i = 0; i < maxPoints; i++) {
|
|
9696
|
+
const t = i / (maxPoints - 1);
|
|
9697
|
+
const pointOnPath1 = /** @type {SVGGeometryElement} */($path1).getPointAtLength(length1 * t);
|
|
9698
|
+
const pointOnPath2 = $path2.getPointAtLength(length2 * t);
|
|
9699
|
+
const prefix = isPath ? (i === 0 ? 'M' : 'L') : '';
|
|
9700
|
+
v1 += prefix + round$1(pointOnPath1.x, 3) + separator + pointOnPath1.y + ' ';
|
|
9701
|
+
v2 += prefix + round$1(pointOnPath2.x, 3) + separator + pointOnPath2.y + ' ';
|
|
9702
|
+
}
|
|
8669
9703
|
}
|
|
8670
9704
|
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
9705
|
+
$path1[morphPointsSymbol] = v2;
|
|
9706
|
+
|
|
9707
|
+
return [v1, v2];
|
|
9708
|
+
};
|
|
9709
|
+
|
|
9710
|
+
var index$1 = /*#__PURE__*/Object.freeze({
|
|
9711
|
+
__proto__: null,
|
|
9712
|
+
createDrawable: createDrawable,
|
|
9713
|
+
createMotionPath: createMotionPath,
|
|
9714
|
+
morphTo: morphTo
|
|
9715
|
+
});
|
|
9716
|
+
|
|
9717
|
+
|
|
9718
|
+
|
|
9719
|
+
const segmenter = (typeof Intl !== 'undefined') && Intl.Segmenter;
|
|
9720
|
+
const valueRgx = /\{value\}/g;
|
|
9721
|
+
const indexRgx = /\{i\}/g;
|
|
9722
|
+
const whiteSpaceGroupRgx = /(\s+)/;
|
|
9723
|
+
const whiteSpaceRgx = /^\s+$/;
|
|
9724
|
+
const lineType = 'line';
|
|
9725
|
+
const wordType = 'word';
|
|
9726
|
+
const charType = 'char';
|
|
9727
|
+
const dataLine = `data-line`;
|
|
9728
|
+
|
|
9729
|
+
/**
|
|
9730
|
+
* @typedef {Object} Segment
|
|
9731
|
+
* @property {String} segment
|
|
9732
|
+
* @property {Boolean} [isWordLike]
|
|
9733
|
+
*/
|
|
8674
9734
|
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
9735
|
+
/**
|
|
9736
|
+
* @typedef {Object} Segmenter
|
|
9737
|
+
* @property {function(String): Iterable<Segment>} segment
|
|
9738
|
+
*/
|
|
8678
9739
|
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
8683
|
-
|
|
8684
|
-
}
|
|
9740
|
+
/** @type {Segmenter} */
|
|
9741
|
+
let wordSegmenter = null;
|
|
9742
|
+
/** @type {Segmenter} */
|
|
9743
|
+
let graphemeSegmenter = null;
|
|
9744
|
+
let $splitTemplate = null;
|
|
8685
9745
|
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
9746
|
+
/**
|
|
9747
|
+
* @param {Segment} seg
|
|
9748
|
+
* @return {Boolean}
|
|
9749
|
+
*/
|
|
9750
|
+
const isSegmentWordLike = seg => {
|
|
9751
|
+
return seg.isWordLike ||
|
|
9752
|
+
seg.segment === ' ' || // Consider spaces as words first, then handle them diffrently later
|
|
9753
|
+
isNum(+seg.segment); // Safari doesn't considers numbers as words
|
|
9754
|
+
};
|
|
8691
9755
|
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
return this;
|
|
8697
|
-
}
|
|
9756
|
+
/**
|
|
9757
|
+
* @param {HTMLElement} $el
|
|
9758
|
+
*/
|
|
9759
|
+
const setAriaHidden = $el => $el.setAttribute('aria-hidden', 'true');
|
|
8698
9760
|
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
9761
|
+
/**
|
|
9762
|
+
* @param {DOMTarget} $el
|
|
9763
|
+
* @param {String} type
|
|
9764
|
+
* @return {Array<HTMLElement>}
|
|
9765
|
+
*/
|
|
9766
|
+
const getAllTopLevelElements = ($el, type) => [.../** @type {*} */($el.querySelectorAll(`[data-${type}]:not([data-${type}] [data-${type}])`))];
|
|
8703
9767
|
|
|
8704
|
-
|
|
8705
|
-
|
|
8706
|
-
|
|
9768
|
+
const debugColors = { line: '#00D672', word: '#FF4B4B', char: '#5A87FF' };
|
|
9769
|
+
|
|
9770
|
+
/**
|
|
9771
|
+
* @param {HTMLElement} $el
|
|
9772
|
+
*/
|
|
9773
|
+
const filterEmptyElements = $el => {
|
|
9774
|
+
if (!$el.childElementCount && !$el.textContent.trim()) {
|
|
9775
|
+
const $parent = $el.parentElement;
|
|
9776
|
+
$el.remove();
|
|
9777
|
+
if ($parent) filterEmptyElements($parent);
|
|
8707
9778
|
}
|
|
9779
|
+
};
|
|
8708
9780
|
|
|
8709
|
-
|
|
8710
|
-
|
|
8711
|
-
|
|
8712
|
-
|
|
8713
|
-
|
|
8714
|
-
|
|
8715
|
-
|
|
8716
|
-
|
|
8717
|
-
|
|
8718
|
-
|
|
8719
|
-
|
|
9781
|
+
/**
|
|
9782
|
+
* @param {HTMLElement} $el
|
|
9783
|
+
* @param {Number} lineIndex
|
|
9784
|
+
* @param {Set<HTMLElement|Node>} bin
|
|
9785
|
+
* @returns {Set<HTMLElement|Node>}
|
|
9786
|
+
*/
|
|
9787
|
+
const filterLineElements = ($el, lineIndex, bin) => {
|
|
9788
|
+
const dataLineAttr = $el.getAttribute(dataLine);
|
|
9789
|
+
if (dataLineAttr !== null && +dataLineAttr !== lineIndex || $el.tagName === 'BR') {
|
|
9790
|
+
bin.add($el);
|
|
9791
|
+
// Also remove adjacent whitespace-only text nodes
|
|
9792
|
+
const prev = $el.previousSibling;
|
|
9793
|
+
const next = $el.nextSibling;
|
|
9794
|
+
if (prev && prev.nodeType === 3 && whiteSpaceRgx.test(prev.textContent)) {
|
|
9795
|
+
bin.add(prev);
|
|
9796
|
+
}
|
|
9797
|
+
if (next && next.nodeType === 3 && whiteSpaceRgx.test(next.textContent)) {
|
|
9798
|
+
bin.add(next);
|
|
9799
|
+
}
|
|
8720
9800
|
}
|
|
9801
|
+
let i = $el.childElementCount;
|
|
9802
|
+
while (i--) filterLineElements(/** @type {HTMLElement} */($el.children[i]), lineIndex, bin);
|
|
9803
|
+
return bin;
|
|
9804
|
+
};
|
|
8721
9805
|
|
|
8722
|
-
|
|
8723
|
-
|
|
8724
|
-
|
|
9806
|
+
/**
|
|
9807
|
+
* @param {'line'|'word'|'char'} type
|
|
9808
|
+
* @param {SplitTemplateParams} params
|
|
9809
|
+
* @return {String}
|
|
9810
|
+
*/
|
|
9811
|
+
const generateTemplate = (type, params = {}) => {
|
|
9812
|
+
let template = ``;
|
|
9813
|
+
const classString = isStr(params.class) ? ` class="${params.class}"` : '';
|
|
9814
|
+
const cloneType = setValue(params.clone, false);
|
|
9815
|
+
const wrapType = setValue(params.wrap, false);
|
|
9816
|
+
const overflow = wrapType ? wrapType === true ? 'clip' : wrapType : cloneType ? 'clip' : false;
|
|
9817
|
+
if (wrapType) template += `<span${overflow ? ` style="overflow:${overflow};"` : ''}>`;
|
|
9818
|
+
template += `<span${classString}${cloneType ? ` style="position:relative;"` : ''} data-${type}="{i}">`;
|
|
9819
|
+
if (cloneType) {
|
|
9820
|
+
const left = cloneType === 'left' ? '-100%' : cloneType === 'right' ? '100%' : '0';
|
|
9821
|
+
const top = cloneType === 'top' ? '-100%' : cloneType === 'bottom' ? '100%' : '0';
|
|
9822
|
+
template += `<span>{value}</span>`;
|
|
9823
|
+
template += `<span inert style="position:absolute;top:${top};left:${left};white-space:nowrap;">{value}</span>`;
|
|
9824
|
+
} else {
|
|
9825
|
+
template += `{value}`;
|
|
8725
9826
|
}
|
|
9827
|
+
template += `</span>`;
|
|
9828
|
+
if (wrapType) template += `</span>`;
|
|
9829
|
+
return template;
|
|
9830
|
+
};
|
|
8726
9831
|
|
|
8727
|
-
|
|
8728
|
-
|
|
9832
|
+
/**
|
|
9833
|
+
* @param {String|SplitFunctionValue} htmlTemplate
|
|
9834
|
+
* @param {Array<HTMLElement>} store
|
|
9835
|
+
* @param {Node|HTMLElement} node
|
|
9836
|
+
* @param {DocumentFragment} $parentFragment
|
|
9837
|
+
* @param {'line'|'word'|'char'} type
|
|
9838
|
+
* @param {Boolean} debug
|
|
9839
|
+
* @param {Number} lineIndex
|
|
9840
|
+
* @param {Number} [wordIndex]
|
|
9841
|
+
* @param {Number} [charIndex]
|
|
9842
|
+
* @return {HTMLElement}
|
|
9843
|
+
*/
|
|
9844
|
+
const processHTMLTemplate = (htmlTemplate, store, node, $parentFragment, type, debug, lineIndex, wordIndex, charIndex) => {
|
|
9845
|
+
const isLine = type === lineType;
|
|
9846
|
+
const isChar = type === charType;
|
|
9847
|
+
const className = `_${type}_`;
|
|
9848
|
+
const template = isFnc(htmlTemplate) ? htmlTemplate(node) : htmlTemplate;
|
|
9849
|
+
const displayStyle = isLine ? 'block' : 'inline-block';
|
|
9850
|
+
$splitTemplate.innerHTML = template
|
|
9851
|
+
.replace(valueRgx, `<i class="${className}"></i>`)
|
|
9852
|
+
.replace(indexRgx, `${isChar ? charIndex : isLine ? lineIndex : wordIndex}`);
|
|
9853
|
+
const $content = $splitTemplate.content;
|
|
9854
|
+
const $highestParent = /** @type {HTMLElement} */($content.firstElementChild);
|
|
9855
|
+
const $split = /** @type {HTMLElement} */($content.querySelector(`[data-${type}]`)) || $highestParent;
|
|
9856
|
+
const $replacables = /** @type {NodeListOf<HTMLElement>} */($content.querySelectorAll(`i.${className}`));
|
|
9857
|
+
const replacablesLength = $replacables.length;
|
|
9858
|
+
if (replacablesLength) {
|
|
9859
|
+
$highestParent.style.display = displayStyle;
|
|
9860
|
+
$split.style.display = displayStyle;
|
|
9861
|
+
$split.setAttribute(dataLine, `${lineIndex}`);
|
|
9862
|
+
if (!isLine) {
|
|
9863
|
+
$split.setAttribute('data-word', `${wordIndex}`);
|
|
9864
|
+
if (isChar) $split.setAttribute('data-char', `${charIndex}`);
|
|
9865
|
+
}
|
|
9866
|
+
let i = replacablesLength;
|
|
9867
|
+
while (i--) {
|
|
9868
|
+
const $replace = $replacables[i];
|
|
9869
|
+
const $closestParent = $replace.parentElement;
|
|
9870
|
+
$closestParent.style.display = displayStyle;
|
|
9871
|
+
if (isLine) {
|
|
9872
|
+
$closestParent.innerHTML = /** @type {HTMLElement} */(node).innerHTML;
|
|
9873
|
+
} else {
|
|
9874
|
+
$closestParent.replaceChild(node.cloneNode(true), $replace);
|
|
9875
|
+
}
|
|
9876
|
+
}
|
|
9877
|
+
store.push($split);
|
|
9878
|
+
$parentFragment.appendChild($content);
|
|
9879
|
+
} else {
|
|
9880
|
+
console.warn(`The expression "{value}" is missing from the provided template.`);
|
|
8729
9881
|
}
|
|
9882
|
+
if (debug) $highestParent.style.outline = `1px dotted ${debugColors[type]}`;
|
|
9883
|
+
return $highestParent;
|
|
9884
|
+
};
|
|
8730
9885
|
|
|
8731
|
-
|
|
8732
|
-
|
|
9886
|
+
/**
|
|
9887
|
+
* A class that splits text into words and wraps them in span elements while preserving the original HTML structure.
|
|
9888
|
+
* @class
|
|
9889
|
+
*/
|
|
9890
|
+
class TextSplitter {
|
|
9891
|
+
/**
|
|
9892
|
+
* @param {HTMLElement|NodeList|String|Array<HTMLElement>} target
|
|
9893
|
+
* @param {TextSplitterParams} [parameters]
|
|
9894
|
+
*/
|
|
9895
|
+
constructor(target, parameters = {}) {
|
|
9896
|
+
// Only init segmenters when needed
|
|
9897
|
+
if (!wordSegmenter) wordSegmenter = segmenter ? new segmenter([], { granularity: wordType }) : {
|
|
9898
|
+
segment: (text) => {
|
|
9899
|
+
const segments = [];
|
|
9900
|
+
const words = text.split(whiteSpaceGroupRgx);
|
|
9901
|
+
for (let i = 0, l = words.length; i < l; i++) {
|
|
9902
|
+
const segment = words[i];
|
|
9903
|
+
segments.push({
|
|
9904
|
+
segment,
|
|
9905
|
+
isWordLike: !whiteSpaceRgx.test(segment), // Consider non-whitespace as word-like
|
|
9906
|
+
});
|
|
9907
|
+
}
|
|
9908
|
+
return segments;
|
|
9909
|
+
}
|
|
9910
|
+
};
|
|
9911
|
+
if (!graphemeSegmenter) graphemeSegmenter = segmenter ? new segmenter([], { granularity: 'grapheme' }) : {
|
|
9912
|
+
segment: text => [...text].map(char => ({ segment: char }))
|
|
9913
|
+
};
|
|
9914
|
+
if (!$splitTemplate && isBrowser) $splitTemplate = doc.createElement('template');
|
|
9915
|
+
if (scope.current) scope.current.register(this);
|
|
9916
|
+
const { words, chars, lines, accessible, includeSpaces, debug } = parameters;
|
|
9917
|
+
const $target = /** @type {HTMLElement} */((target = isArr(target) ? target[0] : target) && /** @type {Node} */(target).nodeType ? target : (getNodeList(target) || [])[0]);
|
|
9918
|
+
const lineParams = lines === true ? {} : lines;
|
|
9919
|
+
const wordParams = words === true || isUnd(words) ? {} : words;
|
|
9920
|
+
const charParams = chars === true ? {} : chars;
|
|
9921
|
+
this.debug = setValue(debug, false);
|
|
9922
|
+
this.includeSpaces = setValue(includeSpaces, false);
|
|
9923
|
+
this.accessible = setValue(accessible, true);
|
|
9924
|
+
this.linesOnly = lineParams && (!wordParams && !charParams);
|
|
9925
|
+
/** @type {String|false|SplitFunctionValue} */
|
|
9926
|
+
this.lineTemplate = isObj(lineParams) ? generateTemplate(lineType, /** @type {SplitTemplateParams} */(lineParams)) : lineParams;
|
|
9927
|
+
/** @type {String|false|SplitFunctionValue} */
|
|
9928
|
+
this.wordTemplate = isObj(wordParams) || this.linesOnly ? generateTemplate(wordType, /** @type {SplitTemplateParams} */(wordParams)) : wordParams;
|
|
9929
|
+
/** @type {String|false|SplitFunctionValue} */
|
|
9930
|
+
this.charTemplate = isObj(charParams) ? generateTemplate(charType, /** @type {SplitTemplateParams} */(charParams)) : charParams;
|
|
9931
|
+
this.$target = $target;
|
|
9932
|
+
this.html = $target && $target.innerHTML;
|
|
9933
|
+
this.lines = [];
|
|
9934
|
+
this.words = [];
|
|
9935
|
+
this.chars = [];
|
|
9936
|
+
this.effects = [];
|
|
9937
|
+
this.effectsCleanups = [];
|
|
9938
|
+
this.cache = null;
|
|
9939
|
+
this.ready = false;
|
|
9940
|
+
this.width = 0;
|
|
9941
|
+
this.resizeTimeout = null;
|
|
9942
|
+
const handleSplit = () => this.html && (lineParams || wordParams || charParams) && this.split();
|
|
9943
|
+
// Make sure this is declared before calling handleSplit() in case revert() is called inside an effect callback
|
|
9944
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
9945
|
+
// Use a setTimeout instead of a Timer for better tree shaking
|
|
9946
|
+
clearTimeout(this.resizeTimeout);
|
|
9947
|
+
this.resizeTimeout = setTimeout(() => {
|
|
9948
|
+
const currentWidth = /** @type {HTMLElement} */($target).offsetWidth;
|
|
9949
|
+
if (currentWidth === this.width) return;
|
|
9950
|
+
this.width = currentWidth;
|
|
9951
|
+
handleSplit();
|
|
9952
|
+
}, 150);
|
|
9953
|
+
});
|
|
9954
|
+
// Only declare the font ready promise when splitting by lines and not alreay split
|
|
9955
|
+
if (this.lineTemplate && !this.ready) {
|
|
9956
|
+
doc.fonts.ready.then(handleSplit);
|
|
9957
|
+
} else {
|
|
9958
|
+
handleSplit();
|
|
9959
|
+
}
|
|
9960
|
+
$target ? this.resizeObserver.observe($target) : console.warn('No Text Splitter target found.');
|
|
8733
9961
|
}
|
|
8734
9962
|
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
|
|
9963
|
+
/**
|
|
9964
|
+
* @param {(...args: any[]) => Tickable | (() => void)} effect
|
|
9965
|
+
* @return this
|
|
9966
|
+
*/
|
|
9967
|
+
addEffect(effect) {
|
|
9968
|
+
if (!isFnc(effect)) return console.warn('Effect must return a function.');
|
|
9969
|
+
const refreshableEffect = keepTime(effect);
|
|
9970
|
+
this.effects.push(refreshableEffect);
|
|
9971
|
+
if (this.ready) this.effectsCleanups[this.effects.length - 1] = refreshableEffect(this);
|
|
9972
|
+
return this;
|
|
8738
9973
|
}
|
|
8739
9974
|
|
|
8740
9975
|
revert() {
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
//
|
|
8745
|
-
|
|
8746
|
-
this.
|
|
8747
|
-
const targetStyle = $el.style;
|
|
8748
|
-
const targetInlineStyles = this._inlineStyles[i];
|
|
8749
|
-
for (let name in targetInlineStyles) {
|
|
8750
|
-
const originalInlinedValue = targetInlineStyles[name];
|
|
8751
|
-
if (isUnd(originalInlinedValue) || originalInlinedValue === emptyString) {
|
|
8752
|
-
targetStyle.removeProperty(toLowerCase(name));
|
|
8753
|
-
} else {
|
|
8754
|
-
targetStyle[name] = originalInlinedValue;
|
|
8755
|
-
}
|
|
8756
|
-
}
|
|
8757
|
-
// Remove style attribute if empty
|
|
8758
|
-
if ($el.getAttribute('style') === emptyString) $el.removeAttribute('style');
|
|
8759
|
-
});
|
|
9976
|
+
clearTimeout(this.resizeTimeout);
|
|
9977
|
+
this.lines.length = this.words.length = this.chars.length = 0;
|
|
9978
|
+
this.resizeObserver.disconnect();
|
|
9979
|
+
// Make sure to revert the effects after disconnecting the resizeObserver to avoid triggering it in the process
|
|
9980
|
+
this.effectsCleanups.forEach(cleanup => isFnc(cleanup) ? cleanup(this) : cleanup.revert && cleanup.revert());
|
|
9981
|
+
this.$target.innerHTML = this.html;
|
|
8760
9982
|
return this;
|
|
8761
9983
|
}
|
|
8762
9984
|
|
|
8763
9985
|
/**
|
|
8764
|
-
*
|
|
9986
|
+
* Recursively processes a node and its children
|
|
9987
|
+
* @param {Node} node
|
|
8765
9988
|
*/
|
|
9989
|
+
splitNode(node) {
|
|
9990
|
+
const wordTemplate = this.wordTemplate;
|
|
9991
|
+
const charTemplate = this.charTemplate;
|
|
9992
|
+
const includeSpaces = this.includeSpaces;
|
|
9993
|
+
const debug = this.debug;
|
|
9994
|
+
const nodeType = node.nodeType;
|
|
9995
|
+
if (nodeType === 3) {
|
|
9996
|
+
const nodeText = node.nodeValue;
|
|
9997
|
+
// If the nodeText is only whitespace, leave it as is
|
|
9998
|
+
if (nodeText.trim()) {
|
|
9999
|
+
const tempWords = [];
|
|
10000
|
+
const words = this.words;
|
|
10001
|
+
const chars = this.chars;
|
|
10002
|
+
const wordSegments = wordSegmenter.segment(nodeText);
|
|
10003
|
+
const $wordsFragment = doc.createDocumentFragment();
|
|
10004
|
+
let prevSeg = null;
|
|
10005
|
+
for (const wordSegment of wordSegments) {
|
|
10006
|
+
const segment = wordSegment.segment;
|
|
10007
|
+
const isWordLike = isSegmentWordLike(wordSegment);
|
|
10008
|
+
// Determine if this segment should be a new word, first segment always becomes a new word
|
|
10009
|
+
if (!prevSeg || (isWordLike && (prevSeg && (isSegmentWordLike(prevSeg))))) {
|
|
10010
|
+
tempWords.push(segment);
|
|
10011
|
+
} else {
|
|
10012
|
+
// Only concatenate if both current and previous are non-word-like and don't contain spaces
|
|
10013
|
+
const lastWordIndex = tempWords.length - 1;
|
|
10014
|
+
const lastWord = tempWords[lastWordIndex];
|
|
10015
|
+
if (!whiteSpaceGroupRgx.test(lastWord) && !whiteSpaceGroupRgx.test(segment)) {
|
|
10016
|
+
tempWords[lastWordIndex] += segment;
|
|
10017
|
+
} else {
|
|
10018
|
+
tempWords.push(segment);
|
|
10019
|
+
}
|
|
10020
|
+
}
|
|
10021
|
+
prevSeg = wordSegment;
|
|
10022
|
+
}
|
|
10023
|
+
|
|
10024
|
+
for (let i = 0, l = tempWords.length; i < l; i++) {
|
|
10025
|
+
const word = tempWords[i];
|
|
10026
|
+
if (!word.trim()) {
|
|
10027
|
+
// Preserve whitespace only if includeSpaces is false and if the current space is not the first node
|
|
10028
|
+
if (i && includeSpaces) continue;
|
|
10029
|
+
$wordsFragment.appendChild(doc.createTextNode(word));
|
|
10030
|
+
} else {
|
|
10031
|
+
const nextWord = tempWords[i + 1];
|
|
10032
|
+
const hasWordFollowingSpace = includeSpaces && nextWord && !nextWord.trim();
|
|
10033
|
+
const wordToProcess = word;
|
|
10034
|
+
const charSegments = charTemplate ? graphemeSegmenter.segment(wordToProcess) : null;
|
|
10035
|
+
const $charsFragment = charTemplate ? doc.createDocumentFragment() : doc.createTextNode(hasWordFollowingSpace ? word + '\xa0' : word);
|
|
10036
|
+
if (charTemplate) {
|
|
10037
|
+
const charSegmentsArray = [...charSegments];
|
|
10038
|
+
for (let j = 0, jl = charSegmentsArray.length; j < jl; j++) {
|
|
10039
|
+
const charSegment = charSegmentsArray[j];
|
|
10040
|
+
const isLastChar = j === jl - 1;
|
|
10041
|
+
// If this is the last character and includeSpaces is true with a following space, append the space
|
|
10042
|
+
const charText = isLastChar && hasWordFollowingSpace ? charSegment.segment + '\xa0' : charSegment.segment;
|
|
10043
|
+
const $charNode = doc.createTextNode(charText);
|
|
10044
|
+
processHTMLTemplate(charTemplate, chars, $charNode, /** @type {DocumentFragment} */($charsFragment), charType, debug, -1, words.length, chars.length);
|
|
10045
|
+
}
|
|
10046
|
+
}
|
|
10047
|
+
if (wordTemplate) {
|
|
10048
|
+
processHTMLTemplate(wordTemplate, words, $charsFragment, $wordsFragment, wordType, debug, -1, words.length, chars.length);
|
|
10049
|
+
// Chars elements must be re-parsed in the split() method if both words and chars are parsed
|
|
10050
|
+
} else if (charTemplate) {
|
|
10051
|
+
$wordsFragment.appendChild($charsFragment);
|
|
10052
|
+
} else {
|
|
10053
|
+
$wordsFragment.appendChild(doc.createTextNode(word));
|
|
10054
|
+
}
|
|
10055
|
+
// Skip the next iteration if we included a space
|
|
10056
|
+
if (hasWordFollowingSpace) i++;
|
|
10057
|
+
}
|
|
10058
|
+
}
|
|
10059
|
+
node.parentNode.replaceChild($wordsFragment, node);
|
|
10060
|
+
}
|
|
10061
|
+
} else if (nodeType === 1) {
|
|
10062
|
+
// Converting to an array is necessary to work around childNodes pottential mutation
|
|
10063
|
+
const childNodes = /** @type {Array<Node>} */([.../** @type {*} */(node.childNodes)]);
|
|
10064
|
+
for (let i = 0, l = childNodes.length; i < l; i++) this.splitNode(childNodes[i]);
|
|
10065
|
+
}
|
|
10066
|
+
}
|
|
8766
10067
|
|
|
8767
10068
|
/**
|
|
8768
|
-
* @param
|
|
8769
|
-
* @return
|
|
10069
|
+
* @param {Boolean} clearCache
|
|
10070
|
+
* @return {this}
|
|
8770
10071
|
*/
|
|
8771
|
-
|
|
8772
|
-
const
|
|
8773
|
-
const
|
|
8774
|
-
|
|
8775
|
-
|
|
8776
|
-
|
|
8777
|
-
|
|
8778
|
-
|
|
8779
|
-
|
|
8780
|
-
|
|
8781
|
-
|
|
8782
|
-
|
|
8783
|
-
}
|
|
10072
|
+
split(clearCache = false) {
|
|
10073
|
+
const $el = this.$target;
|
|
10074
|
+
const isCached = !!this.cache && !clearCache;
|
|
10075
|
+
const lineTemplate = this.lineTemplate;
|
|
10076
|
+
const wordTemplate = this.wordTemplate;
|
|
10077
|
+
const charTemplate = this.charTemplate;
|
|
10078
|
+
const fontsReady = doc.fonts.status !== 'loading';
|
|
10079
|
+
const canSplitLines = lineTemplate && fontsReady;
|
|
10080
|
+
this.ready = !lineTemplate || fontsReady;
|
|
10081
|
+
if (canSplitLines || clearCache) {
|
|
10082
|
+
// No need to revert effects animations here since it's already taken care by the refreshable
|
|
10083
|
+
this.effectsCleanups.forEach(cleanup => isFnc(cleanup) && cleanup(this));
|
|
10084
|
+
}
|
|
10085
|
+
if (!isCached) {
|
|
10086
|
+
if (clearCache) {
|
|
10087
|
+
$el.innerHTML = this.html;
|
|
10088
|
+
this.words.length = this.chars.length = 0;
|
|
10089
|
+
}
|
|
10090
|
+
this.splitNode($el);
|
|
10091
|
+
this.cache = $el.innerHTML;
|
|
10092
|
+
}
|
|
10093
|
+
if (canSplitLines) {
|
|
10094
|
+
if (isCached) $el.innerHTML = this.cache;
|
|
10095
|
+
this.lines.length = 0;
|
|
10096
|
+
if (wordTemplate) this.words = getAllTopLevelElements($el, wordType);
|
|
10097
|
+
}
|
|
10098
|
+
// Always reparse characters after a line reset or if both words and chars are activated
|
|
10099
|
+
if (charTemplate && (canSplitLines || wordTemplate)) {
|
|
10100
|
+
this.chars = getAllTopLevelElements($el, charType);
|
|
10101
|
+
}
|
|
10102
|
+
// Words are used when lines only and prioritized over chars
|
|
10103
|
+
const elementsArray = this.words.length ? this.words : this.chars;
|
|
10104
|
+
let y, linesCount = 0;
|
|
10105
|
+
for (let i = 0, l = elementsArray.length; i < l; i++) {
|
|
10106
|
+
const $el = elementsArray[i];
|
|
10107
|
+
const { top, height } = $el.getBoundingClientRect();
|
|
10108
|
+
if (!isUnd(y) && top - y > height * .5) linesCount++;
|
|
10109
|
+
$el.setAttribute(dataLine, `${linesCount}`);
|
|
10110
|
+
const nested = $el.querySelectorAll(`[${dataLine}]`);
|
|
10111
|
+
let c = nested.length;
|
|
10112
|
+
while (c--) nested[c].setAttribute(dataLine, `${linesCount}`);
|
|
10113
|
+
y = top;
|
|
10114
|
+
}
|
|
10115
|
+
if (canSplitLines) {
|
|
10116
|
+
const linesFragment = doc.createDocumentFragment();
|
|
10117
|
+
const parents = new Set();
|
|
10118
|
+
const clones = [];
|
|
10119
|
+
for (let lineIndex = 0; lineIndex < linesCount + 1; lineIndex++) {
|
|
10120
|
+
const $clone = /** @type {HTMLElement} */($el.cloneNode(true));
|
|
10121
|
+
filterLineElements($clone, lineIndex, new Set()).forEach($el => {
|
|
10122
|
+
const $parent = $el.parentNode;
|
|
10123
|
+
if ($parent) {
|
|
10124
|
+
if ($el.nodeType === 1) parents.add(/** @type {HTMLElement} */($parent));
|
|
10125
|
+
$parent.removeChild($el);
|
|
10126
|
+
}
|
|
10127
|
+
});
|
|
10128
|
+
clones.push($clone);
|
|
10129
|
+
}
|
|
10130
|
+
parents.forEach(filterEmptyElements);
|
|
10131
|
+
for (let cloneIndex = 0, clonesLength = clones.length; cloneIndex < clonesLength; cloneIndex++) {
|
|
10132
|
+
processHTMLTemplate(lineTemplate, this.lines, clones[cloneIndex], linesFragment, lineType, this.debug, cloneIndex);
|
|
10133
|
+
}
|
|
10134
|
+
$el.innerHTML = '';
|
|
10135
|
+
$el.appendChild(linesFragment);
|
|
10136
|
+
if (wordTemplate) this.words = getAllTopLevelElements($el, wordType);
|
|
10137
|
+
if (charTemplate) this.chars = getAllTopLevelElements($el, charType);
|
|
10138
|
+
}
|
|
10139
|
+
|
|
10140
|
+
// Remove the word wrappers and clear the words array if lines split only
|
|
10141
|
+
if (this.linesOnly) {
|
|
10142
|
+
const words = this.words;
|
|
10143
|
+
let w = words.length;
|
|
10144
|
+
while (w--) {
|
|
10145
|
+
const $word = words[w];
|
|
10146
|
+
$word.replaceWith($word.textContent);
|
|
10147
|
+
}
|
|
10148
|
+
words.length = 0;
|
|
10149
|
+
}
|
|
10150
|
+
if (this.accessible && (canSplitLines || !isCached)) {
|
|
10151
|
+
const $accessible = doc.createElement('span');
|
|
10152
|
+
// Make the accessible element visually-hidden (https://www.scottohara.me/blog/2017/04/14/inclusively-hidden.html)
|
|
10153
|
+
$accessible.style.cssText = `position:absolute;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);width:1px;height:1px;white-space:nowrap;`;
|
|
10154
|
+
// $accessible.setAttribute('tabindex', '-1');
|
|
10155
|
+
$accessible.innerHTML = this.html;
|
|
10156
|
+
$el.insertBefore($accessible, $el.firstChild);
|
|
10157
|
+
this.lines.forEach(setAriaHidden);
|
|
10158
|
+
this.words.forEach(setAriaHidden);
|
|
10159
|
+
this.chars.forEach(setAriaHidden);
|
|
10160
|
+
}
|
|
10161
|
+
this.width = /** @type {HTMLElement} */($el).offsetWidth;
|
|
10162
|
+
if (canSplitLines || clearCache) {
|
|
10163
|
+
this.effects.forEach((effect, i) => this.effectsCleanups[i] = effect(this));
|
|
10164
|
+
}
|
|
10165
|
+
return this;
|
|
10166
|
+
}
|
|
10167
|
+
|
|
10168
|
+
refresh() {
|
|
10169
|
+
this.split(true);
|
|
8784
10170
|
}
|
|
8785
10171
|
}
|
|
8786
10172
|
|
|
8787
|
-
const waapi = {
|
|
8788
10173
|
/**
|
|
8789
|
-
* @param
|
|
8790
|
-
* @param
|
|
8791
|
-
* @return {
|
|
10174
|
+
* @param {HTMLElement|NodeList|String|Array<HTMLElement>} target
|
|
10175
|
+
* @param {TextSplitterParams} [parameters]
|
|
10176
|
+
* @return {TextSplitter}
|
|
8792
10177
|
*/
|
|
8793
|
-
|
|
8794
|
-
|
|
10178
|
+
const splitText = (target, parameters) => new TextSplitter(target, parameters);
|
|
10179
|
+
|
|
10180
|
+
/**
|
|
10181
|
+
* @deprecated text.split() is deprecated, import splitText() directly, or text.splitText()
|
|
10182
|
+
*
|
|
10183
|
+
* @param {HTMLElement|NodeList|String|Array<HTMLElement>} target
|
|
10184
|
+
* @param {TextSplitterParams} [parameters]
|
|
10185
|
+
* @return {TextSplitter}
|
|
10186
|
+
*/
|
|
10187
|
+
const split = (target, parameters) => {
|
|
10188
|
+
console.warn('text.split() is deprecated, import splitText() directly, or text.splitText()');
|
|
10189
|
+
return new TextSplitter(target, parameters);
|
|
8795
10190
|
};
|
|
8796
10191
|
|
|
8797
|
-
|
|
10192
|
+
var index = /*#__PURE__*/Object.freeze({
|
|
10193
|
+
__proto__: null,
|
|
10194
|
+
TextSplitter: TextSplitter,
|
|
10195
|
+
split: split,
|
|
10196
|
+
splitText: splitText
|
|
10197
|
+
});
|
|
10198
|
+
|
|
10199
|
+
export { registerTargets as $, Animatable, AutoLayout, Draggable, JSAnimation, Scope, ScrollObserver, Spring, TextSplitter, Timeline, Timer, WAAPIAnimation, animate, clamp, cleanInlineStyles, createAnimatable, createDraggable, createDrawable, createLayout, createMotionPath, createScope, createSeededRandom, createSpring, createTimeline, createTimer, cubicBezier, damp, degToRad, eases, index$3 as easings, engine, get, irregular, keepTime, lerp, linear, mapRange, morphTo, onScroll, padEnd, padStart, radToDeg, random, randomPick, remove, round, roundPad, scrollContainers, set, shuffle, snap, split, splitText, spring, stagger, steps, index$1 as svg, sync, index as text, index$2 as utils, waapi, wrap };
|