@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,323 @@
1
+ import {
2
+ batch,
3
+ createStore,
4
+ mergeStore,
5
+ omit,
6
+ setup,
7
+ sync,
8
+ throwOnConflictingProps,
9
+ } from "@ariakit/store";
10
+ import type { Store, StoreOptions, StoreProps } from "@ariakit/store";
11
+ import { toArray, defaultValue } from "@ariakit/utils";
12
+ import type { PickRequired, SetState } from "@ariakit/utils";
13
+ import type { ComboboxStore } from "../combobox/combobox-store.ts";
14
+ import type {
15
+ CompositeStoreFunctions,
16
+ CompositeStoreItem,
17
+ CompositeStoreOptions,
18
+ CompositeStoreState,
19
+ } from "../composite/composite-store.ts";
20
+ import { createCompositeStore } from "../composite/composite-store.ts";
21
+ import type {
22
+ PopoverStoreFunctions,
23
+ PopoverStoreOptions,
24
+ PopoverStoreState,
25
+ } from "../popover/popover-store.ts";
26
+ import { createPopoverStore } from "../popover/popover-store.ts";
27
+
28
+ type MutableValue<T extends SelectStoreValue = SelectStoreValue> =
29
+ T extends string ? string : T;
30
+
31
+ export function createSelectStore<
32
+ T extends SelectStoreValue = SelectStoreValue,
33
+ >(
34
+ props: PickRequired<SelectStoreProps<T>, "value" | "defaultValue">,
35
+ ): SelectStore<T>;
36
+
37
+ export function createSelectStore(props?: SelectStoreProps): SelectStore;
38
+
39
+ export function createSelectStore({
40
+ combobox,
41
+ ...props
42
+ }: SelectStoreProps = {}): SelectStore {
43
+ const store = mergeStore(
44
+ props.store,
45
+ omit(combobox, [
46
+ "value",
47
+ "items",
48
+ "renderedItems",
49
+ "baseElement",
50
+ "arrowElement",
51
+ "anchorElement",
52
+ "contentElement",
53
+ "popoverElement",
54
+ "disclosureElement",
55
+ ]),
56
+ );
57
+
58
+ throwOnConflictingProps(props, store);
59
+
60
+ const syncState = store.getState();
61
+
62
+ const composite = createCompositeStore({
63
+ ...props,
64
+ store,
65
+ virtualFocus: defaultValue(
66
+ props.virtualFocus,
67
+ syncState.virtualFocus,
68
+ true,
69
+ ),
70
+ includesBaseElement: defaultValue(
71
+ props.includesBaseElement,
72
+ syncState.includesBaseElement,
73
+ false,
74
+ ),
75
+ activeId: defaultValue(
76
+ props.activeId,
77
+ syncState.activeId,
78
+ props.defaultActiveId,
79
+ null,
80
+ ),
81
+ orientation: defaultValue(
82
+ props.orientation,
83
+ syncState.orientation,
84
+ "vertical" as const,
85
+ ),
86
+ });
87
+
88
+ const popover = createPopoverStore({
89
+ ...props,
90
+ store,
91
+ placement: defaultValue(
92
+ props.placement,
93
+ syncState.placement,
94
+ "bottom-start" as const,
95
+ ),
96
+ });
97
+
98
+ const initialValue = new String("") as "";
99
+
100
+ const initialState: SelectStoreState = {
101
+ ...composite.getState(),
102
+ ...popover.getState(),
103
+ value: defaultValue(
104
+ props.value,
105
+ syncState.value,
106
+ props.defaultValue,
107
+ initialValue,
108
+ ),
109
+ setValueOnMove: defaultValue(
110
+ props.setValueOnMove,
111
+ syncState.setValueOnMove,
112
+ false,
113
+ ),
114
+ labelElement: defaultValue(syncState.labelElement, null),
115
+ selectElement: defaultValue(syncState.selectElement, null),
116
+ listElement: defaultValue(syncState.listElement, null),
117
+ };
118
+
119
+ const select = createStore(initialState, composite, popover, store);
120
+
121
+ // Automatically sets the default value if it's not set.
122
+ setup(select, () =>
123
+ sync(select, ["value", "items"], (state) => {
124
+ if (state.value !== initialValue) return;
125
+ if (!state.items.length) return;
126
+ const item = state.items.find(
127
+ (item) => !item.disabled && item.value != null,
128
+ );
129
+ if (item?.value == null) return;
130
+ select.setState("value", item.value);
131
+ }),
132
+ );
133
+
134
+ // Resets the active id to its initial state when the popover is hidden. This
135
+ // guarantees that the active id won't be pointing to another item when the
136
+ // popover is shown again, which would cause the selected item to not be
137
+ // auto-focused. See test "clicking on different tab and clicking outside
138
+ // resets the selected tab".
139
+ setup(select, () =>
140
+ sync(select, ["mounted"], (state) => {
141
+ if (state.mounted) return;
142
+ select.setState("activeId", initialState.activeId);
143
+ }),
144
+ );
145
+
146
+ // Sets the active id when the value changes and the popover is hidden.
147
+ setup(select, () =>
148
+ sync(select, ["mounted", "items", "value"], (state) => {
149
+ // TODO: Revisit this. See test "open with keyboard, then try to open
150
+ // again". Probably deprecate together with using ComboboxProvider as a
151
+ // parent of SelectProvider.
152
+ if (combobox) return;
153
+ if (state.mounted) return;
154
+ const values = toArray(state.value);
155
+ const lastValue = values[values.length - 1];
156
+ if (lastValue == null) return;
157
+ const item = state.items.find(
158
+ (item) => !item.disabled && item.value === lastValue,
159
+ );
160
+ if (!item) return;
161
+ select.setState("activeId", item.id);
162
+ }),
163
+ );
164
+
165
+ // Sets the select value when the active item changes by moving (which usually
166
+ // happens when moving to an item using the keyboard).
167
+ setup(select, () =>
168
+ batch(select, ["setValueOnMove", "moves"], (state) => {
169
+ const { mounted, value, activeId } = select.getState();
170
+ if (!state.setValueOnMove && mounted) return;
171
+ if (Array.isArray(value)) return;
172
+ if (!state.moves) return;
173
+ if (!activeId) return;
174
+ const item = composite.item(activeId);
175
+ if (!item || item.disabled || item.value == null) return;
176
+ select.setState("value", item.value);
177
+ }),
178
+ );
179
+
180
+ return {
181
+ ...composite,
182
+ ...popover,
183
+ ...select,
184
+ combobox,
185
+ setValue: (value) => select.setState("value", value),
186
+ setLabelElement: (element) => select.setState("labelElement", element),
187
+ setSelectElement: (element) => select.setState("selectElement", element),
188
+ setListElement: (element) => select.setState("listElement", element),
189
+ };
190
+ }
191
+
192
+ export type SelectStoreValue = string | readonly string[];
193
+
194
+ export interface SelectStoreItem extends CompositeStoreItem {
195
+ value?: string;
196
+ }
197
+
198
+ export interface SelectStoreState<T extends SelectStoreValue = SelectStoreValue>
199
+ extends CompositeStoreState<SelectStoreItem>, PopoverStoreState {
200
+ /** @default true */
201
+ virtualFocus: CompositeStoreState<SelectStoreItem>["virtualFocus"];
202
+ /** @default null */
203
+ activeId: CompositeStoreState<SelectStoreItem>["activeId"];
204
+ /** @default "vertical" */
205
+ orientation: CompositeStoreState<SelectStoreItem>["orientation"];
206
+ /** @default "bottom-start" */
207
+ placement: PopoverStoreState["placement"];
208
+ /**
209
+ * The select value.
210
+ *
211
+ * Live examples:
212
+ * - [Form with Select](https://ariakit.com/examples/form-select)
213
+ * - [Select Grid](https://ariakit.com/examples/select-grid)
214
+ * - [Select with custom
215
+ * items](https://ariakit.com/examples/select-item-custom)
216
+ * - [Multi-Select](https://ariakit.com/examples/select-multiple)
217
+ * - [Toolbar with Select](https://ariakit.com/examples/toolbar-select)
218
+ * - [Select with Next.js App
219
+ * Router](https://ariakit.com/examples/select-next-router)
220
+ */
221
+ value: MutableValue<T>;
222
+ /**
223
+ * Whether the select
224
+ * [`value`](https://ariakit.com/reference/select-provider#value) should be
225
+ * set when the active item changes by moving (which usually happens when
226
+ * moving to an item using the keyboard).
227
+ *
228
+ * Live examples:
229
+ * - [Select Grid](https://ariakit.com/examples/select-grid)
230
+ * - [Select with custom
231
+ * items](https://ariakit.com/examples/select-item-custom)
232
+ * @default false
233
+ */
234
+ setValueOnMove: boolean;
235
+ /**
236
+ * The select label element.
237
+ */
238
+ labelElement: HTMLElement | null;
239
+ /**
240
+ * The select button element.
241
+ *
242
+ * Live examples:
243
+ * - [Form with Select](https://ariakit.com/examples/form-select)
244
+ */
245
+ selectElement: HTMLElement | null;
246
+ /**
247
+ * The select list element.
248
+ */
249
+ listElement: HTMLElement | null;
250
+ }
251
+
252
+ export interface SelectStoreFunctions<
253
+ T extends SelectStoreValue = SelectStoreValue,
254
+ >
255
+ extends
256
+ Pick<SelectStoreOptions<T>, "combobox">,
257
+ CompositeStoreFunctions<SelectStoreItem>,
258
+ PopoverStoreFunctions {
259
+ /**
260
+ * Sets the [`value`](https://ariakit.com/reference/select-provider#value)
261
+ * state.
262
+ * @example
263
+ * store.setValue("Apple");
264
+ * store.setValue(["Apple", "Banana"]);
265
+ * store.setValue((value) => value === "Apple" ? "Banana" : "Apple"));
266
+ */
267
+ setValue: SetState<SelectStoreState<T>["value"]>;
268
+ /**
269
+ * Sets the `labelElement` state.
270
+ */
271
+ setLabelElement: SetState<SelectStoreState<T>["labelElement"]>;
272
+ /**
273
+ * Sets the `selectElement` state.
274
+ */
275
+ setSelectElement: SetState<SelectStoreState<T>["selectElement"]>;
276
+ /**
277
+ * Sets the `listElement` state.
278
+ */
279
+ setListElement: SetState<SelectStoreState<T>["listElement"]>;
280
+ }
281
+
282
+ export interface SelectStoreOptions<
283
+ T extends SelectStoreValue = SelectStoreValue,
284
+ >
285
+ extends
286
+ StoreOptions<
287
+ SelectStoreState<T>,
288
+ | "virtualFocus"
289
+ | "activeId"
290
+ | "orientation"
291
+ | "placement"
292
+ | "value"
293
+ | "setValueOnMove"
294
+ >,
295
+ CompositeStoreOptions<SelectStoreItem>,
296
+ PopoverStoreOptions {
297
+ /**
298
+ * A reference to a combobox store. This is used when combining the combobox
299
+ * with a select (e.g., select with a search input). The stores will share the
300
+ * same state.
301
+ */
302
+ combobox?: ComboboxStore | null;
303
+ /**
304
+ * The default value. If not set, the first non-disabled item will be used.
305
+ *
306
+ * Live examples:
307
+ * - [Form with Select](https://ariakit.com/examples/form-select)
308
+ * - [Animated Select](https://ariakit.com/examples/select-animated)
309
+ * - [Select with Combobox](https://ariakit.com/examples/select-combobox)
310
+ * - [SelectGroup](https://ariakit.com/examples/select-group)
311
+ * - [Select with Next.js App
312
+ * Router](https://ariakit.com/examples/select-next-router)
313
+ * - [Select with Combobox and
314
+ * Tabs](https://ariakit.com/examples/select-combobox-tab)
315
+ */
316
+ defaultValue?: SelectStoreState<T>["value"];
317
+ }
318
+
319
+ export interface SelectStoreProps<T extends SelectStoreValue = SelectStoreValue>
320
+ extends SelectStoreOptions<T>, StoreProps<SelectStoreState<T>> {}
321
+
322
+ export interface SelectStore<T extends SelectStoreValue = SelectStoreValue>
323
+ extends SelectStoreFunctions<T>, Store<SelectStoreState<T>> {}
@@ -0,0 +1,330 @@
1
+ import {
2
+ batch,
3
+ createStore,
4
+ mergeStore,
5
+ omit,
6
+ setup,
7
+ sync,
8
+ } from "@ariakit/store";
9
+ import type { Store, StoreOptions, StoreProps } from "@ariakit/store";
10
+ import { chain, defaultValue } from "@ariakit/utils";
11
+ import type { SetState } from "@ariakit/utils";
12
+ import type {
13
+ CollectionStore,
14
+ CollectionStoreItem,
15
+ } from "../collection/collection-store.ts";
16
+ import { createCollectionStore } from "../collection/collection-store.ts";
17
+ import type { ComboboxStore } from "../combobox/combobox-store.ts";
18
+ import type {
19
+ CompositeStore,
20
+ CompositeStoreFunctions,
21
+ CompositeStoreItem,
22
+ CompositeStoreOptions,
23
+ CompositeStoreState,
24
+ } from "../composite/composite-store.ts";
25
+ import { createCompositeStore } from "../composite/composite-store.ts";
26
+ import type { SelectStore } from "../select/select-store.ts";
27
+
28
+ export function createTabStore({
29
+ composite: parentComposite,
30
+ combobox,
31
+ ...props
32
+ }: TabStoreProps = {}): TabStore {
33
+ const independentKeys = [
34
+ "items",
35
+ "renderedItems",
36
+ "moves",
37
+ "orientation",
38
+ "virtualFocus",
39
+ "includesBaseElement",
40
+ "baseElement",
41
+ "focusLoop",
42
+ "focusShift",
43
+ "focusWrap",
44
+ ] as const;
45
+
46
+ const store = mergeStore(
47
+ props.store,
48
+ omit(parentComposite, independentKeys),
49
+ omit(combobox, independentKeys),
50
+ );
51
+ const syncState = store?.getState();
52
+
53
+ const composite = createCompositeStore({
54
+ ...props,
55
+ store,
56
+ // We need to explicitly set the default value of `includesBaseElement` to
57
+ // `false` since we don't want the composite store to default it to `true`
58
+ // when the activeId state is null, which could be the case when rendering
59
+ // combobox with tab.
60
+ includesBaseElement: defaultValue(
61
+ props.includesBaseElement,
62
+ syncState?.includesBaseElement,
63
+ false,
64
+ ),
65
+ orientation: defaultValue(
66
+ props.orientation,
67
+ syncState?.orientation,
68
+ "horizontal" as const,
69
+ ),
70
+ focusLoop: defaultValue(props.focusLoop, syncState?.focusLoop, true),
71
+ });
72
+
73
+ const panels = createCollectionStore<TabStorePanel>();
74
+
75
+ const initialState: TabStoreState = {
76
+ ...composite.getState(),
77
+ selectedId: defaultValue(
78
+ props.selectedId,
79
+ syncState?.selectedId,
80
+ props.defaultSelectedId,
81
+ ),
82
+ selectOnMove: defaultValue(
83
+ props.selectOnMove,
84
+ syncState?.selectOnMove,
85
+ true,
86
+ ),
87
+ };
88
+ const tab = createStore(initialState, composite, store);
89
+
90
+ // Selects the active tab when selectOnMove is true. Since we're listening to
91
+ // the moves state, but not the activeId state, this callback will run only
92
+ // when there's a move, which is usually triggered by moving through the tabs
93
+ // using the keyboard.
94
+ setup(tab, () =>
95
+ sync(tab, ["moves"], () => {
96
+ const { activeId, selectOnMove } = tab.getState();
97
+ if (!selectOnMove) return;
98
+ if (!activeId) return;
99
+ const tabItem = composite.item(activeId);
100
+ if (!tabItem) return;
101
+ if (tabItem.dimmed) return;
102
+ if (tabItem.disabled) return;
103
+ tab.setState("selectedId", tabItem.id);
104
+ }),
105
+ );
106
+
107
+ let syncActiveId = true;
108
+
109
+ // Keep activeId in sync with selectedId.
110
+ setup(tab, () =>
111
+ batch(tab, ["selectedId"], (state, prev) => {
112
+ // There are cases where we don't want to sync activeId with selectedId.
113
+ // For example, restoring the selectedId from a select or combobox
114
+ // selected value. In those cases, we set syncActiveId to false.
115
+ if (!syncActiveId) {
116
+ syncActiveId = true;
117
+ return;
118
+ }
119
+ // If there's a parent composite widget, we don't need to sync the
120
+ // activeId state with the initial selectedId state. The parent composite
121
+ // widget should handle the initial activeId state.
122
+ if (parentComposite && state.selectedId === prev.selectedId) return;
123
+ tab.setState("activeId", state.selectedId);
124
+ }),
125
+ );
126
+
127
+ // Automatically set selectedId if it's undefined.
128
+ setup(tab, () =>
129
+ sync(tab, ["selectedId", "renderedItems"], (state) => {
130
+ if (state.selectedId !== undefined) return;
131
+ // First, we try to set selectedId based on the current active tab.
132
+ const { activeId, renderedItems } = tab.getState();
133
+ const tabItem = composite.item(activeId);
134
+ if (tabItem && !tabItem.disabled && !tabItem.dimmed) {
135
+ tab.setState("selectedId", tabItem.id);
136
+ }
137
+ // If there's no active tab or the active tab is dimmed, we get the
138
+ // first enabled tab instead.
139
+ else {
140
+ const tabItem = renderedItems.find(
141
+ (item) => !item.disabled && !item.dimmed,
142
+ );
143
+ tab.setState("selectedId", tabItem?.id);
144
+ }
145
+ }),
146
+ );
147
+
148
+ // Keep panels tabIds in sync with the current tabs.
149
+ setup(tab, () =>
150
+ sync(tab, ["renderedItems"], (state) => {
151
+ const tabs = state.renderedItems;
152
+ if (!tabs.length) return;
153
+ return sync(panels, ["renderedItems"], (state) => {
154
+ const items = state.renderedItems;
155
+ const hasOrphanPanels = items.some((panel) => !panel.tabId);
156
+ if (!hasOrphanPanels) return;
157
+ items.forEach((panel, i) => {
158
+ if (panel.tabId) return;
159
+ const tabItem = tabs[i];
160
+ if (!tabItem) return;
161
+ panels.renderItem({ ...panel, tabId: tabItem.id });
162
+ });
163
+ });
164
+ }),
165
+ );
166
+
167
+ // Preserve the selected tab when a select or combobox value is selected
168
+ // within the tab panel.
169
+ let selectedIdFromSelectedValue: string | null | undefined = null;
170
+
171
+ setup(tab, () => {
172
+ const backupSelectedId = () => {
173
+ selectedIdFromSelectedValue = tab.getState().selectedId;
174
+ };
175
+ const restoreSelectedId = () => {
176
+ // We set syncActiveId to false to prevent the activeId state from being
177
+ // set to the selectedId state since this is just a restoration of the
178
+ // selectedId state from a select or combobox selected value.
179
+ syncActiveId = false;
180
+ tab.setState("selectedId", selectedIdFromSelectedValue);
181
+ };
182
+ if (parentComposite && "setSelectElement" in parentComposite) {
183
+ return chain(
184
+ sync(parentComposite, ["value"], backupSelectedId),
185
+ sync(parentComposite, ["mounted"], restoreSelectedId),
186
+ );
187
+ }
188
+ if (!combobox) return;
189
+ return chain(
190
+ sync(combobox, ["selectedValue"], backupSelectedId),
191
+ sync(combobox, ["mounted"], restoreSelectedId),
192
+ );
193
+ });
194
+
195
+ return {
196
+ ...composite,
197
+ ...tab,
198
+ panels,
199
+ setSelectedId: (id) => tab.setState("selectedId", id),
200
+ select: (id) => {
201
+ tab.setState("selectedId", id);
202
+ composite.move(id);
203
+ },
204
+ };
205
+ }
206
+
207
+ export interface TabStoreItem extends CompositeStoreItem {
208
+ dimmed?: boolean;
209
+ }
210
+
211
+ export interface TabStorePanel extends CollectionStoreItem {
212
+ tabId?: string | null;
213
+ }
214
+
215
+ export interface TabStoreState extends CompositeStoreState<TabStoreItem> {
216
+ /** @default "horizontal" */
217
+ orientation: CompositeStoreState<TabStoreItem>["orientation"];
218
+ /** @default true */
219
+ focusLoop: CompositeStoreState<TabStoreItem>["focusLoop"];
220
+ /**
221
+ * The id of the tab whose panel is currently visible. If it's `undefined`, it
222
+ * will be automatically set to the first enabled tab.
223
+ *
224
+ * Live examples:
225
+ * - [Tab with React Router](https://ariakit.com/examples/tab-react-router)
226
+ * - [Combobox with Tabs](https://ariakit.com/examples/combobox-tabs)
227
+ * - [Select with Combobox and
228
+ * Tabs](https://ariakit.com/examples/select-combobox-tab)
229
+ * - [Command Menu with
230
+ * Tabs](https://ariakit.com/examples/dialog-combobox-tab-command-menu)
231
+ */
232
+ selectedId: TabStoreState["activeId"];
233
+ /**
234
+ * Determines if the tab should be selected when it receives focus. If set to
235
+ * `false`, the tab will only be selected upon clicking, not when using arrow
236
+ * keys to shift focus.
237
+ *
238
+ * Live examples:
239
+ * - [Tab with React Router](https://ariakit.com/examples/tab-react-router)
240
+ * - [Select with Combobox and
241
+ * Tabs](https://ariakit.com/examples/select-combobox-tab)
242
+ * @default true
243
+ */
244
+ selectOnMove?: boolean;
245
+ }
246
+
247
+ export interface TabStoreFunctions extends CompositeStoreFunctions<TabStoreItem> {
248
+ /**
249
+ * Sets the
250
+ * [`selectedId`](https://ariakit.com/reference/tab-provider#selectedid) state
251
+ * without moving focus. If you want to move focus, use the
252
+ * [`select`](https://ariakit.com/reference/use-tab-store#select) function
253
+ * instead.
254
+ * @example
255
+ * // Selects the tab with id "tab-1"
256
+ * store.setSelectedId("tab-1");
257
+ * // Toggles between "tab-1" and "tab-2"
258
+ * store.setSelectedId((id) => id === "tab-1" ? "tab-2" : "tab-1"));
259
+ * // Selects the first tab
260
+ * store.setSelectedId(store.first());
261
+ * // Selects the next tab
262
+ * store.setSelectedId(store.next());
263
+ */
264
+ setSelectedId: SetState<TabStoreState["selectedId"]>;
265
+ /**
266
+ * A collection store containing the tab panels.
267
+ *
268
+ * Live examples:
269
+ * - [Animated TabPanel](https://ariakit.com/examples/tab-panel-animated)
270
+ */
271
+ panels: CollectionStore<TabStorePanel>;
272
+ /**
273
+ * Selects the tab for the given id and moves focus to it. If you want to set
274
+ * the [`selectedId`](https://ariakit.com/reference/tab-provider#selectedid)
275
+ * state without moving focus, use the
276
+ * [`setSelectedId`](https://ariakit.com/reference/use-tab-store#setselectedid-1)
277
+ * function instead.
278
+ * @example
279
+ * // Selects the tab with id "tab-1"
280
+ * store.select("tab-1");
281
+ * // Selects the first tab
282
+ * store.select(store.first());
283
+ * // Selects the next tab
284
+ * store.select(store.next());
285
+ */
286
+ select: TabStore["move"];
287
+ }
288
+
289
+ export interface TabStoreOptions
290
+ extends
291
+ StoreOptions<
292
+ TabStoreState,
293
+ "orientation" | "focusLoop" | "selectedId" | "selectOnMove"
294
+ >,
295
+ CompositeStoreOptions<TabStoreItem> {
296
+ /**
297
+ * A reference to another [composite
298
+ * store](https://ariakit.com/reference/use-composite-store). This is used when
299
+ * rendering tabs as part of another composite widget such as
300
+ * [Combobox](https://ariakit.com/components/combobox) or
301
+ * [Select](https://ariakit.com/components/select). The stores will share the
302
+ * same state.
303
+ */
304
+ composite?: CompositeStore | SelectStore | null;
305
+ /**
306
+ * A reference to a [combobox
307
+ * store](https://ariakit.com/reference/use-combobox-store). This is used when
308
+ * rendering tabs inside a
309
+ * [Combobox](https://ariakit.com/components/combobox).
310
+ */
311
+ combobox?: ComboboxStore | null;
312
+ /**
313
+ * The id of the tab whose panel is currently visible. If it's `undefined`, it
314
+ * will be automatically set to the first enabled tab.
315
+ *
316
+ * Live examples:
317
+ * - [Combobox with Tabs](https://ariakit.com/examples/combobox-tabs)
318
+ * - [Animated TabPanel](https://ariakit.com/examples/tab-panel-animated)
319
+ * - [Select with Combobox and
320
+ * Tabs](https://ariakit.com/examples/select-combobox-tab)
321
+ * - [Command Menu with
322
+ * Tabs](https://ariakit.com/examples/dialog-combobox-tab-command-menu)
323
+ */
324
+ defaultSelectedId?: TabStoreState["selectedId"];
325
+ }
326
+
327
+ export interface TabStoreProps
328
+ extends TabStoreOptions, StoreProps<TabStoreState> {}
329
+
330
+ export interface TabStore extends TabStoreFunctions, Store<TabStoreState> {}