tldraw 3.16.0-canary.8c74738e06fb → 3.16.0-canary.aa1aff3ffe55

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.
Files changed (36) hide show
  1. package/dist-cjs/index.d.ts +1 -32
  2. package/dist-cjs/index.js +1 -2
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/shapes/arrow/arrowTargetState.js +1 -1
  5. package/dist-cjs/lib/shapes/arrow/arrowTargetState.js.map +2 -2
  6. package/dist-cjs/lib/tools/SelectTool/childStates/Translating.js.map +2 -2
  7. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js +1 -149
  8. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js.map +2 -2
  9. package/dist-cjs/lib/ui/context/events.js.map +2 -2
  10. package/dist-cjs/lib/ui/hooks/useTools.js +9 -76
  11. package/dist-cjs/lib/ui/hooks/useTools.js.map +2 -2
  12. package/dist-cjs/lib/ui/version.js +3 -3
  13. package/dist-cjs/lib/ui/version.js.map +1 -1
  14. package/dist-esm/index.d.mts +1 -32
  15. package/dist-esm/index.mjs +1 -3
  16. package/dist-esm/index.mjs.map +2 -2
  17. package/dist-esm/lib/shapes/arrow/arrowTargetState.mjs +1 -1
  18. package/dist-esm/lib/shapes/arrow/arrowTargetState.mjs.map +2 -2
  19. package/dist-esm/lib/tools/SelectTool/childStates/Translating.mjs.map +2 -2
  20. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +3 -157
  21. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs.map +2 -2
  22. package/dist-esm/lib/ui/context/events.mjs.map +2 -2
  23. package/dist-esm/lib/ui/hooks/useTools.mjs +10 -83
  24. package/dist-esm/lib/ui/hooks/useTools.mjs.map +2 -2
  25. package/dist-esm/lib/ui/version.mjs +3 -3
  26. package/dist-esm/lib/ui/version.mjs.map +1 -1
  27. package/package.json +3 -3
  28. package/src/index.ts +0 -2
  29. package/src/lib/shapes/arrow/arrowTargetState.ts +2 -1
  30. package/src/lib/tools/SelectTool/childStates/Translating.ts +1 -0
  31. package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +2 -213
  32. package/src/lib/ui/context/events.tsx +0 -1
  33. package/src/lib/ui/hooks/useTools.tsx +10 -118
  34. package/src/lib/ui/version.ts +3 -3
  35. package/src/test/arrows-megabus.test.tsx +12 -6
  36. package/src/test/inner-outer-margin.test.ts +315 -0
@@ -1,17 +1,9 @@
1
- import {
2
- exhaustiveSwitchError,
3
- getPointerInfo,
4
- preventDefault,
5
- TLPointerEventInfo,
6
- useEditor,
7
- Vec,
8
- } from '@tldraw/editor'
1
+ import { exhaustiveSwitchError, preventDefault } from '@tldraw/editor'
9
2
  import { ContextMenu as _ContextMenu } from 'radix-ui'
10
- import { useMemo, useState } from 'react'
3
+ import { useState } from 'react'
11
4
  import { unwrapLabel } from '../../../context/actions'
12
5
  import { TLUiEventSource } from '../../../context/events'
13
6
  import { useReadonly } from '../../../hooks/useReadonly'
14
- import { TLUiToolItem } from '../../../hooks/useTools'
15
7
  import { TLUiTranslationKey } from '../../../hooks/useTranslation/TLUiTranslationKey'
16
8
  import { useTranslation } from '../../../hooks/useTranslation/useTranslation'
17
9
  import { kbdStr } from '../../../kbd-utils'
@@ -71,10 +63,6 @@ export interface TLUiMenuItemProps<
71
63
  * Whether the item is selected.
72
64
  */
73
65
  isSelected?: boolean
74
- /**
75
- * The function to call when the item is dragged. If this is provided, the item will be draggable.
76
- */
77
- onDragStart?(source: TLUiEventSource, info: TLPointerEventInfo): void
78
66
  }
79
67
 
80
68
  /** @public @react */
@@ -93,7 +81,6 @@ export function TldrawUiMenuItem<
93
81
  onSelect,
94
82
  noClose,
95
83
  isSelected,
96
- onDragStart,
97
84
  }: TLUiMenuItemProps<TranslationKey, IconType>) {
98
85
  const { type: menuType, sourceId } = useTldrawUiMenuContext()
99
86
 
@@ -220,20 +207,6 @@ export function TldrawUiMenuItem<
220
207
  )
