Skip to content
26 changes: 13 additions & 13 deletions packages/@react-aria/calendar/src/useCalendarCell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export interface CalendarCellAria {
*/
export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarState | RangeCalendarState, ref: RefObject<HTMLElement | null>): CalendarCellAria {
let {date, isDisabled} = props;
let {errorMessageId, selectedDateDescription} = hookData.get(state);
let {errorMessageId, selectedDateDescription} = hookData.get(state)!;
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/calendar');
let dateFormatter = useDateFormatter({
weekday: 'long',
Expand All @@ -89,7 +89,7 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
isDisabled = isDisabled || state.isCellDisabled(date);
let isUnavailable = state.isCellUnavailable(date);
let isSelectable = !isDisabled && !isUnavailable;
let isInvalid = state.isValueInvalid && (
let isInvalid = state.isValueInvalid && Boolean(
'highlightedRange' in state
? !state.anchorDate && state.highlightedRange && date.compare(state.highlightedRange.start) >= 0 && date.compare(state.highlightedRange.end) <= 0
: state.value && isSameDay(state.value, date)
Expand Down Expand Up @@ -159,7 +159,7 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta

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

let startDragging = () => {
state.setDragging(true);
touchDragTimerRef.current = null;
touchDragTimerRef.current = undefined;

state.selectDate(date);
state.setFocusedDate(date);
Expand All @@ -215,7 +215,7 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
isRangeBoundaryPressed.current = false;
isAnchorPressed.current = false;
clearTimeout(touchDragTimerRef.current);
touchDragTimerRef.current = null;
touchDragTimerRef.current = undefined;
},
onPress() {
// For non-range selection, always select on press up.
Expand Down Expand Up @@ -269,7 +269,7 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
}
});

let tabIndex = null;
let tabIndex: number | undefined = undefined;
if (!isDisabled) {
tabIndex = isSameDay(date, state.focusedDate) ? 0 : -1;
}
Expand Down Expand Up @@ -298,14 +298,14 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
calendar: date.calendar.identifier
});

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

return {
cellProps: {
role: 'gridcell',
'aria-disabled': !isSelectable || null,
'aria-selected': isSelected || null,
'aria-invalid': isInvalid || null
'aria-disabled': !isSelectable || undefined,
'aria-selected': isSelected || undefined,
'aria-invalid': isInvalid || undefined
},
buttonProps: mergeProps(pressProps, {
onFocus() {
Expand All @@ -315,11 +315,11 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
},
tabIndex,
role: 'button',
'aria-disabled': !isSelectable || null,
'aria-disabled': !isSelectable || undefined,
'aria-label': label,
'aria-invalid': isInvalid || null,
'aria-invalid': isInvalid || undefined,
'aria-describedby': [
isInvalid ? errorMessageId : null,
isInvalid ? errorMessageId : undefined,
descriptionProps['aria-describedby']
].filter(Boolean).join(' ') || undefined,
onPointerEnter(e) {
Expand Down
6 changes: 3 additions & 3 deletions packages/@react-aria/calendar/src/useCalendarGrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export function useCalendarGrid(props: AriaCalendarGridProps, state: CalendarSta

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

let {ariaLabel, ariaLabelledBy} = hookData.get(state);
let {ariaLabel, ariaLabelledBy} = hookData.get(state)!;
let labelProps = useLabels({
'aria-label': [ariaLabel, visibleRangeDescription].filter(Boolean).join(', '),
'aria-labelledby': ariaLabelledBy
Expand All @@ -148,8 +148,8 @@ export function useCalendarGrid(props: AriaCalendarGridProps, state: CalendarSta
return {
gridProps: mergeProps(labelProps, {
role: 'grid',
'aria-readonly': state.isReadOnly || null,
'aria-disabled': state.isDisabled || null,
'aria-readonly': state.isReadOnly || undefined,
'aria-disabled': state.isDisabled || undefined,
'aria-multiselectable': ('highlightedRange' in state) || undefined,
onKeyDown,
onFocus: () => state.setFocused(true),
Expand Down
10 changes: 5 additions & 5 deletions packages/@react-aria/calendar/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,26 @@ import {useDateFormatter, useLocalizedStringFormatter} from '@react-aria/i18n';
import {useMemo} from 'react';

interface HookData {
ariaLabel: string,
ariaLabelledBy: string,
ariaLabel?: string,
ariaLabelledBy?: string,
errorMessageId: string,
selectedDateDescription: string
}

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

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

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

let start: CalendarDate, end: CalendarDate;
let start: CalendarDate | undefined, end: CalendarDate | undefined;
if ('highlightedRange' in state) {
({start, end} = state.highlightedRange || {});
} else {
start = end = state.value;
start = end = state.value ?? undefined;
}

let dateFormatter = useDateFormatter({
Expand Down
10 changes: 5 additions & 5 deletions packages/@react-aria/calendar/stories/Example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
import {Button} from '@react-spectrum/button';
import {CalendarState, RangeCalendarState, useCalendarState} from '@react-stately/calendar';
import {createCalendar, DateDuration, getWeeksInMonth, startOfWeek} from '@internationalized/date';
import React, {useMemo, useRef} from 'react';
import React, {ReactElement, useMemo, useRef} from 'react';
import {useCalendar, useCalendarCell, useCalendarGrid} from '../src';
import {useDateFormatter, useLocale} from '@react-aria/i18n';


export function Example(props) {
let {locale} = useLocale();
const {visibleDuration} = props;

let state = useCalendarState({
...props,
locale,
Expand All @@ -35,7 +35,7 @@ export function Example(props) {
gridCount = visibleDuration.months;
}

let components = [];
let components: Array<ReactElement> = [];
for (let i = 0; i < gridCount; i++) {
components.push(<CalendarGrid key={i} state={state} visibleDuration={visibleDuration} offset={{months: i}} />);
}
Expand Down Expand Up @@ -78,11 +78,11 @@ function CalendarGrid({state, visibleDuration, offset = {}}: {state: CalendarSta
</div>
))}
</div>);

}

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

let dateFormatter = useDateFormatter({
Expand Down
24 changes: 13 additions & 11 deletions packages/@react-aria/combobox/src/useComboBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ export function useComboBox<T>(props: AriaComboBoxOptions<T>, state: ComboBoxSta
isReadOnly,
isDisabled
} = props;
let backupBtnRef = useRef(null);
buttonRef = buttonRef ?? backupBtnRef;

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

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

let onPressStart = (e: PressEvent) => {
if (e.pointerType !== 'touch') {
inputRef.current.focus();
inputRef.current?.focus();
state.toggle((e.pointerType === 'keyboard' || e.pointerType === 'virtual') ? 'first' : null, 'manual');
}
};
Expand All @@ -251,7 +253,7 @@ export function useComboBox<T>(props: AriaComboBoxOptions<T>, state: ComboBoxSta
// Sometimes VoiceOver on iOS fires two touchend events in quick succession. Ignore the second one.
if (e.timeStamp - lastEventTime.current < 500) {
e.preventDefault();
inputRef.current.focus();
inputRef.current?.focus();
return;
}

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

if (touch.clientX === centerX && touch.clientY === centerY) {
e.preventDefault();
inputRef.current.focus();
inputRef.current?.focus();
state.toggle(null, 'manual');

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

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

useEffect(() => {
if (state.isOpen) {
return ariaHideOutside([inputRef.current, popoverRef.current]);
return ariaHideOutside([inputRef.current, popoverRef.current].filter(element => element != null));
}
}, [state.isOpen, inputRef, popoverRef]);

Expand Down
2 changes: 1 addition & 1 deletion packages/@react-aria/combobox/stories/example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ function ListBox(props) {
}

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

let backgroundColor;
Expand Down
6 changes: 3 additions & 3 deletions packages/@react-aria/datepicker/src/useDateField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ export interface DateFieldAria extends ValidationResult {

// Data that is passed between useDateField and useDateSegment.
interface HookData {
ariaLabel: string,
ariaLabelledBy: string,
ariaDescribedBy: string,
ariaLabel?: string,
ariaLabelledBy?: string,
ariaDescribedBy?: string,
focusManager: FocusManager
}

Expand Down
3 changes: 3 additions & 0 deletions packages/@react-aria/datepicker/src/useDatePickerGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ export function useDatePickerGroup(state: DatePickerState | DateRangePickerState

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