@inditextech/weave-sdk 5.5.0 → 5.7.0-SNAPSHOT.595.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/types.js CHANGED
@@ -5937,6 +5937,7 @@ const MOVE_TOOL_STATE = {
5937
5937
  };
5938
5938
  //#endregion
5939
5939
  //#region src/nodes/node.ts
5940
+ const nodeInstanceListeners = /* @__PURE__ */ new WeakMap();
5940
5941
  const augmentKonvaNodeClass = (config) => {
5941
5942
  const { transform } = config ?? {};
5942
5943
  Konva.Node.prototype.getTransformerProperties = function() {
@@ -5952,6 +5953,8 @@ const augmentKonvaNodeClass = (config) => {
5952
5953
  Konva.Node.prototype.triggerCrop = function() {};
5953
5954
  Konva.Node.prototype.closeCrop = function() {};
5954
5955
  Konva.Node.prototype.resetCrop = function() {};
5956
+ Konva.Node.prototype.flipHorizontal = function() {};
5957
+ Konva.Node.prototype.flipVertical = function() {};
5955
5958
  Konva.Node.prototype.dblClick = function() {};
5956
5959
  Konva.Node.prototype.click = function() {};
5957
5960
  Konva.Node.prototype.allowedAnchors = function() {
@@ -6037,6 +6040,8 @@ var WeaveNode = class {
6037
6040
  };
6038
6041
  node.updatePosition = function() {};
6039
6042
  node.resetCrop = function() {};
6043
+ node.flipHorizontal = function() {};
6044
+ node.flipVertical = function() {};
6040
6045
  node.handleMouseover = function() {};
6041
6046
  node.handleMouseout = function() {};
6042
6047
  node.handleSelectNode = function() {};
@@ -6207,7 +6212,7 @@ var WeaveNode = class {
6207
6212
  node.draggable(false);
6208
6213
  };
6209
6214
  if (node.getAttrs().locked ?? false) {
6210
- this.instance.removeEventListener("onNodesChange", handleNodesChange);
6215
+ this.removeNodeInstanceListeners(node, false);
6211
6216
  node.off("transformstart");
6212
6217
  node.off("transform");
6213
6218
  node.off("transformend");
@@ -6217,7 +6222,7 @@ var WeaveNode = class {
6217
6222
  node.off("pointerover");
6218
6223
  node.off("pointerleave");
6219
6224
  } else {
6220
- this.instance.addEventListener("onNodesChange", handleNodesChange);
6225
+ this.setNodeInstanceListener(node, "onNodesChange", handleNodesChange);
6221
6226
  node.off("transformstart");
6222
6227
  node.on("transformstart", (e) => {
6223
6228
  if (e.target.getAttrs().strokeScaleEnabled !== false) {
@@ -6276,7 +6281,7 @@ var WeaveNode = class {
6276
6281
  });
6277
6282
  const stage = this.instance.getStage();
6278
6283
  let originalPosition = null;
6279
- this.instance.addEventListener("onSelectionState", (state) => {
6284
+ this.setNodeInstanceListener(node, "onSelectionState", (state) => {
6280
6285
  const selectedNodes = this.getSelectionPlugin()?.getSelectedNodes() ?? [];
6281
6286
  if (!state && selectedNodes?.some((n) => n.getAttrs().id === node.getAttrs().id)) originalPosition = node.getAbsolutePosition();
6282
6287
  });
@@ -6592,8 +6597,39 @@ var WeaveNode = class {
6592
6597
  async onRegister() {}
6593
6598
  onAdd(nodeInstance) {}
6594
6599
  onDestroy(nodeInstance) {
6600
+ this.removeNodeInstanceListeners(nodeInstance);
6595
6601
  nodeInstance.destroy();
6596
6602
  }
6603
+ /**
6604
+ * Registers a Weave instance listener on behalf of a node. Only one listener
6605
+ * is kept per node and event: registering the same event again for the same
6606
+ * node replaces the previous listener, including the ones registered by
6607
+ * setupDefaultNodeEvents (onNodesChange, onSelectionState).
6608
+ */
6609
+ setNodeInstanceListener(node, event, callback) {
6610
+ let listeners = nodeInstanceListeners.get(node);
6611
+ if (!listeners) {
6612
+ listeners = /* @__PURE__ */ new Map();
6613
+ nodeInstanceListeners.set(node, listeners);
6614
+ }
6615
+ const previous = listeners.get(event);
6616
+ if (previous) this.instance.removeEventListener(event, previous);
6617
+ listeners.set(event, callback);
6618
+ this.instance.addEventListener(event, callback);
6619
+ }
6620
+ /**
6621
+ * Removes the listeners registered with setNodeInstanceListener for the node
6622
+ * and, unless disabled, for its descendants.
6623
+ */
6624
+ removeNodeInstanceListeners(node, includeDescendants = true) {
6625
+ const nodes = includeDescendants && node instanceof Konva.Container ? [node, ...node.find(() => true)] : [node];
6626
+ for (const actNode of nodes) {
6627
+ const listeners = nodeInstanceListeners.get(actNode);
6628
+ if (!listeners) continue;
6629
+ for (const [event, callback] of listeners) this.instance.removeEventListener(event, callback);
6630
+ nodeInstanceListeners.delete(actNode);
6631
+ }
6632
+ }
6597
6633
  onDestroyInstance() {}
6598
6634
  serialize(instance) {
6599
6635
  const attrs = instance.getAttrs();
@@ -9466,7 +9502,7 @@ var WeaveRegisterManager = class {
9466
9502
  };
9467
9503
  //#endregion
9468
9504
  //#region package.json
9469
- var version = "5.5.0";
9505
+ var version = "5.7.0-SNAPSHOT.595.1";
9470
9506
  //#endregion
9471
9507
  //#region src/managers/setup.ts
9472
9508
  var WeaveSetupManager = class {
@@ -11187,6 +11223,9 @@ var Weave = class {
11187
11223
  getBoundingBox(nodes, config) {
11188
11224
  return getBoundingBox(nodes, config);
11189
11225
  }
11226
+ flipImage(node, options) {
11227
+ this.getNodeHandler("image")?.flipImage(node, options);
11228
+ }
11190
11229
  stateTransactional(callback, origin) {
11191
11230
  this.stateManager.stateTransactional(callback, origin);
11192
11231
  }
@@ -14304,99 +14343,2084 @@ var WeaveTextNode = class extends WeaveNode {
14304
14343
  };
14305
14344
  }
14306
14345
  static addNodeState(defaultNodeState, props) {
14307
- return mergeExceptArrays(defaultNodeState, { props: {
14308
- x: props.x,
14309
- y: props.y,
14310
- width: props.width,
14311
- ...props.height && { height: props.height },
14312
- FontFamily: props.fontFamily,
14313
- fontSize: props.fontSize,
14314
- fontStyle: props.fontStyle,
14315
- fontVariant: props.fontVariant,
14316
- textDecoration: props.textDecoration,
14317
- letterSpacing: props.letterSpacing,
14318
- lineHeight: props.lineHeight,
14319
- align: props.align,
14320
- verticalAlign: props.verticalAlign,
14321
- rotation: props.rotation,
14322
- fill: props.fill,
14323
- text: props.text,
14324
- layout: props.layout,
14325
- ...props.strokeEnabled && { strokeEnabled: props.strokeEnabled },
14326
- ...props.stroke && { stroke: props.stroke },
14327
- ...props.strokeWidth && { strokeWidth: props.strokeWidth },
14328
- ...props.fillAfterStrokeEnabled && { fillAfterStrokeEnabled: props.fillAfterStrokeEnabled },
14329
- ...props.link && { link: props.link }
14330
- } });
14346
+ return mergeExceptArrays(defaultNodeState, { props: {
14347
+ x: props.x,
14348
+ y: props.y,
14349
+ width: props.width,
14350
+ ...props.height && { height: props.height },
14351
+ FontFamily: props.fontFamily,
14352
+ fontSize: props.fontSize,
14353
+ fontStyle: props.fontStyle,
14354
+ fontVariant: props.fontVariant,
14355
+ textDecoration: props.textDecoration,
14356
+ letterSpacing: props.letterSpacing,
14357
+ lineHeight: props.lineHeight,
14358
+ align: props.align,
14359
+ verticalAlign: props.verticalAlign,
14360
+ rotation: props.rotation,
14361
+ fill: props.fill,
14362
+ text: props.text,
14363
+ layout: props.layout,
14364
+ ...props.strokeEnabled && { strokeEnabled: props.strokeEnabled },
14365
+ ...props.stroke && { stroke: props.stroke },
14366
+ ...props.strokeWidth && { strokeWidth: props.strokeWidth },
14367
+ ...props.fillAfterStrokeEnabled && { fillAfterStrokeEnabled: props.fillAfterStrokeEnabled },
14368
+ ...props.link && { link: props.link }
14369
+ } });
14370
+ }
14371
+ static updateNodeState(prevNodeState, nextProps) {
14372
+ return mergeExceptArrays(prevNodeState, { props: {
14373
+ x: nextProps.x,
14374
+ y: nextProps.y,
14375
+ width: nextProps.width,
14376
+ ...nextProps.height && { height: nextProps.height },
14377
+ FontFamily: nextProps.fontFamily,
14378
+ fontSize: nextProps.fontSize,
14379
+ fontStyle: nextProps.fontStyle,
14380
+ fontVariant: nextProps.fontVariant,
14381
+ textDecoration: nextProps.textDecoration,
14382
+ letterSpacing: nextProps.letterSpacing,
14383
+ lineHeight: nextProps.lineHeight,
14384
+ align: nextProps.align,
14385
+ verticalAlign: nextProps.verticalAlign,
14386
+ rotation: nextProps.rotation,
14387
+ fill: nextProps.fill,
14388
+ text: nextProps.text,
14389
+ layout: nextProps.layout,
14390
+ ...nextProps.strokeEnabled && { strokeEnabled: nextProps.strokeEnabled },
14391
+ ...nextProps.stroke && { stroke: nextProps.stroke },
14392
+ ...nextProps.strokeWidth && { strokeWidth: nextProps.strokeWidth },
14393
+ ...nextProps.fillAfterStrokeEnabled && { fillAfterStrokeEnabled: nextProps.fillAfterStrokeEnabled },
14394
+ ...nextProps.link && { link: nextProps.link }
14395
+ } });
14396
+ }
14397
+ static getSchema() {
14398
+ return super.getSchema().extend({
14399
+ type: z.literal(WEAVE_TEXT_NODE_TYPE).describe(`Type of the node, for a text node it will always be "${WEAVE_TEXT_NODE_TYPE}"`),
14400
+ props: z.object({
14401
+ nodeType: z.literal(WEAVE_TEXT_NODE_TYPE).describe(`Type of the node, for a text node it will always be "${WEAVE_TEXT_NODE_TYPE}"`),
14402
+ width: z.number().describe("Width of the text in pixels"),
14403
+ height: z.number().optional().describe("Height of the text in pixels. Optional if layout is auto-height or smart."),
14404
+ fontFamily: z.string().default("Arial").describe("Font family of the text, e.g. Arial, Helvetica, etc."),
14405
+ fontSize: z.number().default(16).describe("Font size of the text in pixels."),
14406
+ fontStyle: z.string().regex(/^(?:normal|bold|\d+)(?: italic)?$/).default("normal").describe("Font style of the text, can be \"normal\", \"bold\", \"400\", \"italic\" or a combination like \"bold italic\" or \"700 italic\"."),
14407
+ fontVariant: z.enum(["normal", "small-caps"]).describe("Font variant of the text, can be \"normal\" or \"small-caps\"."),
14408
+ textDecoration: z.enum([
14409
+ "line-through",
14410
+ "underline",
14411
+ ""
14412
+ ]).default("").describe("Text decoration can be \"line-through\", \"underline\" or empty string for none."),
14413
+ letterSpacing: z.number().default(0).describe("Spacing between letters in pixels."),
14414
+ lineHeight: z.number().default(1).describe("Line height of the text, as a multiplier of font size."),
14415
+ align: z.enum([
14416
+ "left",
14417
+ "center",
14418
+ "right",
14419
+ "justify"
14420
+ ]).default("left").describe("Text alignment, can be \"left\", \"center\", \"right\" or \"justify\"."),
14421
+ verticalAlign: z.enum([
14422
+ "top",
14423
+ "middle",
14424
+ "bottom"
14425
+ ]).default("top").describe("Vertical alignment of the text, can be 'top', 'middle' or 'bottom'."),
14426
+ fill: z.string().default("#000000ff").describe("Fill color of the text in hex format with alpha channel (e.g. #RRGGBBAA)."),
14427
+ text: z.string().default("text").describe("The actual text content of the node."),
14428
+ link: z.string().url().refine((value) => isValidTextLink(value), { message: "Link must be an absolute http:// or https:// URL." }).optional().describe("External URL the whole text of the node links to. When set, the text is forced underlined and coloured with the configured link colours regardless of fill/textDecoration, is clickable, and opens the URL in a new tab. Only http(s) URLs are accepted. Omit/remove to make the node plain text again."),
14429
+ linkPreviousFill: z.string().optional().describe("Internal bookkeeping: the fill colour the node had right before a link was set, captured automatically so it can be restored when the link is removed. Managed automatically — do not set directly."),
14430
+ linkPreviousTextDecoration: z.string().optional().describe("Internal bookkeeping: the textDecoration the node had right before a link was set, captured automatically so it can be restored when the link is removed. Managed automatically — do not set directly."),
14431
+ strokeEnabled: z.boolean().default(false).describe("Whether the text outline is enabled."),
14432
+ stroke: z.string().optional().default("#d6d6d6").describe("Color of the text outline in hex format with alpha channel (e.g. #RRGGBBAA)."),
14433
+ strokeWidth: z.number().optional().default(2).describe("Width of the text outline in pixels."),
14434
+ strokeScaleEnabled: z.boolean().default(true).describe("Whether the stroke width should scale when the node is scaled."),
14435
+ fillAfterStrokeEnabled: z.boolean().default(true).describe("Whether the fill should be drawn after the stroke. If false, the stroke will be drawn on top of the fill."),
14436
+ layout: z.enum(TEXT_LAYOUT).default(TEXT_LAYOUT.SMART).describe("Layout mode of the text node. Can be:\n- 'fixed': the text node will have fixed width and height, and the text will be scaled to fit the node.\n- 'auto-height': the width of the text node will be fixed, but the height will adjust to fit the text content.\n- 'auto-all': both width and height of the text node will adjust to fit the text content.\n- 'smart': the text node will try to adjust its size based on the content and layout, but it will not exceed the initial width and height set on the node.")
14437
+ })
14438
+ });
14439
+ }
14440
+ };
14441
+ //#endregion
14442
+ //#region src/nodes/text-v2/model.ts
14443
+ function isEmptyMarks(m) {
14444
+ return !m || Object.keys(m).length === 0;
14445
+ }
14446
+ function sameMarks(a, b) {
14447
+ if (isEmptyMarks(a) && isEmptyMarks(b)) return true;
14448
+ if (isEmptyMarks(a) || isEmptyMarks(b)) return false;
14449
+ const ka = Object.keys(a);
14450
+ const kb = Object.keys(b);
14451
+ return ka.length === kb.length && ka.every((k) => a[k] === b[k]);
14452
+ }
14453
+ /** Merge adjacent runs with equal marks and drop empty runs (keeps >= 0). */
14454
+ function normalizeRuns(runs) {
14455
+ const out = [];
14456
+ for (const run of runs) {
14457
+ if (run.t === "") continue;
14458
+ const last = out[out.length - 1];
14459
+ if (last && sameMarks(last.m, run.m)) last.t += run.t;
14460
+ else out.push({
14461
+ t: run.t,
14462
+ ...isEmptyMarks(run.m) ? {} : { m: run.m }
14463
+ });
14464
+ }
14465
+ return out;
14466
+ }
14467
+ /**
14468
+ * Synthesize blocks from legacy plain `text`. One run per paragraph; a
14469
+ * legacy whole-text `link` becomes a full-range `link` mark.
14470
+ */
14471
+ function synthesizeBlocks(text, link) {
14472
+ return (text ?? "").split("\n").map((paragraph) => ({ runs: paragraph === "" ? [] : [{
14473
+ t: paragraph,
14474
+ ...link ? { m: { link } } : {}
14475
+ }] }));
14476
+ }
14477
+ /** Resolve blocks of a node: stored `blocks` or lazily synthesized (no write). */
14478
+ function resolveBlocks(attrs) {
14479
+ if (Array.isArray(attrs.blocks) && attrs.blocks.length > 0) return attrs.blocks;
14480
+ return synthesizeBlocks(attrs.text ?? "", attrs.link);
14481
+ }
14482
+ function blockText(block) {
14483
+ return block.runs.map((r) => r.t).join("");
14484
+ }
14485
+ /** Derived mirror for the `text` attribute. */
14486
+ function blocksToText(blocks) {
14487
+ return blocks.map(blockText).join("\n");
14488
+ }
14489
+ /** Slice runs of a block between local offsets [from, to). */
14490
+ function sliceRuns(runs, from, to) {
14491
+ const out = [];
14492
+ let pos = 0;
14493
+ for (const run of runs) {
14494
+ const start = pos;
14495
+ const end = pos + run.t.length;
14496
+ pos = end;
14497
+ const s = Math.max(from, start);
14498
+ const e = Math.min(to, end);
14499
+ if (s < e) out.push({
14500
+ t: run.t.slice(s - start, e - start),
14501
+ m: run.m
14502
+ });
14503
+ }
14504
+ return out;
14505
+ }
14506
+ /** Marks of the run at (or immediately before) a local offset. */
14507
+ function marksAt(runs, offset) {
14508
+ let pos = 0;
14509
+ let found;
14510
+ for (const run of runs) {
14511
+ const end = pos + run.t.length;
14512
+ if (offset > pos && offset <= end) return run.m;
14513
+ if (offset === pos && !found) found = run.m;
14514
+ pos = end;
14515
+ }
14516
+ return found;
14517
+ }
14518
+ /**
14519
+ * Replace the global range [start, end) (newline counts as 1 char between
14520
+ * blocks) with plain `insert`. Marks on untouched text are preserved; the
14521
+ * inserted text inherits marks of the character before `start`.
14522
+ */
14523
+ function replaceRange(blocks, start, end, insert) {
14524
+ const total = blocksToText(blocks).length;
14525
+ start = Math.max(0, Math.min(start, total));
14526
+ end = Math.max(start, Math.min(end, total));
14527
+ let before = [];
14528
+ let after = [];
14529
+ const prefixBlocks = [];
14530
+ const suffixBlocks = [];
14531
+ let inheritedMarks;
14532
+ let pos = 0;
14533
+ blocks.forEach((block, i) => {
14534
+ const len = blockText(block).length;
14535
+ const bStart = pos;
14536
+ const bEnd = pos + len;
14537
+ pos = bEnd + 1;
14538
+ if (bEnd < start) prefixBlocks.push(block);
14539
+ else if (bStart > end) suffixBlocks.push(block);
14540
+ else {
14541
+ if (start >= bStart && start <= bEnd) {
14542
+ const local = start - bStart;
14543
+ before = sliceRuns(block.runs, 0, local);
14544
+ inheritedMarks = marksAt(block.runs, local);
14545
+ }
14546
+ if (end >= bStart && end <= bEnd) after = sliceRuns(block.runs, end - bStart, len);
14547
+ }
14548
+ });
14549
+ const lines = insert.split("\n");
14550
+ const mk = (t) => t === "" ? [] : [{
14551
+ t,
14552
+ ...isEmptyMarks(inheritedMarks) ? {} : { m: inheritedMarks }
14553
+ }];
14554
+ const middle = [];
14555
+ lines.forEach((line, idx) => {
14556
+ let runs = mk(line);
14557
+ if (idx === 0) runs = [...before, ...runs];
14558
+ if (idx === lines.length - 1) runs = [...runs, ...after];
14559
+ middle.push({ runs: normalizeRuns(runs) });
14560
+ });
14561
+ return [
14562
+ ...prefixBlocks,
14563
+ ...middle,
14564
+ ...suffixBlocks
14565
+ ];
14566
+ }
14567
+ //#endregion
14568
+ //#region src/nodes/text-v2/layout/engine.ts
14569
+ /** Quote multi-word families like `Konva` does so the font shorthand parses. */
14570
+ function normalizeFontFamily(fontFamily) {
14571
+ return fontFamily.split(",").map((family) => {
14572
+ family = family.trim();
14573
+ const hasSpace = family.includes(" ");
14574
+ const hasQuotes = family.includes("\"") || family.includes("'");
14575
+ return hasSpace && !hasQuotes ? `"${family}"` : family;
14576
+ }).join(", ");
14577
+ }
14578
+ function fontString(s) {
14579
+ return `${s.fontStyle} ${s.fontVariant} ${s.fontSize}px ${normalizeFontFamily(s.fontFamily)}`;
14580
+ }
14581
+ let sharedContext = null;
14582
+ /** Default measurer: works on browser, node-canvas and skia-canvas backends. */
14583
+ const canvasMeasurer = (font, text) => {
14584
+ sharedContext ??= Konva.Util.createCanvasElement().getContext("2d");
14585
+ sharedContext.font = font;
14586
+ return sharedContext.measureText(text).width;
14587
+ };
14588
+ const graphemeSegmenter = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : null;
14589
+ function splitGraphemes(text) {
14590
+ if (text === "") return [];
14591
+ if (graphemeSegmenter) return Array.from(graphemeSegmenter.segment(text), (s) => s.segment);
14592
+ return Array.from(text);
14593
+ }
14594
+ const now = () => typeof performance !== "undefined" ? performance.now() : Date.now();
14595
+ const WS = /\s/;
14596
+ const MAX_CACHE = 2e4;
14597
+ const MAX_CACHED_CHUNK = 256;
14598
+ var TextLayoutEngine = class {
14599
+ measure;
14600
+ cache = /* @__PURE__ */ new Map();
14601
+ paragraphs = [];
14602
+ /** Entries by signature, so paragraphs that shifted index are reused. */
14603
+ pool = /* @__PURE__ */ new Map();
14604
+ lastParagraphsRelaid = 0;
14605
+ constructor(measurer = canvasMeasurer) {
14606
+ this.measure = measurer;
14607
+ }
14608
+ /** Number of paragraphs recomputed by the last `layout()` call. */
14609
+ getLastRelaidCount() {
14610
+ return this.lastParagraphsRelaid;
14611
+ }
14612
+ clearCaches() {
14613
+ this.cache.clear();
14614
+ this.paragraphs = [];
14615
+ this.pool.clear();
14616
+ }
14617
+ paragraphSig(block, style, bounds) {
14618
+ const fixedWidth = bounds.width !== void 0 && !Number.isNaN(bounds.width);
14619
+ const maxWidth = (bounds.width ?? 0) - bounds.padding * 2;
14620
+ return `${fontString(style)}|${style.letterSpacing}|${style.wrap}|${fixedWidth ? maxWidth : "auto"}|${JSON.stringify(block.runs)}`;
14621
+ }
14622
+ /**
14623
+ * Pre-compute paragraph layouts for `blocks` within a time budget so a
14624
+ * following `layout()` is cheap. Call repeatedly until it returns true.
14625
+ * Lets callers time-slice very large content (e.g. bulk paste).
14626
+ */
14627
+ warm(blocks, style, bounds, budgetMs) {
14628
+ const font = fontString(style);
14629
+ const fixedWidth = bounds.width !== void 0 && !Number.isNaN(bounds.width);
14630
+ const maxWidth = (bounds.width ?? 0) - bounds.padding * 2;
14631
+ const deadline = now() + budgetMs;
14632
+ for (const block of blocks) {
14633
+ const sig = this.paragraphSig(block, style, bounds);
14634
+ if (this.pool.has(sig)) continue;
14635
+ this.pool.set(sig, this.layoutParagraph(block, sig, font, style, fixedWidth, maxWidth));
14636
+ if (now() >= deadline) return false;
14637
+ }
14638
+ return true;
14639
+ }
14640
+ measureChunk(font, text, spacing) {
14641
+ if (text === "") return 0;
14642
+ if (text.length > MAX_CACHED_CHUNK) return this.measure(font, text) + text.length * spacing;
14643
+ const key = `${font}\u0000${text}`;
14644
+ let w = this.cache.get(key);
14645
+ if (w === void 0) {
14646
+ w = this.measure(font, text);
14647
+ if (this.cache.size >= MAX_CACHE) this.cache.clear();
14648
+ this.cache.set(key, w);
14649
+ }
14650
+ return w + text.length * spacing;
14651
+ }
14652
+ layout(blocks, style, bounds) {
14653
+ const font = fontString(style);
14654
+ const padding = bounds.padding;
14655
+ const fixedWidth = bounds.width !== void 0 && !Number.isNaN(bounds.width);
14656
+ const fixedHeight = bounds.height !== void 0 && !Number.isNaN(bounds.height);
14657
+ const maxWidth = (bounds.width ?? 0) - padding * 2;
14658
+ const maxHeightPx = (bounds.height ?? 0) - padding * 2;
14659
+ const lineHeightPx = style.lineHeight * style.fontSize;
14660
+ const sigBase = `${font}|${style.letterSpacing}|${style.wrap}|${fixedWidth ? maxWidth : "auto"}`;
14661
+ const prev = this.paragraphs;
14662
+ const next = [];
14663
+ let relaid = 0;
14664
+ const lines = [];
14665
+ let textWidth = 0;
14666
+ let currentHeightPx = 0;
14667
+ let offset = 0;
14668
+ outer: for (let p = 0; p < blocks.length; p++) {
14669
+ const block = blocks[p];
14670
+ const sig = `${sigBase}|${JSON.stringify(block.runs)}`;
14671
+ let entry = prev[p]?.sig === sig ? prev[p] : this.pool.get(sig);
14672
+ if (!entry) {
14673
+ entry = this.layoutParagraph(block, sig, font, style, fixedWidth, maxWidth);
14674
+ relaid++;
14675
+ }
14676
+ next.push(entry);
14677
+ for (const pl of entry.lines) {
14678
+ const text = entry.graphemes.slice(pl.startG, pl.endG).join("");
14679
+ const segments = this.buildSegments(entry, block, pl, font, style);
14680
+ const width = segments.reduce((a, s) => a + s.width, 0);
14681
+ const startChar = entry.graphemes.slice(0, pl.startG).join("").length;
14682
+ lines.push({
14683
+ paragraph: p,
14684
+ start: offset + startChar,
14685
+ end: offset + startChar + text.length,
14686
+ text,
14687
+ width,
14688
+ measuredWidth: pl.measuredWidth,
14689
+ offsetX: 0,
14690
+ y: lines.length * lineHeightPx,
14691
+ segments,
14692
+ lastInParagraph: pl.lastInParagraph
14693
+ });
14694
+ textWidth = Math.max(textWidth, pl.measuredWidth);
14695
+ currentHeightPx += lineHeightPx;
14696
+ if (fixedHeight && currentHeightPx + lineHeightPx > maxHeightPx) break outer;
14697
+ }
14698
+ offset += blockText(block).length + 1;
14699
+ }
14700
+ this.paragraphs = next;
14701
+ this.pool = new Map(next.map((e) => [e.sig, e]));
14702
+ this.lastParagraphsRelaid = relaid;
14703
+ const width = fixedWidth ? bounds.width : textWidth + padding * 2;
14704
+ const height = fixedHeight ? bounds.height : lines.length * lineHeightPx + padding * 2;
14705
+ if (style.align !== "left") for (const line of lines) {
14706
+ const free = width - line.width - padding * 2;
14707
+ line.offsetX = style.align === "center" ? free / 2 : free;
14708
+ }
14709
+ return {
14710
+ lines,
14711
+ textWidth,
14712
+ textHeight: style.fontSize,
14713
+ lineHeightPx,
14714
+ width,
14715
+ height
14716
+ };
14717
+ }
14718
+ rangeWidth(entry, runs, from, to, font, spacing) {
14719
+ let width = 0;
14720
+ let i = from;
14721
+ while (i < to) {
14722
+ const run = entry.runOf[i];
14723
+ let j = i;
14724
+ while (j < to && entry.runOf[j] === run) j++;
14725
+ width += this.measureChunk(font, entry.graphemes.slice(i, j).join(""), spacing);
14726
+ i = j;
14727
+ }
14728
+ return width;
14729
+ }
14730
+ layoutParagraph(block, sig, font, style, fixedWidth, maxWidth) {
14731
+ const graphemes = [];
14732
+ const runOf = [];
14733
+ block.runs.forEach((run, ri) => {
14734
+ for (const g of splitGraphemes(run.t)) {
14735
+ graphemes.push(g);
14736
+ runOf.push(ri);
14737
+ }
14738
+ });
14739
+ const entry = {
14740
+ sig,
14741
+ graphemes,
14742
+ runOf,
14743
+ lines: []
14744
+ };
14745
+ const n = graphemes.length;
14746
+ const spacing = style.letterSpacing;
14747
+ const width = (a, b) => this.rangeWidth(entry, block.runs, a, b, font, spacing);
14748
+ const shouldWrap = style.wrap !== "none";
14749
+ const wrapAtWord = style.wrap !== "char" && shouldWrap;
14750
+ const whole = width(0, n);
14751
+ if (!fixedWidth || !shouldWrap || whole <= maxWidth || n === 0) {
14752
+ entry.lines.push({
14753
+ startG: 0,
14754
+ endG: n,
14755
+ measuredWidth: whole,
14756
+ lastInParagraph: true
14757
+ });
14758
+ return entry;
14759
+ }
14760
+ let start = 0;
14761
+ while (start < n) {
14762
+ let low = 0;
14763
+ let high = n - start;
14764
+ let probe = Math.min(high, 32);
14765
+ while (probe < high && width(start, start + probe) <= maxWidth) probe = Math.min(high, probe * 2);
14766
+ high = probe;
14767
+ let matchLen = 0;
14768
+ let matchWidth = 0;
14769
+ while (low < high) {
14770
+ const mid = low + high >>> 1;
14771
+ const w = width(start, start + mid + 1);
14772
+ if (w <= maxWidth) {
14773
+ low = mid + 1;
14774
+ matchLen = mid + 1;
14775
+ matchWidth = w;
14776
+ } else high = mid;
14777
+ }
14778
+ if (matchLen === 0) {
14779
+ matchLen = 1;
14780
+ matchWidth = width(start, start + 1);
14781
+ }
14782
+ if (start + matchLen >= n) {
14783
+ entry.lines.push({
14784
+ startG: start,
14785
+ endG: n,
14786
+ measuredWidth: matchWidth,
14787
+ lastInParagraph: true
14788
+ });
14789
+ return entry;
14790
+ }
14791
+ if (wrapAtWord) {
14792
+ const nextChar = graphemes[start + matchLen];
14793
+ const nextIsSpaceOrDash = nextChar === " " || nextChar === "-";
14794
+ let wrapIndex;
14795
+ if (nextIsSpaceOrDash && matchWidth <= maxWidth) wrapIndex = matchLen;
14796
+ else {
14797
+ const seg = graphemes.slice(start, start + matchLen);
14798
+ wrapIndex = Math.max(seg.lastIndexOf(" "), seg.lastIndexOf("-")) + 1;
14799
+ }
14800
+ if (wrapIndex > 0) {
14801
+ matchLen = wrapIndex;
14802
+ matchWidth = width(start, start + matchLen);
14803
+ }
14804
+ }
14805
+ let end = start + matchLen;
14806
+ let trimmedEnd = end;
14807
+ while (trimmedEnd > start && WS.test(graphemes[trimmedEnd - 1])) trimmedEnd--;
14808
+ entry.lines.push({
14809
+ startG: start,
14810
+ endG: trimmedEnd,
14811
+ measuredWidth: matchWidth,
14812
+ lastInParagraph: false
14813
+ });
14814
+ while (end < n && WS.test(graphemes[end])) end++;
14815
+ start = end;
14816
+ }
14817
+ const last = entry.lines[entry.lines.length - 1];
14818
+ if (last) last.lastInParagraph = true;
14819
+ return entry;
14820
+ }
14821
+ buildSegments(entry, block, pl, font, style) {
14822
+ const segments = [];
14823
+ let x = 0;
14824
+ let i = pl.startG;
14825
+ while (i < pl.endG) {
14826
+ const run = entry.runOf[i];
14827
+ let j = i;
14828
+ while (j < pl.endG && entry.runOf[j] === run) j++;
14829
+ const t = entry.graphemes.slice(i, j).join("");
14830
+ const width = this.measureChunk(font, t, style.letterSpacing);
14831
+ segments.push({
14832
+ t,
14833
+ m: block.runs[run].m,
14834
+ x,
14835
+ width
14836
+ });
14837
+ x += width;
14838
+ i = j;
14839
+ }
14840
+ return segments;
14841
+ }
14842
+ };
14843
+ //#endregion
14844
+ //#region src/nodes/text-v2/shape.ts
14845
+ const LAYOUT_ATTRS = [
14846
+ "text",
14847
+ "blocks",
14848
+ "link",
14849
+ "fontSize",
14850
+ "fontFamily",
14851
+ "fontStyle",
14852
+ "fontVariant",
14853
+ "lineHeight",
14854
+ "letterSpacing",
14855
+ "wrap",
14856
+ "align",
14857
+ "padding",
14858
+ "width",
14859
+ "height"
14860
+ ];
14861
+ /**
14862
+ * Canvas shape that lays out and paints blocks/runs text with the
14863
+ * `TextLayoutEngine`. Exposes Konva.Text-compatible getters/setters so
14864
+ * code written against `Konva.Text` keeps working.
14865
+ */
14866
+ var WeaveTextV2Shape = class extends Konva.Shape {
14867
+ engine = new TextLayoutEngine();
14868
+ naturalEngine = new TextLayoutEngine();
14869
+ layoutCache = null;
14870
+ blocksKey = {};
14871
+ blocksCache = [];
14872
+ viewport = null;
14873
+ constructor(config) {
14874
+ super(config);
14875
+ this.className = "WeaveTextV2Shape";
14876
+ this.on(LAYOUT_ATTRS.map((a) => `${a}Change.konva`).join(" "), () => {
14877
+ this.layoutCache = null;
14878
+ });
14879
+ this.sceneFunc(this._paint);
14880
+ this.hitFunc(this._hit);
14881
+ }
14882
+ text(value) {
14883
+ if (value === void 0) return typeof this.attrs.text === "string" ? this.attrs.text : blocksToText(this.getBlocks());
14884
+ return this.setText(value);
14885
+ }
14886
+ setText(value) {
14887
+ this._setAttr("blocks", void 0);
14888
+ this._setAttr("text", value == null ? "" : String(value));
14889
+ return this;
14890
+ }
14891
+ /** Resolved blocks: stored or lazily synthesized (never written back). */
14892
+ getBlocks() {
14893
+ const k = this.blocksKey;
14894
+ const { blocks, text, link } = this.attrs;
14895
+ if (k.b !== blocks || k.t !== text || k.l !== link) {
14896
+ this.blocksCache = resolveBlocks({
14897
+ blocks,
14898
+ text,
14899
+ link
14900
+ });
14901
+ this.blocksKey = {
14902
+ b: blocks,
14903
+ t: text,
14904
+ l: link
14905
+ };
14906
+ }
14907
+ return this.blocksCache;
14908
+ }
14909
+ /** Replace content with blocks, keeping the `text` mirror in sync. */
14910
+ setBlocks(blocks) {
14911
+ this._setAttr("blocks", blocks);
14912
+ this._setAttr("text", blocksToText(blocks));
14913
+ return this;
14914
+ }
14915
+ fontSize(v) {
14916
+ return this.accessor("fontSize", 12, v);
14917
+ }
14918
+ fontFamily(v) {
14919
+ return this.accessor("fontFamily", "Arial", v);
14920
+ }
14921
+ fontStyle(v) {
14922
+ return this.accessor("fontStyle", "normal", v);
14923
+ }
14924
+ fontVariant(v) {
14925
+ return this.accessor("fontVariant", "normal", v);
14926
+ }
14927
+ lineHeight(v) {
14928
+ return this.accessor("lineHeight", 1, v);
14929
+ }
14930
+ letterSpacing(v) {
14931
+ return this.accessor("letterSpacing", 0, v);
14932
+ }
14933
+ padding(v) {
14934
+ return this.accessor("padding", 0, v);
14935
+ }
14936
+ align(v) {
14937
+ return this.accessor("align", "left", v);
14938
+ }
14939
+ verticalAlign(v) {
14940
+ return this.accessor("verticalAlign", "top", v);
14941
+ }
14942
+ wrap(v) {
14943
+ return this.accessor("wrap", "word", v);
14944
+ }
14945
+ textDecoration(v) {
14946
+ return this.accessor("textDecoration", "", v);
14947
+ }
14948
+ accessor(name, fallback, value) {
14949
+ if (value === void 0) return this.attrs[name] ?? fallback;
14950
+ this.setAttr(name, value);
14951
+ return this;
14952
+ }
14953
+ getTextWidth() {
14954
+ return this.ensureLayout().textWidth;
14955
+ }
14956
+ getTextHeight() {
14957
+ return this.fontSize();
14958
+ }
14959
+ measureSize(text) {
14960
+ return {
14961
+ width: canvasMeasurer(fontString(this.style()), text),
14962
+ height: this.fontSize()
14963
+ };
14964
+ }
14965
+ getWidth() {
14966
+ const w = this.attrs.width;
14967
+ return w === void 0 || w === "auto" || Number.isNaN(w) ? this.ensureLayout().width : w;
14968
+ }
14969
+ getHeight() {
14970
+ const h = this.attrs.height;
14971
+ return h === void 0 || h === "auto" || Number.isNaN(h) ? this.ensureLayout().height : h;
14972
+ }
14973
+ style() {
14974
+ return {
14975
+ fontSize: this.fontSize(),
14976
+ fontFamily: this.fontFamily(),
14977
+ fontStyle: this.fontStyle(),
14978
+ fontVariant: this.fontVariant(),
14979
+ lineHeight: this.lineHeight(),
14980
+ letterSpacing: this.letterSpacing(),
14981
+ wrap: this.wrap(),
14982
+ align: this.align()
14983
+ };
14984
+ }
14985
+ /** Lazy, cached layout. Size accessors and painting force it. */
14986
+ ensureLayout() {
14987
+ if (!this.layoutCache) {
14988
+ const w = this.attrs.width;
14989
+ const h = this.attrs.height;
14990
+ const num = (v) => typeof v === "number" && !Number.isNaN(v) ? v : void 0;
14991
+ this.layoutCache = this.engine.layout(this.getBlocks(), this.style(), {
14992
+ width: num(w),
14993
+ height: num(h),
14994
+ padding: this.padding()
14995
+ });
14996
+ }
14997
+ return this.layoutCache;
14998
+ }
14999
+ /** Size the text would take with no width constraint (for auto layouts). */
15000
+ measureNaturalSize() {
15001
+ const out = this.naturalEngine.layout(this.getBlocks(), this.style(), {
15002
+ width: void 0,
15003
+ height: void 0,
15004
+ padding: this.padding()
15005
+ });
15006
+ return {
15007
+ width: out.width,
15008
+ height: out.height
15009
+ };
15010
+ }
15011
+ /** Rendered width of a string with this shape's font and letter spacing. */
15012
+ measureText(text) {
15013
+ return canvasMeasurer(fontString(this.style()), text) + text.length * this.letterSpacing();
15014
+ }
15015
+ /** Offset of the first line inside the box (padding + vertical align). */
15016
+ getContentOffset() {
15017
+ const padding = this.padding();
15018
+ const layout = this.ensureLayout();
15019
+ const used = layout.lines.length * layout.lineHeightPx + padding * 2;
15020
+ const va = this.verticalAlign();
15021
+ let alignY = 0;
15022
+ if (va === "middle") alignY = (this.getHeight() - used) / 2;
15023
+ else if (va === "bottom") alignY = this.getHeight() - used;
15024
+ return {
15025
+ x: padding,
15026
+ y: alignY + padding
15027
+ };
15028
+ }
15029
+ /** Pre-compute layout for candidate blocks within a time budget. */
15030
+ warmLayout(blocks, budgetMs) {
15031
+ const num = (v) => typeof v === "number" && !Number.isNaN(v) ? v : void 0;
15032
+ return this.engine.warm(blocks, this.style(), {
15033
+ width: num(this.attrs.width),
15034
+ height: num(this.attrs.height),
15035
+ padding: this.padding()
15036
+ }, budgetMs);
15037
+ }
15038
+ /** Optional visible y-range in local coordinates, used for line culling. */
15039
+ setViewport(range) {
15040
+ this.viewport = range;
15041
+ }
15042
+ visibleRange() {
15043
+ if (this.viewport) return this.viewport;
15044
+ const stage = this.getStage();
15045
+ if (!stage) return null;
15046
+ const inv = this.getAbsoluteTransform().copy().invert();
15047
+ const w = stage.width();
15048
+ const h = stage.height();
15049
+ const pts = [
15050
+ inv.point({
15051
+ x: 0,
15052
+ y: 0
15053
+ }),
15054
+ inv.point({
15055
+ x: w,
15056
+ y: 0
15057
+ }),
15058
+ inv.point({
15059
+ x: 0,
15060
+ y: h
15061
+ }),
15062
+ inv.point({
15063
+ x: w,
15064
+ y: h
15065
+ })
15066
+ ];
15067
+ return {
15068
+ y1: Math.min(...pts.map((p) => p.y)),
15069
+ y2: Math.max(...pts.map((p) => p.y))
15070
+ };
15071
+ }
15072
+ _paint = (ctx, shape) => {
15073
+ const self = shape;
15074
+ const layout = self.ensureLayout();
15075
+ if (layout.lines.length === 0) return;
15076
+ const fontSize = self.fontSize();
15077
+ const padding = self.padding();
15078
+ const lineHeightPx = layout.lineHeightPx;
15079
+ const decoration = self.textDecoration();
15080
+ const underline = decoration.includes("underline");
15081
+ const lineThrough = decoration.includes("line-through");
15082
+ const spacing = self.letterSpacing();
15083
+ const fill = self.fill();
15084
+ let baseline = "middle";
15085
+ let translateY = lineHeightPx / 2;
15086
+ if (!Konva.legacyTextRendering) {
15087
+ const m = self.measureMetrics();
15088
+ baseline = "alphabetic";
15089
+ translateY = (m.ascent - m.descent) / 2 + lineHeightPx / 2;
15090
+ }
15091
+ ctx.setAttr("font", fontString(self.style()));
15092
+ ctx.setAttr("textBaseline", baseline);
15093
+ ctx.setAttr("textAlign", "left");
15094
+ let alignY = 0;
15095
+ const va = self.verticalAlign();
15096
+ const used = layout.lines.length * lineHeightPx + padding * 2;
15097
+ if (va === "middle") alignY = (self.getHeight() - used) / 2;
15098
+ else if (va === "bottom") alignY = self.getHeight() - used;
15099
+ ctx.translate(padding, alignY + padding);
15100
+ const range = self.visibleRange();
15101
+ const paintSelf = self;
15102
+ for (let n = 0; n < layout.lines.length; n++) {
15103
+ const line = layout.lines[n];
15104
+ const y = translateY + n * lineHeightPx;
15105
+ if (range && (alignY + padding + n * lineHeightPx + lineHeightPx < range.y1 - lineHeightPx || alignY + padding + n * lineHeightPx > range.y2 + lineHeightPx)) continue;
15106
+ ctx.save();
15107
+ const baseX = line.offsetX;
15108
+ if (underline) {
15109
+ ctx.save();
15110
+ ctx.beginPath();
15111
+ const uy = y + Math.round(fontSize / 4);
15112
+ ctx.moveTo(baseX, uy);
15113
+ ctx.lineTo(baseX + Math.round(line.width), uy);
15114
+ ctx.lineWidth = fontSize / 15;
15115
+ ctx.strokeStyle = fill;
15116
+ ctx.stroke();
15117
+ ctx.restore();
15118
+ }
15119
+ for (const seg of line.segments) {
15120
+ let x = baseX + seg.x;
15121
+ if (spacing !== 0) for (const ch of Array.from(seg.t)) {
15122
+ paintSelf._partialText = ch;
15123
+ paintSelf._partialTextX = x;
15124
+ paintSelf._partialTextY = y;
15125
+ ctx.fillStrokeShape(self);
15126
+ x += self.measureSize(ch).width + spacing;
15127
+ }
15128
+ else {
15129
+ paintSelf._partialText = seg.t;
15130
+ paintSelf._partialTextX = x;
15131
+ paintSelf._partialTextY = y;
15132
+ ctx.fillStrokeShape(self);
15133
+ }
15134
+ }
15135
+ if (lineThrough) {
15136
+ ctx.save();
15137
+ ctx.beginPath();
15138
+ const ly = y - Math.round(fontSize / 4);
15139
+ ctx.moveTo(baseX, ly);
15140
+ ctx.lineTo(baseX + Math.round(line.width), ly);
15141
+ ctx.lineWidth = fontSize / 15;
15142
+ ctx.strokeStyle = fill;
15143
+ ctx.stroke();
15144
+ ctx.restore();
15145
+ }
15146
+ ctx.restore();
15147
+ }
15148
+ };
15149
+ _hit = (ctx, shape) => {
15150
+ ctx.beginPath();
15151
+ const self = shape;
15152
+ ctx.rect(0, 0, self.getWidth(), self.getHeight());
15153
+ ctx.closePath();
15154
+ ctx.fillStrokeShape(shape);
15155
+ };
15156
+ metricsCache = null;
15157
+ /** Font ascent/descent used for baseline placement (parity with Konva.Text). */
15158
+ measureMetrics() {
15159
+ const font = fontString(this.style());
15160
+ if (this.metricsCache?.key === font) return this.metricsCache;
15161
+ const ctx = Konva.Util.createCanvasElement().getContext("2d");
15162
+ ctx.font = font;
15163
+ const m = ctx.measureText("M");
15164
+ const scale = this.fontSize() / 100;
15165
+ const ascent = m.fontBoundingBoxAscent ?? 91 * scale;
15166
+ const descent = m.fontBoundingBoxDescent ?? 21 * scale;
15167
+ this.metricsCache = {
15168
+ key: font,
15169
+ ascent,
15170
+ descent
15171
+ };
15172
+ return this.metricsCache;
15173
+ }
15174
+ };
15175
+ Object.assign(WeaveTextV2Shape.prototype, {
15176
+ _fillFunc(ctx) {
15177
+ ctx.fillText(this._partialText, this._partialTextX, this._partialTextY);
15178
+ },
15179
+ _strokeFunc(ctx) {
15180
+ ctx.setAttr("miterLimit", 2);
15181
+ ctx.strokeText(this._partialText, this._partialTextX, this._partialTextY);
15182
+ }
15183
+ });
15184
+ //#endregion
15185
+ //#region src/nodes/text-v2/edit/geometry.ts
15186
+ const graphemeSeg = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : null;
15187
+ const wordSeg = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter(void 0, { granularity: "word" }) : null;
15188
+ function boundaries(text, seg) {
15189
+ if (!seg) return Array.from({ length: text.length + 1 }, (_, i) => i);
15190
+ const out = [0];
15191
+ for (const s of seg.segment(text)) out.push(s.index + s.segment.length);
15192
+ return out;
15193
+ }
15194
+ function prevGrapheme(text, offset) {
15195
+ if (offset <= 0) return 0;
15196
+ const b = boundaries(text, graphemeSeg);
15197
+ for (let i = b.length - 1; i >= 0; i--) if (b[i] < offset) return b[i];
15198
+ return 0;
15199
+ }
15200
+ function nextGrapheme(text, offset) {
15201
+ if (offset >= text.length) return text.length;
15202
+ const b = boundaries(text, graphemeSeg);
15203
+ for (const v of b) if (v > offset) return v;
15204
+ return text.length;
15205
+ }
15206
+ /** Word segment [start, end) containing (or adjacent to) the offset. */
15207
+ function wordRangeAt(text, offset) {
15208
+ if (text === "") return [0, 0];
15209
+ offset = Math.max(0, Math.min(offset, text.length));
15210
+ if (!wordSeg) {
15211
+ let s = offset;
15212
+ let e = offset;
15213
+ while (s > 0 && /\S/.test(text[s - 1])) s--;
15214
+ while (e < text.length && /\S/.test(text[e])) e++;
15215
+ return [s, e];
15216
+ }
15217
+ let last = [offset, offset];
15218
+ for (const seg of wordSeg.segment(text)) {
15219
+ const s = seg.index;
15220
+ const e = s + seg.segment.length;
15221
+ last = [s, e];
15222
+ if (offset < e) return [s, e];
15223
+ }
15224
+ return last;
15225
+ }
15226
+ /** Paragraph range [start, end) (no trailing newline) containing offset. */
15227
+ function paragraphRangeAt(text, offset) {
15228
+ offset = Math.max(0, Math.min(offset, text.length));
15229
+ const s = text.lastIndexOf("\n", offset - 1) + 1;
15230
+ let e = text.indexOf("\n", offset);
15231
+ if (e === -1) e = text.length;
15232
+ return [s, e];
15233
+ }
15234
+ /** Previous word start (Ctrl+Left semantics). */
15235
+ function prevWordBoundary(text, offset) {
15236
+ let i = offset;
15237
+ while (i > 0 && /\s/.test(text[i - 1])) i--;
15238
+ if (i === 0) return 0;
15239
+ return wordRangeAt(text, i - 1)[0];
15240
+ }
15241
+ /** Next word end (Ctrl+Right semantics). */
15242
+ function nextWordBoundary(text, offset) {
15243
+ let i = offset;
15244
+ while (i < text.length && /\s/.test(text[i])) i++;
15245
+ if (i >= text.length) return text.length;
15246
+ return wordRangeAt(text, i)[1];
15247
+ }
15248
+ function lineIndexAt(layout, offset) {
15249
+ const lines = layout.lines;
15250
+ if (lines.length === 0) return 0;
15251
+ let fallback = 0;
15252
+ for (let i = 0; i < lines.length; i++) {
15253
+ const l = lines[i];
15254
+ if (l.start <= offset) fallback = i;
15255
+ if (offset >= l.start && offset <= l.end) {
15256
+ const next = lines[i + 1];
15257
+ if (offset === l.end && !l.lastInParagraph && next && next.start === offset) continue;
15258
+ return i;
15259
+ }
15260
+ }
15261
+ return fallback;
15262
+ }
15263
+ function lineX(line, offset, measure) {
15264
+ const k = Math.max(0, Math.min(offset - line.start, line.text.length));
15265
+ return line.offsetX + (k === 0 ? 0 : measure(line.text.slice(0, k)));
15266
+ }
15267
+ /** Caret rectangle in text-local coordinates (before padding/valign offset). */
15268
+ function caretRect(layout, offset, measure) {
15269
+ const i = lineIndexAt(layout, offset);
15270
+ const line = layout.lines[i];
15271
+ if (!line) return {
15272
+ x: 0,
15273
+ y: 0,
15274
+ height: layout.lineHeightPx,
15275
+ line: 0
15276
+ };
15277
+ return {
15278
+ x: lineX(line, offset, measure),
15279
+ y: i * layout.lineHeightPx,
15280
+ height: layout.lineHeightPx,
15281
+ line: i
15282
+ };
15283
+ }
15284
+ /** Offset closest to a point in text-local coordinates. */
15285
+ function offsetFromPoint(layout, point, measure) {
15286
+ const lines = layout.lines;
15287
+ if (lines.length === 0) return 0;
15288
+ return offsetInLine(lines[Math.max(0, Math.min(lines.length - 1, Math.floor(point.y / layout.lineHeightPx)))], point.x, measure);
15289
+ }
15290
+ function offsetInLine(line, x, measure) {
15291
+ const rel = x - line.offsetX;
15292
+ if (rel <= 0 || line.text === "") return line.start;
15293
+ const b = boundaries(line.text, graphemeSeg);
15294
+ let prevW = 0;
15295
+ for (let k = 1; k < b.length; k++) {
15296
+ const w = measure(line.text.slice(0, b[k]));
15297
+ if (rel < (prevW + w) / 2) return line.start + b[k - 1];
15298
+ prevW = w;
15299
+ }
15300
+ return line.end;
15301
+ }
15302
+ /** Move one visual line up/down keeping a desired x. */
15303
+ function moveVertical(layout, offset, dir, desiredX, measure) {
15304
+ const j = lineIndexAt(layout, offset) + dir;
15305
+ if (j < 0) return 0;
15306
+ if (j >= layout.lines.length) {
15307
+ const last = layout.lines[layout.lines.length - 1];
15308
+ return last ? last.end : 0;
15309
+ }
15310
+ return offsetInLine(layout.lines[j], desiredX, measure);
15311
+ }
15312
+ function lineHome(layout, offset) {
15313
+ return layout.lines[lineIndexAt(layout, offset)]?.start ?? 0;
15314
+ }
15315
+ function lineEnd(layout, offset) {
15316
+ return layout.lines[lineIndexAt(layout, offset)]?.end ?? 0;
15317
+ }
15318
+ /** Selection rectangles (one per touched line) in text-local coordinates. */
15319
+ function selectionRects(layout, from, to, measure) {
15320
+ if (from > to) [from, to] = [to, from];
15321
+ if (from === to) return [];
15322
+ const out = [];
15323
+ layout.lines.forEach((line, i) => {
15324
+ const hasNewline = line.lastInParagraph;
15325
+ const lineFrom = Math.max(from, line.start);
15326
+ const lineTo = Math.min(to, hasNewline ? line.end + 1 : line.end);
15327
+ if (lineTo <= lineFrom && !(line.start === line.end && from <= line.start && to > line.start)) return;
15328
+ const x1 = lineX(line, Math.min(lineFrom, line.end), measure);
15329
+ let x2 = lineX(line, Math.min(lineTo, line.end), measure);
15330
+ if (hasNewline && to > line.end) x2 += Math.max(4, layout.textHeight / 3);
15331
+ if (x2 <= x1) return;
15332
+ out.push({
15333
+ x: x1,
15334
+ y: i * layout.lineHeightPx,
15335
+ width: x2 - x1,
15336
+ height: layout.lineHeightPx
15337
+ });
15338
+ });
15339
+ return out;
15340
+ }
15341
+ //#endregion
15342
+ //#region src/nodes/text-v2/edit/model.ts
15343
+ /**
15344
+ * Session-local edit state: content + selection + undo/redo with
15345
+ * coalescing. Typing and deleting merge into one entry while they continue
15346
+ * in the same direction without a selection move; paste and IME composition
15347
+ * are always one entry each. Independent of Konva and the DOM.
15348
+ */
15349
+ var TextEditModel = class {
15350
+ blocks;
15351
+ anchor = 0;
15352
+ focus = 0;
15353
+ /** x kept while moving vertically; reset by any other move. */
15354
+ desiredX = null;
15355
+ undoStack = [];
15356
+ redoStack = [];
15357
+ lastKind = null;
15358
+ lastEditEnd = -1;
15359
+ composing = false;
15360
+ constructor(blocks) {
15361
+ this.blocks = blocks;
15362
+ const len = blocksToText(blocks).length;
15363
+ this.anchor = this.focus = len;
15364
+ }
15365
+ getBlocks() {
15366
+ return this.blocks;
15367
+ }
15368
+ getText() {
15369
+ return blocksToText(this.blocks);
15370
+ }
15371
+ getSelection() {
15372
+ return {
15373
+ start: Math.min(this.anchor, this.focus),
15374
+ end: Math.max(this.anchor, this.focus)
15375
+ };
15376
+ }
15377
+ getSelectedText() {
15378
+ const { start, end } = this.getSelection();
15379
+ return this.getText().slice(start, end);
15380
+ }
15381
+ canUndo() {
15382
+ return this.undoStack.length > 0;
15383
+ }
15384
+ canRedo() {
15385
+ return this.redoStack.length > 0;
15386
+ }
15387
+ setSelection(anchor, focus) {
15388
+ const len = this.getText().length;
15389
+ this.anchor = Math.max(0, Math.min(anchor, len));
15390
+ this.focus = Math.max(0, Math.min(focus, len));
15391
+ this.breakCoalescing();
15392
+ }
15393
+ moveTo(offset, extend = false, keepDesiredX = false) {
15394
+ const len = this.getText().length;
15395
+ const o = Math.max(0, Math.min(offset, len));
15396
+ this.focus = o;
15397
+ if (!extend) this.anchor = o;
15398
+ if (!keepDesiredX) this.desiredX = null;
15399
+ this.breakCoalescing();
15400
+ }
15401
+ selectAll() {
15402
+ this.anchor = 0;
15403
+ this.focus = this.getText().length;
15404
+ this.breakCoalescing();
15405
+ }
15406
+ breakCoalescing() {
15407
+ if (!this.composing) this.lastKind = null;
15408
+ }
15409
+ beginComposition() {
15410
+ this.composing = true;
15411
+ this.lastKind = null;
15412
+ }
15413
+ endComposition() {
15414
+ this.composing = false;
15415
+ this.lastKind = null;
15416
+ }
15417
+ snapshot() {
15418
+ return {
15419
+ blocks: this.blocks,
15420
+ anchor: this.anchor,
15421
+ focus: this.focus
15422
+ };
15423
+ }
15424
+ record(kind, editStart) {
15425
+ if (!(this.lastKind === kind && (kind === "type" || kind === "delete" || kind === "ime") && (kind === "ime" || this.lastEditEnd === editStart || kind === "delete"))) {
15426
+ this.undoStack.push(this.snapshot());
15427
+ if (this.undoStack.length > 500) this.undoStack.shift();
15428
+ }
15429
+ this.redoStack = [];
15430
+ this.lastKind = kind;
15431
+ }
15432
+ /** Replace [start,end) with plain text and put the caret after it. */
15433
+ replaceRange(start, end, text, kind) {
15434
+ const len = this.getText().length;
15435
+ start = Math.max(0, Math.min(start, len));
15436
+ end = Math.max(start, Math.min(end, len));
15437
+ if (start === end && text === "") return false;
15438
+ this.record(kind, start);
15439
+ this.blocks = replaceRange(this.blocks, start, end, text);
15440
+ this.anchor = this.focus = start + text.length;
15441
+ this.lastEditEnd = this.focus;
15442
+ this.desiredX = null;
15443
+ return true;
15444
+ }
15445
+ replaceSelection(text, kind) {
15446
+ const { start, end } = this.getSelection();
15447
+ return this.replaceRange(start, end, text, kind);
15448
+ }
15449
+ backspace(byWord = false) {
15450
+ const { start, end } = this.getSelection();
15451
+ if (start !== end) return this.replaceRange(start, end, "", "delete");
15452
+ if (start === 0) return false;
15453
+ const text = this.getText();
15454
+ const from = byWord ? prevWordBoundary(text, start) : prevGrapheme(text, start);
15455
+ return this.replaceRange(from, start, "", "delete");
15456
+ }
15457
+ del(byWord = false) {
15458
+ const { start, end } = this.getSelection();
15459
+ if (start !== end) return this.replaceRange(start, end, "", "delete");
15460
+ const text = this.getText();
15461
+ if (start >= text.length) return false;
15462
+ const to = byWord ? nextWordBoundary(text, start) : nextGrapheme(text, start);
15463
+ return this.replaceRange(start, to, "", "delete");
15464
+ }
15465
+ undo() {
15466
+ const prev = this.undoStack.pop();
15467
+ if (!prev) return false;
15468
+ this.redoStack.push(this.snapshot());
15469
+ this.restore(prev);
15470
+ return true;
15471
+ }
15472
+ redo() {
15473
+ const next = this.redoStack.pop();
15474
+ if (!next) return false;
15475
+ this.undoStack.push(this.snapshot());
15476
+ this.restore(next);
15477
+ return true;
15478
+ }
15479
+ restore(s) {
15480
+ this.blocks = s.blocks;
15481
+ this.anchor = s.anchor;
15482
+ this.focus = s.focus;
15483
+ this.lastKind = null;
15484
+ this.desiredX = null;
15485
+ }
15486
+ };
15487
+ const SLICE_BUDGET_MS = 8;
15488
+ const BLINK_MS = 530;
15489
+ var TextEditSession = class {
15490
+ model;
15491
+ target;
15492
+ host;
15493
+ id;
15494
+ abort = new AbortController();
15495
+ textarea = null;
15496
+ overlay = null;
15497
+ blinkTimer;
15498
+ caretOn = true;
15499
+ dragging = false;
15500
+ composing = false;
15501
+ pendingTimer;
15502
+ pendingFlush = false;
15503
+ destroyed = false;
15504
+ initialJson;
15505
+ constructor(target, host, id = "text-edit-session") {
15506
+ this.target = target;
15507
+ this.host = host;
15508
+ this.id = id;
15509
+ this.model = new TextEditModel(target.getBlocks());
15510
+ this.initialJson = JSON.stringify(this.model.getBlocks());
15511
+ }
15512
+ start() {
15513
+ const signal = this.abort.signal;
15514
+ host_signal(this.host.signal, this.abort);
15515
+ this.createTextarea();
15516
+ this.createOverlay();
15517
+ const ta = this.textarea;
15518
+ const opts = { signal };
15519
+ ta.addEventListener("keydown", this.onKeyDown, opts);
15520
+ ta.addEventListener("keyup", this.onKeyUp, opts);
15521
+ ta.addEventListener("beforeinput", this.onBeforeInput, opts);
15522
+ ta.addEventListener("input", this.onInput, opts);
15523
+ ta.addEventListener("paste", this.onPaste, opts);
15524
+ ta.addEventListener("compositionstart", this.onCompositionStart, opts);
15525
+ ta.addEventListener("compositionend", this.onCompositionEnd, opts);
15526
+ ta.addEventListener("select", this.syncFromTextarea, opts);
15527
+ ta.ownerDocument.addEventListener("selectionchange", this.syncFromTextarea, opts);
15528
+ window.addEventListener("pointerdown", this.onPointerDown, {
15529
+ signal,
15530
+ capture: true
15531
+ });
15532
+ window.addEventListener("pointermove", this.onPointerMove, { signal });
15533
+ window.addEventListener("pointerup", this.onPointerUp, { signal });
15534
+ window.addEventListener("mousedown", this.onMouseDown, {
15535
+ signal,
15536
+ capture: true
15537
+ });
15538
+ window.weaveTextEditing = {
15539
+ ...window.weaveTextEditing ?? {},
15540
+ [this.id]: this.id
15541
+ };
15542
+ this.syncTextarea();
15543
+ ta.focus();
15544
+ this.positionTextarea();
15545
+ this.startBlink();
15546
+ this.repaint();
15547
+ }
15548
+ /** Final content and whether it differs from when the session started. */
15549
+ getResult() {
15550
+ return {
15551
+ blocks: this.model.getBlocks(),
15552
+ changed: JSON.stringify(this.model.getBlocks()) !== this.initialJson
15553
+ };
15554
+ }
15555
+ destroy() {
15556
+ if (this.destroyed) return;
15557
+ this.destroyed = true;
15558
+ this.flushPending();
15559
+ this.abort.abort();
15560
+ clearInterval(this.blinkTimer);
15561
+ clearTimeout(this.pendingTimer);
15562
+ this.textarea?.remove();
15563
+ this.textarea = null;
15564
+ const layer = this.overlay?.getLayer();
15565
+ this.overlay?.destroy();
15566
+ this.overlay = null;
15567
+ layer?.batchDraw();
15568
+ const w = window;
15569
+ if (w.weaveTextEditing) delete w.weaveTextEditing[this.id];
15570
+ }
15571
+ focus() {
15572
+ this.textarea?.focus();
15573
+ }
15574
+ createTextarea() {
15575
+ const ta = this.host.container.ownerDocument.createElement("textarea");
15576
+ ta.setAttribute("autocapitalize", "off");
15577
+ ta.setAttribute("autocomplete", "off");
15578
+ ta.setAttribute("autocorrect", "off");
15579
+ ta.setAttribute("spellcheck", "false");
15580
+ ta.setAttribute("wrap", "off");
15581
+ ta.setAttribute("aria-label", "Text editor");
15582
+ ta.tabIndex = 1;
15583
+ Object.assign(ta.style, {
15584
+ position: "absolute",
15585
+ left: "0px",
15586
+ top: "0px",
15587
+ width: "1px",
15588
+ height: "1em",
15589
+ margin: "0",
15590
+ padding: "0",
15591
+ border: "0",
15592
+ outline: "none",
15593
+ resize: "none",
15594
+ overflow: "hidden",
15595
+ whiteSpace: "pre",
15596
+ opacity: "0",
15597
+ fontSize: "16px",
15598
+ background: "transparent",
15599
+ color: "transparent",
15600
+ caretColor: "transparent",
15601
+ pointerEvents: "none",
15602
+ zIndex: "1"
15603
+ });
15604
+ this.host.container.appendChild(ta);
15605
+ this.textarea = ta;
15606
+ }
15607
+ syncTextarea() {
15608
+ const ta = this.textarea;
15609
+ if (!ta || this.composing) return;
15610
+ const text = this.model.getText();
15611
+ if (ta.value !== text) ta.value = text;
15612
+ const { start, end } = this.model.getSelection();
15613
+ const backward = this.model.focus < this.model.anchor;
15614
+ if (ta.selectionStart !== start || ta.selectionEnd !== end) ta.setSelectionRange(start, end, backward ? "backward" : "forward");
15615
+ }
15616
+ positionTextarea() {
15617
+ const ta = this.textarea;
15618
+ if (!ta) return;
15619
+ const c = this.caretStagePoint();
15620
+ const p = this.host.stageToContainer(c);
15621
+ ta.style.left = `${p.x}px`;
15622
+ ta.style.top = `${p.y}px`;
15623
+ }
15624
+ caretStagePoint() {
15625
+ const r = caretRect(this.target.getLayout(), this.model.focus, this.target.measure);
15626
+ const o = this.target.getContentOffset();
15627
+ return this.target.getAbsoluteTransform().point({
15628
+ x: r.x + o.x,
15629
+ y: r.y + o.y
15630
+ });
15631
+ }
15632
+ syncFromTextarea = () => {
15633
+ const ta = this.textarea;
15634
+ if (!ta || this.composing || this.destroyed) return;
15635
+ if (ta.getRootNode().activeElement !== ta) return;
15636
+ if (ta.value !== this.model.getText()) return;
15637
+ const backward = ta.selectionDirection === "backward";
15638
+ const a = backward ? ta.selectionEnd : ta.selectionStart;
15639
+ const f = backward ? ta.selectionStart : ta.selectionEnd;
15640
+ if (a !== this.model.anchor || f !== this.model.focus) {
15641
+ this.model.setSelection(a, f);
15642
+ this.afterSelectionChange();
15643
+ }
15644
+ };
15645
+ onKeyDown = (e) => {
15646
+ e.stopPropagation();
15647
+ const mod = e.ctrlKey || e.metaKey;
15648
+ const key = e.key.toLowerCase();
15649
+ if (e.key === "Escape") {
15650
+ e.preventDefault();
15651
+ this.host.onRequestExit("escape");
15652
+ return;
15653
+ }
15654
+ if (mod && key === "z" && !e.shiftKey) {
15655
+ e.preventDefault();
15656
+ this.undo();
15657
+ return;
15658
+ }
15659
+ if (mod && (key === "y" || key === "z" && e.shiftKey)) {
15660
+ e.preventDefault();
15661
+ this.redo();
15662
+ return;
15663
+ }
15664
+ if (e.key === "Tab") {
15665
+ e.preventDefault();
15666
+ return;
15667
+ }
15668
+ if (this.composing) return;
15669
+ if ((e.key === "ArrowUp" || e.key === "ArrowDown") && !mod && !e.altKey) {
15670
+ e.preventDefault();
15671
+ this.moveVertically(e.key === "ArrowUp" ? -1 : 1, e.shiftKey);
15672
+ return;
15673
+ }
15674
+ if ((e.key === "Home" || e.key === "End") && !mod) {
15675
+ e.preventDefault();
15676
+ const layout = this.target.getLayout();
15677
+ const to = e.key === "Home" ? lineHome(layout, this.model.focus) : lineEnd(layout, this.model.focus);
15678
+ this.model.moveTo(to, e.shiftKey);
15679
+ this.afterSelectionChange();
15680
+ return;
15681
+ }
15682
+ setTimeout(this.syncFromTextarea, 0);
15683
+ };
15684
+ onKeyUp = (e) => {
15685
+ e.stopPropagation();
15686
+ this.syncFromTextarea();
15687
+ };
15688
+ onBeforeInput = (e) => {
15689
+ if (e.inputType === "historyUndo" || e.inputType === "historyRedo") e.preventDefault();
15690
+ };
15691
+ moveVertically(dir, extend) {
15692
+ const layout = this.target.getLayout();
15693
+ if (this.model.desiredX === null) this.model.desiredX = caretRect(layout, this.model.focus, this.target.measure).x;
15694
+ const to = moveVertical(layout, this.model.focus, dir, this.model.desiredX, this.target.measure);
15695
+ this.model.moveTo(to, extend, true);
15696
+ this.afterSelectionChange();
15697
+ }
15698
+ onInput = (e) => {
15699
+ const ta = this.textarea;
15700
+ if (!ta) return;
15701
+ const oldText = this.model.getText();
15702
+ const newText = ta.value;
15703
+ if (oldText === newText) return;
15704
+ let p = 0;
15705
+ const max = Math.min(oldText.length, newText.length);
15706
+ while (p < max && oldText[p] === newText[p]) p++;
15707
+ let s = 0;
15708
+ while (s < max - p && oldText[oldText.length - 1 - s] === newText[newText.length - 1 - s]) s++;
15709
+ const inserted = newText.slice(p, newText.length - s);
15710
+ const inputType = e.inputType ?? "";
15711
+ const kind = this.composing || inputType.includes("Composition") ? "ime" : inputType.startsWith("delete") ? "delete" : inputType === "insertFromPaste" ? "paste" : "type";
15712
+ this.model.replaceRange(p, oldText.length - s, inserted, kind);
15713
+ this.model.anchor = this.model.focus = Math.min(ta.selectionEnd, this.model.getText().length);
15714
+ this.afterContentChange(inserted.length);
15715
+ };
15716
+ onPaste = (e) => {
15717
+ e.stopPropagation();
15718
+ e.preventDefault();
15719
+ const raw = e.clipboardData?.getData("text/plain") ?? "";
15720
+ if (raw === "") return;
15721
+ this.insertText(raw.replace(/\r\n?/g, "\n"), "paste");
15722
+ };
15723
+ onCompositionStart = () => {
15724
+ this.composing = true;
15725
+ this.model.beginComposition();
15726
+ };
15727
+ onCompositionEnd = () => {
15728
+ this.composing = false;
15729
+ this.model.endComposition();
15730
+ this.syncFromTextarea();
15731
+ };
15732
+ /** Programmatic insert at the selection (paste, tests, host actions). */
15733
+ insertText(text, kind = "paste") {
15734
+ if (this.model.replaceSelection(text, kind)) {
15735
+ this.syncTextarea();
15736
+ this.afterContentChange(text.length);
15737
+ }
15738
+ }
15739
+ undo() {
15740
+ this.flushPending();
15741
+ if (this.model.undo()) {
15742
+ this.syncTextarea();
15743
+ this.afterContentChange(0, true);
15744
+ }
15745
+ }
15746
+ redo() {
15747
+ this.flushPending();
15748
+ if (this.model.redo()) {
15749
+ this.syncTextarea();
15750
+ this.afterContentChange(0, true);
15751
+ }
15752
+ }
15753
+ toLocal(e) {
15754
+ const stagePt = this.host.clientToStage(e.clientX, e.clientY);
15755
+ const p = this.target.getAbsoluteTransform().copy().invert().point(stagePt);
15756
+ const o = this.target.getContentOffset();
15757
+ return {
15758
+ x: p.x - o.x,
15759
+ y: p.y - o.y
15760
+ };
15761
+ }
15762
+ isInside(local) {
15763
+ const o = this.target.getContentOffset();
15764
+ const { width, height } = this.target.getSize();
15765
+ return local.x + o.x >= 0 && local.y + o.y >= 0 && local.x + o.x <= width && local.y + o.y <= height;
15766
+ }
15767
+ onMouseDown = (e) => {
15768
+ if (this.isInside(this.toLocal(e))) e.preventDefault();
15769
+ };
15770
+ onPointerDown = (e) => {
15771
+ if (e.button !== 0 && e.pointerType === "mouse") return;
15772
+ if (this.textarea && e.target === this.textarea) return;
15773
+ const local = this.toLocal(e);
15774
+ if (!this.isInside(local)) {
15775
+ e.stopPropagation();
15776
+ this.host.onRequestExit("outside");
15777
+ return;
15778
+ }
15779
+ e.stopPropagation();
15780
+ e.preventDefault();
15781
+ const offset = offsetFromPoint(this.target.getLayout(), local, this.target.measure);
15782
+ const text = this.model.getText();
15783
+ if (e.detail >= 3) {
15784
+ const [s, en] = paragraphRangeAt(text, offset);
15785
+ this.model.setSelection(s, en);
15786
+ } else if (e.detail === 2) {
15787
+ const [s, en] = wordRangeAt(text, offset);
15788
+ this.model.setSelection(s, en);
15789
+ } else {
15790
+ this.model.moveTo(offset, e.shiftKey);
15791
+ this.dragging = true;
15792
+ }
15793
+ this.syncTextarea();
15794
+ this.afterSelectionChange();
15795
+ this.textarea?.focus();
15796
+ };
15797
+ onPointerMove = (e) => {
15798
+ if (!this.dragging) return;
15799
+ const local = this.toLocal(e);
15800
+ const offset = offsetFromPoint(this.target.getLayout(), local, this.target.measure);
15801
+ this.model.moveTo(offset, true);
15802
+ this.syncTextarea();
15803
+ this.afterSelectionChange();
15804
+ };
15805
+ onPointerUp = () => {
15806
+ if (!this.dragging) return;
15807
+ this.dragging = false;
15808
+ this.textarea?.focus();
15809
+ };
15810
+ afterSelectionChange() {
15811
+ this.syncTextarea();
15812
+ this.positionTextarea();
15813
+ this.resetBlink();
15814
+ this.repaint();
15815
+ }
15816
+ afterContentChange(insertedLength, immediate = false) {
15817
+ this.flushPending();
15818
+ if (!immediate && insertedLength > 2e4 && typeof this.target.warmLayout === "function") {
15819
+ this.pendingFlush = true;
15820
+ this.sliceLayout();
15821
+ } else this.applyContent();
15822
+ this.resetBlink();
15823
+ }
15824
+ sliceLayout = () => {
15825
+ if (!this.pendingFlush || this.destroyed) return;
15826
+ if (this.target.warmLayout(this.model.getBlocks(), SLICE_BUDGET_MS)) this.flushPending();
15827
+ else this.pendingTimer = setTimeout(this.sliceLayout, 0);
15828
+ };
15829
+ flushPending() {
15830
+ if (!this.pendingFlush) return;
15831
+ this.pendingFlush = false;
15832
+ clearTimeout(this.pendingTimer);
15833
+ this.applyContent();
15834
+ }
15835
+ applyContent() {
15836
+ this.target.setBlocks(this.model.getBlocks());
15837
+ this.host.onChange?.(this.model.getBlocks());
15838
+ this.positionTextarea();
15839
+ this.target.requestRedraw();
15840
+ this.repaint();
15841
+ }
15842
+ createOverlay() {
15843
+ const layer = this.host.utilityLayer;
15844
+ if (!layer) return;
15845
+ const overlay = new Konva.Shape({
15846
+ name: "text-v2-edit-overlay",
15847
+ listening: false,
15848
+ sceneFunc: (ctx, shape) => this.paintOverlay(ctx, shape)
15849
+ });
15850
+ layer.add(overlay);
15851
+ overlay.moveToTop();
15852
+ this.overlay = overlay;
15853
+ }
15854
+ paintOverlay(ctx, shape) {
15855
+ if (this.destroyed || this.pendingFlush) return;
15856
+ const layout = this.target.getLayout();
15857
+ const abs = this.target.getAbsoluteTransform();
15858
+ const rel = shape.getAbsoluteTransform().copy().invert().multiply(abs);
15859
+ const o = this.target.getContentOffset();
15860
+ ctx.save();
15861
+ ctx.transform(rel.m[0], rel.m[1], rel.m[2], rel.m[3], rel.m[4], rel.m[5]);
15862
+ ctx.translate(o.x, o.y);
15863
+ const { start, end } = this.model.getSelection();
15864
+ if (start !== end) {
15865
+ ctx.setAttr("fillStyle", this.host.selectionColor);
15866
+ for (const r of selectionRects(layout, start, end, this.target.measure)) ctx.fillRect(r.x, r.y, r.width, r.height);
15867
+ } else if (this.caretOn) {
15868
+ const c = caretRect(layout, this.model.focus, this.target.measure);
15869
+ const scale = Math.hypot(abs.m[0], abs.m[1]) || 1;
15870
+ const w = Math.max(1, 1.5) / scale;
15871
+ ctx.setAttr("fillStyle", this.host.caretColor);
15872
+ ctx.fillRect(c.x - w / 2, c.y, w, c.height);
15873
+ }
15874
+ ctx.restore();
15875
+ }
15876
+ repaint() {
15877
+ this.overlay?.getLayer()?.batchDraw();
15878
+ }
15879
+ startBlink() {
15880
+ this.blinkTimer = setInterval(() => {
15881
+ this.caretOn = !this.caretOn;
15882
+ this.repaint();
15883
+ }, BLINK_MS);
15884
+ }
15885
+ resetBlink() {
15886
+ this.caretOn = true;
15887
+ if (this.blinkTimer) {
15888
+ clearInterval(this.blinkTimer);
15889
+ this.startBlink();
15890
+ }
15891
+ }
15892
+ };
15893
+ function host_signal(signal, abort) {
15894
+ if (!signal) return;
15895
+ if (signal.aborted) abort.abort();
15896
+ else signal.addEventListener("abort", () => abort.abort(), { once: true });
15897
+ }
15898
+ //#endregion
15899
+ //#region src/nodes/text-v2/constants.ts
15900
+ /** Default threshold for `onTextSizeWarning` (chars of serialized blocks). */
15901
+ const WEAVE_TEXT_V2_SIZE_WARNING_THRESHOLD = 2e5;
15902
+ const WEAVE_TEXT_V2_SELECTION_COLOR = "rgba(66, 133, 244, 0.35)";
15903
+ const WEAVE_TEXT_V2_DEFAULT_CONFIG = {
15904
+ ...WEAVE_TEXT_NODE_DEFAULT_CONFIG,
15905
+ sizeWarning: { threshold: WEAVE_TEXT_V2_SIZE_WARNING_THRESHOLD }
15906
+ };
15907
+ //#endregion
15908
+ //#region src/nodes/text-v2/text-v2.ts
15909
+ const CORNER_ANCHORS = [
15910
+ "top-left",
15911
+ "top-right",
15912
+ "bottom-left",
15913
+ "bottom-right"
15914
+ ];
15915
+ const SIDE_ANCHORS = ["middle-right", "middle-left"];
15916
+ /**
15917
+ * Drop-in replacement for `WeaveTextNode`. Same `nodeType` (`'text'`), so
15918
+ * existing documents load unchanged; register it INSTEAD of `WeaveTextNode`
15919
+ * (handlers are keyed by node type). Renders through `WeaveTextV2Shape`
15920
+ * (blocks/runs model + layout engine) instead of `Konva.Text`.
15921
+ */
15922
+ var WeaveTextV2Node = class extends WeaveNode {
15923
+ config;
15924
+ nodeType = WEAVE_TEXT_NODE_TYPE;
15925
+ createNode = false;
15926
+ editing = false;
15927
+ editingNodeId = null;
15928
+ session = null;
15929
+ sizeWarned = false;
15930
+ nodeRenderedAddedRegistered = false;
15931
+ keyPressHandler;
15932
+ constructor(params) {
15933
+ super();
15934
+ this.config = (0, import_merge.default)({}, WEAVE_TEXT_V2_DEFAULT_CONFIG, params?.config);
15935
+ }
15936
+ initialize() {
15937
+ this.keyPressHandler = void 0;
15938
+ this.editing = false;
15939
+ this.editingNodeId = null;
15940
+ this.session = null;
15941
+ this.nodeRenderedAddedRegistered = false;
15942
+ }
15943
+ /**
15944
+ * Resolve width/height for the layout mode. Auto modes size from the
15945
+ * engine's natural measure (independent of zoom, unlike the DOM-based
15946
+ * measure of the legacy node).
15947
+ */
15948
+ resolveSize(shape, props) {
15949
+ let width = props.width;
15950
+ let height = props.height;
15951
+ if (props.layout === TEXT_LAYOUT.AUTO_ALL) {
15952
+ const nat = shape.measureNaturalSize();
15953
+ width = nat.width + 2;
15954
+ height = nat.height + 2;
15955
+ }
15956
+ if (props.layout === TEXT_LAYOUT.SMART && !props.smartFixedWidth) {
15957
+ width = Math.max(1, shape.measureNaturalSize().width * 1.01);
15958
+ height = void 0;
15959
+ }
15960
+ if (props.layout === TEXT_LAYOUT.SMART && props.smartFixedWidth) height = void 0;
15961
+ if (props.layout === TEXT_LAYOUT.AUTO_HEIGHT) height = void 0;
15962
+ return {
15963
+ width,
15964
+ height
15965
+ };
15966
+ }
15967
+ measureMultilineText(shape) {
15968
+ return () => {
15969
+ const nat = shape.measureNaturalSize();
15970
+ return {
15971
+ width: Math.max(1, nat.width),
15972
+ height: Math.max(1, nat.height)
15973
+ };
15974
+ };
15975
+ }
15976
+ linkOverrides(props) {
15977
+ return {
15978
+ linkPreviousFill: props.link ? props.linkPreviousFill ?? props.fill : props.linkPreviousFill,
15979
+ linkPreviousTextDecoration: props.link ? props.linkPreviousTextDecoration ?? props.textDecoration : props.linkPreviousTextDecoration
15980
+ };
15981
+ }
15982
+ outlineProps() {
15983
+ return this.config.outline.enabled ? {
15984
+ strokeEnabled: true,
15985
+ stroke: this.config.outline.color,
15986
+ strokeWidth: this.config.outline.width,
15987
+ fillAfterStrokeEnabled: true
15988
+ } : { strokeEnabled: false };
15989
+ }
15990
+ onRender(props) {
15991
+ const { linkPreviousFill, linkPreviousTextDecoration } = this.linkOverrides(props);
15992
+ const text = new WeaveTextV2Shape({
15993
+ ...props,
15994
+ name: "node",
15995
+ ...this.outlineProps(),
15996
+ ...props.link && {
15997
+ textDecoration: "underline",
15998
+ fill: this.config.link.defaultColor,
15999
+ linkPreviousFill,
16000
+ linkPreviousTextDecoration
16001
+ }
16002
+ });
16003
+ this.setupDefaultNodeAugmentation(text);
16004
+ const defaultTransformerProperties = this.defaultGetTransformerProperties(this.config.transform);
16005
+ text.getTransformerProperties = function() {
16006
+ const layout = this.getAttrs().layout;
16007
+ if (layout === TEXT_LAYOUT.SMART) return {
16008
+ ...defaultTransformerProperties,
16009
+ ignoreStroke: true,
16010
+ resizeEnabled: true,
16011
+ keepRatio: false,
16012
+ enabledAnchors: []
16013
+ };
16014
+ if (layout === TEXT_LAYOUT.AUTO_ALL) return {
16015
+ ...defaultTransformerProperties,
16016
+ ignoreStroke: true,
16017
+ resizeEnabled: false,
16018
+ enabledAnchors: []
16019
+ };
16020
+ if (layout === TEXT_LAYOUT.AUTO_HEIGHT) return {
16021
+ ...defaultTransformerProperties,
16022
+ ignoreStroke: true,
16023
+ resizeEnabled: true,
16024
+ enabledAnchors: SIDE_ANCHORS
16025
+ };
16026
+ return defaultTransformerProperties;
16027
+ };
16028
+ text.allowedAnchors = function() {
16029
+ const layout = this.getAttrs().layout;
16030
+ if (layout === TEXT_LAYOUT.SMART) return [...CORNER_ANCHORS, ...SIDE_ANCHORS];
16031
+ if (layout === TEXT_LAYOUT.AUTO_ALL) return [];
16032
+ if (layout === TEXT_LAYOUT.AUTO_HEIGHT) return SIDE_ANCHORS;
16033
+ return [
16034
+ ...CORNER_ANCHORS,
16035
+ "top-center",
16036
+ "bottom-center",
16037
+ ...SIDE_ANCHORS
16038
+ ];
16039
+ };
16040
+ text.setAttrs({ measureMultilineText: this.measureMultilineText(text) });
16041
+ this.setupDefaultNodeEvents(text, { performScaleReset: false });
16042
+ text.dblClick = () => {
16043
+ if (this.editing) return;
16044
+ if (!(this.isSelecting() && this.isNodeSelected(text))) return;
16045
+ this.triggerEditMode(text);
16046
+ };
16047
+ text.setAttr("triggerEditMode", this.triggerEditMode.bind(this));
16048
+ this.setupLinkBehavior(text);
16049
+ this.setupTransform(text);
16050
+ if (!this.nodeRenderedAddedRegistered) {
16051
+ this.instance.addEventListener("onNodeRenderedAdded", (node) => {
16052
+ if (this.editing && this.editingNodeId !== null && node.id() === this.editingNodeId && node.getAttr("cancelEditMode")) node.getAttr("cancelEditMode")?.();
16053
+ });
16054
+ this.nodeRenderedAddedRegistered = true;
16055
+ }
16056
+ this.registerKeyPress();
16057
+ return text;
16058
+ }
16059
+ handleKeyPress = (e) => {
16060
+ if (e.code !== "Enter" || this.instance.getActiveAction() !== "selectionTool" || this.editing) return;
16061
+ e.preventDefault();
16062
+ const selected = this.getNodesSelectionPlugin()?.getSelectedNodes() ?? [];
16063
+ if (this.isSelecting() && selected.length === 1 && selected[0].getAttrs().nodeType === "text") this.triggerEditMode(selected[0]);
16064
+ };
16065
+ registerKeyPress() {
16066
+ if (this.instance.isServerSide() || this.keyPressHandler) return;
16067
+ this.keyPressHandler = this.handleKeyPress.bind(this);
16068
+ window.addEventListener("keypress", this.keyPressHandler, { signal: this.instance.getEventsController().signal });
16069
+ }
16070
+ onAdd() {
16071
+ this.registerKeyPress();
16072
+ }
16073
+ onDestroyInstance() {
16074
+ super.onDestroyInstance();
16075
+ this.session?.destroy();
16076
+ this.session = null;
16077
+ if (!this.instance.isServerSide() && this.keyPressHandler) {
16078
+ window.removeEventListener("keypress", this.keyPressHandler);
16079
+ this.keyPressHandler = void 0;
16080
+ }
16081
+ }
16082
+ /** Re-apply layout-driven width/height after live content changes. */
16083
+ applySize(shape) {
16084
+ const { width, height } = this.resolveSize(shape, shape.getAttrs());
16085
+ shape.setAttrs({
16086
+ width,
16087
+ height
16088
+ });
16089
+ }
16090
+ editTarget(shape) {
16091
+ return {
16092
+ getBlocks: () => shape.getBlocks(),
16093
+ setBlocks: (blocks) => {
16094
+ shape.setBlocks(blocks);
16095
+ this.applySize(shape);
16096
+ },
16097
+ getLayout: () => shape.ensureLayout(),
16098
+ measure: (t) => shape.measureText(t),
16099
+ getAbsoluteTransform: () => shape.getAbsoluteTransform(),
16100
+ getContentOffset: () => shape.getContentOffset(),
16101
+ getSize: () => ({
16102
+ width: shape.getWidth(),
16103
+ height: shape.getHeight()
16104
+ }),
16105
+ getFontSize: () => shape.fontSize(),
16106
+ requestRedraw: () => shape.getLayer()?.batchDraw(),
16107
+ warmLayout: (blocks, budget) => shape.warmLayout(blocks, budget)
16108
+ };
16109
+ }
16110
+ triggerEditMode(shape, create = false) {
16111
+ if (create) this.createNode = true;
16112
+ if (!this.instance.setMutexLock({
16113
+ nodeIds: [shape.id()],
16114
+ operation: "text-edit"
16115
+ })) return;
16116
+ this.editing = true;
16117
+ this.editingNodeId = shape.id();
16118
+ this.sizeWarned = false;
16119
+ const selectionPlugin = this.instance.getPlugin("nodesSelection");
16120
+ if (selectionPlugin) {
16121
+ const tr = selectionPlugin.getTransformer();
16122
+ this.instance.disablePlugin("nodesSelection");
16123
+ tr.hide();
16124
+ }
16125
+ const stage = this.instance.getStage();
16126
+ const container = stage.container();
16127
+ const rect = () => container.getBoundingClientRect();
16128
+ this.session = new TextEditSession(this.editTarget(shape), {
16129
+ utilityLayer: this.instance.getUtilityLayer(),
16130
+ container,
16131
+ clientToStage: (cx, cy) => {
16132
+ const r = rect();
16133
+ const sx = r.width ? stage.width() / r.width : 1;
16134
+ const sy = r.height ? stage.height() / r.height : 1;
16135
+ return {
16136
+ x: (cx - r.left) * sx,
16137
+ y: (cy - r.top) * sy
16138
+ };
16139
+ },
16140
+ stageToContainer: (p) => {
16141
+ const r = rect();
16142
+ const sx = r.width ? r.width / stage.width() : 1;
16143
+ const sy = r.height ? r.height / stage.height() : 1;
16144
+ return {
16145
+ x: p.x * sx,
16146
+ y: p.y * sy
16147
+ };
16148
+ },
16149
+ caretColor: this.config.cursor.color,
16150
+ selectionColor: WEAVE_TEXT_V2_SELECTION_COLOR,
16151
+ signal: this.instance.getEventsController().signal,
16152
+ onChange: (blocks) => this.checkSize(shape, blocks),
16153
+ onRequestExit: () => this.exitEditMode(shape)
16154
+ }, shape.id());
16155
+ shape.setAttr("cancelEditMode", () => this.exitEditMode(shape));
16156
+ stage.mode(WEAVE_STAGE_TEXT_EDITION_MODE);
16157
+ this.session.start();
16158
+ this.instance.emitEvent("onEnterTextNodeEditMode", { node: shape });
16159
+ }
16160
+ checkSize(shape, blocks) {
16161
+ const threshold = this.config.sizeWarning.threshold;
16162
+ if (blocksToText(blocks).length * 8 < threshold) {
16163
+ this.sizeWarned = false;
16164
+ return;
16165
+ }
16166
+ const size = JSON.stringify(blocks).length;
16167
+ if (size > threshold && !this.sizeWarned) {
16168
+ this.sizeWarned = true;
16169
+ this.instance.emitEvent("onTextSizeWarning", {
16170
+ node: shape,
16171
+ size,
16172
+ threshold
16173
+ });
16174
+ } else if (size <= threshold) this.sizeWarned = false;
16175
+ }
16176
+ exitEditMode(shape) {
16177
+ const session = this.session;
16178
+ if (!session || !this.editing) return;
16179
+ const { changed } = session.getResult();
16180
+ session.destroy();
16181
+ this.session = null;
16182
+ this.instance.releaseMutexLock();
16183
+ const stage = this.instance.getStage();
16184
+ stage.mode(WEAVE_STAGE_DEFAULT_MODE);
16185
+ this.editing = false;
16186
+ this.editingNodeId = null;
16187
+ shape.setAttr("cancelEditMode", void 0);
16188
+ this.commitNode(shape, changed);
16189
+ const liveNode = stage.findOne(`#${shape.id()}`) ?? shape;
16190
+ const selectionPlugin = this.instance.getPlugin("nodesSelection");
16191
+ if (selectionPlugin) {
16192
+ this.instance.enablePlugin("nodesSelection");
16193
+ selectionPlugin.setSelectedNodes([liveNode]);
16194
+ this.instance.triggerAction(SELECTION_TOOL_ACTION_NAME);
16195
+ }
16196
+ stage.container().tabIndex = 1;
16197
+ stage.container().click();
16198
+ stage.container().focus();
16199
+ this.instance.emitEvent("onExitTextNodeEditMode", { node: liveNode });
16200
+ }
16201
+ /** One `updateNode` per session; none when content is unchanged. */
16202
+ commitNode(shape, changed) {
16203
+ const live = this.instance.getStage().findOne(`#${shape.id()}`) ?? shape;
16204
+ if (this.createNode) {
16205
+ const serialized = this.serialize(live);
16206
+ if (live.text() !== "") {
16207
+ const container = live.getParent();
16208
+ live.destroy();
16209
+ this.instance.addNode(serialized, container?.getAttrs().id);
16210
+ } else live.destroy();
16211
+ } else if (changed) this.instance.updateNode(this.serialize(live));
16212
+ this.createNode = false;
16213
+ }
16214
+ setupTransform(text) {
16215
+ let actualAnchor = void 0;
16216
+ const isSide = () => SIDE_ANCHORS.includes(actualAnchor ?? "");
16217
+ const isCorner = () => CORNER_ANCHORS.includes(actualAnchor ?? "");
16218
+ const autoWidthLayout = () => [TEXT_LAYOUT.AUTO_HEIGHT, TEXT_LAYOUT.SMART].includes(text.getAttrs().layout);
16219
+ text.on("transformstart", (e) => {
16220
+ const ctrlOrMeta = e.evt.ctrlKey || e.evt.metaKey;
16221
+ this.instance.emitEvent("onTransform", e.target);
16222
+ actualAnchor = this.getNodesSelectionPlugin()?.getTransformer()?.getActiveAnchor();
16223
+ const layout = text.getAttrs().layout;
16224
+ this.getNodesSelectionPlugin()?.getTransformer()?.keepRatio(layout === TEXT_LAYOUT.SMART && isCorner() || layout === TEXT_LAYOUT.FIXED && ctrlOrMeta);
16225
+ if (autoWidthLayout() && isSide()) {
16226
+ text.wrap("word");
16227
+ text.scaleY(1);
16228
+ text.height(void 0);
16229
+ }
16230
+ e.cancelBubble = true;
16231
+ });
16232
+ text.on("transform", () => {
16233
+ if (autoWidthLayout() && isSide()) {
16234
+ text.width(text.width() * text.scaleX());
16235
+ text.scaleX(1);
16236
+ text.scaleY(1);
16237
+ text.height(void 0);
16238
+ text.getLayer()?.batchDraw();
16239
+ }
16240
+ const layout = text.getAttrs().layout;
16241
+ if (this.isSelecting() && this.isNodeSelected(text) && (layout !== TEXT_LAYOUT.SMART || !isCorner())) {
16242
+ text.width(text.width() * text.scaleX());
16243
+ resetScale(text);
16244
+ text.fontSize(text.fontSize() * text.scaleY());
16245
+ }
16246
+ text.setAttr("shouldUpdateOnTransform", false);
16247
+ text.getLayer()?.batchDraw();
16248
+ });
16249
+ text.on("transformend", () => {
16250
+ this.instance.emitEvent("onTransform", null);
16251
+ let definedSmartWidth = false;
16252
+ let smartFixedWidth = text.getAttr("smartFixedWidth") ?? false;
16253
+ const layout = text.getAttrs().layout;
16254
+ if (layout !== TEXT_LAYOUT.SMART && !isCorner()) this.scaleReset(text);
16255
+ if (layout === TEXT_LAYOUT.SMART && isCorner()) text.setAttrs({
16256
+ width: Math.ceil(text.width() * text.scaleX()),
16257
+ height: Math.ceil(text.height() * text.scaleY()),
16258
+ fontSize: text.fontSize() * text.scaleY(),
16259
+ scaleX: 1,
16260
+ scaleY: 1
16261
+ });
16262
+ if (autoWidthLayout() && isSide() && !smartFixedWidth && !definedSmartWidth) {
16263
+ text.setAttr("smartFixedWidth", true);
16264
+ smartFixedWidth = true;
16265
+ definedSmartWidth = true;
16266
+ text.width(Math.ceil(text.width() * text.scaleX()));
16267
+ text.scaleX(1);
16268
+ text.scaleY(1);
16269
+ text.height(void 0);
16270
+ text.getLayer()?.batchDraw();
16271
+ text.height(Math.ceil(text.height()));
16272
+ }
16273
+ if (layout === TEXT_LAYOUT.SMART && isSide() && smartFixedWidth && !definedSmartWidth) {
16274
+ text.width(Math.ceil(text.width() * text.scaleX()));
16275
+ text.scaleX(1);
16276
+ }
16277
+ this.instance.updateNode(this.serialize(text));
16278
+ });
16279
+ }
16280
+ onUpdate(nodeInstance, nextProps) {
16281
+ const shape = nodeInstance;
16282
+ if (this.editing && this.editingNodeId === shape.id()) nextProps = {
16283
+ ...nextProps,
16284
+ text: shape.attrs.text,
16285
+ blocks: shape.attrs.blocks
16286
+ };
16287
+ const isLinked = Boolean(nextProps.link);
16288
+ const { linkPreviousFill, linkPreviousTextDecoration } = this.linkOverrides(nextProps);
16289
+ shape.setAttrs({
16290
+ ...nextProps,
16291
+ ...this.outlineProps(),
16292
+ link: nextProps.link,
16293
+ linkPreviousFill: isLinked ? linkPreviousFill : void 0,
16294
+ linkPreviousTextDecoration: isLinked ? linkPreviousTextDecoration : void 0,
16295
+ ...isLinked && {
16296
+ textDecoration: "underline",
16297
+ fill: this.config.link.defaultColor
16298
+ }
16299
+ });
16300
+ if (nextProps.blocks === void 0 && shape.attrs.blocks !== void 0) shape.setAttr("blocks", void 0);
16301
+ const { width, height } = this.resolveSize(shape, nextProps);
16302
+ shape.setAttrs({
16303
+ width,
16304
+ height
16305
+ });
16306
+ if (!this.editing) {
16307
+ const selection = this.getNodesSelectionPlugin();
16308
+ if (selection) selection.setSelectedNodes(selection.getSelectedNodes());
16309
+ }
16310
+ }
16311
+ serialize(instance) {
16312
+ const attrs = instance.getAttrs();
16313
+ const cleanedAttrs = { ...attrs };
16314
+ delete cleanedAttrs.mutexLocked;
16315
+ delete cleanedAttrs.mutexUserId;
16316
+ delete cleanedAttrs.draggable;
16317
+ delete cleanedAttrs.triggerEditMode;
16318
+ delete cleanedAttrs.cancelEditMode;
16319
+ delete cleanedAttrs.measureMultilineText;
16320
+ delete cleanedAttrs.overridesMouseControl;
16321
+ delete cleanedAttrs.shouldUpdateOnTransform;
16322
+ delete cleanedAttrs.dragBoundFunc;
16323
+ if (cleanedAttrs.link) {
16324
+ if (typeof cleanedAttrs.linkPreviousFill === "string") cleanedAttrs.fill = cleanedAttrs.linkPreviousFill;
16325
+ if (typeof cleanedAttrs.linkPreviousTextDecoration === "string") cleanedAttrs.textDecoration = cleanedAttrs.linkPreviousTextDecoration;
16326
+ }
16327
+ return {
16328
+ key: attrs.id ?? "",
16329
+ type: attrs.nodeType,
16330
+ props: {
16331
+ ...cleanedAttrs,
16332
+ isCloned: void 0,
16333
+ isCloneOrigin: void 0,
16334
+ id: attrs.id ?? "",
16335
+ nodeType: attrs.nodeType,
16336
+ children: []
16337
+ }
16338
+ };
16339
+ }
16340
+ setupLinkBehavior(text) {
16341
+ text.handleMouseover = () => {
16342
+ if (!text.getAttrs().link) return;
16343
+ text.fill(this.config.link.hoverColor);
16344
+ text.getLayer()?.batchDraw();
16345
+ };
16346
+ text.handleMouseout = () => {
16347
+ if (!text.getAttrs().link) return;
16348
+ text.fill(this.config.link.defaultColor);
16349
+ text.getLayer()?.batchDraw();
16350
+ };
16351
+ text.defineMousePointer = () => text.getAttrs().link ? "pointer" : "default";
16352
+ let linkClickTimeout;
16353
+ const clearLinkClickTimeout = () => {
16354
+ if (linkClickTimeout) {
16355
+ clearTimeout(linkClickTimeout);
16356
+ linkClickTimeout = void 0;
16357
+ }
16358
+ };
16359
+ const previousDblClick = text.dblClick?.bind(text) ?? (() => {});
16360
+ text.dblClick = () => {
16361
+ clearLinkClickTimeout();
16362
+ previousDblClick();
16363
+ };
16364
+ text.click = ({ wasSelected, ctrlOrMetaPressed }) => {
16365
+ if (!text.getAttrs().link) return;
16366
+ if (ctrlOrMetaPressed) {
16367
+ clearLinkClickTimeout();
16368
+ this.openTextLink(text);
16369
+ return;
16370
+ }
16371
+ if (wasSelected) {
16372
+ clearLinkClickTimeout();
16373
+ linkClickTimeout = setTimeout(() => {
16374
+ linkClickTimeout = void 0;
16375
+ this.openTextLink(text);
16376
+ }, Konva.dblClickWindow);
16377
+ }
16378
+ };
16379
+ }
16380
+ openTextLink(textNode) {
16381
+ if (this.instance.isServerSide()) return;
16382
+ const link = textNode.getAttrs().link;
16383
+ if (typeof link !== "string" || !isValidTextLink(link)) return;
16384
+ window.open(link, "_blank", "noopener,noreferrer");
16385
+ }
16386
+ getLink(nodeInstance) {
16387
+ const link = nodeInstance.getAttrs().link;
16388
+ return typeof link === "string" ? link : void 0;
16389
+ }
16390
+ setLink(nodeInstance, url) {
16391
+ if (!isValidTextLink(url)) return;
16392
+ const serialized = this.serialize(nodeInstance);
16393
+ serialized.props.linkPreviousFill = serialized.props.fill;
16394
+ serialized.props.linkPreviousTextDecoration = serialized.props.textDecoration;
16395
+ serialized.props.link = url;
16396
+ this.instance.updateNode(serialized);
16397
+ }
16398
+ removeLink(nodeInstance) {
16399
+ const serialized = this.serialize(nodeInstance);
16400
+ delete serialized.props.link;
16401
+ delete serialized.props.linkPreviousFill;
16402
+ delete serialized.props.linkPreviousTextDecoration;
16403
+ this.instance.updateNode(serialized);
16404
+ }
16405
+ resetSmartLayout(textNode) {
16406
+ textNode.setAttr("smartFixedWidth", void 0);
16407
+ textNode.width(Math.max(1, textNode.measureNaturalSize().width * 1.01));
16408
+ this.instance.updateNode(this.serialize(textNode));
16409
+ }
16410
+ static defaultState(nodeId, params) {
16411
+ return WeaveTextNode.defaultState(nodeId, params);
16412
+ }
16413
+ static addNodeState(defaultNodeState, props) {
16414
+ return mergeExceptArrays(WeaveTextNode.addNodeState(defaultNodeState, props), { props: { ...props.blocks && { blocks: props.blocks } } });
14331
16415
  }
14332
16416
  static updateNodeState(prevNodeState, nextProps) {
14333
- return mergeExceptArrays(prevNodeState, { props: {
14334
- x: nextProps.x,
14335
- y: nextProps.y,
14336
- width: nextProps.width,
14337
- ...nextProps.height && { height: nextProps.height },
14338
- FontFamily: nextProps.fontFamily,
14339
- fontSize: nextProps.fontSize,
14340
- fontStyle: nextProps.fontStyle,
14341
- fontVariant: nextProps.fontVariant,
14342
- textDecoration: nextProps.textDecoration,
14343
- letterSpacing: nextProps.letterSpacing,
14344
- lineHeight: nextProps.lineHeight,
14345
- align: nextProps.align,
14346
- verticalAlign: nextProps.verticalAlign,
14347
- rotation: nextProps.rotation,
14348
- fill: nextProps.fill,
14349
- text: nextProps.text,
14350
- layout: nextProps.layout,
14351
- ...nextProps.strokeEnabled && { strokeEnabled: nextProps.strokeEnabled },
14352
- ...nextProps.stroke && { stroke: nextProps.stroke },
14353
- ...nextProps.strokeWidth && { strokeWidth: nextProps.strokeWidth },
14354
- ...nextProps.fillAfterStrokeEnabled && { fillAfterStrokeEnabled: nextProps.fillAfterStrokeEnabled },
14355
- ...nextProps.link && { link: nextProps.link }
14356
- } });
16417
+ const next = WeaveTextNode.updateNodeState(prevNodeState, nextProps);
16418
+ if (nextProps.blocks) next.props.blocks = nextProps.blocks;
16419
+ else delete next.props.blocks;
16420
+ return next;
14357
16421
  }
14358
16422
  static getSchema() {
14359
- return super.getSchema().extend({
14360
- type: z.literal(WEAVE_TEXT_NODE_TYPE).describe(`Type of the node, for a text node it will always be "${WEAVE_TEXT_NODE_TYPE}"`),
14361
- props: z.object({
14362
- nodeType: z.literal(WEAVE_TEXT_NODE_TYPE).describe(`Type of the node, for a text node it will always be "${WEAVE_TEXT_NODE_TYPE}"`),
14363
- width: z.number().describe("Width of the text in pixels"),
14364
- height: z.number().optional().describe("Height of the text in pixels. Optional if layout is auto-height or smart."),
14365
- fontFamily: z.string().default("Arial").describe("Font family of the text, e.g. Arial, Helvetica, etc."),
14366
- fontSize: z.number().default(16).describe("Font size of the text in pixels."),
14367
- fontStyle: z.string().regex(/^(?:normal|bold|\d+)(?: italic)?$/).default("normal").describe("Font style of the text, can be \"normal\", \"bold\", \"400\", \"italic\" or a combination like \"bold italic\" or \"700 italic\"."),
14368
- fontVariant: z.enum(["normal", "small-caps"]).describe("Font variant of the text, can be \"normal\" or \"small-caps\"."),
14369
- textDecoration: z.enum([
14370
- "line-through",
14371
- "underline",
14372
- ""
14373
- ]).default("").describe("Text decoration can be \"line-through\", \"underline\" or empty string for none."),
14374
- letterSpacing: z.number().default(0).describe("Spacing between letters in pixels."),
14375
- lineHeight: z.number().default(1).describe("Line height of the text, as a multiplier of font size."),
14376
- align: z.enum([
14377
- "left",
14378
- "center",
14379
- "right",
14380
- "justify"
14381
- ]).default("left").describe("Text alignment, can be \"left\", \"center\", \"right\" or \"justify\"."),
14382
- verticalAlign: z.enum([
14383
- "top",
14384
- "middle",
14385
- "bottom"
14386
- ]).default("top").describe("Vertical alignment of the text, can be 'top', 'middle' or 'bottom'."),
14387
- fill: z.string().default("#000000ff").describe("Fill color of the text in hex format with alpha channel (e.g. #RRGGBBAA)."),
14388
- text: z.string().default("text").describe("The actual text content of the node."),
14389
- link: z.string().url().refine((value) => isValidTextLink(value), { message: "Link must be an absolute http:// or https:// URL." }).optional().describe("External URL the whole text of the node links to. When set, the text is forced underlined and coloured with the configured link colours regardless of fill/textDecoration, is clickable, and opens the URL in a new tab. Only http(s) URLs are accepted. Omit/remove to make the node plain text again."),
14390
- linkPreviousFill: z.string().optional().describe("Internal bookkeeping: the fill colour the node had right before a link was set, captured automatically so it can be restored when the link is removed. Managed automatically — do not set directly."),
14391
- linkPreviousTextDecoration: z.string().optional().describe("Internal bookkeeping: the textDecoration the node had right before a link was set, captured automatically so it can be restored when the link is removed. Managed automatically — do not set directly."),
14392
- strokeEnabled: z.boolean().default(false).describe("Whether the text outline is enabled."),
14393
- stroke: z.string().optional().default("#d6d6d6").describe("Color of the text outline in hex format with alpha channel (e.g. #RRGGBBAA)."),
14394
- strokeWidth: z.number().optional().default(2).describe("Width of the text outline in pixels."),
14395
- strokeScaleEnabled: z.boolean().default(true).describe("Whether the stroke width should scale when the node is scaled."),
14396
- fillAfterStrokeEnabled: z.boolean().default(true).describe("Whether the fill should be drawn after the stroke. If false, the stroke will be drawn on top of the fill."),
14397
- layout: z.enum(TEXT_LAYOUT).default(TEXT_LAYOUT.SMART).describe("Layout mode of the text node. Can be:\n- 'fixed': the text node will have fixed width and height, and the text will be scaled to fit the node.\n- 'auto-height': the width of the text node will be fixed, but the height will adjust to fit the text content.\n- 'auto-all': both width and height of the text node will adjust to fit the text content.\n- 'smart': the text node will try to adjust its size based on the content and layout, but it will not exceed the initial width and height set on the node.")
14398
- })
14399
- });
16423
+ return WeaveTextNode.getSchema();
14400
16424
  }
14401
16425
  };
14402
16426
  //#endregion
@@ -14487,24 +16511,64 @@ var WeaveImageCrop = class WeaveImageCrop {
14487
16511
  this.onClose = () => {};
14488
16512
  this.handleHide = this.hide.bind(this);
14489
16513
  }
14490
- setupCropImage(imageAttrs, visible) {
14491
- this.cropGroup.destroyChildren();
16514
+ /**
16515
+ * Mirrors the start of a segment inside a container along one axis:
16516
+ * the segment [start, start + length] inside [0, size] becomes
16517
+ * [size - start - length, size - start]. Used to convert between visual
16518
+ * space (what the user sees on a flipped image) and source space (where
16519
+ * the crop information is stored). It is its own inverse.
16520
+ */
16521
+ static mirrorOffset(start, length, size) {
16522
+ return size - start - length;
16523
+ }
16524
+ /**
16525
+ * Returns the offset (in uncropped display units) of the visible crop
16526
+ * window inside the visually rendered uncropped image, along with the real
16527
+ * scale. When the image is flipped the stored crop (source space) is
16528
+ * mirrored to match what the user sees.
16529
+ */
16530
+ getVisualCropOffset(imageAttrs) {
14492
16531
  const actualScale = imageAttrs.uncroppedImage.width / imageAttrs.imageInfo.width;
14493
16532
  const realScale = actualScale / (imageAttrs.cropInfo ? imageAttrs.cropInfo.scaleX : actualScale);
16533
+ let x = 0;
16534
+ let y = 0;
16535
+ if (imageAttrs.cropInfo) {
16536
+ x = imageAttrs.cropInfo.x * realScale;
16537
+ y = imageAttrs.cropInfo.y * realScale;
16538
+ if (imageAttrs.flipX === true) x = WeaveImageCrop.mirrorOffset(x, imageAttrs.cropInfo.width * realScale, imageAttrs.uncroppedImage.width);
16539
+ if (imageAttrs.flipY === true) y = WeaveImageCrop.mirrorOffset(y, imageAttrs.cropInfo.height * realScale, imageAttrs.uncroppedImage.height);
16540
+ }
16541
+ return {
16542
+ x,
16543
+ y,
16544
+ realScale
16545
+ };
16546
+ }
16547
+ setupCropImage(imageAttrs, visible) {
16548
+ this.cropGroup.destroyChildren();
16549
+ const { x, y, realScale } = this.getVisualCropOffset(imageAttrs);
16550
+ const flipX = imageAttrs.flipX === true;
16551
+ const flipY = imageAttrs.flipY === true;
14494
16552
  this.cropImage = new Konva.Image({
14495
- x: imageAttrs.cropInfo ? -imageAttrs.cropInfo.x * realScale : 0,
14496
- y: imageAttrs.cropInfo ? -imageAttrs.cropInfo.y * realScale : 0,
16553
+ x: -x,
16554
+ y: -y,
14497
16555
  width: imageAttrs.uncroppedImage.width,
14498
16556
  height: imageAttrs.uncroppedImage.height,
14499
- scaleX: 1,
14500
- scaleY: 1,
16557
+ scaleX: flipX ? -1 : 1,
16558
+ scaleY: flipY ? -1 : 1,
16559
+ offsetX: flipX ? imageAttrs.uncroppedImage.width : 0,
16560
+ offsetY: flipY ? imageAttrs.uncroppedImage.height : 0,
14501
16561
  image: this.internalImage.image(),
14502
16562
  crop: void 0,
14503
16563
  visible,
14504
16564
  listening: false,
14505
16565
  draggable: false
14506
16566
  });
14507
- return { realScale };
16567
+ return {
16568
+ realScale,
16569
+ offsetX: x,
16570
+ offsetY: y
16571
+ };
14508
16572
  }
14509
16573
  show(onClose, options) {
14510
16574
  this.onClose = onClose;
@@ -14515,9 +16579,9 @@ var WeaveImageCrop = class WeaveImageCrop {
14515
16579
  this.image.listening(false);
14516
16580
  const imageAttrs = this.image.getAttrs();
14517
16581
  this.internalImage.hide();
14518
- const { realScale } = this.setupCropImage(imageAttrs, true);
14519
- this.imageOffsetX = imageAttrs.cropInfo ? imageAttrs.cropInfo.x * realScale : 0;
14520
- this.imageOffsetY = imageAttrs.cropInfo ? imageAttrs.cropInfo.y * realScale : 0;
16582
+ const { realScale, offsetX, offsetY } = this.setupCropImage(imageAttrs, true);
16583
+ this.imageOffsetX = offsetX;
16584
+ this.imageOffsetY = offsetY;
14521
16585
  const cropModeConfiguration = this.node.getConfiguration().cropMode;
14522
16586
  this.cropRect = new Konva.Rect({
14523
16587
  x: 0,
@@ -14565,7 +16629,10 @@ var WeaveImageCrop = class WeaveImageCrop {
14565
16629
  const center = this.cropImage.getAbsolutePosition();
14566
16630
  const width = this.cropImage.width() * stage.scaleX();
14567
16631
  const height = this.cropImage.height() * stage.scaleY();
14568
- const offset = this.cropImage.offset();
16632
+ const offset = {
16633
+ x: this.cropImage.scaleX() < 0 ? 0 : this.cropImage.offsetX(),
16634
+ y: this.cropImage.scaleY() < 0 ? 0 : this.cropImage.offsetY()
16635
+ };
14569
16636
  const corners = [
14570
16637
  {
14571
16638
  x: -offset.x,
@@ -14909,11 +16976,15 @@ var WeaveImageCrop = class WeaveImageCrop {
14909
16976
  if (!intersectionRect) return;
14910
16977
  const imageAttrs = this.image.getAttrs();
14911
16978
  const actualScale = imageAttrs.uncroppedImage.width / imageAttrs.imageInfo.width;
16979
+ let sourceX = clipRectGroup.x + this.imageOffsetX;
16980
+ let sourceY = clipRectGroup.y + this.imageOffsetY;
16981
+ if (imageAttrs.flipX === true) sourceX = WeaveImageCrop.mirrorOffset(sourceX, clipRectGroup.width, imageAttrs.uncroppedImage.width);
16982
+ if (imageAttrs.flipY === true) sourceY = WeaveImageCrop.mirrorOffset(sourceY, clipRectGroup.height, imageAttrs.uncroppedImage.height);
14912
16983
  const realClipRect = {
14913
16984
  scaleX: actualScale,
14914
16985
  scaleY: actualScale,
14915
- x: WeaveImageCrop.roundTo6Decimals(clipRectGroup.x + this.imageOffsetX),
14916
- y: WeaveImageCrop.roundTo6Decimals(clipRectGroup.y + this.imageOffsetY),
16986
+ x: WeaveImageCrop.roundTo6Decimals(sourceX),
16987
+ y: WeaveImageCrop.roundTo6Decimals(sourceY),
14917
16988
  width: WeaveImageCrop.roundTo6Decimals(clipRectGroup.width),
14918
16989
  height: WeaveImageCrop.roundTo6Decimals(clipRectGroup.height)
14919
16990
  };
@@ -14955,16 +17026,18 @@ var WeaveImageCrop = class WeaveImageCrop {
14955
17026
  if (!intersectionRect || !intersectionRectAbs) return;
14956
17027
  const imageAttrs = this.image.getAttrs();
14957
17028
  const actualScale = imageAttrs.uncroppedImage.width / imageAttrs.imageInfo.width;
14958
- const realScale = actualScale / (imageAttrs.cropInfo ? imageAttrs.cropInfo.scaleX : actualScale);
14959
- const imageOffsetX = imageAttrs.cropInfo ? imageAttrs.cropInfo.x * realScale : 0;
14960
- const imageOffsetY = imageAttrs.cropInfo ? imageAttrs.cropInfo.y * realScale : 0;
17029
+ const { x: imageOffsetX, y: imageOffsetY } = this.getVisualCropOffset(imageAttrs);
14961
17030
  const diffX = intersectionRect.x - imageRect.x;
14962
17031
  const diffY = intersectionRect.y - imageRect.y;
17032
+ let sourceX = diffX + imageOffsetX;
17033
+ let sourceY = diffY + imageOffsetY;
17034
+ if (imageAttrs.flipX === true) sourceX = WeaveImageCrop.mirrorOffset(sourceX, intersectionRect.width, imageAttrs.uncroppedImage.width);
17035
+ if (imageAttrs.flipY === true) sourceY = WeaveImageCrop.mirrorOffset(sourceY, intersectionRect.height, imageAttrs.uncroppedImage.height);
14963
17036
  const realClipRect = {
14964
17037
  scaleX: actualScale,
14965
17038
  scaleY: actualScale,
14966
- x: WeaveImageCrop.roundTo6Decimals(diffX + imageOffsetX),
14967
- y: WeaveImageCrop.roundTo6Decimals(diffY + imageOffsetY),
17039
+ x: WeaveImageCrop.roundTo6Decimals(sourceX),
17040
+ y: WeaveImageCrop.roundTo6Decimals(sourceY),
14968
17041
  width: WeaveImageCrop.roundTo6Decimals(intersectionRect.width),
14969
17042
  height: WeaveImageCrop.roundTo6Decimals(intersectionRect.height)
14970
17043
  };
@@ -15270,6 +17343,27 @@ var WeaveImageNode = class extends WeaveNode {
15270
17343
  if (!internalImage || !cropGroup) return;
15271
17344
  new WeaveImageCrop(this.instance, this, imageNode, internalImage, cropGroup).unCrop();
15272
17345
  };
17346
+ flipImage = (imageNode, options) => {
17347
+ if (imageNode.getAttrs().cropping) return;
17348
+ const prop = options.axis === "horizontal" ? "flipX" : "flipY";
17349
+ this.instance.stateTransactional(() => {
17350
+ imageNode.setAttrs({ [prop]: !(imageNode.getAttrs()[prop] ?? false) });
17351
+ this.applyFlip(imageNode);
17352
+ this.cacheNode(imageNode);
17353
+ this.instance.updateNode(this.serialize(imageNode));
17354
+ });
17355
+ };
17356
+ applyFlip(image) {
17357
+ const attrs = image.getAttrs();
17358
+ const internalImage = image.findOne(`#${attrs.id}-image`);
17359
+ if (!internalImage) return;
17360
+ const flipX = attrs.flipX === true;
17361
+ const flipY = attrs.flipY === true;
17362
+ internalImage.scaleX(flipX ? -1 : 1);
17363
+ internalImage.scaleY(flipY ? -1 : 1);
17364
+ internalImage.offsetX(flipX ? internalImage.width() : 0);
17365
+ internalImage.offsetY(flipY ? internalImage.height() : 0);
17366
+ }
15273
17367
  loadAsyncElement(nodeId) {
15274
17368
  this.instance.loadAsyncElement(nodeId, WEAVE_IMAGE_NODE_TYPE);
15275
17369
  }
@@ -15317,6 +17411,12 @@ var WeaveImageNode = class extends WeaveNode {
15317
17411
  new WeaveImageCrop(this.instance, this, image, internalImage, cropGroup).unCrop();
15318
17412
  };
15319
17413
  }
17414
+ image.flipHorizontal = () => {
17415
+ this.flipImage(image, { axis: "horizontal" });
17416
+ };
17417
+ image.flipVertical = () => {
17418
+ this.flipImage(image, { axis: "vertical" });
17419
+ };
15320
17420
  const defaultTransformerProperties = this.defaultGetTransformerProperties(this.config.transform);
15321
17421
  image.getTransformerProperties = function() {
15322
17422
  return defaultTransformerProperties;
@@ -15932,6 +18032,10 @@ var WeaveImageNode = class extends WeaveNode {
15932
18032
  if (!this.imageState[imageAttrs.id ?? ""]?.loaded) return;
15933
18033
  }
15934
18034
  updateImageCrop(image) {
18035
+ this.updateImageCropGeometry(image);
18036
+ this.applyFlip(image);
18037
+ }
18038
+ updateImageCropGeometry(image) {
15935
18039
  const imageAttrs = image.getAttrs();
15936
18040
  const internalImage = image?.findOne(`#${imageAttrs.id}-image`);
15937
18041
  if (!this.imageState[imageAttrs.id ?? ""]?.loaded) return;
@@ -16009,8 +18113,12 @@ var WeaveImageNode = class extends WeaveNode {
16009
18113
  });
16010
18114
  }
16011
18115
  if (internalImage) {
16012
- internalImage.width(Math.max(5, internalImage.width() * internalImage.scaleX()));
16013
- internalImage.height(Math.max(5, internalImage.height() * internalImage.scaleY()));
18116
+ internalImage.offset({
18117
+ x: 0,
18118
+ y: 0
18119
+ });
18120
+ internalImage.width(Math.max(5, internalImage.width() * Math.abs(internalImage.scaleX())));
18121
+ internalImage.height(Math.max(5, internalImage.height() * Math.abs(internalImage.scaleY())));
16014
18122
  internalImage.scale({
16015
18123
  x: 1,
16016
18124
  y: 1
@@ -16065,7 +18173,7 @@ var WeaveImageNode = class extends WeaveNode {
16065
18173
  clearTimeout(this.imageTryoutIds[nodeId]);
16066
18174
  delete this.imageTryoutIds[nodeId];
16067
18175
  }
16068
- nodeInstance.destroy();
18176
+ super.onDestroy(nodeInstance);
16069
18177
  }
16070
18178
  loadImageTryout(imageId) {
16071
18179
  this.imageTryoutIds[imageId] = setTimeout(() => {
@@ -16152,7 +18260,9 @@ var WeaveImageNode = class extends WeaveNode {
16152
18260
  width: props.uncroppedImage.width,
16153
18261
  height: props.uncroppedImage.height
16154
18262
  },
16155
- cropping: props.cropping
18263
+ cropping: props.cropping,
18264
+ ...props.flipX !== void 0 && { flipX: props.flipX },
18265
+ ...props.flipY !== void 0 && { flipY: props.flipY }
16156
18266
  } });
16157
18267
  }
16158
18268
  static updateNodeState(prevNodeState, nextProps) {
@@ -16175,7 +18285,9 @@ var WeaveImageNode = class extends WeaveNode {
16175
18285
  width: nextProps.uncroppedImage?.width,
16176
18286
  height: nextProps.uncroppedImage?.height
16177
18287
  } },
16178
- cropping: nextProps.cropping
18288
+ cropping: nextProps.cropping,
18289
+ ...nextProps.flipX !== void 0 && { flipX: nextProps.flipX },
18290
+ ...nextProps.flipY !== void 0 && { flipY: nextProps.flipY }
16179
18291
  } });
16180
18292
  }
16181
18293
  static getSchema() {
@@ -16199,7 +18311,9 @@ var WeaveImageNode = class extends WeaveNode {
16199
18311
  width: z.number().describe("The width of the image before cropping, used for cropping calculations"),
16200
18312
  height: z.number().describe("The height of the image before cropping, used for cropping calculations")
16201
18313
  }),
16202
- cropping: z.boolean().default(false).describe("Whether the image is currently being cropped")
18314
+ cropping: z.boolean().default(false).describe("Whether the image is currently being cropped"),
18315
+ flipX: z.boolean().optional().describe("Whether the image is flipped horizontally"),
18316
+ flipY: z.boolean().optional().describe("Whether the image is flipped vertically")
16203
18317
  })
16204
18318
  });
16205
18319
  }
@@ -16716,14 +18830,26 @@ var WeaveRegularPolygonNode = class extends WeaveNode {
16716
18830
  //#region src/nodes/polygon/constants.ts
16717
18831
  const WEAVE_POLYGON_NODE_TYPE = "polygon";
16718
18832
  //#endregion
18833
+ //#region src/utils/native-backend.ts
18834
+ const MODULE_NOT_FOUND_CODES = ["ERR_MODULE_NOT_FOUND", "MODULE_NOT_FOUND"];
18835
+ const isMissingPackageError = (error, packageName) => error instanceof Error && MODULE_NOT_FOUND_CODES.includes(error.code ?? "") && error.message.includes(`'${packageName}'`);
18836
+ const loadNativeBackend = async (packageName, setupFunctionName, load) => {
18837
+ try {
18838
+ await load();
18839
+ } catch (error) {
18840
+ if (isMissingPackageError(error, packageName)) throw new Error(`${setupFunctionName}() requires the "${packageName}" package. Install it with \`npm install ${packageName}\`, using the version required by @inditextech/weave-sdk.`, { cause: error });
18841
+ throw error;
18842
+ }
18843
+ };
18844
+ //#endregion
16719
18845
  //#region src/backend.ts
16720
18846
  const setupSkiaBackend = async () => {
16721
18847
  globalThis._weave_serverSideBackend = WEAVE_KONVA_BACKEND.SKIA;
16722
- await import("konva/skia-backend");
18848
+ await loadNativeBackend("skia-canvas", "setupSkiaBackend", () => import("konva/skia-backend"));
16723
18849
  };
16724
18850
  const setupCanvasBackend = async () => {
16725
18851
  globalThis._weave_serverSideBackend = WEAVE_KONVA_BACKEND.CANVAS;
16726
- await import("konva/canvas-backend");
18852
+ await loadNativeBackend("canvas", "setupCanvasBackend", () => import("konva/canvas-backend"));
16727
18853
  };
16728
18854
  //#endregion
16729
18855
  //#region src/nodes/polygon/presets.ts
@@ -19638,7 +21764,7 @@ var WeaveCommentNode = class extends WeaveNode {
19638
21764
  this.normalizeNodeSize(commentNode);
19639
21765
  }
19640
21766
  onDestroy(nodeInstance) {
19641
- nodeInstance.destroy();
21767
+ super.onDestroy(nodeInstance);
19642
21768
  }
19643
21769
  expandNode(commentNode) {
19644
21770
  const widthExpanded = this.config.style.expanded.width;
@@ -23292,6 +25418,70 @@ const RECTANGLE_TOOL_STATE = {
23292
25418
  ["ADDED"]: "added"
23293
25419
  };
23294
25420
  //#endregion
25421
+ //#region src/actions/shared/shape-geometry.ts
25422
+ /** Returns the normalized bounds described by a drag, including its modifiers. */
25423
+ function getShapeDragBounds(anchor, pointer, options = {}) {
25424
+ const { centered = false, constrained = false, aspectRatio = 1 } = options;
25425
+ const deltaX = Math.abs(pointer.x - anchor.x);
25426
+ const deltaY = Math.abs(pointer.y - anchor.y);
25427
+ let width = deltaX;
25428
+ let height = deltaY;
25429
+ if (constrained && aspectRatio > 0) {
25430
+ if (width / (height || Number.EPSILON) > aspectRatio) height = width / aspectRatio;
25431
+ else width = height * aspectRatio;
25432
+ }
25433
+ if (constrained && aspectRatio > 0 && width > 0 && height > 0) {
25434
+ const minimumSizeScale = Math.max(1, 1 / width, 1 / height);
25435
+ width *= minimumSizeScale;
25436
+ height *= minimumSizeScale;
25437
+ } else {
25438
+ width = Math.max(1, width);
25439
+ height = Math.max(1, height);
25440
+ }
25441
+ if (centered) {
25442
+ width *= 2;
25443
+ height *= 2;
25444
+ return {
25445
+ x: anchor.x - width / 2,
25446
+ y: anchor.y - height / 2,
25447
+ width,
25448
+ height
25449
+ };
25450
+ }
25451
+ const signedWidth = Math.sign(pointer.x - anchor.x || 1) * width;
25452
+ const signedHeight = Math.sign(pointer.y - anchor.y || 1) * height;
25453
+ return {
25454
+ x: Math.min(anchor.x, anchor.x + signedWidth),
25455
+ y: Math.min(anchor.y, anchor.y + signedHeight),
25456
+ width,
25457
+ height
25458
+ };
25459
+ }
25460
+ //#endregion
25461
+ //#region src/actions/shared/shape-keyboard.ts
25462
+ /** Binds the common keyboard behavior for tools that create shapes by dragging. */
25463
+ function bindShapeDragKeyboardHandlers(signal, isActionActive, isDragInProgress, canUpdateModifiers, onCancel, onModifiersChange) {
25464
+ const handleModifierKey = (event) => {
25465
+ if (event.key !== "Shift" && event.key !== "Alt") return;
25466
+ const actionActive = isActionActive();
25467
+ const dragInProgress = isDragInProgress();
25468
+ if (event.type === "keydown" && event.key === "Alt" && actionActive && dragInProgress) event.preventDefault();
25469
+ if (!actionActive || !canUpdateModifiers()) return;
25470
+ onModifiersChange({
25471
+ shiftKey: event.shiftKey,
25472
+ altKey: event.altKey
25473
+ });
25474
+ };
25475
+ window.addEventListener("keydown", (event) => {
25476
+ if (isActionActive() && (event.code === "Enter" || event.code === "Escape")) {
25477
+ onCancel();
25478
+ return;
25479
+ }
25480
+ handleModifierKey(event);
25481
+ }, { signal });
25482
+ window.addEventListener("keyup", handleModifierKey, { signal });
25483
+ }
25484
+ //#endregion
23295
25485
  //#region src/actions/rectangle-tool/rectangle-tool.ts
23296
25486
  var WeaveRectangleToolAction = class extends WeaveAction {
23297
25487
  initialized = false;
@@ -23337,16 +25527,7 @@ var WeaveRectangleToolAction = class extends WeaveAction {
23337
25527
  }
23338
25528
  setupEvents() {
23339
25529
  const stage = this.instance.getStage();
23340
- window.addEventListener("keydown", (e) => {
23341
- if (e.code === "Enter" && this.instance.getActiveAction() === "rectangleTool") {
23342
- this.cancelAction();
23343
- return;
23344
- }
23345
- if (e.code === "Escape" && this.instance.getActiveAction() === "rectangleTool") {
23346
- this.cancelAction();
23347
- return;
23348
- }
23349
- }, { signal: this.instance.getEventsController().signal });
25530
+ bindShapeDragKeyboardHandlers(this.instance.getEventsController().signal, () => this.instance.getActiveAction() === RECTANGLE_TOOL_ACTION_NAME, () => this.state === RECTANGLE_TOOL_STATE.DEFINING_SIZE, () => this.state === RECTANGLE_TOOL_STATE.DEFINING_SIZE && this.moved, () => this.cancelAction(), (modifiers) => this.handleMovement(modifiers));
23350
25531
  stage.on("pointermove", () => {
23351
25532
  if (this.state === RECTANGLE_TOOL_STATE.IDLE) return;
23352
25533
  this.setCursor();
@@ -23372,12 +25553,12 @@ var WeaveRectangleToolAction = class extends WeaveAction {
23372
25553
  this.state = RECTANGLE_TOOL_STATE.ADDING;
23373
25554
  return;
23374
25555
  }
23375
- if (this.state === RECTANGLE_TOOL_STATE.DEFINING_SIZE) this.handleMovement();
25556
+ if (this.state === RECTANGLE_TOOL_STATE.DEFINING_SIZE) this.handleMovement(e.evt);
23376
25557
  });
23377
25558
  stage.on("pointerup", (e) => {
23378
25559
  this.pointers.delete(e.evt.pointerId);
23379
25560
  if (this.isTap(e)) this.moved = false;
23380
- if (this.state === RECTANGLE_TOOL_STATE.DEFINING_SIZE) this.handleSettingSize();
25561
+ if (this.state === RECTANGLE_TOOL_STATE.DEFINING_SIZE) this.handleSettingSize(e.evt);
23381
25562
  });
23382
25563
  this.initialized = true;
23383
25564
  }
@@ -23411,7 +25592,7 @@ var WeaveRectangleToolAction = class extends WeaveAction {
23411
25592
  }
23412
25593
  this.setState(RECTANGLE_TOOL_STATE.DEFINING_SIZE);
23413
25594
  }
23414
- handleSettingSize() {
25595
+ handleSettingSize(modifiers = {}) {
23415
25596
  if (this.rectId && this.tempRectNode && this.clickPoint && this.container) {
23416
25597
  const { mousePoint } = this.instance.getMousePointerRelativeToContainer(this.container);
23417
25598
  const rectPos = {
@@ -23421,10 +25602,14 @@ var WeaveRectangleToolAction = class extends WeaveAction {
23421
25602
  let rectWidth = this.props.width;
23422
25603
  let rectHeight = this.props.height;
23423
25604
  if (this.moved) {
23424
- rectPos.x = Math.min(this.clickPoint.x, mousePoint.x);
23425
- rectPos.y = Math.min(this.clickPoint.y, mousePoint.y);
23426
- rectWidth = Math.abs(this.clickPoint.x - mousePoint.x);
23427
- rectHeight = Math.abs(this.clickPoint.y - mousePoint.y);
25605
+ const bounds = getShapeDragBounds(this.clickPoint, mousePoint, {
25606
+ centered: modifiers.altKey,
25607
+ constrained: modifiers.shiftKey
25608
+ });
25609
+ rectPos.x = bounds.x;
25610
+ rectPos.y = bounds.y;
25611
+ rectWidth = bounds.width;
25612
+ rectHeight = bounds.height;
23428
25613
  }
23429
25614
  this.tempRectNode.setAttrs({
23430
25615
  ...this.props,
@@ -23456,16 +25641,20 @@ var WeaveRectangleToolAction = class extends WeaveAction {
23456
25641
  }
23457
25642
  this.cancelAction();
23458
25643
  }
23459
- handleMovement() {
25644
+ handleMovement(modifiers = {}) {
23460
25645
  if (this.state !== RECTANGLE_TOOL_STATE.DEFINING_SIZE) return;
23461
25646
  if (this.rectId && this.tempRectNode && this.measureContainer && this.clickPoint) {
23462
25647
  this.moved = true;
23463
25648
  const { mousePoint } = this.instance.getMousePointerRelativeToContainer(this.measureContainer);
23464
- const deltaX = mousePoint.x - this.clickPoint?.x;
23465
- const deltaY = mousePoint.y - this.clickPoint?.y;
25649
+ const bounds = getShapeDragBounds(this.clickPoint, mousePoint, {
25650
+ centered: modifiers.altKey,
25651
+ constrained: modifiers.shiftKey
25652
+ });
23466
25653
  this.tempRectNode.setAttrs({
23467
- width: deltaX,
23468
- height: deltaY
25654
+ x: bounds.x,
25655
+ y: bounds.y,
25656
+ width: bounds.width,
25657
+ height: bounds.height
23469
25658
  });
23470
25659
  }
23471
25660
  }
@@ -23564,16 +25753,7 @@ var WeaveEllipseToolAction = class extends WeaveAction {
23564
25753
  }
23565
25754
  setupEvents() {
23566
25755
  const stage = this.instance.getStage();
23567
- window.addEventListener("keydown", (e) => {
23568
- if (e.code === "Enter" && this.instance.getActiveAction() === "ellipseTool") {
23569
- this.cancelAction();
23570
- return;
23571
- }
23572
- if (e.code === "Escape" && this.instance.getActiveAction() === "ellipseTool") {
23573
- this.cancelAction();
23574
- return;
23575
- }
23576
- }, { signal: this.instance.getEventsController().signal });
25756
+ bindShapeDragKeyboardHandlers(this.instance.getEventsController().signal, () => this.instance.getActiveAction() === ELLIPSE_TOOL_ACTION_NAME, () => this.state === ELLIPSE_TOOL_STATE.DEFINING_SIZE, () => this.state === ELLIPSE_TOOL_STATE.DEFINING_SIZE && this.moved, () => this.cancelAction(), (modifiers) => this.handleMovement(modifiers));
23577
25757
  stage.on("pointerdown", (e) => {
23578
25758
  this.setTapStart(e);
23579
25759
  this.pointers.set(e.evt.pointerId, {
@@ -23600,7 +25780,7 @@ var WeaveEllipseToolAction = class extends WeaveAction {
23600
25780
  }
23601
25781
  if (this.state === ELLIPSE_TOOL_STATE.DEFINING_SIZE) {
23602
25782
  this.moved = true;
23603
- this.handleMovement();
25783
+ this.handleMovement(e.evt);
23604
25784
  }
23605
25785
  });
23606
25786
  stage.on("pointerup", (e) => {
@@ -23608,7 +25788,7 @@ var WeaveEllipseToolAction = class extends WeaveAction {
23608
25788
  if (this.isTap(e)) this.moved = false;
23609
25789
  if (this.state === ELLIPSE_TOOL_STATE.DEFINING_SIZE) {
23610
25790
  this.creating = false;
23611
- this.handleSettingSize();
25791
+ this.handleSettingSize(e.evt);
23612
25792
  }
23613
25793
  });
23614
25794
  this.initialized = true;
@@ -23632,8 +25812,8 @@ var WeaveEllipseToolAction = class extends WeaveAction {
23632
25812
  if (nodeHandler) {
23633
25813
  const node = nodeHandler.create(this.ellipseId, {
23634
25814
  ...this.props,
23635
- x: this.clickPoint?.x ?? 0 + this.props.radiusX,
23636
- y: this.clickPoint?.y ?? 0 + this.props.radiusY,
25815
+ x: this.clickPoint?.x ?? 0,
25816
+ y: this.clickPoint?.y ?? 0,
23637
25817
  radiusX: 1,
23638
25818
  radiusY: 1
23639
25819
  });
@@ -23641,22 +25821,28 @@ var WeaveEllipseToolAction = class extends WeaveAction {
23641
25821
  }
23642
25822
  this.setState(ELLIPSE_TOOL_STATE.DEFINING_SIZE);
23643
25823
  }
23644
- handleSettingSize() {
25824
+ handleSettingSize(modifiers = {}) {
23645
25825
  const ellipse = this.instance.getStage().findOne(`#${this.ellipseId}`);
23646
25826
  if (this.ellipseId && this.clickPoint && this.container && ellipse) {
23647
25827
  const { mousePoint } = this.instance.getMousePointerRelativeToContainer(this.container);
23648
25828
  const nodeHandler = this.instance.getNodeHandler("ellipse");
23649
- const ellipsePos = {
25829
+ let ellipsePos = {
23650
25830
  x: this.clickPoint.x,
23651
25831
  y: this.clickPoint.y
23652
25832
  };
23653
- let ellipseRadiusX = this.props.radiusY;
25833
+ let ellipseRadiusX = this.props.radiusX;
23654
25834
  let ellipseRadiusY = this.props.radiusY;
23655
25835
  if (this.moved) {
23656
- ellipsePos.x = Math.min(this.clickPoint.x, mousePoint.x);
23657
- ellipsePos.y = Math.min(this.clickPoint.y, mousePoint.y);
23658
- ellipseRadiusX = Math.abs(this.clickPoint.x - mousePoint.x);
23659
- ellipseRadiusY = Math.abs(this.clickPoint.y - mousePoint.y);
25836
+ const bounds = getShapeDragBounds(this.clickPoint, mousePoint, {
25837
+ centered: modifiers.altKey,
25838
+ constrained: modifiers.shiftKey
25839
+ });
25840
+ ellipsePos = {
25841
+ x: bounds.x,
25842
+ y: bounds.y
25843
+ };
25844
+ ellipseRadiusX = bounds.width;
25845
+ ellipseRadiusY = bounds.height;
23660
25846
  }
23661
25847
  ellipse.setAttrs({
23662
25848
  ...this.props,
@@ -23670,27 +25856,23 @@ var WeaveEllipseToolAction = class extends WeaveAction {
23670
25856
  }
23671
25857
  this.cancelAction();
23672
25858
  }
23673
- handleMovement() {
25859
+ handleMovement(modifiers = {}) {
23674
25860
  if (this.state !== ELLIPSE_TOOL_STATE.DEFINING_SIZE) return;
23675
25861
  const ellipse = this.instance.getStage().findOne(`#${this.ellipseId}`);
23676
25862
  if (this.ellipseId && this.container && this.clickPoint && ellipse) {
23677
25863
  const { mousePoint } = this.instance.getMousePointerRelativeToContainer(this.container);
23678
- const deltaX = Math.abs(mousePoint.x - this.clickPoint?.x);
23679
- const deltaY = Math.abs(mousePoint.y - this.clickPoint?.y);
23680
- const ellipsePos = {
23681
- x: this.clickPoint.x,
23682
- y: this.clickPoint.y
23683
- };
23684
- if (this.moved) {
23685
- ellipsePos.x = Math.min(this.clickPoint.x, mousePoint.x);
23686
- ellipsePos.y = Math.min(this.clickPoint.y, mousePoint.y);
23687
- }
25864
+ const bounds = getShapeDragBounds(this.clickPoint, mousePoint, {
25865
+ centered: modifiers.altKey,
25866
+ constrained: modifiers.shiftKey
25867
+ });
23688
25868
  const nodeHandler = this.instance.getNodeHandler("ellipse");
23689
25869
  if (nodeHandler) nodeHandler.onUpdate(ellipse, {
23690
25870
  ...this.props,
23691
25871
  id: this.ellipseId,
23692
- radiusX: deltaX / 2,
23693
- radiusY: deltaY / 2
25872
+ x: bounds.x,
25873
+ y: bounds.y,
25874
+ radiusX: bounds.width / 2,
25875
+ radiusY: bounds.height / 2
23694
25876
  });
23695
25877
  }
23696
25878
  }
@@ -26661,16 +28843,7 @@ var WeaveRegularPolygonToolAction = class extends WeaveAction {
26661
28843
  }
26662
28844
  setupEvents() {
26663
28845
  const stage = this.instance.getStage();
26664
- window.addEventListener("keydown", (e) => {
26665
- if (e.code === "Enter" && this.instance.getActiveAction() === "regularPolygonTool") {
26666
- this.cancelAction();
26667
- return;
26668
- }
26669
- if (e.code === "Escape" && this.instance.getActiveAction() === "regularPolygonTool") {
26670
- this.cancelAction();
26671
- return;
26672
- }
26673
- }, { signal: this.instance.getEventsController().signal });
28846
+ bindShapeDragKeyboardHandlers(this.instance.getEventsController().signal, () => this.instance.getActiveAction() === REGULAR_POLYGON_TOOL_ACTION_NAME, () => this.state === REGULAR_POLYGON_TOOL_STATE.DEFINING_SIZE, () => this.state === REGULAR_POLYGON_TOOL_STATE.DEFINING_SIZE && this.moved, () => this.cancelAction(), (modifiers) => this.handleMovement(modifiers));
26674
28847
  stage.on("pointerdown", (e) => {
26675
28848
  this.setTapStart(e);
26676
28849
  this.pointers.set(e.evt.pointerId, {
@@ -26697,7 +28870,7 @@ var WeaveRegularPolygonToolAction = class extends WeaveAction {
26697
28870
  }
26698
28871
  if (this.state === REGULAR_POLYGON_TOOL_STATE.DEFINING_SIZE) {
26699
28872
  this.moved = true;
26700
- this.handleMovement();
28873
+ this.handleMovement(e.evt);
26701
28874
  }
26702
28875
  });
26703
28876
  stage.on("pointerup", (e) => {
@@ -26705,7 +28878,7 @@ var WeaveRegularPolygonToolAction = class extends WeaveAction {
26705
28878
  if (this.isTap(e)) this.moved = false;
26706
28879
  if (this.state === REGULAR_POLYGON_TOOL_STATE.DEFINING_SIZE) {
26707
28880
  this.creating = false;
26708
- this.handleSettingSize();
28881
+ this.handleSettingSize(e.evt);
26709
28882
  }
26710
28883
  });
26711
28884
  this.initialized = true;
@@ -26737,20 +28910,26 @@ var WeaveRegularPolygonToolAction = class extends WeaveAction {
26737
28910
  }
26738
28911
  this.setState(REGULAR_POLYGON_TOOL_STATE.DEFINING_SIZE);
26739
28912
  }
26740
- handleSettingSize() {
28913
+ handleSettingSize(modifiers = {}) {
26741
28914
  const regularPolygon = this.instance.getStage().findOne(`#${this.regularPolygonId}`);
26742
28915
  if (this.regularPolygonId && this.clickPoint && this.container && regularPolygon) {
26743
28916
  const { mousePoint } = this.instance.getMousePointerRelativeToContainer(this.container);
26744
28917
  const nodeHandler = this.instance.getNodeHandler("regular-polygon");
26745
- const starPos = {
28918
+ let starPos = {
26746
28919
  x: this.clickPoint.x,
26747
28920
  y: this.clickPoint.y
26748
28921
  };
26749
28922
  let newRadius = this.props.radius;
26750
28923
  if (this.moved) {
26751
- starPos.x = Math.min(this.clickPoint.x, mousePoint.x);
26752
- starPos.y = Math.min(this.clickPoint.y, mousePoint.y);
26753
- newRadius = Math.abs(this.clickPoint.x - mousePoint.x);
28924
+ const bounds = getShapeDragBounds(this.clickPoint, mousePoint, {
28925
+ centered: modifiers.altKey,
28926
+ constrained: true
28927
+ });
28928
+ starPos = {
28929
+ x: bounds.x,
28930
+ y: bounds.y
28931
+ };
28932
+ newRadius = bounds.width;
26754
28933
  }
26755
28934
  regularPolygon.setAttrs({
26756
28935
  ...this.props,
@@ -26763,25 +28942,22 @@ var WeaveRegularPolygonToolAction = class extends WeaveAction {
26763
28942
  }
26764
28943
  this.cancelAction();
26765
28944
  }
26766
- handleMovement() {
28945
+ handleMovement(modifiers = {}) {
26767
28946
  if (this.state !== REGULAR_POLYGON_TOOL_STATE.DEFINING_SIZE) return;
26768
28947
  const regularPolygon = this.instance.getStage().findOne(`#${this.regularPolygonId}`);
26769
28948
  if (this.regularPolygonId && this.container && this.clickPoint && regularPolygon) {
26770
28949
  const { mousePoint } = this.instance.getMousePointerRelativeToContainer(this.container);
26771
- const deltaX = Math.abs(mousePoint.x - this.clickPoint?.x);
26772
- const starPos = {
26773
- x: this.clickPoint.x,
26774
- y: this.clickPoint.y
26775
- };
26776
- if (this.moved) {
26777
- starPos.x = Math.min(this.clickPoint.x, mousePoint.x);
26778
- starPos.y = Math.min(this.clickPoint.y, mousePoint.y);
26779
- }
28950
+ const bounds = getShapeDragBounds(this.clickPoint, mousePoint, {
28951
+ centered: modifiers.altKey,
28952
+ constrained: true
28953
+ });
26780
28954
  const nodeHandler = this.instance.getNodeHandler("regular-polygon");
26781
28955
  if (nodeHandler) nodeHandler.onUpdate(regularPolygon, {
26782
28956
  ...this.props,
26783
28957
  id: this.regularPolygonId,
26784
- radius: deltaX / 2
28958
+ x: bounds.x,
28959
+ y: bounds.y,
28960
+ radius: bounds.width / 2
26785
28961
  });
26786
28962
  }
26787
28963
  }
@@ -26830,14 +29006,22 @@ const POLYGON_TOOL_ACTION_NAME = "polygonTool";
26830
29006
  const POLYGON_TOOL_STATE = {
26831
29007
  ["IDLE"]: "idle",
26832
29008
  ["ADDING"]: "adding",
29009
+ ["DEFINING_SIZE"]: "defining-size",
26833
29010
  ["ADDED"]: "added"
26834
29011
  };
26835
29012
  //#endregion
26836
29013
  //#region src/actions/polygon-tool/polygon-tool.ts
29014
+ const MIN_POLYGON_DRAG_DISTANCE = 3;
26837
29015
  var WeavePolygonToolAction = class extends WeaveAction {
26838
29016
  initialized = false;
26839
29017
  state;
26840
29018
  polygonId;
29019
+ pointers;
29020
+ clickPoint;
29021
+ container;
29022
+ moved;
29023
+ placementCommitted;
29024
+ polygonNode;
26841
29025
  cancelAction;
26842
29026
  preset;
26843
29027
  onPropsChange = void 0;
@@ -26851,6 +29035,12 @@ var WeavePolygonToolAction = class extends WeaveAction {
26851
29035
  this.initialized = false;
26852
29036
  this.state = POLYGON_TOOL_STATE.IDLE;
26853
29037
  this.polygonId = null;
29038
+ this.pointers = /* @__PURE__ */ new Map();
29039
+ this.clickPoint = null;
29040
+ this.container = void 0;
29041
+ this.moved = false;
29042
+ this.placementCommitted = false;
29043
+ this.polygonNode = null;
26854
29044
  this.props = this.initProps();
26855
29045
  }
26856
29046
  getName() {
@@ -26875,18 +29065,41 @@ var WeavePolygonToolAction = class extends WeaveAction {
26875
29065
  }
26876
29066
  setupEvents() {
26877
29067
  const stage = this.instance.getStage();
26878
- window.addEventListener("keydown", (e) => {
26879
- if ((e.code === "Enter" || e.code === "Escape") && this.instance.getActiveAction() === "polygonTool") this.cancelAction();
26880
- }, { signal: this.instance.getEventsController().signal });
29068
+ bindShapeDragKeyboardHandlers(this.instance.getEventsController().signal, () => this.instance.getActiveAction() === POLYGON_TOOL_ACTION_NAME, () => this.state === POLYGON_TOOL_STATE.DEFINING_SIZE, () => this.state === POLYGON_TOOL_STATE.DEFINING_SIZE && this.moved, () => this.cancelAction(), (modifiers) => this.handleMovement(modifiers));
26881
29069
  stage.on("pointermove", () => {
26882
29070
  if (this.state === POLYGON_TOOL_STATE.IDLE) return;
26883
29071
  this.setCursor();
26884
29072
  });
26885
29073
  stage.on("pointerdown", (e) => {
26886
29074
  this.setTapStart(e);
29075
+ this.pointers.set(e.evt.pointerId, {
29076
+ x: e.evt.clientX,
29077
+ y: e.evt.clientY
29078
+ });
26887
29079
  if (this.state !== POLYGON_TOOL_STATE.ADDING) return;
26888
29080
  this.handleAdding();
26889
29081
  });
29082
+ stage.on("pointermove", (e) => {
29083
+ if (this.state === POLYGON_TOOL_STATE.IDLE) return;
29084
+ this.setCursor();
29085
+ if (!this.isPressed(e) || !this.pointers.has(e.evt.pointerId)) return;
29086
+ if (this.state === POLYGON_TOOL_STATE.DEFINING_SIZE) {
29087
+ const pointerStart = this.pointers.get(e.evt.pointerId);
29088
+ if (!pointerStart || Math.hypot(e.evt.clientX - pointerStart.x, e.evt.clientY - pointerStart.y) < MIN_POLYGON_DRAG_DISTANCE) return;
29089
+ this.moved = true;
29090
+ this.handleMovement(e.evt);
29091
+ }
29092
+ });
29093
+ stage.on("pointerup", (e) => {
29094
+ this.pointers.delete(e.evt.pointerId);
29095
+ if (this.isTap(e)) this.moved = false;
29096
+ if (this.state === POLYGON_TOOL_STATE.DEFINING_SIZE) this.handleSettingSize(e.evt);
29097
+ });
29098
+ stage.on("pointercancel", (e) => {
29099
+ if (!this.pointers.has(e.evt.pointerId)) return;
29100
+ this.pointers.delete(e.evt.pointerId);
29101
+ if (this.state === POLYGON_TOOL_STATE.DEFINING_SIZE) this.cancelAction();
29102
+ });
26890
29103
  this.initialized = true;
26891
29104
  }
26892
29105
  setState(state) {
@@ -26900,7 +29113,10 @@ var WeavePolygonToolAction = class extends WeaveAction {
26900
29113
  }
26901
29114
  handleAdding() {
26902
29115
  const { mousePoint, container } = this.instance.getMousePointer();
29116
+ this.clickPoint = mousePoint;
29117
+ this.container = container;
26903
29118
  this.polygonId = v4();
29119
+ this.polygonNode = null;
26904
29120
  const presetDef = WEAVE_POLYGON_PRESETS[this.preset];
26905
29121
  const scaleFactor = this.props.scaleFactor ?? 1;
26906
29122
  const { points, innerRect, width, height } = instantiatePreset(presetDef, presetDef.defaultWidth * scaleFactor, presetDef.defaultHeight * scaleFactor);
@@ -26916,15 +29132,49 @@ var WeavePolygonToolAction = class extends WeaveAction {
26916
29132
  points,
26917
29133
  innerRect
26918
29134
  });
29135
+ this.polygonNode = node;
26919
29136
  this.instance.addNode(node, container?.getAttrs().id);
26920
29137
  }
26921
- this.instance.emitEvent("onAddedPolygon");
29138
+ this.setState(POLYGON_TOOL_STATE.DEFINING_SIZE);
29139
+ }
29140
+ handleMovement(modifiers = {}) {
29141
+ if (this.state !== POLYGON_TOOL_STATE.DEFINING_SIZE || !this.polygonId || !this.clickPoint || !this.container) return;
29142
+ const polygon = this.instance.getStage().findOne(`#${this.polygonId}`);
29143
+ const nodeHandler = this.instance.getNodeHandler(WEAVE_POLYGON_NODE_TYPE);
29144
+ const presetDef = WEAVE_POLYGON_PRESETS[this.preset];
29145
+ if (!polygon || !nodeHandler) return;
29146
+ const { mousePoint } = this.instance.getMousePointerRelativeToContainer(this.container);
29147
+ const defaultGeometry = instantiatePreset(presetDef, presetDef.defaultWidth, presetDef.defaultHeight);
29148
+ const bounds = getShapeDragBounds(this.clickPoint, mousePoint, {
29149
+ centered: modifiers.altKey,
29150
+ constrained: modifiers.shiftKey,
29151
+ aspectRatio: defaultGeometry.width / defaultGeometry.height
29152
+ });
29153
+ const geometry = instantiatePreset(presetDef, bounds.width * presetDef.defaultWidth / defaultGeometry.width, bounds.height * presetDef.defaultHeight / defaultGeometry.height);
29154
+ nodeHandler.onUpdate(polygon, {
29155
+ ...this.props,
29156
+ id: this.polygonId,
29157
+ x: bounds.x,
29158
+ y: bounds.y,
29159
+ ...geometry
29160
+ });
29161
+ }
29162
+ handleSettingSize(modifiers = {}) {
29163
+ if (this.moved) this.handleMovement(modifiers);
29164
+ const polygon = this.polygonId && this.instance.getStage().findOne(`#${this.polygonId}`);
29165
+ const nodeHandler = this.instance.getNodeHandler(WEAVE_POLYGON_NODE_TYPE);
29166
+ if (polygon && nodeHandler) {
29167
+ this.instance.updateNode(nodeHandler.serialize(polygon));
29168
+ this.placementCommitted = true;
29169
+ this.instance.emitEvent("onAddedPolygon");
29170
+ }
26922
29171
  this.cancelAction();
26923
29172
  }
26924
29173
  trigger(cancelAction, params) {
26925
29174
  if (!this.instance) throw new Error("Instance not defined");
26926
29175
  if (!this.initialized) this.setupEvents();
26927
29176
  this.preset = params?.presetId ?? "pentagon";
29177
+ this.placementCommitted = false;
26928
29178
  const stage = this.instance.getStage();
26929
29179
  stage.container().tabIndex = 1;
26930
29180
  stage.container().focus();
@@ -26937,13 +29187,21 @@ var WeavePolygonToolAction = class extends WeaveAction {
26937
29187
  cleanup() {
26938
29188
  const stage = this.instance.getStage();
26939
29189
  stage.container().style.cursor = "default";
29190
+ const isCancelledPlacement = this.state === POLYGON_TOOL_STATE.DEFINING_SIZE && this.polygonId !== null && !this.placementCommitted;
29191
+ if (isCancelledPlacement && this.polygonNode) this.instance.removeNode(this.polygonNode);
26940
29192
  const selectionPlugin = this.instance.getPlugin("nodesSelection");
26941
29193
  if (selectionPlugin) {
26942
- const node = stage.findOne(`#${this.polygonId}`);
29194
+ const node = isCancelledPlacement ? void 0 : stage.findOne(`#${this.polygonId}`);
26943
29195
  if (node) selectionPlugin.setSelectedNodes([node]);
26944
29196
  this.instance.triggerAction(SELECTION_TOOL_ACTION_NAME);
26945
29197
  }
26946
29198
  this.polygonId = null;
29199
+ this.pointers.clear();
29200
+ this.clickPoint = null;
29201
+ this.container = void 0;
29202
+ this.moved = false;
29203
+ this.placementCommitted = false;
29204
+ this.polygonNode = null;
26947
29205
  this.setState(POLYGON_TOOL_STATE.IDLE);
26948
29206
  }
26949
29207
  setCursor() {
@@ -33313,6 +35571,6 @@ var WeaveNodesSnappingPlugin = class extends WeavePlugin {
33313
35571
  }
33314
35572
  };
33315
35573
  //#endregion
33316
- 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, PREVIEW_HANDOVER_MAX_FRAMES, PREVIEW_ID_SUFFIX, 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_LINK_ALLOWED_PROTOCOLS, 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, setupCanvasBackend, setupSkiaBackend, sleep, spreadLabelProps, weavejsToYjsBinary };
35574
+ 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, PREVIEW_HANDOVER_MAX_FRAMES, PREVIEW_ID_SUFFIX, 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, TextLayoutEngine, 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_LINK_ALLOWED_PROTOCOLS, 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, WeaveTextV2Node, WeaveTextV2Shape, 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, setupCanvasBackend, setupSkiaBackend, sleep, spreadLabelProps, weavejsToYjsBinary };
33317
35575
 
33318
35576
  //# sourceMappingURL=types.js.map