remove safari special-casing for paste (#1470)
Previously, we had a bunch of special-casing around paste to support safari quirks on desktop and ios. Since upgrading radix-ui and useGesture, these are no longer needed and were actually causing issues. This diff removes the special casing for paste and makes it a normal action that get triggered the same way as any other. ### Change Type - [x] `patch` — Bug Fix ### Test Plan 1. Copy text outside of tldraw, paste in tldraw with the context menu, edit menu, and keyboard shortcut 2. Repeat for images outside of tldraw 3. Repeat for shapes inside of tldraw ### Release Notes [fixes a regression introduced during this release]
This commit is contained in:
parent
042edeb4b4
commit
f8b6ee6ab9
7 changed files with 30 additions and 101 deletions
|
@ -1025,10 +1025,10 @@ export function useLanguages(): {
|
||||||
export function useLocalStorageState<T = any>(key: string, defaultValue: T): readonly [T, (setter: ((value: T) => T) | T) => void];
|
export function useLocalStorageState<T = any>(key: string, defaultValue: T): readonly [T, (setter: ((value: T) => T) | T) => void];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function useMenuClipboardEvents(source: TLUiEventSource): {
|
export function useMenuClipboardEvents(): {
|
||||||
copy: () => void;
|
copy: (source: TLUiEventSource) => void;
|
||||||
cut: () => void;
|
cut: (source: TLUiEventSource) => void;
|
||||||
paste: (data: ClipboardItem[] | DataTransfer, point?: VecLike) => Promise<void>;
|
paste: (data: ClipboardItem[] | DataTransfer, source: TLUiEventSource, point?: VecLike) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
|
|
|
@ -5,7 +5,6 @@ import * as React from 'react'
|
||||||
import { useValue } from 'signia-react'
|
import { useValue } from 'signia-react'
|
||||||
import { MenuChild } from '../hooks/menuHelpers'
|
import { MenuChild } from '../hooks/menuHelpers'
|
||||||
import { useBreakpoint } from '../hooks/useBreakpoint'
|
import { useBreakpoint } from '../hooks/useBreakpoint'
|
||||||
import { useMenuClipboardEvents } from '../hooks/useClipboardEvents'
|
|
||||||
import { useContextMenuSchema } from '../hooks/useContextMenuSchema'
|
import { useContextMenuSchema } from '../hooks/useContextMenuSchema'
|
||||||
import { useMenuIsOpen } from '../hooks/useMenuIsOpen'
|
import { useMenuIsOpen } from '../hooks/useMenuIsOpen'
|
||||||
import { useReadonly } from '../hooks/useReadonly'
|
import { useReadonly } from '../hooks/useReadonly'
|
||||||
|
@ -61,7 +60,6 @@ function ContextMenuContent() {
|
||||||
const [_, handleSubOpenChange] = useMenuIsOpen('context menu sub')
|
const [_, handleSubOpenChange] = useMenuIsOpen('context menu sub')
|
||||||
|
|
||||||
const isReadonly = useReadonly()
|
const isReadonly = useReadonly()
|
||||||
const { paste } = useMenuClipboardEvents('context-menu')
|
|
||||||
const breakpoint = useBreakpoint()
|
const breakpoint = useBreakpoint()
|
||||||
const container = useContainer()
|
const container = useContainer()
|
||||||
|
|
||||||
|
@ -73,42 +71,6 @@ function ContextMenuContent() {
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case 'custom': {
|
case 'custom': {
|
||||||
switch (item.id) {
|
switch (item.id) {
|
||||||
case 'MENU_PASTE': {
|
|
||||||
return (
|
|
||||||
<_ContextMenu.Item key={item.id}>
|
|
||||||
<Button
|
|
||||||
className="tlui-menu__button"
|
|
||||||
data-wd={`menu-item.${item.id}`}
|
|
||||||
kbd="$v"
|
|
||||||
label="action.paste"
|
|
||||||
disabled={item.disabled}
|
|
||||||
onClick={() => {
|
|
||||||
if (!app.isSafari || (app.isSafari && app.isIos)) {
|
|
||||||
navigator.clipboard.read().then((clipboardItems) => {
|
|
||||||
paste(clipboardItems, app.inputs.currentPagePoint)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onMouseDown={() => {
|
|
||||||
if (app.isSafari && !app.isIos) {
|
|
||||||
// NOTE: This must be a onMouseDown for Safari/desktop, onClick doesn't work at the time of writing... 😒
|
|
||||||
navigator.clipboard.read().then((clipboardItems) => {
|
|
||||||
paste(clipboardItems, app.inputs.currentPagePoint)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
// onPointerUp={() => {
|
|
||||||
// if (app.isSafari && app.isIos) {
|
|
||||||
// // NOTE: This must be a onPointerUp for Safari/mobile, onClick doesn't work at the time of writing... 😒
|
|
||||||
// navigator.clipboard.read().then((clipboardItems) => {
|
|
||||||
// paste(clipboardItems, app.inputs.currentPagePoint)
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }}
|
|
||||||
/>
|
|
||||||
</_ContextMenu.Item>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case 'MOVE_TO_PAGE_MENU': {
|
case 'MOVE_TO_PAGE_MENU': {
|
||||||
return <MoveToPageMenu key={item.id} />
|
return <MoveToPageMenu key={item.id} />
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { App, preventDefault, useApp } from '@tldraw/editor'
|
import { App, useApp } from '@tldraw/editor'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { MenuChild } from '../hooks/menuHelpers'
|
import { MenuChild } from '../hooks/menuHelpers'
|
||||||
import { useBreakpoint } from '../hooks/useBreakpoint'
|
import { useBreakpoint } from '../hooks/useBreakpoint'
|
||||||
import { useMenuClipboardEvents } from '../hooks/useClipboardEvents'
|
|
||||||
import { useMenuSchema } from '../hooks/useMenuSchema'
|
import { useMenuSchema } from '../hooks/useMenuSchema'
|
||||||
import { useReadonly } from '../hooks/useReadonly'
|
import { useReadonly } from '../hooks/useReadonly'
|
||||||
import { useTranslation } from '../hooks/useTranslation/useTranslation'
|
import { useTranslation } from '../hooks/useTranslation/useTranslation'
|
||||||
|
@ -37,7 +36,6 @@ function MenuContent() {
|
||||||
const menuSchema = useMenuSchema()
|
const menuSchema = useMenuSchema()
|
||||||
const breakpoint = useBreakpoint()
|
const breakpoint = useBreakpoint()
|
||||||
const isReadonly = useReadonly()
|
const isReadonly = useReadonly()
|
||||||
const { paste } = useMenuClipboardEvents('menu')
|
|
||||||
|
|
||||||
function getMenuItem(app: App, item: MenuChild, parent: MenuChild | null, depth: number) {
|
function getMenuItem(app: App, item: MenuChild, parent: MenuChild | null, depth: number) {
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
|
@ -48,35 +46,6 @@ function MenuContent() {
|
||||||
return <LanguageMenu key="item" />
|
return <LanguageMenu key="item" />
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.id === 'MENU_PASTE') {
|
|
||||||
return (
|
|
||||||
<M.Item
|
|
||||||
key={item.id}
|
|
||||||
data-wd={`menu-item.${item.id}`}
|
|
||||||
kbd="$v"
|
|
||||||
label="action.paste"
|
|
||||||
disabled={item.disabled}
|
|
||||||
onMouseDown={() => {
|
|
||||||
if (app.isSafari && navigator.clipboard?.read) {
|
|
||||||
// NOTE: This must be a onMouseDown for Safari/desktop, onClick doesn't work at the time of writing...
|
|
||||||
navigator.clipboard.read().then((clipboardItems) => {
|
|
||||||
paste(clipboardItems)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
if (app.isSafari) {
|
|
||||||
// noop
|
|
||||||
} else if (navigator.clipboard?.read) {
|
|
||||||
navigator.clipboard.read().then((clipboardItems) => {
|
|
||||||
paste(clipboardItems)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onPointerUp={preventDefault}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
case 'group': {
|
case 'group': {
|
||||||
|
|
|
@ -64,17 +64,12 @@ function makeActions(actions: ActionItem[]) {
|
||||||
export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
||||||
const app = useApp()
|
const app = useApp()
|
||||||
|
|
||||||
// const saveFile = useSaveFile()
|
|
||||||
// const saveFileAs = useSaveFileAs()
|
|
||||||
// const newFile = useNewFile()
|
|
||||||
// const openFile = useOpenFile()
|
|
||||||
|
|
||||||
const { addDialog, clearDialogs } = useDialogs()
|
const { addDialog, clearDialogs } = useDialogs()
|
||||||
const { clearToasts } = useToasts()
|
const { clearToasts } = useToasts()
|
||||||
|
|
||||||
const insertMedia = useInsertMedia()
|
const insertMedia = useInsertMedia()
|
||||||
const printSelectionOrPages = usePrint()
|
const printSelectionOrPages = usePrint()
|
||||||
const { cut, copy } = useMenuClipboardEvents('unknown')
|
const { cut, copy, paste } = useMenuClipboardEvents()
|
||||||
const copyAs = useCopyAs()
|
const copyAs = useCopyAs()
|
||||||
const exportAs = useExportAs()
|
const exportAs = useExportAs()
|
||||||
|
|
||||||
|
@ -631,9 +626,8 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
||||||
kbd: '$x',
|
kbd: '$x',
|
||||||
readonlyOk: false,
|
readonlyOk: false,
|
||||||
onSelect(source) {
|
onSelect(source) {
|
||||||
trackEvent('cut', { source })
|
|
||||||
app.mark('cut')
|
app.mark('cut')
|
||||||
cut()
|
cut(source)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -642,8 +636,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
||||||
kbd: '$c',
|
kbd: '$c',
|
||||||
readonlyOk: true,
|
readonlyOk: true,
|
||||||
onSelect(source) {
|
onSelect(source) {
|
||||||
trackEvent('copy', { source })
|
copy(source)
|
||||||
copy()
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -651,9 +644,14 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
||||||
label: 'action.paste',
|
label: 'action.paste',
|
||||||
kbd: '$v',
|
kbd: '$v',
|
||||||
readonlyOk: false,
|
readonlyOk: false,
|
||||||
onSelect() {
|
onSelect(source) {
|
||||||
// must be inlined with a custom menu item
|
navigator.clipboard?.read().then((clipboardItems) => {
|
||||||
// the kbd listed here should have no effect
|
paste(
|
||||||
|
clipboardItems,
|
||||||
|
source,
|
||||||
|
source === 'context-menu' ? app.inputs.currentPagePoint : undefined
|
||||||
|
)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -931,6 +929,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
||||||
copyAs,
|
copyAs,
|
||||||
cut,
|
cut,
|
||||||
copy,
|
copy,
|
||||||
|
paste,
|
||||||
clearDialogs,
|
clearDialogs,
|
||||||
clearToasts,
|
clearToasts,
|
||||||
printSelectionOrPages,
|
printSelectionOrPages,
|
||||||
|
|
|
@ -539,33 +539,37 @@ const handleNativeOrMenuCopy = (app: App) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export function useMenuClipboardEvents(source: TLUiEventSource) {
|
export function useMenuClipboardEvents() {
|
||||||
const app = useApp()
|
const app = useApp()
|
||||||
const trackEvent = useEvents()
|
const trackEvent = useEvents()
|
||||||
|
|
||||||
const copy = useCallback(
|
const copy = useCallback(
|
||||||
function onCopy() {
|
function onCopy(source: TLUiEventSource) {
|
||||||
if (app.selectedIds.length === 0) return
|
if (app.selectedIds.length === 0) return
|
||||||
|
|
||||||
handleNativeOrMenuCopy(app)
|
handleNativeOrMenuCopy(app)
|
||||||
trackEvent('copy', { source })
|
trackEvent('copy', { source })
|
||||||
},
|
},
|
||||||
[app, trackEvent, source]
|
[app, trackEvent]
|
||||||
)
|
)
|
||||||
|
|
||||||
const cut = useCallback(
|
const cut = useCallback(
|
||||||
function onCut() {
|
function onCut(source: TLUiEventSource) {
|
||||||
if (app.selectedIds.length === 0) return
|
if (app.selectedIds.length === 0) return
|
||||||
|
|
||||||
handleNativeOrMenuCopy(app)
|
handleNativeOrMenuCopy(app)
|
||||||
app.deleteShapes()
|
app.deleteShapes()
|
||||||
trackEvent('cut', { source })
|
trackEvent('cut', { source })
|
||||||
},
|
},
|
||||||
[app, trackEvent, source]
|
[app, trackEvent]
|
||||||
)
|
)
|
||||||
|
|
||||||
const paste = useCallback(
|
const paste = useCallback(
|
||||||
async function onPaste(data: DataTransfer | ClipboardItem[], point?: VecLike) {
|
async function onPaste(
|
||||||
|
data: DataTransfer | ClipboardItem[],
|
||||||
|
source: TLUiEventSource,
|
||||||
|
point?: VecLike
|
||||||
|
) {
|
||||||
// If we're editing a shape, or we are focusing an editable input, then
|
// If we're editing a shape, or we are focusing an editable input, then
|
||||||
// we would want the user's paste interaction to go to that element or
|
// we would want the user's paste interaction to go to that element or
|
||||||
// input instead; e.g. when pasting text into a text shape's content
|
// input instead; e.g. when pasting text into a text shape's content
|
||||||
|
@ -577,7 +581,7 @@ export function useMenuClipboardEvents(source: TLUiEventSource) {
|
||||||
} else {
|
} else {
|
||||||
// Read it first and then recurse, kind of weird
|
// Read it first and then recurse, kind of weird
|
||||||
navigator.clipboard.read().then((clipboardItems) => {
|
navigator.clipboard.read().then((clipboardItems) => {
|
||||||
paste(clipboardItems, app.inputs.currentPagePoint)
|
paste(clipboardItems, source, point)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -163,7 +163,7 @@ export const ContextMenuSchemaProvider = track(function ContextMenuSchemaProvide
|
||||||
'clipboard-group',
|
'clipboard-group',
|
||||||
oneSelected && menuItem(actions['cut']),
|
oneSelected && menuItem(actions['cut']),
|
||||||
oneSelected && menuItem(actions['copy']),
|
oneSelected && menuItem(actions['copy']),
|
||||||
showMenuPaste && menuCustom('MENU_PASTE', { readonlyOk: false })
|
showMenuPaste && menuItem(actions['paste'])
|
||||||
),
|
),
|
||||||
atLeastOneShapeOnPage &&
|
atLeastOneShapeOnPage &&
|
||||||
menuGroup(
|
menuGroup(
|
||||||
|
|
|
@ -97,12 +97,7 @@ export function MenuSchemaProvider({ overrides, children }: MenuSchemaProviderPr
|
||||||
'clipboard-actions',
|
'clipboard-actions',
|
||||||
menuItem(actions['cut'], { disabled: noneSelected }),
|
menuItem(actions['cut'], { disabled: noneSelected }),
|
||||||
menuItem(actions['copy'], { disabled: noneSelected }),
|
menuItem(actions['copy'], { disabled: noneSelected }),
|
||||||
{
|
menuItem(actions['paste'], { disabled: !showMenuPaste })
|
||||||
id: 'MENU_PASTE',
|
|
||||||
type: 'custom',
|
|
||||||
disabled: !showMenuPaste,
|
|
||||||
readonlyOk: false,
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
menuGroup(
|
menuGroup(
|
||||||
'conversions',
|
'conversions',
|
||||||
|
|
Loading…
Reference in a new issue