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.
Files changed (69) hide show
  1. package/README.md +11 -11
  2. package/dist/effectExtensions/optionExtensions.d.ts +4 -0
  3. package/dist/effectExtensions/optionExtensions.d.ts.map +1 -1
  4. package/dist/effectExtensions/optionExtensions.js +2 -1
  5. package/dist/fieldValidation/index.d.ts.map +1 -1
  6. package/dist/fieldValidation/index.js +4 -4
  7. package/dist/html/index.d.ts +31 -3
  8. package/dist/html/index.d.ts.map +1 -1
  9. package/dist/html/index.js +49 -7
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +1 -0
  13. package/dist/message/index.d.ts +2 -0
  14. package/dist/message/index.d.ts.map +1 -0
  15. package/dist/message/index.js +1 -0
  16. package/dist/message/public.d.ts +2 -0
  17. package/dist/message/public.d.ts.map +1 -0
  18. package/dist/message/public.js +1 -0
  19. package/dist/route/index.d.ts +1 -0
  20. package/dist/route/index.d.ts.map +1 -1
  21. package/dist/route/index.js +1 -0
  22. package/dist/route/parser.js +17 -17
  23. package/dist/route/public.d.ts +1 -1
  24. package/dist/route/public.d.ts.map +1 -1
  25. package/dist/route/public.js +1 -1
  26. package/dist/runtime/runtime.js +8 -8
  27. package/dist/schema/index.d.ts +36 -8
  28. package/dist/schema/index.d.ts.map +1 -1
  29. package/dist/schema/index.js +6 -0
  30. package/dist/task/dom.d.ts +59 -0
  31. package/dist/task/dom.d.ts.map +1 -0
  32. package/dist/task/dom.js +113 -0
  33. package/dist/task/index.d.ts +6 -108
  34. package/dist/task/index.d.ts.map +1 -1
  35. package/dist/task/index.js +6 -168
  36. package/dist/task/inert.d.ts +25 -0
  37. package/dist/task/inert.d.ts.map +1 -0
  38. package/dist/task/inert.js +88 -0
  39. package/dist/task/public.d.ts +1 -1
  40. package/dist/task/public.d.ts.map +1 -1
  41. package/dist/task/public.js +1 -1
  42. package/dist/task/random.d.ts +12 -0
  43. package/dist/task/random.d.ts.map +1 -0
  44. package/dist/task/random.js +11 -0
  45. package/dist/task/scrollLock.d.ts +24 -0
  46. package/dist/task/scrollLock.d.ts.map +1 -0
  47. package/dist/task/scrollLock.js +48 -0
  48. package/dist/task/time.d.ts +42 -0
  49. package/dist/task/time.d.ts.map +1 -0
  50. package/dist/task/time.js +54 -0
  51. package/dist/task/timing.d.ts +36 -0
  52. package/dist/task/timing.d.ts.map +1 -0
  53. package/dist/task/timing.js +55 -0
  54. package/dist/ui/dialog/index.d.ts.map +1 -1
  55. package/dist/ui/dialog/index.js +4 -4
  56. package/dist/ui/disclosure/index.d.ts.map +1 -1
  57. package/dist/ui/disclosure/index.js +4 -4
  58. package/dist/ui/keyboard.d.ts.map +1 -1
  59. package/dist/ui/keyboard.js +1 -1
  60. package/dist/ui/menu/index.d.ts +70 -17
  61. package/dist/ui/menu/index.d.ts.map +1 -1
  62. package/dist/ui/menu/index.js +317 -112
  63. package/dist/ui/menu/public.d.ts +2 -2
  64. package/dist/ui/menu/public.d.ts.map +1 -1
  65. package/dist/ui/menu/public.js +1 -1
  66. package/dist/ui/tabs/index.d.ts.map +1 -1
  67. package/dist/ui/tabs/index.js +6 -6
  68. package/dist/url/index.js +4 -4
  69. package/package.json +5 -1