221
208
  }
222
209
  case 'toolbar': {
223
- if (onDragStart) {
224
- return (
225
- <DraggableToolbarButton
226
- id={id}
227
- icon={icon}
228
- onSelect={onSelect}
229
- onDragStart={onDragStart}
230
- labelToUse={labelToUse}
231
- titleStr={titleStr}
232
- disabled={disabled}
233
- isSelected={isSelected}
234
- />
235
- )
236
- }
237
210
  return (
238
211
  <TldrawUiToolbarButton
239
212
  aria-label={labelStr}
@@ -254,21 +227,6 @@ export function TldrawUiMenuItem<
254
227
  )
255
228
  }
256
229
  case 'toolbar-overflow': {
257
- if (onDragStart) {
258
- return (
259
- <DraggableToolbarButton
260
- id={id}
261
- icon={icon}
262
- onSelect={onSelect}
263
- onDragStart={onDragStart}
264
- labelToUse={labelToUse}
265
- titleStr={titleStr}
266
- disabled={disabled}
267
- isSelected={isSelected}
268
- overflow
269
- />
270
- )
271
- }
272
230
  return (
273
231
  <TldrawUiToolbarButton
274
232
  aria-label={labelStr}
@@ -291,172 +249,3 @@ export function TldrawUiMenuItem<
291
249
  }
292
250
  }
293
251
  }
