@bareboard/react-native 0.0.0-stage → 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 (127) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +24 -2
  3. package/dist/components/button/button.d.ts +12 -0
  4. package/dist/components/button/button.js +10 -0
  5. package/dist/components/checkbox/checkbox.d.ts +5 -0
  6. package/dist/components/checkbox/checkbox.js +7 -0
  7. package/dist/components/close-button/close-button.d.ts +5 -0
  8. package/dist/components/close-button/close-button.js +14 -0
  9. package/dist/components/combobox/combobox-machine-glue.d.ts +6 -0
  10. package/dist/components/combobox/combobox-machine-glue.js +16 -0
  11. package/dist/components/combobox/combobox-machine.d.ts +143 -0
  12. package/dist/components/combobox/combobox-machine.js +377 -0
  13. package/dist/components/combobox/combobox.d.ts +66 -0
  14. package/dist/components/combobox/combobox.js +199 -0
  15. package/dist/components/data-interactive/data-interactive.d.ts +4 -0
  16. package/dist/components/data-interactive/data-interactive.js +8 -0
  17. package/dist/components/description/description.d.ts +4 -0
  18. package/dist/components/description/description.js +6 -0
  19. package/dist/components/dialog/dialog.d.ts +22 -0
  20. package/dist/components/dialog/dialog.js +27 -0
  21. package/dist/components/disclosure/disclosure.d.ts +35 -0
  22. package/dist/components/disclosure/disclosure.js +35 -0
  23. package/dist/components/field/field.d.ts +6 -0
  24. package/dist/components/field/field.js +9 -0
  25. package/dist/components/fieldset/fieldset.d.ts +3 -0
  26. package/dist/components/fieldset/fieldset.js +3 -0
  27. package/dist/components/focus-trap/focus-trap.d.ts +5 -0
  28. package/dist/components/focus-trap/focus-trap.js +5 -0
  29. package/dist/components/input/input.d.ts +6 -0
  30. package/dist/components/input/input.js +9 -0
  31. package/dist/components/label/label.d.ts +6 -0
  32. package/dist/components/label/label.js +9 -0
  33. package/dist/components/legend/legend.d.ts +6 -0
  34. package/dist/components/legend/legend.js +9 -0
  35. package/dist/components/listbox/listbox-machine-glue.d.ts +6 -0
  36. package/dist/components/listbox/listbox-machine-glue.js +16 -0
  37. package/dist/components/listbox/listbox-machine.d.ts +140 -0
  38. package/dist/components/listbox/listbox-machine.js +347 -0
  39. package/dist/components/listbox/listbox.d.ts +53 -0
  40. package/dist/components/listbox/listbox.js +91 -0
  41. package/dist/components/listbox-label/listbox-label.d.ts +2 -0
  42. package/dist/components/listbox-label/listbox-label.js +2 -0
  43. package/dist/components/listbox-selected-option/listbox-selected-option.d.ts +7 -0
  44. package/dist/components/listbox-selected-option/listbox-selected-option.js +16 -0
  45. package/dist/components/menu/menu-machine-glue.d.ts +6 -0
  46. package/dist/components/menu/menu-machine-glue.js +16 -0
  47. package/dist/components/menu/menu-machine.d.ts +109 -0
  48. package/dist/components/menu/menu-machine.js +238 -0
  49. package/dist/components/menu/menu.d.ts +34 -0
  50. package/dist/components/menu/menu.js +60 -0
  51. package/dist/components/menu-heading/menu-heading.d.ts +4 -0
  52. package/dist/components/menu-heading/menu-heading.js +6 -0
  53. package/dist/components/menu-section/menu-section.d.ts +4 -0
  54. package/dist/components/menu-section/menu-section.js +6 -0
  55. package/dist/components/menu-separator/menu-separator.d.ts +4 -0
  56. package/dist/components/menu-separator/menu-separator.js +6 -0
  57. package/dist/components/popover/popover-machine-glue.d.ts +6 -0
  58. package/dist/components/popover/popover-machine-glue.js +16 -0
  59. package/dist/components/popover/popover-machine.d.ts +30 -0
  60. package/dist/components/popover/popover-machine.js +49 -0
  61. package/dist/components/popover/popover.d.ts +25 -0
  62. package/dist/components/popover/popover.js +37 -0
  63. package/dist/components/popover-backdrop/popover-backdrop.d.ts +6 -0
  64. package/dist/components/popover-backdrop/popover-backdrop.js +14 -0
  65. package/dist/components/popover-group/popover-group.d.ts +4 -0
  66. package/dist/components/popover-group/popover-group.js +6 -0
  67. package/dist/components/popover-overlay/popover-overlay.d.ts +4 -0
  68. package/dist/components/popover-overlay/popover-overlay.js +6 -0
  69. package/dist/components/portal/portal.d.ts +9 -0
  70. package/dist/components/portal/portal.js +8 -0
  71. package/dist/components/radio-group/radio-group.d.ts +29 -0
  72. package/dist/components/radio-group/radio-group.js +36 -0
  73. package/dist/components/select/select.d.ts +7 -0
  74. package/dist/components/select/select.js +10 -0
  75. package/dist/components/switch/switch.d.ts +20 -0
  76. package/dist/components/switch/switch.js +24 -0
  77. package/dist/components/tab-panels/tab-panels.d.ts +4 -0
  78. package/dist/components/tab-panels/tab-panels.js +6 -0
  79. package/dist/components/tabs/tabs.d.ts +29 -0
  80. package/dist/components/tabs/tabs.js +40 -0
  81. package/dist/components/textarea/textarea.d.ts +5 -0
  82. package/dist/components/textarea/textarea.js +7 -0
  83. package/dist/components/transition/transition.d.ts +21 -0
  84. package/dist/components/transition/transition.js +34 -0
  85. package/dist/hooks/use-controllable.d.ts +1 -0
  86. package/dist/hooks/use-controllable.js +34 -0
  87. package/dist/hooks/use-event.d.ts +1 -0
  88. package/dist/hooks/use-event.js +8 -0
  89. package/dist/hooks/use-iso-morphic-effect.d.ts +2 -0
  90. package/dist/hooks/use-iso-morphic-effect.js +3 -0
  91. package/dist/hooks/use-latest-value.d.ts +1 -0
  92. package/dist/hooks/use-latest-value.js +9 -0
  93. package/dist/hooks/use-on-unmount.d.ts +1 -0
  94. package/dist/hooks/use-on-unmount.js +18 -0
  95. package/dist/index.d.ts +33 -0
  96. package/dist/index.js +33 -0
  97. package/dist/internal/close-provider.d.ts +6 -0
  98. package/dist/internal/close-provider.js +9 -0
  99. package/dist/internal/disabled.d.ts +5 -0
  100. package/dist/internal/disabled.js +9 -0
  101. package/dist/internal/toggle.d.ts +17 -0
  102. package/dist/internal/toggle.js +17 -0
  103. package/dist/machine.d.ts +33 -0
  104. package/dist/machine.js +107 -0
  105. package/dist/machines/stack-machine.d.ts +32 -0
  106. package/dist/machines/stack-machine.js +51 -0
  107. package/dist/react-glue.d.ts +2 -0
  108. package/dist/react-glue.js +9 -0
  109. package/dist/utils/calculate-active-index.d.ts +25 -0
  110. package/dist/utils/calculate-active-index.js +74 -0
  111. package/dist/utils/default-map.d.ts +5 -0
  112. package/dist/utils/default-map.js +15 -0
  113. package/dist/utils/disposables.d.ts +31 -0
  114. package/dist/utils/disposables.js +68 -0
  115. package/dist/utils/find-active-index.d.ts +3 -0
  116. package/dist/utils/find-active-index.js +12 -0
  117. package/dist/utils/match.d.ts +1 -0
  118. package/dist/utils/match.js +12 -0
  119. package/dist/utils/micro-task.d.ts +1 -0
  120. package/dist/utils/micro-task.js +13 -0
  121. package/dist/utils/render.d.ts +3 -0
  122. package/dist/utils/render.js +4 -0
  123. package/dist/utils/sort-by-order.d.ts +16 -0
  124. package/dist/utils/sort-by-order.js +33 -0
  125. package/dist/utils/typeahead.d.ts +1 -0
  126. package/dist/utils/typeahead.js +11 -0
  127. package/package.json +43 -3
