diff --git a/packages/@react-aria/dnd/src/index.ts b/packages/@react-aria/dnd/src/index.ts index c72126cd021..76e0f8a6735 100644 --- a/packages/@react-aria/dnd/src/index.ts +++ b/packages/@react-aria/dnd/src/index.ts @@ -65,3 +65,4 @@ export {useClipboard} from './useClipboard'; export {DragPreview} from './DragPreview'; export {ListDropTargetDelegate} from './ListDropTargetDelegate'; export {isVirtualDragging} from './DragManager'; +export {isDirectoryDropItem, isFileDropItem, isTextDropItem} from './utils'; diff --git a/packages/@react-aria/dnd/src/utils.ts b/packages/@react-aria/dnd/src/utils.ts index 866602471cc..c46e495dc8a 100644 --- a/packages/@react-aria/dnd/src/utils.ts +++ b/packages/@react-aria/dnd/src/utils.ts @@ -11,7 +11,7 @@ */ import {CUSTOM_DRAG_TYPE, DROP_OPERATION, GENERIC_TYPE, NATIVE_DRAG_TYPES} from './constants'; -import {DirectoryDropItem, DragItem, DropItem, FileDropItem, DragTypes as IDragTypes} from '@react-types/shared'; +import {DirectoryDropItem, DragItem, DropItem, FileDropItem, DragTypes as IDragTypes, TextDropItem} from '@react-types/shared'; import {DroppableCollectionState} from '@react-stately/dnd'; import {getInteractionModality, useInteractionModality} from '@react-aria/interactions'; import {Key, RefObject} from 'react'; @@ -314,6 +314,21 @@ function getEntryFile(entry: FileSystemFileEntry): Promise { return new Promise((resolve, reject) => entry.file(resolve, reject)); } +/** Returns whether a drop item contains text data. */ +export function isTextDropItem(dropItem: DropItem): dropItem is TextDropItem { + return dropItem.kind === 'text'; +} + +/** Returns whether a drop item is a file. */ +export function isFileDropItem(dropItem: DropItem): dropItem is FileDropItem { + return dropItem.kind === 'file'; +} + +/** Returns whether a drop item is a directory. */ +export function isDirectoryDropItem(dropItem: DropItem): dropItem is DirectoryDropItem { + return dropItem.kind === 'directory'; +} + // Global DnD collection state tracker. export interface DnDState { /** A ref for the of the drag items in the current drag session if any. */ diff --git a/packages/@react-aria/listbox/src/useOption.ts b/packages/@react-aria/listbox/src/useOption.ts index 0f2e9c2b919..8c5a45059df 100644 --- a/packages/@react-aria/listbox/src/useOption.ts +++ b/packages/@react-aria/listbox/src/useOption.ts @@ -119,7 +119,7 @@ export function useOption(props: AriaOptionProps, state: ListState, ref: R } if (isVirtualized) { - let index = Number(state.collection.getItem(key).index); + let index = Number(state.collection.getItem(key)?.index); optionProps['aria-posinset'] = Number.isNaN(index) ? undefined : index + 1; optionProps['aria-setsize'] = getItemCount(state.collection); } diff --git a/packages/@react-aria/selection/src/ListKeyboardDelegate.ts b/packages/@react-aria/selection/src/ListKeyboardDelegate.ts index 7a14cc30a16..8f708c83e6c 100644 --- a/packages/@react-aria/selection/src/ListKeyboardDelegate.ts +++ b/packages/@react-aria/selection/src/ListKeyboardDelegate.ts @@ -36,6 +36,8 @@ export class ListKeyboardDelegate implements KeyboardDelegate { key = this.collection.getKeyAfter(key); } + + return null; } getKeyAbove(key: Key) { @@ -48,6 +50,8 @@ export class ListKeyboardDelegate implements KeyboardDelegate { key = this.collection.getKeyBefore(key); } + + return null; } getFirstKey() { @@ -60,6 +64,8 @@ export class ListKeyboardDelegate implements KeyboardDelegate { key = this.collection.getKeyAfter(key); } + + return null; } getLastKey() { @@ -72,6 +78,8 @@ export class ListKeyboardDelegate implements KeyboardDelegate { key = this.collection.getKeyBefore(key); } + + return null; } private getItem(key: Key): HTMLElement { @@ -89,7 +97,7 @@ export class ListKeyboardDelegate implements KeyboardDelegate { while (item && item.offsetTop > pageY) { key = this.getKeyAbove(key); - item = this.getItem(key); + item = key == null ? null : this.getItem(key); } return key; @@ -106,7 +114,7 @@ export class ListKeyboardDelegate implements KeyboardDelegate { while (item && item.offsetTop < pageY) { key = this.getKeyBelow(key); - item = this.getItem(key); + item = key == null ? null : this.getItem(key); } return key; diff --git a/packages/@react-aria/tabs/src/index.ts b/packages/@react-aria/tabs/src/index.ts index 6a75ea01d1a..e36be83acab 100644 --- a/packages/@react-aria/tabs/src/index.ts +++ b/packages/@react-aria/tabs/src/index.ts @@ -16,4 +16,4 @@ export type {AriaTabListProps, AriaTabPanelProps, AriaTabProps} from '@react-typ export type {Orientation} from '@react-types/shared'; export type {TabAria} from './useTab'; export type {TabPanelAria} from './useTabPanel'; -export type {TabListAria} from './useTabList'; +export type {AriaTabListOptions, TabListAria} from './useTabList'; diff --git a/packages/@react-aria/tabs/src/useTabList.ts b/packages/@react-aria/tabs/src/useTabList.ts index b241126985e..e69e0fd51a6 100644 --- a/packages/@react-aria/tabs/src/useTabList.ts +++ b/packages/@react-aria/tabs/src/useTabList.ts @@ -20,17 +20,18 @@ import {TabsKeyboardDelegate} from './TabsKeyboardDelegate'; import {useLocale} from '@react-aria/i18n'; import {useSelectableCollection} from '@react-aria/selection'; +export interface AriaTabListOptions extends Omit, 'children'> {} + export interface TabListAria { /** Props for the tablist container. */ tabListProps: DOMAttributes } - /** * Provides the behavior and accessibility implementation for a tab list. * Tabs organize content into multiple sections and allow users to navigate between them. */ -export function useTabList(props: AriaTabListProps, state: TabListState, ref: RefObject): TabListAria { +export function useTabList(props: AriaTabListOptions, state: TabListState, ref: RefObject): TabListAria { let { orientation = 'horizontal', keyboardActivation = 'automatic' diff --git a/packages/@react-aria/utils/src/mergeProps.ts b/packages/@react-aria/utils/src/mergeProps.ts index 9fdb5a1c017..5d7444e20f5 100644 --- a/packages/@react-aria/utils/src/mergeProps.ts +++ b/packages/@react-aria/utils/src/mergeProps.ts @@ -18,8 +18,11 @@ interface Props { [key: string]: any } +type PropsArg = Props | null | undefined; + // taken from: https://stackoverflow.com/questions/51603250/typescript-3-parameter-list-intersection-type/51604379#51604379 -type TupleTypes = { [P in keyof T]: T[P] } extends { [key: number]: infer V } ? V : never; +type TupleTypes = { [P in keyof T]: T[P] } extends { [key: number]: infer V } ? NullToObject : never; +type NullToObject = T extends (null | undefined) ? {} : T; // eslint-disable-next-line no-undef, @typescript-eslint/no-unused-vars type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; @@ -30,7 +33,7 @@ type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( * For all other props, the last prop object overrides all previous ones. * @param args - Multiple sets of props to merge together. */ -export function mergeProps(...args: T): UnionToIntersection> { +export function mergeProps(...args: T): UnionToIntersection> { // Start with a base clone of the first argument. This is a lot faster than starting // with an empty object and adding properties as we go. let result: Props = {...args[0]}; diff --git a/packages/@react-stately/calendar/src/useCalendarState.ts b/packages/@react-stately/calendar/src/useCalendarState.ts index e9024962f02..fe6aab36958 100644 --- a/packages/@react-stately/calendar/src/useCalendarState.ts +++ b/packages/@react-stately/calendar/src/useCalendarState.ts @@ -32,7 +32,7 @@ import {CalendarState} from './types'; import {useControlledState} from '@react-stately/utils'; import {useMemo, useRef, useState} from 'react'; -export interface CalendarStateOptions extends CalendarProps { +export interface CalendarStateOptions extends CalendarProps { /** The locale to display and edit the value according to. */ locale: string, /** @@ -55,7 +55,7 @@ export interface CalendarStateOptions extends CalendarProps { * Provides state management for a calendar component. * A calendar displays one or more date grids and allows users to select a single date. */ -export function useCalendarState(props: CalendarStateOptions): CalendarState { +export function useCalendarState(props: CalendarStateOptions): CalendarState { let defaultFormatter = useMemo(() => new DateFormatter(props.locale), [props.locale]); let resolvedOptions = useMemo(() => defaultFormatter.resolvedOptions(), [defaultFormatter]); let { diff --git a/packages/@react-stately/calendar/src/useRangeCalendarState.ts b/packages/@react-stately/calendar/src/useRangeCalendarState.ts index cb21224246e..2bf7e8bb549 100644 --- a/packages/@react-stately/calendar/src/useRangeCalendarState.ts +++ b/packages/@react-stately/calendar/src/useRangeCalendarState.ts @@ -20,7 +20,7 @@ import {useCalendarState} from './useCalendarState'; import {useControlledState} from '@react-stately/utils'; import {useMemo, useRef, useState} from 'react'; -export interface RangeCalendarStateOptions extends RangeCalendarProps { +export interface RangeCalendarStateOptions extends RangeCalendarProps { /** The locale to display and edit the value according to. */ locale: string, /** @@ -41,7 +41,7 @@ export interface RangeCalendarStateOptions extends RangeCalendarProps * Provides state management for a range calendar component. * A range calendar displays one or more date grids and allows users to select a contiguous range of dates. */ -export function useRangeCalendarState(props: RangeCalendarStateOptions): RangeCalendarState { +export function useRangeCalendarState(props: RangeCalendarStateOptions): RangeCalendarState { let {value: valueProp, defaultValue, onChange, createCalendar, locale, visibleDuration = {months: 1}, minValue, maxValue, ...calendarProps} = props; let [value, setValue] = useControlledState( valueProp, diff --git a/packages/@react-stately/datepicker/src/useDateFieldState.ts b/packages/@react-stately/datepicker/src/useDateFieldState.ts index 97315086ae4..ad63325ca7c 100644 --- a/packages/@react-stately/datepicker/src/useDateFieldState.ts +++ b/packages/@react-stately/datepicker/src/useDateFieldState.ts @@ -115,7 +115,7 @@ const TYPE_MAPPING = { dayperiod: 'dayPeriod' }; -export interface DateFieldStateOptions extends DatePickerProps { +export interface DateFieldStateOptions extends DatePickerProps { /** * The maximum unit to display in the date field. * @default 'year' @@ -137,7 +137,7 @@ export interface DateFieldStateOptions extends DatePickerProps { * A date field allows users to enter and edit date and time values using a keyboard. * Each part of a date value is displayed in an individually editable segment. */ -export function useDateFieldState(props: DateFieldStateOptions): DateFieldState { +export function useDateFieldState(props: DateFieldStateOptions): DateFieldState { let { locale, createCalendar, diff --git a/packages/@react-stately/datepicker/src/useDateRangePickerState.ts b/packages/@react-stately/datepicker/src/useDateRangePickerState.ts index 13333a37072..36a6105935f 100644 --- a/packages/@react-stately/datepicker/src/useDateRangePickerState.ts +++ b/packages/@react-stately/datepicker/src/useDateRangePickerState.ts @@ -18,7 +18,7 @@ import {RangeValue, ValidationState} from '@react-types/shared'; import {useControlledState} from '@react-stately/utils'; import {useState} from 'react'; -export interface DateRangePickerStateOptions extends DateRangePickerProps { +export interface DateRangePickerStateOptions extends DateRangePickerProps { /** * Determines whether the date picker popover should close automatically when a date is selected. * @default true @@ -71,7 +71,7 @@ export interface DateRangePickerState extends OverlayTriggerState { * A date range picker combines two DateFields and a RangeCalendar popover to allow * users to enter or select a date and time range. */ -export function useDateRangePickerState(props: DateRangePickerStateOptions): DateRangePickerState { +export function useDateRangePickerState(props: DateRangePickerStateOptions): DateRangePickerState { let overlayState = useOverlayTriggerState(props); let [controlledValue, setControlledValue] = useControlledState(props.value, props.defaultValue || null, props.onChange); let [placeholderValue, setPlaceholderValue] = useState(() => controlledValue || {start: null, end: null}); diff --git a/packages/@react-stately/datepicker/src/useTimeFieldState.ts b/packages/@react-stately/datepicker/src/useTimeFieldState.ts index 0c73068164f..e547583480c 100644 --- a/packages/@react-stately/datepicker/src/useTimeFieldState.ts +++ b/packages/@react-stately/datepicker/src/useTimeFieldState.ts @@ -16,7 +16,7 @@ import {getLocalTimeZone, GregorianCalendar, Time, toCalendarDateTime, today, to import {useControlledState} from '@react-stately/utils'; import {useMemo} from 'react'; -export interface TimeFieldStateOptions extends TimePickerProps { +export interface TimeFieldStateOptions extends TimePickerProps { /** The locale to display and edit the value according to. */ locale: string } @@ -26,7 +26,7 @@ export interface TimeFieldStateOptions extends TimePickerProps { * A time field allows users to enter and edit time values using a keyboard. * Each part of a time value is displayed in an individually editable segment. */ -export function useTimeFieldState(props: TimeFieldStateOptions): DateFieldState { +export function useTimeFieldState(props: TimeFieldStateOptions): DateFieldState { let { placeholderValue = new Time(), minValue, diff --git a/packages/@react-types/calendar/src/index.d.ts b/packages/@react-types/calendar/src/index.d.ts index 84382eefa94..d07946a1581 100644 --- a/packages/@react-types/calendar/src/index.d.ts +++ b/packages/@react-types/calendar/src/index.d.ts @@ -56,8 +56,8 @@ export interface CalendarPropsBase { } export type DateRange = RangeValue; -export interface CalendarProps extends CalendarPropsBase, ValueBase> {} -export interface RangeCalendarProps extends CalendarPropsBase, ValueBase, RangeValue>> { +export interface CalendarProps extends CalendarPropsBase, ValueBase> {} +export interface RangeCalendarProps extends CalendarPropsBase, ValueBase | null, RangeValue>> { /** * When combined with `isDateUnavailable`, determines whether non-contiguous ranges, * i.e. ranges containing unavailable dates, may be selected. diff --git a/packages/@react-types/datepicker/src/index.d.ts b/packages/@react-types/datepicker/src/index.d.ts index 5fbe9cffabe..6d7aa8404d8 100644 --- a/packages/@react-types/datepicker/src/index.d.ts +++ b/packages/@react-types/datepicker/src/index.d.ts @@ -55,17 +55,17 @@ interface DateFieldBase extends InputBase, Validation, Focu } interface AriaDateFieldBaseProps extends DateFieldBase, AriaLabelingProps, DOMProps {} -export interface DateFieldProps extends DateFieldBase, ValueBase> {} +export interface DateFieldProps extends DateFieldBase, ValueBase> {} export interface AriaDateFieldProps extends DateFieldProps, AriaDateFieldBaseProps {} interface DatePickerBase extends DateFieldBase, OverlayTriggerProps {} export interface AriaDatePickerBaseProps extends DatePickerBase, AriaLabelingProps, DOMProps {} -export interface DatePickerProps extends DatePickerBase, ValueBase> {} +export interface DatePickerProps extends DatePickerBase, ValueBase> {} export interface AriaDatePickerProps extends DatePickerProps, AriaDatePickerBaseProps {} export type DateRange = RangeValue; -export interface DateRangePickerProps extends DatePickerBase, ValueBase, RangeValue>> { +export interface DateRangePickerProps extends DatePickerBase, ValueBase | null, RangeValue>> { /** * When combined with `isDateUnavailable`, determines whether non-contiguous ranges, * i.e. ranges containing unavailable dates, may be selected. @@ -112,7 +112,7 @@ type MappedTimeValue = T extends Time ? Time : never; -export interface TimePickerProps extends InputBase, Validation, FocusableProps, LabelableProps, HelpTextProps, ValueBase> { +export interface TimePickerProps extends InputBase, Validation, FocusableProps, LabelableProps, HelpTextProps, ValueBase> { /** Whether to display the time in 12 or 24 hour format. By default, this is determined by the user's locale. */ hourCycle?: 12 | 24, /** diff --git a/packages/@react-types/shared/src/collections.d.ts b/packages/@react-types/shared/src/collections.d.ts index 126386e0eb8..a8769e06d71 100644 --- a/packages/@react-types/shared/src/collections.d.ts +++ b/packages/@react-types/shared/src/collections.d.ts @@ -133,10 +133,10 @@ export interface Collection extends Iterable { getKeys(): Iterable, /** Get an item by its key. */ - getItem(key: Key): T, + getItem(key: Key): T | null, /** Get an item by the index of its key. */ - at(idx: number): T, + at(idx: number): T | null, /** Get the key that comes before the given key in the collection. */ getKeyBefore(key: Key): Key | null, @@ -160,7 +160,7 @@ export interface Node { /** A unique key for the node. */ key: Key, /** The object value the node was created from. */ - value: T, + value: T | null, /** The level of depth this node is at in the heirarchy. */ level: number, /** Whether this item has children, even if not loaded yet. */ @@ -181,11 +181,11 @@ export interface Node { /** A function that should be called to wrap the rendered node. */ wrapper?: (element: ReactElement) => ReactElement, /** The key of the parent node. */ - parentKey?: Key, + parentKey?: Key | null, /** The key of the node before this node. */ - prevKey?: Key, + prevKey?: Key | null, /** The key of the node after this node. */ - nextKey?: Key, + nextKey?: Key | null, /** Additional properties specific to a particular node type. */ props?: any, /** @private */ diff --git a/packages/react-aria-components/docs/Breadcrumbs.mdx b/packages/react-aria-components/docs/Breadcrumbs.mdx index 601f6c5ebbf..bb8ebd2ae81 100644 --- a/packages/react-aria-components/docs/Breadcrumbs.mdx +++ b/packages/react-aria-components/docs/Breadcrumbs.mdx @@ -247,7 +247,7 @@ function Example() { {id: 3, label: 'March 2022 Assets'} ]); - let navigate = (item) => { + let navigate = (item: typeof breadcrumbs[0]) => { setBreadcrumbs(breadcrumbs.slice(0, breadcrumbs.indexOf(item) + 1)); }; diff --git a/packages/react-aria-components/docs/Button.mdx b/packages/react-aria-components/docs/Button.mdx index a69f0541e48..1ac8d2e476b 100644 --- a/packages/react-aria-components/docs/Button.mdx +++ b/packages/react-aria-components/docs/Button.mdx @@ -191,13 +191,13 @@ Each of these handlers receives a

