@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.
@@ -125,6 +125,7 @@ const createDefaultGroup = (partial) => ({
125
125
  locked: false,
126
126
  left: 0,
127
127
  top: 0,
128
+ angle: 0,
128
129
  ...partial
129
130
  });
130
131
  const PAGE_BACKGROUND_ELEMENT_ID = "__pageBackground__";
@@ -847,9 +848,17 @@ function getNodeBounds(node, pageChildren, options) {
847
848
  let width;
848
849
  let height;
849
850
  if (isGroup(node) && (pageChildren == null ? void 0 : pageChildren.length) !== void 0) {
850
- const size = groupBoundsFromChildren(node, pageChildren ?? []);
851
- width = size.width;
852
- height = size.height;
851
+ const group = node;
852
+ const storedWidth = Number(group.width);
853
+ const storedHeight = Number(group.height);
854
+ if (!isStackLayoutMode(group.layoutMode) && storedWidth > 0 && storedHeight > 0) {
855
+ width = storedWidth;
856
+ height = storedHeight;
857
+ } else {
858
+ const size = groupBoundsFromChildren(group, pageChildren ?? []);
859
+ width = size.width;
860
+ height = size.height;
861
+ }
853
862
  } else {
854
863
  width = simpleWidth(node);
855
864
  height = simpleHeight(node);
@@ -10524,6 +10533,65 @@ const normalizeAngle180 = (angle) => {
10524
10533
  const n = (angle % 360 + 360) % 360;
10525
10534
  return n > 180 ? n - 360 : n;
10526
10535
  };
10536
+ 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;
10537
+ const frameFromStoredGroupNode = (group, pageChildren, options) => {
10538
+ const width = Number(group.width);
10539
+ const height = Number(group.height);
10540
+ if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(height) || height <= 0) return null;
10541
+ try {
10542
+ const abs = getAbsoluteBounds(group, pageChildren, options);
10543
+ return { left: abs.left, top: abs.top, width, height };
10544
+ } catch {
10545
+ return null;
10546
+ }
10547
+ };
10548
+ const orientedFrameFromWorldPoints = (points, angle) => {
10549
+ if (points.length === 0 || !Number.isFinite(angle)) return null;
10550
+ const rad = -angle * Math.PI / 180;
10551
+ const cos = Math.cos(rad);
10552
+ const sin = Math.sin(rad);
10553
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
10554
+ for (const p of points) {
10555
+ const xr = p.x * cos - p.y * sin;
10556
+ const yr = p.x * sin + p.y * cos;
10557
+ minX = Math.min(minX, xr);
10558
+ minY = Math.min(minY, yr);
10559
+ maxX = Math.max(maxX, xr);
10560
+ maxY = Math.max(maxY, yr);
10561
+ }
10562
+ if (![minX, minY, maxX, maxY].every(Number.isFinite)) return null;
10563
+ const width = Math.max(1, maxX - minX);
10564
+ const height = Math.max(1, maxY - minY);
10565
+ const centerXRot = minX + width / 2;
10566
+ const centerYRot = minY + height / 2;
10567
+ const back = angle * Math.PI / 180;
10568
+ const c = Math.cos(back);
10569
+ const s = Math.sin(back);
10570
+ const centerX = centerXRot * c - centerYRot * s;
10571
+ const centerY = centerXRot * s + centerYRot * c;
10572
+ return { left: centerX - width / 2, top: centerY - height / 2, width, height };
10573
+ };
10574
+ const collectFabricObjectWorldPoints = (objects) => {
10575
+ const points = [];
10576
+ for (const obj of objects) {
10577
+ try {
10578
+ obj.setCoords();
10579
+ } catch {
10580
+ }
10581
+ const coords = typeof obj.getCoords === "function" ? obj.getCoords() : null;
10582
+ if (Array.isArray(coords) && coords.length) {
10583
+ points.push(...coords.map((p) => ({ x: p.x, y: p.y })));
10584
+ continue;
10585
+ }
10586
+ const aC = obj.aCoords;
10587
+ if (!aC) continue;
10588
+ for (const key of ["tl", "tr", "br", "bl"]) {
10589
+ const p = aC[key];
10590
+ if (p) points.push({ x: p.x, y: p.y });
10591
+ }
10592
+ }
10593
+ return points;
10594
+ };
10527
10595
  let ensureCanvaControlRenders = () => {
10528
10596
  };
10529
10597
  try {
@@ -11454,7 +11522,7 @@ const bakeTextboxScaleIntoTypography = (obj, sourceElement) => {
11454
11522
  };
11455
11523
  return updates;
11456
11524
  };
11457
- function applyWarpAwareSelectionBorders(selection, preferredGroupAngle) {
11525
+ function applyWarpAwareSelectionBorders(selection, preferredGroupAngle, preferredGroupFrame) {
11458
11526
  var _a2;
11459
11527
  if (selection.__pixldocsOrigASHasBorders !== void 0) {
11460
11528
  selection.hasBorders = selection.__pixldocsOrigASHasBorders;
@@ -11517,7 +11585,8 @@ function applyWarpAwareSelectionBorders(selection, preferredGroupAngle) {
11517
11585
  for (const b of buckets) b.area = orientedAreaForAngle(b.angle);
11518
11586
  buckets.sort((a, b) => b.count - a.count || a.area - b.area || Math.abs(b.angle) - Math.abs(a.angle));
11519
11587
  const dominant = buckets[0];
11520
- const preferredAngle = typeof preferredGroupAngle === "number" && Number.isFinite(preferredGroupAngle) ? normalizeAngle180(preferredGroupAngle) : null;
11588
+ const rawPreferredGroupAngle = typeof preferredGroupAngle === "number" && Number.isFinite(preferredGroupAngle) ? preferredGroupAngle : typeof selection.__pixldocsGroupAngle === "number" && Number.isFinite(selection.__pixldocsGroupAngle) ? selection.__pixldocsGroupAngle : null;
11589
+ const preferredAngle = rawPreferredGroupAngle != null ? normalizeAngle180(rawPreferredGroupAngle) : null;
11521
11590
  let targetAngle = preferredAngle;
11522
11591
  const isLogicalGroupSelection = !!selection.__pixldocsGroupSelection;
11523
11592
  if (targetAngle == null && dominant && Math.abs(dominant.angle) > 0.5 && (isLogicalGroupSelection || kids.length === 1 || dominant.count >= 2 || dominant.count === kids.length)) {
@@ -11561,11 +11630,30 @@ function applyWarpAwareSelectionBorders(selection, preferredGroupAngle) {
11561
11630
  targetAngle,
11562
11631
  worldAngles
11563
11632
  });
11633
+ 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;
11564
11634
  selection.set({ angle: targetAngle, scaleX: 1, scaleY: 1, skewX: 0, skewY: 0 });
11635
+ if (fixedFrame) {
11636
+ selection.set({ width: fixedFrame.width, height: fixedFrame.height, originX: "center", originY: "center" });
11637
+ selection.setPositionByOrigin(
11638
+ new fabric__namespace.Point(fixedFrame.left + fixedFrame.width / 2, fixedFrame.top + fixedFrame.height / 2),
11639
+ "center",
11640
+ "center"
11641
+ );
11642
+ }
11565
11643
  restoreKidsFromWorld();
11566
- try {
11567
- (_a2 = selection.triggerLayout) == null ? void 0 : _a2.call(selection);
11568
- } catch {
11644
+ if (!fixedFrame) {
11645
+ try {
11646
+ (_a2 = selection.triggerLayout) == null ? void 0 : _a2.call(selection);
11647
+ } catch {
11648
+ }
11649
+ }
11650
+ if (fixedFrame) {
11651
+ selection.set({ width: fixedFrame.width, height: fixedFrame.height, originX: "center", originY: "center" });
11652
+ selection.setPositionByOrigin(
11653
+ new fabric__namespace.Point(fixedFrame.left + fixedFrame.width / 2, fixedFrame.top + fixedFrame.height / 2),
11654
+ "center",
11655
+ "center"
11656
+ );
11569
11657
  }
11570
11658
  restoreKidsFromWorld();
11571
11659
  selection.setCoords();
@@ -11654,16 +11742,84 @@ const PageCanvas = react.forwardRef(
11654
11742
  const [ready, setReady] = react.useState(false);
11655
11743
  const [unlockRequestId, setUnlockRequestId] = react.useState(0);
11656
11744
  const applyLogicalGroupSelectionVisualState = react.useCallback((selection, groupId) => {
11657
- var _a2;
11745
+ var _a2, _b2;
11658
11746
  selection.__pixldocsGroupSelection = groupId;
11659
11747
  delete selection.__pixldocsLogicalGroupIds;
11660
11748
  const pageNow = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId);
11661
11749
  const groupNode = pageNow ? findNodeById(pageNow.children ?? [], groupId) : null;
11662
- const groupAngle = groupNode && isGroup(groupNode) && typeof groupNode.angle === "number" ? groupNode.angle : void 0;
11663
- if (groupAngle !== void 0) selection.__pixldocsGroupAngle = groupAngle;
11664
- else delete selection.__pixldocsGroupAngle;
11665
- selection.hasBorders = true;
11750
+ let groupAngle = groupNode && isGroup(groupNode) && typeof groupNode.angle === "number" ? groupNode.angle : void 0;
11666
11751
  const members = selection.getObjects();
11752
+ if (groupAngle === void 0 && groupNode && isGroup(groupNode)) {
11753
+ const storedW = Number(groupNode.width);
11754
+ const storedH = Number(groupNode.height);
11755
+ if (storedW > 0 && storedH > 0) {
11756
+ groupAngle = 0;
11757
+ }
11758
+ }
11759
+ if (groupAngle === void 0 && groupNode && isGroup(groupNode)) {
11760
+ try {
11761
+ const selectionMatrix = selection.calcTransformMatrix();
11762
+ const worldAngles = members.map((kid) => normalizeAngle180(fabric__namespace.util.qrDecompose(
11763
+ fabric__namespace.util.multiplyTransformMatrices(
11764
+ selectionMatrix,
11765
+ kid.calcOwnMatrix()
11766
+ )
11767
+ ).angle ?? 0));
11768
+ const buckets = [];
11769
+ for (const angle of worldAngles) {
11770
+ const bucket = buckets.find((candidate) => Math.abs(normalizeAngle180(candidate.angle - angle)) <= 2);
11771
+ if (bucket) bucket.count += 1;
11772
+ else buckets.push({ angle, count: 1 });
11773
+ }
11774
+ buckets.sort((a, b) => b.count - a.count || Math.abs(b.angle) - Math.abs(a.angle));
11775
+ const inferredAngle = (_a2 = buckets[0]) == null ? void 0 : _a2.angle;
11776
+ if (typeof inferredAngle === "number" && Number.isFinite(inferredAngle) && Math.abs(inferredAngle) > 0.01) {
11777
+ groupAngle = inferredAngle;
11778
+ }
11779
+ } catch {
11780
+ }
11781
+ }
11782
+ let groupFrame = groupNode && isGroup(groupNode) ? frameFromStoredGroupNode(groupNode, (pageNow == null ? void 0 : pageNow.children) ?? []) : null;
11783
+ let groupFrameWasInferred = false;
11784
+ if (!groupFrame && groupNode && isGroup(groupNode) && typeof groupAngle === "number" && Number.isFinite(groupAngle)) {
11785
+ groupFrame = orientedFrameFromWorldPoints(collectFabricObjectWorldPoints(members), normalizeAngle180(groupAngle));
11786
+ groupFrameWasInferred = isValidLogicalGroupFrame(groupFrame);
11787
+ }
11788
+ if (groupNode && isGroup(groupNode)) {
11789
+ const updates = {};
11790
+ if (typeof groupAngle === "number" && Number.isFinite(groupAngle)) {
11791
+ const prevAngle = typeof groupNode.angle === "number" ? groupNode.angle : void 0;
11792
+ if (prevAngle === void 0 || Math.abs(normalizeAngle180(prevAngle - groupAngle)) > 0.01) {
11793
+ updates.angle = normalizeAngle180(groupAngle);
11794
+ }
11795
+ }
11796
+ if (isValidLogicalGroupFrame(groupFrame)) {
11797
+ const prevW = Number(groupNode.width);
11798
+ const prevH = Number(groupNode.height);
11799
+ if (!Number.isFinite(prevW) || Math.abs(prevW - groupFrame.width) > 0.5) updates.width = groupFrame.width;
11800
+ if (!Number.isFinite(prevH) || Math.abs(prevH - groupFrame.height) > 0.5) updates.height = groupFrame.height;
11801
+ if (groupFrameWasInferred) {
11802
+ const storePos = absoluteToStorePosition(groupFrame.left, groupFrame.top, groupId, (pageNow == null ? void 0 : pageNow.children) ?? []);
11803
+ const prevLeft = Number(groupNode.left ?? 0);
11804
+ const prevTop = Number(groupNode.top ?? 0);
11805
+ if (!Number.isFinite(prevLeft) || Math.abs(prevLeft - storePos.left) > 0.5) updates.left = storePos.left;
11806
+ if (!Number.isFinite(prevTop) || Math.abs(prevTop - storePos.top) > 0.5) updates.top = storePos.top;
11807
+ }
11808
+ }
11809
+ if (Object.keys(updates).length > 0) {
11810
+ useEditorStore.getState().updateNode(groupId, updates, { recordHistory: false, skipLayoutRecalc: true });
11811
+ }
11812
+ }
11813
+ if (groupAngle !== void 0) {
11814
+ selection.__pixldocsGroupAngle = groupAngle;
11815
+ const aligned = selection.__pixldocsAlignedAngle;
11816
+ if (typeof aligned === "number" && Math.abs(normalizeAngle180(aligned - groupAngle)) > 0.01) {
11817
+ delete selection.__pixldocsAlignedAngle;
11818
+ }
11819
+ } else {
11820
+ delete selection.__pixldocsGroupAngle;
11821
+ }
11822
+ selection.hasBorders = true;
11667
11823
  for (const prev of suppressGroupMemberBordersRef.current) {
11668
11824
  if (members.includes(prev)) continue;
11669
11825
  const origBorders = prev.__pixldocsOrigHasBorders;
@@ -11686,7 +11842,7 @@ const PageCanvas = react.forwardRef(
11686
11842
  if (m.__pixldocsOrigHasControls === void 0) m.__pixldocsOrigHasControls = m.hasControls;
11687
11843
  m.hasBorders = false;
11688
11844
  m.hasControls = false;
11689
- if (m.__cropGroup || ((_a2 = m._ct) == null ? void 0 : _a2.isCropGroup)) {
11845
+ if (m.__cropGroup || ((_b2 = m._ct) == null ? void 0 : _b2.isCropGroup)) {
11690
11846
  if (m.__pixldocsOrigLockScalingX === void 0) {
11691
11847
  m.__pixldocsOrigLockScalingX = m.lockScalingX;
11692
11848
  m.__pixldocsOrigLockScalingY = m.lockScalingY;
@@ -11695,7 +11851,7 @@ const PageCanvas = react.forwardRef(
11695
11851
  m.lockScalingY = false;
11696
11852
  }
11697
11853
  }
11698
- applyWarpAwareSelectionBorders(selection, groupAngle);
11854
+ applyWarpAwareSelectionBorders(selection, groupAngle, groupFrame);
11699
11855
  }, [pageId]);
11700
11856
  const pageBoundsOptions = react.useMemo(
11701
11857
  () => ({ pageContentWidth: canvasWidth, pageContentHeight: canvasHeight }),
@@ -11798,7 +11954,11 @@ const PageCanvas = react.forwardRef(
11798
11954
  const activeMembers = activeBeforeRestore.getObjects();
11799
11955
  const sameMembers = activeMembers.length === members.length && members.every((member) => activeMembers.includes(member));
11800
11956
  const sameGroup = activeBeforeRestore.__pixldocsGroupSelection === groupId;
11801
- const alreadyAligned = activeBeforeRestore.__pixldocsAlignedAngle != null;
11957
+ const pageNow = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId);
11958
+ const groupNode = pageNow ? findNodeById(pageNow.children ?? [], groupId) : null;
11959
+ const savedAngle = groupNode && isGroup(groupNode) && typeof groupNode.angle === "number" ? groupNode.angle : null;
11960
+ const alignedAngle = activeBeforeRestore.__pixldocsAlignedAngle;
11961
+ const alreadyAligned = typeof alignedAngle === "number" && (savedAngle == null || Math.abs(normalizeAngle180(alignedAngle - savedAngle)) <= 0.01);
11802
11962
  if (sameMembers && sameGroup && alreadyAligned) {
11803
11963
  ensureCanvaControlRenders(activeBeforeRestore);
11804
11964
  return;
@@ -11815,9 +11975,11 @@ const PageCanvas = react.forwardRef(
11815
11975
  members.forEach((m) => m.setCoords());
11816
11976
  } catch {
11817
11977
  }
11818
- try {
11819
- (_a2 = selection.triggerLayout) == null ? void 0 : _a2.call(selection);
11820
- } catch {
11978
+ if (!selection.__pixldocsGroupSelection) {
11979
+ try {
11980
+ (_a2 = selection.triggerLayout) == null ? void 0 : _a2.call(selection);
11981
+ } catch {
11982
+ }
11821
11983
  }
11822
11984
  selection.setCoords();
11823
11985
  fc.requestRenderAll();
@@ -13257,14 +13419,18 @@ const PageCanvas = react.forwardRef(
13257
13419
  const groupNode = findNodeById(pageChildren2, groupId);
13258
13420
  if (!groupNode) return;
13259
13421
  const groupAbs = getAbsoluteBounds(groupNode, pageChildren2);
13422
+ const storedGroupFrame = groupNode && isGroup(groupNode) ? frameFromStoredGroupNode(groupNode, pageChildren2, pageBoundsOptions) : null;
13423
+ const groupFrame = storedGroupFrame ?? groupAbs;
13260
13424
  const rect = active.getBoundingRect();
13261
13425
  groupSelectionTransformStartRef.current = {
13262
13426
  groupId,
13263
13427
  selection: active,
13264
13428
  selectionLeft: rect.left,
13265
13429
  selectionTop: rect.top,
13266
- groupLeft: groupAbs.left,
13267
- groupTop: groupAbs.top,
13430
+ groupLeft: groupFrame.left,
13431
+ groupTop: groupFrame.top,
13432
+ groupWidth: groupFrame.width,
13433
+ groupHeight: groupFrame.height,
13268
13434
  selectionAngle: ((active.angle ?? 0) % 360 + 360) % 360
13269
13435
  };
13270
13436
  logRotDriftSelectionSnapshot("transform-start", active, {
@@ -13432,7 +13598,11 @@ const PageCanvas = react.forwardRef(
13432
13598
  const activeIds = active.getObjects().map((obj) => getObjectId(obj)).filter((id) => !!id && id !== "__background__");
13433
13599
  const sameMembers = activeIds.length === snapshot.memberIds.length && snapshot.memberIds.every((id) => activeIds.includes(id));
13434
13600
  const sameGroup = active.__pixldocsGroupSelection === snapshot.groupSelectionId;
13435
- const alreadyAligned = active.__pixldocsAlignedAngle != null;
13601
+ const pageNow = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId);
13602
+ const groupNode = pageNow ? findNodeById(pageNow.children ?? [], snapshot.groupSelectionId) : null;
13603
+ const savedAngle = groupNode && isGroup(groupNode) && typeof groupNode.angle === "number" ? groupNode.angle : null;
13604
+ const alignedAngle = active.__pixldocsAlignedAngle;
13605
+ const alreadyAligned = typeof alignedAngle === "number" && (savedAngle == null || Math.abs(normalizeAngle180(alignedAngle - savedAngle)) <= 0.01);
13436
13606
  if (sameMembers && sameGroup && alreadyAligned) {
13437
13607
  ensureCanvaControlRenders(active);
13438
13608
  return;
@@ -13589,6 +13759,31 @@ const PageCanvas = react.forwardRef(
13589
13759
  }
13590
13760
  if (members.length === 0) return null;
13591
13761
  const savedGroupAngle = typeof g.angle === "number" && Number.isFinite(g.angle) ? normalizeAngle180(g.angle) : null;
13762
+ const savedGroupWidth = Number(g.width);
13763
+ const savedGroupHeight = Number(g.height);
13764
+ if (savedGroupAngle != null && savedGroupWidth > 0 && savedGroupHeight > 0) {
13765
+ const pageNow = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId);
13766
+ const pageChildrenNow = (pageNow == null ? void 0 : pageNow.children) ?? [];
13767
+ const abs = getAbsoluteBounds(g, pageChildrenNow);
13768
+ const cx = abs.left + savedGroupWidth / 2;
13769
+ const cy = abs.top + savedGroupHeight / 2;
13770
+ const rad2 = savedGroupAngle * Math.PI / 180;
13771
+ const c = Math.cos(rad2), s = Math.sin(rad2);
13772
+ const rotate = (x, y) => {
13773
+ const dx = x - cx;
13774
+ const dy = y - cy;
13775
+ return { x: cx + dx * c - dy * s, y: cy + dx * s + dy * c };
13776
+ };
13777
+ return {
13778
+ corners: [
13779
+ rotate(abs.left, abs.top),
13780
+ rotate(abs.left + savedGroupWidth, abs.top),
13781
+ rotate(abs.left + savedGroupWidth, abs.top + savedGroupHeight),
13782
+ rotate(abs.left, abs.top + savedGroupHeight)
13783
+ ],
13784
+ angle: savedGroupAngle
13785
+ };
13786
+ }
13592
13787
  const worldPoints = [];
13593
13788
  for (const m of members) {
13594
13789
  (_a2 = m.setCoords) == null ? void 0 : _a2.call(m);
@@ -14828,6 +15023,7 @@ const PageCanvas = react.forwardRef(
14828
15023
  fabricCanvas.on("object:rotating", (e) => {
14829
15024
  var _a2, _b2;
14830
15025
  markSimpleTransform(e);
15026
+ prepareGroupSelectionTransformStart(e.target);
14831
15027
  didTransformRef.current = true;
14832
15028
  const tr = e.target;
14833
15029
  if (shouldLogRotDriftLiveTick(tr, "rotating")) {
@@ -15017,7 +15213,7 @@ const PageCanvas = react.forwardRef(
15017
15213
  const groupLeft = fabricCenterX - w / 2;
15018
15214
  const groupTop = fabricCenterY - h / 2;
15019
15215
  const storePosGroup = absoluteToStorePosition(groupLeft, groupTop, movedGroupId, pageChildrenSec);
15020
- useEditorStore.getState().updateNode(movedGroupId, { left: storePosGroup.left, top: storePosGroup.top }, { recordHistory: false, skipLayoutRecalc: true });
15216
+ useEditorStore.getState().updateNode(movedGroupId, { left: storePosGroup.left, top: storePosGroup.top, angle: fabricSectionGroup.angle ?? 0 }, { recordHistory: false, skipLayoutRecalc: true });
15021
15217
  }
15022
15218
  commitHistory();
15023
15219
  unlockEditsSoon();
@@ -15160,7 +15356,7 @@ const PageCanvas = react.forwardRef(
15160
15356
  const groupLeft = centerX - w / 2;
15161
15357
  const groupTop = centerY - h / 2;
15162
15358
  const storePosGroup = absoluteToStorePosition(groupLeft, groupTop, groupId, pageChildrenSec);
15163
- useEditorStore.getState().updateNode(groupId, { left: storePosGroup.left, top: storePosGroup.top }, { recordHistory: false, skipLayoutRecalc: true });
15359
+ useEditorStore.getState().updateNode(groupId, { left: storePosGroup.left, top: storePosGroup.top, angle: active.angle ?? 0 }, { recordHistory: false, skipLayoutRecalc: true });
15164
15360
  }
15165
15361
  const node = findNodeById(pageChildrenSec, groupId);
15166
15362
  if (isChildModified && node && !groupMoved) {
@@ -15189,7 +15385,7 @@ const PageCanvas = react.forwardRef(
15189
15385
  const groupLeft = active.originX === "center" ? centerX - w / 2 : centerX;
15190
15386
  const groupTop = active.originY === "center" ? centerY - h / 2 : centerY;
15191
15387
  const storePos = absoluteToStorePosition(groupLeft, groupTop, groupId, pageChildren3);
15192
- useEditorStore.getState().updateNode(groupId, { left: storePos.left, top: storePos.top }, { recordHistory: false, skipLayoutRecalc: true });
15388
+ useEditorStore.getState().updateNode(groupId, { left: storePos.left, top: storePos.top, angle: active.angle ?? 0 }, { recordHistory: false, skipLayoutRecalc: true });
15193
15389
  commitHistory();
15194
15390
  unlockEditsSoon();
15195
15391
  return;
@@ -15371,13 +15567,37 @@ const PageCanvas = react.forwardRef(
15371
15567
  360 - Math.abs(currentSelAngle - startSelAngle)
15372
15568
  );
15373
15569
  const hadRotation = isActiveSelection && activeObj && angleDelta > 0.01;
15374
- if (hadRotation && activeObj instanceof fabric__namespace.ActiveSelection && activeGroupSelectionId === groupToMove.id) {
15375
- useEditorStore.getState().updateNode(
15376
- groupToMove.id,
15377
- { angle: currentSelAngle },
15378
- { recordHistory: false, skipLayoutRecalc: true }
15379
- );
15380
- activeObj.__pixldocsGroupAngle = currentSelAngle;
15570
+ if ((hadScale || hadRotation) && activeObj instanceof fabric__namespace.ActiveSelection && activeGroupSelectionId === groupToMove.id) {
15571
+ const groupTransformUpdates = {};
15572
+ if (hadRotation) {
15573
+ groupTransformUpdates.angle = normalizeAngle180(currentSelAngle);
15574
+ activeObj.__pixldocsGroupAngle = groupTransformUpdates.angle;
15575
+ }
15576
+ if (hadScale && (transformStart == null ? void 0 : transformStart.groupId) === groupToMove.id) {
15577
+ const center = activeObj.getCenterPoint();
15578
+ const nextWidth = Math.max(1, transformStart.groupWidth * Math.abs(activeObj.scaleX ?? 1));
15579
+ const nextHeight = Math.max(1, transformStart.groupHeight * Math.abs(activeObj.scaleY ?? 1));
15580
+ const storePos = absoluteToStorePosition(
15581
+ center.x - nextWidth / 2,
15582
+ center.y - nextHeight / 2,
15583
+ groupToMove.id,
15584
+ pageChildren2
15585
+ );
15586
+ groupTransformUpdates.left = storePos.left;
15587
+ groupTransformUpdates.top = storePos.top;
15588
+ groupTransformUpdates.width = nextWidth;
15589
+ groupTransformUpdates.height = nextHeight;
15590
+ } else if (!Number.isFinite(Number(groupToMove.width)) || !Number.isFinite(Number(groupToMove.height))) {
15591
+ groupTransformUpdates.width = (transformStart == null ? void 0 : transformStart.groupWidth) ?? groupAbs.width;
15592
+ groupTransformUpdates.height = (transformStart == null ? void 0 : transformStart.groupHeight) ?? groupAbs.height;
15593
+ }
15594
+ if (Object.keys(groupTransformUpdates).length > 0) {
15595
+ useEditorStore.getState().updateNode(
15596
+ groupToMove.id,
15597
+ groupTransformUpdates,
15598
+ { recordHistory: false, skipLayoutRecalc: true }
15599
+ );
15600
+ }
15381
15601
  }
15382
15602
  if (!hadScale && !hadRotation && (Math.abs(deltaX) > 0.1 || Math.abs(deltaY) > 0.1)) {
15383
15603
  const { updateNode: updateNodeStore, commitHistory: commitHistoryStore, getCurrentElements } = useEditorStore.getState();
@@ -16014,9 +16234,11 @@ const PageCanvas = react.forwardRef(
16014
16234
  } catch {
16015
16235
  }
16016
16236
  fabricCanvas.setActiveObject(newSel);
16017
- try {
16018
- (_j = newSel.triggerLayout) == null ? void 0 : _j.call(newSel);
16019
- } catch {
16237
+ if (!wasGroupSel) {
16238
+ try {
16239
+ (_j = newSel.triggerLayout) == null ? void 0 : _j.call(newSel);
16240
+ } catch {
16241
+ }
16020
16242
  }
16021
16243
  try {
16022
16244
  for (const member of membersToReselect) member.setCoords();
@@ -16393,7 +16615,12 @@ const PageCanvas = react.forwardRef(
16393
16615
  const prevAS = activeBeforeSync instanceof fabric__namespace.ActiveSelection ? activeBeforeSync : null;
16394
16616
  const prevMembers = prevAS ? prevAS.getObjects() : [];
16395
16617
  const sameMembers = !!prevAS && prevMembers.length === freshMembers.length && prevMembers.every((m) => freshMembers.includes(m));
16396
- const alreadyAligned = sameMembers && prevAS.__pixldocsAlignedAngle != null;
16618
+ const savedSnapshotAngle = activeSelectionSnapshot.groupSelectionId ? (() => {
16619
+ const groupNode = findNodeById(pageTree, activeSelectionSnapshot.groupSelectionId);
16620
+ return groupNode && isGroup(groupNode) && typeof groupNode.angle === "number" ? groupNode.angle : null;
16621
+ })() : null;
16622
+ const prevAlignedAngle = sameMembers ? prevAS.__pixldocsAlignedAngle : null;
16623
+ const alreadyAligned = sameMembers && typeof prevAlignedAngle === "number" && (savedSnapshotAngle == null || Math.abs(normalizeAngle180(prevAlignedAngle - savedSnapshotAngle)) <= 0.01);
16397
16624
  if (sameMembers && alreadyAligned) {
16398
16625
  try {
16399
16626
  ensureCanvaControlRenders(prevAS);
@@ -17435,11 +17662,10 @@ const PageCanvas = react.forwardRef(
17435
17662
  if (sameSelection && (isFlatGroupSelection || isPureSingleGroupSelection)) {
17436
17663
  if (selectedGroupSelectionId && active instanceof fabric__namespace.ActiveSelection) {
17437
17664
  if (isPureSingleGroupSelection) {
17438
- active.__pixldocsGroupSelection = selectedGroupSelectionId;
17439
- delete active.__pixldocsLogicalGroupIds;
17440
- suppressGroupMemberBordersRef.current = active.getObjects();
17665
+ applyLogicalGroupSelectionVisualState(active, selectedGroupSelectionId);
17441
17666
  } else {
17442
17667
  delete active.__pixldocsGroupSelection;
17668
+ delete active.__pixldocsGroupAngle;
17443
17669
  active.__pixldocsLogicalGroupIds = selectedGroupIds;
17444
17670
  active.hasBorders = true;
17445
17671
  suppressGroupMemberBordersRef.current = active.getObjects().filter((m) => {
@@ -17479,10 +17705,10 @@ const PageCanvas = react.forwardRef(
17479
17705
  const selection = new fabric__namespace.ActiveSelection(toSelect, { canvas: fc });
17480
17706
  if (selectedGroupSelectionId) {
17481
17707
  if (isPureSingleGroupSelection) {
17482
- selection.__pixldocsGroupSelection = selectedGroupSelectionId;
17483
- suppressGroupMemberBordersRef.current = toSelect;
17708
+ applyLogicalGroupSelectionVisualState(selection, selectedGroupSelectionId);
17484
17709
  } else {
17485
17710
  selection.__pixldocsLogicalGroupIds = selectedGroupIds;
17711
+ delete selection.__pixldocsGroupAngle;
17486
17712
  selection.hasBorders = true;
17487
17713
  suppressGroupMemberBordersRef.current = toSelect.filter((m) => {
17488
17714
  const id = getObjectId(m);
@@ -25125,9 +25351,9 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
25125
25351
  }
25126
25352
  return svgString;
25127
25353
  }
25128
- const resolvedPackageVersion = "0.5.394";
25354
+ const resolvedPackageVersion = "0.5.396";
25129
25355
  const PACKAGE_VERSION = resolvedPackageVersion;
25130
- const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.394";
25356
+ const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.396";
25131
25357
  const roundParityValue = (value) => {
25132
25358
  if (typeof value !== "number") return value;
25133
25359
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -25941,7 +26167,7 @@ class PixldocsRenderer {
25941
26167
  await this.waitForCanvasScene(container, cloned, i);
25942
26168
  }
25943
26169
  console.log(`[canvas-renderer][pdf-unified] mounted ${cloned.pages.length} page(s), handing off to client exportMultiPagePdf`);
25944
- const { exportMultiPagePdf, preparePagesForExport } = await Promise.resolve().then(() => require("./vectorPdfExport-RJHpxl55.cjs"));
26170
+ const { exportMultiPagePdf, preparePagesForExport } = await Promise.resolve().then(() => require("./vectorPdfExport-oL70Zv8c.cjs"));
25945
26171
  const prepared = preparePagesForExport(
25946
26172
  cloned.pages,
25947
26173
  canvasWidth,
@@ -28261,7 +28487,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
28261
28487
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
28262
28488
  sanitizeSvgTreeForPdf(svgToDraw);
28263
28489
  try {
28264
- const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await Promise.resolve().then(() => require("./vectorPdfExport-RJHpxl55.cjs"));
28490
+ const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await Promise.resolve().then(() => require("./vectorPdfExport-oL70Zv8c.cjs"));
28265
28491
  try {
28266
28492
  await logTextMeasurementDiagnostic(svgToDraw);
28267
28493
  } catch {
@@ -28658,4 +28884,4 @@ exports.setAutoShrinkDebug = setAutoShrinkDebug;
28658
28884
  exports.setBundledAssetPrefixes = setBundledAssetPrefixes;
28659
28885
  exports.warmResolvedTemplateForPreview = warmResolvedTemplateForPreview;
28660
28886
  exports.warmTemplateFromForm = warmTemplateFromForm;
28661
- //# sourceMappingURL=index-mH5LwfhM.cjs.map
28887
+ //# sourceMappingURL=index-DQYnrYMM.cjs.map