@tldraw/editor 3.15.0-canary.11f7238a9bfa → 3.15.0-canary.175d131e5195

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 (129) hide show
  1. package/dist-cjs/index.d.ts +69 -7
  2. package/dist-cjs/index.js +3 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/components/SVGContainer.js +1 -1
  5. package/dist-cjs/lib/components/SVGContainer.js.map +2 -2
  6. package/dist-cjs/lib/components/default-components/DefaultBrush.js +1 -1
  7. package/dist-cjs/lib/components/default-components/DefaultBrush.js.map +2 -2
  8. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +1 -1
  9. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  10. package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js +1 -1
  11. package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js.map +2 -2
  12. package/dist-cjs/lib/components/default-components/DefaultCursor.js +1 -1
  13. package/dist-cjs/lib/components/default-components/DefaultCursor.js.map +2 -2
  14. package/dist-cjs/lib/components/default-components/DefaultGrid.js +1 -1
  15. package/dist-cjs/lib/components/default-components/DefaultGrid.js.map +2 -2
  16. package/dist-cjs/lib/components/default-components/DefaultHandles.js +1 -1
  17. package/dist-cjs/lib/components/default-components/DefaultHandles.js.map +2 -2
  18. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +1 -1
  19. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
  20. package/dist-cjs/lib/components/default-components/DefaultSnapIndictor.js +1 -1
  21. package/dist-cjs/lib/components/default-components/DefaultSnapIndictor.js.map +2 -2
  22. package/dist-cjs/lib/components/default-components/DefaultSpinner.js +27 -15
  23. package/dist-cjs/lib/components/default-components/DefaultSpinner.js.map +3 -3
  24. package/dist-cjs/lib/editor/Editor.js +42 -21
  25. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  26. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +96 -101
  27. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
  28. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  29. package/dist-cjs/lib/editor/tools/StateNode.js +20 -1
  30. package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
  31. package/dist-cjs/lib/hooks/useEditorComponents.js.map +1 -1
  32. package/dist-cjs/lib/license/Watermark.js +2 -2
  33. package/dist-cjs/lib/license/Watermark.js.map +2 -2
  34. package/dist-cjs/lib/primitives/geometry/Arc2d.js +1 -1
  35. package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
  36. package/dist-cjs/lib/primitives/geometry/Circle2d.js +1 -1
  37. package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +2 -2
  38. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +3 -1
  39. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
  40. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js +1 -1
  41. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +2 -2
  42. package/dist-cjs/lib/primitives/geometry/geometry-constants.js +2 -2
  43. package/dist-cjs/lib/primitives/geometry/geometry-constants.js.map +2 -2
  44. package/dist-cjs/lib/primitives/intersect.js +4 -4
  45. package/dist-cjs/lib/primitives/intersect.js.map +2 -2
  46. package/dist-cjs/lib/primitives/utils.js +4 -0
  47. package/dist-cjs/lib/primitives/utils.js.map +2 -2
  48. package/dist-cjs/version.js +3 -3
  49. package/dist-cjs/version.js.map +1 -1
  50. package/dist-esm/index.d.mts +69 -7
  51. package/dist-esm/index.mjs +3 -1
  52. package/dist-esm/index.mjs.map +2 -2
  53. package/dist-esm/lib/components/SVGContainer.mjs +1 -1
  54. package/dist-esm/lib/components/SVGContainer.mjs.map +2 -2
  55. package/dist-esm/lib/components/default-components/DefaultBrush.mjs +1 -1
  56. package/dist-esm/lib/components/default-components/DefaultBrush.mjs.map +2 -2
  57. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +1 -1
  58. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  59. package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs +1 -1
  60. package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs.map +2 -2
  61. package/dist-esm/lib/components/default-components/DefaultCursor.mjs +1 -1
  62. package/dist-esm/lib/components/default-components/DefaultCursor.mjs.map +2 -2
  63. package/dist-esm/lib/components/default-components/DefaultGrid.mjs +1 -1
  64. package/dist-esm/lib/components/default-components/DefaultGrid.mjs.map +2 -2
  65. package/dist-esm/lib/components/default-components/DefaultHandles.mjs +1 -1
  66. package/dist-esm/lib/components/default-components/DefaultHandles.mjs.map +2 -2
  67. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +1 -1
  68. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
  69. package/dist-esm/lib/components/default-components/DefaultSnapIndictor.mjs +1 -1
  70. package/dist-esm/lib/components/default-components/DefaultSnapIndictor.mjs.map +2 -2
  71. package/dist-esm/lib/components/default-components/DefaultSpinner.mjs +17 -15
  72. package/dist-esm/lib/components/default-components/DefaultSpinner.mjs.map +2 -2
  73. package/dist-esm/lib/editor/Editor.mjs +42 -21
  74. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  75. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +96 -101
  76. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
  77. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  78. package/dist-esm/lib/editor/tools/StateNode.mjs +20 -1
  79. package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
  80. package/dist-esm/lib/hooks/useEditorComponents.mjs.map +1 -1
  81. package/dist-esm/lib/license/Watermark.mjs +2 -2
  82. package/dist-esm/lib/license/Watermark.mjs.map +2 -2
  83. package/dist-esm/lib/primitives/geometry/Arc2d.mjs +2 -2
  84. package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
  85. package/dist-esm/lib/primitives/geometry/Circle2d.mjs +2 -2
  86. package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +2 -2
  87. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +3 -1
  88. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
  89. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs +2 -2
  90. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +2 -2
  91. package/dist-esm/lib/primitives/geometry/geometry-constants.mjs +2 -2
  92. package/dist-esm/lib/primitives/geometry/geometry-constants.mjs.map +2 -2
  93. package/dist-esm/lib/primitives/intersect.mjs +5 -5
  94. package/dist-esm/lib/primitives/intersect.mjs.map +2 -2
  95. package/dist-esm/lib/primitives/utils.mjs +4 -0
  96. package/dist-esm/lib/primitives/utils.mjs.map +2 -2
  97. package/dist-esm/version.mjs +3 -3
  98. package/dist-esm/version.mjs.map +1 -1
  99. package/editor.css +17 -4
  100. package/package.json +9 -8
  101. package/src/index.ts +1 -0
  102. package/src/lib/components/SVGContainer.tsx +1 -1
  103. package/src/lib/components/default-components/DefaultBrush.tsx +1 -1
  104. package/src/lib/components/default-components/DefaultCanvas.tsx +1 -1
  105. package/src/lib/components/default-components/DefaultCollaboratorHint.tsx +1 -1
  106. package/src/lib/components/default-components/DefaultCursor.tsx +1 -1
  107. package/src/lib/components/default-components/DefaultGrid.tsx +1 -1
  108. package/src/lib/components/default-components/DefaultHandles.tsx +5 -1
  109. package/src/lib/components/default-components/DefaultShapeIndicator.tsx +1 -1
  110. package/src/lib/components/default-components/DefaultSnapIndictor.tsx +1 -1
  111. package/src/lib/components/default-components/DefaultSpinner.tsx +12 -12
  112. package/src/lib/editor/Editor.test.ts +407 -0
  113. package/src/lib/editor/Editor.ts +55 -22
  114. package/src/lib/editor/managers/TextManager/TextManager.ts +108 -128
  115. package/src/lib/editor/shapes/ShapeUtil.ts +57 -0
  116. package/src/lib/editor/tools/StateNode.test.ts +285 -0
  117. package/src/lib/editor/tools/StateNode.ts +27 -1
  118. package/src/lib/hooks/useEditorComponents.tsx +1 -1
  119. package/src/lib/license/Watermark.tsx +2 -2
  120. package/src/lib/primitives/geometry/Arc2d.ts +2 -2
  121. package/src/lib/primitives/geometry/Circle2d.ts +2 -2
  122. package/src/lib/primitives/geometry/CubicBezier2d.ts +4 -1
  123. package/src/lib/primitives/geometry/Ellipse2d.ts +2 -2
  124. package/src/lib/primitives/geometry/geometry-constants.ts +2 -1
  125. package/src/lib/primitives/intersect.test.ts +946 -0
  126. package/src/lib/primitives/intersect.ts +12 -5
  127. package/src/lib/primitives/utils.ts +11 -0
  128. package/src/version.ts +3 -3
  129. package/src/lib/test/currentToolIdMask.test.ts +0 -49
