tldraw 4.2.0-next.47462e908ff5 → 4.2.0-next.6aa322101785
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 +21 -4
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/shapes/shared/RichTextLabel.js +1 -1
- package/dist-cjs/lib/shapes/shared/RichTextLabel.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/DraggingHandle.js +14 -6
- package/dist-cjs/lib/tools/SelectTool/childStates/DraggingHandle.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/Idle.js +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/Idle.js.map +2 -2
- package/dist-cjs/lib/ui/components/DebugMenu/DefaultDebugMenuContent.js +10 -7
- package/dist-cjs/lib/ui/components/DebugMenu/DefaultDebugMenuContent.js.map +2 -2
- package/dist-cjs/lib/ui/components/Toolbar/DefaultRichTextToolbar.js +6 -2
- package/dist-cjs/lib/ui/components/Toolbar/DefaultRichTextToolbar.js.map +2 -2
- package/dist-cjs/lib/ui/components/Toolbar/DefaultRichTextToolbarContent.js +2 -1
- package/dist-cjs/lib/ui/components/Toolbar/DefaultRichTextToolbarContent.js.map +2 -2
- package/dist-cjs/lib/ui/components/Toolbar/LinkEditor.js +2 -2
- package/dist-cjs/lib/ui/components/Toolbar/LinkEditor.js.map +2 -2
- package/dist-cjs/lib/ui/context/events.js.map +2 -2
- package/dist-cjs/lib/ui/getLocalFiles.js +18 -3
- package/dist-cjs/lib/ui/getLocalFiles.js.map +2 -2
- package/dist-cjs/lib/ui/hooks/useClipboardEvents.js +18 -16
- package/dist-cjs/lib/ui/hooks/useClipboardEvents.js.map +3 -3
- package/dist-cjs/lib/ui/version.js +3 -3
- package/dist-cjs/lib/ui/version.js.map +1 -1
- package/dist-cjs/lib/utils/text/richText.js +5 -6
- package/dist-cjs/lib/utils/text/richText.js.map +3 -3
- package/dist-esm/index.d.mts +21 -4
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/shapes/shared/RichTextLabel.mjs +2 -1
- package/dist-esm/lib/shapes/shared/RichTextLabel.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/DraggingHandle.mjs +14 -6
- package/dist-esm/lib/tools/SelectTool/childStates/DraggingHandle.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/Idle.mjs +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/Idle.mjs.map +2 -2
- package/dist-esm/lib/ui/components/DebugMenu/DefaultDebugMenuContent.mjs +10 -7
- package/dist-esm/lib/ui/components/DebugMenu/DefaultDebugMenuContent.mjs.map +2 -2
- package/dist-esm/lib/ui/components/Toolbar/DefaultRichTextToolbar.mjs +6 -2
- package/dist-esm/lib/ui/components/Toolbar/DefaultRichTextToolbar.mjs.map +2 -2
- package/dist-esm/lib/ui/components/Toolbar/DefaultRichTextToolbarContent.mjs +2 -1
- package/dist-esm/lib/ui/components/Toolbar/DefaultRichTextToolbarContent.mjs.map +2 -2
- package/dist-esm/lib/ui/components/Toolbar/LinkEditor.mjs +3 -3
- package/dist-esm/lib/ui/components/Toolbar/LinkEditor.mjs.map +2 -2
- package/dist-esm/lib/ui/context/events.mjs.map +2 -2
- package/dist-esm/lib/ui/getLocalFiles.mjs +18 -3
- package/dist-esm/lib/ui/getLocalFiles.mjs.map +2 -2
- package/dist-esm/lib/ui/hooks/useClipboardEvents.mjs +18 -16
- package/dist-esm/lib/ui/hooks/useClipboardEvents.mjs.map +3 -3
- package/dist-esm/lib/ui/version.mjs +3 -3
- package/dist-esm/lib/ui/version.mjs.map +1 -1
- package/dist-esm/lib/utils/text/richText.mjs +5 -6
- package/dist-esm/lib/utils/text/richText.mjs.map +2 -2
- package/package.json +10 -10
- package/src/index.ts +3 -0
- package/src/lib/shapes/shared/RichTextLabel.tsx +2 -1
- package/src/lib/tools/SelectTool/childStates/DraggingHandle.tsx +19 -8
- package/src/lib/tools/SelectTool/childStates/Idle.ts +2 -2
- package/src/lib/ui/components/DebugMenu/DefaultDebugMenuContent.tsx +27 -7
- package/src/lib/ui/components/Toolbar/DefaultRichTextToolbar.tsx +6 -2
- package/src/lib/ui/components/Toolbar/DefaultRichTextToolbarContent.tsx +4 -1
- package/src/lib/ui/components/Toolbar/LinkEditor.tsx +3 -3
- package/src/lib/ui/context/events.tsx +1 -0
- package/src/lib/ui/getLocalFiles.ts +20 -3
- package/src/lib/ui/hooks/useClipboardEvents.ts +12 -9
- package/src/lib/ui/version.ts +3 -3
- package/src/lib/utils/text/richText.ts +5 -5
- package/src/test/customSnapping.test.tsx +185 -0
|
@@ -586,6 +586,8 @@ async function handleClipboardThings(editor: Editor, things: ClipboardThing[], p
|
|
|
586
586
|
* @public
|
|
587
587
|
*/
|
|
588
588
|
const handleNativeOrMenuCopy = async (editor: Editor) => {
|
|
589
|
+
const navigator =
|
|
590
|
+
editor.getContainer().ownerDocument?.defaultView?.navigator ?? globalThis.navigator
|
|
589
591
|
const content = await editor.resolveAssetsInContent(
|
|
590
592
|
editor.getContentFromCurrentPage(editor.getSelectedShapeIds())
|
|
591
593
|
)
|
|
@@ -713,6 +715,7 @@ export function useMenuClipboardEvents() {
|
|
|
713
715
|
/** @public */
|
|
714
716
|
export function useNativeClipboardEvents() {
|
|
715
717
|
const editor = useEditor()
|
|
718
|
+
const ownerDocument = editor.getContainer().ownerDocument
|
|
716
719
|
const trackEvent = useUiEvents()
|
|
717
720
|
|
|
718
721
|
const appIsFocused = useValue('editor.isFocused', () => editor.getInstanceState().isFocused, [
|
|
@@ -817,16 +820,16 @@ export function useNativeClipboardEvents() {
|
|
|
817
820
|
trackEvent('paste', { source: 'kbd' })
|
|
818
821
|
}
|
|
819
822
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
823
|
+
ownerDocument?.addEventListener('copy', copy)
|
|
824
|
+
ownerDocument?.addEventListener('cut', cut)
|
|
825
|
+
ownerDocument?.addEventListener('paste', paste)
|
|
826
|
+
ownerDocument?.addEventListener('pointerup', pointerUpHandler)
|
|
824
827
|
|
|
825
828
|
return () => {
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
829
|
+
ownerDocument?.removeEventListener('copy', copy)
|
|
830
|
+
ownerDocument?.removeEventListener('cut', cut)
|
|
831
|
+
ownerDocument?.removeEventListener('paste', paste)
|
|
832
|
+
ownerDocument?.removeEventListener('pointerup', pointerUpHandler)
|
|
830
833
|
}
|
|
831
|
-
}, [editor, trackEvent, appIsFocused])
|
|
834
|
+
}, [editor, trackEvent, appIsFocused, ownerDocument])
|
|
832
835
|
}
|
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 = '4.2.0-next.
|
|
4
|
+
export const version = '4.2.0-next.6aa322101785'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2025-09-18T14:39:22.803Z',
|
|
7
|
-
minor: '2025-
|
|
8
|
-
patch: '2025-
|
|
7
|
+
minor: '2025-11-05T10:36:19.691Z',
|
|
8
|
+
patch: '2025-11-05T10:36:19.691Z',
|
|
9
9
|
}
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
} from '@tiptap/core'
|
|
9
9
|
import Code from '@tiptap/extension-code'
|
|
10
10
|
import Highlight from '@tiptap/extension-highlight'
|
|
11
|
-
import Link from '@tiptap/extension-link'
|
|
12
11
|
import { Node } from '@tiptap/pm/model'
|
|
13
12
|
import StarterKit from '@tiptap/starter-kit'
|
|
14
13
|
import {
|
|
@@ -35,6 +34,7 @@ export const KeyboardShiftEnterTweakExtension = Extension.create({
|
|
|
35
34
|
|
|
36
35
|
// We change the default Code to override what's in the StarterKit.
|
|
37
36
|
// It allows for other attributes/extensions.
|
|
37
|
+
// @ts-ignore this is fine.
|
|
38
38
|
Code.config.excludes = undefined
|
|
39
39
|
|
|
40
40
|
// We want the highlighting to take precedence over bolding/italics/links
|
|
@@ -52,10 +52,10 @@ export const tipTapDefaultExtensions: Extensions = [
|
|
|
52
52
|
blockquote: false,
|
|
53
53
|
codeBlock: false,
|
|
54
54
|
horizontalRule: false,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
link: {
|
|
56
|
+
openOnClick: false,
|
|
57
|
+
autolink: true,
|
|
58
|
+
},
|
|
59
59
|
}),
|
|
60
60
|
Highlight,
|
|
61
61
|
KeyboardShiftEnterTweakExtension,
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
BaseBoxShapeUtil,
|
|
3
3
|
IndexKey,
|
|
4
4
|
Polyline2d,
|
|
5
|
+
ShapeUtil,
|
|
5
6
|
TLAnyShapeUtilConstructor,
|
|
6
7
|
TLBaseShape,
|
|
7
8
|
TLHandle,
|
|
@@ -541,3 +542,187 @@ describe('custom handle snapping', () => {
|
|
|
541
542
|
})
|
|
542
543
|
})
|
|
543
544
|
})
|
|
545
|
+
|
|
546
|
+
describe('custom adjacent handle for shift snapping', () => {
|
|
547
|
+
type BezierShape = TLBaseShape<
|
|
548
|
+
'bezier',
|
|
549
|
+
{
|
|
550
|
+
start: VecModel
|
|
551
|
+
cp1: VecModel
|
|
552
|
+
cp2: VecModel
|
|
553
|
+
end: VecModel
|
|
554
|
+
}
|
|
555
|
+
>
|
|
556
|
+
|
|
557
|
+
class BezierShapeUtil extends ShapeUtil<BezierShape> {
|
|
558
|
+
static override type = 'bezier'
|
|
559
|
+
override getDefaultProps() {
|
|
560
|
+
return {
|
|
561
|
+
start: { x: 0, y: 0 },
|
|
562
|
+
cp1: { x: 50, y: 0 },
|
|
563
|
+
cp2: { x: 50, y: 100 },
|
|
564
|
+
end: { x: 100, y: 100 },
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
override component() {
|
|
568
|
+
throw new Error('Method not implemented.')
|
|
569
|
+
}
|
|
570
|
+
override indicator() {
|
|
571
|
+
throw new Error('Method not implemented.')
|
|
572
|
+
}
|
|
573
|
+
override getGeometry() {
|
|
574
|
+
return new Polyline2d({ points: [] })
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
override getHandles(shape: BezierShape): TLHandle[] {
|
|
578
|
+
return [
|
|
579
|
+
{
|
|
580
|
+
id: 'start',
|
|
581
|
+
type: 'vertex',
|
|
582
|
+
index: 'a0' as IndexKey,
|
|
583
|
+
x: shape.props.start.x,
|
|
584
|
+
y: shape.props.start.y,
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
id: 'cp1',
|
|
588
|
+
type: 'vertex',
|
|
589
|
+
index: 'a1' as IndexKey,
|
|
590
|
+
x: shape.props.cp1.x,
|
|
591
|
+
y: shape.props.cp1.y,
|
|
592
|
+
snapReferenceHandleId: 'start', // cp1 snaps relative to start
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
id: 'cp2',
|
|
596
|
+
type: 'vertex',
|
|
597
|
+
index: 'a2' as IndexKey,
|
|
598
|
+
x: shape.props.cp2.x,
|
|
599
|
+
y: shape.props.cp2.y,
|
|
600
|
+
snapReferenceHandleId: 'end', // cp2 snaps relative to end
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
id: 'end',
|
|
604
|
+
type: 'vertex',
|
|
605
|
+
index: 'a3' as IndexKey,
|
|
606
|
+
x: shape.props.end.x,
|
|
607
|
+
y: shape.props.end.y,
|
|
608
|
+
},
|
|
609
|
+
]
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
override onHandleDrag(shape: BezierShape, { handle }: TLHandleDragInfo<BezierShape>) {
|
|
613
|
+
return {
|
|
614
|
+
...shape,
|
|
615
|
+
props: {
|
|
616
|
+
...shape.props,
|
|
617
|
+
[handle.id]: { x: handle.x, y: handle.y },
|
|
618
|
+
},
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const shapeUtils = [BezierShapeUtil] as TLAnyShapeUtilConstructor[]
|
|
624
|
+
|
|
625
|
+
let editor: TestEditor
|
|
626
|
+
let ids: Record<string, TLShapeId>
|
|
627
|
+
|
|
628
|
+
beforeEach(() => {
|
|
629
|
+
editor = new TestEditor({ shapeUtils })
|
|
630
|
+
ids = editor.createShapesFromJsx([
|
|
631
|
+
<TL.bezier
|
|
632
|
+
ref="bezier"
|
|
633
|
+
x={0}
|
|
634
|
+
y={0}
|
|
635
|
+
w={100}
|
|
636
|
+
h={100}
|
|
637
|
+
start={{ x: 0, y: 0 }}
|
|
638
|
+
cp1={{ x: 50, y: 0 }}
|
|
639
|
+
cp2={{ x: 50, y: 100 }}
|
|
640
|
+
end={{ x: 100, y: 100 }}
|
|
641
|
+
/>,
|
|
642
|
+
])
|
|
643
|
+
})
|
|
644
|
+
|
|
645
|
+
test('cp1 snaps angle relative to start point when using custom adjacent handle', () => {
|
|
646
|
+
editor.select(ids.bezier)
|
|
647
|
+
const bezier = editor.getShape<BezierShape>(ids.bezier)!
|
|
648
|
+
const cp1Handle = editor.getShapeHandles(bezier)!.find((h) => h.id === 'cp1')!
|
|
649
|
+
|
|
650
|
+
// Start dragging cp1 handle
|
|
651
|
+
editor.pointerDown(cp1Handle.x, cp1Handle.y, {
|
|
652
|
+
target: 'handle',
|
|
653
|
+
shape: bezier,
|
|
654
|
+
handle: cp1Handle,
|
|
655
|
+
})
|
|
656
|
+
|
|
657
|
+
// Move with shift key - should snap angle relative to start (0, 0) not cp2
|
|
658
|
+
editor.pointerMove(60, 20, { shiftKey: true })
|
|
659
|
+
|
|
660
|
+
const updatedBezier = editor.getShape<BezierShape>(ids.bezier)!
|
|
661
|
+
const cp1Pos = updatedBezier.props.cp1
|
|
662
|
+
const startPos = updatedBezier.props.start
|
|
663
|
+
|
|
664
|
+
// The angle from start to cp1 should be snapped to nearest 15 degrees
|
|
665
|
+
const angle = Math.atan2(cp1Pos.y - startPos.y, cp1Pos.x - startPos.x)
|
|
666
|
+
const degrees = (angle * 180) / Math.PI
|
|
667
|
+
|
|
668
|
+
// Should snap to a multiple of 15 degrees (snapAngle uses 24 divisions = 15 degrees)
|
|
669
|
+
const remainder = ((degrees % 15) + 15) % 15
|
|
670
|
+
expect(Math.min(remainder, 15 - remainder)).toBeLessThan(1)
|
|
671
|
+
})
|
|
672
|
+
|
|
673
|
+
test('cp2 snaps angle relative to end point when using custom adjacent handle', () => {
|
|
674
|
+
editor.select(ids.bezier)
|
|
675
|
+
const bezier = editor.getShape<BezierShape>(ids.bezier)!
|
|
676
|
+
const cp2Handle = editor.getShapeHandles(bezier)!.find((h) => h.id === 'cp2')!
|
|
677
|
+
|
|
678
|
+
// Start dragging cp2 handle
|
|
679
|
+
editor.pointerDown(cp2Handle.x, cp2Handle.y, {
|
|
680
|
+
target: 'handle',
|
|
681
|
+
shape: bezier,
|
|
682
|
+
handle: cp2Handle,
|
|
683
|
+
})
|
|
684
|
+
|
|
685
|
+
// Move with shift key - should snap angle relative to end (100, 100)
|
|
686
|
+
editor.pointerMove(80, 80, { shiftKey: true })
|
|
687
|
+
|
|
688
|
+
const updatedBezier = editor.getShape<BezierShape>(ids.bezier)!
|
|
689
|
+
const cp2Pos = updatedBezier.props.cp2
|
|
690
|
+
const endPos = updatedBezier.props.end
|
|
691
|
+
|
|
692
|
+
// The angle from end to cp2 should be snapped to nearest 15 degrees
|
|
693
|
+
const angle = Math.atan2(cp2Pos.y - endPos.y, cp2Pos.x - endPos.x)
|
|
694
|
+
const degrees = (angle * 180) / Math.PI
|
|
695
|
+
|
|
696
|
+
// Should snap to a multiple of 15 degrees
|
|
697
|
+
const remainder = ((degrees % 15) + 15) % 15
|
|
698
|
+
expect(Math.min(remainder, 15 - remainder)).toBeLessThan(1)
|
|
699
|
+
})
|
|
700
|
+
|
|
701
|
+
test('default handles use default adjacent handle logic', () => {
|
|
702
|
+
editor.select(ids.bezier)
|
|
703
|
+
const bezier = editor.getShape<BezierShape>(ids.bezier)!
|
|
704
|
+
const startHandle = editor.getShapeHandles(bezier)!.find((h) => h.id === 'start')!
|
|
705
|
+
|
|
706
|
+
// Start dragging start handle
|
|
707
|
+
editor.pointerDown(startHandle.x, startHandle.y, {
|
|
708
|
+
target: 'handle',
|
|
709
|
+
shape: bezier,
|
|
710
|
+
handle: startHandle,
|
|
711
|
+
})
|
|
712
|
+
|
|
713
|
+
// Move with shift key - should use default logic (next vertex handle = cp1)
|
|
714
|
+
editor.pointerMove(10, 10, { shiftKey: true })
|
|
715
|
+
|
|
716
|
+
const updatedBezier = editor.getShape<BezierShape>(ids.bezier)!
|
|
717
|
+
const startPos = updatedBezier.props.start
|
|
718
|
+
const cp1Pos = updatedBezier.props.cp1
|
|
719
|
+
|
|
720
|
+
// The angle from cp1 to start should be snapped to nearest 15 degrees
|
|
721
|
+
const angle = Math.atan2(startPos.y - cp1Pos.y, startPos.x - cp1Pos.x)
|
|
722
|
+
const degrees = (angle * 180) / Math.PI
|
|
723
|
+
|
|
724
|
+
// Should snap to a multiple of 15 degrees
|
|
725
|
+
const remainder = ((degrees % 15) + 15) % 15
|
|
726
|
+
expect(Math.min(remainder, 15 - remainder)).toBeLessThan(1)
|
|
727
|
+
})
|
|
728
|
+
})
|