@octanejs/aria 0.0.4 → 0.0.5

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 (93) hide show
  1. package/package.json +7 -5
  2. package/src/autocomplete/useAutocomplete.ts +656 -0
  3. package/src/collections/BaseCollection.ts +374 -0
  4. package/src/collections/CollectionBuilder.ts +343 -0
  5. package/src/collections/Document.ts +451 -0
  6. package/src/collections/Hidden.ts +84 -0
  7. package/src/collections/useCachedChildren.ts +103 -0
  8. package/src/components/Autocomplete.ts +124 -0
  9. package/src/components/Breadcrumbs.ts +170 -0
  10. package/src/components/Button.ts +209 -0
  11. package/src/components/Checkbox.ts +593 -0
  12. package/src/components/Collection.ts +301 -0
  13. package/src/components/ComboBox.ts +477 -0
  14. package/src/components/Dialog.ts +195 -0
  15. package/src/components/Disclosure.ts +322 -0
  16. package/src/components/DragAndDrop.ts +177 -0
  17. package/src/components/FieldError.ts +79 -0
  18. package/src/components/Form.ts +65 -0
  19. package/src/components/GridList.ts +1099 -0
  20. package/src/components/Group.ts +146 -0
  21. package/src/components/Header.ts +32 -0
  22. package/src/components/Heading.ts +45 -0
  23. package/src/components/Input.ts +139 -0
  24. package/src/components/Keyboard.ts +23 -0
  25. package/src/components/Label.ts +30 -0
  26. package/src/components/Link.ts +137 -0
  27. package/src/components/ListBox.ts +886 -0
  28. package/src/components/Menu.ts +685 -0
  29. package/src/components/Meter.ts +100 -0
  30. package/src/components/Modal.ts +352 -0
  31. package/src/components/NumberField.ts +221 -0
  32. package/src/components/OverlayArrow.ts +117 -0
  33. package/src/components/Popover.ts +385 -0
  34. package/src/components/ProgressBar.ts +112 -0
  35. package/src/components/RadioGroup.ts +542 -0
  36. package/src/components/SearchField.ts +208 -0
  37. package/src/components/Select.ts +505 -0
  38. package/src/components/SelectionIndicator.ts +52 -0
  39. package/src/components/Separator.ts +97 -0
  40. package/src/components/SharedElementTransition.ts +209 -0
  41. package/src/components/Slider.ts +472 -0
  42. package/src/components/Switch.ts +449 -0
  43. package/src/components/Tabs.ts +675 -0
  44. package/src/components/TagGroup.ts +443 -0
  45. package/src/components/Text.ts +27 -0
  46. package/src/components/TextArea.ts +92 -0
  47. package/src/components/TextField.ts +200 -0
  48. package/src/components/ToggleButton.ts +147 -0
  49. package/src/components/ToggleButtonGroup.ts +106 -0
  50. package/src/components/Toolbar.ts +74 -0
  51. package/src/components/Tooltip.ts +249 -0
  52. package/src/components/index.ts +302 -0
  53. package/src/components/useDragAndDrop.ts +164 -0
  54. package/src/components/utils.ts +521 -0
  55. package/src/intl/autocomplete/ar-AE.json +3 -0
  56. package/src/intl/autocomplete/bg-BG.json +3 -0
  57. package/src/intl/autocomplete/cs-CZ.json +3 -0
  58. package/src/intl/autocomplete/da-DK.json +3 -0
  59. package/src/intl/autocomplete/de-DE.json +3 -0
  60. package/src/intl/autocomplete/el-GR.json +3 -0
  61. package/src/intl/autocomplete/en-US.json +3 -0
  62. package/src/intl/autocomplete/es-ES.json +3 -0
  63. package/src/intl/autocomplete/et-EE.json +3 -0
  64. package/src/intl/autocomplete/fi-FI.json +3 -0
  65. package/src/intl/autocomplete/fr-FR.json +3 -0
  66. package/src/intl/autocomplete/he-IL.json +3 -0
  67. package/src/intl/autocomplete/hr-HR.json +3 -0
  68. package/src/intl/autocomplete/hu-HU.json +3 -0
  69. package/src/intl/autocomplete/index.ts +75 -0
  70. package/src/intl/autocomplete/it-IT.json +3 -0
  71. package/src/intl/autocomplete/ja-JP.json +3 -0
  72. package/src/intl/autocomplete/ko-KR.json +3 -0
  73. package/src/intl/autocomplete/lt-LT.json +3 -0
  74. package/src/intl/autocomplete/lv-LV.json +3 -0
  75. package/src/intl/autocomplete/nb-NO.json +3 -0
  76. package/src/intl/autocomplete/nl-NL.json +3 -0
  77. package/src/intl/autocomplete/pl-PL.json +3 -0
  78. package/src/intl/autocomplete/pt-BR.json +3 -0
  79. package/src/intl/autocomplete/pt-PT.json +3 -0
  80. package/src/intl/autocomplete/ro-RO.json +3 -0
  81. package/src/intl/autocomplete/ru-RU.json +3 -0
  82. package/src/intl/autocomplete/sk-SK.json +3 -0
  83. package/src/intl/autocomplete/sl-SI.json +3 -0
  84. package/src/intl/autocomplete/sr-SP.json +3 -0
  85. package/src/intl/autocomplete/sv-SE.json +3 -0
  86. package/src/intl/autocomplete/tr-TR.json +3 -0
  87. package/src/intl/autocomplete/uk-UA.json +3 -0
  88. package/src/intl/autocomplete/zh-CN.json +3 -0
  89. package/src/intl/autocomplete/zh-TW.json +3 -0
  90. package/src/stately/autocomplete/useAutocompleteState.ts +85 -0
  91. package/src/utils/animation.ts +149 -0
  92. package/src/utils/useLoadMoreSentinel.ts +78 -0
  93. package/src/utils/useViewportSize.ts +123 -0
