@tldraw/editor 3.13.0-canary.ca982decc2f2 → 3.13.0-canary.cb6b7a41c175

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 (172) hide show
  1. package/dist-cjs/index.d.ts +113 -111
  2. package/dist-cjs/index.js +7 -22
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js +2 -1
  5. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  6. package/dist-cjs/lib/components/Shape.js +12 -8
  7. package/dist-cjs/lib/components/Shape.js.map +2 -2
  8. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +27 -2
  9. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  10. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +8 -6
  11. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +2 -2
  12. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +17 -11
  13. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
  14. package/dist-cjs/lib/components/default-components/DefaultSpinner.js +1 -1
  15. package/dist-cjs/lib/components/default-components/DefaultSpinner.js.map +2 -2
  16. package/dist-cjs/lib/editor/Editor.js +39 -14
  17. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  18. package/dist-cjs/lib/editor/managers/SnapManager/HandleSnaps.js.map +2 -2
  19. package/dist-cjs/lib/editor/managers/TextManager.js +10 -0
  20. package/dist-cjs/lib/editor/managers/TextManager.js.map +2 -2
  21. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  22. package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js.map +2 -2
  23. package/dist-cjs/lib/exports/getSvgJsx.js +12 -3
  24. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  25. package/dist-cjs/lib/hooks/useDocumentEvents.js +3 -2
  26. package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
  27. package/dist-cjs/lib/hooks/useEditorComponents.js +16 -15
  28. package/dist-cjs/lib/hooks/useEditorComponents.js.map +2 -2
  29. package/dist-cjs/lib/options.js.map +2 -2
  30. package/dist-cjs/lib/primitives/Box.js +16 -0
  31. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  32. package/dist-cjs/lib/primitives/Mat.js +1 -1
  33. package/dist-cjs/lib/primitives/Mat.js.map +2 -2
  34. package/dist-cjs/lib/primitives/Vec.js +20 -0
  35. package/dist-cjs/lib/primitives/Vec.js.map +2 -2
  36. package/dist-cjs/lib/primitives/geometry/Arc2d.js +2 -2
  37. package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
  38. package/dist-cjs/lib/primitives/geometry/Circle2d.js +1 -1
  39. package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +2 -2
  40. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +1 -1
  41. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
  42. package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js.map +2 -2
  43. package/dist-cjs/lib/primitives/geometry/Edge2d.js +1 -1
  44. package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
  45. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +2 -2
  46. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +91 -20
  47. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  48. package/dist-cjs/lib/primitives/geometry/Group2d.js +55 -2
  49. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  50. package/dist-cjs/lib/primitives/geometry/Point2d.js.map +2 -2
  51. package/dist-cjs/lib/primitives/geometry/Polyline2d.js.map +2 -2
  52. package/dist-cjs/lib/primitives/geometry/Stadium2d.js.map +2 -2
  53. package/dist-cjs/lib/utils/areShapesContentEqual.js +25 -0
  54. package/dist-cjs/lib/utils/areShapesContentEqual.js.map +7 -0
  55. package/dist-cjs/lib/utils/debug-flags.js +5 -2
  56. package/dist-cjs/lib/utils/debug-flags.js.map +2 -2
  57. package/dist-cjs/lib/utils/dom.js +3 -3
  58. package/dist-cjs/lib/utils/dom.js.map +2 -2
  59. package/dist-cjs/lib/utils/nearestMultiple.js +34 -0
  60. package/dist-cjs/lib/utils/nearestMultiple.js.map +7 -0
  61. package/dist-cjs/lib/utils/rotation.js +5 -5
  62. package/dist-cjs/lib/utils/rotation.js.map +2 -2
  63. package/dist-cjs/version.js +3 -3
  64. package/dist-cjs/version.js.map +1 -1
  65. package/dist-esm/index.d.mts +113 -111
  66. package/dist-esm/index.mjs +9 -41
  67. package/dist-esm/index.mjs.map +2 -2
  68. package/dist-esm/lib/TldrawEditor.mjs +2 -1
  69. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  70. package/dist-esm/lib/components/Shape.mjs +12 -8
  71. package/dist-esm/lib/components/Shape.mjs.map +2 -2
  72. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +27 -2
  73. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  74. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +8 -6
  75. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +2 -2
  76. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +17 -11
  77. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
  78. package/dist-esm/lib/components/default-components/DefaultSpinner.mjs +1 -1
  79. package/dist-esm/lib/components/default-components/DefaultSpinner.mjs.map +2 -2
  80. package/dist-esm/lib/editor/Editor.mjs +39 -14
  81. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  82. package/dist-esm/lib/editor/managers/SnapManager/HandleSnaps.mjs.map +2 -2
  83. package/dist-esm/lib/editor/managers/TextManager.mjs +10 -0
  84. package/dist-esm/lib/editor/managers/TextManager.mjs.map +2 -2
  85. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  86. package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs.map +2 -2
  87. package/dist-esm/lib/exports/getSvgJsx.mjs +12 -3
  88. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  89. package/dist-esm/lib/hooks/useDocumentEvents.mjs +3 -2
  90. package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
  91. package/dist-esm/lib/hooks/useEditorComponents.mjs +16 -15
  92. package/dist-esm/lib/hooks/useEditorComponents.mjs.map +2 -2
  93. package/dist-esm/lib/options.mjs.map +2 -2
  94. package/dist-esm/lib/primitives/Box.mjs +16 -0
  95. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  96. package/dist-esm/lib/primitives/Mat.mjs +1 -1
  97. package/dist-esm/lib/primitives/Mat.mjs.map +2 -2
  98. package/dist-esm/lib/primitives/Vec.mjs +20 -0
  99. package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
  100. package/dist-esm/lib/primitives/geometry/Arc2d.mjs +2 -2
  101. package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
  102. package/dist-esm/lib/primitives/geometry/Circle2d.mjs +1 -1
  103. package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +2 -2
  104. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +1 -1
  105. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
  106. package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs.map +2 -2
  107. package/dist-esm/lib/primitives/geometry/Edge2d.mjs +1 -1
  108. package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
  109. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +2 -2
  110. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +92 -21
  111. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  112. package/dist-esm/lib/primitives/geometry/Group2d.mjs +55 -2
  113. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  114. package/dist-esm/lib/primitives/geometry/Point2d.mjs.map +2 -2
  115. package/dist-esm/lib/primitives/geometry/Polyline2d.mjs.map +2 -2
  116. package/dist-esm/lib/primitives/geometry/Stadium2d.mjs.map +2 -2
  117. package/dist-esm/lib/utils/areShapesContentEqual.mjs +5 -0
  118. package/dist-esm/lib/utils/areShapesContentEqual.mjs.map +7 -0
  119. package/dist-esm/lib/utils/debug-flags.mjs +5 -2
  120. package/dist-esm/lib/utils/debug-flags.mjs.map +2 -2
  121. package/dist-esm/lib/utils/dom.mjs +3 -3
  122. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  123. package/dist-esm/lib/utils/nearestMultiple.mjs +14 -0
  124. package/dist-esm/lib/utils/nearestMultiple.mjs.map +7 -0
  125. package/dist-esm/lib/utils/rotation.mjs +5 -5
  126. package/dist-esm/lib/utils/rotation.mjs.map +2 -2
  127. package/dist-esm/version.mjs +3 -3
  128. package/dist-esm/version.mjs.map +1 -1
  129. package/editor.css +34 -4
  130. package/package.json +7 -7
  131. package/src/index.ts +16 -31
  132. package/src/lib/TldrawEditor.tsx +6 -1
  133. package/src/lib/components/Shape.tsx +14 -10
  134. package/src/lib/components/default-components/DefaultCanvas.tsx +32 -2
  135. package/src/lib/components/default-components/DefaultErrorFallback.tsx +15 -8
  136. package/src/lib/components/default-components/DefaultShapeIndicator.tsx +17 -8
  137. package/src/lib/components/default-components/DefaultSpinner.tsx +1 -1
  138. package/src/lib/editor/Editor.test.ts +1 -1
  139. package/src/lib/editor/Editor.ts +39 -14
  140. package/src/lib/editor/managers/SnapManager/HandleSnaps.ts +0 -1
  141. package/src/lib/editor/managers/TextManager.ts +12 -0
  142. package/src/lib/editor/shapes/ShapeUtil.ts +22 -2
  143. package/src/lib/editor/shapes/shared/getPerfectDashProps.ts +9 -9
  144. package/src/lib/exports/getSvgJsx.tsx +16 -7
  145. package/src/lib/hooks/useDocumentEvents.ts +7 -2
  146. package/src/lib/hooks/useEditorComponents.tsx +32 -28
  147. package/src/lib/options.ts +4 -0
  148. package/src/lib/primitives/Box.ts +20 -0
  149. package/src/lib/primitives/Mat.ts +5 -4
  150. package/src/lib/primitives/Vec.ts +23 -0
  151. package/src/lib/primitives/geometry/Arc2d.ts +5 -5
  152. package/src/lib/primitives/geometry/Circle2d.ts +4 -4
  153. package/src/lib/primitives/geometry/CubicBezier2d.ts +4 -4
  154. package/src/lib/primitives/geometry/CubicSpline2d.ts +3 -3
  155. package/src/lib/primitives/geometry/Edge2d.ts +3 -3
  156. package/src/lib/primitives/geometry/Ellipse2d.ts +3 -3
  157. package/src/lib/primitives/geometry/Geometry2d.test.ts +42 -0
  158. package/src/lib/primitives/geometry/Geometry2d.ts +123 -35
  159. package/src/lib/primitives/geometry/Group2d.ts +70 -7
  160. package/src/lib/primitives/geometry/Point2d.ts +2 -2
  161. package/src/lib/primitives/geometry/Polyline2d.ts +3 -3
  162. package/src/lib/primitives/geometry/Stadium2d.ts +3 -3
  163. package/src/lib/test/currentToolIdMask.test.ts +1 -1
  164. package/src/lib/test/user.test.ts +1 -1
  165. package/src/lib/utils/areShapesContentEqual.ts +4 -0
  166. package/src/lib/utils/debug-flags.ts +7 -2
  167. package/src/lib/utils/dom.ts +4 -4
  168. package/src/lib/utils/nearestMultiple.ts +13 -0
  169. package/src/lib/utils/rotation.ts +8 -6
  170. package/src/lib/utils/sync/LocalIndexedDb.test.ts +1 -1
  171. package/src/lib/utils/sync/TLLocalSyncClient.test.ts +1 -1
  172. package/src/version.ts +3 -3
