@tldraw/editor 4.6.0-next.fe1474dc57d8 → 5.0.0

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 (207) hide show
  1. package/dist-cjs/index.d.ts +412 -179
  2. package/dist-cjs/index.js +12 -23
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js +3 -0
  5. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  6. package/dist-cjs/lib/components/default-components/CanvasOverlays.js +180 -0
  7. package/dist-cjs/lib/components/default-components/CanvasOverlays.js.map +7 -0
  8. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +44 -249
  9. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +3 -3
  10. package/dist-cjs/lib/editor/Editor.js +78 -28
  11. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  12. package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js +98 -0
  13. package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js.map +7 -0
  14. package/dist-cjs/lib/editor/managers/ThemeManager/defaultThemes.js +14 -0
  15. package/dist-cjs/lib/editor/managers/ThemeManager/defaultThemes.js.map +2 -2
  16. package/dist-cjs/lib/editor/overlays/OverlayManager.js +154 -0
  17. package/dist-cjs/lib/editor/overlays/OverlayManager.js.map +7 -0
  18. package/dist-cjs/lib/editor/overlays/OverlayUtil.js +92 -0
  19. package/dist-cjs/lib/editor/overlays/OverlayUtil.js.map +7 -0
  20. package/dist-cjs/lib/editor/overlays/ShapeIndicatorOverlayUtil.js +161 -0
  21. package/dist-cjs/lib/editor/overlays/ShapeIndicatorOverlayUtil.js.map +7 -0
  22. package/dist-cjs/lib/editor/overlays/getOverlayDisplayValues.js +39 -0
  23. package/dist-cjs/lib/editor/overlays/getOverlayDisplayValues.js.map +7 -0
  24. package/dist-cjs/lib/editor/shapes/BaseFrameLikeShapeUtil.js +3 -0
  25. package/dist-cjs/lib/editor/shapes/BaseFrameLikeShapeUtil.js.map +2 -2
  26. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +25 -23
  27. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  28. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +32 -2
  29. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +2 -2
  30. package/dist-cjs/lib/editor/types/event-types.js.map +2 -2
  31. package/dist-cjs/lib/exports/fetchCache.js +1 -1
  32. package/dist-cjs/lib/exports/fetchCache.js.map +2 -2
  33. package/dist-cjs/lib/hooks/EditorComponentsContext.js.map +2 -2
  34. package/dist-cjs/lib/hooks/useCanvasEvents.js +3 -3
  35. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  36. package/dist-cjs/lib/hooks/useEditorComponents.js +0 -28
  37. package/dist-cjs/lib/hooks/useEditorComponents.js.map +2 -2
  38. package/dist-cjs/lib/hooks/usePeerIds.js +1 -36
  39. package/dist-cjs/lib/hooks/usePeerIds.js.map +2 -2
  40. package/dist-cjs/lib/hooks/useShapeCulling.js +2 -1
  41. package/dist-cjs/lib/hooks/useShapeCulling.js.map +2 -2
  42. package/dist-cjs/lib/options.js +0 -1
  43. package/dist-cjs/lib/options.js.map +2 -2
  44. package/dist-cjs/lib/utils/reparenting.js +20 -7
  45. package/dist-cjs/lib/utils/reparenting.js.map +2 -2
  46. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +3 -0
  47. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
  48. package/dist-cjs/version.js +4 -4
  49. package/dist-cjs/version.js.map +1 -1
  50. package/dist-esm/index.d.mts +412 -179
  51. package/dist-esm/index.mjs +19 -41
  52. package/dist-esm/index.mjs.map +2 -2
  53. package/dist-esm/lib/TldrawEditor.mjs +3 -0
  54. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  55. package/dist-esm/lib/components/default-components/CanvasOverlays.mjs +160 -0
  56. package/dist-esm/lib/components/default-components/CanvasOverlays.mjs.map +7 -0
  57. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +45 -250
  58. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +3 -3
  59. package/dist-esm/lib/editor/Editor.mjs +78 -29
  60. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  61. package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs +83 -0
  62. package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs.map +7 -0
  63. package/dist-esm/lib/editor/managers/ThemeManager/defaultThemes.mjs +14 -0
  64. package/dist-esm/lib/editor/managers/ThemeManager/defaultThemes.mjs.map +2 -2
  65. package/dist-esm/lib/editor/overlays/OverlayManager.mjs +136 -0
  66. package/dist-esm/lib/editor/overlays/OverlayManager.mjs.map +7 -0
  67. package/dist-esm/lib/editor/overlays/OverlayUtil.mjs +72 -0
  68. package/dist-esm/lib/editor/overlays/OverlayUtil.mjs.map +7 -0
  69. package/dist-esm/lib/editor/overlays/ShapeIndicatorOverlayUtil.mjs +141 -0
  70. package/dist-esm/lib/editor/overlays/ShapeIndicatorOverlayUtil.mjs.map +7 -0
  71. package/dist-esm/lib/editor/overlays/getOverlayDisplayValues.mjs +19 -0
  72. package/dist-esm/lib/editor/overlays/getOverlayDisplayValues.mjs.map +7 -0
  73. package/dist-esm/lib/editor/shapes/BaseFrameLikeShapeUtil.mjs +3 -0
  74. package/dist-esm/lib/editor/shapes/BaseFrameLikeShapeUtil.mjs.map +2 -2
  75. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +25 -23
  76. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  77. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +32 -2
  78. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +2 -2
  79. package/dist-esm/lib/editor/types/event-types.mjs.map +2 -2
  80. package/dist-esm/lib/exports/fetchCache.mjs +2 -2
  81. package/dist-esm/lib/exports/fetchCache.mjs.map +2 -2
  82. package/dist-esm/lib/hooks/EditorComponentsContext.mjs.map +2 -2
  83. package/dist-esm/lib/hooks/useCanvasEvents.mjs +3 -3
  84. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  85. package/dist-esm/lib/hooks/useEditorComponents.mjs +0 -28
  86. package/dist-esm/lib/hooks/useEditorComponents.mjs.map +2 -2
  87. package/dist-esm/lib/hooks/usePeerIds.mjs +2 -40
  88. package/dist-esm/lib/hooks/usePeerIds.mjs.map +2 -2
  89. package/dist-esm/lib/hooks/useShapeCulling.mjs +2 -1
  90. package/dist-esm/lib/hooks/useShapeCulling.mjs.map +2 -2
  91. package/dist-esm/lib/options.mjs +0 -1
  92. package/dist-esm/lib/options.mjs.map +2 -2
  93. package/dist-esm/lib/utils/reparenting.mjs +20 -7
  94. package/dist-esm/lib/utils/reparenting.mjs.map +2 -2
  95. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +3 -0
  96. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
  97. package/dist-esm/version.mjs +4 -4
  98. package/dist-esm/version.mjs.map +1 -1
  99. package/editor.css +4 -239
  100. package/package.json +7 -7
  101. package/src/index.ts +17 -39
  102. package/src/lib/TldrawEditor.tsx +9 -0
  103. package/src/lib/components/default-components/CanvasOverlays.tsx +208 -0
  104. package/src/lib/components/default-components/DefaultCanvas.tsx +49 -324
  105. package/src/lib/editor/Editor.test.ts +3 -1
  106. package/src/lib/editor/Editor.ts +80 -24
  107. package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.ts +98 -0
  108. package/src/lib/editor/managers/ThemeManager/defaultThemes.ts +14 -0
  109. package/src/lib/editor/overlays/OverlayManager.ts +183 -0
  110. package/src/lib/editor/overlays/OverlayUtil.ts +143 -0
  111. package/src/lib/editor/overlays/ShapeIndicatorOverlayUtil.ts +216 -0
  112. package/src/lib/editor/overlays/getOverlayDisplayValues.ts +51 -0
  113. package/src/lib/editor/shapes/BaseFrameLikeShapeUtil.tsx +9 -2
  114. package/src/lib/editor/shapes/ShapeUtil.ts +34 -26
  115. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +40 -3
  116. package/src/lib/editor/types/event-types.ts +2 -0
  117. package/src/lib/exports/fetchCache.ts +2 -4
  118. package/src/lib/exports/getSvgJsx.test.ts +3 -1
  119. package/src/lib/hooks/EditorComponentsContext.tsx +0 -27
  120. package/src/lib/hooks/useCanvasEvents.ts +13 -8
  121. package/src/lib/hooks/useEditorComponents.tsx +0 -28
  122. package/src/lib/hooks/usePeerIds.ts +6 -55
  123. package/src/lib/hooks/useShapeCulling.tsx +3 -1
  124. package/src/lib/options.ts +0 -7
  125. package/src/lib/utils/reparenting.ts +22 -9
  126. package/src/lib/utils/sync/TLLocalSyncClient.ts +3 -0
  127. package/src/version.ts +4 -4
  128. package/dist-cjs/lib/components/GeometryDebuggingView.js +0 -115
  129. package/dist-cjs/lib/components/GeometryDebuggingView.js.map +0 -7
  130. package/dist-cjs/lib/components/LiveCollaborators.js +0 -152
  131. package/dist-cjs/lib/components/LiveCollaborators.js.map +0 -7
  132. package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js +0 -234
  133. package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js.map +0 -7
  134. package/dist-cjs/lib/components/default-components/DefaultBrush.js +0 -38
  135. package/dist-cjs/lib/components/default-components/DefaultBrush.js.map +0 -7
  136. package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js +0 -71
  137. package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js.map +0 -7
  138. package/dist-cjs/lib/components/default-components/DefaultCursor.js +0 -59
  139. package/dist-cjs/lib/components/default-components/DefaultCursor.js.map +0 -7
  140. package/dist-cjs/lib/components/default-components/DefaultHandle.js +0 -56
  141. package/dist-cjs/lib/components/default-components/DefaultHandle.js.map +0 -7
  142. package/dist-cjs/lib/components/default-components/DefaultHandles.js +0 -28
  143. package/dist-cjs/lib/components/default-components/DefaultHandles.js.map +0 -7
  144. package/dist-cjs/lib/components/default-components/DefaultScribble.js +0 -51
  145. package/dist-cjs/lib/components/default-components/DefaultScribble.js.map +0 -7
  146. package/dist-cjs/lib/components/default-components/DefaultSelectionForeground.js +0 -69
  147. package/dist-cjs/lib/components/default-components/DefaultSelectionForeground.js.map +0 -7
  148. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +0 -107
  149. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +0 -7
  150. package/dist-cjs/lib/components/default-components/DefaultShapeIndicatorErrorFallback.js +0 -28
  151. package/dist-cjs/lib/components/default-components/DefaultShapeIndicatorErrorFallback.js.map +0 -7
  152. package/dist-cjs/lib/components/default-components/DefaultShapeIndicators.js +0 -102
  153. package/dist-cjs/lib/components/default-components/DefaultShapeIndicators.js.map +0 -7
  154. package/dist-cjs/lib/components/default-components/DefaultSnapIndictor.js +0 -170
  155. package/dist-cjs/lib/components/default-components/DefaultSnapIndictor.js.map +0 -7
  156. package/dist-cjs/lib/hooks/useHandleEvents.js +0 -100
  157. package/dist-cjs/lib/hooks/useHandleEvents.js.map +0 -7
  158. package/dist-cjs/lib/hooks/useSelectionEvents.js +0 -98
  159. package/dist-cjs/lib/hooks/useSelectionEvents.js.map +0 -7
  160. package/dist-esm/lib/components/GeometryDebuggingView.mjs +0 -95
  161. package/dist-esm/lib/components/GeometryDebuggingView.mjs.map +0 -7
  162. package/dist-esm/lib/components/LiveCollaborators.mjs +0 -135
  163. package/dist-esm/lib/components/LiveCollaborators.mjs.map +0 -7
  164. package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs +0 -214
  165. package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs.map +0 -7
  166. package/dist-esm/lib/components/default-components/DefaultBrush.mjs +0 -18
  167. package/dist-esm/lib/components/default-components/DefaultBrush.mjs.map +0 -7
  168. package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs +0 -41
  169. package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs.map +0 -7
  170. package/dist-esm/lib/components/default-components/DefaultCursor.mjs +0 -29
  171. package/dist-esm/lib/components/default-components/DefaultCursor.mjs.map +0 -7
  172. package/dist-esm/lib/components/default-components/DefaultHandle.mjs +0 -26
  173. package/dist-esm/lib/components/default-components/DefaultHandle.mjs.map +0 -7
  174. package/dist-esm/lib/components/default-components/DefaultHandles.mjs +0 -8
  175. package/dist-esm/lib/components/default-components/DefaultHandles.mjs.map +0 -7
  176. package/dist-esm/lib/components/default-components/DefaultScribble.mjs +0 -21
  177. package/dist-esm/lib/components/default-components/DefaultScribble.mjs.map +0 -7
  178. package/dist-esm/lib/components/default-components/DefaultSelectionForeground.mjs +0 -39
  179. package/dist-esm/lib/components/default-components/DefaultSelectionForeground.mjs.map +0 -7
  180. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +0 -77
  181. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +0 -7
  182. package/dist-esm/lib/components/default-components/DefaultShapeIndicatorErrorFallback.mjs +0 -8
  183. package/dist-esm/lib/components/default-components/DefaultShapeIndicatorErrorFallback.mjs.map +0 -7
  184. package/dist-esm/lib/components/default-components/DefaultShapeIndicators.mjs +0 -82
  185. package/dist-esm/lib/components/default-components/DefaultShapeIndicators.mjs.map +0 -7
  186. package/dist-esm/lib/components/default-components/DefaultSnapIndictor.mjs +0 -142
  187. package/dist-esm/lib/components/default-components/DefaultSnapIndictor.mjs.map +0 -7
  188. package/dist-esm/lib/hooks/useHandleEvents.mjs +0 -70
  189. package/dist-esm/lib/hooks/useHandleEvents.mjs.map +0 -7
  190. package/dist-esm/lib/hooks/useSelectionEvents.mjs +0 -78
  191. package/dist-esm/lib/hooks/useSelectionEvents.mjs.map +0 -7
  192. package/src/lib/components/GeometryDebuggingView.tsx +0 -108
  193. package/src/lib/components/LiveCollaborators.tsx +0 -180
  194. package/src/lib/components/default-components/CanvasShapeIndicators.tsx +0 -300
  195. package/src/lib/components/default-components/DefaultBrush.tsx +0 -35
  196. package/src/lib/components/default-components/DefaultCollaboratorHint.tsx +0 -52
  197. package/src/lib/components/default-components/DefaultCursor.tsx +0 -59
  198. package/src/lib/components/default-components/DefaultHandle.tsx +0 -42
  199. package/src/lib/components/default-components/DefaultHandles.tsx +0 -15
  200. package/src/lib/components/default-components/DefaultScribble.tsx +0 -31
  201. package/src/lib/components/default-components/DefaultSelectionForeground.tsx +0 -50
  202. package/src/lib/components/default-components/DefaultShapeIndicator.tsx +0 -104
  203. package/src/lib/components/default-components/DefaultShapeIndicatorErrorFallback.tsx +0 -9
  204. package/src/lib/components/default-components/DefaultShapeIndicators.tsx +0 -118
  205. package/src/lib/components/default-components/DefaultSnapIndictor.tsx +0 -174
  206. package/src/lib/hooks/useHandleEvents.ts +0 -88
  207. package/src/lib/hooks/useSelectionEvents.ts +0 -97
