@inditextech/weave-sdk 5.0.0-SNAPSHOT.377.1 → 5.0.0-SNAPSHOT.403.1

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.
package/dist/sdk.node.js CHANGED
@@ -3500,13 +3500,34 @@ function getBoundingBox(nodes, config) {
3500
3500
  height: maxY - minY
3501
3501
  };
3502
3502
  }
3503
+ /**
3504
+ * Builds an array of Weave node IDs from `groupId` up to the root,
3505
+ * including only nodes that have a `nodeType` attribute (actual Weave nodes,
3506
+ * not Konva stage/layer). Used as the multi-stop set for `getInstanceRecursive`
3507
+ * and group-context ancestor checks.
3508
+ */
3509
+ function buildAncestorGroupIds(groupId, stage) {
3510
+ const ids = [];
3511
+ let cur = stage.findOne(`#${groupId}`) ?? null;
3512
+ while (cur) {
3513
+ const id = cur.getAttrs().id;
3514
+ if (id && cur.getAttrs().nodeType) ids.push(id);
3515
+ cur = cur.getParent();
3516
+ }
3517
+ return ids;
3518
+ }
3503
3519
  function getTargetedNode(instance) {
3504
3520
  const stage = instance.getStage();
3505
3521
  let selectedGroup = void 0;
3506
3522
  const mousePos = stage.getPointerPosition();
3507
3523
  if (mousePos) {
3508
3524
  const inter = stage.getIntersection(mousePos);
3509
- if (inter) selectedGroup = instance.getInstanceRecursive(inter);
3525
+ if (inter) {
3526
+ const selectionPlugin = instance.getPlugin("nodesSelection");
3527
+ const activeGroupContext = selectionPlugin?.getActiveGroupContext() ?? void 0;
3528
+ const stopIds = activeGroupContext ? buildAncestorGroupIds(activeGroupContext, stage) : void 0;
3529
+ selectedGroup = instance.getInstanceRecursive(inter, [], stopIds);
3530
+ }
3510
3531
  }
3511
3532
  return selectedGroup;
3512
3533
  }