@@ -22,6 +22,7 @@ import { Vec } from '../../primitives/Vec'
22
22
  import { toDomPrecision } from '../../primitives/utils'
23
23
  import { debugFlags } from '../../utils/debug-flags'
24
24
  import { setStyleProperty } from '../../utils/dom'
25
+ import { nearestMultiple } from '../../utils/nearestMultiple'
25
26
  import { GeometryDebuggingView } from '../GeometryDebuggingView'
26
27
  import { LiveCollaborators } from '../LiveCollaborators'
27
28
  import { MenuClickCapture } from '../MenuClickCapture'
@@ -168,6 +169,7 @@ export function DefaultCanvas({ className }: TLCanvasComponentProps) {
168
169
  <SnapIndicatorWrapper />
169
170
  <SelectionForegroundWrapper />
170
171
  <HandlesWrapper />
172
+ <OverlaysWrapper />
171
173
  <LiveCollaborators />
172
174
  </div>
173
175
  </div>
@@ -372,14 +374,33 @@ function HandleWrapper({
372
374
  )
373
375
  }
374
376
 
377
+ function OverlaysWrapper() {
378
+ const { Overlays } = useEditorComponents()
379
+ if (!Overlays) return null
380
+ return (
381
+ <div className="tl-custom-overlays tl-overlays__item">
382
+ <Overlays />
383
+ </div>
384
+ )
385
+ }
386
+
375
387
  function ShapesWithSVGs() {
376
388
  const editor = useEditor()
377
389
 
378
390
  const renderingShapes = useValue('rendering shapes', () => editor.getRenderingShapes(), [editor])
379
391
 
392
+ const dprMultiple = useValue(
393
+ 'dpr multiple',
394
+ () =>
395
+ // dprMultiple is the smallest number we can multiply dpr by to get an integer
396
+ // it's usually 1, 2, or 4 (for e.g. dpr of 2, 2.5 and 2.25 respectively)
397
+ nearestMultiple(Math.floor(editor.getInstanceState().devicePixelRatio * 100) / 100),
398
+ [editor]
399
+ )
400
+
380
401
  return renderingShapes.map((result) => (
381
402
  <Fragment key={result.id + '_fragment'}>
382
- <Shape {...result} />
403
+ <Shape {...result} dprMultiple={dprMultiple} />
383
404
  <DebugSvgCopy id={result.id} mode="iframe" />
384
405
  </Fragment>
385
406
  ))
@@ -414,10 +435,19 @@ function ShapesToDisplay() {
414
435
 
415
436
  const renderingShapes = useValue('rendering shapes', () => editor.getRenderingShapes(), [editor])
416
437
 
438
+ const dprMultiple = useValue(
439
+ 'dpr multiple',
440
+ () =>
441
+ // dprMultiple is the smallest number we can multiply dpr by to get an integer
442
+ // it's usually 1, 2, or 4 (for e.g. dpr of 2, 2.5 and 2.25 respectively)
443
+ nearestMultiple(Math.floor(editor.getInstanceState().devicePixelRatio * 100) / 100),
444
+ [editor]
445
+ )
446
+
417
447
  return (
418
448
  <>
419
449
  {renderingShapes.map((result) => (
420
- <Shape key={result.id + '_shape'} {...result} />
450
+ <Shape key={result.id + '_shape'} {...result} dprMultiple={dprMultiple} />
421
451
  ))}
422
452
  {tlenv.isSafari && <ReflowIfNeeded />}
423
453
  </>
@@ -168,16 +168,23 @@ My browser: ${navigator.userAgent}`
168
168
  ) : (
169
169
  <>
170
170
  <h2>Something went wrong</h2>
171
- <p>Please refresh the page to continue.</p>
171
+ <p>Please refresh your browser.</p>
172
172
  <p>
173
- If you keep seeing this screen, you can create a{' '}
174
- <a href={url.toString()}>GitHub issue</a> or ask for help on{' '}
175
- <a href="https://discord.tldraw.com/?utm_source=sdk&utm_medium=organic&utm_campaign=error-screen">
176
- Discord
177
- </a>
178
- . If you are still stuck, you can reset the tldraw data on your machine. This may
179
- erase the project you were working on, so try to get help first.
173
+ If the issue continues after refreshing, you may need to reset the tldraw data stored
174
+ on your device.
180
175
  </p>
176
+ <p>
177
+ <strong>Note:</strong> Resetting will erase your current project and any unsaved work.
178
+ </p>
179
+ {process.env.NODE_ENV !== 'production' && (
180
+ <p>
181
+ If you&apos;re developing with the SDK and need help, join us on{' '}
182
+ <a href="https://discord.tldraw.com/?utm_source=sdk&utm_medium=organic&utm_campaign=error-screen">
183
+ Discord
184
+ </a>
185
+ .
186
+ </p>
187
+ )}
181
188
  {shouldShowError && (
182
189
  <>
183
190
  Message:
@@ -9,13 +9,21 @@ import { useEditorComponents } from '../../hooks/useEditorComponents'
9
9
  import { OptionalErrorBoundary } from '../ErrorBoundary'
10
10
 
11
11
  // need an extra layer of indirection here to allow hooks to be used inside the indicator render
12
- const EvenInnererIndicator = memo(({ shape, util }: { shape: TLShape; util: ShapeUtil<any> }) => {
13
- return useStateTracking('Indicator: ' + shape.type, () =>
14
- // always fetch the latest shape from the store even if the props/meta have not changed, to avoid
15
- // calling the render method with stale data.
16
- util.indicator(util.editor.store.unsafeGetWithoutCapture(shape.id) as TLShape)
17
- )
18
- })
12
+ const EvenInnererIndicator = memo(
13
+ ({ shape, util }: { shape: TLShape; util: ShapeUtil<any> }) => {
14
+ return useStateTracking('Indicator: ' + shape.type, () =>
15
+ // always fetch the latest shape from the store even if the props/meta have not changed, to avoid
16
+ // calling the render method with stale data.
17
+ util.indicator(util.editor.store.unsafeGetWithoutCapture(shape.id) as TLShape)
18
+ )
19
+ },
20
+ (prevProps, nextProps) => {
21
+ return (
22
+ prevProps.shape.props === nextProps.shape.props &&
23
+ prevProps.shape.meta === nextProps.shape.meta
24
+ )
25
+ }
26
+ )
19
27
 
20
28
  const InnerIndicator = memo(({ editor, id }: { editor: Editor; id: TLShapeId }) => {
21
29
  const shape = useValue('shape for indicator', () => editor.store.get(id), [editor, id])
@@ -61,13 +69,14 @@ export const DefaultShapeIndicator = memo(function DefaultShapeIndicator({
61
69
  useQuickReactor(
62
70
  'indicator transform',
63
71
  () => {
72
+ if (hidden) return
64
73
  const elm = rIndicator.current
65
74
  if (!elm) return
66
75
  const pageTransform = editor.getShapePageTransform(shapeId)
67
76
  if (!pageTransform) return
68
77
  elm.style.setProperty('transform', pageTransform.toCssString())
69
78
  },
70
- [editor, shapeId]
79
+ [editor, shapeId, hidden]
71
80
  )
72
81
 
73
82
  useLayoutEffect(() => {
@@ -1,7 +1,7 @@
1
1
  /** @public @react */
2
2
  export function DefaultSpinner() {
3
3
  return (
4
- <svg width={16} height={16} viewBox="0 0 16 16">
4
+ <svg width={16} height={16} viewBox="0 0 16 16" aria-hidden="false">
5
5
  <g strokeWidth={2} fill="none" fillRule="evenodd">
6
6
  <circle strokeOpacity={0.25} cx={8} cy={8} r={7} stroke="currentColor" />
7
7
  <path strokeLinecap="round" d="M15 8c0-4.5-4.5-7-7-7" stroke="currentColor">
@@ -52,7 +52,7 @@ beforeEach(() => {
52
52
  shapeUtils: [CustomShape],
53
53
  bindingUtils: [],
54
54
  tools: [],
55
- store: createTLStore({ shapeUtils: [CustomShape] }),
55
+ store: createTLStore({ shapeUtils: [CustomShape], bindingUtils: [] }),
56
56
  getContainer: () => document.body,
57
57
  })
58
58
  editor.setCameraOptions({ isLocked: true })
@@ -129,6 +129,7 @@ import { Group2d } from '../primitives/geometry/Group2d'
129
129
  import { intersectPolygonPolygon } from '../primitives/intersect'
130
130
  import { PI, approximately, areAnglesCompatible, clamp, pointInPolygon } from '../primitives/utils'
131
131
  import { ReadonlySharedStyleMap, SharedStyle, SharedStyleMap } from '../utils/SharedStylesMap'
132
+ import { areShapesContentEqual } from '../utils/areShapesContentEqual'
132
133
  import { dataUrlToFile } from '../utils/assets'
133
134
  import { debugFlags } from '../utils/debug-flags'
134
135
  import {
@@ -2275,13 +2276,21 @@ export class Editor extends EventEmitter<TLEventMap> {
2275
2276
  setEditingShape(shape: TLShapeId | TLShape | null): this {
2276
2277
  const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
2277
2278
  this.setRichTextEditor(null)
2278
- if (id !== this.getEditingShapeId()) {
2279
+ const prevEditingShapeId = this.getEditingShapeId()
2280
+ if (id !== prevEditingShapeId) {
2279
2281
  if (id) {
2280
2282
  const shape = this.getShape(id)
2281
2283
  if (shape && this.getShapeUtil(shape).canEdit(shape)) {
2282
2284
  this.run(
2283
2285
  () => {
2284
2286
  this._updateCurrentPageState({ editingShapeId: id })
2287
+ if (prevEditingShapeId) {
2288
+ const prevEditingShape = this.getShape(prevEditingShapeId)
2289
+ if (prevEditingShape) {
2290
+ this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape)
2291
+ }
2292
+ }
2293
+ this.getShapeUtil(shape).onEditStart?.(shape)
2285
2294
  },
2286
2295
  { history: 'ignore' }
2287
2296
  )
@@ -2294,6 +2303,12 @@ export class Editor extends EventEmitter<TLEventMap> {
2294
2303
  () => {
2295
2304
  this._updateCurrentPageState({ editingShapeId: null })
2296
2305
  this._currentRichTextEditor.set(null)
2306
+ if (prevEditingShapeId) {
2307
+ const prevEditingShape = this.getShape(prevEditingShapeId)
2308
+ if (prevEditingShape) {
2309
+ this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape)
2310
+ }
2311
+ }
2297
2312
  },
2298
2313
  { history: 'ignore' }
2299
2314
  )
@@ -4574,7 +4589,7 @@ export class Editor extends EventEmitter<TLEventMap> {
4574
4589
  this.fonts.trackFontsForShape(shape)
4575
4590
  return this.getShapeUtil(shape).getGeometry(shape, opts)
4576
4591
  },
4577
- { areRecordsEqual: (a, b) => a.props === b.props }
4592
+ { areRecordsEqual: areShapesContentEqual }
4578
4593
  )
4579
4594
  }
4580
4595
  return this._shapeGeometryCaches[context].get(
@@ -4622,9 +4637,15 @@ export class Editor extends EventEmitter<TLEventMap> {
4622
4637
 
4623
4638
  /** @internal */
4624
4639
  @computed private _getShapeHandlesCache(): ComputedCache<TLHandle[] | undefined, TLShape> {
4625
- return this.store.createComputedCache('handles', (shape) => {
4626
- return this.getShapeUtil(shape).getHandles?.(shape)
4627
- })
4640
+ return this.store.createComputedCache(
4641
+ 'handles',
4642
+ (shape) => {
4643
+ return this.getShapeUtil(shape).getHandles?.(shape)
4644
+ },
4645
+ {
4646
+ areRecordsEqual: areShapesContentEqual,
4647
+ }
4648
+ )
4628
4649
  }
4629
4650
 
4630
4651
  /**
@@ -5845,9 +5866,15 @@ export class Editor extends EventEmitter<TLEventMap> {
5845
5866
  @computed
5846
5867
  private _getBindingsIndexCache() {
5847
5868
  const index = bindingsIndex(this)
5848
- return this.store.createComputedCache<TLBinding[], TLShape>('bindingsIndex', (shape) => {
5849
- return index.get().get(shape.id)
5850
- })
5869
+ return this.store.createComputedCache<TLBinding[], TLShape>(
5870
+ 'bindingsIndex',
5871
+ (shape) => {
5872
+ return index.get().get(shape.id)
5873
+ },
5874
+ // we can ignore the shape equality check here because the index is
5875
+ // computed incrementally based on what bindings are in the store
5876
+ { areRecordsEqual: () => true }
5877
+ )
5851
5878
  }
5852
5879
 
5853
5880
  /**
@@ -10214,7 +10241,7 @@ export class Editor extends EventEmitter<TLEventMap> {
10214
10241
 
10215
10242
  // If the camera behavior is "zoom" and the ctrl key is pressed, then pan;
10216
10243
  // If the camera behavior is "pan" and the ctrl key is not pressed, then zoom
10217
- if (inputs.ctrlKey) behavior = wheelBehavior === 'pan' ? 'zoom' : 'pan'
10244
+ if (info.ctrlKey) behavior = wheelBehavior === 'pan' ? 'zoom' : 'pan'
10218
10245
 
10219
10246
  switch (behavior) {
10220
10247
  case 'zoom': {
@@ -10330,12 +10357,10 @@ export class Editor extends EventEmitter<TLEventMap> {
10330
10357
  if (this.inputs.isPanning && this.inputs.isPointing) {
10331
10358
  // Handle spacebar / middle mouse button panning
10332
10359
  const { currentScreenPoint, previousScreenPoint } = this.inputs
10333
- const { panSpeed } = cameraOptions
10334
10360
  const offset = Vec.Sub(currentScreenPoint, previousScreenPoint)
10335
- this.setCamera(
10336
- new Vec(cx + (offset.x * panSpeed) / cz, cy + (offset.y * panSpeed) / cz, cz),
10337
- { immediate: true }
10338
- )
10361
+ this.setCamera(new Vec(cx + offset.x / cz, cy + offset.y / cz, cz), {
10362
+ immediate: true,
10363
+ })
10339
10364
  this.maybeTrackPerformance('Panning')
10340
10365
  return
10341
10366
  }
@@ -45,7 +45,6 @@ export interface HandleSnapGeometry {
45
45
 
46
46
  const defaultGetSelfSnapOutline = () => null
47
47
  const defaultGetSelfSnapPoints = () => []
48
-
49
48
  /** @public */
50
49
  export class HandleSnaps {
51
50
  readonly editor: Editor
@@ -32,6 +32,7 @@ export interface TLMeasureTextSpanOpts {
32
32
  fontStyle: string
33
33
  lineHeight: number
34
34
  textAlign: TLDefaultHorizontalAlignStyle
35
+ otherStyles?: Record<string, string>
35
36
  }
36
37
 
37
38
  const spaceCharacterRegex = /\s/
@@ -86,6 +87,7 @@ export class TextManager {
86
87
  */
87
88
  maxWidth: null | number
88
89
  minWidth?: null | number
90
+ otherStyles?: Record<string, string>
89
91
  padding: string
90
92
  disableOverflowWrapBreaking?: boolean
91
93
  }
@@ -112,6 +114,11 @@ export class TextManager {
112
114
  'overflow-wrap',
113
115
  opts.disableOverflowWrapBreaking ? 'normal' : 'break-word'
114
116
  )
117
+ if (opts.otherStyles) {
118
+ for (const [key, value] of Object.entries(opts.otherStyles)) {
119
+ wrapperElm.style.setProperty(key, value)
120
+ }
121
+ }
115
122
 
116
123
  const scrollWidth = wrapperElm.scrollWidth
117
124
  const rect = wrapperElm.getBoundingClientRect()
@@ -256,6 +263,11 @@ export class TextManager {
256
263
  elm.style.setProperty('line-height', `${opts.lineHeight * opts.fontSize}px`)
257
264
  elm.style.setProperty('text-align', textAlignmentsForLtr[opts.textAlign])
258
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
+ }
259
271
 
260
272
  const shouldTruncateToFirstLine =
261
273
  opts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'
@@ -19,6 +19,7 @@ import { TLFontFace } from '../managers/FontManager'
19
19
  import { BoundsSnapGeometry } from '../managers/SnapManager/BoundsSnaps'
20
20
  import { HandleSnapGeometry } from '../managers/SnapManager/HandleSnaps'
21
21
  import { SvgExportContext } from '../types/SvgExportContext'
22
+ import { TLClickEventInfo } from '../types/event-types'
22
23
  import { TLResizeHandle } from '../types/selection-types'
23
24
 
24
25
  /** @public */
@@ -671,10 +672,21 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
671
672
  * A callback called when a shape's edge is double clicked.
672
673
  *
673
674
  * @param shape - The shape.
675
+ * @param info - Info about the edge.
674
676
  * @returns A change to apply to the shape, or void.
675
677
  * @public
676
678
  */
677
- onDoubleClickEdge?(shape: Shape): TLShapePartial<Shape> | void
679
+ onDoubleClickEdge?(shape: Shape, info: TLClickEventInfo): TLShapePartial<Shape> | void
680
+
681
+ /**
682
+ * A callback called when a shape's corner is double clicked.
683
+ *
684
+ * @param shape - The shape.
685
+ * @param info - Info about the corner.
686
+ * @returns A change to apply to the shape, or void.
687
+ * @public
688
+ */
689
+ onDoubleClickCorner?(shape: Shape, info: TLClickEventInfo): TLShapePartial<Shape> | void
678
690
 
679
691
  /**
680
692
  * A callback called when a shape is double clicked.
@@ -695,7 +707,15 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
695
707
  onClick?(shape: Shape): TLShapePartial<Shape> | void
696
708
 
697
709
  /**
698
- * A callback called when a shape finishes being editing.
710
+ * A callback called when a shape starts being edited.
711
+ *
712
+ * @param shape - The shape.
713
+ * @public
714
+ */
715
+ onEditStart?(shape: Shape): void
716
+
717
+ /**
718
+ * A callback called when a shape finishes being edited.
699
719
  *
700
720
  * @param shape - The shape.
701
721
  * @public
@@ -4,15 +4,15 @@ import { TLDefaultDashStyle } from '@tldraw/tlschema'
4
4
  export function getPerfectDashProps(
5
5
  totalLength: number,
6
6
  strokeWidth: number,
7
- opts = {} as Partial<{
8
- style: TLDefaultDashStyle
9
- snap: number
10
- end: 'skip' | 'outset' | 'none'
11
- start: 'skip' | 'outset' | 'none'
12
- lengthRatio: number
13
- closed: boolean
14
- forceSolid: boolean
15
- }>
7
+ opts: {
8
+ style?: TLDefaultDashStyle
9
+ snap?: number
10
+ end?: 'skip' | 'outset' | 'none'
11
+ start?: 'skip' | 'outset' | 'none'
12
+ lengthRatio?: number
13
+ closed?: boolean
14
+ forceSolid?: boolean
15
+ } = {}
16
16
  ): {
17
17
  strokeDasharray: string
18
18
  strokeDashoffset: string
@@ -365,6 +365,21 @@ function SvgExport({
365
365
  onMount()
366
366
  }, [onMount, shapeElements])
367
367
 
368
+ let backgroundColor = background ? theme.background : 'transparent'
369
+
370
+ if (singleFrameShapeId && background) {
371
+ const frameShapeUtil = editor.getShapeUtil('frame') as any as
372
+ | undefined
373
+ | { options: { showColors: boolean } }
374
+ if (frameShapeUtil?.options.showColors) {
375
+ const shape = editor.getShape(singleFrameShapeId)! as TLFrameShape
376
+ const color = theme[shape.props.color]
377
+ backgroundColor = color.frame.fill
378
+ } else {
379
+ backgroundColor = theme.solid
380
+ }
381
+ }
382
+
368
383
  return (
369
384
  <SvgExportContextProvider editor={editor} context={exportContext}>
370
385
  <svg
@@ -375,13 +390,7 @@ function SvgExport({
375
390
  viewBox={`${bbox.minX} ${bbox.minY} ${bbox.width} ${bbox.height}`}
376
391
  strokeLinecap="round"
377
392
  strokeLinejoin="round"
378
- style={{
379
- backgroundColor: background
380
- ? singleFrameShapeId
381
- ? theme.solid
382
- : theme.background
383
- : 'transparent',
384
- }}
393
+ style={{ backgroundColor }}
385
394
  data-color-mode={isDarkMode ? 'dark' : 'light'}
386
395
  className={`tl-container tl-theme__force-sRGB ${isDarkMode ? 'tl-theme__dark' : 'tl-theme__light'}`}
387
396
  >
@@ -11,6 +11,7 @@ export function useDocumentEvents() {
11
11
  const editor = useEditor()
12
12
  const container = useContainer()
13
13
 
14
+ const isEditing = useValue('isEditing', () => editor.getEditingShapeId(), [editor])
14
15
  const isAppFocused = useValue('isFocused', () => editor.getIsFocused(), [editor])
15
16
 
16
17
  // Prevent the browser's default drag and drop behavior on our container (UI, etc)
@@ -125,7 +126,11 @@ export function useDocumentEvents() {
125
126
  if (areShortcutsDisabled(editor)) {
126
127
  return
127
128
  }
128
- if (hasSelectedShapes) {
129
+ // isEditing here sounds like it's about text editing
130
+ // but more specifically, this is so you can tab into an
131
+ // embed that's being 'edited'. In our world,
132
+ // editing an embed, means it's interactive.
133
+ if (hasSelectedShapes && !isEditing) {
129
134
  // This is used in tandem with shape navigation.
130
135
  preventDefault(e)
131
136
  }
@@ -289,7 +294,7 @@ export function useDocumentEvents() {
289
294
  container.removeEventListener('keydown', handleKeyDown)
290
295
  container.removeEventListener('keyup', handleKeyUp)
291
296
  }
292
- }, [editor, container, isAppFocused])
297
+ }, [editor, container, isAppFocused, isEditing])
293
298
  }
294
299
 
295
300
  function areShortcutsDisabled(editor: Editor) {
@@ -51,29 +51,30 @@ import { useShallowObjectIdentity } from './useIdentity'
51
51
  /** @public */
52
52
  export interface TLEditorComponents {
53
53
  Background?: ComponentType | null
54
- SvgDefs?: ComponentType | null
55
54
  Brush?: ComponentType<TLBrushProps> | null
56
- ZoomBrush?: ComponentType<TLBrushProps> | null
57
- ShapeIndicators?: ComponentType | null
58
- ShapeIndicator?: ComponentType<TLShapeIndicatorProps> | null
59
- Cursor?: ComponentType<TLCursorProps> | null
60
55
  Canvas?: ComponentType<TLCanvasComponentProps> | null
61
56
  CollaboratorBrush?: ComponentType<TLBrushProps> | null
62
57
  CollaboratorCursor?: ComponentType<TLCursorProps> | null
63
58
  CollaboratorHint?: ComponentType<TLCollaboratorHintProps> | null
59
+ CollaboratorScribble?: ComponentType<TLScribbleProps> | null
64
60
  CollaboratorShapeIndicator?: ComponentType<TLShapeIndicatorProps> | null
61
+ Cursor?: ComponentType<TLCursorProps> | null
65
62
  Grid?: ComponentType<TLGridProps> | null
66
- Scribble?: ComponentType<TLScribbleProps> | null
67
- CollaboratorScribble?: ComponentType<TLScribbleProps> | null
68
- SnapIndicator?: ComponentType<TLSnapIndicatorProps> | null
69
- Handles?: ComponentType<TLHandlesProps> | null
70
63
  Handle?: ComponentType<TLHandleProps> | null
71
- Spinner?: ComponentType | null
72
- SelectionForeground?: ComponentType<TLSelectionForegroundProps> | null
73
- SelectionBackground?: ComponentType<TLSelectionBackgroundProps> | null
74
- OnTheCanvas?: ComponentType | null
64
+ Handles?: ComponentType<TLHandlesProps> | null
75
65
  InFrontOfTheCanvas?: ComponentType | null
76
66
  LoadingScreen?: ComponentType | null
67
+ OnTheCanvas?: ComponentType | null
68
+ Overlays?: ComponentType | null
69
+ Scribble?: ComponentType<TLScribbleProps> | null
70
+ SelectionBackground?: ComponentType<TLSelectionBackgroundProps> | null
71
+ SelectionForeground?: ComponentType<TLSelectionForegroundProps> | null
72
+ ShapeIndicator?: ComponentType<TLShapeIndicatorProps> | null
73
+ ShapeIndicators?: ComponentType | null
74
+ SnapIndicator?: ComponentType<TLSnapIndicatorProps> | null
75
+ Spinner?: ComponentType | null
76
+ SvgDefs?: ComponentType | null
77
+ ZoomBrush?: ComponentType<TLBrushProps> | null
77
78
 
78
79
  // These will always have defaults
79
80
  ErrorFallback?: TLErrorFallbackComponent
@@ -96,32 +97,35 @@ export function EditorComponentsProvider({
96
97
  const value = useMemo(
97
98
  (): Required<TLEditorComponents> => ({
98
99
  Background: DefaultBackground,
99
- SvgDefs: DefaultSvgDefs,
100
100
  Brush: DefaultBrush,
101
- ZoomBrush: DefaultBrush,
101
+ Canvas: DefaultCanvas,
102
102
  CollaboratorBrush: DefaultBrush,
103
- Cursor: DefaultCursor,
104
103
  CollaboratorCursor: DefaultCursor,
105
104
  CollaboratorHint: DefaultCollaboratorHint,
105
+ CollaboratorScribble: DefaultScribble,
106
106
  CollaboratorShapeIndicator: DefaultShapeIndicator,
107
+ Cursor: DefaultCursor,
107
108
  Grid: DefaultGrid,
109
+ Handle: DefaultHandle,
110
+ Handles: DefaultHandles,
111
+ InFrontOfTheCanvas: null,
112
+ LoadingScreen: DefaultLoadingScreen,
113
+ OnTheCanvas: null,
114
+ Overlays: null,
108
115
  Scribble: DefaultScribble,
116
+ SelectionBackground: DefaultSelectionBackground,
117
+ SelectionForeground: DefaultSelectionForeground,
118
+ ShapeIndicator: DefaultShapeIndicator,
119
+ ShapeIndicators: DefaultShapeIndicators,
109
120
  SnapIndicator: DefaultSnapIndicator,
110
- Handles: DefaultHandles,
111
- Handle: DefaultHandle,
112
- CollaboratorScribble: DefaultScribble,
121
+ Spinner: DefaultSpinner,
122
+ SvgDefs: DefaultSvgDefs,
123
+ ZoomBrush: DefaultBrush,
124
+
113
125
  ErrorFallback: DefaultErrorFallback,
114
126
  ShapeErrorFallback: DefaultShapeErrorFallback,
115
127
  ShapeIndicatorErrorFallback: DefaultShapeIndicatorErrorFallback,
116
- Spinner: DefaultSpinner,
117
- SelectionBackground: DefaultSelectionBackground,
118
- SelectionForeground: DefaultSelectionForeground,
119
- ShapeIndicators: DefaultShapeIndicators,
120
- ShapeIndicator: DefaultShapeIndicator,
121
- OnTheCanvas: null,
122
- InFrontOfTheCanvas: null,
123
- Canvas: DefaultCanvas,
124
- LoadingScreen: DefaultLoadingScreen,
128
+
125
129
  ..._overrides,
126
130
  }),
127
131
  [_overrides]
@@ -80,6 +80,10 @@ export interface TldrawOptions {
80
80
  * nonce to use in the editor's styles.
81
81
  */
82
82
  readonly nonce: string | undefined
83
+ /**
84
+ * Branding name of the app, currently only used for adding aria-label for the application.
85
+ */
86
+ readonly branding?: string
83
87
  }
84
88
 
85
89
  /** @public */
@@ -57,6 +57,11 @@ export class Box {
57
57
  this.x = n
58
58
  }
59
59
 
60
+ // eslint-disable-next-line no-restricted-syntax
61
+ get left() {
62
+ return this.x
63
+ }
64
+
60
65
  // eslint-disable-next-line no-restricted-syntax
61
66
  get midX() {
62
67
  return this.x + this.w / 2
@@ -67,6 +72,11 @@ export class Box {
67
72
  return this.x + this.w
68
73
  }
69
74
 
75
+ // eslint-disable-next-line no-restricted-syntax
76
+ get right() {
77
+ return this.x + this.w
78
+ }
79
+
70
80
  // eslint-disable-next-line no-restricted-syntax
71
81
  get minY() {
72
82
  return this.y
@@ -77,6 +87,11 @@ export class Box {
77
87
  this.y = n
78
88
  }
79
89
 
90
+ // eslint-disable-next-line no-restricted-syntax
91
+ get top() {
92
+ return this.y
93
+ }
94
+
80
95
  // eslint-disable-next-line no-restricted-syntax
81
96
  get midY() {
82
97
  return this.y + this.h / 2
@@ -87,6 +102,11 @@ export class Box {
87
102
  return this.y + this.h
88
103
  }
89
104
 
105
+ // eslint-disable-next-line no-restricted-syntax
106
+ get bottom() {
107
+ return this.y + this.h
108
+ }
109
+
90
110
  // eslint-disable-next-line no-restricted-syntax
91
111
  get width() {
92
112
  return this.w
@@ -157,12 +157,13 @@ export class Mat {
157
157
  return Mat.Compose(Mat.Translate(cx, cy!), rotationMatrix, Mat.Translate(-cx, -cy!))
158
158
  }
159
159
 
160
- static Scale(x: number, y: number): MatModel
161
- static Scale(x: number, y: number, cx: number, cy: number): MatModel
162
- static Scale(x: number, y: number, cx?: number, cy?: number): MatModel {
160
+ static Scale(x: number, y: number): Mat
161
+ static Scale(x: number, y: number, cx: number, cy: number): Mat
162
+ static Scale(x: number, y: number, cx?: number, cy?: number): Mat {
163
163
  const scaleMatrix = new Mat(x, 0, 0, y, 0, 0)
164
164
  if (cx === undefined) return scaleMatrix
165
- return Mat.Compose(Mat.Translate(cx, cy!), scaleMatrix, Mat.Translate(-cx, -cy!))
165
+
166
+ return Mat.Translate(cx, cy!).multiply(scaleMatrix).translate(-cx, -cy!)
166
167
  }
167
168
  static Multiply(m1: MatModel, m2: MatModel): MatModel {
168
169
  return {