diff --git a/packages/react-keytips/src/components/Keytip/Keytip.types.ts b/packages/react-keytips/src/components/Keytip/Keytip.types.ts index 2500e31d..352e32ce 100644 --- a/packages/react-keytips/src/components/Keytip/Keytip.types.ts +++ b/packages/react-keytips/src/components/Keytip/Keytip.types.ts @@ -17,13 +17,13 @@ export type KeytipSlots = { content: NonNullable>; }; -export type ExecuteKeytipEventHandler = EventHandler< +export type ExecuteKeytipEventHandler = EventHandler< EventData & { targetElement: E; } >; -export type ReturnKeytipEventHandler = EventHandler< +export type ReturnKeytipEventHandler = EventHandler< EventData & { targetElement: E; } @@ -59,8 +59,11 @@ export type KeytipProps = ComponentProps & { dynamic?: boolean; }; +/** @internal */ export type KeytipWithId = KeytipProps & { uniqueId: string; + isShortcut?: boolean; + dependentKeys?: string[]; }; export type KeytipState = ComponentState & diff --git a/packages/react-keytips/src/components/Keytip/useKeytipStyles.styles.ts b/packages/react-keytips/src/components/Keytip/useKeytipStyles.styles.ts index bdd7bffd..e52baef7 100644 --- a/packages/react-keytips/src/components/Keytip/useKeytipStyles.styles.ts +++ b/packages/react-keytips/src/components/Keytip/useKeytipStyles.styles.ts @@ -6,6 +6,7 @@ import { } from '@fluentui/react-components'; import { KeytipSlots, KeytipState } from './Keytip.types'; import { createSlideStyles } from '@fluentui/react-positioning'; +import { SHOW_DELAY } from '../../constants'; export const keytipClassNames: SlotClassNames = { content: 'fui-Keytip__content', @@ -30,7 +31,7 @@ const useStyles = makeStyles({ backgroundColor: tokens.colorNeutralBackgroundInverted, color: tokens.colorNeutralForegroundInverted, boxShadow: tokens.shadow16, - ...createSlideStyles(15), + ...createSlideStyles(SHOW_DELAY), }, visible: { diff --git a/packages/react-keytips/src/components/Keytips/useKeytips.tsx b/packages/react-keytips/src/components/Keytips/useKeytips.tsx index 8d480b5a..d137828c 100644 --- a/packages/react-keytips/src/components/Keytips/useKeytips.tsx +++ b/packages/react-keytips/src/components/Keytips/useKeytips.tsx @@ -2,11 +2,18 @@ import * as React from 'react'; import { getIntrinsicElementProps, slot, + useIsomorphicLayoutEffect, useFluent, + useTimeout, } from '@fluentui/react-components'; import type { KeytipsProps, KeytipsState } from './Keytips.types'; import { useHotkeys, parseHotkey } from '../../hooks/useHotkeys'; -import { EVENTS, VISUALLY_HIDDEN_STYLES, ACTIONS } from '../../constants'; +import { + KTP_SEPARATOR, + EVENTS, + VISUALLY_HIDDEN_STYLES, + ACTIONS, +} from '../../constants'; import type { KeytipWithId } from '../Keytip'; import { Keytip } from '../Keytip'; import { useEventService } from '../../hooks/useEventService'; @@ -32,9 +39,10 @@ export const useKeytips_unstable = (props: KeytipsProps): KeytipsState => { invokeEvent = 'keydown', startDelay = 0, } = props; - const { subscribe, reset } = useEventService(); + const { subscribe, reset, dispatch: dispatchEvent } = useEventService(); const [state, dispatch] = useKeytipsState(); const tree = useTree(); + const [setShortcutTimeout, clearShortcutTimeout] = useTimeout(); const showKeytips = React.useCallback((ids: string[]) => { dispatch({ type: ACTIONS.SET_VISIBLE_KEYTIPS, ids, targetDocument }); @@ -44,7 +52,6 @@ export const useKeytips_unstable = (props: KeytipsProps): KeytipsState => { (ev: KeyboardEvent) => { if (!state.inKeytipMode) { tree.currentKeytip.current = tree.root; - dispatch({ type: ACTIONS.ENTER_KEYTIP_MODE }); onEnterKeytipsMode?.(ev, { event: ev, type: 'keydown' }); showKeytips(tree.getChildren()); @@ -72,46 +79,56 @@ export const useKeytips_unstable = (props: KeytipsProps): KeytipsState => { const handleReturnSequence = React.useCallback( (ev: KeyboardEvent) => { if (!state.inKeytipMode) return; - const currentKeytip = tree.currentKeytip?.current; + const currentKeytip = tree.currentKeytip.current; if (currentKeytip && currentKeytip.target) { - if (currentKeytip.target) { - currentKeytip?.onReturn?.(ev, { - event: ev, - type: invokeEvent, - targetElement: currentKeytip.target, - }); - } + currentKeytip?.onReturn?.(ev, { + event: ev, + type: invokeEvent, + targetElement: currentKeytip.target, + }); } dispatch({ type: ACTIONS.SET_SEQUENCE, value: '' }); tree.getBack(); showKeytips(tree.getChildren()); if (tree.currentKeytip.current === undefined) { - dispatch({ type: ACTIONS.EXIT_KEYTIP_MODE }); + handleExitKeytipMode(ev); } }, [state.inKeytipMode] ); + const exitSequences = [ + exitSequence, + 'enter', + 'space', + state.inKeytipMode ? 'tab' : '', + ]; + useHotkeys( [ [startSequence, handleEnterKeytipMode, { delay: startDelay }], [returnSequence, handleReturnSequence], - ...[exitSequence, 'tab', 'enter', 'space'].map( - (key) => [key, handleExitKeytipMode] as Hotkey - ), + ...exitSequences.map((key) => [key, handleExitKeytipMode] as Hotkey), ], invokeEvent ); - React.useEffect(() => { + useIsomorphicLayoutEffect(() => { const handleKeytipAdded = (keytip: KeytipWithId) => { tree.addNode(keytip); - dispatch({ - type: ACTIONS.ADD_KEYTIP, - keytip, - }); + if (keytip.isShortcut) { + dispatch({ + type: ACTIONS.ADD_SHORTCUT, + shortcut: keytip, + }); + } else { + dispatch({ + type: ACTIONS.ADD_KEYTIP, + keytip, + }); + } if (tree.isCurrentKeytipParent(keytip)) { showKeytips(tree.getChildren()); @@ -120,7 +137,12 @@ export const useKeytips_unstable = (props: KeytipsProps): KeytipsState => { const handleKeytipRemoved = (keytip: KeytipWithId) => { tree.removeNode(keytip.uniqueId); - dispatch({ type: ACTIONS.REMOVE_KEYTIP, id: keytip.uniqueId }); + + if (keytip.isShortcut) { + dispatch({ type: ACTIONS.REMOVE_SHORTCUT, id: keytip.uniqueId }); + } else { + dispatch({ type: ACTIONS.REMOVE_KEYTIP, id: keytip.uniqueId }); + } }; const handleKeytipUpdated = (keytip: KeytipWithId) => { @@ -130,15 +152,17 @@ export const useKeytips_unstable = (props: KeytipsProps): KeytipsState => { }; subscribe(EVENTS.KEYTIP_ADDED, handleKeytipAdded); + subscribe(EVENTS.SHORTCUT_ADDED, handleKeytipAdded); subscribe(EVENTS.KEYTIP_UPDATED, handleKeytipUpdated); subscribe(EVENTS.KEYTIP_REMOVED, handleKeytipRemoved); + subscribe(EVENTS.SHORTCUT_REMOVED, handleKeytipRemoved); return () => { reset(); }; - }, []); + }, [state.inKeytipMode]); - React.useEffect(() => { + useIsomorphicLayoutEffect(() => { const controller = new AbortController(); const { signal } = controller; @@ -148,6 +172,7 @@ export const useKeytips_unstable = (props: KeytipsProps): KeytipsState => { } }; + targetDocument?.addEventListener('mousedown', handleDismiss, { signal }); targetDocument?.addEventListener('mouseup', handleDismiss, { signal }); targetDocument?.defaultView?.addEventListener('resize', handleDismiss, { signal, @@ -161,7 +186,8 @@ export const useKeytips_unstable = (props: KeytipsProps): KeytipsState => { }; }, [state.inKeytipMode, targetDocument, handleExitKeytipMode]); - const handleMatchingNode = React.useCallback( + // executes any normal keytip, except shortcuts + const handleKeytipExecution = React.useCallback( (ev: KeyboardEvent, node: KeytipTreeNode) => { tree.currentKeytip.current = node; @@ -171,8 +197,9 @@ export const useKeytips_unstable = (props: KeytipsProps): KeytipsState => { type: invokeEvent, targetElement: node.target, }); - } + dispatchEvent(EVENTS.KEYTIP_EXECUTED, node); + } const currentChildren = tree.getChildren(node); const shouldExitKeytipMode = currentChildren.length === 0 && !node.dynamic; @@ -189,6 +216,51 @@ export const useKeytips_unstable = (props: KeytipsProps): KeytipsState => { [handleExitKeytipMode] ); + // executes keytip that was triggered via shortcut + const handleShortcutExecution = React.useCallback( + async (ev: KeyboardEvent, node: KeytipTreeNode) => { + const { dependentKeys } = node; + + if (!targetDocument) return; + + const fullPath = [...dependentKeys, ...node.keySequences].reduce< + string[] + >((acc, key, idx) => { + if (idx === 0) acc.push(sequencesToID([key])); + else + acc.push( + acc[idx - 1] + KTP_SEPARATOR + key.split('').join(KTP_SEPARATOR) + ); + return acc; + }, []); + + // Sequentially execute each keytip in the path + for (const id of fullPath) { + clearShortcutTimeout(); + + await new Promise((resolve) => { + setShortcutTimeout(() => { + const currentNode = tree.getNode(id); + + if (currentNode) { + currentNode.onExecute?.(ev, { + event: ev, + type: invokeEvent, + targetElement: currentNode.target, + }); + + tree.currentKeytip.current = currentNode; + dispatchEvent(EVENTS.KEYTIP_EXECUTED, currentNode); + } + // Proceed to the next keytip + resolve(currentNode); + }, 0); + }); + } + }, + [handleExitKeytipMode] + ); + const handlePartiallyMatchedNodes = React.useCallback((sequence: string) => { const partialNodes = tree.getPartiallyMatched(sequence); if (partialNodes && partialNodes.length > 0) { @@ -199,11 +271,10 @@ export const useKeytips_unstable = (props: KeytipsProps): KeytipsState => { } }, []); - React.useEffect(() => { + useIsomorphicLayoutEffect(() => { if (!targetDocument) return; const handleInvokeEvent = (ev: KeyboardEvent) => { - ev.preventDefault(); ev.stopPropagation(); if (!state.inKeytipMode) return; @@ -213,23 +284,29 @@ export const useKeytips_unstable = (props: KeytipsProps): KeytipsState => { const node = tree.getMatchingNode(currSeq); if (node) { - handleMatchingNode(ev, node); - return; + if (node.isShortcut) { + handleShortcutExecution(ev, node); + } else { + handleKeytipExecution(ev, node); + } + } else { + // if we don't have a match, we have to check if the sequence is a partial match + handlePartiallyMatchedNodes(currSeq); } - // if we don't have a match, we have to check if the sequence is a partial match - handlePartiallyMatchedNodes(currSeq); }; targetDocument?.addEventListener(invokeEvent, handleInvokeEvent); return () => { targetDocument?.removeEventListener(invokeEvent, handleInvokeEvent); + clearShortcutTimeout(); }; }, [ state.inKeytipMode, state.currentSequence, handleExitKeytipMode, handlePartiallyMatchedNodes, - handleMatchingNode, + handleShortcutExecution, + handleKeytipExecution, ]); const visibleKeytips = Object.entries(state.keytips) diff --git a/packages/react-keytips/src/components/Keytips/useKeytipsState.ts b/packages/react-keytips/src/components/Keytips/useKeytipsState.ts index f1b782e5..0a29b1fc 100644 --- a/packages/react-keytips/src/components/Keytips/useKeytipsState.ts +++ b/packages/react-keytips/src/components/Keytips/useKeytipsState.ts @@ -12,13 +12,19 @@ type KeytipsState = { }; type KeytipsAction = - | { type: 'ENTER_KEYTIP_MODE' } - | { type: 'EXIT_KEYTIP_MODE' } - | { type: 'ADD_KEYTIP'; keytip: KeytipWithId } - | { type: 'UPDATE_KEYTIP'; keytip: KeytipWithId } - | { type: 'REMOVE_KEYTIP'; id: string } - | { type: 'SET_VISIBLE_KEYTIPS'; ids: string[]; targetDocument?: Document } - | { type: 'SET_SEQUENCE'; value: string }; + | { type: typeof ACTIONS.ENTER_KEYTIP_MODE } + | { type: typeof ACTIONS.EXIT_KEYTIP_MODE } + | { type: typeof ACTIONS.ADD_KEYTIP; keytip: KeytipWithId } + | { type: typeof ACTIONS.UPDATE_KEYTIP; keytip: KeytipWithId } + | { type: typeof ACTIONS.REMOVE_KEYTIP; id: string } + | { type: typeof ACTIONS.ADD_SHORTCUT; shortcut: KeytipWithId } + | { type: typeof ACTIONS.REMOVE_SHORTCUT; id: string } + | { + type: typeof ACTIONS.SET_VISIBLE_KEYTIPS; + ids: string[]; + targetDocument?: Document; + } + | { type: typeof ACTIONS.SET_SEQUENCE; value: string }; const stateReducer: React.Reducer = ( state, @@ -29,7 +35,11 @@ const stateReducer: React.Reducer = ( return { ...state, inKeytipMode: true }; } case ACTIONS.EXIT_KEYTIP_MODE: { - return { ...state, inKeytipMode: false, currentSequence: '' }; + return { + ...state, + inKeytipMode: false, + currentSequence: '', + }; } case ACTIONS.ADD_KEYTIP: { return { diff --git a/packages/react-keytips/src/constants.ts b/packages/react-keytips/src/constants.ts index c06f912f..c7044cbd 100644 --- a/packages/react-keytips/src/constants.ts +++ b/packages/react-keytips/src/constants.ts @@ -13,12 +13,17 @@ export const KTP_SEPARATOR = '-'; export const DATAKTP_TARGET = 'data-ktp-target'; export const KTP_ROOT_ID = 'ktp'; export const KEYTIP_BORDER_RADIUS = 4; -export const SHOW_DELAY = 250; +export const SHOW_DELAY = 30; +export const INVISIBLE_KEYTIPS_ID = 'invisible-keytips-wrapper'; export const EVENTS = { KEYTIP_ADDED: 'fui-keytip-added', KEYTIP_REMOVED: 'fui-keytip-removed', KEYTIP_UPDATED: 'fui-keytip-updated', + KEYTIP_EXECUTED: 'fui-keytip-executed', + SHORTCUT_ADDED: 'fui-shortcut-added', + SHORTCUT_REMOVED: 'fui-shortcut-removed', + SHORTCUT_EXECUTED: 'fui-shortcut-executed', ENTER_KEYTIP_MODE: 'fui-enter-keytip-mode', EXIT_KEYTIP_MODE: 'fui-exit-keytip-mode', } as const; @@ -47,7 +52,9 @@ export const ACTIONS = { ENTER_KEYTIP_MODE: 'ENTER_KEYTIP_MODE', EXIT_KEYTIP_MODE: 'EXIT_KEYTIP_MODE', ADD_KEYTIP: 'ADD_KEYTIP', + ADD_SHORTCUT: 'ADD_SHORTCUT', REMOVE_KEYTIP: 'REMOVE_KEYTIP', + REMOVE_SHORTCUT: 'REMOVE_SHORTCUT', UPDATE_KEYTIP: 'UPDATE_KEYTIP', SET_VISIBLE_KEYTIPS: 'SET_VISIBLE_KEYTIPS', SET_SEQUENCE: 'SET_SEQUENCE', diff --git a/packages/react-keytips/src/hooks/useEventService.ts b/packages/react-keytips/src/hooks/useEventService.ts index b61910d7..bed9c327 100644 --- a/packages/react-keytips/src/hooks/useEventService.ts +++ b/packages/react-keytips/src/hooks/useEventService.ts @@ -2,6 +2,7 @@ import { useCallback, useRef, useEffect } from 'react'; import { useFluent } from '@fluentui/react-components'; import { EVENTS } from '../constants'; import type { KeytipWithId } from '../components/Keytip'; +import type { KeytipTreeNode } from '../hooks/useTree'; type EventType = (typeof EVENTS)[keyof typeof EVENTS]; @@ -11,6 +12,10 @@ type PayloadDefinition = { [EVENTS.KEYTIP_UPDATED]: KeytipWithId; [EVENTS.KEYTIP_ADDED]: KeytipWithId; [EVENTS.KEYTIP_REMOVED]: KeytipWithId; + [EVENTS.KEYTIP_EXECUTED]: KeytipTreeNode; + [EVENTS.SHORTCUT_ADDED]: KeytipWithId; + [EVENTS.SHORTCUT_REMOVED]: KeytipWithId; + [EVENTS.SHORTCUT_EXECUTED]: KeytipTreeNode; }; function isCustomEvent(event: Event): event is CustomEvent { diff --git a/packages/react-keytips/src/hooks/useMenuShortcut.ts b/packages/react-keytips/src/hooks/useMenuShortcut.ts new file mode 100644 index 00000000..34731d2f --- /dev/null +++ b/packages/react-keytips/src/hooks/useMenuShortcut.ts @@ -0,0 +1,38 @@ +import * as React from 'react'; +import { useEventService } from './useEventService'; +import { EVENTS } from '../constants'; +import type { KeytipWithId } from '../components/Keytip'; +import { useIsomorphicLayoutEffect } from '@fluentui/react-utilities'; + +type ShortcutProps = Pick & { + shortcut: string; +}; + +export const useMenuShortcut = ({ + dependentKeys = [], + onExecute, + shortcut, +}: ShortcutProps) => { + const uniqueId = React.useId(); + const { dispatch } = useEventService(); + + const node = React.useMemo( + () => ({ + uniqueId, + isShortcut: true, + keySequences: [shortcut], + content: '', + dependentKeys, + onExecute, + }), + [dependentKeys, shortcut] + ); + + useIsomorphicLayoutEffect(() => { + dispatch(EVENTS.SHORTCUT_ADDED, node); + + return () => { + dispatch(EVENTS.SHORTCUT_REMOVED, node); + }; + }, []); +}; diff --git a/packages/react-keytips/src/hooks/useTree.test.ts b/packages/react-keytips/src/hooks/useTree.test.ts index c713eb42..8863fc91 100644 --- a/packages/react-keytips/src/hooks/useTree.test.ts +++ b/packages/react-keytips/src/hooks/useTree.test.ts @@ -117,6 +117,7 @@ describe('useTree', () => { expect(result.current.root).toEqual({ id: KTP_ROOT_ID, children: new Set(), + dependentKeys: [], target: null, parent: '', keySequences: [], diff --git a/packages/react-keytips/src/hooks/useTree.ts b/packages/react-keytips/src/hooks/useTree.ts index 249350ab..d942a8b2 100644 --- a/packages/react-keytips/src/hooks/useTree.ts +++ b/packages/react-keytips/src/hooks/useTree.ts @@ -10,6 +10,8 @@ export type KeytipTreeNode = Pick< > & { id: string; uniqueId: string; + isShortcut?: boolean; + dependentKeys: string[]; target: HTMLElement | null; parent: string; children: Set; @@ -29,6 +31,7 @@ export function useTree() { children: new Set(), target: null, parent: '', + dependentKeys: [], keySequences: [], }), [] @@ -40,9 +43,9 @@ export function useTree() { const currentKeytip = React.useRef(); - const addNode = React.useCallback((keytip: KeytipWithId) => { + const addNode = React.useCallback((newNode: KeytipWithId) => { const node = createNode({ - ...keytip, + ...newNode, nodeMap: nodeMap.current, }); @@ -102,6 +105,7 @@ export function useTree() { const getMatchingNode = React.useCallback( (sequence: string) => { const { current } = currentKeytip; + if (!current) { return undefined; } @@ -114,9 +118,13 @@ export function useTree() { return undefined; } - currentKeytip.current = matchingNodes[0]; + const matched = matchingNodes[0]; + + if (!matched) return undefined; - return matchingNodes[0]; + currentKeytip.current = matched; + + return matched; }, [targetDocument] ); @@ -161,9 +169,13 @@ export function useTree() { return currentKeytip.current.id === parentID; }, []); + const getNode = (id: string) => + [...nodeMap.current.values()].find((node) => node.id === id); + return { nodeMap, addNode, + getNode, updateNode, root, currentKeytip, diff --git a/packages/react-keytips/src/index.ts b/packages/react-keytips/src/index.ts index 73c24b51..12c1250c 100644 --- a/packages/react-keytips/src/index.ts +++ b/packages/react-keytips/src/index.ts @@ -20,6 +20,10 @@ export { useKeytips_unstable, } from './components/Keytips'; +export { EVENTS } from './constants'; + export type { KeytipsProps, KeytipsSlots, KeytipsState } from './Keytips'; export { useKeytipRef } from './hooks/useKeytipRef'; +export { useMenuShortcut } from './hooks/useMenuShortcut'; +export { useEventService } from './hooks/useEventService'; diff --git a/packages/react-keytips/src/utilities/createNode.ts b/packages/react-keytips/src/utilities/createNode.ts index 279d419d..c73dc2b2 100644 --- a/packages/react-keytips/src/utilities/createNode.ts +++ b/packages/react-keytips/src/utilities/createNode.ts @@ -8,10 +8,14 @@ export const createNode = ({ onExecute, onReturn, dynamic, + isShortcut, nodeMap, positioning, + dependentKeys = [], }: KeytipWithId & { nodeMap: Map; + isShortcut?: boolean; + dependentKeys?: string[]; }): KeytipTreeNode => { const id = sequencesToID(keySequences); const parent = @@ -34,10 +38,12 @@ export const createNode = ({ target: positioning?.target as HTMLElement, parent, children, + dependentKeys, keySequences: keySequences.map((key) => key.toLowerCase()), onExecute, onReturn, dynamic, + isShortcut, }; return node; diff --git a/packages/react-keytips/stories/Default.stories.tsx b/packages/react-keytips/stories/Default.stories.tsx index c69707c0..56b26c4a 100644 --- a/packages/react-keytips/stories/Default.stories.tsx +++ b/packages/react-keytips/stories/Default.stories.tsx @@ -40,25 +40,27 @@ const useStyles = makeStyles({ const onExecute: ExecuteKeytipEventHandler = (_, { targetElement }) => { if (targetElement) { + console.info(targetElement.getAttribute('aria-3describedby')); + targetElement.focus(); targetElement.click(); } }; const SplitButtonComponent = () => { const splitButton = useKeytipRef({ - keySequences: ['b3'], - content: 'B3', + keySequences: ['1d'], + content: '1D', onExecute, }); const menuItemA = useKeytipRef({ - keySequences: ['b3', '1'], + keySequences: ['1d', '1'], content: '1', onExecute: () => alert('Item A'), }); const menuItemB = useKeytipRef({ - keySequences: ['b3', '2'], + keySequences: ['1d', '2'], content: '2', onExecute: () => alert('Item B'), }); @@ -90,19 +92,19 @@ const SplitButtonComponent = () => { const MenuButtonComponent = () => { const menuRef = useKeytipRef({ - keySequences: ['2a'], - content: '2A', + keySequences: ['1b'], + content: '1B', onExecute, }); const firstMenuItemRef = useKeytipRef({ - keySequences: ['2a', 'e'], + keySequences: ['1b', 'e'], content: 'E', onExecute, }); const secondMenuItemRef = useKeytipRef({ - keySequences: ['2a', 'f'], + keySequences: ['1b', 'f'], content: 'F', onExecute, }); @@ -127,27 +129,27 @@ export const DefaultStory = () => { const classes = useStyles(); const disabledButton = useKeytipRef({ - keySequences: ['b0'], - content: 'B0', + keySequences: ['1a'], + content: '1A', onExecute, }); const normalButton = useKeytipRef({ - keySequences: ['b1'], - content: 'B1', + keySequences: ['1C'], + content: '1C', onExecute, }); const compoundButton = useKeytipRef({ - keySequences: ['b2'], - content: 'B2', + keySequences: ['1E'], + content: '1E', onExecute, }); const offsetButton = useKeytipRef({ - keySequences: ['b4'], + keySequences: ['ee'], positioning: { offset: { crossAxis: -50, mainAxis: 5 } }, - content: 'B4', + content: 'EE', onExecute, }); diff --git a/packages/react-keytips/stories/OverflowMenu.md b/packages/react-keytips/stories/OverflowMenu.md index adc5658a..622a46da 100644 --- a/packages/react-keytips/stories/OverflowMenu.md +++ b/packages/react-keytips/stories/OverflowMenu.md @@ -8,7 +8,6 @@ const subMenuRef = useKeytipRef({ keySequences: ['a', 'b'], content: 'B', dynamic: true, - persited: true, onExecute, }); ``` diff --git a/packages/react-keytips/stories/OverflowMenu.stories.tsx b/packages/react-keytips/stories/OverflowMenu.stories.tsx index 9fc86a04..137316a0 100644 --- a/packages/react-keytips/stories/OverflowMenu.stories.tsx +++ b/packages/react-keytips/stories/OverflowMenu.stories.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { ExecuteKeytipEventHandler, useKeytipRef, + useMenuShortcut, } from '@fluentui-contrib/react-keytips'; import { makeStyles, @@ -56,17 +57,23 @@ const useStyles = makeStyles({ }); const onExecute: ExecuteKeytipEventHandler = (_, el) => { - el.targetElement.click(); + el.targetElement?.click(); }; const SubMenuSecond = () => { const subMenuRef = useKeytipRef({ - keySequences: ['a', 'b', 'c'], - content: 'C', + keySequences: ['d', 'bb', 'cc'], + content: 'CC', dynamic: true, onExecute, }); + const subMenuItemRef = useKeytipRef({ + keySequences: ['d', 'bb', 'cc', 'cz'], + content: 'CZ', + onExecute, + }); + return ( @@ -75,7 +82,7 @@ const SubMenuSecond = () => { - 11 + 11 12 13 @@ -86,8 +93,8 @@ const SubMenuSecond = () => { const SubMenu = () => { const subMenuRef = useKeytipRef({ - keySequences: ['a', 'b'], - content: 'B', + keySequences: ['d', 'bb'], + content: 'BB', dynamic: true, onExecute, }); @@ -125,9 +132,19 @@ const OverflowMenu = ({ itemIds }: { itemIds: string[] }) => { const { ref, overflowCount, isOverflowing } = useOverflowMenu(); + useMenuShortcut({ + shortcut: 'bb', + dependentKeys: ['d'], + }); + + useMenuShortcut({ + shortcut: 'cc', + dependentKeys: ['d', 'bb'], + }); + const menuRef = useKeytipRef({ - keySequences: ['a'], - content: 'A', + keySequences: ['d'], + content: 'D', dynamic: true, onExecute, }); diff --git a/packages/react-keytips/stories/WithTabs.stories.tsx b/packages/react-keytips/stories/WithTabs.stories.tsx index fdbbeca2..0fbfa8fd 100644 --- a/packages/react-keytips/stories/WithTabs.stories.tsx +++ b/packages/react-keytips/stories/WithTabs.stories.tsx @@ -2,7 +2,6 @@ import * as React from 'react'; import { ExecuteKeytipEventHandler, useKeytipRef, - KeytipsProps, } from '@fluentui-contrib/react-keytips'; import { makeStyles, @@ -53,50 +52,50 @@ export const WithTabsStory = () => { }; const refFirstTab = useKeytipRef({ - keySequences: ['a'], - content: 'A', + keySequences: ['b1'], + content: 'B1', onExecute: btnExecute, }); const refSecondTab = useKeytipRef({ - keySequences: ['b'], - content: 'B', + keySequences: ['b2'], + content: 'B2', onExecute: btnExecute, }); const refThirdTab = useKeytipRef({ - keySequences: ['c'], - content: 'C', + keySequences: ['b3'], + content: 'B3', onExecute: btnExecute, }); const checkBoxRef = useKeytipRef({ - keySequences: ['a', '1'], + keySequences: ['b1', '1'], content: '1', onExecute: btnExecute, }); const switchRef = useKeytipRef({ - keySequences: ['a', '2'], + keySequences: ['b1', '2'], content: '2', onExecute: btnExecute, }); const linkRef = useKeytipRef({ - keySequences: ['a', '3'], + keySequences: ['b1', '3'], content: '3', onExecute: btnExecute, }); const btnRef = useKeytipRef({ - keySequences: ['b', '1'], - content: 'B1', + keySequences: ['b2', '1'], + content: '1', onExecute: btnExecute, }); const btnRefSecond = useKeytipRef({ - keySequences: ['c', '1'], - content: 'C1', + keySequences: ['b3', '1'], + content: '1', onExecute: btnExecute, });