On this pageFunctions
Submodel
/**
* Defines the view function of a Submodel, a child component embedded
* via `h.submodel`.
*
* Use this ONLY for views that will be embedded via `h.submodel`. Plain
* view functions (page-level render functions, helper render functions
* that compose Html, etc.) don't need to be defined this way. Write
* them as ordinary `(model) => Html` functions.
*
* Explicit type arguments are required because Message has no
* inferable source on the function signature itself.
*/
<Model, Message, ViewInputs = void>(fn: ViewInputs extends void
? (model: Model) => VNode | null
: (model: Model, viewInputs: ViewInputs) => VNode | null): SubmodelView<Model, Message, ViewInputs>/**
* Configuration for embedding a child Submodel into a parent's view.
*
* - `slotId`: unique identifier for this Submodel instance under the
* current boundary. Name the slot semantically (e.g.
* `'sidebar-group'`). For lists, use a stable per-item id (typically
* `entry.id`), not the array index. If the same model is rendered in
* two DOM positions (desktop + mobile, master + detail), each slot
* needs its own id (e.g. `'desktop-sidebar-group'`,
* `'mobile-sidebar-group'`). Two `h.submodel` calls inside the same
* parent boundary with the same `slotId` throw at view-build time,
* including across `createLazy`/`createKeyedLazy` cache hits.
* - `view`: the child's `SubmodelView`. Must be branded via
* defineView so `h.submodel` can infer the child's Message
* type. Unbranded plain functions fail to type-check here.
* - `model`: the child's model, inferred from `view`'s first parameter.
* Compared by `===` when the boundary is wrapped in a memoizing
* helper such as `createKeyedLazy`.
* - `viewInputs`: optional second-argument data passed to `view`,
* inferred from `view`'s second parameter. Function values AT THE TOP
* LEVEL of `viewInputs` (slot callbacks like `toView`) are
* auto-wrapped to execute in the parent's boundary so handlers the
* consumer builds inside them dispatch through the parent's wrapping
* chain. Function values nested below the top level (e.g.
* `viewInputs: { config: { onSubmit } }`) throw at view-build time
* with a path-based error like `viewInputs.config.onSubmit`. The
* check is runtime-only (TypeScript cannot distinguish a
* user-declared nested callback from a data value whose prototype
* carries methods), so a misuse compiles cleanly and surfaces the
* first time the boundary renders. Keep slot callbacks at the top
* level of `viewInputs`.
* - `toParentMessage`: function that lifts a child message into the
* current boundary's Message type. The argument is typed as the
* child's Message via the view's brand, so destructuring is correctly
* typed without annotation. For per-instance identifiers, capture
* them in a closure
* (`(message) => GotEntryMessage({ entryId: entry.id, message })`).
*
* High-level events the parent handles declaratively flow through
* each Submodel's `OutMessage`. The parent's `GotChildMessage`
* handler unpacks the third tuple element of the child's `update`
* return and pattern-matches on `Option<OutMessage>`. See `Ui.Menu`,
* `Ui.Listbox`, etc., for examples.
*/
type Config = Readonly<{
model: ViewModelOf<View>
slotId: string
toParentMessage: (message: ViewMessageOf<View>) => unknown
view: View
viewInputs: ViewInputsOf<View>
}>/**
* Data-first / data-last signature for a `reflect*` setter built with
* `Function.dual`.
*
* A `reflect*` helper conforms a Submodel to a value that originated
* outside it (a URL, a server push, restored storage, a sibling field),
* without emitting an OutMessage. It is the inbound complement to
* OutMessage's outbound direction: the world is the source of truth, so
* the Submodel mirrors it silently and never announces the change back.
*
* Being dual, it reads two ways. Data-first sets the field and returns the
* model; data-last returns `(model) => model`, which slots point-free into
* an `evo` callback:
*
* ```ts
* // data-first
* const next = ColorListbox.reflectSelectedItem(model.colors, fromUrl)
* // data-last, point-free in evo
* evo(model, { colors: ColorListbox.reflectSelectedItem(fromUrl) })
* ```
*/
type Reflect = (model: Model, value: Value) => Model/**
* Two-argument variant of Reflect, for setters that resolve a
* value against a companion argument (e.g. `Tabs.reflectSelectedTab(value,
* options)`, which finds the value's index in `options`).
*/
type Reflect2 = (model: Model, a: A, b: B) => Model/**
* A view function branded with the Message type it dispatches. Build
* one with defineView:
*
* ```ts
* export const view = defineView<Counter.Model, Counter.Message>(
* (model) => h.button([h.OnClick(Increment())], ['+']),
* )
* ```
*
* When `ViewInputs` is provided, the view takes a second `viewInputs`
* argument:
*
* ```ts
* export const view = defineView<
* Checkbox.Model,
* Checkbox.Message,
* ViewInputs
* >((model, viewInputs) => viewInputs.toView({ checkbox: [...] }))
* ```
*
* Required at the `h.submodel` call site so unbranded plain functions
* fail to type-check there.
*/
type View = ViewInputs extends void
? (model: Model) => VNode | null
: (model: Model, viewInputs: ViewInputs) => VNode | null & {
__submodelMessage: Message
}