diff --git a/.changeset/soft-apricots-sleep.md b/.changeset/soft-apricots-sleep.md new file mode 100644 index 0000000000..f3f71fc186 --- /dev/null +++ b/.changeset/soft-apricots-sleep.md @@ -0,0 +1,6 @@ +--- +"@nextui-org/modal": minor +"@nextui-org/use-draggable": minor +--- + +Add draggable modal (#2647) diff --git a/apps/docs/content/components/modal/draggable-overflow.ts b/apps/docs/content/components/modal/draggable-overflow.ts new file mode 100644 index 0000000000..da7a442fda --- /dev/null +++ b/apps/docs/content/components/modal/draggable-overflow.ts @@ -0,0 +1,45 @@ +const App = `import {Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button, useDisclosure, useDraggable} from "@nextui-org/react"; + +export default function App() { + const {isOpen, onOpen, onOpenChange} = useDisclosure(); + const targetRef = React.useRef(null); + const {moveProps} = useDraggable({targetRef, canOverflow: true}); + + return ( + <> + + + + {(onClose) => ( + <> + Modal Title + +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. + Nullam pulvinar risus non risus hendrerit venenatis. + Pellentesque sit amet hendrerit risus, sed porttitor quam. +

+
+ + + + + + )} +
+
+ + ); +}`; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/modal/draggable.ts b/apps/docs/content/components/modal/draggable.ts new file mode 100644 index 0000000000..a2dada19a7 --- /dev/null +++ b/apps/docs/content/components/modal/draggable.ts @@ -0,0 +1,45 @@ +const App = `import {Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button, useDisclosure, useDraggable} from "@nextui-org/react"; + +export default function App() { + const {isOpen, onOpen, onOpenChange} = useDisclosure(); + const targetRef = React.useRef(null); + const {moveProps} = useDraggable({ targetRef }); + + return ( + <> + + + + {(onClose) => ( + <> + Modal Title + +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. + Nullam pulvinar risus non risus hendrerit venenatis. + Pellentesque sit amet hendrerit risus, sed porttitor quam. +

+
+ + + + + + )} +
+
+ + ); +}`; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/modal/index.ts b/apps/docs/content/components/modal/index.ts index b1e1808e4c..d95be572a0 100644 --- a/apps/docs/content/components/modal/index.ts +++ b/apps/docs/content/components/modal/index.ts @@ -8,6 +8,8 @@ import backdrop from "./backdrop"; import customBackdrop from "./custom-backdrop"; import customMotion from "./custom-motion"; import customStyles from "./custom-styles"; +import draggable from "./draggable"; +import draggableOverflow from "./draggable-overflow"; export const modalContent = { usage, @@ -20,4 +22,6 @@ export const modalContent = { customBackdrop, customMotion, customStyles, + draggable, + draggableOverflow, }; diff --git a/apps/docs/content/docs/components/modal.mdx b/apps/docs/content/docs/components/modal.mdx index e7d2c86b29..9c516b71df 100644 --- a/apps/docs/content/docs/components/modal.mdx +++ b/apps/docs/content/docs/components/modal.mdx @@ -41,18 +41,18 @@ NextUI exports 5 modal-related components: Esc key. +By default, the modal can be closed by clicking on the overlay or pressing the Esc key. You can disable this behavior by setting the following properties: - Set the `isDismissable` property to `false` to prevent the modal from closing when clicking on the overlay. @@ -137,6 +137,18 @@ Modal offers a `motionProps` property to customize the `enter` / `exit` animatio > Learn more about Framer motion variants [here](https://www.framer.com/motion/animation/#variants). +### Draggable + +Try to drag the header part. + + + +### Draggable Overflow + +Set overflow to true can drag overflow the viewport. + + + ## Slots - **wrapper**: The wrapper slot of the modal. It wraps the `base` and the `backdrop` slots. diff --git a/packages/components/modal/__tests__/modal.test.tsx b/packages/components/modal/__tests__/modal.test.tsx index 70b5225690..50201acb9e 100644 --- a/packages/components/modal/__tests__/modal.test.tsx +++ b/packages/components/modal/__tests__/modal.test.tsx @@ -1,12 +1,28 @@ import * as React from "react"; import {act, render, fireEvent} from "@testing-library/react"; -import {Modal, ModalContent, ModalBody, ModalHeader, ModalFooter} from "../src"; +import {Modal, ModalContent, ModalBody, ModalHeader, ModalFooter, useDraggable} from "../src"; // e.g. console.error Warning: Function components cannot be given refs. // Attempts to access this ref will fail. Did you mean to use React.forwardRef()? const spy = jest.spyOn(console, "error").mockImplementation(() => {}); +const ModalDraggable = ({canOverflow = false, isDisabled = false}) => { + const targetRef = React.useRef(null); + + const {moveProps} = useDraggable({targetRef, canOverflow, isDisabled}); + + return ( + + + Modal header + Modal body + Modal footer + + + ); +}; + describe("Modal", () => { afterEach(() => { jest.clearAllMocks(); @@ -108,4 +124,93 @@ describe("Modal", () => { fireEvent.keyDown(modal, {key: "Escape"}); expect(onClose).toHaveBeenCalledTimes(1); }); + + it("should be rendered a draggable modal", () => { + // mock viewport size to 1920x1080 + jest.spyOn(document.documentElement, "clientWidth", "get").mockImplementation(() => 1920); + jest.spyOn(document.documentElement, "clientHeight", "get").mockImplementation(() => 1080); + + const wrapper = render(); + + const modal = wrapper.getByRole("dialog"); + const modalHeader = wrapper.getByText("Modal header"); + + fireEvent.touchStart(modalHeader, {changedTouches: [{pageX: 0, pageY: 0}]}); + fireEvent.touchMove(modalHeader, {changedTouches: [{pageX: 100, pageY: 50}]}); + fireEvent.touchEnd(modalHeader, {changedTouches: [{pageX: 100, pageY: 50}]}); + + expect(() => wrapper.unmount()).not.toThrow(); + expect(document.documentElement.clientWidth).toBe(1920); + expect(document.documentElement.clientHeight).toBe(1080); + expect(modalHeader.style.cursor).toBe("move"); + expect(modal.style.transform).toBe("translate(100px, 50px)"); + }); + + it("should be rendered a draggable modal on mobile", () => { + // mock viewport size to 375x667 + jest.spyOn(document.documentElement, "clientWidth", "get").mockImplementation(() => 375); + jest.spyOn(document.documentElement, "clientHeight", "get").mockImplementation(() => 667); + + const wrapper = render(); + + const modal = wrapper.getByRole("dialog"); + const modalHeader = wrapper.getByText("Modal header"); + + fireEvent.touchStart(modalHeader, {changedTouches: [{pageX: 0, pageY: 0}]}); + fireEvent.touchMove(modalHeader, {changedTouches: [{pageX: 0, pageY: 50}]}); + fireEvent.touchEnd(modalHeader, {changedTouches: [{pageX: 0, pageY: 50}]}); + + expect(document.documentElement.clientWidth).toBe(375); + expect(document.documentElement.clientHeight).toBe(667); + expect(modal.style.transform).toBe("translate(0px, 50px)"); + }); + + it("should not drag overflow viewport", () => { + // mock viewport size to 1920x1080 + jest.spyOn(document.documentElement, "clientWidth", "get").mockImplementation(() => 1920); + jest.spyOn(document.documentElement, "clientHeight", "get").mockImplementation(() => 1080); + const wrapper = render(); + const modal = wrapper.getByRole("dialog"); + const modalHeader = wrapper.getByText("Modal header"); + + fireEvent.touchStart(modalHeader, {changedTouches: [{pageX: 100, pageY: 50}]}); + fireEvent.touchMove(modalHeader, {changedTouches: [{pageX: 10000, pageY: 5000}]}); + fireEvent.touchEnd(modalHeader, {changedTouches: [{pageX: 10000, pageY: 5000}]}); + + expect(modal.style.transform).toBe("translate(1920px, 1080px)"); + }); + + it("should not drag when disabled", () => { + // mock viewport size to 1920x1080 + jest.spyOn(document.documentElement, "clientWidth", "get").mockImplementation(() => 1920); + jest.spyOn(document.documentElement, "clientHeight", "get").mockImplementation(() => 1080); + const wrapper = render(); + const modal = wrapper.getByRole("dialog"); + const modalHeader = wrapper.getByText("Modal header"); + + fireEvent.touchStart(modalHeader, {changedTouches: [{pageX: 100, pageY: 50}]}); + fireEvent.touchMove(modalHeader, {changedTouches: [{pageX: 200, pageY: 100}]}); + fireEvent.touchEnd(modalHeader, {changedTouches: [{pageX: 200, pageY: 100}]}); + + expect(modal.style.transform).toBe(""); + }); + + test("should be rendered a draggable modal with overflow", () => { + // mock viewport size to 1920x1080 + jest.spyOn(document.documentElement, "clientWidth", "get").mockImplementation(() => 1920); + jest.spyOn(document.documentElement, "clientHeight", "get").mockImplementation(() => 1080); + + const wrapper = render(); + + const modal = wrapper.getByRole("dialog"); + const modalHeader = wrapper.getByText("Modal header"); + + fireEvent.touchStart(modalHeader, {changedTouches: [{pageX: 0, pageY: 0}]}); + fireEvent.touchMove(modalHeader, {changedTouches: [{pageX: 2000, pageY: 1500}]}); + fireEvent.touchEnd(modalHeader, {changedTouches: [{pageX: 2000, pageY: 1500}]}); + + expect(document.documentElement.clientWidth).toBe(1920); + expect(document.documentElement.clientHeight).toBe(1080); + expect(modal.style.transform).toBe("translate(2000px, 1500px)"); + }); }); diff --git a/packages/components/modal/package.json b/packages/components/modal/package.json index 1564446ce8..d09de43203 100644 --- a/packages/components/modal/package.json +++ b/packages/components/modal/package.json @@ -42,6 +42,7 @@ }, "dependencies": { "@nextui-org/use-disclosure": "workspace:*", + "@nextui-org/use-draggable": "workspace:*", "@nextui-org/use-aria-button": "workspace:*", "@nextui-org/framer-utils": "workspace:*", "@nextui-org/shared-utils": "workspace:*", @@ -63,6 +64,7 @@ "@nextui-org/checkbox": "workspace:*", "@nextui-org/button": "workspace:*", "@nextui-org/link": "workspace:*", + "@nextui-org/switch": "workspace:*", "react-lorem-component": "0.13.0", "framer-motion": "^11.0.22", "clean-package": "2.2.0", diff --git a/packages/components/modal/src/index.ts b/packages/components/modal/src/index.ts index 229b2c7036..75d6bbdcb8 100644 --- a/packages/components/modal/src/index.ts +++ b/packages/components/modal/src/index.ts @@ -15,6 +15,7 @@ export type {UseDisclosureProps} from "@nextui-org/use-disclosure"; // export hooks export {useModal} from "./use-modal"; export {useDisclosure} from "@nextui-org/use-disclosure"; +export {useDraggable} from "@nextui-org/use-draggable"; // export context export {ModalProvider, useModalContext} from "./modal-context"; diff --git a/packages/components/modal/src/modal-header.tsx b/packages/components/modal/src/modal-header.tsx index 4e6a96da53..1bff157918 100644 --- a/packages/components/modal/src/modal-header.tsx +++ b/packages/components/modal/src/modal-header.tsx @@ -1,11 +1,16 @@ import {useEffect} from "react"; import {forwardRef, HTMLNextUIProps} from "@nextui-org/system"; -import {useDOMRef} from "@nextui-org/react-utils"; +import {ReactRef, useDOMRef} from "@nextui-org/react-utils"; import {clsx} from "@nextui-org/shared-utils"; import {useModalContext} from "./modal-context"; -export interface ModalHeaderProps extends HTMLNextUIProps<"header"> {} +export interface ModalHeaderProps extends HTMLNextUIProps<"header"> { + /** + * Ref to the DOM node. + */ + ref?: ReactRef; +} const ModalHeader = forwardRef<"header", ModalHeaderProps>((props, ref) => { const {as, children, className, ...otherProps} = props; diff --git a/packages/components/modal/stories/modal.stories.tsx b/packages/components/modal/stories/modal.stories.tsx index 0a824fea8b..85181b15ac 100644 --- a/packages/components/modal/stories/modal.stories.tsx +++ b/packages/components/modal/stories/modal.stories.tsx @@ -7,6 +7,7 @@ import {Button} from "@nextui-org/button"; import {Input} from "@nextui-org/input"; import {Checkbox} from "@nextui-org/checkbox"; import {Link} from "@nextui-org/link"; +import {Switch} from "@nextui-org/switch"; import {MailFilledIcon, LockFilledIcon} from "@nextui-org/shared-icons"; import Lorem from "react-lorem-component"; @@ -18,6 +19,7 @@ import { ModalFooter, ModalProps, useDisclosure, + useDraggable, } from "../src"; export default { @@ -204,6 +206,60 @@ const OpenChangeTemplate = (args: ModalProps) => { ); }; +const DraggableTemplate = (args: ModalProps) => { + const {isOpen, onOpen, onClose, onOpenChange} = useDisclosure(); + const targetRef = React.useRef(null); + + const {moveProps} = useDraggable({targetRef}); + + return ( +
+ + + + Modal Title + + + + + + + + +
+ ); +}; +const DraggableOverflowTemplate = (args: ModalProps) => { + const {isOpen, onOpen, onClose, onOpenChange} = useDisclosure(); + const targetRef = React.useRef(null); + const [disableDraggable, setDisableDraggable] = React.useState(false); + const [canOverflow, setCanOverflow] = React.useState(true); + + const {moveProps} = useDraggable({targetRef, isDisabled: disableDraggable, canOverflow}); + + return ( +
+ + + Disable Draggable + + + Overflow viewport + + + + Modal Title + + + + + + + + +
+ ); +}; export const Default = { render: Template, @@ -278,3 +334,19 @@ export const CustomMotion = { }, }, }; + +export const Draggable = { + render: DraggableTemplate, + + args: { + ...defaultProps, + }, +}; + +export const DraggableOverflow = { + render: DraggableOverflowTemplate, + + args: { + ...defaultProps, + }, +}; diff --git a/packages/hooks/use-draggable/README.md b/packages/hooks/use-draggable/README.md new file mode 100644 index 0000000000..b2e0c2e017 --- /dev/null +++ b/packages/hooks/use-draggable/README.md @@ -0,0 +1,24 @@ +# @nextui-org/use-draggable + +A Quick description of the component + +> This is an internal utility, not intended for public usage. + +## Installation + +```sh +yarn add @nextui-org/use-draggable +# or +npm i @nextui-org/use-draggable +``` + +## Contribution + +Yes please! See the +[contributing guidelines](https://github.com/nextui-org/nextui/blob/master/CONTRIBUTING.md) +for details. + +## License + +This project is licensed under the terms of the +[MIT license](https://github.com/nextui-org/nextui/blob/master/LICENSE). diff --git a/packages/hooks/use-draggable/package.json b/packages/hooks/use-draggable/package.json new file mode 100644 index 0000000000..8c0c04e9a4 --- /dev/null +++ b/packages/hooks/use-draggable/package.json @@ -0,0 +1,55 @@ +{ + "name": "@nextui-org/use-draggable", + "version": "0.1.0", + "description": "This hook can provide drag and drop interaction", + "keywords": [ + "use-draggable" + ], + "author": "Junior Garcia ", + "homepage": "https://nextui.org", + "license": "MIT", + "main": "src/index.ts", + "sideEffects": false, + "files": [ + "dist" + ], + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/nextui-org/nextui.git", + "directory": "packages/hooks/use-draggable" + }, + "bugs": { + "url": "https://github.com/nextui-org/nextui/issues" + }, + "scripts": { + "build": "tsup src --dts", + "build:fast": "tsup src", + "dev": "pnpm build:fast --watch", + "clean": "rimraf dist .turbo", + "typecheck": "tsc --noEmit", + "prepack": "clean-package", + "postpack": "clean-package restore" + }, + "peerDependencies": { + "react": ">=18" + }, + "devDependencies": { + "clean-package": "2.2.0", + "react": "^18.0.0" + }, + "clean-package": "../../../clean-package.config.json", + "tsup": { + "clean": true, + "target": "es2019", + "format": [ + "cjs", + "esm" + ] + }, + "dependencies": { + "@react-aria/interactions": "^3.21.1" + } +} diff --git a/packages/hooks/use-draggable/src/index.ts b/packages/hooks/use-draggable/src/index.ts new file mode 100644 index 0000000000..3b6de4bf49 --- /dev/null +++ b/packages/hooks/use-draggable/src/index.ts @@ -0,0 +1,110 @@ +import type {MoveMoveEvent, MoveResult} from "@react-aria/interactions"; + +import {useEffect, useRef, useCallback} from "react"; +import {useMove} from "@react-aria/interactions"; + +export interface UseDraggableProps { + /** + * Ref to the moving target DOM node. + */ + targetRef?: React.RefObject; + /** + * Whether to disable the target is draggable. + * @default false + */ + isDisabled?: boolean; + /** + * Whether the target can overflow the viewport. + * @default false + */ + canOverflow?: boolean; +} + +/** + * A hook to make a target draggable. + * @param props UseDraggableProps + * @returns MoveResult for the drag DOM node. + */ +export function useDraggable(props: UseDraggableProps): MoveResult { + const {targetRef, isDisabled = false, canOverflow = false} = props; + const boundary = useRef({minLeft: 0, minTop: 0, maxLeft: 0, maxTop: 0}); + let transform = {offsetX: 0, offsetY: 0}; + + const onMoveStart = () => { + const {offsetX, offsetY} = transform; + + const targetRect = targetRef?.current?.getBoundingClientRect(); + const targetLeft = targetRect?.left ?? 0; + const targetTop = targetRect?.top ?? 0; + const targetWidth = targetRect?.width ?? 0; + const targetHeight = targetRect?.height ?? 0; + + const clientWidth = document.documentElement.clientWidth; + const clientHeight = document.documentElement.clientHeight; + + const minLeft = -targetLeft + offsetX; + const minTop = -targetTop + offsetY; + const maxLeft = clientWidth - targetLeft - targetWidth + offsetX; + const maxTop = clientHeight - targetTop - targetHeight + offsetY; + + boundary.current = { + minLeft, + minTop, + maxLeft, + maxTop, + }; + }; + + const onMove = (e: MoveMoveEvent) => { + if (isDisabled) { + return; + } + const {offsetX, offsetY} = transform; + const {minLeft, minTop, maxLeft, maxTop} = boundary.current; + let moveX = offsetX + e.deltaX; + let moveY = offsetY + e.deltaY; + + if (!canOverflow) { + moveX = Math.min(Math.max(moveX, minLeft), maxLeft); + moveY = Math.min(Math.max(moveY, minTop), maxTop); + } + + transform = { + offsetX: moveX, + offsetY: moveY, + }; + + if (targetRef?.current) { + targetRef.current.style.transform = `translate(${moveX}px, ${moveY}px)`; + } + }; + + const {moveProps} = useMove({ + onMoveStart, + onMove, + }); + + const preventDefault = useCallback((e: TouchEvent) => { + e.preventDefault(); + }, []); + + // NOTE: This process is due to the modal being displayed at the bottom instead of the center when opened on mobile sizes. + // It will become unnecessary once the modal is centered properly. + useEffect(() => { + if (!isDisabled) { + // Prevent body scroll when dragging at mobile. + document.body.addEventListener("touchmove", preventDefault, {passive: false}); + } + + return () => { + document.body.removeEventListener("touchmove", preventDefault); + }; + }, [isDisabled]); + + return { + moveProps: { + ...moveProps, + style: {cursor: !isDisabled ? "move" : undefined}, + }, + }; +} diff --git a/packages/hooks/use-draggable/tsconfig.json b/packages/hooks/use-draggable/tsconfig.json new file mode 100644 index 0000000000..46e3b466c2 --- /dev/null +++ b/packages/hooks/use-draggable/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../../tsconfig.json", + "include": ["src", "index.ts"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 99a53b83ff..2b44bd9a89 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -146,7 +146,7 @@ importers: version: 4.0.2(eslint@7.32.0)(webpack@5.91.0) eslint-plugin-import: specifier: ^2.26.0 - version: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0) + version: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@7.32.0) eslint-plugin-jest: specifier: ^24.3.6 version: 24.7.0(@typescript-eslint/eslint-plugin@5.62.0)(eslint@7.32.0)(typescript@4.9.5) @@ -1795,6 +1795,9 @@ importers: '@nextui-org/use-disclosure': specifier: workspace:* version: link:../../hooks/use-disclosure + '@nextui-org/use-draggable': + specifier: workspace:* + version: link:../../hooks/use-draggable '@react-aria/dialog': specifier: ^3.5.12 version: 3.5.12(react-dom@18.2.0)(react@18.2.0) @@ -1829,6 +1832,9 @@ importers: '@nextui-org/link': specifier: workspace:* version: link:../link + '@nextui-org/switch': + specifier: workspace:* + version: link:../switch '@nextui-org/system': specifier: workspace:* version: link:../../core/system @@ -3372,6 +3378,19 @@ importers: specifier: ^18.2.0 version: 18.2.0 + packages/hooks/use-draggable: + dependencies: + '@react-aria/interactions': + specifier: ^3.21.1 + version: 3.21.1(react@18.2.0) + devDependencies: + clean-package: + specifier: 2.2.0 + version: 2.2.0 + react: + specifier: ^18.2.0 + version: 18.2.0 + packages/hooks/use-image: dependencies: '@nextui-org/use-safe-layout-effect': @@ -5937,6 +5956,10 @@ packages: peerDependencies: '@effect-ts/otel-node': '*' peerDependenciesMeta: + '@effect-ts/core': + optional: true + '@effect-ts/otel': + optional: true '@effect-ts/otel-node': optional: true dependencies: @@ -14780,6 +14803,7 @@ packages: loose-envify: 1.4.0 object-assign: 4.1.1 dev: true + bundledDependencies: false /create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} @@ -16010,7 +16034,7 @@ packages: dependencies: confusing-browser-globals: 1.0.11 eslint: 7.32.0 - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@7.32.0) object.assign: 4.1.5 object.entries: 1.1.8 dev: true @@ -16043,7 +16067,7 @@ packages: dependencies: eslint: 7.32.0 eslint-config-airbnb-base: 14.2.1(eslint-plugin-import@2.29.1)(eslint@7.32.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@7.32.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@7.32.0) eslint-plugin-react: 7.34.1(eslint@7.32.0) eslint-plugin-react-hooks: 4.6.0(eslint@7.32.0) @@ -16066,7 +16090,7 @@ packages: eslint: 7.32.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@7.32.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@7.32.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@7.32.0) eslint-plugin-react: 7.34.1(eslint@7.32.0) eslint-plugin-react-hooks: 4.6.0(eslint@7.32.0) @@ -16115,7 +16139,7 @@ packages: confusing-browser-globals: 1.0.11 eslint: 7.32.0 eslint-plugin-flowtype: 5.10.0(eslint@7.32.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@7.32.0) eslint-plugin-jest: 24.7.0(@typescript-eslint/eslint-plugin@5.62.0)(eslint@7.32.0)(typescript@4.9.5) eslint-plugin-jsx-a11y: 6.8.0(eslint@7.32.0) eslint-plugin-react: 7.34.1(eslint@7.32.0) @@ -16156,7 +16180,7 @@ packages: dependencies: debug: 4.3.4 eslint: 7.32.0 - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@7.32.0) glob: 7.2.3 is-glob: 4.0.3 resolve: 1.22.8 @@ -16176,7 +16200,7 @@ packages: enhanced-resolve: 5.16.0 eslint: 7.32.0 eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@7.32.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@7.32.0) fast-glob: 3.3.2 get-tsconfig: 4.7.3 is-core-module: 2.13.1 @@ -16287,7 +16311,7 @@ packages: string-natural-compare: 3.0.1 dev: true - /eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0): + /eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@7.32.0): resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} engines: {node: '>=4'} peerDependencies: @@ -22430,6 +22454,9 @@ packages: resolution: {integrity: sha512-W+gxAq7aQ9dJIg/XLKGcRT0cvnStFAQHPaI0pvD0U2l6IVLueUAm3nwN7lkY62zZNmlvNx6jNtE4wlbS+CyqSg==} engines: {node: '>= 12.0.0'} hasBin: true + peerDependenciesMeta: + '@parcel/core': + optional: true dependencies: '@parcel/config-default': 2.12.0(@parcel/core@2.12.0)(typescript@4.9.5) '@parcel/core': 2.12.0