UNPKG

keyboard-i18n

Version:

Internationalization and localization utils for keyboard shortcuts

254 lines (246 loc) 7.26 kB
// src/helpers.ts import { identifyKeyboardLayout } from "keyboard-layout-map"; function isKeyboardEventLike(event) { return !!(event && typeof event === "object" && "key" in event && "code" in event && "shiftKey" in event && "altKey" in event && "ctrlKey" in event && "metaKey" in event && typeof event.key === "string" && typeof event.code === "string"); } function layoutEquals(a, b) { if (a === b) { return true; } if (a && b && identifyKeyboardLayout(a) === identifyKeyboardLayout(b)) { return true; } return false; } // src/localize.ts import { identifyKeyboardLayout as identifyKeyboardLayout2 } from "keyboard-layout-map"; // src/env.ts import { getKeyboardLayout } from "keyboard-layout-map"; var isAppleOS = typeof window !== "undefined" && /Mac|iP(hone|[ao]d)/.test(window.navigator.platform); var layout = null; var updatedAt = 0; function getLayout() { void updateLayout(); return layout; } async function updateLayout() { if (Date.now() - updatedAt < 3e3) { return; } updatedAt = Date.now(); const newLayout = await getKeyboardLayout(); if (!layoutEquals(layout, newLayout)) { layout = newLayout; } } // src/parse.ts function parseShortcut(shortcut, options) { var _a; const apple = (_a = options == null ? void 0 : options.isAppleOS) != null ? _a : isAppleOS; let modifiers; let target; let alt = false; let ctrl = false; let shift = false; let meta = false; if (shortcut.endsWith("++")) { modifiers = shortcut.slice(0, -2).split("+"); target = "+"; } else { const parts = shortcut.split("+"); target = parts.pop(); modifiers = parts; } if (modifiers.includes("shift") || target.startsWith("Shift")) { shift = true; } if (modifiers.includes("ctrl") || target.startsWith("Control")) { ctrl = true; } if (modifiers.includes("alt") || target.startsWith("Alt")) { alt = true; } if (modifiers.includes("meta") || target.startsWith("Meta")) { meta = true; } if (modifiers.includes("mod")) { if (apple) { meta = true; } else { ctrl = true; } } return { target, alt, ctrl, shift, meta }; } // src/localize.ts var defaultLocalizer = (shortcut, layout2) => { var _a; const layoutName = identifyKeyboardLayout2(layout2); const target = (_a = localizeTarget(shortcut.target, layoutName)) != null ? _a : shortcut.target; return { ...shortcut, target }; }; function localizeTarget(target, layout2) { switch (target) { case "BracketLeft": switch (layout2) { case "Spanish": case "Italian": case "German": case "SwissGerman": case "Finnish": case "Portuguese": case "Norwegian": case "Danish": return "Semicolon"; case "LatinAmerican": return "Quote"; case "Dvorak": return "["; } break; case "BracketRight": switch (layout2) { case "Spanish": case "Italian": case "German": case "SwissGerman": case "Finnish": case "Portuguese": case "Norwegian": case "Danish": return "Quote"; case "LatinAmerican": return "Backslash"; case "Dvorak": return "]"; } break; case "Slash": switch (layout2) { case "French": return "Period"; case "Portuguese": case "German": case "SwissGerman": case "SwissFrench": case "Italian": case "LatinAmerican": case "Spanish": return "Minus"; case "Finnish": case "Danish": case "Norwegian": case "Slovak": return "BracketLeft"; case "Dvorak": return "/"; } break; } return null; } function wrapLocalizer(shortcut, options) { const localizer = (options == null ? void 0 : options.localizer) || defaultLocalizer; const parsed = parseShortcut(shortcut, { isAppleOS: options == null ? void 0 : options.isAppleOS }); getLayout(); let layout2 = null; let localized = parsed; return () => { const currentLayout = (options == null ? void 0 : options.layout) || getLayout(); if (currentLayout !== layout2) { layout2 = currentLayout; localized = layout2 ? localizer(parsed, layout2) : parsed; } return localized; }; } // src/checker.ts function createChecker(shortcut, options) { const getLocalized = wrapLocalizer(shortcut, options); return function checker(event) { if (!isKeyboardEventLike(event)) { return false; } const localized = getLocalized(); return eventMatches(event, localized); }; } function eventMatches(event, shortcut) { if (event.isComposing) { return false; } const key = event.key.toLowerCase(); const code = event.code.toLowerCase(); const target = shortcut.target.toLowerCase(); return (key === target || code === target) && event.altKey === shortcut.alt && event.ctrlKey === shortcut.ctrl && event.shiftKey === shortcut.shift && event.metaKey === shortcut.meta; } // src/formatter.ts import { US as USKeyboardLayoutMap } from "keyboard-layout-map/layouts"; function createFormatter(shortcut, options) { const getLocalized = wrapLocalizer(shortcut, options); return function formatter() { var _a, _b, _c; const localized = getLocalized(); const layout2 = (_b = (_a = options == null ? void 0 : options.layout) != null ? _a : getLayout()) != null ? _b : USKeyboardLayoutMap; const apple = (_c = options == null ? void 0 : options.isAppleOS) != null ? _c : isAppleOS; return formatParsed(localized, layout2, apple); }; } function formatParsed(parsed, layout2, isAppleOS2) { var _a; const key = (_a = layout2.get(parsed.target)) != null ? _a : parsed.target; return isAppleOS2 ? [ // Use the order of Control -> Option -> Shift -> Command // // Reference: https://support.apple.com/en-us/HT201236 parsed.ctrl ? "Control" : "", parsed.alt ? "Option" : "", parsed.shift ? "Shift" : "", parsed.meta ? "Command" : "", key ].filter(Boolean) : [ // Use the order of Win -> Ctrl -> Alt -> Shift // // Reference: https://support.microsoft.com/en-us/windows/keyboard-shortcuts-in-windows-dcc61a57-8ff0-cffe-9796-cb9706c75eec parsed.meta ? "Win" : "", parsed.ctrl ? "Ctrl" : "", parsed.alt ? "Alt" : "", parsed.shift ? "Shift" : "", key ].filter(Boolean); } // src/handler.ts import { getActiveElement, isHTMLElement } from "@zag-js/dom-query"; function createHandler(shortcut, callback, options) { const shortcuts = Array.isArray(shortcut) ? shortcut : [shortcut]; const checkers = shortcuts.map( (shortcut2) => createChecker(shortcut2, options) ); return function handler(event) { var _a; for (const checker of checkers) { if (checker(event)) { if (event.key === "Dead") { const target = event.target; if (target && isHTMLElement(target)) { (_a = getActiveElement(target)) == null ? void 0 : _a.blur(); } } callback(event); return; } } }; } export { createChecker, createFormatter, createHandler, defaultLocalizer };