@pixldocs/canvas-renderer 0.5.225 → 0.5.227

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.
@@ -3,7 +3,7 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  var _a;
5
5
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
6
- import { forwardRef, useRef, useState, useMemo, useEffect, useCallback, useImperativeHandle, createElement } from "react";
6
+ import { forwardRef, useRef, useState, useCallback, useMemo, useEffect, useImperativeHandle, createElement } from "react";
7
7
  import { flushSync } from "react-dom";
8
8
  import { toast } from "sonner";
9
9
  import { create } from "zustand";
@@ -436,6 +436,148 @@ function fallbackEstimateHeight(element) {
436
436
  function clearMeasurementCache() {
437
437
  heightCache.clear();
438
438
  }
439
+ function computeAutoShrinkFontSize(element) {
440
+ const baseFontSize = element.fontSize || 16;
441
+ if (element.overflowPolicy !== "auto-shrink") return baseFontSize;
442
+ const text = element.text || element.content || "";
443
+ if (!text) return baseFontSize;
444
+ const width = element.width || 200;
445
+ const height = element.height;
446
+ if (!height) return baseFontSize;
447
+ let fontSize = baseFontSize;
448
+ try {
449
+ while (fontSize > 1) {
450
+ const testTb = new fabric.Textbox(text, {
451
+ width,
452
+ fontSize,
453
+ fontFamily: element.fontFamily || "Open Sans",
454
+ fontWeight: element.fontWeight || 400,
455
+ fontStyle: element.fontStyle || "normal",
456
+ lineHeight: element.lineHeight || 1.2,
457
+ charSpacing: element.charSpacing || 0,
458
+ splitByGrapheme: false
459
+ });
460
+ testTb.initDimensions();
461
+ const textHeight = testTb.height || 0;
462
+ const fitsHeight = textHeight <= height;
463
+ const lineWidths = testTb.__lineWidths;
464
+ const maxLineWidth = lineWidths && lineWidths.length > 0 ? Math.max(...lineWidths) : 0;
465
+ const fitsWidth = maxLineWidth <= width + 1;
466
+ if (fitsHeight && fitsWidth) break;
467
+ fontSize--;
468
+ }
469
+ } catch (e) {
470
+ console.warn("[autoShrink] Failed to compute shrunk font size:", e);
471
+ return baseFontSize;
472
+ }
473
+ return fontSize;
474
+ }
475
+ function measureTextGlyphHeightForStackHug(el) {
476
+ const text = el.text || " ";
477
+ const width = Math.max(1, typeof el.width === "number" ? el.width : 200);
478
+ let fontSize = el.fontSize || 16;
479
+ const lineHeight = el.lineHeight || 1.2;
480
+ const charSpacing = el.charSpacing || 0;
481
+ const fontFamily = el.fontFamily || "Open Sans";
482
+ const fontWeight = el.fontWeight || 400;
483
+ const fontStyle = el.fontStyle || "normal";
484
+ const isAutoShrink = el.overflowPolicy === "auto-shrink";
485
+ const splitByGrapheme = isAutoShrink ? false : el.splitByGrapheme ?? el.wordWrap === "break-word";
486
+ if (isAutoShrink) {
487
+ const minBoxH = Math.max(0, Number(el.minBoxHeight) || 0);
488
+ const heightBound = Math.max(typeof el.height === "number" ? el.height : 0, minBoxH);
489
+ while (fontSize > 1) {
490
+ const testTb = new fabric.Textbox(text, {
491
+ width,
492
+ fontSize,
493
+ fontFamily,
494
+ fontWeight,
495
+ fontStyle,
496
+ lineHeight,
497
+ charSpacing,
498
+ splitByGrapheme: false
499
+ });
500
+ testTb.initDimensions();
501
+ const textHeight = testTb.height || 0;
502
+ const lineWidths = getCanvasMeasuredTextboxLineWidths(testTb);
503
+ const maxLineWidth = lineWidths.length > 0 ? Math.max(...lineWidths) : 0;
504
+ if ((heightBound <= 0 || textHeight <= heightBound) && maxLineWidth <= width + 1) break;
505
+ fontSize--;
506
+ }
507
+ }
508
+ const textbox = new fabric.Textbox(text, {
509
+ width,
510
+ fontSize,
511
+ fontFamily,
512
+ fontWeight,
513
+ fontStyle,
514
+ lineHeight,
515
+ charSpacing,
516
+ splitByGrapheme
517
+ });
518
+ textbox.initDimensions();
519
+ return Math.max(1, (textbox.height || 20) * (el.scaleY || 1));
520
+ }
521
+ function getStackChildLayoutSize(parent, child, pageChildren, options) {
522
+ const b = getNodeBounds(child, pageChildren);
523
+ if (!parent.hugContent) return { width: b.width, height: b.height };
524
+ if (!isElement(child)) return { width: b.width, height: b.height };
525
+ const el = child;
526
+ if (el.type !== "text") return { width: b.width, height: b.height };
527
+ const isHorizontal = parent.layoutMode === "horizontal-stack";
528
+ if (isHorizontal) {
529
+ try {
530
+ const isAutoShrink = el.overflowPolicy === "auto-shrink";
531
+ const effectiveFontSize = isAutoShrink ? computeAutoShrinkFontSize(el) : el.fontSize || 16;
532
+ const probeWidth = isAutoShrink && typeof el.width === "number" ? el.width : 1e4;
533
+ const probe = new fabric.Textbox(el.text || " ", {
534
+ width: probeWidth,
535
+ fontSize: effectiveFontSize,
536
+ fontFamily: el.fontFamily || "Open Sans",
537
+ fontWeight: el.fontWeight || 400,
538
+ fontStyle: el.fontStyle || "normal",
539
+ lineHeight: el.lineHeight || 1.2,
540
+ charSpacing: el.charSpacing || 0,
541
+ splitByGrapheme: el.splitByGrapheme ?? el.wordWrap === "break-word"
542
+ });
543
+ probe.initDimensions();
544
+ const lineWidths = getCanvasMeasuredTextboxLineWidths(probe);
545
+ const maxLine = lineWidths.length > 0 ? Math.max(...lineWidths) : typeof el.width === "number" ? el.width : b.width;
546
+ const measuredW = Math.ceil(maxLine) + 2;
547
+ const w = Math.max(1, measuredW * (el.scaleX || 1));
548
+ return { width: w, height: b.height };
549
+ } catch {
550
+ return { width: b.width, height: b.height };
551
+ }
552
+ }
553
+ try {
554
+ const measuredH = measureTextGlyphHeightForStackHug(el);
555
+ const h = Math.max(1, measuredH);
556
+ return { width: b.width, height: h };
557
+ } catch {
558
+ return { width: b.width, height: b.height };
559
+ }
560
+ }
561
+ function getVerticalHugTextAdjust(parent, child) {
562
+ if (!parent.hugContent) return null;
563
+ if (parent.layoutMode !== "vertical-stack" && parent.layoutMode !== "stack") return null;
564
+ if (!isElement(child)) return null;
565
+ const el = child;
566
+ if (el.type !== "text") return null;
567
+ try {
568
+ const glyphHeight = measureTextGlyphHeightForStackHug(el);
569
+ const minBoxHeight = Math.max(0, Number(el.minBoxHeight) || 0);
570
+ const boxHeight = Math.max(typeof el.height === "number" ? el.height : glyphHeight, minBoxHeight);
571
+ const vAlign = el.verticalAlign ?? "top";
572
+ const extra = Math.max(0, boxHeight - glyphHeight);
573
+ let topOffset = 0;
574
+ if (vAlign === "middle" || vAlign === "center") topOffset = extra / 2;
575
+ else if (vAlign === "bottom") topOffset = extra;
576
+ return { topOffset, glyphHeight };
577
+ } catch {
578
+ return null;
579
+ }
580
+ }
439
581
  function simpleWidth(node) {
440
582
  if (isElement(node)) {
441
583
  const w = node.width;
@@ -476,23 +618,33 @@ function resolveStackGroupEffectivePositions(group, pageChildren, options) {
476
618
  const out = /* @__PURE__ */ new Map();
477
619
  const sizes = /* @__PURE__ */ new Map();
478
620
  for (const c of kids) {
479
- const b = getNodeBounds(c, pageChildren);
480
- sizes.set(c.id, { width: b.width, height: b.height });
621
+ sizes.set(c.id, getStackChildLayoutSize(group, c, pageChildren));
481
622
  }
482
623
  if (isVertical) {
483
624
  let prevBottom = padTop;
484
625
  let firstSeen = false;
626
+ const placedIds = [];
485
627
  for (let i = 0; i < kids.length; i++) {
486
628
  const child = kids[i];
487
629
  const storedTop = getNodeTop(child);
488
630
  const storedLeft = getNodeLeft(child);
489
631
  const mTop = child.marginTop ?? 0;
490
632
  const mLeft = child.marginLeft ?? 0;
491
- const effectiveTop = !firstSeen ? padTop + storedTop + mTop : prevBottom + gap + storedTop + mTop;
633
+ const hugAdjust = getVerticalHugTextAdjust(group, child);
634
+ const hugTopOffset = hugAdjust ? hugAdjust.topOffset : 0;
635
+ const visualTop = !firstSeen ? padTop + storedTop + mTop : prevBottom + gap + storedTop + mTop;
636
+ const effectiveTop = firstSeen ? visualTop : visualTop - hugTopOffset;
637
+ if (firstSeen && hugTopOffset > 0 && placedIds.length > 0) {
638
+ for (const placedId of placedIds) {
639
+ const placed = out.get(placedId);
640
+ if (placed) out.set(placedId, { ...placed, top: placed.top + hugTopOffset });
641
+ }
642
+ }
492
643
  firstSeen = true;
493
644
  out.set(child.id, { top: effectiveTop, left: padLeft + storedLeft + mLeft });
494
- const h = sizes.get(child.id).height;
495
- prevBottom = effectiveTop + h + (child.marginBottom ?? 0);
645
+ const h = hugAdjust ? hugAdjust.glyphHeight : sizes.get(child.id).height;
646
+ prevBottom = effectiveTop + hugTopOffset + h + (child.marginBottom ?? 0);
647
+ placedIds.push(child.id);
496
648
  }
497
649
  } else {
498
650
  let prevRight = padLeft;
@@ -513,7 +665,20 @@ function resolveStackGroupEffectivePositions(group, pageChildren, options) {
513
665
  const containerW = typeof group.width === "number" ? group.width : void 0;
514
666
  const containerH = typeof group.height === "number" ? group.height : void 0;
515
667
  const mainContainer = isVertical ? containerH : containerW;
516
- const crossContainer = isVertical ? containerW : containerH;
668
+ let crossContainer = isVertical ? containerW : containerH;
669
+ if (kids.length > 0) {
670
+ let maxCross = 0;
671
+ for (const c of kids) {
672
+ const sz = sizes.get(c.id);
673
+ if (!sz) continue;
674
+ const cross = isVertical ? sz.width : sz.height;
675
+ if (cross > maxCross) maxCross = cross;
676
+ }
677
+ const crossPad0 = isVertical ? padLeft : padTop;
678
+ const crossPadEnd = isVertical ? padRight : padBottom;
679
+ const computed = maxCross + crossPad0 + crossPadEnd;
680
+ crossContainer = crossContainer != null ? Math.max(crossContainer, computed) : computed;
681
+ }
517
682
  if (align !== "start" && crossContainer != null && kids.length > 0) {
518
683
  const crossPad0 = isVertical ? padLeft : padTop;
519
684
  const crossPadEnd = isVertical ? padRight : padBottom;
@@ -591,7 +756,8 @@ function resolveStackGroupEffectivePositions(group, pageChildren, options) {
591
756
  if (!pos) continue;
592
757
  const startMain = cursor + marginStart;
593
758
  if (isVertical) {
594
- out.set(child.id, { top: startMain, left: pos.left });
759
+ const hugAdjust = getVerticalHugTextAdjust(group, child);
760
+ out.set(child.id, { top: startMain - ((hugAdjust == null ? void 0 : hugAdjust.topOffset) ?? 0), left: pos.left });
595
761
  } else {
596
762
  out.set(child.id, { top: pos.top, left: startMain });
597
763
  }
@@ -613,13 +779,16 @@ function groupBoundsFromChildren(group, pageChildren, options) {
613
779
  const positions = isStack ? resolveStackGroupEffectivePositions(group, pageChildren) : null;
614
780
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
615
781
  for (const child of kids) {
616
- const b = getNodeBounds(child, pageChildren);
782
+ const sz = isStack ? getStackChildLayoutSize(group, child, pageChildren) : (() => {
783
+ const b = getNodeBounds(child, pageChildren);
784
+ return { width: b.width, height: b.height };
785
+ })();
617
786
  const cl = positions ? ((_a2 = positions.get(child.id)) == null ? void 0 : _a2.left) ?? getNodeLeft(child) : getNodeLeft(child);
618
787
  const ct = positions ? ((_b = positions.get(child.id)) == null ? void 0 : _b.top) ?? getNodeTop(child) : getNodeTop(child);
619
788
  minX = Math.min(minX, cl);
620
789
  minY = Math.min(minY, ct);
621
- maxX = Math.max(maxX, cl + b.width);
622
- maxY = Math.max(maxY, ct + b.height);
790
+ maxX = Math.max(maxX, cl + sz.width);
791
+ maxY = Math.max(maxY, ct + sz.height);
623
792
  }
624
793
  if (isStack) {
625
794
  const padRight = group.paddingRight ?? 0;
@@ -1040,6 +1209,7 @@ const useEditorStore = create((set, get) => ({
1040
1209
  showDynamicLabels: false,
1041
1210
  showSections: false,
1042
1211
  hoveredGroupId: null,
1212
+ revealedLayerId: null,
1043
1213
  history: [cloneCanvas(initialCanvas)],
1044
1214
  historyIndex: 0,
1045
1215
  lastCommittedSignature: canvasSignature(initialCanvas),
@@ -1579,7 +1749,16 @@ const useEditorStore = create((set, get) => ({
1579
1749
  let nextChildren = currentPage.children;
1580
1750
  const newNodes = [];
1581
1751
  const duplicateAtPageLevel = [];
1582
- for (const id of ids) {
1752
+ const idSet = new Set(ids);
1753
+ const filteredIds = ids.filter((id) => {
1754
+ let p = findParentGroup(currentPage.children, id);
1755
+ while (p) {
1756
+ if (idSet.has(p.id)) return false;
1757
+ p = findParentGroup(currentPage.children, p.id);
1758
+ }
1759
+ return true;
1760
+ });
1761
+ for (const id of filteredIds) {
1583
1762
  const node = findNodeById(nextChildren, id);
1584
1763
  if (!node) continue;
1585
1764
  const parent = findParentGroup(nextChildren, id);
@@ -1596,19 +1775,9 @@ const useEditorStore = create((set, get) => ({
1596
1775
  }
1597
1776
  const pageLevelIds = new Set(duplicateAtPageLevel);
1598
1777
  if (pageLevelIds.size > 0) {
1599
- const PAGE_MARGIN = 48;
1600
- const PAGE_GAP = 16;
1601
- let maxBottom = PAGE_MARGIN;
1602
- for (const node of currentPage.children) {
1603
- const b = getNodeBounds(node, currentPage.children);
1604
- if (b.bottom > maxBottom) maxBottom = b.bottom;
1605
- }
1606
- const pageLevelClones = newNodes.filter((n) => pageLevelIds.has(n.id));
1607
- const bbox = getBoundingBoxOfRoots(pageLevelClones);
1608
- const shiftX = PAGE_MARGIN - bbox.left;
1609
- const shiftY = maxBottom + PAGE_GAP - bbox.top;
1778
+ const DUPLICATE_NUDGE = 16;
1610
1779
  nextChildren = nextChildren.map(
1611
- (node) => pageLevelIds.has(node.id) ? shiftNodeBy(node, shiftX, shiftY) : node
1780
+ (node) => pageLevelIds.has(node.id) ? shiftNodeBy(node, DUPLICATE_NUDGE, DUPLICATE_NUDGE) : node
1612
1781
  );
1613
1782
  }
1614
1783
  let nextCanvas = updateCurrentPageChildren(state.canvas, () => nextChildren);
@@ -2025,12 +2194,19 @@ const useEditorStore = create((set, get) => ({
2025
2194
  if (firstNodeIndex >= 0) {
2026
2195
  insertIndex = firstNodeIndex;
2027
2196
  }
2197
+ } else if (!commonParent && topLevelIds.length > 0) {
2198
+ const firstNodeIndex = currentPage.children.findIndex((child) => child.id === topLevelIds[0]);
2199
+ if (firstNodeIndex >= 0) {
2200
+ insertIndex = firstNodeIndex;
2201
+ }
2028
2202
  }
2029
- const pageChildrenForBounds = currentPage.children;
2203
+ const orderSource = commonParent ? commonParent.children : currentPage.children;
2204
+ const orderIndex = /* @__PURE__ */ new Map();
2205
+ orderSource.forEach((n, i) => orderIndex.set(n.id, i));
2030
2206
  nodesToGroup.sort((a, b) => {
2031
- const aTop = getAbsoluteBounds(a, pageChildrenForBounds).top ?? 0;
2032
- const bTop = getAbsoluteBounds(b, pageChildrenForBounds).top ?? 0;
2033
- return aTop - bTop;
2207
+ const ai = orderIndex.has(a.id) ? orderIndex.get(a.id) : Number.MAX_SAFE_INTEGER;
2208
+ const bi = orderIndex.has(b.id) ? orderIndex.get(b.id) : Number.MAX_SAFE_INTEGER;
2209
+ return ai - bi;
2034
2210
  });
2035
2211
  const pageChildren = currentPage.children;
2036
2212
  let groupLeft = Infinity, groupTop = Infinity;
@@ -2121,8 +2297,7 @@ const useEditorStore = create((set, get) => ({
2121
2297
  const dt = target.top - cur.top;
2122
2298
  return updateNodeInTree(t, nodeId, { left: dl, top: dt });
2123
2299
  };
2124
- const insertReversed = parentId === null;
2125
- const promotionOrder = insertReversed ? group.children.map((_, i) => group.children.length - 1 - i) : group.children.map((_, i) => i);
2300
+ const promotionOrder = group.children.map((_, i) => i);
2126
2301
  for (let slot = 0; slot < promotionOrder.length; slot++) {
2127
2302
  const i = promotionOrder[slot];
2128
2303
  const child = group.children[i];
@@ -2166,6 +2341,18 @@ const useEditorStore = create((set, get) => ({
2166
2341
  }
2167
2342
  return { collapsedGroups: newCollapsedSet };
2168
2343
  }),
2344
+ revealLayerNode: (id) => set((state) => {
2345
+ if (!id) return { revealedLayerId: null };
2346
+ const currentPage = getCurrentPageFromCanvas(state.canvas);
2347
+ const next = new Set(state.collapsedGroups);
2348
+ let parent = findParentGroup(currentPage.children, id);
2349
+ let guard = 0;
2350
+ while (parent && guard++ < 32) {
2351
+ next.delete(parent.id);
2352
+ parent = findParentGroup(currentPage.children, parent.id);
2353
+ }
2354
+ return { collapsedGroups: next, revealedLayerId: id };
2355
+ }),
2169
2356
  // Convenience aliases
2170
2357
  groupElements: (ids, name) => get().groupNodes(ids, name),
2171
2358
  ungroupElements: (groupId) => get().ungroupNodes(groupId),
@@ -2612,7 +2799,7 @@ const useEditorStore = create((set, get) => ({
2612
2799
  boundFormDefId: config.boundFormDefId,
2613
2800
  boundFormDefName: config.boundFormDefName,
2614
2801
  themeConfig: config.themeConfig ?? void 0,
2615
- pdfTextMode: config.pdfTextMode ?? "selectable"
2802
+ pdfTextMode: config.pdfTextMode ?? "auto"
2616
2803
  };
2617
2804
  const committed = commitFromState(state, nextCanvas);
2618
2805
  const out = {
@@ -4218,6 +4405,9 @@ async function loadSvgAsGroup(url) {
4218
4405
  obj.fill = "#000";
4219
4406
  obj.stroke = null;
4220
4407
  obj.strokeWidth = 0;
4408
+ obj.objectCaching = false;
4409
+ obj.statefullCache = false;
4410
+ obj.noScaleCache = true;
4221
4411
  }
4222
4412
  const group = new fabric.Group(objects, {
4223
4413
  originX: "center",
@@ -4225,8 +4415,14 @@ async function loadSvgAsGroup(url) {
4225
4415
  selectable: false,
4226
4416
  evented: false,
4227
4417
  hasControls: false,
4228
- hasBorders: false
4418
+ hasBorders: false,
4419
+ // Do NOT cache the mask group — Fabric would otherwise bake it to a
4420
+ // bitmap at the group's natural size, so the clipPath edges look soft
4421
+ // / pixelated whenever the host image is scaled up on the canvas.
4422
+ objectCaching: false
4229
4423
  });
4424
+ group.statefullCache = false;
4425
+ group.noScaleCache = true;
4230
4426
  const viewBoxW = Number(options.width) || group.width || 1;
4231
4427
  const viewBoxH = Number(options.height) || group.height || 1;
4232
4428
  return { group, viewBoxW, viewBoxH };
@@ -4244,8 +4440,18 @@ function fitMaskGroupToFrame(maskGroup, frameW, frameH) {
4244
4440
  selectable: false,
4245
4441
  evented: false,
4246
4442
  hasControls: false,
4247
- hasBorders: false
4443
+ hasBorders: false,
4444
+ objectCaching: false
4248
4445
  });
4446
+ const stack = [maskGroup];
4447
+ while (stack.length) {
4448
+ const node = stack.pop();
4449
+ node.objectCaching = false;
4450
+ node.statefullCache = false;
4451
+ node.noScaleCache = true;
4452
+ const kids = node._objects;
4453
+ if (kids && kids.length) stack.push(...kids);
4454
+ }
4249
4455
  maskGroup.absolutePositioned = false;
4250
4456
  maskGroup.excludeFromExport = true;
4251
4457
  maskGroup.inverted = false;
@@ -4368,7 +4574,7 @@ async function buildLuminanceAlphaCanvas(svgUrl, frameW, frameH) {
4368
4574
  const b = px[i + 2];
4369
4575
  const a = px[i + 3];
4370
4576
  const lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
4371
- const alpha = lum / 255 * a;
4577
+ const alpha = (255 - lum) / 255 * a;
4372
4578
  px[i] = 255;
4373
4579
  px[i + 1] = 255;
4374
4580
  px[i + 2] = 255;
@@ -5932,10 +6138,6 @@ const shouldShowOriginalTextBounds = () => {
5932
6138
  return false;
5933
6139
  }
5934
6140
  };
5935
- const hasActiveTextPathDescendant = (obj) => {
5936
- const kids = (obj == null ? void 0 : obj._objects) || [];
5937
- return kids.some((child) => hasActiveTextPath(child) || hasActiveTextPathDescendant(child));
5938
- };
5939
6141
  function applyWarpFillStyle(ctx, obj) {
5940
6142
  const filler = obj.fill;
5941
6143
  if (filler && typeof filler === "object" && Array.isArray(filler.colorStops) && filler.coords) {
@@ -6128,6 +6330,79 @@ function applyTextPathControls(textbox) {
6128
6330
  obj.controls = { ...defaultControls };
6129
6331
  const halfWOf = (t) => (t.width || 0) / 2;
6130
6332
  const halfHOf = (t) => (t.height || 0) / 2;
6333
+ {
6334
+ const cornerLayout = {
6335
+ tl: { sx: -1, sy: -1 },
6336
+ tr: { sx: 1, sy: -1 },
6337
+ br: { sx: 1, sy: 1 },
6338
+ bl: { sx: -1, sy: 1 },
6339
+ mt: { sx: 0, sy: -1 },
6340
+ mb: { sx: 0, sy: 1 },
6341
+ ml: { sx: -1, sy: 0 },
6342
+ mr: { sx: 1, sy: 0 }
6343
+ };
6344
+ for (const key of Object.keys(cornerLayout)) {
6345
+ const existing = obj.controls[key];
6346
+ if (!existing) continue;
6347
+ const { sx, sy } = cornerLayout[key];
6348
+ const customPosition = (_d2, finalMatrix, target) => {
6349
+ const t = target;
6350
+ const bounds = getTextPathHitBounds(t);
6351
+ if (!bounds) {
6352
+ return existing.positionHandler.call(existing, _d2, finalMatrix, target);
6353
+ }
6354
+ const cx = (bounds.minX + bounds.maxX) / 2;
6355
+ const cy = (bounds.minY + bounds.maxY) / 2;
6356
+ const x = sx < 0 ? bounds.minX : sx > 0 ? bounds.maxX : cx;
6357
+ const y = sy < 0 ? bounds.minY : sy > 0 ? bounds.maxY : cy;
6358
+ return scaleLocalToScreen(t, new fabric.Point(x, y)).transform(finalMatrix);
6359
+ };
6360
+ obj.controls[key] = new fabric.Control({
6361
+ x: existing.x,
6362
+ y: existing.y,
6363
+ offsetX: existing.offsetX,
6364
+ offsetY: existing.offsetY,
6365
+ cursorStyle: existing.cursorStyle,
6366
+ cursorStyleHandler: existing.cursorStyleHandler,
6367
+ actionName: existing.actionName,
6368
+ actionHandler: existing.actionHandler,
6369
+ mouseDownHandler: existing.mouseDownHandler,
6370
+ mouseUpHandler: existing.mouseUpHandler,
6371
+ getActionName: existing.getActionName,
6372
+ render: existing.render,
6373
+ sizeX: existing.sizeX,
6374
+ sizeY: existing.sizeY,
6375
+ touchSizeX: existing.touchSizeX,
6376
+ touchSizeY: existing.touchSizeY,
6377
+ withConnection: existing.withConnection,
6378
+ positionHandler: customPosition
6379
+ });
6380
+ }
6381
+ const mtrExisting = obj.controls.mtr;
6382
+ if (mtrExisting) {
6383
+ const customMtrPosition = (_d2, finalMatrix, target) => {
6384
+ const t = target;
6385
+ const bounds = getTextPathHitBounds(t);
6386
+ if (!bounds) return mtrExisting.positionHandler.call(mtrExisting, _d2, finalMatrix, target);
6387
+ const cx = (bounds.minX + bounds.maxX) / 2;
6388
+ const offset = mtrExisting.offsetY ?? -40;
6389
+ return scaleLocalToScreen(t, new fabric.Point(cx, bounds.minY)).transform(finalMatrix).add(new fabric.Point(0, offset));
6390
+ };
6391
+ obj.controls.mtr = new fabric.Control({
6392
+ x: mtrExisting.x,
6393
+ y: mtrExisting.y,
6394
+ offsetX: mtrExisting.offsetX,
6395
+ offsetY: mtrExisting.offsetY,
6396
+ cursorStyle: mtrExisting.cursorStyle,
6397
+ cursorStyleHandler: mtrExisting.cursorStyleHandler,
6398
+ actionName: mtrExisting.actionName,
6399
+ actionHandler: mtrExisting.actionHandler,
6400
+ render: mtrExisting.render,
6401
+ withConnection: mtrExisting.withConnection,
6402
+ positionHandler: customMtrPosition
6403
+ });
6404
+ }
6405
+ }
6131
6406
  const renderPivot = (ctx, left, top) => {
6132
6407
  ctx.save();
6133
6408
  ctx.translate(left, top);
@@ -6749,10 +7024,11 @@ if (typeof TextboxProto._renderControls === "function" && !TextboxProto.__pixldo
6749
7024
  if (!hostCanvas) return;
6750
7025
  const hoverBounds = getTextPathHitBounds(this);
6751
7026
  const active = (_d = hostCanvas.getActiveObject) == null ? void 0 : _d.call(hostCanvas);
6752
- const isActiveOrInActive = active === this || !!active && typeof active.contains === "function" && active.contains(this, true);
6753
- if (hoverBounds && (this.__pdTextPathHovered || isActiveOrInActive)) {
7027
+ const isDirectlyActive = active === this;
7028
+ if (hoverBounds && (this.__pdTextPathHovered || isDirectlyActive)) {
6754
7029
  drawTextPathBounds(ctx, this, hoverBounds, hostCanvas);
6755
7030
  }
7031
+ if (!isDirectlyActive) return;
6756
7032
  const resolved = resolveTextPath(this.textPath, this.width || 0, this.fontSize || 16);
6757
7033
  const path = resolved ? measurePath(resolved.d) : null;
6758
7034
  if (!path) return;
@@ -6866,13 +7142,6 @@ if (typeof TextboxProto._renderControls === "function" && !TextboxProto.__pixldo
6866
7142
  if (!showOrig) {
6867
7143
  this.hasBorders = false;
6868
7144
  this.borderColor = "rgba(0,0,0,0)";
6869
- const filtered = {};
6870
- for (const key of Object.keys(prevControls || {})) {
6871
- if (key === "mtr" || key === "tpPivot" || key.startsWith("cr") || key.startsWith("rs") || key.startsWith("bz")) {
6872
- filtered[key] = prevControls[key];
6873
- }
6874
- }
6875
- this.controls = filtered;
6876
7145
  }
6877
7146
  try {
6878
7147
  drawWarpGuides.call(this, ctx);
@@ -6983,26 +7252,7 @@ if (GroupProto && typeof GroupProto._renderControls === "function" && !GroupProt
6983
7252
  const host = this.canvas;
6984
7253
  const active = (_a2 = host == null ? void 0 : host.getActiveObject) == null ? void 0 : _a2.call(host);
6985
7254
  const isActiveOrInActive = !!host && (active === this || !!active && typeof active.contains === "function" && active.contains(this, true));
6986
- const hideWarpChildBounds = isActiveOrInActive && !shouldShowOriginalTextBounds() && hasActiveTextPathDescendant(this);
6987
- const prevBorders = this.hasBorders;
6988
- const prevControls = this.hasControls;
6989
- const prevBorderColor = this.borderColor;
6990
- if (hideWarpChildBounds) {
6991
- this.hasBorders = false;
6992
- this.hasControls = false;
6993
- this.borderColor = "rgba(0,0,0,0)";
6994
- styleOverride = { ...styleOverride || {}, hasBorders: false, hasControls: false, borderColor: "rgba(0,0,0,0)" };
6995
- childrenOverride = { ...childrenOverride || {}, hasBorders: false, borderColor: "rgba(0,0,0,0)" };
6996
- }
6997
- try {
6998
- GroupProto.__pixldocsOrigRenderControls.call(this, ctx, styleOverride, childrenOverride);
6999
- } finally {
7000
- if (hideWarpChildBounds) {
7001
- this.hasBorders = prevBorders;
7002
- this.hasControls = prevControls;
7003
- this.borderColor = prevBorderColor;
7004
- }
7005
- }
7255
+ GroupProto.__pixldocsOrigRenderControls.call(this, ctx, styleOverride, childrenOverride);
7006
7256
  if (!host || !isActiveOrInActive) return;
7007
7257
  const drawForDescendants = (parent) => {
7008
7258
  const kids = (parent == null ? void 0 : parent._objects) || [];
@@ -7027,17 +7277,12 @@ if (!TextboxProtoHit.__pixldocsOrigGetCoords && typeof TextboxProtoHit.getCoords
7027
7277
  const bounds = getTextPathHitBounds(this);
7028
7278
  if (!bounds) return TextboxProtoHit.__pixldocsOrigGetCoords.call(this);
7029
7279
  const matrix = this.calcTransformMatrix();
7030
- const coords = [
7280
+ return [
7031
7281
  new fabric.Point(bounds.minX, bounds.minY),
7032
7282
  new fabric.Point(bounds.maxX, bounds.minY),
7033
7283
  new fabric.Point(bounds.maxX, bounds.maxY),
7034
7284
  new fabric.Point(bounds.minX, bounds.maxY)
7035
7285
  ].map((p) => fabric.util.transformPoint(p, matrix));
7036
- if (this.group) {
7037
- const groupMatrix = this.group.calcTransformMatrix();
7038
- return coords.map((p) => fabric.util.transformPoint(p, groupMatrix));
7039
- }
7040
- return coords;
7041
7286
  };
7042
7287
  }
7043
7288
  if (!TextboxProtoHit.__pixldocsOrigContainsPoint && typeof TextboxProtoHit.containsPoint === "function") {
@@ -8546,7 +8791,7 @@ function createShape(element) {
8546
8791
  }
8547
8792
  }
8548
8793
  function createText(element) {
8549
- var _a2, _b, _c;
8794
+ var _a2, _b, _c, _d, _e;
8550
8795
  const overflowPolicy = element.overflowPolicy || "grow-and-push";
8551
8796
  let text = element.text || "Text";
8552
8797
  let fontSize = element.fontSize || 16;
@@ -8694,6 +8939,23 @@ function createText(element) {
8694
8939
  objectCaching: false,
8695
8940
  noScaleCache: true,
8696
8941
  splitByGrapheme,
8942
+ // Outline (stroke) support for text — kept in sync with PageCanvas
8943
+ // sync path. Only apply when BOTH a stroke color and positive width are
8944
+ // explicitly set; otherwise fabric defaults stroke to black and renders
8945
+ // an unwanted outline on existing text elements that carry a stray
8946
+ // strokeWidth (e.g. from PDF imports).
8947
+ ...(() => {
8948
+ const s = element.stroke;
8949
+ const w = Number(element.strokeWidth) || 0;
8950
+ const has = !!s && w > 0;
8951
+ return has ? {
8952
+ stroke: s,
8953
+ strokeWidth: w,
8954
+ paintFirst: element.paintFirst || "fill",
8955
+ strokeUniform: true,
8956
+ strokeLineJoin: "round"
8957
+ } : { stroke: void 0, strokeWidth: 0 };
8958
+ })(),
8697
8959
  // When inline markdown formatting is enabled, the displayed text is the
8698
8960
  // PARSED plain text (markdown source lives separately on the element).
8699
8961
  // Allowing canvas inline editing would let the user edit that plain text
@@ -8743,6 +9005,21 @@ function createText(element) {
8743
9005
  });
8744
9006
  } catch {
8745
9007
  }
9008
+ try {
9009
+ const baseCtrls = (_d = (_c = fabric.Object) == null ? void 0 : _c.prototype) == null ? void 0 : _d.controls;
9010
+ if (baseCtrls) {
9011
+ textbox.controls = {
9012
+ ...textbox.controls || {},
9013
+ tl: baseCtrls.tl,
9014
+ tr: baseCtrls.tr,
9015
+ bl: baseCtrls.bl,
9016
+ br: baseCtrls.br,
9017
+ mt: baseCtrls.mt,
9018
+ mb: baseCtrls.mb
9019
+ };
9020
+ }
9021
+ } catch {
9022
+ }
8746
9023
  const scaleXAfterSet = textbox.scaleX ?? 1;
8747
9024
  const scaleYAfterSet = textbox.scaleY ?? 1;
8748
9025
  if (Math.abs(widthAfterSet - targetWidth) > 0.01 || Math.abs(scaleXAfterSet - targetScaleX) > 0.01 || Math.abs(scaleYAfterSet - targetScaleY) > 0.01) {
@@ -8773,7 +9050,7 @@ function createText(element) {
8773
9050
  finalFontSize: fontSize,
8774
9051
  textboxWidth: textbox.width,
8775
9052
  textboxHeight: textbox.height,
8776
- lineCount: ((_c = textbox.textLines) == null ? void 0 : _c.length) || 0,
9053
+ lineCount: ((_e = textbox.textLines) == null ? void 0 : _e.length) || 0,
8777
9054
  lines: (textbox.textLines || []).map((line) => Array.isArray(line) ? line.join("") : String(line ?? "")),
8778
9055
  widthMetrics: getTextboxWidthFitMetrics(textbox, targetWidth)
8779
9056
  }));
@@ -9332,16 +9609,7 @@ function renderSmartElementToDataUri(type, props, width, height) {
9332
9609
  if (!svg) return null;
9333
9610
  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
9334
9611
  }
9335
- const KEY_SHOW_ORIG_TEXT_BOUNDS = "pixldocs:showOriginalTextBounds";
9336
9612
  const EVT_SHOW_ORIG_TEXT_BOUNDS = "pixldocs:showOriginalTextBoundsChanged";
9337
- function getShowOriginalTextBounds() {
9338
- if (typeof window === "undefined") return false;
9339
- try {
9340
- return window.localStorage.getItem(KEY_SHOW_ORIG_TEXT_BOUNDS) === "1";
9341
- } catch {
9342
- return false;
9343
- }
9344
- }
9345
9613
  function subscribeShowOriginalTextBounds(cb) {
9346
9614
  const handler = (e) => cb(!!e.detail);
9347
9615
  window.addEventListener(EVT_SHOW_ORIG_TEXT_BOUNDS, handler);
@@ -9495,31 +9763,12 @@ try {
9495
9763
  console.warn("[PageCanvas] Failed to apply global selection defaults:", e);
9496
9764
  }
9497
9765
  function applyWarpAwareSelectionBorders(selection) {
9498
- if (getShowOriginalTextBounds()) {
9499
- if (selection.__pixldocsOrigASHasBorders !== void 0) {
9500
- selection.hasBorders = selection.__pixldocsOrigASHasBorders;
9501
- }
9502
- return;
9503
- }
9504
- const hasWarpedTextObject = (obj) => {
9505
- if (obj instanceof fabric.Textbox) {
9506
- const tp = obj.textPath;
9507
- return !!(tp && tp.preset && tp.preset !== "none");
9508
- }
9509
- const children = obj._objects;
9510
- return Array.isArray(children) && children.some((child) => hasWarpedTextObject(child));
9511
- };
9512
- const hasWarpedText = selection.getObjects().some((obj) => {
9513
- if (!hasWarpedTextObject(obj)) return false;
9514
- const tp = obj.textPath;
9515
- return !tp || tp.preset !== "none";
9516
- });
9517
- if (hasWarpedText) {
9518
- if (selection.__pixldocsOrigASHasBorders === void 0) {
9519
- selection.__pixldocsOrigASHasBorders = selection.hasBorders;
9520
- }
9521
- selection.hasBorders = false;
9766
+ if (selection.__pixldocsOrigASHasBorders !== void 0) {
9767
+ selection.hasBorders = selection.__pixldocsOrigASHasBorders;
9768
+ delete selection.__pixldocsOrigASHasBorders;
9522
9769
  }
9770
+ selection.hasBorders = true;
9771
+ selection.hasControls = true;
9523
9772
  }
9524
9773
  const PageCanvas = forwardRef(
9525
9774
  ({
@@ -9585,7 +9834,46 @@ const PageCanvas = forwardRef(
9585
9834
  const [sizeLabel, setSizeLabel] = useState(null);
9586
9835
  const [ready, setReady] = useState(false);
9587
9836
  const [unlockRequestId, setUnlockRequestId] = useState(0);
9588
- useMemo(
9837
+ const applyLogicalGroupSelectionVisualState = useCallback((selection, groupId) => {
9838
+ var _a2;
9839
+ selection.__pixldocsGroupSelection = groupId;
9840
+ delete selection.__pixldocsLogicalGroupIds;
9841
+ selection.hasBorders = true;
9842
+ const members = selection.getObjects();
9843
+ for (const prev of suppressGroupMemberBordersRef.current) {
9844
+ if (members.includes(prev)) continue;
9845
+ const origBorders = prev.__pixldocsOrigHasBorders;
9846
+ const origControls = prev.__pixldocsOrigHasControls;
9847
+ const origLockX = prev.__pixldocsOrigLockScalingX;
9848
+ if (origBorders !== void 0) prev.hasBorders = origBorders;
9849
+ if (origControls !== void 0) prev.hasControls = origControls;
9850
+ if (origLockX !== void 0) {
9851
+ prev.lockScalingX = origLockX;
9852
+ prev.lockScalingY = prev.__pixldocsOrigLockScalingY;
9853
+ }
9854
+ delete prev.__pixldocsOrigHasBorders;
9855
+ delete prev.__pixldocsOrigHasControls;
9856
+ delete prev.__pixldocsOrigLockScalingX;
9857
+ delete prev.__pixldocsOrigLockScalingY;
9858
+ }
9859
+ suppressGroupMemberBordersRef.current = members;
9860
+ for (const m of members) {
9861
+ if (m.__pixldocsOrigHasBorders === void 0) m.__pixldocsOrigHasBorders = m.hasBorders;
9862
+ if (m.__pixldocsOrigHasControls === void 0) m.__pixldocsOrigHasControls = m.hasControls;
9863
+ m.hasBorders = false;
9864
+ m.hasControls = false;
9865
+ if (m.__cropGroup || ((_a2 = m._ct) == null ? void 0 : _a2.isCropGroup)) {
9866
+ if (m.__pixldocsOrigLockScalingX === void 0) {
9867
+ m.__pixldocsOrigLockScalingX = m.lockScalingX;
9868
+ m.__pixldocsOrigLockScalingY = m.lockScalingY;
9869
+ }
9870
+ m.lockScalingX = false;
9871
+ m.lockScalingY = false;
9872
+ }
9873
+ }
9874
+ applyWarpAwareSelectionBorders(selection);
9875
+ }, []);
9876
+ const pageBoundsOptions = useMemo(
9589
9877
  () => ({ pageContentWidth: canvasWidth, pageContentHeight: canvasHeight }),
9590
9878
  [canvasWidth, canvasHeight]
9591
9879
  );
@@ -9595,7 +9883,9 @@ const PageCanvas = forwardRef(
9595
9883
  const setGroupOverlayLiveBoundsRef = useRef(setGroupOverlayLiveBounds);
9596
9884
  const skipSelectionClearOnDiscardRef = useRef(false);
9597
9885
  const skipActiveSelectionBakeOnClearRef = useRef(false);
9886
+ const preserveEditingScopeOnSelectionClearRef = useRef(false);
9598
9887
  const preserveActiveSelectionAfterTransformRef = useRef(null);
9888
+ const pendingGroupPromotionRef = useRef(null);
9599
9889
  const imageReloadRequestSeqRef = useRef(/* @__PURE__ */ new Map());
9600
9890
  useRef(null);
9601
9891
  const groupBoundsResizingRef = useRef(false);
@@ -9615,6 +9905,7 @@ const PageCanvas = forwardRef(
9615
9905
  const lastResizeScaleTargetRef = useRef(null);
9616
9906
  const preserveSelectionAfterTransformIdRef = useRef(null);
9617
9907
  const groupSelectionTransformStartRef = useRef(null);
9908
+ const activeSelectionMoveStartRef = useRef(null);
9618
9909
  setGroupOverlayLiveBoundsRef.current = setGroupOverlayLiveBounds;
9619
9910
  const {
9620
9911
  selectElements,
@@ -9635,7 +9926,11 @@ const PageCanvas = forwardRef(
9635
9926
  if (!currentPage || ids.length === 0) return null;
9636
9927
  for (const id of ids) {
9637
9928
  const node = findNodeById(children, id);
9638
- if (node && isGroup(node)) return node;
9929
+ if (node && isGroup(node)) {
9930
+ const memberIds2 = new Set(getAllElementIds(node.children ?? []));
9931
+ const pureGroupOnly = ids.every((selectedId) => selectedId === node.id || memberIds2.has(selectedId));
9932
+ return pureGroupOnly ? node : null;
9933
+ }
9639
9934
  }
9640
9935
  const firstId = ids[0];
9641
9936
  const parent = findParentGroup(children, firstId);
@@ -9989,6 +10284,16 @@ const PageCanvas = forwardRef(
9989
10284
  fabricCanvas.__isUserTransforming = false;
9990
10285
  fabricCanvas.on("mouse:down", () => {
9991
10286
  groupSelectionTransformStartRef.current = null;
10287
+ activeSelectionMoveStartRef.current = null;
10288
+ const active = fabricCanvas.getActiveObject();
10289
+ if (active instanceof fabric.ActiveSelection) {
10290
+ const rect = active.getBoundingRect();
10291
+ activeSelectionMoveStartRef.current = {
10292
+ selection: active,
10293
+ selectionLeft: rect.left,
10294
+ selectionTop: rect.top
10295
+ };
10296
+ }
9992
10297
  if (fabricCanvas._currentTransform) {
9993
10298
  fabricCanvas.__isUserTransforming = true;
9994
10299
  }
@@ -10041,8 +10346,21 @@ const PageCanvas = forwardRef(
10041
10346
  didTransformRef.current = true;
10042
10347
  });
10043
10348
  const syncSelectionToStore = () => {
10044
- var _a2, _b, _c, _d, _e, _f, _g;
10349
+ var _a2, _b, _c, _d;
10045
10350
  if (!isActiveRef.current || isRebuildingRef.current || isSyncingSelectionToFabricRef.current || !allowSelection) return;
10351
+ const walkToTopmostGroup = (childId, children, activeEditingGroupId) => {
10352
+ let topmost = null;
10353
+ let currentId = childId;
10354
+ for (let i = 0; i < 32; i++) {
10355
+ const p = findParentGroup(children, currentId);
10356
+ if (!p) break;
10357
+ if (p.backgroundColor) break;
10358
+ if (activeEditingGroupId && p.id === activeEditingGroupId) break;
10359
+ topmost = p;
10360
+ currentId = p.id;
10361
+ }
10362
+ return topmost;
10363
+ };
10046
10364
  const active = fabricCanvas.getActiveObject();
10047
10365
  let ids = fabricCanvas.getActiveObjects().map((o) => getObjectId(o)).filter((id) => !!id && id !== "__background__");
10048
10366
  if (ids.length === 1 && active && active instanceof fabric.Group && active.__docuforgeSectionGroup) {
@@ -10069,8 +10387,8 @@ const PageCanvas = forwardRef(
10069
10387
  const state = useEditorStore.getState();
10070
10388
  const currentPage2 = (_b = state.canvas.pages) == null ? void 0 : _b.find((p) => p.id === pageId);
10071
10389
  const children = (currentPage2 == null ? void 0 : currentPage2.children) ?? [];
10072
- const parent = findParentGroup(children, clickedId);
10073
- 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));
10390
+ const parent = walkToTopmostGroup(clickedId, children, activeEditingGroupId);
10391
+ const targetIsInCrop = !!(active instanceof fabric.Group && isCropGroupInCropMode(active) || (active == null ? void 0 : active.group) instanceof fabric.Group && isCropGroupInCropMode(active.group));
10074
10392
  if (parent && !targetIsInCrop && activeEditingGroupId !== parent.id && !(active && active instanceof fabric.Group && active.__docuforgeSectionGroup)) {
10075
10393
  const memberIdSet = new Set(getAllElementIds(parent.children ?? []));
10076
10394
  const memberObjs = fabricCanvas.getObjects().filter((o) => {
@@ -10095,9 +10413,75 @@ const PageCanvas = forwardRef(
10095
10413
  selectElements(ids, false, false);
10096
10414
  return;
10097
10415
  }
10416
+ if (ids.length > 1 && !(active instanceof fabric.ActiveSelection && active.__pixldocsGroupSelection)) {
10417
+ const state = useEditorStore.getState();
10418
+ const currentPage2 = (_c = state.canvas.pages) == null ? void 0 : _c.find((p) => p.id === pageId);
10419
+ const children = (currentPage2 == null ? void 0 : currentPage2.children) ?? [];
10420
+ const activeEditingGroupId = fabricCanvas.__activeEditingGroupId ?? null;
10421
+ const involvedGroupIds = /* @__PURE__ */ new Set();
10422
+ const standaloneIds = [];
10423
+ for (const id of ids) {
10424
+ const parent = walkToTopmostGroup(id, children, activeEditingGroupId);
10425
+ if (parent && activeEditingGroupId !== parent.id && !parent.backgroundColor) {
10426
+ involvedGroupIds.add(parent.id);
10427
+ } else {
10428
+ standaloneIds.push(id);
10429
+ }
10430
+ }
10431
+ if (involvedGroupIds.size > 0) {
10432
+ const wantIds = new Set(standaloneIds);
10433
+ const groupedMemberIds = /* @__PURE__ */ new Set();
10434
+ for (const gid of involvedGroupIds) {
10435
+ const g = findNodeById(children, gid);
10436
+ if (g && isGroup(g)) {
10437
+ for (const leafId of getAllElementIds(g.children ?? [])) {
10438
+ wantIds.add(leafId);
10439
+ groupedMemberIds.add(leafId);
10440
+ }
10441
+ }
10442
+ }
10443
+ const currentSet = new Set(ids);
10444
+ const needsExpand = wantIds.size !== currentSet.size || [...wantIds].some((id) => !currentSet.has(id));
10445
+ if (needsExpand) {
10446
+ const wantObjs = fabricCanvas.getObjects().filter((o) => {
10447
+ const oid = getObjectId(o);
10448
+ return !!oid && wantIds.has(oid);
10449
+ });
10450
+ if (wantObjs.length >= 2) {
10451
+ isSyncingSelectionToFabricRef.current = true;
10452
+ try {
10453
+ const sel = new fabric.ActiveSelection(wantObjs, { canvas: fabricCanvas });
10454
+ if (involvedGroupIds.size === 1 && standaloneIds.length === 0) {
10455
+ restoreGroupSelectionVisualState(sel, [...involvedGroupIds][0]);
10456
+ } else {
10457
+ markMixedLogicalSelection(sel, [...involvedGroupIds], groupedMemberIds);
10458
+ }
10459
+ fabricCanvas.setActiveObject(sel);
10460
+ fabricCanvas.requestRenderAll();
10461
+ } finally {
10462
+ isSyncingSelectionToFabricRef.current = false;
10463
+ }
10464
+ }
10465
+ } else {
10466
+ const currentActive = fabricCanvas.getActiveObject();
10467
+ if (currentActive instanceof fabric.ActiveSelection) {
10468
+ if (involvedGroupIds.size === 1 && standaloneIds.length === 0) {
10469
+ restoreGroupSelectionVisualState(currentActive, [...involvedGroupIds][0]);
10470
+ } else {
10471
+ markMixedLogicalSelection(currentActive, [...involvedGroupIds], groupedMemberIds);
10472
+ }
10473
+ currentActive.setCoords();
10474
+ fabricCanvas.requestRenderAll();
10475
+ }
10476
+ }
10477
+ const storeIds = [...involvedGroupIds, ...standaloneIds];
10478
+ selectElements(storeIds, false, false);
10479
+ return;
10480
+ }
10481
+ }
10098
10482
  if (ids.length > 1) {
10099
10483
  const state = useEditorStore.getState();
10100
- const currentPage2 = (_g = state.canvas.pages) == null ? void 0 : _g.find((p) => p.id === pageId);
10484
+ const currentPage2 = (_d = state.canvas.pages) == null ? void 0 : _d.find((p) => p.id === pageId);
10101
10485
  const children = (currentPage2 == null ? void 0 : currentPage2.children) ?? [];
10102
10486
  const currentSelectedIds = state.canvas.selectedIds ?? [];
10103
10487
  for (const sid of currentSelectedIds) {
@@ -10125,6 +10509,20 @@ const PageCanvas = forwardRef(
10125
10509
  } else {
10126
10510
  m.hasBorders = true;
10127
10511
  }
10512
+ const origControls = m.__pixldocsOrigHasControls;
10513
+ if (origControls !== void 0) {
10514
+ m.hasControls = origControls;
10515
+ delete m.__pixldocsOrigHasControls;
10516
+ } else {
10517
+ m.hasControls = true;
10518
+ }
10519
+ const origLockX = m.__pixldocsOrigLockScalingX;
10520
+ if (origLockX !== void 0) {
10521
+ m.lockScalingX = origLockX;
10522
+ m.lockScalingY = m.__pixldocsOrigLockScalingY;
10523
+ delete m.__pixldocsOrigLockScalingX;
10524
+ delete m.__pixldocsOrigLockScalingY;
10525
+ }
10128
10526
  }
10129
10527
  suppressGroupMemberBordersRef.current = [];
10130
10528
  };
@@ -10132,6 +10530,7 @@ const PageCanvas = forwardRef(
10132
10530
  var _a2;
10133
10531
  syncSelectionToStore();
10134
10532
  const activeObj = fabricCanvas.getActiveObject();
10533
+ if (activeObj instanceof fabric.ActiveSelection) applyWarpAwareSelectionBorders(activeObj);
10135
10534
  if (activeObj) applyControlSizeForZoom(fabricCanvas, activeObj);
10136
10535
  if (activeObj && !(activeObj instanceof fabric.ActiveSelection) && (((_a2 = activeObj._ct) == null ? void 0 : _a2.isCropGroup) || activeObj.__cropGroup)) {
10137
10536
  installCanvaMaskControls(activeObj);
@@ -10140,10 +10539,11 @@ const PageCanvas = forwardRef(
10140
10539
  fabricCanvas.on("selection:updated", () => {
10141
10540
  var _a2;
10142
10541
  const next = fabricCanvas.getActiveObject();
10143
- const isGroupSel = next instanceof fabric.ActiveSelection && next.__pixldocsGroupSelection;
10144
- if (!isGroupSel) restoreSuppressedGroupBorders();
10542
+ const isLogicalGroupSel = next instanceof fabric.ActiveSelection && (next.__pixldocsGroupSelection || Array.isArray(next.__pixldocsLogicalGroupIds));
10543
+ if (!isLogicalGroupSel) restoreSuppressedGroupBorders();
10145
10544
  syncSelectionToStore();
10146
10545
  const activeObj = fabricCanvas.getActiveObject();
10546
+ if (activeObj instanceof fabric.ActiveSelection) applyWarpAwareSelectionBorders(activeObj);
10147
10547
  if (activeObj) applyControlSizeForZoom(fabricCanvas, activeObj);
10148
10548
  if (activeObj && !(activeObj instanceof fabric.ActiveSelection) && (((_a2 = activeObj._ct) == null ? void 0 : _a2.isCropGroup) || activeObj.__cropGroup)) {
10149
10549
  installCanvaMaskControls(activeObj);
@@ -10176,24 +10576,83 @@ const PageCanvas = forwardRef(
10176
10576
  const stateNow = useEditorStore.getState();
10177
10577
  const pageNow = (_b = stateNow.canvas.pages) == null ? void 0 : _b.find((p) => p.id === pageId);
10178
10578
  const childrenNow = (pageNow == null ? void 0 : pageNow.children) ?? [];
10179
- const parent = findParentGroup(childrenNow, childId);
10180
- if (!parent) return;
10181
- fabricCanvas.__activeEditingGroupId = parent.id;
10579
+ const chain = [];
10580
+ {
10581
+ let currId = childId;
10582
+ for (let i = 0; i < 32; i++) {
10583
+ const p = findParentGroup(childrenNow, currId);
10584
+ if (!p) break;
10585
+ if (p.backgroundColor) break;
10586
+ chain.push(p);
10587
+ currId = p.id;
10588
+ }
10589
+ }
10590
+ if (chain.length === 0) return;
10591
+ const currentScopeId = fabricCanvas.__activeEditingGroupId ?? null;
10592
+ let scopeIdx = chain.length;
10593
+ if (currentScopeId) {
10594
+ const idx = chain.findIndex((g) => g.id === currentScopeId);
10595
+ if (idx >= 0) scopeIdx = idx;
10596
+ }
10597
+ const entryIdx = scopeIdx - 1;
10598
+ if (entryIdx < 0) return;
10599
+ const newScopeGroup = chain[entryIdx];
10600
+ const selectionIdx = entryIdx - 1;
10601
+ const selectInnerObject = (obj) => {
10602
+ delete obj.__pixldocsGroupSelection;
10603
+ delete obj.__pixldocsLogicalGroupIds;
10604
+ obj.set({ selectable: true, evented: true, hasBorders: true, hasControls: true });
10605
+ fabricCanvas.setActiveObject(obj);
10606
+ obj.setCoords();
10607
+ };
10608
+ pendingGroupPromotionRef.current = null;
10182
10609
  isSyncingSelectionToFabricRef.current = true;
10183
10610
  try {
10611
+ skipSelectionClearOnDiscardRef.current = true;
10612
+ preserveEditingScopeOnSelectionClearRef.current = true;
10184
10613
  fabricCanvas.discardActiveObject();
10185
- fabricCanvas.setActiveObject(hitChild);
10186
- fabricCanvas.requestRenderAll();
10614
+ skipSelectionClearOnDiscardRef.current = false;
10615
+ preserveEditingScopeOnSelectionClearRef.current = false;
10616
+ restoreSuppressedGroupBorders();
10617
+ fabricCanvas.__activeEditingGroupId = newScopeGroup.id;
10618
+ if (selectionIdx < 0) {
10619
+ selectInnerObject(hitChild);
10620
+ fabricCanvas.requestRenderAll();
10621
+ } else {
10622
+ const subgroup = chain[selectionIdx];
10623
+ const memberIds = new Set(getAllElementIds(subgroup.children ?? []));
10624
+ const memberObjs = fabricCanvas.getObjects().filter((o) => {
10625
+ const oid = getObjectId(o);
10626
+ return !!oid && memberIds.has(oid);
10627
+ });
10628
+ if (memberObjs.length > 1) {
10629
+ const selection = new fabric.ActiveSelection(memberObjs, { canvas: fabricCanvas });
10630
+ restoreGroupSelectionVisualState(selection, subgroup.id);
10631
+ fabricCanvas.setActiveObject(selection);
10632
+ selection.setCoords();
10633
+ } else if (memberObjs.length === 1) {
10634
+ const only = memberObjs[0];
10635
+ selectInnerObject(only);
10636
+ } else {
10637
+ selectInnerObject(hitChild);
10638
+ }
10639
+ fabricCanvas.requestRenderAll();
10640
+ }
10187
10641
  } finally {
10642
+ skipSelectionClearOnDiscardRef.current = false;
10643
+ preserveEditingScopeOnSelectionClearRef.current = false;
10188
10644
  isSyncingSelectionToFabricRef.current = false;
10189
10645
  }
10190
- selectElements([childId], false, false);
10646
+ const selectedId = selectionIdx < 0 ? childId : chain[selectionIdx].id;
10647
+ selectElements([selectedId], false, false);
10191
10648
  });
10192
10649
  fabricCanvas.on("selection:cleared", () => {
10193
10650
  if (!isActiveRef.current || isRebuildingRef.current || !allowSelection) return;
10194
10651
  setGroupOverlayLiveBoundsRef.current(null);
10195
10652
  groupBoundsResizingRef.current = false;
10196
- fabricCanvas.__activeEditingGroupId = null;
10653
+ if (!preserveEditingScopeOnSelectionClearRef.current) {
10654
+ fabricCanvas.__activeEditingGroupId = null;
10655
+ }
10197
10656
  const preservedGroupSelection = preserveActiveSelectionAfterTransformRef.current;
10198
10657
  const shouldRestoreGroupSelection = !!((preservedGroupSelection == null ? void 0 : preservedGroupSelection.groupSelectionId) && (!preservedGroupSelection.expiresAt || preservedGroupSelection.expiresAt > Date.now()));
10199
10658
  if (skipSelectionClearOnDiscardRef.current) {
@@ -10245,6 +10704,14 @@ const PageCanvas = forwardRef(
10245
10704
  var _a2, _b;
10246
10705
  const active = target instanceof fabric.ActiveSelection ? target : fabricCanvas.getActiveObject();
10247
10706
  if (!(active instanceof fabric.ActiveSelection)) return;
10707
+ if (!activeSelectionMoveStartRef.current || activeSelectionMoveStartRef.current.selection !== active) {
10708
+ const rect2 = active.getBoundingRect();
10709
+ activeSelectionMoveStartRef.current = {
10710
+ selection: active,
10711
+ selectionLeft: rect2.left,
10712
+ selectionTop: rect2.top
10713
+ };
10714
+ }
10248
10715
  const groupId = active.__pixldocsGroupSelection;
10249
10716
  if (!groupId) return;
10250
10717
  if (((_a2 = groupSelectionTransformStartRef.current) == null ? void 0 : _a2.groupId) === groupId && groupSelectionTransformStartRef.current.selection === active) return;
@@ -10263,16 +10730,123 @@ const PageCanvas = forwardRef(
10263
10730
  };
10264
10731
  };
10265
10732
  const restoreGroupSelectionVisualState = (selection, groupId) => {
10266
- selection.__pixldocsGroupSelection = groupId;
10267
- const members = selection.getObjects();
10733
+ applyLogicalGroupSelectionVisualState(selection, groupId);
10734
+ };
10735
+ const suppressBordersForObjects = (members) => {
10736
+ var _a2;
10737
+ for (const prev of suppressGroupMemberBordersRef.current) {
10738
+ if (members.includes(prev)) continue;
10739
+ const orig = prev.__pixldocsOrigHasBorders;
10740
+ if (orig !== void 0) {
10741
+ prev.hasBorders = orig;
10742
+ delete prev.__pixldocsOrigHasBorders;
10743
+ } else {
10744
+ prev.hasBorders = true;
10745
+ }
10746
+ const origControls = prev.__pixldocsOrigHasControls;
10747
+ if (origControls !== void 0) {
10748
+ prev.hasControls = origControls;
10749
+ delete prev.__pixldocsOrigHasControls;
10750
+ } else {
10751
+ prev.hasControls = true;
10752
+ }
10753
+ const origLockX = prev.__pixldocsOrigLockScalingX;
10754
+ if (origLockX !== void 0) {
10755
+ prev.lockScalingX = origLockX;
10756
+ prev.lockScalingY = prev.__pixldocsOrigLockScalingY;
10757
+ delete prev.__pixldocsOrigLockScalingX;
10758
+ delete prev.__pixldocsOrigLockScalingY;
10759
+ }
10760
+ }
10268
10761
  suppressGroupMemberBordersRef.current = members;
10269
10762
  for (const m of members) {
10270
10763
  if (m.__pixldocsOrigHasBorders === void 0) {
10271
10764
  m.__pixldocsOrigHasBorders = m.hasBorders;
10272
10765
  }
10766
+ if (m.__pixldocsOrigHasControls === void 0) {
10767
+ m.__pixldocsOrigHasControls = m.hasControls;
10768
+ }
10273
10769
  m.hasBorders = false;
10770
+ m.hasControls = false;
10771
+ if (m.__cropGroup || ((_a2 = m._ct) == null ? void 0 : _a2.isCropGroup)) {
10772
+ if (m.__pixldocsOrigLockScalingX === void 0) {
10773
+ m.__pixldocsOrigLockScalingX = m.lockScalingX;
10774
+ m.__pixldocsOrigLockScalingY = m.lockScalingY;
10775
+ }
10776
+ m.lockScalingX = false;
10777
+ m.lockScalingY = false;
10778
+ }
10274
10779
  }
10275
10780
  };
10781
+ const isMultiSelectModifier = (event) => !!((event == null ? void 0 : event.shiftKey) || (event == null ? void 0 : event.metaKey) || (event == null ? void 0 : event.ctrlKey));
10782
+ const pickSelectableObjectAtPointer = (event) => {
10783
+ var _a2;
10784
+ try {
10785
+ const pointer = fabricCanvas.getViewportPoint(event);
10786
+ const objects = fabricCanvas.getObjects();
10787
+ for (let i = objects.length - 1; i >= 0; i--) {
10788
+ const obj = objects[i];
10789
+ const id = getObjectId(obj);
10790
+ if (!id || id === "__background__" || !obj.selectable || !obj.evented || obj.visible === false) continue;
10791
+ const controlHit = (_a2 = obj.findControl) == null ? void 0 : _a2.call(obj, pointer);
10792
+ if (controlHit) continue;
10793
+ if (typeof obj.containsPoint === "function" && obj.containsPoint(pointer)) return obj;
10794
+ }
10795
+ } catch {
10796
+ return null;
10797
+ }
10798
+ return null;
10799
+ };
10800
+ let pendingShiftMultiSelect = null;
10801
+ const applyShiftMultiSelect = (target, event, baselineActive = fabricCanvas.getActiveObject(), baselineObjects = fabricCanvas.getActiveObjects()) => {
10802
+ var _a2, _b, _c;
10803
+ if (!target || !target.selectable) return false;
10804
+ const active = baselineActive;
10805
+ if (!active || ((_a2 = active.getActiveControl) == null ? void 0 : _a2.call(active))) return false;
10806
+ if (active === target && !(active instanceof fabric.ActiveSelection)) return false;
10807
+ isSyncingSelectionToFabricRef.current = true;
10808
+ try {
10809
+ let nextObjects;
10810
+ if (active instanceof fabric.ActiveSelection) {
10811
+ const current = baselineObjects.length ? baselineObjects : active.getObjects();
10812
+ nextObjects = current.includes(target) ? current.filter((obj) => obj !== target) : [...current, target];
10813
+ } else {
10814
+ nextObjects = [active, target];
10815
+ }
10816
+ nextObjects = nextObjects.filter((obj, index, arr) => arr.indexOf(obj) === index && fabricCanvas.getObjects().includes(obj));
10817
+ if (nextObjects.length > 1) {
10818
+ const selection = new fabric.ActiveSelection(nextObjects, { canvas: fabricCanvas });
10819
+ applyWarpAwareSelectionBorders(selection);
10820
+ fabricCanvas.setActiveObject(selection);
10821
+ selection.setCoords();
10822
+ isSyncingSelectionToFabricRef.current = false;
10823
+ syncSelectionToStore();
10824
+ } else if (nextObjects.length === 1) {
10825
+ fabricCanvas.setActiveObject(nextObjects[0]);
10826
+ nextObjects[0].setCoords();
10827
+ const onlyId = getObjectId(nextObjects[0]);
10828
+ if (onlyId) selectElements([onlyId], false, false);
10829
+ }
10830
+ fabricCanvas.requestRenderAll();
10831
+ } finally {
10832
+ requestAnimationFrame(() => {
10833
+ isSyncingSelectionToFabricRef.current = false;
10834
+ });
10835
+ }
10836
+ (_b = event == null ? void 0 : event.preventDefault) == null ? void 0 : _b.call(event);
10837
+ (_c = event == null ? void 0 : event.stopPropagation) == null ? void 0 : _c.call(event);
10838
+ return true;
10839
+ };
10840
+ const markMixedLogicalSelection = (selection, groupIds, groupMemberIds) => {
10841
+ delete selection.__pixldocsGroupSelection;
10842
+ selection.__pixldocsLogicalGroupIds = groupIds;
10843
+ selection.hasBorders = true;
10844
+ const memberObjects = selection.getObjects().filter((obj) => {
10845
+ const id = getObjectId(obj);
10846
+ return !!id && groupMemberIds.has(id);
10847
+ });
10848
+ suppressBordersForObjects(memberObjects);
10849
+ };
10276
10850
  const restorePreservedGroupSelectionSoon = (snapshot = preserveActiveSelectionAfterTransformRef.current) => {
10277
10851
  if (!(snapshot == null ? void 0 : snapshot.groupSelectionId) || snapshot.memberIds.length < 2) return;
10278
10852
  const groupId = snapshot.groupSelectionId;
@@ -10310,7 +10884,7 @@ const PageCanvas = forwardRef(
10310
10884
  return !!(((_a2 = o == null ? void 0 : o._ct) == null ? void 0 : _a2.isCropGroup) || (o == null ? void 0 : o.__cropGroup));
10311
10885
  };
10312
10886
  const promoteToCropGroup = (opt) => {
10313
- var _a2, _b, _c;
10887
+ var _a2, _b, _c, _d, _e, _f;
10314
10888
  const t = opt.target;
10315
10889
  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") {
10316
10890
  if (t && isCropGroup2(t)) {
@@ -10320,6 +10894,9 @@ const PageCanvas = forwardRef(
10320
10894
  }
10321
10895
  return;
10322
10896
  }
10897
+ 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)) {
10898
+ return;
10899
+ }
10323
10900
  if (!t) {
10324
10901
  const pointer = fabricCanvas.getPointer(opt.e);
10325
10902
  const objects = fabricCanvas.getObjects();
@@ -10394,6 +10971,12 @@ const PageCanvas = forwardRef(
10394
10971
  }
10395
10972
  fabricCanvas.on("mouse:down", (opt) => {
10396
10973
  var _a2, _b;
10974
+ if (pendingShiftMultiSelect) {
10975
+ const pending = pendingShiftMultiSelect;
10976
+ pendingShiftMultiSelect = null;
10977
+ applyShiftMultiSelect(pending.target, opt.e, pending.active, pending.activeObjects);
10978
+ return;
10979
+ }
10397
10980
  const target = opt.target;
10398
10981
  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;
10399
10982
  if (cropGroup) {
@@ -10404,8 +10987,42 @@ const PageCanvas = forwardRef(
10404
10987
  fabricCanvas.requestRenderAll();
10405
10988
  }
10406
10989
  });
10990
+ const groupFabricUnionBBox = (g) => {
10991
+ var _a2, _b;
10992
+ const memberIds = new Set(getAllElementIds(g.children ?? []));
10993
+ if (memberIds.size === 0) return null;
10994
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
10995
+ let found = false;
10996
+ for (const o of fabricCanvas.getObjects()) {
10997
+ const oid = getObjectId(o);
10998
+ if (!oid || !memberIds.has(oid)) continue;
10999
+ const br = ((_a2 = o.getBoundingRect) == null ? void 0 : _a2.call(o, true, true)) ?? ((_b = o.getBoundingRect) == null ? void 0 : _b.call(o));
11000
+ if (!br) continue;
11001
+ minX = Math.min(minX, br.left);
11002
+ minY = Math.min(minY, br.top);
11003
+ maxX = Math.max(maxX, br.left + br.width);
11004
+ maxY = Math.max(maxY, br.top + br.height);
11005
+ found = true;
11006
+ }
11007
+ if (!found) return null;
11008
+ return { left: minX, top: minY, right: maxX, bottom: maxY };
11009
+ };
11010
+ const pickGroupAtPointer = (px, py, children, activeEditingGroupId) => {
11011
+ let pick = null;
11012
+ for (const node of children) {
11013
+ if (!isGroup(node)) continue;
11014
+ if (node.backgroundColor) continue;
11015
+ if (activeEditingGroupId && node.id === activeEditingGroupId) continue;
11016
+ const b = groupFabricUnionBBox(node);
11017
+ if (!b) continue;
11018
+ if (px < b.left || py < b.top || px > b.right || py > b.bottom) continue;
11019
+ const area = Math.max(1, (b.right - b.left) * (b.bottom - b.top));
11020
+ if (!pick || area < pick.area) pick = { group: node, area };
11021
+ }
11022
+ return pick;
11023
+ };
10407
11024
  fabricCanvas.on("mouse:down:before", (opt) => {
10408
- var _a2, _b, _c, _d, _e;
11025
+ var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
10409
11026
  if (editLockRef.current) {
10410
11027
  const active = fabricCanvas.getActiveObject();
10411
11028
  if (active && (((_a2 = active._ct) == null ? void 0 : _a2.isCropGroup) || active.__cropGroup)) {
@@ -10421,13 +11038,154 @@ const PageCanvas = forwardRef(
10421
11038
  syncLockedRef.current = true;
10422
11039
  lockEdits();
10423
11040
  }
11041
+ const target = opt.target;
11042
+ const targetId = target ? getObjectId(target) : null;
11043
+ const activeEditingGroupId = fabricCanvas.__activeEditingGroupId ?? null;
11044
+ const pageNow = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId);
11045
+ const childrenNow = (pageNow == null ? void 0 : pageNow.children) ?? [];
11046
+ if (isMultiSelectModifier(opt.e)) {
11047
+ const manualTarget = target && !(target instanceof fabric.ActiveSelection) && targetId && targetId !== "__background__" ? target : pickSelectableObjectAtPointer(opt.e);
11048
+ if (manualTarget) {
11049
+ pendingShiftMultiSelect = {
11050
+ target: manualTarget,
11051
+ active: fabricCanvas.getActiveObject(),
11052
+ activeObjects: fabricCanvas.getActiveObjects().slice()
11053
+ };
11054
+ pendingGroupPromotionRef.current = null;
11055
+ return;
11056
+ }
11057
+ }
11058
+ const findTopmostPromotableGroup = (childId) => {
11059
+ let topmost = null;
11060
+ let currentId = childId;
11061
+ for (let i = 0; i < 32; i++) {
11062
+ const p = findParentGroup(childrenNow, currentId);
11063
+ if (!p) break;
11064
+ if (p.backgroundColor) break;
11065
+ if (activeEditingGroupId && p.id === activeEditingGroupId) break;
11066
+ topmost = p;
11067
+ currentId = p.id;
11068
+ }
11069
+ return topmost;
11070
+ };
11071
+ if (target && targetId && targetId !== "__background__") {
11072
+ const parent = findTopmostPromotableGroup(targetId);
11073
+ const targetIsInCrop = !!(target instanceof fabric.Group && isCropGroupInCropMode(target) || (target == null ? void 0 : target.group) instanceof fabric.Group && isCropGroupInCropMode(target.group));
11074
+ const activeNow = fabricCanvas.getActiveObject();
11075
+ const alreadyThisGroup = activeNow instanceof fabric.ActiveSelection && activeNow.__pixldocsGroupSelection === (parent == null ? void 0 : parent.id);
11076
+ 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));
11077
+ if (parent && !parent.backgroundColor && !targetIsInCrop && activeEditingGroupId !== parent.id && !alreadyThisGroup && !isMultiSelectKey) {
11078
+ const memberIds = new Set(getAllElementIds(parent.children ?? []));
11079
+ const memberObjs = fabricCanvas.getObjects().filter((o) => {
11080
+ const oid = getObjectId(o);
11081
+ return !!oid && memberIds.has(oid);
11082
+ });
11083
+ if (memberObjs.length > 1) {
11084
+ const selection = new fabric.ActiveSelection(memberObjs, { canvas: fabricCanvas });
11085
+ restoreGroupSelectionVisualState(selection, parent.id);
11086
+ fabricCanvas.setActiveObject(selection);
11087
+ selection.setCoords();
11088
+ fabricCanvas._target = selection;
11089
+ opt.target = selection;
11090
+ pendingGroupPromotionRef.current = { groupId: parent.id, selection };
11091
+ } else if (memberObjs.length === 1) {
11092
+ const only = memberObjs[0];
11093
+ only.__pixldocsGroupSelection = parent.id;
11094
+ fabricCanvas.setActiveObject(only);
11095
+ only.setCoords();
11096
+ fabricCanvas._target = only;
11097
+ opt.target = only;
11098
+ pendingGroupPromotionRef.current = { groupId: parent.id, selection: only };
11099
+ }
11100
+ }
11101
+ } else if (!target || targetId === "__background__") {
11102
+ 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));
11103
+ if (isMultiSelectKey) return;
11104
+ try {
11105
+ const pointer = fabricCanvas.getPointer(opt.e);
11106
+ const px = pointer.x;
11107
+ const py = pointer.y;
11108
+ const pick = pickGroupAtPointer(px, py, childrenNow, activeEditingGroupId);
11109
+ if (pick) {
11110
+ const parent = pick.group;
11111
+ if (activeEditingGroupId !== parent.id) {
11112
+ const memberIds = new Set(getAllElementIds(parent.children ?? []));
11113
+ const memberObjs = fabricCanvas.getObjects().filter((o) => {
11114
+ const oid = getObjectId(o);
11115
+ return !!oid && memberIds.has(oid);
11116
+ });
11117
+ if (memberObjs.length > 1) {
11118
+ const selection = new fabric.ActiveSelection(memberObjs, { canvas: fabricCanvas });
11119
+ restoreGroupSelectionVisualState(selection, parent.id);
11120
+ fabricCanvas.setActiveObject(selection);
11121
+ selection.setCoords();
11122
+ fabricCanvas._target = selection;
11123
+ opt.target = selection;
11124
+ pendingGroupPromotionRef.current = { groupId: parent.id, selection };
11125
+ } else if (memberObjs.length === 1) {
11126
+ const only = memberObjs[0];
11127
+ only.__pixldocsGroupSelection = parent.id;
11128
+ fabricCanvas.setActiveObject(only);
11129
+ fabricCanvas._target = only;
11130
+ opt.target = only;
11131
+ pendingGroupPromotionRef.current = { groupId: parent.id, selection: only };
11132
+ }
11133
+ }
11134
+ }
11135
+ } catch {
11136
+ }
11137
+ }
10424
11138
  promoteToCropGroup(opt);
10425
11139
  });
10426
11140
  fabricCanvas.on("mouse:move:before", promoteToCropGroup);
11141
+ fabricCanvas.on("after:render", () => {
11142
+ var _a2;
11143
+ try {
11144
+ const active = fabricCanvas.getActiveObject();
11145
+ if (!(active instanceof fabric.ActiveSelection)) return;
11146
+ const logicalIds = active.__pixldocsLogicalGroupIds;
11147
+ if (!logicalIds || logicalIds.length < 1) return;
11148
+ const pageNow = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId);
11149
+ const childrenNow = (pageNow == null ? void 0 : pageNow.children) ?? [];
11150
+ const ctx = ((_a2 = fabricCanvas.getContext) == null ? void 0 : _a2.call(fabricCanvas)) ?? fabricCanvas.contextContainer;
11151
+ if (!ctx) return;
11152
+ const vt = fabricCanvas.viewportTransform;
11153
+ ctx.save();
11154
+ if (vt) ctx.transform(vt[0], vt[1], vt[2], vt[3], vt[4], vt[5]);
11155
+ ctx.strokeStyle = SELECTION_PRIMARY;
11156
+ ctx.lineWidth = 1.25 / ((vt == null ? void 0 : vt[0]) || 1);
11157
+ ctx.setLineDash([]);
11158
+ for (const gid of logicalIds) {
11159
+ const node = findNodeById(childrenNow, gid);
11160
+ if (!node || !isGroup(node)) continue;
11161
+ const b = groupFabricUnionBBox(node);
11162
+ if (!b) continue;
11163
+ ctx.strokeRect(b.left, b.top, b.right - b.left, b.bottom - b.top);
11164
+ }
11165
+ ctx.restore();
11166
+ } catch {
11167
+ }
11168
+ });
11169
+ fabricCanvas.on("mouse:move", (opt) => {
11170
+ if (fabricCanvas._currentTransform) return;
11171
+ if (editLockRef.current) return;
11172
+ const t = opt.target;
11173
+ const tid = t ? getObjectId(t) : null;
11174
+ if (t && tid && tid !== "__background__") return;
11175
+ try {
11176
+ const pointer = fabricCanvas.getPointer(opt.e);
11177
+ const pageNow = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId);
11178
+ const childrenNow = (pageNow == null ? void 0 : pageNow.children) ?? [];
11179
+ const activeEditingGroupId = fabricCanvas.__activeEditingGroupId ?? null;
11180
+ const pick = pickGroupAtPointer(pointer.x, pointer.y, childrenNow, activeEditingGroupId);
11181
+ fabricCanvas.defaultCursor = pick ? "move" : "default";
11182
+ } catch {
11183
+ fabricCanvas.defaultCursor = "default";
11184
+ }
11185
+ });
10427
11186
  fabricCanvas.on("mouse:down", (ev) => {
10428
11187
  if (fabricCanvas._currentTransform) {
10429
11188
  lockEdits();
10430
- didTransformRef.current = true;
10431
11189
  }
10432
11190
  const o = fabricCanvas.getActiveObject();
10433
11191
  pendingTextEditOnUpRef.current = null;
@@ -10441,6 +11199,38 @@ const PageCanvas = forwardRef(
10441
11199
  setRotationLabel(null);
10442
11200
  setSizeLabel(null);
10443
11201
  dragStarted = false;
11202
+ const pendingPromotion = pendingGroupPromotionRef.current;
11203
+ pendingGroupPromotionRef.current = null;
11204
+ if (pendingPromotion && !didTransformRef.current) {
11205
+ const activeNow = fabricCanvas.getActiveObject();
11206
+ if (activeNow !== pendingPromotion.selection) {
11207
+ isSyncingSelectionToFabricRef.current = true;
11208
+ try {
11209
+ fabricCanvas.setActiveObject(pendingPromotion.selection);
11210
+ pendingPromotion.selection.setCoords();
11211
+ fabricCanvas.requestRenderAll();
11212
+ } finally {
11213
+ requestAnimationFrame(() => {
11214
+ isSyncingSelectionToFabricRef.current = false;
11215
+ });
11216
+ }
11217
+ selectElements([pendingPromotion.groupId], false, false);
11218
+ }
11219
+ try {
11220
+ const sel = pendingPromotion.selection;
11221
+ if (sel instanceof fabric.ActiveSelection) {
11222
+ restoreGroupSelectionVisualState(sel, pendingPromotion.groupId);
11223
+ sel.hasBorders = true;
11224
+ sel.setCoords();
11225
+ applyWarpAwareSelectionBorders(sel);
11226
+ } else {
11227
+ sel.__pixldocsGroupSelection = pendingPromotion.groupId;
11228
+ sel.setCoords();
11229
+ }
11230
+ fabricCanvas.requestRenderAll();
11231
+ } catch {
11232
+ }
11233
+ }
10444
11234
  const activeObj = fabricCanvas.getActiveObject();
10445
11235
  if (activeObj && (activeObj.__cropGroup || ((_a2 = activeObj._ct) == null ? void 0 : _a2.isCropGroup))) {
10446
11236
  activeObj.__lockScaleDuringCrop = false;
@@ -10482,6 +11272,34 @@ const PageCanvas = forwardRef(
10482
11272
  if (!isActiveRef.current) return;
10483
11273
  markTransforming(e.target);
10484
11274
  };
11275
+ const getObjectFrameBoundsInSelection = (selection, obj, frameWidth, frameHeight) => {
11276
+ const w = Math.max(1, frameWidth ?? obj.width ?? 1);
11277
+ const h = Math.max(1, frameHeight ?? obj.height ?? 1);
11278
+ const originX = obj.originX ?? "left";
11279
+ const originY = obj.originY ?? "top";
11280
+ const localLeft = originX === "center" ? -w / 2 : originX === "right" ? -w : 0;
11281
+ const localTop = originY === "center" ? -h / 2 : originY === "bottom" ? -h : 0;
11282
+ const matrix = fabric.util.multiplyTransformMatrices(
11283
+ selection.calcTransformMatrix(),
11284
+ obj.calcOwnMatrix()
11285
+ );
11286
+ const points = [
11287
+ new fabric.Point(localLeft, localTop),
11288
+ new fabric.Point(localLeft + w, localTop),
11289
+ new fabric.Point(localLeft + w, localTop + h),
11290
+ new fabric.Point(localLeft, localTop + h)
11291
+ ].map((point) => fabric.util.transformPoint(point, matrix));
11292
+ const xs = points.map((p) => p.x);
11293
+ const ys = points.map((p) => p.y);
11294
+ const left = Math.min(...xs);
11295
+ const top = Math.min(...ys);
11296
+ return {
11297
+ left,
11298
+ top,
11299
+ width: Math.max(1, Math.max(...xs) - left),
11300
+ height: Math.max(1, Math.max(...ys) - top)
11301
+ };
11302
+ };
10485
11303
  fabricCanvas.on("object:added", (e) => {
10486
11304
  var _a2;
10487
11305
  const obj = e.target;
@@ -10601,11 +11419,18 @@ const PageCanvas = forwardRef(
10601
11419
  }
10602
11420
  if (obj instanceof fabric.Textbox) {
10603
11421
  const sy = obj.scaleY ?? 1;
10604
- if (Math.abs(sy - 1) > 1e-3) {
11422
+ const sx = obj.scaleX ?? 1;
11423
+ const isUniformCornerScale = Math.abs(sx - sy) < 1e-3 && Math.abs(sx - 1) > 1e-3;
11424
+ if (isUniformCornerScale) {
11425
+ obj.setCoords();
11426
+ obj.dirty = true;
11427
+ } else if (Math.abs(sy - 1) > 1e-3) {
10605
11428
  const center = obj.getCenterPoint();
10606
- const newMinH = Math.max(0, (obj.height ?? 0) * Math.abs(sy));
11429
+ const newVisualH = (obj.height ?? 0) * Math.abs(sy);
11430
+ const targetScaleY = Math.abs(sx - 1) > 1e-3 ? Math.abs(sx) : 1;
11431
+ const newMinH = Math.max(0, newVisualH / targetScaleY);
10607
11432
  obj.minBoxHeight = newMinH;
10608
- obj.scaleY = 1;
11433
+ obj.scaleY = targetScaleY;
10609
11434
  try {
10610
11435
  obj.initDimensions();
10611
11436
  } catch {
@@ -10727,7 +11552,7 @@ const PageCanvas = forwardRef(
10727
11552
  });
10728
11553
  let cropGroupSaveTimer = null;
10729
11554
  fabricCanvas.on("object:modified", (e) => {
10730
- var _a2, _b, _c, _d, _e, _f;
11555
+ var _a2, _b, _c, _d, _e, _f, _g;
10731
11556
  try {
10732
11557
  dragStarted = false;
10733
11558
  setGuides([]);
@@ -10948,14 +11773,14 @@ const PageCanvas = forwardRef(
10948
11773
  }
10949
11774
  if (active && active instanceof fabric.Group && !(active instanceof fabric.ActiveSelection) && getObjectId(active)) {
10950
11775
  const groupId = getObjectId(active);
10951
- const pageChildren2 = ((_e = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _e.children) ?? [];
11776
+ const pageChildren3 = ((_e = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _e.children) ?? [];
10952
11777
  const w = (active.width ?? 0) * (active.scaleX ?? 1);
10953
11778
  const h = (active.height ?? 0) * (active.scaleY ?? 1);
10954
11779
  const centerX = active.left ?? 0;
10955
11780
  const centerY = active.top ?? 0;
10956
11781
  const groupLeft = active.originX === "center" ? centerX - w / 2 : centerX;
10957
11782
  const groupTop = active.originY === "center" ? centerY - h / 2 : centerY;
10958
- const storePos = absoluteToStorePosition(groupLeft, groupTop, groupId, pageChildren2);
11783
+ const storePos = absoluteToStorePosition(groupLeft, groupTop, groupId, pageChildren3);
10959
11784
  useEditorStore.getState().updateNode(groupId, { left: storePos.left, top: storePos.top }, { recordHistory: false, skipLayoutRecalc: true });
10960
11785
  commitHistory();
10961
11786
  unlockEditsSoon();
@@ -10970,6 +11795,14 @@ const PageCanvas = forwardRef(
10970
11795
  }
10971
11796
  const activeObj = fabricCanvas.getActiveObject();
10972
11797
  let activeObjects = fabricCanvas.getActiveObjects();
11798
+ const activeSelectionMoveStart = activeObj instanceof fabric.ActiveSelection && ((_f = activeSelectionMoveStartRef.current) == null ? void 0 : _f.selection) === activeObj ? activeSelectionMoveStartRef.current : null;
11799
+ const activeSelectionDelta = activeObj instanceof fabric.ActiveSelection && activeSelectionMoveStart ? (() => {
11800
+ const rect = activeObj.getBoundingRect();
11801
+ return {
11802
+ x: rect.left - activeSelectionMoveStart.selectionLeft,
11803
+ y: rect.top - activeSelectionMoveStart.selectionTop
11804
+ };
11805
+ })() : null;
10973
11806
  if (activeObjects.length === 0 && modifiedTarget && getObjectId(modifiedTarget)) {
10974
11807
  activeObjects = [modifiedTarget];
10975
11808
  }
@@ -10991,8 +11824,53 @@ const PageCanvas = forwardRef(
10991
11824
  const currentPage2 = getCurrentPageStore();
10992
11825
  const selectedElementIds = activeObjects.map((obj) => getObjectId(obj)).filter((id) => !!id && id !== "__background__");
10993
11826
  const anyCropGroup = activeObjects.some((o) => o.__cropGroup);
11827
+ const pageChildren2 = currentPage2.children ?? [];
11828
+ const selectedLogicalGroupIds = (useEditorStore.getState().canvas.selectedIds ?? []).filter((id) => {
11829
+ const node = findNodeById(pageChildren2, id);
11830
+ return !!(node && isGroup(node));
11831
+ });
11832
+ const activeSelectionHadTransform = activeObj instanceof fabric.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);
11833
+ if (!anyCropGroup && activeSelectionDelta && !activeSelectionHadTransform && selectedLogicalGroupIds.length > 0) {
11834
+ const selectedStoreIds = useEditorStore.getState().canvas.selectedIds ?? [];
11835
+ const groupMemberIds = /* @__PURE__ */ new Set();
11836
+ selectedLogicalGroupIds.forEach((gid) => {
11837
+ const groupNode = findNodeById(pageChildren2, gid);
11838
+ if (groupNode && isGroup(groupNode)) {
11839
+ getAllElementIds(groupNode.children ?? []).forEach((id) => groupMemberIds.add(id));
11840
+ }
11841
+ });
11842
+ const logicalStandaloneIds = selectedStoreIds.filter((id) => {
11843
+ if (selectedLogicalGroupIds.includes(id)) return false;
11844
+ if (groupMemberIds.has(id)) return false;
11845
+ return !!findNodeById(pageChildren2, id);
11846
+ });
11847
+ if (logicalStandaloneIds.length > 0) {
11848
+ const { updateNode: updateNodeStore, commitHistory: commitHistoryStore } = useEditorStore.getState();
11849
+ [...selectedLogicalGroupIds, ...logicalStandaloneIds].forEach((id) => {
11850
+ const node = findNodeById(pageChildren2, id);
11851
+ if (!node) return;
11852
+ const abs = getAbsoluteBounds(node, pageChildren2, pageBoundsOptions);
11853
+ const nextAbsLeft = abs.left + activeSelectionDelta.x;
11854
+ const nextAbsTop = abs.top + activeSelectionDelta.y;
11855
+ const storePos = absoluteToStorePosition(nextAbsLeft, nextAbsTop, id, pageChildren2);
11856
+ updateNodeStore(id, { left: storePos.left, top: storePos.top }, { recordHistory: false, skipLayoutRecalc: true });
11857
+ });
11858
+ commitHistoryStore();
11859
+ activeObjects.forEach((obj) => {
11860
+ const objId = getObjectId(obj);
11861
+ if (objId && objId !== "__background__") {
11862
+ justModifiedIdsRef.current.add(objId);
11863
+ modifiedIdsThisRound.add(objId);
11864
+ }
11865
+ });
11866
+ setTimeout(() => modifiedIdsThisRound.forEach((id) => justModifiedIdsRef.current.delete(id)), 150);
11867
+ activeSelectionMoveStartRef.current = null;
11868
+ groupSelectionTransformStartRef.current = null;
11869
+ unlockEditsSoon();
11870
+ return;
11871
+ }
11872
+ }
10994
11873
  if (selectedElementIds.length > 0 && !anyCropGroup) {
10995
- const pageChildren2 = currentPage2.children ?? [];
10996
11874
  const firstObj = activeObjects[0];
10997
11875
  const firstId = getObjectId(firstObj);
10998
11876
  const parentGroups = selectedElementIds.map((id) => findParentGroup(pageChildren2, id)).filter((g) => g !== null);
@@ -11087,6 +11965,7 @@ const PageCanvas = forwardRef(
11087
11965
  }
11088
11966
  }
11089
11967
  }
11968
+ const pendingCropGroupFrameBakes = [];
11090
11969
  for (const obj of activeObjects) {
11091
11970
  const objId = getObjectId(obj);
11092
11971
  if (!objId || objId === "__background__") continue;
@@ -11140,10 +12019,16 @@ const PageCanvas = forwardRef(
11140
12019
  }
11141
12020
  if (obj instanceof fabric.Group && obj.__cropGroup) {
11142
12021
  const ct = obj.__cropData;
11143
- const w = (ct == null ? void 0 : ct.frameW) ?? (obj.width ?? 0) * (obj.scaleX ?? 1);
11144
- const h = (ct == null ? void 0 : ct.frameH) ?? (obj.height ?? 0) * (obj.scaleY ?? 1);
11145
- absoluteLeft = (absoluteLeft ?? 0) - w / 2;
11146
- absoluteTop = (absoluteTop ?? 0) - h / 2;
12022
+ if (isActiveSelection && activeObj instanceof fabric.ActiveSelection) {
12023
+ const frameBounds = getObjectFrameBoundsInSelection(activeObj, obj, ct == null ? void 0 : ct.frameW, ct == null ? void 0 : ct.frameH);
12024
+ absoluteLeft = frameBounds.left;
12025
+ absoluteTop = frameBounds.top;
12026
+ } else {
12027
+ const w = ((ct == null ? void 0 : ct.frameW) ?? obj.width ?? 0) * Math.abs(obj.scaleX ?? 1);
12028
+ const h = ((ct == null ? void 0 : ct.frameH) ?? obj.height ?? 0) * Math.abs(obj.scaleY ?? 1);
12029
+ absoluteLeft = (absoluteLeft ?? 0) - w / 2;
12030
+ absoluteTop = (absoluteTop ?? 0) - h / 2;
12031
+ }
11147
12032
  } else if (obj instanceof fabric.FabricImage && (obj.originX === "center" || obj.originY === "center")) {
11148
12033
  const w = (obj.width ?? 0) * (obj.scaleX ?? 1);
11149
12034
  const h = (obj.height ?? 0) * (obj.scaleY ?? 1);
@@ -11160,17 +12045,49 @@ const PageCanvas = forwardRef(
11160
12045
  if (obj instanceof fabric.Group && obj.__cropGroup) {
11161
12046
  const ct = obj.__cropData;
11162
12047
  if (ct) {
11163
- finalWidth = ct.frameW;
11164
- finalHeight = ct.frameH;
12048
+ const sourceFrameW = Math.max(1, ct.frameW ?? obj.width ?? 1);
12049
+ const sourceFrameH = Math.max(1, ct.frameH ?? obj.height ?? 1);
12050
+ const appliedScaleX = Math.abs(isActiveSelection && activeObj ? activeObj.scaleX ?? 1 : obj.scaleX ?? 1);
12051
+ const appliedScaleY = Math.abs(isActiveSelection && activeObj ? activeObj.scaleY ?? 1 : obj.scaleY ?? 1);
12052
+ finalWidth = Math.max(1, sourceFrameW * appliedScaleX);
12053
+ finalHeight = Math.max(1, sourceFrameH * appliedScaleY);
11165
12054
  finalScaleX = 1;
11166
12055
  finalScaleY = 1;
11167
- obj.set({ scaleX: 1, scaleY: 1 });
11168
- if (!isActiveSelection) {
12056
+ if (isActiveSelection && activeObj instanceof fabric.ActiveSelection) {
12057
+ const frameBounds = getObjectFrameBoundsInSelection(activeObj, obj, sourceFrameW, sourceFrameH);
12058
+ absoluteLeft = frameBounds.left;
12059
+ absoluteTop = frameBounds.top;
12060
+ } else {
12061
+ absoluteLeft = (decomposed.translateX ?? absoluteLeft) - finalWidth / 2;
12062
+ absoluteTop = (decomposed.translateY ?? absoluteTop) - finalHeight / 2;
12063
+ }
12064
+ finalAbsoluteMatrix = fabric.util.composeMatrix({
12065
+ translateX: absoluteLeft + finalWidth / 2,
12066
+ translateY: absoluteTop + finalHeight / 2,
12067
+ angle: decomposed.angle ?? (obj.angle ?? 0),
12068
+ scaleX: 1,
12069
+ scaleY: 1,
12070
+ skewX: 0,
12071
+ skewY: 0
12072
+ });
12073
+ if (isActiveSelection && activeObj instanceof fabric.ActiveSelection) {
12074
+ pendingCropGroupFrameBakes.push({
12075
+ obj,
12076
+ width: finalWidth,
12077
+ height: finalHeight,
12078
+ left: absoluteLeft,
12079
+ top: absoluteTop,
12080
+ angle: decomposed.angle ?? (obj.angle ?? 0)
12081
+ });
12082
+ } else {
12083
+ ct.frameW = finalWidth;
12084
+ ct.frameH = finalHeight;
12085
+ obj.set({ width: finalWidth, height: finalHeight, scaleX: 1, scaleY: 1 });
11169
12086
  updateCoverLayout(obj);
11170
- obj.__lastResizeHandle = null;
12087
+ }
12088
+ obj.__lastResizeHandle = null;
12089
+ if (!isActiveSelection) {
11171
12090
  fabricCanvas.setActiveObject(obj);
11172
- } else {
11173
- obj.__lastResizeHandle = null;
11174
12091
  }
11175
12092
  }
11176
12093
  } else if (obj instanceof fabric.FabricImage) {
@@ -11260,6 +12177,10 @@ const PageCanvas = forwardRef(
11260
12177
  if (obj.textPath) {
11261
12178
  elementUpdate.textPath = obj.textPath;
11262
12179
  }
12180
+ const bakedFs = obj.fontSize;
12181
+ if (typeof bakedFs === "number" && bakedFs > 0) {
12182
+ elementUpdate.fontSize = bakedFs;
12183
+ }
11263
12184
  }
11264
12185
  if (sourceElement && sourceElement.opacity !== void 0) {
11265
12186
  elementUpdate.opacity = sourceElement.opacity;
@@ -11267,7 +12188,7 @@ const PageCanvas = forwardRef(
11267
12188
  updateElement(objId, elementUpdate, { recordHistory: false, skipLayoutRecalc: true });
11268
12189
  obj.setCoords();
11269
12190
  }
11270
- const pageChildrenForReflow = ((_f = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _f.children) ?? [];
12191
+ const pageChildrenForReflow = ((_g = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _g.children) ?? [];
11271
12192
  const stackGroupsToReflow = /* @__PURE__ */ new Set();
11272
12193
  for (const id of modifiedIdsThisRound) {
11273
12194
  const parent = findParentGroup(pageChildrenForReflow, id);
@@ -11291,6 +12212,25 @@ const PageCanvas = forwardRef(
11291
12212
  } finally {
11292
12213
  skipActiveSelectionBakeOnClearRef.current = false;
11293
12214
  }
12215
+ for (const bake of pendingCropGroupFrameBakes) {
12216
+ const ct = bake.obj.__cropData;
12217
+ if (!ct) continue;
12218
+ ct.frameW = bake.width;
12219
+ ct.frameH = bake.height;
12220
+ bake.obj.set({
12221
+ left: bake.left + bake.width / 2,
12222
+ top: bake.top + bake.height / 2,
12223
+ width: bake.width,
12224
+ height: bake.height,
12225
+ scaleX: 1,
12226
+ scaleY: 1,
12227
+ angle: bake.angle,
12228
+ originX: "center",
12229
+ originY: "center"
12230
+ });
12231
+ updateCoverLayout(bake.obj);
12232
+ bake.obj.setCoords();
12233
+ }
11294
12234
  if (membersToReselect.length > 1) {
11295
12235
  const newSel = new fabric.ActiveSelection(membersToReselect, { canvas: fabricCanvas });
11296
12236
  if (wasGroupSel) restoreGroupSelectionVisualState(newSel, wasGroupSel);
@@ -11309,6 +12249,7 @@ const PageCanvas = forwardRef(
11309
12249
  fabricCanvas.requestRenderAll();
11310
12250
  }
11311
12251
  groupSelectionTransformStartRef.current = null;
12252
+ activeSelectionMoveStartRef.current = null;
11312
12253
  setTimeout(() => modifiedIdsThisRound.forEach((id) => justModifiedIdsRef.current.delete(id)), 150);
11313
12254
  commitHistory();
11314
12255
  unlockEditsSoon();
@@ -11337,6 +12278,7 @@ const PageCanvas = forwardRef(
11337
12278
  }
11338
12279
  });
11339
12280
  fabricCanvas.on("mouse:dblclick", (e) => {
12281
+ var _a2, _b;
11340
12282
  if (!isActiveRef.current || !allowEditing) return;
11341
12283
  let target = e.target;
11342
12284
  if (!target) {
@@ -11345,7 +12287,10 @@ const PageCanvas = forwardRef(
11345
12287
  }
11346
12288
  if (target && target instanceof fabric.Group && target.__cropGroup) {
11347
12289
  const ct = target.__cropData;
11348
- if ((ct == null ? void 0 : ct._img) && !isCropGroupInCropMode(target)) {
12290
+ const innerImg = ct == null ? void 0 : ct._img;
12291
+ 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) || "";
12292
+ const isPlaceholder = !innerSrc || innerSrc === EMPTY_IMAGE_PLACEHOLDER_DATA_URL;
12293
+ if (innerImg && !isPlaceholder && !isCropGroupInCropMode(target)) {
11349
12294
  enterCropMode(target);
11350
12295
  return;
11351
12296
  }
@@ -11579,14 +12524,7 @@ const PageCanvas = forwardRef(
11579
12524
  try {
11580
12525
  const newSel = new fabric.ActiveSelection(freshMembers, { canvas: fc });
11581
12526
  if (activeSelectionSnapshot.groupSelectionId) {
11582
- newSel.__pixldocsGroupSelection = activeSelectionSnapshot.groupSelectionId;
11583
- suppressGroupMemberBordersRef.current = freshMembers;
11584
- for (const m of freshMembers) {
11585
- if (m.__pixldocsOrigHasBorders === void 0) {
11586
- m.__pixldocsOrigHasBorders = m.hasBorders;
11587
- }
11588
- m.hasBorders = false;
11589
- }
12527
+ applyLogicalGroupSelectionVisualState(newSel, activeSelectionSnapshot.groupSelectionId);
11590
12528
  }
11591
12529
  fc.setActiveObject(newSel);
11592
12530
  newSel.setCoords();
@@ -11971,6 +12909,7 @@ const PageCanvas = forwardRef(
11971
12909
  continue;
11972
12910
  }
11973
12911
  if (existingObj instanceof fabric.Group && existingObj.__cropGroup) {
12912
+ updateFabricObject(existingObj, element, wasJustModified);
11974
12913
  existingObj.set({
11975
12914
  flipX: element.flipX ?? false,
11976
12915
  flipY: element.flipY ?? false,
@@ -11978,6 +12917,7 @@ const PageCanvas = forwardRef(
11978
12917
  });
11979
12918
  existingObj.setCoords();
11980
12919
  fc.requestRenderAll();
12920
+ if (wasJustModified) justModifiedIdsRef.current.delete(element.id);
11981
12921
  continue;
11982
12922
  }
11983
12923
  if (existingObj instanceof fabric.Textbox && wasJustModified && !syncTriggeredByPanelRef.current) {
@@ -12244,18 +13184,17 @@ const PageCanvas = forwardRef(
12244
13184
  }
12245
13185
  if (!shouldSkipUpdates2) {
12246
13186
  const dfsIds = [];
12247
- const visit = (nodes, reverseGroupChildren = false) => {
12248
- const list = reverseGroupChildren ? [...nodes].reverse() : nodes;
12249
- for (const n of list) {
13187
+ const visit = (nodes) => {
13188
+ for (const n of nodes) {
12250
13189
  if (isElement(n)) dfsIds.push(n.id);
12251
13190
  else if (isGroup(n)) {
12252
13191
  const g = n;
12253
13192
  if (sectionGroupIds.has(g.id)) dfsIds.push(g.id);
12254
- visit(g.children ?? [], true);
13193
+ visit(g.children ?? []);
12255
13194
  }
12256
13195
  }
12257
13196
  };
12258
- visit(pageTree, false);
13197
+ visit(pageTree);
12259
13198
  const allFabricObjects = fc.getObjects();
12260
13199
  allFabricObjects.sort((a, b) => {
12261
13200
  const aIndex = dfsIds.indexOf(getObjectId(a) || "");
@@ -12317,14 +13256,7 @@ const PageCanvas = forwardRef(
12317
13256
  try {
12318
13257
  const newSel = new fabric.ActiveSelection(freshMembers, { canvas: fc });
12319
13258
  if (activeSelectionSnapshot.groupSelectionId) {
12320
- newSel.__pixldocsGroupSelection = activeSelectionSnapshot.groupSelectionId;
12321
- suppressGroupMemberBordersRef.current = freshMembers;
12322
- for (const m of freshMembers) {
12323
- if (m.__pixldocsOrigHasBorders === void 0) {
12324
- m.__pixldocsOrigHasBorders = m.hasBorders;
12325
- }
12326
- m.hasBorders = false;
12327
- }
13259
+ applyLogicalGroupSelectionVisualState(newSel, activeSelectionSnapshot.groupSelectionId);
12328
13260
  }
12329
13261
  fc.setActiveObject(newSel);
12330
13262
  newSel.setCoords();
@@ -12516,12 +13448,24 @@ const PageCanvas = forwardRef(
12516
13448
  isSyncingSelectionToFabricRef.current = true;
12517
13449
  const selectedSet = new Set(selectedIds);
12518
13450
  const pageChildrenForSelection = ((_a2 = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _a2.children) ?? [];
12519
- const selectedGroupSelectionId = selectedIds.find((id) => {
13451
+ const selectedGroupIds = selectedIds.filter((id) => {
12520
13452
  const node = findNodeById(pageChildrenForSelection, id);
12521
13453
  return !!(node && isGroup(node));
12522
13454
  });
12523
- const selectedGroupNode = selectedGroupSelectionId ? findNodeById(pageChildrenForSelection, selectedGroupSelectionId) : null;
12524
- const selectedGroupMemberIds = selectedGroupNode && isGroup(selectedGroupNode) ? new Set(getAllElementIds(selectedGroupNode.children ?? [])) : null;
13455
+ const selectedGroupSelectionId = selectedGroupIds[0];
13456
+ const selectedGroupMemberIdsAll = /* @__PURE__ */ new Set();
13457
+ for (const gid of selectedGroupIds) {
13458
+ const groupNode = findNodeById(pageChildrenForSelection, gid);
13459
+ if (groupNode && isGroup(groupNode)) {
13460
+ getAllElementIds(groupNode.children ?? []).forEach((id) => selectedGroupMemberIdsAll.add(id));
13461
+ }
13462
+ }
13463
+ const standaloneSelectedIds = selectedIds.filter((id) => {
13464
+ if (selectedGroupIds.includes(id)) return false;
13465
+ if (selectedGroupMemberIdsAll.has(id)) return false;
13466
+ return !!findNodeById(pageChildrenForSelection, id);
13467
+ });
13468
+ const isPureSingleGroupSelection = selectedGroupIds.length === 1 && standaloneSelectedIds.length === 0;
12525
13469
  const toSelect = [];
12526
13470
  for (const obj of fc.getObjects()) {
12527
13471
  const id = getObjectId(obj);
@@ -12530,17 +13474,29 @@ const PageCanvas = forwardRef(
12530
13474
  toSelect.push(obj);
12531
13475
  continue;
12532
13476
  }
12533
- if (id && (selectedGroupMemberIds == null ? void 0 : selectedGroupMemberIds.has(id))) {
13477
+ if (id && selectedGroupMemberIdsAll.has(id)) {
12534
13478
  toSelect.push(obj);
12535
13479
  continue;
12536
13480
  }
12537
13481
  if (obj instanceof fabric.Group && obj.__docuforgeSectionGroup) {
12538
13482
  for (const child of obj.getObjects()) {
12539
13483
  const childId = getObjectId(child);
12540
- if (childId && (selectedSet.has(childId) || (selectedGroupMemberIds == null ? void 0 : selectedGroupMemberIds.has(childId)))) toSelect.push(child);
13484
+ if (childId && (selectedSet.has(childId) || selectedGroupMemberIdsAll.has(childId))) toSelect.push(child);
12541
13485
  }
12542
13486
  }
12543
13487
  }
13488
+ const restoreSuppressedBordersForSelectionSync = () => {
13489
+ const suppressed = suppressGroupMemberBordersRef.current;
13490
+ for (const member of suppressed) {
13491
+ const origBorders = member.__pixldocsOrigHasBorders;
13492
+ const origControls = member.__pixldocsOrigHasControls;
13493
+ member.hasBorders = origBorders !== void 0 ? origBorders : true;
13494
+ member.hasControls = origControls !== void 0 ? origControls : true;
13495
+ delete member.__pixldocsOrigHasBorders;
13496
+ delete member.__pixldocsOrigHasControls;
13497
+ }
13498
+ suppressGroupMemberBordersRef.current = [];
13499
+ };
12544
13500
  if (toSelect.length === 0) {
12545
13501
  if (!syncLockedRef.current && !editLockRef.current) {
12546
13502
  fc.discardActiveObject();
@@ -12551,6 +13507,12 @@ const PageCanvas = forwardRef(
12551
13507
  if (objId) {
12552
13508
  justModifiedIdsRef.current.add(objId);
12553
13509
  }
13510
+ if (!selectedGroupSelectionId) {
13511
+ restoreSuppressedBordersForSelectionSync();
13512
+ delete obj.__pixldocsGroupSelection;
13513
+ delete obj.__pixldocsLogicalGroupIds;
13514
+ obj.set({ selectable: true, evented: true, hasBorders: true, hasControls: true });
13515
+ }
12554
13516
  fc.setActiveObject(obj);
12555
13517
  obj.setCoords();
12556
13518
  } else {
@@ -12559,13 +13521,39 @@ const PageCanvas = forwardRef(
12559
13521
  const sameSelection = active instanceof fabric.ActiveSelection && active.getObjects().length === toSelect.length && toSelect.every((obj) => active.getObjects().includes(obj));
12560
13522
  if (sameSelection && isFlatGroupSelection) {
12561
13523
  if (selectedGroupSelectionId && active instanceof fabric.ActiveSelection) {
12562
- active.__pixldocsGroupSelection = selectedGroupSelectionId;
12563
- suppressGroupMemberBordersRef.current = active.getObjects();
12564
- active.getObjects().forEach((m) => {
13524
+ if (isPureSingleGroupSelection) {
13525
+ active.__pixldocsGroupSelection = selectedGroupSelectionId;
13526
+ delete active.__pixldocsLogicalGroupIds;
13527
+ suppressGroupMemberBordersRef.current = active.getObjects();
13528
+ } else {
13529
+ delete active.__pixldocsGroupSelection;
13530
+ active.__pixldocsLogicalGroupIds = selectedGroupIds;
13531
+ active.hasBorders = true;
13532
+ suppressGroupMemberBordersRef.current = active.getObjects().filter((m) => {
13533
+ const id = getObjectId(m);
13534
+ return !!id && selectedGroupMemberIdsAll.has(id);
13535
+ });
13536
+ }
13537
+ suppressGroupMemberBordersRef.current.forEach((m) => {
13538
+ var _a3;
12565
13539
  if (m.__pixldocsOrigHasBorders === void 0) m.__pixldocsOrigHasBorders = m.hasBorders;
13540
+ if (m.__pixldocsOrigHasControls === void 0) m.__pixldocsOrigHasControls = m.hasControls;
12566
13541
  m.hasBorders = false;
13542
+ m.hasControls = false;
13543
+ if (m.__cropGroup || ((_a3 = m._ct) == null ? void 0 : _a3.isCropGroup)) {
13544
+ if (m.__pixldocsOrigLockScalingX === void 0) {
13545
+ m.__pixldocsOrigLockScalingX = m.lockScalingX;
13546
+ m.__pixldocsOrigLockScalingY = m.lockScalingY;
13547
+ }
13548
+ m.lockScalingX = false;
13549
+ m.lockScalingY = false;
13550
+ }
12567
13551
  });
12568
- applyWarpAwareSelectionBorders(active);
13552
+ if (isPureSingleGroupSelection) {
13553
+ active.hasBorders = true;
13554
+ active.setCoords();
13555
+ applyWarpAwareSelectionBorders(active);
13556
+ }
12569
13557
  }
12570
13558
  fc.requestRenderAll();
12571
13559
  } else {
@@ -12575,13 +13563,33 @@ const PageCanvas = forwardRef(
12575
13563
  });
12576
13564
  const selection = new fabric.ActiveSelection(toSelect, { canvas: fc });
12577
13565
  if (selectedGroupSelectionId) {
12578
- selection.__pixldocsGroupSelection = selectedGroupSelectionId;
12579
- suppressGroupMemberBordersRef.current = toSelect;
12580
- toSelect.forEach((m) => {
13566
+ if (isPureSingleGroupSelection) {
13567
+ selection.__pixldocsGroupSelection = selectedGroupSelectionId;
13568
+ suppressGroupMemberBordersRef.current = toSelect;
13569
+ } else {
13570
+ selection.__pixldocsLogicalGroupIds = selectedGroupIds;
13571
+ selection.hasBorders = true;
13572
+ suppressGroupMemberBordersRef.current = toSelect.filter((m) => {
13573
+ const id = getObjectId(m);
13574
+ return !!id && selectedGroupMemberIdsAll.has(id);
13575
+ });
13576
+ }
13577
+ suppressGroupMemberBordersRef.current.forEach((m) => {
13578
+ var _a3;
12581
13579
  if (m.__pixldocsOrigHasBorders === void 0) m.__pixldocsOrigHasBorders = m.hasBorders;
13580
+ if (m.__pixldocsOrigHasControls === void 0) m.__pixldocsOrigHasControls = m.hasControls;
12582
13581
  m.hasBorders = false;
13582
+ m.hasControls = false;
13583
+ if (m.__cropGroup || ((_a3 = m._ct) == null ? void 0 : _a3.isCropGroup)) {
13584
+ if (m.__pixldocsOrigLockScalingX === void 0) {
13585
+ m.__pixldocsOrigLockScalingX = m.lockScalingX;
13586
+ m.__pixldocsOrigLockScalingY = m.lockScalingY;
13587
+ }
13588
+ m.lockScalingX = false;
13589
+ m.lockScalingY = false;
13590
+ }
12583
13591
  });
12584
- applyWarpAwareSelectionBorders(selection);
13592
+ if (isPureSingleGroupSelection) applyWarpAwareSelectionBorders(selection);
12585
13593
  }
12586
13594
  fc.setActiveObject(selection);
12587
13595
  if (!isFlatGroupSelection) {
@@ -12887,7 +13895,7 @@ const PageCanvas = forwardRef(
12887
13895
  const angleTextPathActive = isTextbox && ((_b = element.textPath) == null ? void 0 : _b.preset) === "rise";
12888
13896
  const appliedSkewY = angleTextPathActive ? 0 : element.skewY ?? 0;
12889
13897
  let posIfNotSkipped = skipPositionUpdate ? {} : { left: fabricPos.left, top: fabricPos.top };
12890
- if (!skipPositionUpdate && obj instanceof fabric.FabricImage && obj.originX === "center") {
13898
+ if (!skipPositionUpdate && (obj instanceof fabric.FabricImage && obj.originX === "center" || obj instanceof fabric.Group && obj.__cropGroup)) {
12891
13899
  const vW = rW * effectiveScaleX;
12892
13900
  const vH = rH * effectiveScaleY;
12893
13901
  posIfNotSkipped = { left: fabricPos.left + vW / 2, top: fabricPos.top + vH / 2 };
@@ -13066,7 +14074,23 @@ const PageCanvas = forwardRef(
13066
14074
  objectCaching: false,
13067
14075
  noScaleCache: true,
13068
14076
  splitByGrapheme,
13069
- text
14077
+ text,
14078
+ // Text outline (stroke). Only apply when BOTH a stroke color and
14079
+ // positive width are explicitly set — otherwise fabric defaults
14080
+ // stroke to black and renders an unwanted outline on existing
14081
+ // text elements that carry a stray strokeWidth.
14082
+ ...(() => {
14083
+ const s = element.stroke;
14084
+ const w = Number(element.strokeWidth) || 0;
14085
+ const has = !!s && w > 0;
14086
+ return has ? {
14087
+ stroke: s,
14088
+ strokeWidth: w,
14089
+ paintFirst: element.paintFirst || "fill",
14090
+ strokeUniform: true,
14091
+ strokeLineJoin: "round"
14092
+ } : { stroke: void 0, strokeWidth: 0 };
14093
+ })()
13070
14094
  });
13071
14095
  const valign = element.verticalAlign || "top";
13072
14096
  const minBoxH = Math.max(0, Number(element.minBoxHeight) || 0);
@@ -13722,7 +14746,10 @@ const PageCanvas = forwardRef(
13722
14746
  let panX = element.cropPanX ?? 0.5;
13723
14747
  let panY = element.cropPanY ?? 0.5;
13724
14748
  let zoom2 = element.cropZoom ?? 1;
13725
- if (existingCropGroup) {
14749
+ const elementHasExplicitCrop = element.cropPanX != null || element.cropPanY != null || element.cropZoom != null;
14750
+ const existingGroupSource = existingCropGroup ? String(existingCropGroup.__imageSrc || "") : "";
14751
+ const canPreserveLiveCrop = Boolean(existingCropGroup) && (elementHasExplicitCrop || existingGroupSource === imageUrl);
14752
+ if (canPreserveLiveCrop && existingCropGroup) {
13726
14753
  const existingImg = (_j = existingCropGroup.__cropData) == null ? void 0 : _j._img;
13727
14754
  if (existingImg) {
13728
14755
  panX = ((_k = existingImg._ct) == null ? void 0 : _k.panX) ?? existingImg.__panX ?? panX;
@@ -15063,6 +16090,19 @@ function setInTree(nodes, elementId, targetProperty, value) {
15063
16090
  } else if (targetProperty === "text" && node.type === "text" && typeof value === "string") {
15064
16091
  const textVal = value === "" ? " " : value;
15065
16092
  node[targetProperty] = applyTextCase(textVal, node.textCase);
16093
+ } else if ((targetProperty === "src" || targetProperty === "imageUrl") && node.type === "image") {
16094
+ const previousSrc = String(node.src || node.imageUrl || "");
16095
+ const nextSrc = String(value ?? "");
16096
+ const srcChanged = nextSrc !== "" && nextSrc !== previousSrc;
16097
+ node.src = value;
16098
+ node.imageUrl = value;
16099
+ if (srcChanged) {
16100
+ delete node.imageNaturalWidth;
16101
+ delete node.imageNaturalHeight;
16102
+ delete node.cropPanX;
16103
+ delete node.cropPanY;
16104
+ delete node.cropZoom;
16105
+ }
15066
16106
  } else {
15067
16107
  node[targetProperty] = value;
15068
16108
  }
@@ -15072,10 +16112,6 @@ function setInTree(nodes, elementId, targetProperty, value) {
15072
16112
  delete node.height;
15073
16113
  }
15074
16114
  }
15075
- if ((targetProperty === "src" || targetProperty === "imageUrl") && node.type === "image") {
15076
- delete node.imageNaturalWidth;
15077
- delete node.imageNaturalHeight;
15078
- }
15079
16115
  return true;
15080
16116
  }
15081
16117
  if (node.children && Array.isArray(node.children)) {
@@ -19863,10 +20899,11 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
19863
20899
  for (const obj of objs) {
19864
20900
  const isGroupLike = obj instanceof fabric.Group || (obj == null ? void 0 : obj.type) === "group" || Array.isArray(obj == null ? void 0 : obj._objects);
19865
20901
  const isFadedCropGroup = isGroupLike && (Boolean(obj.__edgeFadeRenderConfig) || Boolean(obj.__edgeFadeKey) || Boolean(obj.__edgeFadeInputKey));
19866
- if (!isFadedCropGroup) continue;
20902
+ const hasSvgMaskClip = isGroupLike && obj.__cropGroup && obj.clipPath && (obj.clipPath.__svgMask === true || obj.clipPath.__svgMaskUrl || obj.clipPath.__svgMaskType || obj.__svgMaskUrl || obj.__svgMaskType);
20903
+ if (!isFadedCropGroup && !hasSvgMaskClip) continue;
19867
20904
  try {
19868
20905
  const baked = obj.toCanvasElement({
19869
- multiplier: 2,
20906
+ multiplier: hasSvgMaskClip ? 4 : 2,
19870
20907
  enableRetinaScaling: false
19871
20908
  });
19872
20909
  const rect = obj.getBoundingRect();
@@ -19939,9 +20976,9 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
19939
20976
  }
19940
20977
  return svgString;
19941
20978
  }
19942
- const resolvedPackageVersion = "0.5.225";
20979
+ const resolvedPackageVersion = "0.5.227";
19943
20980
  const PACKAGE_VERSION = resolvedPackageVersion;
19944
- const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.225";
20981
+ const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.227";
19945
20982
  const roundParityValue = (value) => {
19946
20983
  if (typeof value !== "number") return value;
19947
20984
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -20685,7 +21722,7 @@ class PixldocsRenderer {
20685
21722
  await this.waitForCanvasScene(container, cloned, i);
20686
21723
  }
20687
21724
  console.log(`[canvas-renderer][pdf-unified] mounted ${cloned.pages.length} page(s), handing off to client exportMultiPagePdf`);
20688
- const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-D46sZGKA.js");
21725
+ const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-DAM2BQr4.js");
20689
21726
  const prepared = preparePagesForExport(
20690
21727
  cloned.pages,
20691
21728
  canvasWidth,
@@ -23005,7 +24042,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
23005
24042
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
23006
24043
  sanitizeSvgTreeForPdf(svgToDraw);
23007
24044
  try {
23008
- const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-D46sZGKA.js");
24045
+ const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-DAM2BQr4.js");
23009
24046
  try {
23010
24047
  await logTextMeasurementDiagnostic(svgToDraw);
23011
24048
  } catch {
@@ -23404,4 +24441,4 @@ export {
23404
24441
  buildTeaserBlurFlatKeys as y,
23405
24442
  collectFontDescriptorsFromConfig as z
23406
24443
  };
23407
- //# sourceMappingURL=index-BpViFQMO.js.map
24444
+ //# sourceMappingURL=index-BEJVbec7.js.map