@pixldocs/canvas-renderer 0.5.394 → 0.5.396

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.
@@ -107,6 +107,7 @@ const createDefaultGroup = (partial) => ({
107
107
  locked: false,
108
108
  left: 0,
109
109
  top: 0,
110
+ angle: 0,
110
111
  ...partial
111
112
  });
112
113
  const PAGE_BACKGROUND_ELEMENT_ID = "__pageBackground__";
@@ -829,9 +830,17 @@ function getNodeBounds(node, pageChildren, options) {
829
830
  let width;
830
831
  let height;
831
832
  if (isGroup(node) && (pageChildren == null ? void 0 : pageChildren.length) !== void 0) {
832
- const size = groupBoundsFromChildren(node, pageChildren ?? []);
833
- width = size.width;
834
- height = size.height;
833
+ const group = node;
834
+ const storedWidth = Number(group.width);
835
+ const storedHeight = Number(group.height);
836
+ if (!isStackLayoutMode(group.layoutMode) && storedWidth > 0 && storedHeight > 0) {
837
+ width = storedWidth;
838
+ height = storedHeight;
839
+ } else {
840
+ const size = groupBoundsFromChildren(group, pageChildren ?? []);
841
+ width = size.width;
842
+ height = size.height;
843
+ }
835
844
  } else {
836
845
  width = simpleWidth(node);
837
846
  height = simpleHeight(node);
@@ -10506,6 +10515,65 @@ const normalizeAngle180 = (angle) => {
10506
10515
  const n = (angle % 360 + 360) % 360;
10507
10516
  return n > 180 ? n - 360 : n;
10508
10517
  };
10518
+ const isValidLogicalGroupFrame = (frame) => !!frame && Number.isFinite(frame.left) && Number.isFinite(frame.top) && Number.isFinite(frame.width) && frame.width > 0 && Number.isFinite(frame.height) && frame.height > 0;
10519
+ const frameFromStoredGroupNode = (group, pageChildren, options) => {
10520
+ const width = Number(group.width);
10521
+ const height = Number(group.height);
10522
+ if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(height) || height <= 0) return null;
10523
+ try {
10524
+ const abs = getAbsoluteBounds(group, pageChildren, options);
10525
+ return { left: abs.left, top: abs.top, width, height };
10526
+ } catch {
10527
+ return null;
10528
+ }
10529
+ };
10530
+ const orientedFrameFromWorldPoints = (points, angle) => {
10531
+ if (points.length === 0 || !Number.isFinite(angle)) return null;
10532
+ const rad = -angle * Math.PI / 180;
10533
+ const cos = Math.cos(rad);
10534
+ const sin = Math.sin(rad);
10535
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
10536
+ for (const p of points) {
10537
+ const xr = p.x * cos - p.y * sin;
10538
+ const yr = p.x * sin + p.y * cos;
10539
+ minX = Math.min(minX, xr);
10540
+ minY = Math.min(minY, yr);
10541
+ maxX = Math.max(maxX, xr);
10542
+ maxY = Math.max(maxY, yr);
10543
+ }
10544
+ if (![minX, minY, maxX, maxY].every(Number.isFinite)) return null;
10545
+ const width = Math.max(1, maxX - minX);
10546
+ const height = Math.max(1, maxY - minY);
10547
+ const centerXRot = minX + width / 2;
10548
+ const centerYRot = minY + height / 2;
10549
+ const back = angle * Math.PI / 180;
10550
+ const c = Math.cos(back);
10551
+ const s = Math.sin(back);
10552
+ const centerX = centerXRot * c - centerYRot * s;
10553
+ const centerY = centerXRot * s + centerYRot * c;
10554
+ return { left: centerX - width / 2, top: centerY - height / 2, width, height };
10555
+ };
10556
+ const collectFabricObjectWorldPoints = (objects) => {
10557
+ const points = [];
10558
+ for (const obj of objects) {
10559
+ try {
10560
+ obj.setCoords();
10561
+ } catch {
10562
+ }
10563
+ const coords = typeof obj.getCoords === "function" ? obj.getCoords() : null;
10564
+ if (Array.isArray(coords) && coords.length) {
10565
+ points.push(...coords.map((p) => ({ x: p.x, y: p.y })));
10566
+ continue;
10567
+ }
10568
+ const aC = obj.aCoords;
10569
+ if (!aC) continue;
10570
+ for (const key of ["tl", "tr", "br", "bl"]) {
10571
+ const p = aC[key];
10572
+ if (p) points.push({ x: p.x, y: p.y });
10573
+ }
10574
+ }
10575
+ return points;
10576
+ };
10509
10577
  let ensureCanvaControlRenders = () => {
10510
10578
  };
10511
10579
  try {
@@ -11436,7 +11504,7 @@ const bakeTextboxScaleIntoTypography = (obj, sourceElement) => {
11436
11504
  };
11437
11505
  return updates;
11438
11506
  };
11439
- function applyWarpAwareSelectionBorders(selection, preferredGroupAngle) {
11507
+ function applyWarpAwareSelectionBorders(selection, preferredGroupAngle, preferredGroupFrame) {
11440
11508
  var _a2;
11441
11509
  if (selection.__pixldocsOrigASHasBorders !== void 0) {
11442
11510
  selection.hasBorders = selection.__pixldocsOrigASHasBorders;
@@ -11499,7 +11567,8 @@ function applyWarpAwareSelectionBorders(selection, preferredGroupAngle) {
11499
11567
  for (const b of buckets) b.area = orientedAreaForAngle(b.angle);
11500
11568
  buckets.sort((a, b) => b.count - a.count || a.area - b.area || Math.abs(b.angle) - Math.abs(a.angle));
11501
11569
  const dominant = buckets[0];
11502
- const preferredAngle = typeof preferredGroupAngle === "number" && Number.isFinite(preferredGroupAngle) ? normalizeAngle180(preferredGroupAngle) : null;
11570
+ const rawPreferredGroupAngle = typeof preferredGroupAngle === "number" && Number.isFinite(preferredGroupAngle) ? preferredGroupAngle : typeof selection.__pixldocsGroupAngle === "number" && Number.isFinite(selection.__pixldocsGroupAngle) ? selection.__pixldocsGroupAngle : null;
11571
+ const preferredAngle = rawPreferredGroupAngle != null ? normalizeAngle180(rawPreferredGroupAngle) : null;
11503
11572
  let targetAngle = preferredAngle;
11504
11573
  const isLogicalGroupSelection = !!selection.__pixldocsGroupSelection;
11505
11574
  if (targetAngle == null && dominant && Math.abs(dominant.angle) > 0.5 && (isLogicalGroupSelection || kids.length === 1 || dominant.count >= 2 || dominant.count === kids.length)) {
@@ -11543,11 +11612,30 @@ function applyWarpAwareSelectionBorders(selection, preferredGroupAngle) {
11543
11612
  targetAngle,
11544
11613
  worldAngles
11545
11614
  });
11615
+ const fixedFrame = preferredGroupFrame && Number.isFinite(preferredGroupFrame.left) && Number.isFinite(preferredGroupFrame.top) && Number.isFinite(preferredGroupFrame.width) && preferredGroupFrame.width > 0 && Number.isFinite(preferredGroupFrame.height) && preferredGroupFrame.height > 0 ? preferredGroupFrame : null;
11546
11616
  selection.set({ angle: targetAngle, scaleX: 1, scaleY: 1, skewX: 0, skewY: 0 });
11617
+ if (fixedFrame) {
11618
+ selection.set({ width: fixedFrame.width, height: fixedFrame.height, originX: "center", originY: "center" });
11619
+ selection.setPositionByOrigin(
11620
+ new fabric.Point(fixedFrame.left + fixedFrame.width / 2, fixedFrame.top + fixedFrame.height / 2),
11621
+ "center",
11622
+ "center"
11623
+ );
11624
+ }
11547
11625
  restoreKidsFromWorld();
11548
- try {
11549
- (_a2 = selection.triggerLayout) == null ? void 0 : _a2.call(selection);
11550
- } catch {
11626
+ if (!fixedFrame) {
11627
+ try {
11628
+ (_a2 = selection.triggerLayout) == null ? void 0 : _a2.call(selection);
11629
+ } catch {
11630
+ }
11631
+ }
11632
+ if (fixedFrame) {
11633
+ selection.set({ width: fixedFrame.width, height: fixedFrame.height, originX: "center", originY: "center" });
11634
+ selection.setPositionByOrigin(
11635
+ new fabric.Point(fixedFrame.left + fixedFrame.width / 2, fixedFrame.top + fixedFrame.height / 2),
11636
+ "center",
11637
+ "center"
11638
+ );
11551
11639
  }
11552
11640
  restoreKidsFromWorld();
11553
11641
  selection.setCoords();
@@ -11636,16 +11724,84 @@ const PageCanvas = forwardRef(
11636
11724
  const [ready, setReady] = useState(false);
11637
11725
  const [unlockRequestId, setUnlockRequestId] = useState(0);
11638
11726
  const applyLogicalGroupSelectionVisualState = useCallback((selection, groupId) => {
11639
- var _a2;
11727
+ var _a2, _b2;
11640
11728
  selection.__pixldocsGroupSelection = groupId;
11641
11729
  delete selection.__pixldocsLogicalGroupIds;
11642
11730
  const pageNow = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId);
11643
11731
  const groupNode = pageNow ? findNodeById(pageNow.children ?? [], groupId) : null;
11644
- const groupAngle = groupNode && isGroup(groupNode) && typeof groupNode.angle === "number" ? groupNode.angle : void 0;
11645
- if (groupAngle !== void 0) selection.__pixldocsGroupAngle = groupAngle;
11646
- else delete selection.__pixldocsGroupAngle;
11647
- selection.hasBorders = true;
11732
+ let groupAngle = groupNode && isGroup(groupNode) && typeof groupNode.angle === "number" ? groupNode.angle : void 0;
11648
11733
  const members = selection.getObjects();
11734
+ if (groupAngle === void 0 && groupNode && isGroup(groupNode)) {
11735
+ const storedW = Number(groupNode.width);
11736
+ const storedH = Number(groupNode.height);
11737
+ if (storedW > 0 && storedH > 0) {
11738
+ groupAngle = 0;
11739
+ }
11740
+ }
11741
+ if (groupAngle === void 0 && groupNode && isGroup(groupNode)) {
11742
+ try {
11743
+ const selectionMatrix = selection.calcTransformMatrix();
11744
+ const worldAngles = members.map((kid) => normalizeAngle180(fabric.util.qrDecompose(
11745
+ fabric.util.multiplyTransformMatrices(
11746
+ selectionMatrix,
11747
+ kid.calcOwnMatrix()
11748
+ )
11749
+ ).angle ?? 0));
11750
+ const buckets = [];
11751
+ for (const angle of worldAngles) {
11752
+ const bucket = buckets.find((candidate) => Math.abs(normalizeAngle180(candidate.angle - angle)) <= 2);
11753
+ if (bucket) bucket.count += 1;
11754
+ else buckets.push({ angle, count: 1 });
11755
+ }
11756
+ buckets.sort((a, b) => b.count - a.count || Math.abs(b.angle) - Math.abs(a.angle));
11757
+ const inferredAngle = (_a2 = buckets[0]) == null ? void 0 : _a2.angle;
11758
+ if (typeof inferredAngle === "number" && Number.isFinite(inferredAngle) && Math.abs(inferredAngle) > 0.01) {
11759
+ groupAngle = inferredAngle;
11760
+ }
11761
+ } catch {
11762
+ }
11763
+ }
11764
+ let groupFrame = groupNode && isGroup(groupNode) ? frameFromStoredGroupNode(groupNode, (pageNow == null ? void 0 : pageNow.children) ?? []) : null;
11765
+ let groupFrameWasInferred = false;
11766
+ if (!groupFrame && groupNode && isGroup(groupNode) && typeof groupAngle === "number" && Number.isFinite(groupAngle)) {
11767
+ groupFrame = orientedFrameFromWorldPoints(collectFabricObjectWorldPoints(members), normalizeAngle180(groupAngle));
11768
+ groupFrameWasInferred = isValidLogicalGroupFrame(groupFrame);
11769
+ }
11770
+ if (groupNode && isGroup(groupNode)) {
11771
+ const updates = {};
11772
+ if (typeof groupAngle === "number" && Number.isFinite(groupAngle)) {
11773
+ const prevAngle = typeof groupNode.angle === "number" ? groupNode.angle : void 0;
11774
+ if (prevAngle === void 0 || Math.abs(normalizeAngle180(prevAngle - groupAngle)) > 0.01) {
11775
+ updates.angle = normalizeAngle180(groupAngle);
11776
+ }
11777
+ }
11778
+ if (isValidLogicalGroupFrame(groupFrame)) {
11779
+ const prevW = Number(groupNode.width);
11780
+ const prevH = Number(groupNode.height);
11781
+ if (!Number.isFinite(prevW) || Math.abs(prevW - groupFrame.width) > 0.5) updates.width = groupFrame.width;
11782
+ if (!Number.isFinite(prevH) || Math.abs(prevH - groupFrame.height) > 0.5) updates.height = groupFrame.height;
11783
+ if (groupFrameWasInferred) {
11784
+ const storePos = absoluteToStorePosition(groupFrame.left, groupFrame.top, groupId, (pageNow == null ? void 0 : pageNow.children) ?? []);
11785
+ const prevLeft = Number(groupNode.left ?? 0);
11786
+ const prevTop = Number(groupNode.top ?? 0);
11787
+ if (!Number.isFinite(prevLeft) || Math.abs(prevLeft - storePos.left) > 0.5) updates.left = storePos.left;
11788
+ if (!Number.isFinite(prevTop) || Math.abs(prevTop - storePos.top) > 0.5) updates.top = storePos.top;
11789
+ }
11790
+ }
11791
+ if (Object.keys(updates).length > 0) {
11792
+ useEditorStore.getState().updateNode(groupId, updates, { recordHistory: false, skipLayoutRecalc: true });
11793
+ }
11794
+ }
11795
+ if (groupAngle !== void 0) {
11796
+ selection.__pixldocsGroupAngle = groupAngle;
11797
+ const aligned = selection.__pixldocsAlignedAngle;
11798
+ if (typeof aligned === "number" && Math.abs(normalizeAngle180(aligned - groupAngle)) > 0.01) {
11799
+ delete selection.__pixldocsAlignedAngle;
11800
+ }
11801
+ } else {
11802
+ delete selection.__pixldocsGroupAngle;
11803
+ }
11804
+ selection.hasBorders = true;
11649
11805
  for (const prev of suppressGroupMemberBordersRef.current) {
11650
11806
  if (members.includes(prev)) continue;
11651
11807
  const origBorders = prev.__pixldocsOrigHasBorders;
@@ -11668,7 +11824,7 @@ const PageCanvas = forwardRef(
11668
11824
  if (m.__pixldocsOrigHasControls === void 0) m.__pixldocsOrigHasControls = m.hasControls;
11669
11825
  m.hasBorders = false;
11670
11826
  m.hasControls = false;
11671
- if (m.__cropGroup || ((_a2 = m._ct) == null ? void 0 : _a2.isCropGroup)) {
11827
+ if (m.__cropGroup || ((_b2 = m._ct) == null ? void 0 : _b2.isCropGroup)) {
11672
11828
  if (m.__pixldocsOrigLockScalingX === void 0) {
11673
11829
  m.__pixldocsOrigLockScalingX = m.lockScalingX;
11674
11830
  m.__pixldocsOrigLockScalingY = m.lockScalingY;
@@ -11677,7 +11833,7 @@ const PageCanvas = forwardRef(
11677
11833
  m.lockScalingY = false;
11678
11834
  }
11679
11835
  }
11680
- applyWarpAwareSelectionBorders(selection, groupAngle);
11836
+ applyWarpAwareSelectionBorders(selection, groupAngle, groupFrame);
11681
11837
  }, [pageId]);
11682
11838
  const pageBoundsOptions = useMemo(
11683
11839
  () => ({ pageContentWidth: canvasWidth, pageContentHeight: canvasHeight }),
@@ -11780,7 +11936,11 @@ const PageCanvas = forwardRef(
11780
11936
  const activeMembers = activeBeforeRestore.getObjects();
11781
11937
  const sameMembers = activeMembers.length === members.length && members.every((member) => activeMembers.includes(member));
11782
11938
  const sameGroup = activeBeforeRestore.__pixldocsGroupSelection === groupId;
11783
- const alreadyAligned = activeBeforeRestore.__pixldocsAlignedAngle != null;
11939
+ const pageNow = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId);
11940
+ const groupNode = pageNow ? findNodeById(pageNow.children ?? [], groupId) : null;
11941
+ const savedAngle = groupNode && isGroup(groupNode) && typeof groupNode.angle === "number" ? groupNode.angle : null;
11942
+ const alignedAngle = activeBeforeRestore.__pixldocsAlignedAngle;
11943
+ const alreadyAligned = typeof alignedAngle === "number" && (savedAngle == null || Math.abs(normalizeAngle180(alignedAngle - savedAngle)) <= 0.01);
11784
11944
  if (sameMembers && sameGroup && alreadyAligned) {
11785
11945
  ensureCanvaControlRenders(activeBeforeRestore);
11786
11946
  return;
@@ -11797,9 +11957,11 @@ const PageCanvas = forwardRef(
11797
11957
  members.forEach((m) => m.setCoords());
11798
11958
  } catch {
11799
11959
  }
11800
- try {
11801
- (_a2 = selection.triggerLayout) == null ? void 0 : _a2.call(selection);
11802
- } catch {
11960
+ if (!selection.__pixldocsGroupSelection) {
11961
+ try {
11962
+ (_a2 = selection.triggerLayout) == null ? void 0 : _a2.call(selection);
11963
+ } catch {
11964
+ }
11803
11965
  }
11804
11966
  selection.setCoords();
11805
11967
  fc.requestRenderAll();
@@ -13239,14 +13401,18 @@ const PageCanvas = forwardRef(
13239
13401
  const groupNode = findNodeById(pageChildren2, groupId);
13240
13402
  if (!groupNode) return;
13241
13403
  const groupAbs = getAbsoluteBounds(groupNode, pageChildren2);
13404
+ const storedGroupFrame = groupNode && isGroup(groupNode) ? frameFromStoredGroupNode(groupNode, pageChildren2, pageBoundsOptions) : null;
13405
+ const groupFrame = storedGroupFrame ?? groupAbs;
13242
13406
  const rect = active.getBoundingRect();
13243
13407
  groupSelectionTransformStartRef.current = {
13244
13408
  groupId,
13245
13409
  selection: active,
13246
13410
  selectionLeft: rect.left,
13247
13411
  selectionTop: rect.top,
13248
- groupLeft: groupAbs.left,
13249
- groupTop: groupAbs.top,
13412
+ groupLeft: groupFrame.left,
13413
+ groupTop: groupFrame.top,
13414
+ groupWidth: groupFrame.width,
13415
+ groupHeight: groupFrame.height,
13250
13416
  selectionAngle: ((active.angle ?? 0) % 360 + 360) % 360
13251
13417
  };
13252
13418
  logRotDriftSelectionSnapshot("transform-start", active, {
@@ -13414,7 +13580,11 @@ const PageCanvas = forwardRef(
13414
13580
  const activeIds = active.getObjects().map((obj) => getObjectId(obj)).filter((id) => !!id && id !== "__background__");
13415
13581
  const sameMembers = activeIds.length === snapshot.memberIds.length && snapshot.memberIds.every((id) => activeIds.includes(id));
13416
13582
  const sameGroup = active.__pixldocsGroupSelection === snapshot.groupSelectionId;
13417
- const alreadyAligned = active.__pixldocsAlignedAngle != null;
13583
+ const pageNow = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId);
13584
+ const groupNode = pageNow ? findNodeById(pageNow.children ?? [], snapshot.groupSelectionId) : null;
13585
+ const savedAngle = groupNode && isGroup(groupNode) && typeof groupNode.angle === "number" ? groupNode.angle : null;
13586
+ const alignedAngle = active.__pixldocsAlignedAngle;
13587
+ const alreadyAligned = typeof alignedAngle === "number" && (savedAngle == null || Math.abs(normalizeAngle180(alignedAngle - savedAngle)) <= 0.01);
13418
13588
  if (sameMembers && sameGroup && alreadyAligned) {
13419
13589
  ensureCanvaControlRenders(active);
13420
13590
  return;
@@ -13571,6 +13741,31 @@ const PageCanvas = forwardRef(
13571
13741
  }
13572
13742
  if (members.length === 0) return null;
13573
13743
  const savedGroupAngle = typeof g.angle === "number" && Number.isFinite(g.angle) ? normalizeAngle180(g.angle) : null;
13744
+ const savedGroupWidth = Number(g.width);
13745
+ const savedGroupHeight = Number(g.height);
13746
+ if (savedGroupAngle != null && savedGroupWidth > 0 && savedGroupHeight > 0) {
13747
+ const pageNow = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId);
13748
+ const pageChildrenNow = (pageNow == null ? void 0 : pageNow.children) ?? [];
13749
+ const abs = getAbsoluteBounds(g, pageChildrenNow);
13750
+ const cx = abs.left + savedGroupWidth / 2;
13751
+ const cy = abs.top + savedGroupHeight / 2;
13752
+ const rad2 = savedGroupAngle * Math.PI / 180;
13753
+ const c = Math.cos(rad2), s = Math.sin(rad2);
13754
+ const rotate = (x, y) => {
13755
+ const dx = x - cx;
13756
+ const dy = y - cy;
13757
+ return { x: cx + dx * c - dy * s, y: cy + dx * s + dy * c };
13758
+ };
13759
+ return {
13760
+ corners: [
13761
+ rotate(abs.left, abs.top),
13762
+ rotate(abs.left + savedGroupWidth, abs.top),
13763
+ rotate(abs.left + savedGroupWidth, abs.top + savedGroupHeight),
13764
+ rotate(abs.left, abs.top + savedGroupHeight)
13765
+ ],
13766
+ angle: savedGroupAngle
13767
+ };
13768
+ }
13574
13769
  const worldPoints = [];
13575
13770
  for (const m of members) {
13576
13771
  (_a2 = m.setCoords) == null ? void 0 : _a2.call(m);
@@ -14810,6 +15005,7 @@ const PageCanvas = forwardRef(
14810
15005
  fabricCanvas.on("object:rotating", (e) => {
14811
15006
  var _a2, _b2;
14812
15007
  markSimpleTransform(e);
15008
+ prepareGroupSelectionTransformStart(e.target);
14813
15009
  didTransformRef.current = true;
14814
15010
  const tr = e.target;
14815
15011
  if (shouldLogRotDriftLiveTick(tr, "rotating")) {
@@ -14999,7 +15195,7 @@ const PageCanvas = forwardRef(
14999
15195
  const groupLeft = fabricCenterX - w / 2;
15000
15196
  const groupTop = fabricCenterY - h / 2;
15001
15197
  const storePosGroup = absoluteToStorePosition(groupLeft, groupTop, movedGroupId, pageChildrenSec);
15002
- useEditorStore.getState().updateNode(movedGroupId, { left: storePosGroup.left, top: storePosGroup.top }, { recordHistory: false, skipLayoutRecalc: true });
15198
+ useEditorStore.getState().updateNode(movedGroupId, { left: storePosGroup.left, top: storePosGroup.top, angle: fabricSectionGroup.angle ?? 0 }, { recordHistory: false, skipLayoutRecalc: true });
15003
15199
  }
15004
15200
  commitHistory();
15005
15201
  unlockEditsSoon();
@@ -15142,7 +15338,7 @@ const PageCanvas = forwardRef(
15142
15338
  const groupLeft = centerX - w / 2;
15143
15339
  const groupTop = centerY - h / 2;
15144
15340
  const storePosGroup = absoluteToStorePosition(groupLeft, groupTop, groupId, pageChildrenSec);
15145
- useEditorStore.getState().updateNode(groupId, { left: storePosGroup.left, top: storePosGroup.top }, { recordHistory: false, skipLayoutRecalc: true });
15341
+ useEditorStore.getState().updateNode(groupId, { left: storePosGroup.left, top: storePosGroup.top, angle: active.angle ?? 0 }, { recordHistory: false, skipLayoutRecalc: true });
15146
15342
  }
15147
15343
  const node = findNodeById(pageChildrenSec, groupId);
15148
15344
  if (isChildModified && node && !groupMoved) {
@@ -15171,7 +15367,7 @@ const PageCanvas = forwardRef(
15171
15367
  const groupLeft = active.originX === "center" ? centerX - w / 2 : centerX;
15172
15368
  const groupTop = active.originY === "center" ? centerY - h / 2 : centerY;
15173
15369
  const storePos = absoluteToStorePosition(groupLeft, groupTop, groupId, pageChildren3);
15174
- useEditorStore.getState().updateNode(groupId, { left: storePos.left, top: storePos.top }, { recordHistory: false, skipLayoutRecalc: true });
15370
+ useEditorStore.getState().updateNode(groupId, { left: storePos.left, top: storePos.top, angle: active.angle ?? 0 }, { recordHistory: false, skipLayoutRecalc: true });
15175
15371
  commitHistory();
15176
15372
  unlockEditsSoon();
15177
15373
  return;
@@ -15353,13 +15549,37 @@ const PageCanvas = forwardRef(
15353
15549
  360 - Math.abs(currentSelAngle - startSelAngle)
15354
15550
  );
15355
15551
  const hadRotation = isActiveSelection && activeObj && angleDelta > 0.01;
15356
- if (hadRotation && activeObj instanceof fabric.ActiveSelection && activeGroupSelectionId === groupToMove.id) {
15357
- useEditorStore.getState().updateNode(
15358
- groupToMove.id,
15359
- { angle: currentSelAngle },
15360
- { recordHistory: false, skipLayoutRecalc: true }
15361
- );
15362
- activeObj.__pixldocsGroupAngle = currentSelAngle;
15552
+ if ((hadScale || hadRotation) && activeObj instanceof fabric.ActiveSelection && activeGroupSelectionId === groupToMove.id) {
15553
+ const groupTransformUpdates = {};
15554
+ if (hadRotation) {
15555
+ groupTransformUpdates.angle = normalizeAngle180(currentSelAngle);
15556
+ activeObj.__pixldocsGroupAngle = groupTransformUpdates.angle;
15557
+ }
15558
+ if (hadScale && (transformStart == null ? void 0 : transformStart.groupId) === groupToMove.id) {
15559
+ const center = activeObj.getCenterPoint();
15560
+ const nextWidth = Math.max(1, transformStart.groupWidth * Math.abs(activeObj.scaleX ?? 1));
15561
+ const nextHeight = Math.max(1, transformStart.groupHeight * Math.abs(activeObj.scaleY ?? 1));
15562
+ const storePos = absoluteToStorePosition(
15563
+ center.x - nextWidth / 2,
15564
+ center.y - nextHeight / 2,
15565
+ groupToMove.id,
15566
+ pageChildren2
15567
+ );
15568
+ groupTransformUpdates.left = storePos.left;
15569
+ groupTransformUpdates.top = storePos.top;
15570
+ groupTransformUpdates.width = nextWidth;
15571
+ groupTransformUpdates.height = nextHeight;
15572
+ } else if (!Number.isFinite(Number(groupToMove.width)) || !Number.isFinite(Number(groupToMove.height))) {
15573
+ groupTransformUpdates.width = (transformStart == null ? void 0 : transformStart.groupWidth) ?? groupAbs.width;
15574
+ groupTransformUpdates.height = (transformStart == null ? void 0 : transformStart.groupHeight) ?? groupAbs.height;
15575
+ }
15576
+ if (Object.keys(groupTransformUpdates).length > 0) {
15577
+ useEditorStore.getState().updateNode(
15578
+ groupToMove.id,
15579
+ groupTransformUpdates,
15580
+ { recordHistory: false, skipLayoutRecalc: true }
15581
+ );
15582
+ }
15363
15583
  }
15364
15584
  if (!hadScale && !hadRotation && (Math.abs(deltaX) > 0.1 || Math.abs(deltaY) > 0.1)) {
15365
15585
  const { updateNode: updateNodeStore, commitHistory: commitHistoryStore, getCurrentElements } = useEditorStore.getState();
@@ -15996,9 +16216,11 @@ const PageCanvas = forwardRef(
15996
16216
  } catch {
15997
16217
  }
15998
16218
  fabricCanvas.setActiveObject(newSel);
15999
- try {
16000
- (_j = newSel.triggerLayout) == null ? void 0 : _j.call(newSel);
16001
- } catch {
16219
+ if (!wasGroupSel) {
16220
+ try {
16221
+ (_j = newSel.triggerLayout) == null ? void 0 : _j.call(newSel);
16222
+ } catch {
16223
+ }
16002
16224
  }
16003
16225
  try {
16004
16226
  for (const member of membersToReselect) member.setCoords();
@@ -16375,7 +16597,12 @@ const PageCanvas = forwardRef(
16375
16597
  const prevAS = activeBeforeSync instanceof fabric.ActiveSelection ? activeBeforeSync : null;
16376
16598
  const prevMembers = prevAS ? prevAS.getObjects() : [];
16377
16599
  const sameMembers = !!prevAS && prevMembers.length === freshMembers.length && prevMembers.every((m) => freshMembers.includes(m));
16378
- const alreadyAligned = sameMembers && prevAS.__pixldocsAlignedAngle != null;
16600
+ const savedSnapshotAngle = activeSelectionSnapshot.groupSelectionId ? (() => {
16601
+ const groupNode = findNodeById(pageTree, activeSelectionSnapshot.groupSelectionId);
16602
+ return groupNode && isGroup(groupNode) && typeof groupNode.angle === "number" ? groupNode.angle : null;
16603
+ })() : null;
16604
+ const prevAlignedAngle = sameMembers ? prevAS.__pixldocsAlignedAngle : null;
16605
+ const alreadyAligned = sameMembers && typeof prevAlignedAngle === "number" && (savedSnapshotAngle == null || Math.abs(normalizeAngle180(prevAlignedAngle - savedSnapshotAngle)) <= 0.01);
16379
16606
  if (sameMembers && alreadyAligned) {
16380
16607
  try {
16381
16608
  ensureCanvaControlRenders(prevAS);
@@ -17417,11 +17644,10 @@ const PageCanvas = forwardRef(
17417
17644
  if (sameSelection && (isFlatGroupSelection || isPureSingleGroupSelection)) {
17418
17645
  if (selectedGroupSelectionId && active instanceof fabric.ActiveSelection) {
17419
17646
  if (isPureSingleGroupSelection) {
17420
- active.__pixldocsGroupSelection = selectedGroupSelectionId;
17421
- delete active.__pixldocsLogicalGroupIds;
17422
- suppressGroupMemberBordersRef.current = active.getObjects();
17647
+ applyLogicalGroupSelectionVisualState(active, selectedGroupSelectionId);
17423
17648
  } else {
17424
17649
  delete active.__pixldocsGroupSelection;
17650
+ delete active.__pixldocsGroupAngle;
17425
17651
  active.__pixldocsLogicalGroupIds = selectedGroupIds;
17426
17652
  active.hasBorders = true;
17427
17653
  suppressGroupMemberBordersRef.current = active.getObjects().filter((m) => {
@@ -17461,10 +17687,10 @@ const PageCanvas = forwardRef(
17461
17687
  const selection = new fabric.ActiveSelection(toSelect, { canvas: fc });
17462
17688
  if (selectedGroupSelectionId) {
17463
17689
  if (isPureSingleGroupSelection) {
17464
- selection.__pixldocsGroupSelection = selectedGroupSelectionId;
17465
- suppressGroupMemberBordersRef.current = toSelect;
17690
+ applyLogicalGroupSelectionVisualState(selection, selectedGroupSelectionId);
17466
17691
  } else {
17467
17692
  selection.__pixldocsLogicalGroupIds = selectedGroupIds;
17693
+ delete selection.__pixldocsGroupAngle;
17468
17694
  selection.hasBorders = true;
17469
17695
  suppressGroupMemberBordersRef.current = toSelect.filter((m) => {
17470
17696
  const id = getObjectId(m);
@@ -25107,9 +25333,9 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
25107
25333
  }
25108
25334
  return svgString;
25109
25335
  }
25110
- const resolvedPackageVersion = "0.5.394";
25336
+ const resolvedPackageVersion = "0.5.396";
25111
25337
  const PACKAGE_VERSION = resolvedPackageVersion;
25112
- const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.394";
25338
+ const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.396";
25113
25339
  const roundParityValue = (value) => {
25114
25340
  if (typeof value !== "number") return value;
25115
25341
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -25923,7 +26149,7 @@ class PixldocsRenderer {
25923
26149
  await this.waitForCanvasScene(container, cloned, i);
25924
26150
  }
25925
26151
  console.log(`[canvas-renderer][pdf-unified] mounted ${cloned.pages.length} page(s), handing off to client exportMultiPagePdf`);
25926
- const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-BUj20FDX.js");
26152
+ const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-RZWtHBw4.js");
25927
26153
  const prepared = preparePagesForExport(
25928
26154
  cloned.pages,
25929
26155
  canvasWidth,
@@ -28243,7 +28469,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
28243
28469
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
28244
28470
  sanitizeSvgTreeForPdf(svgToDraw);
28245
28471
  try {
28246
- const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-BUj20FDX.js");
28472
+ const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-RZWtHBw4.js");
28247
28473
  try {
28248
28474
  await logTextMeasurementDiagnostic(svgToDraw);
28249
28475
  } catch {
@@ -28643,4 +28869,4 @@ export {
28643
28869
  buildTeaserBlurFlatKeys as y,
28644
28870
  collectFontDescriptorsFromConfig as z
28645
28871
  };
28646
- //# sourceMappingURL=index-CgnT1WJ6.js.map
28872
+ //# sourceMappingURL=index-BTGqkKaE.js.map