@sandlada/vue-mdc
Version:

308 lines (307 loc) • 10.6 kB
JavaScript
/**
* @license
* Copyright 2025 Sandlada & Kai Orion
* SPDX-License-Identifier: MIT
*/
import { defineComponent, createVNode, createTextVNode } from "vue";
import { isServer } from "../../utils/is-server.js";
import { redispatchEvent } from "../../internals/events/redispatch-event.js";
import "../../internals/navigation/render-navigation-destination.js";
import { componentNamePrefix } from "../../internals/component-name-prefix/component-name-prefix.js";
import { Icon } from "../icon/icon.js";
import "../icon/icon.definition.js";
import { NavigationDrawerDefaultCloseAnimation, NavigationDrawerDefaultOpenAnimation } from "./animations.js";
import { NavigationDrawerController } from "./navigation-drawer-controller.js";
import { NavigationDrawerHeadline } from "./navigation-drawer-headline.js";
import { NavigationDrawerIndicator } from "./navigation-drawer-indicator.js";
import { props } from "./navigation-drawer.definition.js";
import { ENavigationDrawerState } from "./navigation-state.js";
import css from "./styles/navigation-drawer.module.scss.js";
const NavigationDrawer = /* @__PURE__ */ defineComponent({
name: `${componentNamePrefix}-navigation-drawer`,
props,
slots: {},
emits: ["open", "opend", "cancel", "close", "closed", "submit", "update:modelValue"],
data: () => ({
animationController: null,
open: false,
opening: false,
nextClickIsFromContent: false,
escapePressedWithoutCancel: false,
isAtScrollTop: false,
isAtScrollBottom: false,
intersectionObserver: null,
controller: null
}),
computed: {
isOpen: {
get() {
return this.open;
},
set(value) {
if (this.open === value) {
return;
}
this.open = value;
}
}
},
mounted() {
if (isServer()) {
return;
}
const root = this.$el;
this.controller = new NavigationDrawerController(this.$el, this.show, this.close);
this.isOpen = this.defaultOpen;
if (this.isOpen) {
this.setRootAttribute(ENavigationDrawerState.Opend);
} else {
this.setRootAttribute(ENavigationDrawerState.Closed);
}
this.intersectionObserver = new IntersectionObserver((entries) => {
for (const entry of entries) {
this.handleAnchorIntersection(entry);
}
}, {
root: this.getScrollerElement()
});
this.intersectionObserver.observe(this.getTopAnchorElement());
this.intersectionObserver.observe(this.getBottomAnchorElement());
this.getContainerElement().addEventListener("click", this.handleContentClick);
this.getDialogElement().addEventListener("click", this.handleDialogClick);
this.getDialogElement().addEventListener("cancel", this.handleCancel);
this.getDialogElement().addEventListener("close", this.handleClose);
this.getDialogElement().addEventListener("keydown", this.handleKeydown);
this.getScrimElement().addEventListener("click", this.handleClose);
root.addEventListener("request-route", this.handleRequestRoute);
},
methods: {
getDialogElement() {
return this.$el.querySelector(`&>.${css.dialog}`);
},
getScrimElement() {
return this.$el.querySelector(`&>.${css.scrim}`);
},
getContainerElement() {
return this.$el.querySelector(`&>.${css.dialog}>.${css.container}`);
},
getScrollerElement() {
return this.$el.querySelector(`&>.${css.dialog}>.${css.scroller}`);
},
getTopAnchorElement() {
return this.$el.querySelector(`& .${css.top}.${css.anchor}`);
},
getBottomAnchorElement() {
return this.$el.querySelector(`& .${css.bottom}.${css.anchor}`);
},
setRootAttribute(state) {
const root = this.$el;
root.setAttribute("state", state);
},
handleRequestRoute(e) {
this.controller?.setActiveIndicatorByElement(e.detail.element);
},
handleClose(e) {
if (!this.escapePressedWithoutCancel) {
return;
}
this.escapePressedWithoutCancel = false;
this.getDialogElement().dispatchEvent(new Event("cancel", {
cancelable: true
}));
},
handleCancel(e) {
if (e.target !== this.getDialogElement()) {
return;
}
this.escapePressedWithoutCancel = false;
this.$emit("cancel", e);
const preventCancel = !redispatchEvent(this.$el, e);
e.preventDefault();
if (preventCancel) {
return;
}
this.close();
},
handleContentClick() {
this.nextClickIsFromContent = true;
},
handleDialogClick() {
if (this.nextClickIsFromContent) {
this.nextClickIsFromContent = false;
return;
}
const cancelEvent = new Event("cancel");
this.$emit("cancel", cancelEvent);
const preventCancel = !this.$el.dispatchEvent(cancelEvent);
if (preventCancel) {
return;
}
this.close();
},
handleKeydown(event) {
if (event.key !== "Escape") {
return;
}
this.escapePressedWithoutCancel = true;
setTimeout(() => {
this.escapePressedWithoutCancel = false;
});
},
handleAnchorIntersection(entry) {
const {
target,
isIntersecting
} = entry;
if (target === this.getTopAnchorElement()) {
this.isAtScrollTop = isIntersecting;
}
if (target === this.getBottomAnchorElement()) {
this.isAtScrollBottom = isIntersecting;
}
},
async show() {
this.opening = true;
if (this.getDialogElement().open || !this.opening) {
this.opening = false;
return;
}
const openEvent = new Event("open", {
bubbles: false,
cancelable: true,
composed: false
});
this.$emit("open", openEvent);
const preventOpen = !this.$el.dispatchEvent(openEvent);
this.setRootAttribute(ENavigationDrawerState.Opening);
if (preventOpen) {
this.isOpen = false;
this.opening = false;
return;
}
this.isOpen = true;
if (this.modal) {
this.getDialogElement().showModal();
} else {
this.getDialogElement().show();
}
await this.animateDialog(NavigationDrawerDefaultOpenAnimation);
this.opening = false;
const opendEvent = new Event("opend", {
bubbles: false,
cancelable: false,
composed: false
});
this.$emit("opend", opendEvent);
this.$el.dispatchEvent(opendEvent);
this.setRootAttribute(ENavigationDrawerState.Opend);
},
async close() {
this.opening = false;
if (!this.getDialogElement().open || this.opening) {
this.isOpen = false;
return;
}
const closeEvent = new Event("close", {
bubbles: false,
cancelable: true,
composed: false
});
this.$emit("close", closeEvent);
const preventClose = !this.$el.dispatchEvent(closeEvent);
this.setRootAttribute(ENavigationDrawerState.Closing);
if (preventClose) {
return;
}
await this.animateDialog(NavigationDrawerDefaultCloseAnimation);
this.getDialogElement().close();
this.isOpen = false;
const closedEvent = new Event("closed");
this.$emit("closed", closedEvent);
this.$el.dispatchEvent(closeEvent);
this.setRootAttribute(ENavigationDrawerState.Closed);
},
async animateDialog(animation) {
this.animationController?.abort();
this.animationController = new AbortController();
const {
container: containerAnimate,
dialog: dialogAnimate,
scrim: scrimAnimate,
headline: headlineAnimate
// content: contentAnimate,
// actions: actionsAnimate,
} = animation;
const elementAndAnimation = [
[this.getDialogElement(), dialogAnimate ?? []],
[this.getScrimElement(), scrimAnimate ?? []],
[this.getContainerElement(), containerAnimate ?? []]
// [this.getHeadlineElement(), headlineAnimate ?? []],
// [this.getContentElement(), contentAnimate ?? []],
// [this.getActionsElement(), actionsAnimate ?? []],
];
const animations = [];
for (const [element, animation2] of elementAndAnimation) {
for (const animateArgs of animation2) {
const animation3 = element.animate(...animateArgs);
this.animationController.signal.addEventListener("abort", () => {
animation3.cancel();
});
animations.push(animation3);
}
}
await Promise.all(animations.map((animation2) => animation2.finished.catch(() => {
})));
}
},
render() {
const scrollable = this.isOpen && !(this.isAtScrollTop && this.isAtScrollBottom);
const classes = [this.modal ? css.modal : css.standard, scrollable && css.scrollable, scrollable && !this.isAtScrollTop && css["show-top-divider"], scrollable && !this.isAtScrollBottom && css["show-bottom-divider"]];
return createVNode("div", {
"data-component": "navigation-drawer",
"role": "dialog",
"class": [css["navigation-drawer-component-styles"], ...classes]
}, [createVNode("span", {
"aria-hidden": "true",
"class": css.scrim
}, null), createVNode("dialog", {
"class": css.dialog,
"role": "alertdialog"
}, [createVNode("div", {
"class": css.container
}, [createVNode("div", {
"class": css.scroller
}, [createVNode("div", {
"class": css["content-wrapper"]
}, [createVNode("div", {
"class": [css.top, css.anchor]
}, null), createVNode("span", {
"class": css.content
}, [createVNode(NavigationDrawerHeadline, null, {
default: () => [createTextVNode("Headline One")]
}), createVNode("div", {
"class": css["indicator-list"]
}, [createVNode(NavigationDrawerIndicator, {
"defaultActive": true
}, {
default: () => "Label",
icon: () => createVNode(Icon, null, {
default: () => [createTextVNode("Menu")]
})
}), createVNode(NavigationDrawerIndicator, null, {
default: () => [createTextVNode("Label")]
}), createVNode(NavigationDrawerIndicator, null, {
default: () => [createTextVNode("Label")]
})]), this.$slots.default && this.$slots.default()]), createVNode("div", {
"class": [css.bottom, css.anchor]
}, null)])]), createVNode("span", {
"aria-hidden": "true",
"class": css.background
}, null)])])]);
},
inheritAttrs: true
});
export {
NavigationDrawer
};
//# sourceMappingURL=navigation-drawer.js.map