Skip to content

Commit 286dbcd

Browse files
authored
Refactoring date components to use useFocusWithin (#3694)
1 parent 1c456fe commit 286dbcd

File tree

6 files changed

+38
-143
lines changed

6 files changed

+38
-143
lines changed

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

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ import {createFocusManager, FocusManager} from '@react-aria/focus';
1515
import {DateFieldState} from '@react-stately/datepicker';
1616
import {DOMAttributes, KeyboardEvent} from '@react-types/shared';
1717
import {filterDOMProps, mergeProps, useDescription} from '@react-aria/utils';
18+
import {FocusEvent, RefObject, useEffect, useMemo, useRef} from 'react';
1819
// @ts-ignore
1920
import intlMessages from '../intl/*.json';
20-
import {FocusEvent as ReactFocusEvent, RefObject, useEffect, useMemo, useRef} from 'react';
2121
import {useDatePickerGroup} from './useDatePickerGroup';
2222
import {useField} from '@react-aria/label';
2323
import {useFocusWithin} from '@react-aria/interactions';
@@ -64,9 +64,16 @@ export function useDateField<T extends DateValue>(props: AriaDateFieldProps<T>,
6464
});
6565

6666
let {focusWithinProps} = useFocusWithin({
67-
onBlurWithin() {
67+
...props,
68+
onBlurWithin: (e: FocusEvent) => {
6869
state.confirmPlaceholder();
69-
}
70+
71+
if (props.onBlur) {
72+
props.onBlur(e);
73+
}
74+
},
75+
onFocusWithin: props.onFocus,
76+
onFocusWithinChange: props.onFocusChange
7077
});
7178

7279
let stringFormatter = useLocalizedStringFormatter(intlMessages);
@@ -136,36 +143,6 @@ export function useDateField<T extends DateValue>(props: AriaDateFieldProps<T>,
136143
if (props.onKeyUp) {
137144
props.onKeyUp(e);
138145
}
139-
},
140-
onFocus(e: ReactFocusEvent) {
141-
if (state.isFocused) {
142-
return;
143-
}
144-
145-
if (props.onFocus) {
146-
props.onFocus(e);
147-
}
148-
149-
if (props.onFocusChange) {
150-
props.onFocusChange(true);
151-
}
152-
153-
state.setFocused(true);
154-
},
155-
onBlur(e: ReactFocusEvent) {
156-
if (e.currentTarget.contains(e.relatedTarget as Node)) {
157-
return;
158-
}
159-
160-
if (props.onBlur) {
161-
props.onBlur(e);
162-
}
163-
164-
if (props.onFocusChange) {
165-
props.onFocusChange(false);
166-
}
167-
168-
state.setFocused(false);
169146
}
170147
}),
171148
descriptionProps,

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

Lines changed: 11 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@ import {DOMAttributes, KeyboardEvent} from '@react-types/shared';
2020
import {filterDOMProps, mergeProps, useDescription, useId} from '@react-aria/utils';
2121
// @ts-ignore
2222
import intlMessages from '../intl/*.json';
23-
import {FocusEvent as ReactFocusEvent, RefObject, useMemo} from 'react';
23+
import {RefObject, useMemo} from 'react';
2424
import {roleSymbol} from './useDateField';
2525
import {useDatePickerGroup} from './useDatePickerGroup';
2626
import {useField} from '@react-aria/label';
27+
import {useFocusWithin} from '@react-aria/interactions';
2728
import {useLocale, useLocalizedStringFormatter} from '@react-aria/i18n';
2829

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

