✨ (editor) Actions on multiple groups
You can now select groups, move, copy, delete them easily Closes #830, closes #1092
This commit is contained in:
90
apps/builder/src/hooks/useEventListener.ts
Normal file
90
apps/builder/src/hooks/useEventListener.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { RefObject, useEffect, useLayoutEffect, useRef } from 'react'
|
||||
|
||||
export const useIsomorphicLayoutEffect =
|
||||
typeof window !== 'undefined' ? useLayoutEffect : useEffect
|
||||
|
||||
type Options = boolean | (AddEventListenerOptions & { debug?: boolean })
|
||||
// MediaQueryList Event based useEventListener interface
|
||||
function useEventListener<K extends keyof MediaQueryListEventMap>(
|
||||
eventName: K,
|
||||
handler: (event: MediaQueryListEventMap[K]) => void,
|
||||
element: RefObject<MediaQueryList>,
|
||||
options?: Options
|
||||
): void
|
||||
|
||||
// Window Event based useEventListener interface
|
||||
function useEventListener<K extends keyof WindowEventMap>(
|
||||
eventName: K,
|
||||
handler: (event: WindowEventMap[K]) => void,
|
||||
element?: undefined,
|
||||
options?: Options
|
||||
): void
|
||||
|
||||
// Element Event based useEventListener interface
|
||||
function useEventListener<
|
||||
K extends keyof HTMLElementEventMap,
|
||||
T extends HTMLElement = HTMLDivElement
|
||||
>(
|
||||
eventName: K,
|
||||
handler: (event: HTMLElementEventMap[K]) => void,
|
||||
element: RefObject<T>,
|
||||
options?: Options
|
||||
): void
|
||||
|
||||
// Document Event based useEventListener interface
|
||||
function useEventListener<K extends keyof DocumentEventMap>(
|
||||
eventName: K,
|
||||
handler: (event: DocumentEventMap[K]) => void,
|
||||
element: RefObject<Document>,
|
||||
options?: Options
|
||||
): void
|
||||
|
||||
function useEventListener<
|
||||
KW extends keyof WindowEventMap,
|
||||
KH extends keyof HTMLElementEventMap,
|
||||
KM extends keyof MediaQueryListEventMap,
|
||||
T extends HTMLElement | MediaQueryList | void = void
|
||||
>(
|
||||
eventName: KW | KH | KM,
|
||||
handler: (
|
||||
event:
|
||||
| WindowEventMap[KW]
|
||||
| HTMLElementEventMap[KH]
|
||||
| MediaQueryListEventMap[KM]
|
||||
| Event
|
||||
) => void,
|
||||
element?: RefObject<T>,
|
||||
options?: Options
|
||||
) {
|
||||
// Create a ref that stores handler
|
||||
const savedHandler = useRef(handler)
|
||||
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
savedHandler.current = handler
|
||||
}, [handler])
|
||||
|
||||
useEffect(() => {
|
||||
// Define the listening target
|
||||
if (element && !element.current) return
|
||||
|
||||
const targetElement: T | Window = element?.current ?? window
|
||||
|
||||
if (!(targetElement && targetElement.addEventListener)) return
|
||||
|
||||
if (options && typeof options !== 'boolean')
|
||||
console.log('Add event listener', { eventName, element, options })
|
||||
// Create event listener that calls handler function stored in ref
|
||||
const listener: typeof handler = (event) => savedHandler.current(event)
|
||||
|
||||
targetElement.addEventListener(eventName, listener, options)
|
||||
|
||||
// Remove event listener on cleanup
|
||||
return () => {
|
||||
if (options && typeof options !== 'boolean')
|
||||
console.log('Remove event listener')
|
||||
targetElement.removeEventListener(eventName, listener, options)
|
||||
}
|
||||
}, [eventName, element, options])
|
||||
}
|
||||
|
||||
export { useEventListener }
|
||||
69
apps/builder/src/hooks/useKeyboardShortcuts.ts
Normal file
69
apps/builder/src/hooks/useKeyboardShortcuts.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { useEventListener } from './useEventListener'
|
||||
|
||||
type Props = {
|
||||
undo?: () => void
|
||||
redo?: () => void
|
||||
copy?: () => void
|
||||
paste?: () => void
|
||||
cut?: () => void
|
||||
backspace?: () => void
|
||||
}
|
||||
export const useKeyboardShortcuts = ({
|
||||
undo,
|
||||
redo,
|
||||
copy,
|
||||
paste,
|
||||
cut,
|
||||
backspace,
|
||||
}: Props) => {
|
||||
const isUndoShortcut = (event: KeyboardEvent) =>
|
||||
(event.metaKey || event.ctrlKey) && event.key === 'z' && !event.shiftKey
|
||||
|
||||
const isRedoShortcut = (event: KeyboardEvent) =>
|
||||
(event.metaKey || event.ctrlKey) && event.key === 'z' && event.shiftKey
|
||||
|
||||
const isCopyShortcut = (event: KeyboardEvent) =>
|
||||
(event.metaKey || event.ctrlKey) && event.key === 'c'
|
||||
|
||||
const isPasteShortcut = (event: KeyboardEvent) =>
|
||||
(event.metaKey || event.ctrlKey) && event.key === 'v'
|
||||
|
||||
const isCutShortcut = (event: KeyboardEvent) =>
|
||||
(event.metaKey || event.ctrlKey) && event.key === 'x'
|
||||
|
||||
const isBackspaceShortcut = (event: KeyboardEvent) =>
|
||||
event.key === 'Backspace'
|
||||
|
||||
useEventListener('keydown', (event: KeyboardEvent) => {
|
||||
const target = event.target as HTMLElement | null
|
||||
const isTyping =
|
||||
target?.role === 'textbox' ||
|
||||
target instanceof HTMLTextAreaElement ||
|
||||
target instanceof HTMLInputElement
|
||||
if (isTyping) return
|
||||
if (undo && isUndoShortcut(event)) {
|
||||
event.preventDefault()
|
||||
undo()
|
||||
}
|
||||
if (redo && isRedoShortcut(event)) {
|
||||
event.preventDefault()
|
||||
redo()
|
||||
}
|
||||
if (copy && isCopyShortcut(event)) {
|
||||
event.preventDefault()
|
||||
copy()
|
||||
}
|
||||
if (paste && isPasteShortcut(event)) {
|
||||
event.preventDefault()
|
||||
paste()
|
||||
}
|
||||
if (cut && isCutShortcut(event)) {
|
||||
event.preventDefault()
|
||||
cut()
|
||||
}
|
||||
if (backspace && isBackspaceShortcut(event)) {
|
||||
event.preventDefault()
|
||||
backspace()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { useEventListener } from '@chakra-ui/react'
|
||||
|
||||
export const useUndoShortcut = (undo: () => void) => {
|
||||
const isUndoShortcut = (event: KeyboardEvent) =>
|
||||
(event.metaKey || event.ctrlKey) && event.key === 'z'
|
||||
|
||||
useEventListener('keydown', (event: KeyboardEvent) => {
|
||||
const target = event.target as HTMLElement | null
|
||||
const isTyping =
|
||||
target?.role === 'textbox' ||
|
||||
target instanceof HTMLTextAreaElement ||
|
||||
target instanceof HTMLInputElement
|
||||
if (isTyping) return
|
||||
if (isUndoShortcut(event)) {
|
||||
undo()
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user