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