@@ -0,0 +1,16 @@
1
+ import { createContext, useContext, useMemo } from 'react';
2
+ import { useOnUnmount } from '../../hooks/use-on-unmount';
3
+ import { MenuMachine } from './menu-machine';
4
+ export const MenuContext = createContext(null);
5
+ export function useMenuMachineContext(component) {
6
+ let context = useContext(MenuContext);
7
+ if (context === null) {
8
+ throw new Error(`<${component} /> is missing a parent <Menu /> component.`);
9
+ }
10
+ return context;
11
+ }
12
+ export function useMenuMachine({ id }) {
13
+ let machine = useMemo(() => MenuMachine.new({ id }), []);
14
+ useOnUnmount(() => machine.dispose());
15
+ return machine;
16
+ }
@@ -0,0 +1,109 @@
1
+ import { Machine } from '../../machine';
2
+ import { Focus } from '../../utils/calculate-active-index';
3
+ export declare enum MenuState {
4
+ Open = 0,
5
+ Closed = 1
6
+ }
7
+ export declare enum ActivationTrigger {
8
+ Pointer = 0,
9
+ Other = 1
10
+ }
11
+ export type MenuItemDataRef = {
12
+ current: {
13
+ textValue?: string;
14
+ disabled: boolean;
15
+ order?: number;
16
+ };
17
+ };
18
+ export interface State {
19
+ id: string;
20
+ menuState: MenuState;
21
+ items: {
22
+ id: string;
23
+ top?: number;
24
+ dataRef: MenuItemDataRef;
25
+ }[];
26
+ searchQuery: string;
27
+ activeItemIndex: number | null;
28
+ activationTrigger: ActivationTrigger;
29
+ pendingFocus: {
30
+ focus: Exclude<Focus, Focus.Specific>;
31
+ } | {
32
+ focus: Focus.Specific;
33
+ id: string;
34
+ };
35
+ }
36
+ export declare enum ActionTypes {
37
+ OpenMenu = 0,
38
+ CloseMenu = 1,
39
+ GoToItem = 2,
40
+ Search = 3,
41
+ ClearSearch = 4,
42
+ RegisterItems = 5,
43
+ UnregisterItems = 6,
44
+ SetItemsTop = 7
45
+ }
46
+ export type Actions = {
47
+ type: ActionTypes.CloseMenu;
48
+ } | {
49
+ type: ActionTypes.OpenMenu;
50
+ focus: {
51
+ focus: Exclude<Focus, Focus.Specific>;
52
+ } | {
53
+ focus: Focus.Specific;
54
+ id: string;
55
+ };
56
+ trigger?: ActivationTrigger;
57
+ } | {
58
+ type: ActionTypes.GoToItem;
59
+ focus: Focus.Specific;
60
+ id: string;
61
+ trigger?: ActivationTrigger;
62
+ } | {
63
+ type: ActionTypes.GoToItem;
64
+ focus: Exclude<Focus, Focus.Specific>;
65
+ trigger?: ActivationTrigger;
66
+ } | {
67
+ type: ActionTypes.Search;
68
+ value: string;
69
+ } | {
70
+ type: ActionTypes.ClearSearch;
71
+ } | {
72
+ type: ActionTypes.RegisterItems;
73
+ items: {
74
+ id: string;
75
+ dataRef: MenuItemDataRef;
76
+ }[];
77
+ } | {
78
+ type: ActionTypes.UnregisterItems;
79
+ items: string[];
80
+ } | {
81
+ type: ActionTypes.SetItemsTop;
82
+ tops: {
83
+ id: string;
84
+ top: number;
85
+ }[];
86
+ };
87
+ export declare class MenuMachine extends Machine<State, Actions> {
88
+ static new({ id }: {
89
+ id: string;
90
+ }): MenuMachine;
91
+ constructor(initialState: State);
92
+ reduce(state: Readonly<State>, action: Actions): State;
93
+ actions: {
94
+ setItemTop: (id: string, top: number) => void;
95
+ registerItem: (id: string, dataRef: MenuItemDataRef) => void;
96
+ unregisterItem: (id: string) => void;
97
+ open: (focus: {
98
+ focus: Exclude<Focus, Focus.Specific>;
99
+ } | {
100
+ focus: Focus.Specific;
101
+ id: string;
102
+ }) => void;
103
+ close: () => void;
104
+ };
105
+ selectors: {
106
+ activeDescendantId(state: State): string | undefined;
107
+ isActive(state: State, id: string): boolean;
108
+ };
109
+ }
@@ -0,0 +1,238 @@
1
+ import { Machine, batch } from '../../machine';
2
+ import { ActionTypes as StackActionTypes, stackMachines } from '../../machines/stack-machine';
3
+ import { Focus, calculateActiveIndex } from '../../utils/calculate-active-index';
4
+ import { match } from '../../utils/match';
5
+ import { findActiveIndex } from '../../utils/find-active-index';
6
+ import { placeAtTops, sortByOrder } from '../../utils/sort-by-order';
7
+ import { findSearchMatchIndex } from '../../utils/typeahead';
8
+ export var MenuState;
9
+ (function (MenuState) {
10
+ MenuState[MenuState["Open"] = 0] = "Open";
11
+ MenuState[MenuState["Closed"] = 1] = "Closed";
12
+ })(MenuState || (MenuState = {}));
13
+ export var ActivationTrigger;
14
+ (function (ActivationTrigger) {
15
+ ActivationTrigger[ActivationTrigger["Pointer"] = 0] = "Pointer";
16
+ ActivationTrigger[ActivationTrigger["Other"] = 1] = "Other";
17
+ })(ActivationTrigger || (ActivationTrigger = {}));
18
+ export var ActionTypes;
19
+ (function (ActionTypes) {
20
+ ActionTypes[ActionTypes["OpenMenu"] = 0] = "OpenMenu";
21
+ ActionTypes[ActionTypes["CloseMenu"] = 1] = "CloseMenu";
22
+ ActionTypes[ActionTypes["GoToItem"] = 2] = "GoToItem";
23
+ ActionTypes[ActionTypes["Search"] = 3] = "Search";
24
+ ActionTypes[ActionTypes["ClearSearch"] = 4] = "ClearSearch";
25
+ ActionTypes[ActionTypes["RegisterItems"] = 5] = "RegisterItems";
26
+ ActionTypes[ActionTypes["UnregisterItems"] = 6] = "UnregisterItems";
27
+ ActionTypes[ActionTypes["SetItemsTop"] = 7] = "SetItemsTop";
28
+ })(ActionTypes || (ActionTypes = {}));
29
+ let reducers = {
30
+ [ActionTypes.CloseMenu](state) {
31
+ if (state.menuState === MenuState.Closed)
32
+ return state;
33
+ return {
34
+ ...state,
35
+ activeItemIndex: null,
36
+ pendingFocus: { focus: Focus.Nothing },
37
+ menuState: MenuState.Closed,
38
+ };
39
+ },
40
+ [ActionTypes.OpenMenu](state, action) {
41
+ if (state.menuState === MenuState.Open)
42
+ return state;
43
+ return {
44
+ ...state,
45
+ pendingFocus: action.focus,
46
+ menuState: MenuState.Open,
47
+ };
48
+ },
49
+ [ActionTypes.GoToItem]: (state, action) => {
50
+ if (state.menuState === MenuState.Closed)
51
+ return state;
52
+ let base = {
53
+ ...state,
54
+ searchQuery: '',
55
+ activationTrigger: action.trigger ?? ActivationTrigger.Other,
56
+ };
57
+ // There is no need to look at the items if we know that we don't want to focus anything
58
+ if (action.focus === Focus.Nothing) {
59
+ return {
60
+ ...base,
61
+ activeItemIndex: null,
62
+ };
63
+ }
64
+ // There is no need to look at the items if we know exactly where to go
65
+ if (action.focus === Focus.Specific) {
66
+ return {
67
+ ...base,
68
+ activeItemIndex: state.items.findIndex((o) => o.id === action.id),
69
+ };
70
+ }
71
+ let activeItemIndex = calculateActiveIndex(action, {
72
+ resolveItems: () => state.items,
73
+ resolveActiveIndex: () => state.activeItemIndex,
74
+ resolveId: (item) => item.id,
75
+ resolveDisabled: (item) => item.dataRef.current.disabled,
76
+ });
77
+ return {
78
+ ...base,
79
+ activeItemIndex,
80
+ };
81
+ },
82
+ [ActionTypes.Search]: (state, action) => {
83
+ let wasAlreadySearching = state.searchQuery !== '';
84
+ let searchQuery = state.searchQuery + action.value.toLowerCase();
85
+ let matchIdx = findSearchMatchIndex(state.items, state.activeItemIndex, wasAlreadySearching, (item) => !!item.dataRef.current.textValue?.startsWith(searchQuery) && !item.dataRef.current.disabled);
86
+ if (matchIdx === -1 || matchIdx === state.activeItemIndex)
87
+ return { ...state, searchQuery };
88
+ return {
89
+ ...state,
90
+ searchQuery,
91
+ activeItemIndex: matchIdx,
92
+ activationTrigger: ActivationTrigger.Other,
93
+ };
94
+ },
95
+ [ActionTypes.SetItemsTop]: (state, action) => {
96
+ let items = placeAtTops(state.items, action.tops);
97
+ if (!items)
98
+ return state;
99
+ return {
100
+ ...state,
101
+ items,
102
+ activeItemIndex: findActiveIndex(state.items, state.activeItemIndex, items),
103
+ };
104
+ },
105
+ [ActionTypes.ClearSearch](state) {
106
+ if (state.searchQuery === '')
107
+ return state;
108
+ return { ...state, searchQuery: '' };
109
+ },
110
+ [ActionTypes.RegisterItems]: (state, action) => {
111
+ let items = sortByOrder(state.items.concat(action.items.map((item) => item)));
112
+ // Sorting can move the active item, so look it up again.
113
+ let activeItemIndex = findActiveIndex(state.items, state.activeItemIndex, items);
114
+ if (state.pendingFocus.focus !== Focus.Nothing) {
115
+ activeItemIndex = calculateActiveIndex(state.pendingFocus, {
116
+ resolveItems: () => items,
117
+ resolveActiveIndex: () => state.activeItemIndex,
118
+ resolveId: (item) => item.id,
119
+ resolveDisabled: (item) => item.dataRef.current.disabled,
120
+ });
121
+ }
122
+ return {
123
+ ...state,
124
+ items,
125
+ activeItemIndex,
126
+ pendingFocus: { focus: Focus.Nothing },
127
+ };
128
+ },
129
+ [ActionTypes.UnregisterItems]: (state, action) => {
130
+ let items = state.items;
131
+ let idxs = [];
132
+ let ids = new Set(action.items);
133
+ for (let [idx, item] of items.entries()) {
134
+ if (ids.has(item.id)) {
135
+ idxs.push(idx);
136
+ ids.delete(item.id);
137
+ if (ids.size === 0)
138
+ break;
139
+ }
140
+ }
141
+ if (idxs.length > 0) {
142
+ items = items.slice();
143
+ for (let idx of idxs.reverse()) {
144
+ items.splice(idx, 1);
145
+ }
146
+ }
147
+ return {
148
+ ...state,
149
+ items,
150
+ activeItemIndex: findActiveIndex(state.items, state.activeItemIndex, items),
151
+ activationTrigger: ActivationTrigger.Other,
152
+ };
153
+ },
154
+ };
155
+ export class MenuMachine extends Machine {
156
+ static new({ id }) {
157
+ return new MenuMachine({
158
+ id,
159
+ menuState: MenuState.Closed,
160
+ items: [],
161
+ searchQuery: '',
162
+ activeItemIndex: null,
163
+ activationTrigger: ActivationTrigger.Other,
164
+ pendingFocus: { focus: Focus.Nothing },
165
+ });
166
+ }
167
+ constructor(initialState) {
168
+ super(initialState);
169
+ // When the menu is open, and it's not on the top of the hierarchy, we
170
+ // should close it again.
171
+ {
172
+ let id = this.state.id;
173
+ let stackMachine = stackMachines.get(null);
174
+ this.disposables.add(stackMachine.on(StackActionTypes.Push, (state) => {
175
+ if (!stackMachine.selectors.isTop(state, id) && this.state.menuState === MenuState.Open) {
176
+ this.send({ type: ActionTypes.CloseMenu });
177
+ }
178
+ }));
179
+ this.on(ActionTypes.OpenMenu, () => stackMachine.actions.push(id));
180
+ this.on(ActionTypes.CloseMenu, () => stackMachine.actions.pop(id));
181
+ }
182
+ }
183
+ reduce(state, action) {
184
+ return match(action.type, reducers, state, action);
185
+ }
186
+ actions = {
187
+ // Batched version, so the items that mount together are sorted once
188
+ setItemTop: batch(() => {
189
+ let tops = [];
190
+ return [
191
+ (id, top) => tops.push({ id, top }),
192
+ () => this.send({ type: ActionTypes.SetItemsTop, tops: tops.splice(0) }),
193
+ ];
194
+ }),
195
+ // Batched version to register multiple items at the same time
196
+ registerItem: batch(() => {
197
+ let items = [];
198
+ let seen = new Set();
199
+ return [
200
+ (id, dataRef) => {
201
+ if (seen.has(dataRef))
202
+ return;
203
+ seen.add(dataRef);
204
+ items.push({ id, dataRef });
205
+ },
206
+ () => {
207
+ seen.clear();
208
+ return this.send({ type: ActionTypes.RegisterItems, items: items.splice(0) });
209
+ },
210
+ ];
211
+ }),
212
+ unregisterItem: batch(() => {
213
+ let items = [];
214
+ return [
215
+ (id) => items.push(id),
216
+ () => this.send({ type: ActionTypes.UnregisterItems, items: items.splice(0) }),
217
+ ];
218
+ }),
219
+ open: (focus) => {
220
+ this.send({ type: ActionTypes.OpenMenu, focus });
221
+ },
222
+ close: () => {
223
+ this.send({ type: ActionTypes.CloseMenu });
224
+ },
225
+ };
226
+ selectors = {
227
+ activeDescendantId(state) {
228
+ let activeItemIndex = state.activeItemIndex;
229
+ let items = state.items;
230
+ return activeItemIndex === null ? undefined : items[activeItemIndex]?.id;
231
+ },
232
+ isActive(state, id) {
233
+ let activeItemIndex = state.activeItemIndex;
234
+ let items = state.items;
235
+ return activeItemIndex !== null ? items[activeItemIndex]?.id === id : false;
236
+ },
237
+ };
238
+ }
@@ -0,0 +1,34 @@
1
+ import { type ForwardRefExoticComponent, type RefAttributes } from 'react';
2
+ import { type PressableProps, type ViewProps, type ViewInstance } from 'react-native';
3
+ import { type RenderChildren } from '../../utils/render';
4
+ export type MenuRenderPropArg = {
5
+ open: boolean;
6
+ close: () => void;
7
+ };
8
+ export type MenuProps = {
9
+ children?: RenderChildren<MenuRenderPropArg>;
10
+ };
11
+ export declare function Menu({ children }: MenuProps): import("react").JSX.Element;
12
+ export type MenuButtonRenderPropArg = {
13
+ open: boolean;
14
+ disabled: boolean;
15
+ active: boolean;
16
+ };
17
+ export type MenuButtonProps = Omit<PressableProps, 'children' | 'disabled'> & {
18
+ disabled?: boolean;
19
+ children?: RenderChildren<MenuButtonRenderPropArg>;
20
+ };
21
+ export declare const MenuButton: ForwardRefExoticComponent<MenuButtonProps & RefAttributes<ViewInstance>>;
22
+ export type MenuItemsProps = ViewProps;
23
+ export declare const MenuItems: ForwardRefExoticComponent<MenuItemsProps & RefAttributes<ViewInstance>>;
24
+ export type MenuItemRenderPropArg = {
25
+ disabled: boolean;
26
+ active: boolean;
27
+ };
28
+ export type MenuItemProps = Omit<PressableProps, 'children' | 'disabled'> & {
29
+ disabled?: boolean;
30
+ textValue?: string;
31
+ order?: number;
32
+ children?: RenderChildren<MenuItemRenderPropArg>;
33
+ };
34
+ export declare const MenuItem: ForwardRefExoticComponent<MenuItemProps & RefAttributes<ViewInstance>>;
@@ -0,0 +1,60 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef, useId } from 'react';
3
+ import { Pressable, View, } from 'react-native';
4
+ import { useIsoMorphicEffect } from '../../hooks/use-iso-morphic-effect';
5
+ import { useLatestValue } from '../../hooks/use-latest-value';
6
+ import { useSlice } from '../../react-glue';
7
+ import { Focus } from '../../utils/calculate-active-index';
8
+ import { resolveChildren } from '../../utils/render';
9
+ import { MenuState } from './menu-machine';
10
+ import { MenuContext, useMenuMachine, useMenuMachineContext } from './menu-machine-glue';
11
+ export function Menu({ children }) {
12
+ let id = useId();
13
+ let machine = useMenuMachine({ id });
14
+ let open = useSlice(machine, (state) => state.menuState === MenuState.Open);
15
+ return (_jsx(MenuContext.Provider, { value: machine, children: resolveChildren(children, { open, close: machine.actions.close }) }));
16
+ }
17
+ export const MenuButton = forwardRef(function MenuButtonFn(props, ref) {
18
+ let machine = useMenuMachineContext('MenuButton');
19
+ let open = useSlice(machine, (state) => state.menuState === MenuState.Open);
20
+ let { disabled = false, children, onPress, ...theirProps } = props;
21
+ let handlePress = (event) => {
22
+ onPress?.(event);
23
+ if (open) {
24
+ machine.actions.close();
25
+ }
26
+ else {
27
+ machine.actions.open({ focus: Focus.Nothing });
28
+ }
29
+ };
30
+ return (_jsx(Pressable, { ...theirProps, ref: ref, disabled: disabled, accessibilityRole: "button", accessibilityState: { ...theirProps.accessibilityState, expanded: open, disabled }, onPress: handlePress, children: ({ pressed }) => resolveChildren(children, { open, disabled, active: pressed }) }));
31
+ });
32
+ export const MenuItems = forwardRef(function MenuItemsFn(props, ref) {
33
+ let machine = useMenuMachineContext('MenuItems');
34
+ let open = useSlice(machine, (state) => state.menuState === MenuState.Open);
35
+ if (!open)
36
+ return null;
37
+ return _jsx(View, { ...props, ref: ref, accessibilityRole: "menu" });
38
+ });
39
+ export const MenuItem = forwardRef(function MenuItemFn(props, ref) {
40
+ let machine = useMenuMachineContext('MenuItem');
41
+ let id = useId();
42
+ let { disabled = false, textValue, order, children, onPress, onLayout, ...theirProps } = props;
43
+ let dataRef = useLatestValue({ disabled, textValue, order });
44
+ useIsoMorphicEffect(() => {
45
+ machine.actions.registerItem(id, dataRef);
46
+ return () => machine.actions.unregisterItem(id);
47
+ }, [id, machine]);
48
+ let active = useSlice(machine, (state) => machine.selectors.isActive(state, id));
49
+ let handlePress = (event) => {
50
+ if (disabled)
51
+ return;
52
+ onPress?.(event);
53
+ machine.actions.close();
54
+ };
55
+ let handleLayout = (event) => {
56
+ onLayout?.(event);
57
+ machine.actions.setItemTop(id, event.nativeEvent.layout.y);
58
+ };
59
+ return (_jsx(Pressable, { ...theirProps, ref: ref, disabled: disabled, accessibilityRole: "menuitem", accessibilityState: { ...theirProps.accessibilityState, disabled }, onPress: handlePress, onLayout: handleLayout, children: ({ pressed }) => resolveChildren(children, { disabled, active: active || pressed }) }));
60
+ });
@@ -0,0 +1,4 @@
1
+ import { type ForwardRefExoticComponent, type RefAttributes } from 'react';
2
+ import { type TextProps, type TextInstance } from 'react-native';
3
+ export type MenuHeadingProps = TextProps;
4
+ export declare const MenuHeading: ForwardRefExoticComponent<MenuHeadingProps & RefAttributes<TextInstance>>;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from 'react';
3
+ import { Text } from 'react-native';
4
+ export const MenuHeading = forwardRef(function MenuHeadingFn(props, ref) {
5
+ return _jsx(Text, { ...props, ref: ref, accessibilityRole: "header" });
6
+ });
@@ -0,0 +1,4 @@
1
+ import { type ForwardRefExoticComponent, type RefAttributes } from 'react';
2
+ import { type ViewProps, type ViewInstance } from 'react-native';
3
+ export type MenuSectionProps = ViewProps;
4
+ export declare const MenuSection: ForwardRefExoticComponent<MenuSectionProps & RefAttributes<ViewInstance>>;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from 'react';
3
+ import { View } from 'react-native';
4
+ export const MenuSection = forwardRef(function MenuSectionFn(props, ref) {
5
+ return _jsx(View, { ...props, ref: ref, accessibilityRole: "none" });
6
+ });
@@ -0,0 +1,4 @@
1
+ import { type ForwardRefExoticComponent, type RefAttributes } from 'react';
2
+ import { type ViewProps, type ViewInstance } from 'react-native';
3
+ export type MenuSeparatorProps = ViewProps;
4
+ export declare const MenuSeparator: ForwardRefExoticComponent<MenuSeparatorProps & RefAttributes<ViewInstance>>;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from 'react';
3
+ import { View } from 'react-native';
4
+ export const MenuSeparator = forwardRef(function MenuSeparatorFn(props, ref) {
5
+ return _jsx(View, { ...props, ref: ref, accessibilityElementsHidden: true });
6
+ });
@@ -0,0 +1,6 @@
1
+ import { PopoverMachine } from './popover-machine';
2
+ export declare const PopoverContext: import("react").Context<PopoverMachine | null>;
3
+ export declare function usePopoverMachineContext(component: string): PopoverMachine;
4
+ export declare function usePopoverMachine({ id }: {
5
+ id: string;
6
+ }): PopoverMachine;
@@ -0,0 +1,16 @@
1
+ import { createContext, useContext, useMemo } from 'react';
2
+ import { useOnUnmount } from '../../hooks/use-on-unmount';
3
+ import { PopoverMachine } from './popover-machine';
4
+ export const PopoverContext = createContext(null);
5
+ export function usePopoverMachineContext(component) {
6
+ let context = useContext(PopoverContext);
7
+ if (context === null) {
8
+ throw new Error(`<${component} /> is missing a parent <Popover /> component.`);
9
+ }
10
+ return context;
11
+ }
12
+ export function usePopoverMachine({ id }) {
13
+ let machine = useMemo(() => PopoverMachine.new({ id }), []);
14
+ useOnUnmount(() => machine.dispose());
15
+ return machine;
16
+ }
@@ -0,0 +1,30 @@
1
+ import { Machine } from '../../machine';
2
+ export declare enum PopoverStates {
3
+ Open = 0,
4
+ Closed = 1
5
+ }
6
+ interface State {
7
+ id: string;
8
+ popoverState: PopoverStates;
9
+ }
10
+ export declare enum ActionTypes {
11
+ OpenPopover = 0,
12
+ ClosePopover = 1
13
+ }
14
+ export type Actions = {
15
+ type: ActionTypes.OpenPopover;
16
+ } | {
17
+ type: ActionTypes.ClosePopover;
18
+ };
19
+ export declare class PopoverMachine extends Machine<State, Actions> {
20
+ static new({ id }: {
21
+ id: string;
22
+ }): PopoverMachine;
23
+ constructor(initialState: State);
24
+ reduce(state: Readonly<State>, action: Actions): State;
25
+ actions: {
26
+ close: () => void;
27
+ open: () => void;
28
+ };
29
+ }
30
+ export {};
@@ -0,0 +1,49 @@
1
+ import { Machine } from '../../machine';
2
+ import { stackMachines } from '../../machines/stack-machine';
3
+ import { match } from '../../utils/match';
4
+ export var PopoverStates;
5
+ (function (PopoverStates) {
6
+ PopoverStates[PopoverStates["Open"] = 0] = "Open";
7
+ PopoverStates[PopoverStates["Closed"] = 1] = "Closed";
8
+ })(PopoverStates || (PopoverStates = {}));
9
+ export var ActionTypes;
10
+ (function (ActionTypes) {
11
+ ActionTypes[ActionTypes["OpenPopover"] = 0] = "OpenPopover";
12
+ ActionTypes[ActionTypes["ClosePopover"] = 1] = "ClosePopover";
13
+ })(ActionTypes || (ActionTypes = {}));
14
+ let reducers = {
15
+ [ActionTypes.OpenPopover]: (state) => {
16
+ if (state.popoverState === PopoverStates.Open)
17
+ return state;
18
+ return { ...state, popoverState: PopoverStates.Open };
19
+ },
20
+ [ActionTypes.ClosePopover](state) {
21
+ if (state.popoverState === PopoverStates.Closed)
22
+ return state;
23
+ return { ...state, popoverState: PopoverStates.Closed };
24
+ },
25
+ };
26
+ export class PopoverMachine extends Machine {
27
+ static new({ id }) {
28
+ return new PopoverMachine({
29
+ id,
30
+ popoverState: PopoverStates.Closed,
31
+ });
32
+ }
33
+ constructor(initialState) {
34
+ super(initialState);
35
+ {
36
+ let id = this.state.id;
37
+ let stackMachine = stackMachines.get(null);
38
+ this.on(ActionTypes.OpenPopover, () => stackMachine.actions.push(id));
39
+ this.on(ActionTypes.ClosePopover, () => stackMachine.actions.pop(id));
40
+ }
41
+ }
42
+ reduce(state, action) {
43
+ return match(action.type, reducers, state, action);
44
+ }
45
+ actions = {
46
+ close: () => this.send({ type: ActionTypes.ClosePopover }),
47
+ open: () => this.send({ type: ActionTypes.OpenPopover }),
48
+ };
49
+ }
@@ -0,0 +1,25 @@
1
+ import { type ForwardRefExoticComponent, type RefAttributes } from 'react';
2
+ import { type PressableProps, type ViewProps, type ViewInstance } from 'react-native';
3
+ import { type RenderChildren } from '../../utils/render';
4
+ export type PopoverRenderPropArg = {
5
+ open: boolean;
6
+ close: () => void;
7
+ };
8
+ export type PopoverProps = {
9
+ children?: RenderChildren<PopoverRenderPropArg>;
10
+ };
11
+ export declare function Popover({ children }: PopoverProps): import("react").JSX.Element;
12
+ export type PopoverButtonRenderPropArg = {
13
+ open: boolean;
14
+ disabled: boolean;
15
+ active: boolean;
16
+ };
17
+ export type PopoverButtonProps = Omit<PressableProps, 'children' | 'disabled'> & {
18
+ disabled?: boolean;
19
+ children?: RenderChildren<PopoverButtonRenderPropArg>;
20
+ };
21
+ export declare const PopoverButton: ForwardRefExoticComponent<PopoverButtonProps & RefAttributes<ViewInstance>>;
22
+ export type PopoverPanelProps = ViewProps & {
23
+ unmount?: boolean;
24
+ };
25
+ export declare const PopoverPanel: ForwardRefExoticComponent<PopoverPanelProps & RefAttributes<ViewInstance>>;
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef, useId } from 'react';
3
+ import { Pressable, View, } from 'react-native';
4
+ import { useSlice } from '../../react-glue';
5
+ import { resolveChildren } from '../../utils/render';
6
+ import { PopoverStates } from './popover-machine';
7
+ import { PopoverContext, usePopoverMachine, usePopoverMachineContext } from './popover-machine-glue';
8
+ // The web popover closes on outside press and Escape. React Native has neither, so close it with
9
+ // `PopoverBackdrop` or from the panel content with the `close` render prop.
10
+ export function Popover({ children }) {
11
+ let id = useId();
12
+ let machine = usePopoverMachine({ id });
13
+ let open = useSlice(machine, (state) => state.popoverState === PopoverStates.Open);
14
+ return (_jsx(PopoverContext.Provider, { value: machine, children: resolveChildren(children, { open, close: machine.actions.close }) }));
15
+ }
16
+ export const PopoverButton = forwardRef(function PopoverButtonFn(props, ref) {
17
+ let machine = usePopoverMachineContext('PopoverButton');
18
+ let open = useSlice(machine, (state) => state.popoverState === PopoverStates.Open);
19
+ let { disabled = false, children, onPress, ...theirProps } = props;
20
+ let handlePress = (event) => {
21
+ onPress?.(event);
22
+ if (open) {
23
+ machine.actions.close();
24
+ }
25
+ else {
26
+ machine.actions.open();
27
+ }
28
+ };
29
+ return (_jsx(Pressable, { ...theirProps, ref: ref, disabled: disabled, accessibilityRole: "button", accessibilityState: { ...theirProps.accessibilityState, expanded: open, disabled }, onPress: handlePress, children: ({ pressed }) => resolveChildren(children, { open, disabled, active: pressed }) }));
30
+ });
31
+ export const PopoverPanel = forwardRef(function PopoverPanelFn({ unmount = true, ...theirProps }, ref) {
32
+ let machine = usePopoverMachineContext('PopoverPanel');
33
+ let open = useSlice(machine, (state) => state.popoverState === PopoverStates.Open);
34
+ if (!open && unmount)
35
+ return null;
36
+ return _jsx(View, { ...theirProps, ref: ref, style: [theirProps.style, !open && { display: 'none' }] });
37
+ });