|
1 | 1 | <script setup>
|
2 |
| -import { inject, onBeforeUnmount, ref, provide } from 'vue' |
| 2 | +import { inject, onBeforeUnmount, ref, computed, useAttrs, onMounted } from 'vue' |
| 3 | +import { TransitionRoot, TransitionChild, Dialog } from '@headlessui/vue' |
| 4 | +
|
| 5 | +import { getConfig, getConfigByType } from './config' |
| 6 | +import { modalPropNames } from './modalStack' |
| 7 | +import { only } from './helpers' |
| 8 | +import { useModalStack } from './modalStack' |
3 | 9 | import ModalContent from './ModalContent.vue'
|
4 |
| -import ModalWrapper from './ModalWrapper.vue' |
| 10 | +import ModalRenderer from './ModalRenderer.vue' |
5 | 11 | import SlideoverContent from './SlideoverContent.vue'
|
6 |
| -import { useModalStack } from './modalStack' |
7 | 12 |
|
8 | 13 | const props = defineProps({
|
9 | 14 | name: {
|
10 | 15 | type: String,
|
11 | 16 | required: false,
|
12 | 17 | },
|
| 18 | + // The slideover prop in on top because we need to know if it's a slideover |
| 19 | + // before we can determine the defaule value of other props |
| 20 | + slideover: { |
| 21 | + type: Boolean, |
| 22 | + default: () => getConfig('type') === 'slideover', |
| 23 | + }, |
| 24 | + closeButton: { |
| 25 | + type: Boolean, |
| 26 | + default: (props) => getConfigByType(props.slideover, 'closeButton'), |
| 27 | + }, |
| 28 | + closeExplicitly: { |
| 29 | + type: Boolean, |
| 30 | + default: (props) => getConfigByType(props.slideover, 'closeExplicitly'), |
| 31 | + }, |
| 32 | + maxWidth: { |
| 33 | + type: String, |
| 34 | + default: (props) => getConfigByType(props.slideover, 'maxWidth'), |
| 35 | + }, |
| 36 | + paddingClasses: { |
| 37 | + type: [Boolean, String], |
| 38 | + default: (props) => getConfigByType(props.slideover, 'paddingClasses'), |
| 39 | + }, |
| 40 | + panelClasses: { |
| 41 | + type: [Boolean, String], |
| 42 | + default: (props) => getConfigByType(props.slideover, 'panelClasses'), |
| 43 | + }, |
| 44 | + position: { |
| 45 | + type: String, |
| 46 | + default: (props) => getConfigByType(props.slideover, 'position'), |
| 47 | + }, |
13 | 48 | })
|
14 | 49 |
|
15 | 50 | const modalStack = useModalStack()
|
16 |
| -const injectedModalContext = props.name ? ref({}) : inject('modalContext') |
| 51 | +const modalContext = props.name ? ref({}) : inject('modalContext') |
| 52 | +const modalProps = computed(() => { |
| 53 | + return { |
| 54 | + ...only(props, modalPropNames), |
| 55 | + ...modalContext.value.modalProps, |
| 56 | + } |
| 57 | +}) |
17 | 58 |
|
| 59 | +// Local Modals... |
18 | 60 | if (props.name) {
|
19 | 61 | modalStack.registerLocalModal(props.name, function (context) {
|
20 |
| - injectedModalContext.value = context |
| 62 | + modalContext.value = context |
| 63 | + registerEventListeners() |
21 | 64 | })
|
22 | 65 |
|
23 |
| - // Now this component is the provider instead of ModalLink |
24 |
| - provide('modalContext', injectedModalContext) |
25 |
| -
|
26 | 66 | onBeforeUnmount(() => {
|
27 | 67 | modalStack.removeLocalModal(props.name)
|
28 | 68 | })
|
29 | 69 | }
|
30 | 70 |
|
31 |
| -const emits = defineEmits(['emit']) |
| 71 | +onMounted(() => { |
| 72 | + modalStack.verifyRoot() |
| 73 | +
|
| 74 | + if (!props.name) { |
| 75 | + registerEventListeners() |
| 76 | + } |
| 77 | +}) |
| 78 | +
|
| 79 | +function closeDialog() { |
| 80 | + if (!modalProps.value.closeExplicitly) { |
| 81 | + modalContext.value.close() |
| 82 | + } |
| 83 | +} |
| 84 | +
|
| 85 | +const unsubscribeEventListeners = ref(null) |
| 86 | +onBeforeUnmount(() => unsubscribeEventListeners.value?.()) |
| 87 | +
|
| 88 | +const $attrs = useAttrs() |
| 89 | +
|
| 90 | +function registerEventListeners() { |
| 91 | + unsubscribeEventListeners.value = modalContext.value.registerEventListenersFromAttrs($attrs) |
| 92 | +} |
| 93 | +
|
| 94 | +const emits = defineEmits(['modal-event']) |
32 | 95 |
|
33 | 96 | function emit(event, ...args) {
|
34 |
| - emits('emit', event, ...args) |
| 97 | + emits('modal-event', event, ...args) |
35 | 98 | }
|
36 | 99 |
|
37 | 100 | defineExpose({
|
38 |
| - close: injectedModalContext.value.close, |
| 101 | + close: modalContext.value.close, |
39 | 102 | emit,
|
40 |
| - getChildModal: injectedModalContext.value.getChildModal, |
41 |
| - getParentModal: injectedModalContext.value.getParentModal, |
42 |
| - modalContext: injectedModalContext.value, |
43 |
| - reload: injectedModalContext.value.reload, |
| 103 | + getChildModal: modalContext.value.getChildModal, |
| 104 | + getParentModal: modalContext.value.getParentModal, |
| 105 | + modalContext: modalContext.value, |
| 106 | + reload: modalContext.value.reload, |
44 | 107 | })
|
45 | 108 | </script>
|
46 | 109 |
|
47 | 110 | <template>
|
48 |
| - <ModalWrapper v-slot="{ modalContext, modalProps }"> |
49 |
| - <component |
50 |
| - :is="modalProps.slideover ? SlideoverContent : ModalContent" |
51 |
| - :modal-context="modalContext" |
52 |
| - :modal-props="modalProps" |
| 111 | + <TransitionRoot |
| 112 | + :unmount="false" |
| 113 | + :show="modalContext.open ?? false" |
| 114 | + enter="transition transform ease-in-out duration-300" |
| 115 | + enter-from="opacity-0 scale-95" |
| 116 | + enter-to="opacity-100 scale-100" |
| 117 | + leave="transition transform ease-in-out duration-300" |
| 118 | + leave-from="opacity-100 scale-100" |
| 119 | + leave-to="opacity-0 scale-95" |
| 120 | + > |
| 121 | + <Dialog |
| 122 | + :data-inertiaui-modal-id="modalContext.id" |
| 123 | + :data-inertiaui-modal-index="modalContext.index" |
| 124 | + class="im-dialog relative z-20" |
| 125 | + @close="closeDialog" |
53 | 126 | >
|
54 |
| - <slot |
55 |
| - :close="modalContext.close" |
56 |
| - :emit="emit" |
57 |
| - :get-child-modal="modalContext.getChildModal" |
58 |
| - :get-parent-modal="modalContext.getParentModal" |
| 127 | + <!-- Only transition the backdrop for the first modal in the stack --> |
| 128 | + <TransitionChild |
| 129 | + v-if="modalContext.index === 0" |
| 130 | + as="template" |
| 131 | + enter="transition transform ease-in-out duration-300" |
| 132 | + enter-from="opacity-0" |
| 133 | + enter-to="opacity-100" |
| 134 | + leave="transition transform ease-in-out duration-300" |
| 135 | + leave-from="opacity-100" |
| 136 | + leave-to="opacity-0" |
| 137 | + > |
| 138 | + <div |
| 139 | + v-show="modalContext.onTopOfStack" |
| 140 | + class="im-backdrop fixed inset-0 z-30 bg-black/75" |
| 141 | + aria-hidden="true" |
| 142 | + /> |
| 143 | + </TransitionChild> |
| 144 | +
|
| 145 | + <!-- On multiple modals, only show a backdrop for the modal that is on top of the stack --> |
| 146 | + <div |
| 147 | + v-if="modalContext.index > 0 && modalContext.onTopOfStack" |
| 148 | + class="im-backdrop fixed inset-0 z-30 bg-black/75" |
| 149 | + /> |
| 150 | +
|
| 151 | + <!-- The modal/slideover content itself --> |
| 152 | + <component |
| 153 | + :is="modalProps.slideover ? SlideoverContent : ModalContent" |
59 | 154 | :modal-context="modalContext"
|
60 | 155 | :modal-props="modalProps"
|
61 |
| - :reload="modalContext.reload" |
| 156 | + > |
| 157 | + <slot |
| 158 | + :close="modalContext.close" |
| 159 | + :emit="emit" |
| 160 | + :get-child-modal="modalContext.getChildModal" |
| 161 | + :get-parent-modal="modalContext.getParentModal" |
| 162 | + :modal-context="modalContext" |
| 163 | + :modal-props="modalProps" |
| 164 | + :reload="modalContext.reload" |
| 165 | + /> |
| 166 | + </component> |
| 167 | +
|
| 168 | + <!-- The next modal in the stack --> |
| 169 | + <ModalRenderer |
| 170 | + v-if="modalStack.stack.value[modalContext.index + 1]" |
| 171 | + :index="modalContext.index + 1" |
62 | 172 | />
|
63 |
| - </component> |
64 |
| - </ModalWrapper> |
| 173 | + </Dialog> |
| 174 | + </TransitionRoot> |
65 | 175 | </template>
|
0 commit comments