vibrant-tool 0.1.9 → 0.1.10

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.
@@ -1123,11 +1123,25 @@ const useEditorStore = create((set, get) => ({
1123
1123
  back: null
1124
1124
  },
1125
1125
  templateData: null,
1126
+ // Grid state
1127
+ showGrid: false,
1128
+ setShowGrid: (showGrid) => set({ showGrid }),
1129
+ toggleGrid: () => set((state) => ({ showGrid: !state.showGrid })),
1126
1130
  history: [],
1127
1131
  historyIndex: -1,
1128
1132
  // Crop state
1129
1133
  isCropping: false,
1130
- startCropping: () => set({ isCropping: true }),
1134
+ startCropping: () => {
1135
+ const canvas2 = get().canvas;
1136
+ if (canvas2 && canvas2.isDrawingMode) {
1137
+ canvas2.isDrawingMode = false;
1138
+ canvas2.selection = true;
1139
+ canvas2.defaultCursor = "default";
1140
+ canvas2._isCurrentlyDrawing = false;
1141
+ canvas2.requestRenderAll();
1142
+ }
1143
+ set({ isCropping: true });
1144
+ },
1131
1145
  stopCropping: () => set({ isCropping: false }),
1132
1146
  // Canvas setter (NO history here)
1133
1147
  setCanvas: (canvas2) => {
@@ -8442,6 +8456,7 @@ function createShapeCornerOverlays(shape, bounds, pageSide = "front", overlayCol
8442
8456
  originX: "left",
8443
8457
  originY: "top",
8444
8458
  fill: overlayColor,
8459
+ opacity: 0.6,
8445
8460
  strokeWidth: 0,
8446
8461
  selectable: false,
8447
8462
  evented: false,
@@ -8493,6 +8508,7 @@ function createShapeCornerOverlays(shape, bounds, pageSide = "front", overlayCol
8493
8508
  originX: "left",
8494
8509
  originY: "top",
8495
8510
  fill: overlayColor,
8511
+ opacity: 0.6,
8496
8512
  strokeWidth: 0,
8497
8513
  selectable: false,
8498
8514
  evented: false,
@@ -8789,6 +8805,7 @@ function createOverlays(canvas2, workarea, pageSide) {
8789
8805
  const overlayColor = canvas2.backgroundColor || "#F3F4F6";
8790
8806
  const makeOverlay = (props) => new jr({
8791
8807
  fill: overlayColor,
8808
+ opacity: 0.6,
8792
8809
  selectable: false,
8793
8810
  evented: false,
8794
8811
  excludeFromExport: true,
@@ -8862,10 +8879,14 @@ function createOverlays(canvas2, workarea, pageSide) {
8862
8879
  canvas2.requestRenderAll();
8863
8880
  }
8864
8881
  function bringOverlaysToFront(canvas2) {
8882
+ if (!canvas2) return;
8883
+ canvas2.preserveObjectStacking = true;
8884
+ canvas2.controlsAboveOverlay = true;
8865
8885
  const overlays = canvas2.getObjects().filter((o2) => o2.name?.startsWith("overlay-"));
8866
8886
  const guidelines = canvas2.getObjects().filter((o2) => o2.name?.startsWith("guideline-"));
8867
8887
  overlays.forEach((overlay) => canvas2.bringObjectToFront(overlay));
8868
8888
  guidelines.forEach((g2) => canvas2.bringObjectToFront(g2));
8889
+ canvas2.requestRenderAll();
8869
8890
  }
8870
8891
  const BASE_URL = "https://www.api.vibrantprintshop.com";
8871
8892
  const IMAGE_UPLOAD_URL = "https://upload.vibrantprint.com";
@@ -9102,12 +9123,16 @@ async function loadPageToCanvas(canvas2, json, pageSide) {
9102
9123
  }
9103
9124
  }
9104
9125
  createOverlays(canvas2, workarea, currentSide);
9126
+ if (bgImg) {
9127
+ canvas2.sendObjectToBack(bgImg);
9128
+ }
9105
9129
  canvas2.sendObjectToBack(workarea);
9106
9130
  }
9107
9131
  canvas2.getObjects().forEach((obj) => {
9108
9132
  const isWorkarea = obj.name === "workarea";
9109
9133
  const isOverlayOrGuide = obj.name?.startsWith("overlay-") || obj.name?.startsWith("guideline-");
9110
- if (!isWorkarea && !isOverlayOrGuide) {
9134
+ const isBgImage = obj.name === "bg-image";
9135
+ if (!isWorkarea && !isOverlayOrGuide && !isBgImage) {
9111
9136
  obj.set({
9112
9137
  selectable: true,
9113
9138
  evented: true,
@@ -9245,6 +9270,77 @@ async function renderPageDataUrl(json, baseCanvas, multiplier = 1, targetSide, i
9245
9270
  return "";
9246
9271
  }
9247
9272
  }
9273
+ const useDrawStore = create((set) => ({
9274
+ drawTool: "select",
9275
+ strokeWidth: 5,
9276
+ opacity: 100,
9277
+ color: "#000000",
9278
+ setDrawTool: (tool) => set(() => {
9279
+ if (tool === "highlighter") {
9280
+ return { drawTool: tool, opacity: 50 };
9281
+ }
9282
+ if (tool === "brush") {
9283
+ return { drawTool: tool, opacity: 100 };
9284
+ }
9285
+ return { drawTool: tool };
9286
+ }),
9287
+ setStrokeWidth: (v2) => set({ strokeWidth: v2 }),
9288
+ setOpacity: (v2) => set({ opacity: v2 }),
9289
+ setColor: (v2) => set({ color: v2 })
9290
+ }));
9291
+ function deriveType(objects) {
9292
+ if (objects.length === 0) return "none";
9293
+ if (objects.length > 1) return "multiple";
9294
+ const obj = objects[0];
9295
+ const t2 = obj.type?.toLowerCase();
9296
+ if (t2 === "activeselection") return "multiple";
9297
+ if (t2 === "group") {
9298
+ if (obj.name === "qr") return "qr";
9299
+ if (obj.name === "barcode") return "barcode";
9300
+ return "group";
9301
+ }
9302
+ if (t2 === "textbox" || t2 === "i-text" || t2 === "text") return "textbox";
9303
+ if (t2 === "image") return "image";
9304
+ return t2 || "none";
9305
+ }
9306
+ const useSelectionStore = create((set) => ({
9307
+ type: "none",
9308
+ objects: [],
9309
+ activeColor: null,
9310
+ activeColorIndex: 0,
9311
+ setSelection: (objects) => {
9312
+ if (objects && objects.length > 0) {
9313
+ const drawState = useDrawStore.getState();
9314
+ if (drawState.drawTool !== "select") {
9315
+ drawState.setDrawTool("select");
9316
+ }
9317
+ const canvas2 = useEditorStore.getState().canvas;
9318
+ if (canvas2 && canvas2.isDrawingMode) {
9319
+ canvas2.isDrawingMode = false;
9320
+ canvas2.selection = true;
9321
+ canvas2.defaultCursor = "default";
9322
+ canvas2._isCurrentlyDrawing = false;
9323
+ canvas2.requestRenderAll();
9324
+ }
9325
+ }
9326
+ set({
9327
+ objects,
9328
+ type: deriveType(objects),
9329
+ activeColor: null,
9330
+ activeColorIndex: 0
9331
+ });
9332
+ },
9333
+ setActiveColor: (color, index = 0) => set({
9334
+ activeColor: color,
9335
+ activeColorIndex: index
9336
+ }),
9337
+ clearSelection: () => set({
9338
+ type: "none",
9339
+ objects: [],
9340
+ activeColor: null,
9341
+ activeColorIndex: 0
9342
+ })
9343
+ }));
9248
9344
  const HISTORY_CUSTOM_PROPERTIES = [
9249
9345
  "name",
9250
9346
  "id",
@@ -9255,6 +9351,9 @@ const HISTORY_CUSTOM_PROPERTIES = [
9255
9351
  "lockMovementY",
9256
9352
  "rx",
9257
9353
  "ry",
9354
+ "src",
9355
+ "crossOrigin",
9356
+ "__maskPath",
9258
9357
  "__radiusOfCanvas",
9259
9358
  "__shapeType",
9260
9359
  "__pageSide",
@@ -9265,7 +9364,8 @@ const HISTORY_CUSTOM_PROPERTIES = [
9265
9364
  "__initialZoom",
9266
9365
  "__borderRect",
9267
9366
  "__rotationControlSize",
9268
- "__showDegree"
9367
+ "__showDegree",
9368
+ "textAlign"
9269
9369
  ];
9270
9370
  const useHistoryStore = create((set, get) => ({
9271
9371
  canvas: null,
@@ -9288,11 +9388,12 @@ const useHistoryStore = create((set, get) => ({
9288
9388
  setPage: (page) => set({ currentPage: page }),
9289
9389
  saveState: () => {
9290
9390
  const { canvas: canvas2, currentPage, pages, isRestoring } = get();
9291
- if (!canvas2 || isRestoring) return;
9292
- const rawJson = canvas2.toObject(HISTORY_CUSTOM_PROPERTIES);
9391
+ const targetCanvas = canvas2 || useEditorStore.getState().canvas;
9392
+ if (!targetCanvas || isRestoring) return;
9393
+ const rawJson = targetCanvas.toObject(HISTORY_CUSTOM_PROPERTIES);
9293
9394
  const cleanObjects = (rawJson.objects || []).filter((obj) => {
9294
9395
  const name = obj.name || "";
9295
- return name !== "watermark" && !name.startsWith("overlay-") && !name.startsWith("guideline-");
9396
+ return name !== "watermark" && !name.startsWith("overlay-") && !name.startsWith("guideline-") && name !== "canvas-grid";
9296
9397
  });
9297
9398
  const json = { ...rawJson, objects: cleanObjects };
9298
9399
  const history = pages[currentPage]?.history ?? [];
@@ -9312,10 +9413,12 @@ const useHistoryStore = create((set, get) => ({
9312
9413
  },
9313
9414
  undo: async () => {
9314
9415
  const { canvas: canvas2, currentPage, pages, isRestoring } = get();
9315
- if (!canvas2 || isRestoring) return;
9416
+ const targetCanvas = canvas2 || useEditorStore.getState().canvas;
9417
+ if (!targetCanvas || isRestoring) return;
9316
9418
  const page = pages[currentPage];
9317
9419
  if (!page || page.history.length <= 1) return;
9318
- canvas2.discardActiveObject();
9420
+ targetCanvas.discardActiveObject();
9421
+ useSelectionStore.getState().clearSelection();
9319
9422
  const current = page.history.at(-1);
9320
9423
  const previous = page.history.at(-2);
9321
9424
  set({
@@ -9329,8 +9432,8 @@ const useHistoryStore = create((set, get) => ({
9329
9432
  }
9330
9433
  });
9331
9434
  try {
9332
- await loadPageToCanvas(canvas2, previous, currentPage);
9333
- canvas2.requestRenderAll();
9435
+ await loadPageToCanvas(targetCanvas, previous, currentPage);
9436
+ targetCanvas.requestRenderAll();
9334
9437
  const editorStore = useEditorStore.getState();
9335
9438
  if (editorStore.pages) {
9336
9439
  editorStore.setPages({
@@ -9346,10 +9449,12 @@ const useHistoryStore = create((set, get) => ({
9346
9449
  },
9347
9450
  redoAction: async () => {
9348
9451
  const { canvas: canvas2, currentPage, pages, isRestoring } = get();
9349
- if (!canvas2 || isRestoring) return;
9452
+ const targetCanvas = canvas2 || useEditorStore.getState().canvas;
9453
+ if (!targetCanvas || isRestoring) return;
9350
9454
  const page = pages[currentPage];
9351
9455
  if (!page || page.redo.length === 0) return;
9352
- canvas2.discardActiveObject();
9456
+ targetCanvas.discardActiveObject();
9457
+ useSelectionStore.getState().clearSelection();
9353
9458
  const next = page.redo[0];
9354
9459
  set({
9355
9460
  isRestoring: true,
@@ -9362,8 +9467,8 @@ const useHistoryStore = create((set, get) => ({
9362
9467
  }
9363
9468
  });
9364
9469
  try {
9365
- await loadPageToCanvas(canvas2, next, currentPage);
9366
- canvas2.requestRenderAll();
9470
+ await loadPageToCanvas(targetCanvas, next, currentPage);
9471
+ targetCanvas.requestRenderAll();
9367
9472
  const editorStore = useEditorStore.getState();
9368
9473
  if (editorStore.pages) {
9369
9474
  editorStore.setPages({
@@ -9663,7 +9768,7 @@ function triggerDownload(url, filename) {
9663
9768
  }
9664
9769
  function exportDesignJSON(canvas2, workarea) {
9665
9770
  const objects = canvas2.getObjects();
9666
- const children = objects.filter((obj) => obj.name !== "workarea" && !obj.name?.startsWith("overlay-")).map((obj) => {
9771
+ const children = objects.filter((obj) => obj.name !== "workarea" && !obj.name?.startsWith("overlay-") && !obj.name?.startsWith("guideline-") && obj.name !== "canvas-grid").map((obj) => {
9667
9772
  const base = obj.toObject();
9668
9773
  return {
9669
9774
  id: obj.id || crypto.randomUUID(),
@@ -9888,6 +9993,7 @@ async function addImageToCanvas(url) {
9888
9993
  });
9889
9994
  canvas2.add(img);
9890
9995
  canvas2.setActiveObject(img);
9996
+ bringOverlaysToFront(canvas2);
9891
9997
  canvas2.requestRenderAll();
9892
9998
  }).catch((err) => {
9893
9999
  console.warn("Fabric failed to load image from URL:", url, err);
@@ -9910,7 +10016,7 @@ function captureWorkareaSVG(canvas2, workarea, pageSide) {
9910
10016
  canvas2.renderAll();
9911
10017
  const originalProps = /* @__PURE__ */ new Map();
9912
10018
  objects.forEach((obj) => {
9913
- const isExcluded = obj.excludeFromExport || obj.name?.startsWith("overlay-") || obj.name?.startsWith("guide-") || obj.name?.startsWith("guideline-") || obj.name === "crop-grid";
10019
+ const isExcluded = obj.excludeFromExport || obj.name?.startsWith("overlay-") || obj.name?.startsWith("guide-") || obj.name?.startsWith("guideline-") || obj.name === "canvas-grid" || obj.name === "crop-grid";
9914
10020
  if (isExcluded && obj !== targetWorkarea) {
9915
10021
  originalProps.set(obj, {
9916
10022
  visible: obj.visible,
@@ -102390,7 +102496,23 @@ const UploadIcon = () => /* @__PURE__ */ jsx(
102390
102496
  const useSidePanelStore = create(
102391
102497
  (set) => ({
102392
102498
  activePanel: null,
102393
- setActivePanel: (val) => set({ activePanel: val })
102499
+ setActivePanel: (val) => {
102500
+ if (val !== "draw") {
102501
+ const drawState = useDrawStore.getState();
102502
+ if (drawState.drawTool !== "select") {
102503
+ drawState.setDrawTool("select");
102504
+ }
102505
+ const canvas2 = useEditorStore.getState().canvas;
102506
+ if (canvas2 && canvas2.isDrawingMode) {
102507
+ canvas2.isDrawingMode = false;
102508
+ canvas2.selection = true;
102509
+ canvas2.defaultCursor = "default";
102510
+ canvas2._isCurrentlyDrawing = false;
102511
+ canvas2.requestRenderAll();
102512
+ }
102513
+ }
102514
+ set({ activePanel: val });
102515
+ }
102394
102516
  })
102395
102517
  );
102396
102518
  function Panel({ panelName }) {
@@ -102456,43 +102578,6 @@ function Panel({ panelName }) {
102456
102578
  ] })
102457
102579
  ] });
102458
102580
  }
102459
- function deriveType(objects) {
102460
- if (objects.length === 0) return "none";
102461
- if (objects.length > 1) return "multiple";
102462
- const obj = objects[0];
102463
- const t2 = obj.type?.toLowerCase();
102464
- if (t2 === "activeselection") return "multiple";
102465
- if (t2 === "group") {
102466
- if (obj.name === "qr") return "qr";
102467
- if (obj.name === "barcode") return "barcode";
102468
- return "group";
102469
- }
102470
- if (t2 === "textbox" || t2 === "i-text" || t2 === "text") return "textbox";
102471
- if (t2 === "image") return "image";
102472
- return t2 || "none";
102473
- }
102474
- const useSelectionStore = create((set) => ({
102475
- type: "none",
102476
- objects: [],
102477
- activeColor: null,
102478
- activeColorIndex: 0,
102479
- setSelection: (objects) => set({
102480
- objects,
102481
- type: deriveType(objects),
102482
- activeColor: null,
102483
- activeColorIndex: 0
102484
- }),
102485
- setActiveColor: (color, index = 0) => set({
102486
- activeColor: color,
102487
- activeColorIndex: index
102488
- }),
102489
- clearSelection: () => set({
102490
- type: "none",
102491
- objects: [],
102492
- activeColor: null,
102493
- activeColorIndex: 0
102494
- })
102495
- }));
102496
102581
  function TemplatesPanel() {
102497
102582
  const { productData } = useProductStore();
102498
102583
  const { startLoading, stopLoading, isLoading } = useLoaderStore();
@@ -102933,6 +103018,7 @@ async function addSVGToCanvas(type = "svg", url) {
102933
103018
  svg.setCoords();
102934
103019
  canvas2.add(svg);
102935
103020
  canvas2.setActiveObject(svg);
103021
+ bringOverlaysToFront(canvas2);
102936
103022
  useSelectionStore.getState().setSelection([svg]);
102937
103023
  canvas2.requestRenderAll();
102938
103024
  }
@@ -102983,6 +103069,7 @@ async function renderSVGStringToCanvas(type = "svg", svg, options = {}) {
102983
103069
  group.setCoords();
102984
103070
  canvas2.add(group);
102985
103071
  canvas2.setActiveObject(group);
103072
+ bringOverlaysToFront(canvas2);
102986
103073
  useSelectionStore.getState().setSelection([group]);
102987
103074
  canvas2.requestRenderAll();
102988
103075
  return group;
@@ -103387,7 +103474,7 @@ async function addTextToCanvas(fontSize, width, textName) {
103387
103474
  if (!workarea) return;
103388
103475
  const workareaCenter = workarea.getCenterPoint();
103389
103476
  const workareaBounds = workarea.getBoundingRect();
103390
- const maxTextWidth = Math.max(40, workareaBounds.width * 0.8);
103477
+ const maxTextWidth = Math.max(40, workareaBounds.width * 0.85);
103391
103478
  const textWidth = Math.min(width, maxTextWidth);
103392
103479
  const text = new Uo(textName, {
103393
103480
  fontSize,
@@ -103400,19 +103487,26 @@ async function addTextToCanvas(fontSize, width, textName) {
103400
103487
  strokeWidth: 0,
103401
103488
  hasRotatingPoint: true,
103402
103489
  centerTransform: true,
103403
- padding: 5,
103490
+ padding: 0,
103404
103491
  strokeUniform: true,
103405
103492
  paintFirst: "stroke",
103406
103493
  objectCaching: false
103407
103494
  // Ensure maximum sharpness
103408
103495
  });
103496
+ const measuredWidth = Math.ceil(text.calcTextWidth());
103497
+ if (measuredWidth > 0 && !textName.includes("\n")) {
103498
+ text.set({ width: Math.min(Math.max(measuredWidth + 4, 20), maxTextWidth) });
103499
+ text.initDimensions();
103500
+ }
103409
103501
  canvas2.add(text);
103410
103502
  text.set({
103411
103503
  left: workareaCenter.x - text.getScaledWidth() / 2,
103412
103504
  top: workareaCenter.y - text.getScaledHeight() / 2
103413
103505
  });
103414
103506
  canvas2.setActiveObject(text);
103507
+ bringOverlaysToFront(canvas2);
103415
103508
  canvas2.requestRenderAll();
103509
+ useHistoryStore.getState().saveState();
103416
103510
  }
103417
103511
  async function addTextTemplate(id) {
103418
103512
  try {
@@ -103444,6 +103538,7 @@ async function addTextTemplate(id) {
103444
103538
  canvas2.add(...fabricObjects);
103445
103539
  canvas2.renderOnAddRemove = true;
103446
103540
  canvas2.requestRenderAll();
103541
+ useHistoryStore.getState().saveState();
103447
103542
  } catch (err) {
103448
103543
  console.error("Failed to add text template:", err);
103449
103544
  }
@@ -104211,11 +104306,20 @@ function PhotosPanel() {
104211
104306
  }) : !isLoading ? /* @__PURE__ */ jsx("div", { className: "col-span-full py-8 text-center text-xs text-gray-500", children: "No images found" }) : null }) })
104212
104307
  ] });
104213
104308
  }
104309
+ let bgSaveTimer = null;
104310
+ function flushBackgroundSave() {
104311
+ if (bgSaveTimer) {
104312
+ clearTimeout(bgSaveTimer);
104313
+ bgSaveTimer = null;
104314
+ useHistoryStore.getState().saveState();
104315
+ }
104316
+ }
104214
104317
  async function addBackgroundImage(url) {
104215
104318
  const canvas2 = useEditorStore.getState().canvas;
104216
104319
  if (!canvas2) return;
104217
104320
  const workarea = canvas2.getObjects().find((o2) => o2.name === "workarea");
104218
104321
  if (!workarea) return;
104322
+ flushBackgroundSave();
104219
104323
  workarea.set("fill", "#ffffff");
104220
104324
  canvas2.getObjects("image").forEach((o2) => {
104221
104325
  if (o2.name === "bg-image") canvas2.remove(o2);
@@ -104227,7 +104331,16 @@ async function addBackgroundImage(url) {
104227
104331
  const activeSide = workarea.__pageSide || useHistoryStore.getState().currentPage || "front";
104228
104332
  const baseRx = (workarea.rx || workarea.__radiusOfCanvas || 0) * (workarea.scaleX || 1);
104229
104333
  const baseRy = (workarea.ry || workarea.__radiusOfCanvas || 0) * (workarea.scaleY || 1);
104230
- oa.fromURL(url + "?t=vb", { crossOrigin: "anonymous" }).then((img) => {
104334
+ let finalUrl = url;
104335
+ try {
104336
+ const b64 = await getBase64ImageFromURL(url);
104337
+ if (b64 && (b64.startsWith("data:image/png") || b64.startsWith("data:image/jpeg"))) {
104338
+ finalUrl = b64;
104339
+ }
104340
+ } catch {
104341
+ }
104342
+ try {
104343
+ const img = await oa.fromURL(finalUrl, { crossOrigin: "anonymous" });
104231
104344
  const imgW = img.width || 1;
104232
104345
  const imgH = img.height || 1;
104233
104346
  const scale2 = Math.max(waW / imgW, waH / imgH);
@@ -104263,12 +104376,14 @@ async function addBackgroundImage(url) {
104263
104376
  canvas2.add(img);
104264
104377
  canvas2.sendObjectToBack(img);
104265
104378
  canvas2.sendObjectToBack(workarea);
104379
+ bringOverlaysToFront(canvas2);
104266
104380
  canvas2.requestRenderAll();
104267
- }).catch((err) => {
104381
+ useHistoryStore.getState().saveState();
104382
+ } catch (err) {
104268
104383
  console.warn("Fabric failed to load background image:", url, err);
104269
- });
104384
+ }
104270
104385
  }
104271
- async function addBackgroundColor(color) {
104386
+ async function addBackgroundColor(color, immediate = false) {
104272
104387
  const canvas2 = useEditorStore.getState().canvas;
104273
104388
  if (!canvas2) return;
104274
104389
  const workarea = canvas2.getObjects().find((o2) => o2.name === "workarea");
@@ -104277,7 +104392,20 @@ async function addBackgroundColor(color) {
104277
104392
  if (o2.name === "bg-image") canvas2.remove(o2);
104278
104393
  });
104279
104394
  workarea.set("fill", color);
104395
+ bringOverlaysToFront(canvas2);
104280
104396
  canvas2.requestRenderAll();
104397
+ if (bgSaveTimer) {
104398
+ clearTimeout(bgSaveTimer);
104399
+ bgSaveTimer = null;
104400
+ }
104401
+ if (immediate) {
104402
+ useHistoryStore.getState().saveState();
104403
+ } else {
104404
+ bgSaveTimer = setTimeout(() => {
104405
+ useHistoryStore.getState().saveState();
104406
+ bgSaveTimer = null;
104407
+ }, 120);
104408
+ }
104281
104409
  }
104282
104410
  function normalizeHexColor(c2) {
104283
104411
  if (!c2 || typeof c2 !== "string" || c2 === "none" || c2 === "transparent" || c2.trim() === "") return null;
@@ -106037,11 +106165,11 @@ function BackgroundPanel() {
106037
106165
  const [hasMore, setHasMore] = useState(true);
106038
106166
  const [page, setPage] = useState(1);
106039
106167
  const scrollRef = useRef(null);
106040
- const handleApplyBackground = (type, value) => {
106168
+ const handleApplyBackground = async (type, value, immediate = false) => {
106041
106169
  if (type === "color") {
106042
- addBackgroundColor(value);
106170
+ await addBackgroundColor(value, immediate);
106043
106171
  } else {
106044
- addBackgroundImage(value);
106172
+ await addBackgroundImage(value);
106045
106173
  }
106046
106174
  if (window.innerWidth < 768) {
106047
106175
  setActivePanel(null);
@@ -106094,7 +106222,7 @@ function BackgroundPanel() {
106094
106222
  CMYKColorPicker,
106095
106223
  {
106096
106224
  value: "#ffffff",
106097
- onChange: (hex) => handleApplyBackground("color", hex)
106225
+ onChange: (hex) => handleApplyBackground("color", hex, false)
106098
106226
  }
106099
106227
  ),
106100
106228
  CMYK_SAFE_COLORS.map(({ hex, label }) => /* @__PURE__ */ jsx(
@@ -106102,7 +106230,7 @@ function BackgroundPanel() {
106102
106230
  {
106103
106231
  className: "w-8 h-8 rounded border border-neutral-600 cursor-pointer transition-transform hover:scale-110",
106104
106232
  style: { backgroundColor: hex },
106105
- onClick: () => handleApplyBackground("color", hex),
106233
+ onClick: () => handleApplyBackground("color", hex, true),
106106
106234
  title: label
106107
106235
  },
106108
106236
  hex
@@ -106118,7 +106246,7 @@ function BackgroundPanel() {
106118
106246
  const image = await ShapeService.convertImages(item.largeImage);
106119
106247
  const imageUrl = image?.success ? image?.url : null;
106120
106248
  if (!imageUrl) return;
106121
- handleApplyBackground("image", imageUrl);
106249
+ await handleApplyBackground("image", imageUrl);
106122
106250
  if (window.innerWidth < 768) {
106123
106251
  useSidePanelStore.getState().setActivePanel(null);
106124
106252
  }
@@ -106214,24 +106342,6 @@ function UploadPanel() {
106214
106342
  )) })
106215
106343
  ] });
106216
106344
  }
106217
- const useDrawStore = create((set) => ({
106218
- drawTool: "select",
106219
- strokeWidth: 5,
106220
- opacity: 100,
106221
- color: "#000000",
106222
- setDrawTool: (tool) => set(() => {
106223
- if (tool === "highlighter") {
106224
- return { drawTool: tool, opacity: 50 };
106225
- }
106226
- if (tool === "brush") {
106227
- return { drawTool: tool, opacity: 100 };
106228
- }
106229
- return { drawTool: tool };
106230
- }),
106231
- setStrokeWidth: (v2) => set({ strokeWidth: v2 }),
106232
- setOpacity: (v2) => set({ opacity: v2 }),
106233
- setColor: (v2) => set({ color: v2 })
106234
- }));
106235
106345
  function DrawPanel() {
106236
106346
  const {
106237
106347
  drawTool,
@@ -106246,7 +106356,7 @@ function DrawPanel() {
106246
106356
  const isDisabled = drawTool === "select";
106247
106357
  const TOOLS = [
106248
106358
  { key: "select", label: "✦ Selection" },
106249
- { key: "brush", label: "🖊 Brush" },
106359
+ { key: "brush", label: "✏️ Brush" },
106250
106360
  { key: "highlighter", label: "✎ Highlighter" }
106251
106361
  ];
106252
106362
  return /* @__PURE__ */ jsxs("div", { className: "mx-4 select-none", children: [
@@ -111037,8 +111147,8 @@ function MaskPanel() {
111037
111147
  const objWidth = obj.width ?? 1;
111038
111148
  const objHeight = obj.height ?? 1;
111039
111149
  const pathDims = clipPath.getBoundingRect();
111040
- const scaleX = objWidth / pathDims.width;
111041
- const scaleY = objHeight / pathDims.height;
111150
+ const scaleX = objWidth / (clipPath.width || pathDims.width);
111151
+ const scaleY = objHeight / (clipPath.height || pathDims.height);
111042
111152
  clipPath.set({
111043
111153
  scaleX,
111044
111154
  scaleY,
@@ -111047,6 +111157,8 @@ function MaskPanel() {
111047
111157
  angle: 0
111048
111158
  // Should not inherit angle since the parent handles rotation
111049
111159
  });
111160
+ clipPath.__maskPath = svgPath;
111161
+ obj.__maskPath = svgPath;
111050
111162
  obj.set({ clipPath });
111051
111163
  obj.setCoords();
111052
111164
  canvas2.requestRenderAll();
@@ -156997,7 +157109,7 @@ function SidePanel() {
156997
157109
  id: item.id,
156998
157110
  item,
156999
157111
  isActive: activePanel === item.id,
157000
- onClick: () => setActivePanel(item.id)
157112
+ onClick: () => setActivePanel(activePanel === item.id ? null : item.id)
157001
157113
  },
157002
157114
  item.id
157003
157115
  )
@@ -157012,7 +157124,7 @@ function SidePanel() {
157012
157124
  item,
157013
157125
  variant: "mobile",
157014
157126
  isActive: activePanel === item.id,
157015
- onClick: () => setActivePanel(item.id)
157127
+ onClick: () => setActivePanel(activePanel === item.id ? null : item.id)
157016
157128
  },
157017
157129
  item.id
157018
157130
  )
@@ -157138,20 +157250,48 @@ function TextFontSize({
157138
157250
  }
157139
157251
  );
157140
157252
  }
157141
- function TextAlignButton({
157253
+ function TextAlignGroup({
157142
157254
  value,
157143
157255
  onChange
157144
157256
  }) {
157145
- const next = value === "left" ? "center" : value === "center" ? "right" : "left";
157257
+ const current = value === "center" || value === "right" ? value : "left";
157146
157258
  return /* @__PURE__ */ jsxs(
157147
- "button",
157259
+ "div",
157148
157260
  {
157149
- onClick: () => onChange(next),
157150
- className: "p-1 bg-neutral-800 rounded hover:bg-neutral-700",
157261
+ className: "flex items-center bg-neutral-800 rounded p-0.5 gap-0.5 border border-neutral-700",
157262
+ role: "group",
157263
+ "aria-label": "Text alignment",
157151
157264
  children: [
157152
- value === "left" && /* @__PURE__ */ jsx(AlignLeftIcon$1, {}),
157153
- value === "center" && /* @__PURE__ */ jsx(AlignCenterIcon$1, {}),
157154
- value === "right" && /* @__PURE__ */ jsx(AlignRightIcon$1, {})
157265
+ /* @__PURE__ */ jsx(
157266
+ "button",
157267
+ {
157268
+ type: "button",
157269
+ title: "Align Left",
157270
+ onClick: () => onChange("left"),
157271
+ className: `p-1 rounded transition-colors ${current === "left" ? "bg-neutral-600 text-white shadow-sm" : "text-neutral-400 hover:text-white hover:bg-neutral-700"}`,
157272
+ children: /* @__PURE__ */ jsx(AlignLeftIcon$1, {})
157273
+ }
157274
+ ),
157275
+ /* @__PURE__ */ jsx(
157276
+ "button",
157277
+ {
157278
+ type: "button",
157279
+ title: "Align Center",
157280
+ onClick: () => onChange("center"),
157281
+ className: `p-1 rounded transition-colors ${current === "center" ? "bg-neutral-600 text-white shadow-sm" : "text-neutral-400 hover:text-white hover:bg-neutral-700"}`,
157282
+ children: /* @__PURE__ */ jsx(AlignCenterIcon$1, {})
157283
+ }
157284
+ ),
157285
+ /* @__PURE__ */ jsx(
157286
+ "button",
157287
+ {
157288
+ type: "button",
157289
+ title: "Align Right",
157290
+ onClick: () => onChange("right"),
157291
+ className: `p-1 rounded transition-colors ${current === "right" ? "bg-neutral-600 text-white shadow-sm" : "text-neutral-400 hover:text-white hover:bg-neutral-700"}`,
157292
+ children: /* @__PURE__ */ jsx(AlignRightIcon$1, {})
157293
+ }
157294
+ )
157155
157295
  ]
157156
157296
  }
157157
157297
  );
@@ -157204,15 +157344,29 @@ function TextToolbar() {
157204
157344
  }, [type, objects]);
157205
157345
  useEffect(() => {
157206
157346
  if (!text) return;
157207
- setFontSize(text.fontSize ?? 18);
157208
- setLineHeight2((text.lineHeight ?? 1) * 100);
157209
- setCharSpacing(text.charSpacing ?? 0);
157210
- setAlign(text.textAlign ?? "left");
157211
- const dashArray = text.strokeDashArray;
157212
- const currentStrokeStyle = Array.isArray(dashArray) && dashArray.length === 2 && dashArray[0] === 2 && dashArray[1] === 4 ? "Dotted" : Array.isArray(dashArray) && dashArray.length === 2 && dashArray[0] === 10 && dashArray[1] === 5 ? "Dashed" : "Line";
157213
- setStrokeColor(typeof text.stroke === "string" && text.stroke ? text.stroke : "#000000");
157214
- setStrokeSize(Math.max(0, Number(text.strokeWidth ?? 0)));
157215
- setStrokeStyle(currentStrokeStyle);
157347
+ const syncProps = () => {
157348
+ setFontSize(text.fontSize ?? 18);
157349
+ setLineHeight2((text.lineHeight ?? 1) * 100);
157350
+ setCharSpacing(text.charSpacing ?? 0);
157351
+ setAlign(text.textAlign ?? "left");
157352
+ const dashArray = text.strokeDashArray;
157353
+ const currentStrokeStyle = Array.isArray(dashArray) && dashArray.length === 2 && dashArray[0] === 2 && dashArray[1] === 4 ? "Dotted" : Array.isArray(dashArray) && dashArray.length === 2 && dashArray[0] === 10 && dashArray[1] === 5 ? "Dashed" : "Line";
157354
+ setStrokeColor(typeof text.stroke === "string" && text.stroke ? text.stroke : "#000000");
157355
+ setStrokeSize(Math.max(0, Number(text.strokeWidth ?? 0)));
157356
+ setStrokeStyle(currentStrokeStyle);
157357
+ };
157358
+ syncProps();
157359
+ const canvas2 = text.canvas;
157360
+ if (canvas2) {
157361
+ canvas2.on("object:modified", syncProps);
157362
+ canvas2.on("selection:updated", syncProps);
157363
+ }
157364
+ return () => {
157365
+ if (canvas2) {
157366
+ canvas2.off("object:modified", syncProps);
157367
+ canvas2.off("selection:updated", syncProps);
157368
+ }
157369
+ };
157216
157370
  }, [text]);
157217
157371
  const updateText = useCallback(
157218
157372
  (props) => {
@@ -157220,6 +157374,11 @@ function TextToolbar() {
157220
157374
  const canvas2 = text.canvas;
157221
157375
  if (!canvas2) return;
157222
157376
  text.set(props);
157377
+ if (props.textAlign) {
157378
+ text.dirty = true;
157379
+ text.initDimensions?.();
157380
+ text.setCoords();
157381
+ }
157223
157382
  canvas2.requestRenderAll();
157224
157383
  useHistoryStore.getState().saveState();
157225
157384
  },
@@ -157276,7 +157435,7 @@ function TextToolbar() {
157276
157435
  }
157277
157436
  ),
157278
157437
  /* @__PURE__ */ jsx(
157279
- TextAlignButton,
157438
+ TextAlignGroup,
157280
157439
  {
157281
157440
  value: align,
157282
157441
  onChange: (v2) => {
@@ -157532,6 +157691,7 @@ class CropService {
157532
157691
  darkOverlay = null;
157533
157692
  focusedImage = null;
157534
157693
  cropZone = null;
157694
+ maskPath = null;
157535
157695
  onEnd = null;
157536
157696
  init(canvas2) {
157537
157697
  this.canvas = canvas2;
@@ -157543,6 +157703,7 @@ class CropService {
157543
157703
  this.cleanup();
157544
157704
  this.targetImage = image;
157545
157705
  this.onEnd = onEnd || null;
157706
+ this.maskPath = image.__maskPath || image.clipPath?.__maskPath || null;
157546
157707
  const canvas2 = this.canvas;
157547
157708
  canvas2.renderOnAddRemove = false;
157548
157709
  canvas2.discardActiveObject();
@@ -157559,7 +157720,8 @@ class CropService {
157559
157720
  width: naturalWidth,
157560
157721
  height: naturalHeight,
157561
157722
  selectable: false,
157562
- evented: false
157723
+ evented: false,
157724
+ clipPath: void 0
157563
157725
  });
157564
157726
  const cropX = image.cropX || 0;
157565
157727
  const cropY = image.cropY || 0;
@@ -157690,14 +157852,31 @@ class CropService {
157690
157852
  const local = Mn.transformPoint(center, inv);
157691
157853
  const localW = zone.getScaledWidth() / Math.abs(img.scaleX);
157692
157854
  const localH = zone.getScaledHeight() / Math.abs(img.scaleY);
157693
- img.clipPath = new jr({
157694
- left: local.x,
157695
- top: local.y,
157696
- width: localW,
157697
- height: localH,
157698
- originX: "center",
157699
- originY: "center"
157700
- });
157855
+ if (this.maskPath) {
157856
+ const maskClip = new no(this.maskPath, {
157857
+ originX: "center",
157858
+ originY: "center"
157859
+ });
157860
+ const pathW = maskClip.width || 1;
157861
+ const pathH = maskClip.height || 1;
157862
+ maskClip.set({
157863
+ left: local.x,
157864
+ top: local.y,
157865
+ scaleX: localW / pathW,
157866
+ scaleY: localH / pathH,
157867
+ angle: 0
157868
+ });
157869
+ img.clipPath = maskClip;
157870
+ } else {
157871
+ img.clipPath = new jr({
157872
+ left: local.x,
157873
+ top: local.y,
157874
+ width: localW,
157875
+ height: localH,
157876
+ originX: "center",
157877
+ originY: "center"
157878
+ });
157879
+ }
157701
157880
  img.dirty = true;
157702
157881
  }
157703
157882
  /* ======================= APPLY CROP ======================= */
@@ -157722,11 +157901,13 @@ class CropService {
157722
157901
  const minY = Math.min(...ys2);
157723
157902
  const maxX = Math.max(...xs2);
157724
157903
  const maxY = Math.max(...ys2);
157904
+ const newW = Math.min(full.width, maxX - minX);
157905
+ const newH = Math.min(full.height, maxY - minY);
157725
157906
  img.set({
157726
157907
  cropX: Math.max(0, minX),
157727
157908
  cropY: Math.max(0, minY),
157728
- width: Math.min(full.width, maxX - minX),
157729
- height: Math.min(full.height, maxY - minY),
157909
+ width: newW,
157910
+ height: newH,
157730
157911
  scaleX: full.scaleX,
157731
157912
  scaleY: full.scaleY,
157732
157913
  angle: full.angle,
@@ -157738,6 +157919,35 @@ class CropService {
157738
157919
  selectable: true,
157739
157920
  evented: true
157740
157921
  });
157922
+ if (this.maskPath) {
157923
+ const newClip = new no(this.maskPath, {
157924
+ originX: "center",
157925
+ originY: "center"
157926
+ });
157927
+ const pathW = newClip.width || 1;
157928
+ const pathH = newClip.height || 1;
157929
+ newClip.set({
157930
+ scaleX: newW / pathW,
157931
+ scaleY: newH / pathH,
157932
+ left: 0,
157933
+ top: 0,
157934
+ angle: 0
157935
+ });
157936
+ newClip.__maskPath = this.maskPath;
157937
+ img.__maskPath = this.maskPath;
157938
+ img.set({ clipPath: newClip });
157939
+ } else if (img.clipPath && img.clipPath.type === "path") {
157940
+ const pW = img.clipPath.width || 1;
157941
+ const pH = img.clipPath.height || 1;
157942
+ img.clipPath.set({
157943
+ scaleX: newW / pW,
157944
+ scaleY: newH / pH,
157945
+ left: 0,
157946
+ top: 0,
157947
+ originX: "center",
157948
+ originY: "center"
157949
+ });
157950
+ }
157741
157951
  img.setPositionByOrigin(
157742
157952
  new ot(zone.left, zone.top),
157743
157953
  "center",
@@ -157746,7 +157956,9 @@ class CropService {
157746
157956
  img.setCoords();
157747
157957
  this.cleanup();
157748
157958
  this.canvas.setActiveObject(img);
157959
+ bringOverlaysToFront(this.canvas);
157749
157960
  this.canvas.requestRenderAll();
157961
+ useHistoryStore.getState().saveState();
157750
157962
  this.onEnd?.();
157751
157963
  }
157752
157964
  /* ======================== CANCEL ========================== */
@@ -157782,6 +157994,7 @@ class CropService {
157782
157994
  this.fullImage = null;
157783
157995
  this.darkOverlay = null;
157784
157996
  this.targetImage = null;
157997
+ this.maskPath = null;
157785
157998
  }
157786
157999
  }
157787
158000
  const cropService = new CropService();
@@ -157868,7 +158081,7 @@ function PictureToolbar() {
157868
158081
  const [isRemovingBg, setIsRemovingBg] = useState(false);
157869
158082
  const [bgStatus, setBgStatus] = useState(null);
157870
158083
  const activeObj = canvas2?.getActiveObject();
157871
- const hasMask = !!activeObj?.clipPath;
158084
+ const hasMask = !!activeObj?.clipPath || !!activeObj?.__maskPath;
157872
158085
  activeObj && (activeObj.type === "image" || activeObj.isType?.("image"));
157873
158086
  const isBgRemoved = !!activeObj?.__originalSrc || !!activeObj?.__isBgRemoved;
157874
158087
  if (type !== "image" || objects?.[0]?.name === "qr" || objects?.[0]?.name === "barcode")
@@ -157877,8 +158090,9 @@ function PictureToolbar() {
157877
158090
  if (!canvas2) return;
157878
158091
  const obj = canvas2.getActiveObject();
157879
158092
  if (!obj) return;
157880
- if (obj.clipPath) {
158093
+ if (obj.clipPath || obj.__maskPath) {
157881
158094
  obj.set({ clipPath: null });
158095
+ obj.__maskPath = null;
157882
158096
  obj.setCoords();
157883
158097
  canvas2.requestRenderAll();
157884
158098
  saveState();
@@ -158169,6 +158383,7 @@ function DuplicateButton() {
158169
158383
  );
158170
158384
  const newSelection = clones.length > 1 ? new Qo(clones, { canvas: canvas2 }) : clones[0];
158171
158385
  canvas2.setActiveObject(newSelection);
158386
+ bringOverlaysToFront(canvas2);
158172
158387
  canvas2.requestRenderAll();
158173
158388
  saveState();
158174
158389
  return;
@@ -158184,6 +158399,7 @@ function DuplicateButton() {
158184
158399
  canvas2.discardActiveObject();
158185
158400
  canvas2.add(cloned);
158186
158401
  canvas2.setActiveObject(cloned);
158402
+ bringOverlaysToFront(canvas2);
158187
158403
  canvas2.requestRenderAll();
158188
158404
  };
158189
158405
  return /* @__PURE__ */ jsx(
@@ -158737,44 +158953,50 @@ function PositionPicker() {
158737
158953
  if (!active) return [];
158738
158954
  return active.type?.toLowerCase() === "activeselection" ? active.getObjects() : [active];
158739
158955
  };
158740
- const getWorkarea2 = () => canvas2.getObjects().find((o2) => o2.name === "workarea");
158741
- const normalizeOrigin = (obj) => {
158742
- if (obj.originX === "left" && obj.originY === "top") return;
158743
- const point = obj.getPointByOrigin("left", "top");
158744
- obj.set({ originX: "left", originY: "top" });
158745
- obj.setPositionByOrigin(point, "left", "top");
158746
- };
158956
+ const getAlignmentBoundary = () => canvas2.getObjects().find((o2) => o2.name === "workarea");
158747
158957
  const applyAlign = (align) => {
158748
- const workarea = getWorkarea2();
158749
- if (!workarea) return;
158750
- const left = workarea.left ?? 0;
158751
- const top = workarea.top ?? 0;
158752
- const width = workarea.getScaledWidth();
158753
- const height = workarea.getScaledHeight();
158958
+ const boundary = getAlignmentBoundary();
158959
+ if (!boundary) return;
158960
+ const bBounds = boundary.getBoundingRect();
158961
+ const bLeft = bBounds.left;
158962
+ const bTop = bBounds.top;
158963
+ const bWidth = bBounds.width;
158964
+ const bHeight = bBounds.height;
158754
158965
  getTargets().forEach((obj) => {
158755
- normalizeOrigin(obj);
158756
- const w2 = obj.getScaledWidth();
158757
- const h2 = obj.getScaledHeight();
158966
+ const objBounds = obj.getBoundingRect();
158967
+ const pad = obj.padding || 0;
158968
+ const effLeft = objBounds.left - pad;
158969
+ const effTop = objBounds.top - pad;
158970
+ const effWidth = objBounds.width + pad * 2;
158971
+ const effHeight = objBounds.height + pad * 2;
158972
+ let dx = 0;
158973
+ let dy = 0;
158758
158974
  switch (align) {
158759
158975
  case "left":
158760
- obj.set({ left });
158976
+ dx = bLeft - effLeft;
158761
158977
  break;
158762
158978
  case "center":
158763
- obj.set({ left: left + (width - w2) / 2 });
158979
+ dx = bLeft + (bWidth - effWidth) / 2 - effLeft;
158764
158980
  break;
158765
158981
  case "right":
158766
- obj.set({ left: left + width - w2 });
158982
+ dx = bLeft + bWidth - effWidth - effLeft;
158767
158983
  break;
158768
158984
  case "top":
158769
- obj.set({ top });
158985
+ dy = bTop - effTop;
158770
158986
  break;
158771
158987
  case "middle":
158772
- obj.set({ top: top + (height - h2) / 2 });
158988
+ dy = bTop + (bHeight - effHeight) / 2 - effTop;
158773
158989
  break;
158774
158990
  case "bottom":
158775
- obj.set({ top: top + height - h2 });
158991
+ dy = bTop + bHeight - effHeight - effTop;
158776
158992
  break;
158777
158993
  }
158994
+ if (dx !== 0) {
158995
+ obj.set({ left: (obj.left ?? 0) + dx });
158996
+ }
158997
+ if (dy !== 0) {
158998
+ obj.set({ top: (obj.top ?? 0) + dy });
158999
+ }
158778
159000
  obj.setCoords();
158779
159001
  });
158780
159002
  canvas2.requestRenderAll();
@@ -158797,42 +159019,40 @@ function PositionPicker() {
158797
159019
  const objects = active.getObjects();
158798
159020
  if (objects.length && objects.length > 1) {
158799
159021
  objects.forEach(function(obj) {
158800
- const itemWidth = obj.getBoundingRect().width;
159022
+ const itemWidth = obj.getScaledWidth();
159023
+ const itemHeight = obj.getScaledHeight();
159024
+ obj.set({ originX: "center", originY: "center" });
158801
159025
  switch (align) {
158802
159026
  case "left":
158803
- obj.set({ left: -(groupWidth / 2), originX: "left" });
159027
+ obj.set({ left: -groupWidth / 2 + itemWidth / 2 });
158804
159028
  break;
158805
159029
  case "center":
158806
- obj.set({ left: 0 - itemWidth / 2, originX: "left" });
159030
+ obj.set({ left: 0 });
158807
159031
  break;
158808
159032
  case "right":
158809
- obj.set({
158810
- left: groupWidth / 2 - itemWidth / 2,
158811
- originX: "center"
158812
- });
159033
+ obj.set({ left: groupWidth / 2 - itemWidth / 2 });
158813
159034
  break;
158814
159035
  case "top":
158815
- obj.set({ originY: "top", top: -groupHeight / 2 });
158816
- break;
158817
- case "bottom":
158818
- obj.set({ originY: "bottom", top: groupHeight / 2 });
159036
+ obj.set({ top: -groupHeight / 2 + itemHeight / 2 });
158819
159037
  break;
158820
159038
  case "middle":
158821
- obj.set({
158822
- originY: "center",
158823
- top: -groupHeight / 2 + groupHeight / 2
158824
- });
159039
+ obj.set({ top: 0 });
159040
+ break;
159041
+ case "bottom":
159042
+ obj.set({ top: groupHeight / 2 - itemHeight / 2 });
158825
159043
  break;
158826
159044
  }
158827
159045
  obj.setCoords();
158828
- canvas2.requestRenderAll();
158829
- saveState();
158830
159046
  });
159047
+ active.setCoords();
159048
+ canvas2.requestRenderAll();
159049
+ saveState();
158831
159050
  }
158832
159051
  };
158833
159052
  const bringForward = () => {
158834
159053
  const objects = canvas2.getObjects();
158835
159054
  [...getTargets()].sort((a2, b2) => objects.indexOf(a2) - objects.indexOf(b2)).reverse().forEach((obj) => canvas2.bringObjectForward(obj, true));
159055
+ bringOverlaysToFront(canvas2);
158836
159056
  canvas2.requestRenderAll();
158837
159057
  saveState();
158838
159058
  };
@@ -158861,6 +159081,7 @@ function PositionPicker() {
158861
159081
  const bringToFront = () => {
158862
159082
  const objects = canvas2.getObjects();
158863
159083
  [...getTargets()].sort((a2, b2) => objects.indexOf(a2) - objects.indexOf(b2)).reverse().forEach((obj) => canvas2.bringObjectToFront(obj));
159084
+ bringOverlaysToFront(canvas2);
158864
159085
  canvas2.requestRenderAll();
158865
159086
  saveState();
158866
159087
  };
@@ -159215,6 +159436,7 @@ function GroupButton() {
159215
159436
  canvas2.add(group);
159216
159437
  }
159217
159438
  canvas2.setActiveObject(group);
159439
+ bringOverlaysToFront(canvas2);
159218
159440
  canvas2.requestRenderAll();
159219
159441
  useSelectionStore.getState().setSelection([group]);
159220
159442
  saveState();
@@ -159366,6 +159588,9 @@ function configureObjectControls(obj) {
159366
159588
  obj.__rotationControlSize = rotationSize;
159367
159589
  obj.controls.mtr = createRotateControl(rotationSize, rotationOffset);
159368
159590
  const textObject = isTextObject(obj);
159591
+ if (textObject) {
159592
+ obj.set({ padding: 0 });
159593
+ }
159369
159594
  if (!obj.controls.mb) {
159370
159595
  obj.controls.mb = new ai({
159371
159596
  x: 0,
@@ -159484,6 +159709,7 @@ function UngroupButton() {
159484
159709
  });
159485
159710
  canvas2.discardActiveObject();
159486
159711
  useSelectionStore.getState().clearSelection();
159712
+ bringOverlaysToFront(canvas2);
159487
159713
  canvas2.requestRenderAll();
159488
159714
  saveState();
159489
159715
  };
@@ -159593,12 +159819,31 @@ function Toolbar() {
159593
159819
  ) });
159594
159820
  }
159595
159821
  function applyOpacity(hex, opacity) {
159596
- const alpha = Math.max(0, Math.min(1, opacity / 100));
159597
- const r2 = parseInt(hex.slice(1, 3), 16);
159598
- const g2 = parseInt(hex.slice(3, 5), 16);
159599
- const b2 = parseInt(hex.slice(5, 7), 16);
159600
- return `rgba(${r2}, ${g2}, ${b2}, ${alpha})`;
159601
- }
159822
+ const alpha = Math.max(0, Math.min(1, (opacity ?? 100) / 100));
159823
+ if (!hex) return `rgba(0, 0, 0, ${alpha})`;
159824
+ let clean = hex.trim();
159825
+ if (clean.startsWith("#")) {
159826
+ clean = clean.slice(1);
159827
+ if (clean.length === 3) {
159828
+ clean = clean.split("").map((c2) => c2 + c2).join("");
159829
+ }
159830
+ if (clean.length >= 6) {
159831
+ const r2 = parseInt(clean.slice(0, 2), 16) || 0;
159832
+ const g2 = parseInt(clean.slice(2, 4), 16) || 0;
159833
+ const b2 = parseInt(clean.slice(4, 6), 16) || 0;
159834
+ return `rgba(${r2}, ${g2}, ${b2}, ${alpha})`;
159835
+ }
159836
+ }
159837
+ return clean;
159838
+ }
159839
+ const PENCIL_CURSOR_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
159840
+ <path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" fill="#ffffff" stroke="#ffffff" stroke-width="2.5" stroke-linejoin="round"/>
159841
+ <path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" fill="#1e293b" stroke="#0f172a" stroke-width="1.2" stroke-linejoin="round"/>
159842
+ <polygon points="2,22 5.5,20 4,18.5" fill="#f59e0b"/>
159843
+ <polygon points="2,22 3.2,21.3 2.7,20.8" fill="#000000"/>
159844
+ <path d="m15 5 4 4" stroke="#94a3b8" stroke-width="1.2" stroke-linecap="round"/>
159845
+ </svg>`;
159846
+ const PENCIL_CURSOR = `url("data:image/svg+xml;utf8,${encodeURIComponent(PENCIL_CURSOR_SVG)}") 2 22, crosshair`;
159602
159847
  function useFabricDrawing() {
159603
159848
  const { drawTool, strokeWidth, opacity, color } = useDrawStore();
159604
159849
  useEffect(() => {
@@ -159608,19 +159853,99 @@ function useFabricDrawing() {
159608
159853
  canvas2.isDrawingMode = false;
159609
159854
  canvas2.selection = true;
159610
159855
  canvas2.defaultCursor = "default";
159856
+ canvas2.freeDrawingCursor = "default";
159857
+ canvas2.setCursor("default");
159858
+ if (canvas2.upperCanvasEl) {
159859
+ canvas2.upperCanvasEl.style.cursor = "default";
159860
+ }
159861
+ canvas2._isCurrentlyDrawing = false;
159611
159862
  return;
159612
159863
  }
159864
+ if (canvas2.getActiveObject()) {
159865
+ canvas2.discardActiveObject();
159866
+ useSelectionStore.getState().clearSelection();
159867
+ canvas2.requestRenderAll();
159868
+ }
159869
+ const activeCursor = drawTool === "brush" ? PENCIL_CURSOR : "crosshair";
159613
159870
  canvas2.isDrawingMode = true;
159614
159871
  canvas2.selection = false;
159615
- canvas2.defaultCursor = "crosshair";
159872
+ canvas2.defaultCursor = activeCursor;
159873
+ canvas2.freeDrawingCursor = activeCursor;
159874
+ canvas2.setCursor(activeCursor);
159875
+ if (canvas2.upperCanvasEl) {
159876
+ canvas2.upperCanvasEl.style.cursor = activeCursor;
159877
+ }
159616
159878
  const brush = new oo(canvas2);
159617
- brush.width = strokeWidth;
159618
- brush.color = applyOpacity(color, opacity);
159619
159879
  if (drawTool === "highlighter") {
159620
159880
  brush.width = strokeWidth * 1.5;
159621
- brush.color = applyOpacity(color, 30);
159881
+ brush.color = applyOpacity(color, opacity ?? 50);
159882
+ } else {
159883
+ brush.width = strokeWidth;
159884
+ brush.color = applyOpacity(color, opacity ?? 100);
159622
159885
  }
159623
159886
  canvas2.freeDrawingBrush = brush;
159887
+ const handleMouseDownBefore = (opt) => {
159888
+ if (!canvas2.isDrawingMode) return;
159889
+ const target = opt.target || canvas2.findTarget(opt.e);
159890
+ if (!target) return;
159891
+ const name = target.name || "";
159892
+ if (name === "workarea" || name === "watermark" || name.startsWith("overlay-") || name.startsWith("guideline-")) {
159893
+ return;
159894
+ }
159895
+ if (target.isDrawingPath || name === "drawing-path") {
159896
+ return;
159897
+ }
159898
+ if (target.selectable !== false && target.evented !== false) {
159899
+ canvas2.isDrawingMode = false;
159900
+ canvas2.selection = true;
159901
+ canvas2.defaultCursor = "default";
159902
+ canvas2.freeDrawingCursor = "default";
159903
+ canvas2.setCursor("default");
159904
+ if (canvas2.upperCanvasEl) {
159905
+ canvas2.upperCanvasEl.style.cursor = "default";
159906
+ }
159907
+ canvas2._isCurrentlyDrawing = false;
159908
+ useDrawStore.getState().setDrawTool("select");
159909
+ canvas2.setActiveObject(target);
159910
+ canvas2.requestRenderAll();
159911
+ }
159912
+ };
159913
+ const handlePathCreated = (e3) => {
159914
+ if (e3?.path) {
159915
+ e3.path.name = "drawing-path";
159916
+ e3.path.isDrawingPath = true;
159917
+ }
159918
+ };
159919
+ const handleSelectionChange = () => {
159920
+ if (useDrawStore.getState().drawTool !== "select") {
159921
+ useDrawStore.getState().setDrawTool("select");
159922
+ }
159923
+ if (canvas2.isDrawingMode) {
159924
+ canvas2.isDrawingMode = false;
159925
+ canvas2.selection = true;
159926
+ canvas2.defaultCursor = "default";
159927
+ canvas2.freeDrawingCursor = "default";
159928
+ canvas2.setCursor("default");
159929
+ if (canvas2.upperCanvasEl) {
159930
+ canvas2.upperCanvasEl.style.cursor = "default";
159931
+ }
159932
+ canvas2._isCurrentlyDrawing = false;
159933
+ canvas2.requestRenderAll();
159934
+ }
159935
+ };
159936
+ canvas2.on("mouse:down:before", handleMouseDownBefore);
159937
+ canvas2.on("path:created", handlePathCreated);
159938
+ canvas2.on("selection:created", handleSelectionChange);
159939
+ canvas2.on("selection:updated", handleSelectionChange);
159940
+ return () => {
159941
+ canvas2.off("mouse:down:before", handleMouseDownBefore);
159942
+ canvas2.off("path:created", handlePathCreated);
159943
+ canvas2.off("selection:created", handleSelectionChange);
159944
+ canvas2.off("selection:updated", handleSelectionChange);
159945
+ if (canvas2.upperCanvasEl) {
159946
+ canvas2.upperCanvasEl.style.cursor = "default";
159947
+ }
159948
+ };
159624
159949
  }, [drawTool, strokeWidth, opacity, color]);
159625
159950
  }
159626
159951
  function PointerIcon() {
@@ -159651,6 +159976,89 @@ function HandIcon() {
159651
159976
  /* @__PURE__ */ jsx("path", { d: "M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15" })
159652
159977
  ] });
159653
159978
  }
159979
+ function GridIcon({
159980
+ size = 18,
159981
+ className = ""
159982
+ }) {
159983
+ return /* @__PURE__ */ jsxs(
159984
+ "svg",
159985
+ {
159986
+ width: size,
159987
+ height: size,
159988
+ viewBox: "0 0 24 24",
159989
+ fill: "none",
159990
+ stroke: "currentColor",
159991
+ strokeWidth: "2",
159992
+ strokeLinecap: "round",
159993
+ strokeLinejoin: "round",
159994
+ className,
159995
+ children: [
159996
+ /* @__PURE__ */ jsx("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }),
159997
+ /* @__PURE__ */ jsx("path", { d: "M3 9h18" }),
159998
+ /* @__PURE__ */ jsx("path", { d: "M3 15h18" }),
159999
+ /* @__PURE__ */ jsx("path", { d: "M9 3v18" }),
160000
+ /* @__PURE__ */ jsx("path", { d: "M15 3v18" })
160001
+ ]
160002
+ }
160003
+ );
160004
+ }
160005
+ function getGridStep(width, height) {
160006
+ const minDim = Math.min(width, height);
160007
+ if (minDim <= 200) return 10;
160008
+ if (minDim <= 400) return 20;
160009
+ if (minDim <= 800) return 25;
160010
+ return 40;
160011
+ }
160012
+ function renderGrid(canvas2) {
160013
+ if (!canvas2) return;
160014
+ canvas2.getObjects().forEach((o2) => {
160015
+ if (o2.name === "canvas-grid") {
160016
+ canvas2.remove(o2);
160017
+ }
160018
+ });
160019
+ const showGrid = useEditorStore.getState().showGrid;
160020
+ if (!showGrid) {
160021
+ canvas2.requestRenderAll();
160022
+ return;
160023
+ }
160024
+ const workarea = canvas2.getObjects().find((o2) => o2.name === "workarea");
160025
+ if (!workarea) {
160026
+ canvas2.requestRenderAll();
160027
+ return;
160028
+ }
160029
+ const waL = workarea.left ?? 0;
160030
+ const waT = workarea.top ?? 0;
160031
+ const waW = workarea.getScaledWidth();
160032
+ const waH = workarea.getScaledHeight();
160033
+ if (waW <= 0 || waH <= 0) return;
160034
+ const step = getGridStep(waW, waH);
160035
+ let pathData = "";
160036
+ for (let x2 = step; x2 < waW; x2 += step) {
160037
+ const roundX = Math.round(x2 * 100) / 100;
160038
+ pathData += `M ${roundX} 0 V ${waH} `;
160039
+ }
160040
+ for (let y2 = step; y2 < waH; y2 += step) {
160041
+ const roundY = Math.round(y2 * 100) / 100;
160042
+ pathData += `M 0 ${roundY} H ${waW} `;
160043
+ }
160044
+ if (!pathData) return;
160045
+ const gridPath = new no(pathData, {
160046
+ name: "canvas-grid",
160047
+ left: waL,
160048
+ top: waT,
160049
+ originX: "left",
160050
+ originY: "top",
160051
+ stroke: "rgba(100, 116, 139, 0.28)",
160052
+ strokeWidth: 1,
160053
+ fill: "",
160054
+ selectable: false,
160055
+ evented: false,
160056
+ excludeFromExport: true
160057
+ });
160058
+ canvas2.add(gridPath);
160059
+ bringOverlaysToFront(canvas2);
160060
+ canvas2.requestRenderAll();
160061
+ }
159654
160062
  function enablePanMode(canvas2) {
159655
160063
  let isDragging = false;
159656
160064
  let lastClientX = 0;
@@ -159918,10 +160326,11 @@ function setupHistoryRecording(canvas2) {
159918
160326
  const target = e3?.target;
159919
160327
  if (!target) return;
159920
160328
  const name = target.name || "";
159921
- if (name === "workarea" || name === "watermark" || name.startsWith("overlay-") || name.startsWith("guideline-")) {
160329
+ if (name === "workarea" || name === "watermark" || name === "bg-image" || name.startsWith("overlay-") || name.startsWith("guideline-") || name === "canvas-grid") {
159922
160330
  return;
159923
160331
  }
159924
160332
  if (useHistoryStore.getState().isRestoring) return;
160333
+ flushBackgroundSave();
159925
160334
  if (modifiedTimer) {
159926
160335
  clearTimeout(modifiedTimer);
159927
160336
  modifiedTimer = null;
@@ -159936,7 +160345,7 @@ function setupHistoryRecording(canvas2) {
159936
160345
  const target = e3?.target;
159937
160346
  if (!target) return;
159938
160347
  const name = target.name || "";
159939
- if (name === "workarea" || name === "watermark" || name.startsWith("overlay-") || name.startsWith("guideline-")) {
160348
+ if (name === "workarea" || name === "watermark" || name.startsWith("overlay-") || name.startsWith("guideline-") || name === "canvas-grid") {
159940
160349
  return;
159941
160350
  }
159942
160351
  if (useHistoryStore.getState().isRestoring) return;
@@ -159959,6 +160368,12 @@ function setupHistoryRecording(canvas2) {
159959
160368
  }
159960
160369
  function setupDoublClickCrop(canvas2) {
159961
160370
  const handleDoubleClick = (e3) => {
160371
+ const isCropping = useEditorStore.getState().isCropping;
160372
+ if (isCropping) {
160373
+ applyCrop(canvas2);
160374
+ useEditorStore.getState().stopCropping();
160375
+ return;
160376
+ }
159962
160377
  const target = e3.target;
159963
160378
  if (target && target.type === "image" && target.name !== "bg-image") {
159964
160379
  useEditorStore.getState().startCropping();
@@ -160004,6 +160419,13 @@ function setupSelectionHandlers(canvas2, setSelection, clearSelection) {
160004
160419
  canvas2.requestRenderAll();
160005
160420
  };
160006
160421
  canvas2.on("selection:created", (e3) => {
160422
+ if (useDrawStore.getState().drawTool !== "select") {
160423
+ useDrawStore.getState().setDrawTool("select");
160424
+ canvas2.isDrawingMode = false;
160425
+ canvas2.selection = true;
160426
+ canvas2.defaultCursor = "default";
160427
+ canvas2._isCurrentlyDrawing = false;
160428
+ }
160007
160429
  if (e3.selected) {
160008
160430
  e3.selected.forEach((o2) => {
160009
160431
  o2.__showDegree = false;
@@ -160011,8 +160433,16 @@ function setupSelectionHandlers(canvas2, setSelection, clearSelection) {
160011
160433
  }
160012
160434
  updateSelection();
160013
160435
  configureSelectionControls();
160436
+ bringOverlaysToFront(canvas2);
160014
160437
  });
160015
160438
  canvas2.on("selection:updated", (e3) => {
160439
+ if (useDrawStore.getState().drawTool !== "select") {
160440
+ useDrawStore.getState().setDrawTool("select");
160441
+ canvas2.isDrawingMode = false;
160442
+ canvas2.selection = true;
160443
+ canvas2.defaultCursor = "default";
160444
+ canvas2._isCurrentlyDrawing = false;
160445
+ }
160016
160446
  const active = canvas2.getActiveObject();
160017
160447
  const bgImg = canvas2.getObjects().find((o2) => o2.name === "bg-image");
160018
160448
  if (bgImg && active !== bgImg) {
@@ -160033,6 +160463,7 @@ function setupSelectionHandlers(canvas2, setSelection, clearSelection) {
160033
160463
  }
160034
160464
  updateSelection();
160035
160465
  configureSelectionControls();
160466
+ bringOverlaysToFront(canvas2);
160036
160467
  });
160037
160468
  canvas2.on("object:scaling", configureSelectionControls);
160038
160469
  canvas2.on("object:rotating", (e3) => {
@@ -160049,7 +160480,12 @@ function setupSelectionHandlers(canvas2, setSelection, clearSelection) {
160049
160480
  o2.__showDegree = false;
160050
160481
  });
160051
160482
  configureSelectionControls();
160483
+ bringOverlaysToFront(canvas2);
160484
+ };
160485
+ const handleObjectMoving = () => {
160486
+ bringOverlaysToFront(canvas2);
160052
160487
  };
160488
+ canvas2.on("object:moving", handleObjectMoving);
160053
160489
  canvas2.on("object:modified", hideDegree);
160054
160490
  canvas2.on("mouse:up", hideDegree);
160055
160491
  canvas2.on("selection:cleared", () => {
@@ -160066,6 +160502,7 @@ function setupSelectionHandlers(canvas2, setSelection, clearSelection) {
160066
160502
  }
160067
160503
  }
160068
160504
  hideDegree();
160505
+ bringOverlaysToFront(canvas2);
160069
160506
  clearSelection();
160070
160507
  });
160071
160508
  return () => {
@@ -160073,6 +160510,7 @@ function setupSelectionHandlers(canvas2, setSelection, clearSelection) {
160073
160510
  canvas2.off("selection:updated");
160074
160511
  canvas2.off("object:scaling", configureSelectionControls);
160075
160512
  canvas2.off("object:rotating");
160513
+ canvas2.off("object:moving", handleObjectMoving);
160076
160514
  canvas2.off("object:modified", hideDegree);
160077
160515
  canvas2.off("mouse:up", hideDegree);
160078
160516
  canvas2.off("selection:cleared");
@@ -160081,7 +160519,9 @@ function setupSelectionHandlers(canvas2, setSelection, clearSelection) {
160081
160519
  function setupObjectAddedHandler(canvas2) {
160082
160520
  const handleObjectAdded = (e3) => {
160083
160521
  const obj = e3.target;
160084
- if (!obj || obj.name === "workarea") return;
160522
+ if (!obj || obj.name === "workarea" || obj.name?.startsWith("overlay-") || obj.name?.startsWith("guideline-")) {
160523
+ return;
160524
+ }
160085
160525
  configureObjectControls(obj);
160086
160526
  bringOverlaysToFront(canvas2);
160087
160527
  };
@@ -160575,21 +161015,27 @@ function ZoomControls({ onReset }) {
160575
161015
  ] }) });
160576
161016
  }
160577
161017
  const OFFSET = 20;
160578
- function useKeyboardEvents(canvas2) {
161018
+ function useKeyboardEvents(canvasParam) {
160579
161019
  const clipboardRef = useRef(null);
161020
+ const storeCanvas = useEditorStore((s2) => s2.canvas);
161021
+ const historyCanvas = useHistoryStore((s2) => s2.canvas);
161022
+ const canvas2 = canvasParam || storeCanvas || historyCanvas;
160580
161023
  const saveState = useHistoryStore((s2) => s2.saveState);
160581
161024
  useEffect(() => {
160582
- if (!canvas2) return;
161025
+ const getActiveCanvas = () => canvas2 || useEditorStore.getState().canvas || useHistoryStore.getState().canvas;
160583
161026
  const handleCopy = async () => {
160584
- const activeObject = canvas2.getActiveObject();
161027
+ const activeCanvas = getActiveCanvas();
161028
+ if (!activeCanvas) return;
161029
+ const activeObject = activeCanvas.getActiveObject();
160585
161030
  if (!activeObject || activeObject.name === "workarea") return;
160586
161031
  const cloned = await activeObject.clone();
160587
161032
  clipboardRef.current = cloned;
160588
161033
  };
160589
161034
  const handlePaste = async () => {
160590
- if (!clipboardRef.current) return;
161035
+ const activeCanvas = getActiveCanvas();
161036
+ if (!activeCanvas || !clipboardRef.current) return;
160591
161037
  const clonedObj = await clipboardRef.current.clone();
160592
- canvas2.discardActiveObject();
161038
+ activeCanvas.discardActiveObject();
160593
161039
  clonedObj.set({
160594
161040
  left: (clonedObj.left ?? 0) + OFFSET,
160595
161041
  top: (clonedObj.top ?? 0) + OFFSET,
@@ -160600,16 +161046,17 @@ function useKeyboardEvents(canvas2) {
160600
161046
  top: (clipboardRef.current.top ?? 0) + OFFSET
160601
161047
  });
160602
161048
  if (clonedObj.type?.toLowerCase() === "activeselection") {
160603
- clonedObj.canvas = canvas2;
161049
+ clonedObj.canvas = activeCanvas;
160604
161050
  clonedObj.forEachObject((obj) => {
160605
- canvas2.add(obj);
161051
+ activeCanvas.add(obj);
160606
161052
  });
160607
161053
  clonedObj.setCoords();
160608
161054
  } else {
160609
- canvas2.add(clonedObj);
161055
+ activeCanvas.add(clonedObj);
160610
161056
  }
160611
- canvas2.setActiveObject(clonedObj);
160612
- canvas2.requestRenderAll();
161057
+ activeCanvas.setActiveObject(clonedObj);
161058
+ bringOverlaysToFront(activeCanvas);
161059
+ activeCanvas.requestRenderAll();
160613
161060
  saveState();
160614
161061
  };
160615
161062
  const handleKeyDown = (e3) => {
@@ -160617,25 +161064,36 @@ function useKeyboardEvents(canvas2) {
160617
161064
  const isInput = activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement || activeElement?.isContentEditable;
160618
161065
  if (isInput) return;
160619
161066
  const isModifier = e3.ctrlKey || e3.metaKey;
160620
- if (isModifier && e3.key.toLowerCase() === "c") {
161067
+ if (!isModifier) return;
161068
+ const isZ = e3.key?.toLowerCase() === "z" || e3.code === "KeyZ";
161069
+ const isY = e3.key?.toLowerCase() === "y" || e3.code === "KeyY";
161070
+ const isC = e3.key?.toLowerCase() === "c" || e3.code === "KeyC";
161071
+ const isV = e3.key?.toLowerCase() === "v" || e3.code === "KeyV";
161072
+ if (isZ && !e3.shiftKey) {
160621
161073
  e3.preventDefault();
160622
- handleCopy();
161074
+ useHistoryStore.getState().undo();
161075
+ return;
160623
161076
  }
160624
- if (isModifier && e3.key.toLowerCase() === "v") {
161077
+ if (isZ && e3.shiftKey || isY) {
160625
161078
  e3.preventDefault();
160626
- handlePaste();
161079
+ useHistoryStore.getState().redoAction();
161080
+ return;
160627
161081
  }
160628
- if (isModifier && e3.key.toLowerCase() === "z") {
161082
+ if (isC) {
160629
161083
  e3.preventDefault();
160630
- if (e3.shiftKey) {
160631
- useHistoryStore.getState().redoAction();
160632
- } else {
160633
- useHistoryStore.getState().undo();
160634
- }
161084
+ handleCopy();
161085
+ return;
160635
161086
  }
160636
- if (isModifier && e3.key.toLowerCase() === "y") {
161087
+ if (isV) {
160637
161088
  e3.preventDefault();
160638
- useHistoryStore.getState().redoAction();
161089
+ handlePaste();
161090
+ return;
161091
+ }
161092
+ const isQuote = e3.key === "'" || e3.code === "Quote";
161093
+ if (isQuote) {
161094
+ e3.preventDefault();
161095
+ useEditorStore.getState().toggleGrid();
161096
+ return;
160639
161097
  }
160640
161098
  };
160641
161099
  window.addEventListener("keydown", handleKeyDown);
@@ -160651,9 +161109,14 @@ function Workspace() {
160651
161109
  const workareaRef = useRef(null);
160652
161110
  const panHandlersRef = useRef(null);
160653
161111
  const isCropping = useEditorStore((s2) => s2.isCropping);
161112
+ const canvas2 = useEditorStore((s2) => s2.canvas);
160654
161113
  const setCanvas = useEditorStore((s2) => s2.setCanvas);
161114
+ const showGrid = useEditorStore((s2) => s2.showGrid);
161115
+ const toggleGrid = useEditorStore((s2) => s2.toggleGrid);
160655
161116
  const setSelection = useSelectionStore((s2) => s2.setSelection);
160656
161117
  const clearSelection = useSelectionStore((s2) => s2.clearSelection);
161118
+ const drawTool = useDrawStore((s2) => s2.drawTool);
161119
+ const setDrawTool = useDrawStore((s2) => s2.setDrawTool);
160657
161120
  const productData = useProductStore((s2) => s2.productData);
160658
161121
  const isTwoSided = productData?.optionsMap?.printing === "4/4";
160659
161122
  const [currentPage, setCurrentPage] = useState("front");
@@ -160703,20 +161166,20 @@ function Workspace() {
160703
161166
  }
160704
161167
  if (!isMounted || !canvasRef.current || !containerRef.current || fabricRef.current) return;
160705
161168
  initializeRotateImage();
160706
- const canvas2 = initializeCanvas(canvasRef.current, containerRef.current);
160707
- fabricRef.current = canvas2;
160708
- setCanvas(canvas2);
160709
- useHistoryStore.getState().setCanvas(canvas2);
160710
- const workarea = createWorkarea(canvas2, containerRef.current, product);
161169
+ const canvas22 = initializeCanvas(canvasRef.current, containerRef.current);
161170
+ fabricRef.current = canvas22;
161171
+ setCanvas(canvas22);
161172
+ useHistoryStore.getState().setCanvas(canvas22);
161173
+ const workarea = createWorkarea(canvas22, containerRef.current, product);
160711
161174
  workareaRef.current = workarea;
160712
- const cleanupHistory = setupHistoryRecording(canvas2);
160713
- const cleanupCrop = setupDoublClickCrop(canvas2);
161175
+ const cleanupHistory = setupHistoryRecording(canvas22);
161176
+ const cleanupCrop = setupDoublClickCrop(canvas22);
160714
161177
  const cleanupSelection = setupSelectionHandlers(
160715
- canvas2,
161178
+ canvas22,
160716
161179
  setSelection,
160717
161180
  clearSelection
160718
161181
  );
160719
- const cleanupObjectAdded = setupObjectAddedHandler(canvas2);
161182
+ const cleanupObjectAdded = setupObjectAddedHandler(canvas22);
160720
161183
  if (isTextDesign && templateId) {
160721
161184
  const item = await ShapeService.getTextTemplateById(templateId);
160722
161185
  if (item?.jsonData) {
@@ -160724,7 +161187,7 @@ function Workspace() {
160724
161187
  pageJSON.current.front = parsed;
160725
161188
  await loadTemplateFontsIntoHead(null, parsed);
160726
161189
  useHistoryStore.setState({ isRestoring: true });
160727
- await loadPageToCanvas(canvas2, parsed);
161190
+ await loadPageToCanvas(canvas22, parsed);
160728
161191
  useHistoryStore.setState({ isRestoring: false });
160729
161192
  useHistoryStore.getState().saveState();
160730
161193
  }
@@ -160745,7 +161208,7 @@ function Workspace() {
160745
161208
  if (frontData) {
160746
161209
  pageJSON.current.front = frontData;
160747
161210
  useHistoryStore.setState({ isRestoring: true });
160748
- await loadPageToCanvas(canvas2, frontData);
161211
+ await loadPageToCanvas(canvas22, frontData);
160749
161212
  useHistoryStore.setState({ isRestoring: false });
160750
161213
  useHistoryStore.getState().saveState();
160751
161214
  }
@@ -160762,10 +161225,10 @@ function Workspace() {
160762
161225
  cleanupCrop();
160763
161226
  cleanupSelection();
160764
161227
  cleanupObjectAdded();
160765
- if (canvas2.__cleanupResizeObserver) {
160766
- canvas2.__cleanupResizeObserver();
161228
+ if (canvas22.__cleanupResizeObserver) {
161229
+ canvas22.__cleanupResizeObserver();
160767
161230
  }
160768
- canvas2.dispose();
161231
+ canvas22.dispose();
160769
161232
  fabricRef.current = null;
160770
161233
  setCanvas(null);
160771
161234
  clearSelection();
@@ -160803,23 +161266,30 @@ function Workspace() {
160803
161266
  return setupBorderSync(fabricRef.current);
160804
161267
  }, [fabricRef.current]);
160805
161268
  useEffect(() => {
160806
- const canvas2 = fabricRef.current;
160807
- if (!canvas2) return;
160808
- removePanHandlers(canvas2, panHandlersRef.current);
161269
+ const canvas22 = fabricRef.current;
161270
+ if (!canvas22) return;
161271
+ removePanHandlers(canvas22, panHandlersRef.current);
160809
161272
  panHandlersRef.current = null;
160810
161273
  if (isPanning) {
160811
- panHandlersRef.current = enablePanMode(canvas2);
161274
+ panHandlersRef.current = enablePanMode(canvas22);
160812
161275
  } else {
160813
- disablePanMode(canvas2);
161276
+ disablePanMode(canvas22);
160814
161277
  }
160815
161278
  return () => {
160816
- removePanHandlers(canvas2, panHandlersRef.current);
161279
+ removePanHandlers(canvas22, panHandlersRef.current);
160817
161280
  };
160818
161281
  }, [isPanning]);
160819
161282
  useFabricDrawing();
160820
- useKeyboardEvents(fabricRef.current);
161283
+ useKeyboardEvents(canvas2 || fabricRef.current);
161284
+ useEffect(() => {
161285
+ const activeCanvas = canvas2 || fabricRef.current;
161286
+ if (activeCanvas) {
161287
+ renderGrid(activeCanvas);
161288
+ }
161289
+ }, [showGrid, canvas2, currentPage]);
160821
161290
  const switchPage = async (page) => {
160822
161291
  if (!fabricRef.current) return;
161292
+ setDrawTool("select");
160823
161293
  await switchCanvasPage(
160824
161294
  fabricRef.current,
160825
161295
  currentPage,
@@ -160830,6 +161300,7 @@ function Workspace() {
160830
161300
  );
160831
161301
  useEditorStore.getState().setPages({ ...pageJSON.current });
160832
161302
  setCurrentPage(page);
161303
+ renderGrid(fabricRef.current);
160833
161304
  };
160834
161305
  return /* @__PURE__ */ jsxs(Fragment, { children: [
160835
161306
  /* @__PURE__ */ jsxs("div", { className: "absolute bottom-6 z-50 flex items-center bg-white/80 backdrop-blur-xl rounded-2xl shadow-[0_8px_32px_rgba(0,0,0,0.12)] px-1.5 py-1.5 border border-white/40 ring-1 ring-black/5", children: [
@@ -160856,20 +161327,35 @@ function Workspace() {
160856
161327
  /* @__PURE__ */ jsx(
160857
161328
  "button",
160858
161329
  {
160859
- onClick: () => setIsPanning(false),
161330
+ onClick: () => {
161331
+ setIsPanning(false);
161332
+ setDrawTool("select");
161333
+ },
160860
161334
  title: "Selection Mode",
160861
- className: `p-2 rounded-xl transition-all duration-200 ${!isPanning ? "bg-[#02bc71] text-white shadow-lg shadow-blue-200" : "text-gray-400 hover:bg-gray-100 hover:text-gray-600"}`,
161335
+ className: `p-2 rounded-xl transition-all duration-200 cursor-pointer ${!isPanning && drawTool === "select" ? "bg-[#02bc71] text-white shadow-lg shadow-green-200" : "text-gray-400 hover:bg-gray-100 hover:text-gray-600"}`,
160862
161336
  children: /* @__PURE__ */ jsx(PointerIcon, {})
160863
161337
  }
160864
161338
  ),
160865
161339
  /* @__PURE__ */ jsx(
160866
161340
  "button",
160867
161341
  {
160868
- onClick: () => setIsPanning(true),
161342
+ onClick: () => {
161343
+ setIsPanning(true);
161344
+ setDrawTool("select");
161345
+ },
160869
161346
  title: "Hand Mode",
160870
- className: `p-2 rounded-xl transition-all duration-200 ${isPanning ? "bg-[#02bc71] text-white shadow-lg shadow-blue-200" : "text-gray-400 hover:bg-gray-100 hover:text-gray-600"}`,
161347
+ className: `p-2 rounded-xl transition-all duration-200 cursor-pointer ${isPanning ? "bg-[#02bc71] text-white shadow-lg shadow-green-200" : "text-gray-400 hover:bg-gray-100 hover:text-gray-600"}`,
160871
161348
  children: /* @__PURE__ */ jsx(HandIcon, {})
160872
161349
  }
161350
+ ),
161351
+ /* @__PURE__ */ jsx(
161352
+ "button",
161353
+ {
161354
+ onClick: () => toggleGrid(),
161355
+ title: showGrid ? "Hide Grid (Ctrl+')" : "Show Grid (Ctrl+')",
161356
+ className: `p-2 rounded-xl transition-all duration-200 cursor-pointer ${showGrid ? "bg-[#02bc71] text-white shadow-lg shadow-green-200" : "text-gray-400 hover:bg-gray-100 hover:text-gray-600"}`,
161357
+ children: /* @__PURE__ */ jsx(GridIcon, {})
161358
+ }
160873
161359
  )
160874
161360
  ] }),
160875
161361
  /* @__PURE__ */ jsx("div", { className: "w-[1px] h-6 bg-gray-300/50 mx-3" }),