@@ -1,180 +0,0 @@
1
- import { track } from '@tldraw/state-react'
2
- import { TLInstancePresence } from '@tldraw/tlschema'
3
- import { useEffect, useRef, useState } from 'react'
4
- import { Editor } from '../editor/Editor'
5
- import { useEditorComponents } from '../hooks/EditorComponentsContext'
6
- import { useEditor } from '../hooks/useEditor'
7
- import { usePeerIds } from '../hooks/usePeerIds'
8
- import { usePresence } from '../hooks/usePresence'
9
- import {
10
- CollaboratorState,
11
- getCollaboratorStateFromElapsedTime,
12
- shouldShowCollaborator,
13
- } from '../utils/collaboratorState'
14
-
15
- export const LiveCollaborators = track(function Collaborators() {
16
- const peerIds = usePeerIds()
17
- return peerIds.map((id) => <CollaboratorGuard key={id} collaboratorId={id} />)
18
- })
19
-
20
- const CollaboratorGuard = track(function CollaboratorGuard({
21
- collaboratorId,
22
- }: {
23
- collaboratorId: string
24
- }) {
25
- const editor = useEditor()
26
- const presence = usePresence(collaboratorId)
27
- const collaboratorState = useCollaboratorState(editor, presence)
28
-
29
- if (!(presence && presence.currentPageId === editor.getCurrentPageId())) {
30
- // No need to render if we don't have a presence or if they're on a different page
31
- return null
32
- }
33
-
34
- if (!shouldShowCollaborator(editor, presence, collaboratorState)) {
35
- return null
36
- }
37
-
38
- return <Collaborator latestPresence={presence} />
39
- })
40
-
41
- const Collaborator = track(function Collaborator({
42
- latestPresence,
43
- }: {
44
- latestPresence: TLInstancePresence
45
- }) {
46
- const editor = useEditor()
47
-
48
- const {
49
- CollaboratorBrush,
50
- CollaboratorScribble,
51
- CollaboratorCursor,
52
- CollaboratorHint,
53
- CollaboratorShapeIndicator,
54
- } = useEditorComponents()
55
-
56
- const zoomLevel = editor.getZoomLevel()
57
- const viewportPageBounds = editor.getViewportPageBounds()
58
- const { userId, chatMessage, brush, scribbles, selectedShapeIds, userName, cursor, color } =
59
- latestPresence
60
-
61
- if (!cursor) return null
62
-
63
- // Add a little padding to the top-left of the viewport
64
- // so that the cursor doesn't get cut off
65
- const isCursorInViewport = !(
66
- cursor.x < viewportPageBounds.minX - 12 / zoomLevel ||
67
- cursor.y < viewportPageBounds.minY - 16 / zoomLevel ||
68
- cursor.x > viewportPageBounds.maxX - 12 / zoomLevel ||
69
- cursor.y > viewportPageBounds.maxY - 16 / zoomLevel
70
- )
71
-
72
- return (
73
- <>
74
- {brush && CollaboratorBrush ? (
75
- <CollaboratorBrush
76
- className="tl-collaborator__brush"
77
- key={userId + '_brush'}
78
- userId={userId}
79
- brush={brush}
80
- color={color}
81
- opacity={0.1}
82
- />
83
- ) : null}
84
- {isCursorInViewport && CollaboratorCursor ? (
85
- <CollaboratorCursor
86
- className="tl-collaborator__cursor"
87
- key={userId + '_cursor'}
88
- userId={userId}
89
- point={cursor}
90
- color={color}
91
- zoom={zoomLevel}
92
- name={userName !== 'New User' ? userName : null}
93
- chatMessage={chatMessage ?? ''}
94
- />
95
- ) : CollaboratorHint ? (
96
- <CollaboratorHint
97
- className="tl-collaborator__cursor-hint"
98
- key={userId + '_cursor_hint'}
99
- userId={userId}
100
- point={cursor}
101
- color={color}
102
- zoom={zoomLevel}
103
- viewport={viewportPageBounds}
104
- />
105
- ) : null}
106
- {CollaboratorScribble && scribbles.length ? (
107
- <>
108
- {scribbles.map((scribble) => (
109
- <CollaboratorScribble
110
- key={userId + '_scribble_' + scribble.id}
111
- className="tl-collaborator__scribble"
112
- userId={userId}
113
- scribble={scribble}
114
- color={color}
115
- zoom={zoomLevel}
116
- opacity={scribble.color === 'laser' ? 0.5 : 0.1}
117
- />
118
- ))}
119
- </>
120
- ) : null}
121
- {CollaboratorShapeIndicator &&
122
- selectedShapeIds
123
- .filter((id) => {
124
- // Skip hidden shapes
125
- if (editor.isShapeHidden(id)) return false
126
- // When canvas indicators are disabled, render SVG indicators for
127
- // every selected shape. Otherwise shapes that opt into the canvas
128
- // path via useLegacyIndicator() === false would never get a
129
- // collaborator indicator drawn.
130
- if (!editor.options.useCanvasIndicators) return true
131
- // Otherwise, only render SVG indicators for shapes that opt in to
132
- // the legacy path — canvas-based indicators are handled by
133
- // CanvasShapeIndicators.
134
- const shape = editor.getShape(id)
135
- if (!shape) return false
136
- const util = editor.getShapeUtil(shape)
137
- return util.useLegacyIndicator()
138
- })
139
- .map((shapeId) => (
140
- <CollaboratorShapeIndicator
141
- className="tl-collaborator__shape-indicator"
142
- key={userId + '_' + shapeId}
143
- userId={userId}
144
- shapeId={shapeId}
145
- color={color}
146
- opacity={0.5}
147
- />
148
- ))}
149
- </>
150
- )
151
- })
152
-
153
- function useCollaboratorState(
154
- editor: Editor,
155
- latestPresence: TLInstancePresence | null
156
- ): CollaboratorState {
157
- const rLastActivityTimestamp = useRef(latestPresence?.lastActivityTimestamp ?? -1)
158
-
159
- const [state, setState] = useState<CollaboratorState>(() =>
160
- getCollaboratorStateFromElapsedTime(editor, Date.now() - rLastActivityTimestamp.current)
161
- )
162
-
163
- useEffect(() => {
164
- const interval = editor.timers.setInterval(() => {
165
- setState(
166
- getCollaboratorStateFromElapsedTime(editor, Date.now() - rLastActivityTimestamp.current)
167
- )
168
- }, editor.options.collaboratorCheckIntervalMs)
169
-
170
- return () => clearInterval(interval)
171
- }, [editor])
172
-
173
- if (latestPresence) {
174
- // We can do this on every render, it's free and cheaper than an effect
175
- // remember, there can be lots and lots of cursors moving around all the time
176
- rLastActivityTimestamp.current = latestPresence.lastActivityTimestamp ?? Infinity
177
- }
178
-
179
- return state
180
- }
@@ -1,300 +0,0 @@
1
- import { useComputed, useQuickReactor } from '@tldraw/state-react'
2
- import { createComputedCache } from '@tldraw/store'
3
- import { TLShape, TLShapeId } from '@tldraw/tlschema'
4
- import { dedupe } from '@tldraw/utils'
5
- import { memo, useEffect, useRef } from 'react'
6
- import { Editor } from '../../editor/Editor'
7
- import { TLIndicatorPath } from '../../editor/shapes/ShapeUtil'
8
- import { getComputedStyle } from '../../exports/domUtils'
9
- import { useColorMode } from '../../hooks/useColorMode'
10
- import { useEditor } from '../../hooks/useEditor'
11
- import { useActivePeerIds$ } from '../../hooks/usePeerIds'
12
-
13
- interface CollaboratorIndicatorData {
14
- color: string
15
- shapeIds: TLShapeId[]
16
- }
17
-
18
- interface RenderData {
19
- idsToDisplay: Set<TLShapeId>
20
- renderingShapeIds: Set<TLShapeId>
21
- hintingShapeIds: TLShapeId[]
22
- collaboratorIndicators: CollaboratorIndicatorData[]
23
- }
24
-
25
- function setsEqual<T>(a: Set<T>, b: Set<T>): boolean {
26
- if (a.size !== b.size) return false
27
- for (const item of a) {
28
- if (!b.has(item)) return false
29
- }
30
- return true
31
- }
32
-
33
- function arraysEqual<T>(a: readonly T[], b: readonly T[]): boolean {
34
- if (a.length !== b.length) return false
35
- for (let i = 0; i < a.length; i++) {
36
- if (a[i] !== b[i]) return false
37
- }
38
- return true
39
- }
40
-
41
- function collaboratorIndicatorsEqual(
42
- a: CollaboratorIndicatorData[],
43
- b: CollaboratorIndicatorData[]
44
- ): boolean {
45
- if (a.length !== b.length) return false
46
- for (let i = 0; i < a.length; i++) {
47
- if (a[i].color !== b[i].color) return false
48
- if (!arraysEqual(a[i].shapeIds, b[i].shapeIds)) return false
49
- }
50
- return true
51
- }
52
-
53
- function renderDataEqual(a: RenderData, b: RenderData): boolean {
54
- return (
55
- setsEqual(a.idsToDisplay, b.idsToDisplay) &&
56
- setsEqual(a.renderingShapeIds, b.renderingShapeIds) &&
57
- arraysEqual(a.hintingShapeIds, b.hintingShapeIds) &&
58
- collaboratorIndicatorsEqual(a.collaboratorIndicators, b.collaboratorIndicators)
59
- )
60
- }
61
-
62
- const indicatorPathCache = createComputedCache(
63
- 'indicatorPath',
64
- (editor: Editor, shape: TLShape) => {
65
- const util = editor.getShapeUtil(shape)
66
- return util.getIndicatorPath(shape)
67
- }
68
- )
69
-
70
- const getIndicatorPath = (editor: Editor, shape: TLShape) => {
71
- return indicatorPathCache.get(editor, shape.id)
72
- }
73
-
74
- function renderShapeIndicator(
75
- ctx: CanvasRenderingContext2D,
76
- editor: Editor,
77
- shapeId: TLShapeId,
78
- renderingShapeIds: Set<TLShapeId>
79
- ): boolean {
80
- if (!renderingShapeIds.has(shapeId)) return false
81
-
82
- const shape = editor.getShape(shapeId)
83
- if (!shape || shape.isLocked) return false
84
-
85
- const pageTransform = editor.getShapePageTransform(shape)
86
- if (!pageTransform) return false
87
-
88
- const indicatorPath = getIndicatorPath(editor, shape)
89
- if (!indicatorPath) return false
90
-
91
- ctx.save()
92
- ctx.transform(
93
- pageTransform.a,
94
- pageTransform.b,
95
- pageTransform.c,
96
- pageTransform.d,
97
- pageTransform.e,
98
- pageTransform.f
99
- )
100
- renderIndicatorPath(ctx, indicatorPath)
101
- ctx.restore()
102
-
103
- return true
104
- }
105
-
106
- function renderIndicatorPath(ctx: CanvasRenderingContext2D, indicatorPath: TLIndicatorPath) {
107
- if (indicatorPath instanceof Path2D) {
108
- ctx.stroke(indicatorPath)
109
- } else {
110
- const { path, clipPath, additionalPaths } = indicatorPath
111
-
112
- if (clipPath) {
113
- ctx.save()
114
- ctx.clip(clipPath, 'evenodd')
115
- ctx.stroke(path)
116
- ctx.restore()
117
- } else {
118
- ctx.stroke(path)
119
- }
120
-
121
- if (additionalPaths) {
122
- for (const additionalPath of additionalPaths) {
123
- ctx.stroke(additionalPath)
124
- }
125
- }
126
- }
127
- }
128
-
129
- /** @internal @react */
130
- export const CanvasShapeIndicators = memo(function CanvasShapeIndicators() {
131
- const editor = useEditor()
132
-
133
- // Skip canvas indicator rendering when the option is disabled (e.g. A/B test)
134
- if (!editor.options.useCanvasIndicators) {
135
- return null
136
- }
137
-
138
- return <CanvasShapeIndicatorsInner />
139
- })
140
-
141
- const CanvasShapeIndicatorsInner = memo(function CanvasShapeIndicatorsInner() {
142
- const editor = useEditor()
143
- const canvasRef = useRef<HTMLCanvasElement>(null)
144
-
145
- // Cache the selected color to avoid getComputedStyle on every render
146
- const rSelectedColor = useRef<string | null>(null)
147
- const colorMode = useColorMode()
148
-
149
- useEffect(() => {
150
- const timer = editor.timers.setTimeout(() => {
151
- rSelectedColor.current = null
152
- }, 0)
153
- return () => clearTimeout(timer)
154
- }, [colorMode, editor])
155
-
156
- // Get active peer IDs (already handles time-based state transitions)
157
- const activePeerIds$ = useActivePeerIds$()
158
-
159
- const $renderData = useComputed(
160
- 'indicator render data',
161
- () => {
162
- const renderingShapeIds = new Set(editor.getRenderingShapes().map((s) => s.id))
163
-
164
- // Compute ids to display for selected/hovered shapes
165
- const idsToDisplay = new Set<TLShapeId>()
166
- const instanceState = editor.getInstanceState()
167
- const isChangingStyle = instanceState.isChangingStyle
168
- const isIdleOrEditing = editor.isInAny('select.idle', 'select.editing_shape')
169
- const isInSelectState = editor.isInAny(
170
- 'select.brushing',
171
- 'select.scribble_brushing',
172
- 'select.pointing_shape',
173
- 'select.pointing_selection',
174
- 'select.pointing_handle'
175
- )
176
-
177
- if (!isChangingStyle && (isIdleOrEditing || isInSelectState)) {
178
- for (const id of editor.getSelectedShapeIds()) {
179
- idsToDisplay.add(id)
180
- }
181
- if (isIdleOrEditing && instanceState.isHoveringCanvas && !instanceState.isCoarsePointer) {
182
- const hovered = editor.getHoveredShapeId()
183
- if (hovered) idsToDisplay.add(hovered)
184
- }
185
- }
186
-
187
- // Compute hinting shape ids
188
- const hintingShapeIds = dedupe(editor.getHintingShapeIds())
189
-
190
- // Compute collaborator indicators
191
- const collaboratorIndicators: CollaboratorIndicatorData[] = []
192
- const currentPageId = editor.getCurrentPageId()
193
- const activePeerIds = activePeerIds$.get()
194
-
195
- const collaborators = editor.getCollaborators()
196
- for (const peerId of activePeerIds.values()) {
197
- // Skip collaborators on different pages
198
- const presence = collaborators.find((c) => c.userId === peerId)
199
- if (!presence || presence.currentPageId !== currentPageId) continue
200
-
201
- // Filter to shapes that are visible and on the current rendering set
202
- const visibleShapeIds = presence.selectedShapeIds.filter(
203
- (id) => renderingShapeIds.has(id) && !editor.isShapeHidden(id)
204
- )
205
-
206
- if (visibleShapeIds.length > 0) {
207
- collaboratorIndicators.push({
208
- color: presence.color,
209
- shapeIds: visibleShapeIds,
210
- })
211
- }
212
- }
213
-
214
- return {
215
- idsToDisplay,
216
- renderingShapeIds,
217
- hintingShapeIds,
218
- collaboratorIndicators,
219
- }
220
- },
221
- { isEqual: renderDataEqual },
222
- [editor, activePeerIds$]
223
- )
224
-
225
- useQuickReactor(
226
- 'canvas indicators render',
227
- () => {
228
- const canvas = canvasRef.current
229
- if (!canvas) return
230
-
231
- const ctx = canvas.getContext('2d')
232
- if (!ctx) return
233
-
234
- const { idsToDisplay, renderingShapeIds, hintingShapeIds, collaboratorIndicators } =
235
- $renderData.get()
236
-
237
- const { w, h } = editor.getViewportScreenBounds()
238
- const dpr = editor.getInstanceState().devicePixelRatio
239
- const { x: cx, y: cy, z: zoom } = editor.getCamera()
240
-
241
- const canvasWidth = Math.ceil(w * dpr)
242
- const canvasHeight = Math.ceil(h * dpr)
243
-
244
- if (canvas.width !== canvasWidth || canvas.height !== canvasHeight) {
245
- canvas.width = canvasWidth
246
- canvas.height = canvasHeight
247
- canvas.style.width = `${w}px`
248
- canvas.style.height = `${h}px`
249
- }
250
-
251
- ctx.resetTransform()
252
- ctx.clearRect(0, 0, canvas.width, canvas.height)
253
-
254
- ctx.scale(dpr, dpr)
255
- ctx.scale(zoom, zoom)
256
- ctx.translate(cx, cy)
257
-
258
- ctx.lineCap = 'round'
259
- ctx.lineJoin = 'round'
260
-
261
- // Draw collaborator indicators first (underneath local indicators)
262
- // Use 0.5 opacity to match the original SVG-based collaborator indicators
263
- ctx.lineWidth = 1.5 / zoom
264
- for (const collaborator of collaboratorIndicators) {
265
- ctx.strokeStyle = collaborator.color
266
- ctx.globalAlpha = 0.7
267
- for (const shapeId of collaborator.shapeIds) {
268
- renderShapeIndicator(ctx, editor, shapeId, renderingShapeIds)
269
- }
270
- }
271
-
272
- // Reset alpha for local indicators
273
- ctx.globalAlpha = 1.0
274
-
275
- // Use cached color, only call getComputedStyle when cache is empty
276
- if (!rSelectedColor.current) {
277
- rSelectedColor.current = getComputedStyle(canvas).getPropertyValue('--tl-color-selected')
278
- }
279
-
280
- ctx.strokeStyle = rSelectedColor.current
281
-
282
- // Draw selected/hovered indicators (1.5px stroke)
283
- ctx.lineWidth = 1.5 / zoom
284
- for (const shapeId of idsToDisplay) {
285
- renderShapeIndicator(ctx, editor, shapeId, renderingShapeIds)
286
- }
287
-
288
- // Draw hinted indicators with a thicker stroke (2.5px)
289
- if (hintingShapeIds.length > 0) {
290
- ctx.lineWidth = 2.5 / zoom
291
- for (const shapeId of hintingShapeIds) {
292
- renderShapeIndicator(ctx, editor, shapeId, renderingShapeIds)
293
- }
294
- }
295
- },
296
- [editor, $renderData]
297
- )
298
-
299
- return <canvas ref={canvasRef} className="tl-canvas-indicators" />
300
- })
@@ -1,35 +0,0 @@
1
- import { BoxModel } from '@tldraw/tlschema'
2
- import { useRef } from 'react'
3
- import { useTransform } from '../../hooks/useTransform'
4
- import { toDomPrecision } from '../../primitives/utils'
5
-
6
- /** @public */
7
- export interface TLBrushProps {
8
- userId?: string
9
- brush: BoxModel
10
- color?: string
11
- opacity?: number
12
- className?: string
13
- }
14
-
15
- /** @public @react */
16
- export const DefaultBrush = ({ brush, color, opacity, className }: TLBrushProps) => {
17
- const rSvg = useRef<SVGSVGElement>(null)
18
- useTransform(rSvg, brush.x, brush.y)
19
-
20
- const w = toDomPrecision(Math.max(1, brush.w))
21
- const h = toDomPrecision(Math.max(1, brush.h))
22
-
23
- return (
24
- <svg className="tl-overlays__item" ref={rSvg} aria-hidden="true">
25
- {color ? (
26
- <g className="tl-brush" opacity={opacity}>
27
- <rect width={w} height={h} fill={color} opacity={0.75} />
28
- <rect width={w} height={h} fill="none" stroke={color} opacity={0.1} />
29
- </g>
30
- ) : (
31
- <rect className={`tl-brush tl-brush__default ${className}`} width={w} height={h} />
32
- )}
33
- </svg>
34
- )
35
- }
@@ -1,52 +0,0 @@
1
- import { VecModel } from '@tldraw/tlschema'
2
- import classNames from 'classnames'
3
- import { useRef } from 'react'
4
- import { useSharedSafeId } from '../../hooks/useSafeId'
5
- import { useTransform } from '../../hooks/useTransform'
6
- import { Box } from '../../primitives/Box'
7
- import { clamp } from '../../primitives/utils'
8
- import { Vec } from '../../primitives/Vec'
9
-
10
- /** @public */
11
- export interface TLCollaboratorHintProps {
12
- userId: string
13
- className?: string
14
- point: VecModel
15
- viewport: Box
16
- zoom: number
17
- opacity?: number
18
- color: string
19
- }
20
-
21
- /** @public @react */
22
- export function DefaultCollaboratorHint({
23
- className,
24
- zoom,
25
- point,
26
- color,
27
- viewport,
28
- opacity = 1,
29
- }: TLCollaboratorHintProps) {
30
- const rSvg = useRef<SVGSVGElement>(null)
31
-
32
- useTransform(
33
- rSvg,
34
- clamp(point.x, viewport.minX + 5 / zoom, viewport.maxX - 5 / zoom),
35
- clamp(point.y, viewport.minY + 5 / zoom, viewport.maxY - 5 / zoom),
36
- 1 / zoom,
37
- Vec.Angle(viewport.center, point)
38
- )
39
- const cursorHintId = useSharedSafeId('cursor_hint')
40
-
41
- return (
42
- <svg ref={rSvg} className={classNames('tl-overlays__item', className)} aria-hidden="true">
43
- <use
44
- href={`#${cursorHintId}`}
45
- color={color}
46
- strokeWidth={3}
47
- stroke="var(--tl-color-background)"
48
- />
49
- <use href={`#${cursorHintId}`} color={color} opacity={opacity} />
50
- </svg>
51
- )
52
- }
@@ -1,59 +0,0 @@
1
- import { VecModel } from '@tldraw/tlschema'
2
- import classNames from 'classnames'
3
- import { memo, useRef } from 'react'
4
- import { useSharedSafeId } from '../../hooks/useSafeId'
5
- import { useTransform } from '../../hooks/useTransform'
6
-
7
- /** @public */
8
- export interface TLCursorProps {
9
- userId: string
10
- className?: string
11
- point: VecModel | null
12
- zoom: number
13
- color?: string
14
- name: string | null
15
- chatMessage: string
16
- }
17
-
18
- /** @public @react */
19
- export const DefaultCursor = memo(function DefaultCursor({
20
- className,
21
- zoom,
22
- point,
23
- color,
24
- name,
25
- chatMessage,
26
- }: TLCursorProps) {
27
- const rCursor = useRef<HTMLDivElement>(null)
28
- useTransform(rCursor, point?.x, point?.y, 1 / zoom)
29
-
30
- const cursorId = useSharedSafeId('cursor')
31
-
32
- if (!point) return null
33
-
34
- return (
35
- <div ref={rCursor} className={classNames('tl-overlays__item', className)}>
36
- <svg className="tl-cursor" aria-hidden="true">
37
- <use href={`#${cursorId}`} color={color} />
38
- </svg>
39
- {chatMessage ? (
40
- <>
41
- {name && (
42
- <div className="tl-nametag-title" style={{ color }}>
43
- {name}
44
- </div>
45
- )}
46
- <div className="tl-nametag-chat" style={{ backgroundColor: color }}>
47
- {chatMessage}
48
- </div>
49
- </>
50
- ) : (
51
- name && (
52
- <div className="tl-nametag" style={{ backgroundColor: color }}>
53
- {name}
54
- </div>
55
- )
56
- )}
57
- </div>
58
- )
59
- })
@@ -1,42 +0,0 @@
1
- import { TLHandle, TLShapeId } from '@tldraw/tlschema'
2
- import classNames from 'classnames'
3
- import { SIDES } from '../../constants'
4
- import { useEditor } from '../../hooks/useEditor'
5
-
6
- /** @public */
7
- export interface TLHandleProps {
8
- shapeId: TLShapeId
9
- handle: TLHandle
10
- zoom: number
11
- isCoarse: boolean
12
- className?: string
13
- }
14
-
15
- /** @public @react */
16
- export function DefaultHandle({ handle, isCoarse, className, zoom }: TLHandleProps) {
17
- const editor = useEditor()
18
- const br = (isCoarse ? editor.options.coarseHandleRadius : editor.options.handleRadius) / zoom
19
-
20
- if (handle.type === 'clone') {
21
- // bouba
22
- const fr = 3 / zoom
23
- const path = `M0,${-fr} A${fr},${fr} 0 0,1 0,${fr}`
24
-
25
- const index = SIDES.indexOf(handle.id as (typeof SIDES)[number])
26
- return (
27
- <g className={classNames(`tl-handle tl-handle__${handle.type}`, className)}>
28
- <circle className="tl-handle__bg" r={br} />
29
- {/* Half circle */}
30
- <path className="tl-handle__fg" d={path} transform={`rotate(${-90 + 90 * index})`} />
31
- </g>
32
- )
33
- }
34
-
35
- const fr = (handle.type === 'create' && isCoarse ? 3 : 4) / Math.max(zoom, 0.25)
36
- return (
37
- <g className={classNames(`tl-handle tl-handle__${handle.type}`, className)}>
38
- <circle className="tl-handle__bg" r={br} />
39
- <circle className="tl-handle__fg" r={fr} />
40
- </g>
41
- )
42
- }
@@ -1,15 +0,0 @@
1
- import { ReactNode } from 'react'
2
-
3
- /** @public */
4
- export interface TLHandlesProps {
5
- children: ReactNode
6
- }
7
-
8
- /** @public @react */
9
- export const DefaultHandles = ({ children }: TLHandlesProps) => {
10
- return (
11
- <svg className="tl-user-handles tl-overlays__item" aria-hidden="true">
12
- {children}
13
- </svg>
14
- )
15
- }