@tldraw/editor 3.14.0-canary.b1a9df6c4d78 → 3.14.0-canary.b34920584558

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 +36 -50
  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 -20
  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 +36 -50
  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 -20
  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 +1 -39
  25. package/package.json +7 -7
  26. package/src/index.ts +0 -2
  27. package/src/lib/editor/Editor.ts +0 -26
  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 +2 -11
  31. package/src/lib/hooks/useCanvasEvents.ts +1 -0
  32. 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 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
  }
@@ -1,4 +1,4 @@
1
- import { TLAssetId, TLShapeId } from '@tldraw/tlschema'
1
+ import { TLAssetId } from '@tldraw/tlschema'
2
2
  import { VecLike } from '../../primitives/Vec'
3
3
  import { TLContent } from './clipboard-types'
4
4
 
@@ -52,15 +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
56
- }
57
-
58
- /** @public */
59
- export interface TLFileReplaceExternalContent extends TLBaseExternalContent {
60
- type: 'file-replace'
61
- file: File
62
- shapeId: TLShapeId
63
- isImage: boolean
55
+ ignoreParent: boolean
64
56
  }
65
57
 
66
58
  /** @public */
@@ -98,7 +90,6 @@ export interface TLExcalidrawExternalContent extends TLBaseExternalContent {
98
90
  export type TLExternalContent<EmbedDefinition> =
99
91
  | TLTextExternalContent
100
92
  | TLFilesExternalContent
101
- | TLFileReplaceExternalContent
102
93
  | TLUrlExternalContent
103
94
  | TLSvgTextExternalContent
104
95
  | TLEmbedExternalContent<EmbedDefinition>
@@ -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.b1a9df6c4d78'
4
+ export const version = '3.14.0-canary.b34920584558'
5
5
  export const publishDates = {
6
6
  major: '2024-09-13T14:36:29.063Z',
7
- minor: '2025-06-18T12:21:49.668Z',
8
- patch: '2025-06-18T12:21:49.668Z',
7
+ minor: '2025-06-13T12:47:22.619Z',
8
+ patch: '2025-06-13T12:47:22.619Z',
9
9
  }