@@ -1803,7 +1803,9 @@ export class Editor extends EventEmitter<TLEventMap> {
1803
1803
  }
1804
1804
 
1805
1805
  /**
1806
- * Select all direct children of the current page.
1806
+ * Select all shapes. If the user has selected shapes that share a parent,
1807
+ * select all shapes within that parent. If the user has not selected any shapes,
1808
+ * or if the shapes shapes are only on select all shapes on the current page.
1807
1809
  *
1808
1810
  * @example
1809
1811
  * ```ts
@@ -1813,11 +1815,34 @@ export class Editor extends EventEmitter<TLEventMap> {
1813
1815
  * @public
1814
1816
  */
1815
1817
  selectAll(): this {
1816
- const ids = this.getSortedChildIdsForParent(this.getCurrentPageId())
1817
- // page might have no shapes
1818
+ let parentToSelectWithinId: TLParentId | null = null
1819
+
1820
+ const selectedShapeIds = this.getSelectedShapeIds()
1821
+
1822
+ // If we have selected shapes, try to find a parent to select within
1823
+ if (selectedShapeIds.length > 0) {
1824
+ for (const id of selectedShapeIds) {
1825
+ const shape = this.getShape(id)
1826
+ if (!shape) continue
1827
+ if (parentToSelectWithinId === null) {
1828
+ // If we haven't found a parent yet, set this parent as the parent to select within
1829
+ parentToSelectWithinId = shape.parentId
1830
+ } else if (parentToSelectWithinId !== shape.parentId) {
1831
+ // If we've found two different parents, we can't select all, do nothing
1832
+ return this
1833
+ }
1834
+ }
1835
+ }
1836
+
1837
+ // If we haven't found a parent from our selected shapes, select the current page
1838
+ if (!parentToSelectWithinId) {
1839
+ parentToSelectWithinId = this.getCurrentPageId()
1840
+ }
1841
+
1842
+ // Select all the unlocked shapes within the parent
1843
+ const ids = this.getSortedChildIdsForParent(parentToSelectWithinId)
1818
1844
  if (ids.length <= 0) return this
1819
1845
  this.setSelectedShapes(this._getUnlockedShapeIds(ids))
1820
-
1821
1846
  return this
1822
1847
  }
