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,6 +1,11 @@
|
|
|
1
|
-
import { TLBookmarkShape, createShapeId } from '@tldraw/editor'
|
|
1
|
+
import { AssetRecordType, TLBookmarkShape, createShapeId, getHashForString } from '@tldraw/editor'
|
|
2
2
|
import { vi } from 'vitest'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createBookmarkFromUrl,
|
|
5
|
+
getBookmarkShapeHeight,
|
|
6
|
+
getHumanReadableAddress,
|
|
7
|
+
getResolvedBookmarkAssetId,
|
|
8
|
+
} from '../lib/shapes/bookmark/bookmarks'
|
|
4
9
|
import { TestEditor } from './TestEditor'
|
|
5
10
|
|
|
6
11
|
let editor: TestEditor
|
|
@@ -135,11 +140,16 @@ describe('The URL formatter', () => {
|
|
|
135
140
|
})
|
|
136
141
|
|
|
137
142
|
describe('createBookmarkFromUrl', () => {
|
|
138
|
-
it('creates a bookmark shape
|
|
143
|
+
it('creates a placeholder bookmark shape immediately and patches the asset once metadata resolves', async () => {
|
|
139
144
|
const url = 'https://example.com'
|
|
140
145
|
const center = { x: 100, y: 200 }
|
|
141
146
|
|
|
142
|
-
//
|
|
147
|
+
// Use a deferred promise so we can observe state before the asset resolves.
|
|
148
|
+
let resolveAsset: (asset: any) => void = () => {}
|
|
149
|
+
const assetPromise = new Promise<any>((resolve) => {
|
|
150
|
+
resolveAsset = resolve
|
|
151
|
+
})
|
|
152
|
+
|
|
143
153
|
const mockAsset = {
|
|
144
154
|
id: 'asset:test-asset-id' as any,
|
|
145
155
|
typeName: 'asset' as const,
|
|
@@ -154,27 +164,32 @@ describe('createBookmarkFromUrl', () => {
|
|
|
154
164
|
meta: {},
|
|
155
165
|
}
|
|
156
166
|
|
|
157
|
-
|
|
158
|
-
vi.spyOn(editor, 'getAssetForExternalContent').mockResolvedValue(mockAsset)
|
|
167
|
+
vi.spyOn(editor, 'getAssetForExternalContent').mockReturnValue(assetPromise)
|
|
159
168
|
|
|
160
169
|
const result = await createBookmarkFromUrl(editor, { url, center })
|
|
161
170
|
|
|
162
171
|
assert(result.ok, 'Failed to create bookmark')
|
|
163
172
|
const shape = result.value
|
|
173
|
+
|
|
174
|
+
// Placeholder should exist before the asset resolves.
|
|
164
175
|
expect(shape.type).toBe('bookmark')
|
|
165
176
|
expect(shape.props.url).toBe(url)
|
|
166
|
-
expect(shape.props.assetId).toBe(
|
|
177
|
+
expect(shape.props.assetId).toBe(null)
|
|
167
178
|
expect(shape.props.w).toBe(300)
|
|
168
179
|
expect(shape.props.h).toBe(320)
|
|
169
|
-
expect(shape.x).toBe(center.x - 150)
|
|
170
|
-
expect(shape.y).toBe(center.y - 160)
|
|
180
|
+
expect(shape.x).toBe(center.x - 150)
|
|
181
|
+
expect(shape.y).toBe(center.y - 160)
|
|
171
182
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
expect(
|
|
175
|
-
|
|
183
|
+
const placeholderInStore = editor.getShape<TLBookmarkShape>(shape.id)
|
|
184
|
+
expect(placeholderInStore?.props.assetId).toBe(null)
|
|
185
|
+
expect(editor.getAsset('asset:test-asset-id' as any)).toBeUndefined()
|
|
186
|
+
|
|
187
|
+
// Resolve the asset and let the microtask queue flush.
|
|
188
|
+
resolveAsset(mockAsset)
|
|
189
|
+
await assetPromise
|
|
176
190
|
|
|
177
|
-
|
|
191
|
+
const hydrated = editor.getShape<TLBookmarkShape>(shape.id)
|
|
192
|
+
expect(hydrated?.props.assetId).toBe('asset:test-asset-id')
|
|
178
193
|
const createdAsset = editor.getAsset('asset:test-asset-id' as any)
|
|
179
194
|
expect(createdAsset).toBeDefined()
|
|
180
195
|
expect(createdAsset?.type).toBe('bookmark')
|
|
@@ -184,7 +199,6 @@ describe('createBookmarkFromUrl', () => {
|
|
|
184
199
|
const url = 'https://example.com'
|
|
185
200
|
const viewportCenter = { x: 500, y: 300 }
|
|
186
201
|
|
|
187
|
-
// Mock getViewportPageBounds to return a known center
|
|
188
202
|
vi.spyOn(editor, 'getViewportPageBounds').mockReturnValue({
|
|
189
203
|
x: 0,
|
|
190
204
|
y: 0,
|
|
@@ -193,7 +207,7 @@ describe('createBookmarkFromUrl', () => {
|
|
|
193
207
|
center: viewportCenter,
|
|
194
208
|
} as any)
|
|
195
209
|
|
|
196
|
-
|
|
210
|
+
vi.spyOn(editor, 'getAssetForExternalContent').mockResolvedValue({
|
|
197
211
|
id: 'asset:test-asset-id' as any,
|
|
198
212
|
typeName: 'asset' as const,
|
|
199
213
|
type: 'bookmark' as const,
|
|
@@ -205,9 +219,7 @@ describe('createBookmarkFromUrl', () => {
|
|
|
205
219
|
favicon: '',
|
|
206
220
|
},
|
|
207
221
|
meta: {},
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
vi.spyOn(editor, 'getAssetForExternalContent').mockResolvedValue(mockAsset)
|
|
222
|
+
})
|
|
211
223
|
|
|
212
224
|
const result = await createBookmarkFromUrl(editor, { url })
|
|
213
225
|
|
|
@@ -217,28 +229,35 @@ describe('createBookmarkFromUrl', () => {
|
|
|
217
229
|
expect(shape.y).toBe(viewportCenter.y - 160)
|
|
218
230
|
})
|
|
219
231
|
|
|
220
|
-
it('
|
|
232
|
+
it('keeps the placeholder bookmark when the asset fetch rejects', async () => {
|
|
221
233
|
const url = 'https://invalid-url.com'
|
|
222
234
|
const center = { x: 100, y: 200 }
|
|
235
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
223
236
|
|
|
224
|
-
// Mock the asset creation to fail
|
|
225
237
|
vi.spyOn(editor, 'getAssetForExternalContent').mockRejectedValue(new Error('Failed to fetch'))
|
|
226
238
|
|
|
227
239
|
const result = await createBookmarkFromUrl(editor, { url, center })
|
|
228
240
|
|
|
229
|
-
assert(
|
|
230
|
-
|
|
241
|
+
assert(result.ok, 'Expected placeholder bookmark to be created even when fetch fails')
|
|
242
|
+
const shape = result.value
|
|
243
|
+
expect(shape.type).toBe('bookmark')
|
|
244
|
+
expect(shape.props.url).toBe(url)
|
|
245
|
+
expect(shape.props.assetId).toBe(null)
|
|
246
|
+
|
|
247
|
+
// Wait for the rejected promise to be observed.
|
|
248
|
+
await Promise.resolve()
|
|
249
|
+
await Promise.resolve()
|
|
250
|
+
|
|
251
|
+
const stillPlaceholder = editor.getShape<TLBookmarkShape>(shape.id)
|
|
252
|
+
expect(stillPlaceholder?.props.assetId).toBe(null)
|
|
231
253
|
|
|
232
|
-
|
|
233
|
-
const shapes = editor.getCurrentPageShapes()
|
|
234
|
-
expect(shapes).toHaveLength(0)
|
|
254
|
+
consoleSpy.mockRestore()
|
|
235
255
|
})
|
|
236
256
|
|
|
237
257
|
it('creates bookmark shape even when asset creation returns null', async () => {
|
|
238
258
|
const url = 'https://example.com'
|
|
239
259
|
const center = { x: 100, y: 200 }
|
|
240
260
|
|
|
241
|
-
// Mock the asset creation to return null
|
|
242
261
|
vi.spyOn(editor, 'getAssetForExternalContent').mockResolvedValue(null as any)
|
|
243
262
|
|
|
244
263
|
const result = await createBookmarkFromUrl(editor, { url, center })
|
|
@@ -249,8 +268,217 @@ describe('createBookmarkFromUrl', () => {
|
|
|
249
268
|
expect(shape.props.url).toBe(url)
|
|
250
269
|
expect(shape.props.assetId).toBe(null)
|
|
251
270
|
|
|
252
|
-
// Verify the shape was created
|
|
253
271
|
const createdShape = editor.getShape(result.value.id)
|
|
254
272
|
expect(createdShape).toBeDefined()
|
|
255
273
|
})
|
|
274
|
+
|
|
275
|
+
it('reuses an existing bookmark asset for the same URL without re-fetching', async () => {
|
|
276
|
+
const url = 'https://example.com'
|
|
277
|
+
const center = { x: 100, y: 200 }
|
|
278
|
+
|
|
279
|
+
let resolveFirst: (asset: any) => void = () => {}
|
|
280
|
+
const firstPromise = new Promise<any>((resolve) => {
|
|
281
|
+
resolveFirst = resolve
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
const fetchSpy = vi.spyOn(editor, 'getAssetForExternalContent')
|
|
285
|
+
fetchSpy.mockReturnValueOnce(firstPromise)
|
|
286
|
+
|
|
287
|
+
const first = await createBookmarkFromUrl(editor, { url, center })
|
|
288
|
+
assert(first.ok)
|
|
289
|
+
|
|
290
|
+
resolveFirst({
|
|
291
|
+
id: AssetRecordType.createId(getHashForString(url)),
|
|
292
|
+
typeName: 'asset',
|
|
293
|
+
type: 'bookmark',
|
|
294
|
+
props: {
|
|
295
|
+
src: url,
|
|
296
|
+
title: 'Example Site',
|
|
297
|
+
description: 'desc',
|
|
298
|
+
image: 'https://example.com/image.jpg',
|
|
299
|
+
favicon: 'https://example.com/favicon.ico',
|
|
300
|
+
},
|
|
301
|
+
meta: {},
|
|
302
|
+
})
|
|
303
|
+
await firstPromise
|
|
304
|
+
// Yield once more so the hydrate continuation runs after the resolver.
|
|
305
|
+
await Promise.resolve()
|
|
306
|
+
|
|
307
|
+
const hydrated = editor.getShape<TLBookmarkShape>(first.value.id)
|
|
308
|
+
expect(hydrated?.props.assetId).not.toBeNull()
|
|
309
|
+
expect(fetchSpy).toHaveBeenCalledTimes(1)
|
|
310
|
+
|
|
311
|
+
// Second creation for the same URL should reuse the asset and not fetch again.
|
|
312
|
+
const second = await createBookmarkFromUrl(editor, { url, center: { x: 400, y: 500 } })
|
|
313
|
+
assert(second.ok)
|
|
314
|
+
expect(second.value.props.assetId).not.toBeNull()
|
|
315
|
+
expect(fetchSpy).toHaveBeenCalledTimes(1)
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
it('does not patch the asset if the shape URL changed before metadata resolved', async () => {
|
|
319
|
+
const url = 'https://example.com'
|
|
320
|
+
const center = { x: 100, y: 200 }
|
|
321
|
+
|
|
322
|
+
let resolveAsset: (asset: any) => void = () => {}
|
|
323
|
+
const assetPromise = new Promise<any>((resolve) => {
|
|
324
|
+
resolveAsset = resolve
|
|
325
|
+
})
|
|
326
|
+
vi.spyOn(editor, 'getAssetForExternalContent').mockReturnValue(assetPromise)
|
|
327
|
+
|
|
328
|
+
const result = await createBookmarkFromUrl(editor, { url, center })
|
|
329
|
+
assert(result.ok, 'Failed to create bookmark')
|
|
330
|
+
const shape = result.value
|
|
331
|
+
|
|
332
|
+
// Change the URL before the original fetch resolves.
|
|
333
|
+
editor.updateShapes([{ id: shape.id, type: 'bookmark', props: { url: 'https://changed.com' } }])
|
|
334
|
+
|
|
335
|
+
// Now resolve the original fetch — its metadata is for the stale URL.
|
|
336
|
+
resolveAsset({
|
|
337
|
+
id: 'asset:test-asset-id' as any,
|
|
338
|
+
typeName: 'asset' as const,
|
|
339
|
+
type: 'bookmark' as const,
|
|
340
|
+
props: {
|
|
341
|
+
src: url,
|
|
342
|
+
title: 'Example Site',
|
|
343
|
+
description: 'An example website',
|
|
344
|
+
image: 'https://example.com/image.jpg',
|
|
345
|
+
favicon: 'https://example.com/favicon.ico',
|
|
346
|
+
},
|
|
347
|
+
meta: {},
|
|
348
|
+
})
|
|
349
|
+
await assetPromise
|
|
350
|
+
await Promise.resolve()
|
|
351
|
+
|
|
352
|
+
// The shape should keep its current (changed) URL and not be patched with
|
|
353
|
+
// the stale asset.
|
|
354
|
+
const after = editor.getShape<TLBookmarkShape>(shape.id)
|
|
355
|
+
expect(after?.props.url).toBe('https://changed.com')
|
|
356
|
+
expect(after?.props.assetId).toBe(null)
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
it('removes the placeholder cleanly on undo without an extra metadata undo step', async () => {
|
|
360
|
+
const url = 'https://example.com'
|
|
361
|
+
const center = { x: 100, y: 200 }
|
|
362
|
+
|
|
363
|
+
const assetPromise = Promise.resolve({
|
|
364
|
+
id: 'asset:test-asset-id' as any,
|
|
365
|
+
typeName: 'asset' as const,
|
|
366
|
+
type: 'bookmark' as const,
|
|
367
|
+
props: {
|
|
368
|
+
src: url,
|
|
369
|
+
title: 'Example Site',
|
|
370
|
+
description: 'An example website',
|
|
371
|
+
image: 'https://example.com/image.jpg',
|
|
372
|
+
favicon: 'https://example.com/favicon.ico',
|
|
373
|
+
},
|
|
374
|
+
meta: {},
|
|
375
|
+
})
|
|
376
|
+
vi.spyOn(editor, 'getAssetForExternalContent').mockReturnValue(assetPromise)
|
|
377
|
+
|
|
378
|
+
editor.markHistoryStoppingPoint()
|
|
379
|
+
const result = await createBookmarkFromUrl(editor, { url, center })
|
|
380
|
+
assert(result.ok, 'Failed to create bookmark')
|
|
381
|
+
const shape = result.value
|
|
382
|
+
|
|
383
|
+
// Let hydration (history: 'ignore') run.
|
|
384
|
+
await assetPromise
|
|
385
|
+
await Promise.resolve()
|
|
386
|
+
expect(editor.getShape<TLBookmarkShape>(shape.id)?.props.assetId).toBe('asset:test-asset-id')
|
|
387
|
+
|
|
388
|
+
// A single undo should remove the placeholder entirely; the metadata patch
|
|
389
|
+
// must not have created its own undo step.
|
|
390
|
+
editor.undo()
|
|
391
|
+
expect(editor.getShape(shape.id)).toBeUndefined()
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
it('renders the hydrated asset after redo even though the asset patch was history-ignored', async () => {
|
|
395
|
+
const url = 'https://example.com'
|
|
396
|
+
const center = { x: 100, y: 200 }
|
|
397
|
+
const assetId = AssetRecordType.createId(getHashForString(url))
|
|
398
|
+
|
|
399
|
+
const assetPromise = Promise.resolve({
|
|
400
|
+
id: assetId,
|
|
401
|
+
typeName: 'asset' as const,
|
|
402
|
+
type: 'bookmark' as const,
|
|
403
|
+
props: {
|
|
404
|
+
src: url,
|
|
405
|
+
title: 'Example Site',
|
|
406
|
+
description: 'An example website',
|
|
407
|
+
image: 'https://example.com/image.jpg',
|
|
408
|
+
favicon: 'https://example.com/favicon.ico',
|
|
409
|
+
},
|
|
410
|
+
meta: {},
|
|
411
|
+
})
|
|
412
|
+
vi.spyOn(editor, 'getAssetForExternalContent').mockReturnValue(assetPromise)
|
|
413
|
+
|
|
414
|
+
editor.markHistoryStoppingPoint()
|
|
415
|
+
const result = await createBookmarkFromUrl(editor, { url, center })
|
|
416
|
+
assert(result.ok, 'Failed to create bookmark')
|
|
417
|
+
const shape = result.value
|
|
418
|
+
|
|
419
|
+
// Let hydration (history: 'ignore') run.
|
|
420
|
+
await assetPromise
|
|
421
|
+
await Promise.resolve()
|
|
422
|
+
expect(editor.getShape<TLBookmarkShape>(shape.id)?.props.assetId).toBe(assetId)
|
|
423
|
+
|
|
424
|
+
// Undo removes the placeholder; the hydrated asset stays in the store
|
|
425
|
+
// because it was created with history: 'ignore'.
|
|
426
|
+
editor.undo()
|
|
427
|
+
expect(editor.getShape(shape.id)).toBeUndefined()
|
|
428
|
+
expect(editor.getAsset(assetId)).toBeDefined()
|
|
429
|
+
|
|
430
|
+
// Redo restores the placeholder shape with a null assetId (the asset patch
|
|
431
|
+
// was history-ignored). The shape should still resolve its asset by URL so
|
|
432
|
+
// it doesn't stay stuck on the placeholder.
|
|
433
|
+
editor.redo()
|
|
434
|
+
const redone = editor.getShape<TLBookmarkShape>(shape.id)
|
|
435
|
+
assert(redone, 'Expected the bookmark to be restored on redo')
|
|
436
|
+
expect(getResolvedBookmarkAssetId(editor, redone.props.assetId, redone.props.url)).toBe(assetId)
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
it('measures a short bookmark at its resolved height after redo, not the placeholder height', async () => {
|
|
440
|
+
const url = 'https://example.com'
|
|
441
|
+
const center = { x: 100, y: 200 }
|
|
442
|
+
const assetId = AssetRecordType.createId(getHashForString(url))
|
|
443
|
+
|
|
444
|
+
// A no-image unfurl produces a shorter bookmark than the placeholder.
|
|
445
|
+
const assetPromise = Promise.resolve({
|
|
446
|
+
id: assetId,
|
|
447
|
+
typeName: 'asset' as const,
|
|
448
|
+
type: 'bookmark' as const,
|
|
449
|
+
props: {
|
|
450
|
+
src: url,
|
|
451
|
+
title: 'Example Site',
|
|
452
|
+
description: 'An example website',
|
|
453
|
+
image: '',
|
|
454
|
+
favicon: 'https://example.com/favicon.ico',
|
|
455
|
+
},
|
|
456
|
+
meta: {},
|
|
457
|
+
})
|
|
458
|
+
vi.spyOn(editor, 'getAssetForExternalContent').mockReturnValue(assetPromise)
|
|
459
|
+
|
|
460
|
+
editor.markHistoryStoppingPoint()
|
|
461
|
+
const result = await createBookmarkFromUrl(editor, { url, center })
|
|
462
|
+
assert(result.ok, 'Failed to create bookmark')
|
|
463
|
+
const shape = result.value
|
|
464
|
+
|
|
465
|
+
await assetPromise
|
|
466
|
+
await Promise.resolve()
|
|
467
|
+
const hydrated = editor.getShape<TLBookmarkShape>(shape.id)
|
|
468
|
+
assert(hydrated, 'Expected the bookmark to hydrate')
|
|
469
|
+
const shortHeight = hydrated.props.h
|
|
470
|
+
expect(shortHeight).toBeLessThan(320)
|
|
471
|
+
|
|
472
|
+
editor.undo()
|
|
473
|
+
editor.redo()
|
|
474
|
+
|
|
475
|
+
const redone = editor.getShape<TLBookmarkShape>(shape.id)
|
|
476
|
+
assert(redone, 'Expected the bookmark to be restored on redo')
|
|
477
|
+
|
|
478
|
+
// props.h is stale (the history entry recorded the placeholder height), but
|
|
479
|
+
// the effective height and the geometry/selection bounds should match the
|
|
480
|
+
// resolved short bookmark, not the 320px placeholder.
|
|
481
|
+
expect(getBookmarkShapeHeight(editor, redone)).toBe(shortHeight)
|
|
482
|
+
expect(editor.getShapeGeometry(redone.id).bounds.height).toBe(shortHeight)
|
|
483
|
+
})
|
|
256
484
|
})
|
|
@@ -94,7 +94,7 @@ exports[`Matches a snapshot > Basic SVG 1`] = `
|
|
|
94
94
|
xmlns="http://www.w3.org/1999/xhtml"
|
|
95
95
|
>
|
|
96
96
|
<div
|
|
97
|
-
style="font-size: 22px; color: rgb(29, 29, 29); line-height:
|
|
97
|
+
style="font-size: 22px; color: rgb(29, 29, 29); line-height: 30px; text-align: center; width: 100%; word-wrap: break-word; overflow-wrap: break-word; white-space: pre-wrap; text-shadow: var(--tl-text-outline); tab-size: var(--tl-tab-size, 2);"
|
|
98
98
|
>
|
|
99
99
|
<p
|
|
100
100
|
dir="auto"
|
|
@@ -233,7 +233,7 @@ exports[`Returns all shapes when no ids are provided > All shapes 1`] = `
|
|
|
233
233
|
xmlns="http://www.w3.org/1999/xhtml"
|
|
234
234
|
>
|
|
235
235
|
<div
|
|
236
|
-
style="font-size: 22px; color: rgb(29, 29, 29); line-height:
|
|
236
|
+
style="font-size: 22px; color: rgb(29, 29, 29); line-height: 30px; text-align: center; width: 100%; word-wrap: break-word; overflow-wrap: break-word; white-space: pre-wrap; text-shadow: var(--tl-text-outline); tab-size: var(--tl-tab-size, 2);"
|
|
237
237
|
>
|
|
238
238
|
<p
|
|
239
239
|
dir="auto"
|
|
@@ -116,7 +116,9 @@ describe('handleNativeOrMenuCopy', () => {
|
|
|
116
116
|
writeText: vi.fn(),
|
|
117
117
|
},
|
|
118
118
|
})
|
|
119
|
-
globalThis.ClipboardItem = vi.fn((payload: any)
|
|
119
|
+
globalThis.ClipboardItem = vi.fn(function (payload: any) {
|
|
120
|
+
return payload
|
|
121
|
+
}) as any
|
|
120
122
|
return written
|
|
121
123
|
}
|
|
122
124
|
|
|
@@ -136,3 +136,33 @@ it('Accepts a background option', async () => {
|
|
|
136
136
|
)
|
|
137
137
|
expect(svg2.style.backgroundColor).toBe('transparent')
|
|
138
138
|
})
|
|
139
|
+
|
|
140
|
+
// Note: this test exports, which bumps a module-level id counter that the snapshot tests
|
|
141
|
+
// above are sensitive to - so keep it after them.
|
|
142
|
+
it('waits for required fonts to load before measuring the export', async () => {
|
|
143
|
+
// Text geometry is measured from the loaded font, so the export must not be measured
|
|
144
|
+
// until the fonts it uses have loaded.
|
|
145
|
+
let resolveFonts!: () => void
|
|
146
|
+
const fontsLoaded = new Promise<void>((resolve) => {
|
|
147
|
+
resolveFonts = resolve
|
|
148
|
+
})
|
|
149
|
+
const loadFonts = vi
|
|
150
|
+
.spyOn(editor.fonts, 'loadRequiredFontsForCurrentPage')
|
|
151
|
+
.mockReturnValue(fontsLoaded)
|
|
152
|
+
|
|
153
|
+
let resolved = false
|
|
154
|
+
const exportPromise = editor.getSvgString(editor.getSelectedShapeIds()).then((result) => {
|
|
155
|
+
resolved = true
|
|
156
|
+
return result
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
// let the export run up to the font-loading await
|
|
160
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
161
|
+
expect(loadFonts).toHaveBeenCalled()
|
|
162
|
+
expect(resolved).toBe(false)
|
|
163
|
+
|
|
164
|
+
resolveFonts()
|
|
165
|
+
const svg = await exportPromise
|
|
166
|
+
expect(resolved).toBe(true)
|
|
167
|
+
expect(svg!.svg).toMatch(/^<svg/)
|
|
168
|
+
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Vec } from '@tldraw/editor'
|
|
1
|
+
import { TLDrawShape, Vec } from '@tldraw/editor'
|
|
2
2
|
import { TestEditor } from '../TestEditor'
|
|
3
3
|
|
|
4
4
|
let editor: TestEditor
|
|
@@ -27,3 +27,99 @@ it('ignores touch events while in pen mode', async () => {
|
|
|
27
27
|
|
|
28
28
|
expect(editor.getCurrentPageShapes().length).toBe(0)
|
|
29
29
|
})
|
|
30
|
+
|
|
31
|
+
describe('auto-entering pen mode', () => {
|
|
32
|
+
it('auto-enables pen mode for direct-display pen input', () => {
|
|
33
|
+
expect(editor.getInstanceState().isPenMode).toBe(false)
|
|
34
|
+
editor.pointerDown(100, 100, { isPen: true, isPenDirect: true })
|
|
35
|
+
expect(editor.getInstanceState().isPenMode).toBe(true)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('does not auto-enable pen mode for indirect pen input', () => {
|
|
39
|
+
expect(editor.getInstanceState().isPenMode).toBe(false)
|
|
40
|
+
// An indirect desktop tablet stylus (e.g. a Wacom Intuos) still reports as a pen, but should
|
|
41
|
+
// not flip the editor into pen mode.
|
|
42
|
+
editor.pointerDown(100, 100, { isPen: true, isPenDirect: false })
|
|
43
|
+
expect(editor.getInstanceState().isPenMode).toBe(false)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('still draws with indirect pen input without entering pen mode', () => {
|
|
47
|
+
editor.setCurrentTool('draw')
|
|
48
|
+
editor.pointerDown(100, 100, { isPen: true, isPenDirect: false })
|
|
49
|
+
editor.pointerMove(110, 110, { isPen: true, isPenDirect: false })
|
|
50
|
+
editor.pointerMove(120, 120, { isPen: true, isPenDirect: false })
|
|
51
|
+
editor.pointerUp(120, 120, { isPen: true, isPenDirect: false })
|
|
52
|
+
|
|
53
|
+
expect(editor.getInstanceState().isPenMode).toBe(false)
|
|
54
|
+
expect(editor.getCurrentPageShapes().length).toBe(1)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('ignores non-pen input once pen mode has been enabled', () => {
|
|
58
|
+
editor.setCurrentTool('draw')
|
|
59
|
+
// A direct-display pen enables pen mode.
|
|
60
|
+
editor.pointerDown(300, 300, { isPen: true, isPenDirect: true })
|
|
61
|
+
editor.pointerUp(300, 300, { isPen: true, isPenDirect: true })
|
|
62
|
+
expect(editor.getInstanceState().isPenMode).toBe(true)
|
|
63
|
+
|
|
64
|
+
const shapeCount = editor.getCurrentPageShapes().length
|
|
65
|
+
|
|
66
|
+
// A subsequent touch (non-pen) is ignored while in pen mode.
|
|
67
|
+
editor.pointerDown(100, 100, { isPen: false })
|
|
68
|
+
editor.pointerMove(120, 120, { isPen: false })
|
|
69
|
+
editor.pointerUp(120, 120, { isPen: false })
|
|
70
|
+
expect(editor.getCurrentPageShapes().length).toBe(shapeCount)
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('forgiving palm touches when entering pen mode', () => {
|
|
75
|
+
it('does not connect a palm touch to the stylus stroke', () => {
|
|
76
|
+
editor.setCurrentTool('draw')
|
|
77
|
+
expect(editor.getInstanceState().isPenMode).toBe(false)
|
|
78
|
+
|
|
79
|
+
// A palm rests on the canvas first (not a pen, pen mode is still off)
|
|
80
|
+
editor.pointerDown(100, 100, { isPen: false })
|
|
81
|
+
editor.expectToBeIn('draw.drawing')
|
|
82
|
+
|
|
83
|
+
// Then the stylus lands, which switches the editor into pen mode
|
|
84
|
+
editor.pointerDown(300, 300, { isPen: true })
|
|
85
|
+
expect(editor.getInstanceState().isPenMode).toBe(true)
|
|
86
|
+
|
|
87
|
+
// The stylus draws its stroke
|
|
88
|
+
editor.pointerMove(310, 310, { isPen: true })
|
|
89
|
+
editor.pointerMove(320, 320, { isPen: true })
|
|
90
|
+
editor.pointerUp(320, 320, { isPen: true })
|
|
91
|
+
|
|
92
|
+
expect(editor.getCurrentPageShapes().length).toBe(1)
|
|
93
|
+
const shape = editor.getCurrentPageShapes()[0] as TLDrawShape
|
|
94
|
+
|
|
95
|
+
// The stroke should begin where the stylus landed, not at the palm point.
|
|
96
|
+
const bounds = editor.getShapePageBounds(shape.id)!
|
|
97
|
+
expect(bounds.minX).toBeGreaterThan(200)
|
|
98
|
+
expect(bounds.minY).toBeGreaterThan(200)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('forgives a palm touch that was already dragging before the stylus lands', () => {
|
|
102
|
+
editor.setCurrentTool('draw')
|
|
103
|
+
|
|
104
|
+
// A palm rests on the canvas and drags a little before the stylus arrives
|
|
105
|
+
editor.pointerDown(100, 100, { isPen: false })
|
|
106
|
+
editor.pointerMove(120, 120, { isPen: false })
|
|
107
|
+
editor.pointerMove(140, 140, { isPen: false })
|
|
108
|
+
editor.expectToBeIn('draw.drawing')
|
|
109
|
+
|
|
110
|
+
// The stylus lands, switching into pen mode and forgiving the palm stroke
|
|
111
|
+
editor.pointerDown(300, 300, { isPen: true })
|
|
112
|
+
expect(editor.getInstanceState().isPenMode).toBe(true)
|
|
113
|
+
|
|
114
|
+
editor.pointerMove(310, 310, { isPen: true })
|
|
115
|
+
editor.pointerMove(320, 320, { isPen: true })
|
|
116
|
+
editor.pointerUp(320, 320, { isPen: true })
|
|
117
|
+
|
|
118
|
+
expect(editor.getCurrentPageShapes().length).toBe(1)
|
|
119
|
+
const shape = editor.getCurrentPageShapes()[0] as TLDrawShape
|
|
120
|
+
|
|
121
|
+
const bounds = editor.getShapePageBounds(shape.id)!
|
|
122
|
+
expect(bounds.minX).toBeGreaterThan(200)
|
|
123
|
+
expect(bounds.minY).toBeGreaterThan(200)
|
|
124
|
+
})
|
|
125
|
+
})
|
|
@@ -428,6 +428,18 @@ describe('CameraOptions.panSpeed', () => {
|
|
|
428
428
|
expect(editor.getCamera()).toMatchObject({ x: 5, y: 10, z: 1 })
|
|
429
429
|
})
|
|
430
430
|
|
|
431
|
+
it('Does not zoom when spacebar panning momentum is applied on release', () => {
|
|
432
|
+
editor
|
|
433
|
+
.dispatch({ ...keyBoardEvent, key: ' ', code: 'Space' })
|
|
434
|
+
.pointerDown(0, 0)
|
|
435
|
+
.pointerMove(50, 50)
|
|
436
|
+
|
|
437
|
+
editor.inputs.setPointerVelocity(new Vec(1, 1))
|
|
438
|
+
editor.pointerUp().forceTick()
|
|
439
|
+
|
|
440
|
+
expect(editor.getCamera().z).toBe(1)
|
|
441
|
+
})
|
|
442
|
+
|
|
431
443
|
it('Does not affect edge scroll panning', () => {
|
|
432
444
|
const shapeId = createShapeId()
|
|
433
445
|
const viewportScreenBounds = editor.getViewportScreenBounds()
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { b64Vecs, TLPointerEventInfo } from '@tldraw/editor'
|
|
2
|
+
import { onDragFromToolbarToCreateShape } from '../lib/ui/hooks/useTools'
|
|
3
|
+
import { TestEditor } from './TestEditor'
|
|
4
|
+
|
|
5
|
+
let editor: TestEditor
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
editor = new TestEditor()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
editor?.dispose()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
function dragInfo(): TLPointerEventInfo {
|
|
16
|
+
return {
|
|
17
|
+
type: 'pointer',
|
|
18
|
+
target: 'canvas',
|
|
19
|
+
name: 'pointer_move',
|
|
20
|
+
point: { x: 50, y: 50, z: 0.5 },
|
|
21
|
+
shiftKey: false,
|
|
22
|
+
altKey: false,
|
|
23
|
+
ctrlKey: false,
|
|
24
|
+
metaKey: false,
|
|
25
|
+
accelKey: false,
|
|
26
|
+
pointerId: 1,
|
|
27
|
+
button: 0,
|
|
28
|
+
isPen: false,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe('onDragFromToolbarToCreateShape', () => {
|
|
33
|
+
it('masks the toolbar with the created shape type by default', () => {
|
|
34
|
+
onDragFromToolbarToCreateShape(editor, dragInfo(), {
|
|
35
|
+
createShape: (id) => editor.createShape({ id, type: 'geo' }),
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// while dragging, the toolbar shows the dragged shape's tool as active
|
|
39
|
+
expect(editor.getCurrentToolId()).toBe('geo')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('masks the toolbar with toolId when the shape type differs from the source tool', () => {
|
|
43
|
+
// reproduces #9221: dragging a `draw` shape out from the select tool should keep
|
|
44
|
+
// the select tool highlighted, not the draw (pen) tool.
|
|
45
|
+
onDragFromToolbarToCreateShape(editor, dragInfo(), {
|
|
46
|
+
maskedToolId: 'select',
|
|
47
|
+
createShape: (id) =>
|
|
48
|
+
editor.createShape({
|
|
49
|
+
id,
|
|
50
|
+
type: 'draw',
|
|
51
|
+
props: {
|
|
52
|
+
segments: [
|
|
53
|
+
{
|
|
54
|
+
type: 'free',
|
|
55
|
+
path: b64Vecs.encodePoints([
|
|
56
|
+
{ x: 0, y: 0, z: 0.5 },
|
|
57
|
+
{ x: 10, y: 10, z: 0.5 },
|
|
58
|
+
]),
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
expect(editor.getCurrentToolId()).toBe('select')
|
|
66
|
+
})
|
|
67
|
+
})
|