@tldraw/editor 5.2.0-next.e2b8d10bf10e → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist-cjs/index.d.ts +112 -53
- package/dist-cjs/index.js +7 -8
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/components/MenuClickCapture.js +8 -5
- package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
- package/dist-cjs/lib/components/Shape.js +47 -1
- package/dist-cjs/lib/components/Shape.js.map +3 -3
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +1 -16
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +97 -45
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js +3 -3
- package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js +28 -4
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +3 -3
- package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js +24 -2
- package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js +14 -3
- package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js +4 -2
- package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +7 -3
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TickManager/TickManager.js +0 -1
- package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +15 -2
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
- package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js +79 -0
- package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js.map +7 -0
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +4 -0
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +2 -2
- package/dist-cjs/lib/{hooks/useTransform.js → editor/tools/cancelShapeCreationOnLongPress.js} +8 -23
- package/dist-cjs/lib/editor/tools/cancelShapeCreationOnLongPress.js.map +7 -0
- package/dist-cjs/lib/editor/types/event-types.js.map +2 -2
- package/dist-cjs/lib/globals/environment.js +6 -1
- package/dist-cjs/lib/globals/environment.js.map +2 -2
- package/dist-cjs/lib/hooks/EditorComponentsContext.js.map +2 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js +17 -8
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useEditorComponents.js +0 -1
- package/dist-cjs/lib/hooks/useEditorComponents.js.map +2 -2
- package/dist-cjs/lib/hooks/usePresence.js.map +2 -2
- package/dist-cjs/lib/hooks/useScreenBounds.js +1 -0
- package/dist-cjs/lib/hooks/useScreenBounds.js.map +2 -2
- package/dist-cjs/lib/license/LicenseManager.js +5 -1
- package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
- package/dist-cjs/lib/primitives/Vec.js +3 -2
- package/dist-cjs/lib/primitives/Vec.js.map +2 -2
- package/dist-cjs/lib/utils/dom.js +12 -0
- package/dist-cjs/lib/utils/dom.js.map +2 -2
- package/dist-cjs/lib/utils/getPointerInfo.js +2 -1
- package/dist-cjs/lib/utils/getPointerInfo.js.map +2 -2
- package/dist-cjs/lib/utils/pointer.js +37 -0
- package/dist-cjs/lib/utils/pointer.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 +112 -53
- package/dist-esm/index.mjs +9 -13
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/components/MenuClickCapture.mjs +8 -5
- package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
- package/dist-esm/lib/components/Shape.mjs +48 -2
- package/dist-esm/lib/components/Shape.mjs.map +3 -3
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +1 -16
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +97 -45
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs +3 -3
- package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs +28 -4
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +3 -3
- package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs +24 -2
- package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs +14 -3
- package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs +4 -2
- package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +7 -3
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs +0 -1
- package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +15 -2
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
- package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs +59 -0
- package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs.map +7 -0
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +4 -0
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/cancelShapeCreationOnLongPress.mjs +8 -0
- package/dist-esm/lib/editor/tools/cancelShapeCreationOnLongPress.mjs.map +7 -0
- package/dist-esm/lib/editor/types/event-types.mjs.map +2 -2
- package/dist-esm/lib/globals/environment.mjs +6 -1
- package/dist-esm/lib/globals/environment.mjs.map +2 -2
- package/dist-esm/lib/hooks/EditorComponentsContext.mjs.map +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +17 -8
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useEditorComponents.mjs +0 -1
- package/dist-esm/lib/hooks/useEditorComponents.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePresence.mjs.map +2 -2
- package/dist-esm/lib/hooks/useScreenBounds.mjs +1 -0
- package/dist-esm/lib/hooks/useScreenBounds.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseManager.mjs +5 -1
- package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
- package/dist-esm/lib/primitives/Vec.mjs +3 -2
- package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
- package/dist-esm/lib/utils/dom.mjs +12 -0
- package/dist-esm/lib/utils/dom.mjs.map +2 -2
- package/dist-esm/lib/utils/getPointerInfo.mjs +2 -1
- package/dist-esm/lib/utils/getPointerInfo.mjs.map +2 -2
- package/dist-esm/lib/utils/pointer.mjs +17 -0
- package/dist-esm/lib/utils/pointer.mjs.map +7 -0
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +15 -3
- package/package.json +11 -8
- package/src/index.ts +4 -10
- package/src/lib/components/MenuClickCapture.tsx +8 -4
- package/src/lib/components/Shape.tsx +88 -1
- package/src/lib/components/default-components/DefaultCanvas.tsx +1 -17
- package/src/lib/editor/Editor.test.ts +36 -0
- package/src/lib/editor/Editor.ts +145 -55
- package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.test.ts +43 -16
- package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.ts +8 -5
- package/src/lib/editor/managers/FontManager/FontManager.test.ts +37 -10
- package/src/lib/editor/managers/FontManager/FontManager.ts +46 -1
- package/src/lib/editor/managers/HistoryManager/HistoryManager.test.ts +32 -0
- package/src/lib/editor/managers/HistoryManager/HistoryManager.ts +34 -4
- package/src/lib/editor/managers/InputsManager/InputsManager.test.ts +61 -0
- package/src/lib/editor/managers/InputsManager/InputsManager.ts +16 -4
- package/src/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.ts +9 -2
- package/src/lib/editor/managers/TextManager/TextManager.test.ts +16 -14
- package/src/lib/editor/managers/TextManager/TextManager.ts +17 -2
- package/src/lib/editor/managers/TickManager/TickManager.test.ts +0 -40
- package/src/lib/editor/managers/TickManager/TickManager.ts +0 -1
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +12 -2
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +27 -2
- package/src/lib/editor/overlays/strokeShapeIndicators.ts +86 -0
- package/src/lib/editor/shapes/ShapeUtil.ts +41 -0
- package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +5 -0
- package/src/lib/editor/tools/cancelShapeCreationOnLongPress.ts +31 -0
- package/src/lib/editor/types/event-types.ts +7 -0
- package/src/lib/globals/environment.ts +7 -0
- package/src/lib/hooks/EditorComponentsContext.tsx +0 -2
- package/src/lib/hooks/useCanvasEvents.ts +24 -12
- package/src/lib/hooks/useEditorComponents.tsx +0 -1
- package/src/lib/hooks/usePresence.ts +2 -2
- package/src/lib/hooks/useScreenBounds.ts +3 -0
- package/src/lib/license/LicenseManager.test.ts +24 -16
- package/src/lib/license/LicenseManager.ts +8 -2
- package/src/lib/primitives/Vec.ts +5 -2
- package/src/lib/test/ContentElementSlot.test.tsx +201 -0
- package/src/lib/utils/dom.ts +34 -0
- package/src/lib/utils/getPointerInfo.ts +2 -1
- package/src/lib/utils/pointer.test.ts +76 -0
- package/src/lib/utils/pointer.ts +42 -0
- package/src/version.ts +3 -3
- package/dist-cjs/lib/components/default-components/DefaultSelectionBackground.js +0 -49
- package/dist-cjs/lib/components/default-components/DefaultSelectionBackground.js.map +0 -7
- package/dist-cjs/lib/editor/overlays/ShapeIndicatorOverlayUtil.js +0 -161
- package/dist-cjs/lib/editor/overlays/ShapeIndicatorOverlayUtil.js.map +0 -7
- package/dist-cjs/lib/hooks/useTransform.js.map +0 -7
- package/dist-esm/lib/components/default-components/DefaultSelectionBackground.mjs +0 -19
- package/dist-esm/lib/components/default-components/DefaultSelectionBackground.mjs.map +0 -7
- package/dist-esm/lib/editor/overlays/ShapeIndicatorOverlayUtil.mjs +0 -141
- package/dist-esm/lib/editor/overlays/ShapeIndicatorOverlayUtil.mjs.map +0 -7
- package/dist-esm/lib/hooks/useTransform.mjs +0 -23
- package/dist-esm/lib/hooks/useTransform.mjs.map +0 -7
- package/src/lib/components/default-components/DefaultSelectionBackground.tsx +0 -25
- package/src/lib/editor/overlays/ShapeIndicatorOverlayUtil.ts +0 -216
- package/src/lib/hooks/useTransform.ts +0 -30
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { act, render } from '@testing-library/react'
|
|
2
|
+
import { BaseBoxShapeUtil, createShapeId, Editor, RecordProps, T, TLShape } from '../..'
|
|
3
|
+
import { TldrawEditor } from '../TldrawEditor'
|
|
4
|
+
|
|
5
|
+
const PERSISTENT_SHAPE_TYPE = 'test-persistent'
|
|
6
|
+
|
|
7
|
+
declare module '@tldraw/tlschema' {
|
|
8
|
+
export interface TLGlobalShapePropsMap {
|
|
9
|
+
[PERSISTENT_SHAPE_TYPE]: { w: number; h: number }
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type IPersistentShape = TLShape<typeof PERSISTENT_SHAPE_TYPE>
|
|
14
|
+
|
|
15
|
+
// App-owned element registry and parking lot, the way an app using
|
|
16
|
+
// getContentElement would keep elements alive across editor sessions.
|
|
17
|
+
const elements = new Map<string, HTMLElement>()
|
|
18
|
+
let parkingLot: HTMLDivElement
|
|
19
|
+
const releaseCalls: Array<{ shapeId: string; element: HTMLElement; slotWasConnected: boolean }> = []
|
|
20
|
+
|
|
21
|
+
class PersistentShapeUtil extends BaseBoxShapeUtil<IPersistentShape> {
|
|
22
|
+
static override type = PERSISTENT_SHAPE_TYPE
|
|
23
|
+
static override props: RecordProps<IPersistentShape> = {
|
|
24
|
+
w: T.number,
|
|
25
|
+
h: T.number,
|
|
26
|
+
}
|
|
27
|
+
getDefaultProps(): IPersistentShape['props'] {
|
|
28
|
+
return { w: 100, h: 100 }
|
|
29
|
+
}
|
|
30
|
+
getIndicatorPath() {
|
|
31
|
+
return undefined
|
|
32
|
+
}
|
|
33
|
+
component() {
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
override getAppOwnedElement(shape: IPersistentShape) {
|
|
37
|
+
let element = elements.get(shape.id)
|
|
38
|
+
if (!element) {
|
|
39
|
+
element = document.createElement('div')
|
|
40
|
+
element.dataset.testElementFor = shape.id
|
|
41
|
+
elements.set(shape.id, element)
|
|
42
|
+
}
|
|
43
|
+
return element
|
|
44
|
+
}
|
|
45
|
+
override onReleaseAppOwnedElement(shape: IPersistentShape, element: HTMLElement) {
|
|
46
|
+
releaseCalls.push({
|
|
47
|
+
shapeId: shape.id,
|
|
48
|
+
element,
|
|
49
|
+
slotWasConnected: element.parentElement?.isConnected ?? false,
|
|
50
|
+
})
|
|
51
|
+
parkingLot.appendChild(element)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getSlotFor(shapeId: string) {
|
|
56
|
+
return document.querySelector(`[data-shape-id="${shapeId}"] .tl-content-slot`)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe('content element slot', () => {
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
elements.clear()
|
|
62
|
+
releaseCalls.length = 0
|
|
63
|
+
parkingLot = document.createElement('div')
|
|
64
|
+
document.body.appendChild(parkingLot)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
parkingLot.remove()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
async function renderEditor() {
|
|
72
|
+
let editor!: Editor
|
|
73
|
+
const result = await act(async () =>
|
|
74
|
+
render(
|
|
75
|
+
<TldrawEditor
|
|
76
|
+
shapeUtils={[PersistentShapeUtil]}
|
|
77
|
+
onMount={(e) => {
|
|
78
|
+
editor = e
|
|
79
|
+
}}
|
|
80
|
+
/>
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
return { ...result, editor }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
it('adopts the app-owned element into the shape and releases it before editor unmount', async () => {
|
|
87
|
+
const { editor, unmount } = await renderEditor()
|
|
88
|
+
const id = createShapeId()
|
|
89
|
+
|
|
90
|
+
await act(async () => {
|
|
91
|
+
editor.createShape<IPersistentShape>({ id, type: PERSISTENT_SHAPE_TYPE, x: 0, y: 0 })
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const element = elements.get(id)!
|
|
95
|
+
expect(element).toBeDefined()
|
|
96
|
+
expect(element.parentElement).toBe(getSlotFor(id))
|
|
97
|
+
expect(element.isConnected).toBe(true)
|
|
98
|
+
|
|
99
|
+
await act(async () => {
|
|
100
|
+
unmount()
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
// The release callback fired exactly once, while the slot was still
|
|
104
|
+
// connected to the document, so a state-preserving move was possible.
|
|
105
|
+
expect(releaseCalls).toHaveLength(1)
|
|
106
|
+
expect(releaseCalls[0]).toMatchObject({ shapeId: id, element, slotWasConnected: true })
|
|
107
|
+
// The app reclaimed the element into its parking lot and it survived teardown.
|
|
108
|
+
expect(element.parentElement).toBe(parkingLot)
|
|
109
|
+
expect(element.isConnected).toBe(true)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('releases the element when the shape is deleted', async () => {
|
|
113
|
+
const { editor, unmount } = await renderEditor()
|
|
114
|
+
const id = createShapeId()
|
|
115
|
+
|
|
116
|
+
await act(async () => {
|
|
117
|
+
editor.createShape<IPersistentShape>({ id, type: PERSISTENT_SHAPE_TYPE, x: 0, y: 0 })
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
const element = elements.get(id)!
|
|
121
|
+
expect(element.parentElement).toBe(getSlotFor(id))
|
|
122
|
+
|
|
123
|
+
await act(async () => {
|
|
124
|
+
editor.deleteShape(id)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
expect(releaseCalls).toHaveLength(1)
|
|
128
|
+
expect(releaseCalls[0]).toMatchObject({ shapeId: id, element, slotWasConnected: true })
|
|
129
|
+
expect(element.parentElement).toBe(parkingLot)
|
|
130
|
+
|
|
131
|
+
await act(async () => {
|
|
132
|
+
unmount()
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
// The slot is gone; no second release for the deleted shape.
|
|
136
|
+
expect(releaseCalls).toHaveLength(1)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('re-adopts a parked element when the editor remounts, without recreating it', async () => {
|
|
140
|
+
const first = await renderEditor()
|
|
141
|
+
const id = createShapeId()
|
|
142
|
+
|
|
143
|
+
await act(async () => {
|
|
144
|
+
first.editor.createShape<IPersistentShape>({ id, type: PERSISTENT_SHAPE_TYPE, x: 0, y: 0 })
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
const element = elements.get(id)!
|
|
148
|
+
|
|
149
|
+
await act(async () => {
|
|
150
|
+
first.unmount()
|
|
151
|
+
})
|
|
152
|
+
expect(element.parentElement).toBe(parkingLot)
|
|
153
|
+
|
|
154
|
+
const second = await renderEditor()
|
|
155
|
+
await act(async () => {
|
|
156
|
+
second.editor.createShape<IPersistentShape>({ id, type: PERSISTENT_SHAPE_TYPE, x: 0, y: 0 })
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
// The same element instance was adopted into the new editor's slot.
|
|
160
|
+
expect(elements.get(id)).toBe(element)
|
|
161
|
+
expect(element.parentElement).toBe(getSlotFor(id))
|
|
162
|
+
|
|
163
|
+
await act(async () => {
|
|
164
|
+
second.unmount()
|
|
165
|
+
})
|
|
166
|
+
expect(element.parentElement).toBe(parkingLot)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('keeps the element in the same DOM position when z-order changes', async () => {
|
|
170
|
+
const { editor, unmount } = await renderEditor()
|
|
171
|
+
const id = createShapeId()
|
|
172
|
+
const otherId = createShapeId()
|
|
173
|
+
|
|
174
|
+
await act(async () => {
|
|
175
|
+
editor.createShape<IPersistentShape>({ id, type: PERSISTENT_SHAPE_TYPE, x: 0, y: 0 })
|
|
176
|
+
editor.createShape<IPersistentShape>({
|
|
177
|
+
id: otherId,
|
|
178
|
+
type: PERSISTENT_SHAPE_TYPE,
|
|
179
|
+
x: 50,
|
|
180
|
+
y: 50,
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
const element = elements.get(id)!
|
|
185
|
+
const slot = getSlotFor(id)
|
|
186
|
+
expect(element.parentElement).toBe(slot)
|
|
187
|
+
|
|
188
|
+
await act(async () => {
|
|
189
|
+
editor.sendToBack([id])
|
|
190
|
+
editor.bringToFront([id])
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
// No release fired and the element is still in the same slot node.
|
|
194
|
+
expect(releaseCalls).toHaveLength(0)
|
|
195
|
+
expect(element.parentElement).toBe(slot)
|
|
196
|
+
|
|
197
|
+
await act(async () => {
|
|
198
|
+
unmount()
|
|
199
|
+
})
|
|
200
|
+
})
|
|
201
|
+
})
|
package/src/lib/utils/dom.ts
CHANGED
|
@@ -100,6 +100,40 @@ export function setStyleProperty(
|
|
|
100
100
|
elm.style.setProperty(property, String(value))
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Move an element into a new parent, preserving its state where the platform allows it.
|
|
105
|
+
*
|
|
106
|
+
* Uses `Node.moveBefore` (Chromium 133+, Firefox 144+) when both the element and parent are
|
|
107
|
+
* connected to the same document — this moves the element without resetting its state, so
|
|
108
|
+
* iframes don't reload and media keeps playing. Otherwise (older browsers, disconnected nodes,
|
|
109
|
+
* or a cross-document move) it falls back to `appendChild`, which moves the element but resets
|
|
110
|
+
* its state like an iframe reload.
|
|
111
|
+
*
|
|
112
|
+
* This is the primitive tldraw uses to adopt `ShapeUtil.getAppOwnedElement` elements, exposed so
|
|
113
|
+
* apps can perform symmetric state-preserving moves from `ShapeUtil.onReleaseAppOwnedElement` —
|
|
114
|
+
* for example moving an element to an off-canvas parking lot between editor sessions.
|
|
115
|
+
*
|
|
116
|
+
* @param parent - The element to move `element` into, as its last child.
|
|
117
|
+
* @param element - The element to move.
|
|
118
|
+
* @public
|
|
119
|
+
*/
|
|
120
|
+
export function moveElementInto(parent: HTMLElement, element: HTMLElement) {
|
|
121
|
+
if (
|
|
122
|
+
element.isConnected &&
|
|
123
|
+
parent.isConnected &&
|
|
124
|
+
typeof (parent as any).moveBefore === 'function' &&
|
|
125
|
+
element.ownerDocument === parent.ownerDocument
|
|
126
|
+
) {
|
|
127
|
+
try {
|
|
128
|
+
;(parent as any).moveBefore(element, null)
|
|
129
|
+
return
|
|
130
|
+
} catch {
|
|
131
|
+
// fall through to appendChild
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
parent.appendChild(element)
|
|
135
|
+
}
|
|
136
|
+
|
|
103
137
|
/** @internal */
|
|
104
138
|
export function elementShouldCaptureKeys(el: Element | null, includeButtonsAndMenus = true) {
|
|
105
139
|
if (!el) return false
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type React from 'react'
|
|
2
2
|
import { Editor } from '../editor/Editor'
|
|
3
3
|
import { isAccelKey } from './keyboard'
|
|
4
|
+
import { getPointerEventButton } from './pointer'
|
|
4
5
|
|
|
5
6
|
/** @public */
|
|
6
7
|
export function getPointerInfo(editor: Editor, e: React.PointerEvent | PointerEvent) {
|
|
@@ -18,7 +19,7 @@ export function getPointerInfo(editor: Editor, e: React.PointerEvent | PointerEv
|
|
|
18
19
|
metaKey: e.metaKey,
|
|
19
20
|
accelKey: isAccelKey(e),
|
|
20
21
|
pointerId: e.pointerId,
|
|
21
|
-
button: e
|
|
22
|
+
button: getPointerEventButton(e),
|
|
22
23
|
isPen: e.pointerType === 'pen',
|
|
23
24
|
}
|
|
24
25
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { tlenv } from '../globals/environment'
|
|
2
|
+
import { TestEditor } from '../test/TestEditor'
|
|
3
|
+
import { getPointerInfo } from './getPointerInfo'
|
|
4
|
+
import { isDirectDisplayPen, isSecondaryClickEvent } from './pointer'
|
|
5
|
+
|
|
6
|
+
const originalIsDarwin = tlenv.isDarwin
|
|
7
|
+
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
tlenv.isDarwin = originalIsDarwin
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
describe('isSecondaryClickEvent', () => {
|
|
13
|
+
it('treats ctrl + left-click as a secondary click on macOS', () => {
|
|
14
|
+
tlenv.isDarwin = true
|
|
15
|
+
expect(isSecondaryClickEvent({ button: 0, ctrlKey: true, metaKey: false })).toBe(true)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('does not treat ctrl + left-click as a secondary click off macOS', () => {
|
|
19
|
+
tlenv.isDarwin = false
|
|
20
|
+
expect(isSecondaryClickEvent({ button: 0, ctrlKey: true, metaKey: false })).toBe(false)
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
describe('isDirectDisplayPen', () => {
|
|
25
|
+
const originalIsTouchDevice = tlenv.isTouchDevice
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
tlenv.isTouchDevice = originalIsTouchDevice
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
function event(pointerType: string) {
|
|
32
|
+
return { pointerType, pointerId: 1 } as unknown as PointerEvent
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
it('treats a pen on a touch-capable device as a direct-display pen', () => {
|
|
36
|
+
tlenv.isTouchDevice = true
|
|
37
|
+
expect(isDirectDisplayPen(event('pen'))).toBe(true)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('treats a pen on a non-touch device as indirect', () => {
|
|
41
|
+
tlenv.isTouchDevice = false
|
|
42
|
+
expect(isDirectDisplayPen(event('pen'))).toBe(false)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('is never true for mouse or touch input, even on a touch-capable device', () => {
|
|
46
|
+
tlenv.isTouchDevice = true
|
|
47
|
+
expect(isDirectDisplayPen(event('mouse'))).toBe(false)
|
|
48
|
+
expect(isDirectDisplayPen(event('touch'))).toBe(false)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe('ctrl + left-click on macOS fires as a right-click (regression)', () => {
|
|
53
|
+
// Regression test for https://github.com/tldraw/tldraw/issues/8217
|
|
54
|
+
// On macOS, a ctrl + left-click should be treated as a right-click
|
|
55
|
+
let editor: TestEditor
|
|
56
|
+
|
|
57
|
+
beforeEach(() => {
|
|
58
|
+
editor = new TestEditor()
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
afterEach(() => {
|
|
62
|
+
editor.dispose()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('reports button 2 in the dispatched pointer info on macOS', () => {
|
|
66
|
+
tlenv.isDarwin = true
|
|
67
|
+
const event = new PointerEvent('pointerdown', { button: 0, ctrlKey: true })
|
|
68
|
+
expect(getPointerInfo(editor, event).button).toBe(2)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('stays button 0 for the same gesture off macOS', () => {
|
|
72
|
+
tlenv.isDarwin = false
|
|
73
|
+
const event = new PointerEvent('pointerdown', { button: 0, ctrlKey: true })
|
|
74
|
+
expect(getPointerInfo(editor, event).button).toBe(0)
|
|
75
|
+
})
|
|
76
|
+
})
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type React from 'react'
|
|
2
|
+
import { tlenv } from '../globals/environment'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Decide whether a pen pointer event looks like direct manipulation on the display (e.g. Apple
|
|
6
|
+
* Pencil on an iPad or a Surface Pen on a touchscreen) rather than indirect input from a desktop
|
|
7
|
+
* graphics tablet (e.g. a Wacom Intuos).
|
|
8
|
+
*
|
|
9
|
+
* We can't tell the two apart from the pointer event itself: both report `pointerType: 'pen'`, and
|
|
10
|
+
* implicit pointer capture — which in theory distinguishes direct-manipulation pointers — isn't
|
|
11
|
+
* reliably observable across browsers (notably WebKit/iPad). Instead we key off the device: a
|
|
12
|
+
* direct-display pen draws on a touch-capable screen, while an indirect graphics tablet is used on
|
|
13
|
+
* a non-touch desktop alongside a mouse. A device with no touch input therefore can't host a
|
|
14
|
+
* direct-display pen.
|
|
15
|
+
*
|
|
16
|
+
* Note this uses {@link tlenv.isTouchDevice} — the device's fixed touch capability — not the
|
|
17
|
+
* editor's dynamic `isCoarsePointer` state, which a pen `pointerdown` flips to coarse regardless
|
|
18
|
+
* of device.
|
|
19
|
+
*
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export function isDirectDisplayPen(e: React.PointerEvent | PointerEvent): boolean {
|
|
23
|
+
if (e.pointerType !== 'pen') return false
|
|
24
|
+
return tlenv.isTouchDevice
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** @internal */
|
|
28
|
+
interface PointerLike {
|
|
29
|
+
button: number
|
|
30
|
+
ctrlKey: boolean
|
|
31
|
+
metaKey: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** @internal */
|
|
35
|
+
export function isSecondaryClickEvent(e: PointerLike) {
|
|
36
|
+
return e.button === 2 || (tlenv.isDarwin && e.button === 0 && e.ctrlKey && !e.metaKey)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** @internal */
|
|
40
|
+
export function getPointerEventButton(e: PointerLike) {
|
|
41
|
+
return isSecondaryClickEvent(e) ? 2 : e.button
|
|
42
|
+
}
|
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 = '5.2.0
|
|
4
|
+
export const version = '5.2.0'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2026-05-06T16:28:18.473Z',
|
|
7
|
-
minor: '2026-
|
|
8
|
-
patch: '2026-
|
|
7
|
+
minor: '2026-07-01T09:20:01.735Z',
|
|
8
|
+
patch: '2026-07-01T09:20:01.735Z',
|
|
9
9
|
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
var DefaultSelectionBackground_exports = {};
|
|
30
|
-
__export(DefaultSelectionBackground_exports, {
|
|
31
|
-
DefaultSelectionBackground: () => DefaultSelectionBackground
|
|
32
|
-
});
|
|
33
|
-
module.exports = __toCommonJS(DefaultSelectionBackground_exports);
|
|
34
|
-
var import_jsx_runtime = require("react/jsx-runtime");
|
|
35
|
-
var React = __toESM(require("react"), 1);
|
|
36
|
-
var import_useTransform = require("../../hooks/useTransform");
|
|
37
|
-
var import_utils = require("../../primitives/utils");
|
|
38
|
-
function DefaultSelectionBackground({ bounds, rotation }) {
|
|
39
|
-
const rDiv = React.useRef(null);
|
|
40
|
-
(0, import_useTransform.useTransform)(rDiv, bounds.x, bounds.y, 1, rotation);
|
|
41
|
-
React.useLayoutEffect(() => {
|
|
42
|
-
const div = rDiv.current;
|
|
43
|
-
if (!div) return;
|
|
44
|
-
div.style.width = (0, import_utils.toDomPrecision)(Math.max(1, bounds.width)) + "px";
|
|
45
|
-
div.style.height = (0, import_utils.toDomPrecision)(Math.max(1, bounds.height)) + "px";
|
|
46
|
-
}, [bounds.width, bounds.height]);
|
|
47
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: rDiv, className: "tl-selection__bg", draggable: false });
|
|
48
|
-
}
|
|
49
|
-
//# sourceMappingURL=DefaultSelectionBackground.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../../src/lib/components/default-components/DefaultSelectionBackground.tsx"],
|
|
4
|
-
"sourcesContent": ["import * as React from 'react'\nimport { useTransform } from '../../hooks/useTransform'\nimport { Box } from '../../primitives/Box'\nimport { toDomPrecision } from '../../primitives/utils'\n\n/** @public */\nexport interface TLSelectionBackgroundProps {\n\tbounds: Box\n\trotation: number\n}\n\n/** @public @react */\nexport function DefaultSelectionBackground({ bounds, rotation }: TLSelectionBackgroundProps) {\n\tconst rDiv = React.useRef<HTMLDivElement>(null)\n\tuseTransform(rDiv, bounds.x, bounds.y, 1, rotation)\n\n\tReact.useLayoutEffect(() => {\n\t\tconst div = rDiv.current\n\t\tif (!div) return\n\t\tdiv.style.width = toDomPrecision(Math.max(1, bounds.width)) + 'px'\n\t\tdiv.style.height = toDomPrecision(Math.max(1, bounds.height)) + 'px'\n\t}, [bounds.width, bounds.height])\n\n\treturn <div ref={rDiv} className=\"tl-selection__bg\" draggable={false} />\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAuBQ;AAvBR,YAAuB;AACvB,0BAA6B;AAE7B,mBAA+B;AASxB,SAAS,2BAA2B,EAAE,QAAQ,SAAS,GAA+B;AAC5F,QAAM,OAAO,MAAM,OAAuB,IAAI;AAC9C,wCAAa,MAAM,OAAO,GAAG,OAAO,GAAG,GAAG,QAAQ;AAElD,QAAM,gBAAgB,MAAM;AAC3B,UAAM,MAAM,KAAK;AACjB,QAAI,CAAC,IAAK;AACV,QAAI,MAAM,YAAQ,6BAAe,KAAK,IAAI,GAAG,OAAO,KAAK,CAAC,IAAI;AAC9D,QAAI,MAAM,aAAS,6BAAe,KAAK,IAAI,GAAG,OAAO,MAAM,CAAC,IAAI;AAAA,EACjE,GAAG,CAAC,OAAO,OAAO,OAAO,MAAM,CAAC;AAEhC,SAAO,4CAAC,SAAI,KAAK,MAAM,WAAU,oBAAmB,WAAW,OAAO;AACvE;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
var ShapeIndicatorOverlayUtil_exports = {};
|
|
20
|
-
__export(ShapeIndicatorOverlayUtil_exports, {
|
|
21
|
-
ShapeIndicatorOverlayUtil: () => ShapeIndicatorOverlayUtil,
|
|
22
|
-
strokeShapeIndicators: () => strokeShapeIndicators
|
|
23
|
-
});
|
|
24
|
-
module.exports = __toCommonJS(ShapeIndicatorOverlayUtil_exports);
|
|
25
|
-
var import_state = require("@tldraw/state");
|
|
26
|
-
var import_store = require("@tldraw/store");
|
|
27
|
-
var import_OverlayUtil = require("./OverlayUtil");
|
|
28
|
-
const indicatorPathCache = (0, import_store.createComputedCache)(
|
|
29
|
-
"shapeIndicatorPath",
|
|
30
|
-
(editor, shape) => {
|
|
31
|
-
const util = editor.getShapeUtil(shape);
|
|
32
|
-
return util.getIndicatorPath(shape);
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
areRecordsEqual(a, b) {
|
|
36
|
-
return a.props === b.props;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
);
|
|
40
|
-
function strokeShapeIndicators(editor, ctx, shapeIds) {
|
|
41
|
-
if (shapeIds.length === 0) return;
|
|
42
|
-
const batched = new Path2D();
|
|
43
|
-
for (const shapeId of shapeIds) {
|
|
44
|
-
const shape = editor.getShape(shapeId);
|
|
45
|
-
if (!shape || shape.isLocked) continue;
|
|
46
|
-
const pageTransform = editor.getShapePageTransform(shape);
|
|
47
|
-
if (!pageTransform) continue;
|
|
48
|
-
const indicatorPath = indicatorPathCache.get(editor, shape.id);
|
|
49
|
-
if (!indicatorPath) continue;
|
|
50
|
-
if (indicatorPath instanceof Path2D) {
|
|
51
|
-
batched.addPath(indicatorPath, pageTransform);
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
const { path, clipPath, additionalPaths } = indicatorPath;
|
|
55
|
-
if (!clipPath) {
|
|
56
|
-
batched.addPath(path, pageTransform);
|
|
57
|
-
if (additionalPaths) {
|
|
58
|
-
for (const p of additionalPaths) batched.addPath(p, pageTransform);
|
|
59
|
-
}
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
ctx.save();
|
|
63
|
-
ctx.transform(
|
|
64
|
-
pageTransform.a,
|
|
65
|
-
pageTransform.b,
|
|
66
|
-
pageTransform.c,
|
|
67
|
-
pageTransform.d,
|
|
68
|
-
pageTransform.e,
|
|
69
|
-
pageTransform.f
|
|
70
|
-
);
|
|
71
|
-
ctx.save();
|
|
72
|
-
ctx.clip(clipPath, "evenodd");
|
|
73
|
-
ctx.stroke(path);
|
|
74
|
-
ctx.restore();
|
|
75
|
-
if (additionalPaths) {
|
|
76
|
-
for (const p of additionalPaths) ctx.stroke(p);
|
|
77
|
-
}
|
|
78
|
-
ctx.restore();
|
|
79
|
-
}
|
|
80
|
-
ctx.stroke(batched);
|
|
81
|
-
}
|
|
82
|
-
class ShapeIndicatorOverlayUtil extends import_OverlayUtil.OverlayUtil {
|
|
83
|
-
static type = "shape_indicator";
|
|
84
|
-
options = { zIndex: 50, lineWidth: 1.5, hintedLineWidth: 2.5 };
|
|
85
|
-
// Narrow projection of instance state. Reading the full record would
|
|
86
|
-
// re-fire getOverlays on every cursor move / brush update; gating on these
|
|
87
|
-
// three booleans means we only re-fire when one of them actually flips.
|
|
88
|
-
_instanceFlags$ = (0, import_state.computed)(
|
|
89
|
-
"shape indicator instance flags",
|
|
90
|
-
() => {
|
|
91
|
-
const i = this.editor.getInstanceState();
|
|
92
|
-
return {
|
|
93
|
-
isChangingStyle: i.isChangingStyle,
|
|
94
|
-
isHoveringCanvas: i.isHoveringCanvas,
|
|
95
|
-
isCoarsePointer: i.isCoarsePointer
|
|
96
|
-
};
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
isEqual: (a, b) => a.isChangingStyle === b.isChangingStyle && a.isHoveringCanvas === b.isHoveringCanvas && a.isCoarsePointer === b.isCoarsePointer
|
|
100
|
-
}
|
|
101
|
-
);
|
|
102
|
-
isActive() {
|
|
103
|
-
return true;
|
|
104
|
-
}
|
|
105
|
-
getOverlays() {
|
|
106
|
-
const editor = this.editor;
|
|
107
|
-
const renderingShapeIds = new Set(editor.getRenderingShapes().map((s) => s.id));
|
|
108
|
-
const idsToDisplay = [];
|
|
109
|
-
const { isChangingStyle, isHoveringCanvas, isCoarsePointer } = this._instanceFlags$.get();
|
|
110
|
-
const isIdleOrEditing = editor.isInAny("select.idle", "select.editing_shape");
|
|
111
|
-
const isInSelectState = editor.isInAny(
|
|
112
|
-
"select.brushing",
|
|
113
|
-
"select.scribble_brushing",
|
|
114
|
-
"select.pointing_shape",
|
|
115
|
-
"select.pointing_selection",
|
|
116
|
-
"select.pointing_handle"
|
|
117
|
-
);
|
|
118
|
-
if (!isChangingStyle && (isIdleOrEditing || isInSelectState)) {
|
|
119
|
-
for (const id of editor.getSelectedShapeIds()) {
|
|
120
|
-
if (renderingShapeIds.has(id)) idsToDisplay.push(id);
|
|
121
|
-
}
|
|
122
|
-
if (isIdleOrEditing && isHoveringCanvas && !isCoarsePointer) {
|
|
123
|
-
const hovered = editor.getHoveredShapeId();
|
|
124
|
-
if (hovered && renderingShapeIds.has(hovered) && !idsToDisplay.includes(hovered)) {
|
|
125
|
-
idsToDisplay.push(hovered);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
const hintingShapeIds = [];
|
|
130
|
-
for (const id of editor.getHintingShapeIds()) {
|
|
131
|
-
if (renderingShapeIds.has(id)) hintingShapeIds.push(id);
|
|
132
|
-
}
|
|
133
|
-
if (idsToDisplay.length === 0 && hintingShapeIds.length === 0) {
|
|
134
|
-
return [];
|
|
135
|
-
}
|
|
136
|
-
return [
|
|
137
|
-
{
|
|
138
|
-
id: "shape_indicator",
|
|
139
|
-
type: "shape_indicator",
|
|
140
|
-
props: { idsToDisplay, hintingShapeIds }
|
|
141
|
-
}
|
|
142
|
-
];
|
|
143
|
-
}
|
|
144
|
-
render(ctx, overlays) {
|
|
145
|
-
const overlay = overlays[0];
|
|
146
|
-
if (!overlay) return;
|
|
147
|
-
const editor = this.editor;
|
|
148
|
-
const zoom = editor.getZoomLevel();
|
|
149
|
-
const { idsToDisplay, hintingShapeIds } = overlay.props;
|
|
150
|
-
ctx.lineCap = "round";
|
|
151
|
-
ctx.lineJoin = "round";
|
|
152
|
-
ctx.strokeStyle = editor.getCurrentTheme().colors[editor.getColorMode()].selectionStroke;
|
|
153
|
-
ctx.lineWidth = this.options.lineWidth / zoom;
|
|
154
|
-
strokeShapeIndicators(editor, ctx, idsToDisplay);
|
|
155
|
-
if (hintingShapeIds.length > 0) {
|
|
156
|
-
ctx.lineWidth = this.options.hintedLineWidth / zoom;
|
|
157
|
-
strokeShapeIndicators(editor, ctx, hintingShapeIds);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
//# sourceMappingURL=ShapeIndicatorOverlayUtil.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../../src/lib/editor/overlays/ShapeIndicatorOverlayUtil.ts"],
|
|
4
|
-
"sourcesContent": ["import { computed } from '@tldraw/state'\nimport { createComputedCache } from '@tldraw/store'\nimport { TLShape, TLShapeId } from '@tldraw/tlschema'\nimport type { Editor } from '../Editor'\nimport { OverlayUtil, TLOverlay } from './OverlayUtil'\n\ninterface RelevantInstanceFlags {\n\tisChangingStyle: boolean\n\tisHoveringCanvas: boolean | null\n\tisCoarsePointer: boolean\n}\n\n/** @public */\nexport interface TLShapeIndicatorOverlay extends TLOverlay {\n\tprops: {\n\t\tidsToDisplay: TLShapeId[]\n\t\thintingShapeIds: TLShapeId[]\n\t}\n}\n\nconst indicatorPathCache = createComputedCache(\n\t'shapeIndicatorPath',\n\t(editor: Editor, shape: TLShape) => {\n\t\tconst util = editor.getShapeUtil(shape)\n\t\treturn util.getIndicatorPath(shape)\n\t},\n\t{\n\t\tareRecordsEqual(a, b) {\n\t\t\treturn a.props === b.props\n\t\t},\n\t}\n)\n\n/**\n * Combine every batchable shape indicator into a single page-space `Path2D` and\n * emit one stroke call. Shapes whose indicator needs an evenodd clip (e.g.\n * arrows with labels or complex arrowheads) can't be batched \u2014 they still\n * stroke individually inside a save/restore with `ctx.clip` applied.\n *\n * Shared by {@link ShapeIndicatorOverlayUtil} and any overlay util that paints\n * shape indicators (e.g. collaborator selections).\n *\n * @public\n */\nexport function strokeShapeIndicators(\n\teditor: Editor,\n\tctx: CanvasRenderingContext2D,\n\tshapeIds: TLShapeId[]\n): void {\n\tif (shapeIds.length === 0) return\n\n\tconst batched = new Path2D()\n\n\tfor (const shapeId of shapeIds) {\n\t\tconst shape = editor.getShape(shapeId)\n\t\tif (!shape || shape.isLocked) continue\n\n\t\tconst pageTransform = editor.getShapePageTransform(shape)\n\t\tif (!pageTransform) continue\n\n\t\tconst indicatorPath = indicatorPathCache.get(editor, shape.id)\n\t\tif (!indicatorPath) continue\n\n\t\tif (indicatorPath instanceof Path2D) {\n\t\t\tbatched.addPath(indicatorPath, pageTransform)\n\t\t\tcontinue\n\t\t}\n\n\t\tconst { path, clipPath, additionalPaths } = indicatorPath\n\n\t\tif (!clipPath) {\n\t\t\tbatched.addPath(path, pageTransform)\n\t\t\tif (additionalPaths) {\n\t\t\t\tfor (const p of additionalPaths) batched.addPath(p, pageTransform)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// Clipped case: fall back to an individual stroke. Rare (arrows with\n\t\t// labels / complex arrowheads), so the extra save/restore/stroke\n\t\t// pair per such shape isn't worth batching away.\n\t\tctx.save()\n\t\tctx.transform(\n\t\t\tpageTransform.a,\n\t\t\tpageTransform.b,\n\t\t\tpageTransform.c,\n\t\t\tpageTransform.d,\n\t\t\tpageTransform.e,\n\t\t\tpageTransform.f\n\t\t)\n\t\tctx.save()\n\t\tctx.clip(clipPath, 'evenodd')\n\t\tctx.stroke(path)\n\t\tctx.restore()\n\t\tif (additionalPaths) {\n\t\t\tfor (const p of additionalPaths) ctx.stroke(p)\n\t\t}\n\t\tctx.restore()\n\t}\n\n\tctx.stroke(batched)\n}\n\n/**\n * Overlay util for shape indicators \u2014 the selection / hover / hint outlines drawn\n * under the selection foreground. Paints local indicators in the theme's\n * selection color.\n *\n * Remote collaborator selection indicators are drawn by a separate overlay util\n * (e.g. `CollaboratorShapeIndicatorOverlayUtil` from `tldraw`) that runs at a\n * lower z-index so peer selections appear under the local indicators.\n *\n * Non-interactive: contributes no hit-test geometry.\n *\n * @public\n */\nexport class ShapeIndicatorOverlayUtil extends OverlayUtil<TLShapeIndicatorOverlay> {\n\tstatic override type = 'shape_indicator'\n\toverride options = { zIndex: 50, lineWidth: 1.5, hintedLineWidth: 2.5 }\n\n\t// Narrow projection of instance state. Reading the full record would\n\t// re-fire getOverlays on every cursor move / brush update; gating on these\n\t// three booleans means we only re-fire when one of them actually flips.\n\tprivate _instanceFlags$ = computed<RelevantInstanceFlags>(\n\t\t'shape indicator instance flags',\n\t\t() => {\n\t\t\tconst i = this.editor.getInstanceState()\n\t\t\treturn {\n\t\t\t\tisChangingStyle: i.isChangingStyle,\n\t\t\t\tisHoveringCanvas: i.isHoveringCanvas,\n\t\t\t\tisCoarsePointer: i.isCoarsePointer,\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\tisEqual: (a, b) =>\n\t\t\t\ta.isChangingStyle === b.isChangingStyle &&\n\t\t\t\ta.isHoveringCanvas === b.isHoveringCanvas &&\n\t\t\t\ta.isCoarsePointer === b.isCoarsePointer,\n\t\t}\n\t)\n\n\toverride isActive(): boolean {\n\t\treturn true\n\t}\n\n\toverride getOverlays(): TLShapeIndicatorOverlay[] {\n\t\tconst editor = this.editor\n\t\tconst renderingShapeIds = new Set(editor.getRenderingShapes().map((s) => s.id))\n\n\t\t// Local selected / hovered indicators.\n\t\tconst idsToDisplay: TLShapeId[] = []\n\t\tconst { isChangingStyle, isHoveringCanvas, isCoarsePointer } = this._instanceFlags$.get()\n\t\tconst isIdleOrEditing = editor.isInAny('select.idle', 'select.editing_shape')\n\t\tconst isInSelectState = editor.isInAny(\n\t\t\t'select.brushing',\n\t\t\t'select.scribble_brushing',\n\t\t\t'select.pointing_shape',\n\t\t\t'select.pointing_selection',\n\t\t\t'select.pointing_handle'\n\t\t)\n\n\t\tif (!isChangingStyle && (isIdleOrEditing || isInSelectState)) {\n\t\t\tfor (const id of editor.getSelectedShapeIds()) {\n\t\t\t\tif (renderingShapeIds.has(id)) idsToDisplay.push(id)\n\t\t\t}\n\t\t\tif (isIdleOrEditing && isHoveringCanvas && !isCoarsePointer) {\n\t\t\t\tconst hovered = editor.getHoveredShapeId()\n\t\t\t\tif (hovered && renderingShapeIds.has(hovered) && !idsToDisplay.includes(hovered)) {\n\t\t\t\t\tidsToDisplay.push(hovered)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Hinted shapes (drawn thicker). Already deduped at write time in\n\t\t// `updateHintingShapeIds`, so no need to dedupe again here.\n\t\tconst hintingShapeIds: TLShapeId[] = []\n\t\tfor (const id of editor.getHintingShapeIds()) {\n\t\t\tif (renderingShapeIds.has(id)) hintingShapeIds.push(id)\n\t\t}\n\n\t\tif (idsToDisplay.length === 0 && hintingShapeIds.length === 0) {\n\t\t\treturn []\n\t\t}\n\n\t\treturn [\n\t\t\t{\n\t\t\t\tid: 'shape_indicator',\n\t\t\t\ttype: 'shape_indicator',\n\t\t\t\tprops: { idsToDisplay, hintingShapeIds },\n\t\t\t},\n\t\t]\n\t}\n\n\toverride render(ctx: CanvasRenderingContext2D, overlays: TLShapeIndicatorOverlay[]): void {\n\t\tconst overlay = overlays[0]\n\t\tif (!overlay) return\n\n\t\tconst editor = this.editor\n\t\tconst zoom = editor.getZoomLevel()\n\t\tconst { idsToDisplay, hintingShapeIds } = overlay.props\n\n\t\tctx.lineCap = 'round'\n\t\tctx.lineJoin = 'round'\n\n\t\t// Local selected / hovered indicators \u2014 one stroke call for the whole batch.\n\t\tctx.strokeStyle = editor.getCurrentTheme().colors[editor.getColorMode()].selectionStroke\n\t\tctx.lineWidth = this.options.lineWidth / zoom\n\t\tstrokeShapeIndicators(editor, ctx, idsToDisplay)\n\n\t\t// Hinted shapes \u2014 thicker stroke, one call for the whole batch.\n\t\tif (hintingShapeIds.length > 0) {\n\t\t\tctx.lineWidth = this.options.hintedLineWidth / zoom\n\t\t\tstrokeShapeIndicators(editor, ctx, hintingShapeIds)\n\t\t}\n\t}\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/lib/hooks/useTransform.ts"],
|
|
4
|
-
"sourcesContent": ["import { useLayoutEffect } from 'react'\nimport { VecLike } from '../primitives/Vec'\n\n/** @public */\nexport function useTransform(\n\tref: React.RefObject<HTMLElement | SVGElement | null>,\n\tx?: number,\n\ty?: number,\n\tscale?: number,\n\trotate?: number,\n\tadditionalOffset?: VecLike\n) {\n\tuseLayoutEffect(() => {\n\t\tconst elm = ref.current\n\t\tif (!elm) return\n\t\tif (x === undefined) return\n\n\t\tlet trans = `translate(${x}px, ${y}px)`\n\t\tif (scale !== undefined) {\n\t\t\ttrans += ` scale(${scale})`\n\t\t}\n\t\tif (rotate !== undefined) {\n\t\t\ttrans += ` rotate(${rotate}rad)`\n\t\t}\n\t\tif (additionalOffset) {\n\t\t\ttrans += ` translate(${additionalOffset.x}px, ${additionalOffset.y}px)`\n\t\t}\n\t\telm.style.transform = trans\n\t})\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAgC;AAIzB,SAAS,aACf,KACA,GACA,GACA,OACA,QACA,kBACC;AACD,oCAAgB,MAAM;AACrB,UAAM,MAAM,IAAI;AAChB,QAAI,CAAC,IAAK;AACV,QAAI,MAAM,OAAW;AAErB,QAAI,QAAQ,aAAa,CAAC,OAAO,CAAC;AAClC,QAAI,UAAU,QAAW;AACxB,eAAS,UAAU,KAAK;AAAA,IACzB;AACA,QAAI,WAAW,QAAW;AACzB,eAAS,WAAW,MAAM;AAAA,IAC3B;AACA,QAAI,kBAAkB;AACrB,eAAS,cAAc,iBAAiB,CAAC,OAAO,iBAAiB,CAAC;AAAA,IACnE;AACA,QAAI,MAAM,YAAY;AAAA,EACvB,CAAC;AACF;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
import { useTransform } from "../../hooks/useTransform.mjs";
|
|
4
|
-
import { toDomPrecision } from "../../primitives/utils.mjs";
|
|
5
|
-
function DefaultSelectionBackground({ bounds, rotation }) {
|
|
6
|
-
const rDiv = React.useRef(null);
|
|
7
|
-
useTransform(rDiv, bounds.x, bounds.y, 1, rotation);
|
|
8
|
-
React.useLayoutEffect(() => {
|
|
9
|
-
const div = rDiv.current;
|
|
10
|
-
if (!div) return;
|
|
11
|
-
div.style.width = toDomPrecision(Math.max(1, bounds.width)) + "px";
|
|
12
|
-
div.style.height = toDomPrecision(Math.max(1, bounds.height)) + "px";
|
|
13
|
-
}, [bounds.width, bounds.height]);
|
|
14
|
-
return /* @__PURE__ */ jsx("div", { ref: rDiv, className: "tl-selection__bg", draggable: false });
|
|
15
|
-
}
|
|
16
|
-
export {
|
|
17
|
-
DefaultSelectionBackground
|
|
18
|
-
};
|
|
19
|
-
//# sourceMappingURL=DefaultSelectionBackground.mjs.map
|