Skip to content

Commit 7bd90fd

Browse files
committed
feat: implement scopes
1 parent 7432d88 commit 7bd90fd

File tree

8 files changed

+191
-64
lines changed

8 files changed

+191
-64
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
},
1818
"packageManager": "[email protected]",
1919
"devDependencies": {
20-
"@outloud/eslint-config-vue": "^1.0.3",
20+
"@outloud/eslint-config-vue": "^1.1.5",
2121
"@release-it/conventional-changelog": "^8.0.1",
2222
"@types/node": "^20.12.12",
2323
"concurrently": "^8.2.2",

packages/vue-modals/src/Modal.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ref } from 'vue'
44
export type ModalListener = (...args: any[]) => void
55

66
export type ModalId = string | number
7+
export type ModalScope = ModalId
78
export type ModalStatus = 'pending' | 'open' | 'closed'
89
export type ModalProps = Record<string, any>
910
export type ModalListeners = Record<string, ModalListener>
@@ -16,12 +17,14 @@ export interface ModalOptions {
1617

1718
export interface ModalData<Props = ModalProps> extends ModalOptions {
1819
id: ModalId
20+
scope?: ModalScope
1921
props: Props
2022
listeners: ModalListeners
2123
}
2224

2325
export class Modal<T = any> {
2426
readonly id: ModalId
27+
readonly scope?: ModalScope
2528
readonly props: ModalProps
2629
readonly listeners: ModalListeners
2730
readonly options: ModalOptions
@@ -33,8 +36,9 @@ export class Modal<T = any> {
3336
reject?: (err: Error) => void
3437
resolve?: (value: T) => void
3538

36-
constructor({ id, props, listeners, ...options }: ModalData) {
39+
constructor({ id, scope, props, listeners, ...options }: ModalData) {
3740
this.id = id
41+
this.scope = scope
3842
this.props = props
3943
this.listeners = listeners
4044
this.options = options

packages/vue-modals/src/ModalManager.ts

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,41 @@
1-
import type { Component, Raw, Ref } from 'vue'
2-
import { markRaw, reactive } from 'vue'
1+
import type { Component, Ref } from 'vue'
2+
import { markRaw } from 'vue'
33
import { Modal } from './Modal'
4-
import type { ModalData, ModalId, ModalProps } from './Modal'
4+
import type { ModalData, ModalId, ModalProps, ModalScope } from './Modal'
55
import type { ModalsConfig } from './config'
66
import type { ComponentOrImport, ComponentProps, LazyComponent, ModalConfirmProps } from './types'
7+
import { State } from './State'
78

89
export type ModalOpenOptions<Props = ModalProps> = Partial<ModalData<Props>> & {
910
fetchData?: () => Promise<ModalProps | undefined>
1011
}
1112

12-
interface ComponentData {
13-
loader: LazyComponent
14-
component?: Component
15-
}
16-
1713
export class ModalManager {
18-
list = reactive<Raw<Modal>[]>([])
19-
content?: Ref<HTMLElement | undefined>
14+
private readonly state: State
15+
private scopeId?: ModalScope
16+
17+
constructor(options?: Partial<ModalsConfig>, state?: State) {
18+
this.state = state || new State(options)
19+
}
20+
21+
get list() {
22+
return this.state.list
23+
}
2024

21-
private modalId = 1
22-
private components = new Map<string, ComponentData>()
25+
get options() {
26+
return this.state.options
27+
}
28+
29+
get content() {
30+
return this.state.content
31+
}
2332

24-
constructor(public options: ModalsConfig) {}
33+
set content(value: Ref<HTMLElement | undefined> | undefined) {
34+
this.state.content = value
35+
}
2536

2637
getComponent(name: string): ComponentOrImport {
27-
const component = this.components.get(name)
38+
const component = this.state.components.get(name)
2839

2940
if (!component) {
3041
throw new Error(`Component "${name}" not found`)
@@ -49,7 +60,7 @@ export class ModalManager {
4960
}
5061

5162
registerComponent(name: string, loader: LazyComponent, preload = false) {
52-
this.components.set(name, { loader })
63+
this.state.components.set(name, { loader })
5364

5465
if (preload) {
5566
const result = this.getComponent(name)
@@ -112,10 +123,11 @@ export class ModalManager {
112123
component: ComponentOrImport<C>,
113124
options: ModalOpenOptions<ComponentProps<C>> = {},
114125
) {
115-
const mergedOptions = Object.assign({
116-
id: this.modalId++,
126+
const mergedOptions: ModalData = Object.assign({
127+
id: this.state.modalId++,
117128
props: {},
118129
listeners: {},
130+
scope: this.scopeId,
119131
}, this.options, options)
120132

121133
const existingModal: Modal<T> | undefined = this.get(mergedOptions.id)
@@ -162,6 +174,24 @@ export class ModalManager {
162174
return true
163175
}
164176

177+
closeScope() {
178+
if (!this.scopeId) {
179+
return
180+
}
181+
182+
for (const modal of this.list) {
183+
if (modal.scope === this.scopeId) {
184+
this.close(modal)
185+
}
186+
}
187+
}
188+
189+
closeAll() {
190+
for (const modal of this.list) {
191+
this.close(modal)
192+
}
193+
}
194+
165195
confirm<T = boolean>(props: ModalConfirmProps) {
166196
return this.open<T>(this.getComponent('confirm'), {
167197
props,
@@ -172,4 +202,11 @@ export class ModalManager {
172202
const prototype: any = ModalManager.prototype
173203
prototype[name] = def
174204
}
205+
206+
scope(scopeId: ModalScope) {
207+
const scope = new ModalManager(undefined, this.state)
208+
scope.scopeId = scopeId
209+
210+
return scope
211+
}
175212
}

packages/vue-modals/src/State.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { type Component, type Raw, reactive, type Ref } from 'vue'
2+
import type { Modal, ModalOptions } from './Modal'
3+
import type { LazyComponent } from './types'
4+
import { defaults, type ModalsConfig } from './config'
5+
6+
interface ComponentData {
7+
loader: LazyComponent
8+
component?: Component
9+
}
10+
11+
export class State {
12+
list = reactive<Raw<Modal>[]>([])
13+
content?: Ref<HTMLElement | undefined>
14+
components = new Map<string, ComponentData>()
15+
modalId = 1
16+
options: ModalOptions
17+
18+
constructor(options?: Partial<ModalsConfig>) {
19+
this.options = Object.assign({}, defaults, options)
20+
}
21+
}
Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
1-
import { inject } from 'vue'
1+
import { getCurrentInstance, inject, onBeforeUnmount } from 'vue'
22
import type { ModalManager } from '../ModalManager'
33
import { modalsSymbol } from '../symbols'
44

55
export function useModals(): ModalManager {
6-
return inject(modalsSymbol)!
6+
const uid = getCurrentInstance()?.uid
7+
const modals = inject(modalsSymbol)!
8+
const scope = uid !== undefined ? modals.scope(uid) : modals
9+
10+
onBeforeUnmount(() => {
11+
uid !== undefined && scope.closeScope()
12+
})
13+
14+
return scope
715
}

packages/vue-modals/src/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export interface ModalsConfig {
1111
escToClose: boolean
1212
}
1313

14-
export const config: ModalsConfig = {
14+
export const defaults: ModalsConfig = {
1515
clickToClose: true,
1616
escToClose: true,
1717
}

packages/vue-modals/src/plugin.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ import type { App } from 'vue'
22
import { markRaw, warn } from 'vue'
33
import { ModalManager } from './ModalManager'
44
import type { ModalsConfig } from './config'
5-
import { config } from './config'
65
import { modalsSymbol } from './symbols'
76

87
export function createModals(options: Partial<ModalsConfig> = {}) {
9-
const api = markRaw(new ModalManager(Object.assign({}, config, options)))
8+
const api = markRaw(new ModalManager(options))
109

1110
const install = (app: App) => {
1211
if (app.config.globalProperties.$modals) {

0 commit comments

Comments
 (0)