Closed josdejong closed 1 year ago
Apologies for my belated reply. By design, what you're trying to achieve is not possible with a single <Modal />
instance. You can only ever have as many modals as you have <Modal />
instances. So in order to open two modals you need two <Modal />
instances.
Beyond that it's considered a bad practice to have multiple superimposing/nested modal dialogs, which is why I am not planning to add direct support for it to this library. But again, if you need to have multiple modals open at the same time, simply create two global <Modal />
components.
No need to apologize, thanks for your reply.
I agree in general that nested modals are not a very good idea. I have a bit of a special case here though. I'll see how I can go about it. I can probably model a stack holding the state and opening the latest in the stack in a single popup or so.
If you know ahead of time that you'll only ever have to deal with two modals a very simple solution could be the following
<!-- App.svelte -->
<script>
import Modal, { bind } from 'svelte-simple-modal';
import Popup from './Popup.svelte';
import { modal1, modal2 } from './stores'
const showModal = () => modal1.set(bind(Popup, { message: 'Surprise' }));
</script>
<Modal show={$modal1}>
<Modal show={$modal2}>
<button on:click={showModal}>Show modal</button>
</Modal>
</Modal>
<!-- Popup.svelte -->
<script>
import { bind } from 'svelte-simple-modal';
import Popup2 from './Popup2.svelte';
import { modal2 } from './stores'
const showModal = () => modal2.set(bind(Popup2, { message: 'Double Surprise' }));
export let message = 'Hi';
</script>
<p>π {message} πΎ</p>
<button on:click={showModal}>Open another modal</button>
<!-- Popup2.svelte -->
<script>
export let message = 'Hi';
</script>
<p>ππ {message} πΎπΎ</p>
<!-- stores.js -->
import { writable } from 'svelte/store';
export const modal1 = writable(null);
export const modal2 = writable(null);
Live demo: https://svelte.dev/repl/3ed9fa66fdc04664b9a9d93b88aef310?version=3.53.1
This triggers two nested components. There are only two downsides really. First, the number of nested components needs to be known upfront. Second, both modals are relative to the HTML body element. So one cannot really create a modal that's relative to another DOM element (like the first modal).
I hope this helps.
O wow, that is a really helpful example! Thanks Fritz. So it is possible to have multiple modals, though they must know their "level" in advance.
I have a fun, recursive case: a popup in a JSON Editor to edit a small section of a JSON document. Since a JSON structure can be deeply nested, you could open another popup again from the first popup, and again. These are edge cases though, normally you open 1 popup, and in exceptional cases 2. After your feedback I've been thinking about alternative solutions. I think I can neatly solve this by opening 1 modal, and inside this single modal, keep a stack of the nested levels+state, showing only the last one, with a navigation path so you do not lose track. I'll figure something out.
So it is possible to have multiple modals, though they must know their "level" in advance.
Yes, that's correct.
I think I can neatly solve this by opening 1 modal, and inside this single modal, keep a stack of the nested levels+state, showing only the last one, with a navigation path so you do not lose track.
This sounds like a neat solution!
Apart from that, I think another interesting solution to get infinitely-nested Modals is to instantiate a new <Modal />
within a modal. Ideally, the nested modals would overlay only the previous modal, but I haven't gotten around to implement this (i.e., that's what https://github.com/flekschas/svelte-simple-modal/issues/32 is open for)
Here's a demo that supports infinitely nested modals: https://svelte.dev/repl/0341523e52074c92a6a3ba454270921d?version=3.53.1
<!-- App.svelte -->
<script>
import { writable } from 'svelte/store';
import Modal from 'svelte-simple-modal';
import Popup from './Popup.svelte';
const modal = writable(null);
const showModal = () => modal.set(Popup);
</script>
<Modal show={$modal}>
<button on:click={showModal}>Show modal</button>
</Modal>
<!-- Popup.svelte -->
<script>
import { writable } from 'svelte/store';
import Modal, { bind } from 'svelte-simple-modal';
import Popup from './Popup.svelte';
export let level = 1;
const modal = writable(null);
const showModal = () => modal.set(bind(Popup, { level: level + 1 }));
const message = Array.from({ length: level }, () => 'Suprise').join(' ');
</script>
<Modal show={$modal}>
<p>π {message} πΎ</p>
<button on:click={showModal}>Show another modal</button>
</Modal>
Ha, that is nifty π . I couldn't have figured out that use of svelte-simple-modal
myself. I have to say, the library is more flexible than I had expected :). This gives me some good inspiration to work out a solution for my recursive case, thanks.
closeOnEsc -> all modal window terminated... how to each modal terminated?
"closeButton" option as intended. But "closeOnEsc" is all terminated.
https://svelte.dev/repl/514f1335749a4eae9d34ad74dc277f20?version=3.37.0 this example destroy nested modal one by one as "ESC" key clicked
@Neptunium1129 This isn't supported out of the box as the library by default assumes you're using it a global singleton. If you need to support multiple nested components you'll have to implement the closing on escape logic yourself. However, it isn't too hard. The following works for me:
<!-- App.svelte -->
<script>
import { writable } from 'svelte/store';
import Modal from 'svelte-simple-modal';
import Popup from './Popup.svelte';
import { currentModal } from './stores';
const modal = writable(null);
const showModal = () => modal.set(Popup);
const handleKeydown = (event) => {
if (event.key === 'Escape' && $currentModal === 1) {
event.preventDefault();
modal.update(() => null);
currentModal.update(() => 1);
}
}
</script>
<svelte:window on:keydown={handleKeydown} />
<Modal show={$modal} closeOnEsc={false}>
<button on:click={showModal}>Show modal</button>
</Modal>
<!-- Popup.svelte -->
<script>
import Modal, { bind } from 'svelte-simple-modal';
import Popup from './Popup.svelte';
import { currentModal } from './stores';
export let level = 1;
const modal = writable(null);
const showModal = () => modal.set(bind(Popup, { level: level + 1 }));
const message = Array.from({ length: level }, () => 'Suprise').join(' ');
currentModal.update(() => level);
const handleKeydown = (event) => {
if (event.key === 'Escape' && $currentModal === level + 1) {
event.preventDefault();
modal.update(() => null);
currentModal.update(() => level);
}
}
</script>
<svelte:window on:keydown={handleKeydown} />
<Modal show={$modal} closeOnEsc={false}>
<p>π {message} πΎ</p>
<button on:click={showModal}>Show another modal</button>
</Modal>
<!-- stores.js -->
import { writable } from 'svelte/store';
export const currentModal = writable(1);
https://svelte.dev/repl/736fca8151744b4e83bf4791c1fd51ed?version=3.53.1
@flekschas thanks !!
Right now, when I open a second modal from an open modal, the first modal disappears. Ideally, the second modal should be rendered on top of the original one, showing both.