@@ -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 { ts } from '../../schema';
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 = ts('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 = ts('Closed');
39
+ export const Closed = m('Closed');
27
40
  /** Sent when focus leaves the menu items container via Tab key. */
28
- export const ClosedByTab = ts('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 ItemActivated = ts('ItemActivated', {
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 ItemDeactivated = ts('ItemDeactivated');
48
+ export const DeactivatedItem = m('DeactivatedItem');
36
49
  /** Sent when an item is selected via Enter, Space, or click. */
37
- export const ItemSelected = ts('ItemSelected', { index: S.Number });
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 = ts('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 SearchCleared = ts('SearchCleared', { version: S.Number });
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 PointerMovedOverItem = ts('PointerMovedOverItem', {
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 = ts('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, ItemActivated, ItemDeactivated, ItemSelected, PointerMovedOverItem, Searched, SearchCleared, NoOp);
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) => M.value(message).pipe(M.withReturnType(), M.tagsExhaustive({
81
- Opened: ({ maybeActiveItemIndex }) => [
82
- evo(model, {
83
- isOpen: () => true,
84
- maybeActiveItemIndex: () => maybeActiveItemIndex,
85
- activationTrigger: () => Option.match(maybeActiveItemIndex, {
86
- onNone: () => 'Pointer',
87
- onSome: () => 'Keyboard',
88
- }),
89
- searchQuery: () => '',
90
- searchVersion: () => 0,
91
- maybeLastPointerPosition: () => Option.none(),
92
- }),
93
- [Task.focus(itemsSelector(model.id), () => NoOp())],
94
- ],
95
- Closed: () => [
96
- closedModel(model),
97
- [Task.focus(buttonSelector(model.id), () => NoOp())],
98
- ],
99
- ClosedByTab: () => [closedModel(model), []],
100
- ItemActivated: ({ index, activationTrigger }) => [
101
- evo(model, {
102
- maybeActiveItemIndex: () => Option.some(index),
103
- activationTrigger: () => activationTrigger,
104
- }),
105
- activationTrigger === 'Keyboard'
106
- ? [Task.scrollIntoView(itemSelector(model.id, index), () => NoOp())]
107
- : [],
108
- ],
109
- PointerMovedOverItem: ({ index, screenX, screenY }) => {
110
- const isSamePosition = Option.exists(model.maybeLastPointerPosition, (position) => position.screenX === screenX && position.screenY === screenY);
111
- if (isSamePosition) {
112
- return [model, []];
113
- }
114
- return [
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: () => 'Pointer',
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.delay(SEARCH_DEBOUNCE_MILLISECONDS, () => SearchCleared({ version: nextSearchVersion })),
231
+ Task.waitForTransitions(itemsSelector(model.id), () => EndedTransition()),
141
232
  ],
142
- ];
143
- },
144
- SearchCleared: ({ version }) => {
145
- if (version !== model.searchVersion) {
146
- return [model, []];
147
- }
148
- return [evo(model, { searchQuery: () => '' }), []];
149
- },
150
- NoOp: () => [model, []],
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, (nonEmpty) => {
308
+ return Array.chop(tagged, nonEmpty => {
158
309
  const key = Array.headNonEmpty(nonEmpty).key;
159
- const [matching, rest] = Array.span(nonEmpty, (tagged) => tagged.key === key);
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: (index) => index + offset,
321
+ onSome: index => index + offset,
171
322
  });
172
323
  const isEnabledMatch = (index) => !isDisabled(index) &&
173
- pipe(items, Array.get(index), Option.exists((item) => pipe(itemToSearchText(item, index), Str.toLowerCase, Str.startsWith(lowerQuery))));
174
- return pipe(items.length, Array.makeBy((step) => wrapIndex(startIndex + step, items.length)), Array.findFirst(isEnabledMatch));
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, OnMouseLeave, OnPointerMove, Role, Tabindex, Type, keyed, } = html();
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((item) => isItemDisabled(item, index)));
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 handleButtonClick = () => isOpen
190
- ? toMessage(Closed())
191
- : toMessage(Opened({ maybeActiveItemIndex: Option.none() }));
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 handleItemsKeyDown = (key) => M.value(key).pipe(M.when('Escape', () => Option.some(toMessage(Closed()))), M.whenOr('Enter', ' ', () => Option.map(maybeActiveItemIndex, (index) => toMessage(ItemSelected({ index })))), M.whenOr('ArrowDown', 'ArrowUp', 'Home', 'End', 'PageUp', 'PageDown', () => Option.some(toMessage(ItemActivated({
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
- }), M.orElse(() => Option.none()));
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(isOpen),
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
- ...(isOpen ? [DataAttribute('open', '')] : []),
405
+ ...(isVisible ? [DataAttribute('open', '')] : []),
215
406
  ];
216
407
  const maybeActiveDescendant = Option.match(maybeActiveItemIndex, {
217
408
  onNone: () => [],
218
- onSome: (index) => [AriaActiveDescendant(itemId(id, index))],
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
- OnKeyDownPreventDefault(handleItemsKeyDown),
228
- OnBlur(toMessage(ClosedByTab())),
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, (activeIndex) => activeIndex === index);
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
- OnClick(toMessage(ItemSelected({ index }))),
247
- OnPointerMove((screenX, screenY) => toMessage(PointerMovedOverItem({ index, screenX, screenY }))),
248
- OnMouseLeave(toMessage(ItemDeactivated())),
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: (item) => itemGroupKey(item, index),
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: (heading) => [
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`, [Class(backdropClassName), OnClick(toMessage(Closed()))], []);
485
+ const backdrop = keyed('div')(`${id}-backdrop`, [
486
+ Class(backdropClassName),
487
+ ...(isLeaving ? [] : [OnClick(toMessage(Closed()))]),
488
+ ], []);
284
489
  const renderedItems = renderGroupedItems();
285
- const openContent = [
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
- ...(isOpen ? [DataAttribute('open', '')] : []),
496
+ ...(isVisible ? [DataAttribute('open', '')] : []),
292
497
  ];
293
498
  return div(wrapperAttributes, [
294
499
  keyed('button')(`${id}-button`, buttonAttributes, [buttonContent]),
295
- ...(isOpen ? openContent : []),
500
+ ...(isVisible ? visibleContent : []),
296
501
  ]);
297
502
  };
@@ -1,3 +1,3 @@
1
- export { init, update, view, Model, Message } from './index';
2
- export type { ActivationTrigger, Opened, Closed, ClosedByTab, ItemActivated, ItemDeactivated, ItemSelected, PointerMovedOverItem, Searched, SearchCleared, NoOp, InitConfig, ViewConfig, ItemConfig, GroupHeading, } from './index';
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;AAE5D,YAAY,EACV,iBAAiB,EACjB,MAAM,EACN,MAAM,EACN,WAAW,EACX,aAAa,EACb,eAAe,EACf,YAAY,EACZ,oBAAoB,EACpB,QAAQ,EACR,aAAa,EACb,IAAI,EACJ,UAAU,EACV,UAAU,EACV,UAAU,EACV,YAAY,GACb,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"}
@@ -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":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/tabs/index.ts"],"names":[],"mappings":"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"}