foldkit 0.101.0 → 0.102.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/canvas/view.d.ts +1 -1
- package/dist/canvas/view.d.ts.map +1 -1
- package/dist/canvas/view.js +5 -5
- package/dist/command/index.d.ts +71 -0
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +34 -1
- package/dist/command/public.d.ts +1 -1
- package/dist/command/public.d.ts.map +1 -1
- package/dist/command/public.js +1 -1
- package/dist/devTools/overlay.d.ts.map +1 -1
- package/dist/devTools/overlay.js +137 -110
- package/dist/dom/dom.d.ts +8 -11
- package/dist/dom/dom.d.ts.map +1 -1
- package/dist/dom/dom.js +8 -11
- package/dist/dom/elementMovement.d.ts +1 -3
- package/dist/dom/elementMovement.d.ts.map +1 -1
- package/dist/dom/elementMovement.js +1 -3
- package/dist/dom/inert.d.ts +2 -4
- package/dist/dom/inert.d.ts.map +1 -1
- package/dist/dom/inert.js +2 -4
- package/dist/dom/scrollLock.d.ts +2 -2
- package/dist/dom/scrollLock.js +2 -2
- package/dist/dom/waitForAnimation.d.ts +1 -1
- package/dist/dom/waitForAnimation.js +1 -1
- package/dist/html/boundary.d.ts +98 -0
- package/dist/html/boundary.d.ts.map +1 -0
- package/dist/html/boundary.js +176 -0
- package/dist/html/childAttribute.d.ts +44 -0
- package/dist/html/childAttribute.d.ts.map +1 -0
- package/dist/html/childAttribute.js +34 -0
- package/dist/html/index.d.ts +70 -23
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +639 -575
- package/dist/html/lazy.d.ts +12 -7
- package/dist/html/lazy.d.ts.map +1 -1
- package/dist/html/lazy.js +30 -11
- package/dist/html/public.d.ts +2 -2
- package/dist/html/public.d.ts.map +1 -1
- package/dist/html/public.js +1 -1
- package/dist/html/runtimeSingleton.d.ts +72 -0
- package/dist/html/runtimeSingleton.d.ts.map +1 -0
- package/dist/html/runtimeSingleton.js +112 -0
- package/dist/html/submodel.d.ts +98 -0
- package/dist/html/submodel.d.ts.map +1 -0
- package/dist/html/submodel.js +190 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/render/render.d.ts +1 -1
- package/dist/render/render.js +1 -1
- package/dist/runtime/messagePriority.d.ts +5 -1
- package/dist/runtime/messagePriority.d.ts.map +1 -1
- package/dist/runtime/messagePriority.js +25 -4
- package/dist/runtime/runtime.d.ts +1 -1
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +115 -61
- package/dist/submodel/public.d.ts +4 -0
- package/dist/submodel/public.d.ts.map +1 -0
- package/dist/submodel/public.js +1 -0
- package/dist/submodel/submodel.d.ts +32 -0
- package/dist/submodel/submodel.d.ts.map +1 -0
- package/dist/submodel/submodel.js +1 -0
- package/dist/test/apps/disabledButton.d.ts +4 -5
- package/dist/test/apps/disabledButton.d.ts.map +1 -1
- package/dist/test/apps/disabledButton.js +16 -16
- package/dist/test/scene.d.ts +8 -8
- package/dist/test/scene.d.ts.map +1 -1
- package/dist/test/scene.js +25 -13
- package/dist/test/story.d.ts +15 -8
- package/dist/test/story.d.ts.map +1 -1
- package/dist/test/story.js +21 -9
- package/dist/ui/animation/index.d.ts +30 -14
- package/dist/ui/animation/index.d.ts.map +1 -1
- package/dist/ui/animation/index.js +9 -19
- package/dist/ui/animation/public.d.ts +2 -2
- package/dist/ui/animation/public.d.ts.map +1 -1
- package/dist/ui/animation/public.js +1 -1
- package/dist/ui/calendar/index.d.ts +199 -84
- package/dist/ui/calendar/index.d.ts.map +1 -1
- package/dist/ui/calendar/index.js +129 -140
- package/dist/ui/calendar/public.d.ts +2 -2
- package/dist/ui/calendar/public.d.ts.map +1 -1
- package/dist/ui/calendar/public.js +1 -1
- package/dist/ui/checkbox/index.d.ts +93 -21
- package/dist/ui/checkbox/index.d.ts.map +1 -1
- package/dist/ui/checkbox/index.js +62 -33
- package/dist/ui/checkbox/public.d.ts +2 -2
- package/dist/ui/checkbox/public.d.ts.map +1 -1
- package/dist/ui/checkbox/public.js +1 -1
- package/dist/ui/combobox/multi.d.ts +35 -91
- package/dist/ui/combobox/multi.d.ts.map +1 -1
- package/dist/ui/combobox/multi.js +34 -17
- package/dist/ui/combobox/multiPublic.d.ts +2 -2
- package/dist/ui/combobox/multiPublic.d.ts.map +1 -1
- package/dist/ui/combobox/multiPublic.js +1 -1
- package/dist/ui/combobox/public.d.ts +3 -3
- package/dist/ui/combobox/public.d.ts.map +1 -1
- package/dist/ui/combobox/public.js +2 -2
- package/dist/ui/combobox/shared.d.ts +56 -31
- package/dist/ui/combobox/shared.d.ts.map +1 -1
- package/dist/ui/combobox/shared.js +333 -322
- package/dist/ui/combobox/single.d.ts +46 -93
- package/dist/ui/combobox/single.d.ts.map +1 -1
- package/dist/ui/combobox/single.js +44 -17
- package/dist/ui/datePicker/index.d.ts +256 -48
- package/dist/ui/datePicker/index.d.ts.map +1 -1
- package/dist/ui/datePicker/index.js +149 -104
- package/dist/ui/datePicker/public.d.ts +2 -2
- package/dist/ui/datePicker/public.d.ts.map +1 -1
- package/dist/ui/datePicker/public.js +1 -1
- package/dist/ui/dialog/index.d.ts +95 -39
- package/dist/ui/dialog/index.d.ts.map +1 -1
- package/dist/ui/dialog/index.js +71 -62
- package/dist/ui/dialog/public.d.ts +2 -2
- package/dist/ui/dialog/public.d.ts.map +1 -1
- package/dist/ui/dialog/public.js +1 -1
- package/dist/ui/disclosure/index.d.ts +71 -31
- package/dist/ui/disclosure/index.d.ts.map +1 -1
- package/dist/ui/disclosure/index.js +57 -62
- package/dist/ui/disclosure/public.d.ts +2 -2
- package/dist/ui/disclosure/public.d.ts.map +1 -1
- package/dist/ui/disclosure/public.js +1 -1
- package/dist/ui/dragAndDrop/index.d.ts +6 -6
- package/dist/ui/dragAndDrop/index.d.ts.map +1 -1
- package/dist/ui/dragAndDrop/index.js +7 -7
- package/dist/ui/dragAndDrop/public.d.ts +1 -1
- package/dist/ui/dragAndDrop/public.d.ts.map +1 -1
- package/dist/ui/dragAndDrop/public.js +1 -1
- package/dist/ui/fileDrop/index.d.ts +42 -46
- package/dist/ui/fileDrop/index.d.ts.map +1 -1
- package/dist/ui/fileDrop/index.js +30 -46
- package/dist/ui/fileDrop/public.d.ts +2 -2
- package/dist/ui/fileDrop/public.d.ts.map +1 -1
- package/dist/ui/fileDrop/public.js +1 -1
- package/dist/ui/listbox/multi.d.ts +39 -84
- package/dist/ui/listbox/multi.d.ts.map +1 -1
- package/dist/ui/listbox/multi.js +38 -20
- package/dist/ui/listbox/multiPublic.d.ts +2 -2
- package/dist/ui/listbox/multiPublic.d.ts.map +1 -1
- package/dist/ui/listbox/multiPublic.js +1 -1
- package/dist/ui/listbox/public.d.ts +3 -3
- package/dist/ui/listbox/public.d.ts.map +1 -1
- package/dist/ui/listbox/public.js +2 -2
- package/dist/ui/listbox/shared.d.ts +71 -30
- package/dist/ui/listbox/shared.d.ts.map +1 -1
- package/dist/ui/listbox/shared.js +319 -296
- package/dist/ui/listbox/single.d.ts +57 -85
- package/dist/ui/listbox/single.d.ts.map +1 -1
- package/dist/ui/listbox/single.js +48 -24
- package/dist/ui/menu/index.d.ts +80 -36
- package/dist/ui/menu/index.d.ts.map +1 -1
- package/dist/ui/menu/index.js +117 -86
- package/dist/ui/menu/public.d.ts +2 -2
- package/dist/ui/menu/public.d.ts.map +1 -1
- package/dist/ui/menu/public.js +1 -1
- package/dist/ui/popover/index.d.ts +117 -44
- package/dist/ui/popover/index.d.ts.map +1 -1
- package/dist/ui/popover/index.js +88 -101
- package/dist/ui/popover/public.d.ts +2 -2
- package/dist/ui/popover/public.d.ts.map +1 -1
- package/dist/ui/popover/public.js +1 -1
- package/dist/ui/radioGroup/index.d.ts +122 -45
- package/dist/ui/radioGroup/index.d.ts.map +1 -1
- package/dist/ui/radioGroup/index.js +111 -72
- package/dist/ui/radioGroup/public.d.ts +2 -2
- package/dist/ui/radioGroup/public.d.ts.map +1 -1
- package/dist/ui/radioGroup/public.js +1 -1
- package/dist/ui/slider/index.d.ts +72 -34
- package/dist/ui/slider/index.d.ts.map +1 -1
- package/dist/ui/slider/index.js +40 -49
- package/dist/ui/slider/public.d.ts +2 -2
- package/dist/ui/slider/public.d.ts.map +1 -1
- package/dist/ui/slider/public.js +1 -1
- package/dist/ui/switch/index.d.ts +74 -21
- package/dist/ui/switch/index.d.ts.map +1 -1
- package/dist/ui/switch/index.js +62 -33
- package/dist/ui/switch/public.d.ts +2 -2
- package/dist/ui/switch/public.d.ts.map +1 -1
- package/dist/ui/switch/public.js +1 -1
- package/dist/ui/tabs/index.d.ts +107 -45
- package/dist/ui/tabs/index.d.ts.map +1 -1
- package/dist/ui/tabs/index.js +99 -81
- package/dist/ui/tabs/public.d.ts +2 -2
- package/dist/ui/tabs/public.d.ts.map +1 -1
- package/dist/ui/tabs/public.js +1 -1
- package/dist/ui/toast/index.d.ts +93 -109
- package/dist/ui/toast/index.d.ts.map +1 -1
- package/dist/ui/toast/index.js +16 -29
- package/dist/ui/toast/schema.d.ts +15 -4
- package/dist/ui/toast/schema.d.ts.map +1 -1
- package/dist/ui/toast/schema.js +11 -4
- package/dist/ui/toast/update.d.ts +36 -18
- package/dist/ui/toast/update.d.ts.map +1 -1
- package/dist/ui/toast/update.js +33 -14
- package/dist/ui/tooltip/index.d.ts +94 -42
- package/dist/ui/tooltip/index.d.ts.map +1 -1
- package/dist/ui/tooltip/index.js +64 -73
- package/dist/ui/tooltip/public.d.ts +2 -2
- package/dist/ui/tooltip/public.d.ts.map +1 -1
- package/dist/ui/tooltip/public.js +1 -1
- package/dist/ui/virtualList/index.d.ts +18 -41
- package/dist/ui/virtualList/index.d.ts.map +1 -1
- package/dist/ui/virtualList/index.js +17 -37
- package/dist/ui/virtualList/public.d.ts +2 -2
- package/dist/ui/virtualList/public.d.ts.map +1 -1
- package/dist/ui/virtualList/public.js +1 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@ import { Array, Effect, Match as M, Option, Predicate, Result, Schema as S, pipe
|
|
|
2
2
|
import * as Command from '../../command/index.js';
|
|
3
3
|
import * as Dom from '../../dom/index.js';
|
|
4
4
|
import { OptionExt } from '../../effectExtensions/index.js';
|
|
5
|
-
import { html } from '../../html/index.js';
|
|
5
|
+
import { defineView, html, } from '../../html/index.js';
|
|
6
6
|
import { m } from '../../message/index.js';
|
|
7
7
|
import * as Mount from '../../mount/index.js';
|
|
8
8
|
import { makeConstrainedEvo } from '../../struct/index.js';
|
|
@@ -16,7 +16,7 @@ import { groupContiguous } from '../group.js';
|
|
|
16
16
|
import { findFirstEnabledIndex, keyToIndex } from '../keyboard.js';
|
|
17
17
|
export { groupContiguous };
|
|
18
18
|
// MODEL
|
|
19
|
-
/** Schema for the activation trigger
|
|
19
|
+
/** Schema for the activation trigger: whether the user interacted via mouse or keyboard. */
|
|
20
20
|
export const ActivationTrigger = S.Literals(['Pointer', 'Keyboard']);
|
|
21
21
|
/** Schema fields shared by all combobox variants (single-select and multi-select). Spread into each variant's `S.Struct` to avoid duplicating field definitions. */
|
|
22
22
|
export const BaseModel = S.Struct({
|
|
@@ -85,9 +85,9 @@ export const CompletedLockScroll = m('CompletedLockScroll');
|
|
|
85
85
|
/** Sent when the scroll unlock command completes. */
|
|
86
86
|
export const CompletedUnlockScroll = m('CompletedUnlockScroll');
|
|
87
87
|
/** Sent when the inert-others command completes. */
|
|
88
|
-
export const
|
|
88
|
+
export const CompletedInertOthers = m('CompletedInertOthers');
|
|
89
89
|
/** Sent when the restore-inert command completes. */
|
|
90
|
-
export const
|
|
90
|
+
export const CompletedRestoreInert = m('CompletedRestoreInert');
|
|
91
91
|
/** Sent when the focus-input command completes. */
|
|
92
92
|
export const CompletedFocusInput = m('CompletedFocusInput');
|
|
93
93
|
/** Sent when the scroll-into-view command completes after keyboard activation. */
|
|
@@ -124,8 +124,8 @@ export const Message = S.Union([
|
|
|
124
124
|
RequestedItemClick,
|
|
125
125
|
CompletedLockScroll,
|
|
126
126
|
CompletedUnlockScroll,
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
CompletedInertOthers,
|
|
128
|
+
CompletedRestoreInert,
|
|
129
129
|
CompletedFocusInput,
|
|
130
130
|
CompletedScrollIntoView,
|
|
131
131
|
CompletedClickItem,
|
|
@@ -137,6 +137,14 @@ export const Message = S.Union([
|
|
|
137
137
|
UpdatedInputValue,
|
|
138
138
|
PressedToggleButton,
|
|
139
139
|
]);
|
|
140
|
+
// OUT MESSAGE
|
|
141
|
+
/** Sent when a single-select combobox commits a selection, or when a multi-select combobox toggles an item on. The `value` is the string key; consumers that need a richer domain type should look it up from their own state or, in the multi case, branch on `wasAdded` to distinguish add vs remove. */
|
|
142
|
+
export const Selected = m('Selected', {
|
|
143
|
+
value: S.String,
|
|
144
|
+
wasAdded: S.Boolean,
|
|
145
|
+
});
|
|
146
|
+
/** Union of out-messages the combobox component can produce. Single-select comboboxes always emit `wasAdded: true`. Multi-select comboboxes emit `wasAdded: true` when adding to the selection and `wasAdded: false` when toggling off. */
|
|
147
|
+
export const OutMessage = S.Union([Selected]);
|
|
140
148
|
// SELECTORS
|
|
141
149
|
export const inputSelector = (id) => `#${id}-input`;
|
|
142
150
|
export const inputWrapperSelector = (id) => `#${id}-input-wrapper`;
|
|
@@ -145,7 +153,7 @@ export const itemSelector = (id, index) => `#${id}-item-${index}`;
|
|
|
145
153
|
export const itemId = (id, index) => `${id}-item-${index}`;
|
|
146
154
|
// HELPERS
|
|
147
155
|
const constrainedEvo = makeConstrainedEvo();
|
|
148
|
-
/** Resets only shared base fields to their closed state. Does not touch inputValue or selection
|
|
156
|
+
/** Resets only shared base fields to their closed state. Does not touch inputValue or selection. Those are variant-specific. */
|
|
149
157
|
export const closedBaseModel = (model) => constrainedEvo(model, {
|
|
150
158
|
isOpen: () => false,
|
|
151
159
|
maybeActiveItemIndex: () => Option.none(),
|
|
@@ -157,9 +165,9 @@ export const LockScroll = Command.define('LockScroll', CompletedLockScroll)(Dom.
|
|
|
157
165
|
/** Re-enables page scrolling after the combobox popup closes. */
|
|
158
166
|
export const UnlockScroll = Command.define('UnlockScroll', CompletedUnlockScroll)(Dom.unlockScroll.pipe(Effect.as(CompletedUnlockScroll())));
|
|
159
167
|
/** Marks all elements outside the combobox as inert for modal behavior. */
|
|
160
|
-
export const InertOthers = Command.define('InertOthers', { id: S.String },
|
|
168
|
+
export const InertOthers = Command.define('InertOthers', { id: S.String }, CompletedInertOthers)(({ id }) => Dom.inertOthers(id, [inputWrapperSelector(id), itemsSelector(id)]).pipe(Effect.as(CompletedInertOthers())));
|
|
161
169
|
/** Removes the inert attribute from elements outside the combobox. */
|
|
162
|
-
export const RestoreInert = Command.define('RestoreInert', { id: S.String },
|
|
170
|
+
export const RestoreInert = Command.define('RestoreInert', { id: S.String }, CompletedRestoreInert)(({ id }) => Dom.restoreInert(id).pipe(Effect.as(CompletedRestoreInert())));
|
|
163
171
|
/** Moves focus to the combobox input after selection or close. */
|
|
164
172
|
export const FocusInput = Command.define('FocusInput', { id: S.String }, CompletedFocusInput)(({ id }) => Dom.focus(inputSelector(id)).pipe(Effect.ignore, Effect.as(CompletedFocusInput())));
|
|
165
173
|
/** Scrolls the active combobox item into view after keyboard navigation. */
|
|
@@ -170,7 +178,7 @@ export const ClickItem = Command.define('ClickItem', { id: S.String, index: S.Nu
|
|
|
170
178
|
export const DetectMovementOrAnimationEnd = Command.define('DetectMovementOrAnimationEnd', { id: S.String }, GotAnimationMessage)(({ id }) => Effect.raceFirst(Dom.detectElementMovement(inputWrapperSelector(id)).pipe(Effect.as(GotAnimationMessage({ message: AnimationEndedAnimation() }))), Dom.waitForAnimationSettled(itemsSelector(id)).pipe(Effect.as(GotAnimationMessage({ message: AnimationEndedAnimation() })))));
|
|
171
179
|
const delegateToAnimation = (model, animationMessage) => {
|
|
172
180
|
const [nextAnimation, animationCommands, maybeOutMessage] = animationUpdate(model.animation, animationMessage);
|
|
173
|
-
const mappedCommands =
|
|
181
|
+
const mappedCommands = Command.mapMessages(animationCommands, message => GotAnimationMessage({ message }));
|
|
174
182
|
const additionalCommands = Option.match(maybeOutMessage, {
|
|
175
183
|
onNone: () => [],
|
|
176
184
|
onSome: M.type().pipe(M.tagsExhaustive({
|
|
@@ -183,12 +191,13 @@ const delegateToAnimation = (model, animationMessage) => {
|
|
|
183
191
|
return [
|
|
184
192
|
constrainedEvo(model, { animation: () => nextAnimation }),
|
|
185
193
|
[...mappedCommands, ...additionalCommands],
|
|
194
|
+
Option.none(),
|
|
186
195
|
];
|
|
187
196
|
};
|
|
188
197
|
/** Creates a combobox update function from variant-specific handlers. Shared logic (open, close, activate, transition) is handled internally; only close, selection, and immediate-activation behavior varies by variant. */
|
|
189
198
|
export const makeUpdate = (handlers) => {
|
|
190
199
|
const withUpdateReturn = M.withReturnType();
|
|
191
|
-
|
|
200
|
+
const internalUpdate = (model, message) => {
|
|
192
201
|
const maybeLockScroll = OptionExt.when(model.isModal, LockScroll());
|
|
193
202
|
const maybeUnlockScroll = OptionExt.when(model.isModal, UnlockScroll());
|
|
194
203
|
const maybeInertOthers = OptionExt.when(model.isModal, InertOthers({ id: model.id }));
|
|
@@ -203,22 +212,24 @@ export const makeUpdate = (handlers) => {
|
|
|
203
212
|
...Array.getSomes([maybeLockScroll, maybeInertOthers]),
|
|
204
213
|
...animationCommands,
|
|
205
214
|
],
|
|
215
|
+
Option.none(),
|
|
206
216
|
];
|
|
207
217
|
}
|
|
208
218
|
return [
|
|
209
219
|
constrainedEvo(baseModel, { isOpen: () => true }),
|
|
210
220
|
Array.getSomes([maybeLockScroll, maybeInertOthers]),
|
|
221
|
+
Option.none(),
|
|
211
222
|
];
|
|
212
223
|
};
|
|
213
|
-
const closeCombobox = (baseModel, commands) => {
|
|
224
|
+
const closeCombobox = (baseModel, commands, maybeOutMessage = Option.none()) => {
|
|
214
225
|
const closed = handlers.handleClose(baseModel);
|
|
215
226
|
if (model.isAnimated) {
|
|
216
227
|
const [nextModel, animationCommands] = delegateToAnimation(closed, AnimationHid());
|
|
217
|
-
return [nextModel, [...commands, ...animationCommands]];
|
|
228
|
+
return [nextModel, [...commands, ...animationCommands], maybeOutMessage];
|
|
218
229
|
}
|
|
219
|
-
return [closed, commands];
|
|
230
|
+
return [closed, commands, maybeOutMessage];
|
|
220
231
|
};
|
|
221
|
-
return M.value(message).pipe(withUpdateReturn, M.tagsExhaustive({
|
|
232
|
+
return M.value(message).pipe(withUpdateReturn, M.tag('CompletedLockScroll', 'CompletedUnlockScroll', 'CompletedInertOthers', 'CompletedRestoreInert', 'CompletedFocusInput', 'CompletedScrollIntoView', 'CompletedClickItem', 'CompletedAnchorCombobox', 'CompletedAttachComboboxPreventBlur', 'CompletedAttachComboboxSelectOnFocus', 'CompletedPortalComboboxBackdrop', () => [model, [], Option.none()]), M.tagsExhaustive({
|
|
222
233
|
Opened: ({ maybeActiveItemIndex }) => openCombobox(constrainedEvo(model, {
|
|
223
234
|
maybeActiveItemIndex: () => maybeActiveItemIndex,
|
|
224
235
|
activationTrigger: () => Option.match(maybeActiveItemIndex, {
|
|
@@ -243,12 +254,13 @@ export const makeUpdate = (handlers) => {
|
|
|
243
254
|
activationTrigger === 'Keyboard'
|
|
244
255
|
? [ScrollIntoView({ id: model.id, index })]
|
|
245
256
|
: [],
|
|
257
|
+
Option.none(),
|
|
246
258
|
];
|
|
247
259
|
},
|
|
248
260
|
MovedPointerOverItem: ({ index, screenX, screenY }) => {
|
|
249
261
|
const isSamePosition = Option.exists(model.maybeLastPointerPosition, position => position.screenX === screenX && position.screenY === screenY);
|
|
250
262
|
if (isSamePosition) {
|
|
251
|
-
return [model, []];
|
|
263
|
+
return [model, [], Option.none()];
|
|
252
264
|
}
|
|
253
265
|
return [
|
|
254
266
|
constrainedEvo(model, {
|
|
@@ -257,6 +269,7 @@ export const makeUpdate = (handlers) => {
|
|
|
257
269
|
maybeLastPointerPosition: () => Option.some({ screenX, screenY }),
|
|
258
270
|
}),
|
|
259
271
|
[],
|
|
272
|
+
Option.none(),
|
|
260
273
|
];
|
|
261
274
|
},
|
|
262
275
|
DeactivatedItem: () => model.activationTrigger === 'Pointer'
|
|
@@ -265,23 +278,29 @@ export const makeUpdate = (handlers) => {
|
|
|
265
278
|
maybeActiveItemIndex: () => Option.none(),
|
|
266
279
|
}),
|
|
267
280
|
[],
|
|
281
|
+
Option.none(),
|
|
268
282
|
]
|
|
269
|
-
: [model, []],
|
|
283
|
+
: [model, [], Option.none()],
|
|
270
284
|
SelectedItem: ({ item, displayText }) => {
|
|
271
|
-
const [nextModel, commands] = handlers.handleSelectedItem(model, item, displayText, {
|
|
285
|
+
const [nextModel, commands, maybeOutMessage] = handlers.handleSelectedItem(model, item, displayText, {
|
|
272
286
|
focusInput,
|
|
273
287
|
maybeUnlockScroll,
|
|
274
288
|
maybeRestoreInert,
|
|
275
289
|
});
|
|
276
290
|
if (model.isOpen && !nextModel.isOpen && model.isAnimated) {
|
|
277
291
|
const [transitionedModel, animationCommands] = delegateToAnimation(nextModel, AnimationHid());
|
|
278
|
-
return [
|
|
292
|
+
return [
|
|
293
|
+
transitionedModel,
|
|
294
|
+
[...commands, ...animationCommands],
|
|
295
|
+
maybeOutMessage,
|
|
296
|
+
];
|
|
279
297
|
}
|
|
280
|
-
return [nextModel, commands];
|
|
298
|
+
return [nextModel, commands, maybeOutMessage];
|
|
281
299
|
},
|
|
282
300
|
RequestedItemClick: ({ index }) => [
|
|
283
301
|
model,
|
|
284
302
|
[ClickItem({ id: model.id, index })],
|
|
303
|
+
Option.none(),
|
|
285
304
|
],
|
|
286
305
|
UpdatedInputValue: ({ value }) => {
|
|
287
306
|
if (model.isOpen) {
|
|
@@ -292,6 +311,7 @@ export const makeUpdate = (handlers) => {
|
|
|
292
311
|
activationTrigger: () => 'Keyboard',
|
|
293
312
|
}),
|
|
294
313
|
[],
|
|
314
|
+
Option.none(),
|
|
295
315
|
];
|
|
296
316
|
}
|
|
297
317
|
return openCombobox(constrainedEvo(model, {
|
|
@@ -310,22 +330,12 @@ export const makeUpdate = (handlers) => {
|
|
|
310
330
|
activationTrigger: () => 'Pointer',
|
|
311
331
|
maybeLastPointerPosition: () => Option.none(),
|
|
312
332
|
}));
|
|
313
|
-
return [nextModel, [focusInput, ...commands]];
|
|
333
|
+
return [nextModel, [focusInput, ...commands], Option.none()];
|
|
314
334
|
},
|
|
315
335
|
GotAnimationMessage: ({ message: animationMessage }) => delegateToAnimation(model, animationMessage),
|
|
316
|
-
CompletedLockScroll: () => [model, []],
|
|
317
|
-
CompletedUnlockScroll: () => [model, []],
|
|
318
|
-
CompletedSetupInert: () => [model, []],
|
|
319
|
-
CompletedTeardownInert: () => [model, []],
|
|
320
|
-
CompletedFocusInput: () => [model, []],
|
|
321
|
-
CompletedScrollIntoView: () => [model, []],
|
|
322
|
-
CompletedClickItem: () => [model, []],
|
|
323
|
-
CompletedAnchorCombobox: () => [model, []],
|
|
324
|
-
CompletedAttachComboboxPreventBlur: () => [model, []],
|
|
325
|
-
CompletedAttachComboboxSelectOnFocus: () => [model, []],
|
|
326
|
-
CompletedPortalComboboxBackdrop: () => [model, []],
|
|
327
336
|
}));
|
|
328
337
|
};
|
|
338
|
+
return internalUpdate;
|
|
329
339
|
};
|
|
330
340
|
/** The anchor-positioning Mount this Combobox renders when an anchor is
|
|
331
341
|
* configured. Exposed so Scene tests can call
|
|
@@ -391,303 +401,304 @@ export const PortalComboboxBackdrop = Mount.define('PortalComboboxBackdrop', Com
|
|
|
391
401
|
return CompletedPortalComboboxBackdrop();
|
|
392
402
|
}));
|
|
393
403
|
/** Creates a combobox view function from variant-specific behavior. Shared rendering logic (input, items, transitions, keyboard navigation) is handled internally; only selection display varies by variant. */
|
|
394
|
-
export const makeView = (behavior) =>
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
pipe(items, Array.get(
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
onSome: targetItem => ({
|
|
428
|
-
item: itemToValue(targetItem, targetIndex),
|
|
429
|
-
displayText: itemToDisplayText(targetItem, targetIndex),
|
|
430
|
-
}),
|
|
431
|
-
})));
|
|
432
|
-
const handleInputKeyDown = (key) => M.value(key).pipe(M.when('ArrowDown', () => {
|
|
433
|
-
if (!isOpen) {
|
|
434
|
-
return Option.some(toParentMessage(Opened({
|
|
435
|
-
maybeActiveItemIndex: Option.some(firstEnabledIndex),
|
|
436
|
-
})));
|
|
437
|
-
}
|
|
438
|
-
const targetIndex = resolveActiveIndex('ArrowDown');
|
|
439
|
-
return Option.some(toParentMessage(ActivatedItem({
|
|
440
|
-
index: targetIndex,
|
|
441
|
-
activationTrigger: 'Keyboard',
|
|
442
|
-
maybeImmediateSelection: resolveImmediateSelection(targetIndex),
|
|
443
|
-
})));
|
|
444
|
-
}), M.when('ArrowUp', () => {
|
|
445
|
-
if (!isOpen) {
|
|
446
|
-
return Option.some(toParentMessage(Opened({
|
|
447
|
-
maybeActiveItemIndex: Option.some(lastEnabledIndex),
|
|
448
|
-
})));
|
|
449
|
-
}
|
|
450
|
-
const targetIndex = resolveActiveIndex('ArrowUp');
|
|
451
|
-
return Option.some(toParentMessage(ActivatedItem({
|
|
452
|
-
index: targetIndex,
|
|
453
|
-
activationTrigger: 'Keyboard',
|
|
454
|
-
maybeImmediateSelection: resolveImmediateSelection(targetIndex),
|
|
404
|
+
export const makeView = (behavior) => {
|
|
405
|
+
const impl = defineView((model, viewInputs) => {
|
|
406
|
+
const h = html();
|
|
407
|
+
const { id, isOpen, immediate, animation: { transitionState }, maybeActiveItemIndex, } = model;
|
|
408
|
+
const { items, itemToConfig, itemToValue, itemToDisplayText, isItemDisabled, inputClassName, inputAttributes = [], inputPlaceholder, inputWrapperClassName, inputWrapperAttributes = [], itemsClassName, itemsAttributes = [], itemsScrollClassName, itemsScrollAttributes = [], backdropClassName, backdropAttributes = [], className, attributes = [], buttonContent, buttonClassName, buttonAttributes = [], formName, isDisabled, isInvalid, openOnFocus, itemGroupKey, groupToHeading, groupClassName, groupAttributes = [], separatorClassName, separatorAttributes = [], anchor, } = viewInputs;
|
|
409
|
+
const isLeaving = transitionState === 'LeaveStart' || transitionState === 'LeaveAnimating';
|
|
410
|
+
const isVisible = isOpen || isLeaving;
|
|
411
|
+
const animationAttributes = M.value(transitionState).pipe(M.when('EnterStart', () => [
|
|
412
|
+
h.DataAttribute('closed', ''),
|
|
413
|
+
h.DataAttribute('enter', ''),
|
|
414
|
+
h.DataAttribute('transition', ''),
|
|
415
|
+
]), M.when('EnterAnimating', () => [
|
|
416
|
+
h.DataAttribute('enter', ''),
|
|
417
|
+
h.DataAttribute('transition', ''),
|
|
418
|
+
]), M.when('LeaveStart', () => [
|
|
419
|
+
h.DataAttribute('leave', ''),
|
|
420
|
+
h.DataAttribute('transition', ''),
|
|
421
|
+
]), M.when('LeaveAnimating', () => [
|
|
422
|
+
h.DataAttribute('closed', ''),
|
|
423
|
+
h.DataAttribute('leave', ''),
|
|
424
|
+
h.DataAttribute('transition', ''),
|
|
425
|
+
]), M.orElse(() => []));
|
|
426
|
+
const isDisabledAtIndex = (index) => Predicate.isNotUndefined(isItemDisabled) &&
|
|
427
|
+
pipe(items, Array.get(index), Option.exists(item => isItemDisabled(item, index)));
|
|
428
|
+
const firstEnabledIndex = findFirstEnabledIndex(items.length, 0, isDisabledAtIndex)(0, 1);
|
|
429
|
+
const lastEnabledIndex = findFirstEnabledIndex(items.length, 0, isDisabledAtIndex)(items.length - 1, -1);
|
|
430
|
+
const resolveActiveIndex = keyToIndex('ArrowDown', 'ArrowUp', items.length, Option.getOrElse(maybeActiveItemIndex, () => -1), isDisabledAtIndex);
|
|
431
|
+
const resolveImmediateSelection = (targetIndex) => OptionExt.when(immediate, pipe(items, Array.get(targetIndex), Option.match({
|
|
432
|
+
onNone: () => ({ item: '', displayText: '' }),
|
|
433
|
+
onSome: targetItem => ({
|
|
434
|
+
item: itemToValue(targetItem, targetIndex),
|
|
435
|
+
displayText: itemToDisplayText(targetItem, targetIndex),
|
|
436
|
+
}),
|
|
455
437
|
})));
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
return Option.
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
438
|
+
const handleInputKeyDown = (key) => M.value(key).pipe(M.when('ArrowDown', () => {
|
|
439
|
+
if (!isOpen) {
|
|
440
|
+
return Option.some(Opened({
|
|
441
|
+
maybeActiveItemIndex: Option.some(firstEnabledIndex),
|
|
442
|
+
}));
|
|
443
|
+
}
|
|
444
|
+
const targetIndex = resolveActiveIndex('ArrowDown');
|
|
445
|
+
return Option.some(ActivatedItem({
|
|
446
|
+
index: targetIndex,
|
|
447
|
+
activationTrigger: 'Keyboard',
|
|
448
|
+
maybeImmediateSelection: resolveImmediateSelection(targetIndex),
|
|
449
|
+
}));
|
|
450
|
+
}), M.when('ArrowUp', () => {
|
|
451
|
+
if (!isOpen) {
|
|
452
|
+
return Option.some(Opened({
|
|
453
|
+
maybeActiveItemIndex: Option.some(lastEnabledIndex),
|
|
454
|
+
}));
|
|
455
|
+
}
|
|
456
|
+
const targetIndex = resolveActiveIndex('ArrowUp');
|
|
457
|
+
return Option.some(ActivatedItem({
|
|
458
|
+
index: targetIndex,
|
|
459
|
+
activationTrigger: 'Keyboard',
|
|
460
|
+
maybeImmediateSelection: resolveImmediateSelection(targetIndex),
|
|
461
|
+
}));
|
|
462
|
+
}), M.when('Enter', () => {
|
|
463
|
+
if (!isOpen) {
|
|
464
|
+
return Option.none();
|
|
465
|
+
}
|
|
466
|
+
return Option.map(maybeActiveItemIndex, index => RequestedItemClick({ index }));
|
|
467
|
+
}), M.when('Escape', () => {
|
|
468
|
+
if (!isOpen) {
|
|
469
|
+
return Option.none();
|
|
470
|
+
}
|
|
471
|
+
return Option.some(Closed());
|
|
472
|
+
}), M.whenOr('Home', 'End', () => {
|
|
473
|
+
if (!isOpen) {
|
|
474
|
+
return Option.none();
|
|
475
|
+
}
|
|
476
|
+
const targetIndex = resolveActiveIndex(key);
|
|
477
|
+
return Option.some(ActivatedItem({
|
|
478
|
+
index: targetIndex,
|
|
479
|
+
activationTrigger: 'Keyboard',
|
|
480
|
+
maybeImmediateSelection: resolveImmediateSelection(targetIndex),
|
|
481
|
+
}));
|
|
482
|
+
}), M.orElse(() => Option.none()));
|
|
483
|
+
const maybeActiveDescendant = Option.match(maybeActiveItemIndex, {
|
|
484
|
+
onNone: () => [],
|
|
485
|
+
onSome: index => [h.AriaActiveDescendant(itemId(id, index))],
|
|
486
|
+
});
|
|
487
|
+
const resolvedInputAttributes = [
|
|
488
|
+
h.Id(`${id}-input`),
|
|
489
|
+
h.Role('combobox'),
|
|
490
|
+
h.AriaExpanded(isVisible),
|
|
491
|
+
h.AriaControls(`${id}-items`),
|
|
492
|
+
h.Attribute('aria-autocomplete', 'list'),
|
|
493
|
+
h.Attribute('aria-haspopup', 'listbox'),
|
|
494
|
+
h.Autocomplete('off'),
|
|
495
|
+
h.Value(model.inputValue),
|
|
496
|
+
...maybeActiveDescendant,
|
|
497
|
+
...(inputPlaceholder ? [h.Placeholder(inputPlaceholder)] : []),
|
|
498
|
+
...(isDisabled
|
|
499
|
+
? [h.AriaDisabled(true), h.DataAttribute('disabled', '')]
|
|
500
|
+
: [
|
|
501
|
+
h.OnInput(value => UpdatedInputValue({ value })),
|
|
502
|
+
h.OnKeyDownPreventDefault(handleInputKeyDown),
|
|
503
|
+
h.OnBlur(BlurredInput()),
|
|
504
|
+
...(openOnFocus
|
|
505
|
+
? [h.OnFocus(Opened({ maybeActiveItemIndex: Option.none() }))]
|
|
506
|
+
: []),
|
|
507
|
+
]),
|
|
508
|
+
...(isInvalid
|
|
509
|
+
? [h.AriaInvalid(true), h.DataAttribute('invalid', '')]
|
|
510
|
+
: []),
|
|
511
|
+
...(isVisible ? [h.DataAttribute('open', '')] : []),
|
|
512
|
+
...(model.selectInputOnFocus
|
|
513
|
+
? [h.OnMount(AttachComboboxSelectOnFocus())]
|
|
514
|
+
: []),
|
|
515
|
+
...(inputClassName ? [h.Class(inputClassName)] : []),
|
|
516
|
+
...inputAttributes,
|
|
517
|
+
];
|
|
518
|
+
const anchorAttributes = anchor
|
|
519
|
+
? [
|
|
520
|
+
h.Style({
|
|
521
|
+
position: 'absolute',
|
|
522
|
+
margin: '0',
|
|
523
|
+
visibility: 'hidden',
|
|
524
|
+
}),
|
|
525
|
+
h.OnMount(AnchorCombobox({
|
|
526
|
+
buttonId: `${id}-input-wrapper`,
|
|
527
|
+
anchor,
|
|
528
|
+
})),
|
|
529
|
+
]
|
|
530
|
+
: [h.OnMount(AttachComboboxPreventBlur())];
|
|
531
|
+
const itemsContainerAttributes = [
|
|
532
|
+
h.Id(`${id}-items`),
|
|
533
|
+
h.Role('listbox'),
|
|
534
|
+
...(behavior.ariaMultiSelectable ? [h.AriaMultiSelectable(true)] : []),
|
|
535
|
+
h.AriaLabelledBy(`${id}-input`),
|
|
536
|
+
h.Tabindex(-1),
|
|
537
|
+
...anchorAttributes,
|
|
538
|
+
...animationAttributes,
|
|
539
|
+
...(itemsClassName ? [h.Class(itemsClassName)] : []),
|
|
540
|
+
...itemsAttributes,
|
|
541
|
+
];
|
|
542
|
+
const comboboxItems = Array.map(items, (item, index) => {
|
|
543
|
+
const isActiveItem = Option.exists(maybeActiveItemIndex, activeIndex => activeIndex === index);
|
|
544
|
+
const isDisabledItem = isDisabledAtIndex(index);
|
|
545
|
+
const isSelectedItem = behavior.isItemSelected(model, itemToValue(item, index));
|
|
546
|
+
const itemConfig = itemToConfig(item, {
|
|
547
|
+
isActive: isActiveItem,
|
|
548
|
+
isDisabled: isDisabledItem,
|
|
549
|
+
isSelected: isSelectedItem,
|
|
550
|
+
});
|
|
551
|
+
const isInteractive = !isDisabledItem && !isLeaving;
|
|
552
|
+
return h.keyed('div')(itemId(id, index), [
|
|
553
|
+
h.Id(itemId(id, index)),
|
|
554
|
+
h.Role('option'),
|
|
555
|
+
h.AriaSelected(isSelectedItem),
|
|
556
|
+
...(isActiveItem ? [h.DataAttribute('active', '')] : []),
|
|
557
|
+
...(isSelectedItem ? [h.DataAttribute('selected', '')] : []),
|
|
558
|
+
...(isDisabledItem
|
|
559
|
+
? [h.AriaDisabled(true), h.DataAttribute('disabled', '')]
|
|
560
|
+
: []),
|
|
561
|
+
...(isInteractive
|
|
499
562
|
? [
|
|
500
|
-
h.
|
|
563
|
+
h.OnClick(SelectedItem({
|
|
564
|
+
item: itemToValue(item, index),
|
|
565
|
+
displayText: itemToDisplayText(item, index),
|
|
566
|
+
})),
|
|
567
|
+
...(isActiveItem
|
|
568
|
+
? []
|
|
569
|
+
: [
|
|
570
|
+
h.OnPointerMove((screenX, screenY, pointerType) => OptionExt.when(pointerType !== 'touch', MovedPointerOverItem({ index, screenX, screenY }))),
|
|
571
|
+
]),
|
|
572
|
+
h.OnPointerLeave(pointerType => OptionExt.when(pointerType !== 'touch', DeactivatedItem())),
|
|
501
573
|
]
|
|
502
574
|
: []),
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
575
|
+
...(itemConfig.className ? [h.Class(itemConfig.className)] : []),
|
|
576
|
+
], [itemConfig.content]);
|
|
577
|
+
});
|
|
578
|
+
const renderGroupedItems = () => {
|
|
579
|
+
if (!itemGroupKey) {
|
|
580
|
+
return comboboxItems;
|
|
581
|
+
}
|
|
582
|
+
const segments = groupContiguous(comboboxItems, (_, index) => Array.get(items, index).pipe(Option.match({
|
|
583
|
+
onNone: () => '',
|
|
584
|
+
onSome: item => itemGroupKey(item, index),
|
|
585
|
+
})));
|
|
586
|
+
return Array.flatMap(segments, (segment, segmentIndex) => {
|
|
587
|
+
const maybeHeading = Option.fromNullishOr(groupToHeading && groupToHeading(segment.key));
|
|
588
|
+
const headingId = `${id}-heading-${segment.key}`;
|
|
589
|
+
const headingElement = Option.match(maybeHeading, {
|
|
590
|
+
onNone: () => [],
|
|
591
|
+
onSome: heading => [
|
|
592
|
+
h.keyed('div')(headingId, [
|
|
593
|
+
h.Id(headingId),
|
|
594
|
+
h.Role('presentation'),
|
|
595
|
+
...(heading.className ? [h.Class(heading.className)] : []),
|
|
596
|
+
], [heading.content]),
|
|
597
|
+
],
|
|
598
|
+
});
|
|
599
|
+
const groupContent = [...headingElement, ...segment.items];
|
|
600
|
+
const groupElement = h.keyed('div')(`${id}-group-${segment.key}`, [
|
|
601
|
+
h.Role('group'),
|
|
602
|
+
...(Option.isSome(maybeHeading)
|
|
603
|
+
? [h.AriaLabelledBy(headingId)]
|
|
604
|
+
: []),
|
|
605
|
+
...(groupClassName ? [h.Class(groupClassName)] : []),
|
|
606
|
+
...groupAttributes,
|
|
607
|
+
], groupContent);
|
|
608
|
+
const separator = segmentIndex > 0 &&
|
|
609
|
+
(separatorClassName ||
|
|
610
|
+
Array.isReadonlyArrayNonEmpty(separatorAttributes))
|
|
611
|
+
? [
|
|
612
|
+
h.keyed('div')(`${id}-separator-${segmentIndex}`, [
|
|
613
|
+
h.Role('separator'),
|
|
614
|
+
...(separatorClassName
|
|
615
|
+
? [h.Class(separatorClassName)]
|
|
616
|
+
: []),
|
|
617
|
+
...separatorAttributes,
|
|
618
|
+
], []),
|
|
619
|
+
]
|
|
620
|
+
: [];
|
|
621
|
+
return [...separator, groupElement];
|
|
622
|
+
});
|
|
623
|
+
};
|
|
624
|
+
const backdrop = h.keyed('div')(`${id}-backdrop`, [
|
|
625
|
+
h.OnMount(PortalComboboxBackdrop()),
|
|
626
|
+
...(isLeaving ? [] : [h.OnClick(Closed())]),
|
|
627
|
+
...(backdropClassName ? [h.Class(backdropClassName)] : []),
|
|
628
|
+
...backdropAttributes,
|
|
629
|
+
], []);
|
|
630
|
+
const renderedItems = renderGroupedItems();
|
|
631
|
+
const scrollableItems = itemsScrollClassName ||
|
|
632
|
+
Array.isReadonlyArrayNonEmpty(itemsScrollAttributes)
|
|
509
633
|
? [
|
|
510
|
-
h.
|
|
634
|
+
h.div([
|
|
635
|
+
...(itemsScrollClassName
|
|
636
|
+
? [h.Class(itemsScrollClassName)]
|
|
637
|
+
: []),
|
|
638
|
+
...itemsScrollAttributes,
|
|
639
|
+
], renderedItems),
|
|
511
640
|
]
|
|
512
|
-
:
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
const anchorAttributes = anchor
|
|
517
|
-
? [
|
|
518
|
-
h.Style({ position: 'absolute', margin: '0', visibility: 'hidden' }),
|
|
519
|
-
h.OnMount(Mount.mapMessage(AnchorCombobox({
|
|
520
|
-
buttonId: `${id}-input-wrapper`,
|
|
521
|
-
anchor,
|
|
522
|
-
}), toParentMessage)),
|
|
523
|
-
]
|
|
524
|
-
: [
|
|
525
|
-
h.OnMount(Mount.mapMessage(AttachComboboxPreventBlur(), toParentMessage)),
|
|
641
|
+
: renderedItems;
|
|
642
|
+
const visibleContent = [
|
|
643
|
+
backdrop,
|
|
644
|
+
h.keyed('div')(`${id}-items-container`, itemsContainerAttributes, scrollableItems),
|
|
526
645
|
];
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
: []),
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
h.
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
646
|
+
const resolvedInputWrapperAttributes = [
|
|
647
|
+
h.Id(`${id}-input-wrapper`),
|
|
648
|
+
...(inputWrapperClassName ? [h.Class(inputWrapperClassName)] : []),
|
|
649
|
+
...inputWrapperAttributes,
|
|
650
|
+
];
|
|
651
|
+
const toggleButton = buttonContent
|
|
652
|
+
? [
|
|
653
|
+
h.keyed('button')(`${id}-button`, [
|
|
654
|
+
h.Id(`${id}-button`),
|
|
655
|
+
h.Type('button'),
|
|
656
|
+
h.Tabindex(-1),
|
|
657
|
+
h.AriaControls(`${id}-items`),
|
|
658
|
+
h.AriaExpanded(isVisible),
|
|
659
|
+
h.Attribute('aria-haspopup', 'listbox'),
|
|
660
|
+
...(isDisabled
|
|
661
|
+
? [h.AriaDisabled(true), h.DataAttribute('disabled', '')]
|
|
662
|
+
: [h.OnClick(PressedToggleButton())]),
|
|
663
|
+
h.OnMount(AttachComboboxPreventBlur()),
|
|
664
|
+
...(buttonClassName ? [h.Class(buttonClassName)] : []),
|
|
665
|
+
...buttonAttributes,
|
|
666
|
+
], [buttonContent]),
|
|
667
|
+
]
|
|
668
|
+
: [];
|
|
669
|
+
const selectedValues = pipe(items, Array.filterMap((item, index) => {
|
|
670
|
+
const value = itemToValue(item, index);
|
|
671
|
+
return Result.fromOption(OptionExt.when(behavior.isItemSelected(model, value), value), () => undefined);
|
|
672
|
+
}));
|
|
673
|
+
const hiddenInputs = formName
|
|
674
|
+
? Array.match(selectedValues, {
|
|
675
|
+
onEmpty: () => [h.input([h.Type('hidden'), h.Name(formName)])],
|
|
676
|
+
onNonEmpty: Array.map(selectedValue => h.input([
|
|
677
|
+
h.Type('hidden'),
|
|
678
|
+
h.Name(formName),
|
|
679
|
+
h.Value(selectedValue),
|
|
680
|
+
])),
|
|
681
|
+
})
|
|
682
|
+
: [];
|
|
683
|
+
const wrapperAttributes = [
|
|
684
|
+
...(className ? [h.Class(className)] : []),
|
|
685
|
+
...attributes,
|
|
686
|
+
...(isVisible ? [h.DataAttribute('open', '')] : []),
|
|
687
|
+
...(isDisabled ? [h.DataAttribute('disabled', '')] : []),
|
|
688
|
+
...(isInvalid ? [h.DataAttribute('invalid', '')] : []),
|
|
689
|
+
];
|
|
690
|
+
return h.div(wrapperAttributes, [
|
|
691
|
+
h.div(resolvedInputWrapperAttributes, [
|
|
692
|
+
h.input(resolvedInputAttributes),
|
|
693
|
+
...toggleButton,
|
|
694
|
+
]),
|
|
695
|
+
...(isVisible && Array.isReadonlyArrayNonEmpty(items)
|
|
696
|
+
? visibleContent
|
|
567
697
|
: []),
|
|
568
|
-
...
|
|
569
|
-
]
|
|
698
|
+
...hiddenInputs,
|
|
699
|
+
]);
|
|
570
700
|
});
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
}
|
|
575
|
-
const segments = groupContiguous(comboboxItems, (_, index) => Array.get(items, index).pipe(Option.match({
|
|
576
|
-
onNone: () => '',
|
|
577
|
-
onSome: item => itemGroupKey(item, index),
|
|
578
|
-
})));
|
|
579
|
-
return Array.flatMap(segments, (segment, segmentIndex) => {
|
|
580
|
-
const maybeHeading = Option.fromNullishOr(groupToHeading && groupToHeading(segment.key));
|
|
581
|
-
const headingId = `${id}-heading-${segment.key}`;
|
|
582
|
-
const headingElement = Option.match(maybeHeading, {
|
|
583
|
-
onNone: () => [],
|
|
584
|
-
onSome: heading => [
|
|
585
|
-
h.keyed('div')(headingId, [
|
|
586
|
-
h.Id(headingId),
|
|
587
|
-
h.Role('presentation'),
|
|
588
|
-
...(heading.className ? [h.Class(heading.className)] : []),
|
|
589
|
-
], [heading.content]),
|
|
590
|
-
],
|
|
591
|
-
});
|
|
592
|
-
const groupContent = [...headingElement, ...segment.items];
|
|
593
|
-
const groupElement = h.keyed('div')(`${id}-group-${segment.key}`, [
|
|
594
|
-
h.Role('group'),
|
|
595
|
-
...(Option.isSome(maybeHeading)
|
|
596
|
-
? [h.AriaLabelledBy(headingId)]
|
|
597
|
-
: []),
|
|
598
|
-
...(groupClassName ? [h.Class(groupClassName)] : []),
|
|
599
|
-
...groupAttributes,
|
|
600
|
-
], groupContent);
|
|
601
|
-
const separator = segmentIndex > 0 &&
|
|
602
|
-
(separatorClassName ||
|
|
603
|
-
Array.isReadonlyArrayNonEmpty(separatorAttributes))
|
|
604
|
-
? [
|
|
605
|
-
h.keyed('div')(`${id}-separator-${segmentIndex}`, [
|
|
606
|
-
h.Role('separator'),
|
|
607
|
-
...(separatorClassName
|
|
608
|
-
? [h.Class(separatorClassName)]
|
|
609
|
-
: []),
|
|
610
|
-
...separatorAttributes,
|
|
611
|
-
], []),
|
|
612
|
-
]
|
|
613
|
-
: [];
|
|
614
|
-
return [...separator, groupElement];
|
|
615
|
-
});
|
|
616
|
-
};
|
|
617
|
-
const backdrop = h.keyed('div')(`${id}-backdrop`, [
|
|
618
|
-
h.OnMount(Mount.mapMessage(PortalComboboxBackdrop(), toParentMessage)),
|
|
619
|
-
...(isLeaving ? [] : [h.OnClick(toParentMessage(Closed()))]),
|
|
620
|
-
...(backdropClassName ? [h.Class(backdropClassName)] : []),
|
|
621
|
-
...backdropAttributes,
|
|
622
|
-
], []);
|
|
623
|
-
const renderedItems = renderGroupedItems();
|
|
624
|
-
const scrollableItems = itemsScrollClassName ||
|
|
625
|
-
Array.isReadonlyArrayNonEmpty(itemsScrollAttributes)
|
|
626
|
-
? [
|
|
627
|
-
h.div([
|
|
628
|
-
...(itemsScrollClassName
|
|
629
|
-
? [h.Class(itemsScrollClassName)]
|
|
630
|
-
: []),
|
|
631
|
-
...itemsScrollAttributes,
|
|
632
|
-
], renderedItems),
|
|
633
|
-
]
|
|
634
|
-
: renderedItems;
|
|
635
|
-
const visibleContent = [
|
|
636
|
-
backdrop,
|
|
637
|
-
h.keyed('div')(`${id}-items-container`, itemsContainerAttributes, scrollableItems),
|
|
638
|
-
];
|
|
639
|
-
const resolvedInputWrapperAttributes = [
|
|
640
|
-
h.Id(`${id}-input-wrapper`),
|
|
641
|
-
...(inputWrapperClassName ? [h.Class(inputWrapperClassName)] : []),
|
|
642
|
-
...inputWrapperAttributes,
|
|
643
|
-
];
|
|
644
|
-
const toggleButton = buttonContent
|
|
645
|
-
? [
|
|
646
|
-
h.keyed('button')(`${id}-button`, [
|
|
647
|
-
h.Id(`${id}-button`),
|
|
648
|
-
h.Type('button'),
|
|
649
|
-
h.Tabindex(-1),
|
|
650
|
-
h.AriaControls(`${id}-items`),
|
|
651
|
-
h.AriaExpanded(isVisible),
|
|
652
|
-
h.Attribute('aria-haspopup', 'listbox'),
|
|
653
|
-
...(isDisabled
|
|
654
|
-
? [h.AriaDisabled(true), h.DataAttribute('disabled', '')]
|
|
655
|
-
: [h.OnClick(toParentMessage(PressedToggleButton()))]),
|
|
656
|
-
h.OnMount(Mount.mapMessage(AttachComboboxPreventBlur(), toParentMessage)),
|
|
657
|
-
...(buttonClassName ? [h.Class(buttonClassName)] : []),
|
|
658
|
-
...buttonAttributes,
|
|
659
|
-
], [buttonContent]),
|
|
660
|
-
]
|
|
661
|
-
: [];
|
|
662
|
-
const selectedValues = pipe(items, Array.filterMap((item, index) => {
|
|
663
|
-
const value = itemToValue(item, index);
|
|
664
|
-
return Result.fromOption(OptionExt.when(behavior.isItemSelected(config.model, value), value), () => undefined);
|
|
665
|
-
}));
|
|
666
|
-
const hiddenInputs = formName
|
|
667
|
-
? Array.match(selectedValues, {
|
|
668
|
-
onEmpty: () => [h.input([h.Type('hidden'), h.Name(formName)])],
|
|
669
|
-
onNonEmpty: Array.map(selectedValue => h.input([
|
|
670
|
-
h.Type('hidden'),
|
|
671
|
-
h.Name(formName),
|
|
672
|
-
h.Value(selectedValue),
|
|
673
|
-
])),
|
|
674
|
-
})
|
|
675
|
-
: [];
|
|
676
|
-
const wrapperAttributes = [
|
|
677
|
-
...(className ? [h.Class(className)] : []),
|
|
678
|
-
...attributes,
|
|
679
|
-
...(isVisible ? [h.DataAttribute('open', '')] : []),
|
|
680
|
-
...(isDisabled ? [h.DataAttribute('disabled', '')] : []),
|
|
681
|
-
...(isInvalid ? [h.DataAttribute('invalid', '')] : []),
|
|
682
|
-
];
|
|
683
|
-
return h.div(wrapperAttributes, [
|
|
684
|
-
h.div(resolvedInputWrapperAttributes, [
|
|
685
|
-
h.input(resolvedInputAttributes),
|
|
686
|
-
...toggleButton,
|
|
687
|
-
]),
|
|
688
|
-
...(isVisible && Array.isReadonlyArrayNonEmpty(items)
|
|
689
|
-
? visibleContent
|
|
690
|
-
: []),
|
|
691
|
-
...hiddenInputs,
|
|
692
|
-
]);
|
|
701
|
+
return () =>
|
|
702
|
+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
703
|
+
impl;
|
|
693
704
|
};
|