@telekom/scale-components
Version:
Scale is the digital design system for Telekom products and experiences.
312 lines (310 loc) • 9.01 kB
JavaScript
/**
* @license
* Scale https://github.com/telekom/scale
*
* Copyright (c) 2021 Egor Kirpichev and contributors, Deutsche Telekom AG
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { Component, h, Host, Element, Event, Listen, Method, Prop, State, Watch, } from '@stencil/core';
import cx from 'classnames';
import { emitEvent, animationsFinished } from '../../../utils/utils';
/*
TODO add something like this with a better-named prop defaulting to false
if (this.allowInjectingStyleToBody) {
this.bodyOverflowValue = document.body.style.overflow;
// The following style will disable body from scrolling when modal is open
document.body.style.setProperty('overflow', 'hidden');
}
*/
export class TelekomNavItem {
constructor() {
/** Open the flyout menu */
this.expanded = false;
/** (optional) Variant ("mobile" gives it a fixed height of `100vh`) */
this.variant = null;
/** (optinal) Whether the flyout should open on hover (needs better name!) */
this.hover = false;
this.isExpanded = this.expanded;
this.handleSpaceOrEnterForHover = (event) => {
if (this.isExpanded) {
return;
}
if (event.key === 'Enter' || event.key === ' ') {
this.expanded = true;
this.show();
}
};
this.handleTriggerClick = (event) => {
if (event.ctrlKey) {
return;
}
event.preventDefault();
event.stopImmediatePropagation();
this.expanded = !this.expanded;
this.parentElement.removeEventListener('mouseleave', this.handlePointerOut);
};
this.handlePointerIn = () => {
if (this.isExpanded) {
return;
}
this.expanded = true;
this.parentElement.addEventListener('mouseleave', this.handlePointerOut);
};
this.handlePointerOut = () => {
this.expanded = false;
this.parentElement.removeEventListener('mouseleave', this.handlePointerOut);
};
}
handleWindowKeydown(event) {
if (!this.isExpanded) {
return;
}
if (event.key === 'Escape') {
this.expanded = false;
try {
this.triggerElement.focus();
}
catch (err) { }
}
}
handleScaleCloseNavFlyout() {
this.expanded = false;
}
handleDocumentClick(event) {
if (!this.isExpanded) {
return;
}
const { target } = event;
const isNotTrigger = () => target !== this.triggerElement && !this.triggerElement.contains(target);
const isNotWithin = () => !this.hostElement.contains(target);
if (isNotTrigger() && isNotWithin()) {
this.expanded = false;
}
}
expandedChanged(newValue) {
newValue ? this.show() : this.hide();
}
connectedCallback() {
this.parentElement = this.hostElement.parentElement;
if (this.triggerElement == null) {
return;
}
this.triggerElement.setAttribute('aria-haspopup', 'true');
this.triggerElement.setAttribute('aria-expanded', String(this.expanded));
if (this.hover) {
this.triggerElement.addEventListener('mouseenter', this.handlePointerIn);
this.triggerElement.addEventListener('keypress', this.handleSpaceOrEnterForHover);
}
else {
this.triggerElement.addEventListener('click', this.handleTriggerClick);
}
}
disconnectedCallback() {
this.triggerElement.removeEventListener('click', this.handleTriggerClick);
this.triggerElement.removeEventListener('mouseenter', this.handlePointerIn);
this.triggerElement.removeEventListener('keypress', this.handleSpaceOrEnterForHover);
}
async show() {
this.isExpanded = true;
this.animationState = 'in';
requestAnimationFrame(async () => {
await animationsFinished(this.hostElement.shadowRoot);
this.animationState = undefined;
this.triggerElement.setAttribute('aria-expanded', 'true');
emitEvent(this, 'scaleExpanded', { expanded: true });
});
}
async hide() {
this.animationState = 'out';
requestAnimationFrame(async () => {
await animationsFinished(this.hostElement.shadowRoot);
this.animationState = undefined;
this.isExpanded = false;
this.triggerElement.setAttribute('aria-expanded', 'false');
emitEvent(this, 'scaleExpanded', { expanded: false });
});
}
/**
* Get the trigger element "on demand".
* Either query by `trigger-selector` or
* get the previous sibling.
*/
get triggerElement() {
if (this.triggerSelector) {
return this.hostElement.ownerDocument.querySelector(this.triggerSelector);
}
return this.hostElement.previousElementSibling;
}
render() {
return (h(Host, null,
h("div", { part: cx('base', this.animationState, `variant-${this.variant}`, {
expanded: this.isExpanded,
}) },
h("slot", null)),
h("div", { part: cx('backdrop', this.animationState, `variant-${this.variant}`, {
expanded: this.isExpanded,
}), onClick: () => (this.expanded = false) })));
}
static get is() { return "scale-telekom-nav-flyout"; }
static get encapsulation() { return "shadow"; }
static get originalStyleUrls() { return {
"$": ["telekom-nav-flyout.css"]
}; }
static get styleUrls() { return {
"$": ["telekom-nav-flyout.css"]
}; }
static get properties() { return {
"expanded": {
"type": "boolean",
"mutable": true,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Open the flyout menu"
},
"attribute": "expanded",
"reflect": true,
"defaultValue": "false"
},
"triggerSelector": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "(optional) Selector to query the trigger element in case it's not the previous sibling"
},
"attribute": "trigger-selector",
"reflect": false
},
"variant": {
"type": "string",
"mutable": false,
"complexType": {
"original": "null | 'mobile'",
"resolved": "\"mobile\"",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "(optional) Variant (\"mobile\" gives it a fixed height of `100vh`)"
},
"attribute": "variant",
"reflect": false,
"defaultValue": "null"
},
"hover": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "(optinal) Whether the flyout should open on hover (needs better name!)"
},
"attribute": "hover",
"reflect": false,
"defaultValue": "false"
}
}; }
static get states() { return {
"isExpanded": {},
"animationState": {}
}; }
static get events() { return [{
"method": "scaleExpanded",
"name": "scale-expanded",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": ""
},
"complexType": {
"original": "any",
"resolved": "any",
"references": {}
}
}]; }
static get methods() { return {
"show": {
"complexType": {
"signature": "() => Promise<void>",
"parameters": [],
"references": {
"Promise": {
"location": "global"
}
},
"return": "Promise<void>"
},
"docs": {
"text": "",
"tags": []
}
},
"hide": {
"complexType": {
"signature": "() => Promise<void>",
"parameters": [],
"references": {
"Promise": {
"location": "global"
}
},
"return": "Promise<void>"
},
"docs": {
"text": "",
"tags": []
}
}
}; }
static get elementRef() { return "hostElement"; }
static get watchers() { return [{
"propName": "expanded",
"methodName": "expandedChanged"
}]; }
static get listeners() { return [{
"name": "keydown",
"method": "handleWindowKeydown",
"target": "window",
"capture": false,
"passive": false
}, {
"name": "scale-close-nav-flyout",
"method": "handleScaleCloseNavFlyout",
"target": undefined,
"capture": false,
"passive": false
}, {
"name": "click",
"method": "handleDocumentClick",
"target": "document",
"capture": false,
"passive": false
}]; }
}