@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/sdk.node.js
CHANGED
|
@@ -5940,6 +5940,7 @@ const MOVE_TOOL_STATE = {
|
|
|
5940
5940
|
};
|
|
5941
5941
|
//#endregion
|
|
5942
5942
|
//#region src/nodes/node.ts
|
|
5943
|
+
const nodeInstanceListeners = /* @__PURE__ */ new WeakMap();
|
|
5943
5944
|
const augmentKonvaNodeClass = (config) => {
|
|
5944
5945
|
const { transform } = config ?? {};
|
|
5945
5946
|
Konva.Node.prototype.getTransformerProperties = function() {
|
|
@@ -5955,6 +5956,8 @@ const augmentKonvaNodeClass = (config) => {
|
|
|
5955
5956
|
Konva.Node.prototype.triggerCrop = function() {};
|
|
5956
5957
|
Konva.Node.prototype.closeCrop = function() {};
|
|
5957
5958
|
Konva.Node.prototype.resetCrop = function() {};
|
|
5959
|
+
Konva.Node.prototype.flipHorizontal = function() {};
|
|
5960
|
+
Konva.Node.prototype.flipVertical = function() {};
|
|
5958
5961
|
Konva.Node.prototype.dblClick = function() {};
|
|
5959
5962
|
Konva.Node.prototype.click = function() {};
|
|
5960
5963
|
Konva.Node.prototype.allowedAnchors = function() {
|
|
@@ -6040,6 +6043,8 @@ var WeaveNode = class {
|
|
|
6040
6043
|
};
|
|
6041
6044
|
node.updatePosition = function() {};
|
|
6042
6045
|
node.resetCrop = function() {};
|
|
6046
|
+
node.flipHorizontal = function() {};
|
|
6047
|
+
node.flipVertical = function() {};
|
|
6043
6048
|
node.handleMouseover = function() {};
|
|
6044
6049
|
node.handleMouseout = function() {};
|
|
6045
6050
|
node.handleSelectNode = function() {};
|
|
@@ -6210,7 +6215,7 @@ var WeaveNode = class {
|
|
|
6210
6215
|
node.draggable(false);
|
|
6211
6216
|
};
|
|
6212
6217
|
if (node.getAttrs().locked ?? false) {
|
|
6213
|
-
this.
|
|
6218
|
+
this.removeNodeInstanceListeners(node, false);
|
|
6214
6219
|
node.off("transformstart");
|
|
6215
6220
|
node.off("transform");
|
|
6216
6221
|
node.off("transformend");
|
|
@@ -6220,7 +6225,7 @@ var WeaveNode = class {
|
|
|
6220
6225
|
node.off("pointerover");
|
|
6221
6226
|
node.off("pointerleave");
|
|
6222
6227
|
} else {
|
|
6223
|
-
this.
|
|
6228
|
+
this.setNodeInstanceListener(node, "onNodesChange", handleNodesChange);
|
|
6224
6229
|
node.off("transformstart");
|
|
6225
6230
|
node.on("transformstart", (e) => {
|
|
6226
6231
|
if (e.target.getAttrs().strokeScaleEnabled !== false) {
|
|
@@ -6279,7 +6284,7 @@ var WeaveNode = class {
|
|
|
6279
6284
|
});
|
|
6280
6285
|
const stage = this.instance.getStage();
|
|
6281
6286
|
let originalPosition = null;
|
|
6282
|
-
this.
|
|
6287
|
+
this.setNodeInstanceListener(node, "onSelectionState", (state) => {
|
|
6283
6288
|
const selectedNodes = this.getSelectionPlugin()?.getSelectedNodes() ?? [];
|
|
6284
6289
|
if (!state && selectedNodes?.some((n) => n.getAttrs().id === node.getAttrs().id)) originalPosition = node.getAbsolutePosition();
|
|
6285
6290
|
});
|
|
@@ -6595,8 +6600,39 @@ var WeaveNode = class {
|
|
|
6595
6600
|
async onRegister() {}
|
|
6596
6601
|
onAdd(nodeInstance) {}
|
|
6597
6602
|
onDestroy(nodeInstance) {
|
|
6603
|
+
this.removeNodeInstanceListeners(nodeInstance);
|
|
6598
6604
|
nodeInstance.destroy();
|
|
6599
6605
|
}
|
|
6606
|
+
/**
|
|
6607
|
+
* Registers a Weave instance listener on behalf of a node. Only one listener
|
|
6608
|
+
* is kept per node and event: registering the same event again for the same
|
|
6609
|
+
* node replaces the previous listener, including the ones registered by
|
|
6610
|
+
* setupDefaultNodeEvents (onNodesChange, onSelectionState).
|
|
6611
|
+
*/
|
|
6612
|
+
setNodeInstanceListener(node, event, callback) {
|
|
6613
|
+
let listeners = nodeInstanceListeners.get(node);
|
|
6614
|
+
if (!listeners) {
|
|
6615
|
+
listeners = /* @__PURE__ */ new Map();
|
|
6616
|
+
nodeInstanceListeners.set(node, listeners);
|
|
6617
|
+
}
|
|
6618
|
+
const previous = listeners.get(event);
|
|
6619
|
+
if (previous) this.instance.removeEventListener(event, previous);
|
|
6620
|
+
listeners.set(event, callback);
|
|
6621
|
+
this.instance.addEventListener(event, callback);
|
|
6622
|
+
}
|
|
6623
|
+
/**
|
|
6624
|
+
* Removes the listeners registered with setNodeInstanceListener for the node
|
|
6625
|
+
* and, unless disabled, for its descendants.
|
|
6626
|
+
*/
|
|
6627
|
+
removeNodeInstanceListeners(node, includeDescendants = true) {
|
|
6628
|
+
const nodes = includeDescendants && node instanceof Konva.Container ? [node, ...node.find(() => true)] : [node];
|
|
6629
|
+
for (const actNode of nodes) {
|
|
6630
|
+
const listeners = nodeInstanceListeners.get(actNode);
|
|
6631
|
+
if (!listeners) continue;
|
|
6632
|
+
for (const [event, callback] of listeners) this.instance.removeEventListener(event, callback);
|
|
6633
|
+
nodeInstanceListeners.delete(actNode);
|
|
6634
|
+
}
|
|
6635
|
+
}
|
|
6600
6636
|
onDestroyInstance() {}
|
|
6601
6637
|
serialize(instance) {
|
|
6602
6638
|
const attrs = instance.getAttrs();
|
|
@@ -9469,7 +9505,7 @@ var WeaveRegisterManager = class {
|
|
|
9469
9505
|
};
|
|
9470
9506
|
//#endregion
|
|
9471
9507
|
//#region package.json
|
|
9472
|
-
var version = "5.
|
|
9508
|
+
var version = "5.7.0-SNAPSHOT.595.1";
|
|
9473
9509
|
//#endregion
|
|
9474
9510
|
//#region src/managers/setup.ts
|
|
9475
9511
|
var WeaveSetupManager = class {
|
|
@@ -11190,6 +11226,9 @@ var Weave = class {
|
|
|
11190
11226
|
getBoundingBox(nodes, config) {
|
|
11191
11227
|
return getBoundingBox(nodes, config);
|
|
11192
11228
|
}
|
|
11229
|
+
flipImage(node, options) {
|
|
11230
|
+
this.getNodeHandler("image")?.flipImage(node, options);
|
|
11231
|
+
}
|
|
11193
11232
|
stateTransactional(callback, origin) {
|
|
11194
11233
|
this.stateManager.stateTransactional(callback, origin);
|
|
11195
11234
|
}
|
|
@@ -14264,99 +14303,2084 @@ var WeaveTextNode = class extends WeaveNode {
|
|
|
14264
14303
|
};
|
|
14265
14304
|
}
|
|
14266
14305
|
static addNodeState(defaultNodeState, props) {
|
|
14267
|
-
return mergeExceptArrays(defaultNodeState, { props: {
|
|
14268
|
-
x: props.x,
|
|
14269
|
-
y: props.y,
|
|
14270
|
-
width: props.width,
|
|
14271
|
-
...props.height && { height: props.height },
|
|
14272
|
-
FontFamily: props.fontFamily,
|
|
14273
|
-
fontSize: props.fontSize,
|
|
14274
|
-
fontStyle: props.fontStyle,
|
|
14275
|
-
fontVariant: props.fontVariant,
|
|
14276
|
-
textDecoration: props.textDecoration,
|
|
14277
|
-
letterSpacing: props.letterSpacing,
|
|
14278
|
-
lineHeight: props.lineHeight,
|
|
14279
|
-
align: props.align,
|
|
14280
|
-
verticalAlign: props.verticalAlign,
|
|
14281
|
-
rotation: props.rotation,
|
|
14282
|
-
fill: props.fill,
|
|
14283
|
-
text: props.text,
|
|
14284
|
-
layout: props.layout,
|
|
14285
|
-
...props.strokeEnabled && { strokeEnabled: props.strokeEnabled },
|
|
14286
|
-
...props.stroke && { stroke: props.stroke },
|
|
14287
|
-
...props.strokeWidth && { strokeWidth: props.strokeWidth },
|
|
14288
|
-
...props.fillAfterStrokeEnabled && { fillAfterStrokeEnabled: props.fillAfterStrokeEnabled },
|
|
14289
|
-
...props.link && { link: props.link }
|
|
14290
|
-
} });
|
|
14306
|
+
return mergeExceptArrays(defaultNodeState, { props: {
|
|
14307
|
+
x: props.x,
|
|
14308
|
+
y: props.y,
|
|
14309
|
+
width: props.width,
|
|
14310
|
+
...props.height && { height: props.height },
|
|
14311
|
+
FontFamily: props.fontFamily,
|
|
14312
|
+
fontSize: props.fontSize,
|
|
14313
|
+
fontStyle: props.fontStyle,
|
|
14314
|
+
fontVariant: props.fontVariant,
|
|
14315
|
+
textDecoration: props.textDecoration,
|
|
14316
|
+
letterSpacing: props.letterSpacing,
|
|
14317
|
+
lineHeight: props.lineHeight,
|
|
14318
|
+
align: props.align,
|
|
14319
|
+
verticalAlign: props.verticalAlign,
|
|
14320
|
+
rotation: props.rotation,
|
|
14321
|
+
fill: props.fill,
|
|
14322
|
+
text: props.text,
|
|
14323
|
+
layout: props.layout,
|
|
14324
|
+
...props.strokeEnabled && { strokeEnabled: props.strokeEnabled },
|
|
14325
|
+
...props.stroke && { stroke: props.stroke },
|
|
14326
|
+
...props.strokeWidth && { strokeWidth: props.strokeWidth },
|
|
14327
|
+
...props.fillAfterStrokeEnabled && { fillAfterStrokeEnabled: props.fillAfterStrokeEnabled },
|
|
14328
|
+
...props.link && { link: props.link }
|
|
14329
|
+
} });
|
|
14330
|
+
}
|
|
14331
|
+
static updateNodeState(prevNodeState, nextProps) {
|
|
14332
|
+
return mergeExceptArrays(prevNodeState, { props: {
|
|
14333
|
+
x: nextProps.x,
|
|
14334
|
+
y: nextProps.y,
|
|
14335
|
+
width: nextProps.width,
|
|
14336
|
+
...nextProps.height && { height: nextProps.height },
|
|
14337
|
+
FontFamily: nextProps.fontFamily,
|
|
14338
|
+
fontSize: nextProps.fontSize,
|
|
14339
|
+
fontStyle: nextProps.fontStyle,
|
|
14340
|
+
fontVariant: nextProps.fontVariant,
|
|
14341
|
+
textDecoration: nextProps.textDecoration,
|
|
14342
|
+
letterSpacing: nextProps.letterSpacing,
|
|
14343
|
+
lineHeight: nextProps.lineHeight,
|
|
14344
|
+
align: nextProps.align,
|
|
14345
|
+
verticalAlign: nextProps.verticalAlign,
|
|
14346
|
+
rotation: nextProps.rotation,
|
|
14347
|
+
fill: nextProps.fill,
|
|
14348
|
+
text: nextProps.text,
|
|
14349
|
+
layout: nextProps.layout,
|
|
14350
|
+
...nextProps.strokeEnabled && { strokeEnabled: nextProps.strokeEnabled },
|
|
14351
|
+
...nextProps.stroke && { stroke: nextProps.stroke },
|
|
14352
|
+
...nextProps.strokeWidth && { strokeWidth: nextProps.strokeWidth },
|
|
14353
|
+
...nextProps.fillAfterStrokeEnabled && { fillAfterStrokeEnabled: nextProps.fillAfterStrokeEnabled },
|
|
14354
|
+
...nextProps.link && { link: nextProps.link }
|
|
14355
|
+
} });
|
|
14356
|
+
}
|
|
14357
|
+
static getSchema() {
|
|
14358
|
+
return super.getSchema().extend({
|
|
14359
|
+
type: z.literal(WEAVE_TEXT_NODE_TYPE).describe(`Type of the node, for a text node it will always be "${WEAVE_TEXT_NODE_TYPE}"`),
|
|
14360
|
+
props: z.object({
|
|
14361
|
+
nodeType: z.literal(WEAVE_TEXT_NODE_TYPE).describe(`Type of the node, for a text node it will always be "${WEAVE_TEXT_NODE_TYPE}"`),
|
|
14362
|
+
width: z.number().describe("Width of the text in pixels"),
|
|
14363
|
+
height: z.number().optional().describe("Height of the text in pixels. Optional if layout is auto-height or smart."),
|
|
14364
|
+
fontFamily: z.string().default("Arial").describe("Font family of the text, e.g. Arial, Helvetica, etc."),
|
|
14365
|
+
fontSize: z.number().default(16).describe("Font size of the text in pixels."),
|
|
14366
|
+
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\"."),
|
|
14367
|
+
fontVariant: z.enum(["normal", "small-caps"]).describe("Font variant of the text, can be \"normal\" or \"small-caps\"."),
|
|
14368
|
+
textDecoration: z.enum([
|
|
14369
|
+
"line-through",
|
|
14370
|
+
"underline",
|
|
14371
|
+
""
|
|
14372
|
+
]).default("").describe("Text decoration can be \"line-through\", \"underline\" or empty string for none."),
|
|
14373
|
+
letterSpacing: z.number().default(0).describe("Spacing between letters in pixels."),
|
|
14374
|
+
lineHeight: z.number().default(1).describe("Line height of the text, as a multiplier of font size."),
|
|
14375
|
+
align: z.enum([
|
|
14376
|
+
"left",
|
|
14377
|
+
"center",
|
|
14378
|
+
"right",
|
|
14379
|
+
"justify"
|
|
14380
|
+
]).default("left").describe("Text alignment, can be \"left\", \"center\", \"right\" or \"justify\"."),
|
|
14381
|
+
verticalAlign: z.enum([
|
|
14382
|
+
"top",
|
|
14383
|
+
"middle",
|
|
14384
|
+
"bottom"
|
|
14385
|
+
]).default("top").describe("Vertical alignment of the text, can be 'top', 'middle' or 'bottom'."),
|
|
14386
|
+
fill: z.string().default("#000000ff").describe("Fill color of the text in hex format with alpha channel (e.g. #RRGGBBAA)."),
|
|
14387
|
+
text: z.string().default("text").describe("The actual text content of the node."),
|
|
14388
|
+
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."),
|
|
14389
|
+
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."),
|
|
14390
|
+
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."),
|
|
14391
|
+
strokeEnabled: z.boolean().default(false).describe("Whether the text outline is enabled."),
|
|
14392
|
+
stroke: z.string().optional().default("#d6d6d6").describe("Color of the text outline in hex format with alpha channel (e.g. #RRGGBBAA)."),
|
|
14393
|
+
strokeWidth: z.number().optional().default(2).describe("Width of the text outline in pixels."),
|
|
14394
|
+
strokeScaleEnabled: z.boolean().default(true).describe("Whether the stroke width should scale when the node is scaled."),
|
|
14395
|
+
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."),
|
|
14396
|
+
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.")
|
|
14397
|
+
})
|
|
14398
|
+
});
|
|
14399
|
+
}
|
|
14400
|
+
};
|
|
14401
|
+
//#endregion
|
|
14402
|
+
//#region src/nodes/text-v2/model.ts
|
|
14403
|
+
function isEmptyMarks(m) {
|
|
14404
|
+
return !m || Object.keys(m).length === 0;
|
|
14405
|
+
}
|
|
14406
|
+
function sameMarks(a, b) {
|
|
14407
|
+
if (isEmptyMarks(a) && isEmptyMarks(b)) return true;
|
|
14408
|
+
if (isEmptyMarks(a) || isEmptyMarks(b)) return false;
|
|
14409
|
+
const ka = Object.keys(a);
|
|
14410
|
+
const kb = Object.keys(b);
|
|
14411
|
+
return ka.length === kb.length && ka.every((k) => a[k] === b[k]);
|
|
14412
|
+
}
|
|
14413
|
+
/** Merge adjacent runs with equal marks and drop empty runs (keeps >= 0). */
|
|
14414
|
+
function normalizeRuns(runs) {
|
|
14415
|
+
const out = [];
|
|
14416
|
+
for (const run of runs) {
|
|
14417
|
+
if (run.t === "") continue;
|
|
14418
|
+
const last = out[out.length - 1];
|
|
14419
|
+
if (last && sameMarks(last.m, run.m)) last.t += run.t;
|
|
14420
|
+
else out.push({
|
|
14421
|
+
t: run.t,
|
|
14422
|
+
...isEmptyMarks(run.m) ? {} : { m: run.m }
|
|
14423
|
+
});
|
|
14424
|
+
}
|
|
14425
|
+
return out;
|
|
14426
|
+
}
|
|
14427
|
+
/**
|
|
14428
|
+
* Synthesize blocks from legacy plain `text`. One run per paragraph; a
|
|
14429
|
+
* legacy whole-text `link` becomes a full-range `link` mark.
|
|
14430
|
+
*/
|
|
14431
|
+
function synthesizeBlocks(text, link) {
|
|
14432
|
+
return (text ?? "").split("\n").map((paragraph) => ({ runs: paragraph === "" ? [] : [{
|
|
14433
|
+
t: paragraph,
|
|
14434
|
+
...link ? { m: { link } } : {}
|
|
14435
|
+
}] }));
|
|
14436
|
+
}
|
|
14437
|
+
/** Resolve blocks of a node: stored `blocks` or lazily synthesized (no write). */
|
|
14438
|
+
function resolveBlocks(attrs) {
|
|
14439
|
+
if (Array.isArray(attrs.blocks) && attrs.blocks.length > 0) return attrs.blocks;
|
|
14440
|
+
return synthesizeBlocks(attrs.text ?? "", attrs.link);
|
|
14441
|
+
}
|
|
14442
|
+
function blockText(block) {
|
|
14443
|
+
return block.runs.map((r) => r.t).join("");
|
|
14444
|
+
}
|
|
14445
|
+
/** Derived mirror for the `text` attribute. */
|
|
14446
|
+
function blocksToText(blocks) {
|
|
14447
|
+
return blocks.map(blockText).join("\n");
|
|
14448
|
+
}
|
|
14449
|
+
/** Slice runs of a block between local offsets [from, to). */
|
|
14450
|
+
function sliceRuns(runs, from, to) {
|
|
14451
|
+
const out = [];
|
|
14452
|
+
let pos = 0;
|
|
14453
|
+
for (const run of runs) {
|
|
14454
|
+
const start = pos;
|
|
14455
|
+
const end = pos + run.t.length;
|
|
14456
|
+
pos = end;
|
|
14457
|
+
const s = Math.max(from, start);
|
|
14458
|
+
const e = Math.min(to, end);
|
|
14459
|
+
if (s < e) out.push({
|
|
14460
|
+
t: run.t.slice(s - start, e - start),
|
|
14461
|
+
m: run.m
|
|
14462
|
+
});
|
|
14463
|
+
}
|
|
14464
|
+
return out;
|
|
14465
|
+
}
|
|
14466
|
+
/** Marks of the run at (or immediately before) a local offset. */
|
|
14467
|
+
function marksAt(runs, offset) {
|
|
14468
|
+
let pos = 0;
|
|
14469
|
+
let found;
|
|
14470
|
+
for (const run of runs) {
|
|
14471
|
+
const end = pos + run.t.length;
|
|
14472
|
+
if (offset > pos && offset <= end) return run.m;
|
|
14473
|
+
if (offset === pos && !found) found = run.m;
|
|
14474
|
+
pos = end;
|
|
14475
|
+
}
|
|
14476
|
+
return found;
|
|
14477
|
+
}
|
|
14478
|
+
/**
|
|
14479
|
+
* Replace the global range [start, end) (newline counts as 1 char between
|
|
14480
|
+
* blocks) with plain `insert`. Marks on untouched text are preserved; the
|
|
14481
|
+
* inserted text inherits marks of the character before `start`.
|
|
14482
|
+
*/
|
|
14483
|
+
function replaceRange(blocks, start, end, insert) {
|
|
14484
|
+
const total = blocksToText(blocks).length;
|
|
14485
|
+
start = Math.max(0, Math.min(start, total));
|
|
14486
|
+
end = Math.max(start, Math.min(end, total));
|
|
14487
|
+
let before = [];
|
|
14488
|
+
let after = [];
|
|
14489
|
+
const prefixBlocks = [];
|
|
14490
|
+
const suffixBlocks = [];
|
|
14491
|
+
let inheritedMarks;
|
|
14492
|
+
let pos = 0;
|
|
14493
|
+
blocks.forEach((block, i) => {
|
|
14494
|
+
const len = blockText(block).length;
|
|
14495
|
+
const bStart = pos;
|
|
14496
|
+
const bEnd = pos + len;
|
|
14497
|
+
pos = bEnd + 1;
|
|
14498
|
+
if (bEnd < start) prefixBlocks.push(block);
|
|
14499
|
+
else if (bStart > end) suffixBlocks.push(block);
|
|
14500
|
+
else {
|
|
14501
|
+
if (start >= bStart && start <= bEnd) {
|
|
14502
|
+
const local = start - bStart;
|
|
14503
|
+
before = sliceRuns(block.runs, 0, local);
|
|
14504
|
+
inheritedMarks = marksAt(block.runs, local);
|
|
14505
|
+
}
|
|
14506
|
+
if (end >= bStart && end <= bEnd) after = sliceRuns(block.runs, end - bStart, len);
|
|
14507
|
+
}
|
|
14508
|
+
});
|
|
14509
|
+
const lines = insert.split("\n");
|
|
14510
|
+
const mk = (t) => t === "" ? [] : [{
|
|
14511
|
+
t,
|
|
14512
|
+
...isEmptyMarks(inheritedMarks) ? {} : { m: inheritedMarks }
|
|
14513
|
+
}];
|
|
14514
|
+
const middle = [];
|
|
14515
|
+
lines.forEach((line, idx) => {
|
|
14516
|
+
let runs = mk(line);
|
|
14517
|
+
if (idx === 0) runs = [...before, ...runs];
|
|
14518
|
+
if (idx === lines.length - 1) runs = [...runs, ...after];
|
|
14519
|
+
middle.push({ runs: normalizeRuns(runs) });
|
|
14520
|
+
});
|
|
14521
|
+
return [
|
|
14522
|
+
...prefixBlocks,
|
|
14523
|
+
...middle,
|
|
14524
|
+
...suffixBlocks
|
|
14525
|
+
];
|
|
14526
|
+
}
|
|
14527
|
+
//#endregion
|
|
14528
|
+
//#region src/nodes/text-v2/layout/engine.ts
|
|
14529
|
+
/** Quote multi-word families like `Konva` does so the font shorthand parses. */
|
|
14530
|
+
function normalizeFontFamily(fontFamily) {
|
|
14531
|
+
return fontFamily.split(",").map((family) => {
|
|
14532
|
+
family = family.trim();
|
|
14533
|
+
const hasSpace = family.includes(" ");
|
|
14534
|
+
const hasQuotes = family.includes("\"") || family.includes("'");
|
|
14535
|
+
return hasSpace && !hasQuotes ? `"${family}"` : family;
|
|
14536
|
+
}).join(", ");
|
|
14537
|
+
}
|
|
14538
|
+
function fontString(s) {
|
|
14539
|
+
return `${s.fontStyle} ${s.fontVariant} ${s.fontSize}px ${normalizeFontFamily(s.fontFamily)}`;
|
|
14540
|
+
}
|
|
14541
|
+
let sharedContext = null;
|
|
14542
|
+
/** Default measurer: works on browser, node-canvas and skia-canvas backends. */
|
|
14543
|
+
const canvasMeasurer = (font, text) => {
|
|
14544
|
+
sharedContext ??= Konva.Util.createCanvasElement().getContext("2d");
|
|
14545
|
+
sharedContext.font = font;
|
|
14546
|
+
return sharedContext.measureText(text).width;
|
|
14547
|
+
};
|
|
14548
|
+
const graphemeSegmenter = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : null;
|
|
14549
|
+
function splitGraphemes(text) {
|
|
14550
|
+
if (text === "") return [];
|
|
14551
|
+
if (graphemeSegmenter) return Array.from(graphemeSegmenter.segment(text), (s) => s.segment);
|
|
14552
|
+
return Array.from(text);
|
|
14553
|
+
}
|
|
14554
|
+
const now = () => typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
14555
|
+
const WS = /\s/;
|
|
14556
|
+
const MAX_CACHE = 2e4;
|
|
14557
|
+
const MAX_CACHED_CHUNK = 256;
|
|
14558
|
+
var TextLayoutEngine = class {
|
|
14559
|
+
measure;
|
|
14560
|
+
cache = /* @__PURE__ */ new Map();
|
|
14561
|
+
paragraphs = [];
|
|
14562
|
+
/** Entries by signature, so paragraphs that shifted index are reused. */
|
|
14563
|
+
pool = /* @__PURE__ */ new Map();
|
|
14564
|
+
lastParagraphsRelaid = 0;
|
|
14565
|
+
constructor(measurer = canvasMeasurer) {
|
|
14566
|
+
this.measure = measurer;
|
|
14567
|
+
}
|
|
14568
|
+
/** Number of paragraphs recomputed by the last `layout()` call. */
|
|
14569
|
+
getLastRelaidCount() {
|
|
14570
|
+
return this.lastParagraphsRelaid;
|
|
14571
|
+
}
|
|
14572
|
+
clearCaches() {
|
|
14573
|
+
this.cache.clear();
|
|
14574
|
+
this.paragraphs = [];
|
|
14575
|
+
this.pool.clear();
|
|
14576
|
+
}
|
|
14577
|
+
paragraphSig(block, style, bounds) {
|
|
14578
|
+
const fixedWidth = bounds.width !== void 0 && !Number.isNaN(bounds.width);
|
|
14579
|
+
const maxWidth = (bounds.width ?? 0) - bounds.padding * 2;
|
|
14580
|
+
return `${fontString(style)}|${style.letterSpacing}|${style.wrap}|${fixedWidth ? maxWidth : "auto"}|${JSON.stringify(block.runs)}`;
|
|
14581
|
+
}
|
|
14582
|
+
/**
|
|
14583
|
+
* Pre-compute paragraph layouts for `blocks` within a time budget so a
|
|
14584
|
+
* following `layout()` is cheap. Call repeatedly until it returns true.
|
|
14585
|
+
* Lets callers time-slice very large content (e.g. bulk paste).
|
|
14586
|
+
*/
|
|
14587
|
+
warm(blocks, style, bounds, budgetMs) {
|
|
14588
|
+
const font = fontString(style);
|
|
14589
|
+
const fixedWidth = bounds.width !== void 0 && !Number.isNaN(bounds.width);
|
|
14590
|
+
const maxWidth = (bounds.width ?? 0) - bounds.padding * 2;
|
|
14591
|
+
const deadline = now() + budgetMs;
|
|
14592
|
+
for (const block of blocks) {
|
|
14593
|
+
const sig = this.paragraphSig(block, style, bounds);
|
|
14594
|
+
if (this.pool.has(sig)) continue;
|
|
14595
|
+
this.pool.set(sig, this.layoutParagraph(block, sig, font, style, fixedWidth, maxWidth));
|
|
14596
|
+
if (now() >= deadline) return false;
|
|
14597
|
+
}
|
|
14598
|
+
return true;
|
|
14599
|
+
}
|
|
14600
|
+
measureChunk(font, text, spacing) {
|
|
14601
|
+
if (text === "") return 0;
|
|
14602
|
+
if (text.length > MAX_CACHED_CHUNK) return this.measure(font, text) + text.length * spacing;
|
|
14603
|
+
const key = `${font}\u0000${text}`;
|
|
14604
|
+
let w = this.cache.get(key);
|
|
14605
|
+
if (w === void 0) {
|
|
14606
|
+
w = this.measure(font, text);
|
|
14607
|
+
if (this.cache.size >= MAX_CACHE) this.cache.clear();
|
|
14608
|
+
this.cache.set(key, w);
|
|
14609
|
+
}
|
|
14610
|
+
return w + text.length * spacing;
|
|
14611
|
+
}
|
|
14612
|
+
layout(blocks, style, bounds) {
|
|
14613
|
+
const font = fontString(style);
|
|
14614
|
+
const padding = bounds.padding;
|
|
14615
|
+
const fixedWidth = bounds.width !== void 0 && !Number.isNaN(bounds.width);
|
|
14616
|
+
const fixedHeight = bounds.height !== void 0 && !Number.isNaN(bounds.height);
|
|
14617
|
+
const maxWidth = (bounds.width ?? 0) - padding * 2;
|
|
14618
|
+
const maxHeightPx = (bounds.height ?? 0) - padding * 2;
|
|
14619
|
+
const lineHeightPx = style.lineHeight * style.fontSize;
|
|
14620
|
+
const sigBase = `${font}|${style.letterSpacing}|${style.wrap}|${fixedWidth ? maxWidth : "auto"}`;
|
|
14621
|
+
const prev = this.paragraphs;
|
|
14622
|
+
const next = [];
|
|
14623
|
+
let relaid = 0;
|
|
14624
|
+
const lines = [];
|
|
14625
|
+
let textWidth = 0;
|
|
14626
|
+
let currentHeightPx = 0;
|
|
14627
|
+
let offset = 0;
|
|
14628
|
+
outer: for (let p = 0; p < blocks.length; p++) {
|
|
14629
|
+
const block = blocks[p];
|
|
14630
|
+
const sig = `${sigBase}|${JSON.stringify(block.runs)}`;
|
|
14631
|
+
let entry = prev[p]?.sig === sig ? prev[p] : this.pool.get(sig);
|
|
14632
|
+
if (!entry) {
|
|
14633
|
+
entry = this.layoutParagraph(block, sig, font, style, fixedWidth, maxWidth);
|
|
14634
|
+
relaid++;
|
|
14635
|
+
}
|
|
14636
|
+
next.push(entry);
|
|
14637
|
+
for (const pl of entry.lines) {
|
|
14638
|
+
const text = entry.graphemes.slice(pl.startG, pl.endG).join("");
|
|
14639
|
+
const segments = this.buildSegments(entry, block, pl, font, style);
|
|
14640
|
+
const width = segments.reduce((a, s) => a + s.width, 0);
|
|
14641
|
+
const startChar = entry.graphemes.slice(0, pl.startG).join("").length;
|
|
14642
|
+
lines.push({
|
|
14643
|
+
paragraph: p,
|
|
14644
|
+
start: offset + startChar,
|
|
14645
|
+
end: offset + startChar + text.length,
|
|
14646
|
+
text,
|
|
14647
|
+
width,
|
|
14648
|
+
measuredWidth: pl.measuredWidth,
|
|
14649
|
+
offsetX: 0,
|
|
14650
|
+
y: lines.length * lineHeightPx,
|
|
14651
|
+
segments,
|
|
14652
|
+
lastInParagraph: pl.lastInParagraph
|
|
14653
|
+
});
|
|
14654
|
+
textWidth = Math.max(textWidth, pl.measuredWidth);
|
|
14655
|
+
currentHeightPx += lineHeightPx;
|
|
14656
|
+
if (fixedHeight && currentHeightPx + lineHeightPx > maxHeightPx) break outer;
|
|
14657
|
+
}
|
|
14658
|
+
offset += blockText(block).length + 1;
|
|
14659
|
+
}
|
|
14660
|
+
this.paragraphs = next;
|
|
14661
|
+
this.pool = new Map(next.map((e) => [e.sig, e]));
|
|
14662
|
+
this.lastParagraphsRelaid = relaid;
|
|
14663
|
+
const width = fixedWidth ? bounds.width : textWidth + padding * 2;
|
|
14664
|
+
const height = fixedHeight ? bounds.height : lines.length * lineHeightPx + padding * 2;
|
|
14665
|
+
if (style.align !== "left") for (const line of lines) {
|
|
14666
|
+
const free = width - line.width - padding * 2;
|
|
14667
|
+
line.offsetX = style.align === "center" ? free / 2 : free;
|
|
14668
|
+
}
|
|
14669
|
+
return {
|
|
14670
|
+
lines,
|
|
14671
|
+
textWidth,
|
|
14672
|
+
textHeight: style.fontSize,
|
|
14673
|
+
lineHeightPx,
|
|
14674
|
+
width,
|
|
14675
|
+
height
|
|
14676
|
+
};
|
|
14677
|
+
}
|
|
14678
|
+
rangeWidth(entry, runs, from, to, font, spacing) {
|
|
14679
|
+
let width = 0;
|
|
14680
|
+
let i = from;
|
|
14681
|
+
while (i < to) {
|
|
14682
|
+
const run = entry.runOf[i];
|
|
14683
|
+
let j = i;
|
|
14684
|
+
while (j < to && entry.runOf[j] === run) j++;
|
|
14685
|
+
width += this.measureChunk(font, entry.graphemes.slice(i, j).join(""), spacing);
|
|
14686
|
+
i = j;
|
|
14687
|
+
}
|
|
14688
|
+
return width;
|
|
14689
|
+
}
|
|
14690
|
+
layoutParagraph(block, sig, font, style, fixedWidth, maxWidth) {
|
|
14691
|
+
const graphemes = [];
|
|
14692
|
+
const runOf = [];
|
|
14693
|
+
block.runs.forEach((run, ri) => {
|
|
14694
|
+
for (const g of splitGraphemes(run.t)) {
|
|
14695
|
+
graphemes.push(g);
|
|
14696
|
+
runOf.push(ri);
|
|
14697
|
+
}
|
|
14698
|
+
});
|
|
14699
|
+
const entry = {
|
|
14700
|
+
sig,
|
|
14701
|
+
graphemes,
|
|
14702
|
+
runOf,
|
|
14703
|
+
lines: []
|
|
14704
|
+
};
|
|
14705
|
+
const n = graphemes.length;
|
|
14706
|
+
const spacing = style.letterSpacing;
|
|
14707
|
+
const width = (a, b) => this.rangeWidth(entry, block.runs, a, b, font, spacing);
|
|
14708
|
+
const shouldWrap = style.wrap !== "none";
|
|
14709
|
+
const wrapAtWord = style.wrap !== "char" && shouldWrap;
|
|
14710
|
+
const whole = width(0, n);
|
|
14711
|
+
if (!fixedWidth || !shouldWrap || whole <= maxWidth || n === 0) {
|
|
14712
|
+
entry.lines.push({
|
|
14713
|
+
startG: 0,
|
|
14714
|
+
endG: n,
|
|
14715
|
+
measuredWidth: whole,
|
|
14716
|
+
lastInParagraph: true
|
|
14717
|
+
});
|
|
14718
|
+
return entry;
|
|
14719
|
+
}
|
|
14720
|
+
let start = 0;
|
|
14721
|
+
while (start < n) {
|
|
14722
|
+
let low = 0;
|
|
14723
|
+
let high = n - start;
|
|
14724
|
+
let probe = Math.min(high, 32);
|
|
14725
|
+
while (probe < high && width(start, start + probe) <= maxWidth) probe = Math.min(high, probe * 2);
|
|
14726
|
+
high = probe;
|
|
14727
|
+
let matchLen = 0;
|
|
14728
|
+
let matchWidth = 0;
|
|
14729
|
+
while (low < high) {
|
|
14730
|
+
const mid = low + high >>> 1;
|
|
14731
|
+
const w = width(start, start + mid + 1);
|
|
14732
|
+
if (w <= maxWidth) {
|
|
14733
|
+
low = mid + 1;
|
|
14734
|
+
matchLen = mid + 1;
|
|
14735
|
+
matchWidth = w;
|
|
14736
|
+
} else high = mid;
|
|
14737
|
+
}
|
|
14738
|
+
if (matchLen === 0) {
|
|
14739
|
+
matchLen = 1;
|
|
14740
|
+
matchWidth = width(start, start + 1);
|
|
14741
|
+
}
|
|
14742
|
+
if (start + matchLen >= n) {
|
|
14743
|
+
entry.lines.push({
|
|
14744
|
+
startG: start,
|
|
14745
|
+
endG: n,
|
|
14746
|
+
measuredWidth: matchWidth,
|
|
14747
|
+
lastInParagraph: true
|
|
14748
|
+
});
|
|
14749
|
+
return entry;
|
|
14750
|
+
}
|
|
14751
|
+
if (wrapAtWord) {
|
|
14752
|
+
const nextChar = graphemes[start + matchLen];
|
|
14753
|
+
const nextIsSpaceOrDash = nextChar === " " || nextChar === "-";
|
|
14754
|
+
let wrapIndex;
|
|
14755
|
+
if (nextIsSpaceOrDash && matchWidth <= maxWidth) wrapIndex = matchLen;
|
|
14756
|
+
else {
|
|
14757
|
+
const seg = graphemes.slice(start, start + matchLen);
|
|
14758
|
+
wrapIndex = Math.max(seg.lastIndexOf(" "), seg.lastIndexOf("-")) + 1;
|
|
14759
|
+
}
|
|
14760
|
+
if (wrapIndex > 0) {
|
|
14761
|
+
matchLen = wrapIndex;
|
|
14762
|
+
matchWidth = width(start, start + matchLen);
|
|
14763
|
+
}
|
|
14764
|
+
}
|
|
14765
|
+
let end = start + matchLen;
|
|
14766
|
+
let trimmedEnd = end;
|
|
14767
|
+
while (trimmedEnd > start && WS.test(graphemes[trimmedEnd - 1])) trimmedEnd--;
|
|
14768
|
+
entry.lines.push({
|
|
14769
|
+
startG: start,
|
|
14770
|
+
endG: trimmedEnd,
|
|
14771
|
+
measuredWidth: matchWidth,
|
|
14772
|
+
lastInParagraph: false
|
|
14773
|
+
});
|
|
14774
|
+
while (end < n && WS.test(graphemes[end])) end++;
|
|
14775
|
+
start = end;
|
|
14776
|
+
}
|
|
14777
|
+
const last = entry.lines[entry.lines.length - 1];
|
|
14778
|
+
if (last) last.lastInParagraph = true;
|
|
14779
|
+
return entry;
|
|
14780
|
+
}
|
|
14781
|
+
buildSegments(entry, block, pl, font, style) {
|
|
14782
|
+
const segments = [];
|
|
14783
|
+
let x = 0;
|
|
14784
|
+
let i = pl.startG;
|
|
14785
|
+
while (i < pl.endG) {
|
|
14786
|
+
const run = entry.runOf[i];
|
|
14787
|
+
let j = i;
|
|
14788
|
+
while (j < pl.endG && entry.runOf[j] === run) j++;
|
|
14789
|
+
const t = entry.graphemes.slice(i, j).join("");
|
|
14790
|
+
const width = this.measureChunk(font, t, style.letterSpacing);
|
|
14791
|
+
segments.push({
|
|
14792
|
+
t,
|
|
14793
|
+
m: block.runs[run].m,
|
|
14794
|
+
x,
|
|
14795
|
+
width
|
|
14796
|
+
});
|
|
14797
|
+
x += width;
|
|
14798
|
+
i = j;
|
|
14799
|
+
}
|
|
14800
|
+
return segments;
|
|
14801
|
+
}
|
|
14802
|
+
};
|
|
14803
|
+
//#endregion
|
|
14804
|
+
//#region src/nodes/text-v2/shape.ts
|
|
14805
|
+
const LAYOUT_ATTRS = [
|
|
14806
|
+
"text",
|
|
14807
|
+
"blocks",
|
|
14808
|
+
"link",
|
|
14809
|
+
"fontSize",
|
|
14810
|
+
"fontFamily",
|
|
14811
|
+
"fontStyle",
|
|
14812
|
+
"fontVariant",
|
|
14813
|
+
"lineHeight",
|
|
14814
|
+
"letterSpacing",
|
|
14815
|
+
"wrap",
|
|
14816
|
+
"align",
|
|
14817
|
+
"padding",
|
|
14818
|
+
"width",
|
|
14819
|
+
"height"
|
|
14820
|
+
];
|
|
14821
|
+
/**
|
|
14822
|
+
* Canvas shape that lays out and paints blocks/runs text with the
|
|
14823
|
+
* `TextLayoutEngine`. Exposes Konva.Text-compatible getters/setters so
|
|
14824
|
+
* code written against `Konva.Text` keeps working.
|
|
14825
|
+
*/
|
|
14826
|
+
var WeaveTextV2Shape = class extends Konva.Shape {
|
|
14827
|
+
engine = new TextLayoutEngine();
|
|
14828
|
+
naturalEngine = new TextLayoutEngine();
|
|
14829
|
+
layoutCache = null;
|
|
14830
|
+
blocksKey = {};
|
|
14831
|
+
blocksCache = [];
|
|
14832
|
+
viewport = null;
|
|
14833
|
+
constructor(config) {
|
|
14834
|
+
super(config);
|
|
14835
|
+
this.className = "WeaveTextV2Shape";
|
|
14836
|
+
this.on(LAYOUT_ATTRS.map((a) => `${a}Change.konva`).join(" "), () => {
|
|
14837
|
+
this.layoutCache = null;
|
|
14838
|
+
});
|
|
14839
|
+
this.sceneFunc(this._paint);
|
|
14840
|
+
this.hitFunc(this._hit);
|
|
14841
|
+
}
|
|
14842
|
+
text(value) {
|
|
14843
|
+
if (value === void 0) return typeof this.attrs.text === "string" ? this.attrs.text : blocksToText(this.getBlocks());
|
|
14844
|
+
return this.setText(value);
|
|
14845
|
+
}
|
|
14846
|
+
setText(value) {
|
|
14847
|
+
this._setAttr("blocks", void 0);
|
|
14848
|
+
this._setAttr("text", value == null ? "" : String(value));
|
|
14849
|
+
return this;
|
|
14850
|
+
}
|
|
14851
|
+
/** Resolved blocks: stored or lazily synthesized (never written back). */
|
|
14852
|
+
getBlocks() {
|
|
14853
|
+
const k = this.blocksKey;
|
|
14854
|
+
const { blocks, text, link } = this.attrs;
|
|
14855
|
+
if (k.b !== blocks || k.t !== text || k.l !== link) {
|
|
14856
|
+
this.blocksCache = resolveBlocks({
|
|
14857
|
+
blocks,
|
|
14858
|
+
text,
|
|
14859
|
+
link
|
|
14860
|
+
});
|
|
14861
|
+
this.blocksKey = {
|
|
14862
|
+
b: blocks,
|
|
14863
|
+
t: text,
|
|
14864
|
+
l: link
|
|
14865
|
+
};
|
|
14866
|
+
}
|
|
14867
|
+
return this.blocksCache;
|
|
14868
|
+
}
|
|
14869
|
+
/** Replace content with blocks, keeping the `text` mirror in sync. */
|
|
14870
|
+
setBlocks(blocks) {
|
|
14871
|
+
this._setAttr("blocks", blocks);
|
|
14872
|
+
this._setAttr("text", blocksToText(blocks));
|
|
14873
|
+
return this;
|
|
14874
|
+
}
|
|
14875
|
+
fontSize(v) {
|
|
14876
|
+
return this.accessor("fontSize", 12, v);
|
|
14877
|
+
}
|
|
14878
|
+
fontFamily(v) {
|
|
14879
|
+
return this.accessor("fontFamily", "Arial", v);
|
|
14880
|
+
}
|
|
14881
|
+
fontStyle(v) {
|
|
14882
|
+
return this.accessor("fontStyle", "normal", v);
|
|
14883
|
+
}
|
|
14884
|
+
fontVariant(v) {
|
|
14885
|
+
return this.accessor("fontVariant", "normal", v);
|
|
14886
|
+
}
|
|
14887
|
+
lineHeight(v) {
|
|
14888
|
+
return this.accessor("lineHeight", 1, v);
|
|
14889
|
+
}
|
|
14890
|
+
letterSpacing(v) {
|
|
14891
|
+
return this.accessor("letterSpacing", 0, v);
|
|
14892
|
+
}
|
|
14893
|
+
padding(v) {
|
|
14894
|
+
return this.accessor("padding", 0, v);
|
|
14895
|
+
}
|
|
14896
|
+
align(v) {
|
|
14897
|
+
return this.accessor("align", "left", v);
|
|
14898
|
+
}
|
|
14899
|
+
verticalAlign(v) {
|
|
14900
|
+
return this.accessor("verticalAlign", "top", v);
|
|
14901
|
+
}
|
|
14902
|
+
wrap(v) {
|
|
14903
|
+
return this.accessor("wrap", "word", v);
|
|
14904
|
+
}
|
|
14905
|
+
textDecoration(v) {
|
|
14906
|
+
return this.accessor("textDecoration", "", v);
|
|
14907
|
+
}
|
|
14908
|
+
accessor(name, fallback, value) {
|
|
14909
|
+
if (value === void 0) return this.attrs[name] ?? fallback;
|
|
14910
|
+
this.setAttr(name, value);
|
|
14911
|
+
return this;
|
|
14912
|
+
}
|
|
14913
|
+
getTextWidth() {
|
|
14914
|
+
return this.ensureLayout().textWidth;
|
|
14915
|
+
}
|
|
14916
|
+
getTextHeight() {
|
|
14917
|
+
return this.fontSize();
|
|
14918
|
+
}
|
|
14919
|
+
measureSize(text) {
|
|
14920
|
+
return {
|
|
14921
|
+
width: canvasMeasurer(fontString(this.style()), text),
|
|
14922
|
+
height: this.fontSize()
|
|
14923
|
+
};
|
|
14924
|
+
}
|
|
14925
|
+
getWidth() {
|
|
14926
|
+
const w = this.attrs.width;
|
|
14927
|
+
return w === void 0 || w === "auto" || Number.isNaN(w) ? this.ensureLayout().width : w;
|
|
14928
|
+
}
|
|
14929
|
+
getHeight() {
|
|
14930
|
+
const h = this.attrs.height;
|
|
14931
|
+
return h === void 0 || h === "auto" || Number.isNaN(h) ? this.ensureLayout().height : h;
|
|
14932
|
+
}
|
|
14933
|
+
style() {
|
|
14934
|
+
return {
|
|
14935
|
+
fontSize: this.fontSize(),
|
|
14936
|
+
fontFamily: this.fontFamily(),
|
|
14937
|
+
fontStyle: this.fontStyle(),
|
|
14938
|
+
fontVariant: this.fontVariant(),
|
|
14939
|
+
lineHeight: this.lineHeight(),
|
|
14940
|
+
letterSpacing: this.letterSpacing(),
|
|
14941
|
+
wrap: this.wrap(),
|
|
14942
|
+
align: this.align()
|
|
14943
|
+
};
|
|
14944
|
+
}
|
|
14945
|
+
/** Lazy, cached layout. Size accessors and painting force it. */
|
|
14946
|
+
ensureLayout() {
|
|
14947
|
+
if (!this.layoutCache) {
|
|
14948
|
+
const w = this.attrs.width;
|
|
14949
|
+
const h = this.attrs.height;
|
|
14950
|
+
const num = (v) => typeof v === "number" && !Number.isNaN(v) ? v : void 0;
|
|
14951
|
+
this.layoutCache = this.engine.layout(this.getBlocks(), this.style(), {
|
|
14952
|
+
width: num(w),
|
|
14953
|
+
height: num(h),
|
|
14954
|
+
padding: this.padding()
|
|
14955
|
+
});
|
|
14956
|
+
}
|
|
14957
|
+
return this.layoutCache;
|
|
14958
|
+
}
|
|
14959
|
+
/** Size the text would take with no width constraint (for auto layouts). */
|
|
14960
|
+
measureNaturalSize() {
|
|
14961
|
+
const out = this.naturalEngine.layout(this.getBlocks(), this.style(), {
|
|
14962
|
+
width: void 0,
|
|
14963
|
+
height: void 0,
|
|
14964
|
+
padding: this.padding()
|
|
14965
|
+
});
|
|
14966
|
+
return {
|
|
14967
|
+
width: out.width,
|
|
14968
|
+
height: out.height
|
|
14969
|
+
};
|
|
14970
|
+
}
|
|
14971
|
+
/** Rendered width of a string with this shape's font and letter spacing. */
|
|
14972
|
+
measureText(text) {
|
|
14973
|
+
return canvasMeasurer(fontString(this.style()), text) + text.length * this.letterSpacing();
|
|
14974
|
+
}
|
|
14975
|
+
/** Offset of the first line inside the box (padding + vertical align). */
|
|
14976
|
+
getContentOffset() {
|
|
14977
|
+
const padding = this.padding();
|
|
14978
|
+
const layout = this.ensureLayout();
|
|
14979
|
+
const used = layout.lines.length * layout.lineHeightPx + padding * 2;
|
|
14980
|
+
const va = this.verticalAlign();
|
|
14981
|
+
let alignY = 0;
|
|
14982
|
+
if (va === "middle") alignY = (this.getHeight() - used) / 2;
|
|
14983
|
+
else if (va === "bottom") alignY = this.getHeight() - used;
|
|
14984
|
+
return {
|
|
14985
|
+
x: padding,
|
|
14986
|
+
y: alignY + padding
|
|
14987
|
+
};
|
|
14988
|
+
}
|
|
14989
|
+
/** Pre-compute layout for candidate blocks within a time budget. */
|
|
14990
|
+
warmLayout(blocks, budgetMs) {
|
|
14991
|
+
const num = (v) => typeof v === "number" && !Number.isNaN(v) ? v : void 0;
|
|
14992
|
+
return this.engine.warm(blocks, this.style(), {
|
|
14993
|
+
width: num(this.attrs.width),
|
|
14994
|
+
height: num(this.attrs.height),
|
|
14995
|
+
padding: this.padding()
|
|
14996
|
+
}, budgetMs);
|
|
14997
|
+
}
|
|
14998
|
+
/** Optional visible y-range in local coordinates, used for line culling. */
|
|
14999
|
+
setViewport(range) {
|
|
15000
|
+
this.viewport = range;
|
|
15001
|
+
}
|
|
15002
|
+
visibleRange() {
|
|
15003
|
+
if (this.viewport) return this.viewport;
|
|
15004
|
+
const stage = this.getStage();
|
|
15005
|
+
if (!stage) return null;
|
|
15006
|
+
const inv = this.getAbsoluteTransform().copy().invert();
|
|
15007
|
+
const w = stage.width();
|
|
15008
|
+
const h = stage.height();
|
|
15009
|
+
const pts = [
|
|
15010
|
+
inv.point({
|
|
15011
|
+
x: 0,
|
|
15012
|
+
y: 0
|
|
15013
|
+
}),
|
|
15014
|
+
inv.point({
|
|
15015
|
+
x: w,
|
|
15016
|
+
y: 0
|
|
15017
|
+
}),
|
|
15018
|
+
inv.point({
|
|
15019
|
+
x: 0,
|
|
15020
|
+
y: h
|
|
15021
|
+
}),
|
|
15022
|
+
inv.point({
|
|
15023
|
+
x: w,
|
|
15024
|
+
y: h
|
|
15025
|
+
})
|
|
15026
|
+
];
|
|
15027
|
+
return {
|
|
15028
|
+
y1: Math.min(...pts.map((p) => p.y)),
|
|
15029
|
+
y2: Math.max(...pts.map((p) => p.y))
|
|
15030
|
+
};
|
|
15031
|
+
}
|
|
15032
|
+
_paint = (ctx, shape) => {
|
|
15033
|
+
const self = shape;
|
|
15034
|
+
const layout = self.ensureLayout();
|
|
15035
|
+
if (layout.lines.length === 0) return;
|
|
15036
|
+
const fontSize = self.fontSize();
|
|
15037
|
+
const padding = self.padding();
|
|
15038
|
+
const lineHeightPx = layout.lineHeightPx;
|
|
15039
|
+
const decoration = self.textDecoration();
|
|
15040
|
+
const underline = decoration.includes("underline");
|
|
15041
|
+
const lineThrough = decoration.includes("line-through");
|
|
15042
|
+
const spacing = self.letterSpacing();
|
|
15043
|
+
const fill = self.fill();
|
|
15044
|
+
let baseline = "middle";
|
|
15045
|
+
let translateY = lineHeightPx / 2;
|
|
15046
|
+
if (!Konva.legacyTextRendering) {
|
|
15047
|
+
const m = self.measureMetrics();
|
|
15048
|
+
baseline = "alphabetic";
|
|
15049
|
+
translateY = (m.ascent - m.descent) / 2 + lineHeightPx / 2;
|
|
15050
|
+
}
|
|
15051
|
+
ctx.setAttr("font", fontString(self.style()));
|
|
15052
|
+
ctx.setAttr("textBaseline", baseline);
|
|
15053
|
+
ctx.setAttr("textAlign", "left");
|
|
15054
|
+
let alignY = 0;
|
|
15055
|
+
const va = self.verticalAlign();
|
|
15056
|
+
const used = layout.lines.length * lineHeightPx + padding * 2;
|
|
15057
|
+
if (va === "middle") alignY = (self.getHeight() - used) / 2;
|
|
15058
|
+
else if (va === "bottom") alignY = self.getHeight() - used;
|
|
15059
|
+
ctx.translate(padding, alignY + padding);
|
|
15060
|
+
const range = self.visibleRange();
|
|
15061
|
+
const paintSelf = self;
|
|
15062
|
+
for (let n = 0; n < layout.lines.length; n++) {
|
|
15063
|
+
const line = layout.lines[n];
|
|
15064
|
+
const y = translateY + n * lineHeightPx;
|
|
15065
|
+
if (range && (alignY + padding + n * lineHeightPx + lineHeightPx < range.y1 - lineHeightPx || alignY + padding + n * lineHeightPx > range.y2 + lineHeightPx)) continue;
|
|
15066
|
+
ctx.save();
|
|
15067
|
+
const baseX = line.offsetX;
|
|
15068
|
+
if (underline) {
|
|
15069
|
+
ctx.save();
|
|
15070
|
+
ctx.beginPath();
|
|
15071
|
+
const uy = y + Math.round(fontSize / 4);
|
|
15072
|
+
ctx.moveTo(baseX, uy);
|
|
15073
|
+
ctx.lineTo(baseX + Math.round(line.width), uy);
|
|
15074
|
+
ctx.lineWidth = fontSize / 15;
|
|
15075
|
+
ctx.strokeStyle = fill;
|
|
15076
|
+
ctx.stroke();
|
|
15077
|
+
ctx.restore();
|
|
15078
|
+
}
|
|
15079
|
+
for (const seg of line.segments) {
|
|
15080
|
+
let x = baseX + seg.x;
|
|
15081
|
+
if (spacing !== 0) for (const ch of Array.from(seg.t)) {
|
|
15082
|
+
paintSelf._partialText = ch;
|
|
15083
|
+
paintSelf._partialTextX = x;
|
|
15084
|
+
paintSelf._partialTextY = y;
|
|
15085
|
+
ctx.fillStrokeShape(self);
|
|
15086
|
+
x += self.measureSize(ch).width + spacing;
|
|
15087
|
+
}
|
|
15088
|
+
else {
|
|
15089
|
+
paintSelf._partialText = seg.t;
|
|
15090
|
+
paintSelf._partialTextX = x;
|
|
15091
|
+
paintSelf._partialTextY = y;
|
|
15092
|
+
ctx.fillStrokeShape(self);
|
|
15093
|
+
}
|
|
15094
|
+
}
|
|
15095
|
+
if (lineThrough) {
|
|
15096
|
+
ctx.save();
|
|
15097
|
+
ctx.beginPath();
|
|
15098
|
+
const ly = y - Math.round(fontSize / 4);
|
|
15099
|
+
ctx.moveTo(baseX, ly);
|
|
15100
|
+
ctx.lineTo(baseX + Math.round(line.width), ly);
|
|
15101
|
+
ctx.lineWidth = fontSize / 15;
|
|
15102
|
+
ctx.strokeStyle = fill;
|
|
15103
|
+
ctx.stroke();
|
|
15104
|
+
ctx.restore();
|
|
15105
|
+
}
|
|
15106
|
+
ctx.restore();
|
|
15107
|
+
}
|
|
15108
|
+
};
|
|
15109
|
+
_hit = (ctx, shape) => {
|
|
15110
|
+
ctx.beginPath();
|
|
15111
|
+
const self = shape;
|
|
15112
|
+
ctx.rect(0, 0, self.getWidth(), self.getHeight());
|
|
15113
|
+
ctx.closePath();
|
|
15114
|
+
ctx.fillStrokeShape(shape);
|
|
15115
|
+
};
|
|
15116
|
+
metricsCache = null;
|
|
15117
|
+
/** Font ascent/descent used for baseline placement (parity with Konva.Text). */
|
|
15118
|
+
measureMetrics() {
|
|
15119
|
+
const font = fontString(this.style());
|
|
15120
|
+
if (this.metricsCache?.key === font) return this.metricsCache;
|
|
15121
|
+
const ctx = Konva.Util.createCanvasElement().getContext("2d");
|
|
15122
|
+
ctx.font = font;
|
|
15123
|
+
const m = ctx.measureText("M");
|
|
15124
|
+
const scale = this.fontSize() / 100;
|
|
15125
|
+
const ascent = m.fontBoundingBoxAscent ?? 91 * scale;
|
|
15126
|
+
const descent = m.fontBoundingBoxDescent ?? 21 * scale;
|
|
15127
|
+
this.metricsCache = {
|
|
15128
|
+
key: font,
|
|
15129
|
+
ascent,
|
|
15130
|
+
descent
|
|
15131
|
+
};
|
|
15132
|
+
return this.metricsCache;
|
|
15133
|
+
}
|
|
15134
|
+
};
|
|
15135
|
+
Object.assign(WeaveTextV2Shape.prototype, {
|
|
15136
|
+
_fillFunc(ctx) {
|
|
15137
|
+
ctx.fillText(this._partialText, this._partialTextX, this._partialTextY);
|
|
15138
|
+
},
|
|
15139
|
+
_strokeFunc(ctx) {
|
|
15140
|
+
ctx.setAttr("miterLimit", 2);
|
|
15141
|
+
ctx.strokeText(this._partialText, this._partialTextX, this._partialTextY);
|
|
15142
|
+
}
|
|
15143
|
+
});
|
|
15144
|
+
//#endregion
|
|
15145
|
+
//#region src/nodes/text-v2/edit/geometry.ts
|
|
15146
|
+
const graphemeSeg = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : null;
|
|
15147
|
+
const wordSeg = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter(void 0, { granularity: "word" }) : null;
|
|
15148
|
+
function boundaries(text, seg) {
|
|
15149
|
+
if (!seg) return Array.from({ length: text.length + 1 }, (_, i) => i);
|
|
15150
|
+
const out = [0];
|
|
15151
|
+
for (const s of seg.segment(text)) out.push(s.index + s.segment.length);
|
|
15152
|
+
return out;
|
|
15153
|
+
}
|
|
15154
|
+
function prevGrapheme(text, offset) {
|
|
15155
|
+
if (offset <= 0) return 0;
|
|
15156
|
+
const b = boundaries(text, graphemeSeg);
|
|
15157
|
+
for (let i = b.length - 1; i >= 0; i--) if (b[i] < offset) return b[i];
|
|
15158
|
+
return 0;
|
|
15159
|
+
}
|
|
15160
|
+
function nextGrapheme(text, offset) {
|
|
15161
|
+
if (offset >= text.length) return text.length;
|
|
15162
|
+
const b = boundaries(text, graphemeSeg);
|
|
15163
|
+
for (const v of b) if (v > offset) return v;
|
|
15164
|
+
return text.length;
|
|
15165
|
+
}
|
|
15166
|
+
/** Word segment [start, end) containing (or adjacent to) the offset. */
|
|
15167
|
+
function wordRangeAt(text, offset) {
|
|
15168
|
+
if (text === "") return [0, 0];
|
|
15169
|
+
offset = Math.max(0, Math.min(offset, text.length));
|
|
15170
|
+
if (!wordSeg) {
|
|
15171
|
+
let s = offset;
|
|
15172
|
+
let e = offset;
|
|
15173
|
+
while (s > 0 && /\S/.test(text[s - 1])) s--;
|
|
15174
|
+
while (e < text.length && /\S/.test(text[e])) e++;
|
|
15175
|
+
return [s, e];
|
|
15176
|
+
}
|
|
15177
|
+
let last = [offset, offset];
|
|
15178
|
+
for (const seg of wordSeg.segment(text)) {
|
|
15179
|
+
const s = seg.index;
|
|
15180
|
+
const e = s + seg.segment.length;
|
|
15181
|
+
last = [s, e];
|
|
15182
|
+
if (offset < e) return [s, e];
|
|
15183
|
+
}
|
|
15184
|
+
return last;
|
|
15185
|
+
}
|
|
15186
|
+
/** Paragraph range [start, end) (no trailing newline) containing offset. */
|
|
15187
|
+
function paragraphRangeAt(text, offset) {
|
|
15188
|
+
offset = Math.max(0, Math.min(offset, text.length));
|
|
15189
|
+
const s = text.lastIndexOf("\n", offset - 1) + 1;
|
|
15190
|
+
let e = text.indexOf("\n", offset);
|
|
15191
|
+
if (e === -1) e = text.length;
|
|
15192
|
+
return [s, e];
|
|
15193
|
+
}
|
|
15194
|
+
/** Previous word start (Ctrl+Left semantics). */
|
|
15195
|
+
function prevWordBoundary(text, offset) {
|
|
15196
|
+
let i = offset;
|
|
15197
|
+
while (i > 0 && /\s/.test(text[i - 1])) i--;
|
|
15198
|
+
if (i === 0) return 0;
|
|
15199
|
+
return wordRangeAt(text, i - 1)[0];
|
|
15200
|
+
}
|
|
15201
|
+
/** Next word end (Ctrl+Right semantics). */
|
|
15202
|
+
function nextWordBoundary(text, offset) {
|
|
15203
|
+
let i = offset;
|
|
15204
|
+
while (i < text.length && /\s/.test(text[i])) i++;
|
|
15205
|
+
if (i >= text.length) return text.length;
|
|
15206
|
+
return wordRangeAt(text, i)[1];
|
|
15207
|
+
}
|
|
15208
|
+
function lineIndexAt(layout, offset) {
|
|
15209
|
+
const lines = layout.lines;
|
|
15210
|
+
if (lines.length === 0) return 0;
|
|
15211
|
+
let fallback = 0;
|
|
15212
|
+
for (let i = 0; i < lines.length; i++) {
|
|
15213
|
+
const l = lines[i];
|
|
15214
|
+
if (l.start <= offset) fallback = i;
|
|
15215
|
+
if (offset >= l.start && offset <= l.end) {
|
|
15216
|
+
const next = lines[i + 1];
|
|
15217
|
+
if (offset === l.end && !l.lastInParagraph && next && next.start === offset) continue;
|
|
15218
|
+
return i;
|
|
15219
|
+
}
|
|
15220
|
+
}
|
|
15221
|
+
return fallback;
|
|
15222
|
+
}
|
|
15223
|
+
function lineX(line, offset, measure) {
|
|
15224
|
+
const k = Math.max(0, Math.min(offset - line.start, line.text.length));
|
|
15225
|
+
return line.offsetX + (k === 0 ? 0 : measure(line.text.slice(0, k)));
|
|
15226
|
+
}
|
|
15227
|
+
/** Caret rectangle in text-local coordinates (before padding/valign offset). */
|
|
15228
|
+
function caretRect(layout, offset, measure) {
|
|
15229
|
+
const i = lineIndexAt(layout, offset);
|
|
15230
|
+
const line = layout.lines[i];
|
|
15231
|
+
if (!line) return {
|
|
15232
|
+
x: 0,
|
|
15233
|
+
y: 0,
|
|
15234
|
+
height: layout.lineHeightPx,
|
|
15235
|
+
line: 0
|
|
15236
|
+
};
|
|
15237
|
+
return {
|
|
15238
|
+
x: lineX(line, offset, measure),
|
|
15239
|
+
y: i * layout.lineHeightPx,
|
|
15240
|
+
height: layout.lineHeightPx,
|
|
15241
|
+
line: i
|
|
15242
|
+
};
|
|
15243
|
+
}
|
|
15244
|
+
/** Offset closest to a point in text-local coordinates. */
|
|
15245
|
+
function offsetFromPoint(layout, point, measure) {
|
|
15246
|
+
const lines = layout.lines;
|
|
15247
|
+
if (lines.length === 0) return 0;
|
|
15248
|
+
return offsetInLine(lines[Math.max(0, Math.min(lines.length - 1, Math.floor(point.y / layout.lineHeightPx)))], point.x, measure);
|
|
15249
|
+
}
|
|
15250
|
+
function offsetInLine(line, x, measure) {
|
|
15251
|
+
const rel = x - line.offsetX;
|
|
15252
|
+
if (rel <= 0 || line.text === "") return line.start;
|
|
15253
|
+
const b = boundaries(line.text, graphemeSeg);
|
|
15254
|
+
let prevW = 0;
|
|
15255
|
+
for (let k = 1; k < b.length; k++) {
|
|
15256
|
+
const w = measure(line.text.slice(0, b[k]));
|
|
15257
|
+
if (rel < (prevW + w) / 2) return line.start + b[k - 1];
|
|
15258
|
+
prevW = w;
|
|
15259
|
+
}
|
|
15260
|
+
return line.end;
|
|
15261
|
+
}
|
|
15262
|
+
/** Move one visual line up/down keeping a desired x. */
|
|
15263
|
+
function moveVertical(layout, offset, dir, desiredX, measure) {
|
|
15264
|
+
const j = lineIndexAt(layout, offset) + dir;
|
|
15265
|
+
if (j < 0) return 0;
|
|
15266
|
+
if (j >= layout.lines.length) {
|
|
15267
|
+
const last = layout.lines[layout.lines.length - 1];
|
|
15268
|
+
return last ? last.end : 0;
|
|
15269
|
+
}
|
|
15270
|
+
return offsetInLine(layout.lines[j], desiredX, measure);
|
|
15271
|
+
}
|
|
15272
|
+
function lineHome(layout, offset) {
|
|
15273
|
+
return layout.lines[lineIndexAt(layout, offset)]?.start ?? 0;
|
|
15274
|
+
}
|
|
15275
|
+
function lineEnd(layout, offset) {
|
|
15276
|
+
return layout.lines[lineIndexAt(layout, offset)]?.end ?? 0;
|
|
15277
|
+
}
|
|
15278
|
+
/** Selection rectangles (one per touched line) in text-local coordinates. */
|
|
15279
|
+
function selectionRects(layout, from, to, measure) {
|
|
15280
|
+
if (from > to) [from, to] = [to, from];
|
|
15281
|
+
if (from === to) return [];
|
|
15282
|
+
const out = [];
|
|
15283
|
+
layout.lines.forEach((line, i) => {
|
|
15284
|
+
const hasNewline = line.lastInParagraph;
|
|
15285
|
+
const lineFrom = Math.max(from, line.start);
|
|
15286
|
+
const lineTo = Math.min(to, hasNewline ? line.end + 1 : line.end);
|
|
15287
|
+
if (lineTo <= lineFrom && !(line.start === line.end && from <= line.start && to > line.start)) return;
|
|
15288
|
+
const x1 = lineX(line, Math.min(lineFrom, line.end), measure);
|
|
15289
|
+
let x2 = lineX(line, Math.min(lineTo, line.end), measure);
|
|
15290
|
+
if (hasNewline && to > line.end) x2 += Math.max(4, layout.textHeight / 3);
|
|
15291
|
+
if (x2 <= x1) return;
|
|
15292
|
+
out.push({
|
|
15293
|
+
x: x1,
|
|
15294
|
+
y: i * layout.lineHeightPx,
|
|
15295
|
+
width: x2 - x1,
|
|
15296
|
+
height: layout.lineHeightPx
|
|
15297
|
+
});
|
|
15298
|
+
});
|
|
15299
|
+
return out;
|
|
15300
|
+
}
|
|
15301
|
+
//#endregion
|
|
15302
|
+
//#region src/nodes/text-v2/edit/model.ts
|
|
15303
|
+
/**
|
|
15304
|
+
* Session-local edit state: content + selection + undo/redo with
|
|
15305
|
+
* coalescing. Typing and deleting merge into one entry while they continue
|
|
15306
|
+
* in the same direction without a selection move; paste and IME composition
|
|
15307
|
+
* are always one entry each. Independent of Konva and the DOM.
|
|
15308
|
+
*/
|
|
15309
|
+
var TextEditModel = class {
|
|
15310
|
+
blocks;
|
|
15311
|
+
anchor = 0;
|
|
15312
|
+
focus = 0;
|
|
15313
|
+
/** x kept while moving vertically; reset by any other move. */
|
|
15314
|
+
desiredX = null;
|
|
15315
|
+
undoStack = [];
|
|
15316
|
+
redoStack = [];
|
|
15317
|
+
lastKind = null;
|
|
15318
|
+
lastEditEnd = -1;
|
|
15319
|
+
composing = false;
|
|
15320
|
+
constructor(blocks) {
|
|
15321
|
+
this.blocks = blocks;
|
|
15322
|
+
const len = blocksToText(blocks).length;
|
|
15323
|
+
this.anchor = this.focus = len;
|
|
15324
|
+
}
|
|
15325
|
+
getBlocks() {
|
|
15326
|
+
return this.blocks;
|
|
15327
|
+
}
|
|
15328
|
+
getText() {
|
|
15329
|
+
return blocksToText(this.blocks);
|
|
15330
|
+
}
|
|
15331
|
+
getSelection() {
|
|
15332
|
+
return {
|
|
15333
|
+
start: Math.min(this.anchor, this.focus),
|
|
15334
|
+
end: Math.max(this.anchor, this.focus)
|
|
15335
|
+
};
|
|
15336
|
+
}
|
|
15337
|
+
getSelectedText() {
|
|
15338
|
+
const { start, end } = this.getSelection();
|
|
15339
|
+
return this.getText().slice(start, end);
|
|
15340
|
+
}
|
|
15341
|
+
canUndo() {
|
|
15342
|
+
return this.undoStack.length > 0;
|
|
15343
|
+
}
|
|
15344
|
+
canRedo() {
|
|
15345
|
+
return this.redoStack.length > 0;
|
|
15346
|
+
}
|
|
15347
|
+
setSelection(anchor, focus) {
|
|
15348
|
+
const len = this.getText().length;
|
|
15349
|
+
this.anchor = Math.max(0, Math.min(anchor, len));
|
|
15350
|
+
this.focus = Math.max(0, Math.min(focus, len));
|
|
15351
|
+
this.breakCoalescing();
|
|
15352
|
+
}
|
|
15353
|
+
moveTo(offset, extend = false, keepDesiredX = false) {
|
|
15354
|
+
const len = this.getText().length;
|
|
15355
|
+
const o = Math.max(0, Math.min(offset, len));
|
|
15356
|
+
this.focus = o;
|
|
15357
|
+
if (!extend) this.anchor = o;
|
|
15358
|
+
if (!keepDesiredX) this.desiredX = null;
|
|
15359
|
+
this.breakCoalescing();
|
|
15360
|
+
}
|
|
15361
|
+
selectAll() {
|
|
15362
|
+
this.anchor = 0;
|
|
15363
|
+
this.focus = this.getText().length;
|
|
15364
|
+
this.breakCoalescing();
|
|
15365
|
+
}
|
|
15366
|
+
breakCoalescing() {
|
|
15367
|
+
if (!this.composing) this.lastKind = null;
|
|
15368
|
+
}
|
|
15369
|
+
beginComposition() {
|
|
15370
|
+
this.composing = true;
|
|
15371
|
+
this.lastKind = null;
|
|
15372
|
+
}
|
|
15373
|
+
endComposition() {
|
|
15374
|
+
this.composing = false;
|
|
15375
|
+
this.lastKind = null;
|
|
15376
|
+
}
|
|
15377
|
+
snapshot() {
|
|
15378
|
+
return {
|
|
15379
|
+
blocks: this.blocks,
|
|
15380
|
+
anchor: this.anchor,
|
|
15381
|
+
focus: this.focus
|
|
15382
|
+
};
|
|
15383
|
+
}
|
|
15384
|
+
record(kind, editStart) {
|
|
15385
|
+
if (!(this.lastKind === kind && (kind === "type" || kind === "delete" || kind === "ime") && (kind === "ime" || this.lastEditEnd === editStart || kind === "delete"))) {
|
|
15386
|
+
this.undoStack.push(this.snapshot());
|
|
15387
|
+
if (this.undoStack.length > 500) this.undoStack.shift();
|
|
15388
|
+
}
|
|
15389
|
+
this.redoStack = [];
|
|
15390
|
+
this.lastKind = kind;
|
|
15391
|
+
}
|
|
15392
|
+
/** Replace [start,end) with plain text and put the caret after it. */
|
|
15393
|
+
replaceRange(start, end, text, kind) {
|
|
15394
|
+
const len = this.getText().length;
|
|
15395
|
+
start = Math.max(0, Math.min(start, len));
|
|
15396
|
+
end = Math.max(start, Math.min(end, len));
|
|
15397
|
+
if (start === end && text === "") return false;
|
|
15398
|
+
this.record(kind, start);
|
|
15399
|
+
this.blocks = replaceRange(this.blocks, start, end, text);
|
|
15400
|
+
this.anchor = this.focus = start + text.length;
|
|
15401
|
+
this.lastEditEnd = this.focus;
|
|
15402
|
+
this.desiredX = null;
|
|
15403
|
+
return true;
|
|
15404
|
+
}
|
|
15405
|
+
replaceSelection(text, kind) {
|
|
15406
|
+
const { start, end } = this.getSelection();
|
|
15407
|
+
return this.replaceRange(start, end, text, kind);
|
|
15408
|
+
}
|
|
15409
|
+
backspace(byWord = false) {
|
|
15410
|
+
const { start, end } = this.getSelection();
|
|
15411
|
+
if (start !== end) return this.replaceRange(start, end, "", "delete");
|
|
15412
|
+
if (start === 0) return false;
|
|
15413
|
+
const text = this.getText();
|
|
15414
|
+
const from = byWord ? prevWordBoundary(text, start) : prevGrapheme(text, start);
|
|
15415
|
+
return this.replaceRange(from, start, "", "delete");
|
|
15416
|
+
}
|
|
15417
|
+
del(byWord = false) {
|
|
15418
|
+
const { start, end } = this.getSelection();
|
|
15419
|
+
if (start !== end) return this.replaceRange(start, end, "", "delete");
|
|
15420
|
+
const text = this.getText();
|
|
15421
|
+
if (start >= text.length) return false;
|
|
15422
|
+
const to = byWord ? nextWordBoundary(text, start) : nextGrapheme(text, start);
|
|
15423
|
+
return this.replaceRange(start, to, "", "delete");
|
|
15424
|
+
}
|
|
15425
|
+
undo() {
|
|
15426
|
+
const prev = this.undoStack.pop();
|
|
15427
|
+
if (!prev) return false;
|
|
15428
|
+
this.redoStack.push(this.snapshot());
|
|
15429
|
+
this.restore(prev);
|
|
15430
|
+
return true;
|
|
15431
|
+
}
|
|
15432
|
+
redo() {
|
|
15433
|
+
const next = this.redoStack.pop();
|
|
15434
|
+
if (!next) return false;
|
|
15435
|
+
this.undoStack.push(this.snapshot());
|
|
15436
|
+
this.restore(next);
|
|
15437
|
+
return true;
|
|
15438
|
+
}
|
|
15439
|
+
restore(s) {
|
|
15440
|
+
this.blocks = s.blocks;
|
|
15441
|
+
this.anchor = s.anchor;
|
|
15442
|
+
this.focus = s.focus;
|
|
15443
|
+
this.lastKind = null;
|
|
15444
|
+
this.desiredX = null;
|
|
15445
|
+
}
|
|
15446
|
+
};
|
|
15447
|
+
const SLICE_BUDGET_MS = 8;
|
|
15448
|
+
const BLINK_MS = 530;
|
|
15449
|
+
var TextEditSession = class {
|
|
15450
|
+
model;
|
|
15451
|
+
target;
|
|
15452
|
+
host;
|
|
15453
|
+
id;
|
|
15454
|
+
abort = new AbortController();
|
|
15455
|
+
textarea = null;
|
|
15456
|
+
overlay = null;
|
|
15457
|
+
blinkTimer;
|
|
15458
|
+
caretOn = true;
|
|
15459
|
+
dragging = false;
|
|
15460
|
+
composing = false;
|
|
15461
|
+
pendingTimer;
|
|
15462
|
+
pendingFlush = false;
|
|
15463
|
+
destroyed = false;
|
|
15464
|
+
initialJson;
|
|
15465
|
+
constructor(target, host, id = "text-edit-session") {
|
|
15466
|
+
this.target = target;
|
|
15467
|
+
this.host = host;
|
|
15468
|
+
this.id = id;
|
|
15469
|
+
this.model = new TextEditModel(target.getBlocks());
|
|
15470
|
+
this.initialJson = JSON.stringify(this.model.getBlocks());
|
|
15471
|
+
}
|
|
15472
|
+
start() {
|
|
15473
|
+
const signal = this.abort.signal;
|
|
15474
|
+
host_signal(this.host.signal, this.abort);
|
|
15475
|
+
this.createTextarea();
|
|
15476
|
+
this.createOverlay();
|
|
15477
|
+
const ta = this.textarea;
|
|
15478
|
+
const opts = { signal };
|
|
15479
|
+
ta.addEventListener("keydown", this.onKeyDown, opts);
|
|
15480
|
+
ta.addEventListener("keyup", this.onKeyUp, opts);
|
|
15481
|
+
ta.addEventListener("beforeinput", this.onBeforeInput, opts);
|
|
15482
|
+
ta.addEventListener("input", this.onInput, opts);
|
|
15483
|
+
ta.addEventListener("paste", this.onPaste, opts);
|
|
15484
|
+
ta.addEventListener("compositionstart", this.onCompositionStart, opts);
|
|
15485
|
+
ta.addEventListener("compositionend", this.onCompositionEnd, opts);
|
|
15486
|
+
ta.addEventListener("select", this.syncFromTextarea, opts);
|
|
15487
|
+
ta.ownerDocument.addEventListener("selectionchange", this.syncFromTextarea, opts);
|
|
15488
|
+
window.addEventListener("pointerdown", this.onPointerDown, {
|
|
15489
|
+
signal,
|
|
15490
|
+
capture: true
|
|
15491
|
+
});
|
|
15492
|
+
window.addEventListener("pointermove", this.onPointerMove, { signal });
|
|
15493
|
+
window.addEventListener("pointerup", this.onPointerUp, { signal });
|
|
15494
|
+
window.addEventListener("mousedown", this.onMouseDown, {
|
|
15495
|
+
signal,
|
|
15496
|
+
capture: true
|
|
15497
|
+
});
|
|
15498
|
+
window.weaveTextEditing = {
|
|
15499
|
+
...window.weaveTextEditing ?? {},
|
|
15500
|
+
[this.id]: this.id
|
|
15501
|
+
};
|
|
15502
|
+
this.syncTextarea();
|
|
15503
|
+
ta.focus();
|
|
15504
|
+
this.positionTextarea();
|
|
15505
|
+
this.startBlink();
|
|
15506
|
+
this.repaint();
|
|
15507
|
+
}
|
|
15508
|
+
/** Final content and whether it differs from when the session started. */
|
|
15509
|
+
getResult() {
|
|
15510
|
+
return {
|
|
15511
|
+
blocks: this.model.getBlocks(),
|
|
15512
|
+
changed: JSON.stringify(this.model.getBlocks()) !== this.initialJson
|
|
15513
|
+
};
|
|
15514
|
+
}
|
|
15515
|
+
destroy() {
|
|
15516
|
+
if (this.destroyed) return;
|
|
15517
|
+
this.destroyed = true;
|
|
15518
|
+
this.flushPending();
|
|
15519
|
+
this.abort.abort();
|
|
15520
|
+
clearInterval(this.blinkTimer);
|
|
15521
|
+
clearTimeout(this.pendingTimer);
|
|
15522
|
+
this.textarea?.remove();
|
|
15523
|
+
this.textarea = null;
|
|
15524
|
+
const layer = this.overlay?.getLayer();
|
|
15525
|
+
this.overlay?.destroy();
|
|
15526
|
+
this.overlay = null;
|
|
15527
|
+
layer?.batchDraw();
|
|
15528
|
+
const w = window;
|
|
15529
|
+
if (w.weaveTextEditing) delete w.weaveTextEditing[this.id];
|
|
15530
|
+
}
|
|
15531
|
+
focus() {
|
|
15532
|
+
this.textarea?.focus();
|
|
15533
|
+
}
|
|
15534
|
+
createTextarea() {
|
|
15535
|
+
const ta = this.host.container.ownerDocument.createElement("textarea");
|
|
15536
|
+
ta.setAttribute("autocapitalize", "off");
|
|
15537
|
+
ta.setAttribute("autocomplete", "off");
|
|
15538
|
+
ta.setAttribute("autocorrect", "off");
|
|
15539
|
+
ta.setAttribute("spellcheck", "false");
|
|
15540
|
+
ta.setAttribute("wrap", "off");
|
|
15541
|
+
ta.setAttribute("aria-label", "Text editor");
|
|
15542
|
+
ta.tabIndex = 1;
|
|
15543
|
+
Object.assign(ta.style, {
|
|
15544
|
+
position: "absolute",
|
|
15545
|
+
left: "0px",
|
|
15546
|
+
top: "0px",
|
|
15547
|
+
width: "1px",
|
|
15548
|
+
height: "1em",
|
|
15549
|
+
margin: "0",
|
|
15550
|
+
padding: "0",
|
|
15551
|
+
border: "0",
|
|
15552
|
+
outline: "none",
|
|
15553
|
+
resize: "none",
|
|
15554
|
+
overflow: "hidden",
|
|
15555
|
+
whiteSpace: "pre",
|
|
15556
|
+
opacity: "0",
|
|
15557
|
+
fontSize: "16px",
|
|
15558
|
+
background: "transparent",
|
|
15559
|
+
color: "transparent",
|
|
15560
|
+
caretColor: "transparent",
|
|
15561
|
+
pointerEvents: "none",
|
|
15562
|
+
zIndex: "1"
|
|
15563
|
+
});
|
|
15564
|
+
this.host.container.appendChild(ta);
|
|
15565
|
+
this.textarea = ta;
|
|
15566
|
+
}
|
|
15567
|
+
syncTextarea() {
|
|
15568
|
+
const ta = this.textarea;
|
|
15569
|
+
if (!ta || this.composing) return;
|
|
15570
|
+
const text = this.model.getText();
|
|
15571
|
+
if (ta.value !== text) ta.value = text;
|
|
15572
|
+
const { start, end } = this.model.getSelection();
|
|
15573
|
+
const backward = this.model.focus < this.model.anchor;
|
|
15574
|
+
if (ta.selectionStart !== start || ta.selectionEnd !== end) ta.setSelectionRange(start, end, backward ? "backward" : "forward");
|
|
15575
|
+
}
|
|
15576
|
+
positionTextarea() {
|
|
15577
|
+
const ta = this.textarea;
|
|
15578
|
+
if (!ta) return;
|
|
15579
|
+
const c = this.caretStagePoint();
|
|
15580
|
+
const p = this.host.stageToContainer(c);
|
|
15581
|
+
ta.style.left = `${p.x}px`;
|
|
15582
|
+
ta.style.top = `${p.y}px`;
|
|
15583
|
+
}
|
|
15584
|
+
caretStagePoint() {
|
|
15585
|
+
const r = caretRect(this.target.getLayout(), this.model.focus, this.target.measure);
|
|
15586
|
+
const o = this.target.getContentOffset();
|
|
15587
|
+
return this.target.getAbsoluteTransform().point({
|
|
15588
|
+
x: r.x + o.x,
|
|
15589
|
+
y: r.y + o.y
|
|
15590
|
+
});
|
|
15591
|
+
}
|
|
15592
|
+
syncFromTextarea = () => {
|
|
15593
|
+
const ta = this.textarea;
|
|
15594
|
+
if (!ta || this.composing || this.destroyed) return;
|
|
15595
|
+
if (ta.getRootNode().activeElement !== ta) return;
|
|
15596
|
+
if (ta.value !== this.model.getText()) return;
|
|
15597
|
+
const backward = ta.selectionDirection === "backward";
|
|
15598
|
+
const a = backward ? ta.selectionEnd : ta.selectionStart;
|
|
15599
|
+
const f = backward ? ta.selectionStart : ta.selectionEnd;
|
|
15600
|
+
if (a !== this.model.anchor || f !== this.model.focus) {
|
|
15601
|
+
this.model.setSelection(a, f);
|
|
15602
|
+
this.afterSelectionChange();
|
|
15603
|
+
}
|
|
15604
|
+
};
|
|
15605
|
+
onKeyDown = (e) => {
|
|
15606
|
+
e.stopPropagation();
|
|
15607
|
+
const mod = e.ctrlKey || e.metaKey;
|
|
15608
|
+
const key = e.key.toLowerCase();
|
|
15609
|
+
if (e.key === "Escape") {
|
|
15610
|
+
e.preventDefault();
|
|
15611
|
+
this.host.onRequestExit("escape");
|
|
15612
|
+
return;
|
|
15613
|
+
}
|
|
15614
|
+
if (mod && key === "z" && !e.shiftKey) {
|
|
15615
|
+
e.preventDefault();
|
|
15616
|
+
this.undo();
|
|
15617
|
+
return;
|
|
15618
|
+
}
|
|
15619
|
+
if (mod && (key === "y" || key === "z" && e.shiftKey)) {
|
|
15620
|
+
e.preventDefault();
|
|
15621
|
+
this.redo();
|
|
15622
|
+
return;
|
|
15623
|
+
}
|
|
15624
|
+
if (e.key === "Tab") {
|
|
15625
|
+
e.preventDefault();
|
|
15626
|
+
return;
|
|
15627
|
+
}
|
|
15628
|
+
if (this.composing) return;
|
|
15629
|
+
if ((e.key === "ArrowUp" || e.key === "ArrowDown") && !mod && !e.altKey) {
|
|
15630
|
+
e.preventDefault();
|
|
15631
|
+
this.moveVertically(e.key === "ArrowUp" ? -1 : 1, e.shiftKey);
|
|
15632
|
+
return;
|
|
15633
|
+
}
|
|
15634
|
+
if ((e.key === "Home" || e.key === "End") && !mod) {
|
|
15635
|
+
e.preventDefault();
|
|
15636
|
+
const layout = this.target.getLayout();
|
|
15637
|
+
const to = e.key === "Home" ? lineHome(layout, this.model.focus) : lineEnd(layout, this.model.focus);
|
|
15638
|
+
this.model.moveTo(to, e.shiftKey);
|
|
15639
|
+
this.afterSelectionChange();
|
|
15640
|
+
return;
|
|
15641
|
+
}
|
|
15642
|
+
setTimeout(this.syncFromTextarea, 0);
|
|
15643
|
+
};
|
|
15644
|
+
onKeyUp = (e) => {
|
|
15645
|
+
e.stopPropagation();
|
|
15646
|
+
this.syncFromTextarea();
|
|
15647
|
+
};
|
|
15648
|
+
onBeforeInput = (e) => {
|
|
15649
|
+
if (e.inputType === "historyUndo" || e.inputType === "historyRedo") e.preventDefault();
|
|
15650
|
+
};
|
|
15651
|
+
moveVertically(dir, extend) {
|
|
15652
|
+
const layout = this.target.getLayout();
|
|
15653
|
+
if (this.model.desiredX === null) this.model.desiredX = caretRect(layout, this.model.focus, this.target.measure).x;
|
|
15654
|
+
const to = moveVertical(layout, this.model.focus, dir, this.model.desiredX, this.target.measure);
|
|
15655
|
+
this.model.moveTo(to, extend, true);
|
|
15656
|
+
this.afterSelectionChange();
|
|
15657
|
+
}
|
|
15658
|
+
onInput = (e) => {
|
|
15659
|
+
const ta = this.textarea;
|
|
15660
|
+
if (!ta) return;
|
|
15661
|
+
const oldText = this.model.getText();
|
|
15662
|
+
const newText = ta.value;
|
|
15663
|
+
if (oldText === newText) return;
|
|
15664
|
+
let p = 0;
|
|
15665
|
+
const max = Math.min(oldText.length, newText.length);
|
|
15666
|
+
while (p < max && oldText[p] === newText[p]) p++;
|
|
15667
|
+
let s = 0;
|
|
15668
|
+
while (s < max - p && oldText[oldText.length - 1 - s] === newText[newText.length - 1 - s]) s++;
|
|
15669
|
+
const inserted = newText.slice(p, newText.length - s);
|
|
15670
|
+
const inputType = e.inputType ?? "";
|
|
15671
|
+
const kind = this.composing || inputType.includes("Composition") ? "ime" : inputType.startsWith("delete") ? "delete" : inputType === "insertFromPaste" ? "paste" : "type";
|
|
15672
|
+
this.model.replaceRange(p, oldText.length - s, inserted, kind);
|
|
15673
|
+
this.model.anchor = this.model.focus = Math.min(ta.selectionEnd, this.model.getText().length);
|
|
15674
|
+
this.afterContentChange(inserted.length);
|
|
15675
|
+
};
|
|
15676
|
+
onPaste = (e) => {
|
|
15677
|
+
e.stopPropagation();
|
|
15678
|
+
e.preventDefault();
|
|
15679
|
+
const raw = e.clipboardData?.getData("text/plain") ?? "";
|
|
15680
|
+
if (raw === "") return;
|
|
15681
|
+
this.insertText(raw.replace(/\r\n?/g, "\n"), "paste");
|
|
15682
|
+
};
|
|
15683
|
+
onCompositionStart = () => {
|
|
15684
|
+
this.composing = true;
|
|
15685
|
+
this.model.beginComposition();
|
|
15686
|
+
};
|
|
15687
|
+
onCompositionEnd = () => {
|
|
15688
|
+
this.composing = false;
|
|
15689
|
+
this.model.endComposition();
|
|
15690
|
+
this.syncFromTextarea();
|
|
15691
|
+
};
|
|
15692
|
+
/** Programmatic insert at the selection (paste, tests, host actions). */
|
|
15693
|
+
insertText(text, kind = "paste") {
|
|
15694
|
+
if (this.model.replaceSelection(text, kind)) {
|
|
15695
|
+
this.syncTextarea();
|
|
15696
|
+
this.afterContentChange(text.length);
|
|
15697
|
+
}
|
|
15698
|
+
}
|
|
15699
|
+
undo() {
|
|
15700
|
+
this.flushPending();
|
|
15701
|
+
if (this.model.undo()) {
|
|
15702
|
+
this.syncTextarea();
|
|
15703
|
+
this.afterContentChange(0, true);
|
|
15704
|
+
}
|
|
15705
|
+
}
|
|
15706
|
+
redo() {
|
|
15707
|
+
this.flushPending();
|
|
15708
|
+
if (this.model.redo()) {
|
|
15709
|
+
this.syncTextarea();
|
|
15710
|
+
this.afterContentChange(0, true);
|
|
15711
|
+
}
|
|
15712
|
+
}
|
|
15713
|
+
toLocal(e) {
|
|
15714
|
+
const stagePt = this.host.clientToStage(e.clientX, e.clientY);
|
|
15715
|
+
const p = this.target.getAbsoluteTransform().copy().invert().point(stagePt);
|
|
15716
|
+
const o = this.target.getContentOffset();
|
|
15717
|
+
return {
|
|
15718
|
+
x: p.x - o.x,
|
|
15719
|
+
y: p.y - o.y
|
|
15720
|
+
};
|
|
15721
|
+
}
|
|
15722
|
+
isInside(local) {
|
|
15723
|
+
const o = this.target.getContentOffset();
|
|
15724
|
+
const { width, height } = this.target.getSize();
|
|
15725
|
+
return local.x + o.x >= 0 && local.y + o.y >= 0 && local.x + o.x <= width && local.y + o.y <= height;
|
|
15726
|
+
}
|
|
15727
|
+
onMouseDown = (e) => {
|
|
15728
|
+
if (this.isInside(this.toLocal(e))) e.preventDefault();
|
|
15729
|
+
};
|
|
15730
|
+
onPointerDown = (e) => {
|
|
15731
|
+
if (e.button !== 0 && e.pointerType === "mouse") return;
|
|
15732
|
+
if (this.textarea && e.target === this.textarea) return;
|
|
15733
|
+
const local = this.toLocal(e);
|
|
15734
|
+
if (!this.isInside(local)) {
|
|
15735
|
+
e.stopPropagation();
|
|
15736
|
+
this.host.onRequestExit("outside");
|
|
15737
|
+
return;
|
|
15738
|
+
}
|
|
15739
|
+
e.stopPropagation();
|
|
15740
|
+
e.preventDefault();
|
|
15741
|
+
const offset = offsetFromPoint(this.target.getLayout(), local, this.target.measure);
|
|
15742
|
+
const text = this.model.getText();
|
|
15743
|
+
if (e.detail >= 3) {
|
|
15744
|
+
const [s, en] = paragraphRangeAt(text, offset);
|
|
15745
|
+
this.model.setSelection(s, en);
|
|
15746
|
+
} else if (e.detail === 2) {
|
|
15747
|
+
const [s, en] = wordRangeAt(text, offset);
|
|
15748
|
+
this.model.setSelection(s, en);
|
|
15749
|
+
} else {
|
|
15750
|
+
this.model.moveTo(offset, e.shiftKey);
|
|
15751
|
+
this.dragging = true;
|
|
15752
|
+
}
|
|
15753
|
+
this.syncTextarea();
|
|
15754
|
+
this.afterSelectionChange();
|
|
15755
|
+
this.textarea?.focus();
|
|
15756
|
+
};
|
|
15757
|
+
onPointerMove = (e) => {
|
|
15758
|
+
if (!this.dragging) return;
|
|
15759
|
+
const local = this.toLocal(e);
|
|
15760
|
+
const offset = offsetFromPoint(this.target.getLayout(), local, this.target.measure);
|
|
15761
|
+
this.model.moveTo(offset, true);
|
|
15762
|
+
this.syncTextarea();
|
|
15763
|
+
this.afterSelectionChange();
|
|
15764
|
+
};
|
|
15765
|
+
onPointerUp = () => {
|
|
15766
|
+
if (!this.dragging) return;
|
|
15767
|
+
this.dragging = false;
|
|
15768
|
+
this.textarea?.focus();
|
|
15769
|
+
};
|
|
15770
|
+
afterSelectionChange() {
|
|
15771
|
+
this.syncTextarea();
|
|
15772
|
+
this.positionTextarea();
|
|
15773
|
+
this.resetBlink();
|
|
15774
|
+
this.repaint();
|
|
15775
|
+
}
|
|
15776
|
+
afterContentChange(insertedLength, immediate = false) {
|
|
15777
|
+
this.flushPending();
|
|
15778
|
+
if (!immediate && insertedLength > 2e4 && typeof this.target.warmLayout === "function") {
|
|
15779
|
+
this.pendingFlush = true;
|
|
15780
|
+
this.sliceLayout();
|
|
15781
|
+
} else this.applyContent();
|
|
15782
|
+
this.resetBlink();
|
|
15783
|
+
}
|
|
15784
|
+
sliceLayout = () => {
|
|
15785
|
+
if (!this.pendingFlush || this.destroyed) return;
|
|
15786
|
+
if (this.target.warmLayout(this.model.getBlocks(), SLICE_BUDGET_MS)) this.flushPending();
|
|
15787
|
+
else this.pendingTimer = setTimeout(this.sliceLayout, 0);
|
|
15788
|
+
};
|
|
15789
|
+
flushPending() {
|
|
15790
|
+
if (!this.pendingFlush) return;
|
|
15791
|
+
this.pendingFlush = false;
|
|
15792
|
+
clearTimeout(this.pendingTimer);
|
|
15793
|
+
this.applyContent();
|
|
15794
|
+
}
|
|
15795
|
+
applyContent() {
|
|
15796
|
+
this.target.setBlocks(this.model.getBlocks());
|
|
15797
|
+
this.host.onChange?.(this.model.getBlocks());
|
|
15798
|
+
this.positionTextarea();
|
|
15799
|
+
this.target.requestRedraw();
|
|
15800
|
+
this.repaint();
|
|
15801
|
+
}
|
|
15802
|
+
createOverlay() {
|
|
15803
|
+
const layer = this.host.utilityLayer;
|
|
15804
|
+
if (!layer) return;
|
|
15805
|
+
const overlay = new Konva.Shape({
|
|
15806
|
+
name: "text-v2-edit-overlay",
|
|
15807
|
+
listening: false,
|
|
15808
|
+
sceneFunc: (ctx, shape) => this.paintOverlay(ctx, shape)
|
|
15809
|
+
});
|
|
15810
|
+
layer.add(overlay);
|
|
15811
|
+
overlay.moveToTop();
|
|
15812
|
+
this.overlay = overlay;
|
|
15813
|
+
}
|
|
15814
|
+
paintOverlay(ctx, shape) {
|
|
15815
|
+
if (this.destroyed || this.pendingFlush) return;
|
|
15816
|
+
const layout = this.target.getLayout();
|
|
15817
|
+
const abs = this.target.getAbsoluteTransform();
|
|
15818
|
+
const rel = shape.getAbsoluteTransform().copy().invert().multiply(abs);
|
|
15819
|
+
const o = this.target.getContentOffset();
|
|
15820
|
+
ctx.save();
|
|
15821
|
+
ctx.transform(rel.m[0], rel.m[1], rel.m[2], rel.m[3], rel.m[4], rel.m[5]);
|
|
15822
|
+
ctx.translate(o.x, o.y);
|
|
15823
|
+
const { start, end } = this.model.getSelection();
|
|
15824
|
+
if (start !== end) {
|
|
15825
|
+
ctx.setAttr("fillStyle", this.host.selectionColor);
|
|
15826
|
+
for (const r of selectionRects(layout, start, end, this.target.measure)) ctx.fillRect(r.x, r.y, r.width, r.height);
|
|
15827
|
+
} else if (this.caretOn) {
|
|
15828
|
+
const c = caretRect(layout, this.model.focus, this.target.measure);
|
|
15829
|
+
const scale = Math.hypot(abs.m[0], abs.m[1]) || 1;
|
|
15830
|
+
const w = Math.max(1, 1.5) / scale;
|
|
15831
|
+
ctx.setAttr("fillStyle", this.host.caretColor);
|
|
15832
|
+
ctx.fillRect(c.x - w / 2, c.y, w, c.height);
|
|
15833
|
+
}
|
|
15834
|
+
ctx.restore();
|
|
15835
|
+
}
|
|
15836
|
+
repaint() {
|
|
15837
|
+
this.overlay?.getLayer()?.batchDraw();
|
|
15838
|
+
}
|
|
15839
|
+
startBlink() {
|
|
15840
|
+
this.blinkTimer = setInterval(() => {
|
|
15841
|
+
this.caretOn = !this.caretOn;
|
|
15842
|
+
this.repaint();
|
|
15843
|
+
}, BLINK_MS);
|
|
15844
|
+
}
|
|
15845
|
+
resetBlink() {
|
|
15846
|
+
this.caretOn = true;
|
|
15847
|
+
if (this.blinkTimer) {
|
|
15848
|
+
clearInterval(this.blinkTimer);
|
|
15849
|
+
this.startBlink();
|
|
15850
|
+
}
|
|
15851
|
+
}
|
|
15852
|
+
};
|
|
15853
|
+
function host_signal(signal, abort) {
|
|
15854
|
+
if (!signal) return;
|
|
15855
|
+
if (signal.aborted) abort.abort();
|
|
15856
|
+
else signal.addEventListener("abort", () => abort.abort(), { once: true });
|
|
15857
|
+
}
|
|
15858
|
+
//#endregion
|
|
15859
|
+
//#region src/nodes/text-v2/constants.ts
|
|
15860
|
+
/** Default threshold for `onTextSizeWarning` (chars of serialized blocks). */
|
|
15861
|
+
const WEAVE_TEXT_V2_SIZE_WARNING_THRESHOLD = 2e5;
|
|
15862
|
+
const WEAVE_TEXT_V2_SELECTION_COLOR = "rgba(66, 133, 244, 0.35)";
|
|
15863
|
+
const WEAVE_TEXT_V2_DEFAULT_CONFIG = {
|
|
15864
|
+
...WEAVE_TEXT_NODE_DEFAULT_CONFIG,
|
|
15865
|
+
sizeWarning: { threshold: WEAVE_TEXT_V2_SIZE_WARNING_THRESHOLD }
|
|
15866
|
+
};
|
|
15867
|
+
//#endregion
|
|
15868
|
+
//#region src/nodes/text-v2/text-v2.ts
|
|
15869
|
+
const CORNER_ANCHORS = [
|
|
15870
|
+
"top-left",
|
|
15871
|
+
"top-right",
|
|
15872
|
+
"bottom-left",
|
|
15873
|
+
"bottom-right"
|
|
15874
|
+
];
|
|
15875
|
+
const SIDE_ANCHORS = ["middle-right", "middle-left"];
|
|
15876
|
+
/**
|
|
15877
|
+
* Drop-in replacement for `WeaveTextNode`. Same `nodeType` (`'text'`), so
|
|
15878
|
+
* existing documents load unchanged; register it INSTEAD of `WeaveTextNode`
|
|
15879
|
+
* (handlers are keyed by node type). Renders through `WeaveTextV2Shape`
|
|
15880
|
+
* (blocks/runs model + layout engine) instead of `Konva.Text`.
|
|
15881
|
+
*/
|
|
15882
|
+
var WeaveTextV2Node = class extends WeaveNode {
|
|
15883
|
+
config;
|
|
15884
|
+
nodeType = WEAVE_TEXT_NODE_TYPE;
|
|
15885
|
+
createNode = false;
|
|
15886
|
+
editing = false;
|
|
15887
|
+
editingNodeId = null;
|
|
15888
|
+
session = null;
|
|
15889
|
+
sizeWarned = false;
|
|
15890
|
+
nodeRenderedAddedRegistered = false;
|
|
15891
|
+
keyPressHandler;
|
|
15892
|
+
constructor(params) {
|
|
15893
|
+
super();
|
|
15894
|
+
this.config = (0, import_merge.default)({}, WEAVE_TEXT_V2_DEFAULT_CONFIG, params?.config);
|
|
15895
|
+
}
|
|
15896
|
+
initialize() {
|
|
15897
|
+
this.keyPressHandler = void 0;
|
|
15898
|
+
this.editing = false;
|
|
15899
|
+
this.editingNodeId = null;
|
|
15900
|
+
this.session = null;
|
|
15901
|
+
this.nodeRenderedAddedRegistered = false;
|
|
15902
|
+
}
|
|
15903
|
+
/**
|
|
15904
|
+
* Resolve width/height for the layout mode. Auto modes size from the
|
|
15905
|
+
* engine's natural measure (independent of zoom, unlike the DOM-based
|
|
15906
|
+
* measure of the legacy node).
|
|
15907
|
+
*/
|
|
15908
|
+
resolveSize(shape, props) {
|
|
15909
|
+
let width = props.width;
|
|
15910
|
+
let height = props.height;
|
|
15911
|
+
if (props.layout === TEXT_LAYOUT.AUTO_ALL) {
|
|
15912
|
+
const nat = shape.measureNaturalSize();
|
|
15913
|
+
width = nat.width + 2;
|
|
15914
|
+
height = nat.height + 2;
|
|
15915
|
+
}
|
|
15916
|
+
if (props.layout === TEXT_LAYOUT.SMART && !props.smartFixedWidth) {
|
|
15917
|
+
width = Math.max(1, shape.measureNaturalSize().width * 1.01);
|
|
15918
|
+
height = void 0;
|
|
15919
|
+
}
|
|
15920
|
+
if (props.layout === TEXT_LAYOUT.SMART && props.smartFixedWidth) height = void 0;
|
|
15921
|
+
if (props.layout === TEXT_LAYOUT.AUTO_HEIGHT) height = void 0;
|
|
15922
|
+
return {
|
|
15923
|
+
width,
|
|
15924
|
+
height
|
|
15925
|
+
};
|
|
15926
|
+
}
|
|
15927
|
+
measureMultilineText(shape) {
|
|
15928
|
+
return () => {
|
|
15929
|
+
const nat = shape.measureNaturalSize();
|
|
15930
|
+
return {
|
|
15931
|
+
width: Math.max(1, nat.width),
|
|
15932
|
+
height: Math.max(1, nat.height)
|
|
15933
|
+
};
|
|
15934
|
+
};
|
|
15935
|
+
}
|
|
15936
|
+
linkOverrides(props) {
|
|
15937
|
+
return {
|
|
15938
|
+
linkPreviousFill: props.link ? props.linkPreviousFill ?? props.fill : props.linkPreviousFill,
|
|
15939
|
+
linkPreviousTextDecoration: props.link ? props.linkPreviousTextDecoration ?? props.textDecoration : props.linkPreviousTextDecoration
|
|
15940
|
+
};
|
|
15941
|
+
}
|
|
15942
|
+
outlineProps() {
|
|
15943
|
+
return this.config.outline.enabled ? {
|
|
15944
|
+
strokeEnabled: true,
|
|
15945
|
+
stroke: this.config.outline.color,
|
|
15946
|
+
strokeWidth: this.config.outline.width,
|
|
15947
|
+
fillAfterStrokeEnabled: true
|
|
15948
|
+
} : { strokeEnabled: false };
|
|
15949
|
+
}
|
|
15950
|
+
onRender(props) {
|
|
15951
|
+
const { linkPreviousFill, linkPreviousTextDecoration } = this.linkOverrides(props);
|
|
15952
|
+
const text = new WeaveTextV2Shape({
|
|
15953
|
+
...props,
|
|
15954
|
+
name: "node",
|
|
15955
|
+
...this.outlineProps(),
|
|
15956
|
+
...props.link && {
|
|
15957
|
+
textDecoration: "underline",
|
|
15958
|
+
fill: this.config.link.defaultColor,
|
|
15959
|
+
linkPreviousFill,
|
|
15960
|
+
linkPreviousTextDecoration
|
|
15961
|
+
}
|
|
15962
|
+
});
|
|
15963
|
+
this.setupDefaultNodeAugmentation(text);
|
|
15964
|
+
const defaultTransformerProperties = this.defaultGetTransformerProperties(this.config.transform);
|
|
15965
|
+
text.getTransformerProperties = function() {
|
|
15966
|
+
const layout = this.getAttrs().layout;
|
|
15967
|
+
if (layout === TEXT_LAYOUT.SMART) return {
|
|
15968
|
+
...defaultTransformerProperties,
|
|
15969
|
+
ignoreStroke: true,
|
|
15970
|
+
resizeEnabled: true,
|
|
15971
|
+
keepRatio: false,
|
|
15972
|
+
enabledAnchors: []
|
|
15973
|
+
};
|
|
15974
|
+
if (layout === TEXT_LAYOUT.AUTO_ALL) return {
|
|
15975
|
+
...defaultTransformerProperties,
|
|
15976
|
+
ignoreStroke: true,
|
|
15977
|
+
resizeEnabled: false,
|
|
15978
|
+
enabledAnchors: []
|
|
15979
|
+
};
|
|
15980
|
+
if (layout === TEXT_LAYOUT.AUTO_HEIGHT) return {
|
|
15981
|
+
...defaultTransformerProperties,
|
|
15982
|
+
ignoreStroke: true,
|
|
15983
|
+
resizeEnabled: true,
|
|
15984
|
+
enabledAnchors: SIDE_ANCHORS
|
|
15985
|
+
};
|
|
15986
|
+
return defaultTransformerProperties;
|
|
15987
|
+
};
|
|
15988
|
+
text.allowedAnchors = function() {
|
|
15989
|
+
const layout = this.getAttrs().layout;
|
|
15990
|
+
if (layout === TEXT_LAYOUT.SMART) return [...CORNER_ANCHORS, ...SIDE_ANCHORS];
|
|
15991
|
+
if (layout === TEXT_LAYOUT.AUTO_ALL) return [];
|
|
15992
|
+
if (layout === TEXT_LAYOUT.AUTO_HEIGHT) return SIDE_ANCHORS;
|
|
15993
|
+
return [
|
|
15994
|
+
...CORNER_ANCHORS,
|
|
15995
|
+
"top-center",
|
|
15996
|
+
"bottom-center",
|
|
15997
|
+
...SIDE_ANCHORS
|
|
15998
|
+
];
|
|
15999
|
+
};
|
|
16000
|
+
text.setAttrs({ measureMultilineText: this.measureMultilineText(text) });
|
|
16001
|
+
this.setupDefaultNodeEvents(text, { performScaleReset: false });
|
|
16002
|
+
text.dblClick = () => {
|
|
16003
|
+
if (this.editing) return;
|
|
16004
|
+
if (!(this.isSelecting() && this.isNodeSelected(text))) return;
|
|
16005
|
+
this.triggerEditMode(text);
|
|
16006
|
+
};
|
|
16007
|
+
text.setAttr("triggerEditMode", this.triggerEditMode.bind(this));
|
|
16008
|
+
this.setupLinkBehavior(text);
|
|
16009
|
+
this.setupTransform(text);
|
|
16010
|
+
if (!this.nodeRenderedAddedRegistered) {
|
|
16011
|
+
this.instance.addEventListener("onNodeRenderedAdded", (node) => {
|
|
16012
|
+
if (this.editing && this.editingNodeId !== null && node.id() === this.editingNodeId && node.getAttr("cancelEditMode")) node.getAttr("cancelEditMode")?.();
|
|
16013
|
+
});
|
|
16014
|
+
this.nodeRenderedAddedRegistered = true;
|
|
16015
|
+
}
|
|
16016
|
+
this.registerKeyPress();
|
|
16017
|
+
return text;
|
|
16018
|
+
}
|
|
16019
|
+
handleKeyPress = (e) => {
|
|
16020
|
+
if (e.code !== "Enter" || this.instance.getActiveAction() !== "selectionTool" || this.editing) return;
|
|
16021
|
+
e.preventDefault();
|
|
16022
|
+
const selected = this.getNodesSelectionPlugin()?.getSelectedNodes() ?? [];
|
|
16023
|
+
if (this.isSelecting() && selected.length === 1 && selected[0].getAttrs().nodeType === "text") this.triggerEditMode(selected[0]);
|
|
16024
|
+
};
|
|
16025
|
+
registerKeyPress() {
|
|
16026
|
+
if (this.instance.isServerSide() || this.keyPressHandler) return;
|
|
16027
|
+
this.keyPressHandler = this.handleKeyPress.bind(this);
|
|
16028
|
+
window.addEventListener("keypress", this.keyPressHandler, { signal: this.instance.getEventsController().signal });
|
|
16029
|
+
}
|
|
16030
|
+
onAdd() {
|
|
16031
|
+
this.registerKeyPress();
|
|
16032
|
+
}
|
|
16033
|
+
onDestroyInstance() {
|
|
16034
|
+
super.onDestroyInstance();
|
|
16035
|
+
this.session?.destroy();
|
|
16036
|
+
this.session = null;
|
|
16037
|
+
if (!this.instance.isServerSide() && this.keyPressHandler) {
|
|
16038
|
+
window.removeEventListener("keypress", this.keyPressHandler);
|
|
16039
|
+
this.keyPressHandler = void 0;
|
|
16040
|
+
}
|
|
16041
|
+
}
|
|
16042
|
+
/** Re-apply layout-driven width/height after live content changes. */
|
|
16043
|
+
applySize(shape) {
|
|
16044
|
+
const { width, height } = this.resolveSize(shape, shape.getAttrs());
|
|
16045
|
+
shape.setAttrs({
|
|
16046
|
+
width,
|
|
16047
|
+
height
|
|
16048
|
+
});
|
|
16049
|
+
}
|
|
16050
|
+
editTarget(shape) {
|
|
16051
|
+
return {
|
|
16052
|
+
getBlocks: () => shape.getBlocks(),
|
|
16053
|
+
setBlocks: (blocks) => {
|
|
16054
|
+
shape.setBlocks(blocks);
|
|
16055
|
+
this.applySize(shape);
|
|
16056
|
+
},
|
|
16057
|
+
getLayout: () => shape.ensureLayout(),
|
|
16058
|
+
measure: (t) => shape.measureText(t),
|
|
16059
|
+
getAbsoluteTransform: () => shape.getAbsoluteTransform(),
|
|
16060
|
+
getContentOffset: () => shape.getContentOffset(),
|
|
16061
|
+
getSize: () => ({
|
|
16062
|
+
width: shape.getWidth(),
|
|
16063
|
+
height: shape.getHeight()
|
|
16064
|
+
}),
|
|
16065
|
+
getFontSize: () => shape.fontSize(),
|
|
16066
|
+
requestRedraw: () => shape.getLayer()?.batchDraw(),
|
|
16067
|
+
warmLayout: (blocks, budget) => shape.warmLayout(blocks, budget)
|
|
16068
|
+
};
|
|
16069
|
+
}
|
|
16070
|
+
triggerEditMode(shape, create = false) {
|
|
16071
|
+
if (create) this.createNode = true;
|
|
16072
|
+
if (!this.instance.setMutexLock({
|
|
16073
|
+
nodeIds: [shape.id()],
|
|
16074
|
+
operation: "text-edit"
|
|
16075
|
+
})) return;
|
|
16076
|
+
this.editing = true;
|
|
16077
|
+
this.editingNodeId = shape.id();
|
|
16078
|
+
this.sizeWarned = false;
|
|
16079
|
+
const selectionPlugin = this.instance.getPlugin("nodesSelection");
|
|
16080
|
+
if (selectionPlugin) {
|
|
16081
|
+
const tr = selectionPlugin.getTransformer();
|
|
16082
|
+
this.instance.disablePlugin("nodesSelection");
|
|
16083
|
+
tr.hide();
|
|
16084
|
+
}
|
|
16085
|
+
const stage = this.instance.getStage();
|
|
16086
|
+
const container = stage.container();
|
|
16087
|
+
const rect = () => container.getBoundingClientRect();
|
|
16088
|
+
this.session = new TextEditSession(this.editTarget(shape), {
|
|
16089
|
+
utilityLayer: this.instance.getUtilityLayer(),
|
|
16090
|
+
container,
|
|
16091
|
+
clientToStage: (cx, cy) => {
|
|
16092
|
+
const r = rect();
|
|
16093
|
+
const sx = r.width ? stage.width() / r.width : 1;
|
|
16094
|
+
const sy = r.height ? stage.height() / r.height : 1;
|
|
16095
|
+
return {
|
|
16096
|
+
x: (cx - r.left) * sx,
|
|
16097
|
+
y: (cy - r.top) * sy
|
|
16098
|
+
};
|
|
16099
|
+
},
|
|
16100
|
+
stageToContainer: (p) => {
|
|
16101
|
+
const r = rect();
|
|
16102
|
+
const sx = r.width ? r.width / stage.width() : 1;
|
|
16103
|
+
const sy = r.height ? r.height / stage.height() : 1;
|
|
16104
|
+
return {
|
|
16105
|
+
x: p.x * sx,
|
|
16106
|
+
y: p.y * sy
|
|
16107
|
+
};
|
|
16108
|
+
},
|
|
16109
|
+
caretColor: this.config.cursor.color,
|
|
16110
|
+
selectionColor: WEAVE_TEXT_V2_SELECTION_COLOR,
|
|
16111
|
+
signal: this.instance.getEventsController().signal,
|
|
16112
|
+
onChange: (blocks) => this.checkSize(shape, blocks),
|
|
16113
|
+
onRequestExit: () => this.exitEditMode(shape)
|
|
16114
|
+
}, shape.id());
|
|
16115
|
+
shape.setAttr("cancelEditMode", () => this.exitEditMode(shape));
|
|
16116
|
+
stage.mode(WEAVE_STAGE_TEXT_EDITION_MODE);
|
|
16117
|
+
this.session.start();
|
|
16118
|
+
this.instance.emitEvent("onEnterTextNodeEditMode", { node: shape });
|
|
16119
|
+
}
|
|
16120
|
+
checkSize(shape, blocks) {
|
|
16121
|
+
const threshold = this.config.sizeWarning.threshold;
|
|
16122
|
+
if (blocksToText(blocks).length * 8 < threshold) {
|
|
16123
|
+
this.sizeWarned = false;
|
|
16124
|
+
return;
|
|
16125
|
+
}
|
|
16126
|
+
const size = JSON.stringify(blocks).length;
|
|
16127
|
+
if (size > threshold && !this.sizeWarned) {
|
|
16128
|
+
this.sizeWarned = true;
|
|
16129
|
+
this.instance.emitEvent("onTextSizeWarning", {
|
|
16130
|
+
node: shape,
|
|
16131
|
+
size,
|
|
16132
|
+
threshold
|
|
16133
|
+
});
|
|
16134
|
+
} else if (size <= threshold) this.sizeWarned = false;
|
|
16135
|
+
}
|
|
16136
|
+
exitEditMode(shape) {
|
|
16137
|
+
const session = this.session;
|
|
16138
|
+
if (!session || !this.editing) return;
|
|
16139
|
+
const { changed } = session.getResult();
|
|
16140
|
+
session.destroy();
|
|
16141
|
+
this.session = null;
|
|
16142
|
+
this.instance.releaseMutexLock();
|
|
16143
|
+
const stage = this.instance.getStage();
|
|
16144
|
+
stage.mode(WEAVE_STAGE_DEFAULT_MODE);
|
|
16145
|
+
this.editing = false;
|
|
16146
|
+
this.editingNodeId = null;
|
|
16147
|
+
shape.setAttr("cancelEditMode", void 0);
|
|
16148
|
+
this.commitNode(shape, changed);
|
|
16149
|
+
const liveNode = stage.findOne(`#${shape.id()}`) ?? shape;
|
|
16150
|
+
const selectionPlugin = this.instance.getPlugin("nodesSelection");
|
|
16151
|
+
if (selectionPlugin) {
|
|
16152
|
+
this.instance.enablePlugin("nodesSelection");
|
|
16153
|
+
selectionPlugin.setSelectedNodes([liveNode]);
|
|
16154
|
+
this.instance.triggerAction(SELECTION_TOOL_ACTION_NAME);
|
|
16155
|
+
}
|
|
16156
|
+
stage.container().tabIndex = 1;
|
|
16157
|
+
stage.container().click();
|
|
16158
|
+
stage.container().focus();
|
|
16159
|
+
this.instance.emitEvent("onExitTextNodeEditMode", { node: liveNode });
|
|
16160
|
+
}
|
|
16161
|
+
/** One `updateNode` per session; none when content is unchanged. */
|
|
16162
|
+
commitNode(shape, changed) {
|
|
16163
|
+
const live = this.instance.getStage().findOne(`#${shape.id()}`) ?? shape;
|
|
16164
|
+
if (this.createNode) {
|
|
16165
|
+
const serialized = this.serialize(live);
|
|
16166
|
+
if (live.text() !== "") {
|
|
16167
|
+
const container = live.getParent();
|
|
16168
|
+
live.destroy();
|
|
16169
|
+
this.instance.addNode(serialized, container?.getAttrs().id);
|
|
16170
|
+
} else live.destroy();
|
|
16171
|
+
} else if (changed) this.instance.updateNode(this.serialize(live));
|
|
16172
|
+
this.createNode = false;
|
|
16173
|
+
}
|
|
16174
|
+
setupTransform(text) {
|
|
16175
|
+
let actualAnchor = void 0;
|
|
16176
|
+
const isSide = () => SIDE_ANCHORS.includes(actualAnchor ?? "");
|
|
16177
|
+
const isCorner = () => CORNER_ANCHORS.includes(actualAnchor ?? "");
|
|
16178
|
+
const autoWidthLayout = () => [TEXT_LAYOUT.AUTO_HEIGHT, TEXT_LAYOUT.SMART].includes(text.getAttrs().layout);
|
|
16179
|
+
text.on("transformstart", (e) => {
|
|
16180
|
+
const ctrlOrMeta = e.evt.ctrlKey || e.evt.metaKey;
|
|
16181
|
+
this.instance.emitEvent("onTransform", e.target);
|
|
16182
|
+
actualAnchor = this.getNodesSelectionPlugin()?.getTransformer()?.getActiveAnchor();
|
|
16183
|
+
const layout = text.getAttrs().layout;
|
|
16184
|
+
this.getNodesSelectionPlugin()?.getTransformer()?.keepRatio(layout === TEXT_LAYOUT.SMART && isCorner() || layout === TEXT_LAYOUT.FIXED && ctrlOrMeta);
|
|
16185
|
+
if (autoWidthLayout() && isSide()) {
|
|
16186
|
+
text.wrap("word");
|
|
16187
|
+
text.scaleY(1);
|
|
16188
|
+
text.height(void 0);
|
|
16189
|
+
}
|
|
16190
|
+
e.cancelBubble = true;
|
|
16191
|
+
});
|
|
16192
|
+
text.on("transform", () => {
|
|
16193
|
+
if (autoWidthLayout() && isSide()) {
|
|
16194
|
+
text.width(text.width() * text.scaleX());
|
|
16195
|
+
text.scaleX(1);
|
|
16196
|
+
text.scaleY(1);
|
|
16197
|
+
text.height(void 0);
|
|
16198
|
+
text.getLayer()?.batchDraw();
|
|
16199
|
+
}
|
|
16200
|
+
const layout = text.getAttrs().layout;
|
|
16201
|
+
if (this.isSelecting() && this.isNodeSelected(text) && (layout !== TEXT_LAYOUT.SMART || !isCorner())) {
|
|
16202
|
+
text.width(text.width() * text.scaleX());
|
|
16203
|
+
resetScale(text);
|
|
16204
|
+
text.fontSize(text.fontSize() * text.scaleY());
|
|
16205
|
+
}
|
|
16206
|
+
text.setAttr("shouldUpdateOnTransform", false);
|
|
16207
|
+
text.getLayer()?.batchDraw();
|
|
16208
|
+
});
|
|
16209
|
+
text.on("transformend", () => {
|
|
16210
|
+
this.instance.emitEvent("onTransform", null);
|
|
16211
|
+
let definedSmartWidth = false;
|
|
16212
|
+
let smartFixedWidth = text.getAttr("smartFixedWidth") ?? false;
|
|
16213
|
+
const layout = text.getAttrs().layout;
|
|
16214
|
+
if (layout !== TEXT_LAYOUT.SMART && !isCorner()) this.scaleReset(text);
|
|
16215
|
+
if (layout === TEXT_LAYOUT.SMART && isCorner()) text.setAttrs({
|
|
16216
|
+
width: Math.ceil(text.width() * text.scaleX()),
|
|
16217
|
+
height: Math.ceil(text.height() * text.scaleY()),
|
|
16218
|
+
fontSize: text.fontSize() * text.scaleY(),
|
|
16219
|
+
scaleX: 1,
|
|
16220
|
+
scaleY: 1
|
|
16221
|
+
});
|
|
16222
|
+
if (autoWidthLayout() && isSide() && !smartFixedWidth && !definedSmartWidth) {
|
|
16223
|
+
text.setAttr("smartFixedWidth", true);
|
|
16224
|
+
smartFixedWidth = true;
|
|
16225
|
+
definedSmartWidth = true;
|
|
16226
|
+
text.width(Math.ceil(text.width() * text.scaleX()));
|
|
16227
|
+
text.scaleX(1);
|
|
16228
|
+
text.scaleY(1);
|
|
16229
|
+
text.height(void 0);
|
|
16230
|
+
text.getLayer()?.batchDraw();
|
|
16231
|
+
text.height(Math.ceil(text.height()));
|
|
16232
|
+
}
|
|
16233
|
+
if (layout === TEXT_LAYOUT.SMART && isSide() && smartFixedWidth && !definedSmartWidth) {
|
|
16234
|
+
text.width(Math.ceil(text.width() * text.scaleX()));
|
|
16235
|
+
text.scaleX(1);
|
|
16236
|
+
}
|
|
16237
|
+
this.instance.updateNode(this.serialize(text));
|
|
16238
|
+
});
|
|
16239
|
+
}
|
|
16240
|
+
onUpdate(nodeInstance, nextProps) {
|
|
16241
|
+
const shape = nodeInstance;
|
|
16242
|
+
if (this.editing && this.editingNodeId === shape.id()) nextProps = {
|
|
16243
|
+
...nextProps,
|
|
16244
|
+
text: shape.attrs.text,
|
|
16245
|
+
blocks: shape.attrs.blocks
|
|
16246
|
+
};
|
|
16247
|
+
const isLinked = Boolean(nextProps.link);
|
|
16248
|
+
const { linkPreviousFill, linkPreviousTextDecoration } = this.linkOverrides(nextProps);
|
|
16249
|
+
shape.setAttrs({
|
|
16250
|
+
...nextProps,
|
|
16251
|
+
...this.outlineProps(),
|
|
16252
|
+
link: nextProps.link,
|
|
16253
|
+
linkPreviousFill: isLinked ? linkPreviousFill : void 0,
|
|
16254
|
+
linkPreviousTextDecoration: isLinked ? linkPreviousTextDecoration : void 0,
|
|
16255
|
+
...isLinked && {
|
|
16256
|
+
textDecoration: "underline",
|
|
16257
|
+
fill: this.config.link.defaultColor
|
|
16258
|
+
}
|
|
16259
|
+
});
|
|
16260
|
+
if (nextProps.blocks === void 0 && shape.attrs.blocks !== void 0) shape.setAttr("blocks", void 0);
|
|
16261
|
+
const { width, height } = this.resolveSize(shape, nextProps);
|
|
16262
|
+
shape.setAttrs({
|
|
16263
|
+
width,
|
|
16264
|
+
height
|
|
16265
|
+
});
|
|
16266
|
+
if (!this.editing) {
|
|
16267
|
+
const selection = this.getNodesSelectionPlugin();
|
|
16268
|
+
if (selection) selection.setSelectedNodes(selection.getSelectedNodes());
|
|
16269
|
+
}
|
|
16270
|
+
}
|
|
16271
|
+
serialize(instance) {
|
|
16272
|
+
const attrs = instance.getAttrs();
|
|
16273
|
+
const cleanedAttrs = { ...attrs };
|
|
16274
|
+
delete cleanedAttrs.mutexLocked;
|
|
16275
|
+
delete cleanedAttrs.mutexUserId;
|
|
16276
|
+
delete cleanedAttrs.draggable;
|
|
16277
|
+
delete cleanedAttrs.triggerEditMode;
|
|
16278
|
+
delete cleanedAttrs.cancelEditMode;
|
|
16279
|
+
delete cleanedAttrs.measureMultilineText;
|
|
16280
|
+
delete cleanedAttrs.overridesMouseControl;
|
|
16281
|
+
delete cleanedAttrs.shouldUpdateOnTransform;
|
|
16282
|
+
delete cleanedAttrs.dragBoundFunc;
|
|
16283
|
+
if (cleanedAttrs.link) {
|
|
16284
|
+
if (typeof cleanedAttrs.linkPreviousFill === "string") cleanedAttrs.fill = cleanedAttrs.linkPreviousFill;
|
|
16285
|
+
if (typeof cleanedAttrs.linkPreviousTextDecoration === "string") cleanedAttrs.textDecoration = cleanedAttrs.linkPreviousTextDecoration;
|
|
16286
|
+
}
|
|
16287
|
+
return {
|
|
16288
|
+
key: attrs.id ?? "",
|
|
16289
|
+
type: attrs.nodeType,
|
|
16290
|
+
props: {
|
|
16291
|
+
...cleanedAttrs,
|
|
16292
|
+
isCloned: void 0,
|
|
16293
|
+
isCloneOrigin: void 0,
|
|
16294
|
+
id: attrs.id ?? "",
|
|
16295
|
+
nodeType: attrs.nodeType,
|
|
16296
|
+
children: []
|
|
16297
|
+
}
|
|
16298
|
+
};
|
|
16299
|
+
}
|
|
16300
|
+
setupLinkBehavior(text) {
|
|
16301
|
+
text.handleMouseover = () => {
|
|
16302
|
+
if (!text.getAttrs().link) return;
|
|
16303
|
+
text.fill(this.config.link.hoverColor);
|
|
16304
|
+
text.getLayer()?.batchDraw();
|
|
16305
|
+
};
|
|
16306
|
+
text.handleMouseout = () => {
|
|
16307
|
+
if (!text.getAttrs().link) return;
|
|
16308
|
+
text.fill(this.config.link.defaultColor);
|
|
16309
|
+
text.getLayer()?.batchDraw();
|
|
16310
|
+
};
|
|
16311
|
+
text.defineMousePointer = () => text.getAttrs().link ? "pointer" : "default";
|
|
16312
|
+
let linkClickTimeout;
|
|
16313
|
+
const clearLinkClickTimeout = () => {
|
|
16314
|
+
if (linkClickTimeout) {
|
|
16315
|
+
clearTimeout(linkClickTimeout);
|
|
16316
|
+
linkClickTimeout = void 0;
|
|
16317
|
+
}
|
|
16318
|
+
};
|
|
16319
|
+
const previousDblClick = text.dblClick?.bind(text) ?? (() => {});
|
|
16320
|
+
text.dblClick = () => {
|
|
16321
|
+
clearLinkClickTimeout();
|
|
16322
|
+
previousDblClick();
|
|
16323
|
+
};
|
|
16324
|
+
text.click = ({ wasSelected, ctrlOrMetaPressed }) => {
|
|
16325
|
+
if (!text.getAttrs().link) return;
|
|
16326
|
+
if (ctrlOrMetaPressed) {
|
|
16327
|
+
clearLinkClickTimeout();
|
|
16328
|
+
this.openTextLink(text);
|
|
16329
|
+
return;
|
|
16330
|
+
}
|
|
16331
|
+
if (wasSelected) {
|
|
16332
|
+
clearLinkClickTimeout();
|
|
16333
|
+
linkClickTimeout = setTimeout(() => {
|
|
16334
|
+
linkClickTimeout = void 0;
|
|
16335
|
+
this.openTextLink(text);
|
|
16336
|
+
}, Konva.dblClickWindow);
|
|
16337
|
+
}
|
|
16338
|
+
};
|
|
16339
|
+
}
|
|
16340
|
+
openTextLink(textNode) {
|
|
16341
|
+
if (this.instance.isServerSide()) return;
|
|
16342
|
+
const link = textNode.getAttrs().link;
|
|
16343
|
+
if (typeof link !== "string" || !isValidTextLink(link)) return;
|
|
16344
|
+
window.open(link, "_blank", "noopener,noreferrer");
|
|
16345
|
+
}
|
|
16346
|
+
getLink(nodeInstance) {
|
|
16347
|
+
const link = nodeInstance.getAttrs().link;
|
|
16348
|
+
return typeof link === "string" ? link : void 0;
|
|
16349
|
+
}
|
|
16350
|
+
setLink(nodeInstance, url) {
|
|
16351
|
+
if (!isValidTextLink(url)) return;
|
|
16352
|
+
const serialized = this.serialize(nodeInstance);
|
|
16353
|
+
serialized.props.linkPreviousFill = serialized.props.fill;
|
|
16354
|
+
serialized.props.linkPreviousTextDecoration = serialized.props.textDecoration;
|
|
16355
|
+
serialized.props.link = url;
|
|
16356
|
+
this.instance.updateNode(serialized);
|
|
16357
|
+
}
|
|
16358
|
+
removeLink(nodeInstance) {
|
|
16359
|
+
const serialized = this.serialize(nodeInstance);
|
|
16360
|
+
delete serialized.props.link;
|
|
16361
|
+
delete serialized.props.linkPreviousFill;
|
|
16362
|
+
delete serialized.props.linkPreviousTextDecoration;
|
|
16363
|
+
this.instance.updateNode(serialized);
|
|
16364
|
+
}
|
|
16365
|
+
resetSmartLayout(textNode) {
|
|
16366
|
+
textNode.setAttr("smartFixedWidth", void 0);
|
|
16367
|
+
textNode.width(Math.max(1, textNode.measureNaturalSize().width * 1.01));
|
|
16368
|
+
this.instance.updateNode(this.serialize(textNode));
|
|
16369
|
+
}
|
|
16370
|
+
static defaultState(nodeId, params) {
|
|
16371
|
+
return WeaveTextNode.defaultState(nodeId, params);
|
|
16372
|
+
}
|
|
16373
|
+
static addNodeState(defaultNodeState, props) {
|
|
16374
|
+
return mergeExceptArrays(WeaveTextNode.addNodeState(defaultNodeState, props), { props: { ...props.blocks && { blocks: props.blocks } } });
|
|
14291
16375
|
}
|
|
14292
16376
|
static updateNodeState(prevNodeState, nextProps) {
|
|
14293
|
-
|
|
14294
|
-
|
|
14295
|
-
|
|
14296
|
-
|
|
14297
|
-
...nextProps.height && { height: nextProps.height },
|
|
14298
|
-
FontFamily: nextProps.fontFamily,
|
|
14299
|
-
fontSize: nextProps.fontSize,
|
|
14300
|
-
fontStyle: nextProps.fontStyle,
|
|
14301
|
-
fontVariant: nextProps.fontVariant,
|
|
14302
|
-
textDecoration: nextProps.textDecoration,
|
|
14303
|
-
letterSpacing: nextProps.letterSpacing,
|
|
14304
|
-
lineHeight: nextProps.lineHeight,
|
|
14305
|
-
align: nextProps.align,
|
|
14306
|
-
verticalAlign: nextProps.verticalAlign,
|
|
14307
|
-
rotation: nextProps.rotation,
|
|
14308
|
-
fill: nextProps.fill,
|
|
14309
|
-
text: nextProps.text,
|
|
14310
|
-
layout: nextProps.layout,
|
|
14311
|
-
...nextProps.strokeEnabled && { strokeEnabled: nextProps.strokeEnabled },
|
|
14312
|
-
...nextProps.stroke && { stroke: nextProps.stroke },
|
|
14313
|
-
...nextProps.strokeWidth && { strokeWidth: nextProps.strokeWidth },
|
|
14314
|
-
...nextProps.fillAfterStrokeEnabled && { fillAfterStrokeEnabled: nextProps.fillAfterStrokeEnabled },
|
|
14315
|
-
...nextProps.link && { link: nextProps.link }
|
|
14316
|
-
} });
|
|
16377
|
+
const next = WeaveTextNode.updateNodeState(prevNodeState, nextProps);
|
|
16378
|
+
if (nextProps.blocks) next.props.blocks = nextProps.blocks;
|
|
16379
|
+
else delete next.props.blocks;
|
|
16380
|
+
return next;
|
|
14317
16381
|
}
|
|
14318
16382
|
static getSchema() {
|
|
14319
|
-
return
|
|
14320
|
-
type: z.literal(WEAVE_TEXT_NODE_TYPE).describe(`Type of the node, for a text node it will always be "${WEAVE_TEXT_NODE_TYPE}"`),
|
|
14321
|
-
props: z.object({
|
|
14322
|
-
nodeType: z.literal(WEAVE_TEXT_NODE_TYPE).describe(`Type of the node, for a text node it will always be "${WEAVE_TEXT_NODE_TYPE}"`),
|
|
14323
|
-
width: z.number().describe("Width of the text in pixels"),
|
|
14324
|
-
height: z.number().optional().describe("Height of the text in pixels. Optional if layout is auto-height or smart."),
|
|
14325
|
-
fontFamily: z.string().default("Arial").describe("Font family of the text, e.g. Arial, Helvetica, etc."),
|
|
14326
|
-
fontSize: z.number().default(16).describe("Font size of the text in pixels."),
|
|
14327
|
-
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\"."),
|
|
14328
|
-
fontVariant: z.enum(["normal", "small-caps"]).describe("Font variant of the text, can be \"normal\" or \"small-caps\"."),
|
|
14329
|
-
textDecoration: z.enum([
|
|
14330
|
-
"line-through",
|
|
14331
|
-
"underline",
|
|
14332
|
-
""
|
|
14333
|
-
]).default("").describe("Text decoration can be \"line-through\", \"underline\" or empty string for none."),
|
|
14334
|
-
letterSpacing: z.number().default(0).describe("Spacing between letters in pixels."),
|
|
14335
|
-
lineHeight: z.number().default(1).describe("Line height of the text, as a multiplier of font size."),
|
|
14336
|
-
align: z.enum([
|
|
14337
|
-
"left",
|
|
14338
|
-
"center",
|
|
14339
|
-
"right",
|
|
14340
|
-
"justify"
|
|
14341
|
-
]).default("left").describe("Text alignment, can be \"left\", \"center\", \"right\" or \"justify\"."),
|
|
14342
|
-
verticalAlign: z.enum([
|
|
14343
|
-
"top",
|
|
14344
|
-
"middle",
|
|
14345
|
-
"bottom"
|
|
14346
|
-
]).default("top").describe("Vertical alignment of the text, can be 'top', 'middle' or 'bottom'."),
|
|
14347
|
-
fill: z.string().default("#000000ff").describe("Fill color of the text in hex format with alpha channel (e.g. #RRGGBBAA)."),
|
|
14348
|
-
text: z.string().default("text").describe("The actual text content of the node."),
|
|
14349
|
-
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."),
|
|
14350
|
-
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."),
|
|
14351
|
-
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."),
|
|
14352
|
-
strokeEnabled: z.boolean().default(false).describe("Whether the text outline is enabled."),
|
|
14353
|
-
stroke: z.string().optional().default("#d6d6d6").describe("Color of the text outline in hex format with alpha channel (e.g. #RRGGBBAA)."),
|
|
14354
|
-
strokeWidth: z.number().optional().default(2).describe("Width of the text outline in pixels."),
|
|
14355
|
-
strokeScaleEnabled: z.boolean().default(true).describe("Whether the stroke width should scale when the node is scaled."),
|
|
14356
|
-
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."),
|
|
14357
|
-
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.")
|
|
14358
|
-
})
|
|
14359
|
-
});
|
|
16383
|
+
return WeaveTextNode.getSchema();
|
|
14360
16384
|
}
|
|
14361
16385
|
};
|
|
14362
16386
|
//#endregion
|
|
@@ -14447,24 +16471,64 @@ var WeaveImageCrop = class WeaveImageCrop {
|
|
|
14447
16471
|
this.onClose = () => {};
|
|
14448
16472
|
this.handleHide = this.hide.bind(this);
|
|
14449
16473
|
}
|
|
14450
|
-
|
|
14451
|
-
|
|
16474
|
+
/**
|
|
16475
|
+
* Mirrors the start of a segment inside a container along one axis:
|
|
16476
|
+
* the segment [start, start + length] inside [0, size] becomes
|
|
16477
|
+
* [size - start - length, size - start]. Used to convert between visual
|
|
16478
|
+
* space (what the user sees on a flipped image) and source space (where
|
|
16479
|
+
* the crop information is stored). It is its own inverse.
|
|
16480
|
+
*/
|
|
16481
|
+
static mirrorOffset(start, length, size) {
|
|
16482
|
+
return size - start - length;
|
|
16483
|
+
}
|
|
16484
|
+
/**
|
|
16485
|
+
* Returns the offset (in uncropped display units) of the visible crop
|
|
16486
|
+
* window inside the visually rendered uncropped image, along with the real
|
|
16487
|
+
* scale. When the image is flipped the stored crop (source space) is
|
|
16488
|
+
* mirrored to match what the user sees.
|
|
16489
|
+
*/
|
|
16490
|
+
getVisualCropOffset(imageAttrs) {
|
|
14452
16491
|
const actualScale = imageAttrs.uncroppedImage.width / imageAttrs.imageInfo.width;
|
|
14453
16492
|
const realScale = actualScale / (imageAttrs.cropInfo ? imageAttrs.cropInfo.scaleX : actualScale);
|
|
16493
|
+
let x = 0;
|
|
16494
|
+
let y = 0;
|
|
16495
|
+
if (imageAttrs.cropInfo) {
|
|
16496
|
+
x = imageAttrs.cropInfo.x * realScale;
|
|
16497
|
+
y = imageAttrs.cropInfo.y * realScale;
|
|
16498
|
+
if (imageAttrs.flipX === true) x = WeaveImageCrop.mirrorOffset(x, imageAttrs.cropInfo.width * realScale, imageAttrs.uncroppedImage.width);
|
|
16499
|
+
if (imageAttrs.flipY === true) y = WeaveImageCrop.mirrorOffset(y, imageAttrs.cropInfo.height * realScale, imageAttrs.uncroppedImage.height);
|
|
16500
|
+
}
|
|
16501
|
+
return {
|
|
16502
|
+
x,
|
|
16503
|
+
y,
|
|
16504
|
+
realScale
|
|
16505
|
+
};
|
|
16506
|
+
}
|
|
16507
|
+
setupCropImage(imageAttrs, visible) {
|
|
16508
|
+
this.cropGroup.destroyChildren();
|
|
16509
|
+
const { x, y, realScale } = this.getVisualCropOffset(imageAttrs);
|
|
16510
|
+
const flipX = imageAttrs.flipX === true;
|
|
16511
|
+
const flipY = imageAttrs.flipY === true;
|
|
14454
16512
|
this.cropImage = new Konva.Image({
|
|
14455
|
-
x:
|
|
14456
|
-
y:
|
|
16513
|
+
x: -x,
|
|
16514
|
+
y: -y,
|
|
14457
16515
|
width: imageAttrs.uncroppedImage.width,
|
|
14458
16516
|
height: imageAttrs.uncroppedImage.height,
|
|
14459
|
-
scaleX: 1,
|
|
14460
|
-
scaleY: 1,
|
|
16517
|
+
scaleX: flipX ? -1 : 1,
|
|
16518
|
+
scaleY: flipY ? -1 : 1,
|
|
16519
|
+
offsetX: flipX ? imageAttrs.uncroppedImage.width : 0,
|
|
16520
|
+
offsetY: flipY ? imageAttrs.uncroppedImage.height : 0,
|
|
14461
16521
|
image: this.internalImage.image(),
|
|
14462
16522
|
crop: void 0,
|
|
14463
16523
|
visible,
|
|
14464
16524
|
listening: false,
|
|
14465
16525
|
draggable: false
|
|
14466
16526
|
});
|
|
14467
|
-
return {
|
|
16527
|
+
return {
|
|
16528
|
+
realScale,
|
|
16529
|
+
offsetX: x,
|
|
16530
|
+
offsetY: y
|
|
16531
|
+
};
|
|
14468
16532
|
}
|
|
14469
16533
|
show(onClose, options) {
|
|
14470
16534
|
this.onClose = onClose;
|
|
@@ -14475,9 +16539,9 @@ var WeaveImageCrop = class WeaveImageCrop {
|
|
|
14475
16539
|
this.image.listening(false);
|
|
14476
16540
|
const imageAttrs = this.image.getAttrs();
|
|
14477
16541
|
this.internalImage.hide();
|
|
14478
|
-
const { realScale } = this.setupCropImage(imageAttrs, true);
|
|
14479
|
-
this.imageOffsetX =
|
|
14480
|
-
this.imageOffsetY =
|
|
16542
|
+
const { realScale, offsetX, offsetY } = this.setupCropImage(imageAttrs, true);
|
|
16543
|
+
this.imageOffsetX = offsetX;
|
|
16544
|
+
this.imageOffsetY = offsetY;
|
|
14481
16545
|
const cropModeConfiguration = this.node.getConfiguration().cropMode;
|
|
14482
16546
|
this.cropRect = new Konva.Rect({
|
|
14483
16547
|
x: 0,
|
|
@@ -14525,7 +16589,10 @@ var WeaveImageCrop = class WeaveImageCrop {
|
|
|
14525
16589
|
const center = this.cropImage.getAbsolutePosition();
|
|
14526
16590
|
const width = this.cropImage.width() * stage.scaleX();
|
|
14527
16591
|
const height = this.cropImage.height() * stage.scaleY();
|
|
14528
|
-
const offset =
|
|
16592
|
+
const offset = {
|
|
16593
|
+
x: this.cropImage.scaleX() < 0 ? 0 : this.cropImage.offsetX(),
|
|
16594
|
+
y: this.cropImage.scaleY() < 0 ? 0 : this.cropImage.offsetY()
|
|
16595
|
+
};
|
|
14529
16596
|
const corners = [
|
|
14530
16597
|
{
|
|
14531
16598
|
x: -offset.x,
|
|
@@ -14869,11 +16936,15 @@ var WeaveImageCrop = class WeaveImageCrop {
|
|
|
14869
16936
|
if (!intersectionRect) return;
|
|
14870
16937
|
const imageAttrs = this.image.getAttrs();
|
|
14871
16938
|
const actualScale = imageAttrs.uncroppedImage.width / imageAttrs.imageInfo.width;
|
|
16939
|
+
let sourceX = clipRectGroup.x + this.imageOffsetX;
|
|
16940
|
+
let sourceY = clipRectGroup.y + this.imageOffsetY;
|
|
16941
|
+
if (imageAttrs.flipX === true) sourceX = WeaveImageCrop.mirrorOffset(sourceX, clipRectGroup.width, imageAttrs.uncroppedImage.width);
|
|
16942
|
+
if (imageAttrs.flipY === true) sourceY = WeaveImageCrop.mirrorOffset(sourceY, clipRectGroup.height, imageAttrs.uncroppedImage.height);
|
|
14872
16943
|
const realClipRect = {
|
|
14873
16944
|
scaleX: actualScale,
|
|
14874
16945
|
scaleY: actualScale,
|
|
14875
|
-
x: WeaveImageCrop.roundTo6Decimals(
|
|
14876
|
-
y: WeaveImageCrop.roundTo6Decimals(
|
|
16946
|
+
x: WeaveImageCrop.roundTo6Decimals(sourceX),
|
|
16947
|
+
y: WeaveImageCrop.roundTo6Decimals(sourceY),
|
|
14877
16948
|
width: WeaveImageCrop.roundTo6Decimals(clipRectGroup.width),
|
|
14878
16949
|
height: WeaveImageCrop.roundTo6Decimals(clipRectGroup.height)
|
|
14879
16950
|
};
|
|
@@ -14915,16 +16986,18 @@ var WeaveImageCrop = class WeaveImageCrop {
|
|
|
14915
16986
|
if (!intersectionRect || !intersectionRectAbs) return;
|
|
14916
16987
|
const imageAttrs = this.image.getAttrs();
|
|
14917
16988
|
const actualScale = imageAttrs.uncroppedImage.width / imageAttrs.imageInfo.width;
|
|
14918
|
-
const
|
|
14919
|
-
const imageOffsetX = imageAttrs.cropInfo ? imageAttrs.cropInfo.x * realScale : 0;
|
|
14920
|
-
const imageOffsetY = imageAttrs.cropInfo ? imageAttrs.cropInfo.y * realScale : 0;
|
|
16989
|
+
const { x: imageOffsetX, y: imageOffsetY } = this.getVisualCropOffset(imageAttrs);
|
|
14921
16990
|
const diffX = intersectionRect.x - imageRect.x;
|
|
14922
16991
|
const diffY = intersectionRect.y - imageRect.y;
|
|
16992
|
+
let sourceX = diffX + imageOffsetX;
|
|
16993
|
+
let sourceY = diffY + imageOffsetY;
|
|
16994
|
+
if (imageAttrs.flipX === true) sourceX = WeaveImageCrop.mirrorOffset(sourceX, intersectionRect.width, imageAttrs.uncroppedImage.width);
|
|
16995
|
+
if (imageAttrs.flipY === true) sourceY = WeaveImageCrop.mirrorOffset(sourceY, intersectionRect.height, imageAttrs.uncroppedImage.height);
|
|
14923
16996
|
const realClipRect = {
|
|
14924
16997
|
scaleX: actualScale,
|
|
14925
16998
|
scaleY: actualScale,
|
|
14926
|
-
x: WeaveImageCrop.roundTo6Decimals(
|
|
14927
|
-
y: WeaveImageCrop.roundTo6Decimals(
|
|
16999
|
+
x: WeaveImageCrop.roundTo6Decimals(sourceX),
|
|
17000
|
+
y: WeaveImageCrop.roundTo6Decimals(sourceY),
|
|
14928
17001
|
width: WeaveImageCrop.roundTo6Decimals(intersectionRect.width),
|
|
14929
17002
|
height: WeaveImageCrop.roundTo6Decimals(intersectionRect.height)
|
|
14930
17003
|
};
|
|
@@ -15230,6 +17303,27 @@ var WeaveImageNode = class extends WeaveNode {
|
|
|
15230
17303
|
if (!internalImage || !cropGroup) return;
|
|
15231
17304
|
new WeaveImageCrop(this.instance, this, imageNode, internalImage, cropGroup).unCrop();
|
|
15232
17305
|
};
|
|
17306
|
+
flipImage = (imageNode, options) => {
|
|
17307
|
+
if (imageNode.getAttrs().cropping) return;
|
|
17308
|
+
const prop = options.axis === "horizontal" ? "flipX" : "flipY";
|
|
17309
|
+
this.instance.stateTransactional(() => {
|
|
17310
|
+
imageNode.setAttrs({ [prop]: !(imageNode.getAttrs()[prop] ?? false) });
|
|
17311
|
+
this.applyFlip(imageNode);
|
|
17312
|
+
this.cacheNode(imageNode);
|
|
17313
|
+
this.instance.updateNode(this.serialize(imageNode));
|
|
17314
|
+
});
|
|
17315
|
+
};
|
|
17316
|
+
applyFlip(image) {
|
|
17317
|
+
const attrs = image.getAttrs();
|
|
17318
|
+
const internalImage = image.findOne(`#${attrs.id}-image`);
|
|
17319
|
+
if (!internalImage) return;
|
|
17320
|
+
const flipX = attrs.flipX === true;
|
|
17321
|
+
const flipY = attrs.flipY === true;
|
|
17322
|
+
internalImage.scaleX(flipX ? -1 : 1);
|
|
17323
|
+
internalImage.scaleY(flipY ? -1 : 1);
|
|
17324
|
+
internalImage.offsetX(flipX ? internalImage.width() : 0);
|
|
17325
|
+
internalImage.offsetY(flipY ? internalImage.height() : 0);
|
|
17326
|
+
}
|
|
15233
17327
|
loadAsyncElement(nodeId) {
|
|
15234
17328
|
this.instance.loadAsyncElement(nodeId, WEAVE_IMAGE_NODE_TYPE);
|
|
15235
17329
|
}
|
|
@@ -15277,6 +17371,12 @@ var WeaveImageNode = class extends WeaveNode {
|
|
|
15277
17371
|
new WeaveImageCrop(this.instance, this, image, internalImage, cropGroup).unCrop();
|
|
15278
17372
|
};
|
|
15279
17373
|
}
|
|
17374
|
+
image.flipHorizontal = () => {
|
|
17375
|
+
this.flipImage(image, { axis: "horizontal" });
|
|
17376
|
+
};
|
|
17377
|
+
image.flipVertical = () => {
|
|
17378
|
+
this.flipImage(image, { axis: "vertical" });
|
|
17379
|
+
};
|
|
15280
17380
|
const defaultTransformerProperties = this.defaultGetTransformerProperties(this.config.transform);
|
|
15281
17381
|
image.getTransformerProperties = function() {
|
|
15282
17382
|
return defaultTransformerProperties;
|
|
@@ -15892,6 +17992,10 @@ var WeaveImageNode = class extends WeaveNode {
|
|
|
15892
17992
|
if (!this.imageState[imageAttrs.id ?? ""]?.loaded) return;
|
|
15893
17993
|
}
|
|
15894
17994
|
updateImageCrop(image) {
|
|
17995
|
+
this.updateImageCropGeometry(image);
|
|
17996
|
+
this.applyFlip(image);
|
|
17997
|
+
}
|
|
17998
|
+
updateImageCropGeometry(image) {
|
|
15895
17999
|
const imageAttrs = image.getAttrs();
|
|
15896
18000
|
const internalImage = image?.findOne(`#${imageAttrs.id}-image`);
|
|
15897
18001
|
if (!this.imageState[imageAttrs.id ?? ""]?.loaded) return;
|
|
@@ -15969,8 +18073,12 @@ var WeaveImageNode = class extends WeaveNode {
|
|
|
15969
18073
|
});
|
|
15970
18074
|
}
|
|
15971
18075
|
if (internalImage) {
|
|
15972
|
-
internalImage.
|
|
15973
|
-
|
|
18076
|
+
internalImage.offset({
|
|
18077
|
+
x: 0,
|
|
18078
|
+
y: 0
|
|
18079
|
+
});
|
|
18080
|
+
internalImage.width(Math.max(5, internalImage.width() * Math.abs(internalImage.scaleX())));
|
|
18081
|
+
internalImage.height(Math.max(5, internalImage.height() * Math.abs(internalImage.scaleY())));
|
|
15974
18082
|
internalImage.scale({
|
|
15975
18083
|
x: 1,
|
|
15976
18084
|
y: 1
|
|
@@ -16025,7 +18133,7 @@ var WeaveImageNode = class extends WeaveNode {
|
|
|
16025
18133
|
clearTimeout(this.imageTryoutIds[nodeId]);
|
|
16026
18134
|
delete this.imageTryoutIds[nodeId];
|
|
16027
18135
|
}
|
|
16028
|
-
|
|
18136
|
+
super.onDestroy(nodeInstance);
|
|
16029
18137
|
}
|
|
16030
18138
|
loadImageTryout(imageId) {
|
|
16031
18139
|
this.imageTryoutIds[imageId] = setTimeout(() => {
|
|
@@ -16112,7 +18220,9 @@ var WeaveImageNode = class extends WeaveNode {
|
|
|
16112
18220
|
width: props.uncroppedImage.width,
|
|
16113
18221
|
height: props.uncroppedImage.height
|
|
16114
18222
|
},
|
|
16115
|
-
cropping: props.cropping
|
|
18223
|
+
cropping: props.cropping,
|
|
18224
|
+
...props.flipX !== void 0 && { flipX: props.flipX },
|
|
18225
|
+
...props.flipY !== void 0 && { flipY: props.flipY }
|
|
16116
18226
|
} });
|
|
16117
18227
|
}
|
|
16118
18228
|
static updateNodeState(prevNodeState, nextProps) {
|
|
@@ -16135,7 +18245,9 @@ var WeaveImageNode = class extends WeaveNode {
|
|
|
16135
18245
|
width: nextProps.uncroppedImage?.width,
|
|
16136
18246
|
height: nextProps.uncroppedImage?.height
|
|
16137
18247
|
} },
|
|
16138
|
-
cropping: nextProps.cropping
|
|
18248
|
+
cropping: nextProps.cropping,
|
|
18249
|
+
...nextProps.flipX !== void 0 && { flipX: nextProps.flipX },
|
|
18250
|
+
...nextProps.flipY !== void 0 && { flipY: nextProps.flipY }
|
|
16139
18251
|
} });
|
|
16140
18252
|
}
|
|
16141
18253
|
static getSchema() {
|
|
@@ -16159,7 +18271,9 @@ var WeaveImageNode = class extends WeaveNode {
|
|
|
16159
18271
|
width: z.number().describe("The width of the image before cropping, used for cropping calculations"),
|
|
16160
18272
|
height: z.number().describe("The height of the image before cropping, used for cropping calculations")
|
|
16161
18273
|
}),
|
|
16162
|
-
cropping: z.boolean().default(false).describe("Whether the image is currently being cropped")
|
|
18274
|
+
cropping: z.boolean().default(false).describe("Whether the image is currently being cropped"),
|
|
18275
|
+
flipX: z.boolean().optional().describe("Whether the image is flipped horizontally"),
|
|
18276
|
+
flipY: z.boolean().optional().describe("Whether the image is flipped vertically")
|
|
16163
18277
|
})
|
|
16164
18278
|
});
|
|
16165
18279
|
}
|
|
@@ -19588,7 +21702,7 @@ var WeaveCommentNode = class extends WeaveNode {
|
|
|
19588
21702
|
this.normalizeNodeSize(commentNode);
|
|
19589
21703
|
}
|
|
19590
21704
|
onDestroy(nodeInstance) {
|
|
19591
|
-
|
|
21705
|
+
super.onDestroy(nodeInstance);
|
|
19592
21706
|
}
|
|
19593
21707
|
expandNode(commentNode) {
|
|
19594
21708
|
const widthExpanded = this.config.style.expanded.width;
|
|
@@ -23242,6 +25356,70 @@ const RECTANGLE_TOOL_STATE = {
|
|
|
23242
25356
|
["ADDED"]: "added"
|
|
23243
25357
|
};
|
|
23244
25358
|
//#endregion
|
|
25359
|
+
//#region src/actions/shared/shape-geometry.ts
|
|
25360
|
+
/** Returns the normalized bounds described by a drag, including its modifiers. */
|
|
25361
|
+
function getShapeDragBounds(anchor, pointer, options = {}) {
|
|
25362
|
+
const { centered = false, constrained = false, aspectRatio = 1 } = options;
|
|
25363
|
+
const deltaX = Math.abs(pointer.x - anchor.x);
|
|
25364
|
+
const deltaY = Math.abs(pointer.y - anchor.y);
|
|
25365
|
+
let width = deltaX;
|
|
25366
|
+
let height = deltaY;
|
|
25367
|
+
if (constrained && aspectRatio > 0) {
|
|
25368
|
+
if (width / (height || Number.EPSILON) > aspectRatio) height = width / aspectRatio;
|
|
25369
|
+
else width = height * aspectRatio;
|
|
25370
|
+
}
|
|
25371
|
+
if (constrained && aspectRatio > 0 && width > 0 && height > 0) {
|
|
25372
|
+
const minimumSizeScale = Math.max(1, 1 / width, 1 / height);
|
|
25373
|
+
width *= minimumSizeScale;
|
|
25374
|
+
height *= minimumSizeScale;
|
|
25375
|
+
} else {
|
|
25376
|
+
width = Math.max(1, width);
|
|
25377
|
+
height = Math.max(1, height);
|
|
25378
|
+
}
|
|
25379
|
+
if (centered) {
|
|
25380
|
+
width *= 2;
|
|
25381
|
+
height *= 2;
|
|
25382
|
+
return {
|
|
25383
|
+
x: anchor.x - width / 2,
|
|
25384
|
+
y: anchor.y - height / 2,
|
|
25385
|
+
width,
|
|
25386
|
+
height
|
|
25387
|
+
};
|
|
25388
|
+
}
|
|
25389
|
+
const signedWidth = Math.sign(pointer.x - anchor.x || 1) * width;
|
|
25390
|
+
const signedHeight = Math.sign(pointer.y - anchor.y || 1) * height;
|
|
25391
|
+
return {
|
|
25392
|
+
x: Math.min(anchor.x, anchor.x + signedWidth),
|
|
25393
|
+
y: Math.min(anchor.y, anchor.y + signedHeight),
|
|
25394
|
+
width,
|
|
25395
|
+
height
|
|
25396
|
+
};
|
|
25397
|
+
}
|
|
25398
|
+
//#endregion
|
|
25399
|
+
//#region src/actions/shared/shape-keyboard.ts
|
|
25400
|
+
/** Binds the common keyboard behavior for tools that create shapes by dragging. */
|
|
25401
|
+
function bindShapeDragKeyboardHandlers(signal, isActionActive, isDragInProgress, canUpdateModifiers, onCancel, onModifiersChange) {
|
|
25402
|
+
const handleModifierKey = (event) => {
|
|
25403
|
+
if (event.key !== "Shift" && event.key !== "Alt") return;
|
|
25404
|
+
const actionActive = isActionActive();
|
|
25405
|
+
const dragInProgress = isDragInProgress();
|
|
25406
|
+
if (event.type === "keydown" && event.key === "Alt" && actionActive && dragInProgress) event.preventDefault();
|
|
25407
|
+
if (!actionActive || !canUpdateModifiers()) return;
|
|
25408
|
+
onModifiersChange({
|
|
25409
|
+
shiftKey: event.shiftKey,
|
|
25410
|
+
altKey: event.altKey
|
|
25411
|
+
});
|
|
25412
|
+
};
|
|
25413
|
+
window.addEventListener("keydown", (event) => {
|
|
25414
|
+
if (isActionActive() && (event.code === "Enter" || event.code === "Escape")) {
|
|
25415
|
+
onCancel();
|
|
25416
|
+
return;
|
|
25417
|
+
}
|
|
25418
|
+
handleModifierKey(event);
|
|
25419
|
+
}, { signal });
|
|
25420
|
+
window.addEventListener("keyup", handleModifierKey, { signal });
|
|
25421
|
+
}
|
|
25422
|
+
//#endregion
|
|
23245
25423
|
//#region src/actions/rectangle-tool/rectangle-tool.ts
|
|
23246
25424
|
var WeaveRectangleToolAction = class extends WeaveAction {
|
|
23247
25425
|
initialized = false;
|
|
@@ -23287,16 +25465,7 @@ var WeaveRectangleToolAction = class extends WeaveAction {
|
|
|
23287
25465
|
}
|
|
23288
25466
|
setupEvents() {
|
|
23289
25467
|
const stage = this.instance.getStage();
|
|
23290
|
-
|
|
23291
|
-
if (e.code === "Enter" && this.instance.getActiveAction() === "rectangleTool") {
|
|
23292
|
-
this.cancelAction();
|
|
23293
|
-
return;
|
|
23294
|
-
}
|
|
23295
|
-
if (e.code === "Escape" && this.instance.getActiveAction() === "rectangleTool") {
|
|
23296
|
-
this.cancelAction();
|
|
23297
|
-
return;
|
|
23298
|
-
}
|
|
23299
|
-
}, { signal: this.instance.getEventsController().signal });
|
|
25468
|
+
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));
|
|
23300
25469
|
stage.on("pointermove", () => {
|
|
23301
25470
|
if (this.state === RECTANGLE_TOOL_STATE.IDLE) return;
|
|
23302
25471
|
this.setCursor();
|
|
@@ -23322,12 +25491,12 @@ var WeaveRectangleToolAction = class extends WeaveAction {
|
|
|
23322
25491
|
this.state = RECTANGLE_TOOL_STATE.ADDING;
|
|
23323
25492
|
return;
|
|
23324
25493
|
}
|
|
23325
|
-
if (this.state === RECTANGLE_TOOL_STATE.DEFINING_SIZE) this.handleMovement();
|
|
25494
|
+
if (this.state === RECTANGLE_TOOL_STATE.DEFINING_SIZE) this.handleMovement(e.evt);
|
|
23326
25495
|
});
|
|
23327
25496
|
stage.on("pointerup", (e) => {
|
|
23328
25497
|
this.pointers.delete(e.evt.pointerId);
|
|
23329
25498
|
if (this.isTap(e)) this.moved = false;
|
|
23330
|
-
if (this.state === RECTANGLE_TOOL_STATE.DEFINING_SIZE) this.handleSettingSize();
|
|
25499
|
+
if (this.state === RECTANGLE_TOOL_STATE.DEFINING_SIZE) this.handleSettingSize(e.evt);
|
|
23331
25500
|
});
|
|
23332
25501
|
this.initialized = true;
|
|
23333
25502
|
}
|
|
@@ -23361,7 +25530,7 @@ var WeaveRectangleToolAction = class extends WeaveAction {
|
|
|
23361
25530
|
}
|
|
23362
25531
|
this.setState(RECTANGLE_TOOL_STATE.DEFINING_SIZE);
|
|
23363
25532
|
}
|
|
23364
|
-
handleSettingSize() {
|
|
25533
|
+
handleSettingSize(modifiers = {}) {
|
|
23365
25534
|
if (this.rectId && this.tempRectNode && this.clickPoint && this.container) {
|
|
23366
25535
|
const { mousePoint } = this.instance.getMousePointerRelativeToContainer(this.container);
|
|
23367
25536
|
const rectPos = {
|
|
@@ -23371,10 +25540,14 @@ var WeaveRectangleToolAction = class extends WeaveAction {
|
|
|
23371
25540
|
let rectWidth = this.props.width;
|
|
23372
25541
|
let rectHeight = this.props.height;
|
|
23373
25542
|
if (this.moved) {
|
|
23374
|
-
|
|
23375
|
-
|
|
23376
|
-
|
|
23377
|
-
|
|
25543
|
+
const bounds = getShapeDragBounds(this.clickPoint, mousePoint, {
|
|
25544
|
+
centered: modifiers.altKey,
|
|
25545
|
+
constrained: modifiers.shiftKey
|
|
25546
|
+
});
|
|
25547
|
+
rectPos.x = bounds.x;
|
|
25548
|
+
rectPos.y = bounds.y;
|
|
25549
|
+
rectWidth = bounds.width;
|
|
25550
|
+
rectHeight = bounds.height;
|
|
23378
25551
|
}
|
|
23379
25552
|
this.tempRectNode.setAttrs({
|
|
23380
25553
|
...this.props,
|
|
@@ -23406,16 +25579,20 @@ var WeaveRectangleToolAction = class extends WeaveAction {
|
|
|
23406
25579
|
}
|
|
23407
25580
|
this.cancelAction();
|
|
23408
25581
|
}
|
|
23409
|
-
handleMovement() {
|
|
25582
|
+
handleMovement(modifiers = {}) {
|
|
23410
25583
|
if (this.state !== RECTANGLE_TOOL_STATE.DEFINING_SIZE) return;
|
|
23411
25584
|
if (this.rectId && this.tempRectNode && this.measureContainer && this.clickPoint) {
|
|
23412
25585
|
this.moved = true;
|
|
23413
25586
|
const { mousePoint } = this.instance.getMousePointerRelativeToContainer(this.measureContainer);
|
|
23414
|
-
const
|
|
23415
|
-
|
|
25587
|
+
const bounds = getShapeDragBounds(this.clickPoint, mousePoint, {
|
|
25588
|
+
centered: modifiers.altKey,
|
|
25589
|
+
constrained: modifiers.shiftKey
|
|
25590
|
+
});
|
|
23416
25591
|
this.tempRectNode.setAttrs({
|
|
23417
|
-
|
|
23418
|
-
|
|
25592
|
+
x: bounds.x,
|
|
25593
|
+
y: bounds.y,
|
|
25594
|
+
width: bounds.width,
|
|
25595
|
+
height: bounds.height
|
|
23419
25596
|
});
|
|
23420
25597
|
}
|
|
23421
25598
|
}
|
|
@@ -23514,16 +25691,7 @@ var WeaveEllipseToolAction = class extends WeaveAction {
|
|
|
23514
25691
|
}
|
|
23515
25692
|
setupEvents() {
|
|
23516
25693
|
const stage = this.instance.getStage();
|
|
23517
|
-
|
|
23518
|
-
if (e.code === "Enter" && this.instance.getActiveAction() === "ellipseTool") {
|
|
23519
|
-
this.cancelAction();
|
|
23520
|
-
return;
|
|
23521
|
-
}
|
|
23522
|
-
if (e.code === "Escape" && this.instance.getActiveAction() === "ellipseTool") {
|
|
23523
|
-
this.cancelAction();
|
|
23524
|
-
return;
|
|
23525
|
-
}
|
|
23526
|
-
}, { signal: this.instance.getEventsController().signal });
|
|
25694
|
+
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));
|
|
23527
25695
|
stage.on("pointerdown", (e) => {
|
|
23528
25696
|
this.setTapStart(e);
|
|
23529
25697
|
this.pointers.set(e.evt.pointerId, {
|
|
@@ -23550,7 +25718,7 @@ var WeaveEllipseToolAction = class extends WeaveAction {
|
|
|
23550
25718
|
}
|
|
23551
25719
|
if (this.state === ELLIPSE_TOOL_STATE.DEFINING_SIZE) {
|
|
23552
25720
|
this.moved = true;
|
|
23553
|
-
this.handleMovement();
|
|
25721
|
+
this.handleMovement(e.evt);
|
|
23554
25722
|
}
|
|
23555
25723
|
});
|
|
23556
25724
|
stage.on("pointerup", (e) => {
|
|
@@ -23558,7 +25726,7 @@ var WeaveEllipseToolAction = class extends WeaveAction {
|
|
|
23558
25726
|
if (this.isTap(e)) this.moved = false;
|
|
23559
25727
|
if (this.state === ELLIPSE_TOOL_STATE.DEFINING_SIZE) {
|
|
23560
25728
|
this.creating = false;
|
|
23561
|
-
this.handleSettingSize();
|
|
25729
|
+
this.handleSettingSize(e.evt);
|
|
23562
25730
|
}
|
|
23563
25731
|
});
|
|
23564
25732
|
this.initialized = true;
|
|
@@ -23582,8 +25750,8 @@ var WeaveEllipseToolAction = class extends WeaveAction {
|
|
|
23582
25750
|
if (nodeHandler) {
|
|
23583
25751
|
const node = nodeHandler.create(this.ellipseId, {
|
|
23584
25752
|
...this.props,
|
|
23585
|
-
x: this.clickPoint?.x ?? 0
|
|
23586
|
-
y: this.clickPoint?.y ?? 0
|
|
25753
|
+
x: this.clickPoint?.x ?? 0,
|
|
25754
|
+
y: this.clickPoint?.y ?? 0,
|
|
23587
25755
|
radiusX: 1,
|
|
23588
25756
|
radiusY: 1
|
|
23589
25757
|
});
|
|
@@ -23591,22 +25759,28 @@ var WeaveEllipseToolAction = class extends WeaveAction {
|
|
|
23591
25759
|
}
|
|
23592
25760
|
this.setState(ELLIPSE_TOOL_STATE.DEFINING_SIZE);
|
|
23593
25761
|
}
|
|
23594
|
-
handleSettingSize() {
|
|
25762
|
+
handleSettingSize(modifiers = {}) {
|
|
23595
25763
|
const ellipse = this.instance.getStage().findOne(`#${this.ellipseId}`);
|
|
23596
25764
|
if (this.ellipseId && this.clickPoint && this.container && ellipse) {
|
|
23597
25765
|
const { mousePoint } = this.instance.getMousePointerRelativeToContainer(this.container);
|
|
23598
25766
|
const nodeHandler = this.instance.getNodeHandler("ellipse");
|
|
23599
|
-
|
|
25767
|
+
let ellipsePos = {
|
|
23600
25768
|
x: this.clickPoint.x,
|
|
23601
25769
|
y: this.clickPoint.y
|
|
23602
25770
|
};
|
|
23603
|
-
let ellipseRadiusX = this.props.
|
|
25771
|
+
let ellipseRadiusX = this.props.radiusX;
|
|
23604
25772
|
let ellipseRadiusY = this.props.radiusY;
|
|
23605
25773
|
if (this.moved) {
|
|
23606
|
-
|
|
23607
|
-
|
|
23608
|
-
|
|
23609
|
-
|
|
25774
|
+
const bounds = getShapeDragBounds(this.clickPoint, mousePoint, {
|
|
25775
|
+
centered: modifiers.altKey,
|
|
25776
|
+
constrained: modifiers.shiftKey
|
|
25777
|
+
});
|
|
25778
|
+
ellipsePos = {
|
|
25779
|
+
x: bounds.x,
|
|
25780
|
+
y: bounds.y
|
|
25781
|
+
};
|
|
25782
|
+
ellipseRadiusX = bounds.width;
|
|
25783
|
+
ellipseRadiusY = bounds.height;
|
|
23610
25784
|
}
|
|
23611
25785
|
ellipse.setAttrs({
|
|
23612
25786
|
...this.props,
|
|
@@ -23620,27 +25794,23 @@ var WeaveEllipseToolAction = class extends WeaveAction {
|
|
|
23620
25794
|
}
|
|
23621
25795
|
this.cancelAction();
|
|
23622
25796
|
}
|
|
23623
|
-
handleMovement() {
|
|
25797
|
+
handleMovement(modifiers = {}) {
|
|
23624
25798
|
if (this.state !== ELLIPSE_TOOL_STATE.DEFINING_SIZE) return;
|
|
23625
25799
|
const ellipse = this.instance.getStage().findOne(`#${this.ellipseId}`);
|
|
23626
25800
|
if (this.ellipseId && this.container && this.clickPoint && ellipse) {
|
|
23627
25801
|
const { mousePoint } = this.instance.getMousePointerRelativeToContainer(this.container);
|
|
23628
|
-
const
|
|
23629
|
-
|
|
23630
|
-
|
|
23631
|
-
|
|
23632
|
-
y: this.clickPoint.y
|
|
23633
|
-
};
|
|
23634
|
-
if (this.moved) {
|
|
23635
|
-
ellipsePos.x = Math.min(this.clickPoint.x, mousePoint.x);
|
|
23636
|
-
ellipsePos.y = Math.min(this.clickPoint.y, mousePoint.y);
|
|
23637
|
-
}
|
|
25802
|
+
const bounds = getShapeDragBounds(this.clickPoint, mousePoint, {
|
|
25803
|
+
centered: modifiers.altKey,
|
|
25804
|
+
constrained: modifiers.shiftKey
|
|
25805
|
+
});
|
|
23638
25806
|
const nodeHandler = this.instance.getNodeHandler("ellipse");
|
|
23639
25807
|
if (nodeHandler) nodeHandler.onUpdate(ellipse, {
|
|
23640
25808
|
...this.props,
|
|
23641
25809
|
id: this.ellipseId,
|
|
23642
|
-
|
|
23643
|
-
|
|
25810
|
+
x: bounds.x,
|
|
25811
|
+
y: bounds.y,
|
|
25812
|
+
radiusX: bounds.width / 2,
|
|
25813
|
+
radiusY: bounds.height / 2
|
|
23644
25814
|
});
|
|
23645
25815
|
}
|
|
23646
25816
|
}
|
|
@@ -26611,16 +28781,7 @@ var WeaveRegularPolygonToolAction = class extends WeaveAction {
|
|
|
26611
28781
|
}
|
|
26612
28782
|
setupEvents() {
|
|
26613
28783
|
const stage = this.instance.getStage();
|
|
26614
|
-
|
|
26615
|
-
if (e.code === "Enter" && this.instance.getActiveAction() === "regularPolygonTool") {
|
|
26616
|
-
this.cancelAction();
|
|
26617
|
-
return;
|
|
26618
|
-
}
|
|
26619
|
-
if (e.code === "Escape" && this.instance.getActiveAction() === "regularPolygonTool") {
|
|
26620
|
-
this.cancelAction();
|
|
26621
|
-
return;
|
|
26622
|
-
}
|
|
26623
|
-
}, { signal: this.instance.getEventsController().signal });
|
|
28784
|
+
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));
|
|
26624
28785
|
stage.on("pointerdown", (e) => {
|
|
26625
28786
|
this.setTapStart(e);
|
|
26626
28787
|
this.pointers.set(e.evt.pointerId, {
|
|
@@ -26647,7 +28808,7 @@ var WeaveRegularPolygonToolAction = class extends WeaveAction {
|
|
|
26647
28808
|
}
|
|
26648
28809
|
if (this.state === REGULAR_POLYGON_TOOL_STATE.DEFINING_SIZE) {
|
|
26649
28810
|
this.moved = true;
|
|
26650
|
-
this.handleMovement();
|
|
28811
|
+
this.handleMovement(e.evt);
|
|
26651
28812
|
}
|
|
26652
28813
|
});
|
|
26653
28814
|
stage.on("pointerup", (e) => {
|
|
@@ -26655,7 +28816,7 @@ var WeaveRegularPolygonToolAction = class extends WeaveAction {
|
|
|
26655
28816
|
if (this.isTap(e)) this.moved = false;
|
|
26656
28817
|
if (this.state === REGULAR_POLYGON_TOOL_STATE.DEFINING_SIZE) {
|
|
26657
28818
|
this.creating = false;
|
|
26658
|
-
this.handleSettingSize();
|
|
28819
|
+
this.handleSettingSize(e.evt);
|
|
26659
28820
|
}
|
|
26660
28821
|
});
|
|
26661
28822
|
this.initialized = true;
|
|
@@ -26687,20 +28848,26 @@ var WeaveRegularPolygonToolAction = class extends WeaveAction {
|
|
|
26687
28848
|
}
|
|
26688
28849
|
this.setState(REGULAR_POLYGON_TOOL_STATE.DEFINING_SIZE);
|
|
26689
28850
|
}
|
|
26690
|
-
handleSettingSize() {
|
|
28851
|
+
handleSettingSize(modifiers = {}) {
|
|
26691
28852
|
const regularPolygon = this.instance.getStage().findOne(`#${this.regularPolygonId}`);
|
|
26692
28853
|
if (this.regularPolygonId && this.clickPoint && this.container && regularPolygon) {
|
|
26693
28854
|
const { mousePoint } = this.instance.getMousePointerRelativeToContainer(this.container);
|
|
26694
28855
|
const nodeHandler = this.instance.getNodeHandler("regular-polygon");
|
|
26695
|
-
|
|
28856
|
+
let starPos = {
|
|
26696
28857
|
x: this.clickPoint.x,
|
|
26697
28858
|
y: this.clickPoint.y
|
|
26698
28859
|
};
|
|
26699
28860
|
let newRadius = this.props.radius;
|
|
26700
28861
|
if (this.moved) {
|
|
26701
|
-
|
|
26702
|
-
|
|
26703
|
-
|
|
28862
|
+
const bounds = getShapeDragBounds(this.clickPoint, mousePoint, {
|
|
28863
|
+
centered: modifiers.altKey,
|
|
28864
|
+
constrained: true
|
|
28865
|
+
});
|
|
28866
|
+
starPos = {
|
|
28867
|
+
x: bounds.x,
|
|
28868
|
+
y: bounds.y
|
|
28869
|
+
};
|
|
28870
|
+
newRadius = bounds.width;
|
|
26704
28871
|
}
|
|
26705
28872
|
regularPolygon.setAttrs({
|
|
26706
28873
|
...this.props,
|
|
@@ -26713,25 +28880,22 @@ var WeaveRegularPolygonToolAction = class extends WeaveAction {
|
|
|
26713
28880
|
}
|
|
26714
28881
|
this.cancelAction();
|
|
26715
28882
|
}
|
|
26716
|
-
handleMovement() {
|
|
28883
|
+
handleMovement(modifiers = {}) {
|
|
26717
28884
|
if (this.state !== REGULAR_POLYGON_TOOL_STATE.DEFINING_SIZE) return;
|
|
26718
28885
|
const regularPolygon = this.instance.getStage().findOne(`#${this.regularPolygonId}`);
|
|
26719
28886
|
if (this.regularPolygonId && this.container && this.clickPoint && regularPolygon) {
|
|
26720
28887
|
const { mousePoint } = this.instance.getMousePointerRelativeToContainer(this.container);
|
|
26721
|
-
const
|
|
26722
|
-
|
|
26723
|
-
|
|
26724
|
-
|
|
26725
|
-
};
|
|
26726
|
-
if (this.moved) {
|
|
26727
|
-
starPos.x = Math.min(this.clickPoint.x, mousePoint.x);
|
|
26728
|
-
starPos.y = Math.min(this.clickPoint.y, mousePoint.y);
|
|
26729
|
-
}
|
|
28888
|
+
const bounds = getShapeDragBounds(this.clickPoint, mousePoint, {
|
|
28889
|
+
centered: modifiers.altKey,
|
|
28890
|
+
constrained: true
|
|
28891
|
+
});
|
|
26730
28892
|
const nodeHandler = this.instance.getNodeHandler("regular-polygon");
|
|
26731
28893
|
if (nodeHandler) nodeHandler.onUpdate(regularPolygon, {
|
|
26732
28894
|
...this.props,
|
|
26733
28895
|
id: this.regularPolygonId,
|
|
26734
|
-
|
|
28896
|
+
x: bounds.x,
|
|
28897
|
+
y: bounds.y,
|
|
28898
|
+
radius: bounds.width / 2
|
|
26735
28899
|
});
|
|
26736
28900
|
}
|
|
26737
28901
|
}
|
|
@@ -26780,14 +28944,22 @@ const POLYGON_TOOL_ACTION_NAME = "polygonTool";
|
|
|
26780
28944
|
const POLYGON_TOOL_STATE = {
|
|
26781
28945
|
["IDLE"]: "idle",
|
|
26782
28946
|
["ADDING"]: "adding",
|
|
28947
|
+
["DEFINING_SIZE"]: "defining-size",
|
|
26783
28948
|
["ADDED"]: "added"
|
|
26784
28949
|
};
|
|
26785
28950
|
//#endregion
|
|
26786
28951
|
//#region src/actions/polygon-tool/polygon-tool.ts
|
|
28952
|
+
const MIN_POLYGON_DRAG_DISTANCE = 3;
|
|
26787
28953
|
var WeavePolygonToolAction = class extends WeaveAction {
|
|
26788
28954
|
initialized = false;
|
|
26789
28955
|
state;
|
|
26790
28956
|
polygonId;
|
|
28957
|
+
pointers;
|
|
28958
|
+
clickPoint;
|
|
28959
|
+
container;
|
|
28960
|
+
moved;
|
|
28961
|
+
placementCommitted;
|
|
28962
|
+
polygonNode;
|
|
26791
28963
|
cancelAction;
|
|
26792
28964
|
preset;
|
|
26793
28965
|
onPropsChange = void 0;
|
|
@@ -26801,6 +28973,12 @@ var WeavePolygonToolAction = class extends WeaveAction {
|
|
|
26801
28973
|
this.initialized = false;
|
|
26802
28974
|
this.state = POLYGON_TOOL_STATE.IDLE;
|
|
26803
28975
|
this.polygonId = null;
|
|
28976
|
+
this.pointers = /* @__PURE__ */ new Map();
|
|
28977
|
+
this.clickPoint = null;
|
|
28978
|
+
this.container = void 0;
|
|
28979
|
+
this.moved = false;
|
|
28980
|
+
this.placementCommitted = false;
|
|
28981
|
+
this.polygonNode = null;
|
|
26804
28982
|
this.props = this.initProps();
|
|
26805
28983
|
}
|
|
26806
28984
|
getName() {
|
|
@@ -26825,18 +29003,41 @@ var WeavePolygonToolAction = class extends WeaveAction {
|
|
|
26825
29003
|
}
|
|
26826
29004
|
setupEvents() {
|
|
26827
29005
|
const stage = this.instance.getStage();
|
|
26828
|
-
|
|
26829
|
-
if ((e.code === "Enter" || e.code === "Escape") && this.instance.getActiveAction() === "polygonTool") this.cancelAction();
|
|
26830
|
-
}, { signal: this.instance.getEventsController().signal });
|
|
29006
|
+
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));
|
|
26831
29007
|
stage.on("pointermove", () => {
|
|
26832
29008
|
if (this.state === POLYGON_TOOL_STATE.IDLE) return;
|
|
26833
29009
|
this.setCursor();
|
|
26834
29010
|
});
|
|
26835
29011
|
stage.on("pointerdown", (e) => {
|
|
26836
29012
|
this.setTapStart(e);
|
|
29013
|
+
this.pointers.set(e.evt.pointerId, {
|
|
29014
|
+
x: e.evt.clientX,
|
|
29015
|
+
y: e.evt.clientY
|
|
29016
|
+
});
|
|
26837
29017
|
if (this.state !== POLYGON_TOOL_STATE.ADDING) return;
|
|
26838
29018
|
this.handleAdding();
|
|
26839
29019
|
});
|
|
29020
|
+
stage.on("pointermove", (e) => {
|
|
29021
|
+
if (this.state === POLYGON_TOOL_STATE.IDLE) return;
|
|
29022
|
+
this.setCursor();
|
|
29023
|
+
if (!this.isPressed(e) || !this.pointers.has(e.evt.pointerId)) return;
|
|
29024
|
+
if (this.state === POLYGON_TOOL_STATE.DEFINING_SIZE) {
|
|
29025
|
+
const pointerStart = this.pointers.get(e.evt.pointerId);
|
|
29026
|
+
if (!pointerStart || Math.hypot(e.evt.clientX - pointerStart.x, e.evt.clientY - pointerStart.y) < MIN_POLYGON_DRAG_DISTANCE) return;
|
|
29027
|
+
this.moved = true;
|
|
29028
|
+
this.handleMovement(e.evt);
|
|
29029
|
+
}
|
|
29030
|
+
});
|
|
29031
|
+
stage.on("pointerup", (e) => {
|
|
29032
|
+
this.pointers.delete(e.evt.pointerId);
|
|
29033
|
+
if (this.isTap(e)) this.moved = false;
|
|
29034
|
+
if (this.state === POLYGON_TOOL_STATE.DEFINING_SIZE) this.handleSettingSize(e.evt);
|
|
29035
|
+
});
|
|
29036
|
+
stage.on("pointercancel", (e) => {
|
|
29037
|
+
if (!this.pointers.has(e.evt.pointerId)) return;
|
|
29038
|
+
this.pointers.delete(e.evt.pointerId);
|
|
29039
|
+
if (this.state === POLYGON_TOOL_STATE.DEFINING_SIZE) this.cancelAction();
|
|
29040
|
+
});
|
|
26840
29041
|
this.initialized = true;
|
|
26841
29042
|
}
|
|
26842
29043
|
setState(state) {
|
|
@@ -26850,7 +29051,10 @@ var WeavePolygonToolAction = class extends WeaveAction {
|
|
|
26850
29051
|
}
|
|
26851
29052
|
handleAdding() {
|
|
26852
29053
|
const { mousePoint, container } = this.instance.getMousePointer();
|
|
29054
|
+
this.clickPoint = mousePoint;
|
|
29055
|
+
this.container = container;
|
|
26853
29056
|
this.polygonId = v4();
|
|
29057
|
+
this.polygonNode = null;
|
|
26854
29058
|
const presetDef = WEAVE_POLYGON_PRESETS[this.preset];
|
|
26855
29059
|
const scaleFactor = this.props.scaleFactor ?? 1;
|
|
26856
29060
|
const { points, innerRect, width, height } = instantiatePreset(presetDef, presetDef.defaultWidth * scaleFactor, presetDef.defaultHeight * scaleFactor);
|
|
@@ -26866,15 +29070,49 @@ var WeavePolygonToolAction = class extends WeaveAction {
|
|
|
26866
29070
|
points,
|
|
26867
29071
|
innerRect
|
|
26868
29072
|
});
|
|
29073
|
+
this.polygonNode = node;
|
|
26869
29074
|
this.instance.addNode(node, container?.getAttrs().id);
|
|
26870
29075
|
}
|
|
26871
|
-
this.
|
|
29076
|
+
this.setState(POLYGON_TOOL_STATE.DEFINING_SIZE);
|
|
29077
|
+
}
|
|
29078
|
+
handleMovement(modifiers = {}) {
|
|
29079
|
+
if (this.state !== POLYGON_TOOL_STATE.DEFINING_SIZE || !this.polygonId || !this.clickPoint || !this.container) return;
|
|
29080
|
+
const polygon = this.instance.getStage().findOne(`#${this.polygonId}`);
|
|
29081
|
+
const nodeHandler = this.instance.getNodeHandler(WEAVE_POLYGON_NODE_TYPE);
|
|
29082
|
+
const presetDef = WEAVE_POLYGON_PRESETS[this.preset];
|
|
29083
|
+
if (!polygon || !nodeHandler) return;
|
|
29084
|
+
const { mousePoint } = this.instance.getMousePointerRelativeToContainer(this.container);
|
|
29085
|
+
const defaultGeometry = instantiatePreset(presetDef, presetDef.defaultWidth, presetDef.defaultHeight);
|
|
29086
|
+
const bounds = getShapeDragBounds(this.clickPoint, mousePoint, {
|
|
29087
|
+
centered: modifiers.altKey,
|
|
29088
|
+
constrained: modifiers.shiftKey,
|
|
29089
|
+
aspectRatio: defaultGeometry.width / defaultGeometry.height
|
|
29090
|
+
});
|
|
29091
|
+
const geometry = instantiatePreset(presetDef, bounds.width * presetDef.defaultWidth / defaultGeometry.width, bounds.height * presetDef.defaultHeight / defaultGeometry.height);
|
|
29092
|
+
nodeHandler.onUpdate(polygon, {
|
|
29093
|
+
...this.props,
|
|
29094
|
+
id: this.polygonId,
|
|
29095
|
+
x: bounds.x,
|
|
29096
|
+
y: bounds.y,
|
|
29097
|
+
...geometry
|
|
29098
|
+
});
|
|
29099
|
+
}
|
|
29100
|
+
handleSettingSize(modifiers = {}) {
|
|
29101
|
+
if (this.moved) this.handleMovement(modifiers);
|
|
29102
|
+
const polygon = this.polygonId && this.instance.getStage().findOne(`#${this.polygonId}`);
|
|
29103
|
+
const nodeHandler = this.instance.getNodeHandler(WEAVE_POLYGON_NODE_TYPE);
|
|
29104
|
+
if (polygon && nodeHandler) {
|
|
29105
|
+
this.instance.updateNode(nodeHandler.serialize(polygon));
|
|
29106
|
+
this.placementCommitted = true;
|
|
29107
|
+
this.instance.emitEvent("onAddedPolygon");
|
|
29108
|
+
}
|
|
26872
29109
|
this.cancelAction();
|
|
26873
29110
|
}
|
|
26874
29111
|
trigger(cancelAction, params) {
|
|
26875
29112
|
if (!this.instance) throw new Error("Instance not defined");
|
|
26876
29113
|
if (!this.initialized) this.setupEvents();
|
|
26877
29114
|
this.preset = params?.presetId ?? "pentagon";
|
|
29115
|
+
this.placementCommitted = false;
|
|
26878
29116
|
const stage = this.instance.getStage();
|
|
26879
29117
|
stage.container().tabIndex = 1;
|
|
26880
29118
|
stage.container().focus();
|
|
@@ -26887,13 +29125,21 @@ var WeavePolygonToolAction = class extends WeaveAction {
|
|
|
26887
29125
|
cleanup() {
|
|
26888
29126
|
const stage = this.instance.getStage();
|
|
26889
29127
|
stage.container().style.cursor = "default";
|
|
29128
|
+
const isCancelledPlacement = this.state === POLYGON_TOOL_STATE.DEFINING_SIZE && this.polygonId !== null && !this.placementCommitted;
|
|
29129
|
+
if (isCancelledPlacement && this.polygonNode) this.instance.removeNode(this.polygonNode);
|
|
26890
29130
|
const selectionPlugin = this.instance.getPlugin("nodesSelection");
|
|
26891
29131
|
if (selectionPlugin) {
|
|
26892
|
-
const node = stage.findOne(`#${this.polygonId}`);
|
|
29132
|
+
const node = isCancelledPlacement ? void 0 : stage.findOne(`#${this.polygonId}`);
|
|
26893
29133
|
if (node) selectionPlugin.setSelectedNodes([node]);
|
|
26894
29134
|
this.instance.triggerAction(SELECTION_TOOL_ACTION_NAME);
|
|
26895
29135
|
}
|
|
26896
29136
|
this.polygonId = null;
|
|
29137
|
+
this.pointers.clear();
|
|
29138
|
+
this.clickPoint = null;
|
|
29139
|
+
this.container = void 0;
|
|
29140
|
+
this.moved = false;
|
|
29141
|
+
this.placementCommitted = false;
|
|
29142
|
+
this.polygonNode = null;
|
|
26897
29143
|
this.setState(POLYGON_TOOL_STATE.IDLE);
|
|
26898
29144
|
}
|
|
26899
29145
|
setCursor() {
|
|
@@ -33284,14 +35530,26 @@ var WeaveNodesSnappingPlugin = class extends WeavePlugin {
|
|
|
33284
35530
|
}
|
|
33285
35531
|
};
|
|
33286
35532
|
//#endregion
|
|
35533
|
+
//#region src/utils/native-backend.ts
|
|
35534
|
+
const MODULE_NOT_FOUND_CODES = ["ERR_MODULE_NOT_FOUND", "MODULE_NOT_FOUND"];
|
|
35535
|
+
const isMissingPackageError = (error, packageName) => error instanceof Error && MODULE_NOT_FOUND_CODES.includes(error.code ?? "") && error.message.includes(`'${packageName}'`);
|
|
35536
|
+
const loadNativeBackend = async (packageName, setupFunctionName, load) => {
|
|
35537
|
+
try {
|
|
35538
|
+
await load();
|
|
35539
|
+
} catch (error) {
|
|
35540
|
+
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 });
|
|
35541
|
+
throw error;
|
|
35542
|
+
}
|
|
35543
|
+
};
|
|
35544
|
+
//#endregion
|
|
33287
35545
|
//#region src/backend.ts
|
|
33288
35546
|
const setupSkiaBackend = async () => {
|
|
33289
35547
|
globalThis._weave_serverSideBackend = WEAVE_KONVA_BACKEND.SKIA;
|
|
33290
|
-
await import("konva/skia-backend");
|
|
35548
|
+
await loadNativeBackend("skia-canvas", "setupSkiaBackend", () => import("konva/skia-backend"));
|
|
33291
35549
|
};
|
|
33292
35550
|
const setupCanvasBackend = async () => {
|
|
33293
35551
|
globalThis._weave_serverSideBackend = WEAVE_KONVA_BACKEND.CANVAS;
|
|
33294
|
-
await import("konva/canvas-backend");
|
|
35552
|
+
await loadNativeBackend("canvas", "setupCanvasBackend", () => import("konva/canvas-backend"));
|
|
33295
35553
|
};
|
|
33296
35554
|
//#endregion
|
|
33297
|
-
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, getSelectedNodesMetadata, getShapeLabelSchemaFields, getStageClickPoint, getTargetAndSkipNodes, getTargetedNode, getTopmostShadowHost, getVisibleNodes, getVisibleNodesInViewport, hasFrames, hasImages, instantiatePreset, intersectArrays, isIOS, isInShadowDOM, isNodeInSelection, isNumber, isServer, labelId, loadImageSource, memoize, mergeExceptArrays, moveNodeToContainer, moveNodeToContainerNT, resetScale, setupCanvasBackend, setupSkiaBackend, sleep, spreadLabelProps };
|
|
35555
|
+
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, getSelectedNodesMetadata, getShapeLabelSchemaFields, getStageClickPoint, getTargetAndSkipNodes, getTargetedNode, getTopmostShadowHost, getVisibleNodes, getVisibleNodesInViewport, hasFrames, hasImages, instantiatePreset, intersectArrays, isIOS, isInShadowDOM, isNodeInSelection, isNumber, isServer, labelId, loadImageSource, memoize, mergeExceptArrays, moveNodeToContainer, moveNodeToContainerNT, resetScale, setupCanvasBackend, setupSkiaBackend, sleep, spreadLabelProps };
|