12
12
13
13
import { AriaTabPanelProps , SpectrumTabListProps , SpectrumTabPanelsProps , SpectrumTabsProps } from '@react-types/tabs' ;
14
14
import { classNames , SlotProvider , unwrapDOMRef , useDOMRef , useStyleProps } from '@react-spectrum/utils' ;
15
- import { DOMProps , DOMRef , Key , Node , Orientation , StyleProps } from '@react-types/shared' ;
15
+ import { DOMProps , DOMRef , DOMRefValue , Key , Node , Orientation , RefObject , StyleProps } from '@react-types/shared' ;
16
16
import { filterDOMProps , mergeProps , useId , useLayoutEffect , useResizeObserver } from '@react-aria/utils' ;
17
17
import { FocusRing } from '@react-aria/focus' ;
18
18
import { Item , Picker } from '@react-spectrum/picker' ;
19
19
import { ListCollection } from '@react-stately/list' ;
20
20
import React , {
21
- MutableRefObject ,
21
+ CSSProperties ,
22
+ HTMLAttributes ,
22
23
ReactElement ,
23
24
ReactNode ,
24
25
useCallback ,
@@ -40,22 +41,20 @@ import {useTab, useTabList, useTabPanel} from '@react-aria/tabs';
40
41
interface TabsContext < T > {
41
42
tabProps : SpectrumTabsProps < T > ,
42
43
tabState : {
43
- tabListState : TabListState < T > ,
44
+ tabListState : TabListState < T > | null ,
44
45
setTabListState : ( state : TabListState < T > ) => void ,
45
- selectedTab : HTMLElement ,
46
+ selectedTab : HTMLElement | null ,
46
47
collapsed : boolean
47
48
} ,
48
49
refs : {
49
- wrapperRef : MutableRefObject < HTMLDivElement > ,
50
- tablistRef : MutableRefObject < HTMLDivElement >
51
- } ,
52
- tabPanelProps : {
53
- 'aria-labelledby' : string
50
+ wrapperRef : RefObject < HTMLDivElement | null > ,
51
+ tablistRef : RefObject < HTMLDivElement | null >
54
52
} ,
53
+ tabPanelProps : HTMLAttributes < HTMLElement > ,
55
54
tabLineState : Array < DOMRect >
56
55
}
57
56
58
- const TabContext = React . createContext < TabsContext < any > > ( null ) ;
57
+ const TabContext = React . createContext < TabsContext < any > | null > ( null ) ;
59
58
60
59
function Tabs < T extends object > ( props : SpectrumTabsProps < T > , ref : DOMRef < HTMLDivElement > ) {
61
60
props = useProviderProps ( props ) ;
@@ -67,20 +66,20 @@ function Tabs<T extends object>(props: SpectrumTabsProps<T>, ref: DOMRef<HTMLDiv
67
66
} = props ;
68
67
69
68
let domRef = useDOMRef ( ref ) ;
70
- let tablistRef = useRef < HTMLDivElement > ( undefined ) ;
71
- let wrapperRef = useRef < HTMLDivElement > ( undefined ) ;
69
+ let tablistRef = useRef < HTMLDivElement > ( null ) ;
70
+ let wrapperRef = useRef < HTMLDivElement > ( null ) ;
72
71
73
72
let { direction} = useLocale ( ) ;
74
73
let { styleProps} = useStyleProps ( otherProps ) ;
75
74
let [ collapsed , setCollapsed ] = useState ( false ) ;
76
- let [ selectedTab , setSelectedTab ] = useState < HTMLElement > ( ) ;
77
- const [ tabListState , setTabListState ] = useState < TabListState < T > > ( null ) ;
78
- let [ tabPositions , setTabPositions ] = useState ( [ ] ) ;
79
- let prevTabPositions = useRef ( tabPositions ) ;
75
+ let [ selectedTab , setSelectedTab ] = useState < HTMLElement | null > ( null ) ;
76
+ const [ tabListState , setTabListState ] = useState < TabListState < T > | null > ( null ) ;
77
+ let [ tabPositions , setTabPositions ] = useState < DOMRect [ ] > ( [ ] ) ;
78
+ let prevTabPositions = useRef < DOMRect [ ] > ( tabPositions ) ;
80
79
81
80
useEffect ( ( ) => {
82
81
if ( tablistRef . current ) {
83
- let selectedTab : HTMLElement = tablistRef . current . querySelector ( `[data-key="${ CSS . escape ( tabListState ?. selectedKey ?. toString ( ) ) } "]` ) ;
82
+ let selectedTab : HTMLElement | null = tablistRef . current . querySelector ( `[data-key="${ CSS . escape ( tabListState ?. selectedKey ?. toString ( ) ?? '' ) } "]` ) ;
84
83
85
84
if ( selectedTab != null ) {
86
85
setSelectedTab ( selectedTab ) ;
@@ -92,15 +91,16 @@ function Tabs<T extends object>(props: SpectrumTabsProps<T>, ref: DOMRef<HTMLDiv
92
91
let checkShouldCollapse = useCallback ( ( ) => {
93
92
if ( wrapperRef . current && orientation !== 'vertical' ) {
94
93
let tabsComponent = wrapperRef . current ;
95
- let tabs = tablistRef . current . querySelectorAll ( '[role="tab"]' ) ;
96
- let tabDimensions = [ ...tabs ] . map ( tab => tab . getBoundingClientRect ( ) ) ;
94
+ let tabs : NodeListOf < Element > = tablistRef . current ? .querySelectorAll ( '[role="tab"]' ) ?? new NodeList ( ) as NodeListOf < Element > ;
95
+ let tabDimensions = [ ...tabs ] . map ( ( tab : Element ) => tab . getBoundingClientRect ( ) ) ;
97
96
98
97
let end = direction === 'rtl' ? 'left' : 'right' ;
99
98
let farEdgeTabList = tabsComponent . getBoundingClientRect ( ) [ end ] ;
100
99
let farEdgeLastTab = tabDimensions [ tabDimensions . length - 1 ] [ end ] ;
101
100
let shouldCollapse = direction === 'rtl' ? farEdgeLastTab < farEdgeTabList : farEdgeTabList < farEdgeLastTab ;
102
101
setCollapsed ( shouldCollapse ) ;
103
- if ( tabDimensions . length !== prevTabPositions . current . length || tabDimensions . some ( ( box , index ) => box ?. left !== prevTabPositions . current [ index ] ?. left || box ?. right !== prevTabPositions . current [ index ] ?. right ) ) {
102
+ if ( tabDimensions . length !== prevTabPositions . current . length
103
+ || tabDimensions . some ( ( box , index ) => box ?. left !== prevTabPositions . current [ index ] ?. left || box ?. right !== prevTabPositions . current [ index ] ?. right ) ) {
104
104
setTabPositions ( tabDimensions ) ;
105
105
prevTabPositions . current = tabDimensions ;
106
106
}
@@ -113,7 +113,7 @@ function Tabs<T extends object>(props: SpectrumTabsProps<T>, ref: DOMRef<HTMLDiv
113
113
114
114
useResizeObserver ( { ref : wrapperRef , onResize : checkShouldCollapse } ) ;
115
115
116
- let tabPanelProps = {
116
+ let tabPanelProps : HTMLAttributes < HTMLElement > = {
117
117
'aria-labelledby' : undefined
118
118
} ;
119
119
@@ -202,8 +202,8 @@ function Tab<T>(props: TabProps<T>) {
202
202
203
203
interface TabLineProps {
204
204
orientation ?: Orientation ,
205
- selectedTab ?: HTMLElement ,
206
- selectedKey ?: Key
205
+ selectedTab ?: HTMLElement | null ,
206
+ selectedKey ?: Key | null
207
207
}
208
208
209
209
// @private
@@ -218,18 +218,20 @@ function TabLine(props: TabLineProps) {
218
218
219
219
let { direction} = useLocale ( ) ;
220
220
let { scale} = useProvider ( ) ;
221
- let { tabLineState} = useContext ( TabContext ) ;
221
+ let { tabLineState} = useContext ( TabContext ) ! ;
222
222
223
- let [ style , setStyle ] = useState ( {
223
+ let [ style , setStyle ] = useState < CSSProperties > ( {
224
224
width : undefined ,
225
225
height : undefined
226
226
} ) ;
227
227
228
228
let onResize = useCallback ( ( ) => {
229
229
if ( selectedTab ) {
230
- let styleObj = { transform : undefined , width : undefined , height : undefined } ;
230
+ let styleObj : CSSProperties = { transform : undefined , width : undefined , height : undefined } ;
231
231
// In RTL, calculate the transform from the right edge of the tablist so that resizing the window doesn't break the Tabline position due to offsetLeft changes
232
- let offset = direction === 'rtl' ? - 1 * ( ( selectedTab . offsetParent as HTMLElement ) ?. offsetWidth - selectedTab . offsetWidth - selectedTab . offsetLeft ) : selectedTab . offsetLeft ;
232
+ let offset = direction === 'rtl' ?
233
+ - 1 * ( ( selectedTab . offsetParent as HTMLElement ) ?. offsetWidth - selectedTab . offsetWidth - selectedTab . offsetLeft ) :
234
+ selectedTab . offsetLeft ;
233
235
styleObj . transform = orientation === 'vertical'
234
236
? `translateY(${ selectedTab . offsetTop } px)`
235
237
: `translateX(${ offset } px)` ;
@@ -255,7 +257,7 @@ function TabLine(props: TabLineProps) {
255
257
* The keys of the items within the <TabList> must match up with a corresponding item inside the <TabPanels>.
256
258
*/
257
259
export function TabList < T > ( props : SpectrumTabListProps < T > ) {
258
- const tabContext = useContext ( TabContext ) ;
260
+ const tabContext = useContext ( TabContext ) ! ;
259
261
const { refs, tabState, tabProps, tabPanelProps} = tabContext ;
260
262
const { isQuiet, density, isEmphasized, orientation} = tabProps ;
261
263
const { selectedTab, collapsed, setTabListState} = tabState ;
@@ -330,13 +332,13 @@ export function TabList<T>(props: SpectrumTabListProps<T>) {
330
332
* TabPanels is used within Tabs as a container for the content of each tab.
331
333
* The keys of the items within the <TabPanels> must match up with a corresponding item inside the <TabList>.
332
334
*/
333
- export function TabPanels < T > ( props : SpectrumTabPanelsProps < T > ) {
334
- const { tabState, tabProps} = useContext ( TabContext ) ;
335
+ export function TabPanels < T extends object > ( props : SpectrumTabPanelsProps < T > ) {
336
+ const { tabState, tabProps} = useContext ( TabContext ) ! ;
335
337
const { tabListState} = tabState ;
336
338
337
- const factory = useCallback ( nodes => new ListCollection ( nodes ) , [ ] ) ;
339
+ const factory = useCallback ( ( nodes : Iterable < Node < T > > ) => new ListCollection ( nodes ) , [ ] ) ;
338
340
const collection = useCollection ( { items : tabProps . items , ...props } , factory , { suppressTextValueWarning : true } ) ;
339
- const selectedItem = tabListState ? collection . getItem ( tabListState . selectedKey ) : null ;
341
+ const selectedItem = tabListState && tabListState . selectedKey != null ? collection . getItem ( tabListState . selectedKey ) : null ;
340
342
341
343
return (
342
344
< TabPanel { ...props } key = { tabListState ?. selectedKey } >
@@ -351,9 +353,9 @@ interface TabPanelProps extends AriaTabPanelProps, StyleProps {
351
353
352
354
// @private
353
355
function TabPanel ( props : TabPanelProps ) {
354
- const { tabState, tabPanelProps : ctxTabPanelProps } = useContext ( TabContext ) ;
356
+ const { tabState, tabPanelProps : ctxTabPanelProps } = useContext ( TabContext ) ! ;
355
357
const { tabListState} = tabState ;
356
- let ref = useRef ( undefined ) ;
358
+ let ref = useRef < HTMLDivElement | null > ( null ) ;
357
359
const { tabPanelProps} = useTabPanel ( props , tabListState , ref ) ;
358
360
let { styleProps} = useStyleProps ( props ) ;
359
361
@@ -392,8 +394,8 @@ function TabPicker<T>(props: TabPickerProps<T>) {
392
394
visible
393
395
} = props ;
394
396
395
- let ref = useRef ( undefined ) ;
396
- let [ pickerNode , setPickerNode ] = useState ( null ) ;
397
+ let ref = useRef < DOMRefValue < HTMLDivElement > > ( null ) ;
398
+ let [ pickerNode , setPickerNode ] = useState < HTMLElement | null > ( null ) ;
397
399
398
400
useEffect ( ( ) => {
399
401
let node = unwrapDOMRef ( ref ) ;
@@ -408,7 +410,6 @@ function TabPicker<T>(props: TabPickerProps<T>) {
408
410
409
411
const style : React . CSSProperties = visible ? { } : { visibility : 'hidden' , position : 'absolute' } ;
410
412
411
- // TODO: Figure out if tabListProps should go onto the div here, v2 doesn't do it
412
413
return (
413
414
< div
414
415
className = { classNames (
0 commit comments