Skip to content

Commit a413501

Browse files
committed
Modal events
1 parent 392ade5 commit a413501

File tree

9 files changed

+124
-24
lines changed

9 files changed

+124
-24
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<script setup>
2+
import { Modal, ModalLink } from '@inertiaui/modal-vue'
3+
4+
function log(value) {
5+
console.log(value)
6+
}
7+
</script>
8+
9+
<template>
10+
<Modal
11+
@success="log('success')"
12+
@blur="log('blur')"
13+
@focus="log('focus')"
14+
@close="log('close')"
15+
@after-leave="log('after-leave')"
16+
>
17+
<ModalLink href="/roles/create">
18+
Create role
19+
</ModalLink>
20+
</Modal>
21+
</template>

demo-app/routes/web.php

+5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
return redirect('/users');
1414
});
1515

16+
// Modal Events
17+
Route::get('/modal-events', function (User $user) {
18+
return Inertia::modal('ModalEvents')->baseUrl('/users');
19+
})->name('modal-events');
20+
1621
// Edit a user
1722
Route::get('/users/{user}/edit', function (User $user) {
1823
return Inertia::modal('EditUser', [

demo-app/tests/Browser/Browser.php

+49
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,53 @@ public function clickAt(int $x, int $y): self
7979

8080
return $this;
8181
}
82+
83+
/**
84+
* Get the console log from the browser.
85+
*/
86+
public function getConsoleLog(): array
87+
{
88+
return $this->driver->manage()->getLog('browser');
89+
}
90+
91+
/**
92+
* Determine if the console log contains the given message.
93+
*/
94+
private function consoleLogContains(string $needle): bool
95+
{
96+
return collect($this->getConsoleLog())->contains(function (array $entry) use ($needle) {
97+
// E.g.: http://[::1]:5173/@vite/client 494:8 "[vite] connecting..."
98+
preg_match('/^.*\d+:\d+\s"(.+)"$/', $entry['message'], $matches);
99+
100+
return ! empty($matches)
101+
? str_contains($matches[1] ?? '', $needle)
102+
: false;
103+
});
104+
}
105+
106+
/**
107+
* Assert that the console log contains the given message.
108+
*/
109+
public function assertConsoleLogContains(string $needle): self
110+
{
111+
Assert::assertTrue(
112+
$this->consoleLogContains($needle),
113+
"The console log does not contain the expected message: {$needle}"
114+
);
115+
116+
return $this;
117+
}
118+
119+
/**
120+
* Wait for the console log to contain the given message.
121+
*/
122+
public function waitForConsoleLog(string $needle, ?int $seconds = null, int $interval = 100): self
123+
{
124+
return $this->waitUsing(
125+
$seconds,
126+
$interval,
127+
fn () => $this->consoleLogContains($needle),
128+
"The console log does not contain the expected message: {$needle}"
129+
);
130+
}
82131
}

demo-app/tests/Browser/EventsTest.php

+17
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,23 @@ public function it_can_attach_listeners_to_the_modal_link(bool $navigate)
2323
});
2424
}
2525

26+
#[Test]
27+
public function it_can_attach_listeners_to_the_modal_component()
28+
{
29+
$this->browse(function (Browser $browser) {
30+
$browser->visit('/modal-events')
31+
->waitForConsoleLog('success')
32+
->waitForModal()
33+
->clickLink('Create role')
34+
->waitForConsoleLog('blur')
35+
->waitForModal(1)
36+
->clickModalCloseButton(1)
37+
->waitForConsoleLog('focus')
38+
->clickModalCloseButton()
39+
->waitForConsoleLog('close');
40+
});
41+
}
42+
2643
#[DataProvider('booleanProvider')]
2744
#[Test]
2845
public function it_can_attach_a_listener_for_blur(bool $navigate)

demo-app/tests/DuskTestCase.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ protected function driver(): RemoteWebDriver
168168
$_ENV['DUSK_DRIVER_URL'] ?? 'http://localhost:9515',
169169
DesiredCapabilities::chrome()->setCapability(
170170
ChromeOptions::CAPABILITY, $options
171-
)
171+
)->setCapability('goog:loggingPrefs', ['browser' => 'ALL'])
172172
);
173173
}
174174

