Skip to content

Commit

Permalink
Refactoring date components to use useFocusWithin (#3694)
Browse files Browse the repository at this point in the history
  • Loading branch information
ktabors authored Oct 29, 2022
1 parent 1c456fe commit 286dbcd
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 143 deletions.
43 changes: 10 additions & 33 deletions packages/@react-aria/datepicker/src/useDateField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import {createFocusManager, FocusManager} from '@react-aria/focus';
import {DateFieldState} from '@react-stately/datepicker';
import {DOMAttributes, KeyboardEvent} from '@react-types/shared';
import {filterDOMProps, mergeProps, useDescription} from '@react-aria/utils';
import {FocusEvent, RefObject, useEffect, useMemo, useRef} from 'react';
// @ts-ignore
import intlMessages from '../intl/*.json';
import {FocusEvent as ReactFocusEvent, RefObject, useEffect, useMemo, useRef} from 'react';
import {useDatePickerGroup} from './useDatePickerGroup';
import {useField} from '@react-aria/label';
import {useFocusWithin} from '@react-aria/interactions';
Expand Down Expand Up @@ -64,9 +64,16 @@ export function useDateField<T extends DateValue>(props: AriaDateFieldProps<T>,
});

let {focusWithinProps} = useFocusWithin({
onBlurWithin() {
...props,
onBlurWithin: (e: FocusEvent) => {
state.confirmPlaceholder();
}

if (props.onBlur) {
props.onBlur(e);
}
},
onFocusWithin: props.onFocus,
onFocusWithinChange: props.onFocusChange
});

let stringFormatter = useLocalizedStringFormatter(intlMessages);
Expand Down Expand Up @@ -136,36 +143,6 @@ export function useDateField<T extends DateValue>(props: AriaDateFieldProps<T>,
if (props.onKeyUp) {
props.onKeyUp(e);
}
},
onFocus(e: ReactFocusEvent) {
if (state.isFocused) {
return;
}

if (props.onFocus) {
props.onFocus(e);
}

if (props.onFocusChange) {
props.onFocusChange(true);
}

state.setFocused(true);
},
onBlur(e: ReactFocusEvent) {
if (e.currentTarget.contains(e.relatedTarget as Node)) {
return;
}

if (props.onBlur) {
props.onBlur(e);
}

if (props.onFocusChange) {
props.onFocusChange(false);
}

state.setFocused(false);
}
}),
descriptionProps,
Expand Down
51 changes: 11 additions & 40 deletions packages/@react-aria/datepicker/src/useDatePicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ import {DOMAttributes, KeyboardEvent} from '@react-types/shared';
import {filterDOMProps, mergeProps, useDescription, useId} from '@react-aria/utils';
// @ts-ignore
import intlMessages from '../intl/*.json';
import {FocusEvent as ReactFocusEvent, RefObject, useMemo} from 'react';
import {RefObject, useMemo} from 'react';
import {roleSymbol} from './useDateField';
import {useDatePickerGroup} from './useDatePickerGroup';
import {useField} from '@react-aria/label';
import {useFocusWithin} from '@react-aria/interactions';
import {useLocale, useLocalizedStringFormatter} from '@react-aria/i18n';

