@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/types.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 {
@@ -14455,7 +14455,7 @@ function normalizeRuns(runs) {
14455
14455
  const out = [];
14456
14456
  for (const run of runs) {
14457
14457
  if (run.t === "") continue;
14458
- const last = out[out.length - 1];
14458
+ const last = out.at(-1);
14459
14459
  if (last && sameMarks(last.m, run.m)) last.t += run.t;
14460
14460
  else out.push({
14461
14461
  t: run.t,
@@ -14530,7 +14530,7 @@ function replaceRange(blocks, start, end, insert) {
14530
14530
  const suffixBlocks = [];
14531
14531
  let inheritedMarks;
14532
14532
  let pos = 0;
14533
- blocks.forEach((block, i) => {
14533
+ blocks.forEach((block) => {
14534
14534
  const len = blockText(block).length;
14535
14535
  const bStart = pos;
14536
14536
  const bEnd = pos + len;
@@ -14595,6 +14595,43 @@ const now = () => typeof performance !== "undefined" ? performance.now() : Date.
14595
14595
  const WS = /\s/;
14596
14596
  const MAX_CACHE = 2e4;
14597
14597
  const MAX_CACHED_CHUNK = 256;
14598
+ function flattenBlock(block) {
14599
+ const graphemes = [];
14600
+ const runOf = [];
14601
+ for (let ri = 0; ri < block.runs.length; ri++) for (const g of splitGraphemes(block.runs[ri].t)) {
14602
+ graphemes.push(g);
14603
+ runOf.push(ri);
14604
+ }
14605
+ return {
14606
+ graphemes,
14607
+ runOf
14608
+ };
14609
+ }
14610
+ function trimTrailingSpace(graphemes, start, end) {
14611
+ let e = end;
14612
+ while (e > start && WS.test(graphemes[e - 1])) e--;
14613
+ return e;
14614
+ }
14615
+ function skipSpace(graphemes, from) {
14616
+ let i = from;
14617
+ while (i < graphemes.length && WS.test(graphemes[i])) i++;
14618
+ return i;
14619
+ }
14620
+ const isNum = (v) => v !== void 0 && !Number.isNaN(v);
14621
+ /** Konva's word wrap: break after the last space/dash that fits (0 = no break). */
14622
+ function wordWrapIndex(graphemes, start, matchLen, matchWidth, maxWidth) {
14623
+ const nextChar = graphemes[start + matchLen];
14624
+ if ((nextChar === " " || nextChar === "-") && matchWidth <= maxWidth) return matchLen;
14625
+ const seg = graphemes.slice(start, start + matchLen);
14626
+ return Math.max(seg.lastIndexOf(" "), seg.lastIndexOf("-")) + 1;
14627
+ }
14628
+ function applyAlign(lines, align, width, padding) {
14629
+ if (align === "left") return;
14630
+ for (const line of lines) {
14631
+ const free = width - line.width - padding * 2;
14632
+ line.offsetX = align === "center" ? free / 2 : free;
14633
+ }
14634
+ }
14598
14635
  var TextLayoutEngine = class {
14599
14636
  measure;
14600
14637
  cache = /* @__PURE__ */ new Map();
@@ -14652,8 +14689,8 @@ var TextLayoutEngine = class {
14652
14689
  layout(blocks, style, bounds) {
14653
14690
  const font = fontString(style);
14654
14691
  const padding = bounds.padding;
14655
- const fixedWidth = bounds.width !== void 0 && !Number.isNaN(bounds.width);
14656
- const fixedHeight = bounds.height !== void 0 && !Number.isNaN(bounds.height);
14692
+ const fixedWidth = isNum(bounds.width);
14693
+ const fixedHeight = isNum(bounds.height);
14657
14694
  const maxWidth = (bounds.width ?? 0) - padding * 2;
14658
14695
  const maxHeightPx = (bounds.height ?? 0) - padding * 2;
14659
14696
  const lineHeightPx = style.lineHeight * style.fontSize;
@@ -14663,9 +14700,8 @@ var TextLayoutEngine = class {
14663
14700
  let relaid = 0;
14664
14701
  const lines = [];
14665
14702
  let textWidth = 0;
14666
- let currentHeightPx = 0;
14667
14703
  let offset = 0;
14668
- outer: for (let p = 0; p < blocks.length; p++) {
14704
+ for (let p = 0; p < blocks.length; p++) {
14669
14705
  const block = blocks[p];
14670
14706
  const sig = `${sigBase}|${JSON.stringify(block.runs)}`;
14671
14707
  let entry = prev[p]?.sig === sig ? prev[p] : this.pool.get(sig);
@@ -14674,27 +14710,14 @@ var TextLayoutEngine = class {
14674
14710
  relaid++;
14675
14711
  }
14676
14712
  next.push(entry);
14677
- for (const pl of entry.lines) {
14678
- const text = entry.graphemes.slice(pl.startG, pl.endG).join("");
14679
- const segments = this.buildSegments(entry, block, pl, font, style);
14680
- const width = segments.reduce((a, s) => a + s.width, 0);
14681
- const startChar = entry.graphemes.slice(0, pl.startG).join("").length;
14682
- lines.push({
14683
- paragraph: p,
14684
- start: offset + startChar,
14685
- end: offset + startChar + text.length,
14686
- text,
14687
- width,
14688
- measuredWidth: pl.measuredWidth,
14689
- offsetX: 0,
14690
- y: lines.length * lineHeightPx,
14691
- segments,
14692
- lastInParagraph: pl.lastInParagraph
14693
- });
14694
- textWidth = Math.max(textWidth, pl.measuredWidth);
14695
- currentHeightPx += lineHeightPx;
14696
- if (fixedHeight && currentHeightPx + lineHeightPx > maxHeightPx) break outer;
14697
- }
14713
+ const emitted = this.emitLines(entry, block, p, offset, lines, {
14714
+ font,
14715
+ style,
14716
+ lineHeightPx,
14717
+ maxHeightPx: fixedHeight ? maxHeightPx : void 0
14718
+ });
14719
+ textWidth = Math.max(textWidth, emitted.maxWidth);
14720
+ if (emitted.stopped) break;
14698
14721
  offset += blockText(block).length + 1;
14699
14722
  }
14700
14723
  this.paragraphs = next;
@@ -14702,10 +14725,7 @@ var TextLayoutEngine = class {
14702
14725
  this.lastParagraphsRelaid = relaid;
14703
14726
  const width = fixedWidth ? bounds.width : textWidth + padding * 2;
14704
14727
  const height = fixedHeight ? bounds.height : lines.length * lineHeightPx + padding * 2;
14705
- if (style.align !== "left") for (const line of lines) {
14706
- const free = width - line.width - padding * 2;
14707
- line.offsetX = style.align === "center" ? free / 2 : free;
14708
- }
14728
+ applyAlign(lines, style.align, width, padding);
14709
14729
  return {
14710
14730
  lines,
14711
14731
  textWidth,
@@ -14715,7 +14735,38 @@ var TextLayoutEngine = class {
14715
14735
  height
14716
14736
  };
14717
14737
  }
14718
- rangeWidth(entry, runs, from, to, font, spacing) {
14738
+ /** Append a paragraph's lines to `lines`; stops once a fixed height is full. */
14739
+ emitLines(entry, block, paragraph, offset, lines, opts) {
14740
+ const { font, style, lineHeightPx, maxHeightPx } = opts;
14741
+ let maxWidth = 0;
14742
+ for (const pl of entry.lines) {
14743
+ const text = entry.graphemes.slice(pl.startG, pl.endG).join("");
14744
+ const segments = this.buildSegments(entry, block, pl, font, style);
14745
+ const startChar = entry.graphemes.slice(0, pl.startG).join("").length;
14746
+ lines.push({
14747
+ paragraph,
14748
+ start: offset + startChar,
14749
+ end: offset + startChar + text.length,
14750
+ text,
14751
+ width: segments.reduce((a, s) => a + s.width, 0),
14752
+ measuredWidth: pl.measuredWidth,
14753
+ offsetX: 0,
14754
+ y: lines.length * lineHeightPx,
14755
+ segments,
14756
+ lastInParagraph: pl.lastInParagraph
14757
+ });
14758
+ maxWidth = Math.max(maxWidth, pl.measuredWidth);
14759
+ if (maxHeightPx !== void 0 && (lines.length + 1) * lineHeightPx > maxHeightPx) return {
14760
+ stopped: true,
14761
+ maxWidth
14762
+ };
14763
+ }
14764
+ return {
14765
+ stopped: false,
14766
+ maxWidth
14767
+ };
14768
+ }
14769
+ rangeWidth(entry, from, to, font, spacing) {
14719
14770
  let width = 0;
14720
14771
  let i = from;
14721
14772
  while (i < to) {
@@ -14727,15 +14778,39 @@ var TextLayoutEngine = class {
14727
14778
  }
14728
14779
  return width;
14729
14780
  }
14781
+ /**
14782
+ * Longest prefix of graphemes[start..n) that fits `maxWidth`, found with an
14783
+ * exponential probe (bounds the work to about one line) plus a binary
14784
+ * search, like Konva. Always consumes at least one grapheme.
14785
+ */
14786
+ longestFit(start, n, maxWidth, width) {
14787
+ let low = 0;
14788
+ let high = n - start;
14789
+ let probe = Math.min(high, 32);
14790
+ while (probe < high && width(start, start + probe) <= maxWidth) probe = Math.min(high, probe * 2);
14791
+ high = probe;
14792
+ let matchLen = 0;
14793
+ let matchWidth = 0;
14794
+ while (low < high) {
14795
+ const mid = low + high >>> 1;
14796
+ const w = width(start, start + mid + 1);
14797
+ if (w <= maxWidth) {
14798
+ low = mid + 1;
14799
+ matchLen = mid + 1;
14800
+ matchWidth = w;
14801
+ } else high = mid;
14802
+ }
14803
+ if (matchLen === 0) {
14804
+ matchLen = 1;
14805
+ matchWidth = width(start, start + 1);
14806
+ }
14807
+ return {
14808
+ matchLen,
14809
+ matchWidth
14810
+ };
14811
+ }
14730
14812
  layoutParagraph(block, sig, font, style, fixedWidth, maxWidth) {
14731
- const graphemes = [];
14732
- const runOf = [];
14733
- block.runs.forEach((run, ri) => {
14734
- for (const g of splitGraphemes(run.t)) {
14735
- graphemes.push(g);
14736
- runOf.push(ri);
14737
- }
14738
- });
14813
+ const { graphemes, runOf } = flattenBlock(block);
14739
14814
  const entry = {
14740
14815
  sig,
14741
14816
  graphemes,
@@ -14744,7 +14819,7 @@ var TextLayoutEngine = class {
14744
14819
  };
14745
14820
  const n = graphemes.length;
14746
14821
  const spacing = style.letterSpacing;
14747
- const width = (a, b) => this.rangeWidth(entry, block.runs, a, b, font, spacing);
14822
+ const width = (a, b) => this.rangeWidth(entry, a, b, font, spacing);
14748
14823
  const shouldWrap = style.wrap !== "none";
14749
14824
  const wrapAtWord = style.wrap !== "char" && shouldWrap;
14750
14825
  const whole = width(0, n);
@@ -14759,26 +14834,7 @@ var TextLayoutEngine = class {
14759
14834
  }
14760
14835
  let start = 0;
14761
14836
  while (start < n) {
14762
- let low = 0;
14763
- let high = n - start;
14764
- let probe = Math.min(high, 32);
14765
- while (probe < high && width(start, start + probe) <= maxWidth) probe = Math.min(high, probe * 2);
14766
- high = probe;
14767
- let matchLen = 0;
14768
- let matchWidth = 0;
14769
- while (low < high) {
14770
- const mid = low + high >>> 1;
14771
- const w = width(start, start + mid + 1);
14772
- if (w <= maxWidth) {
14773
- low = mid + 1;
14774
- matchLen = mid + 1;
14775
- matchWidth = w;
14776
- } else high = mid;
14777
- }
14778
- if (matchLen === 0) {
14779
- matchLen = 1;
14780
- matchWidth = width(start, start + 1);
14781
- }
14837
+ let { matchLen, matchWidth } = this.longestFit(start, n, maxWidth, width);
14782
14838
  if (start + matchLen >= n) {
14783
14839
  entry.lines.push({
14784
14840
  startG: start,
@@ -14789,32 +14845,22 @@ var TextLayoutEngine = class {
14789
14845
  return entry;
14790
14846
  }
14791
14847
  if (wrapAtWord) {
14792
- const nextChar = graphemes[start + matchLen];
14793
- const nextIsSpaceOrDash = nextChar === " " || nextChar === "-";
14794
- let wrapIndex;
14795
- if (nextIsSpaceOrDash && matchWidth <= maxWidth) wrapIndex = matchLen;
14796
- else {
14797
- const seg = graphemes.slice(start, start + matchLen);
14798
- wrapIndex = Math.max(seg.lastIndexOf(" "), seg.lastIndexOf("-")) + 1;
14799
- }
14800
- if (wrapIndex > 0) {
14801
- matchLen = wrapIndex;
14848
+ const wrapped = wordWrapIndex(graphemes, start, matchLen, matchWidth, maxWidth);
14849
+ if (wrapped > 0) {
14850
+ matchLen = wrapped;
14802
14851
  matchWidth = width(start, start + matchLen);
14803
14852
  }
14804
14853
  }
14805
- let end = start + matchLen;
14806
- let trimmedEnd = end;
14807
- while (trimmedEnd > start && WS.test(graphemes[trimmedEnd - 1])) trimmedEnd--;
14854
+ const end = start + matchLen;
14808
14855
  entry.lines.push({
14809
14856
  startG: start,
14810
- endG: trimmedEnd,
14857
+ endG: trimTrailingSpace(graphemes, start, end),
14811
14858
  measuredWidth: matchWidth,
14812
14859
  lastInParagraph: false
14813
14860
  });
14814
- while (end < n && WS.test(graphemes[end])) end++;
14815
- start = end;
14861
+ start = skipSpace(graphemes, end);
14816
14862
  }
14817
- const last = entry.lines[entry.lines.length - 1];
14863
+ const last = entry.lines.at(-1);
14818
14864
  if (last) last.lastInParagraph = true;
14819
14865
  return entry;
14820
14866
  }
@@ -14876,8 +14922,6 @@ var WeaveTextV2Shape = class extends Konva.Shape {
14876
14922
  this.on(LAYOUT_ATTRS.map((a) => `${a}Change.konva`).join(" "), () => {
14877
14923
  this.layoutCache = null;
14878
14924
  });
14879
- this.sceneFunc(this._paint);
14880
- this.hitFunc(this._hit);
14881
14925
  }
14882
14926
  text(value) {
14883
14927
  if (value === void 0) return typeof this.attrs.text === "string" ? this.attrs.text : blocksToText(this.getBlocks());
@@ -15039,48 +15083,47 @@ var WeaveTextV2Shape = class extends Konva.Shape {
15039
15083
  setViewport(range) {
15040
15084
  this.viewport = range;
15041
15085
  }
15042
- visibleRange() {
15086
+ /**
15087
+ * Visible y-range in local coordinates, derived from the context being
15088
+ * painted (current transform + backing canvas size) instead of the stage.
15089
+ * The stage is not the paint target during export (off-screen canvases,
15090
+ * tiles, server-side), so using it culled visible lines. Returns null (paint
15091
+ * everything) when the transform cannot be read.
15092
+ */
15093
+ visibleRange(ctx) {
15043
15094
  if (this.viewport) return this.viewport;
15044
- const stage = this.getStage();
15045
- if (!stage) return null;
15046
- const inv = this.getAbsoluteTransform().copy().invert();
15047
- const w = stage.width();
15048
- const h = stage.height();
15049
- const pts = [
15050
- inv.point({
15051
- x: 0,
15052
- y: 0
15053
- }),
15054
- inv.point({
15055
- x: w,
15056
- y: 0
15057
- }),
15058
- inv.point({
15059
- x: 0,
15060
- y: h
15061
- }),
15062
- inv.point({
15063
- x: w,
15064
- y: h
15065
- })
15066
- ];
15095
+ const native = ctx._context;
15096
+ const m = native.getTransform?.() ?? native.currentTransform;
15097
+ if (!m) return null;
15098
+ const det = m.a * m.d - m.b * m.c;
15099
+ if (!det || !Number.isFinite(det)) return null;
15100
+ const canvas = ctx.getCanvas();
15101
+ const w = canvas.getWidth();
15102
+ const h = canvas.getHeight();
15103
+ if (!(w > 0) || !(h > 0)) return null;
15104
+ let y1 = Infinity;
15105
+ let y2 = -Infinity;
15106
+ for (const [px, py] of [
15107
+ [0, 0],
15108
+ [w, 0],
15109
+ [0, h],
15110
+ [w, h]
15111
+ ]) {
15112
+ const y = (m.a * (py - m.f) - m.b * (px - m.e)) / det;
15113
+ if (y < y1) y1 = y;
15114
+ if (y > y2) y2 = y;
15115
+ }
15067
15116
  return {
15068
- y1: Math.min(...pts.map((p) => p.y)),
15069
- y2: Math.max(...pts.map((p) => p.y))
15117
+ y1,
15118
+ y2
15070
15119
  };
15071
15120
  }
15072
- _paint = (ctx, shape) => {
15121
+ _sceneFunc = (ctx, shape) => {
15073
15122
  const self = shape;
15074
15123
  const layout = self.ensureLayout();
15075
15124
  if (layout.lines.length === 0) return;
15076
- const fontSize = self.fontSize();
15077
15125
  const padding = self.padding();
15078
15126
  const lineHeightPx = layout.lineHeightPx;
15079
- const decoration = self.textDecoration();
15080
- const underline = decoration.includes("underline");
15081
- const lineThrough = decoration.includes("line-through");
15082
- const spacing = self.letterSpacing();
15083
- const fill = self.fill();
15084
15127
  let baseline = "middle";
15085
15128
  let translateY = lineHeightPx / 2;
15086
15129
  if (!Konva.legacyTextRendering) {
@@ -15091,62 +15134,63 @@ var WeaveTextV2Shape = class extends Konva.Shape {
15091
15134
  ctx.setAttr("font", fontString(self.style()));
15092
15135
  ctx.setAttr("textBaseline", baseline);
15093
15136
  ctx.setAttr("textAlign", "left");
15094
- let alignY = 0;
15095
- const va = self.verticalAlign();
15096
- const used = layout.lines.length * lineHeightPx + padding * 2;
15097
- if (va === "middle") alignY = (self.getHeight() - used) / 2;
15098
- else if (va === "bottom") alignY = self.getHeight() - used;
15137
+ const alignY = self.verticalOffset(layout);
15138
+ const range = self.visibleRange(ctx);
15099
15139
  ctx.translate(padding, alignY + padding);
15100
- const range = self.visibleRange();
15101
- const paintSelf = self;
15140
+ const top = alignY + padding;
15102
15141
  for (let n = 0; n < layout.lines.length; n++) {
15103
- const line = layout.lines[n];
15104
- const y = translateY + n * lineHeightPx;
15105
- if (range && (alignY + padding + n * lineHeightPx + lineHeightPx < range.y1 - lineHeightPx || alignY + padding + n * lineHeightPx > range.y2 + lineHeightPx)) continue;
15106
- ctx.save();
15107
- const baseX = line.offsetX;
15108
- if (underline) {
15109
- ctx.save();
15110
- ctx.beginPath();
15111
- const uy = y + Math.round(fontSize / 4);
15112
- ctx.moveTo(baseX, uy);
15113
- ctx.lineTo(baseX + Math.round(line.width), uy);
15114
- ctx.lineWidth = fontSize / 15;
15115
- ctx.strokeStyle = fill;
15116
- ctx.stroke();
15117
- ctx.restore();
15118
- }
15119
- for (const seg of line.segments) {
15120
- let x = baseX + seg.x;
15121
- if (spacing !== 0) for (const ch of Array.from(seg.t)) {
15122
- paintSelf._partialText = ch;
15123
- paintSelf._partialTextX = x;
15124
- paintSelf._partialTextY = y;
15125
- ctx.fillStrokeShape(self);
15126
- x += self.measureSize(ch).width + spacing;
15127
- }
15128
- else {
15129
- paintSelf._partialText = seg.t;
15130
- paintSelf._partialTextX = x;
15131
- paintSelf._partialTextY = y;
15132
- ctx.fillStrokeShape(self);
15133
- }
15134
- }
15135
- if (lineThrough) {
15136
- ctx.save();
15137
- ctx.beginPath();
15138
- const ly = y - Math.round(fontSize / 4);
15139
- ctx.moveTo(baseX, ly);
15140
- ctx.lineTo(baseX + Math.round(line.width), ly);
15141
- ctx.lineWidth = fontSize / 15;
15142
- ctx.strokeStyle = fill;
15143
- ctx.stroke();
15144
- ctx.restore();
15145
- }
15146
- ctx.restore();
15142
+ const lineTop = top + n * lineHeightPx;
15143
+ if (range && (lineTop + lineHeightPx < range.y1 - lineHeightPx || lineTop > range.y2 + lineHeightPx)) continue;
15144
+ self.paintLine(ctx, layout.lines[n], translateY + n * lineHeightPx);
15147
15145
  }
15148
15146
  };
15149
- _hit = (ctx, shape) => {
15147
+ /** Vertical offset of the text block inside the box (verticalAlign). */
15148
+ verticalOffset(layout) {
15149
+ const used = layout.lines.length * layout.lineHeightPx + this.padding() * 2;
15150
+ const va = this.verticalAlign();
15151
+ if (va === "middle") return (this.getHeight() - used) / 2;
15152
+ if (va === "bottom") return this.getHeight() - used;
15153
+ return 0;
15154
+ }
15155
+ paintLine(ctx, line, y) {
15156
+ const decoration = this.textDecoration();
15157
+ const fontSize = this.fontSize();
15158
+ ctx.save();
15159
+ if (decoration.includes("underline")) this.strokeDecoration(ctx, line, y + Math.round(fontSize / 4));
15160
+ for (const seg of line.segments) this.paintSegment(ctx, seg.t, line.offsetX + seg.x, y);
15161
+ if (decoration.includes("line-through")) this.strokeDecoration(ctx, line, y - Math.round(fontSize / 4));
15162
+ ctx.restore();
15163
+ }
15164
+ strokeDecoration(ctx, line, y) {
15165
+ ctx.save();
15166
+ ctx.beginPath();
15167
+ ctx.moveTo(line.offsetX, y);
15168
+ ctx.lineTo(line.offsetX + Math.round(line.width), y);
15169
+ ctx.lineWidth = this.fontSize() / 15;
15170
+ ctx.strokeStyle = this.fill();
15171
+ ctx.stroke();
15172
+ ctx.restore();
15173
+ }
15174
+ paintSegment(ctx, text, x, y) {
15175
+ const spacing = this.letterSpacing();
15176
+ if (spacing === 0) {
15177
+ this.fillText(ctx, text, x, y);
15178
+ return;
15179
+ }
15180
+ let cx = x;
15181
+ for (const ch of Array.from(text)) {
15182
+ this.fillText(ctx, ch, cx, y);
15183
+ cx += this.measureSize(ch).width + spacing;
15184
+ }
15185
+ }
15186
+ fillText(ctx, text, x, y) {
15187
+ const self = this;
15188
+ self._partialText = text;
15189
+ self._partialTextX = x;
15190
+ self._partialTextY = y;
15191
+ ctx.fillStrokeShape(this);
15192
+ }
15193
+ _hitFunc = (ctx, shape) => {
15150
15194
  ctx.beginPath();
15151
15195
  const self = shape;
15152
15196
  ctx.rect(0, 0, self.getWidth(), self.getHeight());
@@ -15254,7 +15298,7 @@ function lineIndexAt(layout, offset) {
15254
15298
  if (l.start <= offset) fallback = i;
15255
15299
  if (offset >= l.start && offset <= l.end) {
15256
15300
  const next = lines[i + 1];
15257
- if (offset === l.end && !l.lastInParagraph && next && next.start === offset) continue;
15301
+ if (offset === l.end && !l.lastInParagraph && next?.start === offset) continue;
15258
15302
  return i;
15259
15303
  }
15260
15304
  }
@@ -15303,10 +15347,7 @@ function offsetInLine(line, x, measure) {
15303
15347
  function moveVertical(layout, offset, dir, desiredX, measure) {
15304
15348
  const j = lineIndexAt(layout, offset) + dir;
15305
15349
  if (j < 0) return 0;
15306
- if (j >= layout.lines.length) {
15307
- const last = layout.lines[layout.lines.length - 1];
15308
- return last ? last.end : 0;
15309
- }
15350
+ if (j >= layout.lines.length) return layout.lines.at(-1)?.end ?? 0;
15310
15351
  return offsetInLine(layout.lines[j], desiredX, measure);
15311
15352
  }
15312
15353
  function lineHome(layout, offset) {
@@ -15536,9 +15577,10 @@ var TextEditSession = class {
15536
15577
  capture: true
15537
15578
  });
15538
15579
  window.weaveTextEditing = {
15539
- ...window.weaveTextEditing ?? {},
15580
+ ...window.weaveTextEditing,
15540
15581
  [this.id]: this.id
15541
15582
  };
15583
+ if (this.host.selectAllOnStart) this.model.selectAll();
15542
15584
  this.syncTextarea();
15543
15585
  ta.focus();
15544
15586
  this.positionTextarea();
@@ -15642,41 +15684,43 @@ var TextEditSession = class {
15642
15684
  this.afterSelectionChange();
15643
15685
  }
15644
15686
  };
15645
- onKeyDown = (e) => {
15646
- e.stopPropagation();
15687
+ /** Keys the session handles itself; everything else stays native. */
15688
+ handleSessionKey(e) {
15647
15689
  const mod = e.ctrlKey || e.metaKey;
15648
15690
  const key = e.key.toLowerCase();
15649
- if (e.key === "Escape") {
15650
- e.preventDefault();
15651
- this.host.onRequestExit("escape");
15652
- return;
15653
- }
15654
- if (mod && key === "z" && !e.shiftKey) {
15655
- e.preventDefault();
15656
- this.undo();
15657
- return;
15691
+ const isUndo = mod && key === "z" && !e.shiftKey;
15692
+ const isRedo = mod && (key === "y" || key === "z" && e.shiftKey);
15693
+ if (e.key === "Escape") this.host.onRequestExit("escape");
15694
+ else if (isUndo) this.undo();
15695
+ else if (isRedo) this.redo();
15696
+ else if (e.key === "Tab") return true;
15697
+ else return false;
15698
+ return true;
15699
+ }
15700
+ handleNavigationKey(e) {
15701
+ if (e.ctrlKey || e.metaKey) return false;
15702
+ if ((e.key === "ArrowUp" || e.key === "ArrowDown") && !e.altKey) {
15703
+ this.moveVertically(e.key === "ArrowUp" ? -1 : 1, e.shiftKey);
15704
+ return true;
15658
15705
  }
15659
- if (mod && (key === "y" || key === "z" && e.shiftKey)) {
15660
- e.preventDefault();
15661
- this.redo();
15662
- return;
15706
+ if (e.key === "Home" || e.key === "End") {
15707
+ const layout = this.target.getLayout();
15708
+ const to = e.key === "Home" ? lineHome(layout, this.model.focus) : lineEnd(layout, this.model.focus);
15709
+ this.model.moveTo(to, e.shiftKey);
15710
+ this.afterSelectionChange();
15711
+ return true;
15663
15712
  }
15664
- if (e.key === "Tab") {
15713
+ return false;
15714
+ }
15715
+ onKeyDown = (e) => {
15716
+ e.stopPropagation();
15717
+ if (this.handleSessionKey(e)) {
15665
15718
  e.preventDefault();
15666
15719
  return;
15667
15720
  }
15668
15721
  if (this.composing) return;
15669
- if ((e.key === "ArrowUp" || e.key === "ArrowDown") && !mod && !e.altKey) {
15722
+ if (this.handleNavigationKey(e)) {
15670
15723
  e.preventDefault();
15671
- this.moveVertically(e.key === "ArrowUp" ? -1 : 1, e.shiftKey);
15672
- return;
15673
- }
15674
- if ((e.key === "Home" || e.key === "End") && !mod) {
15675
- e.preventDefault();
15676
- const layout = this.target.getLayout();
15677
- const to = e.key === "Home" ? lineHome(layout, this.model.focus) : lineEnd(layout, this.model.focus);
15678
- this.model.moveTo(to, e.shiftKey);
15679
- this.afterSelectionChange();
15680
15724
  return;
15681
15725
  }
15682
15726
  setTimeout(this.syncFromTextarea, 0);
@@ -15690,7 +15734,7 @@ var TextEditSession = class {
15690
15734
  };
15691
15735
  moveVertically(dir, extend) {
15692
15736
  const layout = this.target.getLayout();
15693
- if (this.model.desiredX === null) this.model.desiredX = caretRect(layout, this.model.focus, this.target.measure).x;
15737
+ this.model.desiredX ??= caretRect(layout, this.model.focus, this.target.measure).x;
15694
15738
  const to = moveVertical(layout, this.model.focus, dir, this.model.desiredX, this.target.measure);
15695
15739
  this.model.moveTo(to, extend, true);
15696
15740
  this.afterSelectionChange();
@@ -15708,11 +15752,17 @@ var TextEditSession = class {
15708
15752
  while (s < max - p && oldText[oldText.length - 1 - s] === newText[newText.length - 1 - s]) s++;
15709
15753
  const inserted = newText.slice(p, newText.length - s);
15710
15754
  const inputType = e.inputType ?? "";
15711
- const kind = this.composing || inputType.includes("Composition") ? "ime" : inputType.startsWith("delete") ? "delete" : inputType === "insertFromPaste" ? "paste" : "type";
15755
+ const kind = this.editKindFor(inputType);
15712
15756
  this.model.replaceRange(p, oldText.length - s, inserted, kind);
15713
15757
  this.model.anchor = this.model.focus = Math.min(ta.selectionEnd, this.model.getText().length);
15714
15758
  this.afterContentChange(inserted.length);
15715
15759
  };
15760
+ editKindFor(inputType) {
15761
+ if (this.composing || inputType.includes("Composition")) return "ime";
15762
+ if (inputType.startsWith("delete")) return "delete";
15763
+ if (inputType === "insertFromPaste") return "paste";
15764
+ return "type";
15765
+ }
15716
15766
  onPaste = (e) => {
15717
15767
  e.stopPropagation();
15718
15768
  e.preventDefault();
@@ -15859,6 +15909,17 @@ var TextEditSession = class {
15859
15909
  const o = this.target.getContentOffset();
15860
15910
  ctx.save();
15861
15911
  ctx.transform(rel.m[0], rel.m[1], rel.m[2], rel.m[3], rel.m[4], rel.m[5]);
15912
+ if (this.host.outlineColor) {
15913
+ const scale = Math.hypot(abs.m[0], abs.m[1]) || 1;
15914
+ const lw = (this.host.outlineWidth ?? 1) / scale;
15915
+ const { width, height } = this.target.getSize();
15916
+ ctx.setAttr("strokeStyle", this.host.outlineColor);
15917
+ ctx.setAttr("lineWidth", lw);
15918
+ ctx.beginPath();
15919
+ const pad = (this.host.outlinePadding ?? 0) / scale;
15920
+ ctx.rect(-pad, -pad, width + pad * 2, height + pad * 2);
15921
+ ctx.stroke();
15922
+ }
15862
15923
  ctx.translate(o.x, o.y);
15863
15924
  const { start, end } = this.model.getSelection();
15864
15925
  if (start !== end) {
@@ -15902,6 +15963,12 @@ const WEAVE_TEXT_V2_SIZE_WARNING_THRESHOLD = 2e5;
15902
15963
  const WEAVE_TEXT_V2_SELECTION_COLOR = "rgba(66, 133, 244, 0.35)";
15903
15964
  const WEAVE_TEXT_V2_DEFAULT_CONFIG = {
15904
15965
  ...WEAVE_TEXT_NODE_DEFAULT_CONFIG,
15966
+ edition: { outline: {
15967
+ enabled: true,
15968
+ color: "#0074ff",
15969
+ width: 1,
15970
+ padding: 0
15971
+ } },
15905
15972
  sizeWarning: { threshold: WEAVE_TEXT_V2_SIZE_WARNING_THRESHOLD }
15906
15973
  };
15907
15974
  //#endregion
@@ -15913,6 +15980,8 @@ const CORNER_ANCHORS = [
15913
15980
  "bottom-right"
15914
15981
  ];
15915
15982
  const SIDE_ANCHORS = ["middle-right", "middle-left"];
15983
+ const CORNER_ANCHOR_SET = new Set(CORNER_ANCHORS);
15984
+ const SIDE_ANCHOR_SET = new Set(SIDE_ANCHORS);
15916
15985
  /**
15917
15986
  * Drop-in replacement for `WeaveTextNode`. Same `nodeType` (`'text'`), so
15918
15987
  * existing documents load unchanged; register it INSTEAD of `WeaveTextNode`
@@ -16146,8 +16215,14 @@ var WeaveTextV2Node = class extends WeaveNode {
16146
16215
  y: p.y * sy
16147
16216
  };
16148
16217
  },
16218
+ selectAllOnStart: !create,
16149
16219
  caretColor: this.config.cursor.color,
16150
16220
  selectionColor: WEAVE_TEXT_V2_SELECTION_COLOR,
16221
+ ...this.config.edition.outline.enabled && {
16222
+ outlineColor: this.config.edition.outline.color,
16223
+ outlineWidth: this.config.edition.outline.width,
16224
+ outlinePadding: this.config.edition.outline.padding
16225
+ },
16151
16226
  signal: this.instance.getEventsController().signal,
16152
16227
  onChange: (blocks) => this.checkSize(shape, blocks),
16153
16228
  onRequestExit: () => this.exitEditMode(shape)
@@ -16213,8 +16288,8 @@ var WeaveTextV2Node = class extends WeaveNode {
16213
16288
  }
16214
16289
  setupTransform(text) {
16215
16290
  let actualAnchor = void 0;
16216
- const isSide = () => SIDE_ANCHORS.includes(actualAnchor ?? "");
16217
- const isCorner = () => CORNER_ANCHORS.includes(actualAnchor ?? "");
16291
+ const isSide = () => SIDE_ANCHOR_SET.has(actualAnchor ?? "");
16292
+ const isCorner = () => CORNER_ANCHOR_SET.has(actualAnchor ?? "");
16218
16293
  const autoWidthLayout = () => [TEXT_LAYOUT.AUTO_HEIGHT, TEXT_LAYOUT.SMART].includes(text.getAttrs().layout);
16219
16294
  text.on("transformstart", (e) => {
16220
16295
  const ctrlOrMeta = e.evt.ctrlKey || e.evt.metaKey;
@@ -16311,6 +16386,7 @@ var WeaveTextV2Node = class extends WeaveNode {
16311
16386
  serialize(instance) {
16312
16387
  const attrs = instance.getAttrs();
16313
16388
  const cleanedAttrs = { ...attrs };
16389
+ for (const key of Object.keys(cleanedAttrs)) if (typeof cleanedAttrs[key] === "function") delete cleanedAttrs[key];
16314
16390
  delete cleanedAttrs.mutexLocked;
16315
16391
  delete cleanedAttrs.mutexUserId;
16316
16392
  delete cleanedAttrs.draggable;