@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.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 {
@@ -12519,6 +12519,12 @@ function isSafeLink(value) {
12519
12519
  return false;
12520
12520
  }
12521
12521
  }
12522
+ /** Marks an empty block carries: normalized and never a link. */
12523
+ function emptyBlockMarks(m) {
12524
+ if (!m) return void 0;
12525
+ const { link: _link, ...rest } = m;
12526
+ return normalizeMarks(rest);
12527
+ }
12522
12528
  /** Block fields that describe structure (not content). */
12523
12529
  function blockFields(block, withStart) {
12524
12530
  const out = {};
@@ -12596,6 +12602,76 @@ function sliceRuns(runs, from, to) {
12596
12602
  }
12597
12603
  return out;
12598
12604
  }
12605
+ /** Blocks covering the global range [start, end), keeping structure and marks. */
12606
+ function sliceBlocks(blocks, start, end) {
12607
+ const out = [];
12608
+ let pos = 0;
12609
+ for (const block of blocks) {
12610
+ const len = blockText(block).length;
12611
+ const bStart = pos;
12612
+ const bEnd = pos + len;
12613
+ pos = bEnd + 1;
12614
+ if (bEnd < start || bStart > end) continue;
12615
+ const runs = normalizeRuns(sliceRuns(block.runs, Math.max(start, bStart) - bStart, end - bStart));
12616
+ out.push({
12617
+ ...blockFields(block, true),
12618
+ runs,
12619
+ ...runs.length === 0 && block.m ? { m: block.m } : {}
12620
+ });
12621
+ }
12622
+ return out;
12623
+ }
12624
+ /**
12625
+ * Replace the global range [start, end) with a fragment of blocks. A single
12626
+ * block fragment merges inline; otherwise first/last fragment blocks merge
12627
+ * with the text before/after the range.
12628
+ */
12629
+ function insertBlocks(blocks, start, end, fragment) {
12630
+ if (fragment.length === 0) return blocks;
12631
+ const total = blocksToText(blocks).length;
12632
+ start = Math.max(0, Math.min(start, total));
12633
+ end = Math.max(start, Math.min(end, total));
12634
+ const head = sliceBlocks(blocks, 0, start);
12635
+ const tail = sliceBlocks(blocks, end, total);
12636
+ const before = head.pop() ?? { runs: [] };
12637
+ const after = tail.shift() ?? { runs: [] };
12638
+ const first = fragment[0];
12639
+ const last = fragment[fragment.length - 1];
12640
+ const finish = (b) => {
12641
+ const runs = normalizeRuns(b.runs);
12642
+ const { m: _m, ...rest } = b;
12643
+ return {
12644
+ ...rest,
12645
+ runs,
12646
+ ...runs.length === 0 && b.m ? { m: b.m } : {}
12647
+ };
12648
+ };
12649
+ if (fragment.length === 1) return [
12650
+ ...head,
12651
+ finish({
12652
+ ...before,
12653
+ runs: [
12654
+ ...before.runs,
12655
+ ...first.runs,
12656
+ ...after.runs
12657
+ ]
12658
+ }),
12659
+ ...tail
12660
+ ];
12661
+ return [
12662
+ ...head,
12663
+ finish({
12664
+ ...before,
12665
+ runs: [...before.runs, ...first.runs]
12666
+ }),
12667
+ ...fragment.slice(1, -1).map(finish),
12668
+ finish({
12669
+ ...last,
12670
+ runs: [...last.runs, ...after.runs]
12671
+ }),
12672
+ ...tail
12673
+ ];
12674
+ }
12599
12675
  /** Marks of the run at (or immediately before) a local offset. */
12600
12676
  function marksAt(runs, offset) {
12601
12677
  let pos = 0;
@@ -12636,7 +12712,7 @@ function replaceRange(blocks, start, end, insert, insertMarks) {
12636
12712
  const local = start - bStart;
12637
12713
  before = sliceRuns(block.runs, 0, local);
12638
12714
  startBlock = block;
12639
- inheritedMarks = marksAt(block.runs, local);
12715
+ inheritedMarks = len === 0 ? block.m : marksAt(block.runs, local);
12640
12716
  }
12641
12717
  if (end >= bStart && end <= bEnd) after = sliceRuns(block.runs, end - bStart, len);
12642
12718
  }
