tldraw 3.15.0 → 3.16.0-canary.0e0fb8bde89d
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 +101 -5
- package/dist-cjs/index.js +12 -2
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/canvas/TldrawCropHandles.js.map +2 -2
- package/dist-cjs/lib/defaultExternalContentHandlers.js +1 -0
- package/dist-cjs/lib/defaultExternalContentHandlers.js.map +2 -2
- package/dist-cjs/lib/shapes/arrow/ArrowShapeUtil.js +22 -36
- package/dist-cjs/lib/shapes/arrow/ArrowShapeUtil.js.map +2 -2
- package/dist-cjs/lib/shapes/arrow/arrowLabel.js +16 -4
- package/dist-cjs/lib/shapes/arrow/arrowLabel.js.map +2 -2
- package/dist-cjs/lib/shapes/arrow/toolStates/Pointing.js +3 -0
- package/dist-cjs/lib/shapes/arrow/toolStates/Pointing.js.map +2 -2
- package/dist-cjs/lib/shapes/line/LineShapeUtil.js +15 -1
- package/dist-cjs/lib/shapes/line/LineShapeUtil.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/DraggingHandle.js +43 -22
- package/dist-cjs/lib/tools/SelectTool/childStates/DraggingHandle.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/Idle.js +2 -15
- package/dist-cjs/lib/tools/SelectTool/childStates/Idle.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/PointingShape.js +5 -0
- package/dist-cjs/lib/tools/SelectTool/childStates/PointingShape.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/Resizing.js +8 -0
- package/dist-cjs/lib/tools/SelectTool/childStates/Resizing.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/Rotating.js +8 -0
- package/dist-cjs/lib/tools/SelectTool/childStates/Rotating.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/Translating.js +8 -0
- package/dist-cjs/lib/tools/SelectTool/childStates/Translating.js.map +2 -2
- package/dist-cjs/lib/tools/selection-logic/getHitShapeOnCanvasPointerDown.js.map +2 -2
- package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js +40 -0
- package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js.map +2 -2
- package/dist-cjs/lib/ui/components/Toolbar/ToggleToolLockedButton.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js +1 -0
- package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js +149 -1
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js.map +2 -2
- package/dist-cjs/lib/ui/context/actions.js +14 -7
- package/dist-cjs/lib/ui/context/actions.js.map +2 -2
- package/dist-cjs/lib/ui/context/events.js.map +2 -2
- package/dist-cjs/lib/ui/hooks/menu-hooks.js.map +2 -2
- package/dist-cjs/lib/ui/hooks/useTools.js +76 -9
- package/dist-cjs/lib/ui/hooks/useTools.js.map +2 -2
- package/dist-cjs/lib/ui/hooks/useTranslation/TLUiTranslationKey.js.map +1 -1
- package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js +4 -0
- package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js.map +2 -2
- package/dist-cjs/lib/ui/kbd-utils.js +2 -1
- package/dist-cjs/lib/ui/kbd-utils.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-cjs/lib/utils/excalidraw/putExcalidrawContent.js +1 -1
- package/dist-cjs/lib/utils/excalidraw/putExcalidrawContent.js.map +2 -2
- package/dist-cjs/lib/utils/tldr/buildFromV1Document.js +3 -2
- package/dist-cjs/lib/utils/tldr/buildFromV1Document.js.map +2 -2
- package/dist-esm/index.d.mts +101 -5
- package/dist-esm/index.mjs +18 -3
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/canvas/TldrawCropHandles.mjs.map +2 -2
- package/dist-esm/lib/defaultExternalContentHandlers.mjs +1 -0
- package/dist-esm/lib/defaultExternalContentHandlers.mjs.map +2 -2
- package/dist-esm/lib/shapes/arrow/ArrowShapeUtil.mjs +24 -36
- package/dist-esm/lib/shapes/arrow/ArrowShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/shapes/arrow/arrowLabel.mjs +19 -5
- package/dist-esm/lib/shapes/arrow/arrowLabel.mjs.map +2 -2
- package/dist-esm/lib/shapes/arrow/toolStates/Pointing.mjs +3 -0
- package/dist-esm/lib/shapes/arrow/toolStates/Pointing.mjs.map +2 -2
- package/dist-esm/lib/shapes/line/LineShapeUtil.mjs +15 -1
- package/dist-esm/lib/shapes/line/LineShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/DraggingHandle.mjs +43 -22
- package/dist-esm/lib/tools/SelectTool/childStates/DraggingHandle.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/Idle.mjs +2 -15
- package/dist-esm/lib/tools/SelectTool/childStates/Idle.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/PointingShape.mjs +5 -0
- package/dist-esm/lib/tools/SelectTool/childStates/PointingShape.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/Resizing.mjs +8 -0
- package/dist-esm/lib/tools/SelectTool/childStates/Resizing.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/Rotating.mjs +8 -0
- package/dist-esm/lib/tools/SelectTool/childStates/Rotating.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/Translating.mjs +8 -0
- package/dist-esm/lib/tools/SelectTool/childStates/Translating.mjs.map +2 -2
- package/dist-esm/lib/tools/selection-logic/getHitShapeOnCanvasPointerDown.mjs.map +2 -2
- package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs +40 -0
- package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs.map +2 -2
- package/dist-esm/lib/ui/components/Toolbar/ToggleToolLockedButton.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs +1 -0
- package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +157 -3
- package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs.map +2 -2
- package/dist-esm/lib/ui/context/actions.mjs +14 -7
- package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
- package/dist-esm/lib/ui/context/events.mjs.map +2 -2
- package/dist-esm/lib/ui/hooks/menu-hooks.mjs.map +2 -2
- package/dist-esm/lib/ui/hooks/useTools.mjs +83 -10
- package/dist-esm/lib/ui/hooks/useTools.mjs.map +2 -2
- package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs +4 -0
- package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs.map +2 -2
- package/dist-esm/lib/ui/kbd-utils.mjs +2 -1
- package/dist-esm/lib/ui/kbd-utils.mjs.map +2 -2
- package/dist-esm/lib/ui/version.mjs +3 -3
- package/dist-esm/lib/ui/version.mjs.map +1 -1
- package/dist-esm/lib/utils/excalidraw/putExcalidrawContent.mjs +1 -1
- package/dist-esm/lib/utils/excalidraw/putExcalidrawContent.mjs.map +2 -2
- package/dist-esm/lib/utils/tldr/buildFromV1Document.mjs +3 -2
- package/dist-esm/lib/utils/tldr/buildFromV1Document.mjs.map +2 -2
- package/package.json +3 -3
- package/src/index.ts +11 -1
- package/src/lib/canvas/TldrawCropHandles.tsx +2 -0
- package/src/lib/defaultExternalContentHandlers.ts +2 -1
- package/src/lib/shapes/arrow/ArrowShapeUtil.test.ts +5 -5
- package/src/lib/shapes/arrow/ArrowShapeUtil.tsx +25 -39
- package/src/lib/shapes/arrow/arrowLabel.ts +23 -3
- package/src/lib/shapes/arrow/toolStates/Pointing.tsx +3 -0
- package/src/lib/shapes/line/LineShapeUtil.tsx +19 -2
- package/src/lib/tools/SelectTool/childStates/DraggingHandle.tsx +54 -30
- package/src/lib/tools/SelectTool/childStates/Idle.ts +2 -24
- package/src/lib/tools/SelectTool/childStates/PointingShape.ts +7 -0
- package/src/lib/tools/SelectTool/childStates/Resizing.ts +12 -1
- package/src/lib/tools/SelectTool/childStates/Rotating.ts +11 -0
- package/src/lib/tools/SelectTool/childStates/Translating.ts +11 -1
- package/src/lib/tools/selection-logic/getHitShapeOnCanvasPointerDown.ts +1 -0
- package/src/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.tsx +32 -0
- package/src/lib/ui/components/Toolbar/ToggleToolLockedButton.tsx +3 -1
- package/src/lib/ui/components/primitives/TldrawUiSlider.tsx +1 -0
- package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +213 -2
- package/src/lib/ui/context/actions.tsx +14 -7
- package/src/lib/ui/context/events.tsx +3 -2
- package/src/lib/ui/hooks/menu-hooks.ts +1 -0
- package/src/lib/ui/hooks/useTools.tsx +118 -10
- package/src/lib/ui/hooks/useTranslation/TLUiTranslationKey.ts +4 -0
- package/src/lib/ui/hooks/useTranslation/defaultTranslation.ts +4 -0
- package/src/lib/ui/kbd-utils.ts +2 -1
- package/src/lib/ui/version.ts +3 -3
- package/src/lib/utils/excalidraw/__snapshots__/putExcalidrawContent.test.tsx.snap +16 -2
- package/src/lib/utils/excalidraw/putExcalidrawContent.ts +1 -1
- package/src/lib/utils/tldr/__snapshots__/buildFromV1Document.test.ts.snap +24 -3
- package/src/lib/utils/tldr/buildFromV1Document.ts +2 -1
- package/src/test/SelectTool.test.ts +37 -11
- package/src/test/commands/deletePage.test.ts +84 -1
- package/src/test/shapeutils.test.ts +394 -45
- package/tldraw.css +4 -23
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
exhaustiveSwitchError,
|
|
3
|
+
getPointerInfo,
|
|
4
|
+
preventDefault,
|
|
5
|
+
TLPointerEventInfo,
|
|
6
|
+
useEditor,
|
|
7
|
+
Vec,
|
|
8
|
+
} from '@tldraw/editor'
|
|
2
9
|
import { ContextMenu as _ContextMenu } from 'radix-ui'
|
|
3
|
-
import { useState } from 'react'
|
|
10
|
+
import { useMemo, useState } from 'react'
|
|
4
11
|
import { unwrapLabel } from '../../../context/actions'
|
|
5
12
|
import { TLUiEventSource } from '../../../context/events'
|
|
6
13
|
import { useReadonly } from '../../../hooks/useReadonly'
|
|
14
|
+
import { TLUiToolItem } from '../../../hooks/useTools'
|
|
7
15
|
import { TLUiTranslationKey } from '../../../hooks/useTranslation/TLUiTranslationKey'
|
|
8
16
|
import { useTranslation } from '../../../hooks/useTranslation/useTranslation'
|
|
9
17
|
import { kbdStr } from '../../../kbd-utils'
|
|
@@ -63,6 +71,10 @@ export interface TLUiMenuItemProps<
|
|
|
63
71
|
* Whether the item is selected.
|
|
64
72
|
*/
|
|
65
73
|
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
|
|
66
78
|
}
|
|
67
79
|
|
|
68
80
|
/** @public @react */
|
|
@@ -81,6 +93,7 @@ export function TldrawUiMenuItem<
|
|
|
81
93
|
onSelect,
|
|
82
94
|
noClose,
|
|
83
95
|
isSelected,
|
|
96
|
+
onDragStart,
|
|
84
97
|
}: TLUiMenuItemProps<TranslationKey, IconType>) {
|
|
85
98
|
const { type: menuType, sourceId } = useTldrawUiMenuContext()
|
|
86
99
|
|
|
@@ -207,6 +220,20 @@ export function TldrawUiMenuItem<
|
|
|
207
220
|
)
|
|
208
221
|
}
|
|
209
222
|
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
|
+
}
|
|
210
237
|
return (
|
|
211
238
|
<TldrawUiToolbarButton
|
|
212
239
|
aria-label={labelStr}
|
|
@@ -227,6 +254,21 @@ export function TldrawUiMenuItem<
|
|
|
227
254
|
)
|
|
228
255
|
}
|
|
229
256
|
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
|
+
}
|
|
230
272
|
return (
|
|
231
273
|
<TldrawUiToolbarButton
|
|
232
274
|
aria-label={labelStr}
|
|
@@ -249,3 +291,172 @@ export function TldrawUiMenuItem<
|
|
|
249
291
|
}
|
|
250
292
|
}
|
|
251
293
|
}
|
|
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
|
+
}
|
|
@@ -1037,17 +1037,20 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
|
|
1037
1037
|
id: 'rotate-cw',
|
|
1038
1038
|
label: 'action.rotate-cw',
|
|
1039
1039
|
icon: 'rotate-cw',
|
|
1040
|
+
kbd: 'shift+.,shift+alt+.',
|
|
1040
1041
|
onSelect(source) {
|
|
1041
1042
|
if (!canApplySelectionAction()) return
|
|
1042
1043
|
if (mustGoBackToSelectToolFirst()) return
|
|
1043
1044
|
|
|
1044
|
-
|
|
1045
|
+
const isFine = editor.inputs.altKey
|
|
1046
|
+
trackEvent('rotate-cw', { source, fine: isFine })
|
|
1045
1047
|
editor.markHistoryStoppingPoint('rotate-cw')
|
|
1046
1048
|
editor.run(() => {
|
|
1047
|
-
const
|
|
1048
|
-
const
|
|
1049
|
+
const rotation = HALF_PI / (isFine ? 96 : 6)
|
|
1050
|
+
const offset = editor.getSelectionRotation() % rotation
|
|
1051
|
+
const dontUseOffset = approximately(offset, 0) || approximately(offset, rotation)
|
|
1049
1052
|
const selectedShapeIds = editor.getSelectedShapeIds()
|
|
1050
|
-
editor.rotateShapesBy(selectedShapeIds,
|
|
1053
|
+
editor.rotateShapesBy(selectedShapeIds, rotation - (dontUseOffset ? 0 : offset))
|
|
1051
1054
|
kickoutOccludedShapes(editor, selectedShapeIds)
|
|
1052
1055
|
})
|
|
1053
1056
|
},
|
|
@@ -1056,17 +1059,21 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
|
|
1056
1059
|
id: 'rotate-ccw',
|
|
1057
1060
|
label: 'action.rotate-ccw',
|
|
1058
1061
|
icon: 'rotate-ccw',
|
|
1062
|
+
// omg double comma
|
|
1063
|
+
kbd: 'shift+,,shift+alt+,',
|
|
1059
1064
|
onSelect(source) {
|
|
1060
1065
|
if (!canApplySelectionAction()) return
|
|
1061
1066
|
if (mustGoBackToSelectToolFirst()) return
|
|
1062
1067
|
|
|
1063
|
-
|
|
1068
|
+
const isFine = editor.inputs.altKey
|
|
1069
|
+
trackEvent('rotate-ccw', { source, fine: isFine })
|
|
1064
1070
|
editor.markHistoryStoppingPoint('rotate-ccw')
|
|
1065
1071
|
editor.run(() => {
|
|
1066
|
-
const
|
|
1072
|
+
const rotation = HALF_PI / (isFine ? 96 : 6)
|
|
1073
|
+
const offset = editor.getSelectionRotation() % rotation
|
|
1067
1074
|
const offsetCloseToZero = approximately(offset, 0)
|
|
1068
1075
|
const selectedShapeIds = editor.getSelectedShapeIds()
|
|
1069
|
-
editor.rotateShapesBy(selectedShapeIds, offsetCloseToZero ? -
|
|
1076
|
+
editor.rotateShapesBy(selectedShapeIds, offsetCloseToZero ? -rotation : -offset)
|
|
1070
1077
|
kickoutOccludedShapes(editor, selectedShapeIds)
|
|
1071
1078
|
})
|
|
1072
1079
|
},
|
|
@@ -76,8 +76,8 @@ export interface TLUiEventMap {
|
|
|
76
76
|
'delete-shapes': null
|
|
77
77
|
'select-all-shapes': null
|
|
78
78
|
'select-none-shapes': null
|
|
79
|
-
'rotate-ccw':
|
|
80
|
-
'rotate-cw':
|
|
79
|
+
'rotate-ccw': { fine: boolean }
|
|
80
|
+
'rotate-cw': { fine: boolean }
|
|
81
81
|
'zoom-in': { towardsCursor: boolean }
|
|
82
82
|
'zoom-out': { towardsCursor: boolean }
|
|
83
83
|
'zoom-to-fit': null
|
|
@@ -126,6 +126,7 @@ 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 }
|
|
129
130
|
'image-replace': null
|
|
130
131
|
'video-replace': null
|
|
131
132
|
'open-kbd-shortcuts': null
|
|
@@ -123,6 +123,7 @@ export function useAnySelectedShapesCount(min?: number, max?: number) {
|
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
125
|
* Returns true if the number of UNLOCKED selected shapes is at least min or at most max.
|
|
126
|
+
* @public
|
|
126
127
|
*/
|
|
127
128
|
export function useUnlockedSelectedShapesCount(min?: number, max?: number) {
|
|
128
129
|
const editor = useEditor()
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
assertExists,
|
|
3
|
+
createShapeId,
|
|
4
|
+
Editor,
|
|
5
|
+
GeoShapeGeoStyle,
|
|
6
|
+
TLPointerEventInfo,
|
|
7
|
+
TLShapeId,
|
|
8
|
+
toRichText,
|
|
9
|
+
useMaybeEditor,
|
|
10
|
+
} from '@tldraw/editor'
|
|
2
11
|
import * as React from 'react'
|
|
3
12
|
import { EmbedDialog } from '../components/EmbedDialog'
|
|
4
13
|
import { TLUiIconJsx } from '../components/primitives/TldrawUiIcon'
|
|
@@ -19,6 +28,7 @@ export interface TLUiToolItem<
|
|
|
19
28
|
shortcutsLabel?: TranslationKey
|
|
20
29
|
icon: IconType | TLUiIconJsx
|
|
21
30
|
onSelect(source: TLUiEventSource): void
|
|
31
|
+
onDragStart?(source: TLUiEventSource, info: TLPointerEventInfo): void
|
|
22
32
|
/**
|
|
23
33
|
* The keyboard shortcut for this tool. This is a string that can be a single key,
|
|
24
34
|
* or a combination of keys.
|
|
@@ -126,21 +136,27 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
126
136
|
onToolSelect(source, this)
|
|
127
137
|
},
|
|
128
138
|
},
|
|
129
|
-
...[...GeoShapeGeoStyle.values].map((
|
|
130
|
-
id,
|
|
131
|
-
label: `tool.${
|
|
139
|
+
...[...GeoShapeGeoStyle.values].map((geo) => ({
|
|
140
|
+
id: geo,
|
|
141
|
+
label: `tool.${geo}` as TLUiTranslationKey,
|
|
132
142
|
meta: {
|
|
133
|
-
geo
|
|
143
|
+
geo,
|
|
134
144
|
},
|
|
135
|
-
kbd:
|
|
136
|
-
icon: ('geo-' +
|
|
145
|
+
kbd: geo === 'rectangle' ? 'r' : geo === 'ellipse' ? 'o' : undefined,
|
|
146
|
+
icon: ('geo-' + geo) as TLUiIconType,
|
|
137
147
|
onSelect(source: TLUiEventSource) {
|
|
138
148
|
editor.run(() => {
|
|
139
|
-
editor.setStyleForNextShapes(GeoShapeGeoStyle,
|
|
149
|
+
editor.setStyleForNextShapes(GeoShapeGeoStyle, geo)
|
|
140
150
|
editor.setCurrentTool('geo')
|
|
141
|
-
onToolSelect(source, this, `geo-${
|
|
151
|
+
onToolSelect(source, this, `geo-${geo}`)
|
|
142
152
|
})
|
|
143
153
|
},
|
|
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
|
+
},
|
|
144
160
|
})),
|
|
145
161
|
{
|
|
146
162
|
id: 'arrow',
|
|
@@ -151,6 +167,17 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
151
167
|
editor.setCurrentTool('arrow')
|
|
152
168
|
onToolSelect(source, this)
|
|
153
169
|
},
|
|
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
|
+
},
|
|
154
181
|
},
|
|
155
182
|
{
|
|
156
183
|
id: 'line',
|
|
@@ -171,6 +198,12 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
171
198
|
editor.setCurrentTool('frame')
|
|
172
199
|
onToolSelect(source, this)
|
|
173
200
|
},
|
|
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
|
+
},
|
|
174
207
|
},
|
|
175
208
|
{
|
|
176
209
|
id: 'text',
|
|
@@ -181,6 +214,17 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
181
214
|
editor.setCurrentTool('text')
|
|
182
215
|
onToolSelect(source, this)
|
|
183
216
|
},
|
|
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
|
+
},
|
|
184
228
|
},
|
|
185
229
|
{
|
|
186
230
|
id: 'asset',
|
|
@@ -201,6 +245,16 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
201
245
|
editor.setCurrentTool('note')
|
|
202
246
|
onToolSelect(source, this)
|
|
203
247
|
},
|
|
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
|
+
},
|
|
204
258
|
},
|
|
205
259
|
{
|
|
206
260
|
id: 'laser',
|
|
@@ -244,7 +298,7 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
244
298
|
}
|
|
245
299
|
|
|
246
300
|
return tools
|
|
247
|
-
}, [overrides, editor, helpers, onToolSelect])
|
|
301
|
+
}, [overrides, editor, helpers, onToolSelect, trackEvent])
|
|
248
302
|
|
|
249
303
|
return <ToolsContext.Provider value={tools}>{children}</ToolsContext.Provider>
|
|
250
304
|
}
|
|
@@ -259,3 +313,57 @@ export function useTools() {
|
|
|
259
313
|
|
|
260
314
|
return ctx
|
|
261
315
|
}
|
|
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
|
+
}
|
|
@@ -286,6 +286,10 @@ export type TLUiTranslationKey =
|
|
|
286
286
|
| 'a11y.repeat-shape'
|
|
287
287
|
| 'a11y.move-shape'
|
|
288
288
|
| 'a11y.move-shape-faster'
|
|
289
|
+
| 'a11y.rotate-shape-cw'
|
|
290
|
+
| 'a11y.rotate-shape-ccw'
|
|
291
|
+
| 'a11y.rotate-shape-cw-fine'
|
|
292
|
+
| 'a11y.rotate-shape-ccw-fine'
|
|
289
293
|
| 'a11y.enlarge-shape'
|
|
290
294
|
| 'a11y.shrink-shape'
|
|
291
295
|
| 'a11y.pan-camera'
|
|
@@ -287,6 +287,10 @@ export const DEFAULT_TRANSLATION = {
|
|
|
287
287
|
'a11y.repeat-shape': 'Repeat shape',
|
|
288
288
|
'a11y.move-shape': 'Move shape',
|
|
289
289
|
'a11y.move-shape-faster': 'Move shape faster',
|
|
290
|
+
'a11y.rotate-shape-cw': 'Rotate shape clockwise',
|
|
291
|
+
'a11y.rotate-shape-ccw': 'Rotate shape counterclockwise',
|
|
292
|
+
'a11y.rotate-shape-cw-fine': 'Rotate shape clockwise (fine)',
|
|
293
|
+
'a11y.rotate-shape-ccw-fine': 'Rotate shape counterclockwise (fine)',
|
|
290
294
|
'a11y.enlarge-shape': 'Enlarge shape',
|
|
291
295
|
'a11y.shrink-shape': 'Shrink shape',
|
|
292
296
|
'a11y.pan-camera': 'Pan camera',
|
package/src/lib/ui/kbd-utils.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { tlenv } from '@tldraw/editor'
|
|
|
2
2
|
|
|
3
3
|
// N.B. We rework these Windows placeholders down below.
|
|
4
4
|
const cmdKey = tlenv.isDarwin ? '⌘' : '__CTRL__'
|
|
5
|
+
const ctrlKey = tlenv.isDarwin ? '⌃' : '__CTRL__'
|
|
5
6
|
const altKey = tlenv.isDarwin ? '⌥' : '__ALT__'
|
|
6
7
|
|
|
7
8
|
/** @public */
|
|
@@ -19,7 +20,7 @@ export function kbd(str: string) {
|
|
|
19
20
|
? s.replace(/[[\]]/g, '')
|
|
20
21
|
: s
|
|
21
22
|
.replace(/cmd\+/g, cmdKey)
|
|
22
|
-
.replace(/ctrl\+/g,
|
|
23
|
+
.replace(/ctrl\+/g, ctrlKey)
|
|
23
24
|
.replace(/alt\+/g, altKey)
|
|
24
25
|
.replace(/shift\+/g, '⇧')
|
|
25
26
|
// Backwards compatibility with the old system.
|
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.
|
|
4
|
+
export const version = '3.16.0-canary.0e0fb8bde89d'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2024-09-13T14:36:29.063Z',
|
|
7
|
-
minor: '2025-07-
|
|
8
|
-
patch: '2025-07-
|
|
7
|
+
minor: '2025-07-31T14:45:19.563Z',
|
|
8
|
+
patch: '2025-07-31T14:45:19.563Z',
|
|
9
9
|
}
|
|
@@ -149,13 +149,20 @@ exports[`putExcalidrawContent test fixtures bound-arrows.json 1`] = `
|
|
|
149
149
|
"kind": "arc",
|
|
150
150
|
"labelColor": "black",
|
|
151
151
|
"labelPosition": 0.5,
|
|
152
|
+
"richText": {
|
|
153
|
+
"content": [
|
|
154
|
+
{
|
|
155
|
+
"type": "paragraph",
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
"type": "doc",
|
|
159
|
+
},
|
|
152
160
|
"scale": 1,
|
|
153
161
|
"size": "m",
|
|
154
162
|
"start": {
|
|
155
163
|
"x": 0,
|
|
156
164
|
"y": 0,
|
|
157
165
|
},
|
|
158
|
-
"text": "",
|
|
159
166
|
},
|
|
160
167
|
"rotation": 0,
|
|
161
168
|
"type": "arrow",
|
|
@@ -315,13 +322,20 @@ exports[`putExcalidrawContent test fixtures bound-elbow-arrows.json 1`] = `
|
|
|
315
322
|
"kind": "elbow",
|
|
316
323
|
"labelColor": "black",
|
|
317
324
|
"labelPosition": 0.5,
|
|
325
|
+
"richText": {
|
|
326
|
+
"content": [
|
|
327
|
+
{
|
|
328
|
+
"type": "paragraph",
|
|
329
|
+
},
|
|
330
|
+
],
|
|
331
|
+
"type": "doc",
|
|
332
|
+
},
|
|
318
333
|
"scale": 1,
|
|
319
334
|
"size": "m",
|
|
320
335
|
"start": {
|
|
321
336
|
"x": 0,
|
|
322
337
|
"y": 0,
|
|
323
338
|
},
|
|
324
|
-
"text": "",
|
|
325
339
|
},
|
|
326
340
|
"rotation": 0,
|
|
327
341
|
"type": "arrow",
|
|
@@ -112,13 +112,20 @@ exports[`buildFromV1Document test fixtures arrow-binding.tldr 1`] = `
|
|
|
112
112
|
"kind": "arc",
|
|
113
113
|
"labelColor": "red",
|
|
114
114
|
"labelPosition": 0.5,
|
|
115
|
+
"richText": {
|
|
116
|
+
"content": [
|
|
117
|
+
{
|
|
118
|
+
"type": "paragraph",
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
"type": "doc",
|
|
122
|
+
},
|
|
115
123
|
"scale": 1,
|
|
116
124
|
"size": "m",
|
|
117
125
|
"start": {
|
|
118
126
|
"x": 146.32,
|
|
119
127
|
"y": 0,
|
|
120
128
|
},
|
|
121
|
-
"text": "",
|
|
122
129
|
},
|
|
123
130
|
"rotation": 0,
|
|
124
131
|
"type": "arrow",
|
|
@@ -241,13 +248,20 @@ exports[`buildFromV1Document test fixtures exact-arrow-binding.tldr 1`] = `
|
|
|
241
248
|
"kind": "arc",
|
|
242
249
|
"labelColor": "red",
|
|
243
250
|
"labelPosition": 0.5,
|
|
251
|
+
"richText": {
|
|
252
|
+
"content": [
|
|
253
|
+
{
|
|
254
|
+
"type": "paragraph",
|
|
255
|
+
},
|
|
256
|
+
],
|
|
257
|
+
"type": "doc",
|
|
258
|
+
},
|
|
244
259
|
"scale": 1,
|
|
245
260
|
"size": "m",
|
|
246
261
|
"start": {
|
|
247
262
|
"x": 293.36,
|
|
248
263
|
"y": 0,
|
|
249
264
|
},
|
|
250
|
-
"text": "",
|
|
251
265
|
},
|
|
252
266
|
"rotation": 0,
|
|
253
267
|
"type": "arrow",
|
|
@@ -389,13 +403,20 @@ exports[`buildFromV1Document test fixtures incorrect-arrow-binding.tldr 1`] = `
|
|
|
389
403
|
"kind": "arc",
|
|
390
404
|
"labelColor": "red",
|
|
391
405
|
"labelPosition": 0.5,
|
|
406
|
+
"richText": {
|
|
407
|
+
"content": [
|
|
408
|
+
{
|
|
409
|
+
"type": "paragraph",
|
|
410
|
+
},
|
|
411
|
+
],
|
|
412
|
+
"type": "doc",
|
|
413
|
+
},
|
|
392
414
|
"scale": 1,
|
|
393
415
|
"size": "m",
|
|
394
416
|
"start": {
|
|
395
417
|
"x": 252.64,
|
|
396
418
|
"y": 0,
|
|
397
419
|
},
|
|
398
|
-
"text": "",
|
|
399
420
|
},
|
|
400
421
|
"rotation": 0,
|
|
401
422
|
"type": "arrow",
|