Add dropdown button event handling

This commit is contained in:
tidy-dev 2023-08-22 20:38:39 -04:00
parent 178912ef2c
commit fb788e1881
4 changed files with 77 additions and 15 deletions

View file

@ -131,19 +131,16 @@ export class MenuListItem extends React.Component<IMenuListItemProps, {}> {
private renderLabel() {
const { item, renderLabel } = this.props
if(renderLabel !== undefined) {
if (renderLabel !== undefined) {
return renderLabel(item)
}
if (item.type === 'separator') {
return
}
return (
<AccessText
text={item.label}
highlight={this.props.highlightAccessKey}
/>
<AccessText text={item.label} highlight={this.props.highlightAccessKey} />
)
}

View file

@ -138,21 +138,27 @@ export class MenuPane extends React.Component<IMenuPaneProps> {
}
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/);
const isPrintableCharacterKey = (key: string) =>
key.length === 1 && key.match(/\S/)

View file

@ -70,6 +70,7 @@ export class DropdownSelectButton<
IDropdownSelectButtonState<T>
> {
private invokeButtonRef: HTMLButtonElement | null = null
private dropdownButtonRef: HTMLButtonElement | null = null
private optionsContainerRef: HTMLDivElement | null = null
public constructor(props: IDropdownSelectButtonProps<T>) {
@ -140,12 +141,13 @@ export class DropdownSelectButton<
depth: number | undefined,
event: React.KeyboardEvent<HTMLDivElement>
) => {
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<HTMLButtonElement>
) => {
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<
<Button
className={dropdownClasses}
onClick={this.openSplitButtonDropdown}
onKeyDown={this.onDropdownButtonKeyDown}
onButtonRef={this.onDropdownButtonRef}
type="button"
ariaExpanded={showButtonOptions}
ariaHaspopup={true}

View file

@ -27,6 +27,12 @@ export interface IButtonProps {
*/
readonly onMouseEnter?: (event: React.MouseEvent<HTMLButtonElement>) => void
/**
* A function that's called when the user moves over the button with
* a pointer device.
*/
readonly onKeyDown?: (event: React.KeyboardEvent<HTMLButtonElement>) => void
/** An optional tooltip to render when hovering over the button */
readonly tooltip?: string
@ -185,6 +191,7 @@ export class Button extends React.Component<IButtonProps, {}> {
<button
className={className}
onClick={disabled ? preventDefault : this.onClick}
onKeyDown={this.props.onKeyDown}
onContextMenu={disabled ? preventDefault : this.onContextMenu}
type={this.props.type || 'button'}
ref={this.innerButtonRef}