foldkit 0.101.0 → 0.102.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/README.md +2 -1
  2. package/dist/canvas/view.d.ts +1 -1
  3. package/dist/canvas/view.d.ts.map +1 -1
  4. package/dist/canvas/view.js +5 -5
  5. package/dist/command/index.d.ts +71 -0
  6. package/dist/command/index.d.ts.map +1 -1
  7. package/dist/command/index.js +34 -1
  8. package/dist/command/public.d.ts +1 -1
  9. package/dist/command/public.d.ts.map +1 -1
  10. package/dist/command/public.js +1 -1
  11. package/dist/devTools/overlay.d.ts.map +1 -1
  12. package/dist/devTools/overlay.js +137 -110
  13. package/dist/dom/dom.d.ts +8 -11
  14. package/dist/dom/dom.d.ts.map +1 -1
  15. package/dist/dom/dom.js +8 -11
  16. package/dist/dom/elementMovement.d.ts +1 -3
  17. package/dist/dom/elementMovement.d.ts.map +1 -1
  18. package/dist/dom/elementMovement.js +1 -3
  19. package/dist/dom/inert.d.ts +2 -4
  20. package/dist/dom/inert.d.ts.map +1 -1
  21. package/dist/dom/inert.js +2 -4
  22. package/dist/dom/scrollLock.d.ts +2 -2
  23. package/dist/dom/scrollLock.js +2 -2
  24. package/dist/dom/waitForAnimation.d.ts +1 -1
  25. package/dist/dom/waitForAnimation.js +1 -1
  26. package/dist/html/boundary.d.ts +98 -0
  27. package/dist/html/boundary.d.ts.map +1 -0
  28. package/dist/html/boundary.js +176 -0
  29. package/dist/html/childAttribute.d.ts +44 -0
  30. package/dist/html/childAttribute.d.ts.map +1 -0
  31. package/dist/html/childAttribute.js +34 -0
  32. package/dist/html/index.d.ts +70 -23
  33. package/dist/html/index.d.ts.map +1 -1
  34. package/dist/html/index.js +639 -575
  35. package/dist/html/lazy.d.ts +12 -7
  36. package/dist/html/lazy.d.ts.map +1 -1
  37. package/dist/html/lazy.js +30 -11
  38. package/dist/html/public.d.ts +2 -2
  39. package/dist/html/public.d.ts.map +1 -1
  40. package/dist/html/public.js +1 -1
  41. package/dist/html/runtimeSingleton.d.ts +72 -0
  42. package/dist/html/runtimeSingleton.d.ts.map +1 -0
  43. package/dist/html/runtimeSingleton.js +112 -0
  44. package/dist/html/submodel.d.ts +98 -0
  45. package/dist/html/submodel.d.ts.map +1 -0
  46. package/dist/html/submodel.js +190 -0
  47. package/dist/index.d.ts +1 -0
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +1 -0
  50. package/dist/render/render.d.ts +1 -1
  51. package/dist/render/render.js +1 -1
  52. package/dist/runtime/messagePriority.d.ts +5 -1
  53. package/dist/runtime/messagePriority.d.ts.map +1 -1
  54. package/dist/runtime/messagePriority.js +25 -4
  55. package/dist/runtime/runtime.d.ts +1 -1
  56. package/dist/runtime/runtime.d.ts.map +1 -1
  57. package/dist/runtime/runtime.js +115 -61
  58. package/dist/submodel/public.d.ts +4 -0
  59. package/dist/submodel/public.d.ts.map +1 -0
  60. package/dist/submodel/public.js +1 -0
  61. package/dist/submodel/submodel.d.ts +32 -0
  62. package/dist/submodel/submodel.d.ts.map +1 -0
  63. package/dist/submodel/submodel.js +1 -0
  64. package/dist/test/apps/disabledButton.d.ts +4 -5
  65. package/dist/test/apps/disabledButton.d.ts.map +1 -1
  66. package/dist/test/apps/disabledButton.js +16 -16
  67. package/dist/test/scene.d.ts +8 -8
  68. package/dist/test/scene.d.ts.map +1 -1
  69. package/dist/test/scene.js +25 -13
  70. package/dist/test/story.d.ts +15 -8
  71. package/dist/test/story.d.ts.map +1 -1
  72. package/dist/test/story.js +21 -9
  73. package/dist/ui/animation/index.d.ts +30 -14
  74. package/dist/ui/animation/index.d.ts.map +1 -1
  75. package/dist/ui/animation/index.js +9 -19
  76. package/dist/ui/animation/public.d.ts +2 -2
  77. package/dist/ui/animation/public.d.ts.map +1 -1
  78. package/dist/ui/animation/public.js +1 -1
  79. package/dist/ui/calendar/index.d.ts +199 -84
  80. package/dist/ui/calendar/index.d.ts.map +1 -1
  81. package/dist/ui/calendar/index.js +129 -140
  82. package/dist/ui/calendar/public.d.ts +2 -2
  83. package/dist/ui/calendar/public.d.ts.map +1 -1
  84. package/dist/ui/calendar/public.js +1 -1
  85. package/dist/ui/checkbox/index.d.ts +93 -21
  86. package/dist/ui/checkbox/index.d.ts.map +1 -1
  87. package/dist/ui/checkbox/index.js +62 -33
  88. package/dist/ui/checkbox/public.d.ts +2 -2
  89. package/dist/ui/checkbox/public.d.ts.map +1 -1
  90. package/dist/ui/checkbox/public.js +1 -1
  91. package/dist/ui/combobox/multi.d.ts +35 -91
  92. package/dist/ui/combobox/multi.d.ts.map +1 -1
  93. package/dist/ui/combobox/multi.js +34 -17
  94. package/dist/ui/combobox/multiPublic.d.ts +2 -2
  95. package/dist/ui/combobox/multiPublic.d.ts.map +1 -1
  96. package/dist/ui/combobox/multiPublic.js +1 -1
  97. package/dist/ui/combobox/public.d.ts +3 -3
  98. package/dist/ui/combobox/public.d.ts.map +1 -1
  99. package/dist/ui/combobox/public.js +2 -2
  100. package/dist/ui/combobox/shared.d.ts +56 -31
  101. package/dist/ui/combobox/shared.d.ts.map +1 -1
  102. package/dist/ui/combobox/shared.js +333 -322
  103. package/dist/ui/combobox/single.d.ts +46 -93
  104. package/dist/ui/combobox/single.d.ts.map +1 -1
  105. package/dist/ui/combobox/single.js +44 -17
  106. package/dist/ui/datePicker/index.d.ts +256 -48
  107. package/dist/ui/datePicker/index.d.ts.map +1 -1
  108. package/dist/ui/datePicker/index.js +149 -104
  109. package/dist/ui/datePicker/public.d.ts +2 -2
  110. package/dist/ui/datePicker/public.d.ts.map +1 -1
  111. package/dist/ui/datePicker/public.js +1 -1
  112. package/dist/ui/dialog/index.d.ts +95 -39
  113. package/dist/ui/dialog/index.d.ts.map +1 -1
  114. package/dist/ui/dialog/index.js +71 -62
  115. package/dist/ui/dialog/public.d.ts +2 -2
  116. package/dist/ui/dialog/public.d.ts.map +1 -1
  117. package/dist/ui/dialog/public.js +1 -1
  118. package/dist/ui/disclosure/index.d.ts +71 -31
  119. package/dist/ui/disclosure/index.d.ts.map +1 -1
  120. package/dist/ui/disclosure/index.js +57 -62
  121. package/dist/ui/disclosure/public.d.ts +2 -2
  122. package/dist/ui/disclosure/public.d.ts.map +1 -1
  123. package/dist/ui/disclosure/public.js +1 -1
  124. package/dist/ui/dragAndDrop/index.d.ts +6 -6
  125. package/dist/ui/dragAndDrop/index.d.ts.map +1 -1
  126. package/dist/ui/dragAndDrop/index.js +7 -7
  127. package/dist/ui/dragAndDrop/public.d.ts +1 -1
  128. package/dist/ui/dragAndDrop/public.d.ts.map +1 -1
  129. package/dist/ui/dragAndDrop/public.js +1 -1
  130. package/dist/ui/fileDrop/index.d.ts +42 -46
  131. package/dist/ui/fileDrop/index.d.ts.map +1 -1
  132. package/dist/ui/fileDrop/index.js +30 -46
  133. package/dist/ui/fileDrop/public.d.ts +2 -2
  134. package/dist/ui/fileDrop/public.d.ts.map +1 -1
  135. package/dist/ui/fileDrop/public.js +1 -1
  136. package/dist/ui/listbox/multi.d.ts +39 -84
  137. package/dist/ui/listbox/multi.d.ts.map +1 -1
  138. package/dist/ui/listbox/multi.js +38 -20
  139. package/dist/ui/listbox/multiPublic.d.ts +2 -2
  140. package/dist/ui/listbox/multiPublic.d.ts.map +1 -1
  141. package/dist/ui/listbox/multiPublic.js +1 -1
  142. package/dist/ui/listbox/public.d.ts +3 -3
  143. package/dist/ui/listbox/public.d.ts.map +1 -1
  144. package/dist/ui/listbox/public.js +2 -2
  145. package/dist/ui/listbox/shared.d.ts +71 -30
  146. package/dist/ui/listbox/shared.d.ts.map +1 -1
  147. package/dist/ui/listbox/shared.js +319 -296
  148. package/dist/ui/listbox/single.d.ts +57 -85
  149. package/dist/ui/listbox/single.d.ts.map +1 -1
  150. package/dist/ui/listbox/single.js +48 -24
  151. package/dist/ui/menu/index.d.ts +80 -36
  152. package/dist/ui/menu/index.d.ts.map +1 -1
  153. package/dist/ui/menu/index.js +117 -86
  154. package/dist/ui/menu/public.d.ts +2 -2
  155. package/dist/ui/menu/public.d.ts.map +1 -1
  156. package/dist/ui/menu/public.js +1 -1
  157. package/dist/ui/popover/index.d.ts +117 -44
  158. package/dist/ui/popover/index.d.ts.map +1 -1
  159. package/dist/ui/popover/index.js +88 -101
  160. package/dist/ui/popover/public.d.ts +2 -2
  161. package/dist/ui/popover/public.d.ts.map +1 -1
  162. package/dist/ui/popover/public.js +1 -1
  163. package/dist/ui/radioGroup/index.d.ts +122 -45
  164. package/dist/ui/radioGroup/index.d.ts.map +1 -1
  165. package/dist/ui/radioGroup/index.js +111 -72
  166. package/dist/ui/radioGroup/public.d.ts +2 -2
  167. package/dist/ui/radioGroup/public.d.ts.map +1 -1
  168. package/dist/ui/radioGroup/public.js +1 -1
  169. package/dist/ui/slider/index.d.ts +72 -34
  170. package/dist/ui/slider/index.d.ts.map +1 -1
  171. package/dist/ui/slider/index.js +40 -49
  172. package/dist/ui/slider/public.d.ts +2 -2
  173. package/dist/ui/slider/public.d.ts.map +1 -1
  174. package/dist/ui/slider/public.js +1 -1
  175. package/dist/ui/switch/index.d.ts +74 -21
  176. package/dist/ui/switch/index.d.ts.map +1 -1
  177. package/dist/ui/switch/index.js +62 -33
  178. package/dist/ui/switch/public.d.ts +2 -2
  179. package/dist/ui/switch/public.d.ts.map +1 -1
  180. package/dist/ui/switch/public.js +1 -1
  181. package/dist/ui/tabs/index.d.ts +107 -45
  182. package/dist/ui/tabs/index.d.ts.map +1 -1
  183. package/dist/ui/tabs/index.js +99 -81
  184. package/dist/ui/tabs/public.d.ts +2 -2
  185. package/dist/ui/tabs/public.d.ts.map +1 -1
  186. package/dist/ui/tabs/public.js +1 -1
  187. package/dist/ui/toast/index.d.ts +93 -109
  188. package/dist/ui/toast/index.d.ts.map +1 -1
  189. package/dist/ui/toast/index.js +16 -29
  190. package/dist/ui/toast/schema.d.ts +15 -4
  191. package/dist/ui/toast/schema.d.ts.map +1 -1
  192. package/dist/ui/toast/schema.js +11 -4
  193. package/dist/ui/toast/update.d.ts +36 -18
  194. package/dist/ui/toast/update.d.ts.map +1 -1
  195. package/dist/ui/toast/update.js +33 -14
  196. package/dist/ui/tooltip/index.d.ts +94 -42
  197. package/dist/ui/tooltip/index.d.ts.map +1 -1
  198. package/dist/ui/tooltip/index.js +64 -73
  199. package/dist/ui/tooltip/public.d.ts +2 -2
  200. package/dist/ui/tooltip/public.d.ts.map +1 -1
  201. package/dist/ui/tooltip/public.js +1 -1
  202. package/dist/ui/virtualList/index.d.ts +18 -41
  203. package/dist/ui/virtualList/index.d.ts.map +1 -1
  204. package/dist/ui/virtualList/index.js +17 -37
  205. package/dist/ui/virtualList/public.d.ts +2 -2
  206. package/dist/ui/virtualList/public.d.ts.map +1 -1
  207. package/dist/ui/virtualList/public.js +1 -1
  208. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
