@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/sdk.node.js CHANGED
@@ -9582,7 +9582,7 @@ var WeaveRegisterManager = class {
9582
9582
  };
9583
9583
  //#endregion
9584
9584
  //#region package.json
9585
- var version = "5.7.0";
9585
+ var version = "5.7.1";
9586
9586
  //#endregion
9587
9587
  //#region src/managers/setup.ts
9588
9588
  var WeaveSetupManager = class {
@@ -12522,6 +12522,12 @@ function isSafeLink(value) {
12522
12522
  return false;
12523
12523
  }
12524
12524
  }
12525
+ /** Marks an empty block carries: normalized and never a link. */
12526
+ function emptyBlockMarks(m) {
12527
+ if (!m) return void 0;
12528
+ const { link: _link, ...rest } = m;
12529
+ return normalizeMarks(rest);
12530
+ }
12525
12531
  /** Block fields that describe structure (not content). */
12526
12532
  function blockFields(block, withStart) {
12527
12533
  const out = {};
@@ -12599,6 +12605,76 @@ function sliceRuns(runs, from, to) {
12599
12605
  }
12600
12606
  return out;
12601
12607
  }
12608
+ /** Blocks covering the global range [start, end), keeping structure and marks. */
12609
+ function sliceBlocks(blocks, start, end) {
12610
+ const out = [];
12611
+ let pos = 0;
12612
+ for (const block of blocks) {
12613
+ const len = blockText(block).length;
12614
+ const bStart = pos;
12615
+ const bEnd = pos + len;
12616
+ pos = bEnd + 1;
12617
+ if (bEnd < start || bStart > end) continue;
12618
+ const runs = normalizeRuns(sliceRuns(block.runs, Math.max(start, bStart) - bStart, end - bStart));
12619
+ out.push({
12620
+ ...blockFields(block, true),
12621
+ runs,
12622
+ ...runs.length === 0 && block.m ? { m: block.m } : {}
12623
+ });
12624
+ }
12625
+ return out;
12626
+ }
12627
+ /**
12628
+ * Replace the global range [start, end) with a fragment of blocks. A single
12629
+ * block fragment merges inline; otherwise first/last fragment blocks merge
12630
+ * with the text before/after the range.
12631
+ */
12632
+ function insertBlocks(blocks, start, end, fragment) {
12633
+ if (fragment.length === 0) return blocks;
12634
+ const total = blocksToText(blocks).length;
12635
+ start = Math.max(0, Math.min(start, total));
12636
+ end = Math.max(start, Math.min(end, total));
12637
+ const head = sliceBlocks(blocks, 0, start);
12638
+ const tail = sliceBlocks(blocks, end, total);
12639
+ const before = head.pop() ?? { runs: [] };
12640
+ const after = tail.shift() ?? { runs: [] };
12641
+ const first = fragment[0];
12642
+ const last = fragment[fragment.length - 1];
12643
+ const finish = (b) => {
12644
+ const runs = normalizeRuns(b.runs);
12645
+ const { m: _m, ...rest } = b;
12646
+ return {
12647
+ ...rest,
12648
+ runs,
12649
+ ...runs.length === 0 && b.m ? { m: b.m } : {}
12650
+ };
12651
+ };
12652
+ if (fragment.length === 1) return [
12653
+ ...head,
12654
+ finish({
12655
+ ...before,
12656
+ runs: [
12657
+ ...before.runs,
12658
+ ...first.runs,
12659
+ ...after.runs
12660
+ ]
12661
+ }),
12662
+ ...tail
12663
+ ];
12664
+ return [
12665
+ ...head,
12666
+ finish({
12667
+ ...before,
12668
+ runs: [...before.runs, ...first.runs]
12669
+ }),
12670
+ ...fragment.slice(1, -1).map(finish),
12671
+ finish({
12672
+ ...last,
12673
+ runs: [...last.runs, ...after.runs]
12674
+ }),
12675
+ ...tail
12676
+ ];
12677
+ }
12602
12678
  /** Marks of the run at (or immediately before) a local offset. */
12603
12679
  function marksAt(runs, offset) {
12604
12680
  let pos = 0;
@@ -12639,7 +12715,7 @@ function replaceRange(blocks, start, end, insert, insertMarks) {
12639
12715
  const local = start - bStart;
12640
12716
  before = sliceRuns(block.runs, 0, local);
12641
12717
  startBlock = block;
12642
- inheritedMarks = marksAt(block.runs, local);
12718
+ inheritedMarks = len === 0 ? block.m : marksAt(block.runs, local);
12643
12719
  }
12644
12720
  if (end >= bStart && end <= bEnd) after = sliceRuns(block.runs, end - bStart, len);
12645
12721
  }
