/**
* Copyright Aquera Inc 2023
*
* This source code is licensed under the BSD-3-Clause license found in the
* LICENSE file in the root directory of this source tree.
*/
import { LitElement, html, css } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { query } from 'lit/decorators.js';
import NileElement from '../internal/nile-element';
import type { CSSResultGroup } from 'lit';
import type NileMenuItem from '../nile-menu-item/nile-menu-item';
import { styles } from './nile-side-bar-action-menu.css';
import { HasSlotController } from '../internal/slot';
/**
* A nile-side-bar-action-menu element.
* @summary Menus provide a list of options for the user to choose from.
*
* @slot - The menu's content, including menu items, menu labels, and dividers.
*
* @event nile-select - Emitted when a menu item is selected.
* @fires count-changed - Indicates when the count changes
* @slot - This element has a slot
* @csspart button - The button
*/
@customElement('nile-side-bar-action-menu')
export class NileSideBarActionMenu extends NileElement {
static styles: CSSResultGroup = styles;
@state() searchValue: string = '';
@state() searchWidth: number = 0;
@property({ type: Boolean, reflect: true }) searchEnabled = false;
@property({ type: Boolean, reflect: true }) customSearch = false;
@property({ type: Boolean }) showNoResults: boolean = false;
@property({ type: Boolean }) allowSpaceKey: boolean = false;
@property({ type: String }) noResultsMessage: string = 'No results found';
@query('slot:not([name])') defaultSlot!: HTMLSlotElement;
private readonly hasSlotController = new HasSlotController(
this,
'menu__footer',
'menu__header'
);
connectedCallback() {
super.connectedCallback();
this.setAttribute('role', 'menu');
}
private handleClick(event: MouseEvent) {
const target = event.target as HTMLElement;
const item = target.closest('nile-menu-item');
if (item?.hasSubMenu) {
return;
}
if (!item || item.disabled) {
return;
}
if (item.type === 'checkbox') {
item.checked = !item.checked;
}
this.emit('nile-select', { value: item.value });
}
private handleKeyDown(event: KeyboardEvent) {
if (event.key === 'Enter') {
const item = this.getCurrentItem();
event.preventDefault();
item?.click();
}
if (!this.allowSpaceKey && event.key === ' ') {
event.preventDefault();
}
if (['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) {
const items = this.getAllItems();
const activeItem = this.getCurrentItem();
let index = activeItem ? items.indexOf(activeItem) : 0;
if (items.length > 0) {
event.preventDefault();
if (event.key === 'ArrowDown') index++;
else if (event.key === 'ArrowUp') index--;
else if (event.key === 'Home') index = 0;
else if (event.key === 'End') index = items.length - 1;
if (index < 0) index = items.length - 1;
if (index > items.length - 1) index = 0;
this.setCurrentItem(items[index]);
items[index].focus();
}
}
}
private handleMouseDown(event: MouseEvent) {
const target = event.target as HTMLElement;
if (this.isMenuItem(target)) {
this.setCurrentItem(target as NileMenuItem);
}
}
private handleSlotChange() {
const items = this.getAllItems();
if (items.length > 0) {
this.setCurrentItem(items[0]);
}
}
private isMenuItem(item: HTMLElement) {
return (
item.tagName.toLowerCase() === 'nile-menu-item' ||
['menuitem', 'menuitemcheckbox', 'menuitemradio'].includes(
item.getAttribute('role') ?? ''
)
);
}
getAllItems() {
return [...this.defaultSlot.assignedElements({ flatten: true })].filter(
(el: HTMLElement) => {
if (!this.isMenuItem(el)) {
return false;
}
return true;
}
) as NileMenuItem[];
}
getCurrentItem() {
return this.getAllItems().find(i => i.getAttribute('tabindex') === '0');
}
setCurrentItem(item: NileMenuItem) {
const items = this.getAllItems();
items.forEach(i => {
i.setAttribute('tabindex', i === item ? '0' : '-1');
});
}
private handleSearchChange(e: any) {
let items = this.getAllItems();
let searchValue = e.target.value;
if (this.customSearch) {
this.emit('nile-search-value', { value: searchValue });
return;
}
let counter = 0;
items.map(curr_item => {
this.searchWidth = Math.max(this.searchWidth, curr_item.offsetWidth);
if (
curr_item.innerText.toLowerCase().includes(e.target.value.toLowerCase())
) {
curr_item.style.display = 'block';
counter++;
} else {
curr_item.style.display = 'none';
}
});
this.showNoResults = counter === 0;
}
render() {
return html`