@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,322 @@
1
+ // Ported from react-aria-components (source: .react-spectrum/packages/react-aria-components/src/Disclosure.tsx).
2
+ // octane adaptations: `.tsx` → `.ts`, JSX → `createElement`; NO forwardRef — the forwarded ref
3
+ // is `props.ref` (Disclosure passes it into `useContextProps` explicitly; DisclosureGroup and
4
+ // DisclosurePanel read it directly, DisclosurePanel merging it with the internal panel ref via
5
+ // the binding's `mergeRefs`); the plain-`.ts` components use the S()/subSlot component-slot
6
+ // convention. NOTE: `DOMProps` here is `@react-types/shared`'s (upstream imports it from
7
+ // there in this module), NOT the RAC utils bag. Upstream's `GlobalDOMAttributes` and React's
8
+ // `DOMAttributes` → structural records.
9
+ import type { DOMProps, Key } from '@react-types/shared';
10
+ import { createContext, createElement, useContext, useRef } from 'octane';
11
+
12
+ import { type AriaDisclosureProps, useDisclosure } from '../disclosure/useDisclosure';
13
+ import { useFocusRing } from '../focus/useFocusRing';
14
+ import { S, subSlot } from '../internal';
15
+ import type { LabelAriaProps } from '../label/useLabel';
16
+ import {
17
+ type DisclosureGroupState,
18
+ type DisclosureGroupProps as StatelyDisclosureGroupProps,
19
+ useDisclosureGroupState,
20
+ } from '../stately/disclosure/useDisclosureGroupState';
21
+ import { type DisclosureState, useDisclosureState } from '../stately/disclosure/useDisclosureState';
22
+ import { filterDOMProps } from '../utils/filterDOMProps';
23
+ import { mergeProps } from '../utils/mergeProps';
24
+ import { mergeRefs } from '../utils/mergeRefs';
25
+ import { useId } from '../utils/useId';
26
+ import { ButtonContext } from './Button';
27
+ import {
28
+ type ClassNameOrFunction,
29
+ type ContextValue,
30
+ DEFAULT_SLOT,
31
+ dom,
32
+ Provider,
33
+ type RenderProps,
34
+ type SlotProps,
35
+ useContextProps,
36
+ useRenderProps,
37
+ } from './utils';
38
+
39
+ // octane adaptations: structural bags (upstream's React attribute/handler types).
40
+ type GlobalDOMAttributes = Record<string, any>;
41
+ type DOMAttributes = Record<string, any>;
42
+ type ReactNode = any;
43
+
44
+ export interface DisclosureGroupProps
45
+ extends
46
+ StatelyDisclosureGroupProps,
47
+ RenderProps<DisclosureGroupRenderProps>,
48
+ DOMProps,
49
+ GlobalDOMAttributes {
50
+ /**
51
+ * The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
52
+ * element. A function may be provided to compute the class based on component state.
53
+ *
54
+ * @default 'react-aria-DisclosureGroup'
55
+ */
56
+ className?: ClassNameOrFunction<DisclosureGroupRenderProps>;
57
+ }
58
+
59
+ export interface DisclosureGroupRenderProps {
60
+ /**
61
+ * Whether the disclosure group is disabled.
62
+ *
63
+ * @selector [data-disabled]
64
+ */
65
+ isDisabled: boolean;
66
+ /**
67
+ * State of the disclosure group.
68
+ */
69
+ state: DisclosureGroupState;
70
+ }
71
+
72
+ export const DisclosureGroupStateContext = createContext<DisclosureGroupState | null>(null);
73
+
74
+ /**
75
+ * A DisclosureGroup is a grouping of related disclosures, sometimes called an accordion.
76
+ * It supports both single and multiple expanded items.
77
+ */
78
+ export function DisclosureGroup(props: DisclosureGroupProps): any {
79
+ const slot = S('DisclosureGroup');
80
+ let ref = (props as any).ref;
81
+ let state = useDisclosureGroupState(props, subSlot(slot, 'state'));
82
+
83
+ let renderProps = useRenderProps(
84
+ {
85
+ ...props,
86
+ defaultClassName: 'react-aria-DisclosureGroup',
87
+ values: {
88
+ isDisabled: state.isDisabled,
89
+ state,
90
+ },
91
+ },
92
+ subSlot(slot, 'render'),
93
+ );
94
+
95
+ let domProps = filterDOMProps(props, { global: true });
96
+
97
+ return createElement(dom.div, {
98
+ ...domProps,
99
+ ...renderProps,
100
+ ref,
101
+ 'data-disabled': props.isDisabled || undefined,
102
+ children: createElement(DisclosureGroupStateContext.Provider, {
103
+ value: state,
104
+ children: renderProps.children,
105
+ }),
106
+ });
107
+ }
108
+
109
+ export interface DisclosureProps
110
+ extends
111
+ Omit<AriaDisclosureProps, 'children'>,
112
+ RenderProps<DisclosureRenderProps>,
113
+ SlotProps,
114
+ GlobalDOMAttributes {
115
+ /**
116
+ * The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
117
+ * element. A function may be provided to compute the class based on component state.
118
+ *
119
+ * @default 'react-aria-Disclosure'
120
+ */
121
+ className?: ClassNameOrFunction<DisclosureRenderProps>;
122
+ /**
123
+ * An id for the disclosure when used within a DisclosureGroup, matching the id used in
124
+ * `expandedKeys`.
125
+ */
126
+ id?: Key;
127
+ }
128
+
129
+ export interface DisclosureRenderProps {
130
+ /**
131
+ * Whether the disclosure is expanded.
132
+ *
133
+ * @selector [data-expanded]
134
+ */
135
+ isExpanded: boolean;
136
+ /**
137
+ * Whether the disclosure has keyboard focus.
138
+ *
139
+ * @selector [data-focus-visible-within]
140
+ */
141
+ isFocusVisibleWithin: boolean;
142
+ /**
143
+ * Whether the disclosure is disabled.
144
+ *
145
+ * @selector [data-disabled]
146
+ */
147
+ isDisabled: boolean;
148
+ /**
149
+ * State of the disclosure.
150
+ */
151
+ state: DisclosureState;
152
+ }
153
+
154
+ export const DisclosureContext = createContext<ContextValue<DisclosureProps, HTMLDivElement>>(null);
155
+ export const DisclosureStateContext = createContext<DisclosureState | null>(null);
156
+
157
+ interface InternalDisclosureContextValue {
158
+ panelProps: DOMAttributes;
159
+ panelRef: { current: HTMLDivElement | null };
160
+ }
161
+
162
+ const InternalDisclosureContext = createContext<InternalDisclosureContextValue | null>(null);
163
+
164
+ /**
165
+ * A disclosure is a collapsible section of content. It is composed of a a header with a heading and
166
+ * trigger button, and a panel that contains the content.
167
+ */
168
+ export function Disclosure(props: DisclosureProps): any {
169
+ const slot = S('Disclosure');
170
+ let ref: any;
171
+ [props, ref] = useContextProps(props, props.ref, DisclosureContext, subSlot(slot, 'ctx'));
172
+ let groupState = useContext(DisclosureGroupStateContext)!;
173
+ let { id, ...otherProps } = props;
174
+
175
+ // Generate an id if one wasn't provided.
176
+ // (can't pass id into useId since it can also be a number)
177
+ let defaultId = useId(subSlot(slot, 'defaultId'));
178
+ id ||= defaultId;
179
+
180
+ let isExpanded = groupState ? groupState.expandedKeys.has(id) : props.isExpanded;
181
+ let state = useDisclosureState(
182
+ {
183
+ ...props,
184
+ isExpanded,
185
+ onExpandedChange(isExpanded) {
186
+ if (groupState) {
187
+ groupState.toggleKey(id);
188
+ }
189
+
190
+ props.onExpandedChange?.(isExpanded);
191
+ },
192
+ },
193
+ subSlot(slot, 'state'),
194
+ );
195
+
196
+ let panelRef = useRef<HTMLDivElement | null>(null, subSlot(slot, 'panelRef'));
197
+ let isDisabled = props.isDisabled || groupState?.isDisabled || false;
198
+ let { buttonProps, panelProps } = useDisclosure(
199
+ {
200
+ ...props,
201
+ isExpanded,
202
+ isDisabled,
203
+ },
204
+ state,
205
+ panelRef,
206
+ subSlot(slot, 'disclosure'),
207
+ );
208
+ let { isFocusVisible: isFocusVisibleWithin, focusProps: focusWithinProps } = useFocusRing(
209
+ {
210
+ within: true,
211
+ },
212
+ subSlot(slot, 'focusRing'),
213
+ );
214
+
215
+ let renderProps = useRenderProps(
216
+ {
217
+ ...props,
218
+ id: undefined,
219
+ defaultClassName: 'react-aria-Disclosure',
220
+ values: {
221
+ isExpanded: state.isExpanded,
222
+ isDisabled,
223
+ isFocusVisibleWithin,
224
+ state,
225
+ },
226
+ },
227
+ subSlot(slot, 'render'),
228
+ );
229
+
230
+ let domProps = filterDOMProps(otherProps, { global: true });
231
+
232
+ return createElement(Provider, {
233
+ values: [
234
+ [
235
+ ButtonContext,
236
+ {
237
+ slots: {
238
+ [DEFAULT_SLOT]: {},
239
+ trigger: buttonProps,
240
+ },
241
+ },
242
+ ],
243
+ [InternalDisclosureContext, { panelProps, panelRef }],
244
+ [DisclosureStateContext, state],
245
+ ] as any,
246
+ children: createElement(dom.div, {
247
+ ...mergeProps(domProps, renderProps, focusWithinProps),
248
+ ref,
249
+ 'data-expanded': state.isExpanded || undefined,
250
+ 'data-disabled': isDisabled || undefined,
251
+ 'data-focus-visible-within': isFocusVisibleWithin || undefined,
252
+ children: renderProps.children,
253
+ }),
254
+ });
255
+ }
256
+
257
+ export interface DisclosurePanelRenderProps {
258
+ /**
259
+ * Whether keyboard focus is within the disclosure panel.
260
+ *
261
+ * @selector [data-focus-visible-within]
262
+ */
263
+ isFocusVisibleWithin: boolean;
264
+ }
265
+
266
+ export interface DisclosurePanelProps
267
+ extends RenderProps<DisclosurePanelRenderProps>, DOMProps, LabelAriaProps, GlobalDOMAttributes {
268
+ /**
269
+ * The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
270
+ * element. A function may be provided to compute the class based on component state.
271
+ *
272
+ * @default 'react-aria-DisclosurePanel'
273
+ */
274
+ className?: ClassNameOrFunction<DisclosurePanelRenderProps>;
275
+ /**
276
+ * The accessibility role for the disclosure's panel.
277
+ *
278
+ * @default 'group'
279
+ */
280
+ role?: 'group' | 'region';
281
+ /**
282
+ * The children of the component.
283
+ */
284
+ children: ReactNode;
285
+ }
286
+
287
+ /**
288
+ * A DisclosurePanel provides the content for a disclosure.
289
+ */
290
+ export function DisclosurePanel(props: DisclosurePanelProps): any {
291
+ const slot = S('DisclosurePanel');
292
+ let ref = (props as any).ref;
293
+ let { role = 'group' } = props;
294
+ let { panelProps, panelRef } = useContext(InternalDisclosureContext)!;
295
+ let { isFocusVisible: isFocusVisibleWithin, focusProps: focusWithinProps } = useFocusRing(
296
+ {
297
+ within: true,
298
+ },
299
+ subSlot(slot, 'focusRing'),
300
+ );
301
+ let renderProps = useRenderProps(
302
+ {
303
+ ...props,
304
+ defaultClassName: 'react-aria-DisclosurePanel',
305
+ values: {
306
+ isFocusVisibleWithin,
307
+ },
308
+ },
309
+ subSlot(slot, 'render'),
310
+ );
311
+ let DOMProps = filterDOMProps(props, { global: true, labelable: true });
312
+ return createElement(dom.div, {
313
+ ...mergeProps(DOMProps, renderProps, panelProps, focusWithinProps),
314
+ ref: mergeRefs(ref, panelRef),
315
+ role,
316
+ 'data-focus-visible-within': isFocusVisibleWithin || undefined,
317
+ children: createElement(Provider, {
318
+ values: [[ButtonContext, null]] as any,
319
+ children: props.children,
320
+ }),
321
+ });
322
+ }
@@ -0,0 +1,177 @@
1
+ // Ported from react-aria-components (source: .react-spectrum/packages/react-aria-components/src/DragAndDrop.tsx).
2
+ // PHASE-7: the dnd ENGINE stays unported — the engine-referencing types
3
+ // (DraggableCollectionState, DroppableCollectionState, AriaDropIndicatorProps) come from
4
+ // the structural aliases in './useDragAndDrop'. This module ports the CONTEXT layer the
5
+ // collection components compose: the contexts, DropIndicator, useDndPersistedKeys, and
6
+ // useRenderDropIndicator (all inert until a consumer passes `dragAndDropHooks`, which
7
+ // nothing can construct before Phase 7).
8
+ // octane adaptations: `.tsx` → `.ts`, JSX → `createElement`; NO forwardRef — DropIndicator
9
+ // receives the forwarded ref as `props.ref` and passes it to the context render function;
10
+ // public hooks get the binding's slot threading (splitSlot/subSlot); `ReactNode`/
11
+ // `ForwardedRef` → structural aliases; explicit dep arrays preserved verbatim (including
12
+ // upstream's eslint-annotated `[dropState?.target, ...]` invalidation array).
13
+ import type { ItemDropTarget, Key } from '@react-types/shared';
14
+ import { createElement, useCallback, useContext, useMemo, createContext } from 'octane';
15
+
16
+ import type { ClassNameOrFunction, RenderProps } from './utils';
17
+ import type {
18
+ AriaDropIndicatorProps,
19
+ DragAndDropHooks,
20
+ DraggableCollectionState,
21
+ DroppableCollectionState,
22
+ } from './useDragAndDrop';
23
+ import type { MultipleSelectionManager } from '../stately/selection/types';
24
+ import { S, splitSlot, subSlot } from '../internal';
25
+
26
+ // octane adaptations: structural aliases for the React types upstream drags along.
27
+ type ReactNode = any;
28
+ type ForwardedRef<T> = any;
29
+
30
+ export interface DragAndDropContextValue {
31
+ dragAndDropHooks?: DragAndDropHooks;
32
+ dragState?: DraggableCollectionState;
33
+ dropState?: DroppableCollectionState;
34
+ }
35
+
36
+ export const DragAndDropContext = createContext<DragAndDropContextValue>({});
37
+ export const DropIndicatorContext = createContext<DropIndicatorContextValue | null>(null);
38
+
39
+ export interface DropIndicatorRenderProps {
40
+ /**
41
+ * Whether the drop indicator is currently the active drop target.
42
+ *
43
+ * @selector [data-drop-target]
44
+ */
45
+ isDropTarget: boolean;
46
+ }
47
+
48
+ export interface DropIndicatorProps
49
+ extends Omit<AriaDropIndicatorProps, 'activateButtonRef'>, RenderProps<DropIndicatorRenderProps> {
50
+ /**
51
+ * The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
52
+ * element. A function may be provided to compute the class based on component state.
53
+ *
54
+ * @default 'react-aria-DropIndicator'
55
+ */
56
+ className?: ClassNameOrFunction<DropIndicatorRenderProps>;
57
+ }
58
+ interface DropIndicatorContextValue {
59
+ render: (props: DropIndicatorProps, ref: ForwardedRef<HTMLElement>) => ReactNode;
60
+ }
61
+
62
+ /**
63
+ * A DropIndicator is rendered between items in a collection to indicate where dropped data will be
64
+ * inserted.
65
+ */
66
+ // octane adaptation: no forwardRef — the forwarded ref arrives as `props.ref` and is split
67
+ // off before handing the remaining props to the context render function, preserving
68
+ // upstream's `render(props, ref)` contract.
69
+ export function DropIndicator(
70
+ props: DropIndicatorProps & { ref?: ForwardedRef<HTMLElement> },
71
+ ): any {
72
+ let { render } = useContext(DropIndicatorContext)!;
73
+ let { ref, ...otherProps } = props as any;
74
+ return render(otherProps, ref);
75
+ }
76
+
77
+ type RenderDropIndicatorRetValue = ((target: ItemDropTarget) => ReactNode | undefined) | undefined;
78
+
79
+ export function useRenderDropIndicator(
80
+ dragAndDropHooks?: DragAndDropHooks,
81
+ dropState?: DroppableCollectionState,
82
+ ): RenderDropIndicatorRetValue;
83
+ // Slot-threading form: sibling ported hooks pass their derived sub-slot as the trailing arg.
84
+ export function useRenderDropIndicator(
85
+ dragAndDropHooks: DragAndDropHooks | undefined,
86
+ dropState: DroppableCollectionState | undefined,
87
+ slot: symbol | undefined,
88
+ ): RenderDropIndicatorRetValue;
89
+ export function useRenderDropIndicator(...args: any[]): RenderDropIndicatorRetValue {
90
+ const [user, slotArg] = splitSlot(args);
91
+ const slot = slotArg ?? S('useRenderDropIndicator');
92
+ const dragAndDropHooks = user[0] as DragAndDropHooks | undefined;
93
+ const dropState = user[1] as DroppableCollectionState | undefined;
94
+
95
+ let renderDropIndicator = dragAndDropHooks?.renderDropIndicator;
96
+ let isVirtualDragging = dragAndDropHooks?.isVirtualDragging?.();
97
+ let fn = useCallback(
98
+ (target: ItemDropTarget) => {
99
+ // Only show drop indicators when virtual dragging or this is the current drop target.
100
+ if (isVirtualDragging || dropState?.isDropTarget(target)) {
101
+ return renderDropIndicator
102
+ ? renderDropIndicator(target)
103
+ : createElement(DropIndicator, { target });
104
+ }
105
+ },
106
+ // We invalidate whenever the target changes.
107
+ [dropState?.target, isVirtualDragging, renderDropIndicator],
108
+ subSlot(slot, 'fn'),
109
+ );
110
+ return dragAndDropHooks?.useDropIndicator ? fn : undefined;
111
+ }
112
+
113
+ export function useDndPersistedKeys(
114
+ selectionManager: MultipleSelectionManager,
115
+ dragAndDropHooks?: DragAndDropHooks,
116
+ dropState?: DroppableCollectionState,
117
+ ): Set<Key>;
118
+ // Slot-threading form: sibling ported hooks pass their derived sub-slot as the trailing arg.
119
+ export function useDndPersistedKeys(
120
+ selectionManager: MultipleSelectionManager,
121
+ dragAndDropHooks: DragAndDropHooks | undefined,
122
+ dropState: DroppableCollectionState | undefined,
123
+ slot: symbol | undefined,
124
+ ): Set<Key>;
125
+ export function useDndPersistedKeys(...args: any[]): Set<Key> {
126
+ const [user, slotArg] = splitSlot(args);
127
+ const slot = slotArg ?? S('useDndPersistedKeys');
128
+ const selectionManager = user[0] as MultipleSelectionManager;
129
+ const dragAndDropHooks = user[1] as DragAndDropHooks | undefined;
130
+ const dropState = user[2] as DroppableCollectionState | undefined;
131
+
132
+ // Persist the focused key and the drop target key.
133
+ let focusedKey = selectionManager.focusedKey;
134
+ let dropTargetKey: Key | null | undefined = null;
135
+ if (dragAndDropHooks?.isVirtualDragging?.() && dropState?.target?.type === 'item') {
136
+ dropTargetKey = dropState.target.key;
137
+ if (dropState.target.dropPosition === 'after') {
138
+ // Normalize to the "before" drop position since we only render those to the DOM.
139
+ let nextKey = dropState.collection.getKeyAfter(dropTargetKey);
140
+ let lastDescendantKey: Key | null = null;
141
+ if (nextKey != null) {
142
+ let targetLevel = dropState.collection.getItem(dropTargetKey)?.level ?? 0;
143
+ // Skip over any rows that are descendants of the target ("after" position should be after all children)
144
+ while (nextKey != null) {
145
+ let node = dropState.collection.getItem(nextKey);
146
+ if (!node) {
147
+ break;
148
+ }
149
+ // Skip over non-item nodes (e.g., loaders) since they can't be drop targets.
150
+ if (node.type !== 'item') {
151
+ nextKey = dropState.collection.getKeyAfter(nextKey);
152
+ continue;
153
+ }
154
+
155
+ // Stop once we find an item at the same level or higher
156
+ if ((node.level ?? 0) <= targetLevel) {
157
+ break;
158
+ }
159
+
160
+ lastDescendantKey = nextKey;
161
+ nextKey = dropState.collection.getKeyAfter(nextKey);
162
+ }
163
+ }
164
+
165
+ // If nextKey is null (end of collection), use the last descendant
166
+ dropTargetKey = nextKey ?? lastDescendantKey ?? dropTargetKey;
167
+ }
168
+ }
169
+
170
+ return useMemo(
171
+ () => {
172
+ return new Set([focusedKey, dropTargetKey].filter((k) => k != null));
173
+ },
174
+ [focusedKey, dropTargetKey],
175
+ subSlot(slot, 'keys'),
176
+ );
177
+ }
@@ -0,0 +1,79 @@
1
+ // Ported from react-aria-components (source: .react-spectrum/packages/react-aria-components/src/FieldError.tsx).
2
+ // octane adaptations: `.tsx` → `.ts`, JSX → `createElement`; NO forwardRef — the forwarded ref
3
+ // is `props.ref` and rides the props spread into `FieldErrorInner`; the plain-`.ts` inner
4
+ // component uses the S()/subSlot component-slot convention (the outer component only reads
5
+ // context, which is context-identity keyed).
6
+ import type { DOMProps, ValidationResult } from '@react-types/shared';
7
+ import { createContext, createElement, useContext } from 'octane';
8
+
9
+ import { S, subSlot } from '../internal';
10
+ import { filterDOMProps } from '../utils/filterDOMProps';
11
+ import { Text } from './Text';
12
+ import { type ClassNameOrFunction, type RenderProps, useRenderProps } from './utils';
13
+
14
+ // octane adaptation: structural bag (upstream's `GlobalDOMAttributes` drags React handler types).
15
+ type GlobalDOMAttributes = Record<string, any>;
16
+
17
+ export const FieldErrorContext = createContext<ValidationResult | null>(null);
18
+
19
+ export interface FieldErrorRenderProps extends ValidationResult {}
20
+ export interface FieldErrorProps
21
+ extends RenderProps<FieldErrorRenderProps>, DOMProps, GlobalDOMAttributes {
22
+ /**
23
+ * The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
24
+ * element. A function may be provided to compute the class based on component state.
25
+ *
26
+ * @default 'react-aria-FieldError'
27
+ */
28
+ className?: ClassNameOrFunction<FieldErrorRenderProps>;
29
+ /**
30
+ * The HTML element type to render. Defaults to `'span'`.
31
+ * Set to `'div'` when using block-level children (e.g. `<ul>`) to avoid invalid HTML.
32
+ *
33
+ * @default 'span'
34
+ */
35
+ elementType?: string;
36
+ }
37
+
38
+ /**
39
+ * A FieldError displays validation errors for a form field.
40
+ */
41
+ export function FieldError(props: FieldErrorProps): any {
42
+ let validation = useContext(FieldErrorContext);
43
+ if (!validation?.isInvalid) {
44
+ return null;
45
+ }
46
+
47
+ return createElement(FieldErrorInner, { ...props });
48
+ }
49
+
50
+ function FieldErrorInner(props: FieldErrorProps & { ref?: any }): any {
51
+ const slot = S('FieldErrorInner');
52
+ let validation = useContext(FieldErrorContext)!;
53
+ let { elementType, ref, ...restProps } = props;
54
+ let domProps = filterDOMProps(restProps, { global: true })!;
55
+ let renderProps = useRenderProps(
56
+ {
57
+ ...restProps,
58
+ defaultClassName: 'react-aria-FieldError',
59
+ defaultChildren:
60
+ validation.validationErrors.length === 0
61
+ ? undefined
62
+ : validation.validationErrors.join(' '),
63
+ values: validation,
64
+ },
65
+ subSlot(slot, 'render'),
66
+ );
67
+
68
+ if (renderProps.children == null) {
69
+ return null;
70
+ }
71
+
72
+ return createElement(Text, {
73
+ slot: 'errorMessage',
74
+ elementType,
75
+ ...domProps,
76
+ ...renderProps,
77
+ ref,
78
+ });
79
+ }
@@ -0,0 +1,65 @@
1
+ // Ported from react-aria-components (source: .react-spectrum/packages/react-aria-components/src/Form.tsx).
2
+ // octane adaptations: `.tsx` → `.ts`, JSX → `createElement`; NO forwardRef — the forwarded ref
3
+ // is `props.ref`, passed into `useContextProps` explicitly and re-applied as the merged object
4
+ // ref; the plain-`.ts` component uses the S()/subSlot component-slot convention;
5
+ // FormValidationContext comes from the binding's stately port.
6
+ import type { FormProps as SharedFormProps } from '@react-types/shared';
7
+ import { createContext, createElement } from 'octane';
8
+
9
+ import { FormValidationContext } from '../stately/form/useFormValidationState';
10
+ import { S, subSlot } from '../internal';
11
+ import {
12
+ type ContextValue,
13
+ dom,
14
+ type DOMProps,
15
+ type DOMRenderProps,
16
+ useContextProps,
17
+ } from './utils';
18
+
19
+ // octane adaptation: structural bag (upstream's `GlobalDOMAttributes` drags React handler types).
20
+ type GlobalDOMAttributes = Record<string, any>;
21
+
22
+ export interface FormProps
23
+ extends SharedFormProps, DOMProps, DOMRenderProps<'form', undefined>, GlobalDOMAttributes {
24
+ /**
25
+ * The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
26
+ * element.
27
+ *
28
+ * @default 'react-aria-Form'
29
+ */
30
+ className?: string;
31
+ /**
32
+ * Whether to use native HTML form validation to prevent form submission
33
+ * when a field value is missing or invalid, or mark fields as required
34
+ * or invalid via ARIA.
35
+ *
36
+ * @default 'native'
37
+ */
38
+ validationBehavior?: 'aria' | 'native';
39
+ }
40
+
41
+ export const FormContext = createContext<ContextValue<FormProps, HTMLFormElement>>(null);
42
+
43
+ /**
44
+ * A form is a group of inputs that allows users to submit data to a server,
45
+ * with support for providing field validation errors.
46
+ */
47
+ export function Form(props: FormProps): any {
48
+ const slot = S('Form');
49
+ let ref: any;
50
+ [props, ref] = useContextProps(props, props.ref, FormContext, subSlot(slot, 'ctx'));
51
+ let { validationErrors, validationBehavior = 'native', children, className, ...domProps } = props;
52
+ return createElement(dom.form, {
53
+ noValidate: validationBehavior !== 'native',
54
+ ...domProps,
55
+ ref,
56
+ className: className || 'react-aria-Form',
57
+ children: createElement(FormContext.Provider, {
58
+ value: { ...props, validationBehavior },
59
+ children: createElement(FormValidationContext.Provider, {
60
+ value: validationErrors ?? {},
61
+ children,
62
+ }),
63
+ }),
64
+ });
65
+ }