vue/src/HeadlessModal.vue

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script setup>
22
import { getConfig, getConfigByType } from './config'
3-
import { inject, onBeforeUnmount, ref, computed, useAttrs, onMounted } from 'vue'
3+
import { inject, onBeforeUnmount, ref, computed, useAttrs, onMounted, watch } from 'vue'
44
import { useModalStack } from './modalStack'
55
import ModalRenderer from './ModalRenderer.vue'
66
@@ -85,7 +85,7 @@ function registerEventListeners() {
8585
unsubscribeEventListeners.value = modalContext.value.registerEventListenersFromAttrs($attrs)
8686
}
8787
88-
const emits = defineEmits(['modal-event'])
88+
const emits = defineEmits(['modal-event', 'focus', 'blur', 'close', 'success'])
8989
9090
function emit(event, ...args) {
9191
emits('modal-event', event, ...args)
@@ -123,6 +123,25 @@ defineExpose({
123123
},
124124
})
125125
126+
watch(
127+
() => modalContext.value?.onTopOfStack,
128+
(onTopOfStack, previousOnTopOfStack) => {
129+
if (onTopOfStack && !previousOnTopOfStack) {
130+
emits('focus')
131+
} else if (!onTopOfStack && previousOnTopOfStack) {
132+
emits('blur')
133+
}
134+
},
135+
)
136+
137+
watch(
138+
() => modalContext.value?.isOpen,
139+
(isOpen) => {
140+
isOpen ? emits('success') : emits('close')
141+
},
142+
{ immediate: true },
143+
)
144+
126145
const nextIndex = computed(() => {
127146
return modalStack.stack.value.find((m) => m.shouldRender && m.index > modalContext.value.index)?.index
128147
})

vue/src/Modal.vue

+7
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ const modal = ref(null)
99
const rendered = ref(false)
1010
const preparedDOM = ref(false)
1111
12+
defineEmits(['after-leave', 'blur', 'close', 'focus', 'success'])
13+
1214
watch(
1315
() => modal.value,
1416
(value) => {
@@ -77,6 +79,10 @@ defineExpose({
7779
setOpen,
7880
shouldRender,
7981
}"
82+
@success="$emit('success')"
83+
@close="$emit('close')"
84+
@focus="$emit('focus')"
85+
@blur="$emit('blur')"
8086
>
8187
<teleport
8288
v-if="shouldRender"
@@ -98,6 +104,7 @@ defineExpose({
98104
leave-from-class="opacity-100"
99105
leave-to-class="opacity-0"
100106
@after-appear="rendered = true"
107+
@after-leave="$emit('after-leave')"
101108
>
102109
<div
103110
v-show="isOpen"

vue/src/ModalLink.vue

-14
Original file line numberDiff line numberDiff line change
@@ -74,19 +74,6 @@ const props = defineProps({
7474
const loading = ref(false)
7575
const modalStack = useModalStack()
7676
const modalContext = ref(null)
77-
const modalId = ref()
78-
79-
watch(
80-
props,
81-
() => {
82-
if (modalId.value) {
83-
modalStack.removePendingModalUpdate(modalId.value)
84-
}
85-
86-
modalId.value = generateId()
87-
},
88-
{ immediate: true },
89-
)
9077
9178
provide('modalContext', modalContext)
9279
@@ -161,7 +148,6 @@ function handle() {
161148
onAfterLeave,
162149
props.queryStringArrayFormat,
163150
shouldNavigate.value,
164-
modalId.value,
165151
)
166152
.then((context) => {
167153
modalContext.value = context

vue/src/modalStack.js

+3-7
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@ const baseUrl = ref(null)
1313
const stack = ref([])
1414
const localModals = ref({})
1515

16-
const removePendingModalUpdate = (id) => {
17-
delete pendingModalUpdates.value[id]
18-
}
19-
2016
const setComponentResolver = (resolver) => {
2117
resolveComponent = resolver
2218
}
@@ -67,6 +63,8 @@ class Modal {
6763
}
6864
: pendingOnAfterLeave
6965
}
66+
67+
delete pendingModalUpdates.value[this.id]
7068
}
7169

7270
this.index = computed(() => stack.value.findIndex((m) => m.id === this.id))
@@ -262,9 +260,8 @@ function visit(
262260
onAfterLeave = null,
263261
queryStringArrayFormat = 'brackets',
264262
useBrowserHistory = false,
265-
modalId = null,
266263
) {
267-
modalId = modalId ?? generateId()
264+
const modalId = generateId()
268265

269266
return new Promise((resolve, reject) => {
270267
if (href.startsWith('#')) {
@@ -332,7 +329,6 @@ export const renderApp = (App, props) => {
332329

333330
export function useModalStack() {
334331
return {
335-
removePendingModalUpdate,
336332
setComponentResolver,
337333
getBaseUrl: () => baseUrl.value,
338334
setBaseUrl: (url) => (baseUrl.value = url),

0 commit comments

Comments
 (0)