On this pageFunctions
Ui/RadioGroup
/**
* Pairs the radio group's `view` and `update` (and `select`) behind a
* single Value-typed entry point. Declaring the radio group once at
* module scope ensures the OutMessage's `value` field carries the
* consumer's union type without an `as` cast at the call site:
*
* ```ts
* const ToolRadioGroup = Ui.RadioGroup.create<Tool>()
*
* // In view:
* h.submodel({ view: ToolRadioGroup.view, ... })
*
* // In update:
* const [next, commands, maybeOutMessage] = ToolRadioGroup.update(model, message)
* // maybeOutMessage: Option<RadioGroup.OutMessage<Tool>>
* ```
*
* The view's `ViewInputs.options` stays typed `ReadonlyArray<string>`;
* consumers can pass a `ReadonlyArray<MyUnion>` (assignable) and the
* fenced cast inside `update` types the OutMessage's `value` as
* `MyUnion`.
*/
<Value extends string = string>(): Readonly<{
reflectSelectedValue: Reflect<Model, Option.Option<Value>>
select: (model: Model, value: Value, options: ReadonlyArray<Value>) => readonly [Model, ReadonlyArray<Command.Command<Message>>, Option.Option<OutMessage<Value>>]
update: (model: Model, message: Message) => readonly [Model, ReadonlyArray<Command.Command<Message>>, Option.Option<OutMessage<Value>>]
view: SubmodelView<Model, Message, ViewInputs<Value>>
}>/** Creates an initial radio group model from a config. Defaults to no selection and vertical orientation. */
(config: InitConfig): RadioGroup.Model/** Configuration for creating a radio group model with `init`. */
type InitConfig = Readonly<RadioGroup.Model>/**
* Per-option render info passed to the consumer's `toView`. The consumer
* spreads `option`, `label`, and `description` onto whichever elements
* carry that role in their layout. Generic over `Value extends string`:
* when `Ui.RadioGroup.create<MyUnion>()` is declared, `option.value` is
* typed `MyUnion` so the consumer can switch on it without casting.
*/
type OptionInfo = Readonly<{
description: ReadonlyArray<ChildAttribute>
index: number
isActive: boolean
isDisabled: boolean
isSelected: boolean
label: ReadonlyArray<ChildAttribute>
option: ReadonlyArray<ChildAttribute>
value: Value
}>/** Controls the radio group layout direction and which arrow keys navigate between options. */
type Orientation = Literals<readonly ["Horizontal", "Vertical"]>/**
* Generic over `Value extends string` so consumers who create the radio
* group via `Ui.RadioGroup.create<MyUnion>()` receive `value: MyUnion` in
* the `Selected` OutMessage from the factory's `update`, instead of
* `value: string`. Defaults to `string` for consumers that don't need the
* lift.
*/
type OutMessage = Selected<Value>/**
* Render-time payload published to the consumer's `toView`.
*
* - `group`: ARIA + role attributes for the wrapping radiogroup element.
* - `options`: one entry per option in `viewInputs.options`, in the same
* order. Includes the value, derived state, and the attribute bundles
* for the option element, its label, and its description.
* - `selectedValue`: the currently-selected value, if any. Convenient
* for the consumer when rendering selected-state visuals next to the
* option attributes.
* - `hiddenInput`: when `viewInputs.name` was supplied, attributes for a
* hidden form input carrying the selected value. The consumer
* renders the `<input>` themselves. Empty array when `name` is
* undefined.
*/
type RenderInfo = Readonly<{
group: ReadonlyArray<ChildAttribute>
hiddenInput: ReadonlyArray<ChildAttribute>
options: ReadonlyArray<OptionInfo<Value>>
selectedValue: Option.Option<Value>
}>/**
* Sent to the parent when an option is committed. Carries the selected
* value and its index. Generic over `Value extends string`: the runtime
* schema stores `value: string`, but the type-level OutMessage exposes
* `value: Value` so consumers who supply `options: ReadonlyArray<MyUnion>`
* receive `value: MyUnion` from the factory's `update` without casting at
* the call site. The cast is fenced inside this module's `update` return,
* sound because the value was selected from the options array the
* consumer supplied.
*/
type Selected = Readonly<{
_tag: "Selected"
index: number
value: Value
}>/**
* Per-render view inputs passed to `view` via `h.submodel`'s `viewInputs` field.
* Generic over `Value extends string` so consumers using
* `Ui.RadioGroup.create<MyUnion>()` receive `option.value: MyUnion` in
* `toView` and `(value: MyUnion, index) => boolean` in
* `isOptionDisabled`, without casting.
*/
type ViewInputs = Readonly<{
ariaLabel: string
isDisabled: boolean
isOptionDisabled: (value: Value, index: number) => boolean
name: string
options: ReadonlyArray<Value>
orientation: Orientation
toView: (render: RenderInfo<Value>) => Html
}>/** Sent when the focus-option command completes. */
const CompletedFocusOption: CallableTaggedStruct<"CompletedFocusOption", {}>/** Moves focus to the radio option at the given index. */
const FocusOption: CommandDefinitionWithArgs<"FocusOption", {
id: String
index: Number
}, Effect<{
_tag: "CompletedFocusOption"
}, never, never>>/** Union of all messages the radio group component can produce. */
const Message: S.Union<[typeof SelectedOption, typeof CompletedFocusOption]>/** Schema for the radio group component's state, tracking the selected value and orientation. */
const Model: Struct<{
id: String
orientation: Literals<readonly ["Horizontal", "Vertical"]>
selectedValue: Option<String>
}>const OutMessage: Union<readonly [
CallableTaggedStruct<"Selected", {
index: Number
value: String
}>
]>/**
* Sent to the parent when an option is committed. Carries the selected
* value and its index. Generic over `Value extends string`: the runtime
* schema stores `value: string`, but the type-level OutMessage exposes
* `value: Value` so consumers who supply `options: ReadonlyArray<MyUnion>`
* receive `value: MyUnion` from the factory's `update` without casting at
* the call site. The cast is fenced inside this module's `update` return,
* sound because the value was selected from the options array the
* consumer supplied.
*/
const Selected: CallableTaggedStruct<"Selected", {
index: Number
value: String
}>