@@ -3647,7 +3668,7 @@ function getVisibleNodes({ instance, skipNodes, referenceLayer }) {
3647
3668
  nodes.forEach((node) => {
3648
3669
  const actualNodeParent = instance.getNodeContainer(node);
3649
3670
  if (actualNodeParent?.getAttrs().id !== referenceLayer?.getAttrs().id) return;
3650
- if (node.getParent()?.getAttrs().nodeType === "group") return;
3671
+ if (node.getParent()?.getAttrs().nodeType === "group" && node.getParent()?.getAttrs().id !== referenceLayer?.getAttrs().id) return;
3651
3672
  if (skipNodes.includes(node.getParent()?.getAttrs().nodeId)) return;
3652
3673
  if (skipNodes.includes(node.getAttrs().id ?? "")) return;
3653
3674
  if (node.getAttrs().nodeType === "connector") return;
@@ -4355,6 +4376,7 @@ var TransformerController = class {
4355
4376
  listening: true,
4356
4377
  shouldOverdrawWholeArea: true
4357
4378
  });
4379
+ this.tr.boundBoxFunc(this.getBoundBoxFunc());
4358
4380
  layer.add(this.tr);
4359
4381
  this.trHover = new Konva.Transformer({
4360
4382
  id: "hoverTransformer",
@@ -4370,6 +4392,20 @@ var TransformerController = class {
4370
4392
  this.registerTransformerEvents();
4371
4393
  this.registerInstanceEvents();
4372
4394
  }
4395
+ getBoundBoxFunc() {
4396
+ return (oldBox, newBox) => {
4397
+ const sx = newBox.width / oldBox.width;
4398
+ const sy = newBox.height / oldBox.height;
4399
+ const violatesConstraint = this.tr.nodes().some((node) => {
4400
+ const rect = node.getClientRect({ skipStroke: true });
4401
+ const { width: minWidth, height: minHeight } = node.getNodeMinSize();
4402
+ if (["middle-right", "middle-left"].includes(this.tr.getActiveAnchor() ?? "")) return rect.width * sx < minWidth;
4403
+ if (["top-center", "bottom-center"].includes(this.tr.getActiveAnchor() ?? "")) return rect.height * sy < minHeight;
4404
+ return rect.width * sx < minWidth || rect.height * sy < minHeight;
4405
+ });
4406
+ return violatesConstraint ? oldBox : newBox;
4407
+ };
4408
+ }
4373
4409
  getTransformer() {
4374
4410
  return this.tr;
4375
4411
  }
@@ -4810,6 +4846,7 @@ function handleClickOrTap(ctx, e) {
4810
4846
  let areNodesSelected = false;
4811
4847
  let nodeTargeted = selectedGroup && !(selectedGroup.getAttrs().active ?? false) ? selectedGroup : e.target;
4812
4848
  if (e.target === weave.getStage()) {
4849
+ if (ctx.getActiveGroupContext() !== null) ctx.exitGroupContext();
4813
4850
  ctx.getGesture().resetDoubleTap();
4814
4851
  ctx.getNodesSelectionFeedbackPlugin()?.cleanupSelectedHalos();
4815
4852
  return;
@@ -4819,6 +4856,18 @@ function handleClickOrTap(ctx, e) {
4819
4856
  ctx.getGesture().resetDoubleTap();
4820
4857
  return;
4821
4858
  }
4859
+ const activeGroupContext = ctx.getActiveGroupContext();
4860
+ if (activeGroupContext !== null) {
4861
+ const isInsideActiveGroup = isNodeInsideGroup(nodeTargeted, activeGroupContext, stage);
4862
+ if (isInsideActiveGroup) {
4863
+ const parentId = nodeTargeted.getParent()?.getAttrs().id ?? "";
4864
+ if (parentId !== activeGroupContext) ctx.enterGroupContext(parentId);
4865
+ }
4866
+ if (!isInsideActiveGroup) {
4867
+ ctx.exitGroupContext();
4868
+ nodeTargeted = weave.getInstanceRecursive(nodeTargeted);
4869
+ }
4870
+ }
4822
4871
  const metaPressed = e.evt.shiftKey || e.evt.ctrlKey || e.evt.metaKey;
4823
4872
  const nodeSelectedIndex = tr.nodes().findIndex((node) => {
4824
4873
  return node.getAttrs().id === nodeTargeted.getAttrs().id;
@@ -4834,13 +4883,21 @@ function handleClickOrTap(ctx, e) {
4834
4883
  const isMainLayer = parent === mainLayer;
4835
4884
  const isContainerEmptyArea = e.target.getAttrs().isContainerPrincipal !== void 0 && !e.target.getAttrs().isContainerPrincipal;
4836
4885
  if (isStage || isMainLayer || isContainerEmptyArea) ctx.setSelectedNodes([]);
4886
+ ctx.triggerSelectedNodesEvent();
4837
4887
  return;
4838
4888
  }
4839
- if (nodeTargeted.getAttrs().nodeId) {
4889
+ if (!nodeTargeted.getAttrs().name?.includes("node") && nodeTargeted.getAttrs().nodeId) {
4840
4890
  const realNode = stage.findOne(`#${nodeTargeted.getAttrs().nodeId}`);
4841
4891
  if (realNode) nodeTargeted = realNode;
4842
4892
  }
4843
4893
  if (typeof nodeTargeted.getAttrs().isContainerPrincipal !== "undefined" && !nodeTargeted.getAttrs().isContainerPrincipal) return;
4894
+ let cur = nodeTargeted;
4895
+ let p = cur.getParent();
4896
+ while (p?.getAttrs().nodeType === "group" && activeGroupContext !== p.getAttrs().id && !isAncestorOfActiveGroup(p, activeGroupContext, stage)) {
4897
+ cur = p;
4898
+ p = cur.getParent();
4899
+ }
4900
+ nodeTargeted = cur;
4844
4901
  if (ctx.getGesture().isDoubleTap && !metaPressed) {
4845
4902
  ctx.getGesture().resetDoubleTap();
4846
4903
  nodeTargeted.dblClick();
@@ -4872,6 +4929,40 @@ function handleClickOrTap(ctx, e) {
4872
4929
  }
4873
4930
  ctx.triggerSelectedNodesEvent();
4874
4931
  }
4932
+ /**
4933
+ * Returns true if `node` is anywhere within the group hierarchy rooted at
4934
+ * `groupId`: either a descendant of the active group itself, or a descendant
4935
+ * of any ancestor group in the path from `groupId` to the root.
4936
+ * Used to decide whether a click should keep the current group context active.
4937
+ */
4938
+ function isNodeInsideGroup(node, groupId, stage) {
4939
+ const groupNode = stage.findOne(`#${groupId}`);
4940
+ if (!groupNode) return false;
4941
+ const containerIds = new Set(buildAncestorGroupIds(groupId, stage));
4942
+ let current = node;
4943
+ while (current) {
4944
+ if (containerIds.has(current.getAttrs().id ?? "")) return true;
4945
+ current = current.getParent();
4946
+ }
4947
+ return false;
4948
+ }
4949
+ /**
4950
+ * Returns true if `node` is a (strict) ancestor of the active group — i.e.
4951
+ * it appears in the parent chain above `activeGroupId`, not at the group
4952
+ * itself and not below it.
4953
+ */
4954
+ function isAncestorOfActiveGroup(node, activeGroupId, stage) {
4955
+ if (!node || !activeGroupId) return false;
4956
+ const nodeId = node.getAttrs().id ?? "";
4957
+ const activeGroupNode = stage.findOne(`#${activeGroupId}`);
4958
+ if (!activeGroupNode) return false;
4959
+ let cur = activeGroupNode.getParent();
4960
+ while (cur) {
4961
+ if ((cur.getAttrs().id ?? "") === nodeId) return true;
4962
+ cur = cur.getParent();
4963
+ }
4964
+ return false;
4965
+ }
4875
4966
 
4876
4967
  //#endregion
4877
4968
  //#region src/plugins/nodes-selection/events/keyboard.ts
@@ -4885,6 +4976,13 @@ function registerKeyboardHandlers(ctx) {
4885
4976
  const signal = ctx.getWeaveInstance().getEventsController().signal;
4886
4977
  stage.container().addEventListener("keydown", (e) => {
4887
4978
  if (e.code === "Space") ctx.setSpaceKeyPressed(true);
4979
+ if (e.code === "Escape") {
4980
+ if (ctx.getActiveGroupContext() !== null) {
4981
+ ctx.exitGroupContext();
4982
+ e.stopPropagation();
4983
+ return;
4984
+ }
4985
+ }
4888
4986
  if (e.code === "Backspace" || e.code === "Delete") {
4889
4987
  Promise.resolve().then(() => {
4890
4988
  ctx.removeSelectedNodes();
@@ -4948,7 +5046,10 @@ function handlePointerDown(ctx, e) {
4948
5046
  const nodesSelected = tr.nodes();
4949
5047
  for (const node of nodesSelected) node.fire("onSelectionCleared", { bubbles: true });
4950
5048
  }
5049
+ const activeGroupContext = ctx.getActiveGroupContext();
5050
+ if (activeGroupContext !== null) ctx.exitGroupContext();
4951
5051
  ctx.selectNone();
5052
+ ctx.triggerSelectedNodesEvent();
4952
5053
  ctx.getWeaveInstance().emitEvent("onSelectionState", true);
4953
5054
  ctx.getEdgePanning().start();
4954
5055
  }
@@ -5094,6 +5195,8 @@ var WeaveNodesSelectionPlugin = class extends WeavePlugin {
5094
5195
  gesture = new GestureDetector();
5095
5196
  _handledClickOrTap = false;
5096
5197
  dragSelectedNodes = [];
5198
+ _activeGroupContext = null;
5199
+ onRender = void 0;
5097
5200
  constructor(params) {
5098
5201
  super();
5099
5202
  this.config = mergeExceptArrays(WEAVE_NODES_SELECTION_DEFAULT_CONFIG, params?.config ?? {});
@@ -5118,6 +5221,8 @@ var WeaveNodesSelectionPlugin = class extends WeavePlugin {
5118
5221
  this.enabled = false;
5119
5222
  this.pointers = {};
5120
5223
  this.dragSelectedNodes = [];
5224
+ this._handledClickOrTap = false;
5225
+ this._activeGroupContext = null;
5121
5226
  }
5122
5227
  getName() {
5123
5228
  return WEAVE_NODES_SELECTION_KEY;
@@ -5239,6 +5344,7 @@ var WeaveNodesSelectionPlugin = class extends WeavePlugin {
5239
5344
  this.active = true;
5240
5345
  });
5241
5346
  this.instance.addEventListener("onNodeRemoved", (node) => {
5347
+ if (this._activeGroupContext === node.id) this.exitGroupContext();
5242
5348
  const selectedNodes = this.getSelectedNodes();
5243
5349
  const newSelectedNodes = selectedNodes.filter((actNode) => actNode.getAttrs().id !== node.id);
5244
5350
  this.setSelectedNodes(newSelectedNodes);
@@ -5247,6 +5353,31 @@ var WeaveNodesSelectionPlugin = class extends WeavePlugin {
5247
5353
  stage.container().style.cursor = "default";
5248
5354
  });
5249
5355
  }
5356
+ getActiveGroupContext() {
5357
+ return this._activeGroupContext;
5358
+ }
5359
+ enterGroupContext(groupId) {
5360
+ const stage = this.instance.getStage();
5361
+ const groupNode = stage.findOne(`#${groupId}`);
5362
+ if (!groupNode) return;
5363
+ this._activeGroupContext = groupId;
5364
+ groupNode.getChildren().forEach((child) => {
5365
+ child.setAttr("draggable", true);
5366
+ });
5367
+ this.selectNone();
5368
+ this.instance.emitEvent("onGroupContextChange", groupId);
5369
+ }
5370
+ exitGroupContext() {
5371
+ if (this._activeGroupContext === null) return;
5372
+ const stage = this.instance.getStage();
5373
+ const groupNode = stage.findOne(`#${this._activeGroupContext}`);
5374
+ if (groupNode) groupNode.getChildren().forEach((child) => {
5375
+ child.setAttr("draggable", false);
5376
+ });
5377
+ this._activeGroupContext = null;
5378
+ this.selectNone();
5379
+ this.instance.emitEvent("onGroupContextChange", null);
5380
+ }
5250
5381
  getLayer() {
5251
5382
  const stage = this.instance.getStage();
5252
5383
  return stage.findOne(`#${this.getLayerName()}`);
@@ -5431,6 +5562,9 @@ var WeaveNodesSelectionPlugin = class extends WeavePlugin {
5431
5562
  isDragging() {
5432
5563
  return this.transformerCtrl.isDragging();
5433
5564
  }
5565
+ getBoundBoxFunc() {
5566
+ return this.transformerCtrl.getBoundBoxFunc();
5567
+ }
5434
5568
  getSelectorConfig() {
5435
5569
  return this.config.selection;
5436
5570
  }
@@ -6005,6 +6139,12 @@ const augmentKonvaNodeClass = (config) => {
6005
6139
  };
6006
6140
  Konva.Node.prototype.lockMutex = function() {};
6007
6141
  Konva.Node.prototype.releaseMutex = function() {};
6142
+ Konva.Node.prototype.getNodeMinSize = function() {
6143
+ return {
6144
+ width: 0,
6145
+ height: 0
6146
+ };
6147
+ };
6008
6148
  };
6009
6149
  var WeaveNode = class {
6010
6150
  async register(instance) {
@@ -6168,8 +6308,8 @@ var WeaveNode = class {
6168
6308
  if (selectionPlugin?.getSelectedNodes().map((node) => node.getAttrs().id).includes(ele.getAttrs().id)) return true;
6169
6309
  return false;
6170
6310
  }
6171
- scaleReset(node) {
6172
- const scale = node.scale();
6311
+ scaleReset(node, scaleCustom) {
6312
+ const scale = scaleCustom ?? node.scale();
6173
6313
  node.width(Math.max(5, node.width() * scale.x));
6174
6314
  node.height(Math.max(5, node.height() * scale.y));
6175
6315
  node.scale({
@@ -6188,10 +6328,27 @@ var WeaveNode = class {
6188
6328
  this.hideHoverState();
6189
6329
  return;
6190
6330
  }
6331
+ if (this.isHoverSuppressedByGroupContext(node, selectionPlugin)) {
6332
+ this.hideHoverState();
6333
+ return;
6334
+ }
6191
6335
  if (node?.canBeHovered?.()) selectionPlugin.getHoverTransformer().nodes([node]);
6192
6336
  else selectionPlugin.getHoverTransformer().nodes([]);
6193
6337
  selectionPlugin.getHoverTransformer().moveToTop();
6194
6338
  }
6339
+ isHoverSuppressedByGroupContext(node, selectionPlugin) {
6340
+ const activeGroupId = selectionPlugin.getActiveGroupContext();
6341
+ if (activeGroupId === null) return false;
6342
+ const activeGroupNode = this.instance.getStage().findOne(`#${activeGroupId}`);
6343
+ if (!activeGroupNode) return false;
6344
+ const hoveredId = node.getAttrs().id ?? "";
6345
+ let current = activeGroupNode;
6346
+ while (current) {
6347
+ if ((current.getAttrs().id ?? "") === hoveredId) return true;
6348
+ current = current.getParent();
6349
+ }
6350
+ return false;
6351
+ }
6195
6352
  hideHoverState() {
6196
6353
  const selectionPlugin = this.getSelectionPlugin();
6197
6354
  if (!selectionPlugin) return;
@@ -6259,8 +6416,6 @@ var WeaveNode = class {
6259
6416
  if (e.target.getAttrs()._revertStrokeScaleEnabled === true) e.target.setAttr("strokeScaleEnabled", true);
6260
6417
  e.target.setAttr("_revertStrokeScaleEnabled", void 0);
6261
6418
  this.instance.emitEvent("onTransform", null);
6262
- const nodesSelectionPlugin = this.instance.getPlugin("nodesSelection");
6263
- if (nodesSelectionPlugin) nodesSelectionPlugin.getTransformer().forceUpdate();
6264
6419
  if (performScaleReset) this.scaleReset(node$1);
6265
6420
  if (this.getSelectionPlugin()?.getSelectedNodes().length === 1) {
6266
6421
  this.getNodesSelectionFeedbackPlugin()?.showSelectionHalo(node$1);
@@ -6269,8 +6424,13 @@ var WeaveNode = class {
6269
6424
  const nodeHandler = this.instance.getNodeHandler(node$1.getAttrs().nodeType);
6270
6425
  if (nodeHandler) {
6271
6426
  const shouldUpdateOnTransform = node$1.getAttrs().shouldUpdateOnTransform ?? true;
6272
- if (shouldUpdateOnTransform) this.instance.updateNode(nodeHandler.serialize(node$1));
6427
+ if (shouldUpdateOnTransform) {
6428
+ const serializedNode = nodeHandler.serialize(node$1);
6429
+ this.instance.updateNode(serializedNode);
6430
+ }
6273
6431
  }
6432
+ const nodesSelectionPlugin = this.instance.getPlugin("nodesSelection");
6433
+ if (nodesSelectionPlugin) nodesSelectionPlugin.getTransformer().forceUpdate();
6274
6434
  this.getNodesSelectionPlugin()?.getHoverTransformer().forceUpdate();
6275
6435
  });
6276
6436
  const stage = this.instance.getStage();
@@ -6446,7 +6606,8 @@ var WeaveNode = class {
6446
6606
  }
6447
6607
  this.instance.emitEvent("onDrag", null);
6448
6608
  const realNodeTarget = this.getRealSelectedNode(nodeTarget);
6449
- if (this.isSelecting() && this.getSelectionPlugin()?.getSelectedNodes().length === 1 && (realNodeTarget.getAttrs().lockToContainer === void 0 || !realNodeTarget.getAttrs().lockToContainer)) this.instance.stateTransactional(() => {
6609
+ const isInGroupContext = (this.getSelectionPlugin()?.getActiveGroupContext() ?? null) !== null;
6610
+ if (!isInGroupContext && this.isSelecting() && this.getSelectionPlugin()?.getSelectedNodes().length === 1 && (realNodeTarget.getAttrs().lockToContainer === void 0 || !realNodeTarget.getAttrs().lockToContainer)) this.instance.stateTransactional(() => {
6450
6611
  clearContainerTargets(this.instance);
6451
6612
  const layerToMove = containerOverCursor(this.instance, [realNodeTarget]);
6452
6613
  let containerToMove = this.instance.getMainLayer();
@@ -6534,7 +6695,19 @@ var WeaveNode = class {
6534
6695
  const user = this.instance.getStore().getUser();
6535
6696
  const activeAction = this.instance.getActiveAction();
6536
6697
  const isNodeSelectionEnabled = this.getSelectionPlugin()?.isEnabled();
6537
- const realNode = this.instance.getInstanceRecursive(node);
6698
+ const activeGroupCtx = this.getSelectionPlugin()?.getActiveGroupContext() ?? void 0;
6699
+ let realNode;
6700
+ if (activeGroupCtx) {
6701
+ const stage$1 = this.instance.getStage();
6702
+ const stopIds = [];
6703
+ let cur = stage$1.findOne(`#${activeGroupCtx}`) ?? null;
6704
+ while (cur) {
6705
+ const id = cur.getAttrs().id;
6706
+ if (id) stopIds.push(id);
6707
+ cur = cur.getParent();
6708
+ }
6709
+ realNode = this.instance.getInstanceRecursive(node, [], stopIds);
6710
+ } else realNode = this.instance.getInstanceRecursive(node);
6538
6711
  const canBeTargeted = realNode.getAttrs().canBeTargeted !== false;
6539
6712
  const isLocked = realNode.getAttrs().locked ?? false;
6540
6713
  const isMutexLocked = realNode.getAttrs().mutexLocked && realNode.getAttrs().mutexUserId !== user.id;
@@ -8415,7 +8588,11 @@ var WeaveGroupsManager = class {
8415
8588
  this.instance.removeNodes(sortedNodesByZIndex);
8416
8589
  groupInstance.destroy();
8417
8590
  const groupNode = stage.findOne(`#${groupId}`);
8418
- if (groupHandler && groupNode) this.instance.updateNodeNT(groupHandler.serialize(groupNode));
8591
+ if (groupHandler && groupNode) {
8592
+ groupNode.x(0);
8593
+ groupNode.y(0);
8594
+ this.instance.updateNodeNT(groupHandler.serialize(groupNode));
8595
+ }
8419
8596
  setTimeout(() => {
8420
8597
  this.getNodesMultiSelectionFeedbackPlugin()?.cleanupSelectedHalos();
8421
8598
  const groupNode$1 = stage.findOne(`#${groupId}`);
@@ -8477,6 +8654,8 @@ var WeaveGroupsManager = class {
8477
8654
  y: absScale.y / stage.scaleY()
8478
8655
  });
8479
8656
  child.rotation(absRotation);
8657
+ const nodeHandler = this.instance.getNodeHandler(child.getAttrs().nodeType);
8658
+ if (nodeHandler) nodeHandler.scaleReset(child);
8480
8659
  child.zIndex(newLayerChildrenAmount - 1 + child.zIndex());
8481
8660
  child.setAttr("draggable", true);
8482
8661
  newChildId = child.getAttrs().id;
@@ -9287,6 +9466,7 @@ var WeaveStateManager = class {
9287
9466
  }
9288
9467
  const yjsProps = yjsNode.get("props");
9289
9468
  this.updateYjsMapFromObject(yjsProps, node.props);
9469
+ if (Array.isArray(node.props.children) && node.props.children.length > 0) for (const child of node.props.children) this.updateNode(child);
9290
9470
  this.instance.emitEvent("onNodeUpdated", node);
9291
9471
  }
9292
9472
  updateNodes(nodes) {
@@ -9481,7 +9661,7 @@ var WeaveRegisterManager = class {
9481
9661
 
9482
9662
  //#endregion
9483
9663
  //#region package.json
9484
- var version = "5.0.0-SNAPSHOT.377.1";
9664
+ var version = "5.0.0-SNAPSHOT.403.1";
9485
9665
 
9486
9666
  //#endregion
9487
9667
  //#region src/managers/setup.ts
@@ -9575,13 +9755,18 @@ var WeaveStageManager = class {
9575
9755
  const stage = this.getStage();
9576
9756
  return stage.findOne(`#${WEAVE_UTILITY_LAYER_ID}`);
9577
9757
  }
9578
- getInstanceRecursive(instance, filterInstanceType = []) {
9758
+ getInstanceRecursive(instance, filterInstanceType = [], stopAtGroupId) {
9579
9759
  const attributes = instance.getAttrs();
9760
+ if (stopAtGroupId) {
9761
+ const parentId = instance.getParent()?.getAttrs().id ?? "";
9762
+ const matches = Array.isArray(stopAtGroupId) ? stopAtGroupId.includes(parentId) : parentId === stopAtGroupId;
9763
+ if (matches) return instance;
9764
+ }
9580
9765
  if (instance.getParent() && instance.getParent()?.getAttrs().nodeType && ![
9581
9766
  "stage",
9582
9767
  "layer",
9583
9768
  ...filterInstanceType
9584
- ].includes(instance.getParent()?.getAttrs().nodeType)) return this.getInstanceRecursive(instance.getParent());
9769
+ ].includes(instance.getParent()?.getAttrs().nodeType)) return this.getInstanceRecursive(instance.getParent(), filterInstanceType, stopAtGroupId);
9585
9770
  if (attributes.id === "mainLayer") return this.instance.getMainLayer();
9586
9771
  if (attributes.id === "stage") return this.instance.getMainLayer();
9587
9772
  return instance;
@@ -10920,8 +11105,8 @@ var Weave = class {
10920
11105
  getStageConfiguration() {
10921
11106
  return this.stageManager.getConfiguration();
10922
11107
  }
10923
- getInstanceRecursive(instance, filterInstanceType = []) {
10924
- return this.stageManager.getInstanceRecursive(instance, filterInstanceType);
11108
+ getInstanceRecursive(instance, filterInstanceType = [], stopAtGroupId) {
11109
+ return this.stageManager.getInstanceRecursive(instance, filterInstanceType, stopAtGroupId);
10925
11110
  }
10926
11111
  getContainerNodes() {
10927
11112
  return this.stageManager.getContainerNodes();
@@ -11870,10 +12055,55 @@ var WeaveGroupNode = class extends WeaveNode {
11870
12055
  return intersectArrays(anchorsArrays);
11871
12056
  };
11872
12057
  this.setupDefaultNodeEvents(group);
12058
+ group.on("transform", () => {
12059
+ const sx = group.scaleX();
12060
+ const sy = group.scaleY();
12061
+ group.getChildren().forEach((child) => {
12062
+ child.scaleX(child.scaleX() * sx);
12063
+ child.scaleY(child.scaleY() * sy);
12064
+ child.x(child.x() * sx);
12065
+ child.y(child.y() * sy);
12066
+ const nodeHandler = this.instance.getNodeHandler(child.getAttrs().nodeType);
12067
+ if (nodeHandler) {
12068
+ nodeHandler.scaleReset(child);
12069
+ nodeHandler.onUpdate(child, child.getAttrs());
12070
+ }
12071
+ });
12072
+ group.scale({
12073
+ x: 1,
12074
+ y: 1
12075
+ });
12076
+ });
12077
+ group.dblClick = () => {
12078
+ const selectionPlugin = this.instance.getPlugin("nodesSelection");
12079
+ if (!selectionPlugin) return;
12080
+ const groupId = group.getAttrs().id ?? "";
12081
+ selectionPlugin.enterGroupContext(groupId);
12082
+ const stage = this.instance.getStage();
12083
+ const mousePos = stage.getPointerPosition();
12084
+ if (!mousePos) return;
12085
+ const selectionLayer = stage.findOne("#" + selectionPlugin.getLayerName());
12086
+ selectionLayer?.listening(false);
12087
+ const intersected = stage.getIntersection(mousePos);
12088
+ selectionLayer?.listening(true);
12089
+ if (!intersected) return;
12090
+ const directChild = this.instance.getInstanceRecursive(intersected, [], groupId);
12091
+ if (directChild.getParent()?.getAttrs().id === groupId) {
12092
+ selectionPlugin.setSelectedNodes([directChild]);
12093
+ selectionPlugin.triggerSelectedNodesEvent();
12094
+ }
12095
+ };
11873
12096
  return group;
11874
12097
  }
11875
12098
  onUpdate(nodeInstance, nextProps) {
11876
- nodeInstance.setAttrs({ ...nextProps });
12099
+ nodeInstance.setAttrs({
12100
+ ...nextProps,
12101
+ x: nextProps.x ?? 0,
12102
+ y: nextProps.y ?? 0,
12103
+ scaleX: nextProps.scaleX ?? 1,
12104
+ scaleY: nextProps.scaleY ?? 1,
12105
+ rotation: nextProps.rotation ?? 0
12106
+ });
11877
12107
  const nodesSelectionPlugin = this.instance.getPlugin("nodesSelection");
11878
12108
  if (nodesSelectionPlugin) nodesSelectionPlugin.getTransformer().forceUpdate();
11879
12109
  }
@@ -11910,18 +12140,578 @@ var WeaveGroupNode = class extends WeaveNode {
11910
12140
  }
11911
12141
  };
11912
12142
  }
11913
- scaleReset() {}
12143
+ scaleReset(node) {
12144
+ const sx = node.scaleX();
12145
+ const sy = node.scaleY();
12146
+ node.getChildren().forEach((child) => {
12147
+ child.scaleX(child.scaleX() * sx);
12148
+ child.scaleY(child.scaleY() * sy);
12149
+ child.x(child.x() * sx);
12150
+ child.y(child.y() * sy);
12151
+ const nodeHandler = this.instance.getNodeHandler(child.getAttrs().nodeType);
12152
+ if (nodeHandler) {
12153
+ nodeHandler.scaleReset(child);
12154
+ nodeHandler.onUpdate(child, child.getAttrs());
12155
+ }
12156
+ });
12157
+ node.scale({
12158
+ x: 1,
12159
+ y: 1
12160
+ });
12161
+ }
11914
12162
  };
11915
12163
 
11916
12164
  //#endregion
11917
12165
  //#region src/nodes/rectangle/constants.ts
11918
12166
  const WEAVE_RECTANGLE_NODE_TYPE = "rectangle";
11919
12167
 
12168
+ //#endregion
12169
+ //#region src/nodes/shared/shape-label.constants.ts
12170
+ const WEAVE_STAGE_SHAPE_LABEL_EDITION_MODE = "shape-label-edition";
12171
+ const WEAVE_SHAPE_LABEL_DEFAULTS = {
12172
+ labelText: "",
12173
+ labelFontFamily: "Arial, sans-serif",
12174
+ labelFontSize: 14,
12175
+ labelFontStyle: "normal",
12176
+ labelFontVariant: "normal",
12177
+ labelTextDecoration: "",
12178
+ labelFill: "#000000",
12179
+ labelAlign: "center",
12180
+ labelVerticalAlign: "middle",
12181
+ labelLetterSpacing: 0,
12182
+ labelLineHeight: 1,
12183
+ labelPaddingX: 8,
12184
+ labelPaddingY: 8
12185
+ };
12186
+ const labelId = (id) => `${id}-label`;
12187
+
12188
+ //#endregion
12189
+ //#region src/nodes/shared/shape-label-editor.ts
12190
+ var WeaveShapeLabelEditor = class {
12191
+ editing = false;
12192
+ editingGroup = null;
12193
+ editingTextBounds = null;
12194
+ textArea = null;
12195
+ onLiveResize = null;
12196
+ constructor(instance) {
12197
+ this.instance = instance;
12198
+ }
12199
+ isEditing() {
12200
+ return this.editing;
12201
+ }
12202
+ renderLabel(group, props, textBounds) {
12203
+ const labelText = props.labelText ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelText;
12204
+ const labelNode = new Konva.Text({
12205
+ id: labelId(props.id),
12206
+ x: textBounds.x,
12207
+ y: textBounds.y,
12208
+ width: textBounds.width,
12209
+ height: textBounds.height,
12210
+ text: labelText,
12211
+ fontFamily: props.labelFontFamily ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelFontFamily,
12212
+ fontSize: props.labelFontSize ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelFontSize,
12213
+ fontStyle: props.labelFontStyle ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelFontStyle,
12214
+ fontVariant: props.labelFontVariant ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelFontVariant,
12215
+ textDecoration: props.labelTextDecoration ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelTextDecoration,
12216
+ fill: props.labelFill ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelFill,
12217
+ align: props.labelAlign ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelAlign,
12218
+ verticalAlign: props.labelVerticalAlign ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelVerticalAlign,
12219
+ letterSpacing: props.labelLetterSpacing ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelLetterSpacing,
12220
+ lineHeight: props.labelLineHeight ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelLineHeight,
12221
+ wrap: "word",
12222
+ listening: false,
12223
+ visible: labelText !== ""
12224
+ });
12225
+ group.add(labelNode);
12226
+ return labelNode;
12227
+ }
12228
+ updateLabel(group, nextProps, textBounds, growCallback) {
12229
+ const labelNode = group.findOne(`#${labelId(nextProps.id)}`);
12230
+ if (!labelNode) return;
12231
+ const labelText = nextProps.labelText ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelText;
12232
+ labelNode.setAttrs({
12233
+ x: textBounds.x,
12234
+ y: textBounds.y,
12235
+ width: textBounds.width,
12236
+ height: textBounds.height,
12237
+ text: labelText,
12238
+ fontFamily: nextProps.labelFontFamily ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelFontFamily,
12239
+ fontSize: nextProps.labelFontSize ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelFontSize,
12240
+ fontStyle: nextProps.labelFontStyle ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelFontStyle,
12241
+ fontVariant: nextProps.labelFontVariant ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelFontVariant,
12242
+ textDecoration: nextProps.labelTextDecoration ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelTextDecoration,
12243
+ fill: nextProps.labelFill ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelFill,
12244
+ align: nextProps.labelAlign ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelAlign,
12245
+ verticalAlign: nextProps.labelVerticalAlign ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelVerticalAlign,
12246
+ letterSpacing: nextProps.labelLetterSpacing ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelLetterSpacing,
12247
+ lineHeight: nextProps.labelLineHeight ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelLineHeight,
12248
+ wrap: "word",
12249
+ visible: !this.editing && labelText !== ""
12250
+ });
12251
+ if (labelText !== "") {
12252
+ labelNode.setAttr("height", void 0);
12253
+ const measuredHeight = labelNode.height();
12254
+ labelNode.height(Math.max(textBounds.height, measuredHeight));
12255
+ if (growCallback && measuredHeight > textBounds.height) {
12256
+ const paddingY = nextProps.labelPaddingY ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelPaddingY;
12257
+ growCallback(measuredHeight + paddingY * 2);
12258
+ }
12259
+ }
12260
+ }
12261
+ computeVerticalOffset(verticalAlign, boundsHeightPx, contentHeightPx) {
12262
+ if (verticalAlign === "top") return 0;
12263
+ if (verticalAlign === "bottom") return Math.max(0, boundsHeightPx - contentHeightPx);
12264
+ return Math.max(0, (boundsHeightPx - contentHeightPx) / 2);
12265
+ }
12266
+ triggerEditMode(group, textBounds, onCommit, onLiveResize) {
12267
+ if (this.editing) return;
12268
+ const lockAcquired = this.instance.setMutexLock({
12269
+ nodeIds: [group.id()],
12270
+ operation: "label-edit"
12271
+ });
12272
+ if (!lockAcquired) return;
12273
+ this.editing = true;
12274
+ this.editingGroup = group;
12275
+ this.editingTextBounds = textBounds;
12276
+ this.onLiveResize = onLiveResize ?? null;
12277
+ const labelNode = group.findOne(`#${labelId(group.id())}`);
12278
+ if (labelNode) labelNode.visible(false);
12279
+ const selectionPlugin = this.instance.getPlugin("nodesSelection");
12280
+ if (selectionPlugin) {
12281
+ const tr = selectionPlugin.getTransformer();
12282
+ this.instance.disablePlugin("nodesSelection");
12283
+ tr.hide();
12284
+ }
12285
+ const stage = this.instance.getStage();
12286
+ const upscaleScale = stage.getAttr("upscaleScale") ?? 1;
12287
+ const absoluteTransform = group.getAbsoluteTransform();
12288
+ const topLeft = absoluteTransform.point({
12289
+ x: textBounds.x,
12290
+ y: textBounds.y
12291
+ });
12292
+ this.createTextAreaDOM(group, textBounds, topLeft, upscaleScale, onCommit, onLiveResize);
12293
+ this.instance.getStage().mode(WEAVE_STAGE_SHAPE_LABEL_EDITION_MODE);
12294
+ }
12295
+ exitEditMode() {
12296
+ if (!this.editing) return;
12297
+ this.instance.releaseMutexLock();
12298
+ this.instance.getStage().mode(WEAVE_STAGE_DEFAULT_MODE);
12299
+ this.editing = false;
12300
+ const editedGroupId = this.editingGroup?.id() ?? null;
12301
+ if (this.editingGroup) {
12302
+ const liveGroup = this.instance.getStage().findOne(`#${this.editingGroup.id()}`);
12303
+ const labelNode = liveGroup?.findOne(`#${labelId(this.editingGroup.id())}`);
12304
+ if (labelNode) {
12305
+ labelNode.visible(true);
12306
+ labelNode.getLayer()?.batchDraw();
12307
+ }
12308
+ this.editingGroup = null;
12309
+ }
12310
+ if (this.textArea) this.textArea.remove();
12311
+ this.textArea = null;
12312
+ this.onLiveResize = null;
12313
+ this.editingTextBounds = null;
12314
+ this.instance.getStage().off(".weaveLabelEdit");
12315
+ const selectionPlugin = this.instance.getPlugin("nodesSelection");
12316
+ if (selectionPlugin) {
12317
+ this.instance.enablePlugin("nodesSelection");
12318
+ if (editedGroupId) requestAnimationFrame(() => {
12319
+ const liveGroup = this.instance.getStage().findOne(`#${editedGroupId}`);
12320
+ if (liveGroup) selectionPlugin.setSelectedNodes([liveGroup]);
12321
+ });
12322
+ }
12323
+ }
12324
+ updateTextAreaPosition(group, textBounds) {
12325
+ if (!this.editing || !this.textArea) return;
12326
+ const stage = this.instance.getStage();
12327
+ const upscaleScale = stage.getAttr("upscaleScale") ?? 1;
12328
+ const absoluteTransform = group.getAbsoluteTransform();
12329
+ const topLeft = absoluteTransform.point({
12330
+ x: textBounds.x,
12331
+ y: textBounds.y
12332
+ });
12333
+ this.textArea.style.left = `${topLeft.x * upscaleScale}px`;
12334
+ const absScale = group.getAbsoluteScale();
12335
+ const props = group.getAttrs();
12336
+ const fontSize = (props.labelFontSize ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelFontSize) * absScale.x;
12337
+ this.textArea.style.fontSize = `${fontSize * upscaleScale}px`;
12338
+ const textWidth = textBounds.width * absScale.x;
12339
+ this.textArea.style.width = `${textWidth * upscaleScale}px`;
12340
+ const originalBoundsHeightPx = textBounds.height * absScale.y * upscaleScale;
12341
+ const paddingY = props.labelPaddingY ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelPaddingY;
12342
+ const verticalAlign = props.labelVerticalAlign ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelVerticalAlign;
12343
+ this.textArea.style.height = "auto";
12344
+ const contentHeightPx = this.textArea.scrollHeight;
12345
+ if (contentHeightPx <= originalBoundsHeightPx) {
12346
+ this.textArea.style.height = `${contentHeightPx}px`;
12347
+ const offsetY = this.computeVerticalOffset(verticalAlign, originalBoundsHeightPx, contentHeightPx);
12348
+ this.textArea.style.top = `${topLeft.y * upscaleScale + offsetY}px`;
12349
+ } else {
12350
+ this.textArea.style.height = `${contentHeightPx}px`;
12351
+ this.textArea.style.top = `${topLeft.y * upscaleScale}px`;
12352
+ }
12353
+ if (this.onLiveResize) {
12354
+ const contentHeightInCanvas = contentHeightPx / (absScale.y * upscaleScale);
12355
+ this.onLiveResize(contentHeightInCanvas + paddingY * 2);
12356
+ }
12357
+ }
12358
+ /**
12359
+ * Updates the textarea `left`, `width`, and `top` to match new text bounds.
12360
+ * Call this from an `onLiveResize` callback when the shape grows symmetrically
12361
+ * (e.g. regular polygon) so the textarea tracks the new position on all axes.
12362
+ * Does NOT call `onLiveResize` — there is no re-entrancy risk, but also no need.
12363
+ */
12364
+ repositionTextArea(group, textBounds) {
12365
+ if (!this.editing || !this.textArea) return;
12366
+ this.editingTextBounds = textBounds;
12367
+ const stage = this.instance.getStage();
12368
+ const upscaleScale = stage.getAttr("upscaleScale") ?? 1;
12369
+ const absoluteTransform = group.getAbsoluteTransform();
12370
+ const topLeft = absoluteTransform.point({
12371
+ x: textBounds.x,
12372
+ y: textBounds.y
12373
+ });
12374
+ const absScale = group.getAbsoluteScale();
12375
+ const textWidth = textBounds.width * absScale.x;
12376
+ this.textArea.style.left = `${topLeft.x * upscaleScale}px`;
12377
+ this.textArea.style.width = `${textWidth * upscaleScale}px`;
12378
+ const newBoundsHeightPx = textBounds.height * absScale.y * upscaleScale;
12379
+ const savedHeight = this.textArea.style.height;
12380
+ this.textArea.style.height = "auto";
12381
+ const actualContentHeightPx = this.textArea.scrollHeight;
12382
+ this.textArea.style.height = savedHeight;
12383
+ const groupProps = group.getAttrs();
12384
+ const verticalAlign = groupProps.labelVerticalAlign ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelVerticalAlign;
12385
+ const offsetY = this.computeVerticalOffset(verticalAlign, newBoundsHeightPx, actualContentHeightPx);
12386
+ this.textArea.style.top = `${topLeft.y * upscaleScale + offsetY}px`;
12387
+ }
12388
+ /**
12389
+ * Convergence loop: onLiveResize may change the textarea width (e.g. a
12390
+ * growing polygon becomes wider), which changes line-wrapping, which may
12391
+ * require a different shape height. Iterates until scrollHeight is stable
12392
+ * or a safety limit is reached (5 passes cover any practical input).
12393
+ *
12394
+ * Oscillation prevention: if the sequence alternates (narrow→grow→wide→
12395
+ * restore→narrow→…) the loop exits with the polygon under-sized.
12396
+ * We track `lastUsedPx` — the height last passed to onLiveResize — and
12397
+ * fire a final corrective grow whenever `prevHeightPx > lastUsedPx`
12398
+ * (content at the current width still overflows what was last asked for).
12399
+ */
12400
+ runLiveResizeLoop(onLiveResize, contentHeightPx, effectiveScale, upscaleScale, paddingY) {
12401
+ const MAX_PASSES = 5;
12402
+ let maxNeededPx = contentHeightPx;
12403
+ let prevHeightPx = contentHeightPx;
12404
+ let lastUsedPx = 0;
12405
+ for (let pass = 0; pass < MAX_PASSES; pass++) {
12406
+ lastUsedPx = prevHeightPx;
12407
+ const neededInCanvas = prevHeightPx / (effectiveScale * upscaleScale);
12408
+ onLiveResize(neededInCanvas + paddingY * 2);
12409
+ if (!this.textArea) break;
12410
+ this.textArea.style.height = "auto";
12411
+ const measuredPx = this.textArea.scrollHeight;
12412
+ this.textArea.style.height = `${measuredPx}px`;
12413
+ if (measuredPx > maxNeededPx) maxNeededPx = measuredPx;
12414
+ if (measuredPx === prevHeightPx) break;
12415
+ prevHeightPx = measuredPx;
12416
+ }
12417
+ if (this.textArea && prevHeightPx > lastUsedPx) {
12418
+ const finalInCanvas = maxNeededPx / (effectiveScale * upscaleScale);
12419
+ onLiveResize(finalInCanvas + paddingY * 2);
12420
+ this.textArea.style.height = "auto";
12421
+ const finalPx = this.textArea.scrollHeight;
12422
+ this.textArea.style.height = `${finalPx}px`;
12423
+ }
12424
+ }
12425
+ createTextAreaDOM(group, textBounds, position, upscaleScale, onCommit, onLiveResize) {
12426
+ const stage = this.instance.getStage();
12427
+ const props = group.getAttrs();
12428
+ const absScale = group.getAbsoluteScale();
12429
+ const effectiveScale = absScale.x;
12430
+ this.textArea = document.createElement("textarea");
12431
+ this.textArea.id = `${group.id()}_label_textarea`;
12432
+ this.textArea.rows = 1;
12433
+ stage.container().appendChild(this.textArea);
12434
+ stage.on("dragmove.weaveLabelEdit xChange.weaveLabelEdit yChange.weaveLabelEdit", () => {
12435
+ if (this.editingGroup && this.editingTextBounds) this.repositionTextArea(this.editingGroup, this.editingTextBounds);
12436
+ });
12437
+ const textWidth = textBounds.width * effectiveScale;
12438
+ const fontSize = (props.labelFontSize ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelFontSize) * effectiveScale;
12439
+ const originalBoundsHeightPx = textBounds.height * effectiveScale * upscaleScale;
12440
+ const paddingY = props.labelPaddingY ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelPaddingY;
12441
+ this.textArea.style.position = "absolute";
12442
+ this.textArea.style.visibility = "hidden";
12443
+ this.textArea.style.left = `${position.x * upscaleScale}px`;
12444
+ this.textArea.style.width = `${textWidth * upscaleScale}px`;
12445
+ this.textArea.style.border = "solid 0px #1e40af";
12446
+ this.textArea.style.background = "transparent";
12447
+ this.textArea.style.backgroundColor = "transparent";
12448
+ this.textArea.style.boxSizing = "border-box";
12449
+ this.textArea.style.overflow = "hidden";
12450
+ const rotation = group.getAbsoluteRotation();
12451
+ if (rotation) {
12452
+ this.textArea.style.transformOrigin = "left top";
12453
+ this.textArea.style.transform = `rotate(${rotation}deg)`;
12454
+ }
12455
+ this.textArea.value = props.labelText ?? "";
12456
+ this.textArea.style.fontSize = `${fontSize * upscaleScale}px`;
12457
+ this.textArea.style.fontFamily = props.labelFontFamily ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelFontFamily;
12458
+ this.textArea.style.letterSpacing = `${props.labelLetterSpacing ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelLetterSpacing}px`;
12459
+ this.textArea.style.lineHeight = `${props.labelLineHeight ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelLineHeight}em`;
12460
+ const fontStyle = props.labelFontStyle ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelFontStyle;
12461
+ this.textArea.style.fontStyle = fontStyle.includes("italic") ? "italic" : "normal";
12462
+ this.textArea.style.textDecoration = props.labelTextDecoration ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelTextDecoration;
12463
+ let fontWeight = "normal";
12464
+ const matchNumber = fontStyle.match(/\d+/);
12465
+ if (fontStyle.includes("bold")) fontWeight = "bold";
12466
+ if (matchNumber) fontWeight = matchNumber[0];
12467
+ this.textArea.style.fontWeight = fontWeight;
12468
+ this.textArea.style.fontVariant = props.labelFontVariant ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelFontVariant;
12469
+ this.textArea.style.color = props.labelFill ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelFill;
12470
+ this.textArea.style.textAlign = props.labelAlign ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelAlign;
12471
+ this.textArea.style.outline = "none";
12472
+ this.textArea.style.resize = "none";
12473
+ this.textArea.style.margin = "0";
12474
+ this.textArea.style.padding = "0";
12475
+ this.textArea.style.caretColor = "black";
12476
+ this.textArea.style.overscrollBehavior = "contains";
12477
+ const resizeTextarea = () => {
12478
+ if (!this.textArea) return;
12479
+ this.textArea.style.height = "auto";
12480
+ const contentHeightPx = this.textArea.scrollHeight;
12481
+ const fonts = this.instance.getFonts();
12482
+ const font = fonts.find((f) => f.name === (props.labelFontFamily ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelFontFamily));
12483
+ const currentBounds = this.editingTextBounds ?? textBounds;
12484
+ const liveTL = group.getAbsoluteTransform().point({
12485
+ x: currentBounds.x,
12486
+ y: currentBounds.y + (font?.offsetY ?? 0)
12487
+ });
12488
+ this.textArea.style.left = `${liveTL.x * upscaleScale}px`;
12489
+ if (contentHeightPx <= originalBoundsHeightPx) {
12490
+ this.textArea.style.height = `${contentHeightPx}px`;
12491
+ const verticalAlign = props.labelVerticalAlign ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelVerticalAlign;
12492
+ const offsetY = this.computeVerticalOffset(verticalAlign, originalBoundsHeightPx, contentHeightPx);
12493
+ this.textArea.style.top = `${liveTL.y * upscaleScale + offsetY}px`;
12494
+ } else {
12495
+ this.textArea.style.height = `${contentHeightPx}px`;
12496
+ this.textArea.style.top = `${liveTL.y * upscaleScale}px`;
12497
+ }
12498
+ if (onLiveResize) this.runLiveResizeLoop(onLiveResize, contentHeightPx, effectiveScale, upscaleScale, paddingY);
12499
+ if (this.textArea.style.visibility === "hidden") this.textArea.style.visibility = "";
12500
+ };
12501
+ const commit = (text) => {
12502
+ window.removeEventListener("pointerup", handleOutsideClick);
12503
+ this.exitEditMode();
12504
+ const liveGroup = this.instance.getStage().findOne(`#${group.id()}`);
12505
+ const labelNode = liveGroup?.findOne(`#${labelId(group.id())}`);
12506
+ if (labelNode) labelNode.visible(text !== "");
12507
+ onCommit(text);
12508
+ };
12509
+ this.textArea.addEventListener("keydown", (e) => {
12510
+ e.stopPropagation();
12511
+ if (e.code === "Escape") {
12512
+ e.preventDefault();
12513
+ commit(this.textArea?.value ?? "");
12514
+ return;
12515
+ }
12516
+ resizeTextarea();
12517
+ }, { signal: this.instance.getEventsController().signal });
12518
+ this.textArea.addEventListener("keyup", () => resizeTextarea(), { signal: this.instance.getEventsController().signal });
12519
+ this.textArea.addEventListener("input", () => resizeTextarea(), { signal: this.instance.getEventsController().signal });
12520
+ this.textArea.addEventListener("scroll", () => {
12521
+ if (this.textArea) {
12522
+ this.textArea.scrollTop = 0;
12523
+ this.textArea.scrollLeft = 0;
12524
+ }
12525
+ }, { signal: this.instance.getEventsController().signal });
12526
+ const handleOutsideClick = (e) => {
12527
+ e.stopPropagation();
12528
+ if (!this.textArea) return;
12529
+ const mouseX = e.clientX;
12530
+ const mouseY = e.clientY;
12531
+ let elementUnderMouse = document.elementFromPoint(mouseX, mouseY);
12532
+ if (isInShadowDOM(stage.container())) {
12533
+ const shadowHost = getTopmostShadowHost(stage.container());
12534
+ if (shadowHost) elementUnderMouse = shadowHost.elementFromPoint(mouseX, mouseY);
12535
+ }
12536
+ const clickedOutside = elementUnderMouse?.id !== `${group.id()}_label_textarea`;
12537
+ if (clickedOutside) commit(this.textArea.value);
12538
+ };
12539
+ setTimeout(() => {
12540
+ window.addEventListener("pointerup", handleOutsideClick, { signal: this.instance.getEventsController().signal });
12541
+ }, 0);
12542
+ this.textArea.tabIndex = 1;
12543
+ requestAnimationFrame(() => {
12544
+ resizeTextarea();
12545
+ this.textArea?.focus({ preventScroll: true });
12546
+ if (this.textArea?.value) this.textArea.select();
12547
+ });
12548
+ }
12549
+ };
12550
+
12551
+ //#endregion
12552
+ //#region src/nodes/shared/shape-label.utils.ts
12553
+ /**
12554
+ * Returns a partial props object containing only the label-related fields that
12555
+ * are explicitly set on `props`. Spread this into the `props` section of
12556
+ * `addNodeState` / `updateNodeState` to avoid duplicating the 12-field pattern
12557
+ * across every shape node that supports inline text labels.
12558
+ */
12559
+ function spreadLabelProps(props) {
12560
+ return {
12561
+ ...props.labelText !== void 0 && { labelText: props.labelText },
12562
+ ...props.labelFontFamily !== void 0 && { labelFontFamily: props.labelFontFamily },
12563
+ ...props.labelFontSize !== void 0 && { labelFontSize: props.labelFontSize },
12564
+ ...props.labelFontStyle !== void 0 && { labelFontStyle: props.labelFontStyle },
12565
+ ...props.labelFontVariant !== void 0 && { labelFontVariant: props.labelFontVariant },
12566
+ ...props.labelFill !== void 0 && { labelFill: props.labelFill },
12567
+ ...props.labelAlign !== void 0 && { labelAlign: props.labelAlign },
12568
+ ...props.labelVerticalAlign !== void 0 && { labelVerticalAlign: props.labelVerticalAlign },
12569
+ ...props.labelLetterSpacing !== void 0 && { labelLetterSpacing: props.labelLetterSpacing },
12570
+ ...props.labelLineHeight !== void 0 && { labelLineHeight: props.labelLineHeight },
12571
+ ...props.labelPaddingX !== void 0 && { labelPaddingX: props.labelPaddingX },
12572
+ ...props.labelPaddingY !== void 0 && { labelPaddingY: props.labelPaddingY }
12573
+ };
12574
+ }
12575
+ /**
12576
+ * Returns the shared Zod schema fields for inline text label properties.
12577
+ * Spread the result of this function into a shape node's `props` schema
12578
+ * extension to avoid duplicating the 10-field label schema across rectangle,
12579
+ * ellipse, and any future shape that supports text labels.
12580
+ */
12581
+ function getShapeLabelSchemaFields() {
12582
+ return {
12583
+ labelText: z.string().optional().describe("Text label displayed inside the shape"),
12584
+ labelFontFamily: z.string().optional().describe("Font family for the label text"),
12585
+ labelFontSize: z.number().optional().describe("Font size for the label text in pixels"),
12586
+ labelFontStyle: z.string().optional().describe("Font style for the label text (e.g. \"normal\", \"bold\", \"italic\", \"bold italic\")"),
12587
+ labelFontVariant: z.string().optional().describe("Font variant for the label text (e.g. \"normal\", \"small-caps\")"),
12588
+ labelFill: z.string().optional().describe("Color of the label text in hex format (e.g. #RRGGBBAA)"),
12589
+ labelAlign: z.string().optional().describe("Horizontal alignment of the label text (\"left\", \"center\", \"right\")"),
12590
+ labelVerticalAlign: z.string().optional().describe("Vertical alignment of the label text (\"top\", \"middle\", \"bottom\")"),
12591
+ labelLetterSpacing: z.number().optional().describe("Letter spacing for the label text in pixels"),
12592
+ labelLineHeight: z.number().optional().describe("Line height multiplier for the label text"),
12593
+ labelPaddingX: z.number().optional().describe("Horizontal inset (padding) in pixels applied on each side of the label"),
12594
+ labelPaddingY: z.number().optional().describe("Vertical inset (padding) in pixels applied on top and bottom of the label")
12595
+ };
12596
+ }
12597
+ /**
12598
+ * Extracts the label node and typography settings from a Konva group.
12599
+ * Returns `null` when the group has no label text or no label Konva.Text child.
12600
+ * Used internally by `computeRectangleLabelMinSize`, `computeEllipseLabelMinSize`,
12601
+ * and `computePolygonLabelMinSize` to avoid duplicating the setup logic.
12602
+ */
12603
+ function extractLabelNodeContext(group, skipTransformInClientRect = true) {
12604
+ const attrs = group.getAttrs();
12605
+ const labelText = attrs.labelText ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelText;
12606
+ if (!labelText) return null;
12607
+ const labelNode = group.findOne(`#${labelId(group.id())}`);
12608
+ if (!labelNode) return null;
12609
+ const paddingX = attrs.labelPaddingX ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelPaddingX;
12610
+ const paddingY = attrs.labelPaddingY ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelPaddingY;
12611
+ const fontSize = attrs.labelFontSize ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelFontSize;
12612
+ const cloneLabel = labelNode.clone({ visible: false });
12613
+ cloneLabel.height(void 0);
12614
+ const naturalSize = cloneLabel.getClientRect({
12615
+ skipTransform: skipTransformInClientRect,
12616
+ skipShadow: true
12617
+ });
12618
+ return {
12619
+ paddingX,
12620
+ paddingY,
12621
+ fontSize,
12622
+ labelNode,
12623
+ naturalSize
12624
+ };
12625
+ }
12626
+ /**
12627
+ * Returns the minimum bounding size `{ width, height }` (in Konva canvas
12628
+ * units) that the rectangle must have so its label text is fully visible —
12629
+ * no vertical truncation and no horizontal clipping of the widest word.
12630
+ *
12631
+ * Returns `{ width: 0, height: 0 }` when the label is empty.
12632
+ *
12633
+ * @param group - The rectangle `Konva.Group` returned by `onRender`.
12634
+ */
12635
+ function computeRectangleLabelMinSize(stage, group) {
12636
+ const ctx = extractLabelNodeContext(group);
12637
+ if (!ctx) return {
12638
+ width: 0,
12639
+ height: 0
12640
+ };
12641
+ const { paddingX, paddingY, fontSize, naturalSize } = ctx;
12642
+ return {
12643
+ width: (paddingX * 2 + fontSize) * stage.scaleX(),
12644
+ height: (naturalSize.height + paddingY * 2) * stage.scaleX()
12645
+ };
12646
+ }
12647
+ /**
12648
+ * Returns the minimum bounding box size `{ minWidth, minHeight }` (in Konva
12649
+ * canvas units, i.e. `radiusX * 2` × `radiusY * 2`) that the ellipse must have
12650
+ * so its inscribed label text is fully visible.
12651
+ *
12652
+ * The ellipse label sits inside the largest axis-aligned rectangle inscribed in
12653
+ * the ellipse: `inscribedW = radiusX * √2`, `inscribedH = radiusY * √2`. The
12654
+ * minimum radiusY is back-computed from the text's natural height:
12655
+ * `minRadiusY = ceil(naturalTextH / √2)`
12656
+ *
12657
+ * Returns `{ minWidth: 0, minHeight: 0 }` when the label is empty.
12658
+ *
12659
+ * @param group - The ellipse `Konva.Group` returned by `onRender`.
12660
+ */
12661
+ function computeEllipseLabelMinSize(stage, group) {
12662
+ const ctx = extractLabelNodeContext(group);
12663
+ if (!ctx) return {
12664
+ width: 0,
12665
+ height: 0
12666
+ };
12667
+ const { paddingX, paddingY, fontSize, naturalSize } = ctx;
12668
+ const minRadiusY = Math.ceil((naturalSize.height + paddingY * 2) / Math.SQRT2);
12669
+ const minRadiusX = Math.ceil((fontSize + paddingX * 2) / Math.SQRT2);
12670
+ return {
12671
+ width: minRadiusX * 2 * stage.scaleX(),
12672
+ height: minRadiusY * 2 * stage.scaleY()
12673
+ };
12674
+ }
12675
+ /**
12676
+ * Returns the minimum bounding-box size `{ width, height }` (in Konva canvas
12677
+ * units) that the polygon node must have so its label text is fully visible.
12678
+ *
12679
+ * The polygon label sits inside the stored `innerRect` attribute. The minimum
12680
+ * size is back-computed from the label text's natural wrapped height and the
12681
+ * current ratio of `innerRect` to the overall bounding box.
12682
+ *
12683
+ * Returns `{ width: 0, height: 0 }` when the label is empty.
12684
+ *
12685
+ * @param group - The polygon `Konva.Group` returned by `onRender`.
12686
+ */
12687
+ function computePolygonLabelMinSize(stage, group) {
12688
+ const ctx = extractLabelNodeContext(group);
12689
+ if (!ctx) return {
12690
+ width: 0,
12691
+ height: 0
12692
+ };
12693
+ const { paddingX, paddingY, fontSize, naturalSize } = ctx;
12694
+ const attrs = group.getAttrs();
12695
+ const innerRect = attrs.innerRect;
12696
+ if (!innerRect) return {
12697
+ width: 0,
12698
+ height: 0
12699
+ };
12700
+ return {
12701
+ width: (paddingX * 2 + fontSize) * stage.scaleX(),
12702
+ height: (naturalSize.height + paddingY * 2) * stage.scaleX()
12703
+ };
12704
+ }
12705
+
11920
12706
  //#endregion
11921
12707
  //#region src/nodes/rectangle/rectangle.ts
11922
12708
  var WeaveRectangleNode = class extends WeaveNode {
11923
12709
  nodeType = WEAVE_RECTANGLE_NODE_TYPE;
11924
12710
  initialize = void 0;
12711
+ _transforming = false;
12712
+ get shapeLabelEditor() {
12713
+ return this._shapeLabelEditor ??= new WeaveShapeLabelEditor(this.instance);
12714
+ }
11925
12715
  constructor(params) {
11926
12716
  super();
11927
12717
  const { config } = params ?? {};
@@ -11930,13 +12720,15 @@ var WeaveRectangleNode = class extends WeaveNode {
11930
12720
  onRender(props) {
11931
12721
  const rectangle = new Konva.Group({
11932
12722
  ...props,
12723
+ id: `${props.id}`,
11933
12724
  name: "node"
11934
12725
  });
11935
12726
  const internalRectBg = new Konva.Rect({
11936
12727
  ...props,
11937
12728
  name: void 0,
11938
- id: `${props.id}-bg`,
12729
+ nodeType: void 0,
11939
12730
  nodeId: props.id,
12731
+ id: `${props.id}-bg`,
11940
12732
  x: 0,
11941
12733
  y: 0,
11942
12734
  width: props.width,
@@ -11951,6 +12743,7 @@ var WeaveRectangleNode = class extends WeaveNode {
11951
12743
  const internalRectBorder = new Konva.Rect({
11952
12744
  ...props,
11953
12745
  name: void 0,
12746
+ nodeType: void 0,
11954
12747
  id: `${props.id}-border`,
11955
12748
  x: props.strokeWidth / 2,
11956
12749
  y: props.strokeWidth / 2,
@@ -11960,9 +12753,19 @@ var WeaveRectangleNode = class extends WeaveNode {
11960
12753
  strokeWidth: props.strokeWidth || 0,
11961
12754
  strokeScaleEnabled: true,
11962
12755
  rotation: 0,
11963
- listening: false
12756
+ listening: false,
12757
+ draggable: false
11964
12758
  });
11965
12759
  rectangle.add(internalRectBorder);
12760
+ const paddingX = props.labelPaddingX ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelPaddingX;
12761
+ const paddingY = props.labelPaddingY ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelPaddingY;
12762
+ const labelTextBounds = {
12763
+ x: paddingX,
12764
+ y: paddingY,
12765
+ width: Math.max(1, props.width - paddingX * 2),
12766
+ height: Math.max(1, props.height - paddingY * 2)
12767
+ };
12768
+ this.shapeLabelEditor.renderLabel(rectangle, props, labelTextBounds);
11966
12769
  internalRectBorder.moveToTop();
11967
12770
  internalRectBg.moveToBottom();
11968
12771
  this.setupDefaultNodeAugmentation(rectangle);
@@ -11971,6 +12774,51 @@ var WeaveRectangleNode = class extends WeaveNode {
11971
12774
  return defaultTransformerProperties;
11972
12775
  };
11973
12776
  this.setupDefaultNodeEvents(rectangle);
12777
+ rectangle.on("transformstart", () => {
12778
+ this._transforming = true;
12779
+ });
12780
+ rectangle.on("transform", () => {
12781
+ this.scaleReset(rectangle);
12782
+ this.onUpdate(rectangle, rectangle.getAttrs());
12783
+ });
12784
+ rectangle.on("transformend", () => {
12785
+ this._transforming = false;
12786
+ });
12787
+ rectangle.dblClick = () => {
12788
+ if (this.shapeLabelEditor.isEditing()) return;
12789
+ if (!(this.isSelecting() && this.isNodeSelected(rectangle))) return;
12790
+ const onCommit = (labelText) => {
12791
+ const updatedGroup = this.instance.getStage().findOne(`#${props.id}`);
12792
+ if (!updatedGroup) return;
12793
+ const serialized = this.serialize(updatedGroup);
12794
+ serialized.props.labelText = labelText;
12795
+ this.instance.updateNode(serialized);
12796
+ };
12797
+ const currentAttrs = rectangle.getAttrs();
12798
+ const curPaddingX = currentAttrs.labelPaddingX ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelPaddingX;
12799
+ const curPaddingY = currentAttrs.labelPaddingY ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelPaddingY;
12800
+ const currentLabelTextBounds = {
12801
+ x: curPaddingX,
12802
+ y: curPaddingY,
12803
+ width: Math.max(1, (currentAttrs.width ?? 0) - curPaddingX * 2),
12804
+ height: Math.max(1, (currentAttrs.height ?? 0) - curPaddingY * 2)
12805
+ };
12806
+ const originalHeight = currentAttrs.height ?? 0;
12807
+ this.shapeLabelEditor.triggerEditMode(rectangle, currentLabelTextBounds, onCommit, (neededShapeHeight) => {
12808
+ const finalHeight = Math.max(neededShapeHeight, originalHeight);
12809
+ const liveAttrs = rectangle.getAttrs();
12810
+ const strokeW = liveAttrs.strokeWidth || 0;
12811
+ const bg = rectangle.findOne(`#${liveAttrs.id}-bg`);
12812
+ const border = rectangle.findOne(`#${liveAttrs.id}-border`);
12813
+ rectangle.setAttrs({ height: finalHeight });
12814
+ bg?.setAttrs({ height: finalHeight });
12815
+ border?.setAttrs({ height: Math.max(0, finalHeight - strokeW) });
12816
+ rectangle.getLayer()?.batchDraw();
12817
+ });
12818
+ };
12819
+ rectangle.getNodeMinSize = () => {
12820
+ return computeRectangleLabelMinSize(this.instance.getStage(), rectangle);
12821
+ };
11974
12822
  return rectangle;
11975
12823
  }
11976
12824
  onUpdate(nodeInstance, nextProps) {
@@ -12000,6 +12848,7 @@ var WeaveRectangleNode = class extends WeaveNode {
12000
12848
  internalRectBorder.setAttrs({
12001
12849
  ...nextProps,
12002
12850
  name: void 0,
12851
+ fill: "transparent",
12003
12852
  id: `${nextProps.id}-border`,
12004
12853
  x: nextProps.strokeWidth / 2,
12005
12854
  y: nextProps.strokeWidth / 2,
@@ -12013,6 +12862,26 @@ var WeaveRectangleNode = class extends WeaveNode {
12013
12862
  });
12014
12863
  internalRectBorder.moveToTop();
12015
12864
  }
12865
+ const paddingX = nextProps.labelPaddingX ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelPaddingX;
12866
+ const paddingY = nextProps.labelPaddingY ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelPaddingY;
12867
+ const labelTextBounds = {
12868
+ x: paddingX,
12869
+ y: paddingY,
12870
+ width: Math.max(1, nextProps.width - paddingX * 2),
12871
+ height: Math.max(1, nextProps.height - paddingY * 2)
12872
+ };
12873
+ this.shapeLabelEditor.updateLabel(rectangle, nextProps, labelTextBounds, (neededShapeHeight) => {
12874
+ nodeInstance.setAttrs({ height: neededShapeHeight });
12875
+ internalRectBg?.setAttrs({ height: neededShapeHeight });
12876
+ internalRectBorder?.setAttrs({ height: neededShapeHeight - nextProps.strokeWidth });
12877
+ if (!this._transforming) this.instance.updateNode(this.serialize(nodeInstance));
12878
+ });
12879
+ const labelNode = rectangle.findOne(`#${labelId(nextProps.id ?? "")}`);
12880
+ if (labelNode) {
12881
+ labelNode.moveToTop();
12882
+ internalRectBg?.moveToBottom();
12883
+ internalRectBorder?.moveToTop();
12884
+ }
12016
12885
  const nodesSelectionPlugin = this.instance.getPlugin("nodesSelection");
12017
12886
  if (nodesSelectionPlugin) nodesSelectionPlugin.getTransformer().forceUpdate();
12018
12887
  }
@@ -12033,7 +12902,8 @@ var WeaveRectangleNode = class extends WeaveNode {
12033
12902
  strokeScaleEnabled: true,
12034
12903
  rotation: 0,
12035
12904
  zIndex: 1,
12036
- children: []
12905
+ children: [],
12906
+ ...WEAVE_SHAPE_LABEL_DEFAULTS
12037
12907
  }
12038
12908
  };
12039
12909
  }
@@ -12046,7 +12916,8 @@ var WeaveRectangleNode = class extends WeaveNode {
12046
12916
  rotation: props.rotation,
12047
12917
  fill: props.fill,
12048
12918
  ...props.stroke && { stroke: props.stroke },
12049
- ...props.strokeWidth && { strokeWidth: props.strokeWidth }
12919
+ ...props.strokeWidth && { strokeWidth: props.strokeWidth },
12920
+ ...spreadLabelProps(props)
12050
12921
  } });
12051
12922
  }
12052
12923
  static updateNodeState(prevNodeState, nextProps) {
@@ -12058,7 +12929,8 @@ var WeaveRectangleNode = class extends WeaveNode {
12058
12929
  rotation: nextProps.rotation,
12059
12930
  fill: nextProps.fill,
12060
12931
  ...nextProps.stroke && { stroke: nextProps.stroke },
12061
- ...nextProps.strokeWidth && { strokeWidth: nextProps.strokeWidth }
12932
+ ...nextProps.strokeWidth && { strokeWidth: nextProps.strokeWidth },
12933
+ ...spreadLabelProps(nextProps)
12062
12934
  } });
12063
12935
  }
12064
12936
  static getSchema() {
@@ -12072,7 +12944,8 @@ var WeaveRectangleNode = class extends WeaveNode {
12072
12944
  fill: z.string().describe("Fill color of the rectangle in hex format with alpha channel (e.g. #RRGGBBAA)"),
12073
12945
  stroke: z.string().describe("Stroke color of the rectangle in hex format with alpha channel (e.g. #RRGGBBAA)"),
12074
12946
  strokeWidth: z.number().describe("Stroke width of the rectangle in pixels"),
12075
- strokeScaleEnabled: z.boolean().describe("Whether the rectangle stroke width should scale when the node is scaled. Defaults to true.")
12947
+ strokeScaleEnabled: z.boolean().describe("Whether the rectangle stroke width should scale when the node is scaled. Defaults to true."),
12948
+ ...getShapeLabelSchemaFields()
12076
12949
  })
12077
12950
  });
12078
12951
  return nodeSchema;
@@ -12088,11 +12961,25 @@ const WEAVE_ELLIPSE_NODE_TYPE = "ellipse";
12088
12961
  var WeaveEllipseNode = class extends WeaveNode {
12089
12962
  nodeType = WEAVE_ELLIPSE_NODE_TYPE;
12090
12963
  initialize = void 0;
12964
+ _transforming = false;
12965
+ get shapeLabelEditor() {
12966
+ return this._shapeLabelEditor ??= new WeaveShapeLabelEditor(this.instance);
12967
+ }
12091
12968
  constructor(params) {
12092
12969
  super();
12093
12970
  const { config } = params ?? {};
12094
12971
  this.config = { transform: { ...config?.transform } };
12095
12972
  }
12973
+ getLabelTextBounds(radiusX, radiusY, paddingX, paddingY) {
12974
+ const inscribedW = radiusX * Math.SQRT2;
12975
+ const inscribedH = radiusY * Math.SQRT2;
12976
+ return {
12977
+ x: radiusX - inscribedW / 2 + paddingX,
12978
+ y: radiusY - inscribedH / 2 + paddingY,
12979
+ width: Math.max(1, inscribedW - paddingX * 2),
12980
+ height: Math.max(1, inscribedH - paddingY * 2)
12981
+ };
12982
+ }
12096
12983
  onRender(props) {
12097
12984
  const ellipse = new Konva.Group({
12098
12985
  ...props,
@@ -12130,6 +13017,10 @@ var WeaveEllipseNode = class extends WeaveNode {
12130
13017
  listening: false
12131
13018
  });
12132
13019
  ellipse.add(internalEllipseBorder);
13020
+ const paddingX = props.labelPaddingX ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelPaddingX;
13021
+ const paddingY = props.labelPaddingY ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelPaddingY;
13022
+ const labelTextBounds = this.getLabelTextBounds(Math.max(1, baseRadiusX), Math.max(1, baseRadiusY), paddingX, paddingY);
13023
+ this.shapeLabelEditor.renderLabel(ellipse, props, labelTextBounds);
12133
13024
  internalEllipseBorder.moveToTop();
12134
13025
  internalEllipseBg.moveToBottom();
12135
13026
  this.setupDefaultNodeAugmentation(ellipse);
@@ -12168,6 +13059,56 @@ var WeaveEllipseNode = class extends WeaveNode {
12168
13059
  ];
12169
13060
  };
12170
13061
  this.setupDefaultNodeEvents(ellipse);
13062
+ ellipse.on("transformstart", () => {
13063
+ this._transforming = true;
13064
+ });
13065
+ ellipse.on("transform", () => {
13066
+ this.scaleReset(ellipse);
13067
+ this.onUpdate(ellipse, ellipse.getAttrs());
13068
+ });
13069
+ ellipse.on("transformend", () => {
13070
+ this._transforming = false;
13071
+ });
13072
+ ellipse.dblClick = () => {
13073
+ if (this.shapeLabelEditor.isEditing()) return;
13074
+ if (!(this.isSelecting() && this.isNodeSelected(ellipse))) return;
13075
+ const onCommit = (labelText) => {
13076
+ const updatedGroup = this.instance.getStage().findOne(`#${props.id}`);
13077
+ if (!updatedGroup) return;
13078
+ const serialized = this.serialize(updatedGroup);
13079
+ serialized.props.labelText = labelText;
13080
+ this.instance.updateNode(serialized);
13081
+ };
13082
+ const currentAttrs = ellipse.getAttrs();
13083
+ const curPaddingX = currentAttrs.labelPaddingX ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelPaddingX;
13084
+ const curPaddingY = currentAttrs.labelPaddingY ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelPaddingY;
13085
+ const currentRadiusX = Math.max(1, currentAttrs.radiusX);
13086
+ const currentRadiusY = Math.max(1, currentAttrs.radiusY);
13087
+ const currentLabelTextBounds = this.getLabelTextBounds(currentRadiusX, currentRadiusY, curPaddingX, curPaddingY);
13088
+ const originalRadiusY = currentRadiusY;
13089
+ const originalNeededHeight = currentLabelTextBounds.height + curPaddingY * 2;
13090
+ this.shapeLabelEditor.triggerEditMode(ellipse, currentLabelTextBounds, onCommit, (neededShapeHeight) => {
13091
+ const newRadiusY = neededShapeHeight <= originalNeededHeight ? originalRadiusY : Math.ceil(neededShapeHeight / Math.SQRT2);
13092
+ const liveAttrs = ellipse.getAttrs();
13093
+ const strokeW = liveAttrs.strokeWidth || 0;
13094
+ const bg = ellipse.findOne(`#${liveAttrs.id}-bg`);
13095
+ const border = ellipse.findOne(`#${liveAttrs.id}-border`);
13096
+ ellipse.setAttrs({ radiusY: newRadiusY });
13097
+ bg?.setAttrs({
13098
+ radiusY: Math.max(1, newRadiusY),
13099
+ y: Math.max(1, newRadiusY)
13100
+ });
13101
+ border?.setAttrs({
13102
+ radiusY: Math.max(1, newRadiusY) - strokeW / 2,
13103
+ y: Math.max(1, newRadiusY)
13104
+ });
13105
+ const newLabelTextBounds = this.getLabelTextBounds(currentRadiusX, newRadiusY, curPaddingX, curPaddingY);
13106
+ this.shapeLabelEditor.repositionTextArea(ellipse, newLabelTextBounds);
13107
+ });
13108
+ };
13109
+ ellipse.getNodeMinSize = () => {
13110
+ return computeEllipseLabelMinSize(this.instance.getStage(), ellipse);
13111
+ };
12171
13112
  return ellipse;
12172
13113
  }
12173
13114
  onUpdate(nodeInstance, nextProps) {
@@ -12205,12 +13146,34 @@ var WeaveEllipseNode = class extends WeaveNode {
12205
13146
  radiusY: Math.max(1, baseRadiusY) - (nextProps.strokeWidth || 0) / 2,
12206
13147
  stroke: nextProps.stroke || "transparent",
12207
13148
  strokeWidth: nextProps.strokeWidth || 0,
13149
+ fill: "transparent",
12208
13150
  strokeScaleEnabled: true,
12209
13151
  listening: false,
12210
13152
  rotation: 0
12211
13153
  });
12212
13154
  internalEllipseBorder.moveToTop();
12213
13155
  }
13156
+ const paddingX = nextProps.labelPaddingX ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelPaddingX;
13157
+ const paddingY = nextProps.labelPaddingY ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelPaddingY;
13158
+ const labelTextBounds = this.getLabelTextBounds(Math.max(1, baseRadiusX), Math.max(1, baseRadiusY), paddingX, paddingY);
13159
+ this.shapeLabelEditor.updateLabel(ellipse, nextProps, labelTextBounds, (neededHeight) => {
13160
+ const newRadiusY = Math.ceil(neededHeight / Math.SQRT2);
13161
+ nodeInstance.setAttrs({ radiusY: newRadiusY });
13162
+ internalEllipseBg?.setAttrs({
13163
+ radiusY: Math.max(1, newRadiusY),
13164
+ y: Math.max(1, newRadiusY)
13165
+ });
13166
+ internalEllipseBorder?.setAttrs({
13167
+ radiusY: Math.max(1, newRadiusY) - (nextProps.strokeWidth || 0) / 2,
13168
+ y: Math.max(1, newRadiusY)
13169
+ });
13170
+ if (!this._transforming) this.instance.updateNode(this.serialize(nodeInstance));
13171
+ });
13172
+ const labelNode = ellipse.findOne(`#${labelId(nextProps.id)}`);
13173
+ if (labelNode) {
13174
+ labelNode.moveToTop();
13175
+ internalEllipseBorder?.moveToTop();
13176
+ }
12214
13177
  const nodesSelectionPlugin = this.instance.getPlugin("nodesSelection");
12215
13178
  if (nodesSelectionPlugin) nodesSelectionPlugin.getTransformer().forceUpdate();
12216
13179
  }
@@ -12253,7 +13216,8 @@ var WeaveEllipseNode = class extends WeaveNode {
12253
13216
  strokeScaleEnabled: true,
12254
13217
  rotation: 0,
12255
13218
  zIndex: 1,
12256
- children: []
13219
+ children: [],
13220
+ ...WEAVE_SHAPE_LABEL_DEFAULTS
12257
13221
  }
12258
13222
  };
12259
13223
  }
@@ -12266,7 +13230,8 @@ var WeaveEllipseNode = class extends WeaveNode {
12266
13230
  rotation: props.rotation,
12267
13231
  fill: props.fill,
12268
13232
  ...props.stroke && { stroke: props.stroke },
12269
- ...props.strokeWidth && { strokeWidth: props.strokeWidth }
13233
+ ...props.strokeWidth && { strokeWidth: props.strokeWidth },
13234
+ ...spreadLabelProps(props)
12270
13235
  } });
12271
13236
  }
12272
13237
  static updateNodeState(prevNodeState, nextProps) {
@@ -12278,7 +13243,8 @@ var WeaveEllipseNode = class extends WeaveNode {
12278
13243
  rotation: nextProps.rotation,
12279
13244
  fill: nextProps.fill,
12280
13245
  ...nextProps.stroke && { stroke: nextProps.stroke },
12281
- ...nextProps.strokeWidth && { strokeWidth: nextProps.strokeWidth }
13246
+ ...nextProps.strokeWidth && { strokeWidth: nextProps.strokeWidth },
13247
+ ...spreadLabelProps(nextProps)
12282
13248
  } });
12283
13249
  }
12284
13250
  static getSchema() {
@@ -12292,7 +13258,8 @@ var WeaveEllipseNode = class extends WeaveNode {
12292
13258
  fill: z.string().describe("Fill color of the ellipse in hex format with alpha channel (e.g. #RRGGBBAA)"),
12293
13259
  stroke: z.string().describe("Stroke color of the ellipse in hex format with alpha channel (e.g. #RRGGBBAA)"),
12294
13260
  strokeWidth: z.number().describe("Stroke width of the ellipse in pixels"),
12295
- strokeScaleEnabled: z.boolean().describe("Whether the ellipse stroke width should scale when the node is scaled. Defaults to true.")
13261
+ strokeScaleEnabled: z.boolean().describe("Whether the ellipse stroke width should scale when the node is scaled. Defaults to true."),
13262
+ ...getShapeLabelSchemaFields()
12296
13263
  })
12297
13264
  });
12298
13265
  return nodeSchema;
@@ -15059,6 +16026,7 @@ var WeaveImageNode = class extends WeaveNode {
15059
16026
  x: 1,
15060
16027
  y: 1
15061
16028
  });
16029
+ this.updateImageCrop(node);
15062
16030
  }
15063
16031
  getIsAsync() {
15064
16032
  return true;
@@ -15570,127 +16538,975 @@ var WeaveRegularPolygonNode = class extends WeaveNode {
15570
16538
  rotation: 0,
15571
16539
  listening: false
15572
16540
  });
15573
- const internalRPBorderBox = internalRPBorder.getClientRect({ relativeTo: regularPolygon });
15574
- internalRPBorder.x(internalRPBorder.x() - internalRPBorderBox.x);
15575
- internalRPBorder.y(internalRPBorder.y() - internalRPBorderBox.y);
15576
- regularPolygon.add(internalRPBorder);
15577
- internalRPBorder.moveToTop();
15578
- internalRPBg.moveToBottom();
15579
- this.setupDefaultNodeAugmentation(regularPolygon);
16541
+ const internalRPBorderBox = internalRPBorder.getClientRect({ relativeTo: regularPolygon });
16542
+ internalRPBorder.x(internalRPBorder.x() - internalRPBorderBox.x);
16543
+ internalRPBorder.y(internalRPBorder.y() - internalRPBorderBox.y);
16544
+ regularPolygon.add(internalRPBorder);
16545
+ internalRPBorder.moveToTop();
16546
+ internalRPBg.moveToBottom();
16547
+ this.setupDefaultNodeAugmentation(regularPolygon);
16548
+ const defaultTransformerProperties = this.defaultGetTransformerProperties(this.config.transform);
16549
+ regularPolygon.getTransformerProperties = function() {
16550
+ return {
16551
+ ...defaultTransformerProperties,
16552
+ enabledAnchors: [
16553
+ "top-left",
16554
+ "top-right",
16555
+ "bottom-left",
16556
+ "bottom-right"
16557
+ ],
16558
+ keepRatio: true
16559
+ };
16560
+ };
16561
+ regularPolygon.allowedAnchors = function() {
16562
+ return [
16563
+ "top-left",
16564
+ "top-right",
16565
+ "bottom-left",
16566
+ "bottom-right"
16567
+ ];
16568
+ };
16569
+ this.setupDefaultNodeEvents(regularPolygon);
16570
+ return regularPolygon;
16571
+ }
16572
+ onUpdate(nodeInstance, nextProps) {
16573
+ nodeInstance.setAttrs({ ...nextProps });
16574
+ const sides = nodeInstance.getAttr("sides");
16575
+ const radius = nodeInstance.getAttr("radius");
16576
+ const regularPolygon = nodeInstance;
16577
+ const internalRPBg = regularPolygon.findOne(`#${nextProps.id}-bg`);
16578
+ const internalRPBorder = regularPolygon.findOne(`#${nextProps.id}-border`);
16579
+ if (internalRPBg) {
16580
+ internalRPBg.setAttrs({
16581
+ ...nextProps,
16582
+ name: void 0,
16583
+ id: `${nextProps.id}-bg`,
16584
+ nodeId: nextProps.id,
16585
+ x: radius,
16586
+ y: radius,
16587
+ sides,
16588
+ radius,
16589
+ fill: nextProps.fill || "transparent",
16590
+ strokeWidth: 0,
16591
+ strokeScaleEnabled: true,
16592
+ rotation: 0
16593
+ });
16594
+ const internalRPBgBox = internalRPBg.getClientRect({ relativeTo: regularPolygon });
16595
+ internalRPBg.x(internalRPBg.x() - internalRPBgBox.x);
16596
+ internalRPBg.y(internalRPBg.y() - internalRPBgBox.y);
16597
+ internalRPBg.moveToBottom();
16598
+ }
16599
+ if (internalRPBorder) {
16600
+ internalRPBorder.setAttrs({
16601
+ ...nextProps,
16602
+ name: void 0,
16603
+ id: `${nextProps.id}-border`,
16604
+ x: radius,
16605
+ y: radius,
16606
+ sides,
16607
+ radius: radius - (nextProps.strokeWidth || 0) / 2,
16608
+ stroke: nextProps.stroke || "transparent",
16609
+ strokeWidth: nextProps.strokeWidth || 0,
16610
+ strokeScaleEnabled: true,
16611
+ fill: "transparent",
16612
+ listening: false,
16613
+ rotation: 0
16614
+ });
16615
+ const internalRPBorderBox = internalRPBorder.getClientRect({ relativeTo: regularPolygon });
16616
+ internalRPBorder.x(internalRPBorder.x() - internalRPBorderBox.x);
16617
+ internalRPBorder.y(internalRPBorder.y() - internalRPBorderBox.y);
16618
+ internalRPBorder.moveToTop();
16619
+ }
16620
+ const nodesSelectionPlugin = this.instance.getPlugin("nodesSelection");
16621
+ if (nodesSelectionPlugin) {
16622
+ const actualSelectedNodes = nodesSelectionPlugin.getSelectedNodes();
16623
+ nodesSelectionPlugin.setSelectedNodes(actualSelectedNodes);
16624
+ nodesSelectionPlugin.getTransformer().forceUpdate();
16625
+ }
16626
+ }
16627
+ scaleReset(node) {
16628
+ const absTransform = node.getAbsoluteTransform().copy();
16629
+ const radius = node.getAttr("radius");
16630
+ node.setAttrs({ radius: radius * node.scaleX() });
16631
+ node.scaleX(1);
16632
+ node.scaleY(1);
16633
+ const newTransform = node.getAbsoluteTransform();
16634
+ const dx = absTransform.m[4] - newTransform.m[4];
16635
+ const dy = absTransform.m[5] - newTransform.m[5];
16636
+ node.x(node.x() + dx);
16637
+ node.y(node.y() + dy);
16638
+ }
16639
+ realOffset(element) {
16640
+ return {
16641
+ x: element.props.radius,
16642
+ y: element.props.radius
16643
+ };
16644
+ }
16645
+ static defaultState(nodeId) {
16646
+ return {
16647
+ ...super.defaultState(nodeId),
16648
+ type: WEAVE_REGULAR_POLYGON_NODE_TYPE,
16649
+ props: {
16650
+ ...super.defaultState(nodeId).props,
16651
+ nodeType: WEAVE_REGULAR_POLYGON_NODE_TYPE,
16652
+ x: 0,
16653
+ y: 0,
16654
+ sides: 5,
16655
+ radius: 100,
16656
+ stroke: "#000000",
16657
+ fill: "#FFFFFF",
16658
+ strokeWidth: 1,
16659
+ strokeScaleEnabled: true,
16660
+ rotation: 0,
16661
+ zIndex: 1,
16662
+ children: []
16663
+ }
16664
+ };
16665
+ }
16666
+ static addNodeState(defaultNodeState, props) {
16667
+ return mergeExceptArrays(defaultNodeState, { props: {
16668
+ x: props.x,
16669
+ y: props.y,
16670
+ sides: props.sides,
16671
+ radius: props.radius,
16672
+ rotation: props.rotation,
16673
+ fill: props.fill,
16674
+ ...props.stroke && { stroke: props.stroke },
16675
+ ...props.strokeWidth && { strokeWidth: props.strokeWidth }
16676
+ } });
16677
+ }
16678
+ static updateNodeState(prevNodeState, nextProps) {
16679
+ return mergeExceptArrays(prevNodeState, { props: {
16680
+ x: nextProps.x,
16681
+ y: nextProps.y,
16682
+ sides: nextProps.sides,
16683
+ radius: nextProps.radius,
16684
+ rotation: nextProps.rotation,
16685
+ fill: nextProps.fill,
16686
+ ...nextProps.stroke && { stroke: nextProps.stroke },
16687
+ ...nextProps.strokeWidth && { strokeWidth: nextProps.strokeWidth }
16688
+ } });
16689
+ }
16690
+ static getSchema() {
16691
+ const baseSchema = super.getSchema();
16692
+ const nodeSchema = baseSchema.extend({
16693
+ type: z.literal(WEAVE_REGULAR_POLYGON_NODE_TYPE).describe(`Type of the node, for a regular polygon node it will always be "${WEAVE_REGULAR_POLYGON_NODE_TYPE}"`),
16694
+ props: baseSchema.shape.props.extend({
16695
+ nodeType: z.literal(WEAVE_REGULAR_POLYGON_NODE_TYPE).describe(`Type of the node, for a regular polygon node it will always be "${WEAVE_REGULAR_POLYGON_NODE_TYPE}"`),
16696
+ sides: z.number().describe("Number of sides of the regular polygon, must be 3 or more"),
16697
+ radius: z.number().describe("Radius of the regular polygon in pixels, distance from the center to any vertex"),
16698
+ fill: z.string().describe("Fill color of the regular polygon in hex format with alpha channel (e.g. #RRGGBBAA)"),
16699
+ stroke: z.string().describe("Stroke color of the regular polygon in hex format with alpha channel (e.g. #RRGGBBAA)"),
16700
+ strokeWidth: z.number().describe("Stroke width of the regular polygon in pixels"),
16701
+ strokeScaleEnabled: z.boolean().describe("Whether the regular polygon stroke width should scale when the node is scaled. Defaults to true.")
16702
+ })
16703
+ });
16704
+ return nodeSchema;
16705
+ }
16706
+ };
16707
+
16708
+ //#endregion
16709
+ //#region src/nodes/polygon/constants.ts
16710
+ const WEAVE_POLYGON_NODE_TYPE = "polygon";
16711
+
16712
+ //#endregion
16713
+ //#region src/nodes/polygon/presets.ts
16714
+ const WEAVE_POLYGON_PRESETS = {
16715
+ triangle: {
16716
+ label: "Triangle",
16717
+ sides: 3,
16718
+ defaultWidth: 100,
16719
+ defaultHeight: 100,
16720
+ normalizedPoints: [
16721
+ {
16722
+ x: .5,
16723
+ y: 0
16724
+ },
16725
+ {
16726
+ x: .933013,
16727
+ y: .75
16728
+ },
16729
+ {
16730
+ x: .066987,
16731
+ y: .75
16732
+ }
16733
+ ],
16734
+ normalizedInnerRect: {
16735
+ tl: {
16736
+ x: .283494,
16737
+ y: .375
16738
+ },
16739
+ tr: {
16740
+ x: .716506,
16741
+ y: .375
16742
+ },
16743
+ bl: {
16744
+ x: .283494,
16745
+ y: .75
16746
+ },
16747
+ br: {
16748
+ x: .716506,
16749
+ y: .75
16750
+ }
16751
+ }
16752
+ },
16753
+ diamond: {
16754
+ label: "Diamond",
16755
+ sides: 4,
16756
+ defaultWidth: 100,
16757
+ defaultHeight: 100,
16758
+ normalizedPoints: [
16759
+ {
16760
+ x: .5,
16761
+ y: 0
16762
+ },
16763
+ {
16764
+ x: 1,
16765
+ y: .5
16766
+ },
16767
+ {
16768
+ x: .5,
16769
+ y: 1
16770
+ },
16771
+ {
16772
+ x: 0,
16773
+ y: .5
16774
+ }
16775
+ ],
16776
+ normalizedInnerRect: {
16777
+ tl: {
16778
+ x: .25,
16779
+ y: .25
16780
+ },
16781
+ tr: {
16782
+ x: .75,
16783
+ y: .25
16784
+ },
16785
+ bl: {
16786
+ x: .25,
16787
+ y: .75
16788
+ },
16789
+ br: {
16790
+ x: .75,
16791
+ y: .75
16792
+ }
16793
+ }
16794
+ },
16795
+ pentagon: {
16796
+ label: "Pentagon",
16797
+ sides: 5,
16798
+ defaultWidth: 100,
16799
+ defaultHeight: 100,
16800
+ normalizedPoints: [
16801
+ {
16802
+ x: .5,
16803
+ y: 0
16804
+ },
16805
+ {
16806
+ x: .975528,
16807
+ y: .345492
16808
+ },
16809
+ {
16810
+ x: .793893,
16811
+ y: .904508
16812
+ },
16813
+ {
16814
+ x: .206107,
16815
+ y: .904508
16816
+ },
16817
+ {
16818
+ x: .024472,
16819
+ y: .345492
16820
+ }
16821
+ ],
16822
+ normalizedInnerRect: {
16823
+ tl: {
16824
+ x: .132634,
16825
+ y: .316578
16826
+ },
16827
+ tr: {
16828
+ x: .867366,
16829
+ y: .316578
16830
+ },
16831
+ bl: {
16832
+ x: .132634,
16833
+ y: .678381
16834
+ },
16835
+ br: {
16836
+ x: .867366,
16837
+ y: .678381
16838
+ }
16839
+ }
16840
+ },
16841
+ hexagon: {
16842
+ label: "Hexagon",
16843
+ sides: 6,
16844
+ defaultWidth: 100,
16845
+ defaultHeight: 100,
16846
+ normalizedPoints: [
16847
+ {
16848
+ x: .5,
16849
+ y: 0
16850
+ },
16851
+ {
16852
+ x: .933013,
16853
+ y: .25
16854
+ },
16855
+ {
16856
+ x: .933013,
16857
+ y: .75
16858
+ },
16859
+ {
16860
+ x: .5,
16861
+ y: 1
16862
+ },
16863
+ {
16864
+ x: .066987,
16865
+ y: .75
16866
+ },
16867
+ {
16868
+ x: .066987,
16869
+ y: .25
16870
+ }
16871
+ ],
16872
+ normalizedInnerRect: {
16873
+ tl: {
16874
+ x: .066987,
16875
+ y: .25
16876
+ },
16877
+ tr: {
16878
+ x: .933013,
16879
+ y: .25
16880
+ },
16881
+ bl: {
16882
+ x: .066987,
16883
+ y: .75
16884
+ },
16885
+ br: {
16886
+ x: .933013,
16887
+ y: .75
16888
+ }
16889
+ }
16890
+ },
16891
+ octagon: {
16892
+ label: "Octagon",
16893
+ sides: 8,
16894
+ defaultWidth: 100,
16895
+ defaultHeight: 100,
16896
+ normalizedPoints: [
16897
+ {
16898
+ x: .5,
16899
+ y: 0
16900
+ },
16901
+ {
16902
+ x: .853553,
16903
+ y: .146447
16904
+ },
16905
+ {
16906
+ x: 1,
16907
+ y: .5
16908
+ },
16909
+ {
16910
+ x: .853553,
16911
+ y: .853553
16912
+ },
16913
+ {
16914
+ x: .5,
16915
+ y: 1
16916
+ },
16917
+ {
16918
+ x: .146447,
16919
+ y: .853553
16920
+ },
16921
+ {
16922
+ x: 0,
16923
+ y: .5
16924
+ },
16925
+ {
16926
+ x: .146447,
16927
+ y: .146447
16928
+ }
16929
+ ],
16930
+ normalizedInnerRect: {
16931
+ tl: {
16932
+ x: .25,
16933
+ y: .25
16934
+ },
16935
+ tr: {
16936
+ x: .75,
16937
+ y: .25
16938
+ },
16939
+ bl: {
16940
+ x: .25,
16941
+ y: .75
16942
+ },
16943
+ br: {
16944
+ x: .75,
16945
+ y: .75
16946
+ }
16947
+ }
16948
+ },
16949
+ decagon: {
16950
+ label: "Decagon",
16951
+ sides: 10,
16952
+ defaultWidth: 100,
16953
+ defaultHeight: 100,
16954
+ normalizedPoints: [
16955
+ {
16956
+ x: .5,
16957
+ y: 0
16958
+ },
16959
+ {
16960
+ x: .793893,
16961
+ y: .095492
16962
+ },
16963
+ {
16964
+ x: .975528,
16965
+ y: .345492
16966
+ },
16967
+ {
16968
+ x: .975528,
16969
+ y: .654508
16970
+ },
16971
+ {
16972
+ x: .793893,
16973
+ y: .904508
16974
+ },
16975
+ {
16976
+ x: .5,
16977
+ y: 1
16978
+ },
16979
+ {
16980
+ x: .206107,
16981
+ y: .904508
16982
+ },
16983
+ {
16984
+ x: .024472,
16985
+ y: .654508
16986
+ },
16987
+ {
16988
+ x: .024472,
16989
+ y: .345492
16990
+ },
16991
+ {
16992
+ x: .206107,
16993
+ y: .095492
16994
+ }
16995
+ ],
16996
+ normalizedInnerRect: {
16997
+ tl: {
16998
+ x: .093851,
16999
+ y: .35
17000
+ },
17001
+ tr: {
17002
+ x: .906149,
17003
+ y: .35
17004
+ },
17005
+ bl: {
17006
+ x: .093851,
17007
+ y: .75
17008
+ },
17009
+ br: {
17010
+ x: .906149,
17011
+ y: .75
17012
+ }
17013
+ }
17014
+ }
17015
+ };
17016
+ /**
17017
+ * Scales a preset's normalized points and inner rect to actual pixel dimensions.
17018
+ *
17019
+ * Points are normalized so that minX = 0 and minY = 0 in the resulting pixel
17020
+ * space. This ensures the polygon group's position corresponds exactly to the
17021
+ * visual top-left of the polygon, which is required for the snapping system to
17022
+ * work correctly (it assumes nodeBox.x === node.x()).
17023
+ */
17024
+ function instantiatePreset(def, width, height) {
17025
+ const rawPoints = def.normalizedPoints.map((p) => ({
17026
+ x: p.x * width,
17027
+ y: p.y * height
17028
+ }));
17029
+ const minX = Math.min(...rawPoints.map((p) => p.x));
17030
+ const minY = Math.min(...rawPoints.map((p) => p.y));
17031
+ const points = rawPoints.map((p) => ({
17032
+ x: p.x - minX,
17033
+ y: p.y - minY
17034
+ }));
17035
+ const ir = def.normalizedInnerRect;
17036
+ const innerRect = {
17037
+ tl: {
17038
+ x: ir.tl.x * width - minX,
17039
+ y: ir.tl.y * height - minY
17040
+ },
17041
+ tr: {
17042
+ x: ir.tr.x * width - minX,
17043
+ y: ir.tr.y * height - minY
17044
+ },
17045
+ bl: {
17046
+ x: ir.bl.x * width - minX,
17047
+ y: ir.bl.y * height - minY
17048
+ },
17049
+ br: {
17050
+ x: ir.br.x * width - minX,
17051
+ y: ir.br.y * height - minY
17052
+ }
17053
+ };
17054
+ const visualWidth = Math.max(...points.map((p) => p.x));
17055
+ const visualHeight = Math.max(...points.map((p) => p.y));
17056
+ return {
17057
+ points,
17058
+ innerRect,
17059
+ width: visualWidth,
17060
+ height: visualHeight
17061
+ };
17062
+ }
17063
+
17064
+ //#endregion
17065
+ //#region src/nodes/polygon/polygon.ts
17066
+ function computePolygonBounds(points) {
17067
+ if (!points.length) return {
17068
+ width: 0,
17069
+ height: 0
17070
+ };
17071
+ const maxX = Math.max(...points.map((p) => p.x));
17072
+ const maxY = Math.max(...points.map((p) => p.y));
17073
+ return {
17074
+ width: Math.max(1, maxX),
17075
+ height: Math.max(1, maxY)
17076
+ };
17077
+ }
17078
+ function polygonSelfRect() {
17079
+ const pts = this.getAttr("points");
17080
+ if (!pts?.length) return {
17081
+ x: 0,
17082
+ y: 0,
17083
+ width: 0,
17084
+ height: 0
17085
+ };
17086
+ const minX = Math.min(...pts.map((p) => p.x));
17087
+ const minY = Math.min(...pts.map((p) => p.y));
17088
+ const maxX = Math.max(...pts.map((p) => p.x));
17089
+ const maxY = Math.max(...pts.map((p) => p.y));
17090
+ return {
17091
+ x: minX,
17092
+ y: minY,
17093
+ width: maxX - minX,
17094
+ height: maxY - minY
17095
+ };
17096
+ }
17097
+ function getPolygonLabelTextBounds(innerRect, paddingX, paddingY) {
17098
+ return {
17099
+ x: innerRect.tl.x + paddingX,
17100
+ y: innerRect.tl.y + paddingY,
17101
+ width: Math.max(1, innerRect.tr.x - innerRect.tl.x - paddingX * 2),
17102
+ height: Math.max(1, innerRect.bl.y - innerRect.tl.y - paddingY * 2)
17103
+ };
17104
+ }
17105
+ function sceneFunc(context, shape) {
17106
+ const pts = shape.getAttr("points");
17107
+ if (!pts || pts.length < 3) return;
17108
+ context.beginPath();
17109
+ context.moveTo(pts[0].x, pts[0].y);
17110
+ for (let i = 1; i < pts.length; i++) context.lineTo(pts[i].x, pts[i].y);
17111
+ context.closePath();
17112
+ context.fillStrokeShape(shape);
17113
+ }
17114
+ /**
17115
+ * Draws an "inside" stroke for the border shape.
17116
+ * Clips to the polygon interior then draws 2× the stroke width so the outer
17117
+ * half is clipped away — resulting in a stroke of correct visual width that
17118
+ * stays entirely inside the polygon boundary.
17119
+ * strokeWidth is intentionally 0 on the shape (so getClientRect doesn't
17120
+ * expand the bounding box); the real width is stored in `innerStrokeWidth`.
17121
+ */
17122
+ function borderSceneFunc(context, shape) {
17123
+ const pts = shape.getAttr("points");
17124
+ if (!pts || pts.length < 3) return;
17125
+ const sw = shape.getAttr("innerStrokeWidth");
17126
+ if (!sw) return;
17127
+ const stroke = shape.stroke();
17128
+ if (!stroke || stroke === "transparent") return;
17129
+ const ctx = context._context;
17130
+ const drawPath = () => {
17131
+ ctx.beginPath();
17132
+ ctx.moveTo(pts[0].x, pts[0].y);
17133
+ for (let i = 1; i < pts.length; i++) ctx.lineTo(pts[i].x, pts[i].y);
17134
+ ctx.closePath();
17135
+ };
17136
+ ctx.save();
17137
+ drawPath();
17138
+ ctx.clip();
17139
+ drawPath();
17140
+ ctx.lineWidth = sw * 2;
17141
+ ctx.strokeStyle = stroke;
17142
+ ctx.stroke();
17143
+ ctx.restore();
17144
+ }
17145
+ var WeavePolygonNode = class extends WeaveNode {
17146
+ nodeType = WEAVE_POLYGON_NODE_TYPE;
17147
+ initialize = void 0;
17148
+ _transforming = false;
17149
+ get shapeLabelEditor() {
17150
+ this._shapeLabelEditor ??= new WeaveShapeLabelEditor(this.instance);
17151
+ return this._shapeLabelEditor;
17152
+ }
17153
+ constructor(params) {
17154
+ super();
17155
+ const { config } = params ?? {};
17156
+ this.config = { transform: { ...config?.transform } };
17157
+ }
17158
+ getLabelTextBounds(group) {
17159
+ const attrs = group.getAttrs();
17160
+ const innerRect = attrs.innerRect;
17161
+ const paddingX = attrs.labelPaddingX ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelPaddingX;
17162
+ const paddingY = attrs.labelPaddingY ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelPaddingY;
17163
+ if (!innerRect) return {
17164
+ x: 0,
17165
+ y: 0,
17166
+ width: 1,
17167
+ height: 1
17168
+ };
17169
+ return getPolygonLabelTextBounds(innerRect, paddingX, paddingY);
17170
+ }
17171
+ scalePolygonByDimensions(polygon, nextProps, nodeInstance) {
17172
+ let points = polygon.getAttr("points");
17173
+ const propsMaxX = points.length ? Math.max(...points.map((p) => p.x)) : 0;
17174
+ const propsMaxY = points.length ? Math.max(...points.map((p) => p.y)) : 0;
17175
+ const wantWidth = nextProps.width;
17176
+ const wantHeight = nextProps.height;
17177
+ if (wantWidth === void 0 || wantHeight === void 0) return points;
17178
+ const sX = propsMaxX > 0 ? wantWidth / propsMaxX : 1;
17179
+ const sY = propsMaxY > 0 ? wantHeight / propsMaxY : 1;
17180
+ if (Math.abs(sX - 1) <= .001 && Math.abs(sY - 1) <= .001) return points;
17181
+ const scaledPoints = points.map((p) => ({
17182
+ x: p.x * sX,
17183
+ y: p.y * sY
17184
+ }));
17185
+ const prevInnerRect = polygon.getAttr("innerRect");
17186
+ if (prevInnerRect) {
17187
+ const scaledInnerRect = {
17188
+ tl: {
17189
+ x: prevInnerRect.tl.x * sX,
17190
+ y: prevInnerRect.tl.y * sY
17191
+ },
17192
+ tr: {
17193
+ x: prevInnerRect.tr.x * sX,
17194
+ y: prevInnerRect.tr.y * sY
17195
+ },
17196
+ bl: {
17197
+ x: prevInnerRect.bl.x * sX,
17198
+ y: prevInnerRect.bl.y * sY
17199
+ },
17200
+ br: {
17201
+ x: prevInnerRect.br.x * sX,
17202
+ y: prevInnerRect.br.y * sY
17203
+ }
17204
+ };
17205
+ polygon.setAttr("innerRect", scaledInnerRect);
17206
+ }
17207
+ polygon.setAttr("points", scaledPoints);
17208
+ points = scaledPoints;
17209
+ if (!this._transforming) this.instance.updateNode(this.serialize(nodeInstance));
17210
+ return points;
17211
+ }
17212
+ onLabelGrow(polygon, bgShape, borderShape, nodeInstance, neededHeight) {
17213
+ const livePoints = polygon.getAttr("points");
17214
+ const liveInnerRect = polygon.getAttr("innerRect");
17215
+ if (!liveInnerRect) return;
17216
+ const currentBoundsHeight = liveInnerRect.bl.y - liveInnerRect.tl.y;
17217
+ if (neededHeight <= currentBoundsHeight) return;
17218
+ const oldHeight = Math.max(...livePoints.map((p) => p.y));
17219
+ const scale = currentBoundsHeight > 0 ? neededHeight / currentBoundsHeight : 1;
17220
+ const newHeight = oldHeight * scale;
17221
+ const newPoints = livePoints.map((p) => ({
17222
+ ...p,
17223
+ y: p.y * scale
17224
+ }));
17225
+ const newInnerRect = {
17226
+ tl: {
17227
+ ...liveInnerRect.tl,
17228
+ y: liveInnerRect.tl.y * scale
17229
+ },
17230
+ tr: {
17231
+ ...liveInnerRect.tr,
17232
+ y: liveInnerRect.tr.y * scale
17233
+ },
17234
+ bl: {
17235
+ ...liveInnerRect.bl,
17236
+ y: liveInnerRect.bl.y * scale
17237
+ },
17238
+ br: {
17239
+ ...liveInnerRect.br,
17240
+ y: liveInnerRect.br.y * scale
17241
+ }
17242
+ };
17243
+ polygon.setAttr("points", newPoints);
17244
+ polygon.setAttr("innerRect", newInnerRect);
17245
+ polygon.setAttr("height", newHeight);
17246
+ bgShape?.setAttr("points", newPoints);
17247
+ borderShape?.setAttr("points", newPoints);
17248
+ if (!this._transforming) this.instance.updateNode(this.serialize(nodeInstance));
17249
+ }
17250
+ triggerPolygonLabelEdit(polygon, props) {
17251
+ const onCommit = (labelText) => {
17252
+ const updatedGroup = this.instance.getStage().findOne(`#${props.id}`);
17253
+ if (!updatedGroup) return;
17254
+ const serialized = this.serialize(updatedGroup);
17255
+ serialized.props.labelText = labelText;
17256
+ this.instance.updateNode(serialized);
17257
+ };
17258
+ const currentLabelTextBounds = this.getLabelTextBounds(polygon);
17259
+ this.shapeLabelEditor.triggerEditMode(polygon, currentLabelTextBounds, onCommit, (neededShapeHeight) => {
17260
+ const liveAttrs = polygon.getAttrs();
17261
+ const livePoints = liveAttrs.points;
17262
+ const liveInnerRect = liveAttrs.innerRect;
17263
+ const liveInnerRectHeight = liveInnerRect.bl.y - liveInnerRect.tl.y;
17264
+ if (neededShapeHeight <= liveInnerRectHeight) return;
17265
+ const oldHeight = Math.max(...livePoints.map((p) => p.y));
17266
+ const scale = liveInnerRectHeight > 0 ? neededShapeHeight / liveInnerRectHeight : 1;
17267
+ const newHeight = oldHeight * scale;
17268
+ const newPoints = livePoints.map((p) => ({
17269
+ ...p,
17270
+ y: p.y * scale
17271
+ }));
17272
+ const newInnerRect = {
17273
+ tl: {
17274
+ ...liveInnerRect.tl,
17275
+ y: liveInnerRect.tl.y * scale
17276
+ },
17277
+ tr: {
17278
+ ...liveInnerRect.tr,
17279
+ y: liveInnerRect.tr.y * scale
17280
+ },
17281
+ bl: {
17282
+ ...liveInnerRect.bl,
17283
+ y: liveInnerRect.bl.y * scale
17284
+ },
17285
+ br: {
17286
+ ...liveInnerRect.br,
17287
+ y: liveInnerRect.br.y * scale
17288
+ }
17289
+ };
17290
+ polygon.setAttrs({
17291
+ points: newPoints,
17292
+ innerRect: newInnerRect,
17293
+ height: newHeight
17294
+ });
17295
+ this.onUpdate(polygon, polygon.getAttrs());
17296
+ const newLabelTextBounds = this.getLabelTextBounds(polygon);
17297
+ this.shapeLabelEditor.repositionTextArea(polygon, newLabelTextBounds);
17298
+ });
17299
+ }
17300
+ scaleReset(group) {
17301
+ const scaleX = group.scaleX();
17302
+ const scaleY = group.scaleY();
17303
+ if (scaleX === 1 && scaleY === 1) return;
17304
+ const points = group.getAttr("points");
17305
+ const innerRect = group.getAttr("innerRect");
17306
+ const newPoints = points.map((p) => ({
17307
+ x: p.x * scaleX,
17308
+ y: p.y * scaleY
17309
+ }));
17310
+ const newInnerRect = innerRect ? {
17311
+ tl: {
17312
+ x: innerRect.tl.x * scaleX,
17313
+ y: innerRect.tl.y * scaleY
17314
+ },
17315
+ tr: {
17316
+ x: innerRect.tr.x * scaleX,
17317
+ y: innerRect.tr.y * scaleY
17318
+ },
17319
+ bl: {
17320
+ x: innerRect.bl.x * scaleX,
17321
+ y: innerRect.bl.y * scaleY
17322
+ },
17323
+ br: {
17324
+ x: innerRect.br.x * scaleX,
17325
+ y: innerRect.br.y * scaleY
17326
+ }
17327
+ } : void 0;
17328
+ const absTransform = group.getAbsoluteTransform().copy();
17329
+ group.setAttr("points", newPoints);
17330
+ if (newInnerRect) group.setAttr("innerRect", newInnerRect);
17331
+ group.scaleX(1);
17332
+ group.scaleY(1);
17333
+ group.setAttr("width", Math.max(...newPoints.map((p) => p.x)));
17334
+ group.setAttr("height", Math.max(...newPoints.map((p) => p.y)));
17335
+ const newTransform = group.getAbsoluteTransform();
17336
+ const dx = absTransform.m[4] - newTransform.m[4];
17337
+ const dy = absTransform.m[5] - newTransform.m[5];
17338
+ group.x(group.x() + dx);
17339
+ group.y(group.y() + dy);
17340
+ }
17341
+ onRender(props) {
17342
+ const polygon = new Konva.Group({
17343
+ ...props,
17344
+ name: "node"
17345
+ });
17346
+ const points = polygon.getAttr("points");
17347
+ const strokeWidth = props.strokeWidth || 0;
17348
+ const bgShape = new Konva.Shape({
17349
+ id: `${props.id}-bg`,
17350
+ nodeId: props.id,
17351
+ points,
17352
+ ...computePolygonBounds(points),
17353
+ fill: props.fill || "transparent",
17354
+ strokeWidth: 0,
17355
+ strokeScaleEnabled: false,
17356
+ sceneFunc
17357
+ });
17358
+ bgShape.getSelfRect = polygonSelfRect.bind(bgShape);
17359
+ polygon.add(bgShape);
17360
+ const borderShape = new Konva.Shape({
17361
+ id: `${props.id}-border`,
17362
+ points,
17363
+ fill: "transparent",
17364
+ stroke: props.stroke || "transparent",
17365
+ strokeWidth: 0,
17366
+ innerStrokeWidth: strokeWidth,
17367
+ strokeScaleEnabled: false,
17368
+ listening: false,
17369
+ sceneFunc: borderSceneFunc
17370
+ });
17371
+ borderShape.getSelfRect = polygonSelfRect.bind(borderShape);
17372
+ polygon.add(borderShape);
17373
+ const innerRect = polygon.getAttr("innerRect");
17374
+ const paddingX = props.labelPaddingX ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelPaddingX;
17375
+ const paddingY = props.labelPaddingY ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelPaddingY;
17376
+ const labelTextBounds = innerRect ? getPolygonLabelTextBounds(innerRect, paddingX, paddingY) : {
17377
+ x: 0,
17378
+ y: 0,
17379
+ width: 1,
17380
+ height: 1
17381
+ };
17382
+ this.shapeLabelEditor.renderLabel(polygon, props, labelTextBounds);
17383
+ borderShape.moveToTop();
17384
+ bgShape.moveToBottom();
17385
+ this.setupDefaultNodeAugmentation(polygon);
15580
17386
  const defaultTransformerProperties = this.defaultGetTransformerProperties(this.config.transform);
15581
- regularPolygon.getTransformerProperties = function() {
17387
+ polygon.getTransformerProperties = function() {
15582
17388
  return {
15583
17389
  ...defaultTransformerProperties,
15584
17390
  enabledAnchors: [
15585
17391
  "top-left",
17392
+ "top-center",
15586
17393
  "top-right",
17394
+ "middle-right",
17395
+ "middle-left",
15587
17396
  "bottom-left",
17397
+ "bottom-center",
15588
17398
  "bottom-right"
15589
17399
  ],
15590
- keepRatio: true
17400
+ keepRatio: false
15591
17401
  };
15592
17402
  };
15593
- regularPolygon.allowedAnchors = function() {
17403
+ polygon.allowedAnchors = function() {
15594
17404
  return [
15595
17405
  "top-left",
17406
+ "top-center",
15596
17407
  "top-right",
17408
+ "middle-right",
17409
+ "middle-left",
15597
17410
  "bottom-left",
17411
+ "bottom-center",
15598
17412
  "bottom-right"
15599
17413
  ];
15600
17414
  };
15601
- this.setupDefaultNodeEvents(regularPolygon);
15602
- return regularPolygon;
17415
+ this.setupDefaultNodeEvents(polygon);
17416
+ polygon.on("transformstart", () => {
17417
+ this._transforming = true;
17418
+ });
17419
+ polygon.on("transform", () => {
17420
+ this.scaleReset(polygon);
17421
+ this.onUpdate(polygon, polygon.getAttrs());
17422
+ });
17423
+ polygon.on("transformend", () => {
17424
+ this._transforming = false;
17425
+ });
17426
+ polygon.dblClick = () => {
17427
+ if (this.shapeLabelEditor.isEditing()) return;
17428
+ if (!(this.isSelecting() && this.isNodeSelected(polygon))) return;
17429
+ this.triggerPolygonLabelEdit(polygon, props);
17430
+ };
17431
+ polygon.getNodeMinSize = () => {
17432
+ return computePolygonLabelMinSize(this.instance.getStage(), polygon);
17433
+ };
17434
+ return polygon;
15603
17435
  }
15604
17436
  onUpdate(nodeInstance, nextProps) {
15605
17437
  nodeInstance.setAttrs({ ...nextProps });
15606
- const sides = nodeInstance.getAttr("sides");
15607
- const radius = nodeInstance.getAttr("radius");
15608
- const regularPolygon = nodeInstance;
15609
- const internalRPBg = regularPolygon.findOne(`#${nextProps.id}-bg`);
15610
- const internalRPBorder = regularPolygon.findOne(`#${nextProps.id}-border`);
15611
- if (internalRPBg) {
15612
- internalRPBg.setAttrs({
15613
- ...nextProps,
15614
- name: void 0,
15615
- id: `${nextProps.id}-bg`,
15616
- nodeId: nextProps.id,
15617
- x: radius,
15618
- y: radius,
15619
- sides,
15620
- radius,
17438
+ const polygon = nodeInstance;
17439
+ const strokeWidth = nextProps.strokeWidth || 0;
17440
+ const points = this.scalePolygonByDimensions(polygon, nextProps, nodeInstance);
17441
+ const bgShape = polygon.findOne(`#${nextProps.id}-bg`);
17442
+ if (bgShape) {
17443
+ bgShape.setAttrs({
17444
+ points,
17445
+ ...computePolygonBounds(points),
15621
17446
  fill: nextProps.fill || "transparent",
15622
17447
  strokeWidth: 0,
15623
- strokeScaleEnabled: true,
15624
- rotation: 0
17448
+ strokeScaleEnabled: false
15625
17449
  });
15626
- const internalRPBgBox = internalRPBg.getClientRect({ relativeTo: regularPolygon });
15627
- internalRPBg.x(internalRPBg.x() - internalRPBgBox.x);
15628
- internalRPBg.y(internalRPBg.y() - internalRPBgBox.y);
15629
- internalRPBg.moveToBottom();
17450
+ bgShape.moveToBottom();
15630
17451
  }
15631
- if (internalRPBorder) {
15632
- internalRPBorder.setAttrs({
15633
- ...nextProps,
15634
- name: void 0,
15635
- id: `${nextProps.id}-border`,
15636
- x: radius,
15637
- y: radius,
15638
- sides,
15639
- radius: radius - (nextProps.strokeWidth || 0) / 2,
15640
- stroke: nextProps.stroke || "transparent",
15641
- strokeWidth: nextProps.strokeWidth || 0,
15642
- strokeScaleEnabled: true,
15643
- listening: false,
15644
- rotation: 0
15645
- });
15646
- const internalRPBorderBox = internalRPBorder.getClientRect({ relativeTo: regularPolygon });
15647
- internalRPBorder.x(internalRPBorder.x() - internalRPBorderBox.x);
15648
- internalRPBorder.y(internalRPBorder.y() - internalRPBorderBox.y);
15649
- internalRPBorder.moveToTop();
17452
+ const borderShape = polygon.findOne(`#${nextProps.id}-border`);
17453
+ if (borderShape) borderShape.setAttrs({
17454
+ points,
17455
+ fill: "transparent",
17456
+ stroke: nextProps.stroke || "transparent",
17457
+ strokeWidth: 0,
17458
+ innerStrokeWidth: strokeWidth,
17459
+ strokeScaleEnabled: false,
17460
+ listening: false
17461
+ });
17462
+ const paddingX = nextProps.labelPaddingX ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelPaddingX;
17463
+ const paddingY = nextProps.labelPaddingY ?? WEAVE_SHAPE_LABEL_DEFAULTS.labelPaddingY;
17464
+ const innerRect = polygon.getAttr("innerRect");
17465
+ const labelTextBounds = innerRect ? getPolygonLabelTextBounds(innerRect, paddingX, paddingY) : {
17466
+ x: 0,
17467
+ y: 0,
17468
+ width: 1,
17469
+ height: 1
17470
+ };
17471
+ this.shapeLabelEditor.updateLabel(polygon, nextProps, labelTextBounds, (neededHeight) => this.onLabelGrow(polygon, bgShape, borderShape, nodeInstance, neededHeight));
17472
+ const labelNode = polygon.findOne(`#${labelId(nextProps.id)}`);
17473
+ if (labelNode) {
17474
+ labelNode.moveToTop();
17475
+ borderShape?.moveToTop();
15650
17476
  }
15651
17477
  const nodesSelectionPlugin = this.instance.getPlugin("nodesSelection");
15652
- if (nodesSelectionPlugin) {
15653
- const actualSelectedNodes = nodesSelectionPlugin.getSelectedNodes();
15654
- nodesSelectionPlugin.setSelectedNodes(actualSelectedNodes);
15655
- nodesSelectionPlugin.getTransformer().forceUpdate();
15656
- }
15657
- }
15658
- scaleReset(node) {
15659
- const absTransform = node.getAbsoluteTransform().copy();
15660
- const radius = node.getAttr("radius");
15661
- node.setAttrs({ radius: radius * node.scaleX() });
15662
- node.scaleX(1);
15663
- node.scaleY(1);
15664
- const newTransform = node.getAbsoluteTransform();
15665
- const dx = absTransform.m[4] - newTransform.m[4];
15666
- const dy = absTransform.m[5] - newTransform.m[5];
15667
- node.x(node.x() + dx);
15668
- node.y(node.y() + dy);
17478
+ if (nodesSelectionPlugin) nodesSelectionPlugin.getTransformer().forceUpdate();
15669
17479
  }
15670
- realOffset(element) {
17480
+ realOffset(_element) {
15671
17481
  return {
15672
- x: element.props.radius,
15673
- y: element.props.radius
17482
+ x: 0,
17483
+ y: 0
15674
17484
  };
15675
17485
  }
15676
17486
  static defaultState(nodeId) {
17487
+ const preset = WEAVE_POLYGON_PRESETS.pentagon;
17488
+ const { points, innerRect, width, height } = instantiatePreset(preset, preset.defaultWidth, preset.defaultHeight);
15677
17489
  return {
15678
17490
  ...super.defaultState(nodeId),
15679
- type: WEAVE_REGULAR_POLYGON_NODE_TYPE,
17491
+ type: WEAVE_POLYGON_NODE_TYPE,
15680
17492
  props: {
15681
17493
  ...super.defaultState(nodeId).props,
15682
- nodeType: WEAVE_REGULAR_POLYGON_NODE_TYPE,
17494
+ nodeType: WEAVE_POLYGON_NODE_TYPE,
15683
17495
  x: 0,
15684
17496
  y: 0,
15685
- sides: 5,
15686
- radius: 100,
17497
+ width,
17498
+ height,
17499
+ sides: preset.sides,
17500
+ points,
17501
+ innerRect,
15687
17502
  stroke: "#000000",
15688
17503
  fill: "#FFFFFF",
15689
17504
  strokeWidth: 1,
15690
- strokeScaleEnabled: true,
17505
+ strokeScaleEnabled: false,
15691
17506
  rotation: 0,
15692
17507
  zIndex: 1,
15693
- children: []
17508
+ children: [],
17509
+ ...WEAVE_SHAPE_LABEL_DEFAULTS
15694
17510
  }
15695
17511
  };
15696
17512
  }
@@ -15698,38 +17514,103 @@ var WeaveRegularPolygonNode = class extends WeaveNode {
15698
17514
  return mergeExceptArrays(defaultNodeState, { props: {
15699
17515
  x: props.x,
15700
17516
  y: props.y,
17517
+ width: props.width,
17518
+ height: props.height,
15701
17519
  sides: props.sides,
15702
- radius: props.radius,
17520
+ points: props.points,
17521
+ innerRect: props.innerRect,
15703
17522
  rotation: props.rotation,
15704
17523
  fill: props.fill,
15705
17524
  ...props.stroke && { stroke: props.stroke },
15706
- ...props.strokeWidth && { strokeWidth: props.strokeWidth }
17525
+ ...props.strokeWidth !== void 0 && { strokeWidth: props.strokeWidth },
17526
+ ...props.labelText !== void 0 && { labelText: props.labelText },
17527
+ ...props.labelFontFamily !== void 0 && { labelFontFamily: props.labelFontFamily },
17528
+ ...props.labelFontSize !== void 0 && { labelFontSize: props.labelFontSize },
17529
+ ...props.labelFontStyle !== void 0 && { labelFontStyle: props.labelFontStyle },
17530
+ ...props.labelFontVariant !== void 0 && { labelFontVariant: props.labelFontVariant },
17531
+ ...props.labelFill !== void 0 && { labelFill: props.labelFill },
17532
+ ...props.labelAlign !== void 0 && { labelAlign: props.labelAlign },
17533
+ ...props.labelVerticalAlign !== void 0 && { labelVerticalAlign: props.labelVerticalAlign },
17534
+ ...props.labelLetterSpacing !== void 0 && { labelLetterSpacing: props.labelLetterSpacing },
17535
+ ...props.labelLineHeight !== void 0 && { labelLineHeight: props.labelLineHeight },
17536
+ ...props.labelPaddingX !== void 0 && { labelPaddingX: props.labelPaddingX },
17537
+ ...props.labelPaddingY !== void 0 && { labelPaddingY: props.labelPaddingY }
15707
17538
  } });
15708
17539
  }
15709
17540
  static updateNodeState(prevNodeState, nextProps) {
15710
17541
  return mergeExceptArrays(prevNodeState, { props: {
15711
17542
  x: nextProps.x,
15712
17543
  y: nextProps.y,
17544
+ ...nextProps.width !== void 0 && { width: nextProps.width },
17545
+ ...nextProps.height !== void 0 && { height: nextProps.height },
15713
17546
  sides: nextProps.sides,
15714
- radius: nextProps.radius,
17547
+ points: nextProps.points,
17548
+ innerRect: nextProps.innerRect,
15715
17549
  rotation: nextProps.rotation,
15716
17550
  fill: nextProps.fill,
15717
17551
  ...nextProps.stroke && { stroke: nextProps.stroke },
15718
- ...nextProps.strokeWidth && { strokeWidth: nextProps.strokeWidth }
17552
+ ...nextProps.strokeWidth !== void 0 && { strokeWidth: nextProps.strokeWidth },
17553
+ ...nextProps.labelText !== void 0 && { labelText: nextProps.labelText },
17554
+ ...nextProps.labelFontFamily !== void 0 && { labelFontFamily: nextProps.labelFontFamily },
17555
+ ...nextProps.labelFontSize !== void 0 && { labelFontSize: nextProps.labelFontSize },
17556
+ ...nextProps.labelFontStyle !== void 0 && { labelFontStyle: nextProps.labelFontStyle },
17557
+ ...nextProps.labelFontVariant !== void 0 && { labelFontVariant: nextProps.labelFontVariant },
17558
+ ...nextProps.labelFill !== void 0 && { labelFill: nextProps.labelFill },
17559
+ ...nextProps.labelAlign !== void 0 && { labelAlign: nextProps.labelAlign },
17560
+ ...nextProps.labelVerticalAlign !== void 0 && { labelVerticalAlign: nextProps.labelVerticalAlign },
17561
+ ...nextProps.labelLetterSpacing !== void 0 && { labelLetterSpacing: nextProps.labelLetterSpacing },
17562
+ ...nextProps.labelLineHeight !== void 0 && { labelLineHeight: nextProps.labelLineHeight },
17563
+ ...nextProps.labelPaddingX !== void 0 && { labelPaddingX: nextProps.labelPaddingX },
17564
+ ...nextProps.labelPaddingY !== void 0 && { labelPaddingY: nextProps.labelPaddingY }
15719
17565
  } });
15720
17566
  }
15721
17567
  static getSchema() {
15722
17568
  const baseSchema = super.getSchema();
15723
17569
  const nodeSchema = baseSchema.extend({
15724
- type: z.literal(WEAVE_REGULAR_POLYGON_NODE_TYPE).describe(`Type of the node, for a regular polygon node it will always be "${WEAVE_REGULAR_POLYGON_NODE_TYPE}"`),
17570
+ type: z.literal(WEAVE_POLYGON_NODE_TYPE).describe(`Type of the node, for a polygon node it will always be "${WEAVE_POLYGON_NODE_TYPE}"`),
15725
17571
  props: baseSchema.shape.props.extend({
15726
- nodeType: z.literal(WEAVE_REGULAR_POLYGON_NODE_TYPE).describe(`Type of the node, for a regular polygon node it will always be "${WEAVE_REGULAR_POLYGON_NODE_TYPE}"`),
15727
- sides: z.number().describe("Number of sides of the regular polygon, must be 3 or more"),
15728
- radius: z.number().describe("Radius of the regular polygon in pixels, distance from the center to any vertex"),
15729
- fill: z.string().describe("Fill color of the regular polygon in hex format with alpha channel (e.g. #RRGGBBAA)"),
15730
- stroke: z.string().describe("Stroke color of the regular polygon in hex format with alpha channel (e.g. #RRGGBBAA)"),
15731
- strokeWidth: z.number().describe("Stroke width of the regular polygon in pixels"),
15732
- strokeScaleEnabled: z.boolean().describe("Whether the regular polygon stroke width should scale when the node is scaled. Defaults to true.")
17572
+ nodeType: z.literal(WEAVE_POLYGON_NODE_TYPE).describe(`Type of the node, for a polygon node it will always be "${WEAVE_POLYGON_NODE_TYPE}"`),
17573
+ sides: z.number().describe("Number of sides of the polygon (3 or more)"),
17574
+ width: z.number().optional().describe("Visual width of the polygon in pixels (= maxX of vertices). Setting this rescales vertices proportionally."),
17575
+ height: z.number().optional().describe("Visual height of the polygon in pixels (= maxY of vertices). Setting this rescales vertices proportionally."),
17576
+ points: z.array(z.object({
17577
+ x: z.number(),
17578
+ y: z.number()
17579
+ })).describe("Vertex positions of the polygon in group-local pixel space"),
17580
+ innerRect: z.object({
17581
+ tl: z.object({
17582
+ x: z.number(),
17583
+ y: z.number()
17584
+ }),
17585
+ tr: z.object({
17586
+ x: z.number(),
17587
+ y: z.number()
17588
+ }),
17589
+ bl: z.object({
17590
+ x: z.number(),
17591
+ y: z.number()
17592
+ }),
17593
+ br: z.object({
17594
+ x: z.number(),
17595
+ y: z.number()
17596
+ })
17597
+ }).describe("Largest inscribed axis-aligned rectangle inside the polygon (used for label bounds)"),
17598
+ fill: z.string().describe("Fill color of the polygon in hex format with alpha channel (e.g. #RRGGBBAA)"),
17599
+ stroke: z.string().describe("Stroke color of the polygon in hex format with alpha channel (e.g. #RRGGBBAA)"),
17600
+ strokeWidth: z.number().describe("Stroke width of the polygon in pixels"),
17601
+ strokeScaleEnabled: z.boolean().describe("Whether the polygon stroke width should scale when the node is scaled. Defaults to false."),
17602
+ labelText: z.string().optional().describe("Text label displayed inside the polygon"),
17603
+ labelFontFamily: z.string().optional().describe("Font family for the label text"),
17604
+ labelFontSize: z.number().optional().describe("Font size for the label text in pixels"),
17605
+ labelFontStyle: z.string().optional().describe("Font style for the label text (e.g. \"normal\", \"bold\", \"italic\", \"bold italic\")"),
17606
+ labelFontVariant: z.string().optional().describe("Font variant for the label text (e.g. \"normal\", \"small-caps\")"),
17607
+ labelFill: z.string().optional().describe("Color of the label text in hex format (e.g. #RRGGBBAA)"),
17608
+ labelAlign: z.string().optional().describe("Horizontal alignment of the label text (\"left\", \"center\", \"right\")"),
17609
+ labelVerticalAlign: z.string().optional().describe("Vertical alignment of the label text (\"top\", \"middle\", \"bottom\")"),
17610
+ labelLetterSpacing: z.number().optional().describe("Letter spacing for the label text in pixels"),
17611
+ labelLineHeight: z.number().optional().describe("Line height multiplier for the label text"),
17612
+ labelPaddingX: z.number().optional().describe("Horizontal inset (padding) in pixels applied on each side of the label"),
17613
+ labelPaddingY: z.number().optional().describe("Vertical inset (padding) in pixels applied on top and bottom of the label")
15733
17614
  })
15734
17615
  });
15735
17616
  return nodeSchema;
@@ -21628,6 +23509,7 @@ var WeaveRectangleToolAction = class extends WeaveAction {
21628
23509
  if (node) selectionPlugin.setSelectedNodes([node]);
21629
23510
  this.instance.triggerAction(SELECTION_TOOL_ACTION_NAME);
21630
23511
  }
23512
+ if (this.tempRectNode) this.tempRectNode.destroy();
21631
23513
  this.rectId = null;
21632
23514
  this.tempRectNode = null;
21633
23515
  this.moved = false;
@@ -24738,6 +26620,137 @@ var WeaveRegularPolygonToolAction = class extends WeaveAction {
24738
26620
  }
24739
26621
  };
24740
26622
 
26623
+ //#endregion
26624
+ //#region src/actions/polygon-tool/constants.ts
26625
+ const POLYGON_TOOL_ACTION_NAME = "polygonTool";
26626
+ const POLYGON_TOOL_STATE = {
26627
+ ["IDLE"]: "idle",
26628
+ ["ADDING"]: "adding",
26629
+ ["ADDED"]: "added"
26630
+ };
26631
+
26632
+ //#endregion
26633
+ //#region src/actions/polygon-tool/polygon-tool.ts
26634
+ var WeavePolygonToolAction = class extends WeaveAction {
26635
+ initialized = false;
26636
+ onPropsChange = void 0;
26637
+ onInit = void 0;
26638
+ constructor(preset) {
26639
+ super();
26640
+ this.preset = preset ?? "pentagon";
26641
+ this.initialize();
26642
+ }
26643
+ initialize() {
26644
+ this.initialized = false;
26645
+ this.state = POLYGON_TOOL_STATE.IDLE;
26646
+ this.polygonId = null;
26647
+ this.props = this.initProps();
26648
+ }
26649
+ getName() {
26650
+ return POLYGON_TOOL_ACTION_NAME;
26651
+ }
26652
+ initProps() {
26653
+ return {
26654
+ opacity: 1,
26655
+ fill: "#ffffffff",
26656
+ stroke: "#000000ff",
26657
+ strokeWidth: 1
26658
+ };
26659
+ }
26660
+ getPolygonsPresets() {
26661
+ return WEAVE_POLYGON_PRESETS;
26662
+ }
26663
+ getPolygonPreset() {
26664
+ return this.preset;
26665
+ }
26666
+ setPolygonPreset(preset) {
26667
+ this.preset = preset;
26668
+ }
26669
+ setupEvents() {
26670
+ const stage = this.instance.getStage();
26671
+ window.addEventListener("keydown", (e) => {
26672
+ if ((e.code === "Enter" || e.code === "Escape") && this.instance.getActiveAction() === POLYGON_TOOL_ACTION_NAME) this.cancelAction();
26673
+ }, { signal: this.instance.getEventsController().signal });
26674
+ stage.on("pointermove", () => {
26675
+ if (this.state === POLYGON_TOOL_STATE.IDLE) return;
26676
+ this.setCursor();
26677
+ });
26678
+ stage.on("pointerdown", (e) => {
26679
+ this.setTapStart(e);
26680
+ if (this.state !== POLYGON_TOOL_STATE.ADDING) return;
26681
+ this.handleAdding();
26682
+ });
26683
+ this.initialized = true;
26684
+ }
26685
+ setState(state) {
26686
+ this.state = state;
26687
+ }
26688
+ addPolygon() {
26689
+ this.setCursor();
26690
+ this.setFocusStage();
26691
+ this.instance.emitEvent("onAddingPolygon");
26692
+ this.setState(POLYGON_TOOL_STATE.ADDING);
26693
+ }
26694
+ handleAdding() {
26695
+ const { mousePoint, container } = this.instance.getMousePointer();
26696
+ this.polygonId = v4_default();
26697
+ const presetDef = WEAVE_POLYGON_PRESETS[this.preset];
26698
+ const scaleFactor = this.props.scaleFactor ?? 1;
26699
+ const { points, innerRect, width, height } = instantiatePreset(presetDef, presetDef.defaultWidth * scaleFactor, presetDef.defaultHeight * scaleFactor);
26700
+ const nodeHandler = this.instance.getNodeHandler(WEAVE_POLYGON_NODE_TYPE);
26701
+ if (nodeHandler) {
26702
+ const node = nodeHandler.create(this.polygonId, {
26703
+ ...this.props,
26704
+ x: mousePoint?.x ?? 0,
26705
+ y: mousePoint?.y ?? 0,
26706
+ width,
26707
+ height,
26708
+ sides: presetDef.sides,
26709
+ points,
26710
+ innerRect
26711
+ });
26712
+ this.instance.addNode(node, container?.getAttrs().id);
26713
+ }
26714
+ this.instance.emitEvent("onAddedPolygon");
26715
+ this.cancelAction();
26716
+ }
26717
+ trigger(cancelAction, params) {
26718
+ if (!this.instance) throw new Error("Instance not defined");
26719
+ if (!this.initialized) this.setupEvents();
26720
+ this.preset = params?.presetId ?? "pentagon";
26721
+ const stage = this.instance.getStage();
26722
+ stage.container().tabIndex = 1;
26723
+ stage.container().focus();
26724
+ this.cancelAction = cancelAction;
26725
+ const selectionPlugin = this.instance.getPlugin("nodesSelection");
26726
+ if (selectionPlugin) selectionPlugin.setSelectedNodes([]);
26727
+ this.props = this.initProps();
26728
+ this.addPolygon();
26729
+ }
26730
+ cleanup() {
26731
+ const stage = this.instance.getStage();
26732
+ stage.container().style.cursor = "default";
26733
+ const selectionPlugin = this.instance.getPlugin("nodesSelection");
26734
+ if (selectionPlugin) {
26735
+ const node = stage.findOne(`#${this.polygonId}`);
26736
+ if (node) selectionPlugin.setSelectedNodes([node]);
26737
+ this.instance.triggerAction(SELECTION_TOOL_ACTION_NAME);
26738
+ }
26739
+ this.polygonId = null;
26740
+ this.setState(POLYGON_TOOL_STATE.IDLE);
26741
+ }
26742
+ setCursor() {
26743
+ const stage = this.instance.getStage();
26744
+ stage.container().style.cursor = "crosshair";
26745
+ }
26746
+ setFocusStage() {
26747
+ const stage = this.instance.getStage();
26748
+ stage.container().tabIndex = 1;
26749
+ stage.container().blur();
26750
+ stage.container().focus();
26751
+ }
26752
+ };
26753
+
24741
26754
  //#endregion
24742
26755
  //#region src/actions/frame-tool/constants.ts
24743
26756
  const FRAME_TOOL_ACTION_NAME = "frameTool";
@@ -30367,19 +32380,40 @@ var WeaveNodesSnappingGuides = class {
30367
32380
  const stage = this.instance.getStage();
30368
32381
  const scaleX = stage.scaleX();
30369
32382
  const scaleY = stage.scaleY();
30370
- let finalContainer = container;
30371
- if (container !== stage) finalContainer = container.getParent();
30372
- const pos = finalContainer.position();
30373
- const rect = finalContainer.getClientRect({ relativeTo: stage });
30374
- const x = finalContainer === stage ? -pos.x / scaleX : rect.x;
30375
- const y = finalContainer === stage ? -pos.y / scaleX : rect.y;
30376
- const width = finalContainer === stage ? stage.width() / scaleX : rect.width;
30377
- const height = finalContainer === stage ? stage.height() / scaleY : rect.height;
32383
+ const mainLayer = this.instance.getMainLayer();
32384
+ if (container === stage || container === mainLayer) {
32385
+ const pos$1 = stage.position();
32386
+ return {
32387
+ x: -pos$1.x / scaleX,
32388
+ y: -pos$1.y / scaleY,
32389
+ width: stage.width() / scaleX,
32390
+ height: stage.height() / scaleY
32391
+ };
32392
+ }
32393
+ let frameNode = null;
32394
+ let cur = container;
32395
+ while (cur && cur !== mainLayer && cur !== stage) {
32396
+ if (cur.getAttrs().nodeType === "frame") {
32397
+ frameNode = cur;
32398
+ break;
32399
+ }
32400
+ cur = cur.getParent();
32401
+ }
32402
+ if (frameNode) {
32403
+ const rect = frameNode.getClientRect({ relativeTo: stage });
32404
+ return {
32405
+ x: rect.x,
32406
+ y: rect.y,
32407
+ width: rect.width,
32408
+ height: rect.height
32409
+ };
32410
+ }
32411
+ const pos = stage.position();
30378
32412
  return {
30379
- x,
30380
- y,
30381
- width,
30382
- height
32413
+ x: -pos.x / scaleX,
32414
+ y: -pos.y / scaleY,
32415
+ width: stage.width() / scaleX,
32416
+ height: stage.height() / scaleY
30383
32417
  };
30384
32418
  }
30385
32419
  renderSnapGuides(container, snap) {
@@ -30391,8 +32425,11 @@ var WeaveNodesSnappingGuides = class {
30391
32425
  if (snap.containerId !== "mainLayer") {
30392
32426
  const containerNode = stage.findOne(`#${snap.containerId}`);
30393
32427
  if (containerNode) {
30394
- const containerPos = containerNode.getClientRect({ relativeTo: stage });
30395
- value = containerPos.x + snap.guide;
32428
+ const canvasPt = containerNode.getAbsoluteTransform().point({
32429
+ x: snap.guide,
32430
+ y: 0
32431
+ });
32432
+ value = stage.getAbsoluteTransform().copy().invert().point(canvasPt).x;
30396
32433
  }
30397
32434
  }
30398
32435
  this.layer.add(new Konva.Line({
@@ -30415,8 +32452,11 @@ var WeaveNodesSnappingGuides = class {
30415
32452
  if (snap.containerId !== "mainLayer") {
30416
32453
  const containerNode = stage.findOne(`#${snap.containerId}`);
30417
32454
  if (containerNode) {
30418
- const containerPos = containerNode.getClientRect({ relativeTo: stage });
30419
- value = containerPos.y + snap.guide;
32455
+ const canvasPt = containerNode.getAbsoluteTransform().point({
32456
+ x: 0,
32457
+ y: snap.guide
32458
+ });
32459
+ value = stage.getAbsoluteTransform().copy().invert().point(canvasPt).y;
30420
32460
  }
30421
32461
  }
30422
32462
  this.layer.add(new Konva.Line({
@@ -30850,14 +32890,20 @@ var WeaveNodesSnappingPlugin = class extends WeavePlugin {
30850
32890
  }
30851
32891
  return updatedBox;
30852
32892
  };
30853
- const bindedBoundingBoxFunc = boundingBoxFunc.bind(this);
30854
- tr.boundBoxFunc(bindedBoundingBoxFunc);
32893
+ const snapBoundingBoxFunc = boundingBoxFunc.bind(this);
32894
+ const newBoundFunc = (oldBox, newBox) => {
32895
+ const mainBoundBoxFunc = nodesSelectionPlugin.getBoundBoxFunc();
32896
+ const actualBox = mainBoundBoxFunc(oldBox, newBox);
32897
+ if (actualBox === oldBox) return actualBox;
32898
+ return snapBoundingBoxFunc(oldBox, newBox);
32899
+ };
32900
+ tr.boundBoxFunc(newBoundFunc);
30855
32901
  }
30856
32902
  transformEndHandler() {
30857
32903
  const nodesSelectionPlugin = this.getNodesSelectionPlugin();
30858
32904
  if (nodesSelectionPlugin) {
30859
32905
  const tr = nodesSelectionPlugin.getTransformer();
30860
- tr.boundBoxFunc(void 0);
32906
+ tr.boundBoxFunc(nodesSelectionPlugin.getBoundBoxFunc());
30861
32907
  }
30862
32908
  this.snappingGuides = [];
30863
32909
  }
@@ -30876,6 +32922,10 @@ var WeaveNodesSnappingPlugin = class extends WeavePlugin {
30876
32922
  this.relativeToId = containerNode.id();
30877
32923
  }
30878
32924
  }
32925
+ if (container !== this.instance.getMainLayer() && !container.getAttrs().nodeId && container.getAttrs().nodeType === "group") {
32926
+ this.relativeTo = container;
32927
+ this.relativeToId = container.id();
32928
+ }
30879
32929
  if (!this.relativeTo) return;
30880
32930
  this.visibleNodes = getVisibleNodes({
30881
32931
  instance: this.instance,
@@ -30906,8 +32956,8 @@ var WeaveNodesSnappingPlugin = class extends WeavePlugin {
30906
32956
  y: -1 * (relativeTo.getAttrs().containerCompensationY ?? 0)
30907
32957
  };
30908
32958
  const diff = {
30909
- x: Math.abs(nodeBox.x - node.x()),
30910
- y: Math.abs(nodeBox.y - node.y())
32959
+ x: node.x() - nodeBox.x,
32960
+ y: node.y() - nodeBox.y
30911
32961
  };
30912
32962
  this.selectionOffsets.push({
30913
32963
  x: nodeBox.x - nodesBox.x + diff.x + containerCompensation.x,
@@ -31066,4 +33116,4 @@ const setupCanvasBackend = async () => {
31066
33116
  };
31067
33117
 
31068
33118
  //#endregion
31069
- export { ALIGN_NODES_ALIGN_TO, ALIGN_NODES_TOOL_ACTION_NAME, ALIGN_NODES_TOOL_STATE, BRUSH_TOOL_ACTION_NAME, BRUSH_TOOL_DEFAULT_CONFIG, BRUSH_TOOL_STATE, CONNECTOR_TOOL_ACTION_NAME, CONNECTOR_TOOL_DEFAULT_CONFIG, CONNECTOR_TOOL_STATE, COPY_PASTE_NODES_PLUGIN_STATE, DEFAULT_GUIDE_TOOL_ACTION_CONFIG, DEFAULT_SNAPPING_MANAGER_CONFIG, ELLIPSE_TOOL_ACTION_NAME, ELLIPSE_TOOL_STATE, ERASER_TOOL_ACTION_NAME, ERASER_TOOL_STATE, FRAME_TOOL_ACTION_NAME, FRAME_TOOL_STATE, GUIDE_DISTANCE_NAME, GUIDE_DISTANCE_ORIGIN, GUIDE_KIND, GUIDE_NAME, GUIDE_ORIENTATION, GUIDE_STATE, GUIDE_TOOL_ACTION_NAME, GUIDE_TOOL_STATE, LINE_TOOL_ACTION_NAME, LINE_TOOL_DEFAULT_CONFIG, LINE_TOOL_STATE, MEASURE_TOOL_ACTION_NAME, MEASURE_TOOL_STATE, MOVE_ORIENTATION, MOVE_TOOL_ACTION_NAME, MOVE_TOOL_STATE, PEN_TOOL_ACTION_NAME, PEN_TOOL_STATE, RECTANGLE_TOOL_ACTION_NAME, RECTANGLE_TOOL_STATE, REGULAR_POLYGON_TOOL_ACTION_NAME, REGULAR_POLYGON_TOOL_STATE, SELECTION_TOOL_ACTION_NAME, SELECTION_TOOL_STATE, STAGE_MINIMAP_DEFAULT_CONFIG, STAR_TOOL_ACTION_NAME, STAR_TOOL_STATE, TEXT_LAYOUT, TEXT_TOOL_ACTION_NAME, TEXT_TOOL_STATE, VIDEO_TOOL_ACTION_NAME, VIDEO_TOOL_STATE, WEAVE_ARROW_NODE_TYPE, WEAVE_ARROW_TOOL_ACTION_NAME, WEAVE_ARROW_TOOL_STATE, WEAVE_COMMENTS_RENDERER_KEY, WEAVE_COMMENTS_TOOL_LAYER_ID, WEAVE_COMMENT_CREATE_ACTION, WEAVE_COMMENT_NODE_ACTION, WEAVE_COMMENT_NODE_DEFAULTS, WEAVE_COMMENT_NODE_TYPE, WEAVE_COMMENT_STATUS, WEAVE_COMMENT_TOOL_ACTION_NAME, WEAVE_COMMENT_TOOL_DEFAULT_CONFIG, WEAVE_COMMENT_TOOL_STATE, WEAVE_COMMENT_VIEW_ACTION, WEAVE_CONNECTOR_NODE_ANCHOR_ORIGIN, WEAVE_CONNECTOR_NODE_DECORATOR_TYPE, WEAVE_CONNECTOR_NODE_DEFAULT_CONFIG, WEAVE_CONNECTOR_NODE_LINE_ORIGIN, WEAVE_CONNECTOR_NODE_LINE_TYPE, WEAVE_CONNECTOR_NODE_TYPE, WEAVE_COPY_PASTE_CONFIG_DEFAULT, WEAVE_COPY_PASTE_NODES_KEY, WEAVE_COPY_PASTE_PASTE_CATCHER_ID, WEAVE_COPY_PASTE_PASTE_MODES, WEAVE_DEFAULT_USER_INFO_FUNCTION, WEAVE_ELLIPSE_NODE_TYPE, WEAVE_FRAME_DEFAULT_BACKGROUND_COLOR, WEAVE_FRAME_NODE_DEFAULT_CONFIG, WEAVE_FRAME_NODE_DEFAULT_PROPS, WEAVE_FRAME_NODE_TYPE, WEAVE_GRID_DEFAULT_CONFIG, WEAVE_GRID_DOT_TYPES, WEAVE_GRID_LAYER_ID, WEAVE_GRID_TYPES, WEAVE_GROUP_NODE_TYPE, WEAVE_IMAGES_TOOL_ACTION_NAME, WEAVE_IMAGES_TOOL_DEFAULT_CONFIG, WEAVE_IMAGES_TOOL_STATE, WEAVE_IMAGES_TOOL_UPLOAD_TYPE, WEAVE_IMAGE_CROP_ANCHOR_POSITION, WEAVE_IMAGE_CROP_END_TYPE, WEAVE_IMAGE_DEFAULT_CONFIG, WEAVE_IMAGE_NODE_TYPE, WEAVE_IMAGE_TOOL_ACTION_NAME, WEAVE_IMAGE_TOOL_CONFIG_DEFAULT, WEAVE_IMAGE_TOOL_STATE, WEAVE_IMAGE_TOOL_UPLOAD_TYPE, WEAVE_LAYER_NODE_TYPE, WEAVE_LINE_NODE_DEFAULT_CONFIG, WEAVE_LINE_NODE_TYPE, WEAVE_MEASURE_NODE_DEFAULT_CONFIG, WEAVE_MEASURE_NODE_TYPE, WEAVE_MEASURE_TOOL_DEFAULT_CONFIG, WEAVE_NODES_MULTI_SELECTION_FEEDBACK_PLUGIN_DEFAULT_CONFIG, WEAVE_NODES_MULTI_SELECTION_FEEDBACK_PLUGIN_KEY, WEAVE_NODES_MULTI_SELECTION_FEEDBACK_PLUGIN_LAYER_ID, WEAVE_NODES_SELECTION_DEFAULT_CONFIG, WEAVE_NODES_SELECTION_KEY, WEAVE_NODES_SELECTION_LAYER_ID, WEAVE_NODES_SNAPPING_PLUGIN_KEY, WEAVE_RECTANGLE_NODE_TYPE, WEAVE_REGULAR_POLYGON_NODE_TYPE, WEAVE_STAGE_DEFAULT_MODE, WEAVE_STAGE_DROP_AREA_KEY, WEAVE_STAGE_GRID_PLUGIN_KEY, WEAVE_STAGE_IMAGE_CROPPING_MODE, WEAVE_STAGE_KEYBOARD_MOVE_DEFAULT_CONFIG, WEAVE_STAGE_KEYBOARD_MOVE_KEY, WEAVE_STAGE_KEYBOARD_MOVE_ORIENTATION, WEAVE_STAGE_MINIMAP_KEY, WEAVE_STAGE_NODE_TYPE, WEAVE_STAGE_PANNING_DEFAULT_CONFIG, WEAVE_STAGE_PANNING_KEY, WEAVE_STAGE_PANNING_THROTTLE_MS, WEAVE_STAGE_TEXT_EDITION_MODE, WEAVE_STAGE_ZOOM_DEFAULT_CONFIG, WEAVE_STAGE_ZOOM_KEY, WEAVE_STAGE_ZOOM_TYPE, WEAVE_STAR_NODE_TYPE, WEAVE_STROKE_NODE_DEFAULT_CONFIG, WEAVE_STROKE_NODE_TYPE, WEAVE_STROKE_SINGLE_NODE_DEFAULT_CONFIG, WEAVE_STROKE_SINGLE_NODE_TIP_SIDE, WEAVE_STROKE_SINGLE_NODE_TIP_TYPE, WEAVE_STROKE_SINGLE_NODE_TYPE, WEAVE_STROKE_TOOL_ACTION_NAME, WEAVE_STROKE_TOOL_ACTION_NAME_ALIASES, WEAVE_STROKE_TOOL_DEFAULT_CONFIG, WEAVE_STROKE_TOOL_STATE, WEAVE_TEXT_NODE_DEFAULT_CONFIG, WEAVE_TEXT_NODE_TYPE, WEAVE_USERS_POINTERS_CONFIG_DEFAULT_PROPS, WEAVE_USERS_POINTERS_KEY, WEAVE_USERS_PRESENCE_CONFIG_DEFAULT_PROPS, WEAVE_USERS_PRESENCE_PLUGIN_KEY, WEAVE_USERS_SELECTION_KEY, WEAVE_USER_POINTER_KEY, WEAVE_USER_PRESENCE_KEY, WEAVE_USER_SELECTION_KEY, WEAVE_VIDEO_DEFAULT_CONFIG, WEAVE_VIDEO_NODE_TYPE, Weave, WeaveAction, WeaveAlignNodesToolAction, WeaveArrowNode, WeaveArrowToolAction, WeaveBrushToolAction, WeaveCommentNode, WeaveCommentToolAction, WeaveCommentsRendererPlugin, WeaveConnectedUsersPlugin, WeaveConnectorNode, WeaveConnectorToolAction, WeaveContextMenuPlugin, WeaveCopyPasteNodesPlugin, WeaveEllipseNode, WeaveEllipseToolAction, WeaveEraserToolAction, WeaveExportNodesToolAction, WeaveExportStageToolAction, WeaveFitToScreenToolAction, WeaveFitToSelectionToolAction, WeaveFrameNode, WeaveFrameToolAction, WeaveGroupNode, WeaveGuideToolAction, WeaveImageNode, WeaveImageToolAction, WeaveImagesToolAction, WeaveLayerNode, WeaveLineNode, WeaveLineToolAction, WeaveMeasureNode, WeaveMeasureToolAction, WeaveMoveToolAction, WeaveNode, WeaveNodesMultiSelectionFeedbackPlugin, WeaveNodesSelectionPlugin, WeaveNodesSnappingPlugin, WeavePenToolAction, WeavePlugin, WeaveRectangleNode, WeaveRectangleToolAction, WeaveRegularPolygonNode, WeaveRegularPolygonToolAction, WeaveRenderer, WeaveSelectionToolAction, WeaveStageDropAreaPlugin, WeaveStageGridPlugin, WeaveStageKeyboardMovePlugin, WeaveStageMinimapPlugin, WeaveStageNode, WeaveStagePanningPlugin, WeaveStageResizePlugin, WeaveStageZoomPlugin, WeaveStarNode, WeaveStarToolAction, WeaveStateManipulation, WeaveStore, WeaveStrokeNode, WeaveStrokeSingleNode, WeaveStrokeToolAction, WeaveTextNode, WeaveTextToolAction, WeaveUsersPointersPlugin, WeaveUsersPresencePlugin, WeaveUsersSelectionPlugin, WeaveVideoNode, WeaveVideoToolAction, WeaveZoomInToolAction, WeaveZoomOutToolAction, canComposite, clearContainerTargets, containerOverCursor, containsNodeDeep, defaultInitialState, downscaleImageFile, downscaleImageFromURL, getBoundingBox, getDownscaleRatio, getExportBoundingBox, getImageSizeFromFile, getSelectedNodesMetadata, getStageClickPoint, getTargetAndSkipNodes, getTargetedNode, getTopmostShadowHost, getVisibleNodes, getVisibleNodesInViewport, hasFrames, hasImages, intersectArrays, isIOS, isInShadowDOM, isNodeInSelection, isNumber, isServer, loadImageSource, memoize, mergeExceptArrays, moveNodeToContainer, moveNodeToContainerNT, resetScale, setupCanvasBackend, setupSkiaBackend };
33119
+ export { ALIGN_NODES_ALIGN_TO, ALIGN_NODES_TOOL_ACTION_NAME, ALIGN_NODES_TOOL_STATE, BRUSH_TOOL_ACTION_NAME, BRUSH_TOOL_DEFAULT_CONFIG, BRUSH_TOOL_STATE, CONNECTOR_TOOL_ACTION_NAME, CONNECTOR_TOOL_DEFAULT_CONFIG, CONNECTOR_TOOL_STATE, COPY_PASTE_NODES_PLUGIN_STATE, DEFAULT_GUIDE_TOOL_ACTION_CONFIG, DEFAULT_SNAPPING_MANAGER_CONFIG, ELLIPSE_TOOL_ACTION_NAME, ELLIPSE_TOOL_STATE, ERASER_TOOL_ACTION_NAME, ERASER_TOOL_STATE, FRAME_TOOL_ACTION_NAME, FRAME_TOOL_STATE, GUIDE_DISTANCE_NAME, GUIDE_DISTANCE_ORIGIN, GUIDE_KIND, GUIDE_NAME, GUIDE_ORIENTATION, GUIDE_STATE, GUIDE_TOOL_ACTION_NAME, GUIDE_TOOL_STATE, LINE_TOOL_ACTION_NAME, LINE_TOOL_DEFAULT_CONFIG, LINE_TOOL_STATE, MEASURE_TOOL_ACTION_NAME, MEASURE_TOOL_STATE, MOVE_ORIENTATION, MOVE_TOOL_ACTION_NAME, MOVE_TOOL_STATE, PEN_TOOL_ACTION_NAME, PEN_TOOL_STATE, POLYGON_TOOL_ACTION_NAME, POLYGON_TOOL_STATE, RECTANGLE_TOOL_ACTION_NAME, RECTANGLE_TOOL_STATE, REGULAR_POLYGON_TOOL_ACTION_NAME, REGULAR_POLYGON_TOOL_STATE, SELECTION_TOOL_ACTION_NAME, SELECTION_TOOL_STATE, STAGE_MINIMAP_DEFAULT_CONFIG, STAR_TOOL_ACTION_NAME, STAR_TOOL_STATE, TEXT_LAYOUT, TEXT_TOOL_ACTION_NAME, TEXT_TOOL_STATE, VIDEO_TOOL_ACTION_NAME, VIDEO_TOOL_STATE, WEAVE_ARROW_NODE_TYPE, WEAVE_ARROW_TOOL_ACTION_NAME, WEAVE_ARROW_TOOL_STATE, WEAVE_COMMENTS_RENDERER_KEY, WEAVE_COMMENTS_TOOL_LAYER_ID, WEAVE_COMMENT_CREATE_ACTION, WEAVE_COMMENT_NODE_ACTION, WEAVE_COMMENT_NODE_DEFAULTS, WEAVE_COMMENT_NODE_TYPE, WEAVE_COMMENT_STATUS, WEAVE_COMMENT_TOOL_ACTION_NAME, WEAVE_COMMENT_TOOL_DEFAULT_CONFIG, WEAVE_COMMENT_TOOL_STATE, WEAVE_COMMENT_VIEW_ACTION, WEAVE_CONNECTOR_NODE_ANCHOR_ORIGIN, WEAVE_CONNECTOR_NODE_DECORATOR_TYPE, WEAVE_CONNECTOR_NODE_DEFAULT_CONFIG, WEAVE_CONNECTOR_NODE_LINE_ORIGIN, WEAVE_CONNECTOR_NODE_LINE_TYPE, WEAVE_CONNECTOR_NODE_TYPE, WEAVE_COPY_PASTE_CONFIG_DEFAULT, WEAVE_COPY_PASTE_NODES_KEY, WEAVE_COPY_PASTE_PASTE_CATCHER_ID, WEAVE_COPY_PASTE_PASTE_MODES, WEAVE_DEFAULT_USER_INFO_FUNCTION, WEAVE_ELLIPSE_NODE_TYPE, WEAVE_FRAME_DEFAULT_BACKGROUND_COLOR, WEAVE_FRAME_NODE_DEFAULT_CONFIG, WEAVE_FRAME_NODE_DEFAULT_PROPS, WEAVE_FRAME_NODE_TYPE, WEAVE_GRID_DEFAULT_CONFIG, WEAVE_GRID_DOT_TYPES, WEAVE_GRID_LAYER_ID, WEAVE_GRID_TYPES, WEAVE_GROUP_NODE_TYPE, WEAVE_IMAGES_TOOL_ACTION_NAME, WEAVE_IMAGES_TOOL_DEFAULT_CONFIG, WEAVE_IMAGES_TOOL_STATE, WEAVE_IMAGES_TOOL_UPLOAD_TYPE, WEAVE_IMAGE_CROP_ANCHOR_POSITION, WEAVE_IMAGE_CROP_END_TYPE, WEAVE_IMAGE_DEFAULT_CONFIG, WEAVE_IMAGE_NODE_TYPE, WEAVE_IMAGE_TOOL_ACTION_NAME, WEAVE_IMAGE_TOOL_CONFIG_DEFAULT, WEAVE_IMAGE_TOOL_STATE, WEAVE_IMAGE_TOOL_UPLOAD_TYPE, WEAVE_LAYER_NODE_TYPE, WEAVE_LINE_NODE_DEFAULT_CONFIG, WEAVE_LINE_NODE_TYPE, WEAVE_MEASURE_NODE_DEFAULT_CONFIG, WEAVE_MEASURE_NODE_TYPE, WEAVE_MEASURE_TOOL_DEFAULT_CONFIG, WEAVE_NODES_MULTI_SELECTION_FEEDBACK_PLUGIN_DEFAULT_CONFIG, WEAVE_NODES_MULTI_SELECTION_FEEDBACK_PLUGIN_KEY, WEAVE_NODES_MULTI_SELECTION_FEEDBACK_PLUGIN_LAYER_ID, WEAVE_NODES_SELECTION_DEFAULT_CONFIG, WEAVE_NODES_SELECTION_KEY, WEAVE_NODES_SELECTION_LAYER_ID, WEAVE_NODES_SNAPPING_PLUGIN_KEY, WEAVE_POLYGON_NODE_TYPE, WEAVE_POLYGON_PRESETS, WEAVE_RECTANGLE_NODE_TYPE, WEAVE_REGULAR_POLYGON_NODE_TYPE, WEAVE_SHAPE_LABEL_DEFAULTS, WEAVE_STAGE_DEFAULT_MODE, WEAVE_STAGE_DROP_AREA_KEY, WEAVE_STAGE_GRID_PLUGIN_KEY, WEAVE_STAGE_IMAGE_CROPPING_MODE, WEAVE_STAGE_KEYBOARD_MOVE_DEFAULT_CONFIG, WEAVE_STAGE_KEYBOARD_MOVE_KEY, WEAVE_STAGE_KEYBOARD_MOVE_ORIENTATION, WEAVE_STAGE_MINIMAP_KEY, WEAVE_STAGE_NODE_TYPE, WEAVE_STAGE_PANNING_DEFAULT_CONFIG, WEAVE_STAGE_PANNING_KEY, WEAVE_STAGE_PANNING_THROTTLE_MS, WEAVE_STAGE_SHAPE_LABEL_EDITION_MODE, WEAVE_STAGE_TEXT_EDITION_MODE, WEAVE_STAGE_ZOOM_DEFAULT_CONFIG, WEAVE_STAGE_ZOOM_KEY, WEAVE_STAGE_ZOOM_TYPE, WEAVE_STAR_NODE_TYPE, WEAVE_STROKE_NODE_DEFAULT_CONFIG, WEAVE_STROKE_NODE_TYPE, WEAVE_STROKE_SINGLE_NODE_DEFAULT_CONFIG, WEAVE_STROKE_SINGLE_NODE_TIP_SIDE, WEAVE_STROKE_SINGLE_NODE_TIP_TYPE, WEAVE_STROKE_SINGLE_NODE_TYPE, WEAVE_STROKE_TOOL_ACTION_NAME, WEAVE_STROKE_TOOL_ACTION_NAME_ALIASES, WEAVE_STROKE_TOOL_DEFAULT_CONFIG, WEAVE_STROKE_TOOL_STATE, WEAVE_TEXT_NODE_DEFAULT_CONFIG, WEAVE_TEXT_NODE_TYPE, WEAVE_USERS_POINTERS_CONFIG_DEFAULT_PROPS, WEAVE_USERS_POINTERS_KEY, WEAVE_USERS_PRESENCE_CONFIG_DEFAULT_PROPS, WEAVE_USERS_PRESENCE_PLUGIN_KEY, WEAVE_USERS_SELECTION_KEY, WEAVE_USER_POINTER_KEY, WEAVE_USER_PRESENCE_KEY, WEAVE_USER_SELECTION_KEY, WEAVE_VIDEO_DEFAULT_CONFIG, WEAVE_VIDEO_NODE_TYPE, Weave, WeaveAction, WeaveAlignNodesToolAction, WeaveArrowNode, WeaveArrowToolAction, WeaveBrushToolAction, WeaveCommentNode, WeaveCommentToolAction, WeaveCommentsRendererPlugin, WeaveConnectedUsersPlugin, WeaveConnectorNode, WeaveConnectorToolAction, WeaveContextMenuPlugin, WeaveCopyPasteNodesPlugin, WeaveEllipseNode, WeaveEllipseToolAction, WeaveEraserToolAction, WeaveExportNodesToolAction, WeaveExportStageToolAction, WeaveFitToScreenToolAction, WeaveFitToSelectionToolAction, WeaveFrameNode, WeaveFrameToolAction, WeaveGroupNode, WeaveGuideToolAction, WeaveImageNode, WeaveImageToolAction, WeaveImagesToolAction, WeaveLayerNode, WeaveLineNode, WeaveLineToolAction, WeaveMeasureNode, WeaveMeasureToolAction, WeaveMoveToolAction, WeaveNode, WeaveNodesMultiSelectionFeedbackPlugin, WeaveNodesSelectionPlugin, WeaveNodesSnappingPlugin, WeavePenToolAction, WeavePlugin, WeavePolygonNode, WeavePolygonToolAction, WeaveRectangleNode, WeaveRectangleToolAction, WeaveRegularPolygonNode, WeaveRegularPolygonToolAction, WeaveRenderer, WeaveSelectionToolAction, WeaveStageDropAreaPlugin, WeaveStageGridPlugin, WeaveStageKeyboardMovePlugin, WeaveStageMinimapPlugin, WeaveStageNode, WeaveStagePanningPlugin, WeaveStageResizePlugin, WeaveStageZoomPlugin, WeaveStarNode, WeaveStarToolAction, WeaveStateManipulation, WeaveStore, WeaveStrokeNode, WeaveStrokeSingleNode, WeaveStrokeToolAction, WeaveTextNode, WeaveTextToolAction, WeaveUsersPointersPlugin, WeaveUsersPresencePlugin, WeaveUsersSelectionPlugin, WeaveVideoNode, WeaveVideoToolAction, WeaveZoomInToolAction, WeaveZoomOutToolAction, buildAncestorGroupIds, canComposite, clearContainerTargets, computeEllipseLabelMinSize, computePolygonLabelMinSize, computeRectangleLabelMinSize, containerOverCursor, containsNodeDeep, defaultInitialState, downscaleImageFile, downscaleImageFromURL, getBoundingBox, getDownscaleRatio, getExportBoundingBox, getImageSizeFromFile, getSelectedNodesMetadata, getShapeLabelSchemaFields, getStageClickPoint, getTargetAndSkipNodes, getTargetedNode, getTopmostShadowHost, getVisibleNodes, getVisibleNodesInViewport, hasFrames, hasImages, instantiatePreset, intersectArrays, isIOS, isInShadowDOM, isNodeInSelection, isNumber, isServer, labelId, loadImageSource, memoize, mergeExceptArrays, moveNodeToContainer, moveNodeToContainerNT, resetScale, setupCanvasBackend, setupSkiaBackend, spreadLabelProps };