Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add overflow menu (ActionMenu) to focus zone in ActionBar #2259

Merged
merged 9 commits into from
Oct 6, 2023
9 changes: 9 additions & 0 deletions .changeset/weak-mails-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@primer/view-components': minor
---

* Includes ActionMenu in ActionBar focus trap when present.
* Adjusts `focus_group.ts` to set `tabindex="0"` back to invoker if it is non-focusable.
* Prevents popover invokers from being triggered with 'left' and 'right' arrow keys.

<!-- Changed components: Primer::Alpha::ActionBar, Primer::Alpha::ActionMenu -->
4 changes: 2 additions & 2 deletions app/components/primer/alpha/action_bar_element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,11 @@ class ActionBarElement extends HTMLElement {
if (this.#focusZoneAbortController) {
this.#focusZoneAbortController.abort()
}
this.#focusZoneAbortController = focusZone(this.itemContainer, {
this.#focusZoneAbortController = focusZone(this, {
bindKeys: FocusKeys.ArrowHorizontal | FocusKeys.HomeAndEnd,
focusOutBehavior: 'wrap',
focusableElementFilter: element => {
return !element.closest('.ActionBar-item[hidden]')
return !element.closest('.ActionBar-item[hidden]') && !element.closest('li.ActionListItem')
}
})
}
Expand Down
28 changes: 27 additions & 1 deletion app/components/primer/focus_group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const getMnemonicFor = (item: Element) => item.textContent?.trim()[0].toLowerCas
const printable = /^\S$/

export default class FocusGroupElement extends HTMLElement {
#retainSignal: AbortController | null = null

get nowrap(): boolean {
return this.hasAttribute('nowrap')
}
Expand Down Expand Up @@ -60,8 +62,32 @@ export default class FocusGroupElement extends HTMLElement {
const {direction, nowrap} = this
if (event.type === 'focusin') {
if (this.retain && event.target instanceof Element && event.target.matches(menuItemSelector)) {
this.#retainSignal?.abort()
const {signal} = (this.#retainSignal = new AbortController())
for (const item of this.#items) {
item.setAttribute('tabindex', item === event.target ? '0' : '-1')
const popover = event.target.closest<HTMLElement>('[popover]')
if (item === event.target && popover?.popover === 'auto' && popover.closest('focus-group') === this) {
popover.addEventListener(
'toggle',
(toggleEvent: Event) => {
if (!(toggleEvent.target instanceof Element)) return
if ((toggleEvent as ToggleEvent).newState === 'closed') {
this.#retainSignal?.abort()
item.setAttribute('tabindex', '-1')
if (popover.id) {
const invoker = this.querySelector(`[popovertarget="${popover.id}"]`)
if (invoker) {
invoker.setAttribute('tabindex', '0')
} else {
this.#items[0]?.setAttribute('tabindex', '0')
}
}
}
},
{signal}
)
}
}
}
} else if (event instanceof KeyboardEvent) {
Expand Down Expand Up @@ -111,7 +137,7 @@ export default class FocusGroupElement extends HTMLElement {
let el: HTMLElement | null = focusEl
do {
el = el.closest(`[popover]:not(:popover-open)`)
if (el?.popover === 'auto') {
if (el?.popover === 'auto' && !['ArrowRight', 'ArrowLeft'].includes(event.key)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This prevents the left and right arrow keys from triggering a menu. Without, if you were to navigate the ActionBar with arrow keys, the menu would automatically open when either key is pressed.

el.showPopover()
}
el = el?.parentElement || null
Expand Down
Loading