@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.
Files changed (32) hide show
  1. package/dist-cjs/index.d.ts +35 -35
  2. package/dist-cjs/index.js +1 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/editor/Editor.js +2 -11
  5. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  6. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +42 -72
  7. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
  8. package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
  9. package/dist-cjs/lib/hooks/useCanvasEvents.js +2 -1
  10. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  11. package/dist-cjs/version.js +3 -3
  12. package/dist-cjs/version.js.map +1 -1
  13. package/dist-esm/index.d.mts +35 -35
  14. package/dist-esm/index.mjs +1 -1
  15. package/dist-esm/index.mjs.map +2 -2
  16. package/dist-esm/lib/editor/Editor.mjs +2 -11
  17. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  18. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +42 -72
  19. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
  20. package/dist-esm/lib/hooks/useCanvasEvents.mjs +2 -1
  21. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  22. package/dist-esm/version.mjs +3 -3
  23. package/dist-esm/version.mjs.map +1 -1
  24. package/editor.css +482 -433
  25. package/package.json +7 -7
  26. package/src/index.ts +0 -1
  27. package/src/lib/editor/Editor.ts +0 -16
  28. package/src/lib/editor/managers/TextManager/TextManager.test.ts +5 -1
  29. package/src/lib/editor/managers/TextManager/TextManager.ts +86 -116
  30. package/src/lib/editor/types/external-content.ts +1 -1
  31. package/src/lib/hooks/useCanvasEvents.ts +1 -0
  32. 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.7524fb3ab164",
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.7524fb3ab164",
52
- "@tldraw/state-react": "3.14.0-canary.7524fb3ab164",
53
- "@tldraw/store": "3.14.0-canary.7524fb3ab164",
54
- "@tldraw/tlschema": "3.14.0-canary.7524fb3ab164",
55
- "@tldraw/utils": "3.14.0-canary.7524fb3ab164",
56
- "@tldraw/validate": "3.14.0-canary.7524fb3ab164",
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'
@@ -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('', { ...defaultOpts, measureScrollWidth: true })
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 elm: HTMLDivElement
65
- private defaultStyles: Record<string, string | null>
42
+ private baseElem: HTMLDivElement
66
43
 
67
44
  constructor(public editor: Editor) {
68
- const elm = document.createElement('div')
69
- elm.classList.add('tl-text')
70
- elm.classList.add('tl-text-measure')
71
- elm.setAttribute('dir', 'auto')
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
- private resetElmStyles() {
93
- const { elm, defaultStyles } = this
94
- for (const key in defaultStyles) {
95
- elm.style.setProperty(key, defaultStyles[key])
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(html: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {
106
- const { elm } = this
107
-
108
- if (opts.otherStyles) {
109
- for (const key in opts.otherStyles) {
110
- if (!this.defaultStyles[key]) {
111
- // we need to save the original style so that we can restore it when we're done
112
- this.defaultStyles[key] = elm.style.getPropertyValue(key)
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
- elm.innerHTML = html
118
-
119
- // Apply the default styles to the element (for all styles here or that were ever seen in opts.otherStyles)
120
- this.resetElmStyles()
121
-
122
- elm.style.setProperty('font-family', opts.fontFamily)
123
- elm.style.setProperty('font-style', opts.fontStyle)
124
- elm.style.setProperty('font-weight', opts.fontWeight)
125
- elm.style.setProperty('font-size', opts.fontSize + 'px')
126
- elm.style.setProperty('line-height', opts.lineHeight * opts.fontSize + 'px')
127
- elm.style.setProperty('padding', opts.padding)
128
-
129
- if (opts.maxWidth) {
130
- elm.style.setProperty('max-width', opts.maxWidth + 'px')
131
- }
132
-
133
- if (opts.minWidth) {
134
- elm.style.setProperty('min-width', opts.minWidth + 'px')
135
- }
136
-
137
- if (opts.disableOverflowWrapBreaking) {
138
- elm.style.setProperty('overflow-wrap', 'normal')
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
- elm.style.setProperty(key, value)
119
+ wrapperElm.style.setProperty(key, value)
144
120
  }
145
121
  }
146
122
 
147
- const scrollWidth = opts.measureScrollWidth ? elm.scrollWidth : 0
148
- const rect = elm.getBoundingClientRect()
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 { elm } = this
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
  }
@@ -52,7 +52,7 @@ export interface TLTextExternalContent extends TLBaseExternalContent {
52
52
  export interface TLFilesExternalContent extends TLBaseExternalContent {
53
53
  type: 'files'
54
54
  files: File[]
55
- ignoreParent?: boolean
55
+ ignoreParent: boolean
56
56
  }
57
57
 
58
58
  /** @public */
@@ -137,6 +137,7 @@ export function useCanvasEvents() {
137
137
  type: 'files',
138
138
  files,
139
139
  point: editor.screenToPage({ x: e.clientX, y: e.clientY }),
140
+ ignoreParent: false,
140
141
  })
141
142
  return
142
143
  }
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.7524fb3ab164'
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-18T09:46:13.586Z',
8
- patch: '2025-06-18T09:46:13.586Z',
7
+ minor: '2025-06-12T10:52:35.222Z',
8
+ patch: '2025-06-12T10:52:35.222Z',
9
9
  }