@tldraw/editor 3.14.0-canary.7524fb3ab164 → 3.14.0-canary.766a02a82239
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 +482 -433
- 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
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.766a02a82239",
|
|
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.766a02a82239",
|
|
52
|
+
"@tldraw/state-react": "3.14.0-canary.766a02a82239",
|
|
53
|
+
"@tldraw/store": "3.14.0-canary.766a02a82239",
|
|
54
|
+
"@tldraw/tlschema": "3.14.0-canary.766a02a82239",
|
|
55
|
+
"@tldraw/utils": "3.14.0-canary.766a02a82239",
|
|
56
|
+
"@tldraw/validate": "3.14.0-canary.766a02a82239",
|
|
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,7 +174,6 @@ export {
|
|
|
174
174
|
} from './lib/editor/managers/SnapManager/SnapManager'
|
|
175
175
|
export {
|
|
176
176
|
TextManager,
|
|
177
|
-
type TLMeasureTextOpts,
|
|
178
177
|
type TLMeasureTextSpanOpts,
|
|
179
178
|
} from './lib/editor/managers/TextManager/TextManager'
|
|
180
179
|
export { UserPreferencesManager } from './lib/editor/managers/UserPreferencesManager/UserPreferencesManager'
|
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -348,8 +348,6 @@ 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
|
-
|
|
353
351
|
this.fonts = new FontManager(this, fontAssetUrls)
|
|
354
352
|
|
|
355
353
|
this._tickManager = new TickManager(this)
|
|
@@ -2122,20 +2120,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2122
2120
|
return this.getShapesPageBounds(this.getSelectedShapeIds())
|
|
2123
2121
|
}
|
|
2124
2122
|
|
|
2125
|
-
/**
|
|
2126
|
-
* The bounds of the selection bounding box in the current page space.
|
|
2127
|
-
*
|
|
2128
|
-
* @readonly
|
|
2129
|
-
* @public
|
|
2130
|
-
*/
|
|
2131
|
-
@computed getSelectionScreenBounds(): Box | undefined {
|
|
2132
|
-
const bounds = this.getSelectionPageBounds()
|
|
2133
|
-
if (!bounds) return undefined
|
|
2134
|
-
const { x, y } = this.pageToScreen(bounds.point)
|
|
2135
|
-
const zoom = this.getZoomLevel()
|
|
2136
|
-
return new Box(x, y, bounds.width * zoom, bounds.height * zoom)
|
|
2137
|
-
}
|
|
2138
|
-
|
|
2139
2123
|
/**
|
|
2140
2124
|
* @internal
|
|
2141
2125
|
*/
|
|
@@ -99,7 +99,7 @@ describe('TextManager', () => {
|
|
|
99
99
|
})
|
|
100
100
|
|
|
101
101
|
it('should handle empty text', () => {
|
|
102
|
-
const result = textManager.measureText('',
|
|
102
|
+
const result = textManager.measureText('', defaultOpts)
|
|
103
103
|
expect(result).toHaveProperty('x', 0)
|
|
104
104
|
expect(result).toHaveProperty('y', 0)
|
|
105
105
|
expect(result).toHaveProperty('w')
|
|
@@ -128,6 +128,7 @@ describe('TextManager', () => {
|
|
|
128
128
|
y: 0,
|
|
129
129
|
w: expect.any(Number),
|
|
130
130
|
h: expect.any(Number),
|
|
131
|
+
scrollWidth: expect.any(Number),
|
|
131
132
|
})
|
|
132
133
|
})
|
|
133
134
|
|
|
@@ -140,6 +141,7 @@ describe('TextManager', () => {
|
|
|
140
141
|
y: 0,
|
|
141
142
|
w: expect.any(Number),
|
|
142
143
|
h: expect.any(Number),
|
|
144
|
+
scrollWidth: expect.any(Number),
|
|
143
145
|
})
|
|
144
146
|
})
|
|
145
147
|
|
|
@@ -152,6 +154,7 @@ describe('TextManager', () => {
|
|
|
152
154
|
y: 0,
|
|
153
155
|
w: expect.any(Number),
|
|
154
156
|
h: expect.any(Number),
|
|
157
|
+
scrollWidth: expect.any(Number),
|
|
155
158
|
})
|
|
156
159
|
})
|
|
157
160
|
|
|
@@ -170,6 +173,7 @@ describe('TextManager', () => {
|
|
|
170
173
|
y: 0,
|
|
171
174
|
w: expect.any(Number),
|
|
172
175
|
h: expect.any(Number),
|
|
176
|
+
scrollWidth: expect.any(Number),
|
|
173
177
|
})
|
|
174
178
|
})
|
|
175
179
|
})
|
|
@@ -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.766a02a82239'
|
|
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-12T10:52:35.222Z',
|
|
8
|
+
patch: '2025-06-12T10:52:35.222Z',
|
|
9
9
|
}
|