75+
let {focusWithinProps} = useFocusWithin({
76+
...props,
77+
isDisabled: state.isOpen,
78+
onBlurWithin: props.onBlur,
79+
onFocusWithin: props.onFocus,
80+
onFocusWithinChange: props.onFocusChange
81+
});
82+
7483
return {
75-
groupProps: mergeProps(domProps, groupProps, fieldProps, descProps, {
84+
groupProps: mergeProps(domProps, groupProps, fieldProps, descProps, focusWithinProps, {
7685
role: 'group',
7786
'aria-disabled': props.isDisabled || null,
7887
'aria-labelledby': labelledBy,
@@ -94,44 +103,6 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
94103
if (props.onKeyUp) {
95104
props.onKeyUp(e);
96105
}
97-
},
98-
onFocus(e: ReactFocusEvent) {
99-
if (state.isFocused) {
100-
return;
101-
}
102-
103-
if (state.isOpen) {
104-
return;
105-
}
106-
107-
if (props.onFocus) {
108-
props.onFocus(e);
109-
}
110-
111-
if (props.onFocusChange) {
112-
props.onFocusChange(true);
113-
}
114-
115-
state.setFocused(true);
116-
},
117-
onBlur(e: ReactFocusEvent) {
118-
if (state.isOpen) {
119-
return;
120-
}
121-
122-
if (e.currentTarget.contains(e.relatedTarget as Node)) {
123-
return;
124-
}
125-
126-
if (props.onBlur) {
127-
props.onBlur(e);
128-
}
129-
130-
if (props.onFocusChange) {
131-
props.onFocusChange(false);
132-
}
133-
134-
state.setFocused(false);
135106
}
136107
}),
137108
labelProps: {

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

Lines changed: 11 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ import {focusManagerSymbol, roleSymbol} from './useDateField';
2121
// @ts-ignore
2222
import intlMessages from '../intl/*.json';
2323
import {RangeCalendarProps} from '@react-types/calendar';
24-
import {FocusEvent as ReactFocusEvent, RefObject, useMemo} from 'react';
24+
import {RefObject, useMemo} from 'react';
2525
import {useDatePickerGroup} from './useDatePickerGroup';
2626
import {useField} from '@react-aria/label';
27+
import {useFocusWithin} from '@react-aria/interactions';
2728
import {useLocale, useLocalizedStringFormatter} from '@react-aria/i18n';
2829

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

106107
let domProps = filterDOMProps(props);
107108

109+
let {focusWithinProps} = useFocusWithin({
110+
...props,
111+
isDisabled: state.isOpen,
112+
onBlurWithin: props.onBlur,
113+
onFocusWithin: props.onFocus,
114+
onFocusWithinChange: props.onFocusChange
115+
});
116+
108117
return {
109-
groupProps: mergeProps(domProps, groupProps, fieldProps, descProps, {
118+
groupProps: mergeProps(domProps, groupProps, fieldProps, descProps, focusWithinProps, {
110119
role: 'group',
111120
'aria-disabled': props.isDisabled || null,
112121
'aria-describedby': ariaDescribedBy,
@@ -127,44 +136,6 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
127136
if (props.onKeyUp) {
128137
props.onKeyUp(e);
129138
}
130-
},
131-
onFocus(e: ReactFocusEvent) {
132-
if (state.isFocused) {
133-
return;
134-
}
135-
136-
if (state.isOpen) {
137-
return;
138-
}
139-
140-
if (props.onFocus) {
141-
props.onFocus(e);
142-
}
143-
144-
if (props.onFocusChange) {
145-
props.onFocusChange(true);
146-
}
147-
148-
state.setFocused(true);
149-
},
150-
onBlur(e: ReactFocusEvent) {
151-
if (state.isOpen) {
152-
return;
153-
}
154-
155-
if (e.currentTarget.contains(e.relatedTarget as Node)) {
156-
return;
157-
}
158-
159-
if (props.onBlur) {
160-
props.onBlur(e);
161-
}
162-
163-
if (props.onFocusChange) {
164-
props.onFocusChange(false);
165-
}
166-
167-
state.setFocused(false);
168139
}
169140
}),
170141
labelProps: {

packages/@react-stately/datepicker/src/useDateFieldState.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,7 @@ export interface DateFieldState {
8787
/** Clears the value of the given segment, reverting it to the placeholder. */
8888
clearSegment(type: SegmentType): void,
8989
/** Formats the current date value using the given options. */
90-
formatValue(fieldOptions: FieldOptions): string,
91-
/** Whether the date field is currently focused. */
92-
readonly isFocused: boolean,
93-
/** Sets whether the date field is focused. */
94-
setFocused(isFocused: boolean): void
90+
formatValue(fieldOptions: FieldOptions): string
9591
}
9692

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

158-
let [isFocused, setFocused] = useState(false);
159-
160154
// props.granularity must actually exist in the value if one is provided.
161155
if (v && !(granularity in v)) {
162156
throw new Error('Invalid granularity ' + granularity + ' for value ' + v.toString());
@@ -379,9 +373,7 @@ export function useDateFieldState(props: DateFieldStateOptions): DateFieldState
379373
let formatOptions = getFormatOptions(fieldOptions, formatOpts);
380374
let formatter = new DateFormatter(locale, formatOptions);
381375
return formatter.format(dateValue);
382-
},
383-
isFocused,
384-
setFocused
376+
}
385377
};
386378
}
387379

packages/@react-stately/datepicker/src/useDatePickerState.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,7 @@ export interface DatePickerState extends OverlayTriggerState {
5757
/** The current validation state of the date picker, based on the `validationState`, `minValue`, and `maxValue` props. */
5858
validationState: ValidationState,
5959
/** Formats the selected value using the given options. */
60-
formatValue(locale: string, fieldOptions: FieldOptions): string,
61-
/** Whether the date picker is currently focused. */
62-
readonly isFocused: boolean,
63-
/** Sets whether the date picker is focused. */
64-
setFocused(isFocused: boolean): void
60+
formatValue(locale: string, fieldOptions: FieldOptions): string
6561
}
6662

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

75-
let [isFocused, setFocused] = useState(false);
76-
7771
let v = (value || props.placeholderValue);
7872
let [granularity, defaultTimeZone] = useDefaultProps(v, props.granularity);
7973
let dateValue = value != null ? value.toDate(defaultTimeZone ?? 'UTC') : null;
@@ -165,8 +159,6 @@ export function useDatePickerState<T extends DateValue = DateValue>(props: DateP
165159

166160
let formatter = new DateFormatter(locale, formatOptions);
167161
return formatter.format(dateValue);
168-
},
169-
isFocused,
170-
setFocused
162+
}
171163
};
172164
}

