astro-magic-move 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-magic-move",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Animated code morphing for Astro, powered by Shiki Magic Move. Build-time tokenization, zero-framework client JS, CSS-variable theming.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -34,4 +34,4 @@
34
34
  "shiki": "^3.0.0",
35
35
  "shiki-magic-move": "^1.3.0"
36
36
  }
37
- }
37
+ }
@@ -76,14 +76,30 @@ const stepsJson = JSON.stringify(compiledSteps);
76
76
 
77
77
  class MagicMoveElement extends HTMLElement {
78
78
  private renderer: MagicMoveRenderer | null = null;
79
- private steps: any[] = [];
80
- private currentStep = 0;
79
+ private _steps: any[] = [];
80
+ private _currentStep = 0;
81
+ private _animating = false;
82
+ private _pendingStep: number | null = null;
81
83
  private observer: IntersectionObserver | null = null;
82
84
 
85
+ get step(): number {
86
+ return this._currentStep;
87
+ }
88
+
89
+ set step(value: number) {
90
+ const idx = Number(value);
91
+ if (Number.isNaN(idx)) return;
92
+ this.animateToStep(idx);
93
+ }
94
+
95
+ get totalSteps(): number {
96
+ return this._steps.length;
97
+ }
98
+
83
99
  connectedCallback() {
84
100
  const dataScript = this.querySelector('script[type="application/json"]');
85
101
  if (!dataScript?.textContent) return;
86
- this.steps = JSON.parse(dataScript.textContent);
102
+ this._steps = JSON.parse(dataScript.textContent);
87
103
  dataScript.remove();
88
104
 
89
105
  const container = this.querySelector("pre");
@@ -103,9 +119,12 @@ const stepsJson = JSON.stringify(compiledSteps);
103
119
  animateContainer: true,
104
120
  });
105
121
 
106
- // replace() for instant initial display (no animation, no empty frame)
107
- this.renderer.replace(this.steps[0]);
108
- this.currentStep = 0;
122
+ if (trigger === "auto") {
123
+ this._currentStep = -1;
124
+ } else {
125
+ this.renderer.replace(this._steps[0]);
126
+ this._currentStep = 0;
127
+ }
109
128
  this.setupTrigger(trigger, threshold);
110
129
  }
111
130
 
@@ -121,14 +140,14 @@ const stepsJson = JSON.stringify(compiledSteps);
121
140
  case "click":
122
141
  this.style.cursor = "pointer";
123
142
  this.addEventListener("click", () => {
124
- const next = (this.currentStep + 1) % this.steps.length;
143
+ const next = (this._currentStep + 1) % this._steps.length;
125
144
  this.animateToStep(next);
126
145
  });
127
146
  break;
128
147
  case "auto":
129
- requestAnimationFrame(() => {
130
- this.animateToStep(1);
131
- });
148
+ requestAnimationFrame(() => this.autoPlay());
149
+ break;
150
+ case "none":
132
151
  break;
133
152
  }
134
153
  }
@@ -146,19 +165,44 @@ const stepsJson = JSON.stringify(compiledSteps);
146
165
  this.observer.observe(this);
147
166
  }
148
167
 
168
+ private async autoPlay() {
169
+ const duration = Number(this.dataset.duration ?? 800);
170
+ for (let i = 0; i < this._steps.length; i++) {
171
+ await this.animateToStep(i);
172
+ if (i < this._steps.length - 1) {
173
+ await new Promise((r) => setTimeout(r, duration + 200));
174
+ }
175
+ }
176
+ }
177
+
149
178
  private async animateToStep(stepIndex: number) {
150
- if (!this.renderer || stepIndex === this.currentStep) return;
151
- if (stepIndex < 0 || stepIndex >= this.steps.length) return;
179
+ if (!this.renderer || stepIndex === this._currentStep) return;
180
+ if (stepIndex < 0 || stepIndex >= this._steps.length) return;
152
181
 
153
- await this.renderer.render(this.steps[stepIndex]);
154
- this.currentStep = stepIndex;
182
+ if (this._animating) {
183
+ this._pendingStep = stepIndex;
184
+ return;
185
+ }
155
186
 
156
- this.dispatchEvent(
157
- new CustomEvent("magic-move:step", {
158
- detail: { step: stepIndex, total: this.steps.length },
159
- bubbles: true,
160
- }),
161
- );
187
+ this._animating = true;
188
+ try {
189
+ await this.renderer.render(this._steps[stepIndex]);
190
+ this._currentStep = stepIndex;
191
+ this.dispatchEvent(
192
+ new CustomEvent("magic-move:step", {
193
+ detail: { step: stepIndex, total: this._steps.length },
194
+ bubbles: true,
195
+ }),
196
+ );
197
+ } finally {
198
+ this._animating = false;
199
+ }
200
+
201
+ if (this._pendingStep !== null) {
202
+ const next = this._pendingStep;
203
+ this._pendingStep = null;
204
+ this.animateToStep(next);
205
+ }
162
206
  }
163
207
  }
164
208
 
package/src/types.ts CHANGED
@@ -20,8 +20,9 @@ export interface MagicMoveProps {
20
20
  * - `'scroll'`: animate when element enters viewport (default)
21
21
  * - `'click'`: toggle forward on click, wraps around
22
22
  * - `'auto'`: animate immediately when the element mounts
23
+ * - `'none'`: no built-in trigger; control steps externally via the element's `step` property
23
24
  */
24
- trigger?: "scroll" | "click" | "auto";
25
+ trigger?: "scroll" | "click" | "auto" | "none";
25
26
 
26
27
  /** Animation duration in ms. Default: `800` */
27
28
  duration?: number;