- import { Effect, Schema as S } from 'effect';
1
+ import { Effect, Option, Schema as S } from 'effect';
2
2
  import * as Command from '../../command/index.js';
3
- import { type Attribute, type Html } from '../../html/index.js';
3
+ import { type ChildAttribute, type Html, type SubmodelView } from '../../html/index.js';
4
+ import type { Reflect } from '../../submodel/submodel.js';
4
5
  /** Controls the radio group layout direction and which arrow keys navigate between options. */
5
6
  export declare const Orientation: S.Literals<readonly ["Horizontal", "Vertical"]>;
6
7
  export type Orientation = typeof Orientation.Type;
@@ -26,6 +27,33 @@ export declare const Message: S.Union<[
26
27
  export type SelectedOption = typeof SelectedOption.Type;
27
28
  export type CompletedFocusOption = typeof CompletedFocusOption.Type;
28
29
  export type Message = typeof Message.Type;
30
+ /** Sent to the parent when an option is committed. Carries the selected
31
+ * value and its index. Generic over `Value extends string`: the runtime
32
+ * schema stores `value: string`, but the type-level OutMessage exposes
33
+ * `value: Value` so consumers who supply `options: ReadonlyArray<MyUnion>`
34
+ * receive `value: MyUnion` from the factory's `update` without casting at
35
+ * the call site. The cast is fenced inside this module's `update` return,
36
+ * sound because the value was selected from the options array the
37
+ * consumer supplied. */
38
+ export declare const Selected: import("../../schema/index.js").CallableTaggedStruct<"Selected", {
39
+ value: S.String;
40
+ index: S.Number;
41
+ }>;
42
+ export type Selected<Value extends string = string> = Readonly<{
43
+ readonly _tag: 'Selected';
44
+ readonly value: Value;
45
+ readonly index: number;
46
+ }>;
47
+ export declare const OutMessage: S.Union<readonly [import("../../schema/index.js").CallableTaggedStruct<"Selected", {
48
+ value: S.String;
49
+ index: S.Number;
50
+ }>]>;
51
+ /** Generic over `Value extends string` so consumers who create the radio
52
+ * group via `Ui.RadioGroup.create<MyUnion>()` receive `value: MyUnion` in
53
+ * the `Selected` OutMessage from the factory's `update`, instead of
54
+ * `value: string`. Defaults to `string` for consumers that don't need the
55
+ * lift. */
56
+ export type OutMessage<Value extends string = string> = Selected<Value>;
29
57
  /** Configuration for creating a radio group model with `init`. */
30
58
  export type InitConfig = Readonly<{
31
59
  id: string;
@@ -41,52 +69,101 @@ export declare const FocusOption: Command.CommandDefinitionWithArgs<"FocusOption
41
69
  }, Effect.Effect<{
42
70
  readonly _tag: "CompletedFocusOption";
43
71
  }, never, never>>;
44
- /** Processes a radio group message and returns the next model and commands. */
45
- export declare const update: (model: Model, message: Message) => readonly [Model, ReadonlyArray<Command.Command<Message>>];
46
- /** Programmatically selects a value in the radio group, updating the model and returning
47
- * focus commands. Use this in domain-event handlers when the radio group uses `onSelected`. */
48
- export declare const select: (model: Model, value: string, options: ReadonlyArray<string>) => readonly [Model, ReadonlyArray<Command.Command<Message>>];
49
- /** Attribute groups the radio group component provides for each option's `content` callback. */
50
- export type OptionAttributes<ParentMessage> = Readonly<{
51
- option: ReadonlyArray<Attribute<ParentMessage>>;
52
- label: ReadonlyArray<Attribute<ParentMessage>>;
53
- description: ReadonlyArray<Attribute<ParentMessage>>;
72
+ type UpdateReturn<Value extends string = string> = readonly [
73
+ Model,
74
+ ReadonlyArray<Command.Command<Message>>,
75
+ Option.Option<OutMessage<Value>>
76
+ ];
77
+ /** Processes a radio group message and returns the next model, commands, and
78
+ * optional OutMessage. Generic over `Value extends string`: pass the consumer's
79
+ * union type at the call site to receive `Selected({ value: MyUnion })` without
80
+ * casting. Defaults to `string`. */
81
+ export declare const update: <Value extends string = string>(model: Model, message: Message) => UpdateReturn<Value>;
82
+ /** Programmatically selects a value in the radio group, updating the model
83
+ * and returning focus commands plus a `Selected` OutMessage. */
84
+ export declare const select: <Value extends string = string>(model: Model, value: Value, options: ReadonlyArray<Value>) => UpdateReturn<Value>;
85
+ /** Reflects an externally-sourced selection onto the model without
86
+ * emitting an OutMessage or running the focus command. Use this to mirror
87
+ * external truth (a URL parameter, restored storage, a server push) onto
88
+ * the radio group's selection. Contrast with `select`, which represents a
89
+ * user or programmatic *choice*: it focuses the option and emits
90
+ * `Selected`. Takes no `options` (unlike `select`) because it sets the
91
+ * value directly rather than deriving a focus index. Returns the model
92
+ * directly because it produces no commands and no OutMessage. */
93
+ export declare const reflectSelectedValue: Reflect<Model, Option.Option<string>>;
94
+ /** Per-option render info passed to the consumer's `toView`. The consumer
95
+ * spreads `option`, `label`, and `description` onto whichever elements
96
+ * carry that role in their layout. Generic over `Value extends string`:
97
+ * when `Ui.RadioGroup.create<MyUnion>()` is declared, `option.value` is
98
+ * typed `MyUnion` so the consumer can switch on it without casting. */
99
+ export type OptionInfo<Value extends string = string> = Readonly<{
100
+ value: Value;
101
+ index: number;
102
+ isSelected: boolean;
103
+ isActive: boolean;
104
+ isDisabled: boolean;
105
+ option: ReadonlyArray<ChildAttribute>;
106
+ label: ReadonlyArray<ChildAttribute>;
107
+ description: ReadonlyArray<ChildAttribute>;
54
108
  }>;
55
- /** Configuration for an individual radio option. The `value` field carries the generic `RadioOption` type
56
- * so it flows through to `toParentMessage` callbacks without widening to `string`. */
57
- export type OptionConfig<ParentMessage, RadioOption extends string = string> = Readonly<{
58
- value: RadioOption;
59
- content: (attributes: OptionAttributes<ParentMessage>) => Html;
109
+ /** Render-time payload published to the consumer's `toView`.
110
+ *
111
+ * - `group`: ARIA + role attributes for the wrapping radiogroup element.
112
+ * - `options`: one entry per option in `viewInputs.options`, in the same
113
+ * order. Includes the value, derived state, and the attribute bundles
114
+ * for the option element, its label, and its description.
115
+ * - `selectedValue`: the currently-selected value, if any. Convenient
116
+ * for the consumer when rendering selected-state visuals next to the
117
+ * option attributes.
118
+ * - `hiddenInput`: when `viewInputs.name` was supplied, attributes for a
119
+ * hidden form input carrying the selected value. The consumer
120
+ * renders the `<input>` themselves. Empty array when `name` is
121
+ * undefined. */
122
+ export type RenderInfo<Value extends string = string> = Readonly<{
123
+ group: ReadonlyArray<ChildAttribute>;
124
+ options: ReadonlyArray<OptionInfo<Value>>;
125
+ selectedValue: Option.Option<Value>;
126
+ hiddenInput: ReadonlyArray<ChildAttribute>;
60
127
  }>;
61
- /** The `SelectedOption` message as seen by `toParentMessage` callbacks, with `value` narrowed
62
- * to the generic `RadioOption` type instead of `string`. */
63
- export type NarrowedSelectedOption<RadioOption extends string> = Readonly<{
64
- readonly _tag: 'SelectedOption';
65
- readonly value: RadioOption;
66
- readonly index: number;
67
- }>;
68
- /** Configuration for rendering a radio group with `view`. */
69
- export type ViewConfig<ParentMessage, RadioOption extends string> = Readonly<{
70
- model: Model;
71
- toParentMessage: (message: NarrowedSelectedOption<RadioOption> | CompletedFocusOption) => ParentMessage;
72
- onSelected?: (value: RadioOption, index: number) => ParentMessage;
73
- options: ReadonlyArray<RadioOption>;
74
- optionToConfig: (option: RadioOption, context: Readonly<{
75
- isSelected: boolean;
76
- isActive: boolean;
77
- isDisabled: boolean;
78
- }>) => OptionConfig<ParentMessage, RadioOption>;
79
- isOptionDisabled?: (option: RadioOption, index: number) => boolean;
80
- orientation?: Orientation;
128
+ /** Per-render view inputs passed to `view` via `h.submodel`'s `viewInputs` field.
129
+ * Generic over `Value extends string` so consumers using
130
+ * `Ui.RadioGroup.create<MyUnion>()` receive `option.value: MyUnion` in
131
+ * `toView` and `(value: MyUnion, index) => boolean` in
132
+ * `isOptionDisabled`, without casting. */
133
+ export type ViewInputs<Value extends string = string> = Readonly<{
134
+ options: ReadonlyArray<Value>;
81
135
  ariaLabel: string;
82
- className?: string;
83
- attributes?: ReadonlyArray<Attribute<ParentMessage>>;
84
- name?: string;
136
+ toView: (render: RenderInfo<Value>) => Html;
137
+ isOptionDisabled?: (value: Value, index: number) => boolean;
85
138
  isDisabled?: boolean;
139
+ name?: string;
140
+ orientation?: Orientation;
141
+ }>;
142
+ /** Pairs the radio group's `view` and `update` (and `select`) behind a
143
+ * single Value-typed entry point. Declaring the radio group once at
144
+ * module scope ensures the OutMessage's `value` field carries the
145
+ * consumer's union type without an `as` cast at the call site:
146
+ *
147
+ * ```ts
148
+ * const ToolRadioGroup = Ui.RadioGroup.create<Tool>()
149
+ *
150
+ * // In view:
151
+ * h.submodel({ view: ToolRadioGroup.view, ... })
152
+ *
153
+ * // In update:
154
+ * const [next, commands, maybeOutMessage] = ToolRadioGroup.update(model, message)
155
+ * // maybeOutMessage: Option<RadioGroup.OutMessage<Tool>>
156
+ * ```
157
+ *
158
+ * The view's `ViewInputs.options` stays typed `ReadonlyArray<string>`;
159
+ * consumers can pass a `ReadonlyArray<MyUnion>` (assignable) and the
160
+ * fenced cast inside `update` types the OutMessage's `value` as
161
+ * `MyUnion`. */
162
+ export declare const create: <Value extends string = string>() => Readonly<{
163
+ view: SubmodelView<Model, Message, ViewInputs<Value>>;
164
+ update: (model: Model, message: Message) => readonly [Model, ReadonlyArray<Command.Command<Message>>, Option.Option<OutMessage<Value>>];
165
+ select: (model: Model, value: Value, options: ReadonlyArray<Value>) => readonly [Model, ReadonlyArray<Command.Command<Message>>, Option.Option<OutMessage<Value>>];
166
+ reflectSelectedValue: Reflect<Model, Option.Option<Value>>;
86
167
  }>;
87
- /** Renders an accessible radio group by building ARIA attribute groups and delegating layout to the consumer's `optionToConfig` callback. */
88
- export declare const view: <ParentMessage, RadioOption extends string>(config: ViewConfig<ParentMessage, RadioOption>) => Html;
89
- /** Creates a memoized radio group view. Static config is captured in a closure;
90
- * only `model` and `toParentMessage` are compared per render via `createLazy`. */
91
- export declare const lazy: <ParentMessage, RadioOption extends string>(staticConfig: Omit<ViewConfig<ParentMessage, RadioOption>, "model" | "toParentMessage" | "onSelected">) => ((model: Model, toParentMessage: ViewConfig<ParentMessage, RadioOption>["toParentMessage"]) => Html);
168
+ export {};
92
169
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/radioGroup/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/radioGroup/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,MAAM,EAGN,MAAM,EAEN,MAAM,IAAI,CAAC,EAGZ,MAAM,QAAQ,CAAA;AAEf,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAA;AAEjD,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,IAAI,EACT,KAAK,YAAY,EAIlB,MAAM,qBAAqB,CAAA;AAG5B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAA;AAKzD,+FAA+F;AAC/F,eAAO,MAAM,WAAW,iDAAyC,CAAA;AACjE,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAC,IAAI,CAAA;AAEjD,iGAAiG;AACjG,eAAO,MAAM,KAAK;;;;EAIhB,CAAA;AAEF,MAAM,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,IAAI,CAAA;AAIrC,6EAA6E;AAC7E,eAAO,MAAM,cAAc;;;EAGzB,CAAA;AACF,oDAAoD;AACpD,eAAO,MAAM,oBAAoB,kFAA4B,CAAA;AAE7D,mEAAmE;AACnE,eAAO,MAAM,OAAO,EAAE,CAAC,CAAC,KAAK,CAC3B;IAAC,OAAO,cAAc;IAAE,OAAO,oBAAoB;CAAC,CACH,CAAA;AAEnD,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAA;AACvD,MAAM,MAAM,oBAAoB,GAAG,OAAO,oBAAoB,CAAC,IAAI,CAAA;AAEnE,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAA;AAIzC;;;;;;;yBAOyB;AACzB,eAAO,MAAM,QAAQ;;;EAAsD,CAAA;AAE3E,MAAM,MAAM,QAAQ,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,CAAC;IAC7D,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB,CAAC,CAAA;AAEF,eAAO,MAAM,UAAU;;;IAAsB,CAAA;AAE7C;;;;YAIY;AACZ,MAAM,MAAM,UAAU,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAA;AAIvE,kEAAkE;AAClE,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,WAAW,CAAC,EAAE,WAAW,CAAA;CAC1B,CAAC,CAAA;AAEF,6GAA6G;AAC7G,eAAO,MAAM,IAAI,GAAI,QAAQ,UAAU,KAAG,KAIxC,CAAA;AAMF,0DAA0D;AAC1D,eAAO,MAAM,WAAW;;;;;iBASvB,CAAA;AAED,KAAK,YAAY,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,IAAI,SAAS;IAC1D,KAAK;IACL,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACvC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;CACjC,CAAA;AAMD;;;qCAGqC;AACrC,eAAO,MAAM,MAAM,GAAI,KAAK,SAAS,MAAM,GAAG,MAAM,EAClD,OAAO,KAAK,EACZ,SAAS,OAAO,KACf,YAAY,CAAC,KAAK,CAcpB,CAAA;AAED;iEACiE;AACjE,eAAO,MAAM,MAAM,GAAI,KAAK,SAAS,MAAM,GAAG,MAAM,EAClD,OAAO,KAAK,EACZ,OAAO,KAAK,EACZ,SAAS,aAAa,CAAC,KAAK,CAAC,KAC5B,YAAY,CAAC,KAAK,CAQlB,CAAA;AAEH;;;;;;;kEAOkE;AAClE,eAAO,MAAM,oBAAoB,EAAE,OAAO,CACxC,KAAK,EACL,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAKtB,CAAA;AAID;;;;wEAIwE;AACxE,MAAM,MAAM,UAAU,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,CAAC;IAC/D,KAAK,EAAE,KAAK,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,OAAO,CAAA;IACnB,QAAQ,EAAE,OAAO,CAAA;IACjB,UAAU,EAAE,OAAO,CAAA;IACnB,MAAM,EAAE,aAAa,CAAC,cAAc,CAAC,CAAA;IACrC,KAAK,EAAE,aAAa,CAAC,cAAc,CAAC,CAAA;IACpC,WAAW,EAAE,aAAa,CAAC,cAAc,CAAC,CAAA;CAC3C,CAAC,CAAA;AAEF;;;;;;;;;;;;mBAYmB;AACnB,MAAM,MAAM,UAAU,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,CAAC;IAC/D,KAAK,EAAE,aAAa,CAAC,cAAc,CAAC,CAAA;IACpC,OAAO,EAAE,aAAa,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAA;IACzC,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACnC,WAAW,EAAE,aAAa,CAAC,cAAc,CAAC,CAAA;CAC3C,CAAC,CAAA;AAEF;;;;2CAI2C;AAC3C,MAAM,MAAM,UAAU,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,CAAC;IAC/D,OAAO,EAAE,aAAa,CAAC,KAAK,CAAC,CAAA;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,KAAK,IAAI,CAAA;IAC3C,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAA;IAC3D,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,WAAW,CAAA;CAC1B,CAAC,CAAA;AA4LF;;;;;;;;;;;;;;;;;;;iBAmBiB;AACjB,eAAO,MAAM,MAAM,GAAI,KAAK,SAAS,MAAM,GAAG,MAAM,OAAK,QAAQ,CAAC;IAChE,IAAI,EAAE,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,CAAA;IACrD,MAAM,EAAE,CACN,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,OAAO,KACb,SAAS,CACZ,KAAK,EACL,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,EACvC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CACjC,CAAA;IACD,MAAM,EAAE,CACN,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,aAAa,CAAC,KAAK,CAAC,KAC1B,SAAS,CACZ,KAAK,EACL,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,EACvC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CACjC,CAAA;IACD,oBAAoB,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;CAC3D,CAcC,CAAA"}
@@ -1,7 +1,7 @@
1
- import { Array, Effect, Match as M, Option, Predicate, Schema as S, String, pipe, } from 'effect';
1
+ import { Array, Effect, Function, Match as M, Option, Predicate, Schema as S, String, pipe, } from 'effect';
2
2
  import * as Command from '../../command/index.js';
3
3
  import * as Dom from '../../dom/index.js';
4
- import { createLazy, html, } from '../../html/index.js';
4
+ import { childAttributes, defineView, html, } from '../../html/index.js';
5
5
  import { m } from '../../message/index.js';
6
6
  import { evo } from '../../struct/index.js';
7
7
  import { keyToIndex } from '../keyboard.js';
@@ -24,6 +24,17 @@ export const SelectedOption = m('SelectedOption', {
24
24
  export const CompletedFocusOption = m('CompletedFocusOption');
25
25
  /** Union of all messages the radio group component can produce. */
26
26
  export const Message = S.Union([SelectedOption, CompletedFocusOption]);
27
+ // OUT MESSAGE
28
+ /** Sent to the parent when an option is committed. Carries the selected
29
+ * value and its index. Generic over `Value extends string`: the runtime
30
+ * schema stores `value: string`, but the type-level OutMessage exposes
31
+ * `value: Value` so consumers who supply `options: ReadonlyArray<MyUnion>`
32
+ * receive `value: MyUnion` from the factory's `update` without casting at
33
+ * the call site. The cast is fenced inside this module's `update` return,
34
+ * sound because the value was selected from the options array the
35
+ * consumer supplied. */
36
+ export const Selected = m('Selected', { value: S.String, index: S.Number });
37
+ export const OutMessage = S.Union([Selected]);
27
38
  /** Creates an initial radio group model from a config. Defaults to no selection and vertical orientation. */
28
39
  export const init = (config) => ({
29
40
  id: config.id,
@@ -34,29 +45,44 @@ export const init = (config) => ({
34
45
  const optionId = (id, index) => `${id}-option-${index}`;
35
46
  /** Moves focus to the radio option at the given index. */
36
47
  export const FocusOption = Command.define('FocusOption', { id: S.String, index: S.Number }, CompletedFocusOption)(({ id, index }) => Dom.focus(`#${optionId(id, index)}`).pipe(Effect.ignore, Effect.as(CompletedFocusOption())));
37
- /** Processes a radio group message and returns the next model and commands. */
38
- export const update = (model, message) => M.value(message).pipe(M.withReturnType(), M.tagsExhaustive({
39
- SelectedOption: ({ value, index }) => [
40
- evo(model, { selectedValue: () => Option.some(value) }),
41
- [FocusOption({ id: model.id, index })],
42
- ],
43
- CompletedFocusOption: () => [model, []],
44
- }));
45
- /** Programmatically selects a value in the radio group, updating the model and returning
46
- * focus commands. Use this in domain-event handlers when the radio group uses `onSelected`. */
48
+ const withInternalUpdateReturn = M.withReturnType();
49
+ /** Processes a radio group message and returns the next model, commands, and
50
+ * optional OutMessage. Generic over `Value extends string`: pass the consumer's
51
+ * union type at the call site to receive `Selected({ value: MyUnion })` without
52
+ * casting. Defaults to `string`. */
53
+ export const update = (model, message) => {
54
+ const result = M.value(message).pipe(withInternalUpdateReturn, M.tagsExhaustive({
55
+ SelectedOption: ({ value, index }) => [
56
+ evo(model, { selectedValue: () => Option.some(value) }),
57
+ [FocusOption({ id: model.id, index })],
58
+ Option.some(Selected({ value, index })),
59
+ ],
60
+ CompletedFocusOption: () => [model, [], Option.none()],
61
+ }));
62
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
63
+ return result;
64
+ };
65
+ /** Programmatically selects a value in the radio group, updating the model
66
+ * and returning focus commands plus a `Selected` OutMessage. */
47
67
  export const select = (model, value, options) => pipe(options, Array.findFirstIndex(option => option === value), Option.match({
48
- onNone: () => [model, []],
68
+ onNone: () => [model, [], Option.none()],
49
69
  onSome: index => update(model, SelectedOption({ value, index })),
50
70
  }));
71
+ /** Reflects an externally-sourced selection onto the model without
72
+ * emitting an OutMessage or running the focus command. Use this to mirror
73
+ * external truth (a URL parameter, restored storage, a server push) onto
74
+ * the radio group's selection. Contrast with `select`, which represents a
75
+ * user or programmatic *choice*: it focuses the option and emits
76
+ * `Selected`. Takes no `options` (unlike `select`) because it sets the
77
+ * value directly rather than deriving a focus index. Returns the model
78
+ * directly because it produces no commands and no OutMessage. */
79
+ export const reflectSelectedValue = Function.dual(2, (model, maybeValue) => evo(model, { selectedValue: () => maybeValue }));
51
80
  const labelId = (id, index) => `${id}-option-${index}-label`;
52
81
  const descriptionId = (id, index) => `${id}-option-${index}-description`;
53
- /** Renders an accessible radio group by building ARIA attribute groups and delegating layout to the consumer's `optionToConfig` callback. */
54
- export const view = (config) => {
82
+ const internalView = defineView((model, viewInputs) => {
55
83
  const h = html();
56
- const { model, model: { id, selectedValue }, toParentMessage, onSelected, options, optionToConfig, isOptionDisabled: isOptionDisabledFn, orientation = model.orientation, ariaLabel, className, attributes = [], name, isDisabled: isGroupDisabled = false, } = config;
57
- const dispatchSelected = (value, index) => onSelected
58
- ? onSelected(value, index)
59
- : toParentMessage({ _tag: 'SelectedOption', value, index });
84
+ const { id, selectedValue } = model;
85
+ const { options, ariaLabel, toView, isOptionDisabled: isOptionDisabledFn, isDisabled: isGroupDisabled = false, name, orientation = model.orientation, } = viewInputs;
60
86
  const isDisabled = (index) => {
61
87
  if (isGroupDisabled) {
62
88
  return true;
@@ -66,11 +92,7 @@ export const view = (config) => {
66
92
  }
67
93
  return pipe(options, Array.get(index), Option.exists(option => isOptionDisabledFn(option, index)));
68
94
  };
69
- const selectedIndex = Option.flatMap(selectedValue, value => Array.findFirstIndex(options, option => optionToConfig(option, {
70
- isSelected: false,
71
- isActive: false,
72
- isDisabled: false,
73
- }).value === value));
95
+ const selectedIndex = Option.flatMap(selectedValue, value => Array.findFirstIndex(options, option => option === value));
74
96
  const focusedIndex = pipe(selectedIndex, Option.getOrElse(() => pipe(options.length, Array.makeBy(index => index), Array.findFirst(Predicate.not(isDisabled)), Option.getOrElse(() => 0))));
75
97
  const { nextKey, previousKey } = M.value(orientation).pipe(M.when('Horizontal', () => ({
76
98
  nextKey: 'ArrowRight',
@@ -79,28 +101,22 @@ export const view = (config) => {
79
101
  nextKey: 'ArrowDown',
80
102
  previousKey: 'ArrowUp',
81
103
  })), M.exhaustive);
82
- const optionValues = Array.map(options, (option, index) => optionToConfig(option, {
83
- isSelected: Option.exists(selectedIndex, selectedIdx => selectedIdx === index),
84
- isActive: index === focusedIndex,
85
- isDisabled: isDisabled(index),
86
- }).value);
87
104
  const resolveKeyIndex = keyToIndex(nextKey, previousKey, options.length, focusedIndex, isDisabled);
88
105
  const handleKeyDown = (currentIndex) => (key) => M.value(key).pipe(M.whenOr(nextKey, previousKey, 'Home', 'End', 'PageUp', 'PageDown', () => {
89
106
  const nextIndex = resolveKeyIndex(key);
90
- return pipe(optionValues, Array.get(nextIndex), Option.map(value => dispatchSelected(value, nextIndex)));
91
- }), M.when(' ', () => pipe(optionValues, Array.get(currentIndex), Option.map(value => dispatchSelected(value, currentIndex)))), M.orElse(() => Option.none()));
92
- const renderedOptions = Array.map(options, (option, index) => {
107
+ return pipe(options, Array.get(nextIndex), Option.map(value => SelectedOption({ value, index: nextIndex })));
108
+ }), M.when(' ', () => pipe(options, Array.get(currentIndex), Option.map(value => SelectedOption({ value, index: currentIndex })))), M.orElse(() => Option.none()));
109
+ const optionInfos = Array.map(options, (value, index) => {
93
110
  const isSelected = Option.exists(selectedIndex, selectedIdx => selectedIdx === index);
94
111
  const isFocusable = index === focusedIndex;
95
- const isOptionDisabled = isDisabled(index);
96
- const optionConfig = optionToConfig(option, {
97
- isSelected,
98
- isActive: isFocusable,
99
- isDisabled: isOptionDisabled,
100
- });
101
- const checkedAttributes = isSelected ? [h.DataAttribute('checked', '')] : [];
102
- const activeAttributes = isFocusable ? [h.DataAttribute('active', '')] : [];
103
- const disabledAttributes = isOptionDisabled
112
+ const isOptionDisabledNow = isDisabled(index);
113
+ const checkedAttributes = isSelected
114
+ ? [h.DataAttribute('checked', '')]
115
+ : [];
116
+ const activeAttributes = isFocusable
117
+ ? [h.DataAttribute('active', '')]
118
+ : [];
119
+ const disabledAttributes = isOptionDisabledNow
104
120
  ? [h.AriaDisabled(true), h.DataAttribute('disabled', '')]
105
121
  : [];
106
122
  const optionAttributes = [
@@ -113,45 +129,68 @@ export const view = (config) => {
113
129
  ...checkedAttributes,
114
130
  ...activeAttributes,
115
131
  ...disabledAttributes,
116
- ...(isOptionDisabled
132
+ ...(isOptionDisabledNow
117
133
  ? []
118
134
  : [
119
- h.OnClick(dispatchSelected(optionConfig.value, index)),
135
+ h.OnClick(SelectedOption({ value, index })),
120
136
  h.OnKeyDownPreventDefault(handleKeyDown(index)),
121
137
  ]),
122
138
  ];
123
- const labelAttributes = [
124
- h.Id(labelId(id, index)),
125
- ];
126
- const descriptionAttributes = [
127
- h.Id(descriptionId(id, index)),
128
- ];
129
- return optionConfig.content({
130
- option: optionAttributes,
131
- label: labelAttributes,
132
- description: descriptionAttributes,
133
- });
139
+ const labelAttributes = [h.Id(labelId(id, index))];
140
+ const descriptionAttributes = [h.Id(descriptionId(id, index))];
141
+ return {
142
+ value,
143
+ index,
144
+ isSelected,
145
+ isActive: isFocusable,
146
+ isDisabled: isOptionDisabledNow,
147
+ option: childAttributes(optionAttributes),
148
+ label: childAttributes(labelAttributes),
149
+ description: childAttributes(descriptionAttributes),
150
+ };
134
151
  });
135
- const hiddenInputs = pipe(name, Option.fromNullishOr, Option.flatMap(inputName => pipe(selectedValue, Option.map(value => h.input([h.Type('hidden'), h.Name(inputName), h.Value(value)])))), Option.match({
136
- onNone: () => [],
137
- onSome: hiddenInput => [hiddenInput],
138
- }));
139
152
  const groupAttributes = [
140
153
  h.Role('radiogroup'),
141
154
  h.AriaOrientation(String.toLowerCase(orientation)),
142
155
  h.AriaLabel(ariaLabel),
143
- ...(className ? [h.Class(className)] : []),
144
- ...attributes,
145
156
  ];
146
- return h.div(groupAttributes, [...renderedOptions, ...hiddenInputs]);
147
- };
148
- /** Creates a memoized radio group view. Static config is captured in a closure;
149
- * only `model` and `toParentMessage` are compared per render via `createLazy`. */
150
- export const lazy = (staticConfig) => {
151
- const lazyView = createLazy();
152
- return (model, toParentMessage) => lazyView((currentModel, currentToParentMessage) => view({
153
- ...staticConfig,
154
- model: currentModel,
155
- toParentMessage: currentToParentMessage,
156
- }), [model, toParentMessage]);
157
- };
157
+ const hiddenInputAttributes = pipe(Option.fromNullishOr(name), Option.flatMap(inputName => Option.map(selectedValue, value => [
158
+ h.Type('hidden'),
159
+ h.Name(inputName),
160
+ h.Value(value),
161
+ ])), Option.getOrElse(() => []));
162
+ return toView({
163
+ group: childAttributes(groupAttributes),
164
+ options: optionInfos,
165
+ selectedValue,
166
+ hiddenInput: childAttributes(hiddenInputAttributes),
167
+ });
168
+ });
169
+ /** Pairs the radio group's `view` and `update` (and `select`) behind a
170
+ * single Value-typed entry point. Declaring the radio group once at
171
+ * module scope ensures the OutMessage's `value` field carries the
172
+ * consumer's union type without an `as` cast at the call site:
173
+ *
174
+ * ```ts
175
+ * const ToolRadioGroup = Ui.RadioGroup.create<Tool>()
176
+ *
177
+ * // In view:
178
+ * h.submodel({ view: ToolRadioGroup.view, ... })
179
+ *
180
+ * // In update:
181
+ * const [next, commands, maybeOutMessage] = ToolRadioGroup.update(model, message)
182
+ * // maybeOutMessage: Option<RadioGroup.OutMessage<Tool>>
183
+ * ```
184
+ *
185
+ * The view's `ViewInputs.options` stays typed `ReadonlyArray<string>`;
186
+ * consumers can pass a `ReadonlyArray<MyUnion>` (assignable) and the
187
+ * fenced cast inside `update` types the OutMessage's `value` as
188
+ * `MyUnion`. */
189
+ export const create = () => ({
190
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
191
+ view: internalView,
192
+ update: (model, message) => update(model, message),
193
+ select: (model, value, options) => select(model, value, options),
194
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
195
+ reflectSelectedValue: reflectSelectedValue,
196
+ });
@@ -1,3 +1,3 @@
1
- export { init, update, select, view, lazy, Model, Message, SelectedOption, CompletedFocusOption, FocusOption, } from './index.js';
2
- export type { NarrowedSelectedOption, Orientation, InitConfig, ViewConfig, OptionAttributes, OptionConfig, } from './index.js';
1
+ export { init, create, Model, Message, OutMessage, Selected, SelectedOption, CompletedFocusOption, FocusOption, } from './index.js';
2
+ export type { Orientation, InitConfig, ViewInputs, RenderInfo, OptionInfo, } from './index.js';
3
3
  //# sourceMappingURL=public.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/ui/radioGroup/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,EACJ,MAAM,EACN,MAAM,EACN,IAAI,EACJ,IAAI,EACJ,KAAK,EACL,OAAO,EACP,cAAc,EACd,oBAAoB,EACpB,WAAW,GACZ,MAAM,YAAY,CAAA;AAEnB,YAAY,EACV,sBAAsB,EACtB,WAAW,EACX,UAAU,EACV,UAAU,EACV,gBAAgB,EAChB,YAAY,GACb,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/ui/radioGroup/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,EACJ,MAAM,EACN,KAAK,EACL,OAAO,EACP,UAAU,EACV,QAAQ,EACR,cAAc,EACd,oBAAoB,EACpB,WAAW,GACZ,MAAM,YAAY,CAAA;AAEnB,YAAY,EACV,WAAW,EACX,UAAU,EACV,UAAU,EACV,UAAU,EACV,UAAU,GACX,MAAM,YAAY,CAAA"}
@@ -1 +1 @@
1
- export { init, update, select, view, lazy, Model, Message, SelectedOption, CompletedFocusOption, FocusOption, } from './index.js';
1
+ export { init, create, Model, Message, OutMessage, Selected, SelectedOption, CompletedFocusOption, FocusOption, } from './index.js';
@@ -1,6 +1,7 @@
1
1
  import { Option, Schema as S, Stream } from 'effect';
2
2
  import type { Command } from '../../command/index.js';
3
- import { type Attribute, type Html } from '../../html/index.js';
3
+ import { type ChildAttribute, type Html } from '../../html/index.js';
4
+ import type { Reflect } from '../../submodel/submodel.js';
4
5
  /** Schema for the slider component's state. Tracks the current value, the
5
6
  * range (min/max/step), and the active drag phase. */
6
7
  export declare const Model: S.Struct<{
@@ -51,15 +52,16 @@ export type MovedDragPointer = typeof MovedDragPointer.Type;
51
52
  export type ReleasedDragPointer = typeof ReleasedDragPointer.Type;
52
53
  export type CancelledDrag = typeof CancelledDrag.Type;
53
54
  export type PressedKeyboardNavigation = typeof PressedKeyboardNavigation.Type;
54
- /** Emitted when the slider value changes. The parent uses this to react to
55
- * value updates e.g. to run validation or trigger a side effect. */
55
+ /** Emitted when the slider value changes. The parent can handle this to
56
+ * update its own state or dispatch its own Commands, for example to run
57
+ * validation or trigger a downstream Command. */
56
58
  export declare const ChangedValue: import("../../schema/index.js").CallableTaggedStruct<"ChangedValue", {
57
59
  value: S.Number;
58
60
  }>;
59
61
  /** Union of all out-messages the slider component can emit to its parent. */
60
- export declare const OutMessage: import("../../schema/index.js").CallableTaggedStruct<"ChangedValue", {
62
+ export declare const OutMessage: S.Union<readonly [import("../../schema/index.js").CallableTaggedStruct<"ChangedValue", {
61
63
  value: S.Number;
62
- }>;
64
+ }>]>;
63
65
  export type OutMessage = typeof OutMessage.Type;
64
66
  /** Configuration for creating a slider model with `init`. */
65
67
  export type InitConfig = Readonly<{
@@ -83,20 +85,20 @@ type UpdateReturn = readonly [
83
85
  /** Processes a slider message and returns the next model, commands, and an
84
86
  * optional out-message for the parent. */
85
87
  export declare const update: (model: Model, message: Message) => UpdateReturn;
86
- /** Updates the slider's range. Snaps and clamps the current value into the
87
- * new range. Use this when min/max derive from external state (e.g. a
88
- * bounded buffer whose first/last index shifts over time). Unlike `setValue`,
89
- * this runs even while the user is Dragging: a structural range change
90
- * cannot leave the value out of bounds. */
91
- export declare const setRange: (model: Model, range: Readonly<{
88
+ /** Reflects an externally-driven range onto the slider. Snaps and clamps the
89
+ * current value into the new range. Use this when min/max derive from
90
+ * external state (e.g. a bounded buffer whose first/last index shifts over
91
+ * time). Unlike `reflectValue`, this runs even while the user is Dragging: a
92
+ * structural range change cannot leave the value out of bounds. */
93
+ export declare const reflectRange: Reflect<Model, Readonly<{
92
94
  min: number;
93
95
  max: number;
94
- }>) => Model;
95
- /** Sets the slider's value, snapped and clamped into the current range.
96
- * No-op while the user is actively dragging. drag state owns the value.
97
- * Does not emit `ChangedValue`; use when the value is being driven by
96
+ }>>;
97
+ /** Reflects an externally-driven value onto the slider, snapped and clamped
98
+ * into range; a no-op while the user is dragging, since drag state owns the
99
+ * value. Does not emit `ChangedValue`; use when the value is being driven by
98
100
  * external state rather than user input. */
99
- export declare const setValue: (model: Model, value: number) => Model;
101
+ export declare const reflectValue: Reflect<Model, number>;
100
102
  /** Builds slider drag subscriptions, looking up the track
101
103
  * element through the supplied root resolver. Use this when the slider is
102
104
  * rendered inside a Shadow DOM. The root is read lazily so consumers can
@@ -297,20 +299,20 @@ export declare const subscriptions: {
297
299
  };
298
300
  };
299
301
  /** Attribute groups the slider component provides to the consumer's `toView`
300
- * callback. */
301
- export type SliderAttributes<ParentMessage> = Readonly<{
302
- root: ReadonlyArray<Attribute<ParentMessage>>;
303
- track: ReadonlyArray<Attribute<ParentMessage>>;
304
- filledTrack: ReadonlyArray<Attribute<ParentMessage>>;
305
- thumb: ReadonlyArray<Attribute<ParentMessage>>;
306
- label: ReadonlyArray<Attribute<ParentMessage>>;
307
- hiddenInput: ReadonlyArray<Attribute<ParentMessage>>;
302
+ * callback. Each bundle carries the boundary's captured dispatch, so the
303
+ * consumer can spread it directly into element attributes without manual
304
+ * Message wrapping. */
305
+ export type SliderAttributes = Readonly<{
306
+ root: ReadonlyArray<ChildAttribute>;
307
+ track: ReadonlyArray<ChildAttribute>;
308
+ filledTrack: ReadonlyArray<ChildAttribute>;
309
+ thumb: ReadonlyArray<ChildAttribute>;
310
+ label: ReadonlyArray<ChildAttribute>;
311
+ hiddenInput: ReadonlyArray<ChildAttribute>;
308
312
  }>;
309
- /** Configuration for rendering a slider with `view`. */
310
- export type ViewConfig<ParentMessage> = Readonly<{
311
- model: Model;
312
- toParentMessage: (message: PressedThumb | PressedPointer | MovedDragPointer | ReleasedDragPointer | CancelledDrag | PressedKeyboardNavigation) => ParentMessage;
313
- toView: (attributes: SliderAttributes<ParentMessage>) => Html;
313
+ /** Per-render view inputs passed to `view` via `h.submodel`'s `viewInputs` field. */
314
+ export type ViewInputs = Readonly<{
315
+ toView: (attributes: SliderAttributes) => Html;
314
316
  ariaLabel?: string;
315
317
  ariaLabelledBy?: string;
316
318
  formatValue?: (value: number) => string;
@@ -324,12 +326,48 @@ export type ViewConfig<ParentMessage> = Readonly<{
324
326
  }>;
325
327
  /** Renders an accessible slider by building ARIA attribute groups and
326
328
  * delegating layout to the consumer's `toView` callback. Follows the
327
- * WAI-ARIA slider pattern role="slider" on the thumb, aria-valuemin /
329
+ * WAI-ARIA slider pattern: role="slider" on the thumb, aria-valuemin /
328
330
  * aria-valuemax / aria-valuenow, keyboard navigation by step / page / home /
329
331
  * end. Pointer drag is handled by the component's drag subscriptions. */
330
- export declare const view: <ParentMessage>(config: ViewConfig<ParentMessage>) => Html;
331
- /** Creates a memoized slider view. Static config is captured in a closure;
332
- * only `model` and `toParentMessage` are compared per render via `createLazy`. */
333
- export declare const lazy: <ParentMessage>(staticConfig: Omit<ViewConfig<ParentMessage>, "model" | "toParentMessage">) => ((model: Model, toParentMessage: ViewConfig<ParentMessage>["toParentMessage"]) => Html);
332
+ export declare const view: import("../../html/submodel.js").SubmodelView<{
333
+ readonly id: string;
334
+ readonly value: number;
335
+ readonly min: number;
336
+ readonly max: number;
337
+ readonly step: number;
338
+ readonly dragState: {
339
+ readonly _tag: "Idle";
340
+ } | {
341
+ readonly _tag: "Dragging";
342
+ readonly originValue: number;
343
+ };
344
+ }, {
345
+ readonly _tag: "PressedThumb";
346
+ } | {
347
+ readonly _tag: "PressedPointer";
348
+ readonly value: number;
349
+ } | {
350
+ readonly _tag: "MovedDragPointer";
351
+ readonly value: number;
352
+ } | {
353
+ readonly _tag: "ReleasedDragPointer";
354
+ } | {
355
+ readonly _tag: "CancelledDrag";
356
+ } | {
357
+ readonly _tag: "PressedKeyboardNavigation";
358
+ readonly direction: "Max" | "Min" | "StepDecrement" | "StepIncrement" | "PageDecrement" | "PageIncrement";
359
+ }, Readonly<{
360
+ toView: (attributes: SliderAttributes) => Html;
361
+ ariaLabel?: string;
362
+ ariaLabelledBy?: string;
363
+ formatValue?: (value: number) => string;
364
+ isDisabled?: boolean;
365
+ name?: string;
366
+ /** Resolves the root that holds the slider track when looking it up by its
367
+ * `data-slider-track-id` attribute. Defaults to `document`. Provide a
368
+ * ShadowRoot when rendering the slider inside a shadow tree so pointer
369
+ * events on the track can map clientX into a value. */
370
+ getTrackRoot?: () => Document | ShadowRoot;
371
+ }>>;
334
372
  export {};
335
373
  //# sourceMappingURL=index.d.ts.map