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