foldkit 0.111.1 → 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 (222) hide show
  1. package/README.md +2 -2
  2. package/dist/devTools/host.d.ts +6 -0
  3. package/dist/devTools/host.d.ts.map +1 -0
  4. package/dist/devTools/host.js +4 -0
  5. package/dist/dom/dom.d.ts +6 -1
  6. package/dist/dom/dom.d.ts.map +1 -1
  7. package/dist/dom/dom.js +24 -1
  8. package/dist/html/index.d.ts +4 -3
  9. package/dist/html/index.d.ts.map +1 -1
  10. package/dist/html/index.js +4 -3
  11. package/dist/html/submodel.d.ts +2 -2
  12. package/dist/html/submodel.d.ts.map +1 -1
  13. package/dist/index.d.ts +0 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +0 -1
  16. package/dist/runtime/public.d.ts +1 -1
  17. package/dist/runtime/public.d.ts.map +1 -1
  18. package/dist/runtime/runtime.d.ts +14 -1
  19. package/dist/runtime/runtime.d.ts.map +1 -1
  20. package/dist/runtime/runtime.js +7 -4
  21. package/dist/runtime/subscription.d.ts +1 -1
  22. package/dist/subscription/public.d.ts +1 -1
  23. package/dist/subscription/public.d.ts.map +1 -1
  24. package/package.json +12 -93
  25. package/dist/devTools/overlay-styles.d.ts +0 -3
  26. package/dist/devTools/overlay-styles.d.ts.map +0 -1
  27. package/dist/devTools/overlay-styles.js +0 -706
  28. package/dist/devTools/overlay.d.ts +0 -63
  29. package/dist/devTools/overlay.d.ts.map +0 -1
  30. package/dist/devTools/overlay.js +0 -1244
  31. package/dist/test/apps/disabledButton.d.ts +0 -38
  32. package/dist/test/apps/disabledButton.d.ts.map +0 -1
  33. package/dist/test/apps/disabledButton.js +0 -71
  34. package/dist/ui/anchor.d.ts +0 -38
  35. package/dist/ui/anchor.d.ts.map +0 -1
  36. package/dist/ui/anchor.js +0 -142
  37. package/dist/ui/animation/index.d.ts +0 -49
  38. package/dist/ui/animation/index.d.ts.map +0 -1
  39. package/dist/ui/animation/index.js +0 -74
  40. package/dist/ui/animation/public.d.ts +0 -3
  41. package/dist/ui/animation/public.d.ts.map +0 -1
  42. package/dist/ui/animation/public.js +0 -1
  43. package/dist/ui/animation/schema.d.ts +0 -43
  44. package/dist/ui/animation/schema.d.ts.map +0 -1
  45. package/dist/ui/animation/schema.js +0 -41
  46. package/dist/ui/animation/update.d.ts +0 -24
  47. package/dist/ui/animation/update.d.ts.map +0 -1
  48. package/dist/ui/animation/update.js +0 -67
  49. package/dist/ui/button/index.d.ts +0 -17
  50. package/dist/ui/button/index.d.ts.map +0 -1
  51. package/dist/ui/button/index.js +0 -22
  52. package/dist/ui/button/public.d.ts +0 -3
  53. package/dist/ui/button/public.d.ts.map +0 -1
  54. package/dist/ui/button/public.js +0 -1
  55. package/dist/ui/calendar/index.d.ts +0 -462
  56. package/dist/ui/calendar/index.d.ts.map +0 -1
  57. package/dist/ui/calendar/index.js +0 -824
  58. package/dist/ui/calendar/public.d.ts +0 -3
  59. package/dist/ui/calendar/public.d.ts.map +0 -1
  60. package/dist/ui/calendar/public.js +0 -1
  61. package/dist/ui/checkbox/index.d.ts +0 -119
  62. package/dist/ui/checkbox/index.d.ts.map +0 -1
  63. package/dist/ui/checkbox/index.js +0 -110
  64. package/dist/ui/checkbox/public.d.ts +0 -3
  65. package/dist/ui/checkbox/public.d.ts.map +0 -1
  66. package/dist/ui/checkbox/public.js +0 -1
  67. package/dist/ui/combobox/multi.d.ts +0 -184
  68. package/dist/ui/combobox/multi.d.ts.map +0 -1
  69. package/dist/ui/combobox/multi.js +0 -81
  70. package/dist/ui/combobox/multiPublic.d.ts +0 -3
  71. package/dist/ui/combobox/multiPublic.d.ts.map +0 -1
  72. package/dist/ui/combobox/multiPublic.js +0 -1
  73. package/dist/ui/combobox/public.d.ts +0 -7
  74. package/dist/ui/combobox/public.d.ts.map +0 -1
  75. package/dist/ui/combobox/public.js +0 -3
  76. package/dist/ui/combobox/shared.d.ts +0 -422
  77. package/dist/ui/combobox/shared.d.ts.map +0 -1
  78. package/dist/ui/combobox/shared.js +0 -707
  79. package/dist/ui/combobox/single.d.ts +0 -199
  80. package/dist/ui/combobox/single.d.ts.map +0 -1
  81. package/dist/ui/combobox/single.js +0 -106
  82. package/dist/ui/datePicker/index.d.ts +0 -457
  83. package/dist/ui/datePicker/index.d.ts.map +0 -1
  84. package/dist/ui/datePicker/index.js +0 -317
  85. package/dist/ui/datePicker/public.d.ts +0 -3
  86. package/dist/ui/datePicker/public.d.ts.map +0 -1
  87. package/dist/ui/datePicker/public.js +0 -1
  88. package/dist/ui/dialog/index.d.ts +0 -155
  89. package/dist/ui/dialog/index.d.ts.map +0 -1
  90. package/dist/ui/dialog/index.js +0 -208
  91. package/dist/ui/dialog/public.d.ts +0 -3
  92. package/dist/ui/dialog/public.d.ts.map +0 -1
  93. package/dist/ui/dialog/public.js +0 -1
  94. package/dist/ui/disclosure/index.d.ts +0 -110
  95. package/dist/ui/disclosure/index.d.ts.map +0 -1
  96. package/dist/ui/disclosure/index.js +0 -110
  97. package/dist/ui/disclosure/public.d.ts +0 -3
  98. package/dist/ui/disclosure/public.d.ts.map +0 -1
  99. package/dist/ui/disclosure/public.js +0 -1
  100. package/dist/ui/dragAndDrop/index.d.ts +0 -540
  101. package/dist/ui/dragAndDrop/index.d.ts.map +0 -1
  102. package/dist/ui/dragAndDrop/index.js +0 -535
  103. package/dist/ui/dragAndDrop/public.d.ts +0 -3
  104. package/dist/ui/dragAndDrop/public.d.ts.map +0 -1
  105. package/dist/ui/dragAndDrop/public.js +0 -1
  106. package/dist/ui/fieldset/index.d.ts +0 -21
  107. package/dist/ui/fieldset/index.d.ts.map +0 -1
  108. package/dist/ui/fieldset/index.js +0 -25
  109. package/dist/ui/fieldset/public.d.ts +0 -3
  110. package/dist/ui/fieldset/public.d.ts.map +0 -1
  111. package/dist/ui/fieldset/public.js +0 -1
  112. package/dist/ui/fileDrop/index.d.ts +0 -109
  113. package/dist/ui/fileDrop/index.d.ts.map +0 -1
  114. package/dist/ui/fileDrop/index.js +0 -126
  115. package/dist/ui/fileDrop/public.d.ts +0 -3
  116. package/dist/ui/fileDrop/public.d.ts.map +0 -1
  117. package/dist/ui/fileDrop/public.js +0 -1
  118. package/dist/ui/group.d.ts +0 -8
  119. package/dist/ui/group.d.ts.map +0 -1
  120. package/dist/ui/group.js +0 -13
  121. package/dist/ui/index.d.ts +0 -25
  122. package/dist/ui/index.d.ts.map +0 -1
  123. package/dist/ui/index.js +0 -24
  124. package/dist/ui/input/index.d.ts +0 -26
  125. package/dist/ui/input/index.d.ts.map +0 -1
  126. package/dist/ui/input/index.js +0 -43
  127. package/dist/ui/input/public.d.ts +0 -3
  128. package/dist/ui/input/public.d.ts.map +0 -1
  129. package/dist/ui/input/public.js +0 -1
  130. package/dist/ui/keyboard.d.ts +0 -6
  131. package/dist/ui/keyboard.d.ts.map +0 -1
  132. package/dist/ui/keyboard.js +0 -9
  133. package/dist/ui/listbox/multi.d.ts +0 -190
  134. package/dist/ui/listbox/multi.d.ts.map +0 -1
  135. package/dist/ui/listbox/multi.js +0 -65
  136. package/dist/ui/listbox/multiPublic.d.ts +0 -3
  137. package/dist/ui/listbox/multiPublic.d.ts.map +0 -1
  138. package/dist/ui/listbox/multiPublic.js +0 -1
  139. package/dist/ui/listbox/public.d.ts +0 -7
  140. package/dist/ui/listbox/public.d.ts.map +0 -1
  141. package/dist/ui/listbox/public.js +0 -3
  142. package/dist/ui/listbox/shared.d.ts +0 -431
  143. package/dist/ui/listbox/shared.d.ts.map +0 -1
  144. package/dist/ui/listbox/shared.js +0 -669
  145. package/dist/ui/listbox/single.d.ts +0 -208
  146. package/dist/ui/listbox/single.d.ts.map +0 -1
  147. package/dist/ui/listbox/single.js +0 -73
  148. package/dist/ui/menu/index.d.ts +0 -367
  149. package/dist/ui/menu/index.d.ts.map +0 -1
  150. package/dist/ui/menu/index.js +0 -681
  151. package/dist/ui/menu/public.d.ts +0 -4
  152. package/dist/ui/menu/public.d.ts.map +0 -1
  153. package/dist/ui/menu/public.js +0 -1
  154. package/dist/ui/popover/index.d.ts +0 -267
  155. package/dist/ui/popover/index.d.ts.map +0 -1
  156. package/dist/ui/popover/index.js +0 -345
  157. package/dist/ui/popover/public.d.ts +0 -4
  158. package/dist/ui/popover/public.d.ts.map +0 -1
  159. package/dist/ui/popover/public.js +0 -1
  160. package/dist/ui/radioGroup/index.d.ts +0 -169
  161. package/dist/ui/radioGroup/index.d.ts.map +0 -1
  162. package/dist/ui/radioGroup/index.js +0 -196
  163. package/dist/ui/radioGroup/public.d.ts +0 -3
  164. package/dist/ui/radioGroup/public.d.ts.map +0 -1
  165. package/dist/ui/radioGroup/public.js +0 -1
  166. package/dist/ui/select/index.d.ts +0 -24
  167. package/dist/ui/select/index.d.ts.map +0 -1
  168. package/dist/ui/select/index.js +0 -40
  169. package/dist/ui/select/public.d.ts +0 -3
  170. package/dist/ui/select/public.d.ts.map +0 -1
  171. package/dist/ui/select/public.js +0 -1
  172. package/dist/ui/slider/index.d.ts +0 -318
  173. package/dist/ui/slider/index.d.ts.map +0 -1
  174. package/dist/ui/slider/index.js +0 -336
  175. package/dist/ui/slider/public.d.ts +0 -3
  176. package/dist/ui/slider/public.d.ts.map +0 -1
  177. package/dist/ui/slider/public.js +0 -1
  178. package/dist/ui/switch/index.d.ts +0 -99
  179. package/dist/ui/switch/index.d.ts.map +0 -1
  180. package/dist/ui/switch/index.js +0 -106
  181. package/dist/ui/switch/public.d.ts +0 -3
  182. package/dist/ui/switch/public.d.ts.map +0 -1
  183. package/dist/ui/switch/public.js +0 -1
  184. package/dist/ui/tabs/index.d.ts +0 -155
  185. package/dist/ui/tabs/index.d.ts.map +0 -1
  186. package/dist/ui/tabs/index.js +0 -184
  187. package/dist/ui/tabs/public.d.ts +0 -3
  188. package/dist/ui/tabs/public.d.ts.map +0 -1
  189. package/dist/ui/tabs/public.js +0 -1
  190. package/dist/ui/textarea/index.d.ts +0 -26
  191. package/dist/ui/textarea/index.d.ts.map +0 -1
  192. package/dist/ui/textarea/index.js +0 -44
  193. package/dist/ui/textarea/public.d.ts +0 -3
  194. package/dist/ui/textarea/public.d.ts.map +0 -1
  195. package/dist/ui/textarea/public.js +0 -1
  196. package/dist/ui/toast/index.d.ts +0 -608
  197. package/dist/ui/toast/index.d.ts.map +0 -1
  198. package/dist/ui/toast/index.js +0 -145
  199. package/dist/ui/toast/public.d.ts +0 -4
  200. package/dist/ui/toast/public.d.ts.map +0 -1
  201. package/dist/ui/toast/public.js +0 -1
  202. package/dist/ui/toast/schema.d.ts +0 -154
  203. package/dist/ui/toast/schema.d.ts.map +0 -1
  204. package/dist/ui/toast/schema.js +0 -93
  205. package/dist/ui/toast/update.d.ts +0 -510
  206. package/dist/ui/toast/update.d.ts.map +0 -1
  207. package/dist/ui/toast/update.js +0 -225
  208. package/dist/ui/tooltip/index.d.ts +0 -170
  209. package/dist/ui/tooltip/index.d.ts.map +0 -1
  210. package/dist/ui/tooltip/index.js +0 -252
  211. package/dist/ui/tooltip/public.d.ts +0 -4
  212. package/dist/ui/tooltip/public.d.ts.map +0 -1
  213. package/dist/ui/tooltip/public.js +0 -1
  214. package/dist/ui/typeahead.d.ts +0 -4
  215. package/dist/ui/typeahead.d.ts.map +0 -1
  216. package/dist/ui/typeahead.js +0 -14
  217. package/dist/ui/virtualList/index.d.ts +0 -202
  218. package/dist/ui/virtualList/index.d.ts.map +0 -1
  219. package/dist/ui/virtualList/index.js +0 -391
  220. package/dist/ui/virtualList/public.d.ts +0 -3
  221. package/dist/ui/virtualList/public.d.ts.map +0 -1
  222. package/dist/ui/virtualList/public.js +0 -1
