@tldraw/editor 3.13.0-canary.ae83a75c91b6 → 3.13.0-canary.b29c8448ae7b

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 (180) hide show
  1. package/dist-cjs/index.d.ts +114 -112
  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 +36 -7
  9. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  10. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +14 -12
  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 +40 -15
  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 +1 -1
  22. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  23. package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js.map +2 -2
  24. package/dist-cjs/lib/exports/getSvgJsx.js +12 -3
  25. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  26. package/dist-cjs/lib/hooks/useDocumentEvents.js +3 -2
  27. package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
  28. package/dist-cjs/lib/hooks/useEditorComponents.js +16 -16
  29. package/dist-cjs/lib/hooks/useEditorComponents.js.map +2 -2
  30. package/dist-cjs/lib/license/LicenseManager.js +8 -1
  31. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  32. package/dist-cjs/lib/options.js.map +2 -2
  33. package/dist-cjs/lib/primitives/Box.js +16 -0
  34. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  35. package/dist-cjs/lib/primitives/Mat.js +1 -1
  36. package/dist-cjs/lib/primitives/Mat.js.map +2 -2
  37. package/dist-cjs/lib/primitives/Vec.js +20 -0
  38. package/dist-cjs/lib/primitives/Vec.js.map +2 -2
  39. package/dist-cjs/lib/primitives/geometry/Arc2d.js +2 -2
  40. package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
  41. package/dist-cjs/lib/primitives/geometry/Circle2d.js +1 -1
  42. package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +2 -2
  43. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +1 -1
  44. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
  45. package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js.map +2 -2
  46. package/dist-cjs/lib/primitives/geometry/Edge2d.js +1 -1
  47. package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
  48. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +2 -2
  49. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +91 -20
  50. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  51. package/dist-cjs/lib/primitives/geometry/Group2d.js +55 -2
  52. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  53. package/dist-cjs/lib/primitives/geometry/Point2d.js.map +2 -2
  54. package/dist-cjs/lib/primitives/geometry/Polyline2d.js.map +2 -2
  55. package/dist-cjs/lib/primitives/geometry/Stadium2d.js.map +2 -2
  56. package/dist-cjs/lib/utils/areShapesContentEqual.js +25 -0
  57. package/dist-cjs/lib/utils/areShapesContentEqual.js.map +7 -0
  58. package/dist-cjs/lib/utils/debug-flags.js +5 -2
  59. package/dist-cjs/lib/utils/debug-flags.js.map +2 -2
  60. package/dist-cjs/lib/utils/dom.js +3 -3
  61. package/dist-cjs/lib/utils/dom.js.map +2 -2
  62. package/dist-cjs/lib/utils/nearestMultiple.js +34 -0
  63. package/dist-cjs/lib/utils/nearestMultiple.js.map +7 -0
  64. package/dist-cjs/lib/utils/rotation.js +5 -5
  65. package/dist-cjs/lib/utils/rotation.js.map +2 -2
  66. package/dist-cjs/version.js +3 -3
  67. package/dist-cjs/version.js.map +1 -1
  68. package/dist-esm/index.d.mts +114 -112
  69. package/dist-esm/index.mjs +9 -41
  70. package/dist-esm/index.mjs.map +2 -2
  71. package/dist-esm/lib/TldrawEditor.mjs +2 -1
  72. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  73. package/dist-esm/lib/components/Shape.mjs +12 -8
  74. package/dist-esm/lib/components/Shape.mjs.map +2 -2
  75. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +36 -7
  76. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  77. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +14 -12
  78. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +2 -2
  79. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +17 -11
  80. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
  81. package/dist-esm/lib/components/default-components/DefaultSpinner.mjs +1 -1
  82. package/dist-esm/lib/components/default-components/DefaultSpinner.mjs.map +2 -2
  83. package/dist-esm/lib/editor/Editor.mjs +40 -15
  84. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  85. package/dist-esm/lib/editor/managers/SnapManager/HandleSnaps.mjs.map +2 -2
  86. package/dist-esm/lib/editor/managers/TextManager.mjs +10 -0
  87. package/dist-esm/lib/editor/managers/TextManager.mjs.map +2 -2
  88. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +1 -1
  89. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  90. package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs.map +2 -2
  91. package/dist-esm/lib/exports/getSvgJsx.mjs +12 -3
  92. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  93. package/dist-esm/lib/hooks/useDocumentEvents.mjs +3 -2
  94. package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
  95. package/dist-esm/lib/hooks/useEditorComponents.mjs +16 -18
  96. package/dist-esm/lib/hooks/useEditorComponents.mjs.map +2 -2
  97. package/dist-esm/lib/license/LicenseManager.mjs +8 -1
  98. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  99. package/dist-esm/lib/options.mjs.map +2 -2
  100. package/dist-esm/lib/primitives/Box.mjs +16 -0
  101. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  102. package/dist-esm/lib/primitives/Mat.mjs +1 -1
  103. package/dist-esm/lib/primitives/Mat.mjs.map +2 -2
  104. package/dist-esm/lib/primitives/Vec.mjs +20 -0
  105. package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
  106. package/dist-esm/lib/primitives/geometry/Arc2d.mjs +2 -2
  107. package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
  108. package/dist-esm/lib/primitives/geometry/Circle2d.mjs +1 -1
  109. package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +2 -2
  110. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +1 -1
  111. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
  112. package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs.map +2 -2
  113. package/dist-esm/lib/primitives/geometry/Edge2d.mjs +1 -1
  114. package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
  115. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +2 -2
  116. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +92 -21
  117. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  118. package/dist-esm/lib/primitives/geometry/Group2d.mjs +55 -2
  119. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  120. package/dist-esm/lib/primitives/geometry/Point2d.mjs.map +2 -2
  121. package/dist-esm/lib/primitives/geometry/Polyline2d.mjs.map +2 -2
  122. package/dist-esm/lib/primitives/geometry/Stadium2d.mjs.map +2 -2
  123. package/dist-esm/lib/utils/areShapesContentEqual.mjs +5 -0
  124. package/dist-esm/lib/utils/areShapesContentEqual.mjs.map +7 -0
  125. package/dist-esm/lib/utils/debug-flags.mjs +5 -2
  126. package/dist-esm/lib/utils/debug-flags.mjs.map +2 -2
  127. package/dist-esm/lib/utils/dom.mjs +3 -3
  128. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  129. package/dist-esm/lib/utils/nearestMultiple.mjs +14 -0
  130. package/dist-esm/lib/utils/nearestMultiple.mjs.map +7 -0
  131. package/dist-esm/lib/utils/rotation.mjs +5 -5
  132. package/dist-esm/lib/utils/rotation.mjs.map +2 -2
  133. package/dist-esm/version.mjs +3 -3
  134. package/dist-esm/version.mjs.map +1 -1
  135. package/editor.css +42 -4
  136. package/package.json +7 -7
  137. package/src/index.ts +16 -31
  138. package/src/lib/TldrawEditor.tsx +6 -1
  139. package/src/lib/components/Shape.tsx +14 -10
  140. package/src/lib/components/default-components/DefaultCanvas.tsx +41 -7
  141. package/src/lib/components/default-components/DefaultErrorFallback.tsx +25 -14
  142. package/src/lib/components/default-components/DefaultShapeIndicator.tsx +17 -8
  143. package/src/lib/components/default-components/DefaultSpinner.tsx +1 -1
  144. package/src/lib/editor/Editor.test.ts +1 -1
  145. package/src/lib/editor/Editor.ts +40 -15
  146. package/src/lib/editor/managers/SnapManager/HandleSnaps.ts +0 -1
  147. package/src/lib/editor/managers/TextManager.ts +12 -0
  148. package/src/lib/editor/shapes/ShapeUtil.ts +23 -3
  149. package/src/lib/editor/shapes/shared/getPerfectDashProps.ts +9 -9
  150. package/src/lib/exports/getSvgJsx.tsx +16 -7
  151. package/src/lib/hooks/useDocumentEvents.ts +7 -2
  152. package/src/lib/hooks/useEditorComponents.tsx +33 -32
  153. package/src/lib/license/LicenseManager.test.ts +40 -0
  154. package/src/lib/license/LicenseManager.ts +13 -1
  155. package/src/lib/options.ts +4 -0
  156. package/src/lib/primitives/Box.ts +20 -0
  157. package/src/lib/primitives/Mat.ts +5 -4
  158. package/src/lib/primitives/Vec.ts +23 -0
  159. package/src/lib/primitives/geometry/Arc2d.ts +5 -5
  160. package/src/lib/primitives/geometry/Circle2d.ts +4 -4
  161. package/src/lib/primitives/geometry/CubicBezier2d.ts +4 -4
  162. package/src/lib/primitives/geometry/CubicSpline2d.ts +3 -3
  163. package/src/lib/primitives/geometry/Edge2d.ts +3 -3
  164. package/src/lib/primitives/geometry/Ellipse2d.ts +3 -3
  165. package/src/lib/primitives/geometry/Geometry2d.test.ts +42 -0
  166. package/src/lib/primitives/geometry/Geometry2d.ts +123 -35
  167. package/src/lib/primitives/geometry/Group2d.ts +70 -7
  168. package/src/lib/primitives/geometry/Point2d.ts +2 -2
  169. package/src/lib/primitives/geometry/Polyline2d.ts +3 -3
  170. package/src/lib/primitives/geometry/Stadium2d.ts +3 -3
  171. package/src/lib/test/currentToolIdMask.test.ts +1 -1
  172. package/src/lib/test/user.test.ts +1 -1
  173. package/src/lib/utils/areShapesContentEqual.ts +4 -0
  174. package/src/lib/utils/debug-flags.ts +7 -2
  175. package/src/lib/utils/dom.ts +4 -4
  176. package/src/lib/utils/nearestMultiple.ts +13 -0
  177. package/src/lib/utils/rotation.ts +8 -6
  178. package/src/lib/utils/sync/LocalIndexedDb.test.ts +1 -1
  179. package/src/lib/utils/sync/TLLocalSyncClient.test.ts +1 -1
  180. package/src/version.ts +3 -3
