@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/sdk.js +2454 -208
- package/dist/sdk.node.js +2468 -210
- package/dist/sdk.node.stats.html +1 -1
- package/dist/sdk.stats.html +1 -1
- package/dist/types.d.ts +378 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2468 -210
- package/dist/types.js.map +1 -1
- package/dist/types.stats.html +1 -1
- package/package.json +17 -8
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
14334
|
-
|
|
14335
|
-
|
|
14336
|
-
|
|
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
|
|
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
|
-
|
|
14491
|
-
|
|
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:
|
|
14496
|
-
y:
|
|
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 {
|
|
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 =
|
|
14520
|
-
this.imageOffsetY =
|
|
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 =
|
|
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(
|
|
14916
|
-
y: WeaveImageCrop.roundTo6Decimals(
|
|
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
|
|
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(
|
|
14967
|
-
y: WeaveImageCrop.roundTo6Decimals(
|
|
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.
|
|
16013
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23425
|
-
|
|
23426
|
-
|
|
23427
|
-
|
|
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
|
|
23465
|
-
|
|
25649
|
+
const bounds = getShapeDragBounds(this.clickPoint, mousePoint, {
|
|
25650
|
+
centered: modifiers.altKey,
|
|
25651
|
+
constrained: modifiers.shiftKey
|
|
25652
|
+
});
|
|
23466
25653
|
this.tempRectNode.setAttrs({
|
|
23467
|
-
|
|
23468
|
-
|
|
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
|
-
|
|
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
|
|
23636
|
-
y: this.clickPoint?.y ?? 0
|
|
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
|
-
|
|
25829
|
+
let ellipsePos = {
|
|
23650
25830
|
x: this.clickPoint.x,
|
|
23651
25831
|
y: this.clickPoint.y
|
|
23652
25832
|
};
|
|
23653
|
-
let ellipseRadiusX = this.props.
|
|
25833
|
+
let ellipseRadiusX = this.props.radiusX;
|
|
23654
25834
|
let ellipseRadiusY = this.props.radiusY;
|
|
23655
25835
|
if (this.moved) {
|
|
23656
|
-
|
|
23657
|
-
|
|
23658
|
-
|
|
23659
|
-
|
|
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
|
|
23679
|
-
|
|
23680
|
-
|
|
23681
|
-
|
|
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
|
-
|
|
23693
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26752
|
-
|
|
26753
|
-
|
|
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
|
|
26772
|
-
|
|
26773
|
-
|
|
26774
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|