@tldraw/editor 3.14.0-canary.4deeaa9df15f → 3.14.0-canary.4f9d90070add
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 +35 -35
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +2 -11
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +42 -72
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
- package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
- package/dist-cjs/lib/hooks/useCanvasEvents.js +2 -1
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +35 -35
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +2 -11
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +42 -72
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +2 -1
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +1 -20
- package/package.json +7 -7
- package/src/index.ts +0 -1
- package/src/lib/editor/Editor.ts +0 -16
- package/src/lib/editor/managers/TextManager/TextManager.test.ts +5 -1
- package/src/lib/editor/managers/TextManager/TextManager.ts +86 -116
- package/src/lib/editor/types/external-content.ts +1 -1
- package/src/lib/hooks/useCanvasEvents.ts +1 -0
- package/src/version.ts +3 -3
|
@@ -20,27 +20,6 @@ const textAlignmentsForLtr = {
|
|
|
20
20
|
'end-legacy': 'right',
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
/** @public */
|
|
24
|
-
export interface TLMeasureTextOpts {
|
|
25
|
-
fontStyle: string
|
|
26
|
-
fontWeight: string
|
|
27
|
-
fontFamily: string
|
|
28
|
-
fontSize: number
|
|
29
|
-
lineHeight: number
|
|
30
|
-
/**
|
|
31
|
-
* When maxWidth is a number, the text will be wrapped to that maxWidth. When maxWidth
|
|
32
|
-
* is null, the text will be measured without wrapping, but explicit line breaks and
|
|
33
|
-
* space are preserved.
|
|
34
|
-
*/
|
|
35
|
-
maxWidth: null | number
|
|
36
|
-
minWidth?: null | number
|
|
37
|
-
// todo: make this a number so that it is consistent with other TLMeasureTextSpanOpts
|
|
38
|
-
padding: string
|
|
39
|
-
otherStyles?: Record<string, string>
|
|
40
|
-
disableOverflowWrapBreaking?: boolean
|
|
41
|
-
measureScrollWidth?: boolean
|
|
42
|
-
}
|
|
43
|
-
|
|
44
23
|
/** @public */
|
|
45
24
|
export interface TLMeasureTextSpanOpts {
|
|
46
25
|
overflow: 'wrap' | 'truncate-ellipsis' | 'truncate-clip'
|
|
@@ -54,98 +33,96 @@ export interface TLMeasureTextSpanOpts {
|
|
|
54
33
|
lineHeight: number
|
|
55
34
|
textAlign: TLDefaultHorizontalAlignStyle
|
|
56
35
|
otherStyles?: Record<string, string>
|
|
57
|
-
measureScrollWidth?: boolean
|
|
58
36
|
}
|
|
59
37
|
|
|
60
38
|
const spaceCharacterRegex = /\s/
|
|
61
39
|
|
|
62
40
|
/** @public */
|
|
63
41
|
export class TextManager {
|
|
64
|
-
private
|
|
65
|
-
private defaultStyles: Record<string, string | null>
|
|
42
|
+
private baseElem: HTMLDivElement
|
|
66
43
|
|
|
67
44
|
constructor(public editor: Editor) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
elm.tabIndex = -1
|
|
73
|
-
this.editor.getContainer().appendChild(elm)
|
|
74
|
-
|
|
75
|
-
// we need to save the default styles so that we can restore them when we're done
|
|
76
|
-
// these must be the css names, not the js names for the styles
|
|
77
|
-
this.defaultStyles = {
|
|
78
|
-
'word-break': 'auto',
|
|
79
|
-
width: null,
|
|
80
|
-
height: null,
|
|
81
|
-
'max-width': null,
|
|
82
|
-
'min-width': null,
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
this.elm = elm
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
dispose() {
|
|
89
|
-
return this.elm.remove()
|
|
45
|
+
this.baseElem = document.createElement('div')
|
|
46
|
+
this.baseElem.classList.add('tl-text')
|
|
47
|
+
this.baseElem.classList.add('tl-text-measure')
|
|
48
|
+
this.baseElem.tabIndex = -1
|
|
90
49
|
}
|
|
91
50
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
51
|
+
measureText(
|
|
52
|
+
textToMeasure: string,
|
|
53
|
+
opts: {
|
|
54
|
+
fontStyle: string
|
|
55
|
+
fontWeight: string
|
|
56
|
+
fontFamily: string
|
|
57
|
+
fontSize: number
|
|
58
|
+
lineHeight: number
|
|
59
|
+
/**
|
|
60
|
+
* When maxWidth is a number, the text will be wrapped to that maxWidth. When maxWidth
|
|
61
|
+
* is null, the text will be measured without wrapping, but explicit line breaks and
|
|
62
|
+
* space are preserved.
|
|
63
|
+
*/
|
|
64
|
+
maxWidth: null | number
|
|
65
|
+
minWidth?: null | number
|
|
66
|
+
padding: string
|
|
67
|
+
disableOverflowWrapBreaking?: boolean
|
|
96
68
|
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
measureText(textToMeasure: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {
|
|
69
|
+
): BoxModel & { scrollWidth: number } {
|
|
100
70
|
const div = document.createElement('div')
|
|
101
71
|
div.textContent = normalizeTextForDom(textToMeasure)
|
|
102
72
|
return this.measureHtml(div.innerHTML, opts)
|
|
103
73
|
}
|
|
104
74
|
|
|
105
|
-
measureHtml(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
75
|
+
measureHtml(
|
|
76
|
+
html: string,
|
|
77
|
+
opts: {
|
|
78
|
+
fontStyle: string
|
|
79
|
+
fontWeight: string
|
|
80
|
+
fontFamily: string
|
|
81
|
+
fontSize: number
|
|
82
|
+
lineHeight: number
|
|
83
|
+
/**
|
|
84
|
+
* When maxWidth is a number, the text will be wrapped to that maxWidth. When maxWidth
|
|
85
|
+
* is null, the text will be measured without wrapping, but explicit line breaks and
|
|
86
|
+
* space are preserved.
|
|
87
|
+
*/
|
|
88
|
+
maxWidth: null | number
|
|
89
|
+
minWidth?: null | number
|
|
90
|
+
otherStyles?: Record<string, string>
|
|
91
|
+
padding: string
|
|
92
|
+
disableOverflowWrapBreaking?: boolean
|
|
115
93
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
94
|
+
): BoxModel & { scrollWidth: number } {
|
|
95
|
+
// Duplicate our base element; we don't need to clone deep
|
|
96
|
+
const wrapperElm = this.baseElem.cloneNode() as HTMLDivElement
|
|
97
|
+
this.editor.getContainer().appendChild(wrapperElm)
|
|
98
|
+
wrapperElm.innerHTML = html
|
|
99
|
+
this.baseElem.insertAdjacentElement('afterend', wrapperElm)
|
|
100
|
+
|
|
101
|
+
wrapperElm.setAttribute('dir', 'auto')
|
|
102
|
+
// N.B. This property, while discouraged ("intended for Document Type Definition (DTD) designers")
|
|
103
|
+
// is necessary for ensuring correct mixed RTL/LTR behavior when exporting SVGs.
|
|
104
|
+
wrapperElm.style.setProperty('unicode-bidi', 'plaintext')
|
|
105
|
+
wrapperElm.style.setProperty('font-family', opts.fontFamily)
|
|
106
|
+
wrapperElm.style.setProperty('font-style', opts.fontStyle)
|
|
107
|
+
wrapperElm.style.setProperty('font-weight', opts.fontWeight)
|
|
108
|
+
wrapperElm.style.setProperty('font-size', opts.fontSize + 'px')
|
|
109
|
+
wrapperElm.style.setProperty('line-height', opts.lineHeight * opts.fontSize + 'px')
|
|
110
|
+
wrapperElm.style.setProperty('max-width', opts.maxWidth === null ? null : opts.maxWidth + 'px')
|
|
111
|
+
wrapperElm.style.setProperty('min-width', opts.minWidth === null ? null : opts.minWidth + 'px')
|
|
112
|
+
wrapperElm.style.setProperty('padding', opts.padding)
|
|
113
|
+
wrapperElm.style.setProperty(
|
|
114
|
+
'overflow-wrap',
|
|
115
|
+
opts.disableOverflowWrapBreaking ? 'normal' : 'break-word'
|
|
116
|
+
)
|
|
141
117
|
if (opts.otherStyles) {
|
|
142
118
|
for (const [key, value] of Object.entries(opts.otherStyles)) {
|
|
143
|
-
|
|
119
|
+
wrapperElm.style.setProperty(key, value)
|
|
144
120
|
}
|
|
145
121
|
}
|
|
146
122
|
|
|
147
|
-
const scrollWidth =
|
|
148
|
-
const rect =
|
|
123
|
+
const scrollWidth = wrapperElm.scrollWidth
|
|
124
|
+
const rect = wrapperElm.getBoundingClientRect()
|
|
125
|
+
wrapperElm.remove()
|
|
149
126
|
|
|
150
127
|
return {
|
|
151
128
|
x: 0,
|
|
@@ -270,29 +247,27 @@ export class TextManager {
|
|
|
270
247
|
): { text: string; box: BoxModel }[] {
|
|
271
248
|
if (textToMeasure === '') return []
|
|
272
249
|
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
if (opts.otherStyles) {
|
|
276
|
-
for (const key in opts.otherStyles) {
|
|
277
|
-
if (!this.defaultStyles[key]) {
|
|
278
|
-
// we need to save the original style so that we can restore it when we're done
|
|
279
|
-
this.defaultStyles[key] = elm.style.getPropertyValue(key)
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
this.resetElmStyles()
|
|
285
|
-
|
|
286
|
-
elm.style.setProperty('font-family', opts.fontFamily)
|
|
287
|
-
elm.style.setProperty('font-style', opts.fontStyle)
|
|
288
|
-
elm.style.setProperty('font-weight', opts.fontWeight)
|
|
289
|
-
elm.style.setProperty('font-size', opts.fontSize + 'px')
|
|
290
|
-
elm.style.setProperty('line-height', opts.lineHeight * opts.fontSize + 'px')
|
|
250
|
+
const elm = this.baseElem.cloneNode() as HTMLDivElement
|
|
251
|
+
this.editor.getContainer().appendChild(elm)
|
|
291
252
|
|
|
292
253
|
const elementWidth = Math.ceil(opts.width - opts.padding * 2)
|
|
254
|
+
elm.setAttribute('dir', 'auto')
|
|
255
|
+
// N.B. This property, while discouraged ("intended for Document Type Definition (DTD) designers")
|
|
256
|
+
// is necessary for ensuring correct mixed RTL/LTR behavior when exporting SVGs.
|
|
257
|
+
elm.style.setProperty('unicode-bidi', 'plaintext')
|
|
293
258
|
elm.style.setProperty('width', `${elementWidth}px`)
|
|
294
259
|
elm.style.setProperty('height', 'min-content')
|
|
260
|
+
elm.style.setProperty('font-size', `${opts.fontSize}px`)
|
|
261
|
+
elm.style.setProperty('font-family', opts.fontFamily)
|
|
262
|
+
elm.style.setProperty('font-weight', opts.fontWeight)
|
|
263
|
+
elm.style.setProperty('line-height', `${opts.lineHeight * opts.fontSize}px`)
|
|
295
264
|
elm.style.setProperty('text-align', textAlignmentsForLtr[opts.textAlign])
|
|
265
|
+
elm.style.setProperty('font-style', opts.fontStyle)
|
|
266
|
+
if (opts.otherStyles) {
|
|
267
|
+
for (const [key, value] of Object.entries(opts.otherStyles)) {
|
|
268
|
+
elm.style.setProperty(key, value)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
296
271
|
|
|
297
272
|
const shouldTruncateToFirstLine =
|
|
298
273
|
opts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'
|
|
@@ -302,12 +277,6 @@ export class TextManager {
|
|
|
302
277
|
elm.style.setProperty('word-break', 'break-all')
|
|
303
278
|
}
|
|
304
279
|
|
|
305
|
-
if (opts.otherStyles) {
|
|
306
|
-
for (const [key, value] of Object.entries(opts.otherStyles)) {
|
|
307
|
-
elm.style.setProperty(key, value)
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
280
|
const normalizedText = normalizeTextForDom(textToMeasure)
|
|
312
281
|
|
|
313
282
|
// Render the text into the measurement element:
|
|
@@ -344,10 +313,11 @@ export class TextManager {
|
|
|
344
313
|
h: lastSpan.box.h,
|
|
345
314
|
},
|
|
346
315
|
})
|
|
347
|
-
|
|
348
316
|
return truncatedSpans
|
|
349
317
|
}
|
|
350
318
|
|
|
319
|
+
elm.remove()
|
|
320
|
+
|
|
351
321
|
return spans
|
|
352
322
|
}
|
|
353
323
|
}
|
package/src/version.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// This file is automatically generated by internal/scripts/refresh-assets.ts.
|
|
2
2
|
// Do not edit manually. Or do, I'm a comment, not a cop.
|
|
3
3
|
|
|
4
|
-
export const version = '3.14.0-canary.
|
|
4
|
+
export const version = '3.14.0-canary.4f9d90070add'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2024-09-13T14:36:29.063Z',
|
|
7
|
-
minor: '2025-06-
|
|
8
|
-
patch: '2025-06-
|
|
7
|
+
minor: '2025-06-15T10:30:26.984Z',
|
|
8
|
+
patch: '2025-06-15T10:30:26.984Z',
|
|
9
9
|
}
|