@tldraw/editor 3.14.2 → 3.15.0-canary.0183e172b2bf

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.
@@ -21,7 +21,6 @@ __export(TextManager_exports, {
21
21
  TextManager: () => TextManager
22
22
  });
23
23
  module.exports = __toCommonJS(TextManager_exports);
24
- var import_utils = require("@tldraw/utils");
25
24
  const fixNewLines = /\r?\n|\r/g;
26
25
  function normalizeTextForDom(text) {
27
26
  return text.replace(fixNewLines, "\n").split("\n").map((x) => x || " ").join("\n");
@@ -35,14 +34,6 @@ const textAlignmentsForLtr = {
35
34
  "end-legacy": "right"
36
35
  };
37
36
  const spaceCharacterRegex = /\s/;
38
- const initialDefaultStyles = Object.freeze({
39
- "overflow-wrap": "break-word",
40
- "word-break": "auto",
41
- width: null,
42
- height: null,
43
- "max-width": null,
44
- "min-width": null
45
- });
46
37
  class TextManager {
47
38
  constructor(editor) {
48
39
  this.editor = editor;
@@ -52,31 +43,27 @@ class TextManager {
52
43
  elm.setAttribute("dir", "auto");
53
44
  elm.tabIndex = -1;
54
45
  this.editor.getContainer().appendChild(elm);
46
+ this.defaultStyles = {
47
+ "overflow-wrap": "break-word",
48
+ "word-break": "auto",
49
+ width: null,
50
+ height: null,
51
+ "max-width": null,
52
+ "min-width": null
53
+ };
55
54
  this.elm = elm;
56
- for (const key of (0, import_utils.objectMapKeys)(initialDefaultStyles)) {
57
- elm.style.setProperty(key, initialDefaultStyles[key]);
58
- }
59
55
  }
60
56
  elm;
61
- setElementStyles(styles) {
62
- const stylesToReinstate = {};
63
- for (const key of (0, import_utils.objectMapKeys)(styles)) {
64
- if (typeof styles[key] === "string") {
65
- const oldValue = this.elm.style.getPropertyValue(key);
66
- if (oldValue === styles[key]) continue;
67
- stylesToReinstate[key] = oldValue;
68
- this.elm.style.setProperty(key, styles[key]);
69
- }
70
- }
71
- return () => {
72
- for (const key of (0, import_utils.objectMapKeys)(stylesToReinstate)) {
73
- this.elm.style.setProperty(key, stylesToReinstate[key]);
74
- }
75
- };
76
- }
57
+ defaultStyles;
77
58
  dispose() {
78
59
  return this.elm.remove();
79
60
  }
61
+ resetElmStyles() {
62
+ const { elm, defaultStyles } = this;
63
+ for (const key in defaultStyles) {
64
+ elm.style.setProperty(key, defaultStyles[key]);
65
+ }
66
+ }
80
67
  measureText(textToMeasure, opts) {
81
68
  const div = document.createElement("div");
82
69
  div.textContent = normalizeTextForDom(textToMeasure);
@@ -84,33 +71,44 @@ class TextManager {
84
71
  }
85
72
  measureHtml(html, opts) {
86
73
  const { elm } = this;
87
- const newStyles = {
88
- "font-family": opts.fontFamily,
89
- "font-style": opts.fontStyle,
90
- "font-weight": opts.fontWeight,
91
- "font-size": opts.fontSize + "px",
92
- "line-height": opts.lineHeight.toString(),
93
- padding: opts.padding,
94
- "max-width": opts.maxWidth ? opts.maxWidth + "px" : void 0,
95
- "min-width": opts.minWidth ? opts.minWidth + "px" : void 0,
96
- "overflow-wrap": opts.disableOverflowWrapBreaking ? "normal" : void 0,
97
- ...opts.otherStyles
98
- };
99
- const restoreStyles = this.setElementStyles(newStyles);
100
- try {
101
- elm.innerHTML = html;
102
- const scrollWidth = opts.measureScrollWidth ? elm.scrollWidth : 0;
103
- const rect = elm.getBoundingClientRect();
104
- return {
105
- x: 0,
106
- y: 0,
107
- w: rect.width,
108
- h: rect.height,
109
- scrollWidth
110
- };
111
- } finally {
112
- restoreStyles();
74
+ if (opts.otherStyles) {
75
+ for (const key in opts.otherStyles) {
76
+ if (!this.defaultStyles[key]) {
77
+ this.defaultStyles[key] = elm.style.getPropertyValue(key);
78
+ }
79
+ }
80
+ }
81
+ elm.innerHTML = html;
82
+ this.resetElmStyles();
83
+ elm.style.setProperty("font-family", opts.fontFamily);
84
+ elm.style.setProperty("font-style", opts.fontStyle);
85
+ elm.style.setProperty("font-weight", opts.fontWeight);
86
+ elm.style.setProperty("font-size", opts.fontSize + "px");
87
+ elm.style.setProperty("line-height", opts.lineHeight.toString());
88
+ elm.style.setProperty("padding", opts.padding);
89
+ if (opts.maxWidth) {
90
+ elm.style.setProperty("max-width", opts.maxWidth + "px");
91
+ }
92
+ if (opts.minWidth) {
93
+ elm.style.setProperty("min-width", opts.minWidth + "px");
94
+ }
95
+ if (opts.disableOverflowWrapBreaking) {
96
+ elm.style.setProperty("overflow-wrap", "normal");
113
97
  }
98
+ if (opts.otherStyles) {
99
+ for (const [key, value] of Object.entries(opts.otherStyles)) {
100
+ elm.style.setProperty(key, value);
101
+ }
102
+ }
103
+ const scrollWidth = opts.measureScrollWidth ? elm.scrollWidth : 0;
104
+ const rect = elm.getBoundingClientRect();
105
+ return {
106
+ x: 0,
107
+ y: 0,
108
+ w: rect.width,
109
+ h: rect.height,
110
+ scrollWidth
111
+ };
114
112
  }
115
113
  /**
116
114
  * Given an html element, measure the position of each span of unbroken
@@ -190,52 +188,59 @@ class TextManager {
190
188
  measureTextSpans(textToMeasure, opts) {
191
189
  if (textToMeasure === "") return [];
192
190
  const { elm } = this;
193
- const shouldTruncateToFirstLine = opts.overflow === "truncate-ellipsis" || opts.overflow === "truncate-clip";
191
+ if (opts.otherStyles) {
192
+ for (const key in opts.otherStyles) {
193
+ if (!this.defaultStyles[key]) {
194
+ this.defaultStyles[key] = elm.style.getPropertyValue(key);
195
+ }
196
+ }
197
+ }
198
+ this.resetElmStyles();
199
+ elm.style.setProperty("font-family", opts.fontFamily);
200
+ elm.style.setProperty("font-style", opts.fontStyle);
201
+ elm.style.setProperty("font-weight", opts.fontWeight);
202
+ elm.style.setProperty("font-size", opts.fontSize + "px");
203
+ elm.style.setProperty("line-height", opts.lineHeight.toString());
194
204
  const elementWidth = Math.ceil(opts.width - opts.padding * 2);
195
- const newStyles = {
196
- "font-family": opts.fontFamily,
197
- "font-style": opts.fontStyle,
198
- "font-weight": opts.fontWeight,
199
- "font-size": opts.fontSize + "px",
200
- "line-height": opts.lineHeight.toString(),
201
- width: `${elementWidth}px`,
202
- height: "min-content",
203
- "text-align": textAlignmentsForLtr[opts.textAlign],
204
- "overflow-wrap": shouldTruncateToFirstLine ? "anywhere" : void 0,
205
- "word-break": shouldTruncateToFirstLine ? "break-all" : void 0,
206
- ...opts.otherStyles
207
- };
208
- const restoreStyles = this.setElementStyles(newStyles);
209
- try {
210
- const normalizedText = normalizeTextForDom(textToMeasure);
205
+ elm.style.setProperty("width", `${elementWidth}px`);
206
+ elm.style.setProperty("height", "min-content");
207
+ elm.style.setProperty("text-align", textAlignmentsForLtr[opts.textAlign]);
208
+ const shouldTruncateToFirstLine = opts.overflow === "truncate-ellipsis" || opts.overflow === "truncate-clip";
209
+ if (shouldTruncateToFirstLine) {
210
+ elm.style.setProperty("overflow-wrap", "anywhere");
211
+ elm.style.setProperty("word-break", "break-all");
212
+ }
213
+ if (opts.otherStyles) {
214
+ for (const [key, value] of Object.entries(opts.otherStyles)) {
215
+ elm.style.setProperty(key, value);
216
+ }
217
+ }
218
+ const normalizedText = normalizeTextForDom(textToMeasure);
219
+ elm.textContent = normalizedText;
220
+ const { spans, didTruncate } = this.measureElementTextNodeSpans(elm, {
221
+ shouldTruncateToFirstLine
222
+ });
223
+ if (opts.overflow === "truncate-ellipsis" && didTruncate) {
224
+ elm.textContent = "\u2026";
225
+ const ellipsisWidth = Math.ceil(this.measureElementTextNodeSpans(elm).spans[0].box.w);
226
+ elm.style.setProperty("width", `${elementWidth - ellipsisWidth}px`);
211
227
  elm.textContent = normalizedText;
212
- const { spans, didTruncate } = this.measureElementTextNodeSpans(elm, {
213
- shouldTruncateToFirstLine
228
+ const truncatedSpans = this.measureElementTextNodeSpans(elm, {
229
+ shouldTruncateToFirstLine: true
230
+ }).spans;
231
+ const lastSpan = truncatedSpans[truncatedSpans.length - 1];
232
+ truncatedSpans.push({
233
+ text: "\u2026",
234
+ box: {
235
+ x: Math.min(lastSpan.box.x + lastSpan.box.w, opts.width - opts.padding - ellipsisWidth),
236
+ y: lastSpan.box.y,
237
+ w: ellipsisWidth,
238
+ h: lastSpan.box.h
239
+ }
214
240
  });
215
- if (opts.overflow === "truncate-ellipsis" && didTruncate) {
216
- elm.textContent = "\u2026";
217
- const ellipsisWidth = Math.ceil(this.measureElementTextNodeSpans(elm).spans[0].box.w);
218
- elm.style.setProperty("width", `${elementWidth - ellipsisWidth}px`);
219
- elm.textContent = normalizedText;
220
- const truncatedSpans = this.measureElementTextNodeSpans(elm, {
221
- shouldTruncateToFirstLine: true
222
- }).spans;
223
- const lastSpan = truncatedSpans[truncatedSpans.length - 1];
224
- truncatedSpans.push({
225
- text: "\u2026",
226
- box: {
227
- x: Math.min(lastSpan.box.x + lastSpan.box.w, opts.width - opts.padding - ellipsisWidth),
228
- y: lastSpan.box.y,
229
- w: ellipsisWidth,
230
- h: lastSpan.box.h
231
- }
232
- });
233
- return truncatedSpans;
234
- }
235
- return spans;
236
- } finally {
237
- restoreStyles();
241
+ return truncatedSpans;
238
242
  }
243
+ return spans;
239
244
  }
240
245
  }
241
246
  //# sourceMappingURL=TextManager.js.map
@@ -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 { objectMapKeys } from '@tldraw/utils'\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\t/** This must be a number, e.g. 1.35, not a pixel value. */\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\nconst initialDefaultStyles = Object.freeze({\n\t'overflow-wrap': 'break-word',\n\t'word-break': 'auto',\n\twidth: null,\n\theight: null,\n\t'max-width': null,\n\t'min-width': null,\n})\n\n/** @public */\nexport class TextManager {\n\tprivate elm: HTMLDivElement\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\tthis.elm = elm\n\n\t\tfor (const key of objectMapKeys(initialDefaultStyles)) {\n\t\t\telm.style.setProperty(key, initialDefaultStyles[key])\n\t\t}\n\t}\n\n\tprivate setElementStyles(styles: Record<string, string | undefined>) {\n\t\tconst stylesToReinstate = {} as any\n\t\tfor (const key of objectMapKeys(styles)) {\n\t\t\tif (typeof styles[key] === 'string') {\n\t\t\t\tconst oldValue = this.elm.style.getPropertyValue(key)\n\t\t\t\tif (oldValue === styles[key]) continue\n\t\t\t\tstylesToReinstate[key] = oldValue\n\t\t\t\tthis.elm.style.setProperty(key, styles[key])\n\t\t\t}\n\t\t}\n\t\treturn () => {\n\t\t\tfor (const key of objectMapKeys(stylesToReinstate)) {\n\t\t\t\tthis.elm.style.setProperty(key, stylesToReinstate[key])\n\t\t\t}\n\t\t}\n\t}\n\n\tdispose() {\n\t\treturn this.elm.remove()\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\tconst newStyles = {\n\t\t\t'font-family': opts.fontFamily,\n\t\t\t'font-style': opts.fontStyle,\n\t\t\t'font-weight': opts.fontWeight,\n\t\t\t'font-size': opts.fontSize + 'px',\n\t\t\t'line-height': opts.lineHeight.toString(),\n\t\t\tpadding: opts.padding,\n\t\t\t'max-width': opts.maxWidth ? opts.maxWidth + 'px' : undefined,\n\t\t\t'min-width': opts.minWidth ? opts.minWidth + 'px' : undefined,\n\t\t\t'overflow-wrap': opts.disableOverflowWrapBreaking ? 'normal' : undefined,\n\t\t\t...opts.otherStyles,\n\t\t}\n\n\t\tconst restoreStyles = this.setElementStyles(newStyles)\n\n\t\ttry {\n\t\t\telm.innerHTML = html\n\n\t\t\tconst scrollWidth = opts.measureScrollWidth ? elm.scrollWidth : 0\n\t\t\tconst rect = elm.getBoundingClientRect()\n\n\t\t\treturn {\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\tw: rect.width,\n\t\t\t\th: rect.height,\n\t\t\t\tscrollWidth,\n\t\t\t}\n\t\t} finally {\n\t\t\trestoreStyles()\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\tconst shouldTruncateToFirstLine =\n\t\t\topts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'\n\t\tconst elementWidth = Math.ceil(opts.width - opts.padding * 2)\n\t\tconst newStyles = {\n\t\t\t'font-family': opts.fontFamily,\n\t\t\t'font-style': opts.fontStyle,\n\t\t\t'font-weight': opts.fontWeight,\n\t\t\t'font-size': opts.fontSize + 'px',\n\t\t\t'line-height': opts.lineHeight.toString(),\n\t\t\twidth: `${elementWidth}px`,\n\t\t\theight: 'min-content',\n\t\t\t'text-align': textAlignmentsForLtr[opts.textAlign],\n\t\t\t'overflow-wrap': shouldTruncateToFirstLine ? 'anywhere' : undefined,\n\t\t\t'word-break': shouldTruncateToFirstLine ? 'break-all' : undefined,\n\t\t\t...opts.otherStyles,\n\t\t}\n\t\tconst restoreStyles = this.setElementStyles(newStyles)\n\n\t\ttry {\n\t\t\tconst normalizedText = normalizeTextForDom(textToMeasure)\n\n\t\t\t// Render the text into the measurement element:\n\t\t\telm.textContent = normalizedText\n\n\t\t\t// actually measure the text:\n\t\t\tconst { spans, didTruncate } = this.measureElementTextNodeSpans(elm, {\n\t\t\t\tshouldTruncateToFirstLine,\n\t\t\t})\n\n\t\t\tif (opts.overflow === 'truncate-ellipsis' && didTruncate) {\n\t\t\t\t// we need to measure the ellipsis to know how much space it takes up\n\t\t\t\telm.textContent = '\u2026'\n\t\t\t\tconst ellipsisWidth = Math.ceil(this.measureElementTextNodeSpans(elm).spans[0].box.w)\n\n\t\t\t\t// then, we need to subtract that space from the width we have and measure again:\n\t\t\t\telm.style.setProperty('width', `${elementWidth - ellipsisWidth}px`)\n\t\t\t\telm.textContent = normalizedText\n\t\t\t\tconst truncatedSpans = this.measureElementTextNodeSpans(elm, {\n\t\t\t\t\tshouldTruncateToFirstLine: true,\n\t\t\t\t}).spans\n\n\t\t\t\t// Finally, we add in our ellipsis at the end of the last span. We\n\t\t\t\t// have to do this after measuring, not before, because adding the\n\t\t\t\t// ellipsis changes how whitespace might be getting collapsed by the\n\t\t\t\t// browser.\n\t\t\t\tconst lastSpan = truncatedSpans[truncatedSpans.length - 1]!\n\t\t\t\ttruncatedSpans.push({\n\t\t\t\t\ttext: '\u2026',\n\t\t\t\t\tbox: {\n\t\t\t\t\t\tx: Math.min(lastSpan.box.x + lastSpan.box.w, opts.width - opts.padding - ellipsisWidth),\n\t\t\t\t\t\ty: lastSpan.box.y,\n\t\t\t\t\t\tw: ellipsisWidth,\n\t\t\t\t\t\th: lastSpan.box.h,\n\t\t\t\t\t},\n\t\t\t\t})\n\n\t\t\t\treturn truncatedSpans\n\t\t\t}\n\n\t\t\treturn spans\n\t\t} finally {\n\t\t\trestoreStyles()\n\t\t}\n\t}\n}\n"],
5
- "mappings": "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",
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\t/** This must be a number, e.g. 1.35, not a pixel value. */\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'overflow-wrap': 'break-word',\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.toString())\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.toString())\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": "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",
6
6
  "names": []
7
7
  }
@@ -50,10 +50,7 @@ function useEditor() {
50
50
  function useMaybeEditor() {
51
51
  return import_react.default.useContext(EditorContext);
52
52
  }
53
- function EditorProvider({
54
- editor,
55
- children
56
- }) {
53
+ function EditorProvider({ editor, children }) {
57
54
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EditorContext.Provider, { value: editor, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_useSafeId.IdProvider, { children }) });
58
55
  }
59
56
  //# sourceMappingURL=useEditor.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/hooks/useEditor.tsx"],
4
- "sourcesContent": ["import React, { createContext } from 'react'\nimport { Editor } from '../editor/Editor'\nimport { IdProvider } from './useSafeId'\n\n/** @public */\nexport const EditorContext = createContext<Editor | null>(null)\n\n/** @public */\nexport function useEditor(): Editor {\n\tconst editor = React.useContext(EditorContext)\n\tif (!editor) {\n\t\tthrow new Error(\n\t\t\t'useEditor must be used inside of the <Tldraw /> or <TldrawEditor /> components'\n\t\t)\n\t}\n\treturn editor\n}\n\n/** @public */\nexport function useMaybeEditor(): Editor | null {\n\treturn React.useContext(EditorContext)\n}\n\nexport function EditorProvider({\n\teditor,\n\tchildren,\n}: {\n\teditor: Editor\n\tchildren: React.ReactNode\n}) {\n\treturn (\n\t\t<EditorContext.Provider value={editor}>\n\t\t\t<IdProvider>{children}</IdProvider>\n\t\t</EditorContext.Provider>\n\t)\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgCG;AAhCH,mBAAqC;AAErC,uBAA2B;AAGpB,MAAM,oBAAgB,4BAA6B,IAAI;AAGvD,SAAS,YAAoB;AACnC,QAAM,SAAS,aAAAA,QAAM,WAAW,aAAa;AAC7C,MAAI,CAAC,QAAQ;AACZ,UAAM,IAAI;AAAA,MACT;AAAA,IACD;AAAA,EACD;AACA,SAAO;AACR;AAGO,SAAS,iBAAgC;AAC/C,SAAO,aAAAA,QAAM,WAAW,aAAa;AACtC;AAEO,SAAS,eAAe;AAAA,EAC9B;AAAA,EACA;AACD,GAGG;AACF,SACC,4CAAC,cAAc,UAAd,EAAuB,OAAO,QAC9B,sDAAC,+BAAY,UAAS,GACvB;AAEF;",
4
+ "sourcesContent": ["import React, { createContext } from 'react'\nimport { Editor } from '../editor/Editor'\nimport { IdProvider } from './useSafeId'\n\n/** @public */\nexport const EditorContext = createContext<Editor | null>(null)\n\n/** @public */\nexport function useEditor(): Editor {\n\tconst editor = React.useContext(EditorContext)\n\tif (!editor) {\n\t\tthrow new Error(\n\t\t\t'useEditor must be used inside of the <Tldraw /> or <TldrawEditor /> components'\n\t\t)\n\t}\n\treturn editor\n}\n\n/** @public */\nexport function useMaybeEditor(): Editor | null {\n\treturn React.useContext(EditorContext)\n}\n\n/** @public */\nexport interface EditorProviderProps {\n\teditor: Editor\n\tchildren: React.ReactNode\n}\n\n/** @public @react */\nexport function EditorProvider({ editor, children }: EditorProviderProps) {\n\treturn (\n\t\t<EditorContext.Provider value={editor}>\n\t\t\t<IdProvider>{children}</IdProvider>\n\t\t</EditorContext.Provider>\n\t)\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiCG;AAjCH,mBAAqC;AAErC,uBAA2B;AAGpB,MAAM,oBAAgB,4BAA6B,IAAI;AAGvD,SAAS,YAAoB;AACnC,QAAM,SAAS,aAAAA,QAAM,WAAW,aAAa;AAC7C,MAAI,CAAC,QAAQ;AACZ,UAAM,IAAI;AAAA,MACT;AAAA,IACD;AAAA,EACD;AACA,SAAO;AACR;AAGO,SAAS,iBAAgC;AAC/C,SAAO,aAAAA,QAAM,WAAW,aAAa;AACtC;AASO,SAAS,eAAe,EAAE,QAAQ,SAAS,GAAwB;AACzE,SACC,4CAAC,cAAc,UAAd,EAAuB,OAAO,QAC9B,sDAAC,+BAAY,UAAS,GACvB;AAEF;",
6
6
  "names": ["React"]
7
7
  }
@@ -22,10 +22,10 @@ __export(version_exports, {
22
22
  version: () => version
23
23
  });
24
24
  module.exports = __toCommonJS(version_exports);
25
- const version = "3.14.2";
25
+ const version = "3.15.0-canary.0183e172b2bf";
26
26
  const publishDates = {
27
27
  major: "2024-09-13T14:36:29.063Z",
28
- minor: "2025-07-03T08:34:52.269Z",
29
- patch: "2025-07-10T09:54:52.889Z"
28
+ minor: "2025-07-03T13:14:18.791Z",
29
+ patch: "2025-07-03T13:14:18.791Z"
30
30
  };
31
31
  //# sourceMappingURL=version.js.map
@@ -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.2'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-07-03T08:34:52.269Z',\n\tpatch: '2025-07-10T09:54:52.889Z',\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.15.0-canary.0183e172b2bf'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-07-03T13:14:18.791Z',\n\tpatch: '2025-07-03T13:14:18.791Z',\n}\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
6
6
  "names": []
7
7
  }
@@ -1,10 +1,12 @@
1
+ /// <reference types="react" />
2
+
1
3
  import { Atom } from '@tldraw/state';
2
4
  import { BoxModel } from '@tldraw/tlschema';
3
5
  import { ComponentType } from 'react';
4
6
  import { Computed } from '@tldraw/state';
5
7
  import { Dispatch } from 'react';
6
8
  import { Editor as Editor_2 } from '@tiptap/core';
7
- import { EditorProviderProps } from '@tiptap/react';
9
+ import { EditorProviderProps as EditorProviderProps_2 } from '@tiptap/react';
8
10
  import EventEmitter from 'eventemitter3';
9
11
  import { ExoticComponent } from 'react';
10
12
  import { HistoryEntry } from '@tldraw/store';
@@ -17,8 +19,8 @@ import { NamedExoticComponent } from 'react';
17
19
  import { Node as Node_2 } from '@tiptap/pm/model';
18
20
  import { PerformanceTracker } from '@tldraw/utils';
19
21
  import { PointerEventHandler } from 'react';
20
- import * as React_2 from 'react';
21
- import { default as React_3 } from 'react';
22
+ import { default as React_2 } from 'react';
23
+ import * as React_3 from 'react';
22
24
  import { ReactElement } from 'react';
23
25
  import { ReactNode } from 'react';
24
26
  import { RecordProps } from '@tldraw/tlschema';
@@ -57,7 +59,6 @@ import { TLImageAsset } from '@tldraw/tlschema';
57
59
  import { TLInstance } from '@tldraw/tlschema';
58
60
  import { TLInstancePageState } from '@tldraw/tlschema';
59
61
  import { TLInstancePresence } from '@tldraw/tlschema';
60
- import { TLOpacityType } from '@tldraw/tlschema';
61
62
  import { TLPage } from '@tldraw/tlschema';
62
63
  import { TLPageId } from '@tldraw/tlschema';
63
64
  import { TLParentId } from '@tldraw/tlschema';
@@ -715,8 +716,8 @@ export declare function createTLStore({ initialData, defaultName, id, assets, on
715
716
 
716
717
  /** @public */
717
718
  export declare function createTLUser(opts?: {
718
- setUserPreferences?: (userPreferences: TLUserPreferences) => void;
719
- userPreferences?: Signal<TLUserPreferences>;
719
+ setUserPreferences?: ((userPreferences: TLUserPreferences) => void) | undefined;
720
+ userPreferences?: Signal<TLUserPreferences, unknown> | undefined;
720
721
  }): TLUser;
721
722
 
722
723
  /** @public */
@@ -847,7 +848,7 @@ export declare const defaultTldrawOptions: {
847
848
  readonly edgeScrollSpeed: 25;
848
849
  readonly enableToolbarKeyboardShortcuts: true;
849
850
  readonly exportProvider: ExoticComponent< {
850
- children?: ReactNode | undefined;
851
+ children?: ReactNode;
851
852
  }>;
852
853
  readonly flattenImageBoundsExpand: 64;
853
854
  readonly flattenImageBoundsPadding: 16;
@@ -1017,8 +1018,8 @@ export declare class Editor extends EventEmitter<TLEventMap> {
1017
1018
  readonly timers: {
1018
1019
  dispose: () => void;
1019
1020
  requestAnimationFrame: (callback: FrameRequestCallback) => number;
1020
- setInterval: (handler: TimerHandler, timeout?: number, ...args: any[]) => number;
1021
- setTimeout: (handler: TimerHandler, timeout?: number, ...args: any[]) => number;
1021
+ setInterval: (handler: TimerHandler, timeout?: number | undefined, ...args: any[]) => number;
1022
+ setTimeout: (handler: TimerHandler, timeout?: number | undefined, ...args: any[]) => number;
1022
1023
  };
1023
1024
  /**
1024
1025
  * A manager for the user and their preferences.
@@ -2067,10 +2068,10 @@ export declare class Editor extends EventEmitter<TLEventMap> {
2067
2068
  */
2068
2069
  slideCamera(opts?: {
2069
2070
  direction: VecLike;
2070
- force?: boolean;
2071
- friction?: number;
2071
+ force?: boolean | undefined;
2072
+ friction?: number | undefined;
2072
2073
  speed: number;
2073
- speedThreshold?: number;
2074
+ speedThreshold?: number | undefined;
2074
2075
  }): this;
2075
2076
  /**
2076
2077
  * Animate the camera to a user's cursor position. This also briefly show the user's cursor if it's not currently visible.
@@ -2682,12 +2683,12 @@ export declare class Editor extends EventEmitter<TLEventMap> {
2682
2683
  */
2683
2684
  getShapeAtPoint(point: VecLike, opts?: {
2684
2685
  filter?(shape: TLShape): boolean;
2685
- hitFrameInside?: boolean;
2686
- hitInside?: boolean;
2687
- hitLabels?: boolean;
2688
- hitLocked?: boolean;
2689
- margin?: number;
2690
- renderingOnly?: boolean;
2686
+ hitFrameInside?: boolean | undefined;
2687
+ hitInside?: boolean | undefined;
2688
+ hitLabels?: boolean | undefined;
2689
+ hitLocked?: boolean | undefined;
2690
+ margin?: number | undefined;
2691
+ renderingOnly?: boolean | undefined;
2691
2692
  }): TLShape | undefined;
2692
2693
  /**
2693
2694
  * Get the shapes, if any, at a given page point.
@@ -2706,8 +2707,8 @@ export declare class Editor extends EventEmitter<TLEventMap> {
2706
2707
  * @public
2707
2708
  */
2708
2709
  getShapesAtPoint(point: VecLike, opts?: {
2709
- hitInside?: boolean;
2710
- margin?: number;
2710
+ hitInside?: boolean | undefined;
2711
+ margin?: number | undefined;
2711
2712
  }): TLShape[];
2712
2713
  /**
2713
2714
  * Test whether a point (in the current page space) will will a shape. This method takes into account masks,
@@ -2725,8 +2726,8 @@ export declare class Editor extends EventEmitter<TLEventMap> {
2725
2726
  * @public
2726
2727
  */
2727
2728
  isPointInShape(shape: TLShape | TLShapeId, point: VecLike, opts?: {
2728
- hitInside?: boolean;
2729
- margin?: number;
2729
+ hitInside?: boolean | undefined;
2730
+ margin?: number | undefined;
2730
2731
  }): boolean;
2731
2732
  /**
2732
2733
  * Convert a point in the current page space to a point in the local space of a shape. For example, if a
@@ -4012,7 +4013,16 @@ export declare class Editor extends EventEmitter<TLEventMap> {
4012
4013
  }
4013
4014
 
4014
4015
  /** @public */
4015
- export declare const EditorContext: React_3.Context<Editor | null>;
4016
+ export declare const EditorContext: React_2.Context<Editor | null>;
4017
+
4018
+ /** @public @react */
4019
+ export declare function EditorProvider({ editor, children }: EditorProviderProps): JSX_2.Element;
4020
+
4021
+ /** @public */
4022
+ export declare interface EditorProviderProps {
4023
+ editor: Editor;
4024
+ children: React_2.ReactNode;
4025
+ }
4016
4026
 
4017
4027
  /** @public */
4018
4028
  export declare class Ellipse2d extends Geometry2d {
@@ -4037,7 +4047,7 @@ export declare class Ellipse2d extends Geometry2d {
4037
4047
  }
4038
4048
 
4039
4049
  /** @public */
4040
- export declare class ErrorBoundary extends React_2.Component<React_2.PropsWithRef<React_2.PropsWithChildren<TLErrorBoundaryProps>>, {
4050
+ export declare class ErrorBoundary extends React_3.Component<React_3.PropsWithRef<React_3.PropsWithChildren<TLErrorBoundaryProps>>, {
4041
4051
  error: Error | null;
4042
4052
  }> {
4043
4053
  static getDerivedStateFromError(error: Error): {
@@ -4047,7 +4057,7 @@ export declare class ErrorBoundary extends React_2.Component<React_2.PropsWithRe
4047
4057
  error: null;
4048
4058
  };
4049
4059
  componentDidCatch(error: unknown): void;
4050
- render(): boolean | JSX_2.Element | Iterable<React_2.ReactNode> | null | number | string | undefined;
4060
+ render(): boolean | JSX_2.Element | Iterable<React_3.ReactNode> | null | number | string | undefined;
4051
4061
  }
4052
4062
 
4053
4063
  /** @public @react */
@@ -4451,7 +4461,7 @@ export declare class HistoryManager<R extends UnknownRecord> {
4451
4461
  export declare function HTMLContainer({ children, className, ...rest }: HTMLContainerProps): JSX_2.Element;
4452
4462
 
4453
4463
  /** @public */
4454
- export declare type HTMLContainerProps = React_2.HTMLAttributes<HTMLDivElement>;
4464
+ export declare type HTMLContainerProps = React_3.HTMLAttributes<HTMLDivElement>;
4455
4465
 
4456
4466
  /** @public */
4457
4467
  export declare const inlineBase64AssetStore: TLAssetStore;
@@ -4813,7 +4823,7 @@ export declare function precise(A: VecLike): string;
4813
4823
  * @param event - To prevent default on
4814
4824
  * @public
4815
4825
  */
4816
- export declare function preventDefault(event: Event | React_3.BaseSyntheticEvent): void;
4826
+ export declare function preventDefault(event: Event | React_2.BaseSyntheticEvent): void;
4817
4827
 
4818
4828
  /**
4819
4829
  * Convert radians to degrees.
@@ -4848,10 +4858,10 @@ export declare class ReadonlySharedStyleMap {
4848
4858
  getAsKnownValue<T>(prop: StyleProp<T>): T | undefined;
4849
4859
  get size(): number;
4850
4860
  equals(other: ReadonlySharedStyleMap): boolean;
4851
- keys(): MapIterator<StyleProp<any>>;
4852
- values(): MapIterator<SharedStyle<unknown>>;
4853
- entries(): MapIterator<[StyleProp<any>, SharedStyle<unknown>]>;
4854
- [Symbol.iterator](): MapIterator<[StyleProp<any>, SharedStyle<unknown>]>;
4861
+ keys(): IterableIterator<StyleProp<any>>;
4862
+ values(): IterableIterator<SharedStyle<unknown>>;
4863
+ entries(): IterableIterator<[StyleProp<any>, SharedStyle<unknown>]>;
4864
+ [Symbol.iterator](): IterableIterator<[StyleProp<any>, SharedStyle<unknown>]>;
4855
4865
  }
4856
4866
 
4857
4867
  /** @public */
@@ -4875,7 +4885,7 @@ export declare class Rectangle2d extends Polygon2d {
4875
4885
  export declare function refreshPage(): void;
4876
4886
 
4877
4887
  /** @public */
4878
- export declare function releasePointerCapture(element: Element, event: PointerEvent | React_3.PointerEvent<Element>): void;
4888
+ export declare function releasePointerCapture(element: Element, event: PointerEvent | React_2.PointerEvent<Element>): void;
4879
4889
 
4880
4890
  /** @public */
4881
4891
  export declare type RequiredKeys<T, K extends keyof T> = Required<Pick<T, K>> & Omit<T, K>;
@@ -5018,7 +5028,7 @@ export declare type SelectionEdge = 'bottom' | 'left' | 'right' | 'top';
5018
5028
  export declare type SelectionHandle = SelectionCorner | SelectionEdge;
5019
5029
 
5020
5030
  /** @public */
5021
- export declare function setPointerCapture(element: Element, event: PointerEvent | React_3.PointerEvent<Element>): void;
5031
+ export declare function setPointerCapture(element: Element, event: PointerEvent | React_2.PointerEvent<Element>): void;
5022
5032
 
5023
5033
  /** @public */
5024
5034
  export declare function setRuntimeOverrides(input: Partial<typeof runtime>): void;
@@ -5724,7 +5734,7 @@ export declare function suffixSafeId(id: SafeId, suffix: string): SafeId;
5724
5734
  export declare function SVGContainer({ children, className, ...rest }: SVGContainerProps): JSX_2.Element;
5725
5735
 
5726
5736
  /** @public */
5727
- export declare type SVGContainerProps = React_2.ComponentProps<'svg'>;
5737
+ export declare type SVGContainerProps = React_3.ComponentProps<'svg'>;
5728
5738
 
5729
5739
  /** @public */
5730
5740
  export declare interface SvgExportContext {
@@ -5785,9 +5795,10 @@ export declare const TAB_ID: string;
5785
5795
  export declare class TextManager {
5786
5796
  editor: Editor;
5787
5797
  private elm;
5798
+ private defaultStyles;
5788
5799
  constructor(editor: Editor);
5789
- private setElementStyles;
5790
5800
  dispose(): void;
5801
+ private resetElmStyles;
5791
5802
  measureText(textToMeasure: string, opts: TLMeasureTextOpts): BoxModel & {
5792
5803
  scrollWidth: number;
5793
5804
  };
@@ -6131,7 +6142,7 @@ export declare interface TLDragShapesOverInfo {
6131
6142
  }
6132
6143
 
6133
6144
  /** @public @react */
6134
- export declare const TldrawEditor: React_3.NamedExoticComponent<TldrawEditorProps>;
6145
+ export declare const TldrawEditor: React_2.NamedExoticComponent<TldrawEditorProps>;
6135
6146
 
6136
6147
  /**
6137
6148
  * Base props for the {@link tldraw#Tldraw} and {@link TldrawEditor} components.
@@ -6535,7 +6546,7 @@ export declare const tlenv: {
6535
6546
 
6536
6547
  /** @public */
6537
6548
  export declare interface TLErrorBoundaryProps {
6538
- children: React_2.ReactNode;
6549
+ children: React_3.ReactNode;
6539
6550
  onError?: ((error: unknown) => void) | null;
6540
6551
  fallback: TLErrorFallbackComponent;
6541
6552
  }
@@ -7429,7 +7440,7 @@ export declare interface TLTextExternalContentSource {
7429
7440
 
7430
7441
  /** @public */
7431
7442
  export declare interface TLTextOptions {
7432
- tipTapConfig?: EditorProviderProps;
7443
+ tipTapConfig?: EditorProviderProps_2;
7433
7444
  addFontsFromNode?: RichTextFontVisitor;
7434
7445
  }
7435
7446