@tldraw/editor 5.1.1 → 5.2.0-canary.019da1aa690a

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 (171) hide show
  1. package/README.md +7 -1
  2. package/dist-cjs/index.d.ts +52 -50
  3. package/dist-cjs/index.js +4 -4
  4. package/dist-cjs/index.js.map +2 -2
  5. package/dist-cjs/lib/components/MenuClickCapture.js +8 -5
  6. package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
  7. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +4 -1
  8. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +3 -3
  9. package/dist-cjs/lib/components/default-components/DefaultLoadingScreen.js +2 -2
  10. package/dist-cjs/lib/components/default-components/DefaultLoadingScreen.js.map +2 -2
  11. package/dist-cjs/lib/components/default-components/DefaultShapeErrorFallback.js +1 -1
  12. package/dist-cjs/lib/components/default-components/DefaultShapeErrorFallback.js.map +3 -3
  13. package/dist-cjs/lib/components/default-components/DefaultSvgDefs.js +2 -2
  14. package/dist-cjs/lib/components/default-components/DefaultSvgDefs.js.map +2 -2
  15. package/dist-cjs/lib/editor/Editor.js +121 -55
  16. package/dist-cjs/lib/editor/Editor.js.map +3 -3
  17. package/dist-cjs/lib/editor/derivations/bindingsIndex.js +2 -2
  18. package/dist-cjs/lib/editor/derivations/bindingsIndex.js.map +2 -2
  19. package/dist-cjs/lib/editor/derivations/parentsToChildren.js +2 -2
  20. package/dist-cjs/lib/editor/derivations/parentsToChildren.js.map +2 -2
  21. package/dist-cjs/lib/editor/derivations/shapeIdsInCurrentPage.js +2 -2
  22. package/dist-cjs/lib/editor/derivations/shapeIdsInCurrentPage.js.map +2 -2
  23. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js +8 -58
  24. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +2 -2
  25. package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js +3 -3
  26. package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js.map +2 -2
  27. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js +1 -2
  28. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js.map +2 -2
  29. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js +24 -2
  30. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +2 -2
  31. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js +14 -3
  32. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js.map +2 -2
  33. package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js +4 -2
  34. package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js.map +2 -2
  35. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +7 -3
  36. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
  37. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js +0 -1
  38. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +2 -2
  39. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +15 -2
  40. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  41. package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js +79 -0
  42. package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js.map +7 -0
  43. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +3 -0
  44. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +2 -2
  45. package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
  46. package/dist-cjs/lib/editor/types/event-types.js +0 -2
  47. package/dist-cjs/lib/editor/types/event-types.js.map +2 -2
  48. package/dist-cjs/lib/hooks/useCanvasEvents.js +14 -7
  49. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  50. package/dist-cjs/lib/hooks/usePresence.js.map +2 -2
  51. package/dist-cjs/lib/license/LicenseProvider.js +3 -1
  52. package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
  53. package/dist-cjs/lib/primitives/utils.js +2 -2
  54. package/dist-cjs/lib/primitives/utils.js.map +2 -2
  55. package/dist-cjs/lib/utils/dom.js +5 -3
  56. package/dist-cjs/lib/utils/dom.js.map +2 -2
  57. package/dist-cjs/lib/utils/getPointerInfo.js +2 -1
  58. package/dist-cjs/lib/utils/getPointerInfo.js.map +2 -2
  59. package/dist-cjs/lib/utils/pointer.js +32 -0
  60. package/dist-cjs/lib/utils/pointer.js.map +7 -0
  61. package/dist-cjs/version.js +3 -3
  62. package/dist-cjs/version.js.map +1 -1
  63. package/dist-esm/index.d.mts +52 -50
  64. package/dist-esm/index.mjs +5 -7
  65. package/dist-esm/index.mjs.map +2 -2
  66. package/dist-esm/lib/components/MenuClickCapture.mjs +8 -5
  67. package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
  68. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +4 -1
  69. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +3 -3
  70. package/dist-esm/lib/components/default-components/DefaultLoadingScreen.mjs +2 -2
  71. package/dist-esm/lib/components/default-components/DefaultLoadingScreen.mjs.map +2 -2
  72. package/dist-esm/lib/components/default-components/DefaultShapeErrorFallback.mjs +1 -1
  73. package/dist-esm/lib/components/default-components/DefaultShapeErrorFallback.mjs.map +3 -3
  74. package/dist-esm/lib/components/default-components/DefaultSvgDefs.mjs +2 -2
  75. package/dist-esm/lib/components/default-components/DefaultSvgDefs.mjs.map +2 -2
  76. package/dist-esm/lib/editor/Editor.mjs +121 -55
  77. package/dist-esm/lib/editor/Editor.mjs.map +3 -3
  78. package/dist-esm/lib/editor/derivations/bindingsIndex.mjs +2 -2
  79. package/dist-esm/lib/editor/derivations/bindingsIndex.mjs.map +2 -2
  80. package/dist-esm/lib/editor/derivations/parentsToChildren.mjs +2 -2
  81. package/dist-esm/lib/editor/derivations/parentsToChildren.mjs.map +2 -2
  82. package/dist-esm/lib/editor/derivations/shapeIdsInCurrentPage.mjs +2 -2
  83. package/dist-esm/lib/editor/derivations/shapeIdsInCurrentPage.mjs.map +2 -2
  84. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs +8 -58
  85. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +2 -2
  86. package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs +3 -3
  87. package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs.map +2 -2
  88. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs +1 -2
  89. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +2 -2
  90. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs +24 -2
  91. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +2 -2
  92. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs +14 -3
  93. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs.map +2 -2
  94. package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs +4 -2
  95. package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs.map +2 -2
  96. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +7 -3
  97. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
  98. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs +0 -1
  99. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +2 -2
  100. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +15 -2
  101. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  102. package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs +59 -0
  103. package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs.map +7 -0
  104. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +3 -0
  105. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +2 -2
  106. package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
  107. package/dist-esm/lib/editor/types/event-types.mjs +0 -2
  108. package/dist-esm/lib/editor/types/event-types.mjs.map +2 -2
  109. package/dist-esm/lib/hooks/useCanvasEvents.mjs +14 -7
  110. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  111. package/dist-esm/lib/hooks/usePresence.mjs.map +2 -2
  112. package/dist-esm/lib/license/LicenseProvider.mjs +3 -1
  113. package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
  114. package/dist-esm/lib/primitives/utils.mjs +2 -2
  115. package/dist-esm/lib/primitives/utils.mjs.map +2 -2
  116. package/dist-esm/lib/utils/dom.mjs +5 -3
  117. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  118. package/dist-esm/lib/utils/getPointerInfo.mjs +2 -1
  119. package/dist-esm/lib/utils/getPointerInfo.mjs.map +2 -2
  120. package/dist-esm/lib/utils/pointer.mjs +12 -0
  121. package/dist-esm/lib/utils/pointer.mjs.map +7 -0
  122. package/dist-esm/version.mjs +3 -3
  123. package/dist-esm/version.mjs.map +1 -1
  124. package/editor.css +5 -3
  125. package/package.json +11 -8
  126. package/src/index.ts +2 -5
  127. package/src/lib/components/MenuClickCapture.tsx +8 -4
  128. package/src/lib/components/default-components/DefaultErrorFallback.tsx +4 -1
  129. package/src/lib/components/default-components/DefaultLoadingScreen.tsx +1 -1
  130. package/src/lib/components/default-components/DefaultShapeErrorFallback.tsx +4 -3
  131. package/src/lib/components/default-components/DefaultSvgDefs.tsx +1 -1
  132. package/src/lib/editor/Editor.ts +168 -72
  133. package/src/lib/editor/derivations/bindingsIndex.ts +1 -1
  134. package/src/lib/editor/derivations/parentsToChildren.ts +1 -1
  135. package/src/lib/editor/derivations/shapeIdsInCurrentPage.ts +1 -1
  136. package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +54 -74
  137. package/src/lib/editor/managers/ClickManager/ClickManager.ts +15 -65
  138. package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.test.ts +43 -16
  139. package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.ts +8 -5
  140. package/src/lib/editor/managers/FocusManager/FocusManager.test.ts +4 -4
  141. package/src/lib/editor/managers/FocusManager/FocusManager.ts +1 -2
  142. package/src/lib/editor/managers/FontManager/FontManager.test.ts +13 -9
  143. package/src/lib/editor/managers/HistoryManager/HistoryManager.test.ts +32 -0
  144. package/src/lib/editor/managers/HistoryManager/HistoryManager.ts +34 -4
  145. package/src/lib/editor/managers/InputsManager/InputsManager.test.ts +61 -0
  146. package/src/lib/editor/managers/InputsManager/InputsManager.ts +16 -4
  147. package/src/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.ts +9 -2
  148. package/src/lib/editor/managers/TextManager/TextManager.test.ts +16 -14
  149. package/src/lib/editor/managers/TextManager/TextManager.ts +17 -2
  150. package/src/lib/editor/managers/TickManager/TickManager.test.ts +0 -40
  151. package/src/lib/editor/managers/TickManager/TickManager.ts +0 -1
  152. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +12 -2
  153. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +27 -2
  154. package/src/lib/editor/overlays/strokeShapeIndicators.ts +86 -0
  155. package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +4 -0
  156. package/src/lib/editor/tools/StateNode.ts +0 -2
  157. package/src/lib/editor/types/event-types.ts +2 -6
  158. package/src/lib/hooks/useCanvasEvents.ts +19 -12
  159. package/src/lib/hooks/usePresence.ts +2 -2
  160. package/src/lib/license/LicenseProvider.tsx +3 -1
  161. package/src/lib/primitives/utils.ts +1 -1
  162. package/src/lib/utils/dom.ts +5 -3
  163. package/src/lib/utils/getPointerInfo.ts +2 -1
  164. package/src/lib/utils/pointer.test.ts +48 -0
  165. package/src/lib/utils/pointer.ts +18 -0
  166. package/src/version.ts +3 -3
  167. package/dist-cjs/lib/editor/overlays/ShapeIndicatorOverlayUtil.js +0 -161
  168. package/dist-cjs/lib/editor/overlays/ShapeIndicatorOverlayUtil.js.map +0 -7
  169. package/dist-esm/lib/editor/overlays/ShapeIndicatorOverlayUtil.mjs +0 -141
  170. package/dist-esm/lib/editor/overlays/ShapeIndicatorOverlayUtil.mjs.map +0 -7
  171. package/src/lib/editor/overlays/ShapeIndicatorOverlayUtil.ts +0 -216
