@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,675 @@
1
+ // Ported from react-aria-components (source: .react-spectrum/packages/react-aria-components/src/Tabs.tsx).
2
+ // octane adaptations: `.tsx` → `.ts`, JSX → `createElement`; NO forwardRef — the forwarded ref
3
+ // is `props.ref` (Tabs passes it into `useContextProps` explicitly; TabList/TabPanels/TabPanel
4
+ // adapt theirs with `useObjectRef` exactly like upstream's forwarded refs); the plain-`.ts`
5
+ // components use the S()/subSlot component-slot convention. The collection composes the
6
+ // Phase-4 engine: `CollectionBuilder`/`createLeafComponent` from `../collections/
7
+ // CollectionBuilder`, `createHideableComponent` from `../collections/Hidden`, and the
8
+ // renderer's `CollectionRoot` via `CollectionRendererContext`. Upstream's RAC-local
9
+ // `CollectionProps` import is our `ItemCollectionProps` (see ./Collection.ts). Upstream's
10
+ // `inertValue` (React <19 string compat) collapses to the plain boolean — octane follows
11
+ // React 19 `inert` semantics. Explicit dep arrays are preserved verbatim.
12
+ import type {
13
+ AriaLabelingProps,
14
+ FocusEvents,
15
+ HoverEvents,
16
+ Collection as ICollection,
17
+ Key,
18
+ LinkDOMProps,
19
+ Node,
20
+ Orientation,
21
+ PressEvents,
22
+ } from '@react-types/shared';
23
+ import { createContext, createElement, useContext, useMemo, useRef, useState } from 'octane';
24
+
25
+ import { CollectionNode } from '../collections/BaseCollection';
26
+ import { CollectionBuilder, createLeafComponent } from '../collections/CollectionBuilder';
27
+ import { createHideableComponent } from '../collections/Hidden';
28
+ import { useFocusRing } from '../focus/useFocusRing';
29
+ import { useHover } from '../interactions/useHover';
30
+ import { S, subSlot } from '../internal';
31
+ import { type TabListState, useTabListState } from '../stately/tabs/useTabListState';
32
+ import { useTab } from '../tabs/useTab';
33
+ import { type AriaTabListProps, useTabList } from '../tabs/useTabList';
34
+ import { type AriaTabPanelProps, useTabPanel } from '../tabs/useTabPanel';
35
+ import { useEnterAnimation, useExitAnimation } from '../utils/animation';
36
+ import { filterDOMProps } from '../utils/filterDOMProps';
37
+ import { mergeProps } from '../utils/mergeProps';
38
+ import { useLayoutEffect } from '../utils/useLayoutEffect';
39
+ import { useObjectRef } from '../utils/useObjectRef';
40
+ import {
41
+ Collection,
42
+ CollectionRendererContext,
43
+ DefaultCollectionRenderer,
44
+ type ItemCollectionProps,
45
+ usePersistedKeys,
46
+ } from './Collection';
47
+ import { SelectionIndicatorContext } from './SelectionIndicator';
48
+ import { SharedElementTransition } from './SharedElementTransition';
49
+ import {
50
+ type ClassNameOrFunction,
51
+ type ContextValue,
52
+ dom,
53
+ type DOMRenderProps,
54
+ type PossibleLinkDOMRenderProps,
55
+ Provider,
56
+ type RenderProps,
57
+ type SlotProps,
58
+ type StyleProps,
59
+ type StyleRenderProps,
60
+ useContextProps,
61
+ useRenderProps,
62
+ useSlottedContext,
63
+ } from './utils';
64
+
65
+ // octane adaptation: structural bag (upstream's `GlobalDOMAttributes` drags React handler types).
66
+ type GlobalDOMAttributes = Record<string, any>;
67
+ type RefObject<T> = { current: T };
68
+
69
+ export interface TabsProps
70
+ extends
71
+ Omit<AriaTabListProps<any>, 'items' | 'children'>,
72
+ RenderProps<TabsRenderProps>,
73
+ SlotProps,
74
+ GlobalDOMAttributes {
75
+ /**
76
+ * The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
77
+ * element. A function may be provided to compute the class based on component state.
78
+ *
79
+ * @default 'react-aria-Tabs'
80
+ */
81
+ className?: ClassNameOrFunction<TabsRenderProps>;
82
+ }
83
+
84
+ export interface TabsRenderProps {
85
+ /**
86
+ * The orientation of the tabs.
87
+ *
88
+ * @selector [data-orientation="horizontal | vertical"]
89
+ */
90
+ orientation: Orientation;
91
+ }
92
+
93
+ export interface TabListProps<T>
94
+ extends
95
+ StyleRenderProps<TabListRenderProps>,
96
+ AriaLabelingProps,
97
+ Omit<ItemCollectionProps<T>, 'disabledKeys'>,
98
+ GlobalDOMAttributes {
99
+ /**
100
+ * The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
101
+ * element. A function may be provided to compute the class based on component state.
102
+ *
103
+ * @default 'react-aria-TabList'
104
+ */
105
+ className?: ClassNameOrFunction<TabListRenderProps>;
106
+ }
107
+
108
+ export interface TabListRenderProps {
109
+ /**
110
+ * The orientation of the tab list.
111
+ *
112
+ * @selector [data-orientation="horizontal | vertical"]
113
+ */
114
+ orientation: Orientation;
115
+ /**
116
+ * State of the tab list.
117
+ */
118
+ state: TabListState<unknown>;
119
+ }
120
+
121
+ export interface TabProps
122
+ extends
123
+ Omit<RenderProps<TabRenderProps>, 'render'>,
124
+ PossibleLinkDOMRenderProps<'div', TabRenderProps>,
125
+ AriaLabelingProps,
126
+ LinkDOMProps,
127
+ HoverEvents,
128
+ FocusEvents,
129
+ PressEvents,
130
+ Omit<GlobalDOMAttributes, 'onClick'> {
131
+ /**
132
+ * The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
133
+ * element. A function may be provided to compute the class based on component state.
134
+ *
135
+ * @default 'react-aria-Tab'
136
+ */
137
+ className?: ClassNameOrFunction<TabRenderProps>;
138
+ /** The unique id of the tab. */
139
+ id?: Key;
140
+ /** Whether the tab is disabled. */
141
+ isDisabled?: boolean;
142
+ }
143
+
144
+ export interface TabRenderProps {
145
+ /**
146
+ * Whether the tab is currently hovered with a mouse.
147
+ *
148
+ * @selector [data-hovered]
149
+ */
150
+ isHovered: boolean;
151
+ /**
152
+ * Whether the tab is currently in a pressed state.
153
+ *
154
+ * @selector [data-pressed]
155
+ */
156
+ isPressed: boolean;
157
+ /**
158
+ * Whether the tab is currently selected.
159
+ *
160
+ * @selector [data-selected]
161
+ */
162
+ isSelected: boolean;
163
+ /**
164
+ * Whether the tab is currently focused.
165
+ *
166
+ * @selector [data-focused]
167
+ */
168
+ isFocused: boolean;
169
+ /**
170
+ * Whether the tab is currently keyboard focused.
171
+ *
172
+ * @selector [data-focus-visible]
173
+ */
174
+ isFocusVisible: boolean;
175
+ /**
176
+ * Whether the tab is disabled.
177
+ *
178
+ * @selector [data-disabled]
179
+ */
180
+ isDisabled: boolean;
181
+ }
182
+
183
+ export interface TabPanelProps
184
+ extends AriaTabPanelProps, RenderProps<TabPanelRenderProps>, GlobalDOMAttributes {
185
+ /**
186
+ * The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
187
+ * element. A function may be provided to compute the class based on component state.
188
+ *
189
+ * @default 'react-aria-TabPanel'
190
+ */
191
+ className?: ClassNameOrFunction<TabPanelRenderProps>;
192
+ /**
193
+ * Whether to mount the tab panel in the DOM even when it is not currently selected. Inactive tab
194
+ * panels are inert and cannot be interacted with. They must be styled appropriately so this is
195
+ * clear to the user visually.
196
+ *
197
+ * @default false
198
+ */
199
+ shouldForceMount?: boolean;
200
+ }
201
+
202
+ export interface TabPanelRenderProps {
203
+ /**
204
+ * Whether the tab panel is currently focused.
205
+ *
206
+ * @selector [data-focused]
207
+ */
208
+ isFocused: boolean;
209
+ /**
210
+ * Whether the tab panel is currently keyboard focused.
211
+ *
212
+ * @selector [data-focus-visible]
213
+ */
214
+ isFocusVisible: boolean;
215
+ /**
216
+ * Whether the tab panel is currently non-interactive. This occurs when the
217
+ * `shouldForceMount` prop is true, and the corresponding tab is not selected.
218
+ *
219
+ * @selector [data-inert]
220
+ */
221
+ isInert: boolean;
222
+ /**
223
+ * Whether the tab panel is currently entering. Use this to apply animations.
224
+ *
225
+ * @selector [data-entering]
226
+ */
227
+ isEntering: boolean;
228
+ /**
229
+ * Whether the tab panel is currently exiting. Use this to apply animations.
230
+ *
231
+ * @selector [data-exiting]
232
+ */
233
+ isExiting: boolean;
234
+ /**
235
+ * State of the tab list.
236
+ */
237
+ state: TabListState<unknown>;
238
+ }
239
+
240
+ export const TabsContext = createContext<ContextValue<TabsProps, HTMLDivElement>>(null);
241
+ export const TabListStateContext = createContext<TabListState<any> | null>(null);
242
+
243
+ /**
244
+ * Tabs organize content into multiple sections and allow users to navigate between them.
245
+ */
246
+ export function Tabs(props: TabsProps): any {
247
+ const slot = S('Tabs');
248
+ let ref: any;
249
+ [props, ref] = useContextProps(props, props.ref, TabsContext, subSlot(slot, 'ctx'));
250
+ let { children, orientation = 'horizontal' } = props;
251
+ children = useMemo(
252
+ () =>
253
+ typeof children === 'function' ? children({ orientation, defaultChildren: null }) : children,
254
+ [children, orientation],
255
+ subSlot(slot, 'children'),
256
+ );
257
+
258
+ return createElement(CollectionBuilder, {
259
+ content: children,
260
+ children: (collection: ICollection<Node<any>>) =>
261
+ createElement(TabsInner, { props, collection, tabsRef: ref }),
262
+ });
263
+ }
264
+
265
+ interface TabsInnerProps {
266
+ props: TabsProps;
267
+ collection: ICollection<Node<any>>;
268
+ tabsRef: RefObject<HTMLDivElement | null>;
269
+ }
270
+
271
+ function TabsInner({ props, tabsRef: ref, collection }: TabsInnerProps): any {
272
+ const slot = S('TabsInner');
273
+ let { orientation = 'horizontal' } = props;
274
+ let state = useTabListState(
275
+ {
276
+ ...props,
277
+ collection,
278
+ children: undefined,
279
+ } as any,
280
+ subSlot(slot, 'state'),
281
+ );
282
+ let { focusProps, isFocused, isFocusVisible } = useFocusRing(
283
+ { within: true },
284
+ subSlot(slot, 'focusRing'),
285
+ );
286
+ let values = useMemo(
287
+ () => ({
288
+ orientation,
289
+ isFocusWithin: isFocused,
290
+ isFocusVisible,
291
+ }),
292
+ [orientation, isFocused, isFocusVisible],
293
+ subSlot(slot, 'values'),
294
+ );
295
+ let renderProps = useRenderProps(
296
+ {
297
+ ...props,
298
+ defaultClassName: 'react-aria-Tabs',
299
+ values,
300
+ } as any,
301
+ subSlot(slot, 'render'),
302
+ );
303
+
304
+ let DOMProps = filterDOMProps(props, { global: true });
305
+
306
+ return createElement(dom.div, {
307
+ ...mergeProps(DOMProps, renderProps, focusProps),
308
+ ref,
309
+ slot: props.slot || undefined,
310
+ 'data-focused': isFocused || undefined,
311
+ 'data-orientation': orientation,
312
+ 'data-focus-visible': isFocusVisible || undefined,
313
+ 'data-disabled': state.isDisabled || undefined,
314
+ children: createElement(Provider, {
315
+ values: [
316
+ [TabsContext, props],
317
+ [TabListStateContext, state],
318
+ ] as any,
319
+ children: renderProps.children,
320
+ }),
321
+ });
322
+ }
323
+
324
+ /**
325
+ * A TabList is used within Tabs to group tabs that a user can switch between. The ids of the items
326
+ * within the <TabList> must match up with a corresponding item inside the <TabPanels>.
327
+ */
328
+ export function TabList<T extends object>(props: TabListProps<T>): any {
329
+ let state = useContext(TabListStateContext);
330
+ return state
331
+ ? createElement(TabListInner, { props, forwardedRef: (props as any).ref })
332
+ : createElement(Collection, props as any);
333
+ }
334
+
335
+ interface TabListInnerProps<T> {
336
+ props: TabListProps<T>;
337
+ forwardedRef: any;
338
+ }
339
+
340
+ function TabListInner<T extends object>({ props, forwardedRef }: TabListInnerProps<T>): any {
341
+ const slot = S('TabListInner');
342
+ let state = useContext(TabListStateContext)!;
343
+ let { CollectionRoot } = useContext(CollectionRendererContext);
344
+ let { orientation = 'horizontal', keyboardActivation = 'automatic' } =
345
+ useSlottedContext(TabsContext)!;
346
+ let objectRef = useObjectRef<HTMLDivElement>(forwardedRef, subSlot(slot, 'objectRef'));
347
+
348
+ let { tabListProps } = useTabList(
349
+ {
350
+ ...props,
351
+ orientation,
352
+ keyboardActivation,
353
+ } as any,
354
+ state,
355
+ objectRef,
356
+ subSlot(slot, 'tabList'),
357
+ );
358
+
359
+ let renderProps = useRenderProps(
360
+ {
361
+ ...props,
362
+ children: null,
363
+ defaultClassName: 'react-aria-TabList',
364
+ values: {
365
+ orientation,
366
+ state,
367
+ },
368
+ } as any,
369
+ subSlot(slot, 'render'),
370
+ );
371
+
372
+ let DOMProps = filterDOMProps(props, { global: true });
373
+ delete DOMProps.id;
374
+
375
+ let persistedKeys = usePersistedKeys(
376
+ state.selectionManager.focusedKey,
377
+ subSlot(slot, 'persisted'),
378
+ );
379
+
380
+ return createElement(dom.div, {
381
+ ...mergeProps(DOMProps, renderProps, tabListProps),
382
+ ref: objectRef,
383
+ 'data-orientation': orientation || undefined,
384
+ children: createElement(SharedElementTransition, {
385
+ children: createElement(CollectionRoot, {
386
+ collection: state.collection,
387
+ persistedKeys,
388
+ }),
389
+ }),
390
+ });
391
+ }
392
+
393
+ class TabItemNode extends CollectionNode<unknown> {
394
+ static readonly type = 'item';
395
+ }
396
+
397
+ /**
398
+ * A Tab provides a title for an individual item within a TabList.
399
+ */
400
+ export const Tab: (props: TabProps & { ref?: any }) => any = createLeafComponent(
401
+ TabItemNode,
402
+ // The third (item) parameter is declared so `render.length === 3` keeps the
403
+ // engine's "cannot be rendered outside a collection" guard; it is always
404
+ // provided when rendered from a collection node.
405
+ (props: TabProps, forwardedRef: any, item?: Node<unknown>): any => {
406
+ const slot = S('Tab');
407
+ let state = useContext(TabListStateContext)!;
408
+ let ref = useObjectRef<any>(forwardedRef, subSlot(slot, 'objectRef'));
409
+ let { tabProps, isSelected, isDisabled, isPressed } = useTab(
410
+ { key: item!.key, ...props },
411
+ state,
412
+ ref,
413
+ subSlot(slot, 'tab'),
414
+ );
415
+ let { focusProps, isFocused, isFocusVisible } = useFocusRing(
416
+ undefined,
417
+ subSlot(slot, 'focusRing'),
418
+ );
419
+ let { hoverProps, isHovered } = useHover(
420
+ {
421
+ isDisabled,
422
+ onHoverStart: props.onHoverStart,
423
+ onHoverEnd: props.onHoverEnd,
424
+ onHoverChange: props.onHoverChange,
425
+ },
426
+ subSlot(slot, 'hover'),
427
+ );
428
+
429
+ let renderProps = useRenderProps<TabRenderProps, any>(
430
+ {
431
+ ...props,
432
+ id: undefined,
433
+ children: item!.rendered,
434
+ defaultClassName: 'react-aria-Tab',
435
+ values: {
436
+ isSelected,
437
+ isDisabled,
438
+ isFocused,
439
+ isFocusVisible,
440
+ isPressed,
441
+ isHovered,
442
+ },
443
+ } as any,
444
+ subSlot(slot, 'render'),
445
+ );
446
+
447
+ let ElementType = item!.props.href ? dom.a : dom.div;
448
+ let DOMProps = filterDOMProps(props as any, { global: true });
449
+ delete DOMProps.id;
450
+ delete DOMProps.onClick;
451
+
452
+ return createElement(ElementType, {
453
+ ...mergeProps(DOMProps, renderProps, tabProps, focusProps, hoverProps),
454
+ ref,
455
+ 'data-selected': isSelected || undefined,
456
+ 'data-disabled': isDisabled || undefined,
457
+ 'data-focused': isFocused || undefined,
458
+ 'data-focus-visible': isFocusVisible || undefined,
459
+ 'data-pressed': isPressed || undefined,
460
+ 'data-hovered': isHovered || undefined,
461
+ children: createElement(SelectionIndicatorContext.Provider, {
462
+ value: { isSelected },
463
+ children: renderProps.children,
464
+ }),
465
+ });
466
+ },
467
+ );
468
+
469
+ export interface TabPanelsProps<T>
470
+ extends
471
+ Omit<ItemCollectionProps<T>, 'disabledKeys'>,
472
+ StyleProps,
473
+ DOMRenderProps<'div', undefined>,
474
+ GlobalDOMAttributes {
475
+ /**
476
+ * The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
477
+ * element.
478
+ *
479
+ * @default 'react-aria-TabPanels'
480
+ */
481
+ className?: string;
482
+ }
483
+
484
+ /**
485
+ * Groups multiple `<TabPanel>` elements, and provides CSS variables for animated transitions.
486
+ */
487
+ export const TabPanels: <T extends object>(props: TabPanelsProps<T> & { ref?: any }) => any =
488
+ createHideableComponent(function TabPanels(props: TabPanelsProps<any>, forwardedRef: any): any {
489
+ const slot = S('TabPanels');
490
+ let state = useContext(TabListStateContext)!;
491
+ let ref = useObjectRef<HTMLDivElement>(forwardedRef, subSlot(slot, 'objectRef'));
492
+
493
+ let selectedKeyRef = useRef(state.selectedKey, subSlot(slot, 'selectedKey'));
494
+ let prevSize = useRef<DOMRect | null>(null, subSlot(slot, 'prevSize'));
495
+ let hasTransition = useRef<boolean | null>(null, subSlot(slot, 'hasTransition'));
496
+ useLayoutEffect(
497
+ () => {
498
+ let el = ref.current;
499
+ if (!el) {
500
+ return;
501
+ }
502
+
503
+ if (hasTransition.current == null) {
504
+ hasTransition.current = /width|height|block-size|inline-size|all/.test(
505
+ window.getComputedStyle(el).transition,
506
+ );
507
+ }
508
+
509
+ if (
510
+ hasTransition.current &&
511
+ selectedKeyRef.current != null &&
512
+ selectedKeyRef.current !== state.selectedKey
513
+ ) {
514
+ // Measure auto size.
515
+ el.style.setProperty('--tab-panel-width', 'auto');
516
+ el.style.setProperty('--tab-panel-height', 'auto');
517
+ let { width, height } = el.getBoundingClientRect();
518
+
519
+ if (
520
+ prevSize.current &&
521
+ (prevSize.current.width !== width || prevSize.current.height !== height)
522
+ ) {
523
+ // Revert to previous size.
524
+ el.style.setProperty('--tab-panel-width', prevSize.current.width + 'px');
525
+ el.style.setProperty('--tab-panel-height', prevSize.current.height + 'px');
526
+
527
+ // Force style re-calculation to trigger animations.
528
+ window.getComputedStyle(el).height;
529
+
530
+ // Animate to current pixel size.
531
+ el.style.setProperty('--tab-panel-width', width + 'px');
532
+ el.style.setProperty('--tab-panel-height', height + 'px');
533
+
534
+ // When animations complete, revert back to auto size.
535
+ Promise.all(el.getAnimations().map((a) => a.finished))
536
+ .then(() => {
537
+ el.style.setProperty('--tab-panel-width', 'auto');
538
+ el.style.setProperty('--tab-panel-height', 'auto');
539
+ })
540
+ .catch(() => {});
541
+ }
542
+ }
543
+
544
+ selectedKeyRef.current = state.selectedKey;
545
+ },
546
+ [ref, state.selectedKey],
547
+ subSlot(slot, 'measure'),
548
+ );
549
+
550
+ // Store previous size before DOM updates occur.
551
+ // This breaks the rules of hooks because there is no effect that runs _before_ DOM updates.
552
+ if (
553
+ state.selectedKey != null &&
554
+ state.selectedKey !== selectedKeyRef.current &&
555
+ ref.current &&
556
+ hasTransition.current
557
+ ) {
558
+ prevSize.current = ref.current.getBoundingClientRect();
559
+ }
560
+
561
+ let DOMProps = filterDOMProps(props, { labelable: true, global: true });
562
+ delete DOMProps.id;
563
+
564
+ return createElement(dom.div, {
565
+ render: props.render,
566
+ ...DOMProps,
567
+ ref,
568
+ style: props.style,
569
+ className: props.className || 'react-aria-TabPanels',
570
+ children: createElement(Collection, props as any),
571
+ });
572
+ });
573
+
574
+ /**
575
+ * A TabPanel provides the content for a tab.
576
+ */
577
+ export const TabPanel: (props: TabPanelProps & { ref?: any }) => any = createHideableComponent(
578
+ function TabPanel(props: TabPanelProps, forwardedRef: any): any {
579
+ const slot = S('TabPanel');
580
+ const state = useContext(TabListStateContext)!;
581
+ let ref = useObjectRef<HTMLDivElement>(forwardedRef, subSlot(slot, 'objectRef'));
582
+
583
+ // Track if the tab panel was initially selected on mount (after extra render to populate the collection).
584
+ // In this case, we don't want to trigger animations.
585
+ let isSelected = state.selectedKey === props.id;
586
+ let [isInitiallySelected, setInitiallySelected] = useState<boolean | null>(
587
+ state.selectedKey != null ? isSelected : null,
588
+ subSlot(slot, 'initiallySelected'),
589
+ );
590
+ if (isInitiallySelected == null && state.selectedKey != null) {
591
+ setInitiallySelected(isSelected);
592
+ } else if (!isSelected && isInitiallySelected) {
593
+ setInitiallySelected(false);
594
+ }
595
+
596
+ let isExiting = useExitAnimation(ref, isSelected, subSlot(slot, 'exit'));
597
+ if (!isSelected && !props.shouldForceMount && !isExiting) {
598
+ return null;
599
+ }
600
+
601
+ return createElement(TabPanelInner, {
602
+ ...props,
603
+ tabPanelRef: ref,
604
+ isInitiallySelected: isInitiallySelected || false,
605
+ isExiting,
606
+ });
607
+ },
608
+ );
609
+
610
+ function TabPanelInner(
611
+ props: TabPanelProps & {
612
+ tabPanelRef: RefObject<HTMLDivElement | null>;
613
+ isInitiallySelected: boolean;
614
+ isExiting: boolean;
615
+ },
616
+ ): any {
617
+ const slot = S('TabPanelInner');
618
+ let state = useContext(TabListStateContext)!;
619
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
620
+ let { id, tabPanelRef: ref, isInitiallySelected, isExiting, ...otherProps } = props;
621
+ let { tabPanelProps } = useTabPanel(props, state, ref, subSlot(slot, 'tabPanel'));
622
+ let { focusProps, isFocused, isFocusVisible } = useFocusRing(
623
+ undefined,
624
+ subSlot(slot, 'focusRing'),
625
+ );
626
+
627
+ let isSelected = state.selectedKey === props.id;
628
+ let isEntering =
629
+ useEnterAnimation(ref, undefined, subSlot(slot, 'enter')) && !isInitiallySelected;
630
+ let renderProps = useRenderProps(
631
+ {
632
+ ...props,
633
+ defaultClassName: 'react-aria-TabPanel',
634
+ values: {
635
+ isFocused,
636
+ isFocusVisible,
637
+ // octane adaptation: upstream `inertValue` is React <19 string compat; octane
638
+ // follows React 19 boolean `inert` semantics.
639
+ isInert: !isSelected,
640
+ isEntering,
641
+ isExiting,
642
+ state,
643
+ },
644
+ } as any,
645
+ subSlot(slot, 'render'),
646
+ );
647
+
648
+ let DOMProps = filterDOMProps(otherProps, { global: true });
649
+ delete DOMProps.id;
650
+
651
+ let domProps = isSelected
652
+ ? mergeProps(DOMProps, tabPanelProps, focusProps, renderProps)
653
+ : mergeProps(DOMProps, renderProps);
654
+
655
+ return createElement(dom.div, {
656
+ ...domProps,
657
+ ref,
658
+ 'data-focused': isFocused || undefined,
659
+ 'data-focus-visible': isFocusVisible || undefined,
660
+ inert: !isSelected || (props as any).inert || undefined,
661
+ 'data-inert': !isSelected ? 'true' : undefined,
662
+ 'data-entering': isEntering || undefined,
663
+ 'data-exiting': isExiting || undefined,
664
+ children: createElement(Provider, {
665
+ values: [
666
+ [TabsContext, null],
667
+ [TabListStateContext, null],
668
+ ] as any,
669
+ children: createElement(CollectionRendererContext.Provider, {
670
+ value: DefaultCollectionRenderer,
671
+ children: renderProps.children,
672
+ }),
673
+ }),
674
+ });
675
+ }