Skip to content
Merged
1 change: 1 addition & 0 deletions packages/@react-aria/dnd/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
17 changes: 16 additions & 1 deletion packages/@react-aria/dnd/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -314,6 +314,21 @@ function getEntryFile(entry: FileSystemFileEntry): Promise<File> {
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. */
Expand Down
2 changes: 1 addition & 1 deletion packages/@react-aria/listbox/src/useOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export function useOption<T>(props: AriaOptionProps, state: ListState<T>, 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);
}
Expand Down
12 changes: 10 additions & 2 deletions packages/@react-aria/selection/src/ListKeyboardDelegate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export class ListKeyboardDelegate<T> implements KeyboardDelegate {

key = this.collection.getKeyAfter(key);
}

return null;
}

getKeyAbove(key: Key) {
Expand All @@ -48,6 +50,8 @@ export class ListKeyboardDelegate<T> implements KeyboardDelegate {

key = this.collection.getKeyBefore(key);
}

return null;
}

getFirstKey() {
Expand All @@ -60,6 +64,8 @@ export class ListKeyboardDelegate<T> implements KeyboardDelegate {

key = this.collection.getKeyAfter(key);
}

return null;
}

getLastKey() {
Expand All @@ -72,6 +78,8 @@ export class ListKeyboardDelegate<T> implements KeyboardDelegate {

key = this.collection.getKeyBefore(key);
}

return null;
}

