@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,33 @@
1
+ export declare abstract class Machine<State, Event extends {
2
+ type: number | string;
3
+ }> {
4
+ #private;
5
+ disposables: {
6
+ requestAnimationFrame(callback: (time: number) => void): () => void;
7
+ nextFrame(callback: (time: number) => void): () => void;
8
+ setTimeout(callback: (_: void) => void, delay?: number | undefined): () => void;
9
+ microTask(cb: () => void): () => void;
10
+ group(cb: (d: {
11
+ requestAnimationFrame(callback: (time: number) => void): () => void;
12
+ nextFrame(callback: (time: number) => void): () => void;
13
+ setTimeout(callback: (_: void) => void, delay?: number | undefined): () => void;
14
+ microTask(cb: () => void): () => void;
15
+ group(cb: (d: /*elided*/ any) => void): () => void;
16
+ add(cb: () => void): () => void;
17
+ dispose(): void;
18
+ }) => void): () => void;
19
+ add(cb: () => void): () => void;
20
+ dispose(): void;
21
+ };
22
+ constructor(initialState: State);
23
+ dispose(): void;
24
+ get state(): Readonly<State>;
25
+ abstract reduce(state: Readonly<State>, event: Event): Readonly<State>;
26
+ subscribe<Slice>(selector: (state: Readonly<State>) => Slice, callback: (state: Slice) => void): () => void;
27
+ on<T extends Event['type']>(type: T, callback: (state: State, event: Extract<Event, {
28
+ type: T;
29
+ }>) => void): () => void;
30
+ send(event: Event): void;
31
+ }
32
+ export declare function shallowEqual(a: any, b: any): boolean;
33
+ export declare function batch<F extends (...args: any[]) => void, P extends any[] = Parameters<F>>(setup: () => [callback: F, handle: () => void]): (...args: P) => void;
@@ -0,0 +1,107 @@
1
+ import { DefaultMap } from './utils/default-map';
2
+ import { disposables } from './utils/disposables';
3
+ export class Machine {
4
+ #state = {};
5
+ #eventSubscribers = new DefaultMap(() => new Set());
6
+ #subscribers = new Set();
7
+ disposables = disposables();
8
+ constructor(initialState) {
9
+ this.#state = initialState;
10
+ }
11
+ dispose() {
12
+ this.disposables.dispose();
13
+ }
14
+ get state() {
15
+ return this.#state;
16
+ }
17
+ subscribe(selector, callback) {
18
+ let subscriber = {
19
+ selector,
20
+ callback,
21
+ current: selector(this.#state),
22
+ };
23
+ this.#subscribers.add(subscriber);
24
+ return this.disposables.add(() => {
25
+ this.#subscribers.delete(subscriber);
26
+ });
27
+ }
28
+ on(type, callback) {
29
+ this.#eventSubscribers.get(type).add(callback);
30
+ return this.disposables.add(() => {
31
+ this.#eventSubscribers.get(type).delete(callback);
32
+ });
33
+ }
34
+ send(event) {
35
+ let newState = this.reduce(this.#state, event);
36
+ if (newState === this.#state)
37
+ return; // No change
38
+ this.#state = newState;
39
+ for (let subscriber of this.#subscribers) {
40
+ let slice = subscriber.selector(this.#state);
41
+ if (shallowEqual(subscriber.current, slice))
42
+ continue;
43
+ subscriber.current = slice;
44
+ subscriber.callback(slice);
45
+ }
46
+ for (let callback of this.#eventSubscribers.get(event.type)) {
47
+ callback(this.#state, event);
48
+ }
49
+ }
50
+ }
51
+ export function shallowEqual(a, b) {
52
+ // Exact same reference
53
+ if (Object.is(a, b))
54
+ return true;
55
+ // Must be some type of object
56
+ if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null)
57
+ return false;
58
+ // Arrays
59
+ if (Array.isArray(a) && Array.isArray(b)) {
60
+ if (a.length !== b.length)
61
+ return false;
62
+ return compareEntries(a[Symbol.iterator](), b[Symbol.iterator]());
63
+ }
64
+ // Map and Set. Map entries are [key, value] pairs, so they are compared by their parts.
65
+ if ((a instanceof Map && b instanceof Map) || (a instanceof Set && b instanceof Set)) {
66
+ if (a.size !== b.size)
67
+ return false;
68
+ return compareEntries(a.entries(), b.entries(), pairsAreEqual);
69
+ }
70
+ // Plain objects
71
+ if (isPlainObject(a) && isPlainObject(b)) {
72
+ return compareEntries(Object.entries(a)[Symbol.iterator](), Object.entries(b)[Symbol.iterator](), pairsAreEqual);
73
+ }
74
+ // TODO: Not sure how to compare other types of objects
75
+ return false;
76
+ }
77
+ function pairsAreEqual(a, b) {
78
+ return Object.is(a[0], b[0]) && Object.is(a[1], b[1]);
79
+ }
80
+ function compareEntries(a, b, isEqual = Object.is) {
81
+ let aResult = a.next();
82
+ let bResult = b.next();
83
+ while (!aResult.done && !bResult.done) {
84
+ if (!isEqual(aResult.value, bResult.value))
85
+ return false;
86
+ aResult = a.next();
87
+ bResult = b.next();
88
+ }
89
+ // Both iterators must be exhausted together to be equal
90
+ return aResult.done === bResult.done;
91
+ }
92
+ function isPlainObject(value) {
93
+ if (Object.prototype.toString.call(value) !== '[object Object]') {
94
+ return false;
95
+ }
96
+ let prototype = Object.getPrototypeOf(value);
97
+ return prototype === null || Object.getPrototypeOf(prototype) === null;
98
+ }
99
+ export function batch(setup) {
100
+ let [callback, handle] = setup();
101
+ let d = disposables();
102
+ return (...args) => {
103
+ callback(...args);
104
+ d.dispose();
105
+ d.microTask(handle);
106
+ };
107
+ }
@@ -0,0 +1,32 @@
1
+ import { Machine } from '../machine';
2
+ import { DefaultMap } from '../utils/default-map';
3
+ type Scope = string | null;
4
+ type Id = string;
5
+ interface State {
6
+ stack: Id[];
7
+ }
8
+ export declare enum ActionTypes {
9
+ Push = 0,
10
+ Pop = 1
11
+ }
12
+ export type Actions = {
13
+ type: ActionTypes.Push;
14
+ id: Id;
15
+ } | {
16
+ type: ActionTypes.Pop;
17
+ id: Id;
18
+ };
19
+ declare class StackMachine extends Machine<State, Actions> {
20
+ static new(): StackMachine;
21
+ reduce(state: Readonly<State>, action: Actions): State;
22
+ actions: {
23
+ push: (id: Id) => void;
24
+ pop: (id: Id) => void;
25
+ };
26
+ selectors: {
27
+ isTop: (state: State, id: Id) => boolean;
28
+ inStack: (state: State, id: Id) => boolean;
29
+ };
30
+ }
31
+ export declare const stackMachines: DefaultMap<Scope, StackMachine>;
32
+ export {};
@@ -0,0 +1,51 @@
1
+ import { Machine } from '../machine';
2
+ import { DefaultMap } from '../utils/default-map';
3
+ import { match } from '../utils/match';
4
+ export var ActionTypes;
5
+ (function (ActionTypes) {
6
+ ActionTypes[ActionTypes["Push"] = 0] = "Push";
7
+ ActionTypes[ActionTypes["Pop"] = 1] = "Pop";
8
+ })(ActionTypes || (ActionTypes = {}));
9
+ let reducers = {
10
+ [ActionTypes.Push](state, action) {
11
+ let id = action.id;
12
+ let stack = state.stack;
13
+ let idx = state.stack.indexOf(id);
14
+ // Already in the stack, move it to the top
15
+ if (idx !== -1) {
16
+ let copy = state.stack.slice();
17
+ copy.splice(idx, 1);
18
+ copy.push(id);
19
+ stack = copy;
20
+ return { ...state, stack };
21
+ }
22
+ // Not in the stack, add it to the top
23
+ return { ...state, stack: [...state.stack, id] };
24
+ },
25
+ [ActionTypes.Pop](state, action) {
26
+ let id = action.id;
27
+ let idx = state.stack.indexOf(id);
28
+ if (idx === -1)
29
+ return state; // Not in the stack
30
+ let copy = state.stack.slice();
31
+ copy.splice(idx, 1);
32
+ return { ...state, stack: copy };
33
+ },
34
+ };
35
+ class StackMachine extends Machine {
36
+ static new() {
37
+ return new StackMachine({ stack: [] });
38
+ }
39
+ reduce(state, action) {
40
+ return match(action.type, reducers, state, action);
41
+ }
42
+ actions = {
43
+ push: (id) => this.send({ type: ActionTypes.Push, id }),
44
+ pop: (id) => this.send({ type: ActionTypes.Pop, id }),
45
+ };
46
+ selectors = {
47
+ isTop: (state, id) => state.stack[state.stack.length - 1] === id,
48
+ inStack: (state, id) => state.stack.includes(id),
49
+ };
50
+ }
51
+ export const stackMachines = new DefaultMap(() => StackMachine.new());
@@ -0,0 +1,2 @@
1
+ import { shallowEqual, type Machine } from './machine';
2
+ export declare function useSlice<M extends Machine<any, any>, Slice>(machine: M, selector: (state: Readonly<M extends Machine<infer State, any> ? State : never>) => Slice, compare?: typeof shallowEqual): Slice;
@@ -0,0 +1,9 @@
1
+ import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector';
2
+ import { useEvent } from './hooks/use-event';
3
+ import { shallowEqual } from './machine';
4
+ export function useSlice(machine, selector, compare = shallowEqual) {
5
+ return useSyncExternalStoreWithSelector(useEvent((onStoreChange) => machine.subscribe(identity, onStoreChange)), useEvent(() => machine.state), useEvent(() => machine.state), useEvent(selector), compare);
6
+ }
7
+ function identity(value) {
8
+ return value;
9
+ }
@@ -0,0 +1,25 @@
1
+ export declare enum Focus {
2
+ /** Focus the first non-disabled item. */
3
+ First = 0,
4
+ /** Focus the previous non-disabled item. */
5
+ Previous = 1,
6
+ /** Focus the next non-disabled item. */
7
+ Next = 2,
8
+ /** Focus the last non-disabled item. */
9
+ Last = 3,
10
+ /** Focus a specific item based on the `id` of the item. */
11
+ Specific = 4,
12
+ /** Focus no items at all. */
13
+ Nothing = 5
14
+ }
15
+ export declare function calculateActiveIndex<TItem>(action: {
16
+ focus: Focus.Specific;
17
+ id: string;
18
+ } | {
19
+ focus: Exclude<Focus, Focus.Specific>;
20
+ }, resolvers: {
21
+ resolveItems(): TItem[];
22
+ resolveActiveIndex(): number | null;
23
+ resolveId(item: TItem, index: number, items: TItem[]): string;
24
+ resolveDisabled(item: TItem, index: number, items: TItem[]): boolean;
25
+ }): number | null;
@@ -0,0 +1,74 @@
1
+ function assertNever(x) {
2
+ throw new Error('Unexpected object: ' + x);
3
+ }
4
+ export var Focus;
5
+ (function (Focus) {
6
+ /** Focus the first non-disabled item. */
7
+ Focus[Focus["First"] = 0] = "First";
8
+ /** Focus the previous non-disabled item. */
9
+ Focus[Focus["Previous"] = 1] = "Previous";
10
+ /** Focus the next non-disabled item. */
11
+ Focus[Focus["Next"] = 2] = "Next";
12
+ /** Focus the last non-disabled item. */
13
+ Focus[Focus["Last"] = 3] = "Last";
14
+ /** Focus a specific item based on the `id` of the item. */
15
+ Focus[Focus["Specific"] = 4] = "Specific";
16
+ /** Focus no items at all. */
17
+ Focus[Focus["Nothing"] = 5] = "Nothing";
18
+ })(Focus || (Focus = {}));
19
+ export function calculateActiveIndex(action, resolvers) {
20
+ let items = resolvers.resolveItems();
21
+ if (items.length <= 0)
22
+ return null;
23
+ let currentActiveIndex = resolvers.resolveActiveIndex();
24
+ let activeIndex = currentActiveIndex ?? -1;
25
+ switch (action.focus) {
26
+ case Focus.First: {
27
+ for (let i = 0; i < items.length; ++i) {
28
+ if (!resolvers.resolveDisabled(items[i], i, items)) {
29
+ return i;
30
+ }
31
+ }
32
+ return currentActiveIndex;
33
+ }
34
+ case Focus.Previous: {
35
+ // If nothing is active, focus the last relevant item
36
+ if (activeIndex === -1)
37
+ activeIndex = items.length;
38
+ for (let i = activeIndex - 1; i >= 0; --i) {
39
+ if (!resolvers.resolveDisabled(items[i], i, items)) {
40
+ return i;
41
+ }
42
+ }
43
+ return currentActiveIndex;
44
+ }
45
+ case Focus.Next: {
46
+ for (let i = activeIndex + 1; i < items.length; ++i) {
47
+ if (!resolvers.resolveDisabled(items[i], i, items)) {
48
+ return i;
49
+ }
50
+ }
51
+ return currentActiveIndex;
52
+ }
53
+ case Focus.Last: {
54
+ for (let i = items.length - 1; i >= 0; --i) {
55
+ if (!resolvers.resolveDisabled(items[i], i, items)) {
56
+ return i;
57
+ }
58
+ }
59
+ return currentActiveIndex;
60
+ }
61
+ case Focus.Specific: {
62
+ for (let i = 0; i < items.length; ++i) {
63
+ if (resolvers.resolveId(items[i], i, items) === action.id) {
64
+ return i;
65
+ }
66
+ }
67
+ return currentActiveIndex;
68
+ }
69
+ case Focus.Nothing:
70
+ return null;
71
+ default:
72
+ assertNever(action);
73
+ }
74
+ }
@@ -0,0 +1,5 @@
1
+ export declare class DefaultMap<T = string, V = any> extends Map<T, V> {
2
+ private factory;
3
+ constructor(factory: (key: T) => V);
4
+ get(key: T): V;
5
+ }
@@ -0,0 +1,15 @@
1
+ export class DefaultMap extends Map {
2
+ factory;
3
+ constructor(factory) {
4
+ super();
5
+ this.factory = factory;
6
+ }
7
+ get(key) {
8
+ let value = super.get(key);
9
+ if (value === undefined) {
10
+ value = this.factory(key);
11
+ this.set(key, value);
12
+ }
13
+ return value;
14
+ }
15
+ }
@@ -0,0 +1,31 @@
1
+ export type Disposables = ReturnType<typeof disposables>;
2
+ /**
3
+ * Disposables are a way to manage functions like `setTimeout` and
4
+ * `requestAnimationFrame` that need to be cleaned up when they are no longer
5
+ * needed.
6
+ *
7
+ * When you register a disposable function, it is added to a collection of
8
+ * disposables. Each disposable in the collection provides a `dispose` clean up
9
+ * function that can be called when it's no longer needed. There is also a
10
+ * `dispose` function on the collection itself that can be used to clean up all
11
+ * pending disposables in that collection.
12
+ *
13
+ * Event listeners and DOM styles are not managed here: React Native has no DOM.
14
+ */
15
+ export declare function disposables(): {
16
+ requestAnimationFrame(callback: (time: number) => void): () => void;
17
+ nextFrame(callback: (time: number) => void): () => void;
18
+ setTimeout(callback: (_: void) => void, delay?: number | undefined): () => void;
19
+ microTask(cb: () => void): () => void;
20
+ group(cb: (d: {
21
+ requestAnimationFrame(callback: (time: number) => void): () => void;
22
+ nextFrame(callback: (time: number) => void): () => void;
23
+ setTimeout(callback: (_: void) => void, delay?: number | undefined): () => void;
24
+ microTask(cb: () => void): () => void;
25
+ group(cb: (d: /*elided*/ any) => void): () => void;
26
+ add(cb: () => void): () => void;
27
+ dispose(): void;
28
+ }) => void): () => void;
29
+ add(cb: () => void): () => void;
30
+ dispose(): void;
31
+ };
@@ -0,0 +1,68 @@
1
+ import { microTask } from './micro-task';
2
+ /**
3
+ * Disposables are a way to manage functions like `setTimeout` and
4
+ * `requestAnimationFrame` that need to be cleaned up when they are no longer
5
+ * needed.
6
+ *
7
+ * When you register a disposable function, it is added to a collection of
8
+ * disposables. Each disposable in the collection provides a `dispose` clean up
9
+ * function that can be called when it's no longer needed. There is also a
10
+ * `dispose` function on the collection itself that can be used to clean up all
11
+ * pending disposables in that collection.
12
+ *
13
+ * Event listeners and DOM styles are not managed here: React Native has no DOM.
14
+ */
15
+ export function disposables() {
16
+ let _disposables = [];
17
+ let api = {
18
+ requestAnimationFrame(...args) {
19
+ let raf = requestAnimationFrame(...args);
20
+ return api.add(() => cancelAnimationFrame(raf));
21
+ },
22
+ nextFrame(...args) {
23
+ return api.requestAnimationFrame(() => {
24
+ return api.requestAnimationFrame(...args);
25
+ });
26
+ },
27
+ setTimeout(...args) {
28
+ let timer = setTimeout(...args);
29
+ return api.add(() => clearTimeout(timer));
30
+ },
31
+ microTask(...args) {
32
+ let task = { current: true };
33
+ microTask(() => {
34
+ if (task.current) {
35
+ args[0]();
36
+ }
37
+ });
38
+ return api.add(() => {
39
+ task.current = false;
40
+ });
41
+ },
42
+ group(cb) {
43
+ let d = disposables();
44
+ cb(d);
45
+ return this.add(() => d.dispose());
46
+ },
47
+ add(cb) {
48
+ // Ensure we don't add the same callback twice
49
+ if (!_disposables.includes(cb)) {
50
+ _disposables.push(cb);
51
+ }
52
+ return () => {
53
+ let idx = _disposables.indexOf(cb);
54
+ if (idx >= 0) {
55
+ for (let dispose of _disposables.splice(idx, 1)) {
56
+ dispose();
57
+ }
58
+ }
59
+ };
60
+ },
61
+ dispose() {
62
+ for (let dispose of _disposables.splice(0)) {
63
+ dispose();
64
+ }
65
+ },
66
+ };
67
+ return api;
68
+ }
@@ -0,0 +1,3 @@
1
+ export declare function findActiveIndex<T extends {
2
+ id: string;
3
+ }>(previous: T[], activeIndex: number | null, next: T[]): number | null;
@@ -0,0 +1,12 @@
1
+ // The active option is tracked by its id, not by its index. When the list is rebuilt, for example
2
+ // by a registration, an unregistration or a new position, the active index moves with its item. It
3
+ // is cleared if the item is gone.
4
+ export function findActiveIndex(previous, activeIndex, next) {
5
+ if (activeIndex === null)
6
+ return null;
7
+ let activeItem = previous[activeIndex];
8
+ if (activeItem === undefined)
9
+ return null;
10
+ let idx = next.findIndex((item) => item.id === activeItem.id);
11
+ return idx === -1 ? null : idx;
12
+ }
@@ -0,0 +1 @@
1
+ export declare function match<TValue extends string | number = string, TReturnValue = unknown>(value: TValue, lookup: Record<TValue, TReturnValue | ((...args: any[]) => TReturnValue)>, ...args: any[]): TReturnValue;
@@ -0,0 +1,12 @@
1
+ export function match(value, lookup, ...args) {
2
+ if (value in lookup) {
3
+ let returnValue = lookup[value];
4
+ return typeof returnValue === 'function' ? returnValue(...args) : returnValue;
5
+ }
6
+ let error = new Error(`Tried to handle "${value}" but there is no handler defined. Only defined handlers are: ${Object.keys(lookup)
7
+ .map((key) => `"${key}"`)
8
+ .join(', ')}.`);
9
+ if (Error.captureStackTrace)
10
+ Error.captureStackTrace(error, match);
11
+ throw error;
12
+ }
@@ -0,0 +1 @@
1
+ export declare function microTask(cb: () => void): void;
@@ -0,0 +1,13 @@
1
+ // Polyfill
2
+ export function microTask(cb) {
3
+ if (typeof queueMicrotask === 'function') {
4
+ queueMicrotask(cb);
5
+ }
6
+ else {
7
+ Promise.resolve()
8
+ .then(cb)
9
+ .catch((e) => setTimeout(() => {
10
+ throw e;
11
+ }));
12
+ }
13
+ }
@@ -0,0 +1,3 @@
1
+ import type { ReactNode } from 'react';
2
+ export type RenderChildren<TSlot> = ReactNode | ((slot: TSlot) => ReactNode);
3
+ export declare function resolveChildren<TSlot>(children: RenderChildren<TSlot>, slot: TSlot): ReactNode;
@@ -0,0 +1,4 @@
1
+ // Render-prop children receive the component state, static children are returned as-is.
2
+ export function resolveChildren(children, slot) {
3
+ return typeof children === 'function' ? children(slot) : children;
4
+ }
@@ -0,0 +1,16 @@
1
+ type Orderable = {
2
+ top?: number;
3
+ dataRef: {
4
+ current: {
5
+ order?: number;
6
+ };
7
+ };
8
+ };
9
+ export declare function sortByOrder<T extends Orderable>(items: T[]): T[];
10
+ export declare function placeAtTops<T extends Orderable & {
11
+ id: string;
12
+ }>(items: T[], tops: {
13
+ id: string;
14
+ top: number;
15
+ }[]): T[] | null;
16
+ export {};
@@ -0,0 +1,33 @@
1
+ // Compares two optional numbers. A missing value goes after the ones that are set.
2
+ function compareOptional(a, z) {
3
+ if (a === z)
4
+ return 0;
5
+ if (a === undefined)
6
+ return 1;
7
+ if (z === undefined)
8
+ return -1;
9
+ return a - z;
10
+ }
11
+ // Options sort by their explicit `order` first. Without one, they sort by their position on screen,
12
+ // which is the order a web page gives them in the DOM. `Array.prototype.sort` is stable.
13
+ export function sortByOrder(items) {
14
+ return items
15
+ .slice()
16
+ .sort((a, z) => compareOptional(a.dataRef.current.order, z.dataRef.current.order) ||
17
+ compareOptional(a.top, z.top));
18
+ }
19
+ // Sets the screen positions of several items at once, then sorts once. Sorting is the expensive
20
+ // part on long lists, so a batch of positions costs a single sort. Returns `null` when no position
21
+ // changed, so the caller can skip the update.
22
+ export function placeAtTops(items, tops) {
23
+ let topById = new Map(tops.map(({ id, top }) => [id, top]));
24
+ let changed = false;
25
+ let placed = items.map((item) => {
26
+ let top = topById.get(item.id);
27
+ if (top === undefined || top === item.top)
28
+ return item;
29
+ changed = true;
30
+ return { ...item, top };
31
+ });
32
+ return changed ? sortByOrder(placed) : null;
33
+ }
@@ -0,0 +1 @@
1
+ export declare function findSearchMatchIndex<T>(items: T[], activeIndex: number | null, wasAlreadySearching: boolean, matches: (item: T) => boolean): number;
@@ -0,0 +1,11 @@
1
+ // Finds the next item that matches the search, starting after the active item and wrapping around.
2
+ // Returns its index, or `-1` when nothing matches.
3
+ export function findSearchMatchIndex(items, activeIndex, wasAlreadySearching, matches) {
4
+ // While a search is in progress, the active item can still match, so the search starts on it.
5
+ let offset = wasAlreadySearching ? 0 : 1;
6
+ let reordered = activeIndex !== null
7
+ ? items.slice(activeIndex + offset).concat(items.slice(0, activeIndex + offset))
8
+ : items;
9
+ let match = reordered.find(matches);
10
+ return match ? items.indexOf(match) : -1;
11
+ }
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
2
  "name": "@bareboard/react-native",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "A set of completely unstyled, fully accessible UI components for React Native.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/jbrxl/bareboard.git",
9
+ "directory": "packages/react-native"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "main": "dist/index.js",
15
+ "types": "dist/index.d.ts",
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "dependencies": {
20
+ "use-sync-external-store": "^1.5.0"
21
+ },
22
+ "devDependencies": {
23
+ "@playwright/test": "^1.64.1",
24
+ "@react-native/babel-preset": "0.88.0",
25
+ "@testing-library/react-native": "^13.3.3",
26
+ "@types/react": "~19.3.0",
27
+ "@types/use-sync-external-store": "^1.5.0",
28
+ "@vitejs/plugin-react": "^4.6.0",
29
+ "react": "19.3.0",
30
+ "react-dom": "19.3.0",
31
+ "react-native": "0.88.0",
32
+ "react-native-web": "~0.21.4",
33
+ "react-test-renderer": "19.3.0",
34
+ "vite": "^5.4.20"
35
+ },
36
+ "peerDependencies": {
37
+ "react": "^18 || ^19",
38
+ "react-native": "*"
39
+ },
40
+ "scripts": {
41
+ "build": "tsc -p tsconfig.build.json",
42
+ "lint": "vp lint",
43
+ "test:web": "playwright test -c e2e-web/playwright.config.ts",
44
+ "typecheck": "tsc --noEmit"
45
+ }
6
46
  }