@rhds/elements
Version:
Red Hat Design System Elements
288 lines (287 loc) • 15.9 kB
JavaScript
var _RhMenuDropdown_instances, _RhMenuDropdown_items_get, _RhMenuDropdown_outsideClick, _RhMenuDropdown_validateSlotContent, _RhMenuDropdown_tabindex, _RhMenuDropdown_float, _RhMenuDropdown_toggleMenu, _RhMenuDropdown_focusFirstItem, _RhMenuDropdown_onToggleKeydown, _RhMenuDropdown_handleSelection, _RhMenuDropdown_onSelect, _RhMenuDropdown_onKeyDown, _RhMenuDropdown_onFocusOut, _RhMenuDropdown_positionMenu;
var RhMenuDropdown_1;
import { __classPrivateFieldGet, __decorate } from "tslib";
import { LitElement, html, isServer } from 'lit';
import { customElement } from 'lit/decorators/custom-element.js';
import { property } from 'lit/decorators/property.js';
import { query } from 'lit/decorators/query.js';
import { queryAll } from 'lit/decorators/query-all.js';
import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js';
import { RovingTabindexController } from '@patternfly/pfe-core/controllers/roving-tabindex-controller.js';
import { FloatingDOMController } from '@patternfly/pfe-core/controllers/floating-dom-controller.js';
import '@rhds/elements/rh-button/rh-button.js';
import '@rhds/elements/rh-icon/rh-icon.js';
import '@rhds/elements/rh-menu/rh-menu.js';
import { RhMenuItem } from '../rh-menu/rh-menu-item.js';
import { css } from "lit";
const styles = css `:host{--_border:var(--rh-border-width-sm,1px) solid light-dark(var(--rh-color-gray-30,#c7c7c7),var(--rh-color-gray-50,#707070));--_color:var(--rh-color-text-primary);position:relative;display:inline-block}#menu-list{position:absolute;padding:var(--rh-space-md,8px) 0;left:0;top:0;z-index:1000;min-inline-size:18.5rem;max-inline-size:100%;border-radius:var(--rh-border-radius-default,3px);border:var(--_border);background:light-dark(var(--rh-color-surface-lightest,#fff),var(--rh-color-surface-darkest,#151515));box-shadow:var(--rh-box-shadow-md,0 4px 6px 1px #15151540);transition:opacity .3s cubic-bezier(.54,1.5,.38,1.11) 0s;translate:var(--_floating-content-translate)}.visible{display:block}::slotted([slot=toggle-label]){color:var(--_color);font-family:var(--rh-font-family-body-text,RedHatText,"Red Hat Text",Helvetica,Arial,sans-serif);font-size:var(--rh-font-size-body-text-md,1rem)!important;font-style:normal;font-weight:var(--rh-font-weight-body-text-regular,400);line-height:var(--rh-line-height-code,1.5)}#menu-toggle{--_interactive-border-color:light-dark(var(--rh-color-accent-base-on-light,#06c),var(--rh-color-accent-base-on-dark,#92c5f9));color:var(--_color);font-family:var(--rh-font-family-body-text,RedHatText,"Red Hat Text",Helvetica,Arial,sans-serif);font-size:var(--rh-font-size-body-text-md,1rem);font-style:normal;font-weight:var(--rh-font-weight-body-text-regular,400);line-height:var(--rh-line-height-code,1.5);inline-size:100%;padding:var(--rh-space-sm,6px) var(--rh-space-lg,16px);display:flex;align-items:center;justify-content:space-between;box-sizing:border-box;border-radius:var(--rh-border-radius-default,3px);background:light-dark(var(--rh-color-surface-lightest,#fff),var(--rh-color-surface-darkest,#151515));border:none}#menu-toggle .action-icon rh-icon{--rh-icon-size:12px;padding-inline-start:var(--rh-space-md,8px)}#menu-toggle .info-section{display:flex;align-items:center;justify-content:flex-start}#menu-toggle.boxed{box-shadow:inset 0 0 0 1px light-dark(var(--rh-color-gray-30,#c7c7c7),var(--rh-color-gray-50,#707070))}#menu-toggle.boxed:not(.open):hover{box-shadow:inset 0 0 0 1px var(--_interactive-border-color)}#menu-toggle.boxed:not(.open):active{box-shadow:inset 0 0 0 var(--rh-length-3xs,2px) var(--_interactive-border-color)}#menu-toggle.boxed:not(.open):focus{outline:var(--rh-length-3xs,2px) solid var(--_interactive-border-color);outline-offset:var(--rh-length-3xs,2px)}#menu-toggle.boxed.open{box-shadow:inset 0 0 0 var(--rh-length-3xs,2px) var(--_interactive-border-color)}#menu-toggle.boxed.open:focus{outline:var(--rh-length-3xs,2px) solid var(--_interactive-border-color);outline-offset:var(--rh-length-3xs,2px)}#menu-toggle.compact{padding:var(--rh-space-md,8px)}#menu-toggle.compact.open:not(.boxed),#menu-toggle.compact:not(.boxed):active,#menu-toggle.compact:not(.boxed):focus,#menu-toggle.compact:not(.boxed):hover{background-color:light-dark(var(--rh-color-surface-lighter,#f2f2f2),var(--rh-color-surface-darker,#1f1f1f))}#menu-toggle.compact:not(.boxed):active,#menu-toggle.compact:not(.boxed):focus{box-shadow:inset 0 0 0 var(--rh-length-3xs,2px) var(--_interactive-border-color)}#menu-toggle.disabled{pointer-events:none;cursor:not-allowed;background:light-dark(var(--rh-color-gray-30,#c7c7c7),var(--rh-color-gray-40,#a3a3a3))}#menu-toggle.disabled:active,#menu-toggle.disabled:focus,#menu-toggle.disabled:hover{box-shadow:none}#menu-dropdown-container{inline-size:100%;position:relative}::slotted(hr){border:0;border-top:var(--_border);inline-size:100%}::slotted(rh-icon){margin-inline-end:var(--rh-space-md,8px)}rh-menu::part(menu){inline-size:100%}:host([aria-disabled=true]){pointer-events:none;cursor:not-allowed}:host([disabled]) .action-icon rh-icon,:host([disabled]) ::slotted([slot=toggle-label]){color:light-dark(var(--rh-color-gray-50,#707070),var(--rh-color-gray-60,#4d4d4d))}`;
/** Fired when a user selects an action or link from the menu */
export class MenuDropdownSelectEvent extends Event {
constructor(selectedItem, text) {
super('select', { bubbles: true, composed: true });
this.selectedItem = selectedItem;
this.text = text;
}
}
/**
* A menu dropdown presents a list of actions or links in a vertically stacked menu,
* appearing when a user interacts with a toggle button.
*
* @summary A collapsible menu for presenting a list of options or actions
*
* @alias menu-dropdown
*/
let RhMenuDropdown = RhMenuDropdown_1 = class RhMenuDropdown extends LitElement {
constructor() {
super(...arguments);
_RhMenuDropdown_instances.add(this);
/**
* whether the dropdown is currently open.
*/
this.open = false;
/**
* Disables user interaction with the dropdown. When true, the dropdown cannot
* be opened or interacted with, and appears visually disabled.
*/
this.disabled = false;
/**
* Provides an accessible name for the dropdown's trigger, improving screen reader support.
* This label is announced to assistive technologies to describe the purpose of
* the compact menu dropdown.
*/
this.accessibleLabel = 'Toggle menu';
_RhMenuDropdown_tabindex.set(this, RovingTabindexController.of(this, {
getItems: () => __classPrivateFieldGet(this, _RhMenuDropdown_instances, "a", _RhMenuDropdown_items_get),
}));
_RhMenuDropdown_float.set(this, new FloatingDOMController(this, {
content: () => this.menuList,
invoker: () => this.menuToggleButton,
}));
}
connectedCallback() {
super.connectedCallback();
RhMenuDropdown_1.instances.add(this);
}
disconnectedCallback() {
RhMenuDropdown_1.instances.delete(this);
}
firstUpdated() {
this.updateComplete.then(() => {
__classPrivateFieldGet(this, _RhMenuDropdown_instances, "m", _RhMenuDropdown_validateSlotContent).call(this);
});
}
/**
* Moves focus to the currently active (focused) item.
*/
focus() {
__classPrivateFieldGet(this, _RhMenuDropdown_tabindex, "f").items[__classPrivateFieldGet(this, _RhMenuDropdown_tabindex, "f").atFocusedItemIndex]?.focus();
}
render() {
const { alignment, anchor, styles, open } = __classPrivateFieldGet(this, _RhMenuDropdown_float, "f");
return html `
<div =${__classPrivateFieldGet(this, _RhMenuDropdown_instances, "m", _RhMenuDropdown_onFocusOut)} id="menu-dropdown-container">
<button id="menu-toggle"
type="button"
aria-haspopup="menu"
aria-expanded="${this.open}"
="${__classPrivateFieldGet(this, _RhMenuDropdown_instances, "m", _RhMenuDropdown_toggleMenu)}"
aria-controls="menu-list"
aria-disabled="${this.disabled}"
="${__classPrivateFieldGet(this, _RhMenuDropdown_instances, "m", _RhMenuDropdown_onToggleKeydown)}"
class="${classMap({
boxed: this.variant !== 'borderless',
compact: this.layout === 'compact',
disabled: this.disabled,
open: this.open,
})}">
${this.layout === 'compact' ?
html `<rh-icon set="ui" accessible-label=${this.accessibleLabel} icon="ellipsis-vertical-fill"></rh-icon>`
: html `
<span class="info-section">
<!-- Use this slot for the toggle label. Keep toggle labels short and succinct. -->
<slot name="toggle-label"></slot>
</span>
<span class="action-icon">
<rh-icon set="microns" icon="${this.open ? 'caret-up' : 'caret-down'}"></rh-icon>
</span>
`}
</button>
<div id="menu-list"
?hidden=${!this.open}
=${__classPrivateFieldGet(this, _RhMenuDropdown_instances, "m", _RhMenuDropdown_onSelect)}
style="${styleMap(styles)}"
class="${classMap({ [anchor]: !!anchor, [alignment]: !!alignment, open })}"
=${__classPrivateFieldGet(this, _RhMenuDropdown_instances, "m", _RhMenuDropdown_onKeyDown)}>
<rh-menu role="menu" aria-labelledby="menu-toggle">
<!--
Use this slot to provide the menu content. Use the "rh-menu" component
for the menu panel, and use "rh-menu-items" to define the individual menu items.
To organize menu items into groups, use the "rh-menu-item-group" component.
-->
<slot></slot>
</rh-menu>
</div>
</div>
`;
}
get items() {
if (!isServer) {
return Array.from(this.querySelectorAll('rh-menu-item'));
}
else {
return [];
}
}
};
_RhMenuDropdown_tabindex = new WeakMap();
_RhMenuDropdown_float = new WeakMap();
_RhMenuDropdown_instances = new WeakSet();
_RhMenuDropdown_items_get = function _RhMenuDropdown_items_get() {
return this.items;
};
_RhMenuDropdown_outsideClick = function _RhMenuDropdown_outsideClick(event) {
const path = event.composedPath();
if (!path.includes(this)) {
if (this.open) {
this.open = false;
}
}
};
_RhMenuDropdown_validateSlotContent = function _RhMenuDropdown_validateSlotContent() {
this.slotElement?.forEach((slot) => {
const assignedElements = slot.assignedElements({ flatten: true });
assignedElements.forEach(el => {
if (el instanceof HTMLHRElement) {
el.inert = true;
}
});
});
};
_RhMenuDropdown_toggleMenu = function _RhMenuDropdown_toggleMenu() {
if (!this.disabled) {
this.open = !this.open;
if (this.open) {
this.updateComplete.then(async () => {
await __classPrivateFieldGet(this, _RhMenuDropdown_instances, "m", _RhMenuDropdown_positionMenu).call(this);
__classPrivateFieldGet(this, _RhMenuDropdown_instances, "m", _RhMenuDropdown_focusFirstItem).call(this);
});
}
else {
__classPrivateFieldGet(this, _RhMenuDropdown_float, "f").hide();
}
}
};
_RhMenuDropdown_focusFirstItem = function _RhMenuDropdown_focusFirstItem() {
this.items[0]?.focus();
};
_RhMenuDropdown_onToggleKeydown = function _RhMenuDropdown_onToggleKeydown(e) {
if (!this.disabled) {
if (['Enter', ' ', 'ArrowDown'].includes(e.key)) {
e.preventDefault();
this.open = true;
this.updateComplete.then(async () => {
await __classPrivateFieldGet(this, _RhMenuDropdown_instances, "m", _RhMenuDropdown_positionMenu).call(this);
__classPrivateFieldGet(this, _RhMenuDropdown_instances, "m", _RhMenuDropdown_focusFirstItem).call(this);
});
}
}
};
_RhMenuDropdown_handleSelection = function _RhMenuDropdown_handleSelection(target) {
this.open = false;
this.menuToggleButton.focus();
this.dispatchEvent(new MenuDropdownSelectEvent(target, target.textContent ? target.textContent : ''));
if (target.href) {
if (target.external) {
window.open(target.href, '_blank', 'noopener,noreferrer');
}
else {
window.location.href = target.href;
}
}
};
_RhMenuDropdown_onSelect = function _RhMenuDropdown_onSelect(event) {
if (event.target instanceof RhMenuItem) {
__classPrivateFieldGet(this, _RhMenuDropdown_instances, "m", _RhMenuDropdown_handleSelection).call(this, event.target);
}
};
_RhMenuDropdown_onKeyDown = function _RhMenuDropdown_onKeyDown(event) {
if (event.key === 'Escape') {
if (this.open) {
this.open = false;
this.menuToggleButton.focus();
}
}
else if (event.target instanceof RhMenuItem
&& (event.key === 'Enter' || event.key === ' ')
&& !event.target.disabled) {
event.preventDefault();
__classPrivateFieldGet(this, _RhMenuDropdown_instances, "m", _RhMenuDropdown_handleSelection).call(this, event.target);
}
};
_RhMenuDropdown_onFocusOut = function _RhMenuDropdown_onFocusOut(event) {
const relatedTarget = event.relatedTarget;
// If the next focused element is outside this component, close the dropdown
if (relatedTarget !== this.menuToggleButton
&& relatedTarget !== this.menuList
&& relatedTarget && !this.contains(relatedTarget)) {
if (this.open) {
this.open = false;
}
}
// Also close if nothing is focused (focus left the document)
if (!relatedTarget) {
this.open = false;
}
};
_RhMenuDropdown_positionMenu = async function _RhMenuDropdown_positionMenu() {
await this.updateComplete;
const placement = 'bottom-start';
const mainAxis = 4;
const offset = { mainAxis: mainAxis, alignmentAxis: 0 };
await __classPrivateFieldGet(this, _RhMenuDropdown_float, "f").show({ offset: offset, placement: placement });
};
RhMenuDropdown.styles = [styles];
RhMenuDropdown.instances = new Set();
RhMenuDropdown.shadowRootOptions = {
...LitElement.shadowRootOptions,
delegatesFocus: true,
};
(() => {
if (!isServer) {
document.addEventListener('click', function (event) {
for (const instance of RhMenuDropdown_1.instances) {
__classPrivateFieldGet(instance, _RhMenuDropdown_instances, "m", _RhMenuDropdown_outsideClick).call(instance, event);
}
});
}
})();
__decorate([
property({ type: Boolean, reflect: true })
], RhMenuDropdown.prototype, "open", void 0);
__decorate([
property({ reflect: true })
], RhMenuDropdown.prototype, "variant", void 0);
__decorate([
property({ attribute: 'layout', reflect: true })
], RhMenuDropdown.prototype, "layout", void 0);
__decorate([
property({ type: Boolean, reflect: true })
], RhMenuDropdown.prototype, "disabled", void 0);
__decorate([
property({ attribute: 'accessible-label', reflect: true })
], RhMenuDropdown.prototype, "accessibleLabel", void 0);
__decorate([
query('#menu-toggle')
], RhMenuDropdown.prototype, "menuToggleButton", void 0);
__decorate([
query('#menu-list')
], RhMenuDropdown.prototype, "menuList", void 0);
__decorate([
queryAll('slot')
], RhMenuDropdown.prototype, "slotElement", void 0);
RhMenuDropdown = RhMenuDropdown_1 = __decorate([
customElement('rh-menu-dropdown')
], RhMenuDropdown);
export { RhMenuDropdown };
//# sourceMappingURL=rh-menu-dropdown.js.map