react-teleporter
Version:
Teleport React components in the same React tree.
1 lines • 4.19 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","sources":["../src/index.tsx"],"sourcesContent":["import * as React from \"react\";\nimport * as ReactDOM from \"react-dom\";\n\n// Internal types\ntype As<Props = any> = React.ElementType<Props>;\ntype PropsWithAs<Props = {}, Type extends As = As> = Props &\n Omit<React.ComponentProps<Type>, \"as\" | keyof Props> & {\n as?: Type;\n };\n\ntype ComponentWithAs<Props, DefaultType extends As> = {\n <Type extends As>(\n props: PropsWithAs<Props, Type> & { as: Type }\n ): JSX.Element;\n (props: PropsWithAs<Props, DefaultType>): JSX.Element;\n};\n\ntype TargetRefRef = { current: TargetRef | null };\n\ninterface Context {\n element: Element | null;\n set: TargetRefRef | null;\n}\n\ntype ChildrenFunction = (target: Element) => React.ReactNode;\n\nexport interface CreateTeleporterOptions {\n multiSources?: Boolean;\n}\n\nexport interface TargetRef {\n (element: Element | null): void;\n}\n\nexport interface Teleporter {\n Source: React.FC<{ children: React.ReactNode | ChildrenFunction }>;\n Target: ComponentWithAs<{}, \"div\">;\n useTargetRef: () => TargetRef;\n}\n\nexport const createTeleporter = ({\n multiSources,\n}: CreateTeleporterOptions = {}): Teleporter => {\n const context: Context = { element: null, set: null };\n\n const setElement: TargetRef = (element) => {\n context.element = element;\n if (context.set && context.set.current) {\n context.set.current(element);\n }\n };\n\n const useTargetRef = () => setElement;\n\n const Target = ({ as: As = \"div\", ...props }: PropsWithAs<{}, \"div\">) => {\n return <As ref={setElement} {...props} />;\n };\n\n const Source = ({\n children,\n }: {\n children: React.ReactNode | ChildrenFunction;\n }) => {\n const [element, setElement] = React.useState<Element | null>(null);\n\n React.useLayoutEffect(() => {\n const setRef: TargetRefRef = { current: setElement };\n let previousSet: TargetRefRef;\n\n if (context.set) {\n previousSet = context.set;\n if (!multiSources && context.set.current) {\n context.set.current(null);\n }\n }\n\n context.set = setRef;\n if (context.element !== undefined) {\n setElement(context.element);\n }\n\n return () => {\n setRef.current = null;\n context.set = null;\n\n if (previousSet && previousSet.current) {\n context.set = previousSet;\n if (context.element !== undefined) {\n previousSet.current(context.element);\n }\n }\n };\n }, []);\n if (!element) return null;\n const content =\n typeof children === \"function\" ? children(element) : children;\n return ReactDOM.createPortal(content, element);\n };\n\n return { Source, Target, useTargetRef };\n};\n"],"names":[],"mappings":";;;AAAgE,MAAC,gBAAgB,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,EAAC,CAAC,CAAC,OAAM,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,EAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;;;;"}