@foldkit/ui 0.112.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 (201) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +67 -0
  3. package/dist/anchor.d.ts +38 -0
  4. package/dist/anchor.d.ts.map +1 -0
  5. package/dist/anchor.js +142 -0
  6. package/dist/animation/index.d.ts +49 -0
  7. package/dist/animation/index.d.ts.map +1 -0
  8. package/dist/animation/index.js +75 -0
  9. package/dist/animation/public.d.ts +3 -0
  10. package/dist/animation/public.d.ts.map +1 -0
  11. package/dist/animation/public.js +1 -0
  12. package/dist/animation/schema.d.ts +43 -0
  13. package/dist/animation/schema.d.ts.map +1 -0
  14. package/dist/animation/schema.js +41 -0
  15. package/dist/animation/update.d.ts +24 -0
  16. package/dist/animation/update.d.ts.map +1 -0
  17. package/dist/animation/update.js +67 -0
  18. package/dist/button/index.d.ts +17 -0
  19. package/dist/button/index.d.ts.map +1 -0
  20. package/dist/button/index.js +22 -0
  21. package/dist/button/public.d.ts +3 -0
  22. package/dist/button/public.d.ts.map +1 -0
  23. package/dist/button/public.js +1 -0
  24. package/dist/calendar/index.d.ts +462 -0
  25. package/dist/calendar/index.d.ts.map +1 -0
  26. package/dist/calendar/index.js +825 -0
  27. package/dist/calendar/public.d.ts +3 -0
  28. package/dist/calendar/public.d.ts.map +1 -0
  29. package/dist/calendar/public.js +1 -0
  30. package/dist/checkbox/index.d.ts +119 -0
  31. package/dist/checkbox/index.d.ts.map +1 -0
  32. package/dist/checkbox/index.js +111 -0
  33. package/dist/checkbox/public.d.ts +3 -0
  34. package/dist/checkbox/public.d.ts.map +1 -0
  35. package/dist/checkbox/public.js +1 -0
  36. package/dist/combobox/multi.d.ts +183 -0
  37. package/dist/combobox/multi.d.ts.map +1 -0
  38. package/dist/combobox/multi.js +81 -0
  39. package/dist/combobox/multiPublic.d.ts +3 -0
  40. package/dist/combobox/multiPublic.d.ts.map +1 -0
  41. package/dist/combobox/multiPublic.js +1 -0
  42. package/dist/combobox/public.d.ts +7 -0
  43. package/dist/combobox/public.d.ts.map +1 -0
  44. package/dist/combobox/public.js +3 -0
  45. package/dist/combobox/shared.d.ts +423 -0
  46. package/dist/combobox/shared.d.ts.map +1 -0
  47. package/dist/combobox/shared.js +708 -0
  48. package/dist/combobox/single.d.ts +198 -0
  49. package/dist/combobox/single.d.ts.map +1 -0
  50. package/dist/combobox/single.js +106 -0
  51. package/dist/datePicker/index.d.ts +457 -0
  52. package/dist/datePicker/index.d.ts.map +1 -0
  53. package/dist/datePicker/index.js +318 -0
  54. package/dist/datePicker/public.d.ts +3 -0
  55. package/dist/datePicker/public.d.ts.map +1 -0
  56. package/dist/datePicker/public.js +1 -0
  57. package/dist/dialog/index.d.ts +160 -0
  58. package/dist/dialog/index.d.ts.map +1 -0
  59. package/dist/dialog/index.js +211 -0
  60. package/dist/dialog/public.d.ts +3 -0
  61. package/dist/dialog/public.d.ts.map +1 -0
  62. package/dist/dialog/public.js +1 -0
  63. package/dist/disclosure/index.d.ts +110 -0
  64. package/dist/disclosure/index.d.ts.map +1 -0
  65. package/dist/disclosure/index.js +111 -0
  66. package/dist/disclosure/public.d.ts +3 -0
  67. package/dist/disclosure/public.d.ts.map +1 -0
  68. package/dist/disclosure/public.js +1 -0
  69. package/dist/dragAndDrop/index.d.ts +540 -0
  70. package/dist/dragAndDrop/index.d.ts.map +1 -0
  71. package/dist/dragAndDrop/index.js +535 -0
  72. package/dist/dragAndDrop/public.d.ts +3 -0
  73. package/dist/dragAndDrop/public.d.ts.map +1 -0
  74. package/dist/dragAndDrop/public.js +1 -0
  75. package/dist/fieldset/index.d.ts +21 -0
  76. package/dist/fieldset/index.d.ts.map +1 -0
  77. package/dist/fieldset/index.js +25 -0
  78. package/dist/fieldset/public.d.ts +3 -0
  79. package/dist/fieldset/public.d.ts.map +1 -0
  80. package/dist/fieldset/public.js +1 -0
  81. package/dist/fileDrop/index.d.ts +109 -0
  82. package/dist/fileDrop/index.d.ts.map +1 -0
  83. package/dist/fileDrop/index.js +127 -0
  84. package/dist/fileDrop/public.d.ts +3 -0
  85. package/dist/fileDrop/public.d.ts.map +1 -0
  86. package/dist/fileDrop/public.js +1 -0
  87. package/dist/group.d.ts +8 -0
  88. package/dist/group.d.ts.map +1 -0
  89. package/dist/group.js +13 -0
  90. package/dist/index.d.ts +25 -0
  91. package/dist/index.d.ts.map +1 -0
  92. package/dist/index.js +24 -0
  93. package/dist/input/index.d.ts +26 -0
  94. package/dist/input/index.d.ts.map +1 -0
  95. package/dist/input/index.js +43 -0
  96. package/dist/input/public.d.ts +3 -0
  97. package/dist/input/public.d.ts.map +1 -0
  98. package/dist/input/public.js +1 -0
  99. package/dist/internal/optionExtensions.d.ts +6 -0
  100. package/dist/internal/optionExtensions.d.ts.map +1 -0
  101. package/dist/internal/optionExtensions.js +2 -0
  102. package/dist/keyboard.d.ts +6 -0
  103. package/dist/keyboard.d.ts.map +1 -0
  104. package/dist/keyboard.js +9 -0
  105. package/dist/listbox/multi.d.ts +189 -0
  106. package/dist/listbox/multi.d.ts.map +1 -0
  107. package/dist/listbox/multi.js +65 -0
  108. package/dist/listbox/multiPublic.d.ts +3 -0
  109. package/dist/listbox/multiPublic.d.ts.map +1 -0
  110. package/dist/listbox/multiPublic.js +1 -0
  111. package/dist/listbox/public.d.ts +7 -0
  112. package/dist/listbox/public.d.ts.map +1 -0
  113. package/dist/listbox/public.js +3 -0
  114. package/dist/listbox/shared.d.ts +432 -0
  115. package/dist/listbox/shared.d.ts.map +1 -0
  116. package/dist/listbox/shared.js +670 -0
  117. package/dist/listbox/single.d.ts +207 -0
  118. package/dist/listbox/single.d.ts.map +1 -0
  119. package/dist/listbox/single.js +73 -0
  120. package/dist/menu/index.d.ts +368 -0
  121. package/dist/menu/index.d.ts.map +1 -0
  122. package/dist/menu/index.js +682 -0
  123. package/dist/menu/public.d.ts +4 -0
  124. package/dist/menu/public.d.ts.map +1 -0
  125. package/dist/menu/public.js +1 -0
  126. package/dist/popover/index.d.ts +267 -0
  127. package/dist/popover/index.d.ts.map +1 -0
  128. package/dist/popover/index.js +346 -0
  129. package/dist/popover/public.d.ts +4 -0
  130. package/dist/popover/public.d.ts.map +1 -0
  131. package/dist/popover/public.js +1 -0
  132. package/dist/radioGroup/index.d.ts +169 -0
  133. package/dist/radioGroup/index.d.ts.map +1 -0
  134. package/dist/radioGroup/index.js +197 -0
  135. package/dist/radioGroup/public.d.ts +3 -0
  136. package/dist/radioGroup/public.d.ts.map +1 -0
  137. package/dist/radioGroup/public.js +1 -0
  138. package/dist/select/index.d.ts +24 -0
  139. package/dist/select/index.d.ts.map +1 -0
  140. package/dist/select/index.js +40 -0
  141. package/dist/select/public.d.ts +3 -0
  142. package/dist/select/public.d.ts.map +1 -0
  143. package/dist/select/public.js +1 -0
  144. package/dist/slider/index.d.ts +318 -0
  145. package/dist/slider/index.d.ts.map +1 -0
  146. package/dist/slider/index.js +337 -0
  147. package/dist/slider/public.d.ts +3 -0
  148. package/dist/slider/public.d.ts.map +1 -0
  149. package/dist/slider/public.js +1 -0
  150. package/dist/switch/index.d.ts +99 -0
  151. package/dist/switch/index.d.ts.map +1 -0
  152. package/dist/switch/index.js +107 -0
  153. package/dist/switch/public.d.ts +3 -0
  154. package/dist/switch/public.d.ts.map +1 -0
  155. package/dist/switch/public.js +1 -0
  156. package/dist/tabs/index.d.ts +155 -0
  157. package/dist/tabs/index.d.ts.map +1 -0
  158. package/dist/tabs/index.js +185 -0
  159. package/dist/tabs/public.d.ts +3 -0
  160. package/dist/tabs/public.d.ts.map +1 -0
  161. package/dist/tabs/public.js +1 -0
  162. package/dist/test/apps/disabledButton.d.ts +38 -0
  163. package/dist/test/apps/disabledButton.d.ts.map +1 -0
  164. package/dist/test/apps/disabledButton.js +71 -0
  165. package/dist/textarea/index.d.ts +26 -0
  166. package/dist/textarea/index.d.ts.map +1 -0
  167. package/dist/textarea/index.js +44 -0
  168. package/dist/textarea/public.d.ts +3 -0
  169. package/dist/textarea/public.d.ts.map +1 -0
  170. package/dist/textarea/public.js +1 -0
  171. package/dist/toast/index.d.ts +608 -0
  172. package/dist/toast/index.d.ts.map +1 -0
  173. package/dist/toast/index.js +146 -0
  174. package/dist/toast/public.d.ts +4 -0
  175. package/dist/toast/public.d.ts.map +1 -0
  176. package/dist/toast/public.js +1 -0
  177. package/dist/toast/schema.d.ts +154 -0
  178. package/dist/toast/schema.d.ts.map +1 -0
  179. package/dist/toast/schema.js +93 -0
  180. package/dist/toast/update.d.ts +510 -0
  181. package/dist/toast/update.d.ts.map +1 -0
  182. package/dist/toast/update.js +225 -0
  183. package/dist/tooltip/index.d.ts +170 -0
  184. package/dist/tooltip/index.d.ts.map +1 -0
  185. package/dist/tooltip/index.js +253 -0
  186. package/dist/tooltip/public.d.ts +4 -0
  187. package/dist/tooltip/public.d.ts.map +1 -0
  188. package/dist/tooltip/public.js +1 -0
  189. package/dist/typeahead.d.ts +4 -0
  190. package/dist/typeahead.d.ts.map +1 -0
  191. package/dist/typeahead.js +14 -0
  192. package/dist/virtualList/index.d.ts +203 -0
  193. package/dist/virtualList/index.d.ts.map +1 -0
  194. package/dist/virtualList/index.js +392 -0
  195. package/dist/virtualList/public.d.ts +3 -0
  196. package/dist/virtualList/public.d.ts.map +1 -0
  197. package/dist/virtualList/public.js +1 -0
  198. package/dist/vitest-setup.d.ts +2 -0
  199. package/dist/vitest-setup.d.ts.map +1 -0
  200. package/dist/vitest-setup.js +2 -0
  201. package/package.json +161 -0
