angular-movement 0.0.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.
@@ -0,0 +1,2190 @@
1
+ import { isPlatformBrowser, DOCUMENT } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { InjectionToken, inject, PLATFORM_ID, Injectable, input, ElementRef, Directive, computed, effect, forwardRef, afterEveryRender, NgZone, signal, ViewContainerRef, TemplateRef, makeEnvironmentProviders } from '@angular/core';
4
+
5
+ const MOVEMENT_DEFAULTS = {
6
+ duration: 300,
7
+ easing: 'cubic-bezier(0.16, 1, 0.3, 1)',
8
+ delay: 0,
9
+ disabled: false,
10
+ };
11
+ const MOVEMENT_CONFIG = new InjectionToken('MOVEMENT_CONFIG', {
12
+ factory: () => ({ ...MOVEMENT_DEFAULTS }),
13
+ });
14
+
15
+ const DEFAULT_DURATION = 300;
16
+ const DEFAULT_EASING = 'ease';
17
+ const DEFAULT_DELAY = 0;
18
+ const DEFAULT_PERSPECTIVE = '1200px';
19
+ const DEFAULT_SPRING = {
20
+ stiffness: 500,
21
+ damping: 30,
22
+ };
23
+ const SPRING_BASE = {
24
+ stiffness: 100,
25
+ damping: 10,
26
+ mass: 1,
27
+ velocity: 0,
28
+ };
29
+ const SIMULATION_TICK_RATE = 1000 / 60; // 60fps
30
+ const SIMULATION_MAX_ITERATIONS = 600;
31
+ const SIMULATION_SETTLED_THRESHOLD = 0.001;
32
+ const SCROLL_MAP_DURATION = 1000;
33
+ const SCROLL_WAAPI_CAP = 0.999;
34
+ const SCROLL_LERP_FACTOR = 0.12;
35
+ const SCROLL_RAF_THRESHOLD = 0.001;
36
+ const SMOOTH_SCROLL_FRICTION = 0.92;
37
+ const SMOOTH_SCROLL_MIN_VELOCITY = 0.05;
38
+ const DEFAULT_STAGGER_TIME = 100;
39
+ const DEFAULT_TEXT_STAGGER = 30;
40
+ const DRAG_SNAP_DURATION = 300;
41
+ const DRAG_SNAP_EASING = 'ease';
42
+
43
+ function getValAt(arr, index) {
44
+ if (!arr || arr.length === 0)
45
+ return undefined;
46
+ return arr[Math.min(index, arr.length - 1)];
47
+ }
48
+ function getInterpolated(arr, i1, i2, p) {
49
+ if (!arr || arr.length === 0)
50
+ return undefined;
51
+ const v1 = arr[Math.min(i1, arr.length - 1)];
52
+ const v2 = arr[Math.min(i2, arr.length - 1)];
53
+ return v1 + (v2 - v1) * p;
54
+ }
55
+ function buildKeyframe(frames, getVal) {
56
+ const keyframe = {};
57
+ const opacity = getVal(frames.opacity);
58
+ if (opacity !== undefined) {
59
+ keyframe.opacity = opacity;
60
+ }
61
+ const x = getVal(frames.x);
62
+ const y = getVal(frames.y);
63
+ if (x !== undefined || y !== undefined) {
64
+ keyframe.translate = `${x ?? 0}px ${y ?? 0}px`;
65
+ }
66
+ const scale = getVal(frames.scale);
67
+ if (scale !== undefined) {
68
+ keyframe.scale = `${scale}`;
69
+ }
70
+ else {
71
+ const scaleX = getVal(frames.scaleX);
72
+ const scaleY = getVal(frames.scaleY);
73
+ if (scaleX !== undefined || scaleY !== undefined) {
74
+ keyframe.scale = `${scaleX ?? 1} ${scaleY ?? 1}`;
75
+ }
76
+ }
77
+ const rotate = getVal(frames.rotate);
78
+ if (rotate !== undefined) {
79
+ keyframe.rotate = `${rotate}deg`;
80
+ }
81
+ const blur = getVal(frames.blur);
82
+ if (blur !== undefined) {
83
+ keyframe.filter = `blur(${blur}px)`;
84
+ }
85
+ const rotateX = getVal(frames.rotateX);
86
+ const rotateY = getVal(frames.rotateY);
87
+ if (rotateX !== undefined || rotateY !== undefined) {
88
+ keyframe.transform = `perspective(${DEFAULT_PERSPECTIVE}) rotateX(${rotateX ?? 0}deg) rotateY(${rotateY ?? 0}deg)`;
89
+ }
90
+ return keyframe;
91
+ }
92
+ function composeKeyframeAt(frames, index) {
93
+ return buildKeyframe(frames, (arr) => getValAt(arr, index));
94
+ }
95
+ function composeInterpolatedKeyframe(frames, i1, i2, p) {
96
+ return buildKeyframe(frames, (arr) => getInterpolated(arr, i1, i2, p));
97
+ }
98
+ function composeInitialStyle(frames) {
99
+ return buildKeyframe(frames, (arr) => (arr && arr.length > 0 ? arr[0] : undefined));
100
+ }
101
+ function composeFinalStyle(frames) {
102
+ return buildKeyframe(frames, (arr) => (arr && arr.length > 0 ? arr[arr.length - 1] : undefined));
103
+ }
104
+ function applyComposedStyle(el, style) {
105
+ if (style.opacity !== undefined)
106
+ el.style.opacity = `${style.opacity}`;
107
+ if (style.translate !== undefined)
108
+ el.style.translate = style.translate;
109
+ if (style.scale !== undefined)
110
+ el.style.scale = style.scale;
111
+ if (style.rotate !== undefined)
112
+ el.style.rotate = style.rotate;
113
+ if (style.transform !== undefined)
114
+ el.style.transform = style.transform;
115
+ if (style.filter !== undefined)
116
+ el.style.filter = style.filter;
117
+ }
118
+ function clearComposedStyle(el) {
119
+ el.style.opacity = '';
120
+ el.style.translate = '';
121
+ el.style.scale = '';
122
+ el.style.rotate = '';
123
+ el.style.transform = '';
124
+ el.style.filter = '';
125
+ }
126
+
127
+ const DEFAULT_FADE_OPACITY = [0, 1];
128
+ const DEFAULT_LEAVE_OPACITY = [1, 0];
129
+ const MOVE_PRESETS = {
130
+ 'fade-up': {
131
+ enter: { opacity: DEFAULT_FADE_OPACITY, y: [24, 0] },
132
+ leave: { opacity: DEFAULT_LEAVE_OPACITY, y: [0, -16] },
133
+ },
134
+ 'fade-down': {
135
+ enter: { opacity: DEFAULT_FADE_OPACITY, y: [-24, 0] },
136
+ leave: { opacity: DEFAULT_LEAVE_OPACITY, y: [0, 16] },
137
+ },
138
+ 'fade-left': {
139
+ enter: { opacity: DEFAULT_FADE_OPACITY, x: [24, 0] },
140
+ leave: { opacity: DEFAULT_LEAVE_OPACITY, x: [0, -16] },
141
+ },
142
+ 'fade-right': {
143
+ enter: { opacity: DEFAULT_FADE_OPACITY, x: [-24, 0] },
144
+ leave: { opacity: DEFAULT_LEAVE_OPACITY, x: [0, 16] },
145
+ },
146
+ 'slide-up': {
147
+ enter: { y: [60, 0], opacity: DEFAULT_FADE_OPACITY },
148
+ leave: { y: [0, -60], opacity: DEFAULT_LEAVE_OPACITY },
149
+ },
150
+ 'slide-down': {
151
+ enter: { y: [-60, 0], opacity: DEFAULT_FADE_OPACITY },
152
+ leave: { y: [0, 60], opacity: DEFAULT_LEAVE_OPACITY },
153
+ },
154
+ 'slide-left': {
155
+ enter: { x: [60, 0], opacity: DEFAULT_FADE_OPACITY },
156
+ leave: { x: [0, -60], opacity: DEFAULT_LEAVE_OPACITY },
157
+ },
158
+ 'slide-right': {
159
+ enter: { x: [-60, 0], opacity: DEFAULT_FADE_OPACITY },
160
+ leave: { x: [0, 60], opacity: DEFAULT_LEAVE_OPACITY },
161
+ },
162
+ 'zoom-in': {
163
+ enter: { opacity: DEFAULT_FADE_OPACITY, scale: [0.5, 1] },
164
+ leave: { opacity: DEFAULT_LEAVE_OPACITY, scale: [1, 0.5] },
165
+ },
166
+ 'zoom-out': {
167
+ enter: { opacity: DEFAULT_FADE_OPACITY, scale: [1.3, 1] },
168
+ leave: { opacity: DEFAULT_LEAVE_OPACITY, scale: [1, 1.3] },
169
+ },
170
+ 'flip-x': {
171
+ enter: { opacity: DEFAULT_FADE_OPACITY, rotateX: [-90, 0] },
172
+ leave: { opacity: DEFAULT_LEAVE_OPACITY, rotateX: [0, 90] },
173
+ },
174
+ 'flip-y': {
175
+ enter: { opacity: DEFAULT_FADE_OPACITY, rotateY: [-90, 0] },
176
+ leave: { opacity: DEFAULT_LEAVE_OPACITY, rotateY: [0, 90] },
177
+ },
178
+ 'bounce-in': {
179
+ enter: { opacity: DEFAULT_FADE_OPACITY, y: [30, 0], scale: [0.85, 1] },
180
+ leave: { opacity: DEFAULT_LEAVE_OPACITY, y: [0, -20], scale: [1, 0.9] },
181
+ },
182
+ 'blur-in': {
183
+ enter: { opacity: DEFAULT_FADE_OPACITY, blur: [10, 0] },
184
+ leave: { opacity: DEFAULT_LEAVE_OPACITY, blur: [0, 10] },
185
+ },
186
+ spin: {
187
+ enter: { opacity: DEFAULT_FADE_OPACITY, rotate: [-360, 0] },
188
+ leave: { opacity: DEFAULT_LEAVE_OPACITY, rotate: [0, 360] },
189
+ },
190
+ pulse: {
191
+ enter: { scale: [1, 1.05, 1] },
192
+ leave: { scale: [1, 0.95, 1] },
193
+ },
194
+ none: {
195
+ enter: { opacity: [1, 1] },
196
+ leave: { opacity: [1, 1] },
197
+ },
198
+ };
199
+
200
+ function resolveMovementConfig(defaults, overrides, reducedMotion) {
201
+ if (reducedMotion && typeof ngDevMode !== 'undefined' && ngDevMode) {
202
+ console.warn('[Movement] Animations disabled: prefers-reduced-motion is active. ' +
203
+ 'Disable "Reduce motion" in your OS accessibility settings to see animations.');
204
+ }
205
+ return {
206
+ duration: Math.max(0, overrides.duration ?? defaults.duration),
207
+ easing: overrides.easing ?? defaults.easing,
208
+ delay: Math.max(0, overrides.delay ?? defaults.delay),
209
+ disabled: reducedMotion || (overrides.disabled ?? defaults.disabled),
210
+ };
211
+ }
212
+ function resolveMoveFrames(value, phase) {
213
+ if (typeof value === 'string') {
214
+ const preset = MOVE_PRESETS[value];
215
+ if (!preset) {
216
+ if (typeof ngDevMode !== 'undefined' && ngDevMode) {
217
+ console.warn(`[Movement] Unknown preset: "${value}". Using "none" preset.`);
218
+ }
219
+ return MOVE_PRESETS['none'][phase];
220
+ }
221
+ return preset[phase];
222
+ }
223
+ return value;
224
+ }
225
+ function prefersReducedMotion(documentRef) {
226
+ const view = documentRef.defaultView;
227
+ if (!view || typeof view.matchMedia !== 'function') {
228
+ return false;
229
+ }
230
+ return view.matchMedia('(prefers-reduced-motion: reduce)').matches;
231
+ }
232
+ /**
233
+ * Reverses all keyframe value arrays in a MoveKeyframes object.
234
+ * Used by hover and tap directives to animate back to the original state.
235
+ */
236
+ function reverseFrames(frames) {
237
+ const reversed = {};
238
+ for (const key in frames) {
239
+ const k = key;
240
+ const arr = frames[k];
241
+ if (arr) {
242
+ reversed[k] = [...arr].reverse();
243
+ }
244
+ }
245
+ return reversed;
246
+ }
247
+ /**
248
+ * Applies the first keyframe values as inline styles to an element.
249
+ * Used by in-view and text directives to set the initial (hidden) state
250
+ * before the IntersectionObserver triggers the animation.
251
+ */
252
+ function applyInitialStyles(el, frames) {
253
+ applyComposedStyle(el, composeInitialStyle(frames));
254
+ }
255
+ /**
256
+ * Clears all inline styles set by `applyInitialStyles`.
257
+ * Called just before WAAPI animates so it can take full control.
258
+ */
259
+ function clearInitialStyles(el) {
260
+ clearComposedStyle(el);
261
+ }
262
+
263
+ class WaapiPlayer {
264
+ #animation = null;
265
+ #resolveFinished;
266
+ finished = new Promise((resolve) => {
267
+ this.#resolveFinished = resolve;
268
+ });
269
+ constructor(host, frames, config, onDone) {
270
+ if (typeof host.animate !== 'function') {
271
+ this.#resolveFinished();
272
+ onDone?.();
273
+ return;
274
+ }
275
+ const keyframes = this.#toWAAPIKeyframes(frames);
276
+ this.#animation = host.animate(keyframes, {
277
+ duration: config.duration,
278
+ easing: config.easing,
279
+ delay: config.delay,
280
+ fill: 'both',
281
+ });
282
+ this.#animation.addEventListener('finish', () => {
283
+ this.#animation?.commitStyles?.();
284
+ this.#animation?.cancel();
285
+ this.#resolveFinished();
286
+ onDone?.();
287
+ }, { once: true });
288
+ }
289
+ play() {
290
+ this.#animation?.play();
291
+ }
292
+ pause() {
293
+ this.#animation?.pause();
294
+ }
295
+ cancel() {
296
+ if (this.#animation?.playState !== 'idle') {
297
+ this.#animation?.cancel();
298
+ }
299
+ this.#resolveFinished();
300
+ }
301
+ get currentTime() {
302
+ return this.#animation?.currentTime ?? 0;
303
+ }
304
+ set currentTime(time) {
305
+ if (this.#animation) {
306
+ this.#animation.currentTime = time;
307
+ }
308
+ }
309
+ #toWAAPIKeyframes(frames) {
310
+ let maxLength = 0;
311
+ for (const key in frames) {
312
+ const arr = frames[key];
313
+ if (Array.isArray(arr)) {
314
+ maxLength = Math.max(maxLength, arr.length);
315
+ }
316
+ }
317
+ if (maxLength === 0)
318
+ return [];
319
+ const keyframes = [];
320
+ for (let i = 0; i < maxLength; i++) {
321
+ keyframes.push(composeKeyframeAt(frames, i));
322
+ }
323
+ return keyframes;
324
+ }
325
+ }
326
+
327
+ class SpringPlayer {
328
+ host;
329
+ frames;
330
+ delay;
331
+ onDone;
332
+ #resolveFinished;
333
+ finished = new Promise((resolve) => {
334
+ this.#resolveFinished = resolve;
335
+ });
336
+ #animation = null;
337
+ constructor(host, frames, userConfig, delay, onDone) {
338
+ this.host = host;
339
+ this.frames = frames;
340
+ this.delay = delay;
341
+ this.onDone = onDone;
342
+ if (typeof host.animate !== 'function') {
343
+ this.#resolveFinished();
344
+ onDone?.();
345
+ return;
346
+ }
347
+ const config = {
348
+ stiffness: 100,
349
+ damping: 10,
350
+ mass: 1,
351
+ velocity: 0,
352
+ ...userConfig,
353
+ };
354
+ const keyframes = this.#generateSpringKeyframes(frames, config);
355
+ if (keyframes.length === 0) {
356
+ this.#resolveFinished();
357
+ onDone?.();
358
+ return;
359
+ }
360
+ // Default duration of the calculated simulation is bound to the arrays output.
361
+ // We run it over that specific time frame, we know exactly the duration by counting ticks * tick duration.
362
+ // Let's assume tick rate is 16.66ms (60fps simulation)
363
+ const duration = keyframes.length * SIMULATION_TICK_RATE;
364
+ this.#animation = host.animate(keyframes, {
365
+ duration,
366
+ delay: this.delay,
367
+ fill: 'both',
368
+ easing: 'linear', // Spring physics already has the easing baked into the frames
369
+ });
370
+ this.#animation.addEventListener('finish', () => {
371
+ this.#animation?.commitStyles?.();
372
+ this.#animation?.cancel();
373
+ this.#resolveFinished();
374
+ onDone?.();
375
+ }, { once: true });
376
+ }
377
+ play() {
378
+ this.#animation?.play();
379
+ }
380
+ pause() {
381
+ this.#animation?.pause();
382
+ }
383
+ cancel() {
384
+ if (this.#animation?.playState !== 'idle') {
385
+ this.#animation?.cancel();
386
+ }
387
+ this.#resolveFinished();
388
+ }
389
+ get currentTime() {
390
+ return this.#animation?.currentTime ?? 0;
391
+ }
392
+ set currentTime(time) {
393
+ if (this.#animation) {
394
+ this.#animation.currentTime = time;
395
+ }
396
+ }
397
+ #generateSpringKeyframes(frames, config) {
398
+ let maxSteps = 0;
399
+ for (const key in frames) {
400
+ const arr = frames[key];
401
+ if (Array.isArray(arr)) {
402
+ maxSteps = Math.max(maxSteps, arr.length);
403
+ }
404
+ }
405
+ if (maxSteps <= 1)
406
+ return [];
407
+ const keyframes = [];
408
+ const dt = 1 / 60; // Simulate at 60fps (16.66ms per tick)
409
+ const stiffness = config.stiffness;
410
+ const damping = config.damping;
411
+ const mass = config.mass;
412
+ // We will simulate segments between keyframes based on physics
413
+ for (let step = 0; step < maxSteps - 1; step++) {
414
+ let progress = 0;
415
+ let velocity = step === 0 ? (config.velocity ?? 0) : 0;
416
+ let isSettled = false;
417
+ // Simulate the spring for this intermediate segment
418
+ // Prevent infinite loops safely by bounding iterations
419
+ let iterations = 0;
420
+ const maxIterations = SIMULATION_MAX_ITERATIONS; // max 10 seconds per step
421
+ while (!isSettled && iterations < maxIterations) {
422
+ // Evaluate frame
423
+ const p = Math.min(Math.max(progress, 0), 1);
424
+ keyframes.push(this.#composeFrame(frames, step, step + 1, p));
425
+ // Advance physics F = -k*x - c*v
426
+ const displacement = progress - 1;
427
+ const force = -stiffness * displacement - damping * velocity;
428
+ const acceleration = force / mass;
429
+ velocity += acceleration * dt;
430
+ progress += velocity * dt;
431
+ if (Math.abs(displacement) < SIMULATION_SETTLED_THRESHOLD &&
432
+ Math.abs(velocity) < SIMULATION_SETTLED_THRESHOLD) {
433
+ isSettled = true;
434
+ }
435
+ iterations++;
436
+ }
437
+ // Ensure the final state of the step is exactly 1
438
+ keyframes.push(this.#composeFrame(frames, step, step + 1, 1));
439
+ }
440
+ return keyframes;
441
+ }
442
+ #composeFrame(frames, i1, i2, p) {
443
+ return composeInterpolatedKeyframe(frames, i1, i2, p);
444
+ }
445
+ }
446
+
447
+ class AnimationEngine {
448
+ #platformId = inject(PLATFORM_ID);
449
+ #defaults = inject(MOVEMENT_CONFIG);
450
+ play(host, frames, options = {}) {
451
+ if (!isPlatformBrowser(this.#platformId)) {
452
+ options.onDone?.();
453
+ return null;
454
+ }
455
+ if (options.disabled) {
456
+ this.#applyFinalStyles(host, frames);
457
+ options.onDone?.();
458
+ return null;
459
+ }
460
+ const config = options.config ?? this.#defaults;
461
+ const isSpring = options.spring || config.easing === 'spring';
462
+ if (isSpring) {
463
+ return new SpringPlayer(host, frames, options.spring ?? {}, options.delay ?? config.delay, options.onDone);
464
+ }
465
+ else {
466
+ return new WaapiPlayer(host, frames, {
467
+ duration: config.duration,
468
+ easing: config.easing,
469
+ delay: options.delay ?? config.delay,
470
+ disabled: false,
471
+ }, options.onDone);
472
+ }
473
+ }
474
+ #applyFinalStyles(host, frames) {
475
+ applyComposedStyle(host, composeFinalStyle(frames));
476
+ }
477
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: AnimationEngine, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
478
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: AnimationEngine, providedIn: 'root' });
479
+ }
480
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: AnimationEngine, decorators: [{
481
+ type: Injectable,
482
+ args: [{ providedIn: 'root' }]
483
+ }] });
484
+
485
+ const MOVE_STAGGER_PARENT = new InjectionToken('MOVE_STAGGER_PARENT');
486
+
487
+ const MOVE_PRESENCE_PARENT = new InjectionToken('MOVE_PRESENCE_PARENT');
488
+
489
+ class MoveAnimateDirective {
490
+ move = input(undefined, ...(ngDevMode ? [{ debugName: "move" }] : /* istanbul ignore next */ []));
491
+ moveAnimate = input(undefined, ...(ngDevMode ? [{ debugName: "moveAnimate" }] : /* istanbul ignore next */ []));
492
+ moveAnimateLeave = input(undefined, ...(ngDevMode ? [{ debugName: "moveAnimateLeave" }] : /* istanbul ignore next */ []));
493
+ moveDuration = input(undefined, ...(ngDevMode ? [{ debugName: "moveDuration" }] : /* istanbul ignore next */ []));
494
+ moveEasing = input(undefined, ...(ngDevMode ? [{ debugName: "moveEasing" }] : /* istanbul ignore next */ []));
495
+ moveDelay = input(undefined, ...(ngDevMode ? [{ debugName: "moveDelay" }] : /* istanbul ignore next */ []));
496
+ moveDisabled = input(undefined, ...(ngDevMode ? [{ debugName: "moveDisabled" }] : /* istanbul ignore next */ []));
497
+ moveSpring = input(undefined, ...(ngDevMode ? [{ debugName: "moveSpring" }] : /* istanbul ignore next */ []));
498
+ #defaults = inject(MOVEMENT_CONFIG);
499
+ #documentRef = inject(DOCUMENT);
500
+ #host = inject((ElementRef));
501
+ #engine = inject(AnimationEngine);
502
+ #stagger = inject(MOVE_STAGGER_PARENT, { optional: true });
503
+ #presence = inject(MOVE_PRESENCE_PARENT, { optional: true });
504
+ #config = this.#defaults;
505
+ #enterPlayer = null;
506
+ #leavePlayer = null;
507
+ ngOnInit() {
508
+ this.#stagger?.register(this.#host.nativeElement);
509
+ this.#presence?.register(this);
510
+ Promise.resolve().then(() => {
511
+ const staggerDelay = this.#stagger?.getDelay(this.#host.nativeElement) ?? 0;
512
+ this.#config = resolveMovementConfig(this.#defaults, {
513
+ duration: this.moveDuration(),
514
+ easing: this.moveEasing(),
515
+ delay: (this.moveDelay() ?? 0) + staggerDelay,
516
+ disabled: this.moveDisabled(),
517
+ }, prefersReducedMotion(this.#documentRef));
518
+ const enterInput = this.resolveEnterInput();
519
+ this.#enterPlayer = this.#engine.play(this.#host.nativeElement, resolveMoveFrames(enterInput, 'enter'), {
520
+ config: this.#config,
521
+ spring: this.moveSpring(),
522
+ disabled: this.#config.disabled,
523
+ });
524
+ });
525
+ }
526
+ ngOnDestroy() {
527
+ this.#stagger?.unregister(this.#host.nativeElement);
528
+ this.#presence?.unregister(this);
529
+ this.#enterPlayer?.cancel();
530
+ this.#leavePlayer?.cancel();
531
+ }
532
+ playLeave() {
533
+ if (this.#config.disabled) {
534
+ return Promise.resolve();
535
+ }
536
+ this.#enterPlayer?.cancel();
537
+ this.#leavePlayer = this.#engine.play(this.#host.nativeElement, resolveMoveFrames(this.resolveLeaveInput(), 'leave'), {
538
+ config: this.#config,
539
+ spring: this.moveSpring(),
540
+ disabled: false,
541
+ });
542
+ return this.#leavePlayer?.finished ?? Promise.resolve();
543
+ }
544
+ resolveEnterInput() {
545
+ return this.moveAnimate() ?? this.move() ?? 'none';
546
+ }
547
+ resolveLeaveInput() {
548
+ return this.moveAnimateLeave() ?? this.resolveEnterInput();
549
+ }
550
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveAnimateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
551
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.2", type: MoveAnimateDirective, isStandalone: true, selector: "[move],[moveAnimate]", inputs: { move: { classPropertyName: "move", publicName: "move", isSignal: true, isRequired: false, transformFunction: null }, moveAnimate: { classPropertyName: "moveAnimate", publicName: "moveAnimate", isSignal: true, isRequired: false, transformFunction: null }, moveAnimateLeave: { classPropertyName: "moveAnimateLeave", publicName: "moveAnimateLeave", isSignal: true, isRequired: false, transformFunction: null }, moveDuration: { classPropertyName: "moveDuration", publicName: "moveDuration", isSignal: true, isRequired: false, transformFunction: null }, moveEasing: { classPropertyName: "moveEasing", publicName: "moveEasing", isSignal: true, isRequired: false, transformFunction: null }, moveDelay: { classPropertyName: "moveDelay", publicName: "moveDelay", isSignal: true, isRequired: false, transformFunction: null }, moveDisabled: { classPropertyName: "moveDisabled", publicName: "moveDisabled", isSignal: true, isRequired: false, transformFunction: null }, moveSpring: { classPropertyName: "moveSpring", publicName: "moveSpring", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
552
+ }
553
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveAnimateDirective, decorators: [{
554
+ type: Directive,
555
+ args: [{
556
+ selector: '[move],[moveAnimate]',
557
+ }]
558
+ }], propDecorators: { move: [{ type: i0.Input, args: [{ isSignal: true, alias: "move", required: false }] }], moveAnimate: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveAnimate", required: false }] }], moveAnimateLeave: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveAnimateLeave", required: false }] }], moveDuration: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDuration", required: false }] }], moveEasing: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveEasing", required: false }] }], moveDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDelay", required: false }] }], moveDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDisabled", required: false }] }], moveSpring: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveSpring", required: false }] }] } });
559
+
560
+ class MoveAnimationDirective {
561
+ moveAnimation = input.required(...(ngDevMode ? [{ debugName: "moveAnimation" }] : /* istanbul ignore next */ []));
562
+ #defaults = inject(MOVEMENT_CONFIG);
563
+ #documentRef = inject(DOCUMENT);
564
+ #host = inject((ElementRef));
565
+ #engine = inject(AnimationEngine);
566
+ #stagger = inject(MOVE_STAGGER_PARENT, { optional: true });
567
+ #presence = inject(MOVE_PRESENCE_PARENT, { optional: true });
568
+ #config = this.#defaults;
569
+ #enterPlayer = null;
570
+ #leavePlayer = null;
571
+ ngOnInit() {
572
+ this.#stagger?.register(this.#host.nativeElement);
573
+ this.#presence?.register(this);
574
+ Promise.resolve().then(() => {
575
+ const cfg = this.moveAnimation();
576
+ const staggerDelay = this.#stagger?.getDelay(this.#host.nativeElement) ?? 0;
577
+ this.#config = resolveMovementConfig(this.#defaults, {
578
+ duration: cfg.duration,
579
+ easing: cfg.easing,
580
+ delay: (cfg.delay ?? 0) + staggerDelay,
581
+ disabled: undefined,
582
+ }, prefersReducedMotion(this.#documentRef));
583
+ if (!cfg.initial || !cfg.animate)
584
+ return;
585
+ const frames = statesToKeyframes(cfg.initial, cfg.animate);
586
+ if (Object.keys(frames).length === 0)
587
+ return;
588
+ this.#enterPlayer = this.#engine.play(this.#host.nativeElement, frames, {
589
+ config: this.#config,
590
+ spring: cfg.spring,
591
+ disabled: this.#config.disabled,
592
+ });
593
+ });
594
+ }
595
+ ngOnDestroy() {
596
+ this.#stagger?.unregister(this.#host.nativeElement);
597
+ this.#presence?.unregister(this);
598
+ this.#enterPlayer?.cancel();
599
+ this.#leavePlayer?.cancel();
600
+ }
601
+ playLeave() {
602
+ const cfg = this.moveAnimation();
603
+ if (this.#config.disabled || !cfg.exit || !cfg.animate) {
604
+ return Promise.resolve();
605
+ }
606
+ this.#enterPlayer?.cancel();
607
+ const frames = statesToKeyframes(cfg.animate, cfg.exit);
608
+ if (Object.keys(frames).length === 0)
609
+ return Promise.resolve();
610
+ this.#leavePlayer = this.#engine.play(this.#host.nativeElement, frames, {
611
+ config: this.#config,
612
+ spring: cfg.spring,
613
+ disabled: false,
614
+ });
615
+ return this.#leavePlayer?.finished ?? Promise.resolve();
616
+ }
617
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveAnimationDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
618
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.2", type: MoveAnimationDirective, isStandalone: true, selector: "[moveAnimation]", inputs: { moveAnimation: { classPropertyName: "moveAnimation", publicName: "moveAnimation", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
619
+ }
620
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveAnimationDirective, decorators: [{
621
+ type: Directive,
622
+ args: [{
623
+ selector: '[moveAnimation]',
624
+ }]
625
+ }], propDecorators: { moveAnimation: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveAnimation", required: true }] }] } });
626
+ function statesToKeyframes(from, to) {
627
+ const result = {};
628
+ for (const key of Object.keys(from)) {
629
+ const f = from[key];
630
+ const t = to[key];
631
+ if (f !== undefined && t !== undefined) {
632
+ result[key] = [f, t];
633
+ }
634
+ }
635
+ return result;
636
+ }
637
+
638
+ class MoveEnterDirective {
639
+ moveEnter = input('none', ...(ngDevMode ? [{ debugName: "moveEnter" }] : /* istanbul ignore next */ []));
640
+ moveDuration = input(undefined, ...(ngDevMode ? [{ debugName: "moveDuration" }] : /* istanbul ignore next */ []));
641
+ moveEasing = input(undefined, ...(ngDevMode ? [{ debugName: "moveEasing" }] : /* istanbul ignore next */ []));
642
+ moveDelay = input(undefined, ...(ngDevMode ? [{ debugName: "moveDelay" }] : /* istanbul ignore next */ []));
643
+ moveDisabled = input(undefined, ...(ngDevMode ? [{ debugName: "moveDisabled" }] : /* istanbul ignore next */ []));
644
+ moveSpring = input(undefined, ...(ngDevMode ? [{ debugName: "moveSpring" }] : /* istanbul ignore next */ []));
645
+ #defaults = inject(MOVEMENT_CONFIG);
646
+ #documentRef = inject(DOCUMENT);
647
+ #host = inject((ElementRef));
648
+ #engine = inject(AnimationEngine);
649
+ #stagger = inject(MOVE_STAGGER_PARENT, { optional: true });
650
+ #player = null;
651
+ ngOnInit() {
652
+ this.#stagger?.register(this.#host.nativeElement);
653
+ Promise.resolve().then(() => {
654
+ const staggerDelay = this.#stagger?.getDelay(this.#host.nativeElement) ?? 0;
655
+ const config = resolveMovementConfig(this.#defaults, {
656
+ duration: this.moveDuration(),
657
+ easing: this.moveEasing(),
658
+ delay: (this.moveDelay() ?? 0) + staggerDelay,
659
+ disabled: this.moveDisabled(),
660
+ }, prefersReducedMotion(this.#documentRef));
661
+ this.#player = this.#engine.play(this.#host.nativeElement, resolveMoveFrames(this.moveEnter(), 'enter'), {
662
+ config: config,
663
+ spring: this.moveSpring(),
664
+ disabled: config.disabled,
665
+ });
666
+ });
667
+ }
668
+ ngOnDestroy() {
669
+ this.#stagger?.unregister(this.#host.nativeElement);
670
+ this.#player?.cancel();
671
+ }
672
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveEnterDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
673
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.2", type: MoveEnterDirective, isStandalone: true, selector: "[moveEnter]", inputs: { moveEnter: { classPropertyName: "moveEnter", publicName: "moveEnter", isSignal: true, isRequired: false, transformFunction: null }, moveDuration: { classPropertyName: "moveDuration", publicName: "moveDuration", isSignal: true, isRequired: false, transformFunction: null }, moveEasing: { classPropertyName: "moveEasing", publicName: "moveEasing", isSignal: true, isRequired: false, transformFunction: null }, moveDelay: { classPropertyName: "moveDelay", publicName: "moveDelay", isSignal: true, isRequired: false, transformFunction: null }, moveDisabled: { classPropertyName: "moveDisabled", publicName: "moveDisabled", isSignal: true, isRequired: false, transformFunction: null }, moveSpring: { classPropertyName: "moveSpring", publicName: "moveSpring", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
674
+ }
675
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveEnterDirective, decorators: [{
676
+ type: Directive,
677
+ args: [{
678
+ selector: '[moveEnter]',
679
+ }]
680
+ }], propDecorators: { moveEnter: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveEnter", required: false }] }], moveDuration: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDuration", required: false }] }], moveEasing: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveEasing", required: false }] }], moveDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDelay", required: false }] }], moveDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDisabled", required: false }] }], moveSpring: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveSpring", required: false }] }] } });
681
+
682
+ class MoveLeaveDirective {
683
+ moveLeave = input('none', ...(ngDevMode ? [{ debugName: "moveLeave" }] : /* istanbul ignore next */ []));
684
+ moveDuration = input(undefined, ...(ngDevMode ? [{ debugName: "moveDuration" }] : /* istanbul ignore next */ []));
685
+ moveEasing = input(undefined, ...(ngDevMode ? [{ debugName: "moveEasing" }] : /* istanbul ignore next */ []));
686
+ moveDelay = input(undefined, ...(ngDevMode ? [{ debugName: "moveDelay" }] : /* istanbul ignore next */ []));
687
+ moveDisabled = input(undefined, ...(ngDevMode ? [{ debugName: "moveDisabled" }] : /* istanbul ignore next */ []));
688
+ moveSpring = input(undefined, ...(ngDevMode ? [{ debugName: "moveSpring" }] : /* istanbul ignore next */ []));
689
+ #defaults = inject(MOVEMENT_CONFIG);
690
+ #documentRef = inject(DOCUMENT);
691
+ #host = inject((ElementRef));
692
+ #engine = inject(AnimationEngine);
693
+ #presence = inject(MOVE_PRESENCE_PARENT, { optional: true });
694
+ #config = this.#defaults;
695
+ #player = null;
696
+ ngOnInit() {
697
+ this.#presence?.register(this);
698
+ this.#config = resolveMovementConfig(this.#defaults, {
699
+ duration: this.moveDuration(),
700
+ easing: this.moveEasing(),
701
+ delay: this.moveDelay(),
702
+ disabled: this.moveDisabled(),
703
+ }, prefersReducedMotion(this.#documentRef));
704
+ }
705
+ ngOnDestroy() {
706
+ this.#presence?.unregister(this);
707
+ this.#player?.cancel();
708
+ }
709
+ playLeave() {
710
+ if (this.#config.disabled) {
711
+ return Promise.resolve();
712
+ }
713
+ this.#player = this.#engine.play(this.#host.nativeElement, resolveMoveFrames(this.moveLeave(), 'leave'), {
714
+ config: this.#config,
715
+ spring: this.moveSpring(),
716
+ disabled: false,
717
+ });
718
+ return this.#player?.finished ?? Promise.resolve();
719
+ }
720
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveLeaveDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
721
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.2", type: MoveLeaveDirective, isStandalone: true, selector: "[moveLeave]", inputs: { moveLeave: { classPropertyName: "moveLeave", publicName: "moveLeave", isSignal: true, isRequired: false, transformFunction: null }, moveDuration: { classPropertyName: "moveDuration", publicName: "moveDuration", isSignal: true, isRequired: false, transformFunction: null }, moveEasing: { classPropertyName: "moveEasing", publicName: "moveEasing", isSignal: true, isRequired: false, transformFunction: null }, moveDelay: { classPropertyName: "moveDelay", publicName: "moveDelay", isSignal: true, isRequired: false, transformFunction: null }, moveDisabled: { classPropertyName: "moveDisabled", publicName: "moveDisabled", isSignal: true, isRequired: false, transformFunction: null }, moveSpring: { classPropertyName: "moveSpring", publicName: "moveSpring", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
722
+ }
723
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveLeaveDirective, decorators: [{
724
+ type: Directive,
725
+ args: [{
726
+ selector: '[moveLeave]',
727
+ }]
728
+ }], propDecorators: { moveLeave: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveLeave", required: false }] }], moveDuration: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDuration", required: false }] }], moveEasing: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveEasing", required: false }] }], moveDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDelay", required: false }] }], moveDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDisabled", required: false }] }], moveSpring: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveSpring", required: false }] }] } });
729
+
730
+ class MoveHoverDirective {
731
+ moveWhileHover = input.required(...(ngDevMode ? [{ debugName: "moveWhileHover" }] : /* istanbul ignore next */ []));
732
+ moveDuration = input(undefined, ...(ngDevMode ? [{ debugName: "moveDuration" }] : /* istanbul ignore next */ []));
733
+ moveEasing = input(undefined, ...(ngDevMode ? [{ debugName: "moveEasing" }] : /* istanbul ignore next */ []));
734
+ moveDelay = input(undefined, ...(ngDevMode ? [{ debugName: "moveDelay" }] : /* istanbul ignore next */ []));
735
+ moveDisabled = input(undefined, ...(ngDevMode ? [{ debugName: "moveDisabled" }] : /* istanbul ignore next */ []));
736
+ moveSpring = input(undefined, ...(ngDevMode ? [{ debugName: "moveSpring" }] : /* istanbul ignore next */ []));
737
+ #defaults = inject(MOVEMENT_CONFIG);
738
+ #documentRef = inject(DOCUMENT);
739
+ #host = inject((ElementRef));
740
+ #engine = inject(AnimationEngine);
741
+ #currentPlayer = null;
742
+ #isHovered = false;
743
+ onMouseEnter() {
744
+ if (this.#isHovered)
745
+ return;
746
+ this.#isHovered = true;
747
+ this.play(false);
748
+ }
749
+ onMouseLeave() {
750
+ if (!this.#isHovered)
751
+ return;
752
+ this.#isHovered = false;
753
+ this.play(true);
754
+ }
755
+ onTouchStart(event) {
756
+ event.preventDefault();
757
+ if (this.#isHovered)
758
+ return;
759
+ this.#isHovered = true;
760
+ this.play(false);
761
+ }
762
+ onTouchEnd() {
763
+ if (!this.#isHovered)
764
+ return;
765
+ this.#isHovered = false;
766
+ this.play(true);
767
+ }
768
+ play(reverse) {
769
+ this.#currentPlayer?.cancel();
770
+ const isReduced = prefersReducedMotion(this.#documentRef);
771
+ const config = resolveMovementConfig(this.#defaults, {
772
+ duration: this.moveDuration(),
773
+ easing: this.moveEasing(),
774
+ delay: this.moveDelay(),
775
+ disabled: this.moveDisabled(),
776
+ }, isReduced);
777
+ if (config.disabled)
778
+ return;
779
+ let frames = resolveMoveFrames(this.moveWhileHover(), 'enter');
780
+ if (reverse) {
781
+ frames = reverseFrames(frames);
782
+ }
783
+ this.#currentPlayer = this.#engine.play(this.#host.nativeElement, frames, {
784
+ config,
785
+ spring: this.moveSpring(),
786
+ disabled: false,
787
+ });
788
+ }
789
+ ngOnDestroy() {
790
+ this.#currentPlayer?.cancel();
791
+ }
792
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveHoverDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
793
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.2", type: MoveHoverDirective, isStandalone: true, selector: "[moveWhileHover]", inputs: { moveWhileHover: { classPropertyName: "moveWhileHover", publicName: "moveWhileHover", isSignal: true, isRequired: true, transformFunction: null }, moveDuration: { classPropertyName: "moveDuration", publicName: "moveDuration", isSignal: true, isRequired: false, transformFunction: null }, moveEasing: { classPropertyName: "moveEasing", publicName: "moveEasing", isSignal: true, isRequired: false, transformFunction: null }, moveDelay: { classPropertyName: "moveDelay", publicName: "moveDelay", isSignal: true, isRequired: false, transformFunction: null }, moveDisabled: { classPropertyName: "moveDisabled", publicName: "moveDisabled", isSignal: true, isRequired: false, transformFunction: null }, moveSpring: { classPropertyName: "moveSpring", publicName: "moveSpring", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "mouseenter": "onMouseEnter()", "mouseleave": "onMouseLeave()", "touchstart": "onTouchStart($event)", "touchend": "onTouchEnd()", "touchcancel": "onTouchEnd()" } }, ngImport: i0 });
794
+ }
795
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveHoverDirective, decorators: [{
796
+ type: Directive,
797
+ args: [{
798
+ selector: '[moveWhileHover]',
799
+ host: {
800
+ '(mouseenter)': 'onMouseEnter()',
801
+ '(mouseleave)': 'onMouseLeave()',
802
+ '(touchstart)': 'onTouchStart($event)',
803
+ '(touchend)': 'onTouchEnd()',
804
+ '(touchcancel)': 'onTouchEnd()',
805
+ },
806
+ }]
807
+ }], propDecorators: { moveWhileHover: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveWhileHover", required: true }] }], moveDuration: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDuration", required: false }] }], moveEasing: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveEasing", required: false }] }], moveDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDelay", required: false }] }], moveDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDisabled", required: false }] }], moveSpring: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveSpring", required: false }] }] } });
808
+
809
+ class MoveTapDirective {
810
+ moveWhileTap = input.required(...(ngDevMode ? [{ debugName: "moveWhileTap" }] : /* istanbul ignore next */ []));
811
+ moveDuration = input(undefined, ...(ngDevMode ? [{ debugName: "moveDuration" }] : /* istanbul ignore next */ []));
812
+ moveEasing = input(undefined, ...(ngDevMode ? [{ debugName: "moveEasing" }] : /* istanbul ignore next */ []));
813
+ moveDelay = input(undefined, ...(ngDevMode ? [{ debugName: "moveDelay" }] : /* istanbul ignore next */ []));
814
+ moveDisabled = input(undefined, ...(ngDevMode ? [{ debugName: "moveDisabled" }] : /* istanbul ignore next */ []));
815
+ moveSpring = input(undefined, ...(ngDevMode ? [{ debugName: "moveSpring" }] : /* istanbul ignore next */ []));
816
+ #defaults = inject(MOVEMENT_CONFIG);
817
+ #documentRef = inject(DOCUMENT);
818
+ #host = inject((ElementRef));
819
+ #engine = inject(AnimationEngine);
820
+ #currentPlayer = null;
821
+ #isTapped = false;
822
+ onPointerDown() {
823
+ if (this.#isTapped)
824
+ return;
825
+ this.#isTapped = true;
826
+ this.play(false);
827
+ }
828
+ onPointerUp() {
829
+ if (!this.#isTapped)
830
+ return;
831
+ this.#isTapped = false;
832
+ this.play(true);
833
+ }
834
+ play(reverse) {
835
+ this.#currentPlayer?.cancel();
836
+ const isReduced = prefersReducedMotion(this.#documentRef);
837
+ const config = resolveMovementConfig(this.#defaults, {
838
+ duration: this.moveDuration(),
839
+ easing: this.moveEasing(),
840
+ delay: this.moveDelay(),
841
+ disabled: this.moveDisabled(),
842
+ }, isReduced);
843
+ if (config.disabled)
844
+ return;
845
+ let frames = resolveMoveFrames(this.moveWhileTap(), 'enter');
846
+ if (reverse) {
847
+ frames = reverseFrames(frames);
848
+ }
849
+ this.#currentPlayer = this.#engine.play(this.#host.nativeElement, frames, {
850
+ config,
851
+ spring: this.moveSpring(),
852
+ disabled: false,
853
+ });
854
+ }
855
+ ngOnDestroy() {
856
+ this.#currentPlayer?.cancel();
857
+ }
858
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveTapDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
859
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.2", type: MoveTapDirective, isStandalone: true, selector: "[moveWhileTap]", inputs: { moveWhileTap: { classPropertyName: "moveWhileTap", publicName: "moveWhileTap", isSignal: true, isRequired: true, transformFunction: null }, moveDuration: { classPropertyName: "moveDuration", publicName: "moveDuration", isSignal: true, isRequired: false, transformFunction: null }, moveEasing: { classPropertyName: "moveEasing", publicName: "moveEasing", isSignal: true, isRequired: false, transformFunction: null }, moveDelay: { classPropertyName: "moveDelay", publicName: "moveDelay", isSignal: true, isRequired: false, transformFunction: null }, moveDisabled: { classPropertyName: "moveDisabled", publicName: "moveDisabled", isSignal: true, isRequired: false, transformFunction: null }, moveSpring: { classPropertyName: "moveSpring", publicName: "moveSpring", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "pointerdown": "onPointerDown()", "pointerup": "onPointerUp()", "pointercancel": "onPointerUp()", "pointerleave": "onPointerUp()" } }, ngImport: i0 });
860
+ }
861
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveTapDirective, decorators: [{
862
+ type: Directive,
863
+ args: [{
864
+ selector: '[moveWhileTap]',
865
+ host: {
866
+ '(pointerdown)': 'onPointerDown()',
867
+ '(pointerup)': 'onPointerUp()',
868
+ '(pointercancel)': 'onPointerUp()',
869
+ '(pointerleave)': 'onPointerUp()',
870
+ },
871
+ }]
872
+ }], propDecorators: { moveWhileTap: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveWhileTap", required: true }] }], moveDuration: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDuration", required: false }] }], moveEasing: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveEasing", required: false }] }], moveDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDelay", required: false }] }], moveDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDisabled", required: false }] }], moveSpring: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveSpring", required: false }] }] } });
873
+
874
+ const MOVE_VARIANTS_PARENT = new InjectionToken('MOVE_VARIANTS_PARENT');
875
+ class MoveVariantsDirective {
876
+ moveVariants = input.required(...(ngDevMode ? [{ debugName: "moveVariants" }] : /* istanbul ignore next */ []));
877
+ moveAnimate = input(undefined, ...(ngDevMode ? [{ debugName: "moveAnimate" }] : /* istanbul ignore next */ []));
878
+ moveDuration = input(undefined, ...(ngDevMode ? [{ debugName: "moveDuration" }] : /* istanbul ignore next */ []));
879
+ moveEasing = input(undefined, ...(ngDevMode ? [{ debugName: "moveEasing" }] : /* istanbul ignore next */ []));
880
+ moveDelay = input(undefined, ...(ngDevMode ? [{ debugName: "moveDelay" }] : /* istanbul ignore next */ []));
881
+ moveDisabled = input(undefined, ...(ngDevMode ? [{ debugName: "moveDisabled" }] : /* istanbul ignore next */ []));
882
+ moveSpring = input(undefined, ...(ngDevMode ? [{ debugName: "moveSpring" }] : /* istanbul ignore next */ []));
883
+ #parent = inject(MOVE_VARIANTS_PARENT, { optional: true, skipSelf: true });
884
+ #engine = inject(AnimationEngine);
885
+ #defaults = inject(MOVEMENT_CONFIG);
886
+ #documentRef = inject(DOCUMENT);
887
+ #host = inject((ElementRef));
888
+ #stagger = inject(MOVE_STAGGER_PARENT, { optional: true });
889
+ #currentPlayer = null;
890
+ #isReducedMotion = false;
891
+ activeVariant = computed(() => {
892
+ return this.moveAnimate() ?? this.#parent?.activeVariant();
893
+ }, ...(ngDevMode ? [{ debugName: "activeVariant" }] : /* istanbul ignore next */ []));
894
+ constructor() {
895
+ this.#isReducedMotion = prefersReducedMotion(this.#documentRef);
896
+ this.#stagger?.register(this.#host.nativeElement);
897
+ effect(() => {
898
+ const variantName = this.activeVariant();
899
+ if (!variantName)
900
+ return;
901
+ const variants = this.moveVariants();
902
+ if (!variants)
903
+ return;
904
+ const state = variants[variantName];
905
+ if (!state)
906
+ return;
907
+ this.#currentPlayer?.cancel();
908
+ const { spring, duration, easing, delay, ...keyframesMap } = state;
909
+ const keyframes = keyframesMap;
910
+ const staggerDelay = this.#stagger?.getDelay(this.#host.nativeElement) ?? 0;
911
+ const config = resolveMovementConfig(this.#defaults, {
912
+ duration: duration ?? this.moveDuration(),
913
+ easing: easing ?? this.moveEasing(),
914
+ delay: (delay ?? this.moveDelay() ?? 0) + staggerDelay,
915
+ disabled: this.moveDisabled(),
916
+ }, this.#isReducedMotion);
917
+ this.#currentPlayer = this.#engine.play(this.#host.nativeElement, keyframes, {
918
+ config,
919
+ spring: spring ?? this.moveSpring(),
920
+ disabled: config.disabled,
921
+ });
922
+ });
923
+ }
924
+ ngOnDestroy() {
925
+ this.#stagger?.unregister(this.#host.nativeElement);
926
+ this.#currentPlayer?.cancel();
927
+ }
928
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveVariantsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
929
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.2", type: MoveVariantsDirective, isStandalone: true, selector: "[moveVariants]", inputs: { moveVariants: { classPropertyName: "moveVariants", publicName: "moveVariants", isSignal: true, isRequired: true, transformFunction: null }, moveAnimate: { classPropertyName: "moveAnimate", publicName: "moveAnimate", isSignal: true, isRequired: false, transformFunction: null }, moveDuration: { classPropertyName: "moveDuration", publicName: "moveDuration", isSignal: true, isRequired: false, transformFunction: null }, moveEasing: { classPropertyName: "moveEasing", publicName: "moveEasing", isSignal: true, isRequired: false, transformFunction: null }, moveDelay: { classPropertyName: "moveDelay", publicName: "moveDelay", isSignal: true, isRequired: false, transformFunction: null }, moveDisabled: { classPropertyName: "moveDisabled", publicName: "moveDisabled", isSignal: true, isRequired: false, transformFunction: null }, moveSpring: { classPropertyName: "moveSpring", publicName: "moveSpring", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
930
+ {
931
+ provide: MOVE_VARIANTS_PARENT,
932
+ useExisting: forwardRef(() => MoveVariantsDirective),
933
+ },
934
+ ], ngImport: i0 });
935
+ }
936
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveVariantsDirective, decorators: [{
937
+ type: Directive,
938
+ args: [{
939
+ selector: '[moveVariants]',
940
+ providers: [
941
+ {
942
+ provide: MOVE_VARIANTS_PARENT,
943
+ useExisting: forwardRef(() => MoveVariantsDirective),
944
+ },
945
+ ],
946
+ }]
947
+ }], ctorParameters: () => [], propDecorators: { moveVariants: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveVariants", required: true }] }], moveAnimate: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveAnimate", required: false }] }], moveDuration: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDuration", required: false }] }], moveEasing: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveEasing", required: false }] }], moveDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDelay", required: false }] }], moveDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDisabled", required: false }] }], moveSpring: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveSpring", required: false }] }] } });
948
+
949
+ class MoveStaggerDirective {
950
+ moveStagger = input.required(...(ngDevMode ? [{ debugName: "moveStagger" }] : /* istanbul ignore next */ []));
951
+ moveStaggerDirection = input('first', ...(ngDevMode ? [{ debugName: "moveStaggerDirection" }] : /* istanbul ignore next */ []));
952
+ #children = new Set();
953
+ register(el) {
954
+ this.#children.add(el);
955
+ }
956
+ unregister(el) {
957
+ this.#children.delete(el);
958
+ }
959
+ getDelay(el) {
960
+ if (!this.#children.has(el))
961
+ return 0;
962
+ const list = Array.from(this.#children).sort((a, b) => {
963
+ // Unreliable on detached elements, but they are in DOM when sorting
964
+ const pos = a.compareDocumentPosition(b);
965
+ return pos & Node.DOCUMENT_POSITION_PRECEDING ? 1 : -1;
966
+ });
967
+ const index = list.indexOf(el);
968
+ if (index === -1)
969
+ return 0;
970
+ const staggerConfig = this.moveStagger();
971
+ // If it's a spring config but used for stagger, default to 100ms or try to derive.
972
+ const staggerTime = typeof staggerConfig === 'number' ? staggerConfig : 100;
973
+ const direction = this.moveStaggerDirection();
974
+ const total = list.length;
975
+ let staggerIndex = index;
976
+ if (direction === 'last') {
977
+ staggerIndex = total - 1 - index;
978
+ }
979
+ else if (direction === 'center') {
980
+ const center = (total - 1) / 2;
981
+ staggerIndex = Math.abs(index - center);
982
+ }
983
+ return staggerIndex * staggerTime;
984
+ }
985
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveStaggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
986
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.2", type: MoveStaggerDirective, isStandalone: true, selector: "[moveStagger]", inputs: { moveStagger: { classPropertyName: "moveStagger", publicName: "moveStagger", isSignal: true, isRequired: true, transformFunction: null }, moveStaggerDirection: { classPropertyName: "moveStaggerDirection", publicName: "moveStaggerDirection", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
987
+ {
988
+ provide: MOVE_STAGGER_PARENT,
989
+ useExisting: forwardRef(() => MoveStaggerDirective),
990
+ },
991
+ ], ngImport: i0 });
992
+ }
993
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveStaggerDirective, decorators: [{
994
+ type: Directive,
995
+ args: [{
996
+ selector: '[moveStagger]',
997
+ providers: [
998
+ {
999
+ provide: MOVE_STAGGER_PARENT,
1000
+ useExisting: forwardRef(() => MoveStaggerDirective),
1001
+ },
1002
+ ],
1003
+ }]
1004
+ }], propDecorators: { moveStagger: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveStagger", required: true }] }], moveStaggerDirection: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveStaggerDirection", required: false }] }] } });
1005
+
1006
+ class MoveLayoutDirective {
1007
+ moveLayout = input(true, ...(ngDevMode ? [{ debugName: "moveLayout" }] : /* istanbul ignore next */ []));
1008
+ moveLayoutId = input(undefined, ...(ngDevMode ? [{ debugName: "moveLayoutId" }] : /* istanbul ignore next */ []));
1009
+ moveDuration = input(undefined, ...(ngDevMode ? [{ debugName: "moveDuration" }] : /* istanbul ignore next */ []));
1010
+ moveEasing = input(undefined, ...(ngDevMode ? [{ debugName: "moveEasing" }] : /* istanbul ignore next */ []));
1011
+ moveDelay = input(undefined, ...(ngDevMode ? [{ debugName: "moveDelay" }] : /* istanbul ignore next */ []));
1012
+ moveDisabled = input(undefined, ...(ngDevMode ? [{ debugName: "moveDisabled" }] : /* istanbul ignore next */ []));
1013
+ moveSpring = input(undefined, ...(ngDevMode ? [{ debugName: "moveSpring" }] : /* istanbul ignore next */ []));
1014
+ #defaults = inject(MOVEMENT_CONFIG);
1015
+ #documentRef = inject(DOCUMENT);
1016
+ #host = inject((ElementRef));
1017
+ #engine = inject(AnimationEngine);
1018
+ #snapshot = null;
1019
+ #currentPlayer = null;
1020
+ #isReducedMotion = false;
1021
+ #isAnimating = false;
1022
+ constructor() {
1023
+ this.#isReducedMotion = prefersReducedMotion(this.#documentRef);
1024
+ afterEveryRender({
1025
+ earlyRead: () => {
1026
+ if (this.moveLayout() === false ||
1027
+ this.moveDisabled() ||
1028
+ this.#isReducedMotion ||
1029
+ this.#isAnimating) {
1030
+ return null;
1031
+ }
1032
+ const currentRect = this.#host.nativeElement.getBoundingClientRect();
1033
+ if (this.#snapshot) {
1034
+ const dx = this.#snapshot.left - currentRect.left;
1035
+ const dy = this.#snapshot.top - currentRect.top;
1036
+ const dw = this.#snapshot.width / currentRect.width;
1037
+ const dh = this.#snapshot.height / currentRect.height;
1038
+ if (Math.abs(dx) > 0.5 ||
1039
+ Math.abs(dy) > 0.5 ||
1040
+ Math.abs(dw - 1) > 0.01 ||
1041
+ Math.abs(dh - 1) > 0.01) {
1042
+ return {
1043
+ dx,
1044
+ dy,
1045
+ dw,
1046
+ dh,
1047
+ targetRect: currentRect,
1048
+ };
1049
+ }
1050
+ }
1051
+ this.#snapshot = currentRect;
1052
+ return null;
1053
+ },
1054
+ write: (flipData) => {
1055
+ if (flipData) {
1056
+ this.playFlip(flipData);
1057
+ }
1058
+ },
1059
+ });
1060
+ }
1061
+ playFlip(flipData) {
1062
+ this.#isAnimating = true;
1063
+ // The host is currently visually at its NEW position (unpainted).
1064
+ // We apply the inverse transform to make it LOOK like it's at the OLD position.
1065
+ // We apply transform origin 0 0 so scaling works correctly from top-left.
1066
+ const transformOrigin = this.#host.nativeElement.style.transformOrigin;
1067
+ this.#host.nativeElement.style.transformOrigin = '0 0';
1068
+ const config = resolveMovementConfig(this.#defaults, {
1069
+ duration: this.moveDuration(),
1070
+ easing: this.moveEasing(),
1071
+ delay: this.moveDelay(),
1072
+ disabled: this.moveDisabled(),
1073
+ }, this.#isReducedMotion);
1074
+ this.#currentPlayer = this.#engine.play(this.#host.nativeElement, {
1075
+ x: [flipData.dx, 0],
1076
+ y: [flipData.dy, 0],
1077
+ scaleX: [flipData.dw, 1],
1078
+ scaleY: [flipData.dh, 1],
1079
+ }, {
1080
+ config,
1081
+ spring: this.moveSpring(),
1082
+ disabled: false,
1083
+ onDone: () => {
1084
+ this.#isAnimating = false;
1085
+ this.#host.nativeElement.style.transformOrigin = transformOrigin;
1086
+ // Take final snapshot so next check is fresh
1087
+ this.#snapshot = this.#host.nativeElement.getBoundingClientRect();
1088
+ },
1089
+ });
1090
+ }
1091
+ ngOnDestroy() {
1092
+ this.#currentPlayer?.cancel();
1093
+ }
1094
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveLayoutDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1095
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.2", type: MoveLayoutDirective, isStandalone: true, selector: "[moveLayout]", inputs: { moveLayout: { classPropertyName: "moveLayout", publicName: "moveLayout", isSignal: true, isRequired: false, transformFunction: null }, moveLayoutId: { classPropertyName: "moveLayoutId", publicName: "moveLayoutId", isSignal: true, isRequired: false, transformFunction: null }, moveDuration: { classPropertyName: "moveDuration", publicName: "moveDuration", isSignal: true, isRequired: false, transformFunction: null }, moveEasing: { classPropertyName: "moveEasing", publicName: "moveEasing", isSignal: true, isRequired: false, transformFunction: null }, moveDelay: { classPropertyName: "moveDelay", publicName: "moveDelay", isSignal: true, isRequired: false, transformFunction: null }, moveDisabled: { classPropertyName: "moveDisabled", publicName: "moveDisabled", isSignal: true, isRequired: false, transformFunction: null }, moveSpring: { classPropertyName: "moveSpring", publicName: "moveSpring", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
1096
+ }
1097
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveLayoutDirective, decorators: [{
1098
+ type: Directive,
1099
+ args: [{
1100
+ selector: '[moveLayout]',
1101
+ }]
1102
+ }], ctorParameters: () => [], propDecorators: { moveLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveLayout", required: false }] }], moveLayoutId: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveLayoutId", required: false }] }], moveDuration: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDuration", required: false }] }], moveEasing: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveEasing", required: false }] }], moveDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDelay", required: false }] }], moveDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDisabled", required: false }] }], moveSpring: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveSpring", required: false }] }] } });
1103
+
1104
+ /**
1105
+ * SmoothScrollService — Lenis-inspired smooth scroll for Angular.
1106
+ *
1107
+ * Desktop: intercepts wheel events → lerp interpolation → synthetic scrollTop.
1108
+ * Mobile: listens to native touch scroll → lerp interpolation → synthetic scrollTop.
1109
+ * Native scroll is prevented during touch to avoid the vibration conflict.
1110
+ *
1111
+ * The `scrollY` signal is updated on every RAF tick and can be consumed by
1112
+ * `MoveScrollDirective` (or any other consumer) to react to smooth scroll position
1113
+ * without relying on the native `scroll` event (which is NOT fired during smooth scroll).
1114
+ *
1115
+ * Usage (in app root or a layout service):
1116
+ *
1117
+ * constructor() {
1118
+ * inject(SmoothScrollService).init();
1119
+ * }
1120
+ */
1121
+ class SmoothScrollService {
1122
+ #document = inject(DOCUMENT);
1123
+ #platformId = inject(PLATFORM_ID);
1124
+ #zone = inject(NgZone);
1125
+ /** Lerp factor — lower = smoother/slower, higher = snappier. Range 0–1. */
1126
+ #lerp = 0.1;
1127
+ #targetY = 0;
1128
+ #currentY = 0;
1129
+ #rafId = 0;
1130
+ #isRunning = false;
1131
+ #scrollElement = null;
1132
+ /**
1133
+ * Reactive scroll position (in pixels) updated on every RAF tick.
1134
+ * Use this signal instead of listening to `window.scroll` when smooth scroll is active,
1135
+ * since native scroll events are NOT fired during lerp-based scrolling.
1136
+ */
1137
+ scrollY = signal(0, ...(ngDevMode ? [{ debugName: "scrollY" }] : /* istanbul ignore next */ []));
1138
+ // Touch tracking
1139
+ #touchStartY = 0;
1140
+ #lastTouchY = 0;
1141
+ #touchVelocity = 0;
1142
+ #isTouching = false;
1143
+ #lastTouchTimestamp = 0;
1144
+ // ─── Event handlers ────────────────────────────────────────────────────────
1145
+ #onWheel = (e) => {
1146
+ if (this.#isInsideScrollable(e.target))
1147
+ return;
1148
+ e.preventDefault();
1149
+ this.#targetY = this.#clamp(this.#targetY + e.deltaY);
1150
+ };
1151
+ #onTouchStart = (e) => {
1152
+ if (this.#isInsideScrollable(e.target))
1153
+ return;
1154
+ this.#isTouching = true;
1155
+ this.#touchVelocity = 0;
1156
+ this.#touchStartY = e.touches[0].clientY;
1157
+ this.#lastTouchY = this.#touchStartY;
1158
+ this.#lastTouchTimestamp = e.timeStamp;
1159
+ };
1160
+ #onTouchMove = (e) => {
1161
+ if (!this.#isTouching)
1162
+ return;
1163
+ if (this.#isInsideScrollable(e.target))
1164
+ return;
1165
+ // Prevent native scroll on the root element — we handle it ourselves
1166
+ e.preventDefault();
1167
+ const currentY = e.touches[0].clientY;
1168
+ const deltaTime = Math.max(e.timeStamp - this.#lastTouchTimestamp, 1);
1169
+ const deltaY = this.#lastTouchY - currentY; // inverted: swipe up → scroll down
1170
+ // Track velocity (px/ms) for momentum after lift
1171
+ this.#touchVelocity = deltaY / deltaTime;
1172
+ this.#targetY = this.#clamp(this.#targetY + deltaY);
1173
+ this.#lastTouchY = currentY;
1174
+ this.#lastTouchTimestamp = e.timeStamp;
1175
+ };
1176
+ #onTouchEnd = () => {
1177
+ this.#isTouching = false;
1178
+ // Apply momentum: fling the targetY by velocity × decay frames
1179
+ this.#applyMomentum();
1180
+ };
1181
+ // ─── Public API ────────────────────────────────────────────────────────────
1182
+ /** Whether the service is currently active (i.e. `init()` has been called). */
1183
+ get isActive() {
1184
+ return this.#isRunning;
1185
+ }
1186
+ init(options = {}) {
1187
+ if (!isPlatformBrowser(this.#platformId))
1188
+ return;
1189
+ if (this.#isRunning)
1190
+ return;
1191
+ this.#lerp = options.lerp ?? 0.1;
1192
+ this.#scrollElement = options.element ?? this.#document.documentElement;
1193
+ this.#currentY = this.#scrollElement.scrollTop;
1194
+ this.#targetY = this.#currentY;
1195
+ // Run entirely outside Angular zone to avoid change detection on every RAF
1196
+ this.#zone.runOutsideAngular(() => {
1197
+ const el = this.#scrollElement;
1198
+ el.addEventListener('wheel', this.#onWheel, { passive: false });
1199
+ // Touch events must also be non-passive to call preventDefault()
1200
+ el.addEventListener('touchstart', this.#onTouchStart, { passive: true });
1201
+ el.addEventListener('touchmove', this.#onTouchMove, { passive: false });
1202
+ el.addEventListener('touchend', this.#onTouchEnd, { passive: true });
1203
+ this.#isRunning = true;
1204
+ this.#tick();
1205
+ });
1206
+ }
1207
+ destroy() {
1208
+ if (!isPlatformBrowser(this.#platformId))
1209
+ return;
1210
+ this.#isRunning = false;
1211
+ cancelAnimationFrame(this.#rafId);
1212
+ const el = this.#scrollElement;
1213
+ if (el) {
1214
+ el.removeEventListener('wheel', this.#onWheel);
1215
+ el.removeEventListener('touchstart', this.#onTouchStart);
1216
+ el.removeEventListener('touchmove', this.#onTouchMove);
1217
+ el.removeEventListener('touchend', this.#onTouchEnd);
1218
+ }
1219
+ this.#scrollElement = null;
1220
+ }
1221
+ /** Scroll programmatically to a Y position */
1222
+ scrollTo(y, instant = false) {
1223
+ this.#targetY = this.#clamp(y);
1224
+ if (instant) {
1225
+ this.#currentY = this.#targetY;
1226
+ this.#applyScroll(this.#currentY);
1227
+ }
1228
+ }
1229
+ ngOnDestroy() {
1230
+ this.destroy();
1231
+ }
1232
+ // ─── Internal ──────────────────────────────────────────────────────────────
1233
+ #tick() {
1234
+ if (!this.#isRunning)
1235
+ return;
1236
+ this.#currentY += (this.#targetY - this.#currentY) * this.#lerp;
1237
+ if (Math.abs(this.#targetY - this.#currentY) < 0.1) {
1238
+ this.#currentY = this.#targetY;
1239
+ }
1240
+ this.#applyScroll(this.#currentY);
1241
+ this.#rafId = requestAnimationFrame(() => this.#tick());
1242
+ }
1243
+ /**
1244
+ * After finger lift, simulate Lenis-style momentum decay.
1245
+ * Velocity decays by a friction factor each frame until negligible.
1246
+ */
1247
+ #applyMomentum() {
1248
+ const FRICTION = 0.92; // higher = more glide; lower = stops faster
1249
+ const MIN_VELOCITY = 0.05; // px/ms threshold to stop
1250
+ let v = this.#touchVelocity * 16; // convert px/ms → px/frame (~16ms)
1251
+ const step = () => {
1252
+ if (!this.#isRunning || Math.abs(v) < MIN_VELOCITY)
1253
+ return;
1254
+ v *= FRICTION;
1255
+ this.#targetY = this.#clamp(this.#targetY + v);
1256
+ requestAnimationFrame(step);
1257
+ };
1258
+ requestAnimationFrame(step);
1259
+ }
1260
+ #applyScroll(y) {
1261
+ if (!this.#scrollElement)
1262
+ return;
1263
+ this.#scrollElement.scrollTop = y;
1264
+ // Update the reactive signal so consumers (e.g. MoveScrollDirective) can react
1265
+ // without relying on native scroll events which don't fire during lerp-based scroll.
1266
+ this.scrollY.set(y);
1267
+ }
1268
+ #getMaxScroll() {
1269
+ if (!this.#scrollElement)
1270
+ return 0;
1271
+ return this.#scrollElement.scrollHeight - this.#scrollElement.clientHeight;
1272
+ }
1273
+ #clamp(y) {
1274
+ return Math.max(0, Math.min(y, this.#getMaxScroll()));
1275
+ }
1276
+ /**
1277
+ * Checks if the element is inside a scrollable container OTHER than the root scroll element.
1278
+ * Used to let native scroll handle inner overflow-y:auto containers (like Lenis does).
1279
+ */
1280
+ #isInsideScrollable(el) {
1281
+ let current = el.parentElement;
1282
+ while (current && current !== this.#scrollElement) {
1283
+ const { overflowY } = getComputedStyle(current);
1284
+ if ((overflowY === 'auto' || overflowY === 'scroll') &&
1285
+ current.scrollHeight > current.clientHeight) {
1286
+ return true;
1287
+ }
1288
+ current = current.parentElement;
1289
+ }
1290
+ return false;
1291
+ }
1292
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: SmoothScrollService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1293
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: SmoothScrollService, providedIn: 'root' });
1294
+ }
1295
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: SmoothScrollService, decorators: [{
1296
+ type: Injectable,
1297
+ args: [{ providedIn: 'root' }]
1298
+ }] });
1299
+
1300
+ class MoveScrollDirective {
1301
+ moveScroll = input(undefined, ...(ngDevMode ? [{ debugName: "moveScroll" }] : /* istanbul ignore next */ []));
1302
+ moveScrollOffset = input(['0 1', '1 0'], ...(ngDevMode ? [{ debugName: "moveScrollOffset" }] : /* istanbul ignore next */ []));
1303
+ /** Optional CSS selector for a custom scrollable container. Defaults to window scroll. */
1304
+ moveScrollContainer = input(null, ...(ngDevMode ? [{ debugName: "moveScrollContainer" }] : /* istanbul ignore next */ []));
1305
+ progress = signal(0, ...(ngDevMode ? [{ debugName: "progress" }] : /* istanbul ignore next */ []));
1306
+ #documentRef = inject(DOCUMENT);
1307
+ #platformId = inject(PLATFORM_ID);
1308
+ #host = inject((ElementRef));
1309
+ #engine = inject(AnimationEngine);
1310
+ #smoothScroll = inject(SmoothScrollService, { optional: true });
1311
+ #player = null;
1312
+ #observer = null;
1313
+ #isVisible = false;
1314
+ #scrollListener = () => this.#updateProgress();
1315
+ #scrollTarget = null;
1316
+ #targetProgress = 0;
1317
+ #animProgress = 0;
1318
+ #rafId = null;
1319
+ constructor() {
1320
+ // Recreate the player whenever keyframes change (e.g. user changes effect type).
1321
+ // Uses duration=1000 + linear easing so that currentTime ∈ [0,1000] maps cleanly
1322
+ // to the [0,1] scroll progress without easing distortion or premature finish.
1323
+ effect(() => {
1324
+ if (!isPlatformBrowser(this.#platformId))
1325
+ return;
1326
+ const keyframes = this.moveScroll();
1327
+ if (this.#rafId !== null) {
1328
+ this.#documentRef.defaultView?.cancelAnimationFrame(this.#rafId);
1329
+ this.#rafId = null;
1330
+ }
1331
+ this.#player?.cancel();
1332
+ this.#player = null;
1333
+ this.#animProgress = 0;
1334
+ this.#targetProgress = 0;
1335
+ if (!keyframes)
1336
+ return;
1337
+ const view = this.#documentRef.defaultView;
1338
+ if (!view)
1339
+ return;
1340
+ this.#player = this.#engine.play(this.#host.nativeElement, keyframes, {
1341
+ config: { duration: 1000, easing: 'linear', delay: 0, disabled: false },
1342
+ });
1343
+ this.#player?.pause();
1344
+ // Sync the new player to the current scroll position immediately.
1345
+ this.#updateProgress();
1346
+ });
1347
+ // Smooth-scroll service support (fires via RAF signal instead of native scroll event).
1348
+ effect(() => {
1349
+ this.#smoothScroll?.scrollY();
1350
+ if (this.#smoothScroll?.isActive && this.#isVisible) {
1351
+ this.#updateProgress();
1352
+ }
1353
+ });
1354
+ }
1355
+ ngOnInit() {
1356
+ if (!isPlatformBrowser(this.#platformId))
1357
+ return;
1358
+ const view = this.#documentRef.defaultView;
1359
+ if (!view)
1360
+ return;
1361
+ // Resolve scroll target: custom container selector or window.
1362
+ const containerSelector = this.moveScrollContainer();
1363
+ const containerEl = containerSelector
1364
+ ? this.#documentRef.querySelector(containerSelector)
1365
+ : null;
1366
+ this.#scrollTarget = containerEl ?? view;
1367
+ // Use the custom container as IntersectionObserver root when available so that
1368
+ // the observer fires based on container-viewport visibility, not page-viewport visibility.
1369
+ this.#observer = new IntersectionObserver((entries) => {
1370
+ const entry = entries[0];
1371
+ this.#isVisible = entry.isIntersecting;
1372
+ if (entry.isIntersecting) {
1373
+ if (!this.#smoothScroll?.isActive) {
1374
+ this.#scrollTarget.addEventListener('scroll', this.#scrollListener, { passive: true });
1375
+ }
1376
+ this.#updateProgress();
1377
+ }
1378
+ else {
1379
+ this.#scrollTarget?.removeEventListener('scroll', this.#scrollListener);
1380
+ }
1381
+ }, {
1382
+ root: containerEl ?? null,
1383
+ threshold: [0, 0.1, 0.25, 0.5, 0.75, 0.9, 1],
1384
+ });
1385
+ this.#observer.observe(this.#host.nativeElement);
1386
+ }
1387
+ #updateProgress() {
1388
+ const view = this.#documentRef.defaultView;
1389
+ if (!view)
1390
+ return;
1391
+ const el = this.#host.nativeElement;
1392
+ const containerEl = this.#scrollTarget instanceof HTMLElement ? this.#scrollTarget : null;
1393
+ let p;
1394
+ if (containerEl) {
1395
+ // Container-relative scroll progress.
1396
+ const containerRect = containerEl.getBoundingClientRect();
1397
+ const elRect = el.getBoundingClientRect();
1398
+ // Element's fixed position within the scroll content (invariant to scrollTop changes).
1399
+ const elTop = elRect.top - containerRect.top + containerEl.scrollTop;
1400
+ const elHeight = el.offsetHeight;
1401
+ const containerHeight = containerEl.clientHeight;
1402
+ const offsets = this.moveScrollOffset();
1403
+ const startScroll = this.#calcContainerOffset(elTop, elHeight, containerHeight, offsets[0]);
1404
+ const endScroll = this.#calcContainerOffset(elTop, elHeight, containerHeight, offsets[1]);
1405
+ const range = endScroll - startScroll;
1406
+ if (range === 0)
1407
+ return;
1408
+ p = (containerEl.scrollTop - startScroll) / range;
1409
+ }
1410
+ else {
1411
+ // Viewport-relative scroll progress (window scroll).
1412
+ const rect = el.getBoundingClientRect();
1413
+ const windowHeight = view.innerHeight;
1414
+ const offsets = this.moveScrollOffset();
1415
+ const startY = this.#calcViewportOffset(rect, windowHeight, offsets[0]);
1416
+ const endY = this.#calcViewportOffset(rect, windowHeight, offsets[1]);
1417
+ const totalDistance = startY - endY;
1418
+ if (totalDistance === 0)
1419
+ return;
1420
+ p = startY / totalDistance;
1421
+ }
1422
+ // Cap at 0.999 to prevent the WAAPI player's `finish` event from firing,
1423
+ // which would cancel the animation and make it uncontrollable for further scroll.
1424
+ p = Math.max(0, Math.min(0.999, p));
1425
+ this.#targetProgress = p;
1426
+ this.#startRaf();
1427
+ }
1428
+ #startRaf() {
1429
+ if (this.#rafId !== null)
1430
+ return;
1431
+ const view = this.#documentRef.defaultView;
1432
+ if (!view)
1433
+ return;
1434
+ const tick = () => {
1435
+ const diff = this.#targetProgress - this.#animProgress;
1436
+ // Stop the loop when close enough (< 0.001 difference).
1437
+ if (Math.abs(diff) < 0.001) {
1438
+ this.#animProgress = this.#targetProgress;
1439
+ this.#rafId = null;
1440
+ this.#applyProgress(this.#animProgress);
1441
+ return;
1442
+ }
1443
+ this.#animProgress += diff * 0.12;
1444
+ this.#applyProgress(this.#animProgress);
1445
+ this.#rafId = view.requestAnimationFrame(tick);
1446
+ };
1447
+ this.#rafId = view.requestAnimationFrame(tick);
1448
+ }
1449
+ #applyProgress(p) {
1450
+ this.progress.set(p);
1451
+ if (this.#player) {
1452
+ this.#player.currentTime = p * 1000;
1453
+ }
1454
+ }
1455
+ /**
1456
+ * Scroll position at which the animation start/end offset is reached.
1457
+ * "elFraction viewFraction" → e.g. "0 1" = element-top meets container-bottom.
1458
+ */
1459
+ #calcContainerOffset(elTop, elHeight, containerHeight, offsetStr) {
1460
+ const [elVal, viewVal] = offsetStr.split(' ').map(parseFloat);
1461
+ return elTop + elHeight * elVal - containerHeight * viewVal;
1462
+ }
1463
+ #calcViewportOffset(rect, windowHeight, offsetStr) {
1464
+ const [elVal, viewVal] = offsetStr.split(' ').map(parseFloat);
1465
+ return rect.top + rect.height * elVal - windowHeight * viewVal;
1466
+ }
1467
+ ngOnDestroy() {
1468
+ if (this.#rafId !== null) {
1469
+ this.#documentRef.defaultView?.cancelAnimationFrame(this.#rafId);
1470
+ this.#rafId = null;
1471
+ }
1472
+ this.#player?.cancel();
1473
+ this.#scrollTarget?.removeEventListener('scroll', this.#scrollListener);
1474
+ this.#observer?.disconnect();
1475
+ }
1476
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveScrollDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1477
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.2", type: MoveScrollDirective, isStandalone: true, selector: "[moveScroll]", inputs: { moveScroll: { classPropertyName: "moveScroll", publicName: "moveScroll", isSignal: true, isRequired: false, transformFunction: null }, moveScrollOffset: { classPropertyName: "moveScrollOffset", publicName: "moveScrollOffset", isSignal: true, isRequired: false, transformFunction: null }, moveScrollContainer: { classPropertyName: "moveScrollContainer", publicName: "moveScrollContainer", isSignal: true, isRequired: false, transformFunction: null } }, exportAs: ["moveScroll"], ngImport: i0 });
1478
+ }
1479
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveScrollDirective, decorators: [{
1480
+ type: Directive,
1481
+ args: [{
1482
+ selector: '[moveScroll]',
1483
+ exportAs: 'moveScroll',
1484
+ }]
1485
+ }], ctorParameters: () => [], propDecorators: { moveScroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveScroll", required: false }] }], moveScrollOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveScrollOffset", required: false }] }], moveScrollContainer: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveScrollContainer", required: false }] }] } });
1486
+
1487
+ class MovePresenceDirective {
1488
+ movePresence = input(...(ngDevMode ? [undefined, { debugName: "movePresence" }] : /* istanbul ignore next */ []));
1489
+ #viewContainer = inject(ViewContainerRef);
1490
+ #template = inject(TemplateRef);
1491
+ #view = null;
1492
+ #isRemoving = false;
1493
+ #children = new Set();
1494
+ constructor() {
1495
+ effect(() => {
1496
+ const show = !!this.movePresence();
1497
+ if (show) {
1498
+ if (!this.#view) {
1499
+ this.#view = this.#viewContainer.createEmbeddedView(this.#template);
1500
+ this.#isRemoving = false;
1501
+ }
1502
+ else if (this.#isRemoving) {
1503
+ // If we were removing, cancel the removal
1504
+ this.#isRemoving = false;
1505
+ }
1506
+ }
1507
+ else if (!show && this.#view && !this.#isRemoving) {
1508
+ this.#isRemoving = true;
1509
+ this.removeView();
1510
+ }
1511
+ });
1512
+ }
1513
+ register(child) {
1514
+ this.#children.add(child);
1515
+ }
1516
+ unregister(child) {
1517
+ this.#children.delete(child);
1518
+ }
1519
+ async removeView() {
1520
+ const promises = [];
1521
+ // Trigger leave on all registered children
1522
+ for (const child of this.#children) {
1523
+ promises.push(child.playLeave());
1524
+ }
1525
+ try {
1526
+ if (promises.length > 0) {
1527
+ await Promise.all(promises);
1528
+ }
1529
+ }
1530
+ catch {
1531
+ // Ignore errors in animations
1532
+ }
1533
+ if (this.#isRemoving && this.#view) {
1534
+ this.#viewContainer.clear();
1535
+ this.#view = null;
1536
+ this.#isRemoving = false;
1537
+ this.#children.clear();
1538
+ }
1539
+ }
1540
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MovePresenceDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1541
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.2", type: MovePresenceDirective, isStandalone: true, selector: "[movePresence]", inputs: { movePresence: { classPropertyName: "movePresence", publicName: "movePresence", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
1542
+ {
1543
+ provide: MOVE_PRESENCE_PARENT,
1544
+ useExisting: forwardRef(() => MovePresenceDirective),
1545
+ },
1546
+ ], ngImport: i0 });
1547
+ }
1548
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MovePresenceDirective, decorators: [{
1549
+ type: Directive,
1550
+ args: [{
1551
+ selector: '[movePresence]',
1552
+ providers: [
1553
+ {
1554
+ provide: MOVE_PRESENCE_PARENT,
1555
+ useExisting: forwardRef(() => MovePresenceDirective),
1556
+ },
1557
+ ],
1558
+ }]
1559
+ }], ctorParameters: () => [], propDecorators: { movePresence: [{ type: i0.Input, args: [{ isSignal: true, alias: "movePresence", required: false }] }] } });
1560
+
1561
+ class MoveDragDirective {
1562
+ moveDrag = input(true, ...(ngDevMode ? [{ debugName: "moveDrag" }] : /* istanbul ignore next */ []));
1563
+ moveDragConstraints = input(undefined, ...(ngDevMode ? [{ debugName: "moveDragConstraints" }] : /* istanbul ignore next */ []));
1564
+ moveDragElastic = input(0.5, ...(ngDevMode ? [{ debugName: "moveDragElastic" }] : /* istanbul ignore next */ []));
1565
+ moveSpring = input(undefined, ...(ngDevMode ? [{ debugName: "moveSpring" }] : /* istanbul ignore next */ []));
1566
+ #documentRef = inject(DOCUMENT);
1567
+ #host = inject((ElementRef));
1568
+ #engine = inject(AnimationEngine);
1569
+ #isDragging = false;
1570
+ #pointerId = null;
1571
+ #startX = 0;
1572
+ #startY = 0;
1573
+ #_x = 0;
1574
+ #_y = 0;
1575
+ #dragBounds = null;
1576
+ #player = null;
1577
+ onPointerDown(e) {
1578
+ if (this.moveDrag() === false || e.button !== 0)
1579
+ return;
1580
+ this.#isDragging = true;
1581
+ this.#pointerId = e.pointerId;
1582
+ this.#host.nativeElement.setPointerCapture(e.pointerId);
1583
+ this.#player?.cancel();
1584
+ // read bounds cleanly before next render
1585
+ this.#dragBounds = this.resolveBounds();
1586
+ this.#startX = e.clientX - this.#_x;
1587
+ this.#startY = e.clientY - this.#_y;
1588
+ // Prevent text selection while dragging
1589
+ this.#host.nativeElement.style.touchAction = 'none';
1590
+ this.#host.nativeElement.style.userSelect = 'none';
1591
+ }
1592
+ onPointerMove(e) {
1593
+ if (!this.#isDragging || e.pointerId !== this.#pointerId)
1594
+ return;
1595
+ this.#_x = e.clientX - this.#startX;
1596
+ this.#_y = e.clientY - this.#startY;
1597
+ this.applyTransform();
1598
+ }
1599
+ onPointerUp(e) {
1600
+ if (!this.#isDragging || e.pointerId !== this.#pointerId)
1601
+ return;
1602
+ this.#isDragging = false;
1603
+ this.#host.nativeElement.releasePointerCapture(e.pointerId);
1604
+ this.#pointerId = null;
1605
+ this.#host.nativeElement.style.touchAction = '';
1606
+ this.#host.nativeElement.style.userSelect = '';
1607
+ this.snapBackIfNeeded();
1608
+ }
1609
+ resolveBounds() {
1610
+ const constraints = this.moveDragConstraints();
1611
+ if (!constraints)
1612
+ return null;
1613
+ if (constraints instanceof HTMLElement) {
1614
+ const oldTranslate = this.#host.nativeElement.style.translate;
1615
+ this.#host.nativeElement.style.translate = 'none';
1616
+ const elRect = this.#host.nativeElement.getBoundingClientRect();
1617
+ const containerRect = constraints.getBoundingClientRect();
1618
+ this.#host.nativeElement.style.translate = oldTranslate;
1619
+ return {
1620
+ left: containerRect.left - elRect.left,
1621
+ right: containerRect.right - elRect.right,
1622
+ top: containerRect.top - elRect.top,
1623
+ bottom: containerRect.bottom - elRect.bottom,
1624
+ };
1625
+ }
1626
+ return constraints;
1627
+ }
1628
+ applyTransform() {
1629
+ let x = this.#_x;
1630
+ let y = this.#_y;
1631
+ if (this.#dragBounds) {
1632
+ const elastic = this.moveDragElastic();
1633
+ if (this.#dragBounds.left !== undefined && x < this.#dragBounds.left) {
1634
+ x = this.#dragBounds.left - (this.#dragBounds.left - x) * elastic;
1635
+ }
1636
+ else if (this.#dragBounds.right !== undefined && x > this.#dragBounds.right) {
1637
+ x = this.#dragBounds.right + (x - this.#dragBounds.right) * elastic;
1638
+ }
1639
+ if (this.#dragBounds.top !== undefined && y < this.#dragBounds.top) {
1640
+ y = this.#dragBounds.top - (this.#dragBounds.top - y) * elastic;
1641
+ }
1642
+ else if (this.#dragBounds.bottom !== undefined && y > this.#dragBounds.bottom) {
1643
+ y = this.#dragBounds.bottom + (y - this.#dragBounds.bottom) * elastic;
1644
+ }
1645
+ }
1646
+ this.#host.nativeElement.style.translate = `${x}px ${y}px`;
1647
+ }
1648
+ snapBackIfNeeded() {
1649
+ if (!this.#dragBounds)
1650
+ return;
1651
+ // We base the snap on the current logical _x, _y, calculating the nearest valid position.
1652
+ let targetX = this.#_x;
1653
+ let targetY = this.#_y;
1654
+ if (this.#dragBounds.left !== undefined && targetX < this.#dragBounds.left)
1655
+ targetX = this.#dragBounds.left;
1656
+ if (this.#dragBounds.right !== undefined && targetX > this.#dragBounds.right)
1657
+ targetX = this.#dragBounds.right;
1658
+ if (this.#dragBounds.top !== undefined && targetY < this.#dragBounds.top)
1659
+ targetY = this.#dragBounds.top;
1660
+ if (this.#dragBounds.bottom !== undefined && targetY > this.#dragBounds.bottom)
1661
+ targetY = this.#dragBounds.bottom;
1662
+ if (targetX !== this.#_x || targetY !== this.#_y) {
1663
+ // Find the currently visible coordinates (which include elasticity)
1664
+ let currentVisX = this.#_x;
1665
+ let currentVisY = this.#_y;
1666
+ const elastic = this.moveDragElastic();
1667
+ if (this.#dragBounds.left !== undefined && this.#_x < this.#dragBounds.left) {
1668
+ currentVisX = this.#dragBounds.left - (this.#dragBounds.left - this.#_x) * elastic;
1669
+ }
1670
+ else if (this.#dragBounds.right !== undefined && this.#_x > this.#dragBounds.right) {
1671
+ currentVisX = this.#dragBounds.right + (this.#_x - this.#dragBounds.right) * elastic;
1672
+ }
1673
+ if (this.#dragBounds.top !== undefined && this.#_y < this.#dragBounds.top) {
1674
+ currentVisY = this.#dragBounds.top - (this.#dragBounds.top - this.#_y) * elastic;
1675
+ }
1676
+ else if (this.#dragBounds.bottom !== undefined && this.#_y > this.#dragBounds.bottom) {
1677
+ currentVisY = this.#dragBounds.bottom + (this.#_y - this.#dragBounds.bottom) * elastic;
1678
+ }
1679
+ this.#player = this.#engine.play(this.#host.nativeElement, {
1680
+ x: [currentVisX, targetX],
1681
+ y: [currentVisY, targetY],
1682
+ }, {
1683
+ config: { duration: 300, easing: 'ease', delay: 0, disabled: false },
1684
+ spring: this.moveSpring() ?? { stiffness: 500, damping: 30 },
1685
+ disabled: prefersReducedMotion(this.#documentRef),
1686
+ });
1687
+ this.#_x = targetX;
1688
+ this.#_y = targetY;
1689
+ }
1690
+ }
1691
+ ngOnDestroy() {
1692
+ this.#player?.cancel();
1693
+ }
1694
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveDragDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1695
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.2", type: MoveDragDirective, isStandalone: true, selector: "[moveDrag]", inputs: { moveDrag: { classPropertyName: "moveDrag", publicName: "moveDrag", isSignal: true, isRequired: false, transformFunction: null }, moveDragConstraints: { classPropertyName: "moveDragConstraints", publicName: "moveDragConstraints", isSignal: true, isRequired: false, transformFunction: null }, moveDragElastic: { classPropertyName: "moveDragElastic", publicName: "moveDragElastic", isSignal: true, isRequired: false, transformFunction: null }, moveSpring: { classPropertyName: "moveSpring", publicName: "moveSpring", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "pointerdown": "onPointerDown($event)", "pointermove": "onPointerMove($event)", "pointerup": "onPointerUp($event)", "pointercancel": "onPointerUp($event)" } }, ngImport: i0 });
1696
+ }
1697
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveDragDirective, decorators: [{
1698
+ type: Directive,
1699
+ args: [{
1700
+ selector: '[moveDrag]',
1701
+ host: {
1702
+ '(pointerdown)': 'onPointerDown($event)',
1703
+ '(pointermove)': 'onPointerMove($event)',
1704
+ '(pointerup)': 'onPointerUp($event)',
1705
+ '(pointercancel)': 'onPointerUp($event)',
1706
+ },
1707
+ }]
1708
+ }], propDecorators: { moveDrag: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDrag", required: false }] }], moveDragConstraints: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDragConstraints", required: false }] }], moveDragElastic: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDragElastic", required: false }] }], moveSpring: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveSpring", required: false }] }] } });
1709
+
1710
+ class MoveInViewDirective {
1711
+ moveInView = input('none', ...(ngDevMode ? [{ debugName: "moveInView" }] : /* istanbul ignore next */ []));
1712
+ moveInViewMargin = input('0px', ...(ngDevMode ? [{ debugName: "moveInViewMargin" }] : /* istanbul ignore next */ []));
1713
+ moveInViewOnce = input(true, ...(ngDevMode ? [{ debugName: "moveInViewOnce" }] : /* istanbul ignore next */ []));
1714
+ /** CSS selector for the IntersectionObserver root (for inner scrollable containers). */
1715
+ moveInViewRoot = input(null, ...(ngDevMode ? [{ debugName: "moveInViewRoot" }] : /* istanbul ignore next */ []));
1716
+ moveDuration = input(undefined, ...(ngDevMode ? [{ debugName: "moveDuration" }] : /* istanbul ignore next */ []));
1717
+ moveEasing = input(undefined, ...(ngDevMode ? [{ debugName: "moveEasing" }] : /* istanbul ignore next */ []));
1718
+ moveDelay = input(undefined, ...(ngDevMode ? [{ debugName: "moveDelay" }] : /* istanbul ignore next */ []));
1719
+ moveDisabled = input(undefined, ...(ngDevMode ? [{ debugName: "moveDisabled" }] : /* istanbul ignore next */ []));
1720
+ moveSpring = input(undefined, ...(ngDevMode ? [{ debugName: "moveSpring" }] : /* istanbul ignore next */ []));
1721
+ #defaults = inject(MOVEMENT_CONFIG);
1722
+ #documentRef = inject(DOCUMENT);
1723
+ #platformId = inject(PLATFORM_ID);
1724
+ #host = inject((ElementRef));
1725
+ #engine = inject(AnimationEngine);
1726
+ #player = null;
1727
+ #observer = null;
1728
+ #frames = null;
1729
+ #isAnimated = false;
1730
+ ngOnInit() {
1731
+ if (!isPlatformBrowser(this.#platformId) || this.moveDisabled())
1732
+ return;
1733
+ const isReduced = prefersReducedMotion(this.#documentRef);
1734
+ if (isReduced)
1735
+ return;
1736
+ this.#frames = resolveMoveFrames(this.moveInView(), 'enter');
1737
+ // Apply initial (invisible) state directly to DOM - avoid creating a player
1738
+ applyInitialStyles(this.#host.nativeElement, this.#frames);
1739
+ const rootSelector = this.moveInViewRoot();
1740
+ const rootEl = rootSelector ? this.#documentRef.querySelector(rootSelector) : null;
1741
+ this.#observer = new IntersectionObserver((entries) => {
1742
+ const entry = entries[0];
1743
+ if (entry.isIntersecting && !this.#isAnimated) {
1744
+ this.#playAnimation();
1745
+ if (this.moveInViewOnce()) {
1746
+ this.#isAnimated = true;
1747
+ this.#observer?.disconnect();
1748
+ }
1749
+ }
1750
+ else if (!entry.isIntersecting && !this.moveInViewOnce() && this.#isAnimated) {
1751
+ // Reset to initial state for re-animation
1752
+ this.#player?.cancel();
1753
+ this.#player = null;
1754
+ this.#isAnimated = false;
1755
+ if (this.#frames) {
1756
+ applyInitialStyles(this.#host.nativeElement, this.#frames);
1757
+ }
1758
+ }
1759
+ }, {
1760
+ root: rootEl,
1761
+ rootMargin: this.moveInViewMargin(),
1762
+ threshold: 0,
1763
+ });
1764
+ this.#observer.observe(this.#host.nativeElement);
1765
+ }
1766
+ #playAnimation() {
1767
+ if (!this.#frames)
1768
+ return;
1769
+ const config = resolveMovementConfig(this.#defaults, {
1770
+ duration: this.moveDuration(),
1771
+ easing: this.moveEasing(),
1772
+ delay: this.moveDelay() ?? 0,
1773
+ disabled: false,
1774
+ }, false);
1775
+ // Clear inline styles before animating so WAAPI can take over cleanly
1776
+ clearInitialStyles(this.#host.nativeElement);
1777
+ this.#player = this.#engine.play(this.#host.nativeElement, this.#frames, {
1778
+ config,
1779
+ spring: this.moveSpring(),
1780
+ disabled: false,
1781
+ });
1782
+ this.#isAnimated = true;
1783
+ }
1784
+ ngOnDestroy() {
1785
+ this.#observer?.disconnect();
1786
+ this.#player?.cancel();
1787
+ }
1788
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveInViewDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1789
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.2", type: MoveInViewDirective, isStandalone: true, selector: "[moveInView]", inputs: { moveInView: { classPropertyName: "moveInView", publicName: "moveInView", isSignal: true, isRequired: false, transformFunction: null }, moveInViewMargin: { classPropertyName: "moveInViewMargin", publicName: "moveInViewMargin", isSignal: true, isRequired: false, transformFunction: null }, moveInViewOnce: { classPropertyName: "moveInViewOnce", publicName: "moveInViewOnce", isSignal: true, isRequired: false, transformFunction: null }, moveInViewRoot: { classPropertyName: "moveInViewRoot", publicName: "moveInViewRoot", isSignal: true, isRequired: false, transformFunction: null }, moveDuration: { classPropertyName: "moveDuration", publicName: "moveDuration", isSignal: true, isRequired: false, transformFunction: null }, moveEasing: { classPropertyName: "moveEasing", publicName: "moveEasing", isSignal: true, isRequired: false, transformFunction: null }, moveDelay: { classPropertyName: "moveDelay", publicName: "moveDelay", isSignal: true, isRequired: false, transformFunction: null }, moveDisabled: { classPropertyName: "moveDisabled", publicName: "moveDisabled", isSignal: true, isRequired: false, transformFunction: null }, moveSpring: { classPropertyName: "moveSpring", publicName: "moveSpring", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
1790
+ }
1791
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveInViewDirective, decorators: [{
1792
+ type: Directive,
1793
+ args: [{
1794
+ selector: '[moveInView]',
1795
+ }]
1796
+ }], propDecorators: { moveInView: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveInView", required: false }] }], moveInViewMargin: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveInViewMargin", required: false }] }], moveInViewOnce: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveInViewOnce", required: false }] }], moveInViewRoot: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveInViewRoot", required: false }] }], moveDuration: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDuration", required: false }] }], moveEasing: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveEasing", required: false }] }], moveDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDelay", required: false }] }], moveDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDisabled", required: false }] }], moveSpring: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveSpring", required: false }] }] } });
1797
+
1798
+ class MoveTextDirective {
1799
+ moveText = input('fade-up', ...(ngDevMode ? [{ debugName: "moveText" }] : /* istanbul ignore next */ []));
1800
+ moveTextSplit = input('chars', ...(ngDevMode ? [{ debugName: "moveTextSplit" }] : /* istanbul ignore next */ []));
1801
+ moveTextStagger = input(30, ...(ngDevMode ? [{ debugName: "moveTextStagger" }] : /* istanbul ignore next */ []));
1802
+ moveDuration = input(undefined, ...(ngDevMode ? [{ debugName: "moveDuration" }] : /* istanbul ignore next */ []));
1803
+ moveEasing = input(undefined, ...(ngDevMode ? [{ debugName: "moveEasing" }] : /* istanbul ignore next */ []));
1804
+ moveDelay = input(undefined, ...(ngDevMode ? [{ debugName: "moveDelay" }] : /* istanbul ignore next */ []));
1805
+ moveDisabled = input(undefined, ...(ngDevMode ? [{ debugName: "moveDisabled" }] : /* istanbul ignore next */ []));
1806
+ moveSpring = input(undefined, ...(ngDevMode ? [{ debugName: "moveSpring" }] : /* istanbul ignore next */ []));
1807
+ #defaults = inject(MOVEMENT_CONFIG);
1808
+ #documentRef = inject(DOCUMENT);
1809
+ #platformId = inject(PLATFORM_ID);
1810
+ #host = inject((ElementRef));
1811
+ #engine = inject(AnimationEngine);
1812
+ #players = [];
1813
+ #spans = [];
1814
+ #observer = null;
1815
+ #frames = null;
1816
+ ngOnInit() {
1817
+ if (!isPlatformBrowser(this.#platformId) || this.moveDisabled())
1818
+ return;
1819
+ // Defer to after Angular has completed its first rendering pass and set
1820
+ // the interpolated text content on the host element (same pattern as moveEnter).
1821
+ Promise.resolve().then(() => {
1822
+ this.#splitText();
1823
+ const isReduced = prefersReducedMotion(this.#documentRef);
1824
+ if (isReduced)
1825
+ return;
1826
+ this.#frames = resolveMoveFrames(this.moveText(), 'enter');
1827
+ // Apply initial (invisible) state to each span directly — no player yet
1828
+ this.#spans.forEach((span) => {
1829
+ if (this.#frames) {
1830
+ applyInitialStyles(span, this.#frames);
1831
+ }
1832
+ });
1833
+ // Create player and animate only when visible
1834
+ this.#observer = new IntersectionObserver((entries) => {
1835
+ if (entries[0].isIntersecting) {
1836
+ this.#playAll();
1837
+ this.#observer?.disconnect();
1838
+ }
1839
+ });
1840
+ this.#observer.observe(this.#host.nativeElement);
1841
+ });
1842
+ }
1843
+ #playAll() {
1844
+ if (!this.#frames)
1845
+ return;
1846
+ const baseDelay = this.moveDelay() ?? 0;
1847
+ const stagger = this.moveTextStagger();
1848
+ this.#spans.forEach((span, index) => {
1849
+ if (!this.#frames)
1850
+ return;
1851
+ const config = resolveMovementConfig(this.#defaults, {
1852
+ duration: this.moveDuration(),
1853
+ easing: this.moveEasing(),
1854
+ delay: baseDelay + index * stagger,
1855
+ disabled: false,
1856
+ }, false);
1857
+ // Clear inline styles so WAAPI can animate from the keyframe starting point
1858
+ clearInitialStyles(span);
1859
+ const player = this.#engine.play(span, this.#frames, {
1860
+ config,
1861
+ spring: this.moveSpring(),
1862
+ disabled: false,
1863
+ });
1864
+ if (player) {
1865
+ this.#players.push(player);
1866
+ }
1867
+ });
1868
+ }
1869
+ #splitText() {
1870
+ const el = this.#host.nativeElement;
1871
+ const text = (el.textContent ?? '').trim();
1872
+ el.innerHTML = '';
1873
+ el.setAttribute('aria-label', text);
1874
+ const byChars = this.moveTextSplit() === 'chars';
1875
+ if (byChars) {
1876
+ // Split character by character, preserving spaces as text nodes
1877
+ [...text].forEach((char) => {
1878
+ if (char === ' ') {
1879
+ el.appendChild(this.#documentRef.createTextNode(' '));
1880
+ return;
1881
+ }
1882
+ const span = this.#documentRef.createElement('span');
1883
+ span.setAttribute('aria-hidden', 'true');
1884
+ span.style.display = 'inline-block';
1885
+ span.textContent = char;
1886
+ el.appendChild(span);
1887
+ this.#spans.push(span);
1888
+ });
1889
+ }
1890
+ else {
1891
+ // Split word by word
1892
+ const words = text.split(/\s+/);
1893
+ words.forEach((word, index) => {
1894
+ const span = this.#documentRef.createElement('span');
1895
+ span.setAttribute('aria-hidden', 'true');
1896
+ span.style.display = 'inline-block';
1897
+ span.style.whiteSpace = 'pre';
1898
+ span.textContent = index < words.length - 1 ? word + ' ' : word;
1899
+ el.appendChild(span);
1900
+ this.#spans.push(span);
1901
+ });
1902
+ }
1903
+ }
1904
+ ngOnDestroy() {
1905
+ this.#observer?.disconnect();
1906
+ this.#players.forEach((p) => p.cancel());
1907
+ }
1908
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveTextDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1909
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.2", type: MoveTextDirective, isStandalone: true, selector: "[moveText]", inputs: { moveText: { classPropertyName: "moveText", publicName: "moveText", isSignal: true, isRequired: false, transformFunction: null }, moveTextSplit: { classPropertyName: "moveTextSplit", publicName: "moveTextSplit", isSignal: true, isRequired: false, transformFunction: null }, moveTextStagger: { classPropertyName: "moveTextStagger", publicName: "moveTextStagger", isSignal: true, isRequired: false, transformFunction: null }, moveDuration: { classPropertyName: "moveDuration", publicName: "moveDuration", isSignal: true, isRequired: false, transformFunction: null }, moveEasing: { classPropertyName: "moveEasing", publicName: "moveEasing", isSignal: true, isRequired: false, transformFunction: null }, moveDelay: { classPropertyName: "moveDelay", publicName: "moveDelay", isSignal: true, isRequired: false, transformFunction: null }, moveDisabled: { classPropertyName: "moveDisabled", publicName: "moveDisabled", isSignal: true, isRequired: false, transformFunction: null }, moveSpring: { classPropertyName: "moveSpring", publicName: "moveSpring", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
1910
+ }
1911
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveTextDirective, decorators: [{
1912
+ type: Directive,
1913
+ args: [{
1914
+ selector: '[moveText]',
1915
+ }]
1916
+ }], propDecorators: { moveText: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveText", required: false }] }], moveTextSplit: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveTextSplit", required: false }] }], moveTextStagger: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveTextStagger", required: false }] }], moveDuration: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDuration", required: false }] }], moveEasing: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveEasing", required: false }] }], moveDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDelay", required: false }] }], moveDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDisabled", required: false }] }], moveSpring: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveSpring", required: false }] }] } });
1917
+
1918
+ /**
1919
+ * Directive that enables smooth scrolling on a specific scrollable container.
1920
+ *
1921
+ * Supports both mouse wheel (desktop) and touch scroll (mobile) with
1922
+ * momentum-based inertia after finger lift, similar to Lenis.
1923
+ *
1924
+ * ⚠️ SmoothScrollService is a root singleton. Do NOT use this directive
1925
+ * on multiple elements simultaneously — use it once on the page root or
1926
+ * inject the service directly where needed.
1927
+ *
1928
+ * Usage:
1929
+ * <div moveSmoothScroll [moveSmoothScrollLerp]="0.08" style="overflow-y: auto; height: 100vh">
1930
+ * ...content...
1931
+ * </div>
1932
+ *
1933
+ * Or use the service directly (e.g. on documentElement):
1934
+ * constructor() {
1935
+ * inject(SmoothScrollService).init({ lerp: 0.1 });
1936
+ * }
1937
+ */
1938
+ class MoveSmoothScrollDirective {
1939
+ /** Lerp factor passed to SmoothScrollService. Lower = smoother. Range: 0–1. */
1940
+ moveSmoothScrollLerp = input(0.1, ...(ngDevMode ? [{ debugName: "moveSmoothScrollLerp" }] : /* istanbul ignore next */ []));
1941
+ #host = inject((ElementRef));
1942
+ #scroll = inject(SmoothScrollService);
1943
+ #platformId = inject(PLATFORM_ID);
1944
+ ngOnInit() {
1945
+ if (!isPlatformBrowser(this.#platformId))
1946
+ return;
1947
+ this.#scroll.init({
1948
+ lerp: this.moveSmoothScrollLerp(),
1949
+ element: this.#host.nativeElement,
1950
+ });
1951
+ }
1952
+ ngOnDestroy() {
1953
+ this.#scroll.destroy();
1954
+ }
1955
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveSmoothScrollDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1956
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.2", type: MoveSmoothScrollDirective, isStandalone: true, selector: "[moveSmoothScroll]", inputs: { moveSmoothScrollLerp: { classPropertyName: "moveSmoothScrollLerp", publicName: "moveSmoothScrollLerp", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
1957
+ }
1958
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveSmoothScrollDirective, decorators: [{
1959
+ type: Directive,
1960
+ args: [{
1961
+ selector: '[moveSmoothScroll]',
1962
+ }]
1963
+ }], propDecorators: { moveSmoothScrollLerp: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveSmoothScrollLerp", required: false }] }] } });
1964
+
1965
+ class MoveFocusDirective {
1966
+ moveWhileFocus = input.required(...(ngDevMode ? [{ debugName: "moveWhileFocus" }] : /* istanbul ignore next */ []));
1967
+ moveDuration = input(undefined, ...(ngDevMode ? [{ debugName: "moveDuration" }] : /* istanbul ignore next */ []));
1968
+ moveEasing = input(undefined, ...(ngDevMode ? [{ debugName: "moveEasing" }] : /* istanbul ignore next */ []));
1969
+ moveDelay = input(undefined, ...(ngDevMode ? [{ debugName: "moveDelay" }] : /* istanbul ignore next */ []));
1970
+ moveDisabled = input(undefined, ...(ngDevMode ? [{ debugName: "moveDisabled" }] : /* istanbul ignore next */ []));
1971
+ moveSpring = input(undefined, ...(ngDevMode ? [{ debugName: "moveSpring" }] : /* istanbul ignore next */ []));
1972
+ #defaults = inject(MOVEMENT_CONFIG);
1973
+ #documentRef = inject(DOCUMENT);
1974
+ #host = inject((ElementRef));
1975
+ #engine = inject(AnimationEngine);
1976
+ #currentPlayer = null;
1977
+ #isFocused = false;
1978
+ onFocus() {
1979
+ if (this.#isFocused)
1980
+ return;
1981
+ this.#isFocused = true;
1982
+ this.play(false);
1983
+ }
1984
+ onBlur() {
1985
+ if (!this.#isFocused)
1986
+ return;
1987
+ this.#isFocused = false;
1988
+ this.play(true);
1989
+ }
1990
+ play(reverse) {
1991
+ this.#currentPlayer?.cancel();
1992
+ const isReduced = prefersReducedMotion(this.#documentRef);
1993
+ const config = resolveMovementConfig(this.#defaults, {
1994
+ duration: this.moveDuration(),
1995
+ easing: this.moveEasing(),
1996
+ delay: this.moveDelay(),
1997
+ disabled: this.moveDisabled(),
1998
+ }, isReduced);
1999
+ if (config.disabled)
2000
+ return;
2001
+ let frames = resolveMoveFrames(this.moveWhileFocus(), 'enter');
2002
+ if (reverse) {
2003
+ frames = reverseFrames(frames);
2004
+ }
2005
+ this.#currentPlayer = this.#engine.play(this.#host.nativeElement, frames, {
2006
+ config,
2007
+ spring: this.moveSpring(),
2008
+ disabled: false,
2009
+ });
2010
+ }
2011
+ ngOnDestroy() {
2012
+ this.#currentPlayer?.cancel();
2013
+ }
2014
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveFocusDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2015
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.2", type: MoveFocusDirective, isStandalone: true, selector: "[moveWhileFocus]", inputs: { moveWhileFocus: { classPropertyName: "moveWhileFocus", publicName: "moveWhileFocus", isSignal: true, isRequired: true, transformFunction: null }, moveDuration: { classPropertyName: "moveDuration", publicName: "moveDuration", isSignal: true, isRequired: false, transformFunction: null }, moveEasing: { classPropertyName: "moveEasing", publicName: "moveEasing", isSignal: true, isRequired: false, transformFunction: null }, moveDelay: { classPropertyName: "moveDelay", publicName: "moveDelay", isSignal: true, isRequired: false, transformFunction: null }, moveDisabled: { classPropertyName: "moveDisabled", publicName: "moveDisabled", isSignal: true, isRequired: false, transformFunction: null }, moveSpring: { classPropertyName: "moveSpring", publicName: "moveSpring", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "focusin": "onFocus()", "focusout": "onBlur()" } }, ngImport: i0 });
2016
+ }
2017
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveFocusDirective, decorators: [{
2018
+ type: Directive,
2019
+ args: [{
2020
+ selector: '[moveWhileFocus]',
2021
+ host: {
2022
+ '(focusin)': 'onFocus()',
2023
+ '(focusout)': 'onBlur()',
2024
+ },
2025
+ }]
2026
+ }], propDecorators: { moveWhileFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveWhileFocus", required: true }] }], moveDuration: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDuration", required: false }] }], moveEasing: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveEasing", required: false }] }], moveDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDelay", required: false }] }], moveDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDisabled", required: false }] }], moveSpring: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveSpring", required: false }] }] } });
2027
+
2028
+ class MoveParallaxDirective {
2029
+ moveParallax = input(0.2, ...(ngDevMode ? [{ debugName: "moveParallax" }] : /* istanbul ignore next */ []));
2030
+ moveParallaxAxis = input('y', ...(ngDevMode ? [{ debugName: "moveParallaxAxis" }] : /* istanbul ignore next */ []));
2031
+ #documentRef = inject(DOCUMENT);
2032
+ #platformId = inject(PLATFORM_ID);
2033
+ #host = inject((ElementRef));
2034
+ #engine = inject(AnimationEngine);
2035
+ #smoothScroll = inject(SmoothScrollService, { optional: true });
2036
+ #player = null;
2037
+ #observer = null;
2038
+ #isVisible = false;
2039
+ #scrollListener = () => this.#updateProgress();
2040
+ #elHeight = 0;
2041
+ #windowHeight = 0;
2042
+ #totalDistance = 0;
2043
+ #initialAbsoluteTop = 0;
2044
+ constructor() {
2045
+ effect(() => {
2046
+ this.#smoothScroll?.scrollY();
2047
+ if (this.#smoothScroll?.isActive && this.#isVisible) {
2048
+ this.#updateProgress();
2049
+ }
2050
+ });
2051
+ }
2052
+ ngOnInit() {
2053
+ if (!isPlatformBrowser(this.#platformId))
2054
+ return;
2055
+ const view = this.#documentRef.defaultView;
2056
+ if (!view)
2057
+ return;
2058
+ this.#initAnimation();
2059
+ this.#observer = new IntersectionObserver((entries) => {
2060
+ const entry = entries[0];
2061
+ this.#isVisible = entry.isIntersecting;
2062
+ if (entry.isIntersecting) {
2063
+ if (!this.#smoothScroll?.isActive) {
2064
+ view.addEventListener('scroll', this.#scrollListener, { passive: true });
2065
+ }
2066
+ this.#updateProgress();
2067
+ }
2068
+ else {
2069
+ view.removeEventListener('scroll', this.#scrollListener);
2070
+ }
2071
+ }, { root: null });
2072
+ this.#observer.observe(this.#host.nativeElement);
2073
+ // Re-initialize animation on resize to update dimensions
2074
+ view.addEventListener('resize', this.#resizeListener, { passive: true });
2075
+ }
2076
+ #resizeListener = () => {
2077
+ this.#initAnimation();
2078
+ };
2079
+ #initAnimation() {
2080
+ const view = this.#documentRef.defaultView;
2081
+ if (!view)
2082
+ return;
2083
+ this.#windowHeight = view.innerHeight;
2084
+ this.#elHeight = this.#host.nativeElement.offsetHeight;
2085
+ this.#totalDistance = this.#windowHeight + this.#elHeight;
2086
+ const scrollY = this.#smoothScroll?.isActive
2087
+ ? this.#smoothScroll.scrollY()
2088
+ : view.scrollY || view.pageYOffset || 0;
2089
+ const rect = this.#host.nativeElement.getBoundingClientRect();
2090
+ this.#initialAbsoluteTop = scrollY + rect.top;
2091
+ const speed = this.moveParallax();
2092
+ const axis = this.moveParallaxAxis();
2093
+ // Total translation distance across the entire scroll intersection
2094
+ const translateDist = this.#totalDistance * speed;
2095
+ const frames = {};
2096
+ if (axis === 'x') {
2097
+ frames.x = [translateDist / 2, -translateDist / 2];
2098
+ }
2099
+ else {
2100
+ frames.y = [translateDist / 2, -translateDist / 2];
2101
+ }
2102
+ this.#player?.cancel();
2103
+ this.#player = this.#engine.play(this.#host.nativeElement, frames, {
2104
+ config: { duration: 1000, delay: 0, easing: 'linear', disabled: false },
2105
+ });
2106
+ this.#player?.pause();
2107
+ if (this.#player) {
2108
+ this.#player.currentTime = 0;
2109
+ }
2110
+ }
2111
+ #updateProgress() {
2112
+ const view = this.#documentRef.defaultView;
2113
+ if (!view || this.#totalDistance === 0)
2114
+ return;
2115
+ const scrollY = this.#smoothScroll?.isActive
2116
+ ? this.#smoothScroll.scrollY()
2117
+ : view.scrollY || view.pageYOffset || 0;
2118
+ // Efficiently calculate the current visual top without triggering layout thrashing
2119
+ // or feedback loops caused by the active CSS transform translating the element.
2120
+ const currentVirtualTop = this.#initialAbsoluteTop - scrollY;
2121
+ // progress is 0 when element top hits window bottom (currentVirtualTop === windowHeight)
2122
+ // progress is 1 when element bottom hits window top (currentVirtualTop === -elHeight)
2123
+ let p = (this.#windowHeight - currentVirtualTop) / this.#totalDistance;
2124
+ p = Math.max(0, Math.min(1, p));
2125
+ if (this.#player) {
2126
+ this.#player.currentTime = p * 1000;
2127
+ }
2128
+ }
2129
+ ngOnDestroy() {
2130
+ this.#player?.cancel();
2131
+ const view = this.#documentRef.defaultView;
2132
+ if (view) {
2133
+ view.removeEventListener('scroll', this.#scrollListener);
2134
+ view.removeEventListener('resize', this.#resizeListener);
2135
+ }
2136
+ this.#observer?.disconnect();
2137
+ }
2138
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveParallaxDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2139
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.2", type: MoveParallaxDirective, isStandalone: true, selector: "[moveParallax]", inputs: { moveParallax: { classPropertyName: "moveParallax", publicName: "moveParallax", isSignal: true, isRequired: false, transformFunction: null }, moveParallaxAxis: { classPropertyName: "moveParallaxAxis", publicName: "moveParallaxAxis", isSignal: true, isRequired: false, transformFunction: null } }, exportAs: ["moveParallax"], ngImport: i0 });
2140
+ }
2141
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveParallaxDirective, decorators: [{
2142
+ type: Directive,
2143
+ args: [{
2144
+ selector: '[moveParallax]',
2145
+ exportAs: 'moveParallax',
2146
+ }]
2147
+ }], ctorParameters: () => [], propDecorators: { moveParallax: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveParallax", required: false }] }], moveParallaxAxis: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveParallaxAxis", required: false }] }] } });
2148
+
2149
+ function provideMovement(config = {}) {
2150
+ return makeEnvironmentProviders([
2151
+ {
2152
+ provide: MOVEMENT_CONFIG,
2153
+ useValue: {
2154
+ ...MOVEMENT_DEFAULTS,
2155
+ ...config,
2156
+ },
2157
+ },
2158
+ ]);
2159
+ }
2160
+
2161
+ const MOVEMENT_DIRECTIVES = [
2162
+ MoveEnterDirective,
2163
+ MoveLeaveDirective,
2164
+ MoveAnimateDirective,
2165
+ MoveHoverDirective,
2166
+ MoveTapDirective,
2167
+ MoveVariantsDirective,
2168
+ MoveStaggerDirective,
2169
+ MoveLayoutDirective,
2170
+ MoveScrollDirective,
2171
+ MovePresenceDirective,
2172
+ MoveDragDirective,
2173
+ MoveInViewDirective,
2174
+ MoveTextDirective,
2175
+ MoveSmoothScrollDirective,
2176
+ MoveFocusDirective,
2177
+ MoveParallaxDirective,
2178
+ MoveAnimationDirective,
2179
+ ];
2180
+
2181
+ /*
2182
+ * Public API Surface of movement
2183
+ */
2184
+
2185
+ /**
2186
+ * Generated bundle index. Do not edit.
2187
+ */
2188
+
2189
+ export { AnimationEngine, MOVEMENT_CONFIG, MOVEMENT_DEFAULTS, MOVEMENT_DIRECTIVES, MOVE_PRESENCE_PARENT, MOVE_PRESETS, MOVE_STAGGER_PARENT, MOVE_VARIANTS_PARENT, MoveAnimateDirective, MoveAnimationDirective, MoveDragDirective, MoveEnterDirective, MoveFocusDirective, MoveHoverDirective, MoveInViewDirective, MoveLayoutDirective, MoveLeaveDirective, MoveParallaxDirective, MovePresenceDirective, MoveScrollDirective, MoveSmoothScrollDirective, MoveStaggerDirective, MoveTapDirective, MoveTextDirective, MoveVariantsDirective, SmoothScrollService, SpringPlayer, WaapiPlayer, provideMovement };
2190
+ //# sourceMappingURL=angular-movement.mjs.map