refactor(modal-provider): make it work

This commit is contained in:
07akioni 2024-01-29 23:36:51 +08:00
parent e7f5534394
commit 2bf5229543
17 changed files with 135 additions and 75 deletions

View File

@ -20,11 +20,11 @@ const appVue = `<template>
<n-loading-bar-provider>
<n-message-provider>
<n-notification-provider>
<n-modal-provider>
<n-dialog-provider>
<demo />
</n-dialog-provider>
</n-modal-provider>
<n-modal-provider>
<n-dialog-provider>
<demo />
</n-dialog-provider>
</n-modal-provider>
</n-notification-provider>
</n-message-provider>
</n-loading-bar-provider>

View File

@ -58,7 +58,7 @@ export interface DialogApiInjection {
export interface DialogProviderInjection {
clickedRef: Ref<boolean>
clickPositionRef: Ref<{ x: number, y: number } | null>
clickedPositionRef: Ref<{ x: number, y: number } | null>
}
export type DialogReactiveListInjection = Ref<DialogReactive[]>
@ -129,7 +129,7 @@ export const NDialogProvider = defineComponent({
provide(dialogApiInjectionKey, api)
provide(dialogProviderInjectionKey, {
clickedRef: useClicked(64),
clickPositionRef: useClickPosition()
clickedPositionRef: useClickPosition()
})
provide(dialogReactiveListInjectionKey, dialogListRef)
return {

View File

@ -6,7 +6,7 @@ Basic usage of modal. You can put anything in modal, a card for example.
<template>
<n-button @click="showModal = true">
Start Me up
Start me up
</n-button>
<n-modal v-model:show="showModal">
<n-card

View File

@ -6,7 +6,7 @@ Modal can be controlled.
<template>
<n-button @click="handleClick">
Start Me up
Start me up
</n-button>
<n-modal :show="showModal">
<n-card

View File

@ -6,7 +6,7 @@ Use fixed position to set the position of the modal.
<template>
<n-button @click="showModal = true">
Start Me up
Start me up
</n-button>
<n-modal v-model:show="showModal">
<n-card

View File

@ -2,10 +2,24 @@
It just pops and shows you something.
<n-alert title="Prerequisite" type="warning" :bordered="false">
If you want to create modal using <n-text code>useModal</n-text>, you need to wrap the component where you call related methods inside <n-text code>n-modal-provider</n-text> and use <n-text code>useModal</n-text> to get the API.
</n-alert>
For example:
```html
<!-- App.vue -->
<n-modal-provider>
<content />
</n-modal-provider>
```
## Demos
```demo
basic.vue
reactive.vue
controlled.vue
mask-closable.vue
custom-position.vue
@ -13,11 +27,29 @@ preset-card.vue
preset-confirm.vue
preset-confirm-slot.vue
transform-origin.vue
reactive.vue
```
## API
### ModalProvider Props
Provided since NEXT_VERSION.
| Name | Type | Default | Description | Version |
| --- | --- | --- | --- | --- |
| to | `string \| HTMLElement` | `body` | Container node of the modal content. | NEXT_VERSION |
### useModal API
Provided since NEXT_VERSION.
| Name | Type | Description | Version |
| --- | --- | --- | --- |
| create | `(options: ModalOptions) => ModalReactive` | Create a modal. | NEXT_VERSION |
| destroyAll | `() => void` | Destroy all modals. | NEXT_VERSION |
`ModalOptions` and `ModalReactive`'s properties are the same as `ModalProps` (properties should use camelCase, for example `auto-focus` property should use `autoFocus` as option property).
### Modal Props
| Name | Type | Default | Description | Version |
@ -39,6 +71,14 @@ reactive.vue
| on-mask-click | `() => void` | `undefined` | Callback on mask is clicked. | |
| on-update:show | `(value: boolean) => void` | `undefined` | Callback when modal's display status is changed. | |
### ModalProvider Props
Provided since NEXT_VERSION.
| Name | Type | Default | Description | Version |
| --- | --- | --- | --- | --- |
| to | `string \| HTMLElement` | `body` | Container node of the modal content. | NEXT_VERSION |
### Modal with Preset Card Props
See [Card props](card#Card-Props)

View File

@ -6,7 +6,7 @@ Use `mask-closable=false` to make modal not emit the event which may close the m
<template>
<n-button @click="showModal = true">
Start Me up
Start me up
</n-button>
<n-modal
v-model:show="showModal"

View File

@ -6,7 +6,7 @@ Modal has some presets, which means you can use props & slots of the preset afte
<template>
<n-button @click="showModal = true">
Start Me up
Start me up
</n-button>
<n-modal
v-model:show="showModal"

View File

@ -6,7 +6,7 @@ Slots are also related to preset.
<template>
<n-button @click="showModal = true">
Start Me up
Start me up
</n-button>
<n-modal v-model:show="showModal" preset="dialog" title="Dialog">
<template #header>

View File

@ -6,7 +6,7 @@ An example of preset `dialog`.
<template>
<n-button @click="showModal = true">
Start Me up
Start me up
</n-button>
<n-modal
v-model:show="showModal"

View File

@ -1,11 +1,14 @@
<markdown>
# Use Modal
# Imperative API
Provided since NEXT_VERSION.
You can use `useModal.create` to create a modal. (Please make sure this API is called inside `n-modal-provider`.)
</markdown>
<template>
<n-button @click="handleClick">
Come
Start me up
</n-button>
</template>

View File

@ -2,10 +2,24 @@
它会弹出来,然后给你看点东西。
<n-alert title="使用前提" type="warning" :bordered="false">
如果你想通过 <n-text code>useModal</n-text> 使用对话框,你需要把调用其方法的组件放在 <n-text code>n-modal-provider</n-text> 内部并且使用 <n-text code>useModal</n-text> 去获取 API。
</n-alert>
例如:
```html
<!-- App.vue -->
<n-modal-provider>
<content />
</n-modal-provider>
```
## 演示
```demo
basic.vue
reactive.vue
controlled.vue
mask-closable.vue
custom-position.vue
@ -13,7 +27,6 @@ preset-card.vue
preset-confirm.vue
preset-confirm-slot.vue
transform-origin.vue
reactive.vue
nested-debug.vue
a11y-debug.vue
raw-debug.vue
@ -33,6 +46,25 @@ mask-click-debug.vue
## API
### ModalProvider Props
自 NEXT_VERSION 开始提供。
| 名称 | 类型 | 默认值 | 说明 | 版本 |
| ---- | ----------------------- | ------ | -------------- | ------------ |
| to | `string \| HTMLElement` | `body` | 模态的挂载位置 | NEXT_VERSION |
### useModal API
自 NEXT_VERSION 开始提供。
| 名称 | 类型 | 说明 | 版本 |
| --- | --- | --- | --- |
| create | `(options: ModalOptions) => ModalReactive` | 创建模态框 | NEXT_VERSION |
| destroyAll | `() => void` | 销毁所有弹出的模态框 | NEXT_VERSION |
`ModalOptions` 的属性和 `ModalReactive` 属性同 `ModalProps`(属性应使用 camelCase例如 `auto-focus` 对应 `autoFocus`)。
### Modal Props
| 名称 | 类型 | 默认值 | 说明 | 版本 |

