angular-movement 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,13 @@
1
1
  import { isPlatformBrowser, DOCUMENT } from '@angular/common';
2
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';
3
+ import { InjectionToken, inject, PLATFORM_ID, Injectable, input, ElementRef, computed, effect, forwardRef, Directive, afterEveryRender, NgZone, signal, ViewContainerRef, TemplateRef, Renderer2, makeEnvironmentProviders } from '@angular/core';
4
4
 
5
5
  const MOVEMENT_DEFAULTS = {
6
6
  duration: 300,
7
7
  easing: 'cubic-bezier(0.16, 1, 0.3, 1)',
8
8
  delay: 0,
9
9
  disabled: false,
10
+ iterations: 1,
10
11
  };
11
12
  const MOVEMENT_CONFIG = new InjectionToken('MOVEMENT_CONFIG', {
12
13
  factory: () => ({ ...MOVEMENT_DEFAULTS }),
@@ -52,6 +53,18 @@ function getInterpolated(arr, i1, i2, p) {
52
53
  const v2 = arr[Math.min(i2, arr.length - 1)];
53
54
  return v1 + (v2 - v1) * p;
54
55
  }
56
+ const KNOWN_KEYS = new Set([
57
+ 'opacity',
58
+ 'x',
59
+ 'y',
60
+ 'scale',
61
+ 'scaleX',
62
+ 'scaleY',
63
+ 'rotate',
64
+ 'rotateX',
65
+ 'rotateY',
66
+ 'blur',
67
+ ]);
55
68
  function buildKeyframe(frames, getVal) {
56
69
  const keyframe = {};
57
70
  const opacity = getVal(frames.opacity);
@@ -87,6 +100,16 @@ function buildKeyframe(frames, getVal) {
87
100
  if (rotateX !== undefined || rotateY !== undefined) {
88
101
  keyframe.transform = `perspective(${DEFAULT_PERSPECTIVE}) rotateX(${rotateX ?? 0}deg) rotateY(${rotateY ?? 0}deg)`;
89
102
  }
103
+ // Passthrough arbitrary properties for WAAPI (e.g. strokeDashoffset)
104
+ for (const key in frames) {
105
+ if (KNOWN_KEYS.has(key))
106
+ continue;
107
+ const arr = frames[key];
108
+ const val = getVal(arr);
109
+ if (val !== undefined) {
110
+ keyframe[key] = val;
111
+ }
112
+ }
90
113
  return keyframe;
91
114
  }
92
115
  function composeKeyframeAt(frames, index) {
@@ -101,27 +124,51 @@ function composeInitialStyle(frames) {
101
124
  function composeFinalStyle(frames) {
102
125
  return buildKeyframe(frames, (arr) => (arr && arr.length > 0 ? arr[arr.length - 1] : undefined));
103
126
  }
127
+ const KNOWN_STYLE_KEYS = new Set([
128
+ 'opacity',
129
+ 'translate',
130
+ 'scale',
131
+ 'rotate',
132
+ 'transform',
133
+ 'filter',
134
+ ]);
104
135
  function applyComposedStyle(el, style) {
136
+ const styledEl = el;
105
137
  if (style.opacity !== undefined)
106
- el.style.opacity = `${style.opacity}`;
138
+ styledEl.style.opacity = `${style.opacity}`;
107
139
  if (style.translate !== undefined)
108
- el.style.translate = style.translate;
140
+ styledEl.style.translate = style.translate;
109
141
  if (style.scale !== undefined)
110
- el.style.scale = style.scale;
142
+ styledEl.style.scale = style.scale;
111
143
  if (style.rotate !== undefined)
112
- el.style.rotate = style.rotate;
144
+ styledEl.style.rotate = style.rotate;
113
145
  if (style.transform !== undefined)
114
- el.style.transform = style.transform;
146
+ styledEl.style.transform = style.transform;
115
147
  if (style.filter !== undefined)
116
- el.style.filter = style.filter;
148
+ styledEl.style.filter = style.filter;
149
+ // Passthrough arbitrary properties (e.g. strokeDashoffset)
150
+ for (const key in style) {
151
+ if (KNOWN_STYLE_KEYS.has(key))
152
+ continue;
153
+ const val = style[key];
154
+ if (val !== undefined) {
155
+ styledEl.style[key] = String(val);
156
+ }
157
+ }
117
158
  }
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 = '';
159
+ function clearComposedStyle(el, extraKeys) {
160
+ const styledEl = el;
161
+ styledEl.style.opacity = '';
162
+ styledEl.style.translate = '';
163
+ styledEl.style.scale = '';
164
+ styledEl.style.rotate = '';
165
+ styledEl.style.transform = '';
166
+ styledEl.style.filter = '';
167
+ if (extraKeys) {
168
+ for (const key of extraKeys) {
169
+ styledEl.style[key] = '';
170
+ }
171
+ }
125
172
  }
126
173
 
127
174
  const DEFAULT_FADE_OPACITY = [0, 1];
@@ -178,6 +225,7 @@ const MOVE_PRESETS = {
178
225
  'bounce-in': {
179
226
  enter: { opacity: DEFAULT_FADE_OPACITY, y: [30, 0], scale: [0.85, 1] },
180
227
  leave: { opacity: DEFAULT_LEAVE_OPACITY, y: [0, -20], scale: [1, 0.9] },
228
+ loop: { y: [0, -10, 0], scale: [1, 0.95, 1] },
181
229
  },
182
230
  'blur-in': {
183
231
  enter: { opacity: DEFAULT_FADE_OPACITY, blur: [10, 0] },
@@ -186,10 +234,81 @@ const MOVE_PRESETS = {
186
234
  spin: {
187
235
  enter: { opacity: DEFAULT_FADE_OPACITY, rotate: [-360, 0] },
188
236
  leave: { opacity: DEFAULT_LEAVE_OPACITY, rotate: [0, 360] },
237
+ loop: { rotate: [0, 360] },
189
238
  },
190
239
  pulse: {
191
240
  enter: { scale: [1, 1.05, 1] },
192
241
  leave: { scale: [1, 0.95, 1] },
242
+ loop: { scale: [1, 1.05, 1] },
243
+ },
244
+ shake: {
245
+ enter: { opacity: DEFAULT_FADE_OPACITY, x: [0, -10, 10, -10, 10, -5, 5, -5, 5, 0] },
246
+ leave: { opacity: DEFAULT_LEAVE_OPACITY, x: [0, 10, -10, 10, -10, 5, -5, 5, -5, 0] },
247
+ },
248
+ swing: {
249
+ enter: { opacity: DEFAULT_FADE_OPACITY, rotate: [0, 15, -10, 5, -5, 0] },
250
+ leave: { opacity: DEFAULT_LEAVE_OPACITY, rotate: [0, -15, 10, -5, 5, 0] },
251
+ },
252
+ wobble: {
253
+ enter: {
254
+ opacity: DEFAULT_FADE_OPACITY,
255
+ x: [0, -25, 20, -15, 10, -5, 0],
256
+ rotate: [0, -5, 3, -3, 2, -1, 0],
257
+ },
258
+ leave: {
259
+ opacity: DEFAULT_LEAVE_OPACITY,
260
+ x: [0, 25, -20, 15, -10, 5, 0],
261
+ rotate: [0, 5, -3, 3, -2, 1, 0],
262
+ },
263
+ },
264
+ 'rubber-band': {
265
+ enter: {
266
+ opacity: DEFAULT_FADE_OPACITY,
267
+ scaleX: [1, 1.25, 0.75, 1.15, 0.95, 1.05, 1],
268
+ scaleY: [1, 0.75, 1.25, 0.85, 1.05, 0.95, 1],
269
+ },
270
+ leave: {
271
+ opacity: DEFAULT_LEAVE_OPACITY,
272
+ scaleX: [1, 1.25, 0.75, 1.15, 0.95, 1.05, 1],
273
+ scaleY: [1, 0.75, 1.25, 0.85, 1.05, 0.95, 1],
274
+ },
275
+ },
276
+ 'heart-beat': {
277
+ enter: { opacity: DEFAULT_FADE_OPACITY, scale: [1, 1.3, 1, 1.3, 1] },
278
+ leave: { opacity: DEFAULT_LEAVE_OPACITY, scale: [1, 1.3, 1, 1.3, 1] },
279
+ loop: { scale: [1, 1.3, 1, 1.3, 1] },
280
+ },
281
+ tada: {
282
+ enter: {
283
+ opacity: DEFAULT_FADE_OPACITY,
284
+ scale: [1, 0.9, 1.1, 1.1, 1.1, 1],
285
+ rotate: [0, -3, 3, -3, 3, 0],
286
+ },
287
+ leave: {
288
+ opacity: DEFAULT_LEAVE_OPACITY,
289
+ scale: [1, 0.9, 1.1, 1.1, 1.1, 1],
290
+ rotate: [0, 3, -3, 3, -3, 0],
291
+ },
292
+ },
293
+ jello: {
294
+ enter: {
295
+ opacity: DEFAULT_FADE_OPACITY,
296
+ scaleX: [1, 1.25, 0.75, 1.15, 0.95, 1.05, 1],
297
+ scaleY: [1, 0.75, 1.25, 0.85, 1.05, 0.95, 1],
298
+ },
299
+ leave: {
300
+ opacity: DEFAULT_LEAVE_OPACITY,
301
+ scaleX: [1, 1.25, 0.75, 1.15, 0.95, 1.05, 1],
302
+ scaleY: [1, 0.75, 1.25, 0.85, 1.05, 0.95, 1],
303
+ },
304
+ },
305
+ 'light-speed': {
306
+ enter: { opacity: [0, 1], x: [200, 0], scaleX: [0, 1] },
307
+ leave: { opacity: [1, 0], x: [0, 200], scaleX: [1, 0] },
308
+ },
309
+ 'roll-in': {
310
+ enter: { opacity: [0, 1], x: [-100, 0], rotate: [-120, 0] },
311
+ leave: { opacity: [1, 0], x: [0, 100], rotate: [0, 120] },
193
312
  },
194
313
  none: {
195
314
  enter: { opacity: [1, 1] },
@@ -207,6 +326,7 @@ function resolveMovementConfig(defaults, overrides, reducedMotion) {
207
326
  easing: overrides.easing ?? defaults.easing,
208
327
  delay: Math.max(0, overrides.delay ?? defaults.delay),
209
328
  disabled: reducedMotion || (overrides.disabled ?? defaults.disabled),
329
+ iterations: overrides.iterations ?? defaults.iterations,
210
330
  };
211
331
  }
212
332
  function resolveMoveFrames(value, phase) {
@@ -216,9 +336,9 @@ function resolveMoveFrames(value, phase) {
216
336
  if (typeof ngDevMode !== 'undefined' && ngDevMode) {
217
337
  console.warn(`[Movement] Unknown preset: "${value}". Using "none" preset.`);
218
338
  }
219
- return MOVE_PRESETS['none'][phase];
339
+ return MOVE_PRESETS['none'][phase] ?? MOVE_PRESETS['none'].enter;
220
340
  }
221
- return preset[phase];
341
+ return preset[phase] ?? preset.enter;
222
342
  }
223
343
  return value;
224
344
  }
@@ -259,6 +379,63 @@ function applyInitialStyles(el, frames) {
259
379
  function clearInitialStyles(el) {
260
380
  clearComposedStyle(el);
261
381
  }
382
+ /**
383
+ * Validates MoveSpring configuration and returns sanitized values.
384
+ * Warns in development mode for invalid values.
385
+ */
386
+ function validateSpring(spring) {
387
+ if (!spring)
388
+ return undefined;
389
+ const validated = {};
390
+ if (spring.stiffness !== undefined) {
391
+ if (spring.stiffness <= 0 && typeof ngDevMode !== 'undefined' && ngDevMode) {
392
+ console.warn('[Movement] Spring stiffness must be > 0. Using default.');
393
+ }
394
+ validated.stiffness = spring.stiffness > 0 ? spring.stiffness : 100;
395
+ }
396
+ if (spring.damping !== undefined) {
397
+ if (spring.damping < 0 && typeof ngDevMode !== 'undefined' && ngDevMode) {
398
+ console.warn('[Movement] Spring damping must be >= 0. Using default.');
399
+ }
400
+ validated.damping = spring.damping >= 0 ? spring.damping : 10;
401
+ }
402
+ if (spring.mass !== undefined) {
403
+ if (spring.mass <= 0 && typeof ngDevMode !== 'undefined' && ngDevMode) {
404
+ console.warn('[Movement] Spring mass must be > 0. Using default.');
405
+ }
406
+ validated.mass = spring.mass > 0 ? spring.mass : 1;
407
+ }
408
+ if (spring.velocity !== undefined) {
409
+ validated.velocity = spring.velocity;
410
+ }
411
+ return validated;
412
+ }
413
+ /**
414
+ * Validates scroll offset string format "elFraction viewFraction".
415
+ * Returns true if valid, warns in dev mode if invalid.
416
+ */
417
+ function isValidScrollOffset(offset) {
418
+ const parts = offset.split(' ').map(parseFloat);
419
+ if (parts.length !== 2 || parts.some(Number.isNaN)) {
420
+ if (typeof ngDevMode !== 'undefined' && ngDevMode) {
421
+ console.warn(`[Movement] Invalid scroll offset: "${offset}". Expected format "elFraction viewFraction" (e.g. "0 1").`);
422
+ }
423
+ return false;
424
+ }
425
+ return true;
426
+ }
427
+ /**
428
+ * Validates drag elastic factor. Must be between 0 and 1.
429
+ */
430
+ function validateDragElastic(elastic) {
431
+ if (elastic < 0 || elastic > 1) {
432
+ if (typeof ngDevMode !== 'undefined' && ngDevMode) {
433
+ console.warn(`[Movement] Drag elastic must be between 0 and 1. Got ${elastic}. Clamping to range.`);
434
+ }
435
+ return Math.max(0, Math.min(1, elastic));
436
+ }
437
+ return elastic;
438
+ }
262
439
 
263
440
  class WaapiPlayer {
264
441
  #animation = null;
@@ -273,12 +450,18 @@ class WaapiPlayer {
273
450
  return;
274
451
  }
275
452
  const keyframes = this.#toWAAPIKeyframes(frames);
453
+ const iterations = config.iterations ?? 1;
276
454
  this.#animation = host.animate(keyframes, {
277
455
  duration: config.duration,
278
456
  easing: config.easing,
279
457
  delay: config.delay,
280
458
  fill: 'both',
459
+ iterations,
281
460
  });
461
+ if (iterations === Infinity) {
462
+ // Infinite loops never finish; consumer must call cancel() manually.
463
+ return;
464
+ }
282
465
  this.#animation.addEventListener('finish', () => {
283
466
  this.#animation?.commitStyles?.();
284
467
  this.#animation?.cancel();
@@ -328,22 +511,28 @@ class SpringPlayer {
328
511
  host;
329
512
  frames;
330
513
  delay;
514
+ iterations;
331
515
  onDone;
332
516
  #resolveFinished;
333
517
  finished = new Promise((resolve) => {
334
518
  this.#resolveFinished = resolve;
335
519
  });
336
520
  #animation = null;
337
- constructor(host, frames, userConfig, delay, onDone) {
521
+ constructor(host, frames, userConfig, delay, iterations = 1, onDone) {
338
522
  this.host = host;
339
523
  this.frames = frames;
340
524
  this.delay = delay;
525
+ this.iterations = iterations;
341
526
  this.onDone = onDone;
342
527
  if (typeof host.animate !== 'function') {
343
528
  this.#resolveFinished();
344
529
  onDone?.();
345
530
  return;
346
531
  }
532
+ if (this.iterations !== 1 && typeof ngDevMode !== 'undefined' && ngDevMode) {
533
+ console.warn('[Movement] Spring animations with iterations !== 1 may produce visual glitches. ' +
534
+ 'Consider using WaapiPlayer (no spring) for loops.');
535
+ }
347
536
  const config = {
348
537
  stiffness: 100,
349
538
  damping: 10,
@@ -366,7 +555,12 @@ class SpringPlayer {
366
555
  delay: this.delay,
367
556
  fill: 'both',
368
557
  easing: 'linear', // Spring physics already has the easing baked into the frames
558
+ iterations: this.iterations,
369
559
  });
560
+ if (this.iterations === Infinity) {
561
+ // Infinite loops never finish; consumer must call cancel() manually.
562
+ return;
563
+ }
370
564
  this.#animation.addEventListener('finish', () => {
371
565
  this.#animation?.commitStyles?.();
372
566
  this.#animation?.cancel();
@@ -453,14 +647,18 @@ class AnimationEngine {
453
647
  return null;
454
648
  }
455
649
  if (options.disabled) {
650
+ this.#prepareSvgStrokeDraw(host, frames);
456
651
  this.#applyFinalStyles(host, frames);
457
652
  options.onDone?.();
458
653
  return null;
459
654
  }
655
+ this.#prepareSvgStrokeDraw(host, frames);
460
656
  const config = options.config ?? this.#defaults;
461
- const isSpring = options.spring || config.easing === 'spring';
657
+ const spring = validateSpring(options.spring);
658
+ const isSpring = spring || config.easing === 'spring';
659
+ const iterations = options.iterations ?? config.iterations;
462
660
  if (isSpring) {
463
- return new SpringPlayer(host, frames, options.spring ?? {}, options.delay ?? config.delay, options.onDone);
661
+ return new SpringPlayer(host, frames, spring ?? {}, options.delay ?? config.delay, iterations, options.onDone);
464
662
  }
465
663
  else {
466
664
  return new WaapiPlayer(host, frames, {
@@ -468,12 +666,36 @@ class AnimationEngine {
468
666
  easing: config.easing,
469
667
  delay: options.delay ?? config.delay,
470
668
  disabled: false,
669
+ iterations,
471
670
  }, options.onDone);
472
671
  }
473
672
  }
474
673
  #applyFinalStyles(host, frames) {
475
674
  applyComposedStyle(host, composeFinalStyle(frames));
476
675
  }
676
+ #prepareSvgStrokeDraw(host, frames) {
677
+ if (!frames['strokeDashoffset'] || !this.#isSvgGeometryElement(host)) {
678
+ return;
679
+ }
680
+ let length = 28;
681
+ try {
682
+ length = host.getTotalLength() || length;
683
+ }
684
+ catch {
685
+ length = 28;
686
+ }
687
+ const styledHost = host;
688
+ styledHost.style.strokeDasharray = `${length}`;
689
+ styledHost.style.strokeDashoffset = `${length}`;
690
+ }
691
+ #isSvgGeometryElement(host) {
692
+ const view = host.ownerDocument?.defaultView;
693
+ const SvgGeometryElement = view?.SVGGeometryElement;
694
+ if (typeof SvgGeometryElement === 'function' && host instanceof SvgGeometryElement) {
695
+ return true;
696
+ }
697
+ return typeof host.getTotalLength === 'function';
698
+ }
477
699
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: AnimationEngine, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
478
700
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: AnimationEngine, providedIn: 'root' });
479
701
  }
@@ -486,6 +708,81 @@ const MOVE_STAGGER_PARENT = new InjectionToken('MOVE_STAGGER_PARENT');
486
708
 
487
709
  const MOVE_PRESENCE_PARENT = new InjectionToken('MOVE_PRESENCE_PARENT');
488
710
 
711
+ const MOVE_VARIANTS_PARENT = new InjectionToken('MOVE_VARIANTS_PARENT');
712
+ class MoveVariantsDirective {
713
+ moveVariants = input.required(...(ngDevMode ? [{ debugName: "moveVariants" }] : /* istanbul ignore next */ []));
714
+ moveAnimate = input(undefined, ...(ngDevMode ? [{ debugName: "moveAnimate" }] : /* istanbul ignore next */ []));
715
+ moveDuration = input(undefined, ...(ngDevMode ? [{ debugName: "moveDuration" }] : /* istanbul ignore next */ []));
716
+ moveEasing = input(undefined, ...(ngDevMode ? [{ debugName: "moveEasing" }] : /* istanbul ignore next */ []));
717
+ moveDelay = input(undefined, ...(ngDevMode ? [{ debugName: "moveDelay" }] : /* istanbul ignore next */ []));
718
+ moveDisabled = input(undefined, ...(ngDevMode ? [{ debugName: "moveDisabled" }] : /* istanbul ignore next */ []));
719
+ moveSpring = input(undefined, ...(ngDevMode ? [{ debugName: "moveSpring" }] : /* istanbul ignore next */ []));
720
+ #parent = inject(MOVE_VARIANTS_PARENT, { optional: true, skipSelf: true });
721
+ #engine = inject(AnimationEngine);
722
+ #defaults = inject(MOVEMENT_CONFIG);
723
+ #documentRef = inject(DOCUMENT);
724
+ #host = inject((ElementRef));
725
+ #stagger = inject(MOVE_STAGGER_PARENT, { optional: true });
726
+ #currentPlayer = null;
727
+ #isReducedMotion = false;
728
+ activeVariant = computed(() => {
729
+ return this.moveAnimate() ?? this.#parent?.activeVariant();
730
+ }, ...(ngDevMode ? [{ debugName: "activeVariant" }] : /* istanbul ignore next */ []));
731
+ constructor() {
732
+ this.#isReducedMotion = prefersReducedMotion(this.#documentRef);
733
+ this.#stagger?.register(this.#host.nativeElement);
734
+ effect(() => {
735
+ const variantName = this.activeVariant();
736
+ if (!variantName)
737
+ return;
738
+ const variants = this.moveVariants();
739
+ if (!variants)
740
+ return;
741
+ const state = variants[variantName];
742
+ if (!state)
743
+ return;
744
+ this.#currentPlayer?.cancel();
745
+ const { spring, duration, easing, delay, ...keyframesMap } = state;
746
+ const keyframes = keyframesMap;
747
+ const staggerDelay = this.#stagger?.getDelay(this.#host.nativeElement) ?? 0;
748
+ const config = resolveMovementConfig(this.#defaults, {
749
+ duration: duration ?? this.moveDuration(),
750
+ easing: easing ?? this.moveEasing(),
751
+ delay: (delay ?? this.moveDelay() ?? 0) + staggerDelay,
752
+ disabled: this.moveDisabled(),
753
+ }, this.#isReducedMotion);
754
+ this.#currentPlayer = this.#engine.play(this.#host.nativeElement, keyframes, {
755
+ config,
756
+ spring: spring ?? this.moveSpring(),
757
+ disabled: config.disabled,
758
+ });
759
+ });
760
+ }
761
+ ngOnDestroy() {
762
+ this.#stagger?.unregister(this.#host.nativeElement);
763
+ this.#currentPlayer?.cancel();
764
+ }
765
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveVariantsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
766
+ 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: [
767
+ {
768
+ provide: MOVE_VARIANTS_PARENT,
769
+ useExisting: forwardRef(() => MoveVariantsDirective),
770
+ },
771
+ ], ngImport: i0 });
772
+ }
773
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveVariantsDirective, decorators: [{
774
+ type: Directive,
775
+ args: [{
776
+ selector: '[moveVariants]',
777
+ providers: [
778
+ {
779
+ provide: MOVE_VARIANTS_PARENT,
780
+ useExisting: forwardRef(() => MoveVariantsDirective),
781
+ },
782
+ ],
783
+ }]
784
+ }], 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 }] }] } });
785
+
489
786
  class MoveAnimateDirective {
490
787
  move = input(undefined, ...(ngDevMode ? [{ debugName: "move" }] : /* istanbul ignore next */ []));
491
788
  moveAnimate = input(undefined, ...(ngDevMode ? [{ debugName: "moveAnimate" }] : /* istanbul ignore next */ []));
@@ -501,10 +798,15 @@ class MoveAnimateDirective {
501
798
  #engine = inject(AnimationEngine);
502
799
  #stagger = inject(MOVE_STAGGER_PARENT, { optional: true });
503
800
  #presence = inject(MOVE_PRESENCE_PARENT, { optional: true });
801
+ #variantsParent = inject(MOVE_VARIANTS_PARENT, { optional: true });
504
802
  #config = this.#defaults;
505
803
  #enterPlayer = null;
506
804
  #leavePlayer = null;
507
805
  ngOnInit() {
806
+ // If moveVariants is on the same host, let it handle animation
807
+ if (this.#variantsParent) {
808
+ return;
809
+ }
508
810
  this.#stagger?.register(this.#host.nativeElement);
509
811
  this.#presence?.register(this);
510
812
  Promise.resolve().then(() => {
@@ -530,7 +832,7 @@ class MoveAnimateDirective {
530
832
  this.#leavePlayer?.cancel();
531
833
  }
532
834
  playLeave() {
533
- if (this.#config.disabled) {
835
+ if (this.#variantsParent || this.#config.disabled) {
534
836
  return Promise.resolve();
535
837
  }
536
838
  this.#enterPlayer?.cancel();
@@ -871,81 +1173,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImpor
871
1173
  }]
872
1174
  }], 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
1175
 
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
1176
  class MoveStaggerDirective {
950
1177
  moveStagger = input.required(...(ngDevMode ? [{ debugName: "moveStagger" }] : /* istanbul ignore next */ []));
951
1178
  moveStaggerDirection = input('first', ...(ngDevMode ? [{ debugName: "moveStaggerDirection" }] : /* istanbul ignore next */ []));
@@ -960,9 +1187,14 @@ class MoveStaggerDirective {
960
1187
  if (!this.#children.has(el))
961
1188
  return 0;
962
1189
  const list = Array.from(this.#children).sort((a, b) => {
963
- // Unreliable on detached elements, but they are in DOM when sorting
1190
+ if (a === b)
1191
+ return 0;
964
1192
  const pos = a.compareDocumentPosition(b);
965
- return pos & Node.DOCUMENT_POSITION_PRECEDING ? 1 : -1;
1193
+ if (pos & Node.DOCUMENT_POSITION_PRECEDING)
1194
+ return 1;
1195
+ if (pos & Node.DOCUMENT_POSITION_FOLLOWING)
1196
+ return -1;
1197
+ return 0;
966
1198
  });
967
1199
  const index = list.indexOf(el);
968
1200
  if (index === -1)
@@ -1029,7 +1261,11 @@ class MoveLayoutDirective {
1029
1261
  this.#isAnimating) {
1030
1262
  return null;
1031
1263
  }
1032
- const currentRect = this.#host.nativeElement.getBoundingClientRect();
1264
+ const el = this.#host.nativeElement;
1265
+ if (typeof el.getBoundingClientRect !== 'function') {
1266
+ return null;
1267
+ }
1268
+ const currentRect = el.getBoundingClientRect();
1033
1269
  if (this.#snapshot) {
1034
1270
  const dx = this.#snapshot.left - currentRect.left;
1035
1271
  const dy = this.#snapshot.top - currentRect.top;
@@ -1337,8 +1573,13 @@ class MoveScrollDirective {
1337
1573
  const view = this.#documentRef.defaultView;
1338
1574
  if (!view)
1339
1575
  return;
1576
+ // Validate scroll offsets
1577
+ const offsets = this.moveScrollOffset();
1578
+ if (!isValidScrollOffset(offsets[0]) || !isValidScrollOffset(offsets[1])) {
1579
+ return;
1580
+ }
1340
1581
  this.#player = this.#engine.play(this.#host.nativeElement, keyframes, {
1341
- config: { duration: 1000, easing: 'linear', delay: 0, disabled: false },
1582
+ config: { duration: 1000, easing: 'linear', delay: 0, disabled: false, iterations: 1 },
1342
1583
  });
1343
1584
  this.#player?.pause();
1344
1585
  // Sync the new player to the current scroll position immediately.
@@ -1579,7 +1820,9 @@ class MoveDragDirective {
1579
1820
  return;
1580
1821
  this.#isDragging = true;
1581
1822
  this.#pointerId = e.pointerId;
1582
- this.#host.nativeElement.setPointerCapture(e.pointerId);
1823
+ if (typeof this.#host.nativeElement.setPointerCapture === 'function') {
1824
+ this.#host.nativeElement.setPointerCapture(e.pointerId);
1825
+ }
1583
1826
  this.#player?.cancel();
1584
1827
  // read bounds cleanly before next render
1585
1828
  this.#dragBounds = this.resolveBounds();
@@ -1600,7 +1843,9 @@ class MoveDragDirective {
1600
1843
  if (!this.#isDragging || e.pointerId !== this.#pointerId)
1601
1844
  return;
1602
1845
  this.#isDragging = false;
1603
- this.#host.nativeElement.releasePointerCapture(e.pointerId);
1846
+ if (typeof this.#host.nativeElement.releasePointerCapture === 'function') {
1847
+ this.#host.nativeElement.releasePointerCapture(e.pointerId);
1848
+ }
1604
1849
  this.#pointerId = null;
1605
1850
  this.#host.nativeElement.style.touchAction = '';
1606
1851
  this.#host.nativeElement.style.userSelect = '';
@@ -1629,7 +1874,7 @@ class MoveDragDirective {
1629
1874
  let x = this.#_x;
1630
1875
  let y = this.#_y;
1631
1876
  if (this.#dragBounds) {
1632
- const elastic = this.moveDragElastic();
1877
+ const elastic = validateDragElastic(this.moveDragElastic());
1633
1878
  if (this.#dragBounds.left !== undefined && x < this.#dragBounds.left) {
1634
1879
  x = this.#dragBounds.left - (this.#dragBounds.left - x) * elastic;
1635
1880
  }
@@ -1663,7 +1908,7 @@ class MoveDragDirective {
1663
1908
  // Find the currently visible coordinates (which include elasticity)
1664
1909
  let currentVisX = this.#_x;
1665
1910
  let currentVisY = this.#_y;
1666
- const elastic = this.moveDragElastic();
1911
+ const elastic = validateDragElastic(this.moveDragElastic());
1667
1912
  if (this.#dragBounds.left !== undefined && this.#_x < this.#dragBounds.left) {
1668
1913
  currentVisX = this.#dragBounds.left - (this.#dragBounds.left - this.#_x) * elastic;
1669
1914
  }
@@ -1689,6 +1934,19 @@ class MoveDragDirective {
1689
1934
  }
1690
1935
  }
1691
1936
  ngOnDestroy() {
1937
+ if (this.#pointerId !== null) {
1938
+ try {
1939
+ if (typeof this.#host.nativeElement.releasePointerCapture === 'function') {
1940
+ this.#host.nativeElement.releasePointerCapture(this.#pointerId);
1941
+ }
1942
+ }
1943
+ catch {
1944
+ // Element may already be detached
1945
+ }
1946
+ this.#pointerId = null;
1947
+ }
1948
+ this.#host.nativeElement.style.touchAction = '';
1949
+ this.#host.nativeElement.style.userSelect = '';
1692
1950
  this.#player?.cancel();
1693
1951
  }
1694
1952
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveDragDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
@@ -1809,6 +2067,7 @@ class MoveTextDirective {
1809
2067
  #platformId = inject(PLATFORM_ID);
1810
2068
  #host = inject((ElementRef));
1811
2069
  #engine = inject(AnimationEngine);
2070
+ #renderer = inject(Renderer2);
1812
2071
  #players = [];
1813
2072
  #spans = [];
1814
2073
  #observer = null;
@@ -1869,21 +2128,24 @@ class MoveTextDirective {
1869
2128
  #splitText() {
1870
2129
  const el = this.#host.nativeElement;
1871
2130
  const text = (el.textContent ?? '').trim();
1872
- el.innerHTML = '';
1873
- el.setAttribute('aria-label', text);
2131
+ // Clear existing content safely via Renderer2
2132
+ while (el.firstChild) {
2133
+ this.#renderer.removeChild(el, el.firstChild);
2134
+ }
2135
+ this.#renderer.setAttribute(el, 'aria-label', text);
1874
2136
  const byChars = this.moveTextSplit() === 'chars';
1875
2137
  if (byChars) {
1876
2138
  // Split character by character, preserving spaces as text nodes
1877
2139
  [...text].forEach((char) => {
1878
2140
  if (char === ' ') {
1879
- el.appendChild(this.#documentRef.createTextNode(' '));
2141
+ this.#renderer.appendChild(el, this.#documentRef.createTextNode(' '));
1880
2142
  return;
1881
2143
  }
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);
2144
+ const span = this.#renderer.createElement('span');
2145
+ this.#renderer.setAttribute(span, 'aria-hidden', 'true');
2146
+ this.#renderer.setStyle(span, 'display', 'inline-block');
2147
+ this.#renderer.setProperty(span, 'textContent', char);
2148
+ this.#renderer.appendChild(el, span);
1887
2149
  this.#spans.push(span);
1888
2150
  });
1889
2151
  }
@@ -1891,12 +2153,12 @@ class MoveTextDirective {
1891
2153
  // Split word by word
1892
2154
  const words = text.split(/\s+/);
1893
2155
  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);
2156
+ const span = this.#renderer.createElement('span');
2157
+ this.#renderer.setAttribute(span, 'aria-hidden', 'true');
2158
+ this.#renderer.setStyle(span, 'display', 'inline-block');
2159
+ this.#renderer.setStyle(span, 'white-space', 'pre');
2160
+ this.#renderer.setProperty(span, 'textContent', index < words.length - 1 ? word + ' ' : word);
2161
+ this.#renderer.appendChild(el, span);
1900
2162
  this.#spans.push(span);
1901
2163
  });
1902
2164
  }
@@ -1904,6 +2166,8 @@ class MoveTextDirective {
1904
2166
  ngOnDestroy() {
1905
2167
  this.#observer?.disconnect();
1906
2168
  this.#players.forEach((p) => p.cancel());
2169
+ this.#players = [];
2170
+ this.#spans = [];
1907
2171
  }
1908
2172
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveTextDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1909
2173
  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 });
@@ -2101,7 +2365,7 @@ class MoveParallaxDirective {
2101
2365
  }
2102
2366
  this.#player?.cancel();
2103
2367
  this.#player = this.#engine.play(this.#host.nativeElement, frames, {
2104
- config: { duration: 1000, delay: 0, easing: 'linear', disabled: false },
2368
+ config: { duration: 1000, delay: 0, easing: 'linear', disabled: false, iterations: 1 },
2105
2369
  });
2106
2370
  this.#player?.pause();
2107
2371
  if (this.#player) {
@@ -2146,6 +2410,130 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImpor
2146
2410
  }]
2147
2411
  }], 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
2412
 
2413
+ class MoveLoopDirective {
2414
+ moveLoop = input('none', ...(ngDevMode ? [{ debugName: "moveLoop" }] : /* istanbul ignore next */ []));
2415
+ moveDuration = input(undefined, ...(ngDevMode ? [{ debugName: "moveDuration" }] : /* istanbul ignore next */ []));
2416
+ moveEasing = input(undefined, ...(ngDevMode ? [{ debugName: "moveEasing" }] : /* istanbul ignore next */ []));
2417
+ moveDelay = input(undefined, ...(ngDevMode ? [{ debugName: "moveDelay" }] : /* istanbul ignore next */ []));
2418
+ moveDisabled = input(undefined, ...(ngDevMode ? [{ debugName: "moveDisabled" }] : /* istanbul ignore next */ []));
2419
+ moveSpring = input(undefined, ...(ngDevMode ? [{ debugName: "moveSpring" }] : /* istanbul ignore next */ []));
2420
+ #defaults = inject(MOVEMENT_CONFIG);
2421
+ #documentRef = inject(DOCUMENT);
2422
+ #host = inject((ElementRef));
2423
+ #engine = inject(AnimationEngine);
2424
+ #player = null;
2425
+ ngOnInit() {
2426
+ const frames = resolveMoveFrames(this.moveLoop(), 'loop');
2427
+ // Skip noop presets to avoid creating an infinite animation that does nothing
2428
+ if (this.moveLoop() === 'none' || Object.keys(frames).length === 0) {
2429
+ return;
2430
+ }
2431
+ const isReduced = prefersReducedMotion(this.#documentRef);
2432
+ const config = resolveMovementConfig(this.#defaults, {
2433
+ duration: this.moveDuration(),
2434
+ easing: this.moveEasing(),
2435
+ delay: this.moveDelay(),
2436
+ disabled: this.moveDisabled(),
2437
+ }, isReduced);
2438
+ this.#player = this.#engine.play(this.#host.nativeElement, frames, {
2439
+ config,
2440
+ spring: this.moveSpring(),
2441
+ disabled: config.disabled,
2442
+ iterations: Infinity,
2443
+ });
2444
+ }
2445
+ ngOnDestroy() {
2446
+ this.#player?.cancel();
2447
+ }
2448
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveLoopDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2449
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.2", type: MoveLoopDirective, isStandalone: true, selector: "[moveLoop]", inputs: { moveLoop: { classPropertyName: "moveLoop", publicName: "moveLoop", 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 });
2450
+ }
2451
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveLoopDirective, decorators: [{
2452
+ type: Directive,
2453
+ args: [{
2454
+ selector: '[moveLoop]',
2455
+ }]
2456
+ }], propDecorators: { moveLoop: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveLoop", 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 }] }] } });
2457
+
2458
+ function optionalNumberAttribute(value) {
2459
+ if (value === undefined || value === null || value === '') {
2460
+ return undefined;
2461
+ }
2462
+ return Number(value);
2463
+ }
2464
+ class MoveTargetDirective {
2465
+ moveTarget = input.required(...(ngDevMode ? [{ debugName: "moveTarget" }] : /* istanbul ignore next */ []));
2466
+ moveFrames = input.required(...(ngDevMode ? [{ debugName: "moveFrames" }] : /* istanbul ignore next */ []));
2467
+ moveDuration = input(undefined, { ...(ngDevMode ? { debugName: "moveDuration" } : /* istanbul ignore next */ {}), transform: optionalNumberAttribute });
2468
+ moveEasing = input(undefined, ...(ngDevMode ? [{ debugName: "moveEasing" }] : /* istanbul ignore next */ []));
2469
+ moveDelay = input(undefined, { ...(ngDevMode ? { debugName: "moveDelay" } : /* istanbul ignore next */ {}), transform: optionalNumberAttribute });
2470
+ moveSpring = input(undefined, ...(ngDevMode ? [{ debugName: "moveSpring" }] : /* istanbul ignore next */ []));
2471
+ moveDisabled = input(undefined, ...(ngDevMode ? [{ debugName: "moveDisabled" }] : /* istanbul ignore next */ []));
2472
+ moveReverseDuration = input(undefined, { ...(ngDevMode ? { debugName: "moveReverseDuration" } : /* istanbul ignore next */ {}), transform: optionalNumberAttribute });
2473
+ moveReverseEasing = input(undefined, ...(ngDevMode ? [{ debugName: "moveReverseEasing" }] : /* istanbul ignore next */ []));
2474
+ #defaults = inject(MOVEMENT_CONFIG);
2475
+ #documentRef = inject(DOCUMENT);
2476
+ #host = inject((ElementRef));
2477
+ #engine = inject(AnimationEngine);
2478
+ #currentPlayer = null;
2479
+ #hasPlayedForward = false;
2480
+ #targetEffect = effect(() => {
2481
+ const active = this.moveTarget();
2482
+ const frames = this.moveFrames();
2483
+ if (active) {
2484
+ this.#playForward(frames);
2485
+ this.#hasPlayedForward = true;
2486
+ return;
2487
+ }
2488
+ if (this.#hasPlayedForward) {
2489
+ this.#playReverse(frames);
2490
+ }
2491
+ }, ...(ngDevMode ? [{ debugName: "#targetEffect" }] : /* istanbul ignore next */ []));
2492
+ #playForward(frames) {
2493
+ this.#currentPlayer?.cancel();
2494
+ const isReduced = prefersReducedMotion(this.#documentRef);
2495
+ const config = resolveMovementConfig(this.#defaults, {
2496
+ duration: this.moveDuration(),
2497
+ easing: this.moveEasing(),
2498
+ delay: this.moveDelay(),
2499
+ disabled: this.moveDisabled(),
2500
+ }, isReduced);
2501
+ this.#currentPlayer = this.#engine.play(this.#host.nativeElement, frames, {
2502
+ config,
2503
+ spring: this.moveSpring(),
2504
+ disabled: config.disabled,
2505
+ });
2506
+ }
2507
+ #playReverse(frames) {
2508
+ this.#currentPlayer?.cancel();
2509
+ const isReduced = prefersReducedMotion(this.#documentRef);
2510
+ const config = resolveMovementConfig({ ...this.#defaults, duration: 200, easing: 'ease-out', delay: 0 }, {
2511
+ duration: this.moveReverseDuration() ?? this.moveDuration(),
2512
+ easing: this.moveReverseEasing(),
2513
+ delay: 0,
2514
+ disabled: this.moveDisabled(),
2515
+ }, isReduced);
2516
+ this.#currentPlayer = this.#engine.play(this.#host.nativeElement, reverseFrames(frames), {
2517
+ config,
2518
+ spring: this.moveSpring(),
2519
+ disabled: config.disabled,
2520
+ });
2521
+ }
2522
+ ngOnDestroy() {
2523
+ this.#targetEffect.destroy();
2524
+ this.#currentPlayer?.cancel();
2525
+ }
2526
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveTargetDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2527
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.2", type: MoveTargetDirective, isStandalone: true, selector: "[moveTarget]", inputs: { moveTarget: { classPropertyName: "moveTarget", publicName: "moveTarget", isSignal: true, isRequired: true, transformFunction: null }, moveFrames: { classPropertyName: "moveFrames", publicName: "moveFrames", 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 }, moveSpring: { classPropertyName: "moveSpring", publicName: "moveSpring", isSignal: true, isRequired: false, transformFunction: null }, moveDisabled: { classPropertyName: "moveDisabled", publicName: "moveDisabled", isSignal: true, isRequired: false, transformFunction: null }, moveReverseDuration: { classPropertyName: "moveReverseDuration", publicName: "moveReverseDuration", isSignal: true, isRequired: false, transformFunction: null }, moveReverseEasing: { classPropertyName: "moveReverseEasing", publicName: "moveReverseEasing", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
2528
+ }
2529
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MoveTargetDirective, decorators: [{
2530
+ type: Directive,
2531
+ args: [{
2532
+ selector: '[moveTarget]',
2533
+ standalone: true,
2534
+ }]
2535
+ }], propDecorators: { moveTarget: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveTarget", required: true }] }], moveFrames: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveFrames", 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 }] }], moveSpring: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveSpring", required: false }] }], moveDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveDisabled", required: false }] }], moveReverseDuration: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveReverseDuration", required: false }] }], moveReverseEasing: [{ type: i0.Input, args: [{ isSignal: true, alias: "moveReverseEasing", required: false }] }] } });
2536
+
2149
2537
  function provideMovement(config = {}) {
2150
2538
  return makeEnvironmentProviders([
2151
2539
  {
@@ -2176,6 +2564,8 @@ const MOVEMENT_DIRECTIVES = [
2176
2564
  MoveFocusDirective,
2177
2565
  MoveParallaxDirective,
2178
2566
  MoveAnimationDirective,
2567
+ MoveLoopDirective,
2568
+ MoveTargetDirective,
2179
2569
  ];
2180
2570
 
2181
2571
  /*
@@ -2186,5 +2576,5 @@ const MOVEMENT_DIRECTIVES = [
2186
2576
  * Generated bundle index. Do not edit.
2187
2577
  */
2188
2578
 
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 };
2579
+ 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, MoveLoopDirective, MoveParallaxDirective, MovePresenceDirective, MoveScrollDirective, MoveSmoothScrollDirective, MoveStaggerDirective, MoveTapDirective, MoveTargetDirective, MoveTextDirective, MoveVariantsDirective, SmoothScrollService, SpringPlayer, WaapiPlayer, applyInitialStyles, clearInitialStyles, isValidScrollOffset, prefersReducedMotion, provideMovement, resolveMoveFrames, resolveMovementConfig, reverseFrames, validateDragElastic, validateSpring };
2190
2580
  //# sourceMappingURL=angular-movement.mjs.map