@@ -0,0 +1,169 @@
1
+ import { Effect, Option, Schema as S } from 'effect';
2
+ import * as Command from 'foldkit/command';
3
+ import { type ChildAttribute, type Html } from 'foldkit/html';
4
+ import { type Reflect, type View as SubmodelView } from 'foldkit/submodel';
5
+ /** Controls the radio group layout direction and which arrow keys navigate between options. */
6
+ export declare const Orientation: S.Literals<readonly ["Horizontal", "Vertical"]>;
7
+ export type Orientation = typeof Orientation.Type;
8
+ /** Schema for the radio group component's state, tracking the selected value and orientation. */
9
+ export declare const Model: S.Struct<{
10
+ readonly id: S.String;
11
+ readonly selectedValue: S.Option<S.String>;
12
+ readonly orientation: S.Literals<readonly ["Horizontal", "Vertical"]>;
13
+ }>;
14
+ export type Model = typeof Model.Type;
15
+ /** Sent when a radio option is selected via click or keyboard navigation. */
16
+ export declare const SelectedOption: import("foldkit/schema").CallableTaggedStruct<"SelectedOption", {
17
+ value: S.String;
18
+ index: S.Number;
19
+ }>;
20
+ /** Sent when the focus-option command completes. */
21
+ export declare const CompletedFocusOption: import("foldkit/schema").CallableTaggedStruct<"CompletedFocusOption", {}>;
22
+ /** Union of all messages the radio group component can produce. */
23
+ export declare const Message: S.Union<[
24
+ typeof SelectedOption,
25
+ typeof CompletedFocusOption
26
+ ]>;
27
+ export type SelectedOption = typeof SelectedOption.Type;
28
+ export type CompletedFocusOption = typeof CompletedFocusOption.Type;
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("foldkit/schema").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("foldkit/schema").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 `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>;
57
+ /** Configuration for creating a radio group model with `init`. */
58
+ export type InitConfig = Readonly<{
59
+ id: string;
60
+ selectedValue?: string;
61
+ orientation?: Orientation;
62
+ }>;
63
+ /** Creates an initial radio group model from a config. Defaults to no selection and vertical orientation. */
64
+ export declare const init: (config: InitConfig) => Model;
65
+ /** Moves focus to the radio option at the given index. */
66
+ export declare const FocusOption: Command.CommandDefinitionWithArgs<"FocusOption", {
67
+ id: S.String;
68
+ index: S.Number;
69
+ }, Effect.Effect<{
70
+ readonly _tag: "CompletedFocusOption";
71
+ }, never, never>>;
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 `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>;
108
+ }>;
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>;
127
+ }>;
128
+ /** Per-render view inputs passed to `view` via `h.submodel`'s `viewInputs` field.
129
+ * Generic over `Value extends string` so consumers using
130
+ * `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>;
135
+ ariaLabel: string;
136
+ toView: (render: RenderInfo<Value>) => Html;
137
+ isOptionDisabled?: (value: Value, index: number) => boolean;
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 = 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>>;
167
+ }>;
168
+ export {};
169
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/radioGroup/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,197 @@
1
+ import { Array, Effect, Function, Match as M, Option, Predicate, Schema as S, String, pipe, } from 'effect';
2
+ import * as Command from 'foldkit/command';
3
+ import * as Dom from 'foldkit/dom';
4
+ import { childAttributes, html, } from 'foldkit/html';
5
+ import { m } from 'foldkit/message';
6
+ import { evo } from 'foldkit/struct';
7
+ import { defineView, } from 'foldkit/submodel';
8
+ import { keyToIndex } from '../keyboard.js';
9
+ // MODEL
10
+ /** Controls the radio group layout direction and which arrow keys navigate between options. */
11
+ export const Orientation = S.Literals(['Horizontal', 'Vertical']);
12
+ /** Schema for the radio group component's state, tracking the selected value and orientation. */
13
+ export const Model = S.Struct({
14
+ id: S.String,
15
+ selectedValue: S.Option(S.String),
16
+ orientation: Orientation,
17
+ });
18
+ // MESSAGE
19
+ /** Sent when a radio option is selected via click or keyboard navigation. */
20
+ export const SelectedOption = m('SelectedOption', {
21
+ value: S.String,
22
+ index: S.Number,
23
+ });
24
+ /** Sent when the focus-option command completes. */
25
+ export const CompletedFocusOption = m('CompletedFocusOption');
26
+ /** Union of all messages the radio group component can produce. */
27
+ export const Message = S.Union([SelectedOption, CompletedFocusOption]);
28
+ // OUT MESSAGE
29
+ /** Sent to the parent when an option is committed. Carries the selected
30
+ * value and its index. Generic over `Value extends string`: the runtime
31
+ * schema stores `value: string`, but the type-level OutMessage exposes
32
+ * `value: Value` so consumers who supply `options: ReadonlyArray<MyUnion>`
33
+ * receive `value: MyUnion` from the factory's `update` without casting at
34
+ * the call site. The cast is fenced inside this module's `update` return,
35
+ * sound because the value was selected from the options array the
36
+ * consumer supplied. */
37
+ export const Selected = m('Selected', { value: S.String, index: S.Number });
38
+ export const OutMessage = S.Union([Selected]);
39
+ /** Creates an initial radio group model from a config. Defaults to no selection and vertical orientation. */
40
+ export const init = (config) => ({
41
+ id: config.id,
42
+ selectedValue: Option.fromNullishOr(config.selectedValue),
43
+ orientation: config.orientation ?? 'Vertical',
44
+ });
45
+ // UPDATE
46
+ const optionId = (id, index) => `${id}-option-${index}`;
47
+ /** Moves focus to the radio option at the given index. */
48
+ 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())));
49
+ const withInternalUpdateReturn = M.withReturnType();
50
+ /** Processes a radio group message and returns the next model, commands, and
51
+ * optional OutMessage. Generic over `Value extends string`: pass the consumer's
52
+ * union type at the call site to receive `Selected({ value: MyUnion })` without
53
+ * casting. Defaults to `string`. */
54
+ export const update = (model, message) => {
55
+ const result = M.value(message).pipe(withInternalUpdateReturn, M.tagsExhaustive({
56
+ SelectedOption: ({ value, index }) => [
57
+ evo(model, { selectedValue: () => Option.some(value) }),
58
+ [FocusOption({ id: model.id, index })],
59
+ Option.some(Selected({ value, index })),
60
+ ],
61
+ CompletedFocusOption: () => [model, [], Option.none()],
62
+ }));
63
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
64
+ return result;
65
+ };
66
+ /** Programmatically selects a value in the radio group, updating the model
67
+ * and returning focus commands plus a `Selected` OutMessage. */
68
+ export const select = (model, value, options) => pipe(options, Array.findFirstIndex(option => option === value), Option.match({
69
+ onNone: () => [model, [], Option.none()],
70
+ onSome: index => update(model, SelectedOption({ value, index })),
71
+ }));
72
+ /** Reflects an externally-sourced selection onto the model without
73
+ * emitting an OutMessage or running the focus command. Use this to mirror
74
+ * external truth (a URL parameter, restored storage, a server push) onto
75
+ * the radio group's selection. Contrast with `select`, which represents a
76
+ * user or programmatic *choice*: it focuses the option and emits
77
+ * `Selected`. Takes no `options` (unlike `select`) because it sets the
78
+ * value directly rather than deriving a focus index. Returns the model
79
+ * directly because it produces no commands and no OutMessage. */
80
+ export const reflectSelectedValue = Function.dual(2, (model, maybeValue) => evo(model, { selectedValue: () => maybeValue }));
81
+ const labelId = (id, index) => `${id}-option-${index}-label`;
82
+ const descriptionId = (id, index) => `${id}-option-${index}-description`;
83
+ const internalView = defineView((model, viewInputs) => {
84
+ const h = html();
85
+ const { id, selectedValue } = model;
86
+ const { options, ariaLabel, toView, isOptionDisabled: isOptionDisabledFn, isDisabled: isGroupDisabled = false, name, orientation = model.orientation, } = viewInputs;
87
+ const isDisabled = (index) => {
88
+ if (isGroupDisabled) {
89
+ return true;
90
+ }
91
+ if (!isOptionDisabledFn) {
92
+ return false;
93
+ }
94
+ return pipe(options, Array.get(index), Option.exists(option => isOptionDisabledFn(option, index)));
95
+ };
96
+ const selectedIndex = Option.flatMap(selectedValue, value => Array.findFirstIndex(options, option => option === value));
97
+ const focusedIndex = pipe(selectedIndex, Option.getOrElse(() => pipe(options.length, Array.makeBy(index => index), Array.findFirst(Predicate.not(isDisabled)), Option.getOrElse(() => 0))));
98
+ const { nextKey, previousKey } = M.value(orientation).pipe(M.when('Horizontal', () => ({
99
+ nextKey: 'ArrowRight',
100
+ previousKey: 'ArrowLeft',
101
+ })), M.when('Vertical', () => ({
102
+ nextKey: 'ArrowDown',
103
+ previousKey: 'ArrowUp',
104
+ })), M.exhaustive);
105
+ const resolveKeyIndex = keyToIndex(nextKey, previousKey, options.length, focusedIndex, isDisabled);
106
+ const handleKeyDown = (currentIndex) => (key) => M.value(key).pipe(M.whenOr(nextKey, previousKey, 'Home', 'End', 'PageUp', 'PageDown', () => {
107
+ const nextIndex = resolveKeyIndex(key);
108
+ return pipe(options, Array.get(nextIndex), Option.map(value => SelectedOption({ value, index: nextIndex })));
109
+ }), M.when(' ', () => pipe(options, Array.get(currentIndex), Option.map(value => SelectedOption({ value, index: currentIndex })))), M.orElse(() => Option.none()));
110
+ const optionInfos = Array.map(options, (value, index) => {
111
+ const isSelected = Option.exists(selectedIndex, selectedIdx => selectedIdx === index);
112
+ const isFocusable = index === focusedIndex;
113
+ const isOptionDisabledNow = isDisabled(index);
114
+ const checkedAttributes = isSelected
115
+ ? [h.DataAttribute('checked', '')]
116
+ : [];
117
+ const activeAttributes = isFocusable
118
+ ? [h.DataAttribute('active', '')]
119
+ : [];
120
+ const disabledAttributes = isOptionDisabledNow
121
+ ? [h.AriaDisabled(true), h.DataAttribute('disabled', '')]
122
+ : [];
123
+ const optionAttributes = [
124
+ h.Id(optionId(id, index)),
125
+ h.Role('radio'),
126
+ h.AriaChecked(isSelected),
127
+ h.AriaLabelledBy(labelId(id, index)),
128
+ h.AriaDescribedBy(descriptionId(id, index)),
129
+ h.Tabindex(isFocusable ? 0 : -1),
130
+ ...checkedAttributes,
131
+ ...activeAttributes,
132
+ ...disabledAttributes,
133
+ ...(isOptionDisabledNow
134
+ ? []
135
+ : [
136
+ h.OnClick(SelectedOption({ value, index })),
137
+ h.OnKeyDownPreventDefault(handleKeyDown(index)),
138
+ ]),
139
+ ];
140
+ const labelAttributes = [h.Id(labelId(id, index))];
141
+ const descriptionAttributes = [h.Id(descriptionId(id, index))];
142
+ return {
143
+ value,
144
+ index,
145
+ isSelected,
146
+ isActive: isFocusable,
147
+ isDisabled: isOptionDisabledNow,
148
+ option: childAttributes(optionAttributes),
149
+ label: childAttributes(labelAttributes),
150
+ description: childAttributes(descriptionAttributes),
151
+ };
152
+ });
153
+ const groupAttributes = [
154
+ h.Role('radiogroup'),
155
+ h.AriaOrientation(String.toLowerCase(orientation)),
156
+ h.AriaLabel(ariaLabel),
157
+ ];
158
+ const hiddenInputAttributes = pipe(Option.fromNullishOr(name), Option.flatMap(inputName => Option.map(selectedValue, value => [
159
+ h.Type('hidden'),
160
+ h.Name(inputName),
161
+ h.Value(value),
162
+ ])), Option.getOrElse(() => []));
163
+ return toView({
164
+ group: childAttributes(groupAttributes),
165
+ options: optionInfos,
166
+ selectedValue,
167
+ hiddenInput: childAttributes(hiddenInputAttributes),
168
+ });
169
+ });
170
+ /** Pairs the radio group's `view` and `update` (and `select`) behind a
171
+ * single Value-typed entry point. Declaring the radio group once at
172
+ * module scope ensures the OutMessage's `value` field carries the
173
+ * consumer's union type without an `as` cast at the call site:
174
+ *
175
+ * ```ts
176
+ * const ToolRadioGroup = RadioGroup.create<Tool>()
177
+ *
178
+ * // In view:
179
+ * h.submodel({ view: ToolRadioGroup.view, ... })
180
+ *
181
+ * // In update:
182
+ * const [next, commands, maybeOutMessage] = ToolRadioGroup.update(model, message)
183
+ * // maybeOutMessage: Option<RadioGroup.OutMessage<Tool>>
184
+ * ```
185
+ *
186
+ * The view's `ViewInputs.options` stays typed `ReadonlyArray<string>`;
187
+ * consumers can pass a `ReadonlyArray<MyUnion>` (assignable) and the
188
+ * fenced cast inside `update` types the OutMessage's `value` as
189
+ * `MyUnion`. */
190
+ export const create = () => ({
191
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
192
+ view: internalView,
193
+ update: (model, message) => update(model, message),
194
+ select: (model, value, options) => select(model, value, options),
195
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
196
+ reflectSelectedValue: reflectSelectedValue,
197
+ });
@@ -0,0 +1,3 @@
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
+ //# sourceMappingURL=public.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/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"}
@@ -0,0 +1 @@
1
+ export { init, create, Model, Message, OutMessage, Selected, SelectedOption, CompletedFocusOption, FocusOption, } from './index.js';
@@ -0,0 +1,24 @@
1
+ import type { Attribute } from 'foldkit/html';
2
+ import type { Html } from 'foldkit/html';
3
+ /** Attribute groups the select component provides to the consumer's `toView` callback. */
4
+ export type SelectAttributes<ParentMessage> = Readonly<{
5
+ select: ReadonlyArray<Attribute<ParentMessage>>;
6
+ label: ReadonlyArray<Attribute<ParentMessage>>;
7
+ description: ReadonlyArray<Attribute<ParentMessage>>;
8
+ }>;
9
+ /** Configuration for rendering a select with `view`. */
10
+ export type ViewConfig<ParentMessage> = Readonly<{
11
+ id: string;
12
+ toView: (attributes: SelectAttributes<ParentMessage>) => Html;
13
+ onChange?: (value: string) => ParentMessage;
14
+ value?: string;
15
+ isDisabled?: boolean;
16
+ isInvalid?: boolean;
17
+ isAutofocus?: boolean;
18
+ name?: string;
19
+ }>;
20
+ /** Generates the description element ID from the select's base ID. */
21
+ export declare const descriptionId: (id: string) => string;
22
+ /** Renders an accessible select by building ARIA attribute groups and delegating layout to the consumer's `toView` callback. */
23
+ export declare const view: <ParentMessage>(config: ViewConfig<ParentMessage>) => Html;
24
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/select/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AAIxC,0FAA0F;AAC1F,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,wDAAwD;AACxD,MAAM,MAAM,UAAU,CAAC,aAAa,IAAI,QAAQ,CAAC;IAC/C,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,CAAC,UAAU,EAAE,gBAAgB,CAAC,aAAa,CAAC,KAAK,IAAI,CAAA;IAC7D,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,aAAa,CAAA;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAC,CAAA;AAEF,sEAAsE;AACtE,eAAO,MAAM,aAAa,GAAI,IAAI,MAAM,KAAG,MAA6B,CAAA;AAExE,gIAAgI;AAChI,eAAO,MAAM,IAAI,GAAI,aAAa,EAChC,QAAQ,UAAU,CAAC,aAAa,CAAC,KAChC,IAuDF,CAAA"}
@@ -0,0 +1,40 @@
1
+ import { Predicate } from 'effect';
2
+ import { html } from 'foldkit/html';
3
+ /** Generates the description element ID from the select's base ID. */
4
+ export const descriptionId = (id) => `${id}-description`;
5
+ /** Renders an accessible select by building ARIA attribute groups and delegating layout to the consumer's `toView` callback. */
6
+ export const view = (config) => {
7
+ const h = html();
8
+ const { toView, id, onChange, value, isDisabled = false, isInvalid = false, isAutofocus = false, name, } = config;
9
+ const disabledAttributes = isDisabled
10
+ ? [h.AriaDisabled(true), h.Disabled(true), h.DataAttribute('disabled', '')]
11
+ : [];
12
+ const invalidAttributes = isInvalid
13
+ ? [h.AriaInvalid(true), h.DataAttribute('invalid', '')]
14
+ : [];
15
+ const changeAttributes = Predicate.isNotUndefined(onChange) && !isDisabled
16
+ ? [h.OnChange(onChange)]
17
+ : [];
18
+ const valueAttributes = Predicate.isNotUndefined(value)
19
+ ? [h.Value(value)]
20
+ : [];
21
+ const autofocusAttributes = isAutofocus ? [h.Autofocus(true)] : [];
22
+ const nameAttributes = Predicate.isNotUndefined(name) ? [h.Name(name)] : [];
23
+ const allSelectAttributes = [
24
+ h.Id(id),
25
+ h.AriaDescribedBy(descriptionId(id)),
26
+ ...disabledAttributes,
27
+ ...invalidAttributes,
28
+ ...changeAttributes,
29
+ ...valueAttributes,
30
+ ...autofocusAttributes,
31
+ ...nameAttributes,
32
+ ];
33
+ const labelAttributes = [h.For(id)];
34
+ const descriptionAttributes = [h.Id(descriptionId(id))];
35
+ return toView({
36
+ select: allSelectAttributes,
37
+ label: labelAttributes,
38
+ description: descriptionAttributes,
39
+ });
40
+ };
@@ -0,0 +1,3 @@
1
+ export { view, descriptionId } from './index.js';
2
+ export type { ViewConfig, SelectAttributes } from './index.js';
3
+ //# sourceMappingURL=public.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/select/public.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAEhD,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA"}
@@ -0,0 +1 @@
1
+ export { view, descriptionId } from './index.js';