View File

@ -1,6 +1,9 @@
<markdown>
# Use Modal
# 命令式 API
NEXT_VERSION 开始提供
你可以使用 `useModal.create` 来打开一个模态框请确保使用此 API 的组件被 `n-modal-provider` 包含
</markdown>
<template>

View File

@ -30,7 +30,7 @@ import { modalLight } from '../styles'
import type { ModalTheme } from '../styles'
import { presetProps, presetPropsKeys } from './presetProps'
import NModalBodyWrapper from './BodyWrapper'
import { modalInjectionKey } from './interface'
import { modalInjectionKey, modalProviderInjectionKey } from './interface'
import style from './styles/index.cssr'
export const modalProps = {
@ -86,6 +86,7 @@ export const modalProps = {
onMaskClick: Function as PropType<(e: MouseEvent) => void>,
// private
internalDialog: Boolean,
internalModal: Boolean,
internalAppear: {
type: Boolean as PropType<boolean | undefined>,
default: undefined
@ -144,6 +145,9 @@ export default defineComponent({
const NDialogProvider = props.internalDialog
? inject(dialogProviderInjectionKey, null)
: null
const NModalProvider = props.internalModal
? inject(modalProviderInjectionKey, null)
: null
const isComposingRef = useIsComposing()
@ -220,10 +224,11 @@ export default defineComponent({
}
provide(modalInjectionKey, {
getMousePosition: () => {
if (NDialogProvider) {
const { clickedRef, clickPositionRef } = NDialogProvider
if (clickedRef.value && clickPositionRef.value) {
return clickPositionRef.value
const mergedProvider = NDialogProvider || NModalProvider
if (mergedProvider) {
const { clickedRef, clickedPositionRef } = mergedProvider
if (clickedRef.value && clickedPositionRef.value) {
return clickedPositionRef.value
}
}
if (clickedRef.value) {

View File

@ -1,44 +1,16 @@
// use absolute path to make sure no circular ref of style
// this -> modal-index -> modal-style
import { h, defineComponent, type PropType, ref, type CSSProperties } from 'vue'
import { h, defineComponent, type PropType, ref } from 'vue'
import NModal, { modalProps } from './Modal'
export const exposedModalEnvProps = {
...modalProps,
onAfterEnter: Function as PropType<() => void>,
onAfterLeave: Function as PropType<() => void>,
transformOrigin: String as PropType<'center' | 'mouse'>,
blockScroll: { type: Boolean, default: true },
closeOnEsc: { type: Boolean, default: true },
onEsc: Function as PropType<() => void>,
autoFocus: {
type: Boolean,
default: true
},
internalStyle: [String, Object] as PropType<string | CSSProperties>,
maskClosable: {
type: Boolean,
default: true
},
onPositiveClick: Function as PropType<
(e: MouseEvent) => Promise<unknown> | unknown
>,
onNegativeClick: Function as PropType<
(e: MouseEvent) => Promise<unknown> | unknown
>,
onClose: Function as PropType<() => Promise<unknown> | unknown>,
onMaskClick: Function as PropType<(e: MouseEvent) => void>
} as const
export const NModalEnvironment = defineComponent({
name: 'ModalEnvironment',
props: {
...exposedModalEnvProps,
...modalProps,
internalKey: {
type: String,
required: true
},
to: [String, Object] as PropType<string | HTMLElement>,
// private
onInternalAfterLeave: {
type: Function as PropType<(key: string) => void>,
@ -52,10 +24,10 @@ export const NModalEnvironment = defineComponent({
if (onInternalAfterLeave) onInternalAfterLeave(internalKey)
if (onAfterLeave) onAfterLeave()
}
function handlePositiveClick (e: MouseEvent): void {
function handlePositiveClick (): void {
const { onPositiveClick } = props
if (onPositiveClick) {
void Promise.resolve(onPositiveClick(e)).then((result) => {
void Promise.resolve(onPositiveClick()).then((result) => {
if (result === false) return
hide()
})
@ -63,10 +35,10 @@ export const NModalEnvironment = defineComponent({
hide()
}
}
function handleNegativeClick (e: MouseEvent): void {
function handleNegativeClick (): void {
const { onNegativeClick } = props
if (onNegativeClick) {
void Promise.resolve(onNegativeClick(e)).then((result) => {
void Promise.resolve(onNegativeClick()).then((result) => {
if (result === false) return
hide()
})
@ -122,8 +94,6 @@ export const NModalEnvironment = defineComponent({
handleAfterLeave,
handleMaskClick,
handleEsc,
to,
maskClosable,
show
} = this
return (
@ -133,16 +103,9 @@ export const NModalEnvironment = defineComponent({
onUpdateShow={handleUpdateShow}
onMaskClick={handleMaskClick}
onEsc={handleEsc}
to={to}
maskClosable={maskClosable}
onAfterEnter={this.onAfterEnter}
onAfterLeave={handleAfterLeave}
closeOnEsc={this.closeOnEsc}
blockScroll={this.blockScroll}
autoFocus={this.autoFocus}
transformOrigin={this.transformOrigin}
internalAppear
internalDialog
internalModal
></NModal>
)
}

View File

@ -43,13 +43,13 @@ type TypeSafeModalReactive = ModalReactive & {
}
export interface ModalApiInjection {
destroy: () => void
destroyAll: () => void
create: (options: ModalOptions) => ModalReactive
}
export interface ModalProviderInjection {
clickedRef: Ref<boolean>
clickPositionRef: Ref<{ x: number, y: number } | null>
clickedPositionRef: Ref<{ x: number, y: number } | null>
}
export type ModalReactiveListInjection = Ref<ModalReactive[]>
@ -61,7 +61,6 @@ interface ModalInst {
export type ModalProviderInst = ModalApiInjection
export const modalProviderProps = {
injectionKey: String,
to: [String, Object] as PropType<string | HTMLElement>
}
@ -73,6 +72,9 @@ export const NModalProvider = defineComponent({
name: 'ModalProvider',
props: modalProviderProps,
setup () {
const clickedRef = useClicked(64)
const clickedPositionRef = useClickPosition()
const modalListRef = ref<TypeSafeModalReactive[]>([])
const modalInstRefs: Record<string, ModalInst> = {}
function create (options: ModalOptions = {}): ModalReactive {
@ -96,7 +98,7 @@ export const NModalProvider = defineComponent({
)
}
function destroy (): void {
function destroyAll (): void {
Object.values(modalInstRefs).forEach((modalInstRef) => {
modalInstRef.hide()
})
@ -104,15 +106,19 @@ export const NModalProvider = defineComponent({
const api = {
create,
destroy
destroyAll
}
provide(modalApiInjectionKey, api)
provide(modalProviderInjectionKey, {
clickedRef: useClicked(64),
clickPositionRef: useClickPosition()
clickedPositionRef: useClickPosition()
})
provide(modalReactiveListInjectionKey, modalListRef)
provide(modalProviderInjectionKey, {
clickedRef,
clickedPositionRef
})
return {
...api,
modalList: modalListRef,
@ -127,7 +133,7 @@ export const NModalProvider = defineComponent({
NModalEnvironment,
omit(modal, ['destroy', 'style'], {
internalStyle: modal.style,
to: this.to,
to: modal.to ?? this.to,
ref: ((inst: ModalInst | null) => {
if (inst === null) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete

View File

@ -10,6 +10,14 @@ HTMLElement | ComponentPublicInstance | null
export const modalBodyInjectionKey =
createInjectionKey<ModalBodyInjection>('n-modal-body')
export interface ModalProviderInjection {
clickedRef: Ref<boolean>
clickedPositionRef: Ref<{ x: number, y: number } | null>
}
export const modalProviderInjectionKey =
createInjectionKey<ModalProviderInjection>('n-modal-provider')
export interface ModalInjection {
getMousePosition: () => {
x: number