@octanejs/aria 0.0.3 → 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,117 @@
|
|
|
1
|
+
// Ported from react-aria-components (source: .react-spectrum/packages/react-aria-components/src/OverlayArrow.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; React's
|
|
5
|
+
// HTMLAttributes prop bag → a structural record; `filterDOMProps` / `PlacementAxis` come from
|
|
6
|
+
// the binding's ported react-aria modules.
|
|
7
|
+
import type { DOMProps as SharedDOMProps } from '@react-types/shared';
|
|
8
|
+
import { createContext, createElement } from 'octane';
|
|
9
|
+
|
|
10
|
+
import { S, subSlot } from '../internal';
|
|
11
|
+
import type { PlacementAxis } from '../overlays/useOverlayPosition';
|
|
12
|
+
import { filterDOMProps } from '../utils/filterDOMProps';
|
|
13
|
+
import {
|
|
14
|
+
type ClassNameOrFunction,
|
|
15
|
+
type ContextValue,
|
|
16
|
+
type CSSProperties,
|
|
17
|
+
dom,
|
|
18
|
+
type RenderProps,
|
|
19
|
+
type SlotProps,
|
|
20
|
+
useContextProps,
|
|
21
|
+
useRenderProps,
|
|
22
|
+
} from './utils';
|
|
23
|
+
|
|
24
|
+
// octane adaptation: structural prop bag (upstream extends React's HTMLAttributes).
|
|
25
|
+
type HTMLAttributes = Record<string, any>;
|
|
26
|
+
|
|
27
|
+
// octane adaptation: SlotProps is explicit here — upstream inherits `slot` through React's
|
|
28
|
+
// HTMLAttributes, which the structural record alias cannot surface as a declared property.
|
|
29
|
+
interface OverlayArrowContextValue extends OverlayArrowProps, SlotProps {
|
|
30
|
+
placement: PlacementAxis | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const OverlayArrowContext = createContext<
|
|
34
|
+
ContextValue<OverlayArrowContextValue, HTMLDivElement>
|
|
35
|
+
>({
|
|
36
|
+
placement: 'bottom',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export interface OverlayArrowProps
|
|
40
|
+
extends
|
|
41
|
+
Omit<HTMLAttributes, 'className' | 'style' | 'render' | 'children'>,
|
|
42
|
+
RenderProps<OverlayArrowRenderProps>,
|
|
43
|
+
SharedDOMProps {
|
|
44
|
+
/**
|
|
45
|
+
* The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
|
|
46
|
+
* element. A function may be provided to compute the class based on component state.
|
|
47
|
+
*
|
|
48
|
+
* @default 'react-aria-OverlayArrow'
|
|
49
|
+
*/
|
|
50
|
+
className?: ClassNameOrFunction<OverlayArrowRenderProps>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface OverlayArrowRenderProps {
|
|
54
|
+
/**
|
|
55
|
+
* The placement of the overlay relative to the trigger.
|
|
56
|
+
*
|
|
57
|
+
* @selector [data-placement="left | right | top | bottom"]
|
|
58
|
+
*/
|
|
59
|
+
placement: PlacementAxis | null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* An OverlayArrow renders a custom arrow element relative to an overlay element
|
|
64
|
+
* such as a popover or tooltip such that it aligns with a trigger element.
|
|
65
|
+
*/
|
|
66
|
+
export function OverlayArrow(props: OverlayArrowProps): any {
|
|
67
|
+
const slot = S('OverlayArrow');
|
|
68
|
+
let ref: any;
|
|
69
|
+
// octane adaptation: explicit type args — the context value type has a REQUIRED
|
|
70
|
+
// `placement`, which defeats inference against the ContextValue union.
|
|
71
|
+
[props, ref] = useContextProps<OverlayArrowProps, OverlayArrowContextValue, HTMLDivElement>(
|
|
72
|
+
props,
|
|
73
|
+
props.ref,
|
|
74
|
+
OverlayArrowContext,
|
|
75
|
+
subSlot(slot, 'ctx'),
|
|
76
|
+
);
|
|
77
|
+
let placement = (props as OverlayArrowContextValue).placement;
|
|
78
|
+
let style: CSSProperties = {
|
|
79
|
+
position: 'absolute',
|
|
80
|
+
transform:
|
|
81
|
+
placement === 'top' || placement === 'bottom' ? 'translateX(-50%)' : 'translateY(-50%)',
|
|
82
|
+
};
|
|
83
|
+
if (placement != null) {
|
|
84
|
+
style[placement] = '100%';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let renderProps = useRenderProps(
|
|
88
|
+
{
|
|
89
|
+
...props,
|
|
90
|
+
defaultClassName: 'react-aria-OverlayArrow',
|
|
91
|
+
values: {
|
|
92
|
+
placement,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
subSlot(slot, 'renderProps'),
|
|
96
|
+
);
|
|
97
|
+
// remove undefined values from renderProps.style object so that it can be
|
|
98
|
+
// spread merged with the other style object
|
|
99
|
+
if (renderProps.style) {
|
|
100
|
+
Object.keys(renderProps.style).forEach(
|
|
101
|
+
(key) => renderProps.style![key] === undefined && delete renderProps.style![key],
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let DOMProps = filterDOMProps(props);
|
|
106
|
+
|
|
107
|
+
return createElement(dom.div, {
|
|
108
|
+
...DOMProps,
|
|
109
|
+
...renderProps,
|
|
110
|
+
style: {
|
|
111
|
+
...style,
|
|
112
|
+
...renderProps.style,
|
|
113
|
+
},
|
|
114
|
+
ref,
|
|
115
|
+
'data-placement': placement,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
// Ported from react-aria-components (source: .react-spectrum/packages/react-aria-components/src/Popover.tsx).
|
|
2
|
+
// octane adaptations: `.tsx` → `.ts`, JSX → `createElement` (multi-child elements use the
|
|
3
|
+
// positional-children form); NO forwardRef — the forwarded ref is `props.ref`, passed into
|
|
4
|
+
// `useContextProps` explicitly; the plain-`.ts` components use the S()/subSlot component-slot
|
|
5
|
+
// convention; the hidden-tree branch returns the children value directly (octane components
|
|
6
|
+
// may return any renderable, no Fragment wrapper needed); react-aria private imports come from
|
|
7
|
+
// the binding's ported modules; React's Context/element types → structural aliases.
|
|
8
|
+
import type { AriaLabelingProps, RefObject as SharedRefObject } from '@react-types/shared';
|
|
9
|
+
import {
|
|
10
|
+
type Context,
|
|
11
|
+
createContext,
|
|
12
|
+
createElement,
|
|
13
|
+
useCallback,
|
|
14
|
+
useContext,
|
|
15
|
+
useEffect,
|
|
16
|
+
useMemo,
|
|
17
|
+
useRef,
|
|
18
|
+
useState,
|
|
19
|
+
} from 'octane';
|
|
20
|
+
|
|
21
|
+
import { type AriaPopoverProps, usePopover } from '../overlays/usePopover';
|
|
22
|
+
import { DismissButton } from '../overlays/DismissButton';
|
|
23
|
+
import { focusSafely } from '../interactions/focusSafely';
|
|
24
|
+
import { getInteractionModality } from '../interactions/useFocusVisible';
|
|
25
|
+
import { S, subSlot } from '../internal';
|
|
26
|
+
import { isFocusWithin } from '../utils/shadowdom/DOMFunctions';
|
|
27
|
+
import { filterDOMProps } from '../utils/filterDOMProps';
|
|
28
|
+
import { mergeProps } from '../utils/mergeProps';
|
|
29
|
+
import { Overlay } from '../overlays/Overlay';
|
|
30
|
+
import { OverlayArrowContext } from './OverlayArrow';
|
|
31
|
+
import {
|
|
32
|
+
type OverlayTriggerProps,
|
|
33
|
+
type OverlayTriggerState,
|
|
34
|
+
useOverlayTriggerState,
|
|
35
|
+
} from '../stately/overlays/useOverlayTriggerState';
|
|
36
|
+
import { OverlayTriggerStateContext } from './Dialog';
|
|
37
|
+
import type { PlacementAxis, PositionProps } from '../overlays/useOverlayPosition';
|
|
38
|
+
import { useEnterAnimation, useExitAnimation } from '../utils/animation';
|
|
39
|
+
import { useIsHidden } from '../collections/Hidden';
|
|
40
|
+
import { useLayoutEffect } from '../utils/useLayoutEffect';
|
|
41
|
+
import { useLocale } from '../i18n/I18nProvider';
|
|
42
|
+
import { useResizeObserver } from '../utils/useResizeObserver';
|
|
43
|
+
import {
|
|
44
|
+
type ClassNameOrFunction,
|
|
45
|
+
type ContextValue,
|
|
46
|
+
dom,
|
|
47
|
+
type RenderProps,
|
|
48
|
+
type SlotProps,
|
|
49
|
+
useContextProps,
|
|
50
|
+
useRenderProps,
|
|
51
|
+
} from './utils';
|
|
52
|
+
|
|
53
|
+
// octane adaptation: structural bag (upstream's `GlobalDOMAttributes` drags React handler types).
|
|
54
|
+
type GlobalDOMAttributes = Record<string, any>;
|
|
55
|
+
type RefObject<T> = SharedRefObject<T>;
|
|
56
|
+
|
|
57
|
+
export interface PopoverProps
|
|
58
|
+
extends
|
|
59
|
+
Omit<PositionProps, 'isOpen'>,
|
|
60
|
+
Omit<AriaPopoverProps, 'popoverRef' | 'triggerRef' | 'groupRef' | 'offset' | 'arrowSize'>,
|
|
61
|
+
OverlayTriggerProps,
|
|
62
|
+
RenderProps<PopoverRenderProps>,
|
|
63
|
+
SlotProps,
|
|
64
|
+
AriaLabelingProps,
|
|
65
|
+
GlobalDOMAttributes {
|
|
66
|
+
/**
|
|
67
|
+
* The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
|
|
68
|
+
* element. A function may be provided to compute the class based on component state.
|
|
69
|
+
*
|
|
70
|
+
* @default 'react-aria-Popover'
|
|
71
|
+
*/
|
|
72
|
+
className?: ClassNameOrFunction<PopoverRenderProps>;
|
|
73
|
+
/**
|
|
74
|
+
* The name of the component that triggered the popover. This is reflected on the element
|
|
75
|
+
* as the `data-trigger` attribute, and can be used to provide specific
|
|
76
|
+
* styles for the popover depending on which element triggered it.
|
|
77
|
+
*/
|
|
78
|
+
trigger?: string;
|
|
79
|
+
/**
|
|
80
|
+
* The ref for the element which the popover positions itself with respect to.
|
|
81
|
+
*
|
|
82
|
+
* When used within a trigger component such as DialogTrigger, MenuTrigger, Select, etc.,
|
|
83
|
+
* this is set automatically. It is only required when used standalone.
|
|
84
|
+
*/
|
|
85
|
+
triggerRef?: RefObject<Element | null>;
|
|
86
|
+
/**
|
|
87
|
+
* Whether the popover is currently performing an entry animation.
|
|
88
|
+
*/
|
|
89
|
+
isEntering?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Whether the popover is currently performing an exit animation.
|
|
92
|
+
*/
|
|
93
|
+
isExiting?: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* The container element in which the overlay portal will be placed. This may have unknown
|
|
96
|
+
* behavior depending on where it is portalled to.
|
|
97
|
+
*
|
|
98
|
+
* @deprecated - Use a parent UNSAFE_PortalProvider to set your portal container instead.
|
|
99
|
+
* @default document.body
|
|
100
|
+
*/
|
|
101
|
+
UNSTABLE_portalContainer?: Element;
|
|
102
|
+
/**
|
|
103
|
+
* The additional offset applied along the main axis between the element and its
|
|
104
|
+
* anchor element.
|
|
105
|
+
*
|
|
106
|
+
* @default 8
|
|
107
|
+
*/
|
|
108
|
+
offset?: number;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface PopoverRenderProps {
|
|
112
|
+
/**
|
|
113
|
+
* The name of the component that triggered the popover, e.g. "DialogTrigger" or "ComboBox".
|
|
114
|
+
*
|
|
115
|
+
* @selector [data-trigger="..."]
|
|
116
|
+
*/
|
|
117
|
+
trigger: string | null;
|
|
118
|
+
/**
|
|
119
|
+
* The placement of the popover relative to the trigger.
|
|
120
|
+
*
|
|
121
|
+
* @selector [data-placement="left | right | top | bottom"]
|
|
122
|
+
*/
|
|
123
|
+
placement: PlacementAxis | null;
|
|
124
|
+
/**
|
|
125
|
+
* Whether the popover is currently entering. Use this to apply animations.
|
|
126
|
+
*
|
|
127
|
+
* @selector [data-entering]
|
|
128
|
+
*/
|
|
129
|
+
isEntering: boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Whether the popover is currently exiting. Use this to apply animations.
|
|
132
|
+
*
|
|
133
|
+
* @selector [data-exiting]
|
|
134
|
+
*/
|
|
135
|
+
isExiting: boolean;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
interface PopoverContextValue extends PopoverProps {
|
|
139
|
+
/** Contexts to clear. */
|
|
140
|
+
clearContexts?: Context<any>[];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export const PopoverContext = createContext<ContextValue<PopoverContextValue, HTMLElement>>(null);
|
|
144
|
+
|
|
145
|
+
// Stores a ref for the portal container for a group of popovers (e.g. submenus).
|
|
146
|
+
const PopoverGroupContext = createContext<RefObject<Element | null> | null>(null);
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* A popover is an overlay element positioned relative to a trigger.
|
|
150
|
+
*/
|
|
151
|
+
export function Popover(props: PopoverProps): any {
|
|
152
|
+
const slot = S('Popover');
|
|
153
|
+
let ref: any;
|
|
154
|
+
[props, ref] = useContextProps(props, (props as any).ref, PopoverContext, subSlot(slot, 'ctx'));
|
|
155
|
+
let contextState = useContext(OverlayTriggerStateContext);
|
|
156
|
+
let localState = useOverlayTriggerState(props, subSlot(slot, 'state'));
|
|
157
|
+
let state =
|
|
158
|
+
props.isOpen != null || props.defaultOpen != null || !contextState ? localState : contextState;
|
|
159
|
+
let isExiting =
|
|
160
|
+
useExitAnimation(ref, state.isOpen, subSlot(slot, 'exit')) || props.isExiting || false;
|
|
161
|
+
let isHidden = useIsHidden();
|
|
162
|
+
let { direction } = useLocale(subSlot(slot, 'locale'));
|
|
163
|
+
|
|
164
|
+
// If we are in a hidden tree, we still need to preserve our children.
|
|
165
|
+
if (isHidden) {
|
|
166
|
+
let children = props.children;
|
|
167
|
+
if (typeof children === 'function') {
|
|
168
|
+
children = children({
|
|
169
|
+
trigger: props.trigger || null,
|
|
170
|
+
placement: 'bottom',
|
|
171
|
+
isEntering: false,
|
|
172
|
+
isExiting: false,
|
|
173
|
+
defaultChildren: null,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return children;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (state && !state.isOpen && !isExiting) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return createElement(PopoverInner, {
|
|
185
|
+
...props,
|
|
186
|
+
triggerRef: props.triggerRef!,
|
|
187
|
+
state,
|
|
188
|
+
popoverRef: ref,
|
|
189
|
+
isExiting,
|
|
190
|
+
dir: direction,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
interface PopoverInnerProps extends AriaPopoverProps, RenderProps<PopoverRenderProps>, SlotProps {
|
|
195
|
+
state: OverlayTriggerState;
|
|
196
|
+
isEntering?: boolean;
|
|
197
|
+
isExiting: boolean;
|
|
198
|
+
UNSTABLE_portalContainer?: Element;
|
|
199
|
+
trigger?: string;
|
|
200
|
+
dir?: 'ltr' | 'rtl';
|
|
201
|
+
clearContexts?: Context<any>[];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function PopoverInner(allProps: PopoverInnerProps): any {
|
|
205
|
+
const slot = S('PopoverInner');
|
|
206
|
+
let { state, isExiting, UNSTABLE_portalContainer, clearContexts, ...props } =
|
|
207
|
+
allProps as PopoverInnerProps & GlobalDOMAttributes;
|
|
208
|
+
// Calculate the arrow size internally (and remove props.arrowSize from PopoverProps)
|
|
209
|
+
// Referenced from: packages/@react-spectrum/tooltip/src/TooltipTrigger.tsx
|
|
210
|
+
let arrowRef = useRef<HTMLDivElement | null>(null, subSlot(slot, 'arrowRef'));
|
|
211
|
+
let containerRef = useRef<HTMLDivElement | null>(null, subSlot(slot, 'containerRef'));
|
|
212
|
+
let groupCtx = useContext(PopoverGroupContext);
|
|
213
|
+
let isSubPopover = groupCtx && props.trigger === 'SubmenuTrigger';
|
|
214
|
+
|
|
215
|
+
let { popoverProps, underlayProps, arrowProps, placement, triggerAnchorPoint } = usePopover(
|
|
216
|
+
{
|
|
217
|
+
...props,
|
|
218
|
+
offset: (props as any).offset ?? 8,
|
|
219
|
+
arrowRef,
|
|
220
|
+
// If this is a submenu/subdialog, use the root popover's container
|
|
221
|
+
// to detect outside interaction and add aria-hidden.
|
|
222
|
+
groupRef: isSubPopover ? groupCtx! : containerRef,
|
|
223
|
+
},
|
|
224
|
+
state,
|
|
225
|
+
subSlot(slot, 'popover'),
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
let ref = props.popoverRef as RefObject<HTMLDivElement | null>;
|
|
229
|
+
let isEntering =
|
|
230
|
+
useEnterAnimation(ref, !!placement, subSlot(slot, 'enter')) || props.isEntering || false;
|
|
231
|
+
let renderProps = useRenderProps(
|
|
232
|
+
{
|
|
233
|
+
...props,
|
|
234
|
+
defaultClassName: 'react-aria-Popover',
|
|
235
|
+
values: {
|
|
236
|
+
trigger: props.trigger || null,
|
|
237
|
+
placement,
|
|
238
|
+
isEntering,
|
|
239
|
+
isExiting,
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
subSlot(slot, 'render'),
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
// Automatically render Popover with role=dialog except when isNonModal is true,
|
|
246
|
+
// or a dialog is already nested inside the popover.
|
|
247
|
+
let shouldBeDialog = !props.isNonModal || props.trigger === 'SubmenuTrigger';
|
|
248
|
+
let [isDialog, setDialog] = useState(false, subSlot(slot, 'isDialog'));
|
|
249
|
+
useLayoutEffect(
|
|
250
|
+
() => {
|
|
251
|
+
if (ref.current) {
|
|
252
|
+
setDialog(shouldBeDialog && !ref.current.querySelector('[role=dialog]'));
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
[ref, shouldBeDialog],
|
|
256
|
+
subSlot(slot, 'dialogCheck'),
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
// Focus the popover itself on mount, unless a child element is already focused.
|
|
260
|
+
// Skip this for submenus since hovering a submenutrigger should keep focus on the trigger
|
|
261
|
+
useEffect(
|
|
262
|
+
() => {
|
|
263
|
+
if (
|
|
264
|
+
isDialog &&
|
|
265
|
+
(props.trigger !== 'SubmenuTrigger' || getInteractionModality() !== 'pointer') &&
|
|
266
|
+
ref.current &&
|
|
267
|
+
!isFocusWithin(ref.current)
|
|
268
|
+
) {
|
|
269
|
+
focusSafely(ref.current);
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
[isDialog, ref, props.trigger],
|
|
273
|
+
subSlot(slot, 'autofocus'),
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
let children = useMemo(
|
|
277
|
+
() => {
|
|
278
|
+
let children = renderProps.children;
|
|
279
|
+
if (clearContexts) {
|
|
280
|
+
for (let Context of clearContexts) {
|
|
281
|
+
children = createElement((Context as any).Provider, { value: null, children });
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return children;
|
|
285
|
+
},
|
|
286
|
+
[renderProps.children, clearContexts],
|
|
287
|
+
subSlot(slot, 'children'),
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
let [triggerWidth, setTriggerWidth] = useState<string | null>(
|
|
291
|
+
null,
|
|
292
|
+
subSlot(slot, 'triggerWidth'),
|
|
293
|
+
);
|
|
294
|
+
let onResize = useCallback(
|
|
295
|
+
() => {
|
|
296
|
+
if (props.triggerRef.current) {
|
|
297
|
+
setTriggerWidth(props.triggerRef.current.getBoundingClientRect().width + 'px');
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
[props.triggerRef],
|
|
301
|
+
subSlot(slot, 'onResize'),
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
useLayoutEffect(onResize, [onResize], subSlot(slot, 'resizeEffect'));
|
|
305
|
+
useResizeObserver(
|
|
306
|
+
{
|
|
307
|
+
ref: renderProps.style?.['--trigger-width'] ? undefined : props.triggerRef,
|
|
308
|
+
onResize: onResize,
|
|
309
|
+
},
|
|
310
|
+
subSlot(slot, 'resizeObserver'),
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
let style = {
|
|
314
|
+
...popoverProps.style,
|
|
315
|
+
'--trigger-anchor-point': triggerAnchorPoint
|
|
316
|
+
? `${triggerAnchorPoint.x}px ${triggerAnchorPoint.y}px`
|
|
317
|
+
: undefined,
|
|
318
|
+
...renderProps.style,
|
|
319
|
+
'--trigger-width': renderProps.style?.['--trigger-width'] || triggerWidth,
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
let overlay = createElement(
|
|
323
|
+
dom.div,
|
|
324
|
+
{
|
|
325
|
+
...mergeProps(filterDOMProps(props, { global: true }), popoverProps),
|
|
326
|
+
...renderProps,
|
|
327
|
+
role: isDialog ? 'dialog' : undefined,
|
|
328
|
+
tabIndex: isDialog ? -1 : undefined,
|
|
329
|
+
'aria-label': props['aria-label'],
|
|
330
|
+
'aria-labelledby': props['aria-labelledby'],
|
|
331
|
+
ref,
|
|
332
|
+
slot: props.slot || undefined,
|
|
333
|
+
style,
|
|
334
|
+
dir: props.dir,
|
|
335
|
+
'data-trigger': props.trigger,
|
|
336
|
+
'data-placement': placement,
|
|
337
|
+
'data-entering': isEntering || undefined,
|
|
338
|
+
'data-exiting': isExiting || undefined,
|
|
339
|
+
},
|
|
340
|
+
!props.isNonModal ? createElement(DismissButton, { onDismiss: state.close }) : null,
|
|
341
|
+
createElement(OverlayArrowContext.Provider, {
|
|
342
|
+
value: { ...arrowProps, placement, ref: arrowRef } as any,
|
|
343
|
+
children,
|
|
344
|
+
}),
|
|
345
|
+
createElement(DismissButton, { onDismiss: state.close }),
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
// If this is a root popover, render an extra div to act as the portal container for submenus/subdialogs.
|
|
349
|
+
if (!isSubPopover) {
|
|
350
|
+
return createElement(
|
|
351
|
+
Overlay,
|
|
352
|
+
{
|
|
353
|
+
...props,
|
|
354
|
+
shouldContainFocus: isDialog,
|
|
355
|
+
isExiting,
|
|
356
|
+
portalContainer: UNSTABLE_portalContainer,
|
|
357
|
+
// octane adaptation: children arrive positionally below.
|
|
358
|
+
} as any,
|
|
359
|
+
!props.isNonModal && state.isOpen
|
|
360
|
+
? createElement('div', {
|
|
361
|
+
'data-testid': 'underlay',
|
|
362
|
+
...underlayProps,
|
|
363
|
+
style: { position: 'fixed', inset: 0 },
|
|
364
|
+
})
|
|
365
|
+
: null,
|
|
366
|
+
createElement('div', {
|
|
367
|
+
ref: containerRef,
|
|
368
|
+
style: { display: 'contents' },
|
|
369
|
+
children: createElement(PopoverGroupContext.Provider, {
|
|
370
|
+
value: containerRef,
|
|
371
|
+
children: overlay,
|
|
372
|
+
}),
|
|
373
|
+
}),
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Submenus/subdialogs are mounted into the root popover's container.
|
|
378
|
+
return createElement(Overlay, {
|
|
379
|
+
...props,
|
|
380
|
+
shouldContainFocus: isDialog,
|
|
381
|
+
isExiting,
|
|
382
|
+
portalContainer: UNSTABLE_portalContainer ?? groupCtx?.current ?? undefined,
|
|
383
|
+
children: overlay,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Ported from react-aria-components (source: .react-spectrum/packages/react-aria-components/src/ProgressBar.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; `clamp` comes
|
|
5
|
+
// from the binding's stately utils port.
|
|
6
|
+
import { createContext, createElement } from 'octane';
|
|
7
|
+
|
|
8
|
+
import { S, subSlot } from '../internal';
|
|
9
|
+
import { type AriaProgressBarProps, useProgressBar } from '../progress/useProgressBar';
|
|
10
|
+
import { clamp } from '../stately/utils/number';
|
|
11
|
+
import { filterDOMProps } from '../utils/filterDOMProps';
|
|
12
|
+
import { mergeProps } from '../utils/mergeProps';
|
|
13
|
+
import { LabelContext } from './Label';
|
|
14
|
+
import {
|
|
15
|
+
type ClassNameOrFunction,
|
|
16
|
+
type ContextValue,
|
|
17
|
+
dom,
|
|
18
|
+
type RenderProps,
|
|
19
|
+
type SlotProps,
|
|
20
|
+
useContextProps,
|
|
21
|
+
useRenderProps,
|
|
22
|
+
useSlot,
|
|
23
|
+
} from './utils';
|
|
24
|
+
|
|
25
|
+
// octane adaptation: structural bag (upstream's `GlobalDOMAttributes` drags React handler types).
|
|
26
|
+
type GlobalDOMAttributes = Record<string, any>;
|
|
27
|
+
|
|
28
|
+
export interface ProgressBarProps
|
|
29
|
+
extends
|
|
30
|
+
Omit<AriaProgressBarProps, 'label'>,
|
|
31
|
+
RenderProps<ProgressBarRenderProps>,
|
|
32
|
+
SlotProps,
|
|
33
|
+
GlobalDOMAttributes {
|
|
34
|
+
/**
|
|
35
|
+
* The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
|
|
36
|
+
* element. A function may be provided to compute the class based on component state.
|
|
37
|
+
*
|
|
38
|
+
* @default 'react-aria-ProgressBar'
|
|
39
|
+
*/
|
|
40
|
+
className?: ClassNameOrFunction<ProgressBarRenderProps>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ProgressBarRenderProps {
|
|
44
|
+
/**
|
|
45
|
+
* The value as a percentage between the minimum and maximum.
|
|
46
|
+
*/
|
|
47
|
+
percentage: number | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* A formatted version of the value.
|
|
50
|
+
*
|
|
51
|
+
* @selector [aria-valuetext]
|
|
52
|
+
*/
|
|
53
|
+
valueText: string | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* Whether the progress bar is indeterminate.
|
|
56
|
+
*
|
|
57
|
+
* @selector :not([aria-valuenow])
|
|
58
|
+
*/
|
|
59
|
+
isIndeterminate: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const ProgressBarContext =
|
|
63
|
+
createContext<ContextValue<ProgressBarProps, HTMLDivElement>>(null);
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Progress bars show either determinate or indeterminate progress of an operation
|
|
67
|
+
* over time.
|
|
68
|
+
*/
|
|
69
|
+
export function ProgressBar(props: ProgressBarProps): any {
|
|
70
|
+
const slot = S('ProgressBar');
|
|
71
|
+
let ref: any;
|
|
72
|
+
[props, ref] = useContextProps(props, props.ref, ProgressBarContext, subSlot(slot, 'ctx'));
|
|
73
|
+
let { value = 0, minValue = 0, maxValue = 100, isIndeterminate = false } = props;
|
|
74
|
+
value = clamp(value, minValue, maxValue);
|
|
75
|
+
|
|
76
|
+
let [labelRef, label] = useSlot(
|
|
77
|
+
!props['aria-label'] && !props['aria-labelledby'],
|
|
78
|
+
subSlot(slot, 'labelSlot'),
|
|
79
|
+
);
|
|
80
|
+
let { progressBarProps, labelProps } = useProgressBar(
|
|
81
|
+
{ ...props, label },
|
|
82
|
+
subSlot(slot, 'progressBar'),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Calculate the width of the progress bar as a percentage
|
|
86
|
+
let percentage = isIndeterminate ? undefined : ((value - minValue) / (maxValue - minValue)) * 100;
|
|
87
|
+
|
|
88
|
+
let renderProps = useRenderProps(
|
|
89
|
+
{
|
|
90
|
+
...props,
|
|
91
|
+
defaultClassName: 'react-aria-ProgressBar',
|
|
92
|
+
values: {
|
|
93
|
+
percentage,
|
|
94
|
+
valueText: progressBarProps['aria-valuetext'],
|
|
95
|
+
isIndeterminate,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
subSlot(slot, 'render'),
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
let DOMProps = filterDOMProps(props, { global: true });
|
|
102
|
+
|
|
103
|
+
return createElement(dom.div, {
|
|
104
|
+
...mergeProps(DOMProps, renderProps, progressBarProps),
|
|
105
|
+
ref,
|
|
106
|
+
slot: props.slot || undefined,
|
|
107
|
+
children: createElement(LabelContext.Provider, {
|
|
108
|
+
value: { ...labelProps, ref: labelRef, elementType: 'span' },
|
|
109
|
+
children: renderProps.children,
|
|
110
|
+
}),
|
|
111
|
+
});
|
|
112
|
+
}
|