@tldraw/editor 3.15.0-canary.14c6b9d1aa1e → 3.15.0-canary.1fc6c3fa6d1c
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 +3 -4
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/editor/Editor.js +2 -20
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +101 -96
- 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 +3 -4
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/editor/Editor.mjs +2 -20
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +101 -96
- 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/package.json +7 -7
- package/src/lib/editor/Editor.test.ts +0 -407
- package/src/lib/editor/Editor.ts +4 -29
- package/src/lib/editor/managers/TextManager/TextManager.ts +128 -108
- package/src/version.ts +3 -3
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { BoxModel, TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema'
|
|
2
|
-
import { objectMapKeys } from '@tldraw/utils'
|
|
3
2
|
import { Editor } from '../../Editor'
|
|
4
3
|
|
|
5
4
|
const fixNewLines = /\r?\n|\r/g
|
|
@@ -61,18 +60,10 @@ export interface TLMeasureTextSpanOpts {
|
|
|
61
60
|
|
|
62
61
|
const spaceCharacterRegex = /\s/
|
|
63
62
|
|
|
64
|
-
const initialDefaultStyles = Object.freeze({
|
|
65
|
-
'overflow-wrap': 'break-word',
|
|
66
|
-
'word-break': 'auto',
|
|
67
|
-
width: null,
|
|
68
|
-
height: null,
|
|
69
|
-
'max-width': null,
|
|
70
|
-
'min-width': null,
|
|
71
|
-
})
|
|
72
|
-
|
|
73
63
|
/** @public */
|
|
74
64
|
export class TextManager {
|
|
75
65
|
private elm: HTMLDivElement
|
|
66
|
+
private defaultStyles: Record<string, string | null>
|
|
76
67
|
|
|
77
68
|
constructor(public editor: Editor) {
|
|
78
69
|
const elm = document.createElement('div')
|
|
@@ -82,34 +73,31 @@ export class TextManager {
|
|
|
82
73
|
elm.tabIndex = -1
|
|
83
74
|
this.editor.getContainer().appendChild(elm)
|
|
84
75
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
76
|
+
// we need to save the default styles so that we can restore them when we're done
|
|
77
|
+
// these must be the css names, not the js names for the styles
|
|
78
|
+
this.defaultStyles = {
|
|
79
|
+
'overflow-wrap': 'break-word',
|
|
80
|
+
'word-break': 'auto',
|
|
81
|
+
width: null,
|
|
82
|
+
height: null,
|
|
83
|
+
'max-width': null,
|
|
84
|
+
'min-width': null,
|
|
89
85
|
}
|
|
90
|
-
}
|
|
91
86
|
|
|
92
|
-
|
|
93
|
-
const stylesToReinstate = {} as any
|
|
94
|
-
for (const key of objectMapKeys(styles)) {
|
|
95
|
-
if (typeof styles[key] === 'string') {
|
|
96
|
-
const oldValue = this.elm.style.getPropertyValue(key)
|
|
97
|
-
if (oldValue === styles[key]) continue
|
|
98
|
-
stylesToReinstate[key] = oldValue
|
|
99
|
-
this.elm.style.setProperty(key, styles[key])
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return () => {
|
|
103
|
-
for (const key of objectMapKeys(stylesToReinstate)) {
|
|
104
|
-
this.elm.style.setProperty(key, stylesToReinstate[key])
|
|
105
|
-
}
|
|
106
|
-
}
|
|
87
|
+
this.elm = elm
|
|
107
88
|
}
|
|
108
89
|
|
|
109
90
|
dispose() {
|
|
110
91
|
return this.elm.remove()
|
|
111
92
|
}
|
|
112
93
|
|
|
94
|
+
private resetElmStyles() {
|
|
95
|
+
const { elm, defaultStyles } = this
|
|
96
|
+
for (const key in defaultStyles) {
|
|
97
|
+
elm.style.setProperty(key, defaultStyles[key])
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
113
101
|
measureText(textToMeasure: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {
|
|
114
102
|
const div = document.createElement('div')
|
|
115
103
|
div.textContent = normalizeTextForDom(textToMeasure)
|
|
@@ -119,36 +107,54 @@ export class TextManager {
|
|
|
119
107
|
measureHtml(html: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {
|
|
120
108
|
const { elm } = this
|
|
121
109
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
'max-width': opts.maxWidth ? opts.maxWidth + 'px' : undefined,
|
|
130
|
-
'min-width': opts.minWidth ? opts.minWidth + 'px' : undefined,
|
|
131
|
-
'overflow-wrap': opts.disableOverflowWrapBreaking ? 'normal' : undefined,
|
|
132
|
-
...opts.otherStyles,
|
|
110
|
+
if (opts.otherStyles) {
|
|
111
|
+
for (const key in opts.otherStyles) {
|
|
112
|
+
if (!this.defaultStyles[key]) {
|
|
113
|
+
// we need to save the original style so that we can restore it when we're done
|
|
114
|
+
this.defaultStyles[key] = elm.style.getPropertyValue(key)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
133
117
|
}
|
|
134
118
|
|
|
135
|
-
|
|
119
|
+
elm.innerHTML = html
|
|
136
120
|
|
|
137
|
-
|
|
138
|
-
|
|
121
|
+
// Apply the default styles to the element (for all styles here or that were ever seen in opts.otherStyles)
|
|
122
|
+
this.resetElmStyles()
|
|
139
123
|
|
|
140
|
-
|
|
141
|
-
|
|
124
|
+
elm.style.setProperty('font-family', opts.fontFamily)
|
|
125
|
+
elm.style.setProperty('font-style', opts.fontStyle)
|
|
126
|
+
elm.style.setProperty('font-weight', opts.fontWeight)
|
|
127
|
+
elm.style.setProperty('font-size', opts.fontSize + 'px')
|
|
128
|
+
elm.style.setProperty('line-height', opts.lineHeight.toString())
|
|
129
|
+
elm.style.setProperty('padding', opts.padding)
|
|
142
130
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
131
|
+
if (opts.maxWidth) {
|
|
132
|
+
elm.style.setProperty('max-width', opts.maxWidth + 'px')
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (opts.minWidth) {
|
|
136
|
+
elm.style.setProperty('min-width', opts.minWidth + 'px')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (opts.disableOverflowWrapBreaking) {
|
|
140
|
+
elm.style.setProperty('overflow-wrap', 'normal')
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (opts.otherStyles) {
|
|
144
|
+
for (const [key, value] of Object.entries(opts.otherStyles)) {
|
|
145
|
+
elm.style.setProperty(key, value)
|
|
149
146
|
}
|
|
150
|
-
}
|
|
151
|
-
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const scrollWidth = opts.measureScrollWidth ? elm.scrollWidth : 0
|
|
150
|
+
const rect = elm.getBoundingClientRect()
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
x: 0,
|
|
154
|
+
y: 0,
|
|
155
|
+
w: rect.width,
|
|
156
|
+
h: rect.height,
|
|
157
|
+
scrollWidth,
|
|
152
158
|
}
|
|
153
159
|
}
|
|
154
160
|
|
|
@@ -268,68 +274,82 @@ export class TextManager {
|
|
|
268
274
|
|
|
269
275
|
const { elm } = this
|
|
270
276
|
|
|
277
|
+
if (opts.otherStyles) {
|
|
278
|
+
for (const key in opts.otherStyles) {
|
|
279
|
+
if (!this.defaultStyles[key]) {
|
|
280
|
+
// we need to save the original style so that we can restore it when we're done
|
|
281
|
+
this.defaultStyles[key] = elm.style.getPropertyValue(key)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
this.resetElmStyles()
|
|
287
|
+
|
|
288
|
+
elm.style.setProperty('font-family', opts.fontFamily)
|
|
289
|
+
elm.style.setProperty('font-style', opts.fontStyle)
|
|
290
|
+
elm.style.setProperty('font-weight', opts.fontWeight)
|
|
291
|
+
elm.style.setProperty('font-size', opts.fontSize + 'px')
|
|
292
|
+
elm.style.setProperty('line-height', opts.lineHeight.toString())
|
|
293
|
+
|
|
294
|
+
const elementWidth = Math.ceil(opts.width - opts.padding * 2)
|
|
295
|
+
elm.style.setProperty('width', `${elementWidth}px`)
|
|
296
|
+
elm.style.setProperty('height', 'min-content')
|
|
297
|
+
elm.style.setProperty('text-align', textAlignmentsForLtr[opts.textAlign])
|
|
298
|
+
|
|
271
299
|
const shouldTruncateToFirstLine =
|
|
272
300
|
opts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
'
|
|
276
|
-
'
|
|
277
|
-
'font-weight': opts.fontWeight,
|
|
278
|
-
'font-size': opts.fontSize + 'px',
|
|
279
|
-
'line-height': opts.lineHeight.toString(),
|
|
280
|
-
width: `${elementWidth}px`,
|
|
281
|
-
height: 'min-content',
|
|
282
|
-
'text-align': textAlignmentsForLtr[opts.textAlign],
|
|
283
|
-
'overflow-wrap': shouldTruncateToFirstLine ? 'anywhere' : undefined,
|
|
284
|
-
'word-break': shouldTruncateToFirstLine ? 'break-all' : undefined,
|
|
285
|
-
...opts.otherStyles,
|
|
301
|
+
|
|
302
|
+
if (shouldTruncateToFirstLine) {
|
|
303
|
+
elm.style.setProperty('overflow-wrap', 'anywhere')
|
|
304
|
+
elm.style.setProperty('word-break', 'break-all')
|
|
286
305
|
}
|
|
287
|
-
const restoreStyles = this.setElementStyles(newStyles)
|
|
288
306
|
|
|
289
|
-
|
|
290
|
-
const
|
|
307
|
+
if (opts.otherStyles) {
|
|
308
|
+
for (const [key, value] of Object.entries(opts.otherStyles)) {
|
|
309
|
+
elm.style.setProperty(key, value)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
291
312
|
|
|
292
|
-
|
|
293
|
-
elm.textContent = normalizedText
|
|
313
|
+
const normalizedText = normalizeTextForDom(textToMeasure)
|
|
294
314
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
shouldTruncateToFirstLine,
|
|
298
|
-
})
|
|
315
|
+
// Render the text into the measurement element:
|
|
316
|
+
elm.textContent = normalizedText
|
|
299
317
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
// then, we need to subtract that space from the width we have and measure again:
|
|
306
|
-
elm.style.setProperty('width', `${elementWidth - ellipsisWidth}px`)
|
|
307
|
-
elm.textContent = normalizedText
|
|
308
|
-
const truncatedSpans = this.measureElementTextNodeSpans(elm, {
|
|
309
|
-
shouldTruncateToFirstLine: true,
|
|
310
|
-
}).spans
|
|
311
|
-
|
|
312
|
-
// Finally, we add in our ellipsis at the end of the last span. We
|
|
313
|
-
// have to do this after measuring, not before, because adding the
|
|
314
|
-
// ellipsis changes how whitespace might be getting collapsed by the
|
|
315
|
-
// browser.
|
|
316
|
-
const lastSpan = truncatedSpans[truncatedSpans.length - 1]!
|
|
317
|
-
truncatedSpans.push({
|
|
318
|
-
text: '…',
|
|
319
|
-
box: {
|
|
320
|
-
x: Math.min(lastSpan.box.x + lastSpan.box.w, opts.width - opts.padding - ellipsisWidth),
|
|
321
|
-
y: lastSpan.box.y,
|
|
322
|
-
w: ellipsisWidth,
|
|
323
|
-
h: lastSpan.box.h,
|
|
324
|
-
},
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
return truncatedSpans
|
|
328
|
-
}
|
|
318
|
+
// actually measure the text:
|
|
319
|
+
const { spans, didTruncate } = this.measureElementTextNodeSpans(elm, {
|
|
320
|
+
shouldTruncateToFirstLine,
|
|
321
|
+
})
|
|
329
322
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
323
|
+
if (opts.overflow === 'truncate-ellipsis' && didTruncate) {
|
|
324
|
+
// we need to measure the ellipsis to know how much space it takes up
|
|
325
|
+
elm.textContent = '…'
|
|
326
|
+
const ellipsisWidth = Math.ceil(this.measureElementTextNodeSpans(elm).spans[0].box.w)
|
|
327
|
+
|
|
328
|
+
// then, we need to subtract that space from the width we have and measure again:
|
|
329
|
+
elm.style.setProperty('width', `${elementWidth - ellipsisWidth}px`)
|
|
330
|
+
elm.textContent = normalizedText
|
|
331
|
+
const truncatedSpans = this.measureElementTextNodeSpans(elm, {
|
|
332
|
+
shouldTruncateToFirstLine: true,
|
|
333
|
+
}).spans
|
|
334
|
+
|
|
335
|
+
// Finally, we add in our ellipsis at the end of the last span. We
|
|
336
|
+
// have to do this after measuring, not before, because adding the
|
|
337
|
+
// ellipsis changes how whitespace might be getting collapsed by the
|
|
338
|
+
// browser.
|
|
339
|
+
const lastSpan = truncatedSpans[truncatedSpans.length - 1]!
|
|
340
|
+
truncatedSpans.push({
|
|
341
|
+
text: '…',
|
|
342
|
+
box: {
|
|
343
|
+
x: Math.min(lastSpan.box.x + lastSpan.box.w, opts.width - opts.padding - ellipsisWidth),
|
|
344
|
+
y: lastSpan.box.y,
|
|
345
|
+
w: ellipsisWidth,
|
|
346
|
+
h: lastSpan.box.h,
|
|
347
|
+
},
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
return truncatedSpans
|
|
333
351
|
}
|
|
352
|
+
|
|
353
|
+
return spans
|
|
334
354
|
}
|
|
335
355
|
}
|
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.15.0-canary.
|
|
4
|
+
export const version = '3.15.0-canary.1fc6c3fa6d1c'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2024-09-13T14:36:29.063Z',
|
|
7
|
-
minor: '2025-07-
|
|
8
|
-
patch: '2025-07-
|
|
7
|
+
minor: '2025-07-09T11:51:13.402Z',
|
|
8
|
+
patch: '2025-07-09T11:51:13.402Z',
|
|
9
9
|
}
|