foldkit 0.100.1 → 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 (217) hide show
  1. package/README.md +3 -2
  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 +156 -149
  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 +11 -11
  56. package/dist/runtime/runtime.d.ts.map +1 -1
  57. package/dist/runtime/runtime.js +118 -63
  58. package/dist/runtime/subscription.d.ts +139 -19
  59. package/dist/runtime/subscription.d.ts.map +1 -1
  60. package/dist/runtime/subscription.js +90 -9
  61. package/dist/submodel/public.d.ts +4 -0
  62. package/dist/submodel/public.d.ts.map +1 -0
  63. package/dist/submodel/public.js +1 -0
  64. package/dist/submodel/submodel.d.ts +32 -0
  65. package/dist/submodel/submodel.d.ts.map +1 -0
  66. package/dist/submodel/submodel.js +1 -0
  67. package/dist/subscription/animationFrame.d.ts +23 -26
  68. package/dist/subscription/animationFrame.d.ts.map +1 -1
  69. package/dist/subscription/animationFrame.js +17 -18
  70. package/dist/subscription/public.d.ts +2 -2
  71. package/dist/subscription/public.d.ts.map +1 -1
  72. package/dist/subscription/public.js +1 -1
  73. package/dist/test/apps/disabledButton.d.ts +4 -5
  74. package/dist/test/apps/disabledButton.d.ts.map +1 -1
  75. package/dist/test/apps/disabledButton.js +16 -16
  76. package/dist/test/scene.d.ts +8 -8
  77. package/dist/test/scene.d.ts.map +1 -1
  78. package/dist/test/scene.js +25 -13
  79. package/dist/test/story.d.ts +15 -8
  80. package/dist/test/story.d.ts.map +1 -1
  81. package/dist/test/story.js +21 -9
  82. package/dist/ui/animation/index.d.ts +30 -14
  83. package/dist/ui/animation/index.d.ts.map +1 -1
  84. package/dist/ui/animation/index.js +9 -19
  85. package/dist/ui/animation/public.d.ts +2 -2
  86. package/dist/ui/animation/public.d.ts.map +1 -1
  87. package/dist/ui/animation/public.js +1 -1
  88. package/dist/ui/calendar/index.d.ts +199 -84
  89. package/dist/ui/calendar/index.d.ts.map +1 -1
  90. package/dist/ui/calendar/index.js +129 -140
  91. package/dist/ui/calendar/public.d.ts +2 -2
  92. package/dist/ui/calendar/public.d.ts.map +1 -1
  93. package/dist/ui/calendar/public.js +1 -1
  94. package/dist/ui/checkbox/index.d.ts +93 -21
  95. package/dist/ui/checkbox/index.d.ts.map +1 -1
  96. package/dist/ui/checkbox/index.js +62 -33
  97. package/dist/ui/checkbox/public.d.ts +2 -2
  98. package/dist/ui/checkbox/public.d.ts.map +1 -1
  99. package/dist/ui/checkbox/public.js +1 -1
  100. package/dist/ui/combobox/multi.d.ts +35 -91
  101. package/dist/ui/combobox/multi.d.ts.map +1 -1
  102. package/dist/ui/combobox/multi.js +34 -17
  103. package/dist/ui/combobox/multiPublic.d.ts +2 -2
  104. package/dist/ui/combobox/multiPublic.d.ts.map +1 -1
  105. package/dist/ui/combobox/multiPublic.js +1 -1
  106. package/dist/ui/combobox/public.d.ts +3 -3
  107. package/dist/ui/combobox/public.d.ts.map +1 -1
  108. package/dist/ui/combobox/public.js +2 -2
  109. package/dist/ui/combobox/shared.d.ts +56 -31
  110. package/dist/ui/combobox/shared.d.ts.map +1 -1
  111. package/dist/ui/combobox/shared.js +333 -322
  112. package/dist/ui/combobox/single.d.ts +46 -93
  113. package/dist/ui/combobox/single.d.ts.map +1 -1
  114. package/dist/ui/combobox/single.js +44 -17
  115. package/dist/ui/datePicker/index.d.ts +256 -48
  116. package/dist/ui/datePicker/index.d.ts.map +1 -1
  117. package/dist/ui/datePicker/index.js +149 -104
  118. package/dist/ui/datePicker/public.d.ts +2 -2
  119. package/dist/ui/datePicker/public.d.ts.map +1 -1
  120. package/dist/ui/datePicker/public.js +1 -1
  121. package/dist/ui/dialog/index.d.ts +95 -39
  122. package/dist/ui/dialog/index.d.ts.map +1 -1
  123. package/dist/ui/dialog/index.js +71 -62
  124. package/dist/ui/dialog/public.d.ts +2 -2
  125. package/dist/ui/dialog/public.d.ts.map +1 -1
  126. package/dist/ui/dialog/public.js +1 -1
  127. package/dist/ui/disclosure/index.d.ts +71 -31
  128. package/dist/ui/disclosure/index.d.ts.map +1 -1
  129. package/dist/ui/disclosure/index.js +57 -62
  130. package/dist/ui/disclosure/public.d.ts +2 -2
  131. package/dist/ui/disclosure/public.d.ts.map +1 -1
  132. package/dist/ui/disclosure/public.js +1 -1
  133. package/dist/ui/dragAndDrop/index.d.ts +385 -103
  134. package/dist/ui/dragAndDrop/index.d.ts.map +1 -1
  135. package/dist/ui/dragAndDrop/index.js +26 -31
  136. package/dist/ui/dragAndDrop/public.d.ts +1 -1
  137. package/dist/ui/dragAndDrop/public.d.ts.map +1 -1
  138. package/dist/ui/dragAndDrop/public.js +1 -1
  139. package/dist/ui/fileDrop/index.d.ts +42 -46
  140. package/dist/ui/fileDrop/index.d.ts.map +1 -1
  141. package/dist/ui/fileDrop/index.js +30 -46
  142. package/dist/ui/fileDrop/public.d.ts +2 -2
  143. package/dist/ui/fileDrop/public.d.ts.map +1 -1
  144. package/dist/ui/fileDrop/public.js +1 -1
  145. package/dist/ui/listbox/multi.d.ts +39 -84
  146. package/dist/ui/listbox/multi.d.ts.map +1 -1
  147. package/dist/ui/listbox/multi.js +38 -20
  148. package/dist/ui/listbox/multiPublic.d.ts +2 -2
  149. package/dist/ui/listbox/multiPublic.d.ts.map +1 -1
  150. package/dist/ui/listbox/multiPublic.js +1 -1
  151. package/dist/ui/listbox/public.d.ts +3 -3
  152. package/dist/ui/listbox/public.d.ts.map +1 -1
  153. package/dist/ui/listbox/public.js +2 -2
  154. package/dist/ui/listbox/shared.d.ts +71 -30
  155. package/dist/ui/listbox/shared.d.ts.map +1 -1
  156. package/dist/ui/listbox/shared.js +319 -296
  157. package/dist/ui/listbox/single.d.ts +57 -85
  158. package/dist/ui/listbox/single.d.ts.map +1 -1
  159. package/dist/ui/listbox/single.js +48 -24
  160. package/dist/ui/menu/index.d.ts +80 -36
  161. package/dist/ui/menu/index.d.ts.map +1 -1
  162. package/dist/ui/menu/index.js +117 -86
  163. package/dist/ui/menu/public.d.ts +2 -2
  164. package/dist/ui/menu/public.d.ts.map +1 -1
  165. package/dist/ui/menu/public.js +1 -1
  166. package/dist/ui/popover/index.d.ts +117 -44
  167. package/dist/ui/popover/index.d.ts.map +1 -1
  168. package/dist/ui/popover/index.js +88 -101
  169. package/dist/ui/popover/public.d.ts +2 -2
  170. package/dist/ui/popover/public.d.ts.map +1 -1
  171. package/dist/ui/popover/public.js +1 -1
  172. package/dist/ui/radioGroup/index.d.ts +122 -45
  173. package/dist/ui/radioGroup/index.d.ts.map +1 -1
  174. package/dist/ui/radioGroup/index.js +111 -72
  175. package/dist/ui/radioGroup/public.d.ts +2 -2
  176. package/dist/ui/radioGroup/public.d.ts.map +1 -1
  177. package/dist/ui/radioGroup/public.js +1 -1
  178. package/dist/ui/slider/index.d.ts +247 -103
  179. package/dist/ui/slider/index.d.ts.map +1 -1
  180. package/dist/ui/slider/index.js +52 -68
  181. package/dist/ui/slider/public.d.ts +2 -2
  182. package/dist/ui/slider/public.d.ts.map +1 -1
  183. package/dist/ui/slider/public.js +1 -1
  184. package/dist/ui/switch/index.d.ts +74 -21
  185. package/dist/ui/switch/index.d.ts.map +1 -1
  186. package/dist/ui/switch/index.js +62 -33
  187. package/dist/ui/switch/public.d.ts +2 -2
  188. package/dist/ui/switch/public.d.ts.map +1 -1
  189. package/dist/ui/switch/public.js +1 -1
  190. package/dist/ui/tabs/index.d.ts +107 -45
  191. package/dist/ui/tabs/index.d.ts.map +1 -1
  192. package/dist/ui/tabs/index.js +99 -81
  193. package/dist/ui/tabs/public.d.ts +2 -2
  194. package/dist/ui/tabs/public.d.ts.map +1 -1
  195. package/dist/ui/tabs/public.js +1 -1
  196. package/dist/ui/toast/index.d.ts +93 -109
  197. package/dist/ui/toast/index.d.ts.map +1 -1
  198. package/dist/ui/toast/index.js +16 -29
  199. package/dist/ui/toast/schema.d.ts +15 -4
  200. package/dist/ui/toast/schema.d.ts.map +1 -1
  201. package/dist/ui/toast/schema.js +11 -4
  202. package/dist/ui/toast/update.d.ts +36 -18
  203. package/dist/ui/toast/update.d.ts.map +1 -1
  204. package/dist/ui/toast/update.js +33 -14
  205. package/dist/ui/tooltip/index.d.ts +94 -42
  206. package/dist/ui/tooltip/index.d.ts.map +1 -1
  207. package/dist/ui/tooltip/index.js +64 -73
  208. package/dist/ui/tooltip/public.d.ts +2 -2
  209. package/dist/ui/tooltip/public.d.ts.map +1 -1
  210. package/dist/ui/tooltip/public.js +1 -1
  211. package/dist/ui/virtualList/index.d.ts +63 -80
  212. package/dist/ui/virtualList/index.d.ts.map +1 -1
  213. package/dist/ui/virtualList/index.js +22 -49
  214. package/dist/ui/virtualList/public.d.ts +2 -2
  215. package/dist/ui/virtualList/public.d.ts.map +1 -1
  216. package/dist/ui/virtualList/public.js +1 -1
  217. 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":"AAAA,OAAO,EAEL,MAAM,EAIN,MAAM,IAAI,CAAC,EAGZ,MAAM,QAAQ,CAAA;AAEf,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAA;AAEjD,OAAO,EACL,KAAK,SAAS,EACd,KAAK,IAAI,EAGV,MAAM,qBAAqB,CAAA;AAO5B,+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,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,+EAA+E;AAC/E,eAAO,MAAM,MAAM,GACjB,OAAO,KAAK,EACZ,SAAS,OAAO,KACf,SAAS,CAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAYxD,CAAA;AAEH;gGACgG;AAChG,eAAO,MAAM,MAAM,GACjB,OAAO,KAAK,EACZ,OAAO,MAAM,EACb,SAAS,aAAa,CAAC,MAAM,CAAC,KAC7B,SAAS,CAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAQxD,CAAA;AAIH,gGAAgG;AAChG,MAAM,MAAM,gBAAgB,CAAC,aAAa,IAAI,QAAQ,CAAC;IACrD,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAA;IAC/C,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAA;IAC9C,WAAW,EAAE,aAAa,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAA;CACrD,CAAC,CAAA;AAEF;uFACuF;AACvF,MAAM,MAAM,YAAY,CACtB,aAAa,EACb,WAAW,SAAS,MAAM,GAAG,MAAM,IACjC,QAAQ,CAAC;IACX,KAAK,EAAE,WAAW,CAAA;IAClB,OAAO,EAAE,CAAC,UAAU,EAAE,gBAAgB,CAAC,aAAa,CAAC,KAAK,IAAI,CAAA;CAC/D,CAAC,CAAA;AAEF;6DAC6D;AAC7D,MAAM,MAAM,sBAAsB,CAAC,WAAW,SAAS,MAAM,IAAI,QAAQ,CAAC;IACxE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAA;IAC/B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB,CAAC,CAAA;AAEF,6DAA6D;AAC7D,MAAM,MAAM,UAAU,CAAC,aAAa,EAAE,WAAW,SAAS,MAAM,IAAI,QAAQ,CAAC;IAC3E,KAAK,EAAE,KAAK,CAAA;IACZ,eAAe,EAAE,CACf,OAAO,EAAE,sBAAsB,CAAC,WAAW,CAAC,GAAG,oBAAoB,KAChE,aAAa,CAAA;IAClB,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,KAAK,aAAa,CAAA;IACjE,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,CAAA;IACnC,cAAc,EAAE,CACd,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,QAAQ,CAAC;QAChB,UAAU,EAAE,OAAO,CAAA;QACnB,QAAQ,EAAE,OAAO,CAAA;QACjB,UAAU,EAAE,OAAO,CAAA;KACpB,CAAC,KACC,YAAY,CAAC,aAAa,EAAE,WAAW,CAAC,CAAA;IAC7C,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAA;IAClE,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAA;IACpD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB,CAAC,CAAA;AAQF,6IAA6I;AAC7I,eAAO,MAAM,IAAI,GAAI,aAAa,EAAE,WAAW,SAAS,MAAM,EAC5D,QAAQ,UAAU,CAAC,aAAa,EAAE,WAAW,CAAC,KAC7C,IAiNF,CAAA;AAED;mFACmF;AACnF,eAAO,MAAM,IAAI,GAAI,aAAa,EAAE,WAAW,SAAS,MAAM,EAC5D,cAAc,IAAI,CAChB,UAAU,CAAC,aAAa,EAAE,WAAW,CAAC,EACtC,OAAO,GAAG,iBAAiB,GAAG,YAAY,CAC3C,KACA,CAAC,CACF,KAAK,EAAE,KAAK,EACZ,eAAe,EAAE,UAAU,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC,iBAAiB,CAAC,KACvE,IAAI,CAmBR,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/radioGroup/index.ts"],"names":[],"mappings":"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"}
@@ -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';