@varianta/sdk 0.1.3 → 0.1.4

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
@@ -1,6 +1,4 @@
1
- import { jsx as $ } from "react/jsx-runtime";
2
- import { forwardRef as Z, useRef as k, useEffect as L, useImperativeHandle as q, useState as S, useCallback as M } from "react";
3
- class B {
1
+ class N {
4
2
  constructor(e) {
5
3
  this.animationFrameId = null, this.needsRender = !0, this.areas = [], this.contents = /* @__PURE__ */ new Map(), this.artboard = null, this.bgImageElement = null, this.loadedImages = /* @__PURE__ */ new Map(), this.loadingImages = /* @__PURE__ */ new Set(), this.zoom = 1, this.pan = { x: 0, y: 0 }, this.selectedAreaId = null, this.hoveredHandle = null, this.eventListeners = /* @__PURE__ */ new Map(), this.safeArea = null, this.safeAreaViolations = /* @__PURE__ */ new Set(), this.showAreaBorders = !0, this.areaSelectionEnabled = !0, this.renderLoop = () => {
6
4
  try {
@@ -131,10 +129,10 @@ class B {
131
129
  getCenteredPan(e) {
132
130
  if (!this.artboard)
133
131
  return null;
134
- const { width: t, height: i } = this.artboard, n = window.devicePixelRatio || 1, r = this.canvas.width / n, s = this.canvas.height / n;
132
+ const { width: t, height: i } = this.artboard, n = window.devicePixelRatio || 1, a = this.canvas.width / n, o = this.canvas.height / n;
135
133
  return {
136
- x: (r / e - t) / 2,
137
- y: (s / e - i) / 2
134
+ x: (a / e - t) / 2,
135
+ y: (o / e - i) / 2
138
136
  };
139
137
  }
140
138
  /**
@@ -149,11 +147,11 @@ class B {
149
147
  fitToView() {
150
148
  if (!this.artboard)
151
149
  return;
152
- const e = 0, { width: t, height: i } = this.artboard, n = window.devicePixelRatio || 1, r = this.canvas.width / n - e * 2, s = this.canvas.height / n - e * 2;
153
- if (r <= 0 || s <= 0)
150
+ const e = 0, { width: t, height: i } = this.artboard, n = window.devicePixelRatio || 1, a = this.canvas.width / n - e * 2, o = this.canvas.height / n - e * 2;
151
+ if (a <= 0 || o <= 0)
154
152
  return;
155
- const a = r / t, o = s / i, d = Math.min(a, o), l = this.canvas.width / n, h = this.canvas.height / n, u = (l / d - t) / 2, g = (h / d - i) / 2;
156
- this.zoom = d, this.pan = { x: u, y: g }, this.requestRender();
153
+ const s = a / t, r = o / i, d = Math.min(s, r), l = this.canvas.width / n, c = this.canvas.height / n, u = (l / d - t) / 2, p = (c / d - i) / 2;
154
+ this.zoom = d, this.pan = { x: u, y: p }, this.requestRender();
157
155
  }
158
156
  /**
159
157
  * Set the selected area
@@ -183,8 +181,8 @@ class B {
183
181
  * Convert screen coordinates to canvas coordinates
184
182
  */
185
183
  screenToCanvas(e, t) {
186
- const i = window.devicePixelRatio || 1, n = e / i, r = t / i, s = n / this.zoom - this.pan.x, a = r / this.zoom - this.pan.y;
187
- return { x: s, y: a };
184
+ const i = window.devicePixelRatio || 1, n = e / i, a = t / i, o = n / this.zoom - this.pan.x, s = a / this.zoom - this.pan.y;
185
+ return { x: o, y: s };
188
186
  }
189
187
  /**
190
188
  * Get content bounds for an area (in artboard coordinates)
@@ -196,34 +194,34 @@ class B {
196
194
  const i = this.contents.get(e);
197
195
  if (!i)
198
196
  return null;
199
- const { location: n } = t, { x: r, y: s, width: a, height: o } = n;
197
+ const { location: n } = t, { x: a, y: o, width: s, height: r } = n;
200
198
  if (i.type === "text") {
201
- const d = i.offset || { x: 0, y: 0 }, l = i.scale ?? 1, h = i.rotation ?? 0, u = a * l, g = o * l, m = r + a / 2 + d.x, f = s + o / 2 + d.y;
199
+ const d = i.offset || { x: 0, y: 0 }, l = i.scale ?? 1, c = i.rotation ?? 0, u = s * l, p = r * l, g = a + s / 2 + d.x, m = o + r / 2 + d.y;
202
200
  return {
203
- x: m - u / 2,
204
- y: f - g / 2,
201
+ x: g - u / 2,
202
+ y: m - p / 2,
205
203
  width: u,
206
- height: g,
207
- rotation: h,
208
- centerX: m,
209
- centerY: f
204
+ height: p,
205
+ rotation: c,
206
+ centerX: g,
207
+ centerY: m
210
208
  };
211
209
  } else if (i.type === "image") {
212
210
  const d = this.loadedImages.get(e);
213
211
  if (!d)
214
212
  return null;
215
- const l = i.offset || { x: 0, y: 0 }, h = i.scale ?? 1, u = i.rotation ?? 0, g = d.naturalWidth / d.naturalHeight, m = a / o;
216
- let f, y;
217
- g > m ? (f = a, y = a / g) : (y = o, f = o * g);
218
- const v = f * h, w = y * h, p = r + a / 2 + l.x, C = s + o / 2 + l.y;
213
+ const l = i.offset || { x: 0, y: 0 }, c = i.scale ?? 1, u = i.rotation ?? 0, p = d.naturalWidth / d.naturalHeight, g = s / r;
214
+ let m, v;
215
+ p > g ? (m = s, v = s / p) : (v = r, m = r * p);
216
+ const b = m * c, y = v * c, x = a + s / 2 + l.x, w = o + r / 2 + l.y;
219
217
  return {
220
- x: p - v / 2,
221
- y: C - w / 2,
222
- width: v,
223
- height: w,
218
+ x: x - b / 2,
219
+ y: w - y / 2,
220
+ width: b,
221
+ height: y,
224
222
  rotation: u,
225
- centerX: p,
226
- centerY: C
223
+ centerX: x,
224
+ centerY: w
227
225
  };
228
226
  }
229
227
  return null;
@@ -236,8 +234,8 @@ class B {
236
234
  if (!this.areaSelectionEnabled)
237
235
  return null;
238
236
  for (let i = this.areas.length - 1; i >= 0; i--) {
239
- const n = this.areas[i], r = this.getContentBounds(n.id);
240
- if (r && this.isPointInBounds(e, t, r))
237
+ const n = this.areas[i], a = this.getContentBounds(n.id);
238
+ if (a && this.isPointInBounds(e, t, a))
241
239
  return n.id;
242
240
  }
243
241
  return null;
@@ -250,8 +248,8 @@ class B {
250
248
  if (!this.areaSelectionEnabled)
251
249
  return null;
252
250
  for (let i = this.areas.length - 1; i >= 0; i--) {
253
- const n = this.areas[i], { x: r, y: s, width: a, height: o } = n.location;
254
- if (e >= r && e <= r + a && t >= s && t <= s + o)
251
+ const n = this.areas[i], { x: a, y: o, width: s, height: r } = n.location;
252
+ if (e >= a && e <= a + s && t >= o && t <= o + r)
255
253
  return n.id;
256
254
  }
257
255
  return null;
@@ -260,14 +258,14 @@ class B {
260
258
  * Test if a point is inside rotated bounds
261
259
  */
262
260
  isPointInBounds(e, t, i) {
263
- const { centerX: n, centerY: r, width: s, height: a, rotation: o } = i, d = -o * Math.PI / 180, l = Math.cos(d), h = Math.sin(d), u = e - n, g = t - r, m = u * l - g * h, f = u * h + g * l;
264
- return m >= -s / 2 && m <= s / 2 && f >= -a / 2 && f <= a / 2;
261
+ const { centerX: n, centerY: a, width: o, height: s, rotation: r } = i, d = -r * Math.PI / 180, l = Math.cos(d), c = Math.sin(d), u = e - n, p = t - a, g = u * l - p * c, m = u * c + p * l;
262
+ return g >= -o / 2 && g <= o / 2 && m >= -s / 2 && m <= s / 2;
265
263
  }
266
264
  /**
267
265
  * Get handle positions for the selected content
268
266
  */
269
267
  getContentHandlePositions(e) {
270
- const t = this.areas.find((v) => v.id === e);
268
+ const t = this.areas.find((b) => b.id === e);
271
269
  if (!t)
272
270
  return null;
273
271
  const i = this.contents.get(e);
@@ -276,25 +274,25 @@ class B {
276
274
  const n = this.getContentBounds(e);
277
275
  if (!n)
278
276
  return null;
279
- const { centerX: r, centerY: s, width: a, height: o, rotation: d } = n, l = 6 / this.zoom, h = 30 / this.zoom, u = d * Math.PI / 180, g = Math.cos(u), m = Math.sin(u), f = (v, w) => ({
280
- x: r + v * g - w * m,
281
- y: s + v * m + w * g
282
- }), y = [];
277
+ const { centerX: a, centerY: o, width: s, height: r, rotation: d } = n, 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
280
+ }), v = [];
283
281
  if (i.type === "image" && t.imageOptions?.allowScaling || i.type === "text" && t.textOptions?.allowScaling) {
284
- const v = a / 2, w = o / 2, p = f(-v, -w);
285
- y.push({ type: "nw", ...p, radius: l });
286
- const C = f(v, -w);
287
- y.push({ type: "ne", ...C, radius: l });
288
- const I = f(v, w);
289
- y.push({ type: "se", ...I, radius: l });
290
- const x = f(-v, w);
291
- y.push({ type: "sw", ...x, radius: l });
282
+ const b = s / 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);
287
+ v.push({ type: "se", ...C, radius: l });
288
+ const M = m(-b, y);
289
+ v.push({ type: "sw", ...M, radius: l });
292
290
  }
293
291
  if (i.type === "image" && t.imageOptions?.allowRotation || i.type === "text" && t.textOptions?.allowRotation) {
294
- const v = f(0, -o / 2 - h);
295
- y.push({ type: "rotate", ...v, radius: l });
292
+ const b = m(0, -r / 2 - c);
293
+ v.push({ type: "rotate", ...b, radius: l });
296
294
  }
297
- return y;
295
+ return v;
298
296
  }
299
297
  /**
300
298
  * Hit test handles at given canvas coordinates
@@ -303,10 +301,10 @@ class B {
303
301
  const n = this.getContentHandlePositions(i);
304
302
  if (!n)
305
303
  return null;
306
- for (const r of n) {
307
- const s = e - r.x, a = t - r.y;
308
- if (Math.sqrt(s * s + a * a) <= r.radius * 2)
309
- return r.type;
304
+ for (const a of n) {
305
+ const o = e - a.x, s = t - a.y;
306
+ if (Math.sqrt(o * o + s * s) <= a.radius * 2)
307
+ return a.type;
310
308
  }
311
309
  return null;
312
310
  }
@@ -346,15 +344,15 @@ class B {
346
344
  checkSafeAreaViolations() {
347
345
  if (!this.safeArea || !this.artboard)
348
346
  return [];
349
- const e = [], { top: t, right: i, bottom: n, left: r } = this.safeArea, s = r, a = t, o = this.artboard.width - i, d = this.artboard.height - n;
347
+ const e = [], { top: t, right: i, bottom: n, left: a } = this.safeArea, o = a, s = t, r = this.artboard.width - i, d = this.artboard.height - n;
350
348
  for (const l of this.areas) {
351
349
  if (!this.contents.get(l.id))
352
350
  continue;
353
- const h = this.getContentBounds(l.id);
354
- if (!h)
351
+ const c = this.getContentBounds(l.id);
352
+ if (!c)
355
353
  continue;
356
- const { centerX: u, centerY: g, width: m, height: f, rotation: y } = h, v = y * Math.PI / 180, w = Math.abs(Math.cos(v)), p = Math.abs(Math.sin(v)), C = m * w + f * p, I = m * p + f * w, x = u - C / 2, R = g - I / 2, D = u + C / 2, U = g + I / 2;
357
- (x < s || R < a || D > o || U > d) && e.push(l.id);
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, M = u - w / 2, A = p - C / 2, D = u + w / 2, R = p + C / 2;
355
+ (M < o || A < s || D > r || R > d) && e.push(l.id);
358
356
  }
359
357
  return this.safeAreaViolations = new Set(e), this.requestRender(), e;
360
358
  }
@@ -383,15 +381,15 @@ class B {
383
381
  throw new Error("No artboard configured");
384
382
  const { width: e, height: t, backgroundColor: i } = this.artboard, n = document.createElement("canvas");
385
383
  n.width = e, n.height = t;
386
- const r = n.getContext("2d");
387
- if (!r)
384
+ const a = n.getContext("2d");
385
+ if (!a)
388
386
  throw new Error("Failed to get 2D context for export");
389
- r.fillStyle = i, r.fillRect(0, 0, e, t), this.backgroundImage?.position === "behind" && this.bgImageElement && this.drawBackgroundImageOnContext(r, e, t);
390
- for (const s of this.areas)
391
- this.drawAreaContentOnContext(r, s);
392
- return this.backgroundImage?.position === "overlay" && this.bgImageElement && this.drawBackgroundImageOnContext(r, e, t), new Promise((s, a) => {
393
- n.toBlob((o) => {
394
- o ? s(o) : a(new Error("Failed to create PNG blob"));
387
+ a.fillStyle = i, a.fillRect(0, 0, e, t), this.backgroundImage?.position === "behind" && this.bgImageElement && this.drawBackgroundImageOnContext(a, e, t);
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, s) => {
391
+ n.toBlob((r) => {
392
+ r ? o(r) : s(new Error("Failed to create PNG blob"));
395
393
  }, "image/png", 1);
396
394
  });
397
395
  }
@@ -424,17 +422,17 @@ class B {
424
422
  e.save();
425
423
  const i = window.devicePixelRatio || 1;
426
424
  e.scale(i, i), e.scale(this.zoom, this.zoom), e.translate(this.pan.x, this.pan.y);
427
- const { width: n, height: r, backgroundColor: s } = this.artboard;
428
- e.fillStyle = s, e.fillRect(0, 0, n, r), e.strokeStyle = "#d1d5db", e.lineWidth = 1 / this.zoom, e.strokeRect(0, 0, n, r), this.backgroundImage?.position === "behind" && this.renderBackgroundImage();
429
- for (const a of this.areas)
430
- this.renderAreaWithContent(a);
425
+ const { width: n, height: a, backgroundColor: o } = this.artboard;
426
+ e.fillStyle = o, e.fillRect(0, 0, n, a), e.strokeStyle = "#d1d5db", e.lineWidth = 1 / this.zoom, e.strokeRect(0, 0, n, a), this.backgroundImage?.position === "behind" && this.renderBackgroundImage();
427
+ for (const s of this.areas)
428
+ this.renderAreaWithContent(s);
431
429
  this.backgroundImage?.position === "overlay" && this.renderBackgroundImage(), this.renderSafeAreaGuide();
432
- for (const a of this.safeAreaViolations) {
433
- const o = this.areas.find((g) => g.id === a);
434
- if (!o)
430
+ for (const s of this.safeAreaViolations) {
431
+ const r = this.areas.find((p) => p.id === s);
432
+ if (!r)
435
433
  continue;
436
- const { x: d, y: l, width: h, height: u } = o.location;
437
- e.save(), e.fillStyle = "rgba(239, 68, 68, 0.15)", e.fillRect(d, l, h, u), e.restore();
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
436
  }
439
437
  this.selectedAreaId && this.renderSelectionOverlay(this.selectedAreaId), e.restore();
440
438
  }
@@ -444,36 +442,36 @@ class B {
444
442
  renderSelectionOverlay(e) {
445
443
  const t = this.getContentBounds(e);
446
444
  if (!t) {
447
- const l = this.areas.find((y) => y.id === e);
445
+ const l = this.areas.find((v) => v.id === e);
448
446
  if (!l)
449
447
  return;
450
- const { ctx: h } = this, { x: u, y: g, width: m, height: f } = l.location;
451
- h.save(), h.strokeStyle = "#3b82f6", h.lineWidth = 2 / this.zoom, h.setLineDash([]), h.strokeRect(u, g, m, f), h.restore();
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
450
  return;
453
451
  }
454
- const { ctx: i } = this, { centerX: n, centerY: r, width: s, height: a, rotation: o } = t;
455
- i.save(), i.translate(n, r), i.rotate(o * Math.PI / 180), i.strokeStyle = "#3b82f6", i.lineWidth = 2 / this.zoom, i.setLineDash([]), i.strokeRect(-s / 2, -a / 2, s, a), i.restore();
452
+ const { ctx: i } = this, { centerX: n, centerY: a, width: o, height: s, rotation: r } = t;
453
+ i.save(), i.translate(n, a), i.rotate(r * Math.PI / 180), i.strokeStyle = "#3b82f6", i.lineWidth = 2 / this.zoom, i.setLineDash([]), i.strokeRect(-o / 2, -s / 2, o, s), i.restore();
456
454
  const d = this.getContentHandlePositions(e);
457
455
  if (d)
458
456
  for (const l of d) {
459
457
  i.save();
460
- const h = this.hoveredHandle === l.type, u = h ? "#dbeafe" : "#ffffff", g = h ? "#2563eb" : "#3b82f6";
458
+ const c = this.hoveredHandle === l.type, u = c ? "#dbeafe" : "#ffffff", p = c ? "#2563eb" : "#3b82f6";
461
459
  if (l.type === "rotate") {
462
- const m = this.getRotatedPoint(n, r - a / 2, n, r, o);
463
- i.beginPath(), i.strokeStyle = g, i.lineWidth = 1 / this.zoom, i.setLineDash([4 / this.zoom, 4 / this.zoom]), i.moveTo(m.x, m.y), i.lineTo(l.x, l.y), i.stroke(), i.setLineDash([]), i.beginPath(), i.fillStyle = u, i.strokeStyle = g, 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 = g, 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();
460
+ const g = this.getRotatedPoint(n, a - s / 2, n, 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
462
  } else
465
- i.fillStyle = u, i.strokeStyle = g, 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);
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);
466
464
  i.restore();
467
465
  }
468
466
  }
469
467
  /**
470
468
  * Rotate a point around a center
471
469
  */
472
- getRotatedPoint(e, t, i, n, r) {
473
- const s = r * Math.PI / 180, a = Math.cos(s), o = Math.sin(s), d = e - i, l = t - n;
470
+ getRotatedPoint(e, t, i, n, a) {
471
+ const o = a * Math.PI / 180, s = Math.cos(o), r = Math.sin(o), d = e - i, l = t - n;
474
472
  return {
475
- x: i + d * a - l * o,
476
- y: n + d * o + l * a
473
+ x: i + d * s - l * r,
474
+ y: n + d * r + l * s
477
475
  };
478
476
  }
479
477
  /**
@@ -484,9 +482,9 @@ class B {
484
482
  return;
485
483
  const { ctx: e } = this, { opacity: t } = this.backgroundImage, { width: i, height: n } = this.artboard;
486
484
  e.save(), e.globalAlpha = t;
487
- const r = this.bgImageElement.naturalWidth / this.bgImageElement.naturalHeight, s = i / n;
488
- let a, o, d, l;
489
- r > s ? (o = n, a = n * r, d = (i - a) / 2, l = 0) : (a = i, o = i / r, d = 0, l = (n - o) / 2), e.drawImage(this.bgImageElement, d, l, a, o), e.restore();
485
+ const a = this.bgImageElement.naturalWidth / this.bgImageElement.naturalHeight, o = i / n;
486
+ let s, r, d, l;
487
+ a > o ? (r = n, s = n * a, d = (i - s) / 2, l = 0) : (s = i, r = i / a, d = 0, l = (n - r) / 2), e.drawImage(this.bgImageElement, d, l, s, r), e.restore();
490
488
  }
491
489
  /**
492
490
  * Render safe area guide as dashed green rectangle
@@ -494,17 +492,17 @@ class B {
494
492
  renderSafeAreaGuide() {
495
493
  if (!this.safeArea || !this.artboard)
496
494
  return;
497
- const { ctx: e } = this, { top: t, right: i, bottom: n, left: r } = this.safeArea, s = r, a = t, o = this.artboard.width - r - i, d = this.artboard.height - t - n;
498
- o <= 0 || d <= 0 || (e.save(), e.strokeStyle = "#22c55e", e.lineWidth = 1 / this.zoom, e.setLineDash([6 / this.zoom, 4 / this.zoom]), e.strokeRect(s, a, o, d), e.setLineDash([]), e.restore());
495
+ const { ctx: e } = this, { top: t, right: i, bottom: n, left: a } = this.safeArea, o = a, s = t, r = this.artboard.width - a - i, d = this.artboard.height - t - n;
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, s, r, d), e.setLineDash([]), e.restore());
499
497
  }
500
498
  /**
501
499
  * Render an area with its content
502
500
  */
503
501
  renderAreaWithContent(e) {
504
- const { ctx: t } = this, { location: i, backgroundColor: n } = e, { x: r, y: s, width: a, height: o } = i;
505
- n && (t.fillStyle = n, t.fillRect(r, s, a, o));
502
+ const { ctx: t } = this, { location: i, backgroundColor: n } = e, { x: a, y: o, width: s, height: r } = i;
503
+ n && (t.fillStyle = n, t.fillRect(a, o, s, r));
506
504
  const d = this.contents.get(e.id);
507
- 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(r, s, a, o), t.setLineDash([]), t.restore());
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, s, r), t.setLineDash([]), t.restore());
508
506
  }
509
507
  /**
510
508
  * Render text content in an area
@@ -512,22 +510,22 @@ class B {
512
510
  renderTextContent(e, t) {
513
511
  if (!t.text.trim())
514
512
  return;
515
- const { ctx: i } = this, { location: n } = e, { x: r, y: s, width: a, height: o } = n, d = t.offset || { x: 0, y: 0 }, l = t.scale ?? 1, h = t.rotation ?? 0, u = r + a / 2 + d.x, g = s + o / 2 + d.y;
516
- i.save(), i.beginPath(), i.rect(r, s, a, o), i.clip(), i.translate(u, g), i.rotate(h * Math.PI / 180), i.scale(l, l), i.font = `${t.size}px ${t.font}, sans-serif`, i.fillStyle = t.color, i.textBaseline = "middle";
517
- let m;
513
+ const { ctx: i } = this, { location: n } = e, { x: a, y: o, width: s, height: r } = n, d = t.offset || { x: 0, y: 0 }, l = t.scale ?? 1, c = t.rotation ?? 0, u = a + s / 2 + d.x, p = o + r / 2 + d.y;
514
+ i.save(), i.beginPath(), i.rect(a, o, s, 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";
515
+ let g;
518
516
  switch (t.align) {
519
517
  case "left":
520
- i.textAlign = "left", m = -a / 2 + 10;
518
+ i.textAlign = "left", g = -s / 2 + 10;
521
519
  break;
522
520
  case "right":
523
- i.textAlign = "right", m = a / 2 - 10;
521
+ i.textAlign = "right", g = s / 2 - 10;
524
522
  break;
525
523
  case "center":
526
524
  default:
527
- i.textAlign = "center", m = 0;
525
+ i.textAlign = "center", g = 0;
528
526
  break;
529
527
  }
530
- i.fillText(t.text, m, 0), i.restore();
528
+ i.fillText(t.text, g, 0), i.restore();
531
529
  }
532
530
  /**
533
531
  * Render image content in an area
@@ -536,13 +534,13 @@ class B {
536
534
  const i = this.loadedImages.get(e.id);
537
535
  if (!i)
538
536
  return;
539
- const { ctx: n } = this, { location: r } = e, { x: s, y: a, width: o, height: d } = r, l = t.offset || { x: 0, y: 0 }, h = t.scale ?? 1, u = t.rotation ?? 0;
540
- n.save(), n.beginPath(), n.rect(s, a, o, d), n.clip();
541
- const g = i.naturalWidth / i.naturalHeight, m = o / d;
542
- let f, y;
543
- g > m ? (f = o, y = o / g) : (y = d, f = d * g);
544
- const v = f * h, w = y * h, p = s + o / 2 + l.x, C = a + d / 2 + l.y;
545
- n.translate(p, C), n.rotate(u * Math.PI / 180), n.drawImage(i, -v / 2, -w / 2, v, w), n.restore();
537
+ const { ctx: n } = this, { location: a } = e, { x: o, y: s, width: r, height: d } = a, l = t.offset || { x: 0, y: 0 }, c = t.scale ?? 1, u = t.rotation ?? 0;
538
+ n.save(), n.beginPath(), n.rect(o, s, r, d), n.clip();
539
+ const p = i.naturalWidth / i.naturalHeight, g = r / d;
540
+ 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 = s + d / 2 + l.y;
543
+ n.translate(x, w), n.rotate(u * Math.PI / 180), n.drawImage(i, -b / 2, -y / 2, b, y), n.restore();
546
544
  }
547
545
  /**
548
546
  * Draw background image on a specific context (for export)
@@ -552,48 +550,48 @@ class B {
552
550
  return;
553
551
  const { opacity: n } = this.backgroundImage;
554
552
  e.save(), e.globalAlpha = n;
555
- const r = this.bgImageElement.naturalWidth / this.bgImageElement.naturalHeight, s = t / i;
556
- let a, o, d, l;
557
- r > s ? (o = i, a = i * r, d = (t - a) / 2, l = 0) : (a = t, o = t / r, d = 0, l = (i - o) / 2), e.drawImage(this.bgImageElement, d, l, a, o), e.restore();
553
+ const a = this.bgImageElement.naturalWidth / this.bgImageElement.naturalHeight, o = t / i;
554
+ let s, r, d, l;
555
+ a > o ? (r = i, s = i * a, d = (t - s) / 2, l = 0) : (s = t, r = t / a, d = 0, l = (i - r) / 2), e.drawImage(this.bgImageElement, d, l, s, r), e.restore();
558
556
  }
559
557
  /**
560
558
  * Draw area content on a specific context (for export)
561
559
  */
562
560
  drawAreaContentOnContext(e, t) {
563
- const { location: i, backgroundColor: n } = t, { x: r, y: s, width: a, height: o } = i;
564
- n && (e.fillStyle = n, e.fillRect(r, s, a, o));
561
+ const { location: i, backgroundColor: n } = t, { x: a, y: o, width: s, height: r } = i;
562
+ n && (e.fillStyle = n, e.fillRect(a, o, s, r));
565
563
  const d = this.contents.get(t.id);
566
564
  if (d) {
567
565
  if (d.type === "text") {
568
566
  if (!d.text.trim())
569
567
  return;
570
- const l = d.offset || { x: 0, y: 0 }, h = d.scale ?? 1, u = d.rotation ?? 0, g = r + a / 2 + l.x, m = s + o / 2 + l.y;
571
- e.save(), e.beginPath(), e.rect(r, s, a, o), e.clip(), e.translate(g, m), e.rotate(u * Math.PI / 180), e.scale(h, h), e.font = `${d.size}px ${d.font}, sans-serif`, e.fillStyle = d.color, e.textBaseline = "middle";
572
- let f;
568
+ const l = d.offset || { x: 0, y: 0 }, c = d.scale ?? 1, u = d.rotation ?? 0, p = a + s / 2 + l.x, g = o + r / 2 + l.y;
569
+ e.save(), e.beginPath(), e.rect(a, o, s, 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";
570
+ let m;
573
571
  switch (d.align) {
574
572
  case "left":
575
- e.textAlign = "left", f = -a / 2 + 10;
573
+ e.textAlign = "left", m = -s / 2 + 10;
576
574
  break;
577
575
  case "right":
578
- e.textAlign = "right", f = a / 2 - 10;
576
+ e.textAlign = "right", m = s / 2 - 10;
579
577
  break;
580
578
  case "center":
581
579
  default:
582
- e.textAlign = "center", f = 0;
580
+ e.textAlign = "center", m = 0;
583
581
  break;
584
582
  }
585
- e.fillText(d.text, f, 0), e.restore();
583
+ e.fillText(d.text, m, 0), e.restore();
586
584
  } else if (d.type === "image") {
587
585
  const l = this.loadedImages.get(t.id);
588
586
  if (!l)
589
587
  return;
590
- const h = d.offset || { x: 0, y: 0 }, u = d.scale ?? 1, g = d.rotation ?? 0;
591
- e.save(), e.beginPath(), e.rect(r, s, a, o), e.clip();
592
- const m = l.naturalWidth / l.naturalHeight, f = a / o;
593
- let y, v;
594
- m > f ? (y = a, v = a / m) : (v = o, y = o * m);
595
- const w = y * u, p = v * u, C = r + a / 2 + h.x, I = s + o / 2 + h.y;
596
- e.translate(C, I), e.rotate(g * Math.PI / 180), e.drawImage(l, -w / 2, -p / 2, w, p), e.restore();
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, s, r), e.clip();
590
+ const g = l.naturalWidth / l.naturalHeight, m = s / r;
591
+ let v, b;
592
+ g > m ? (v = s, b = s / g) : (b = r, v = r * g);
593
+ const y = v * u, x = b * u, w = a + s / 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();
597
595
  }
598
596
  }
599
597
  }
@@ -604,8 +602,8 @@ class B {
604
602
  this.stop(), this.bgImageElement = null, this.loadedImages.clear(), this.loadingImages.clear(), this.eventListeners.clear();
605
603
  }
606
604
  }
607
- function P(b) {
608
- const e = b.textOptions;
605
+ function E(f) {
606
+ const e = f.textOptions;
609
607
  return {
610
608
  type: "text",
611
609
  text: e?.defaultText || "",
@@ -615,14 +613,14 @@ function P(b) {
615
613
  align: e?.defaultAlign || "center"
616
614
  };
617
615
  }
618
- function A(b) {
619
- return b.contentType === "text" || b.contentType === "both";
616
+ function S(f) {
617
+ return f.contentType === "text" || f.contentType === "both";
620
618
  }
621
- function O(b) {
622
- return b.contentType === "image" || b.contentType === "both";
619
+ function k(f) {
620
+ return f.contentType === "image" || f.contentType === "both";
623
621
  }
624
- const Y = 50;
625
- class j {
622
+ const U = 50;
623
+ class O {
626
624
  constructor(e) {
627
625
  this.history = [], this.historyIndex = -1, this.listeners = /* @__PURE__ */ new Set(), this.isRestoring = !1, this.state = this.cloneState(e), this.history.push({
628
626
  state: this.cloneState(e),
@@ -673,7 +671,7 @@ class j {
673
671
  state: this.cloneState(e),
674
672
  timestamp: Date.now(),
675
673
  description: t
676
- }), this.history.length > Y && this.history.shift(), this.historyIndex = this.history.length - 1;
674
+ }), this.history.length > U && this.history.shift(), this.historyIndex = this.history.length - 1;
677
675
  }
678
676
  restoreFromHistory() {
679
677
  const e = this.history[this.historyIndex];
@@ -696,14 +694,14 @@ class j {
696
694
  return JSON.parse(JSON.stringify(e));
697
695
  }
698
696
  }
699
- class X {
697
+ class T {
700
698
  constructor(e) {
701
699
  this.listeners = /* @__PURE__ */ new Set(), this.areas = e, this.contents = this.buildInitialContents();
702
700
  }
703
701
  buildInitialContents() {
704
702
  const e = /* @__PURE__ */ new Map();
705
703
  for (const t of this.areas)
706
- A(t) && e.set(t.id, P(t));
704
+ S(t) && e.set(t.id, E(t));
707
705
  return e;
708
706
  }
709
707
  getContents() {
@@ -713,7 +711,7 @@ class X {
713
711
  return this.contents.get(e);
714
712
  }
715
713
  setTextContent(e, t) {
716
- const i = this.contents.get(e), n = this.areas.find((s) => s.id === e), r = n ? P(n) : {
714
+ const i = this.contents.get(e), n = this.areas.find((o) => o.id === e), a = n ? E(n) : {
717
715
  type: "text",
718
716
  text: "",
719
717
  font: "Inter",
@@ -721,7 +719,7 @@ class X {
721
719
  color: "#000000",
722
720
  align: "center"
723
721
  };
724
- i?.type === "text" ? this.contents.set(e, { ...i, ...t }) : this.contents.set(e, { ...r, ...t }), this.notify();
722
+ i?.type === "text" ? this.contents.set(e, { ...i, ...t }) : this.contents.set(e, { ...a, ...t }), this.notify();
725
723
  }
726
724
  setImageContent(e, t, i) {
727
725
  const n = {
@@ -733,7 +731,7 @@ class X {
733
731
  }
734
732
  clearContent(e) {
735
733
  const t = this.areas.find((i) => i.id === e);
736
- t && A(t) ? this.contents.set(e, P(t)) : this.contents.delete(e), this.notify();
734
+ t && S(t) ? this.contents.set(e, E(t)) : this.contents.delete(e), this.notify();
737
735
  }
738
736
  setContentOffset(e, t) {
739
737
  const i = this.contents.get(e);
@@ -778,7 +776,7 @@ class X {
778
776
  }
779
777
  }
780
778
  }
781
- class T {
779
+ class I {
782
780
  constructor() {
783
781
  this.listeners = /* @__PURE__ */ new Map();
784
782
  }
@@ -825,11 +823,11 @@ class T {
825
823
  const i = this.listeners.get(e);
826
824
  if (i) {
827
825
  const n = Array.from(i);
828
- for (const r of n)
826
+ for (const a of n)
829
827
  try {
830
- r(t);
831
- } catch (s) {
832
- console.error(`Error in event handler for "${String(e)}":`, s);
828
+ a(t);
829
+ } catch (o) {
830
+ console.error(`Error in event handler for "${String(e)}":`, o);
833
831
  }
834
832
  }
835
833
  }
@@ -851,7 +849,7 @@ class T {
851
849
  return this.listeners.get(e)?.size ?? 0;
852
850
  }
853
851
  }
854
- class W extends T {
852
+ class V extends I {
855
853
  constructor() {
856
854
  super(...arguments), this.activeTouches = /* @__PURE__ */ new Map(), this.initialPinchDistance = null, this.lastPinchCenter = null, this.isTwoFingerGesture = !1;
857
855
  }
@@ -883,14 +881,14 @@ class W extends T {
883
881
  }
884
882
  if (this.activeTouches.size === 2 && this.initialPinchDistance !== null && this.lastPinchCenter) {
885
883
  e.preventDefault();
886
- const [t, i] = Array.from(this.activeTouches.values()), n = this.getDistance(t, i), r = this.getCenter(t, i), s = n / this.initialPinchDistance;
884
+ const [t, i] = Array.from(this.activeTouches.values()), n = this.getDistance(t, i), a = this.getCenter(t, i), o = n / this.initialPinchDistance;
887
885
  this.emit("pinch", {
888
- scale: s,
889
- centerX: r.x,
890
- centerY: r.y
886
+ scale: o,
887
+ centerX: a.x,
888
+ centerY: a.y
891
889
  });
892
- const a = r.x - this.lastPinchCenter.x, o = r.y - this.lastPinchCenter.y;
893
- (Math.abs(a) > 0.5 || Math.abs(o) > 0.5) && this.emit("pan", { deltaX: a, deltaY: o }), this.initialPinchDistance = n, this.lastPinchCenter = r;
890
+ const s = a.x - this.lastPinchCenter.x, r = a.y - this.lastPinchCenter.y;
891
+ (Math.abs(s) > 0.5 || Math.abs(r) > 0.5) && this.emit("pan", { deltaX: s, deltaY: r }), this.initialPinchDistance = n, this.lastPinchCenter = a;
894
892
  }
895
893
  }
896
894
  handleTouchEnd(e) {
@@ -914,24 +912,24 @@ class W extends T {
914
912
  };
915
913
  }
916
914
  }
917
- class G extends T {
915
+ class F extends I {
918
916
  constructor(e, t, i, n) {
919
- super(), this.interactionState = { mode: "idle" }, this.initialPinchZoom = 1, this.canvas = e, this.engine = t, this.getContents = i, this.getSelectedAreaId = n, this.gestureHandler = new W(), 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: r }) => {
920
- const s = Math.max(0.1, Math.min(5, this.initialPinchZoom * r));
921
- this.emit("zoom", { zoom: s });
922
- }), this.gestureHandler.on("pan", ({ deltaX: r, deltaY: s }) => {
923
- const a = this.engine.getPan(), o = this.engine.getZoom();
917
+ super(), this.interactionState = { mode: "idle" }, this.initialPinchZoom = 1, this.canvas = e, this.engine = t, this.getContents = i, this.getSelectedAreaId = n, this.gestureHandler = new V(), 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));
919
+ this.emit("zoom", { zoom: o });
920
+ }), this.gestureHandler.on("pan", ({ deltaX: a, deltaY: o }) => {
921
+ const s = this.engine.getPan(), r = this.engine.getZoom();
924
922
  this.emit("pan", {
925
923
  pan: {
926
- x: a.x + r / o,
927
- y: a.y + s / o
924
+ x: s.x + a / r,
925
+ y: s.y + o / r
928
926
  }
929
927
  });
930
928
  });
931
929
  }
932
930
  getCanvasPosition(e, t) {
933
- const i = this.canvas.getBoundingClientRect(), n = window.devicePixelRatio || 1, r = (e - i.left) * n, s = (t - i.top) * n;
934
- return this.engine.screenToCanvas(r, s);
931
+ const i = this.canvas.getBoundingClientRect(), n = window.devicePixelRatio || 1, a = (e - i.left) * n, o = (t - i.top) * n;
932
+ return this.engine.screenToCanvas(a, o);
935
933
  }
936
934
  handleMouseDown(e) {
937
935
  const t = this.getCanvasPosition(e.clientX, e.clientY);
@@ -978,83 +976,83 @@ class G extends T {
978
976
  processPointerDown(e) {
979
977
  const t = this.getSelectedAreaId(), i = this.getContents();
980
978
  if (t) {
981
- const s = this.engine.hitTestHandle(e.x, e.y, t);
982
- if (s) {
983
- const a = i.get(t), o = this.engine.getContentBounds(t);
984
- if (s === "rotate" && a?.type === "image" && o) {
985
- const d = Math.atan2(e.y - o.centerY, e.x - o.centerX) * (180 / Math.PI);
979
+ const o = this.engine.hitTestHandle(e.x, e.y, t);
980
+ if (o) {
981
+ const s = i.get(t), r = this.engine.getContentBounds(t);
982
+ if (o === "rotate" && s?.type === "image" && r) {
983
+ const d = Math.atan2(e.y - r.centerY, e.x - r.centerX) * (180 / Math.PI);
986
984
  return this.interactionState = {
987
985
  mode: "rotating",
988
986
  areaId: t,
989
- startRotation: a.rotation ?? 0,
990
- centerPoint: { x: o.centerX, y: o.centerY },
987
+ startRotation: s.rotation ?? 0,
988
+ centerPoint: { x: r.centerX, y: r.centerY },
991
989
  startAngle: d
992
990
  }, !0;
993
- } else if (s !== "rotate" && a?.type === "image" && o)
991
+ } else if (o !== "rotate" && s?.type === "image" && r)
994
992
  return this.interactionState = {
995
993
  mode: "scaling",
996
994
  areaId: t,
997
- handle: s,
998
- startScale: a.scale ?? 1,
995
+ handle: o,
996
+ startScale: s.scale ?? 1,
999
997
  startMousePos: e,
1000
- pivotPoint: { x: o.centerX, y: o.centerY }
998
+ pivotPoint: { x: r.centerX, y: r.centerY }
1001
999
  }, !0;
1002
1000
  }
1003
1001
  }
1004
1002
  const n = this.engine.hitTestContent(e.x, e.y);
1005
1003
  if (n) {
1006
1004
  if (this.emit("area:select", { areaId: n }), this.engine.isPositioningAllowed(n)) {
1007
- const s = i.get(n)?.offset ?? { x: 0, y: 0 };
1005
+ const o = i.get(n)?.offset ?? { x: 0, y: 0 };
1008
1006
  return this.interactionState = {
1009
1007
  mode: "dragging",
1010
1008
  areaId: n,
1011
- startOffset: s,
1009
+ startOffset: o,
1012
1010
  startMousePos: e
1013
1011
  }, !0;
1014
1012
  }
1015
1013
  return !0;
1016
1014
  }
1017
- const r = this.engine.hitTestArea(e.x, e.y);
1018
- return r ? (this.emit("area:select", { areaId: r }), !1) : (this.emit("area:select", { areaId: null }), !1);
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);
1019
1017
  }
1020
1018
  processPointerMove(e) {
1021
1019
  if (this.interactionState.mode === "dragging") {
1022
- const { areaId: t, startOffset: i, startMousePos: n } = this.interactionState, r = e.x - n.x, s = e.y - n.y;
1023
- let a = {
1024
- x: i.x + r,
1025
- y: i.y + s
1020
+ const { areaId: t, startOffset: i, startMousePos: n } = this.interactionState, a = e.x - n.x, o = e.y - n.y;
1021
+ let s = {
1022
+ x: i.x + a,
1023
+ y: i.y + o
1026
1024
  };
1027
- const o = this.engine.getAreaLocation(t);
1028
- if (o) {
1029
- const d = o.width / 2 - 10, l = o.height / 2 - 10;
1030
- a.x = Math.max(-d, Math.min(d, a.x)), a.y = Math.max(-l, Math.min(l, a.y));
1025
+ const r = this.engine.getAreaLocation(t);
1026
+ if (r) {
1027
+ const d = r.width / 2 - 10, l = r.height / 2 - 10;
1028
+ s.x = Math.max(-d, Math.min(d, s.x)), s.y = Math.max(-l, Math.min(l, s.y));
1031
1029
  }
1032
- this.emit("content:drag", { areaId: t, offset: a });
1030
+ this.emit("content:drag", { areaId: t, offset: s });
1033
1031
  return;
1034
1032
  }
1035
1033
  if (this.interactionState.mode === "scaling") {
1036
- const { areaId: t, startScale: i, startMousePos: n, pivotPoint: r } = this.interactionState, s = Math.sqrt(
1037
- Math.pow(n.x - r.x, 2) + Math.pow(n.y - r.y, 2)
1038
- ), a = Math.sqrt(
1039
- Math.pow(e.x - r.x, 2) + Math.pow(e.y - r.y, 2)
1034
+ const { areaId: t, startScale: i, startMousePos: n, pivotPoint: a } = this.interactionState, o = Math.sqrt(
1035
+ Math.pow(n.x - a.x, 2) + Math.pow(n.y - a.y, 2)
1036
+ ), s = Math.sqrt(
1037
+ Math.pow(e.x - a.x, 2) + Math.pow(e.y - a.y, 2)
1040
1038
  );
1041
- if (s > 0) {
1042
- const o = a / s, d = Math.max(0.1, Math.min(5, i * o));
1039
+ if (o > 0) {
1040
+ const r = s / o, d = Math.max(0.1, Math.min(5, i * r));
1043
1041
  this.emit("content:scale", { areaId: t, scale: d });
1044
1042
  }
1045
1043
  return;
1046
1044
  }
1047
1045
  if (this.interactionState.mode === "rotating") {
1048
- const { areaId: t, startRotation: i, centerPoint: n, startAngle: r } = this.interactionState, s = Math.atan2(e.y - n.y, e.x - n.x) * (180 / Math.PI);
1049
- let a = i + (s - r);
1050
- a = a % 360, a < 0 && (a += 360);
1051
- const o = [0, 90, 180, 270, 360];
1052
- for (const d of o)
1053
- if (Math.abs(a - d) < 5) {
1054
- a = d === 360 ? 0 : d;
1046
+ const { areaId: t, startRotation: i, centerPoint: n, startAngle: a } = this.interactionState, o = Math.atan2(e.y - n.y, e.x - n.x) * (180 / Math.PI);
1047
+ let s = i + (o - a);
1048
+ s = s % 360, s < 0 && (s += 360);
1049
+ const r = [0, 90, 180, 270, 360];
1050
+ for (const d of r)
1051
+ if (Math.abs(s - d) < 5) {
1052
+ s = d === 360 ? 0 : d;
1055
1053
  break;
1056
1054
  }
1057
- this.emit("content:rotate", { areaId: t, rotation: a });
1055
+ this.emit("content:rotate", { areaId: t, rotation: s });
1058
1056
  return;
1059
1057
  }
1060
1058
  this.updateCursor(e);
@@ -1079,7 +1077,7 @@ class G extends T {
1079
1077
  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();
1080
1078
  }
1081
1079
  }
1082
- class J {
1080
+ class $ {
1083
1081
  constructor(e = "/api") {
1084
1082
  this.baseUrl = e;
1085
1083
  }
@@ -1097,6 +1095,20 @@ class J {
1097
1095
  throw t instanceof Error && t.message.includes("Failed to fetch") ? new Error(`Network error loading template: ${e}`) : t;
1098
1096
  }
1099
1097
  }
1098
+ async getProduct(e) {
1099
+ try {
1100
+ const t = await fetch(`${this.baseUrl}/products/${e}`);
1101
+ if (!t.ok) {
1102
+ let i = `Failed to load product (${t.status} ${t.statusText}): ${e}
1103
+
1104
+ `;
1105
+ throw t.status === 404 ? i += "Product not found." : t.status === 403 ? i += "Access forbidden." : t.status >= 500 && (i += "Server error."), new Error(i);
1106
+ }
1107
+ return t.json();
1108
+ } catch (t) {
1109
+ throw t instanceof Error && t.message.includes("Failed to fetch") ? new Error(`Network error loading product: ${e}`) : t;
1110
+ }
1111
+ }
1100
1112
  async finalizeDesign(e, t) {
1101
1113
  try {
1102
1114
  const i = await fetch(`${this.baseUrl}/finalize`, {
@@ -1113,8 +1125,8 @@ class J {
1113
1125
 
1114
1126
  `;
1115
1127
  if (i.status === 400) {
1116
- const r = await i.json().catch(() => ({}));
1117
- n += r.error || "Invalid design data.";
1128
+ const a = await i.json().catch(() => ({}));
1129
+ n += a.error || "Invalid design data.";
1118
1130
  } else i.status === 429 ? n += "Rate limit exceeded." : i.status >= 500 && (n += "Server error.");
1119
1131
  throw new Error(n);
1120
1132
  }
@@ -1132,14 +1144,14 @@ class J {
1132
1144
  body: i
1133
1145
  });
1134
1146
  if (!n.ok) {
1135
- let r = `Failed to upload asset (${n.status} ${n.statusText})
1147
+ let a = `Failed to upload asset (${n.status} ${n.statusText})
1136
1148
 
1137
1149
  `;
1138
1150
  if (n.status === 400) {
1139
- const s = await n.json().catch(() => ({}));
1140
- r += s.error || "Invalid file.";
1141
- } else n.status === 413 ? r += "File too large (max 15MB)." : n.status >= 500 && (r += "Server error.");
1142
- throw new Error(r);
1151
+ const o = await n.json().catch(() => ({}));
1152
+ a += o.error || "Invalid file.";
1153
+ } else n.status === 413 ? a += "File too large (max 15MB)." : n.status >= 500 && (a += "Server error.");
1154
+ throw new Error(a);
1143
1155
  }
1144
1156
  return n.json();
1145
1157
  } catch (i) {
@@ -1157,96 +1169,125 @@ class J {
1157
1169
  }
1158
1170
  }
1159
1171
  }
1160
- function c(b, e, ...t) {
1161
- const i = document.createElement(b);
1162
- return e && Object.entries(e).forEach(([n, r]) => {
1163
- n === "class" ? i.className = r : n in i ? i[n] = r : i.setAttribute(n, String(r));
1172
+ function h(f, e, ...t) {
1173
+ const i = document.createElement(f);
1174
+ return e && Object.entries(e).forEach(([n, a]) => {
1175
+ n === "class" ? i.className = a : n in i ? i[n] = a : i.setAttribute(n, String(a));
1164
1176
  }), t.forEach((n) => {
1165
1177
  typeof n == "string" ? i.appendChild(document.createTextNode(n)) : i.appendChild(n);
1166
1178
  }), i;
1167
1179
  }
1168
- function V(b, e) {
1180
+ function H(f, e) {
1169
1181
  let t = null, i = null;
1170
- const n = (...r) => {
1171
- i = r, t && clearTimeout(t), t = setTimeout(() => {
1172
- i && b(...i), t = null, i = null;
1182
+ const n = (...a) => {
1183
+ i = a, t && clearTimeout(t), t = setTimeout(() => {
1184
+ i && f(...i), t = null, i = null;
1173
1185
  }, e);
1174
1186
  };
1175
1187
  return n.flush = () => {
1176
- t && (clearTimeout(t), t = null), i && (b(...i), i = null);
1188
+ t && (clearTimeout(t), t = null), i && (f(...i), i = null);
1177
1189
  }, n.cancel = () => {
1178
1190
  t && (clearTimeout(t), t = null), i = null;
1179
1191
  }, n;
1180
1192
  }
1181
- class K extends T {
1193
+ class B extends I {
1182
1194
  constructor() {
1183
- super(), this.percentLabel = c("span", { class: "zoom-percent" }, "100%");
1184
- const e = c("button", {
1195
+ super(), this.percentLabel = h("span", { class: "zoom-percent" }, "100%");
1196
+ const e = h("button", {
1185
1197
  class: "zoom-btn",
1186
1198
  title: "Zoom out"
1187
1199
  }, "−");
1188
1200
  e.addEventListener("click", () => this.emit("zoom-out", void 0));
1189
- const t = c("button", {
1201
+ const t = h("button", {
1190
1202
  class: "zoom-btn",
1191
1203
  title: "Fit to view"
1192
1204
  }, "Fit");
1193
1205
  t.addEventListener("click", () => this.emit("zoom-fit", void 0));
1194
- const i = c("button", {
1206
+ const i = h("button", {
1195
1207
  class: "zoom-btn",
1196
1208
  title: "Zoom in"
1197
1209
  }, "+");
1198
- i.addEventListener("click", () => this.emit("zoom-in", void 0)), this.element = c("div", { class: "zoom-toolbar" }), this.element.appendChild(e), this.element.appendChild(t), this.element.appendChild(i), this.element.appendChild(this.percentLabel);
1210
+ i.addEventListener("click", () => this.emit("zoom-in", void 0)), this.viewSelect = document.createElement("select"), this.viewSelect.className = "view-select", this.viewSelect.title = "Switch view", this.viewSelect.style.display = "none", this.viewSelect.addEventListener("change", () => {
1211
+ this.emit("view-change", { viewName: this.viewSelect.value });
1212
+ }), 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
+ const n = h("div", { class: "zoom-toolbar-spacer" });
1214
+ this.element.appendChild(n);
1215
+ const a = h("button", {
1216
+ class: "zoom-btn zoom-close-btn",
1217
+ title: "Close editor"
1218
+ }, "✕ Close");
1219
+ a.addEventListener("click", () => this.emit("close", void 0)), this.element.appendChild(a), this.saveBtn = h("button", {
1220
+ class: "zoom-btn zoom-save-btn",
1221
+ title: "Save customization"
1222
+ }, "Save Customization"), this.saveBtn.style.display = "none", this.saveBtn.addEventListener("click", () => this.emit("save", void 0)), this.element.appendChild(this.saveBtn);
1199
1223
  }
1200
1224
  setZoom(e) {
1201
1225
  this.percentLabel.textContent = `${Math.round(e * 100)}%`;
1202
1226
  }
1227
+ showSaveButton(e) {
1228
+ this.saveBtn.style.display = e ? "" : "none";
1229
+ }
1230
+ setSaveDisabled(e, t) {
1231
+ this.saveBtn.disabled = e, t && (this.saveBtn.textContent = t);
1232
+ }
1233
+ setViews(e) {
1234
+ this.viewSelect.innerHTML = "";
1235
+ for (const t of e) {
1236
+ const i = document.createElement("option");
1237
+ i.value = t.viewName, i.textContent = t.viewName, this.viewSelect.appendChild(i);
1238
+ }
1239
+ this.viewSelect.style.display = e.length > 1 ? "" : "none";
1240
+ }
1241
+ setActiveView(e) {
1242
+ this.viewSelect.value = e;
1243
+ }
1203
1244
  getElement() {
1204
1245
  return this.element;
1205
1246
  }
1206
1247
  }
1207
- const Q = [
1248
+ const Z = [
1208
1249
  { label: "Inter", value: "Inter" },
1209
1250
  { label: "Arial", value: "Arial" },
1210
1251
  { label: "Georgia", value: "Georgia" },
1211
1252
  { label: "Times New Roman", value: "Times New Roman" },
1212
1253
  { label: "Courier New", value: "Courier New" }
1213
- ], _ = [
1254
+ ], q = [
1214
1255
  { label: "L", value: "left" },
1215
1256
  { label: "C", value: "center" },
1216
1257
  { label: "R", value: "right" }
1217
- ], H = ["image/png", "image/jpeg", "image/webp", "image/svg+xml"], ee = 15 * 1024 * 1024;
1218
- class te extends T {
1258
+ ], P = ["image/png", "image/jpeg", "image/webp", "image/svg+xml"], Y = 15 * 1024 * 1024;
1259
+ class L extends I {
1219
1260
  constructor(e) {
1220
1261
  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;
1221
- const t = A(e), i = O(e);
1222
- this.mode = i && !t ? "image" : "text", this.element = c("div", { class: "area-card" }), this.element.addEventListener("click", (o) => {
1223
- o.target.tagName === "INPUT" || o.target.tagName === "SELECT" || o.target.tagName === "BUTTON" || this.emit("select", { areaId: e.id });
1262
+ const t = S(e), i = k(e);
1263
+ this.mode = i && !t ? "image" : "text", this.element = h("div", { class: "area-card" }), this.element.addEventListener("click", (r) => {
1264
+ r.target.tagName === "INPUT" || r.target.tagName === "SELECT" || r.target.tagName === "BUTTON" || this.emit("select", { areaId: e.id });
1224
1265
  });
1225
- const n = c("div", { class: "area-card-header" }), r = c("div", { class: "area-card-name-row" }), s = c("span", { class: "area-card-name" }, e.name);
1226
- if (r.appendChild(s), e.required) {
1227
- const o = c("span", { class: "area-card-badge" }, "Required");
1228
- r.appendChild(o);
1266
+ const n = h("div", { class: "area-card-header" }), a = h("div", { class: "area-card-name-row" }), o = h("span", { class: "area-card-name" }, e.name);
1267
+ if (a.appendChild(o), e.required) {
1268
+ const r = h("span", { class: "area-card-badge" }, "Required");
1269
+ a.appendChild(r);
1229
1270
  }
1230
- n.appendChild(r);
1231
- const a = c("button", {
1271
+ n.appendChild(a);
1272
+ const s = h("button", {
1232
1273
  class: "area-card-reset-btn",
1233
1274
  title: "Reset content"
1234
1275
  }, "Reset");
1235
- if (a.addEventListener("click", (o) => {
1236
- o.stopPropagation(), this.emit("clear", { areaId: e.id });
1237
- }), n.appendChild(a), this.element.appendChild(n), t && i) {
1238
- const o = c("div", { class: "area-card-mode-switcher" }), d = c("button", {
1276
+ if (s.addEventListener("click", (r) => {
1277
+ r.stopPropagation(), this.emit("clear", { areaId: e.id });
1278
+ }), n.appendChild(s), this.element.appendChild(n), t && i) {
1279
+ const r = h("div", { class: "area-card-mode-switcher" }), d = h("button", {
1239
1280
  class: "mode-btn mode-btn-active"
1240
1281
  }, "Text");
1241
1282
  d.dataset.mode = "text";
1242
- const l = c("button", { class: "mode-btn" }, "Image");
1243
- l.dataset.mode = "image", d.addEventListener("click", (h) => {
1244
- h.stopPropagation(), this.setMode("text"), d.classList.add("mode-btn-active"), l.classList.remove("mode-btn-active"), this.currentContent?.type === "image" && this.emit("clear", { areaId: e.id });
1245
- }), l.addEventListener("click", (h) => {
1246
- h.stopPropagation(), this.setMode("image"), l.classList.add("mode-btn-active"), d.classList.remove("mode-btn-active"), this.currentContent?.type === "text" && this.emit("clear", { areaId: e.id });
1247
- }), o.appendChild(d), o.appendChild(l), this.element.appendChild(o);
1283
+ const l = h("button", { class: "mode-btn" }, "Image");
1284
+ l.dataset.mode = "image", d.addEventListener("click", (c) => {
1285
+ c.stopPropagation(), this.setMode("text"), d.classList.add("mode-btn-active"), l.classList.remove("mode-btn-active"), this.currentContent?.type === "image" && this.emit("clear", { areaId: e.id });
1286
+ }), l.addEventListener("click", (c) => {
1287
+ c.stopPropagation(), this.setMode("image"), l.classList.add("mode-btn-active"), d.classList.remove("mode-btn-active"), this.currentContent?.type === "text" && this.emit("clear", { areaId: e.id });
1288
+ }), r.appendChild(d), r.appendChild(l), this.element.appendChild(r);
1248
1289
  }
1249
- this.contentContainer = c("div", { class: "area-card-content" }), this.element.appendChild(this.contentContainer), this.transformContainer = c("div", { class: "area-card-transforms" }), this.element.appendChild(this.transformContainer), this.renderContent();
1290
+ this.contentContainer = h("div", { class: "area-card-content" }), this.element.appendChild(this.contentContainer), this.transformContainer = h("div", { class: "area-card-transforms" }), this.element.appendChild(this.transformContainer), this.renderContent();
1250
1291
  }
1251
1292
  setMode(e) {
1252
1293
  this.mode = e, this.renderContent();
@@ -1269,11 +1310,11 @@ class te extends T {
1269
1310
  }
1270
1311
  renderContent() {
1271
1312
  this.contentContainer.innerHTML = "", this.textInputEl = null, this.fontSelectEl = null, this.sizeInputEl = null, this.colorInputEl = null, this.alignGroupEl = null;
1272
- const e = A(this.area), t = O(this.area), i = !t || e && t && this.mode === "text", n = !e || e && t && this.mode === "image";
1313
+ const e = S(this.area), t = k(this.area), i = !t || e && t && this.mode === "text", n = !e || e && t && this.mode === "image";
1273
1314
  e && i && this.renderTextControls(), t && n && this.renderImageControls();
1274
1315
  }
1275
1316
  renderTextControls() {
1276
- const e = this.currentContent, t = e?.type === "text" ? e : P(this.area), i = c("input", {
1317
+ const e = this.currentContent, t = e?.type === "text" ? e : E(this.area), i = h("input", {
1277
1318
  class: "area-input-text",
1278
1319
  type: "text",
1279
1320
  placeholder: this.area.placeholder || "Enter text..."
@@ -1281,79 +1322,79 @@ class te extends T {
1281
1322
  i.value = t.text, this.area.textOptions?.maxLength && (i.maxLength = this.area.textOptions.maxLength), i.addEventListener("input", () => {
1282
1323
  this.emit("text:change", { areaId: this.area.id, updates: { text: i.value } });
1283
1324
  }), this.contentContainer.appendChild(i), this.textInputEl = i;
1284
- const n = c("div", { class: "area-input-row" }), r = c("select", { class: "area-input-font" });
1285
- for (const l of Q) {
1286
- const h = c("option");
1287
- h.value = l.value, h.textContent = l.label, l.value === t.font && (h.selected = !0), r.appendChild(h);
1325
+ const n = h("div", { class: "area-input-row" }), a = h("select", { class: "area-input-font" });
1326
+ for (const l of Z) {
1327
+ const c = h("option");
1328
+ c.value = l.value, c.textContent = l.label, l.value === t.font && (c.selected = !0), a.appendChild(c);
1288
1329
  }
1289
- r.addEventListener("change", () => {
1290
- this.emit("text:change", { areaId: this.area.id, updates: { font: r.value } });
1291
- }), n.appendChild(r), this.fontSelectEl = r;
1292
- const s = c("input", {
1330
+ a.addEventListener("change", () => {
1331
+ this.emit("text:change", { areaId: this.area.id, updates: { font: a.value } });
1332
+ }), n.appendChild(a), this.fontSelectEl = a;
1333
+ const o = h("input", {
1293
1334
  class: "area-input-size",
1294
1335
  type: "number"
1295
1336
  });
1296
- s.value = String(t.size), s.min = String(this.area.textOptions?.minSize || 8), s.max = String(this.area.textOptions?.maxSize || 200), s.addEventListener("change", () => {
1337
+ o.value = String(t.size), o.min = String(this.area.textOptions?.minSize || 8), o.max = String(this.area.textOptions?.maxSize || 200), o.addEventListener("change", () => {
1297
1338
  this.emit("text:change", {
1298
1339
  areaId: this.area.id,
1299
- updates: { size: parseInt(s.value, 10) || 24 }
1340
+ updates: { size: parseInt(o.value, 10) || 24 }
1300
1341
  });
1301
- }), n.appendChild(s), this.sizeInputEl = s, this.contentContainer.appendChild(n);
1302
- const a = c("div", { class: "area-input-row" }), o = c("div", { class: "area-input-align" });
1303
- for (const l of _) {
1304
- const h = c("button", {
1342
+ }), n.appendChild(o), this.sizeInputEl = o, this.contentContainer.appendChild(n);
1343
+ const s = h("div", { class: "area-input-row" }), r = h("div", { class: "area-input-align" });
1344
+ for (const l of q) {
1345
+ const c = h("button", {
1305
1346
  class: `align-btn${t.align === l.value ? " align-btn-active" : ""}`,
1306
1347
  title: l.label === "L" ? "Left" : l.label === "C" ? "Center" : "Right"
1307
1348
  }, l.label);
1308
- h.addEventListener("click", (u) => {
1309
- u.stopPropagation(), this.emit("text:change", { areaId: this.area.id, updates: { align: l.value } }), o.querySelectorAll(".align-btn").forEach((g) => g.classList.remove("align-btn-active")), h.classList.add("align-btn-active");
1310
- }), o.appendChild(h);
1349
+ c.addEventListener("click", (u) => {
1350
+ 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");
1351
+ }), r.appendChild(c);
1311
1352
  }
1312
- a.appendChild(o), this.alignGroupEl = o;
1313
- const d = c("input", {
1353
+ s.appendChild(r), this.alignGroupEl = r;
1354
+ const d = h("input", {
1314
1355
  class: "area-input-color",
1315
1356
  type: "color"
1316
1357
  });
1317
1358
  d.value = t.color, d.addEventListener("input", () => {
1318
1359
  this.emit("text:change", { areaId: this.area.id, updates: { color: d.value } });
1319
- }), a.appendChild(d), this.colorInputEl = d, this.contentContainer.appendChild(a);
1360
+ }), s.appendChild(d), this.colorInputEl = d, this.contentContainer.appendChild(s);
1320
1361
  }
1321
1362
  renderImageControls() {
1322
1363
  const e = this.currentContent;
1323
1364
  if (e?.type === "image") {
1324
- const t = c("div", { class: "area-image-preview" }), i = c("img", { class: "area-image-thumb" });
1365
+ const t = h("div", { class: "area-image-preview" }), i = h("img", { class: "area-image-thumb" });
1325
1366
  i.src = e.dataUrl, i.alt = e.filename || "Uploaded image", t.appendChild(i);
1326
- const n = c("div", { class: "area-image-info" });
1327
- n.appendChild(c("span", { class: "area-image-name" }, e.filename || "Image"));
1328
- const r = c("button", { class: "area-image-remove-btn" }, "Remove");
1329
- r.addEventListener("click", (s) => {
1330
- s.stopPropagation(), this.emit("clear", { areaId: this.area.id });
1331
- }), n.appendChild(r), t.appendChild(n), this.contentContainer.appendChild(t);
1367
+ const n = h("div", { class: "area-image-info" });
1368
+ n.appendChild(h("span", { class: "area-image-name" }, e.filename || "Image"));
1369
+ const a = h("button", { class: "area-image-remove-btn" }, "Remove");
1370
+ a.addEventListener("click", (o) => {
1371
+ o.stopPropagation(), this.emit("clear", { areaId: this.area.id });
1372
+ }), n.appendChild(a), t.appendChild(n), this.contentContainer.appendChild(t);
1332
1373
  } else {
1333
- const t = c("button", { class: "area-upload-btn" }, "Upload Image"), i = c("input", { type: "file" });
1334
- i.accept = H.join(","), i.style.display = "none";
1335
- const n = c("div", { class: "area-validation-error" });
1374
+ const t = h("button", { class: "area-upload-btn" }, "Upload Image"), i = h("input", { type: "file" });
1375
+ i.accept = P.join(","), i.style.display = "none";
1376
+ const n = h("div", { class: "area-validation-error" });
1336
1377
  n.style.display = "none", i.addEventListener("change", () => {
1337
- const r = i.files?.[0];
1338
- if (!r) return;
1339
- if (!H.includes(r.type)) {
1340
- const a = "Only PNG, JPEG, WebP, and SVG images are accepted";
1341
- n.textContent = a, n.style.display = "block", this.emit("validation:error", { areaId: this.area.id, message: a }), i.value = "";
1378
+ const a = i.files?.[0];
1379
+ if (!a) return;
1380
+ if (!P.includes(a.type)) {
1381
+ const s = "Only PNG, JPEG, WebP, and SVG images are accepted";
1382
+ n.textContent = s, n.style.display = "block", this.emit("validation:error", { areaId: this.area.id, message: s }), i.value = "";
1342
1383
  return;
1343
1384
  }
1344
- if (r.size > ee) {
1345
- const a = "File must be smaller than 15MB";
1346
- n.textContent = a, n.style.display = "block", this.emit("validation:error", { areaId: this.area.id, message: a }), i.value = "";
1385
+ if (a.size > Y) {
1386
+ const s = "File must be smaller than 15MB";
1387
+ n.textContent = s, n.style.display = "block", this.emit("validation:error", { areaId: this.area.id, message: s }), i.value = "";
1347
1388
  return;
1348
1389
  }
1349
1390
  n.style.display = "none";
1350
- const s = new FileReader();
1351
- s.onload = () => {
1352
- const a = s.result;
1353
- this.emit("image:change", { areaId: this.area.id, dataUrl: a, filename: r.name });
1354
- }, s.readAsDataURL(r), i.value = "";
1355
- }), t.addEventListener("click", (r) => {
1356
- r.stopPropagation(), i.click();
1391
+ const o = new FileReader();
1392
+ o.onload = () => {
1393
+ const s = o.result;
1394
+ this.emit("image:change", { areaId: this.area.id, dataUrl: s, filename: a.name });
1395
+ }, o.readAsDataURL(a), i.value = "";
1396
+ }), t.addEventListener("click", (a) => {
1397
+ a.stopPropagation(), i.click();
1357
1398
  }), this.contentContainer.appendChild(t), this.contentContainer.appendChild(i), this.contentContainer.appendChild(n);
1358
1399
  }
1359
1400
  }
@@ -1363,72 +1404,82 @@ class te extends T {
1363
1404
  if (!this.isSelected || !e) return;
1364
1405
  const t = e.type === "text" ? this.area.textOptions?.allowPositioning : this.area.imageOptions?.allowPositioning, i = e.type === "image" && this.area.imageOptions?.allowScaling, n = e.type === "image" && this.area.imageOptions?.allowRotation;
1365
1406
  if (!t && !i && !n) return;
1366
- const r = c("div", { class: "area-card-divider" });
1367
- this.transformContainer.appendChild(r);
1368
- const s = c("div", { class: "transform-header" });
1369
- s.appendChild(c("span", { class: "transform-title" }, "Transform"));
1370
- const a = c("button", { class: "transform-reset-btn" }, "Reset");
1371
- a.addEventListener("click", (d) => {
1407
+ const a = h("div", { class: "area-card-divider" });
1408
+ this.transformContainer.appendChild(a);
1409
+ const o = h("div", { class: "transform-header" });
1410
+ o.appendChild(h("span", { class: "transform-title" }, "Transform"));
1411
+ const s = h("button", { class: "transform-reset-btn" }, "Reset");
1412
+ s.addEventListener("click", (d) => {
1372
1413
  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 }), n && this.emit("rotation:change", { areaId: this.area.id, rotation: 0 });
1373
- }), s.appendChild(a), this.transformContainer.appendChild(s);
1374
- const o = e.offset ?? { x: 0, y: 0 };
1414
+ }), o.appendChild(s), this.transformContainer.appendChild(o);
1415
+ const r = e.offset ?? { x: 0, y: 0 };
1375
1416
  if (t) {
1376
- const d = c("div", { class: "area-input-row" }), l = c("label", { class: "transform-label" }, "X"), h = c("input", { class: "transform-input", type: "number" });
1377
- h.value = String(Math.round(o.x)), h.addEventListener("change", () => {
1417
+ const d = h("div", { class: "area-input-row" }), l = h("label", { class: "transform-label" }, "X"), c = h("input", { class: "transform-input", type: "number" });
1418
+ c.value = String(Math.round(r.x)), c.addEventListener("change", () => {
1378
1419
  this.emit("offset:change", {
1379
1420
  areaId: this.area.id,
1380
- offset: { ...o, x: parseInt(h.value, 10) || 0 }
1421
+ offset: { ...r, x: parseInt(c.value, 10) || 0 }
1381
1422
  });
1382
1423
  });
1383
- const u = c("label", { class: "transform-label" }, "Y"), g = c("input", { class: "transform-input", type: "number" });
1384
- g.value = String(Math.round(o.y)), g.addEventListener("change", () => {
1424
+ const u = h("label", { class: "transform-label" }, "Y"), p = h("input", { class: "transform-input", type: "number" });
1425
+ p.value = String(Math.round(r.y)), p.addEventListener("change", () => {
1385
1426
  this.emit("offset:change", {
1386
1427
  areaId: this.area.id,
1387
- offset: { ...o, y: parseInt(g.value, 10) || 0 }
1428
+ offset: { ...r, y: parseInt(p.value, 10) || 0 }
1388
1429
  });
1389
- }), d.appendChild(l), d.appendChild(h), d.appendChild(u), d.appendChild(g), this.transformContainer.appendChild(d);
1430
+ }), d.appendChild(l), d.appendChild(c), d.appendChild(u), d.appendChild(p), this.transformContainer.appendChild(d);
1390
1431
  }
1391
1432
  if (i && e.type === "image") {
1392
- const d = e.scale ?? 1, l = c("div", { class: "area-input-row transform-slider-row" }), h = c("label", { class: "transform-label" }, `Scale: ${Math.round(d * 100)}%`), u = c("input", { class: "transform-slider", type: "range" });
1433
+ 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" });
1393
1434
  u.min = "10", u.max = "500", u.step = "5", u.value = String(Math.round(d * 100)), u.addEventListener("input", () => {
1394
- const g = parseInt(u.value, 10) / 100;
1395
- h.textContent = `Scale: ${Math.round(g * 100)}%`, this.emit("scale:change", { areaId: this.area.id, scale: g });
1396
- }), l.appendChild(h), l.appendChild(u), this.transformContainer.appendChild(l);
1435
+ const p = parseInt(u.value, 10) / 100;
1436
+ c.textContent = `Scale: ${Math.round(p * 100)}%`, this.emit("scale:change", { areaId: this.area.id, scale: p });
1437
+ }), l.appendChild(c), l.appendChild(u), this.transformContainer.appendChild(l);
1397
1438
  }
1398
1439
  if (n && e.type === "image") {
1399
- const d = e.rotation ?? 0, l = c("div", { class: "area-input-row transform-slider-row" }), h = c("label", { class: "transform-label" }, `Rotation: ${Math.round(d)}°`), u = c("input", { class: "transform-slider", type: "range" });
1440
+ 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" });
1400
1441
  u.min = "0", u.max = "360", u.step = "1", u.value = String(Math.round(d)), u.addEventListener("input", () => {
1401
- const g = parseInt(u.value, 10);
1402
- h.textContent = `Rotation: ${g}°`, this.emit("rotation:change", { areaId: this.area.id, rotation: g });
1403
- }), l.appendChild(h), l.appendChild(u), this.transformContainer.appendChild(l);
1442
+ const p = parseInt(u.value, 10);
1443
+ c.textContent = `Rotation: ${p}°`, this.emit("rotation:change", { areaId: this.area.id, rotation: p });
1444
+ }), l.appendChild(c), l.appendChild(u), this.transformContainer.appendChild(l);
1404
1445
  }
1405
1446
  }
1406
1447
  getElement() {
1407
1448
  return this.element;
1408
1449
  }
1409
1450
  }
1410
- class ie extends T {
1451
+ class j extends I {
1411
1452
  constructor(e) {
1412
1453
  super(), this.cards = /* @__PURE__ */ new Map(), this.isMobile = !1, this.boundEscapeHandler = (n) => {
1413
1454
  n.key === "Escape" && this.emit("dismiss", void 0);
1414
- }, this.backdrop = c("div", { class: "area-panel-backdrop" }), this.backdrop.addEventListener("click", () => {
1455
+ }, this.backdrop = h("div", { class: "area-panel-backdrop" }), this.backdrop.addEventListener("click", () => {
1415
1456
  this.emit("dismiss", void 0);
1416
- }), this.element = c("div", { class: "area-panel" });
1417
- const t = c("div", { class: "area-panel-header" });
1418
- t.appendChild(c("span", { class: "area-panel-title" }, "Customize"));
1419
- const i = c("button", { class: "area-panel-close-btn", title: "Close" }, "×");
1457
+ }), this.element = h("div", { class: "area-panel" });
1458
+ const t = h("div", { class: "area-panel-header" });
1459
+ t.appendChild(h("span", { class: "area-panel-title" }, "Customize"));
1460
+ const i = h("button", { class: "area-panel-close-btn", title: "Close" }, "×");
1420
1461
  if (i.addEventListener("click", () => {
1421
1462
  this.emit("dismiss", void 0);
1422
- }), t.appendChild(i), this.element.appendChild(t), this.panelContent = c("div", { class: "area-panel-content" }), e.length === 0) {
1423
- const n = c("p", { class: "area-panel-empty" }, "No customization areas defined.");
1463
+ }), t.appendChild(i), this.element.appendChild(t), this.panelContent = h("div", { class: "area-panel-content" }), e.length === 0) {
1464
+ const n = h("p", { class: "area-panel-empty" }, "No customization areas defined.");
1424
1465
  this.panelContent.appendChild(n);
1425
1466
  } else
1426
1467
  for (const n of e) {
1427
- const r = new te(n);
1428
- this.cards.set(n.id, r), r.on("text:change", (s) => this.emit("text:change", s)), r.on("image:change", (s) => this.emit("image:change", s)), r.on("clear", (s) => this.emit("clear", s)), r.on("select", (s) => this.emit("select", s)), r.on("offset:change", (s) => this.emit("offset:change", s)), r.on("scale:change", (s) => this.emit("scale:change", s)), r.on("rotation:change", (s) => this.emit("rotation:change", s)), r.on("validation:error", (s) => this.emit("validation:error", s)), this.panelContent.appendChild(r.getElement());
1468
+ const a = new L(n);
1469
+ this.cards.set(n.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());
1429
1470
  }
1430
1471
  this.element.appendChild(this.panelContent);
1431
1472
  }
1473
+ setAreas(e) {
1474
+ if (this.cards.clear(), this.panelContent.innerHTML = "", e.length === 0) {
1475
+ const t = h("p", { class: "area-panel-empty" }, "No customization areas defined.");
1476
+ this.panelContent.appendChild(t);
1477
+ } else
1478
+ for (const t of e) {
1479
+ const i = new L(t);
1480
+ this.cards.set(t.id, i), i.on("text:change", (n) => this.emit("text:change", n)), i.on("image:change", (n) => this.emit("image:change", n)), i.on("clear", (n) => this.emit("clear", n)), i.on("select", (n) => this.emit("select", n)), i.on("offset:change", (n) => this.emit("offset:change", n)), i.on("scale:change", (n) => this.emit("scale:change", n)), i.on("rotation:change", (n) => this.emit("rotation:change", n)), i.on("validation:error", (n) => this.emit("validation:error", n)), this.panelContent.appendChild(i.getElement());
1481
+ }
1482
+ }
1432
1483
  setContents(e) {
1433
1484
  for (const [t, i] of this.cards)
1434
1485
  i.setContent(e.get(t));
@@ -1450,7 +1501,7 @@ class ie extends T {
1450
1501
  return this.backdrop;
1451
1502
  }
1452
1503
  }
1453
- class ne {
1504
+ class X {
1454
1505
  constructor(e = window) {
1455
1506
  this.target = e, this.commands = [], this.isEnabled = !0, this.handleKeyDown = (t) => {
1456
1507
  if (!this.isEnabled) return;
@@ -1504,31 +1555,31 @@ class ne {
1504
1555
  this.target.removeEventListener("keydown", this.handleKeyDown), this.commands = [];
1505
1556
  }
1506
1557
  }
1507
- function re() {
1558
+ function W() {
1508
1559
  return /Mac|iPod|iPhone|iPad/.test(navigator.platform);
1509
1560
  }
1510
- function ae() {
1511
- return re() ? "meta" : "ctrl";
1561
+ function J() {
1562
+ return W() ? "meta" : "ctrl";
1512
1563
  }
1513
- function se(b, e, t, i) {
1514
- const n = b / t, r = e / i;
1515
- return Math.min(n, r);
1564
+ function G(f, e, t, i) {
1565
+ const n = f / t, a = e / i;
1566
+ return Math.min(n, a);
1516
1567
  }
1517
- function oe(b, e, t = 8, i = 10, n = 300) {
1518
- const r = se(b, e, t, i), s = r >= n;
1568
+ function K(f, e, t = 8, i = 10, n = 300) {
1569
+ const a = G(f, e, t, i), o = a >= n;
1519
1570
  return {
1520
- actualDPI: Math.round(r),
1571
+ actualDPI: Math.round(a),
1521
1572
  targetDPI: n,
1522
- width: b,
1573
+ width: f,
1523
1574
  height: e,
1524
- passed: s,
1525
- warning: s ? void 0 : `Image resolution is ${Math.round(r)} DPI at 100% scale. For best print quality, use images with at least ${n} DPI. This may result in pixelated or blurry prints.`
1575
+ passed: o,
1576
+ warning: o ? void 0 : `Image resolution is ${Math.round(a)} DPI at 100% scale. For best print quality, use images with at least ${n} DPI. This may result in pixelated or blurry prints.`
1526
1577
  };
1527
1578
  }
1528
- const F = ':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}.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}}', de = "2.0.0", le = 768;
1529
- class he extends HTMLElement {
1579
+ const z = ':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;
1580
+ class ee extends HTMLElement {
1530
1581
  constructor() {
1531
- super(), this.resizeObserver = null, this.isReady = !1, this.currentTemplate = null, this.loadingTemplateId = null, this.isMobileLayout = !1, this.subscriptions = [], this.storageUsage = null, this.storageUsageLastRefresh = 0, this.emitChange = V(() => {
1582
+ 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 = H(() => {
1532
1583
  this.dispatchEvent(
1533
1584
  new CustomEvent("change", {
1534
1585
  detail: { design: this.getDesign() },
@@ -1539,7 +1590,7 @@ class he extends HTMLElement {
1539
1590
  }, 300), this.shadow = this.attachShadow({ mode: "open" });
1540
1591
  }
1541
1592
  static get observedAttributes() {
1542
- return ["template-id", "theme", "mode", "store-id", "api-url"];
1593
+ return ["template-id", "product-id", "theme", "mode", "store-id", "api-url"];
1543
1594
  }
1544
1595
  async connectedCallback() {
1545
1596
  try {
@@ -1552,14 +1603,14 @@ class he extends HTMLElement {
1552
1603
  this.cleanup();
1553
1604
  }
1554
1605
  attributeChangedCallback(e, t, i) {
1555
- t !== i && e === "template-id" && this.isReady && i && this.loadTemplate(i);
1606
+ t !== i && (e === "template-id" && this.isReady && i && this.loadTemplate(i), e === "product-id" && this.isReady && i && this.loadProduct(i));
1556
1607
  }
1557
1608
  // ─── Public API ───
1558
1609
  getDesign() {
1559
1610
  if (!this.currentTemplate) throw new Error("No design loaded");
1560
1611
  return {
1561
1612
  templateId: this.currentTemplate.metadata.id,
1562
- engineVersion: de,
1613
+ engineVersion: Q,
1563
1614
  contents: this.contentManager.toJSON(),
1564
1615
  userData: {}
1565
1616
  };
@@ -1583,7 +1634,7 @@ class he extends HTMLElement {
1583
1634
  contents: this.contentManager.toJSON(),
1584
1635
  selectedAreaId: null,
1585
1636
  isDirty: !1
1586
- }), this.engine.setSelectedArea(null), this.inputPanel.setSelectedArea(null), this.engine.fitToView(), this.stateManager.update({
1637
+ }), this.zoomToolbar?.showSaveButton(!1), this.engine.setSelectedArea(null), this.inputPanel.setSelectedArea(null), this.engine.fitToView(), this.stateManager.update({
1587
1638
  zoom: this.engine.getZoom(),
1588
1639
  pan: this.engine.getPan()
1589
1640
  });
@@ -1594,6 +1645,24 @@ class he extends HTMLElement {
1594
1645
  getSelectedAreaId() {
1595
1646
  return this.stateManager.getState().selectedAreaId;
1596
1647
  }
1648
+ getActiveView() {
1649
+ return this.activeViewName;
1650
+ }
1651
+ getViews() {
1652
+ return this.productViews.map((e) => ({
1653
+ viewName: e.viewName,
1654
+ viewOrder: e.viewOrder,
1655
+ isRequired: e.isRequired,
1656
+ isDefault: e.isDefault,
1657
+ templateId: e.template.id
1658
+ }));
1659
+ }
1660
+ setActiveView(e) {
1661
+ if (this.activeViewName === e) return;
1662
+ const t = this.productViews.find((i) => i.viewName === e);
1663
+ if (!t) throw new Error(`View not found: ${e}`);
1664
+ this.switchToView(t);
1665
+ }
1597
1666
  setAreaContent(e, t) {
1598
1667
  t.type === "text" ? this.contentManager.setTextContent(e, t) : this.contentManager.setImageContent(e, t.dataUrl, t.filename), this.syncEngineContents(), this.saveContentState();
1599
1668
  }
@@ -1603,10 +1672,10 @@ class he extends HTMLElement {
1603
1672
  async finalize() {
1604
1673
  const e = this.engine.checkSafeAreaViolations();
1605
1674
  if (e.length > 0) {
1606
- const r = this.currentTemplate?.areas || [], s = e.map((a) => r.find((o) => o.id === a)?.name || a).join(", ");
1675
+ const a = this.currentTemplate?.areas || [], o = e.map((s) => a.find((r) => r.id === s)?.name || s).join(", ");
1607
1676
  if (!await this.showConfirmation(
1608
1677
  "Content outside safe area",
1609
- `Content in ${s} extends beyond the safe area. It may be trimmed during printing. Continue?`,
1678
+ `Content in ${o} extends beyond the safe area. It may be trimmed during printing. Continue?`,
1610
1679
  "Cancel",
1611
1680
  "Proceed Anyway"
1612
1681
  ))
@@ -1630,21 +1699,21 @@ class he extends HTMLElement {
1630
1699
  // ─── Initialization ───
1631
1700
  renderLoading() {
1632
1701
  const e = document.createElement("style");
1633
- e.textContent = F, this.shadow.appendChild(e);
1634
- const t = c(
1702
+ e.textContent = z, this.shadow.appendChild(e);
1703
+ const t = h(
1635
1704
  "div",
1636
1705
  { class: "loading-container" },
1637
- c("div", { class: "loading-spinner" })
1706
+ h("div", { class: "loading-spinner" })
1638
1707
  );
1639
1708
  this.shadow.appendChild(t);
1640
1709
  }
1641
1710
  async initialize() {
1642
- const e = this.getAttribute("api-url") || "http://localhost:4000/public";
1643
- this.apiClient = new J(e);
1644
- const t = this.getAttribute("template-id");
1645
- if (!t)
1646
- throw new Error("template-id attribute is required");
1647
- await this.loadTemplate(t), this.isReady = !0, this.dispatchEvent(new CustomEvent("ready", { bubbles: !0, composed: !0 }));
1711
+ const e = this.getAttribute("api-url") || "http://localhost:4000";
1712
+ this.apiClient = new $(`${e.replace(/\/+$/, "")}/public`);
1713
+ const t = this.getAttribute("product-id"), i = this.getAttribute("template-id");
1714
+ if (!t && !i)
1715
+ throw new Error("Either template-id or product-id attribute is required");
1716
+ t ? await this.loadProduct(t) : await this.loadTemplate(i), this.isReady = !0, this.dispatchEvent(new CustomEvent("ready", { bubbles: !0, composed: !0 }));
1648
1717
  }
1649
1718
  async loadTemplate(e) {
1650
1719
  if (this.loadingTemplateId !== e) {
@@ -1652,57 +1721,120 @@ class he extends HTMLElement {
1652
1721
  try {
1653
1722
  const t = await this.apiClient.getTemplate(e);
1654
1723
  if (this.loadingTemplateId !== e) return;
1655
- this.currentTemplate = t;
1656
- const i = t.areas || [];
1657
- this.contentManager = new X(i);
1658
- const n = {
1659
- template: t,
1660
- contents: this.contentManager.toJSON(),
1661
- selectedAreaId: null,
1662
- zoom: 1,
1663
- pan: { x: 0, y: 0 },
1664
- isDirty: !1,
1665
- warnings: []
1666
- };
1667
- this.stateManager = new j(n), this.buildUI(t, i), this.engine = new B(this.canvas), this.engine.setArtboard({
1668
- width: t.artboard.width,
1669
- height: t.artboard.height,
1670
- backgroundColor: t.artboard.backgroundColor ?? "#ffffff"
1671
- }), this.engine.setAreas(i), this.engine.setContents(this.contentManager.getContents()), t.backgroundImage && this.engine.setBackgroundImage(t.backgroundImage);
1672
- const r = t.artboard.safeArea ?? t.safeArea;
1673
- r && this.engine.setSafeArea(r), this.engine.start(), this.engine.fitToView(), this.stateManager.update({
1674
- zoom: this.engine.getZoom(),
1675
- pan: this.engine.getPan()
1676
- }), this.zoomToolbar.setZoom(this.engine.getZoom()), this.interaction = new G(
1677
- this.canvas,
1678
- this.engine,
1679
- () => this.contentManager.getContents(),
1680
- () => this.stateManager.getState().selectedAreaId
1681
- ), this.wireInteractionEvents(), this.subscriptions.push(
1682
- this.contentManager.subscribe(() => {
1683
- this.syncEngineContents(), this.inputPanel.setContents(this.contentManager.getContents());
1684
- })
1685
- ), this.keyboard = new ne(this), this.setupKeyboardShortcuts(), this.inputPanel.setContents(this.contentManager.getContents());
1686
- const s = this.getAttribute("store-id");
1687
- s && this.apiClient.getStorageUsage(s).then((a) => {
1688
- this.storageUsage = a, a.usagePercent >= 100 ? this.showToast("Storage quota exceeded. Upgrade your plan for more storage.", "error") : a.usagePercent >= 80 && this.showToast(`Storage ${a.usagePercent}% full. Consider upgrading your plan.`, "warning");
1689
- }).catch(() => {
1690
- }), this.loadingTemplateId = null;
1724
+ await this.loadTemplateData(t), this.loadingTemplateId = null;
1691
1725
  } catch (t) {
1692
1726
  throw this.loadingTemplateId = null, new Error(`Failed to load template: ${t.message}`);
1693
1727
  }
1694
1728
  }
1695
1729
  }
1730
+ async loadTemplateData(e) {
1731
+ this.currentTemplate = e;
1732
+ const t = e.areas || [];
1733
+ this.contentManager = new T(t);
1734
+ const i = {
1735
+ template: e,
1736
+ contents: this.contentManager.toJSON(),
1737
+ selectedAreaId: null,
1738
+ zoom: 1,
1739
+ pan: { x: 0, y: 0 },
1740
+ isDirty: !1,
1741
+ warnings: []
1742
+ };
1743
+ this.stateManager = new O(i), this.buildUI(e, t), this.engine = new N(this.canvas), this.engine.setArtboard({
1744
+ width: e.artboard.width,
1745
+ height: e.artboard.height,
1746
+ backgroundColor: e.artboard.backgroundColor ?? "#ffffff"
1747
+ }), this.engine.setAreas(t), this.engine.setContents(this.contentManager.getContents()), e.backgroundImage && this.engine.setBackgroundImage(e.backgroundImage);
1748
+ const n = e.artboard.safeArea ?? e.safeArea;
1749
+ n && this.engine.setSafeArea(n), this.engine.start(), this.engine.fitToView(), this.stateManager.update({
1750
+ zoom: this.engine.getZoom(),
1751
+ pan: this.engine.getPan()
1752
+ }), this.zoomToolbar.setZoom(this.engine.getZoom()), this.interaction = new F(
1753
+ this.canvas,
1754
+ this.engine,
1755
+ () => this.contentManager.getContents(),
1756
+ () => this.stateManager.getState().selectedAreaId
1757
+ ), this.wireInteractionEvents(), this.subscriptions.push(
1758
+ this.contentManager.subscribe(() => {
1759
+ this.syncEngineContents(), this.inputPanel.setContents(this.contentManager.getContents());
1760
+ })
1761
+ ), this.keyboard = new X(this), this.setupKeyboardShortcuts(), this.inputPanel.setContents(this.contentManager.getContents());
1762
+ const a = this.getAttribute("store-id");
1763
+ a && this.apiClient.getStorageUsage(a).then((o) => {
1764
+ 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");
1765
+ }).catch(() => {
1766
+ });
1767
+ }
1768
+ async loadProduct(e) {
1769
+ try {
1770
+ const t = await this.apiClient.getProduct(e);
1771
+ if (t.views.length === 0)
1772
+ throw new Error("Product has no active templates");
1773
+ this.productViews = t.views, this.perViewContents.clear();
1774
+ const i = t.views.find((n) => n.isDefault) || t.views[0];
1775
+ await this.loadTemplateData(i.template.templateJson), this.activeViewName = i.viewName, this.zoomToolbar && t.views.length > 1 && (this.zoomToolbar.setViews(this.getViews()), this.zoomToolbar.setActiveView(i.viewName));
1776
+ } catch (t) {
1777
+ throw new Error(`Failed to load product: ${t.message}`);
1778
+ }
1779
+ }
1780
+ switchToView(e) {
1781
+ this.activeViewName && this.contentManager && this.perViewContents.set(this.activeViewName, this.contentManager.toJSON()), this.activeViewName = e.viewName, this.zoomToolbar?.setActiveView(e.viewName);
1782
+ const t = e.template.templateJson;
1783
+ this.currentTemplate = t;
1784
+ const i = t.areas || [];
1785
+ this.contentManager = new T(i);
1786
+ const n = this.perViewContents.get(e.viewName);
1787
+ n && this.contentManager.fromJSON(n), this.engine.setArtboard({
1788
+ width: t.artboard.width,
1789
+ height: t.artboard.height,
1790
+ backgroundColor: t.artboard.backgroundColor ?? "#ffffff"
1791
+ }), this.engine.setAreas(i), this.engine.setContents(this.contentManager.getContents()), this.engine.setBackgroundImage(t.backgroundImage ?? void 0);
1792
+ const a = t.artboard.safeArea ?? t.safeArea;
1793
+ this.engine.setSafeArea(a ?? null), this.engine.fitToView(), this.stateManager.setState({
1794
+ template: t,
1795
+ contents: this.contentManager.toJSON(),
1796
+ selectedAreaId: null,
1797
+ isDirty: this.stateManager.getState().isDirty,
1798
+ zoom: this.engine.getZoom(),
1799
+ pan: this.engine.getPan(),
1800
+ warnings: []
1801
+ }), this.zoomToolbar.setZoom(this.engine.getZoom()), this.inputPanel.setAreas(i), this.inputPanel.setContents(this.contentManager.getContents()), this.inputPanel.setSelectedArea(null), this.engine.setSelectedArea(null), this.dispatchEvent(
1802
+ new CustomEvent("view-change", {
1803
+ detail: { viewName: e.viewName },
1804
+ bubbles: !0,
1805
+ composed: !0
1806
+ })
1807
+ );
1808
+ }
1696
1809
  buildUI(e, t) {
1697
1810
  this.shadow.innerHTML = "";
1698
1811
  const i = document.createElement("style");
1699
- i.textContent = F, this.shadow.appendChild(i), this.container = c("div", { class: "editor-container" }), this.zoomToolbar = new K(), 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()));
1700
- const n = c("div", { class: "canvas-area" });
1701
- n.appendChild(this.zoomToolbar.getElement()), this.canvasWrapper = c("div", { class: "canvas-wrapper" }), this.canvas = document.createElement("canvas"), this.canvas.className = "editor-canvas", this.canvasWrapper.appendChild(this.canvas), n.appendChild(this.canvasWrapper), this.inputPanel = new ie(t), this.wireInputPanelEvents();
1702
- const r = c("div", { class: "editor-main" });
1703
- r.appendChild(n), r.appendChild(this.inputPanel.getBackdrop()), r.appendChild(this.inputPanel.getElement()), this.container.appendChild(r), this.shadow.appendChild(this.container), this.resizeObserver = new ResizeObserver((s) => {
1704
- for (const a of s)
1705
- a.target === this.canvasWrapper && this.handleCanvasResize(a.contentRect), a.target === this.container && this.handleLayoutResize(a.contentRect);
1812
+ i.textContent = z, this.shadow.appendChild(i), this.container = h("div", { class: "editor-container" }), this.zoomToolbar = new B(), 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 }) => {
1813
+ const s = this.productViews.find((r) => r.viewName === o);
1814
+ s && this.switchToView(s);
1815
+ })), this.subscriptions.push(this.zoomToolbar.on("close", () => {
1816
+ this.dispatchEvent(new CustomEvent("customizer:close", {
1817
+ bubbles: !0,
1818
+ composed: !0
1819
+ }));
1820
+ })), this.subscriptions.push(this.zoomToolbar.on("save", async () => {
1821
+ this.zoomToolbar.setSaveDisabled(!0, "Saving...");
1822
+ try {
1823
+ await this.finalize(), this.zoomToolbar.setSaveDisabled(!0, "Saved!"), this.showToast("Design saved successfully!", "info"), setTimeout(() => {
1824
+ this.zoomToolbar.setSaveDisabled(!1, "Save Customization"), this.zoomToolbar.showSaveButton(!1), this.stateManager.update({ isDirty: !1 });
1825
+ }, 2e3);
1826
+ } catch (o) {
1827
+ console.error("Customizer: finalize failed", o);
1828
+ const s = o instanceof Error ? o.message : "Save failed. Please try again.";
1829
+ this.showToast(s, "error"), this.zoomToolbar.setSaveDisabled(!1, "Save Customization");
1830
+ }
1831
+ }));
1832
+ const n = h("div", { class: "canvas-area" });
1833
+ n.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), n.appendChild(this.canvasWrapper), this.inputPanel = new j(t), this.wireInputPanelEvents();
1834
+ const a = h("div", { class: "editor-main" });
1835
+ a.appendChild(n), a.appendChild(this.inputPanel.getBackdrop()), a.appendChild(this.inputPanel.getElement()), this.container.appendChild(a), this.shadow.appendChild(this.container), this.resizeObserver = new ResizeObserver((o) => {
1836
+ for (const s of o)
1837
+ s.target === this.canvasWrapper && this.handleCanvasResize(s.contentRect), s.target === this.container && this.handleLayoutResize(s.contentRect);
1706
1838
  }), this.resizeObserver.observe(this.canvasWrapper), this.resizeObserver.observe(this.container);
1707
1839
  }
1708
1840
  // ─── Event Wiring ───
@@ -1751,10 +1883,10 @@ class he extends HTMLElement {
1751
1883
  ), this.subscriptions.push(
1752
1884
  this.inputPanel.on("image:change", async ({ areaId: e, dataUrl: t, filename: i }) => {
1753
1885
  const n = this.contentManager.getContents();
1754
- let r = 0;
1755
- for (const [a, o] of n)
1756
- o.type === "image" && a !== e && r++;
1757
- if (r >= 10) {
1886
+ let a = 0;
1887
+ for (const [s, r] of n)
1888
+ r.type === "image" && s !== e && a++;
1889
+ if (a >= 10) {
1758
1890
  this.showToast("Maximum of 10 images per design reached", "error");
1759
1891
  return;
1760
1892
  }
@@ -1762,28 +1894,28 @@ class he extends HTMLElement {
1762
1894
  this.showToast("Storage quota exceeded. Upgrade your plan for more storage.", "error");
1763
1895
  return;
1764
1896
  }
1765
- const s = this.currentTemplate?.areas.find((a) => a.id === e);
1766
- if (s && this.currentTemplate) {
1767
- const a = this.currentTemplate.print.targetDpi;
1897
+ const o = this.currentTemplate?.areas.find((s) => s.id === e);
1898
+ if (o && this.currentTemplate) {
1899
+ const s = this.currentTemplate.print.targetDpi;
1768
1900
  try {
1769
- const o = new Image(), d = await new Promise((g, m) => {
1770
- o.onload = () => g({ width: o.naturalWidth, height: o.naturalHeight }), o.onerror = () => m(new Error("Failed to load image")), o.src = t;
1771
- }), l = s.location.width / a, h = s.location.height / a, u = oe(
1901
+ const r = new Image(), d = await new Promise((p, g) => {
1902
+ r.onload = () => p({ width: r.naturalWidth, height: r.naturalHeight }), r.onerror = () => g(new Error("Failed to load image")), r.src = t;
1903
+ }), l = o.location.width / s, c = o.location.height / s, u = K(
1772
1904
  d.width,
1773
1905
  d.height,
1774
1906
  l,
1775
- h,
1776
- a
1907
+ c,
1908
+ s
1777
1909
  );
1778
1910
  if (u.actualDPI < 150) {
1779
1911
  if (!await this.showConfirmation(
1780
1912
  "Low resolution detected",
1781
- `This image is ${u.actualDPI} DPI at print size. Print may appear blurry. Recommended: ${a} DPI.`,
1913
+ `This image is ${u.actualDPI} DPI at print size. Print may appear blurry. Recommended: ${s} DPI.`,
1782
1914
  "Cancel",
1783
1915
  "Use Anyway"
1784
1916
  )) return;
1785
- } else u.actualDPI < a && (this.showToast(
1786
- `Image resolution is ${u.actualDPI} DPI (${a} recommended)`,
1917
+ } else u.actualDPI < s && (this.showToast(
1918
+ `Image resolution is ${u.actualDPI} DPI (${s} recommended)`,
1787
1919
  "warning"
1788
1920
  ), this.stateManager.update({
1789
1921
  warnings: [
@@ -1795,8 +1927,8 @@ class he extends HTMLElement {
1795
1927
  }
1796
1928
  }
1797
1929
  if (this.contentManager.setImageContent(e, t, i), this.saveContentState(), this.storageUsage) {
1798
- const a = Math.round(t.length * 0.75);
1799
- this.storageUsage.storageUsed += a, this.storageUsage.usagePercent = this.storageUsage.storageQuota > 0 ? Math.round(this.storageUsage.storageUsed / this.storageUsage.storageQuota * 100) : 0;
1930
+ const s = Math.round(t.length * 0.75);
1931
+ this.storageUsage.storageUsed += s, this.storageUsage.usagePercent = this.storageUsage.storageQuota > 0 ? Math.round(this.storageUsage.storageUsed / this.storageUsage.storageQuota * 100) : 0;
1800
1932
  }
1801
1933
  })
1802
1934
  ), this.subscriptions.push(
@@ -1860,8 +1992,8 @@ class he extends HTMLElement {
1860
1992
  this.canvas.width = t * n, this.canvas.height = i * n, 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()));
1861
1993
  }
1862
1994
  handleLayoutResize(e) {
1863
- const t = e.width < le;
1864
- t !== this.isMobileLayout && (this.isMobileLayout = t, this.container.classList.toggle("layout-desktop", !t), this.container.classList.toggle("layout-mobile", t), this.inputPanel.setMobile(t));
1995
+ const t = e.width < _;
1996
+ this.container.classList.toggle("layout-desktop", !t), this.container.classList.toggle("layout-mobile", t), t !== this.isMobileLayout && (this.isMobileLayout = t, this.inputPanel.setMobile(t));
1865
1997
  }
1866
1998
  // ─── State Management ───
1867
1999
  async refreshStorageUsage() {
@@ -1877,7 +2009,7 @@ class he extends HTMLElement {
1877
2009
  this.stateManager.setState({
1878
2010
  contents: this.contentManager.toJSON(),
1879
2011
  isDirty: !0
1880
- }), this.engine.checkSafeAreaViolations(), this.emitChange();
2012
+ }), this.zoomToolbar?.showSaveButton(!0), this.engine.checkSafeAreaViolations(), this.emitChange();
1881
2013
  }
1882
2014
  syncEngineContents() {
1883
2015
  this.engine?.setContents(this.contentManager.getContents());
@@ -1888,7 +2020,7 @@ class he extends HTMLElement {
1888
2020
  }
1889
2021
  // ─── Keyboard ───
1890
2022
  setupKeyboardShortcuts() {
1891
- const e = ae();
2023
+ const e = J();
1892
2024
  this.keyboard.register({
1893
2025
  key: "z",
1894
2026
  [e]: !0,
@@ -1905,32 +2037,32 @@ class he extends HTMLElement {
1905
2037
  }
1906
2038
  // ─── Toast & Modal ───
1907
2039
  showToast(e, t = "info") {
1908
- const i = c("div", { class: `editor-toast editor-toast-${t}` }, e);
2040
+ const i = h("div", { class: `editor-toast editor-toast-${t}` }, e);
1909
2041
  this.shadow.appendChild(i), setTimeout(() => i.remove(), 4e3);
1910
2042
  }
1911
2043
  showConfirmation(e, t, i = "Cancel", n = "Continue") {
1912
- return new Promise((r) => {
1913
- const s = c("div", { class: "editor-modal-overlay" }), a = c("div", { class: "editor-modal" });
1914
- a.appendChild(c("div", { class: "editor-modal-title" }, e)), a.appendChild(c("div", { class: "editor-modal-body" }, t));
1915
- const o = c("div", { class: "editor-modal-actions" }), d = c("button", { class: "editor-modal-btn" }, i);
2044
+ return new Promise((a) => {
2045
+ const o = h("div", { class: "editor-modal-overlay" }), s = h("div", { class: "editor-modal" });
2046
+ s.appendChild(h("div", { class: "editor-modal-title" }, e)), s.appendChild(h("div", { class: "editor-modal-body" }, t));
2047
+ const r = h("div", { class: "editor-modal-actions" }), d = h("button", { class: "editor-modal-btn" }, i);
1916
2048
  d.addEventListener("click", () => {
1917
- s.remove(), r(!1);
2049
+ o.remove(), a(!1);
1918
2050
  });
1919
- const l = c("button", { class: "editor-modal-btn editor-modal-btn-primary" }, n);
2051
+ const l = h("button", { class: "editor-modal-btn editor-modal-btn-primary" }, n);
1920
2052
  l.addEventListener("click", () => {
1921
- s.remove(), r(!0);
1922
- }), o.appendChild(d), o.appendChild(l), a.appendChild(o), s.appendChild(a), s.addEventListener("click", (h) => {
1923
- h.target === s && (s.remove(), r(!1));
1924
- }), this.shadow.appendChild(s);
2053
+ o.remove(), a(!0);
2054
+ }), r.appendChild(d), r.appendChild(l), s.appendChild(r), o.appendChild(s), o.addEventListener("click", (c) => {
2055
+ c.target === o && (o.remove(), a(!1));
2056
+ }), this.shadow.appendChild(o);
1925
2057
  });
1926
2058
  }
1927
2059
  // ─── Error / Cleanup ───
1928
2060
  showError(e, t) {
1929
2061
  this.shadow.innerHTML = "";
1930
2062
  const i = document.createElement("style");
1931
- i.textContent = F, this.shadow.appendChild(i);
1932
- const n = c("div", { class: "error-container" });
1933
- n.appendChild(c("div", { class: "error-title" }, e)), n.appendChild(c("div", { class: "error-message" }, t?.message ?? "")), this.shadow.appendChild(n), this.dispatchEvent(
2063
+ i.textContent = z, this.shadow.appendChild(i);
2064
+ const n = h("div", { class: "error-container" });
2065
+ n.appendChild(h("div", { class: "error-title" }, e)), n.appendChild(h("div", { class: "error-message" }, t?.message ?? "")), this.shadow.appendChild(n), this.dispatchEvent(
1934
2066
  new CustomEvent("error", {
1935
2067
  detail: { message: e, error: t },
1936
2068
  bubbles: !0,
@@ -1941,32 +2073,36 @@ class he extends HTMLElement {
1941
2073
  cleanup() {
1942
2074
  for (const e of this.subscriptions)
1943
2075
  e();
1944
- 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;
2076
+ 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();
1945
2077
  }
1946
2078
  }
1947
- customElements.get("customizer-editor") || customElements.define("customizer-editor", he);
1948
- function ce(b, e) {
1949
- const t = typeof b == "string" ? document.querySelector(b) : b;
2079
+ customElements.get("customizer-editor") || customElements.define("customizer-editor", ee);
2080
+ function te(f, e) {
2081
+ const t = typeof f == "string" ? document.querySelector(f) : f;
1950
2082
  if (!t)
1951
2083
  throw new Error(
1952
- `Container not found: ${typeof b == "string" ? b : "provided element is null"}`
2084
+ `Container not found: ${typeof f == "string" ? f : "provided element is null"}`
1953
2085
  );
2086
+ if (!e.templateId && !e.productId)
2087
+ throw new Error("Either templateId or productId must be provided");
2088
+ if (e.templateId && e.productId)
2089
+ throw new Error("Only one of templateId or productId should be provided, not both");
1954
2090
  const i = document.createElement("customizer-editor");
1955
- 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), i.style.width = "100%", i.style.height = "100%";
1956
- const n = [], r = (a, o) => {
1957
- i.addEventListener(a, o), n.push({ event: a, handler: o });
2091
+ 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), i.style.width = "100%", i.style.height = "100%";
2092
+ const n = [], a = (s, r) => {
2093
+ i.addEventListener(s, r), n.push({ event: s, handler: r });
1958
2094
  };
1959
2095
  t.innerHTML = "", t.appendChild(i);
1960
- const s = {
2096
+ const o = {
1961
2097
  getDesign() {
1962
2098
  if (typeof i.getDesign != "function")
1963
2099
  throw new Error("Editor not ready: getDesign method not available");
1964
2100
  return i.getDesign();
1965
2101
  },
1966
- setDesign(a) {
2102
+ setDesign(s) {
1967
2103
  if (typeof i.setDesign != "function")
1968
2104
  throw new Error("Editor not ready: setDesign method not available");
1969
- i.setDesign(a);
2105
+ i.setDesign(s);
1970
2106
  },
1971
2107
  undo() {
1972
2108
  if (typeof i.undo != "function")
@@ -1985,49 +2121,49 @@ function ce(b, e) {
1985
2121
  return typeof i.canRedo != "function" ? !1 : i.canRedo();
1986
2122
  },
1987
2123
  async finalize() {
1988
- const a = this.getDesign(), o = e.apiUrl || "https://api.varianta.io";
2124
+ const s = this.getDesign(), r = e.apiUrl || "https://api.varianta.io";
1989
2125
  try {
1990
- const d = await fetch(`${o}/public/finalize`, {
2126
+ const d = await fetch(`${r}/public/finalize`, {
1991
2127
  method: "POST",
1992
2128
  headers: {
1993
2129
  "Content-Type": "application/json"
1994
2130
  },
1995
2131
  body: JSON.stringify({
1996
- templateId: e.templateId,
1997
- designJson: a
2132
+ templateId: s.templateId || e.templateId,
2133
+ designJson: s
1998
2134
  })
1999
2135
  });
2000
2136
  if (!d.ok) {
2001
- const f = await d.json().catch(() => ({
2137
+ const m = await d.json().catch(() => ({
2002
2138
  message: "Finalization failed"
2003
2139
  }));
2004
- throw new Error(f.message || "Finalization failed");
2140
+ throw new Error(m.message || "Finalization failed");
2005
2141
  }
2006
2142
  const l = await d.json();
2007
- let h = {
2143
+ let c = {
2008
2144
  designId: l.designId,
2009
2145
  status: l.status,
2010
2146
  proofUrl: l.proofUrl ?? null,
2011
2147
  errorMessage: l.errorMessage ?? null
2012
2148
  };
2013
- const u = 1500, g = 40;
2014
- let m = 0;
2015
- for (; h.status === "processing" && m < g; ) {
2016
- await new Promise((v) => setTimeout(v, u)), m++;
2017
- const f = await fetch(
2018
- `${o}/public/designs/${h.designId}/status`
2149
+ const u = 1500, p = 40;
2150
+ let g = 0;
2151
+ for (; c.status === "processing" && g < p; ) {
2152
+ await new Promise((b) => setTimeout(b, u)), g++;
2153
+ const m = await fetch(
2154
+ `${r}/public/designs/${c.designId}/status`
2019
2155
  );
2020
- if (!f.ok)
2156
+ if (!m.ok)
2021
2157
  throw new Error("Failed to check design status");
2022
- const y = await f.json();
2023
- h = {
2024
- designId: y.designId,
2025
- status: y.status,
2026
- proofUrl: y.proofUrl ?? null,
2027
- errorMessage: y.errorMessage ?? null
2158
+ const v = await m.json();
2159
+ c = {
2160
+ designId: v.designId,
2161
+ status: v.status,
2162
+ proofUrl: v.proofUrl ?? null,
2163
+ errorMessage: v.errorMessage ?? null
2028
2164
  };
2029
2165
  }
2030
- return h.status === "processing" && (h = { ...h, status: "failed", errorMessage: "Render timed out" }), e.onFinalize && e.onFinalize(h), h;
2166
+ return c.status === "processing" && (c = { ...c, status: "failed", errorMessage: "Render timed out" }), e.onFinalize && e.onFinalize(c), c;
2031
2167
  } catch (d) {
2032
2168
  const l = {
2033
2169
  code: "FINALIZE_ERROR",
@@ -2037,341 +2173,82 @@ function ce(b, e) {
2037
2173
  throw e.onError && e.onError(l), d;
2038
2174
  }
2039
2175
  },
2040
- addTextLayer(a) {
2176
+ addTextLayer(s) {
2041
2177
  if (typeof i.addTextLayer != "function")
2042
2178
  throw new Error("Editor not ready: addTextLayer method not available");
2043
- i.addTextLayer(a);
2179
+ i.addTextLayer(s);
2044
2180
  },
2045
- async addImageLayer(a) {
2181
+ async addImageLayer(s) {
2046
2182
  if (typeof i.addImageLayer != "function")
2047
2183
  throw new Error("Editor not ready: addImageLayer method not available");
2048
- return i.addImageLayer(a);
2184
+ return i.addImageLayer(s);
2049
2185
  },
2050
- removeLayer(a) {
2186
+ removeLayer(s) {
2051
2187
  if (typeof i.removeLayer != "function")
2052
2188
  throw new Error("Editor not ready: removeLayer method not available");
2053
- i.removeLayer(a);
2189
+ i.removeLayer(s);
2054
2190
  },
2055
- selectLayer(a) {
2191
+ selectLayer(s) {
2056
2192
  if (typeof i.selectLayer != "function")
2057
2193
  throw new Error("Editor not ready: selectLayer method not available");
2058
- i.selectLayer(a);
2194
+ i.selectLayer(s);
2059
2195
  },
2060
2196
  getSelectedLayerId() {
2061
2197
  return typeof i.getSelectedLayerId != "function" ? null : i.getSelectedLayerId();
2062
2198
  },
2063
- setTheme(a) {
2064
- i.setAttribute("theme", a);
2199
+ getActiveView() {
2200
+ return typeof i.getActiveView != "function" ? null : i.getActiveView();
2201
+ },
2202
+ getViews() {
2203
+ return typeof i.getViews != "function" ? [] : i.getViews();
2204
+ },
2205
+ setActiveView(s) {
2206
+ if (typeof i.setActiveView != "function")
2207
+ throw new Error("Editor not ready: setActiveView method not available");
2208
+ i.setActiveView(s);
2065
2209
  },
2066
- setMode(a) {
2067
- i.setAttribute("mode", a);
2210
+ setTheme(s) {
2211
+ i.setAttribute("theme", s);
2212
+ },
2213
+ setMode(s) {
2214
+ i.setAttribute("mode", s);
2068
2215
  },
2069
2216
  destroy() {
2070
- n.forEach(({ event: a, handler: o }) => {
2071
- i.removeEventListener(a, o);
2217
+ n.forEach(({ event: s, handler: r }) => {
2218
+ i.removeEventListener(s, r);
2072
2219
  }), i.parentNode && i.parentNode.removeChild(i);
2073
2220
  },
2074
2221
  getElement() {
2075
2222
  return i;
2076
2223
  }
2077
2224
  };
2078
- return e.onReady && r("ready", (() => {
2079
- e.onReady?.(s);
2080
- })), e.onChange && r("change", ((a) => {
2081
- const o = a.detail.design;
2082
- e.onChange?.(o);
2083
- })), e.onLayerSelect && r("layer:select", ((a) => {
2084
- e.onLayerSelect?.(a.detail.layerId);
2085
- })), e.onLayerAdd && r("layer:add", ((a) => {
2086
- e.onLayerAdd?.(a.detail.layerId);
2087
- })), e.onLayerRemove && r("layer:remove", ((a) => {
2088
- e.onLayerRemove?.(a.detail.layerId);
2089
- })), e.onLayerUpdate && r("layer:update", ((a) => {
2090
- e.onLayerUpdate?.(a.detail.layerId);
2091
- })), e.onError && r("error", ((a) => {
2092
- const o = a.detail.error;
2093
- e.onError?.(o);
2225
+ return e.onReady && a("ready", (() => {
2226
+ e.onReady?.(o);
2227
+ })), e.onChange && a("change", ((s) => {
2228
+ const r = s.detail.design;
2229
+ e.onChange?.(r);
2230
+ })), e.onLayerSelect && a("layer:select", ((s) => {
2231
+ e.onLayerSelect?.(s.detail.layerId);
2232
+ })), e.onLayerAdd && a("layer:add", ((s) => {
2233
+ e.onLayerAdd?.(s.detail.layerId);
2234
+ })), e.onLayerRemove && a("layer:remove", ((s) => {
2235
+ e.onLayerRemove?.(s.detail.layerId);
2236
+ })), e.onLayerUpdate && a("layer:update", ((s) => {
2237
+ e.onLayerUpdate?.(s.detail.layerId);
2238
+ })), e.onError && a("error", ((s) => {
2239
+ const r = s.detail.error;
2240
+ e.onError?.(r);
2241
+ })), e.onViewChange && a("view-change", ((s) => {
2242
+ e.onViewChange?.(s.detail.viewName);
2094
2243
  })), e.initialDesign && i.addEventListener(
2095
2244
  "ready",
2096
2245
  () => {
2097
2246
  e.initialDesign && i.setDesign(e.initialDesign);
2098
2247
  },
2099
2248
  { once: !0 }
2100
- ), e.debug && (console.log("[Customizer SDK] Initialized with options:", e), console.log("[Customizer SDK] Instance:", s)), s;
2101
- }
2102
- const ue = Z(
2103
- (b, e) => {
2104
- const {
2105
- templateId: t,
2106
- apiUrl: i,
2107
- theme: n = "light",
2108
- mode: r = "edit",
2109
- debug: s = !1,
2110
- className: a,
2111
- style: o,
2112
- initialDesign: d,
2113
- onReady: l,
2114
- onChange: h,
2115
- onLayerSelect: u,
2116
- onLayerAdd: g,
2117
- onLayerRemove: m,
2118
- onLayerUpdate: f,
2119
- onError: y,
2120
- onFinalize: v
2121
- } = b, w = k(null), p = k(null), C = k({
2122
- onReady: l,
2123
- onChange: h,
2124
- onLayerSelect: u,
2125
- onLayerAdd: g,
2126
- onLayerRemove: m,
2127
- onLayerUpdate: f,
2128
- onError: y,
2129
- onFinalize: v
2130
- });
2131
- return L(() => {
2132
- C.current = {
2133
- onReady: l,
2134
- onChange: h,
2135
- onLayerSelect: u,
2136
- onLayerAdd: g,
2137
- onLayerRemove: m,
2138
- onLayerUpdate: f,
2139
- onError: y,
2140
- onFinalize: v
2141
- };
2142
- }, [l, h, u, g, m, f, y, v]), L(() => {
2143
- if (!w.current) return;
2144
- const I = {
2145
- templateId: t,
2146
- apiUrl: i,
2147
- theme: n,
2148
- mode: r,
2149
- debug: s,
2150
- initialDesign: d,
2151
- // Use callback refs to always get latest callbacks
2152
- onReady: () => C.current.onReady?.(),
2153
- onChange: (x) => C.current.onChange?.(x),
2154
- onLayerSelect: (x) => C.current.onLayerSelect?.(x),
2155
- onLayerAdd: (x) => C.current.onLayerAdd?.(x),
2156
- onLayerRemove: (x) => C.current.onLayerRemove?.(x),
2157
- onLayerUpdate: (x) => C.current.onLayerUpdate?.(x),
2158
- onError: (x) => C.current.onError?.(x),
2159
- onFinalize: (x) => C.current.onFinalize?.(x)
2160
- };
2161
- try {
2162
- const x = ce(w.current, I);
2163
- p.current = x;
2164
- } catch (x) {
2165
- console.error("[Customizer React] Failed to initialize:", x), C.current.onError?.({
2166
- code: "INIT_ERROR",
2167
- message: x instanceof Error ? x.message : "Unknown error",
2168
- details: x
2169
- });
2170
- }
2171
- return () => {
2172
- p.current && (p.current.destroy(), p.current = null);
2173
- };
2174
- }, [t, i, s, d]), L(() => {
2175
- p.current && p.current.setTheme(n);
2176
- }, [n]), L(() => {
2177
- p.current && p.current.setMode(r);
2178
- }, [r]), q(
2179
- e,
2180
- () => ({
2181
- getDesign: () => {
2182
- if (!p.current)
2183
- throw new Error("Editor not initialized");
2184
- return p.current.getDesign();
2185
- },
2186
- setDesign: (I) => {
2187
- if (!p.current)
2188
- throw new Error("Editor not initialized");
2189
- p.current.setDesign(I);
2190
- },
2191
- undo: () => {
2192
- if (!p.current)
2193
- throw new Error("Editor not initialized");
2194
- p.current.undo();
2195
- },
2196
- redo: () => {
2197
- if (!p.current)
2198
- throw new Error("Editor not initialized");
2199
- p.current.redo();
2200
- },
2201
- canUndo: () => p.current ? p.current.canUndo() : !1,
2202
- canRedo: () => p.current ? p.current.canRedo() : !1,
2203
- finalize: async () => {
2204
- if (!p.current)
2205
- throw new Error("Editor not initialized");
2206
- return p.current.finalize();
2207
- },
2208
- addTextLayer: (I) => {
2209
- if (!p.current)
2210
- throw new Error("Editor not initialized");
2211
- p.current.addTextLayer(I);
2212
- },
2213
- addImageLayer: async (I) => {
2214
- if (!p.current)
2215
- throw new Error("Editor not initialized");
2216
- return p.current.addImageLayer(I);
2217
- },
2218
- removeLayer: (I) => {
2219
- if (!p.current)
2220
- throw new Error("Editor not initialized");
2221
- p.current.removeLayer(I);
2222
- },
2223
- selectLayer: (I) => {
2224
- if (!p.current)
2225
- throw new Error("Editor not initialized");
2226
- p.current.selectLayer(I);
2227
- },
2228
- getSelectedLayerId: () => p.current ? p.current.getSelectedLayerId() : null,
2229
- setTheme: (I) => {
2230
- if (!p.current)
2231
- throw new Error("Editor not initialized");
2232
- p.current.setTheme(I);
2233
- },
2234
- setMode: (I) => {
2235
- if (!p.current)
2236
- throw new Error("Editor not initialized");
2237
- p.current.setMode(I);
2238
- },
2239
- destroy: () => {
2240
- p.current && (p.current.destroy(), p.current = null);
2241
- },
2242
- getElement: () => {
2243
- if (!p.current)
2244
- throw new Error("Editor not initialized");
2245
- return p.current.getElement();
2246
- }
2247
- }),
2248
- []
2249
- // Empty deps is OK - we always read from instanceRef.current
2250
- ), /* @__PURE__ */ $(
2251
- "div",
2252
- {
2253
- ref: w,
2254
- className: a,
2255
- style: {
2256
- width: "100%",
2257
- height: "100%",
2258
- ...o
2259
- }
2260
- }
2261
- );
2262
- }
2263
- );
2264
- ue.displayName = "Customizer";
2265
- function me(b = {}) {
2266
- const {
2267
- autoSave: e = !1,
2268
- autoSaveKey: t = "customizer-design",
2269
- autoSaveDebounce: i = 1e3
2270
- } = b, n = k(null), [r, s] = S(null), [a, o] = S(!1), [d, l] = S(!1), [h, u] = S(null), [g, m] = S(!1), [f, y] = S(
2271
- null
2272
- ), v = k(void 0);
2273
- L(() => {
2274
- if (!(!e || !r))
2275
- return v.current && clearTimeout(v.current), v.current = setTimeout(() => {
2276
- try {
2277
- localStorage.setItem(t, JSON.stringify(r));
2278
- } catch (E) {
2279
- console.error("[useCustomizer] Auto-save failed:", E);
2280
- }
2281
- }, i), () => {
2282
- v.current && clearTimeout(v.current);
2283
- };
2284
- }, [r, e, t, i]);
2285
- const w = M(() => {
2286
- if (!n.current) return null;
2287
- try {
2288
- return n.current.getDesign();
2289
- } catch (E) {
2290
- return console.error("[useCustomizer] getDesign failed:", E), null;
2291
- }
2292
- }, []), p = M((E) => {
2293
- if (n.current)
2294
- try {
2295
- n.current.setDesign(E), s(E);
2296
- } catch (z) {
2297
- console.error("[useCustomizer] setDesign failed:", z);
2298
- }
2299
- }, []), C = M(() => {
2300
- if (n.current)
2301
- try {
2302
- n.current.undo(), o(n.current.canUndo()), l(n.current.canRedo());
2303
- } catch (E) {
2304
- console.error("[useCustomizer] undo failed:", E);
2305
- }
2306
- }, []), I = M(() => {
2307
- if (n.current)
2308
- try {
2309
- n.current.redo(), o(n.current.canUndo()), l(n.current.canRedo());
2310
- } catch (E) {
2311
- console.error("[useCustomizer] redo failed:", E);
2312
- }
2313
- }, []), x = M(async () => {
2314
- if (!n.current || g) return null;
2315
- m(!0);
2316
- try {
2317
- const E = await n.current.finalize();
2318
- return y(E), E;
2319
- } catch (E) {
2320
- return console.error("[useCustomizer] finalize failed:", E), null;
2321
- } finally {
2322
- m(!1);
2323
- }
2324
- }, [g]), R = M((E) => {
2325
- if (n.current)
2326
- try {
2327
- n.current.addTextLayer(E);
2328
- } catch (z) {
2329
- console.error("[useCustomizer] addTextLayer failed:", z);
2330
- }
2331
- }, []), D = M(async (E) => {
2332
- if (n.current)
2333
- try {
2334
- await n.current.addImageLayer(E);
2335
- } catch (z) {
2336
- console.error("[useCustomizer] addImageLayer failed:", z);
2337
- }
2338
- }, []), U = M((E) => {
2339
- if (n.current)
2340
- try {
2341
- n.current.removeLayer(E);
2342
- } catch (z) {
2343
- console.error("[useCustomizer] removeLayer failed:", z);
2344
- }
2345
- }, []), N = M((E) => {
2346
- if (n.current)
2347
- try {
2348
- n.current.selectLayer(E), u(E);
2349
- } catch (z) {
2350
- console.error("[useCustomizer] selectLayer failed:", z);
2351
- }
2352
- }, []);
2353
- return {
2354
- customizerRef: n,
2355
- design: r,
2356
- canUndo: a,
2357
- canRedo: d,
2358
- selectedLayerId: h,
2359
- isFinalizing: g,
2360
- finalizeResult: f,
2361
- getDesign: w,
2362
- setDesign: p,
2363
- undo: C,
2364
- redo: I,
2365
- finalize: x,
2366
- addTextLayer: R,
2367
- addImageLayer: D,
2368
- removeLayer: U,
2369
- selectLayer: N
2370
- };
2249
+ ), e.debug && (console.log("[Customizer SDK] Initialized with options:", e), console.log("[Customizer SDK] Instance:", o)), o;
2371
2250
  }
2372
2251
  export {
2373
- ue as Customizer,
2374
- ce as initCustomizer,
2375
- me as useCustomizer
2252
+ te as initCustomizer
2376
2253
  };
2377
2254
  //# sourceMappingURL=index.esm.js.map