tldraw 5.4.0-next.ef99ab47e5ce → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DOCS.md +2826 -3226
- package/RELEASE_NOTES.md +190 -2
- package/dist-cjs/index.d.ts +264 -275
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/defaultExternalContentHandlers.js +32 -16
- package/dist-cjs/lib/defaultExternalContentHandlers.js.map +2 -2
- package/dist-cjs/lib/shapes/arrow/curved-arrow.js +2 -2
- package/dist-cjs/lib/shapes/arrow/curved-arrow.js.map +2 -2
- package/dist-cjs/lib/shapes/arrow/elbow/getElbowArrowInfo.js +2 -2
- package/dist-cjs/lib/shapes/arrow/elbow/getElbowArrowInfo.js.map +2 -2
- package/dist-cjs/lib/shapes/arrow/getArrowInfo.js +1 -1
- package/dist-cjs/lib/shapes/arrow/getArrowInfo.js.map +2 -2
- package/dist-cjs/lib/shapes/arrow/straight-arrow.js +2 -2
- package/dist-cjs/lib/shapes/arrow/straight-arrow.js.map +2 -2
- package/dist-cjs/lib/shapes/arrow/toolStates/Idle.js +2 -3
- package/dist-cjs/lib/shapes/arrow/toolStates/Idle.js.map +2 -2
- package/dist-cjs/lib/shapes/arrow/toolStates/Pointing.js +7 -5
- package/dist-cjs/lib/shapes/arrow/toolStates/Pointing.js.map +2 -2
- package/dist-cjs/lib/shapes/draw/toolStates/Drawing.js +8 -6
- package/dist-cjs/lib/shapes/draw/toolStates/Drawing.js.map +2 -2
- package/dist-cjs/lib/shapes/geo/getGeoShapePath.js +3 -1
- package/dist-cjs/lib/shapes/geo/getGeoShapePath.js.map +2 -2
- package/dist-cjs/lib/shapes/geo/toolStates/Idle.js +2 -3
- package/dist-cjs/lib/shapes/geo/toolStates/Idle.js.map +2 -2
- package/dist-cjs/lib/shapes/geo/toolStates/Pointing.js +1 -1
- package/dist-cjs/lib/shapes/geo/toolStates/Pointing.js.map +2 -2
- package/dist-cjs/lib/shapes/line/toolStates/Pointing.js +2 -6
- package/dist-cjs/lib/shapes/line/toolStates/Pointing.js.map +2 -2
- package/dist-cjs/lib/shapes/note/NoteShapeUtil.js +4 -4
- package/dist-cjs/lib/shapes/note/NoteShapeUtil.js.map +2 -2
- package/dist-cjs/lib/shapes/note/noteHelpers.js +6 -5
- package/dist-cjs/lib/shapes/note/noteHelpers.js.map +2 -2
- package/dist-cjs/lib/shapes/note/toolStates/Pointing.js +1 -1
- package/dist-cjs/lib/shapes/note/toolStates/Pointing.js.map +2 -2
- package/dist-cjs/lib/shapes/shared/crop.js +1 -1
- package/dist-cjs/lib/shapes/shared/crop.js.map +2 -2
- package/dist-cjs/lib/shapes/text/toolStates/Idle.js +1 -1
- package/dist-cjs/lib/shapes/text/toolStates/Idle.js.map +2 -2
- package/dist-cjs/lib/shapes/text/toolStates/Pointing.js +1 -0
- package/dist-cjs/lib/shapes/text/toolStates/Pointing.js.map +2 -2
- package/dist-cjs/lib/shapes/video/VideoShapeUtil.js +3 -2
- package/dist-cjs/lib/shapes/video/VideoShapeUtil.js.map +2 -2
- package/dist-cjs/lib/tools/EraserTool/childStates/Erasing.js +2 -0
- package/dist-cjs/lib/tools/EraserTool/childStates/Erasing.js.map +2 -2
- package/dist-cjs/lib/tools/HandTool/childStates/OneFingerZooming.js +6 -0
- package/dist-cjs/lib/tools/HandTool/childStates/OneFingerZooming.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/SelectTool.js +3 -0
- package/dist-cjs/lib/tools/SelectTool/SelectTool.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/Brushing.js +1 -0
- package/dist-cjs/lib/tools/SelectTool/childStates/Brushing.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/Crop/Crop.js +1 -0
- package/dist-cjs/lib/tools/SelectTool/childStates/Crop/Crop.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/DraggingHandle.js +1 -3
- package/dist-cjs/lib/tools/SelectTool/childStates/DraggingHandle.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/Idle.js +6 -3
- package/dist-cjs/lib/tools/SelectTool/childStates/Idle.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/PointingArrowLabel.js +1 -1
- package/dist-cjs/lib/tools/SelectTool/childStates/PointingArrowLabel.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/PointingCanvas.js +3 -0
- package/dist-cjs/lib/tools/SelectTool/childStates/PointingCanvas.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/PointingHandle.js +1 -0
- package/dist-cjs/lib/tools/SelectTool/childStates/PointingHandle.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/PointingShape.js +1 -1
- package/dist-cjs/lib/tools/SelectTool/childStates/PointingShape.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/Resizing.js +22 -30
- package/dist-cjs/lib/tools/SelectTool/childStates/Resizing.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/ScribbleBrushing.js +1 -3
- package/dist-cjs/lib/tools/SelectTool/childStates/ScribbleBrushing.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/Translating.js +4 -1
- package/dist-cjs/lib/tools/SelectTool/childStates/Translating.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/selectHelpers.js +10 -0
- package/dist-cjs/lib/tools/SelectTool/selectHelpers.js.map +2 -2
- package/dist-cjs/lib/ui/TldrawUi.js +2 -3
- package/dist-cjs/lib/ui/TldrawUi.js.map +2 -2
- package/dist-cjs/lib/ui/components/ActionsMenu/DefaultActionsMenu.js +1 -11
- package/dist-cjs/lib/ui/components/ActionsMenu/DefaultActionsMenu.js.map +2 -2
- package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenu.js +6 -1
- package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenu.js.map +2 -2
- package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenuContent.js +1 -1
- package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenuContent.js.map +2 -2
- package/dist-cjs/lib/ui/components/Dialogs.js +8 -0
- package/dist-cjs/lib/ui/components/Dialogs.js.map +2 -2
- package/dist-cjs/lib/ui/components/EditLinkDialog.js +5 -5
- package/dist-cjs/lib/ui/components/EditLinkDialog.js.map +2 -2
- package/dist-cjs/lib/ui/components/Minimap/DefaultMinimap.js +4 -20
- package/dist-cjs/lib/ui/components/Minimap/DefaultMinimap.js.map +2 -2
- package/dist-cjs/lib/ui/components/SharePanel/DefaultPeopleMenuItem.js +1 -2
- package/dist-cjs/lib/ui/components/SharePanel/DefaultPeopleMenuItem.js.map +2 -2
- package/dist-cjs/lib/ui/components/StylePanel/StylePanelDoubleDropdownPicker.js +2 -0
- package/dist-cjs/lib/ui/components/StylePanel/StylePanelDoubleDropdownPicker.js.map +2 -2
- package/dist-cjs/lib/ui/components/Toolbar/AltTextEditor.js +1 -0
- package/dist-cjs/lib/ui/components/Toolbar/AltTextEditor.js.map +2 -2
- package/dist-cjs/lib/ui/components/Toolbar/DefaultImageToolbarContent.js +10 -8
- package/dist-cjs/lib/ui/components/Toolbar/DefaultImageToolbarContent.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js +20 -1
- package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js.map +2 -2
- package/dist-cjs/lib/ui/context/actions.js +8 -8
- package/dist-cjs/lib/ui/context/actions.js.map +2 -2
- package/dist-cjs/lib/ui/hooks/useClipboardEvents.js +2 -0
- package/dist-cjs/lib/ui/hooks/useClipboardEvents.js.map +2 -2
- package/dist-cjs/lib/ui/hooks/useCommentingEnabled.js +1 -1
- package/dist-cjs/lib/ui/hooks/useCommentingEnabled.js.map +2 -2
- package/dist-cjs/lib/ui/hooks/useFlatten.js +42 -23
- package/dist-cjs/lib/ui/hooks/useFlatten.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 +264 -275
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/defaultExternalContentHandlers.mjs +32 -16
- package/dist-esm/lib/defaultExternalContentHandlers.mjs.map +2 -2
- package/dist-esm/lib/shapes/arrow/curved-arrow.mjs +2 -2
- package/dist-esm/lib/shapes/arrow/curved-arrow.mjs.map +2 -2
- package/dist-esm/lib/shapes/arrow/elbow/getElbowArrowInfo.mjs +2 -2
- package/dist-esm/lib/shapes/arrow/elbow/getElbowArrowInfo.mjs.map +2 -2
- package/dist-esm/lib/shapes/arrow/getArrowInfo.mjs +1 -1
- package/dist-esm/lib/shapes/arrow/getArrowInfo.mjs.map +2 -2
- package/dist-esm/lib/shapes/arrow/straight-arrow.mjs +2 -2
- package/dist-esm/lib/shapes/arrow/straight-arrow.mjs.map +2 -2
- package/dist-esm/lib/shapes/arrow/toolStates/Idle.mjs +3 -4
- package/dist-esm/lib/shapes/arrow/toolStates/Idle.mjs.map +2 -2
- package/dist-esm/lib/shapes/arrow/toolStates/Pointing.mjs +7 -5
- package/dist-esm/lib/shapes/arrow/toolStates/Pointing.mjs.map +2 -2
- package/dist-esm/lib/shapes/draw/toolStates/Drawing.mjs +8 -6
- package/dist-esm/lib/shapes/draw/toolStates/Drawing.mjs.map +2 -2
- package/dist-esm/lib/shapes/geo/getGeoShapePath.mjs +3 -1
- package/dist-esm/lib/shapes/geo/getGeoShapePath.mjs.map +2 -2
- package/dist-esm/lib/shapes/geo/toolStates/Idle.mjs +3 -4
- package/dist-esm/lib/shapes/geo/toolStates/Idle.mjs.map +2 -2
- package/dist-esm/lib/shapes/geo/toolStates/Pointing.mjs +1 -1
- package/dist-esm/lib/shapes/geo/toolStates/Pointing.mjs.map +2 -2
- package/dist-esm/lib/shapes/line/toolStates/Pointing.mjs +2 -7
- package/dist-esm/lib/shapes/line/toolStates/Pointing.mjs.map +2 -2
- package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs +4 -4
- package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/shapes/note/noteHelpers.mjs +6 -5
- package/dist-esm/lib/shapes/note/noteHelpers.mjs.map +2 -2
- package/dist-esm/lib/shapes/note/toolStates/Pointing.mjs +1 -1
- package/dist-esm/lib/shapes/note/toolStates/Pointing.mjs.map +2 -2
- package/dist-esm/lib/shapes/shared/crop.mjs +1 -1
- package/dist-esm/lib/shapes/shared/crop.mjs.map +2 -2
- package/dist-esm/lib/shapes/text/toolStates/Idle.mjs +2 -2
- package/dist-esm/lib/shapes/text/toolStates/Idle.mjs.map +2 -2
- package/dist-esm/lib/shapes/text/toolStates/Pointing.mjs +1 -0
- package/dist-esm/lib/shapes/text/toolStates/Pointing.mjs.map +2 -2
- package/dist-esm/lib/shapes/video/VideoShapeUtil.mjs +3 -2
- package/dist-esm/lib/shapes/video/VideoShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/tools/EraserTool/childStates/Erasing.mjs +2 -0
- package/dist-esm/lib/tools/EraserTool/childStates/Erasing.mjs.map +2 -2
- package/dist-esm/lib/tools/HandTool/childStates/OneFingerZooming.mjs +6 -0
- package/dist-esm/lib/tools/HandTool/childStates/OneFingerZooming.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/SelectTool.mjs +3 -0
- package/dist-esm/lib/tools/SelectTool/SelectTool.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/Brushing.mjs +1 -0
- package/dist-esm/lib/tools/SelectTool/childStates/Brushing.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/Crop/Crop.mjs +1 -0
- package/dist-esm/lib/tools/SelectTool/childStates/Crop/Crop.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/DraggingHandle.mjs +1 -3
- package/dist-esm/lib/tools/SelectTool/childStates/DraggingHandle.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/Idle.mjs +6 -3
- package/dist-esm/lib/tools/SelectTool/childStates/Idle.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/PointingArrowLabel.mjs +1 -1
- package/dist-esm/lib/tools/SelectTool/childStates/PointingArrowLabel.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/PointingCanvas.mjs +3 -0
- package/dist-esm/lib/tools/SelectTool/childStates/PointingCanvas.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/PointingHandle.mjs +5 -1
- package/dist-esm/lib/tools/SelectTool/childStates/PointingHandle.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/PointingShape.mjs +1 -1
- package/dist-esm/lib/tools/SelectTool/childStates/PointingShape.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/Resizing.mjs +22 -31
- package/dist-esm/lib/tools/SelectTool/childStates/Resizing.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/ScribbleBrushing.mjs +1 -3
- package/dist-esm/lib/tools/SelectTool/childStates/ScribbleBrushing.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/Translating.mjs +4 -1
- package/dist-esm/lib/tools/SelectTool/childStates/Translating.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/selectHelpers.mjs +10 -0
- package/dist-esm/lib/tools/SelectTool/selectHelpers.mjs.map +2 -2
- package/dist-esm/lib/ui/TldrawUi.mjs +2 -3
- package/dist-esm/lib/ui/TldrawUi.mjs.map +2 -2
- package/dist-esm/lib/ui/components/ActionsMenu/DefaultActionsMenu.mjs +2 -12
- package/dist-esm/lib/ui/components/ActionsMenu/DefaultActionsMenu.mjs.map +2 -2
- package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenu.mjs +7 -2
- package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenu.mjs.map +2 -2
- package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenuContent.mjs +1 -1
- package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenuContent.mjs.map +2 -2
- package/dist-esm/lib/ui/components/Dialogs.mjs +9 -1
- package/dist-esm/lib/ui/components/Dialogs.mjs.map +2 -2
- package/dist-esm/lib/ui/components/EditLinkDialog.mjs +5 -5
- package/dist-esm/lib/ui/components/EditLinkDialog.mjs.map +2 -2
- package/dist-esm/lib/ui/components/Minimap/DefaultMinimap.mjs +5 -22
- package/dist-esm/lib/ui/components/Minimap/DefaultMinimap.mjs.map +2 -2
- package/dist-esm/lib/ui/components/SharePanel/DefaultPeopleMenuItem.mjs +1 -2
- package/dist-esm/lib/ui/components/SharePanel/DefaultPeopleMenuItem.mjs.map +2 -2
- package/dist-esm/lib/ui/components/StylePanel/StylePanelDoubleDropdownPicker.mjs +2 -0
- package/dist-esm/lib/ui/components/StylePanel/StylePanelDoubleDropdownPicker.mjs.map +2 -2
- package/dist-esm/lib/ui/components/Toolbar/AltTextEditor.mjs +1 -0
- package/dist-esm/lib/ui/components/Toolbar/AltTextEditor.mjs.map +2 -2
- package/dist-esm/lib/ui/components/Toolbar/DefaultImageToolbarContent.mjs +10 -8
- package/dist-esm/lib/ui/components/Toolbar/DefaultImageToolbarContent.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs +20 -1
- package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs.map +2 -2
- package/dist-esm/lib/ui/context/actions.mjs +8 -8
- package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
- package/dist-esm/lib/ui/hooks/useClipboardEvents.mjs +2 -0
- package/dist-esm/lib/ui/hooks/useClipboardEvents.mjs.map +2 -2
- package/dist-esm/lib/ui/hooks/useCommentingEnabled.mjs +2 -2
- package/dist-esm/lib/ui/hooks/useCommentingEnabled.mjs.map +2 -2
- package/dist-esm/lib/ui/hooks/useFlatten.mjs +42 -23
- package/dist-esm/lib/ui/hooks/useFlatten.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 +12 -6
- package/src/lib/defaultExternalContentHandlers.test.ts +43 -0
- package/src/lib/defaultExternalContentHandlers.ts +40 -18
- package/src/lib/shapes/arrow/ArrowShapeTool.test.ts +17 -0
- package/src/lib/shapes/arrow/curved-arrow.ts +2 -2
- package/src/lib/shapes/arrow/elbow/getElbowArrowInfo.tsx +5 -3
- package/src/lib/shapes/arrow/getArrowInfo.ts +2 -1
- package/src/lib/shapes/arrow/straight-arrow.ts +2 -2
- package/src/lib/shapes/arrow/toolStates/Idle.tsx +3 -4
- package/src/lib/shapes/arrow/toolStates/Pointing.tsx +10 -6
- package/src/lib/shapes/draw/DrawShapeTool.test.ts +19 -0
- package/src/lib/shapes/draw/toolStates/Drawing.ts +12 -6
- package/src/lib/shapes/frame/FrameShapeTool.test.ts +17 -0
- package/src/lib/shapes/geo/GeoShapeTool.test.ts +28 -0
- package/src/lib/shapes/geo/getGeoShapePath.ts +3 -1
- package/src/lib/shapes/geo/toolStates/Idle.ts +3 -4
- package/src/lib/shapes/geo/toolStates/Pointing.ts +2 -1
- package/src/lib/shapes/line/LineShapeTool.test.ts +30 -1
- package/src/lib/shapes/line/toolStates/Pointing.ts +6 -7
- package/src/lib/shapes/note/NoteShapeTool.test.ts +45 -0
- package/src/lib/shapes/note/NoteShapeUtil.tsx +7 -4
- package/src/lib/shapes/note/noteHelpers.ts +18 -8
- package/src/lib/shapes/note/toolStates/Pointing.ts +2 -1
- package/src/lib/shapes/shared/crop.ts +7 -1
- package/src/lib/shapes/text/TextShapeTool.test.ts +33 -0
- package/src/lib/shapes/text/toolStates/Idle.ts +2 -2
- package/src/lib/shapes/text/toolStates/Pointing.ts +3 -0
- package/src/lib/shapes/video/VideoShapeUtil.tsx +4 -2
- package/src/lib/tools/EraserTool/childStates/Erasing.ts +5 -0
- package/src/lib/tools/HandTool/childStates/OneFingerZooming.ts +10 -0
- package/src/lib/tools/SelectTool/SelectTool.ts +4 -0
- package/src/lib/tools/SelectTool/childStates/Brushing.ts +4 -0
- package/src/lib/tools/SelectTool/childStates/Crop/Crop.ts +4 -0
- package/src/lib/tools/SelectTool/childStates/DraggingHandle.tsx +3 -4
- package/src/lib/tools/SelectTool/childStates/Idle.ts +10 -3
- package/src/lib/tools/SelectTool/childStates/PointingArrowLabel.ts +1 -1
- package/src/lib/tools/SelectTool/childStates/PointingCanvas.ts +4 -0
- package/src/lib/tools/SelectTool/childStates/PointingHandle.ts +6 -1
- package/src/lib/tools/SelectTool/childStates/PointingShape.ts +4 -1
- package/src/lib/tools/SelectTool/childStates/Resizing.ts +30 -33
- package/src/lib/tools/SelectTool/childStates/ScribbleBrushing.ts +3 -3
- package/src/lib/tools/SelectTool/childStates/Translating.ts +6 -0
- package/src/lib/tools/SelectTool/selectHelpers.ts +21 -0
- package/src/lib/ui/TldrawUi.tsx +1 -2
- package/src/lib/ui/components/ActionsMenu/DefaultActionsMenu.tsx +4 -14
- package/src/lib/ui/components/ContextMenu/DefaultContextMenu.tsx +8 -2
- package/src/lib/ui/components/ContextMenu/DefaultContextMenuContent.tsx +5 -1
- package/src/lib/ui/components/Dialogs.tsx +17 -1
- package/src/lib/ui/components/EditLinkDialog.tsx +8 -7
- package/src/lib/ui/components/Minimap/DefaultMinimap.tsx +5 -23
- package/src/lib/ui/components/SharePanel/DefaultPeopleMenuItem.tsx +2 -3
- package/src/lib/ui/components/StylePanel/StylePanelDoubleDropdownPicker.tsx +2 -0
- package/src/lib/ui/components/Toolbar/AltTextEditor.tsx +1 -0
- package/src/lib/ui/components/Toolbar/DefaultImageToolbarContent.tsx +13 -8
- package/src/lib/ui/components/primitives/TldrawUiSlider.test.tsx +92 -0
- package/src/lib/ui/components/primitives/TldrawUiSlider.tsx +25 -1
- package/src/lib/ui/context/actions.tsx +25 -8
- package/src/lib/ui/hooks/useClipboardEvents.ts +2 -0
- package/src/lib/ui/hooks/useCommentingEnabled.ts +5 -3
- package/src/lib/ui/hooks/useFlatten.ts +56 -23
- package/src/lib/ui/version.ts +3 -3
- package/src/lib/ui.css +7 -16
- package/src/test/ClickManager.test.ts +18 -0
- package/src/test/Editor.test.tsx +3 -0
- package/src/test/EraserTool.test.ts +18 -2
- package/src/test/HandTool.test.ts +21 -0
- package/src/test/LaserTool.test.ts +46 -0
- package/src/test/ScribbleManager.test.ts +19 -0
- package/src/test/SelectTool.test.ts +110 -0
- package/src/test/TldrawEditor.test.tsx +18 -0
- package/src/test/VideoShapeUtil.test.tsx +126 -0
- package/src/test/actions-menu-readonly.test.tsx +37 -0
- package/src/test/bookmark-shapes.test.ts +110 -0
- package/src/test/commands/__snapshots__/packShapes.test.ts.snap +6 -6
- package/src/test/commands/clipboard.test.ts +36 -1
- package/src/test/commands/lockShapes.test.ts +11 -0
- package/src/test/commands/packShapes.test.ts +20 -0
- package/src/test/commands/putContent.test.ts +35 -1
- package/src/test/commands/setCamera.test.ts +69 -0
- package/src/test/commands/stackShapes.test.ts +12 -0
- package/src/test/commands/updateViewportPageBounds.test.ts +8 -0
- package/src/test/cropping.test.ts +162 -0
- package/src/test/customSnapping.test.tsx +32 -0
- package/src/test/defaultHandleExternalFileContent.test.ts +86 -0
- package/src/test/drawing.test.ts +60 -1
- package/src/test/duplicate.test.ts +22 -0
- package/src/test/flatten.test.ts +135 -0
- package/src/test/frames.test.ts +51 -0
- package/src/test/getShapeAtPoint.test.ts +44 -1
- package/src/test/handleDeepLink.test.tsx +15 -0
- package/src/test/minimap-wheel.test.tsx +48 -0
- package/src/test/resizing.test.ts +46 -0
- package/src/test/selection-omnibus.test.ts +50 -0
- package/src/test/spacebarPanning.test.ts +58 -0
- package/src/test/translating.test.ts +42 -0
- package/src/test/ui/AltTextEditor.test.tsx +51 -0
- package/src/test/ui/ContextMenu.test.tsx +61 -2
- package/src/test/ui/EditLinkDialog.test.tsx +77 -0
- package/src/test/ui/ImageToolbar.test.tsx +58 -0
- package/src/test/ui/StylePanel.test.tsx +33 -2
- package/src/test/ui/keyboardShortcuts.test.tsx +27 -0
- package/src/test/ui/presenceActivity.test.tsx +44 -0
- package/src/test/ui/unlockAll.test.tsx +55 -0
- package/src/test/ui-outside-editor.test.tsx +74 -0
- package/src/test/viewport-following.test.ts +29 -1
- package/tldraw.css +7 -16
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 = '5.4.0
|
|
4
|
+
export const version = '5.4.0'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2026-05-06T16:28:18.473Z',
|
|
7
|
-
minor: '2026-
|
|
8
|
-
patch: '2026-
|
|
7
|
+
minor: '2026-09-02T11:04:02.901Z',
|
|
8
|
+
patch: '2026-09-02T11:04:02.901Z',
|
|
9
9
|
}
|
package/src/lib/ui.css
CHANGED
|
@@ -2104,7 +2104,8 @@ tldraw? probably.
|
|
|
2104
2104
|
align-items: center;
|
|
2105
2105
|
}
|
|
2106
2106
|
|
|
2107
|
-
.tlui-people-menu__user__color
|
|
2107
|
+
.tlui-people-menu__user__color,
|
|
2108
|
+
.tlui-people-menu__user__edit {
|
|
2108
2109
|
flex-shrink: 0;
|
|
2109
2110
|
}
|
|
2110
2111
|
|
|
@@ -2150,7 +2151,8 @@ tldraw? probably.
|
|
|
2150
2151
|
}
|
|
2151
2152
|
|
|
2152
2153
|
.tlui-people-menu__item {
|
|
2153
|
-
|
|
2154
|
+
display: flex;
|
|
2155
|
+
align-items: center;
|
|
2154
2156
|
}
|
|
2155
2157
|
|
|
2156
2158
|
.tlui-people-menu__item:last-of-type .tlui-button__menu {
|
|
@@ -2158,7 +2160,8 @@ tldraw? probably.
|
|
|
2158
2160
|
}
|
|
2159
2161
|
|
|
2160
2162
|
.tlui-people-menu__item__button {
|
|
2161
|
-
padding:
|
|
2163
|
+
padding-inline-start: 12px;
|
|
2164
|
+
padding-inline-end: 0px;
|
|
2162
2165
|
overflow: hidden;
|
|
2163
2166
|
}
|
|
2164
2167
|
|
|
@@ -2168,7 +2171,7 @@ tldraw? probably.
|
|
|
2168
2171
|
align-items: auto;
|
|
2169
2172
|
justify-content: flex-start;
|
|
2170
2173
|
flex-grow: 2;
|
|
2171
|
-
gap:
|
|
2174
|
+
gap: 12px;
|
|
2172
2175
|
}
|
|
2173
2176
|
|
|
2174
2177
|
.tlui-people-menu__name {
|
|
@@ -2179,18 +2182,9 @@ tldraw? probably.
|
|
|
2179
2182
|
}
|
|
2180
2183
|
|
|
2181
2184
|
.tlui-people-menu__item__follow {
|
|
2182
|
-
position: absolute;
|
|
2183
|
-
top: 0px;
|
|
2184
|
-
inset-inline-end: 0px;
|
|
2185
|
-
max-width: 40px;
|
|
2186
2185
|
flex-shrink: 0;
|
|
2187
2186
|
}
|
|
2188
2187
|
|
|
2189
|
-
.tlui-people-menu__item[data-follow='true'],
|
|
2190
|
-
.tlui-people-menu__item:has(.tlui-button:focus-visible) {
|
|
2191
|
-
padding-inline-end: 36px;
|
|
2192
|
-
}
|
|
2193
|
-
|
|
2194
2188
|
.tlui-people-menu__item[data-follow='true'] .tlui-people-menu__item__follow,
|
|
2195
2189
|
.tlui-people-menu__item:has(.tlui-button:focus-visible) .tlui-people-menu__item__follow {
|
|
2196
2190
|
opacity: 1;
|
|
@@ -2201,9 +2195,6 @@ tldraw? probably.
|
|
|
2201
2195
|
opacity: 0;
|
|
2202
2196
|
}
|
|
2203
2197
|
|
|
2204
|
-
.tlui-people-menu__item:hover {
|
|
2205
|
-
padding-right: 36px;
|
|
2206
|
-
}
|
|
2207
2198
|
.tlui-people-menu__item:hover .tlui-people-menu__item__follow {
|
|
2208
2199
|
opacity: 1;
|
|
2209
2200
|
}
|
|
@@ -263,6 +263,24 @@ it('Cancels when click moves', () => {
|
|
|
263
263
|
expect(event.name).toBe('pointer_up')
|
|
264
264
|
})
|
|
265
265
|
|
|
266
|
+
it('Does not cancel a double click on a small move when the container is offset', () => {
|
|
267
|
+
// click points are client coordinates; comparing them against the container-relative
|
|
268
|
+
// screen point used to cancel on any movement at all in an offset container
|
|
269
|
+
editor.setScreenBounds({ x: 300, y: 100, w: 1080, h: 720 })
|
|
270
|
+
const events: any[] = []
|
|
271
|
+
editor.addListener('event', (info) => events.push(info))
|
|
272
|
+
|
|
273
|
+
editor.pointerDown(500, 400).pointerMove(501, 400).pointerUp(501, 400).pointerDown(501, 400)
|
|
274
|
+
|
|
275
|
+
expect(events).toMatchObject([
|
|
276
|
+
{ name: 'pointer_down' },
|
|
277
|
+
{ name: 'pointer_move' },
|
|
278
|
+
{ name: 'pointer_up' },
|
|
279
|
+
{ name: 'pointer_down' },
|
|
280
|
+
{ name: 'double_click', type: 'click', phase: 'down' },
|
|
281
|
+
])
|
|
282
|
+
})
|
|
283
|
+
|
|
266
284
|
it('Resets when the focus layer changes', () => {
|
|
267
285
|
const boxId = editor.testShapeID('box')
|
|
268
286
|
const otherBoxId = editor.testShapeID('otherBox')
|
package/src/test/Editor.test.tsx
CHANGED
|
@@ -913,6 +913,9 @@ describe('instance.isReadonly', () => {
|
|
|
913
913
|
expect(editor.getIsReadonly()).toBe(false)
|
|
914
914
|
mode.set('readonly')
|
|
915
915
|
expect(editor.getIsReadonly()).toBe(true)
|
|
916
|
+
|
|
917
|
+
// syncing the mode is not a user edit, so it must not show up in the undo history
|
|
918
|
+
expect(editor.getCanUndo()).toBe(false)
|
|
916
919
|
})
|
|
917
920
|
})
|
|
918
921
|
|
|
@@ -411,6 +411,22 @@ describe('When clicking and dragging', () => {
|
|
|
411
411
|
expect(editor.getShape(ids.box3)).not.toBeDefined()
|
|
412
412
|
})
|
|
413
413
|
|
|
414
|
+
it.each([
|
|
415
|
+
['exactly on the outline', 300],
|
|
416
|
+
['a pixel inside the outline', 301],
|
|
417
|
+
['a pixel outside the outline', 299],
|
|
418
|
+
])('Keeps erasing a frame pressed %s when the drag begins', (_label, x) => {
|
|
419
|
+
editor.setCurrentTool('eraser')
|
|
420
|
+
editor.pointerDown(x, 50) // on frame1's left edge, not its children
|
|
421
|
+
expect(editor.getErasingShapeIds()).toEqual([ids.frame1])
|
|
422
|
+
editor.pointerMove(325, 50) // drag inward, still in the frame, clear of box4
|
|
423
|
+
vi.advanceTimersByTime(16)
|
|
424
|
+
editor.expectToBeIn('eraser.erasing')
|
|
425
|
+
expect(editor.getErasingShapeIds()).toEqual([ids.frame1])
|
|
426
|
+
editor.pointerUp()
|
|
427
|
+
expect(editor.getShape(ids.frame1)).not.toBeDefined()
|
|
428
|
+
})
|
|
429
|
+
|
|
414
430
|
it('Only erases masked shapes when pointer is inside the mask', () => {
|
|
415
431
|
editor.setCurrentTool('eraser')
|
|
416
432
|
editor.pointerMove(425, 0)
|
|
@@ -423,8 +439,8 @@ describe('When clicking and dragging', () => {
|
|
|
423
439
|
editor.pointerUp()
|
|
424
440
|
expect(editor.getShape(ids.box3)).toBeDefined()
|
|
425
441
|
|
|
426
|
-
editor.pointerMove(375,
|
|
427
|
-
editor.pointerDown() //
|
|
442
|
+
editor.pointerMove(375, 20)
|
|
443
|
+
editor.pointerDown() // Inside frame1 (clear of its outline), above the not-masked part of box3
|
|
428
444
|
editor.pointerMove(375, 500) // Through the masked part of box3
|
|
429
445
|
expect(editor.getInstanceState().scribbles.length).toBe(1)
|
|
430
446
|
expect(editor.getErasingShapeIds()).toEqual([ids.box3])
|
|
@@ -39,6 +39,27 @@ describe(HandTool, () => {
|
|
|
39
39
|
expect(editor.getCamera().z).toBeGreaterThan(before)
|
|
40
40
|
})
|
|
41
41
|
|
|
42
|
+
it('Ends one-finger zoom when a second touch starts, instead of jumping the zoom', () => {
|
|
43
|
+
editor.setCurrentTool('hand')
|
|
44
|
+
editor.updateInstanceState({ isCoarsePointer: true })
|
|
45
|
+
|
|
46
|
+
editor.pointerDown(100, 100).pointerUp(100, 100)
|
|
47
|
+
editor.pointerDown(100, 100)
|
|
48
|
+
vi.advanceTimersByTime(editor.options.multiClickDurationMs)
|
|
49
|
+
editor.expectToBeIn('hand.one_finger_zooming')
|
|
50
|
+
|
|
51
|
+
editor.pointerMove(100, 120)
|
|
52
|
+
const zoomBeforeSecondTouch = editor.getCamera().z
|
|
53
|
+
expect(zoomBeforeSecondTouch).toBeGreaterThan(1)
|
|
54
|
+
|
|
55
|
+
// A second finger lands far away and moves; the pan/zoom must yield rather than
|
|
56
|
+
// treat the new pointer as a continuation of the first one.
|
|
57
|
+
editor.pointerDown(300, 400, { pointerId: 2 })
|
|
58
|
+
editor.expectToBeIn('hand.idle')
|
|
59
|
+
editor.pointerMove(300, 420, { pointerId: 2 })
|
|
60
|
+
expect(editor.getCamera().z).toBe(zoomBeforeSecondTouch)
|
|
61
|
+
})
|
|
62
|
+
|
|
42
63
|
it('Zooms in (not one-finger zoom) when a coarse pointer double-taps and releases', () => {
|
|
43
64
|
editor.setCurrentTool('hand')
|
|
44
65
|
editor.updateInstanceState({ isCoarsePointer: true })
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PageRecordType } from '@tldraw/editor'
|
|
1
2
|
import { vi } from 'vitest'
|
|
2
3
|
import { TestEditor } from './TestEditor'
|
|
3
4
|
|
|
@@ -385,6 +386,51 @@ describe('LaserTool', () => {
|
|
|
385
386
|
})
|
|
386
387
|
})
|
|
387
388
|
|
|
389
|
+
describe('Page changes', () => {
|
|
390
|
+
it('clears the trail when the current page changes', () => {
|
|
391
|
+
editor.setCurrentTool('laser')
|
|
392
|
+
|
|
393
|
+
editor.pointerDown(0, 0)
|
|
394
|
+
for (let i = 1; i <= 10; i++) {
|
|
395
|
+
editor.pointerMove(i * 5, i * 5)
|
|
396
|
+
vi.advanceTimersByTime(16)
|
|
397
|
+
}
|
|
398
|
+
editor.pointerUp()
|
|
399
|
+
expect(editor.getInstanceState().scribbles.length).toBeGreaterThan(0)
|
|
400
|
+
|
|
401
|
+
const page2Id = PageRecordType.createId('page2')
|
|
402
|
+
editor.createPage({ id: page2Id, name: 'Page 2' })
|
|
403
|
+
editor.setCurrentPage(page2Id)
|
|
404
|
+
vi.advanceTimersByTime(16)
|
|
405
|
+
|
|
406
|
+
expect(editor.getInstanceState().scribbles).toEqual([])
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
it('starts a fresh session on the new page after a page change', () => {
|
|
410
|
+
editor.setCurrentTool('laser')
|
|
411
|
+
|
|
412
|
+
editor.pointerDown(0, 0)
|
|
413
|
+
editor.pointerMove(10, 10)
|
|
414
|
+
vi.advanceTimersByTime(16)
|
|
415
|
+
editor.pointerUp()
|
|
416
|
+
|
|
417
|
+
const page2Id = PageRecordType.createId('page2')
|
|
418
|
+
editor.createPage({ id: page2Id, name: 'Page 2' })
|
|
419
|
+
editor.setCurrentPage(page2Id)
|
|
420
|
+
vi.advanceTimersByTime(16)
|
|
421
|
+
|
|
422
|
+
editor.pointerDown(0, 50)
|
|
423
|
+
for (let i = 1; i <= 10; i++) {
|
|
424
|
+
editor.pointerMove(i * 5, 50 + i * 5)
|
|
425
|
+
vi.advanceTimersByTime(16)
|
|
426
|
+
}
|
|
427
|
+
editor.pointerUp()
|
|
428
|
+
|
|
429
|
+
// Only the stroke drawn on the new page is visible
|
|
430
|
+
expect(editor.getInstanceState().scribbles.length).toBe(1)
|
|
431
|
+
})
|
|
432
|
+
})
|
|
433
|
+
|
|
388
434
|
describe('Edge cases', () => {
|
|
389
435
|
it('handles rapid pointer up/down', () => {
|
|
390
436
|
editor.setCurrentTool('laser')
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PageRecordType } from '@tldraw/editor'
|
|
1
2
|
import { vi } from 'vitest'
|
|
2
3
|
import { TestEditor } from './TestEditor'
|
|
3
4
|
|
|
@@ -341,6 +342,24 @@ describe('ScribbleManager', () => {
|
|
|
341
342
|
|
|
342
343
|
expect(editor.scribbles.isSessionActive(sessionId)).toBe(false)
|
|
343
344
|
})
|
|
345
|
+
|
|
346
|
+
it('clears sessions started on another page when the current page changes', () => {
|
|
347
|
+
const sessionId = editor.scribbles.startSession({ selfConsume: false })
|
|
348
|
+
const item = editor.scribbles.addScribbleToSession(sessionId, {})
|
|
349
|
+
for (let i = 0; i < 10; i++) {
|
|
350
|
+
editor.scribbles.addPointToSession(sessionId, item.id, i * 5, i * 5)
|
|
351
|
+
editor.scribbles.tick(16)
|
|
352
|
+
}
|
|
353
|
+
expect(editor.getInstanceState().scribbles.length).toBe(1)
|
|
354
|
+
|
|
355
|
+
const page2Id = PageRecordType.createId('page2')
|
|
356
|
+
editor.createPage({ id: page2Id, name: 'Page 2' })
|
|
357
|
+
editor.setCurrentPage(page2Id)
|
|
358
|
+
editor.scribbles.tick(16)
|
|
359
|
+
|
|
360
|
+
expect(editor.getInstanceState().scribbles).toEqual([])
|
|
361
|
+
expect(editor.scribbles.isSessionActive(sessionId)).toBe(false)
|
|
362
|
+
})
|
|
344
363
|
})
|
|
345
364
|
})
|
|
346
365
|
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
ShapeUtil,
|
|
4
4
|
TLArrowShape,
|
|
5
5
|
TLFrameShape,
|
|
6
|
+
TLGeoShape,
|
|
6
7
|
createShapeId,
|
|
7
8
|
toRichText,
|
|
8
9
|
} from '@tldraw/editor'
|
|
@@ -47,6 +48,13 @@ describe('TLSelectTool.Idle', () => {
|
|
|
47
48
|
editor.expectToBeIn('select.pointing_canvas')
|
|
48
49
|
})
|
|
49
50
|
|
|
51
|
+
it('Returns to idle when a canvas press is cancelled', () => {
|
|
52
|
+
editor.pointerDown(10, 10, { target: 'canvas' })
|
|
53
|
+
editor.expectToBeIn('select.pointing_canvas')
|
|
54
|
+
editor.cancel()
|
|
55
|
+
editor.expectToBeIn('select.idle')
|
|
56
|
+
})
|
|
57
|
+
|
|
50
58
|
it('Nudges selected shapes on arrow key down', () => {
|
|
51
59
|
const shape = editor.getShape(ids.box1)!
|
|
52
60
|
editor.select(shape.id)
|
|
@@ -56,6 +64,47 @@ describe('TLSelectTool.Idle', () => {
|
|
|
56
64
|
expect(nudgedShape).toBeDefined()
|
|
57
65
|
expect(nudgedShape?.x).toBe(101)
|
|
58
66
|
})
|
|
67
|
+
|
|
68
|
+
it('Does not nudge selected shapes on arrow key down while spacebar panning', () => {
|
|
69
|
+
const shape = editor.getShape(ids.box1)!
|
|
70
|
+
editor.select(shape.id)
|
|
71
|
+
editor.keyDown(' ')
|
|
72
|
+
expect(editor.inputs.getIsSpacebarPanning()).toBe(true)
|
|
73
|
+
editor.keyDown('ArrowRight')
|
|
74
|
+
editor.keyRepeat('ArrowRight')
|
|
75
|
+
editor.keyUp('ArrowRight')
|
|
76
|
+
editor.keyUp(' ')
|
|
77
|
+
expect(editor.getShape(shape.id)).toMatchObject({ x: 100, y: 100 })
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('Does not nudge selected shapes on arrow key down while alt is held', () => {
|
|
81
|
+
const shape = editor.getShape(ids.box1)!
|
|
82
|
+
editor.select(shape.id)
|
|
83
|
+
editor.keyDown('Alt')
|
|
84
|
+
editor.keyDown('ArrowRight')
|
|
85
|
+
editor.keyRepeat('ArrowRight')
|
|
86
|
+
editor.keyUp('ArrowRight')
|
|
87
|
+
editor.keyUp('Alt')
|
|
88
|
+
expect(editor.getShape(shape.id)).toMatchObject({ x: 100, y: 100 })
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('Makes a shape double click change its own undo step', () => {
|
|
92
|
+
const shape = editor.getShape<TLGeoShape>(ids.box1)!
|
|
93
|
+
expect(shape.props.geo).toBe('rectangle')
|
|
94
|
+
|
|
95
|
+
// Alt+double-click swaps a rectangle to a check-box via GeoShapeUtil.onDoubleClick
|
|
96
|
+
editor.keyDown('Alt')
|
|
97
|
+
editor.doubleClick(150, 150, { target: 'shape', shape }, { altKey: true })
|
|
98
|
+
editor.keyUp('Alt')
|
|
99
|
+
|
|
100
|
+
expect(editor.getShape<TLGeoShape>(ids.box1)!.props.geo).toBe('check-box')
|
|
101
|
+
expect(editor.getOnlySelectedShapeId()).toBe(ids.box1)
|
|
102
|
+
|
|
103
|
+
editor.undo()
|
|
104
|
+
|
|
105
|
+
expect(editor.getShape<TLGeoShape>(ids.box1)!.props.geo).toBe('rectangle')
|
|
106
|
+
expect(editor.getOnlySelectedShapeId()).toBe(ids.box1)
|
|
107
|
+
})
|
|
59
108
|
})
|
|
60
109
|
|
|
61
110
|
// todo: turn on feature flag for these tests or remove them
|
|
@@ -501,6 +550,42 @@ describe('PointingLabel', () => {
|
|
|
501
550
|
editor.expectToBeIn('select.idle')
|
|
502
551
|
})
|
|
503
552
|
|
|
553
|
+
it('Keeps the dragged label position on complete', () => {
|
|
554
|
+
editor.createShapes([
|
|
555
|
+
{
|
|
556
|
+
id: ids.arrow1,
|
|
557
|
+
type: 'arrow',
|
|
558
|
+
x: 100,
|
|
559
|
+
y: 100,
|
|
560
|
+
props: {
|
|
561
|
+
richText: toRichText('Test Label'),
|
|
562
|
+
start: { x: 0, y: 0 },
|
|
563
|
+
end: { x: 100, y: 0 },
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
])
|
|
567
|
+
const shape = editor.getShape<TLArrowShape>(ids.arrow1)!
|
|
568
|
+
const initialLabelPosition = shape.props.labelPosition
|
|
569
|
+
|
|
570
|
+
editor.pointerDown(150, 100, {
|
|
571
|
+
target: 'shape',
|
|
572
|
+
shape,
|
|
573
|
+
})
|
|
574
|
+
editor.pointerMove(160, 100)
|
|
575
|
+
editor.expectToBeIn('select.pointing_arrow_label')
|
|
576
|
+
editor.pointerMove(190, 100)
|
|
577
|
+
|
|
578
|
+
const draggedLabelPosition = editor.getShape<TLArrowShape>(ids.arrow1)!.props.labelPosition
|
|
579
|
+
expect(draggedLabelPosition).not.toBe(initialLabelPosition)
|
|
580
|
+
|
|
581
|
+
// A menu opening or an undo keypress mid-drag completes the interaction
|
|
582
|
+
editor.complete()
|
|
583
|
+
editor.expectToBeIn('select.idle')
|
|
584
|
+
expect(editor.getShape<TLArrowShape>(ids.arrow1)!.props.labelPosition).toBe(
|
|
585
|
+
draggedLabelPosition
|
|
586
|
+
)
|
|
587
|
+
})
|
|
588
|
+
|
|
504
589
|
it('Doesnt go into pointing_arrow_label mode if not selecting the arrow shape', () => {
|
|
505
590
|
editor.createShapes([
|
|
506
591
|
{
|
|
@@ -744,6 +829,31 @@ describe('When double clicking a selection handle that registers as a canvas eve
|
|
|
744
829
|
|
|
745
830
|
expect(overlayEditor.getShape<TLArrowShape>(id)!.props.arrowheadEnd).toBe('arrow')
|
|
746
831
|
})
|
|
832
|
+
|
|
833
|
+
it('Undoes an arrowhead toggled by handle double-click without undoing the selection', () => {
|
|
834
|
+
const id = createShapeId()
|
|
835
|
+
overlayEditor
|
|
836
|
+
.createShapes([
|
|
837
|
+
{
|
|
838
|
+
id,
|
|
839
|
+
type: 'arrow',
|
|
840
|
+
x: 100,
|
|
841
|
+
y: 100,
|
|
842
|
+
props: { start: { x: 0, y: 0 }, end: { x: 100, y: 100 } },
|
|
843
|
+
},
|
|
844
|
+
])
|
|
845
|
+
.selectNone()
|
|
846
|
+
overlayEditor.markHistoryStoppingPoint('before selecting arrow')
|
|
847
|
+
overlayEditor.select(id)
|
|
848
|
+
|
|
849
|
+
overlayEditor.doubleClick(200, 200)
|
|
850
|
+
expect(overlayEditor.getShape<TLArrowShape>(id)!.props.arrowheadEnd).toBe('none')
|
|
851
|
+
|
|
852
|
+
overlayEditor.undo()
|
|
853
|
+
|
|
854
|
+
expect(overlayEditor.getShape<TLArrowShape>(id)!.props.arrowheadEnd).toBe('arrow')
|
|
855
|
+
expect(overlayEditor.getSelectedShapeIds()).toEqual([id])
|
|
856
|
+
})
|
|
747
857
|
})
|
|
748
858
|
|
|
749
859
|
describe('When editing shapes', () => {
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
createTLStore,
|
|
14
14
|
noop,
|
|
15
15
|
react,
|
|
16
|
+
tleditors,
|
|
16
17
|
toRichText,
|
|
17
18
|
} from '@tldraw/editor'
|
|
18
19
|
import { StrictMode } from 'react'
|
|
@@ -95,6 +96,21 @@ describe('<TldrawEditor />', () => {
|
|
|
95
96
|
)
|
|
96
97
|
})
|
|
97
98
|
|
|
99
|
+
it('runs the teardown returned by the store onMount option when unmounted', async () => {
|
|
100
|
+
const teardown = vi.fn()
|
|
101
|
+
const storeOnMount = vi.fn(() => teardown)
|
|
102
|
+
const store = createTLStore({ shapeUtils: [], bindingUtils: [], onMount: storeOnMount })
|
|
103
|
+
const rendered = await renderTldrawComponent(
|
|
104
|
+
<TldrawEditor store={store} tools={defaultTools} initialState="select" />,
|
|
105
|
+
{ waitForPatterns: false }
|
|
106
|
+
)
|
|
107
|
+
expect(storeOnMount).toHaveBeenCalledTimes(1)
|
|
108
|
+
expect(teardown).not.toHaveBeenCalled()
|
|
109
|
+
|
|
110
|
+
act(() => rendered.unmount())
|
|
111
|
+
expect(teardown).toHaveBeenCalledTimes(1)
|
|
112
|
+
})
|
|
113
|
+
|
|
98
114
|
it('throws if the store has different shapes to the ones passed in', async () => {
|
|
99
115
|
const spy = vi.spyOn(console, 'error').mockImplementation(noop)
|
|
100
116
|
// expect(() =>
|
|
@@ -181,6 +197,7 @@ describe('<TldrawEditor />', () => {
|
|
|
181
197
|
|
|
182
198
|
// mounted after render:
|
|
183
199
|
expect(editor.getIsMounted()).toBe(true)
|
|
200
|
+
expect(tleditors.getMounted()).toEqual([editor])
|
|
184
201
|
|
|
185
202
|
// getIsMounted is reactive:
|
|
186
203
|
const mountedValues: boolean[] = []
|
|
@@ -191,6 +208,7 @@ describe('<TldrawEditor />', () => {
|
|
|
191
208
|
// the unmount event fired and the mounted state flipped back to false:
|
|
192
209
|
expect(onUnmount).toHaveBeenCalledTimes(1)
|
|
193
210
|
expect(editor.getIsMounted()).toBe(false)
|
|
211
|
+
expect(tleditors.getMounted()).toEqual([])
|
|
194
212
|
expect(mountedValues).toEqual([true, false])
|
|
195
213
|
|
|
196
214
|
stop()
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { act, fireEvent } from '@testing-library/react'
|
|
2
|
+
import { AssetRecordType, Editor, TLAssetId, TLShapeId, createShapeId } from '@tldraw/editor'
|
|
3
|
+
import { Tldraw } from '../lib/Tldraw'
|
|
4
|
+
import { renderTldrawComponentWithEditor } from './testutils/renderTldrawComponent'
|
|
5
|
+
|
|
6
|
+
const LOADING_BACKGROUND = 'var(--tl-color-low)'
|
|
7
|
+
const LOADING_BORDER = '1px solid var(--tl-color-low-border)'
|
|
8
|
+
|
|
9
|
+
let editor: Editor
|
|
10
|
+
let videoShapeId: TLShapeId
|
|
11
|
+
let videoAssetId: TLAssetId
|
|
12
|
+
|
|
13
|
+
function getShapeContainer() {
|
|
14
|
+
const container = document.getElementById(videoShapeId)
|
|
15
|
+
expect(container).toBeTruthy()
|
|
16
|
+
return container!
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
beforeEach(async () => {
|
|
20
|
+
videoShapeId = createShapeId('video')
|
|
21
|
+
videoAssetId = AssetRecordType.createId('videoAsset')
|
|
22
|
+
|
|
23
|
+
const result = await renderTldrawComponentWithEditor((onMount) => <Tldraw onMount={onMount} />, {
|
|
24
|
+
waitForPatterns: false,
|
|
25
|
+
})
|
|
26
|
+
editor = result.editor
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
describe('VideoShapeUtil loading state', () => {
|
|
30
|
+
it('shows the loading plate when the asset record is missing', async () => {
|
|
31
|
+
await act(async () => {
|
|
32
|
+
editor.createShapes([
|
|
33
|
+
{ id: videoShapeId, type: 'video', x: 0, y: 0, props: { w: 100, h: 100 } },
|
|
34
|
+
])
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const container = getShapeContainer()
|
|
38
|
+
expect(container.style.backgroundColor).toBe(LOADING_BACKGROUND)
|
|
39
|
+
expect(container.style.border).toBe(LOADING_BORDER)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('shows the loading plate while the asset is uploading (no src)', async () => {
|
|
43
|
+
await act(async () => {
|
|
44
|
+
editor.createAssets([
|
|
45
|
+
{
|
|
46
|
+
id: videoAssetId,
|
|
47
|
+
type: 'video',
|
|
48
|
+
typeName: 'asset',
|
|
49
|
+
props: {
|
|
50
|
+
w: 100,
|
|
51
|
+
h: 100,
|
|
52
|
+
name: 'video.mp4',
|
|
53
|
+
isAnimated: true,
|
|
54
|
+
mimeType: 'video/mp4',
|
|
55
|
+
src: '',
|
|
56
|
+
},
|
|
57
|
+
meta: {},
|
|
58
|
+
},
|
|
59
|
+
])
|
|
60
|
+
editor.createShapes([
|
|
61
|
+
{
|
|
62
|
+
id: videoShapeId,
|
|
63
|
+
type: 'video',
|
|
64
|
+
x: 0,
|
|
65
|
+
y: 0,
|
|
66
|
+
props: { w: 100, h: 100, assetId: videoAssetId },
|
|
67
|
+
},
|
|
68
|
+
])
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const container = getShapeContainer()
|
|
72
|
+
expect(container.style.backgroundColor).toBe(LOADING_BACKGROUND)
|
|
73
|
+
expect(container.style.border).toBe(LOADING_BORDER)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('shows the loading plate until the video loads, then goes transparent', async () => {
|
|
77
|
+
await act(async () => {
|
|
78
|
+
editor.createAssets([
|
|
79
|
+
{
|
|
80
|
+
id: videoAssetId,
|
|
81
|
+
type: 'video',
|
|
82
|
+
typeName: 'asset',
|
|
83
|
+
props: {
|
|
84
|
+
w: 100,
|
|
85
|
+
h: 100,
|
|
86
|
+
name: 'video.mp4',
|
|
87
|
+
isAnimated: true,
|
|
88
|
+
mimeType: 'video/mp4',
|
|
89
|
+
src: 'http://localhost/video.mp4',
|
|
90
|
+
},
|
|
91
|
+
meta: {},
|
|
92
|
+
},
|
|
93
|
+
])
|
|
94
|
+
editor.createShapes([
|
|
95
|
+
{
|
|
96
|
+
id: videoShapeId,
|
|
97
|
+
type: 'video',
|
|
98
|
+
x: 0,
|
|
99
|
+
y: 0,
|
|
100
|
+
props: { w: 100, h: 100, assetId: videoAssetId },
|
|
101
|
+
},
|
|
102
|
+
])
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// Wait for the async asset URL resolution so the <video> element renders
|
|
106
|
+
await act(async () => {
|
|
107
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const container = getShapeContainer()
|
|
111
|
+
const video = container.querySelector('video')
|
|
112
|
+
expect(video).toBeTruthy()
|
|
113
|
+
|
|
114
|
+
// The video element exists but hasn't fired loadeddata yet
|
|
115
|
+
expect(container.style.backgroundColor).toBe(LOADING_BACKGROUND)
|
|
116
|
+
expect(container.style.border).toBe(LOADING_BORDER)
|
|
117
|
+
|
|
118
|
+
await act(async () => {
|
|
119
|
+
fireEvent.loadedData(video!)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
expect(container.style.backgroundColor).toBe('transparent')
|
|
123
|
+
// jsdom doesn't round-trip the `border: none` shorthand, so check the style directly
|
|
124
|
+
expect(container.style.borderStyle).toBe('none')
|
|
125
|
+
})
|
|
126
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { act } from '@testing-library/react'
|
|
2
|
+
import { Editor } from '@tldraw/editor'
|
|
3
|
+
import { Tldraw } from '../lib/Tldraw'
|
|
4
|
+
import { renderTldrawComponentWithEditor } from './testutils/renderTldrawComponent'
|
|
5
|
+
|
|
6
|
+
let editor: Editor
|
|
7
|
+
let rendered: Awaited<ReturnType<typeof renderTldrawComponentWithEditor>>['rendered']
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
// Pin the quick actions into the menu panel so the test does not depend on jsdom's viewport width
|
|
11
|
+
;({ editor, rendered } = await renderTldrawComponentWithEditor(
|
|
12
|
+
(onMount) => <Tldraw onMount={onMount} options={{ actionShortcutsLocation: 'menu' }} />,
|
|
13
|
+
{ waitForPatterns: true }
|
|
14
|
+
))
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
editor?.dispose()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
describe('actions menu in readonly mode', () => {
|
|
22
|
+
it('shows the actions menu button when not readonly', async () => {
|
|
23
|
+
expect(await rendered.findByTestId('actions-menu.button')).toBeTruthy()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it.each(['select', 'hand', 'zoom', 'eraser'])(
|
|
27
|
+
'hides the actions menu button in readonly mode with the %s tool',
|
|
28
|
+
async (toolId) => {
|
|
29
|
+
act(() => {
|
|
30
|
+
editor.updateInstanceState({ isReadonly: true })
|
|
31
|
+
editor.setCurrentTool(toolId)
|
|
32
|
+
})
|
|
33
|
+
expect(editor.getCurrentToolId()).toBe(toolId)
|
|
34
|
+
expect(rendered.queryByTestId('actions-menu.button')).toBeNull()
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
})
|