Skip to content

Commit 04462b3

Browse files
committed
FaModalFaDrawer 分别增加命令式调用API
1 parent 5726479 commit 04462b3

File tree

7 files changed

+470
-194
lines changed

7 files changed

+470
-194
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import type { Component, HTMLAttributes } from 'vue'
2+
import { isVNode } from 'vue'
3+
import Drawer from './index.vue'
4+
5+
export interface DrawerProps {
6+
id?: string
7+
modelValue?: boolean
8+
side?: 'top' | 'bottom' | 'left' | 'right'
9+
title?: string
10+
description?: string
11+
loading?: boolean
12+
closable?: boolean
13+
centered?: boolean
14+
bordered?: boolean
15+
overlay?: boolean
16+
overlayBlur?: boolean
17+
showConfirmButton?: boolean
18+
showCancelButton?: boolean
19+
confirmButtonText?: string
20+
cancelButtonText?: string
21+
confirmButtonDisabled?: boolean
22+
confirmButtonLoading?: boolean
23+
beforeClose?: (
24+
action: 'confirm' | 'cancel' | 'close',
25+
done: () => void
26+
) => void
27+
header?: boolean
28+
footer?: boolean
29+
closeOnClickOverlay?: boolean
30+
closeOnPressEscape?: boolean
31+
destroyOnClose?: boolean
32+
contentClass?: HTMLAttributes['class']
33+
headerClass?: HTMLAttributes['class']
34+
footerClass?: HTMLAttributes['class']
35+
}
36+
37+
export interface DrawerEmits {
38+
'update:modelValue': [value: boolean]
39+
'open': []
40+
'opened': []
41+
'close': []
42+
'closed': []
43+
'confirm': []
44+
'cancel': []
45+
}
46+
47+
type BaseOptions = Omit<DrawerProps, 'modelValue'> & {
48+
content?: Component | VNode | string
49+
onOpen?: () => void
50+
onOpened?: () => void
51+
onClose?: () => void
52+
onClosed?: () => void
53+
onConfirm?: () => void
54+
onCancel?: () => void
55+
}
56+
57+
export function useFaDrawer() {
58+
function create(initialOptions: BaseOptions) {
59+
const container = document.createElement('div')
60+
const visible = ref(false)
61+
const options = reactive({ ...initialOptions })
62+
const app = createApp({
63+
render() {
64+
return h(Drawer, Object.assign({
65+
'id': useId(),
66+
'modelValue': visible.value,
67+
'onUpdate:modelValue': (val: boolean) => {
68+
visible.value = val
69+
},
70+
}, options), {
71+
default: () => {
72+
if (typeof options.content === 'string') {
73+
return options.content
74+
}
75+
else if (isVNode(options.content)) {
76+
return options.content
77+
}
78+
else if (options.content) {
79+
return h(options.content)
80+
}
81+
return null
82+
},
83+
})
84+
},
85+
})
86+
// 继承主应用的上下文
87+
const instance = getCurrentInstance()
88+
if (instance && instance.appContext) {
89+
Object.assign(app._context, instance.appContext)
90+
}
91+
app.mount(container)
92+
const open = () => {
93+
visible.value = true
94+
}
95+
const close = () => {
96+
visible.value = false
97+
}
98+
const update = (newOptions: BaseOptions) => {
99+
Object.assign(options, newOptions)
100+
}
101+
return {
102+
open,
103+
close,
104+
update,
105+
}
106+
}
107+
return {
108+
create,
109+
}
110+
}

src/ui/components/FaDrawer/index.vue