@@ -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 */
@@ -244,7 +245,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
244
245
  *
245
246
  * @public
246
247
  */
247
- canEditInReadOnly(_shape: Shape): boolean {
248
+ canEditInReadonly(_shape: Shape): boolean {
248
249
  return false
249
250
  }
250
251
 
@@ -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) {
@@ -19,10 +19,7 @@ import { DefaultHandle, TLHandleProps } from '../components/default-components/D
19
19
  import { DefaultHandles, TLHandlesProps } from '../components/default-components/DefaultHandles'
20
20
  import { DefaultLoadingScreen } from '../components/default-components/DefaultLoadingScreen'
21
21
  import { DefaultScribble, TLScribbleProps } from '../components/default-components/DefaultScribble'
22
- import {
23
- DefaultSelectionBackground,
24
- TLSelectionBackgroundProps,
25
- } from '../components/default-components/DefaultSelectionBackground'
22
+ import { TLSelectionBackgroundProps } from '../components/default-components/DefaultSelectionBackground'
26
23
  import {
27
24
  DefaultSelectionForeground,
28
25
  TLSelectionForegroundProps,
@@ -51,29 +48,30 @@ import { useShallowObjectIdentity } from './useIdentity'
51
48
  /** @public */
52
49
  export interface TLEditorComponents {
53
50
  Background?: ComponentType | null
54
- SvgDefs?: ComponentType | null
55
51
  Brush?: ComponentType<TLBrushProps> | null
56
- ZoomBrush?: ComponentType<TLBrushProps> | null
57
- ShapeIndicators?: ComponentType | null
58
- ShapeIndicator?: ComponentType<TLShapeIndicatorProps> | null
59
- Cursor?: ComponentType<TLCursorProps> | null
60
52
  Canvas?: ComponentType<TLCanvasComponentProps> | null
61
53
  CollaboratorBrush?: ComponentType<TLBrushProps> | null
62
54
  CollaboratorCursor?: ComponentType<TLCursorProps> | null
63
55
  CollaboratorHint?: ComponentType<TLCollaboratorHintProps> | null
56
+ CollaboratorScribble?: ComponentType<TLScribbleProps> | null
64
57
  CollaboratorShapeIndicator?: ComponentType<TLShapeIndicatorProps> | null
58
+ Cursor?: ComponentType<TLCursorProps> | null
65
59
  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
60
  Handle?: ComponentType<TLHandleProps> | null
71
- Spinner?: ComponentType | null
72
- SelectionForeground?: ComponentType<TLSelectionForegroundProps> | null
73
- SelectionBackground?: ComponentType<TLSelectionBackgroundProps> | null
74
- OnTheCanvas?: ComponentType | null
61
+ Handles?: ComponentType<TLHandlesProps> | null
75
62
  InFrontOfTheCanvas?: ComponentType | null
76
63
  LoadingScreen?: ComponentType | null
64
+ OnTheCanvas?: ComponentType | null
65
+ Overlays?: ComponentType | null
66
+ Scribble?: ComponentType<TLScribbleProps> | null
67
+ SelectionBackground?: ComponentType<TLSelectionBackgroundProps> | null
68
+ SelectionForeground?: ComponentType<TLSelectionForegroundProps> | null
69
+ ShapeIndicator?: ComponentType<TLShapeIndicatorProps> | null
70
+ ShapeIndicators?: ComponentType | null
71
+ SnapIndicator?: ComponentType<TLSnapIndicatorProps> | null
72
+ Spinner?: ComponentType | null
73
+ SvgDefs?: ComponentType | null
74
+ ZoomBrush?: ComponentType<TLBrushProps> | null
77
75
 
78
76
  // These will always have defaults
79
77
  ErrorFallback?: TLErrorFallbackComponent
@@ -96,32 +94,35 @@ export function EditorComponentsProvider({
96
94
  const value = useMemo(
97
95
  (): Required<TLEditorComponents> => ({
98
96
  Background: DefaultBackground,
99
- SvgDefs: DefaultSvgDefs,
100
97
  Brush: DefaultBrush,
101
- ZoomBrush: DefaultBrush,
98
+ Canvas: DefaultCanvas,
102
99
  CollaboratorBrush: DefaultBrush,
103
- Cursor: DefaultCursor,
104
100
  CollaboratorCursor: DefaultCursor,
105
101
  CollaboratorHint: DefaultCollaboratorHint,
102
+ CollaboratorScribble: DefaultScribble,
106
103
  CollaboratorShapeIndicator: DefaultShapeIndicator,
104
+ Cursor: DefaultCursor,
107
105
  Grid: DefaultGrid,
106
+ Handle: DefaultHandle,
107
+ Handles: DefaultHandles,
108
+ InFrontOfTheCanvas: null,
109
+ LoadingScreen: DefaultLoadingScreen,
110
+ OnTheCanvas: null,
111
+ Overlays: null,
108
112
  Scribble: DefaultScribble,
113
+ SelectionBackground: null,
114
+ SelectionForeground: DefaultSelectionForeground,
115
+ ShapeIndicator: DefaultShapeIndicator,
116
+ ShapeIndicators: DefaultShapeIndicators,
109
117
  SnapIndicator: DefaultSnapIndicator,
110
- Handles: DefaultHandles,
111
- Handle: DefaultHandle,
112
- CollaboratorScribble: DefaultScribble,
118
+ Spinner: DefaultSpinner,
119
+ SvgDefs: DefaultSvgDefs,
120
+ ZoomBrush: DefaultBrush,
121
+
113
122
  ErrorFallback: DefaultErrorFallback,
114
123
  ShapeErrorFallback: DefaultShapeErrorFallback,
115
124
  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,
125
+
125
126
  ..._overrides,
126
127
  }),
127
128
  [_overrides]
@@ -317,6 +317,46 @@ describe('LicenseManager', () => {
317
317
  expect(result.isDomainValid).toBe(false)
318
318
  })
319
319
 
320
+ it('Succeeds if it is a vscode extension', async () => {
321
+ // @ts-ignore
322
+ delete window.location
323
+ // @ts-ignore
324
+ window.location = new URL(
325
+ 'vscode-webview:vscode-webview://1ipd8pun8ud7nd7hv9d112g7evi7m10vak9vviuvia66ou6aibp3/index.html?id=6ec2dc7a-afe9-45d9-bd71-1749f9568d28&origin=955b256f-37e1-4a72-a2f4-ad633e88239c&swVersion=4&extensionId=tldraw-org.tldraw-vscode&platform=electron&vscode-resource-base-authority=vscode-resource.vscode-cdn.net&parentOrigin=vscode-file%3A%2F%2Fvscode-app'
326
+ )
327
+
328
+ const permissiveHostsInfo = JSON.parse(STANDARD_LICENSE_INFO)
329
+ permissiveHostsInfo[PROPERTIES.HOSTS] = ['tldraw-org.tldraw-vscode']
330
+ const permissiveLicenseKey = await generateLicenseKey(
331
+ JSON.stringify(permissiveHostsInfo),
332
+ keyPair
333
+ )
334
+ const result = (await licenseManager.getLicenseFromKey(
335
+ permissiveLicenseKey
336
+ )) as ValidLicenseKeyResult
337
+ expect(result.isDomainValid).toBe(true)
338
+ })
339
+
340
+ it('Fails if it is a vscode extension with the wrong id', async () => {
341
+ // @ts-ignore
342
+ delete window.location
343
+ // @ts-ignore
344
+ window.location = new URL(
345
+ 'vscode-webview:vscode-webview://1ipd8pun8ud7nd7hv9d112g7evi7m10vak9vviuvia66ou6aibp3/index.html?id=6ec2dc7a-afe9-45d9-bd71-1749f9568d28&origin=955b256f-37e1-4a72-a2f4-ad633e88239c&swVersion=4&extensionId=tldraw-org.tldraw-vscode&platform=electron&vscode-resource-base-authority=vscode-resource.vscode-cdn.net&parentOrigin=vscode-file%3A%2F%2Fvscode-app'
346
+ )
347
+
348
+ const permissiveHostsInfo = JSON.parse(STANDARD_LICENSE_INFO)
349
+ permissiveHostsInfo[PROPERTIES.HOSTS] = ['blah-org.blah-vscode']
350
+ const permissiveLicenseKey = await generateLicenseKey(
351
+ JSON.stringify(permissiveHostsInfo),
352
+ keyPair
353
+ )
354
+ const result = (await licenseManager.getLicenseFromKey(
355
+ permissiveLicenseKey
356
+ )) as ValidLicenseKeyResult
357
+ expect(result.isDomainValid).toBe(false)
358
+ })
359
+
320
360
  it('Checks for internal license', async () => {
321
361
  const internalLicenseInfo = JSON.parse(STANDARD_LICENSE_INFO)
322
362
  internalLicenseInfo[PROPERTIES.FLAGS] = FLAGS.INTERNAL_LICENSE
@@ -111,7 +111,10 @@ export class LicenseManager {
111
111
  if (testEnvironment === 'production') return false
112
112
 
113
113
  // If we are using https on a non-localhost domain we assume it's a production env and a development one otherwise
114
- return window.location.protocol !== 'https:' || window.location.hostname === 'localhost'
114
+ return (
115
+ !['https:', 'vscode-webview:'].includes(window.location.protocol) ||
116
+ window.location.hostname === 'localhost'
117
+ )
115
118
  }
116
119
 
117
120
  private async extractLicenseKey(licenseKey: string): Promise<LicenseInfo> {
@@ -250,6 +253,15 @@ export class LicenseManager {
250
253
  return globToRegex.test(currentHostname) || globToRegex.test(`www.${currentHostname}`)
251
254
  }
252
255
 
256
+ // VSCode support
257
+ if (window.location.protocol === 'vscode-webview:') {
258
+ const currentUrl = new URL(window.location.href)
259
+ const extensionId = currentUrl.searchParams.get('extensionId')
260
+ if (normalizedHost === extensionId) {
261
+ return true
262
+ }
263
+ }
264
+
253
265
  return false
254
266
  })
255
267
  }
@@ -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 {
@@ -319,6 +319,11 @@ export class Vec {
319
319
  return ((A.y - B.y) ** 2 + (A.x - B.x) ** 2) ** 0.5
320
320
  }
321
321
 
322
+ // Get the Manhattan distance between two points.
323
+ static ManhattanDist(A: VecLike, B: VecLike): number {
324
+ return Math.abs(A.x - B.x) + Math.abs(A.y - B.y)
325
+ }
326
+
322
327
  // Get whether a distance between two points is less than a number. This is faster to calulate than using `Vec.Dist(a, b) < n`.
323
328
  static DistMin(A: VecLike, B: VecLike, n: number): boolean {
324
329
  return (A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y) < n ** 2
@@ -465,10 +470,28 @@ export class Vec {
465
470
  return isNaN(A.x) || isNaN(A.y)
466
471
  }
467
472
 
473
+ /**
474
+ * Get the angle from position A to position B.
475
+ */
468
476
  static Angle(A: VecLike, B: VecLike): number {
469
477
  return Math.atan2(B.y - A.y, B.x - A.x)
470
478
  }
471
479
 
480
+ /**
481
+ * Get the angle between vector A and vector B. This will return the smallest angle between the
482
+ * two vectors, between -π and π. The sign indicates direction of angle.
483
+ */
484
+ static AngleBetween(A: VecLike, B: VecLike): number {
485
+ const p = A.x * B.x + A.y * B.y
486
+ const n = Math.sqrt(
487
+ (Math.pow(A.x, 2) + Math.pow(A.y, 2)) * (Math.pow(B.x, 2) + Math.pow(B.y, 2))
488
+ )
489
+ const sign = A.x * B.y - A.y * B.x < 0 ? -1 : 1
490
+ const angle = sign * Math.acos(p / n)
491
+
492
+ return angle
493
+ }
494
+
472
495
  /**
473
496
  * Linearly interpolate between two points.
474
497
  * @param A - The first point.
@@ -1,4 +1,4 @@
1
- import { Vec } from '../Vec'
1
+ import { Vec, VecLike } from '../Vec'
2
2
  import { intersectLineSegmentCircle } from '../intersect'
3
3
  import { getArcMeasure, getPointInArcT, getPointOnCircle } from '../utils'
4
4
  import { Geometry2d, Geometry2dOptions } from './Geometry2d'
@@ -44,14 +44,14 @@ export class Arc2d extends Geometry2d {
44
44
  this._center = center
45
45
  }
46
46
 
47
- nearestPoint(point: Vec): Vec {
47
+ nearestPoint(point: VecLike): Vec {
48
48
  const { _center, measure, radius, angleEnd, angleStart, start: A, end: B } = this
49
49
  const t = getPointInArcT(measure, angleStart, angleEnd, _center.angle(point))
50
50
  if (t <= 0) return A
51
51
  if (t >= 1) return B
52
52
 
53
53
  // Get the point (P) on the arc, then pick the nearest of A, B, and P
54
- const P = _center.clone().add(point.clone().sub(_center).uni().mul(radius))
54
+ const P = Vec.Sub(point, _center).uni().mul(radius).add(_center)
55
55
 
56
56
  let nearest: Vec | undefined
57
57
  let dist = Infinity
@@ -67,7 +67,7 @@ export class Arc2d extends Geometry2d {
67
67
  return nearest
68
68
  }
69
69
 
70
- hitTestLineSegment(A: Vec, B: Vec): boolean {
70
+ hitTestLineSegment(A: VecLike, B: VecLike): boolean {
71
71
  const { _center, radius, measure, angleStart, angleEnd } = this
72
72
  const intersection = intersectLineSegmentCircle(A, B, _center, radius)
73
73
  if (intersection === null) return false
@@ -95,6 +95,6 @@ export class Arc2d extends Geometry2d {
95
95
  }
96
96
 
97
97
  override getLength() {
98
- return this.measure * this.radius
98
+ return Math.abs(this.measure * this.radius)
99
99
  }
100
100
  }
@@ -1,5 +1,5 @@
1
1
  import { Box } from '../Box'
2
- import { Vec } from '../Vec'
2
+ import { Vec, VecLike } from '../Vec'
3
3
  import { intersectLineSegmentCircle } from '../intersect'
4
4
  import { PI2, getPointOnCircle } from '../utils'
5
5
  import { Geometry2d, Geometry2dOptions } from './Geometry2d'
@@ -43,13 +43,13 @@ export class Circle2d extends Geometry2d {
43
43
  return vertices
44
44
  }
45
45
 
46
- nearestPoint(point: Vec): Vec {
46
+ nearestPoint(point: VecLike): Vec {
47
47
  const { _center, radius } = this
48
48
  if (_center.equals(point)) return Vec.AddXY(_center, radius, 0)
49
- return _center.clone().add(point.clone().sub(_center).uni().mul(radius))
49
+ return Vec.Sub(point, _center).uni().mul(radius).add(_center)
50
50
  }
51
51
 
52
- hitTestLineSegment(A: Vec, B: Vec, distance = 0): boolean {
52
+ hitTestLineSegment(A: VecLike, B: VecLike, distance = 0): boolean {
53
53
  const { _center, radius } = this
54
54
  return intersectLineSegmentCircle(A, B, _center, radius + distance) !== null
55
55
  }
@@ -1,5 +1,5 @@
1
- import { Vec } from '../Vec'
2
- import { Geometry2dOptions } from './Geometry2d'
1
+ import { Vec, VecLike } from '../Vec'
2
+ import { Geometry2dFilters, Geometry2dOptions } from './Geometry2d'
3
3
  import { Polyline2d } from './Polyline2d'
4
4
 
5
5
  /** @public */
@@ -52,7 +52,7 @@ export class CubicBezier2d extends Polyline2d {
52
52
  return CubicBezier2d.GetAtT(this, 0.5)
53
53
  }
54
54
 
55
- nearestPoint(A: Vec): Vec {
55
+ nearestPoint(A: VecLike): Vec {
56
56
  let nearest: Vec | undefined
57
57
  let dist = Infinity
58
58
  let d: number
@@ -89,7 +89,7 @@ export class CubicBezier2d extends Polyline2d {
89
89
  )
90
90
  }
91
91
 
92
- override getLength(precision = 32) {
92
+ override getLength(filters?: Geometry2dFilters, precision = 32) {
93
93
  let n1: Vec,
94
94
  p1 = this.a,
95
95
  length = 0
@@ -1,4 +1,4 @@
1
- import { Vec } from '../Vec'
1
+ import { Vec, VecLike } from '../Vec'
2
2
  import { CubicBezier2d } from './CubicBezier2d'
3
3
  import { Geometry2d, Geometry2dOptions } from './Geometry2d'
4
4
 
@@ -58,7 +58,7 @@ export class CubicSpline2d extends Geometry2d {
58
58
  return vertices
59
59
  }
60
60
 
61
- nearestPoint(A: Vec): Vec {
61
+ nearestPoint(A: VecLike): Vec {
62
62
  let nearest: Vec | undefined
63
63
  let dist = Infinity
64
64
  let d: number
@@ -75,7 +75,7 @@ export class CubicSpline2d extends Geometry2d {
75
75
  return nearest
76
76
  }
77
77
 
78
- hitTestLineSegment(A: Vec, B: Vec): boolean {
78
+ hitTestLineSegment(A: VecLike, B: VecLike): boolean {
79
79
  return this.segments.some((segment) => segment.hitTestLineSegment(A, B))
80
80
  }
81
81
 
@@ -1,5 +1,5 @@
1
- import { Vec } from '../Vec'
2
1
  import { linesIntersect } from '../intersect'
2
+ import { Vec, VecLike } from '../Vec'
3
3
  import { Geometry2d } from './Geometry2d'
4
4
 
5
5
  /** @public */
@@ -34,7 +34,7 @@ export class Edge2d extends Geometry2d {
34
34
  return [this.start, this.end]
35
35
  }
36
36
 
37
- override nearestPoint(point: Vec): Vec {
37
+ override nearestPoint(point: VecLike): Vec {
38
38
  const { start, end, d, u, ul: l } = this
39
39
  if (d.len() === 0) return start // start and end are the same
40
40
  if (l === 0) return start // no length in the unit vector
@@ -48,7 +48,7 @@ export class Edge2d extends Geometry2d {
48
48
  return new Vec(cx, cy)
49
49
  }
50
50
 
51
- override hitTestLineSegment(A: Vec, B: Vec, distance = 0): boolean {
51
+ override hitTestLineSegment(A: VecLike, B: VecLike, distance = 0): boolean {
52
52
  return (
53
53
  linesIntersect(A, B, this.start, this.end) || this.distanceToLineSegment(A, B) <= distance
54
54
  )
@@ -1,5 +1,5 @@
1
1
  import { Box } from '../Box'
2
- import { Vec } from '../Vec'
2
+ import { Vec, VecLike } from '../Vec'
3
3
  import { PI, PI2, perimeterOfEllipse } from '../utils'
4
4
  import { Edge2d } from './Edge2d'
5
5
  import { Geometry2d, Geometry2dOptions } from './Geometry2d'
@@ -73,7 +73,7 @@ export class Ellipse2d extends Geometry2d {
73
73
  return vertices
74
74
  }
75
75
 
76
- nearestPoint(A: Vec): Vec {
76
+ nearestPoint(A: VecLike): Vec {
77
77
  let nearest: Vec | undefined
78
78
  let dist = Infinity
79
79
  let d: number
@@ -90,7 +90,7 @@ export class Ellipse2d extends Geometry2d {
90
90
  return nearest
91
91
  }
92
92
 
93
- hitTestLineSegment(A: Vec, B: Vec): boolean {
93
+ hitTestLineSegment(A: VecLike, B: VecLike): boolean {
94
94
  return this.edges.some((edge) => edge.hitTestLineSegment(A, B))
95
95
  }
96
96