Skip to content

Commit

Permalink
fix(Designer): Keyboard Focus Moves Out of TokenPicker Navigating Usi…
Browse files Browse the repository at this point in the history
…ng Keyboard (#4160)

* added focusTrapZone with disabling it on not showing

* fixed handleFocus not being triggered on token insertion

* triggered closeTokenEditor on inserToken & removed handleFocus from top div
  • Loading branch information
Elaina-Lee committed Feb 13, 2024
1 parent 1b86f03 commit 609c4c5
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 78 deletions.
2 changes: 1 addition & 1 deletion libs/designer-ui/src/lib/editor/base/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ export const BaseEditor = ({
{htmlEditor === 'rich-html' ? null : <ArrowNavigation />}
{tokens ? (
<>
<InsertTokenNode />
<InsertTokenNode closeTokenPicker={() => setIsTokenPickerOpened(false)} />
<DeleteTokenNode />
<OpenTokenPicker openTokenPicker={openTokenPicker} />
<CloseTokenPicker closeTokenPicker={() => setIsTokenPickerOpened(false)} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import { useEffect } from 'react';

export const INSERT_TOKEN_NODE: LexicalCommand<TokenNodeProps> = createCommand();

export default function InsertTokenNode(): null {
export interface InsertTokenNodeProps {
closeTokenPicker: () => void;
}

export default function InsertTokenNode({ closeTokenPicker }: InsertTokenNodeProps): null {
const [editor] = useLexicalComposerContext();

useEffect(() => {
Expand All @@ -24,11 +28,12 @@ export default function InsertTokenNode(): null {
const tokenNode = $createTokenNode({ ...payload, value, readonly: false });
selection.insertNodes([tokenNode]);
}
closeTokenPicker();
return true;
},
COMMAND_PRIORITY_EDITOR
);
}, [editor]);
}, [editor, closeTokenPicker]);

return null;
}
155 changes: 80 additions & 75 deletions libs/designer-ui/src/lib/tokenpicker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { TokenPickerPivot } from './tokenpickerpivot';
import type { GetValueSegmentHandler } from './tokenpickersection/tokenpickeroption';
import { TokenPickerSection } from './tokenpickersection/tokenpickersection';
import type { ICalloutContentStyles, ISearchBox, PivotItem } from '@fluentui/react';
import { SearchBox, Callout, DirectionalHint } from '@fluentui/react';
import { SearchBox, Callout, DirectionalHint, FocusTrapZone } from '@fluentui/react';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import type { LexicalEditor, NodeKey } from 'lexical';
import type { editor } from 'monaco-editor';
Expand Down Expand Up @@ -65,6 +65,7 @@ export function TokenPicker({
}: TokenPickerProps): JSX.Element {
const intl = useIntl();
const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions());
const [isTokenPickerVisible, setIsTokenPickerVisible] = useState(true);
const [searchQuery, setSearchQuery] = useState('');
const [selectedKey, setSelectedKey] = useState<TokenPickerMode>(initialMode ?? TokenPickerMode.TOKEN);
const [expressionToBeUpdated, setExpressionToBeUpdated] = useState<NodeKey | null>(null);
Expand Down Expand Up @@ -177,9 +178,11 @@ export function TokenPicker({
}
}}
onDismiss={(e) => {
setIsTokenPickerVisible(false);
if (e?.type === 'keydown' && (e as React.KeyboardEvent<HTMLElement>).key === 'Escape') {
editor?.focus();
}

closeTokenPicker();
}}
onRestoreFocus={() => {
Expand All @@ -190,88 +193,90 @@ export function TokenPicker({
hostId: 'msla-layer-host',
}}
>
<div
className="msla-token-picker-container-v3"
style={
fullScreen
? {
height: Math.max(windowDimensions.height - 100, Math.min(windowDimensions.height, 550)),
width: Math.max(
windowDimensions.width - (parseInt(PanelSize.Medium, 10) + 40),
Math.min(windowDimensions.width - 16, 400)
),
}
: { maxHeight: Math.min(windowDimensions.height, 550), width: Math.min(windowDimensions.width - 16, 400) }
}
>
<div className="msla-token-picker">
{initialMode ? (
<TokenPickerHeader
fullScreen={fullScreen}
isExpression={isExpression}
closeTokenPicker={closeTokenPicker}
setFullScreen={setFullScreen}
pasteLastUsedExpression={pasteLastUsedExpression}
/>
) : null}
<FocusTrapZone isClickableOutsideFocusTrap disabled={!isTokenPickerVisible}>
<div
className="msla-token-picker-container-v3"
style={
fullScreen
? {
height: Math.max(windowDimensions.height - 100, Math.min(windowDimensions.height, 550)),
width: Math.max(
windowDimensions.width - (parseInt(PanelSize.Medium, 10) + 40),
Math.min(windowDimensions.width - 16, 400)
),
}
: { maxHeight: Math.min(windowDimensions.height, 550), width: Math.min(windowDimensions.width - 16, 400) }
}
>
<div className="msla-token-picker">
{initialMode ? (
<TokenPickerHeader
fullScreen={fullScreen}
isExpression={isExpression}
closeTokenPicker={closeTokenPicker}
setFullScreen={setFullScreen}
pasteLastUsedExpression={pasteLastUsedExpression}
/>
) : null}

{isExpression ? (
<div className="msla-token-picker-expression-subheader">
<ExpressionEditor
initialValue={expression.value}
editorRef={expressionEditorRef}
onBlur={onExpressionEditorBlur}
isDragging={isDraggingExpressionEditor}
dragDistance={expressionEditorDragDistance}
setIsDragging={setIsDraggingExpressionEditor}
currentHeight={expressionEditorCurrentHeight}
setCurrentHeight={setExpressionEditorCurrentHeight}
setExpressionEditorError={setExpressionEditorError}
{isExpression ? (
<div className="msla-token-picker-expression-subheader">
<ExpressionEditor
initialValue={expression.value}
editorRef={expressionEditorRef}
onBlur={onExpressionEditorBlur}
isDragging={isDraggingExpressionEditor}
dragDistance={expressionEditorDragDistance}
setIsDragging={setIsDraggingExpressionEditor}
currentHeight={expressionEditorCurrentHeight}
setCurrentHeight={setExpressionEditorCurrentHeight}
setExpressionEditorError={setExpressionEditorError}
/>
<div className="msla-token-picker-expression-editor-error">{expressionEditorError}</div>
<TokenPickerPivot selectedKey={selectedKey} selectKey={handleSelectKey} hideExpressions={!!tokenClickedCallback} />
</div>
) : null}
<div className="msla-token-picker-search-container">
<SearchBox
className="msla-token-picker-search"
componentRef={(e) => {
searchBoxRef.current = e;
}}
placeholder={tokenPickerPlaceHolderText}
onChange={(_, newValue) => {
setSearchQuery(newValue ?? '');
}}
data-automation-id="msla-token-picker-search"
/>
<div className="msla-token-picker-expression-editor-error">{expressionEditorError}</div>
<TokenPickerPivot selectedKey={selectedKey} selectKey={handleSelectKey} hideExpressions={!!tokenClickedCallback} />
</div>
) : null}
<div className="msla-token-picker-search-container">
<SearchBox
className="msla-token-picker-search"
componentRef={(e) => {
searchBoxRef.current = e;
}}
placeholder={tokenPickerPlaceHolderText}
onChange={(_, newValue) => {
setSearchQuery(newValue ?? '');
}}
data-automation-id="msla-token-picker-search"
/>
</div>
<TokenPickerSection
tokenGroup={(selectedKey === TokenPickerMode.TOKEN ? filteredTokenGroup : tokenGroup) ?? []}
expressionGroup={expressionGroup ?? []}
expressionEditorRef={expressionEditorRef}
selectedKey={selectedKey}
searchQuery={searchQuery}
fullScreen={fullScreen}
expression={expression}
setExpression={setExpression}
getValueSegmentFromToken={getValueSegmentFromToken}
tokenClickedCallback={tokenClickedCallback}
noDynamicContent={!isDynamicContentAvailable(filteredTokenGroup ?? [])}
closeTokenPicker={closeTokenPicker}
expressionEditorCurrentHeight={expressionEditorCurrentHeight}
/>
{isExpression ? (
<TokenPickerFooter
tokenGroup={tokenGroup ?? []}
<TokenPickerSection
tokenGroup={(selectedKey === TokenPickerMode.TOKEN ? filteredTokenGroup : tokenGroup) ?? []}
expressionGroup={expressionGroup ?? []}
expressionEditorRef={expressionEditorRef}
selectedKey={selectedKey}
searchQuery={searchQuery}
fullScreen={fullScreen}
expression={expression}
expressionToBeUpdated={expressionToBeUpdated}
setExpression={setExpression}
getValueSegmentFromToken={getValueSegmentFromToken}
setExpressionEditorError={setExpressionEditorError}
tokenClickedCallback={tokenClickedCallback}
noDynamicContent={!isDynamicContentAvailable(filteredTokenGroup ?? [])}
closeTokenPicker={closeTokenPicker}
expressionEditorCurrentHeight={expressionEditorCurrentHeight}
/>
) : null}
{isExpression ? (
<TokenPickerFooter
tokenGroup={tokenGroup ?? []}
expression={expression}
expressionToBeUpdated={expressionToBeUpdated}
getValueSegmentFromToken={getValueSegmentFromToken}
setExpressionEditorError={setExpressionEditorError}
closeTokenPicker={closeTokenPicker}
/>
) : null}
</div>
</div>
</div>
</FocusTrapZone>
</Callout>
{tokenClickedCallback ? null : <TokenPickerHandler handleUpdateExpressionToken={handleUpdateExpressionToken} />}
{tokenClickedCallback ? null : <UpdateTokenNode />}
Expand Down

0 comments on commit 609c4c5

Please sign in to comment.