@tldraw/editor 3.13.0-canary.a2884bb1bab2 → 3.13.0-canary.bbec36f93805
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 +5 -0
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/TldrawEditor.js +2 -1
- package/dist-cjs/lib/TldrawEditor.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +14 -12
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultSpinner.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultSpinner.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +25 -20
- package/dist-cjs/lib/editor/Editor.js.map +3 -3
- package/dist-cjs/lib/hooks/useDocumentEvents.js +3 -2
- package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
- package/dist-cjs/lib/license/LicenseManager.js +8 -1
- package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
- package/dist-cjs/lib/options.js.map +2 -2
- package/dist-cjs/lib/utils/dom.js +3 -3
- package/dist-cjs/lib/utils/dom.js.map +2 -2
- package/dist-cjs/lib/utils/rotation.js +5 -5
- package/dist-cjs/lib/utils/rotation.js.map +2 -2
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +5 -0
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/TldrawEditor.mjs +2 -1
- package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +14 -12
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultSpinner.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultSpinner.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +25 -20
- package/dist-esm/lib/editor/Editor.mjs.map +3 -3
- package/dist-esm/lib/hooks/useDocumentEvents.mjs +3 -2
- package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseManager.mjs +8 -1
- package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
- package/dist-esm/lib/options.mjs.map +2 -2
- package/dist-esm/lib/utils/dom.mjs +3 -3
- package/dist-esm/lib/utils/dom.mjs.map +2 -2
- package/dist-esm/lib/utils/rotation.mjs +5 -5
- package/dist-esm/lib/utils/rotation.mjs.map +2 -2
- 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/lib/TldrawEditor.tsx +6 -1
- package/src/lib/components/default-components/DefaultErrorFallback.tsx +25 -14
- package/src/lib/components/default-components/DefaultSpinner.tsx +1 -1
- package/src/lib/editor/Editor.ts +22 -19
- package/src/lib/hooks/useDocumentEvents.ts +7 -2
- package/src/lib/license/LicenseManager.test.ts +40 -0
- package/src/lib/license/LicenseManager.ts +13 -1
- package/src/lib/options.ts +4 -0
- package/src/lib/utils/dom.ts +4 -4
- package/src/lib/utils/rotation.ts +8 -6
- package/src/version.ts +3 -3
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
TLImageAsset,
|
|
43
43
|
TLInstance,
|
|
44
44
|
TLInstancePageState,
|
|
45
|
+
TLInstancePresence,
|
|
45
46
|
TLNoteShape,
|
|
46
47
|
TLPOINTER_ID,
|
|
47
48
|
TLPage,
|
|
@@ -1704,8 +1705,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1704
1705
|
* @readonly
|
|
1705
1706
|
*/
|
|
1706
1707
|
@computed getSelectedShapes(): TLShape[] {
|
|
1707
|
-
|
|
1708
|
-
return compact(selectedShapeIds.map((id) => this.store.get(id)))
|
|
1708
|
+
return compact(this.getSelectedShapeIds().map((id) => this.store.get(id)))
|
|
1709
1709
|
}
|
|
1710
1710
|
|
|
1711
1711
|
/**
|
|
@@ -2575,14 +2575,25 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2575
2575
|
return baseCamera
|
|
2576
2576
|
}
|
|
2577
2577
|
|
|
2578
|
+
private _getFollowingPresence(targetUserId: string | null) {
|
|
2579
|
+
const visited = [this.user.getId()]
|
|
2580
|
+
const collaborators = this.getCollaborators()
|
|
2581
|
+
let leaderPresence = null as null | TLInstancePresence
|
|
2582
|
+
while (targetUserId && !visited.includes(targetUserId)) {
|
|
2583
|
+
leaderPresence = collaborators.find((c) => c.userId === targetUserId) ?? null
|
|
2584
|
+
targetUserId = leaderPresence?.followingUserId ?? null
|
|
2585
|
+
if (leaderPresence) {
|
|
2586
|
+
visited.push(leaderPresence.userId)
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
return leaderPresence
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2578
2592
|
@computed
|
|
2579
2593
|
private getViewportPageBoundsForFollowing(): null | Box {
|
|
2580
|
-
const
|
|
2581
|
-
if (!followingUserId) return null
|
|
2582
|
-
const leaderPresence = this.getCollaborators().find((c) => c.userId === followingUserId)
|
|
2583
|
-
if (!leaderPresence) return null
|
|
2594
|
+
const leaderPresence = this._getFollowingPresence(this.getInstanceState().followingUserId)
|
|
2584
2595
|
|
|
2585
|
-
if (!leaderPresence
|
|
2596
|
+
if (!leaderPresence?.camera || !leaderPresence?.screenBounds) return null
|
|
2586
2597
|
|
|
2587
2598
|
// Fit their viewport inside of our screen bounds
|
|
2588
2599
|
// 1. calculate their viewport in page space
|
|
@@ -3781,15 +3792,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3781
3792
|
// if we were already following someone, stop following them
|
|
3782
3793
|
this.stopFollowingUser()
|
|
3783
3794
|
|
|
3784
|
-
const leaderPresences = this._getCollaboratorsQuery()
|
|
3785
|
-
.get()
|
|
3786
|
-
.filter((p) => p.userId === userId)
|
|
3787
|
-
|
|
3788
|
-
if (!leaderPresences.length) {
|
|
3789
|
-
console.warn('User not found')
|
|
3790
|
-
return this
|
|
3791
|
-
}
|
|
3792
|
-
|
|
3793
3795
|
const thisUserId = this.user.getId()
|
|
3794
3796
|
|
|
3795
3797
|
if (!thisUserId) {
|
|
@@ -3797,13 +3799,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3797
3799
|
// allow to continue since it's probably fine most of the time.
|
|
3798
3800
|
}
|
|
3799
3801
|
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
+
const leaderPresence = this._getFollowingPresence(userId)
|
|
3803
|
+
|
|
3804
|
+
if (!leaderPresence) {
|
|
3802
3805
|
return this
|
|
3803
3806
|
}
|
|
3804
3807
|
|
|
3805
3808
|
const latestLeaderPresence = computed('latestLeaderPresence', () => {
|
|
3806
|
-
return this.
|
|
3809
|
+
return this._getFollowingPresence(userId)
|
|
3807
3810
|
})
|
|
3808
3811
|
|
|
3809
3812
|
transact(() => {
|
|
@@ -11,6 +11,7 @@ export function useDocumentEvents() {
|
|
|
11
11
|
const editor = useEditor()
|
|
12
12
|
const container = useContainer()
|
|
13
13
|
|
|
14
|
+
const isEditing = useValue('isEditing', () => editor.getEditingShapeId(), [editor])
|
|
14
15
|
const isAppFocused = useValue('isFocused', () => editor.getIsFocused(), [editor])
|
|
15
16
|
|
|
16
17
|
// Prevent the browser's default drag and drop behavior on our container (UI, etc)
|
|
@@ -125,7 +126,11 @@ export function useDocumentEvents() {
|
|
|
125
126
|
if (areShortcutsDisabled(editor)) {
|
|
126
127
|
return
|
|
127
128
|
}
|
|
128
|
-
|
|
129
|
+
// isEditing here sounds like it's about text editing
|
|
130
|
+
// but more specifically, this is so you can tab into an
|
|
131
|
+
// embed that's being 'edited'. In our world,
|
|
132
|
+
// editing an embed, means it's interactive.
|
|
133
|
+
if (hasSelectedShapes && !isEditing) {
|
|
129
134
|
// This is used in tandem with shape navigation.
|
|
130
135
|
preventDefault(e)
|
|
131
136
|
}
|
|
@@ -289,7 +294,7 @@ export function useDocumentEvents() {
|
|
|
289
294
|
container.removeEventListener('keydown', handleKeyDown)
|
|
290
295
|
container.removeEventListener('keyup', handleKeyUp)
|
|
291
296
|
}
|
|
292
|
-
}, [editor, container, isAppFocused])
|
|
297
|
+
}, [editor, container, isAppFocused, isEditing])
|
|
293
298
|
}
|
|
294
299
|
|
|
295
300
|
function areShortcutsDisabled(editor: Editor) {
|
|
@@ -317,6 +317,46 @@ describe('LicenseManager', () => {
|
|
|
317
317
|
expect(result.isDomainValid).toBe(false)
|
|
318
318
|
})
|
|
319
319
|
|
|
320
|
+
it('Succeeds if it is a vscode extension', async () => {
|
|
321
|
+
// @ts-ignore
|
|
322
|
+
delete window.location
|
|
323
|
+
// @ts-ignore
|
|
324
|
+
window.location = new URL(
|
|
325
|
+
'vscode-webview:vscode-webview://1ipd8pun8ud7nd7hv9d112g7evi7m10vak9vviuvia66ou6aibp3/index.html?id=6ec2dc7a-afe9-45d9-bd71-1749f9568d28&origin=955b256f-37e1-4a72-a2f4-ad633e88239c&swVersion=4&extensionId=tldraw-org.tldraw-vscode&platform=electron&vscode-resource-base-authority=vscode-resource.vscode-cdn.net&parentOrigin=vscode-file%3A%2F%2Fvscode-app'
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
const permissiveHostsInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
329
|
+
permissiveHostsInfo[PROPERTIES.HOSTS] = ['tldraw-org.tldraw-vscode']
|
|
330
|
+
const permissiveLicenseKey = await generateLicenseKey(
|
|
331
|
+
JSON.stringify(permissiveHostsInfo),
|
|
332
|
+
keyPair
|
|
333
|
+
)
|
|
334
|
+
const result = (await licenseManager.getLicenseFromKey(
|
|
335
|
+
permissiveLicenseKey
|
|
336
|
+
)) as ValidLicenseKeyResult
|
|
337
|
+
expect(result.isDomainValid).toBe(true)
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
it('Fails if it is a vscode extension with the wrong id', async () => {
|
|
341
|
+
// @ts-ignore
|
|
342
|
+
delete window.location
|
|
343
|
+
// @ts-ignore
|
|
344
|
+
window.location = new URL(
|
|
345
|
+
'vscode-webview:vscode-webview://1ipd8pun8ud7nd7hv9d112g7evi7m10vak9vviuvia66ou6aibp3/index.html?id=6ec2dc7a-afe9-45d9-bd71-1749f9568d28&origin=955b256f-37e1-4a72-a2f4-ad633e88239c&swVersion=4&extensionId=tldraw-org.tldraw-vscode&platform=electron&vscode-resource-base-authority=vscode-resource.vscode-cdn.net&parentOrigin=vscode-file%3A%2F%2Fvscode-app'
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
const permissiveHostsInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
349
|
+
permissiveHostsInfo[PROPERTIES.HOSTS] = ['blah-org.blah-vscode']
|
|
350
|
+
const permissiveLicenseKey = await generateLicenseKey(
|
|
351
|
+
JSON.stringify(permissiveHostsInfo),
|
|
352
|
+
keyPair
|
|
353
|
+
)
|
|
354
|
+
const result = (await licenseManager.getLicenseFromKey(
|
|
355
|
+
permissiveLicenseKey
|
|
356
|
+
)) as ValidLicenseKeyResult
|
|
357
|
+
expect(result.isDomainValid).toBe(false)
|
|
358
|
+
})
|
|
359
|
+
|
|
320
360
|
it('Checks for internal license', async () => {
|
|
321
361
|
const internalLicenseInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
322
362
|
internalLicenseInfo[PROPERTIES.FLAGS] = FLAGS.INTERNAL_LICENSE
|
|
@@ -111,7 +111,10 @@ export class LicenseManager {
|
|
|
111
111
|
if (testEnvironment === 'production') return false
|
|
112
112
|
|
|
113
113
|
// If we are using https on a non-localhost domain we assume it's a production env and a development one otherwise
|
|
114
|
-
return
|
|
114
|
+
return (
|
|
115
|
+
!['https:', 'vscode-webview:'].includes(window.location.protocol) ||
|
|
116
|
+
window.location.hostname === 'localhost'
|
|
117
|
+
)
|
|
115
118
|
}
|
|
116
119
|
|
|
117
120
|
private async extractLicenseKey(licenseKey: string): Promise<LicenseInfo> {
|
|
@@ -250,6 +253,15 @@ export class LicenseManager {
|
|
|
250
253
|
return globToRegex.test(currentHostname) || globToRegex.test(`www.${currentHostname}`)
|
|
251
254
|
}
|
|
252
255
|
|
|
256
|
+
// VSCode support
|
|
257
|
+
if (window.location.protocol === 'vscode-webview:') {
|
|
258
|
+
const currentUrl = new URL(window.location.href)
|
|
259
|
+
const extensionId = currentUrl.searchParams.get('extensionId')
|
|
260
|
+
if (normalizedHost === extensionId) {
|
|
261
|
+
return true
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
253
265
|
return false
|
|
254
266
|
})
|
|
255
267
|
}
|
package/src/lib/options.ts
CHANGED
|
@@ -80,6 +80,10 @@ export interface TldrawOptions {
|
|
|
80
80
|
* nonce to use in the editor's styles.
|
|
81
81
|
*/
|
|
82
82
|
readonly nonce: string | undefined
|
|
83
|
+
/**
|
|
84
|
+
* Branding name of the app, currently only used for adding aria-label for the application.
|
|
85
|
+
*/
|
|
86
|
+
readonly branding?: string
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
/** @public */
|
package/src/lib/utils/dom.ts
CHANGED
|
@@ -91,14 +91,14 @@ export const setStyleProperty = (
|
|
|
91
91
|
elm.style.setProperty(property, value as string)
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
const INPUTS = ['input', 'select', 'button', 'textarea']
|
|
95
|
-
|
|
96
94
|
/** @internal */
|
|
97
|
-
export function activeElementShouldCaptureKeys() {
|
|
95
|
+
export function activeElementShouldCaptureKeys(allowButtons = false) {
|
|
98
96
|
const { activeElement } = document
|
|
97
|
+
const elements = allowButtons ? ['input', 'textarea'] : ['input', 'select', 'button', 'textarea']
|
|
99
98
|
return !!(
|
|
100
99
|
activeElement &&
|
|
101
100
|
((activeElement as HTMLElement).isContentEditable ||
|
|
102
|
-
|
|
101
|
+
elements.indexOf(activeElement.tagName.toLowerCase()) > -1 ||
|
|
102
|
+
activeElement.classList.contains('tlui-slider__thumb'))
|
|
103
103
|
)
|
|
104
104
|
}
|
|
@@ -26,11 +26,13 @@ export function getRotationSnapshot({
|
|
|
26
26
|
return null
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const
|
|
29
|
+
const initialPageCenter = rotatedPageBounds.center
|
|
30
|
+
.clone()
|
|
31
|
+
.rotWith(rotatedPageBounds.point, rotation)
|
|
30
32
|
|
|
31
33
|
return {
|
|
32
|
-
|
|
33
|
-
initialCursorAngle:
|
|
34
|
+
initialPageCenter,
|
|
35
|
+
initialCursorAngle: initialPageCenter.angle(editor.inputs.originPagePoint),
|
|
34
36
|
initialShapesRotation: rotation,
|
|
35
37
|
shapeSnapshots: shapes.map((shape) => ({
|
|
36
38
|
shape,
|
|
@@ -43,7 +45,7 @@ export function getRotationSnapshot({
|
|
|
43
45
|
* @internal
|
|
44
46
|
**/
|
|
45
47
|
export interface TLRotationSnapshot {
|
|
46
|
-
|
|
48
|
+
initialPageCenter: Vec
|
|
47
49
|
initialCursorAngle: number
|
|
48
50
|
initialShapesRotation: number
|
|
49
51
|
shapeSnapshots: {
|
|
@@ -66,7 +68,7 @@ export function applyRotationToSnapshotShapes({
|
|
|
66
68
|
stage: 'start' | 'update' | 'end' | 'one-off'
|
|
67
69
|
centerOverride?: VecLike
|
|
68
70
|
}) {
|
|
69
|
-
const {
|
|
71
|
+
const { initialPageCenter, shapeSnapshots } = snapshot
|
|
70
72
|
|
|
71
73
|
editor.updateShapes(
|
|
72
74
|
shapeSnapshots.map(({ shape, initialPagePoint }) => {
|
|
@@ -77,7 +79,7 @@ export function applyRotationToSnapshotShapes({
|
|
|
77
79
|
? editor.getShapePageTransform(shape.parentId)!
|
|
78
80
|
: Mat.Identity()
|
|
79
81
|
|
|
80
|
-
const newPagePoint = Vec.RotWith(initialPagePoint, centerOverride ??
|
|
82
|
+
const newPagePoint = Vec.RotWith(initialPagePoint, centerOverride ?? initialPageCenter, delta)
|
|
81
83
|
|
|
82
84
|
const newLocalPoint = Mat.applyToPoint(
|
|
83
85
|
// use the current parent transform in case it has moved/resized since the start
|
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 = '3.13.0-canary.
|
|
4
|
+
export const version = '3.13.0-canary.bbec36f93805'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2024-09-13T14:36:29.063Z',
|
|
7
|
-
minor: '2025-04-
|
|
8
|
-
patch: '2025-04-
|
|
7
|
+
minor: '2025-04-29T14:04:26.024Z',
|
|
8
|
+
patch: '2025-04-29T14:04:26.024Z',
|
|
9
9
|
}
|