@tldraw/editor 4.4.0-next.84d68f44c848 → 4.4.0-next.f181afb0ab39
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.
- package/dist-cjs/index.d.ts +175 -11
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/components/LiveCollaborators.js +14 -24
- package/dist-cjs/lib/components/LiveCollaborators.js.map +2 -2
- package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js +201 -0
- package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js.map +7 -0
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +29 -14
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +3 -1
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicators.js +13 -1
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicators.js.map +2 -2
- package/dist-cjs/lib/config/TLUserPreferences.js +9 -3
- package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +4 -1
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js +378 -89
- package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +8 -3
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js +29 -0
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/hooks/usePeerIds.js +29 -0
- package/dist-cjs/lib/hooks/usePeerIds.js.map +2 -2
- package/dist-cjs/lib/options.js +1 -0
- package/dist-cjs/lib/options.js.map +2 -2
- package/dist-cjs/lib/utils/collaboratorState.js +42 -0
- package/dist-cjs/lib/utils/collaboratorState.js.map +7 -0
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +175 -11
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/components/LiveCollaborators.mjs +17 -24
- package/dist-esm/lib/components/LiveCollaborators.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs +181 -0
- package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs.map +7 -0
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +29 -14
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +3 -1
- package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultShapeIndicators.mjs +13 -1
- package/dist-esm/lib/components/default-components/DefaultShapeIndicators.mjs.map +2 -2
- package/dist-esm/lib/config/TLUserPreferences.mjs +9 -3
- package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +4 -1
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs +378 -89
- package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +8 -3
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +29 -0
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePeerIds.mjs +33 -1
- package/dist-esm/lib/hooks/usePeerIds.mjs.map +2 -2
- package/dist-esm/lib/options.mjs +1 -0
- package/dist-esm/lib/options.mjs.map +2 -2
- package/dist-esm/lib/utils/collaboratorState.mjs +22 -0
- package/dist-esm/lib/utils/collaboratorState.mjs.map +7 -0
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +6 -0
- package/package.json +7 -7
- package/src/index.ts +2 -0
- package/src/lib/components/LiveCollaborators.tsx +26 -37
- package/src/lib/components/default-components/CanvasShapeIndicators.tsx +244 -0
- package/src/lib/components/default-components/DefaultCanvas.tsx +15 -1
- package/src/lib/components/default-components/DefaultShapeIndicator.tsx +6 -1
- package/src/lib/components/default-components/DefaultShapeIndicators.tsx +16 -1
- package/src/lib/config/TLUserPreferences.test.ts +1 -0
- package/src/lib/config/TLUserPreferences.ts +8 -0
- package/src/lib/editor/Editor.ts +10 -1
- package/src/lib/editor/managers/ScribbleManager/ScribbleManager.ts +491 -106
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +24 -0
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +8 -0
- package/src/lib/editor/shapes/ShapeUtil.ts +44 -0
- package/src/lib/hooks/usePeerIds.ts +46 -1
- package/src/lib/options.ts +7 -0
- package/src/lib/utils/collaboratorState.ts +54 -0
- package/src/version.ts +3 -3
- package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +0 -621
|
@@ -31,6 +31,7 @@ describe('UserPreferencesManager', () => {
|
|
|
31
31
|
isDynamicSizeMode: false,
|
|
32
32
|
isPasteAtCursorMode: false,
|
|
33
33
|
inputMode: null,
|
|
34
|
+
isZoomDirectionInverted: false,
|
|
34
35
|
...overrides,
|
|
35
36
|
})
|
|
36
37
|
|
|
@@ -235,6 +236,7 @@ describe('UserPreferencesManager', () => {
|
|
|
235
236
|
isWrapMode: mockUserPreferences.isWrapMode,
|
|
236
237
|
isDynamicResizeMode: mockUserPreferences.isDynamicSizeMode,
|
|
237
238
|
inputMode: mockUserPreferences.inputMode,
|
|
239
|
+
isZoomDirectionInverted: mockUserPreferences.isZoomDirectionInverted,
|
|
238
240
|
})
|
|
239
241
|
})
|
|
240
242
|
|
|
@@ -475,6 +477,24 @@ describe('UserPreferencesManager', () => {
|
|
|
475
477
|
expect(userPreferencesManager.getInputMode()).toBe('mouse')
|
|
476
478
|
})
|
|
477
479
|
})
|
|
480
|
+
|
|
481
|
+
describe('getIsZoomDirectionInverted', () => {
|
|
482
|
+
it('should return user zoom direction inverted setting', () => {
|
|
483
|
+
expect(userPreferencesManager.getIsZoomDirectionInverted()).toBe(false)
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
it('should return default when null', () => {
|
|
487
|
+
userPreferencesAtom.set({ ...mockUserPreferences, isZoomDirectionInverted: null })
|
|
488
|
+
expect(userPreferencesManager.getIsZoomDirectionInverted()).toBe(
|
|
489
|
+
defaultUserPreferences.isZoomDirectionInverted
|
|
490
|
+
)
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
it('should return true when inverted', () => {
|
|
494
|
+
userPreferencesAtom.set({ ...mockUserPreferences, isZoomDirectionInverted: true })
|
|
495
|
+
expect(userPreferencesManager.getIsZoomDirectionInverted()).toBe(true)
|
|
496
|
+
})
|
|
497
|
+
})
|
|
478
498
|
})
|
|
479
499
|
|
|
480
500
|
describe('reactive behavior', () => {
|
|
@@ -536,6 +556,7 @@ describe('UserPreferencesManager', () => {
|
|
|
536
556
|
isWrapMode: null,
|
|
537
557
|
isDynamicSizeMode: null,
|
|
538
558
|
isPasteAtCursorMode: null,
|
|
559
|
+
isZoomDirectionInverted: null,
|
|
539
560
|
})
|
|
540
561
|
|
|
541
562
|
userPreferencesAtom.set(nullPrefs)
|
|
@@ -558,6 +579,9 @@ describe('UserPreferencesManager', () => {
|
|
|
558
579
|
expect(userPreferencesManager.getIsPasteAtCursorMode()).toBe(
|
|
559
580
|
defaultUserPreferences.isPasteAtCursorMode
|
|
560
581
|
)
|
|
582
|
+
expect(userPreferencesManager.getIsZoomDirectionInverted()).toBe(
|
|
583
|
+
defaultUserPreferences.isZoomDirectionInverted
|
|
584
|
+
)
|
|
561
585
|
})
|
|
562
586
|
|
|
563
587
|
it('should handle matchMedia with null response', () => {
|
|
@@ -51,6 +51,7 @@ export class UserPreferencesManager {
|
|
|
51
51
|
isDynamicResizeMode: this.getIsDynamicResizeMode(),
|
|
52
52
|
enhancedA11yMode: this.getEnhancedA11yMode(),
|
|
53
53
|
inputMode: this.getInputMode(),
|
|
54
|
+
isZoomDirectionInverted: this.getIsZoomDirectionInverted(),
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
|
|
@@ -131,4 +132,11 @@ export class UserPreferencesManager {
|
|
|
131
132
|
@computed getInputMode() {
|
|
132
133
|
return this.user.userPreferences.get().inputMode ?? defaultUserPreferences.inputMode
|
|
133
134
|
}
|
|
135
|
+
|
|
136
|
+
@computed getIsZoomDirectionInverted() {
|
|
137
|
+
return (
|
|
138
|
+
this.user.userPreferences.get().isZoomDirectionInverted ??
|
|
139
|
+
defaultUserPreferences.isZoomDirectionInverted
|
|
140
|
+
)
|
|
141
|
+
}
|
|
134
142
|
}
|
|
@@ -75,6 +75,19 @@ export interface TLShapeUtilCanvasSvgDef {
|
|
|
75
75
|
component: React.ComponentType
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Return type for {@link ShapeUtil.getIndicatorPath}. Can be either a simple Path2D
|
|
80
|
+
* or an object with additional rendering info like clip paths for complex indicators.
|
|
81
|
+
* @public
|
|
82
|
+
*/
|
|
83
|
+
export type TLIndicatorPath =
|
|
84
|
+
| Path2D
|
|
85
|
+
| {
|
|
86
|
+
path: Path2D
|
|
87
|
+
clipPath?: Path2D
|
|
88
|
+
additionalPaths?: Path2D[]
|
|
89
|
+
}
|
|
90
|
+
|
|
78
91
|
/** @public */
|
|
79
92
|
export abstract class ShapeUtil<Shape extends TLShape = TLShape> {
|
|
80
93
|
/** Configure this shape utils {@link ShapeUtil.options | `options`}. */
|
|
@@ -173,6 +186,37 @@ export abstract class ShapeUtil<Shape extends TLShape = TLShape> {
|
|
|
173
186
|
*/
|
|
174
187
|
abstract indicator(shape: Shape): any
|
|
175
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Whether to use the legacy React-based indicator rendering.
|
|
191
|
+
*
|
|
192
|
+
* Override this to return `false` if your shape implements {@link ShapeUtil.getIndicatorPath}
|
|
193
|
+
* for canvas-based indicator rendering.
|
|
194
|
+
*
|
|
195
|
+
* @returns `true` to use SVG indicators (default), `false` to use canvas indicators.
|
|
196
|
+
* @public
|
|
197
|
+
*/
|
|
198
|
+
useLegacyIndicator(): boolean {
|
|
199
|
+
return true
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Get a Path2D for rendering the shape's indicator on the canvas.
|
|
204
|
+
*
|
|
205
|
+
* When implemented, this is used instead of {@link ShapeUtil.indicator} for more
|
|
206
|
+
* efficient canvas-based indicator rendering. Shapes that return `undefined` will
|
|
207
|
+
* fall back to SVG-based rendering via {@link ShapeUtil.indicator}.
|
|
208
|
+
*
|
|
209
|
+
* For complex indicators that need clipping (e.g., arrows with labels), return an
|
|
210
|
+
* object with `path`, `clipPath`, and `additionalPaths` properties.
|
|
211
|
+
*
|
|
212
|
+
* @param shape - The shape.
|
|
213
|
+
* @returns A Path2D to stroke, or an object with clipping info, or undefined to use SVG fallback.
|
|
214
|
+
* @public
|
|
215
|
+
*/
|
|
216
|
+
getIndicatorPath(shape: Shape): TLIndicatorPath | undefined {
|
|
217
|
+
return undefined
|
|
218
|
+
}
|
|
219
|
+
|
|
176
220
|
/**
|
|
177
221
|
* Get the font faces that should be rendered in the document in order for this shape to render
|
|
178
222
|
* correctly.
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import { useComputed, useValue } from '@tldraw/state-react'
|
|
1
|
+
import { useAtom, useComputed, useValue } from '@tldraw/state-react'
|
|
2
|
+
import { isEqual } from '@tldraw/utils'
|
|
3
|
+
import { useEffect } from 'react'
|
|
4
|
+
import {
|
|
5
|
+
getCollaboratorStateFromElapsedTime,
|
|
6
|
+
shouldShowCollaborator,
|
|
7
|
+
} from '../utils/collaboratorState'
|
|
2
8
|
import { uniq } from '../utils/uniq'
|
|
3
9
|
import { useEditor } from './useEditor'
|
|
4
10
|
|
|
@@ -19,3 +25,42 @@ export function usePeerIds() {
|
|
|
19
25
|
|
|
20
26
|
return useValue($userIds)
|
|
21
27
|
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Returns a computed signal of active peer user IDs that should be shown.
|
|
31
|
+
* Automatically re-evaluates on an interval to handle time-based state transitions
|
|
32
|
+
* (active -> idle -> inactive).
|
|
33
|
+
*
|
|
34
|
+
* @returns A computed signal containing a Set of active peer user IDs
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export function useActivePeerIds$() {
|
|
38
|
+
const $time = useAtom('peerIdsTime', Date.now())
|
|
39
|
+
const editor = useEditor()
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const interval = editor.timers.setInterval(() => {
|
|
42
|
+
$time.set(Date.now())
|
|
43
|
+
}, editor.options.collaboratorCheckIntervalMs)
|
|
44
|
+
|
|
45
|
+
return () => clearInterval(interval)
|
|
46
|
+
}, [editor, $time])
|
|
47
|
+
|
|
48
|
+
return useComputed(
|
|
49
|
+
'activePeerIds',
|
|
50
|
+
() => {
|
|
51
|
+
const now = $time.get()
|
|
52
|
+
return new Set(
|
|
53
|
+
editor
|
|
54
|
+
.getCollaborators()
|
|
55
|
+
.filter((p) => {
|
|
56
|
+
const elapsed = Math.max(0, now - (p.lastActivityTimestamp ?? Infinity))
|
|
57
|
+
const state = getCollaboratorStateFromElapsedTime(editor, elapsed)
|
|
58
|
+
return shouldShowCollaborator(editor, p, state)
|
|
59
|
+
})
|
|
60
|
+
.map((p) => p.userId)
|
|
61
|
+
)
|
|
62
|
+
},
|
|
63
|
+
{ isEqual },
|
|
64
|
+
[editor]
|
|
65
|
+
)
|
|
66
|
+
}
|
package/src/lib/options.ts
CHANGED
|
@@ -54,6 +54,12 @@ export interface TldrawOptions {
|
|
|
54
54
|
readonly flattenImageBoundsExpand: number
|
|
55
55
|
readonly flattenImageBoundsPadding: number
|
|
56
56
|
readonly laserDelayMs: number
|
|
57
|
+
/**
|
|
58
|
+
* How long (in milliseconds) to fade all laser scribbles after the session ends.
|
|
59
|
+
* The total points across all scribbles will be removed proportionally over this duration.
|
|
60
|
+
* Defaults to 500ms (0.5 seconds).
|
|
61
|
+
*/
|
|
62
|
+
readonly laserFadeoutMs: number
|
|
57
63
|
readonly maxExportDelayMs: number
|
|
58
64
|
readonly tooltipDelayMs: number
|
|
59
65
|
/**
|
|
@@ -157,6 +163,7 @@ export const defaultTldrawOptions = {
|
|
|
157
163
|
flattenImageBoundsExpand: 64,
|
|
158
164
|
flattenImageBoundsPadding: 16,
|
|
159
165
|
laserDelayMs: 1200,
|
|
166
|
+
laserFadeoutMs: 500,
|
|
160
167
|
maxExportDelayMs: 5000,
|
|
161
168
|
tooltipDelayMs: 700,
|
|
162
169
|
temporaryAssetPreviewLifetimeMs: 180000,
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { TLInstancePresence } from '@tldraw/tlschema'
|
|
2
|
+
import { Editor } from '../editor/Editor'
|
|
3
|
+
|
|
4
|
+
/** The activity state of a collaborator */
|
|
5
|
+
export type CollaboratorState = 'active' | 'idle' | 'inactive'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get the activity state of a collaborator based on elapsed time since their last activity.
|
|
9
|
+
*
|
|
10
|
+
* @param editor - The editor instance
|
|
11
|
+
* @param elapsed - Time in milliseconds since the collaborator's last activity
|
|
12
|
+
* @returns The collaborator's activity state
|
|
13
|
+
*/
|
|
14
|
+
export function getCollaboratorStateFromElapsedTime(
|
|
15
|
+
editor: Editor,
|
|
16
|
+
elapsed: number
|
|
17
|
+
): CollaboratorState {
|
|
18
|
+
return elapsed > editor.options.collaboratorInactiveTimeoutMs
|
|
19
|
+
? 'inactive'
|
|
20
|
+
: elapsed > editor.options.collaboratorIdleTimeoutMs
|
|
21
|
+
? 'idle'
|
|
22
|
+
: 'active'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Determine whether a collaborator should be shown based on their activity state
|
|
27
|
+
* and the current instance state (following, highlighted users, etc.).
|
|
28
|
+
*
|
|
29
|
+
* @param editor - The editor instance
|
|
30
|
+
* @param presence - The collaborator's presence data
|
|
31
|
+
* @param state - The collaborator's activity state
|
|
32
|
+
* @returns Whether the collaborator should be shown
|
|
33
|
+
*/
|
|
34
|
+
export function shouldShowCollaborator(
|
|
35
|
+
editor: Editor,
|
|
36
|
+
presence: TLInstancePresence,
|
|
37
|
+
state: CollaboratorState
|
|
38
|
+
): boolean {
|
|
39
|
+
const { followingUserId, highlightedUserIds } = editor.getInstanceState()
|
|
40
|
+
|
|
41
|
+
switch (state) {
|
|
42
|
+
case 'inactive':
|
|
43
|
+
// If they're inactive, only show if we're following them or they're highlighted
|
|
44
|
+
return followingUserId === presence.userId || highlightedUserIds.includes(presence.userId)
|
|
45
|
+
case 'idle':
|
|
46
|
+
// If they're idle and following us, hide them unless they have a chat message or are highlighted
|
|
47
|
+
if (presence.followingUserId === editor.user.getId()) {
|
|
48
|
+
return !!(presence.chatMessage || highlightedUserIds.includes(presence.userId))
|
|
49
|
+
}
|
|
50
|
+
return true
|
|
51
|
+
case 'active':
|
|
52
|
+
return true
|
|
53
|
+
}
|
|
54
|
+
}
|
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 = '4.4.0-next.
|
|
4
|
+
export const version = '4.4.0-next.f181afb0ab39'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2025-09-18T14:39:22.803Z',
|
|
7
|
-
minor: '2026-01-
|
|
8
|
-
patch: '2026-01-
|
|
7
|
+
minor: '2026-01-28T14:03:05.785Z',
|
|
8
|
+
patch: '2026-01-28T14:03:05.785Z',
|
|
9
9
|
}
|