@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.
- package/package.json +7 -5
- package/src/autocomplete/useAutocomplete.ts +656 -0
- package/src/collections/BaseCollection.ts +374 -0
- package/src/collections/CollectionBuilder.ts +343 -0
- package/src/collections/Document.ts +451 -0
- package/src/collections/Hidden.ts +84 -0
- package/src/collections/useCachedChildren.ts +103 -0
- package/src/components/Autocomplete.ts +124 -0
- package/src/components/Breadcrumbs.ts +170 -0
- package/src/components/Button.ts +209 -0
- package/src/components/Checkbox.ts +593 -0
- package/src/components/Collection.ts +301 -0
- package/src/components/ComboBox.ts +477 -0
- package/src/components/Dialog.ts +195 -0
- package/src/components/Disclosure.ts +322 -0
- package/src/components/DragAndDrop.ts +177 -0
- package/src/components/FieldError.ts +79 -0
- package/src/components/Form.ts +65 -0
- package/src/components/GridList.ts +1099 -0
- package/src/components/Group.ts +146 -0
- package/src/components/Header.ts +32 -0
- package/src/components/Heading.ts +45 -0
- package/src/components/Input.ts +139 -0
- package/src/components/Keyboard.ts +23 -0
- package/src/components/Label.ts +30 -0
- package/src/components/Link.ts +137 -0
- package/src/components/ListBox.ts +886 -0
- package/src/components/Menu.ts +685 -0
- package/src/components/Meter.ts +100 -0
- package/src/components/Modal.ts +352 -0
- package/src/components/NumberField.ts +221 -0
- package/src/components/OverlayArrow.ts +117 -0
- package/src/components/Popover.ts +385 -0
- package/src/components/ProgressBar.ts +112 -0
- package/src/components/RadioGroup.ts +542 -0
- package/src/components/SearchField.ts +208 -0
- package/src/components/Select.ts +505 -0
- package/src/components/SelectionIndicator.ts +52 -0
- package/src/components/Separator.ts +97 -0
- package/src/components/SharedElementTransition.ts +209 -0
- package/src/components/Slider.ts +472 -0
- package/src/components/Switch.ts +449 -0
- package/src/components/Tabs.ts +675 -0
- package/src/components/TagGroup.ts +443 -0
- package/src/components/Text.ts +27 -0
- package/src/components/TextArea.ts +92 -0
- package/src/components/TextField.ts +200 -0
- package/src/components/ToggleButton.ts +147 -0
- package/src/components/ToggleButtonGroup.ts +106 -0
- package/src/components/Toolbar.ts +74 -0
- package/src/components/Tooltip.ts +249 -0
- package/src/components/index.ts +302 -0
- package/src/components/useDragAndDrop.ts +164 -0
- package/src/components/utils.ts +521 -0
- package/src/intl/autocomplete/ar-AE.json +3 -0
- package/src/intl/autocomplete/bg-BG.json +3 -0
- package/src/intl/autocomplete/cs-CZ.json +3 -0
- package/src/intl/autocomplete/da-DK.json +3 -0
- package/src/intl/autocomplete/de-DE.json +3 -0
- package/src/intl/autocomplete/el-GR.json +3 -0
- package/src/intl/autocomplete/en-US.json +3 -0
- package/src/intl/autocomplete/es-ES.json +3 -0
- package/src/intl/autocomplete/et-EE.json +3 -0
- package/src/intl/autocomplete/fi-FI.json +3 -0
- package/src/intl/autocomplete/fr-FR.json +3 -0
- package/src/intl/autocomplete/he-IL.json +3 -0
- package/src/intl/autocomplete/hr-HR.json +3 -0
- package/src/intl/autocomplete/hu-HU.json +3 -0
- package/src/intl/autocomplete/index.ts +75 -0
- package/src/intl/autocomplete/it-IT.json +3 -0
- package/src/intl/autocomplete/ja-JP.json +3 -0
- package/src/intl/autocomplete/ko-KR.json +3 -0
- package/src/intl/autocomplete/lt-LT.json +3 -0
- package/src/intl/autocomplete/lv-LV.json +3 -0
- package/src/intl/autocomplete/nb-NO.json +3 -0
- package/src/intl/autocomplete/nl-NL.json +3 -0
- package/src/intl/autocomplete/pl-PL.json +3 -0
- package/src/intl/autocomplete/pt-BR.json +3 -0
- package/src/intl/autocomplete/pt-PT.json +3 -0
- package/src/intl/autocomplete/ro-RO.json +3 -0
- package/src/intl/autocomplete/ru-RU.json +3 -0
- package/src/intl/autocomplete/sk-SK.json +3 -0
- package/src/intl/autocomplete/sl-SI.json +3 -0
- package/src/intl/autocomplete/sr-SP.json +3 -0
- package/src/intl/autocomplete/sv-SE.json +3 -0
- package/src/intl/autocomplete/tr-TR.json +3 -0
- package/src/intl/autocomplete/uk-UA.json +3 -0
- package/src/intl/autocomplete/zh-CN.json +3 -0
- package/src/intl/autocomplete/zh-TW.json +3 -0
- package/src/stately/autocomplete/useAutocompleteState.ts +85 -0
- package/src/utils/animation.ts +149 -0
- package/src/utils/useLoadMoreSentinel.ts +78 -0
- 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
|
+
}
|