@ariakit/components 0.1.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 (115) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/checkbox/checkbox-store.d.ts +47 -0
  3. package/dist/checkbox/checkbox-store.d.ts.map +1 -0
  4. package/dist/checkbox/checkbox-store.js +16 -0
  5. package/dist/checkbox/checkbox-store.js.map +1 -0
  6. package/dist/collection/collection-store.d.ts +2 -0
  7. package/dist/collection/collection-store.js +132 -0
  8. package/dist/collection/collection-store.js.map +1 -0
  9. package/dist/collection-store-yNe83BiS.d.ts +81 -0
  10. package/dist/collection-store-yNe83BiS.d.ts.map +1 -0
  11. package/dist/combobox/combobox-store.d.ts +150 -0
  12. package/dist/combobox/combobox-store.d.ts.map +1 -0
  13. package/dist/combobox/combobox-store.js +83 -0
  14. package/dist/combobox/combobox-store.js.map +1 -0
  15. package/dist/composite/composite-overflow-store.d.ts +16 -0
  16. package/dist/composite/composite-overflow-store.d.ts.map +1 -0
  17. package/dist/composite/composite-overflow-store.js +12 -0
  18. package/dist/composite/composite-overflow-store.js.map +1 -0
  19. package/dist/composite/composite-store.d.ts +2 -0
  20. package/dist/composite/composite-store.js +167 -0
  21. package/dist/composite/composite-store.js.map +1 -0
  22. package/dist/composite-store-B-iDEtZZ.d.ts +331 -0
  23. package/dist/composite-store-B-iDEtZZ.d.ts.map +1 -0
  24. package/dist/dialog/dialog-store.d.ts +2 -0
  25. package/dist/dialog/dialog-store.js +12 -0
  26. package/dist/dialog/dialog-store.js.map +1 -0
  27. package/dist/dialog-store-BOLvw2IX.d.ts +16 -0
  28. package/dist/dialog-store-BOLvw2IX.d.ts.map +1 -0
  29. package/dist/disclosure/disclosure-store.d.ts +2 -0
  30. package/dist/disclosure/disclosure-store.js +47 -0
  31. package/dist/disclosure/disclosure-store.js.map +1 -0
  32. package/dist/disclosure-store-xKlQffR0.d.ts +142 -0
  33. package/dist/disclosure-store-xKlQffR0.d.ts.map +1 -0
  34. package/dist/form/form-store.d.ts +247 -0
  35. package/dist/form/form-store.d.ts.map +1 -0
  36. package/dist/form/form-store.js +211 -0
  37. package/dist/form/form-store.js.map +1 -0
  38. package/dist/form/types.d.ts +37 -0
  39. package/dist/form/types.d.ts.map +1 -0
  40. package/dist/form/types.js +0 -0
  41. package/dist/hovercard/hovercard-store.d.ts +65 -0
  42. package/dist/hovercard/hovercard-store.d.ts.map +1 -0
  43. package/dist/hovercard/hovercard-store.js +31 -0
  44. package/dist/hovercard/hovercard-store.js.map +1 -0
  45. package/dist/index.d.ts +5 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +6 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/menu/menu-bar-store.d.ts +16 -0
  50. package/dist/menu/menu-bar-store.d.ts.map +1 -0
  51. package/dist/menu/menu-bar-store.js +12 -0
  52. package/dist/menu/menu-bar-store.js.map +1 -0
  53. package/dist/menu/menu-store.d.ts +100 -0
  54. package/dist/menu/menu-store.d.ts.map +1 -0
  55. package/dist/menu/menu-store.js +74 -0
  56. package/dist/menu/menu-store.js.map +1 -0
  57. package/dist/menubar/menubar-store.d.ts +2 -0
  58. package/dist/menubar/menubar-store.js +24 -0
  59. package/dist/menubar/menubar-store.js.map +1 -0
  60. package/dist/menubar-store-CD3YDYfW.d.ts +16 -0
  61. package/dist/menubar-store-CD3YDYfW.d.ts.map +1 -0
  62. package/dist/popover/popover-store.d.ts +2 -0
  63. package/dist/popover/popover-store.js +44 -0
  64. package/dist/popover/popover-store.js.map +1 -0
  65. package/dist/popover-store-DoCiTmUQ.d.ts +106 -0
  66. package/dist/popover-store-DoCiTmUQ.d.ts.map +1 -0
  67. package/dist/radio/radio-store.d.ts +42 -0
  68. package/dist/radio/radio-store.d.ts.map +1 -0
  69. package/dist/radio/radio-store.js +27 -0
  70. package/dist/radio/radio-store.js.map +1 -0
  71. package/dist/select/select-store.d.ts +116 -0
  72. package/dist/select/select-store.d.ts.map +1 -0
  73. package/dist/select/select-store.js +93 -0
  74. package/dist/select/select-store.js.map +1 -0
  75. package/dist/tab/tab-store.d.ts +127 -0
  76. package/dist/tab/tab-store.d.ts.map +1 -0
  77. package/dist/tab/tab-store.js +107 -0
  78. package/dist/tab/tab-store.js.map +1 -0
  79. package/dist/tag/tag-store.d.ts +2 -0
  80. package/dist/tag/tag-store.js +60 -0
  81. package/dist/tag/tag-store.js.map +1 -0
  82. package/dist/tag-store-D47X5_zA.d.ts +83 -0
  83. package/dist/tag-store-D47X5_zA.d.ts.map +1 -0
  84. package/dist/toolbar/toolbar-store.d.ts +21 -0
  85. package/dist/toolbar/toolbar-store.d.ts.map +1 -0
  86. package/dist/toolbar/toolbar-store.js +18 -0
  87. package/dist/toolbar/toolbar-store.js.map +1 -0
  88. package/dist/tooltip/tooltip-store.d.ts +35 -0
  89. package/dist/tooltip/tooltip-store.d.ts.map +1 -0
  90. package/dist/tooltip/tooltip-store.js +29 -0
  91. package/dist/tooltip/tooltip-store.js.map +1 -0
  92. package/license +21 -0
  93. package/package.json +121 -0
  94. package/readme.md +19 -0
  95. package/src/checkbox/checkbox-store.ts +93 -0
  96. package/src/collection/collection-store.ts +301 -0
  97. package/src/combobox/combobox-store.ts +382 -0
  98. package/src/composite/composite-overflow-store.ts +30 -0
  99. package/src/composite/composite-store.ts +711 -0
  100. package/src/dialog/dialog-store.ts +26 -0
  101. package/src/disclosure/disclosure-store.ts +226 -0
  102. package/src/form/form-store.ts +608 -0
  103. package/src/form/types.ts +44 -0
  104. package/src/hovercard/hovercard-store.ts +112 -0
  105. package/src/index.ts +1 -0
  106. package/src/menu/menu-bar-store.ts +28 -0
  107. package/src/menu/menu-store.ts +263 -0
  108. package/src/menubar/menubar-store.ts +51 -0
  109. package/src/popover/popover-store.ts +170 -0
  110. package/src/radio/radio-store.ts +80 -0
  111. package/src/select/select-store.ts +323 -0
  112. package/src/tab/tab-store.ts +330 -0
  113. package/src/tag/tag-store.ts +170 -0
  114. package/src/toolbar/toolbar-store.ts +47 -0
  115. package/src/tooltip/tooltip-store.ts +93 -0
