@tldraw/editor 3.14.0-canary.b1a9df6c4d78 → 3.14.0-canary.b30c3e039479

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.
Files changed (32) hide show
  1. package/dist-cjs/index.d.ts +36 -50
  2. package/dist-cjs/index.js +1 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/editor/Editor.js +2 -20
  5. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  6. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +42 -72
  7. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
  8. package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
  9. package/dist-cjs/lib/hooks/useCanvasEvents.js +2 -1
  10. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  11. package/dist-cjs/version.js +3 -3
  12. package/dist-cjs/version.js.map +1 -1
  13. package/dist-esm/index.d.mts +36 -50
  14. package/dist-esm/index.mjs +1 -1
  15. package/dist-esm/index.mjs.map +2 -2
  16. package/dist-esm/lib/editor/Editor.mjs +2 -20
  17. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  18. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +42 -72
  19. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
  20. package/dist-esm/lib/hooks/useCanvasEvents.mjs +2 -1
  21. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  22. package/dist-esm/version.mjs +3 -3
  23. package/dist-esm/version.mjs.map +1 -1
  24. package/editor.css +1 -20
  25. package/package.json +7 -7
  26. package/src/index.ts +0 -2
  27. package/src/lib/editor/Editor.ts +0 -26
  28. package/src/lib/editor/managers/TextManager/TextManager.test.ts +5 -1
  29. package/src/lib/editor/managers/TextManager/TextManager.ts +86 -116
  30. package/src/lib/editor/types/external-content.ts +2 -11
  31. package/src/lib/hooks/useCanvasEvents.ts +1 -0
  32. package/src/version.ts +3 -3
