@inditextech/weave-sdk 5.7.0 → 5.7.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/types.js CHANGED
@@ -9579,7 +9579,7 @@ var WeaveRegisterManager = class {
9579
9579
  };
9580
9580
  //#endregion
9581
9581
  //#region package.json
9582
- var version = "5.7.0";
9582
+ var version = "5.7.1";
9583
9583
  //#endregion
9584
9584
  //#region src/managers/setup.ts
9585
9585
  var WeaveSetupManager = class {
@@ -12562,6 +12562,12 @@ function isSafeLink(value) {
12562
12562
  return false;
12563
12563
  }
12564
12564
  }
12565
+ /** Marks an empty block carries: normalized and never a link. */
12566
+ function emptyBlockMarks(m) {
12567
+ if (!m) return void 0;
12568
+ const { link: _link, ...rest } = m;
12569
+ return normalizeMarks(rest);
12570
+ }
12565
12571
  /** Block fields that describe structure (not content). */
12566
12572
  function blockFields(block, withStart) {
12567
12573
  const out = {};
@@ -12639,6 +12645,76 @@ function sliceRuns(runs, from, to) {
12639
12645
  }
12640
12646
  return out;
12641
12647
  }
12648
+ /** Blocks covering the global range [start, end), keeping structure and marks. */
12649
+ function sliceBlocks(blocks, start, end) {
12650
+ const out = [];
12651
+ let pos = 0;
12652
+ for (const block of blocks) {
12653
+ const len = blockText(block).length;
12654
+ const bStart = pos;
12655
+ const bEnd = pos + len;
12656
+ pos = bEnd + 1;
12657
+ if (bEnd < start || bStart > end) continue;
12658
+ const runs = normalizeRuns(sliceRuns(block.runs, Math.max(start, bStart) - bStart, end - bStart));
12659
+ out.push({
12660
+ ...blockFields(block, true),
12661
+ runs,
12662
+ ...runs.length === 0 && block.m ? { m: block.m } : {}
12663
+ });
12664
+ }
12665
+ return out;
12666
+ }
12667
+ /**
12668
+ * Replace the global range [start, end) with a fragment of blocks. A single
12669
+ * block fragment merges inline; otherwise first/last fragment blocks merge
12670
+ * with the text before/after the range.
12671
+ */
12672
+ function insertBlocks(blocks, start, end, fragment) {
12673
+ if (fragment.length === 0) return blocks;
12674
+ const total = blocksToText(blocks).length;
12675
+ start = Math.max(0, Math.min(start, total));
12676
+ end = Math.max(start, Math.min(end, total));
12677
+ const head = sliceBlocks(blocks, 0, start);
12678
+ const tail = sliceBlocks(blocks, end, total);
12679
+ const before = head.pop() ?? { runs: [] };
12680
+ const after = tail.shift() ?? { runs: [] };
12681
+ const first = fragment[0];
12682
+ const last = fragment[fragment.length - 1];
12683
+ const finish = (b) => {
12684
+ const runs = normalizeRuns(b.runs);
12685
+ const { m: _m, ...rest } = b;
12686
+ return {
12687
+ ...rest,
12688
+ runs,
12689
+ ...runs.length === 0 && b.m ? { m: b.m } : {}
12690
+ };
12691
+ };
12692
+ if (fragment.length === 1) return [
12693
+ ...head,
12694
+ finish({
12695
+ ...before,
12696
+ runs: [
12697
+ ...before.runs,
12698
+ ...first.runs,
12699
+ ...after.runs
12700
+ ]
12701
+ }),
12702
+ ...tail
12703
+ ];
12704
+ return [
12705
+ ...head,
12706
+ finish({
12707
+ ...before,
12708
+ runs: [...before.runs, ...first.runs]
12709
+ }),
12710
+ ...fragment.slice(1, -1).map(finish),
12711
+ finish({
12712
+ ...last,
12713
+ runs: [...last.runs, ...after.runs]
12714
+ }),
12715
+ ...tail
12716
+ ];
12717
+ }
12642
12718
  /** Marks of the run at (or immediately before) a local offset. */
12643
12719
  function marksAt(runs, offset) {
12644
12720
  let pos = 0;
@@ -12679,7 +12755,7 @@ function replaceRange(blocks, start, end, insert, insertMarks) {
12679
12755
  const local = start - bStart;
12680
12756
  before = sliceRuns(block.runs, 0, local);
12681
12757
  startBlock = block;
12682
- inheritedMarks = marksAt(block.runs, local);
12758
+ inheritedMarks = len === 0 ? block.m : marksAt(block.runs, local);
12683
12759
  }
12684
12760
  if (end >= bStart && end <= bEnd) after = sliceRuns(block.runs, end - bStart, len);
12685
12761
  }
