Skip to content

Commit fb28ab3

Browse files
fix: TS Strict aria C and D (#6761)
* TS Strict aria C and D * Propagate input type from textfield to focusable props * Check input key for null in useDateSegment before calling onInput * review followup * fix types * review comments * fix remaining ts error * sort descriptor is required * keep value required --------- Co-authored-by: Devon Govett <[email protected]>
1 parent 09dc3c0 commit fb28ab3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+419
-298
lines changed

packages/@react-aria/calendar/src/useCalendarCell.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export interface CalendarCellAria {
7474
*/
7575
export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarState | RangeCalendarState, ref: RefObject<HTMLElement | null>): CalendarCellAria {
7676
let {date, isDisabled} = props;
77-
let {errorMessageId, selectedDateDescription} = hookData.get(state);
77+
let {errorMessageId, selectedDateDescription} = hookData.get(state)!;
7878
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/calendar');
7979
let dateFormatter = useDateFormatter({
8080
weekday: 'long',
@@ -89,7 +89,7 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
8989
isDisabled = isDisabled || state.isCellDisabled(date);
9090
let isUnavailable = state.isCellUnavailable(date);
9191
let isSelectable = !isDisabled && !isUnavailable;
92-
let isInvalid = state.isValueInvalid && (
92+
let isInvalid = state.isValueInvalid && Boolean(
9393
'highlightedRange' in state
9494
? !state.anchorDate && state.highlightedRange && date.compare(state.highlightedRange.start) >= 0 && date.compare(state.highlightedRange.end) <= 0
9595
: state.value && isSameDay(state.value, date)
@@ -159,7 +159,7 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
159159

160160
let isAnchorPressed = useRef(false);
161161
let isRangeBoundaryPressed = useRef(false);
162-
let touchDragTimerRef = useRef(null);
162+
let touchDragTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
163163
let {pressProps, isPressed} = usePress({
164164
// When dragging to select a range, we don't want dragging over the original anchor
165165
// again to trigger onPressStart. Cancel presses immediately when the pointer exits.
@@ -195,7 +195,7 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
195195

196196
let startDragging = () => {
197197
state.setDragging(true);
198-
touchDragTimerRef.current = null;
198+
touchDragTimerRef.current = undefined;
199199

200200
state.selectDate(date);
201201
state.setFocusedDate(date);
@@ -215,7 +215,7 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
215215
isRangeBoundaryPressed.current = false;
216216
isAnchorPressed.current = false;
217217
clearTimeout(touchDragTimerRef.current);
218-
touchDragTimerRef.current = null;
218+
touchDragTimerRef.current = undefined;
219219
},
220220
onPress() {
221221
// For non-range selection, always select on press up.
@@ -269,7 +269,7 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
269269
}
270270
});
271271

272-
let tabIndex = null;
272+
let tabIndex: number | undefined = undefined;
273273
if (!isDisabled) {
274274
tabIndex = isSameDay(date, state.focusedDate) ? 0 : -1;
275275
}
@@ -298,14 +298,14 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
298298
calendar: date.calendar.identifier
299299
});
300300

301-
let formattedDate = useMemo(() => cellDateFormatter.formatToParts(nativeDate).find(part => part.type === 'day').value, [cellDateFormatter, nativeDate]);
301+
let formattedDate = useMemo(() => cellDateFormatter.formatToParts(nativeDate).find(part => part.type === 'day')!.value, [cellDateFormatter, nativeDate]);
302302

303303
return {
304304
cellProps: {
305305
role: 'gridcell',
306-
'aria-disabled': !isSelectable || null,
307-
'aria-selected': isSelected || null,
308-
'aria-invalid': isInvalid || null
306+
'aria-disabled': !isSelectable || undefined,
307+
'aria-selected': isSelected || undefined,
308+
'aria-invalid': isInvalid || undefined
309309
},
310310
buttonProps: mergeProps(pressProps, {
311311
onFocus() {
@@ -315,11 +315,11 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
315315
},
316316
tabIndex,
317317
role: 'button',
318-
'aria-disabled': !isSelectable || null,
318+
'aria-disabled': !isSelectable || undefined,
319319
'aria-label': label,
320-
'aria-invalid': isInvalid || null,
320+
'aria-invalid': isInvalid || undefined,
321321
'aria-describedby': [
322-
isInvalid ? errorMessageId : null,
322+
isInvalid ? errorMessageId : undefined,
323323
descriptionProps['aria-describedby']
324324
].filter(Boolean).join(' ') || undefined,
325325
onPointerEnter(e) {

packages/@react-aria/calendar/src/useCalendarGrid.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export function useCalendarGrid(props: AriaCalendarGridProps, state: CalendarSta
128128

129129
let visibleRangeDescription = useVisibleRangeDescription(startDate, endDate, state.timeZone, true);
130130

131-
let {ariaLabel, ariaLabelledBy} = hookData.get(state);
131+
let {ariaLabel, ariaLabelledBy} = hookData.get(state)!;
132132
let labelProps = useLabels({
133133
'aria-label': [ariaLabel, visibleRangeDescription].filter(Boolean).join(', '),
134134
'aria-labelledby': ariaLabelledBy
@@ -148,8 +148,8 @@ export function useCalendarGrid(props: AriaCalendarGridProps, state: CalendarSta
148148
return {
149149
gridProps: mergeProps(labelProps, {
150150
role: 'grid',
151-
'aria-readonly': state.isReadOnly || null,
152-
'aria-disabled': state.isDisabled || null,
151+
'aria-readonly': state.isReadOnly || undefined,
152+
'aria-disabled': state.isDisabled || undefined,
153153
'aria-multiselectable': ('highlightedRange' in state) || undefined,
154154
onKeyDown,
155155
onFocus: () => state.setFocused(true),

packages/@react-aria/calendar/src/utils.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,26 @@ import {useDateFormatter, useLocalizedStringFormatter} from '@react-aria/i18n';
1919
import {useMemo} from 'react';
2020

2121
interface HookData {
22-
ariaLabel: string,
23-
ariaLabelledBy: string,
22+
ariaLabel?: string,
23+
ariaLabelledBy?: string,
2424
errorMessageId: string,
2525
selectedDateDescription: string
2626
}
2727

2828
export const hookData = new WeakMap<CalendarState | RangeCalendarState, HookData>();
2929

30-
export function getEraFormat(date: CalendarDate): 'short' | undefined {
30+
export function getEraFormat(date: CalendarDate | undefined): 'short' | undefined {
3131
return date?.calendar.identifier === 'gregory' && date.era === 'BC' ? 'short' : undefined;
3232
}
3333

3434
export function useSelectedDateDescription(state: CalendarState | RangeCalendarState) {
3535
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/calendar');
3636

37-
let start: CalendarDate, end: CalendarDate;
37+
let start: CalendarDate | undefined, end: CalendarDate | undefined;
3838
if ('highlightedRange' in state) {
3939
({start, end} = state.highlightedRange || {});
4040
} else {
41-
start = end = state.value;
41+
start = end = state.value ?? undefined;
4242
}
4343

4444
let dateFormatter = useDateFormatter({

packages/@react-aria/calendar/stories/Example.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@
1212
import {Button} from '@react-spectrum/button';
1313
import {CalendarState, RangeCalendarState, useCalendarState} from '@react-stately/calendar';
1414
import {createCalendar, DateDuration, getWeeksInMonth, startOfWeek} from '@internationalized/date';
15-
import React, {useMemo, useRef} from 'react';
15+
import React, {ReactElement, useMemo, useRef} from 'react';
1616
import {useCalendar, useCalendarCell, useCalendarGrid} from '../src';
1717
import {useDateFormatter, useLocale} from '@react-aria/i18n';
1818

1919

2020
export function Example(props) {
2121
let {locale} = useLocale();
2222
const {visibleDuration} = props;
23-
23+
2424
let state = useCalendarState({
2525
...props,
2626
locale,
@@ -35,7 +35,7 @@ export function Example(props) {
3535
gridCount = visibleDuration.months;
3636
}
3737

38-
let components = [];
38+
let components: Array<ReactElement> = [];
3939
for (let i = 0; i < gridCount; i++) {
4040
components.push(<CalendarGrid key={i} state={state} visibleDuration={visibleDuration} offset={{months: i}} />);
4141
}
@@ -78,11 +78,11 @@ function CalendarGrid({state, visibleDuration, offset = {}}: {state: CalendarSta
7878
</div>
7979
))}
8080
</div>);
81-
81+
8282
}
8383

8484
function Cell(props) {
85-
let ref = useRef(undefined);
85+
let ref = useRef<HTMLSpanElement | null>(null);
8686
let {cellProps, buttonProps} = useCalendarCell(props, props.state, ref);
8787

8888
let dateFormatter = useDateFormatter({

packages/@react-aria/combobox/src/useComboBox.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ export function useComboBox<T>(props: AriaComboBoxOptions<T>, state: ComboBoxSta
8181
isReadOnly,
8282
isDisabled
8383
} = props;
84+
let backupBtnRef = useRef(null);
85+
buttonRef = buttonRef ?? backupBtnRef;
8486

8587
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/combobox');
8688
let {menuTriggerProps, menuProps} = useMenuTrigger<T>(
@@ -136,11 +138,11 @@ export function useComboBox<T>(props: AriaComboBoxOptions<T>, state: ComboBoxSta
136138
}
137139

138140
// If the focused item is a link, trigger opening it. Items that are links are not selectable.
139-
if (state.isOpen && state.selectionManager.focusedKey != null && state.selectionManager.isLink(state.selectionManager.focusedKey)) {
140-
if (e.key === 'Enter') {
141-
let item = listBoxRef.current.querySelector(`[data-key="${CSS.escape(state.selectionManager.focusedKey.toString())}"]`);
142-
if (item instanceof HTMLAnchorElement) {
143-
let collectionItem = state.collection.getItem(state.selectionManager.focusedKey);
141+
if (state.isOpen && listBoxRef.current && state.selectionManager.focusedKey != null && state.selectionManager.isLink(state.selectionManager.focusedKey)) {
142+
let item = listBoxRef.current.querySelector(`[data-key="${CSS.escape(state.selectionManager.focusedKey.toString())}"]`);
143+
if (e.key === 'Enter' && item instanceof HTMLAnchorElement) {
144+
let collectionItem = state.collection.getItem(state.selectionManager.focusedKey);
145+
if (collectionItem) {
144146
router.open(item, e, collectionItem.props.href, collectionItem.props.routerOptions as RouterOptions);
145147
}
146148
}
@@ -217,14 +219,14 @@ export function useComboBox<T>(props: AriaComboBoxOptions<T>, state: ComboBoxSta
217219
let onPress = (e: PressEvent) => {
218220
if (e.pointerType === 'touch') {
219221
// Focus the input field in case it isn't focused yet
220-
inputRef.current.focus();
222+
inputRef.current?.focus();
221223
state.toggle(null, 'manual');
222224
}
223225
};
224226

225227
let onPressStart = (e: PressEvent) => {
226228
if (e.pointerType !== 'touch') {
227-
inputRef.current.focus();
229+
inputRef.current?.focus();
228230
state.toggle((e.pointerType === 'keyboard' || e.pointerType === 'virtual') ? 'first' : null, 'manual');
229231
}
230232
};
@@ -251,7 +253,7 @@ export function useComboBox<T>(props: AriaComboBoxOptions<T>, state: ComboBoxSta
251253
// Sometimes VoiceOver on iOS fires two touchend events in quick succession. Ignore the second one.
252254
if (e.timeStamp - lastEventTime.current < 500) {
253255
e.preventDefault();
254-
inputRef.current.focus();
256+
inputRef.current?.focus();
255257
return;
256258
}
257259

@@ -263,7 +265,7 @@ export function useComboBox<T>(props: AriaComboBoxOptions<T>, state: ComboBoxSta
263265

264266
if (touch.clientX === centerX && touch.clientY === centerY) {
265267
e.preventDefault();
266-
inputRef.current.focus();
268+
inputRef.current?.focus();
267269
state.toggle(null, 'manual');
268270

269271
lastEventTime.current = e.timeStamp;
@@ -287,7 +289,7 @@ export function useComboBox<T>(props: AriaComboBoxOptions<T>, state: ComboBoxSta
287289
let sectionTitle = section?.['aria-label'] || (typeof section?.rendered === 'string' ? section.rendered : '') || '';
288290

289291
let announcement = stringFormatter.format('focusAnnouncement', {
290-
isGroupChange: section && sectionKey !== lastSection.current,
292+
isGroupChange: (section && sectionKey !== lastSection.current) ?? false,
291293
groupTitle: sectionTitle,
292294
groupCount: section ? [...getChildNodes(section, state.collection)].length : 0,
293295
optionText: focusedItem['aria-label'] || focusedItem.textValue || '',
@@ -336,7 +338,7 @@ export function useComboBox<T>(props: AriaComboBoxOptions<T>, state: ComboBoxSta
336338

337339
useEffect(() => {
338340
if (state.isOpen) {
339-
return ariaHideOutside([inputRef.current, popoverRef.current]);
341+
return ariaHideOutside([inputRef.current, popoverRef.current].filter(element => element != null));
340342
}
341343
}, [state.isOpen, inputRef, popoverRef]);
342344

packages/@react-aria/combobox/stories/example.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ function ListBox(props) {
152152
}
153153

154154
function Option({item, state}) {
155-
let ref = React.useRef(undefined);
155+
let ref = React.useRef<HTMLLIElement | null>(null);
156156
let {optionProps, isSelected, isFocused, isDisabled} = useOption({key: item.key}, state, ref);
157157

158158
let backgroundColor;

packages/@react-aria/datepicker/src/useDateField.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ export interface DateFieldAria extends ValidationResult {
4545

4646
// Data that is passed between useDateField and useDateSegment.
4747
interface HookData {
48-
ariaLabel: string,
49-
ariaLabelledBy: string,
50-
ariaDescribedBy: string,
48+
ariaLabel?: string,
49+
ariaLabelledBy?: string,
50+
ariaDescribedBy?: string,
5151
focusManager: FocusManager
5252
}
5353

packages/@react-aria/datepicker/src/useDatePickerGroup.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ export function useDatePickerGroup(state: DatePickerState | DateRangePickerState
5050

5151
// Focus the first placeholder segment from the end on mouse down/touch up in the field.
5252
let focusLast = () => {
53+
if (!ref.current) {
54+
return;
55+
}
5356
// Try to find the segment prior to the element that was clicked on.
5457
let target = window.event?.target as FocusableElement;
5558
let walker = getFocusableTreeWalker(ref.current, {tabbable: true});

0 commit comments

Comments
 (0)