From c2cb6db7630e3ce895526eec7069def53d37601b Mon Sep 17 00:00:00 2001 From: Gabriel Raniere Date: Tue, 9 Mar 2021 18:16:59 -0300 Subject: [PATCH 1/2] create prop to toggle showing drawer below and lint fixes --- .eslintrc.js | 37 ++++---- src/Demo/DemoModal.vue | 34 ++++--- src/Demo/Main.vue | 21 ++--- src/Layouts/Dialog.vue | 27 +++--- src/Layouts/Drawer.vue | 37 ++++---- src/ModalStack.vue | 208 ++++++++++++++++++++--------------------- src/Modals.ts | 50 +++++----- src/Types.ts | 56 +++++------ src/main.ts | 10 +- 9 files changed, 245 insertions(+), 235 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index b2c2a7f..a752915 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,20 +1,19 @@ module.exports = { - env: { - browser: true, - es2020: true - }, - extends: ['plugin:vue/essential', 'standard', 'plugin:prettier/recommended'], - parserOptions: { - ecmaVersion: 11, - parser: '@typescript-eslint/parser', - sourceType: 'module' - }, - plugins: ['vue', '@typescript-eslint', 'prettier'], - rules: { - 'prettier/prettier': 'error', - 'no-unused-vars': 'off', - '@typescript-eslint/no-unused-vars': ['error'] - }, - ignorePatterns: ['node_modules', 'dist'] - }; - \ No newline at end of file + env: { + browser: true, + es2020: true + }, + extends: ['plugin:vue/essential', 'standard', 'plugin:prettier/recommended'], + parserOptions: { + ecmaVersion: 11, + parser: '@typescript-eslint/parser', + sourceType: 'module' + }, + plugins: ['vue', '@typescript-eslint', 'prettier'], + rules: { + 'prettier/prettier': 'error', + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': ['error'] + }, + ignorePatterns: ['node_modules', 'dist'] +}; diff --git a/src/Demo/DemoModal.vue b/src/Demo/DemoModal.vue index 9cedb82..0cc9e76 100644 --- a/src/Demo/DemoModal.vue +++ b/src/Demo/DemoModal.vue @@ -1,48 +1,52 @@ \ No newline at end of file +}; + diff --git a/src/Demo/Main.vue b/src/Demo/Main.vue index a35a404..583b59f 100644 --- a/src/Demo/Main.vue +++ b/src/Demo/Main.vue @@ -12,34 +12,33 @@ \ No newline at end of file + diff --git a/src/Layouts/Dialog.vue b/src/Layouts/Dialog.vue index 006686b..74a47f2 100644 --- a/src/Layouts/Dialog.vue +++ b/src/Layouts/Dialog.vue @@ -1,43 +1,41 @@ \ No newline at end of file + diff --git a/src/Layouts/Drawer.vue b/src/Layouts/Drawer.vue index cf1dc13..4d70e56 100644 --- a/src/Layouts/Drawer.vue +++ b/src/Layouts/Drawer.vue @@ -1,36 +1,40 @@ \ No newline at end of file + diff --git a/src/ModalStack.vue b/src/ModalStack.vue index 1f9e135..713cf8e 100644 --- a/src/ModalStack.vue +++ b/src/ModalStack.vue @@ -1,37 +1,37 @@ @@ -39,47 +39,46 @@ import { VueConstructor } from 'vue'; import { Component, Prop, Vue, Watch } from 'vue-property-decorator'; import Modals from './Modals'; -import { Modal, ModalOptions } from './Types' +import { Modal, ModalOptions } from './Types'; -var globalId = 1 -const SymbolReject = Symbol('Reject') -const SymbolResolve = Symbol('Resolve') -const SymbolFulfilled = Symbol('Fulfilled') +var globalId = 1; +const SymbolReject = Symbol('Reject'); +const SymbolResolve = Symbol('Resolve'); +const SymbolFulfilled = Symbol('Fulfilled'); @Component export default class ModalStack extends Vue { - @Prop({ type: String, - default: 'default', + default: 'default' }) readonly name: string; @Prop({ type: String, - default: 'drawer', + default: 'drawer' }) readonly layout: string; /** * List of modals in sequence. Last one is top most modal */ - stack: Modal[] = [] + stack: Modal[] = []; created() { // Register instance - Modals.registerStack(this.name, this) + Modals.registerStack(this.name, this); } beforeDestroy() { // Unregister instance - Modals.unregisterStack(this.name, this) + Modals.unregisterStack(this.name, this); } @Watch('name') onNameChanged(newName, oldName) { - oldName && Modals.unregisterStack(oldName, this) - newName && Modals.registerStack(newName, this) + oldName && Modals.unregisterStack(oldName, this); + newName && Modals.registerStack(newName, this); } /** @@ -87,26 +86,26 @@ export default class ModalStack extends Vue { * calling any hooks or notifying the modal */ removeFromStack(modal: Modal) { - let modalIndex = this.stack.findIndex(el => el == modal) + const modalIndex = this.stack.findIndex(el => el === modal); if (modalIndex < 0) return; - this.stack.splice(modalIndex, 1) + this.stack.splice(modalIndex, 1); } /** * Simply adds the modal to the stack */ addToStack(modal: Modal) { - this.stack.push(modal) + this.stack.push(modal); } /** * Adds a new Modal to the stack */ push(options: ModalOptions): Modal { - let modal = this.buildModal(options) - this.addToStack(modal) + const modal = this.buildModal(options); + this.addToStack(modal); - return modal + return modal; } /** @@ -115,94 +114,93 @@ export default class ModalStack extends Vue { */ close(modal: Modal, error?: Error): boolean { if (!this.canClose(modal)) { - return false + return false; } - this.destroy(modal, error) - return true + this.destroy(modal, error); + return true; } /** * Returns the correct component for the modal */ layoutForModal(modal: Modal): string | Vue | VueConstructor { - let componentName = modal.layout ?? modal.options.layout ?? this.layout ?? 'section' + const componentName = modal.layout ?? modal.options.layout ?? this.layout ?? 'section'; if (typeof componentName === 'string') { - let component = Vue.component(componentName) - if (component) return component + const component = Vue.component(componentName); + if (component) return component; } - return componentName + return componentName; } canClose(modal: Modal): boolean { - let event = new Event('beforeClose', {cancelable: true}) - + const event = new Event('beforeClose', { cancelable: true }); + // Delegate to modal if (modal.options.beforeClose) { - modal.options.beforeClose(event) + modal.options.beforeClose(event); if (event.defaultPrevented) { - return false + return false; } } // Delegate to view reference - const vm = this.$refs[modal.id]?.[0] - if (vm && (typeof vm['beforeClose']) === 'function') { - vm['beforeClose'](event) + const vm = this.$refs[modal.id]?.[0]; + if (vm && typeof vm.beforeClose === 'function') { + vm.beforeClose(event); if (event.defaultPrevented) { - return false + return false; } } // Check recursively if there are other modals that have this as parent and can't be closed - if (!this.stack.filter(check => check.options.parent == vm).every(modal => this.canClose(modal))) { - return false + if (!this.stack.filter(check => check.options.parent === vm).every(modal => this.canClose(modal))) { + return false; } - return true + return true; } /** - * Forcefully destroys a modal and closes it. + * Forcefully destroys a modal and closes it. * No option is given to keep it open with this call. */ destroy(modal: Modal, error?: Error) { // Reject the modal promise if (!modal[SymbolFulfilled]) { - modal[SymbolFulfilled] = true - modal[SymbolReject](error ?? new Error('Modal closed')) + modal[SymbolFulfilled] = true; + modal[SymbolReject](error ?? new Error('Modal closed')); } - this.removeFromStack(modal) + this.removeFromStack(modal); } answer(modal: Modal, answer?: any) { // Resolve the modal promise if (!modal[SymbolFulfilled]) { - modal[SymbolFulfilled] = true - modal[SymbolResolve](answer) + modal[SymbolFulfilled] = true; + modal[SymbolResolve](answer); } - this.removeFromStack(modal) + this.removeFromStack(modal); } pop(): Modal { if (this.stack.length) { - let modal = this.stack[this.stack.length - 1] - if (this.close(modal)) - return modal + const modal = this.stack[this.stack.length - 1]; + if (this.close(modal)) return modal; } - return null + return null; } popUntil(modal: Modal) { // Do not close modals if the modal is not present - if (this.stack.findIndex(check => modal == check) < 0) return + if (this.stack.findIndex(check => modal === check) < 0) return; while (this.stack.length > 0) { - let check = this.pop() + const check = this.pop(); if (check === modal || check === null) { - return + return; } } } @@ -212,51 +210,50 @@ export default class ModalStack extends Vue { */ clear() { while (this.stack.length) { - this.destroy(this.stack[this.stack.length - 1]) + this.destroy(this.stack[this.stack.length - 1]); } } buildModal(options: ModalOptions): Modal { - let resolve, reject - const modal = new Promise((_resolve, _reject) => { - resolve = _resolve - reject = (val) => 'default' in options ? _resolve(options.default) : _reject(val) - }) as Modal + let _resolve, _reject; + const modal = new Promise((resolve, reject) => { + _resolve = resolve; + _reject = val => ('default' in options ? resolve(options.default) : reject(val)); + }) as Modal; // Try to find out layout from component if (!options.layout && options.component?.layout) { - options.layout = options.component.layout + options.layout = options.component.layout; } // modal.layoutComponent = Modals.layout(options.layout) // Listen to parent element destroy hook in order to destroy the modal too if (options.parent) { - options.parent.$once('hook:beforeDestroy', () => this.destroy(modal, new Error('Component was destroyed'))) + options.parent.$once('hook:beforeDestroy', () => this.destroy(modal, new Error('Component was destroyed'))); } // Add bindings for future integrations - modal.id = globalId++ - modal.stack = this - modal.options = options - modal.layout = this.layoutForModal(modal) + modal.id = globalId++; + modal.stack = this; + modal.options = options; + modal.layout = this.layoutForModal(modal); // Internal properties to track promise - modal[SymbolReject] = reject - modal[SymbolResolve] = resolve - modal[SymbolFulfilled] = false + modal[SymbolReject] = _reject; + modal[SymbolResolve] = _resolve; + modal[SymbolFulfilled] = false; // Expose methods for closing and destroying modal - modal.close = (error) => this.close(modal, error) - modal.destroy = (error) => this.destroy(modal, error) - - return modal + modal.close = error => this.close(modal, error); + modal.destroy = error => this.destroy(modal, error); + + return modal; } } \ No newline at end of file + diff --git a/src/Modals.ts b/src/Modals.ts index bc2e1b1..c661576 100644 --- a/src/Modals.ts +++ b/src/Modals.ts @@ -1,66 +1,72 @@ -import ModalStack from './ModalStack.vue' -import Drawer from './Layouts/Drawer.vue' +import ModalStack from './ModalStack.vue'; import { Vue } from 'vue-property-decorator'; import { ModalOptions } from './Types'; export default class Modals { - static globalStackInstances = new Map() + static globalStackInstances = new Map(); /** * Creates a stack instance with the given name */ static createStackInstance(name): ModalStack { - console.warn(`[VueModals] No ModalStack was found for name "${name}". Using Modals without a stack instance causes one to be created as default at the end of the document body.`) - const rootElement = document.createElement('aside') - document.body.insertAdjacentElement('beforeend', rootElement) + console.warn( + `[VueModals] No ModalStack was found for name "${name}". Using Modals without a stack instance causes one to be created as default at the end of the document body.` + ); + const rootElement = document.createElement('aside'); + document.body.insertAdjacentElement('beforeend', rootElement); new Vue({ render: h => h(ModalStack), props: { name } - }).$mount(rootElement) as ModalStack - return this.globalStackInstances.get(name) + }).$mount(rootElement) as ModalStack; + return this.globalStackInstances.get(name); } /** * Returns the stack modal instance by name. - * If default instance is requested and is not registered, + * If default instance is requested and is not registered, * it creates one and returns. */ static stack(name: string = 'default'): ModalStack { // Create instance if no default one exists if (name === 'default' && !this.globalStackInstances.has('default')) { - return this.createStackInstance('default') + return this.createStackInstance('default'); } if (this.globalStackInstances.has(name)) { - return this.globalStackInstances.get(name) + return this.globalStackInstances.get(name); } - throw new Error(`[VueModals] No stack instance found for name "${name ?? 'default'}". Create a ModalStack instance with this name to use it`) + throw new Error( + `[VueModals] No stack instance found for name "${name ?? + 'default'}". Create a ModalStack instance with this name to use it` + ); } static open(options: ModalOptions) { - (this.stack(options.stack) as any).push(options) + (this.stack(options.stack) as any).push(options); } static registerLayout(name: string, layout: any) { - Vue.component(name, layout) + Vue.component(name, layout); } static registerStack(name: string, stack: ModalStack) { - console.warn('register', name, stack) + console.warn('register', name, stack); if (this.globalStackInstances.has(name)) { - console.warn(`[VueModals] Multiple instances of ModalStack must have unique names. Duplicate name found for ModalStack "${name}".`) + console.warn( + `[VueModals] Multiple instances of ModalStack must have unique names. Duplicate name found for ModalStack "${name}".` + ); } else { - this.globalStackInstances.set(name, stack) + this.globalStackInstances.set(name, stack); } } static unregisterStack(name: string, stack: ModalStack) { - console.warn('unregister', name, stack) - if (this.globalStackInstances.get(name) == stack) { - this.globalStackInstances.delete(this.name) + console.warn('unregister', name, stack); + if (this.globalStackInstances.get(name) === stack) { + this.globalStackInstances.delete(this.name); } else { - console.warn(`[VueModals] Could not unregister ModalStack because a duplicate name was found "${name}".`) + console.warn(`[VueModals] Could not unregister ModalStack because a duplicate name was found "${name}".`); } } } -window['Modals'] = Modals \ No newline at end of file +window.Modals = Modals; diff --git a/src/Types.ts b/src/Types.ts index 511b001..1bac284 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -1,69 +1,69 @@ -import { VueConstructor } from 'vue/types/umd' -import ModalStack from './ModalStack.vue' +import { VueConstructor } from 'vue/types/umd'; +import ModalStack from './ModalStack.vue'; export interface ModalOptions { /** * Name of the ModalStack to assign this Modal to. - * + * * Uses the default stack or creates a new one by default */ - stack?: string, + stack?: string; /** * Parent vue instance to watch for when being destroyed. - * + * * If not set, will keep the dialog open even if the * component who created it was destroyed. */ - parent?: Vue, + parent?: Vue; /** * The layout to use as container for the component */ - layout?: 'drawer' | 'window' | string, + layout?: 'drawer' | 'window' | string; /** * Name or Class of the component to use inside the Modal */ - component: any, + component: any; /** * Additional props to pass to the component. * Use this to assign custom parameters and inform the component * of important state when it's built. */ - props?: {[key: string]: any}, + props?: { [key: string]: any }; /** - * Default value for the result. - * + * Default value for the result. + * * If not assigned and the modal is closed, will throw an error. */ - default?: any, + default?: any; - width?: string, - minWidth?: string, - maxWidth?: string, + width?: string; + minWidth?: string; + maxWidth?: string; - height?: string, - minHeight?: string, - maxHeight?: string, + height?: string; + minHeight?: string; + maxHeight?: string; - hideOverlay?: boolean, - clickToClose?: boolean, + hideOverlay?: boolean; + clickToClose?: boolean; - beforeClose?: (Event) => void + beforeClose?: (Event) => void; } export interface ModalResult { - id: number - stack: ModalStack - options: ModalOptions + id: number; + stack: ModalStack; + options: ModalOptions; /** * Component that will render the layout */ - layout: Vue | VueConstructor | string - close(error?: any) - destroy(error?: any) + layout: Vue | VueConstructor | string; + close(error?: any); + destroy(error?: any); } -export type Modal = Promise & ModalResult \ No newline at end of file +export type Modal = Promise & ModalResult; diff --git a/src/main.ts b/src/main.ts index 5b0e207..44919e9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,10 +1,10 @@ -import Vue from "vue"; -import Demo from '@/Demo/Main.vue' -import '@/Layouts/Drawer.vue' -import '@/Layouts/Dialog.vue' +import Vue from 'vue'; +import Demo from '@/Demo/Main.vue'; +import '@/Layouts/Drawer.vue'; +import '@/Layouts/Dialog.vue'; // Vue.component('Drawer', Drawer) new Vue({ render: h => h(Demo) -}).$mount('#app'); \ No newline at end of file +}).$mount('#app'); From f84fd4fe3b295c8a06774d642142c9d3dff3afbb Mon Sep 17 00:00:00 2001 From: Gabriel Raniere Date: Tue, 9 Mar 2021 18:36:46 -0300 Subject: [PATCH 2/2] remove hideLastDrawer from demo --- src/Demo/Main.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Demo/Main.vue b/src/Demo/Main.vue index 583b59f..3858d9d 100644 --- a/src/Demo/Main.vue +++ b/src/Demo/Main.vue @@ -29,7 +29,7 @@ export default class App extends Vue { layout, component: DemoModal, default: false, - props: { rand: Math.random(), hideLastDrawer: true } + props: { rand: Math.random() } }); }