animejs 4.2.2 → 4.3.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -28
- package/dist/bundles/anime.esm.js +2705 -1303
- package/dist/bundles/anime.esm.min.js +2 -2
- package/dist/bundles/anime.umd.js +2710 -1306
- package/dist/bundles/anime.umd.min.js +2 -2
- package/dist/modules/animatable/animatable.cjs +1 -1
- package/dist/modules/animatable/animatable.js +1 -1
- package/dist/modules/animatable/index.cjs +1 -1
- package/dist/modules/animatable/index.js +1 -1
- package/dist/modules/animation/additive.cjs +1 -1
- package/dist/modules/animation/additive.js +1 -1
- package/dist/modules/animation/animation.cjs +18 -9
- package/dist/modules/animation/animation.js +19 -10
- package/dist/modules/animation/composition.cjs +1 -1
- package/dist/modules/animation/composition.js +1 -1
- package/dist/modules/animation/index.cjs +1 -1
- package/dist/modules/animation/index.js +1 -1
- package/dist/modules/core/clock.cjs +10 -10
- package/dist/modules/core/clock.d.ts +1 -1
- package/dist/modules/core/clock.js +10 -10
- package/dist/modules/core/colors.cjs +1 -1
- package/dist/modules/core/colors.js +1 -1
- package/dist/modules/core/consts.cjs +6 -4
- package/dist/modules/core/consts.d.ts +13 -5
- package/dist/modules/core/consts.js +6 -4
- package/dist/modules/core/globals.cjs +5 -2
- package/dist/modules/core/globals.d.ts +1 -0
- package/dist/modules/core/globals.js +5 -3
- package/dist/modules/core/helpers.cjs +1 -1
- package/dist/modules/core/helpers.js +1 -1
- package/dist/modules/core/render.cjs +1 -1
- package/dist/modules/core/render.js +1 -1
- package/dist/modules/core/styles.cjs +1 -1
- package/dist/modules/core/styles.js +1 -1
- package/dist/modules/core/targets.cjs +1 -1
- package/dist/modules/core/targets.js +1 -1
- package/dist/modules/core/transforms.cjs +1 -1
- package/dist/modules/core/transforms.js +1 -1
- package/dist/modules/core/units.cjs +1 -1
- package/dist/modules/core/units.js +1 -1
- package/dist/modules/core/values.cjs +1 -1
- package/dist/modules/core/values.js +1 -1
- package/dist/modules/draggable/draggable.cjs +1 -1
- package/dist/modules/draggable/draggable.js +1 -1
- package/dist/modules/draggable/index.cjs +1 -1
- package/dist/modules/draggable/index.js +1 -1
- package/dist/modules/easings/cubic-bezier/index.cjs +1 -1
- package/dist/modules/easings/cubic-bezier/index.js +1 -1
- package/dist/modules/easings/eases/index.cjs +1 -1
- package/dist/modules/easings/eases/index.js +1 -1
- package/dist/modules/easings/eases/parser.cjs +1 -1
- package/dist/modules/easings/eases/parser.js +1 -1
- package/dist/modules/easings/index.cjs +1 -1
- package/dist/modules/easings/index.js +1 -1
- package/dist/modules/easings/irregular/index.cjs +1 -1
- package/dist/modules/easings/irregular/index.js +1 -1
- package/dist/modules/easings/linear/index.cjs +1 -1
- package/dist/modules/easings/linear/index.js +1 -1
- package/dist/modules/easings/none.cjs +1 -1
- package/dist/modules/easings/none.js +1 -1
- package/dist/modules/easings/spring/index.cjs +1 -1
- package/dist/modules/easings/spring/index.js +1 -1
- package/dist/modules/easings/steps/index.cjs +1 -1
- package/dist/modules/easings/steps/index.js +1 -1
- package/dist/modules/engine/engine.cjs +2 -2
- package/dist/modules/engine/engine.js +2 -2
- package/dist/modules/engine/index.cjs +1 -1
- package/dist/modules/engine/index.js +1 -1
- package/dist/modules/events/index.cjs +1 -1
- package/dist/modules/events/index.js +1 -1
- package/dist/modules/events/scroll.cjs +1 -1
- package/dist/modules/events/scroll.js +1 -1
- package/dist/modules/index.cjs +4 -1
- package/dist/modules/index.d.ts +1 -0
- package/dist/modules/index.js +2 -1
- package/dist/modules/layout/index.cjs +15 -0
- package/dist/modules/layout/index.d.ts +1 -0
- package/dist/modules/layout/index.js +8 -0
- package/dist/modules/layout/layout.cjs +1384 -0
- package/dist/modules/layout/layout.d.ts +211 -0
- package/dist/modules/layout/layout.js +1381 -0
- package/dist/modules/scope/index.cjs +1 -1
- package/dist/modules/scope/index.js +1 -1
- package/dist/modules/scope/scope.cjs +1 -1
- package/dist/modules/scope/scope.js +1 -1
- package/dist/modules/svg/drawable.cjs +1 -1
- package/dist/modules/svg/drawable.js +1 -1
- package/dist/modules/svg/helpers.cjs +1 -1
- package/dist/modules/svg/helpers.js +1 -1
- package/dist/modules/svg/index.cjs +1 -1
- package/dist/modules/svg/index.js +1 -1
- package/dist/modules/svg/morphto.cjs +1 -1
- package/dist/modules/svg/morphto.js +1 -1
- package/dist/modules/svg/motionpath.cjs +1 -1
- package/dist/modules/svg/motionpath.js +1 -1
- package/dist/modules/text/index.cjs +1 -1
- package/dist/modules/text/index.js +1 -1
- package/dist/modules/text/split.cjs +23 -9
- package/dist/modules/text/split.js +23 -9
- package/dist/modules/timeline/index.cjs +1 -1
- package/dist/modules/timeline/index.js +1 -1
- package/dist/modules/timeline/position.cjs +1 -1
- package/dist/modules/timeline/position.js +1 -1
- package/dist/modules/timeline/timeline.cjs +14 -6
- package/dist/modules/timeline/timeline.d.ts +2 -0
- package/dist/modules/timeline/timeline.js +15 -7
- package/dist/modules/timer/index.cjs +1 -1
- package/dist/modules/timer/index.js +1 -1
- package/dist/modules/timer/timer.cjs +26 -13
- package/dist/modules/timer/timer.d.ts +1 -0
- package/dist/modules/timer/timer.js +27 -14
- package/dist/modules/types/index.d.ts +3 -1
- package/dist/modules/utils/chainable.cjs +1 -1
- package/dist/modules/utils/chainable.js +1 -1
- package/dist/modules/utils/index.cjs +1 -1
- package/dist/modules/utils/index.js +1 -1
- package/dist/modules/utils/number.cjs +1 -1
- package/dist/modules/utils/number.js +1 -1
- package/dist/modules/utils/random.cjs +1 -1
- package/dist/modules/utils/random.js +1 -1
- package/dist/modules/utils/stagger.cjs +1 -1
- package/dist/modules/utils/stagger.js +1 -1
- package/dist/modules/utils/target.cjs +1 -1
- package/dist/modules/utils/target.js +1 -1
- package/dist/modules/utils/time.cjs +1 -1
- package/dist/modules/utils/time.js +1 -1
- package/dist/modules/waapi/composition.cjs +1 -1
- package/dist/modules/waapi/composition.js +1 -1
- package/dist/modules/waapi/index.cjs +1 -1
- package/dist/modules/waapi/index.js +1 -1
- package/dist/modules/waapi/waapi.cjs +15 -7
- package/dist/modules/waapi/waapi.js +16 -8
- package/package.json +8 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Anime.js - UMD bundle
|
|
3
|
-
* @version v4.
|
|
3
|
+
* @version v4.3.0-beta.1
|
|
4
4
|
* @license MIT
|
|
5
5
|
* @copyright 2025 - Julian Garnier
|
|
6
6
|
*/
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
/** @typedef {JSAnimation|Timeline} Renderable */
|
|
38
38
|
/** @typedef {Timer|Renderable} Tickable */
|
|
39
39
|
/** @typedef {Timer&JSAnimation&Timeline} CallbackArgument */
|
|
40
|
-
/** @typedef {Animatable|Tickable|WAAPIAnimation|Draggable|ScrollObserver|TextSplitter|Scope} Revertible */
|
|
40
|
+
/** @typedef {Animatable|Tickable|WAAPIAnimation|Draggable|ScrollObserver|TextSplitter|Scope|AutoLayout} Revertible */
|
|
41
41
|
|
|
42
42
|
// Stagger types
|
|
43
43
|
|
|
@@ -361,6 +361,7 @@
|
|
|
361
361
|
* @typedef {Object} TimelineOptions
|
|
362
362
|
* @property {DefaultsParams} [defaults]
|
|
363
363
|
* @property {EasingParam} [playbackEase]
|
|
364
|
+
* @property {Boolean} [composition]
|
|
364
365
|
*/
|
|
365
366
|
|
|
366
367
|
/**
|
|
@@ -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.1', 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,1521 +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
|
-
* @param {Boolean} shouldClamp
|
|
7666
|
-
* @return {DOMPoint}
|
|
7796
|
+
* @typedef {DOMTargetSelector|Array<DOMTargetSelector>} LayoutChildrenParam
|
|
7667
7797
|
*/
|
|
7668
|
-
const getPathPoint = ($path, totalLength, progress, lookup, shouldClamp) => {
|
|
7669
|
-
const point = progress + lookup;
|
|
7670
|
-
const pointOnPath = shouldClamp
|
|
7671
|
-
? Math.max(0, Math.min(point, totalLength)) // Clamp between 0 and totalLength
|
|
7672
|
-
: (point % totalLength + totalLength) % totalLength; // Wrap around
|
|
7673
|
-
return $path.getPointAtLength(pointOnPath);
|
|
7674
|
-
};
|
|
7675
7798
|
|
|
7676
7799
|
/**
|
|
7677
|
-
* @
|
|
7678
|
-
* @param {String} pathProperty
|
|
7679
|
-
* @param {Number} [offset=0]
|
|
7680
|
-
* @return {FunctionValue}
|
|
7800
|
+
* @typedef {Record<String, Number|String>} LayoutStateParams
|
|
7681
7801
|
*/
|
|
7682
|
-
const getPathProgess = ($path, pathProperty, offset = 0) => {
|
|
7683
|
-
return $el => {
|
|
7684
|
-
const totalLength = +($path.getTotalLength());
|
|
7685
|
-
const inSvg = $el[isSvgSymbol];
|
|
7686
|
-
const ctm = $path.getCTM();
|
|
7687
|
-
const shouldClamp = offset === 0;
|
|
7688
|
-
/** @type {TweenObjectValue} */
|
|
7689
|
-
return {
|
|
7690
|
-
from: 0,
|
|
7691
|
-
to: totalLength,
|
|
7692
|
-
/** @type {TweenModifier} */
|
|
7693
|
-
modifier: progress => {
|
|
7694
|
-
const offsetLength = offset * totalLength;
|
|
7695
|
-
const newProgress = progress + offsetLength;
|
|
7696
|
-
if (pathProperty === 'a') {
|
|
7697
|
-
const p0 = getPathPoint($path, totalLength, newProgress, -1, shouldClamp);
|
|
7698
|
-
const p1 = getPathPoint($path, totalLength, newProgress, 1, shouldClamp);
|
|
7699
|
-
return atan2(p1.y - p0.y, p1.x - p0.x) * 180 / PI;
|
|
7700
|
-
} else {
|
|
7701
|
-
const p = getPathPoint($path, totalLength, newProgress, 0, shouldClamp);
|
|
7702
|
-
return pathProperty === 'x' ?
|
|
7703
|
-
inSvg || !ctm ? p.x : p.x * ctm.a + p.y * ctm.c + ctm.e :
|
|
7704
|
-
inSvg || !ctm ? p.y : p.x * ctm.b + p.y * ctm.d + ctm.f
|
|
7705
|
-
}
|
|
7706
|
-
}
|
|
7707
|
-
}
|
|
7708
|
-
}
|
|
7709
|
-
};
|
|
7710
7802
|
|
|
7711
7803
|
/**
|
|
7712
|
-
* @
|
|
7713
|
-
* @
|
|
7804
|
+
* @typedef {Object} LayoutAnimationParams
|
|
7805
|
+
* @property {Number|FunctionValue} [delay]
|
|
7806
|
+
* @property {Number|FunctionValue} [duration]
|
|
7807
|
+
* @property {EasingParam} [ease]
|
|
7808
|
+
* @property {LayoutStateParams} [frozen]
|
|
7809
|
+
* @property {LayoutStateParams} [added]
|
|
7810
|
+
* @property {LayoutStateParams} [removed]
|
|
7811
|
+
* @property {Callback<AutoLayout>} [onComplete]
|
|
7714
7812
|
*/
|
|
7715
|
-
const createMotionPath = (path, offset = 0) => {
|
|
7716
|
-
const $path = getPath(path);
|
|
7717
|
-
if (!$path) return;
|
|
7718
|
-
return {
|
|
7719
|
-
translateX: getPathProgess($path, 'x', offset),
|
|
7720
|
-
translateY: getPathProgess($path, 'y', offset),
|
|
7721
|
-
rotate: getPathProgess($path, 'a', offset),
|
|
7722
|
-
}
|
|
7723
|
-
};
|
|
7724
|
-
|
|
7725
|
-
|
|
7726
7813
|
|
|
7727
7814
|
/**
|
|
7728
|
-
* @
|
|
7729
|
-
*
|
|
7815
|
+
* @typedef {LayoutAnimationParams & {
|
|
7816
|
+
* children?: LayoutChildrenParam,
|
|
7817
|
+
* properties?: Array<String>,
|
|
7818
|
+
* }} AutoLayoutParams
|
|
7730
7819
|
*/
|
|
7731
|
-
const getScaleFactor = $el => {
|
|
7732
|
-
let scaleFactor = 1;
|
|
7733
|
-
if ($el && $el.getCTM) {
|
|
7734
|
-
const ctm = $el.getCTM();
|
|
7735
|
-
if (ctm) {
|
|
7736
|
-
const scaleX = sqrt(ctm.a * ctm.a + ctm.b * ctm.b);
|
|
7737
|
-
const scaleY = sqrt(ctm.c * ctm.c + ctm.d * ctm.d);
|
|
7738
|
-
scaleFactor = (scaleX + scaleY) / 2;
|
|
7739
|
-
}
|
|
7740
|
-
}
|
|
7741
|
-
return scaleFactor;
|
|
7742
|
-
};
|
|
7743
7820
|
|
|
7744
7821
|
/**
|
|
7745
|
-
*
|
|
7746
|
-
*
|
|
7747
|
-
*
|
|
7748
|
-
*
|
|
7749
|
-
*
|
|
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
|
|
7750
7833
|
*/
|
|
7751
|
-
const createDrawableProxy = ($el, start, end) => {
|
|
7752
|
-
const pathLength = K;
|
|
7753
|
-
const computedStyles = getComputedStyle($el);
|
|
7754
|
-
const strokeLineCap = computedStyles.strokeLinecap;
|
|
7755
|
-
// @ts-ignore
|
|
7756
|
-
const $scalled = computedStyles.vectorEffect === 'non-scaling-stroke' ? $el : null;
|
|
7757
|
-
let currentCap = strokeLineCap;
|
|
7758
|
-
|
|
7759
|
-
const proxy = new Proxy($el, {
|
|
7760
|
-
get(target, property) {
|
|
7761
|
-
const value = target[property];
|
|
7762
|
-
if (property === proxyTargetSymbol) return target;
|
|
7763
|
-
if (property === 'setAttribute') {
|
|
7764
|
-
return (...args) => {
|
|
7765
|
-
if (args[0] === 'draw') {
|
|
7766
|
-
const value = args[1];
|
|
7767
|
-
const values = value.split(' ');
|
|
7768
|
-
const v1 = +values[0];
|
|
7769
|
-
const v2 = +values[1];
|
|
7770
|
-
// TOTO: Benchmark if performing two slices is more performant than one split
|
|
7771
|
-
// const spaceIndex = value.indexOf(' ');
|
|
7772
|
-
// const v1 = round(+value.slice(0, spaceIndex), precision);
|
|
7773
|
-
// const v2 = round(+value.slice(spaceIndex + 1), precision);
|
|
7774
|
-
const scaleFactor = getScaleFactor($scalled);
|
|
7775
|
-
const os = v1 * -pathLength * scaleFactor;
|
|
7776
|
-
const d1 = (v2 * pathLength * scaleFactor) + os;
|
|
7777
|
-
const d2 = (pathLength * scaleFactor +
|
|
7778
|
-
((v1 === 0 && v2 === 1) || (v1 === 1 && v2 === 0) ? 0 : 10 * scaleFactor) - d1);
|
|
7779
|
-
if (strokeLineCap !== 'butt') {
|
|
7780
|
-
const newCap = v1 === v2 ? 'butt' : strokeLineCap;
|
|
7781
|
-
if (currentCap !== newCap) {
|
|
7782
|
-
target.style.strokeLinecap = `${newCap}`;
|
|
7783
|
-
currentCap = newCap;
|
|
7784
|
-
}
|
|
7785
|
-
}
|
|
7786
|
-
target.setAttribute('stroke-dashoffset', `${os}`);
|
|
7787
|
-
target.setAttribute('stroke-dasharray', `${d1} ${d2}`);
|
|
7788
|
-
}
|
|
7789
|
-
return Reflect.apply(value, target, args);
|
|
7790
|
-
};
|
|
7791
|
-
}
|
|
7792
7834
|
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
|
|
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
|
+
*/
|
|
7800
7884
|
|
|
7801
|
-
|
|
7802
|
-
|
|
7803
|
-
proxy.setAttribute('draw', `${start} ${end}`);
|
|
7804
|
-
}
|
|
7885
|
+
let layoutId = 0;
|
|
7886
|
+
let nodeId = 0;
|
|
7805
7887
|
|
|
7806
|
-
|
|
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);
|
|
7807
7896
|
};
|
|
7808
7897
|
|
|
7809
7898
|
/**
|
|
7810
|
-
*
|
|
7811
|
-
* @param {
|
|
7812
|
-
* @
|
|
7813
|
-
* @param {number} [end=0] - Ending position (0-1)
|
|
7814
|
-
* @return {Array<DrawableSVGGeometry>} - Array of proxied elements with drawing functionality
|
|
7899
|
+
* @param {Node} node
|
|
7900
|
+
* @param {'previousSibling'|'nextSibling'} direction
|
|
7901
|
+
* @return {Boolean}
|
|
7815
7902
|
*/
|
|
7816
|
-
const
|
|
7817
|
-
|
|
7818
|
-
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
));
|
|
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;
|
|
7823
7909
|
};
|
|
7824
7910
|
|
|
7825
|
-
|
|
7826
|
-
|
|
7827
7911
|
/**
|
|
7828
|
-
* @param
|
|
7829
|
-
* @
|
|
7830
|
-
* @return {FunctionValue}
|
|
7912
|
+
* @param {DOMTarget} $el
|
|
7913
|
+
* @return {Boolean}
|
|
7831
7914
|
*/
|
|
7832
|
-
const
|
|
7833
|
-
const tagName1 = ($path1.tagName || '').toLowerCase();
|
|
7834
|
-
if (!tagName1.match(/^(path|polygon|polyline)$/)) {
|
|
7835
|
-
throw new Error(`Can't morph a <${$path1.tagName}> SVG element. Use <path>, <polygon> or <polyline>.`);
|
|
7836
|
-
}
|
|
7837
|
-
const $path2 = /** @type {SVGGeometryElement} */(getPath(path2));
|
|
7838
|
-
if (!$path2) {
|
|
7839
|
-
throw new Error("Can't morph to an invalid target. 'path2' must resolve to an existing <path>, <polygon> or <polyline> SVG element.");
|
|
7840
|
-
}
|
|
7841
|
-
const tagName2 = ($path2.tagName || '').toLowerCase();
|
|
7842
|
-
if (!tagName2.match(/^(path|polygon|polyline)$/)) {
|
|
7843
|
-
throw new Error(`Can't morph a <${$path2.tagName}> SVG element. Use <path>, <polygon> or <polyline>.`);
|
|
7844
|
-
}
|
|
7845
|
-
const isPath = $path1.tagName === 'path';
|
|
7846
|
-
const separator = isPath ? ' ' : ',';
|
|
7847
|
-
const previousPoints = $path1[morphPointsSymbol];
|
|
7848
|
-
if (previousPoints) $path1.setAttribute(isPath ? 'd' : 'points', previousPoints);
|
|
7915
|
+
const isElementSurroundedByText = $el => hasTextSibling($el, 'previousSibling') || hasTextSibling($el, 'nextSibling');
|
|
7849
7916
|
|
|
7850
|
-
|
|
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
|
+
};
|
|
7851
7928
|
|
|
7852
|
-
|
|
7853
|
-
|
|
7854
|
-
|
|
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;
|
|
7855
7938
|
} else {
|
|
7856
|
-
|
|
7857
|
-
const length2 = $path2.getTotalLength();
|
|
7858
|
-
const maxPoints = Math.max(Math.ceil(length1 * precision), Math.ceil(length2 * precision));
|
|
7859
|
-
for (let i = 0; i < maxPoints; i++) {
|
|
7860
|
-
const t = i / (maxPoints - 1);
|
|
7861
|
-
const pointOnPath1 = /** @type {SVGGeometryElement} */($path1).getPointAtLength(length1 * t);
|
|
7862
|
-
const pointOnPath2 = $path2.getPointAtLength(length2 * t);
|
|
7863
|
-
const prefix = isPath ? (i === 0 ? 'M' : 'L') : '';
|
|
7864
|
-
v1 += prefix + round$1(pointOnPath1.x, 3) + separator + pointOnPath1.y + ' ';
|
|
7865
|
-
v2 += prefix + round$1(pointOnPath2.x, 3) + separator + pointOnPath2.y + ' ';
|
|
7866
|
-
}
|
|
7939
|
+
style.removeProperty('transition');
|
|
7867
7940
|
}
|
|
7868
|
-
|
|
7869
|
-
$path1[morphPointsSymbol] = v2;
|
|
7870
|
-
|
|
7871
|
-
return [v1, v2];
|
|
7872
7941
|
};
|
|
7873
7942
|
|
|
7874
|
-
var index$1 = /*#__PURE__*/Object.freeze({
|
|
7875
|
-
__proto__: null,
|
|
7876
|
-
createDrawable: createDrawable,
|
|
7877
|
-
createMotionPath: createMotionPath,
|
|
7878
|
-
morphTo: morphTo
|
|
7879
|
-
});
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
7883
|
-
const segmenter = (typeof Intl !== 'undefined') && Intl.Segmenter;
|
|
7884
|
-
const valueRgx = /\{value\}/g;
|
|
7885
|
-
const indexRgx = /\{i\}/g;
|
|
7886
|
-
const whiteSpaceGroupRgx = /(\s+)/;
|
|
7887
|
-
const whiteSpaceRgx = /^\s+$/;
|
|
7888
|
-
const lineType = 'line';
|
|
7889
|
-
const wordType = 'word';
|
|
7890
|
-
const charType = 'char';
|
|
7891
|
-
const dataLine = `data-line`;
|
|
7892
|
-
|
|
7893
7943
|
/**
|
|
7894
|
-
* @
|
|
7895
|
-
* @property {String} segment
|
|
7896
|
-
* @property {Boolean} [isWordLike]
|
|
7944
|
+
* @param {LayoutNode} node
|
|
7897
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
|
+
};
|
|
7898
7953
|
|
|
7899
7954
|
/**
|
|
7900
|
-
* @
|
|
7901
|
-
* @property {function(String): Iterable<Segment>} segment
|
|
7955
|
+
* @param {Map<DOMTarget, String|null>} store
|
|
7902
7956
|
*/
|
|
7957
|
+
const restoreLayoutTransition = store => {
|
|
7958
|
+
store.forEach((value, $el) => restoreElementTransition($el, value));
|
|
7959
|
+
store.clear();
|
|
7960
|
+
};
|
|
7903
7961
|
|
|
7904
|
-
/** @type {
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
7962
|
+
const hiddenComputedStyle = /** @type {CSSStyleDeclaration} */({
|
|
7963
|
+
display: 'none',
|
|
7964
|
+
visibility: 'hidden',
|
|
7965
|
+
opacity: '0',
|
|
7966
|
+
transform: 'none',
|
|
7967
|
+
position: 'static',
|
|
7968
|
+
});
|
|
7909
7969
|
|
|
7910
7970
|
/**
|
|
7911
|
-
* @param
|
|
7912
|
-
* @return {Boolean}
|
|
7971
|
+
* @param {LayoutNode|null} node
|
|
7913
7972
|
*/
|
|
7914
|
-
const
|
|
7915
|
-
|
|
7916
|
-
|
|
7917
|
-
|
|
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;
|
|
7918
7984
|
};
|
|
7919
7985
|
|
|
7920
7986
|
/**
|
|
7921
|
-
* @param {
|
|
7922
|
-
|
|
7923
|
-
|
|
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
|
+
};
|
|
7924
8049
|
|
|
7925
8050
|
/**
|
|
7926
|
-
* @param {
|
|
7927
|
-
* @param {
|
|
7928
|
-
* @
|
|
7929
|
-
|
|
7930
|
-
|
|
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
|
+
}
|
|
7931
8095
|
|
|
7932
|
-
|
|
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
|
+
};
|
|
7933
8157
|
|
|
7934
8158
|
/**
|
|
7935
|
-
* @param {
|
|
8159
|
+
* @param {LayoutNode} node
|
|
8160
|
+
* @param {LayoutStateParams} [props]
|
|
7936
8161
|
*/
|
|
7937
|
-
const
|
|
7938
|
-
if (
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
if ($parent) filterEmptyElements($parent);
|
|
8162
|
+
const updateNodeProperties = (node, props) => {
|
|
8163
|
+
if (!props) return;
|
|
8164
|
+
for (let name in props) {
|
|
8165
|
+
node.properties[name] = props[name];
|
|
7942
8166
|
}
|
|
7943
8167
|
};
|
|
7944
8168
|
|
|
7945
8169
|
/**
|
|
7946
|
-
* @param {
|
|
7947
|
-
* @param {Number} lineIndex
|
|
7948
|
-
* @param {Set<HTMLElement>} bin
|
|
7949
|
-
* @returns {Set<HTMLElement>}
|
|
8170
|
+
* @param {LayoutNode} node
|
|
7950
8171
|
*/
|
|
7951
|
-
const
|
|
7952
|
-
const
|
|
7953
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
|
|
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
|
+
});
|
|
7957
8179
|
};
|
|
7958
8180
|
|
|
7959
8181
|
/**
|
|
7960
|
-
* @param
|
|
7961
|
-
* @param {SplitTemplateParams} params
|
|
7962
|
-
* @return {String}
|
|
8182
|
+
* @param {LayoutNode} node
|
|
7963
8183
|
*/
|
|
7964
|
-
const
|
|
7965
|
-
|
|
7966
|
-
const
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
template += `<span inert style="position:absolute;top:${top};left:${left};white-space:nowrap;">{value}</span>`;
|
|
7977
|
-
} else {
|
|
7978
|
-
template += `{value}`;
|
|
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
|
+
}
|
|
7979
8196
|
}
|
|
7980
|
-
template += `</span>`;
|
|
7981
|
-
if (wrapType) template += `</span>`;
|
|
7982
|
-
return template;
|
|
7983
8197
|
};
|
|
7984
8198
|
|
|
7985
8199
|
/**
|
|
7986
|
-
* @param
|
|
7987
|
-
* @param {Array<HTMLElement>} store
|
|
7988
|
-
* @param {Node|HTMLElement} node
|
|
7989
|
-
* @param {DocumentFragment} $parentFragment
|
|
7990
|
-
* @param {'line'|'word'|'char'} type
|
|
7991
|
-
* @param {Boolean} debug
|
|
7992
|
-
* @param {Number} lineIndex
|
|
7993
|
-
* @param {Number} [wordIndex]
|
|
7994
|
-
* @param {Number} [charIndex]
|
|
7995
|
-
* @return {HTMLElement}
|
|
8200
|
+
* @param {LayoutNode} node
|
|
7996
8201
|
*/
|
|
7997
|
-
const
|
|
7998
|
-
const
|
|
7999
|
-
const
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
|
|
8005
|
-
|
|
8006
|
-
|
|
8007
|
-
|
|
8008
|
-
|
|
8009
|
-
|
|
8010
|
-
|
|
8011
|
-
|
|
8012
|
-
|
|
8013
|
-
$split.style.display = displayStyle;
|
|
8014
|
-
$split.setAttribute(dataLine, `${lineIndex}`);
|
|
8015
|
-
if (!isLine) {
|
|
8016
|
-
$split.setAttribute('data-word', `${wordIndex}`);
|
|
8017
|
-
if (isChar) $split.setAttribute('data-char', `${charIndex}`);
|
|
8018
|
-
}
|
|
8019
|
-
let i = replacablesLength;
|
|
8020
|
-
while (i--) {
|
|
8021
|
-
const $replace = $replacables[i];
|
|
8022
|
-
const $closestParent = $replace.parentElement;
|
|
8023
|
-
$closestParent.style.display = displayStyle;
|
|
8024
|
-
if (isLine) {
|
|
8025
|
-
$closestParent.innerHTML = /** @type {HTMLElement} */(node).innerHTML;
|
|
8026
|
-
} else {
|
|
8027
|
-
$closestParent.replaceChild(node.cloneNode(true), $replace);
|
|
8028
|
-
}
|
|
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');
|
|
8029
8218
|
}
|
|
8030
|
-
store.push($split);
|
|
8031
|
-
$parentFragment.appendChild($content);
|
|
8032
|
-
} else {
|
|
8033
|
-
console.warn(`The expression "{value}" is missing from the provided template.`);
|
|
8034
8219
|
}
|
|
8035
|
-
|
|
8036
|
-
|
|
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;
|
|
8228
|
+
}
|
|
8037
8229
|
};
|
|
8038
8230
|
|
|
8039
8231
|
/**
|
|
8040
|
-
*
|
|
8041
|
-
* @class
|
|
8232
|
+
* @param {LayoutNode} node
|
|
8042
8233
|
*/
|
|
8043
|
-
|
|
8044
|
-
|
|
8045
|
-
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
if (!wordSegmenter) wordSegmenter = segmenter ? new segmenter([], { granularity: wordType }) : {
|
|
8051
|
-
segment: (text) => {
|
|
8052
|
-
const segments = [];
|
|
8053
|
-
const words = text.split(whiteSpaceGroupRgx);
|
|
8054
|
-
for (let i = 0, l = words.length; i < l; i++) {
|
|
8055
|
-
const segment = words[i];
|
|
8056
|
-
segments.push({
|
|
8057
|
-
segment,
|
|
8058
|
-
isWordLike: !whiteSpaceRgx.test(segment), // Consider non-whitespace as word-like
|
|
8059
|
-
});
|
|
8060
|
-
}
|
|
8061
|
-
return segments;
|
|
8062
|
-
}
|
|
8063
|
-
};
|
|
8064
|
-
if (!graphemeSegmenter) graphemeSegmenter = segmenter ? new segmenter([], { granularity: 'grapheme' }) : {
|
|
8065
|
-
segment: text => [...text].map(char => ({ segment: char }))
|
|
8066
|
-
};
|
|
8067
|
-
if (!$splitTemplate && isBrowser) $splitTemplate = doc.createElement('template');
|
|
8068
|
-
if (scope.current) scope.current.register(this);
|
|
8069
|
-
const { words, chars, lines, accessible, includeSpaces, debug } = parameters;
|
|
8070
|
-
const $target = /** @type {HTMLElement} */((target = isArr(target) ? target[0] : target) && /** @type {Node} */(target).nodeType ? target : (getNodeList(target) || [])[0]);
|
|
8071
|
-
const lineParams = lines === true ? {} : lines;
|
|
8072
|
-
const wordParams = words === true || isUnd(words) ? {} : words;
|
|
8073
|
-
const charParams = chars === true ? {} : chars;
|
|
8074
|
-
this.debug = setValue(debug, false);
|
|
8075
|
-
this.includeSpaces = setValue(includeSpaces, false);
|
|
8076
|
-
this.accessible = setValue(accessible, true);
|
|
8077
|
-
this.linesOnly = lineParams && (!wordParams && !charParams);
|
|
8078
|
-
/** @type {String|false|SplitFunctionValue} */
|
|
8079
|
-
this.lineTemplate = isObj(lineParams) ? generateTemplate(lineType, /** @type {SplitTemplateParams} */(lineParams)) : lineParams;
|
|
8080
|
-
/** @type {String|false|SplitFunctionValue} */
|
|
8081
|
-
this.wordTemplate = isObj(wordParams) || this.linesOnly ? generateTemplate(wordType, /** @type {SplitTemplateParams} */(wordParams)) : wordParams;
|
|
8082
|
-
/** @type {String|false|SplitFunctionValue} */
|
|
8083
|
-
this.charTemplate = isObj(charParams) ? generateTemplate(charType, /** @type {SplitTemplateParams} */(charParams)) : charParams;
|
|
8084
|
-
this.$target = $target;
|
|
8085
|
-
this.html = $target && $target.innerHTML;
|
|
8086
|
-
this.lines = [];
|
|
8087
|
-
this.words = [];
|
|
8088
|
-
this.chars = [];
|
|
8089
|
-
this.effects = [];
|
|
8090
|
-
this.effectsCleanups = [];
|
|
8091
|
-
this.cache = null;
|
|
8092
|
-
this.ready = false;
|
|
8093
|
-
this.width = 0;
|
|
8094
|
-
this.resizeTimeout = null;
|
|
8095
|
-
const handleSplit = () => this.html && (lineParams || wordParams || charParams) && this.split();
|
|
8096
|
-
// Make sure this is declared before calling handleSplit() in case revert() is called inside an effect callback
|
|
8097
|
-
this.resizeObserver = new ResizeObserver(() => {
|
|
8098
|
-
// Use a setTimeout instead of a Timer for better tree shaking
|
|
8099
|
-
clearTimeout(this.resizeTimeout);
|
|
8100
|
-
this.resizeTimeout = setTimeout(() => {
|
|
8101
|
-
const currentWidth = /** @type {HTMLElement} */($target).offsetWidth;
|
|
8102
|
-
if (currentWidth === this.width) return;
|
|
8103
|
-
this.width = currentWidth;
|
|
8104
|
-
handleSplit();
|
|
8105
|
-
}, 150);
|
|
8106
|
-
});
|
|
8107
|
-
// Only declare the font ready promise when splitting by lines and not alreay split
|
|
8108
|
-
if (this.lineTemplate && !this.ready) {
|
|
8109
|
-
doc.fonts.ready.then(handleSplit);
|
|
8110
|
-
} else {
|
|
8111
|
-
handleSplit();
|
|
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');
|
|
8112
8241
|
}
|
|
8113
|
-
$target ? this.resizeObserver.observe($target) : console.warn('No Text Splitter target found.');
|
|
8114
8242
|
}
|
|
8243
|
+
if (node.measuredIsRemoved) {
|
|
8244
|
+
node.layout.pendingRemoved.delete(node.$el);
|
|
8245
|
+
}
|
|
8246
|
+
};
|
|
8247
|
+
|
|
8248
|
+
/**
|
|
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;
|
|
8269
|
+
};
|
|
8115
8270
|
|
|
8271
|
+
class LayoutSnapshot {
|
|
8116
8272
|
/**
|
|
8117
|
-
* @param
|
|
8118
|
-
* @return this
|
|
8273
|
+
* @param {AutoLayout} layout
|
|
8119
8274
|
*/
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
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;
|
|
8126
8288
|
}
|
|
8127
8289
|
|
|
8290
|
+
/**
|
|
8291
|
+
* @return {this}
|
|
8292
|
+
*/
|
|
8128
8293
|
revert() {
|
|
8129
|
-
|
|
8130
|
-
|
|
8131
|
-
|
|
8132
|
-
|
|
8133
|
-
this.
|
|
8134
|
-
this
|
|
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();
|
|
8135
8301
|
return this;
|
|
8136
8302
|
}
|
|
8137
8303
|
|
|
8138
8304
|
/**
|
|
8139
|
-
*
|
|
8140
|
-
* @
|
|
8305
|
+
* @param {DOMTarget} $el
|
|
8306
|
+
* @return {LayoutNodeProperties|undefined}
|
|
8141
8307
|
*/
|
|
8142
|
-
|
|
8143
|
-
const
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
|
|
8150
|
-
// If the nodeText is only whitespace, leave it as is
|
|
8151
|
-
if (nodeText.trim()) {
|
|
8152
|
-
const tempWords = [];
|
|
8153
|
-
const words = this.words;
|
|
8154
|
-
const chars = this.chars;
|
|
8155
|
-
const wordSegments = wordSegmenter.segment(nodeText);
|
|
8156
|
-
const $wordsFragment = doc.createDocumentFragment();
|
|
8157
|
-
let prevSeg = null;
|
|
8158
|
-
for (const wordSegment of wordSegments) {
|
|
8159
|
-
const segment = wordSegment.segment;
|
|
8160
|
-
const isWordLike = isSegmentWordLike(wordSegment);
|
|
8161
|
-
// Determine if this segment should be a new word, first segment always becomes a new word
|
|
8162
|
-
if (!prevSeg || (isWordLike && (prevSeg && (isSegmentWordLike(prevSeg))))) {
|
|
8163
|
-
tempWords.push(segment);
|
|
8164
|
-
} else {
|
|
8165
|
-
// Only concatenate if both current and previous are non-word-like and don't contain spaces
|
|
8166
|
-
const lastWordIndex = tempWords.length - 1;
|
|
8167
|
-
const lastWord = tempWords[lastWordIndex];
|
|
8168
|
-
if (!lastWord.includes(' ') && !segment.includes(' ')) {
|
|
8169
|
-
tempWords[lastWordIndex] += segment;
|
|
8170
|
-
} else {
|
|
8171
|
-
tempWords.push(segment);
|
|
8172
|
-
}
|
|
8173
|
-
}
|
|
8174
|
-
prevSeg = wordSegment;
|
|
8175
|
-
}
|
|
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;
|
|
8313
|
+
}
|
|
8314
|
+
return node.properties;
|
|
8315
|
+
}
|
|
8176
8316
|
|
|
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
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
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;
|
|
8349
|
+
} else {
|
|
8350
|
+
while (node && !node._next) {
|
|
8351
|
+
node = node.parentNode;
|
|
8211
8352
|
}
|
|
8212
|
-
node
|
|
8353
|
+
if (node) node = node._next;
|
|
8213
8354
|
}
|
|
8214
|
-
} else if (nodeType === 1) {
|
|
8215
|
-
// Converting to an array is necessary to work around childNodes pottential mutation
|
|
8216
|
-
const childNodes = /** @type {Array<Node>} */([.../** @type {*} */(node.childNodes)]);
|
|
8217
|
-
for (let i = 0, l = childNodes.length; i < l; i++) this.splitNode(childNodes[i]);
|
|
8218
8355
|
}
|
|
8219
8356
|
}
|
|
8220
8357
|
|
|
8221
8358
|
/**
|
|
8222
|
-
* @param {
|
|
8223
|
-
* @return {this}
|
|
8359
|
+
* @param {LayoutNodeIterator} cb
|
|
8224
8360
|
*/
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
// No need to revert effects animations here since it's already taken care by the refreshable
|
|
8236
|
-
this.effectsCleanups.forEach(cleanup => isFnc(cleanup) && cleanup(this));
|
|
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);
|
|
8237
8371
|
}
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
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;
|
|
8435
|
+
}
|
|
8436
|
+
} else {
|
|
8437
|
+
node = createNode($current, $parent, this, node);
|
|
8242
8438
|
}
|
|
8243
|
-
|
|
8244
|
-
|
|
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;
|
|
8245
8479
|
}
|
|
8246
|
-
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8480
|
+
|
|
8481
|
+
return firstNode;
|
|
8482
|
+
}
|
|
8483
|
+
|
|
8484
|
+
/**
|
|
8485
|
+
* @param {DOMTarget} $el
|
|
8486
|
+
* @param {Set<DOMTarget>} candidates
|
|
8487
|
+
* @return {LayoutNode|null}
|
|
8488
|
+
*/
|
|
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;
|
|
8250
8502
|
}
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
|
|
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;
|
|
8254
8518
|
}
|
|
8255
|
-
|
|
8256
|
-
const
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
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
|
+
}
|
|
8267
8554
|
}
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
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
|
+
|
|
8584
|
+
return this;
|
|
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|FunctionValue} */
|
|
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, 'inOut(3.5)');
|
|
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
|
+
}
|
|
8673
|
+
|
|
8674
|
+
/**
|
|
8675
|
+
* @return {this}
|
|
8676
|
+
*/
|
|
8677
|
+
revert() {
|
|
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));
|
|
8691
|
+
return this;
|
|
8692
|
+
}
|
|
8693
|
+
|
|
8694
|
+
/**
|
|
8695
|
+
* @return {this}
|
|
8696
|
+
*/
|
|
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;
|
|
8870
|
+
}
|
|
8871
|
+
}
|
|
8872
|
+
}
|
|
8873
|
+
|
|
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);
|
|
8278
8911
|
});
|
|
8279
|
-
|
|
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
|
+
}
|
|
8280
8976
|
}
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
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
|
+
});
|
|
8284
8984
|
}
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
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
|
+
}
|
|
8297
9025
|
}
|
|
8298
|
-
|
|
9026
|
+
|
|
8299
9027
|
}
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
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)}`;
|
|
9070
|
+
}
|
|
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
|
+
];
|
|
9083
|
+
}
|
|
9084
|
+
}
|
|
9085
|
+
this.timeline.add(animatedFrozen, animatedFrozenParams, 0);
|
|
9086
|
+
}
|
|
9087
|
+
|
|
8310
9088
|
}
|
|
8311
|
-
|
|
8312
|
-
|
|
8313
|
-
|
|
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);
|
|
8314
9107
|
}
|
|
8315
|
-
|
|
9108
|
+
|
|
9109
|
+
return this.timeline.init();
|
|
8316
9110
|
}
|
|
8317
9111
|
|
|
8318
|
-
|
|
8319
|
-
|
|
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;
|
|
8320
9122
|
}
|
|
8321
9123
|
}
|
|
8322
9124
|
|
|
8323
9125
|
/**
|
|
8324
|
-
* @param
|
|
8325
|
-
* @param
|
|
8326
|
-
* @return {
|
|
9126
|
+
* @param {DOMTargetSelector} root
|
|
9127
|
+
* @param {AutoLayoutParams} [params]
|
|
9128
|
+
* @return {AutoLayout}
|
|
8327
9129
|
*/
|
|
8328
|
-
const
|
|
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 = {};
|
|
8329
9137
|
|
|
8330
9138
|
/**
|
|
8331
|
-
* @
|
|
9139
|
+
* @callback UtilityFunction
|
|
9140
|
+
* @param {...*} args
|
|
9141
|
+
* @return {Number|String}
|
|
8332
9142
|
*
|
|
8333
|
-
* @param
|
|
8334
|
-
* @param
|
|
8335
|
-
* @return {
|
|
9143
|
+
* @param {UtilityFunction} fn
|
|
9144
|
+
* @param {Number} [last=0]
|
|
9145
|
+
* @return {function(...(Number|String)): function(Number|String): (Number|String)}
|
|
8336
9146
|
*/
|
|
8337
|
-
const
|
|
8338
|
-
|
|
8339
|
-
|
|
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
|
+
}
|
|
8340
9164
|
};
|
|
8341
9165
|
|
|
8342
|
-
|
|
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));
|
|
9297
|
+
|
|
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
|
+
*/
|
|
9307
|
+
|
|
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;
|
|
9337
|
+
}
|
|
9338
|
+
};
|
|
9339
|
+
|
|
9340
|
+
/**
|
|
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
|
|
9346
|
+
*/
|
|
9347
|
+
const randomPick = items => items[random(0, items.length - 1)];
|
|
9348
|
+
|
|
9349
|
+
/**
|
|
9350
|
+
* Shuffles an array in-place using the Fisher-Yates algorithm
|
|
9351
|
+
* Adapted from https://bost.ocks.org/mike/shuffle/
|
|
9352
|
+
*
|
|
9353
|
+
* @param {Array} items - The array to shuffle (will be modified in-place)
|
|
9354
|
+
* @return {Array} The same array reference, now shuffled
|
|
9355
|
+
*/
|
|
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;
|
|
9360
|
+
};
|
|
9361
|
+
|
|
9362
|
+
|
|
9363
|
+
|
|
9364
|
+
|
|
9365
|
+
|
|
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({
|
|
8343
9458
|
__proto__: null,
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
|
|
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
|
|
8347
9483
|
});
|
|
8348
9484
|
|
|
8349
9485
|
|
|
8350
9486
|
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
8355
|
-
/**
|
|
8356
|
-
* Converts an easing function into a valid CSS linear() timing function string
|
|
8357
|
-
* @param {EasingFunction} fn
|
|
8358
|
-
* @param {number} [samples=100]
|
|
8359
|
-
* @returns {string} CSS linear() timing function
|
|
8360
|
-
*/
|
|
8361
|
-
const easingToLinear = (fn, samples = 100) => {
|
|
8362
|
-
const points = [];
|
|
8363
|
-
for (let i = 0; i <= samples; i++) points.push(round$1(fn(i / samples), 4));
|
|
8364
|
-
return `linear(${points.join(', ')})`;
|
|
8365
|
-
};
|
|
8366
|
-
|
|
8367
|
-
const WAAPIEasesLookups = {};
|
|
8368
|
-
|
|
8369
9487
|
/**
|
|
8370
|
-
* @param {
|
|
8371
|
-
* @return {
|
|
9488
|
+
* @param {TargetsParam} path
|
|
9489
|
+
* @return {SVGGeometryElement|void}
|
|
8372
9490
|
*/
|
|
8373
|
-
const
|
|
8374
|
-
|
|
8375
|
-
|
|
8376
|
-
|
|
8377
|
-
|
|
8378
|
-
if (
|
|
8379
|
-
stringStartsWith(ease, 'linear') ||
|
|
8380
|
-
stringStartsWith(ease, 'cubic-') ||
|
|
8381
|
-
stringStartsWith(ease, 'steps') ||
|
|
8382
|
-
stringStartsWith(ease, 'ease')
|
|
8383
|
-
) {
|
|
8384
|
-
parsedEase = ease;
|
|
8385
|
-
} else if (stringStartsWith(ease, 'cubicB')) {
|
|
8386
|
-
parsedEase = toLowerCase(ease);
|
|
8387
|
-
} else {
|
|
8388
|
-
const parsed = parseEaseString(ease);
|
|
8389
|
-
if (isFnc(parsed)) parsedEase = parsed === none ? 'linear' : easingToLinear(parsed);
|
|
8390
|
-
}
|
|
8391
|
-
// Only cache string based easing name, otherwise function arguments get lost
|
|
8392
|
-
WAAPIEasesLookups[ease] = parsedEase;
|
|
8393
|
-
} else if (isFnc(ease)) {
|
|
8394
|
-
const easing = easingToLinear(ease);
|
|
8395
|
-
if (easing) parsedEase = easing;
|
|
8396
|
-
} else if (/** @type {Spring} */(ease).ease) {
|
|
8397
|
-
parsedEase = easingToLinear(/** @type {Spring} */(ease).ease);
|
|
8398
|
-
}
|
|
8399
|
-
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;
|
|
8400
9496
|
};
|
|
8401
9497
|
|
|
8402
|
-
|
|
8403
|
-
const commonDefaultPXProperties = [
|
|
8404
|
-
'perspective',
|
|
8405
|
-
'width',
|
|
8406
|
-
'height',
|
|
8407
|
-
'margin',
|
|
8408
|
-
'padding',
|
|
8409
|
-
'top',
|
|
8410
|
-
'right',
|
|
8411
|
-
'bottom',
|
|
8412
|
-
'left',
|
|
8413
|
-
'borderWidth',
|
|
8414
|
-
'fontSize',
|
|
8415
|
-
'borderRadius',
|
|
8416
|
-
...transformsShorthands
|
|
8417
|
-
];
|
|
8418
|
-
|
|
8419
|
-
const validIndividualTransforms = /*#__PURE__*/ (() => [...transformsShorthands, ...validTransforms.filter(t => ['X', 'Y', 'Z'].some(axis => t.endsWith(axis)))])();
|
|
9498
|
+
|
|
8420
9499
|
|
|
8421
|
-
|
|
9500
|
+
// Motion path animation
|
|
8422
9501
|
|
|
8423
9502
|
/**
|
|
8424
|
-
* @param
|
|
8425
|
-
* @param
|
|
8426
|
-
* @param
|
|
8427
|
-
* @param
|
|
8428
|
-
* @param
|
|
8429
|
-
* @return {
|
|
9503
|
+
* @param {SVGGeometryElement} $path
|
|
9504
|
+
* @param {Number} totalLength
|
|
9505
|
+
* @param {Number} progress
|
|
9506
|
+
* @param {Number} lookup
|
|
9507
|
+
* @param {Boolean} shouldClamp
|
|
9508
|
+
* @return {DOMPoint}
|
|
8430
9509
|
*/
|
|
8431
|
-
const
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
|
|
8435
|
-
|
|
8436
|
-
|
|
8437
|
-
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);
|
|
8438
9516
|
};
|
|
8439
9517
|
|
|
8440
9518
|
/**
|
|
8441
|
-
* @param
|
|
8442
|
-
* @param
|
|
8443
|
-
* @param
|
|
8444
|
-
* @
|
|
8445
|
-
* @param {Number} i
|
|
8446
|
-
* @param {Number} targetsLength
|
|
8447
|
-
* @return {WAAPITweenValue}
|
|
9519
|
+
* @param {SVGGeometryElement} $path
|
|
9520
|
+
* @param {String} pathProperty
|
|
9521
|
+
* @param {Number} [offset=0]
|
|
9522
|
+
* @return {FunctionValue}
|
|
8448
9523
|
*/
|
|
8449
|
-
const
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
const
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
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
|
+
}
|
|
8458
9550
|
}
|
|
8459
|
-
return tweenValue;
|
|
8460
9551
|
};
|
|
8461
9552
|
|
|
8462
|
-
class WAAPIAnimation {
|
|
8463
9553
|
/**
|
|
8464
|
-
* @param {
|
|
8465
|
-
* @param {
|
|
9554
|
+
* @param {TargetsParam} path
|
|
9555
|
+
* @param {Number} [offset=0]
|
|
8466
9556
|
*/
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
validTransforms.forEach(t => {
|
|
8477
|
-
const isSkew = stringStartsWith(t, 'skew');
|
|
8478
|
-
const isScale = stringStartsWith(t, 'scale');
|
|
8479
|
-
const isRotate = stringStartsWith(t, 'rotate');
|
|
8480
|
-
const isTranslate = stringStartsWith(t, 'translate');
|
|
8481
|
-
const isAngle = isRotate || isSkew;
|
|
8482
|
-
const syntax = isAngle ? '<angle>' : isScale ? "<number>" : isTranslate ? "<length-percentage>" : "*";
|
|
8483
|
-
try {
|
|
8484
|
-
CSS.registerProperty({
|
|
8485
|
-
name: '--' + t,
|
|
8486
|
-
syntax,
|
|
8487
|
-
inherits: false,
|
|
8488
|
-
initialValue: isTranslate ? '0px' : isAngle ? '0deg' : isScale ? '1' : '0',
|
|
8489
|
-
});
|
|
8490
|
-
} catch {} });
|
|
8491
|
-
transformsPropertiesRegistered = true;
|
|
8492
|
-
}
|
|
8493
|
-
}
|
|
8494
|
-
|
|
8495
|
-
const parsedTargets = registerTargets(targets);
|
|
8496
|
-
const targetsLength = parsedTargets.length;
|
|
8497
|
-
|
|
8498
|
-
if (!targetsLength) {
|
|
8499
|
-
console.warn(`No target found. Make sure the element you're trying to animate is accessible before creating your animation.`);
|
|
8500
|
-
}
|
|
8501
|
-
|
|
8502
|
-
const ease = setValue(params.ease, parseWAAPIEasing(globals.defaults.ease));
|
|
8503
|
-
const spring = /** @type {Spring} */(ease).ease && ease;
|
|
8504
|
-
const autoplay = setValue(params.autoplay, globals.defaults.autoplay);
|
|
8505
|
-
const scroll = autoplay && /** @type {ScrollObserver} */(autoplay).link ? autoplay : false;
|
|
8506
|
-
const alternate = params.alternate && /** @type {Boolean} */(params.alternate) === true;
|
|
8507
|
-
const reversed = params.reversed && /** @type {Boolean} */(params.reversed) === true;
|
|
8508
|
-
const loop = setValue(params.loop, globals.defaults.loop);
|
|
8509
|
-
const iterations = /** @type {Number} */((loop === true || loop === Infinity) ? Infinity : isNum(loop) ? loop + 1 : 1);
|
|
8510
|
-
/** @type {PlaybackDirection} */
|
|
8511
|
-
const direction = alternate ? reversed ? 'alternate-reverse' : 'alternate' : reversed ? 'reverse' : 'normal';
|
|
8512
|
-
/** @type {FillMode} */
|
|
8513
|
-
const fill = 'both'; // We use 'both' here because the animation can be reversed during playback
|
|
8514
|
-
/** @type {String} */
|
|
8515
|
-
const easing = parseWAAPIEasing(ease);
|
|
8516
|
-
const timeScale = (globals.timeScale === 1 ? 1 : K);
|
|
8517
|
-
|
|
8518
|
-
/** @type {DOMTargetsArray}] */
|
|
8519
|
-
this.targets = parsedTargets;
|
|
8520
|
-
/** @type {Array<globalThis.Animation>}] */
|
|
8521
|
-
this.animations = [];
|
|
8522
|
-
/** @type {globalThis.Animation}] */
|
|
8523
|
-
this.controlAnimation = null;
|
|
8524
|
-
/** @type {Callback<this>} */
|
|
8525
|
-
this.onComplete = params.onComplete || /** @type {Callback<WAAPIAnimation>} */(/** @type {unknown} */(globals.defaults.onComplete));
|
|
8526
|
-
/** @type {Number} */
|
|
8527
|
-
this.duration = 0;
|
|
8528
|
-
/** @type {Boolean} */
|
|
8529
|
-
this.muteCallbacks = false;
|
|
8530
|
-
/** @type {Boolean} */
|
|
8531
|
-
this.completed = false;
|
|
8532
|
-
/** @type {Boolean} */
|
|
8533
|
-
this.paused = !autoplay || scroll !== false;
|
|
8534
|
-
/** @type {Boolean} */
|
|
8535
|
-
this.reversed = reversed;
|
|
8536
|
-
/** @type {Boolean} */
|
|
8537
|
-
this.persist = setValue(params.persist, globals.defaults.persist);
|
|
8538
|
-
/** @type {Boolean|ScrollObserver} */
|
|
8539
|
-
this.autoplay = autoplay;
|
|
8540
|
-
/** @type {Number} */
|
|
8541
|
-
this._speed = setValue(params.playbackRate, globals.defaults.playbackRate);
|
|
8542
|
-
/** @type {Function} */
|
|
8543
|
-
this._resolve = noop; // Used by .then()
|
|
8544
|
-
/** @type {Number} */
|
|
8545
|
-
this._completed = 0;
|
|
8546
|
-
/** @type {Array.<Object>} */
|
|
8547
|
-
this._inlineStyles = [];
|
|
8548
|
-
|
|
8549
|
-
parsedTargets.forEach(($el, i) => {
|
|
8550
|
-
|
|
8551
|
-
const cachedTransforms = $el[transformsSymbol];
|
|
8552
|
-
const hasIndividualTransforms = validIndividualTransforms.some(t => params.hasOwnProperty(t));
|
|
8553
|
-
const elStyle = $el.style;
|
|
8554
|
-
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
|
+
};
|
|
8555
9566
|
|
|
8556
|
-
|
|
8557
|
-
const duration = (spring ? /** @type {Spring} */(spring).settlingDuration : getFunctionValue(setValue(params.duration, globals.defaults.duration), $el, i, targetsLength)) * timeScale;
|
|
8558
|
-
/** @type {Number} */
|
|
8559
|
-
const delay = getFunctionValue(setValue(params.delay, globals.defaults.delay), $el, i, targetsLength) * timeScale;
|
|
8560
|
-
/** @type {CompositeOperation} */
|
|
8561
|
-
const composite = /** @type {CompositeOperation} */(setValue(params.composition, 'replace'));
|
|
9567
|
+
|
|
8562
9568
|
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
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
|
+
};
|
|
8571
9585
|
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
|
|
8575
|
-
|
|
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;
|
|
8576
9600
|
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
} else {
|
|
8604
|
-
const key = `--${individualTransformProperty}`;
|
|
8605
|
-
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
|
+
}
|
|
8606
9627
|
}
|
|
9628
|
+
target.setAttribute('stroke-dashoffset', `${os}`);
|
|
9629
|
+
target.setAttribute('stroke-dasharray', `${d1} ${d2}`);
|
|
8607
9630
|
}
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
propertyValue.map((/** @type {any} */v) => normalizeTweenValue(name, v, $el, i, targetsLength)) :
|
|
8611
|
-
normalizeTweenValue(name, /** @type {any} */(propertyValue), $el, i, targetsLength);
|
|
8612
|
-
if (individualTransformProperty) {
|
|
8613
|
-
keyframes[`--${individualTransformProperty}`] = parsedPropertyValue;
|
|
8614
|
-
cachedTransforms[individualTransformProperty] = parsedPropertyValue;
|
|
8615
|
-
} else {
|
|
8616
|
-
keyframes[name] = parsedPropertyValue;
|
|
8617
|
-
}
|
|
8618
|
-
addWAAPIAnimation(this, $el, name, keyframes, tweenParams);
|
|
8619
|
-
}
|
|
8620
|
-
}
|
|
8621
|
-
if (hasIndividualTransforms) {
|
|
8622
|
-
let transforms = emptyString;
|
|
8623
|
-
for (let t in cachedTransforms) {
|
|
8624
|
-
transforms += `${transformsFragmentStrings[t]}var(--${t})) `;
|
|
8625
|
-
}
|
|
8626
|
-
elStyle.transform = transforms;
|
|
9631
|
+
return Reflect.apply(value, target, args);
|
|
9632
|
+
};
|
|
8627
9633
|
}
|
|
8628
|
-
});
|
|
8629
9634
|
|
|
8630
|
-
|
|
8631
|
-
|
|
9635
|
+
if (isFnc(value)) {
|
|
9636
|
+
return (...args) => Reflect.apply(value, target, args);
|
|
9637
|
+
} else {
|
|
9638
|
+
return value;
|
|
9639
|
+
}
|
|
8632
9640
|
}
|
|
9641
|
+
});
|
|
9642
|
+
|
|
9643
|
+
if ($el.getAttribute('pathLength') !== `${pathLength}`) {
|
|
9644
|
+
$el.setAttribute('pathLength', `${pathLength}`);
|
|
9645
|
+
proxy.setAttribute('draw', `${start} ${end}`);
|
|
8633
9646
|
}
|
|
8634
9647
|
|
|
8635
|
-
/**
|
|
8636
|
-
|
|
8637
|
-
* @param {globalThis.Animation} animation
|
|
8638
|
-
*/
|
|
9648
|
+
return /** @type {DrawableSVGGeometry} */(proxy);
|
|
9649
|
+
};
|
|
8639
9650
|
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
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
|
+
};
|
|
8649
9666
|
|
|
8650
|
-
|
|
8651
|
-
return this._speed;
|
|
8652
|
-
}
|
|
9667
|
+
|
|
8653
9668
|
|
|
8654
|
-
|
|
8655
|
-
|
|
8656
|
-
|
|
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>.`);
|
|
8657
9678
|
}
|
|
8658
|
-
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
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>.`);
|
|
8663
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);
|
|
8664
9691
|
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
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
|
+
}
|
|
8675
9709
|
}
|
|
8676
9710
|
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
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
|
+
*/
|
|
8680
9740
|
|
|
8681
|
-
|
|
8682
|
-
|
|
8683
|
-
|
|
9741
|
+
/**
|
|
9742
|
+
* @typedef {Object} Segmenter
|
|
9743
|
+
* @property {function(String): Iterable<Segment>} segment
|
|
9744
|
+
*/
|
|
8684
9745
|
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
}
|
|
9746
|
+
/** @type {Segmenter} */
|
|
9747
|
+
let wordSegmenter = null;
|
|
9748
|
+
/** @type {Segmenter} */
|
|
9749
|
+
let graphemeSegmenter = null;
|
|
9750
|
+
let $splitTemplate = null;
|
|
8691
9751
|
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
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
|
+
};
|
|
8697
9761
|
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
return this;
|
|
8703
|
-
}
|
|
9762
|
+
/**
|
|
9763
|
+
* @param {HTMLElement} $el
|
|
9764
|
+
*/
|
|
9765
|
+
const setAriaHidden = $el => $el.setAttribute('aria-hidden', 'true');
|
|
8704
9766
|
|
|
8705
|
-
|
|
8706
|
-
|
|
8707
|
-
|
|
8708
|
-
|
|
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}])`))];
|
|
8709
9773
|
|
|
8710
|
-
|
|
8711
|
-
|
|
8712
|
-
|
|
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);
|
|
8713
9784
|
}
|
|
9785
|
+
};
|
|
8714
9786
|
|
|
8715
|
-
|
|
8716
|
-
|
|
8717
|
-
|
|
8718
|
-
|
|
8719
|
-
|
|
8720
|
-
|
|
8721
|
-
|
|
8722
|
-
|
|
8723
|
-
|
|
8724
|
-
|
|
8725
|
-
|
|
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
|
+
}
|
|
8726
9806
|
}
|
|
9807
|
+
let i = $el.childElementCount;
|
|
9808
|
+
while (i--) filterLineElements(/** @type {HTMLElement} */($el.children[i]), lineIndex, bin);
|
|
9809
|
+
return bin;
|
|
9810
|
+
};
|
|
8727
9811
|
|
|
8728
|
-
|
|
8729
|
-
|
|
8730
|
-
|
|
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}`;
|
|
8731
9832
|
}
|
|
9833
|
+
template += `</span>`;
|
|
9834
|
+
if (wrapType) template += `</span>`;
|
|
9835
|
+
return template;
|
|
9836
|
+
};
|
|
8732
9837
|
|
|
8733
|
-
|
|
8734
|
-
|
|
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.`);
|
|
8735
9887
|
}
|
|
9888
|
+
if (debug) $highestParent.style.outline = `1px dotted ${debugColors[type]}`;
|
|
9889
|
+
return $highestParent;
|
|
9890
|
+
};
|
|
8736
9891
|
|
|
8737
|
-
|
|
8738
|
-
|
|
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.');
|
|
8739
9967
|
}
|
|
8740
9968
|
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
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;
|
|
8744
9979
|
}
|
|
8745
9980
|
|
|
8746
9981
|
revert() {
|
|
8747
|
-
|
|
8748
|
-
|
|
8749
|
-
|
|
8750
|
-
//
|
|
8751
|
-
|
|
8752
|
-
this.
|
|
8753
|
-
const targetStyle = $el.style;
|
|
8754
|
-
const targetInlineStyles = this._inlineStyles[i];
|
|
8755
|
-
for (let name in targetInlineStyles) {
|
|
8756
|
-
const originalInlinedValue = targetInlineStyles[name];
|
|
8757
|
-
if (isUnd(originalInlinedValue) || originalInlinedValue === emptyString) {
|
|
8758
|
-
targetStyle.removeProperty(toLowerCase(name));
|
|
8759
|
-
} else {
|
|
8760
|
-
targetStyle[name] = originalInlinedValue;
|
|
8761
|
-
}
|
|
8762
|
-
}
|
|
8763
|
-
// Remove style attribute if empty
|
|
8764
|
-
if ($el.getAttribute('style') === emptyString) $el.removeAttribute('style');
|
|
8765
|
-
});
|
|
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;
|
|
8766
9988
|
return this;
|
|
8767
9989
|
}
|
|
8768
9990
|
|
|
8769
9991
|
/**
|
|
8770
|
-
*
|
|
9992
|
+
* Recursively processes a node and its children
|
|
9993
|
+
* @param {Node} node
|
|
8771
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
|
+
}
|
|
8772
10073
|
|
|
8773
10074
|
/**
|
|
8774
|
-
* @param
|
|
8775
|
-
* @return
|
|
10075
|
+
* @param {Boolean} clearCache
|
|
10076
|
+
* @return {this}
|
|
8776
10077
|
*/
|
|
8777
|
-
|
|
8778
|
-
const
|
|
8779
|
-
const
|
|
8780
|
-
|
|
8781
|
-
|
|
8782
|
-
|
|
8783
|
-
|
|
8784
|
-
|
|
8785
|
-
|
|
8786
|
-
|
|
8787
|
-
|
|
8788
|
-
|
|
8789
|
-
}
|
|
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);
|
|
8790
10176
|
}
|
|
8791
10177
|
}
|
|
8792
10178
|
|
|
8793
|
-
const waapi = {
|
|
8794
10179
|
/**
|
|
8795
|
-
* @param
|
|
8796
|
-
* @param
|
|
8797
|
-
* @return {
|
|
10180
|
+
* @param {HTMLElement|NodeList|String|Array<HTMLElement>} target
|
|
10181
|
+
* @param {TextSplitterParams} [parameters]
|
|
10182
|
+
* @return {TextSplitter}
|
|
8798
10183
|
*/
|
|
8799
|
-
|
|
8800
|
-
|
|
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);
|
|
8801
10196
|
};
|
|
8802
10197
|
|
|
10198
|
+
var index = /*#__PURE__*/Object.freeze({
|
|
10199
|
+
__proto__: null,
|
|
10200
|
+
TextSplitter: TextSplitter,
|
|
10201
|
+
split: split,
|
|
10202
|
+
splitText: splitText
|
|
10203
|
+
});
|
|
10204
|
+
|
|
8803
10205
|
exports.$ = registerTargets;
|
|
8804
10206
|
exports.Animatable = Animatable;
|
|
10207
|
+
exports.AutoLayout = AutoLayout;
|
|
8805
10208
|
exports.Draggable = Draggable;
|
|
8806
10209
|
exports.JSAnimation = JSAnimation;
|
|
8807
10210
|
exports.Scope = Scope;
|
|
@@ -8817,6 +10220,7 @@
|
|
|
8817
10220
|
exports.createAnimatable = createAnimatable;
|
|
8818
10221
|
exports.createDraggable = createDraggable;
|
|
8819
10222
|
exports.createDrawable = createDrawable;
|
|
10223
|
+
exports.createLayout = createLayout;
|
|
8820
10224
|
exports.createMotionPath = createMotionPath;
|
|
8821
10225
|
exports.createScope = createScope;
|
|
8822
10226
|
exports.createSeededRandom = createSeededRandom;
|