{pointerType ? `You are pressing the button with a ${pointerType}!` : 'Ready to be pressed.'}

diff --git a/packages/react-aria-components/docs/Calendar.mdx b/packages/react-aria-components/docs/Calendar.mdx index ba96b296379..f4e459f06ea 100644 --- a/packages/react-aria-components/docs/Calendar.mdx +++ b/packages/react-aria-components/docs/Calendar.mdx @@ -473,7 +473,7 @@ The below example displays a `Calendar` in the Hindi language, using the Indian import {I18nProvider} from '@react-aria/i18n'; function Example() { - let [date, setDate] = React.useState(null); + let [date, setDate] = React.useState(null); return ( @@ -515,7 +515,7 @@ function Example() { ]; let {locale} = useLocale(); - let isDateUnavailable = (date) => isWeekend(date, locale) || disabledRanges.some((interval) => date.compare(interval[0]) >= 0 && date.compare(interval[1]) <= 0); + let isDateUnavailable = (date: DateValue) => isWeekend(date, locale) || disabledRanges.some((interval) => date.compare(interval[0]) >= 0 && date.compare(interval[1]) <= 0); return } @@ -580,7 +580,7 @@ function Example() { value={date} onChange={setDate} validationState={isInvalid ? 'invalid' : 'valid'} - errorMessage={isInvalid ? 'We are closed on weekends' : null} /> + errorMessage={isInvalid ? 'We are closed on weekends' : undefined} /> ); } ``` diff --git a/packages/react-aria-components/docs/CheckboxGroup.mdx b/packages/react-aria-components/docs/CheckboxGroup.mdx index 0f17f4a9b53..4bfd8b6f9f1 100644 --- a/packages/react-aria-components/docs/CheckboxGroup.mdx +++ b/packages/react-aria-components/docs/CheckboxGroup.mdx @@ -436,10 +436,10 @@ prop to `"invalid"` when no options are selected and removes it otherwise. ```tsx example function Example() { - let [selected, setSelected] = React.useState([]); + let [selected, setSelected] = React.useState([]); return ( - + Lettuce Tomato Onion @@ -460,13 +460,13 @@ indicates that the group is required, not any individual option. In addition, `v ```tsx example function Example() { - let [selected, setSelected] = React.useState([]); + let [selected, setSelected] = React.useState([]); return ( - Terms and conditions - Privacy policy - Cookie policy + Terms and conditions + Privacy policy + Cookie policy ); } @@ -487,8 +487,8 @@ function Example() { onChange={setChecked} value={checked} validationState={isValid ? 'valid' : 'invalid'} - description={isValid ? 'Select your pets.' : null} - errorMessage={isValid ? null : checked.includes('cats') + description={isValid ? 'Select your pets.' : undefined} + errorMessage={isValid ? undefined : checked.includes('cats') ? 'No cats allowed.' : 'Select only dogs and dragons.'} > diff --git a/packages/react-aria-components/docs/ComboBox.mdx b/packages/react-aria-components/docs/ComboBox.mdx index 7b22a780f5d..8d6d233693a 100644 --- a/packages/react-aria-components/docs/ComboBox.mdx +++ b/packages/react-aria-components/docs/ComboBox.mdx @@ -778,7 +778,7 @@ function ControlledComboBox() { // Store ComboBox input value, selected option, open state, and items // in a state tracker let [fieldState, setFieldState] = React.useState({ - selectedKey: '', + selectedKey: '' as React.Key, inputValue: '', items: optionList }); @@ -789,7 +789,7 @@ function ControlledComboBox() { // Specify how each of the ComboBox values should change when an // option is selected from the list box - let onSelectionChange = (key) => { + let onSelectionChange = (key: React.Key) => { setFieldState(prevState => { let selectedItem = prevState.items.find(option => option.id === key); return ({ @@ -802,7 +802,7 @@ function ControlledComboBox() { // Specify how each of the ComboBox values should change when the input // field is altered by the user - let onInputChange = (value) => { + let onInputChange = (value: string) => { setFieldState(prevState => ({ inputValue: value, selectedKey: value === '' ? '' : prevState.selectedKey, @@ -811,7 +811,7 @@ function ControlledComboBox() { }; // Show entire list if user opens the menu manually - let onOpenChange = (isOpen, menuTrigger) => { + let onOpenChange = (isOpen: boolean, menuTrigger?: string) => { if (menuTrigger === 'manual' && isOpen) { setFieldState(prevState => ({ inputValue: prevState.inputValue, diff --git a/packages/react-aria-components/docs/DateField.mdx b/packages/react-aria-components/docs/DateField.mdx index a19139b6e96..ee707264944 100644 --- a/packages/react-aria-components/docs/DateField.mdx +++ b/packages/react-aria-components/docs/DateField.mdx @@ -471,7 +471,7 @@ The below example displays a `DateField` in the Hindi language, using the Indian import {I18nProvider} from '@react-aria/i18n'; function Example() { - let [date, setDate] = React.useState(null); + let [date, setDate] = React.useState(null); return ( @@ -517,8 +517,8 @@ function Example() { value={date} onChange={setDate} validationState={isInvalid ? 'invalid' : 'valid'} - description={isInvalid ? null : 'Select a weekday'} - errorMessage={isInvalid ? 'We are closed on weekends' : null} /> + description={isInvalid ? undefined : 'Select a weekday'} + errorMessage={isInvalid ? 'We are closed on weekends' : undefined} /> ); } ``` diff --git a/packages/react-aria-components/docs/DatePicker.mdx b/packages/react-aria-components/docs/DatePicker.mdx index 01a42066d81..75c18e1e92e 100644 --- a/packages/react-aria-components/docs/DatePicker.mdx +++ b/packages/react-aria-components/docs/DatePicker.mdx @@ -845,7 +845,7 @@ The below example displays a `DatePicker` in the Hindi language, using the India import {I18nProvider} from '@react-aria/i18n'; function Example() { - let [date, setDate] = React.useState(null); + let [date, setDate] = React.useState(null); return ( @@ -889,7 +889,7 @@ function Example() { ]; let {locale} = useLocale(); - let isDateUnavailable = (date) => isWeekend(date, locale) || disabledRanges.some((interval) => date.compare(interval[0]) >= 0 && date.compare(interval[1]) <= 0); + let isDateUnavailable = (date: DateValue) => isWeekend(date, locale) || disabledRanges.some((interval) => date.compare(interval[0]) >= 0 && date.compare(interval[1]) <= 0); return } diff --git a/packages/react-aria-components/docs/DateRangePicker.mdx b/packages/react-aria-components/docs/DateRangePicker.mdx index e4127068fd1..239ec74eea4 100644 --- a/packages/react-aria-components/docs/DateRangePicker.mdx +++ b/packages/react-aria-components/docs/DateRangePicker.mdx @@ -903,7 +903,7 @@ import type {DateRange} from 'react-aria-components'; import {I18nProvider} from '@react-aria/i18n'; function Example() { - let [range, setRange] = React.useState(null); + let [range, setRange] = React.useState(null); return ( @@ -952,9 +952,9 @@ function Example() { [now.add({days: 23}), now.add({days: 24})], ]; - let isDateUnavailable = (date) => disabledRanges.some((interval) => date.compare(interval[0]) >= 0 && date.compare(interval[1]) <= 0); - let [value, setValue] = React.useState(null); - let isInvalid = value && disabledRanges.some(interval => value.end.compare(interval[0]) >= 0 && value.start.compare(interval[1]) <= 0); + let isDateUnavailable = (date: DateValue) => disabledRanges.some((interval) => date.compare(interval[0]) >= 0 && date.compare(interval[1]) <= 0); + let [value, setValue] = React.useState(null); + let isInvalid = disabledRanges.some(interval => value && value.end.compare(interval[0]) >= 0 && value.start.compare(interval[1]) <= 0); return ( + validationState={isInvalid ? 'invalid' : undefined} /> ); } ``` @@ -1007,8 +1007,8 @@ function Example() { value={range} onChange={setRange} validationState={isInvalid ? 'invalid' : 'valid'} - description={isInvalid ? null : 'Select your vacation dates'} - errorMessage={isInvalid ? 'Maximum stay duration is 1 week' : null} /> + description={isInvalid ? undefined : 'Select your vacation dates'} + errorMessage={isInvalid ? 'Maximum stay duration is 1 week' : undefined} /> ); } ``` diff --git a/packages/react-aria-components/docs/GridList.mdx b/packages/react-aria-components/docs/GridList.mdx index 3216a666724..de9af8abc36 100644 --- a/packages/react-aria-components/docs/GridList.mdx +++ b/packages/react-aria-components/docs/GridList.mdx @@ -846,7 +846,7 @@ function DraggableGridList() { /*- begin highlight -*/ getItems(keys) { return [...keys].map(key => { - let item = items.get(key as string); + let item = items.get(key as string)!; return { 'text/plain': item.name, 'text/html': `<${item.style}>${item.name}`, @@ -876,8 +876,13 @@ function DraggableGridList() { Dropping on the GridList as a whole can be enabled using the `onRootDrop` event. When a valid drag hovers over the GridList, it receives the `isDropTarget` state and can be styled using the `[data-drop-target]` CSS selector. ```tsx example +interface Item { + id: number, + name: string +} + function Example() { - let [items, setItems] = React.useState([]); + let [items, setItems] = React.useState([]); let { dragAndDropHooks } = useDragAndDrop({ /*- begin highlight -*/ @@ -1091,17 +1096,25 @@ A ([]); let { dragAndDropHooks } = useDragAndDrop({ /*- begin highlight -*/ acceptedDragTypes: ['custom-app-type'], async onRootDrop(e) { let items = await Promise.all( - e.items.map(async (item: TextDropItem) => JSON.parse(await item.getText('custom-app-type'))) + e.items + .filter(isTextDropItem) + .map(async item => JSON.parse(await item.getText('custom-app-type'))) ); setItems(items); } @@ -1110,7 +1123,7 @@ function DroppableGridList() { return ( "Drop items here"}> - {({name, style: Tag = 'span'}) => {name}} + {item => {React.createElement(item.style, null, item.name)}} ); } @@ -1129,17 +1142,23 @@ A ([]); let { dragAndDropHooks } = useDragAndDrop({ /*- begin highlight -*/ acceptedDragTypes: ['image/jpeg', 'image/png'], async onRootDrop(e) { let items = await Promise.all( - e.items.map(async (item: FileDropItem) => ({ + e.items.filter(isFileDropItem).map(async item => ({ id: Math.random(), url: URL.createObjectURL(await item.getFile()), name: item.name @@ -1200,23 +1219,28 @@ The `getEntries` method returns an [async iterable](https://developer.mozilla.or This example accepts directory drops over the whole collection, and renders the contents as items in the list. `DIRECTORY_DRAG_TYPE` is imported from `react-aria-components` and included in the `acceptedDragTypes` prop to limit the accepted items to only directories. ```tsx example -import type {DirectoryDropItem} from 'react-aria-components'; import File from '@spectrum-icons/workflow/FileTxt'; import Folder from '@spectrum-icons/workflow/Folder'; ///- begin highlight -/// -import {DIRECTORY_DRAG_TYPE} from 'react-aria-components'; +import {DIRECTORY_DRAG_TYPE, isDirectoryDropItem} from 'react-aria-components'; ///- end highlight -/// +interface DirItem { + name: string, + kind: string +} + function Example() { - let [files, setFiles] = React.useState([]); + let [files, setFiles] = React.useState([]); let { dragAndDropHooks } = useDragAndDrop({ /*- begin highlight -*/ acceptedDragTypes: [DIRECTORY_DRAG_TYPE], async onRootDrop(e) { // Read entries in directory and update state with relevant info. + let dir = e.items.find(isDirectoryDropItem)!; let files = []; - for await (let entry of (e.items[0] as DirectoryDropItem).getEntries()) { + for await (let entry of dir.getEntries()) { files.push({ name: entry.name, kind: entry.kind @@ -1399,8 +1423,15 @@ The `getDropOperation` function passed to `useDragAndDrop` can be used to provid In the below example, the drop target only supports dropping PNG images. If a PNG is dragged over the target, it will be highlighted and the operating system displays a copy cursor. If another type is dragged over the target, then there is no visual feedback, indicating that a drop is not accepted there. If the user holds a modifier key such as Control while dragging over the drop target in order to change the drop operation, then the drop target does not accept the drop. ```tsx example +///- begin collapse -/// +interface ImageItem { + id: number, + url: string, + name: string +} +///- end collapse -/// function Example() { - let [items, setItems] = React.useState([]); + let [items, setItems] = React.useState([]); let { dragAndDropHooks } = useDragAndDrop({ /*- begin highlight -*/ @@ -1410,7 +1441,7 @@ function Example() { async onRootDrop(e) { ///- begin collapse -/// let items = await Promise.all( - e.items.map(async (item: FileDropItem) => ({ + e.items.filter(isFileDropItem).map(async item => ({ id: Math.random(), url: URL.createObjectURL(await item.getFile()), name: item.name @@ -1468,7 +1499,7 @@ let onItemDrop = async (e) => { This example puts together many of the concepts described above, allowing users to drag items between lists bidirectionally. It also supports reordering items within the same list. When a list is empty, it accepts drops on the whole collection. `getDropOperation` ensures that items are always moved rather than copied, which avoids duplicate items. ```tsx example -import type {TextDropItem} from 'react-aria-components'; +import {isTextDropItem} from 'react-aria-components'; interface FileItem { id: string, @@ -1517,7 +1548,9 @@ function DndGridList(props: DndGridListProps) { // Handle drops between items from other lists. async onInsert(e) { let processedItems = await Promise.all( - e.items.map(async (item: TextDropItem) => JSON.parse(await item.getText('custom-app-type'))) + e.items + .filter(isTextDropItem) + .map(async item => JSON.parse(await item.getText('custom-app-type'))) ); if (e.target.dropPosition === 'before') { list.insertBefore(e.target.key, ...processedItems); @@ -1529,7 +1562,9 @@ function DndGridList(props: DndGridListProps) { // Handle drops on the collection when empty. async onRootDrop(e) { let processedItems = await Promise.all( - e.items.map(async (item: TextDropItem) => JSON.parse(await item.getText('custom-app-type'))) + e.items + .filter(isTextDropItem) + .map(async item => JSON.parse(await item.getText('custom-app-type'))) ); list.append(...processedItems); }, diff --git a/packages/react-aria-components/docs/Link.mdx b/packages/react-aria-components/docs/Link.mdx index 2a74077f5ea..564de953d43 100644 --- a/packages/react-aria-components/docs/Link.mdx +++ b/packages/react-aria-components/docs/Link.mdx @@ -208,13 +208,13 @@ Each of these handlers receives a setPointerType(e.pointerType)} - onPressEnd={e => setPointerType(null)}> + onPressEnd={() => setPointerType('')}> Press me

{pointerType ? `You are pressing the link with a ${pointerType}!` : 'Ready to be pressed.'}

diff --git a/packages/react-aria-components/docs/ListBox.mdx b/packages/react-aria-components/docs/ListBox.mdx index 04ba5ba3bd1..bb1ee14e239 100644 --- a/packages/react-aria-components/docs/ListBox.mdx +++ b/packages/react-aria-components/docs/ListBox.mdx @@ -812,7 +812,7 @@ function DraggableListBox() { /*- begin highlight -*/ getItems(keys) { return [...keys].map(key => { - let item = items.get(key as string); + let item = items.get(key as string)!; return { 'text/plain': item.name, 'text/html': `<${item.style}>${item.name}`, @@ -842,8 +842,13 @@ function DraggableListBox() { Dropping on the ListBox as a whole can be enabled using the `onRootDrop` event. When a valid drag hovers over the ListBox, it receives the `isDropTarget` state and can be styled using the `[data-drop-target]` CSS selector. ```tsx example +interface Item { + id: number, + name: string +} + function Example() { - let [items, setItems] = React.useState([]); + let [items, setItems] = React.useState([]); let { dragAndDropHooks } = useDragAndDrop({ /*- begin highlight -*/ @@ -926,8 +931,6 @@ function Example() { Dropping between items can be enabled using the `onInsert` event. ListBox renders a between items to indicate the insertion position, which can be styled using the `.react-aria-DropIndicator` selector. When it is active, it receives the `[data-drop-target]` state. ```tsx example -import type {TextDropItem} from 'react-aria-components'; - function Example() { let list = useListData({ initialItems: [ @@ -1059,17 +1062,25 @@ A ([]); let { dragAndDropHooks } = useDragAndDrop({ /*- begin highlight -*/ acceptedDragTypes: ['custom-app-type'], async onRootDrop(e) { let items = await Promise.all( - e.items.map(async (item: TextDropItem) => JSON.parse(await item.getText('custom-app-type'))) + e.items + .filter(isTextDropItem) + .map(async item => JSON.parse(await item.getText('custom-app-type'))) ); setItems(items); } @@ -1078,7 +1089,7 @@ function DroppableListBox() { return ( "Drop items here"}> - {({name, style: Tag = 'span'}) => {name}} + {item => {React.createElement(item.style, null, item.name)}} ); } @@ -1097,17 +1108,23 @@ A ([]); let { dragAndDropHooks } = useDragAndDrop({ /*- begin highlight -*/ acceptedDragTypes: ['image/jpeg', 'image/png'], async onRootDrop(e) { let items = await Promise.all( - e.items.map(async (item: FileDropItem) => ({ + e.items.filter(isFileDropItem).map(async item => ({ id: Math.random(), url: URL.createObjectURL(await item.getFile()), name: item.name @@ -1168,23 +1185,28 @@ The `getEntries` method returns an [async iterable](https://developer.mozilla.or This example accepts directory drops over the whole collection, and renders the contents as items in the list. `DIRECTORY_DRAG_TYPE` is imported from `react-aria-components` and included in the `acceptedDragTypes` prop to limit the accepted items to only directories. ```tsx example -import type {DirectoryDropItem} from 'react-aria-components'; import File from '@spectrum-icons/workflow/FileTxt'; import Folder from '@spectrum-icons/workflow/Folder'; ///- begin highlight -/// -import {DIRECTORY_DRAG_TYPE} from 'react-aria-components'; +import {DIRECTORY_DRAG_TYPE, isDirectoryDropItem} from 'react-aria-components'; ///- end highlight -/// +interface DirItem { + name: string, + kind: string +} + function Example() { - let [files, setFiles] = React.useState([]); + let [files, setFiles] = React.useState([]); let { dragAndDropHooks } = useDragAndDrop({ /*- begin highlight -*/ acceptedDragTypes: [DIRECTORY_DRAG_TYPE], async onRootDrop(e) { // Read entries in directory and update state with relevant info. + let dir = e.items.find(isDirectoryDropItem)!; let files = []; - for await (let entry of (e.items[0] as DirectoryDropItem).getEntries()) { + for await (let entry of dir.getEntries()) { files.push({ name: entry.name, kind: entry.kind @@ -1367,8 +1389,15 @@ The `getDropOperation` function passed to `useDragAndDrop` can be used to provid In the below example, the drop target only supports dropping PNG images. If a PNG is dragged over the target, it will be highlighted and the operating system displays a copy cursor. If another type is dragged over the target, then there is no visual feedback, indicating that a drop is not accepted there. If the user holds a modifier key such as Control while dragging over the drop target in order to change the drop operation, then the drop target does not accept the drop. ```tsx example +///- begin collapse -/// +interface ImageItem { + id: number, + url: string, + name: string +} +///- end collapse -/// function Example() { - let [items, setItems] = React.useState([]); + let [items, setItems] = React.useState([]); let { dragAndDropHooks } = useDragAndDrop({ /*- begin highlight -*/ @@ -1378,7 +1407,7 @@ function Example() { async onRootDrop(e) { ///- begin collapse -/// let items = await Promise.all( - e.items.map(async (item: FileDropItem) => ({ + e.items.filter(isFileDropItem).map(async item => ({ id: Math.random(), url: URL.createObjectURL(await item.getFile()), name: item.name @@ -1433,10 +1462,10 @@ let onItemDrop = async (e) => { ### Drag between lists -This example puts together many of the concepts described above, allowing users to drag items between lists bidirectionally. Items are always moved to avoid duplicate items being added to the same list. +This example puts together many of the concepts described above, allowing users to drag items between lists bidirectionally. It also supports reordering items within the same list. When a list is empty, it accepts drops on the whole collection. `getDropOperation` ensures that items are always moved rather than copied, which avoids duplicate items. ```tsx example -import type {TextDropItem} from 'react-aria-components'; +import {isTextDropItem} from 'react-aria-components'; interface FileItem { id: string, @@ -1485,7 +1514,9 @@ function DndListBox(props: DndListBoxProps) { // Handle drops between items from other lists. async onInsert(e) { let processedItems = await Promise.all( - e.items.map(async (item: TextDropItem) => JSON.parse(await item.getText('custom-app-type'))) + e.items + .filter(isTextDropItem) + .map(async item => JSON.parse(await item.getText('custom-app-type'))) ); if (e.target.dropPosition === 'before') { list.insertBefore(e.target.key, ...processedItems); @@ -1497,7 +1528,9 @@ function DndListBox(props: DndListBoxProps) { // Handle drops on the collection when empty. async onRootDrop(e) { let processedItems = await Promise.all( - e.items.map(async (item: TextDropItem) => JSON.parse(await item.getText('custom-app-type'))) + e.items + .filter(isTextDropItem) + .map(async item => JSON.parse(await item.getText('custom-app-type'))) ); list.append(...processedItems); }, diff --git a/packages/react-aria-components/docs/NumberField.mdx b/packages/react-aria-components/docs/NumberField.mdx index cd7f7ccc880..7b05840fbfa 100644 --- a/packages/react-aria-components/docs/NumberField.mdx +++ b/packages/react-aria-components/docs/NumberField.mdx @@ -528,8 +528,8 @@ function Example() { value={value} onChange={setValue} label="Positive numbers only" - description={isValid ? 'Enter a positive number.' : null} - errorMessage={isValid ? null : value === 0 ? 'Is zero positive?' : 'Positive numbers are bigger than 0.'} /> + description={isValid ? 'Enter a positive number.' : undefined} + errorMessage={isValid ? undefined : value === 0 ? 'Is zero positive?' : 'Positive numbers are bigger than 0.'} /> ); } ``` diff --git a/packages/react-aria-components/docs/RadioGroup.mdx b/packages/react-aria-components/docs/RadioGroup.mdx index 88977517c31..977c3327780 100644 --- a/packages/react-aria-components/docs/RadioGroup.mdx +++ b/packages/react-aria-components/docs/RadioGroup.mdx @@ -397,8 +397,8 @@ function Example() { aria-label="Favorite pet" onChange={setSelected} validationState={isValid ? 'valid' : 'invalid'} - description={isValid ? 'Please select a pet.' : null} - errorMessage={isValid ? null : + description={isValid ? 'Please select a pet.' : undefined} + errorMessage={isValid ? undefined : selected === 'cats' ? 'No cats allowed.' : 'Please select dogs.' diff --git a/packages/react-aria-components/docs/RangeCalendar.mdx b/packages/react-aria-components/docs/RangeCalendar.mdx index 4f86965c02f..ae96bcf4d38 100644 --- a/packages/react-aria-components/docs/RangeCalendar.mdx +++ b/packages/react-aria-components/docs/RangeCalendar.mdx @@ -505,10 +505,11 @@ Selected dates passed to `onChange` always use the same calendar system as the ` The below example displays a `RangeCalendar` in the Hindi language, using the Indian calendar. Dates emitted from `onChange` are in the Gregorian calendar. ```tsx example +import type {DateRange} from 'react-aria-components'; import {I18nProvider} from '@react-aria/i18n'; function Example() { - let [range, setRange] = React.useState(null); + let [range, setRange] = React.useState(null); return ( @@ -550,7 +551,7 @@ function Example() { [now.add({days: 23}), now.add({days: 24})], ]; - let isDateUnavailable = (date) => disabledRanges.some((interval) => date.compare(interval[0]) >= 0 && date.compare(interval[1]) <= 0); + let isDateUnavailable = (date: DateValue) => disabledRanges.some((interval) => date.compare(interval[0]) >= 0 && date.compare(interval[1]) <= 0); return } @@ -633,7 +634,7 @@ function Example() { value={range} onChange={setRange} validationState={isInvalid ? 'invalid' : 'valid'} - errorMessage={isInvalid ? 'Maximum stay duration is 1 week' : null} /> + errorMessage={isInvalid ? 'Maximum stay duration is 1 week' : undefined} /> ); } ``` diff --git a/packages/react-aria-components/docs/Select.mdx b/packages/react-aria-components/docs/Select.mdx index 8ed41a43377..b97ab0b81b6 100644 --- a/packages/react-aria-components/docs/Select.mdx +++ b/packages/react-aria-components/docs/Select.mdx @@ -906,8 +906,8 @@ function Example() { { +interface PokemonTableProps extends TableProps { items?: Pokemon[], renderEmptyState?: () => string } @@ -1225,7 +1227,7 @@ function DraggableTable() { /*- begin highlight -*/ getItems(keys) { return [...keys].map(key => { - let item = items.find(item => item.id === key); + let item = items.find(item => item.id === key)!; return { 'text/plain': `${item.name} – ${item.type}`, 'text/html': `${item.name}${item.type}`, @@ -1256,15 +1258,18 @@ function DraggableTable() { Dropping on the Table as a whole can be enabled using the `onRootDrop` event. When a valid drag hovers over the Table, it receives the `isDropTarget` state and can be styled using the `[data-drop-target]` CSS selector. ```tsx example -import type {TextDropItem} from 'react-aria-components'; +import {isTextDropItem} from 'react-aria-components'; function Example() { - let [items, setItems] = React.useState([]); + let [items, setItems] = React.useState([]); let { dragAndDropHooks } = useDragAndDrop({ /*- begin highlight -*/ async onRootDrop(e) { - let items = await Promise.all(e.items.map(async (item: TextDropItem) => ( + let items = await Promise.all( + e.items + .filter(isTextDropItem) + .map(async item => ( JSON.parse(await item.getText('pokemon')) ))); setItems(items); @@ -1338,7 +1343,7 @@ function Example() { Dropping between items can be enabled using the `onInsert` event. Table renders a between items to indicate the insertion position, which can be styled using the `.react-aria-DropIndicator` selector. When it is active, it receives the `[data-drop-target]` state. ```tsx example -import type {TextDropItem} from 'react-aria-components'; +import {isTextDropItem} from 'react-aria-components'; function Example() { let list = useListData({ @@ -1353,7 +1358,7 @@ function Example() { let { dragAndDropHooks } = useDragAndDrop({ ///- begin highlight -/// async onInsert(e) { - let items = await Promise.all(e.items.map(async (item: TextDropItem) => { + let items = await Promise.all(e.items.filter(isTextDropItem).map(async item => { let {name, type, level} = JSON.parse(await item.getText('pokemon')); return {id: Math.random(), name, type, level}; })); @@ -1399,7 +1404,7 @@ function Example() { // ... ///- begin collapse -/// async onInsert(e) { - let items = await Promise.all(e.items.map(async (item: TextDropItem) => { + let items = await Promise.all(e.items.filter(isTextDropItem).map(async item => { let {name, type, level} = JSON.parse(await item.getText('pokemon')); return {id: Math.random(), name, type, level}; })); @@ -1471,17 +1476,19 @@ A ([]); let { dragAndDropHooks } = useDragAndDrop({ /*- begin highlight -*/ acceptedDragTypes: ['pokemon'], async onRootDrop(e) { let items = await Promise.all( - e.items.map(async (item: TextDropItem) => JSON.parse(await item.getText('pokemon'))) + e.items + .filter(isTextDropItem) + .map(async item => JSON.parse(await item.getText('pokemon'))) ); setItems(items); } @@ -1510,17 +1517,25 @@ A ([]); let { dragAndDropHooks } = useDragAndDrop({ /*- begin highlight -*/ acceptedDragTypes: ['image/jpeg', 'image/png'], async onRootDrop(e) { let items = await Promise.all( - e.items.map(async (item: FileDropItem) => { + e.items.filter(isFileDropItem).map(async item => { let file = await item.getFile(); return { id: Math.random(), @@ -1584,23 +1599,29 @@ The `getEntries` method returns an [async iterable](https://developer.mozilla.or This example accepts directory drops over the whole collection, and renders the contents as items in the list. `DIRECTORY_DRAG_TYPE` is imported from `react-aria-components` and included in the `acceptedDragTypes` prop to limit the accepted items to only directories. ```tsx example -import type {DirectoryDropItem} from 'react-aria-components'; import File from '@spectrum-icons/workflow/FileTxt'; import Folder from '@spectrum-icons/workflow/Folder'; ///- begin highlight -/// -import {DIRECTORY_DRAG_TYPE} from 'react-aria-components'; +import {DIRECTORY_DRAG_TYPE, isDirectoryDropItem} from 'react-aria-components'; ///- end highlight -/// +interface DirItem { + name: string, + kind: string, + type: string +} + function Example() { - let [files, setFiles] = React.useState([]); + let [files, setFiles] = React.useState([]); let { dragAndDropHooks } = useDragAndDrop({ /*- begin highlight -*/ acceptedDragTypes: [DIRECTORY_DRAG_TYPE], async onRootDrop(e) { // Read entries in directory and update state with relevant info. + let dir = e.items.find(isDirectoryDropItem)!; let files = []; - for await (let entry of (e.items[0] as DirectoryDropItem).getEntries()) { + for await (let entry of dir.getEntries()) { files.push({ name: entry.name, kind: entry.kind, @@ -1767,8 +1788,17 @@ The `getDropOperation` function passed to `useDragAndDrop` can be used to provid In the below example, the drop target only supports dropping PNG images. If a PNG is dragged over the target, it will be highlighted and the operating system displays a copy cursor. If another type is dragged over the target, then there is no visual feedback, indicating that a drop is not accepted there. If the user holds a modifier key such as Control while dragging over the drop target in order to change the drop operation, then the drop target does not accept the drop. ```tsx example +///- begin collapse -/// +interface ImageItem { + id: number, + url: string, + name: string, + type: string, + lastModified: number +} +///- end collapse -/// function Example() { - let [items, setItems] = React.useState([]); + let [items, setItems] = React.useState([]); let { dragAndDropHooks } = useDragAndDrop({ /*- begin highlight -*/ @@ -1778,7 +1808,7 @@ function Example() { async onRootDrop(e) { ///- begin collapse -/// let items = await Promise.all( - e.items.map(async (item: FileDropItem) => { + e.items.filter(isFileDropItem).map(async item => { let file = await item.getFile(); return { id: Math.random(), @@ -1851,7 +1881,7 @@ let onItemDrop = async (e) => { This example puts together many of the concepts described above, allowing users to drag items between tables bidirectionally. It also supports reordering items within the same table. When a table is empty, it accepts drops on the whole collection. `getDropOperation` ensures that items are always moved rather than copied, which avoids duplicate items. ```tsx example -import type {TextDropItem} from 'react-aria-components'; +import {isTextDropItem} from 'react-aria-components'; interface FileItem { id: string, @@ -1900,7 +1930,9 @@ function DndTable(props: DndTableProps) { // Handle drops between items from other lists. async onInsert(e) { let processedItems = await Promise.all( - e.items.map(async (item: TextDropItem) => JSON.parse(await item.getText('custom-app-type'))) + e.items + .filter(isTextDropItem) + .map(async item => JSON.parse(await item.getText('custom-app-type'))) ); if (e.target.dropPosition === 'before') { list.insertBefore(e.target.key, ...processedItems); @@ -1912,7 +1944,9 @@ function DndTable(props: DndTableProps) { // Handle drops on the collection when empty. async onRootDrop(e) { let processedItems = await Promise.all( - e.items.map(async (item: TextDropItem) => JSON.parse(await item.getText('custom-app-type'))) + e.items + .filter(isTextDropItem) + .map(async item => JSON.parse(await item.getText('custom-app-type'))) ); list.append(...processedItems); }, diff --git a/packages/react-aria-components/docs/TimeField.mdx b/packages/react-aria-components/docs/TimeField.mdx index 65bf6190c8e..bb7c8c1607e 100644 --- a/packages/react-aria-components/docs/TimeField.mdx +++ b/packages/react-aria-components/docs/TimeField.mdx @@ -451,8 +451,8 @@ function Example() { value={time} onChange={setTime} validationState={isInvalid ? 'invalid' : 'valid'} - description={isInvalid ? null : 'Select a meeting time'} - errorMessage={isInvalid ? 'Meetings start every 15 minutes.' : null} /> + description={isInvalid ? undefined : 'Select a meeting time'} + errorMessage={isInvalid ? 'Meetings start every 15 minutes.' : undefined} /> ); } ``` diff --git a/packages/react-aria-components/src/Breadcrumbs.tsx b/packages/react-aria-components/src/Breadcrumbs.tsx index bb374562126..5fe3209c668 100644 --- a/packages/react-aria-components/src/Breadcrumbs.tsx +++ b/packages/react-aria-components/src/Breadcrumbs.tsx @@ -15,6 +15,7 @@ import {ContextValue, forwardRefType, Provider, SlotProps, StyleProps, useContex import {filterDOMProps} from '@react-aria/utils'; import {HeadingContext} from './Heading'; import {LinkContext} from './Link'; +import {Node} from 'react-stately'; import React, {createContext, ForwardedRef, forwardRef, HTMLAttributes} from 'react'; export interface BreadcrumbsProps extends Omit, 'disabledKeys'>, Omit, StyleProps, SlotProps { @@ -58,11 +59,17 @@ function Breadcrumbs(props: BreadcrumbsProps, ref: Forwarde const _Breadcrumbs = (forwardRef as forwardRefType)(Breadcrumbs); export {_Breadcrumbs as Breadcrumbs}; -function BreadcrumbItem({node, isCurrent, isDisabled}) { +interface BreadcrumbItemProps { + node: Node, + isCurrent: boolean, + isDisabled?: boolean +} + +function BreadcrumbItem({node, isCurrent, isDisabled}: BreadcrumbItemProps) { // Recreating useBreadcrumbItem because we want to use composition instead of having the link builtin. - let headingProps: HTMLAttributes = isCurrent ? {'aria-current': 'page'} : undefined; + let headingProps: HTMLAttributes | null = isCurrent ? {'aria-current': 'page'} : null; let linkProps = { - 'aria-current': isCurrent ? 'page' : undefined, + 'aria-current': isCurrent ? 'page' : null, isDisabled: isDisabled || isCurrent }; diff --git a/packages/react-aria-components/src/Calendar.tsx b/packages/react-aria-components/src/Calendar.tsx index 5adce19c31f..cc617ec2a09 100644 --- a/packages/react-aria-components/src/Calendar.tsx +++ b/packages/react-aria-components/src/Calendar.tsx @@ -38,8 +38,8 @@ export interface RangeCalendarProps extends Omit, HTMLDivElement>>({}); export const RangeCalendarContext = createContext, HTMLDivElement>>({}); -const InternalCalendarContext = createContext(null); -const InternalCalendarGridContext = createContext(null); +const InternalCalendarContext = createContext(null); +const InternalCalendarGridContext = createContext(null); function Calendar(props: CalendarProps, ref: ForwardedRef) { [props, ref] = useContextProps(props, ref, CalendarContext); @@ -257,7 +257,7 @@ export interface CalendarGridProps extends StyleProps { } function CalendarGrid(props: CalendarGridProps, ref: ForwardedRef) { - let state = useContext(InternalCalendarContext); + let state = useContext(InternalCalendarContext)!; let startDate = state.visibleRange.start; if (props.offset) { startDate = startDate.add(props.offset); @@ -313,8 +313,8 @@ export interface CalendarCellProps extends RenderProps } function CalendarCell({date, ...otherProps}: CalendarCellProps, ref: ForwardedRef) { - let state = useContext(InternalCalendarContext); - let currentMonth = useContext(InternalCalendarGridContext); + let state = useContext(InternalCalendarContext)!; + let currentMonth = useContext(InternalCalendarGridContext)!; let objectRef = useObjectRef(ref); let {cellProps, buttonProps, ...states} = useCalendarCell( {date}, diff --git a/packages/react-aria-components/src/Checkbox.tsx b/packages/react-aria-components/src/Checkbox.tsx index ae2a51ab591..e4999320a9c 100644 --- a/packages/react-aria-components/src/Checkbox.tsx +++ b/packages/react-aria-components/src/Checkbox.tsx @@ -87,7 +87,7 @@ export interface CheckboxRenderProps { * Whether the checkbox is valid or invalid. * @selector [data-validation-state="valid | invalid"] */ - validationState: ValidationState, + validationState?: ValidationState, /** * Whether the checkbox is required. * @selector [data-required] @@ -96,7 +96,7 @@ export interface CheckboxRenderProps { } export const CheckboxGroupContext = createContext>(null); -const InternalCheckboxGroupContext = createContext(null); +const InternalCheckboxGroupContext = createContext(null); function CheckboxGroup(props: CheckboxGroupProps, ref: ForwardedRef) { [props, ref] = useContextProps(props, ref, CheckboxGroupContext); diff --git a/packages/react-aria-components/src/Collection.tsx b/packages/react-aria-components/src/Collection.tsx index d3609f09e3f..6d55017da64 100644 --- a/packages/react-aria-components/src/Collection.tsx +++ b/packages/react-aria-components/src/Collection.tsx @@ -11,9 +11,10 @@ */ import {CollectionBase} from '@react-types/shared'; import {createPortal} from 'react-dom'; -import {DOMProps, RenderProps, useContextProps} from './utils'; +import {DOMProps, RenderProps} from './utils'; import {Collection as ICollection, Node, SelectionBehavior, SelectionMode, ItemProps as SharedItemProps, SectionProps as SharedSectionProps} from 'react-stately'; -import React, {cloneElement, createContext, Key, ReactElement, ReactNode, ReactPortal, useCallback, useMemo} from 'react'; +import {mergeProps} from 'react-aria'; +import React, {cloneElement, createContext, Key, ReactElement, ReactNode, ReactPortal, useCallback, useContext, useMemo} from 'react'; import {useLayoutEffect} from '@react-aria/utils'; import {useSyncExternalStore} from 'use-sync-external-store/shim/index.js'; @@ -39,12 +40,12 @@ type Mutable = { export class NodeValue implements Node { readonly type: string; readonly key: Key; - readonly value: T; + readonly value: T | null = null; readonly level: number = 0; readonly hasChildNodes: boolean = false; readonly rendered: ReactNode = null; - readonly textValue: string | null = null; - readonly 'aria-label'?: string = null; + readonly textValue: string = ''; + readonly 'aria-label'?: string = undefined; readonly index: number = 0; readonly parentKey: Key | null = null; readonly prevKey: Key | null = null; @@ -86,11 +87,11 @@ export class NodeValue implements Node { * and queues an update with the owner document. */ class BaseNode { - private _firstChild: ElementNode | null; - private _lastChild: ElementNode | null; - private _previousSibling: ElementNode | null; - private _nextSibling: ElementNode | null; - private _parentNode: BaseNode | null; + private _firstChild: ElementNode | null = null; + private _lastChild: ElementNode | null = null; + private _previousSibling: ElementNode | null = null; + private _nextSibling: ElementNode | null = null; + private _parentNode: BaseNode | null = null; ownerDocument: Document; constructor(ownerDocument: Document) { @@ -191,14 +192,14 @@ class BaseNode { if (this.firstChild === referenceNode) { this.firstChild = newNode; - } else { + } else if (referenceNode.previousSibling) { referenceNode.previousSibling.nextSibling = newNode; } referenceNode.previousSibling = newNode; newNode.parentNode = referenceNode.parentNode; - let node = referenceNode; + let node: ElementNode | null = referenceNode; while (node) { node.index++; node = node.nextSibling; @@ -237,7 +238,7 @@ class BaseNode { child.parentNode = null; child.nextSibling = null; child.previousSibling = null; - child.index = null; + child.index = 0; this.ownerDocument.removeNode(child); } @@ -260,7 +261,7 @@ const TYPE_MAP = { export class ElementNode extends BaseNode { nodeType = 8; // COMMENT_NODE (we'd use ELEMENT_NODE but React DevTools will fail to get its dimensions) node: NodeValue; - private _index: number; + private _index: number = 0; constructor(type: string, ownerDocument: Document) { super(ownerDocument); @@ -276,7 +277,7 @@ export class ElementNode extends BaseNode { this.ownerDocument.dirtyNodes.add(this); } - get level() { + get level(): number { if (this.parentNode instanceof ElementNode) { return this.parentNode.level + (this.node.type === 'item' ? 1 : 0); } @@ -289,16 +290,16 @@ export class ElementNode extends BaseNode { node.index = this.index; node.level = this.level; node.parentKey = this.parentNode instanceof ElementNode ? this.parentNode.node.key : null; - node.prevKey = this.previousSibling?.node.key; - node.nextKey = this.nextSibling?.node.key; + node.prevKey = this.previousSibling?.node.key ?? null; + node.nextKey = this.nextSibling?.node.key ?? null; node.hasChildNodes = !!this.firstChild; - node.firstChildKey = this.firstChild?.node.key; - node.lastChildKey = this.lastChild?.node.key; + node.firstChildKey = this.firstChild?.node.key ?? null; + node.lastChildKey = this.lastChild?.node.key ?? null; } // Special property that React passes through as an object rather than a string via setAttribute. // See below for details. - set multiple(value) { + set multiple(value: any) { let node = this.ownerDocument.getMutableNode(this); node.props = value; node.rendered = value.rendered; @@ -322,8 +323,8 @@ export class ElementNode extends BaseNode { hasAttribute() {} setAttribute(key: string, value: string) { - if (key in this.node) { - let node = this.ownerDocument.getMutableNode(this); + let node = this.ownerDocument.getMutableNode(this); + if (key in node) { node[key] = value; } } @@ -351,10 +352,10 @@ export class BaseCollection implements ICollection> { } *[Symbol.iterator]() { - let node: Node = this.keyMap.get(this.firstKey); + let node: Node | undefined = this.firstKey != null ? this.keyMap.get(this.firstKey) : undefined; while (node) { yield node; - node = this.keyMap.get(node.nextKey); + node = node.nextKey != null ? this.keyMap.get(node.nextKey) : undefined; } } @@ -363,10 +364,10 @@ export class BaseCollection implements ICollection> { return { *[Symbol.iterator]() { let parent = keyMap.get(key); - let node = parent && keyMap.get(parent.firstChildKey); + let node = parent?.firstChildKey != null ? keyMap.get(parent.firstChildKey) : null; while (node) { - yield node; - node = keyMap.get(node.nextKey); + yield node as Node; + node = node.nextKey != null ? keyMap.get(node.nextKey) : undefined; } } }; @@ -381,11 +382,11 @@ export class BaseCollection implements ICollection> { if (node.prevKey != null) { node = this.keyMap.get(node.prevKey); - while (node.type !== 'item' && node.lastChildKey != null) { + while (node && node.type !== 'item' && node.lastChildKey != null) { node = this.keyMap.get(node.lastChildKey); } - return node.key; + return node?.key ?? null; } return node.parentKey; @@ -412,6 +413,8 @@ export class BaseCollection implements ICollection> { return null; } } + + return null; } getFirstKey() { @@ -419,16 +422,16 @@ export class BaseCollection implements ICollection> { } getLastKey() { - let node = this.keyMap.get(this.lastKey); + let node = this.lastKey != null ? this.keyMap.get(this.lastKey) : null; while (node?.lastChildKey != null) { node = this.keyMap.get(node.lastChildKey); } - return node?.key; + return node?.key ?? null; } - getItem(key: Key): Node { - return this.keyMap.get(key); + getItem(key: Key): Node | null { + return this.keyMap.get(key) ?? null; } at(): Node { @@ -463,7 +466,7 @@ export class BaseCollection implements ICollection> { this.keyMap.delete(key); } - commit(firstKey: Key, lastKey: Key) { + commit(firstKey: Key | null, lastKey: Key | null) { if (this.frozen) { throw new Error('Cannot commit a frozen collection'); } @@ -488,6 +491,7 @@ export class Document> extends BaseNode { private subscriptions: Set<() => void> = new Set(); constructor(collection: C) { + // @ts-ignore super(null); this.collection = collection; this.collectionMutated = true; @@ -562,7 +566,7 @@ export class Document> extends BaseNode { } } - collection.commit(this.firstChild?.node.key, this.lastChild?.node.key); + collection.commit(this.firstChild?.node.key ?? null, this.lastChild?.node.key ?? null); this.mutatedNodes.clear(); } @@ -596,7 +600,7 @@ export function useCachedChildren(props: CachedChildrenOptions let cache = useMemo(() => new WeakMap(), []); return useMemo(() => { if (items && typeof children === 'function') { - let res = []; + let res: ReactElement[] = []; for (let item of items) { let rendered = cache.get(item); if (!rendered) { @@ -733,11 +737,12 @@ export function Section(props: SectionProps): JSX.Element { return
{children}
; } -export const CollectionContext = createContext>(null); +export const CollectionContext = createContext | null>(null); export const CollectionRendererContext = createContext['children']>(null); export function Collection(props: CollectionProps): JSX.Element { - [props] = useContextProps(props, null, CollectionContext); + let ctx = useContext(CollectionContext)!; + props = mergeProps(ctx, props); let renderer = typeof props.children === 'function' ? props.children : null; return ( diff --git a/packages/react-aria-components/src/ComboBox.tsx b/packages/react-aria-components/src/ComboBox.tsx index 8ddf4121441..a8bf7a81fb2 100644 --- a/packages/react-aria-components/src/ComboBox.tsx +++ b/packages/react-aria-components/src/ComboBox.tsx @@ -42,14 +42,14 @@ function ComboBox(props: ComboBoxProps, ref: ForwardedRef(null); let inputRef = useRef(null); - let listBoxRef = useRef(null); - let popoverRef = useRef(null); + let listBoxRef = useRef(null); + let popoverRef = useRef(null); let [labelRef, label] = useSlot(); let { buttonProps, @@ -69,7 +69,7 @@ function ComboBox(props: ComboBoxProps, ref: ForwardedRef(null); let onResize = useCallback(() => { if (inputRef.current) { let buttonRect = buttonRef.current?.getBoundingClientRect(); diff --git a/packages/react-aria-components/src/DateField.tsx b/packages/react-aria-components/src/DateField.tsx index b85c0d3a4a3..d300c7795c4 100644 --- a/packages/react-aria-components/src/DateField.tsx +++ b/packages/react-aria-components/src/DateField.tsx @@ -39,7 +39,7 @@ function DateField(props: DateFieldProps, ref: Forwarded createCalendar }); - let fieldRef = useRef(); + let fieldRef = useRef(null); let [labelRef, label] = useSlot(); let {labelProps, fieldProps, descriptionProps, errorMessageProps} = useDateField({...props, label}, state, fieldRef); @@ -83,7 +83,7 @@ function TimeField(props: TimeFieldProps, ref: Forwarded locale }); - let fieldRef = useRef(); + let fieldRef = useRef(null); let [labelRef, label] = useSlot(); let {labelProps, fieldProps, descriptionProps, errorMessageProps} = useTimeField({...props, label}, state, fieldRef); @@ -116,10 +116,10 @@ function TimeField(props: TimeFieldProps, ref: Forwarded * A time field allows users to enter and edit time values using a keyboard. * Each part of a time value is displayed in an individually editable segment. */ -const _TimeField = forwardRef(TimeField); +const _TimeField = (forwardRef as forwardRefType)(TimeField); export {_TimeField as TimeField}; -const InternalDateInputContext = createContext(null); +const InternalDateInputContext = createContext(null); export interface DateInputProps extends SlotProps, StyleProps { children: (segment: IDateSegment) => ReactElement @@ -176,7 +176,7 @@ export interface DateSegmentProps extends RenderProps { } function DateSegment({segment, ...otherProps}: DateSegmentProps, ref: ForwardedRef) { - let state = useContext(InternalDateInputContext); + let state = useContext(InternalDateInputContext)!; let domRef = useObjectRef(ref); let {segmentProps} = useDateSegment(segment, state, domRef); let renderProps = useRenderProps({ diff --git a/packages/react-aria-components/src/DatePicker.tsx b/packages/react-aria-components/src/DatePicker.tsx index 5b768dfcbf7..0c3ea534ba8 100644 --- a/packages/react-aria-components/src/DatePicker.tsx +++ b/packages/react-aria-components/src/DatePicker.tsx @@ -32,7 +32,7 @@ export const DateRangePickerContext = createContext(props: DatePickerProps, ref: ForwardedRef) { [props, ref] = useContextProps(props, ref, DatePickerContext); let state = useDatePickerState(props); - let groupRef = useRef(); + let groupRef = useRef(null); let [labelRef, label] = useSlot(); let { groupProps, @@ -52,7 +52,7 @@ function DatePicker(props: DatePickerProps, ref: Forward createCalendar }); - let fieldRef = useRef(); + let fieldRef = useRef(null); let {fieldProps: dateFieldProps} = useDateField({...fieldProps, label}, fieldState, fieldRef); let renderProps = useRenderProps({ @@ -94,7 +94,7 @@ export {_DatePicker as DatePicker}; function DateRangePicker(props: DateRangePickerProps, ref: ForwardedRef) { [props, ref] = useContextProps(props, ref, DateRangePickerContext); let state = useDateRangePickerState(props); - let groupRef = useRef(); + let groupRef = useRef(null); let [labelRef, label] = useSlot(); let { groupProps, @@ -115,7 +115,7 @@ function DateRangePicker(props: DateRangePickerProps, re createCalendar }); - let startFieldRef = useRef(); + let startFieldRef = useRef(null); let {fieldProps: startDateFieldProps} = useDateField({...startFieldProps, label}, startFieldState, startFieldRef); let endFieldState = useDateFieldState({ @@ -124,7 +124,7 @@ function DateRangePicker(props: DateRangePickerProps, re createCalendar }); - let endFieldRef = useRef(); + let endFieldRef = useRef(null); let {fieldProps: endDateFieldProps} = useDateField({...endFieldProps, label}, endFieldState, endFieldRef); let renderProps = useRenderProps({ diff --git a/packages/react-aria-components/src/Dialog.tsx b/packages/react-aria-components/src/Dialog.tsx index 579bd1f8a14..350f00a2813 100644 --- a/packages/react-aria-components/src/Dialog.tsx +++ b/packages/react-aria-components/src/Dialog.tsx @@ -40,7 +40,7 @@ export const DialogContext = createContext(null); let {triggerProps, overlayProps} = useOverlayTrigger({type: 'dialog'}, state, buttonRef); return ( @@ -64,7 +64,7 @@ function Dialog(props: DialogProps, ref: ForwardedRef) { let children = props.children; if (typeof children === 'function') { children = children({ - close: props.onClose + close: props.onClose || (() => {}) }); } diff --git a/packages/react-aria-components/src/GridList.tsx b/packages/react-aria-components/src/GridList.tsx index c333e232013..d1207db9217 100644 --- a/packages/react-aria-components/src/GridList.tsx +++ b/packages/react-aria-components/src/GridList.tsx @@ -9,7 +9,7 @@ * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ -import {AriaGridListProps, DraggableItemResult, DropIndicatorAria, DroppableCollectionResult, ListKeyboardDelegate, mergeProps, useFocusRing, useGridList, useGridListItem, useGridListSelectionCheckbox, useHover, useVisuallyHidden, VisuallyHidden} from 'react-aria'; +import {AriaGridListProps, DraggableItemResult, DragPreviewRenderer, DropIndicatorAria, DroppableCollectionResult, ListKeyboardDelegate, mergeProps, useFocusRing, useGridList, useGridListItem, useGridListSelectionCheckbox, useHover, useVisuallyHidden, VisuallyHidden} from 'react-aria'; import {ButtonContext} from './Button'; import {CheckboxContext} from './Checkbox'; import {CollectionProps, ItemProps, useCachedChildren, useCollection} from './Collection'; @@ -17,7 +17,7 @@ import {ContextValue, defaultSlot, forwardRefType, Provider, SlotProps, StyleRen import {DragAndDropHooks, DropIndicator, DropIndicatorContext, DropIndicatorProps} from './useDragAndDrop'; import {DraggableCollectionState, DroppableCollectionState, ListState, Node, SelectionBehavior, useListState} from 'react-stately'; import {filterDOMProps, isIOS, isWebKit, useObjectRef} from '@react-aria/utils'; -import React, {createContext, ForwardedRef, forwardRef, ReactNode, RefObject, useContext, useEffect, useRef} from 'react'; +import React, {createContext, ForwardedRef, forwardRef, HTMLAttributes, ReactNode, RefObject, useContext, useEffect, useRef} from 'react'; import {TextContext} from './Text'; export interface GridListRenderProps { @@ -49,17 +49,17 @@ interface InternalGridListContextValue { dropState?: DroppableCollectionState } -export const GridListContext = createContext, HTMLUListElement>>(null); -const InternalGridListContext = createContext(null); +export const GridListContext = createContext, HTMLDivElement>>(null); +const InternalGridListContext = createContext(null); -function GridList(props: GridListProps, ref: ForwardedRef) { +function GridList(props: GridListProps, ref: ForwardedRef) { [props, ref] = useContextProps(props, ref, GridListContext); let {dragAndDropHooks} = props; let {portal, collection} = useCollection(props); let state = useListState({ ...props, collection, - children: null + children: undefined }); let {gridProps} = useGridList(props, state, ref); @@ -88,28 +88,29 @@ function GridList(props: GridListProps, ref: ForwardedRef(null); - if (isListDraggable) { - dragState = dragAndDropHooks.useDraggableCollectionState({ + if (isListDraggable && dragAndDropHooks) { + dragState = dragAndDropHooks.useDraggableCollectionState!({ collection, selectionManager, - preview: dragAndDropHooks.renderDragPreview ? preview : null + preview: dragAndDropHooks.renderDragPreview ? preview : undefined }); - dragAndDropHooks.useDraggableCollection({}, dragState, ref); + dragAndDropHooks.useDraggableCollection!({}, dragState, ref); + let DragPreview = dragAndDropHooks.DragPreview!; dragPreview = dragAndDropHooks.renderDragPreview - ? {dragAndDropHooks.renderDragPreview} + ? {dragAndDropHooks.renderDragPreview} : null; } - if (isListDroppable) { - dropState = dragAndDropHooks.useDroppableCollectionState({ + if (isListDroppable && dragAndDropHooks) { + dropState = dragAndDropHooks.useDroppableCollectionState!({ collection, selectionManager }); @@ -120,7 +121,7 @@ function GridList(props: GridListProps, ref: ForwardedRef(props: GridListProps, ref: ForwardedRef | null = null; if (state.collection.size === 0 && props.renderEmptyState) { // Ideally we'd use `display: contents` on the row and cell elements so that // they don't affect the layout of the children. However, WebKit currently has @@ -208,8 +209,8 @@ const _GridList = (forwardRef as forwardRefType)(GridList); export {_GridList as GridList}; function GridListItem({item}) { - let {state, dragAndDropHooks, dragState, dropState} = useContext(InternalGridListContext); - let ref = React.useRef(); + let {state, dragAndDropHooks, dragState, dropState} = useContext(InternalGridListContext)!; + let ref = React.useRef(null); let {rowProps, gridCellProps, descriptionProps, ...states} = useGridListItem( { node: item, @@ -229,16 +230,16 @@ function GridListItem({item}) { state ); - let draggableItem: DraggableItemResult; - if (dragState) { - draggableItem = dragAndDropHooks.useDraggableItem({key: item.key, hasDragButton: true}, dragState); + let draggableItem: DraggableItemResult | null = null; + if (dragState && dragAndDropHooks) { + draggableItem = dragAndDropHooks.useDraggableItem!({key: item.key, hasDragButton: true}, dragState); } - let dropIndicator: DropIndicatorAria; - let dropIndicatorRef = useRef(null); + let dropIndicator: DropIndicatorAria | null = null; + let dropIndicatorRef = useRef(null); let {visuallyHiddenProps} = useVisuallyHidden(); - if (dropState) { - dropIndicator = dragAndDropHooks.useDropIndicator({ + if (dropState && dragAndDropHooks) { + dropIndicator = dragAndDropHooks.useDropIndicator!({ target: {type: 'item', key: item.key, dropPosition: 'on'} }, dropState, dropIndicatorRef); } @@ -263,7 +264,7 @@ function GridListItem({item}) { }); let renderDropIndicator = dragAndDropHooks?.renderDropIndicator || (target => ); - let dragButtonRef = useRef(null); + let dragButtonRef = useRef(null); useEffect(() => { if (dragState && !dragButtonRef.current) { console.warn('Draggable items in a GridList must contain a