@primer/view-components
Version:
ViewComponents for the Primer Design System
164 lines (163 loc) • 8.32 kB
JavaScript
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _FocusGroupElement_instances, _FocusGroupElement_retainSignal, _FocusGroupElement_abortController, _FocusGroupElement_items_get;
import '@oddbird/popover-polyfill';
const validSelectors = ['[role="menuitem"]', '[role="menuitemcheckbox"]', '[role="menuitemradio"]', '[role="option"]'];
const menuItemSelector = validSelectors.map(selector => `:not([hidden]) > ${selector}`).join(', ');
const getMnemonicFor = (item) => item.textContent?.trim()[0].toLowerCase();
const printable = /^\S$/;
class FocusGroupElement extends HTMLElement {
constructor() {
super(...arguments);
_FocusGroupElement_instances.add(this);
_FocusGroupElement_retainSignal.set(this, null);
_FocusGroupElement_abortController.set(this, null);
}
get nowrap() {
return this.hasAttribute('nowrap');
}
set nowrap(value) {
this.toggleAttribute('nowrap', value);
}
get direction() {
if (this.getAttribute('direction') === 'horizontal')
return 'horizontal';
return 'vertical';
}
set direction(value) {
this.setAttribute('direction', `${value}`);
}
get retain() {
return this.hasAttribute('retain');
}
set retain(value) {
this.toggleAttribute('retain', value);
}
get mnemonics() {
return this.hasAttribute('mnemonics');
}
connectedCallback() {
__classPrivateFieldSet(this, _FocusGroupElement_abortController, new AbortController(), "f");
const { signal } = __classPrivateFieldGet(this, _FocusGroupElement_abortController, "f");
this.addEventListener('keydown', this, { signal });
this.addEventListener('click', this, { signal });
this.addEventListener('mouseover', this, { signal });
this.addEventListener('focusin', this, { signal });
}
disconnectedCallback() {
__classPrivateFieldGet(this, _FocusGroupElement_abortController, "f")?.abort();
}
handleEvent(event) {
const { direction, nowrap } = this;
if (event.type === 'focusin') {
if (this.retain && event.target instanceof Element && event.target.matches(menuItemSelector)) {
__classPrivateFieldGet(this, _FocusGroupElement_retainSignal, "f")?.abort();
const { signal } = (__classPrivateFieldSet(this, _FocusGroupElement_retainSignal, new AbortController(), "f"));
for (const item of __classPrivateFieldGet(this, _FocusGroupElement_instances, "a", _FocusGroupElement_items_get)) {
item.setAttribute('tabindex', item === event.target ? '0' : '-1');
const popover = event.target.closest('[popover]');
if (item === event.target && popover?.popover === 'auto' && popover.closest('focus-group') === this) {
popover.addEventListener('toggle', (toggleEvent) => {
if (!(toggleEvent.target instanceof Element))
return;
if (toggleEvent.newState === 'closed') {
__classPrivateFieldGet(this, _FocusGroupElement_retainSignal, "f")?.abort();
item.setAttribute('tabindex', '-1');
if (popover.id) {
const invoker = this.querySelector(`[popovertarget="${popover.id}"]`);
if (invoker) {
invoker.setAttribute('tabindex', '0');
}
else {
__classPrivateFieldGet(this, _FocusGroupElement_instances, "a", _FocusGroupElement_items_get)[0]?.setAttribute('tabindex', '0');
}
}
}
}, { signal });
}
}
}
}
else if (event instanceof KeyboardEvent) {
const items = Array.from(__classPrivateFieldGet(this, _FocusGroupElement_instances, "a", _FocusGroupElement_items_get));
let index = items.indexOf(event.target);
const key = event.key;
if (key === 'Up' || key === 'ArrowUp') {
if (direction === 'vertical' || direction === 'both') {
index -= index < 0 ? 0 : 1;
event.preventDefault();
}
}
else if (key === 'Down' || key === 'ArrowDown') {
if (direction === 'vertical' || direction === 'both') {
index += 1;
event.preventDefault();
}
}
else if (event.key === 'Left' || event.key === 'ArrowLeft') {
if (direction === 'horizontal' || direction === 'both') {
index -= 1;
event.preventDefault();
}
}
else if (event.key === 'Right' || event.key === 'ArrowRight') {
if (direction === 'horizontal' || direction === 'both') {
index += 1;
event.preventDefault();
}
}
else if (event.key === 'Home' || event.key === 'PageUp') {
index = 0;
event.preventDefault();
}
else if (event.key === 'End' || event.key === 'PageDown') {
index = items.length - 1;
event.preventDefault();
}
else if (this.mnemonics && printable.test(key)) {
const mnemonic = key.toLowerCase();
const offset = index > 0 && getMnemonicFor(event.target) === mnemonic ? index : 0;
index = items.findIndex((item, i) => i > offset && getMnemonicFor(item) === mnemonic);
if (index < 0 && !nowrap) {
index = items.findIndex(item => getMnemonicFor(item) === mnemonic);
}
}
else {
return;
}
if (nowrap && index < 0)
index = 0;
if (!nowrap && index >= items.length)
index = 0;
const focusEl = items.at(Math.min(index, items.length - 1));
{
let el = focusEl;
do {
el = el.closest(`[popover]:not(:popover-open)`);
if (el?.popover === 'auto' && !['ArrowRight', 'ArrowLeft'].includes(event.key)) {
el.showPopover();
}
el = el?.parentElement || null;
} while (el);
}
focusEl?.focus();
}
}
}
_FocusGroupElement_retainSignal = new WeakMap(), _FocusGroupElement_abortController = new WeakMap(), _FocusGroupElement_instances = new WeakSet(), _FocusGroupElement_items_get = function _FocusGroupElement_items_get() {
return this.querySelectorAll(menuItemSelector);
};
export default FocusGroupElement;
if (!customElements.get('focus-group')) {
window.FocusGroupElement = FocusGroupElement;
customElements.define('focus-group', FocusGroupElement);
}