@limetech/lime-elements
Version:
182 lines (181 loc) • 5.52 kB
JavaScript
import { Corner, MDCMenuSurface } from '@material/menu-surface';
import { h, } from '@stencil/core';
import { isDescendant } from '../../util/dom';
import { ESCAPE, TAB } from '../../util/keycodes';
/**
* @slot - Content to put inside the surface
* @private
*/
export class MenuSurface {
constructor() {
this.setup = () => {
const menuElement = this.host.shadowRoot.querySelector('.mdc-menu-surface');
if (!menuElement) {
return;
}
this.menuSurface = new MDCMenuSurface(menuElement);
this.menuSurface.setAnchorCorner(Corner.TOP_START);
document.addEventListener('mousedown', this.handleDocumentClick, {
capture: true,
});
this.host.addEventListener('keydown', this.handleKeyDown);
};
this.teardown = () => {
var _a;
(_a = this.menuSurface) === null || _a === void 0 ? void 0 : _a.destroy();
document.removeEventListener('mousedown', this.handleDocumentClick, {
capture: true,
});
this.host.removeEventListener('keydown', this.handleKeyDown);
};
this.handleDocumentClick = (event) => {
const elementPath = event.composedPath ? event.composedPath() : [];
if (!this.open) {
return;
}
if (isDescendant(event.target, this.host)) {
return;
}
if (this.allowClicksElement) {
const clickedInAllowedElement = elementPath.includes(this.allowClicksElement);
if (clickedInAllowedElement) {
return;
}
}
this.dismiss.emit();
this.preventClickEventPropagation();
};
this.preventClickEventPropagation = () => {
// When the menu surface is open, we want to stop the `click` event from propagating
// when clicking outside the surface itself. This is to prevent any dialog that might
// be open from closing, etc. However, when dragging a scrollbar no `click` event is emitted,
// only mousedown and mouseup. So we listen for `mousedown` and attach a one-time listener
// for `click`, so we can capture and "kill" it.
document.addEventListener('click', this.stopEvent, {
capture: true,
once: true,
});
// We also capture and "kill" the next `mouseup` event.
document.addEventListener('mouseup', this.stopEvent, {
capture: true,
once: true,
});
// If the user dragged the scrollbar, no `click` event happens. So when we get the
// `mouseup` event, remove the handler for `click` if it's still there.
// Otherwise, we would catch the next click even though the menu is no longer open.
document.addEventListener('mouseup', () => {
document.removeEventListener('click', this.stopEvent, {
capture: true,
});
}, {
once: true,
});
};
this.stopEvent = (event) => {
event.stopPropagation();
event.preventDefault();
};
this.handleKeyDown = (event) => {
const isEscape = event.key === ESCAPE;
const isTab = event.key === TAB;
if (this.open && (isEscape || isTab)) {
event.stopPropagation();
this.dismiss.emit();
}
};
this.open = false;
this.allowClicksElement = undefined;
}
connectedCallback() {
this.setup();
}
disconnectedCallback() {
this.teardown();
}
componentDidLoad() {
this.setup();
}
render() {
const classList = {
'mdc-menu': true,
'mdc-menu-surface': true,
'mdc-menu-surface--open': this.open,
'mdc-elevation-transition': true,
'mdc-elevation--z4': true,
};
return (h("div", { class: classList, tabindex: "-1" }, h("slot", null)));
}
static get is() { return "limel-menu-surface"; }
static get encapsulation() { return "shadow"; }
static get originalStyleUrls() {
return {
"$": ["menu-surface.scss"]
};
}
static get styleUrls() {
return {
"$": ["menu-surface.css"]
};
}
static get properties() {
return {
"open": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "True if the menu surface is open, false otherwise"
},
"attribute": "open",
"reflect": false,
"defaultValue": "false"
},
"allowClicksElement": {
"type": "unknown",
"mutable": false,
"complexType": {
"original": "HTMLElement",
"resolved": "HTMLElement",
"references": {
"HTMLElement": {
"location": "global"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Clicks in this element should not be prevented when the menu surface is open"
}
}
};
}
static get events() {
return [{
"method": "dismiss",
"name": "dismiss",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Emitted when the menu surface is dismissed and should be closed"
},
"complexType": {
"original": "void",
"resolved": "void",
"references": {}
}
}];
}
static get elementRef() { return "host"; }
}
//# sourceMappingURL=menu-surface.js.map