astro-magic-move 0.1.2 → 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.2",
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",
@@ -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");
@@ -104,10 +120,10 @@ const stepsJson = JSON.stringify(compiledSteps);
104
120
  });
105
121
 
106
122
  if (trigger === "auto") {
107
- this.currentStep = -1;
123
+ this._currentStep = -1;
108
124
  } else {
109
- this.renderer.replace(this.steps[0]);
110
- this.currentStep = 0;
125
+ this.renderer.replace(this._steps[0]);
126
+ this._currentStep = 0;
111
127
  }
112
128
  this.setupTrigger(trigger, threshold);
113
129
  }
@@ -124,13 +140,15 @@ const stepsJson = JSON.stringify(compiledSteps);
124
140
  case "click":
125
141
  this.style.cursor = "pointer";
126
142
  this.addEventListener("click", () => {
127
- const next = (this.currentStep + 1) % this.steps.length;
143
+ const next = (this._currentStep + 1) % this._steps.length;
128
144
  this.animateToStep(next);
129
145
  });
130
146
  break;
131
147
  case "auto":
132
148
  requestAnimationFrame(() => this.autoPlay());
133
149
  break;
150
+ case "none":
151
+ break;
134
152
  }
135
153
  }
136
154
 
@@ -148,28 +166,43 @@ const stepsJson = JSON.stringify(compiledSteps);
148
166
  }
149
167
 
150
168
  private async autoPlay() {
151
- const duration = Number(this.dataset.duration ?? 800);
152
- for (let i = 0; i < this.steps.length; i++) {
153
- await this.animateToStep(i);
154
- if (i < this.steps.length - 1) {
155
- await new Promise((r) => setTimeout(r, duration + 200));
156
- }
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));
157
174
  }
158
175
  }
176
+ }
159
177
 
160
- private async animateToStep(stepIndex: number) {
161
- if (!this.renderer || stepIndex === this.currentStep) return;
162
- if (stepIndex < 0 || stepIndex >= this.steps.length) return;
178
+ private async animateToStep(stepIndex: number) {
179
+ if (!this.renderer || stepIndex === this._currentStep) return;
180
+ if (stepIndex < 0 || stepIndex >= this._steps.length) return;
163
181
 
164
- await this.renderer.render(this.steps[stepIndex]);
165
- this.currentStep = stepIndex;
182
+ if (this._animating) {
183
+ this._pendingStep = stepIndex;
184
+ return;
185
+ }
166
186
 
167
- this.dispatchEvent(
168
- new CustomEvent("magic-move:step", {
169
- detail: { step: stepIndex, total: this.steps.length },
170
- bubbles: true,
171
- }),
172
- );
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
+ }
173
206
  }
174
207
  }
175
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;