@@ -1,669 +0,0 @@
1
- import { Array, Effect, Equal, Match as M, Option, Predicate, Schema as S, String as Str, pipe, } from 'effect';
2
- import * as Command from '../../command/index.js';
3
- import * as Dom from '../../dom/index.js';
4
- import { OptionExt } from '../../effectExtensions/index.js';
5
- import { defineView, html, } from '../../html/index.js';
6
- import { m } from '../../message/index.js';
7
- import * as Mount from '../../mount/index.js';
8
- import { makeConstrainedEvo } from '../../struct/index.js';
9
- import { AnchorConfig, anchorSetup, portalToBody } from '../anchor.js';
10
- // NOTE: Animation imports are split across schema + update to avoid a circular
11
- // dependency: animation → html → runtime → devtools → listbox → animation.
12
- // The barrel (../animation) imports from html, which starts the cycle.
13
- import { EndedAnimation as AnimationEndedAnimation, Hid as AnimationHid, Message as AnimationMessage, Model as AnimationModel, Showed as AnimationShowed, init as animationInit, } from '../animation/schema.js';
14
- import { update as animationUpdate } from '../animation/update.js';
15
- import { groupContiguous } from '../group.js';
16
- import { findFirstEnabledIndex, isPrintableKey, keyToIndex, } from '../keyboard.js';
17
- import { resolveTypeaheadMatch } from '../typeahead.js';
18
- export { resolveTypeaheadMatch };
19
- // MODEL
20
- /** Schema for the activation trigger: whether the user interacted via mouse or keyboard. */
21
- export const ActivationTrigger = S.Literals(['Pointer', 'Keyboard']);
22
- /** Schema for the listbox orientation: whether items flow vertically or horizontally. */
23
- export const Orientation = S.Literals(['Vertical', 'Horizontal']);
24
- /** Schema fields shared by all listbox variants (single-select and multi-select). Spread into each variant's `S.Struct` to avoid duplicating field definitions. */
25
- export const BaseModel = S.Struct({
26
- id: S.String,
27
- isOpen: S.Boolean,
28
- isAnimated: S.Boolean,
29
- isModal: S.Boolean,
30
- orientation: Orientation,
31
- animation: AnimationModel,
32
- maybeActiveItemIndex: S.Option(S.Number),
33
- activationTrigger: ActivationTrigger,
34
- searchQuery: S.String,
35
- searchVersion: S.Number,
36
- maybeLastPointerPosition: S.Option(S.Struct({ screenX: S.Number, screenY: S.Number })),
37
- maybeLastButtonPointerType: S.Option(S.String),
38
- });
39
- /** Creates the shared base fields for a listbox model from a config. Each variant spreads this and adds its selection field. */
40
- export const baseInit = (config) => ({
41
- id: config.id,
42
- isOpen: false,
43
- isAnimated: config.isAnimated ?? false,
44
- isModal: config.isModal ?? false,
45
- orientation: config.orientation ?? 'Vertical',
46
- animation: animationInit({ id: `${config.id}-listbox` }),
47
- maybeActiveItemIndex: Option.none(),
48
- activationTrigger: 'Keyboard',
49
- searchQuery: '',
50
- searchVersion: 0,
51
- maybeLastPointerPosition: Option.none(),
52
- maybeLastButtonPointerType: Option.none(),
53
- });
54
- // MESSAGE
55
- /** Sent when the listbox opens via button click or keyboard. Contains an optional initial active item index: None for pointer, Some for keyboard. */
56
- export const Opened = m('Opened', {
57
- maybeActiveItemIndex: S.Option(S.Number),
58
- });
59
- /** Sent when the listbox closes via Escape key or backdrop click. */
60
- export const Closed = m('Closed');
61
- /** Sent when the listbox items container loses focus. */
62
- export const BlurredItems = m('BlurredItems');
63
- /** Sent when an item is highlighted via arrow keys or mouse hover. Includes activation trigger. */
64
- export const ActivatedItem = m('ActivatedItem', {
65
- index: S.Number,
66
- activationTrigger: ActivationTrigger,
67
- });
68
- /** Sent when the mouse leaves an enabled item. */
69
- export const DeactivatedItem = m('DeactivatedItem');
70
- /** Sent when an item is selected via Enter, Space, or click. Contains the item's string value. */
71
- export const SelectedItem = m('SelectedItem', { item: S.String });
72
- /** Sent when Enter or Space is pressed on the active item, triggering a programmatic click on the DOM element. */
73
- export const RequestedItemClick = m('RequestedItemClick', {
74
- index: S.Number,
75
- });
76
- /** Sent when a printable character is typed for typeahead search. */
77
- export const Searched = m('Searched', {
78
- key: S.String,
79
- maybeTargetIndex: S.Option(S.Number),
80
- });
81
- /** Sent after the search debounce period to clear the accumulated query. */
82
- export const ClearedSearch = m('ClearedSearch', { version: S.Number });
83
- /** Sent when the pointer moves over a listbox item, carrying screen coordinates for tracked-pointer comparison. */
84
- export const MovedPointerOverItem = m('MovedPointerOverItem', {
85
- index: S.Number,
86
- screenX: S.Number,
87
- screenY: S.Number,
88
- });
89
- /** Sent when the scroll lock command completes. */
90
- export const CompletedLockScroll = m('CompletedLockScroll');
91
- /** Sent when the scroll unlock command completes. */
92
- export const CompletedUnlockScroll = m('CompletedUnlockScroll');
93
- /** Sent when the inert-others command completes. */
94
- export const CompletedInertOthers = m('CompletedInertOthers');
95
- /** Sent when the restore-inert command completes. */
96
- export const CompletedRestoreInert = m('CompletedRestoreInert');
97
- /** Sent when the focus-button command completes after closing. */
98
- export const CompletedFocusButton = m('CompletedFocusButton');
99
- /** Sent when the focus-items command completes after opening. */
100
- export const CompletedFocusItems = m('CompletedFocusItems');
101
- /** Sent when the scroll-into-view command completes after keyboard activation. */
102
- export const CompletedScrollIntoView = m('CompletedScrollIntoView');
103
- /** Sent when the programmatic item click command completes. */
104
- export const CompletedClickItem = m('CompletedClickItem');
105
- /** Sent when a mouse click on the button is ignored because pointer-down already handled the toggle. */
106
- export const IgnoredMouseClick = m('IgnoredMouseClick');
107
- /** Sent when a Space key-up is captured to prevent page scrolling. */
108
- export const SuppressedSpaceScroll = m('SuppressedSpaceScroll');
109
- /** Sent when the listbox items panel mounts and Floating UI has positioned it. Update no-ops; surfaces the positioning side effect for DevTools. */
110
- export const CompletedAnchorListbox = m('CompletedAnchorListbox');
111
- /** Sent when the listbox backdrop mounts and is portaled to the document body. Update no-ops; surfaces the portal side effect for DevTools. */
112
- export const CompletedPortalListboxBackdrop = m('CompletedPortalListboxBackdrop');
113
- /** Wraps an Animation submodel message for delegation. */
114
- export const GotAnimationMessage = m('GotAnimationMessage', {
115
- message: AnimationMessage,
116
- });
117
- /** Sent when the user presses a pointer device on the listbox button. Records pointer type for click handling. */
118
- export const PressedPointerOnButton = m('PressedPointerOnButton', {
119
- pointerType: S.String,
120
- button: S.Number,
121
- });
122
- /** Union of all messages the listbox component can produce. */
123
- export const Message = S.Union([
124
- Opened,
125
- Closed,
126
- BlurredItems,
127
- ActivatedItem,
128
- DeactivatedItem,
129
- SelectedItem,
130
- MovedPointerOverItem,
131
- RequestedItemClick,
132
- Searched,
133
- ClearedSearch,
134
- CompletedLockScroll,
135
- CompletedUnlockScroll,
136
- CompletedInertOthers,
137
- CompletedRestoreInert,
138
- CompletedFocusButton,
139
- CompletedFocusItems,
140
- CompletedScrollIntoView,
141
- CompletedClickItem,
142
- IgnoredMouseClick,
143
- SuppressedSpaceScroll,
144
- CompletedAnchorListbox,
145
- CompletedPortalListboxBackdrop,
146
- GotAnimationMessage,
147
- PressedPointerOnButton,
148
- ]);
149
- // OUT MESSAGE
150
- /** Sent when a single-select listbox commits a selection, or when a multi-select listbox toggles an item. Generic over `Value extends string`: the runtime schema stores `value: string`, but the type-level OutMessage exposes `value: Value` so consumers who supply `items: ReadonlyArray<MyUnion>` receive `value: MyUnion` from `update<MyUnion>` without casting. The cast is fenced inside this module's `update` return, sound because the value was extracted from the items array the consumer supplied. */
151
- export const Selected = m('Selected', {
152
- value: S.String,
153
- wasAdded: S.Boolean,
154
- });
155
- /** Union of out-messages the listbox component can produce. Single-select listboxes always emit `wasAdded: true`. Multi-select listboxes emit `wasAdded: true` when adding to the selection and `wasAdded: false` when toggling off. */
156
- export const OutMessage = S.Union([Selected]);
157
- // CONSTANTS
158
- export const SEARCH_DEBOUNCE_MILLISECONDS = 350;
159
- export const LEFT_MOUSE_BUTTON = 0;
160
- // SELECTORS
161
- export const buttonSelector = (id) => `#${id}-button`;
162
- export const itemsSelector = (id) => `#${id}-items`;
163
- export const itemSelector = (id, index) => `#${id}-item-${index}`;
164
- export const itemId = (id, index) => `${id}-item-${index}`;
165
- // HELPERS
166
- const constrainedEvo = makeConstrainedEvo();
167
- export const closedModel = (model) => constrainedEvo(model, {
168
- isOpen: () => false,
169
- maybeActiveItemIndex: () => Option.none(),
170
- searchQuery: () => '',
171
- searchVersion: () => 0,
172
- maybeLastPointerPosition: () => Option.none(),
173
- maybeLastButtonPointerType: () => Option.none(),
174
- });
175
- /** Prevents page scrolling while the listbox is open in modal mode. */
176
- export const LockScroll = Command.define('LockScroll', CompletedLockScroll)(Dom.lockScroll.pipe(Effect.as(CompletedLockScroll())));
177
- /** Re-enables page scrolling after the listbox closes. */
178
- export const UnlockScroll = Command.define('UnlockScroll', CompletedUnlockScroll)(Dom.unlockScroll.pipe(Effect.as(CompletedUnlockScroll())));
179
- /** Marks all elements outside the listbox as inert for modal behavior. */
180
- export const InertOthers = Command.define('InertOthers', { id: S.String }, CompletedInertOthers)(({ id }) => Dom.inertOthers(id, [buttonSelector(id), itemsSelector(id)]).pipe(Effect.as(CompletedInertOthers())));
181
- /** Removes the inert attribute from elements outside the listbox. */
182
- export const RestoreInert = Command.define('RestoreInert', { id: S.String }, CompletedRestoreInert)(({ id }) => Dom.restoreInert(id).pipe(Effect.as(CompletedRestoreInert())));
183
- /** Moves focus back to the listbox button after closing. */
184
- export const FocusButton = Command.define('FocusButton', { id: S.String }, CompletedFocusButton)(({ id }) => Dom.focus(buttonSelector(id)).pipe(Effect.ignore, Effect.as(CompletedFocusButton())));
185
- /** Moves focus to the listbox items container after opening. */
186
- export const FocusItems = Command.define('FocusItems', { id: S.String }, CompletedFocusItems)(({ id }) => Dom.focus(itemsSelector(id)).pipe(Effect.ignore, Effect.as(CompletedFocusItems())));
187
- /** Scrolls the active listbox item into view after keyboard navigation. */
188
- export const ScrollIntoView = Command.define('ScrollIntoView', { id: S.String, index: S.Number }, CompletedScrollIntoView)(({ id, index }) => Dom.scrollIntoView(itemSelector(id, index)).pipe(Effect.ignore, Effect.as(CompletedScrollIntoView())));
189
- /** Programmatically clicks the active listbox item's DOM element. */
190
- export const ClickItem = Command.define('ClickItem', { id: S.String, index: S.Number }, CompletedClickItem)(({ id, index }) => Dom.clickElement(itemSelector(id, index)).pipe(Effect.ignore, Effect.as(CompletedClickItem())));
191
- /** Waits for the typeahead search debounce period before clearing the query. */
192
- export const DelayClearSearch = Command.define('DelayClearSearch', { version: S.Number }, ClearedSearch)(({ version }) => Effect.sleep(SEARCH_DEBOUNCE_MILLISECONDS).pipe(Effect.as(ClearedSearch({ version }))));
193
- /** Detects whether the listbox button moved or the leave animation ended. Whichever comes first; both outcomes signal the Animation submodel that leave is complete. */
194
- export const DetectMovementOrAnimationEnd = Command.define('DetectMovementOrAnimationEnd', { id: S.String }, GotAnimationMessage)(({ id }) => Effect.raceFirst(Dom.detectElementMovement(buttonSelector(id)).pipe(Effect.as(GotAnimationMessage({ message: AnimationEndedAnimation() }))), Dom.waitForAnimationSettled(itemsSelector(id)).pipe(Effect.as(GotAnimationMessage({ message: AnimationEndedAnimation() })))));
195
- export const makeUpdate = (handleSelectedItem) => {
196
- const withUpdateReturn = M.withReturnType();
197
- const delegateToAnimation = (model, animationMessage) => {
198
- const [nextAnimation, animationCommands, maybeOutMessage] = animationUpdate(model.animation, animationMessage);
199
- const mappedCommands = Command.mapMessages(animationCommands, message => GotAnimationMessage({ message }));
200
- const additionalCommands = Option.match(maybeOutMessage, {
201
- onNone: () => [],
202
- onSome: M.type().pipe(M.tagsExhaustive({
203
- StartedLeaveAnimating: () => [
204
- DetectMovementOrAnimationEnd({ id: model.id }),
205
- ],
206
- TransitionedOut: () => [],
207
- })),
208
- });
209
- return [
210
- constrainedEvo(model, { animation: () => nextAnimation }),
211
- [...mappedCommands, ...additionalCommands],
212
- Option.none(),
213
- ];
214
- };
215
- const openListbox = (baseModel, openCommands) => {
216
- if (baseModel.isAnimated) {
217
- const [nextModel, animationCommands] = delegateToAnimation(baseModel, AnimationShowed());
218
- return [
219
- constrainedEvo(nextModel, { isOpen: () => true }),
220
- [...openCommands, ...animationCommands],
221
- Option.none(),
222
- ];
223
- }
224
- return [
225
- constrainedEvo(baseModel, { isOpen: () => true }),
226
- openCommands,
227
- Option.none(),
228
- ];
229
- };
230
- const closeListbox = (baseModel, commands, maybeOutMessage = Option.none()) => {
231
- const closed = closedModel(baseModel);
232
- if (baseModel.isAnimated) {
233
- const [nextModel, animationCommands] = delegateToAnimation(closed, AnimationHid());
234
- return [nextModel, [...commands, ...animationCommands], maybeOutMessage];
235
- }
236
- return [closed, commands, maybeOutMessage];
237
- };
238
- const internalUpdate = (model, message) => {
239
- const maybeLockScroll = OptionExt.when(model.isModal, LockScroll());
240
- const maybeUnlockScroll = OptionExt.when(model.isModal, UnlockScroll());
241
- const maybeInertOthers = OptionExt.when(model.isModal, InertOthers({ id: model.id }));
242
- const maybeRestoreInert = OptionExt.when(model.isModal, RestoreInert({ id: model.id }));
243
- const focusButton = FocusButton({ id: model.id });
244
- const focusItems = FocusItems({ id: model.id });
245
- const openCommands = [
246
- ...Array.getSomes([maybeLockScroll, maybeInertOthers]),
247
- focusItems,
248
- ];
249
- const closeWithFocusCommands = [
250
- focusButton,
251
- ...Array.getSomes([maybeUnlockScroll, maybeRestoreInert]),
252
- ];
253
- const closeWithoutFocusCommands = Array.getSomes([
254
- maybeUnlockScroll,
255
- maybeRestoreInert,
256
- ]);
257
- return M.value(message).pipe(withUpdateReturn, M.tag('CompletedLockScroll', 'CompletedUnlockScroll', 'CompletedInertOthers', 'CompletedRestoreInert', 'CompletedFocusButton', 'CompletedFocusItems', 'CompletedScrollIntoView', 'CompletedClickItem', 'SuppressedSpaceScroll', 'CompletedAnchorListbox', 'CompletedPortalListboxBackdrop', () => [model, [], Option.none()]), M.tagsExhaustive({
258
- Opened: ({ maybeActiveItemIndex }) => openListbox(constrainedEvo(model, {
259
- maybeActiveItemIndex: () => maybeActiveItemIndex,
260
- activationTrigger: () => Option.match(maybeActiveItemIndex, {
261
- onNone: () => 'Pointer',
262
- onSome: () => 'Keyboard',
263
- }),
264
- searchQuery: () => '',
265
- searchVersion: () => 0,
266
- maybeLastPointerPosition: () => Option.none(),
267
- }), openCommands),
268
- Closed: () => closeListbox(model, closeWithFocusCommands),
269
- BlurredItems: () => {
270
- if (Option.exists(model.maybeLastButtonPointerType, Equal.equals('mouse'))) {
271
- return [model, [], Option.none()];
272
- }
273
- return closeListbox(model, closeWithoutFocusCommands);
274
- },
275
- ActivatedItem: ({ index, activationTrigger }) => [
276
- constrainedEvo(model, {
277
- maybeActiveItemIndex: () => Option.some(index),
278
- activationTrigger: () => activationTrigger,
279
- }),
280
- activationTrigger === 'Keyboard'
281
- ? [ScrollIntoView({ id: model.id, index })]
282
- : [],
283
- Option.none(),
284
- ],
285
- MovedPointerOverItem: ({ index, screenX, screenY }) => {
286
- const isSamePosition = Option.exists(model.maybeLastPointerPosition, position => position.screenX === screenX && position.screenY === screenY);
287
- if (isSamePosition) {
288
- return [model, [], Option.none()];
289
- }
290
- return [
291
- constrainedEvo(model, {
292
- maybeActiveItemIndex: () => Option.some(index),
293
- activationTrigger: () => 'Pointer',
294
- maybeLastPointerPosition: () => Option.some({ screenX, screenY }),
295
- }),
296
- [],
297
- Option.none(),
298
- ];
299
- },
300
- DeactivatedItem: () => model.activationTrigger === 'Pointer'
301
- ? [
302
- constrainedEvo(model, {
303
- maybeActiveItemIndex: () => Option.none(),
304
- }),
305
- [],
306
- Option.none(),
307
- ]
308
- : [model, [], Option.none()],
309
- SelectedItem: ({ item }) => handleSelectedItem(model, item, {
310
- closeWithFocus: (closeModel, maybeOutMessage = Option.none()) => closeListbox(closeModel, closeWithFocusCommands, maybeOutMessage),
311
- closeWithoutFocus: (closeModel, maybeOutMessage = Option.none()) => closeListbox(closeModel, closeWithoutFocusCommands, maybeOutMessage),
312
- }),
313
- RequestedItemClick: ({ index }) => [
314
- model,
315
- [ClickItem({ id: model.id, index })],
316
- Option.none(),
317
- ],
318
- Searched: ({ key, maybeTargetIndex }) => {
319
- const nextSearchQuery = model.searchQuery + key;
320
- const nextSearchVersion = model.searchVersion + 1;
321
- return [
322
- constrainedEvo(model, {
323
- searchQuery: () => nextSearchQuery,
324
- searchVersion: () => nextSearchVersion,
325
- maybeActiveItemIndex: () => Option.orElse(maybeTargetIndex, () => model.maybeActiveItemIndex),
326
- }),
327
- [DelayClearSearch({ version: nextSearchVersion })],
328
- Option.none(),
329
- ];
330
- },
331
- ClearedSearch: ({ version }) => {
332
- if (version !== model.searchVersion) {
333
- return [model, [], Option.none()];
334
- }
335
- return [
336
- constrainedEvo(model, { searchQuery: () => '' }),
337
- [],
338
- Option.none(),
339
- ];
340
- },
341
- GotAnimationMessage: ({ message: animationMessage }) => delegateToAnimation(model, animationMessage),
342
- PressedPointerOnButton: ({ pointerType, button }) => {
343
- const withPointerType = constrainedEvo(model, {
344
- maybeLastButtonPointerType: () => Option.some(pointerType),
345
- });
346
- if (pointerType !== 'mouse' || button !== LEFT_MOUSE_BUTTON) {
347
- return [withPointerType, [], Option.none()];
348
- }
349
- if (model.isOpen) {
350
- const [closed, commands] = closeListbox(withPointerType, closeWithFocusCommands);
351
- return [
352
- constrainedEvo(closed, {
353
- maybeLastButtonPointerType: () => Option.some(pointerType),
354
- }),
355
- commands,
356
- Option.none(),
357
- ];
358
- }
359
- return openListbox(constrainedEvo(withPointerType, {
360
- maybeActiveItemIndex: () => Option.none(),
361
- activationTrigger: () => 'Pointer',
362
- searchQuery: () => '',
363
- searchVersion: () => 0,
364
- maybeLastPointerPosition: () => Option.none(),
365
- }), openCommands);
366
- },
367
- IgnoredMouseClick: () => [
368
- constrainedEvo(model, {
369
- maybeLastButtonPointerType: () => Option.none(),
370
- }),
371
- [],
372
- Option.none(),
373
- ],
374
- }));
375
- };
376
- return internalUpdate;
377
- };
378
- /** The anchor-positioning Mount this Listbox renders on its items panel.
379
- * The panel is always anchored to the button via Floating UI and portaled
380
- * to the document body (opt out of portaling with `anchor.portal: false`),
381
- * so it escapes ancestor stacking contexts and overflow clipping. Exposed
382
- * so Scene tests can call
383
- * `Scene.Mount.resolve(AnchorListbox, CompletedAnchorListbox())`. */
384
- export const AnchorListbox = Mount.define('AnchorListbox', { buttonId: S.String, anchor: AnchorConfig }, CompletedAnchorListbox)(({ buttonId, anchor }) => element => Effect.gen(function* () {
385
- yield* Effect.acquireRelease(Effect.sync(() => anchorSetup({ buttonId, anchor })(element)), cleanup => Effect.sync(cleanup));
386
- return CompletedAnchorListbox();
387
- }));
388
- /** The backdrop-portaling Mount this Listbox renders. Exposed so Scene tests can
389
- * call `Scene.Mount.resolve(PortalListboxBackdrop, CompletedPortalListboxBackdrop())` to
390
- * acknowledge the mount produced by the rendered backdrop. */
391
- export const PortalListboxBackdrop = Mount.define('PortalListboxBackdrop', CompletedPortalListboxBackdrop)(element => Effect.gen(function* () {
392
- yield* Effect.acquireRelease(Effect.sync(() => portalToBody(element)), cleanup => Effect.sync(cleanup));
393
- return CompletedPortalListboxBackdrop();
394
- }));
395
- export const makeView = (behavior) => {
396
- const impl = defineView((model, viewInputs) => {
397
- const h = html();
398
- const { id, isOpen, orientation, animation: { transitionState }, maybeActiveItemIndex, searchQuery, maybeLastButtonPointerType, } = model;
399
- const { items, itemToConfig, isItemDisabled, isButtonDisabled, buttonContent, buttonClassName, buttonAttributes = [], itemsClassName, itemsAttributes = [], itemsScrollClassName, itemsScrollAttributes = [], backdropClassName, backdropAttributes = [], className, attributes = [], itemGroupKey, groupToHeading, groupClassName, groupAttributes = [], separatorClassName, separatorAttributes = [], anchor = {}, name, form, isDisabled, isInvalid, } = viewInputs;
400
- const itemToValue = viewInputs.itemToValue ?? ((item) => String(item));
401
- const itemToSearchText = viewInputs.itemToSearchText ?? ((item) => itemToValue(item));
402
- const isLeaving = transitionState === 'LeaveStart' || transitionState === 'LeaveAnimating';
403
- const isVisible = isOpen || isLeaving;
404
- const animationAttributes = M.value(transitionState).pipe(M.when('EnterStart', () => [
405
- h.DataAttribute('closed', ''),
406
- h.DataAttribute('enter', ''),
407
- h.DataAttribute('transition', ''),
408
- ]), M.when('EnterAnimating', () => [
409
- h.DataAttribute('enter', ''),
410
- h.DataAttribute('transition', ''),
411
- ]), M.when('LeaveStart', () => [
412
- h.DataAttribute('leave', ''),
413
- h.DataAttribute('transition', ''),
414
- ]), M.when('LeaveAnimating', () => [
415
- h.DataAttribute('closed', ''),
416
- h.DataAttribute('leave', ''),
417
- h.DataAttribute('transition', ''),
418
- ]), M.orElse(() => []));
419
- const isItemDisabledByIndex = (index) => Predicate.isNotUndefined(isItemDisabled) &&
420
- pipe(items, Array.get(index), Option.exists(item => isItemDisabled(item, index)));
421
- const isButtonEffectivelyDisabled = isDisabled || isButtonDisabled;
422
- const nextKey = orientation === 'Horizontal' ? 'ArrowRight' : 'ArrowDown';
423
- const previousKey = orientation === 'Horizontal' ? 'ArrowLeft' : 'ArrowUp';
424
- const navigationKeys = [
425
- nextKey,
426
- previousKey,
427
- 'Home',
428
- 'End',
429
- 'PageUp',
430
- 'PageDown',
431
- ];
432
- const isNavigationKey = (key) => Array.contains(navigationKeys, key);
433
- const firstEnabledIndex = findFirstEnabledIndex(items.length, 0, isItemDisabledByIndex)(0, 1);
434
- const lastEnabledIndex = findFirstEnabledIndex(items.length, 0, isItemDisabledByIndex)(items.length - 1, -1);
435
- const selectedItemIndex = behavior.selectedItemIndex(model, items, itemToValue);
436
- const handleButtonKeyDown = (key) => {
437
- if (isOpen) {
438
- return handleItemsKeyDown(key);
439
- }
440
- return M.value(key).pipe(M.whenOr('Enter', ' ', 'ArrowDown', () => Option.some(Opened({
441
- maybeActiveItemIndex: Option.orElse(selectedItemIndex, () => Option.some(firstEnabledIndex)),
442
- }))), M.when('ArrowUp', () => Option.some(Opened({
443
- maybeActiveItemIndex: Option.orElse(selectedItemIndex, () => Option.some(lastEnabledIndex)),
444
- }))), M.orElse(() => Option.none()));
445
- };
446
- const handleButtonPointerDown = (pointerType, button) => Option.some(PressedPointerOnButton({ pointerType, button }));
447
- const handleButtonClick = () => {
448
- const isMouse = Option.exists(maybeLastButtonPointerType, type => type === 'mouse');
449
- if (isMouse) {
450
- return IgnoredMouseClick();
451
- }
452
- else if (isOpen) {
453
- return Closed();
454
- }
455
- else {
456
- return Opened({ maybeActiveItemIndex: Option.none() });
457
- }
458
- };
459
- const handleSpaceKeyUp = (key) => OptionExt.when(key === ' ', SuppressedSpaceScroll());
460
- const resolveActiveIndex = (key) => Option.match(maybeActiveItemIndex, {
461
- onNone: () => M.value(key).pipe(M.whenOr(previousKey, 'End', 'PageDown', () => lastEnabledIndex), M.orElse(() => firstEnabledIndex)),
462
- onSome: activeIndex => keyToIndex(nextKey, previousKey, items.length, activeIndex, isItemDisabledByIndex)(key),
463
- });
464
- const searchForKey = (key) => {
465
- const nextQuery = searchQuery + key;
466
- const maybeTargetIndex = resolveTypeaheadMatch(items, nextQuery, maybeActiveItemIndex, isItemDisabledByIndex, itemToSearchText, Str.isNonEmpty(searchQuery));
467
- return Option.some(Searched({ key, maybeTargetIndex }));
468
- };
469
- const handleItemsKeyDown = (key) => M.value(key).pipe(M.when('Escape', () => Option.some(Closed())), M.when('Enter', () => Option.map(maybeActiveItemIndex, index => RequestedItemClick({ index }))), M.when(' ', () => Str.isNonEmpty(searchQuery)
470
- ? searchForKey(' ')
471
- : Option.map(maybeActiveItemIndex, index => RequestedItemClick({ index }))), M.when(isNavigationKey, () => Option.some(ActivatedItem({
472
- index: resolveActiveIndex(key),
473
- activationTrigger: 'Keyboard',
474
- }))), M.when(isPrintableKey, () => searchForKey(key)), M.orElse(() => Option.none()));
475
- const resolvedButtonAttributes = [
476
- h.Id(`${id}-button`),
477
- h.Type('button'),
478
- h.AriaHasPopup('listbox'),
479
- h.AriaExpanded(isVisible),
480
- h.AriaControls(`${id}-items`),
481
- ...(isButtonEffectivelyDisabled
482
- ? [h.AriaDisabled(true), h.DataAttribute('disabled', '')]
483
- : [
484
- h.OnPointerDown(handleButtonPointerDown),
485
- h.OnKeyDownPreventDefault(handleButtonKeyDown),
486
- h.OnKeyUpPreventDefault(handleSpaceKeyUp),
487
- h.OnClick(handleButtonClick()),
488
- ]),
489
- ...(isVisible
490
- ? [
491
- h.DataAttribute('open', ''),
492
- h.Style({ position: 'relative', zIndex: '1' }),
493
- ]
494
- : []),
495
- ...(isInvalid ? [h.DataAttribute('invalid', '')] : []),
496
- ...(buttonClassName ? [h.Class(buttonClassName)] : []),
497
- ...buttonAttributes,
498
- ];
499
- const maybeActiveDescendant = Option.match(maybeActiveItemIndex, {
500
- onNone: () => [],
501
- onSome: index => [h.AriaActiveDescendant(itemId(id, index))],
502
- });
503
- const anchorAttributes = [
504
- h.Style({
505
- position: 'absolute',
506
- margin: '0',
507
- visibility: 'hidden',
508
- }),
509
- h.OnMount(AnchorListbox({ buttonId: `${id}-button`, anchor })),
510
- ];
511
- const itemsContainerAttributes = [
512
- h.Id(`${id}-items`),
513
- h.Role('listbox'),
514
- h.AriaOrientation(Str.toLowerCase(orientation)),
515
- ...(behavior.ariaMultiSelectable ? [h.AriaMultiSelectable(true)] : []),
516
- h.AriaLabelledBy(`${id}-button`),
517
- ...maybeActiveDescendant,
518
- h.Tabindex(0),
519
- ...anchorAttributes,
520
- ...animationAttributes,
521
- ...(isLeaving
522
- ? []
523
- : [
524
- h.OnKeyDownPreventDefault(handleItemsKeyDown),
525
- h.OnKeyUpPreventDefault(handleSpaceKeyUp),
526
- h.OnBlur(BlurredItems()),
527
- ]),
528
- ...(itemsClassName ? [h.Class(itemsClassName)] : []),
529
- ...itemsAttributes,
530
- ];
531
- const listboxItems = Array.map(items, (item, index) => {
532
- const isActiveItem = Option.exists(maybeActiveItemIndex, activeIndex => activeIndex === index);
533
- const isDisabledItem = isItemDisabledByIndex(index);
534
- const isSelectedItem = behavior.isItemSelected(model, itemToValue(item));
535
- const itemConfig = itemToConfig(item, {
536
- isActive: isActiveItem,
537
- isDisabled: isDisabledItem,
538
- isSelected: isSelectedItem,
539
- });
540
- const isInteractive = !isDisabledItem && !isLeaving;
541
- return h.keyed('div')(itemId(id, index), [
542
- h.Id(itemId(id, index)),
543
- h.Role('option'),
544
- h.AriaSelected(isSelectedItem),
545
- ...(isActiveItem ? [h.DataAttribute('active', '')] : []),
546
- ...(isSelectedItem ? [h.DataAttribute('selected', '')] : []),
547
- ...(isDisabledItem
548
- ? [h.AriaDisabled(true), h.DataAttribute('disabled', '')]
549
- : []),
550
- ...(isInteractive
551
- ? [
552
- h.OnClick(SelectedItem({ item: itemToValue(item) })),
553
- ...(isActiveItem
554
- ? []
555
- : [
556
- h.OnPointerMove((screenX, screenY, pointerType) => OptionExt.when(pointerType !== 'touch', MovedPointerOverItem({
557
- index,
558
- screenX,
559
- screenY,
560
- }))),
561
- ]),
562
- h.OnPointerLeave(pointerType => OptionExt.when(pointerType !== 'touch', DeactivatedItem())),
563
- ]
564
- : []),
565
- ...(itemConfig.className ? [h.Class(itemConfig.className)] : []),
566
- ], [itemConfig.content]);
567
- });
568
- const renderGroupedItems = () => {
569
- if (!itemGroupKey) {
570
- return listboxItems;
571
- }
572
- const segments = groupContiguous(listboxItems, (_, index) => Array.get(items, index).pipe(Option.match({
573
- onNone: () => '',
574
- onSome: item => itemGroupKey(item, index),
575
- })));
576
- return Array.flatMap(segments, (segment, segmentIndex) => {
577
- const maybeHeading = Option.fromNullishOr(groupToHeading?.(segment.key));
578
- const headingId = `${id}-heading-${segment.key}`;
579
- const headingElement = Option.match(maybeHeading, {
580
- onNone: () => [],
581
- onSome: heading => [
582
- h.keyed('div')(headingId, [
583
- h.Id(headingId),
584
- h.Role('presentation'),
585
- ...(heading.className ? [h.Class(heading.className)] : []),
586
- ], [heading.content]),
587
- ],
588
- });
589
- const groupContent = [...headingElement, ...segment.items];
590
- const groupElement = h.keyed('div')(`${id}-group-${segment.key}`, [
591
- h.Role('group'),
592
- ...(Option.isSome(maybeHeading)
593
- ? [h.AriaLabelledBy(headingId)]
594
- : []),
595
- ...(groupClassName ? [h.Class(groupClassName)] : []),
596
- ...groupAttributes,
597
- ], groupContent);
598
- const separator = segmentIndex > 0 &&
599
- (separatorClassName ||
600
- Array.isReadonlyArrayNonEmpty(separatorAttributes))
601
- ? [
602
- h.keyed('div')(`${id}-separator-${segmentIndex}`, [
603
- h.Role('separator'),
604
- ...(separatorClassName
605
- ? [h.Class(separatorClassName)]
606
- : []),
607
- ...separatorAttributes,
608
- ], []),
609
- ]
610
- : [];
611
- return [...separator, groupElement];
612
- });
613
- };
614
- const backdrop = h.keyed('div')(`${id}-backdrop`, [
615
- h.OnMount(PortalListboxBackdrop()),
616
- ...(isLeaving ? [] : [h.OnClick(Closed())]),
617
- ...(backdropClassName ? [h.Class(backdropClassName)] : []),
618
- ...backdropAttributes,
619
- ], []);
620
- const renderedItems = renderGroupedItems();
621
- const scrollableItems = itemsScrollClassName ||
622
- Array.isReadonlyArrayNonEmpty(itemsScrollAttributes)
623
- ? [
624
- h.div([
625
- ...(itemsScrollClassName
626
- ? [h.Class(itemsScrollClassName)]
627
- : []),
628
- ...itemsScrollAttributes,
629
- ], renderedItems),
630
- ]
631
- : renderedItems;
632
- const visibleContent = [
633
- backdrop,
634
- h.keyed('div')(`${id}-items-container`, itemsContainerAttributes, scrollableItems),
635
- ];
636
- const formAttribute = form ? [h.Attribute('form', form)] : [];
637
- const selectedValues = pipe(items, Array.filter(item => behavior.isItemSelected(model, itemToValue(item))), Array.map(itemToValue));
638
- const hiddenInputs = name
639
- ? Array.match(selectedValues, {
640
- onEmpty: () => [
641
- h.input([h.Type('hidden'), h.Name(name), ...formAttribute]),
642
- ],
643
- onNonEmpty: Array.map(selectedValue => h.input([
644
- h.Type('hidden'),
645
- h.Name(name),
646
- h.Value(selectedValue),
647
- ...formAttribute,
648
- ])),
649
- })
650
- : [];
651
- const wrapperAttributes = [
652
- ...(className ? [h.Class(className)] : []),
653
- ...attributes,
654
- ...(isVisible ? [h.DataAttribute('open', '')] : []),
655
- ...(isDisabled ? [h.DataAttribute('disabled', '')] : []),
656
- ...(isInvalid ? [h.DataAttribute('invalid', '')] : []),
657
- ];
658
- return h.div(wrapperAttributes, [
659
- h.keyed('button')(`${id}-button`, resolvedButtonAttributes, [
660
- buttonContent,
661
- ]),
662
- ...hiddenInputs,
663
- ...(isVisible ? visibleContent : []),
664
- ]);
665
- });
666
- return () =>
667
- /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
668
- impl;
669
- };