@inditextech/weave-sdk 5.7.0-SNAPSHOT.595.1 → 5.7.0-SNAPSHOT.597.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
@@ -9502,7 +9502,7 @@ var WeaveRegisterManager = class {
9502
9502
  };
9503
9503
  //#endregion
9504
9504
  //#region package.json
9505
- var version = "5.7.0-SNAPSHOT.595.1";
9505
+ var version = "5.7.0-SNAPSHOT.597.1";
9506
9506
  //#endregion
9507
9507
  //#region src/managers/setup.ts
9508
9508
  var WeaveSetupManager = class {
@@ -14412,7 +14412,7 @@ function normalizeRuns(runs) {
14412
14412
  const out = [];
14413
14413
  for (const run of runs) {
14414
14414
  if (run.t === "") continue;
14415
- const last = out[out.length - 1];
14415
+ const last = out.at(-1);
14416
14416
  if (last && sameMarks(last.m, run.m)) last.t += run.t;
14417
14417
  else out.push({
14418
14418
  t: run.t,
@@ -14487,7 +14487,7 @@ function replaceRange(blocks, start, end, insert) {
14487
14487
  const suffixBlocks = [];
14488
14488
  let inheritedMarks;
14489
14489
  let pos = 0;
14490
- blocks.forEach((block, i) => {
14490
+ blocks.forEach((block) => {
14491
14491
  const len = blockText(block).length;
14492
14492
  const bStart = pos;
14493
14493
  const bEnd = pos + len;
@@ -14552,6 +14552,43 @@ const now = () => typeof performance !== "undefined" ? performance.now() : Date.
14552
14552
  const WS = /\s/;
14553
14553
  const MAX_CACHE = 2e4;
14554
14554
  const MAX_CACHED_CHUNK = 256;
14555
+ function flattenBlock(block) {
14556
+ const graphemes = [];
14557
+ const runOf = [];
14558
+ for (let ri = 0; ri < block.runs.length; ri++) for (const g of splitGraphemes(block.runs[ri].t)) {
14559
+ graphemes.push(g);
14560
+ runOf.push(ri);
14561
+ }
14562
+ return {
14563
+ graphemes,
14564
+ runOf
14565
+ };
14566
+ }
14567
+ function trimTrailingSpace(graphemes, start, end) {
14568
+ let e = end;
14569
+ while (e > start && WS.test(graphemes[e - 1])) e--;
14570
+ return e;
14571
+ }
14572
+ function skipSpace(graphemes, from) {
14573
+ let i = from;
14574
+ while (i < graphemes.length && WS.test(graphemes[i])) i++;
14575
+ return i;
14576
+ }
14577
+ const isNum = (v) => v !== void 0 && !Number.isNaN(v);
14578
+ /** Konva's word wrap: break after the last space/dash that fits (0 = no break). */
14579
+ function wordWrapIndex(graphemes, start, matchLen, matchWidth, maxWidth) {
14580
+ const nextChar = graphemes[start + matchLen];
14581
+ if ((nextChar === " " || nextChar === "-") && matchWidth <= maxWidth) return matchLen;
14582
+ const seg = graphemes.slice(start, start + matchLen);
14583
+ return Math.max(seg.lastIndexOf(" "), seg.lastIndexOf("-")) + 1;
14584
+ }
14585
+ function applyAlign(lines, align, width, padding) {
14586
+ if (align === "left") return;
14587
+ for (const line of lines) {
14588
+ const free = width - line.width - padding * 2;
14589
+ line.offsetX = align === "center" ? free / 2 : free;
14590
+ }
14591
+ }
14555
14592
  var TextLayoutEngine = class {
14556
14593
  measure;
14557
14594
  cache = /* @__PURE__ */ new Map();
@@ -14609,8 +14646,8 @@ var TextLayoutEngine = class {
14609
14646
  layout(blocks, style, bounds) {
14610
14647
  const font = fontString(style);
14611
14648
  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);
14649
+ const fixedWidth = isNum(bounds.width);
14650
+ const fixedHeight = isNum(bounds.height);
14614
14651
  const maxWidth = (bounds.width ?? 0) - padding * 2;
14615
14652
  const maxHeightPx = (bounds.height ?? 0) - padding * 2;
14616
14653
  const lineHeightPx = style.lineHeight * style.fontSize;
@@ -14620,9 +14657,8 @@ var TextLayoutEngine = class {
14620
14657
  let relaid = 0;
14621
14658
  const lines = [];
14622
14659
  let textWidth = 0;
14623
- let currentHeightPx = 0;
14624
14660
  let offset = 0;
14625
- outer: for (let p = 0; p < blocks.length; p++) {
14661
+ for (let p = 0; p < blocks.length; p++) {
14626
14662
  const block = blocks[p];
14627
14663
  const sig = `${sigBase}|${JSON.stringify(block.runs)}`;
14628
14664
  let entry = prev[p]?.sig === sig ? prev[p] : this.pool.get(sig);
@@ -14631,27 +14667,14 @@ var TextLayoutEngine = class {
14631
14667
  relaid++;
14632
14668
  }
14633
14669
  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
- }
14670
+ const emitted = this.emitLines(entry, block, p, offset, lines, {
14671
+ font,
14672
+ style,
14673
+ lineHeightPx,
14674
+ maxHeightPx: fixedHeight ? maxHeightPx : void 0
14675
+ });
14676
+ textWidth = Math.max(textWidth, emitted.maxWidth);
14677
+ if (emitted.stopped) break;
14655
14678
  offset += blockText(block).length + 1;
14656
14679
  }
14657
14680
  this.paragraphs = next;
@@ -14659,10 +14682,7 @@ var TextLayoutEngine = class {
14659
14682
  this.lastParagraphsRelaid = relaid;
14660
14683
  const width = fixedWidth ? bounds.width : textWidth + padding * 2;
14661
14684
  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
- }
14685
+ applyAlign(lines, style.align, width, padding);
14666
14686
  return {
14667
14687
  lines,
14668
14688
  textWidth,
@@ -14672,7 +14692,38 @@ var TextLayoutEngine = class {
14672
14692
  height
14673
14693
  };
14674
14694
  }
14675
- rangeWidth(entry, runs, from, to, font, spacing) {
14695
+ /** Append a paragraph's lines to `lines`; stops once a fixed height is full. */
14696
+ emitLines(entry, block, paragraph, offset, lines, opts) {
14697
+ const { font, style, lineHeightPx, maxHeightPx } = opts;
14698
+ let maxWidth = 0;
14699
+ for (const pl of entry.lines) {
14700
+ const text = entry.graphemes.slice(pl.startG, pl.endG).join("");
14701
+ const segments = this.buildSegments(entry, block, pl, font, style);
14702
+ const startChar = entry.graphemes.slice(0, pl.startG).join("").length;
14703
+ lines.push({
14704
+ paragraph,
14705
+ start: offset + startChar,
14706
+ end: offset + startChar + text.length,
14707
+ text,
14708
+ width: segments.reduce((a, s) => a + s.width, 0),
14709
+ measuredWidth: pl.measuredWidth,
14710
+ offsetX: 0,
14711
+ y: lines.length * lineHeightPx,
14712
+ segments,
14713
+ lastInParagraph: pl.lastInParagraph
14714
+ });
14715
+ maxWidth = Math.max(maxWidth, pl.measuredWidth);
14716
+ if (maxHeightPx !== void 0 && (lines.length + 1) * lineHeightPx > maxHeightPx) return {
14717
+ stopped: true,
14718
+ maxWidth
14719
+ };
14720
+ }
14721
+ return {
14722
+ stopped: false,
14723
+ maxWidth
14724
+ };
14725
+ }
14726
+ rangeWidth(entry, from, to, font, spacing) {
14676
14727
  let width = 0;
14677
14728
  let i = from;
14678
14729
  while (i < to) {
@@ -14684,15 +14735,39 @@ var TextLayoutEngine = class {
14684
14735
  }
14685
14736
  return width;
14686
14737
  }
14738
+ /**
14739
+ * Longest prefix of graphemes[start..n) that fits `maxWidth`, found with an
14740
+ * exponential probe (bounds the work to about one line) plus a binary
14741
+ * search, like Konva. Always consumes at least one grapheme.
14742
+ */
14743
+ longestFit(start, n, maxWidth, width) {
14744
+ let low = 0;
14745
+ let high = n - start;
14746
+ let probe = Math.min(high, 32);
14747
+ while (probe < high && width(start, start + probe) <= maxWidth) probe = Math.min(high, probe * 2);
14748
+ high = probe;
14749
+ let matchLen = 0;
14750
+ let matchWidth = 0;
14751
+ while (low < high) {
14752
+ const mid = low + high >>> 1;
14753
+ const w = width(start, start + mid + 1);
14754
+ if (w <= maxWidth) {
14755
+ low = mid + 1;
14756
+ matchLen = mid + 1;
14757
+ matchWidth = w;
14758
+ } else high = mid;
14759
+ }
14760
+ if (matchLen === 0) {
14761
+ matchLen = 1;
14762
+ matchWidth = width(start, start + 1);
14763
+ }
14764
+ return {
14765
+ matchLen,
14766
+ matchWidth
14767
+ };
14768
+ }
14687
14769
  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
- });
14770
+ const { graphemes, runOf } = flattenBlock(block);
14696
14771
  const entry = {
14697
14772
  sig,
14698
14773
  graphemes,
@@ -14701,7 +14776,7 @@ var TextLayoutEngine = class {
14701
14776
  };
14702
14777
  const n = graphemes.length;
14703
14778
  const spacing = style.letterSpacing;
14704
- const width = (a, b) => this.rangeWidth(entry, block.runs, a, b, font, spacing);
14779
+ const width = (a, b) => this.rangeWidth(entry, a, b, font, spacing);
14705
14780
  const shouldWrap = style.wrap !== "none";
14706
14781
  const wrapAtWord = style.wrap !== "char" && shouldWrap;
14707
14782
  const whole = width(0, n);
@@ -14716,26 +14791,7 @@ var TextLayoutEngine = class {
14716
14791
  }
14717
14792
  let start = 0;
14718
14793
  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
- }
14794
+ let { matchLen, matchWidth } = this.longestFit(start, n, maxWidth, width);
14739
14795
  if (start + matchLen >= n) {
14740
14796
  entry.lines.push({
14741
14797
  startG: start,
@@ -14746,32 +14802,22 @@ var TextLayoutEngine = class {
14746
14802
  return entry;
14747
14803
  }
14748
14804
  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;
14805
+ const wrapped = wordWrapIndex(graphemes, start, matchLen, matchWidth, maxWidth);
14806
+ if (wrapped > 0) {
14807
+ matchLen = wrapped;
14759
14808
  matchWidth = width(start, start + matchLen);
14760
14809
  }
14761
14810
  }
14762
- let end = start + matchLen;
14763
- let trimmedEnd = end;
14764
- while (trimmedEnd > start && WS.test(graphemes[trimmedEnd - 1])) trimmedEnd--;
14811
+ const end = start + matchLen;
14765
14812
  entry.lines.push({
14766
14813
  startG: start,
14767
- endG: trimmedEnd,
14814
+ endG: trimTrailingSpace(graphemes, start, end),
14768
14815
  measuredWidth: matchWidth,
14769
14816
  lastInParagraph: false
14770
14817
  });
14771
- while (end < n && WS.test(graphemes[end])) end++;
14772
- start = end;
14818
+ start = skipSpace(graphemes, end);
14773
14819
  }
14774
- const last = entry.lines[entry.lines.length - 1];
14820
+ const last = entry.lines.at(-1);
14775
14821
  if (last) last.lastInParagraph = true;
14776
14822
  return entry;
14777
14823
  }
@@ -14833,8 +14879,6 @@ var WeaveTextV2Shape = class extends Konva.Shape {
14833
14879
  this.on(LAYOUT_ATTRS.map((a) => `${a}Change.konva`).join(" "), () => {
14834
14880
  this.layoutCache = null;
14835
14881
  });
14836
- this.sceneFunc(this._paint);
14837
- this.hitFunc(this._hit);
14838
14882
  }
14839
14883
  text(value) {
14840
14884
  if (value === void 0) return typeof this.attrs.text === "string" ? this.attrs.text : blocksToText(this.getBlocks());
@@ -14996,48 +15040,47 @@ var WeaveTextV2Shape = class extends Konva.Shape {
14996
15040
  setViewport(range) {
14997
15041
  this.viewport = range;
14998
15042
  }
14999
- visibleRange() {
15043
+ /**
15044
+ * Visible y-range in local coordinates, derived from the context being
15045
+ * painted (current transform + backing canvas size) instead of the stage.
15046
+ * The stage is not the paint target during export (off-screen canvases,
15047
+ * tiles, server-side), so using it culled visible lines. Returns null (paint
15048
+ * everything) when the transform cannot be read.
15049
+ */
15050
+ visibleRange(ctx) {
15000
15051
  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
- ];
15052
+ const native = ctx._context;
15053
+ const m = native.getTransform?.() ?? native.currentTransform;
15054
+ if (!m) return null;
15055
+ const det = m.a * m.d - m.b * m.c;
15056
+ if (!det || !Number.isFinite(det)) return null;
15057
+ const canvas = ctx.getCanvas();
15058
+ const w = canvas.getWidth();
15059
+ const h = canvas.getHeight();
15060
+ if (!(w > 0) || !(h > 0)) return null;
15061
+ let y1 = Infinity;
15062
+ let y2 = -Infinity;
15063
+ for (const [px, py] of [
15064
+ [0, 0],
15065
+ [w, 0],
15066
+ [0, h],
15067
+ [w, h]
15068
+ ]) {
15069
+ const y = (m.a * (py - m.f) - m.b * (px - m.e)) / det;
15070
+ if (y < y1) y1 = y;
15071
+ if (y > y2) y2 = y;
15072
+ }
15024
15073
  return {
15025
- y1: Math.min(...pts.map((p) => p.y)),
15026
- y2: Math.max(...pts.map((p) => p.y))
15074
+ y1,
15075
+ y2
15027
15076
  };
15028
15077
  }
15029
- _paint = (ctx, shape) => {
15078
+ _sceneFunc = (ctx, shape) => {
15030
15079
  const self = shape;
15031
15080
  const layout = self.ensureLayout();
15032
15081
  if (layout.lines.length === 0) return;
15033
- const fontSize = self.fontSize();
15034
15082
  const padding = self.padding();
15035
15083
  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
15084
  let baseline = "middle";
15042
15085
  let translateY = lineHeightPx / 2;
15043
15086
  if (!Konva.legacyTextRendering) {
@@ -15048,62 +15091,63 @@ var WeaveTextV2Shape = class extends Konva.Shape {
15048
15091
  ctx.setAttr("font", fontString(self.style()));
15049
15092
  ctx.setAttr("textBaseline", baseline);
15050
15093
  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;
15094
+ const alignY = self.verticalOffset(layout);
15095
+ const range = self.visibleRange(ctx);
15056
15096
  ctx.translate(padding, alignY + padding);
15057
- const range = self.visibleRange();
15058
- const paintSelf = self;
15097
+ const top = alignY + padding;
15059
15098
  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();
15099
+ const lineTop = top + n * lineHeightPx;
15100
+ if (range && (lineTop + lineHeightPx < range.y1 - lineHeightPx || lineTop > range.y2 + lineHeightPx)) continue;
15101
+ self.paintLine(ctx, layout.lines[n], translateY + n * lineHeightPx);
15104
15102
  }
15105
15103
  };
15106
- _hit = (ctx, shape) => {
15104
+ /** Vertical offset of the text block inside the box (verticalAlign). */
15105
+ verticalOffset(layout) {
15106
+ const used = layout.lines.length * layout.lineHeightPx + this.padding() * 2;
15107
+ const va = this.verticalAlign();
15108
+ if (va === "middle") return (this.getHeight() - used) / 2;
15109
+ if (va === "bottom") return this.getHeight() - used;
15110
+ return 0;
15111
+ }
15112
+ paintLine(ctx, line, y) {
15113
+ const decoration = this.textDecoration();
15114
+ const fontSize = this.fontSize();
15115
+ ctx.save();
15116
+ if (decoration.includes("underline")) this.strokeDecoration(ctx, line, y + Math.round(fontSize / 4));
15117
+ for (const seg of line.segments) this.paintSegment(ctx, seg.t, line.offsetX + seg.x, y);
15118
+ if (decoration.includes("line-through")) this.strokeDecoration(ctx, line, y - Math.round(fontSize / 4));
15119
+ ctx.restore();
15120
+ }
15121
+ strokeDecoration(ctx, line, y) {
15122
+ ctx.save();
15123
+ ctx.beginPath();
15124
+ ctx.moveTo(line.offsetX, y);
15125
+ ctx.lineTo(line.offsetX + Math.round(line.width), y);
15126
+ ctx.lineWidth = this.fontSize() / 15;
15127
+ ctx.strokeStyle = this.fill();
15128
+ ctx.stroke();
15129
+ ctx.restore();
15130
+ }
15131
+ paintSegment(ctx, text, x, y) {
15132
+ const spacing = this.letterSpacing();
15133
+ if (spacing === 0) {
15134
+ this.fillText(ctx, text, x, y);
15135
+ return;
15136
+ }
15137
+ let cx = x;
15138
+ for (const ch of Array.from(text)) {
15139
+ this.fillText(ctx, ch, cx, y);
15140
+ cx += this.measureSize(ch).width + spacing;
15141
+ }
15142
+ }
15143
+ fillText(ctx, text, x, y) {
15144
+ const self = this;
15145
+ self._partialText = text;
15146
+ self._partialTextX = x;
15147
+ self._partialTextY = y;
15148
+ ctx.fillStrokeShape(this);
15149
+ }
15150
+ _hitFunc = (ctx, shape) => {
15107
15151
  ctx.beginPath();
15108
15152
  const self = shape;
15109
15153
  ctx.rect(0, 0, self.getWidth(), self.getHeight());
@@ -15211,7 +15255,7 @@ function lineIndexAt(layout, offset) {
15211
15255
  if (l.start <= offset) fallback = i;
15212
15256
  if (offset >= l.start && offset <= l.end) {
15213
15257
  const next = lines[i + 1];
15214
- if (offset === l.end && !l.lastInParagraph && next && next.start === offset) continue;
15258
+ if (offset === l.end && !l.lastInParagraph && next?.start === offset) continue;
15215
15259
  return i;
15216
15260
  }
15217
15261
  }
@@ -15260,10 +15304,7 @@ function offsetInLine(line, x, measure) {
15260
15304
  function moveVertical(layout, offset, dir, desiredX, measure) {
15261
15305
  const j = lineIndexAt(layout, offset) + dir;
15262
15306
  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
- }
15307
+ if (j >= layout.lines.length) return layout.lines.at(-1)?.end ?? 0;
15267
15308
  return offsetInLine(layout.lines[j], desiredX, measure);
15268
15309
  }
15269
15310
  function lineHome(layout, offset) {
@@ -15493,9 +15534,10 @@ var TextEditSession = class {
15493
15534
  capture: true
15494
15535
  });
15495
15536
  window.weaveTextEditing = {
15496
- ...window.weaveTextEditing ?? {},
15537
+ ...window.weaveTextEditing,
15497
15538
  [this.id]: this.id
15498
15539
  };
15540
+ if (this.host.selectAllOnStart) this.model.selectAll();
15499
15541
  this.syncTextarea();
15500
15542
  ta.focus();
15501
15543
  this.positionTextarea();
@@ -15599,41 +15641,43 @@ var TextEditSession = class {
15599
15641
  this.afterSelectionChange();
15600
15642
  }
15601
15643
  };
15602
- onKeyDown = (e) => {
15603
- e.stopPropagation();
15644
+ /** Keys the session handles itself; everything else stays native. */
15645
+ handleSessionKey(e) {
15604
15646
  const mod = e.ctrlKey || e.metaKey;
15605
15647
  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;
15648
+ const isUndo = mod && key === "z" && !e.shiftKey;
15649
+ const isRedo = mod && (key === "y" || key === "z" && e.shiftKey);
15650
+ if (e.key === "Escape") this.host.onRequestExit("escape");
15651
+ else if (isUndo) this.undo();
15652
+ else if (isRedo) this.redo();
15653
+ else if (e.key === "Tab") return true;
15654
+ else return false;
15655
+ return true;
15656
+ }
15657
+ handleNavigationKey(e) {
15658
+ if (e.ctrlKey || e.metaKey) return false;
15659
+ if ((e.key === "ArrowUp" || e.key === "ArrowDown") && !e.altKey) {
15660
+ this.moveVertically(e.key === "ArrowUp" ? -1 : 1, e.shiftKey);
15661
+ return true;
15615
15662
  }
15616
- if (mod && (key === "y" || key === "z" && e.shiftKey)) {
15617
- e.preventDefault();
15618
- this.redo();
15619
- return;
15663
+ if (e.key === "Home" || e.key === "End") {
15664
+ const layout = this.target.getLayout();
15665
+ const to = e.key === "Home" ? lineHome(layout, this.model.focus) : lineEnd(layout, this.model.focus);
15666
+ this.model.moveTo(to, e.shiftKey);
15667
+ this.afterSelectionChange();
15668
+ return true;
15620
15669
  }
15621
- if (e.key === "Tab") {
15670
+ return false;
15671
+ }
15672
+ onKeyDown = (e) => {
15673
+ e.stopPropagation();
15674
+ if (this.handleSessionKey(e)) {
15622
15675
  e.preventDefault();
15623
15676
  return;
15624
15677
  }
15625
15678
  if (this.composing) return;
15626
- if ((e.key === "ArrowUp" || e.key === "ArrowDown") && !mod && !e.altKey) {
15679
+ if (this.handleNavigationKey(e)) {
15627
15680
  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
15681
  return;
15638
15682
  }
15639
15683
  setTimeout(this.syncFromTextarea, 0);
@@ -15647,7 +15691,7 @@ var TextEditSession = class {
15647
15691
  };
15648
15692
  moveVertically(dir, extend) {
15649
15693
  const layout = this.target.getLayout();
15650
- if (this.model.desiredX === null) this.model.desiredX = caretRect(layout, this.model.focus, this.target.measure).x;
15694
+ this.model.desiredX ??= caretRect(layout, this.model.focus, this.target.measure).x;
15651
15695
  const to = moveVertical(layout, this.model.focus, dir, this.model.desiredX, this.target.measure);
15652
15696
  this.model.moveTo(to, extend, true);
15653
15697
  this.afterSelectionChange();
@@ -15665,11 +15709,17 @@ var TextEditSession = class {
15665
15709
  while (s < max - p && oldText[oldText.length - 1 - s] === newText[newText.length - 1 - s]) s++;
15666
15710
  const inserted = newText.slice(p, newText.length - s);
15667
15711
  const inputType = e.inputType ?? "";
15668
- const kind = this.composing || inputType.includes("Composition") ? "ime" : inputType.startsWith("delete") ? "delete" : inputType === "insertFromPaste" ? "paste" : "type";
15712
+ const kind = this.editKindFor(inputType);
15669
15713
  this.model.replaceRange(p, oldText.length - s, inserted, kind);
15670
15714
  this.model.anchor = this.model.focus = Math.min(ta.selectionEnd, this.model.getText().length);
15671
15715
  this.afterContentChange(inserted.length);
15672
15716
  };
15717
+ editKindFor(inputType) {
15718
+ if (this.composing || inputType.includes("Composition")) return "ime";
15719
+ if (inputType.startsWith("delete")) return "delete";
15720
+ if (inputType === "insertFromPaste") return "paste";
15721
+ return "type";
15722
+ }
15673
15723
  onPaste = (e) => {
15674
15724
  e.stopPropagation();
15675
15725
  e.preventDefault();
@@ -15816,6 +15866,17 @@ var TextEditSession = class {
15816
15866
  const o = this.target.getContentOffset();
15817
15867
  ctx.save();
15818
15868
  ctx.transform(rel.m[0], rel.m[1], rel.m[2], rel.m[3], rel.m[4], rel.m[5]);
15869
+ if (this.host.outlineColor) {
15870
+ const scale = Math.hypot(abs.m[0], abs.m[1]) || 1;
15871
+ const lw = (this.host.outlineWidth ?? 1) / scale;
15872
+ const { width, height } = this.target.getSize();
15873
+ ctx.setAttr("strokeStyle", this.host.outlineColor);
15874
+ ctx.setAttr("lineWidth", lw);
15875
+ ctx.beginPath();
15876
+ const pad = (this.host.outlinePadding ?? 0) / scale;
15877
+ ctx.rect(-pad, -pad, width + pad * 2, height + pad * 2);
15878
+ ctx.stroke();
15879
+ }
15819
15880
  ctx.translate(o.x, o.y);
15820
15881
  const { start, end } = this.model.getSelection();
15821
15882
  if (start !== end) {
@@ -15859,6 +15920,12 @@ const WEAVE_TEXT_V2_SIZE_WARNING_THRESHOLD = 2e5;
15859
15920
  const WEAVE_TEXT_V2_SELECTION_COLOR = "rgba(66, 133, 244, 0.35)";
15860
15921
  const WEAVE_TEXT_V2_DEFAULT_CONFIG = {
15861
15922
  ...WEAVE_TEXT_NODE_DEFAULT_CONFIG,
15923
+ edition: { outline: {
15924
+ enabled: true,
15925
+ color: "#0074ff",
15926
+ width: 1,
15927
+ padding: 0
15928
+ } },
15862
15929
  sizeWarning: { threshold: WEAVE_TEXT_V2_SIZE_WARNING_THRESHOLD }
15863
15930
  };
15864
15931
  //#endregion
@@ -15870,6 +15937,8 @@ const CORNER_ANCHORS = [
15870
15937
  "bottom-right"
15871
15938
  ];
15872
15939
  const SIDE_ANCHORS = ["middle-right", "middle-left"];
15940
+ const CORNER_ANCHOR_SET = new Set(CORNER_ANCHORS);
15941
+ const SIDE_ANCHOR_SET = new Set(SIDE_ANCHORS);
15873
15942
  /**
15874
15943
  * Drop-in replacement for `WeaveTextNode`. Same `nodeType` (`'text'`), so
15875
15944
  * existing documents load unchanged; register it INSTEAD of `WeaveTextNode`
@@ -16103,8 +16172,14 @@ var WeaveTextV2Node = class extends WeaveNode {
16103
16172
  y: p.y * sy
16104
16173
  };
16105
16174
  },
16175
+ selectAllOnStart: !create,
16106
16176
  caretColor: this.config.cursor.color,
16107
16177
  selectionColor: WEAVE_TEXT_V2_SELECTION_COLOR,
16178
+ ...this.config.edition.outline.enabled && {
16179
+ outlineColor: this.config.edition.outline.color,
16180
+ outlineWidth: this.config.edition.outline.width,
16181
+ outlinePadding: this.config.edition.outline.padding
16182
+ },
16108
16183
  signal: this.instance.getEventsController().signal,
16109
16184
  onChange: (blocks) => this.checkSize(shape, blocks),
16110
16185
  onRequestExit: () => this.exitEditMode(shape)
@@ -16170,8 +16245,8 @@ var WeaveTextV2Node = class extends WeaveNode {
16170
16245
  }
16171
16246
  setupTransform(text) {
16172
16247
  let actualAnchor = void 0;
16173
- const isSide = () => SIDE_ANCHORS.includes(actualAnchor ?? "");
16174
- const isCorner = () => CORNER_ANCHORS.includes(actualAnchor ?? "");
16248
+ const isSide = () => SIDE_ANCHOR_SET.has(actualAnchor ?? "");
16249
+ const isCorner = () => CORNER_ANCHOR_SET.has(actualAnchor ?? "");
16175
16250
  const autoWidthLayout = () => [TEXT_LAYOUT.AUTO_HEIGHT, TEXT_LAYOUT.SMART].includes(text.getAttrs().layout);
16176
16251
  text.on("transformstart", (e) => {
16177
16252
  const ctrlOrMeta = e.evt.ctrlKey || e.evt.metaKey;
@@ -16268,6 +16343,7 @@ var WeaveTextV2Node = class extends WeaveNode {
16268
16343
  serialize(instance) {
16269
16344
  const attrs = instance.getAttrs();
16270
16345
  const cleanedAttrs = { ...attrs };
16346
+ for (const key of Object.keys(cleanedAttrs)) if (typeof cleanedAttrs[key] === "function") delete cleanedAttrs[key];
16271
16347
  delete cleanedAttrs.mutexLocked;
16272
16348
  delete cleanedAttrs.mutexUserId;
16273
16349
  delete cleanedAttrs.draggable;