@@ -0,0 +1,343 @@
1
+ // Ported from react-aria (source: .react-spectrum/packages/react-aria/src/collections/CollectionBuilder.tsx).
2
+ // octane adaptations (docs/aria-migration-plan.md §2a — the detached-real-DOM
3
+ // collection host):
4
+ // - The hidden structural copy renders through `Hidden` via octane
5
+ // `createPortal` into the Document's DETACHED real container (upstream:
6
+ // a React portal into the fake Document). `CollectionRoot` therefore does
7
+ // not portal again — its subtree is already inside the hidden portal — it
8
+ // only flips the contexts (doc → null so nested Collections render inline,
9
+ // shallow → the Document so leaf/branch wrappers render placeholders).
10
+ // - `ShallowRenderContext` carries the owning `Document` instead of upstream's
11
+ // boolean: real placeholder elements have no `ownerDocument` back-pointer to
12
+ // the store (upstream's fake elements do), so the ref-registration callback
13
+ // reads the Document from context. `isShallow` ≙ `doc != null`.
14
+ // - Leaf/branch placeholder refs register `(NodeClass, props, rendered,
15
+ // render)` through `Document.setElementProps` — the user-visible JSX is
16
+ // cached on the node (`rendered`/`render`), NOT rendered in the hidden tree,
17
+ // exactly upstream's contract.
18
+ // - React 16/17 useSyncExternalStore shim fallback dropped (octane's native
19
+ // useSyncExternalStore supports getServerSnapshot).
20
+ // - `.tsx` → `.ts` (JSX → createElement), no forwardRef (octane refs are
21
+ // props), S()/subSlot slot conventions; hooks may sit behind conditions
22
+ // (octane hooks are slot-keyed, so upstream's rules-of-hooks escape comments
23
+ // collapse away).
24
+ // - SSR follows upstream's render-phase in-order registration path against the
25
+ // Document's lightweight SSR tree. PHASE-8: SSR/hydration coverage deferred.
26
+ import type { Key, Node } from '@react-types/shared';
27
+ import {
28
+ Fragment,
29
+ createContext,
30
+ createElement,
31
+ useCallback,
32
+ useContext,
33
+ useMemo,
34
+ useState,
35
+ useSyncExternalStore,
36
+ } from 'octane';
37
+ import { BaseCollection, CollectionNode } from './BaseCollection';
38
+ import { Document, ElementNode } from './Document';
39
+ import { CachedChildrenOptions, useCachedChildren } from './useCachedChildren';
40
+ import { FocusableContext } from '../interactions/useFocusable';
41
+ import { Hidden } from './Hidden';
42
+ import { useIsSSR } from '../ssr/SSRProvider';
43
+
44
+ import { S, subSlot } from '../internal';
45
+
46
+ // octane adaptation: carries the owning Document (not a boolean) so placeholder
47
+ // refs can reach the store; a non-null value means "shallow render".
48
+ const ShallowRenderContext = createContext<Document<any, BaseCollection<any>> | null>(null);
49
+ const CollectionDocumentContext = createContext<Document<any, BaseCollection<any>> | null>(null);
50
+
51
+ export interface CollectionBuilderProps<C extends BaseCollection<any>> {
52
+ content: any;
53
+ children: (collection: C) => any;
54
+ createCollection?: () => C;
55
+ }
56
+
57
+ /**
58
+ * Builds a `Collection` from the children provided to the `content` prop, and passes it to the
59
+ * child render prop function.
60
+ */
61
+ export function CollectionBuilder<C extends BaseCollection<any>>(
62
+ props: CollectionBuilderProps<C>,
63
+ ): any {
64
+ const slot = S('CollectionBuilder');
65
+ // If a document was provided above us, we're already in a hidden tree. Just render the content.
66
+ let doc = useContext(CollectionDocumentContext);
67
+ if (doc) {
68
+ return props.content;
69
+ }
70
+
71
+ // Otherwise, render a hidden copy of the children so that we can build the collection
72
+ // before constructing the state. This should always come before the real DOM content so
73
+ // we have built the collection by the time it renders during SSR.
74
+ let { collection, document } = useCollectionDocument<any, C>(
75
+ props.createCollection,
76
+ subSlot(slot, 'doc'),
77
+ );
78
+ return createElement(
79
+ Fragment,
80
+ null,
81
+ createElement(Hidden, {
82
+ target: document.getRootElement(),
83
+ children: createElement(CollectionDocumentContext.Provider, {
84
+ value: document,
85
+ children: props.content,
86
+ }),
87
+ }),
88
+ createElement(CollectionInner, { render: props.children, collection }),
89
+ );
90
+ }
91
+
92
+ function CollectionInner(props: { collection: any; render: (collection: any) => any }): any {
93
+ return props.render(props.collection);
94
+ }
95
+
96
+ interface CollectionDocumentResult<T, C extends BaseCollection<T>> {
97
+ collection: C;
98
+ document: Document<T, C>;
99
+ }
100
+
101
+ function useCollectionDocument<T extends object, C extends BaseCollection<T>>(
102
+ createCollection: (() => C) | undefined,
103
+ slot: symbol | undefined,
104
+ ): CollectionDocumentResult<T, C> {
105
+ // The document instance is mutable, and should never change between renders.
106
+ // useSyncExternalStore is used to subscribe to updates, which vends immutable
107
+ // Collection objects.
108
+ let [document] = useState(
109
+ () => new Document<T, C>(createCollection?.() || (new BaseCollection() as C)),
110
+ subSlot(slot, 'document'),
111
+ );
112
+ let subscribe = useCallback(
113
+ (fn: () => void) => document.subscribe(fn),
114
+ [document],
115
+ subSlot(slot, 'subscribe'),
116
+ );
117
+ let getSnapshot = useCallback(
118
+ () => {
119
+ let collection = document.getCollection();
120
+ if (document.isSSR) {
121
+ // After SSR is complete, reset the document to empty so it is ready for the
122
+ // client to render the portal into. We do this _after_ getting the collection
123
+ // above so that the collection still has content in it from SSR during the
124
+ // current render, before the client render has finished.
125
+ document.resetAfterSSR();
126
+ }
127
+ return collection;
128
+ },
129
+ [document],
130
+ subSlot(slot, 'getSnapshot'),
131
+ );
132
+ let getServerSnapshot = useCallback(
133
+ () => {
134
+ document.isSSR = true;
135
+ return document.getCollection();
136
+ },
137
+ [document],
138
+ subSlot(slot, 'getServerSnapshot'),
139
+ );
140
+ let collection = useSyncExternalStore(
141
+ subscribe,
142
+ getSnapshot,
143
+ getServerSnapshot,
144
+ subSlot(slot, 'collection'),
145
+ );
146
+ return { collection, document };
147
+ }
148
+
149
+ const SSRContext = createContext<Document<any, any> | ElementNode<any> | null>(null);
150
+
151
+ export type CollectionNodeClass<T> = {
152
+ new (key: Key): CollectionNode<T>;
153
+ readonly type: string;
154
+ };
155
+
156
+ function createCollectionNodeClass(type: string): CollectionNodeClass<any> {
157
+ let NodeClass = class extends CollectionNode<any> {
158
+ static readonly type = type;
159
+ };
160
+ return NodeClass;
161
+ }
162
+
163
+ function useSSRCollectionNode<T extends Element>(
164
+ CollectionNodeClass: CollectionNodeClass<T> | string,
165
+ props: object,
166
+ ref: any,
167
+ rendered: any,
168
+ children: any,
169
+ render: ((node: Node<any>) => any) | undefined,
170
+ slot: symbol | undefined,
171
+ ): any {
172
+ // To prevent breaking change, if CollectionNodeClass is a string, create a
173
+ // CollectionNodeClass using the string as the type
174
+ if (typeof CollectionNodeClass === 'string') {
175
+ CollectionNodeClass = createCollectionNodeClass(CollectionNodeClass);
176
+ }
177
+ const NodeClass = CollectionNodeClass as CollectionNodeClass<any>;
178
+
179
+ let doc = useContext(ShallowRenderContext);
180
+ // octane adaptation: the ref receives the REAL placeholder element; it
181
+ // registers into the Document store read from context (upstream's fake
182
+ // element carried `setProps` itself). Prop updates re-fire the ref (new
183
+ // callback identity per render), which is the synchronous dirty signal;
184
+ // structural-only changes are caught by the Document's MutationObserver.
185
+ let itemRef = useCallback(
186
+ (element: Element | null) => {
187
+ if (element !== null) {
188
+ doc!.setElementProps(element, props as any, ref, NodeClass, rendered, render);
189
+ }
190
+ },
191
+ [doc, props, ref, rendered, render, NodeClass],
192
+ subSlot(slot, 'itemRef'),
193
+ );
194
+ let parentNode = useContext(SSRContext);
195
+ if (parentNode) {
196
+ // During SSR, portals are not supported, so the collection children are wrapped
197
+ // in an SSRContext. Since SSR occurs only once, we assume that the elements are
198
+ // rendered in order and never re-render. Therefore we can create nodes in our
199
+ // collection document during render so that they are in the collection by the
200
+ // time we need it to render the real DOM. (Re-render guard kept from upstream.)
201
+ let element = parentNode.ownerDocument.nodesByProps.get(props);
202
+ if (!element) {
203
+ element = parentNode.ownerDocument.createElement(NodeClass.type);
204
+ element.setProps(props as any, ref, NodeClass, rendered, render);
205
+ parentNode.appendChild(element);
206
+ parentNode.ownerDocument.updateCollection();
207
+ parentNode.ownerDocument.nodesByProps.set(props, element);
208
+ }
209
+
210
+ return children ? createElement(SSRContext.Provider, { value: element, children }) : null;
211
+ }
212
+
213
+ // The placeholder tag is the node type (`<item>`/`<section>`-style) — a valid
214
+ // real HTMLUnknownElement in the detached container.
215
+ return createElement(NodeClass.type, { ref: itemRef, children });
216
+ }
217
+
218
+ export function createLeafComponent<T, P extends object, E extends Element>(
219
+ CollectionNodeClass: CollectionNodeClass<any> | string,
220
+ render: (props: P, ref: any, node?: Node<T>) => any,
221
+ ): (props: P & { ref?: any }) => any {
222
+ let Component = ({ node }: { node: Node<any> }) => render(node.props, node.props.ref, node);
223
+ let Result = (props: P & { ref?: any }) => {
224
+ const slot = S('createLeafComponent');
225
+ let ref = (props as any).ref;
226
+ let focusableProps = useContext(FocusableContext);
227
+ let doc = useContext(ShallowRenderContext);
228
+ if (!doc) {
229
+ if (render.length >= 3) {
230
+ throw new Error(render.name + ' cannot be rendered outside a collection.');
231
+ }
232
+ return render(props, ref);
233
+ }
234
+
235
+ return useSSRCollectionNode(
236
+ CollectionNodeClass,
237
+ props,
238
+ ref,
239
+ 'children' in props ? (props as any).children : null,
240
+ null,
241
+ (node) =>
242
+ // Forward FocusableContext to real DOM tree so tooltips work.
243
+ createElement(FocusableContext.Provider, {
244
+ value: focusableProps,
245
+ children: createElement(Component, { node }),
246
+ }),
247
+ subSlot(slot, 'node'),
248
+ );
249
+ };
250
+ return Result;
251
+ }
252
+
253
+ export function createBranchComponent<T, P extends { children?: any }, E extends Element>(
254
+ CollectionNodeClass: CollectionNodeClass<any> | string,
255
+ render: (props: P, ref: any, node: Node<T>) => any,
256
+ useChildren: (props: P, slot?: symbol) => any = useCollectionChildren,
257
+ ): (props: P & { ref?: any }) => any {
258
+ let Component = ({ node }: { node: Node<any> }) => render(node.props, node.props.ref, node);
259
+ let Result = (props: P & { ref?: any }) => {
260
+ const slot = S('createBranchComponent');
261
+ let children = useChildren(props, subSlot(slot, 'children'));
262
+ return (
263
+ useSSRCollectionNode(
264
+ CollectionNodeClass,
265
+ props,
266
+ (props as any).ref,
267
+ null,
268
+ children,
269
+ (node) => createElement(Component, { node }),
270
+ subSlot(slot, 'node'),
271
+ ) ?? null
272
+ );
273
+ };
274
+ return Result;
275
+ }
276
+
277
+ function useCollectionChildren<T>(options: CachedChildrenOptions<T>, slot?: symbol): any {
278
+ return useCachedChildren({ ...options, addIdAndValue: true }, slot);
279
+ }
280
+
281
+ export interface CollectionProps<T> extends CachedChildrenOptions<T> {}
282
+
283
+ const CollectionContext = createContext<CachedChildrenOptions<unknown> | null>(null);
284
+
285
+ /** A Collection renders a list of items, automatically managing caching and keys. */
286
+ export function Collection<T>(props: CollectionProps<T>): any {
287
+ const slot = S('Collection');
288
+ let ctx = useContext(CollectionContext)!;
289
+ let dependencies = (ctx?.dependencies || []).concat(props.dependencies);
290
+ let idScope = props.idScope ?? ctx?.idScope;
291
+ let children = useCollectionChildren(
292
+ {
293
+ ...props,
294
+ idScope,
295
+ dependencies,
296
+ },
297
+ subSlot(slot, 'children'),
298
+ );
299
+
300
+ let doc = useContext(CollectionDocumentContext);
301
+ if (doc) {
302
+ children = createElement(CollectionRoot, { children });
303
+ }
304
+
305
+ // Propagate dependencies and idScope to child collections.
306
+ ctx = useMemo(
307
+ () => ({
308
+ dependencies,
309
+ idScope,
310
+ }),
311
+ [idScope, ...dependencies],
312
+ subSlot(slot, 'ctx'),
313
+ );
314
+
315
+ return createElement(CollectionContext.Provider, { value: ctx, children });
316
+ }
317
+
318
+ function CollectionRoot(props: { children: any }): any {
319
+ const slot = S('CollectionRoot');
320
+ let doc = useContext(CollectionDocumentContext);
321
+ let wrappedChildren = useMemo(
322
+ () =>
323
+ createElement(CollectionDocumentContext.Provider, {
324
+ value: null,
325
+ children: createElement(ShallowRenderContext.Provider, {
326
+ value: doc,
327
+ children: props.children,
328
+ }),
329
+ }),
330
+ [props.children, doc],
331
+ subSlot(slot, 'wrapped'),
332
+ );
333
+ // During SSR, we render the content directly, and append nodes to the document
334
+ // during render. The collection children return null so that nothing is actually
335
+ // rendered into the HTML.
336
+ // octane adaptation: on the client there is NO second portal — this subtree
337
+ // already renders inside `Hidden`'s portal into the Document's detached
338
+ // container, so the placeholders land in the walked tree directly.
339
+ let isSSR = useIsSSR(subSlot(slot, 'ssr'));
340
+ return isSSR
341
+ ? createElement(SSRContext.Provider, { value: doc, children: wrappedChildren })
342
+ : wrappedChildren;
343
+ }