Skip to content

Commit

Permalink
Merge pull request #4979 from hamidrezahanafi/hrh.removeonclickoutside
Browse files Browse the repository at this point in the history
Remove usages of react-onclickoutside to support React 19
  • Loading branch information
martijnrusschen authored Jul 18, 2024
2 parents 4e6c014 + 407f289 commit 7502407
Show file tree
Hide file tree
Showing 20 changed files with 196 additions and 113 deletions.
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
"@types/eslint": "^8.56.10",
"@types/jest": "^29.5.12",
"@types/node": "20",
"@types/react-onclickoutside": "^6.7.10",
"@typescript-eslint/eslint-plugin": "^7.9.0",
"@typescript-eslint/parser": "^7.9.0",
"axe-core": "^4.4.1",
Expand Down Expand Up @@ -83,8 +82,7 @@
"@floating-ui/react": "^0.26.2",
"clsx": "^2.1.0",
"date-fns": "^3.3.1",
"prop-types": "^15.7.2",
"react-onclickoutside": "^6.13.0"
"prop-types": "^15.7.2"
},
"scripts": {
"eslint": "eslint --ext .js,.jsx,.ts,.tsx ./src",
Expand Down
1 change: 0 additions & 1 deletion rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ const dateFnsSubpackages = Object.keys(dateFnsPackageJson.exports)
const globals = {
react: "React",
"prop-types": "PropTypes",
"react-onclickoutside": "onClickOutside",
};

// NOTE:https://rollupjs.org/migration/#changed-defaults
Expand Down
8 changes: 0 additions & 8 deletions src/@types/react-onclickoutside/index.d.ts

This file was deleted.

16 changes: 12 additions & 4 deletions src/calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { clsx } from "clsx";
import React, { Component, createRef } from "react";

import CalendarContainer from "./calendar_container";
import { ClickOutsideWrapper } from "./click_outside_wrapper";
import {
newDate,
setMonth,
Expand Down Expand Up @@ -48,6 +49,7 @@ import Time from "./time";
import Year from "./year";
import YearDropdown from "./year_dropdown";

import type { ClickOutsideHandler } from "./click_outside_wrapper";
import type { Day } from "date-fns/types";

interface YearDropdownProps
Expand Down Expand Up @@ -154,7 +156,8 @@ type CalendarProps = React.PropsWithChildren &
onDayMouseEnter?: (date: Date) => void;
onMonthMouseLeave?: VoidFunction;
weekLabel?: string;
onClickOutside: React.MouseEventHandler<HTMLElement>;
onClickOutside: ClickOutsideHandler;
outsideClickIgnoreClass?: string;
previousMonthButtonLabel?: React.ReactNode;
previousYearButtonLabel?: string;
previousMonthAriaLabel?: string;
Expand Down Expand Up @@ -279,7 +282,7 @@ export default class Calendar extends Component<CalendarProps, CalendarState> {

assignMonthContainer: void | undefined;

handleClickOutside = (event: React.MouseEvent<HTMLElement>): void => {
handleClickOutside = (event: MouseEvent): void => {
this.props.onClickOutside(event);
};

Expand Down Expand Up @@ -1084,7 +1087,12 @@ export default class Calendar extends Component<CalendarProps, CalendarState> {
render(): JSX.Element {
const Container = this.props.container || CalendarContainer;
return (
<div style={{ display: "contents" }} ref={this.containerRef}>
<ClickOutsideWrapper
onClickOutside={this.handleClickOutside}
style={{ display: "contents" }}
containerRef={this.containerRef}
ignoreClass={this.props.outsideClickIgnoreClass}
>
<Container
className={clsx("react-datepicker", this.props.className, {
"react-datepicker--time-only": this.props.showTimeSelectOnly,
Expand All @@ -1102,7 +1110,7 @@ export default class Calendar extends Component<CalendarProps, CalendarState> {
{this.renderInputTimeSection()}
{this.renderChildren()}
</Container>
</div>
</ClickOutsideWrapper>
);
}
}
69 changes: 69 additions & 0 deletions src/click_outside_wrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React, { useCallback, useEffect, useRef } from "react";

export type ClickOutsideHandler = (event: MouseEvent) => void;

interface ClickOutsideWrapperProps {
onClickOutside: ClickOutsideHandler;
className?: string;
children: React.ReactNode;
containerRef?: React.MutableRefObject<HTMLDivElement | null>;
style?: React.CSSProperties;
ignoreClass?: string;
}

const useDetectClickOutside = (
onClickOutside: ClickOutsideHandler,
ignoreClass?: string,
) => {
const ref = useRef<HTMLDivElement | null>(null);
const onClickOutsideRef = useRef(onClickOutside);
onClickOutsideRef.current = onClickOutside;
const handleClickOutside = useCallback(
(event: MouseEvent) => {
if (ref.current && !ref.current.contains(event.target as Node)) {
if (
!(
ignoreClass &&
event.target instanceof HTMLElement &&
event.target.classList.contains(ignoreClass)
)
) {
onClickOutsideRef.current?.(event);
}
}
},
[ignoreClass],
);
useEffect(() => {
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [handleClickOutside]);
return ref;
};

export const ClickOutsideWrapper: React.FC<ClickOutsideWrapperProps> = ({
children,
onClickOutside,
className,
containerRef,
style,
ignoreClass,
}) => {
const detectRef = useDetectClickOutside(onClickOutside, ignoreClass);
return (
<div
className={className}
style={style}
ref={(node: HTMLDivElement | null) => {
detectRef.current = node;
if (containerRef) {
containerRef.current = node;
}
}}
>
{children}
</div>
);
};
32 changes: 16 additions & 16 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { clsx } from "clsx";
import React, { Component, cloneElement } from "react";
import onClickOutside, {
type WrapperInstance,
type AdditionalProps,
} from "react-onclickoutside";

import Calendar from "./calendar";
import CalendarIcon from "./calendar_icon";
Expand Down Expand Up @@ -59,12 +55,13 @@ import PopperComponent from "./popper_component";
import Portal from "./portal";
import TabLoop from "./tab_loop";

import type { ClickOutsideHandler } from "./click_outside_wrapper";

export { default as CalendarContainer } from "./calendar_container";

export { registerLocale, setDefaultLocale, getDefaultLocale };

const outsideClickIgnoreClass = "react-datepicker-ignore-onclickoutside";
const WrappedCalendar = onClickOutside(Calendar);

export { ReactDatePickerCustomHeaderProps } from "./calendar";

Expand Down Expand Up @@ -102,7 +99,10 @@ interface PortalProps extends React.ComponentPropsWithoutRef<typeof Portal> {}
interface PopperComponentProps
extends React.ComponentPropsWithoutRef<typeof PopperComponent> {}

export type DatePickerProps = Omit<
// see https://github.com/microsoft/TypeScript/issues/31501
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type OmitUnion<T, K extends keyof any> = T extends any ? Omit<T, K> : never;
export type DatePickerProps = OmitUnion<
CalendarProps,
| "setOpen"
| "dateFormat"
Expand All @@ -126,10 +126,9 @@ export type DatePickerProps = Omit<
| "selectsMultiple"
| "dropdownMode"
> &
Partial<Pick<AdditionalProps, "excludeScrollbar">> &
Pick<CalendarIconProps, "icon"> &
Omit<PortalProps, "children" | "portalId"> &
Omit<
OmitUnion<PortalProps, "children" | "portalId"> &
OmitUnion<
PopperComponentProps,
| "className"
| "hidePopper"
Expand All @@ -151,7 +150,7 @@ export type DatePickerProps = Omit<
startOpen?: boolean;
onFocus?: React.FocusEventHandler<HTMLElement>;
onBlur?: React.FocusEventHandler<HTMLElement>;
onClickOutside?: React.MouseEventHandler<HTMLElement>;
onClickOutside?: ClickOutsideHandler;
onInputClick?: VoidFunction;
preventOpenOnFocus?: boolean;
closeOnScroll?: boolean | ((event: Event) => boolean);
Expand Down Expand Up @@ -383,7 +382,7 @@ export default class DatePicker extends Component<

inputFocusTimeout: ReturnType<typeof setTimeout> | undefined;

calendar: WrapperInstance<CalendarProps, typeof Calendar> | null = null;
calendar: Calendar | null = null;

input: HTMLElement | null = null;

Expand Down Expand Up @@ -568,7 +567,7 @@ export default class DatePicker extends Component<
this.setState({ focused: false });
};

handleCalendarClickOutside = (event: React.MouseEvent<HTMLElement>) => {
handleCalendarClickOutside = (event: MouseEvent) => {
if (!this.props.inline) {
this.setOpen(false);
}
Expand Down Expand Up @@ -932,8 +931,8 @@ export default class DatePicker extends Component<
? '.react-datepicker__month-text[tabindex="0"]'
: '.react-datepicker__day[tabindex="0"]';
const selectedItem =
this.calendar?.componentNode instanceof Element &&
this.calendar.componentNode.querySelector(selectorString);
this.calendar?.containerRef.current instanceof Element &&
this.calendar.containerRef.current.querySelector(selectorString);
selectedItem instanceof HTMLElement &&
selectedItem.focus({ preventScroll: true });

Expand Down Expand Up @@ -1214,7 +1213,8 @@ export default class DatePicker extends Component<
return null;
}
return (
<WrappedCalendar
<Calendar
showMonthYearDropdown={undefined}
ref={(elem) => {
this.calendar = elem;
}}
Expand All @@ -1241,7 +1241,7 @@ export default class DatePicker extends Component<
}
>
{this.props.children}
</WrappedCalendar>
</Calendar>
);
};

Expand Down
5 changes: 1 addition & 4 deletions src/month_dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { Component } from "react";
import onClickOutside from "react-onclickoutside";

import {
getMonthShortInLocale,
Expand All @@ -11,8 +10,6 @@ import MonthDropdownOptions from "./month_dropdown_options";
interface MonthDropdownOptionsProps
extends React.ComponentPropsWithoutRef<typeof MonthDropdownOptions> {}

const WrappedMonthDropdownOptions = onClickOutside(MonthDropdownOptions);

interface MonthDropdownProps
extends Omit<
MonthDropdownOptionsProps,
Expand Down Expand Up @@ -70,7 +67,7 @@ export default class MonthDropdown extends Component<
);

renderDropdown = (monthNames: string[]): JSX.Element => (
<WrappedMonthDropdownOptions
<MonthDropdownOptions
key="dropdown"
{...this.props}
monthNames={monthNames}
Expand Down
9 changes: 7 additions & 2 deletions src/month_dropdown_options.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, { Component } from "react";

import { ClickOutsideWrapper } from "./click_outside_wrapper";

interface MonthDropdownOptionsProps {
onCancel: VoidFunction;
onChange: (month: number) => void;
Expand Down Expand Up @@ -40,9 +42,12 @@ export default class MonthDropdownOptions extends Component<MonthDropdownOptions

render(): JSX.Element {
return (
<div className="react-datepicker__month-dropdown">
<ClickOutsideWrapper
className="react-datepicker__month-dropdown"
onClickOutside={this.handleClickOutside}
>
{this.renderOptions()}
</div>
</ClickOutsideWrapper>
);
}
}
7 changes: 1 addition & 6 deletions src/month_year_dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { Component } from "react";
import onClickOutside from "react-onclickoutside";

import {
addMonths,
Expand All @@ -17,10 +16,6 @@ import MonthYearDropdownOptions from "./month_year_dropdown_options";
interface MonthYearDropdownOptionsProps
extends React.ComponentPropsWithoutRef<typeof MonthYearDropdownOptions> {}

const WrappedMonthYearDropdownOptions = onClickOutside(
MonthYearDropdownOptions,
);

interface MonthYearDropdownProps
extends Omit<MonthYearDropdownOptionsProps, "onChange" | "onCancel"> {
dropdownMode: "scroll" | "select";
Expand Down Expand Up @@ -96,7 +91,7 @@ export default class MonthYearDropdown extends Component<
};

renderDropdown = (): JSX.Element => (
<WrappedMonthYearDropdownOptions
<MonthYearDropdownOptions
key="dropdown"
{...this.props}
onChange={this.onChange}
Expand Down
10 changes: 9 additions & 1 deletion src/month_year_dropdown_options.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { clsx } from "clsx";
import React, { Component } from "react";

import { ClickOutsideWrapper } from "./click_outside_wrapper";
import {
addMonths,
formatDate,
Expand Down Expand Up @@ -103,6 +104,13 @@ export default class MonthYearDropdownOptions extends Component<
this.props.scrollableMonthYearDropdown,
});

return <div className={dropdownClass}>{this.renderOptions()}</div>;
return (
<ClickOutsideWrapper
className={dropdownClass}
onClickOutside={this.handleClickOutside}
>
{this.renderOptions()}
</ClickOutsideWrapper>
);
}
}
Loading

0 comments on commit 7502407

Please sign in to comment.