-
Notifications
You must be signed in to change notification settings - Fork 0
добавлен vanilla popper, удален react-popper из проекта #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 7 commits
7a1088b
6f90d43
9b735e4
51207ea
932171c
be4b38f
84ab4af
0540e77
b8cf53a
60780e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,3 +2,4 @@ | |
| box-sizing: border-box; | ||
| line-height: 0; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import React from "react"; | ||
|
|
||
| export const ManagerReferenceNodeContext = React.createContext<HTMLElement>(null!); | ||
| export const ManagerReferenceNodeSetterContext = React.createContext<(elem: HTMLElement) => void>(null!); | ||
|
|
||
| export function Manager({ children }: React.PropsWithChildren<{}>) { | ||
| const [referenceNode, setReferenceNode] = React.useState<any>(null); | ||
|
|
||
| const hasUnmounted = React.useRef(false); | ||
| React.useEffect(() => { | ||
| return () => { | ||
| hasUnmounted.current = true; | ||
| }; | ||
| }, []); | ||
|
|
||
| const handleSetReferenceNode = React.useCallback((node) => { | ||
| if (!hasUnmounted.current) { | ||
| setReferenceNode(node); | ||
| } | ||
| }, []); | ||
|
|
||
| return ( | ||
| <ManagerReferenceNodeContext.Provider value={referenceNode}> | ||
| <ManagerReferenceNodeSetterContext.Provider value={handleSetReferenceNode}> | ||
| {children} | ||
| </ManagerReferenceNodeSetterContext.Provider> | ||
| </ManagerReferenceNodeContext.Provider> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| import React, { Ref } from "react"; | ||
| import { provideRef } from "@worksolutions/react-utils"; | ||
| import { Modifier, Options, Placement, PositioningStrategy, State } from "@popperjs/core/lib"; | ||
|
|
||
| import { ManagerReferenceNodeContext } from "../Manager"; | ||
|
|
||
| import { unwrapArray } from "../utils"; | ||
| import { useVanillaPopper } from "../useVanillaPopper"; | ||
|
|
||
| type ReferenceElement = HTMLElement; | ||
|
|
||
| export interface PopperArrowProps { | ||
| ref: React.Ref<any>; | ||
| style: React.CSSProperties; | ||
| } | ||
|
|
||
| export type PopperChildrenProps = { | ||
| ref: Ref<any>; | ||
| style: CSSStyleDeclaration; | ||
| placement: Placement; | ||
| isReferenceHidden?: boolean; | ||
| hasPopperEscaped?: boolean; | ||
| update: () => Promise<null | State>; | ||
| forceUpdate: () => void; | ||
| arrowProps: PopperArrowProps; | ||
| }; | ||
|
|
||
| type Modifiers = Array<Partial<Modifier<any, any>>>; | ||
| export type PopperChildren = (popperChildrenProps: PopperChildrenProps) => React.ReactNode; | ||
|
|
||
| export type PopperProps = { | ||
| children: PopperChildren; | ||
| innerRef?: Ref<any>; | ||
| modifiers?: Modifiers; | ||
| placement?: Placement; | ||
| strategy?: PositioningStrategy; | ||
| referenceElement?: ReferenceElement; | ||
| onFirstUpdate?: (arg0: Partial<State>) => void; | ||
| }; | ||
|
|
||
| const NOOP = () => void 0; | ||
|
grigoriy-grisha marked this conversation as resolved.
Outdated
|
||
| const NOOP_PROMISE = () => Promise.resolve(null); | ||
| const EMPTY_MODIFIERS: Modifiers = []; | ||
|
|
||
| export function Popper({ | ||
| placement = "bottom", | ||
| strategy = "absolute", | ||
| modifiers = EMPTY_MODIFIERS, | ||
| referenceElement, | ||
| onFirstUpdate, | ||
| innerRef, | ||
| children, | ||
| }: PopperProps) { | ||
| const referenceNode = React.useContext(ManagerReferenceNodeContext); | ||
|
|
||
| const [popperElement, setPopperElement] = React.useState(null); | ||
| const [arrowElement, setArrowElement] = React.useState(null); | ||
|
|
||
| React.useEffect(() => { | ||
| provideRef(innerRef)(popperElement!); | ||
| }, [innerRef, popperElement]); | ||
|
|
||
| const options: Options = React.useMemo( | ||
| () => ({ | ||
| placement, | ||
| strategy, | ||
| onFirstUpdate, | ||
| modifiers: [ | ||
| ...modifiers, | ||
| { | ||
| name: "arrow", | ||
| enabled: arrowElement != null, | ||
| options: { element: arrowElement }, | ||
| }, | ||
| ], | ||
| }), | ||
| [placement, strategy, onFirstUpdate, modifiers, arrowElement], | ||
| ); | ||
|
|
||
| const { state, forceUpdate, update } = useVanillaPopper(referenceElement || referenceNode, popperElement, options); | ||
|
|
||
| const childrenProps = React.useMemo( | ||
| () => ({ | ||
| ref: setPopperElement, | ||
| style: state?.styles?.popper, | ||
| placement: state ? state.placement : placement, | ||
| hasPopperEscaped: state && state.modifiersData.hide ? state.modifiersData.hide.hasPopperEscaped : null, | ||
| isReferenceHidden: state && state.modifiersData.hide ? state.modifiersData.hide.isReferenceHidden : null, | ||
| arrowProps: { | ||
| style: state?.styles?.arrow, | ||
| ref: setArrowElement, | ||
| }, | ||
| forceUpdate: forceUpdate || NOOP, | ||
| update: update || NOOP_PROMISE, | ||
| }), | ||
| [setPopperElement, setArrowElement, placement, state, update, forceUpdate], | ||
| ); | ||
|
|
||
| return unwrapArray(children)(childrenProps); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import React, { Ref } from "react"; | ||
| import { provideRef } from "@worksolutions/react-utils"; | ||
|
|
||
| import { unwrapArray } from "../utils"; | ||
| import { ManagerReferenceNodeSetterContext } from "../Manager"; | ||
|
|
||
| export type ReferenceChildrenProps = { ref: Ref<any> }; | ||
| export type ReferenceProps = { | ||
| children: (ReferenceChildrenProps: ReferenceChildrenProps) => React.ReactNode; | ||
| innerRef?: Ref<any>; | ||
| }; | ||
|
|
||
| export function Reference({ children, innerRef }: ReferenceProps) { | ||
| const setReferenceNode = React.useContext(ManagerReferenceNodeSetterContext); | ||
|
|
||
| const refHandler = React.useCallback( | ||
| (node?: HTMLElement) => { | ||
| if (!node) return; | ||
| provideRef(innerRef)(node); | ||
| setReferenceNode(node); | ||
| }, | ||
| [innerRef, setReferenceNode], | ||
| ); | ||
|
|
||
| React.useEffect(() => () => provideRef(innerRef)(null!), []); | ||
|
|
||
| React.useEffect(() => { | ||
| console.warn("`Reference` should not be used outside of a `Manager` component."); | ||
| }, [setReferenceNode]); | ||
|
|
||
| return unwrapArray(children)({ ref: refHandler }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| import { useLayoutEffect, useMemo, useState } from "react"; | ||
|
grigoriy-grisha marked this conversation as resolved.
Outdated
|
||
| import { | ||
| createPopper, | ||
| Instance as PopperInstance, | ||
| Modifier, | ||
| Options as PopperOptions, | ||
| Placement, | ||
| PositioningStrategy, | ||
| State as PopperState, | ||
| } from "@popperjs/core"; | ||
|
|
||
| type Options = { createPopper?: typeof createPopper } & { | ||
| placement?: Placement; | ||
| modifiers?: Partial<Modifier<any, any>>[]; | ||
| strategy?: PositioningStrategy; | ||
| onFirstUpdate?: ((state: Partial<PopperState>) => void) | undefined; | ||
| }; | ||
|
|
||
| const EMPTY_MODIFIERS: PopperOptions["modifiers"] = []; | ||
|
|
||
| type UsePopperResult = { | ||
| state: PopperState | null; | ||
| update: PopperInstance["update"] | null; | ||
| forceUpdate: PopperInstance["forceUpdate"] | null; | ||
| }; | ||
|
|
||
| export function useVanillaPopper( | ||
| reference: HTMLElement | null, | ||
| tooltip: HTMLElement | null, | ||
| options: Options, | ||
| ): UsePopperResult { | ||
| const [popperInstance, setPopperInstance] = useState<PopperInstance>(); | ||
|
|
||
| const popperOptions = useMemo(() => { | ||
| return { | ||
| onFirstUpdate: options.onFirstUpdate, | ||
| placement: options.placement || "bottom", | ||
| strategy: options.strategy || "absolute", | ||
| modifiers: options.modifiers || EMPTY_MODIFIERS, | ||
| }; | ||
| }, [options.modifiers, options.placement, options.strategy, options.onFirstUpdate]); | ||
|
|
||
| useLayoutEffect(() => { | ||
| if (!reference) return; | ||
| if (!tooltip) return; | ||
|
|
||
| const popper = createPopper(reference, tooltip, popperOptions); | ||
| setPopperInstance(popper); | ||
|
|
||
| return () => popper.destroy(); | ||
| }, [reference, tooltip, popperOptions]); | ||
|
|
||
| return { | ||
| forceUpdate: popperInstance ? popperInstance.forceUpdate : null, | ||
| update: popperInstance ? popperInstance.update : null, | ||
| state: popperInstance ? popperInstance.state : null, | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export const unwrapArray = (arg: any): any => (Array.isArray(arg) ? arg[0] : arg); | ||
|
grigoriy-grisha marked this conversation as resolved.
Outdated
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,10 @@ | ||
| import React from "react"; | ||
| import { Manager as ReactPopperManager, Reference as ReactPopperReference } from "react-popper"; | ||
| import { provideRef } from "@worksolutions/react-utils"; | ||
| import { observer } from "mobx-react-lite"; | ||
|
|
||
| import { Reference } from "primitives/Popper/Reference"; | ||
| import { Manager } from "primitives/Popper/Manager"; | ||
|
|
||
| import VisibilityManager, { VisibilityManagerContextInterface } from "../../VisibilityManager"; | ||
| import { SetVisibilityContextAndTriggerRef } from "./types"; | ||
|
|
||
|
|
@@ -27,14 +29,14 @@ function PopupManagerForClick({ | |
| const Element = React.useCallback( | ||
| (context: VisibilityManagerContextInterface) => ( | ||
| <> | ||
| <ReactPopperReference> | ||
| <Reference> | ||
| {({ ref: reactPopperReferenceRef }) => ( | ||
| <TriggerElement | ||
| {...context} | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. мне не очень нравиться что попер так сильно связан с VisibilityManagerContextInterface. По факту, я хочу чтобы попер занимался только позиционированием моего элемента. А как и когда его показывать уже должен решать я. И желательно чтоб я это мог пропсами рулить
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. поэтому PopupManagerForClick.tsx и тот же для hover вообще не должны знать что они poper отображают |
||
| initRef={provideRef(context.initRef, reactPopperReferenceRef, setVisibilityContextAndTriggerRef(context))} | ||
| /> | ||
| )} | ||
| </ReactPopperReference> | ||
| </Reference> | ||
| </> | ||
| ), | ||
| [TriggerElement, setVisibilityContextAndTriggerRef], | ||
|
|
@@ -43,12 +45,12 @@ function PopupManagerForClick({ | |
| const ignoreElements = React.useMemo(() => [popupElementHtmlNode], [popupElementHtmlNode]); | ||
|
|
||
| return ( | ||
| <ReactPopperManager> | ||
| <Manager> | ||
| <VisibilityManager outsideClickIgnoreElements={ignoreElements} closeOnClickOutside={closeOnClickOutside}> | ||
| {Element} | ||
| </VisibilityManager> | ||
| {popupElementNode && React.cloneElement(popupElementNode as any, { ref: setPopupElementHtmlNode })} | ||
| </ReactPopperManager> | ||
| </Manager> | ||
| ); | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.