@@ -12695,9 +12771,12 @@ function replaceRange(blocks, start, end, insert, insertMarks) {
12695
12771
  let runs = mk(line);
12696
12772
  if (idx === 0) runs = [...before, ...runs];
12697
12773
  if (idx === lines.length - 1) runs = [...runs, ...after];
12774
+ const normalized = normalizeRuns(runs);
12775
+ const carried = emptyBlockMarks(inheritedMarks);
12698
12776
  middle.push({
12699
12777
  ...blockFields(startBlock, idx === 0),
12700
- runs: normalizeRuns(runs)
12778
+ runs: normalized,
12779
+ ...normalized.length === 0 && carried ? { m: carried } : {}
12701
12780
  });
12702
12781
  });
12703
12782
  return [
@@ -12761,7 +12840,11 @@ function blockIndicesInRange(blocks, start, end) {
12761
12840
  }
12762
12841
  /** Marks that typing at a collapsed offset would inherit. */
12763
12842
  function marksAtOffset(blocks, offset) {
12764
- for (const s of blockSpans(blocks)) if (offset >= s.start && offset <= s.end) return cleanMarks(marksAt(blocks[s.index].runs, offset - s.start));
12843
+ for (const s of blockSpans(blocks)) if (offset >= s.start && offset <= s.end) {
12844
+ const block = blocks[s.index];
12845
+ if (block.runs.length === 0) return cleanMarks(block.m);
12846
+ return cleanMarks(marksAt(block.runs, offset - s.start));
12847
+ }
12765
12848
  }
12766
12849
  function mapRunsInRange(block, from, to, fn) {
12767
12850
  const runs = [];
@@ -12794,6 +12877,26 @@ function mapRunsInRange(block, from, to, fn) {
12794
12877
  runs: normalizeRuns(runs)
12795
12878
  };
12796
12879
  }
12880
+ function withEmptyMark(block, key, value) {
12881
+ const { m: _m, ...rest } = block;
12882
+ const m = normalizeMarks({
12883
+ ...block.m,
12884
+ [key]: value
12885
+ });
12886
+ return m ? {
12887
+ ...rest,
12888
+ m
12889
+ } : rest;
12890
+ }
12891
+ /**
12892
+ * Set (or remove with `undefined`) one mark on the empty block holding the
12893
+ * collapsed offset. Other blocks are returned untouched.
12894
+ */
12895
+ function applyMarkToEmptyBlock(blocks, offset, key, value) {
12896
+ const span = blockSpans(blocks).find((s) => offset >= s.start && offset <= s.end);
12897
+ if (!span || blocks[span.index].runs.length > 0) return blocks;
12898
+ return blocks.map((b, i) => i === span.index ? withEmptyMark(b, key, value) : b);
12899
+ }
12797
12900
  /** Set (or remove with `undefined`) one mark over the global range. */
12798
12901
  function applyMark(blocks, start, end, key, value) {
12799
12902
  if (start >= end) return blocks;
@@ -12806,6 +12909,7 @@ function applyMark(blocks, start, end, key, value) {
12806
12909
  return blocks.map((block, i) => {
12807
12910
  const s = spans[i];
12808
12911
  if (s.end <= start || s.start >= end) return block;
12912
+ if (block.runs.length === 0) return withEmptyMark(block, key, value);
12809
12913
  return mapRunsInRange(block, Math.max(start, s.start) - s.start, Math.min(end, s.end) - s.start, (m) => normalizeMarks({
12810
12914
  ...m,
12811
12915
  [key]: value
@@ -13031,6 +13135,44 @@ function alignQuery(blocks, start, end) {
13031
13135
  value: set[0]
13032
13136
  };
13033
13137
  }
13138
+ const WEAVE_TEXT_MIME = "application/x-weave-text+json";
13139
+ /** Defensive parse of a clipboard payload; undefined when invalid. */
13140
+ function parseWeaveClipboard(raw) {
13141
+ if (!raw) return void 0;
13142
+ try {
13143
+ const data = JSON.parse(raw);
13144
+ if (data?.v !== 1 || !Array.isArray(data.blocks) || !data.blocks.length) return;
13145
+ const out = [];
13146
+ for (const b of data.blocks.slice(0, 5e3)) {
13147
+ if (!b || !Array.isArray(b.runs)) return void 0;
13148
+ const runs = [];
13149
+ for (const r of b.runs) {
13150
+ if (typeof r?.t !== "string") return void 0;
13151
+ const m = cleanMarks(r.m && typeof r.m === "object" ? { ...r.m } : void 0);
13152
+ if (m && "link" in m && !isSafeLink(m.link)) delete m.link;
13153
+ runs.push({
13154
+ t: r.t,
13155
+ ...m && Object.keys(m).length ? { m } : {}
13156
+ });
13157
+ }
13158
+ const block = { runs: normalizeRuns(runs) };
13159
+ if (b.kind === "ul" || b.kind === "ol") block.kind = b.kind;
13160
+ if (Number.isInteger(b.depth) && b.depth > 0) block.depth = b.depth;
13161
+ if (Number.isInteger(b.start)) block.start = b.start;
13162
+ if ([
13163
+ "left",
13164
+ "center",
13165
+ "right"
13166
+ ].includes(b.align)) block.align = b.align;
13167
+ const em = cleanMarks(b.m && typeof b.m === "object" ? b.m : void 0);
13168
+ if (!block.runs.length && em) block.m = em;
13169
+ out.push(block);
13170
+ }
13171
+ return out;
13172
+ } catch {
13173
+ return;
13174
+ }
13175
+ }
13034
13176
  //#endregion
13035
13177
  //#region src/nodes/text-v2/layout/engine.ts
13036
13178
  const DEFAULT_INDENT_STEP = 1.5;
@@ -13179,7 +13321,7 @@ var TextLayoutEngine = class {
13179
13321
  return Math.min(wanted, limit);
13180
13322
  }
13181
13323
  sigFor(block, style, fixedWidth, maxWidth, indent) {
13182
- return `${fontString(style)}|${style.lineHeight}|${style.letterSpacing}|${style.wrap}|${fixedWidth ? maxWidth - indent : "auto"}|${JSON.stringify(block.runs)}`;
13324
+ return `${fontString(style)}|${style.lineHeight}|${style.letterSpacing}|${style.wrap}|${fixedWidth ? maxWidth - indent : "auto"}|${JSON.stringify(block.runs)}|${block.runs.length === 0 ? JSON.stringify(block.m ?? null) : ""}`;
13183
13325
  }
13184
13326
  /**
13185
13327
  * Pre-compute paragraph layouts for `blocks` within a time budget so a
@@ -13276,7 +13418,7 @@ var TextLayoutEngine = class {
13276
13418
  const startChar = entry.graphemes.slice(0, pl.startG).join("").length;
13277
13419
  let marker;
13278
13420
  if (first && opts.marker) {
13279
- const m0 = block.runs[0]?.m;
13421
+ const m0 = block.runs[0]?.m ?? block.m;
13280
13422
  const rs = runStyle(style, m0);
13281
13423
  const stepPx = Math.min(indent, this.stepPx(style));
13282
13424
  marker = {
@@ -13390,9 +13532,10 @@ var TextLayoutEngine = class {
13390
13532
  desc = Math.max(desc, m.descent);
13391
13533
  }
13392
13534
  if (seen.size === 0) {
13393
- size = style.fontSize;
13394
- height = style.lineHeight * size;
13395
- const m = this.metrics(baseFont, style.fontSize);
13535
+ const rs = runStyle(style, entry.emptyMarks);
13536
+ size = rs.fontSize;
13537
+ height = (runLineHeight(entry.emptyMarks) ?? style.lineHeight) * size;
13538
+ const m = this.metrics(entry.emptyMarks ? fontString(rs) : baseFont, size);
13396
13539
  asc = m.ascent;
13397
13540
  desc = m.descent;
13398
13541
  }
@@ -13412,6 +13555,7 @@ var TextLayoutEngine = class {
13412
13555
  fonts,
13413
13556
  lineHeights: block.runs.map((r) => runLineHeight(r.m) ?? style.lineHeight),
13414
13557
  sizes,
13558
+ ...block.runs.length === 0 && block.m ? { emptyMarks: block.m } : {},
13415
13559
  lines: []
13416
13560
  };
13417
13561
  const n = graphemes.length;
@@ -14467,6 +14611,10 @@ var TextEditModel = class {
14467
14611
  const { start, end } = this.getSelection();
14468
14612
  return this.getText().slice(start, end);
14469
14613
  }
14614
+ getSelectedBlocks() {
14615
+ const { start, end } = this.getSelection();
14616
+ return sliceBlocks(this.blocks, start, end);
14617
+ }
14470
14618
  canUndo() {
14471
14619
  return this.undoStack.length > 0;
14472
14620
  }
@@ -14541,6 +14689,19 @@ var TextEditModel = class {
14541
14689
  this.desiredX = null;
14542
14690
  return true;
14543
14691
  }
14692
+ /** Replace the selection with rich blocks; caret ends after them. */
14693
+ replaceSelectionWithBlocks(fragment, kind) {
14694
+ if (fragment.length === 0) return false;
14695
+ const { start, end } = this.getSelection();
14696
+ const insertedLen = blocksToText(fragment).length;
14697
+ this.record(kind, start);
14698
+ this.blocks = insertBlocks(this.blocks, start, end, fragment);
14699
+ this.storedMarks = null;
14700
+ this.anchor = this.focus = start + insertedLen;
14701
+ this.lastEditEnd = this.focus;
14702
+ this.desiredX = null;
14703
+ return true;
14704
+ }
14544
14705
  replaceSelection(text, kind) {
14545
14706
  const { start, end } = this.getSelection();
14546
14707
  return this.replaceRange(start, end, text, kind);
@@ -14596,6 +14757,12 @@ var TextEditModel = class {
14596
14757
  if (value === void 0 || value === false) delete base[key];
14597
14758
  else base[key] = value;
14598
14759
  this.storedMarks = cleanMarks(base) ?? {};
14760
+ const next = applyMarkToEmptyBlock(this.blocks, start, key, value);
14761
+ if (next !== this.blocks) {
14762
+ const stored = this.storedMarks;
14763
+ this.commitFormat(next);
14764
+ this.storedMarks = stored;
14765
+ }
14599
14766
  return true;
14600
14767
  }
14601
14768
  return this.commitFormat(applyMark(this.blocks, start, end, key, value));
@@ -14667,7 +14834,7 @@ var TextEditModel = class {
14667
14834
  function outdentToPlain(blocks, index) {
14668
14835
  return blocks.map((b, i) => {
14669
14836
  if (i !== index) return b;
14670
- const { kind: _k, depth: _d, start: _s, ...rest } = b;
14837
+ const { kind: _k, depth: _d, start: _s, m: _m, ...rest } = b;
14671
14838
  return rest;
14672
14839
  });
14673
14840
  }
@@ -14708,6 +14875,8 @@ var TextEditSession = class {
14708
14875
  ta.addEventListener("beforeinput", this.onBeforeInput, opts);
14709
14876
  ta.addEventListener("input", this.onInput, opts);
14710
14877
  ta.addEventListener("paste", this.onPaste, opts);
14878
+ ta.addEventListener("copy", this.onCopy, opts);
14879
+ ta.addEventListener("cut", this.onCut, opts);
14711
14880
  ta.addEventListener("compositionstart", this.onCompositionStart, opts);
14712
14881
  ta.addEventListener("compositionend", this.onCompositionEnd, opts);
14713
14882
  ta.addEventListener("select", this.syncFromTextarea, opts);
@@ -14954,9 +15123,43 @@ var TextEditSession = class {
14954
15123
  if (inputType === "insertFromPaste") return "paste";
14955
15124
  return "type";
14956
15125
  }
15126
+ writeRich(e) {
15127
+ const { start, end } = this.model.getSelection();
15128
+ if (start === end || !this.host.richText || !e.clipboardData) return false;
15129
+ e.clipboardData.setData("text/plain", this.model.getSelectedText());
15130
+ e.clipboardData.setData(WEAVE_TEXT_MIME, JSON.stringify({
15131
+ v: 1,
15132
+ blocks: this.model.getSelectedBlocks()
15133
+ }));
15134
+ e.preventDefault();
15135
+ return true;
15136
+ }
15137
+ onCopy = (e) => {
15138
+ e.stopPropagation();
15139
+ this.writeRich(e);
15140
+ };
15141
+ onCut = (e) => {
15142
+ e.stopPropagation();
15143
+ if (!this.writeRich(e)) return;
15144
+ if (this.model.replaceSelection("", "delete")) {
15145
+ this.syncTextarea();
15146
+ this.afterContentChange(0);
15147
+ }
15148
+ };
14957
15149
  onPaste = (e) => {
14958
15150
  e.stopPropagation();
14959
15151
  e.preventDefault();
15152
+ if (this.host.richText) {
15153
+ const blocks = parseWeaveClipboard(e.clipboardData?.getData(WEAVE_TEXT_MIME));
15154
+ if (blocks) {
15155
+ const len = blocksToText(blocks).length;
15156
+ if (this.model.replaceSelectionWithBlocks(blocks, "paste")) {
15157
+ this.syncTextarea();
15158
+ this.afterContentChange(len);
15159
+ }
15160
+ return;
15161
+ }
15162
+ }
14960
15163
  const raw = e.clipboardData?.getData("text/plain") ?? "";
14961
15164
  if (raw === "") return;
14962
15165
  this.insertText(raw.replace(/\r\n?/g, "\n"), "paste");
@@ -17601,7 +17804,7 @@ var WeaveTextV2Node = class extends WeaveNode {
17601
17804
  },
17602
17805
  signal: this.instance.getEventsController().signal,
17603
17806
  onChange: (blocks) => this.checkSize(shape, blocks),
17604
- onRequestExit: () => this.exitEditMode(shape)
17807
+ onRequestExit: (reason) => this.exitEditMode(shape, reason)
17605
17808
  }, shape.id());
17606
17809
  shape.setAttr("cancelEditMode", () => this.exitEditMode(shape));
17607
17810
  stage.mode(WEAVE_STAGE_TEXT_EDITION_MODE);
@@ -17624,7 +17827,7 @@ var WeaveTextV2Node = class extends WeaveNode {
17624
17827
  });
17625
17828
  } else if (size <= threshold) this.sizeWarned = false;
17626
17829
  }
17627
- exitEditMode(shape) {
17830
+ exitEditMode(shape, reason) {
17628
17831
  const session = this.session;
17629
17832
  if (!session || !this.editing) return;
17630
17833
  clearTimeout(this.selectionTimer);
@@ -17642,7 +17845,7 @@ var WeaveTextV2Node = class extends WeaveNode {
17642
17845
  const selectionPlugin = this.instance.getPlugin("nodesSelection");
17643
17846
  if (selectionPlugin) {
17644
17847
  this.instance.enablePlugin("nodesSelection");
17645
- selectionPlugin.setSelectedNodes([liveNode]);
17848
+ selectionPlugin.setSelectedNodes(reason === "outside" ? [] : [liveNode]);
17646
17849
  this.instance.triggerAction(SELECTION_TOOL_ACTION_NAME);
17647
17850
  }
17648
17851
  stage.container().tabIndex = 1;
@@ -17657,12 +17860,33 @@ var WeaveTextV2Node = class extends WeaveNode {
17657
17860
  const serialized = this.serialize(live);
17658
17861
  if (live.text() !== "") {
17659
17862
  const container = live.getParent();
17660
- live.destroy();
17863
+ this.swapOnRendered(live);
17661
17864
  this.instance.addNode(serialized, container?.getAttrs().id);
17662
17865
  } else live.destroy();
17663
17866
  } else if (changed) this.instance.updateNode(this.serialize(live));
17664
17867
  this.createNode = false;
17665
17868
  }
17869
+ /** Destroy `temp` once the real node with the same id is in the tree. */
17870
+ swapOnRendered(temp) {
17871
+ const id = temp.id();
17872
+ const timer = {};
17873
+ const finish = (real) => {
17874
+ clearTimeout(timer.id);
17875
+ this.instance.removeEventListener("onNodeRenderedAdded", onRendered);
17876
+ temp.destroy();
17877
+ if (!real) return;
17878
+ const selection = this.instance.getPlugin("nodesSelection");
17879
+ const selected = selection?.getSelectedNodes?.() ?? [];
17880
+ if (selection && selected.some((n) => n.id() === id)) selection.setSelectedNodes([real]);
17881
+ real.getLayer()?.batchDraw();
17882
+ };
17883
+ const onRendered = (node) => {
17884
+ if (node === temp || node.id() !== id || !node.getParent()) return;
17885
+ finish(node);
17886
+ };
17887
+ this.instance.addEventListener("onNodeRenderedAdded", onRendered);
17888
+ timer.id = setTimeout(() => finish(), 2e3);
17889
+ }
17666
17890
  setupTransform(text) {
17667
17891
  let actualAnchor = void 0;
17668
17892
  const isSide = () => SIDE_ANCHOR_SET.has(actualAnchor ?? "");