@@ -8,6 +8,7 @@ import {
8
8
  setPointerCapture,
9
9
  } from '../utils/dom'
10
10
  import { getPointerInfo } from '../utils/getPointerInfo'
11
+ import { getPointerEventButton, isSecondaryClickEvent } from '../utils/pointer'
11
12
  import { useEditor } from './useEditor'
12
13
 
13
14
  export function useCanvasEvents() {
@@ -17,12 +18,16 @@ export function useCanvasEvents() {
17
18
 
18
19
  const events = useMemo(
19
20
  function canvasEvents() {
21
+ let isSecondaryClickPointerDown = false
22
+
20
23
  function onPointerDown(e: React.PointerEvent) {
21
24
  if (editor.wasEventAlreadyHandled(e)) return
25
+ const button = getPointerEventButton(e)
26
+ isSecondaryClickPointerDown = button === 2
22
27
 
23
28
  // With right-click panning disabled, fire right_click on press and let the
24
29
  // native contextmenu through so the menu opens at the pointer-down location.
25
- if (e.button === 2 && !editor.options.rightClickPanning) {
30
+ if (button === 2 && !editor.options.rightClickPanning) {
26
31
  editor.dispatch({
27
32
  type: 'pointer',
28
33
  target: 'canvas',
@@ -32,7 +37,7 @@ export function useCanvasEvents() {
32
37
  return
33
38
  }
34
39
 
35
- if (e.button !== 0 && e.button !== 1 && e.button !== 2 && e.button !== 5) return
40
+ if (button !== 0 && button !== 1 && button !== 2 && button !== 5) return
36
41
 
37
42
  setPointerCapture(e.currentTarget, e)
38
43
 
@@ -46,12 +51,13 @@ export function useCanvasEvents() {
46
51
 
47
52
  function onPointerUp(e: React.PointerEvent) {
48
53
  if (editor.wasEventAlreadyHandled(e)) return
49
- if (e.button !== 0 && e.button !== 1 && e.button !== 2 && e.button !== 5) return
54
+ const button = isSecondaryClickPointerDown ? 2 : getPointerEventButton(e)
55
+ if (button !== 0 && button !== 1 && button !== 2 && button !== 5) return
50
56
 
51
57
  const rightClickPanning = editor.options.rightClickPanning
52
58
  // Check before dispatch (which resets isPanning)
53
59
  const wasRightClickPanning =
54
- rightClickPanning && e.button === 2 && editor.inputs.getIsPanning()
60
+ rightClickPanning && button === 2 && editor.inputs.getIsPanning()
55
61
 
56
62
  releasePointerCapture(e.currentTarget, e)
57
63
 
@@ -60,10 +66,11 @@ export function useCanvasEvents() {
60
66
  target: 'canvas',
61
67
  name: 'pointer_up',
62
68
  ...getPointerInfo(editor, e),
69
+ button,
63
70
  })
64
71
 
65
72
  // Static right-click: fire contextmenu at the pointer-up location
66
- if (rightClickPanning && e.button === 2 && !wasRightClickPanning) {
73
+ if (rightClickPanning && button === 2 && !wasRightClickPanning) {
67
74
  const contextMenuEvent = new PointerEvent('contextmenu', {
68
75
  bubbles: true,
69
76
  clientX: e.clientX,
@@ -76,6 +83,7 @@ export function useCanvasEvents() {
76
83
  })
77
84
  e.currentTarget.dispatchEvent(contextMenuEvent)
78
85
  }
86
+ isSecondaryClickPointerDown = false
79
87
  }
80
88
 
81
89
  function onPointerEnter(e: React.PointerEvent) {
@@ -171,16 +179,15 @@ export function useCanvasEvents() {
171
179
  // Synthetic events — our own dispatch from onPointerUp, or tests using
172
180
  // fireEvent.contextMenu — pass through so Radix can open the menu.
173
181
  if (!e.nativeEvent.isTrusted) return
174
- // Only suppress the native browser contextmenu when it follows a real
175
- // right-click (button=2 with no ctrl modifier). For those, our pointer
176
- // handling has already decided what to do (either we'll dispatch a
177
- // synthetic contextmenu on pointerup to open the menu at the release
178
- // position, or we panned and don't want a menu at all).
182
+ // Only suppress the native browser contextmenu when it follows a
183
+ // secondary click. For those, our pointer handling has already
184
+ // decided what to do (either we'll dispatch a synthetic contextmenu on
185
+ // pointerup to open the menu at the release position, or we panned and
186
+ // don't want a menu at all).
179
187
  //
180
188
  // Other contextmenu sources must reach Radix so the menu opens:
181
- // - ctrl+click on macOS (button=0, or button=2 with ctrlKey=true)
182
189
  // - long-press on touch devices (button=0, pointerType=touch)
183
- if (e.button !== 2 || e.ctrlKey) return
190
+ if (!isSecondaryClickEvent(e)) return
184
191
  preventDefault(e)
185
192
  }
186
193
 
@@ -1,5 +1,5 @@
1
1
  import { useValue } from '@tldraw/state-react'
2
- import { TLInstancePresence } from '@tldraw/tlschema'
2
+ import { TLInstancePresence, TLUserId } from '@tldraw/tlschema'
3
3
  import { useEditor } from './useEditor'
4
4
 
5
5
  // TODO: maybe move this to a computed property on the App class?
@@ -7,7 +7,7 @@ import { useEditor } from './useEditor'
7
7
  * @returns The latest presence of the user matching userId
8
8
  * @public
9
9
  */
10
- export function usePresence(userId: string): TLInstancePresence | null {
10
+ export function usePresence(userId: TLUserId): TLInstancePresence | null {
11
11
  const editor = useEditor()
12
12
 
13
13
  const latestPresence = useValue(
@@ -6,7 +6,9 @@ import { LicenseManager } from './LicenseManager'
6
6
  export const LicenseContext = createContext({} as LicenseManager)
7
7
 
8
8
  /** @internal */
9
- export const useLicenseContext = () => useContext(LicenseContext)
9
+ export function useLicenseContext() {
10
+ return useContext(LicenseContext)
11
+ }
10
12
 
11
13
  function shouldHideEditorAfterDelay(licenseState: string): boolean {
12
14
  return licenseState === 'expired' || licenseState === 'unlicensed-production'
@@ -363,7 +363,7 @@ export function toFixed(v: number) {
363
363
  * Check if a float is safe to use. ie: Not too big or small.
364
364
  * @public
365
365
  */
366
- export const isSafeFloat = (n: number) => {
366
+ export function isSafeFloat(n: number) {
367
367
  return Math.abs(n) < Number.MAX_SAFE_INTEGER
368
368
  }
369
369
 
@@ -86,14 +86,16 @@ export function releasePointerCapture(
86
86
  *
87
87
  * @public
88
88
  */
89
- export const stopEventPropagation = (e: any) => e.stopPropagation()
89
+ export function stopEventPropagation(e: any) {
90
+ return e.stopPropagation()
91
+ }
90
92
 
91
93
  /** @internal */
92
- export const setStyleProperty = (
94
+ export function setStyleProperty(
93
95
  elm: HTMLElement | null,
94
96
  property: string,
95
97
  value: string | number
96
- ) => {
98
+ ) {
97
99
  if (!elm) return
98
100
  elm.style.setProperty(property, String(value))
99
101
  }
@@ -1,6 +1,7 @@
1
1
  import type React from 'react'
2
2
  import { Editor } from '../editor/Editor'
3
3
  import { isAccelKey } from './keyboard'
4
+ import { getPointerEventButton } from './pointer'
4
5
 
5
6
  /** @public */
6
7
  export function getPointerInfo(editor: Editor, e: React.PointerEvent | PointerEvent) {
@@ -18,7 +19,7 @@ export function getPointerInfo(editor: Editor, e: React.PointerEvent | PointerEv
18
19
  metaKey: e.metaKey,
19
20
  accelKey: isAccelKey(e),
20
21
  pointerId: e.pointerId,
21
- button: e.button,
22
+ button: getPointerEventButton(e),
22
23
  isPen: e.pointerType === 'pen',
23
24
  }
24
25
  }
@@ -0,0 +1,48 @@
1
+ import { tlenv } from '../globals/environment'
2
+ import { TestEditor } from '../test/TestEditor'
3
+ import { getPointerInfo } from './getPointerInfo'
4
+ import { isSecondaryClickEvent } from './pointer'
5
+
6
+ const originalIsDarwin = tlenv.isDarwin
7
+
8
+ afterEach(() => {
9
+ tlenv.isDarwin = originalIsDarwin
10
+ })
11
+
12
+ describe('isSecondaryClickEvent', () => {
13
+ it('treats ctrl + left-click as a secondary click on macOS', () => {
14
+ tlenv.isDarwin = true
15
+ expect(isSecondaryClickEvent({ button: 0, ctrlKey: true, metaKey: false })).toBe(true)
16
+ })
17
+
18
+ it('does not treat ctrl + left-click as a secondary click off macOS', () => {
19
+ tlenv.isDarwin = false
20
+ expect(isSecondaryClickEvent({ button: 0, ctrlKey: true, metaKey: false })).toBe(false)
21
+ })
22
+ })
23
+
24
+ describe('ctrl + left-click on macOS fires as a right-click (regression)', () => {
25
+ // Regression test for https://github.com/tldraw/tldraw/issues/8217
26
+ // On macOS, a ctrl + left-click should be treated as a right-click
27
+ let editor: TestEditor
28
+
29
+ beforeEach(() => {
30
+ editor = new TestEditor()
31
+ })
32
+
33
+ afterEach(() => {
34
+ editor.dispose()
35
+ })
36
+
37
+ it('reports button 2 in the dispatched pointer info on macOS', () => {
38
+ tlenv.isDarwin = true
39
+ const event = new PointerEvent('pointerdown', { button: 0, ctrlKey: true })
40
+ expect(getPointerInfo(editor, event).button).toBe(2)
41
+ })
42
+
43
+ it('stays button 0 for the same gesture off macOS', () => {
44
+ tlenv.isDarwin = false
45
+ const event = new PointerEvent('pointerdown', { button: 0, ctrlKey: true })
46
+ expect(getPointerInfo(editor, event).button).toBe(0)
47
+ })
48
+ })
@@ -0,0 +1,18 @@
1
+ import { tlenv } from '../globals/environment'
2
+
3
+ /** @internal */
4
+ interface PointerLike {
5
+ button: number
6
+ ctrlKey: boolean
7
+ metaKey: boolean
8
+ }
9
+
10
+ /** @internal */
11
+ export function isSecondaryClickEvent(e: PointerLike) {
12
+ return e.button === 2 || (tlenv.isDarwin && e.button === 0 && e.ctrlKey && !e.metaKey)
13
+ }
14
+
15
+ /** @internal */
16
+ export function getPointerEventButton(e: PointerLike) {
17
+ return isSecondaryClickEvent(e) ? 2 : e.button
18
+ }
package/src/version.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  // This file is automatically generated by internal/scripts/refresh-assets.ts.
2
2
  // Do not edit manually. Or do, I'm a comment, not a cop.
3
3
 
4
- export const version = '5.1.1'
4
+ export const version = '5.2.0-canary.019da1aa690a'
5
5
  export const publishDates = {
6
6
  major: '2026-05-06T16:28:18.473Z',
7
- minor: '2026-06-03T10:26:13.606Z',
8
- patch: '2026-06-12T16:33:21.130Z',
7
+ minor: '2026-06-18T15:57:15.608Z',
8
+ patch: '2026-06-18T15:57:15.608Z',
9
9
  }
@@ -1,161 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
- var ShapeIndicatorOverlayUtil_exports = {};
20
- __export(ShapeIndicatorOverlayUtil_exports, {
21
- ShapeIndicatorOverlayUtil: () => ShapeIndicatorOverlayUtil,
22
- strokeShapeIndicators: () => strokeShapeIndicators
23
- });
24
- module.exports = __toCommonJS(ShapeIndicatorOverlayUtil_exports);
25
- var import_state = require("@tldraw/state");
26
- var import_store = require("@tldraw/store");
27
- var import_OverlayUtil = require("./OverlayUtil");
28
- const indicatorPathCache = (0, import_store.createComputedCache)(
29
- "shapeIndicatorPath",
30
- (editor, shape) => {
31
- const util = editor.getShapeUtil(shape);
32
- return util.getIndicatorPath(shape);
33
- },
34
- {
35
- areRecordsEqual(a, b) {
36
- return a.props === b.props;
37
- }
38
- }
39
- );
40
- function strokeShapeIndicators(editor, ctx, shapeIds) {
41
- if (shapeIds.length === 0) return;
42
- const batched = new Path2D();
43
- for (const shapeId of shapeIds) {
44
- const shape = editor.getShape(shapeId);
45
- if (!shape || shape.isLocked) continue;
46
- const pageTransform = editor.getShapePageTransform(shape);
47
- if (!pageTransform) continue;
48
- const indicatorPath = indicatorPathCache.get(editor, shape.id);
49
- if (!indicatorPath) continue;
50
- if (indicatorPath instanceof Path2D) {
51
- batched.addPath(indicatorPath, pageTransform);
52
- continue;
53
- }
54
- const { path, clipPath, additionalPaths } = indicatorPath;
55
- if (!clipPath) {
56
- batched.addPath(path, pageTransform);
57
- if (additionalPaths) {
58
- for (const p of additionalPaths) batched.addPath(p, pageTransform);
59
- }
60
- continue;
61
- }
62
- ctx.save();
63
- ctx.transform(
64
- pageTransform.a,
65
- pageTransform.b,
66
- pageTransform.c,
67
- pageTransform.d,
68
- pageTransform.e,
69
- pageTransform.f
70
- );
71
- ctx.save();
72
- ctx.clip(clipPath, "evenodd");
73
- ctx.stroke(path);
74
- ctx.restore();
75
- if (additionalPaths) {
76
- for (const p of additionalPaths) ctx.stroke(p);
77
- }
78
- ctx.restore();
79
- }
80
- ctx.stroke(batched);
81
- }
82
- class ShapeIndicatorOverlayUtil extends import_OverlayUtil.OverlayUtil {
83
- static type = "shape_indicator";
84
- options = { zIndex: 50, lineWidth: 1.5, hintedLineWidth: 2.5 };
85
- // Narrow projection of instance state. Reading the full record would
86
- // re-fire getOverlays on every cursor move / brush update; gating on these
87
- // three booleans means we only re-fire when one of them actually flips.
88
- _instanceFlags$ = (0, import_state.computed)(
89
- "shape indicator instance flags",
90
- () => {
91
- const i = this.editor.getInstanceState();
92
- return {
93
- isChangingStyle: i.isChangingStyle,
94
- isHoveringCanvas: i.isHoveringCanvas,
95
- isCoarsePointer: i.isCoarsePointer
96
- };
97
- },
98
- {
99
- isEqual: (a, b) => a.isChangingStyle === b.isChangingStyle && a.isHoveringCanvas === b.isHoveringCanvas && a.isCoarsePointer === b.isCoarsePointer
100
- }
101
- );
102
- isActive() {
103
- return true;
104
- }
105
- getOverlays() {
106
- const editor = this.editor;
107
- const renderingShapeIds = new Set(editor.getRenderingShapes().map((s) => s.id));
108
- const idsToDisplay = [];
109
- const { isChangingStyle, isHoveringCanvas, isCoarsePointer } = this._instanceFlags$.get();
110
- const isIdleOrEditing = editor.isInAny("select.idle", "select.editing_shape");
111
- const isInSelectState = editor.isInAny(
112
- "select.brushing",
113
- "select.scribble_brushing",
114
- "select.pointing_shape",
115
- "select.pointing_selection",
116
- "select.pointing_handle"
117
- );
118
- if (!isChangingStyle && (isIdleOrEditing || isInSelectState)) {
119
- for (const id of editor.getSelectedShapeIds()) {
120
- if (renderingShapeIds.has(id)) idsToDisplay.push(id);
121
- }
122
- if (isIdleOrEditing && isHoveringCanvas && !isCoarsePointer) {
123
- const hovered = editor.getHoveredShapeId();
124
- if (hovered && renderingShapeIds.has(hovered) && !idsToDisplay.includes(hovered)) {
125
- idsToDisplay.push(hovered);
126
- }
127
- }
128
- }
129
- const hintingShapeIds = [];
130
- for (const id of editor.getHintingShapeIds()) {
131
- if (renderingShapeIds.has(id)) hintingShapeIds.push(id);
132
- }
133
- if (idsToDisplay.length === 0 && hintingShapeIds.length === 0) {
134
- return [];
135
- }
136
- return [
137
- {
138
- id: "shape_indicator",
139
- type: "shape_indicator",
140
- props: { idsToDisplay, hintingShapeIds }
141
- }
142
- ];
143
- }
144
- render(ctx, overlays) {
145
- const overlay = overlays[0];
146
- if (!overlay) return;
147
- const editor = this.editor;
148
- const zoom = editor.getZoomLevel();
149
- const { idsToDisplay, hintingShapeIds } = overlay.props;
150
- ctx.lineCap = "round";
151
- ctx.lineJoin = "round";
152
- ctx.strokeStyle = editor.getCurrentTheme().colors[editor.getColorMode()].selectionStroke;
153
- ctx.lineWidth = this.options.lineWidth / zoom;
154
- strokeShapeIndicators(editor, ctx, idsToDisplay);
155
- if (hintingShapeIds.length > 0) {
156
- ctx.lineWidth = this.options.hintedLineWidth / zoom;
157
- strokeShapeIndicators(editor, ctx, hintingShapeIds);
158
- }
159
- }
160
- }
161
- //# sourceMappingURL=ShapeIndicatorOverlayUtil.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../src/lib/editor/overlays/ShapeIndicatorOverlayUtil.ts"],
4
- "sourcesContent": ["import { computed } from '@tldraw/state'\nimport { createComputedCache } from '@tldraw/store'\nimport { TLShape, TLShapeId } from '@tldraw/tlschema'\nimport type { Editor } from '../Editor'\nimport { OverlayUtil, TLOverlay } from './OverlayUtil'\n\ninterface RelevantInstanceFlags {\n\tisChangingStyle: boolean\n\tisHoveringCanvas: boolean | null\n\tisCoarsePointer: boolean\n}\n\n/** @public */\nexport interface TLShapeIndicatorOverlay extends TLOverlay {\n\tprops: {\n\t\tidsToDisplay: TLShapeId[]\n\t\thintingShapeIds: TLShapeId[]\n\t}\n}\n\nconst indicatorPathCache = createComputedCache(\n\t'shapeIndicatorPath',\n\t(editor: Editor, shape: TLShape) => {\n\t\tconst util = editor.getShapeUtil(shape)\n\t\treturn util.getIndicatorPath(shape)\n\t},\n\t{\n\t\tareRecordsEqual(a, b) {\n\t\t\treturn a.props === b.props\n\t\t},\n\t}\n)\n\n/**\n * Combine every batchable shape indicator into a single page-space `Path2D` and\n * emit one stroke call. Shapes whose indicator needs an evenodd clip (e.g.\n * arrows with labels or complex arrowheads) can't be batched \u2014 they still\n * stroke individually inside a save/restore with `ctx.clip` applied.\n *\n * Shared by {@link ShapeIndicatorOverlayUtil} and any overlay util that paints\n * shape indicators (e.g. collaborator selections).\n *\n * @public\n */\nexport function strokeShapeIndicators(\n\teditor: Editor,\n\tctx: CanvasRenderingContext2D,\n\tshapeIds: TLShapeId[]\n): void {\n\tif (shapeIds.length === 0) return\n\n\tconst batched = new Path2D()\n\n\tfor (const shapeId of shapeIds) {\n\t\tconst shape = editor.getShape(shapeId)\n\t\tif (!shape || shape.isLocked) continue\n\n\t\tconst pageTransform = editor.getShapePageTransform(shape)\n\t\tif (!pageTransform) continue\n\n\t\tconst indicatorPath = indicatorPathCache.get(editor, shape.id)\n\t\tif (!indicatorPath) continue\n\n\t\tif (indicatorPath instanceof Path2D) {\n\t\t\tbatched.addPath(indicatorPath, pageTransform)\n\t\t\tcontinue\n\t\t}\n\n\t\tconst { path, clipPath, additionalPaths } = indicatorPath\n\n\t\tif (!clipPath) {\n\t\t\tbatched.addPath(path, pageTransform)\n\t\t\tif (additionalPaths) {\n\t\t\t\tfor (const p of additionalPaths) batched.addPath(p, pageTransform)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// Clipped case: fall back to an individual stroke. Rare (arrows with\n\t\t// labels / complex arrowheads), so the extra save/restore/stroke\n\t\t// pair per such shape isn't worth batching away.\n\t\tctx.save()\n\t\tctx.transform(\n\t\t\tpageTransform.a,\n\t\t\tpageTransform.b,\n\t\t\tpageTransform.c,\n\t\t\tpageTransform.d,\n\t\t\tpageTransform.e,\n\t\t\tpageTransform.f\n\t\t)\n\t\tctx.save()\n\t\tctx.clip(clipPath, 'evenodd')\n\t\tctx.stroke(path)\n\t\tctx.restore()\n\t\tif (additionalPaths) {\n\t\t\tfor (const p of additionalPaths) ctx.stroke(p)\n\t\t}\n\t\tctx.restore()\n\t}\n\n\tctx.stroke(batched)\n}\n\n/**\n * Overlay util for shape indicators \u2014 the selection / hover / hint outlines drawn\n * under the selection foreground. Paints local indicators in the theme's\n * selection color.\n *\n * Remote collaborator selection indicators are drawn by a separate overlay util\n * (e.g. `CollaboratorShapeIndicatorOverlayUtil` from `tldraw`) that runs at a\n * lower z-index so peer selections appear under the local indicators.\n *\n * Non-interactive: contributes no hit-test geometry.\n *\n * @public\n */\nexport class ShapeIndicatorOverlayUtil extends OverlayUtil<TLShapeIndicatorOverlay> {\n\tstatic override type = 'shape_indicator'\n\toverride options = { zIndex: 50, lineWidth: 1.5, hintedLineWidth: 2.5 }\n\n\t// Narrow projection of instance state. Reading the full record would\n\t// re-fire getOverlays on every cursor move / brush update; gating on these\n\t// three booleans means we only re-fire when one of them actually flips.\n\tprivate _instanceFlags$ = computed<RelevantInstanceFlags>(\n\t\t'shape indicator instance flags',\n\t\t() => {\n\t\t\tconst i = this.editor.getInstanceState()\n\t\t\treturn {\n\t\t\t\tisChangingStyle: i.isChangingStyle,\n\t\t\t\tisHoveringCanvas: i.isHoveringCanvas,\n\t\t\t\tisCoarsePointer: i.isCoarsePointer,\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\tisEqual: (a, b) =>\n\t\t\t\ta.isChangingStyle === b.isChangingStyle &&\n\t\t\t\ta.isHoveringCanvas === b.isHoveringCanvas &&\n\t\t\t\ta.isCoarsePointer === b.isCoarsePointer,\n\t\t}\n\t)\n\n\toverride isActive(): boolean {\n\t\treturn true\n\t}\n\n\toverride getOverlays(): TLShapeIndicatorOverlay[] {\n\t\tconst editor = this.editor\n\t\tconst renderingShapeIds = new Set(editor.getRenderingShapes().map((s) => s.id))\n\n\t\t// Local selected / hovered indicators.\n\t\tconst idsToDisplay: TLShapeId[] = []\n\t\tconst { isChangingStyle, isHoveringCanvas, isCoarsePointer } = this._instanceFlags$.get()\n\t\tconst isIdleOrEditing = editor.isInAny('select.idle', 'select.editing_shape')\n\t\tconst isInSelectState = editor.isInAny(\n\t\t\t'select.brushing',\n\t\t\t'select.scribble_brushing',\n\t\t\t'select.pointing_shape',\n\t\t\t'select.pointing_selection',\n\t\t\t'select.pointing_handle'\n\t\t)\n\n\t\tif (!isChangingStyle && (isIdleOrEditing || isInSelectState)) {\n\t\t\tfor (const id of editor.getSelectedShapeIds()) {\n\t\t\t\tif (renderingShapeIds.has(id)) idsToDisplay.push(id)\n\t\t\t}\n\t\t\tif (isIdleOrEditing && isHoveringCanvas && !isCoarsePointer) {\n\t\t\t\tconst hovered = editor.getHoveredShapeId()\n\t\t\t\tif (hovered && renderingShapeIds.has(hovered) && !idsToDisplay.includes(hovered)) {\n\t\t\t\t\tidsToDisplay.push(hovered)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Hinted shapes (drawn thicker). Already deduped at write time in\n\t\t// `updateHintingShapeIds`, so no need to dedupe again here.\n\t\tconst hintingShapeIds: TLShapeId[] = []\n\t\tfor (const id of editor.getHintingShapeIds()) {\n\t\t\tif (renderingShapeIds.has(id)) hintingShapeIds.push(id)\n\t\t}\n\n\t\tif (idsToDisplay.length === 0 && hintingShapeIds.length === 0) {\n\t\t\treturn []\n\t\t}\n\n\t\treturn [\n\t\t\t{\n\t\t\t\tid: 'shape_indicator',\n\t\t\t\ttype: 'shape_indicator',\n\t\t\t\tprops: { idsToDisplay, hintingShapeIds },\n\t\t\t},\n\t\t]\n\t}\n\n\toverride render(ctx: CanvasRenderingContext2D, overlays: TLShapeIndicatorOverlay[]): void {\n\t\tconst overlay = overlays[0]\n\t\tif (!overlay) return\n\n\t\tconst editor = this.editor\n\t\tconst zoom = editor.getZoomLevel()\n\t\tconst { idsToDisplay, hintingShapeIds } = overlay.props\n\n\t\tctx.lineCap = 'round'\n\t\tctx.lineJoin = 'round'\n\n\t\t// Local selected / hovered indicators \u2014 one stroke call for the whole batch.\n\t\tctx.strokeStyle = editor.getCurrentTheme().colors[editor.getColorMode()].selectionStroke\n\t\tctx.lineWidth = this.options.lineWidth / zoom\n\t\tstrokeShapeIndicators(editor, ctx, idsToDisplay)\n\n\t\t// Hinted shapes \u2014 thicker stroke, one call for the whole batch.\n\t\tif (hintingShapeIds.length > 0) {\n\t\t\tctx.lineWidth = this.options.hintedLineWidth / zoom\n\t\t\tstrokeShapeIndicators(editor, ctx, hintingShapeIds)\n\t\t}\n\t}\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAyB;AACzB,mBAAoC;AAGpC,yBAAuC;AAgBvC,MAAM,yBAAqB;AAAA,EAC1B;AAAA,EACA,CAAC,QAAgB,UAAmB;AACnC,UAAM,OAAO,OAAO,aAAa,KAAK;AACtC,WAAO,KAAK,iBAAiB,KAAK;AAAA,EACnC;AAAA,EACA;AAAA,IACC,gBAAgB,GAAG,GAAG;AACrB,aAAO,EAAE,UAAU,EAAE;AAAA,IACtB;AAAA,EACD;AACD;AAaO,SAAS,sBACf,QACA,KACA,UACO;AACP,MAAI,SAAS,WAAW,EAAG;AAE3B,QAAM,UAAU,IAAI,OAAO;AAE3B,aAAW,WAAW,UAAU;AAC/B,UAAM,QAAQ,OAAO,SAAS,OAAO;AACrC,QAAI,CAAC,SAAS,MAAM,SAAU;AAE9B,UAAM,gBAAgB,OAAO,sBAAsB,KAAK;AACxD,QAAI,CAAC,cAAe;AAEpB,UAAM,gBAAgB,mBAAmB,IAAI,QAAQ,MAAM,EAAE;AAC7D,QAAI,CAAC,cAAe;AAEpB,QAAI,yBAAyB,QAAQ;AACpC,cAAQ,QAAQ,eAAe,aAAa;AAC5C;AAAA,IACD;AAEA,UAAM,EAAE,MAAM,UAAU,gBAAgB,IAAI;AAE5C,QAAI,CAAC,UAAU;AACd,cAAQ,QAAQ,MAAM,aAAa;AACnC,UAAI,iBAAiB;AACpB,mBAAW,KAAK,gBAAiB,SAAQ,QAAQ,GAAG,aAAa;AAAA,MAClE;AACA;AAAA,IACD;AAKA,QAAI,KAAK;AACT,QAAI;AAAA,MACH,cAAc;AAAA,MACd,cAAc;AAAA,MACd,cAAc;AAAA,MACd,cAAc;AAAA,MACd,cAAc;AAAA,MACd,cAAc;AAAA,IACf;AACA,QAAI,KAAK;AACT,QAAI,KAAK,UAAU,SAAS;AAC5B,QAAI,OAAO,IAAI;AACf,QAAI,QAAQ;AACZ,QAAI,iBAAiB;AACpB,iBAAW,KAAK,gBAAiB,KAAI,OAAO,CAAC;AAAA,IAC9C;AACA,QAAI,QAAQ;AAAA,EACb;AAEA,MAAI,OAAO,OAAO;AACnB;AAeO,MAAM,kCAAkC,+BAAqC;AAAA,EACnF,OAAgB,OAAO;AAAA,EACd,UAAU,EAAE,QAAQ,IAAI,WAAW,KAAK,iBAAiB,IAAI;AAAA;AAAA;AAAA;AAAA,EAK9D,sBAAkB;AAAA,IACzB;AAAA,IACA,MAAM;AACL,YAAM,IAAI,KAAK,OAAO,iBAAiB;AACvC,aAAO;AAAA,QACN,iBAAiB,EAAE;AAAA,QACnB,kBAAkB,EAAE;AAAA,QACpB,iBAAiB,EAAE;AAAA,MACpB;AAAA,IACD;AAAA,IACA;AAAA,MACC,SAAS,CAAC,GAAG,MACZ,EAAE,oBAAoB,EAAE,mBACxB,EAAE,qBAAqB,EAAE,oBACzB,EAAE,oBAAoB,EAAE;AAAA,IAC1B;AAAA,EACD;AAAA,EAES,WAAoB;AAC5B,WAAO;AAAA,EACR;AAAA,EAES,cAAyC;AACjD,UAAM,SAAS,KAAK;AACpB,UAAM,oBAAoB,IAAI,IAAI,OAAO,mBAAmB,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAG9E,UAAM,eAA4B,CAAC;AACnC,UAAM,EAAE,iBAAiB,kBAAkB,gBAAgB,IAAI,KAAK,gBAAgB,IAAI;AACxF,UAAM,kBAAkB,OAAO,QAAQ,eAAe,sBAAsB;AAC5E,UAAM,kBAAkB,OAAO;AAAA,MAC9B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAEA,QAAI,CAAC,oBAAoB,mBAAmB,kBAAkB;AAC7D,iBAAW,MAAM,OAAO,oBAAoB,GAAG;AAC9C,YAAI,kBAAkB,IAAI,EAAE,EAAG,cAAa,KAAK,EAAE;AAAA,MACpD;AACA,UAAI,mBAAmB,oBAAoB,CAAC,iBAAiB;AAC5D,cAAM,UAAU,OAAO,kBAAkB;AACzC,YAAI,WAAW,kBAAkB,IAAI,OAAO,KAAK,CAAC,aAAa,SAAS,OAAO,GAAG;AACjF,uBAAa,KAAK,OAAO;AAAA,QAC1B;AAAA,MACD;AAAA,IACD;AAIA,UAAM,kBAA+B,CAAC;AACtC,eAAW,MAAM,OAAO,mBAAmB,GAAG;AAC7C,UAAI,kBAAkB,IAAI,EAAE,EAAG,iBAAgB,KAAK,EAAE;AAAA,IACvD;AAEA,QAAI,aAAa,WAAW,KAAK,gBAAgB,WAAW,GAAG;AAC9D,aAAO,CAAC;AAAA,IACT;AAEA,WAAO;AAAA,MACN;AAAA,QACC,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,OAAO,EAAE,cAAc,gBAAgB;AAAA,MACxC;AAAA,IACD;AAAA,EACD;AAAA,EAES,OAAO,KAA+B,UAA2C;AACzF,UAAM,UAAU,SAAS,CAAC;AAC1B,QAAI,CAAC,QAAS;AAEd,UAAM,SAAS,KAAK;AACpB,UAAM,OAAO,OAAO,aAAa;AACjC,UAAM,EAAE,cAAc,gBAAgB,IAAI,QAAQ;AAElD,QAAI,UAAU;AACd,QAAI,WAAW;AAGf,QAAI,cAAc,OAAO,gBAAgB,EAAE,OAAO,OAAO,aAAa,CAAC,EAAE;AACzE,QAAI,YAAY,KAAK,QAAQ,YAAY;AACzC,0BAAsB,QAAQ,KAAK,YAAY;AAG/C,QAAI,gBAAgB,SAAS,GAAG;AAC/B,UAAI,YAAY,KAAK,QAAQ,kBAAkB;AAC/C,4BAAsB,QAAQ,KAAK,eAAe;AAAA,IACnD;AAAA,EACD;AACD;",
6
- "names": []
7
- }
@@ -1,141 +0,0 @@
1
- import { computed } from "@tldraw/state";
2
- import { createComputedCache } from "@tldraw/store";
3
- import { OverlayUtil } from "./OverlayUtil.mjs";
4
- const indicatorPathCache = createComputedCache(
5
- "shapeIndicatorPath",
6
- (editor, shape) => {
7
- const util = editor.getShapeUtil(shape);
8
- return util.getIndicatorPath(shape);
9
- },
10
- {
11
- areRecordsEqual(a, b) {
12
- return a.props === b.props;
13
- }
14
- }
15
- );
16
- function strokeShapeIndicators(editor, ctx, shapeIds) {
17
- if (shapeIds.length === 0) return;
18
- const batched = new Path2D();
19
- for (const shapeId of shapeIds) {
20
- const shape = editor.getShape(shapeId);
21
- if (!shape || shape.isLocked) continue;
22
- const pageTransform = editor.getShapePageTransform(shape);
23
- if (!pageTransform) continue;
24
- const indicatorPath = indicatorPathCache.get(editor, shape.id);
25
- if (!indicatorPath) continue;
26
- if (indicatorPath instanceof Path2D) {
27
- batched.addPath(indicatorPath, pageTransform);
28
- continue;
29
- }
30
- const { path, clipPath, additionalPaths } = indicatorPath;
31
- if (!clipPath) {
32
- batched.addPath(path, pageTransform);
33
- if (additionalPaths) {
34
- for (const p of additionalPaths) batched.addPath(p, pageTransform);
35
- }
36
- continue;
37
- }
38
- ctx.save();
39
- ctx.transform(
40
- pageTransform.a,
41
- pageTransform.b,
42
- pageTransform.c,
43
- pageTransform.d,
44
- pageTransform.e,
45
- pageTransform.f
46
- );
47
- ctx.save();
48
- ctx.clip(clipPath, "evenodd");
49
- ctx.stroke(path);
50
- ctx.restore();
51
- if (additionalPaths) {
52
- for (const p of additionalPaths) ctx.stroke(p);
53
- }
54
- ctx.restore();
55
- }
56
- ctx.stroke(batched);
57
- }
58
- class ShapeIndicatorOverlayUtil extends OverlayUtil {
59
- static type = "shape_indicator";
60
- options = { zIndex: 50, lineWidth: 1.5, hintedLineWidth: 2.5 };
61
- // Narrow projection of instance state. Reading the full record would
62
- // re-fire getOverlays on every cursor move / brush update; gating on these
63
- // three booleans means we only re-fire when one of them actually flips.
64
- _instanceFlags$ = computed(
65
- "shape indicator instance flags",
66
- () => {
67
- const i = this.editor.getInstanceState();
68
- return {
69
- isChangingStyle: i.isChangingStyle,
70
- isHoveringCanvas: i.isHoveringCanvas,
71
- isCoarsePointer: i.isCoarsePointer
72
- };
73
- },
74
- {
75
- isEqual: (a, b) => a.isChangingStyle === b.isChangingStyle && a.isHoveringCanvas === b.isHoveringCanvas && a.isCoarsePointer === b.isCoarsePointer
76
- }
77
- );
78
- isActive() {
79
- return true;
80
- }
81
- getOverlays() {
82
- const editor = this.editor;
83
- const renderingShapeIds = new Set(editor.getRenderingShapes().map((s) => s.id));
84
- const idsToDisplay = [];
85
- const { isChangingStyle, isHoveringCanvas, isCoarsePointer } = this._instanceFlags$.get();
86
- const isIdleOrEditing = editor.isInAny("select.idle", "select.editing_shape");
87
- const isInSelectState = editor.isInAny(
88
- "select.brushing",
89
- "select.scribble_brushing",
90
- "select.pointing_shape",
91
- "select.pointing_selection",
92
- "select.pointing_handle"
93
- );
94
- if (!isChangingStyle && (isIdleOrEditing || isInSelectState)) {
95
- for (const id of editor.getSelectedShapeIds()) {
96
- if (renderingShapeIds.has(id)) idsToDisplay.push(id);
97
- }
98
- if (isIdleOrEditing && isHoveringCanvas && !isCoarsePointer) {
99
- const hovered = editor.getHoveredShapeId();
100
- if (hovered && renderingShapeIds.has(hovered) && !idsToDisplay.includes(hovered)) {
101
- idsToDisplay.push(hovered);
102
- }
103
- }
104
- }
105
- const hintingShapeIds = [];
106
- for (const id of editor.getHintingShapeIds()) {
107
- if (renderingShapeIds.has(id)) hintingShapeIds.push(id);
108
- }
109
- if (idsToDisplay.length === 0 && hintingShapeIds.length === 0) {
110
- return [];
111
- }
112
- return [
113
- {
114
- id: "shape_indicator",
115
- type: "shape_indicator",
116
- props: { idsToDisplay, hintingShapeIds }
117
- }
118
- ];
119
- }
120
- render(ctx, overlays) {
121
- const overlay = overlays[0];
122
- if (!overlay) return;
123
- const editor = this.editor;
124
- const zoom = editor.getZoomLevel();
125
- const { idsToDisplay, hintingShapeIds } = overlay.props;
126
- ctx.lineCap = "round";
127
- ctx.lineJoin = "round";
128
- ctx.strokeStyle = editor.getCurrentTheme().colors[editor.getColorMode()].selectionStroke;
129
- ctx.lineWidth = this.options.lineWidth / zoom;
130
- strokeShapeIndicators(editor, ctx, idsToDisplay);
131
- if (hintingShapeIds.length > 0) {
132
- ctx.lineWidth = this.options.hintedLineWidth / zoom;
133
- strokeShapeIndicators(editor, ctx, hintingShapeIds);
134
- }
135
- }
136
- }
137
- export {
138
- ShapeIndicatorOverlayUtil,
139
- strokeShapeIndicators
140
- };
141
- //# sourceMappingURL=ShapeIndicatorOverlayUtil.mjs.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../src/lib/editor/overlays/ShapeIndicatorOverlayUtil.ts"],
4
- "sourcesContent": ["import { computed } from '@tldraw/state'\nimport { createComputedCache } from '@tldraw/store'\nimport { TLShape, TLShapeId } from '@tldraw/tlschema'\nimport type { Editor } from '../Editor'\nimport { OverlayUtil, TLOverlay } from './OverlayUtil'\n\ninterface RelevantInstanceFlags {\n\tisChangingStyle: boolean\n\tisHoveringCanvas: boolean | null\n\tisCoarsePointer: boolean\n}\n\n/** @public */\nexport interface TLShapeIndicatorOverlay extends TLOverlay {\n\tprops: {\n\t\tidsToDisplay: TLShapeId[]\n\t\thintingShapeIds: TLShapeId[]\n\t}\n}\n\nconst indicatorPathCache = createComputedCache(\n\t'shapeIndicatorPath',\n\t(editor: Editor, shape: TLShape) => {\n\t\tconst util = editor.getShapeUtil(shape)\n\t\treturn util.getIndicatorPath(shape)\n\t},\n\t{\n\t\tareRecordsEqual(a, b) {\n\t\t\treturn a.props === b.props\n\t\t},\n\t}\n)\n\n/**\n * Combine every batchable shape indicator into a single page-space `Path2D` and\n * emit one stroke call. Shapes whose indicator needs an evenodd clip (e.g.\n * arrows with labels or complex arrowheads) can't be batched \u2014 they still\n * stroke individually inside a save/restore with `ctx.clip` applied.\n *\n * Shared by {@link ShapeIndicatorOverlayUtil} and any overlay util that paints\n * shape indicators (e.g. collaborator selections).\n *\n * @public\n */\nexport function strokeShapeIndicators(\n\teditor: Editor,\n\tctx: CanvasRenderingContext2D,\n\tshapeIds: TLShapeId[]\n): void {\n\tif (shapeIds.length === 0) return\n\n\tconst batched = new Path2D()\n\n\tfor (const shapeId of shapeIds) {\n\t\tconst shape = editor.getShape(shapeId)\n\t\tif (!shape || shape.isLocked) continue\n\n\t\tconst pageTransform = editor.getShapePageTransform(shape)\n\t\tif (!pageTransform) continue\n\n\t\tconst indicatorPath = indicatorPathCache.get(editor, shape.id)\n\t\tif (!indicatorPath) continue\n\n\t\tif (indicatorPath instanceof Path2D) {\n\t\t\tbatched.addPath(indicatorPath, pageTransform)\n\t\t\tcontinue\n\t\t}\n\n\t\tconst { path, clipPath, additionalPaths } = indicatorPath\n\n\t\tif (!clipPath) {\n\t\t\tbatched.addPath(path, pageTransform)\n\t\t\tif (additionalPaths) {\n\t\t\t\tfor (const p of additionalPaths) batched.addPath(p, pageTransform)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// Clipped case: fall back to an individual stroke. Rare (arrows with\n\t\t// labels / complex arrowheads), so the extra save/restore/stroke\n\t\t// pair per such shape isn't worth batching away.\n\t\tctx.save()\n\t\tctx.transform(\n\t\t\tpageTransform.a,\n\t\t\tpageTransform.b,\n\t\t\tpageTransform.c,\n\t\t\tpageTransform.d,\n\t\t\tpageTransform.e,\n\t\t\tpageTransform.f\n\t\t)\n\t\tctx.save()\n\t\tctx.clip(clipPath, 'evenodd')\n\t\tctx.stroke(path)\n\t\tctx.restore()\n\t\tif (additionalPaths) {\n\t\t\tfor (const p of additionalPaths) ctx.stroke(p)\n\t\t}\n\t\tctx.restore()\n\t}\n\n\tctx.stroke(batched)\n}\n\n/**\n * Overlay util for shape indicators \u2014 the selection / hover / hint outlines drawn\n * under the selection foreground. Paints local indicators in the theme's\n * selection color.\n *\n * Remote collaborator selection indicators are drawn by a separate overlay util\n * (e.g. `CollaboratorShapeIndicatorOverlayUtil` from `tldraw`) that runs at a\n * lower z-index so peer selections appear under the local indicators.\n *\n * Non-interactive: contributes no hit-test geometry.\n *\n * @public\n */\nexport class ShapeIndicatorOverlayUtil extends OverlayUtil<TLShapeIndicatorOverlay> {\n\tstatic override type = 'shape_indicator'\n\toverride options = { zIndex: 50, lineWidth: 1.5, hintedLineWidth: 2.5 }\n\n\t// Narrow projection of instance state. Reading the full record would\n\t// re-fire getOverlays on every cursor move / brush update; gating on these\n\t// three booleans means we only re-fire when one of them actually flips.\n\tprivate _instanceFlags$ = computed<RelevantInstanceFlags>(\n\t\t'shape indicator instance flags',\n\t\t() => {\n\t\t\tconst i = this.editor.getInstanceState()\n\t\t\treturn {\n\t\t\t\tisChangingStyle: i.isChangingStyle,\n\t\t\t\tisHoveringCanvas: i.isHoveringCanvas,\n\t\t\t\tisCoarsePointer: i.isCoarsePointer,\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\tisEqual: (a, b) =>\n\t\t\t\ta.isChangingStyle === b.isChangingStyle &&\n\t\t\t\ta.isHoveringCanvas === b.isHoveringCanvas &&\n\t\t\t\ta.isCoarsePointer === b.isCoarsePointer,\n\t\t}\n\t)\n\n\toverride isActive(): boolean {\n\t\treturn true\n\t}\n\n\toverride getOverlays(): TLShapeIndicatorOverlay[] {\n\t\tconst editor = this.editor\n\t\tconst renderingShapeIds = new Set(editor.getRenderingShapes().map((s) => s.id))\n\n\t\t// Local selected / hovered indicators.\n\t\tconst idsToDisplay: TLShapeId[] = []\n\t\tconst { isChangingStyle, isHoveringCanvas, isCoarsePointer } = this._instanceFlags$.get()\n\t\tconst isIdleOrEditing = editor.isInAny('select.idle', 'select.editing_shape')\n\t\tconst isInSelectState = editor.isInAny(\n\t\t\t'select.brushing',\n\t\t\t'select.scribble_brushing',\n\t\t\t'select.pointing_shape',\n\t\t\t'select.pointing_selection',\n\t\t\t'select.pointing_handle'\n\t\t)\n\n\t\tif (!isChangingStyle && (isIdleOrEditing || isInSelectState)) {\n\t\t\tfor (const id of editor.getSelectedShapeIds()) {\n\t\t\t\tif (renderingShapeIds.has(id)) idsToDisplay.push(id)\n\t\t\t}\n\t\t\tif (isIdleOrEditing && isHoveringCanvas && !isCoarsePointer) {\n\t\t\t\tconst hovered = editor.getHoveredShapeId()\n\t\t\t\tif (hovered && renderingShapeIds.has(hovered) && !idsToDisplay.includes(hovered)) {\n\t\t\t\t\tidsToDisplay.push(hovered)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Hinted shapes (drawn thicker). Already deduped at write time in\n\t\t// `updateHintingShapeIds`, so no need to dedupe again here.\n\t\tconst hintingShapeIds: TLShapeId[] = []\n\t\tfor (const id of editor.getHintingShapeIds()) {\n\t\t\tif (renderingShapeIds.has(id)) hintingShapeIds.push(id)\n\t\t}\n\n\t\tif (idsToDisplay.length === 0 && hintingShapeIds.length === 0) {\n\t\t\treturn []\n\t\t}\n\n\t\treturn [\n\t\t\t{\n\t\t\t\tid: 'shape_indicator',\n\t\t\t\ttype: 'shape_indicator',\n\t\t\t\tprops: { idsToDisplay, hintingShapeIds },\n\t\t\t},\n\t\t]\n\t}\n\n\toverride render(ctx: CanvasRenderingContext2D, overlays: TLShapeIndicatorOverlay[]): void {\n\t\tconst overlay = overlays[0]\n\t\tif (!overlay) return\n\n\t\tconst editor = this.editor\n\t\tconst zoom = editor.getZoomLevel()\n\t\tconst { idsToDisplay, hintingShapeIds } = overlay.props\n\n\t\tctx.lineCap = 'round'\n\t\tctx.lineJoin = 'round'\n\n\t\t// Local selected / hovered indicators \u2014 one stroke call for the whole batch.\n\t\tctx.strokeStyle = editor.getCurrentTheme().colors[editor.getColorMode()].selectionStroke\n\t\tctx.lineWidth = this.options.lineWidth / zoom\n\t\tstrokeShapeIndicators(editor, ctx, idsToDisplay)\n\n\t\t// Hinted shapes \u2014 thicker stroke, one call for the whole batch.\n\t\tif (hintingShapeIds.length > 0) {\n\t\t\tctx.lineWidth = this.options.hintedLineWidth / zoom\n\t\t\tstrokeShapeIndicators(editor, ctx, hintingShapeIds)\n\t\t}\n\t}\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }