@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,198 @@
1
+ import { Option, Schema as S } from 'effect';
2
+ import type * as Command from 'foldkit/command';
3
+ import type { Reflect, View as SubmodelView } from 'foldkit/submodel';
4
+ import { type BaseInitConfig, type BaseViewInputs, type Message, type OutMessage } from './shared.js';
5
+ /** Schema for the single-select combobox component's state, tracking open/closed status, active item, input value, selected item, and display text. */
6
+ export declare const Model: S.Struct<{
7
+ readonly maybeSelectedItem: S.Option<S.String>;
8
+ readonly maybeSelectedDisplayText: S.Option<S.String>;
9
+ readonly id: S.String;
10
+ readonly isOpen: S.Boolean;
11
+ readonly isAnimated: S.Boolean;
12
+ readonly isModal: S.Boolean;
13
+ readonly nullable: S.Boolean;
14
+ readonly immediate: S.Boolean;
15
+ readonly selectInputOnFocus: S.Boolean;
16
+ readonly animation: S.Struct<{
17
+ readonly id: S.String;
18
+ readonly isShowing: S.Boolean;
19
+ readonly transitionState: S.Literals<readonly ["Idle", "EnterStart", "EnterAnimating", "LeaveStart", "LeaveAnimating"]>;
20
+ }>;
21
+ readonly maybeActiveItemIndex: S.Option<S.Number>;
22
+ readonly activationTrigger: S.Literals<readonly ["Pointer", "Keyboard"]>;
23
+ readonly inputValue: S.String;
24
+ readonly maybeLastPointerPosition: S.Option<S.Struct<{
25
+ readonly screenX: S.Number;
26
+ readonly screenY: S.Number;
27
+ }>>;
28
+ }>;
29
+ export type Model = typeof Model.Type;
30
+ /** Configuration for creating a single-select combobox model with `init`. `isAnimated` enables CSS transition coordination (default `false`). `isModal` locks page scroll and inerts other elements when open (default `false`). `selectedItem` sets the initial selection (default none). */
31
+ export type InitConfig = BaseInitConfig & Readonly<{
32
+ selectedItem?: string;
33
+ selectedDisplayText?: string;
34
+ }>;
35
+ /** Creates an initial single-select combobox model from a config. Defaults to closed with no active item, empty input, and no selection. */
36
+ export declare const init: (config: InitConfig) => Model;
37
+ /** Processes a combobox message and returns the next model and commands. Closes the combobox on selection (single-select behavior). */
38
+ export declare const update: (model: {
39
+ readonly maybeSelectedItem: Option.Option<string>;
40
+ readonly maybeSelectedDisplayText: Option.Option<string>;
41
+ readonly id: string;
42
+ readonly isOpen: boolean;
43
+ readonly isAnimated: boolean;
44
+ readonly isModal: boolean;
45
+ readonly nullable: boolean;
46
+ readonly immediate: boolean;
47
+ readonly selectInputOnFocus: boolean;
48
+ readonly animation: {
49
+ readonly id: string;
50
+ readonly isShowing: boolean;
51
+ readonly transitionState: "Idle" | "EnterStart" | "EnterAnimating" | "LeaveStart" | "LeaveAnimating";
52
+ };
53
+ readonly maybeActiveItemIndex: Option.Option<number>;
54
+ readonly activationTrigger: "Pointer" | "Keyboard";
55
+ readonly inputValue: string;
56
+ readonly maybeLastPointerPosition: Option.Option<{
57
+ readonly screenX: number;
58
+ readonly screenY: number;
59
+ }>;
60
+ }, message: Message) => readonly [{
61
+ readonly maybeSelectedItem: Option.Option<string>;
62
+ readonly maybeSelectedDisplayText: Option.Option<string>;
63
+ readonly id: string;
64
+ readonly isOpen: boolean;
65
+ readonly isAnimated: boolean;
66
+ readonly isModal: boolean;
67
+ readonly nullable: boolean;
68
+ readonly immediate: boolean;
69
+ readonly selectInputOnFocus: boolean;
70
+ readonly animation: {
71
+ readonly id: string;
72
+ readonly isShowing: boolean;
73
+ readonly transitionState: "Idle" | "EnterStart" | "EnterAnimating" | "LeaveStart" | "LeaveAnimating";
74
+ };
75
+ readonly maybeActiveItemIndex: Option.Option<number>;
76
+ readonly activationTrigger: "Pointer" | "Keyboard";
77
+ readonly inputValue: string;
78
+ readonly maybeLastPointerPosition: Option.Option<{
79
+ readonly screenX: number;
80
+ readonly screenY: number;
81
+ }>;
82
+ }, readonly Readonly<{
83
+ name: string;
84
+ args?: Record<string, unknown>;
85
+ effect: import("effect/Effect").Effect<{
86
+ readonly _tag: "CompletedLockScroll";
87
+ } | {
88
+ readonly _tag: "CompletedUnlockScroll";
89
+ } | {
90
+ readonly _tag: "CompletedInertOthers";
91
+ } | {
92
+ readonly _tag: "CompletedRestoreInert";
93
+ } | {
94
+ readonly _tag: "Closed";
95
+ } | {
96
+ readonly _tag: "Opened";
97
+ readonly maybeActiveItemIndex: Option.Option<number>;
98
+ } | {
99
+ readonly _tag: "BlurredInput";
100
+ } | {
101
+ readonly _tag: "ActivatedItem";
102
+ readonly index: number;
103
+ readonly activationTrigger: "Pointer" | "Keyboard";
104
+ readonly maybeImmediateSelection: Option.Option<{
105
+ readonly item: string;
106
+ readonly displayText: string;
107
+ }>;
108
+ } | {
109
+ readonly _tag: "DeactivatedItem";
110
+ } | {
111
+ readonly _tag: "SelectedItem";
112
+ readonly item: string;
113
+ readonly displayText: string;
114
+ } | {
115
+ readonly _tag: "MovedPointerOverItem";
116
+ readonly index: number;
117
+ readonly screenX: number;
118
+ readonly screenY: number;
119
+ } | {
120
+ readonly _tag: "RequestedItemClick";
121
+ readonly index: number;
122
+ } | {
123
+ readonly _tag: "CompletedFocusInput";
124
+ } | {
125
+ readonly _tag: "CompletedScrollIntoView";
126
+ } | {
127
+ readonly _tag: "CompletedClickItem";
128
+ } | {
129
+ readonly _tag: "CompletedAnchorCombobox";
130
+ } | {
131
+ readonly _tag: "CompletedAttachComboboxPreventBlur";
132
+ } | {
133
+ readonly _tag: "CompletedAttachComboboxSelectOnFocus";
134
+ } | {
135
+ readonly _tag: "CompletedPortalComboboxBackdrop";
136
+ } | {
137
+ readonly _tag: "GotAnimationMessage";
138
+ readonly message: {
139
+ readonly _tag: "Showed";
140
+ } | {
141
+ readonly _tag: "Hid";
142
+ } | {
143
+ readonly _tag: "AdvancedAnimationFrame";
144
+ } | {
145
+ readonly _tag: "EndedAnimation";
146
+ };
147
+ } | {
148
+ readonly _tag: "UpdatedInputValue";
149
+ readonly value: string;
150
+ } | {
151
+ readonly _tag: "PressedToggleButton";
152
+ }, never, never>;
153
+ }>[], Option.Option<Readonly<{
154
+ readonly _tag: "Selected";
155
+ readonly value: string;
156
+ readonly wasAdded: boolean;
157
+ }>>];
158
+ type UpdateReturn = ReturnType<typeof update>;
159
+ /** Programmatically opens the combobox, updating the model and returning
160
+ * focus and modal commands. Use this in domain-event handlers to open the combobox. */
161
+ export declare const open: (model: Model) => UpdateReturn;
162
+ /** Programmatically closes the combobox, updating the model and returning
163
+ * focus and modal commands. Use this in domain-event handlers to close the combobox. */
164
+ export declare const close: (model: Model) => UpdateReturn;
165
+ /** Programmatically selects an item in the single-select combobox. Emits `Selected({ value, wasAdded })`. */
166
+ export declare const selectItem: (model: Model, item: string, displayText: string) => UpdateReturn;
167
+ /** Reflects an externally-sourced selection onto the model without
168
+ * emitting an OutMessage or running selection side effects (no close, no
169
+ * focus). Sets the selected item, its display text, and the input text
170
+ * together, so the input shows the reflected selection. Pass
171
+ * `Option.none()` to clear. Use this to mirror external truth (a URL
172
+ * parameter, restored storage, a server push) onto the combobox.
173
+ * Contrast with `selectItem`, a user/programmatic *choice* that emits
174
+ * `Selected`. Returns the model directly because it produces no commands
175
+ * and no OutMessage. */
176
+ export declare const reflectSelectedItem: Reflect<Model, Option.Option<{
177
+ readonly item: string;
178
+ readonly displayText: string;
179
+ }>>;
180
+ /** Per-render view inputs passed to the view via `h.submodel`'s `viewInputs` field. */
181
+ export type ViewInputs<Item extends string> = BaseViewInputs<Item>;
182
+ /** Pairs the single-select combobox's `view` and `update` (and programmatic
183
+ * helpers) behind a single Item-typed entry point. See `Listbox.create`
184
+ * for the rationale; the combobox factory follows the same shape with
185
+ * `selectItem` taking both `item` and `displayText`. */
186
+ export declare const create: <Item extends string = string>() => Readonly<{
187
+ view: SubmodelView<Model, Message, BaseViewInputs<Item>>;
188
+ update: (model: Model, message: Message) => readonly [Model, ReadonlyArray<Command.Command<Message>>, Option.Option<OutMessage<Item>>];
189
+ selectItem: (model: Model, item: Item, displayText: string) => readonly [Model, ReadonlyArray<Command.Command<Message>>, Option.Option<OutMessage<Item>>];
190
+ open: (model: Model) => readonly [Model, ReadonlyArray<Command.Command<Message>>, Option.Option<OutMessage<Item>>];
191
+ close: (model: Model) => readonly [Model, ReadonlyArray<Command.Command<Message>>, Option.Option<OutMessage<Item>>];
192
+ reflectSelectedItem: Reflect<Model, Option.Option<{
193
+ readonly item: Item;
194
+ readonly displayText: string;
195
+ }>>;
196
+ }>;
197
+ export {};
198
+ //# sourceMappingURL=single.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"single.d.ts","sourceRoot":"","sources":["../../src/combobox/single.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ import { Array, Function, Option, Schema as S, pipe } from 'effect';
2
+ import { evo } from 'foldkit/struct';
3
+ import { BaseModel, Closed, Opened, SelectedItem, Selected as SharedSelected, baseInit, closedBaseModel, makeUpdate, makeView, } from './shared.js';
4
+ // MODEL
5
+ /** Schema for the single-select combobox component's state, tracking open/closed status, active item, input value, selected item, and display text. */
6
+ export const Model = S.Struct({
7
+ ...BaseModel.fields,
8
+ maybeSelectedItem: S.Option(S.String),
9
+ maybeSelectedDisplayText: S.Option(S.String),
10
+ });
11
+ /** Creates an initial single-select combobox model from a config. Defaults to closed with no active item, empty input, and no selection. */
12
+ export const init = (config) => ({
13
+ ...baseInit(config),
14
+ maybeSelectedItem: Option.fromNullishOr(config.selectedItem),
15
+ maybeSelectedDisplayText: Option.fromNullishOr(config.selectedDisplayText ?? config.selectedItem),
16
+ });
17
+ // UPDATE
18
+ /** Processes a combobox message and returns the next model and commands. Closes the combobox on selection (single-select behavior). */
19
+ export const update = makeUpdate({
20
+ handleClose: model => {
21
+ if (model.nullable && model.inputValue === '') {
22
+ return evo(closedBaseModel(model), {
23
+ maybeSelectedItem: () => Option.none(),
24
+ maybeSelectedDisplayText: () => Option.none(),
25
+ inputValue: () => '',
26
+ });
27
+ }
28
+ return evo(closedBaseModel(model), {
29
+ inputValue: () => Option.getOrElse(model.maybeSelectedDisplayText, () => ''),
30
+ });
31
+ },
32
+ handleSelectedItem: (model, item, displayText, context) => {
33
+ const isAlreadySelected = Option.exists(model.maybeSelectedItem, selectedItem => selectedItem === item);
34
+ const nullableDeselect = model.nullable && isAlreadySelected;
35
+ const nextModel = nullableDeselect
36
+ ? evo(closedBaseModel(model), {
37
+ inputValue: () => '',
38
+ maybeSelectedItem: () => Option.none(),
39
+ maybeSelectedDisplayText: () => Option.none(),
40
+ })
41
+ : evo(closedBaseModel(model), {
42
+ inputValue: () => displayText,
43
+ maybeSelectedItem: () => Option.some(item),
44
+ maybeSelectedDisplayText: () => Option.some(displayText),
45
+ });
46
+ return [
47
+ nextModel,
48
+ pipe(Array.getSomes([context.maybeUnlockScroll, context.maybeRestoreInert]), Array.prepend(context.focusInput)),
49
+ Option.some(SharedSelected({ value: item, wasAdded: !nullableDeselect })),
50
+ ];
51
+ },
52
+ handleImmediateActivation: (model, item, displayText) => evo(model, {
53
+ maybeSelectedItem: () => Option.some(item),
54
+ maybeSelectedDisplayText: () => Option.some(displayText),
55
+ }),
56
+ });
57
+ /** Programmatically opens the combobox, updating the model and returning
58
+ * focus and modal commands. Use this in domain-event handlers to open the combobox. */
59
+ export const open = (model) => update(model, Opened({ maybeActiveItemIndex: Option.none() }));
60
+ /** Programmatically closes the combobox, updating the model and returning
61
+ * focus and modal commands. Use this in domain-event handlers to close the combobox. */
62
+ export const close = (model) => update(model, Closed());
63
+ /** Programmatically selects an item in the single-select combobox. Emits `Selected({ value, wasAdded })`. */
64
+ export const selectItem = (model, item, displayText) => update(model, SelectedItem({ item, displayText }));
65
+ /** Reflects an externally-sourced selection onto the model without
66
+ * emitting an OutMessage or running selection side effects (no close, no
67
+ * focus). Sets the selected item, its display text, and the input text
68
+ * together, so the input shows the reflected selection. Pass
69
+ * `Option.none()` to clear. Use this to mirror external truth (a URL
70
+ * parameter, restored storage, a server push) onto the combobox.
71
+ * Contrast with `selectItem`, a user/programmatic *choice* that emits
72
+ * `Selected`. Returns the model directly because it produces no commands
73
+ * and no OutMessage. */
74
+ export const reflectSelectedItem = Function.dual(2, (model, maybeSelection) => Option.match(maybeSelection, {
75
+ onNone: () => evo(model, {
76
+ maybeSelectedItem: () => Option.none(),
77
+ maybeSelectedDisplayText: () => Option.none(),
78
+ inputValue: () => '',
79
+ }),
80
+ onSome: ({ item, displayText }) => evo(model, {
81
+ maybeSelectedItem: () => Option.some(item),
82
+ maybeSelectedDisplayText: () => Option.some(displayText),
83
+ inputValue: () => displayText,
84
+ }),
85
+ }));
86
+ const internalView = makeView({
87
+ isItemSelected: (model, itemValue) => Option.exists(model.maybeSelectedItem, selectedItem => selectedItem === itemValue),
88
+ ariaMultiSelectable: false,
89
+ });
90
+ /** Pairs the single-select combobox's `view` and `update` (and programmatic
91
+ * helpers) behind a single Item-typed entry point. See `Listbox.create`
92
+ * for the rationale; the combobox factory follows the same shape with
93
+ * `selectItem` taking both `item` and `displayText`. */
94
+ export const create = () => {
95
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
96
+ const typedUpdate = update;
97
+ return {
98
+ view: internalView(),
99
+ update: typedUpdate,
100
+ selectItem: (model, item, displayText) => typedUpdate(model, SelectedItem({ item, displayText })),
101
+ open: model => typedUpdate(model, Opened({ maybeActiveItemIndex: Option.none() })),
102
+ close: model => typedUpdate(model, Closed()),
103
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
104
+ reflectSelectedItem: reflectSelectedItem,
105
+ };
106
+ };