export interface DatePickerAria {
Expand Down Expand Up @@ -71,8 +72,16 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
let domProps = filterDOMProps(props);
let focusManager = useMemo(() => createFocusManager(ref), [ref]);

let {focusWithinProps} = useFocusWithin({
...props,
isDisabled: state.isOpen,
onBlurWithin: props.onBlur,
onFocusWithin: props.onFocus,
onFocusWithinChange: props.onFocusChange
});

return {
groupProps: mergeProps(domProps, groupProps, fieldProps, descProps, {
groupProps: mergeProps(domProps, groupProps, fieldProps, descProps, focusWithinProps, {
role: 'group',
'aria-disabled': props.isDisabled || null,
'aria-labelledby': labelledBy,
Expand All @@ -94,44 +103,6 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
if (props.onKeyUp) {
props.onKeyUp(e);
}
},
onFocus(e: ReactFocusEvent) {
if (state.isFocused) {
return;
}

if (state.isOpen) {
return;
}

if (props.onFocus) {
props.onFocus(e);
}

if (props.onFocusChange) {
props.onFocusChange(true);
}

state.setFocused(true);
},
onBlur(e: ReactFocusEvent) {
if (state.isOpen) {
return;
}

if (e.currentTarget.contains(e.relatedTarget as Node)) {
return;
}

if (props.onBlur) {
props.onBlur(e);
}

if (props.onFocusChange) {
props.onFocusChange(false);
}

state.setFocused(false);
}
}),
labelProps: {
Expand Down
51 changes: 11 additions & 40 deletions packages/@react-aria/datepicker/src/useDateRangePicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ import {focusManagerSymbol, roleSymbol} from './useDateField';
// @ts-ignore
import intlMessages from '../intl/*.json';
import {RangeCalendarProps} from '@react-types/calendar';
import {FocusEvent as ReactFocusEvent, RefObject, useMemo} from 'react';
import {RefObject, useMemo} from 'react';
import {useDatePickerGroup} from './useDatePickerGroup';
import {useField} from '@react-aria/label';
import {useFocusWithin} from '@react-aria/interactions';
import {useLocale, useLocalizedStringFormatter} from '@react-aria/i18n';

export interface DateRangePickerAria {
Expand Down Expand Up @@ -105,8 +106,16 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick

let domProps = filterDOMProps(props);

let {focusWithinProps} = useFocusWithin({
...props,
isDisabled: state.isOpen,
onBlurWithin: props.onBlur,
onFocusWithin: props.onFocus,
onFocusWithinChange: props.onFocusChange
});

return {
groupProps: mergeProps(domProps, groupProps, fieldProps, descProps, {
groupProps: mergeProps(domProps, groupProps, fieldProps, descProps, focusWithinProps, {
role: 'group',
'aria-disabled': props.isDisabled || null,
'aria-describedby': ariaDescribedBy,
Expand All @@ -127,44 +136,6 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
if (props.onKeyUp) {
props.onKeyUp(e);
}
},
onFocus(e: ReactFocusEvent) {
if (state.isFocused) {
return;
}

if (state.isOpen) {
return;
}

if (props.onFocus) {
props.onFocus(e);
}

if (props.onFocusChange) {
props.onFocusChange(true);
}

state.setFocused(true);
},
onBlur(e: ReactFocusEvent) {
if (state.isOpen) {
return;
}

if (e.currentTarget.contains(e.relatedTarget as Node)) {
return;
}

if (props.onBlur) {
props.onBlur(e);
}

if (props.onFocusChange) {
props.onFocusChange(false);
}

state.setFocused(false);
}
}),
labelProps: {
Expand Down
12 changes: 2 additions & 10 deletions packages/@react-stately/datepicker/src/useDateFieldState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,7 @@ export interface DateFieldState {
/** Clears the value of the given segment, reverting it to the placeholder. */
clearSegment(type: SegmentType): void,
/** Formats the current date value using the given options. */
formatValue(fieldOptions: FieldOptions): string,
/** Whether the date field is currently focused. */
readonly isFocused: boolean,
/** Sets whether the date field is focused. */
setFocused(isFocused: boolean): void
formatValue(fieldOptions: FieldOptions): string
}

const EDITABLE_SEGMENTS = {
Expand Down Expand Up @@ -155,8 +151,6 @@ export function useDateFieldState(props: DateFieldStateOptions): DateFieldState
let [granularity, defaultTimeZone] = useDefaultProps(v, props.granularity);
let timeZone = defaultTimeZone || 'UTC';

let [isFocused, setFocused] = useState(false);

// props.granularity must actually exist in the value if one is provided.
if (v && !(granularity in v)) {
throw new Error('Invalid granularity ' + granularity + ' for value ' + v.toString());
Expand Down Expand Up @@ -379,9 +373,7 @@ export function useDateFieldState(props: DateFieldStateOptions): DateFieldState
let formatOptions = getFormatOptions(fieldOptions, formatOpts);
let formatter = new DateFormatter(locale, formatOptions);
return formatter.format(dateValue);
},
isFocused,
setFocused
}
};
}

Expand Down
12 changes: 2 additions & 10 deletions packages/@react-stately/datepicker/src/useDatePickerState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,7 @@ export interface DatePickerState extends OverlayTriggerState {
/** The current validation state of the date picker, based on the `validationState`, `minValue`, and `maxValue` props. */
validationState: ValidationState,
/** Formats the selected value using the given options. */
formatValue(locale: string, fieldOptions: FieldOptions): string,
/** Whether the date picker is currently focused. */
readonly isFocused: boolean,
/** Sets whether the date picker is focused. */
setFocused(isFocused: boolean): void
formatValue(locale: string, fieldOptions: FieldOptions): string
}

/**
Expand All @@ -72,8 +68,6 @@ export function useDatePickerState<T extends DateValue = DateValue>(props: DateP
let overlayState = useOverlayTriggerState(props);
let [value, setValue] = useControlledState<DateValue>(props.value, props.defaultValue || null, props.onChange);

let [isFocused, setFocused] = useState(false);

let v = (value || props.placeholderValue);
let [granularity, defaultTimeZone] = useDefaultProps(v, props.granularity);
let dateValue = value != null ? value.toDate(defaultTimeZone ?? 'UTC') : null;
Expand Down Expand Up @@ -165,8 +159,6 @@ export function useDatePickerState<T extends DateValue = DateValue>(props: DateP

let formatter = new DateFormatter(locale, formatOptions);
return formatter.format(dateValue);
},
isFocused,
setFocused
}
};
}
12 changes: 2 additions & 10 deletions packages/@react-stately/datepicker/src/useDateRangePickerState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,7 @@ export interface DateRangePickerState extends OverlayTriggerState {
/** The current validation state of the date picker, based on the `validationState`, `minValue`, and `maxValue` props. */
validationState: ValidationState,
/** Formats the selected range using the given options. */
formatValue(locale: string, fieldOptions: FieldOptions): {start: string, end: string},
/** Whether the date range picker is currently focused. */
readonly isFocused: boolean,
/** Sets whether the date range picker is focused. */
setFocused(isFocused: boolean): void
formatValue(locale: string, fieldOptions: FieldOptions): {start: string, end: string}
}

/**
Expand All @@ -80,8 +76,6 @@ export function useDateRangePickerState(props: DateRangePickerStateOptions): Dat
let [controlledValue, setControlledValue] = useControlledState<DateRange>(props.value, props.defaultValue || null, props.onChange);
let [placeholderValue, setPlaceholderValue] = useState(() => controlledValue || {start: null, end: null});

let [isFocused, setFocused] = useState(false);

// Reset the placeholder if the value prop is set to null.
if (controlledValue == null && placeholderValue.start && placeholderValue.end) {
placeholderValue = {start: null, end: null};
Expand Down Expand Up @@ -269,8 +263,6 @@ export function useDateRangePickerState(props: DateRangePickerStateOptions): Dat
start: startFormatter.format(startDate),
end: endFormatter.format(endDate)
};
},
isFocused,
setFocused
}
};
}

1 comment on commit 286dbcd

@adobe-bot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.