@pixldocs/canvas-renderer 0.5.225 → 0.5.226

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.
@@ -454,6 +454,148 @@ function fallbackEstimateHeight(element) {
454
454
  function clearMeasurementCache() {
455
455
  heightCache.clear();
456
456
  }
457
+ function computeAutoShrinkFontSize(element) {
458
+ const baseFontSize = element.fontSize || 16;
459
+ if (element.overflowPolicy !== "auto-shrink") return baseFontSize;
460
+ const text = element.text || element.content || "";
461
+ if (!text) return baseFontSize;
462
+ const width = element.width || 200;
463
+ const height = element.height;
464
+ if (!height) return baseFontSize;
465
+ let fontSize = baseFontSize;
466
+ try {
467
+ while (fontSize > 1) {
468
+ const testTb = new fabric__namespace.Textbox(text, {
469
+ width,
470
+ fontSize,
471
+ fontFamily: element.fontFamily || "Open Sans",
472
+ fontWeight: element.fontWeight || 400,
473
+ fontStyle: element.fontStyle || "normal",
474
+ lineHeight: element.lineHeight || 1.2,
475
+ charSpacing: element.charSpacing || 0,
476
+ splitByGrapheme: false
477
+ });
478
+ testTb.initDimensions();
479
+ const textHeight = testTb.height || 0;
480
+ const fitsHeight = textHeight <= height;
481
+ const lineWidths = testTb.__lineWidths;
482
+ const maxLineWidth = lineWidths && lineWidths.length > 0 ? Math.max(...lineWidths) : 0;
483
+ const fitsWidth = maxLineWidth <= width + 1;
484
+ if (fitsHeight && fitsWidth) break;
485
+ fontSize--;
486
+ }
487
+ } catch (e) {
488
+ console.warn("[autoShrink] Failed to compute shrunk font size:", e);
489
+ return baseFontSize;
490
+ }
491
+ return fontSize;
492
+ }
493
+ function measureTextGlyphHeightForStackHug(el) {
494
+ const text = el.text || " ";
495
+ const width = Math.max(1, typeof el.width === "number" ? el.width : 200);
496
+ let fontSize = el.fontSize || 16;
497
+ const lineHeight = el.lineHeight || 1.2;
498
+ const charSpacing = el.charSpacing || 0;
499
+ const fontFamily = el.fontFamily || "Open Sans";
500
+ const fontWeight = el.fontWeight || 400;
501
+ const fontStyle = el.fontStyle || "normal";
502
+ const isAutoShrink = el.overflowPolicy === "auto-shrink";
503
+ const splitByGrapheme = isAutoShrink ? false : el.splitByGrapheme ?? el.wordWrap === "break-word";
504
+ if (isAutoShrink) {
505
+ const minBoxH = Math.max(0, Number(el.minBoxHeight) || 0);
506
+ const heightBound = Math.max(typeof el.height === "number" ? el.height : 0, minBoxH);
507
+ while (fontSize > 1) {
508
+ const testTb = new fabric__namespace.Textbox(text, {
509
+ width,
510
+ fontSize,
511
+ fontFamily,
512
+ fontWeight,
513
+ fontStyle,
514
+ lineHeight,
515
+ charSpacing,
516
+ splitByGrapheme: false
517
+ });
518
+ testTb.initDimensions();
519
+ const textHeight = testTb.height || 0;
520
+ const lineWidths = getCanvasMeasuredTextboxLineWidths(testTb);
521
+ const maxLineWidth = lineWidths.length > 0 ? Math.max(...lineWidths) : 0;
522
+ if ((heightBound <= 0 || textHeight <= heightBound) && maxLineWidth <= width + 1) break;
523
+ fontSize--;
524
+ }
525
+ }
526
+ const textbox = new fabric__namespace.Textbox(text, {
527
+ width,
528
+ fontSize,
529
+ fontFamily,
530
+ fontWeight,
531
+ fontStyle,
532
+ lineHeight,
533
+ charSpacing,
534
+ splitByGrapheme
535
+ });
536
+ textbox.initDimensions();
537
+ return Math.max(1, (textbox.height || 20) * (el.scaleY || 1));
538
+ }
539
+ function getStackChildLayoutSize(parent, child, pageChildren, options) {
540
+ const b = getNodeBounds(child, pageChildren);
541
+ if (!parent.hugContent) return { width: b.width, height: b.height };
542
+ if (!isElement(child)) return { width: b.width, height: b.height };
543
+ const el = child;
544
+ if (el.type !== "text") return { width: b.width, height: b.height };
545
+ const isHorizontal = parent.layoutMode === "horizontal-stack";
546
+ if (isHorizontal) {
547
+ try {
548
+ const isAutoShrink = el.overflowPolicy === "auto-shrink";
549
+ const effectiveFontSize = isAutoShrink ? computeAutoShrinkFontSize(el) : el.fontSize || 16;
550
+ const probeWidth = isAutoShrink && typeof el.width === "number" ? el.width : 1e4;
551
+ const probe = new fabric__namespace.Textbox(el.text || " ", {
552
+ width: probeWidth,
553
+ fontSize: effectiveFontSize,
554
+ fontFamily: el.fontFamily || "Open Sans",
555
+ fontWeight: el.fontWeight || 400,
556
+ fontStyle: el.fontStyle || "normal",
557
+ lineHeight: el.lineHeight || 1.2,
558
+ charSpacing: el.charSpacing || 0,
559
+ splitByGrapheme: el.splitByGrapheme ?? el.wordWrap === "break-word"
560
+ });
561
+ probe.initDimensions();
562
+ const lineWidths = getCanvasMeasuredTextboxLineWidths(probe);
563
+ const maxLine = lineWidths.length > 0 ? Math.max(...lineWidths) : typeof el.width === "number" ? el.width : b.width;
564
+ const measuredW = Math.ceil(maxLine) + 2;
565
+ const w = Math.max(1, measuredW * (el.scaleX || 1));
566
+ return { width: w, height: b.height };
567
+ } catch {
568
+ return { width: b.width, height: b.height };
569
+ }
570
+ }
571
+ try {
572
+ const measuredH = measureTextGlyphHeightForStackHug(el);
573
+ const h = Math.max(1, measuredH);
574
+ return { width: b.width, height: h };
575
+ } catch {
576
+ return { width: b.width, height: b.height };
577
+ }
578
+ }
579
+ function getVerticalHugTextAdjust(parent, child) {
580
+ if (!parent.hugContent) return null;
581
+ if (parent.layoutMode !== "vertical-stack" && parent.layoutMode !== "stack") return null;
582
+ if (!isElement(child)) return null;
583
+ const el = child;
584
+ if (el.type !== "text") return null;
585
+ try {
586
+ const glyphHeight = measureTextGlyphHeightForStackHug(el);
587
+ const minBoxHeight = Math.max(0, Number(el.minBoxHeight) || 0);
588
+ const boxHeight = Math.max(typeof el.height === "number" ? el.height : glyphHeight, minBoxHeight);
589
+ const vAlign = el.verticalAlign ?? "top";
590
+ const extra = Math.max(0, boxHeight - glyphHeight);
591
+ let topOffset = 0;
592
+ if (vAlign === "middle" || vAlign === "center") topOffset = extra / 2;
593
+ else if (vAlign === "bottom") topOffset = extra;
594
+ return { topOffset, glyphHeight };
595
+ } catch {
596
+ return null;
597
+ }
598
+ }
457
599
  function simpleWidth(node) {
458
600
  if (isElement(node)) {
459
601
  const w = node.width;
@@ -494,23 +636,33 @@ function resolveStackGroupEffectivePositions(group, pageChildren, options) {
494
636
  const out = /* @__PURE__ */ new Map();
495
637
  const sizes = /* @__PURE__ */ new Map();
496
638
  for (const c of kids) {
497
- const b = getNodeBounds(c, pageChildren);
498
- sizes.set(c.id, { width: b.width, height: b.height });
639
+ sizes.set(c.id, getStackChildLayoutSize(group, c, pageChildren));
499
640
  }
500
641
  if (isVertical) {
501
642
  let prevBottom = padTop;
502
643
  let firstSeen = false;
644
+ const placedIds = [];
503
645
  for (let i = 0; i < kids.length; i++) {
504
646
  const child = kids[i];
505
647
  const storedTop = getNodeTop(child);
506
648
  const storedLeft = getNodeLeft(child);
507
649
  const mTop = child.marginTop ?? 0;
508
650
  const mLeft = child.marginLeft ?? 0;
509
- const effectiveTop = !firstSeen ? padTop + storedTop + mTop : prevBottom + gap + storedTop + mTop;
651
+ const hugAdjust = getVerticalHugTextAdjust(group, child);
652
+ const hugTopOffset = hugAdjust ? hugAdjust.topOffset : 0;
653
+ const visualTop = !firstSeen ? padTop + storedTop + mTop : prevBottom + gap + storedTop + mTop;
654
+ const effectiveTop = firstSeen ? visualTop : visualTop - hugTopOffset;
655
+ if (firstSeen && hugTopOffset > 0 && placedIds.length > 0) {
656
+ for (const placedId of placedIds) {
657
+ const placed = out.get(placedId);
658
+ if (placed) out.set(placedId, { ...placed, top: placed.top + hugTopOffset });
659
+ }
660
+ }
510
661
  firstSeen = true;
511
662
  out.set(child.id, { top: effectiveTop, left: padLeft + storedLeft + mLeft });
512
- const h = sizes.get(child.id).height;
513
- prevBottom = effectiveTop + h + (child.marginBottom ?? 0);
663
+ const h = hugAdjust ? hugAdjust.glyphHeight : sizes.get(child.id).height;
664
+ prevBottom = effectiveTop + hugTopOffset + h + (child.marginBottom ?? 0);
665
+ placedIds.push(child.id);
514
666
  }
515
667
  } else {
516
668
  let prevRight = padLeft;
@@ -531,7 +683,20 @@ function resolveStackGroupEffectivePositions(group, pageChildren, options) {
531
683
  const containerW = typeof group.width === "number" ? group.width : void 0;
532
684
  const containerH = typeof group.height === "number" ? group.height : void 0;
533
685
  const mainContainer = isVertical ? containerH : containerW;
534
- const crossContainer = isVertical ? containerW : containerH;
686
+ let crossContainer = isVertical ? containerW : containerH;
687
+ if (kids.length > 0) {
688
+ let maxCross = 0;
689
+ for (const c of kids) {
690
+ const sz = sizes.get(c.id);
691
+ if (!sz) continue;
692
+ const cross = isVertical ? sz.width : sz.height;
693
+ if (cross > maxCross) maxCross = cross;
694
+ }
695
+ const crossPad0 = isVertical ? padLeft : padTop;
696
+ const crossPadEnd = isVertical ? padRight : padBottom;
697
+ const computed = maxCross + crossPad0 + crossPadEnd;
698
+ crossContainer = crossContainer != null ? Math.max(crossContainer, computed) : computed;
699
+ }
535
700
  if (align !== "start" && crossContainer != null && kids.length > 0) {
536
701
  const crossPad0 = isVertical ? padLeft : padTop;
537
702
  const crossPadEnd = isVertical ? padRight : padBottom;
@@ -609,7 +774,8 @@ function resolveStackGroupEffectivePositions(group, pageChildren, options) {
609
774
  if (!pos) continue;
610
775
  const startMain = cursor + marginStart;
611
776
  if (isVertical) {
612
- out.set(child.id, { top: startMain, left: pos.left });
777
+ const hugAdjust = getVerticalHugTextAdjust(group, child);
778
+ out.set(child.id, { top: startMain - ((hugAdjust == null ? void 0 : hugAdjust.topOffset) ?? 0), left: pos.left });
613
779
  } else {
614
780
  out.set(child.id, { top: pos.top, left: startMain });
615
781
  }
@@ -631,13 +797,16 @@ function groupBoundsFromChildren(group, pageChildren, options) {
631
797
  const positions = isStack ? resolveStackGroupEffectivePositions(group, pageChildren) : null;
632
798
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
633
799
  for (const child of kids) {
634
- const b = getNodeBounds(child, pageChildren);
800
+ const sz = isStack ? getStackChildLayoutSize(group, child, pageChildren) : (() => {
801
+ const b = getNodeBounds(child, pageChildren);
802
+ return { width: b.width, height: b.height };
803
+ })();
635
804
  const cl = positions ? ((_a2 = positions.get(child.id)) == null ? void 0 : _a2.left) ?? getNodeLeft(child) : getNodeLeft(child);
636
805
  const ct = positions ? ((_b = positions.get(child.id)) == null ? void 0 : _b.top) ?? getNodeTop(child) : getNodeTop(child);
637
806
  minX = Math.min(minX, cl);
638
807
  minY = Math.min(minY, ct);
639
- maxX = Math.max(maxX, cl + b.width);
640
- maxY = Math.max(maxY, ct + b.height);
808
+ maxX = Math.max(maxX, cl + sz.width);
809
+ maxY = Math.max(maxY, ct + sz.height);
641
810
  }
642
811
  if (isStack) {
643
812
  const padRight = group.paddingRight ?? 0;
@@ -1058,6 +1227,7 @@ const useEditorStore = zustand.create((set, get) => ({
1058
1227
  showDynamicLabels: false,
1059
1228
  showSections: false,
1060
1229
  hoveredGroupId: null,
1230
+ revealedLayerId: null,
1061
1231
  history: [cloneCanvas(initialCanvas)],
1062
1232
  historyIndex: 0,
1063
1233
  lastCommittedSignature: canvasSignature(initialCanvas),
@@ -1597,7 +1767,16 @@ const useEditorStore = zustand.create((set, get) => ({
1597
1767
  let nextChildren = currentPage.children;
1598
1768
  const newNodes = [];
1599
1769
  const duplicateAtPageLevel = [];
1600
- for (const id of ids) {
1770
+ const idSet = new Set(ids);
1771
+ const filteredIds = ids.filter((id) => {
1772
+ let p = findParentGroup(currentPage.children, id);
1773
+ while (p) {
1774
+ if (idSet.has(p.id)) return false;
1775
+ p = findParentGroup(currentPage.children, p.id);
1776
+ }
1777
+ return true;
1778
+ });
1779
+ for (const id of filteredIds) {
1601
1780
  const node = findNodeById(nextChildren, id);
1602
1781
  if (!node) continue;
1603
1782
  const parent = findParentGroup(nextChildren, id);
@@ -1614,19 +1793,9 @@ const useEditorStore = zustand.create((set, get) => ({
1614
1793
  }
1615
1794
  const pageLevelIds = new Set(duplicateAtPageLevel);
1616
1795
  if (pageLevelIds.size > 0) {
1617
- const PAGE_MARGIN = 48;
1618
- const PAGE_GAP = 16;
1619
- let maxBottom = PAGE_MARGIN;
1620
- for (const node of currentPage.children) {
1621
- const b = getNodeBounds(node, currentPage.children);
1622
- if (b.bottom > maxBottom) maxBottom = b.bottom;
1623
- }
1624
- const pageLevelClones = newNodes.filter((n) => pageLevelIds.has(n.id));
1625
- const bbox = getBoundingBoxOfRoots(pageLevelClones);
1626
- const shiftX = PAGE_MARGIN - bbox.left;
1627
- const shiftY = maxBottom + PAGE_GAP - bbox.top;
1796
+ const DUPLICATE_NUDGE = 16;
1628
1797
  nextChildren = nextChildren.map(
1629
- (node) => pageLevelIds.has(node.id) ? shiftNodeBy(node, shiftX, shiftY) : node
1798
+ (node) => pageLevelIds.has(node.id) ? shiftNodeBy(node, DUPLICATE_NUDGE, DUPLICATE_NUDGE) : node
1630
1799
  );
1631
1800
  }
1632
1801
  let nextCanvas = updateCurrentPageChildren(state.canvas, () => nextChildren);
@@ -2043,12 +2212,19 @@ const useEditorStore = zustand.create((set, get) => ({
2043
2212
  if (firstNodeIndex >= 0) {
2044
2213
  insertIndex = firstNodeIndex;
2045
2214
  }
2215
+ } else if (!commonParent && topLevelIds.length > 0) {
2216
+ const firstNodeIndex = currentPage.children.findIndex((child) => child.id === topLevelIds[0]);
2217
+ if (firstNodeIndex >= 0) {
2218
+ insertIndex = firstNodeIndex;
2219
+ }
2046
2220
  }
2047
- const pageChildrenForBounds = currentPage.children;
2221
+ const orderSource = commonParent ? commonParent.children : currentPage.children;
2222
+ const orderIndex = /* @__PURE__ */ new Map();
2223
+ orderSource.forEach((n, i) => orderIndex.set(n.id, i));
2048
2224
  nodesToGroup.sort((a, b) => {
2049
- const aTop = getAbsoluteBounds(a, pageChildrenForBounds).top ?? 0;
2050
- const bTop = getAbsoluteBounds(b, pageChildrenForBounds).top ?? 0;
2051
- return aTop - bTop;
2225
+ const ai = orderIndex.has(a.id) ? orderIndex.get(a.id) : Number.MAX_SAFE_INTEGER;
2226
+ const bi = orderIndex.has(b.id) ? orderIndex.get(b.id) : Number.MAX_SAFE_INTEGER;
2227
+ return ai - bi;
2052
2228
  });
2053
2229
  const pageChildren = currentPage.children;
2054
2230
  let groupLeft = Infinity, groupTop = Infinity;
@@ -2139,8 +2315,7 @@ const useEditorStore = zustand.create((set, get) => ({
2139
2315
  const dt = target.top - cur.top;
2140
2316
  return updateNodeInTree(t, nodeId, { left: dl, top: dt });
2141
2317
  };
2142
- const insertReversed = parentId === null;
2143
- const promotionOrder = insertReversed ? group.children.map((_, i) => group.children.length - 1 - i) : group.children.map((_, i) => i);
2318
+ const promotionOrder = group.children.map((_, i) => i);
2144
2319
  for (let slot = 0; slot < promotionOrder.length; slot++) {
2145
2320
  const i = promotionOrder[slot];
2146
2321
  const child = group.children[i];
@@ -2184,6 +2359,18 @@ const useEditorStore = zustand.create((set, get) => ({
2184
2359
  }
2185
2360
  return { collapsedGroups: newCollapsedSet };
2186
2361
  }),
2362
+ revealLayerNode: (id) => set((state) => {
2363
+ if (!id) return { revealedLayerId: null };
2364
+ const currentPage = getCurrentPageFromCanvas(state.canvas);
2365
+ const next = new Set(state.collapsedGroups);
2366
+ let parent = findParentGroup(currentPage.children, id);
2367
+ let guard = 0;
2368
+ while (parent && guard++ < 32) {
2369
+ next.delete(parent.id);
2370
+ parent = findParentGroup(currentPage.children, parent.id);
2371
+ }
2372
+ return { collapsedGroups: next, revealedLayerId: id };
2373
+ }),
2187
2374
  // Convenience aliases
2188
2375
  groupElements: (ids, name) => get().groupNodes(ids, name),
2189
2376
  ungroupElements: (groupId) => get().ungroupNodes(groupId),
@@ -2630,7 +2817,7 @@ const useEditorStore = zustand.create((set, get) => ({
2630
2817
  boundFormDefId: config.boundFormDefId,
2631
2818
  boundFormDefName: config.boundFormDefName,
2632
2819
  themeConfig: config.themeConfig ?? void 0,
2633
- pdfTextMode: config.pdfTextMode ?? "selectable"
2820
+ pdfTextMode: config.pdfTextMode ?? "auto"
2634
2821
  };
2635
2822
  const committed = commitFromState(state, nextCanvas);
2636
2823
  const out = {
@@ -4236,6 +4423,9 @@ async function loadSvgAsGroup(url) {
4236
4423
  obj.fill = "#000";
4237
4424
  obj.stroke = null;
4238
4425
  obj.strokeWidth = 0;
4426
+ obj.objectCaching = false;
4427
+ obj.statefullCache = false;
4428
+ obj.noScaleCache = true;
4239
4429
  }
4240
4430
  const group = new fabric__namespace.Group(objects, {
4241
4431
  originX: "center",
@@ -4243,8 +4433,14 @@ async function loadSvgAsGroup(url) {
4243
4433
  selectable: false,
4244
4434
  evented: false,
4245
4435
  hasControls: false,
4246
- hasBorders: false
4436
+ hasBorders: false,
4437
+ // Do NOT cache the mask group — Fabric would otherwise bake it to a
4438
+ // bitmap at the group's natural size, so the clipPath edges look soft
4439
+ // / pixelated whenever the host image is scaled up on the canvas.
4440
+ objectCaching: false
4247
4441
  });
4442
+ group.statefullCache = false;
4443
+ group.noScaleCache = true;
4248
4444
  const viewBoxW = Number(options.width) || group.width || 1;
4249
4445
  const viewBoxH = Number(options.height) || group.height || 1;
4250
4446
  return { group, viewBoxW, viewBoxH };
@@ -4262,8 +4458,18 @@ function fitMaskGroupToFrame(maskGroup, frameW, frameH) {
4262
4458
  selectable: false,
4263
4459
  evented: false,
4264
4460
  hasControls: false,
4265
- hasBorders: false
4461
+ hasBorders: false,
4462
+ objectCaching: false
4266
4463
  });
4464
+ const stack = [maskGroup];
4465
+ while (stack.length) {
4466
+ const node = stack.pop();
4467
+ node.objectCaching = false;
4468
+ node.statefullCache = false;
4469
+ node.noScaleCache = true;
4470
+ const kids = node._objects;
4471
+ if (kids && kids.length) stack.push(...kids);
4472
+ }
4267
4473
  maskGroup.absolutePositioned = false;
4268
4474
  maskGroup.excludeFromExport = true;
4269
4475
  maskGroup.inverted = false;
@@ -4386,7 +4592,7 @@ async function buildLuminanceAlphaCanvas(svgUrl, frameW, frameH) {
4386
4592
  const b = px[i + 2];
4387
4593
  const a = px[i + 3];
4388
4594
  const lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
4389
- const alpha = lum / 255 * a;
4595
+ const alpha = (255 - lum) / 255 * a;
4390
4596
  px[i] = 255;
4391
4597
  px[i + 1] = 255;
4392
4598
  px[i + 2] = 255;
@@ -5950,10 +6156,6 @@ const shouldShowOriginalTextBounds = () => {
5950
6156
  return false;
5951
6157
  }
5952
6158
  };
5953
- const hasActiveTextPathDescendant = (obj) => {
5954
- const kids = (obj == null ? void 0 : obj._objects) || [];
5955
- return kids.some((child) => hasActiveTextPath(child) || hasActiveTextPathDescendant(child));
5956
- };
5957
6159
  function applyWarpFillStyle(ctx, obj) {
5958
6160
  const filler = obj.fill;
5959
6161
  if (filler && typeof filler === "object" && Array.isArray(filler.colorStops) && filler.coords) {
@@ -6146,6 +6348,79 @@ function applyTextPathControls(textbox) {
6146
6348
  obj.controls = { ...defaultControls };
6147
6349
  const halfWOf = (t) => (t.width || 0) / 2;
6148
6350
  const halfHOf = (t) => (t.height || 0) / 2;
6351
+ {
6352
+ const cornerLayout = {
6353
+ tl: { sx: -1, sy: -1 },
6354
+ tr: { sx: 1, sy: -1 },
6355
+ br: { sx: 1, sy: 1 },
6356
+ bl: { sx: -1, sy: 1 },
6357
+ mt: { sx: 0, sy: -1 },
6358
+ mb: { sx: 0, sy: 1 },
6359
+ ml: { sx: -1, sy: 0 },
6360
+ mr: { sx: 1, sy: 0 }
6361
+ };
6362
+ for (const key of Object.keys(cornerLayout)) {
6363
+ const existing = obj.controls[key];
6364
+ if (!existing) continue;
6365
+ const { sx, sy } = cornerLayout[key];
6366
+ const customPosition = (_d2, finalMatrix, target) => {
6367
+ const t = target;
6368
+ const bounds = getTextPathHitBounds(t);
6369
+ if (!bounds) {
6370
+ return existing.positionHandler.call(existing, _d2, finalMatrix, target);
6371
+ }
6372
+ const cx = (bounds.minX + bounds.maxX) / 2;
6373
+ const cy = (bounds.minY + bounds.maxY) / 2;
6374
+ const x = sx < 0 ? bounds.minX : sx > 0 ? bounds.maxX : cx;
6375
+ const y = sy < 0 ? bounds.minY : sy > 0 ? bounds.maxY : cy;
6376
+ return scaleLocalToScreen(t, new fabric__namespace.Point(x, y)).transform(finalMatrix);
6377
+ };
6378
+ obj.controls[key] = new fabric__namespace.Control({
6379
+ x: existing.x,
6380
+ y: existing.y,
6381
+ offsetX: existing.offsetX,
6382
+ offsetY: existing.offsetY,
6383
+ cursorStyle: existing.cursorStyle,
6384
+ cursorStyleHandler: existing.cursorStyleHandler,
6385
+ actionName: existing.actionName,
6386
+ actionHandler: existing.actionHandler,
6387
+ mouseDownHandler: existing.mouseDownHandler,
6388
+ mouseUpHandler: existing.mouseUpHandler,
6389
+ getActionName: existing.getActionName,
6390
+ render: existing.render,
6391
+ sizeX: existing.sizeX,
6392
+ sizeY: existing.sizeY,
6393
+ touchSizeX: existing.touchSizeX,
6394
+ touchSizeY: existing.touchSizeY,
6395
+ withConnection: existing.withConnection,
6396
+ positionHandler: customPosition
6397
+ });
6398
+ }
6399
+ const mtrExisting = obj.controls.mtr;
6400
+ if (mtrExisting) {
6401
+ const customMtrPosition = (_d2, finalMatrix, target) => {
6402
+ const t = target;
6403
+ const bounds = getTextPathHitBounds(t);
6404
+ if (!bounds) return mtrExisting.positionHandler.call(mtrExisting, _d2, finalMatrix, target);
6405
+ const cx = (bounds.minX + bounds.maxX) / 2;
6406
+ const offset = mtrExisting.offsetY ?? -40;
6407
+ return scaleLocalToScreen(t, new fabric__namespace.Point(cx, bounds.minY)).transform(finalMatrix).add(new fabric__namespace.Point(0, offset));
6408
+ };
6409
+ obj.controls.mtr = new fabric__namespace.Control({
6410
+ x: mtrExisting.x,
6411
+ y: mtrExisting.y,
6412
+ offsetX: mtrExisting.offsetX,
6413
+ offsetY: mtrExisting.offsetY,
6414
+ cursorStyle: mtrExisting.cursorStyle,
6415
+ cursorStyleHandler: mtrExisting.cursorStyleHandler,
6416
+ actionName: mtrExisting.actionName,
6417
+ actionHandler: mtrExisting.actionHandler,
6418
+ render: mtrExisting.render,
6419
+ withConnection: mtrExisting.withConnection,
6420
+ positionHandler: customMtrPosition
6421
+ });
6422
+ }
6423
+ }
6149
6424
  const renderPivot = (ctx, left, top) => {
6150
6425
  ctx.save();
6151
6426
  ctx.translate(left, top);
@@ -6767,10 +7042,11 @@ if (typeof TextboxProto._renderControls === "function" && !TextboxProto.__pixldo
6767
7042
  if (!hostCanvas) return;
6768
7043
  const hoverBounds = getTextPathHitBounds(this);
6769
7044
  const active = (_d = hostCanvas.getActiveObject) == null ? void 0 : _d.call(hostCanvas);
6770
- const isActiveOrInActive = active === this || !!active && typeof active.contains === "function" && active.contains(this, true);
6771
- if (hoverBounds && (this.__pdTextPathHovered || isActiveOrInActive)) {
7045
+ const isDirectlyActive = active === this;
7046
+ if (hoverBounds && (this.__pdTextPathHovered || isDirectlyActive)) {
6772
7047
  drawTextPathBounds(ctx, this, hoverBounds, hostCanvas);
6773
7048
  }
7049
+ if (!isDirectlyActive) return;
6774
7050
  const resolved = resolveTextPath(this.textPath, this.width || 0, this.fontSize || 16);
6775
7051
  const path = resolved ? measurePath(resolved.d) : null;
6776
7052
  if (!path) return;
@@ -6884,13 +7160,6 @@ if (typeof TextboxProto._renderControls === "function" && !TextboxProto.__pixldo
6884
7160
  if (!showOrig) {
6885
7161
  this.hasBorders = false;
6886
7162
  this.borderColor = "rgba(0,0,0,0)";
6887
- const filtered = {};
6888
- for (const key of Object.keys(prevControls || {})) {
6889
- if (key === "mtr" || key === "tpPivot" || key.startsWith("cr") || key.startsWith("rs") || key.startsWith("bz")) {
6890
- filtered[key] = prevControls[key];
6891
- }
6892
- }
6893
- this.controls = filtered;
6894
7163
  }
6895
7164
  try {
6896
7165
  drawWarpGuides.call(this, ctx);
@@ -7001,26 +7270,7 @@ if (GroupProto && typeof GroupProto._renderControls === "function" && !GroupProt
7001
7270
  const host = this.canvas;
7002
7271
  const active = (_a2 = host == null ? void 0 : host.getActiveObject) == null ? void 0 : _a2.call(host);
7003
7272
  const isActiveOrInActive = !!host && (active === this || !!active && typeof active.contains === "function" && active.contains(this, true));
7004
- const hideWarpChildBounds = isActiveOrInActive && !shouldShowOriginalTextBounds() && hasActiveTextPathDescendant(this);
7005
- const prevBorders = this.hasBorders;
7006
- const prevControls = this.hasControls;
7007
- const prevBorderColor = this.borderColor;
7008
- if (hideWarpChildBounds) {
7009
- this.hasBorders = false;
7010
- this.hasControls = false;
7011
- this.borderColor = "rgba(0,0,0,0)";
7012
- styleOverride = { ...styleOverride || {}, hasBorders: false, hasControls: false, borderColor: "rgba(0,0,0,0)" };
7013
- childrenOverride = { ...childrenOverride || {}, hasBorders: false, borderColor: "rgba(0,0,0,0)" };
7014
- }
7015
- try {
7016
- GroupProto.__pixldocsOrigRenderControls.call(this, ctx, styleOverride, childrenOverride);
7017
- } finally {
7018
- if (hideWarpChildBounds) {
7019
- this.hasBorders = prevBorders;
7020
- this.hasControls = prevControls;
7021
- this.borderColor = prevBorderColor;
7022
- }
7023
- }
7273
+ GroupProto.__pixldocsOrigRenderControls.call(this, ctx, styleOverride, childrenOverride);
7024
7274
  if (!host || !isActiveOrInActive) return;
7025
7275
  const drawForDescendants = (parent) => {
7026
7276
  const kids = (parent == null ? void 0 : parent._objects) || [];
@@ -7045,17 +7295,12 @@ if (!TextboxProtoHit.__pixldocsOrigGetCoords && typeof TextboxProtoHit.getCoords
7045
7295
  const bounds = getTextPathHitBounds(this);
7046
7296
  if (!bounds) return TextboxProtoHit.__pixldocsOrigGetCoords.call(this);
7047
7297
  const matrix = this.calcTransformMatrix();
7048
- const coords = [
7298
+ return [
7049
7299
  new fabric__namespace.Point(bounds.minX, bounds.minY),
7050
7300
  new fabric__namespace.Point(bounds.maxX, bounds.minY),
7051
7301
  new fabric__namespace.Point(bounds.maxX, bounds.maxY),
7052
7302
  new fabric__namespace.Point(bounds.minX, bounds.maxY)
7053
7303
  ].map((p) => fabric__namespace.util.transformPoint(p, matrix));
7054
- if (this.group) {
7055
- const groupMatrix = this.group.calcTransformMatrix();
7056
- return coords.map((p) => fabric__namespace.util.transformPoint(p, groupMatrix));
7057
- }
7058
- return coords;
7059
7304
  };
7060
7305
  }
7061
7306
  if (!TextboxProtoHit.__pixldocsOrigContainsPoint && typeof TextboxProtoHit.containsPoint === "function") {
@@ -8564,7 +8809,7 @@ function createShape(element) {
8564
8809
  }
8565
8810
  }
8566
8811
  function createText(element) {
8567
- var _a2, _b, _c;
8812
+ var _a2, _b, _c, _d, _e;
8568
8813
  const overflowPolicy = element.overflowPolicy || "grow-and-push";
8569
8814
  let text = element.text || "Text";
8570
8815
  let fontSize = element.fontSize || 16;
@@ -8712,6 +8957,23 @@ function createText(element) {
8712
8957
  objectCaching: false,
8713
8958
  noScaleCache: true,
8714
8959
  splitByGrapheme,
8960
+ // Outline (stroke) support for text — kept in sync with PageCanvas
8961
+ // sync path. Only apply when BOTH a stroke color and positive width are
8962
+ // explicitly set; otherwise fabric defaults stroke to black and renders
8963
+ // an unwanted outline on existing text elements that carry a stray
8964
+ // strokeWidth (e.g. from PDF imports).
8965
+ ...(() => {
8966
+ const s = element.stroke;
8967
+ const w = Number(element.strokeWidth) || 0;
8968
+ const has = !!s && w > 0;
8969
+ return has ? {
8970
+ stroke: s,
8971
+ strokeWidth: w,
8972
+ paintFirst: element.paintFirst || "fill",
8973
+ strokeUniform: true,
8974
+ strokeLineJoin: "round"
8975
+ } : { stroke: void 0, strokeWidth: 0 };
8976
+ })(),
8715
8977
  // When inline markdown formatting is enabled, the displayed text is the
8716
8978
  // PARSED plain text (markdown source lives separately on the element).
8717
8979
  // Allowing canvas inline editing would let the user edit that plain text
@@ -8761,6 +9023,21 @@ function createText(element) {
8761
9023
  });
8762
9024
  } catch {
8763
9025
  }
9026
+ try {
9027
+ const baseCtrls = (_d = (_c = fabric__namespace.Object) == null ? void 0 : _c.prototype) == null ? void 0 : _d.controls;
9028
+ if (baseCtrls) {
9029
+ textbox.controls = {
9030
+ ...textbox.controls || {},
9031
+ tl: baseCtrls.tl,
9032
+ tr: baseCtrls.tr,
9033
+ bl: baseCtrls.bl,
9034
+ br: baseCtrls.br,
9035
+ mt: baseCtrls.mt,
9036
+ mb: baseCtrls.mb
9037
+ };
9038
+ }
9039
+ } catch {
9040
+ }
8764
9041
  const scaleXAfterSet = textbox.scaleX ?? 1;
8765
9042
  const scaleYAfterSet = textbox.scaleY ?? 1;
8766
9043
  if (Math.abs(widthAfterSet - targetWidth) > 0.01 || Math.abs(scaleXAfterSet - targetScaleX) > 0.01 || Math.abs(scaleYAfterSet - targetScaleY) > 0.01) {
@@ -8791,7 +9068,7 @@ function createText(element) {
8791
9068
  finalFontSize: fontSize,
8792
9069
  textboxWidth: textbox.width,
8793
9070
  textboxHeight: textbox.height,
8794
- lineCount: ((_c = textbox.textLines) == null ? void 0 : _c.length) || 0,
9071
+ lineCount: ((_e = textbox.textLines) == null ? void 0 : _e.length) || 0,
8795
9072
  lines: (textbox.textLines || []).map((line) => Array.isArray(line) ? line.join("") : String(line ?? "")),
8796
9073
  widthMetrics: getTextboxWidthFitMetrics(textbox, targetWidth)
8797
9074
  }));
@@ -9350,16 +9627,7 @@ function renderSmartElementToDataUri(type, props, width, height) {
9350
9627
  if (!svg) return null;
9351
9628
  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
9352
9629
  }
9353
- const KEY_SHOW_ORIG_TEXT_BOUNDS = "pixldocs:showOriginalTextBounds";
9354
9630
  const EVT_SHOW_ORIG_TEXT_BOUNDS = "pixldocs:showOriginalTextBoundsChanged";
9355
- function getShowOriginalTextBounds() {
9356
- if (typeof window === "undefined") return false;
9357
- try {
9358
- return window.localStorage.getItem(KEY_SHOW_ORIG_TEXT_BOUNDS) === "1";
9359
- } catch {
9360
- return false;
9361
- }
9362
- }
9363
9631
  function subscribeShowOriginalTextBounds(cb) {
9364
9632
  const handler = (e) => cb(!!e.detail);
9365
9633
  window.addEventListener(EVT_SHOW_ORIG_TEXT_BOUNDS, handler);
@@ -9513,31 +9781,12 @@ try {
9513
9781
  console.warn("[PageCanvas] Failed to apply global selection defaults:", e);
9514
9782
  }
9515
9783
  function applyWarpAwareSelectionBorders(selection) {
9516
- if (getShowOriginalTextBounds()) {
9517
- if (selection.__pixldocsOrigASHasBorders !== void 0) {
9518
- selection.hasBorders = selection.__pixldocsOrigASHasBorders;
9519
- }
9520
- return;
9521
- }
9522
- const hasWarpedTextObject = (obj) => {
9523
- if (obj instanceof fabric__namespace.Textbox) {
9524
- const tp = obj.textPath;
9525
- return !!(tp && tp.preset && tp.preset !== "none");
9526
- }
9527
- const children = obj._objects;
9528
- return Array.isArray(children) && children.some((child) => hasWarpedTextObject(child));
9529
- };
9530
- const hasWarpedText = selection.getObjects().some((obj) => {
9531
- if (!hasWarpedTextObject(obj)) return false;
9532
- const tp = obj.textPath;
9533
- return !tp || tp.preset !== "none";
9534
- });
9535
- if (hasWarpedText) {
9536
- if (selection.__pixldocsOrigASHasBorders === void 0) {
9537
- selection.__pixldocsOrigASHasBorders = selection.hasBorders;
9538
- }
9539
- selection.hasBorders = false;
9784
+ if (selection.__pixldocsOrigASHasBorders !== void 0) {
9785
+ selection.hasBorders = selection.__pixldocsOrigASHasBorders;
9786
+ delete selection.__pixldocsOrigASHasBorders;
9540
9787
  }
9788
+ selection.hasBorders = true;
9789
+ selection.hasControls = true;
9541
9790
  }
9542
9791
  const PageCanvas = react.forwardRef(
9543
9792
  ({
@@ -9603,7 +9852,46 @@ const PageCanvas = react.forwardRef(
9603
9852
  const [sizeLabel, setSizeLabel] = react.useState(null);
9604
9853
  const [ready, setReady] = react.useState(false);
9605
9854
  const [unlockRequestId, setUnlockRequestId] = react.useState(0);
9606
- react.useMemo(
9855
+ const applyLogicalGroupSelectionVisualState = react.useCallback((selection, groupId) => {
9856
+ var _a2;
9857
+ selection.__pixldocsGroupSelection = groupId;
9858
+ delete selection.__pixldocsLogicalGroupIds;
9859
+ selection.hasBorders = true;
9860
+ const members = selection.getObjects();
9861
+ for (const prev of suppressGroupMemberBordersRef.current) {
9862
+ if (members.includes(prev)) continue;
9863
+ const origBorders = prev.__pixldocsOrigHasBorders;
9864
+ const origControls = prev.__pixldocsOrigHasControls;
9865
+ const origLockX = prev.__pixldocsOrigLockScalingX;
9866
+ if (origBorders !== void 0) prev.hasBorders = origBorders;
9867
+ if (origControls !== void 0) prev.hasControls = origControls;
9868
+ if (origLockX !== void 0) {
9869
+ prev.lockScalingX = origLockX;
9870
+ prev.lockScalingY = prev.__pixldocsOrigLockScalingY;
9871
+ }
9872
+ delete prev.__pixldocsOrigHasBorders;
9873
+ delete prev.__pixldocsOrigHasControls;
9874
+ delete prev.__pixldocsOrigLockScalingX;
9875
+ delete prev.__pixldocsOrigLockScalingY;
9876
+ }
9877
+ suppressGroupMemberBordersRef.current = members;
9878
+ for (const m of members) {
9879
+ if (m.__pixldocsOrigHasBorders === void 0) m.__pixldocsOrigHasBorders = m.hasBorders;
9880
+ if (m.__pixldocsOrigHasControls === void 0) m.__pixldocsOrigHasControls = m.hasControls;
9881
+ m.hasBorders = false;
9882
+ m.hasControls = false;
9883
+ if (m.__cropGroup || ((_a2 = m._ct) == null ? void 0 : _a2.isCropGroup)) {
9884
+ if (m.__pixldocsOrigLockScalingX === void 0) {
9885
+ m.__pixldocsOrigLockScalingX = m.lockScalingX;
9886
+ m.__pixldocsOrigLockScalingY = m.lockScalingY;
9887
+ }
9888
+ m.lockScalingX = false;
9889
+ m.lockScalingY = false;
9890
+ }
9891
+ }
9892
+ applyWarpAwareSelectionBorders(selection);
9893
+ }, []);
9894
+ const pageBoundsOptions = react.useMemo(
9607
9895
  () => ({ pageContentWidth: canvasWidth, pageContentHeight: canvasHeight }),
9608
9896
  [canvasWidth, canvasHeight]
9609
9897
  );
@@ -9613,7 +9901,9 @@ const PageCanvas = react.forwardRef(
9613
9901
  const setGroupOverlayLiveBoundsRef = react.useRef(setGroupOverlayLiveBounds);
9614
9902
  const skipSelectionClearOnDiscardRef = react.useRef(false);
9615
9903
  const skipActiveSelectionBakeOnClearRef = react.useRef(false);
9904
+ const preserveEditingScopeOnSelectionClearRef = react.useRef(false);
9616
9905
  const preserveActiveSelectionAfterTransformRef = react.useRef(null);
9906
+ const pendingGroupPromotionRef = react.useRef(null);
9617
9907
  const imageReloadRequestSeqRef = react.useRef(/* @__PURE__ */ new Map());
9618
9908
  react.useRef(null);
9619
9909
  const groupBoundsResizingRef = react.useRef(false);
@@ -9633,6 +9923,7 @@ const PageCanvas = react.forwardRef(
9633
9923
  const lastResizeScaleTargetRef = react.useRef(null);
9634
9924
  const preserveSelectionAfterTransformIdRef = react.useRef(null);
9635
9925
  const groupSelectionTransformStartRef = react.useRef(null);
9926
+ const activeSelectionMoveStartRef = react.useRef(null);
9636
9927
  setGroupOverlayLiveBoundsRef.current = setGroupOverlayLiveBounds;
9637
9928
  const {
9638
9929
  selectElements,
@@ -9653,7 +9944,11 @@ const PageCanvas = react.forwardRef(
9653
9944
  if (!currentPage || ids.length === 0) return null;
9654
9945
  for (const id of ids) {
9655
9946
  const node = findNodeById(children, id);
9656
- if (node && isGroup(node)) return node;
9947
+ if (node && isGroup(node)) {
9948
+ const memberIds2 = new Set(getAllElementIds(node.children ?? []));
9949
+ const pureGroupOnly = ids.every((selectedId) => selectedId === node.id || memberIds2.has(selectedId));
9950
+ return pureGroupOnly ? node : null;
9951
+ }
9657
9952
  }
9658
9953
  const firstId = ids[0];
9659
9954
  const parent = findParentGroup(children, firstId);
@@ -10007,6 +10302,16 @@ const PageCanvas = react.forwardRef(
10007
10302
  fabricCanvas.__isUserTransforming = false;
10008
10303
  fabricCanvas.on("mouse:down", () => {
10009
10304
  groupSelectionTransformStartRef.current = null;
10305
+ activeSelectionMoveStartRef.current = null;
10306
+ const active = fabricCanvas.getActiveObject();
10307
+ if (active instanceof fabric__namespace.ActiveSelection) {
10308
+ const rect = active.getBoundingRect();
10309
+ activeSelectionMoveStartRef.current = {
10310
+ selection: active,
10311
+ selectionLeft: rect.left,
10312
+ selectionTop: rect.top
10313
+ };
10314
+ }
10010
10315
  if (fabricCanvas._currentTransform) {
10011
10316
  fabricCanvas.__isUserTransforming = true;
10012
10317
  }
@@ -10059,8 +10364,21 @@ const PageCanvas = react.forwardRef(
10059
10364
  didTransformRef.current = true;
10060
10365
  });
10061
10366
  const syncSelectionToStore = () => {
10062
- var _a2, _b, _c, _d, _e, _f, _g;
10367
+ var _a2, _b, _c, _d;
10063
10368
  if (!isActiveRef.current || isRebuildingRef.current || isSyncingSelectionToFabricRef.current || !allowSelection) return;
10369
+ const walkToTopmostGroup = (childId, children, activeEditingGroupId) => {
10370
+ let topmost = null;
10371
+ let currentId = childId;
10372
+ for (let i = 0; i < 32; i++) {
10373
+ const p = findParentGroup(children, currentId);
10374
+ if (!p) break;
10375
+ if (p.backgroundColor) break;
10376
+ if (activeEditingGroupId && p.id === activeEditingGroupId) break;
10377
+ topmost = p;
10378
+ currentId = p.id;
10379
+ }
10380
+ return topmost;
10381
+ };
10064
10382
  const active = fabricCanvas.getActiveObject();
10065
10383
  let ids = fabricCanvas.getActiveObjects().map((o) => getObjectId(o)).filter((id) => !!id && id !== "__background__");
10066
10384
  if (ids.length === 1 && active && active instanceof fabric__namespace.Group && active.__docuforgeSectionGroup) {
@@ -10087,8 +10405,8 @@ const PageCanvas = react.forwardRef(
10087
10405
  const state = useEditorStore.getState();
10088
10406
  const currentPage2 = (_b = state.canvas.pages) == null ? void 0 : _b.find((p) => p.id === pageId);
10089
10407
  const children = (currentPage2 == null ? void 0 : currentPage2.children) ?? [];
10090
- const parent = findParentGroup(children, clickedId);
10091
- const targetIsInCrop = !!(((_c = active == null ? void 0 : active._ct) == null ? void 0 : _c.isCropGroup) || (active == null ? void 0 : active.__cropGroup) || ((_e = (_d = active == null ? void 0 : active.group) == null ? void 0 : _d._ct) == null ? void 0 : _e.isCropGroup) || ((_f = active == null ? void 0 : active.group) == null ? void 0 : _f.__cropGroup));
10408
+ const parent = walkToTopmostGroup(clickedId, children, activeEditingGroupId);
10409
+ const targetIsInCrop = !!(active instanceof fabric__namespace.Group && isCropGroupInCropMode(active) || (active == null ? void 0 : active.group) instanceof fabric__namespace.Group && isCropGroupInCropMode(active.group));
10092
10410
  if (parent && !targetIsInCrop && activeEditingGroupId !== parent.id && !(active && active instanceof fabric__namespace.Group && active.__docuforgeSectionGroup)) {
10093
10411
  const memberIdSet = new Set(getAllElementIds(parent.children ?? []));
10094
10412
  const memberObjs = fabricCanvas.getObjects().filter((o) => {
@@ -10113,9 +10431,75 @@ const PageCanvas = react.forwardRef(
10113
10431
  selectElements(ids, false, false);
10114
10432
  return;
10115
10433
  }
10434
+ if (ids.length > 1 && !(active instanceof fabric__namespace.ActiveSelection && active.__pixldocsGroupSelection)) {
10435
+ const state = useEditorStore.getState();
10436
+ const currentPage2 = (_c = state.canvas.pages) == null ? void 0 : _c.find((p) => p.id === pageId);
10437
+ const children = (currentPage2 == null ? void 0 : currentPage2.children) ?? [];
10438
+ const activeEditingGroupId = fabricCanvas.__activeEditingGroupId ?? null;
10439
+ const involvedGroupIds = /* @__PURE__ */ new Set();
10440
+ const standaloneIds = [];
10441
+ for (const id of ids) {
10442
+ const parent = walkToTopmostGroup(id, children, activeEditingGroupId);
10443
+ if (parent && activeEditingGroupId !== parent.id && !parent.backgroundColor) {
10444
+ involvedGroupIds.add(parent.id);
10445
+ } else {
10446
+ standaloneIds.push(id);
10447
+ }
10448
+ }
10449
+ if (involvedGroupIds.size > 0) {
10450
+ const wantIds = new Set(standaloneIds);
10451
+ const groupedMemberIds = /* @__PURE__ */ new Set();
10452
+ for (const gid of involvedGroupIds) {
10453
+ const g = findNodeById(children, gid);
10454
+ if (g && isGroup(g)) {
10455
+ for (const leafId of getAllElementIds(g.children ?? [])) {
10456
+ wantIds.add(leafId);
10457
+ groupedMemberIds.add(leafId);
10458
+ }
10459
+ }
10460
+ }
10461
+ const currentSet = new Set(ids);
10462
+ const needsExpand = wantIds.size !== currentSet.size || [...wantIds].some((id) => !currentSet.has(id));
10463
+ if (needsExpand) {
10464
+ const wantObjs = fabricCanvas.getObjects().filter((o) => {
10465
+ const oid = getObjectId(o);
10466
+ return !!oid && wantIds.has(oid);
10467
+ });
10468
+ if (wantObjs.length >= 2) {
10469
+ isSyncingSelectionToFabricRef.current = true;
10470
+ try {
10471
+ const sel = new fabric__namespace.ActiveSelection(wantObjs, { canvas: fabricCanvas });
10472
+ if (involvedGroupIds.size === 1 && standaloneIds.length === 0) {
10473
+ restoreGroupSelectionVisualState(sel, [...involvedGroupIds][0]);
10474
+ } else {
10475
+ markMixedLogicalSelection(sel, [...involvedGroupIds], groupedMemberIds);
10476
+ }
10477
+ fabricCanvas.setActiveObject(sel);
10478
+ fabricCanvas.requestRenderAll();
10479
+ } finally {
10480
+ isSyncingSelectionToFabricRef.current = false;
10481
+ }
10482
+ }
10483
+ } else {
10484
+ const currentActive = fabricCanvas.getActiveObject();
10485
+ if (currentActive instanceof fabric__namespace.ActiveSelection) {
10486
+ if (involvedGroupIds.size === 1 && standaloneIds.length === 0) {
10487
+ restoreGroupSelectionVisualState(currentActive, [...involvedGroupIds][0]);
10488
+ } else {
10489
+ markMixedLogicalSelection(currentActive, [...involvedGroupIds], groupedMemberIds);
10490
+ }
10491
+ currentActive.setCoords();
10492
+ fabricCanvas.requestRenderAll();
10493
+ }
10494
+ }
10495
+ const storeIds = [...involvedGroupIds, ...standaloneIds];
10496
+ selectElements(storeIds, false, false);
10497
+ return;
10498
+ }
10499
+ }
10116
10500
  if (ids.length > 1) {
10117
10501
  const state = useEditorStore.getState();
10118
- const currentPage2 = (_g = state.canvas.pages) == null ? void 0 : _g.find((p) => p.id === pageId);
10502
+ const currentPage2 = (_d = state.canvas.pages) == null ? void 0 : _d.find((p) => p.id === pageId);
10119
10503
  const children = (currentPage2 == null ? void 0 : currentPage2.children) ?? [];
10120
10504
  const currentSelectedIds = state.canvas.selectedIds ?? [];
10121
10505
  for (const sid of currentSelectedIds) {
@@ -10143,6 +10527,20 @@ const PageCanvas = react.forwardRef(
10143
10527
  } else {
10144
10528
  m.hasBorders = true;
10145
10529
  }
10530
+ const origControls = m.__pixldocsOrigHasControls;
10531
+ if (origControls !== void 0) {
10532
+ m.hasControls = origControls;
10533
+ delete m.__pixldocsOrigHasControls;
10534
+ } else {
10535
+ m.hasControls = true;
10536
+ }
10537
+ const origLockX = m.__pixldocsOrigLockScalingX;
10538
+ if (origLockX !== void 0) {
10539
+ m.lockScalingX = origLockX;
10540
+ m.lockScalingY = m.__pixldocsOrigLockScalingY;
10541
+ delete m.__pixldocsOrigLockScalingX;
10542
+ delete m.__pixldocsOrigLockScalingY;
10543
+ }
10146
10544
  }
10147
10545
  suppressGroupMemberBordersRef.current = [];
10148
10546
  };
@@ -10150,6 +10548,7 @@ const PageCanvas = react.forwardRef(
10150
10548
  var _a2;
10151
10549
  syncSelectionToStore();
10152
10550
  const activeObj = fabricCanvas.getActiveObject();
10551
+ if (activeObj instanceof fabric__namespace.ActiveSelection) applyWarpAwareSelectionBorders(activeObj);
10153
10552
  if (activeObj) applyControlSizeForZoom(fabricCanvas, activeObj);
10154
10553
  if (activeObj && !(activeObj instanceof fabric__namespace.ActiveSelection) && (((_a2 = activeObj._ct) == null ? void 0 : _a2.isCropGroup) || activeObj.__cropGroup)) {
10155
10554
  installCanvaMaskControls(activeObj);
@@ -10158,10 +10557,11 @@ const PageCanvas = react.forwardRef(
10158
10557
  fabricCanvas.on("selection:updated", () => {
10159
10558
  var _a2;
10160
10559
  const next = fabricCanvas.getActiveObject();
10161
- const isGroupSel = next instanceof fabric__namespace.ActiveSelection && next.__pixldocsGroupSelection;
10162
- if (!isGroupSel) restoreSuppressedGroupBorders();
10560
+ const isLogicalGroupSel = next instanceof fabric__namespace.ActiveSelection && (next.__pixldocsGroupSelection || Array.isArray(next.__pixldocsLogicalGroupIds));
10561
+ if (!isLogicalGroupSel) restoreSuppressedGroupBorders();
10163
10562
  syncSelectionToStore();
10164
10563
  const activeObj = fabricCanvas.getActiveObject();
10564
+ if (activeObj instanceof fabric__namespace.ActiveSelection) applyWarpAwareSelectionBorders(activeObj);
10165
10565
  if (activeObj) applyControlSizeForZoom(fabricCanvas, activeObj);
10166
10566
  if (activeObj && !(activeObj instanceof fabric__namespace.ActiveSelection) && (((_a2 = activeObj._ct) == null ? void 0 : _a2.isCropGroup) || activeObj.__cropGroup)) {
10167
10567
  installCanvaMaskControls(activeObj);
@@ -10194,24 +10594,83 @@ const PageCanvas = react.forwardRef(
10194
10594
  const stateNow = useEditorStore.getState();
10195
10595
  const pageNow = (_b = stateNow.canvas.pages) == null ? void 0 : _b.find((p) => p.id === pageId);
10196
10596
  const childrenNow = (pageNow == null ? void 0 : pageNow.children) ?? [];
10197
- const parent = findParentGroup(childrenNow, childId);
10198
- if (!parent) return;
10199
- fabricCanvas.__activeEditingGroupId = parent.id;
10597
+ const chain = [];
10598
+ {
10599
+ let currId = childId;
10600
+ for (let i = 0; i < 32; i++) {
10601
+ const p = findParentGroup(childrenNow, currId);
10602
+ if (!p) break;
10603
+ if (p.backgroundColor) break;
10604
+ chain.push(p);
10605
+ currId = p.id;
10606
+ }
10607
+ }
10608
+ if (chain.length === 0) return;
10609
+ const currentScopeId = fabricCanvas.__activeEditingGroupId ?? null;
10610
+ let scopeIdx = chain.length;
10611
+ if (currentScopeId) {
10612
+ const idx = chain.findIndex((g) => g.id === currentScopeId);
10613
+ if (idx >= 0) scopeIdx = idx;
10614
+ }
10615
+ const entryIdx = scopeIdx - 1;
10616
+ if (entryIdx < 0) return;
10617
+ const newScopeGroup = chain[entryIdx];
10618
+ const selectionIdx = entryIdx - 1;
10619
+ const selectInnerObject = (obj) => {
10620
+ delete obj.__pixldocsGroupSelection;
10621
+ delete obj.__pixldocsLogicalGroupIds;
10622
+ obj.set({ selectable: true, evented: true, hasBorders: true, hasControls: true });
10623
+ fabricCanvas.setActiveObject(obj);
10624
+ obj.setCoords();
10625
+ };
10626
+ pendingGroupPromotionRef.current = null;
10200
10627
  isSyncingSelectionToFabricRef.current = true;
10201
10628
  try {
10629
+ skipSelectionClearOnDiscardRef.current = true;
10630
+ preserveEditingScopeOnSelectionClearRef.current = true;
10202
10631
  fabricCanvas.discardActiveObject();
10203
- fabricCanvas.setActiveObject(hitChild);
10204
- fabricCanvas.requestRenderAll();
10632
+ skipSelectionClearOnDiscardRef.current = false;
10633
+ preserveEditingScopeOnSelectionClearRef.current = false;
10634
+ restoreSuppressedGroupBorders();
10635
+ fabricCanvas.__activeEditingGroupId = newScopeGroup.id;
10636
+ if (selectionIdx < 0) {
10637
+ selectInnerObject(hitChild);
10638
+ fabricCanvas.requestRenderAll();
10639
+ } else {
10640
+ const subgroup = chain[selectionIdx];
10641
+ const memberIds = new Set(getAllElementIds(subgroup.children ?? []));
10642
+ const memberObjs = fabricCanvas.getObjects().filter((o) => {
10643
+ const oid = getObjectId(o);
10644
+ return !!oid && memberIds.has(oid);
10645
+ });
10646
+ if (memberObjs.length > 1) {
10647
+ const selection = new fabric__namespace.ActiveSelection(memberObjs, { canvas: fabricCanvas });
10648
+ restoreGroupSelectionVisualState(selection, subgroup.id);
10649
+ fabricCanvas.setActiveObject(selection);
10650
+ selection.setCoords();
10651
+ } else if (memberObjs.length === 1) {
10652
+ const only = memberObjs[0];
10653
+ selectInnerObject(only);
10654
+ } else {
10655
+ selectInnerObject(hitChild);
10656
+ }
10657
+ fabricCanvas.requestRenderAll();
10658
+ }
10205
10659
  } finally {
10660
+ skipSelectionClearOnDiscardRef.current = false;
10661
+ preserveEditingScopeOnSelectionClearRef.current = false;
10206
10662
  isSyncingSelectionToFabricRef.current = false;
10207
10663
  }
10208
- selectElements([childId], false, false);
10664
+ const selectedId = selectionIdx < 0 ? childId : chain[selectionIdx].id;
10665
+ selectElements([selectedId], false, false);
10209
10666
  });
10210
10667
  fabricCanvas.on("selection:cleared", () => {
10211
10668
  if (!isActiveRef.current || isRebuildingRef.current || !allowSelection) return;
10212
10669
  setGroupOverlayLiveBoundsRef.current(null);
10213
10670
  groupBoundsResizingRef.current = false;
10214
- fabricCanvas.__activeEditingGroupId = null;
10671
+ if (!preserveEditingScopeOnSelectionClearRef.current) {
10672
+ fabricCanvas.__activeEditingGroupId = null;
10673
+ }
10215
10674
  const preservedGroupSelection = preserveActiveSelectionAfterTransformRef.current;
10216
10675
  const shouldRestoreGroupSelection = !!((preservedGroupSelection == null ? void 0 : preservedGroupSelection.groupSelectionId) && (!preservedGroupSelection.expiresAt || preservedGroupSelection.expiresAt > Date.now()));
10217
10676
  if (skipSelectionClearOnDiscardRef.current) {
@@ -10263,6 +10722,14 @@ const PageCanvas = react.forwardRef(
10263
10722
  var _a2, _b;
10264
10723
  const active = target instanceof fabric__namespace.ActiveSelection ? target : fabricCanvas.getActiveObject();
10265
10724
  if (!(active instanceof fabric__namespace.ActiveSelection)) return;
10725
+ if (!activeSelectionMoveStartRef.current || activeSelectionMoveStartRef.current.selection !== active) {
10726
+ const rect2 = active.getBoundingRect();
10727
+ activeSelectionMoveStartRef.current = {
10728
+ selection: active,
10729
+ selectionLeft: rect2.left,
10730
+ selectionTop: rect2.top
10731
+ };
10732
+ }
10266
10733
  const groupId = active.__pixldocsGroupSelection;
10267
10734
  if (!groupId) return;
10268
10735
  if (((_a2 = groupSelectionTransformStartRef.current) == null ? void 0 : _a2.groupId) === groupId && groupSelectionTransformStartRef.current.selection === active) return;
@@ -10281,16 +10748,123 @@ const PageCanvas = react.forwardRef(
10281
10748
  };
10282
10749
  };
10283
10750
  const restoreGroupSelectionVisualState = (selection, groupId) => {
10284
- selection.__pixldocsGroupSelection = groupId;
10285
- const members = selection.getObjects();
10751
+ applyLogicalGroupSelectionVisualState(selection, groupId);
10752
+ };
10753
+ const suppressBordersForObjects = (members) => {
10754
+ var _a2;
10755
+ for (const prev of suppressGroupMemberBordersRef.current) {
10756
+ if (members.includes(prev)) continue;
10757
+ const orig = prev.__pixldocsOrigHasBorders;
10758
+ if (orig !== void 0) {
10759
+ prev.hasBorders = orig;
10760
+ delete prev.__pixldocsOrigHasBorders;
10761
+ } else {
10762
+ prev.hasBorders = true;
10763
+ }
10764
+ const origControls = prev.__pixldocsOrigHasControls;
10765
+ if (origControls !== void 0) {
10766
+ prev.hasControls = origControls;
10767
+ delete prev.__pixldocsOrigHasControls;
10768
+ } else {
10769
+ prev.hasControls = true;
10770
+ }
10771
+ const origLockX = prev.__pixldocsOrigLockScalingX;
10772
+ if (origLockX !== void 0) {
10773
+ prev.lockScalingX = origLockX;
10774
+ prev.lockScalingY = prev.__pixldocsOrigLockScalingY;
10775
+ delete prev.__pixldocsOrigLockScalingX;
10776
+ delete prev.__pixldocsOrigLockScalingY;
10777
+ }
10778
+ }
10286
10779
  suppressGroupMemberBordersRef.current = members;
10287
10780
  for (const m of members) {
10288
10781
  if (m.__pixldocsOrigHasBorders === void 0) {
10289
10782
  m.__pixldocsOrigHasBorders = m.hasBorders;
10290
10783
  }
10784
+ if (m.__pixldocsOrigHasControls === void 0) {
10785
+ m.__pixldocsOrigHasControls = m.hasControls;
10786
+ }
10291
10787
  m.hasBorders = false;
10788
+ m.hasControls = false;
10789
+ if (m.__cropGroup || ((_a2 = m._ct) == null ? void 0 : _a2.isCropGroup)) {
10790
+ if (m.__pixldocsOrigLockScalingX === void 0) {
10791
+ m.__pixldocsOrigLockScalingX = m.lockScalingX;
10792
+ m.__pixldocsOrigLockScalingY = m.lockScalingY;
10793
+ }
10794
+ m.lockScalingX = false;
10795
+ m.lockScalingY = false;
10796
+ }
10292
10797
  }
10293
10798
  };
10799
+ const isMultiSelectModifier = (event) => !!((event == null ? void 0 : event.shiftKey) || (event == null ? void 0 : event.metaKey) || (event == null ? void 0 : event.ctrlKey));
10800
+ const pickSelectableObjectAtPointer = (event) => {
10801
+ var _a2;
10802
+ try {
10803
+ const pointer = fabricCanvas.getViewportPoint(event);
10804
+ const objects = fabricCanvas.getObjects();
10805
+ for (let i = objects.length - 1; i >= 0; i--) {
10806
+ const obj = objects[i];
10807
+ const id = getObjectId(obj);
10808
+ if (!id || id === "__background__" || !obj.selectable || !obj.evented || obj.visible === false) continue;
10809
+ const controlHit = (_a2 = obj.findControl) == null ? void 0 : _a2.call(obj, pointer);
10810
+ if (controlHit) continue;
10811
+ if (typeof obj.containsPoint === "function" && obj.containsPoint(pointer)) return obj;
10812
+ }
10813
+ } catch {
10814
+ return null;
10815
+ }
10816
+ return null;
10817
+ };
10818
+ let pendingShiftMultiSelect = null;
10819
+ const applyShiftMultiSelect = (target, event, baselineActive = fabricCanvas.getActiveObject(), baselineObjects = fabricCanvas.getActiveObjects()) => {
10820
+ var _a2, _b, _c;
10821
+ if (!target || !target.selectable) return false;
10822
+ const active = baselineActive;
10823
+ if (!active || ((_a2 = active.getActiveControl) == null ? void 0 : _a2.call(active))) return false;
10824
+ if (active === target && !(active instanceof fabric__namespace.ActiveSelection)) return false;
10825
+ isSyncingSelectionToFabricRef.current = true;
10826
+ try {
10827
+ let nextObjects;
10828
+ if (active instanceof fabric__namespace.ActiveSelection) {
10829
+ const current = baselineObjects.length ? baselineObjects : active.getObjects();
10830
+ nextObjects = current.includes(target) ? current.filter((obj) => obj !== target) : [...current, target];
10831
+ } else {
10832
+ nextObjects = [active, target];
10833
+ }
10834
+ nextObjects = nextObjects.filter((obj, index, arr) => arr.indexOf(obj) === index && fabricCanvas.getObjects().includes(obj));
10835
+ if (nextObjects.length > 1) {
10836
+ const selection = new fabric__namespace.ActiveSelection(nextObjects, { canvas: fabricCanvas });
10837
+ applyWarpAwareSelectionBorders(selection);
10838
+ fabricCanvas.setActiveObject(selection);
10839
+ selection.setCoords();
10840
+ isSyncingSelectionToFabricRef.current = false;
10841
+ syncSelectionToStore();
10842
+ } else if (nextObjects.length === 1) {
10843
+ fabricCanvas.setActiveObject(nextObjects[0]);
10844
+ nextObjects[0].setCoords();
10845
+ const onlyId = getObjectId(nextObjects[0]);
10846
+ if (onlyId) selectElements([onlyId], false, false);
10847
+ }
10848
+ fabricCanvas.requestRenderAll();
10849
+ } finally {
10850
+ requestAnimationFrame(() => {
10851
+ isSyncingSelectionToFabricRef.current = false;
10852
+ });
10853
+ }
10854
+ (_b = event == null ? void 0 : event.preventDefault) == null ? void 0 : _b.call(event);
10855
+ (_c = event == null ? void 0 : event.stopPropagation) == null ? void 0 : _c.call(event);
10856
+ return true;
10857
+ };
10858
+ const markMixedLogicalSelection = (selection, groupIds, groupMemberIds) => {
10859
+ delete selection.__pixldocsGroupSelection;
10860
+ selection.__pixldocsLogicalGroupIds = groupIds;
10861
+ selection.hasBorders = true;
10862
+ const memberObjects = selection.getObjects().filter((obj) => {
10863
+ const id = getObjectId(obj);
10864
+ return !!id && groupMemberIds.has(id);
10865
+ });
10866
+ suppressBordersForObjects(memberObjects);
10867
+ };
10294
10868
  const restorePreservedGroupSelectionSoon = (snapshot = preserveActiveSelectionAfterTransformRef.current) => {
10295
10869
  if (!(snapshot == null ? void 0 : snapshot.groupSelectionId) || snapshot.memberIds.length < 2) return;
10296
10870
  const groupId = snapshot.groupSelectionId;
@@ -10328,7 +10902,7 @@ const PageCanvas = react.forwardRef(
10328
10902
  return !!(((_a2 = o == null ? void 0 : o._ct) == null ? void 0 : _a2.isCropGroup) || (o == null ? void 0 : o.__cropGroup));
10329
10903
  };
10330
10904
  const promoteToCropGroup = (opt) => {
10331
- var _a2, _b, _c;
10905
+ var _a2, _b, _c, _d, _e, _f;
10332
10906
  const t = opt.target;
10333
10907
  if (((_a2 = opt.e) == null ? void 0 : _a2.type) !== "mousedown" && ((_b = opt.e) == null ? void 0 : _b.type) !== "pointerdown" && ((_c = opt.e) == null ? void 0 : _c.type) !== "touchstart") {
10334
10908
  if (t && isCropGroup2(t)) {
@@ -10338,6 +10912,9 @@ const PageCanvas = react.forwardRef(
10338
10912
  }
10339
10913
  return;
10340
10914
  }
10915
+ if (((_d = opt.e) == null ? void 0 : _d.shiftKey) || ((_e = opt.e) == null ? void 0 : _e.metaKey) || ((_f = opt.e) == null ? void 0 : _f.ctrlKey)) {
10916
+ return;
10917
+ }
10341
10918
  if (!t) {
10342
10919
  const pointer = fabricCanvas.getPointer(opt.e);
10343
10920
  const objects = fabricCanvas.getObjects();
@@ -10412,6 +10989,12 @@ const PageCanvas = react.forwardRef(
10412
10989
  }
10413
10990
  fabricCanvas.on("mouse:down", (opt) => {
10414
10991
  var _a2, _b;
10992
+ if (pendingShiftMultiSelect) {
10993
+ const pending = pendingShiftMultiSelect;
10994
+ pendingShiftMultiSelect = null;
10995
+ applyShiftMultiSelect(pending.target, opt.e, pending.active, pending.activeObjects);
10996
+ return;
10997
+ }
10415
10998
  const target = opt.target;
10416
10999
  const cropGroup = ((_a2 = target == null ? void 0 : target._ct) == null ? void 0 : _a2.isCropGroup) || (target == null ? void 0 : target.__cropGroup) ? target : (target == null ? void 0 : target.group) && (((_b = target.group._ct) == null ? void 0 : _b.isCropGroup) || target.group.__cropGroup) ? target.group : null;
10417
11000
  if (cropGroup) {
@@ -10422,8 +11005,42 @@ const PageCanvas = react.forwardRef(
10422
11005
  fabricCanvas.requestRenderAll();
10423
11006
  }
10424
11007
  });
11008
+ const groupFabricUnionBBox = (g) => {
11009
+ var _a2, _b;
11010
+ const memberIds = new Set(getAllElementIds(g.children ?? []));
11011
+ if (memberIds.size === 0) return null;
11012
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
11013
+ let found = false;
11014
+ for (const o of fabricCanvas.getObjects()) {
11015
+ const oid = getObjectId(o);
11016
+ if (!oid || !memberIds.has(oid)) continue;
11017
+ const br = ((_a2 = o.getBoundingRect) == null ? void 0 : _a2.call(o, true, true)) ?? ((_b = o.getBoundingRect) == null ? void 0 : _b.call(o));
11018
+ if (!br) continue;
11019
+ minX = Math.min(minX, br.left);
11020
+ minY = Math.min(minY, br.top);
11021
+ maxX = Math.max(maxX, br.left + br.width);
11022
+ maxY = Math.max(maxY, br.top + br.height);
11023
+ found = true;
11024
+ }
11025
+ if (!found) return null;
11026
+ return { left: minX, top: minY, right: maxX, bottom: maxY };
11027
+ };
11028
+ const pickGroupAtPointer = (px, py, children, activeEditingGroupId) => {
11029
+ let pick = null;
11030
+ for (const node of children) {
11031
+ if (!isGroup(node)) continue;
11032
+ if (node.backgroundColor) continue;
11033
+ if (activeEditingGroupId && node.id === activeEditingGroupId) continue;
11034
+ const b = groupFabricUnionBBox(node);
11035
+ if (!b) continue;
11036
+ if (px < b.left || py < b.top || px > b.right || py > b.bottom) continue;
11037
+ const area = Math.max(1, (b.right - b.left) * (b.bottom - b.top));
11038
+ if (!pick || area < pick.area) pick = { group: node, area };
11039
+ }
11040
+ return pick;
11041
+ };
10425
11042
  fabricCanvas.on("mouse:down:before", (opt) => {
10426
- var _a2, _b, _c, _d, _e;
11043
+ var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
10427
11044
  if (editLockRef.current) {
10428
11045
  const active = fabricCanvas.getActiveObject();
10429
11046
  if (active && (((_a2 = active._ct) == null ? void 0 : _a2.isCropGroup) || active.__cropGroup)) {
@@ -10439,13 +11056,154 @@ const PageCanvas = react.forwardRef(
10439
11056
  syncLockedRef.current = true;
10440
11057
  lockEdits();
10441
11058
  }
11059
+ const target = opt.target;
11060
+ const targetId = target ? getObjectId(target) : null;
11061
+ const activeEditingGroupId = fabricCanvas.__activeEditingGroupId ?? null;
11062
+ const pageNow = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId);
11063
+ const childrenNow = (pageNow == null ? void 0 : pageNow.children) ?? [];
11064
+ if (isMultiSelectModifier(opt.e)) {
11065
+ const manualTarget = target && !(target instanceof fabric__namespace.ActiveSelection) && targetId && targetId !== "__background__" ? target : pickSelectableObjectAtPointer(opt.e);
11066
+ if (manualTarget) {
11067
+ pendingShiftMultiSelect = {
11068
+ target: manualTarget,
11069
+ active: fabricCanvas.getActiveObject(),
11070
+ activeObjects: fabricCanvas.getActiveObjects().slice()
11071
+ };
11072
+ pendingGroupPromotionRef.current = null;
11073
+ return;
11074
+ }
11075
+ }
11076
+ const findTopmostPromotableGroup = (childId) => {
11077
+ let topmost = null;
11078
+ let currentId = childId;
11079
+ for (let i = 0; i < 32; i++) {
11080
+ const p = findParentGroup(childrenNow, currentId);
11081
+ if (!p) break;
11082
+ if (p.backgroundColor) break;
11083
+ if (activeEditingGroupId && p.id === activeEditingGroupId) break;
11084
+ topmost = p;
11085
+ currentId = p.id;
11086
+ }
11087
+ return topmost;
11088
+ };
11089
+ if (target && targetId && targetId !== "__background__") {
11090
+ const parent = findTopmostPromotableGroup(targetId);
11091
+ const targetIsInCrop = !!(target instanceof fabric__namespace.Group && isCropGroupInCropMode(target) || (target == null ? void 0 : target.group) instanceof fabric__namespace.Group && isCropGroupInCropMode(target.group));
11092
+ const activeNow = fabricCanvas.getActiveObject();
11093
+ const alreadyThisGroup = activeNow instanceof fabric__namespace.ActiveSelection && activeNow.__pixldocsGroupSelection === (parent == null ? void 0 : parent.id);
11094
+ const isMultiSelectKey = !!(((_f = opt.e) == null ? void 0 : _f.shiftKey) || ((_g = opt.e) == null ? void 0 : _g.metaKey) || ((_h = opt.e) == null ? void 0 : _h.ctrlKey));
11095
+ if (parent && !parent.backgroundColor && !targetIsInCrop && activeEditingGroupId !== parent.id && !alreadyThisGroup && !isMultiSelectKey) {
11096
+ const memberIds = new Set(getAllElementIds(parent.children ?? []));
11097
+ const memberObjs = fabricCanvas.getObjects().filter((o) => {
11098
+ const oid = getObjectId(o);
11099
+ return !!oid && memberIds.has(oid);
11100
+ });
11101
+ if (memberObjs.length > 1) {
11102
+ const selection = new fabric__namespace.ActiveSelection(memberObjs, { canvas: fabricCanvas });
11103
+ restoreGroupSelectionVisualState(selection, parent.id);
11104
+ fabricCanvas.setActiveObject(selection);
11105
+ selection.setCoords();
11106
+ fabricCanvas._target = selection;
11107
+ opt.target = selection;
11108
+ pendingGroupPromotionRef.current = { groupId: parent.id, selection };
11109
+ } else if (memberObjs.length === 1) {
11110
+ const only = memberObjs[0];
11111
+ only.__pixldocsGroupSelection = parent.id;
11112
+ fabricCanvas.setActiveObject(only);
11113
+ only.setCoords();
11114
+ fabricCanvas._target = only;
11115
+ opt.target = only;
11116
+ pendingGroupPromotionRef.current = { groupId: parent.id, selection: only };
11117
+ }
11118
+ }
11119
+ } else if (!target || targetId === "__background__") {
11120
+ const isMultiSelectKey = !!(((_i = opt.e) == null ? void 0 : _i.shiftKey) || ((_j = opt.e) == null ? void 0 : _j.metaKey) || ((_k = opt.e) == null ? void 0 : _k.ctrlKey));
11121
+ if (isMultiSelectKey) return;
11122
+ try {
11123
+ const pointer = fabricCanvas.getPointer(opt.e);
11124
+ const px = pointer.x;
11125
+ const py = pointer.y;
11126
+ const pick = pickGroupAtPointer(px, py, childrenNow, activeEditingGroupId);
11127
+ if (pick) {
11128
+ const parent = pick.group;
11129
+ if (activeEditingGroupId !== parent.id) {
11130
+ const memberIds = new Set(getAllElementIds(parent.children ?? []));
11131
+ const memberObjs = fabricCanvas.getObjects().filter((o) => {
11132
+ const oid = getObjectId(o);
11133
+ return !!oid && memberIds.has(oid);
11134
+ });
11135
+ if (memberObjs.length > 1) {
11136
+ const selection = new fabric__namespace.ActiveSelection(memberObjs, { canvas: fabricCanvas });
11137
+ restoreGroupSelectionVisualState(selection, parent.id);
11138
+ fabricCanvas.setActiveObject(selection);
11139
+ selection.setCoords();
11140
+ fabricCanvas._target = selection;
11141
+ opt.target = selection;
11142
+ pendingGroupPromotionRef.current = { groupId: parent.id, selection };
11143
+ } else if (memberObjs.length === 1) {
11144
+ const only = memberObjs[0];
11145
+ only.__pixldocsGroupSelection = parent.id;
11146
+ fabricCanvas.setActiveObject(only);
11147
+ fabricCanvas._target = only;
11148
+ opt.target = only;
11149
+ pendingGroupPromotionRef.current = { groupId: parent.id, selection: only };
11150
+ }
11151
+ }
11152
+ }
11153
+ } catch {
11154
+ }
11155
+ }
10442
11156
  promoteToCropGroup(opt);
10443
11157
  });
10444
11158
  fabricCanvas.on("mouse:move:before", promoteToCropGroup);
11159
+ fabricCanvas.on("after:render", () => {
11160
+ var _a2;
11161
+ try {
11162
+ const active = fabricCanvas.getActiveObject();
11163
+ if (!(active instanceof fabric__namespace.ActiveSelection)) return;
11164
+ const logicalIds = active.__pixldocsLogicalGroupIds;
11165
+ if (!logicalIds || logicalIds.length < 1) return;
11166
+ const pageNow = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId);
11167
+ const childrenNow = (pageNow == null ? void 0 : pageNow.children) ?? [];
11168
+ const ctx = ((_a2 = fabricCanvas.getContext) == null ? void 0 : _a2.call(fabricCanvas)) ?? fabricCanvas.contextContainer;
11169
+ if (!ctx) return;
11170
+ const vt = fabricCanvas.viewportTransform;
11171
+ ctx.save();
11172
+ if (vt) ctx.transform(vt[0], vt[1], vt[2], vt[3], vt[4], vt[5]);
11173
+ ctx.strokeStyle = SELECTION_PRIMARY;
11174
+ ctx.lineWidth = 1.25 / ((vt == null ? void 0 : vt[0]) || 1);
11175
+ ctx.setLineDash([]);
11176
+ for (const gid of logicalIds) {
11177
+ const node = findNodeById(childrenNow, gid);
11178
+ if (!node || !isGroup(node)) continue;
11179
+ const b = groupFabricUnionBBox(node);
11180
+ if (!b) continue;
11181
+ ctx.strokeRect(b.left, b.top, b.right - b.left, b.bottom - b.top);
11182
+ }
11183
+ ctx.restore();
11184
+ } catch {
11185
+ }
11186
+ });
11187
+ fabricCanvas.on("mouse:move", (opt) => {
11188
+ if (fabricCanvas._currentTransform) return;
11189
+ if (editLockRef.current) return;
11190
+ const t = opt.target;
11191
+ const tid = t ? getObjectId(t) : null;
11192
+ if (t && tid && tid !== "__background__") return;
11193
+ try {
11194
+ const pointer = fabricCanvas.getPointer(opt.e);
11195
+ const pageNow = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId);
11196
+ const childrenNow = (pageNow == null ? void 0 : pageNow.children) ?? [];
11197
+ const activeEditingGroupId = fabricCanvas.__activeEditingGroupId ?? null;
11198
+ const pick = pickGroupAtPointer(pointer.x, pointer.y, childrenNow, activeEditingGroupId);
11199
+ fabricCanvas.defaultCursor = pick ? "move" : "default";
11200
+ } catch {
11201
+ fabricCanvas.defaultCursor = "default";
11202
+ }
11203
+ });
10445
11204
  fabricCanvas.on("mouse:down", (ev) => {
10446
11205
  if (fabricCanvas._currentTransform) {
10447
11206
  lockEdits();
10448
- didTransformRef.current = true;
10449
11207
  }
10450
11208
  const o = fabricCanvas.getActiveObject();
10451
11209
  pendingTextEditOnUpRef.current = null;
@@ -10459,6 +11217,38 @@ const PageCanvas = react.forwardRef(
10459
11217
  setRotationLabel(null);
10460
11218
  setSizeLabel(null);
10461
11219
  dragStarted = false;
11220
+ const pendingPromotion = pendingGroupPromotionRef.current;
11221
+ pendingGroupPromotionRef.current = null;
11222
+ if (pendingPromotion && !didTransformRef.current) {
11223
+ const activeNow = fabricCanvas.getActiveObject();
11224
+ if (activeNow !== pendingPromotion.selection) {
11225
+ isSyncingSelectionToFabricRef.current = true;
11226
+ try {
11227
+ fabricCanvas.setActiveObject(pendingPromotion.selection);
11228
+ pendingPromotion.selection.setCoords();
11229
+ fabricCanvas.requestRenderAll();
11230
+ } finally {
11231
+ requestAnimationFrame(() => {
11232
+ isSyncingSelectionToFabricRef.current = false;
11233
+ });
11234
+ }
11235
+ selectElements([pendingPromotion.groupId], false, false);
11236
+ }
11237
+ try {
11238
+ const sel = pendingPromotion.selection;
11239
+ if (sel instanceof fabric__namespace.ActiveSelection) {
11240
+ restoreGroupSelectionVisualState(sel, pendingPromotion.groupId);
11241
+ sel.hasBorders = true;
11242
+ sel.setCoords();
11243
+ applyWarpAwareSelectionBorders(sel);
11244
+ } else {
11245
+ sel.__pixldocsGroupSelection = pendingPromotion.groupId;
11246
+ sel.setCoords();
11247
+ }
11248
+ fabricCanvas.requestRenderAll();
11249
+ } catch {
11250
+ }
11251
+ }
10462
11252
  const activeObj = fabricCanvas.getActiveObject();
10463
11253
  if (activeObj && (activeObj.__cropGroup || ((_a2 = activeObj._ct) == null ? void 0 : _a2.isCropGroup))) {
10464
11254
  activeObj.__lockScaleDuringCrop = false;
@@ -10500,6 +11290,34 @@ const PageCanvas = react.forwardRef(
10500
11290
  if (!isActiveRef.current) return;
10501
11291
  markTransforming(e.target);
10502
11292
  };
11293
+ const getObjectFrameBoundsInSelection = (selection, obj, frameWidth, frameHeight) => {
11294
+ const w = Math.max(1, frameWidth ?? obj.width ?? 1);
11295
+ const h = Math.max(1, frameHeight ?? obj.height ?? 1);
11296
+ const originX = obj.originX ?? "left";
11297
+ const originY = obj.originY ?? "top";
11298
+ const localLeft = originX === "center" ? -w / 2 : originX === "right" ? -w : 0;
11299
+ const localTop = originY === "center" ? -h / 2 : originY === "bottom" ? -h : 0;
11300
+ const matrix = fabric__namespace.util.multiplyTransformMatrices(
11301
+ selection.calcTransformMatrix(),
11302
+ obj.calcOwnMatrix()
11303
+ );
11304
+ const points = [
11305
+ new fabric__namespace.Point(localLeft, localTop),
11306
+ new fabric__namespace.Point(localLeft + w, localTop),
11307
+ new fabric__namespace.Point(localLeft + w, localTop + h),
11308
+ new fabric__namespace.Point(localLeft, localTop + h)
11309
+ ].map((point) => fabric__namespace.util.transformPoint(point, matrix));
11310
+ const xs = points.map((p) => p.x);
11311
+ const ys = points.map((p) => p.y);
11312
+ const left = Math.min(...xs);
11313
+ const top = Math.min(...ys);
11314
+ return {
11315
+ left,
11316
+ top,
11317
+ width: Math.max(1, Math.max(...xs) - left),
11318
+ height: Math.max(1, Math.max(...ys) - top)
11319
+ };
11320
+ };
10503
11321
  fabricCanvas.on("object:added", (e) => {
10504
11322
  var _a2;
10505
11323
  const obj = e.target;
@@ -10619,11 +11437,18 @@ const PageCanvas = react.forwardRef(
10619
11437
  }
10620
11438
  if (obj instanceof fabric__namespace.Textbox) {
10621
11439
  const sy = obj.scaleY ?? 1;
10622
- if (Math.abs(sy - 1) > 1e-3) {
11440
+ const sx = obj.scaleX ?? 1;
11441
+ const isUniformCornerScale = Math.abs(sx - sy) < 1e-3 && Math.abs(sx - 1) > 1e-3;
11442
+ if (isUniformCornerScale) {
11443
+ obj.setCoords();
11444
+ obj.dirty = true;
11445
+ } else if (Math.abs(sy - 1) > 1e-3) {
10623
11446
  const center = obj.getCenterPoint();
10624
- const newMinH = Math.max(0, (obj.height ?? 0) * Math.abs(sy));
11447
+ const newVisualH = (obj.height ?? 0) * Math.abs(sy);
11448
+ const targetScaleY = Math.abs(sx - 1) > 1e-3 ? Math.abs(sx) : 1;
11449
+ const newMinH = Math.max(0, newVisualH / targetScaleY);
10625
11450
  obj.minBoxHeight = newMinH;
10626
- obj.scaleY = 1;
11451
+ obj.scaleY = targetScaleY;
10627
11452
  try {
10628
11453
  obj.initDimensions();
10629
11454
  } catch {
@@ -10745,7 +11570,7 @@ const PageCanvas = react.forwardRef(
10745
11570
  });
10746
11571
  let cropGroupSaveTimer = null;
10747
11572
  fabricCanvas.on("object:modified", (e) => {
10748
- var _a2, _b, _c, _d, _e, _f;
11573
+ var _a2, _b, _c, _d, _e, _f, _g;
10749
11574
  try {
10750
11575
  dragStarted = false;
10751
11576
  setGuides([]);
@@ -10966,14 +11791,14 @@ const PageCanvas = react.forwardRef(
10966
11791
  }
10967
11792
  if (active && active instanceof fabric__namespace.Group && !(active instanceof fabric__namespace.ActiveSelection) && getObjectId(active)) {
10968
11793
  const groupId = getObjectId(active);
10969
- const pageChildren2 = ((_e = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _e.children) ?? [];
11794
+ const pageChildren3 = ((_e = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _e.children) ?? [];
10970
11795
  const w = (active.width ?? 0) * (active.scaleX ?? 1);
10971
11796
  const h = (active.height ?? 0) * (active.scaleY ?? 1);
10972
11797
  const centerX = active.left ?? 0;
10973
11798
  const centerY = active.top ?? 0;
10974
11799
  const groupLeft = active.originX === "center" ? centerX - w / 2 : centerX;
10975
11800
  const groupTop = active.originY === "center" ? centerY - h / 2 : centerY;
10976
- const storePos = absoluteToStorePosition(groupLeft, groupTop, groupId, pageChildren2);
11801
+ const storePos = absoluteToStorePosition(groupLeft, groupTop, groupId, pageChildren3);
10977
11802
  useEditorStore.getState().updateNode(groupId, { left: storePos.left, top: storePos.top }, { recordHistory: false, skipLayoutRecalc: true });
10978
11803
  commitHistory();
10979
11804
  unlockEditsSoon();
@@ -10988,6 +11813,14 @@ const PageCanvas = react.forwardRef(
10988
11813
  }
10989
11814
  const activeObj = fabricCanvas.getActiveObject();
10990
11815
  let activeObjects = fabricCanvas.getActiveObjects();
11816
+ const activeSelectionMoveStart = activeObj instanceof fabric__namespace.ActiveSelection && ((_f = activeSelectionMoveStartRef.current) == null ? void 0 : _f.selection) === activeObj ? activeSelectionMoveStartRef.current : null;
11817
+ const activeSelectionDelta = activeObj instanceof fabric__namespace.ActiveSelection && activeSelectionMoveStart ? (() => {
11818
+ const rect = activeObj.getBoundingRect();
11819
+ return {
11820
+ x: rect.left - activeSelectionMoveStart.selectionLeft,
11821
+ y: rect.top - activeSelectionMoveStart.selectionTop
11822
+ };
11823
+ })() : null;
10991
11824
  if (activeObjects.length === 0 && modifiedTarget && getObjectId(modifiedTarget)) {
10992
11825
  activeObjects = [modifiedTarget];
10993
11826
  }
@@ -11009,8 +11842,53 @@ const PageCanvas = react.forwardRef(
11009
11842
  const currentPage2 = getCurrentPageStore();
11010
11843
  const selectedElementIds = activeObjects.map((obj) => getObjectId(obj)).filter((id) => !!id && id !== "__background__");
11011
11844
  const anyCropGroup = activeObjects.some((o) => o.__cropGroup);
11845
+ const pageChildren2 = currentPage2.children ?? [];
11846
+ const selectedLogicalGroupIds = (useEditorStore.getState().canvas.selectedIds ?? []).filter((id) => {
11847
+ const node = findNodeById(pageChildren2, id);
11848
+ return !!(node && isGroup(node));
11849
+ });
11850
+ const activeSelectionHadTransform = activeObj instanceof fabric__namespace.ActiveSelection && (Math.abs((activeObj.scaleX ?? 1) - 1) > 0.01 || Math.abs((activeObj.scaleY ?? 1) - 1) > 0.01 || Math.abs((activeObj.angle ?? 0) % 360) > 0.01);
11851
+ if (!anyCropGroup && activeSelectionDelta && !activeSelectionHadTransform && selectedLogicalGroupIds.length > 0) {
11852
+ const selectedStoreIds = useEditorStore.getState().canvas.selectedIds ?? [];
11853
+ const groupMemberIds = /* @__PURE__ */ new Set();
11854
+ selectedLogicalGroupIds.forEach((gid) => {
11855
+ const groupNode = findNodeById(pageChildren2, gid);
11856
+ if (groupNode && isGroup(groupNode)) {
11857
+ getAllElementIds(groupNode.children ?? []).forEach((id) => groupMemberIds.add(id));
11858
+ }
11859
+ });
11860
+ const logicalStandaloneIds = selectedStoreIds.filter((id) => {
11861
+ if (selectedLogicalGroupIds.includes(id)) return false;
11862
+ if (groupMemberIds.has(id)) return false;
11863
+ return !!findNodeById(pageChildren2, id);
11864
+ });
11865
+ if (logicalStandaloneIds.length > 0) {
11866
+ const { updateNode: updateNodeStore, commitHistory: commitHistoryStore } = useEditorStore.getState();
11867
+ [...selectedLogicalGroupIds, ...logicalStandaloneIds].forEach((id) => {
11868
+ const node = findNodeById(pageChildren2, id);
11869
+ if (!node) return;
11870
+ const abs = getAbsoluteBounds(node, pageChildren2, pageBoundsOptions);
11871
+ const nextAbsLeft = abs.left + activeSelectionDelta.x;
11872
+ const nextAbsTop = abs.top + activeSelectionDelta.y;
11873
+ const storePos = absoluteToStorePosition(nextAbsLeft, nextAbsTop, id, pageChildren2);
11874
+ updateNodeStore(id, { left: storePos.left, top: storePos.top }, { recordHistory: false, skipLayoutRecalc: true });
11875
+ });
11876
+ commitHistoryStore();
11877
+ activeObjects.forEach((obj) => {
11878
+ const objId = getObjectId(obj);
11879
+ if (objId && objId !== "__background__") {
11880
+ justModifiedIdsRef.current.add(objId);
11881
+ modifiedIdsThisRound.add(objId);
11882
+ }
11883
+ });
11884
+ setTimeout(() => modifiedIdsThisRound.forEach((id) => justModifiedIdsRef.current.delete(id)), 150);
11885
+ activeSelectionMoveStartRef.current = null;
11886
+ groupSelectionTransformStartRef.current = null;
11887
+ unlockEditsSoon();
11888
+ return;
11889
+ }
11890
+ }
11012
11891
  if (selectedElementIds.length > 0 && !anyCropGroup) {
11013
- const pageChildren2 = currentPage2.children ?? [];
11014
11892
  const firstObj = activeObjects[0];
11015
11893
  const firstId = getObjectId(firstObj);
11016
11894
  const parentGroups = selectedElementIds.map((id) => findParentGroup(pageChildren2, id)).filter((g) => g !== null);
@@ -11105,6 +11983,7 @@ const PageCanvas = react.forwardRef(
11105
11983
  }
11106
11984
  }
11107
11985
  }
11986
+ const pendingCropGroupFrameBakes = [];
11108
11987
  for (const obj of activeObjects) {
11109
11988
  const objId = getObjectId(obj);
11110
11989
  if (!objId || objId === "__background__") continue;
@@ -11158,10 +12037,16 @@ const PageCanvas = react.forwardRef(
11158
12037
  }
11159
12038
  if (obj instanceof fabric__namespace.Group && obj.__cropGroup) {
11160
12039
  const ct = obj.__cropData;
11161
- const w = (ct == null ? void 0 : ct.frameW) ?? (obj.width ?? 0) * (obj.scaleX ?? 1);
11162
- const h = (ct == null ? void 0 : ct.frameH) ?? (obj.height ?? 0) * (obj.scaleY ?? 1);
11163
- absoluteLeft = (absoluteLeft ?? 0) - w / 2;
11164
- absoluteTop = (absoluteTop ?? 0) - h / 2;
12040
+ if (isActiveSelection && activeObj instanceof fabric__namespace.ActiveSelection) {
12041
+ const frameBounds = getObjectFrameBoundsInSelection(activeObj, obj, ct == null ? void 0 : ct.frameW, ct == null ? void 0 : ct.frameH);
12042
+ absoluteLeft = frameBounds.left;
12043
+ absoluteTop = frameBounds.top;
12044
+ } else {
12045
+ const w = ((ct == null ? void 0 : ct.frameW) ?? obj.width ?? 0) * Math.abs(obj.scaleX ?? 1);
12046
+ const h = ((ct == null ? void 0 : ct.frameH) ?? obj.height ?? 0) * Math.abs(obj.scaleY ?? 1);
12047
+ absoluteLeft = (absoluteLeft ?? 0) - w / 2;
12048
+ absoluteTop = (absoluteTop ?? 0) - h / 2;
12049
+ }
11165
12050
  } else if (obj instanceof fabric__namespace.FabricImage && (obj.originX === "center" || obj.originY === "center")) {
11166
12051
  const w = (obj.width ?? 0) * (obj.scaleX ?? 1);
11167
12052
  const h = (obj.height ?? 0) * (obj.scaleY ?? 1);
@@ -11178,17 +12063,49 @@ const PageCanvas = react.forwardRef(
11178
12063
  if (obj instanceof fabric__namespace.Group && obj.__cropGroup) {
11179
12064
  const ct = obj.__cropData;
11180
12065
  if (ct) {
11181
- finalWidth = ct.frameW;
11182
- finalHeight = ct.frameH;
12066
+ const sourceFrameW = Math.max(1, ct.frameW ?? obj.width ?? 1);
12067
+ const sourceFrameH = Math.max(1, ct.frameH ?? obj.height ?? 1);
12068
+ const appliedScaleX = Math.abs(isActiveSelection && activeObj ? activeObj.scaleX ?? 1 : obj.scaleX ?? 1);
12069
+ const appliedScaleY = Math.abs(isActiveSelection && activeObj ? activeObj.scaleY ?? 1 : obj.scaleY ?? 1);
12070
+ finalWidth = Math.max(1, sourceFrameW * appliedScaleX);
12071
+ finalHeight = Math.max(1, sourceFrameH * appliedScaleY);
11183
12072
  finalScaleX = 1;
11184
12073
  finalScaleY = 1;
11185
- obj.set({ scaleX: 1, scaleY: 1 });
11186
- if (!isActiveSelection) {
12074
+ if (isActiveSelection && activeObj instanceof fabric__namespace.ActiveSelection) {
12075
+ const frameBounds = getObjectFrameBoundsInSelection(activeObj, obj, sourceFrameW, sourceFrameH);
12076
+ absoluteLeft = frameBounds.left;
12077
+ absoluteTop = frameBounds.top;
12078
+ } else {
12079
+ absoluteLeft = (decomposed.translateX ?? absoluteLeft) - finalWidth / 2;
12080
+ absoluteTop = (decomposed.translateY ?? absoluteTop) - finalHeight / 2;
12081
+ }
12082
+ finalAbsoluteMatrix = fabric__namespace.util.composeMatrix({
12083
+ translateX: absoluteLeft + finalWidth / 2,
12084
+ translateY: absoluteTop + finalHeight / 2,
12085
+ angle: decomposed.angle ?? (obj.angle ?? 0),
12086
+ scaleX: 1,
12087
+ scaleY: 1,
12088
+ skewX: 0,
12089
+ skewY: 0
12090
+ });
12091
+ if (isActiveSelection && activeObj instanceof fabric__namespace.ActiveSelection) {
12092
+ pendingCropGroupFrameBakes.push({
12093
+ obj,
12094
+ width: finalWidth,
12095
+ height: finalHeight,
12096
+ left: absoluteLeft,
12097
+ top: absoluteTop,
12098
+ angle: decomposed.angle ?? (obj.angle ?? 0)
12099
+ });
12100
+ } else {
12101
+ ct.frameW = finalWidth;
12102
+ ct.frameH = finalHeight;
12103
+ obj.set({ width: finalWidth, height: finalHeight, scaleX: 1, scaleY: 1 });
11187
12104
  updateCoverLayout(obj);
11188
- obj.__lastResizeHandle = null;
12105
+ }
12106
+ obj.__lastResizeHandle = null;
12107
+ if (!isActiveSelection) {
11189
12108
  fabricCanvas.setActiveObject(obj);
11190
- } else {
11191
- obj.__lastResizeHandle = null;
11192
12109
  }
11193
12110
  }
11194
12111
  } else if (obj instanceof fabric__namespace.FabricImage) {
@@ -11278,6 +12195,10 @@ const PageCanvas = react.forwardRef(
11278
12195
  if (obj.textPath) {
11279
12196
  elementUpdate.textPath = obj.textPath;
11280
12197
  }
12198
+ const bakedFs = obj.fontSize;
12199
+ if (typeof bakedFs === "number" && bakedFs > 0) {
12200
+ elementUpdate.fontSize = bakedFs;
12201
+ }
11281
12202
  }
11282
12203
  if (sourceElement && sourceElement.opacity !== void 0) {
11283
12204
  elementUpdate.opacity = sourceElement.opacity;
@@ -11285,7 +12206,7 @@ const PageCanvas = react.forwardRef(
11285
12206
  updateElement(objId, elementUpdate, { recordHistory: false, skipLayoutRecalc: true });
11286
12207
  obj.setCoords();
11287
12208
  }
11288
- const pageChildrenForReflow = ((_f = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _f.children) ?? [];
12209
+ const pageChildrenForReflow = ((_g = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _g.children) ?? [];
11289
12210
  const stackGroupsToReflow = /* @__PURE__ */ new Set();
11290
12211
  for (const id of modifiedIdsThisRound) {
11291
12212
  const parent = findParentGroup(pageChildrenForReflow, id);
@@ -11309,6 +12230,25 @@ const PageCanvas = react.forwardRef(
11309
12230
  } finally {
11310
12231
  skipActiveSelectionBakeOnClearRef.current = false;
11311
12232
  }
12233
+ for (const bake of pendingCropGroupFrameBakes) {
12234
+ const ct = bake.obj.__cropData;
12235
+ if (!ct) continue;
12236
+ ct.frameW = bake.width;
12237
+ ct.frameH = bake.height;
12238
+ bake.obj.set({
12239
+ left: bake.left + bake.width / 2,
12240
+ top: bake.top + bake.height / 2,
12241
+ width: bake.width,
12242
+ height: bake.height,
12243
+ scaleX: 1,
12244
+ scaleY: 1,
12245
+ angle: bake.angle,
12246
+ originX: "center",
12247
+ originY: "center"
12248
+ });
12249
+ updateCoverLayout(bake.obj);
12250
+ bake.obj.setCoords();
12251
+ }
11312
12252
  if (membersToReselect.length > 1) {
11313
12253
  const newSel = new fabric__namespace.ActiveSelection(membersToReselect, { canvas: fabricCanvas });
11314
12254
  if (wasGroupSel) restoreGroupSelectionVisualState(newSel, wasGroupSel);
@@ -11327,6 +12267,7 @@ const PageCanvas = react.forwardRef(
11327
12267
  fabricCanvas.requestRenderAll();
11328
12268
  }
11329
12269
  groupSelectionTransformStartRef.current = null;
12270
+ activeSelectionMoveStartRef.current = null;
11330
12271
  setTimeout(() => modifiedIdsThisRound.forEach((id) => justModifiedIdsRef.current.delete(id)), 150);
11331
12272
  commitHistory();
11332
12273
  unlockEditsSoon();
@@ -11355,6 +12296,7 @@ const PageCanvas = react.forwardRef(
11355
12296
  }
11356
12297
  });
11357
12298
  fabricCanvas.on("mouse:dblclick", (e) => {
12299
+ var _a2, _b;
11358
12300
  if (!isActiveRef.current || !allowEditing) return;
11359
12301
  let target = e.target;
11360
12302
  if (!target) {
@@ -11363,7 +12305,10 @@ const PageCanvas = react.forwardRef(
11363
12305
  }
11364
12306
  if (target && target instanceof fabric__namespace.Group && target.__cropGroup) {
11365
12307
  const ct = target.__cropData;
11366
- if ((ct == null ? void 0 : ct._img) && !isCropGroupInCropMode(target)) {
12308
+ const innerImg = ct == null ? void 0 : ct._img;
12309
+ const innerSrc = ((_a2 = innerImg == null ? void 0 : innerImg.getSrc) == null ? void 0 : _a2.call(innerImg)) || ((_b = innerImg == null ? void 0 : innerImg._originalElement) == null ? void 0 : _b.src) || (innerImg == null ? void 0 : innerImg.src) || "";
12310
+ const isPlaceholder = !innerSrc || innerSrc === EMPTY_IMAGE_PLACEHOLDER_DATA_URL;
12311
+ if (innerImg && !isPlaceholder && !isCropGroupInCropMode(target)) {
11367
12312
  enterCropMode(target);
11368
12313
  return;
11369
12314
  }
@@ -11597,14 +12542,7 @@ const PageCanvas = react.forwardRef(
11597
12542
  try {
11598
12543
  const newSel = new fabric__namespace.ActiveSelection(freshMembers, { canvas: fc });
11599
12544
  if (activeSelectionSnapshot.groupSelectionId) {
11600
- newSel.__pixldocsGroupSelection = activeSelectionSnapshot.groupSelectionId;
11601
- suppressGroupMemberBordersRef.current = freshMembers;
11602
- for (const m of freshMembers) {
11603
- if (m.__pixldocsOrigHasBorders === void 0) {
11604
- m.__pixldocsOrigHasBorders = m.hasBorders;
11605
- }
11606
- m.hasBorders = false;
11607
- }
12545
+ applyLogicalGroupSelectionVisualState(newSel, activeSelectionSnapshot.groupSelectionId);
11608
12546
  }
11609
12547
  fc.setActiveObject(newSel);
11610
12548
  newSel.setCoords();
@@ -11989,6 +12927,7 @@ const PageCanvas = react.forwardRef(
11989
12927
  continue;
11990
12928
  }
11991
12929
  if (existingObj instanceof fabric__namespace.Group && existingObj.__cropGroup) {
12930
+ updateFabricObject(existingObj, element, wasJustModified);
11992
12931
  existingObj.set({
11993
12932
  flipX: element.flipX ?? false,
11994
12933
  flipY: element.flipY ?? false,
@@ -11996,6 +12935,7 @@ const PageCanvas = react.forwardRef(
11996
12935
  });
11997
12936
  existingObj.setCoords();
11998
12937
  fc.requestRenderAll();
12938
+ if (wasJustModified) justModifiedIdsRef.current.delete(element.id);
11999
12939
  continue;
12000
12940
  }
12001
12941
  if (existingObj instanceof fabric__namespace.Textbox && wasJustModified && !syncTriggeredByPanelRef.current) {
@@ -12262,18 +13202,17 @@ const PageCanvas = react.forwardRef(
12262
13202
  }
12263
13203
  if (!shouldSkipUpdates2) {
12264
13204
  const dfsIds = [];
12265
- const visit = (nodes, reverseGroupChildren = false) => {
12266
- const list = reverseGroupChildren ? [...nodes].reverse() : nodes;
12267
- for (const n of list) {
13205
+ const visit = (nodes) => {
13206
+ for (const n of nodes) {
12268
13207
  if (isElement(n)) dfsIds.push(n.id);
12269
13208
  else if (isGroup(n)) {
12270
13209
  const g = n;
12271
13210
  if (sectionGroupIds.has(g.id)) dfsIds.push(g.id);
12272
- visit(g.children ?? [], true);
13211
+ visit(g.children ?? []);
12273
13212
  }
12274
13213
  }
12275
13214
  };
12276
- visit(pageTree, false);
13215
+ visit(pageTree);
12277
13216
  const allFabricObjects = fc.getObjects();
12278
13217
  allFabricObjects.sort((a, b) => {
12279
13218
  const aIndex = dfsIds.indexOf(getObjectId(a) || "");
@@ -12335,14 +13274,7 @@ const PageCanvas = react.forwardRef(
12335
13274
  try {
12336
13275
  const newSel = new fabric__namespace.ActiveSelection(freshMembers, { canvas: fc });
12337
13276
  if (activeSelectionSnapshot.groupSelectionId) {
12338
- newSel.__pixldocsGroupSelection = activeSelectionSnapshot.groupSelectionId;
12339
- suppressGroupMemberBordersRef.current = freshMembers;
12340
- for (const m of freshMembers) {
12341
- if (m.__pixldocsOrigHasBorders === void 0) {
12342
- m.__pixldocsOrigHasBorders = m.hasBorders;
12343
- }
12344
- m.hasBorders = false;
12345
- }
13277
+ applyLogicalGroupSelectionVisualState(newSel, activeSelectionSnapshot.groupSelectionId);
12346
13278
  }
12347
13279
  fc.setActiveObject(newSel);
12348
13280
  newSel.setCoords();
@@ -12534,12 +13466,24 @@ const PageCanvas = react.forwardRef(
12534
13466
  isSyncingSelectionToFabricRef.current = true;
12535
13467
  const selectedSet = new Set(selectedIds);
12536
13468
  const pageChildrenForSelection = ((_a2 = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _a2.children) ?? [];
12537
- const selectedGroupSelectionId = selectedIds.find((id) => {
13469
+ const selectedGroupIds = selectedIds.filter((id) => {
12538
13470
  const node = findNodeById(pageChildrenForSelection, id);
12539
13471
  return !!(node && isGroup(node));
12540
13472
  });
12541
- const selectedGroupNode = selectedGroupSelectionId ? findNodeById(pageChildrenForSelection, selectedGroupSelectionId) : null;
12542
- const selectedGroupMemberIds = selectedGroupNode && isGroup(selectedGroupNode) ? new Set(getAllElementIds(selectedGroupNode.children ?? [])) : null;
13473
+ const selectedGroupSelectionId = selectedGroupIds[0];
13474
+ const selectedGroupMemberIdsAll = /* @__PURE__ */ new Set();
13475
+ for (const gid of selectedGroupIds) {
13476
+ const groupNode = findNodeById(pageChildrenForSelection, gid);
13477
+ if (groupNode && isGroup(groupNode)) {
13478
+ getAllElementIds(groupNode.children ?? []).forEach((id) => selectedGroupMemberIdsAll.add(id));
13479
+ }
13480
+ }
13481
+ const standaloneSelectedIds = selectedIds.filter((id) => {
13482
+ if (selectedGroupIds.includes(id)) return false;
13483
+ if (selectedGroupMemberIdsAll.has(id)) return false;
13484
+ return !!findNodeById(pageChildrenForSelection, id);
13485
+ });
13486
+ const isPureSingleGroupSelection = selectedGroupIds.length === 1 && standaloneSelectedIds.length === 0;
12543
13487
  const toSelect = [];
12544
13488
  for (const obj of fc.getObjects()) {
12545
13489
  const id = getObjectId(obj);
@@ -12548,17 +13492,29 @@ const PageCanvas = react.forwardRef(
12548
13492
  toSelect.push(obj);
12549
13493
  continue;
12550
13494
  }
12551
- if (id && (selectedGroupMemberIds == null ? void 0 : selectedGroupMemberIds.has(id))) {
13495
+ if (id && selectedGroupMemberIdsAll.has(id)) {
12552
13496
  toSelect.push(obj);
12553
13497
  continue;
12554
13498
  }
12555
13499
  if (obj instanceof fabric__namespace.Group && obj.__docuforgeSectionGroup) {
12556
13500
  for (const child of obj.getObjects()) {
12557
13501
  const childId = getObjectId(child);
12558
- if (childId && (selectedSet.has(childId) || (selectedGroupMemberIds == null ? void 0 : selectedGroupMemberIds.has(childId)))) toSelect.push(child);
13502
+ if (childId && (selectedSet.has(childId) || selectedGroupMemberIdsAll.has(childId))) toSelect.push(child);
12559
13503
  }
12560
13504
  }
12561
13505
  }
13506
+ const restoreSuppressedBordersForSelectionSync = () => {
13507
+ const suppressed = suppressGroupMemberBordersRef.current;
13508
+ for (const member of suppressed) {
13509
+ const origBorders = member.__pixldocsOrigHasBorders;
13510
+ const origControls = member.__pixldocsOrigHasControls;
13511
+ member.hasBorders = origBorders !== void 0 ? origBorders : true;
13512
+ member.hasControls = origControls !== void 0 ? origControls : true;
13513
+ delete member.__pixldocsOrigHasBorders;
13514
+ delete member.__pixldocsOrigHasControls;
13515
+ }
13516
+ suppressGroupMemberBordersRef.current = [];
13517
+ };
12562
13518
  if (toSelect.length === 0) {
12563
13519
  if (!syncLockedRef.current && !editLockRef.current) {
12564
13520
  fc.discardActiveObject();
@@ -12569,6 +13525,12 @@ const PageCanvas = react.forwardRef(
12569
13525
  if (objId) {
12570
13526
  justModifiedIdsRef.current.add(objId);
12571
13527
  }
13528
+ if (!selectedGroupSelectionId) {
13529
+ restoreSuppressedBordersForSelectionSync();
13530
+ delete obj.__pixldocsGroupSelection;
13531
+ delete obj.__pixldocsLogicalGroupIds;
13532
+ obj.set({ selectable: true, evented: true, hasBorders: true, hasControls: true });
13533
+ }
12572
13534
  fc.setActiveObject(obj);
12573
13535
  obj.setCoords();
12574
13536
  } else {
@@ -12577,13 +13539,39 @@ const PageCanvas = react.forwardRef(
12577
13539
  const sameSelection = active instanceof fabric__namespace.ActiveSelection && active.getObjects().length === toSelect.length && toSelect.every((obj) => active.getObjects().includes(obj));
12578
13540
  if (sameSelection && isFlatGroupSelection) {
12579
13541
  if (selectedGroupSelectionId && active instanceof fabric__namespace.ActiveSelection) {
12580
- active.__pixldocsGroupSelection = selectedGroupSelectionId;
12581
- suppressGroupMemberBordersRef.current = active.getObjects();
12582
- active.getObjects().forEach((m) => {
13542
+ if (isPureSingleGroupSelection) {
13543
+ active.__pixldocsGroupSelection = selectedGroupSelectionId;
13544
+ delete active.__pixldocsLogicalGroupIds;
13545
+ suppressGroupMemberBordersRef.current = active.getObjects();
13546
+ } else {
13547
+ delete active.__pixldocsGroupSelection;
13548
+ active.__pixldocsLogicalGroupIds = selectedGroupIds;
13549
+ active.hasBorders = true;
13550
+ suppressGroupMemberBordersRef.current = active.getObjects().filter((m) => {
13551
+ const id = getObjectId(m);
13552
+ return !!id && selectedGroupMemberIdsAll.has(id);
13553
+ });
13554
+ }
13555
+ suppressGroupMemberBordersRef.current.forEach((m) => {
13556
+ var _a3;
12583
13557
  if (m.__pixldocsOrigHasBorders === void 0) m.__pixldocsOrigHasBorders = m.hasBorders;
13558
+ if (m.__pixldocsOrigHasControls === void 0) m.__pixldocsOrigHasControls = m.hasControls;
12584
13559
  m.hasBorders = false;
13560
+ m.hasControls = false;
13561
+ if (m.__cropGroup || ((_a3 = m._ct) == null ? void 0 : _a3.isCropGroup)) {
13562
+ if (m.__pixldocsOrigLockScalingX === void 0) {
13563
+ m.__pixldocsOrigLockScalingX = m.lockScalingX;
13564
+ m.__pixldocsOrigLockScalingY = m.lockScalingY;
13565
+ }
13566
+ m.lockScalingX = false;
13567
+ m.lockScalingY = false;
13568
+ }
12585
13569
  });
12586
- applyWarpAwareSelectionBorders(active);
13570
+ if (isPureSingleGroupSelection) {
13571
+ active.hasBorders = true;
13572
+ active.setCoords();
13573
+ applyWarpAwareSelectionBorders(active);
13574
+ }
12587
13575
  }
12588
13576
  fc.requestRenderAll();
12589
13577
  } else {
@@ -12593,13 +13581,33 @@ const PageCanvas = react.forwardRef(
12593
13581
  });
12594
13582
  const selection = new fabric__namespace.ActiveSelection(toSelect, { canvas: fc });
12595
13583
  if (selectedGroupSelectionId) {
12596
- selection.__pixldocsGroupSelection = selectedGroupSelectionId;
12597
- suppressGroupMemberBordersRef.current = toSelect;
12598
- toSelect.forEach((m) => {
13584
+ if (isPureSingleGroupSelection) {
13585
+ selection.__pixldocsGroupSelection = selectedGroupSelectionId;
13586
+ suppressGroupMemberBordersRef.current = toSelect;
13587
+ } else {
13588
+ selection.__pixldocsLogicalGroupIds = selectedGroupIds;
13589
+ selection.hasBorders = true;
13590
+ suppressGroupMemberBordersRef.current = toSelect.filter((m) => {
13591
+ const id = getObjectId(m);
13592
+ return !!id && selectedGroupMemberIdsAll.has(id);
13593
+ });
13594
+ }
13595
+ suppressGroupMemberBordersRef.current.forEach((m) => {
13596
+ var _a3;
12599
13597
  if (m.__pixldocsOrigHasBorders === void 0) m.__pixldocsOrigHasBorders = m.hasBorders;
13598
+ if (m.__pixldocsOrigHasControls === void 0) m.__pixldocsOrigHasControls = m.hasControls;
12600
13599
  m.hasBorders = false;
13600
+ m.hasControls = false;
13601
+ if (m.__cropGroup || ((_a3 = m._ct) == null ? void 0 : _a3.isCropGroup)) {
13602
+ if (m.__pixldocsOrigLockScalingX === void 0) {
13603
+ m.__pixldocsOrigLockScalingX = m.lockScalingX;
13604
+ m.__pixldocsOrigLockScalingY = m.lockScalingY;
13605
+ }
13606
+ m.lockScalingX = false;
13607
+ m.lockScalingY = false;
13608
+ }
12601
13609
  });
12602
- applyWarpAwareSelectionBorders(selection);
13610
+ if (isPureSingleGroupSelection) applyWarpAwareSelectionBorders(selection);
12603
13611
  }
12604
13612
  fc.setActiveObject(selection);
12605
13613
  if (!isFlatGroupSelection) {
@@ -12905,7 +13913,7 @@ const PageCanvas = react.forwardRef(
12905
13913
  const angleTextPathActive = isTextbox && ((_b = element.textPath) == null ? void 0 : _b.preset) === "rise";
12906
13914
  const appliedSkewY = angleTextPathActive ? 0 : element.skewY ?? 0;
12907
13915
  let posIfNotSkipped = skipPositionUpdate ? {} : { left: fabricPos.left, top: fabricPos.top };
12908
- if (!skipPositionUpdate && obj instanceof fabric__namespace.FabricImage && obj.originX === "center") {
13916
+ if (!skipPositionUpdate && (obj instanceof fabric__namespace.FabricImage && obj.originX === "center" || obj instanceof fabric__namespace.Group && obj.__cropGroup)) {
12909
13917
  const vW = rW * effectiveScaleX;
12910
13918
  const vH = rH * effectiveScaleY;
12911
13919
  posIfNotSkipped = { left: fabricPos.left + vW / 2, top: fabricPos.top + vH / 2 };
@@ -13084,7 +14092,23 @@ const PageCanvas = react.forwardRef(
13084
14092
  objectCaching: false,
13085
14093
  noScaleCache: true,
13086
14094
  splitByGrapheme,
13087
- text
14095
+ text,
14096
+ // Text outline (stroke). Only apply when BOTH a stroke color and
14097
+ // positive width are explicitly set — otherwise fabric defaults
14098
+ // stroke to black and renders an unwanted outline on existing
14099
+ // text elements that carry a stray strokeWidth.
14100
+ ...(() => {
14101
+ const s = element.stroke;
14102
+ const w = Number(element.strokeWidth) || 0;
14103
+ const has = !!s && w > 0;
14104
+ return has ? {
14105
+ stroke: s,
14106
+ strokeWidth: w,
14107
+ paintFirst: element.paintFirst || "fill",
14108
+ strokeUniform: true,
14109
+ strokeLineJoin: "round"
14110
+ } : { stroke: void 0, strokeWidth: 0 };
14111
+ })()
13088
14112
  });
13089
14113
  const valign = element.verticalAlign || "top";
13090
14114
  const minBoxH = Math.max(0, Number(element.minBoxHeight) || 0);
@@ -13740,7 +14764,10 @@ const PageCanvas = react.forwardRef(
13740
14764
  let panX = element.cropPanX ?? 0.5;
13741
14765
  let panY = element.cropPanY ?? 0.5;
13742
14766
  let zoom2 = element.cropZoom ?? 1;
13743
- if (existingCropGroup) {
14767
+ const elementHasExplicitCrop = element.cropPanX != null || element.cropPanY != null || element.cropZoom != null;
14768
+ const existingGroupSource = existingCropGroup ? String(existingCropGroup.__imageSrc || "") : "";
14769
+ const canPreserveLiveCrop = Boolean(existingCropGroup) && (elementHasExplicitCrop || existingGroupSource === imageUrl);
14770
+ if (canPreserveLiveCrop && existingCropGroup) {
13744
14771
  const existingImg = (_j = existingCropGroup.__cropData) == null ? void 0 : _j._img;
13745
14772
  if (existingImg) {
13746
14773
  panX = ((_k = existingImg._ct) == null ? void 0 : _k.panX) ?? existingImg.__panX ?? panX;
@@ -15081,6 +16108,19 @@ function setInTree(nodes, elementId, targetProperty, value) {
15081
16108
  } else if (targetProperty === "text" && node.type === "text" && typeof value === "string") {
15082
16109
  const textVal = value === "" ? " " : value;
15083
16110
  node[targetProperty] = applyTextCase(textVal, node.textCase);
16111
+ } else if ((targetProperty === "src" || targetProperty === "imageUrl") && node.type === "image") {
16112
+ const previousSrc = String(node.src || node.imageUrl || "");
16113
+ const nextSrc = String(value ?? "");
16114
+ const srcChanged = nextSrc !== "" && nextSrc !== previousSrc;
16115
+ node.src = value;
16116
+ node.imageUrl = value;
16117
+ if (srcChanged) {
16118
+ delete node.imageNaturalWidth;
16119
+ delete node.imageNaturalHeight;
16120
+ delete node.cropPanX;
16121
+ delete node.cropPanY;
16122
+ delete node.cropZoom;
16123
+ }
15084
16124
  } else {
15085
16125
  node[targetProperty] = value;
15086
16126
  }
@@ -15090,10 +16130,6 @@ function setInTree(nodes, elementId, targetProperty, value) {
15090
16130
  delete node.height;
15091
16131
  }
15092
16132
  }
15093
- if ((targetProperty === "src" || targetProperty === "imageUrl") && node.type === "image") {
15094
- delete node.imageNaturalWidth;
15095
- delete node.imageNaturalHeight;
15096
- }
15097
16133
  return true;
15098
16134
  }
15099
16135
  if (node.children && Array.isArray(node.children)) {
@@ -19881,10 +20917,11 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
19881
20917
  for (const obj of objs) {
19882
20918
  const isGroupLike = obj instanceof fabric__namespace.Group || (obj == null ? void 0 : obj.type) === "group" || Array.isArray(obj == null ? void 0 : obj._objects);
19883
20919
  const isFadedCropGroup = isGroupLike && (Boolean(obj.__edgeFadeRenderConfig) || Boolean(obj.__edgeFadeKey) || Boolean(obj.__edgeFadeInputKey));
19884
- if (!isFadedCropGroup) continue;
20920
+ const hasSvgMaskClip = isGroupLike && obj.__cropGroup && obj.clipPath && (obj.clipPath.__svgMask === true || obj.clipPath.__svgMaskUrl || obj.clipPath.__svgMaskType || obj.__svgMaskUrl || obj.__svgMaskType);
20921
+ if (!isFadedCropGroup && !hasSvgMaskClip) continue;
19885
20922
  try {
19886
20923
  const baked = obj.toCanvasElement({
19887
- multiplier: 2,
20924
+ multiplier: hasSvgMaskClip ? 4 : 2,
19888
20925
  enableRetinaScaling: false
19889
20926
  });
19890
20927
  const rect = obj.getBoundingRect();
@@ -19957,9 +20994,9 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
19957
20994
  }
19958
20995
  return svgString;
19959
20996
  }
19960
- const resolvedPackageVersion = "0.5.225";
20997
+ const resolvedPackageVersion = "0.5.226";
19961
20998
  const PACKAGE_VERSION = resolvedPackageVersion;
19962
- const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.225";
20999
+ const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.226";
19963
21000
  const roundParityValue = (value) => {
19964
21001
  if (typeof value !== "number") return value;
19965
21002
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -20703,7 +21740,7 @@ class PixldocsRenderer {
20703
21740
  await this.waitForCanvasScene(container, cloned, i);
20704
21741
  }
20705
21742
  console.log(`[canvas-renderer][pdf-unified] mounted ${cloned.pages.length} page(s), handing off to client exportMultiPagePdf`);
20706
- const { exportMultiPagePdf, preparePagesForExport } = await Promise.resolve().then(() => require("./vectorPdfExport-CVeK--lR.cjs"));
21743
+ const { exportMultiPagePdf, preparePagesForExport } = await Promise.resolve().then(() => require("./vectorPdfExport-C18SGeJb.cjs"));
20707
21744
  const prepared = preparePagesForExport(
20708
21745
  cloned.pages,
20709
21746
  canvasWidth,
@@ -23023,7 +24060,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
23023
24060
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
23024
24061
  sanitizeSvgTreeForPdf(svgToDraw);
23025
24062
  try {
23026
- const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await Promise.resolve().then(() => require("./vectorPdfExport-CVeK--lR.cjs"));
24063
+ const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await Promise.resolve().then(() => require("./vectorPdfExport-C18SGeJb.cjs"));
23027
24064
  try {
23028
24065
  await logTextMeasurementDiagnostic(svgToDraw);
23029
24066
  } catch {
@@ -23419,4 +24456,4 @@ exports.setAutoShrinkDebug = setAutoShrinkDebug;
23419
24456
  exports.setBundledAssetPrefixes = setBundledAssetPrefixes;
23420
24457
  exports.warmResolvedTemplateForPreview = warmResolvedTemplateForPreview;
23421
24458
  exports.warmTemplateFromForm = warmTemplateFromForm;
23422
- //# sourceMappingURL=index-6nrov1rx.cjs.map
24459
+ //# sourceMappingURL=index-CXOoVCq1.cjs.map