@tldraw/editor 3.14.0 → 3.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cjs/index.d.ts +36 -38
- package/dist-cjs/index.js +16 -16
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +96 -101
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
- package/dist-cjs/version.js +2 -2
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +36 -38
- package/dist-esm/index.mjs +41 -41
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +96 -101
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
- package/dist-esm/version.mjs +2 -2
- package/dist-esm/version.mjs.map +1 -1
- package/package.json +7 -7
- package/src/index.ts +62 -62
- package/src/lib/editor/managers/TextManager/TextManager.ts +108 -128
- package/src/lib/license/LicenseManager.test.ts +1 -1
- package/src/version.ts +2 -2
|
@@ -21,6 +21,7 @@ __export(TextManager_exports, {
|
|
|
21
21
|
TextManager: () => TextManager
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(TextManager_exports);
|
|
24
|
+
var import_utils = require("@tldraw/utils");
|
|
24
25
|
const fixNewLines = /\r?\n|\r/g;
|
|
25
26
|
function normalizeTextForDom(text) {
|
|
26
27
|
return text.replace(fixNewLines, "\n").split("\n").map((x) => x || " ").join("\n");
|
|
@@ -34,6 +35,14 @@ const textAlignmentsForLtr = {
|
|
|
34
35
|
"end-legacy": "right"
|
|
35
36
|
};
|
|
36
37
|
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
|
+
});
|
|
37
46
|
class TextManager {
|
|
38
47
|
constructor(editor) {
|
|
39
48
|
this.editor = editor;
|
|
@@ -43,27 +52,31 @@ class TextManager {
|
|
|
43
52
|
elm.setAttribute("dir", "auto");
|
|
44
53
|
elm.tabIndex = -1;
|
|
45
54
|
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
|
-
};
|
|
54
55
|
this.elm = elm;
|
|
56
|
+
for (const key of (0, import_utils.objectMapKeys)(initialDefaultStyles)) {
|
|
57
|
+
elm.style.setProperty(key, initialDefaultStyles[key]);
|
|
58
|
+
}
|
|
55
59
|
}
|
|
56
60
|
elm;
|
|
57
|
-
|
|
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
|
+
}
|
|
58
77
|
dispose() {
|
|
59
78
|
return this.elm.remove();
|
|
60
79
|
}
|
|
61
|
-
resetElmStyles() {
|
|
62
|
-
const { elm, defaultStyles } = this;
|
|
63
|
-
for (const key in defaultStyles) {
|
|
64
|
-
elm.style.setProperty(key, defaultStyles[key]);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
80
|
measureText(textToMeasure, opts) {
|
|
68
81
|
const div = document.createElement("div");
|
|
69
82
|
div.textContent = normalizeTextForDom(textToMeasure);
|
|
@@ -71,44 +84,33 @@ class TextManager {
|
|
|
71
84
|
}
|
|
72
85
|
measureHtml(html, opts) {
|
|
73
86
|
const { elm } = this;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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");
|
|
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
|
|
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
|
|
111
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();
|
|
113
|
+
}
|
|
112
114
|
}
|
|
113
115
|
/**
|
|
114
116
|
* Given an html element, measure the position of each span of unbroken
|
|
@@ -188,59 +190,52 @@ class TextManager {
|
|
|
188
190
|
measureTextSpans(textToMeasure, opts) {
|
|
189
191
|
if (textToMeasure === "") return [];
|
|
190
192
|
const { elm } = this;
|
|
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());
|
|
204
|
-
const elementWidth = Math.ceil(opts.width - opts.padding * 2);
|
|
205
|
-
elm.style.setProperty("width", `${elementWidth}px`);
|
|
206
|
-
elm.style.setProperty("height", "min-content");
|
|
207
|
-
elm.style.setProperty("text-align", textAlignmentsForLtr[opts.textAlign]);
|
|
208
193
|
const shouldTruncateToFirstLine = opts.overflow === "truncate-ellipsis" || opts.overflow === "truncate-clip";
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
elm.style.setProperty("width", `${elementWidth - ellipsisWidth}px`);
|
|
194
|
+
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);
|
|
227
211
|
elm.textContent = normalizedText;
|
|
228
|
-
const
|
|
229
|
-
shouldTruncateToFirstLine
|
|
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
|
-
}
|
|
212
|
+
const { spans, didTruncate } = this.measureElementTextNodeSpans(elm, {
|
|
213
|
+
shouldTruncateToFirstLine
|
|
240
214
|
});
|
|
241
|
-
|
|
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();
|
|
242
238
|
}
|
|
243
|
-
return spans;
|
|
244
239
|
}
|
|
245
240
|
}
|
|
246
241
|
//# 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 { 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": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
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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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist-cjs/version.js
CHANGED
|
@@ -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.
|
|
25
|
+
const version = "3.14.1";
|
|
26
26
|
const publishDates = {
|
|
27
27
|
major: "2024-09-13T14:36:29.063Z",
|
|
28
28
|
minor: "2025-07-03T08:34:52.269Z",
|
|
29
|
-
patch: "2025-07-
|
|
29
|
+
patch: "2025-07-09T16:10:51.026Z"
|
|
30
30
|
};
|
|
31
31
|
//# sourceMappingURL=version.js.map
|
package/dist-cjs/version.js.map
CHANGED
|
@@ -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.
|
|
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.1'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-07-03T08:34:52.269Z',\n\tpatch: '2025-07-09T16:10:51.026Z',\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
|
}
|
package/dist-esm/index.d.mts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/// <reference types="react" />
|
|
2
|
-
|
|
3
1
|
import { Atom } from '@tldraw/state';
|
|
4
2
|
import { BoxModel } from '@tldraw/tlschema';
|
|
5
3
|
import { ComponentType } from 'react';
|
|
@@ -19,8 +17,8 @@ import { NamedExoticComponent } from 'react';
|
|
|
19
17
|
import { Node as Node_2 } from '@tiptap/pm/model';
|
|
20
18
|
import { PerformanceTracker } from '@tldraw/utils';
|
|
21
19
|
import { PointerEventHandler } from 'react';
|
|
22
|
-
import
|
|
23
|
-
import
|
|
20
|
+
import * as React_2 from 'react';
|
|
21
|
+
import { default as React_3 } from 'react';
|
|
24
22
|
import { ReactElement } from 'react';
|
|
25
23
|
import { ReactNode } from 'react';
|
|
26
24
|
import { RecordProps } from '@tldraw/tlschema';
|
|
@@ -59,6 +57,7 @@ import { TLImageAsset } from '@tldraw/tlschema';
|
|
|
59
57
|
import { TLInstance } from '@tldraw/tlschema';
|
|
60
58
|
import { TLInstancePageState } from '@tldraw/tlschema';
|
|
61
59
|
import { TLInstancePresence } from '@tldraw/tlschema';
|
|
60
|
+
import { TLOpacityType } from '@tldraw/tlschema';
|
|
62
61
|
import { TLPage } from '@tldraw/tlschema';
|
|
63
62
|
import { TLPageId } from '@tldraw/tlschema';
|
|
64
63
|
import { TLParentId } from '@tldraw/tlschema';
|
|
@@ -716,8 +715,8 @@ export declare function createTLStore({ initialData, defaultName, id, assets, on
|
|
|
716
715
|
|
|
717
716
|
/** @public */
|
|
718
717
|
export declare function createTLUser(opts?: {
|
|
719
|
-
setUserPreferences?: (
|
|
720
|
-
userPreferences?: Signal<TLUserPreferences
|
|
718
|
+
setUserPreferences?: (userPreferences: TLUserPreferences) => void;
|
|
719
|
+
userPreferences?: Signal<TLUserPreferences>;
|
|
721
720
|
}): TLUser;
|
|
722
721
|
|
|
723
722
|
/** @public */
|
|
@@ -848,7 +847,7 @@ export declare const defaultTldrawOptions: {
|
|
|
848
847
|
readonly edgeScrollSpeed: 25;
|
|
849
848
|
readonly enableToolbarKeyboardShortcuts: true;
|
|
850
849
|
readonly exportProvider: ExoticComponent< {
|
|
851
|
-
children?: ReactNode;
|
|
850
|
+
children?: ReactNode | undefined;
|
|
852
851
|
}>;
|
|
853
852
|
readonly flattenImageBoundsExpand: 64;
|
|
854
853
|
readonly flattenImageBoundsPadding: 16;
|
|
@@ -1018,8 +1017,8 @@ export declare class Editor extends EventEmitter<TLEventMap> {
|
|
|
1018
1017
|
readonly timers: {
|
|
1019
1018
|
dispose: () => void;
|
|
1020
1019
|
requestAnimationFrame: (callback: FrameRequestCallback) => number;
|
|
1021
|
-
setInterval: (handler: TimerHandler, timeout?: number
|
|
1022
|
-
setTimeout: (handler: TimerHandler, timeout?: number
|
|
1020
|
+
setInterval: (handler: TimerHandler, timeout?: number, ...args: any[]) => number;
|
|
1021
|
+
setTimeout: (handler: TimerHandler, timeout?: number, ...args: any[]) => number;
|
|
1023
1022
|
};
|
|
1024
1023
|
/**
|
|
1025
1024
|
* A manager for the user and their preferences.
|
|
@@ -2068,10 +2067,10 @@ export declare class Editor extends EventEmitter<TLEventMap> {
|
|
|
2068
2067
|
*/
|
|
2069
2068
|
slideCamera(opts?: {
|
|
2070
2069
|
direction: VecLike;
|
|
2071
|
-
force?: boolean
|
|
2072
|
-
friction?: number
|
|
2070
|
+
force?: boolean;
|
|
2071
|
+
friction?: number;
|
|
2073
2072
|
speed: number;
|
|
2074
|
-
speedThreshold?: number
|
|
2073
|
+
speedThreshold?: number;
|
|
2075
2074
|
}): this;
|
|
2076
2075
|
/**
|
|
2077
2076
|
* Animate the camera to a user's cursor position. This also briefly show the user's cursor if it's not currently visible.
|
|
@@ -2683,12 +2682,12 @@ export declare class Editor extends EventEmitter<TLEventMap> {
|
|
|
2683
2682
|
*/
|
|
2684
2683
|
getShapeAtPoint(point: VecLike, opts?: {
|
|
2685
2684
|
filter?(shape: TLShape): boolean;
|
|
2686
|
-
hitFrameInside?: boolean
|
|
2687
|
-
hitInside?: boolean
|
|
2688
|
-
hitLabels?: boolean
|
|
2689
|
-
hitLocked?: boolean
|
|
2690
|
-
margin?: number
|
|
2691
|
-
renderingOnly?: boolean
|
|
2685
|
+
hitFrameInside?: boolean;
|
|
2686
|
+
hitInside?: boolean;
|
|
2687
|
+
hitLabels?: boolean;
|
|
2688
|
+
hitLocked?: boolean;
|
|
2689
|
+
margin?: number;
|
|
2690
|
+
renderingOnly?: boolean;
|
|
2692
2691
|
}): TLShape | undefined;
|
|
2693
2692
|
/**
|
|
2694
2693
|
* Get the shapes, if any, at a given page point.
|
|
@@ -2707,8 +2706,8 @@ export declare class Editor extends EventEmitter<TLEventMap> {
|
|
|
2707
2706
|
* @public
|
|
2708
2707
|
*/
|
|
2709
2708
|
getShapesAtPoint(point: VecLike, opts?: {
|
|
2710
|
-
hitInside?: boolean
|
|
2711
|
-
margin?: number
|
|
2709
|
+
hitInside?: boolean;
|
|
2710
|
+
margin?: number;
|
|
2712
2711
|
}): TLShape[];
|
|
2713
2712
|
/**
|
|
2714
2713
|
* Test whether a point (in the current page space) will will a shape. This method takes into account masks,
|
|
@@ -2726,8 +2725,8 @@ export declare class Editor extends EventEmitter<TLEventMap> {
|
|
|
2726
2725
|
* @public
|
|
2727
2726
|
*/
|
|
2728
2727
|
isPointInShape(shape: TLShape | TLShapeId, point: VecLike, opts?: {
|
|
2729
|
-
hitInside?: boolean
|
|
2730
|
-
margin?: number
|
|
2728
|
+
hitInside?: boolean;
|
|
2729
|
+
margin?: number;
|
|
2731
2730
|
}): boolean;
|
|
2732
2731
|
/**
|
|
2733
2732
|
* Convert a point in the current page space to a point in the local space of a shape. For example, if a
|
|
@@ -4013,7 +4012,7 @@ export declare class Editor extends EventEmitter<TLEventMap> {
|
|
|
4013
4012
|
}
|
|
4014
4013
|
|
|
4015
4014
|
/** @public */
|
|
4016
|
-
export declare const EditorContext:
|
|
4015
|
+
export declare const EditorContext: React_3.Context<Editor | null>;
|
|
4017
4016
|
|
|
4018
4017
|
/** @public */
|
|
4019
4018
|
export declare class Ellipse2d extends Geometry2d {
|
|
@@ -4038,7 +4037,7 @@ export declare class Ellipse2d extends Geometry2d {
|
|
|
4038
4037
|
}
|
|
4039
4038
|
|
|
4040
4039
|
/** @public */
|
|
4041
|
-
export declare class ErrorBoundary extends
|
|
4040
|
+
export declare class ErrorBoundary extends React_2.Component<React_2.PropsWithRef<React_2.PropsWithChildren<TLErrorBoundaryProps>>, {
|
|
4042
4041
|
error: Error | null;
|
|
4043
4042
|
}> {
|
|
4044
4043
|
static getDerivedStateFromError(error: Error): {
|
|
@@ -4048,7 +4047,7 @@ export declare class ErrorBoundary extends React_3.Component<React_3.PropsWithRe
|
|
|
4048
4047
|
error: null;
|
|
4049
4048
|
};
|
|
4050
4049
|
componentDidCatch(error: unknown): void;
|
|
4051
|
-
render(): boolean | JSX_2.Element | Iterable<
|
|
4050
|
+
render(): boolean | JSX_2.Element | Iterable<React_2.ReactNode> | null | number | string | undefined;
|
|
4052
4051
|
}
|
|
4053
4052
|
|
|
4054
4053
|
/** @public @react */
|
|
@@ -4452,7 +4451,7 @@ export declare class HistoryManager<R extends UnknownRecord> {
|
|
|
4452
4451
|
export declare function HTMLContainer({ children, className, ...rest }: HTMLContainerProps): JSX_2.Element;
|
|
4453
4452
|
|
|
4454
4453
|
/** @public */
|
|
4455
|
-
export declare type HTMLContainerProps =
|
|
4454
|
+
export declare type HTMLContainerProps = React_2.HTMLAttributes<HTMLDivElement>;
|
|
4456
4455
|
|
|
4457
4456
|
/** @public */
|
|
4458
4457
|
export declare const inlineBase64AssetStore: TLAssetStore;
|
|
@@ -4814,7 +4813,7 @@ export declare function precise(A: VecLike): string;
|
|
|
4814
4813
|
* @param event - To prevent default on
|
|
4815
4814
|
* @public
|
|
4816
4815
|
*/
|
|
4817
|
-
export declare function preventDefault(event: Event |
|
|
4816
|
+
export declare function preventDefault(event: Event | React_3.BaseSyntheticEvent): void;
|
|
4818
4817
|
|
|
4819
4818
|
/**
|
|
4820
4819
|
* Convert radians to degrees.
|
|
@@ -4849,10 +4848,10 @@ export declare class ReadonlySharedStyleMap {
|
|
|
4849
4848
|
getAsKnownValue<T>(prop: StyleProp<T>): T | undefined;
|
|
4850
4849
|
get size(): number;
|
|
4851
4850
|
equals(other: ReadonlySharedStyleMap): boolean;
|
|
4852
|
-
keys():
|
|
4853
|
-
values():
|
|
4854
|
-
entries():
|
|
4855
|
-
[Symbol.iterator]():
|
|
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>]>;
|
|
4856
4855
|
}
|
|
4857
4856
|
|
|
4858
4857
|
/** @public */
|
|
@@ -4876,7 +4875,7 @@ export declare class Rectangle2d extends Polygon2d {
|
|
|
4876
4875
|
export declare function refreshPage(): void;
|
|
4877
4876
|
|
|
4878
4877
|
/** @public */
|
|
4879
|
-
export declare function releasePointerCapture(element: Element, event: PointerEvent |
|
|
4878
|
+
export declare function releasePointerCapture(element: Element, event: PointerEvent | React_3.PointerEvent<Element>): void;
|
|
4880
4879
|
|
|
4881
4880
|
/** @public */
|
|
4882
4881
|
export declare type RequiredKeys<T, K extends keyof T> = Required<Pick<T, K>> & Omit<T, K>;
|
|
@@ -5019,7 +5018,7 @@ export declare type SelectionEdge = 'bottom' | 'left' | 'right' | 'top';
|
|
|
5019
5018
|
export declare type SelectionHandle = SelectionCorner | SelectionEdge;
|
|
5020
5019
|
|
|
5021
5020
|
/** @public */
|
|
5022
|
-
export declare function setPointerCapture(element: Element, event: PointerEvent |
|
|
5021
|
+
export declare function setPointerCapture(element: Element, event: PointerEvent | React_3.PointerEvent<Element>): void;
|
|
5023
5022
|
|
|
5024
5023
|
/** @public */
|
|
5025
5024
|
export declare function setRuntimeOverrides(input: Partial<typeof runtime>): void;
|
|
@@ -5725,7 +5724,7 @@ export declare function suffixSafeId(id: SafeId, suffix: string): SafeId;
|
|
|
5725
5724
|
export declare function SVGContainer({ children, className, ...rest }: SVGContainerProps): JSX_2.Element;
|
|
5726
5725
|
|
|
5727
5726
|
/** @public */
|
|
5728
|
-
export declare type SVGContainerProps =
|
|
5727
|
+
export declare type SVGContainerProps = React_2.ComponentProps<'svg'>;
|
|
5729
5728
|
|
|
5730
5729
|
/** @public */
|
|
5731
5730
|
export declare interface SvgExportContext {
|
|
@@ -5786,10 +5785,9 @@ export declare const TAB_ID: string;
|
|
|
5786
5785
|
export declare class TextManager {
|
|
5787
5786
|
editor: Editor;
|
|
5788
5787
|
private elm;
|
|
5789
|
-
private defaultStyles;
|
|
5790
5788
|
constructor(editor: Editor);
|
|
5789
|
+
private setElementStyles;
|
|
5791
5790
|
dispose(): void;
|
|
5792
|
-
private resetElmStyles;
|
|
5793
5791
|
measureText(textToMeasure: string, opts: TLMeasureTextOpts): BoxModel & {
|
|
5794
5792
|
scrollWidth: number;
|
|
5795
5793
|
};
|
|
@@ -6133,7 +6131,7 @@ export declare interface TLDragShapesOverInfo {
|
|
|
6133
6131
|
}
|
|
6134
6132
|
|
|
6135
6133
|
/** @public @react */
|
|
6136
|
-
export declare const TldrawEditor:
|
|
6134
|
+
export declare const TldrawEditor: React_3.NamedExoticComponent<TldrawEditorProps>;
|
|
6137
6135
|
|
|
6138
6136
|
/**
|
|
6139
6137
|
* Base props for the {@link tldraw#Tldraw} and {@link TldrawEditor} components.
|
|
@@ -6537,7 +6535,7 @@ export declare const tlenv: {
|
|
|
6537
6535
|
|
|
6538
6536
|
/** @public */
|
|
6539
6537
|
export declare interface TLErrorBoundaryProps {
|
|
6540
|
-
children:
|
|
6538
|
+
children: React_2.ReactNode;
|
|
6541
6539
|
onError?: ((error: unknown) => void) | null;
|
|
6542
6540
|
fallback: TLErrorFallbackComponent;
|
|
6543
6541
|
}
|