@tldraw/editor 3.14.0-canary.7be18509d9b9 → 3.14.0-canary.7c3d5520bd87
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 +25 -34
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +1 -0
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +72 -42
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.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 +25 -34
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +1 -0
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +72 -42
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +458 -523
- package/package.json +7 -7
- package/src/index.ts +1 -0
- package/src/lib/editor/Editor.ts +2 -0
- package/src/lib/editor/managers/TextManager/TextManager.ts +114 -86
- package/src/version.ts +3 -3
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldraw/editor",
|
|
3
3
|
"description": "A tiny little drawing app (editor).",
|
|
4
|
-
"version": "3.14.0-canary.
|
|
4
|
+
"version": "3.14.0-canary.7c3d5520bd87",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -48,12 +48,12 @@
|
|
|
48
48
|
"@tiptap/core": "^2.9.1",
|
|
49
49
|
"@tiptap/pm": "^2.9.1",
|
|
50
50
|
"@tiptap/react": "^2.9.1",
|
|
51
|
-
"@tldraw/state": "3.14.0-canary.
|
|
52
|
-
"@tldraw/state-react": "3.14.0-canary.
|
|
53
|
-
"@tldraw/store": "3.14.0-canary.
|
|
54
|
-
"@tldraw/tlschema": "3.14.0-canary.
|
|
55
|
-
"@tldraw/utils": "3.14.0-canary.
|
|
56
|
-
"@tldraw/validate": "3.14.0-canary.
|
|
51
|
+
"@tldraw/state": "3.14.0-canary.7c3d5520bd87",
|
|
52
|
+
"@tldraw/state-react": "3.14.0-canary.7c3d5520bd87",
|
|
53
|
+
"@tldraw/store": "3.14.0-canary.7c3d5520bd87",
|
|
54
|
+
"@tldraw/tlschema": "3.14.0-canary.7c3d5520bd87",
|
|
55
|
+
"@tldraw/utils": "3.14.0-canary.7c3d5520bd87",
|
|
56
|
+
"@tldraw/validate": "3.14.0-canary.7c3d5520bd87",
|
|
57
57
|
"@types/core-js": "^2.5.8",
|
|
58
58
|
"@use-gesture/react": "^10.3.1",
|
|
59
59
|
"classnames": "^2.5.1",
|
package/src/index.ts
CHANGED
|
@@ -174,6 +174,7 @@ export {
|
|
|
174
174
|
} from './lib/editor/managers/SnapManager/SnapManager'
|
|
175
175
|
export {
|
|
176
176
|
TextManager,
|
|
177
|
+
type TLMeasureTextOpts,
|
|
177
178
|
type TLMeasureTextSpanOpts,
|
|
178
179
|
} from './lib/editor/managers/TextManager/TextManager'
|
|
179
180
|
export { UserPreferencesManager } from './lib/editor/managers/UserPreferencesManager/UserPreferencesManager'
|
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -348,6 +348,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
348
348
|
this.getContainer = getContainer
|
|
349
349
|
|
|
350
350
|
this.textMeasure = new TextManager(this)
|
|
351
|
+
this.disposables.add(() => this.textMeasure.dispose())
|
|
352
|
+
|
|
351
353
|
this.fonts = new FontManager(this, fontAssetUrls)
|
|
352
354
|
|
|
353
355
|
this._tickManager = new TickManager(this)
|
|
@@ -20,6 +20,26 @@ 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
|
+
}
|
|
42
|
+
|
|
23
43
|
/** @public */
|
|
24
44
|
export interface TLMeasureTextSpanOpts {
|
|
25
45
|
overflow: 'wrap' | 'truncate-ellipsis' | 'truncate-clip'
|
|
@@ -39,90 +59,91 @@ const spaceCharacterRegex = /\s/
|
|
|
39
59
|
|
|
40
60
|
/** @public */
|
|
41
61
|
export class TextManager {
|
|
42
|
-
private
|
|
62
|
+
private elm: HTMLDivElement
|
|
63
|
+
private defaultStyles: Record<string, string | null>
|
|
43
64
|
|
|
44
65
|
constructor(public editor: Editor) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
66
|
+
const elm = document.createElement('div')
|
|
67
|
+
elm.classList.add('tl-text')
|
|
68
|
+
elm.classList.add('tl-text-measure')
|
|
69
|
+
elm.setAttribute('dir', 'auto')
|
|
70
|
+
elm.tabIndex = -1
|
|
71
|
+
this.editor.getContainer().appendChild(elm)
|
|
72
|
+
|
|
73
|
+
// we need to save the default styles so that we can restore them when we're done
|
|
74
|
+
// these must be the css names, not the js names for the styles
|
|
75
|
+
this.defaultStyles = {
|
|
76
|
+
'word-break': 'auto',
|
|
77
|
+
width: null,
|
|
78
|
+
height: null,
|
|
79
|
+
'max-width': null,
|
|
80
|
+
'min-width': null,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this.elm = elm
|
|
49
84
|
}
|
|
50
85
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
86
|
+
dispose() {
|
|
87
|
+
return this.elm.remove()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private resetElmStyles() {
|
|
91
|
+
const { elm, defaultStyles } = this
|
|
92
|
+
for (const key in defaultStyles) {
|
|
93
|
+
elm.style.setProperty(key, defaultStyles[key])
|
|
68
94
|
}
|
|
69
|
-
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
measureText(textToMeasure: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {
|
|
70
98
|
const div = document.createElement('div')
|
|
71
99
|
div.textContent = normalizeTextForDom(textToMeasure)
|
|
72
100
|
return this.measureHtml(div.innerHTML, opts)
|
|
73
101
|
}
|
|
74
102
|
|
|
75
|
-
measureHtml(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
103
|
+
measureHtml(html: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {
|
|
104
|
+
const { elm } = this
|
|
105
|
+
|
|
106
|
+
if (opts.otherStyles) {
|
|
107
|
+
for (const key in opts.otherStyles) {
|
|
108
|
+
if (!this.defaultStyles[key]) {
|
|
109
|
+
// we need to save the original style so that we can restore it when we're done
|
|
110
|
+
this.defaultStyles[key] = elm.style.getPropertyValue(key)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
93
113
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
|
|
115
|
+
elm.innerHTML = html
|
|
116
|
+
|
|
117
|
+
// Apply the default styles to the element (for all styles here or that were ever seen in opts.otherStyles)
|
|
118
|
+
this.resetElmStyles()
|
|
119
|
+
|
|
120
|
+
elm.style.setProperty('font-family', opts.fontFamily)
|
|
121
|
+
elm.style.setProperty('font-style', opts.fontStyle)
|
|
122
|
+
elm.style.setProperty('font-weight', opts.fontWeight)
|
|
123
|
+
elm.style.setProperty('font-size', opts.fontSize + 'px')
|
|
124
|
+
elm.style.setProperty('line-height', opts.lineHeight * opts.fontSize + 'px')
|
|
125
|
+
elm.style.setProperty('padding', opts.padding)
|
|
126
|
+
|
|
127
|
+
if (opts.maxWidth) {
|
|
128
|
+
elm.style.setProperty('max-width', opts.maxWidth + 'px')
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (opts.minWidth) {
|
|
132
|
+
elm.style.setProperty('min-width', opts.minWidth + 'px')
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (opts.disableOverflowWrapBreaking) {
|
|
136
|
+
elm.style.setProperty('overflow-wrap', 'normal')
|
|
137
|
+
}
|
|
138
|
+
|
|
117
139
|
if (opts.otherStyles) {
|
|
118
140
|
for (const [key, value] of Object.entries(opts.otherStyles)) {
|
|
119
|
-
|
|
141
|
+
elm.style.setProperty(key, value)
|
|
120
142
|
}
|
|
121
143
|
}
|
|
122
144
|
|
|
123
|
-
const scrollWidth =
|
|
124
|
-
const rect =
|
|
125
|
-
wrapperElm.remove()
|
|
145
|
+
const scrollWidth = elm.scrollWidth
|
|
146
|
+
const rect = elm.getBoundingClientRect()
|
|
126
147
|
|
|
127
148
|
return {
|
|
128
149
|
x: 0,
|
|
@@ -247,27 +268,29 @@ export class TextManager {
|
|
|
247
268
|
): { text: string; box: BoxModel }[] {
|
|
248
269
|
if (textToMeasure === '') return []
|
|
249
270
|
|
|
250
|
-
const elm = this
|
|
251
|
-
|
|
271
|
+
const { elm } = this
|
|
272
|
+
|
|
273
|
+
if (opts.otherStyles) {
|
|
274
|
+
for (const key in opts.otherStyles) {
|
|
275
|
+
if (!this.defaultStyles[key]) {
|
|
276
|
+
// we need to save the original style so that we can restore it when we're done
|
|
277
|
+
this.defaultStyles[key] = elm.style.getPropertyValue(key)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
this.resetElmStyles()
|
|
283
|
+
|
|
284
|
+
elm.style.setProperty('font-family', opts.fontFamily)
|
|
285
|
+
elm.style.setProperty('font-style', opts.fontStyle)
|
|
286
|
+
elm.style.setProperty('font-weight', opts.fontWeight)
|
|
287
|
+
elm.style.setProperty('font-size', opts.fontSize + 'px')
|
|
288
|
+
elm.style.setProperty('line-height', opts.lineHeight * opts.fontSize + 'px')
|
|
252
289
|
|
|
253
290
|
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')
|
|
258
291
|
elm.style.setProperty('width', `${elementWidth}px`)
|
|
259
292
|
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`)
|
|
264
293
|
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
|
-
}
|
|
271
294
|
|
|
272
295
|
const shouldTruncateToFirstLine =
|
|
273
296
|
opts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'
|
|
@@ -277,6 +300,12 @@ export class TextManager {
|
|
|
277
300
|
elm.style.setProperty('word-break', 'break-all')
|
|
278
301
|
}
|
|
279
302
|
|
|
303
|
+
if (opts.otherStyles) {
|
|
304
|
+
for (const [key, value] of Object.entries(opts.otherStyles)) {
|
|
305
|
+
elm.style.setProperty(key, value)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
280
309
|
const normalizedText = normalizeTextForDom(textToMeasure)
|
|
281
310
|
|
|
282
311
|
// Render the text into the measurement element:
|
|
@@ -313,11 +342,10 @@ export class TextManager {
|
|
|
313
342
|
h: lastSpan.box.h,
|
|
314
343
|
},
|
|
315
344
|
})
|
|
345
|
+
|
|
316
346
|
return truncatedSpans
|
|
317
347
|
}
|
|
318
348
|
|
|
319
|
-
elm.remove()
|
|
320
|
-
|
|
321
349
|
return spans
|
|
322
350
|
}
|
|
323
351
|
}
|
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.7c3d5520bd87'
|
|
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-15T21:37:09.721Z',
|
|
8
|
+
patch: '2025-06-15T21:37:09.721Z',
|
|
9
9
|
}
|