@tldraw/editor 4.4.0-next.84d68f44c848 → 4.4.0-next.bde73a32273d
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 +277 -30
- package/dist-cjs/index.js +1 -6
- package/dist-cjs/index.js.map +3 -3
- package/dist-cjs/lib/TldrawEditor.js +20 -8
- package/dist-cjs/lib/TldrawEditor.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/Shape.js +12 -17
- package/dist-cjs/lib/components/Shape.js.map +2 -2
- package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js +226 -0
- package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js.map +7 -0
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +45 -15
- 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/config/createTLStore.js.map +1 -1
- package/dist-cjs/lib/editor/Editor.js +39 -18
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +32 -13
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +2 -2
- package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js +2 -3
- package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js +13 -38
- package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +3 -3
- 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/editor/tools/RootState.js +0 -13
- package/dist-cjs/lib/editor/tools/RootState.js.map +2 -2
- package/dist-cjs/lib/hooks/usePeerIds.js +35 -0
- package/dist-cjs/lib/hooks/usePeerIds.js.map +2 -2
- package/dist-cjs/lib/hooks/useShapeCulling.js +75 -0
- package/dist-cjs/lib/hooks/useShapeCulling.js.map +7 -0
- package/dist-cjs/lib/license/LicenseManager.js +6 -6
- package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
- package/dist-cjs/lib/options.js +7 -1
- 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 +277 -30
- package/dist-esm/index.mjs +1 -6
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/TldrawEditor.mjs +20 -8
- package/dist-esm/lib/TldrawEditor.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/Shape.mjs +12 -17
- package/dist-esm/lib/components/Shape.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs +206 -0
- package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs.map +7 -0
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +45 -15
- 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/config/createTLStore.mjs.map +1 -1
- package/dist-esm/lib/editor/Editor.mjs +39 -18
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +32 -13
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs +2 -3
- package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs +14 -39
- package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.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/editor/tools/RootState.mjs +0 -13
- package/dist-esm/lib/editor/tools/RootState.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePeerIds.mjs +39 -1
- package/dist-esm/lib/hooks/usePeerIds.mjs.map +2 -2
- package/dist-esm/lib/hooks/useShapeCulling.mjs +55 -0
- package/dist-esm/lib/hooks/useShapeCulling.mjs.map +7 -0
- package/dist-esm/lib/license/LicenseManager.mjs +6 -6
- package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
- package/dist-esm/lib/options.mjs +7 -1
- 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 +28 -11
- package/package.json +8 -11
- package/src/index.ts +2 -5
- package/src/lib/TldrawEditor.tsx +35 -13
- package/src/lib/components/LiveCollaborators.tsx +26 -37
- package/src/lib/components/Shape.tsx +15 -16
- package/src/lib/components/default-components/CanvasShapeIndicators.tsx +288 -0
- package/src/lib/components/default-components/DefaultCanvas.tsx +39 -3
- 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/config/createTLStore.ts +1 -1
- package/src/lib/editor/Editor.ts +72 -20
- package/src/lib/editor/derivations/notVisibleShapes.ts +39 -17
- package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +0 -35
- package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.ts +4 -8
- package/src/lib/editor/managers/HistoryManager/HistoryManager.ts +19 -47
- 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 +63 -5
- package/src/lib/editor/tools/RootState.ts +0 -16
- package/src/lib/hooks/usePeerIds.ts +53 -1
- package/src/lib/hooks/useShapeCulling.tsx +98 -0
- package/src/lib/license/LicenseManager.ts +6 -6
- package/src/lib/options.ts +48 -2
- 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
|
}
|
|
@@ -35,17 +35,31 @@ export interface TLShapeUtilConstructor<T extends TLShape, U extends ShapeUtil<T
|
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Options passed to {@link ShapeUtil.canBind}. A binding that could be made. At least one of
|
|
38
|
-
* `
|
|
38
|
+
* `fromShape` or `toShape` will belong to this shape util.
|
|
39
|
+
*
|
|
40
|
+
* The shapes may be full {@link @tldraw/tlschema#TLShape} objects when available, or just
|
|
41
|
+
* `{ type }` stubs when the shape hasn't been created yet (e.g. during arrow creation). Use
|
|
42
|
+
* `'id' in shape` to check whether the full shape is available.
|
|
39
43
|
*
|
|
40
44
|
* @public
|
|
41
45
|
*/
|
|
42
46
|
export interface TLShapeUtilCanBindOpts<Shape extends TLShape = TLShape> {
|
|
43
|
-
/** The
|
|
44
|
-
|
|
45
|
-
/** The
|
|
46
|
-
|
|
47
|
+
/** The shape referenced by the `fromId` of the binding, or a `{ type }` stub if unavailable. */
|
|
48
|
+
fromShape: TLShape | { type: TLShape['type'] }
|
|
49
|
+
/** The shape referenced by the `toId` of the binding, or a `{ type }` stub if unavailable. */
|
|
50
|
+
toShape: TLShape | { type: TLShape['type'] }
|
|
47
51
|
/** The type of binding. */
|
|
48
52
|
bindingType: string
|
|
53
|
+
/**
|
|
54
|
+
* The type of shape referenced by the `fromId` of the binding.
|
|
55
|
+
* @deprecated Use `fromShape.type` instead.
|
|
56
|
+
*/
|
|
57
|
+
fromShapeType: TLShape['type']
|
|
58
|
+
/**
|
|
59
|
+
* The type of shape referenced by the `toId` of the binding.
|
|
60
|
+
* @deprecated Use `toShape.type` instead.
|
|
61
|
+
*/
|
|
62
|
+
toShapeType: TLShape['type']
|
|
49
63
|
}
|
|
50
64
|
|
|
51
65
|
/**
|
|
@@ -75,6 +89,19 @@ export interface TLShapeUtilCanvasSvgDef {
|
|
|
75
89
|
component: React.ComponentType
|
|
76
90
|
}
|
|
77
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Return type for {@link ShapeUtil.getIndicatorPath}. Can be either a simple Path2D
|
|
94
|
+
* or an object with additional rendering info like clip paths for complex indicators.
|
|
95
|
+
* @public
|
|
96
|
+
*/
|
|
97
|
+
export type TLIndicatorPath =
|
|
98
|
+
| Path2D
|
|
99
|
+
| {
|
|
100
|
+
path: Path2D
|
|
101
|
+
clipPath?: Path2D
|
|
102
|
+
additionalPaths?: Path2D[]
|
|
103
|
+
}
|
|
104
|
+
|
|
78
105
|
/** @public */
|
|
79
106
|
export abstract class ShapeUtil<Shape extends TLShape = TLShape> {
|
|
80
107
|
/** Configure this shape utils {@link ShapeUtil.options | `options`}. */
|
|
@@ -173,6 +200,37 @@ export abstract class ShapeUtil<Shape extends TLShape = TLShape> {
|
|
|
173
200
|
*/
|
|
174
201
|
abstract indicator(shape: Shape): any
|
|
175
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Whether to use the legacy React-based indicator rendering.
|
|
205
|
+
*
|
|
206
|
+
* Override this to return `false` if your shape implements {@link ShapeUtil.getIndicatorPath}
|
|
207
|
+
* for canvas-based indicator rendering.
|
|
208
|
+
*
|
|
209
|
+
* @returns `true` to use SVG indicators (default), `false` to use canvas indicators.
|
|
210
|
+
* @public
|
|
211
|
+
*/
|
|
212
|
+
useLegacyIndicator(): boolean {
|
|
213
|
+
return true
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get a Path2D for rendering the shape's indicator on the canvas.
|
|
218
|
+
*
|
|
219
|
+
* When implemented, this is used instead of {@link ShapeUtil.indicator} for more
|
|
220
|
+
* efficient canvas-based indicator rendering. Shapes that return `undefined` will
|
|
221
|
+
* fall back to SVG-based rendering via {@link ShapeUtil.indicator}.
|
|
222
|
+
*
|
|
223
|
+
* For complex indicators that need clipping (e.g., arrows with labels), return an
|
|
224
|
+
* object with `path`, `clipPath`, and `additionalPaths` properties.
|
|
225
|
+
*
|
|
226
|
+
* @param shape - The shape.
|
|
227
|
+
* @returns A Path2D to stroke, or an object with clipping info, or undefined to use SVG fallback.
|
|
228
|
+
* @public
|
|
229
|
+
*/
|
|
230
|
+
getIndicatorPath(shape: Shape): TLIndicatorPath | undefined {
|
|
231
|
+
return undefined
|
|
232
|
+
}
|
|
233
|
+
|
|
176
234
|
/**
|
|
177
235
|
* Get the font faces that should be rendered in the document in order for this shape to render
|
|
178
236
|
* correctly.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { TLKeyboardEventInfo } from '../types/event-types'
|
|
2
1
|
import { StateNode } from './StateNode'
|
|
3
2
|
|
|
4
3
|
export class RootState extends StateNode {
|
|
@@ -7,19 +6,4 @@ export class RootState extends StateNode {
|
|
|
7
6
|
static override children() {
|
|
8
7
|
return []
|
|
9
8
|
}
|
|
10
|
-
|
|
11
|
-
override onKeyDown(info: TLKeyboardEventInfo) {
|
|
12
|
-
// todo: move this logic up to the tldraw library, as the "zoom" tool only exists there
|
|
13
|
-
switch (info.code) {
|
|
14
|
-
case 'KeyZ': {
|
|
15
|
-
if (!(info.shiftKey || info.ctrlKey)) {
|
|
16
|
-
const currentTool = this.getCurrent()
|
|
17
|
-
if (currentTool && currentTool.getCurrent()?.id === 'idle' && this.children!['zoom']) {
|
|
18
|
-
this.editor.setCurrentTool('zoom', { ...info, onInteractionEnd: currentTool.id })
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
break
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
9
|
}
|
|
@@ -1,7 +1,20 @@
|
|
|
1
|
-
import { useComputed, useValue } from '@tldraw/state-react'
|
|
1
|
+
import { useAtom, useComputed, useValue } from '@tldraw/state-react'
|
|
2
|
+
import { useEffect } from 'react'
|
|
3
|
+
import {
|
|
4
|
+
getCollaboratorStateFromElapsedTime,
|
|
5
|
+
shouldShowCollaborator,
|
|
6
|
+
} from '../utils/collaboratorState'
|
|
2
7
|
import { uniq } from '../utils/uniq'
|
|
3
8
|
import { useEditor } from './useEditor'
|
|
4
9
|
|
|
10
|
+
function setsEqual<T>(a: Set<T>, b: Set<T>): boolean {
|
|
11
|
+
if (a.size !== b.size) return false
|
|
12
|
+
for (const item of a) {
|
|
13
|
+
if (!b.has(item)) return false
|
|
14
|
+
}
|
|
15
|
+
return true
|
|
16
|
+
}
|
|
17
|
+
|
|
5
18
|
// TODO: maybe move this to a computed property on the App class?
|
|
6
19
|
/**
|
|
7
20
|
* @returns The list of peer UserIDs
|
|
@@ -19,3 +32,42 @@ export function usePeerIds() {
|
|
|
19
32
|
|
|
20
33
|
return useValue($userIds)
|
|
21
34
|
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Returns a computed signal of active peer user IDs that should be shown.
|
|
38
|
+
* Automatically re-evaluates on an interval to handle time-based state transitions
|
|
39
|
+
* (active -> idle -> inactive).
|
|
40
|
+
*
|
|
41
|
+
* @returns A computed signal containing a Set of active peer user IDs
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
export function useActivePeerIds$() {
|
|
45
|
+
const $time = useAtom('peerIdsTime', Date.now())
|
|
46
|
+
const editor = useEditor()
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const interval = editor.timers.setInterval(() => {
|
|
49
|
+
$time.set(Date.now())
|
|
50
|
+
}, editor.options.collaboratorCheckIntervalMs)
|
|
51
|
+
|
|
52
|
+
return () => clearInterval(interval)
|
|
53
|
+
}, [editor, $time])
|
|
54
|
+
|
|
55
|
+
return useComputed(
|
|
56
|
+
'activePeerIds',
|
|
57
|
+
() => {
|
|
58
|
+
const now = $time.get()
|
|
59
|
+
return new Set(
|
|
60
|
+
editor
|
|
61
|
+
.getCollaborators()
|
|
62
|
+
.filter((p) => {
|
|
63
|
+
const elapsed = Math.max(0, now - (p.lastActivityTimestamp ?? Infinity))
|
|
64
|
+
const state = getCollaboratorStateFromElapsedTime(editor, elapsed)
|
|
65
|
+
return shouldShowCollaborator(editor, p, state)
|
|
66
|
+
})
|
|
67
|
+
.map((p) => p.userId)
|
|
68
|
+
)
|
|
69
|
+
},
|
|
70
|
+
{ isEqual: setsEqual },
|
|
71
|
+
[editor]
|
|
72
|
+
)
|
|
73
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { TLShapeId } from '@tldraw/tlschema'
|
|
2
|
+
import { createContext, useCallback, useContext, useMemo, useRef } from 'react'
|
|
3
|
+
import { setStyleProperty } from '../utils/dom'
|
|
4
|
+
|
|
5
|
+
interface ShapeContainerEntry {
|
|
6
|
+
container: HTMLDivElement
|
|
7
|
+
bgContainer: HTMLDivElement | null
|
|
8
|
+
isCulled: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface ShapeCullingContextValue {
|
|
12
|
+
register(
|
|
13
|
+
id: TLShapeId,
|
|
14
|
+
container: HTMLDivElement,
|
|
15
|
+
bgContainer: HTMLDivElement | null,
|
|
16
|
+
isCulled: boolean
|
|
17
|
+
): void
|
|
18
|
+
unregister(id: TLShapeId): void
|
|
19
|
+
updateCulling(culledShapes: Set<TLShapeId>): void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const ShapeCullingContext = createContext<ShapeCullingContextValue | null>(null)
|
|
23
|
+
|
|
24
|
+
/** @internal */
|
|
25
|
+
export interface ShapeCullingProviderProps {
|
|
26
|
+
children: React.ReactNode
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Provides centralized culling management for shape containers.
|
|
31
|
+
* This allows a single reactor to update all shape display states
|
|
32
|
+
* instead of each shape having its own subscription.
|
|
33
|
+
*
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
export function ShapeCullingProvider({ children }: ShapeCullingProviderProps) {
|
|
37
|
+
const containersRef = useRef(new Map<TLShapeId, ShapeContainerEntry>())
|
|
38
|
+
|
|
39
|
+
const register = useCallback(
|
|
40
|
+
(
|
|
41
|
+
id: TLShapeId,
|
|
42
|
+
container: HTMLDivElement,
|
|
43
|
+
bgContainer: HTMLDivElement | null,
|
|
44
|
+
isCulled: boolean
|
|
45
|
+
) => {
|
|
46
|
+
const display = isCulled ? 'none' : 'block'
|
|
47
|
+
setStyleProperty(container, 'display', display)
|
|
48
|
+
setStyleProperty(bgContainer, 'display', display)
|
|
49
|
+
|
|
50
|
+
containersRef.current.set(id, {
|
|
51
|
+
container,
|
|
52
|
+
bgContainer,
|
|
53
|
+
isCulled,
|
|
54
|
+
})
|
|
55
|
+
},
|
|
56
|
+
[]
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
const unregister = useCallback((id: TLShapeId) => {
|
|
60
|
+
containersRef.current.delete(id)
|
|
61
|
+
}, [])
|
|
62
|
+
|
|
63
|
+
const updateCulling = useCallback((culledShapes: Set<TLShapeId>) => {
|
|
64
|
+
for (const [id, entry] of containersRef.current) {
|
|
65
|
+
const shouldBeCulled = culledShapes.has(id)
|
|
66
|
+
if (shouldBeCulled !== entry.isCulled) {
|
|
67
|
+
const display = shouldBeCulled ? 'none' : 'block'
|
|
68
|
+
setStyleProperty(entry.container, 'display', display)
|
|
69
|
+
setStyleProperty(entry.bgContainer, 'display', display)
|
|
70
|
+
entry.isCulled = shouldBeCulled
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}, [])
|
|
74
|
+
|
|
75
|
+
const value = useMemo(
|
|
76
|
+
() => ({
|
|
77
|
+
register,
|
|
78
|
+
unregister,
|
|
79
|
+
updateCulling,
|
|
80
|
+
}),
|
|
81
|
+
[register, unregister, updateCulling]
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return <ShapeCullingContext.Provider value={value}>{children}</ShapeCullingContext.Provider>
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Hook to access the shape culling context for container registration.
|
|
89
|
+
*
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
92
|
+
export function useShapeCulling(): ShapeCullingContextValue {
|
|
93
|
+
const context = useContext(ShapeCullingContext)
|
|
94
|
+
if (!context) {
|
|
95
|
+
throw new Error('useShapeCulling must be used within ShapeCullingProvider')
|
|
96
|
+
}
|
|
97
|
+
return context
|
|
98
|
+
}
|
|
@@ -431,25 +431,25 @@ export class LicenseManager {
|
|
|
431
431
|
private outputMessages(messages: string[], type: 'warning' | 'error' = 'error') {
|
|
432
432
|
if (this.isTest) return
|
|
433
433
|
if (this.verbose) {
|
|
434
|
-
this.outputDelimiter()
|
|
434
|
+
this.outputDelimiter(type)
|
|
435
435
|
for (const message of messages) {
|
|
436
|
-
const color = type === 'warning' ? 'orange' : 'crimson'
|
|
437
436
|
const bgColor = type === 'warning' ? 'orange' : 'crimson'
|
|
438
437
|
// eslint-disable-next-line no-console
|
|
439
438
|
console.log(
|
|
440
439
|
`%c${message}`,
|
|
441
|
-
`color:
|
|
440
|
+
`color: white; background: ${bgColor}; padding: 2px; border-radius: 3px;`
|
|
442
441
|
)
|
|
443
442
|
}
|
|
444
|
-
this.outputDelimiter()
|
|
443
|
+
this.outputDelimiter(type)
|
|
445
444
|
}
|
|
446
445
|
}
|
|
447
446
|
|
|
448
|
-
private outputDelimiter() {
|
|
447
|
+
private outputDelimiter(type: 'warning' | 'error' = 'error') {
|
|
448
|
+
const bgColor = type === 'warning' ? 'orange' : 'crimson'
|
|
449
449
|
// eslint-disable-next-line no-console
|
|
450
450
|
console.log(
|
|
451
451
|
'%c-------------------------------------------------------------------',
|
|
452
|
-
`color: white; background:
|
|
452
|
+
`color: white; background: ${bgColor}; padding: 2px; border-radius: 3px;`
|
|
453
453
|
)
|
|
454
454
|
}
|
|
455
455
|
|
package/src/lib/options.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { ComponentType, Fragment } from 'react'
|
|
2
|
+
import { DEFAULT_CAMERA_OPTIONS } from './constants'
|
|
3
|
+
import { TLCameraOptions } from './editor/types/misc-types'
|
|
4
|
+
import { TLDeepLinkOptions } from './utils/deepLinks'
|
|
5
|
+
import { TLTextOptions } from './utils/richText'
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
8
|
* Options for configuring tldraw. For defaults, see {@link defaultTldrawOptions}.
|
|
@@ -54,6 +58,12 @@ export interface TldrawOptions {
|
|
|
54
58
|
readonly flattenImageBoundsExpand: number
|
|
55
59
|
readonly flattenImageBoundsPadding: number
|
|
56
60
|
readonly laserDelayMs: number
|
|
61
|
+
/**
|
|
62
|
+
* How long (in milliseconds) to fade all laser scribbles after the session ends.
|
|
63
|
+
* The total points across all scribbles will be removed proportionally over this duration.
|
|
64
|
+
* Defaults to 500ms (0.5 seconds).
|
|
65
|
+
*/
|
|
66
|
+
readonly laserFadeoutMs: number
|
|
57
67
|
readonly maxExportDelayMs: number
|
|
58
68
|
readonly tooltipDelayMs: number
|
|
59
69
|
/**
|
|
@@ -89,13 +99,13 @@ export interface TldrawOptions {
|
|
|
89
99
|
readonly branding?: string
|
|
90
100
|
/**
|
|
91
101
|
* Whether to use debounced zoom level for certain rendering optimizations. When true,
|
|
92
|
-
* `editor.
|
|
102
|
+
* `editor.getEfficientZoomLevel()` returns a cached zoom value while the camera is moving,
|
|
93
103
|
* reducing re-renders. When false, it always returns the current zoom level.
|
|
94
104
|
*/
|
|
95
105
|
readonly debouncedZoom: boolean
|
|
96
106
|
/**
|
|
97
107
|
* The number of shapes that must be on the page for the debounced zoom level to be used.
|
|
98
|
-
* Defaults to
|
|
108
|
+
* Defaults to 500 shapes.
|
|
99
109
|
*/
|
|
100
110
|
readonly debouncedZoomThreshold: number
|
|
101
111
|
/**
|
|
@@ -114,6 +124,37 @@ export interface TldrawOptions {
|
|
|
114
124
|
* The distance (in screen pixels) at which shapes snap to guides and other shapes.
|
|
115
125
|
*/
|
|
116
126
|
readonly snapThreshold: number
|
|
127
|
+
/**
|
|
128
|
+
* Options for the editor's camera. These are the initial camera options.
|
|
129
|
+
* Use {@link Editor.setCameraOptions} to update camera options at runtime.
|
|
130
|
+
*/
|
|
131
|
+
readonly camera: Partial<TLCameraOptions>
|
|
132
|
+
/**
|
|
133
|
+
* Options for the editor's text rendering. These include TipTap configuration and
|
|
134
|
+
* font handling. These are the initial text options and cannot be changed at runtime.
|
|
135
|
+
*/
|
|
136
|
+
readonly text: TLTextOptions
|
|
137
|
+
/**
|
|
138
|
+
* Options for syncing the editor's camera state with the URL. Set to `true` to enable
|
|
139
|
+
* with default options, or pass an options object to customize behavior.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```tsx
|
|
143
|
+
* // Enable with defaults
|
|
144
|
+
* <Tldraw options={{ deepLinks: true }} />
|
|
145
|
+
*
|
|
146
|
+
* // Enable with custom options
|
|
147
|
+
* <Tldraw options={{ deepLinks: { param: 'd', debounceMs: 500 } }} />
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
readonly deepLinks: true | TLDeepLinkOptions | undefined
|
|
151
|
+
/**
|
|
152
|
+
* Whether the quick-zoom brush preserves its screen-pixel size when the user
|
|
153
|
+
* zooms the overview. When true, zooming in shrinks the target viewport (higher
|
|
154
|
+
* return zoom); zooming out expands it. When false, the brush keeps the original
|
|
155
|
+
* viewport's page dimensions regardless of overview zoom changes.
|
|
156
|
+
*/
|
|
157
|
+
readonly quickZoomPreservesScreenBounds: boolean
|
|
117
158
|
}
|
|
118
159
|
|
|
119
160
|
/** @public */
|
|
@@ -157,6 +198,7 @@ export const defaultTldrawOptions = {
|
|
|
157
198
|
flattenImageBoundsExpand: 64,
|
|
158
199
|
flattenImageBoundsPadding: 16,
|
|
159
200
|
laserDelayMs: 1200,
|
|
201
|
+
laserFadeoutMs: 500,
|
|
160
202
|
maxExportDelayMs: 5000,
|
|
161
203
|
tooltipDelayMs: 700,
|
|
162
204
|
temporaryAssetPreviewLifetimeMs: 180000,
|
|
@@ -171,4 +213,8 @@ export const defaultTldrawOptions = {
|
|
|
171
213
|
spacebarPanning: true,
|
|
172
214
|
zoomToFitPadding: 128,
|
|
173
215
|
snapThreshold: 8,
|
|
216
|
+
camera: DEFAULT_CAMERA_OPTIONS,
|
|
217
|
+
text: {},
|
|
218
|
+
deepLinks: undefined,
|
|
219
|
+
quickZoomPreservesScreenBounds: true,
|
|
174
220
|
} as const satisfies TldrawOptions
|
|
@@ -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.bde73a32273d'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2025-09-18T14:39:22.803Z',
|
|
7
|
-
minor: '2026-
|
|
8
|
-
patch: '2026-
|
|
7
|
+
minor: '2026-02-11T22:21:46.227Z',
|
|
8
|
+
patch: '2026-02-11T22:21:46.227Z',
|
|
9
9
|
}
|