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