A cursed way to have vue-like slots api in SolidJS.
This library does not use Portals, and provides a ssr-friendly slots api.
When designing compound components our api is sometimes at odds with underlying layout. Consider a layout component, where we want to put the header and the content into different elements. E.g. we want html like this:
<div>
<h1>Title</h1>
<main>
<p>Info</p>
<p>Extra info</p>
</main>
</div>This library allows us to provide an api like this:
<Layout>
<Header>Title</Header>
<p>Info</p>
<p>Extra info</p>
</Layout>Here, we extract our title from our children, and put it outside of <main> tag.
This is done via core primitive resolveSlots:
const Header = createSlot();
function Layout(props) {
const [header, children] = resolveSlots(() => props.children, Header);
return <div>
<h1>{header()}</h1>
<main>{children()}</main>
</div>;
}And that's it.
This library abuses some implementation details of SolidJS rendering to implement this nice api. These implementation details are unlikely to change, but beware.
The api of the library has settled general direction, but is expected to change in minor details. There are some non-trivial uses that are possible, but not yet supported.
The core idea of the library is slots - it is the component that can be introspected.
They are created via createSlot function:
const Header = createSlot();
const Footer = createSlot();
const Aside = createSlot();The returned slots are solid components, that just render their children:
<Header>
<b>Hello</b> World
</Header>Renders as just <b>Hello</b> World.
But they have a superpower to be introspected.
In your consuming component, you call resolveSlots, to extract slotted components from your slots.
function Layout(props) {
const [header, footer, children] = resolveSlots(() => props.children, Header, Footer);
// ...
}Here resolveSlots traverses the props.children.
It collects all children that are Header into header() accessor,
and all children that are Footer into footer() accessor.
All other children are put into the children() accessor.
Note, how we ignore <Aside> slot.
If the user passes an unknown slot, resolveSlots puts it into children.
The question is, when will you see a slot in you children. It is a non-trivial question, and I will not give a definitive answer in all cases.
Here is a common list of examples:
const MyHeader = (props) => <Header>My {props.children}</Header>;
<Layout>
<Header>Yes</Header>
<MyHeader>Yes</MyHeader>
<><Header>Yes</Header></>
<div><Header>No</Header></div>
<Show when={cond}>
<Header>Probably</Header>
</Show>
<For each={[1, 2, 3]}>{() => <>
<Header>Probably</Header>
</>}</For>
</Layout>On top of that, any call to children helper from solid-js, or to resolveSlots expands all slots.
E.g.
import {children} from "solid-js";
const resolved = children(() => props.children);
// Slot will never be seen, as `children` has removed all slots machinery
const [slot, rest] = resolveSlots(resolved, Slot);