294
-
295
- function useDraggableEvents(
296
- onDragStart: TLUiToolItem['onDragStart'],
297
- onSelect: TLUiToolItem['onSelect']
298
- ) {
299
- const editor = useEditor()
300
- const events = useMemo(() => {
301
- let state = { name: 'idle' } as
302
- | {
303
- name: 'idle'
304
- }
305
- | {
306
- name: 'pointing'
307
- start: Vec
308
- }
309
- | {
310
- name: 'dragging'
311
- start: Vec
312
- }
313
- | {
314
- name: 'dragged'
315
- }
316
-
317
- function handlePointerDown(e: React.PointerEvent<HTMLButtonElement>) {
318
- state = {
319
- name: 'pointing',
320
- start: editor.inputs.currentPagePoint.clone(),
321
- }
322
-
323
- e.currentTarget.setPointerCapture(e.pointerId)
324
- }
325
-
326
- function handlePointerMove(e: React.PointerEvent<HTMLButtonElement>) {
327
- if ((e as any).isSpecialRedispatchedEvent) return
328
-
329
- if (state.name === 'pointing') {
330
- const distance = Vec.Dist2(state.start, editor.inputs.currentPagePoint)
331
- if (
332
- distance >
333
- (editor.getInstanceState().isCoarsePointer
334
- ? editor.options.coarseDragDistanceSquared
335
- : editor.options.dragDistanceSquared)
336
- ) {
337
- const start = state.start
338
- state = {
339
- name: 'dragging',
340
- start,
341
- }
342
-
343
- editor.run(() => {
344
- // Set origin point
345
- editor.dispatch({
346
- type: 'pointer',
347
- target: 'canvas',
348
- name: 'pointer_down',
349
- ...getPointerInfo(e),
350
- point: start,
351
- })
352
-
353
- // Pointer down potentially selects shapes, so we need to deselect them.
354
- editor.selectNone()
355
-
356
- // start drag
357
- onDragStart?.('toolbar', {
358
- type: 'pointer',
359
- target: 'canvas',
360
- name: 'pointer_move',
361
- ...getPointerInfo(e),
362
- })
363
- })
364
- }
365
- }
366
- }
367
-
368
- function handlePointerUp(e: React.PointerEvent<HTMLButtonElement>) {
369
- if ((e as any).isSpecialRedispatchedEvent) return
370
-
371
- e.currentTarget.releasePointerCapture(e.pointerId)
372
-
373
- editor.dispatch({
374
- type: 'pointer',
375
- target: 'canvas',
376
- name: 'pointer_up',
377
- ...getPointerInfo(e),
378
- })
379
- }
380
-
381
- function handleClick() {
382
- if (state.name === 'dragging' || state.name === 'dragged') {
383
- state = { name: 'idle' }
384
- return true
385
- }
386
-
387
- state = { name: 'idle' }
388
- onSelect?.('toolbar')
389
- }
390
-
391
- return {
392
- onPointerDown: handlePointerDown,
393
- onPointerMove: handlePointerMove,
394
- onPointerUp: handlePointerUp,
395
- onClick: handleClick,
396
- }
397
- }, [onDragStart, editor, onSelect])
398
-
399
- return events
400
- }
401
-
402
- function DraggableToolbarButton({
403
- id,
404
- labelToUse,
405
- titleStr,
406
- disabled,
407
- isSelected,
408
- icon,
409
- onSelect,
410
- onDragStart,
411
- overflow,
412
- }: {
413
- id: string
414
- disabled: boolean
415
- labelToUse?: string
416
- titleStr?: string
417
- isSelected?: boolean
418
- icon: TLUiMenuItemProps['icon']
419
- onSelect: TLUiMenuItemProps['onSelect']
420
- onDragStart: TLUiMenuItemProps['onDragStart']
421
- overflow?: boolean
422
- }) {
423
- const events = useDraggableEvents(onDragStart, onSelect)
424
-
425
- if (overflow) {
426
- return (
427
- <TldrawUiToolbarButton
428
- aria-label={labelToUse}
429
- aria-pressed={isSelected ? 'true' : 'false'}
430
- isActive={isSelected}
431
- className="tlui-button-grid__button"
432
- data-testid={`tools.more.${id}`}
433
- data-value={id}
434
- disabled={disabled}
435
- title={titleStr}
436
- type="icon"
437
- {...events}
438
- >
439
- <TldrawUiButtonIcon icon={icon!} />
440
- </TldrawUiToolbarButton>
441
- )
442
- }
443
-
444
- return (
445
- <TldrawUiToolbarButton
446
- aria-label={labelToUse}
447
- aria-pressed={isSelected ? 'true' : 'false'}
448
- data-testid={`tools.${id}`}
449
- data-value={id}
450
- disabled={disabled}
451
- onTouchStart={(e) => {
452
- preventDefault(e)
453
- onSelect('toolbar')
454
- }}
455
- title={titleStr}
456
- type="tool"
457
- {...events}
458
- >
459
- <TldrawUiButtonIcon icon={icon!} />
460
- </TldrawUiToolbarButton>
461
- )
462
- }
@@ -126,7 +126,6 @@ export interface TLUiEventMap {
126
126
  'open-context-menu': null
127
127
  'adjust-shape-styles': null
128
128
  'copy-link': null
129
- 'drag-tool': { id: string }
130
129
  'image-replace': null
131
130
  'video-replace': null
132
131
  'open-kbd-shortcuts': null
@@ -1,13 +1,4 @@
1
- import {
2
- assertExists,
3
- createShapeId,
4
- Editor,
5
- GeoShapeGeoStyle,
6
- TLPointerEventInfo,
7
- TLShapeId,
8
- toRichText,
9
- useMaybeEditor,
10
- } from '@tldraw/editor'
1
+ import { Editor, GeoShapeGeoStyle, useMaybeEditor } from '@tldraw/editor'
11
2
  import * as React from 'react'
12
3
  import { EmbedDialog } from '../components/EmbedDialog'
13
4
  import { TLUiIconJsx } from '../components/primitives/TldrawUiIcon'
@@ -28,7 +19,6 @@ export interface TLUiToolItem<
28
19
  shortcutsLabel?: TranslationKey
29
20
  icon: IconType | TLUiIconJsx
30
21
  onSelect(source: TLUiEventSource): void
31
- onDragStart?(source: TLUiEventSource, info: TLPointerEventInfo): void
32
22
  /**
33
23
  * The keyboard shortcut for this tool. This is a string that can be a single key,
34
24
  * or a combination of keys.
@@ -136,27 +126,21 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
136
126
  onToolSelect(source, this)
137
127
  },
138
128
  },
139
- ...[...GeoShapeGeoStyle.values].map((geo) => ({
140
- id: geo,
141
- label: `tool.${geo}` as TLUiTranslationKey,
129
+ ...[...GeoShapeGeoStyle.values].map((id) => ({
130
+ id,
131
+ label: `tool.${id}` as TLUiTranslationKey,
142
132
  meta: {
143
- geo,
133
+ geo: id,
144
134
  },
145
- kbd: geo === 'rectangle' ? 'r' : geo === 'ellipse' ? 'o' : undefined,
146
- icon: ('geo-' + geo) as TLUiIconType,
135
+ kbd: id === 'rectangle' ? 'r' : id === 'ellipse' ? 'o' : undefined,
136
+ icon: ('geo-' + id) as TLUiIconType,
147
137
  onSelect(source: TLUiEventSource) {
148
138
  editor.run(() => {
149
- editor.setStyleForNextShapes(GeoShapeGeoStyle, geo)
139
+ editor.setStyleForNextShapes(GeoShapeGeoStyle, id)
150
140
  editor.setCurrentTool('geo')
151
- onToolSelect(source, this, `geo-${geo}`)
141
+ onToolSelect(source, this, `geo-${id}`)
152
142
  })
153
143
  },
154
- onDragStart(source: TLUiEventSource, info: TLPointerEventInfo) {
155
- onDragFromToolbarToCreateShape(editor, info, {
156
- createShape: (id) => editor.createShape({ id, type: 'geo', props: { geo } }),
157
- })
158
- trackEvent('drag-tool', { source, id: 'geo' })
159
- },
160
144
  })),
161
145
  {
162
146
  id: 'arrow',
@@ -167,17 +151,6 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
167
151
  editor.setCurrentTool('arrow')
168
152
  onToolSelect(source, this)
169
153
  },
170
- onDragStart(source: TLUiEventSource, info: TLPointerEventInfo) {
171
- onDragFromToolbarToCreateShape(editor, info, {
172
- createShape: (id) =>
173
- editor.createShape({
174
- id,
175
- type: 'arrow',
176
- props: { start: { x: 0, y: 0 }, end: { x: 200, y: 0 } },
177
- }),
178
- })
179
- trackEvent('drag-tool', { source, id: 'arrow' })
180
- },
181
154
  },
182
155
  {
183
156
  id: 'line',
@@ -198,12 +171,6 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
198
171
  editor.setCurrentTool('frame')
199
172
  onToolSelect(source, this)
200
173
  },
201
- onDragStart(source, info) {
202
- onDragFromToolbarToCreateShape(editor, info, {
203
- createShape: (id) => editor.createShape({ id, type: 'frame' }),
204
- })
205
- trackEvent('drag-tool', { source, id: 'frame' })
206
- },
207
174
  },
208
175
  {
209
176
  id: 'text',
@@ -214,17 +181,6 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
214
181
  editor.setCurrentTool('text')
215
182
  onToolSelect(source, this)
216
183
  },
217
- onDragStart(source, info) {
218
- onDragFromToolbarToCreateShape(editor, info, {
219
- createShape: (id) =>
220
- editor.createShape({ id, type: 'text', props: { richText: toRichText('Text') } }),
221
- onDragEnd: (id) => {
222
- editor.emit('select-all-text', { shapeId: id })
223
- editor.setEditingShape(id)
224
- },
225
- })
226
- trackEvent('drag-tool', { source, id: 'text' })
227
- },
228
184
  },
229
185
  {
230
186
  id: 'asset',
@@ -245,16 +201,6 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
245
201
  editor.setCurrentTool('note')
246
202
  onToolSelect(source, this)
247
203
  },
248
- onDragStart(source, info) {
249
- onDragFromToolbarToCreateShape(editor, info, {
250
- createShape: (id) => editor.createShape({ id, type: 'note' }),
251
- onDragEnd: (id) => {
252
- editor.emit('select-all-text', { shapeId: id })
253
- editor.setEditingShape(id)
254
- },
255
- })
256
- trackEvent('drag-tool', { source, id: 'note' })
257
- },
258
204
  },
259
205
  {
260
206
  id: 'laser',
@@ -298,7 +244,7 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
298
244
  }
299
245
 
300
246
  return tools
301
- }, [overrides, editor, helpers, onToolSelect, trackEvent])
247
+ }, [overrides, editor, helpers, onToolSelect])
302
248
 
303
249
  return <ToolsContext.Provider value={tools}>{children}</ToolsContext.Provider>
304
250
  }
@@ -313,57 +259,3 @@ export function useTools() {
313
259
 
314
260
  return ctx
315
261
  }
316
-
317
- /**
318
- * Options for {@link onDragFromToolbarToCreateShape}.
319
- * @public
320
- */
321
- export interface OnDragFromToolbarToCreateShapesOpts {
322
- /**
323
- * Create the shape being dragged. You don't need to worry about positioning it, as it'll be
324
- * immediately updated with the correct position.
325
- */
326
- createShape(id: TLShapeId): void
327
- /**
328
- * Called once the drag interaction has finished.
329
- */
330
- onDragEnd?(id: TLShapeId): void
331
- }
332
-
333
- /**
334
- * A helper method to use in {@link TLUiToolItem#onDragStart} to create a shape by dragging it from
335
- * the toolbar.
336
- * @public
337
- */
338
- export function onDragFromToolbarToCreateShape(
339
- editor: Editor,
340
- info: TLPointerEventInfo,
341
- opts: OnDragFromToolbarToCreateShapesOpts
342
- ) {
343
- const { x, y } = editor.inputs.currentPagePoint
344
-
345
- const stoppingPoint = editor.markHistoryStoppingPoint('drag shape tool')
346
- editor.setCurrentTool('select.translating')
347
-
348
- const id = createShapeId()
349
- opts.createShape(id)
350
- const shape = assertExists(editor.getShape(id), 'Shape not found')
351
-
352
- const { w, h } = editor.getShapePageBounds(id)!
353
- editor.updateShape({ id, type: shape.type, x: x - w / 2, y: y - h / 2 })
354
- editor.select(id)
355
-
356
- editor.setCurrentTool('select.translating', {
357
- ...info,
358
- target: 'shape',
359
- shape: editor.getShape(id),
360
- isCreating: true,
361
- creatingMarkId: stoppingPoint,
362
- onCreate() {
363
- editor.setCurrentTool('select.idle')
364
- editor.select(id)
365
- opts.onDragEnd?.(id)
366
- },
367
- })
368
- editor.getCurrentTool().setCurrentToolIdMask(shape.type)
369
- }
@@ -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.8c74738e06fb'
4
+ export const version = '3.16.0-canary.aa1aff3ffe55'
5
5
  export const publishDates = {
6
6
  major: '2024-09-13T14:36:29.063Z',
7
- minor: '2025-08-06T08:29:26.797Z',
8
- patch: '2025-08-06T08:29:26.797Z',
7
+ minor: '2025-08-06T11:17:10.614Z',
8
+ patch: '2025-08-06T11:17:10.614Z',
9
9
  }
@@ -288,11 +288,13 @@ describe('When shapes are overlapping', () => {
288
288
  editor.pointerDown(0, 50) // over nothing
289
289
  editor.pointerMove(125, 50) // over box1 only
290
290
  expect(bindings().end).toMatchObject({ toId: ids.box1 })
291
- editor.pointerMove(175, 50) // box2 is higher but box1 is filled?
291
+ editor.pointerMove(175, 50) // box2 is higher but box1 is filled, but we're on the edge ofd box 2
292
+ expect(bindings().end).toMatchObject({ toId: ids.box2 })
293
+ editor.pointerMove(175, 70) // box2 is higher but box1 is filled, and we're inside of box2
292
294
  expect(bindings().end).toMatchObject({ toId: ids.box1 })
293
- editor.pointerMove(225, 50) // box3 is higher
295
+ editor.pointerMove(225, 70) // box3 is higher
294
296
  expect(bindings().end).toMatchObject({ toId: ids.box3 })
295
- editor.pointerMove(275, 50) // box4 is higher but box 3 is filled
297
+ editor.pointerMove(275, 70) // box4 is higher but box 3 is filled
296
298
  expect(bindings().end).toMatchObject({ toId: ids.box3 })
297
299
  })
298
300
 
@@ -304,14 +306,18 @@ describe('When shapes are overlapping', () => {
304
306
  ])
305
307
  editor.setCurrentTool('arrow')
306
308
  editor.pointerDown(0, 50)
307
- editor.pointerMove(175, 50) // box1 is smaller even though it's behind box2
309
+ editor.pointerMove(175, 50) // box1 is smaller even though it's behind box2, but we're on the edge of box 2
310
+ expect(bindings().end).toMatchObject({ toId: ids.box2 })
311
+ editor.pointerMove(175, 70) // box1 is smaller even though it's behind box2
308
312
  expect(bindings().end).toMatchObject({ toId: ids.box1 })
309
- editor.pointerMove(150, 90) // box3 is smaller and at the front
313
+ editor.pointerMove(150, 90) // box3 is smaller and at the front but we're on the edge of box 2
314
+ expect(bindings().end).toMatchObject({ toId: ids.box2 })
315
+ editor.pointerMove(160, 90) // box3 is smaller and at the front and we're in box1 and box 3 and box 2
310
316
  expect(bindings().end).toMatchObject({ toId: ids.box3 })
311
317
  editor.sendToBack([ids.box3])
312
318
  editor.pointerMove(149, 90) // box3 is smaller, even when at the back
313
319
  expect(bindings().end).toMatchObject({ toId: ids.box3 })
314
- editor.pointerMove(175, 50)
320
+ editor.pointerMove(175, 60) // inside of box1 and box 2, but box 1 is smaller
315
321
  expect(bindings().end).toMatchObject({ toId: ids.box1 })
316
322
  })
317
323
  })