@varianta/sdk 0.1.6 → 0.1.8

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/dist/index.esm.js CHANGED
@@ -78,12 +78,12 @@ class N {
78
78
  */
79
79
  setBackgroundImage(e) {
80
80
  if (this.backgroundImage = e, e?.url && e.url !== this.bgImageElement?.src) {
81
- const t = new Image();
82
- t.crossOrigin = "anonymous", t.onload = () => {
83
- this.bgImageElement = t, this.requestRender();
84
- }, t.onerror = () => {
85
- this.bgImageElement = null, console.error("Failed to load background image:", e.url), this.requestRender();
86
- }, t.src = e.url;
81
+ const t = e.url, i = new Image();
82
+ i.crossOrigin = "anonymous", i.onload = () => {
83
+ this.bgImageElement = i, this.requestRender();
84
+ }, i.onerror = () => {
85
+ this.bgImageElement = null, console.error("Failed to load background image:", t), this.requestRender();
86
+ }, i.src = t;
87
87
  } else e?.url || (this.bgImageElement = null);
88
88
  this.requestRender();
89
89
  }
@@ -129,9 +129,9 @@ class N {
129
129
  getCenteredPan(e) {
130
130
  if (!this.artboard)
131
131
  return null;
132
- const { width: t, height: i } = this.artboard, s = window.devicePixelRatio || 1, a = this.canvas.width / s, o = this.canvas.height / s;
132
+ const { width: t, height: i } = this.artboard, s = window.devicePixelRatio || 1, n = this.canvas.width / s, o = this.canvas.height / s;
133
133
  return {
134
- x: (a / e - t) / 2,
134
+ x: (n / e - t) / 2,
135
135
  y: (o / e - i) / 2
136
136
  };
137
137
  }
@@ -147,11 +147,11 @@ class N {
147
147
  fitToView() {
148
148
  if (!this.artboard)
149
149
  return;
150
- const e = 0, { width: t, height: i } = this.artboard, s = window.devicePixelRatio || 1, a = this.canvas.width / s - e * 2, o = this.canvas.height / s - e * 2;
151
- if (a <= 0 || o <= 0)
150
+ const e = 0, { width: t, height: i } = this.artboard, s = window.devicePixelRatio || 1, n = this.canvas.width / s - e * 2, o = this.canvas.height / s - e * 2;
151
+ if (n <= 0 || o <= 0)
152
152
  return;
153
- const n = a / t, r = o / i, d = Math.min(n, r), l = this.canvas.width / s, c = this.canvas.height / s, u = (l / d - t) / 2, p = (c / d - i) / 2;
154
- this.zoom = d, this.pan = { x: u, y: p }, this.requestRender();
153
+ const a = n / t, r = o / i, d = Math.min(a, r), l = this.canvas.width / s, c = this.canvas.height / s, p = (l / d - t) / 2, u = (c / d - i) / 2;
154
+ this.zoom = d, this.pan = { x: p, y: u }, this.requestRender();
155
155
  }
156
156
  /**
157
157
  * Set the selected area
@@ -181,8 +181,8 @@ class N {
181
181
  * Convert screen coordinates to canvas coordinates
182
182
  */
183
183
  screenToCanvas(e, t) {
184
- const i = window.devicePixelRatio || 1, s = e / i, a = t / i, o = s / this.zoom - this.pan.x, n = a / this.zoom - this.pan.y;
185
- return { x: o, y: n };
184
+ const i = window.devicePixelRatio || 1, s = e / i, n = t / i, o = s / this.zoom - this.pan.x, a = n / this.zoom - this.pan.y;
185
+ return { x: o, y: a };
186
186
  }
187
187
  /**
188
188
  * Get content bounds for an area (in artboard coordinates)
@@ -194,14 +194,14 @@ class N {
194
194
  const i = this.contents.get(e);
195
195
  if (!i)
196
196
  return null;
197
- const { location: s } = t, { x: a, y: o, width: n, height: r } = s;
197
+ const { location: s } = t, { x: n, y: o, width: a, height: r } = s;
198
198
  if (i.type === "text") {
199
- const d = i.offset || { x: 0, y: 0 }, l = i.scale ?? 1, c = i.rotation ?? 0, u = n * l, p = r * l, g = a + n / 2 + d.x, m = o + r / 2 + d.y;
199
+ const d = i.offset || { x: 0, y: 0 }, l = i.scale ?? 1, c = i.rotation ?? 0, p = a * l, u = r * l, g = n + a / 2 + d.x, m = o + r / 2 + d.y;
200
200
  return {
201
- x: g - u / 2,
202
- y: m - p / 2,
203
- width: u,
204
- height: p,
201
+ x: g - p / 2,
202
+ y: m - u / 2,
203
+ width: p,
204
+ height: u,
205
205
  rotation: c,
206
206
  centerX: g,
207
207
  centerY: m
@@ -210,18 +210,18 @@ class N {
210
210
  const d = this.loadedImages.get(e);
211
211
  if (!d)
212
212
  return null;
213
- const l = i.offset || { x: 0, y: 0 }, c = i.scale ?? 1, u = i.rotation ?? 0, p = d.naturalWidth / d.naturalHeight, g = n / r;
213
+ const l = i.offset || { x: 0, y: 0 }, c = i.scale ?? 1, p = i.rotation ?? 0, u = d.naturalWidth / d.naturalHeight, g = a / r;
214
214
  let m, v;
215
- p > g ? (m = n, v = n / p) : (v = r, m = r * p);
216
- const b = m * c, y = v * c, x = a + n / 2 + l.x, w = o + r / 2 + l.y;
215
+ u > g ? (m = a, v = a / u) : (v = r, m = r * u);
216
+ const b = m * c, w = v * c, y = n + a / 2 + l.x, x = o + r / 2 + l.y;
217
217
  return {
218
- x: x - b / 2,
219
- y: w - y / 2,
218
+ x: y - b / 2,
219
+ y: x - w / 2,
220
220
  width: b,
221
- height: y,
222
- rotation: u,
223
- centerX: x,
224
- centerY: w
221
+ height: w,
222
+ rotation: p,
223
+ centerX: y,
224
+ centerY: x
225
225
  };
226
226
  }
227
227
  return null;
@@ -234,8 +234,8 @@ class N {
234
234
  if (!this.areaSelectionEnabled)
235
235
  return null;
236
236
  for (let i = this.areas.length - 1; i >= 0; i--) {
237
- const s = this.areas[i], a = this.getContentBounds(s.id);
238
- if (a && this.isPointInBounds(e, t, a))
237
+ const s = this.areas[i], n = this.getContentBounds(s.id);
238
+ if (n && this.isPointInBounds(e, t, n))
239
239
  return s.id;
240
240
  }
241
241
  return null;
@@ -248,8 +248,8 @@ class N {
248
248
  if (!this.areaSelectionEnabled)
249
249
  return null;
250
250
  for (let i = this.areas.length - 1; i >= 0; i--) {
251
- const s = this.areas[i], { x: a, y: o, width: n, height: r } = s.location;
252
- if (e >= a && e <= a + n && t >= o && t <= o + r)
251
+ const s = this.areas[i], { x: n, y: o, width: a, height: r } = s.location;
252
+ if (e >= n && e <= n + a && t >= o && t <= o + r)
253
253
  return s.id;
254
254
  }
255
255
  return null;
@@ -258,8 +258,8 @@ class N {
258
258
  * Test if a point is inside rotated bounds
259
259
  */
260
260
  isPointInBounds(e, t, i) {
261
- const { centerX: s, centerY: a, width: o, height: n, rotation: r } = i, d = -r * Math.PI / 180, l = Math.cos(d), c = Math.sin(d), u = e - s, p = t - a, g = u * l - p * c, m = u * c + p * l;
262
- return g >= -o / 2 && g <= o / 2 && m >= -n / 2 && m <= n / 2;
261
+ const { centerX: s, centerY: n, width: o, height: a, rotation: r } = i, d = -r * Math.PI / 180, l = Math.cos(d), c = Math.sin(d), p = e - s, u = t - n, g = p * l - u * c, m = p * c + u * l;
262
+ return g >= -o / 2 && g <= o / 2 && m >= -a / 2 && m <= a / 2;
263
263
  }
264
264
  /**
265
265
  * Get handle positions for the selected content
@@ -274,19 +274,19 @@ class N {
274
274
  const s = this.getContentBounds(e);
275
275
  if (!s)
276
276
  return null;
277
- const { centerX: a, centerY: o, width: n, height: r, rotation: d } = s, l = 6 / this.zoom, c = 30 / this.zoom, u = d * Math.PI / 180, p = Math.cos(u), g = Math.sin(u), m = (b, y) => ({
278
- x: a + b * p - y * g,
279
- y: o + b * g + y * p
277
+ const { centerX: n, centerY: o, width: a, height: r, rotation: d } = s, l = 6 / this.zoom, c = 30 / this.zoom, p = d * Math.PI / 180, u = Math.cos(p), g = Math.sin(p), m = (b, w) => ({
278
+ x: n + b * u - w * g,
279
+ y: o + b * g + w * u
280
280
  }), v = [];
281
281
  if (i.type === "image" && t.imageOptions?.allowScaling || i.type === "text" && t.textOptions?.allowScaling) {
282
- const b = n / 2, y = r / 2, x = m(-b, -y);
283
- v.push({ type: "nw", ...x, radius: l });
284
- const w = m(b, -y);
285
- v.push({ type: "ne", ...w, radius: l });
286
- const C = m(b, y);
282
+ const b = a / 2, w = r / 2, y = m(-b, -w);
283
+ v.push({ type: "nw", ...y, radius: l });
284
+ const x = m(b, -w);
285
+ v.push({ type: "ne", ...x, radius: l });
286
+ const C = m(b, w);
287
287
  v.push({ type: "se", ...C, radius: l });
288
- const z = m(-b, y);
289
- v.push({ type: "sw", ...z, radius: l });
288
+ const M = m(-b, w);
289
+ v.push({ type: "sw", ...M, radius: l });
290
290
  }
291
291
  if (i.type === "image" && t.imageOptions?.allowRotation || i.type === "text" && t.textOptions?.allowRotation) {
292
292
  const b = m(0, -r / 2 - c);
@@ -301,10 +301,10 @@ class N {
301
301
  const s = this.getContentHandlePositions(i);
302
302
  if (!s)
303
303
  return null;
304
- for (const a of s) {
305
- const o = e - a.x, n = t - a.y;
306
- if (Math.sqrt(o * o + n * n) <= a.radius * 2)
307
- return a.type;
304
+ for (const n of s) {
305
+ const o = e - n.x, a = t - n.y;
306
+ if (Math.sqrt(o * o + a * a) <= n.radius * 2)
307
+ return n.type;
308
308
  }
309
309
  return null;
310
310
  }
@@ -344,15 +344,15 @@ class N {
344
344
  checkSafeAreaViolations() {
345
345
  if (!this.safeArea || !this.artboard)
346
346
  return [];
347
- const e = [], { top: t, right: i, bottom: s, left: a } = this.safeArea, o = a, n = t, r = this.artboard.width - i, d = this.artboard.height - s;
347
+ const e = [], { top: t, right: i, bottom: s, left: n } = this.safeArea, o = n, a = t, r = this.artboard.width - i, d = this.artboard.height - s;
348
348
  for (const l of this.areas) {
349
349
  if (!this.contents.get(l.id))
350
350
  continue;
351
351
  const c = this.getContentBounds(l.id);
352
352
  if (!c)
353
353
  continue;
354
- const { centerX: u, centerY: p, width: g, height: m, rotation: v } = c, b = v * Math.PI / 180, y = Math.abs(Math.cos(b)), x = Math.abs(Math.sin(b)), w = g * y + m * x, C = g * x + m * y, z = u - w / 2, A = p - C / 2, D = u + w / 2, R = p + C / 2;
355
- (z < o || A < n || D > r || R > d) && e.push(l.id);
354
+ const { centerX: p, centerY: u, width: g, height: m, rotation: v } = c, b = v * Math.PI / 180, w = Math.abs(Math.cos(b)), y = Math.abs(Math.sin(b)), x = g * w + m * y, C = g * y + m * w, M = p - x / 2, R = u - C / 2, U = p + x / 2, D = u + C / 2;
355
+ (M < o || R < a || U > r || D > d) && e.push(l.id);
356
356
  }
357
357
  return this.safeAreaViolations = new Set(e), this.requestRender(), e;
358
358
  }
@@ -381,16 +381,20 @@ class N {
381
381
  throw new Error("No artboard configured");
382
382
  const { width: e, height: t, backgroundColor: i } = this.artboard, s = document.createElement("canvas");
383
383
  s.width = e, s.height = t;
384
- const a = s.getContext("2d");
385
- if (!a)
384
+ const n = s.getContext("2d");
385
+ if (!n)
386
386
  throw new Error("Failed to get 2D context for export");
387
- a.fillStyle = i, a.fillRect(0, 0, e, t), this.backgroundImage?.position === "behind" && this.bgImageElement && this.drawBackgroundImageOnContext(a, e, t);
387
+ n.fillStyle = i, n.fillRect(0, 0, e, t), this.backgroundImage?.position === "behind" && this.bgImageElement && this.drawBackgroundImageOnContext(n, e, t);
388
388
  for (const o of this.areas)
389
- this.drawAreaContentOnContext(a, o);
390
- return this.backgroundImage?.position === "overlay" && this.bgImageElement && this.drawBackgroundImageOnContext(a, e, t), new Promise((o, n) => {
391
- s.toBlob((r) => {
392
- r ? o(r) : n(new Error("Failed to create PNG blob"));
393
- }, "image/png", 1);
389
+ this.drawAreaContentOnContext(n, o);
390
+ return this.backgroundImage?.position === "overlay" && this.bgImageElement && this.drawBackgroundImageOnContext(n, e, t), new Promise((o, a) => {
391
+ try {
392
+ s.toBlob((r) => {
393
+ r ? o(r) : a(new Error("Failed to create PNG blob"));
394
+ }, "image/png", 1);
395
+ } catch (r) {
396
+ r instanceof DOMException && r.name === "SecurityError" ? a(new Error("Cannot export: images failed to load with CORS. Check CDN configuration.")) : a(r);
397
+ }
394
398
  });
395
399
  }
396
400
  /**
@@ -401,7 +405,7 @@ class N {
401
405
  if (t.type === "image" && !this.loadedImages.has(e) && !this.loadingImages.has(e)) {
402
406
  this.loadingImages.add(e);
403
407
  const i = new Image();
404
- i.onload = () => {
408
+ i.crossOrigin = "anonymous", i.onload = () => {
405
409
  this.loadedImages.set(e, i), this.loadingImages.delete(e), this.requestRender();
406
410
  }, i.onerror = () => {
407
411
  this.loadingImages.delete(e), console.error("Failed to load image for area:", e);
@@ -422,17 +426,17 @@ class N {
422
426
  e.save();
423
427
  const i = window.devicePixelRatio || 1;
424
428
  e.scale(i, i), e.scale(this.zoom, this.zoom), e.translate(this.pan.x, this.pan.y);
425
- const { width: s, height: a, backgroundColor: o } = this.artboard;
426
- e.fillStyle = o, e.fillRect(0, 0, s, a), e.strokeStyle = "#d1d5db", e.lineWidth = 1 / this.zoom, e.strokeRect(0, 0, s, a), this.backgroundImage?.position === "behind" && this.renderBackgroundImage();
427
- for (const n of this.areas)
428
- this.renderAreaWithContent(n);
429
+ const { width: s, height: n, backgroundColor: o } = this.artboard;
430
+ e.fillStyle = o, e.fillRect(0, 0, s, n), e.strokeStyle = "#d1d5db", e.lineWidth = 1 / this.zoom, e.strokeRect(0, 0, s, n), this.backgroundImage?.position === "behind" && this.renderBackgroundImage();
431
+ for (const a of this.areas)
432
+ this.renderAreaWithContent(a);
429
433
  this.backgroundImage?.position === "overlay" && this.renderBackgroundImage(), this.renderSafeAreaGuide();
430
- for (const n of this.safeAreaViolations) {
431
- const r = this.areas.find((p) => p.id === n);
434
+ for (const a of this.safeAreaViolations) {
435
+ const r = this.areas.find((u) => u.id === a);
432
436
  if (!r)
433
437
  continue;
434
- const { x: d, y: l, width: c, height: u } = r.location;
435
- e.save(), e.fillStyle = "rgba(239, 68, 68, 0.15)", e.fillRect(d, l, c, u), e.restore();
438
+ const { x: d, y: l, width: c, height: p } = r.location;
439
+ e.save(), e.fillStyle = "rgba(239, 68, 68, 0.15)", e.fillRect(d, l, c, p), e.restore();
436
440
  }
437
441
  this.selectedAreaId && this.renderSelectionOverlay(this.selectedAreaId), e.restore();
438
442
  }
@@ -445,33 +449,33 @@ class N {
445
449
  const l = this.areas.find((v) => v.id === e);
446
450
  if (!l)
447
451
  return;
448
- const { ctx: c } = this, { x: u, y: p, width: g, height: m } = l.location;
449
- c.save(), c.strokeStyle = "#3b82f6", c.lineWidth = 2 / this.zoom, c.setLineDash([]), c.strokeRect(u, p, g, m), c.restore();
452
+ const { ctx: c } = this, { x: p, y: u, width: g, height: m } = l.location;
453
+ c.save(), c.strokeStyle = "#3b82f6", c.lineWidth = 2 / this.zoom, c.setLineDash([]), c.strokeRect(p, u, g, m), c.restore();
450
454
  return;
451
455
  }
452
- const { ctx: i } = this, { centerX: s, centerY: a, width: o, height: n, rotation: r } = t;
453
- i.save(), i.translate(s, a), i.rotate(r * Math.PI / 180), i.strokeStyle = "#3b82f6", i.lineWidth = 2 / this.zoom, i.setLineDash([]), i.strokeRect(-o / 2, -n / 2, o, n), i.restore();
456
+ const { ctx: i } = this, { centerX: s, centerY: n, width: o, height: a, rotation: r } = t;
457
+ i.save(), i.translate(s, n), i.rotate(r * Math.PI / 180), i.strokeStyle = "#3b82f6", i.lineWidth = 2 / this.zoom, i.setLineDash([]), i.strokeRect(-o / 2, -a / 2, o, a), i.restore();
454
458
  const d = this.getContentHandlePositions(e);
455
459
  if (d)
456
460
  for (const l of d) {
457
461
  i.save();
458
- const c = this.hoveredHandle === l.type, u = c ? "#dbeafe" : "#ffffff", p = c ? "#2563eb" : "#3b82f6";
462
+ const c = this.hoveredHandle === l.type, p = c ? "#dbeafe" : "#ffffff", u = c ? "#2563eb" : "#3b82f6";
459
463
  if (l.type === "rotate") {
460
- const g = this.getRotatedPoint(s, a - n / 2, s, a, r);
461
- i.beginPath(), i.strokeStyle = p, i.lineWidth = 1 / this.zoom, i.setLineDash([4 / this.zoom, 4 / this.zoom]), i.moveTo(g.x, g.y), i.lineTo(l.x, l.y), i.stroke(), i.setLineDash([]), i.beginPath(), i.fillStyle = u, i.strokeStyle = p, i.lineWidth = 2 / this.zoom, i.arc(l.x, l.y, l.radius, 0, Math.PI * 2), i.fill(), i.stroke(), i.beginPath(), i.strokeStyle = p, i.lineWidth = 1.5 / this.zoom, i.arc(l.x, l.y, l.radius * 0.5, -Math.PI * 0.8, Math.PI * 0.5), i.stroke();
464
+ const g = this.getRotatedPoint(s, n - a / 2, s, n, r);
465
+ i.beginPath(), i.strokeStyle = u, i.lineWidth = 1 / this.zoom, i.setLineDash([4 / this.zoom, 4 / this.zoom]), i.moveTo(g.x, g.y), i.lineTo(l.x, l.y), i.stroke(), i.setLineDash([]), i.beginPath(), i.fillStyle = p, i.strokeStyle = u, i.lineWidth = 2 / this.zoom, i.arc(l.x, l.y, l.radius, 0, Math.PI * 2), i.fill(), i.stroke(), i.beginPath(), i.strokeStyle = u, i.lineWidth = 1.5 / this.zoom, i.arc(l.x, l.y, l.radius * 0.5, -Math.PI * 0.8, Math.PI * 0.5), i.stroke();
462
466
  } else
463
- i.fillStyle = u, i.strokeStyle = p, i.lineWidth = 2 / this.zoom, i.fillRect(l.x - l.radius, l.y - l.radius, l.radius * 2, l.radius * 2), i.strokeRect(l.x - l.radius, l.y - l.radius, l.radius * 2, l.radius * 2);
467
+ i.fillStyle = p, i.strokeStyle = u, i.lineWidth = 2 / this.zoom, i.fillRect(l.x - l.radius, l.y - l.radius, l.radius * 2, l.radius * 2), i.strokeRect(l.x - l.radius, l.y - l.radius, l.radius * 2, l.radius * 2);
464
468
  i.restore();
465
469
  }
466
470
  }
467
471
  /**
468
472
  * Rotate a point around a center
469
473
  */
470
- getRotatedPoint(e, t, i, s, a) {
471
- const o = a * Math.PI / 180, n = Math.cos(o), r = Math.sin(o), d = e - i, l = t - s;
474
+ getRotatedPoint(e, t, i, s, n) {
475
+ const o = n * Math.PI / 180, a = Math.cos(o), r = Math.sin(o), d = e - i, l = t - s;
472
476
  return {
473
- x: i + d * n - l * r,
474
- y: s + d * r + l * n
477
+ x: i + d * a - l * r,
478
+ y: s + d * r + l * a
475
479
  };
476
480
  }
477
481
  /**
@@ -482,9 +486,9 @@ class N {
482
486
  return;
483
487
  const { ctx: e } = this, { opacity: t } = this.backgroundImage, { width: i, height: s } = this.artboard;
484
488
  e.save(), e.globalAlpha = t;
485
- const a = this.bgImageElement.naturalWidth / this.bgImageElement.naturalHeight, o = i / s;
486
- let n, r, d, l;
487
- a > o ? (r = s, n = s * a, d = (i - n) / 2, l = 0) : (n = i, r = i / a, d = 0, l = (s - r) / 2), e.drawImage(this.bgImageElement, d, l, n, r), e.restore();
489
+ const n = this.bgImageElement.naturalWidth / this.bgImageElement.naturalHeight, o = i / s;
490
+ let a, r, d, l;
491
+ n > o ? (r = s, a = s * n, d = (i - a) / 2, l = 0) : (a = i, r = i / n, d = 0, l = (s - r) / 2), e.drawImage(this.bgImageElement, d, l, a, r), e.restore();
488
492
  }
489
493
  /**
490
494
  * Render safe area guide as dashed green rectangle
@@ -492,17 +496,17 @@ class N {
492
496
  renderSafeAreaGuide() {
493
497
  if (!this.safeArea || !this.artboard)
494
498
  return;
495
- const { ctx: e } = this, { top: t, right: i, bottom: s, left: a } = this.safeArea, o = a, n = t, r = this.artboard.width - a - i, d = this.artboard.height - t - s;
496
- r <= 0 || d <= 0 || (e.save(), e.strokeStyle = "#22c55e", e.lineWidth = 1 / this.zoom, e.setLineDash([6 / this.zoom, 4 / this.zoom]), e.strokeRect(o, n, r, d), e.setLineDash([]), e.restore());
499
+ const { ctx: e } = this, { top: t, right: i, bottom: s, left: n } = this.safeArea, o = n, a = t, r = this.artboard.width - n - i, d = this.artboard.height - t - s;
500
+ r <= 0 || d <= 0 || (e.save(), e.strokeStyle = "#22c55e", e.lineWidth = 1 / this.zoom, e.setLineDash([6 / this.zoom, 4 / this.zoom]), e.strokeRect(o, a, r, d), e.setLineDash([]), e.restore());
497
501
  }
498
502
  /**
499
503
  * Render an area with its content
500
504
  */
501
505
  renderAreaWithContent(e) {
502
- const { ctx: t } = this, { location: i, backgroundColor: s } = e, { x: a, y: o, width: n, height: r } = i;
503
- s && (t.fillStyle = s, t.fillRect(a, o, n, r));
506
+ const { ctx: t } = this, { location: i, backgroundColor: s } = e, { x: n, y: o, width: a, height: r } = i;
507
+ s && (t.fillStyle = s, t.fillRect(n, o, a, r));
504
508
  const d = this.contents.get(e.id);
505
- d && (d.type === "text" ? this.renderTextContent(e, d) : d.type === "image" && this.renderImageContent(e, d)), this.showAreaBorders && e.showBorder && (t.save(), t.strokeStyle = e.borderColor || "#3b82f6", t.lineWidth = 1 / this.zoom, t.setLineDash([4 / this.zoom, 4 / this.zoom]), t.strokeRect(a, o, n, r), t.setLineDash([]), t.restore());
509
+ d && (d.type === "text" ? this.renderTextContent(e, d) : d.type === "image" && this.renderImageContent(e, d)), this.showAreaBorders && e.showBorder && (t.save(), t.strokeStyle = e.borderColor || "#3b82f6", t.lineWidth = 1 / this.zoom, t.setLineDash([4 / this.zoom, 4 / this.zoom]), t.strokeRect(n, o, a, r), t.setLineDash([]), t.restore());
506
510
  }
507
511
  /**
508
512
  * Render text content in an area
@@ -510,15 +514,15 @@ class N {
510
514
  renderTextContent(e, t) {
511
515
  if (!t.text.trim())
512
516
  return;
513
- const { ctx: i } = this, { location: s } = e, { x: a, y: o, width: n, height: r } = s, d = t.offset || { x: 0, y: 0 }, l = t.scale ?? 1, c = t.rotation ?? 0, u = a + n / 2 + d.x, p = o + r / 2 + d.y;
514
- i.save(), i.beginPath(), i.rect(a, o, n, r), i.clip(), i.translate(u, p), i.rotate(c * Math.PI / 180), i.scale(l, l), i.font = `${t.size}px ${t.font}, sans-serif`, i.fillStyle = t.color, i.textBaseline = "middle";
517
+ const { ctx: i } = this, { location: s } = e, { x: n, y: o, width: a, height: r } = s, d = t.offset || { x: 0, y: 0 }, l = t.scale ?? 1, c = t.rotation ?? 0, p = n + a / 2 + d.x, u = o + r / 2 + d.y;
518
+ i.save(), i.beginPath(), i.rect(n, o, a, r), i.clip(), i.translate(p, u), i.rotate(c * Math.PI / 180), i.scale(l, l), i.font = `${t.size}px ${t.font}, sans-serif`, i.fillStyle = t.color, i.textBaseline = "middle";
515
519
  let g;
516
520
  switch (t.align) {
517
521
  case "left":
518
- i.textAlign = "left", g = -n / 2 + 10;
522
+ i.textAlign = "left", g = -a / 2 + 10;
519
523
  break;
520
524
  case "right":
521
- i.textAlign = "right", g = n / 2 - 10;
525
+ i.textAlign = "right", g = a / 2 - 10;
522
526
  break;
523
527
  case "center":
524
528
  default:
@@ -534,13 +538,13 @@ class N {
534
538
  const i = this.loadedImages.get(e.id);
535
539
  if (!i)
536
540
  return;
537
- const { ctx: s } = this, { location: a } = e, { x: o, y: n, width: r, height: d } = a, l = t.offset || { x: 0, y: 0 }, c = t.scale ?? 1, u = t.rotation ?? 0;
538
- s.save(), s.beginPath(), s.rect(o, n, r, d), s.clip();
539
- const p = i.naturalWidth / i.naturalHeight, g = r / d;
541
+ const { ctx: s } = this, { location: n } = e, { x: o, y: a, width: r, height: d } = n, l = t.offset || { x: 0, y: 0 }, c = t.scale ?? 1, p = t.rotation ?? 0;
542
+ s.save(), s.beginPath(), s.rect(o, a, r, d), s.clip();
543
+ const u = i.naturalWidth / i.naturalHeight, g = r / d;
540
544
  let m, v;
541
- p > g ? (m = r, v = r / p) : (v = d, m = d * p);
542
- const b = m * c, y = v * c, x = o + r / 2 + l.x, w = n + d / 2 + l.y;
543
- s.translate(x, w), s.rotate(u * Math.PI / 180), s.drawImage(i, -b / 2, -y / 2, b, y), s.restore();
545
+ u > g ? (m = r, v = r / u) : (v = d, m = d * u);
546
+ const b = m * c, w = v * c, y = o + r / 2 + l.x, x = a + d / 2 + l.y;
547
+ s.translate(y, x), s.rotate(p * Math.PI / 180), s.drawImage(i, -b / 2, -w / 2, b, w), s.restore();
544
548
  }
545
549
  /**
546
550
  * Draw background image on a specific context (for export)
@@ -550,30 +554,30 @@ class N {
550
554
  return;
551
555
  const { opacity: s } = this.backgroundImage;
552
556
  e.save(), e.globalAlpha = s;
553
- const a = this.bgImageElement.naturalWidth / this.bgImageElement.naturalHeight, o = t / i;
554
- let n, r, d, l;
555
- a > o ? (r = i, n = i * a, d = (t - n) / 2, l = 0) : (n = t, r = t / a, d = 0, l = (i - r) / 2), e.drawImage(this.bgImageElement, d, l, n, r), e.restore();
557
+ const n = this.bgImageElement.naturalWidth / this.bgImageElement.naturalHeight, o = t / i;
558
+ let a, r, d, l;
559
+ n > o ? (r = i, a = i * n, d = (t - a) / 2, l = 0) : (a = t, r = t / n, d = 0, l = (i - r) / 2), e.drawImage(this.bgImageElement, d, l, a, r), e.restore();
556
560
  }
557
561
  /**
558
562
  * Draw area content on a specific context (for export)
559
563
  */
560
564
  drawAreaContentOnContext(e, t) {
561
- const { location: i, backgroundColor: s } = t, { x: a, y: o, width: n, height: r } = i;
562
- s && (e.fillStyle = s, e.fillRect(a, o, n, r));
565
+ const { location: i, backgroundColor: s } = t, { x: n, y: o, width: a, height: r } = i;
566
+ s && (e.fillStyle = s, e.fillRect(n, o, a, r));
563
567
  const d = this.contents.get(t.id);
564
568
  if (d) {
565
569
  if (d.type === "text") {
566
570
  if (!d.text.trim())
567
571
  return;
568
- const l = d.offset || { x: 0, y: 0 }, c = d.scale ?? 1, u = d.rotation ?? 0, p = a + n / 2 + l.x, g = o + r / 2 + l.y;
569
- e.save(), e.beginPath(), e.rect(a, o, n, r), e.clip(), e.translate(p, g), e.rotate(u * Math.PI / 180), e.scale(c, c), e.font = `${d.size}px ${d.font}, sans-serif`, e.fillStyle = d.color, e.textBaseline = "middle";
572
+ const l = d.offset || { x: 0, y: 0 }, c = d.scale ?? 1, p = d.rotation ?? 0, u = n + a / 2 + l.x, g = o + r / 2 + l.y;
573
+ e.save(), e.beginPath(), e.rect(n, o, a, r), e.clip(), e.translate(u, g), e.rotate(p * Math.PI / 180), e.scale(c, c), e.font = `${d.size}px ${d.font}, sans-serif`, e.fillStyle = d.color, e.textBaseline = "middle";
570
574
  let m;
571
575
  switch (d.align) {
572
576
  case "left":
573
- e.textAlign = "left", m = -n / 2 + 10;
577
+ e.textAlign = "left", m = -a / 2 + 10;
574
578
  break;
575
579
  case "right":
576
- e.textAlign = "right", m = n / 2 - 10;
580
+ e.textAlign = "right", m = a / 2 - 10;
577
581
  break;
578
582
  case "center":
579
583
  default:
@@ -585,13 +589,13 @@ class N {
585
589
  const l = this.loadedImages.get(t.id);
586
590
  if (!l)
587
591
  return;
588
- const c = d.offset || { x: 0, y: 0 }, u = d.scale ?? 1, p = d.rotation ?? 0;
589
- e.save(), e.beginPath(), e.rect(a, o, n, r), e.clip();
590
- const g = l.naturalWidth / l.naturalHeight, m = n / r;
592
+ const c = d.offset || { x: 0, y: 0 }, p = d.scale ?? 1, u = d.rotation ?? 0;
593
+ e.save(), e.beginPath(), e.rect(n, o, a, r), e.clip();
594
+ const g = l.naturalWidth / l.naturalHeight, m = a / r;
591
595
  let v, b;
592
- g > m ? (v = n, b = n / g) : (b = r, v = r * g);
593
- const y = v * u, x = b * u, w = a + n / 2 + c.x, C = o + r / 2 + c.y;
594
- e.translate(w, C), e.rotate(p * Math.PI / 180), e.drawImage(l, -y / 2, -x / 2, y, x), e.restore();
596
+ g > m ? (v = a, b = a / g) : (b = r, v = r * g);
597
+ const w = v * p, y = b * p, x = n + a / 2 + c.x, C = o + r / 2 + c.y;
598
+ e.translate(x, C), e.rotate(u * Math.PI / 180), e.drawImage(l, -w / 2, -y / 2, w, y), e.restore();
595
599
  }
596
600
  }
597
601
  }
@@ -602,7 +606,7 @@ class N {
602
606
  this.stop(), this.bgImageElement = null, this.loadedImages.clear(), this.loadingImages.clear(), this.eventListeners.clear();
603
607
  }
604
608
  }
605
- function E(f) {
609
+ function S(f) {
606
610
  const e = f.textOptions;
607
611
  return {
608
612
  type: "text",
@@ -613,14 +617,14 @@ function E(f) {
613
617
  align: e?.defaultAlign || "center"
614
618
  };
615
619
  }
616
- function S(f) {
620
+ function E(f) {
617
621
  return f.contentType === "text" || f.contentType === "both";
618
622
  }
619
623
  function T(f) {
620
624
  return f.contentType === "image" || f.contentType === "both";
621
625
  }
622
- const U = 50;
623
- class V {
626
+ const V = 50;
627
+ class F {
624
628
  constructor(e) {
625
629
  this.history = [], this.historyIndex = -1, this.listeners = /* @__PURE__ */ new Set(), this.isRestoring = !1, this.state = this.cloneState(e), this.history.push({
626
630
  state: this.cloneState(e),
@@ -671,7 +675,7 @@ class V {
671
675
  state: this.cloneState(e),
672
676
  timestamp: Date.now(),
673
677
  description: t
674
- }), this.history.length > U && this.history.shift(), this.historyIndex = this.history.length - 1;
678
+ }), this.history.length > V && this.history.shift(), this.historyIndex = this.history.length - 1;
675
679
  }
676
680
  restoreFromHistory() {
677
681
  const e = this.history[this.historyIndex];
@@ -694,14 +698,14 @@ class V {
694
698
  return JSON.parse(JSON.stringify(e));
695
699
  }
696
700
  }
697
- class k {
701
+ class P {
698
702
  constructor(e) {
699
703
  this.listeners = /* @__PURE__ */ new Set(), this.areas = e, this.contents = this.buildInitialContents();
700
704
  }
701
705
  buildInitialContents() {
702
706
  const e = /* @__PURE__ */ new Map();
703
707
  for (const t of this.areas)
704
- S(t) && e.set(t.id, E(t));
708
+ E(t) && e.set(t.id, S(t));
705
709
  return e;
706
710
  }
707
711
  getContents() {
@@ -711,7 +715,7 @@ class k {
711
715
  return this.contents.get(e);
712
716
  }
713
717
  setTextContent(e, t) {
714
- const i = this.contents.get(e), s = this.areas.find((o) => o.id === e), a = s ? E(s) : {
718
+ const i = this.contents.get(e), s = this.areas.find((o) => o.id === e), n = s ? S(s) : {
715
719
  type: "text",
716
720
  text: "",
717
721
  font: "Inter",
@@ -719,19 +723,27 @@ class k {
719
723
  color: "#000000",
720
724
  align: "center"
721
725
  };
722
- i?.type === "text" ? this.contents.set(e, { ...i, ...t }) : this.contents.set(e, { ...a, ...t }), this.notify();
726
+ i?.type === "text" ? this.contents.set(e, { ...i, ...t }) : this.contents.set(e, { ...n, ...t }), this.notify();
723
727
  }
724
- setImageContent(e, t, i) {
725
- const s = {
728
+ setImageContent(e, t, i, s) {
729
+ const n = this.contents.get(e), o = {
726
730
  type: "image",
727
731
  dataUrl: t,
728
- filename: i
732
+ filename: i,
733
+ assetId: s?.assetId,
734
+ assetUrl: s?.assetUrl,
735
+ // Preserve transforms from existing image content
736
+ ...n?.type === "image" && {
737
+ offset: n.offset,
738
+ scale: n.scale,
739
+ rotation: n.rotation
740
+ }
729
741
  };
730
- this.contents.set(e, s), this.notify();
742
+ this.contents.set(e, o), this.notify();
731
743
  }
732
744
  clearContent(e) {
733
745
  const t = this.areas.find((i) => i.id === e);
734
- t && S(t) ? this.contents.set(e, E(t)) : this.contents.delete(e), this.notify();
746
+ t && E(t) ? this.contents.set(e, S(t)) : this.contents.delete(e), this.notify();
735
747
  }
736
748
  setContentOffset(e, t) {
737
749
  const i = this.contents.get(e);
@@ -776,7 +788,7 @@ class k {
776
788
  }
777
789
  }
778
790
  }
779
- class I {
791
+ class z {
780
792
  constructor() {
781
793
  this.listeners = /* @__PURE__ */ new Map();
782
794
  }
@@ -823,9 +835,9 @@ class I {
823
835
  const i = this.listeners.get(e);
824
836
  if (i) {
825
837
  const s = Array.from(i);
826
- for (const a of s)
838
+ for (const n of s)
827
839
  try {
828
- a(t);
840
+ n(t);
829
841
  } catch (o) {
830
842
  console.error(`Error in event handler for "${String(e)}":`, o);
831
843
  }
@@ -849,7 +861,7 @@ class I {
849
861
  return this.listeners.get(e)?.size ?? 0;
850
862
  }
851
863
  }
852
- class O extends I {
864
+ class O extends z {
853
865
  constructor() {
854
866
  super(...arguments), this.activeTouches = /* @__PURE__ */ new Map(), this.initialPinchDistance = null, this.lastPinchCenter = null, this.isTwoFingerGesture = !1;
855
867
  }
@@ -881,14 +893,14 @@ class O extends I {
881
893
  }
882
894
  if (this.activeTouches.size === 2 && this.initialPinchDistance !== null && this.lastPinchCenter) {
883
895
  e.preventDefault();
884
- const [t, i] = Array.from(this.activeTouches.values()), s = this.getDistance(t, i), a = this.getCenter(t, i), o = s / this.initialPinchDistance;
896
+ const [t, i] = Array.from(this.activeTouches.values()), s = this.getDistance(t, i), n = this.getCenter(t, i), o = s / this.initialPinchDistance;
885
897
  this.emit("pinch", {
886
898
  scale: o,
887
- centerX: a.x,
888
- centerY: a.y
899
+ centerX: n.x,
900
+ centerY: n.y
889
901
  });
890
- const n = a.x - this.lastPinchCenter.x, r = a.y - this.lastPinchCenter.y;
891
- (Math.abs(n) > 0.5 || Math.abs(r) > 0.5) && this.emit("pan", { deltaX: n, deltaY: r }), this.initialPinchDistance = s, this.lastPinchCenter = a;
902
+ const a = n.x - this.lastPinchCenter.x, r = n.y - this.lastPinchCenter.y;
903
+ (Math.abs(a) > 0.5 || Math.abs(r) > 0.5) && this.emit("pan", { deltaX: a, deltaY: r }), this.initialPinchDistance = s, this.lastPinchCenter = n;
892
904
  }
893
905
  }
894
906
  handleTouchEnd(e) {
@@ -912,24 +924,24 @@ class O extends I {
912
924
  };
913
925
  }
914
926
  }
915
- class B extends I {
927
+ class $ extends z {
916
928
  constructor(e, t, i, s) {
917
- super(), this.interactionState = { mode: "idle" }, this.initialPinchZoom = 1, this.canvas = e, this.engine = t, this.getContents = i, this.getSelectedAreaId = s, this.gestureHandler = new O(), this.boundMouseDown = this.handleMouseDown.bind(this), this.boundMouseMove = this.handleMouseMove.bind(this), this.boundMouseUp = this.handleMouseUp.bind(this), this.boundMouseLeave = this.handleMouseLeave.bind(this), this.boundContextMenu = this.handleContextMenu.bind(this), this.boundDoubleClick = this.handleDoubleClick.bind(this), this.boundTouchStart = this.handleTouchStart.bind(this), this.boundTouchMove = this.handleTouchMove.bind(this), this.boundTouchEnd = this.handleTouchEnd.bind(this), e.addEventListener("mousedown", this.boundMouseDown), e.addEventListener("mousemove", this.boundMouseMove), e.addEventListener("mouseup", this.boundMouseUp), e.addEventListener("mouseleave", this.boundMouseLeave), e.addEventListener("contextmenu", this.boundContextMenu), e.addEventListener("dblclick", this.boundDoubleClick), e.addEventListener("touchstart", this.boundTouchStart, { passive: !1 }), e.addEventListener("touchmove", this.boundTouchMove, { passive: !1 }), e.addEventListener("touchend", this.boundTouchEnd), this.gestureHandler.on("pinch", ({ scale: a }) => {
918
- const o = Math.max(0.1, Math.min(5, this.initialPinchZoom * a));
929
+ super(), this.interactionState = { mode: "idle" }, this.initialPinchZoom = 1, this.canvas = e, this.engine = t, this.getContents = i, this.getSelectedAreaId = s, this.gestureHandler = new O(), this.boundMouseDown = this.handleMouseDown.bind(this), this.boundMouseMove = this.handleMouseMove.bind(this), this.boundMouseUp = this.handleMouseUp.bind(this), this.boundMouseLeave = this.handleMouseLeave.bind(this), this.boundContextMenu = this.handleContextMenu.bind(this), this.boundDoubleClick = this.handleDoubleClick.bind(this), this.boundTouchStart = this.handleTouchStart.bind(this), this.boundTouchMove = this.handleTouchMove.bind(this), this.boundTouchEnd = this.handleTouchEnd.bind(this), e.addEventListener("mousedown", this.boundMouseDown), e.addEventListener("mousemove", this.boundMouseMove), e.addEventListener("mouseup", this.boundMouseUp), e.addEventListener("mouseleave", this.boundMouseLeave), e.addEventListener("contextmenu", this.boundContextMenu), e.addEventListener("dblclick", this.boundDoubleClick), e.addEventListener("touchstart", this.boundTouchStart, { passive: !1 }), e.addEventListener("touchmove", this.boundTouchMove, { passive: !1 }), e.addEventListener("touchend", this.boundTouchEnd), this.gestureHandler.on("pinch", ({ scale: n }) => {
930
+ const o = Math.max(0.1, Math.min(5, this.initialPinchZoom * n));
919
931
  this.emit("zoom", { zoom: o });
920
- }), this.gestureHandler.on("pan", ({ deltaX: a, deltaY: o }) => {
921
- const n = this.engine.getPan(), r = this.engine.getZoom();
932
+ }), this.gestureHandler.on("pan", ({ deltaX: n, deltaY: o }) => {
933
+ const a = this.engine.getPan(), r = this.engine.getZoom();
922
934
  this.emit("pan", {
923
935
  pan: {
924
- x: n.x + a / r,
925
- y: n.y + o / r
936
+ x: a.x + n / r,
937
+ y: a.y + o / r
926
938
  }
927
939
  });
928
940
  });
929
941
  }
930
942
  getCanvasPosition(e, t) {
931
- const i = this.canvas.getBoundingClientRect(), s = window.devicePixelRatio || 1, a = (e - i.left) * s, o = (t - i.top) * s;
932
- return this.engine.screenToCanvas(a, o);
943
+ const i = this.canvas.getBoundingClientRect(), s = window.devicePixelRatio || 1, n = (e - i.left) * s, o = (t - i.top) * s;
944
+ return this.engine.screenToCanvas(n, o);
933
945
  }
934
946
  handleMouseDown(e) {
935
947
  const t = this.getCanvasPosition(e.clientX, e.clientY);
@@ -978,22 +990,22 @@ class B extends I {
978
990
  if (t) {
979
991
  const o = this.engine.hitTestHandle(e.x, e.y, t);
980
992
  if (o) {
981
- const n = i.get(t), r = this.engine.getContentBounds(t);
982
- if (o === "rotate" && n?.type === "image" && r) {
993
+ const a = i.get(t), r = this.engine.getContentBounds(t);
994
+ if (o === "rotate" && a?.type === "image" && r) {
983
995
  const d = Math.atan2(e.y - r.centerY, e.x - r.centerX) * (180 / Math.PI);
984
996
  return this.interactionState = {
985
997
  mode: "rotating",
986
998
  areaId: t,
987
- startRotation: n.rotation ?? 0,
999
+ startRotation: a.rotation ?? 0,
988
1000
  centerPoint: { x: r.centerX, y: r.centerY },
989
1001
  startAngle: d
990
1002
  }, !0;
991
- } else if (o !== "rotate" && n?.type === "image" && r)
1003
+ } else if (o !== "rotate" && a?.type === "image" && r)
992
1004
  return this.interactionState = {
993
1005
  mode: "scaling",
994
1006
  areaId: t,
995
1007
  handle: o,
996
- startScale: n.scale ?? 1,
1008
+ startScale: a.scale ?? 1,
997
1009
  startMousePos: e,
998
1010
  pivotPoint: { x: r.centerX, y: r.centerY }
999
1011
  }, !0;
@@ -1012,47 +1024,45 @@ class B extends I {
1012
1024
  }
1013
1025
  return !0;
1014
1026
  }
1015
- const a = this.engine.hitTestArea(e.x, e.y);
1016
- return a ? (this.emit("area:select", { areaId: a }), !1) : (this.emit("area:select", { areaId: null }), !1);
1027
+ const n = this.engine.hitTestArea(e.x, e.y);
1028
+ return n ? (this.emit("area:select", { areaId: n }), !1) : (this.emit("area:select", { areaId: null }), !1);
1017
1029
  }
1018
1030
  processPointerMove(e) {
1019
1031
  if (this.interactionState.mode === "dragging") {
1020
- const { areaId: t, startOffset: i, startMousePos: s } = this.interactionState, a = e.x - s.x, o = e.y - s.y;
1021
- let n = {
1022
- x: i.x + a,
1032
+ const { areaId: t, startOffset: i, startMousePos: s } = this.interactionState, n = e.x - s.x, o = e.y - s.y, a = {
1033
+ x: i.x + n,
1023
1034
  y: i.y + o
1024
- };
1025
- const r = this.engine.getAreaLocation(t);
1035
+ }, r = this.engine.getAreaLocation(t);
1026
1036
  if (r) {
1027
1037
  const d = r.width / 2 - 10, l = r.height / 2 - 10;
1028
- n.x = Math.max(-d, Math.min(d, n.x)), n.y = Math.max(-l, Math.min(l, n.y));
1038
+ a.x = Math.max(-d, Math.min(d, a.x)), a.y = Math.max(-l, Math.min(l, a.y));
1029
1039
  }
1030
- this.emit("content:drag", { areaId: t, offset: n });
1040
+ this.emit("content:drag", { areaId: t, offset: a });
1031
1041
  return;
1032
1042
  }
1033
1043
  if (this.interactionState.mode === "scaling") {
1034
- const { areaId: t, startScale: i, startMousePos: s, pivotPoint: a } = this.interactionState, o = Math.sqrt(
1035
- Math.pow(s.x - a.x, 2) + Math.pow(s.y - a.y, 2)
1036
- ), n = Math.sqrt(
1037
- Math.pow(e.x - a.x, 2) + Math.pow(e.y - a.y, 2)
1044
+ const { areaId: t, startScale: i, startMousePos: s, pivotPoint: n } = this.interactionState, o = Math.sqrt(
1045
+ Math.pow(s.x - n.x, 2) + Math.pow(s.y - n.y, 2)
1046
+ ), a = Math.sqrt(
1047
+ Math.pow(e.x - n.x, 2) + Math.pow(e.y - n.y, 2)
1038
1048
  );
1039
1049
  if (o > 0) {
1040
- const r = n / o, d = Math.max(0.1, Math.min(5, i * r));
1050
+ const r = a / o, d = Math.max(0.1, Math.min(5, i * r));
1041
1051
  this.emit("content:scale", { areaId: t, scale: d });
1042
1052
  }
1043
1053
  return;
1044
1054
  }
1045
1055
  if (this.interactionState.mode === "rotating") {
1046
- const { areaId: t, startRotation: i, centerPoint: s, startAngle: a } = this.interactionState, o = Math.atan2(e.y - s.y, e.x - s.x) * (180 / Math.PI);
1047
- let n = i + (o - a);
1048
- n = n % 360, n < 0 && (n += 360);
1056
+ const { areaId: t, startRotation: i, centerPoint: s, startAngle: n } = this.interactionState, o = Math.atan2(e.y - s.y, e.x - s.x) * (180 / Math.PI);
1057
+ let a = i + (o - n);
1058
+ a = a % 360, a < 0 && (a += 360);
1049
1059
  const r = [0, 90, 180, 270, 360];
1050
1060
  for (const d of r)
1051
- if (Math.abs(n - d) < 5) {
1052
- n = d === 360 ? 0 : d;
1061
+ if (Math.abs(a - d) < 5) {
1062
+ a = d === 360 ? 0 : d;
1053
1063
  break;
1054
1064
  }
1055
- this.emit("content:rotate", { areaId: t, rotation: n });
1065
+ this.emit("content:rotate", { areaId: t, rotation: a });
1056
1066
  return;
1057
1067
  }
1058
1068
  this.updateCursor(e);
@@ -1077,7 +1087,7 @@ class B extends I {
1077
1087
  this.canvas.removeEventListener("mousedown", this.boundMouseDown), this.canvas.removeEventListener("mousemove", this.boundMouseMove), this.canvas.removeEventListener("mouseup", this.boundMouseUp), this.canvas.removeEventListener("mouseleave", this.boundMouseLeave), this.canvas.removeEventListener("contextmenu", this.boundContextMenu), this.canvas.removeEventListener("dblclick", this.boundDoubleClick), this.canvas.removeEventListener("touchstart", this.boundTouchStart), this.canvas.removeEventListener("touchmove", this.boundTouchMove), this.canvas.removeEventListener("touchend", this.boundTouchEnd), this.gestureHandler.reset(), this.clear();
1078
1088
  }
1079
1089
  }
1080
- class F {
1090
+ class B {
1081
1091
  constructor(e = "/api") {
1082
1092
  this.baseUrl = e;
1083
1093
  }
@@ -1125,8 +1135,8 @@ class F {
1125
1135
 
1126
1136
  `;
1127
1137
  if (i.status === 400) {
1128
- const a = await i.json().catch(() => ({}));
1129
- s += a.error || "Invalid design data.";
1138
+ const n = await i.json().catch(() => ({}));
1139
+ s += n.error || "Invalid design data.";
1130
1140
  } else i.status === 429 ? s += "Rate limit exceeded." : i.status >= 500 && (s += "Server error.");
1131
1141
  throw new Error(s);
1132
1142
  }
@@ -1135,6 +1145,79 @@ class F {
1135
1145
  throw i instanceof Error && i.message.includes("Failed to fetch") ? new Error("Network error finalizing design") : i;
1136
1146
  }
1137
1147
  }
1148
+ async finalizeMultiView(e) {
1149
+ try {
1150
+ const t = e.designs.map((s) => ({
1151
+ templateId: s.templateId,
1152
+ viewName: s.viewName,
1153
+ areaContents: s.contents.map(([n, o]) => {
1154
+ const a = { areaId: n, isModified: !0 };
1155
+ return o.type === "text" ? a.text = {
1156
+ content: o.text,
1157
+ font: o.font,
1158
+ size: o.size,
1159
+ color: o.color,
1160
+ align: o.align,
1161
+ position: { x: o.offset?.x ?? 0, y: o.offset?.y ?? 0 }
1162
+ } : o.type === "image" && (a.image = {
1163
+ assetId: o.assetId || "inline",
1164
+ assetUrl: o.assetUrl || "",
1165
+ rotation: o.rotation ?? 0,
1166
+ scale: o.scale ?? 1,
1167
+ position: { x: o.offset?.x ?? 0, y: o.offset?.y ?? 0 },
1168
+ // Only send dataUrl if the image wasn't pre-uploaded
1169
+ ...o.assetId ? {} : { dataUrl: o.dataUrl }
1170
+ }), a;
1171
+ })
1172
+ })), i = await fetch(`${this.baseUrl}/finalize`, {
1173
+ method: "POST",
1174
+ headers: { "Content-Type": "application/json" },
1175
+ body: JSON.stringify({
1176
+ productId: e.productId,
1177
+ designs: t,
1178
+ cartContext: e.cartContext,
1179
+ customerId: e.customerId,
1180
+ customerEmail: e.customerEmail,
1181
+ sessionId: e.sessionId
1182
+ })
1183
+ });
1184
+ if (!i.ok) {
1185
+ let s = `Failed to finalize multi-view design (${i.status} ${i.statusText})
1186
+
1187
+ `;
1188
+ if (i.status === 400) {
1189
+ const n = await i.json().catch(() => ({}));
1190
+ s += n.error || "Invalid design data.";
1191
+ } else i.status === 429 ? s += "Rate limit exceeded." : i.status >= 500 && (s += "Server error.");
1192
+ throw new Error(s);
1193
+ }
1194
+ return i.json();
1195
+ } catch (t) {
1196
+ throw t instanceof Error && t.message.includes("Failed to fetch") ? new Error("Network error finalizing multi-view design") : t;
1197
+ }
1198
+ }
1199
+ async getDesignStatus(e) {
1200
+ try {
1201
+ const t = await fetch(`${this.baseUrl}/designs/${e}/status`);
1202
+ if (!t.ok)
1203
+ throw new Error(`Failed to check design status (${t.status})`);
1204
+ return t.json();
1205
+ } catch (t) {
1206
+ throw t instanceof Error && t.message.includes("Failed to fetch") ? new Error("Network error checking design status") : t;
1207
+ }
1208
+ }
1209
+ async pollMultiViewStatus(e) {
1210
+ try {
1211
+ const t = await fetch(
1212
+ `${this.baseUrl}/finalize-status?designIds=${e.join(",")}`
1213
+ );
1214
+ if (!t.ok)
1215
+ throw new Error(`Failed to check multi-view design status (${t.status})`);
1216
+ return t.json();
1217
+ } catch (t) {
1218
+ throw t instanceof Error && t.message.includes("Failed to fetch") ? new Error("Network error checking multi-view design status") : t;
1219
+ }
1220
+ }
1138
1221
  async uploadAsset(e, t) {
1139
1222
  try {
1140
1223
  const i = new FormData();
@@ -1144,14 +1227,14 @@ class F {
1144
1227
  body: i
1145
1228
  });
1146
1229
  if (!s.ok) {
1147
- let a = `Failed to upload asset (${s.status} ${s.statusText})
1230
+ let n = `Failed to upload asset (${s.status} ${s.statusText})
1148
1231
 
1149
1232
  `;
1150
1233
  if (s.status === 400) {
1151
1234
  const o = await s.json().catch(() => ({}));
1152
- a += o.error || "Invalid file.";
1153
- } else s.status === 413 ? a += "File too large (max 15MB)." : s.status >= 500 && (a += "Server error.");
1154
- throw new Error(a);
1235
+ n += o.error || "Invalid file.";
1236
+ } else s.status === 413 ? n += "File too large (max 15MB)." : s.status >= 500 && (n += "Server error.");
1237
+ throw new Error(n);
1155
1238
  }
1156
1239
  return s.json();
1157
1240
  } catch (i) {
@@ -1171,16 +1254,16 @@ class F {
1171
1254
  }
1172
1255
  function h(f, e, ...t) {
1173
1256
  const i = document.createElement(f);
1174
- return e && Object.entries(e).forEach(([s, a]) => {
1175
- s === "class" ? i.className = a : s in i ? i[s] = a : i.setAttribute(s, String(a));
1257
+ return e && Object.entries(e).forEach(([s, n]) => {
1258
+ s === "class" ? i.className = n : s in i ? i[s] = n : i.setAttribute(s, String(n));
1176
1259
  }), t.forEach((s) => {
1177
1260
  typeof s == "string" ? i.appendChild(document.createTextNode(s)) : i.appendChild(s);
1178
1261
  }), i;
1179
1262
  }
1180
- function $(f, e) {
1263
+ function q(f, e) {
1181
1264
  let t = null, i = null;
1182
- const s = (...a) => {
1183
- i = a, t && clearTimeout(t), t = setTimeout(() => {
1265
+ const s = (...n) => {
1266
+ i = n, t && clearTimeout(t), t = setTimeout(() => {
1184
1267
  i && f(...i), t = null, i = null;
1185
1268
  }, e);
1186
1269
  };
@@ -1190,7 +1273,7 @@ function $(f, e) {
1190
1273
  t && (clearTimeout(t), t = null), i = null;
1191
1274
  }, s;
1192
1275
  }
1193
- class H extends I {
1276
+ class H extends z {
1194
1277
  constructor() {
1195
1278
  super(), this.saveEnabled = !0, this.percentLabel = h("span", { class: "zoom-percent" }, "100%");
1196
1279
  const e = h("button", {
@@ -1211,13 +1294,13 @@ class H extends I {
1211
1294
  this.emit("view-change", { viewName: this.viewSelect.value });
1212
1295
  }), this.element = h("div", { class: "zoom-toolbar" }), this.element.appendChild(e), this.element.appendChild(t), this.element.appendChild(i), this.element.appendChild(this.percentLabel), this.element.appendChild(this.viewSelect);
1213
1296
  const s = h("div", { class: "zoom-toolbar-spacer" });
1214
- this.element.appendChild(s), this.closeBtn = h("button", {
1215
- class: "zoom-btn zoom-close-btn",
1216
- title: "Close editor"
1217
- }, "✕ Close"), this.closeBtn.addEventListener("click", () => this.emit("close", void 0)), this.element.appendChild(this.closeBtn), this.saveBtn = h("button", {
1297
+ this.element.appendChild(s), this.saveBtn = h("button", {
1218
1298
  class: "zoom-btn zoom-save-btn",
1219
1299
  title: "Save customization"
1220
- }, "Save Customization"), this.saveBtn.style.display = "none", this.saveBtn.addEventListener("click", () => this.emit("save", void 0)), this.element.appendChild(this.saveBtn);
1300
+ }, ""), this.saveBtn.style.display = "none", this.saveBtn.addEventListener("click", () => this.emit("save", void 0)), this.element.appendChild(this.saveBtn), this.closeBtn = h("button", {
1301
+ class: "zoom-btn zoom-close-btn",
1302
+ title: "Close editor"
1303
+ }, "✕"), this.closeBtn.addEventListener("click", () => this.emit("close", void 0)), this.element.appendChild(this.closeBtn);
1221
1304
  }
1222
1305
  setZoom(e) {
1223
1306
  this.percentLabel.textContent = `${Math.round(e * 100)}%`;
@@ -1255,31 +1338,31 @@ const Z = [
1255
1338
  { label: "Georgia", value: "Georgia" },
1256
1339
  { label: "Times New Roman", value: "Times New Roman" },
1257
1340
  { label: "Courier New", value: "Courier New" }
1258
- ], q = [
1341
+ ], j = [
1259
1342
  { label: "L", value: "left" },
1260
1343
  { label: "C", value: "center" },
1261
1344
  { label: "R", value: "right" }
1262
- ], P = ["image/png", "image/jpeg", "image/webp", "image/svg+xml"], Y = 15 * 1024 * 1024;
1263
- class L extends I {
1345
+ ], A = ["image/png", "image/jpeg", "image/webp", "image/svg+xml"], Y = 15 * 1024 * 1024;
1346
+ class L extends z {
1264
1347
  constructor(e) {
1265
- super(), this.mode = "text", this.isSelected = !1, this.textInputEl = null, this.fontSelectEl = null, this.sizeInputEl = null, this.colorInputEl = null, this.alignGroupEl = null, this.area = e;
1266
- const t = S(e), i = T(e);
1348
+ super(), this.mode = "text", this.isSelected = !1, this.isUploading = !1, this.textInputEl = null, this.fontSelectEl = null, this.sizeInputEl = null, this.colorInputEl = null, this.alignGroupEl = null, this.area = e;
1349
+ const t = E(e), i = T(e);
1267
1350
  this.mode = i && !t ? "image" : "text", this.element = h("div", { class: "area-card" }), this.element.addEventListener("click", (r) => {
1268
1351
  r.target.tagName === "INPUT" || r.target.tagName === "SELECT" || r.target.tagName === "BUTTON" || this.emit("select", { areaId: e.id });
1269
1352
  });
1270
- const s = h("div", { class: "area-card-header" }), a = h("div", { class: "area-card-name-row" }), o = h("span", { class: "area-card-name" }, e.name);
1271
- if (a.appendChild(o), e.required) {
1353
+ const s = h("div", { class: "area-card-header" }), n = h("div", { class: "area-card-name-row" }), o = h("span", { class: "area-card-name" }, e.name);
1354
+ if (n.appendChild(o), e.required) {
1272
1355
  const r = h("span", { class: "area-card-badge" }, "Required");
1273
- a.appendChild(r);
1356
+ n.appendChild(r);
1274
1357
  }
1275
- s.appendChild(a);
1276
- const n = h("button", {
1358
+ s.appendChild(n);
1359
+ const a = h("button", {
1277
1360
  class: "area-card-reset-btn",
1278
1361
  title: "Reset content"
1279
1362
  }, "Reset");
1280
- if (n.addEventListener("click", (r) => {
1363
+ if (a.addEventListener("click", (r) => {
1281
1364
  r.stopPropagation(), this.emit("clear", { areaId: e.id });
1282
- }), s.appendChild(n), this.element.appendChild(s), t && i) {
1365
+ }), s.appendChild(a), this.element.appendChild(s), t && i) {
1283
1366
  const r = h("div", { class: "area-card-mode-switcher" }), d = h("button", {
1284
1367
  class: "mode-btn mode-btn-active"
1285
1368
  }, "Text");
@@ -1298,7 +1381,7 @@ class L extends I {
1298
1381
  }
1299
1382
  setContent(e) {
1300
1383
  const t = this.currentContent?.type;
1301
- this.currentContent = e, e?.type === "image" && (this.mode = "image"), this.element.querySelectorAll(".mode-btn").forEach((i) => {
1384
+ this.currentContent = e, e?.type === "image" ? this.mode = "image" : t === "image" && (this.mode = E(this.area) ? "text" : "image"), this.element.querySelectorAll(".mode-btn").forEach((i) => {
1302
1385
  const s = i;
1303
1386
  s.classList.toggle("mode-btn-active", s.dataset.mode === this.mode);
1304
1387
  }), e?.type !== t || e?.type === "image" || e === void 0 ? this.renderContent() : e?.type === "text" && this.updateTextValues(e), this.renderTransforms();
@@ -1314,11 +1397,11 @@ class L extends I {
1314
1397
  }
1315
1398
  renderContent() {
1316
1399
  this.contentContainer.innerHTML = "", this.textInputEl = null, this.fontSelectEl = null, this.sizeInputEl = null, this.colorInputEl = null, this.alignGroupEl = null;
1317
- const e = S(this.area), t = T(this.area), i = !t || e && t && this.mode === "text", s = !e || e && t && this.mode === "image";
1318
- e && i && this.renderTextControls(), t && s && this.renderImageControls();
1400
+ const e = E(this.area), t = T(this.area), i = !t || e && t && this.mode === "text", s = !e || e && t && this.mode === "image";
1401
+ e && i && this.renderTextControls(), t && s && this.renderImageControls(), this.applyUploadSpinner();
1319
1402
  }
1320
1403
  renderTextControls() {
1321
- const e = this.currentContent, t = e?.type === "text" ? e : E(this.area), i = h("input", {
1404
+ const e = this.currentContent, t = e?.type === "text" ? e : S(this.area), i = h("input", {
1322
1405
  class: "area-input-text",
1323
1406
  type: "text",
1324
1407
  placeholder: this.area.placeholder || "Enter text..."
@@ -1326,14 +1409,14 @@ class L extends I {
1326
1409
  i.value = t.text, this.area.textOptions?.maxLength && (i.maxLength = this.area.textOptions.maxLength), i.addEventListener("input", () => {
1327
1410
  this.emit("text:change", { areaId: this.area.id, updates: { text: i.value } });
1328
1411
  }), this.contentContainer.appendChild(i), this.textInputEl = i;
1329
- const s = h("div", { class: "area-input-row" }), a = h("select", { class: "area-input-font" });
1412
+ const s = h("div", { class: "area-input-row" }), n = h("select", { class: "area-input-font" });
1330
1413
  for (const l of Z) {
1331
1414
  const c = h("option");
1332
- c.value = l.value, c.textContent = l.label, l.value === t.font && (c.selected = !0), a.appendChild(c);
1415
+ c.value = l.value, c.textContent = l.label, l.value === t.font && (c.selected = !0), n.appendChild(c);
1333
1416
  }
1334
- a.addEventListener("change", () => {
1335
- this.emit("text:change", { areaId: this.area.id, updates: { font: a.value } });
1336
- }), s.appendChild(a), this.fontSelectEl = a;
1417
+ n.addEventListener("change", () => {
1418
+ this.emit("text:change", { areaId: this.area.id, updates: { font: n.value } });
1419
+ }), s.appendChild(n), this.fontSelectEl = n;
1337
1420
  const o = h("input", {
1338
1421
  class: "area-input-size",
1339
1422
  type: "number"
@@ -1344,24 +1427,24 @@ class L extends I {
1344
1427
  updates: { size: parseInt(o.value, 10) || 24 }
1345
1428
  });
1346
1429
  }), s.appendChild(o), this.sizeInputEl = o, this.contentContainer.appendChild(s);
1347
- const n = h("div", { class: "area-input-row" }), r = h("div", { class: "area-input-align" });
1348
- for (const l of q) {
1430
+ const a = h("div", { class: "area-input-row" }), r = h("div", { class: "area-input-align" });
1431
+ for (const l of j) {
1349
1432
  const c = h("button", {
1350
1433
  class: `align-btn${t.align === l.value ? " align-btn-active" : ""}`,
1351
1434
  title: l.label === "L" ? "Left" : l.label === "C" ? "Center" : "Right"
1352
1435
  }, l.label);
1353
- c.addEventListener("click", (u) => {
1354
- u.stopPropagation(), this.emit("text:change", { areaId: this.area.id, updates: { align: l.value } }), r.querySelectorAll(".align-btn").forEach((p) => p.classList.remove("align-btn-active")), c.classList.add("align-btn-active");
1436
+ c.addEventListener("click", (p) => {
1437
+ p.stopPropagation(), this.emit("text:change", { areaId: this.area.id, updates: { align: l.value } }), r.querySelectorAll(".align-btn").forEach((u) => u.classList.remove("align-btn-active")), c.classList.add("align-btn-active");
1355
1438
  }), r.appendChild(c);
1356
1439
  }
1357
- n.appendChild(r), this.alignGroupEl = r;
1440
+ a.appendChild(r), this.alignGroupEl = r;
1358
1441
  const d = h("input", {
1359
1442
  class: "area-input-color",
1360
1443
  type: "color"
1361
1444
  });
1362
1445
  d.value = t.color, d.addEventListener("input", () => {
1363
1446
  this.emit("text:change", { areaId: this.area.id, updates: { color: d.value } });
1364
- }), n.appendChild(d), this.colorInputEl = d, this.contentContainer.appendChild(n);
1447
+ }), a.appendChild(d), this.colorInputEl = d, this.contentContainer.appendChild(a);
1365
1448
  }
1366
1449
  renderImageControls() {
1367
1450
  const e = this.currentContent;
@@ -1370,35 +1453,35 @@ class L extends I {
1370
1453
  i.src = e.dataUrl, i.alt = e.filename || "Uploaded image", t.appendChild(i);
1371
1454
  const s = h("div", { class: "area-image-info" });
1372
1455
  s.appendChild(h("span", { class: "area-image-name" }, e.filename || "Image"));
1373
- const a = h("button", { class: "area-image-remove-btn" }, "Remove");
1374
- a.addEventListener("click", (o) => {
1456
+ const n = h("button", { class: "area-image-remove-btn" }, "Remove");
1457
+ n.addEventListener("click", (o) => {
1375
1458
  o.stopPropagation(), this.emit("clear", { areaId: this.area.id });
1376
- }), s.appendChild(a), t.appendChild(s), this.contentContainer.appendChild(t);
1459
+ }), s.appendChild(n), t.appendChild(s), this.contentContainer.appendChild(t);
1377
1460
  } else {
1378
1461
  const t = h("button", { class: "area-upload-btn" }, "Upload Image"), i = h("input", { type: "file" });
1379
- i.accept = P.join(","), i.style.display = "none";
1462
+ i.accept = A.join(","), i.style.display = "none";
1380
1463
  const s = h("div", { class: "area-validation-error" });
1381
1464
  s.style.display = "none", i.addEventListener("change", () => {
1382
- const a = i.files?.[0];
1383
- if (!a) return;
1384
- if (!P.includes(a.type)) {
1385
- const n = "Only PNG, JPEG, WebP, and SVG images are accepted";
1386
- s.textContent = n, s.style.display = "block", this.emit("validation:error", { areaId: this.area.id, message: n }), i.value = "";
1465
+ const n = i.files?.[0];
1466
+ if (!n) return;
1467
+ if (!A.includes(n.type)) {
1468
+ const a = "Only PNG, JPEG, WebP, and SVG images are accepted";
1469
+ s.textContent = a, s.style.display = "block", this.emit("validation:error", { areaId: this.area.id, message: a }), i.value = "";
1387
1470
  return;
1388
1471
  }
1389
- if (a.size > Y) {
1390
- const n = "File must be smaller than 15MB";
1391
- s.textContent = n, s.style.display = "block", this.emit("validation:error", { areaId: this.area.id, message: n }), i.value = "";
1472
+ if (n.size > Y) {
1473
+ const a = "File must be smaller than 15MB";
1474
+ s.textContent = a, s.style.display = "block", this.emit("validation:error", { areaId: this.area.id, message: a }), i.value = "";
1392
1475
  return;
1393
1476
  }
1394
1477
  s.style.display = "none";
1395
1478
  const o = new FileReader();
1396
1479
  o.onload = () => {
1397
- const n = o.result;
1398
- this.emit("image:change", { areaId: this.area.id, dataUrl: n, filename: a.name });
1399
- }, o.readAsDataURL(a), i.value = "";
1400
- }), t.addEventListener("click", (a) => {
1401
- a.stopPropagation(), i.click();
1480
+ const a = o.result;
1481
+ this.emit("image:change", { areaId: this.area.id, dataUrl: a, filename: n.name });
1482
+ }, o.readAsDataURL(n), i.value = "";
1483
+ }), t.addEventListener("click", (n) => {
1484
+ n.stopPropagation(), i.click();
1402
1485
  }), this.contentContainer.appendChild(t), this.contentContainer.appendChild(i), this.contentContainer.appendChild(s);
1403
1486
  }
1404
1487
  }
@@ -1408,14 +1491,14 @@ class L extends I {
1408
1491
  if (!this.isSelected || !e) return;
1409
1492
  const t = e.type === "text" ? this.area.textOptions?.allowPositioning : this.area.imageOptions?.allowPositioning, i = e.type === "image" && this.area.imageOptions?.allowScaling, s = e.type === "image" && this.area.imageOptions?.allowRotation;
1410
1493
  if (!t && !i && !s) return;
1411
- const a = h("div", { class: "area-card-divider" });
1412
- this.transformContainer.appendChild(a);
1494
+ const n = h("div", { class: "area-card-divider" });
1495
+ this.transformContainer.appendChild(n);
1413
1496
  const o = h("div", { class: "transform-header" });
1414
1497
  o.appendChild(h("span", { class: "transform-title" }, "Transform"));
1415
- const n = h("button", { class: "transform-reset-btn" }, "Reset");
1416
- n.addEventListener("click", (d) => {
1498
+ const a = h("button", { class: "transform-reset-btn" }, "Reset");
1499
+ a.addEventListener("click", (d) => {
1417
1500
  d.stopPropagation(), t && this.emit("offset:change", { areaId: this.area.id, offset: { x: 0, y: 0 } }), i && this.emit("scale:change", { areaId: this.area.id, scale: 1 }), s && this.emit("rotation:change", { areaId: this.area.id, rotation: 0 });
1418
- }), o.appendChild(n), this.transformContainer.appendChild(o);
1501
+ }), o.appendChild(a), this.transformContainer.appendChild(o);
1419
1502
  const r = e.offset ?? { x: 0, y: 0 };
1420
1503
  if (t) {
1421
1504
  const d = h("div", { class: "area-input-row" }), l = h("label", { class: "transform-label" }, "X"), c = h("input", { class: "transform-input", type: "number" });
@@ -1425,34 +1508,46 @@ class L extends I {
1425
1508
  offset: { ...r, x: parseInt(c.value, 10) || 0 }
1426
1509
  });
1427
1510
  });
1428
- const u = h("label", { class: "transform-label" }, "Y"), p = h("input", { class: "transform-input", type: "number" });
1429
- p.value = String(Math.round(r.y)), p.addEventListener("change", () => {
1511
+ const p = h("label", { class: "transform-label" }, "Y"), u = h("input", { class: "transform-input", type: "number" });
1512
+ u.value = String(Math.round(r.y)), u.addEventListener("change", () => {
1430
1513
  this.emit("offset:change", {
1431
1514
  areaId: this.area.id,
1432
- offset: { ...r, y: parseInt(p.value, 10) || 0 }
1515
+ offset: { ...r, y: parseInt(u.value, 10) || 0 }
1433
1516
  });
1434
- }), d.appendChild(l), d.appendChild(c), d.appendChild(u), d.appendChild(p), this.transformContainer.appendChild(d);
1517
+ }), d.appendChild(l), d.appendChild(c), d.appendChild(p), d.appendChild(u), this.transformContainer.appendChild(d);
1435
1518
  }
1436
1519
  if (i && e.type === "image") {
1437
- const d = e.scale ?? 1, l = h("div", { class: "area-input-row transform-slider-row" }), c = h("label", { class: "transform-label" }, `Scale: ${Math.round(d * 100)}%`), u = h("input", { class: "transform-slider", type: "range" });
1438
- u.min = "10", u.max = "500", u.step = "5", u.value = String(Math.round(d * 100)), u.addEventListener("input", () => {
1439
- const p = parseInt(u.value, 10) / 100;
1440
- c.textContent = `Scale: ${Math.round(p * 100)}%`, this.emit("scale:change", { areaId: this.area.id, scale: p });
1441
- }), l.appendChild(c), l.appendChild(u), this.transformContainer.appendChild(l);
1520
+ const d = e.scale ?? 1, l = h("div", { class: "area-input-row transform-slider-row" }), c = h("label", { class: "transform-label" }, `Scale: ${Math.round(d * 100)}%`), p = h("input", { class: "transform-slider", type: "range" });
1521
+ p.min = "10", p.max = "500", p.step = "5", p.value = String(Math.round(d * 100)), p.addEventListener("input", () => {
1522
+ const u = parseInt(p.value, 10) / 100;
1523
+ c.textContent = `Scale: ${Math.round(u * 100)}%`, this.emit("scale:change", { areaId: this.area.id, scale: u });
1524
+ }), l.appendChild(c), l.appendChild(p), this.transformContainer.appendChild(l);
1442
1525
  }
1443
1526
  if (s && e.type === "image") {
1444
- const d = e.rotation ?? 0, l = h("div", { class: "area-input-row transform-slider-row" }), c = h("label", { class: "transform-label" }, `Rotation: ${Math.round(d)}°`), u = h("input", { class: "transform-slider", type: "range" });
1445
- u.min = "0", u.max = "360", u.step = "1", u.value = String(Math.round(d)), u.addEventListener("input", () => {
1446
- const p = parseInt(u.value, 10);
1447
- c.textContent = `Rotation: ${p}°`, this.emit("rotation:change", { areaId: this.area.id, rotation: p });
1448
- }), l.appendChild(c), l.appendChild(u), this.transformContainer.appendChild(l);
1527
+ const d = e.rotation ?? 0, l = h("div", { class: "area-input-row transform-slider-row" }), c = h("label", { class: "transform-label" }, `Rotation: ${Math.round(d)}°`), p = h("input", { class: "transform-slider", type: "range" });
1528
+ p.min = "0", p.max = "360", p.step = "1", p.value = String(Math.round(d)), p.addEventListener("input", () => {
1529
+ const u = parseInt(p.value, 10);
1530
+ c.textContent = `Rotation: ${u}°`, this.emit("rotation:change", { areaId: this.area.id, rotation: u });
1531
+ }), l.appendChild(c), l.appendChild(p), this.transformContainer.appendChild(l);
1449
1532
  }
1450
1533
  }
1534
+ setUploading(e) {
1535
+ this.isUploading = e, this.applyUploadSpinner();
1536
+ }
1537
+ applyUploadSpinner() {
1538
+ const e = this.contentContainer.querySelector(".area-image-preview");
1539
+ if (!e) return;
1540
+ const t = e.querySelector(".area-upload-spinner-overlay");
1541
+ if (this.isUploading && !t) {
1542
+ const i = h("div", { class: "area-upload-spinner-overlay" });
1543
+ i.appendChild(h("div", { class: "area-upload-spinner" })), e.appendChild(i);
1544
+ } else !this.isUploading && t && t.remove();
1545
+ }
1451
1546
  getElement() {
1452
1547
  return this.element;
1453
1548
  }
1454
1549
  }
1455
- class j extends I {
1550
+ class J extends z {
1456
1551
  constructor(e) {
1457
1552
  super(), this.cards = /* @__PURE__ */ new Map(), this.isMobile = !1, this.boundEscapeHandler = (s) => {
1458
1553
  s.key === "Escape" && this.emit("dismiss", void 0);
@@ -1469,8 +1564,8 @@ class j extends I {
1469
1564
  this.panelContent.appendChild(s);
1470
1565
  } else
1471
1566
  for (const s of e) {
1472
- const a = new L(s);
1473
- this.cards.set(s.id, a), a.on("text:change", (o) => this.emit("text:change", o)), a.on("image:change", (o) => this.emit("image:change", o)), a.on("clear", (o) => this.emit("clear", o)), a.on("select", (o) => this.emit("select", o)), a.on("offset:change", (o) => this.emit("offset:change", o)), a.on("scale:change", (o) => this.emit("scale:change", o)), a.on("rotation:change", (o) => this.emit("rotation:change", o)), a.on("validation:error", (o) => this.emit("validation:error", o)), this.panelContent.appendChild(a.getElement());
1567
+ const n = new L(s);
1568
+ this.cards.set(s.id, n), n.on("text:change", (o) => this.emit("text:change", o)), n.on("image:change", (o) => this.emit("image:change", o)), n.on("clear", (o) => this.emit("clear", o)), n.on("select", (o) => this.emit("select", o)), n.on("offset:change", (o) => this.emit("offset:change", o)), n.on("scale:change", (o) => this.emit("scale:change", o)), n.on("rotation:change", (o) => this.emit("rotation:change", o)), n.on("validation:error", (o) => this.emit("validation:error", o)), this.panelContent.appendChild(n.getElement());
1474
1569
  }
1475
1570
  this.element.appendChild(this.panelContent);
1476
1571
  }
@@ -1495,6 +1590,10 @@ class j extends I {
1495
1590
  }
1496
1591
  e ? (this.element.classList.add("area-panel-visible"), this.isMobile && this.backdrop.classList.add("area-panel-backdrop-visible"), document.addEventListener("keydown", this.boundEscapeHandler)) : (this.element.classList.remove("area-panel-visible"), this.isMobile && this.backdrop.classList.remove("area-panel-backdrop-visible"), document.removeEventListener("keydown", this.boundEscapeHandler));
1497
1592
  }
1593
+ setUploading(e, t) {
1594
+ const i = this.cards.get(e);
1595
+ i && i.setUploading(t);
1596
+ }
1498
1597
  setMobile(e) {
1499
1598
  this.isMobile = e, this.element.classList.toggle("area-panel-mobile", e), e || (this.element.classList.remove("area-panel-visible"), this.backdrop.classList.remove("area-panel-backdrop-visible"));
1500
1599
  }
@@ -1505,7 +1604,7 @@ class j extends I {
1505
1604
  return this.backdrop;
1506
1605
  }
1507
1606
  }
1508
- class X {
1607
+ class W {
1509
1608
  constructor(e = window) {
1510
1609
  this.target = e, this.commands = [], this.isEnabled = !0, this.handleKeyDown = (t) => {
1511
1610
  if (!this.isEnabled) return;
@@ -1559,31 +1658,31 @@ class X {
1559
1658
  this.target.removeEventListener("keydown", this.handleKeyDown), this.commands = [];
1560
1659
  }
1561
1660
  }
1562
- function W() {
1661
+ function X() {
1563
1662
  return /Mac|iPod|iPhone|iPad/.test(navigator.platform);
1564
1663
  }
1565
- function J() {
1566
- return W() ? "meta" : "ctrl";
1664
+ function G() {
1665
+ return X() ? "meta" : "ctrl";
1567
1666
  }
1568
- function G(f, e, t, i) {
1569
- const s = f / t, a = e / i;
1570
- return Math.min(s, a);
1667
+ function K(f, e, t, i) {
1668
+ const s = f / t, n = e / i;
1669
+ return Math.min(s, n);
1571
1670
  }
1572
- function K(f, e, t = 8, i = 10, s = 300) {
1573
- const a = G(f, e, t, i), o = a >= s;
1671
+ function Q(f, e, t = 8, i = 10, s = 300) {
1672
+ const n = K(f, e, t, i), o = n >= s;
1574
1673
  return {
1575
- actualDPI: Math.round(a),
1674
+ actualDPI: Math.round(n),
1576
1675
  targetDPI: s,
1577
1676
  width: f,
1578
1677
  height: e,
1579
1678
  passed: o,
1580
- warning: o ? void 0 : `Image resolution is ${Math.round(a)} DPI at 100% scale. For best print quality, use images with at least ${s} DPI. This may result in pixelated or blurry prints.`
1679
+ warning: o ? void 0 : `Image resolution is ${Math.round(n)} DPI at 100% scale. For best print quality, use images with at least ${s} DPI. This may result in pixelated or blurry prints.`
1581
1680
  };
1582
1681
  }
1583
- const M = ':host{--editor-bg: #f3f4f6;--editor-surface: #ffffff;--editor-border: #e5e7eb;--editor-text: #111827;--editor-text-muted: #6b7280;--editor-primary: #3b82f6;--editor-primary-hover: #2563eb;--editor-danger: #ef4444;--editor-radius: 8px;--editor-radius-sm: 4px;--editor-font: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--editor-panel-width: 280px;display:block;width:100%;height:100%;font-family:var(--editor-font);font-size:13px;color:var(--editor-text);line-height:1.4}:host([theme="dark"]){--editor-bg: #1f2937;--editor-surface: #374151;--editor-border: #4b5563;--editor-text: #f9fafb;--editor-text-muted: #9ca3af}.editor-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative;overflow:hidden;background:var(--editor-bg)}.layout-desktop .editor-main{display:flex;flex:1;min-height:0}.layout-desktop .canvas-area{flex:1;min-width:0;position:relative;display:flex;flex-direction:column}.layout-desktop .area-panel{width:var(--editor-panel-width);border-left:1px solid var(--editor-border);background:var(--editor-surface);display:none;flex-direction:column;overflow:hidden}.layout-desktop .area-panel.area-panel-visible{display:flex}.layout-mobile .editor-main{display:flex;flex-direction:column;flex:1;min-height:0}.layout-mobile .canvas-area{flex:1;position:relative;display:flex;flex-direction:column;min-height:0}.layout-mobile .area-panel{position:absolute;bottom:0;left:0;right:0;max-height:50%;background:var(--editor-surface);border-top:1px solid var(--editor-border);border-radius:var(--editor-radius) var(--editor-radius) 0 0;box-shadow:0 -4px 20px #00000026;transform:translateY(100%);transition:transform .25s ease-out;z-index:20;display:flex;flex-direction:column;overflow:hidden}.layout-mobile .area-panel.area-panel-visible{transform:translateY(0)}.area-panel-backdrop{display:none}.layout-mobile .area-panel-backdrop{display:block;position:absolute;inset:0;background:#0000004d;z-index:15;opacity:0;pointer-events:none;transition:opacity .25s ease-out}.layout-mobile .area-panel-backdrop.area-panel-backdrop-visible{opacity:1;pointer-events:auto}.zoom-toolbar{display:flex;align-items:center;gap:4px;padding:8px 12px;background:var(--editor-surface);border-bottom:1px solid var(--editor-border)}.zoom-btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:14px;font-family:var(--editor-font);transition:background .15s;-webkit-tap-highlight-color:transparent;touch-action:manipulation}.zoom-btn:hover{background:var(--editor-bg)}.zoom-btn:active{background:var(--editor-border)}.zoom-percent{margin-left:8px;font-size:12px;color:var(--editor-text-muted);min-width:40px}.view-select{margin-left:8px;padding:4px 8px;height:32px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);font-family:var(--editor-font);font-size:12px;cursor:pointer;-webkit-tap-highlight-color:transparent}.view-select:hover{background:var(--editor-bg)}.view-select:focus{outline:2px solid var(--editor-primary);outline-offset:-1px}.zoom-toolbar-spacer{flex:1}.zoom-close-btn{width:auto;padding:0 12px;font-size:13px;gap:4px}.zoom-save-btn{width:auto;padding:0 16px;margin-left:8px;font-size:13px;font-weight:600;background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.zoom-save-btn:hover{background:var(--editor-primary-hover)}.zoom-save-btn:disabled{opacity:.5;cursor:not-allowed}.canvas-wrapper{flex:1;position:relative;overflow:hidden;min-height:200px}.editor-canvas{position:absolute;top:0;left:0}.area-panel-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--editor-border);flex-shrink:0}.area-panel-title{font-weight:600;font-size:14px}.area-panel-close-btn{width:28px;height:28px;border:none;background:none;color:var(--editor-text-muted);cursor:pointer;font-size:18px;line-height:1;border-radius:var(--editor-radius-sm);display:flex;align-items:center;justify-content:center}.area-panel-close-btn:hover{background:var(--editor-bg);color:var(--editor-text)}.area-panel-content{flex:1;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:12px}.area-panel-empty{color:var(--editor-text-muted);font-size:12px;text-align:center;padding:20px 0}.area-card{padding:12px;border:1px solid var(--editor-border);border-radius:var(--editor-radius);background:var(--editor-surface);cursor:pointer;transition:border-color .15s}.area-card:hover{border-color:var(--editor-primary)}.area-card-selected{border-color:var(--editor-primary);box-shadow:0 0 0 1px var(--editor-primary)}.area-card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}.area-card-name-row{display:flex;align-items:center;gap:6px}.area-card-name{font-weight:600;font-size:13px}.area-card-badge{display:inline-block;padding:1px 6px;font-size:10px;font-weight:500;background:#fef3c7;color:#92400e;border-radius:10px}.area-card-reset-btn{padding:2px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-text-muted);cursor:pointer;font-size:11px;font-family:var(--editor-font)}.area-card-reset-btn:hover{background:var(--editor-bg);color:var(--editor-danger);border-color:var(--editor-danger)}.area-card-mode-switcher{display:flex;gap:4px;margin-bottom:8px}.mode-btn{flex:1;padding:4px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:12px;font-family:var(--editor-font);transition:all .15s}.mode-btn-active{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.area-input-text{width:100%;padding:6px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:13px;color:var(--editor-text);background:var(--editor-surface);box-sizing:border-box;margin-bottom:6px}.area-input-text:focus{outline:none;border-color:var(--editor-primary);box-shadow:0 0 0 1px var(--editor-primary)}.area-input-row{display:flex;gap:6px;align-items:center;margin-bottom:6px}.area-input-font{flex:1;padding:4px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface);min-width:0}.area-input-size{width:56px;padding:4px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface)}.area-input-align{display:flex;gap:2px}.align-btn{width:28px;height:28px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:12px;font-weight:600;font-family:var(--editor-font)}.align-btn-active{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.area-input-color{width:32px;height:28px;padding:0;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);cursor:pointer;background:none}.area-upload-btn{width:100%;padding:10px;border:2px dashed var(--editor-border);border-radius:var(--editor-radius);background:var(--editor-bg);color:var(--editor-text-muted);cursor:pointer;font-family:var(--editor-font);font-size:12px;text-align:center;transition:all .15s;min-height:44px}.area-upload-btn:hover{border-color:var(--editor-primary);color:var(--editor-primary)}.area-image-preview{display:flex;gap:8px;align-items:center}.area-image-thumb{width:48px;height:48px;object-fit:cover;border-radius:var(--editor-radius-sm);border:1px solid var(--editor-border)}.area-image-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.area-image-name{font-size:12px;color:var(--editor-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.area-image-remove-btn{padding:2px 8px;border:1px solid var(--editor-danger);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-danger);cursor:pointer;font-size:11px;font-family:var(--editor-font);width:fit-content}.area-image-remove-btn:hover{background:#fef2f2}.area-card-divider{height:1px;background:var(--editor-border);margin:8px 0}.transform-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}.transform-title{font-weight:600;font-size:12px}.transform-reset-btn{padding:2px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-text-muted);cursor:pointer;font-size:11px;font-family:var(--editor-font)}.transform-reset-btn:hover{background:var(--editor-bg)}.transform-label{font-size:11px;color:var(--editor-text-muted);white-space:nowrap}.transform-input{width:60px;padding:3px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface)}.transform-slider-row{flex-direction:column;align-items:stretch}.transform-slider{width:100%;accent-color:var(--editor-primary)}.loading-container{display:flex;justify-content:center;align-items:center;width:100%;height:100%;min-height:200px}.loading-spinner{width:32px;height:32px;border:3px solid var(--editor-border);border-top-color:var(--editor-primary);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.error-container{display:flex;flex-direction:column;justify-content:center;align-items:center;width:100%;height:100%;min-height:200px;padding:20px;text-align:center}.error-title{font-weight:600;font-size:16px;color:var(--editor-danger);margin-bottom:8px}.error-message{font-size:13px;color:var(--editor-text-muted);max-width:400px;white-space:pre-wrap}:host(:focus){outline:2px solid var(--editor-primary);outline-offset:-2px}input:focus,select:focus,button:focus-visible{outline:2px solid var(--editor-primary);outline-offset:-1px}.context-menu{position:absolute;z-index:100;min-width:160px;background:var(--editor-surface);border:1px solid var(--editor-border);border-radius:var(--editor-radius);box-shadow:0 4px 12px #00000026;padding:4px 0;font-size:13px}.context-menu-header{padding:6px 12px;font-weight:600;font-size:12px;color:var(--editor-text-muted);user-select:none}.context-menu-divider{height:1px;background:var(--editor-border);margin:4px 0}.context-menu-item{padding:6px 12px;cursor:pointer;color:var(--editor-text);user-select:none;transition:background .1s}.context-menu-item:hover{background:var(--editor-bg)}.context-menu-item:active{background:var(--editor-border)}.context-menu-item-disabled{color:var(--editor-text-muted);cursor:default;pointer-events:none}.editor-toast{position:absolute;top:16px;left:50%;transform:translate(-50%);padding:10px 20px;border-radius:var(--editor-radius);font-size:13px;font-family:var(--editor-font);line-height:1.4;z-index:200;max-width:400px;text-align:center;animation:toast-in .25s ease-out,toast-out .25s ease-in forwards;animation-delay:0s,var(--toast-duration, 3.75s);pointer-events:none;box-shadow:0 4px 12px #00000026}.editor-toast-warning{background:#fef3c7;color:#92400e;border:1px solid #f59e0b}.editor-toast-error{background:#fef2f2;color:#991b1b;border:1px solid var(--editor-danger)}.editor-toast-info{background:#eff6ff;color:#1e40af;border:1px solid var(--editor-primary)}@keyframes toast-in{0%{opacity:0;transform:translate(-50%) translateY(-10px)}to{opacity:1;transform:translate(-50%) translateY(0)}}@keyframes toast-out{0%{opacity:1;transform:translate(-50%) translateY(0)}to{opacity:0;transform:translate(-50%) translateY(-10px)}}.editor-modal-overlay{position:absolute;inset:0;background:#0006;z-index:300;display:flex;align-items:center;justify-content:center;animation:modal-overlay-in .2s ease-out}@keyframes modal-overlay-in{0%{opacity:0}to{opacity:1}}.editor-modal{background:var(--editor-surface);border-radius:var(--editor-radius);box-shadow:0 8px 32px #0003;max-width:380px;width:calc(100% - 32px);padding:20px;animation:modal-in .2s ease-out}@keyframes modal-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}.editor-modal-title{font-weight:600;font-size:15px;margin-bottom:8px;color:var(--editor-text)}.editor-modal-body{font-size:13px;color:var(--editor-text-muted);margin-bottom:16px;line-height:1.5}.editor-modal-actions{display:flex;justify-content:flex-end;gap:8px}.editor-modal-btn{padding:8px 16px;border-radius:var(--editor-radius-sm);font-size:13px;font-family:var(--editor-font);cursor:pointer;border:1px solid var(--editor-border);background:var(--editor-surface);color:var(--editor-text);transition:background .15s}.editor-modal-btn:hover{background:var(--editor-bg)}.editor-modal-btn-primary{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.editor-modal-btn-primary:hover{background:var(--editor-primary-hover)}.area-validation-error{font-size:11px;color:var(--editor-danger);margin-top:4px}@media(pointer:coarse){.zoom-btn{min-width:44px;min-height:44px}.area-input-text,.area-input-font,.area-input-size{min-height:44px;font-size:16px}.align-btn{min-width:44px;min-height:44px}.area-upload-btn{min-height:48px}.view-select{min-height:44px;font-size:16px}}', Q = "2.0.0", _ = 768;
1584
- class ee extends HTMLElement {
1682
+ const k = ':host{--editor-bg: #f3f4f6;--editor-surface: #ffffff;--editor-border: #e5e7eb;--editor-text: #111827;--editor-text-muted: #6b7280;--editor-primary: #3b82f6;--editor-primary-hover: #2563eb;--editor-danger: #ef4444;--editor-radius: 8px;--editor-radius-sm: 4px;--editor-font: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--editor-panel-width: 280px;display:block;width:100%;height:100%;font-family:var(--editor-font);font-size:13px;color:var(--editor-text);line-height:1.4}:host([theme="dark"]){--editor-bg: #1f2937;--editor-surface: #374151;--editor-border: #4b5563;--editor-text: #f9fafb;--editor-text-muted: #9ca3af}.editor-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative;overflow:hidden;background:var(--editor-bg)}.layout-desktop .editor-main{display:flex;flex:1;min-height:0}.layout-desktop .canvas-area{flex:1;min-width:0;position:relative;display:flex;flex-direction:column}.layout-desktop .area-panel{width:var(--editor-panel-width);border-left:1px solid var(--editor-border);background:var(--editor-surface);display:none;flex-direction:column;overflow:hidden}.layout-desktop .area-panel.area-panel-visible{display:flex}.layout-mobile .editor-main{display:flex;flex-direction:column;flex:1;min-height:0}.layout-mobile .canvas-area{flex:1;position:relative;display:flex;flex-direction:column;min-height:0}.layout-mobile .area-panel{position:absolute;bottom:0;left:0;right:0;max-height:50%;background:var(--editor-surface);border-top:1px solid var(--editor-border);border-radius:var(--editor-radius) var(--editor-radius) 0 0;box-shadow:0 -4px 20px #00000026;transform:translateY(100%);transition:transform .25s ease-out;z-index:20;display:flex;flex-direction:column;overflow:hidden}.layout-mobile .area-panel.area-panel-visible{transform:translateY(0)}.area-panel-backdrop{display:none}.layout-mobile .area-panel-backdrop{display:block;position:absolute;inset:0;background:#0000004d;z-index:15;opacity:0;pointer-events:none;transition:opacity .25s ease-out}.layout-mobile .area-panel-backdrop.area-panel-backdrop-visible{opacity:1;pointer-events:auto}.zoom-toolbar{display:flex;align-items:center;gap:4px;padding:8px 12px;background:var(--editor-surface);border-bottom:1px solid var(--editor-border)}.zoom-btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:14px;font-family:var(--editor-font);transition:background .15s;-webkit-tap-highlight-color:transparent;touch-action:manipulation}.zoom-btn:hover{background:var(--editor-bg)}.zoom-btn:active{background:var(--editor-border)}.zoom-percent{margin-left:8px;font-size:12px;color:var(--editor-text-muted);min-width:40px}.view-select{margin-left:8px;padding:4px 8px;height:32px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);font-family:var(--editor-font);font-size:12px;cursor:pointer;-webkit-tap-highlight-color:transparent}.view-select:hover{background:var(--editor-bg)}.view-select:focus{outline:2px solid var(--editor-primary);outline-offset:-1px}.zoom-toolbar-spacer{flex:1}.zoom-close-btn{width:auto;padding:0 12px;font-size:13px;gap:4px}.zoom-save-btn{width:auto;padding:0 16px;margin-left:8px;font-size:13px;font-weight:600;background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.zoom-save-btn:hover{background:var(--editor-primary-hover)}.zoom-save-btn:disabled{opacity:.5;cursor:not-allowed}.canvas-wrapper{flex:1;position:relative;overflow:hidden;min-height:200px}.editor-canvas{position:absolute;top:0;left:0}.area-panel-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--editor-border);flex-shrink:0}.area-panel-title{font-weight:600;font-size:14px}.area-panel-close-btn{width:28px;height:28px;border:none;background:none;color:var(--editor-text-muted);cursor:pointer;font-size:18px;line-height:1;border-radius:var(--editor-radius-sm);display:flex;align-items:center;justify-content:center}.area-panel-close-btn:hover{background:var(--editor-bg);color:var(--editor-text)}.area-panel-content{flex:1;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:12px}.area-panel-empty{color:var(--editor-text-muted);font-size:12px;text-align:center;padding:20px 0}.area-card{padding:12px;border:1px solid var(--editor-border);border-radius:var(--editor-radius);background:var(--editor-surface);cursor:pointer;transition:border-color .15s}.area-card:hover{border-color:var(--editor-primary)}.area-card-selected{border-color:var(--editor-primary);box-shadow:0 0 0 1px var(--editor-primary)}.area-card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}.area-card-name-row{display:flex;align-items:center;gap:6px}.area-card-name{font-weight:600;font-size:13px}.area-card-badge{display:inline-block;padding:1px 6px;font-size:10px;font-weight:500;background:#fef3c7;color:#92400e;border-radius:10px}.area-card-reset-btn{padding:2px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-text-muted);cursor:pointer;font-size:11px;font-family:var(--editor-font)}.area-card-reset-btn:hover{background:var(--editor-bg);color:var(--editor-danger);border-color:var(--editor-danger)}.area-card-mode-switcher{display:flex;gap:4px;margin-bottom:8px}.mode-btn{flex:1;padding:4px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:12px;font-family:var(--editor-font);transition:all .15s}.mode-btn-active{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.area-input-text{width:100%;padding:6px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:13px;color:var(--editor-text);background:var(--editor-surface);box-sizing:border-box;margin-bottom:6px}.area-input-text:focus{outline:none;border-color:var(--editor-primary);box-shadow:0 0 0 1px var(--editor-primary)}.area-input-row{display:flex;gap:6px;align-items:center;margin-bottom:6px}.area-input-font{flex:1;padding:4px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface);min-width:0}.area-input-size{width:56px;padding:4px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface)}.area-input-align{display:flex;gap:2px}.align-btn{width:28px;height:28px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:12px;font-weight:600;font-family:var(--editor-font)}.align-btn-active{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.area-input-color{width:32px;height:28px;padding:0;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);cursor:pointer;background:none}.area-upload-btn{width:100%;padding:10px;border:2px dashed var(--editor-border);border-radius:var(--editor-radius);background:var(--editor-bg);color:var(--editor-text-muted);cursor:pointer;font-family:var(--editor-font);font-size:12px;text-align:center;transition:all .15s;min-height:44px}.area-upload-btn:hover{border-color:var(--editor-primary);color:var(--editor-primary)}.area-image-preview{display:flex;gap:8px;align-items:center}.area-image-thumb{width:48px;height:48px;object-fit:cover;border-radius:var(--editor-radius-sm);border:1px solid var(--editor-border)}.area-image-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.area-image-name{font-size:12px;color:var(--editor-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.area-image-remove-btn{padding:2px 8px;border:1px solid var(--editor-danger);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-danger);cursor:pointer;font-size:11px;font-family:var(--editor-font);width:fit-content}.area-image-remove-btn:hover{background:#fef2f2}.area-card-divider{height:1px;background:var(--editor-border);margin:8px 0}.transform-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}.transform-title{font-weight:600;font-size:12px}.transform-reset-btn{padding:2px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-text-muted);cursor:pointer;font-size:11px;font-family:var(--editor-font)}.transform-reset-btn:hover{background:var(--editor-bg)}.transform-label{font-size:11px;color:var(--editor-text-muted);white-space:nowrap}.transform-input{width:60px;padding:3px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface)}.transform-slider-row{flex-direction:column;align-items:stretch}.transform-slider{width:100%;accent-color:var(--editor-primary)}.area-image-preview{position:relative}.area-upload-spinner-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:#fff9;border-radius:var(--editor-radius-sm);z-index:1}:host([theme="dark"]) .area-upload-spinner-overlay{background:#37415199}.area-upload-spinner{width:16px;height:16px;border:2px solid var(--editor-border);border-top-color:var(--editor-primary);border-radius:50%;animation:spin .8s linear infinite}.loading-container{display:flex;justify-content:center;align-items:center;width:100%;height:100%;min-height:200px}.loading-spinner{width:32px;height:32px;border:3px solid var(--editor-border);border-top-color:var(--editor-primary);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.error-container{display:flex;flex-direction:column;justify-content:center;align-items:center;width:100%;height:100%;min-height:200px;padding:20px;text-align:center}.error-title{font-weight:600;font-size:16px;color:var(--editor-danger);margin-bottom:8px}.error-message{font-size:13px;color:var(--editor-text-muted);max-width:400px;white-space:pre-wrap}:host(:focus){outline:2px solid var(--editor-primary);outline-offset:-2px}input:focus,select:focus,button:focus-visible{outline:2px solid var(--editor-primary);outline-offset:-1px}.context-menu{position:absolute;z-index:100;min-width:160px;background:var(--editor-surface);border:1px solid var(--editor-border);border-radius:var(--editor-radius);box-shadow:0 4px 12px #00000026;padding:4px 0;font-size:13px}.context-menu-header{padding:6px 12px;font-weight:600;font-size:12px;color:var(--editor-text-muted);user-select:none}.context-menu-divider{height:1px;background:var(--editor-border);margin:4px 0}.context-menu-item{padding:6px 12px;cursor:pointer;color:var(--editor-text);user-select:none;transition:background .1s}.context-menu-item:hover{background:var(--editor-bg)}.context-menu-item:active{background:var(--editor-border)}.context-menu-item-disabled{color:var(--editor-text-muted);cursor:default;pointer-events:none}.editor-toast{position:absolute;top:16px;left:50%;transform:translate(-50%);padding:10px 20px;border-radius:var(--editor-radius);font-size:13px;font-family:var(--editor-font);line-height:1.4;z-index:200;max-width:400px;text-align:center;animation:toast-in .25s ease-out,toast-out .25s ease-in forwards;animation-delay:0s,var(--toast-duration, 3.75s);pointer-events:none;box-shadow:0 4px 12px #00000026}.editor-toast-warning{background:#fef3c7;color:#92400e;border:1px solid #f59e0b}.editor-toast-error{background:#fef2f2;color:#991b1b;border:1px solid var(--editor-danger)}.editor-toast-info{background:#eff6ff;color:#1e40af;border:1px solid var(--editor-primary)}@keyframes toast-in{0%{opacity:0;transform:translate(-50%) translateY(-10px)}to{opacity:1;transform:translate(-50%) translateY(0)}}@keyframes toast-out{0%{opacity:1;transform:translate(-50%) translateY(0)}to{opacity:0;transform:translate(-50%) translateY(-10px)}}.editor-modal-overlay{position:absolute;inset:0;background:#0006;z-index:300;display:flex;align-items:center;justify-content:center;animation:modal-overlay-in .2s ease-out}@keyframes modal-overlay-in{0%{opacity:0}to{opacity:1}}.editor-modal{background:var(--editor-surface);border-radius:var(--editor-radius);box-shadow:0 8px 32px #0003;max-width:380px;width:calc(100% - 32px);padding:20px;animation:modal-in .2s ease-out}@keyframes modal-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}.editor-modal-title{font-weight:600;font-size:15px;margin-bottom:8px;color:var(--editor-text)}.editor-modal-body{font-size:13px;color:var(--editor-text-muted);margin-bottom:16px;line-height:1.5}.editor-modal-actions{display:flex;justify-content:flex-end;gap:8px}.editor-modal-btn{padding:8px 16px;border-radius:var(--editor-radius-sm);font-size:13px;font-family:var(--editor-font);cursor:pointer;border:1px solid var(--editor-border);background:var(--editor-surface);color:var(--editor-text);transition:background .15s}.editor-modal-btn:hover{background:var(--editor-bg)}.editor-modal-btn-primary{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.editor-modal-btn-primary:hover{background:var(--editor-primary-hover)}.area-validation-error{font-size:11px;color:var(--editor-danger);margin-top:4px}@media(pointer:coarse){.zoom-toolbar{padding:6px 8px;gap:2px}.zoom-btn{min-width:44px;min-height:44px}.zoom-percent{display:none}.view-select{max-width:110px;text-overflow:ellipsis;overflow:hidden;min-height:44px;font-size:16px}.zoom-close-btn,.zoom-save-btn{padding:0 8px}.area-input-text,.area-input-font,.area-input-size{min-height:44px;font-size:16px}.align-btn{min-width:44px;min-height:44px}.area-upload-btn{min-height:48px}}', _ = "2.0.0", ee = 768;
1683
+ class I extends HTMLElement {
1585
1684
  constructor() {
1586
- super(), this.resizeObserver = null, this.isReady = !1, this.currentTemplate = null, this.loadingTemplateId = null, this.isMobileLayout = !1, this.productViews = [], this.activeViewName = null, this.perViewContents = /* @__PURE__ */ new Map(), this.subscriptions = [], this.storageUsage = null, this.storageUsageLastRefresh = 0, this.emitChange = $(() => {
1685
+ super(), this.resizeObserver = null, this.pendingUploads = /* @__PURE__ */ new Map(), this.isReady = !1, this.currentTemplate = null, this.loadingTemplateId = null, this.isMobileLayout = !1, this.productViews = [], this.activeViewName = null, this.perViewContents = /* @__PURE__ */ new Map(), this.subscriptions = [], this.renderAbortController = null, this.contentManagerUnsub = null, this.refitRafId = null, this.storageUsage = null, this.storageUsageLastRefresh = 0, this.emitChange = q(() => {
1587
1686
  this.dispatchEvent(
1588
1687
  new CustomEvent("change", {
1589
1688
  detail: { design: this.getDesign() },
@@ -1614,7 +1713,7 @@ class ee extends HTMLElement {
1614
1713
  if (!this.currentTemplate) throw new Error("No design loaded");
1615
1714
  return {
1616
1715
  templateId: this.currentTemplate.metadata.id,
1617
- engineVersion: Q,
1716
+ engineVersion: _,
1618
1717
  contents: this.contentManager.toJSON(),
1619
1718
  userData: {}
1620
1719
  };
@@ -1673,37 +1772,176 @@ class ee extends HTMLElement {
1673
1772
  getAreaContent(e) {
1674
1773
  return this.contentManager.getContent(e);
1675
1774
  }
1775
+ static hasEditorContent(e) {
1776
+ return e.type === "text" ? e.text.trim().length > 0 : e.type === "image" ? !!e.dataUrl : !1;
1777
+ }
1778
+ validateRequiredContent() {
1779
+ const e = [];
1780
+ if (this.productViews.length > 1) {
1781
+ this.activeViewName && this.contentManager && this.perViewContents.set(this.activeViewName, this.contentManager.toJSON());
1782
+ for (const t of this.productViews) {
1783
+ const i = this.perViewContents.get(t.viewName), s = i?.some(([, o]) => I.hasEditorContent(o)) ?? !1;
1784
+ if (t.isRequired && !s) {
1785
+ e.push(`View "${t.viewName}" is required`);
1786
+ continue;
1787
+ }
1788
+ const n = t.template.templateJson.areas || [];
1789
+ for (const o of n) {
1790
+ if (!o.required) continue;
1791
+ const a = i?.find(([r]) => r === o.id);
1792
+ a && I.hasEditorContent(a[1]) || e.push(`"${o.name}" in view "${t.viewName}" is required`);
1793
+ }
1794
+ }
1795
+ } else {
1796
+ const t = this.currentTemplate?.areas || [], i = this.contentManager.getContents();
1797
+ for (const s of t) {
1798
+ if (!s.required) continue;
1799
+ const n = i.get(s.id);
1800
+ n && I.hasEditorContent(n) || e.push(`"${s.name}" is required`);
1801
+ }
1802
+ }
1803
+ return e;
1804
+ }
1805
+ showValidationErrors(e) {
1806
+ return new Promise((t) => {
1807
+ const i = h("div", { class: "editor-modal-overlay" }), s = h("div", { class: "editor-modal" });
1808
+ s.appendChild(h("div", { class: "editor-modal-title" }, "Required content missing"));
1809
+ const n = h("div", { class: "editor-modal-body" }), o = document.createElement("ul");
1810
+ o.style.margin = "8px 0", o.style.paddingLeft = "20px";
1811
+ for (const d of e) {
1812
+ const l = document.createElement("li");
1813
+ l.textContent = d, o.appendChild(l);
1814
+ }
1815
+ n.appendChild(o), s.appendChild(n);
1816
+ const a = h("div", { class: "editor-modal-actions" }), r = h("button", { class: "editor-modal-btn editor-modal-btn-primary" }, "OK");
1817
+ r.addEventListener("click", () => {
1818
+ i.remove(), t();
1819
+ }), a.appendChild(r), s.appendChild(a), i.appendChild(s), i.addEventListener("click", (d) => {
1820
+ d.target === i && (i.remove(), t());
1821
+ }), this.shadow.appendChild(i);
1822
+ });
1823
+ }
1676
1824
  async finalize() {
1677
- const e = this.engine.checkSafeAreaViolations();
1678
- if (e.length > 0) {
1679
- const a = this.currentTemplate?.areas || [], o = e.map((n) => a.find((r) => r.id === n)?.name || n).join(", ");
1825
+ const e = this.validateRequiredContent();
1826
+ if (e.length > 0)
1827
+ return await this.showValidationErrors(e), null;
1828
+ const t = this.engine.checkSafeAreaViolations();
1829
+ if (t.length > 0) {
1830
+ const o = this.currentTemplate?.areas || [], a = t.map((r) => o.find((d) => d.id === r)?.name || r).join(", ");
1680
1831
  if (!await this.showConfirmation(
1681
1832
  "Content outside safe area",
1682
- `Content in ${o} extends beyond the safe area. It may be trimmed during printing. Continue?`,
1833
+ `Content in ${a} extends beyond the safe area. It may be trimmed during printing. Continue?`,
1683
1834
  "Cancel",
1684
1835
  "Proceed Anyway"
1685
1836
  ))
1686
1837
  throw new Error("Finalization cancelled by user");
1687
1838
  }
1688
- const t = this.getDesign(), i = this.getAttribute("store-id") || "demo-shop", s = await this.apiClient.finalizeDesign(i, t);
1839
+ const i = this.getAttribute("store-id") || "demo-shop";
1840
+ if (this.pendingUploads.size > 0 && await Promise.allSettled(this.pendingUploads.values()), this.productViews.length > 1) {
1841
+ this.activeViewName && this.contentManager && this.perViewContents.set(this.activeViewName, this.contentManager.toJSON());
1842
+ const o = [];
1843
+ for (const c of this.productViews) {
1844
+ const p = this.perViewContents.get(c.viewName);
1845
+ !p || p.length === 0 || !p.some(([, u]) => I.hasEditorContent(u)) || o.push({
1846
+ templateId: c.template.id,
1847
+ viewName: c.viewName,
1848
+ contents: p
1849
+ });
1850
+ }
1851
+ if (o.length === 0)
1852
+ throw new Error("No views have content to finalize");
1853
+ const a = this.getAttribute("product-id");
1854
+ if (!a)
1855
+ throw new Error("product-id attribute is required for multi-view finalize");
1856
+ const r = await this.apiClient.finalizeMultiView({
1857
+ productId: a,
1858
+ designs: o
1859
+ }), d = r.views.map((c) => ({
1860
+ viewName: c.viewName,
1861
+ designId: c.designId,
1862
+ proofUrl: c.proofUrl || "",
1863
+ status: c.status
1864
+ })), l = d[0];
1865
+ return this.dispatchEvent(
1866
+ new CustomEvent("customizer:finalize", {
1867
+ detail: {
1868
+ designId: l.designId,
1869
+ proofUrl: l.proofUrl,
1870
+ status: "processing",
1871
+ views: d,
1872
+ requestId: r.requestId
1873
+ },
1874
+ bubbles: !0,
1875
+ composed: !0
1876
+ })
1877
+ ), { ...l, views: d };
1878
+ }
1879
+ const s = this.getDesign(), n = await this.apiClient.finalizeDesign(i, s);
1689
1880
  return this.dispatchEvent(
1690
1881
  new CustomEvent("customizer:finalize", {
1691
1882
  detail: {
1692
- designId: s.designId,
1693
- proofUrl: s.proofUrl,
1694
- templateId: t.templateId,
1695
- designJson: t,
1696
- status: s.status
1883
+ designId: n.designId,
1884
+ proofUrl: n.proofUrl,
1885
+ templateId: s.templateId,
1886
+ designJson: s,
1887
+ status: n.status
1697
1888
  },
1698
1889
  bubbles: !0,
1699
1890
  composed: !0
1700
1891
  })
1701
- ), s;
1892
+ ), n;
1893
+ }
1894
+ async waitForResult(e, t) {
1895
+ const i = t?.pollInterval ?? 1500, s = t?.maxPolls ?? 40, n = t?.signal;
1896
+ let o = 0;
1897
+ for (; o < s; ) {
1898
+ if (n?.aborted)
1899
+ throw new DOMException("Polling aborted", "AbortError");
1900
+ await new Promise((d, l) => {
1901
+ const c = setTimeout(d, i);
1902
+ n?.addEventListener("abort", () => {
1903
+ clearTimeout(c), l(new DOMException("Polling aborted", "AbortError"));
1904
+ }, { once: !0 });
1905
+ }), o++;
1906
+ let r;
1907
+ if (e.length === 1) {
1908
+ const d = await this.apiClient.getDesignStatus(e[0]);
1909
+ r = {
1910
+ designs: [d],
1911
+ allCompleted: d.status !== "processing"
1912
+ };
1913
+ } else
1914
+ r = await this.apiClient.pollMultiViewStatus(e);
1915
+ if (r.allCompleted)
1916
+ return this.dispatchEvent(
1917
+ new CustomEvent("customizer:render-complete", {
1918
+ detail: r,
1919
+ bubbles: !0,
1920
+ composed: !0
1921
+ })
1922
+ ), r;
1923
+ }
1924
+ const a = {
1925
+ designs: e.map((r) => ({
1926
+ designId: r,
1927
+ status: "failed",
1928
+ proofUrl: null,
1929
+ errorMessage: "Render timed out"
1930
+ })),
1931
+ allCompleted: !0
1932
+ };
1933
+ return this.dispatchEvent(
1934
+ new CustomEvent("customizer:render-complete", {
1935
+ detail: a,
1936
+ bubbles: !0,
1937
+ composed: !0
1938
+ })
1939
+ ), a;
1702
1940
  }
1703
1941
  // ─── Initialization ───
1704
1942
  renderLoading() {
1705
1943
  const e = document.createElement("style");
1706
- e.textContent = M, this.shadow.appendChild(e);
1944
+ e.textContent = k, this.shadow.appendChild(e);
1707
1945
  const t = h(
1708
1946
  "div",
1709
1947
  { class: "loading-container" },
@@ -1713,7 +1951,7 @@ class ee extends HTMLElement {
1713
1951
  }
1714
1952
  async initialize() {
1715
1953
  const e = this.getAttribute("api-url") || "http://localhost:4000";
1716
- this.apiClient = new F(`${e.replace(/\/+$/, "")}/public`);
1954
+ this.apiClient = new B(`${e.replace(/\/+$/, "")}/public`);
1717
1955
  const t = this.getAttribute("product-id"), i = this.getAttribute("template-id");
1718
1956
  if (!t && !i)
1719
1957
  throw new Error("Either template-id or product-id attribute is required");
@@ -1734,7 +1972,7 @@ class ee extends HTMLElement {
1734
1972
  async loadTemplateData(e) {
1735
1973
  this.currentTemplate = e;
1736
1974
  const t = e.areas || [];
1737
- this.contentManager = new k(t);
1975
+ this.contentManager = new P(t);
1738
1976
  const i = {
1739
1977
  template: e,
1740
1978
  contents: this.contentManager.toJSON(),
@@ -1744,27 +1982,22 @@ class ee extends HTMLElement {
1744
1982
  isDirty: !1,
1745
1983
  warnings: []
1746
1984
  };
1747
- this.stateManager = new V(i), this.buildUI(e, t), this.engine = new N(this.canvas), this.engine.setArtboard({
1985
+ this.stateManager = new F(i), this.buildUI(e, t), this.engine = new N(this.canvas), this.engine.setArtboard({
1748
1986
  width: e.artboard.width,
1749
1987
  height: e.artboard.height,
1750
1988
  backgroundColor: e.artboard.backgroundColor ?? "#ffffff"
1751
1989
  }), this.engine.setAreas(t), this.engine.setContents(this.contentManager.getContents()), e.backgroundImage && this.engine.setBackgroundImage(e.backgroundImage);
1752
1990
  const s = e.artboard.safeArea ?? e.safeArea;
1753
- s && this.engine.setSafeArea(s), this.engine.start(), this.engine.fitToView(), this.stateManager.update({
1754
- zoom: this.engine.getZoom(),
1755
- pan: this.engine.getPan()
1756
- }), this.zoomToolbar.setZoom(this.engine.getZoom()), this.interaction = new B(
1991
+ s && this.engine.setSafeArea(s), this.engine.start(), this.scheduleCanvasRefit(), this.interaction = new $(
1757
1992
  this.canvas,
1758
1993
  this.engine,
1759
1994
  () => this.contentManager.getContents(),
1760
1995
  () => this.stateManager.getState().selectedAreaId
1761
- ), this.wireInteractionEvents(), this.subscriptions.push(
1762
- this.contentManager.subscribe(() => {
1763
- this.syncEngineContents(), this.inputPanel.setContents(this.contentManager.getContents());
1764
- })
1765
- ), this.keyboard = new X(this), this.setupKeyboardShortcuts(), this.inputPanel.setContents(this.contentManager.getContents());
1766
- const a = this.getAttribute("store-id");
1767
- a && this.apiClient.getStorageUsage(a).then((o) => {
1996
+ ), this.wireInteractionEvents(), this.contentManagerUnsub = this.contentManager.subscribe(() => {
1997
+ this.syncEngineContents(), this.inputPanel.setContents(this.contentManager.getContents());
1998
+ }), this.subscriptions.push(this.contentManagerUnsub), this.keyboard = new W(this), this.setupKeyboardShortcuts(), this.inputPanel.setContents(this.contentManager.getContents());
1999
+ const n = this.getAttribute("store-id");
2000
+ n && this.apiClient.getStorageUsage(n).then((o) => {
1768
2001
  this.storageUsage = o, o.usagePercent >= 100 ? this.showToast("Storage quota exceeded. Upgrade your plan for more storage.", "error") : o.usagePercent >= 80 && this.showToast(`Storage ${o.usagePercent}% full. Consider upgrading your plan.`, "warning");
1769
2002
  }).catch(() => {
1770
2003
  });
@@ -1786,15 +2019,17 @@ class ee extends HTMLElement {
1786
2019
  const t = e.template.templateJson;
1787
2020
  this.currentTemplate = t;
1788
2021
  const i = t.areas || [];
1789
- this.contentManager = new k(i);
2022
+ this.contentManager = new P(i), this.contentManagerUnsub && this.contentManagerUnsub(), this.contentManagerUnsub = this.contentManager.subscribe(() => {
2023
+ this.syncEngineContents(), this.inputPanel.setContents(this.contentManager.getContents());
2024
+ });
1790
2025
  const s = this.perViewContents.get(e.viewName);
1791
2026
  s && this.contentManager.fromJSON(s), this.engine.setArtboard({
1792
2027
  width: t.artboard.width,
1793
2028
  height: t.artboard.height,
1794
2029
  backgroundColor: t.artboard.backgroundColor ?? "#ffffff"
1795
2030
  }), this.engine.setAreas(i), this.engine.setContents(this.contentManager.getContents()), this.engine.setBackgroundImage(t.backgroundImage ?? void 0);
1796
- const a = t.artboard.safeArea ?? t.safeArea;
1797
- this.engine.setSafeArea(a ?? null), this.engine.fitToView(), this.stateManager.setState({
2031
+ const n = t.artboard.safeArea ?? t.safeArea;
2032
+ this.engine.setSafeArea(n ?? null), this.engine.fitToView(), this.stateManager.setState({
1798
2033
  template: t,
1799
2034
  contents: this.contentManager.toJSON(),
1800
2035
  selectedAreaId: null,
@@ -1813,37 +2048,45 @@ class ee extends HTMLElement {
1813
2048
  buildUI(e, t) {
1814
2049
  this.shadow.innerHTML = "";
1815
2050
  const i = document.createElement("style");
1816
- i.textContent = M, this.shadow.appendChild(i), this.container = h("div", { class: "editor-container" }), this.zoomToolbar = new H(), this.subscriptions.push(this.zoomToolbar.on("zoom-in", () => this.handleZoomIn())), this.subscriptions.push(this.zoomToolbar.on("zoom-out", () => this.handleZoomOut())), this.subscriptions.push(this.zoomToolbar.on("zoom-fit", () => this.handleZoomFit())), this.subscriptions.push(this.zoomToolbar.on("view-change", ({ viewName: o }) => {
1817
- const n = this.productViews.find((r) => r.viewName === o);
1818
- n && this.switchToView(n);
2051
+ i.textContent = k, this.shadow.appendChild(i), this.container = h("div", { class: "editor-container" }), this.zoomToolbar = new H(), this.subscriptions.push(this.zoomToolbar.on("zoom-in", () => this.handleZoomIn())), this.subscriptions.push(this.zoomToolbar.on("zoom-out", () => this.handleZoomOut())), this.subscriptions.push(this.zoomToolbar.on("zoom-fit", () => this.handleZoomFit())), this.subscriptions.push(this.zoomToolbar.on("view-change", ({ viewName: o }) => {
2052
+ const a = this.productViews.find((r) => r.viewName === o);
2053
+ a && this.switchToView(a);
1819
2054
  })), this.subscriptions.push(this.zoomToolbar.on("close", () => {
1820
2055
  this.dispatchEvent(new CustomEvent("customizer:close", {
1821
2056
  bubbles: !0,
1822
2057
  composed: !0
1823
2058
  }));
1824
2059
  })), this.subscriptions.push(this.zoomToolbar.on("save", async () => {
1825
- this.zoomToolbar.setSaveDisabled(!0, "Saving...");
2060
+ this.pendingUploads.size > 0 && (this.zoomToolbar.setSaveDisabled(!0, "Uploading..."), await Promise.allSettled(this.pendingUploads.values())), this.zoomToolbar.setSaveDisabled(!0, "Saving...");
1826
2061
  try {
1827
2062
  const o = await this.finalize();
2063
+ if (!o) {
2064
+ this.zoomToolbar.setSaveDisabled(!1, "✓");
2065
+ return;
2066
+ }
1828
2067
  this.zoomToolbar.setSaveDisabled(!0, "Saved!"), this.showToast("Design saved successfully!", "info"), this.dispatchEvent(new CustomEvent("customizer:save", {
1829
2068
  detail: o,
1830
2069
  bubbles: !0,
1831
2070
  composed: !0
1832
- })), setTimeout(() => {
1833
- this.zoomToolbar.setSaveDisabled(!1, "Save Customization"), this.zoomToolbar.showSaveButton(!1), this.stateManager.update({ isDirty: !1 });
2071
+ }));
2072
+ const a = o.views ? o.views.map((r) => r.designId) : [o.designId];
2073
+ this.renderAbortController?.abort(), this.renderAbortController = new AbortController(), this.waitForResult(a, { signal: this.renderAbortController.signal }).catch((r) => {
2074
+ r instanceof DOMException && r.name === "AbortError" || console.error("Customizer: background render polling failed", r);
2075
+ }), setTimeout(() => {
2076
+ this.zoomToolbar.setSaveDisabled(!1, "✓"), this.zoomToolbar.showSaveButton(!1), this.stateManager.update({ isDirty: !1 });
1834
2077
  }, 2e3);
1835
2078
  } catch (o) {
1836
2079
  console.error("Customizer: finalize failed", o);
1837
- const n = o instanceof Error ? o.message : "Save failed. Please try again.";
1838
- this.showToast(n, "error"), this.zoomToolbar.setSaveDisabled(!1, "Save Customization");
2080
+ const a = o instanceof Error ? o.message : "Save failed. Please try again.";
2081
+ this.showToast(a, "error"), this.zoomToolbar.setSaveDisabled(!1, "");
1839
2082
  }
1840
2083
  })), this.getAttribute("show-close-button") === "false" && this.zoomToolbar.setCloseButtonVisible(!1), this.getAttribute("show-save-button") === "false" && this.zoomToolbar.setSaveButtonEnabled(!1);
1841
2084
  const s = h("div", { class: "canvas-area" });
1842
- s.appendChild(this.zoomToolbar.getElement()), this.canvasWrapper = h("div", { class: "canvas-wrapper" }), this.canvas = document.createElement("canvas"), this.canvas.className = "editor-canvas", this.canvasWrapper.appendChild(this.canvas), s.appendChild(this.canvasWrapper), this.inputPanel = new j(t), this.wireInputPanelEvents();
1843
- const a = h("div", { class: "editor-main" });
1844
- a.appendChild(s), a.appendChild(this.inputPanel.getBackdrop()), a.appendChild(this.inputPanel.getElement()), this.container.appendChild(a), this.shadow.appendChild(this.container), this.resizeObserver = new ResizeObserver((o) => {
1845
- for (const n of o)
1846
- n.target === this.canvasWrapper && this.handleCanvasResize(n.contentRect), n.target === this.container && this.handleLayoutResize(n.contentRect);
2085
+ s.appendChild(this.zoomToolbar.getElement()), this.canvasWrapper = h("div", { class: "canvas-wrapper" }), this.canvas = document.createElement("canvas"), this.canvas.className = "editor-canvas", this.canvasWrapper.appendChild(this.canvas), s.appendChild(this.canvasWrapper), this.inputPanel = new J(t), this.wireInputPanelEvents();
2086
+ const n = h("div", { class: "editor-main" });
2087
+ n.appendChild(s), n.appendChild(this.inputPanel.getBackdrop()), n.appendChild(this.inputPanel.getElement()), this.container.appendChild(n), this.shadow.appendChild(this.container), this.resizeObserver = new ResizeObserver((o) => {
2088
+ for (const a of o)
2089
+ a.target === this.canvasWrapper && this.handleCanvasResize(a.contentRect), a.target === this.container && this.handleLayoutResize(a.contentRect);
1847
2090
  }), this.resizeObserver.observe(this.canvasWrapper), this.resizeObserver.observe(this.container);
1848
2091
  }
1849
2092
  // ─── Event Wiring ───
@@ -1892,10 +2135,10 @@ class ee extends HTMLElement {
1892
2135
  ), this.subscriptions.push(
1893
2136
  this.inputPanel.on("image:change", async ({ areaId: e, dataUrl: t, filename: i }) => {
1894
2137
  const s = this.contentManager.getContents();
1895
- let a = 0;
1896
- for (const [n, r] of s)
1897
- r.type === "image" && n !== e && a++;
1898
- if (a >= 10) {
2138
+ let n = 0;
2139
+ for (const [r, d] of s)
2140
+ d.type === "image" && r !== e && n++;
2141
+ if (n >= 10) {
1899
2142
  this.showToast("Maximum of 10 images per design reached", "error");
1900
2143
  return;
1901
2144
  }
@@ -1903,28 +2146,30 @@ class ee extends HTMLElement {
1903
2146
  this.showToast("Storage quota exceeded. Upgrade your plan for more storage.", "error");
1904
2147
  return;
1905
2148
  }
1906
- const o = this.currentTemplate?.areas.find((n) => n.id === e);
2149
+ const o = this.currentTemplate?.areas.find((r) => r.id === e);
1907
2150
  if (o && this.currentTemplate) {
1908
- const n = this.currentTemplate.print.targetDpi;
2151
+ const r = this.currentTemplate.print.targetDpi;
1909
2152
  try {
1910
- const r = new Image(), d = await new Promise((p, g) => {
1911
- r.onload = () => p({ width: r.naturalWidth, height: r.naturalHeight }), r.onerror = () => g(new Error("Failed to load image")), r.src = t;
1912
- }), l = o.location.width / n, c = o.location.height / n, u = K(
1913
- d.width,
1914
- d.height,
1915
- l,
2153
+ const d = new Image();
2154
+ d.crossOrigin = "anonymous";
2155
+ const l = await new Promise((g, m) => {
2156
+ d.onload = () => g({ width: d.naturalWidth, height: d.naturalHeight }), d.onerror = () => m(new Error("Failed to load image")), d.src = t;
2157
+ }), c = o.location.width / r, p = o.location.height / r, u = Q(
2158
+ l.width,
2159
+ l.height,
1916
2160
  c,
1917
- n
2161
+ p,
2162
+ r
1918
2163
  );
1919
2164
  if (u.actualDPI < 150) {
1920
2165
  if (!await this.showConfirmation(
1921
2166
  "Low resolution detected",
1922
- `This image is ${u.actualDPI} DPI at print size. Print may appear blurry. Recommended: ${n} DPI.`,
2167
+ `This image is ${u.actualDPI} DPI at print size. Print may appear blurry. Recommended: ${r} DPI.`,
1923
2168
  "Cancel",
1924
2169
  "Use Anyway"
1925
2170
  )) return;
1926
- } else u.actualDPI < n && (this.showToast(
1927
- `Image resolution is ${u.actualDPI} DPI (${n} recommended)`,
2171
+ } else u.actualDPI < r && (this.showToast(
2172
+ `Image resolution is ${u.actualDPI} DPI (${r} recommended)`,
1928
2173
  "warning"
1929
2174
  ), this.stateManager.update({
1930
2175
  warnings: [
@@ -1935,9 +2180,11 @@ class ee extends HTMLElement {
1935
2180
  } catch {
1936
2181
  }
1937
2182
  }
1938
- if (this.contentManager.setImageContent(e, t, i), this.saveContentState(), this.storageUsage) {
1939
- const n = Math.round(t.length * 0.75);
1940
- this.storageUsage.storageUsed += n, this.storageUsage.usagePercent = this.storageUsage.storageQuota > 0 ? Math.round(this.storageUsage.storageUsed / this.storageUsage.storageQuota * 100) : 0;
2183
+ this.contentManager.setImageContent(e, t, i), this.saveContentState();
2184
+ const a = this.getAttribute("store-id");
2185
+ if (a && this.preUploadImage(e, t, a), this.storageUsage) {
2186
+ const r = Math.round(t.length * 0.75);
2187
+ this.storageUsage.storageUsed += r, this.storageUsage.usagePercent = this.storageUsage.storageQuota > 0 ? Math.round(this.storageUsage.storageUsed / this.storageUsage.storageQuota * 100) : 0;
1941
2188
  }
1942
2189
  })
1943
2190
  ), this.subscriptions.push(
@@ -1946,7 +2193,7 @@ class ee extends HTMLElement {
1946
2193
  })
1947
2194
  ), this.subscriptions.push(
1948
2195
  this.inputPanel.on("clear", ({ areaId: e }) => {
1949
- this.contentManager.clearContent(e), this.saveContentState();
2196
+ this.contentManager.clearContent(e), this.syncEngineContents(), this.inputPanel.setContents(this.contentManager.getContents()), this.saveContentState();
1950
2197
  })
1951
2198
  ), this.subscriptions.push(
1952
2199
  this.inputPanel.on("select", ({ areaId: e }) => {
@@ -1994,6 +2241,19 @@ class ee extends HTMLElement {
1994
2241
  handleZoomFit() {
1995
2242
  this.engine.fitToView(), this.stateManager.update({ zoom: this.engine.getZoom(), pan: this.engine.getPan() }), this.zoomToolbar.setZoom(this.engine.getZoom());
1996
2243
  }
2244
+ /**
2245
+ * Schedule a canvas refit after layout settles (double-rAF ensures
2246
+ * ResizeObserver callbacks and layout-class changes have been applied).
2247
+ */
2248
+ scheduleCanvasRefit() {
2249
+ this.refitRafId != null && cancelAnimationFrame(this.refitRafId), this.refitRafId = requestAnimationFrame(() => {
2250
+ this.refitRafId = requestAnimationFrame(() => {
2251
+ if (this.refitRafId = null, !this.canvasWrapper || !this.engine) return;
2252
+ const e = this.canvasWrapper.getBoundingClientRect();
2253
+ e.width > 0 && e.height > 0 && this.handleCanvasResize(e);
2254
+ });
2255
+ });
2256
+ }
1997
2257
  handleCanvasResize(e) {
1998
2258
  const { width: t, height: i } = e;
1999
2259
  if (t === 0 || i === 0) return;
@@ -2001,8 +2261,8 @@ class ee extends HTMLElement {
2001
2261
  this.canvas.width = t * s, this.canvas.height = i * s, this.canvas.style.width = `${t}px`, this.canvas.style.height = `${i}px`, this.engine && (this.engine.fitToView(), this.stateManager.update({ zoom: this.engine.getZoom(), pan: this.engine.getPan() }), this.zoomToolbar.setZoom(this.engine.getZoom()));
2002
2262
  }
2003
2263
  handleLayoutResize(e) {
2004
- const t = e.width < _;
2005
- this.container.classList.toggle("layout-desktop", !t), this.container.classList.toggle("layout-mobile", t), t !== this.isMobileLayout && (this.isMobileLayout = t, this.inputPanel.setMobile(t));
2264
+ const t = e.width < ee;
2265
+ this.container.classList.toggle("layout-desktop", !t), this.container.classList.toggle("layout-mobile", t), t !== this.isMobileLayout && (this.isMobileLayout = t, this.inputPanel.setMobile(t), this.scheduleCanvasRefit());
2006
2266
  }
2007
2267
  // ─── State Management ───
2008
2268
  async refreshStorageUsage() {
@@ -2014,6 +2274,42 @@ class ee extends HTMLElement {
2014
2274
  } catch {
2015
2275
  }
2016
2276
  }
2277
+ /**
2278
+ * Pre-upload an image to the server so finalize doesn't need to send base64.
2279
+ * On success, updates the content manager with assetId/assetUrl.
2280
+ * Tracks the promise so finalize() can await it.
2281
+ */
2282
+ preUploadImage(e, t, i) {
2283
+ const s = this.activeViewName, n = this._doPreUpload(e, t, i, s);
2284
+ return this.pendingUploads.set(e, n), this.inputPanel?.setUploading(e, !0), this.updateSaveButtonForUploads(), n.finally(() => {
2285
+ this.pendingUploads.get(e) === n && this.pendingUploads.delete(e), this.inputPanel?.setUploading(e, !1), this.updateSaveButtonForUploads();
2286
+ }), n;
2287
+ }
2288
+ async _doPreUpload(e, t, i, s) {
2289
+ const [n, o] = t.split(","), a = n.match(/:(.*?);/)?.[1] || "image/png", r = atob(o), d = new Uint8Array(r.length);
2290
+ for (let g = 0; g < r.length; g++) d[g] = r.charCodeAt(g);
2291
+ const l = new Blob([d], { type: a }), c = a.split("/")[1] || "png", p = new File([l], `upload.${c}`, { type: a });
2292
+ let u;
2293
+ try {
2294
+ u = await this.apiClient.uploadAsset(p, i);
2295
+ } catch (g) {
2296
+ console.error("Customizer: image pre-upload failed", g), this.showToast("Image upload failed. Will retry on save.", "warning");
2297
+ return;
2298
+ }
2299
+ if (this.activeViewName === s) {
2300
+ const g = this.contentManager.getContent(e);
2301
+ g?.type === "image" && g.dataUrl === t && this.contentManager.setImageContent(e, u.publicUrl, g.filename, {
2302
+ assetId: u.assetId,
2303
+ assetUrl: u.publicUrl
2304
+ });
2305
+ } else if (s) {
2306
+ const g = this.perViewContents.get(s);
2307
+ if (g) {
2308
+ const m = g.map(([v, b]) => v === e && b.type === "image" && b.dataUrl === t ? [v, { ...b, dataUrl: u.publicUrl, assetId: u.assetId, assetUrl: u.publicUrl }] : [v, b]);
2309
+ this.perViewContents.set(s, m);
2310
+ }
2311
+ }
2312
+ }
2017
2313
  saveContentState() {
2018
2314
  this.stateManager.setState({
2019
2315
  contents: this.contentManager.toJSON(),
@@ -2029,7 +2325,7 @@ class ee extends HTMLElement {
2029
2325
  }
2030
2326
  // ─── Keyboard ───
2031
2327
  setupKeyboardShortcuts() {
2032
- const e = J();
2328
+ const e = G();
2033
2329
  this.keyboard.register({
2034
2330
  key: "z",
2035
2331
  [e]: !0,
@@ -2044,24 +2340,27 @@ class ee extends HTMLElement {
2044
2340
  handler: () => this.handleAreaSelect(null)
2045
2341
  });
2046
2342
  }
2343
+ updateSaveButtonForUploads() {
2344
+ this.zoomToolbar && this.stateManager?.getState().isDirty && (this.pendingUploads.size > 0 ? this.zoomToolbar.setSaveDisabled(!0, "Uploading...") : this.zoomToolbar.setSaveDisabled(!1, "✓"));
2345
+ }
2047
2346
  // ─── Toast & Modal ───
2048
2347
  showToast(e, t = "info") {
2049
2348
  const i = h("div", { class: `editor-toast editor-toast-${t}` }, e);
2050
2349
  this.shadow.appendChild(i), setTimeout(() => i.remove(), 4e3);
2051
2350
  }
2052
2351
  showConfirmation(e, t, i = "Cancel", s = "Continue") {
2053
- return new Promise((a) => {
2054
- const o = h("div", { class: "editor-modal-overlay" }), n = h("div", { class: "editor-modal" });
2055
- n.appendChild(h("div", { class: "editor-modal-title" }, e)), n.appendChild(h("div", { class: "editor-modal-body" }, t));
2352
+ return new Promise((n) => {
2353
+ const o = h("div", { class: "editor-modal-overlay" }), a = h("div", { class: "editor-modal" });
2354
+ a.appendChild(h("div", { class: "editor-modal-title" }, e)), a.appendChild(h("div", { class: "editor-modal-body" }, t));
2056
2355
  const r = h("div", { class: "editor-modal-actions" }), d = h("button", { class: "editor-modal-btn" }, i);
2057
2356
  d.addEventListener("click", () => {
2058
- o.remove(), a(!1);
2357
+ o.remove(), n(!1);
2059
2358
  });
2060
2359
  const l = h("button", { class: "editor-modal-btn editor-modal-btn-primary" }, s);
2061
2360
  l.addEventListener("click", () => {
2062
- o.remove(), a(!0);
2063
- }), r.appendChild(d), r.appendChild(l), n.appendChild(r), o.appendChild(n), o.addEventListener("click", (c) => {
2064
- c.target === o && (o.remove(), a(!1));
2361
+ o.remove(), n(!0);
2362
+ }), r.appendChild(d), r.appendChild(l), a.appendChild(r), o.appendChild(a), o.addEventListener("click", (c) => {
2363
+ c.target === o && (o.remove(), n(!1));
2065
2364
  }), this.shadow.appendChild(o);
2066
2365
  });
2067
2366
  }
@@ -2069,7 +2368,7 @@ class ee extends HTMLElement {
2069
2368
  showError(e, t) {
2070
2369
  this.shadow.innerHTML = "";
2071
2370
  const i = document.createElement("style");
2072
- i.textContent = M, this.shadow.appendChild(i);
2371
+ i.textContent = k, this.shadow.appendChild(i);
2073
2372
  const s = h("div", { class: "error-container" });
2074
2373
  s.appendChild(h("div", { class: "error-title" }, e)), s.appendChild(h("div", { class: "error-message" }, t?.message ?? "")), this.shadow.appendChild(s), this.dispatchEvent(
2075
2374
  new CustomEvent("error", {
@@ -2080,12 +2379,13 @@ class ee extends HTMLElement {
2080
2379
  );
2081
2380
  }
2082
2381
  cleanup() {
2382
+ this.renderAbortController?.abort(), this.renderAbortController = null;
2083
2383
  for (const e of this.subscriptions)
2084
2384
  e();
2085
- this.subscriptions = [], this.resizeObserver && (this.resizeObserver.disconnect(), this.resizeObserver = null), this.engine?.destroy(), this.interaction?.destroy(), this.keyboard?.destroy(), this.isReady = !1, this.currentTemplate = null, this.productViews = [], this.activeViewName = null, this.perViewContents.clear();
2385
+ this.subscriptions = [], this.refitRafId != null && (cancelAnimationFrame(this.refitRafId), this.refitRafId = null), this.resizeObserver && (this.resizeObserver.disconnect(), this.resizeObserver = null), this.engine?.destroy(), this.interaction?.destroy(), this.keyboard?.destroy(), this.isReady = !1, this.currentTemplate = null, this.productViews = [], this.activeViewName = null, this.perViewContents.clear();
2086
2386
  }
2087
2387
  }
2088
- customElements.get("customizer-editor") || customElements.define("customizer-editor", ee);
2388
+ customElements.get("customizer-editor") || customElements.define("customizer-editor", I);
2089
2389
  function te(f, e) {
2090
2390
  const t = typeof f == "string" ? document.querySelector(f) : f;
2091
2391
  if (!t)
@@ -2098,8 +2398,8 @@ function te(f, e) {
2098
2398
  throw new Error("Only one of templateId or productId should be provided, not both");
2099
2399
  const i = document.createElement("customizer-editor");
2100
2400
  e.productId ? i.setAttribute("product-id", e.productId) : i.setAttribute("template-id", e.templateId), e.theme && i.setAttribute("theme", e.theme), e.mode && i.setAttribute("mode", e.mode), e.className && i.classList.add(e.className), e.showCloseButton === !1 && i.setAttribute("show-close-button", "false"), e.showSaveButton === !1 && i.setAttribute("show-save-button", "false"), i.style.width = "100%", i.style.height = "100%";
2101
- const s = [], a = (n, r) => {
2102
- i.addEventListener(n, r), s.push({ event: n, handler: r });
2401
+ const s = [], n = (a, r) => {
2402
+ i.addEventListener(a, r), s.push({ event: a, handler: r });
2103
2403
  };
2104
2404
  t.innerHTML = "", t.appendChild(i);
2105
2405
  const o = {
@@ -2108,10 +2408,10 @@ function te(f, e) {
2108
2408
  throw new Error("Editor not ready: getDesign method not available");
2109
2409
  return i.getDesign();
2110
2410
  },
2111
- setDesign(n) {
2411
+ setDesign(a) {
2112
2412
  if (typeof i.setDesign != "function")
2113
2413
  throw new Error("Editor not ready: setDesign method not available");
2114
- i.setDesign(n);
2414
+ i.setDesign(a);
2115
2415
  },
2116
2416
  undo() {
2117
2417
  if (typeof i.undo != "function")
@@ -2130,77 +2430,116 @@ function te(f, e) {
2130
2430
  return typeof i.canRedo != "function" ? !1 : i.canRedo();
2131
2431
  },
2132
2432
  async finalize() {
2133
- const n = this.getDesign(), r = e.apiUrl || "https://api.varianta.io";
2433
+ const a = e.apiUrl || "https://api.varianta.io";
2134
2434
  try {
2135
- const d = await fetch(`${r}/public/finalize`, {
2435
+ if (e.productId && typeof i.finalize == "function") {
2436
+ const p = await i.finalize(), u = {
2437
+ designId: p.designId,
2438
+ status: "processing",
2439
+ proofUrl: p.proofUrl ?? null,
2440
+ errorMessage: null,
2441
+ requestId: p.requestId,
2442
+ views: p.views
2443
+ };
2444
+ return e.onFinalize && e.onFinalize(u), u;
2445
+ }
2446
+ const r = this.getDesign(), d = await fetch(`${a}/public/finalize`, {
2136
2447
  method: "POST",
2137
2448
  headers: {
2138
2449
  "Content-Type": "application/json"
2139
2450
  },
2140
2451
  body: JSON.stringify({
2141
- templateId: n.templateId || e.templateId,
2142
- designJson: n
2452
+ templateId: r.templateId || e.templateId,
2453
+ designJson: r
2143
2454
  })
2144
2455
  });
2145
2456
  if (!d.ok) {
2146
- const m = await d.json().catch(() => ({
2457
+ const p = await d.json().catch(() => ({
2147
2458
  message: "Finalization failed"
2148
2459
  }));
2149
- throw new Error(m.message || "Finalization failed");
2460
+ throw new Error(p.message || "Finalization failed");
2150
2461
  }
2151
- const l = await d.json();
2152
- let c = {
2462
+ const l = await d.json(), c = {
2153
2463
  designId: l.designId,
2154
2464
  status: l.status,
2155
2465
  proofUrl: l.proofUrl ?? null,
2156
2466
  errorMessage: l.errorMessage ?? null
2157
2467
  };
2158
- const u = 1500, p = 40;
2159
- let g = 0;
2160
- for (; c.status === "processing" && g < p; ) {
2161
- await new Promise((b) => setTimeout(b, u)), g++;
2162
- const m = await fetch(
2163
- `${r}/public/designs/${c.designId}/status`
2164
- );
2165
- if (!m.ok)
2166
- throw new Error("Failed to check design status");
2167
- const v = await m.json();
2168
- c = {
2169
- designId: v.designId,
2170
- status: v.status,
2171
- proofUrl: v.proofUrl ?? null,
2172
- errorMessage: v.errorMessage ?? null
2173
- };
2174
- }
2175
- return c.status === "processing" && (c = { ...c, status: "failed", errorMessage: "Render timed out" }), e.onFinalize && e.onFinalize(c), c;
2176
- } catch (d) {
2177
- const l = {
2468
+ return e.onFinalize && e.onFinalize(c), c;
2469
+ } catch (r) {
2470
+ const d = {
2178
2471
  code: "FINALIZE_ERROR",
2179
- message: d instanceof Error ? d.message : "Unknown error",
2180
- details: d
2472
+ message: r instanceof Error ? r.message : "Unknown error",
2473
+ details: r
2181
2474
  };
2182
- throw e.onError && e.onError(l), d;
2475
+ throw e.onError && e.onError(d), r;
2476
+ }
2477
+ },
2478
+ async waitForResult(a, r) {
2479
+ if (typeof i.waitForResult == "function") {
2480
+ const m = await i.waitForResult(a, r);
2481
+ return e.onRenderComplete && e.onRenderComplete(m), m;
2183
2482
  }
2483
+ const d = e.apiUrl || "https://api.varianta.io", l = r?.pollInterval ?? 1500, c = r?.maxPolls ?? 40, p = r?.signal;
2484
+ let u = 0;
2485
+ for (; u < c; ) {
2486
+ if (p?.aborted)
2487
+ throw new DOMException("Polling aborted", "AbortError");
2488
+ await new Promise((v, b) => {
2489
+ const w = setTimeout(v, l);
2490
+ p?.addEventListener("abort", () => {
2491
+ clearTimeout(w), b(new DOMException("Polling aborted", "AbortError"));
2492
+ }, { once: !0 });
2493
+ }), u++;
2494
+ let m;
2495
+ if (a.length === 1) {
2496
+ const v = await fetch(`${d}/public/designs/${a[0]}/status`);
2497
+ if (!v.ok) throw new Error("Failed to check design status");
2498
+ const b = await v.json();
2499
+ m = {
2500
+ designs: [b],
2501
+ allCompleted: b.status !== "processing"
2502
+ };
2503
+ } else {
2504
+ const v = await fetch(
2505
+ `${d}/public/finalize-status?designIds=${a.join(",")}`
2506
+ );
2507
+ if (!v.ok) throw new Error("Failed to check design status");
2508
+ m = await v.json();
2509
+ }
2510
+ if (m.allCompleted)
2511
+ return e.onRenderComplete && e.onRenderComplete(m), m;
2512
+ }
2513
+ const g = {
2514
+ designs: a.map((m) => ({
2515
+ designId: m,
2516
+ status: "failed",
2517
+ proofUrl: null,
2518
+ errorMessage: "Render timed out"
2519
+ })),
2520
+ allCompleted: !0
2521
+ };
2522
+ return e.onRenderComplete && e.onRenderComplete(g), g;
2184
2523
  },
2185
- addTextLayer(n) {
2524
+ addTextLayer(a) {
2186
2525
  if (typeof i.addTextLayer != "function")
2187
2526
  throw new Error("Editor not ready: addTextLayer method not available");
2188
- i.addTextLayer(n);
2527
+ i.addTextLayer(a);
2189
2528
  },
2190
- async addImageLayer(n) {
2529
+ async addImageLayer(a) {
2191
2530
  if (typeof i.addImageLayer != "function")
2192
2531
  throw new Error("Editor not ready: addImageLayer method not available");
2193
- return i.addImageLayer(n);
2532
+ return i.addImageLayer(a);
2194
2533
  },
2195
- removeLayer(n) {
2534
+ removeLayer(a) {
2196
2535
  if (typeof i.removeLayer != "function")
2197
2536
  throw new Error("Editor not ready: removeLayer method not available");
2198
- i.removeLayer(n);
2537
+ i.removeLayer(a);
2199
2538
  },
2200
- selectLayer(n) {
2539
+ selectLayer(a) {
2201
2540
  if (typeof i.selectLayer != "function")
2202
2541
  throw new Error("Editor not ready: selectLayer method not available");
2203
- i.selectLayer(n);
2542
+ i.selectLayer(a);
2204
2543
  },
2205
2544
  getSelectedLayerId() {
2206
2545
  return typeof i.getSelectedLayerId != "function" ? null : i.getSelectedLayerId();
@@ -2211,48 +2550,50 @@ function te(f, e) {
2211
2550
  getViews() {
2212
2551
  return typeof i.getViews != "function" ? [] : i.getViews();
2213
2552
  },
2214
- setActiveView(n) {
2553
+ setActiveView(a) {
2215
2554
  if (typeof i.setActiveView != "function")
2216
2555
  throw new Error("Editor not ready: setActiveView method not available");
2217
- i.setActiveView(n);
2556
+ i.setActiveView(a);
2218
2557
  },
2219
- setTheme(n) {
2220
- i.setAttribute("theme", n);
2558
+ setTheme(a) {
2559
+ i.setAttribute("theme", a);
2221
2560
  },
2222
- setMode(n) {
2223
- i.setAttribute("mode", n);
2561
+ setMode(a) {
2562
+ i.setAttribute("mode", a);
2224
2563
  },
2225
2564
  destroy() {
2226
- s.forEach(({ event: n, handler: r }) => {
2227
- i.removeEventListener(n, r);
2565
+ s.forEach(({ event: a, handler: r }) => {
2566
+ i.removeEventListener(a, r);
2228
2567
  }), i.parentNode && i.parentNode.removeChild(i);
2229
2568
  },
2230
2569
  getElement() {
2231
2570
  return i;
2232
2571
  }
2233
2572
  };
2234
- return e.onReady && a("ready", (() => {
2573
+ return e.onReady && n("ready", (() => {
2235
2574
  e.onReady?.(o);
2236
- })), e.onChange && a("change", ((n) => {
2237
- const r = n.detail.design;
2575
+ })), e.onChange && n("change", ((a) => {
2576
+ const r = a.detail.design;
2238
2577
  e.onChange?.(r);
2239
- })), e.onLayerSelect && a("layer:select", ((n) => {
2240
- e.onLayerSelect?.(n.detail.layerId);
2241
- })), e.onLayerAdd && a("layer:add", ((n) => {
2242
- e.onLayerAdd?.(n.detail.layerId);
2243
- })), e.onLayerRemove && a("layer:remove", ((n) => {
2244
- e.onLayerRemove?.(n.detail.layerId);
2245
- })), e.onLayerUpdate && a("layer:update", ((n) => {
2246
- e.onLayerUpdate?.(n.detail.layerId);
2247
- })), e.onError && a("error", ((n) => {
2248
- const r = n.detail.error;
2578
+ })), e.onLayerSelect && n("layer:select", ((a) => {
2579
+ e.onLayerSelect?.(a.detail.layerId);
2580
+ })), e.onLayerAdd && n("layer:add", ((a) => {
2581
+ e.onLayerAdd?.(a.detail.layerId);
2582
+ })), e.onLayerRemove && n("layer:remove", ((a) => {
2583
+ e.onLayerRemove?.(a.detail.layerId);
2584
+ })), e.onLayerUpdate && n("layer:update", ((a) => {
2585
+ e.onLayerUpdate?.(a.detail.layerId);
2586
+ })), e.onError && n("error", ((a) => {
2587
+ const r = a.detail.error;
2249
2588
  e.onError?.(r);
2250
- })), e.onViewChange && a("view-change", ((n) => {
2251
- e.onViewChange?.(n.detail.viewName);
2252
- })), e.onClose && a("customizer:close", (() => {
2589
+ })), e.onViewChange && n("view-change", ((a) => {
2590
+ e.onViewChange?.(a.detail.viewName);
2591
+ })), e.onRenderComplete && n("customizer:render-complete", ((a) => {
2592
+ e.onRenderComplete?.(a.detail);
2593
+ })), e.onClose && n("customizer:close", (() => {
2253
2594
  e.onClose?.();
2254
- })), e.onSave && a("customizer:save", ((n) => {
2255
- e.onSave?.(n.detail);
2595
+ })), e.onSave && n("customizer:save", ((a) => {
2596
+ e.onSave?.(a.detail);
2256
2597
  })), e.initialDesign && i.addEventListener(
2257
2598
  "ready",
2258
2599
  () => {