The implementation of modals in #568 has a few patterns we're trying to avoid in this version. First, it breaks the principle of component composition by, second, dispatching a state update to a global modal store. This imperative approach to building UI breaks the assumptions about Svelte components, making things like passing slot components impossible. As @eyeseast wrote yesterday, "for modals, we're sort of stepping out of the composition tree, or jumping back up [...] it creates a cross-dependency between components that are otherwise unrelated, but I can't think of a way around that. This, at least, keeps the relationship explicit."
Stepping out of the component tree reminded me of React's Portals API. While it doesn't look like Svelte has the same feature built-in, it is possible with some community examples and libraries.
Let's consider adding a Portal component (either library or original) that renders a modal component to the root of the layout. This way, adding a modal becomes a matter of rendering a component tree:
<!-- lib/components/common/Portal.svelte -->
<!-- Source: https://github.com/sveltejs/svelte/issues/3088#issuecomment-1065827485 -->
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
export let target: HTMLElement | null | undefined = globalThis.document?.body;
let ref: HTMLElement;
onMount(() => {
if (target) {
target.appendChild(ref);
}
})
// this block is almost needless/useless (if not totally) as, on destroy, the ref will no longer exist/be in the DOM anyways
onDestroy(() => {
setTimeout(() => {
if (ref?.parentNode) {
ref.parentNode?.removeChild(ref);
}
})
})
</script>
<div bind:this={ref}>
<slot />
</div>
(EDIT: Rereading this example, this may be achievable without writable—a simple let formOpen may get the job done!)
This approach has a few improvements on the global-state managed modal:
Whether a modal is displayed is an extension of the UI that triggers it, so it can be kept local.
There's now a loose coupling between separate components that each manage their own concerns (Portal: where do I render?; Modal: how am I positioning and overlying content?; children: slots!).
This sample Portal optionally supports mounting to other DOM targets, so it can help us rendering other components to other trees.
In theory, multiple modals can be displayed if the Portal is appending to the body. While we are designing now for one modal layer, it's nice to not totally close the door on the possibility when designing new features.
The implementation of modals in #568 has a few patterns we're trying to avoid in this version. First, it breaks the principle of component composition by, second, dispatching a state update to a global modal store. This imperative approach to building UI breaks the assumptions about Svelte components, making things like passing
slot
components impossible. As @eyeseast wrote yesterday, "for modals, we're sort of stepping out of the composition tree, or jumping back up [...] it creates a cross-dependency between components that are otherwise unrelated, but I can't think of a way around that. This, at least, keeps the relationship explicit."Stepping out of the component tree reminded me of React's Portals API. While it doesn't look like Svelte has the same feature built-in, it is possible with some community examples and libraries.
Let's consider adding a
Portal
component (either library or original) that renders a modal component to the root of the layout. This way, adding a modal becomes a matter of rendering a component tree:(EDIT: Rereading this example, this may be achievable without writable—a simple
let formOpen
may get the job done!)This approach has a few improvements on the global-state managed modal: