From a343a9035608d27c37fd29e3e936e1d806c51035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B7=AF=E6=8C=AF=E5=87=AF?= Date: Tue, 30 Sep 2025 14:18:59 +0800 Subject: [PATCH 1/2] feat: support keyborad change --- src/Tour.tsx | 52 +++++++++++++++++++++++++++++++++++++++++------- src/interface.ts | 1 + 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/Tour.tsx b/src/Tour.tsx index 7c4afeb..a624126 100644 --- a/src/Tour.tsx +++ b/src/Tour.tsx @@ -4,7 +4,8 @@ import type { TriggerRef } from '@rc-component/trigger'; import Trigger from '@rc-component/trigger'; import { clsx } from 'clsx'; import useLayoutEffect from '@rc-component/util/lib/hooks/useLayoutEffect'; -import useMergedState from '@rc-component/util/lib/hooks/useMergedState'; +import useEvent from '@rc-component/util/lib/hooks/useEvent'; +import KeyCode from '@rc-component/util/lib/KeyCode'; import useControlledState from '@rc-component/util/lib/hooks/useControlledState'; import { useMemo } from 'react'; import { useClosable } from './hooks/useClosable'; @@ -35,6 +36,7 @@ const Tour: React.FC = props => { steps = [], defaultCurrent, current, + keyboard = true, onChange, onClose, onFinish, @@ -88,7 +90,7 @@ const Tour: React.FC = props => { setHasOpened(true); } openRef.current = mergedOpen; - }, [mergedOpen]); + }, [mergedOpen, setMergedCurrent]); const { target, @@ -156,6 +158,47 @@ const Tour: React.FC = props => { } return getPlacements(arrowPointAtCenter); }, [builtinPlacements, arrowPointAtCenter]); + const handleClose = () => { + setMergedOpen(false); + onClose?.(mergedCurrent); + }; + + // ========================= Keyboard ========================= + // Support Esc to close (if closable) and ArrowLeft/ArrowRight to navigate steps. + const keyboardHandler = useEvent((e: KeyboardEvent) => { + if (keyboard && e.keyCode === KeyCode.ESC) { + if (mergedClosable !== null) { + e.stopPropagation(); + e.preventDefault(); + handleClose(); + } + return; + } + + if (keyboard && e.keyCode === KeyCode.LEFT) { + if (mergedCurrent > 0) { + e.preventDefault(); + onInternalChange(mergedCurrent - 1); + } + return; + } + + if (keyboard && e.keyCode === KeyCode.RIGHT) { + e.preventDefault(); + if (mergedCurrent < steps.length - 1) { + onInternalChange(mergedCurrent + 1); + } + return; + } + }); + + useLayoutEffect(() => { + if (!mergedOpen) return; + window.addEventListener('keydown', keyboardHandler); + return () => { + window.removeEventListener('keydown', keyboardHandler); + }; + }, [mergedOpen, keyboardHandler]); // ========================= Render ========================= // Skip if not init yet @@ -163,11 +206,6 @@ const Tour: React.FC = props => { return null; } - const handleClose = () => { - setMergedOpen(false); - onClose?.(mergedCurrent); - }; - const getPopupElement = () => ( { style?: React.CSSProperties; steps?: TourStepInfo[]; open?: boolean; + keyboard?: boolean; defaultOpen?: boolean; defaultCurrent?: number; current?: number; From 551f26c7ef6aaf0592ea77f4227616e4c3733f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B7=AF=E6=8C=AF=E5=87=AF?= Date: Tue, 30 Sep 2025 14:37:25 +0800 Subject: [PATCH 2/2] update: editable target exclude --- src/Tour.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Tour.tsx b/src/Tour.tsx index a624126..7260720 100644 --- a/src/Tour.tsx +++ b/src/Tour.tsx @@ -166,6 +166,17 @@ const Tour: React.FC = props => { // ========================= Keyboard ========================= // Support Esc to close (if closable) and ArrowLeft/ArrowRight to navigate steps. const keyboardHandler = useEvent((e: KeyboardEvent) => { + // Ignore keyboard events from input-like elements to avoid interfering when typing + const el = e.target as HTMLElement | null; + if ( + el?.tagName === 'INPUT' || + el?.tagName === 'TEXTAREA' || + el?.tagName === 'SELECT' || + el?.isContentEditable + ) { + return; + } + if (keyboard && e.keyCode === KeyCode.ESC) { if (mergedClosable !== null) { e.stopPropagation(); @@ -184,8 +195,8 @@ const Tour: React.FC = props => { } if (keyboard && e.keyCode === KeyCode.RIGHT) { - e.preventDefault(); if (mergedCurrent < steps.length - 1) { + e.preventDefault(); onInternalChange(mergedCurrent + 1); } return;