@@ -12655,9 +12731,12 @@ function replaceRange(blocks, start, end, insert, insertMarks) {
12655
12731
  let runs = mk(line);
12656
12732
  if (idx === 0) runs = [...before, ...runs];
12657
12733
  if (idx === lines.length - 1) runs = [...runs, ...after];
12734
+ const normalized = normalizeRuns(runs);
12735
+ const carried = emptyBlockMarks(inheritedMarks);
12658
12736
  middle.push({
12659
12737
  ...blockFields(startBlock, idx === 0),
12660
- runs: normalizeRuns(runs)
12738
+ runs: normalized,
12739
+ ...normalized.length === 0 && carried ? { m: carried } : {}
12661
12740
  });
12662
12741
  });
12663
12742
  return [
@@ -12721,7 +12800,11 @@ function blockIndicesInRange(blocks, start, end) {
12721
12800
  }
12722
12801
  /** Marks that typing at a collapsed offset would inherit. */
12723
12802
  function marksAtOffset(blocks, offset) {
12724
- for (const s of blockSpans(blocks)) if (offset >= s.start && offset <= s.end) return cleanMarks(marksAt(blocks[s.index].runs, offset - s.start));
12803
+ for (const s of blockSpans(blocks)) if (offset >= s.start && offset <= s.end) {
12804
+ const block = blocks[s.index];
12805
+ if (block.runs.length === 0) return cleanMarks(block.m);
12806
+ return cleanMarks(marksAt(block.runs, offset - s.start));
12807
+ }
12725
12808
  }
12726
12809
  function mapRunsInRange(block, from, to, fn) {
12727
12810
  const runs = [];
@@ -12754,6 +12837,26 @@ function mapRunsInRange(block, from, to, fn) {
12754
12837
  runs: normalizeRuns(runs)
12755
12838
  };
12756
12839
  }
12840
+ function withEmptyMark(block, key, value) {
12841
+ const { m: _m, ...rest } = block;
12842
+ const m = normalizeMarks({
12843
+ ...block.m,
12844
+ [key]: value
12845
+ });
12846
+ return m ? {
12847
+ ...rest,
12848
+ m
12849
+ } : rest;
12850
+ }
12851
+ /**
12852
+ * Set (or remove with `undefined`) one mark on the empty block holding the
12853
+ * collapsed offset. Other blocks are returned untouched.
12854
+ */
12855
+ function applyMarkToEmptyBlock(blocks, offset, key, value) {
12856
+ const span = blockSpans(blocks).find((s) => offset >= s.start && offset <= s.end);
12857
+ if (!span || blocks[span.index].runs.length > 0) return blocks;
12858
+ return blocks.map((b, i) => i === span.index ? withEmptyMark(b, key, value) : b);
12859
+ }
12757
12860
  /** Set (or remove with `undefined`) one mark over the global range. */
12758
12861
  function applyMark(blocks, start, end, key, value) {
12759
12862
  if (start >= end) return blocks;
@@ -12766,6 +12869,7 @@ function applyMark(blocks, start, end, key, value) {
12766
12869
  return blocks.map((block, i) => {
12767
12870
  const s = spans[i];
12768
12871
  if (s.end <= start || s.start >= end) return block;
12872
+ if (block.runs.length === 0) return withEmptyMark(block, key, value);
12769
12873
  return mapRunsInRange(block, Math.max(start, s.start) - s.start, Math.min(end, s.end) - s.start, (m) => normalizeMarks({
12770
12874
  ...m,
12771
12875
  [key]: value
@@ -12991,6 +13095,44 @@ function alignQuery(blocks, start, end) {
12991
13095
  value: set[0]
12992
13096
  };
12993
13097
  }
13098
+ const WEAVE_TEXT_MIME = "application/x-weave-text+json";
13099
+ /** Defensive parse of a clipboard payload; undefined when invalid. */
13100
+ function parseWeaveClipboard(raw) {
13101
+ if (!raw) return void 0;
13102
+ try {
13103
+ const data = JSON.parse(raw);
13104
+ if (data?.v !== 1 || !Array.isArray(data.blocks) || !data.blocks.length) return;
13105
+ const out = [];
13106
+ for (const b of data.blocks.slice(0, 5e3)) {
13107
+ if (!b || !Array.isArray(b.runs)) return void 0;
13108
+ const runs = [];
13109
+ for (const r of b.runs) {
13110
+ if (typeof r?.t !== "string") return void 0;
13111
+ const m = cleanMarks(r.m && typeof r.m === "object" ? { ...r.m } : void 0);
13112
+ if (m && "link" in m && !isSafeLink(m.link)) delete m.link;
13113
+ runs.push({
13114
+ t: r.t,
13115
+ ...m && Object.keys(m).length ? { m } : {}
13116
+ });
13117
+ }
13118
+ const block = { runs: normalizeRuns(runs) };
13119
+ if (b.kind === "ul" || b.kind === "ol") block.kind = b.kind;
13120
+ if (Number.isInteger(b.depth) && b.depth > 0) block.depth = b.depth;
13121
+ if (Number.isInteger(b.start)) block.start = b.start;
13122
+ if ([
13123
+ "left",
13124
+ "center",
13125
+ "right"
13126
+ ].includes(b.align)) block.align = b.align;
13127
+ const em = cleanMarks(b.m && typeof b.m === "object" ? b.m : void 0);
13128
+ if (!block.runs.length && em) block.m = em;
13129
+ out.push(block);
13130
+ }
13131
+ return out;
13132
+ } catch {
13133
+ return;
13134
+ }
13135
+ }
12994
13136
  //#endregion
12995
13137
  //#region src/nodes/text-v2/layout/engine.ts
12996
13138
  const DEFAULT_INDENT_STEP = 1.5;
@@ -13139,7 +13281,7 @@ var TextLayoutEngine = class {
13139
13281
  return Math.min(wanted, limit);
13140
13282
  }
13141
13283
  sigFor(block, style, fixedWidth, maxWidth, indent) {
13142
- return `${fontString(style)}|${style.lineHeight}|${style.letterSpacing}|${style.wrap}|${fixedWidth ? maxWidth - indent : "auto"}|${JSON.stringify(block.runs)}`;
13284
+ 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) : ""}`;
13143
13285
  }
13144
13286
  /**
13145
13287
  * Pre-compute paragraph layouts for `blocks` within a time budget so a
@@ -13236,7 +13378,7 @@ var TextLayoutEngine = class {
13236
13378
  const startChar = entry.graphemes.slice(0, pl.startG).join("").length;
13237
13379
  let marker;
13238
13380
  if (first && opts.marker) {
13239
- const m0 = block.runs[0]?.m;
13381
+ const m0 = block.runs[0]?.m ?? block.m;
13240
13382
  const rs = runStyle(style, m0);
13241
13383
  const stepPx = Math.min(indent, this.stepPx(style));
13242
13384
  marker = {
@@ -13350,9 +13492,10 @@ var TextLayoutEngine = class {
13350
13492
  desc = Math.max(desc, m.descent);
13351
13493
  }
13352
13494
  if (seen.size === 0) {
13353
- size = style.fontSize;
13354
- height = style.lineHeight * size;
13355
- const m = this.metrics(baseFont, style.fontSize);
13495
+ const rs = runStyle(style, entry.emptyMarks);
13496
+ size = rs.fontSize;
13497
+ height = (runLineHeight(entry.emptyMarks) ?? style.lineHeight) * size;
13498
+ const m = this.metrics(entry.emptyMarks ? fontString(rs) : baseFont, size);
13356
13499
  asc = m.ascent;
13357
13500
  desc = m.descent;
13358
13501
  }
@@ -13372,6 +13515,7 @@ var TextLayoutEngine = class {
13372
13515
  fonts,
13373
13516
  lineHeights: block.runs.map((r) => runLineHeight(r.m) ?? style.lineHeight),
13374
13517
  sizes,
13518
+ ...block.runs.length === 0 && block.m ? { emptyMarks: block.m } : {},
13375
13519
  lines: []
13376
13520
  };
13377
13521
  const n = graphemes.length;
@@ -14427,6 +14571,10 @@ var TextEditModel = class {
14427
14571
  const { start, end } = this.getSelection();
14428
14572
  return this.getText().slice(start, end);
14429
14573
  }
14574
+ getSelectedBlocks() {
14575
+ const { start, end } = this.getSelection();
14576
+ return sliceBlocks(this.blocks, start, end);
14577
+ }
14430
14578
  canUndo() {
14431
14579
  return this.undoStack.length > 0;
14432
14580
  }
@@ -14501,6 +14649,19 @@ var TextEditModel = class {
14501
14649
  this.desiredX = null;
14502
14650
  return true;
14503
14651
  }
14652
+ /** Replace the selection with rich blocks; caret ends after them. */
14653
+ replaceSelectionWithBlocks(fragment, kind) {
14654
+ if (fragment.length === 0) return false;
14655
+ const { start, end } = this.getSelection();
14656
+ const insertedLen = blocksToText(fragment).length;
14657
+ this.record(kind, start);
14658
+ this.blocks = insertBlocks(this.blocks, start, end, fragment);
14659
+ this.storedMarks = null;
14660
+ this.anchor = this.focus = start + insertedLen;
14661
+ this.lastEditEnd = this.focus;
14662
+ this.desiredX = null;
14663
+ return true;
14664
+ }
14504
14665
  replaceSelection(text, kind) {
14505
14666
  const { start, end } = this.getSelection();
14506
14667
  return this.replaceRange(start, end, text, kind);
@@ -14556,6 +14717,12 @@ var TextEditModel = class {
14556
14717
  if (value === void 0 || value === false) delete base[key];
14557
14718
  else base[key] = value;
14558
14719
  this.storedMarks = cleanMarks(base) ?? {};
14720
+ const next = applyMarkToEmptyBlock(this.blocks, start, key, value);
14721
+ if (next !== this.blocks) {
14722
+ const stored = this.storedMarks;
14723
+ this.commitFormat(next);
14724
+ this.storedMarks = stored;
14725
+ }
14559
14726
  return true;
14560
14727
  }
14561
14728
  return this.commitFormat(applyMark(this.blocks, start, end, key, value));
@@ -14627,7 +14794,7 @@ var TextEditModel = class {
14627
14794
  function outdentToPlain(blocks, index) {
14628
14795
  return blocks.map((b, i) => {
14629
14796
  if (i !== index) return b;
14630
- const { kind: _k, depth: _d, start: _s, ...rest } = b;
14797
+ const { kind: _k, depth: _d, start: _s, m: _m, ...rest } = b;
14631
14798
  return rest;
14632
14799
  });
14633
14800
  }
@@ -14668,6 +14835,8 @@ var TextEditSession = class {
14668
14835
  ta.addEventListener("beforeinput", this.onBeforeInput, opts);
14669
14836
  ta.addEventListener("input", this.onInput, opts);
14670
14837
  ta.addEventListener("paste", this.onPaste, opts);
14838
+ ta.addEventListener("copy", this.onCopy, opts);
14839
+ ta.addEventListener("cut", this.onCut, opts);
14671
14840
  ta.addEventListener("compositionstart", this.onCompositionStart, opts);
14672
14841
  ta.addEventListener("compositionend", this.onCompositionEnd, opts);
14673
14842
  ta.addEventListener("select", this.syncFromTextarea, opts);
@@ -14914,9 +15083,43 @@ var TextEditSession = class {
14914
15083
  if (inputType === "insertFromPaste") return "paste";
14915
15084
  return "type";
14916
15085
  }
15086
+ writeRich(e) {
15087
+ const { start, end } = this.model.getSelection();
15088
+ if (start === end || !this.host.richText || !e.clipboardData) return false;
15089
+ e.clipboardData.setData("text/plain", this.model.getSelectedText());
15090
+ e.clipboardData.setData(WEAVE_TEXT_MIME, JSON.stringify({
15091
+ v: 1,
15092
+ blocks: this.model.getSelectedBlocks()
15093
+ }));
15094
+ e.preventDefault();
15095
+ return true;
15096
+ }
15097
+ onCopy = (e) => {
15098
+ e.stopPropagation();
15099
+ this.writeRich(e);
15100
+ };
15101
+ onCut = (e) => {
15102
+ e.stopPropagation();
15103
+ if (!this.writeRich(e)) return;
15104
+ if (this.model.replaceSelection("", "delete")) {
15105
+ this.syncTextarea();
15106
+ this.afterContentChange(0);
15107
+ }
15108
+ };
14917
15109
  onPaste = (e) => {
14918
15110
  e.stopPropagation();
14919
15111
  e.preventDefault();
15112
+ if (this.host.richText) {
15113
+ const blocks = parseWeaveClipboard(e.clipboardData?.getData(WEAVE_TEXT_MIME));
15114
+ if (blocks) {
15115
+ const len = blocksToText(blocks).length;
15116
+ if (this.model.replaceSelectionWithBlocks(blocks, "paste")) {
15117
+ this.syncTextarea();
15118
+ this.afterContentChange(len);
15119
+ }
15120
+ return;
15121
+ }
15122
+ }
14920
15123
  const raw = e.clipboardData?.getData("text/plain") ?? "";
14921
15124
  if (raw === "") return;
14922
15125
  this.insertText(raw.replace(/\r\n?/g, "\n"), "paste");
@@ -17561,7 +17764,7 @@ var WeaveTextV2Node = class extends WeaveNode {
17561
17764
  },
17562
17765
  signal: this.instance.getEventsController().signal,
17563
17766
  onChange: (blocks) => this.checkSize(shape, blocks),
17564
- onRequestExit: () => this.exitEditMode(shape)
17767
+ onRequestExit: (reason) => this.exitEditMode(shape, reason)
17565
17768
  }, shape.id());
17566
17769
  shape.setAttr("cancelEditMode", () => this.exitEditMode(shape));
17567
17770
  stage.mode(WEAVE_STAGE_TEXT_EDITION_MODE);
@@ -17584,7 +17787,7 @@ var WeaveTextV2Node = class extends WeaveNode {
17584
17787
  });
17585
17788
  } else if (size <= threshold) this.sizeWarned = false;
17586
17789
  }
17587
- exitEditMode(shape) {
17790
+ exitEditMode(shape, reason) {
17588
17791
  const session = this.session;
17589
17792
  if (!session || !this.editing) return;
17590
17793
  clearTimeout(this.selectionTimer);
@@ -17602,7 +17805,7 @@ var WeaveTextV2Node = class extends WeaveNode {
17602
17805
  const selectionPlugin = this.instance.getPlugin("nodesSelection");
17603
17806
  if (selectionPlugin) {
17604
17807
  this.instance.enablePlugin("nodesSelection");
17605
- selectionPlugin.setSelectedNodes([liveNode]);
17808
+ selectionPlugin.setSelectedNodes(reason === "outside" ? [] : [liveNode]);
17606
17809
  this.instance.triggerAction(SELECTION_TOOL_ACTION_NAME);
17607
17810
  }
17608
17811
  stage.container().tabIndex = 1;
@@ -17617,12 +17820,33 @@ var WeaveTextV2Node = class extends WeaveNode {
17617
17820
  const serialized = this.serialize(live);
17618
17821
  if (live.text() !== "") {
17619
17822
  const container = live.getParent();
17620
- live.destroy();
17823
+ this.swapOnRendered(live);
17621
17824
  this.instance.addNode(serialized, container?.getAttrs().id);
17622
17825
  } else live.destroy();
17623
17826
  } else if (changed) this.instance.updateNode(this.serialize(live));
17624
17827
  this.createNode = false;
17625
17828
  }
17829
+ /** Destroy `temp` once the real node with the same id is in the tree. */
17830
+ swapOnRendered(temp) {
17831
+ const id = temp.id();
17832
+ const timer = {};
17833
+ const finish = (real) => {
17834
+ clearTimeout(timer.id);
17835
+ this.instance.removeEventListener("onNodeRenderedAdded", onRendered);
17836
+ temp.destroy();
17837
+ if (!real) return;
17838
+ const selection = this.instance.getPlugin("nodesSelection");
17839
+ const selected = selection?.getSelectedNodes?.() ?? [];
17840
+ if (selection && selected.some((n) => n.id() === id)) selection.setSelectedNodes([real]);
17841
+ real.getLayer()?.batchDraw();
17842
+ };
17843
+ const onRendered = (node) => {
17844
+ if (node === temp || node.id() !== id || !node.getParent()) return;
17845
+ finish(node);
17846
+ };
17847
+ this.instance.addEventListener("onNodeRenderedAdded", onRendered);
17848
+ timer.id = setTimeout(() => finish(), 2e3);
17849
+ }
17626
17850
  setupTransform(text) {
17627
17851
  let actualAnchor = void 0;
17628
17852
  const isSide = () => SIDE_ANCHOR_SET.has(actualAnchor ?? "");