mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-21 07:21:46 +01:00
feat(useKbd): new composable (#73)
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { ref, computed, toValue } from 'vue'
|
||||
import type { MaybeRef } from 'vue'
|
||||
import { useEventListener, useDebounceFn } from '@vueuse/core'
|
||||
import { useShortcuts } from './useShortcuts'
|
||||
import { useEventListener, useActiveElement, useDebounceFn } from '@vueuse/core'
|
||||
import { useKbd } from '#imports'
|
||||
|
||||
type Handler = () => void
|
||||
type Handler = (e?: any) => void
|
||||
|
||||
export interface ShortcutConfig {
|
||||
handler: Handler
|
||||
@@ -35,15 +35,36 @@ interface Shortcut {
|
||||
const chainedShortcutRegex = /^[^-]+.*-.*[^-]+$/
|
||||
const combinedShortcutRegex = /^[^_]+.*_.*[^_]+$/
|
||||
|
||||
export const defineShortcuts = (config: MaybeRef<ShortcutsConfig>, options: ShortcutsOptions = {}) => {
|
||||
const { macOS, usingInput } = useShortcuts()
|
||||
export function extractShortcuts(items: any[] | any[][]) {
|
||||
const shortcuts: Record<string, Handler> = {}
|
||||
|
||||
function traverse(items: any[]) {
|
||||
items.forEach((item) => {
|
||||
if (item.kbds?.length && (item.select || item.click)) {
|
||||
const shortcutKey = item.kbds.join('_')
|
||||
shortcuts[shortcutKey] = item.select || item.click
|
||||
}
|
||||
if (item.children) {
|
||||
traverse(item.children.flat())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
traverse(items.flat())
|
||||
|
||||
return shortcuts
|
||||
}
|
||||
|
||||
export function defineShortcuts(config: MaybeRef<ShortcutsConfig>, options: ShortcutsOptions = {}) {
|
||||
const chainedInputs = ref<string[]>([])
|
||||
const clearChainedInput = () => {
|
||||
chainedInputs.value.splice(0, chainedInputs.value.length)
|
||||
}
|
||||
const debouncedClearChainedInput = useDebounceFn(clearChainedInput, options.chainDelay ?? 800)
|
||||
|
||||
const { macOS } = useKbd()
|
||||
const activeElement = useActiveElement()
|
||||
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
// Input autocomplete triggers a keydown event
|
||||
if (!e.key) {
|
||||
@@ -65,7 +86,7 @@ export const defineShortcuts = (config: MaybeRef<ShortcutsConfig>, options: Shor
|
||||
|
||||
if (shortcut.enabled) {
|
||||
e.preventDefault()
|
||||
shortcut.handler()
|
||||
shortcut.handler(e)
|
||||
}
|
||||
clearChainedInput()
|
||||
return
|
||||
@@ -102,6 +123,19 @@ export const defineShortcuts = (config: MaybeRef<ShortcutsConfig>, options: Shor
|
||||
debouncedClearChainedInput()
|
||||
}
|
||||
|
||||
const usingInput = computed(() => {
|
||||
const tagName = activeElement.value?.tagName
|
||||
const contentEditable = activeElement.value?.contentEditable
|
||||
|
||||
const usingInput = !!(tagName === 'INPUT' || tagName === 'TEXTAREA' || contentEditable === 'true' || contentEditable === 'plaintext-only')
|
||||
|
||||
if (usingInput) {
|
||||
return ((activeElement.value as any)?.name as string) || true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
// Map config to full detailled shortcuts
|
||||
const shortcuts = computed<Shortcut[]>(() => {
|
||||
return Object.entries(toValue(config)).map(([key, shortcutConfig]) => {
|
||||
@@ -132,11 +166,11 @@ export const defineShortcuts = (config: MaybeRef<ShortcutsConfig>, options: Shor
|
||||
} else {
|
||||
const keySplit = key.toLowerCase().split('_').map(k => k)
|
||||
shortcut = {
|
||||
key: keySplit.filter(k => !['meta', 'ctrl', 'shift', 'alt'].includes(k)).join('_'),
|
||||
metaKey: keySplit.includes('meta'),
|
||||
key: keySplit.filter(k => !['meta', 'command', 'ctrl', 'shift', 'alt', 'option'].includes(k)).join('_'),
|
||||
metaKey: keySplit.includes('meta') || keySplit.includes('command'),
|
||||
ctrlKey: keySplit.includes('ctrl'),
|
||||
shiftKey: keySplit.includes('shift'),
|
||||
altKey: keySplit.includes('alt')
|
||||
altKey: keySplit.includes('alt') || keySplit.includes('option')
|
||||
}
|
||||
}
|
||||
shortcut.chained = chained
|
||||
|
||||
53
src/runtime/composables/useKbd.ts
Normal file
53
src/runtime/composables/useKbd.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { createSharedComposable } from '@vueuse/core'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
|
||||
export const kbdKeysMap = {
|
||||
meta: '',
|
||||
command: '⌘',
|
||||
shift: '⇧',
|
||||
ctrl: '⌃',
|
||||
option: '⌥',
|
||||
alt: '⎇',
|
||||
enter: '↵',
|
||||
delete: '⌦',
|
||||
backspace: '⌫',
|
||||
escape: '⎋',
|
||||
tab: '⇥',
|
||||
capslock: '⇪',
|
||||
arrowup: '↑',
|
||||
arrowright: '→',
|
||||
arrowdown: '↓',
|
||||
arrowleft: '←',
|
||||
pageup: '⇞',
|
||||
pagedown: '⇟',
|
||||
home: '↖',
|
||||
end: '↘'
|
||||
}
|
||||
|
||||
export type KbdKey = keyof typeof kbdKeysMap
|
||||
|
||||
const _useKbd = () => {
|
||||
const macOS = computed(() => import.meta.client && navigator && navigator.userAgent && navigator.userAgent.match(/Macintosh;/))
|
||||
|
||||
const metaSymbol = ref(' ')
|
||||
|
||||
onMounted(() => {
|
||||
metaSymbol.value = macOS.value ? kbdKeysMap.command : kbdKeysMap.ctrl
|
||||
})
|
||||
|
||||
function getKbdKey(value: KbdKey | string) {
|
||||
if (value === 'meta') {
|
||||
return metaSymbol.value
|
||||
}
|
||||
|
||||
return kbdKeysMap[value as KbdKey] || value.toUpperCase()
|
||||
}
|
||||
|
||||
return {
|
||||
macOS,
|
||||
metaSymbol,
|
||||
getKbdKey
|
||||
}
|
||||
}
|
||||
|
||||
export const useKbd = createSharedComposable(_useKbd)
|
||||
@@ -1,36 +0,0 @@
|
||||
import { createSharedComposable, useActiveElement } from '@vueuse/core'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import type {} from '@vueuse/shared'
|
||||
|
||||
const _useShortcuts = () => {
|
||||
const macOS = computed(() => import.meta.client && navigator && navigator.userAgent && navigator.userAgent.match(/Macintosh;/))
|
||||
|
||||
const metaSymbol = ref(' ')
|
||||
|
||||
const activeElement = useActiveElement()
|
||||
const usingInput = computed(() => {
|
||||
const tagName = activeElement.value?.tagName
|
||||
const contentEditable = activeElement.value?.contentEditable
|
||||
|
||||
const usingInput = !!(tagName === 'INPUT' || tagName === 'TEXTAREA' || contentEditable === 'true' || contentEditable === 'plaintext-only')
|
||||
|
||||
if (usingInput) {
|
||||
return ((activeElement.value as any)?.name as string) || true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
metaSymbol.value = macOS.value ? '⌘' : 'Ctrl'
|
||||
})
|
||||
|
||||
return {
|
||||
macOS,
|
||||
metaSymbol,
|
||||
activeElement,
|
||||
usingInput
|
||||
}
|
||||
}
|
||||
|
||||
export const useShortcuts = createSharedComposable(_useShortcuts)
|
||||
Reference in New Issue
Block a user