Skip to content

Commit

Permalink
Check ModalRoot component
Browse files Browse the repository at this point in the history
  • Loading branch information
pascalbaljet committed Oct 2, 2024
1 parent ff76279 commit 2eb7050
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 41 deletions.
39 changes: 21 additions & 18 deletions vue/src/Modal.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<script setup>
import { inject, onBeforeUnmount, ref, computed } from 'vue'
import { inject, onBeforeUnmount, ref, computed, useAttrs, onMounted, nextTick } from 'vue'
import { TransitionRoot, TransitionChild, Dialog } from '@headlessui/vue'
import { getConfig, getConfigByType } from './config'
import { modalPropNames } from './modalStack'
import { only } from './helpers'
import { useModalStack } from './modalStack'
import ModalContent from './ModalContent.vue'
import ModalResolver from './ModalResolver.vue'
import ModalRenderer from './ModalRenderer.vue'
import SlideoverContent from './SlideoverContent.vue'
const props = defineProps({
Expand Down Expand Up @@ -56,42 +56,45 @@ const modalProps = computed(() => {
}
})
// Local Modals...
if (props.name) {
modalStack.registerLocalModal(props.name, function (context) {
modalContext.value = context
registerEventListeners()
})
onBeforeUnmount(() => {
modalStack.removeLocalModal(props.name)
})
}
onMounted(() => {
modalStack.verifyRoot()
if (!props.name) {
registerEventListeners()
}
})
function closeDialog() {
if (!modalProps.value.closeExplicitly) {
modalContext.value.close()
}
}
const unsubscribeEventListeners = ref(null)
onBeforeUnmount(() => unsubscribeEventListeners.value?.())
const $attrs = useAttrs()
Object.keys($attrs)
.filter((key) => key.startsWith('on'))
.forEach((key) => {
// e.g. onRefreshKey -> refresh-key
const snakeCaseKey = key
.replace(/^on/, '')
.replace(/^./, (firstLetter) => firstLetter.toLowerCase())
.replace(/([A-Z])/g, '-$1')
.toLowerCase()
// TODO: after unmounting, we need to remove the event listener
modalContext.value.on(snakeCaseKey, $attrs[key])
})
function registerEventListeners() {
unsubscribeEventListeners.value = modalContext.value.registerEventListenersFromAttrs($attrs)
}
const emits = defineEmits(['emit'])
const emits = defineEmits(['modal-event'])
function emit(event, ...args) {
emits('emit', event, ...args)
emits('modal-event', event, ...args)
}
defineExpose({
Expand Down Expand Up @@ -163,7 +166,7 @@ defineExpose({
</component>
<!-- The next modal in the stack -->
<ModalResolver
<ModalRenderer
v-if="modalStack.stack.value[modalContext.index + 1]"
:index="modalContext.index + 1"
/>
Expand Down
21 changes: 7 additions & 14 deletions vue/src/ModalLink.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup>
import { modalPropNames, useModalStack } from './modalStack'
import { nextTick, ref, provide, watch, onMounted, useAttrs } from 'vue'
import { nextTick, ref, provide, watch, onMounted, useAttrs, onBeforeUnmount } from 'vue'
import { only, rejectNullValues } from './helpers'
const props = defineProps({
Expand Down Expand Up @@ -95,26 +95,20 @@ watch(
)
onMounted(() => {
modalStack.verifyRoot()
if (props.fragment && window.location.hash === `#${props.fragment}`) {
handle()
}
})
const unsubscribeEventListeners = ref(null)
onBeforeUnmount(() => unsubscribeEventListeners.value?.())
const $attrs = useAttrs()
function registerEventListeners() {
Object.keys($attrs)
.filter((key) => key.startsWith('on'))
.forEach((key) => {
// e.g. onRefreshKey -> refresh-key
const snakeCaseKey = key
.replace(/^on/, '')
.replace(/^./, (firstLetter) => firstLetter.toLowerCase())
.replace(/([A-Z])/g, '-$1')
.toLowerCase()
modalContext.value.on(snakeCaseKey, $attrs[key])
})
unsubscribeEventListeners.value = modalContext.value.registerEventListenersFromAttrs($attrs)
}
watch(modalContext, (value, oldValue) => {
Expand All @@ -123,7 +117,6 @@ watch(modalContext, (value, oldValue) => {
window.location.hash = props.fragment
}
// TODO: after unmounting, remove event listeners
registerEventListeners()
nextTick(() => {
Expand Down
11 changes: 5 additions & 6 deletions vue/src/ModalResolver.vue → vue/src/ModalRenderer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { useModalStack } from './modalStack'
import { computed, provide } from 'vue'
const props = defineProps({
index: Number,
index: {
type: Number,
required: true,
},
})
const modalStack = useModalStack()
Expand All @@ -13,16 +16,12 @@ const modalContext = computed(() => {
})
provide('modalContext', modalContext)
function handleEmittedEvent(event, ...args) {
modalContext.value.emit(event, ...args)
}
</script>

<template>
<modalContext.component
v-if="modalContext?.component"
v-bind="modalContext.componentProps"
@emit="handleEmittedEvent"
@modal-event="(event, ...args) => modalContext.emit(event, ...args)"
/>
</template>
9 changes: 7 additions & 2 deletions vue/src/ModalRoot.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
<script setup>
import { onBeforeMount } from 'vue';
import { useModalStack } from './modalStack'
import ModalResolver from './ModalResolver.vue'
import ModalRenderer from './ModalRenderer.vue'
const modalStack = useModalStack()
onBeforeMount(() => {
modalStack.rootPresent.value = true
})
</script>

<template>
<slot />

<ModalResolver
<ModalRenderer
v-if="modalStack.stack.value.length"
:index="0"
/>
Expand Down
31 changes: 30 additions & 1 deletion vue/src/modalStack.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ class Modal {
}

afterLeave = () => {
// console.log(`After leave modal ${this.id}`)
stack.value = stack.value.filter((m) => m.id !== this.id)
this.afterLeaveCallback()
}
Expand All @@ -78,6 +77,26 @@ class Modal {
this.listeners[event]?.forEach((callback) => callback(...args))
}

registerEventListenersFromAttrs = ($attrs) => {
const unsubscribers = []

Object.keys($attrs)
.filter((key) => key.startsWith('on'))
.forEach((key) => {
// e.g. onRefreshKey -> refresh-key
const snakeCaseKey = key
.replace(/^on/, '')
.replace(/^./, (firstLetter) => firstLetter.toLowerCase())
.replace(/([A-Z])/g, '-$1')
.toLowerCase()

this.on(snakeCaseKey, $attrs[key])
unsubscribers.push(() => this.off(snakeCaseKey, $attrs[key]))
})

return () => unsubscribers.forEach((unsub) => unsub())
}

reload = (options = {}) => {
let keys = Object.keys(this.response.props)

Expand Down Expand Up @@ -150,6 +169,8 @@ function push(component, response, modalProps, onClose, afterLeave) {
return newModal
}

export const rootPresent = ref(false)

export const modalPropNames = ['closeButton', 'closeExplicitly', 'maxWidth', 'paddingClasses', 'panelClasses', 'position', 'slideover']

export function useModalStack() {
Expand All @@ -161,5 +182,13 @@ export function useModalStack() {
callLocalModal,
registerLocalModal,
removeLocalModal: (name) => delete localModals.value[name],
rootPresent,
verifyRoot: () => {
if (!rootPresent.value) {
throw new Error(
'The <ModalRoot> component is missing from your app layout. Please check the documentation for more information: https://inertiaui.com/inertia-modal/docs/installation',
)
}
},
}
}

0 comments on commit 2eb7050

Please sign in to comment.