foldkit 0.18.0 → 0.19.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 +11 -11
- package/dist/effectExtensions/optionExtensions.d.ts +4 -0
- package/dist/effectExtensions/optionExtensions.d.ts.map +1 -1
- package/dist/effectExtensions/optionExtensions.js +2 -1
- package/dist/fieldValidation/index.d.ts.map +1 -1
- package/dist/fieldValidation/index.js +4 -4
- package/dist/html/index.d.ts +31 -3
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +49 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/message/index.d.ts +2 -0
- package/dist/message/index.d.ts.map +1 -0
- package/dist/message/index.js +1 -0
- package/dist/message/public.d.ts +2 -0
- package/dist/message/public.d.ts.map +1 -0
- package/dist/message/public.js +1 -0
- package/dist/route/index.d.ts +1 -0
- package/dist/route/index.d.ts.map +1 -1
- package/dist/route/index.js +1 -0
- package/dist/route/parser.js +17 -17
- package/dist/route/public.d.ts +1 -1
- package/dist/route/public.d.ts.map +1 -1
- package/dist/route/public.js +1 -1
- package/dist/runtime/runtime.js +8 -8
- package/dist/schema/index.d.ts +36 -8
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +6 -0
- package/dist/task/dom.d.ts +59 -0
- package/dist/task/dom.d.ts.map +1 -0
- package/dist/task/dom.js +113 -0
- package/dist/task/index.d.ts +6 -108
- package/dist/task/index.d.ts.map +1 -1
- package/dist/task/index.js +6 -168
- package/dist/task/inert.d.ts +25 -0
- package/dist/task/inert.d.ts.map +1 -0
- package/dist/task/inert.js +88 -0
- package/dist/task/public.d.ts +1 -1
- package/dist/task/public.d.ts.map +1 -1
- package/dist/task/public.js +1 -1
- package/dist/task/random.d.ts +12 -0
- package/dist/task/random.d.ts.map +1 -0
- package/dist/task/random.js +11 -0
- package/dist/task/scrollLock.d.ts +24 -0
- package/dist/task/scrollLock.d.ts.map +1 -0
- package/dist/task/scrollLock.js +48 -0
- package/dist/task/time.d.ts +42 -0
- package/dist/task/time.d.ts.map +1 -0
- package/dist/task/time.js +54 -0
- package/dist/task/timing.d.ts +36 -0
- package/dist/task/timing.d.ts.map +1 -0
- package/dist/task/timing.js +55 -0
- package/dist/ui/dialog/index.d.ts.map +1 -1
- package/dist/ui/dialog/index.js +4 -4
- package/dist/ui/disclosure/index.d.ts.map +1 -1
- package/dist/ui/disclosure/index.js +4 -4
- package/dist/ui/keyboard.d.ts.map +1 -1
- package/dist/ui/keyboard.js +1 -1
- package/dist/ui/menu/index.d.ts +70 -17
- package/dist/ui/menu/index.d.ts.map +1 -1
- package/dist/ui/menu/index.js +317 -112
- 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/tabs/index.d.ts.map +1 -1
- package/dist/ui/tabs/index.js +6 -6
- package/dist/url/index.js +4 -4
- package/package.json +5 -1
package/dist/ui/menu/index.js
CHANGED
|
@@ -1,162 +1,313 @@
|
|
|
1
1
|
import { Array, Match as M, Option, Schema as S, String as Str, pipe, } from 'effect';
|
|
2
|
+
import { OptionExt } from '../../effectExtensions';
|
|
2
3
|
import { html } from '../../html';
|
|
3
|
-
import {
|
|
4
|
+
import { m } from '../../message';
|
|
4
5
|
import { evo } from '../../struct';
|
|
5
6
|
import * as Task from '../../task';
|
|
6
7
|
import { findFirstEnabledIndex, keyToIndex, wrapIndex } from '../keyboard';
|
|
7
8
|
// MODEL
|
|
8
9
|
/** Schema for the activation trigger — whether the user interacted via mouse or keyboard. */
|
|
9
10
|
export const ActivationTrigger = S.Literal('Pointer', 'Keyboard');
|
|
11
|
+
/** Schema for the transition animation state, tracking enter/leave phases for CSS transition coordination. */
|
|
12
|
+
export const TransitionState = S.Literal('Idle', 'EnterStart', 'EnterAnimating', 'LeaveStart', 'LeaveAnimating');
|
|
13
|
+
const PointerOrigin = S.Struct({
|
|
14
|
+
screenX: S.Number,
|
|
15
|
+
screenY: S.Number,
|
|
16
|
+
timeStamp: S.Number,
|
|
17
|
+
});
|
|
10
18
|
/** Schema for the menu component's state, tracking open/closed status, active item, activation trigger, and typeahead search. */
|
|
11
19
|
export const Model = S.Struct({
|
|
12
20
|
id: S.String,
|
|
13
21
|
isOpen: S.Boolean,
|
|
22
|
+
isAnimated: S.Boolean,
|
|
23
|
+
isModal: S.Boolean,
|
|
24
|
+
transitionState: TransitionState,
|
|
14
25
|
maybeActiveItemIndex: S.OptionFromSelf(S.Number),
|
|
15
26
|
activationTrigger: ActivationTrigger,
|
|
16
27
|
searchQuery: S.String,
|
|
17
28
|
searchVersion: S.Number,
|
|
18
29
|
maybeLastPointerPosition: S.OptionFromSelf(S.Struct({ screenX: S.Number, screenY: S.Number })),
|
|
30
|
+
maybeLastButtonPointerType: S.OptionFromSelf(S.String),
|
|
31
|
+
maybePointerOrigin: S.OptionFromSelf(PointerOrigin),
|
|
19
32
|
});
|
|
20
33
|
// MESSAGE
|
|
21
34
|
/** Sent when the menu opens via button click or keyboard. Contains an optional initial active item index — None for pointer, Some for keyboard. */
|
|
22
|
-
export const Opened =
|
|
35
|
+
export const Opened = m('Opened', {
|
|
23
36
|
maybeActiveItemIndex: S.OptionFromSelf(S.Number),
|
|
24
37
|
});
|
|
25
38
|
/** Sent when the menu closes via Escape key or backdrop click. */
|
|
26
|
-
export const Closed =
|
|
39
|
+
export const Closed = m('Closed');
|
|
27
40
|
/** Sent when focus leaves the menu items container via Tab key. */
|
|
28
|
-
export const ClosedByTab =
|
|
41
|
+
export const ClosedByTab = m('ClosedByTab');
|
|
29
42
|
/** Sent when an item is highlighted via arrow keys or mouse hover. Includes activation trigger. */
|
|
30
|
-
export const
|
|
43
|
+
export const ActivatedItem = m('ActivatedItem', {
|
|
31
44
|
index: S.Number,
|
|
32
45
|
activationTrigger: ActivationTrigger,
|
|
33
46
|
});
|
|
34
47
|
/** Sent when the mouse leaves an enabled item. */
|
|
35
|
-
export const
|
|
48
|
+
export const DeactivatedItem = m('DeactivatedItem');
|
|
36
49
|
/** Sent when an item is selected via Enter, Space, or click. */
|
|
37
|
-
export const
|
|
50
|
+
export const SelectedItem = m('SelectedItem', { index: S.Number });
|
|
51
|
+
/** Sent when Enter or Space is pressed on the active item, triggering a programmatic click on the DOM element. */
|
|
52
|
+
export const RequestedItemClick = m('RequestedItemClick', {
|
|
53
|
+
index: S.Number,
|
|
54
|
+
});
|
|
38
55
|
/** Sent when a printable character is typed for typeahead search. */
|
|
39
|
-
export const Searched =
|
|
56
|
+
export const Searched = m('Searched', {
|
|
40
57
|
key: S.String,
|
|
41
58
|
maybeTargetIndex: S.OptionFromSelf(S.Number),
|
|
42
59
|
});
|
|
43
60
|
/** Sent after the search debounce period to clear the accumulated query. */
|
|
44
|
-
export const
|
|
61
|
+
export const ClearedSearch = m('ClearedSearch', { version: S.Number });
|
|
45
62
|
/** Sent when the pointer moves over a menu item, carrying screen coordinates for tracked-pointer comparison. */
|
|
46
|
-
export const
|
|
63
|
+
export const MovedPointerOverItem = m('MovedPointerOverItem', {
|
|
47
64
|
index: S.Number,
|
|
48
65
|
screenX: S.Number,
|
|
49
66
|
screenY: S.Number,
|
|
50
67
|
});
|
|
51
68
|
/** Placeholder message used when no action is needed. */
|
|
52
|
-
export const NoOp =
|
|
69
|
+
export const NoOp = m('NoOp');
|
|
70
|
+
/** Sent internally when a double-rAF completes, advancing the transition to its animating phase. */
|
|
71
|
+
export const AdvancedTransitionFrame = m('AdvancedTransitionFrame');
|
|
72
|
+
/** Sent internally when all CSS transitions on the menu items container have completed. */
|
|
73
|
+
export const EndedTransition = m('EndedTransition');
|
|
74
|
+
/** Sent when the user presses a pointer device on the menu button. Records pointer type and toggles for mouse. */
|
|
75
|
+
export const PressedPointerOnButton = m('PressedPointerOnButton', {
|
|
76
|
+
pointerType: S.String,
|
|
77
|
+
button: S.Number,
|
|
78
|
+
screenX: S.Number,
|
|
79
|
+
screenY: S.Number,
|
|
80
|
+
timeStamp: S.Number,
|
|
81
|
+
});
|
|
82
|
+
/** Sent when the user releases a pointer on the items container, enabling drag-to-select for mouse. */
|
|
83
|
+
export const ReleasedPointerOnItems = m('ReleasedPointerOnItems', {
|
|
84
|
+
screenX: S.Number,
|
|
85
|
+
screenY: S.Number,
|
|
86
|
+
timeStamp: S.Number,
|
|
87
|
+
});
|
|
53
88
|
/** Union of all messages the menu component can produce. */
|
|
54
|
-
export const Message = S.Union(Opened, Closed, ClosedByTab,
|
|
89
|
+
export const Message = S.Union(Opened, Closed, ClosedByTab, ActivatedItem, DeactivatedItem, SelectedItem, MovedPointerOverItem, RequestedItemClick, Searched, ClearedSearch, NoOp, AdvancedTransitionFrame, EndedTransition, PressedPointerOnButton, ReleasedPointerOnItems);
|
|
55
90
|
// INIT
|
|
56
91
|
const SEARCH_DEBOUNCE_MILLISECONDS = 350;
|
|
92
|
+
const POINTER_HOLD_THRESHOLD_MILLISECONDS = 200;
|
|
93
|
+
const POINTER_MOVEMENT_THRESHOLD_PIXELS = 5;
|
|
57
94
|
/** Creates an initial menu model from a config. Defaults to closed with no active item. */
|
|
58
95
|
export const init = (config) => ({
|
|
59
96
|
id: config.id,
|
|
60
97
|
isOpen: false,
|
|
98
|
+
isAnimated: config.isAnimated ?? false,
|
|
99
|
+
isModal: config.isModal ?? true,
|
|
100
|
+
transitionState: 'Idle',
|
|
61
101
|
maybeActiveItemIndex: Option.none(),
|
|
62
102
|
activationTrigger: 'Keyboard',
|
|
63
103
|
searchQuery: '',
|
|
64
104
|
searchVersion: 0,
|
|
65
105
|
maybeLastPointerPosition: Option.none(),
|
|
106
|
+
maybeLastButtonPointerType: Option.none(),
|
|
107
|
+
maybePointerOrigin: Option.none(),
|
|
66
108
|
});
|
|
67
109
|
// UPDATE
|
|
68
110
|
const closedModel = (model) => evo(model, {
|
|
69
111
|
isOpen: () => false,
|
|
112
|
+
transitionState: () => (model.isAnimated ? 'LeaveStart' : 'Idle'),
|
|
70
113
|
maybeActiveItemIndex: () => Option.none(),
|
|
71
114
|
activationTrigger: () => 'Keyboard',
|
|
72
115
|
searchQuery: () => '',
|
|
73
116
|
searchVersion: () => 0,
|
|
74
117
|
maybeLastPointerPosition: () => Option.none(),
|
|
118
|
+
maybeLastButtonPointerType: () => Option.none(),
|
|
119
|
+
maybePointerOrigin: () => Option.none(),
|
|
75
120
|
});
|
|
76
121
|
const buttonSelector = (id) => `#${id}-button`;
|
|
77
122
|
const itemsSelector = (id) => `#${id}-items`;
|
|
78
123
|
const itemSelector = (id, index) => `#${id}-item-${index}`;
|
|
79
124
|
/** Processes a menu message and returns the next model and commands. */
|
|
80
|
-
export const update = (model, message) =>
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
125
|
+
export const update = (model, message) => {
|
|
126
|
+
const maybeNextFrameCommand = OptionExt.when(model.isAnimated, Task.nextFrame(() => AdvancedTransitionFrame()));
|
|
127
|
+
const maybeLockScrollCommand = OptionExt.when(model.isModal, Task.lockScroll(() => NoOp()));
|
|
128
|
+
const maybeUnlockScrollCommand = OptionExt.when(model.isModal, Task.unlockScroll(() => NoOp()));
|
|
129
|
+
const maybeInertOthersCommand = OptionExt.when(model.isModal, Task.inertOthers(model.id, [buttonSelector(model.id), itemsSelector(model.id)], () => NoOp()));
|
|
130
|
+
const maybeRestoreInertCommand = OptionExt.when(model.isModal, Task.restoreInert(model.id, () => NoOp()));
|
|
131
|
+
return M.value(message).pipe(M.withReturnType(), M.tagsExhaustive({
|
|
132
|
+
Opened: ({ maybeActiveItemIndex }) => {
|
|
133
|
+
const nextModel = evo(model, {
|
|
134
|
+
isOpen: () => true,
|
|
135
|
+
transitionState: () => (model.isAnimated ? 'EnterStart' : 'Idle'),
|
|
136
|
+
maybeActiveItemIndex: () => maybeActiveItemIndex,
|
|
137
|
+
activationTrigger: () => Option.match(maybeActiveItemIndex, {
|
|
138
|
+
onNone: () => 'Pointer',
|
|
139
|
+
onSome: () => 'Keyboard',
|
|
140
|
+
}),
|
|
141
|
+
searchQuery: () => '',
|
|
142
|
+
searchVersion: () => 0,
|
|
143
|
+
maybeLastPointerPosition: () => Option.none(),
|
|
144
|
+
});
|
|
145
|
+
return [
|
|
146
|
+
nextModel,
|
|
147
|
+
pipe(Array.getSomes([
|
|
148
|
+
maybeNextFrameCommand,
|
|
149
|
+
maybeLockScrollCommand,
|
|
150
|
+
maybeInertOthersCommand,
|
|
151
|
+
]), Array.prepend(Task.focus(itemsSelector(model.id), () => NoOp()))),
|
|
152
|
+
];
|
|
153
|
+
},
|
|
154
|
+
Closed: () => [
|
|
155
|
+
closedModel(model),
|
|
156
|
+
pipe(Array.getSomes([
|
|
157
|
+
maybeNextFrameCommand,
|
|
158
|
+
maybeUnlockScrollCommand,
|
|
159
|
+
maybeRestoreInertCommand,
|
|
160
|
+
]), Array.prepend(Task.focus(buttonSelector(model.id), () => NoOp()))),
|
|
161
|
+
],
|
|
162
|
+
ClosedByTab: () => [
|
|
163
|
+
closedModel(model),
|
|
164
|
+
Array.getSomes([
|
|
165
|
+
maybeNextFrameCommand,
|
|
166
|
+
maybeUnlockScrollCommand,
|
|
167
|
+
maybeRestoreInertCommand,
|
|
168
|
+
]),
|
|
169
|
+
],
|
|
170
|
+
ActivatedItem: ({ index, activationTrigger }) => [
|
|
115
171
|
evo(model, {
|
|
116
172
|
maybeActiveItemIndex: () => Option.some(index),
|
|
117
|
-
activationTrigger: () =>
|
|
118
|
-
maybeLastPointerPosition: () => Option.some({ screenX, screenY }),
|
|
119
|
-
}),
|
|
120
|
-
[],
|
|
121
|
-
];
|
|
122
|
-
},
|
|
123
|
-
ItemDeactivated: () => model.activationTrigger === 'Pointer'
|
|
124
|
-
? [evo(model, { maybeActiveItemIndex: () => Option.none() }), []]
|
|
125
|
-
: [model, []],
|
|
126
|
-
ItemSelected: () => [
|
|
127
|
-
closedModel(model),
|
|
128
|
-
[Task.focus(buttonSelector(model.id), () => NoOp())],
|
|
129
|
-
],
|
|
130
|
-
Searched: ({ key, maybeTargetIndex }) => {
|
|
131
|
-
const nextSearchQuery = model.searchQuery + key;
|
|
132
|
-
const nextSearchVersion = model.searchVersion + 1;
|
|
133
|
-
return [
|
|
134
|
-
evo(model, {
|
|
135
|
-
searchQuery: () => nextSearchQuery,
|
|
136
|
-
searchVersion: () => nextSearchVersion,
|
|
137
|
-
maybeActiveItemIndex: () => Option.orElse(maybeTargetIndex, () => model.maybeActiveItemIndex),
|
|
173
|
+
activationTrigger: () => activationTrigger,
|
|
138
174
|
}),
|
|
175
|
+
activationTrigger === 'Keyboard'
|
|
176
|
+
? [Task.scrollIntoView(itemSelector(model.id, index), () => NoOp())]
|
|
177
|
+
: [],
|
|
178
|
+
],
|
|
179
|
+
MovedPointerOverItem: ({ index, screenX, screenY }) => {
|
|
180
|
+
const isSamePosition = Option.exists(model.maybeLastPointerPosition, position => position.screenX === screenX && position.screenY === screenY);
|
|
181
|
+
if (isSamePosition) {
|
|
182
|
+
return [model, []];
|
|
183
|
+
}
|
|
184
|
+
return [
|
|
185
|
+
evo(model, {
|
|
186
|
+
maybeActiveItemIndex: () => Option.some(index),
|
|
187
|
+
activationTrigger: () => 'Pointer',
|
|
188
|
+
maybeLastPointerPosition: () => Option.some({ screenX, screenY }),
|
|
189
|
+
}),
|
|
190
|
+
[],
|
|
191
|
+
];
|
|
192
|
+
},
|
|
193
|
+
DeactivatedItem: () => model.activationTrigger === 'Pointer'
|
|
194
|
+
? [evo(model, { maybeActiveItemIndex: () => Option.none() }), []]
|
|
195
|
+
: [model, []],
|
|
196
|
+
SelectedItem: () => [
|
|
197
|
+
closedModel(model),
|
|
198
|
+
pipe(Array.getSomes([
|
|
199
|
+
maybeNextFrameCommand,
|
|
200
|
+
maybeUnlockScrollCommand,
|
|
201
|
+
maybeRestoreInertCommand,
|
|
202
|
+
]), Array.prepend(Task.focus(buttonSelector(model.id), () => NoOp()))),
|
|
203
|
+
],
|
|
204
|
+
RequestedItemClick: ({ index }) => [
|
|
205
|
+
model,
|
|
206
|
+
[Task.clickElement(itemSelector(model.id, index), () => NoOp())],
|
|
207
|
+
],
|
|
208
|
+
Searched: ({ key, maybeTargetIndex }) => {
|
|
209
|
+
const nextSearchQuery = model.searchQuery + key;
|
|
210
|
+
const nextSearchVersion = model.searchVersion + 1;
|
|
211
|
+
return [
|
|
212
|
+
evo(model, {
|
|
213
|
+
searchQuery: () => nextSearchQuery,
|
|
214
|
+
searchVersion: () => nextSearchVersion,
|
|
215
|
+
maybeActiveItemIndex: () => Option.orElse(maybeTargetIndex, () => model.maybeActiveItemIndex),
|
|
216
|
+
}),
|
|
217
|
+
[
|
|
218
|
+
Task.delay(SEARCH_DEBOUNCE_MILLISECONDS, () => ClearedSearch({ version: nextSearchVersion })),
|
|
219
|
+
],
|
|
220
|
+
];
|
|
221
|
+
},
|
|
222
|
+
ClearedSearch: ({ version }) => {
|
|
223
|
+
if (version !== model.searchVersion) {
|
|
224
|
+
return [model, []];
|
|
225
|
+
}
|
|
226
|
+
return [evo(model, { searchQuery: () => '' }), []];
|
|
227
|
+
},
|
|
228
|
+
AdvancedTransitionFrame: () => M.value(model.transitionState).pipe(M.withReturnType(), M.when('EnterStart', () => [
|
|
229
|
+
evo(model, { transitionState: () => 'EnterAnimating' }),
|
|
139
230
|
[
|
|
140
|
-
Task.
|
|
231
|
+
Task.waitForTransitions(itemsSelector(model.id), () => EndedTransition()),
|
|
141
232
|
],
|
|
142
|
-
]
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
233
|
+
]), M.when('LeaveStart', () => [
|
|
234
|
+
evo(model, { transitionState: () => 'LeaveAnimating' }),
|
|
235
|
+
[
|
|
236
|
+
Task.waitForTransitions(itemsSelector(model.id), () => EndedTransition()),
|
|
237
|
+
],
|
|
238
|
+
]), M.orElse(() => [model, []])),
|
|
239
|
+
EndedTransition: () => M.value(model.transitionState).pipe(M.withReturnType(), M.whenOr('EnterAnimating', 'LeaveAnimating', () => [
|
|
240
|
+
evo(model, { transitionState: () => 'Idle' }),
|
|
241
|
+
[],
|
|
242
|
+
]), M.orElse(() => [model, []])),
|
|
243
|
+
PressedPointerOnButton: ({ pointerType, button, screenX, screenY, timeStamp, }) => {
|
|
244
|
+
const withPointerType = evo(model, {
|
|
245
|
+
maybeLastButtonPointerType: () => Option.some(pointerType),
|
|
246
|
+
});
|
|
247
|
+
if (pointerType !== 'mouse' || button !== 0) {
|
|
248
|
+
return [withPointerType, []];
|
|
249
|
+
}
|
|
250
|
+
if (model.isOpen) {
|
|
251
|
+
return [
|
|
252
|
+
closedModel(withPointerType),
|
|
253
|
+
pipe(Array.getSomes([
|
|
254
|
+
maybeNextFrameCommand,
|
|
255
|
+
maybeUnlockScrollCommand,
|
|
256
|
+
maybeRestoreInertCommand,
|
|
257
|
+
]), Array.prepend(Task.focus(buttonSelector(model.id), () => NoOp()))),
|
|
258
|
+
];
|
|
259
|
+
}
|
|
260
|
+
const nextModel = evo(withPointerType, {
|
|
261
|
+
isOpen: () => true,
|
|
262
|
+
transitionState: () => (model.isAnimated ? 'EnterStart' : 'Idle'),
|
|
263
|
+
maybeActiveItemIndex: () => Option.none(),
|
|
264
|
+
activationTrigger: () => 'Pointer',
|
|
265
|
+
searchQuery: () => '',
|
|
266
|
+
searchVersion: () => 0,
|
|
267
|
+
maybeLastPointerPosition: () => Option.none(),
|
|
268
|
+
maybePointerOrigin: () => Option.some({ screenX, screenY, timeStamp }),
|
|
269
|
+
});
|
|
270
|
+
return [
|
|
271
|
+
nextModel,
|
|
272
|
+
pipe(Array.getSomes([
|
|
273
|
+
maybeNextFrameCommand,
|
|
274
|
+
maybeLockScrollCommand,
|
|
275
|
+
maybeInertOthersCommand,
|
|
276
|
+
]), Array.prepend(Task.focus(itemsSelector(model.id), () => NoOp()))),
|
|
277
|
+
];
|
|
278
|
+
},
|
|
279
|
+
ReleasedPointerOnItems: ({ screenX, screenY, timeStamp }) => {
|
|
280
|
+
const hasNoOrigin = Option.isNone(model.maybePointerOrigin);
|
|
281
|
+
const hasNoActiveItem = Option.isNone(model.maybeActiveItemIndex);
|
|
282
|
+
const isMovementBelowThreshold = Option.exists(model.maybePointerOrigin, origin => Math.abs(screenX - origin.screenX) <
|
|
283
|
+
POINTER_MOVEMENT_THRESHOLD_PIXELS &&
|
|
284
|
+
Math.abs(screenY - origin.screenY) <
|
|
285
|
+
POINTER_MOVEMENT_THRESHOLD_PIXELS);
|
|
286
|
+
const isHoldTimeBelowThreshold = Option.exists(model.maybePointerOrigin, origin => timeStamp - origin.timeStamp < POINTER_HOLD_THRESHOLD_MILLISECONDS);
|
|
287
|
+
if (hasNoOrigin ||
|
|
288
|
+
isMovementBelowThreshold ||
|
|
289
|
+
isHoldTimeBelowThreshold ||
|
|
290
|
+
hasNoActiveItem) {
|
|
291
|
+
return [model, []];
|
|
292
|
+
}
|
|
293
|
+
return [
|
|
294
|
+
model,
|
|
295
|
+
[
|
|
296
|
+
Task.clickElement(itemSelector(model.id, model.maybeActiveItemIndex.value), () => NoOp()),
|
|
297
|
+
],
|
|
298
|
+
];
|
|
299
|
+
},
|
|
300
|
+
NoOp: () => [model, []],
|
|
301
|
+
}));
|
|
302
|
+
};
|
|
152
303
|
export const groupContiguous = (items, toKey) => {
|
|
153
304
|
const tagged = Array.map(items, (item, index) => ({
|
|
154
305
|
key: toKey(item, index),
|
|
155
306
|
item,
|
|
156
307
|
}));
|
|
157
|
-
return Array.chop(tagged,
|
|
308
|
+
return Array.chop(tagged, nonEmpty => {
|
|
158
309
|
const key = Array.headNonEmpty(nonEmpty).key;
|
|
159
|
-
const [matching, rest] = Array.span(nonEmpty,
|
|
310
|
+
const [matching, rest] = Array.span(nonEmpty, tagged => tagged.key === key);
|
|
160
311
|
return [{ key, items: Array.map(matching, ({ item }) => item) }, rest];
|
|
161
312
|
});
|
|
162
313
|
};
|
|
@@ -167,18 +318,35 @@ export const resolveTypeaheadMatch = (items, query, maybeActiveItemIndex, isDisa
|
|
|
167
318
|
const offset = isRefinement ? 0 : 1;
|
|
168
319
|
const startIndex = Option.match(maybeActiveItemIndex, {
|
|
169
320
|
onNone: () => 0,
|
|
170
|
-
onSome:
|
|
321
|
+
onSome: index => index + offset,
|
|
171
322
|
});
|
|
172
323
|
const isEnabledMatch = (index) => !isDisabled(index) &&
|
|
173
|
-
pipe(items, Array.get(index), Option.exists(
|
|
174
|
-
return pipe(items.length, Array.makeBy(
|
|
324
|
+
pipe(items, Array.get(index), Option.exists(item => pipe(itemToSearchText(item, index), Str.toLowerCase, Str.startsWith(lowerQuery))));
|
|
325
|
+
return pipe(items.length, Array.makeBy(step => wrapIndex(startIndex + step, items.length)), Array.findFirst(isEnabledMatch));
|
|
175
326
|
};
|
|
176
327
|
/** Renders a headless menu with typeahead search, keyboard navigation, and aria-activedescendant focus management. */
|
|
177
328
|
export const view = (config) => {
|
|
178
|
-
const { div, AriaActiveDescendant, AriaControls, AriaDisabled, AriaExpanded, AriaHasPopup, AriaLabelledBy, Class, DataAttribute, Id, OnBlur, OnClick, OnKeyDownPreventDefault,
|
|
179
|
-
const { model: { id, isOpen, maybeActiveItemIndex, searchQuery }, toMessage, items, itemToConfig, isItemDisabled, itemToSearchText = (item) => item, isButtonDisabled, buttonContent, buttonClassName, itemsClassName, backdropClassName, className, itemGroupKey, groupToHeading, groupClassName, separatorClassName, } = config;
|
|
329
|
+
const { div, AriaActiveDescendant, AriaControls, AriaDisabled, AriaExpanded, AriaHasPopup, AriaLabelledBy, Class, DataAttribute, Id, OnBlur, OnClick, OnKeyDownPreventDefault, OnKeyUpPreventDefault, OnPointerDown, OnPointerLeave, OnPointerMove, OnPointerUp, Role, Tabindex, Type, keyed, } = html();
|
|
330
|
+
const { model: { id, isOpen, transitionState, maybeActiveItemIndex, searchQuery, maybeLastButtonPointerType, }, toMessage, items, itemToConfig, isItemDisabled, itemToSearchText = (item) => item, isButtonDisabled, buttonContent, buttonClassName, itemsClassName, backdropClassName, className, itemGroupKey, groupToHeading, groupClassName, separatorClassName, } = config;
|
|
331
|
+
const isLeaving = transitionState === 'LeaveStart' || transitionState === 'LeaveAnimating';
|
|
332
|
+
const isVisible = isOpen || isLeaving;
|
|
333
|
+
const transitionAttributes = M.value(transitionState).pipe(M.when('EnterStart', () => [
|
|
334
|
+
DataAttribute('closed', ''),
|
|
335
|
+
DataAttribute('enter', ''),
|
|
336
|
+
DataAttribute('transition', ''),
|
|
337
|
+
]), M.when('EnterAnimating', () => [
|
|
338
|
+
DataAttribute('enter', ''),
|
|
339
|
+
DataAttribute('transition', ''),
|
|
340
|
+
]), M.when('LeaveStart', () => [
|
|
341
|
+
DataAttribute('leave', ''),
|
|
342
|
+
DataAttribute('transition', ''),
|
|
343
|
+
]), M.when('LeaveAnimating', () => [
|
|
344
|
+
DataAttribute('closed', ''),
|
|
345
|
+
DataAttribute('leave', ''),
|
|
346
|
+
DataAttribute('transition', ''),
|
|
347
|
+
]), M.orElse(() => []));
|
|
180
348
|
const isDisabled = (index) => !!isItemDisabled &&
|
|
181
|
-
pipe(items, Array.get(index), Option.exists(
|
|
349
|
+
pipe(items, Array.get(index), Option.exists(item => isItemDisabled(item, index)));
|
|
182
350
|
const firstEnabledIndex = findFirstEnabledIndex(items.length, 0, isDisabled)(0, 1);
|
|
183
351
|
const lastEnabledIndex = findFirstEnabledIndex(items.length, 0, isDisabled)(items.length - 1, -1);
|
|
184
352
|
const handleButtonKeyDown = (key) => M.value(key).pipe(M.whenOr('Enter', ' ', 'ArrowDown', () => Option.some(toMessage(Opened({
|
|
@@ -186,36 +354,59 @@ export const view = (config) => {
|
|
|
186
354
|
})))), M.when('ArrowUp', () => Option.some(toMessage(Opened({
|
|
187
355
|
maybeActiveItemIndex: Option.some(lastEnabledIndex),
|
|
188
356
|
})))), M.orElse(() => Option.none()));
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
357
|
+
const handleButtonPointerDown = (pointerType, button, screenX, screenY, timeStamp) => Option.some(toMessage(PressedPointerOnButton({
|
|
358
|
+
pointerType,
|
|
359
|
+
button,
|
|
360
|
+
screenX,
|
|
361
|
+
screenY,
|
|
362
|
+
timeStamp,
|
|
363
|
+
})));
|
|
364
|
+
const handleButtonClick = () => {
|
|
365
|
+
const isMouse = Option.exists(maybeLastButtonPointerType, type => type === 'mouse');
|
|
366
|
+
if (isMouse) {
|
|
367
|
+
return toMessage(NoOp());
|
|
368
|
+
}
|
|
369
|
+
else if (isOpen) {
|
|
370
|
+
return toMessage(Closed());
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
return toMessage(Opened({ maybeActiveItemIndex: Option.none() }));
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
const handleSpaceKeyUp = (key) => OptionExt.when(key === ' ', toMessage(NoOp()));
|
|
192
377
|
const resolveActiveIndex = keyToIndex('ArrowDown', 'ArrowUp', items.length, Option.getOrElse(maybeActiveItemIndex, () => 0), isDisabled);
|
|
193
|
-
const
|
|
194
|
-
index: resolveActiveIndex(key),
|
|
195
|
-
activationTrigger: 'Keyboard',
|
|
196
|
-
})))), M.when((key) => key.length === 1, () => {
|
|
378
|
+
const searchForKey = (key) => {
|
|
197
379
|
const nextQuery = searchQuery + key;
|
|
198
380
|
const maybeTargetIndex = resolveTypeaheadMatch(items, nextQuery, maybeActiveItemIndex, isDisabled, itemToSearchText, Str.isNonEmpty(searchQuery));
|
|
199
381
|
return Option.some(toMessage(Searched({ key, maybeTargetIndex })));
|
|
200
|
-
}
|
|
382
|
+
};
|
|
383
|
+
const handleItemsKeyDown = (key) => M.value(key).pipe(M.when('Escape', () => Option.some(toMessage(Closed()))), M.when('Enter', () => Option.map(maybeActiveItemIndex, index => toMessage(RequestedItemClick({ index })))), M.when(' ', () => Str.isNonEmpty(searchQuery)
|
|
384
|
+
? searchForKey(' ')
|
|
385
|
+
: Option.map(maybeActiveItemIndex, index => toMessage(RequestedItemClick({ index })))), M.whenOr('ArrowDown', 'ArrowUp', 'Home', 'End', 'PageUp', 'PageDown', () => Option.some(toMessage(ActivatedItem({
|
|
386
|
+
index: resolveActiveIndex(key),
|
|
387
|
+
activationTrigger: 'Keyboard',
|
|
388
|
+
})))), M.when(key => key.length === 1, () => searchForKey(key)), M.orElse(() => Option.none()));
|
|
389
|
+
const handleItemsPointerUp = (screenX, screenY, pointerType, timeStamp) => OptionExt.when(pointerType === 'mouse', toMessage(ReleasedPointerOnItems({ screenX, screenY, timeStamp })));
|
|
201
390
|
const buttonAttributes = [
|
|
202
391
|
Id(`${id}-button`),
|
|
203
392
|
Type('button'),
|
|
204
393
|
Class(buttonClassName),
|
|
205
394
|
AriaHasPopup('menu'),
|
|
206
|
-
AriaExpanded(
|
|
395
|
+
AriaExpanded(isVisible),
|
|
207
396
|
AriaControls(`${id}-items`),
|
|
208
397
|
...(isButtonDisabled
|
|
209
398
|
? [AriaDisabled(true), DataAttribute('disabled', '')]
|
|
210
399
|
: [
|
|
400
|
+
OnPointerDown(handleButtonPointerDown),
|
|
211
401
|
OnKeyDownPreventDefault(handleButtonKeyDown),
|
|
402
|
+
OnKeyUpPreventDefault(handleSpaceKeyUp),
|
|
212
403
|
OnClick(handleButtonClick()),
|
|
213
404
|
]),
|
|
214
|
-
...(
|
|
405
|
+
...(isVisible ? [DataAttribute('open', '')] : []),
|
|
215
406
|
];
|
|
216
407
|
const maybeActiveDescendant = Option.match(maybeActiveItemIndex, {
|
|
217
408
|
onNone: () => [],
|
|
218
|
-
onSome:
|
|
409
|
+
onSome: index => [AriaActiveDescendant(itemId(id, index))],
|
|
219
410
|
});
|
|
220
411
|
const itemsContainerAttributes = [
|
|
221
412
|
Id(`${id}-items`),
|
|
@@ -224,16 +415,24 @@ export const view = (config) => {
|
|
|
224
415
|
...maybeActiveDescendant,
|
|
225
416
|
Tabindex(0),
|
|
226
417
|
Class(itemsClassName),
|
|
227
|
-
|
|
228
|
-
|
|
418
|
+
...transitionAttributes,
|
|
419
|
+
...(isLeaving
|
|
420
|
+
? []
|
|
421
|
+
: [
|
|
422
|
+
OnKeyDownPreventDefault(handleItemsKeyDown),
|
|
423
|
+
OnKeyUpPreventDefault(handleSpaceKeyUp),
|
|
424
|
+
OnPointerUp(handleItemsPointerUp),
|
|
425
|
+
OnBlur(toMessage(ClosedByTab())),
|
|
426
|
+
]),
|
|
229
427
|
];
|
|
230
428
|
const menuItems = Array.map(items, (item, index) => {
|
|
231
|
-
const isActiveItem = Option.exists(maybeActiveItemIndex,
|
|
429
|
+
const isActiveItem = Option.exists(maybeActiveItemIndex, activeIndex => activeIndex === index);
|
|
232
430
|
const isDisabledItem = isDisabled(index);
|
|
233
431
|
const itemConfig = itemToConfig(item, {
|
|
234
432
|
isActive: isActiveItem,
|
|
235
433
|
isDisabled: isDisabledItem,
|
|
236
434
|
});
|
|
435
|
+
const isInteractive = !isDisabledItem && !isLeaving;
|
|
237
436
|
return keyed('div')(itemId(id, index), [
|
|
238
437
|
Id(itemId(id, index)),
|
|
239
438
|
Role('menuitem'),
|
|
@@ -242,11 +441,14 @@ export const view = (config) => {
|
|
|
242
441
|
...(isActiveItem ? [DataAttribute('active', '')] : []),
|
|
243
442
|
...(isDisabledItem
|
|
244
443
|
? [AriaDisabled(true), DataAttribute('disabled', '')]
|
|
245
|
-
: [
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
444
|
+
: []),
|
|
445
|
+
...(isInteractive
|
|
446
|
+
? [
|
|
447
|
+
OnClick(toMessage(SelectedItem({ index }))),
|
|
448
|
+
OnPointerMove((screenX, screenY, pointerType) => OptionExt.when(pointerType !== 'touch', toMessage(MovedPointerOverItem({ index, screenX, screenY })))),
|
|
449
|
+
OnPointerLeave(pointerType => OptionExt.when(pointerType !== 'touch', toMessage(DeactivatedItem()))),
|
|
450
|
+
]
|
|
451
|
+
: []),
|
|
250
452
|
], [itemConfig.content]);
|
|
251
453
|
});
|
|
252
454
|
const renderGroupedItems = () => {
|
|
@@ -255,14 +457,14 @@ export const view = (config) => {
|
|
|
255
457
|
}
|
|
256
458
|
const segments = groupContiguous(menuItems, (_, index) => Array.get(items, index).pipe(Option.match({
|
|
257
459
|
onNone: () => '',
|
|
258
|
-
onSome:
|
|
460
|
+
onSome: item => itemGroupKey(item, index),
|
|
259
461
|
})));
|
|
260
462
|
return Array.flatMap(segments, (segment, segmentIndex) => {
|
|
261
463
|
const maybeHeading = Option.fromNullable(groupToHeading && groupToHeading(segment.key));
|
|
262
464
|
const headingId = `${id}-heading-${segment.key}`;
|
|
263
465
|
const headingElement = Option.match(maybeHeading, {
|
|
264
466
|
onNone: () => [],
|
|
265
|
-
onSome:
|
|
467
|
+
onSome: heading => [
|
|
266
468
|
keyed('div')(headingId, [Id(headingId), Role('presentation'), Class(heading.className)], [heading.content]),
|
|
267
469
|
],
|
|
268
470
|
});
|
|
@@ -280,18 +482,21 @@ export const view = (config) => {
|
|
|
280
482
|
return [...separator, groupElement];
|
|
281
483
|
});
|
|
282
484
|
};
|
|
283
|
-
const backdrop = keyed('div')(`${id}-backdrop`, [
|
|
485
|
+
const backdrop = keyed('div')(`${id}-backdrop`, [
|
|
486
|
+
Class(backdropClassName),
|
|
487
|
+
...(isLeaving ? [] : [OnClick(toMessage(Closed()))]),
|
|
488
|
+
], []);
|
|
284
489
|
const renderedItems = renderGroupedItems();
|
|
285
|
-
const
|
|
490
|
+
const visibleContent = [
|
|
286
491
|
backdrop,
|
|
287
492
|
keyed('div')(`${id}-items-container`, itemsContainerAttributes, renderedItems),
|
|
288
493
|
];
|
|
289
494
|
const wrapperAttributes = [
|
|
290
495
|
...(className ? [Class(className)] : []),
|
|
291
|
-
...(
|
|
496
|
+
...(isVisible ? [DataAttribute('open', '')] : []),
|
|
292
497
|
];
|
|
293
498
|
return div(wrapperAttributes, [
|
|
294
499
|
keyed('button')(`${id}-button`, buttonAttributes, [buttonContent]),
|
|
295
|
-
...(
|
|
500
|
+
...(isVisible ? visibleContent : []),
|
|
296
501
|
]);
|
|
297
502
|
};
|
package/dist/ui/menu/public.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { init, update, view, Model, Message } from './index';
|
|
2
|
-
export type { ActivationTrigger, Opened, Closed, ClosedByTab,
|
|
1
|
+
export { init, update, view, Model, Message, TransitionState } from './index';
|
|
2
|
+
export type { ActivationTrigger, Opened, Closed, ClosedByTab, ActivatedItem, DeactivatedItem, SelectedItem, MovedPointerOverItem, Searched, ClearedSearch, NoOp, AdvancedTransitionFrame, EndedTransition, InitConfig, ViewConfig, ItemConfig, GroupHeading, } from './index';
|
|
3
3
|
//# sourceMappingURL=public.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/ui/menu/public.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/ui/menu/public.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAE7E,YAAY,EACV,iBAAiB,EACjB,MAAM,EACN,MAAM,EACN,WAAW,EACX,aAAa,EACb,eAAe,EACf,YAAY,EACZ,oBAAoB,EACpB,QAAQ,EACR,aAAa,EACb,IAAI,EACJ,uBAAuB,EACvB,eAAe,EACf,UAAU,EACV,UAAU,EACV,UAAU,EACV,YAAY,GACb,MAAM,SAAS,CAAA"}
|
package/dist/ui/menu/public.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { init, update, view, Model, Message } from './index';
|
|
1
|
+
export { init, update, view, Model, Message, TransitionState } from './index';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/tabs/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,MAAM,IAAI,CAAC,EAAgB,MAAM,QAAQ,CAAA;AAG7E,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/tabs/index.ts"],"names":[],"mappings":"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"}
|