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