@tldraw/editor 3.16.0-canary.6c77a180e58d → 3.16.0-canary.7379d3553d7e
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 +53 -4
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/TldrawEditor.js +0 -2
- package/dist-cjs/lib/TldrawEditor.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +11 -1
- package/dist-cjs/lib/components/default-components/DefaultCanvas.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 +44 -5
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js +4 -2
- package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.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/hooks/useCanvasEvents.js +19 -16
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useDocumentEvents.js +5 -5
- package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js +1 -2
- package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useGestureEvents.js +1 -1
- package/dist-cjs/lib/hooks/useGestureEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useHandleEvents.js +6 -6
- package/dist-cjs/lib/hooks/useHandleEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useSelectionEvents.js +8 -8
- package/dist-cjs/lib/hooks/useSelectionEvents.js.map +2 -2
- package/dist-cjs/lib/license/LicenseManager.js +24 -4
- package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
- package/dist-cjs/lib/license/Watermark.js +97 -90
- package/dist-cjs/lib/license/Watermark.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js +24 -2
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Group2d.js +5 -1
- package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
- package/dist-cjs/lib/utils/dom.js.map +2 -2
- package/dist-cjs/lib/utils/getPointerInfo.js +2 -3
- package/dist-cjs/lib/utils/getPointerInfo.js.map +2 -2
- package/dist-cjs/lib/utils/reparenting.js +5 -1
- package/dist-cjs/lib/utils/reparenting.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 +53 -4
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/TldrawEditor.mjs +0 -2
- package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +11 -1
- package/dist-esm/lib/components/default-components/DefaultCanvas.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 +44 -5
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs +4 -2
- package/dist-esm/lib/editor/managers/FocusManager/FocusManager.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/hooks/useCanvasEvents.mjs +20 -22
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useDocumentEvents.mjs +6 -6
- package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs +1 -2
- package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useGestureEvents.mjs +2 -2
- package/dist-esm/lib/hooks/useGestureEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useHandleEvents.mjs +6 -6
- package/dist-esm/lib/hooks/useHandleEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useSelectionEvents.mjs +9 -14
- package/dist-esm/lib/hooks/useSelectionEvents.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseManager.mjs +24 -4
- package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
- package/dist-esm/lib/license/Watermark.mjs +98 -91
- package/dist-esm/lib/license/Watermark.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +24 -2
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Group2d.mjs +5 -1
- package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
- package/dist-esm/lib/utils/dom.mjs.map +2 -2
- package/dist-esm/lib/utils/getPointerInfo.mjs +2 -3
- package/dist-esm/lib/utils/getPointerInfo.mjs.map +2 -2
- package/dist-esm/lib/utils/reparenting.mjs +5 -1
- package/dist-esm/lib/utils/reparenting.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/package.json +7 -7
- package/src/lib/TldrawEditor.tsx +0 -2
- package/src/lib/components/default-components/DefaultCanvas.tsx +7 -1
- package/src/lib/config/TLUserPreferences.ts +8 -0
- package/src/lib/editor/Editor.test.ts +90 -0
- package/src/lib/editor/Editor.ts +57 -5
- package/src/lib/editor/managers/FocusManager/FocusManager.ts +6 -2
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +18 -0
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +5 -0
- package/src/lib/hooks/useCanvasEvents.ts +20 -20
- package/src/lib/hooks/useDocumentEvents.ts +6 -6
- package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +1 -1
- package/src/lib/hooks/useGestureEvents.ts +2 -2
- package/src/lib/hooks/useHandleEvents.ts +6 -6
- package/src/lib/hooks/useSelectionEvents.ts +9 -14
- package/src/lib/license/LicenseManager.test.ts +78 -2
- package/src/lib/license/LicenseManager.ts +31 -5
- package/src/lib/license/Watermark.tsx +100 -92
- package/src/lib/primitives/geometry/Geometry2d.test.ts +420 -0
- package/src/lib/primitives/geometry/Geometry2d.ts +29 -2
- package/src/lib/primitives/geometry/Group2d.ts +6 -1
- package/src/lib/test/InFrontOfTheCanvas.test.tsx +187 -0
- package/src/lib/utils/dom.test.ts +103 -0
- package/src/lib/utils/dom.ts +8 -1
- package/src/lib/utils/getPointerInfo.ts +3 -2
- package/src/lib/utils/reparenting.ts +7 -1
- package/src/version.ts +3 -3
|
@@ -25,6 +25,7 @@ export interface TLUserPreferences {
|
|
|
25
25
|
isDynamicSizeMode?: boolean | null
|
|
26
26
|
isPasteAtCursorMode?: boolean | null
|
|
27
27
|
showUiLabels?: boolean | null
|
|
28
|
+
inputMode?: 'trackpad' | 'mouse' | null
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
interface UserDataSnapshot {
|
|
@@ -54,6 +55,7 @@ export const userTypeValidator: T.Validator<TLUserPreferences> = T.object<TLUser
|
|
|
54
55
|
isDynamicSizeMode: T.boolean.nullable().optional(),
|
|
55
56
|
isPasteAtCursorMode: T.boolean.nullable().optional(),
|
|
56
57
|
showUiLabels: T.boolean.nullable().optional(),
|
|
58
|
+
inputMode: T.literalEnum('trackpad', 'mouse').nullable().optional(),
|
|
57
59
|
})
|
|
58
60
|
|
|
59
61
|
const Versions = {
|
|
@@ -67,6 +69,7 @@ const Versions = {
|
|
|
67
69
|
AddPasteAtCursor: 8,
|
|
68
70
|
AddKeyboardShortcuts: 9,
|
|
69
71
|
AddShowUiLabels: 10,
|
|
72
|
+
AddPointerPeripheral: 11,
|
|
70
73
|
} as const
|
|
71
74
|
|
|
72
75
|
const CURRENT_VERSION = Math.max(...Object.values(Versions))
|
|
@@ -109,6 +112,10 @@ function migrateSnapshot(data: { version: number; user: any }) {
|
|
|
109
112
|
data.user.showUiLabels = false
|
|
110
113
|
}
|
|
111
114
|
|
|
115
|
+
if (data.version < Versions.AddPointerPeripheral) {
|
|
116
|
+
data.user.inputMode = null
|
|
117
|
+
}
|
|
118
|
+
|
|
112
119
|
// finally
|
|
113
120
|
data.version = CURRENT_VERSION
|
|
114
121
|
}
|
|
@@ -158,6 +165,7 @@ export const defaultUserPreferences = Object.freeze({
|
|
|
158
165
|
isPasteAtCursorMode: false,
|
|
159
166
|
showUiLabels: false,
|
|
160
167
|
colorScheme: 'light',
|
|
168
|
+
inputMode: null,
|
|
161
169
|
}) satisfies Readonly<Omit<TLUserPreferences, 'id'>>
|
|
162
170
|
|
|
163
171
|
/** @public */
|
|
@@ -833,3 +833,93 @@ describe('selectAll', () => {
|
|
|
833
833
|
setSelectedShapesSpy.mockRestore()
|
|
834
834
|
})
|
|
835
835
|
})
|
|
836
|
+
|
|
837
|
+
describe('putExternalContent', () => {
|
|
838
|
+
let mockHandler: any
|
|
839
|
+
|
|
840
|
+
beforeEach(() => {
|
|
841
|
+
mockHandler = vi.fn()
|
|
842
|
+
editor.registerExternalContentHandler('text', mockHandler)
|
|
843
|
+
})
|
|
844
|
+
|
|
845
|
+
it('calls external content handler when not readonly', async () => {
|
|
846
|
+
vi.spyOn(editor, 'getIsReadonly').mockReturnValue(false)
|
|
847
|
+
|
|
848
|
+
const info = { type: 'text' as const, text: 'test-data' }
|
|
849
|
+
await editor.putExternalContent(info)
|
|
850
|
+
|
|
851
|
+
expect(mockHandler).toHaveBeenCalledWith(info)
|
|
852
|
+
})
|
|
853
|
+
|
|
854
|
+
it('does not call external content handler when readonly', async () => {
|
|
855
|
+
vi.spyOn(editor, 'getIsReadonly').mockReturnValue(true)
|
|
856
|
+
|
|
857
|
+
const info = { type: 'text' as const, text: 'test-data' }
|
|
858
|
+
await editor.putExternalContent(info)
|
|
859
|
+
|
|
860
|
+
expect(mockHandler).not.toHaveBeenCalled()
|
|
861
|
+
})
|
|
862
|
+
|
|
863
|
+
it('calls external content handler when readonly but force is true', async () => {
|
|
864
|
+
vi.spyOn(editor, 'getIsReadonly').mockReturnValue(true)
|
|
865
|
+
|
|
866
|
+
const info = { type: 'text' as const, text: 'test-data' }
|
|
867
|
+
await editor.putExternalContent(info, { force: true })
|
|
868
|
+
|
|
869
|
+
expect(mockHandler).toHaveBeenCalledWith(info)
|
|
870
|
+
})
|
|
871
|
+
|
|
872
|
+
it('calls external content handler when force is false and not readonly', async () => {
|
|
873
|
+
vi.spyOn(editor, 'getIsReadonly').mockReturnValue(false)
|
|
874
|
+
|
|
875
|
+
const info = { type: 'text' as const, text: 'test-data' }
|
|
876
|
+
await editor.putExternalContent(info, { force: false })
|
|
877
|
+
|
|
878
|
+
expect(mockHandler).toHaveBeenCalledWith(info)
|
|
879
|
+
})
|
|
880
|
+
})
|
|
881
|
+
|
|
882
|
+
describe('replaceExternalContent', () => {
|
|
883
|
+
let mockHandler: any
|
|
884
|
+
|
|
885
|
+
beforeEach(() => {
|
|
886
|
+
mockHandler = vi.fn()
|
|
887
|
+
editor.registerExternalContentHandler('text', mockHandler)
|
|
888
|
+
})
|
|
889
|
+
|
|
890
|
+
it('calls external content handler when not readonly', async () => {
|
|
891
|
+
vi.spyOn(editor, 'getIsReadonly').mockReturnValue(false)
|
|
892
|
+
|
|
893
|
+
const info = { type: 'text' as const, text: 'test-data' }
|
|
894
|
+
await editor.replaceExternalContent(info)
|
|
895
|
+
|
|
896
|
+
expect(mockHandler).toHaveBeenCalledWith(info)
|
|
897
|
+
})
|
|
898
|
+
|
|
899
|
+
it('does not call external content handler when readonly', async () => {
|
|
900
|
+
vi.spyOn(editor, 'getIsReadonly').mockReturnValue(true)
|
|
901
|
+
|
|
902
|
+
const info = { type: 'text' as const, text: 'test-data' }
|
|
903
|
+
await editor.replaceExternalContent(info)
|
|
904
|
+
|
|
905
|
+
expect(mockHandler).not.toHaveBeenCalled()
|
|
906
|
+
})
|
|
907
|
+
|
|
908
|
+
it('calls external content handler when readonly but force is true', async () => {
|
|
909
|
+
vi.spyOn(editor, 'getIsReadonly').mockReturnValue(true)
|
|
910
|
+
|
|
911
|
+
const info = { type: 'text' as const, text: 'test-data' }
|
|
912
|
+
await editor.replaceExternalContent(info, { force: true })
|
|
913
|
+
|
|
914
|
+
expect(mockHandler).toHaveBeenCalledWith(info)
|
|
915
|
+
})
|
|
916
|
+
|
|
917
|
+
it('calls external content handler when force is false and not readonly', async () => {
|
|
918
|
+
vi.spyOn(editor, 'getIsReadonly').mockReturnValue(false)
|
|
919
|
+
|
|
920
|
+
const info = { type: 'text' as const, text: 'test-data' }
|
|
921
|
+
await editor.replaceExternalContent(info, { force: false })
|
|
922
|
+
|
|
923
|
+
expect(mockHandler).toHaveBeenCalledWith(info)
|
|
924
|
+
})
|
|
925
|
+
})
|
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -343,6 +343,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
343
343
|
this.root = new NewRoot(this)
|
|
344
344
|
this.root.children = {}
|
|
345
345
|
|
|
346
|
+
this.markEventAsHandled = this.markEventAsHandled.bind(this)
|
|
347
|
+
|
|
346
348
|
const allShapeUtils = checkShapesAndAddCore(shapeUtils)
|
|
347
349
|
|
|
348
350
|
const _shapeUtils = {} as Record<string, ShapeUtil<any>>
|
|
@@ -4680,8 +4682,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4680
4682
|
return this.store.createComputedCache<Box, TLShape>('pageBoundsCache', (shape) => {
|
|
4681
4683
|
const pageTransform = this.getShapePageTransform(shape)
|
|
4682
4684
|
if (!pageTransform) return undefined
|
|
4683
|
-
|
|
4684
|
-
return Box.FromPoints(
|
|
4685
|
+
|
|
4686
|
+
return Box.FromPoints(
|
|
4687
|
+
pageTransform.applyToPoints(this.getShapeGeometry(shape).boundsVertices)
|
|
4688
|
+
)
|
|
4685
4689
|
})
|
|
4686
4690
|
}
|
|
4687
4691
|
|
|
@@ -8831,8 +8835,13 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8831
8835
|
* Handle external content, such as files, urls, embeds, or plain text which has been put into the app, for example by pasting external text or dropping external images onto canvas.
|
|
8832
8836
|
*
|
|
8833
8837
|
* @param info - Info about the external content.
|
|
8838
|
+
* @param opts - Options for handling external content, including force flag to bypass readonly checks.
|
|
8834
8839
|
*/
|
|
8835
|
-
async putExternalContent<E>(
|
|
8840
|
+
async putExternalContent<E>(
|
|
8841
|
+
info: TLExternalContent<E>,
|
|
8842
|
+
opts = {} as { force?: boolean }
|
|
8843
|
+
): Promise<void> {
|
|
8844
|
+
if (!opts.force && this.getIsReadonly()) return
|
|
8836
8845
|
return this.externalContentHandlers[info.type]?.(info as any)
|
|
8837
8846
|
}
|
|
8838
8847
|
|
|
@@ -8840,8 +8849,13 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8840
8849
|
* Handle replacing external content.
|
|
8841
8850
|
*
|
|
8842
8851
|
* @param info - Info about the external content.
|
|
8852
|
+
* @param opts - Options for handling external content, including force flag to bypass readonly checks.
|
|
8843
8853
|
*/
|
|
8844
|
-
async replaceExternalContent<E>(
|
|
8854
|
+
async replaceExternalContent<E>(
|
|
8855
|
+
info: TLExternalContent<E>,
|
|
8856
|
+
opts = {} as { force?: boolean }
|
|
8857
|
+
): Promise<void> {
|
|
8858
|
+
if (!opts.force && this.getIsReadonly()) return
|
|
8845
8859
|
return this.externalContentHandlers[info.type]?.(info as any)
|
|
8846
8860
|
}
|
|
8847
8861
|
|
|
@@ -10085,6 +10099,37 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10085
10099
|
/** @internal */
|
|
10086
10100
|
private performanceTrackerTimeout = -1 as any
|
|
10087
10101
|
|
|
10102
|
+
/** @internal */
|
|
10103
|
+
private handledEvents = new WeakSet<Event>()
|
|
10104
|
+
|
|
10105
|
+
/**
|
|
10106
|
+
* In tldraw, events are sometimes handled by multiple components. For example, the shapes might
|
|
10107
|
+
* have events, but the canvas handles events too. The way that the canvas handles events can
|
|
10108
|
+
* interfere with the with the shapes event handlers - for example, it calls `.preventDefault()`
|
|
10109
|
+
* on `pointerDown`, which also prevents `click` events from firing on the shapes.
|
|
10110
|
+
*
|
|
10111
|
+
* You can use `.stopPropagation()` to prevent the event from propagating to the rest of the
|
|
10112
|
+
* DOM, but that can impact non-tldraw event handlers set up elsewhere. By using
|
|
10113
|
+
* `markEventAsHandled`, you'll stop other parts of tldraw from handling the event without
|
|
10114
|
+
* impacting other, non-tldraw event handlers. See also {@link Editor.wasEventAlreadyHandled}.
|
|
10115
|
+
*
|
|
10116
|
+
* @public
|
|
10117
|
+
*/
|
|
10118
|
+
markEventAsHandled(e: Event | { nativeEvent: Event }) {
|
|
10119
|
+
const nativeEvent = 'nativeEvent' in e ? e.nativeEvent : e
|
|
10120
|
+
this.handledEvents.add(nativeEvent)
|
|
10121
|
+
}
|
|
10122
|
+
|
|
10123
|
+
/**
|
|
10124
|
+
* Checks if an event has already been handled. See {@link Editor.markEventAsHandled}.
|
|
10125
|
+
*
|
|
10126
|
+
* @public
|
|
10127
|
+
*/
|
|
10128
|
+
wasEventAlreadyHandled(e: Event | { nativeEvent: Event }) {
|
|
10129
|
+
const nativeEvent = 'nativeEvent' in e ? e.nativeEvent : e
|
|
10130
|
+
return this.handledEvents.has(nativeEvent)
|
|
10131
|
+
}
|
|
10132
|
+
|
|
10088
10133
|
/**
|
|
10089
10134
|
* Dispatch an event to the editor.
|
|
10090
10135
|
*
|
|
@@ -10289,7 +10334,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10289
10334
|
|
|
10290
10335
|
this._updateInputsFromEvent(info)
|
|
10291
10336
|
|
|
10292
|
-
const { panSpeed, zoomSpeed
|
|
10337
|
+
const { panSpeed, zoomSpeed } = cameraOptions
|
|
10338
|
+
let wheelBehavior = cameraOptions.wheelBehavior
|
|
10339
|
+
const inputMode = this.user.getUserPreferences().inputMode
|
|
10340
|
+
|
|
10341
|
+
// If the user has set their input mode preference, then use that to determine the wheel behavior
|
|
10342
|
+
if (inputMode !== null) {
|
|
10343
|
+
wheelBehavior = inputMode === 'trackpad' ? 'pan' : 'zoom'
|
|
10344
|
+
}
|
|
10293
10345
|
|
|
10294
10346
|
if (wheelBehavior !== 'none') {
|
|
10295
10347
|
// Stop any camera animation
|
|
@@ -58,8 +58,12 @@ export class FocusManager {
|
|
|
58
58
|
|
|
59
59
|
private handleKeyDown(keyEvent: KeyboardEvent) {
|
|
60
60
|
const container = this.editor.getContainer()
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
const activeEl = document.activeElement
|
|
62
|
+
// Edit mode should remove the focus ring, however if the active element's
|
|
63
|
+
// parent is the contextual toolbar, then allow it.
|
|
64
|
+
if (this.editor.isIn('select.editing_shape') && !activeEl?.closest('.tlui-contextual-toolbar'))
|
|
65
|
+
return
|
|
66
|
+
if (activeEl === container && this.editor.getSelectedShapeIds().length > 0) return
|
|
63
67
|
if (['Tab', 'ArrowUp', 'ArrowDown'].includes(keyEvent.key)) {
|
|
64
68
|
container.classList.remove('tl-container__no-focus-ring')
|
|
65
69
|
}
|
|
@@ -30,6 +30,7 @@ describe('UserPreferencesManager', () => {
|
|
|
30
30
|
isWrapMode: false,
|
|
31
31
|
isDynamicSizeMode: false,
|
|
32
32
|
isPasteAtCursorMode: false,
|
|
33
|
+
inputMode: null,
|
|
33
34
|
...overrides,
|
|
34
35
|
})
|
|
35
36
|
|
|
@@ -233,6 +234,7 @@ describe('UserPreferencesManager', () => {
|
|
|
233
234
|
isDarkMode: false, // light mode
|
|
234
235
|
isWrapMode: mockUserPreferences.isWrapMode,
|
|
235
236
|
isDynamicResizeMode: mockUserPreferences.isDynamicSizeMode,
|
|
237
|
+
inputMode: mockUserPreferences.inputMode,
|
|
236
238
|
})
|
|
237
239
|
})
|
|
238
240
|
|
|
@@ -453,6 +455,22 @@ describe('UserPreferencesManager', () => {
|
|
|
453
455
|
)
|
|
454
456
|
})
|
|
455
457
|
})
|
|
458
|
+
|
|
459
|
+
describe('getInputMode', () => {
|
|
460
|
+
it('should return user input mode setting', () => {
|
|
461
|
+
expect(userPreferencesManager.getInputMode()).toBe(null)
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
it('should return trackpad if input mode is trackpad', () => {
|
|
465
|
+
userPreferencesAtom.set({ ...mockUserPreferences, inputMode: 'trackpad' })
|
|
466
|
+
expect(userPreferencesManager.getInputMode()).toBe('trackpad')
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
it('should return mouse if input mode is mouse', () => {
|
|
470
|
+
userPreferencesAtom.set({ ...mockUserPreferences, inputMode: 'mouse' })
|
|
471
|
+
expect(userPreferencesManager.getInputMode()).toBe('mouse')
|
|
472
|
+
})
|
|
473
|
+
})
|
|
456
474
|
})
|
|
457
475
|
|
|
458
476
|
describe('reactive behavior', () => {
|
|
@@ -50,6 +50,7 @@ export class UserPreferencesManager {
|
|
|
50
50
|
isWrapMode: this.getIsWrapMode(),
|
|
51
51
|
isDynamicResizeMode: this.getIsDynamicResizeMode(),
|
|
52
52
|
showUiLabels: this.getShowUiLabels(),
|
|
53
|
+
inputMode: this.getInputMode(),
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -124,4 +125,8 @@ export class UserPreferencesManager {
|
|
|
124
125
|
@computed getShowUiLabels() {
|
|
125
126
|
return this.user.userPreferences.get().showUiLabels ?? defaultUserPreferences.showUiLabels
|
|
126
127
|
}
|
|
128
|
+
|
|
129
|
+
@computed getInputMode() {
|
|
130
|
+
return this.user.userPreferences.get().inputMode ?? defaultUserPreferences.inputMode
|
|
131
|
+
}
|
|
127
132
|
}
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { useValue } from '@tldraw/state-react'
|
|
2
2
|
import React, { useEffect, useMemo } from 'react'
|
|
3
3
|
import { RIGHT_MOUSE_BUTTON } from '../constants'
|
|
4
|
-
import {
|
|
5
|
-
preventDefault,
|
|
6
|
-
releasePointerCapture,
|
|
7
|
-
setPointerCapture,
|
|
8
|
-
stopEventPropagation,
|
|
9
|
-
} from '../utils/dom'
|
|
4
|
+
import { preventDefault, releasePointerCapture, setPointerCapture } from '../utils/dom'
|
|
10
5
|
import { getPointerInfo } from '../utils/getPointerInfo'
|
|
11
6
|
import { useEditor } from './useEditor'
|
|
12
7
|
|
|
@@ -17,14 +12,14 @@ export function useCanvasEvents() {
|
|
|
17
12
|
const events = useMemo(
|
|
18
13
|
function canvasEvents() {
|
|
19
14
|
function onPointerDown(e: React.PointerEvent) {
|
|
20
|
-
if ((e
|
|
15
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
21
16
|
|
|
22
17
|
if (e.button === RIGHT_MOUSE_BUTTON) {
|
|
23
18
|
editor.dispatch({
|
|
24
19
|
type: 'pointer',
|
|
25
20
|
target: 'canvas',
|
|
26
21
|
name: 'right_click',
|
|
27
|
-
...getPointerInfo(e),
|
|
22
|
+
...getPointerInfo(editor, e),
|
|
28
23
|
})
|
|
29
24
|
return
|
|
30
25
|
}
|
|
@@ -37,12 +32,12 @@ export function useCanvasEvents() {
|
|
|
37
32
|
type: 'pointer',
|
|
38
33
|
target: 'canvas',
|
|
39
34
|
name: 'pointer_down',
|
|
40
|
-
...getPointerInfo(e),
|
|
35
|
+
...getPointerInfo(editor, e),
|
|
41
36
|
})
|
|
42
37
|
}
|
|
43
38
|
|
|
44
39
|
function onPointerUp(e: React.PointerEvent) {
|
|
45
|
-
if ((e
|
|
40
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
46
41
|
if (e.button !== 0 && e.button !== 1 && e.button !== 2 && e.button !== 5) return
|
|
47
42
|
|
|
48
43
|
releasePointerCapture(e.currentTarget, e)
|
|
@@ -51,31 +46,33 @@ export function useCanvasEvents() {
|
|
|
51
46
|
type: 'pointer',
|
|
52
47
|
target: 'canvas',
|
|
53
48
|
name: 'pointer_up',
|
|
54
|
-
...getPointerInfo(e),
|
|
49
|
+
...getPointerInfo(editor, e),
|
|
55
50
|
})
|
|
56
51
|
}
|
|
57
52
|
|
|
58
53
|
function onPointerEnter(e: React.PointerEvent) {
|
|
59
|
-
if ((e
|
|
54
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
60
55
|
if (editor.getInstanceState().isPenMode && e.pointerType !== 'pen') return
|
|
61
56
|
const canHover = e.pointerType === 'mouse' || e.pointerType === 'pen'
|
|
62
57
|
editor.updateInstanceState({ isHoveringCanvas: canHover ? true : null })
|
|
63
58
|
}
|
|
64
59
|
|
|
65
60
|
function onPointerLeave(e: React.PointerEvent) {
|
|
66
|
-
if ((e
|
|
61
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
67
62
|
if (editor.getInstanceState().isPenMode && e.pointerType !== 'pen') return
|
|
68
63
|
const canHover = e.pointerType === 'mouse' || e.pointerType === 'pen'
|
|
69
64
|
editor.updateInstanceState({ isHoveringCanvas: canHover ? false : null })
|
|
70
65
|
}
|
|
71
66
|
|
|
72
67
|
function onTouchStart(e: React.TouchEvent) {
|
|
73
|
-
|
|
68
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
69
|
+
editor.markEventAsHandled(e)
|
|
74
70
|
preventDefault(e)
|
|
75
71
|
}
|
|
76
72
|
|
|
77
73
|
function onTouchEnd(e: React.TouchEvent) {
|
|
78
|
-
|
|
74
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
75
|
+
editor.markEventAsHandled(e)
|
|
79
76
|
// check that e.target is an HTMLElement
|
|
80
77
|
if (!(e.target instanceof HTMLElement)) return
|
|
81
78
|
|
|
@@ -94,12 +91,14 @@ export function useCanvasEvents() {
|
|
|
94
91
|
}
|
|
95
92
|
|
|
96
93
|
function onDragOver(e: React.DragEvent<Element>) {
|
|
94
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
97
95
|
preventDefault(e)
|
|
98
96
|
}
|
|
99
97
|
|
|
100
98
|
async function onDrop(e: React.DragEvent<Element>) {
|
|
99
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
101
100
|
preventDefault(e)
|
|
102
|
-
|
|
101
|
+
e.stopPropagation()
|
|
103
102
|
|
|
104
103
|
if (e.dataTransfer?.files?.length) {
|
|
105
104
|
const files = Array.from(e.dataTransfer.files)
|
|
@@ -124,7 +123,8 @@ export function useCanvasEvents() {
|
|
|
124
123
|
}
|
|
125
124
|
|
|
126
125
|
function onClick(e: React.MouseEvent) {
|
|
127
|
-
|
|
126
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
127
|
+
e.stopPropagation()
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
return {
|
|
@@ -151,8 +151,8 @@ export function useCanvasEvents() {
|
|
|
151
151
|
let lastX: number, lastY: number
|
|
152
152
|
|
|
153
153
|
function onPointerMove(e: PointerEvent) {
|
|
154
|
-
if ((e
|
|
155
|
-
|
|
154
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
155
|
+
editor.markEventAsHandled(e)
|
|
156
156
|
|
|
157
157
|
if (e.clientX === lastX && e.clientY === lastY) return
|
|
158
158
|
lastX = e.clientX
|
|
@@ -168,7 +168,7 @@ export function useCanvasEvents() {
|
|
|
168
168
|
type: 'pointer',
|
|
169
169
|
target: 'canvas',
|
|
170
170
|
name: 'pointer_move',
|
|
171
|
-
...getPointerInfo(singleEvent),
|
|
171
|
+
...getPointerInfo(editor, singleEvent),
|
|
172
172
|
})
|
|
173
173
|
}
|
|
174
174
|
}
|
|
@@ -2,7 +2,7 @@ import { useValue } from '@tldraw/state-react'
|
|
|
2
2
|
import { useEffect } from 'react'
|
|
3
3
|
import { Editor } from '../editor/Editor'
|
|
4
4
|
import { TLKeyboardEventInfo } from '../editor/types/event-types'
|
|
5
|
-
import { activeElementShouldCaptureKeys, preventDefault
|
|
5
|
+
import { activeElementShouldCaptureKeys, preventDefault } from '../utils/dom'
|
|
6
6
|
import { isAccelKey } from '../utils/keyboard'
|
|
7
7
|
import { useContainer } from './useContainer'
|
|
8
8
|
import { useEditor } from './useEditor'
|
|
@@ -29,7 +29,7 @@ export function useDocumentEvents() {
|
|
|
29
29
|
// re-dispatched, which would lead to an infinite loop.
|
|
30
30
|
if ((e as any).isSpecialRedispatchedEvent) return
|
|
31
31
|
preventDefault(e)
|
|
32
|
-
|
|
32
|
+
e.stopPropagation()
|
|
33
33
|
const cvs = container.querySelector('.tl-canvas')
|
|
34
34
|
if (!cvs) return
|
|
35
35
|
const newEvent = new DragEvent(e.type, e)
|
|
@@ -103,8 +103,8 @@ export function useDocumentEvents() {
|
|
|
103
103
|
preventDefault(e)
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
if ((e
|
|
107
|
-
|
|
106
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
107
|
+
editor.markEventAsHandled(e)
|
|
108
108
|
const hasSelectedShapes = !!editor.getSelectedShapeIds().length
|
|
109
109
|
|
|
110
110
|
switch (e.key) {
|
|
@@ -211,8 +211,8 @@ export function useDocumentEvents() {
|
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
const handleKeyUp = (e: KeyboardEvent) => {
|
|
214
|
-
if ((e
|
|
215
|
-
|
|
214
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
215
|
+
editor.markEventAsHandled(e)
|
|
216
216
|
|
|
217
217
|
if (areShortcutsDisabled(editor)) {
|
|
218
218
|
return
|
|
@@ -19,7 +19,7 @@ export function useFixSafariDoubleTapZoomPencilEvents(ref: React.RefObject<HTMLE
|
|
|
19
19
|
|
|
20
20
|
const handleEvent = (e: PointerEvent | TouchEvent) => {
|
|
21
21
|
if (e instanceof PointerEvent && e.pointerType === 'pen') {
|
|
22
|
-
|
|
22
|
+
editor.markEventAsHandled(e)
|
|
23
23
|
const { target } = e
|
|
24
24
|
|
|
25
25
|
// Allow events to propagate if the app is editing a shape, or if the event is occurring in a text area or input
|
|
@@ -3,7 +3,7 @@ import { createUseGesture, pinchAction, wheelAction } from '@use-gesture/react'
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import { TLWheelEventInfo } from '../editor/types/event-types'
|
|
5
5
|
import { Vec } from '../primitives/Vec'
|
|
6
|
-
import { preventDefault
|
|
6
|
+
import { preventDefault } from '../utils/dom'
|
|
7
7
|
import { isAccelKey } from '../utils/keyboard'
|
|
8
8
|
import { normalizeWheel } from '../utils/normalizeWheel'
|
|
9
9
|
import { useEditor } from './useEditor'
|
|
@@ -113,7 +113,7 @@ export function useGestureEvents(ref: React.RefObject<HTMLDivElement>) {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
preventDefault(event)
|
|
116
|
-
|
|
116
|
+
event.stopPropagation()
|
|
117
117
|
const delta = normalizeWheel(event)
|
|
118
118
|
|
|
119
119
|
if (delta.x === 0 && delta.y === 0) return
|
|
@@ -16,7 +16,7 @@ export function useHandleEvents(id: TLShapeId, handleId: string) {
|
|
|
16
16
|
|
|
17
17
|
return React.useMemo(() => {
|
|
18
18
|
const onPointerDown = (e: React.PointerEvent) => {
|
|
19
|
-
if ((e
|
|
19
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
20
20
|
|
|
21
21
|
// Must set pointer capture on an HTML element!
|
|
22
22
|
const target = loopToHtmlElement(e.currentTarget)
|
|
@@ -32,7 +32,7 @@ export function useHandleEvents(id: TLShapeId, handleId: string) {
|
|
|
32
32
|
handle,
|
|
33
33
|
shape,
|
|
34
34
|
name: 'pointer_down',
|
|
35
|
-
...getPointerInfo(e),
|
|
35
|
+
...getPointerInfo(editor, e),
|
|
36
36
|
})
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -40,7 +40,7 @@ export function useHandleEvents(id: TLShapeId, handleId: string) {
|
|
|
40
40
|
let lastX: number, lastY: number
|
|
41
41
|
|
|
42
42
|
const onPointerMove = (e: React.PointerEvent) => {
|
|
43
|
-
if ((e
|
|
43
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
44
44
|
if (e.clientX === lastX && e.clientY === lastY) return
|
|
45
45
|
lastX = e.clientX
|
|
46
46
|
lastY = e.clientY
|
|
@@ -55,12 +55,12 @@ export function useHandleEvents(id: TLShapeId, handleId: string) {
|
|
|
55
55
|
handle,
|
|
56
56
|
shape,
|
|
57
57
|
name: 'pointer_move',
|
|
58
|
-
...getPointerInfo(e),
|
|
58
|
+
...getPointerInfo(editor, e),
|
|
59
59
|
})
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
const onPointerUp = (e: React.PointerEvent) => {
|
|
63
|
-
if ((e
|
|
63
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
64
64
|
|
|
65
65
|
const target = loopToHtmlElement(e.currentTarget)
|
|
66
66
|
releasePointerCapture(target, e)
|
|
@@ -75,7 +75,7 @@ export function useHandleEvents(id: TLShapeId, handleId: string) {
|
|
|
75
75
|
handle,
|
|
76
76
|
shape,
|
|
77
77
|
name: 'pointer_up',
|
|
78
|
-
...getPointerInfo(e),
|
|
78
|
+
...getPointerInfo(editor, e),
|
|
79
79
|
})
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { useMemo } from 'react'
|
|
2
2
|
import { RIGHT_MOUSE_BUTTON } from '../constants'
|
|
3
3
|
import { TLSelectionHandle } from '../editor/types/selection-types'
|
|
4
|
-
import {
|
|
5
|
-
loopToHtmlElement,
|
|
6
|
-
releasePointerCapture,
|
|
7
|
-
setPointerCapture,
|
|
8
|
-
stopEventPropagation,
|
|
9
|
-
} from '../utils/dom'
|
|
4
|
+
import { loopToHtmlElement, releasePointerCapture, setPointerCapture } from '../utils/dom'
|
|
10
5
|
import { getPointerInfo } from '../utils/getPointerInfo'
|
|
11
6
|
import { useEditor } from './useEditor'
|
|
12
7
|
|
|
@@ -17,7 +12,7 @@ export function useSelectionEvents(handle: TLSelectionHandle) {
|
|
|
17
12
|
const events = useMemo(
|
|
18
13
|
function selectionEvents() {
|
|
19
14
|
const onPointerDown: React.PointerEventHandler = (e) => {
|
|
20
|
-
if ((e
|
|
15
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
21
16
|
|
|
22
17
|
if (e.button === RIGHT_MOUSE_BUTTON) {
|
|
23
18
|
editor.dispatch({
|
|
@@ -25,7 +20,7 @@ export function useSelectionEvents(handle: TLSelectionHandle) {
|
|
|
25
20
|
target: 'selection',
|
|
26
21
|
handle,
|
|
27
22
|
name: 'right_click',
|
|
28
|
-
...getPointerInfo(e),
|
|
23
|
+
...getPointerInfo(editor, e),
|
|
29
24
|
})
|
|
30
25
|
return
|
|
31
26
|
}
|
|
@@ -52,16 +47,16 @@ export function useSelectionEvents(handle: TLSelectionHandle) {
|
|
|
52
47
|
type: 'pointer',
|
|
53
48
|
target: 'selection',
|
|
54
49
|
handle,
|
|
55
|
-
...getPointerInfo(e),
|
|
50
|
+
...getPointerInfo(editor, e),
|
|
56
51
|
})
|
|
57
|
-
|
|
52
|
+
editor.markEventAsHandled(e)
|
|
58
53
|
}
|
|
59
54
|
|
|
60
55
|
// Track the last screen point
|
|
61
56
|
let lastX: number, lastY: number
|
|
62
57
|
|
|
63
58
|
function onPointerMove(e: React.PointerEvent) {
|
|
64
|
-
if ((e
|
|
59
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
65
60
|
if (e.button !== 0) return
|
|
66
61
|
if (e.clientX === lastX && e.clientY === lastY) return
|
|
67
62
|
lastX = e.clientX
|
|
@@ -72,12 +67,12 @@ export function useSelectionEvents(handle: TLSelectionHandle) {
|
|
|
72
67
|
type: 'pointer',
|
|
73
68
|
target: 'selection',
|
|
74
69
|
handle,
|
|
75
|
-
...getPointerInfo(e),
|
|
70
|
+
...getPointerInfo(editor, e),
|
|
76
71
|
})
|
|
77
72
|
}
|
|
78
73
|
|
|
79
74
|
const onPointerUp: React.PointerEventHandler = (e) => {
|
|
80
|
-
if ((e
|
|
75
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
81
76
|
if (e.button !== 0) return
|
|
82
77
|
|
|
83
78
|
editor.dispatch({
|
|
@@ -85,7 +80,7 @@ export function useSelectionEvents(handle: TLSelectionHandle) {
|
|
|
85
80
|
type: 'pointer',
|
|
86
81
|
target: 'selection',
|
|
87
82
|
handle,
|
|
88
|
-
...getPointerInfo(e),
|
|
83
|
+
...getPointerInfo(editor, e),
|
|
89
84
|
})
|
|
90
85
|
}
|
|
91
86
|
|