private getItem(key: Key): HTMLElement {
Expand All @@ -89,7 +97,7 @@ export class ListKeyboardDelegate<T> implements KeyboardDelegate {

while (item && item.offsetTop > pageY) {
key = this.getKeyAbove(key);
item = this.getItem(key);
item = key == null ? null : this.getItem(key);
}

return key;
Expand All @@ -106,7 +114,7 @@ export class ListKeyboardDelegate<T> implements KeyboardDelegate {

while (item && item.offsetTop < pageY) {
key = this.getKeyBelow(key);
item = this.getItem(key);
item = key == null ? null : this.getItem(key);
}

return key;
Expand Down
2 changes: 1 addition & 1 deletion packages/@react-aria/tabs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
5 changes: 3 additions & 2 deletions packages/@react-aria/tabs/src/useTabList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,18 @@ import {TabsKeyboardDelegate} from './TabsKeyboardDelegate';
import {useLocale} from '@react-aria/i18n';
import {useSelectableCollection} from '@react-aria/selection';

export interface AriaTabListOptions<T> extends Omit<AriaTabListProps<T>, '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<T>(props: AriaTabListProps<T>, state: TabListState<T>, ref: RefObject<HTMLElement>): TabListAria {
export function useTabList<T>(props: AriaTabListOptions<T>, state: TabListState<T>, ref: RefObject<HTMLElement>): TabListAria {
let {
orientation = 'horizontal',
keyboardActivation = 'automatic'
Expand Down
7 changes: 5 additions & 2 deletions packages/@react-aria/utils/src/mergeProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> = { [P in keyof T]: T[P] } extends { [key: number]: infer V } ? V : never;
type TupleTypes<T> = { [P in keyof T]: T[P] } extends { [key: number]: infer V } ? NullToObject<V> : never;
type NullToObject<T> = T extends (null | undefined) ? {} : T;
// eslint-disable-next-line no-undef, @typescript-eslint/no-unused-vars
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;

Expand All @@ -30,7 +33,7 @@ type UnionToIntersection<U> = (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<T extends Props[]>(...args: T): UnionToIntersection<TupleTypes<T>> {
export function mergeProps<T extends PropsArg[]>(...args: T): UnionToIntersection<TupleTypes<T>> {
// 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]};
Expand Down
4 changes: 2 additions & 2 deletions packages/@react-stately/calendar/src/useCalendarState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<DateValue> {
export interface CalendarStateOptions<T extends DateValue = DateValue> extends CalendarProps<T> {
/** The locale to display and edit the value according to. */
locale: string,
/**
Expand All @@ -55,7 +55,7 @@ export interface CalendarStateOptions extends CalendarProps<DateValue> {
* 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<T extends DateValue = DateValue>(props: CalendarStateOptions<T>): CalendarState {
let defaultFormatter = useMemo(() => new DateFormatter(props.locale), [props.locale]);
let resolvedOptions = useMemo(() => defaultFormatter.resolvedOptions(), [defaultFormatter]);
let {
Expand Down
4 changes: 2 additions & 2 deletions packages/@react-stately/calendar/src/useRangeCalendarState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<DateValue> {
export interface RangeCalendarStateOptions<T extends DateValue = DateValue> extends RangeCalendarProps<T> {
/** The locale to display and edit the value according to. */
locale: string,
/**
Expand All @@ -41,7 +41,7 @@ export interface RangeCalendarStateOptions extends RangeCalendarProps<DateValue>
* 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<T extends DateValue = DateValue>(props: RangeCalendarStateOptions<T>): RangeCalendarState {
let {value: valueProp, defaultValue, onChange, createCalendar, locale, visibleDuration = {months: 1}, minValue, maxValue, ...calendarProps} = props;
let [value, setValue] = useControlledState<DateRange>(
valueProp,
Expand Down
4 changes: 2 additions & 2 deletions packages/@react-stately/datepicker/src/useDateFieldState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ const TYPE_MAPPING = {
dayperiod: 'dayPeriod'
};

export interface DateFieldStateOptions extends DatePickerProps<DateValue> {
export interface DateFieldStateOptions<T extends DateValue = DateValue> extends DatePickerProps<T> {
/**
* The maximum unit to display in the date field.
* @default 'year'
Expand All @@ -137,7 +137,7 @@ export interface DateFieldStateOptions extends DatePickerProps<DateValue> {
* 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<T extends DateValue = DateValue>(props: DateFieldStateOptions<T>): DateFieldState {
let {
locale,
createCalendar,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<DateValue> {
export interface DateRangePickerStateOptions<T extends DateValue = DateValue> extends DateRangePickerProps<T> {
/**
* Determines whether the date picker popover should close automatically when a date is selected.
* @default true
Expand Down Expand Up @@ -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<T extends DateValue = DateValue>(props: DateRangePickerStateOptions<T>): DateRangePickerState {
let overlayState = useOverlayTriggerState(props);
let [controlledValue, setControlledValue] = useControlledState<DateRange>(props.value, props.defaultValue || null, props.onChange);
let [placeholderValue, setPlaceholderValue] = useState(() => controlledValue || {start: null, end: null});
Expand Down
4 changes: 2 additions & 2 deletions packages/@react-stately/datepicker/src/useTimeFieldState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<TimeValue> {
export interface TimeFieldStateOptions<T extends TimeValue = TimeValue> extends TimePickerProps<T> {
/** The locale to display and edit the value according to. */
locale: string
}
Expand All @@ -26,7 +26,7 @@ export interface TimeFieldStateOptions extends TimePickerProps<TimeValue> {
* 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<T extends TimeValue = TimeValue>(props: TimeFieldStateOptions<T>): DateFieldState {
let {
placeholderValue = new Time(),
minValue,
Expand Down
4 changes: 2 additions & 2 deletions packages/@react-types/calendar/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ export interface CalendarPropsBase {
}

export type DateRange = RangeValue<DateValue>;
export interface CalendarProps<T extends DateValue> extends CalendarPropsBase, ValueBase<T, MappedDateValue<T>> {}
export interface RangeCalendarProps<T extends DateValue> extends CalendarPropsBase, ValueBase<RangeValue<T>, RangeValue<MappedDateValue<T>>> {
export interface CalendarProps<T extends DateValue> extends CalendarPropsBase, ValueBase<T | null, MappedDateValue<T>> {}
export interface RangeCalendarProps<T extends DateValue> extends CalendarPropsBase, ValueBase<RangeValue<T> | null, RangeValue<MappedDateValue<T>>> {
/**
* When combined with `isDateUnavailable`, determines whether non-contiguous ranges,
* i.e. ranges containing unavailable dates, may be selected.
Expand Down
8 changes: 4 additions & 4 deletions packages/@react-types/datepicker/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,17 @@ interface DateFieldBase<T extends DateValue> extends InputBase, Validation, Focu
}

interface AriaDateFieldBaseProps<T extends DateValue> extends DateFieldBase<T>, AriaLabelingProps, DOMProps {}
export interface DateFieldProps<T extends DateValue> extends DateFieldBase<T>, ValueBase<T, MappedDateValue<T>> {}
export interface DateFieldProps<T extends DateValue> extends DateFieldBase<T>, ValueBase<T | null, MappedDateValue<T>> {}
export interface AriaDateFieldProps<T extends DateValue> extends DateFieldProps<T>, AriaDateFieldBaseProps<T> {}

interface DatePickerBase<T extends DateValue> extends DateFieldBase<T>, OverlayTriggerProps {}
export interface AriaDatePickerBaseProps<T extends DateValue> extends DatePickerBase<T>, AriaLabelingProps, DOMProps {}

export interface DatePickerProps<T extends DateValue> extends DatePickerBase<T>, ValueBase<T, MappedDateValue<T>> {}
export interface DatePickerProps<T extends DateValue> extends DatePickerBase<T>, ValueBase<T | null, MappedDateValue<T>> {}
export interface AriaDatePickerProps<T extends DateValue> extends DatePickerProps<T>, AriaDatePickerBaseProps<T> {}

export type DateRange = RangeValue<DateValue>;
export interface DateRangePickerProps<T extends DateValue> extends DatePickerBase<T>, ValueBase<RangeValue<T>, RangeValue<MappedDateValue<T>>> {
export interface DateRangePickerProps<T extends DateValue> extends DatePickerBase<T>, ValueBase<RangeValue<T> | null, RangeValue<MappedDateValue<T>>> {
/**
* When combined with `isDateUnavailable`, determines whether non-contiguous ranges,
* i.e. ranges containing unavailable dates, may be selected.
Expand Down Expand Up @@ -112,7 +112,7 @@ type MappedTimeValue<T> =
T extends Time ? Time :
never;

export interface TimePickerProps<T extends TimeValue> extends InputBase, Validation, FocusableProps, LabelableProps, HelpTextProps, ValueBase<T, MappedTimeValue<T>> {
export interface TimePickerProps<T extends TimeValue> extends InputBase, Validation, FocusableProps, LabelableProps, HelpTextProps, ValueBase<T | null, MappedTimeValue<T>> {
/** Whether to display the time in 12 or 24 hour format. By default, this is determined by the user's locale. */
hourCycle?: 12 | 24,
/**
Expand Down
12 changes: 6 additions & 6 deletions packages/@react-types/shared/src/collections.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,10 @@ export interface Collection<T> extends Iterable<T> {
getKeys(): Iterable<Key>,

/** 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,
Expand All @@ -160,7 +160,7 @@ export interface Node<T> {
/** 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. */
Expand All @@ -181,11 +181,11 @@ export interface Node<T> {
/** 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 */
Expand Down
2 changes: 1 addition & 1 deletion packages/react-aria-components/docs/Breadcrumbs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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));
};

Expand Down
4 changes: 2 additions & 2 deletions packages/react-aria-components/docs/Button.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -191,13 +191,13 @@ Each of these handlers receives a <TypeLink links={typesDocs.links} type={typesD

```tsx example
function Example() {
let [pointerType, setPointerType] = React.useState(null);
let [pointerType, setPointerType] = React.useState('');

return (
<>
<Button
onPressStart={e => setPointerType(e.pointerType)}
onPressEnd={e => setPointerType(null)}>
onPressEnd={() => setPointerType('')}>
Press me
</Button>
<p>{pointerType ? `You are pressing the button with a ${pointerType}!` : 'Ready to be pressed.'}</p>
Expand Down
6 changes: 3 additions & 3 deletions packages/react-aria-components/docs/Calendar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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<DateValue>(null);
let [date, setDate] = React.useState<DateValue | null>(null);
return (
<I18nProvider locale="hi-IN-u-ca-indian">
<MyCalendar aria-label="Date" value={date} onChange={setDate} />
Expand Down Expand Up @@ -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 <MyCalendar aria-label="Appointment date" minValue={today(getLocalTimeZone())} isDateUnavailable={isDateUnavailable} />
}
Expand Down Expand Up @@ -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} />
);
}
```
Expand Down
Loading