@inditextech/weave-sdk 5.6.0 → 5.7.0-SNAPSHOT.596.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 +2058 -2
- package/dist/sdk.node.js +2058 -2
- package/dist/sdk.node.stats.html +1 -1
- package/dist/sdk.stats.html +1 -1
- package/dist/types.d.ts +362 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2058 -2
- package/dist/types.js.map +1 -1
- package/dist/types.stats.html +1 -1
- package/package.json +2 -2
package/dist/sdk.js
CHANGED
|
@@ -9502,7 +9502,7 @@ var WeaveRegisterManager = class {
|
|
|
9502
9502
|
};
|
|
9503
9503
|
//#endregion
|
|
9504
9504
|
//#region package.json
|
|
9505
|
-
var version = "5.
|
|
9505
|
+
var version = "5.7.0-SNAPSHOT.596.1";
|
|
9506
9506
|
//#endregion
|
|
9507
9507
|
//#region src/managers/setup.ts
|
|
9508
9508
|
var WeaveSetupManager = class {
|
|
@@ -14396,6 +14396,2062 @@ var WeaveTextNode = class extends WeaveNode {
|
|
|
14396
14396
|
}
|
|
14397
14397
|
};
|
|
14398
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.at(-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) => {
|
|
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
|
+
function flattenBlock(block) {
|
|
14556
|
+
const graphemes = [];
|
|
14557
|
+
const runOf = [];
|
|
14558
|
+
for (let ri = 0; ri < block.runs.length; ri++) for (const g of splitGraphemes(block.runs[ri].t)) {
|
|
14559
|
+
graphemes.push(g);
|
|
14560
|
+
runOf.push(ri);
|
|
14561
|
+
}
|
|
14562
|
+
return {
|
|
14563
|
+
graphemes,
|
|
14564
|
+
runOf
|
|
14565
|
+
};
|
|
14566
|
+
}
|
|
14567
|
+
function trimTrailingSpace(graphemes, start, end) {
|
|
14568
|
+
let e = end;
|
|
14569
|
+
while (e > start && WS.test(graphemes[e - 1])) e--;
|
|
14570
|
+
return e;
|
|
14571
|
+
}
|
|
14572
|
+
function skipSpace(graphemes, from) {
|
|
14573
|
+
let i = from;
|
|
14574
|
+
while (i < graphemes.length && WS.test(graphemes[i])) i++;
|
|
14575
|
+
return i;
|
|
14576
|
+
}
|
|
14577
|
+
const isNum = (v) => v !== void 0 && !Number.isNaN(v);
|
|
14578
|
+
/** Konva's word wrap: break after the last space/dash that fits (0 = no break). */
|
|
14579
|
+
function wordWrapIndex(graphemes, start, matchLen, matchWidth, maxWidth) {
|
|
14580
|
+
const nextChar = graphemes[start + matchLen];
|
|
14581
|
+
if ((nextChar === " " || nextChar === "-") && matchWidth <= maxWidth) return matchLen;
|
|
14582
|
+
const seg = graphemes.slice(start, start + matchLen);
|
|
14583
|
+
return Math.max(seg.lastIndexOf(" "), seg.lastIndexOf("-")) + 1;
|
|
14584
|
+
}
|
|
14585
|
+
function applyAlign(lines, align, width, padding) {
|
|
14586
|
+
if (align === "left") return;
|
|
14587
|
+
for (const line of lines) {
|
|
14588
|
+
const free = width - line.width - padding * 2;
|
|
14589
|
+
line.offsetX = align === "center" ? free / 2 : free;
|
|
14590
|
+
}
|
|
14591
|
+
}
|
|
14592
|
+
var TextLayoutEngine = class {
|
|
14593
|
+
measure;
|
|
14594
|
+
cache = /* @__PURE__ */ new Map();
|
|
14595
|
+
paragraphs = [];
|
|
14596
|
+
/** Entries by signature, so paragraphs that shifted index are reused. */
|
|
14597
|
+
pool = /* @__PURE__ */ new Map();
|
|
14598
|
+
lastParagraphsRelaid = 0;
|
|
14599
|
+
constructor(measurer = canvasMeasurer) {
|
|
14600
|
+
this.measure = measurer;
|
|
14601
|
+
}
|
|
14602
|
+
/** Number of paragraphs recomputed by the last `layout()` call. */
|
|
14603
|
+
getLastRelaidCount() {
|
|
14604
|
+
return this.lastParagraphsRelaid;
|
|
14605
|
+
}
|
|
14606
|
+
clearCaches() {
|
|
14607
|
+
this.cache.clear();
|
|
14608
|
+
this.paragraphs = [];
|
|
14609
|
+
this.pool.clear();
|
|
14610
|
+
}
|
|
14611
|
+
paragraphSig(block, style, bounds) {
|
|
14612
|
+
const fixedWidth = bounds.width !== void 0 && !Number.isNaN(bounds.width);
|
|
14613
|
+
const maxWidth = (bounds.width ?? 0) - bounds.padding * 2;
|
|
14614
|
+
return `${fontString(style)}|${style.letterSpacing}|${style.wrap}|${fixedWidth ? maxWidth : "auto"}|${JSON.stringify(block.runs)}`;
|
|
14615
|
+
}
|
|
14616
|
+
/**
|
|
14617
|
+
* Pre-compute paragraph layouts for `blocks` within a time budget so a
|
|
14618
|
+
* following `layout()` is cheap. Call repeatedly until it returns true.
|
|
14619
|
+
* Lets callers time-slice very large content (e.g. bulk paste).
|
|
14620
|
+
*/
|
|
14621
|
+
warm(blocks, style, bounds, budgetMs) {
|
|
14622
|
+
const font = fontString(style);
|
|
14623
|
+
const fixedWidth = bounds.width !== void 0 && !Number.isNaN(bounds.width);
|
|
14624
|
+
const maxWidth = (bounds.width ?? 0) - bounds.padding * 2;
|
|
14625
|
+
const deadline = now() + budgetMs;
|
|
14626
|
+
for (const block of blocks) {
|
|
14627
|
+
const sig = this.paragraphSig(block, style, bounds);
|
|
14628
|
+
if (this.pool.has(sig)) continue;
|
|
14629
|
+
this.pool.set(sig, this.layoutParagraph(block, sig, font, style, fixedWidth, maxWidth));
|
|
14630
|
+
if (now() >= deadline) return false;
|
|
14631
|
+
}
|
|
14632
|
+
return true;
|
|
14633
|
+
}
|
|
14634
|
+
measureChunk(font, text, spacing) {
|
|
14635
|
+
if (text === "") return 0;
|
|
14636
|
+
if (text.length > MAX_CACHED_CHUNK) return this.measure(font, text) + text.length * spacing;
|
|
14637
|
+
const key = `${font}\u0000${text}`;
|
|
14638
|
+
let w = this.cache.get(key);
|
|
14639
|
+
if (w === void 0) {
|
|
14640
|
+
w = this.measure(font, text);
|
|
14641
|
+
if (this.cache.size >= MAX_CACHE) this.cache.clear();
|
|
14642
|
+
this.cache.set(key, w);
|
|
14643
|
+
}
|
|
14644
|
+
return w + text.length * spacing;
|
|
14645
|
+
}
|
|
14646
|
+
layout(blocks, style, bounds) {
|
|
14647
|
+
const font = fontString(style);
|
|
14648
|
+
const padding = bounds.padding;
|
|
14649
|
+
const fixedWidth = isNum(bounds.width);
|
|
14650
|
+
const fixedHeight = isNum(bounds.height);
|
|
14651
|
+
const maxWidth = (bounds.width ?? 0) - padding * 2;
|
|
14652
|
+
const maxHeightPx = (bounds.height ?? 0) - padding * 2;
|
|
14653
|
+
const lineHeightPx = style.lineHeight * style.fontSize;
|
|
14654
|
+
const sigBase = `${font}|${style.letterSpacing}|${style.wrap}|${fixedWidth ? maxWidth : "auto"}`;
|
|
14655
|
+
const prev = this.paragraphs;
|
|
14656
|
+
const next = [];
|
|
14657
|
+
let relaid = 0;
|
|
14658
|
+
const lines = [];
|
|
14659
|
+
let textWidth = 0;
|
|
14660
|
+
let offset = 0;
|
|
14661
|
+
for (let p = 0; p < blocks.length; p++) {
|
|
14662
|
+
const block = blocks[p];
|
|
14663
|
+
const sig = `${sigBase}|${JSON.stringify(block.runs)}`;
|
|
14664
|
+
let entry = prev[p]?.sig === sig ? prev[p] : this.pool.get(sig);
|
|
14665
|
+
if (!entry) {
|
|
14666
|
+
entry = this.layoutParagraph(block, sig, font, style, fixedWidth, maxWidth);
|
|
14667
|
+
relaid++;
|
|
14668
|
+
}
|
|
14669
|
+
next.push(entry);
|
|
14670
|
+
const emitted = this.emitLines(entry, block, p, offset, lines, {
|
|
14671
|
+
font,
|
|
14672
|
+
style,
|
|
14673
|
+
lineHeightPx,
|
|
14674
|
+
maxHeightPx: fixedHeight ? maxHeightPx : void 0
|
|
14675
|
+
});
|
|
14676
|
+
textWidth = Math.max(textWidth, emitted.maxWidth);
|
|
14677
|
+
if (emitted.stopped) break;
|
|
14678
|
+
offset += blockText(block).length + 1;
|
|
14679
|
+
}
|
|
14680
|
+
this.paragraphs = next;
|
|
14681
|
+
this.pool = new Map(next.map((e) => [e.sig, e]));
|
|
14682
|
+
this.lastParagraphsRelaid = relaid;
|
|
14683
|
+
const width = fixedWidth ? bounds.width : textWidth + padding * 2;
|
|
14684
|
+
const height = fixedHeight ? bounds.height : lines.length * lineHeightPx + padding * 2;
|
|
14685
|
+
applyAlign(lines, style.align, width, padding);
|
|
14686
|
+
return {
|
|
14687
|
+
lines,
|
|
14688
|
+
textWidth,
|
|
14689
|
+
textHeight: style.fontSize,
|
|
14690
|
+
lineHeightPx,
|
|
14691
|
+
width,
|
|
14692
|
+
height
|
|
14693
|
+
};
|
|
14694
|
+
}
|
|
14695
|
+
/** Append a paragraph's lines to `lines`; stops once a fixed height is full. */
|
|
14696
|
+
emitLines(entry, block, paragraph, offset, lines, opts) {
|
|
14697
|
+
const { font, style, lineHeightPx, maxHeightPx } = opts;
|
|
14698
|
+
let maxWidth = 0;
|
|
14699
|
+
for (const pl of entry.lines) {
|
|
14700
|
+
const text = entry.graphemes.slice(pl.startG, pl.endG).join("");
|
|
14701
|
+
const segments = this.buildSegments(entry, block, pl, font, style);
|
|
14702
|
+
const startChar = entry.graphemes.slice(0, pl.startG).join("").length;
|
|
14703
|
+
lines.push({
|
|
14704
|
+
paragraph,
|
|
14705
|
+
start: offset + startChar,
|
|
14706
|
+
end: offset + startChar + text.length,
|
|
14707
|
+
text,
|
|
14708
|
+
width: segments.reduce((a, s) => a + s.width, 0),
|
|
14709
|
+
measuredWidth: pl.measuredWidth,
|
|
14710
|
+
offsetX: 0,
|
|
14711
|
+
y: lines.length * lineHeightPx,
|
|
14712
|
+
segments,
|
|
14713
|
+
lastInParagraph: pl.lastInParagraph
|
|
14714
|
+
});
|
|
14715
|
+
maxWidth = Math.max(maxWidth, pl.measuredWidth);
|
|
14716
|
+
if (maxHeightPx !== void 0 && (lines.length + 1) * lineHeightPx > maxHeightPx) return {
|
|
14717
|
+
stopped: true,
|
|
14718
|
+
maxWidth
|
|
14719
|
+
};
|
|
14720
|
+
}
|
|
14721
|
+
return {
|
|
14722
|
+
stopped: false,
|
|
14723
|
+
maxWidth
|
|
14724
|
+
};
|
|
14725
|
+
}
|
|
14726
|
+
rangeWidth(entry, from, to, font, spacing) {
|
|
14727
|
+
let width = 0;
|
|
14728
|
+
let i = from;
|
|
14729
|
+
while (i < to) {
|
|
14730
|
+
const run = entry.runOf[i];
|
|
14731
|
+
let j = i;
|
|
14732
|
+
while (j < to && entry.runOf[j] === run) j++;
|
|
14733
|
+
width += this.measureChunk(font, entry.graphemes.slice(i, j).join(""), spacing);
|
|
14734
|
+
i = j;
|
|
14735
|
+
}
|
|
14736
|
+
return width;
|
|
14737
|
+
}
|
|
14738
|
+
/**
|
|
14739
|
+
* Longest prefix of graphemes[start..n) that fits `maxWidth`, found with an
|
|
14740
|
+
* exponential probe (bounds the work to about one line) plus a binary
|
|
14741
|
+
* search, like Konva. Always consumes at least one grapheme.
|
|
14742
|
+
*/
|
|
14743
|
+
longestFit(start, n, maxWidth, width) {
|
|
14744
|
+
let low = 0;
|
|
14745
|
+
let high = n - start;
|
|
14746
|
+
let probe = Math.min(high, 32);
|
|
14747
|
+
while (probe < high && width(start, start + probe) <= maxWidth) probe = Math.min(high, probe * 2);
|
|
14748
|
+
high = probe;
|
|
14749
|
+
let matchLen = 0;
|
|
14750
|
+
let matchWidth = 0;
|
|
14751
|
+
while (low < high) {
|
|
14752
|
+
const mid = low + high >>> 1;
|
|
14753
|
+
const w = width(start, start + mid + 1);
|
|
14754
|
+
if (w <= maxWidth) {
|
|
14755
|
+
low = mid + 1;
|
|
14756
|
+
matchLen = mid + 1;
|
|
14757
|
+
matchWidth = w;
|
|
14758
|
+
} else high = mid;
|
|
14759
|
+
}
|
|
14760
|
+
if (matchLen === 0) {
|
|
14761
|
+
matchLen = 1;
|
|
14762
|
+
matchWidth = width(start, start + 1);
|
|
14763
|
+
}
|
|
14764
|
+
return {
|
|
14765
|
+
matchLen,
|
|
14766
|
+
matchWidth
|
|
14767
|
+
};
|
|
14768
|
+
}
|
|
14769
|
+
layoutParagraph(block, sig, font, style, fixedWidth, maxWidth) {
|
|
14770
|
+
const { graphemes, runOf } = flattenBlock(block);
|
|
14771
|
+
const entry = {
|
|
14772
|
+
sig,
|
|
14773
|
+
graphemes,
|
|
14774
|
+
runOf,
|
|
14775
|
+
lines: []
|
|
14776
|
+
};
|
|
14777
|
+
const n = graphemes.length;
|
|
14778
|
+
const spacing = style.letterSpacing;
|
|
14779
|
+
const width = (a, b) => this.rangeWidth(entry, a, b, font, spacing);
|
|
14780
|
+
const shouldWrap = style.wrap !== "none";
|
|
14781
|
+
const wrapAtWord = style.wrap !== "char" && shouldWrap;
|
|
14782
|
+
const whole = width(0, n);
|
|
14783
|
+
if (!fixedWidth || !shouldWrap || whole <= maxWidth || n === 0) {
|
|
14784
|
+
entry.lines.push({
|
|
14785
|
+
startG: 0,
|
|
14786
|
+
endG: n,
|
|
14787
|
+
measuredWidth: whole,
|
|
14788
|
+
lastInParagraph: true
|
|
14789
|
+
});
|
|
14790
|
+
return entry;
|
|
14791
|
+
}
|
|
14792
|
+
let start = 0;
|
|
14793
|
+
while (start < n) {
|
|
14794
|
+
let { matchLen, matchWidth } = this.longestFit(start, n, maxWidth, width);
|
|
14795
|
+
if (start + matchLen >= n) {
|
|
14796
|
+
entry.lines.push({
|
|
14797
|
+
startG: start,
|
|
14798
|
+
endG: n,
|
|
14799
|
+
measuredWidth: matchWidth,
|
|
14800
|
+
lastInParagraph: true
|
|
14801
|
+
});
|
|
14802
|
+
return entry;
|
|
14803
|
+
}
|
|
14804
|
+
if (wrapAtWord) {
|
|
14805
|
+
const wrapped = wordWrapIndex(graphemes, start, matchLen, matchWidth, maxWidth);
|
|
14806
|
+
if (wrapped > 0) {
|
|
14807
|
+
matchLen = wrapped;
|
|
14808
|
+
matchWidth = width(start, start + matchLen);
|
|
14809
|
+
}
|
|
14810
|
+
}
|
|
14811
|
+
const end = start + matchLen;
|
|
14812
|
+
entry.lines.push({
|
|
14813
|
+
startG: start,
|
|
14814
|
+
endG: trimTrailingSpace(graphemes, start, end),
|
|
14815
|
+
measuredWidth: matchWidth,
|
|
14816
|
+
lastInParagraph: false
|
|
14817
|
+
});
|
|
14818
|
+
start = skipSpace(graphemes, end);
|
|
14819
|
+
}
|
|
14820
|
+
const last = entry.lines.at(-1);
|
|
14821
|
+
if (last) last.lastInParagraph = true;
|
|
14822
|
+
return entry;
|
|
14823
|
+
}
|
|
14824
|
+
buildSegments(entry, block, pl, font, style) {
|
|
14825
|
+
const segments = [];
|
|
14826
|
+
let x = 0;
|
|
14827
|
+
let i = pl.startG;
|
|
14828
|
+
while (i < pl.endG) {
|
|
14829
|
+
const run = entry.runOf[i];
|
|
14830
|
+
let j = i;
|
|
14831
|
+
while (j < pl.endG && entry.runOf[j] === run) j++;
|
|
14832
|
+
const t = entry.graphemes.slice(i, j).join("");
|
|
14833
|
+
const width = this.measureChunk(font, t, style.letterSpacing);
|
|
14834
|
+
segments.push({
|
|
14835
|
+
t,
|
|
14836
|
+
m: block.runs[run].m,
|
|
14837
|
+
x,
|
|
14838
|
+
width
|
|
14839
|
+
});
|
|
14840
|
+
x += width;
|
|
14841
|
+
i = j;
|
|
14842
|
+
}
|
|
14843
|
+
return segments;
|
|
14844
|
+
}
|
|
14845
|
+
};
|
|
14846
|
+
//#endregion
|
|
14847
|
+
//#region src/nodes/text-v2/shape.ts
|
|
14848
|
+
const LAYOUT_ATTRS = [
|
|
14849
|
+
"text",
|
|
14850
|
+
"blocks",
|
|
14851
|
+
"link",
|
|
14852
|
+
"fontSize",
|
|
14853
|
+
"fontFamily",
|
|
14854
|
+
"fontStyle",
|
|
14855
|
+
"fontVariant",
|
|
14856
|
+
"lineHeight",
|
|
14857
|
+
"letterSpacing",
|
|
14858
|
+
"wrap",
|
|
14859
|
+
"align",
|
|
14860
|
+
"padding",
|
|
14861
|
+
"width",
|
|
14862
|
+
"height"
|
|
14863
|
+
];
|
|
14864
|
+
/**
|
|
14865
|
+
* Canvas shape that lays out and paints blocks/runs text with the
|
|
14866
|
+
* `TextLayoutEngine`. Exposes Konva.Text-compatible getters/setters so
|
|
14867
|
+
* code written against `Konva.Text` keeps working.
|
|
14868
|
+
*/
|
|
14869
|
+
var WeaveTextV2Shape = class extends Konva.Shape {
|
|
14870
|
+
engine = new TextLayoutEngine();
|
|
14871
|
+
naturalEngine = new TextLayoutEngine();
|
|
14872
|
+
layoutCache = null;
|
|
14873
|
+
blocksKey = {};
|
|
14874
|
+
blocksCache = [];
|
|
14875
|
+
viewport = null;
|
|
14876
|
+
constructor(config) {
|
|
14877
|
+
super(config);
|
|
14878
|
+
this.className = "WeaveTextV2Shape";
|
|
14879
|
+
this.on(LAYOUT_ATTRS.map((a) => `${a}Change.konva`).join(" "), () => {
|
|
14880
|
+
this.layoutCache = null;
|
|
14881
|
+
});
|
|
14882
|
+
}
|
|
14883
|
+
text(value) {
|
|
14884
|
+
if (value === void 0) return typeof this.attrs.text === "string" ? this.attrs.text : blocksToText(this.getBlocks());
|
|
14885
|
+
return this.setText(value);
|
|
14886
|
+
}
|
|
14887
|
+
setText(value) {
|
|
14888
|
+
this._setAttr("blocks", void 0);
|
|
14889
|
+
this._setAttr("text", value == null ? "" : String(value));
|
|
14890
|
+
return this;
|
|
14891
|
+
}
|
|
14892
|
+
/** Resolved blocks: stored or lazily synthesized (never written back). */
|
|
14893
|
+
getBlocks() {
|
|
14894
|
+
const k = this.blocksKey;
|
|
14895
|
+
const { blocks, text, link } = this.attrs;
|
|
14896
|
+
if (k.b !== blocks || k.t !== text || k.l !== link) {
|
|
14897
|
+
this.blocksCache = resolveBlocks({
|
|
14898
|
+
blocks,
|
|
14899
|
+
text,
|
|
14900
|
+
link
|
|
14901
|
+
});
|
|
14902
|
+
this.blocksKey = {
|
|
14903
|
+
b: blocks,
|
|
14904
|
+
t: text,
|
|
14905
|
+
l: link
|
|
14906
|
+
};
|
|
14907
|
+
}
|
|
14908
|
+
return this.blocksCache;
|
|
14909
|
+
}
|
|
14910
|
+
/** Replace content with blocks, keeping the `text` mirror in sync. */
|
|
14911
|
+
setBlocks(blocks) {
|
|
14912
|
+
this._setAttr("blocks", blocks);
|
|
14913
|
+
this._setAttr("text", blocksToText(blocks));
|
|
14914
|
+
return this;
|
|
14915
|
+
}
|
|
14916
|
+
fontSize(v) {
|
|
14917
|
+
return this.accessor("fontSize", 12, v);
|
|
14918
|
+
}
|
|
14919
|
+
fontFamily(v) {
|
|
14920
|
+
return this.accessor("fontFamily", "Arial", v);
|
|
14921
|
+
}
|
|
14922
|
+
fontStyle(v) {
|
|
14923
|
+
return this.accessor("fontStyle", "normal", v);
|
|
14924
|
+
}
|
|
14925
|
+
fontVariant(v) {
|
|
14926
|
+
return this.accessor("fontVariant", "normal", v);
|
|
14927
|
+
}
|
|
14928
|
+
lineHeight(v) {
|
|
14929
|
+
return this.accessor("lineHeight", 1, v);
|
|
14930
|
+
}
|
|
14931
|
+
letterSpacing(v) {
|
|
14932
|
+
return this.accessor("letterSpacing", 0, v);
|
|
14933
|
+
}
|
|
14934
|
+
padding(v) {
|
|
14935
|
+
return this.accessor("padding", 0, v);
|
|
14936
|
+
}
|
|
14937
|
+
align(v) {
|
|
14938
|
+
return this.accessor("align", "left", v);
|
|
14939
|
+
}
|
|
14940
|
+
verticalAlign(v) {
|
|
14941
|
+
return this.accessor("verticalAlign", "top", v);
|
|
14942
|
+
}
|
|
14943
|
+
wrap(v) {
|
|
14944
|
+
return this.accessor("wrap", "word", v);
|
|
14945
|
+
}
|
|
14946
|
+
textDecoration(v) {
|
|
14947
|
+
return this.accessor("textDecoration", "", v);
|
|
14948
|
+
}
|
|
14949
|
+
accessor(name, fallback, value) {
|
|
14950
|
+
if (value === void 0) return this.attrs[name] ?? fallback;
|
|
14951
|
+
this.setAttr(name, value);
|
|
14952
|
+
return this;
|
|
14953
|
+
}
|
|
14954
|
+
getTextWidth() {
|
|
14955
|
+
return this.ensureLayout().textWidth;
|
|
14956
|
+
}
|
|
14957
|
+
getTextHeight() {
|
|
14958
|
+
return this.fontSize();
|
|
14959
|
+
}
|
|
14960
|
+
measureSize(text) {
|
|
14961
|
+
return {
|
|
14962
|
+
width: canvasMeasurer(fontString(this.style()), text),
|
|
14963
|
+
height: this.fontSize()
|
|
14964
|
+
};
|
|
14965
|
+
}
|
|
14966
|
+
getWidth() {
|
|
14967
|
+
const w = this.attrs.width;
|
|
14968
|
+
return w === void 0 || w === "auto" || Number.isNaN(w) ? this.ensureLayout().width : w;
|
|
14969
|
+
}
|
|
14970
|
+
getHeight() {
|
|
14971
|
+
const h = this.attrs.height;
|
|
14972
|
+
return h === void 0 || h === "auto" || Number.isNaN(h) ? this.ensureLayout().height : h;
|
|
14973
|
+
}
|
|
14974
|
+
style() {
|
|
14975
|
+
return {
|
|
14976
|
+
fontSize: this.fontSize(),
|
|
14977
|
+
fontFamily: this.fontFamily(),
|
|
14978
|
+
fontStyle: this.fontStyle(),
|
|
14979
|
+
fontVariant: this.fontVariant(),
|
|
14980
|
+
lineHeight: this.lineHeight(),
|
|
14981
|
+
letterSpacing: this.letterSpacing(),
|
|
14982
|
+
wrap: this.wrap(),
|
|
14983
|
+
align: this.align()
|
|
14984
|
+
};
|
|
14985
|
+
}
|
|
14986
|
+
/** Lazy, cached layout. Size accessors and painting force it. */
|
|
14987
|
+
ensureLayout() {
|
|
14988
|
+
if (!this.layoutCache) {
|
|
14989
|
+
const w = this.attrs.width;
|
|
14990
|
+
const h = this.attrs.height;
|
|
14991
|
+
const num = (v) => typeof v === "number" && !Number.isNaN(v) ? v : void 0;
|
|
14992
|
+
this.layoutCache = this.engine.layout(this.getBlocks(), this.style(), {
|
|
14993
|
+
width: num(w),
|
|
14994
|
+
height: num(h),
|
|
14995
|
+
padding: this.padding()
|
|
14996
|
+
});
|
|
14997
|
+
}
|
|
14998
|
+
return this.layoutCache;
|
|
14999
|
+
}
|
|
15000
|
+
/** Size the text would take with no width constraint (for auto layouts). */
|
|
15001
|
+
measureNaturalSize() {
|
|
15002
|
+
const out = this.naturalEngine.layout(this.getBlocks(), this.style(), {
|
|
15003
|
+
width: void 0,
|
|
15004
|
+
height: void 0,
|
|
15005
|
+
padding: this.padding()
|
|
15006
|
+
});
|
|
15007
|
+
return {
|
|
15008
|
+
width: out.width,
|
|
15009
|
+
height: out.height
|
|
15010
|
+
};
|
|
15011
|
+
}
|
|
15012
|
+
/** Rendered width of a string with this shape's font and letter spacing. */
|
|
15013
|
+
measureText(text) {
|
|
15014
|
+
return canvasMeasurer(fontString(this.style()), text) + text.length * this.letterSpacing();
|
|
15015
|
+
}
|
|
15016
|
+
/** Offset of the first line inside the box (padding + vertical align). */
|
|
15017
|
+
getContentOffset() {
|
|
15018
|
+
const padding = this.padding();
|
|
15019
|
+
const layout = this.ensureLayout();
|
|
15020
|
+
const used = layout.lines.length * layout.lineHeightPx + padding * 2;
|
|
15021
|
+
const va = this.verticalAlign();
|
|
15022
|
+
let alignY = 0;
|
|
15023
|
+
if (va === "middle") alignY = (this.getHeight() - used) / 2;
|
|
15024
|
+
else if (va === "bottom") alignY = this.getHeight() - used;
|
|
15025
|
+
return {
|
|
15026
|
+
x: padding,
|
|
15027
|
+
y: alignY + padding
|
|
15028
|
+
};
|
|
15029
|
+
}
|
|
15030
|
+
/** Pre-compute layout for candidate blocks within a time budget. */
|
|
15031
|
+
warmLayout(blocks, budgetMs) {
|
|
15032
|
+
const num = (v) => typeof v === "number" && !Number.isNaN(v) ? v : void 0;
|
|
15033
|
+
return this.engine.warm(blocks, this.style(), {
|
|
15034
|
+
width: num(this.attrs.width),
|
|
15035
|
+
height: num(this.attrs.height),
|
|
15036
|
+
padding: this.padding()
|
|
15037
|
+
}, budgetMs);
|
|
15038
|
+
}
|
|
15039
|
+
/** Optional visible y-range in local coordinates, used for line culling. */
|
|
15040
|
+
setViewport(range) {
|
|
15041
|
+
this.viewport = range;
|
|
15042
|
+
}
|
|
15043
|
+
visibleRange() {
|
|
15044
|
+
if (this.viewport) return this.viewport;
|
|
15045
|
+
const stage = this.getStage();
|
|
15046
|
+
if (!stage) return null;
|
|
15047
|
+
const inv = this.getAbsoluteTransform().copy().invert();
|
|
15048
|
+
const w = stage.width();
|
|
15049
|
+
const h = stage.height();
|
|
15050
|
+
const pts = [
|
|
15051
|
+
inv.point({
|
|
15052
|
+
x: 0,
|
|
15053
|
+
y: 0
|
|
15054
|
+
}),
|
|
15055
|
+
inv.point({
|
|
15056
|
+
x: w,
|
|
15057
|
+
y: 0
|
|
15058
|
+
}),
|
|
15059
|
+
inv.point({
|
|
15060
|
+
x: 0,
|
|
15061
|
+
y: h
|
|
15062
|
+
}),
|
|
15063
|
+
inv.point({
|
|
15064
|
+
x: w,
|
|
15065
|
+
y: h
|
|
15066
|
+
})
|
|
15067
|
+
];
|
|
15068
|
+
return {
|
|
15069
|
+
y1: Math.min(...pts.map((p) => p.y)),
|
|
15070
|
+
y2: Math.max(...pts.map((p) => p.y))
|
|
15071
|
+
};
|
|
15072
|
+
}
|
|
15073
|
+
_sceneFunc = (ctx, shape) => {
|
|
15074
|
+
const self = shape;
|
|
15075
|
+
const layout = self.ensureLayout();
|
|
15076
|
+
if (layout.lines.length === 0) return;
|
|
15077
|
+
const padding = self.padding();
|
|
15078
|
+
const lineHeightPx = layout.lineHeightPx;
|
|
15079
|
+
let baseline = "middle";
|
|
15080
|
+
let translateY = lineHeightPx / 2;
|
|
15081
|
+
if (!Konva.legacyTextRendering) {
|
|
15082
|
+
const m = self.measureMetrics();
|
|
15083
|
+
baseline = "alphabetic";
|
|
15084
|
+
translateY = (m.ascent - m.descent) / 2 + lineHeightPx / 2;
|
|
15085
|
+
}
|
|
15086
|
+
ctx.setAttr("font", fontString(self.style()));
|
|
15087
|
+
ctx.setAttr("textBaseline", baseline);
|
|
15088
|
+
ctx.setAttr("textAlign", "left");
|
|
15089
|
+
const alignY = self.verticalOffset(layout);
|
|
15090
|
+
ctx.translate(padding, alignY + padding);
|
|
15091
|
+
const range = self.visibleRange();
|
|
15092
|
+
const top = alignY + padding;
|
|
15093
|
+
for (let n = 0; n < layout.lines.length; n++) {
|
|
15094
|
+
const lineTop = top + n * lineHeightPx;
|
|
15095
|
+
if (range && (lineTop + lineHeightPx < range.y1 - lineHeightPx || lineTop > range.y2 + lineHeightPx)) continue;
|
|
15096
|
+
self.paintLine(ctx, layout.lines[n], translateY + n * lineHeightPx);
|
|
15097
|
+
}
|
|
15098
|
+
};
|
|
15099
|
+
/** Vertical offset of the text block inside the box (verticalAlign). */
|
|
15100
|
+
verticalOffset(layout) {
|
|
15101
|
+
const used = layout.lines.length * layout.lineHeightPx + this.padding() * 2;
|
|
15102
|
+
const va = this.verticalAlign();
|
|
15103
|
+
if (va === "middle") return (this.getHeight() - used) / 2;
|
|
15104
|
+
if (va === "bottom") return this.getHeight() - used;
|
|
15105
|
+
return 0;
|
|
15106
|
+
}
|
|
15107
|
+
paintLine(ctx, line, y) {
|
|
15108
|
+
const decoration = this.textDecoration();
|
|
15109
|
+
const fontSize = this.fontSize();
|
|
15110
|
+
ctx.save();
|
|
15111
|
+
if (decoration.includes("underline")) this.strokeDecoration(ctx, line, y + Math.round(fontSize / 4));
|
|
15112
|
+
for (const seg of line.segments) this.paintSegment(ctx, seg.t, line.offsetX + seg.x, y);
|
|
15113
|
+
if (decoration.includes("line-through")) this.strokeDecoration(ctx, line, y - Math.round(fontSize / 4));
|
|
15114
|
+
ctx.restore();
|
|
15115
|
+
}
|
|
15116
|
+
strokeDecoration(ctx, line, y) {
|
|
15117
|
+
ctx.save();
|
|
15118
|
+
ctx.beginPath();
|
|
15119
|
+
ctx.moveTo(line.offsetX, y);
|
|
15120
|
+
ctx.lineTo(line.offsetX + Math.round(line.width), y);
|
|
15121
|
+
ctx.lineWidth = this.fontSize() / 15;
|
|
15122
|
+
ctx.strokeStyle = this.fill();
|
|
15123
|
+
ctx.stroke();
|
|
15124
|
+
ctx.restore();
|
|
15125
|
+
}
|
|
15126
|
+
paintSegment(ctx, text, x, y) {
|
|
15127
|
+
const spacing = this.letterSpacing();
|
|
15128
|
+
if (spacing === 0) {
|
|
15129
|
+
this.fillText(ctx, text, x, y);
|
|
15130
|
+
return;
|
|
15131
|
+
}
|
|
15132
|
+
let cx = x;
|
|
15133
|
+
for (const ch of Array.from(text)) {
|
|
15134
|
+
this.fillText(ctx, ch, cx, y);
|
|
15135
|
+
cx += this.measureSize(ch).width + spacing;
|
|
15136
|
+
}
|
|
15137
|
+
}
|
|
15138
|
+
fillText(ctx, text, x, y) {
|
|
15139
|
+
const self = this;
|
|
15140
|
+
self._partialText = text;
|
|
15141
|
+
self._partialTextX = x;
|
|
15142
|
+
self._partialTextY = y;
|
|
15143
|
+
ctx.fillStrokeShape(this);
|
|
15144
|
+
}
|
|
15145
|
+
_hitFunc = (ctx, shape) => {
|
|
15146
|
+
ctx.beginPath();
|
|
15147
|
+
const self = shape;
|
|
15148
|
+
ctx.rect(0, 0, self.getWidth(), self.getHeight());
|
|
15149
|
+
ctx.closePath();
|
|
15150
|
+
ctx.fillStrokeShape(shape);
|
|
15151
|
+
};
|
|
15152
|
+
metricsCache = null;
|
|
15153
|
+
/** Font ascent/descent used for baseline placement (parity with Konva.Text). */
|
|
15154
|
+
measureMetrics() {
|
|
15155
|
+
const font = fontString(this.style());
|
|
15156
|
+
if (this.metricsCache?.key === font) return this.metricsCache;
|
|
15157
|
+
const ctx = Konva.Util.createCanvasElement().getContext("2d");
|
|
15158
|
+
ctx.font = font;
|
|
15159
|
+
const m = ctx.measureText("M");
|
|
15160
|
+
const scale = this.fontSize() / 100;
|
|
15161
|
+
const ascent = m.fontBoundingBoxAscent ?? 91 * scale;
|
|
15162
|
+
const descent = m.fontBoundingBoxDescent ?? 21 * scale;
|
|
15163
|
+
this.metricsCache = {
|
|
15164
|
+
key: font,
|
|
15165
|
+
ascent,
|
|
15166
|
+
descent
|
|
15167
|
+
};
|
|
15168
|
+
return this.metricsCache;
|
|
15169
|
+
}
|
|
15170
|
+
};
|
|
15171
|
+
Object.assign(WeaveTextV2Shape.prototype, {
|
|
15172
|
+
_fillFunc(ctx) {
|
|
15173
|
+
ctx.fillText(this._partialText, this._partialTextX, this._partialTextY);
|
|
15174
|
+
},
|
|
15175
|
+
_strokeFunc(ctx) {
|
|
15176
|
+
ctx.setAttr("miterLimit", 2);
|
|
15177
|
+
ctx.strokeText(this._partialText, this._partialTextX, this._partialTextY);
|
|
15178
|
+
}
|
|
15179
|
+
});
|
|
15180
|
+
//#endregion
|
|
15181
|
+
//#region src/nodes/text-v2/edit/geometry.ts
|
|
15182
|
+
const graphemeSeg = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : null;
|
|
15183
|
+
const wordSeg = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter(void 0, { granularity: "word" }) : null;
|
|
15184
|
+
function boundaries(text, seg) {
|
|
15185
|
+
if (!seg) return Array.from({ length: text.length + 1 }, (_, i) => i);
|
|
15186
|
+
const out = [0];
|
|
15187
|
+
for (const s of seg.segment(text)) out.push(s.index + s.segment.length);
|
|
15188
|
+
return out;
|
|
15189
|
+
}
|
|
15190
|
+
function prevGrapheme(text, offset) {
|
|
15191
|
+
if (offset <= 0) return 0;
|
|
15192
|
+
const b = boundaries(text, graphemeSeg);
|
|
15193
|
+
for (let i = b.length - 1; i >= 0; i--) if (b[i] < offset) return b[i];
|
|
15194
|
+
return 0;
|
|
15195
|
+
}
|
|
15196
|
+
function nextGrapheme(text, offset) {
|
|
15197
|
+
if (offset >= text.length) return text.length;
|
|
15198
|
+
const b = boundaries(text, graphemeSeg);
|
|
15199
|
+
for (const v of b) if (v > offset) return v;
|
|
15200
|
+
return text.length;
|
|
15201
|
+
}
|
|
15202
|
+
/** Word segment [start, end) containing (or adjacent to) the offset. */
|
|
15203
|
+
function wordRangeAt(text, offset) {
|
|
15204
|
+
if (text === "") return [0, 0];
|
|
15205
|
+
offset = Math.max(0, Math.min(offset, text.length));
|
|
15206
|
+
if (!wordSeg) {
|
|
15207
|
+
let s = offset;
|
|
15208
|
+
let e = offset;
|
|
15209
|
+
while (s > 0 && /\S/.test(text[s - 1])) s--;
|
|
15210
|
+
while (e < text.length && /\S/.test(text[e])) e++;
|
|
15211
|
+
return [s, e];
|
|
15212
|
+
}
|
|
15213
|
+
let last = [offset, offset];
|
|
15214
|
+
for (const seg of wordSeg.segment(text)) {
|
|
15215
|
+
const s = seg.index;
|
|
15216
|
+
const e = s + seg.segment.length;
|
|
15217
|
+
last = [s, e];
|
|
15218
|
+
if (offset < e) return [s, e];
|
|
15219
|
+
}
|
|
15220
|
+
return last;
|
|
15221
|
+
}
|
|
15222
|
+
/** Paragraph range [start, end) (no trailing newline) containing offset. */
|
|
15223
|
+
function paragraphRangeAt(text, offset) {
|
|
15224
|
+
offset = Math.max(0, Math.min(offset, text.length));
|
|
15225
|
+
const s = text.lastIndexOf("\n", offset - 1) + 1;
|
|
15226
|
+
let e = text.indexOf("\n", offset);
|
|
15227
|
+
if (e === -1) e = text.length;
|
|
15228
|
+
return [s, e];
|
|
15229
|
+
}
|
|
15230
|
+
/** Previous word start (Ctrl+Left semantics). */
|
|
15231
|
+
function prevWordBoundary(text, offset) {
|
|
15232
|
+
let i = offset;
|
|
15233
|
+
while (i > 0 && /\s/.test(text[i - 1])) i--;
|
|
15234
|
+
if (i === 0) return 0;
|
|
15235
|
+
return wordRangeAt(text, i - 1)[0];
|
|
15236
|
+
}
|
|
15237
|
+
/** Next word end (Ctrl+Right semantics). */
|
|
15238
|
+
function nextWordBoundary(text, offset) {
|
|
15239
|
+
let i = offset;
|
|
15240
|
+
while (i < text.length && /\s/.test(text[i])) i++;
|
|
15241
|
+
if (i >= text.length) return text.length;
|
|
15242
|
+
return wordRangeAt(text, i)[1];
|
|
15243
|
+
}
|
|
15244
|
+
function lineIndexAt(layout, offset) {
|
|
15245
|
+
const lines = layout.lines;
|
|
15246
|
+
if (lines.length === 0) return 0;
|
|
15247
|
+
let fallback = 0;
|
|
15248
|
+
for (let i = 0; i < lines.length; i++) {
|
|
15249
|
+
const l = lines[i];
|
|
15250
|
+
if (l.start <= offset) fallback = i;
|
|
15251
|
+
if (offset >= l.start && offset <= l.end) {
|
|
15252
|
+
const next = lines[i + 1];
|
|
15253
|
+
if (offset === l.end && !l.lastInParagraph && next?.start === offset) continue;
|
|
15254
|
+
return i;
|
|
15255
|
+
}
|
|
15256
|
+
}
|
|
15257
|
+
return fallback;
|
|
15258
|
+
}
|
|
15259
|
+
function lineX(line, offset, measure) {
|
|
15260
|
+
const k = Math.max(0, Math.min(offset - line.start, line.text.length));
|
|
15261
|
+
return line.offsetX + (k === 0 ? 0 : measure(line.text.slice(0, k)));
|
|
15262
|
+
}
|
|
15263
|
+
/** Caret rectangle in text-local coordinates (before padding/valign offset). */
|
|
15264
|
+
function caretRect(layout, offset, measure) {
|
|
15265
|
+
const i = lineIndexAt(layout, offset);
|
|
15266
|
+
const line = layout.lines[i];
|
|
15267
|
+
if (!line) return {
|
|
15268
|
+
x: 0,
|
|
15269
|
+
y: 0,
|
|
15270
|
+
height: layout.lineHeightPx,
|
|
15271
|
+
line: 0
|
|
15272
|
+
};
|
|
15273
|
+
return {
|
|
15274
|
+
x: lineX(line, offset, measure),
|
|
15275
|
+
y: i * layout.lineHeightPx,
|
|
15276
|
+
height: layout.lineHeightPx,
|
|
15277
|
+
line: i
|
|
15278
|
+
};
|
|
15279
|
+
}
|
|
15280
|
+
/** Offset closest to a point in text-local coordinates. */
|
|
15281
|
+
function offsetFromPoint(layout, point, measure) {
|
|
15282
|
+
const lines = layout.lines;
|
|
15283
|
+
if (lines.length === 0) return 0;
|
|
15284
|
+
return offsetInLine(lines[Math.max(0, Math.min(lines.length - 1, Math.floor(point.y / layout.lineHeightPx)))], point.x, measure);
|
|
15285
|
+
}
|
|
15286
|
+
function offsetInLine(line, x, measure) {
|
|
15287
|
+
const rel = x - line.offsetX;
|
|
15288
|
+
if (rel <= 0 || line.text === "") return line.start;
|
|
15289
|
+
const b = boundaries(line.text, graphemeSeg);
|
|
15290
|
+
let prevW = 0;
|
|
15291
|
+
for (let k = 1; k < b.length; k++) {
|
|
15292
|
+
const w = measure(line.text.slice(0, b[k]));
|
|
15293
|
+
if (rel < (prevW + w) / 2) return line.start + b[k - 1];
|
|
15294
|
+
prevW = w;
|
|
15295
|
+
}
|
|
15296
|
+
return line.end;
|
|
15297
|
+
}
|
|
15298
|
+
/** Move one visual line up/down keeping a desired x. */
|
|
15299
|
+
function moveVertical(layout, offset, dir, desiredX, measure) {
|
|
15300
|
+
const j = lineIndexAt(layout, offset) + dir;
|
|
15301
|
+
if (j < 0) return 0;
|
|
15302
|
+
if (j >= layout.lines.length) return layout.lines.at(-1)?.end ?? 0;
|
|
15303
|
+
return offsetInLine(layout.lines[j], desiredX, measure);
|
|
15304
|
+
}
|
|
15305
|
+
function lineHome(layout, offset) {
|
|
15306
|
+
return layout.lines[lineIndexAt(layout, offset)]?.start ?? 0;
|
|
15307
|
+
}
|
|
15308
|
+
function lineEnd(layout, offset) {
|
|
15309
|
+
return layout.lines[lineIndexAt(layout, offset)]?.end ?? 0;
|
|
15310
|
+
}
|
|
15311
|
+
/** Selection rectangles (one per touched line) in text-local coordinates. */
|
|
15312
|
+
function selectionRects(layout, from, to, measure) {
|
|
15313
|
+
if (from > to) [from, to] = [to, from];
|
|
15314
|
+
if (from === to) return [];
|
|
15315
|
+
const out = [];
|
|
15316
|
+
layout.lines.forEach((line, i) => {
|
|
15317
|
+
const hasNewline = line.lastInParagraph;
|
|
15318
|
+
const lineFrom = Math.max(from, line.start);
|
|
15319
|
+
const lineTo = Math.min(to, hasNewline ? line.end + 1 : line.end);
|
|
15320
|
+
if (lineTo <= lineFrom && !(line.start === line.end && from <= line.start && to > line.start)) return;
|
|
15321
|
+
const x1 = lineX(line, Math.min(lineFrom, line.end), measure);
|
|
15322
|
+
let x2 = lineX(line, Math.min(lineTo, line.end), measure);
|
|
15323
|
+
if (hasNewline && to > line.end) x2 += Math.max(4, layout.textHeight / 3);
|
|
15324
|
+
if (x2 <= x1) return;
|
|
15325
|
+
out.push({
|
|
15326
|
+
x: x1,
|
|
15327
|
+
y: i * layout.lineHeightPx,
|
|
15328
|
+
width: x2 - x1,
|
|
15329
|
+
height: layout.lineHeightPx
|
|
15330
|
+
});
|
|
15331
|
+
});
|
|
15332
|
+
return out;
|
|
15333
|
+
}
|
|
15334
|
+
//#endregion
|
|
15335
|
+
//#region src/nodes/text-v2/edit/model.ts
|
|
15336
|
+
/**
|
|
15337
|
+
* Session-local edit state: content + selection + undo/redo with
|
|
15338
|
+
* coalescing. Typing and deleting merge into one entry while they continue
|
|
15339
|
+
* in the same direction without a selection move; paste and IME composition
|
|
15340
|
+
* are always one entry each. Independent of Konva and the DOM.
|
|
15341
|
+
*/
|
|
15342
|
+
var TextEditModel = class {
|
|
15343
|
+
blocks;
|
|
15344
|
+
anchor = 0;
|
|
15345
|
+
focus = 0;
|
|
15346
|
+
/** x kept while moving vertically; reset by any other move. */
|
|
15347
|
+
desiredX = null;
|
|
15348
|
+
undoStack = [];
|
|
15349
|
+
redoStack = [];
|
|
15350
|
+
lastKind = null;
|
|
15351
|
+
lastEditEnd = -1;
|
|
15352
|
+
composing = false;
|
|
15353
|
+
constructor(blocks) {
|
|
15354
|
+
this.blocks = blocks;
|
|
15355
|
+
const len = blocksToText(blocks).length;
|
|
15356
|
+
this.anchor = this.focus = len;
|
|
15357
|
+
}
|
|
15358
|
+
getBlocks() {
|
|
15359
|
+
return this.blocks;
|
|
15360
|
+
}
|
|
15361
|
+
getText() {
|
|
15362
|
+
return blocksToText(this.blocks);
|
|
15363
|
+
}
|
|
15364
|
+
getSelection() {
|
|
15365
|
+
return {
|
|
15366
|
+
start: Math.min(this.anchor, this.focus),
|
|
15367
|
+
end: Math.max(this.anchor, this.focus)
|
|
15368
|
+
};
|
|
15369
|
+
}
|
|
15370
|
+
getSelectedText() {
|
|
15371
|
+
const { start, end } = this.getSelection();
|
|
15372
|
+
return this.getText().slice(start, end);
|
|
15373
|
+
}
|
|
15374
|
+
canUndo() {
|
|
15375
|
+
return this.undoStack.length > 0;
|
|
15376
|
+
}
|
|
15377
|
+
canRedo() {
|
|
15378
|
+
return this.redoStack.length > 0;
|
|
15379
|
+
}
|
|
15380
|
+
setSelection(anchor, focus) {
|
|
15381
|
+
const len = this.getText().length;
|
|
15382
|
+
this.anchor = Math.max(0, Math.min(anchor, len));
|
|
15383
|
+
this.focus = Math.max(0, Math.min(focus, len));
|
|
15384
|
+
this.breakCoalescing();
|
|
15385
|
+
}
|
|
15386
|
+
moveTo(offset, extend = false, keepDesiredX = false) {
|
|
15387
|
+
const len = this.getText().length;
|
|
15388
|
+
const o = Math.max(0, Math.min(offset, len));
|
|
15389
|
+
this.focus = o;
|
|
15390
|
+
if (!extend) this.anchor = o;
|
|
15391
|
+
if (!keepDesiredX) this.desiredX = null;
|
|
15392
|
+
this.breakCoalescing();
|
|
15393
|
+
}
|
|
15394
|
+
selectAll() {
|
|
15395
|
+
this.anchor = 0;
|
|
15396
|
+
this.focus = this.getText().length;
|
|
15397
|
+
this.breakCoalescing();
|
|
15398
|
+
}
|
|
15399
|
+
breakCoalescing() {
|
|
15400
|
+
if (!this.composing) this.lastKind = null;
|
|
15401
|
+
}
|
|
15402
|
+
beginComposition() {
|
|
15403
|
+
this.composing = true;
|
|
15404
|
+
this.lastKind = null;
|
|
15405
|
+
}
|
|
15406
|
+
endComposition() {
|
|
15407
|
+
this.composing = false;
|
|
15408
|
+
this.lastKind = null;
|
|
15409
|
+
}
|
|
15410
|
+
snapshot() {
|
|
15411
|
+
return {
|
|
15412
|
+
blocks: this.blocks,
|
|
15413
|
+
anchor: this.anchor,
|
|
15414
|
+
focus: this.focus
|
|
15415
|
+
};
|
|
15416
|
+
}
|
|
15417
|
+
record(kind, editStart) {
|
|
15418
|
+
if (!(this.lastKind === kind && (kind === "type" || kind === "delete" || kind === "ime") && (kind === "ime" || this.lastEditEnd === editStart || kind === "delete"))) {
|
|
15419
|
+
this.undoStack.push(this.snapshot());
|
|
15420
|
+
if (this.undoStack.length > 500) this.undoStack.shift();
|
|
15421
|
+
}
|
|
15422
|
+
this.redoStack = [];
|
|
15423
|
+
this.lastKind = kind;
|
|
15424
|
+
}
|
|
15425
|
+
/** Replace [start,end) with plain text and put the caret after it. */
|
|
15426
|
+
replaceRange(start, end, text, kind) {
|
|
15427
|
+
const len = this.getText().length;
|
|
15428
|
+
start = Math.max(0, Math.min(start, len));
|
|
15429
|
+
end = Math.max(start, Math.min(end, len));
|
|
15430
|
+
if (start === end && text === "") return false;
|
|
15431
|
+
this.record(kind, start);
|
|
15432
|
+
this.blocks = replaceRange(this.blocks, start, end, text);
|
|
15433
|
+
this.anchor = this.focus = start + text.length;
|
|
15434
|
+
this.lastEditEnd = this.focus;
|
|
15435
|
+
this.desiredX = null;
|
|
15436
|
+
return true;
|
|
15437
|
+
}
|
|
15438
|
+
replaceSelection(text, kind) {
|
|
15439
|
+
const { start, end } = this.getSelection();
|
|
15440
|
+
return this.replaceRange(start, end, text, kind);
|
|
15441
|
+
}
|
|
15442
|
+
backspace(byWord = false) {
|
|
15443
|
+
const { start, end } = this.getSelection();
|
|
15444
|
+
if (start !== end) return this.replaceRange(start, end, "", "delete");
|
|
15445
|
+
if (start === 0) return false;
|
|
15446
|
+
const text = this.getText();
|
|
15447
|
+
const from = byWord ? prevWordBoundary(text, start) : prevGrapheme(text, start);
|
|
15448
|
+
return this.replaceRange(from, start, "", "delete");
|
|
15449
|
+
}
|
|
15450
|
+
del(byWord = false) {
|
|
15451
|
+
const { start, end } = this.getSelection();
|
|
15452
|
+
if (start !== end) return this.replaceRange(start, end, "", "delete");
|
|
15453
|
+
const text = this.getText();
|
|
15454
|
+
if (start >= text.length) return false;
|
|
15455
|
+
const to = byWord ? nextWordBoundary(text, start) : nextGrapheme(text, start);
|
|
15456
|
+
return this.replaceRange(start, to, "", "delete");
|
|
15457
|
+
}
|
|
15458
|
+
undo() {
|
|
15459
|
+
const prev = this.undoStack.pop();
|
|
15460
|
+
if (!prev) return false;
|
|
15461
|
+
this.redoStack.push(this.snapshot());
|
|
15462
|
+
this.restore(prev);
|
|
15463
|
+
return true;
|
|
15464
|
+
}
|
|
15465
|
+
redo() {
|
|
15466
|
+
const next = this.redoStack.pop();
|
|
15467
|
+
if (!next) return false;
|
|
15468
|
+
this.undoStack.push(this.snapshot());
|
|
15469
|
+
this.restore(next);
|
|
15470
|
+
return true;
|
|
15471
|
+
}
|
|
15472
|
+
restore(s) {
|
|
15473
|
+
this.blocks = s.blocks;
|
|
15474
|
+
this.anchor = s.anchor;
|
|
15475
|
+
this.focus = s.focus;
|
|
15476
|
+
this.lastKind = null;
|
|
15477
|
+
this.desiredX = null;
|
|
15478
|
+
}
|
|
15479
|
+
};
|
|
15480
|
+
const SLICE_BUDGET_MS = 8;
|
|
15481
|
+
const BLINK_MS = 530;
|
|
15482
|
+
var TextEditSession = class {
|
|
15483
|
+
model;
|
|
15484
|
+
target;
|
|
15485
|
+
host;
|
|
15486
|
+
id;
|
|
15487
|
+
abort = new AbortController();
|
|
15488
|
+
textarea = null;
|
|
15489
|
+
overlay = null;
|
|
15490
|
+
blinkTimer;
|
|
15491
|
+
caretOn = true;
|
|
15492
|
+
dragging = false;
|
|
15493
|
+
composing = false;
|
|
15494
|
+
pendingTimer;
|
|
15495
|
+
pendingFlush = false;
|
|
15496
|
+
destroyed = false;
|
|
15497
|
+
initialJson;
|
|
15498
|
+
constructor(target, host, id = "text-edit-session") {
|
|
15499
|
+
this.target = target;
|
|
15500
|
+
this.host = host;
|
|
15501
|
+
this.id = id;
|
|
15502
|
+
this.model = new TextEditModel(target.getBlocks());
|
|
15503
|
+
this.initialJson = JSON.stringify(this.model.getBlocks());
|
|
15504
|
+
}
|
|
15505
|
+
start() {
|
|
15506
|
+
const signal = this.abort.signal;
|
|
15507
|
+
host_signal(this.host.signal, this.abort);
|
|
15508
|
+
this.createTextarea();
|
|
15509
|
+
this.createOverlay();
|
|
15510
|
+
const ta = this.textarea;
|
|
15511
|
+
const opts = { signal };
|
|
15512
|
+
ta.addEventListener("keydown", this.onKeyDown, opts);
|
|
15513
|
+
ta.addEventListener("keyup", this.onKeyUp, opts);
|
|
15514
|
+
ta.addEventListener("beforeinput", this.onBeforeInput, opts);
|
|
15515
|
+
ta.addEventListener("input", this.onInput, opts);
|
|
15516
|
+
ta.addEventListener("paste", this.onPaste, opts);
|
|
15517
|
+
ta.addEventListener("compositionstart", this.onCompositionStart, opts);
|
|
15518
|
+
ta.addEventListener("compositionend", this.onCompositionEnd, opts);
|
|
15519
|
+
ta.addEventListener("select", this.syncFromTextarea, opts);
|
|
15520
|
+
ta.ownerDocument.addEventListener("selectionchange", this.syncFromTextarea, opts);
|
|
15521
|
+
window.addEventListener("pointerdown", this.onPointerDown, {
|
|
15522
|
+
signal,
|
|
15523
|
+
capture: true
|
|
15524
|
+
});
|
|
15525
|
+
window.addEventListener("pointermove", this.onPointerMove, { signal });
|
|
15526
|
+
window.addEventListener("pointerup", this.onPointerUp, { signal });
|
|
15527
|
+
window.addEventListener("mousedown", this.onMouseDown, {
|
|
15528
|
+
signal,
|
|
15529
|
+
capture: true
|
|
15530
|
+
});
|
|
15531
|
+
window.weaveTextEditing = {
|
|
15532
|
+
...window.weaveTextEditing,
|
|
15533
|
+
[this.id]: this.id
|
|
15534
|
+
};
|
|
15535
|
+
if (this.host.selectAllOnStart) this.model.selectAll();
|
|
15536
|
+
this.syncTextarea();
|
|
15537
|
+
ta.focus();
|
|
15538
|
+
this.positionTextarea();
|
|
15539
|
+
this.startBlink();
|
|
15540
|
+
this.repaint();
|
|
15541
|
+
}
|
|
15542
|
+
/** Final content and whether it differs from when the session started. */
|
|
15543
|
+
getResult() {
|
|
15544
|
+
return {
|
|
15545
|
+
blocks: this.model.getBlocks(),
|
|
15546
|
+
changed: JSON.stringify(this.model.getBlocks()) !== this.initialJson
|
|
15547
|
+
};
|
|
15548
|
+
}
|
|
15549
|
+
destroy() {
|
|
15550
|
+
if (this.destroyed) return;
|
|
15551
|
+
this.destroyed = true;
|
|
15552
|
+
this.flushPending();
|
|
15553
|
+
this.abort.abort();
|
|
15554
|
+
clearInterval(this.blinkTimer);
|
|
15555
|
+
clearTimeout(this.pendingTimer);
|
|
15556
|
+
this.textarea?.remove();
|
|
15557
|
+
this.textarea = null;
|
|
15558
|
+
const layer = this.overlay?.getLayer();
|
|
15559
|
+
this.overlay?.destroy();
|
|
15560
|
+
this.overlay = null;
|
|
15561
|
+
layer?.batchDraw();
|
|
15562
|
+
const w = window;
|
|
15563
|
+
if (w.weaveTextEditing) delete w.weaveTextEditing[this.id];
|
|
15564
|
+
}
|
|
15565
|
+
focus() {
|
|
15566
|
+
this.textarea?.focus();
|
|
15567
|
+
}
|
|
15568
|
+
createTextarea() {
|
|
15569
|
+
const ta = this.host.container.ownerDocument.createElement("textarea");
|
|
15570
|
+
ta.setAttribute("autocapitalize", "off");
|
|
15571
|
+
ta.setAttribute("autocomplete", "off");
|
|
15572
|
+
ta.setAttribute("autocorrect", "off");
|
|
15573
|
+
ta.setAttribute("spellcheck", "false");
|
|
15574
|
+
ta.setAttribute("wrap", "off");
|
|
15575
|
+
ta.setAttribute("aria-label", "Text editor");
|
|
15576
|
+
ta.tabIndex = 1;
|
|
15577
|
+
Object.assign(ta.style, {
|
|
15578
|
+
position: "absolute",
|
|
15579
|
+
left: "0px",
|
|
15580
|
+
top: "0px",
|
|
15581
|
+
width: "1px",
|
|
15582
|
+
height: "1em",
|
|
15583
|
+
margin: "0",
|
|
15584
|
+
padding: "0",
|
|
15585
|
+
border: "0",
|
|
15586
|
+
outline: "none",
|
|
15587
|
+
resize: "none",
|
|
15588
|
+
overflow: "hidden",
|
|
15589
|
+
whiteSpace: "pre",
|
|
15590
|
+
opacity: "0",
|
|
15591
|
+
fontSize: "16px",
|
|
15592
|
+
background: "transparent",
|
|
15593
|
+
color: "transparent",
|
|
15594
|
+
caretColor: "transparent",
|
|
15595
|
+
pointerEvents: "none",
|
|
15596
|
+
zIndex: "1"
|
|
15597
|
+
});
|
|
15598
|
+
this.host.container.appendChild(ta);
|
|
15599
|
+
this.textarea = ta;
|
|
15600
|
+
}
|
|
15601
|
+
syncTextarea() {
|
|
15602
|
+
const ta = this.textarea;
|
|
15603
|
+
if (!ta || this.composing) return;
|
|
15604
|
+
const text = this.model.getText();
|
|
15605
|
+
if (ta.value !== text) ta.value = text;
|
|
15606
|
+
const { start, end } = this.model.getSelection();
|
|
15607
|
+
const backward = this.model.focus < this.model.anchor;
|
|
15608
|
+
if (ta.selectionStart !== start || ta.selectionEnd !== end) ta.setSelectionRange(start, end, backward ? "backward" : "forward");
|
|
15609
|
+
}
|
|
15610
|
+
positionTextarea() {
|
|
15611
|
+
const ta = this.textarea;
|
|
15612
|
+
if (!ta) return;
|
|
15613
|
+
const c = this.caretStagePoint();
|
|
15614
|
+
const p = this.host.stageToContainer(c);
|
|
15615
|
+
ta.style.left = `${p.x}px`;
|
|
15616
|
+
ta.style.top = `${p.y}px`;
|
|
15617
|
+
}
|
|
15618
|
+
caretStagePoint() {
|
|
15619
|
+
const r = caretRect(this.target.getLayout(), this.model.focus, this.target.measure);
|
|
15620
|
+
const o = this.target.getContentOffset();
|
|
15621
|
+
return this.target.getAbsoluteTransform().point({
|
|
15622
|
+
x: r.x + o.x,
|
|
15623
|
+
y: r.y + o.y
|
|
15624
|
+
});
|
|
15625
|
+
}
|
|
15626
|
+
syncFromTextarea = () => {
|
|
15627
|
+
const ta = this.textarea;
|
|
15628
|
+
if (!ta || this.composing || this.destroyed) return;
|
|
15629
|
+
if (ta.getRootNode().activeElement !== ta) return;
|
|
15630
|
+
if (ta.value !== this.model.getText()) return;
|
|
15631
|
+
const backward = ta.selectionDirection === "backward";
|
|
15632
|
+
const a = backward ? ta.selectionEnd : ta.selectionStart;
|
|
15633
|
+
const f = backward ? ta.selectionStart : ta.selectionEnd;
|
|
15634
|
+
if (a !== this.model.anchor || f !== this.model.focus) {
|
|
15635
|
+
this.model.setSelection(a, f);
|
|
15636
|
+
this.afterSelectionChange();
|
|
15637
|
+
}
|
|
15638
|
+
};
|
|
15639
|
+
/** Keys the session handles itself; everything else stays native. */
|
|
15640
|
+
handleSessionKey(e) {
|
|
15641
|
+
const mod = e.ctrlKey || e.metaKey;
|
|
15642
|
+
const key = e.key.toLowerCase();
|
|
15643
|
+
const isUndo = mod && key === "z" && !e.shiftKey;
|
|
15644
|
+
const isRedo = mod && (key === "y" || key === "z" && e.shiftKey);
|
|
15645
|
+
if (e.key === "Escape") this.host.onRequestExit("escape");
|
|
15646
|
+
else if (isUndo) this.undo();
|
|
15647
|
+
else if (isRedo) this.redo();
|
|
15648
|
+
else if (e.key === "Tab") return true;
|
|
15649
|
+
else return false;
|
|
15650
|
+
return true;
|
|
15651
|
+
}
|
|
15652
|
+
handleNavigationKey(e) {
|
|
15653
|
+
if (e.ctrlKey || e.metaKey) return false;
|
|
15654
|
+
if ((e.key === "ArrowUp" || e.key === "ArrowDown") && !e.altKey) {
|
|
15655
|
+
this.moveVertically(e.key === "ArrowUp" ? -1 : 1, e.shiftKey);
|
|
15656
|
+
return true;
|
|
15657
|
+
}
|
|
15658
|
+
if (e.key === "Home" || e.key === "End") {
|
|
15659
|
+
const layout = this.target.getLayout();
|
|
15660
|
+
const to = e.key === "Home" ? lineHome(layout, this.model.focus) : lineEnd(layout, this.model.focus);
|
|
15661
|
+
this.model.moveTo(to, e.shiftKey);
|
|
15662
|
+
this.afterSelectionChange();
|
|
15663
|
+
return true;
|
|
15664
|
+
}
|
|
15665
|
+
return false;
|
|
15666
|
+
}
|
|
15667
|
+
onKeyDown = (e) => {
|
|
15668
|
+
e.stopPropagation();
|
|
15669
|
+
if (this.handleSessionKey(e)) {
|
|
15670
|
+
e.preventDefault();
|
|
15671
|
+
return;
|
|
15672
|
+
}
|
|
15673
|
+
if (this.composing) return;
|
|
15674
|
+
if (this.handleNavigationKey(e)) {
|
|
15675
|
+
e.preventDefault();
|
|
15676
|
+
return;
|
|
15677
|
+
}
|
|
15678
|
+
setTimeout(this.syncFromTextarea, 0);
|
|
15679
|
+
};
|
|
15680
|
+
onKeyUp = (e) => {
|
|
15681
|
+
e.stopPropagation();
|
|
15682
|
+
this.syncFromTextarea();
|
|
15683
|
+
};
|
|
15684
|
+
onBeforeInput = (e) => {
|
|
15685
|
+
if (e.inputType === "historyUndo" || e.inputType === "historyRedo") e.preventDefault();
|
|
15686
|
+
};
|
|
15687
|
+
moveVertically(dir, extend) {
|
|
15688
|
+
const layout = this.target.getLayout();
|
|
15689
|
+
this.model.desiredX ??= caretRect(layout, this.model.focus, this.target.measure).x;
|
|
15690
|
+
const to = moveVertical(layout, this.model.focus, dir, this.model.desiredX, this.target.measure);
|
|
15691
|
+
this.model.moveTo(to, extend, true);
|
|
15692
|
+
this.afterSelectionChange();
|
|
15693
|
+
}
|
|
15694
|
+
onInput = (e) => {
|
|
15695
|
+
const ta = this.textarea;
|
|
15696
|
+
if (!ta) return;
|
|
15697
|
+
const oldText = this.model.getText();
|
|
15698
|
+
const newText = ta.value;
|
|
15699
|
+
if (oldText === newText) return;
|
|
15700
|
+
let p = 0;
|
|
15701
|
+
const max = Math.min(oldText.length, newText.length);
|
|
15702
|
+
while (p < max && oldText[p] === newText[p]) p++;
|
|
15703
|
+
let s = 0;
|
|
15704
|
+
while (s < max - p && oldText[oldText.length - 1 - s] === newText[newText.length - 1 - s]) s++;
|
|
15705
|
+
const inserted = newText.slice(p, newText.length - s);
|
|
15706
|
+
const inputType = e.inputType ?? "";
|
|
15707
|
+
const kind = this.editKindFor(inputType);
|
|
15708
|
+
this.model.replaceRange(p, oldText.length - s, inserted, kind);
|
|
15709
|
+
this.model.anchor = this.model.focus = Math.min(ta.selectionEnd, this.model.getText().length);
|
|
15710
|
+
this.afterContentChange(inserted.length);
|
|
15711
|
+
};
|
|
15712
|
+
editKindFor(inputType) {
|
|
15713
|
+
if (this.composing || inputType.includes("Composition")) return "ime";
|
|
15714
|
+
if (inputType.startsWith("delete")) return "delete";
|
|
15715
|
+
if (inputType === "insertFromPaste") return "paste";
|
|
15716
|
+
return "type";
|
|
15717
|
+
}
|
|
15718
|
+
onPaste = (e) => {
|
|
15719
|
+
e.stopPropagation();
|
|
15720
|
+
e.preventDefault();
|
|
15721
|
+
const raw = e.clipboardData?.getData("text/plain") ?? "";
|
|
15722
|
+
if (raw === "") return;
|
|
15723
|
+
this.insertText(raw.replace(/\r\n?/g, "\n"), "paste");
|
|
15724
|
+
};
|
|
15725
|
+
onCompositionStart = () => {
|
|
15726
|
+
this.composing = true;
|
|
15727
|
+
this.model.beginComposition();
|
|
15728
|
+
};
|
|
15729
|
+
onCompositionEnd = () => {
|
|
15730
|
+
this.composing = false;
|
|
15731
|
+
this.model.endComposition();
|
|
15732
|
+
this.syncFromTextarea();
|
|
15733
|
+
};
|
|
15734
|
+
/** Programmatic insert at the selection (paste, tests, host actions). */
|
|
15735
|
+
insertText(text, kind = "paste") {
|
|
15736
|
+
if (this.model.replaceSelection(text, kind)) {
|
|
15737
|
+
this.syncTextarea();
|
|
15738
|
+
this.afterContentChange(text.length);
|
|
15739
|
+
}
|
|
15740
|
+
}
|
|
15741
|
+
undo() {
|
|
15742
|
+
this.flushPending();
|
|
15743
|
+
if (this.model.undo()) {
|
|
15744
|
+
this.syncTextarea();
|
|
15745
|
+
this.afterContentChange(0, true);
|
|
15746
|
+
}
|
|
15747
|
+
}
|
|
15748
|
+
redo() {
|
|
15749
|
+
this.flushPending();
|
|
15750
|
+
if (this.model.redo()) {
|
|
15751
|
+
this.syncTextarea();
|
|
15752
|
+
this.afterContentChange(0, true);
|
|
15753
|
+
}
|
|
15754
|
+
}
|
|
15755
|
+
toLocal(e) {
|
|
15756
|
+
const stagePt = this.host.clientToStage(e.clientX, e.clientY);
|
|
15757
|
+
const p = this.target.getAbsoluteTransform().copy().invert().point(stagePt);
|
|
15758
|
+
const o = this.target.getContentOffset();
|
|
15759
|
+
return {
|
|
15760
|
+
x: p.x - o.x,
|
|
15761
|
+
y: p.y - o.y
|
|
15762
|
+
};
|
|
15763
|
+
}
|
|
15764
|
+
isInside(local) {
|
|
15765
|
+
const o = this.target.getContentOffset();
|
|
15766
|
+
const { width, height } = this.target.getSize();
|
|
15767
|
+
return local.x + o.x >= 0 && local.y + o.y >= 0 && local.x + o.x <= width && local.y + o.y <= height;
|
|
15768
|
+
}
|
|
15769
|
+
onMouseDown = (e) => {
|
|
15770
|
+
if (this.isInside(this.toLocal(e))) e.preventDefault();
|
|
15771
|
+
};
|
|
15772
|
+
onPointerDown = (e) => {
|
|
15773
|
+
if (e.button !== 0 && e.pointerType === "mouse") return;
|
|
15774
|
+
if (this.textarea && e.target === this.textarea) return;
|
|
15775
|
+
const local = this.toLocal(e);
|
|
15776
|
+
if (!this.isInside(local)) {
|
|
15777
|
+
e.stopPropagation();
|
|
15778
|
+
this.host.onRequestExit("outside");
|
|
15779
|
+
return;
|
|
15780
|
+
}
|
|
15781
|
+
e.stopPropagation();
|
|
15782
|
+
e.preventDefault();
|
|
15783
|
+
const offset = offsetFromPoint(this.target.getLayout(), local, this.target.measure);
|
|
15784
|
+
const text = this.model.getText();
|
|
15785
|
+
if (e.detail >= 3) {
|
|
15786
|
+
const [s, en] = paragraphRangeAt(text, offset);
|
|
15787
|
+
this.model.setSelection(s, en);
|
|
15788
|
+
} else if (e.detail === 2) {
|
|
15789
|
+
const [s, en] = wordRangeAt(text, offset);
|
|
15790
|
+
this.model.setSelection(s, en);
|
|
15791
|
+
} else {
|
|
15792
|
+
this.model.moveTo(offset, e.shiftKey);
|
|
15793
|
+
this.dragging = true;
|
|
15794
|
+
}
|
|
15795
|
+
this.syncTextarea();
|
|
15796
|
+
this.afterSelectionChange();
|
|
15797
|
+
this.textarea?.focus();
|
|
15798
|
+
};
|
|
15799
|
+
onPointerMove = (e) => {
|
|
15800
|
+
if (!this.dragging) return;
|
|
15801
|
+
const local = this.toLocal(e);
|
|
15802
|
+
const offset = offsetFromPoint(this.target.getLayout(), local, this.target.measure);
|
|
15803
|
+
this.model.moveTo(offset, true);
|
|
15804
|
+
this.syncTextarea();
|
|
15805
|
+
this.afterSelectionChange();
|
|
15806
|
+
};
|
|
15807
|
+
onPointerUp = () => {
|
|
15808
|
+
if (!this.dragging) return;
|
|
15809
|
+
this.dragging = false;
|
|
15810
|
+
this.textarea?.focus();
|
|
15811
|
+
};
|
|
15812
|
+
afterSelectionChange() {
|
|
15813
|
+
this.syncTextarea();
|
|
15814
|
+
this.positionTextarea();
|
|
15815
|
+
this.resetBlink();
|
|
15816
|
+
this.repaint();
|
|
15817
|
+
}
|
|
15818
|
+
afterContentChange(insertedLength, immediate = false) {
|
|
15819
|
+
this.flushPending();
|
|
15820
|
+
if (!immediate && insertedLength > 2e4 && typeof this.target.warmLayout === "function") {
|
|
15821
|
+
this.pendingFlush = true;
|
|
15822
|
+
this.sliceLayout();
|
|
15823
|
+
} else this.applyContent();
|
|
15824
|
+
this.resetBlink();
|
|
15825
|
+
}
|
|
15826
|
+
sliceLayout = () => {
|
|
15827
|
+
if (!this.pendingFlush || this.destroyed) return;
|
|
15828
|
+
if (this.target.warmLayout(this.model.getBlocks(), SLICE_BUDGET_MS)) this.flushPending();
|
|
15829
|
+
else this.pendingTimer = setTimeout(this.sliceLayout, 0);
|
|
15830
|
+
};
|
|
15831
|
+
flushPending() {
|
|
15832
|
+
if (!this.pendingFlush) return;
|
|
15833
|
+
this.pendingFlush = false;
|
|
15834
|
+
clearTimeout(this.pendingTimer);
|
|
15835
|
+
this.applyContent();
|
|
15836
|
+
}
|
|
15837
|
+
applyContent() {
|
|
15838
|
+
this.target.setBlocks(this.model.getBlocks());
|
|
15839
|
+
this.host.onChange?.(this.model.getBlocks());
|
|
15840
|
+
this.positionTextarea();
|
|
15841
|
+
this.target.requestRedraw();
|
|
15842
|
+
this.repaint();
|
|
15843
|
+
}
|
|
15844
|
+
createOverlay() {
|
|
15845
|
+
const layer = this.host.utilityLayer;
|
|
15846
|
+
if (!layer) return;
|
|
15847
|
+
const overlay = new Konva.Shape({
|
|
15848
|
+
name: "text-v2-edit-overlay",
|
|
15849
|
+
listening: false,
|
|
15850
|
+
sceneFunc: (ctx, shape) => this.paintOverlay(ctx, shape)
|
|
15851
|
+
});
|
|
15852
|
+
layer.add(overlay);
|
|
15853
|
+
overlay.moveToTop();
|
|
15854
|
+
this.overlay = overlay;
|
|
15855
|
+
}
|
|
15856
|
+
paintOverlay(ctx, shape) {
|
|
15857
|
+
if (this.destroyed || this.pendingFlush) return;
|
|
15858
|
+
const layout = this.target.getLayout();
|
|
15859
|
+
const abs = this.target.getAbsoluteTransform();
|
|
15860
|
+
const rel = shape.getAbsoluteTransform().copy().invert().multiply(abs);
|
|
15861
|
+
const o = this.target.getContentOffset();
|
|
15862
|
+
ctx.save();
|
|
15863
|
+
ctx.transform(rel.m[0], rel.m[1], rel.m[2], rel.m[3], rel.m[4], rel.m[5]);
|
|
15864
|
+
if (this.host.outlineColor) {
|
|
15865
|
+
const scale = Math.hypot(abs.m[0], abs.m[1]) || 1;
|
|
15866
|
+
const lw = (this.host.outlineWidth ?? 1) / scale;
|
|
15867
|
+
const { width, height } = this.target.getSize();
|
|
15868
|
+
ctx.setAttr("strokeStyle", this.host.outlineColor);
|
|
15869
|
+
ctx.setAttr("lineWidth", lw);
|
|
15870
|
+
ctx.beginPath();
|
|
15871
|
+
const pad = (this.host.outlinePadding ?? 0) / scale;
|
|
15872
|
+
ctx.rect(-pad, -pad, width + pad * 2, height + pad * 2);
|
|
15873
|
+
ctx.stroke();
|
|
15874
|
+
}
|
|
15875
|
+
ctx.translate(o.x, o.y);
|
|
15876
|
+
const { start, end } = this.model.getSelection();
|
|
15877
|
+
if (start !== end) {
|
|
15878
|
+
ctx.setAttr("fillStyle", this.host.selectionColor);
|
|
15879
|
+
for (const r of selectionRects(layout, start, end, this.target.measure)) ctx.fillRect(r.x, r.y, r.width, r.height);
|
|
15880
|
+
} else if (this.caretOn) {
|
|
15881
|
+
const c = caretRect(layout, this.model.focus, this.target.measure);
|
|
15882
|
+
const scale = Math.hypot(abs.m[0], abs.m[1]) || 1;
|
|
15883
|
+
const w = Math.max(1, 1.5) / scale;
|
|
15884
|
+
ctx.setAttr("fillStyle", this.host.caretColor);
|
|
15885
|
+
ctx.fillRect(c.x - w / 2, c.y, w, c.height);
|
|
15886
|
+
}
|
|
15887
|
+
ctx.restore();
|
|
15888
|
+
}
|
|
15889
|
+
repaint() {
|
|
15890
|
+
this.overlay?.getLayer()?.batchDraw();
|
|
15891
|
+
}
|
|
15892
|
+
startBlink() {
|
|
15893
|
+
this.blinkTimer = setInterval(() => {
|
|
15894
|
+
this.caretOn = !this.caretOn;
|
|
15895
|
+
this.repaint();
|
|
15896
|
+
}, BLINK_MS);
|
|
15897
|
+
}
|
|
15898
|
+
resetBlink() {
|
|
15899
|
+
this.caretOn = true;
|
|
15900
|
+
if (this.blinkTimer) {
|
|
15901
|
+
clearInterval(this.blinkTimer);
|
|
15902
|
+
this.startBlink();
|
|
15903
|
+
}
|
|
15904
|
+
}
|
|
15905
|
+
};
|
|
15906
|
+
function host_signal(signal, abort) {
|
|
15907
|
+
if (!signal) return;
|
|
15908
|
+
if (signal.aborted) abort.abort();
|
|
15909
|
+
else signal.addEventListener("abort", () => abort.abort(), { once: true });
|
|
15910
|
+
}
|
|
15911
|
+
//#endregion
|
|
15912
|
+
//#region src/nodes/text-v2/constants.ts
|
|
15913
|
+
/** Default threshold for `onTextSizeWarning` (chars of serialized blocks). */
|
|
15914
|
+
const WEAVE_TEXT_V2_SIZE_WARNING_THRESHOLD = 2e5;
|
|
15915
|
+
const WEAVE_TEXT_V2_SELECTION_COLOR = "rgba(66, 133, 244, 0.35)";
|
|
15916
|
+
const WEAVE_TEXT_V2_DEFAULT_CONFIG = {
|
|
15917
|
+
...WEAVE_TEXT_NODE_DEFAULT_CONFIG,
|
|
15918
|
+
edition: { outline: {
|
|
15919
|
+
enabled: true,
|
|
15920
|
+
color: "#0074ff",
|
|
15921
|
+
width: 1,
|
|
15922
|
+
padding: 0
|
|
15923
|
+
} },
|
|
15924
|
+
sizeWarning: { threshold: WEAVE_TEXT_V2_SIZE_WARNING_THRESHOLD }
|
|
15925
|
+
};
|
|
15926
|
+
//#endregion
|
|
15927
|
+
//#region src/nodes/text-v2/text-v2.ts
|
|
15928
|
+
const CORNER_ANCHORS = [
|
|
15929
|
+
"top-left",
|
|
15930
|
+
"top-right",
|
|
15931
|
+
"bottom-left",
|
|
15932
|
+
"bottom-right"
|
|
15933
|
+
];
|
|
15934
|
+
const SIDE_ANCHORS = ["middle-right", "middle-left"];
|
|
15935
|
+
const CORNER_ANCHOR_SET = new Set(CORNER_ANCHORS);
|
|
15936
|
+
const SIDE_ANCHOR_SET = new Set(SIDE_ANCHORS);
|
|
15937
|
+
/**
|
|
15938
|
+
* Drop-in replacement for `WeaveTextNode`. Same `nodeType` (`'text'`), so
|
|
15939
|
+
* existing documents load unchanged; register it INSTEAD of `WeaveTextNode`
|
|
15940
|
+
* (handlers are keyed by node type). Renders through `WeaveTextV2Shape`
|
|
15941
|
+
* (blocks/runs model + layout engine) instead of `Konva.Text`.
|
|
15942
|
+
*/
|
|
15943
|
+
var WeaveTextV2Node = class extends WeaveNode {
|
|
15944
|
+
config;
|
|
15945
|
+
nodeType = WEAVE_TEXT_NODE_TYPE;
|
|
15946
|
+
createNode = false;
|
|
15947
|
+
editing = false;
|
|
15948
|
+
editingNodeId = null;
|
|
15949
|
+
session = null;
|
|
15950
|
+
sizeWarned = false;
|
|
15951
|
+
nodeRenderedAddedRegistered = false;
|
|
15952
|
+
keyPressHandler;
|
|
15953
|
+
constructor(params) {
|
|
15954
|
+
super();
|
|
15955
|
+
this.config = (0, import_merge.default)({}, WEAVE_TEXT_V2_DEFAULT_CONFIG, params?.config);
|
|
15956
|
+
}
|
|
15957
|
+
initialize() {
|
|
15958
|
+
this.keyPressHandler = void 0;
|
|
15959
|
+
this.editing = false;
|
|
15960
|
+
this.editingNodeId = null;
|
|
15961
|
+
this.session = null;
|
|
15962
|
+
this.nodeRenderedAddedRegistered = false;
|
|
15963
|
+
}
|
|
15964
|
+
/**
|
|
15965
|
+
* Resolve width/height for the layout mode. Auto modes size from the
|
|
15966
|
+
* engine's natural measure (independent of zoom, unlike the DOM-based
|
|
15967
|
+
* measure of the legacy node).
|
|
15968
|
+
*/
|
|
15969
|
+
resolveSize(shape, props) {
|
|
15970
|
+
let width = props.width;
|
|
15971
|
+
let height = props.height;
|
|
15972
|
+
if (props.layout === TEXT_LAYOUT.AUTO_ALL) {
|
|
15973
|
+
const nat = shape.measureNaturalSize();
|
|
15974
|
+
width = nat.width + 2;
|
|
15975
|
+
height = nat.height + 2;
|
|
15976
|
+
}
|
|
15977
|
+
if (props.layout === TEXT_LAYOUT.SMART && !props.smartFixedWidth) {
|
|
15978
|
+
width = Math.max(1, shape.measureNaturalSize().width * 1.01);
|
|
15979
|
+
height = void 0;
|
|
15980
|
+
}
|
|
15981
|
+
if (props.layout === TEXT_LAYOUT.SMART && props.smartFixedWidth) height = void 0;
|
|
15982
|
+
if (props.layout === TEXT_LAYOUT.AUTO_HEIGHT) height = void 0;
|
|
15983
|
+
return {
|
|
15984
|
+
width,
|
|
15985
|
+
height
|
|
15986
|
+
};
|
|
15987
|
+
}
|
|
15988
|
+
measureMultilineText(shape) {
|
|
15989
|
+
return () => {
|
|
15990
|
+
const nat = shape.measureNaturalSize();
|
|
15991
|
+
return {
|
|
15992
|
+
width: Math.max(1, nat.width),
|
|
15993
|
+
height: Math.max(1, nat.height)
|
|
15994
|
+
};
|
|
15995
|
+
};
|
|
15996
|
+
}
|
|
15997
|
+
linkOverrides(props) {
|
|
15998
|
+
return {
|
|
15999
|
+
linkPreviousFill: props.link ? props.linkPreviousFill ?? props.fill : props.linkPreviousFill,
|
|
16000
|
+
linkPreviousTextDecoration: props.link ? props.linkPreviousTextDecoration ?? props.textDecoration : props.linkPreviousTextDecoration
|
|
16001
|
+
};
|
|
16002
|
+
}
|
|
16003
|
+
outlineProps() {
|
|
16004
|
+
return this.config.outline.enabled ? {
|
|
16005
|
+
strokeEnabled: true,
|
|
16006
|
+
stroke: this.config.outline.color,
|
|
16007
|
+
strokeWidth: this.config.outline.width,
|
|
16008
|
+
fillAfterStrokeEnabled: true
|
|
16009
|
+
} : { strokeEnabled: false };
|
|
16010
|
+
}
|
|
16011
|
+
onRender(props) {
|
|
16012
|
+
const { linkPreviousFill, linkPreviousTextDecoration } = this.linkOverrides(props);
|
|
16013
|
+
const text = new WeaveTextV2Shape({
|
|
16014
|
+
...props,
|
|
16015
|
+
name: "node",
|
|
16016
|
+
...this.outlineProps(),
|
|
16017
|
+
...props.link && {
|
|
16018
|
+
textDecoration: "underline",
|
|
16019
|
+
fill: this.config.link.defaultColor,
|
|
16020
|
+
linkPreviousFill,
|
|
16021
|
+
linkPreviousTextDecoration
|
|
16022
|
+
}
|
|
16023
|
+
});
|
|
16024
|
+
this.setupDefaultNodeAugmentation(text);
|
|
16025
|
+
const defaultTransformerProperties = this.defaultGetTransformerProperties(this.config.transform);
|
|
16026
|
+
text.getTransformerProperties = function() {
|
|
16027
|
+
const layout = this.getAttrs().layout;
|
|
16028
|
+
if (layout === TEXT_LAYOUT.SMART) return {
|
|
16029
|
+
...defaultTransformerProperties,
|
|
16030
|
+
ignoreStroke: true,
|
|
16031
|
+
resizeEnabled: true,
|
|
16032
|
+
keepRatio: false,
|
|
16033
|
+
enabledAnchors: []
|
|
16034
|
+
};
|
|
16035
|
+
if (layout === TEXT_LAYOUT.AUTO_ALL) return {
|
|
16036
|
+
...defaultTransformerProperties,
|
|
16037
|
+
ignoreStroke: true,
|
|
16038
|
+
resizeEnabled: false,
|
|
16039
|
+
enabledAnchors: []
|
|
16040
|
+
};
|
|
16041
|
+
if (layout === TEXT_LAYOUT.AUTO_HEIGHT) return {
|
|
16042
|
+
...defaultTransformerProperties,
|
|
16043
|
+
ignoreStroke: true,
|
|
16044
|
+
resizeEnabled: true,
|
|
16045
|
+
enabledAnchors: SIDE_ANCHORS
|
|
16046
|
+
};
|
|
16047
|
+
return defaultTransformerProperties;
|
|
16048
|
+
};
|
|
16049
|
+
text.allowedAnchors = function() {
|
|
16050
|
+
const layout = this.getAttrs().layout;
|
|
16051
|
+
if (layout === TEXT_LAYOUT.SMART) return [...CORNER_ANCHORS, ...SIDE_ANCHORS];
|
|
16052
|
+
if (layout === TEXT_LAYOUT.AUTO_ALL) return [];
|
|
16053
|
+
if (layout === TEXT_LAYOUT.AUTO_HEIGHT) return SIDE_ANCHORS;
|
|
16054
|
+
return [
|
|
16055
|
+
...CORNER_ANCHORS,
|
|
16056
|
+
"top-center",
|
|
16057
|
+
"bottom-center",
|
|
16058
|
+
...SIDE_ANCHORS
|
|
16059
|
+
];
|
|
16060
|
+
};
|
|
16061
|
+
text.setAttrs({ measureMultilineText: this.measureMultilineText(text) });
|
|
16062
|
+
this.setupDefaultNodeEvents(text, { performScaleReset: false });
|
|
16063
|
+
text.dblClick = () => {
|
|
16064
|
+
if (this.editing) return;
|
|
16065
|
+
if (!(this.isSelecting() && this.isNodeSelected(text))) return;
|
|
16066
|
+
this.triggerEditMode(text);
|
|
16067
|
+
};
|
|
16068
|
+
text.setAttr("triggerEditMode", this.triggerEditMode.bind(this));
|
|
16069
|
+
this.setupLinkBehavior(text);
|
|
16070
|
+
this.setupTransform(text);
|
|
16071
|
+
if (!this.nodeRenderedAddedRegistered) {
|
|
16072
|
+
this.instance.addEventListener("onNodeRenderedAdded", (node) => {
|
|
16073
|
+
if (this.editing && this.editingNodeId !== null && node.id() === this.editingNodeId && node.getAttr("cancelEditMode")) node.getAttr("cancelEditMode")?.();
|
|
16074
|
+
});
|
|
16075
|
+
this.nodeRenderedAddedRegistered = true;
|
|
16076
|
+
}
|
|
16077
|
+
this.registerKeyPress();
|
|
16078
|
+
return text;
|
|
16079
|
+
}
|
|
16080
|
+
handleKeyPress = (e) => {
|
|
16081
|
+
if (e.code !== "Enter" || this.instance.getActiveAction() !== "selectionTool" || this.editing) return;
|
|
16082
|
+
e.preventDefault();
|
|
16083
|
+
const selected = this.getNodesSelectionPlugin()?.getSelectedNodes() ?? [];
|
|
16084
|
+
if (this.isSelecting() && selected.length === 1 && selected[0].getAttrs().nodeType === "text") this.triggerEditMode(selected[0]);
|
|
16085
|
+
};
|
|
16086
|
+
registerKeyPress() {
|
|
16087
|
+
if (this.instance.isServerSide() || this.keyPressHandler) return;
|
|
16088
|
+
this.keyPressHandler = this.handleKeyPress.bind(this);
|
|
16089
|
+
window.addEventListener("keypress", this.keyPressHandler, { signal: this.instance.getEventsController().signal });
|
|
16090
|
+
}
|
|
16091
|
+
onAdd() {
|
|
16092
|
+
this.registerKeyPress();
|
|
16093
|
+
}
|
|
16094
|
+
onDestroyInstance() {
|
|
16095
|
+
super.onDestroyInstance();
|
|
16096
|
+
this.session?.destroy();
|
|
16097
|
+
this.session = null;
|
|
16098
|
+
if (!this.instance.isServerSide() && this.keyPressHandler) {
|
|
16099
|
+
window.removeEventListener("keypress", this.keyPressHandler);
|
|
16100
|
+
this.keyPressHandler = void 0;
|
|
16101
|
+
}
|
|
16102
|
+
}
|
|
16103
|
+
/** Re-apply layout-driven width/height after live content changes. */
|
|
16104
|
+
applySize(shape) {
|
|
16105
|
+
const { width, height } = this.resolveSize(shape, shape.getAttrs());
|
|
16106
|
+
shape.setAttrs({
|
|
16107
|
+
width,
|
|
16108
|
+
height
|
|
16109
|
+
});
|
|
16110
|
+
}
|
|
16111
|
+
editTarget(shape) {
|
|
16112
|
+
return {
|
|
16113
|
+
getBlocks: () => shape.getBlocks(),
|
|
16114
|
+
setBlocks: (blocks) => {
|
|
16115
|
+
shape.setBlocks(blocks);
|
|
16116
|
+
this.applySize(shape);
|
|
16117
|
+
},
|
|
16118
|
+
getLayout: () => shape.ensureLayout(),
|
|
16119
|
+
measure: (t) => shape.measureText(t),
|
|
16120
|
+
getAbsoluteTransform: () => shape.getAbsoluteTransform(),
|
|
16121
|
+
getContentOffset: () => shape.getContentOffset(),
|
|
16122
|
+
getSize: () => ({
|
|
16123
|
+
width: shape.getWidth(),
|
|
16124
|
+
height: shape.getHeight()
|
|
16125
|
+
}),
|
|
16126
|
+
getFontSize: () => shape.fontSize(),
|
|
16127
|
+
requestRedraw: () => shape.getLayer()?.batchDraw(),
|
|
16128
|
+
warmLayout: (blocks, budget) => shape.warmLayout(blocks, budget)
|
|
16129
|
+
};
|
|
16130
|
+
}
|
|
16131
|
+
triggerEditMode(shape, create = false) {
|
|
16132
|
+
if (create) this.createNode = true;
|
|
16133
|
+
if (!this.instance.setMutexLock({
|
|
16134
|
+
nodeIds: [shape.id()],
|
|
16135
|
+
operation: "text-edit"
|
|
16136
|
+
})) return;
|
|
16137
|
+
this.editing = true;
|
|
16138
|
+
this.editingNodeId = shape.id();
|
|
16139
|
+
this.sizeWarned = false;
|
|
16140
|
+
const selectionPlugin = this.instance.getPlugin("nodesSelection");
|
|
16141
|
+
if (selectionPlugin) {
|
|
16142
|
+
const tr = selectionPlugin.getTransformer();
|
|
16143
|
+
this.instance.disablePlugin("nodesSelection");
|
|
16144
|
+
tr.hide();
|
|
16145
|
+
}
|
|
16146
|
+
const stage = this.instance.getStage();
|
|
16147
|
+
const container = stage.container();
|
|
16148
|
+
const rect = () => container.getBoundingClientRect();
|
|
16149
|
+
this.session = new TextEditSession(this.editTarget(shape), {
|
|
16150
|
+
utilityLayer: this.instance.getUtilityLayer(),
|
|
16151
|
+
container,
|
|
16152
|
+
clientToStage: (cx, cy) => {
|
|
16153
|
+
const r = rect();
|
|
16154
|
+
const sx = r.width ? stage.width() / r.width : 1;
|
|
16155
|
+
const sy = r.height ? stage.height() / r.height : 1;
|
|
16156
|
+
return {
|
|
16157
|
+
x: (cx - r.left) * sx,
|
|
16158
|
+
y: (cy - r.top) * sy
|
|
16159
|
+
};
|
|
16160
|
+
},
|
|
16161
|
+
stageToContainer: (p) => {
|
|
16162
|
+
const r = rect();
|
|
16163
|
+
const sx = r.width ? r.width / stage.width() : 1;
|
|
16164
|
+
const sy = r.height ? r.height / stage.height() : 1;
|
|
16165
|
+
return {
|
|
16166
|
+
x: p.x * sx,
|
|
16167
|
+
y: p.y * sy
|
|
16168
|
+
};
|
|
16169
|
+
},
|
|
16170
|
+
selectAllOnStart: !create,
|
|
16171
|
+
caretColor: this.config.cursor.color,
|
|
16172
|
+
selectionColor: WEAVE_TEXT_V2_SELECTION_COLOR,
|
|
16173
|
+
...this.config.edition.outline.enabled && {
|
|
16174
|
+
outlineColor: this.config.edition.outline.color,
|
|
16175
|
+
outlineWidth: this.config.edition.outline.width,
|
|
16176
|
+
outlinePadding: this.config.edition.outline.padding
|
|
16177
|
+
},
|
|
16178
|
+
signal: this.instance.getEventsController().signal,
|
|
16179
|
+
onChange: (blocks) => this.checkSize(shape, blocks),
|
|
16180
|
+
onRequestExit: () => this.exitEditMode(shape)
|
|
16181
|
+
}, shape.id());
|
|
16182
|
+
shape.setAttr("cancelEditMode", () => this.exitEditMode(shape));
|
|
16183
|
+
stage.mode(WEAVE_STAGE_TEXT_EDITION_MODE);
|
|
16184
|
+
this.session.start();
|
|
16185
|
+
this.instance.emitEvent("onEnterTextNodeEditMode", { node: shape });
|
|
16186
|
+
}
|
|
16187
|
+
checkSize(shape, blocks) {
|
|
16188
|
+
const threshold = this.config.sizeWarning.threshold;
|
|
16189
|
+
if (blocksToText(blocks).length * 8 < threshold) {
|
|
16190
|
+
this.sizeWarned = false;
|
|
16191
|
+
return;
|
|
16192
|
+
}
|
|
16193
|
+
const size = JSON.stringify(blocks).length;
|
|
16194
|
+
if (size > threshold && !this.sizeWarned) {
|
|
16195
|
+
this.sizeWarned = true;
|
|
16196
|
+
this.instance.emitEvent("onTextSizeWarning", {
|
|
16197
|
+
node: shape,
|
|
16198
|
+
size,
|
|
16199
|
+
threshold
|
|
16200
|
+
});
|
|
16201
|
+
} else if (size <= threshold) this.sizeWarned = false;
|
|
16202
|
+
}
|
|
16203
|
+
exitEditMode(shape) {
|
|
16204
|
+
const session = this.session;
|
|
16205
|
+
if (!session || !this.editing) return;
|
|
16206
|
+
const { changed } = session.getResult();
|
|
16207
|
+
session.destroy();
|
|
16208
|
+
this.session = null;
|
|
16209
|
+
this.instance.releaseMutexLock();
|
|
16210
|
+
const stage = this.instance.getStage();
|
|
16211
|
+
stage.mode(WEAVE_STAGE_DEFAULT_MODE);
|
|
16212
|
+
this.editing = false;
|
|
16213
|
+
this.editingNodeId = null;
|
|
16214
|
+
shape.setAttr("cancelEditMode", void 0);
|
|
16215
|
+
this.commitNode(shape, changed);
|
|
16216
|
+
const liveNode = stage.findOne(`#${shape.id()}`) ?? shape;
|
|
16217
|
+
const selectionPlugin = this.instance.getPlugin("nodesSelection");
|
|
16218
|
+
if (selectionPlugin) {
|
|
16219
|
+
this.instance.enablePlugin("nodesSelection");
|
|
16220
|
+
selectionPlugin.setSelectedNodes([liveNode]);
|
|
16221
|
+
this.instance.triggerAction(SELECTION_TOOL_ACTION_NAME);
|
|
16222
|
+
}
|
|
16223
|
+
stage.container().tabIndex = 1;
|
|
16224
|
+
stage.container().click();
|
|
16225
|
+
stage.container().focus();
|
|
16226
|
+
this.instance.emitEvent("onExitTextNodeEditMode", { node: liveNode });
|
|
16227
|
+
}
|
|
16228
|
+
/** One `updateNode` per session; none when content is unchanged. */
|
|
16229
|
+
commitNode(shape, changed) {
|
|
16230
|
+
const live = this.instance.getStage().findOne(`#${shape.id()}`) ?? shape;
|
|
16231
|
+
if (this.createNode) {
|
|
16232
|
+
const serialized = this.serialize(live);
|
|
16233
|
+
if (live.text() !== "") {
|
|
16234
|
+
const container = live.getParent();
|
|
16235
|
+
live.destroy();
|
|
16236
|
+
this.instance.addNode(serialized, container?.getAttrs().id);
|
|
16237
|
+
} else live.destroy();
|
|
16238
|
+
} else if (changed) this.instance.updateNode(this.serialize(live));
|
|
16239
|
+
this.createNode = false;
|
|
16240
|
+
}
|
|
16241
|
+
setupTransform(text) {
|
|
16242
|
+
let actualAnchor = void 0;
|
|
16243
|
+
const isSide = () => SIDE_ANCHOR_SET.has(actualAnchor ?? "");
|
|
16244
|
+
const isCorner = () => CORNER_ANCHOR_SET.has(actualAnchor ?? "");
|
|
16245
|
+
const autoWidthLayout = () => [TEXT_LAYOUT.AUTO_HEIGHT, TEXT_LAYOUT.SMART].includes(text.getAttrs().layout);
|
|
16246
|
+
text.on("transformstart", (e) => {
|
|
16247
|
+
const ctrlOrMeta = e.evt.ctrlKey || e.evt.metaKey;
|
|
16248
|
+
this.instance.emitEvent("onTransform", e.target);
|
|
16249
|
+
actualAnchor = this.getNodesSelectionPlugin()?.getTransformer()?.getActiveAnchor();
|
|
16250
|
+
const layout = text.getAttrs().layout;
|
|
16251
|
+
this.getNodesSelectionPlugin()?.getTransformer()?.keepRatio(layout === TEXT_LAYOUT.SMART && isCorner() || layout === TEXT_LAYOUT.FIXED && ctrlOrMeta);
|
|
16252
|
+
if (autoWidthLayout() && isSide()) {
|
|
16253
|
+
text.wrap("word");
|
|
16254
|
+
text.scaleY(1);
|
|
16255
|
+
text.height(void 0);
|
|
16256
|
+
}
|
|
16257
|
+
e.cancelBubble = true;
|
|
16258
|
+
});
|
|
16259
|
+
text.on("transform", () => {
|
|
16260
|
+
if (autoWidthLayout() && isSide()) {
|
|
16261
|
+
text.width(text.width() * text.scaleX());
|
|
16262
|
+
text.scaleX(1);
|
|
16263
|
+
text.scaleY(1);
|
|
16264
|
+
text.height(void 0);
|
|
16265
|
+
text.getLayer()?.batchDraw();
|
|
16266
|
+
}
|
|
16267
|
+
const layout = text.getAttrs().layout;
|
|
16268
|
+
if (this.isSelecting() && this.isNodeSelected(text) && (layout !== TEXT_LAYOUT.SMART || !isCorner())) {
|
|
16269
|
+
text.width(text.width() * text.scaleX());
|
|
16270
|
+
resetScale(text);
|
|
16271
|
+
text.fontSize(text.fontSize() * text.scaleY());
|
|
16272
|
+
}
|
|
16273
|
+
text.setAttr("shouldUpdateOnTransform", false);
|
|
16274
|
+
text.getLayer()?.batchDraw();
|
|
16275
|
+
});
|
|
16276
|
+
text.on("transformend", () => {
|
|
16277
|
+
this.instance.emitEvent("onTransform", null);
|
|
16278
|
+
let definedSmartWidth = false;
|
|
16279
|
+
let smartFixedWidth = text.getAttr("smartFixedWidth") ?? false;
|
|
16280
|
+
const layout = text.getAttrs().layout;
|
|
16281
|
+
if (layout !== TEXT_LAYOUT.SMART && !isCorner()) this.scaleReset(text);
|
|
16282
|
+
if (layout === TEXT_LAYOUT.SMART && isCorner()) text.setAttrs({
|
|
16283
|
+
width: Math.ceil(text.width() * text.scaleX()),
|
|
16284
|
+
height: Math.ceil(text.height() * text.scaleY()),
|
|
16285
|
+
fontSize: text.fontSize() * text.scaleY(),
|
|
16286
|
+
scaleX: 1,
|
|
16287
|
+
scaleY: 1
|
|
16288
|
+
});
|
|
16289
|
+
if (autoWidthLayout() && isSide() && !smartFixedWidth && !definedSmartWidth) {
|
|
16290
|
+
text.setAttr("smartFixedWidth", true);
|
|
16291
|
+
smartFixedWidth = true;
|
|
16292
|
+
definedSmartWidth = true;
|
|
16293
|
+
text.width(Math.ceil(text.width() * text.scaleX()));
|
|
16294
|
+
text.scaleX(1);
|
|
16295
|
+
text.scaleY(1);
|
|
16296
|
+
text.height(void 0);
|
|
16297
|
+
text.getLayer()?.batchDraw();
|
|
16298
|
+
text.height(Math.ceil(text.height()));
|
|
16299
|
+
}
|
|
16300
|
+
if (layout === TEXT_LAYOUT.SMART && isSide() && smartFixedWidth && !definedSmartWidth) {
|
|
16301
|
+
text.width(Math.ceil(text.width() * text.scaleX()));
|
|
16302
|
+
text.scaleX(1);
|
|
16303
|
+
}
|
|
16304
|
+
this.instance.updateNode(this.serialize(text));
|
|
16305
|
+
});
|
|
16306
|
+
}
|
|
16307
|
+
onUpdate(nodeInstance, nextProps) {
|
|
16308
|
+
const shape = nodeInstance;
|
|
16309
|
+
if (this.editing && this.editingNodeId === shape.id()) nextProps = {
|
|
16310
|
+
...nextProps,
|
|
16311
|
+
text: shape.attrs.text,
|
|
16312
|
+
blocks: shape.attrs.blocks
|
|
16313
|
+
};
|
|
16314
|
+
const isLinked = Boolean(nextProps.link);
|
|
16315
|
+
const { linkPreviousFill, linkPreviousTextDecoration } = this.linkOverrides(nextProps);
|
|
16316
|
+
shape.setAttrs({
|
|
16317
|
+
...nextProps,
|
|
16318
|
+
...this.outlineProps(),
|
|
16319
|
+
link: nextProps.link,
|
|
16320
|
+
linkPreviousFill: isLinked ? linkPreviousFill : void 0,
|
|
16321
|
+
linkPreviousTextDecoration: isLinked ? linkPreviousTextDecoration : void 0,
|
|
16322
|
+
...isLinked && {
|
|
16323
|
+
textDecoration: "underline",
|
|
16324
|
+
fill: this.config.link.defaultColor
|
|
16325
|
+
}
|
|
16326
|
+
});
|
|
16327
|
+
if (nextProps.blocks === void 0 && shape.attrs.blocks !== void 0) shape.setAttr("blocks", void 0);
|
|
16328
|
+
const { width, height } = this.resolveSize(shape, nextProps);
|
|
16329
|
+
shape.setAttrs({
|
|
16330
|
+
width,
|
|
16331
|
+
height
|
|
16332
|
+
});
|
|
16333
|
+
if (!this.editing) {
|
|
16334
|
+
const selection = this.getNodesSelectionPlugin();
|
|
16335
|
+
if (selection) selection.setSelectedNodes(selection.getSelectedNodes());
|
|
16336
|
+
}
|
|
16337
|
+
}
|
|
16338
|
+
serialize(instance) {
|
|
16339
|
+
const attrs = instance.getAttrs();
|
|
16340
|
+
const cleanedAttrs = { ...attrs };
|
|
16341
|
+
for (const key of Object.keys(cleanedAttrs)) if (typeof cleanedAttrs[key] === "function") delete cleanedAttrs[key];
|
|
16342
|
+
delete cleanedAttrs.mutexLocked;
|
|
16343
|
+
delete cleanedAttrs.mutexUserId;
|
|
16344
|
+
delete cleanedAttrs.draggable;
|
|
16345
|
+
delete cleanedAttrs.triggerEditMode;
|
|
16346
|
+
delete cleanedAttrs.cancelEditMode;
|
|
16347
|
+
delete cleanedAttrs.measureMultilineText;
|
|
16348
|
+
delete cleanedAttrs.overridesMouseControl;
|
|
16349
|
+
delete cleanedAttrs.shouldUpdateOnTransform;
|
|
16350
|
+
delete cleanedAttrs.dragBoundFunc;
|
|
16351
|
+
if (cleanedAttrs.link) {
|
|
16352
|
+
if (typeof cleanedAttrs.linkPreviousFill === "string") cleanedAttrs.fill = cleanedAttrs.linkPreviousFill;
|
|
16353
|
+
if (typeof cleanedAttrs.linkPreviousTextDecoration === "string") cleanedAttrs.textDecoration = cleanedAttrs.linkPreviousTextDecoration;
|
|
16354
|
+
}
|
|
16355
|
+
return {
|
|
16356
|
+
key: attrs.id ?? "",
|
|
16357
|
+
type: attrs.nodeType,
|
|
16358
|
+
props: {
|
|
16359
|
+
...cleanedAttrs,
|
|
16360
|
+
isCloned: void 0,
|
|
16361
|
+
isCloneOrigin: void 0,
|
|
16362
|
+
id: attrs.id ?? "",
|
|
16363
|
+
nodeType: attrs.nodeType,
|
|
16364
|
+
children: []
|
|
16365
|
+
}
|
|
16366
|
+
};
|
|
16367
|
+
}
|
|
16368
|
+
setupLinkBehavior(text) {
|
|
16369
|
+
text.handleMouseover = () => {
|
|
16370
|
+
if (!text.getAttrs().link) return;
|
|
16371
|
+
text.fill(this.config.link.hoverColor);
|
|
16372
|
+
text.getLayer()?.batchDraw();
|
|
16373
|
+
};
|
|
16374
|
+
text.handleMouseout = () => {
|
|
16375
|
+
if (!text.getAttrs().link) return;
|
|
16376
|
+
text.fill(this.config.link.defaultColor);
|
|
16377
|
+
text.getLayer()?.batchDraw();
|
|
16378
|
+
};
|
|
16379
|
+
text.defineMousePointer = () => text.getAttrs().link ? "pointer" : "default";
|
|
16380
|
+
let linkClickTimeout;
|
|
16381
|
+
const clearLinkClickTimeout = () => {
|
|
16382
|
+
if (linkClickTimeout) {
|
|
16383
|
+
clearTimeout(linkClickTimeout);
|
|
16384
|
+
linkClickTimeout = void 0;
|
|
16385
|
+
}
|
|
16386
|
+
};
|
|
16387
|
+
const previousDblClick = text.dblClick?.bind(text) ?? (() => {});
|
|
16388
|
+
text.dblClick = () => {
|
|
16389
|
+
clearLinkClickTimeout();
|
|
16390
|
+
previousDblClick();
|
|
16391
|
+
};
|
|
16392
|
+
text.click = ({ wasSelected, ctrlOrMetaPressed }) => {
|
|
16393
|
+
if (!text.getAttrs().link) return;
|
|
16394
|
+
if (ctrlOrMetaPressed) {
|
|
16395
|
+
clearLinkClickTimeout();
|
|
16396
|
+
this.openTextLink(text);
|
|
16397
|
+
return;
|
|
16398
|
+
}
|
|
16399
|
+
if (wasSelected) {
|
|
16400
|
+
clearLinkClickTimeout();
|
|
16401
|
+
linkClickTimeout = setTimeout(() => {
|
|
16402
|
+
linkClickTimeout = void 0;
|
|
16403
|
+
this.openTextLink(text);
|
|
16404
|
+
}, Konva.dblClickWindow);
|
|
16405
|
+
}
|
|
16406
|
+
};
|
|
16407
|
+
}
|
|
16408
|
+
openTextLink(textNode) {
|
|
16409
|
+
if (this.instance.isServerSide()) return;
|
|
16410
|
+
const link = textNode.getAttrs().link;
|
|
16411
|
+
if (typeof link !== "string" || !isValidTextLink(link)) return;
|
|
16412
|
+
window.open(link, "_blank", "noopener,noreferrer");
|
|
16413
|
+
}
|
|
16414
|
+
getLink(nodeInstance) {
|
|
16415
|
+
const link = nodeInstance.getAttrs().link;
|
|
16416
|
+
return typeof link === "string" ? link : void 0;
|
|
16417
|
+
}
|
|
16418
|
+
setLink(nodeInstance, url) {
|
|
16419
|
+
if (!isValidTextLink(url)) return;
|
|
16420
|
+
const serialized = this.serialize(nodeInstance);
|
|
16421
|
+
serialized.props.linkPreviousFill = serialized.props.fill;
|
|
16422
|
+
serialized.props.linkPreviousTextDecoration = serialized.props.textDecoration;
|
|
16423
|
+
serialized.props.link = url;
|
|
16424
|
+
this.instance.updateNode(serialized);
|
|
16425
|
+
}
|
|
16426
|
+
removeLink(nodeInstance) {
|
|
16427
|
+
const serialized = this.serialize(nodeInstance);
|
|
16428
|
+
delete serialized.props.link;
|
|
16429
|
+
delete serialized.props.linkPreviousFill;
|
|
16430
|
+
delete serialized.props.linkPreviousTextDecoration;
|
|
16431
|
+
this.instance.updateNode(serialized);
|
|
16432
|
+
}
|
|
16433
|
+
resetSmartLayout(textNode) {
|
|
16434
|
+
textNode.setAttr("smartFixedWidth", void 0);
|
|
16435
|
+
textNode.width(Math.max(1, textNode.measureNaturalSize().width * 1.01));
|
|
16436
|
+
this.instance.updateNode(this.serialize(textNode));
|
|
16437
|
+
}
|
|
16438
|
+
static defaultState(nodeId, params) {
|
|
16439
|
+
return WeaveTextNode.defaultState(nodeId, params);
|
|
16440
|
+
}
|
|
16441
|
+
static addNodeState(defaultNodeState, props) {
|
|
16442
|
+
return mergeExceptArrays(WeaveTextNode.addNodeState(defaultNodeState, props), { props: { ...props.blocks && { blocks: props.blocks } } });
|
|
16443
|
+
}
|
|
16444
|
+
static updateNodeState(prevNodeState, nextProps) {
|
|
16445
|
+
const next = WeaveTextNode.updateNodeState(prevNodeState, nextProps);
|
|
16446
|
+
if (nextProps.blocks) next.props.blocks = nextProps.blocks;
|
|
16447
|
+
else delete next.props.blocks;
|
|
16448
|
+
return next;
|
|
16449
|
+
}
|
|
16450
|
+
static getSchema() {
|
|
16451
|
+
return WeaveTextNode.getSchema();
|
|
16452
|
+
}
|
|
16453
|
+
};
|
|
16454
|
+
//#endregion
|
|
14399
16455
|
//#region src/nodes/image/constants.ts
|
|
14400
16456
|
const WEAVE_IMAGE_NODE_TYPE = "image";
|
|
14401
16457
|
const WEAVE_STAGE_IMAGE_CROPPING_MODE = "image-cropping";
|
|
@@ -33564,4 +35620,4 @@ function getJSONFromYjsBinary(actualState) {
|
|
|
33564
35620
|
return JSON.parse(actualStateString);
|
|
33565
35621
|
}
|
|
33566
35622
|
//#endregion
|
|
33567
|
-
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 };
|
|
35623
|
+
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 };
|