Skip to content

Commit 56b9187

Browse files
committed
fix: correctly close relative pickers dialogs
1 parent e126fc9 commit 56b9187

File tree

8 files changed

+72
-37
lines changed

8 files changed

+72
-37
lines changed

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/DatePicker/hooks/useDatePickerProps.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ export function useDatePickerProps<T extends DateTime | RangeValue<DateTime>>(
150150
);
151151
},
152152
modal: !props.disableFocusTrap,
153+
returnFocus: !props.disableFocusTrap,
153154
disablePortal: props.disablePortal,
154155
placement: props.popupPlacement,
155156
offset: props.popupOffset,

src/components/RelativeDateField/RelativeDateField.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,13 @@ export function RelativeDateField(props: RelativeDateFieldProps) {
5454
const [anchor, setAnchor] = React.useState<HTMLElement | null>(null);
5555

5656
const [isOpen, setOpen] = React.useState(false);
57+
const dialogClosing = React.useRef(false);
5758

5859
const {focusWithinProps} = useFocusWithin({
5960
onFocusWithinChange: (isFocusWithin) => {
60-
setOpen(isFocusWithin);
61+
if (!dialogClosing.current) {
62+
setOpen(isFocusWithin);
63+
}
6164
},
6265
isDisabled: isMobile,
6366
});
@@ -86,6 +89,12 @@ export function RelativeDateField(props: RelativeDateFieldProps) {
8689
setOpen(true);
8790
}
8891
}}
92+
controlProps={{
93+
...inputProps.controlProps,
94+
onClick: () => {
95+
setOpen(true);
96+
},
97+
}}
8998
/>
9099
<HiddenInput
91100
name={props.name}
@@ -100,10 +109,16 @@ export function RelativeDateField(props: RelativeDateFieldProps) {
100109
<Popup
101110
anchorElement={anchor}
102111
open={isOpen}
103-
onOpenChange={(_open, _event, reason) => {
104-
if (reason === 'escape-key') {
112+
onOpenChange={(open) => {
113+
if (!open) {
105114
setOpen(false);
106115
}
116+
dialogClosing.current = !open;
117+
}}
118+
onTransitionOutComplete={() => {
119+
setTimeout(() => {
120+
dialogClosing.current = false;
121+
});
107122
}}
108123
placement={props.popupPlacement}
109124
offset={props.popupOffset}

src/components/RelativeDatePicker/hooks/useRelativeDatePickerProps.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ export function useRelativeDatePickerProps(
217217
offset: props.popupOffset,
218218
className: props.popupClassName,
219219
style: props.popupStyle,
220+
returnFocus: false,
220221
},
221222
calendarProps: {
222223
ref: calendarRef,

src/components/RelativeRangeDatePicker/RelativeRangeDatePicker.tsx

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,22 @@ export function RelativeRangeDatePicker(props: RelativeRangeDatePickerProps) {
2323
const isMobile = useMobile();
2424

2525
const [anchor, setAnchor] = React.useState<HTMLDivElement | null>(null);
26-
const inputRef = React.useRef<HTMLInputElement>(null);
26+
const dialogClosing = React.useRef(false);
2727

28-
const [isActive, setIsActive] = React.useState(false);
2928
const [open, setOpen] = useControlledState<boolean>(undefined, false, props.onOpenChange);
3029

3130
const {focusWithinProps} = useFocusWithin({
3231
isDisabled: props.disabled,
33-
onFocusWithin: (e) => {
34-
if (!isActive) {
35-
props.onFocus?.(e);
36-
}
37-
},
38-
onBlurWithin: (e) => {
39-
if (!open) {
40-
setIsActive(false);
41-
props.onBlur?.(e);
32+
onFocusWithin: props.onFocus,
33+
onBlurWithin: props.onBlur,
34+
onFocusWithinChange: (isFocusWithin) => {
35+
if (
36+
isFocusWithin &&
37+
!dialogClosing.current &&
38+
!props.renderControl &&
39+
document.activeElement?.tagName !== 'BUTTON'
40+
) {
41+
setOpen(true);
4242
}
4343
},
4444
});
@@ -54,16 +54,13 @@ export function RelativeRangeDatePicker(props: RelativeRangeDatePickerProps) {
5454
props={props}
5555
state={state}
5656
open={open}
57+
setOpen={setOpen}
5758
isMobile={isMobile}
58-
ref={inputRef}
5959
onClick={() => {
6060
if (props.disabled) {
6161
return;
6262
}
63-
if (!open) {
64-
setIsActive(true);
65-
setOpen(true);
66-
}
63+
setOpen(true);
6764
}}
6865
onKeyDown={(e) => {
6966
if (props.disabled) {
@@ -75,15 +72,8 @@ export function RelativeRangeDatePicker(props: RelativeRangeDatePickerProps) {
7572
}
7673
}}
7774
onClickCalendar={() => {
78-
setIsActive(true);
7975
setOpen(!open);
8076
}}
81-
onFocus={() => {
82-
if (!isActive) {
83-
setIsActive(true);
84-
setOpen(true);
85-
}
86-
}}
8777
onUpdate={(v: string) => {
8878
if (!props.readOnly && !v) {
8979
state.setValue(null, 'default');
@@ -134,16 +124,18 @@ export function RelativeRangeDatePicker(props: RelativeRangeDatePickerProps) {
134124
onUpdate={state.setValue}
135125
timeZone={state.timeZone}
136126
open={open}
137-
onClose={(reason) => {
127+
onClose={() => {
138128
setOpen(false);
139-
if (reason === 'escape-key') {
140-
setTimeout(() => {
141-
inputRef.current?.focus({preventScroll: true});
142-
});
143-
}
129+
dialogClosing.current = true;
130+
}}
131+
onRemove={() => {
132+
setTimeout(() => {
133+
dialogClosing.current = false;
134+
});
144135
}}
145136
anchor={anchor}
146137
modal
138+
returnFocus
147139
popupClassName={props.popupClassName}
148140
popupStyle={props.popupStyle}
149141
popupPlacement={props.popupPlacement}

src/components/RelativeRangeDatePicker/components/Control/Control.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ export type ControlProps = {
1717
props: RelativeRangeDatePickerProps;
1818
state: RelativeRangeDatePickerState;
1919
open: boolean;
20+
setOpen: (open: boolean) => void;
2021
isMobile?: boolean;
2122
onClick: (e: React.MouseEvent<HTMLElement>) => void;
2223
onKeyDown: (e: React.KeyboardEvent<HTMLElement>) => void;
23-
onFocus: (e: React.FocusEvent<HTMLElement>) => void;
24+
onFocus?: (e: React.FocusEvent<HTMLElement>) => void;
2425
onClickCalendar: (e: React.MouseEvent<HTMLElement>) => void;
2526
onUpdate: (value: string) => void;
2627
};
@@ -29,7 +30,18 @@ const b = block('relative-range-date-picker-control');
2930

3031
export const Control = React.forwardRef<HTMLInputElement, ControlProps>(
3132
(
32-
{props, state, open, isMobile, onClick, onKeyDown, onFocus, onClickCalendar, onUpdate},
33+
{
34+
props,
35+
state,
36+
open,
37+
setOpen,
38+
isMobile,
39+
onClick,
40+
onKeyDown,
41+
onFocus,
42+
onClickCalendar,
43+
onUpdate,
44+
},
3345
ref,
3446
) => {
3547
const {alwaysShowAsAbsolute, presetTabs, getRangeTitle} = props;
@@ -75,6 +87,7 @@ export const Control = React.forwardRef<HTMLInputElement, ControlProps>(
7587
validationState,
7688
errorMessage,
7789
open,
90+
setOpen,
7891
triggerProps,
7992
})
8093
) : (
@@ -111,6 +124,7 @@ export const Control = React.forwardRef<HTMLInputElement, ControlProps>(
111124
aria-expanded={open}
112125
aria-label={i18n('Range date picker')}
113126
onClick={onClickCalendar}
127+
tabIndex={-1}
114128
>
115129
<Icon data={CalendarIcon} />
116130
</Button>

src/components/RelativeRangeDatePicker/components/PickerDialog/PickerDialog.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,12 @@ export interface PickerDialogProps extends PickerFormProps, PopupStyleProps {
2020
open: boolean;
2121
/** Handles popup close event */
2222
onClose: (reason?: Parameters<NonNullable<PopupProps['onOpenChange']>>[2] | 'apply') => void;
23+
/** Handles popup remove from DOM event */
24+
onRemove?: () => void;
2325
/** If true `Popup` act like a modal dialog */
2426
modal?: boolean;
27+
/** Element which focus should be returned to */
28+
returnFocus?: PopupProps['returnFocus'];
2529
/** If true `Sheet` is used instead of `Popup` */
2630
isMobile?: boolean;
2731
}
@@ -36,6 +40,8 @@ export function PickerDialog({
3640
popupPlacement,
3741
popupOffset,
3842
modal,
43+
returnFocus = false,
44+
onRemove,
3945
...props
4046
}: PickerDialogProps) {
4147
if (isMobile) {
@@ -44,6 +50,9 @@ export function PickerDialog({
4450
visible={open}
4551
onClose={() => {
4652
onClose('outside-press');
53+
setTimeout(() => {
54+
onRemove?.();
55+
});
4756
}}
4857
contentClassName={b('content', {mobile: true, size: 'xl'}, popupClassName)}
4958
>
@@ -73,6 +82,8 @@ export function PickerDialog({
7382
className={b('content', {size: props.size}, popupClassName)}
7483
style={popupStyle}
7584
modal={modal}
85+
returnFocus={returnFocus}
86+
onTransitionOutComplete={onRemove}
7687
>
7788
<PickerForm
7889
{...props}

src/components/RelativeRangeDatePicker/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export interface RelativeRangeDatePickerRenderControlProps {
4545
errorMessage?: React.ReactNode;
4646
validationState?: 'invalid';
4747
open: boolean;
48+
setOpen: (open: boolean) => void;
4849
triggerProps: RelativeRangeDatePickerTriggerProps;
4950
}
5051

0 commit comments

Comments
 (0)