@@ -12652,9 +12728,12 @@ function replaceRange(blocks, start, end, insert, insertMarks) {
12652
12728
  let runs = mk(line);
12653
12729
  if (idx === 0) runs = [...before, ...runs];
12654
12730
  if (idx === lines.length - 1) runs = [...runs, ...after];
12731
+ const normalized = normalizeRuns(runs);
12732
+ const carried = emptyBlockMarks(inheritedMarks);
12655
12733
  middle.push({
12656
12734
  ...blockFields(startBlock, idx === 0),
12657
- runs: normalizeRuns(runs)
12735
+ runs: normalized,
12736
+ ...normalized.length === 0 && carried ? { m: carried } : {}
12658
12737
  });
12659
12738
  });
12660
12739
  return [
@@ -12718,7 +12797,11 @@ function blockIndicesInRange(blocks, start, end) {
12718
12797
  }
12719
12798
  /** Marks that typing at a collapsed offset would inherit. */
12720
12799
  function marksAtOffset(blocks, offset) {
12721
- for (const s of blockSpans(blocks)) if (offset >= s.start && offset <= s.end) return cleanMarks(marksAt(blocks[s.index].runs, offset - s.start));
12800
+ for (const s of blockSpans(blocks)) if (offset >= s.start && offset <= s.end) {
12801
+ const block = blocks[s.index];
12802
+ if (block.runs.length === 0) return cleanMarks(block.m);
12803
+ return cleanMarks(marksAt(block.runs, offset - s.start));
12804
+ }
12722
12805
  }
12723
12806
  function mapRunsInRange(block, from, to, fn) {
12724
12807
  const runs = [];
@@ -12751,6 +12834,26 @@ function mapRunsInRange(block, from, to, fn) {
12751
12834
  runs: normalizeRuns(runs)
12752
12835
  };
12753
12836
  }
12837
+ function withEmptyMark(block, key, value) {
12838
+ const { m: _m, ...rest } = block;
12839
+ const m = normalizeMarks({
12840
+ ...block.m,
12841
+ [key]: value
12842
+ });
12843
+ return m ? {
12844
+ ...rest,
12845
+ m
12846
+ } : rest;
12847
+ }
12848
+ /**
12849
+ * Set (or remove with `undefined`) one mark on the empty block holding the
12850
+ * collapsed offset. Other blocks are returned untouched.
12851
+ */
12852
+ function applyMarkToEmptyBlock(blocks, offset, key, value) {
12853
+ const span = blockSpans(blocks).find((s) => offset >= s.start && offset <= s.end);
12854
+ if (!span || blocks[span.index].runs.length > 0) return blocks;
12855
+ return blocks.map((b, i) => i === span.index ? withEmptyMark(b, key, value) : b);
12856
+ }
12754
12857
  /** Set (or remove with `undefined`) one mark over the global range. */
12755
12858
  function applyMark(blocks, start, end, key, value) {
12756
12859
  if (start >= end) return blocks;
@@ -12763,6 +12866,7 @@ function applyMark(blocks, start, end, key, value) {
12763
12866
  return blocks.map((block, i) => {
12764
12867
  const s = spans[i];
12765
12868
  if (s.end <= start || s.start >= end) return block;
12869
+ if (block.runs.length === 0) return withEmptyMark(block, key, value);
12766
12870
  return mapRunsInRange(block, Math.max(start, s.start) - s.start, Math.min(end, s.end) - s.start, (m) => normalizeMarks({
12767
12871
  ...m,
12768
12872
  [key]: value
@@ -12988,6 +13092,44 @@ function alignQuery(blocks, start, end) {
12988
13092
  value: set[0]
12989
13093
  };
12990
13094
  }
13095
+ const WEAVE_TEXT_MIME = "application/x-weave-text+json";
13096
+ /** Defensive parse of a clipboard payload; undefined when invalid. */
13097
+ function parseWeaveClipboard(raw) {
13098
+ if (!raw) return void 0;
13099
+ try {
13100
+ const data = JSON.parse(raw);
13101
+ if (data?.v !== 1 || !Array.isArray(data.blocks) || !data.blocks.length) return;
13102
+ const out = [];
13103
+ for (const b of data.blocks.slice(0, 5e3)) {
13104
+ if (!b || !Array.isArray(b.runs)) return void 0;
13105
+ const runs = [];
13106
+ for (const r of b.runs) {
13107
+ if (typeof r?.t !== "string") return void 0;
13108
+ const m = cleanMarks(r.m && typeof r.m === "object" ? { ...r.m } : void 0);
13109
+ if (m && "link" in m && !isSafeLink(m.link)) delete m.link;
13110
+ runs.push({
13111
+ t: r.t,
13112
+ ...m && Object.keys(m).length ? { m } : {}
13113
+ });
13114
+ }
13115
+ const block = { runs: normalizeRuns(runs) };
13116
+ if (b.kind === "ul" || b.kind === "ol") block.kind = b.kind;
13117
+ if (Number.isInteger(b.depth) && b.depth > 0) block.depth = b.depth;
13118
+ if (Number.isInteger(b.start)) block.start = b.start;
13119
+ if ([
13120
+ "left",
13121
+ "center",
13122
+ "right"
13123
+ ].includes(b.align)) block.align = b.align;
13124
+ const em = cleanMarks(b.m && typeof b.m === "object" ? b.m : void 0);
13125
+ if (!block.runs.length && em) block.m = em;
13126
+ out.push(block);
13127
+ }
13128
+ return out;
13129
+ } catch {
13130
+ return;
13131
+ }
13132
+ }
12991
13133
  //#endregion
12992
13134
  //#region src/nodes/text-v2/layout/engine.ts
12993
13135
  const DEFAULT_INDENT_STEP = 1.5;
@@ -13136,7 +13278,7 @@ var TextLayoutEngine = class {
13136
13278
  return Math.min(wanted, limit);
13137
13279
  }
13138
13280
  sigFor(block, style, fixedWidth, maxWidth, indent) {
13139
- return `${fontString(style)}|${style.lineHeight}|${style.letterSpacing}|${style.wrap}|${fixedWidth ? maxWidth - indent : "auto"}|${JSON.stringify(block.runs)}`;
13281
+ 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) : ""}`;
13140
13282
  }
13141
13283
  /**
13142
13284
  * Pre-compute paragraph layouts for `blocks` within a time budget so a
@@ -13233,7 +13375,7 @@ var TextLayoutEngine = class {
13233
13375
  const startChar = entry.graphemes.slice(0, pl.startG).join("").length;
13234
13376
  let marker;
13235
13377
  if (first && opts.marker) {
13236
- const m0 = block.runs[0]?.m;
13378
+ const m0 = block.runs[0]?.m ?? block.m;
13237
13379
  const rs = runStyle(style, m0);
13238
13380
  const stepPx = Math.min(indent, this.stepPx(style));
13239
13381
  marker = {
@@ -13347,9 +13489,10 @@ var TextLayoutEngine = class {
13347
13489
  desc = Math.max(desc, m.descent);
13348
13490
  }
13349
13491
  if (seen.size === 0) {
13350
- size = style.fontSize;
13351
- height = style.lineHeight * size;
13352
- const m = this.metrics(baseFont, style.fontSize);
13492
+ const rs = runStyle(style, entry.emptyMarks);
13493
+ size = rs.fontSize;
13494
+ height = (runLineHeight(entry.emptyMarks) ?? style.lineHeight) * size;
13495
+ const m = this.metrics(entry.emptyMarks ? fontString(rs) : baseFont, size);
13353
13496
  asc = m.ascent;
13354
13497
  desc = m.descent;
13355
13498
  }
@@ -13369,6 +13512,7 @@ var TextLayoutEngine = class {
13369
13512
  fonts,
13370
13513
  lineHeights: block.runs.map((r) => runLineHeight(r.m) ?? style.lineHeight),
13371
13514
  sizes,
13515
+ ...block.runs.length === 0 && block.m ? { emptyMarks: block.m } : {},
13372
13516
  lines: []
13373
13517
  };
13374
13518
  const n = graphemes.length;
@@ -14424,6 +14568,10 @@ var TextEditModel = class {
14424
14568
  const { start, end } = this.getSelection();
14425
14569
  return this.getText().slice(start, end);
14426
14570
  }
14571
+ getSelectedBlocks() {
14572
+ const { start, end } = this.getSelection();
14573
+ return sliceBlocks(this.blocks, start, end);
14574
+ }
14427
14575
  canUndo() {
14428
14576
  return this.undoStack.length > 0;
14429
14577
  }
@@ -14498,6 +14646,19 @@ var TextEditModel = class {
14498
14646
  this.desiredX = null;
14499
14647
  return true;
14500
14648
  }
14649
+ /** Replace the selection with rich blocks; caret ends after them. */
14650
+ replaceSelectionWithBlocks(fragment, kind) {
14651
+ if (fragment.length === 0) return false;
14652
+ const { start, end } = this.getSelection();
14653
+ const insertedLen = blocksToText(fragment).length;
14654
+ this.record(kind, start);
14655
+ this.blocks = insertBlocks(this.blocks, start, end, fragment);
14656
+ this.storedMarks = null;
14657
+ this.anchor = this.focus = start + insertedLen;
14658
+ this.lastEditEnd = this.focus;
14659
+ this.desiredX = null;
14660
+ return true;
14661
+ }
14501
14662
  replaceSelection(text, kind) {
14502
14663
  const { start, end } = this.getSelection();
14503
14664
  return this.replaceRange(start, end, text, kind);
@@ -14553,6 +14714,12 @@ var TextEditModel = class {
14553
14714
  if (value === void 0 || value === false) delete base[key];
14554
14715
  else base[key] = value;
14555
14716
  this.storedMarks = cleanMarks(base) ?? {};
14717
+ const next = applyMarkToEmptyBlock(this.blocks, start, key, value);
14718
+ if (next !== this.blocks) {
14719
+ const stored = this.storedMarks;
14720
+ this.commitFormat(next);
14721
+ this.storedMarks = stored;
14722
+ }
14556
14723
  return true;
14557
14724
  }
14558
14725
  return this.commitFormat(applyMark(this.blocks, start, end, key, value));
@@ -14624,7 +14791,7 @@ var TextEditModel = class {
14624
14791
  function outdentToPlain(blocks, index) {
14625
14792
  return blocks.map((b, i) => {
14626
14793
  if (i !== index) return b;
14627
- const { kind: _k, depth: _d, start: _s, ...rest } = b;
14794
+ const { kind: _k, depth: _d, start: _s, m: _m, ...rest } = b;
14628
14795
  return rest;
14629
14796
  });
14630
14797
  }
@@ -14665,6 +14832,8 @@ var TextEditSession = class {
14665
14832
  ta.addEventListener("beforeinput", this.onBeforeInput, opts);
14666
14833
  ta.addEventListener("input", this.onInput, opts);
14667
14834
  ta.addEventListener("paste", this.onPaste, opts);
14835
+ ta.addEventListener("copy", this.onCopy, opts);
14836
+ ta.addEventListener("cut", this.onCut, opts);
14668
14837
  ta.addEventListener("compositionstart", this.onCompositionStart, opts);
14669
14838
  ta.addEventListener("compositionend", this.onCompositionEnd, opts);
14670
14839
  ta.addEventListener("select", this.syncFromTextarea, opts);
@@ -14911,9 +15080,43 @@ var TextEditSession = class {
14911
15080
  if (inputType === "insertFromPaste") return "paste";
14912
15081
  return "type";
14913
15082
  }
15083
+ writeRich(e) {
15084
+ const { start, end } = this.model.getSelection();
15085
+ if (start === end || !this.host.richText || !e.clipboardData) return false;
15086
+ e.clipboardData.setData("text/plain", this.model.getSelectedText());
15087
+ e.clipboardData.setData(WEAVE_TEXT_MIME, JSON.stringify({
15088
+ v: 1,
15089
+ blocks: this.model.getSelectedBlocks()
15090
+ }));
15091
+ e.preventDefault();
15092
+ return true;
15093
+ }
15094
+ onCopy = (e) => {
15095
+ e.stopPropagation();
15096
+ this.writeRich(e);
15097
+ };
15098
+ onCut = (e) => {
15099
+ e.stopPropagation();
15100
+ if (!this.writeRich(e)) return;
15101
+ if (this.model.replaceSelection("", "delete")) {
15102
+ this.syncTextarea();
15103
+ this.afterContentChange(0);
15104
+ }
15105
+ };
14914
15106
  onPaste = (e) => {
14915
15107
  e.stopPropagation();
14916
15108
  e.preventDefault();
15109
+ if (this.host.richText) {
15110
+ const blocks = parseWeaveClipboard(e.clipboardData?.getData(WEAVE_TEXT_MIME));
15111
+ if (blocks) {
15112
+ const len = blocksToText(blocks).length;
15113
+ if (this.model.replaceSelectionWithBlocks(blocks, "paste")) {
15114
+ this.syncTextarea();
15115
+ this.afterContentChange(len);
15116
+ }
15117
+ return;
15118
+ }
15119
+ }
14917
15120
  const raw = e.clipboardData?.getData("text/plain") ?? "";
14918
15121
  if (raw === "") return;
14919
15122
  this.insertText(raw.replace(/\r\n?/g, "\n"), "paste");
@@ -17558,7 +17761,7 @@ var WeaveTextV2Node = class extends WeaveNode {
17558
17761
  },
17559
17762
  signal: this.instance.getEventsController().signal,
17560
17763
  onChange: (blocks) => this.checkSize(shape, blocks),
17561
- onRequestExit: () => this.exitEditMode(shape)
17764
+ onRequestExit: (reason) => this.exitEditMode(shape, reason)
17562
17765
  }, shape.id());
17563
17766
  shape.setAttr("cancelEditMode", () => this.exitEditMode(shape));
17564
17767
  stage.mode(WEAVE_STAGE_TEXT_EDITION_MODE);
@@ -17581,7 +17784,7 @@ var WeaveTextV2Node = class extends WeaveNode {
17581
17784
  });
17582
17785
  } else if (size <= threshold) this.sizeWarned = false;
17583
17786
  }
17584
- exitEditMode(shape) {
17787
+ exitEditMode(shape, reason) {
17585
17788
  const session = this.session;
17586
17789
  if (!session || !this.editing) return;
17587
17790
  clearTimeout(this.selectionTimer);
@@ -17599,7 +17802,7 @@ var WeaveTextV2Node = class extends WeaveNode {
17599
17802
  const selectionPlugin = this.instance.getPlugin("nodesSelection");
17600
17803
  if (selectionPlugin) {
17601
17804
  this.instance.enablePlugin("nodesSelection");
17602
- selectionPlugin.setSelectedNodes([liveNode]);
17805
+ selectionPlugin.setSelectedNodes(reason === "outside" ? [] : [liveNode]);
17603
17806
  this.instance.triggerAction(SELECTION_TOOL_ACTION_NAME);
17604
17807
  }
17605
17808
  stage.container().tabIndex = 1;
@@ -17614,12 +17817,33 @@ var WeaveTextV2Node = class extends WeaveNode {
17614
17817
  const serialized = this.serialize(live);
17615
17818
  if (live.text() !== "") {
17616
17819
  const container = live.getParent();
17617
- live.destroy();
17820
+ this.swapOnRendered(live);
17618
17821
  this.instance.addNode(serialized, container?.getAttrs().id);
17619
17822
  } else live.destroy();
17620
17823
  } else if (changed) this.instance.updateNode(this.serialize(live));
17621
17824
  this.createNode = false;
17622
17825
  }
17826
+ /** Destroy `temp` once the real node with the same id is in the tree. */
17827
+ swapOnRendered(temp) {
17828
+ const id = temp.id();
17829
+ const timer = {};
17830
+ const finish = (real) => {
17831
+ clearTimeout(timer.id);
17832
+ this.instance.removeEventListener("onNodeRenderedAdded", onRendered);
17833
+ temp.destroy();
17834
+ if (!real) return;
17835
+ const selection = this.instance.getPlugin("nodesSelection");
17836
+ const selected = selection?.getSelectedNodes?.() ?? [];
17837
+ if (selection && selected.some((n) => n.id() === id)) selection.setSelectedNodes([real]);
17838
+ real.getLayer()?.batchDraw();
17839
+ };
17840
+ const onRendered = (node) => {
17841
+ if (node === temp || node.id() !== id || !node.getParent()) return;
17842
+ finish(node);
17843
+ };
17844
+ this.instance.addEventListener("onNodeRenderedAdded", onRendered);
17845
+ timer.id = setTimeout(() => finish(), 2e3);
17846
+ }
17623
17847
  setupTransform(text) {
17624
17848
  let actualAnchor = void 0;
17625
17849
  const isSide = () => SIDE_ANCHOR_SET.has(actualAnchor ?? "");