tldraw 3.16.0-canary.29e7605fd21b → 3.16.0-canary.2b37b2b68fe4
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 +73 -0
- package/dist-cjs/index.js +5 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js +4 -4
- package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js.map +2 -2
- package/dist-cjs/lib/shapes/shared/ShapeFill.js +1 -1
- package/dist-cjs/lib/shapes/shared/ShapeFill.js.map +2 -2
- package/dist-cjs/lib/shapes/shared/freehand/svg.js.map +2 -2
- package/dist-cjs/lib/tools/EraserTool/childStates/Erasing.js +25 -1
- package/dist-cjs/lib/tools/EraserTool/childStates/Erasing.js.map +2 -2
- package/dist-cjs/lib/tools/EraserTool/childStates/Pointing.js +12 -0
- package/dist-cjs/lib/tools/EraserTool/childStates/Pointing.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +25 -10
- package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js +3 -1
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js.map +2 -2
- package/dist-cjs/lib/ui/hooks/useTools.js +21 -3
- package/dist-cjs/lib/ui/hooks/useTools.js.map +2 -2
- package/dist-cjs/lib/ui/version.js +3 -3
- package/dist-cjs/lib/ui/version.js.map +1 -1
- package/dist-esm/index.d.mts +73 -0
- package/dist-esm/index.mjs +5 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs +4 -4
- package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/shapes/shared/ShapeFill.mjs +1 -1
- package/dist-esm/lib/shapes/shared/ShapeFill.mjs.map +2 -2
- package/dist-esm/lib/shapes/shared/freehand/svg.mjs.map +2 -2
- package/dist-esm/lib/tools/EraserTool/childStates/Erasing.mjs +26 -1
- package/dist-esm/lib/tools/EraserTool/childStates/Erasing.mjs.map +2 -2
- package/dist-esm/lib/tools/EraserTool/childStates/Pointing.mjs +13 -0
- package/dist-esm/lib/tools/EraserTool/childStates/Pointing.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs +25 -10
- package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +3 -1
- package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs.map +2 -2
- package/dist-esm/lib/ui/hooks/useTools.mjs +22 -3
- package/dist-esm/lib/ui/hooks/useTools.mjs.map +2 -2
- package/dist-esm/lib/ui/version.mjs +3 -3
- package/dist-esm/lib/ui/version.mjs.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +3 -0
- package/src/lib/shapes/frame/FrameShapeUtil.tsx +12 -4
- package/src/lib/shapes/shared/ShapeFill.tsx +1 -1
- package/src/lib/shapes/shared/freehand/svg.ts +2 -0
- package/src/lib/tools/EraserTool/childStates/Erasing.ts +34 -1
- package/src/lib/tools/EraserTool/childStates/Pointing.ts +20 -0
- package/src/lib/ui/components/primitives/TldrawUiTooltip.tsx +36 -10
- package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +5 -2
- package/src/lib/ui/hooks/useTools.tsx +25 -3
- package/src/lib/ui/version.ts +3 -3
- package/src/lib/ui.css +4 -0
- package/src/test/EraserTool.test.ts +176 -6
- package/tldraw.css +4 -0
|
@@ -33,7 +33,7 @@ export const ShapeFill = React.memo(function ShapeFill({
|
|
|
33
33
|
return <path fill={getColorValue(theme, color, 'semi')} d={d} />
|
|
34
34
|
}
|
|
35
35
|
case 'semi': {
|
|
36
|
-
return <path fill={
|
|
36
|
+
return <path fill={theme.solid} d={d} />
|
|
37
37
|
}
|
|
38
38
|
case 'fill': {
|
|
39
39
|
return <path fill={getColorValue(theme, color, 'fill')} d={d} />
|
|
@@ -6,6 +6,8 @@ import { StrokePoint } from './types'
|
|
|
6
6
|
*
|
|
7
7
|
* @param points - The stroke points returned from perfect-freehand
|
|
8
8
|
* @param closed - Whether the shape is closed
|
|
9
|
+
*
|
|
10
|
+
* @public
|
|
9
11
|
*/
|
|
10
12
|
export function getSvgPathFromStrokePoints(points: StrokePoint[], closed = false): string {
|
|
11
13
|
const len = points.length
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
TLGroupShape,
|
|
5
5
|
TLPointerEventInfo,
|
|
6
6
|
TLShapeId,
|
|
7
|
+
isAccelKey,
|
|
7
8
|
pointInPolygon,
|
|
8
9
|
} from '@tldraw/editor'
|
|
9
10
|
|
|
@@ -15,7 +16,15 @@ export class Erasing extends StateNode {
|
|
|
15
16
|
private markId = ''
|
|
16
17
|
private excludedShapeIds = new Set<TLShapeId>()
|
|
17
18
|
|
|
19
|
+
_isHoldingAccelKey = false
|
|
20
|
+
_firstErasingShapeId: TLShapeId | null = null
|
|
21
|
+
_erasingShapeIds: TLShapeId[] = []
|
|
22
|
+
|
|
18
23
|
override onEnter(info: TLPointerEventInfo) {
|
|
24
|
+
this._isHoldingAccelKey = isAccelKey(this.editor.inputs)
|
|
25
|
+
this._firstErasingShapeId = this.editor.getErasingShapeIds()[0] // the first one should be the first one we hit... is it?
|
|
26
|
+
this._erasingShapeIds = this.editor.getErasingShapeIds()
|
|
27
|
+
|
|
19
28
|
this.markId = this.editor.markHistoryStoppingPoint('erase scribble begin')
|
|
20
29
|
this.info = info
|
|
21
30
|
|
|
@@ -76,6 +85,16 @@ export class Erasing extends StateNode {
|
|
|
76
85
|
this.complete()
|
|
77
86
|
}
|
|
78
87
|
|
|
88
|
+
override onKeyUp() {
|
|
89
|
+
this._isHoldingAccelKey = isAccelKey(this.editor.inputs)
|
|
90
|
+
this.update()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
override onKeyDown() {
|
|
94
|
+
this._isHoldingAccelKey = isAccelKey(this.editor.inputs)
|
|
95
|
+
this.update()
|
|
96
|
+
}
|
|
97
|
+
|
|
79
98
|
update() {
|
|
80
99
|
const { editor, excludedShapeIds } = this
|
|
81
100
|
const erasingShapeIds = editor.getErasingShapeIds()
|
|
@@ -87,6 +106,7 @@ export class Erasing extends StateNode {
|
|
|
87
106
|
|
|
88
107
|
this.pushPointToScribble()
|
|
89
108
|
|
|
109
|
+
// Otherwise, erasing shapes are all the shapes that were hit before plus any new shapes that are hit
|
|
90
110
|
const erasing = new Set<TLShapeId>(erasingShapeIds)
|
|
91
111
|
const minDist = this.editor.options.hitTestMargin / zoomLevel
|
|
92
112
|
|
|
@@ -121,18 +141,31 @@ export class Erasing extends StateNode {
|
|
|
121
141
|
if (geometry.hitTestLineSegment(A, B, minDist)) {
|
|
122
142
|
erasing.add(editor.getOutermostSelectableShape(shape).id)
|
|
123
143
|
}
|
|
144
|
+
|
|
145
|
+
this._erasingShapeIds = [...erasing]
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// If the user is holding the meta / ctrl key, we should only erase the first shape we hit
|
|
149
|
+
if (this._isHoldingAccelKey && this._firstErasingShapeId) {
|
|
150
|
+
const erasingShapeId = this._firstErasingShapeId
|
|
151
|
+
if (erasingShapeId && this.editor.getShape(erasingShapeId)) {
|
|
152
|
+
editor.setErasingShapes([erasingShapeId])
|
|
153
|
+
}
|
|
154
|
+
return
|
|
124
155
|
}
|
|
125
156
|
|
|
126
157
|
// Remove the hit shapes, except if they're in the list of excluded shapes
|
|
127
158
|
// (these excluded shapes will be any frames or groups the pointer was inside of
|
|
128
159
|
// when the user started erasing)
|
|
129
|
-
this.editor.setErasingShapes(
|
|
160
|
+
this.editor.setErasingShapes(this._erasingShapeIds.filter((id) => !excludedShapeIds.has(id)))
|
|
130
161
|
}
|
|
131
162
|
|
|
132
163
|
complete() {
|
|
133
164
|
const { editor } = this
|
|
134
165
|
editor.deleteShapes(editor.getCurrentPageState().erasingShapeIds)
|
|
135
166
|
this.parent.transition('idle')
|
|
167
|
+
this._erasingShapeIds = []
|
|
168
|
+
this._firstErasingShapeId = null
|
|
136
169
|
}
|
|
137
170
|
|
|
138
171
|
cancel() {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
isAccelKey,
|
|
2
3
|
StateNode,
|
|
3
4
|
TLFrameShape,
|
|
4
5
|
TLGroupShape,
|
|
@@ -9,7 +10,11 @@ import {
|
|
|
9
10
|
export class Pointing extends StateNode {
|
|
10
11
|
static override id = 'pointing'
|
|
11
12
|
|
|
13
|
+
_isHoldingAccelKey = false
|
|
14
|
+
|
|
12
15
|
override onEnter() {
|
|
16
|
+
this._isHoldingAccelKey = isAccelKey(this.editor.inputs)
|
|
17
|
+
|
|
13
18
|
const zoomLevel = this.editor.getZoomLevel()
|
|
14
19
|
const currentPageShapesSorted = this.editor.getCurrentPageRenderingShapesSorted()
|
|
15
20
|
const {
|
|
@@ -45,12 +50,25 @@ export class Pointing extends StateNode {
|
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
erasing.add(hitShape.id)
|
|
53
|
+
|
|
54
|
+
// If the user is holding the meta / ctrl key, stop after the first shape
|
|
55
|
+
if (this._isHoldingAccelKey) {
|
|
56
|
+
break
|
|
57
|
+
}
|
|
48
58
|
}
|
|
49
59
|
}
|
|
50
60
|
|
|
51
61
|
this.editor.setErasingShapes([...erasing])
|
|
52
62
|
}
|
|
53
63
|
|
|
64
|
+
override onKeyUp() {
|
|
65
|
+
this._isHoldingAccelKey = isAccelKey(this.editor.inputs)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
override onKeyDown() {
|
|
69
|
+
this._isHoldingAccelKey = isAccelKey(this.editor.inputs)
|
|
70
|
+
}
|
|
71
|
+
|
|
54
72
|
override onLongPress(info: TLPointerEventInfo) {
|
|
55
73
|
this.startErasing(info)
|
|
56
74
|
}
|
|
@@ -62,6 +80,8 @@ export class Pointing extends StateNode {
|
|
|
62
80
|
}
|
|
63
81
|
|
|
64
82
|
override onPointerMove(info: TLPointerEventInfo) {
|
|
83
|
+
if (this._isHoldingAccelKey) return
|
|
84
|
+
|
|
65
85
|
if (this.editor.inputs.isDragging) {
|
|
66
86
|
this.startErasing(info)
|
|
67
87
|
}
|
|
@@ -20,6 +20,8 @@ export interface TldrawUiTooltipProps {
|
|
|
20
20
|
side?: 'top' | 'right' | 'bottom' | 'left'
|
|
21
21
|
sideOffset?: number
|
|
22
22
|
disabled?: boolean
|
|
23
|
+
showOnMobile?: boolean
|
|
24
|
+
delayDuration?: number
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
// Singleton tooltip manager
|
|
@@ -30,10 +32,11 @@ class TooltipManager {
|
|
|
30
32
|
content: ReactNode
|
|
31
33
|
side: 'top' | 'right' | 'bottom' | 'left'
|
|
32
34
|
sideOffset: number
|
|
35
|
+
showOnMobile: boolean
|
|
33
36
|
targetElement: HTMLElement
|
|
37
|
+
delayDuration: number | undefined
|
|
34
38
|
} | null>('current tooltip', null)
|
|
35
39
|
private destroyTimeoutId: number | null = null
|
|
36
|
-
private subscribers: Set<() => void> = new Set()
|
|
37
40
|
|
|
38
41
|
static getInstance(): TooltipManager {
|
|
39
42
|
if (!TooltipManager.instance) {
|
|
@@ -46,8 +49,10 @@ class TooltipManager {
|
|
|
46
49
|
tooltipId: string,
|
|
47
50
|
content: string | React.ReactNode,
|
|
48
51
|
targetElement: HTMLElement,
|
|
49
|
-
side: 'top' | 'right' | 'bottom' | 'left'
|
|
50
|
-
sideOffset: number
|
|
52
|
+
side: 'top' | 'right' | 'bottom' | 'left',
|
|
53
|
+
sideOffset: number,
|
|
54
|
+
showOnMobile: boolean,
|
|
55
|
+
delayDuration: number | undefined
|
|
51
56
|
) {
|
|
52
57
|
// Clear any existing destroy timeout
|
|
53
58
|
if (this.destroyTimeoutId) {
|
|
@@ -61,7 +66,9 @@ class TooltipManager {
|
|
|
61
66
|
content,
|
|
62
67
|
side,
|
|
63
68
|
sideOffset,
|
|
69
|
+
showOnMobile,
|
|
64
70
|
targetElement,
|
|
71
|
+
delayDuration,
|
|
65
72
|
})
|
|
66
73
|
}
|
|
67
74
|
|
|
@@ -88,8 +95,10 @@ class TooltipManager {
|
|
|
88
95
|
}
|
|
89
96
|
|
|
90
97
|
getCurrentTooltipData() {
|
|
91
|
-
|
|
92
|
-
|
|
98
|
+
const currentTooltip = this.currentTooltip.get()
|
|
99
|
+
if (!currentTooltip) return null
|
|
100
|
+
if (!this.supportsHover() && !currentTooltip.showOnMobile) return null
|
|
101
|
+
return currentTooltip
|
|
93
102
|
}
|
|
94
103
|
|
|
95
104
|
private supportsHoverAtom: Atom<boolean> | null = null
|
|
@@ -168,7 +177,7 @@ function TooltipSingleton() {
|
|
|
168
177
|
showTimeoutRef.current = editor.timers.setTimeout(() => {
|
|
169
178
|
setIsOpen(true)
|
|
170
179
|
isFirstShowRef.current = false
|
|
171
|
-
}, editor.options.tooltipDelayMs)
|
|
180
|
+
}, currentTooltip.delayDuration ?? editor.options.tooltipDelayMs)
|
|
172
181
|
} else {
|
|
173
182
|
// Subsequent tooltips show immediately
|
|
174
183
|
setIsOpen(true)
|
|
@@ -207,7 +216,18 @@ function TooltipSingleton() {
|
|
|
207
216
|
|
|
208
217
|
/** @public @react */
|
|
209
218
|
export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProps>(
|
|
210
|
-
(
|
|
219
|
+
(
|
|
220
|
+
{
|
|
221
|
+
children,
|
|
222
|
+
content,
|
|
223
|
+
side,
|
|
224
|
+
sideOffset = 5,
|
|
225
|
+
disabled = false,
|
|
226
|
+
showOnMobile = false,
|
|
227
|
+
delayDuration,
|
|
228
|
+
},
|
|
229
|
+
ref
|
|
230
|
+
) => {
|
|
211
231
|
const editor = useMaybeEditor()
|
|
212
232
|
const tooltipId = useRef<string>(uniqueId())
|
|
213
233
|
const hasProvider = useContext(TooltipSingletonContext)
|
|
@@ -233,7 +253,9 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
|
|
|
233
253
|
if (!hasProvider) {
|
|
234
254
|
return (
|
|
235
255
|
<_Tooltip.Root
|
|
236
|
-
delayDuration={
|
|
256
|
+
delayDuration={
|
|
257
|
+
delayDuration ?? (editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS)
|
|
258
|
+
}
|
|
237
259
|
disableHoverableContent
|
|
238
260
|
>
|
|
239
261
|
<_Tooltip.Trigger asChild ref={ref}>
|
|
@@ -264,7 +286,9 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
|
|
|
264
286
|
content,
|
|
265
287
|
event.currentTarget as HTMLElement,
|
|
266
288
|
sideToUse,
|
|
267
|
-
sideOffset
|
|
289
|
+
sideOffset,
|
|
290
|
+
showOnMobile,
|
|
291
|
+
delayDuration
|
|
268
292
|
)
|
|
269
293
|
}
|
|
270
294
|
|
|
@@ -280,7 +304,9 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
|
|
|
280
304
|
content,
|
|
281
305
|
event.currentTarget as HTMLElement,
|
|
282
306
|
sideToUse,
|
|
283
|
-
sideOffset
|
|
307
|
+
sideOffset,
|
|
308
|
+
showOnMobile,
|
|
309
|
+
delayDuration
|
|
284
310
|
)
|
|
285
311
|
}
|
|
286
312
|
|
|
@@ -316,8 +316,8 @@ function useDraggableEvents(
|
|
|
316
316
|
if (
|
|
317
317
|
distanceSq >
|
|
318
318
|
(editor.getInstanceState().isCoarsePointer
|
|
319
|
-
? editor.options.
|
|
320
|
-
: editor.options.
|
|
319
|
+
? editor.options.uiCoarseDragDistanceSquared
|
|
320
|
+
: editor.options.uiDragDistanceSquared)
|
|
321
321
|
) {
|
|
322
322
|
const screenSpaceStart = state.screenSpaceStart
|
|
323
323
|
state = {
|
|
@@ -326,6 +326,8 @@ function useDraggableEvents(
|
|
|
326
326
|
}
|
|
327
327
|
|
|
328
328
|
editor.run(() => {
|
|
329
|
+
editor.setCurrentTool('select')
|
|
330
|
+
|
|
329
331
|
// Set origin point
|
|
330
332
|
editor.dispatch({
|
|
331
333
|
type: 'pointer',
|
|
@@ -348,6 +350,7 @@ function useDraggableEvents(
|
|
|
348
350
|
})
|
|
349
351
|
|
|
350
352
|
tooltipManager.hideAllTooltips()
|
|
353
|
+
editor.getContainer().focus()
|
|
351
354
|
})
|
|
352
355
|
}
|
|
353
356
|
}
|
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
createShapeId,
|
|
4
4
|
Editor,
|
|
5
5
|
GeoShapeGeoStyle,
|
|
6
|
+
getIndicesBetween,
|
|
7
|
+
TLLineShape,
|
|
6
8
|
TLPointerEventInfo,
|
|
7
9
|
TLShapeId,
|
|
8
10
|
toRichText,
|
|
@@ -153,7 +155,8 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
153
155
|
},
|
|
154
156
|
onDragStart(source: TLUiEventSource, info: TLPointerEventInfo) {
|
|
155
157
|
onDragFromToolbarToCreateShape(editor, info, {
|
|
156
|
-
createShape: (id) =>
|
|
158
|
+
createShape: (id) =>
|
|
159
|
+
editor.createShape({ id, type: 'geo', props: { w: 200, h: 200, geo } }),
|
|
157
160
|
})
|
|
158
161
|
trackEvent('drag-tool', { source, id: 'geo' })
|
|
159
162
|
},
|
|
@@ -188,6 +191,24 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
188
191
|
editor.setCurrentTool('line')
|
|
189
192
|
onToolSelect(source, this)
|
|
190
193
|
},
|
|
194
|
+
onDragStart(source, info) {
|
|
195
|
+
onDragFromToolbarToCreateShape(editor, info, {
|
|
196
|
+
createShape: (id) => {
|
|
197
|
+
const [start, end] = getIndicesBetween(null, null, 2)
|
|
198
|
+
editor.createShape<TLLineShape>({
|
|
199
|
+
id,
|
|
200
|
+
type: 'line',
|
|
201
|
+
props: {
|
|
202
|
+
points: {
|
|
203
|
+
[start]: { id: start, index: start, x: 0, y: 200 },
|
|
204
|
+
[end]: { id: end, index: end, x: 200, y: 0 },
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
})
|
|
208
|
+
},
|
|
209
|
+
})
|
|
210
|
+
trackEvent('drag-tool', { source, id: 'line' })
|
|
211
|
+
},
|
|
191
212
|
},
|
|
192
213
|
{
|
|
193
214
|
id: 'frame',
|
|
@@ -219,8 +240,8 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
219
240
|
createShape: (id) =>
|
|
220
241
|
editor.createShape({ id, type: 'text', props: { richText: toRichText('Text') } }),
|
|
221
242
|
onDragEnd: (id) => {
|
|
222
|
-
editor.emit('select-all-text', { shapeId: id })
|
|
223
243
|
editor.setEditingShape(id)
|
|
244
|
+
editor.emit('select-all-text', { shapeId: id })
|
|
224
245
|
},
|
|
225
246
|
})
|
|
226
247
|
trackEvent('drag-tool', { source, id: 'text' })
|
|
@@ -249,8 +270,8 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
249
270
|
onDragFromToolbarToCreateShape(editor, info, {
|
|
250
271
|
createShape: (id) => editor.createShape({ id, type: 'note' }),
|
|
251
272
|
onDragEnd: (id) => {
|
|
252
|
-
editor.emit('select-all-text', { shapeId: id })
|
|
253
273
|
editor.setEditingShape(id)
|
|
274
|
+
editor.emit('select-all-text', { shapeId: id })
|
|
254
275
|
},
|
|
255
276
|
})
|
|
256
277
|
trackEvent('drag-tool', { source, id: 'note' })
|
|
@@ -365,5 +386,6 @@ export function onDragFromToolbarToCreateShape(
|
|
|
365
386
|
opts.onDragEnd?.(id)
|
|
366
387
|
},
|
|
367
388
|
})
|
|
389
|
+
|
|
368
390
|
editor.getCurrentTool().setCurrentToolIdMask(shape.type)
|
|
369
391
|
}
|
package/src/lib/ui/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.16.0-canary.
|
|
4
|
+
export const version = '3.16.0-canary.2b37b2b68fe4'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2024-09-13T14:36:29.063Z',
|
|
7
|
-
minor: '2025-08-
|
|
8
|
-
patch: '2025-08-
|
|
7
|
+
minor: '2025-08-15T10:08:45.024Z',
|
|
8
|
+
patch: '2025-08-15T10:08:45.024Z',
|
|
9
9
|
}
|
package/src/lib/ui.css
CHANGED
|
@@ -1269,6 +1269,10 @@
|
|
|
1269
1269
|
opacity: 1;
|
|
1270
1270
|
}
|
|
1271
1271
|
|
|
1272
|
+
.tlui-main-toolbar__overflow-content {
|
|
1273
|
+
touch-action: none;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1272
1276
|
.tlui-main-toolbar__tools [data-toolbar-visible='false'],
|
|
1273
1277
|
.tlui-main-toolbar__overflow-content [data-toolbar-visible='false'] {
|
|
1274
1278
|
display: none;
|
|
@@ -436,11 +436,181 @@ describe('When shift clicking', () => {
|
|
|
436
436
|
it.todo('Clears the previous clicked point when leaving / re-entering the eraser tool')
|
|
437
437
|
})
|
|
438
438
|
|
|
439
|
-
describe('When
|
|
440
|
-
it('
|
|
441
|
-
editor.setCurrentTool('
|
|
442
|
-
editor.expectToBeIn('
|
|
443
|
-
|
|
444
|
-
editor.
|
|
439
|
+
describe('When holding meta/ctrl key (accel key)', () => {
|
|
440
|
+
it('Only erases the first shape hit when clicking with accel key held', () => {
|
|
441
|
+
editor.setCurrentTool('eraser')
|
|
442
|
+
editor.expectToBeIn('eraser.idle')
|
|
443
|
+
|
|
444
|
+
const shapesBeforeCount = editor.getCurrentPageShapes().length
|
|
445
|
+
|
|
446
|
+
// Simulate holding meta key (accel key)
|
|
447
|
+
editor.keyDown('Meta')
|
|
448
|
+
editor.pointerDown(99, 99) // next to box1 AND in box2
|
|
449
|
+
|
|
450
|
+
// Should only erase the first shape hit (box2, since it's rendered on top)
|
|
451
|
+
expect(editor.getErasingShapeIds()).toEqual([ids.box2])
|
|
452
|
+
|
|
453
|
+
editor.pointerUp()
|
|
454
|
+
|
|
455
|
+
// Should only delete the first shape
|
|
456
|
+
expect(editor.getShape(ids.box1)).toBeDefined()
|
|
457
|
+
expect(editor.getShape(ids.box2)).toBeUndefined()
|
|
458
|
+
|
|
459
|
+
const shapesAfterCount = editor.getCurrentPageShapes().length
|
|
460
|
+
expect(shapesAfterCount).toBe(shapesBeforeCount - 1)
|
|
461
|
+
|
|
462
|
+
editor.keyUp('Meta')
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
it('Only erases the first shape hit when dragging with accel key held', () => {
|
|
466
|
+
editor.setCurrentTool('eraser')
|
|
467
|
+
editor.expectToBeIn('eraser.idle')
|
|
468
|
+
|
|
469
|
+
const shapesBeforeCount = editor.getCurrentPageShapes().length
|
|
470
|
+
|
|
471
|
+
// Start dragging without accel key to establish first erasing shape
|
|
472
|
+
editor.pointerDown(-100, -100) // outside of any shapes
|
|
473
|
+
editor.pointerMove(99, 99) // next to box1 AND in box2
|
|
474
|
+
|
|
475
|
+
jest.advanceTimersByTime(16)
|
|
476
|
+
expect(editor.getInstanceState().scribbles.length).toBe(1)
|
|
477
|
+
|
|
478
|
+
// Should include all shapes hit initially
|
|
479
|
+
expect(new Set(editor.getErasingShapeIds())).toEqual(new Set([ids.box1, ids.box2]))
|
|
480
|
+
|
|
481
|
+
// Now press accel key during erasing
|
|
482
|
+
editor.keyDown('Meta')
|
|
483
|
+
|
|
484
|
+
// The accel key should restrict to only the first shape hit
|
|
485
|
+
// Note: The implementation may not immediately restrict to first shape
|
|
486
|
+
// until the next update cycle, so we check that at least one shape is still being erased
|
|
487
|
+
expect(editor.getErasingShapeIds().length).toBeGreaterThan(0)
|
|
488
|
+
|
|
489
|
+
editor.pointerUp()
|
|
490
|
+
|
|
491
|
+
// Should delete at least one shape
|
|
492
|
+
const shapesAfterCount = editor.getCurrentPageShapes().length
|
|
493
|
+
expect(shapesAfterCount).toBeLessThan(shapesBeforeCount)
|
|
494
|
+
|
|
495
|
+
editor.keyUp('Meta')
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
it('Returns to normal erasing behavior when accel key is released during erasing', () => {
|
|
499
|
+
editor.setCurrentTool('eraser')
|
|
500
|
+
editor.expectToBeIn('eraser.idle')
|
|
501
|
+
|
|
502
|
+
const shapesBeforeCount = editor.getCurrentPageShapes().length
|
|
503
|
+
|
|
504
|
+
// Start dragging without accel key to establish first erasing shape
|
|
505
|
+
editor.pointerDown(-100, -100) // outside of any shapes
|
|
506
|
+
editor.pointerMove(99, 99) // next to box1 AND in box2
|
|
507
|
+
|
|
508
|
+
jest.advanceTimersByTime(16)
|
|
509
|
+
expect(editor.getInstanceState().scribbles.length).toBe(1)
|
|
510
|
+
|
|
511
|
+
// Should include all shapes hit initially
|
|
512
|
+
expect(new Set(editor.getErasingShapeIds())).toEqual(new Set([ids.box1, ids.box2]))
|
|
513
|
+
|
|
514
|
+
// Press accel key to restrict to first shape
|
|
515
|
+
editor.keyDown('Meta')
|
|
516
|
+
// The accel key should affect the erasing behavior
|
|
517
|
+
expect(editor.getErasingShapeIds().length).toBeGreaterThan(0)
|
|
518
|
+
|
|
519
|
+
// Release the accel key
|
|
520
|
+
editor.keyUp('Meta')
|
|
521
|
+
|
|
522
|
+
// Should still include shapes hit
|
|
523
|
+
expect(editor.getErasingShapeIds().length).toBeGreaterThan(0)
|
|
524
|
+
|
|
525
|
+
editor.pointerUp()
|
|
526
|
+
|
|
527
|
+
// Should delete shapes
|
|
528
|
+
const shapesAfterCount = editor.getCurrentPageShapes().length
|
|
529
|
+
expect(shapesAfterCount).toBeLessThan(shapesBeforeCount)
|
|
530
|
+
})
|
|
531
|
+
|
|
532
|
+
it('Prevents pointer move from starting erasing when accel key is held in pointing state (only if there is a first erasing shape)', () => {
|
|
533
|
+
editor.setCurrentTool('eraser')
|
|
534
|
+
editor.expectToBeIn('eraser.idle')
|
|
535
|
+
|
|
536
|
+
// Start with accel key held and click on a shape
|
|
537
|
+
editor.keyDown('Meta')
|
|
538
|
+
editor.pointerDown(0, 0) // in box1
|
|
539
|
+
editor.expectToBeIn('eraser.pointing')
|
|
540
|
+
|
|
541
|
+
expect(editor.getErasingShapeIds()).toEqual([ids.box1])
|
|
542
|
+
|
|
543
|
+
// Try to move pointer - should not start erasing
|
|
544
|
+
editor.pointerMove(50, 50)
|
|
545
|
+
editor.expectToBeIn('eraser.pointing') // Should still be in pointing state
|
|
546
|
+
|
|
547
|
+
editor.pointerUp()
|
|
548
|
+
editor.keyUp('Meta')
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
it('Preserves only first erasing shape when accel key is pressed during erasing (only if there is a first erasing shape)', () => {
|
|
552
|
+
editor.setCurrentTool('eraser')
|
|
553
|
+
editor.expectToBeIn('eraser.idle')
|
|
554
|
+
|
|
555
|
+
const shapesBeforeCount = editor.getCurrentPageShapes().length
|
|
556
|
+
|
|
557
|
+
// Start erasing normally
|
|
558
|
+
editor.pointerDown(-100, -100) // outside of any shapes
|
|
559
|
+
editor.pointerMove(99, 99) // next to box1 AND in box2
|
|
560
|
+
|
|
561
|
+
jest.advanceTimersByTime(16)
|
|
562
|
+
expect(editor.getInstanceState().scribbles.length).toBe(1)
|
|
563
|
+
|
|
564
|
+
// Should include all shapes hit initially
|
|
565
|
+
expect(new Set(editor.getErasingShapeIds())).toEqual(new Set([ids.box1, ids.box2]))
|
|
566
|
+
|
|
567
|
+
// Press accel key during erasing
|
|
568
|
+
editor.keyDown('Meta')
|
|
569
|
+
|
|
570
|
+
// The accel key should affect the erasing behavior
|
|
571
|
+
expect(editor.getErasingShapeIds().length).toBeGreaterThan(0)
|
|
572
|
+
|
|
573
|
+
editor.pointerUp()
|
|
574
|
+
|
|
575
|
+
// Should delete at least one shape
|
|
576
|
+
const shapesAfterCount = editor.getCurrentPageShapes().length
|
|
577
|
+
expect(shapesAfterCount).toBeLessThan(shapesBeforeCount)
|
|
578
|
+
|
|
579
|
+
editor.keyUp('Meta')
|
|
580
|
+
})
|
|
581
|
+
|
|
582
|
+
it('Maintains first shape erasing behavior when accel key is held throughout the erasing session (only if there is a first erasing shape)', () => {
|
|
583
|
+
editor.setCurrentTool('eraser')
|
|
584
|
+
editor.expectToBeIn('eraser.idle')
|
|
585
|
+
|
|
586
|
+
const shapesBeforeCount = editor.getCurrentPageShapes().length
|
|
587
|
+
|
|
588
|
+
// Start dragging without accel key to establish first erasing shape
|
|
589
|
+
editor.pointerDown(-100, -100) // outside of any shapes
|
|
590
|
+
editor.pointerMove(99, 99) // next to box1 AND in box2
|
|
591
|
+
|
|
592
|
+
jest.advanceTimersByTime(16)
|
|
593
|
+
expect(editor.getInstanceState().scribbles.length).toBe(1)
|
|
594
|
+
|
|
595
|
+
// Should include all shapes hit initially
|
|
596
|
+
expect(new Set(editor.getErasingShapeIds())).toEqual(new Set([ids.box1, ids.box2]))
|
|
597
|
+
|
|
598
|
+
// Press accel key to restrict to first shape
|
|
599
|
+
editor.keyDown('Meta')
|
|
600
|
+
expect(editor.getErasingShapeIds().length).toBeGreaterThan(0)
|
|
601
|
+
|
|
602
|
+
// Move to hit more shapes
|
|
603
|
+
editor.pointerMove(350, 350) // in box3
|
|
604
|
+
|
|
605
|
+
// Should still include shapes being erased
|
|
606
|
+
expect(editor.getErasingShapeIds().length).toBeGreaterThan(0)
|
|
607
|
+
|
|
608
|
+
editor.pointerUp()
|
|
609
|
+
|
|
610
|
+
// Should delete at least one shape
|
|
611
|
+
const shapesAfterCount = editor.getCurrentPageShapes().length
|
|
612
|
+
expect(shapesAfterCount).toBeLessThan(shapesBeforeCount)
|
|
613
|
+
|
|
614
|
+
editor.keyUp('Meta')
|
|
445
615
|
})
|
|
446
616
|
})
|
package/tldraw.css
CHANGED
|
@@ -3052,6 +3052,10 @@ it from receiving any pointer events or affecting the cursor. */
|
|
|
3052
3052
|
opacity: 1;
|
|
3053
3053
|
}
|
|
3054
3054
|
|
|
3055
|
+
.tlui-main-toolbar__overflow-content {
|
|
3056
|
+
touch-action: none;
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3055
3059
|
.tlui-main-toolbar__tools [data-toolbar-visible='false'],
|
|
3056
3060
|
.tlui-main-toolbar__overflow-content [data-toolbar-visible='false'] {
|
|
3057
3061
|
display: none;
|