UNPKG

duoyun-ui

Version:

A lightweight desktop UI component library, implemented using Gem

248 lines 7.22 kB
import { isNotBoolean } from './types'; import { proxyObject } from './utils'; /** * @see * https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code/code_values */ export function normalizeKey(code) { const s = code.toLowerCase(); if (s.startsWith('control')) return 'ctrl'; if (s.startsWith('meta') || s === 'command' || s.startsWith('os')) return 'meta'; if (s.startsWith('shift')) return 'shift'; if (s.startsWith('alt') || s === 'option') return 'alt'; return s.replace(/^(digit|key|numpad)/, ''); } function appendToMap(keys) { Object.entries(keys).forEach(([named, key]) => { Object.entries(key).forEach(([_, k]) => { map[k] = named; }); }); } const keysInfo = { ctrl: { alias: 'control', macSymbol: '⌃', }, meta: { win: 'win', mac: 'command', macSymbol: '⌘', }, shift: { symbol: '⇧', }, alt: { mac: 'option', macSymbol: '⌥', }, escape: { alias: 'esc', }, backspace: { symbol: '⌫', }, enter: { alias: 'return', symbol: '↵', }, space: { symbol: '␣', }, capsLock: { symbol: '⇪', }, arrowdown: { alias: 'down', symbol: '↓', }, arrowup: { alias: 'up', symbol: '↑', }, arrowleft: { alias: 'left', symbol: '←', }, arrowright: { alias: 'right', symbol: '→', }, minus: { symbol: '-', }, equal: { symbol: '=', }, period: { symbol: '.', }, comma: { symbol: ',', }, slash: { symbol: '/', }, backslash: { symbol: '|', }, bracketleft: { symbol: '[', }, bracketright: { symbol: ']', }, semicolon: { symbol: ';', }, quote: { symbol: "'", }, backquote: { symbol: '`', }, }; const map = proxyObject({}); appendToMap(keysInfo); export const isMac = navigator.platform.includes('Mac'); /**Get the platform button */ export function getDisplayKey(code, type) { const key = normalizeKey(code); const keyObj = keysInfo[key]; let result = undefined; if (!keyObj) { result = key; } else if (type) { result = keyObj[type]; } if (!result) { result = (isMac ? keyObj.macSymbol || keyObj.mac : keyObj.win) || keyObj.symbol || key; } return result.replace(/^(.)/, (_substr, $1) => $1.toUpperCase()); } /**Custom key map */ export function setKeys(keysRecord) { Object.assign(keysInfo, keysRecord); appendToMap(keysRecord); } const hotkeySplitter = /,(?!,)/; // https://bugs.webkit.org/show_bug.cgi?id=174931 // const keySplitter = /(?<!\+)\+/; const keySplitter = /\+/; /**Detect whether the current keyboard event matches the specified button */ export function matchHotKey(evt, hotkey) { const keys = hotkey.split(keySplitter).map((k) => map[k]); const targetKeyEvent = { ctrl: false, meta: false, shift: false, alt: false, namedKey: '' }; keys.forEach((named) => { switch (named) { case 'ctrl': return (targetKeyEvent.ctrl = true); case 'meta': return (targetKeyEvent.meta = true); case 'shift': return (targetKeyEvent.shift = true); case 'alt': return (targetKeyEvent.alt = true); default: return (targetKeyEvent.namedKey = named); } }); let nextKey = ''; if (targetKeyEvent.namedKey.length > 2 && targetKeyEvent.namedKey.includes('-')) { // not support `a--`, `--a`, `a--b`, only allow `a-b` [targetKeyEvent.namedKey, nextKey] = [...targetKeyEvent.namedKey.split('-')]; } const match = evt.ctrlKey === targetKeyEvent.ctrl && evt.metaKey === targetKeyEvent.meta && evt.shiftKey === targetKeyEvent.shift && evt.altKey === targetKeyEvent.alt && (!targetKeyEvent.namedKey || normalizeKey(evt.code) === targetKeyEvent.namedKey || /** * https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values */ evt.key.toLowerCase() === targetKeyEvent.namedKey); return nextKey ? match && nextKey : match; } let locked = false; const unlockCallback = new Set(); /**Release the lock state of the continuous button, see `hotkeys` */ export function unlock() { locked = false; unlockCallback.forEach((callback) => callback()); unlockCallback.clear(); } /** * Must have non-control character; * Not case sensitive; * Support `a-b`, press `a`, hotkeys be locked, wait next `keydown` event, allow call `unlock` */ export function hotkeys(handles, options = {}) { const { stopPropagation, preventDefault = true } = options; return (event) => { if (event.isComposing) return; if (locked) return; let captured = false; const nextKeyHandleSet = new Map(); for (const str in handles) { const handle = handles[str]; if (!handle) break; const shortcuts = str.split(hotkeySplitter).map((e) => e.trim()); const matchResult = shortcuts.map((hotkey) => matchHotKey(event, hotkey)); if (matchResult.some((r) => r === true)) { captured = true; if (preventDefault) event.preventDefault(); if (stopPropagation) event.stopPropagation(); handle(event); } matchResult.filter(isNotBoolean).forEach((key) => { const set = nextKeyHandleSet.get(key) || new Set(); set.add(handle); nextKeyHandleSet.set(key, set); }); } if (nextKeyHandleSet.size) { captured = true; unlockCallback.clear(); handles.onLock?.(event); locked = true; const nextKeyHandle = (evt) => { handles.onUnlock?.(evt); locked = false; evt.stopPropagation(); evt.preventDefault(); let nextKeyCaptured = false; nextKeyHandleSet.forEach((handleSet, k) => { if (matchHotKey(evt, k)) { nextKeyCaptured = true; handleSet.forEach((h) => h(evt)); } }); if (!nextKeyCaptured) handles.onUncapture?.(evt); }; unlockCallback.add(() => removeEventListener('keydown', nextKeyHandle, { capture: true })); addEventListener('keydown', nextKeyHandle, { once: true, capture: true }); } if (!captured) handles.onUncapture?.(event); }; } /** * Support space,enter */ export const commonHandle = hotkeys({ 'space,enter': (evt) => evt.target.click(), esc: (evt) => evt.target.blur(), }); //# sourceMappingURL=hotkeys.js.map