@@ -14,70 +14,44 @@ const spaceCharacterRegex = /\s/;
14
14
  class TextManager {
15
15
  constructor(editor) {
16
16
  this.editor = editor;
17
- const elm = document.createElement("div");
18
- elm.classList.add("tl-text");
19
- elm.classList.add("tl-text-measure");
20
- elm.setAttribute("dir", "auto");
21
- elm.tabIndex = -1;
22
- this.editor.getContainer().appendChild(elm);
23
- this.defaultStyles = {
24
- "word-break": "auto",
25
- width: null,
26
- height: null,
27
- "max-width": null,
28
- "min-width": null
29
- };
30
- this.elm = elm;
31
- }
32
- elm;
33
- defaultStyles;
34
- dispose() {
35
- return this.elm.remove();
36
- }
37
- resetElmStyles() {
38
- const { elm, defaultStyles } = this;
39
- for (const key in defaultStyles) {
40
- elm.style.setProperty(key, defaultStyles[key]);
41
- }
17
+ this.baseElem = document.createElement("div");
18
+ this.baseElem.classList.add("tl-text");
19
+ this.baseElem.classList.add("tl-text-measure");
20
+ this.baseElem.tabIndex = -1;
42
21
  }
22
+ baseElem;
43
23
  measureText(textToMeasure, opts) {
44
24
  const div = document.createElement("div");
45
25
  div.textContent = normalizeTextForDom(textToMeasure);
46
26
  return this.measureHtml(div.innerHTML, opts);
47
27
  }
48
28
  measureHtml(html, opts) {
49
- const { elm } = this;
50
- if (opts.otherStyles) {
51
- for (const key in opts.otherStyles) {
52
- if (!this.defaultStyles[key]) {
53
- this.defaultStyles[key] = elm.style.getPropertyValue(key);
54
- }
55
- }
56
- }
57
- elm.innerHTML = html;
58
- this.resetElmStyles();
59
- elm.style.setProperty("font-family", opts.fontFamily);
60
- elm.style.setProperty("font-style", opts.fontStyle);
61
- elm.style.setProperty("font-weight", opts.fontWeight);
62
- elm.style.setProperty("font-size", opts.fontSize + "px");
63
- elm.style.setProperty("line-height", opts.lineHeight * opts.fontSize + "px");
64
- elm.style.setProperty("padding", opts.padding);
65
- if (opts.maxWidth) {
66
- elm.style.setProperty("max-width", opts.maxWidth + "px");
67
- }
68
- if (opts.minWidth) {
69
- elm.style.setProperty("min-width", opts.minWidth + "px");
70
- }
71
- if (opts.disableOverflowWrapBreaking) {
72
- elm.style.setProperty("overflow-wrap", "normal");
73
- }
29
+ const wrapperElm = this.baseElem.cloneNode();
30
+ this.editor.getContainer().appendChild(wrapperElm);
31
+ wrapperElm.innerHTML = html;
32
+ this.baseElem.insertAdjacentElement("afterend", wrapperElm);
33
+ wrapperElm.setAttribute("dir", "auto");
34
+ wrapperElm.style.setProperty("unicode-bidi", "plaintext");
35
+ wrapperElm.style.setProperty("font-family", opts.fontFamily);
36
+ wrapperElm.style.setProperty("font-style", opts.fontStyle);
37
+ wrapperElm.style.setProperty("font-weight", opts.fontWeight);
38
+ wrapperElm.style.setProperty("font-size", opts.fontSize + "px");
39
+ wrapperElm.style.setProperty("line-height", opts.lineHeight * opts.fontSize + "px");
40
+ wrapperElm.style.setProperty("max-width", opts.maxWidth === null ? null : opts.maxWidth + "px");
41
+ wrapperElm.style.setProperty("min-width", opts.minWidth === null ? null : opts.minWidth + "px");
42
+ wrapperElm.style.setProperty("padding", opts.padding);
43
+ wrapperElm.style.setProperty(
44
+ "overflow-wrap",
45
+ opts.disableOverflowWrapBreaking ? "normal" : "break-word"
46
+ );
74
47
  if (opts.otherStyles) {
75
48
  for (const [key, value] of Object.entries(opts.otherStyles)) {
76
- elm.style.setProperty(key, value);
49
+ wrapperElm.style.setProperty(key, value);
77
50
  }
78
51
  }
79
- const scrollWidth = opts.measureScrollWidth ? elm.scrollWidth : 0;
80
- const rect = elm.getBoundingClientRect();
52
+ const scrollWidth = wrapperElm.scrollWidth;
53
+ const rect = wrapperElm.getBoundingClientRect();
54
+ wrapperElm.remove();
81
55
  return {
82
56
  x: 0,
83
57
  y: 0,
@@ -163,34 +137,29 @@ class TextManager {
163
137
  */
164
138
  measureTextSpans(textToMeasure, opts) {
165
139
  if (textToMeasure === "") return [];
166
- const { elm } = this;
167
- if (opts.otherStyles) {
168
- for (const key in opts.otherStyles) {
169
- if (!this.defaultStyles[key]) {
170
- this.defaultStyles[key] = elm.style.getPropertyValue(key);
171
- }
172
- }
173
- }
174
- this.resetElmStyles();
175
- elm.style.setProperty("font-family", opts.fontFamily);
176
- elm.style.setProperty("font-style", opts.fontStyle);
177
- elm.style.setProperty("font-weight", opts.fontWeight);
178
- elm.style.setProperty("font-size", opts.fontSize + "px");
179
- elm.style.setProperty("line-height", opts.lineHeight * opts.fontSize + "px");
140
+ const elm = this.baseElem.cloneNode();
141
+ this.editor.getContainer().appendChild(elm);
180
142
  const elementWidth = Math.ceil(opts.width - opts.padding * 2);
143
+ elm.setAttribute("dir", "auto");
144
+ elm.style.setProperty("unicode-bidi", "plaintext");
181
145
  elm.style.setProperty("width", `${elementWidth}px`);
182
146
  elm.style.setProperty("height", "min-content");
147
+ elm.style.setProperty("font-size", `${opts.fontSize}px`);
148
+ elm.style.setProperty("font-family", opts.fontFamily);
149
+ elm.style.setProperty("font-weight", opts.fontWeight);
150
+ elm.style.setProperty("line-height", `${opts.lineHeight * opts.fontSize}px`);
183
151
  elm.style.setProperty("text-align", textAlignmentsForLtr[opts.textAlign]);
184
- const shouldTruncateToFirstLine = opts.overflow === "truncate-ellipsis" || opts.overflow === "truncate-clip";
185
- if (shouldTruncateToFirstLine) {
186
- elm.style.setProperty("overflow-wrap", "anywhere");
187
- elm.style.setProperty("word-break", "break-all");
188
- }
152
+ elm.style.setProperty("font-style", opts.fontStyle);
189
153
  if (opts.otherStyles) {
190
154
  for (const [key, value] of Object.entries(opts.otherStyles)) {
191
155
  elm.style.setProperty(key, value);
192
156
  }
193
157
  }
158
+ const shouldTruncateToFirstLine = opts.overflow === "truncate-ellipsis" || opts.overflow === "truncate-clip";
159
+ if (shouldTruncateToFirstLine) {
160
+ elm.style.setProperty("overflow-wrap", "anywhere");
161
+ elm.style.setProperty("word-break", "break-all");
162
+ }
194
163
  const normalizedText = normalizeTextForDom(textToMeasure);
195
164
  elm.textContent = normalizedText;
196
165
  const { spans, didTruncate } = this.measureElementTextNodeSpans(elm, {
@@ -216,6 +185,7 @@ class TextManager {
216
185
  });
217
186
  return truncatedSpans;
218
187
  }
188
+ elm.remove();
219
189
  return spans;
220
190
  }
221
191
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/lib/editor/managers/TextManager/TextManager.ts"],
4
- "sourcesContent": ["import { BoxModel, TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema'\nimport { Editor } from '../../Editor'\n\nconst fixNewLines = /\\r?\\n|\\r/g\n\nfunction normalizeTextForDom(text: string) {\n\treturn text\n\t\t.replace(fixNewLines, '\\n')\n\t\t.split('\\n')\n\t\t.map((x) => x || ' ')\n\t\t.join('\\n')\n}\n\nconst textAlignmentsForLtr = {\n\tstart: 'left',\n\t'start-legacy': 'left',\n\tmiddle: 'center',\n\t'middle-legacy': 'center',\n\tend: 'right',\n\t'end-legacy': 'right',\n}\n\n/** @public */\nexport interface TLMeasureTextOpts {\n\tfontStyle: string\n\tfontWeight: string\n\tfontFamily: string\n\tfontSize: number\n\tlineHeight: number\n\t/**\n\t * When maxWidth is a number, the text will be wrapped to that maxWidth. When maxWidth\n\t * is null, the text will be measured without wrapping, but explicit line breaks and\n\t * space are preserved.\n\t */\n\tmaxWidth: null | number\n\tminWidth?: null | number\n\t// todo: make this a number so that it is consistent with other TLMeasureTextSpanOpts\n\tpadding: string\n\totherStyles?: Record<string, string>\n\tdisableOverflowWrapBreaking?: boolean\n\tmeasureScrollWidth?: boolean\n}\n\n/** @public */\nexport interface TLMeasureTextSpanOpts {\n\toverflow: 'wrap' | 'truncate-ellipsis' | 'truncate-clip'\n\twidth: number\n\theight: number\n\tpadding: number\n\tfontSize: number\n\tfontWeight: string\n\tfontFamily: string\n\tfontStyle: string\n\tlineHeight: number\n\ttextAlign: TLDefaultHorizontalAlignStyle\n\totherStyles?: Record<string, string>\n\tmeasureScrollWidth?: boolean\n}\n\nconst spaceCharacterRegex = /\\s/\n\n/** @public */\nexport class TextManager {\n\tprivate elm: HTMLDivElement\n\tprivate defaultStyles: Record<string, string | null>\n\n\tconstructor(public editor: Editor) {\n\t\tconst elm = document.createElement('div')\n\t\telm.classList.add('tl-text')\n\t\telm.classList.add('tl-text-measure')\n\t\telm.setAttribute('dir', 'auto')\n\t\telm.tabIndex = -1\n\t\tthis.editor.getContainer().appendChild(elm)\n\n\t\t// we need to save the default styles so that we can restore them when we're done\n\t\t// these must be the css names, not the js names for the styles\n\t\tthis.defaultStyles = {\n\t\t\t'word-break': 'auto',\n\t\t\twidth: null,\n\t\t\theight: null,\n\t\t\t'max-width': null,\n\t\t\t'min-width': null,\n\t\t}\n\n\t\tthis.elm = elm\n\t}\n\n\tdispose() {\n\t\treturn this.elm.remove()\n\t}\n\n\tprivate resetElmStyles() {\n\t\tconst { elm, defaultStyles } = this\n\t\tfor (const key in defaultStyles) {\n\t\t\telm.style.setProperty(key, defaultStyles[key])\n\t\t}\n\t}\n\n\tmeasureText(textToMeasure: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {\n\t\tconst div = document.createElement('div')\n\t\tdiv.textContent = normalizeTextForDom(textToMeasure)\n\t\treturn this.measureHtml(div.innerHTML, opts)\n\t}\n\n\tmeasureHtml(html: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {\n\t\tconst { elm } = this\n\n\t\tif (opts.otherStyles) {\n\t\t\tfor (const key in opts.otherStyles) {\n\t\t\t\tif (!this.defaultStyles[key]) {\n\t\t\t\t\t// we need to save the original style so that we can restore it when we're done\n\t\t\t\t\tthis.defaultStyles[key] = elm.style.getPropertyValue(key)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\telm.innerHTML = html\n\n\t\t// Apply the default styles to the element (for all styles here or that were ever seen in opts.otherStyles)\n\t\tthis.resetElmStyles()\n\n\t\telm.style.setProperty('font-family', opts.fontFamily)\n\t\telm.style.setProperty('font-style', opts.fontStyle)\n\t\telm.style.setProperty('font-weight', opts.fontWeight)\n\t\telm.style.setProperty('font-size', opts.fontSize + 'px')\n\t\telm.style.setProperty('line-height', opts.lineHeight * opts.fontSize + 'px')\n\t\telm.style.setProperty('padding', opts.padding)\n\n\t\tif (opts.maxWidth) {\n\t\t\telm.style.setProperty('max-width', opts.maxWidth + 'px')\n\t\t}\n\n\t\tif (opts.minWidth) {\n\t\t\telm.style.setProperty('min-width', opts.minWidth + 'px')\n\t\t}\n\n\t\tif (opts.disableOverflowWrapBreaking) {\n\t\t\telm.style.setProperty('overflow-wrap', 'normal')\n\t\t}\n\n\t\tif (opts.otherStyles) {\n\t\t\tfor (const [key, value] of Object.entries(opts.otherStyles)) {\n\t\t\t\telm.style.setProperty(key, value)\n\t\t\t}\n\t\t}\n\n\t\tconst scrollWidth = opts.measureScrollWidth ? elm.scrollWidth : 0\n\t\tconst rect = elm.getBoundingClientRect()\n\n\t\treturn {\n\t\t\tx: 0,\n\t\t\ty: 0,\n\t\t\tw: rect.width,\n\t\t\th: rect.height,\n\t\t\tscrollWidth,\n\t\t}\n\t}\n\n\t/**\n\t * Given an html element, measure the position of each span of unbroken\n\t * word/white-space characters within any text nodes it contains.\n\t */\n\tmeasureElementTextNodeSpans(\n\t\telement: HTMLElement,\n\t\t{ shouldTruncateToFirstLine = false }: { shouldTruncateToFirstLine?: boolean } = {}\n\t): { spans: { box: BoxModel; text: string }[]; didTruncate: boolean } {\n\t\tconst spans = []\n\n\t\t// Measurements of individual spans are relative to the containing element\n\t\tconst elmBounds = element.getBoundingClientRect()\n\t\tconst offsetX = -elmBounds.left\n\t\tconst offsetY = -elmBounds.top\n\n\t\t// we measure by creating a range that spans each character in the elements text node\n\t\tconst range = new Range()\n\t\tconst textNode = element.childNodes[0]\n\t\tlet idx = 0\n\n\t\tlet currentSpan = null\n\t\tlet prevCharWasSpaceCharacter = null\n\t\tlet prevCharTop = 0\n\t\tlet prevCharLeftForRTLTest = 0\n\t\tlet didTruncate = false\n\t\tfor (const childNode of element.childNodes) {\n\t\t\tif (childNode.nodeType !== Node.TEXT_NODE) continue\n\n\t\t\tfor (const char of childNode.textContent ?? '') {\n\t\t\t\t// place the range around the characters we're interested in\n\t\t\t\trange.setStart(textNode, idx)\n\t\t\t\trange.setEnd(textNode, idx + char.length)\n\t\t\t\t// measure the range. some browsers return multiple rects for the\n\t\t\t\t// first char in a new line - one for the line break, and one for\n\t\t\t\t// the character itself. we're only interested in the character.\n\t\t\t\tconst rects = range.getClientRects()\n\t\t\t\tconst rect = rects[rects.length - 1]!\n\n\t\t\t\t// calculate the position of the character relative to the element\n\t\t\t\tconst top = rect.top + offsetY\n\t\t\t\tconst left = rect.left + offsetX\n\t\t\t\tconst right = rect.right + offsetX\n\t\t\t\tconst isRTL = left < prevCharLeftForRTLTest\n\n\t\t\t\tconst isSpaceCharacter = spaceCharacterRegex.test(char)\n\t\t\t\tif (\n\t\t\t\t\t// If we're at a word boundary...\n\t\t\t\t\tisSpaceCharacter !== prevCharWasSpaceCharacter ||\n\t\t\t\t\t// ...or we're on a different line...\n\t\t\t\t\ttop !== prevCharTop ||\n\t\t\t\t\t// ...or we're at the start of the text and haven't created a span yet...\n\t\t\t\t\t!currentSpan\n\t\t\t\t) {\n\t\t\t\t\t// ...then we're at a span boundary!\n\n\t\t\t\t\tif (currentSpan) {\n\t\t\t\t\t\t// if we're truncating to a single line & we just finished the first line, stop there\n\t\t\t\t\t\tif (shouldTruncateToFirstLine && top !== prevCharTop) {\n\t\t\t\t\t\t\tdidTruncate = true\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// otherwise add the span to the list ready to start a new one\n\t\t\t\t\t\tspans.push(currentSpan)\n\t\t\t\t\t}\n\n\t\t\t\t\t// start a new span\n\t\t\t\t\tcurrentSpan = {\n\t\t\t\t\t\tbox: { x: left, y: top, w: rect.width, h: rect.height },\n\t\t\t\t\t\ttext: char,\n\t\t\t\t\t}\n\t\t\t\t\tprevCharLeftForRTLTest = left\n\t\t\t\t} else {\n\t\t\t\t\t// Looks like we're in RTL mode, so we need to adjust the left position.\n\t\t\t\t\tif (isRTL) {\n\t\t\t\t\t\tcurrentSpan.box.x = left\n\t\t\t\t\t}\n\n\t\t\t\t\t// otherwise we just need to extend the current span with the next character\n\t\t\t\t\tcurrentSpan.box.w = isRTL ? currentSpan.box.w + rect.width : right - currentSpan.box.x\n\t\t\t\t\tcurrentSpan.text += char\n\t\t\t\t}\n\n\t\t\t\tif (char === '\\n') {\n\t\t\t\t\tprevCharLeftForRTLTest = 0\n\t\t\t\t}\n\n\t\t\t\tprevCharWasSpaceCharacter = isSpaceCharacter\n\t\t\t\tprevCharTop = top\n\t\t\t\tidx += char.length\n\t\t\t}\n\t\t}\n\n\t\t// Add the last span\n\t\tif (currentSpan) {\n\t\t\tspans.push(currentSpan)\n\t\t}\n\n\t\treturn { spans, didTruncate }\n\t}\n\n\t/**\n\t * Measure text into individual spans. Spans are created by rendering the\n\t * text, then dividing it up according to line breaks and word boundaries.\n\t *\n\t * It works by having the browser render the text, then measuring the\n\t * position of each character. You can use this to replicate the text-layout\n\t * algorithm of the current browser in e.g. an SVG export.\n\t */\n\tmeasureTextSpans(\n\t\ttextToMeasure: string,\n\t\topts: TLMeasureTextSpanOpts\n\t): { text: string; box: BoxModel }[] {\n\t\tif (textToMeasure === '') return []\n\n\t\tconst { elm } = this\n\n\t\tif (opts.otherStyles) {\n\t\t\tfor (const key in opts.otherStyles) {\n\t\t\t\tif (!this.defaultStyles[key]) {\n\t\t\t\t\t// we need to save the original style so that we can restore it when we're done\n\t\t\t\t\tthis.defaultStyles[key] = elm.style.getPropertyValue(key)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.resetElmStyles()\n\n\t\telm.style.setProperty('font-family', opts.fontFamily)\n\t\telm.style.setProperty('font-style', opts.fontStyle)\n\t\telm.style.setProperty('font-weight', opts.fontWeight)\n\t\telm.style.setProperty('font-size', opts.fontSize + 'px')\n\t\telm.style.setProperty('line-height', opts.lineHeight * opts.fontSize + 'px')\n\n\t\tconst elementWidth = Math.ceil(opts.width - opts.padding * 2)\n\t\telm.style.setProperty('width', `${elementWidth}px`)\n\t\telm.style.setProperty('height', 'min-content')\n\t\telm.style.setProperty('text-align', textAlignmentsForLtr[opts.textAlign])\n\n\t\tconst shouldTruncateToFirstLine =\n\t\t\topts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'\n\n\t\tif (shouldTruncateToFirstLine) {\n\t\t\telm.style.setProperty('overflow-wrap', 'anywhere')\n\t\t\telm.style.setProperty('word-break', 'break-all')\n\t\t}\n\n\t\tif (opts.otherStyles) {\n\t\t\tfor (const [key, value] of Object.entries(opts.otherStyles)) {\n\t\t\t\telm.style.setProperty(key, value)\n\t\t\t}\n\t\t}\n\n\t\tconst normalizedText = normalizeTextForDom(textToMeasure)\n\n\t\t// Render the text into the measurement element:\n\t\telm.textContent = normalizedText\n\n\t\t// actually measure the text:\n\t\tconst { spans, didTruncate } = this.measureElementTextNodeSpans(elm, {\n\t\t\tshouldTruncateToFirstLine,\n\t\t})\n\n\t\tif (opts.overflow === 'truncate-ellipsis' && didTruncate) {\n\t\t\t// we need to measure the ellipsis to know how much space it takes up\n\t\t\telm.textContent = '\u2026'\n\t\t\tconst ellipsisWidth = Math.ceil(this.measureElementTextNodeSpans(elm).spans[0].box.w)\n\n\t\t\t// then, we need to subtract that space from the width we have and measure again:\n\t\t\telm.style.setProperty('width', `${elementWidth - ellipsisWidth}px`)\n\t\t\telm.textContent = normalizedText\n\t\t\tconst truncatedSpans = this.measureElementTextNodeSpans(elm, {\n\t\t\t\tshouldTruncateToFirstLine: true,\n\t\t\t}).spans\n\n\t\t\t// Finally, we add in our ellipsis at the end of the last span. We\n\t\t\t// have to do this after measuring, not before, because adding the\n\t\t\t// ellipsis changes how whitespace might be getting collapsed by the\n\t\t\t// browser.\n\t\t\tconst lastSpan = truncatedSpans[truncatedSpans.length - 1]!\n\t\t\ttruncatedSpans.push({\n\t\t\t\ttext: '\u2026',\n\t\t\t\tbox: {\n\t\t\t\t\tx: Math.min(lastSpan.box.x + lastSpan.box.w, opts.width - opts.padding - ellipsisWidth),\n\t\t\t\t\ty: lastSpan.box.y,\n\t\t\t\t\tw: ellipsisWidth,\n\t\t\t\t\th: lastSpan.box.h,\n\t\t\t\t},\n\t\t\t})\n\n\t\t\treturn truncatedSpans\n\t\t}\n\n\t\treturn spans\n\t}\n}\n"],
5
- "mappings": "AAGA,MAAM,cAAc;AAEpB,SAAS,oBAAoB,MAAc;AAC1C,SAAO,KACL,QAAQ,aAAa,IAAI,EACzB,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,KAAK,GAAG,EACnB,KAAK,IAAI;AACZ;AAEA,MAAM,uBAAuB;AAAA,EAC5B,OAAO;AAAA,EACP,gBAAgB;AAAA,EAChB,QAAQ;AAAA,EACR,iBAAiB;AAAA,EACjB,KAAK;AAAA,EACL,cAAc;AACf;AAuCA,MAAM,sBAAsB;AAGrB,MAAM,YAAY;AAAA,EAIxB,YAAmB,QAAgB;AAAhB;AAClB,UAAM,MAAM,SAAS,cAAc,KAAK;AACxC,QAAI,UAAU,IAAI,SAAS;AAC3B,QAAI,UAAU,IAAI,iBAAiB;AACnC,QAAI,aAAa,OAAO,MAAM;AAC9B,QAAI,WAAW;AACf,SAAK,OAAO,aAAa,EAAE,YAAY,GAAG;AAI1C,SAAK,gBAAgB;AAAA,MACpB,cAAc;AAAA,MACd,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,aAAa;AAAA,IACd;AAEA,SAAK,MAAM;AAAA,EACZ;AAAA,EAtBQ;AAAA,EACA;AAAA,EAuBR,UAAU;AACT,WAAO,KAAK,IAAI,OAAO;AAAA,EACxB;AAAA,EAEQ,iBAAiB;AACxB,UAAM,EAAE,KAAK,cAAc,IAAI;AAC/B,eAAW,OAAO,eAAe;AAChC,UAAI,MAAM,YAAY,KAAK,cAAc,GAAG,CAAC;AAAA,IAC9C;AAAA,EACD;AAAA,EAEA,YAAY,eAAuB,MAA6D;AAC/F,UAAM,MAAM,SAAS,cAAc,KAAK;AACxC,QAAI,cAAc,oBAAoB,aAAa;AACnD,WAAO,KAAK,YAAY,IAAI,WAAW,IAAI;AAAA,EAC5C;AAAA,EAEA,YAAY,MAAc,MAA6D;AACtF,UAAM,EAAE,IAAI,IAAI;AAEhB,QAAI,KAAK,aAAa;AACrB,iBAAW,OAAO,KAAK,aAAa;AACnC,YAAI,CAAC,KAAK,cAAc,GAAG,GAAG;AAE7B,eAAK,cAAc,GAAG,IAAI,IAAI,MAAM,iBAAiB,GAAG;AAAA,QACzD;AAAA,MACD;AAAA,IACD;AAEA,QAAI,YAAY;AAGhB,SAAK,eAAe;AAEpB,QAAI,MAAM,YAAY,eAAe,KAAK,UAAU;AACpD,QAAI,MAAM,YAAY,cAAc,KAAK,SAAS;AAClD,QAAI,MAAM,YAAY,eAAe,KAAK,UAAU;AACpD,QAAI,MAAM,YAAY,aAAa,KAAK,WAAW,IAAI;AACvD,QAAI,MAAM,YAAY,eAAe,KAAK,aAAa,KAAK,WAAW,IAAI;AAC3E,QAAI,MAAM,YAAY,WAAW,KAAK,OAAO;AAE7C,QAAI,KAAK,UAAU;AAClB,UAAI,MAAM,YAAY,aAAa,KAAK,WAAW,IAAI;AAAA,IACxD;AAEA,QAAI,KAAK,UAAU;AAClB,UAAI,MAAM,YAAY,aAAa,KAAK,WAAW,IAAI;AAAA,IACxD;AAEA,QAAI,KAAK,6BAA6B;AACrC,UAAI,MAAM,YAAY,iBAAiB,QAAQ;AAAA,IAChD;AAEA,QAAI,KAAK,aAAa;AACrB,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,WAAW,GAAG;AAC5D,YAAI,MAAM,YAAY,KAAK,KAAK;AAAA,MACjC;AAAA,IACD;AAEA,UAAM,cAAc,KAAK,qBAAqB,IAAI,cAAc;AAChE,UAAM,OAAO,IAAI,sBAAsB;AAEvC,WAAO;AAAA,MACN,GAAG;AAAA,MACH,GAAG;AAAA,MACH,GAAG,KAAK;AAAA,MACR,GAAG,KAAK;AAAA,MACR;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,4BACC,SACA,EAAE,4BAA4B,MAAM,IAA6C,CAAC,GACb;AACrE,UAAM,QAAQ,CAAC;AAGf,UAAM,YAAY,QAAQ,sBAAsB;AAChD,UAAM,UAAU,CAAC,UAAU;AAC3B,UAAM,UAAU,CAAC,UAAU;AAG3B,UAAM,QAAQ,IAAI,MAAM;AACxB,UAAM,WAAW,QAAQ,WAAW,CAAC;AACrC,QAAI,MAAM;AAEV,QAAI,cAAc;AAClB,QAAI,4BAA4B;AAChC,QAAI,cAAc;AAClB,QAAI,yBAAyB;AAC7B,QAAI,cAAc;AAClB,eAAW,aAAa,QAAQ,YAAY;AAC3C,UAAI,UAAU,aAAa,KAAK,UAAW;AAE3C,iBAAW,QAAQ,UAAU,eAAe,IAAI;AAE/C,cAAM,SAAS,UAAU,GAAG;AAC5B,cAAM,OAAO,UAAU,MAAM,KAAK,MAAM;AAIxC,cAAM,QAAQ,MAAM,eAAe;AACnC,cAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AAGnC,cAAM,MAAM,KAAK,MAAM;AACvB,cAAM,OAAO,KAAK,OAAO;AACzB,cAAM,QAAQ,KAAK,QAAQ;AAC3B,cAAM,QAAQ,OAAO;AAErB,cAAM,mBAAmB,oBAAoB,KAAK,IAAI;AACtD;AAAA;AAAA,UAEC,qBAAqB;AAAA,UAErB,QAAQ;AAAA,UAER,CAAC;AAAA,UACA;AAGD,cAAI,aAAa;AAEhB,gBAAI,6BAA6B,QAAQ,aAAa;AACrD,4BAAc;AACd;AAAA,YACD;AAEA,kBAAM,KAAK,WAAW;AAAA,UACvB;AAGA,wBAAc;AAAA,YACb,KAAK,EAAE,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,OAAO,GAAG,KAAK,OAAO;AAAA,YACtD,MAAM;AAAA,UACP;AACA,mCAAyB;AAAA,QAC1B,OAAO;AAEN,cAAI,OAAO;AACV,wBAAY,IAAI,IAAI;AAAA,UACrB;AAGA,sBAAY,IAAI,IAAI,QAAQ,YAAY,IAAI,IAAI,KAAK,QAAQ,QAAQ,YAAY,IAAI;AACrF,sBAAY,QAAQ;AAAA,QACrB;AAEA,YAAI,SAAS,MAAM;AAClB,mCAAyB;AAAA,QAC1B;AAEA,oCAA4B;AAC5B,sBAAc;AACd,eAAO,KAAK;AAAA,MACb;AAAA,IACD;AAGA,QAAI,aAAa;AAChB,YAAM,KAAK,WAAW;AAAA,IACvB;AAEA,WAAO,EAAE,OAAO,YAAY;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,iBACC,eACA,MACoC;AACpC,QAAI,kBAAkB,GAAI,QAAO,CAAC;AAElC,UAAM,EAAE,IAAI,IAAI;AAEhB,QAAI,KAAK,aAAa;AACrB,iBAAW,OAAO,KAAK,aAAa;AACnC,YAAI,CAAC,KAAK,cAAc,GAAG,GAAG;AAE7B,eAAK,cAAc,GAAG,IAAI,IAAI,MAAM,iBAAiB,GAAG;AAAA,QACzD;AAAA,MACD;AAAA,IACD;AAEA,SAAK,eAAe;AAEpB,QAAI,MAAM,YAAY,eAAe,KAAK,UAAU;AACpD,QAAI,MAAM,YAAY,cAAc,KAAK,SAAS;AAClD,QAAI,MAAM,YAAY,eAAe,KAAK,UAAU;AACpD,QAAI,MAAM,YAAY,aAAa,KAAK,WAAW,IAAI;AACvD,QAAI,MAAM,YAAY,eAAe,KAAK,aAAa,KAAK,WAAW,IAAI;AAE3E,UAAM,eAAe,KAAK,KAAK,KAAK,QAAQ,KAAK,UAAU,CAAC;AAC5D,QAAI,MAAM,YAAY,SAAS,GAAG,YAAY,IAAI;AAClD,QAAI,MAAM,YAAY,UAAU,aAAa;AAC7C,QAAI,MAAM,YAAY,cAAc,qBAAqB,KAAK,SAAS,CAAC;AAExE,UAAM,4BACL,KAAK,aAAa,uBAAuB,KAAK,aAAa;AAE5D,QAAI,2BAA2B;AAC9B,UAAI,MAAM,YAAY,iBAAiB,UAAU;AACjD,UAAI,MAAM,YAAY,cAAc,WAAW;AAAA,IAChD;AAEA,QAAI,KAAK,aAAa;AACrB,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,WAAW,GAAG;AAC5D,YAAI,MAAM,YAAY,KAAK,KAAK;AAAA,MACjC;AAAA,IACD;AAEA,UAAM,iBAAiB,oBAAoB,aAAa;AAGxD,QAAI,cAAc;AAGlB,UAAM,EAAE,OAAO,YAAY,IAAI,KAAK,4BAA4B,KAAK;AAAA,MACpE;AAAA,IACD,CAAC;AAED,QAAI,KAAK,aAAa,uBAAuB,aAAa;AAEzD,UAAI,cAAc;AAClB,YAAM,gBAAgB,KAAK,KAAK,KAAK,4BAA4B,GAAG,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC;AAGpF,UAAI,MAAM,YAAY,SAAS,GAAG,eAAe,aAAa,IAAI;AAClE,UAAI,cAAc;AAClB,YAAM,iBAAiB,KAAK,4BAA4B,KAAK;AAAA,QAC5D,2BAA2B;AAAA,MAC5B,CAAC,EAAE;AAMH,YAAM,WAAW,eAAe,eAAe,SAAS,CAAC;AACzD,qBAAe,KAAK;AAAA,QACnB,MAAM;AAAA,QACN,KAAK;AAAA,UACJ,GAAG,KAAK,IAAI,SAAS,IAAI,IAAI,SAAS,IAAI,GAAG,KAAK,QAAQ,KAAK,UAAU,aAAa;AAAA,UACtF,GAAG,SAAS,IAAI;AAAA,UAChB,GAAG;AAAA,UACH,GAAG,SAAS,IAAI;AAAA,QACjB;AAAA,MACD,CAAC;AAED,aAAO;AAAA,IACR;AAEA,WAAO;AAAA,EACR;AACD;",
4
+ "sourcesContent": ["import { BoxModel, TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema'\nimport { Editor } from '../../Editor'\n\nconst fixNewLines = /\\r?\\n|\\r/g\n\nfunction normalizeTextForDom(text: string) {\n\treturn text\n\t\t.replace(fixNewLines, '\\n')\n\t\t.split('\\n')\n\t\t.map((x) => x || ' ')\n\t\t.join('\\n')\n}\n\nconst textAlignmentsForLtr = {\n\tstart: 'left',\n\t'start-legacy': 'left',\n\tmiddle: 'center',\n\t'middle-legacy': 'center',\n\tend: 'right',\n\t'end-legacy': 'right',\n}\n\n/** @public */\nexport interface TLMeasureTextSpanOpts {\n\toverflow: 'wrap' | 'truncate-ellipsis' | 'truncate-clip'\n\twidth: number\n\theight: number\n\tpadding: number\n\tfontSize: number\n\tfontWeight: string\n\tfontFamily: string\n\tfontStyle: string\n\tlineHeight: number\n\ttextAlign: TLDefaultHorizontalAlignStyle\n\totherStyles?: Record<string, string>\n}\n\nconst spaceCharacterRegex = /\\s/\n\n/** @public */\nexport class TextManager {\n\tprivate baseElem: HTMLDivElement\n\n\tconstructor(public editor: Editor) {\n\t\tthis.baseElem = document.createElement('div')\n\t\tthis.baseElem.classList.add('tl-text')\n\t\tthis.baseElem.classList.add('tl-text-measure')\n\t\tthis.baseElem.tabIndex = -1\n\t}\n\n\tmeasureText(\n\t\ttextToMeasure: string,\n\t\topts: {\n\t\t\tfontStyle: string\n\t\t\tfontWeight: string\n\t\t\tfontFamily: string\n\t\t\tfontSize: number\n\t\t\tlineHeight: number\n\t\t\t/**\n\t\t\t * When maxWidth is a number, the text will be wrapped to that maxWidth. When maxWidth\n\t\t\t * is null, the text will be measured without wrapping, but explicit line breaks and\n\t\t\t * space are preserved.\n\t\t\t */\n\t\t\tmaxWidth: null | number\n\t\t\tminWidth?: null | number\n\t\t\tpadding: string\n\t\t\tdisableOverflowWrapBreaking?: boolean\n\t\t}\n\t): BoxModel & { scrollWidth: number } {\n\t\tconst div = document.createElement('div')\n\t\tdiv.textContent = normalizeTextForDom(textToMeasure)\n\t\treturn this.measureHtml(div.innerHTML, opts)\n\t}\n\n\tmeasureHtml(\n\t\thtml: string,\n\t\topts: {\n\t\t\tfontStyle: string\n\t\t\tfontWeight: string\n\t\t\tfontFamily: string\n\t\t\tfontSize: number\n\t\t\tlineHeight: number\n\t\t\t/**\n\t\t\t * When maxWidth is a number, the text will be wrapped to that maxWidth. When maxWidth\n\t\t\t * is null, the text will be measured without wrapping, but explicit line breaks and\n\t\t\t * space are preserved.\n\t\t\t */\n\t\t\tmaxWidth: null | number\n\t\t\tminWidth?: null | number\n\t\t\totherStyles?: Record<string, string>\n\t\t\tpadding: string\n\t\t\tdisableOverflowWrapBreaking?: boolean\n\t\t}\n\t): BoxModel & { scrollWidth: number } {\n\t\t// Duplicate our base element; we don't need to clone deep\n\t\tconst wrapperElm = this.baseElem.cloneNode() as HTMLDivElement\n\t\tthis.editor.getContainer().appendChild(wrapperElm)\n\t\twrapperElm.innerHTML = html\n\t\tthis.baseElem.insertAdjacentElement('afterend', wrapperElm)\n\n\t\twrapperElm.setAttribute('dir', 'auto')\n\t\t// N.B. This property, while discouraged (\"intended for Document Type Definition (DTD) designers\")\n\t\t// is necessary for ensuring correct mixed RTL/LTR behavior when exporting SVGs.\n\t\twrapperElm.style.setProperty('unicode-bidi', 'plaintext')\n\t\twrapperElm.style.setProperty('font-family', opts.fontFamily)\n\t\twrapperElm.style.setProperty('font-style', opts.fontStyle)\n\t\twrapperElm.style.setProperty('font-weight', opts.fontWeight)\n\t\twrapperElm.style.setProperty('font-size', opts.fontSize + 'px')\n\t\twrapperElm.style.setProperty('line-height', opts.lineHeight * opts.fontSize + 'px')\n\t\twrapperElm.style.setProperty('max-width', opts.maxWidth === null ? null : opts.maxWidth + 'px')\n\t\twrapperElm.style.setProperty('min-width', opts.minWidth === null ? null : opts.minWidth + 'px')\n\t\twrapperElm.style.setProperty('padding', opts.padding)\n\t\twrapperElm.style.setProperty(\n\t\t\t'overflow-wrap',\n\t\t\topts.disableOverflowWrapBreaking ? 'normal' : 'break-word'\n\t\t)\n\t\tif (opts.otherStyles) {\n\t\t\tfor (const [key, value] of Object.entries(opts.otherStyles)) {\n\t\t\t\twrapperElm.style.setProperty(key, value)\n\t\t\t}\n\t\t}\n\n\t\tconst scrollWidth = wrapperElm.scrollWidth\n\t\tconst rect = wrapperElm.getBoundingClientRect()\n\t\twrapperElm.remove()\n\n\t\treturn {\n\t\t\tx: 0,\n\t\t\ty: 0,\n\t\t\tw: rect.width,\n\t\t\th: rect.height,\n\t\t\tscrollWidth,\n\t\t}\n\t}\n\n\t/**\n\t * Given an html element, measure the position of each span of unbroken\n\t * word/white-space characters within any text nodes it contains.\n\t */\n\tmeasureElementTextNodeSpans(\n\t\telement: HTMLElement,\n\t\t{ shouldTruncateToFirstLine = false }: { shouldTruncateToFirstLine?: boolean } = {}\n\t): { spans: { box: BoxModel; text: string }[]; didTruncate: boolean } {\n\t\tconst spans = []\n\n\t\t// Measurements of individual spans are relative to the containing element\n\t\tconst elmBounds = element.getBoundingClientRect()\n\t\tconst offsetX = -elmBounds.left\n\t\tconst offsetY = -elmBounds.top\n\n\t\t// we measure by creating a range that spans each character in the elements text node\n\t\tconst range = new Range()\n\t\tconst textNode = element.childNodes[0]\n\t\tlet idx = 0\n\n\t\tlet currentSpan = null\n\t\tlet prevCharWasSpaceCharacter = null\n\t\tlet prevCharTop = 0\n\t\tlet prevCharLeftForRTLTest = 0\n\t\tlet didTruncate = false\n\t\tfor (const childNode of element.childNodes) {\n\t\t\tif (childNode.nodeType !== Node.TEXT_NODE) continue\n\n\t\t\tfor (const char of childNode.textContent ?? '') {\n\t\t\t\t// place the range around the characters we're interested in\n\t\t\t\trange.setStart(textNode, idx)\n\t\t\t\trange.setEnd(textNode, idx + char.length)\n\t\t\t\t// measure the range. some browsers return multiple rects for the\n\t\t\t\t// first char in a new line - one for the line break, and one for\n\t\t\t\t// the character itself. we're only interested in the character.\n\t\t\t\tconst rects = range.getClientRects()\n\t\t\t\tconst rect = rects[rects.length - 1]!\n\n\t\t\t\t// calculate the position of the character relative to the element\n\t\t\t\tconst top = rect.top + offsetY\n\t\t\t\tconst left = rect.left + offsetX\n\t\t\t\tconst right = rect.right + offsetX\n\t\t\t\tconst isRTL = left < prevCharLeftForRTLTest\n\n\t\t\t\tconst isSpaceCharacter = spaceCharacterRegex.test(char)\n\t\t\t\tif (\n\t\t\t\t\t// If we're at a word boundary...\n\t\t\t\t\tisSpaceCharacter !== prevCharWasSpaceCharacter ||\n\t\t\t\t\t// ...or we're on a different line...\n\t\t\t\t\ttop !== prevCharTop ||\n\t\t\t\t\t// ...or we're at the start of the text and haven't created a span yet...\n\t\t\t\t\t!currentSpan\n\t\t\t\t) {\n\t\t\t\t\t// ...then we're at a span boundary!\n\n\t\t\t\t\tif (currentSpan) {\n\t\t\t\t\t\t// if we're truncating to a single line & we just finished the first line, stop there\n\t\t\t\t\t\tif (shouldTruncateToFirstLine && top !== prevCharTop) {\n\t\t\t\t\t\t\tdidTruncate = true\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// otherwise add the span to the list ready to start a new one\n\t\t\t\t\t\tspans.push(currentSpan)\n\t\t\t\t\t}\n\n\t\t\t\t\t// start a new span\n\t\t\t\t\tcurrentSpan = {\n\t\t\t\t\t\tbox: { x: left, y: top, w: rect.width, h: rect.height },\n\t\t\t\t\t\ttext: char,\n\t\t\t\t\t}\n\t\t\t\t\tprevCharLeftForRTLTest = left\n\t\t\t\t} else {\n\t\t\t\t\t// Looks like we're in RTL mode, so we need to adjust the left position.\n\t\t\t\t\tif (isRTL) {\n\t\t\t\t\t\tcurrentSpan.box.x = left\n\t\t\t\t\t}\n\n\t\t\t\t\t// otherwise we just need to extend the current span with the next character\n\t\t\t\t\tcurrentSpan.box.w = isRTL ? currentSpan.box.w + rect.width : right - currentSpan.box.x\n\t\t\t\t\tcurrentSpan.text += char\n\t\t\t\t}\n\n\t\t\t\tif (char === '\\n') {\n\t\t\t\t\tprevCharLeftForRTLTest = 0\n\t\t\t\t}\n\n\t\t\t\tprevCharWasSpaceCharacter = isSpaceCharacter\n\t\t\t\tprevCharTop = top\n\t\t\t\tidx += char.length\n\t\t\t}\n\t\t}\n\n\t\t// Add the last span\n\t\tif (currentSpan) {\n\t\t\tspans.push(currentSpan)\n\t\t}\n\n\t\treturn { spans, didTruncate }\n\t}\n\n\t/**\n\t * Measure text into individual spans. Spans are created by rendering the\n\t * text, then dividing it up according to line breaks and word boundaries.\n\t *\n\t * It works by having the browser render the text, then measuring the\n\t * position of each character. You can use this to replicate the text-layout\n\t * algorithm of the current browser in e.g. an SVG export.\n\t */\n\tmeasureTextSpans(\n\t\ttextToMeasure: string,\n\t\topts: TLMeasureTextSpanOpts\n\t): { text: string; box: BoxModel }[] {\n\t\tif (textToMeasure === '') return []\n\n\t\tconst elm = this.baseElem.cloneNode() as HTMLDivElement\n\t\tthis.editor.getContainer().appendChild(elm)\n\n\t\tconst elementWidth = Math.ceil(opts.width - opts.padding * 2)\n\t\telm.setAttribute('dir', 'auto')\n\t\t// N.B. This property, while discouraged (\"intended for Document Type Definition (DTD) designers\")\n\t\t// is necessary for ensuring correct mixed RTL/LTR behavior when exporting SVGs.\n\t\telm.style.setProperty('unicode-bidi', 'plaintext')\n\t\telm.style.setProperty('width', `${elementWidth}px`)\n\t\telm.style.setProperty('height', 'min-content')\n\t\telm.style.setProperty('font-size', `${opts.fontSize}px`)\n\t\telm.style.setProperty('font-family', opts.fontFamily)\n\t\telm.style.setProperty('font-weight', opts.fontWeight)\n\t\telm.style.setProperty('line-height', `${opts.lineHeight * opts.fontSize}px`)\n\t\telm.style.setProperty('text-align', textAlignmentsForLtr[opts.textAlign])\n\t\telm.style.setProperty('font-style', opts.fontStyle)\n\t\tif (opts.otherStyles) {\n\t\t\tfor (const [key, value] of Object.entries(opts.otherStyles)) {\n\t\t\t\telm.style.setProperty(key, value)\n\t\t\t}\n\t\t}\n\n\t\tconst shouldTruncateToFirstLine =\n\t\t\topts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'\n\n\t\tif (shouldTruncateToFirstLine) {\n\t\t\telm.style.setProperty('overflow-wrap', 'anywhere')\n\t\t\telm.style.setProperty('word-break', 'break-all')\n\t\t}\n\n\t\tconst normalizedText = normalizeTextForDom(textToMeasure)\n\n\t\t// Render the text into the measurement element:\n\t\telm.textContent = normalizedText\n\n\t\t// actually measure the text:\n\t\tconst { spans, didTruncate } = this.measureElementTextNodeSpans(elm, {\n\t\t\tshouldTruncateToFirstLine,\n\t\t})\n\n\t\tif (opts.overflow === 'truncate-ellipsis' && didTruncate) {\n\t\t\t// we need to measure the ellipsis to know how much space it takes up\n\t\t\telm.textContent = '\u2026'\n\t\t\tconst ellipsisWidth = Math.ceil(this.measureElementTextNodeSpans(elm).spans[0].box.w)\n\n\t\t\t// then, we need to subtract that space from the width we have and measure again:\n\t\t\telm.style.setProperty('width', `${elementWidth - ellipsisWidth}px`)\n\t\t\telm.textContent = normalizedText\n\t\t\tconst truncatedSpans = this.measureElementTextNodeSpans(elm, {\n\t\t\t\tshouldTruncateToFirstLine: true,\n\t\t\t}).spans\n\n\t\t\t// Finally, we add in our ellipsis at the end of the last span. We\n\t\t\t// have to do this after measuring, not before, because adding the\n\t\t\t// ellipsis changes how whitespace might be getting collapsed by the\n\t\t\t// browser.\n\t\t\tconst lastSpan = truncatedSpans[truncatedSpans.length - 1]!\n\t\t\ttruncatedSpans.push({\n\t\t\t\ttext: '\u2026',\n\t\t\t\tbox: {\n\t\t\t\t\tx: Math.min(lastSpan.box.x + lastSpan.box.w, opts.width - opts.padding - ellipsisWidth),\n\t\t\t\t\ty: lastSpan.box.y,\n\t\t\t\t\tw: ellipsisWidth,\n\t\t\t\t\th: lastSpan.box.h,\n\t\t\t\t},\n\t\t\t})\n\t\t\treturn truncatedSpans\n\t\t}\n\n\t\telm.remove()\n\n\t\treturn spans\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -103,7 +103,8 @@ function useCanvasEvents() {
103
103
  await editor.putExternalContent({
104
104
  type: "files",
105
105
  files,
106
- point: editor.screenToPage({ x: e.clientX, y: e.clientY })
106
+ point: editor.screenToPage({ x: e.clientX, y: e.clientY }),
107
+ ignoreParent: false
107
108
  });
108
109
  return;
109
110
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/hooks/useCanvasEvents.ts"],
4
- "sourcesContent": ["import { useValue } from '@tldraw/state-react'\nimport React, { useMemo } from 'react'\nimport { RIGHT_MOUSE_BUTTON } from '../constants'\nimport {\n\tpreventDefault,\n\treleasePointerCapture,\n\tsetPointerCapture,\n\tstopEventPropagation,\n} from '../utils/dom'\nimport { getPointerInfo } from '../utils/getPointerInfo'\nimport { useEditor } from './useEditor'\n\nexport function useCanvasEvents() {\n\tconst editor = useEditor()\n\tconst currentTool = useValue('current tool', () => editor.getCurrentTool(), [editor])\n\n\tconst events = useMemo(\n\t\tfunction canvasEvents() {\n\t\t\t// Track the last screen point\n\t\t\tlet lastX: number, lastY: number\n\n\t\t\tfunction onPointerDown(e: React.PointerEvent) {\n\t\t\t\tif ((e as any).isKilled) return\n\n\t\t\t\tif (e.button === RIGHT_MOUSE_BUTTON) {\n\t\t\t\t\teditor.dispatch({\n\t\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t\t\tname: 'right_click',\n\t\t\t\t\t\t...getPointerInfo(e),\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif (e.button !== 0 && e.button !== 1 && e.button !== 5) return\n\n\t\t\t\tsetPointerCapture(e.currentTarget, e)\n\n\t\t\t\teditor.dispatch({\n\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t\tname: 'pointer_down',\n\t\t\t\t\t...getPointerInfo(e),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tfunction onPointerMove(e: React.PointerEvent) {\n\t\t\t\tif ((e as any).isKilled) return\n\n\t\t\t\tif (e.clientX === lastX && e.clientY === lastY) return\n\t\t\t\tlastX = e.clientX\n\t\t\t\tlastY = e.clientY\n\n\t\t\t\t// For tools that benefit from a higher fidelity of events,\n\t\t\t\t// we dispatch the coalesced events.\n\t\t\t\t// N.B. Sometimes getCoalescedEvents isn't present on iOS, ugh.\n\t\t\t\tconst events =\n\t\t\t\t\tcurrentTool.useCoalescedEvents && e.nativeEvent.getCoalescedEvents\n\t\t\t\t\t\t? e.nativeEvent.getCoalescedEvents()\n\t\t\t\t\t\t: [e]\n\t\t\t\tfor (const singleEvent of events) {\n\t\t\t\t\teditor.dispatch({\n\t\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t\t\tname: 'pointer_move',\n\t\t\t\t\t\t...getPointerInfo(singleEvent),\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfunction onPointerUp(e: React.PointerEvent) {\n\t\t\t\tif ((e as any).isKilled) return\n\t\t\t\tif (e.button !== 0 && e.button !== 1 && e.button !== 2 && e.button !== 5) return\n\t\t\t\tlastX = e.clientX\n\t\t\t\tlastY = e.clientY\n\n\t\t\t\treleasePointerCapture(e.currentTarget, e)\n\n\t\t\t\teditor.dispatch({\n\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t\tname: 'pointer_up',\n\t\t\t\t\t...getPointerInfo(e),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tfunction onPointerEnter(e: React.PointerEvent) {\n\t\t\t\tif ((e as any).isKilled) return\n\t\t\t\tif (editor.getInstanceState().isPenMode && e.pointerType !== 'pen') return\n\t\t\t\tconst canHover = e.pointerType === 'mouse' || e.pointerType === 'pen'\n\t\t\t\teditor.updateInstanceState({ isHoveringCanvas: canHover ? true : null })\n\t\t\t}\n\n\t\t\tfunction onPointerLeave(e: React.PointerEvent) {\n\t\t\t\tif ((e as any).isKilled) return\n\t\t\t\tif (editor.getInstanceState().isPenMode && e.pointerType !== 'pen') return\n\t\t\t\tconst canHover = e.pointerType === 'mouse' || e.pointerType === 'pen'\n\t\t\t\teditor.updateInstanceState({ isHoveringCanvas: canHover ? false : null })\n\t\t\t}\n\n\t\t\tfunction onTouchStart(e: React.TouchEvent) {\n\t\t\t\t;(e as any).isKilled = true\n\t\t\t\tpreventDefault(e)\n\t\t\t}\n\n\t\t\tfunction onTouchEnd(e: React.TouchEvent) {\n\t\t\t\t;(e as any).isKilled = true\n\t\t\t\t// check that e.target is an HTMLElement\n\t\t\t\tif (!(e.target instanceof HTMLElement)) return\n\n\t\t\t\tif (\n\t\t\t\t\te.target.tagName !== 'A' &&\n\t\t\t\t\te.target.tagName !== 'TEXTAREA' &&\n\t\t\t\t\t!e.target.isContentEditable &&\n\t\t\t\t\t// When in EditingShape state, we are actually clicking on a 'DIV'\n\t\t\t\t\t// not A/TEXTAREA/contenteditable element yet. So, to preserve cursor position\n\t\t\t\t\t// for edit mode on mobile we need to not preventDefault.\n\t\t\t\t\t// TODO: Find out if we still need this preventDefault in general though.\n\t\t\t\t\t!(editor.getEditingShape() && e.target.className.includes('tl-text-content'))\n\t\t\t\t) {\n\t\t\t\t\tpreventDefault(e)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfunction onDragOver(e: React.DragEvent<Element>) {\n\t\t\t\tpreventDefault(e)\n\t\t\t}\n\n\t\t\tasync function onDrop(e: React.DragEvent<Element>) {\n\t\t\t\tpreventDefault(e)\n\t\t\t\tstopEventPropagation(e)\n\n\t\t\t\tif (e.dataTransfer?.files?.length) {\n\t\t\t\t\tconst files = Array.from(e.dataTransfer.files)\n\n\t\t\t\t\tawait editor.putExternalContent({\n\t\t\t\t\t\ttype: 'files',\n\t\t\t\t\t\tfiles,\n\t\t\t\t\t\tpoint: editor.screenToPage({ x: e.clientX, y: e.clientY }),\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst url = e.dataTransfer.getData('url')\n\t\t\t\tif (url) {\n\t\t\t\t\tawait editor.putExternalContent({\n\t\t\t\t\t\ttype: 'url',\n\t\t\t\t\t\turl,\n\t\t\t\t\t\tpoint: editor.screenToPage({ x: e.clientX, y: e.clientY }),\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfunction onClick(e: React.MouseEvent) {\n\t\t\t\tstopEventPropagation(e)\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tonPointerDown,\n\t\t\t\tonPointerMove,\n\t\t\t\tonPointerUp,\n\t\t\t\tonPointerEnter,\n\t\t\t\tonPointerLeave,\n\t\t\t\tonDragOver,\n\t\t\t\tonDrop,\n\t\t\t\tonTouchStart,\n\t\t\t\tonTouchEnd,\n\t\t\t\tonClick,\n\t\t\t}\n\t\t},\n\t\t[editor, currentTool]\n\t)\n\n\treturn events\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { useValue } from '@tldraw/state-react'\nimport React, { useMemo } from 'react'\nimport { RIGHT_MOUSE_BUTTON } from '../constants'\nimport {\n\tpreventDefault,\n\treleasePointerCapture,\n\tsetPointerCapture,\n\tstopEventPropagation,\n} from '../utils/dom'\nimport { getPointerInfo } from '../utils/getPointerInfo'\nimport { useEditor } from './useEditor'\n\nexport function useCanvasEvents() {\n\tconst editor = useEditor()\n\tconst currentTool = useValue('current tool', () => editor.getCurrentTool(), [editor])\n\n\tconst events = useMemo(\n\t\tfunction canvasEvents() {\n\t\t\t// Track the last screen point\n\t\t\tlet lastX: number, lastY: number\n\n\t\t\tfunction onPointerDown(e: React.PointerEvent) {\n\t\t\t\tif ((e as any).isKilled) return\n\n\t\t\t\tif (e.button === RIGHT_MOUSE_BUTTON) {\n\t\t\t\t\teditor.dispatch({\n\t\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t\t\tname: 'right_click',\n\t\t\t\t\t\t...getPointerInfo(e),\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif (e.button !== 0 && e.button !== 1 && e.button !== 5) return\n\n\t\t\t\tsetPointerCapture(e.currentTarget, e)\n\n\t\t\t\teditor.dispatch({\n\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t\tname: 'pointer_down',\n\t\t\t\t\t...getPointerInfo(e),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tfunction onPointerMove(e: React.PointerEvent) {\n\t\t\t\tif ((e as any).isKilled) return\n\n\t\t\t\tif (e.clientX === lastX && e.clientY === lastY) return\n\t\t\t\tlastX = e.clientX\n\t\t\t\tlastY = e.clientY\n\n\t\t\t\t// For tools that benefit from a higher fidelity of events,\n\t\t\t\t// we dispatch the coalesced events.\n\t\t\t\t// N.B. Sometimes getCoalescedEvents isn't present on iOS, ugh.\n\t\t\t\tconst events =\n\t\t\t\t\tcurrentTool.useCoalescedEvents && e.nativeEvent.getCoalescedEvents\n\t\t\t\t\t\t? e.nativeEvent.getCoalescedEvents()\n\t\t\t\t\t\t: [e]\n\t\t\t\tfor (const singleEvent of events) {\n\t\t\t\t\teditor.dispatch({\n\t\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t\t\tname: 'pointer_move',\n\t\t\t\t\t\t...getPointerInfo(singleEvent),\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfunction onPointerUp(e: React.PointerEvent) {\n\t\t\t\tif ((e as any).isKilled) return\n\t\t\t\tif (e.button !== 0 && e.button !== 1 && e.button !== 2 && e.button !== 5) return\n\t\t\t\tlastX = e.clientX\n\t\t\t\tlastY = e.clientY\n\n\t\t\t\treleasePointerCapture(e.currentTarget, e)\n\n\t\t\t\teditor.dispatch({\n\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t\tname: 'pointer_up',\n\t\t\t\t\t...getPointerInfo(e),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tfunction onPointerEnter(e: React.PointerEvent) {\n\t\t\t\tif ((e as any).isKilled) return\n\t\t\t\tif (editor.getInstanceState().isPenMode && e.pointerType !== 'pen') return\n\t\t\t\tconst canHover = e.pointerType === 'mouse' || e.pointerType === 'pen'\n\t\t\t\teditor.updateInstanceState({ isHoveringCanvas: canHover ? true : null })\n\t\t\t}\n\n\t\t\tfunction onPointerLeave(e: React.PointerEvent) {\n\t\t\t\tif ((e as any).isKilled) return\n\t\t\t\tif (editor.getInstanceState().isPenMode && e.pointerType !== 'pen') return\n\t\t\t\tconst canHover = e.pointerType === 'mouse' || e.pointerType === 'pen'\n\t\t\t\teditor.updateInstanceState({ isHoveringCanvas: canHover ? false : null })\n\t\t\t}\n\n\t\t\tfunction onTouchStart(e: React.TouchEvent) {\n\t\t\t\t;(e as any).isKilled = true\n\t\t\t\tpreventDefault(e)\n\t\t\t}\n\n\t\t\tfunction onTouchEnd(e: React.TouchEvent) {\n\t\t\t\t;(e as any).isKilled = true\n\t\t\t\t// check that e.target is an HTMLElement\n\t\t\t\tif (!(e.target instanceof HTMLElement)) return\n\n\t\t\t\tif (\n\t\t\t\t\te.target.tagName !== 'A' &&\n\t\t\t\t\te.target.tagName !== 'TEXTAREA' &&\n\t\t\t\t\t!e.target.isContentEditable &&\n\t\t\t\t\t// When in EditingShape state, we are actually clicking on a 'DIV'\n\t\t\t\t\t// not A/TEXTAREA/contenteditable element yet. So, to preserve cursor position\n\t\t\t\t\t// for edit mode on mobile we need to not preventDefault.\n\t\t\t\t\t// TODO: Find out if we still need this preventDefault in general though.\n\t\t\t\t\t!(editor.getEditingShape() && e.target.className.includes('tl-text-content'))\n\t\t\t\t) {\n\t\t\t\t\tpreventDefault(e)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfunction onDragOver(e: React.DragEvent<Element>) {\n\t\t\t\tpreventDefault(e)\n\t\t\t}\n\n\t\t\tasync function onDrop(e: React.DragEvent<Element>) {\n\t\t\t\tpreventDefault(e)\n\t\t\t\tstopEventPropagation(e)\n\n\t\t\t\tif (e.dataTransfer?.files?.length) {\n\t\t\t\t\tconst files = Array.from(e.dataTransfer.files)\n\n\t\t\t\t\tawait editor.putExternalContent({\n\t\t\t\t\t\ttype: 'files',\n\t\t\t\t\t\tfiles,\n\t\t\t\t\t\tpoint: editor.screenToPage({ x: e.clientX, y: e.clientY }),\n\t\t\t\t\t\tignoreParent: false,\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst url = e.dataTransfer.getData('url')\n\t\t\t\tif (url) {\n\t\t\t\t\tawait editor.putExternalContent({\n\t\t\t\t\t\ttype: 'url',\n\t\t\t\t\t\turl,\n\t\t\t\t\t\tpoint: editor.screenToPage({ x: e.clientX, y: e.clientY }),\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfunction onClick(e: React.MouseEvent) {\n\t\t\t\tstopEventPropagation(e)\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tonPointerDown,\n\t\t\t\tonPointerMove,\n\t\t\t\tonPointerUp,\n\t\t\t\tonPointerEnter,\n\t\t\t\tonPointerLeave,\n\t\t\t\tonDragOver,\n\t\t\t\tonDrop,\n\t\t\t\tonTouchStart,\n\t\t\t\tonTouchEnd,\n\t\t\t\tonClick,\n\t\t\t}\n\t\t},\n\t\t[editor, currentTool]\n\t)\n\n\treturn events\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["events"]
7
7
  }
@@ -1,8 +1,8 @@
1
- const version = "3.14.0-canary.b1a9df6c4d78";
1
+ const version = "3.14.0-canary.b30c3e039479";
2
2
  const publishDates = {
3
3
  major: "2024-09-13T14:36:29.063Z",
4
- minor: "2025-06-18T12:21:49.668Z",
5
- patch: "2025-06-18T12:21:49.668Z"
4
+ minor: "2025-06-15T12:03:15.375Z",
5
+ patch: "2025-06-15T12:03:15.375Z"
6
6
  };
7
7
  export {
8
8
  publishDates,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/version.ts"],
4
- "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '3.14.0-canary.b1a9df6c4d78'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-06-18T12:21:49.668Z',\n\tpatch: '2025-06-18T12:21:49.668Z',\n}\n"],
4
+ "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '3.14.0-canary.b30c3e039479'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-06-15T12:03:15.375Z',\n\tpatch: '2025-06-15T12:03:15.375Z',\n}\n"],
5
5
  "mappings": "AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
6
6
  "names": []
7
7
  }
package/editor.css CHANGED
@@ -575,23 +575,6 @@ input,
575
575
 
576
576
  /* ---------------------- Text ---------------------- */
577
577
 
578
- .tl-text-shape-label {
579
- position: relative;
580
- font-weight: normal;
581
- min-width: 1px;
582
- padding: 0px;
583
- margin: 0px;
584
- border: none;
585
- width: fit-content;
586
- height: fit-content;
587
- font-variant: normal;
588
- font-style: normal;
589
- pointer-events: all;
590
- white-space: pre-wrap;
591
- overflow-wrap: break-word;
592
- text-shadow: var(--tl-text-outline);
593
- }
594
-
595
578
  .tl-text-wrapper[data-font='draw'] {
596
579
  font-family: var(--tl-font-draw);
597
580
  }
@@ -686,9 +669,6 @@ input,
686
669
  border: none;
687
670
  user-select: none;
688
671
  contain: style paint;
689
- visibility: hidden;
690
- /* N.B. This property, while discouraged ("intended for Document Type Definition (DTD) designers") is necessary for ensuring correct mixed RTL/LTR behavior when exporting SVGs. */
691
- unicode-bidi: plaintext;
692
672
  -webkit-user-select: none;
693
673
  }
694
674
 
@@ -1398,6 +1378,7 @@ input,
1398
1378
 
1399
1379
  .tl-image-container {
1400
1380
  position: relative;
1381
+ overflow: hidden;
1401
1382
  }
1402
1383
 
1403
1384
  .tl-image {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tldraw/editor",
3
3
  "description": "A tiny little drawing app (editor).",
4
- "version": "3.14.0-canary.b1a9df6c4d78",
4
+ "version": "3.14.0-canary.b30c3e039479",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -48,12 +48,12 @@
48
48
  "@tiptap/core": "^2.9.1",
49
49
  "@tiptap/pm": "^2.9.1",
50
50
  "@tiptap/react": "^2.9.1",
51
- "@tldraw/state": "3.14.0-canary.b1a9df6c4d78",
52
- "@tldraw/state-react": "3.14.0-canary.b1a9df6c4d78",
53
- "@tldraw/store": "3.14.0-canary.b1a9df6c4d78",
54
- "@tldraw/tlschema": "3.14.0-canary.b1a9df6c4d78",
55
- "@tldraw/utils": "3.14.0-canary.b1a9df6c4d78",
56
- "@tldraw/validate": "3.14.0-canary.b1a9df6c4d78",
51
+ "@tldraw/state": "3.14.0-canary.b30c3e039479",
52
+ "@tldraw/state-react": "3.14.0-canary.b30c3e039479",
53
+ "@tldraw/store": "3.14.0-canary.b30c3e039479",
54
+ "@tldraw/tlschema": "3.14.0-canary.b30c3e039479",
55
+ "@tldraw/utils": "3.14.0-canary.b30c3e039479",
56
+ "@tldraw/validate": "3.14.0-canary.b30c3e039479",
57
57
  "@types/core-js": "^2.5.8",
58
58
  "@use-gesture/react": "^10.3.1",
59
59
  "classnames": "^2.5.1",
package/src/index.ts CHANGED
@@ -174,7 +174,6 @@ export {
174
174
  } from './lib/editor/managers/SnapManager/SnapManager'
175
175
  export {
176
176
  TextManager,
177
- type TLMeasureTextOpts,
178
177
  type TLMeasureTextSpanOpts,
179
178
  } from './lib/editor/managers/TextManager/TextManager'
180
179
  export { UserPreferencesManager } from './lib/editor/managers/UserPreferencesManager/UserPreferencesManager'
@@ -253,7 +252,6 @@ export {
253
252
  type TLExternalContent,
254
253
  type TLExternalContentSource,
255
254
  type TLFileExternalAsset,
256
- type TLFileReplaceExternalContent,
257
255
  type TLFilesExternalContent,
258
256
  type TLSvgTextExternalContent,
259
257
  type TLTextExternalContent,
@@ -348,8 +348,6 @@ export class Editor extends EventEmitter<TLEventMap> {
348
348
  this.getContainer = getContainer
349
349
 
350
350
  this.textMeasure = new TextManager(this)
351
- this.disposables.add(() => this.textMeasure.dispose())
352
-
353
351
  this.fonts = new FontManager(this, fontAssetUrls)
354
352
 
355
353
  this._tickManager = new TickManager(this)
@@ -2122,20 +2120,6 @@ export class Editor extends EventEmitter<TLEventMap> {
2122
2120
  return this.getShapesPageBounds(this.getSelectedShapeIds())
2123
2121
  }
2124
2122
 
2125
- /**
2126
- * The bounds of the selection bounding box in the current page space.
2127
- *
2128
- * @readonly
2129
- * @public
2130
- */
2131
- @computed getSelectionScreenBounds(): Box | undefined {
2132
- const bounds = this.getSelectionPageBounds()
2133
- if (!bounds) return undefined
2134
- const { x, y } = this.pageToScreen(bounds.point)
2135
- const zoom = this.getZoomLevel()
2136
- return new Box(x, y, bounds.width * zoom, bounds.height * zoom)
2137
- }
2138
-
2139
2123
  /**
2140
2124
  * @internal
2141
2125
  */
@@ -8827,7 +8811,6 @@ export class Editor extends EventEmitter<TLEventMap> {
8827
8811
  } = {
8828
8812
  text: null,
8829
8813
  files: null,
8830
- 'file-replace': null,
8831
8814
  embed: null,
8832
8815
  'svg-text': null,
8833
8816
  url: null,
@@ -8877,15 +8860,6 @@ export class Editor extends EventEmitter<TLEventMap> {
8877
8860
  return this.externalContentHandlers[info.type]?.(info as any)
8878
8861
  }
8879
8862
 
8880
- /**
8881
- * Handle replacing external content.
8882
- *
8883
- * @param info - Info about the external content.
8884
- */
8885
- async replaceExternalContent<E>(info: TLExternalContent<E>): Promise<void> {
8886
- return this.externalContentHandlers[info.type]?.(info as any)
8887
- }
8888
-
8889
8863
  /**
8890
8864
  * Get content that can be exported for the given shape ids.
8891
8865
  *
@@ -99,7 +99,7 @@ describe('TextManager', () => {
99
99
  })
100
100
 
101
101
  it('should handle empty text', () => {
102
- const result = textManager.measureText('', { ...defaultOpts, measureScrollWidth: true })
102
+ const result = textManager.measureText('', defaultOpts)
103
103
  expect(result).toHaveProperty('x', 0)
104
104
  expect(result).toHaveProperty('y', 0)
105
105
  expect(result).toHaveProperty('w')
@@ -128,6 +128,7 @@ describe('TextManager', () => {
128
128
  y: 0,
129
129
  w: expect.any(Number),
130
130
  h: expect.any(Number),
131
+ scrollWidth: expect.any(Number),
131
132
  })
132
133
  })
133
134
 
@@ -140,6 +141,7 @@ describe('TextManager', () => {
140
141
  y: 0,
141
142
  w: expect.any(Number),
142
143
  h: expect.any(Number),
144
+ scrollWidth: expect.any(Number),
143
145
  })
144
146
  })
145
147
 
@@ -152,6 +154,7 @@ describe('TextManager', () => {
152
154
  y: 0,
153
155
  w: expect.any(Number),
154
156
  h: expect.any(Number),
157
+ scrollWidth: expect.any(Number),
155
158
  })
156
159
  })
157
160
 
@@ -170,6 +173,7 @@ describe('TextManager', () => {
170
173
  y: 0,
171
174
  w: expect.any(Number),
172
175
  h: expect.any(Number),
176
+ scrollWidth: expect.any(Number),
173
177
  })
174
178
  })
175
179
  })