tldraw 5.2.0-next.e2b8d10bf10e → 5.2.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 +20294 -0
- package/README.md +13 -1
- package/RELEASE_NOTES.md +2908 -0
- package/dist-cjs/index.d.ts +110 -23
- package/dist-cjs/index.js +3 -3
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/TldrawImage.js +0 -1
- package/dist-cjs/lib/TldrawImage.js.map +2 -2
- package/dist-cjs/lib/bindings/arrow/ArrowBindingUtil.js +2 -1
- package/dist-cjs/lib/bindings/arrow/ArrowBindingUtil.js.map +2 -2
- package/dist-cjs/lib/defaultEmbedDefinitions.js +3 -2
- package/dist-cjs/lib/defaultEmbedDefinitions.js.map +2 -2
- package/dist-cjs/lib/defaultOverlayUtils.js +11 -11
- package/dist-cjs/lib/defaultOverlayUtils.js.map +2 -2
- package/dist-cjs/lib/overlays/SelectionForegroundOverlayUtil.js +76 -64
- package/dist-cjs/lib/overlays/SelectionForegroundOverlayUtil.js.map +2 -2
- package/dist-cjs/lib/overlays/ShapeHandleOverlayUtil.js +1 -1
- package/dist-cjs/lib/overlays/ShapeHandleOverlayUtil.js.map +2 -2
- package/dist-cjs/lib/overlays/ShapeIndicatorOverlayUtil.js +107 -0
- package/dist-cjs/lib/overlays/ShapeIndicatorOverlayUtil.js.map +7 -0
- 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/bookmark/BookmarkShapeUtil.js +12 -3
- package/dist-cjs/lib/shapes/bookmark/BookmarkShapeUtil.js.map +2 -2
- package/dist-cjs/lib/shapes/bookmark/bookmarks.js +54 -6
- package/dist-cjs/lib/shapes/bookmark/bookmarks.js.map +2 -2
- package/dist-cjs/lib/shapes/draw/DrawShapeUtil.js +2 -1
- package/dist-cjs/lib/shapes/draw/DrawShapeUtil.js.map +2 -2
- package/dist-cjs/lib/shapes/draw/getPath.js +1 -1
- package/dist-cjs/lib/shapes/draw/getPath.js.map +2 -2
- package/dist-cjs/lib/shapes/draw/toolStates/Drawing.js +45 -47
- package/dist-cjs/lib/shapes/draw/toolStates/Drawing.js.map +2 -2
- package/dist-cjs/lib/shapes/embed/EmbedShapeUtil.js +2 -1
- package/dist-cjs/lib/shapes/embed/EmbedShapeUtil.js.map +2 -2
- package/dist-cjs/lib/shapes/frame/FrameShapeTool.js +23 -18
- package/dist-cjs/lib/shapes/frame/FrameShapeTool.js.map +2 -2
- package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js +1 -1
- package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js.map +2 -2
- package/dist-cjs/lib/shapes/geo/toolStates/Pointing.js +3 -0
- package/dist-cjs/lib/shapes/geo/toolStates/Pointing.js.map +2 -2
- package/dist-cjs/lib/shapes/highlight/HighlightShapeUtil.js +11 -7
- package/dist-cjs/lib/shapes/highlight/HighlightShapeUtil.js.map +2 -2
- package/dist-cjs/lib/shapes/image/ImageShapeUtil.js +3 -11
- package/dist-cjs/lib/shapes/image/ImageShapeUtil.js.map +2 -2
- package/dist-cjs/lib/shapes/line/toolStates/Pointing.js +5 -1
- package/dist-cjs/lib/shapes/line/toolStates/Pointing.js.map +2 -2
- package/dist-cjs/lib/shapes/note/NoteShapeUtil.js +31 -41
- package/dist-cjs/lib/shapes/note/NoteShapeUtil.js.map +2 -2
- package/dist-cjs/lib/shapes/note/noteHelpers.js +1 -1
- package/dist-cjs/lib/shapes/note/noteHelpers.js.map +2 -2
- package/dist-cjs/lib/shapes/note/toolStates/Pointing.js +28 -6
- package/dist-cjs/lib/shapes/note/toolStates/Pointing.js.map +2 -2
- package/dist-cjs/lib/shapes/shared/PlainTextLabel.js +3 -2
- package/dist-cjs/lib/shapes/shared/PlainTextLabel.js.map +2 -2
- package/dist-cjs/lib/shapes/shared/RichTextLabel.js +3 -3
- package/dist-cjs/lib/shapes/shared/RichTextLabel.js.map +2 -2
- package/dist-cjs/lib/shapes/shared/defaultStyleDefs.js +17 -3
- package/dist-cjs/lib/shapes/shared/defaultStyleDefs.js.map +2 -2
- package/dist-cjs/lib/shapes/shared/freehand/core.js +424 -0
- package/dist-cjs/lib/shapes/shared/freehand/core.js.map +7 -0
- package/dist-cjs/lib/shapes/shared/freehand/fmt.js +106 -0
- package/dist-cjs/lib/shapes/shared/freehand/fmt.js.map +7 -0
- package/dist-cjs/lib/shapes/shared/freehand/getStroke.js +6 -6
- package/dist-cjs/lib/shapes/shared/freehand/getStroke.js.map +2 -2
- package/dist-cjs/lib/shapes/shared/freehand/getStrokeOutlinePoints.js +320 -86
- package/dist-cjs/lib/shapes/shared/freehand/getStrokeOutlinePoints.js.map +3 -3
- package/dist-cjs/lib/shapes/shared/freehand/getStrokePoints.js +20 -96
- package/dist-cjs/lib/shapes/shared/freehand/getStrokePoints.js.map +3 -3
- package/dist-cjs/lib/shapes/shared/freehand/svg.js +55 -19
- package/dist-cjs/lib/shapes/shared/freehand/svg.js.map +2 -2
- package/dist-cjs/lib/shapes/shared/freehand/svgInk.js +189 -97
- package/dist-cjs/lib/shapes/shared/freehand/svgInk.js.map +3 -3
- package/dist-cjs/lib/shapes/shared/freehand/types.js.map +1 -1
- package/dist-cjs/lib/shapes/shared/interpolate-props.js +6 -2
- package/dist-cjs/lib/shapes/shared/interpolate-props.js.map +2 -2
- package/dist-cjs/lib/shapes/text/toolStates/Pointing.js +3 -0
- package/dist-cjs/lib/shapes/text/toolStates/Pointing.js.map +2 -2
- package/dist-cjs/lib/tools/HandTool/HandTool.js +16 -6
- package/dist-cjs/lib/tools/HandTool/HandTool.js.map +2 -2
- package/dist-cjs/lib/tools/HandTool/childStates/Dragging.js +10 -1
- package/dist-cjs/lib/tools/HandTool/childStates/Dragging.js.map +2 -2
- package/dist-cjs/lib/tools/HandTool/childStates/OneFingerZooming.js +84 -0
- package/dist-cjs/lib/tools/HandTool/childStates/OneFingerZooming.js.map +7 -0
- package/dist-cjs/lib/tools/SelectTool/childStates/Idle.js +12 -0
- package/dist-cjs/lib/tools/SelectTool/childStates/Idle.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/PointingShape.js +4 -0
- package/dist-cjs/lib/tools/SelectTool/childStates/PointingShape.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/Resizing.js +107 -46
- package/dist-cjs/lib/tools/SelectTool/childStates/Resizing.js.map +2 -2
- package/dist-cjs/lib/ui/TldrawUi.js +4 -3
- package/dist-cjs/lib/ui/TldrawUi.js.map +2 -2
- package/dist-cjs/lib/ui/components/A11y.js +2 -0
- package/dist-cjs/lib/ui/components/A11y.js.map +2 -2
- package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenu.js +14 -1
- package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenu.js.map +2 -2
- package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenuContent.js +0 -6
- package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenuContent.js.map +2 -2
- package/dist-cjs/lib/ui/components/DefaultFollowingIndicator.js.map +2 -2
- package/dist-cjs/lib/ui/components/Dialogs.js +17 -37
- package/dist-cjs/lib/ui/components/Dialogs.js.map +2 -2
- package/dist-cjs/lib/ui/components/HelperButtons/BackToContent.js +19 -4
- package/dist-cjs/lib/ui/components/HelperButtons/BackToContent.js.map +2 -2
- package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js +12 -2
- package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js.map +2 -2
- package/dist-cjs/lib/ui/components/Minimap/DefaultMinimap.js +47 -70
- package/dist-cjs/lib/ui/components/Minimap/DefaultMinimap.js.map +2 -2
- package/dist-cjs/lib/ui/components/Minimap/MinimapManager.js +25 -46
- package/dist-cjs/lib/ui/components/Minimap/MinimapManager.js.map +2 -2
- package/dist-cjs/lib/ui/components/SharePanel/DefaultPeopleMenuAvatar.js.map +2 -2
- package/dist-cjs/lib/ui/components/SharePanel/DefaultPeopleMenuContent.js.map +2 -2
- package/dist-cjs/lib/ui/components/SharePanel/DefaultPeopleMenuFacePile.js.map +2 -2
- package/dist-cjs/lib/ui/components/SharePanel/DefaultPeopleMenuItem.js +1 -1
- package/dist-cjs/lib/ui/components/SharePanel/DefaultPeopleMenuItem.js.map +2 -2
- package/dist-cjs/lib/ui/components/StylePanel/StylePanelButtonPicker.js +1 -0
- package/dist-cjs/lib/ui/components/StylePanel/StylePanelButtonPicker.js.map +2 -2
- package/dist-cjs/lib/ui/components/menu-items.js +7 -0
- package/dist-cjs/lib/ui/components/menu-items.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/TldrawUiDropdownMenu.js +2 -1
- package/dist-cjs/lib/ui/components/primitives/TldrawUiDropdownMenu.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/TldrawUiIcon.js +14 -3
- package/dist-cjs/lib/ui/components/primitives/TldrawUiIcon.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/TldrawUiInput.js +2 -0
- package/dist-cjs/lib/ui/components/primitives/TldrawUiInput.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/TldrawUiPopover.js +2 -0
- package/dist-cjs/lib/ui/components/primitives/TldrawUiPopover.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.js +0 -2
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuSubmenu.js +1 -2
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuSubmenu.js.map +2 -2
- package/dist-cjs/lib/ui/context/actions.js +70 -1
- package/dist-cjs/lib/ui/context/actions.js.map +2 -2
- package/dist-cjs/lib/ui/context/asset-urls.js +5 -8
- package/dist-cjs/lib/ui/context/asset-urls.js.map +2 -2
- package/dist-cjs/lib/ui/context/events.js.map +2 -2
- package/dist-cjs/lib/ui/hooks/useKeyboardShortcuts.js +13 -0
- package/dist-cjs/lib/ui/hooks/useKeyboardShortcuts.js.map +2 -2
- package/dist-cjs/lib/ui/hooks/useTools.js +1 -1
- 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 +2 -0
- package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.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/embeds/embeds.js +4 -4
- package/dist-cjs/lib/utils/embeds/embeds.js.map +2 -2
- package/dist-cjs/lib/utils/test-helpers.js +2 -2
- package/dist-cjs/lib/utils/test-helpers.js.map +2 -2
- package/dist-cjs/lib/utils/text/richText.js +3 -1
- package/dist-cjs/lib/utils/text/richText.js.map +2 -2
- package/dist-esm/index.d.mts +110 -23
- package/dist-esm/index.mjs +5 -3
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/TldrawImage.mjs +0 -1
- package/dist-esm/lib/TldrawImage.mjs.map +2 -2
- package/dist-esm/lib/bindings/arrow/ArrowBindingUtil.mjs +2 -1
- package/dist-esm/lib/bindings/arrow/ArrowBindingUtil.mjs.map +2 -2
- package/dist-esm/lib/defaultEmbedDefinitions.mjs +3 -2
- package/dist-esm/lib/defaultEmbedDefinitions.mjs.map +2 -2
- package/dist-esm/lib/defaultOverlayUtils.mjs +11 -11
- package/dist-esm/lib/defaultOverlayUtils.mjs.map +2 -2
- package/dist-esm/lib/overlays/SelectionForegroundOverlayUtil.mjs +77 -65
- package/dist-esm/lib/overlays/SelectionForegroundOverlayUtil.mjs.map +2 -2
- package/dist-esm/lib/overlays/ShapeHandleOverlayUtil.mjs +1 -1
- package/dist-esm/lib/overlays/ShapeHandleOverlayUtil.mjs.map +2 -2
- package/dist-esm/lib/overlays/ShapeIndicatorOverlayUtil.mjs +87 -0
- package/dist-esm/lib/overlays/ShapeIndicatorOverlayUtil.mjs.map +7 -0
- package/dist-esm/lib/shapes/arrow/toolStates/Pointing.mjs +9 -1
- package/dist-esm/lib/shapes/arrow/toolStates/Pointing.mjs.map +2 -2
- package/dist-esm/lib/shapes/bookmark/BookmarkShapeUtil.mjs +15 -3
- package/dist-esm/lib/shapes/bookmark/BookmarkShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/shapes/bookmark/bookmarks.mjs +54 -6
- package/dist-esm/lib/shapes/bookmark/bookmarks.mjs.map +2 -2
- package/dist-esm/lib/shapes/draw/DrawShapeUtil.mjs +3 -1
- package/dist-esm/lib/shapes/draw/DrawShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/shapes/draw/getPath.mjs +1 -1
- package/dist-esm/lib/shapes/draw/getPath.mjs.map +2 -2
- package/dist-esm/lib/shapes/draw/toolStates/Drawing.mjs +46 -47
- package/dist-esm/lib/shapes/draw/toolStates/Drawing.mjs.map +2 -2
- package/dist-esm/lib/shapes/embed/EmbedShapeUtil.mjs +2 -1
- package/dist-esm/lib/shapes/embed/EmbedShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/shapes/frame/FrameShapeTool.mjs +23 -18
- package/dist-esm/lib/shapes/frame/FrameShapeTool.mjs.map +2 -2
- package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs +2 -1
- package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/shapes/geo/toolStates/Pointing.mjs +4 -0
- package/dist-esm/lib/shapes/geo/toolStates/Pointing.mjs.map +2 -2
- package/dist-esm/lib/shapes/highlight/HighlightShapeUtil.mjs +12 -7
- package/dist-esm/lib/shapes/highlight/HighlightShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs +3 -11
- package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/shapes/line/toolStates/Pointing.mjs +6 -1
- package/dist-esm/lib/shapes/line/toolStates/Pointing.mjs.map +2 -2
- package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs +32 -42
- package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/shapes/note/noteHelpers.mjs +1 -1
- package/dist-esm/lib/shapes/note/noteHelpers.mjs.map +2 -2
- package/dist-esm/lib/shapes/note/toolStates/Pointing.mjs +29 -6
- package/dist-esm/lib/shapes/note/toolStates/Pointing.mjs.map +2 -2
- package/dist-esm/lib/shapes/shared/PlainTextLabel.mjs +3 -2
- package/dist-esm/lib/shapes/shared/PlainTextLabel.mjs.map +2 -2
- package/dist-esm/lib/shapes/shared/RichTextLabel.mjs +4 -3
- package/dist-esm/lib/shapes/shared/RichTextLabel.mjs.map +2 -2
- package/dist-esm/lib/shapes/shared/defaultStyleDefs.mjs +17 -3
- package/dist-esm/lib/shapes/shared/defaultStyleDefs.mjs.map +2 -2
- package/dist-esm/lib/shapes/shared/freehand/core.mjs +404 -0
- package/dist-esm/lib/shapes/shared/freehand/core.mjs.map +7 -0
- package/dist-esm/lib/shapes/shared/freehand/fmt.mjs +86 -0
- package/dist-esm/lib/shapes/shared/freehand/fmt.mjs.map +7 -0
- package/dist-esm/lib/shapes/shared/freehand/getStroke.mjs +7 -7
- package/dist-esm/lib/shapes/shared/freehand/getStroke.mjs.map +2 -2
- package/dist-esm/lib/shapes/shared/freehand/getStrokeOutlinePoints.mjs +332 -86
- package/dist-esm/lib/shapes/shared/freehand/getStrokeOutlinePoints.mjs.map +3 -3
- package/dist-esm/lib/shapes/shared/freehand/getStrokePoints.mjs +31 -96
- package/dist-esm/lib/shapes/shared/freehand/getStrokePoints.mjs.map +3 -3
- package/dist-esm/lib/shapes/shared/freehand/svg.mjs +55 -19
- package/dist-esm/lib/shapes/shared/freehand/svg.mjs.map +2 -2
- package/dist-esm/lib/shapes/shared/freehand/svgInk.mjs +214 -99
- package/dist-esm/lib/shapes/shared/freehand/svgInk.mjs.map +3 -3
- package/dist-esm/lib/shapes/shared/interpolate-props.mjs +6 -2
- package/dist-esm/lib/shapes/shared/interpolate-props.mjs.map +2 -2
- package/dist-esm/lib/shapes/text/toolStates/Pointing.mjs +4 -0
- package/dist-esm/lib/shapes/text/toolStates/Pointing.mjs.map +2 -2
- package/dist-esm/lib/tools/HandTool/HandTool.mjs +16 -6
- package/dist-esm/lib/tools/HandTool/HandTool.mjs.map +2 -2
- package/dist-esm/lib/tools/HandTool/childStates/Dragging.mjs +10 -1
- package/dist-esm/lib/tools/HandTool/childStates/Dragging.mjs.map +2 -2
- package/dist-esm/lib/tools/HandTool/childStates/OneFingerZooming.mjs +64 -0
- package/dist-esm/lib/tools/HandTool/childStates/OneFingerZooming.mjs.map +7 -0
- package/dist-esm/lib/tools/SelectTool/childStates/Idle.mjs +12 -0
- package/dist-esm/lib/tools/SelectTool/childStates/Idle.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/PointingShape.mjs +4 -0
- package/dist-esm/lib/tools/SelectTool/childStates/PointingShape.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/Resizing.mjs +108 -46
- package/dist-esm/lib/tools/SelectTool/childStates/Resizing.mjs.map +2 -2
- package/dist-esm/lib/ui/TldrawUi.mjs +4 -3
- package/dist-esm/lib/ui/TldrawUi.mjs.map +2 -2
- package/dist-esm/lib/ui/components/A11y.mjs +2 -0
- package/dist-esm/lib/ui/components/A11y.mjs.map +2 -2
- package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenu.mjs +21 -2
- package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenu.mjs.map +2 -2
- package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenuContent.mjs +0 -6
- package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenuContent.mjs.map +2 -2
- package/dist-esm/lib/ui/components/DefaultFollowingIndicator.mjs.map +2 -2
- package/dist-esm/lib/ui/components/Dialogs.mjs +19 -39
- package/dist-esm/lib/ui/components/Dialogs.mjs.map +2 -2
- package/dist-esm/lib/ui/components/HelperButtons/BackToContent.mjs +20 -5
- package/dist-esm/lib/ui/components/HelperButtons/BackToContent.mjs.map +2 -2
- package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs +12 -2
- package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs.map +2 -2
- package/dist-esm/lib/ui/components/Minimap/DefaultMinimap.mjs +47 -70
- package/dist-esm/lib/ui/components/Minimap/DefaultMinimap.mjs.map +2 -2
- package/dist-esm/lib/ui/components/Minimap/MinimapManager.mjs +25 -46
- package/dist-esm/lib/ui/components/Minimap/MinimapManager.mjs.map +2 -2
- package/dist-esm/lib/ui/components/SharePanel/DefaultPeopleMenuAvatar.mjs.map +2 -2
- package/dist-esm/lib/ui/components/SharePanel/DefaultPeopleMenuContent.mjs.map +2 -2
- package/dist-esm/lib/ui/components/SharePanel/DefaultPeopleMenuFacePile.mjs.map +2 -2
- package/dist-esm/lib/ui/components/SharePanel/DefaultPeopleMenuItem.mjs +1 -1
- package/dist-esm/lib/ui/components/SharePanel/DefaultPeopleMenuItem.mjs.map +2 -2
- package/dist-esm/lib/ui/components/StylePanel/StylePanelButtonPicker.mjs +1 -0
- package/dist-esm/lib/ui/components/StylePanel/StylePanelButtonPicker.mjs.map +2 -2
- package/dist-esm/lib/ui/components/menu-items.mjs +7 -0
- package/dist-esm/lib/ui/components/menu-items.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/TldrawUiDropdownMenu.mjs +2 -1
- package/dist-esm/lib/ui/components/primitives/TldrawUiDropdownMenu.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/TldrawUiIcon.mjs +14 -3
- package/dist-esm/lib/ui/components/primitives/TldrawUiIcon.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/TldrawUiInput.mjs +2 -0
- package/dist-esm/lib/ui/components/primitives/TldrawUiInput.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/TldrawUiPopover.mjs +2 -0
- package/dist-esm/lib/ui/components/primitives/TldrawUiPopover.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.mjs +0 -2
- package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuSubmenu.mjs +1 -2
- package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuSubmenu.mjs.map +2 -2
- package/dist-esm/lib/ui/context/actions.mjs +71 -1
- package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
- package/dist-esm/lib/ui/context/asset-urls.mjs +5 -8
- package/dist-esm/lib/ui/context/asset-urls.mjs.map +2 -2
- package/dist-esm/lib/ui/context/events.mjs.map +2 -2
- package/dist-esm/lib/ui/hooks/useKeyboardShortcuts.mjs +13 -0
- package/dist-esm/lib/ui/hooks/useKeyboardShortcuts.mjs.map +2 -2
- package/dist-esm/lib/ui/hooks/useTools.mjs +1 -1
- package/dist-esm/lib/ui/hooks/useTools.mjs.map +2 -2
- package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs +2 -0
- package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.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/embeds/embeds.mjs +4 -4
- package/dist-esm/lib/utils/embeds/embeds.mjs.map +2 -2
- package/dist-esm/lib/utils/test-helpers.mjs +2 -2
- package/dist-esm/lib/utils/test-helpers.mjs.map +2 -2
- package/dist-esm/lib/utils/text/richText.mjs +3 -1
- package/dist-esm/lib/utils/text/richText.mjs.map +2 -2
- package/package.json +11 -6
- package/src/index.ts +6 -1
- package/src/lib/TldrawImage.tsx +2 -2
- package/src/lib/bindings/arrow/ArrowBindingUtil.ts +5 -1
- package/src/lib/defaultEmbedDefinitions.ts +28 -6
- package/src/lib/defaultOverlayUtils.ts +11 -11
- package/src/lib/overlays/SelectionForegroundOverlayUtil.ts +91 -72
- package/src/lib/overlays/ShapeHandleOverlayUtil.ts +3 -1
- package/src/lib/overlays/ShapeIndicatorOverlayUtil.ts +133 -0
- package/src/lib/shapes/arrow/toolStates/Pointing.tsx +11 -1
- package/src/lib/shapes/bookmark/BookmarkShapeUtil.tsx +16 -3
- package/src/lib/shapes/bookmark/bookmarks.ts +105 -15
- package/src/lib/shapes/draw/DrawShapeUtil.tsx +7 -5
- package/src/lib/shapes/draw/getPath.ts +1 -1
- package/src/lib/shapes/draw/toolStates/Drawing.ts +55 -48
- package/src/lib/shapes/embed/EmbedShapeUtil.tsx +14 -4
- package/src/lib/shapes/frame/FrameShapeTool.ts +35 -24
- package/src/lib/shapes/geo/GeoShapeUtil.test.tsx +46 -0
- package/src/lib/shapes/geo/GeoShapeUtil.tsx +2 -1
- package/src/lib/shapes/geo/toolStates/Pointing.ts +5 -0
- package/src/lib/shapes/highlight/HighlightShapeUtil.tsx +14 -11
- package/src/lib/shapes/image/ImageShapeUtil.tsx +7 -11
- package/src/lib/shapes/line/LineShapeTool.test.ts +41 -0
- package/src/lib/shapes/line/toolStates/Pointing.ts +12 -2
- package/src/lib/shapes/note/NoteShapeUtil.tsx +36 -51
- package/src/lib/shapes/note/noteCloning.test.ts +5 -5
- package/src/lib/shapes/note/noteHelpers.ts +1 -1
- package/src/lib/shapes/note/toolStates/Pointing.ts +40 -7
- package/src/lib/shapes/shared/PlainTextLabel.tsx +3 -3
- package/src/lib/shapes/shared/RichTextLabel.tsx +4 -3
- package/src/lib/shapes/shared/defaultStyleDefs.tsx +25 -3
- package/src/lib/shapes/shared/freehand/core.ts +547 -0
- package/src/lib/shapes/shared/freehand/fmt.ts +106 -0
- package/src/lib/shapes/shared/freehand/getStroke.ts +7 -7
- package/src/lib/shapes/shared/freehand/getStrokeOutlinePoints.ts +451 -139
- package/src/lib/shapes/shared/freehand/getStrokePoints.ts +33 -153
- package/src/lib/shapes/shared/freehand/svg.ts +72 -23
- package/src/lib/shapes/shared/freehand/svgInk.ts +281 -136
- package/src/lib/shapes/shared/freehand/types.ts +0 -1
- package/src/lib/shapes/shared/interpolate-props.ts +6 -2
- package/src/lib/shapes/text/toolStates/Pointing.ts +5 -0
- package/src/lib/tools/HandTool/HandTool.ts +19 -6
- package/src/lib/tools/HandTool/childStates/Dragging.ts +17 -1
- package/src/lib/tools/HandTool/childStates/OneFingerZooming.ts +79 -0
- package/src/lib/tools/SelectTool/childStates/Idle.ts +16 -2
- package/src/lib/tools/SelectTool/childStates/PointingShape.ts +8 -0
- package/src/lib/tools/SelectTool/childStates/Resizing.ts +137 -49
- package/src/lib/ui/TldrawUi.tsx +3 -3
- package/src/lib/ui/components/A11y.tsx +2 -0
- package/src/lib/ui/components/ContextMenu/DefaultContextMenu.tsx +32 -2
- package/src/lib/ui/components/ContextMenu/DefaultContextMenuContent.tsx +5 -6
- package/src/lib/ui/components/DefaultFollowingIndicator.tsx +2 -2
- package/src/lib/ui/components/Dialogs.tsx +20 -23
- package/src/lib/ui/components/HelperButtons/BackToContent.tsx +32 -4
- package/src/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.tsx +10 -0
- package/src/lib/ui/components/Minimap/DefaultMinimap.tsx +60 -74
- package/src/lib/ui/components/Minimap/MinimapManager.ts +35 -57
- package/src/lib/ui/components/SharePanel/DefaultPeopleMenuAvatar.tsx +2 -2
- package/src/lib/ui/components/SharePanel/DefaultPeopleMenuContent.tsx +2 -1
- package/src/lib/ui/components/SharePanel/DefaultPeopleMenuFacePile.tsx +2 -1
- package/src/lib/ui/components/SharePanel/DefaultPeopleMenuItem.tsx +3 -3
- package/src/lib/ui/components/StylePanel/StylePanelButtonPicker.tsx +5 -0
- package/src/lib/ui/components/menu-items.tsx +9 -0
- package/src/lib/ui/components/primitives/TldrawUiDropdownMenu.tsx +3 -1
- package/src/lib/ui/components/primitives/TldrawUiIcon.tsx +15 -3
- package/src/lib/ui/components/primitives/TldrawUiInput.tsx +4 -0
- package/src/lib/ui/components/primitives/TldrawUiPopover.tsx +7 -0
- package/src/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.tsx +0 -2
- package/src/lib/ui/components/primitives/menus/TldrawUiMenuSubmenu.tsx +0 -1
- package/src/lib/ui/context/actions.tsx +88 -1
- package/src/lib/ui/context/asset-urls.tsx +9 -8
- package/src/lib/ui/context/events.tsx +2 -0
- package/src/lib/ui/hooks/useKeyboardShortcuts.ts +45 -7
- package/src/lib/ui/hooks/useTools.tsx +7 -1
- package/src/lib/ui/hooks/useTranslation/TLUiTranslationKey.ts +2 -0
- package/src/lib/ui/hooks/useTranslation/defaultTranslation.ts +2 -0
- package/src/lib/ui/version.ts +3 -3
- package/src/lib/ui.css +20 -9
- package/src/lib/utils/embeds/embeds.test.ts +14 -12
- package/src/lib/utils/embeds/embeds.ts +10 -4
- package/src/lib/utils/test-helpers.ts +3 -3
- package/src/lib/utils/text/richText.test.ts +36 -0
- package/src/lib/utils/text/richText.ts +9 -1
- package/src/test/HandTool.test.ts +93 -0
- package/src/test/ImageShapeUtil.test.ts +88 -0
- package/src/test/SelectTool.test.ts +102 -1
- package/src/test/__snapshots__/drawing.test.ts.snap +2 -1
- package/src/test/attribution.test.ts +40 -29
- package/src/test/bookmark-shapes.test.ts +256 -28
- package/src/test/commands/__snapshots__/getSvgString.test.ts.snap +2 -2
- package/src/test/commands/clipboardCallbacks.test.ts +3 -1
- package/src/test/commands/getSvgString.test.ts +30 -0
- package/src/test/commands/penmode.test.ts +97 -1
- package/src/test/commands/setCamera.test.ts +12 -0
- package/src/test/dragFromToolbar.test.ts +67 -0
- package/src/test/drawing.test.ts +48 -7
- package/src/test/frames.test.ts +72 -0
- package/src/test/freehand/corpus/presets.ts +71 -0
- package/src/test/freehand/corpus/real.ts +370 -0
- package/src/test/freehand/corpus/strokes.ts +50 -0
- package/src/test/freehand/svg.test.ts +20 -0
- package/src/test/freehand/wellformed.test.ts +30 -0
- package/src/test/long-press-create.test.ts +102 -0
- package/src/test/overlays/CollaboratorBrushOverlayUtil.test.ts +4 -4
- package/src/test/overlays/CollaboratorCursorOverlayUtil.test.ts +18 -18
- package/src/test/overlays/CollaboratorHintOverlayUtil.test.ts +7 -7
- package/src/test/overlays/CollaboratorScribbleOverlayUtil.test.ts +3 -3
- package/src/test/overlays/OverlayManager.test.ts +1 -1
- package/src/test/overlays/SelectionForegroundOverlayUtil.test.ts +58 -0
- package/src/test/overlays/ShapeHandleOverlayUtil.test.ts +2 -2
- package/src/test/overlays/ShapeIndicatorOverlayUtil.test.ts +36 -0
- package/src/test/resizing.test.ts +250 -0
- package/src/test/right-click-context-menu.test.ts +90 -0
- package/src/test/rightClickPanning.test.ts +11 -0
- package/src/test/selection-omnibus.test.ts +15 -2
- package/src/test/spatialIndex.test.ts +132 -0
- package/src/test/translating.test.ts +0 -6
- package/src/test/ui/ContextMenu.test.tsx +146 -3
- package/src/test/ui/Dialogs.test.tsx +256 -0
- package/src/test/ui/copyHoveredStyles.test.tsx +154 -0
- package/src/test/ui/keyboardShortcuts.test.tsx +266 -0
- package/tldraw.css +35 -12
- package/dist-cjs/lib/shapes/shared/freehand/setStrokePointRadii.js +0 -100
- package/dist-cjs/lib/shapes/shared/freehand/setStrokePointRadii.js.map +0 -7
- package/dist-esm/lib/shapes/shared/freehand/setStrokePointRadii.mjs +0 -80
- package/dist-esm/lib/shapes/shared/freehand/setStrokePointRadii.mjs.map +0 -7
- package/src/lib/shapes/shared/freehand/setStrokePointRadii.ts +0 -127
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { fireEvent, screen } from '@testing-library/react'
|
|
2
|
-
import { createShapeId } from '@tldraw/editor'
|
|
1
|
+
import { act, fireEvent, screen } from '@testing-library/react'
|
|
2
|
+
import { createShapeId, tlenvReactive } from '@tldraw/editor'
|
|
3
3
|
import { TLComponents, Tldraw } from '../../lib/Tldraw'
|
|
4
4
|
import { DefaultContextMenu } from '../../lib/ui/components/ContextMenu/DefaultContextMenu'
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
renderTldrawComponent,
|
|
7
|
+
renderTldrawComponentWithEditor,
|
|
8
|
+
} from '../testutils/renderTldrawComponent'
|
|
6
9
|
|
|
7
10
|
it('opens on right-click', async () => {
|
|
8
11
|
await renderTldrawComponent(
|
|
@@ -23,6 +26,146 @@ it('opens on right-click', async () => {
|
|
|
23
26
|
expect(screen.queryByTestId('context-menu')).toBeNull()
|
|
24
27
|
})
|
|
25
28
|
|
|
29
|
+
// A touch long-press (coarse pointer) opens the menu only in the select tool. In any
|
|
30
|
+
// other tool the long-press belongs to that tool's gesture, so the menu stays closed.
|
|
31
|
+
// The instance's isCoarsePointer is synced from tlenv, so flip that to simulate touch.
|
|
32
|
+
describe('on a coarse pointer (touch long-press)', () => {
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
tlenvReactive.update((prev) => ({ ...prev, isCoarsePointer: true }))
|
|
35
|
+
})
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
tlenvReactive.update((prev) => ({ ...prev, isCoarsePointer: false }))
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('opens the menu in the select tool', async () => {
|
|
41
|
+
await renderTldrawComponent(
|
|
42
|
+
<Tldraw
|
|
43
|
+
onMount={(editor) => {
|
|
44
|
+
editor.createShape({ id: createShapeId(), type: 'geo' })
|
|
45
|
+
}}
|
|
46
|
+
/>,
|
|
47
|
+
{ waitForPatterns: false }
|
|
48
|
+
)
|
|
49
|
+
const canvas = await screen.findByTestId('canvas')
|
|
50
|
+
|
|
51
|
+
fireEvent.contextMenu(canvas)
|
|
52
|
+
await screen.findByTestId('context-menu')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it.each([
|
|
56
|
+
'geo',
|
|
57
|
+
'note',
|
|
58
|
+
'line',
|
|
59
|
+
'text',
|
|
60
|
+
'arrow',
|
|
61
|
+
'frame',
|
|
62
|
+
'eraser',
|
|
63
|
+
'draw',
|
|
64
|
+
'highlight',
|
|
65
|
+
'laser',
|
|
66
|
+
'hand',
|
|
67
|
+
'zoom',
|
|
68
|
+
])('does not open the menu in the %s tool', async (tool) => {
|
|
69
|
+
await renderTldrawComponent(
|
|
70
|
+
<Tldraw
|
|
71
|
+
onMount={(editor) => {
|
|
72
|
+
editor.createShape({ id: createShapeId(), type: 'geo' })
|
|
73
|
+
editor.setCurrentTool(tool)
|
|
74
|
+
}}
|
|
75
|
+
/>,
|
|
76
|
+
{ waitForPatterns: false }
|
|
77
|
+
)
|
|
78
|
+
const canvas = await screen.findByTestId('canvas')
|
|
79
|
+
|
|
80
|
+
fireEvent.contextMenu(canvas)
|
|
81
|
+
expect(screen.queryByTestId('context-menu')).toBeNull()
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
// A right-click (fine pointer) opens the menu in any tool — the editor routes the
|
|
86
|
+
// click through the select tool. Regression guard: gating on the tool instead of the
|
|
87
|
+
// pointer type used to suppress this because the switch-to-select lagged the render.
|
|
88
|
+
it.each(['select', 'geo', 'note', 'eraser', 'draw'])(
|
|
89
|
+
'opens on right-click in the %s tool',
|
|
90
|
+
async (tool) => {
|
|
91
|
+
await renderTldrawComponent(
|
|
92
|
+
<Tldraw
|
|
93
|
+
onMount={(editor) => {
|
|
94
|
+
editor.createShape({ id: createShapeId(), type: 'geo' })
|
|
95
|
+
editor.setCurrentTool(tool)
|
|
96
|
+
}}
|
|
97
|
+
/>,
|
|
98
|
+
{ waitForPatterns: false }
|
|
99
|
+
)
|
|
100
|
+
const canvas = await screen.findByTestId('canvas')
|
|
101
|
+
|
|
102
|
+
fireEvent.contextMenu(canvas)
|
|
103
|
+
await screen.findByTestId('context-menu')
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
it('right-click in a shape tool reveals shape actions (rightClickPanning off)', async () => {
|
|
108
|
+
// Regression for #8277: a right-click while a shape-creation tool is active
|
|
109
|
+
// should switch to the select tool and select the shape under the pointer
|
|
110
|
+
// *before* the menu opens, so the menu carries the full set of shape actions.
|
|
111
|
+
// With rightClickPanning off the native contextmenu fires on press, so the
|
|
112
|
+
// switch must complete synchronously on the pointer-down — this asserts the
|
|
113
|
+
// switch beats the menu.
|
|
114
|
+
const id = createShapeId()
|
|
115
|
+
const { editor } = await renderTldrawComponentWithEditor(
|
|
116
|
+
(onMount) => (
|
|
117
|
+
<Tldraw
|
|
118
|
+
options={{ rightClickPanning: false }}
|
|
119
|
+
onMount={(editor) => {
|
|
120
|
+
editor.createShape({
|
|
121
|
+
id,
|
|
122
|
+
type: 'geo',
|
|
123
|
+
x: 0,
|
|
124
|
+
y: 0,
|
|
125
|
+
props: { w: 100, h: 100, geo: 'rectangle' },
|
|
126
|
+
})
|
|
127
|
+
editor.setCurrentTool('geo')
|
|
128
|
+
onMount(editor)
|
|
129
|
+
}}
|
|
130
|
+
/>
|
|
131
|
+
),
|
|
132
|
+
{ waitForPatterns: false }
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
const canvas = await screen.findByTestId('canvas')
|
|
136
|
+
|
|
137
|
+
// right-click over the shape: the button-2 pointer-down dispatches a
|
|
138
|
+
// `right_click`, which the geo tool's idle routes through the select tool.
|
|
139
|
+
act(() => {
|
|
140
|
+
editor.dispatch({
|
|
141
|
+
type: 'pointer',
|
|
142
|
+
name: 'pointer_down',
|
|
143
|
+
target: 'canvas',
|
|
144
|
+
point: { x: 50, y: 50, z: 0.5 },
|
|
145
|
+
pointerId: 1,
|
|
146
|
+
button: 2,
|
|
147
|
+
isPen: false,
|
|
148
|
+
shiftKey: false,
|
|
149
|
+
altKey: false,
|
|
150
|
+
ctrlKey: false,
|
|
151
|
+
metaKey: false,
|
|
152
|
+
accelKey: false,
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
// the switch beats the menu: select is active and the shape is selected
|
|
157
|
+
// before the contextmenu event is ever handled
|
|
158
|
+
expect(editor.getCurrentToolId()).toBe('select')
|
|
159
|
+
expect(editor.getSelectedShapeIds()).toEqual([id])
|
|
160
|
+
|
|
161
|
+
fireEvent.contextMenu(canvas)
|
|
162
|
+
await screen.findByTestId('context-menu')
|
|
163
|
+
// shape actions are present because the shape is selected
|
|
164
|
+
await screen.findByTestId('context-menu.cut')
|
|
165
|
+
await screen.findByTestId('context-menu.copy')
|
|
166
|
+
await screen.findByTestId('context-menu.delete')
|
|
167
|
+
})
|
|
168
|
+
|
|
26
169
|
it('tunnels context menu', async () => {
|
|
27
170
|
const components: TLComponents = {
|
|
28
171
|
ContextMenu: (props) => {
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { fireEvent, screen } from '@testing-library/react'
|
|
2
|
+
import { Select as _Select } from 'radix-ui'
|
|
3
|
+
import { TLComponents, Tldraw } from '../../lib/Tldraw'
|
|
4
|
+
import { useDialogs } from '../../lib/ui/context/dialogs'
|
|
5
|
+
import { renderTldrawComponent } from '../testutils/renderTldrawComponent'
|
|
6
|
+
|
|
7
|
+
// jsdom doesn't implement scrollIntoView, which Radix Select calls when it opens.
|
|
8
|
+
Element.prototype.scrollIntoView ??= () => {}
|
|
9
|
+
|
|
10
|
+
function DialogWithSelect({ onClose }: { onClose(): void }) {
|
|
11
|
+
return (
|
|
12
|
+
<div data-testid="dialog-inner">
|
|
13
|
+
<_Select.Root defaultValue="a">
|
|
14
|
+
<_Select.Trigger data-testid="select-trigger">
|
|
15
|
+
<_Select.Value />
|
|
16
|
+
</_Select.Trigger>
|
|
17
|
+
<_Select.Portal>
|
|
18
|
+
<_Select.Content data-testid="select-content">
|
|
19
|
+
<_Select.Viewport>
|
|
20
|
+
<_Select.Item value="a" data-testid="select-a">
|
|
21
|
+
<_Select.ItemText>A</_Select.ItemText>
|
|
22
|
+
</_Select.Item>
|
|
23
|
+
<_Select.Item value="b" data-testid="select-b">
|
|
24
|
+
<_Select.ItemText>B</_Select.ItemText>
|
|
25
|
+
</_Select.Item>
|
|
26
|
+
</_Select.Viewport>
|
|
27
|
+
</_Select.Content>
|
|
28
|
+
</_Select.Portal>
|
|
29
|
+
</_Select.Root>
|
|
30
|
+
<button data-testid="dialog-close" onClick={onClose}>
|
|
31
|
+
Close
|
|
32
|
+
</button>
|
|
33
|
+
</div>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function OpenDialogButton({ preventBackgroundClose }: { preventBackgroundClose?: boolean }) {
|
|
38
|
+
const { addDialog } = useDialogs()
|
|
39
|
+
return (
|
|
40
|
+
<button
|
|
41
|
+
data-testid="open-dialog"
|
|
42
|
+
onClick={() => addDialog({ component: DialogWithSelect, preventBackgroundClose })}
|
|
43
|
+
>
|
|
44
|
+
open
|
|
45
|
+
</button>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// A confirm-style dialog opened from inside another dialog (e.g. the dotcom workspace
|
|
50
|
+
// settings dialog's regenerate/leave/delete confirmations).
|
|
51
|
+
function NestedConfirmDialog({ onClose }: { onClose(): void }) {
|
|
52
|
+
return (
|
|
53
|
+
<div data-testid="nested-dialog">
|
|
54
|
+
<button data-testid="nested-confirm" onClick={onClose}>
|
|
55
|
+
Confirm
|
|
56
|
+
</button>
|
|
57
|
+
</div>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function DialogThatOpensNested({ onClose }: { onClose(): void }) {
|
|
62
|
+
const { addDialog } = useDialogs()
|
|
63
|
+
return (
|
|
64
|
+
<div data-testid="parent-dialog">
|
|
65
|
+
<button
|
|
66
|
+
data-testid="open-nested"
|
|
67
|
+
onClick={() => addDialog({ component: NestedConfirmDialog })}
|
|
68
|
+
>
|
|
69
|
+
open nested
|
|
70
|
+
</button>
|
|
71
|
+
<button data-testid="parent-close" onClick={onClose}>
|
|
72
|
+
Close
|
|
73
|
+
</button>
|
|
74
|
+
</div>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function OpenParentDialogButton() {
|
|
79
|
+
const { addDialog } = useDialogs()
|
|
80
|
+
return (
|
|
81
|
+
<button
|
|
82
|
+
data-testid="open-parent"
|
|
83
|
+
onClick={() => addDialog({ component: DialogThatOpensNested })}
|
|
84
|
+
>
|
|
85
|
+
open parent
|
|
86
|
+
</button>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function renderWithParentDialog() {
|
|
91
|
+
const components: TLComponents = {
|
|
92
|
+
SharePanel: () => <OpenParentDialogButton />,
|
|
93
|
+
}
|
|
94
|
+
return renderTldrawComponent(<Tldraw components={components} />, { waitForPatterns: false })
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function renderWithDialog(opts: { preventBackgroundClose?: boolean } = {}) {
|
|
98
|
+
const components: TLComponents = {
|
|
99
|
+
SharePanel: () => <OpenDialogButton preventBackgroundClose={opts.preventBackgroundClose} />,
|
|
100
|
+
}
|
|
101
|
+
return renderTldrawComponent(<Tldraw components={components} />, { waitForPatterns: false })
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// A full press on the dialog backdrop. Radix dismisses on pointer-down, but we fire the
|
|
105
|
+
// whole sequence so a layer-blind click handler (the bug this guards against) would be caught.
|
|
106
|
+
function pressBackdrop() {
|
|
107
|
+
const el = document.querySelector('.tlui-dialog__positioner')
|
|
108
|
+
if (!el) throw new Error('dialog positioner (backdrop) not found')
|
|
109
|
+
fireEvent.pointerDown(el, { pointerType: 'mouse' })
|
|
110
|
+
fireEvent.pointerUp(el, { pointerType: 'mouse' })
|
|
111
|
+
fireEvent.click(el)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Press the backdrop of the topmost dialog (the last positioner in the DOM) — used when
|
|
115
|
+
// dialogs are stacked, so the press lands on the layer that should dismiss first.
|
|
116
|
+
function pressTopBackdrop() {
|
|
117
|
+
const positioners = document.querySelectorAll('.tlui-dialog__positioner')
|
|
118
|
+
const el = positioners[positioners.length - 1]
|
|
119
|
+
if (!el) throw new Error('dialog positioner (backdrop) not found')
|
|
120
|
+
fireEvent.pointerDown(el, { pointerType: 'mouse' })
|
|
121
|
+
fireEvent.pointerUp(el, { pointerType: 'mouse' })
|
|
122
|
+
fireEvent.click(el)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
it('dismisses a select opened inside a modal before the modal itself', async () => {
|
|
126
|
+
await renderWithDialog()
|
|
127
|
+
|
|
128
|
+
fireEvent.click(screen.getByTestId('open-dialog'))
|
|
129
|
+
await screen.findByTestId('dialog-inner')
|
|
130
|
+
|
|
131
|
+
// Open the select.
|
|
132
|
+
fireEvent.pointerDown(screen.getByTestId('select-trigger'), { button: 0, pointerType: 'mouse' })
|
|
133
|
+
await screen.findByTestId('select-content')
|
|
134
|
+
|
|
135
|
+
// A press on the dialog backdrop closes only the select; the dialog stays open.
|
|
136
|
+
pressBackdrop()
|
|
137
|
+
expect(screen.queryByTestId('select-content')).toBeNull()
|
|
138
|
+
expect(screen.queryByTestId('dialog-inner')).not.toBeNull()
|
|
139
|
+
|
|
140
|
+
// A second press on the backdrop then closes the dialog.
|
|
141
|
+
pressBackdrop()
|
|
142
|
+
expect(screen.queryByTestId('dialog-inner')).toBeNull()
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('dismisses a normal dialog on a backdrop press', async () => {
|
|
146
|
+
await renderWithDialog()
|
|
147
|
+
|
|
148
|
+
fireEvent.click(screen.getByTestId('open-dialog'))
|
|
149
|
+
await screen.findByTestId('dialog-inner')
|
|
150
|
+
|
|
151
|
+
pressBackdrop()
|
|
152
|
+
expect(screen.queryByTestId('dialog-inner')).toBeNull()
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('does not dismiss when a press starts inside the content and ends on the backdrop', async () => {
|
|
156
|
+
await renderWithDialog()
|
|
157
|
+
|
|
158
|
+
fireEvent.click(screen.getByTestId('open-dialog'))
|
|
159
|
+
await screen.findByTestId('dialog-inner')
|
|
160
|
+
|
|
161
|
+
// A press that starts inside the content (e.g. selecting text)...
|
|
162
|
+
fireEvent.pointerDown(screen.getByTestId('dialog-inner'), { pointerType: 'mouse' })
|
|
163
|
+
// ...and is released on the backdrop should not dismiss the dialog, since Radix
|
|
164
|
+
// dismisses on pointer-down and the press began inside the content.
|
|
165
|
+
const el = document.querySelector('.tlui-dialog__positioner')!
|
|
166
|
+
fireEvent.pointerUp(el, { pointerType: 'mouse' })
|
|
167
|
+
fireEvent.click(el)
|
|
168
|
+
|
|
169
|
+
expect(screen.queryByTestId('dialog-inner')).not.toBeNull()
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('dismisses a dialog on escape', async () => {
|
|
173
|
+
await renderWithDialog()
|
|
174
|
+
|
|
175
|
+
fireEvent.click(screen.getByTestId('open-dialog'))
|
|
176
|
+
await screen.findByTestId('dialog-inner')
|
|
177
|
+
|
|
178
|
+
fireEvent.keyDown(document.body, { key: 'Escape' })
|
|
179
|
+
expect(screen.queryByTestId('dialog-inner')).toBeNull()
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('escape dismisses one stacked layer at a time: the select first, then the dialog', async () => {
|
|
183
|
+
await renderWithDialog()
|
|
184
|
+
|
|
185
|
+
fireEvent.click(screen.getByTestId('open-dialog'))
|
|
186
|
+
await screen.findByTestId('dialog-inner')
|
|
187
|
+
|
|
188
|
+
fireEvent.pointerDown(screen.getByTestId('select-trigger'), { button: 0, pointerType: 'mouse' })
|
|
189
|
+
await screen.findByTestId('select-content')
|
|
190
|
+
|
|
191
|
+
// The first escape closes only the select (the topmost layer); the dialog stays.
|
|
192
|
+
fireEvent.keyDown(document.body, { key: 'Escape' })
|
|
193
|
+
expect(screen.queryByTestId('select-content')).toBeNull()
|
|
194
|
+
expect(screen.queryByTestId('dialog-inner')).not.toBeNull()
|
|
195
|
+
|
|
196
|
+
// The second escape then closes the dialog.
|
|
197
|
+
fireEvent.keyDown(document.body, { key: 'Escape' })
|
|
198
|
+
expect(screen.queryByTestId('dialog-inner')).toBeNull()
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('keeps a preventBackgroundClose dialog open on a backdrop press but still closes on escape', async () => {
|
|
202
|
+
await renderWithDialog({ preventBackgroundClose: true })
|
|
203
|
+
|
|
204
|
+
fireEvent.click(screen.getByTestId('open-dialog'))
|
|
205
|
+
await screen.findByTestId('dialog-inner')
|
|
206
|
+
|
|
207
|
+
// A backdrop press is ignored...
|
|
208
|
+
pressBackdrop()
|
|
209
|
+
expect(screen.queryByTestId('dialog-inner')).not.toBeNull()
|
|
210
|
+
|
|
211
|
+
// ...but escape still dismisses the dialog.
|
|
212
|
+
fireEvent.keyDown(document.body, { key: 'Escape' })
|
|
213
|
+
expect(screen.queryByTestId('dialog-inner')).toBeNull()
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('backdrop presses dismiss stacked dialogs one layer at a time', async () => {
|
|
217
|
+
await renderWithParentDialog()
|
|
218
|
+
|
|
219
|
+
fireEvent.click(screen.getByTestId('open-parent'))
|
|
220
|
+
await screen.findByTestId('parent-dialog')
|
|
221
|
+
|
|
222
|
+
fireEvent.click(screen.getByTestId('open-nested'))
|
|
223
|
+
await screen.findByTestId('nested-dialog')
|
|
224
|
+
|
|
225
|
+
// Opening the nested dialog (which stacks on top and grabs focus) must not dismiss
|
|
226
|
+
// the parent — the dialogs stack rather than collapsing together.
|
|
227
|
+
expect(screen.queryByTestId('parent-dialog')).not.toBeNull()
|
|
228
|
+
|
|
229
|
+
// A backdrop press closes only the topmost (nested) dialog; the parent stays open.
|
|
230
|
+
pressTopBackdrop()
|
|
231
|
+
expect(screen.queryByTestId('nested-dialog')).toBeNull()
|
|
232
|
+
expect(screen.queryByTestId('parent-dialog')).not.toBeNull()
|
|
233
|
+
|
|
234
|
+
// A second backdrop press then closes the parent.
|
|
235
|
+
pressTopBackdrop()
|
|
236
|
+
expect(screen.queryByTestId('parent-dialog')).toBeNull()
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('escape dismisses a nested dialog before its parent', async () => {
|
|
240
|
+
await renderWithParentDialog()
|
|
241
|
+
|
|
242
|
+
fireEvent.click(screen.getByTestId('open-parent'))
|
|
243
|
+
await screen.findByTestId('parent-dialog')
|
|
244
|
+
|
|
245
|
+
fireEvent.click(screen.getByTestId('open-nested'))
|
|
246
|
+
await screen.findByTestId('nested-dialog')
|
|
247
|
+
|
|
248
|
+
// The first escape closes only the nested dialog; the parent stays.
|
|
249
|
+
fireEvent.keyDown(document.body, { key: 'Escape' })
|
|
250
|
+
expect(screen.queryByTestId('nested-dialog')).toBeNull()
|
|
251
|
+
expect(screen.queryByTestId('parent-dialog')).not.toBeNull()
|
|
252
|
+
|
|
253
|
+
// The second escape then closes the parent.
|
|
254
|
+
fireEvent.keyDown(document.body, { key: 'Escape' })
|
|
255
|
+
expect(screen.queryByTestId('parent-dialog')).toBeNull()
|
|
256
|
+
})
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { act } from '@testing-library/react'
|
|
2
|
+
import { Box, DefaultColorStyle, Editor, createShapeId } from '@tldraw/editor'
|
|
3
|
+
import { useEffect } from 'react'
|
|
4
|
+
import { Tldraw } from '../../lib/Tldraw'
|
|
5
|
+
import { TLUiActionsContextType, useActions } from '../../lib/ui/context/actions'
|
|
6
|
+
import { renderTldrawComponentWithEditor } from '../testutils/renderTldrawComponent'
|
|
7
|
+
|
|
8
|
+
function ActionCapturer({ onCapture }: { onCapture(actions: TLUiActionsContextType): void }) {
|
|
9
|
+
const actions = useActions()
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
onCapture(actions)
|
|
12
|
+
}, [actions, onCapture])
|
|
13
|
+
return null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Move the pointer so the editor's current page point is over a given location. The
|
|
17
|
+
// copy-hovered-styles action hit-tests this point, which also drills into groups.
|
|
18
|
+
function movePointerTo(editor: Editor, x: number, y: number) {
|
|
19
|
+
act(() => {
|
|
20
|
+
editor.dispatch({
|
|
21
|
+
type: 'pointer',
|
|
22
|
+
target: 'canvas',
|
|
23
|
+
name: 'pointer_move',
|
|
24
|
+
point: { x, y },
|
|
25
|
+
pointerId: 1,
|
|
26
|
+
button: 0,
|
|
27
|
+
isPen: false,
|
|
28
|
+
shiftKey: false,
|
|
29
|
+
altKey: false,
|
|
30
|
+
ctrlKey: false,
|
|
31
|
+
metaKey: false,
|
|
32
|
+
accelKey: false,
|
|
33
|
+
})
|
|
34
|
+
editor.emit('tick', 16)
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function setup() {
|
|
39
|
+
let actions: TLUiActionsContextType | null = null
|
|
40
|
+
const { editor } = await renderTldrawComponentWithEditor(
|
|
41
|
+
(onMount) => (
|
|
42
|
+
<Tldraw onMount={onMount}>
|
|
43
|
+
<ActionCapturer onCapture={(a) => (actions = a)} />
|
|
44
|
+
</Tldraw>
|
|
45
|
+
),
|
|
46
|
+
{ waitForPatterns: false }
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
// The action hit-tests rendering shapes, so give the editor a real viewport.
|
|
50
|
+
act(() => {
|
|
51
|
+
editor.updateViewportScreenBounds(new Box(0, 0, 1000, 800))
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return { editor, copyHoveredStyles: actions!['copy-hovered-styles'] }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
describe('copy-hovered-styles action', () => {
|
|
58
|
+
it('copies styles from the shape under the pointer in any tool idle state', async () => {
|
|
59
|
+
const { editor, copyHoveredStyles } = await setup()
|
|
60
|
+
|
|
61
|
+
const redId = createShapeId()
|
|
62
|
+
editor.createShapes([
|
|
63
|
+
{
|
|
64
|
+
id: redId,
|
|
65
|
+
type: 'geo',
|
|
66
|
+
x: 0,
|
|
67
|
+
y: 0,
|
|
68
|
+
props: { w: 100, h: 100, color: 'red', fill: 'solid' },
|
|
69
|
+
},
|
|
70
|
+
])
|
|
71
|
+
|
|
72
|
+
// Point at the red shape.
|
|
73
|
+
movePointerTo(editor, 50, 50)
|
|
74
|
+
|
|
75
|
+
// From the select tool's idle state, the red shape's color is copied to the next shape.
|
|
76
|
+
editor.setStyleForNextShapes(DefaultColorStyle, 'black')
|
|
77
|
+
editor.setCurrentTool('select.idle')
|
|
78
|
+
expect(editor.getPath()).toBe('select.idle')
|
|
79
|
+
await copyHoveredStyles.onSelect('kbd')
|
|
80
|
+
expect(editor.getStyleForNextShape(DefaultColorStyle)).toBe('red')
|
|
81
|
+
|
|
82
|
+
// It also runs from another tool's idle state, like geo.idle.
|
|
83
|
+
editor.setStyleForNextShapes(DefaultColorStyle, 'black')
|
|
84
|
+
editor.setCurrentTool('geo')
|
|
85
|
+
expect(editor.getPath()).toBe('geo.idle')
|
|
86
|
+
await copyHoveredStyles.onSelect('kbd')
|
|
87
|
+
expect(editor.getStyleForNextShape(DefaultColorStyle)).toBe('red')
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('does nothing when the tool is not in an idle state', async () => {
|
|
91
|
+
const { editor, copyHoveredStyles } = await setup()
|
|
92
|
+
|
|
93
|
+
const redId = createShapeId()
|
|
94
|
+
editor.createShapes([
|
|
95
|
+
{
|
|
96
|
+
id: redId,
|
|
97
|
+
type: 'geo',
|
|
98
|
+
x: 0,
|
|
99
|
+
y: 0,
|
|
100
|
+
props: { w: 100, h: 100, color: 'red', fill: 'solid' },
|
|
101
|
+
},
|
|
102
|
+
])
|
|
103
|
+
|
|
104
|
+
movePointerTo(editor, 50, 50)
|
|
105
|
+
|
|
106
|
+
// Brushing is not an idle state, so the action is a no-op.
|
|
107
|
+
editor.setStyleForNextShapes(DefaultColorStyle, 'black')
|
|
108
|
+
editor.setCurrentTool('select.brushing')
|
|
109
|
+
expect(editor.getPath()).toBe('select.brushing')
|
|
110
|
+
await copyHoveredStyles.onSelect('kbd')
|
|
111
|
+
expect(editor.getStyleForNextShape(DefaultColorStyle)).toBe('black')
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('copies styles from the child under the cursor when pointing inside a group', async () => {
|
|
115
|
+
const { editor, copyHoveredStyles } = await setup()
|
|
116
|
+
|
|
117
|
+
const redId = createShapeId()
|
|
118
|
+
const blueId = createShapeId()
|
|
119
|
+
editor.createShapes([
|
|
120
|
+
{
|
|
121
|
+
id: redId,
|
|
122
|
+
type: 'geo',
|
|
123
|
+
x: 0,
|
|
124
|
+
y: 0,
|
|
125
|
+
props: { w: 100, h: 100, color: 'red', fill: 'solid' },
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: blueId,
|
|
129
|
+
type: 'geo',
|
|
130
|
+
x: 200,
|
|
131
|
+
y: 0,
|
|
132
|
+
props: { w: 100, h: 100, color: 'blue', fill: 'solid' },
|
|
133
|
+
},
|
|
134
|
+
])
|
|
135
|
+
editor.setSelectedShapes([redId, blueId])
|
|
136
|
+
editor.groupShapes(editor.getSelectedShapeIds())
|
|
137
|
+
const groupId = editor.getOnlySelectedShapeId()!
|
|
138
|
+
expect(editor.isShapeOfType(editor.getShape(groupId)!, 'group')).toBe(true)
|
|
139
|
+
editor.selectNone()
|
|
140
|
+
editor.setCurrentTool('select.idle')
|
|
141
|
+
|
|
142
|
+
// The whole group is highlighted, but the hit test drills into it: pointing at the
|
|
143
|
+
// red child copies red, and pointing at the blue child copies blue.
|
|
144
|
+
editor.setStyleForNextShapes(DefaultColorStyle, 'black')
|
|
145
|
+
movePointerTo(editor, 50, 50)
|
|
146
|
+
await copyHoveredStyles.onSelect('kbd')
|
|
147
|
+
expect(editor.getStyleForNextShape(DefaultColorStyle)).toBe('red')
|
|
148
|
+
|
|
149
|
+
editor.setStyleForNextShapes(DefaultColorStyle, 'black')
|
|
150
|
+
movePointerTo(editor, 250, 50)
|
|
151
|
+
await copyHoveredStyles.onSelect('kbd')
|
|
152
|
+
expect(editor.getStyleForNextShape(DefaultColorStyle)).toBe('blue')
|
|
153
|
+
})
|
|
154
|
+
})
|