@rhds/elements
Version:
Red Hat Design System Elements
399 lines • 24.1 kB
JavaScript
var _RhNavigationSecondary_instances, _a, _RhNavigationSecondary_logger, _RhNavigationSecondary_logoCopy, _RhNavigationSecondary_compact, _RhNavigationSecondary_internals, _RhNavigationSecondary_computedPalette_get, _RhNavigationSecondary_screenSize, _RhNavigationSecondary_onExpandRequest, _RhNavigationSecondary_onFocusout, _RhNavigationSecondary_onOverlayClick, _RhNavigationSecondary_onKeydown, _RhNavigationSecondary_onKeyup, _RhNavigationSecondary_onTabKeyup, _RhNavigationSecondary_onTabKeydown, _RhNavigationSecondary_getDropdownIndex, _RhNavigationSecondary_dropdownByIndex, _RhNavigationSecondary_expand, _RhNavigationSecondary_allDropdowns, _RhNavigationSecondary_closeDropdown, _RhNavigationSecondary_openDropdown, _RhNavigationSecondary_onOverlayChange, _RhNavigationSecondary_upgradeAccessibility, _RhNavigationSecondary_toggleMobileMenu;
var RhNavigationSecondary_1;
import { __classPrivateFieldGet, __classPrivateFieldSet, __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 { classMap } from 'lit/directives/class-map.js';
import { state } from 'lit/decorators/state.js';
import { ComposedEvent } from '@patternfly/pfe-core';
import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js';
import { Logger } from '@patternfly/pfe-core/controllers/logger.js';
import { colorPalettes } from '@rhds/elements/lib/color-palettes.js';
import { themable } from '@rhds/elements/lib/themable.js';
import '@rhds/elements/rh-surface/rh-surface.js';
import './rh-navigation-secondary-menu-section.js';
import './rh-navigation-secondary-overlay.js';
import { RhNavigationSecondaryDropdown, SecondaryNavDropdownExpandEvent, } from './rh-navigation-secondary-dropdown.js';
import { ScreenSizeController } from '../../lib/ScreenSizeController.js';
export class SecondaryNavOverlayChangeEvent extends ComposedEvent {
constructor(open, toggle) {
super('overlay-change');
this.open = open;
this.toggle = toggle;
}
}
import { css } from "lit";
const styles = css `:host{--_chevron-size:calc(var(--rh-font-size-body-text-md, 1rem)*0.35);--_chevron-thickness:calc(var(--rh-font-size-body-text-md, 1rem)*0.125);--_chevron-up:45deg;--_chevron-down:-135deg;--_chevron-correction-x:calc(var(--rh-font-size-body-text-md, 1rem)*-1/16);--_chevron-correction-y:calc(var(--rh-font-size-body-text-md, 1rem)*-1/16);--_chevron-color:var(--rh-color-text-primary);--_chevron-transform-collapsed:rotate(var(--_chevron-up)) translate(var(--_chevron-correction-x),var(--_chevron-correction-x));--_chevron-transform-expanded:rotate(var(--_chevron-down)) translate(var(--_chevron-correction-y),var(--_chevron-correction-y));--_button-font-color:var(--rh-color-text-primary);--_nav-max-height:var(--_max-height,max-content);--_nav-min-height:var(--_min-height,80px);--_current-active-child-border-color:var(--rh-color-brand-red);--_border-color:light-dark(#0000,var(--rh-color-border-subtle));z-index:var(--rh-navigation-secondary-z-index,102)}:host(:dir(rtl)){--_chevron-transform-collapsed:rotate(calc(var(--_chevron-up)*-1)) translate(calc(var(--_chevron-correction-y)*1),var(--_chevron-correction-y));--_chevron-transform-expanded:rotate(calc(var(--_chevron-down)*-1)) translate(calc(var(--_chevron-correction-x)*1),var(--_chevron-correction-x))}#container,nav{z-index:var(--rh-navigation-secondary-z-index,102)}#container{display:grid;position:absolute;width:100%;background-color:light-dark(var(--rh-color-surface-lighter,#f2f2f2),var(--rh-color-surface-dark,#383838));gap:0 var(--rh-space-lg,16px);grid-template-rows:minmax(var(--_nav-min-height),var(--_nav-max-height)) max-content max-content;grid-template-columns:1fr max-content;grid-template-areas:"logo menu" "main main";height:fit-content;min-height:100%;max-height:100vh;overflow-y:auto;border-block-end:var(--rh-border-width-sm,1px) solid var(--_border-color)}rh-surface{grid-area:main;display:flex;flex-direction:column;height:100%;width:100%}::slotted([slot=cta]),::slotted([slot=nav]){grid-area:unset!important}#container.expanded ::slotted([slot=cta]),#container.expanded ::slotted([slot=nav]){display:flex!important}#container.expanded ::slotted([slot=nav]){list-style:none;flex-direction:column;padding:2rem 1rem 0;padding:var(--rh-space-2xl,32px) var(--rh-space-lg,16px) 0 var(--rh-space-lg,16px);margin:0!important}#container.expanded ::slotted([slot=cta]){padding:2rem 1rem}button{grid-area:menu;border:none;display:flex;height:100%;align-items:center;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);padding:var(--rh-space-lg,16px);border-block-start:var(--rh-border-width-lg,3px) solid #0000;margin-inline-end:var(--rh-space-lg,16px);color:var(--_button-font-color);background-color:var(--rh-color-surface);gap:var(--rh-space-md,8px)}button:hover{border-block-start-color:var(--rh-color-text-brand-on-light,#e00)}button:after{box-sizing:initial!important;content:"";display:block;width:var(--_chevron-size);height:var(--_chevron-size);border-inline-end:var(--_chevron-thickness) solid var(--_chevron-color);border-block-end:var(--_chevron-thickness) solid var(--_chevron-color);transform:var(--_chevron-transform-collapsed)}button[aria-expanded=true]:after{transform:var(--_chevron-transform-expanded)}button:focus{border-block-start-color:var(--rh-color-text-brand-on-light,#e00)}:host([color-palette=dark]) button{background-color:var(--rh-color-surface)}:host([color-palette=dark]) button[aria-expanded=true],button:active,button[aria-expanded=true]{--_chevron-color:var(--rh-color-text-primary-on-light,#151515);color:var(--rh-color-text-primary-on-light,#151515);background-color:var(--rh-color-surface-lightest,#fff);border-block-start-color:var(--rh-color-text-brand-on-light,#e00);border-block-end:none}:host([color-palette=dark]) button:active{color:var(--rh-color-text-primary-on-dark,#fff)}:host([color-palette=dark]) button[aria-expanded=true]:active{color:var(--rh-color-text-primary-on-light,#151515)} screen and (min-width:768px){button{margin-inline-end:var(--rh-space-2xl,32px)}#container.expanded ::slotted([slot=nav]){padding:var(--rh-space-2xl,32px) var(--rh-space-2xl,32px) 0!important}#container.expanded ::slotted([slot=cta]){padding:var(--rh-space-2xl,32px)!important}} screen and (min-width:992px){:host{--_min-height:86px}#container{grid-template-areas:"logo main";grid-template-rows:auto;grid-template-columns:max-content 1fr;height:100%;max-height:none;overflow-y:initial}rh-surface{flex-direction:row;justify-content:space-between}#container.expanded ::slotted([slot=nav]){max-height:calc(100vh - var(--_nav-min-height))}button{display:none}}`;
/* TODO: Abstract this out to a shareable function, should RTI handle something similar? */
function focusableChildElements(parent) {
return parent.querySelectorAll(`a,
button:not([disabled]),
input:not([disabled]),
select:not([disabled]),
textarea:not([disabled]),
[tabindex]:not([tabindex="-1"]):not([disabled]),
details:not([disabled]),
summary:not(:disabled)`);
}
/**
* The secondary navigation is used to connect a series of pages together. It displays wayfinding content and links relevant to the page it is placed on. It should be used in conjunction with the [primary navigation](../navigation-primary).
*
* @summary Propagates related content across a series of pages
*
* @alias Navigation (secondary)
*
* @fires {SecondaryNavOverlayChangeEvent} overlay-change -
* Fires when an dropdown is opened or closed in desktop
* view or when the mobile menu button is toggled in mobile
* view.
*/
let RhNavigationSecondary = RhNavigationSecondary_1 = _a = class RhNavigationSecondary extends LitElement {
constructor() {
super(...arguments);
_RhNavigationSecondary_instances.add(this);
_RhNavigationSecondary_logger.set(this, new Logger(this));
_RhNavigationSecondary_logoCopy.set(this, null);
/** Compact mode */
_RhNavigationSecondary_compact.set(this, false);
_RhNavigationSecondary_internals.set(this, InternalsController.of(this, { role: 'navigation' }));
/**
* Color palette dark | lighter (default: lighter)
*/
this.colorPalette = 'lighter';
/**
* Customize the default `aria-label` on the `<nav>` container.
* Defaults to "secondary" if no attribute/property is set.
*/
this.accessibleLabel = 'secondary';
/**
* `mobileMenuExpanded` property is toggled when the mobile menu button is clicked,
* a focusout event occurs, or on an overlay click event. It also switches state
* when the viewport changes breakpoints depending on if a dropdown is open or not.
*/
this.mobileMenuExpanded = false;
this.overlayOpen = false;
/**
* ScreenSizeController effects callback to set #compact
* When viewport size changes,
* - If viewport is mobile, open mobile menu
* - otherwise, close mobile menu and close overlay
*/
_RhNavigationSecondary_screenSize.set(this, new ScreenSizeController(this, 'md', {
onChange: matches => {
__classPrivateFieldSet(this, _RhNavigationSecondary_compact, !matches, "f");
const dropdownsOpen = __classPrivateFieldGet(this, _RhNavigationSecondary_instances, "m", _RhNavigationSecondary_allDropdowns).call(this).some(x => x.expanded);
this.mobileMenuExpanded = __classPrivateFieldGet(this, _RhNavigationSecondary_compact, "f") && dropdownsOpen;
this.overlayOpen = dropdownsOpen;
},
}));
}
/**
* Checks if passed in element is a RhNavigationSecondaryDropdown
* @param element possibly an rh-navigation-secondary-dropdown
*/
static isDropdown(element) {
return element instanceof RhNavigationSecondaryDropdown;
}
async connectedCallback() {
super.connectedCallback();
RhNavigationSecondary_1.instances.add(this);
this.addEventListener('expand-request', __classPrivateFieldGet(this, _RhNavigationSecondary_instances, "m", _RhNavigationSecondary_onExpandRequest));
this.addEventListener('overlay-change', __classPrivateFieldGet(this, _RhNavigationSecondary_instances, "m", _RhNavigationSecondary_onOverlayChange));
this.addEventListener('focusout', __classPrivateFieldGet(this, _RhNavigationSecondary_instances, "m", _RhNavigationSecondary_onFocusout));
this.addEventListener('keydown', __classPrivateFieldGet(this, _RhNavigationSecondary_instances, "m", _RhNavigationSecondary_onKeydown));
if (!isServer) {
__classPrivateFieldGet(this, _RhNavigationSecondary_instances, "m", _RhNavigationSecondary_upgradeAccessibility).call(this);
}
}
async firstUpdated() {
if (!isServer) {
await this.updateComplete;
__classPrivateFieldSet(this, _RhNavigationSecondary_compact, !__classPrivateFieldGet(this, _RhNavigationSecondary_screenSize, "f").matches.has('md'), "f");
}
}
disconnectedCallback() {
super.disconnectedCallback();
RhNavigationSecondary_1.instances.delete(this);
}
render() {
const expanded = this.mobileMenuExpanded;
// CTA must always be 'lightest' on mobile screens
const dropdownPalette = __classPrivateFieldGet(this, _RhNavigationSecondary_compact, "f") ? 'lightest' : __classPrivateFieldGet(this, _RhNavigationSecondary_instances, "a", _RhNavigationSecondary_computedPalette_get);
return html `
<!-- container, \`<nav>\` element -->
<div part="nav"
class="${classMap({ compact: __classPrivateFieldGet(this, _RhNavigationSecondary_compact, "f") })}">
${__classPrivateFieldGet(this, _RhNavigationSecondary_logoCopy, "f")}
<!-- container, \`<div>\` element -->
<div id="container" part="container" class="${classMap({ expanded })}">
<!-- Logo added to the main nav bar, expects \`<a>Text</a> | <a><svg/></a> | <a><img/></a>\` element -->
<slot name="logo" id="logo"></slot>
<button aria-controls="container"
aria-expanded="${String(expanded)}"
="${__classPrivateFieldGet(this, _RhNavigationSecondary_instances, "m", _RhNavigationSecondary_toggleMobileMenu)}"><!--
Text label for the mobile menu button, for l10n. Defaults to "Menu"
--><slot name="mobile-menu">Menu</slot></button>
<rh-surface color-palette="${dropdownPalette}">
<!-- Navigation list added to the main nav bar, expects \`<ul>\` element -->
<slot name="nav"></slot>
<!-- container, \`<div>\` element -->
<div id="cta" part="cta">
<!-- Nav bar level CTA, expects \`<rh-cta>\` element -->
<slot name="cta"></slot>
</div>
</rh-surface>
</div>
</div>
<rh-navigation-secondary-overlay
.open="${this.overlayOpen}"
="${__classPrivateFieldGet(this, _RhNavigationSecondary_instances, "m", _RhNavigationSecondary_onOverlayClick)}"
></rh-navigation-secondary-overlay>
`;
}
/**
* Opens a specific dropdown based on index.
* Closes all open dropdowns before opening specified.
* Toggles overlay to open
* @param index - index of the dropdown to open
*/
open(index) {
if (index != null) {
const dropdown = __classPrivateFieldGet(this, _RhNavigationSecondary_instances, "m", _RhNavigationSecondary_dropdownByIndex).call(this, index);
if (dropdown && RhNavigationSecondary_1.isDropdown(dropdown)) {
this.close();
__classPrivateFieldGet(this, _RhNavigationSecondary_instances, "m", _RhNavigationSecondary_expand).call(this, index);
dropdown?.querySelector('a')?.focus();
this.overlayOpen = true;
}
}
}
/**
* Closes all open dropdowns
*/
close() {
__classPrivateFieldGet(this, _RhNavigationSecondary_instances, "m", _RhNavigationSecondary_allDropdowns).call(this)
.forEach(dropdown => __classPrivateFieldGet(this, _RhNavigationSecondary_instances, "m", _RhNavigationSecondary_closeDropdown).call(this, dropdown));
}
};
_RhNavigationSecondary_logger = new WeakMap();
_RhNavigationSecondary_logoCopy = new WeakMap();
_RhNavigationSecondary_compact = new WeakMap();
_RhNavigationSecondary_internals = new WeakMap();
_RhNavigationSecondary_screenSize = new WeakMap();
_RhNavigationSecondary_instances = new WeakSet();
_RhNavigationSecondary_computedPalette_get = function _RhNavigationSecondary_computedPalette_get() {
switch (this.colorPalette) {
case 'lighter':
case 'dark':
return this.colorPalette;
case 'light':
case 'lightest':
return 'lighter';
case 'darker':
case 'darkest':
return 'dark';
default:
return 'lightest';
}
};
_RhNavigationSecondary_onExpandRequest = function _RhNavigationSecondary_onExpandRequest(event) {
if (event instanceof SecondaryNavDropdownExpandEvent) {
const index = __classPrivateFieldGet(this, _RhNavigationSecondary_instances, "m", _RhNavigationSecondary_getDropdownIndex).call(this, event.target);
if (index === null || index === undefined) {
return;
}
this.close();
if (event.expanded) {
__classPrivateFieldGet(this, _RhNavigationSecondary_instances, "m", _RhNavigationSecondary_expand).call(this, index);
}
if (__classPrivateFieldGet(this, _RhNavigationSecondary_screenSize, "f").matches.has('md')) {
this.dispatchEvent(new SecondaryNavOverlayChangeEvent(event.expanded, event.toggle));
}
}
};
_RhNavigationSecondary_onFocusout = function _RhNavigationSecondary_onFocusout(event) {
const target = event.relatedTarget;
if (target?.closest('rh-navigation-secondary') === this || target === null) {
// if the focus is still inside the rh-navigation-secondary exit
return;
}
else {
if (__classPrivateFieldGet(this, _RhNavigationSecondary_screenSize, "f").matches.has('md')) {
this.mobileMenuExpanded = false;
}
this.close();
this.overlayOpen = false;
}
};
_RhNavigationSecondary_onOverlayClick = function _RhNavigationSecondary_onOverlayClick() {
this.close();
this.overlayOpen = false;
if (!__classPrivateFieldGet(this, _RhNavigationSecondary_screenSize, "f").matches.has('md')) {
this.mobileMenuExpanded = false;
}
};
_RhNavigationSecondary_onKeydown = function _RhNavigationSecondary_onKeydown(event) {
switch (event.key) {
case 'Escape': {
if (!__classPrivateFieldGet(this, _RhNavigationSecondary_screenSize, "f").matches.has('md')) {
this.mobileMenuExpanded = false;
this.shadowRoot?.querySelector('button')?.focus?.();
}
this.close();
this.overlayOpen = false;
break;
}
case 'Tab':
__classPrivateFieldGet(this, _RhNavigationSecondary_instances, "m", _RhNavigationSecondary_onTabKeydown).call(this, event);
break;
default:
break;
}
};
_RhNavigationSecondary_onKeyup = function _RhNavigationSecondary_onKeyup(event) {
switch (event.key) {
case 'Tab':
__classPrivateFieldGet(this, _RhNavigationSecondary_instances, "m", _RhNavigationSecondary_onTabKeyup).call(this, event);
break;
default:
break;
}
};
_RhNavigationSecondary_onTabKeyup = function _RhNavigationSecondary_onTabKeyup(event) {
if (!this.mobileMenuExpanded) {
return;
}
const { target } = event;
if (!this.contains(target)) {
__classPrivateFieldGet(this, _RhNavigationSecondary_instances, "m", _RhNavigationSecondary_toggleMobileMenu).call(this);
this.overlayOpen = false;
}
};
_RhNavigationSecondary_onTabKeydown = function _RhNavigationSecondary_onTabKeydown(event) {
// target is the element we are leaving with tab press
const target = event.target;
// get target parent dropdown
const dropdowns = __classPrivateFieldGet(this, _RhNavigationSecondary_instances, "m", _RhNavigationSecondary_allDropdowns).call(this);
const dropdownParent = dropdowns.find(dropdown => dropdown.contains(target));
if (!dropdownParent) {
return;
}
const focusableChildren = focusableChildElements(dropdownParent);
if (!focusableChildren) {
return;
}
if (event.shiftKey) {
const firstFocusable = focusableChildren[0] === target;
if (!firstFocusable) {
return;
}
else {
this.close();
if (!this.mobileMenuExpanded) {
this.overlayOpen = false;
}
}
}
else {
// is the target the last focusableChildren element in the dropdown
const lastFocusable = focusableChildren[focusableChildren.length - 1] === target;
if (!lastFocusable) {
return;
}
this.close();
if (!this.mobileMenuExpanded) {
this.overlayOpen = false;
}
}
};
_RhNavigationSecondary_getDropdownIndex = function _RhNavigationSecondary_getDropdownIndex(element) {
if (!RhNavigationSecondary_1.isDropdown(element)) {
__classPrivateFieldGet(this, _RhNavigationSecondary_logger, "f").warn('The getDropdownIndex method expects to receive a dropdown element.');
return;
}
const dropdowns = __classPrivateFieldGet(this, _RhNavigationSecondary_instances, "m", _RhNavigationSecondary_allDropdowns).call(this);
const index = dropdowns.findIndex(dropdown => dropdown.id === element.id);
return index;
};
_RhNavigationSecondary_dropdownByIndex = function _RhNavigationSecondary_dropdownByIndex(index) {
const dropdowns = __classPrivateFieldGet(this, _RhNavigationSecondary_instances, "m", _RhNavigationSecondary_allDropdowns).call(this);
if (dropdowns[index] === undefined) {
__classPrivateFieldGet(this, _RhNavigationSecondary_logger, "f").error('This dropdown index does not exist.');
return;
}
return dropdowns[index];
};
_RhNavigationSecondary_expand = function _RhNavigationSecondary_expand(index) {
if (index == null) {
return;
}
const dropdown = __classPrivateFieldGet(this, _RhNavigationSecondary_instances, "m", _RhNavigationSecondary_dropdownByIndex).call(this, index);
if (dropdown && RhNavigationSecondary_1.isDropdown(dropdown)) {
__classPrivateFieldGet(this, _RhNavigationSecondary_instances, "m", _RhNavigationSecondary_openDropdown).call(this, dropdown);
}
};
_RhNavigationSecondary_allDropdowns = function _RhNavigationSecondary_allDropdowns() {
return Array.from(this.querySelectorAll('rh-navigation-secondary-dropdown'));
};
_RhNavigationSecondary_closeDropdown = function _RhNavigationSecondary_closeDropdown(dropdown) {
if (dropdown.expanded === false) {
return;
}
dropdown.expanded = false;
};
_RhNavigationSecondary_openDropdown = function _RhNavigationSecondary_openDropdown(dropdown) {
if (dropdown.expanded === true) {
return;
}
dropdown.expanded = true;
};
_RhNavigationSecondary_onOverlayChange = function _RhNavigationSecondary_onOverlayChange(event) {
if (event instanceof SecondaryNavOverlayChangeEvent) {
if (this.contains(event.toggle)) {
this.overlayOpen = event.open;
}
}
};
_RhNavigationSecondary_upgradeAccessibility = function _RhNavigationSecondary_upgradeAccessibility() {
// remove role="navigation" from host on upgrade
this.removeAttribute('role');
// remove aria-labelledby from slotted `<ul>` on upgrade
this.querySelector(':is([slot="nav"]):is(ul)')?.removeAttribute('aria-labelledby');
__classPrivateFieldGet(this, _RhNavigationSecondary_internals, "f").ariaLabel = this.accessibleLabel;
};
_RhNavigationSecondary_toggleMobileMenu = function _RhNavigationSecondary_toggleMobileMenu() {
this.mobileMenuExpanded = !this.mobileMenuExpanded;
this.dispatchEvent(new SecondaryNavOverlayChangeEvent(this.mobileMenuExpanded, this));
};
RhNavigationSecondary.styles = [styles];
RhNavigationSecondary.instances = new Set();
(() => {
if (!isServer) {
document.addEventListener('keyup', (event) => {
const { instances } = RhNavigationSecondary_1;
for (const instance of instances) {
__classPrivateFieldGet(instance, _RhNavigationSecondary_instances, "m", _RhNavigationSecondary_onKeyup).call(instance, event);
}
}, { capture: false });
}
})();
__decorate([
property({ reflect: true, attribute: 'color-palette' })
], RhNavigationSecondary.prototype, "colorPalette", void 0);
__decorate([
property({ attribute: 'accessible-label' })
], RhNavigationSecondary.prototype, "accessibleLabel", void 0);
__decorate([
state()
], RhNavigationSecondary.prototype, "mobileMenuExpanded", void 0);
__decorate([
state()
], RhNavigationSecondary.prototype, "overlayOpen", void 0);
RhNavigationSecondary = RhNavigationSecondary_1 = __decorate([
customElement('rh-navigation-secondary'),
colorPalettes,
themable
], RhNavigationSecondary);
export { RhNavigationSecondary };
//# sourceMappingURL=rh-navigation-secondary.js.map