From fb788e1881e341c2a0c1fd50daac3b58619a3887 Mon Sep 17 00:00:00 2001 From: tidy-dev <75402236+tidy-dev@users.noreply.github.com> Date: Tue, 22 Aug 2023 20:38:39 -0400 Subject: [PATCH] Add dropdown button event handling --- app/src/ui/app-menu/menu-list-item.tsx | 9 ++--- app/src/ui/app-menu/menu-pane.tsx | 22 +++++++---- app/src/ui/dropdown-select-button.tsx | 54 +++++++++++++++++++++++++- app/src/ui/lib/button.tsx | 7 ++++ 4 files changed, 77 insertions(+), 15 deletions(-) diff --git a/app/src/ui/app-menu/menu-list-item.tsx b/app/src/ui/app-menu/menu-list-item.tsx index a4afa116ad..b83580f888 100644 --- a/app/src/ui/app-menu/menu-list-item.tsx +++ b/app/src/ui/app-menu/menu-list-item.tsx @@ -131,19 +131,16 @@ export class MenuListItem extends React.Component { private renderLabel() { const { item, renderLabel } = this.props - if(renderLabel !== undefined) { + if (renderLabel !== undefined) { return renderLabel(item) } if (item.type === 'separator') { return } - + return ( - + ) } diff --git a/app/src/ui/app-menu/menu-pane.tsx b/app/src/ui/app-menu/menu-pane.tsx index c64ba57c5d..c0dd8aab00 100644 --- a/app/src/ui/app-menu/menu-pane.tsx +++ b/app/src/ui/app-menu/menu-pane.tsx @@ -138,21 +138,27 @@ export class MenuPane extends React.Component { } private tryMoveSelectionByFirstCharacter(key: string, source: ClickSource) { - if (key.length > 1 || !isPrintableCharacterKey(key) || !this.props.allowFirstCharacterNavigation) { + if ( + key.length > 1 || + !isPrintableCharacterKey(key) || + !this.props.allowFirstCharacterNavigation + ) { return } const { items, selectedItem } = this.props - const char = key.toLowerCase(); + const char = key.toLowerCase() const currentRow = selectedItem ? items.indexOf(selectedItem) + 1 : 0 - const start = currentRow + 1 > items.length ? 0 : currentRow + 1; + const start = currentRow + 1 > items.length ? 0 : currentRow + 1 + + const firstChars = items.map(v => + v.type === 'separator' ? '' : v.label.trim()[0].toLowerCase() + ) - const firstChars = items.map(v => v.type === 'separator' ? '' : v.label.trim()[0].toLowerCase()) - // Check menu items after selected let ix: number = firstChars.indexOf(char, start) // check menu items before selected - if(ix === -1) { + if (ix === -1) { ix = firstChars.indexOf(char, 0) } @@ -286,5 +292,5 @@ const supportedKeys = [ const isSupportedKey = (key: string): key is typeof supportedKeys[number] => (supportedKeys as readonly string[]).includes(key) -const isPrintableCharacterKey = (key: string) => key.length === 1 && key.match(/\S/); - \ No newline at end of file +const isPrintableCharacterKey = (key: string) => + key.length === 1 && key.match(/\S/) diff --git a/app/src/ui/dropdown-select-button.tsx b/app/src/ui/dropdown-select-button.tsx index d543365eb5..5db7e40e0c 100644 --- a/app/src/ui/dropdown-select-button.tsx +++ b/app/src/ui/dropdown-select-button.tsx @@ -70,6 +70,7 @@ export class DropdownSelectButton< IDropdownSelectButtonState > { private invokeButtonRef: HTMLButtonElement | null = null + private dropdownButtonRef: HTMLButtonElement | null = null private optionsContainerRef: HTMLDivElement | null = null public constructor(props: IDropdownSelectButtonProps) { @@ -140,12 +141,13 @@ export class DropdownSelectButton< depth: number | undefined, event: React.KeyboardEvent ) => { - if (event.key !== 'Escape') { + if (event.key !== 'Escape' && event.key !== 'Esc') { return } event.preventDefault() event.stopPropagation() + this.dropdownButtonRef?.focus() this.setState({ showButtonOptions: false }) } @@ -167,10 +169,58 @@ export class DropdownSelectButton< this.setState({ showButtonOptions: !this.state.showButtonOptions }) } + private onDropdownButtonKeyDown = ( + event: React.KeyboardEvent + ) => { + const { key } = event + let flag = false + + switch (key) { + case ' ': + case 'Enter': + case 'ArrowDown': + case 'Down': + this.setState({ + selectedOption: this.props.options.at(0) ?? null, + showButtonOptions: true, + }) + flag = true + break + + case 'Esc': + case 'Escape': + this.dropdownButtonRef?.focus() + this.setState({ showButtonOptions: false }) + flag = true + break + + case 'Up': + case 'ArrowUp': + this.setState({ + selectedOption: this.props.options.at(-1) ?? null, + showButtonOptions: true, + }) + flag = true + break + + default: + break + } + + if (flag) { + event.stopPropagation() + event.preventDefault() + } + } + private onInvokeButtonRef = (buttonRef: HTMLButtonElement | null) => { this.invokeButtonRef = buttonRef } + private onDropdownButtonRef = (buttonRef: HTMLButtonElement | null) => { + this.dropdownButtonRef = buttonRef + } + private onOptionsContainerRef = (ref: HTMLDivElement | null) => { this.optionsContainerRef = ref } @@ -284,6 +334,8 @@ export class DropdownSelectButton<