UNPKG

@lobehub/ui

Version:

Lobe UI is an open-source UI component library for building AIGC web apps

195 lines (192 loc) 6.4 kB
'use client'; import FlexBasic_default from "../Flex/FlexBasic.mjs"; import { NORMATIVE_MODIFIER, checkIsAppleDevice, splitKeysByPlus } from "../Hotkey/utils.mjs"; import Hotkey_default from "../Hotkey/Hotkey.mjs"; import ActionIcon_default from "../ActionIcon/ActionIcon.mjs"; import { useTranslation } from "../i18n/useTranslation.mjs"; import hotkey_default from "../i18n/resources/en/hotkey.mjs"; import { styles, variants } from "./style.mjs"; import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { jsx, jsxs } from "react/jsx-runtime"; import { cx, useThemeMode } from "antd-style"; import useMergeState from "use-merge-value"; import { isEqual } from "es-toolkit/compat"; import { Undo2Icon } from "lucide-react"; import { useHotkeys, useRecordHotkeys } from "react-hotkeys-hook"; //#region src/HotkeyInput/HotkeyInput.tsx const HotkeyInput = memo(({ value = "", defaultValue = "", resetValue = "", onChange, onConflict, placeholder, disabled, shadow, allowReset = true, style, className, hotkeyConflicts = [], variant, texts, isApple, onBlur, onReset, onFocus }) => { const [isFocused, setIsFocused] = useState(false); const [hasConflict, setHasConflict] = useState(false); const [hasInvalidCombination, setHasInvalidCombination] = useState(false); const inputRef = useRef(null); const { isDarkMode } = useThemeMode(); const { t } = useTranslation(hotkey_default); const isAppleDevice = useMemo(() => checkIsAppleDevice(isApple), [isApple]); const [hotkeyValue, setHotkeyValue] = useMergeState(defaultValue, { defaultValue, onChange, value }); const [recordedKeys, { start, stop, isRecording, resetKeys }] = useRecordHotkeys(); useHotkeys("*", () => { inputRef.current?.blur(); }, { enableOnContentEditable: true, enableOnFormTags: true, enabled: isRecording && !disabled, keydown: false, keyup: true, preventDefault: true }); const { isValid, keys } = useCallback((keysSet) => { const modifiers = []; const normalKeys = []; for (const key of keysSet) { const normalizedKey = key.toLowerCase(); if (NORMATIVE_MODIFIER.includes(normalizedKey)) { if (!isAppleDevice && normalizedKey === "ctrl" || isAppleDevice && normalizedKey === "meta") { if (!modifiers.includes("mod")) modifiers.push("mod"); } else if (!modifiers.includes(normalizedKey)) modifiers.push(normalizedKey); } else normalKeys.push(key); } if (modifiers.length === 0 && normalKeys.length > 0) return { isValid: false, keys: [] }; const shortcuts = [modifiers, normalKeys.length > 0 ? [normalKeys.at(-1)] : []]; return { isValid: shortcuts.every((k) => k.length > 0), keys: shortcuts.flat() }; }, [])(recordedKeys); const keysString = keys.join("+"); const checkHotkeyConflict = useCallback((newHotkey) => { return hotkeyConflicts.filter((conflictKey) => conflictKey !== resetValue).some((conflictKey) => { return isEqual(splitKeysByPlus(newHotkey), splitKeysByPlus(conflictKey)); }); }, [hotkeyConflicts]); useEffect(() => { if (recordedKeys.size > 0 && !isRecording) { if (!isValid) { setHasInvalidCombination(true); setHasConflict(false); return; } setHasInvalidCombination(false); const newKeysString = keysString; if (checkHotkeyConflict(newKeysString)) { setHasConflict(true); onConflict?.(newKeysString); } else { setHasConflict(false); setHotkeyValue?.(newKeysString); } } }, [ recordedKeys, isRecording, isValid, keysString, checkHotkeyConflict, setHotkeyValue, onConflict ]); const handleFocus = (e) => { if (disabled) return; setIsFocused(true); setHasConflict(false); setHasInvalidCombination(false); start(); onFocus?.(e); }; const handleBlur = (e) => { setIsFocused(false); stop(); onBlur?.(e); }; const handleReset = (e) => { e.preventDefault(); e.stopPropagation(); setHotkeyValue?.(resetValue); resetKeys(); setHasConflict(false); setHasInvalidCombination(false); setIsFocused(false); stop(); onReset?.(hotkeyValue, resetValue); }; const handleClick = (e) => { e.preventDefault(); e.stopPropagation(); if (disabled || isFocused) return; inputRef.current?.focus(); }; const placeholderText = placeholder ?? t("hotkey.placeholder"); const resetTitle = texts?.reset ?? t("hotkey.reset"); const conflictText = texts?.conflicts ?? t("hotkey.conflict"); const invalidText = texts?.invalidCombination ?? t("hotkey.invalidCombination"); return /* @__PURE__ */ jsxs(FlexBasic_default, { className, gap: 8, style: { position: "relative", ...style }, children: [ /* @__PURE__ */ jsxs(FlexBasic_default, { align: "center", className: cx(variants({ disabled, error: hasConflict || hasInvalidCombination, focused: isFocused, shadow, variant: variant || (isDarkMode ? "filled" : "outlined") })), horizontal: true, justify: "space-between", onClick: handleClick, children: [ /* @__PURE__ */ jsx("div", { style: { pointerEvents: "none" }, children: isRecording ? /* @__PURE__ */ jsx("span", { className: styles.placeholder, children: keys.length > 0 ? /* @__PURE__ */ jsx(Hotkey_default, { keys: keysString }) : placeholderText }) : hotkeyValue ? /* @__PURE__ */ jsx(Hotkey_default, { keys: hotkeyValue }) : /* @__PURE__ */ jsx("span", { className: styles.placeholder, children: placeholderText }) }), /* @__PURE__ */ jsx("input", { className: styles.hiddenInput, disabled, onBlur: handleBlur, onFocus: handleFocus, readOnly: true, ref: inputRef, style: { pointerEvents: "none" } }), !isFocused && allowReset && hotkeyValue && hotkeyValue !== resetValue && !disabled && /* @__PURE__ */ jsx(ActionIcon_default, { icon: Undo2Icon, onClick: handleReset, size: "small", title: resetTitle, variant: "filled" }) ] }), hasConflict && /* @__PURE__ */ jsx("div", { className: styles.errorText, children: conflictText }), hasInvalidCombination && /* @__PURE__ */ jsx("div", { className: styles.errorText, children: invalidText }) ] }); }); HotkeyInput.displayName = "HotkeyInput"; var HotkeyInput_default = HotkeyInput; //#endregion export { HotkeyInput_default as default }; //# sourceMappingURL=HotkeyInput.mjs.map