Lines changed: 66 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import type { HTMLAttributes } from 'vue'
2+
import type { DrawerEmits, DrawerProps } from '.'
33
import { cn } from '@/utils'
44
import {
55
Sheet,
@@ -15,33 +15,9 @@ defineOptions({
1515
})
1616
1717
const props = withDefaults(
18-
defineProps<{
19-
modelValue?: boolean
20-
side?: 'top' | 'bottom' | 'left' | 'right'
21-
title: string
22-
description?: string
23-
loading?: boolean
24-
closable?: boolean
25-
centered?: boolean
26-
bordered?: boolean
27-
overlay?: boolean
28-
overlayBlur?: boolean
29-
showConfirmButton?: boolean
30-
showCancelButton?: boolean
31-
confirmButtonText?: string
32-
cancelButtonText?: string
33-
confirmButtonDisabled?: boolean
34-
confirmButtonLoading?: boolean
35-
header?: boolean
36-
footer?: boolean
37-
closeOnClickOverlay?: boolean
38-
closeOnPressEscape?: boolean
39-
destroyOnClose?: boolean
40-
contentClass?: HTMLAttributes['class']
41-
headerClass?: HTMLAttributes['class']
42-
footerClass?: HTMLAttributes['class']
43-
}>(),
18+
defineProps<DrawerProps>(),
4419
{
20+
id: useId(),
4521
modelValue: false,
4622
side: 'right',
4723
loading: false,
@@ -64,18 +40,7 @@ const props = withDefaults(
6440
},
6541
)
6642
67-
const emits = defineEmits<{
68-
'update:modelValue': [value: boolean]
69-
'open': []
70-
'opened': []
71-
'close': []
72-
'closed': []
73-
'confirm': []
74-
'cancel': []
75-
}>()
76-
77-
const id = useId()
78-
provide('DrawerId', id)
43+
const emits = defineEmits<DrawerEmits>()
7944
8045
const isOpen = ref(props.modelValue)
8146
@@ -86,9 +51,16 @@ watch(() => props.modelValue, (newValue) => {
8651
const hasOpened = ref(false)
8752
const isClosed = ref(true)
8853
89-
watch(() => isOpen.value, (value) => {
54+
watch(isOpen, (val) => {
55+
emits('update:modelValue', val)
56+
if (val) {
57+
emits('open')
58+
}
59+
else {
60+
emits('close')
61+
}
9062
isClosed.value = false
91-
if (value && !hasOpened.value) {
63+
if (val && !hasOpened.value) {
9264
hasOpened.value = true
9365
}
9466
}, {
@@ -97,25 +69,62 @@ watch(() => isOpen.value, (value) => {
9769
9870
const forceMount = computed(() => !props.destroyOnClose && hasOpened.value)
9971
100-
function updateOpen(value: boolean) {
101-
isOpen.value = value
102-
emits('update:modelValue', value)
72+
async function updateOpen(value: boolean) {
10373
if (value) {
74+
isOpen.value = value
10475
emits('open')
10576
}
10677
else {
107-
emits('close')
78+
if (props.beforeClose) {
79+
await props.beforeClose(
80+
'close',
81+
() => {
82+
isOpen.value = value
83+
emits('close')
84+
},
85+
)
86+
}
87+
else {
88+
isOpen.value = value
89+
emits('close')
90+
}
10891
}
10992
}
11093
111-
function onConfirm() {
112-
updateOpen(false)
113-
emits('confirm')
94+
const isConfirmButtonLoading = ref(false)
95+
96+
async function onConfirm() {
97+
if (props.beforeClose) {
98+
isConfirmButtonLoading.value = true
99+
await props.beforeClose(
100+
'confirm',
101+
() => {
102+
isOpen.value = false
103+
emits('confirm')
104+
},
105+
)
106+
isConfirmButtonLoading.value = false
107+
}
108+
else {
109+
isOpen.value = false
110+
emits('confirm')
111+
}
114112
}
115113
116-
function onCancel() {
117-
updateOpen(false)
118-
emits('cancel')
114+
async function onCancel() {
115+
if (props.beforeClose) {
116+
await props.beforeClose(
117+
'cancel',
118+
() => {
119+
isOpen.value = false
120+
emits('cancel')
121+
},
122+
)
123+
}
124+
else {
125+
isOpen.value = false
126+
emits('cancel')
127+
}
119128
}
120129
121130
function handleFocusOutside(e: Event) {
@@ -124,7 +133,7 @@ function handleFocusOutside(e: Event) {
124133
}
125134
126135
function handleClickOutside(e: Event) {
127-
if (!props.closeOnClickOverlay || (e.target as HTMLElement).dataset.drawerId !== id) {
136+
if (!props.closeOnClickOverlay || (e.target as HTMLElement).dataset.drawerId !== props.id) {
128137
e.preventDefault()
129138
e.stopPropagation()
130139
}
@@ -151,8 +160,9 @@ function handleAnimationEnd() {
151160
<template>
152161
<Sheet :modal="false" :open="isOpen" @update:open="updateOpen">
153162
<SheetContent
154-
:closable="props.closable"
163+
:drawer-id="props.id"
155164
:open="isOpen"
165+
:closable="props.closable"
156166
:overlay="props.overlay"
157167
:overlay-blur="props.overlayBlur"
158168
:class="cn('w-full flex flex-col gap-0 p-0', props.contentClass, {
@@ -187,10 +197,10 @@ function handleAnimationEnd() {
187197
<div class="p-4">
188198
<slot />
189199
</div>
200+
<div v-show="props.loading" class="absolute inset-0 z-1000 size-full flex-center bg-popover/75">
201+
<FaIcon name="i-line-md:loading-twotone-loop" class="size-10" />
202+
</div>
190203
</FaScrollArea>
191-
<div v-show="props.loading" class="absolute inset-0 z-1000 size-full flex-center bg-popover/75">
192-
<FaIcon name="i-line-md:loading-twotone-loop" class="size-10" />
193-
</div>
194204
</div>
195205
<SheetFooter
196206
v-if="footer" :class="cn('p-2 gap-y-2', props.footerClass, {

src/ui/components/FaDrawer/sheet/SheetContent.vue

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { cn } from '@/utils'
1515
import { sheetVariants } from '.'
1616
1717
interface SheetContentProps extends DialogContentProps {
18+
drawerId: string
1819
class?: HTMLAttributes['class']
1920
open?: boolean
2021
side?: SheetVariants['side']
@@ -51,8 +52,6 @@ watch(showOverlay, (val) => {
5152
isLocked.value = false
5253
}
5354
})
54-
55-
const id = inject('DrawerId')
5655
</script>
5756

5857
<template>
@@ -70,7 +69,7 @@ const id = inject('DrawerId')
7069
>
7170
<div
7271
v-if="showOverlay"
73-
:data-drawer-id="id"
72+
:data-drawer-id="props.drawerId"
7473
:class="cn('fixed inset-0 z-2000 data-[state=closed]:animate-out data-[state=open]:animate-in bg-black/50 data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0', {
7574
'backdrop-blur-sm': props.overlayBlur,
7675
})"

0 commit comments

Comments
 (0)