packages/@react-stately/datepicker/src/useDateRangePickerState.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,7 @@ export interface DateRangePickerState extends OverlayTriggerState {
6363
/** The current validation state of the date picker, based on the `validationState`, `minValue`, and `maxValue` props. */
6464
validationState: ValidationState,
6565
/** Formats the selected range using the given options. */
66-
formatValue(locale: string, fieldOptions: FieldOptions): {start: string, end: string},
67-
/** Whether the date range picker is currently focused. */
68-
readonly isFocused: boolean,
69-
/** Sets whether the date range picker is focused. */
70-
setFocused(isFocused: boolean): void
66+
formatValue(locale: string, fieldOptions: FieldOptions): {start: string, end: string}
7167
}
7268

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

83-
let [isFocused, setFocused] = useState(false);
84-
8579
// Reset the placeholder if the value prop is set to null.
8680
if (controlledValue == null && placeholderValue.start && placeholderValue.end) {
8781
placeholderValue = {start: null, end: null};
@@ -269,8 +263,6 @@ export function useDateRangePickerState(props: DateRangePickerStateOptions): Dat
269263
start: startFormatter.format(startDate),
270264
end: endFormatter.format(endDate)
271265
};
272-
},
273-
isFocused,
274-
setFocused
266+
}
275267
};
276268
}

0 commit comments

Comments
 (0)