1823
1848
 
@@ -1838,10 +1863,11 @@ export class Editor extends EventEmitter<TLEventMap> {
1838
1863
  firstParentId &&
1839
1864
  selectedShapeIds.every((shapeId) => this.getShape(shapeId)?.parentId === firstParentId) &&
1840
1865
  !isPageId(firstParentId)
1866
+ const filteredShapes = isSelectedWithinContainer
1867
+ ? this.getCurrentPageShapes().filter((shape) => shape.parentId === firstParentId)
1868
+ : this.getCurrentPageShapes().filter((shape) => isPageId(shape.parentId))
1841
1869
  const readingOrderShapes = isSelectedWithinContainer
1842
- ? this._getShapesInReadingOrder(
1843
- this.getCurrentPageShapes().filter((shape) => shape.parentId === firstParentId)
1844
- )
1870
+ ? this._getShapesInReadingOrder(filteredShapes)
1845
1871
  : this.getCurrentPageShapesInReadingOrder()
1846
1872
  const currentShapeId: TLShapeId | undefined =
1847
1873
  selectedShapeIds.length === 1
@@ -1858,7 +1884,7 @@ export class Editor extends EventEmitter<TLEventMap> {
1858
1884
  adjacentShapeId = shapeIds[adjacentIndex]
1859
1885
  } else {
1860
1886
  if (!currentShapeId) return
1861
- adjacentShapeId = this.getNearestAdjacentShape(currentShapeId, direction)
1887
+ adjacentShapeId = this.getNearestAdjacentShape(filteredShapes, currentShapeId, direction)
1862
1888
  }
1863
1889
 
1864
1890
  const shape = this.getShape(adjacentShapeId)
@@ -1957,6 +1983,7 @@ export class Editor extends EventEmitter<TLEventMap> {
1957
1983
  * @public
1958
1984
  */
1959
1985
  getNearestAdjacentShape(
1986
+ shapes: TLShape[],
1960
1987
  currentShapeId: TLShapeId,
1961
1988
  direction: 'left' | 'right' | 'up' | 'down'
1962
1989
  ): TLShapeId {
@@ -1964,7 +1991,6 @@ export class Editor extends EventEmitter<TLEventMap> {
1964
1991
  const currentShape = this.getShape(currentShapeId)
1965
1992
  if (!currentShape) return currentShapeId
1966
1993
 
1967
- const shapes = this.getCurrentPageShapes()
1968
1994
  const tabbableShapes = shapes.filter(
1969
1995
  (shape) => this.getShapeUtil(shape).canTabTo(shape) && shape.id !== currentShapeId
1970
1996
  )
@@ -4395,21 +4421,28 @@ export class Editor extends EventEmitter<TLEventMap> {
4395
4421
  */
4396
4422
  deletePage(page: TLPageId | TLPage): this {
4397
4423
  const id = typeof page === 'string' ? page : page.id
4398
- this.run(() => {
4399
- if (this.getIsReadonly()) return
4400
- const pages = this.getPages()
4401
- if (pages.length === 1) return
4424
+ this.run(
4425
+ () => {
4426
+ if (this.getIsReadonly()) return
4427
+ const pages = this.getPages()
4428
+ if (pages.length === 1) return
4402
4429
 
4403
- const deletedPage = this.getPage(id)
4404
- if (!deletedPage) return
4430
+ const deletedPage = this.getPage(id)
4431
+ if (!deletedPage) return
4405
4432
 
4406
- if (id === this.getCurrentPageId()) {
4407
- const index = pages.findIndex((page) => page.id === id)
4408
- const next = pages[index - 1] ?? pages[index + 1]
4409
- this.setCurrentPage(next.id)
4410
- }
4411
- this.store.remove([deletedPage.id])
4412
- })
4433
+ if (id === this.getCurrentPageId()) {
4434
+ const index = pages.findIndex((page) => page.id === id)
4435
+ const next = pages[index - 1] ?? pages[index + 1]
4436
+ this.setCurrentPage(next.id)
4437
+ }
4438
+
4439
+ const shapes = this.getSortedChildIdsForParent(deletedPage.id)
4440
+ this.deleteShapes(shapes)
4441
+
4442
+ this.store.remove([deletedPage.id])
4443
+ },
4444
+ { ignoreShapeLock: true }
4445
+ )
4413
4446
  return this
4414
4447
  }
4415
4448
 
@@ -1,4 +1,5 @@
1
1
  import { BoxModel, TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema'
2
+ import { objectMapKeys } from '@tldraw/utils'
2
3
  import { Editor } from '../../Editor'
3
4
 
4
5
  const fixNewLines = /\r?\n|\r/g
@@ -60,10 +61,18 @@ export interface TLMeasureTextSpanOpts {
60
61
 
61
62
  const spaceCharacterRegex = /\s/
62
63
 
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
+
63
73
  /** @public */
64
74
  export class TextManager {
65
75
  private elm: HTMLDivElement
66
- private defaultStyles: Record<string, string | null>
67
76
 
68
77
  constructor(public editor: Editor) {
69
78
  const elm = document.createElement('div')
@@ -73,31 +82,34 @@ export class TextManager {
73
82
  elm.tabIndex = -1
74
83
  this.editor.getContainer().appendChild(elm)
75
84
 
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,
85
+ this.elm = elm
86
+
87
+ for (const key of objectMapKeys(initialDefaultStyles)) {
88
+ elm.style.setProperty(key, initialDefaultStyles[key])
85
89
  }
90
+ }
86
91
 
87
- this.elm = elm
92
+ private setElementStyles(styles: Record<string, string | undefined>) {
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
+ }
88
107
  }
89
108
 
90
109
  dispose() {
91
110
  return this.elm.remove()
92
111
  }
93
112
 
94
- private resetElmStyles() {
95
- const { elm, defaultStyles } = this
96
- for (const key in defaultStyles) {
97
- elm.style.setProperty(key, defaultStyles[key])
98
- }
99
- }
100
-
101
113
  measureText(textToMeasure: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {
102
114
  const div = document.createElement('div')
103
115
  div.textContent = normalizeTextForDom(textToMeasure)
@@ -107,54 +119,36 @@ export class TextManager {
107
119
  measureHtml(html: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {
108
120
  const { elm } = this
109
121
 
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
- }
122
+ const newStyles = {
123
+ 'font-family': opts.fontFamily,
124
+ 'font-style': opts.fontStyle,
125
+ 'font-weight': opts.fontWeight,
126
+ 'font-size': opts.fontSize + 'px',
127
+ 'line-height': opts.lineHeight.toString(),
128
+ padding: opts.padding,
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,
117
133
  }
118
134
 
119
- elm.innerHTML = html
135
+ const restoreStyles = this.setElementStyles(newStyles)
120
136
 
121
- // Apply the default styles to the element (for all styles here or that were ever seen in opts.otherStyles)
122
- this.resetElmStyles()
137
+ try {
138
+ elm.innerHTML = html
123
139
 
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)
140
+ const scrollWidth = opts.measureScrollWidth ? elm.scrollWidth : 0
141
+ const rect = elm.getBoundingClientRect()
130
142
 
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)
143
+ return {
144
+ x: 0,
145
+ y: 0,
146
+ w: rect.width,
147
+ h: rect.height,
148
+ scrollWidth,
146
149
  }
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,
150
+ } finally {
151
+ restoreStyles()
158
152
  }
159
153
  }
160
154
 
@@ -274,82 +268,68 @@ export class TextManager {
274
268
 
275
269
  const { elm } = this
276
270
 
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
-
299
271
  const shouldTruncateToFirstLine =
300
272
  opts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'
301
-
302
- if (shouldTruncateToFirstLine) {
303
- elm.style.setProperty('overflow-wrap', 'anywhere')
304
- elm.style.setProperty('word-break', 'break-all')
305
- }
306
-
307
- if (opts.otherStyles) {
308
- for (const [key, value] of Object.entries(opts.otherStyles)) {
309
- elm.style.setProperty(key, value)
310
- }
273
+ const elementWidth = Math.ceil(opts.width - opts.padding * 2)
274
+ const newStyles = {
275
+ 'font-family': opts.fontFamily,
276
+ 'font-style': opts.fontStyle,
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,
311
286
  }
287
+ const restoreStyles = this.setElementStyles(newStyles)
312
288
 
313
- const normalizedText = normalizeTextForDom(textToMeasure)
314
-
315
- // Render the text into the measurement element:
316
- elm.textContent = normalizedText
317
-
318
- // actually measure the text:
319
- const { spans, didTruncate } = this.measureElementTextNodeSpans(elm, {
320
- shouldTruncateToFirstLine,
321
- })
289
+ try {
290
+ const normalizedText = normalizeTextForDom(textToMeasure)
322
291
 
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`)
292
+ // Render the text into the measurement element:
330
293
  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
- },
294
+
295
+ // actually measure the text:
296
+ const { spans, didTruncate } = this.measureElementTextNodeSpans(elm, {
297
+ shouldTruncateToFirstLine,
348
298
  })
349
299
 
350
- return truncatedSpans
351
- }
300
+ if (opts.overflow === 'truncate-ellipsis' && didTruncate) {
301
+ // we need to measure the ellipsis to know how much space it takes up
302
+ elm.textContent = '…'
303
+ const ellipsisWidth = Math.ceil(this.measureElementTextNodeSpans(elm).spans[0].box.w)
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
+ }
352
329
 
353
- return spans
330
+ return spans
331
+ } finally {
332
+ restoreStyles()
333
+ }
354
334
  }
355
335
  }
@@ -584,6 +584,15 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
584
584
  */
585
585
  onResizeEnd?(initial: Shape, current: Shape): TLShapePartial<Shape> | void
586
586
 
587
+ /**
588
+ * A callback called when a shape resize is cancelled.
589
+ *
590
+ * @param initial - The shape at the start of the resize.
591
+ * @param current - The current shape.
592
+ * @public
593
+ */
594
+ onResizeCancel?(initial: Shape, current: Shape): void
595
+
587
596
  /**
588
597
  * A callback called when a shape starts being translated.
589
598
  *
@@ -613,6 +622,25 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
613
622
  */
614
623
  onTranslateEnd?(initial: Shape, current: Shape): TLShapePartial<Shape> | void
615
624
 
625
+ /**
626
+ * A callback called when a shape translation is cancelled.
627
+ *
628
+ * @param initial - The shape at the start of the translation.
629
+ * @param current - The current shape.
630
+ * @public
631
+ */
632
+ onTranslateCancel?(initial: Shape, current: Shape): void
633
+
634
+ /**
635
+ * A callback called when a shape's handle starts being dragged.
636
+ *
637
+ * @param shape - The shape.
638
+ * @param info - An object containing the handle and whether the handle is 'precise' or not.
639
+ * @returns A change to apply to the shape, or void.
640
+ * @public
641
+ */
642
+ onHandleDragStart?(shape: Shape, info: TLHandleDragInfo<Shape>): TLShapePartial<Shape> | void
643
+
616
644
  /**
617
645
  * A callback called when a shape's handle changes.
618
646
  *
@@ -623,6 +651,25 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
623
651
  */
624
652
  onHandleDrag?(shape: Shape, info: TLHandleDragInfo<Shape>): TLShapePartial<Shape> | void
625
653
 
654
+ /**
655
+ * A callback called when a shape's handle finishes being dragged.
656
+ *
657
+ * @param current - The current shape.
658
+ * @param info - An object containing the handle and whether the handle is 'precise' or not.
659
+ * @returns A change to apply to the shape, or void.
660
+ * @public
661
+ */
662
+ onHandleDragEnd?(current: Shape, info: TLHandleDragInfo<Shape>): TLShapePartial<Shape> | void
663
+
664
+ /**
665
+ * A callback called when a shape's handle drag is cancelled.
666
+ *
667
+ * @param current - The current shape.
668
+ * @param info - An object containing the handle and whether the handle is 'precise' or not.
669
+ * @public
670
+ */
671
+ onHandleDragCancel?(current: Shape, info: TLHandleDragInfo<Shape>): void
672
+
626
673
  /**
627
674
  * A callback called when a shape starts being rotated.
628
675
  *
@@ -652,6 +699,15 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
652
699
  */
653
700
  onRotateEnd?(initial: Shape, current: Shape): TLShapePartial<Shape> | void
654
701
 
702
+ /**
703
+ * A callback called when a shape rotation is cancelled.
704
+ *
705
+ * @param initial - The shape at the start of the rotation.
706
+ * @param current - The current shape.
707
+ * @public
708
+ */
709
+ onRotateCancel?(initial: Shape, current: Shape): void
710
+
655
711
  /**
656
712
  * Not currently used.
657
713
  *
@@ -819,5 +875,6 @@ export interface TLResizeInfo<T extends TLShape> {
819
875
  export interface TLHandleDragInfo<T extends TLShape> {
820
876
  handle: TLHandle
821
877
  isPrecise: boolean
878
+ isCreatingShape: boolean
822
879
  initial?: T | undefined
823
880
  }