czydojade/resources/ts/components/ui/dialog.ts
2020-10-02 20:00:24 +02:00

188 lines
4.9 KiB
TypeScript

import Vue from "vue";
import { Component, Prop, Watch } from "vue-property-decorator";
import Popper, { Placement } from "popper.js";
import { defaultBreakpoints } from "../../filters";
/**
* How popup will be presented to user:
* - "modal" - modal window
* - "popup" - simple popup
*/
export type DialogBehaviour = "modal" | "popup";
@Component({
template: require('../../../components/ui/dialog.html'),
inheritAttrs: false,
})
export default class UiDialog extends Vue {
@Prop({ type: String, default: "popup" })
private behaviour: DialogBehaviour;
@Prop({ type: String })
private mobileBehaviour: DialogBehaviour;
@Prop([String, HTMLElement])
public reference: string | HTMLElement;
@Prop(Object)
public refs: string;
@Prop({ type: String, default: "auto" })
public placement: Placement;
@Prop(Boolean)
public arrow: boolean;
@Prop({ type: Boolean, default: true })
public responsive: boolean;
@Prop(String)
public title: string;
private isMobile: boolean = false;
private _focusOutEvent;
private _resizeEvent;
private _popper;
get currentBehaviour(): DialogBehaviour {
if (!this.mobileBehaviour) {
return this.behaviour;
}
return this.isMobile ? this.mobileBehaviour : this.behaviour;
}
get hasFooter() {
return this.$hasSlot('footer')
}
get hasHeader() {
return this.$hasSlot('header')
}
private getReferenceElement() {
const isInWrapper = this.$parent.$options.name == 'portalTarget';
if (typeof this.reference === 'string') {
if (this.refs) {
return this.refs[this.reference];
}
if (isInWrapper) {
return this.$parent.$parent.$refs[this.reference];
}
return this.$parent.$refs[this.reference];
}
if (this.reference instanceof HTMLElement) {
return this.reference;
}
return isInWrapper ? this.$parent.$el : this.$el.parentElement;
}
focusOut(event: MouseEvent) {
if (this.$el.contains(event.target as Node)) {
return;
}
this.$emit('leave', event);
}
mounted() {
this.handleWindowResize();
if (this.behaviour === 'popup') {
this.initPopper();
}
window.addEventListener('resize', this._resizeEvent = this.handleWindowResize.bind(this));
}
private initPopper() {
const reference = this.getReferenceElement();
this._popper = new Popper(reference, this.$el, {
placement: this.placement,
modifiers: {
arrow: { enabled: this.arrow, element: this.$refs['arrow'] as Element },
responsive: {
enabled: this.responsive,
order: 890,
fn(data) {
if (window.innerWidth < 560) {
data.instance.options.placement = 'top';
data.styles.transform = `translate3d(0, ${ data.offsets.popper.top }px, 0)`;
data.styles.right = '0';
data.styles.left = '0';
data.styles.width = 'auto';
data.arrowStyles.left = `${ data.offsets.popper.left + data.offsets.arrow.left }px`;
}
return data;
}
}
}
});
this.$nextTick(() => {
this._popper && this._popper.update();
document.addEventListener('click', this._focusOutEvent = this.focusOut.bind(this), { capture: true });
});
}
private removePopper() {
this._popper.destroy()
this._popper = null;
}
updated() {
if (this._popper) {
this._popper.update();
}
}
beforeDestroy() {
this._focusOutEvent && document.removeEventListener('click', this._focusOutEvent, { capture: true });
}
removed() {
if (this._popper) {
this.removePopper();
}
}
private handleBackdropClick(ev: Event) {
const target = ev.target as HTMLElement;
if (target.classList.contains("ui-backdrop")) {
this.$emit('leave');
}
}
private handleCloseClick() {
this.$emit('leave');
this.$emit('close');
}
private handleWindowResize() {
this.isMobile = screen.width < defaultBreakpoints.md;
}
@Watch('currentBehaviour')
private handleBehaviourChange(newBehaviour: DialogBehaviour, oldBehaviour: DialogBehaviour) {
if (oldBehaviour === 'popup') {
this.removePopper();
}
if (newBehaviour === 'popup') {
this.$nextTick(() => this.initPopper());
}
}
}
Vue.component("ui-dialog", UiDialog);