Bundle / foldkit-bundle
Gates and custom Links
when in a placement config is the parent's own gate: while it returns
false, the placement's Subscriptions and resources stop. An absent child also
stops them, renders nothing, and ignores its Messages.
Page.place(MediaQuery, 'narrow', {
args: { query: '(max-width: 40rem)' },
when: model => !model.helpOpen,
})A child that is not a top-level field is placed through a Link, with
bundle.at(link, config) and bundle.each(link, config). The scope builds Links
without restating the parent, and Links are pipeable:
// SidebarPage's Model is { sidebar, open }; GotSidebarMessage is Link.wrapper('GotSidebarMessage', MediaQueryMessage).
const Sidebar = MediaQuery.at(
SidebarPage.link
.field('sidebar', GotSidebarMessage)
.pipe(Link.when((model: typeof SidebarPage.Model.Type) => model.open)),
{ args: { query: '(min-width: 60rem)' } },
)| Link | The child is |
|---|---|
Page.link.field(key, wrapper) or Link.field<Parent>()(…) | a struct field, always present |
Page.link.optional(key, wrapper) or Link.optional<Parent>()(…) | an Option field; absent while None |
Page.link.collection(key, keyedWrapper) or Link.collection<Parent>()(…) | a record of items by key |
Page.link.collectionById(key, keyedWrapper, { id }) or Link.collectionById<Parent>()(…) | an array of items, in order |
outer.pipe(Link.andThen(inner)) | inside another placed child |
link.pipe(Link.when(predicate)) | gated by the parent |
Link.make({ read, write, wrapper, path }) | anywhere a lens can reach |
Link.wrapper(tag, ChildMessage) and Link.keyedWrapper(tag, ChildMessage, key?)
build the parent variants a Link carries; Link.wrapper(Message.GotXMessage)
takes one the parent's union already declares. When rest gives a placement's
top-level field, placements.initial keeps that value and skips its init, so
an optional child can start as None. A nested placement is always initialised,
inside whatever rest gave, and outer placements initialise before nested ones.
A Link.make whose path is one literal segment, path: ['sidebar'], names
that top-level field to initial, as field and optional do.
Services
When the parent's own update needs services, name them on the scope:
// clockUpdate: (model: Model, message: Message) => Update.Return<Model, Message, Clock>
Page.withServices<Clock>()
.assemble(Page.at(Dark, { args: { query: '(prefers-color-scheme: dark)' } }))
.update(clockUpdate)