@@ -0,0 +1,711 @@
1
+ import { createStore, setup, sync } from "@ariakit/store";
2
+ import type { Store, StoreOptions, StoreProps } from "@ariakit/store";
3
+ import { flatten2DArray, reverseArray, defaultValue } from "@ariakit/utils";
4
+ import type { SetState } from "@ariakit/utils";
5
+ import type {
6
+ CollectionStoreFunctions,
7
+ CollectionStoreItem,
8
+ CollectionStoreOptions,
9
+ CollectionStoreState,
10
+ } from "../collection/collection-store.ts";
11
+ import { createCollectionStore } from "../collection/collection-store.ts";
12
+
13
+ type Orientation = "horizontal" | "vertical" | "both";
14
+
15
+ interface NextOptions extends Pick<
16
+ Partial<CompositeStoreState>,
17
+ | "activeId"
18
+ | "focusShift"
19
+ | "focusLoop"
20
+ | "focusWrap"
21
+ | "includesBaseElement"
22
+ | "renderedItems"
23
+ | "rtl"
24
+ > {
25
+ /**
26
+ * The number of items to skip.
27
+ */
28
+ skip?: number;
29
+ }
30
+
31
+ const NULL_ITEM = { id: null as unknown as string };
32
+
33
+ function findFirstEnabledItem(items: CompositeStoreItem[], excludeId?: string) {
34
+ return items.find((item) => {
35
+ if (excludeId) {
36
+ return !item.disabled && item.id !== excludeId;
37
+ }
38
+ return !item.disabled;
39
+ });
40
+ }
41
+
42
+ function getEnabledItems(items: CompositeStoreItem[], excludeId?: string) {
43
+ return items.filter((item) => {
44
+ if (excludeId) {
45
+ return !item.disabled && item.id !== excludeId;
46
+ }
47
+ return !item.disabled;
48
+ });
49
+ }
50
+
51
+ function getItemsInRow(items: CompositeStoreItem[], rowId?: string) {
52
+ return items.filter((item) => item.rowId === rowId);
53
+ }
54
+
55
+ function flipItems(
56
+ items: CompositeStoreItem[],
57
+ activeId: string,
58
+ shouldInsertNullItem = false,
59
+ ): CompositeStoreItem[] {
60
+ const index = items.findIndex((item) => item.id === activeId);
61
+ return [
62
+ ...items.slice(index + 1),
63
+ ...(shouldInsertNullItem ? [NULL_ITEM] : []),
64
+ ...items.slice(0, index),
65
+ ];
66
+ }
67
+
68
+ function groupItemsByRows(items: CompositeStoreItem[]) {
69
+ const rows: CompositeStoreItem[][] = [];
70
+ for (const item of items) {
71
+ const row = rows.find((currentRow) => currentRow[0]?.rowId === item.rowId);
72
+ if (row) {
73
+ row.push(item);
74
+ } else {
75
+ rows.push([item]);
76
+ }
77
+ }
78
+ return rows;
79
+ }
80
+
81
+ function getMaxRowLength(array: CompositeStoreItem[][]) {
82
+ let maxLength = 0;
83
+ for (const { length } of array) {
84
+ if (length > maxLength) {
85
+ maxLength = length;
86
+ }
87
+ }
88
+ return maxLength;
89
+ }
90
+
91
+ function createEmptyItem(rowId?: string) {
92
+ return {
93
+ id: "__EMPTY_ITEM__",
94
+ disabled: true,
95
+ rowId,
96
+ };
97
+ }
98
+
99
+ function normalizeRows(
100
+ rows: CompositeStoreItem[][],
101
+ activeId?: string | null,
102
+ focusShift?: boolean,
103
+ ) {
104
+ const maxLength = getMaxRowLength(rows);
105
+ for (const row of rows) {
106
+ for (let i = 0; i < maxLength; i += 1) {
107
+ const item = row[i];
108
+ if (!item || (focusShift && item.disabled)) {
109
+ const isFirst = i === 0;
110
+ const previousItem =
111
+ isFirst && focusShift ? findFirstEnabledItem(row) : row[i - 1];
112
+ row[i] =
113
+ previousItem && activeId !== previousItem.id && focusShift
114
+ ? previousItem
115
+ : createEmptyItem(previousItem?.rowId);
116
+ }
117
+ }
118
+ }
119
+ return rows;
120
+ }
121
+
122
+ function verticalizeItems(items: CompositeStoreItem[]) {
123
+ const rows = groupItemsByRows(items);
124
+ const maxLength = getMaxRowLength(rows);
125
+ const verticalized: CompositeStoreItem[] = [];
126
+ for (let i = 0; i < maxLength; i += 1) {
127
+ for (const row of rows) {
128
+ const item = row[i];
129
+ if (item) {
130
+ verticalized.push({
131
+ ...item,
132
+ // If there's no rowId, it means that it's not a grid composite, but
133
+ // a single row instead. So, instead of verticalizing it, that is,
134
+ // assigning a different rowId based on the column index, we keep it
135
+ // undefined so they will be part of the same row. This is useful
136
+ // when using up/down on one-dimensional composites.
137
+ rowId: item.rowId ? `${i}` : undefined,
138
+ });
139
+ }
140
+ }
141
+ }
142
+ return verticalized;
143
+ }
144
+
145
+ /**
146
+ * Creates a composite store.
147
+ */
148
+ export function createCompositeStore<
149
+ T extends CompositeStoreItem = CompositeStoreItem,
150
+ >(props: CompositeStoreProps<T> = {}): CompositeStore<T> {
151
+ const syncState = props.store?.getState();
152
+
153
+ const collection = createCollectionStore(props);
154
+
155
+ const activeId = defaultValue(
156
+ props.activeId,
157
+ syncState?.activeId,
158
+ props.defaultActiveId,
159
+ );
160
+
161
+ const initialState: CompositeStoreState<T> = {
162
+ ...collection.getState(),
163
+ id:
164
+ defaultValue(props.id, syncState?.id) ??
165
+ `id-${Math.random().toString(36).slice(2, 8)}`,
166
+ activeId,
167
+ baseElement: defaultValue(syncState?.baseElement, null),
168
+ includesBaseElement: defaultValue(
169
+ props.includesBaseElement,
170
+ syncState?.includesBaseElement,
171
+ activeId === null,
172
+ ),
173
+ moves: defaultValue(syncState?.moves, 0),
174
+ orientation: defaultValue(
175
+ props.orientation,
176
+ syncState?.orientation,
177
+ "both" as const,
178
+ ),
179
+ rtl: defaultValue(props.rtl, syncState?.rtl, false),
180
+ virtualFocus: defaultValue(
181
+ props.virtualFocus,
182
+ syncState?.virtualFocus,
183
+ false,
184
+ ),
185
+ focusLoop: defaultValue(props.focusLoop, syncState?.focusLoop, false),
186
+ focusWrap: defaultValue(props.focusWrap, syncState?.focusWrap, false),
187
+ focusShift: defaultValue(props.focusShift, syncState?.focusShift, false),
188
+ };
189
+
190
+ const composite = createStore(initialState, collection, props.store);
191
+
192
+ // When the activeId is undefined, we need to find the first enabled item and
193
+ // set it as the activeId.
194
+ setup(composite, () =>
195
+ sync(composite, ["renderedItems", "activeId"], (state) => {
196
+ composite.setState("activeId", (activeId) => {
197
+ if (activeId !== undefined) return activeId;
198
+ return findFirstEnabledItem(state.renderedItems)?.id;
199
+ });
200
+ }),
201
+ );
202
+
203
+ const getNextId = (
204
+ direction: "next" | "previous" | "up" | "down" = "next",
205
+ options: NextOptions = {},
206
+ ): string | null | undefined => {
207
+ const defaultState = composite.getState();
208
+ const {
209
+ skip = 0,
210
+ activeId = defaultState.activeId,
211
+ focusShift = defaultState.focusShift,
212
+ focusLoop = defaultState.focusLoop,
213
+ focusWrap = defaultState.focusWrap,
214
+ includesBaseElement = defaultState.includesBaseElement,
215
+ renderedItems = defaultState.renderedItems,
216
+ rtl = defaultState.rtl,
217
+ } = options;
218
+
219
+ const isVerticalDirection = direction === "up" || direction === "down";
220
+ const isNextDirection = direction === "next" || direction === "down";
221
+
222
+ const canReverse = isNextDirection
223
+ ? rtl && !isVerticalDirection
224
+ : !rtl || isVerticalDirection;
225
+
226
+ const canShift = focusShift && !skip;
227
+
228
+ let items = !isVerticalDirection
229
+ ? renderedItems
230
+ : flatten2DArray(
231
+ normalizeRows(groupItemsByRows(renderedItems), activeId, canShift),
232
+ );
233
+
234
+ items = canReverse ? reverseArray(items) : items;
235
+ items = isVerticalDirection ? verticalizeItems(items) : items;
236
+
237
+ if (activeId == null) {
238
+ // If there's no item focused, we just move the first one.
239
+ return findFirstEnabledItem(items)?.id;
240
+ }
241
+
242
+ const activeItem = items.find((item) => item.id === activeId);
243
+ if (!activeItem) {
244
+ // If there's no item focused, we just move to the first one.
245
+ return findFirstEnabledItem(items)?.id;
246
+ }
247
+
248
+ const isGrid = items.some((item) => item.rowId);
249
+ const activeIndex = items.indexOf(activeItem);
250
+ const nextItems = items.slice(activeIndex + 1);
251
+ const nextItemsInRow = getItemsInRow(nextItems, activeItem.rowId);
252
+
253
+ if (skip) {
254
+ // Home, End, PageUp, PageDown
255
+ const nextEnabledItemsInRow = getEnabledItems(nextItemsInRow, activeId);
256
+ const nextItem =
257
+ nextEnabledItemsInRow.slice(skip)[0] ||
258
+ // If we can't find an item, just return the last one.
259
+ nextEnabledItemsInRow[nextEnabledItemsInRow.length - 1];
260
+ return nextItem?.id;
261
+ }
262
+
263
+ const canLoop =
264
+ focusLoop &&
265
+ (isVerticalDirection
266
+ ? focusLoop !== "horizontal"
267
+ : focusLoop !== "vertical");
268
+
269
+ const canWrap =
270
+ isGrid &&
271
+ focusWrap &&
272
+ (isVerticalDirection
273
+ ? focusWrap !== "horizontal"
274
+ : focusWrap !== "vertical");
275
+
276
+ // When calling next directly, hasNullItem will only be true if if it's not
277
+ // a grid and focusLoop is set to true, which means that pressing right or
278
+ // down keys on grids will never focus the composite container element. On
279
+ // one-dimensional composites that don't loop, pressing right or down keys
280
+ // also doesn't focus on the composite container element.
281
+ const hasNullItem = isNextDirection
282
+ ? (!isGrid || isVerticalDirection) && canLoop && includesBaseElement
283
+ : isVerticalDirection
284
+ ? includesBaseElement
285
+ : false;
286
+
287
+ if (canLoop) {
288
+ const loopItems =
289
+ canWrap && !hasNullItem
290
+ ? items
291
+ : getItemsInRow(items, activeItem.rowId);
292
+ const sortedItems = flipItems(loopItems, activeId, hasNullItem);
293
+ const nextItem = findFirstEnabledItem(sortedItems, activeId);
294
+ return nextItem?.id;
295
+ }
296
+
297
+ if (canWrap) {
298
+ const nextItem = findFirstEnabledItem(
299
+ // We can use nextItems, which contains all the next items, including
300
+ // items from other rows, to wrap between rows. However, if there is a
301
+ // null item (the composite container), we'll only use the next items in
302
+ // the row. So moving next from the last item will focus on the
303
+ // composite container. On grid composites, horizontal navigation never
304
+ // focuses on the composite container, only vertical.
305
+ hasNullItem ? nextItemsInRow : nextItems,
306
+ activeId,
307
+ );
308
+ const nextId = hasNullItem ? nextItem?.id || null : nextItem?.id;
309
+ return nextId;
310
+ }
311
+
312
+ const nextItem = findFirstEnabledItem(nextItemsInRow, activeId);
313
+ if (!nextItem && hasNullItem) {
314
+ return null;
315
+ }
316
+ return nextItem?.id;
317
+ };
318
+
319
+ return {
320
+ ...collection,
321
+ ...composite,
322
+
323
+ setBaseElement: (element) => composite.setState("baseElement", element),
324
+ setActiveId: (id) => composite.setState("activeId", id),
325
+
326
+ move: (id) => {
327
+ // move() does nothing
328
+ if (id === undefined) return;
329
+ composite.setState("activeId", id);
330
+ composite.setState("moves", (moves) => moves + 1);
331
+ },
332
+
333
+ first: () => findFirstEnabledItem(composite.getState().renderedItems)?.id,
334
+ last: () =>
335
+ findFirstEnabledItem(reverseArray(composite.getState().renderedItems))
336
+ ?.id,
337
+
338
+ next: (options) => {
339
+ if (options !== undefined && typeof options === "number") {
340
+ options = { skip: options };
341
+ }
342
+ return getNextId("next", options);
343
+ },
344
+
345
+ previous: (options) => {
346
+ if (options !== undefined && typeof options === "number") {
347
+ options = { skip: options };
348
+ }
349
+ return getNextId("previous", options);
350
+ },
351
+
352
+ down: (options) => {
353
+ if (options !== undefined && typeof options === "number") {
354
+ options = { skip: options };
355
+ }
356
+ return getNextId("down", options);
357
+ },
358
+
359
+ up: (options) => {
360
+ if (options !== undefined && typeof options === "number") {
361
+ options = { skip: options };
362
+ }
363
+ return getNextId("up", options);
364
+ },
365
+ };
366
+ }
367
+
368
+ export type CompositeStoreOrientation = Orientation;
369
+
370
+ export interface CompositeStoreItem extends CollectionStoreItem {
371
+ /**
372
+ * The row id of the item. This is only used on two-dimensional composite
373
+ * widgets (when using
374
+ * [`CompositeRow`](https://ariakit.com/reference/composite-row)).
375
+ */
376
+ rowId?: string;
377
+ /**
378
+ * If enabled, the item will be disabled and users won't be able to focus on
379
+ * it using arrow keys.
380
+ */
381
+ disabled?: boolean;
382
+ /**
383
+ * The item children. This can be used for typeahead purposes.
384
+ */
385
+ children?: string;
386
+ }
387
+
388
+ export interface CompositeStoreState<
389
+ T extends CompositeStoreItem = CompositeStoreItem,
390
+ > extends CollectionStoreState<T> {
391
+ /**
392
+ * The ID of the composite store is used to reference elements within the
393
+ * composite widget before hydration. If not provided, a random ID will be
394
+ * generated.
395
+ */
396
+ id: string;
397
+ /**
398
+ * The composite element itself. Typically, it's the wrapper element that
399
+ * contains composite items. However, in a combobox, it's the input element.
400
+ *
401
+ * Live examples:
402
+ * - [Sliding Menu](https://ariakit.com/examples/menu-slide)
403
+ */
404
+ baseElement: HTMLElement | null;
405
+ /**
406
+ * If enabled, the composite element will act as an
407
+ * [aria-activedescendant](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_focus_activedescendant)
408
+ * container instead of [roving
409
+ * tabindex](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_roving_tabindex).
410
+ * DOM focus will remain on the composite element while its items receive
411
+ * virtual focus.
412
+ *
413
+ * In both scenarios, the item in focus will carry the
414
+ * [`data-active-item`](https://ariakit.com/guide/styling#data-active-item)
415
+ * attribute.
416
+ *
417
+ * Live examples:
418
+ * - [Select with Combobox and
419
+ * Tabs](https://ariakit.com/examples/select-combobox-tab)
420
+ * @default false
421
+ */
422
+ virtualFocus: boolean;
423
+ /**
424
+ * Defines the orientation of the composite widget. If the composite has a
425
+ * single row or column (one-dimensional), the `orientation` value determines
426
+ * which arrow keys can be used to move focus:
427
+ * - `both`: all arrow keys work.
428
+ * - `horizontal`: only left and right arrow keys work.
429
+ * - `vertical`: only up and down arrow keys work.
430
+ *
431
+ * It doesn't have any effect on two-dimensional composites.
432
+ * @default "both"
433
+ */
434
+ orientation: Orientation;
435
+ /**
436
+ * Determines how the
437
+ * [`next`](https://ariakit.com/reference/use-composite-store#next) and
438
+ * [`previous`](https://ariakit.com/reference/use-composite-store#previous)
439
+ * functions will behave. If `rtl` is set to `true`, they will be inverted.
440
+ *
441
+ * This only affects the composite widget behavior. You still need to set
442
+ * `dir="rtl"` on HTML/CSS.
443
+ * @default false
444
+ */
445
+ rtl: boolean;
446
+ /**
447
+ * Determines how the focus behaves when the user reaches the end of the
448
+ * composite widget.
449
+ *
450
+ * On one-dimensional composite widgets:
451
+ * - `true` loops from the last item to the first item and vice-versa.
452
+ * - `horizontal` loops only if
453
+ * [`orientation`](https://ariakit.com/reference/composite-provider#orientation)
454
+ * is `horizontal` or not set.
455
+ * - `vertical` loops only if
456
+ * [`orientation`](https://ariakit.com/reference/composite-provider#orientation)
457
+ * is `vertical` or not set.
458
+ * - If
459
+ * [`includesBaseElement`](https://ariakit.com/reference/composite-provider#includesbaseelement)
460
+ * is set to `true` (or
461
+ * [`activeId`](https://ariakit.com/reference/composite-provider#activeid)
462
+ * is initially set to `null`), the composite element will be focused in
463
+ * between the last and first items.
464
+ *
465
+ * On two-dimensional composite widgets (when using
466
+ * [`CompositeRow`](https://ariakit.com/reference/composite-row) or explicitly
467
+ * passing a [`rowId`](https://ariakit.com/reference/composite-item#rowid)
468
+ * prop to composite items):
469
+ * - `true` loops from the last row/column item to the first item in the same
470
+ * row/column and vice-versa. If it's the last item in the last row, it
471
+ * moves to the first item in the first row and vice-versa.
472
+ * - `horizontal` loops only from the last row item to the first item in the
473
+ * same row.
474
+ * - `vertical` loops only from the last column item to the first item in the
475
+ * column row.
476
+ * - If
477
+ * [`includesBaseElement`](https://ariakit.com/reference/composite-provider#includesbaseelement)
478
+ * is set to `true` (or
479
+ * [`activeId`](https://ariakit.com/reference/composite-provider#activeid)
480
+ * is initially set to `null`), vertical loop will have no effect as moving
481
+ * down from the last row or up from the first row will focus on the
482
+ * composite element.
483
+ * - If
484
+ * [`focusWrap`](https://ariakit.com/reference/composite-provider#focuswrap)
485
+ * matches the value of `focusLoop`, it'll wrap between the last item in the
486
+ * last row or column and the first item in the first row or column and
487
+ * vice-versa.
488
+ *
489
+ * Live examples:
490
+ * - [Command Menu](https://ariakit.com/examples/dialog-combobox-command-menu)
491
+ * - [Command Menu with
492
+ * Tabs](https://ariakit.com/examples/dialog-combobox-tab-command-menu)
493
+ * @default false
494
+ */
495
+ focusLoop: boolean | Orientation;
496
+ /**
497
+ * **Works only on two-dimensional composite widgets**.
498
+ *
499
+ * If enabled, moving to the next item from the last one in a row or column
500
+ * will focus on the first item in the next row or column and vice-versa.
501
+ * - `true` wraps between rows and columns.
502
+ * - `horizontal` wraps only between rows.
503
+ * - `vertical` wraps only between columns.
504
+ * - If
505
+ * [`focusLoop`](https://ariakit.com/reference/composite-provider#focusloop)
506
+ * matches the value of `focusWrap`, it'll wrap between the last item in the
507
+ * last row or column and the first item in the first row or column and
508
+ * vice-versa.
509
+ *
510
+ * Live examples:
511
+ * - [Command Menu with
512
+ * Tabs](https://ariakit.com/examples/dialog-combobox-tab-command-menu)
513
+ * @default false
514
+ */
515
+ focusWrap: boolean | Orientation;
516
+ /**
517
+ * **Works only on two-dimensional composite widgets**.
518
+ *
519
+ * If enabled, moving up or down when there's no next item or when the next
520
+ * item is disabled will shift to the item right before it.
521
+ *
522
+ * Live examples:
523
+ * - [Command Menu with
524
+ * Tabs](https://ariakit.com/examples/dialog-combobox-tab-command-menu)
525
+ * @default false
526
+ */
527
+ focusShift: boolean;
528
+ /**
529
+ * The number of times the
530
+ * [`move`](https://ariakit.com/reference/use-composite-store#move) function
531
+ * has been called.
532
+ */
533
+ moves: number;
534
+ /**
535
+ * Indicates if the composite base element (the one with a [composite
536
+ * role](https://w3c.github.io/aria/#composite)) should be part of the focus
537
+ * order when navigating with arrow keys. In other words, moving to the
538
+ * previous element when the first item is in focus will focus on the
539
+ * composite element itself. The same applies to the last item when moving to
540
+ * the next element.
541
+ *
542
+ * Live examples:
543
+ * - [Submenu with
544
+ * Combobox](https://ariakit.com/examples/menu-nested-combobox)
545
+ * - [Command Menu](https://ariakit.com/examples/dialog-combobox-command-menu)
546
+ * @default false
547
+ */
548
+ includesBaseElement: boolean;
549
+ /**
550
+ * The current active item `id`. The active item is the element within the
551
+ * composite widget that has either DOM or virtual focus (in case
552
+ * [`virtualFocus`](https://ariakit.com/reference/composite-provider#virtualfocus)
553
+ * is enabled).
554
+ * - `null` represents the base composite element (the one with a [composite
555
+ * role](https://w3c.github.io/aria/#composite)). Users will be able to
556
+ * navigate out of it using arrow keys.
557
+ * - If `activeId` is initially set to `null`, the
558
+ * [`includesBaseElement`](https://ariakit.com/reference/composite-provider#includesbaseelement)
559
+ * prop will also default to `true`, which means the base composite element
560
+ * itself will have focus and users will be able to navigate to it using
561
+ * arrow keys.
562
+ *
563
+ * Live examples:
564
+ * - [Combobox with Tabs](https://ariakit.com/examples/combobox-tabs)
565
+ */
566
+ activeId: string | null | undefined;
567
+ }
568
+
569
+ export interface CompositeStoreFunctions<
570
+ T extends CompositeStoreItem = CompositeStoreItem,
571
+ > extends CollectionStoreFunctions<T> {
572
+ /**
573
+ * Sets the `baseElement` state.
574
+ */
575
+ setBaseElement: SetState<CompositeStoreState<T>["baseElement"]>;
576
+ /**
577
+ * Sets the
578
+ * [`activeId`](https://ariakit.com/reference/composite-provider#activeid)
579
+ * state _without moving focus_. If you want to move focus, use the
580
+ * [`move`](https://ariakit.com/reference/use-composite-store#move) function
581
+ * instead.
582
+ * @example
583
+ * // Sets the composite element as the active item
584
+ * store.setActiveId(null);
585
+ * // Sets the item with id "item-1" as the active item
586
+ * store.setActiveId("item-1");
587
+ * // Sets the next item as the active item
588
+ * store.setActiveId(store.next());
589
+ */
590
+ setActiveId: SetState<CompositeStoreState<T>["activeId"]>;
591
+ /**
592
+ * Moves focus to a given item id and sets it as the active item.
593
+ * - Passing `null` will focus on the composite element itself (the one with a
594
+ * [composite role](https://w3c.github.io/aria/#composite)). Users will be
595
+ * able to navigate out of it using arrow keys.
596
+ * - If you want to set the active item id _without moving focus_, use the
597
+ * [`setActiveId`](https://ariakit.com/reference/use-composite-store#setactiveid)
598
+ * function instead.
599
+ *
600
+ * Live examples:
601
+ * - [Select Grid](https://ariakit.com/examples/select-grid)
602
+ * @example
603
+ * // Moves focus to the composite element
604
+ * store.move(null);
605
+ * // Moves focus to the item with id "item-1"
606
+ * store.move("item-1");
607
+ * // Moves focus to the next item
608
+ * store.move(store.next());
609
+ */
610
+ move: (id?: string | null) => void;
611
+ /**
612
+ * Returns the id of the next enabled item based on the current
613
+ * [`activeId`](https://ariakit.com/reference/composite-provider#activeid)
614
+ * state. You can pass additional options to override the current state.
615
+ * @example
616
+ * const nextId = store.next();
617
+ */
618
+ next: {
619
+ (options?: NextOptions): string | null | undefined;
620
+ /**
621
+ * @deprecated Use the object syntax instead: `next({ skip: 2 })`.
622
+ */
623
+ (skip?: number): string | null | undefined;
624
+ };
625
+ /**
626
+ * Returns the id of the previous enabled item based on the current
627
+ * [`activeId`](https://ariakit.com/reference/composite-provider#activeid)
628
+ * state. You can pass additional options to override the current state.
629
+ * @example
630
+ * const previousId = store.previous();
631
+ */
632
+ previous: {
633
+ (options?: NextOptions): string | null | undefined;
634
+ /**
635
+ * @deprecated Use the object syntax instead: `previous({ skip: 2 })`.
636
+ */
637
+ (skip?: number): string | null | undefined;
638
+ };
639
+ /**
640
+ * Returns the id of the enabled item above based on the current
641
+ * [`activeId`](https://ariakit.com/reference/composite-provider#activeid)
642
+ * state. You can pass additional options to override the current state.
643
+ * @example
644
+ * const upId = store.up();
645
+ */
646
+ up: {
647
+ (options?: NextOptions): string | null | undefined;
648
+ /**
649
+ * @deprecated Use the object syntax instead: `up({ skip: 2 })`.
650
+ */
651
+ (skip?: number): string | null | undefined;
652
+ };
653
+ /**
654
+ * Returns the id of the enabled item below based on the current
655
+ * [`activeId`](https://ariakit.com/reference/composite-provider#activeid)
656
+ * state. You can pass additional options to override the current state.
657
+ * @example
658
+ * const downId = store.down();
659
+ */
660
+ down: {
661
+ (options?: NextOptions): string | null | undefined;
662
+ /**
663
+ * @deprecated Use the object syntax instead: `down({ skip: 2 })`.
664
+ */
665
+ (skip?: number): string | null | undefined;
666
+ };
667
+ /**
668
+ * Returns the id of the first enabled item.
669
+ */
670
+ first: () => string | null | undefined;
671
+ /**
672
+ * Returns the id of the last enabled item.
673
+ */
674
+ last: () => string | null | undefined;
675
+ }
676
+
677
+ export interface CompositeStoreOptions<
678
+ T extends CompositeStoreItem = CompositeStoreItem,
679
+ >
680
+ extends
681
+ CollectionStoreOptions<T>,
682
+ StoreOptions<
683
+ CompositeStoreState<T>,
684
+ | "id"
685
+ | "virtualFocus"
686
+ | "orientation"
687
+ | "rtl"
688
+ | "focusLoop"
689
+ | "focusWrap"
690
+ | "focusShift"
691
+ | "includesBaseElement"
692
+ | "activeId"
693
+ > {
694
+ /**
695
+ * The composite item id that should be active by default when the composite
696
+ * widget is rendered. If `null`, the composite element itself will have focus
697
+ * and users will be able to navigate to it using arrow keys. If `undefined`,
698
+ * the first enabled item will be focused.
699
+ */
700
+ defaultActiveId?: CompositeStoreState<T>["activeId"];
701
+ }
702
+
703
+ export interface CompositeStoreProps<
704
+ T extends CompositeStoreItem = CompositeStoreItem,
705
+ >
706
+ extends CompositeStoreOptions<T>, StoreProps<CompositeStoreState<T>> {}
707
+
708
+ export interface CompositeStore<
709
+ T extends CompositeStoreItem = CompositeStoreItem,
710
+ >
711
+ extends CompositeStoreFunctions<T>, Store<CompositeStoreState<T>> {}