On this pageFunctions
Ui/RadioGroup
/**
* Renders an accessible radio group as a stateless controlled component. The
* parent owns the selection (`selectedValue`) and receives the parent's own
* Message via `onSelect` when an option is chosen. Moving focus onto the
* newly-active option is the radio group's own concern: it happens inside the
* component's click and keydown handlers, so the parent's `update` only stores
* the value.
*
* ```ts
* // In view:
* RadioGroup.view<Tool, Message>({
* id: TOOL_RADIO_GROUP_ID,
* selectedValue: Option.some(model.tool),
* options: TOOLS,
* ariaLabel: 'Tool',
* onSelect: tool => SelectedTool({ tool }),
* toView: ({ group, options }) => ...,
* })
*
* // In update:
* SelectedTool: ({ tool }) => [evo(model, { tool: () => tool }), []],
* ```
*/
<Value extends string, ParentMessage>(config: ViewConfig<Value, ParentMessage>): Html/**
* 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` so
* `option.value` carries the consumer's union type, and over `ParentMessage`
* so the attribute bundles dispatch the parent's own Message.
*/
type OptionInfo = Readonly<{
description: ReadonlyArray<Attribute<ParentMessage>>
index: number
isActive: boolean
isDisabled: boolean
isSelected: boolean
label: ReadonlyArray<Attribute<ParentMessage>>
option: ReadonlyArray<Attribute<ParentMessage>>
value: 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 `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 `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<Attribute<ParentMessage>>
hiddenInput: ReadonlyArray<Attribute<ParentMessage>>
options: ReadonlyArray<OptionInfo<Value, ParentMessage>>
selectedValue: Option.Option<Value>
}>/**
* Per-render view configuration for the stateless controlled view.
* Generic over `Value extends string` (the option union) and `ParentMessage`
* (the message `onSelect` dispatches).
*
* - `selectedValue`: the current selection, read straight from the parent
* Model. The roving tabindex and checked state derive from it.
* - `onSelect`: dispatched with the chosen value when an option is clicked or
* navigated to. Handle it in the parent's `update` by storing the value.
* The radio group manages focus itself, so the handler needs to do nothing
* else.
* - `toView`: receives the RenderInfo and lays out the group.
*/
type ViewConfig = Readonly<{
ariaLabel: string
id: string
isDisabled: boolean
isOptionDisabled: (value: Value, index: number) => boolean
name: string
onSelect: (value: Value) => ParentMessage
options: ReadonlyArray<Value>
orientation: Orientation
selectedValue: Option.Option<Value>
toView: (render: RenderInfo<Value, ParentMessage>) => Html
}>