react-magic-portal 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +6 -6
- package/__tests__/src/magic-portal.test.tsx +7 -7
- package/package.json +1 -1
- package/packages/component/dist/index.d.ts +1 -1
- package/packages/component/dist/index.d.ts.map +1 -1
- package/packages/component/dist/index.js +1 -1
- package/packages/component/dist/index.js.map +1 -1
- package/packages/component/src/index.ts +7 -7
- package/packages/example/dist/assets/{index-BBgHwIii.js → index-D7od5Grv.js} +8 -8
- package/packages/example/dist/assets/{index-CDQ6J_Ti.css → index-cHGDwajU.css} +1 -1
- package/packages/example/dist/index.html +2 -2
- package/packages/example/src/App.css +2 -2
- package/packages/example/src/App.tsx +6 -6
- package/packages/example/src/components/portal-content.tsx +4 -4
- package/packages/example/src/index.css +42 -42
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [1.3.1](https://github.com/molvqingtai/react-magic-portal/compare/v1.3.0...v1.3.1) (2025-11-07)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Performance Improvements
|
|
5
|
+
|
|
6
|
+
* better position name ([af11ae6](https://github.com/molvqingtai/react-magic-portal/commit/af11ae67e1bf8998067d0cf756128b279ea1f385))
|
|
7
|
+
|
|
1
8
|
# [1.3.0](https://github.com/molvqingtai/react-magic-portal/compare/v1.2.1...v1.3.0) (2025-10-10)
|
|
2
9
|
|
|
3
10
|
|
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ React Magic Portal solves these challenges by automatically detecting when targe
|
|
|
26
26
|
## Features
|
|
27
27
|
|
|
28
28
|
- **Dynamic Anchor Detection** - Automatically detects when target elements appear or disappear in the DOM
|
|
29
|
-
- **Multiple Positioning Options** - Support for `
|
|
29
|
+
- **Multiple Positioning Options** - Support for `last`, `first`, `before`, and `after` positioning
|
|
30
30
|
- **Flexible Anchor Selection** - Support for CSS selectors, element references, functions, and direct elements
|
|
31
31
|
- **Lifecycle Callbacks** - `onMount` and `onUnmount` callbacks for portal lifecycle management
|
|
32
32
|
- **TypeScript Support** - Full TypeScript support with comprehensive type definitions
|
|
@@ -77,11 +77,11 @@ function App() {
|
|
|
77
77
|
<div>Content before target</div>
|
|
78
78
|
</MagicPortal>
|
|
79
79
|
|
|
80
|
-
<MagicPortal anchor="#target" position="
|
|
80
|
+
<MagicPortal anchor="#target" position="first">
|
|
81
81
|
<div>Content at start of target</div>
|
|
82
82
|
</MagicPortal>
|
|
83
83
|
|
|
84
|
-
<MagicPortal anchor="#target" position="
|
|
84
|
+
<MagicPortal anchor="#target" position="last">
|
|
85
85
|
<div>Content at end of target</div>
|
|
86
86
|
</MagicPortal>
|
|
87
87
|
|
|
@@ -100,7 +100,7 @@ function App() {
|
|
|
100
100
|
| Prop | Type | Default | Description |
|
|
101
101
|
| ----------- | ------------------------------------------------------------------------------------------ | --------------- | ------------------------------------------------------------ |
|
|
102
102
|
| `anchor` | `string \| (() => Element \| null) \| Element \| React.RefObject<Element \| null> \| null` | **Required** | The target element where the portal content will be rendered |
|
|
103
|
-
| `position` | `'
|
|
103
|
+
| `position` | `'last' \| 'first' \| 'before' \| 'after'` | `'last'` | Position relative to the anchor element |
|
|
104
104
|
| `root` | `Element` | `document.body` | The root element to observe for DOM mutations |
|
|
105
105
|
| `children` | `React.ReactElement \| null` | `undefined` | A single React element to render in the portal (does not support Fragment) |
|
|
106
106
|
| `onMount` | `(anchor: Element, container: Element) => void` | `undefined` | Callback fired when the portal is mounted |
|
|
@@ -150,7 +150,7 @@ const elementRef = useRef(null)
|
|
|
150
150
|
|
|
151
151
|
### Position Options
|
|
152
152
|
|
|
153
|
-
#### `
|
|
153
|
+
#### `last` (default)
|
|
154
154
|
|
|
155
155
|
Adds content inside the anchor element at the end:
|
|
156
156
|
|
|
@@ -161,7 +161,7 @@ Adds content inside the anchor element at the end:
|
|
|
161
161
|
</div>
|
|
162
162
|
```
|
|
163
163
|
|
|
164
|
-
#### `
|
|
164
|
+
#### `first`
|
|
165
165
|
|
|
166
166
|
Adds content inside the anchor element at the beginning:
|
|
167
167
|
|
|
@@ -191,7 +191,7 @@ describe('MagicPortal', () => {
|
|
|
191
191
|
document.body.appendChild(anchor)
|
|
192
192
|
})
|
|
193
193
|
|
|
194
|
-
it('should
|
|
194
|
+
it('should last by default', () => {
|
|
195
195
|
render(
|
|
196
196
|
<MagicPortal anchor="#position-anchor">
|
|
197
197
|
<div data-testid="portal-content">Portal Content</div>
|
|
@@ -204,9 +204,9 @@ describe('MagicPortal', () => {
|
|
|
204
204
|
expect(anchor.contains(portalContent)).toBe(true)
|
|
205
205
|
})
|
|
206
206
|
|
|
207
|
-
it('should
|
|
207
|
+
it('should first when position is first', () => {
|
|
208
208
|
render(
|
|
209
|
-
<MagicPortal anchor="#position-anchor" position="
|
|
209
|
+
<MagicPortal anchor="#position-anchor" position="first">
|
|
210
210
|
<div data-testid="portal-content">Portal Content</div>
|
|
211
211
|
</MagicPortal>
|
|
212
212
|
)
|
|
@@ -389,7 +389,7 @@ describe('MagicPortal', () => {
|
|
|
389
389
|
document.body.appendChild(anchor)
|
|
390
390
|
|
|
391
391
|
const { rerender } = render(
|
|
392
|
-
<MagicPortal anchor="#position-ref-anchor" position="
|
|
392
|
+
<MagicPortal anchor="#position-ref-anchor" position="last">
|
|
393
393
|
<div ref={contentRef} data-testid="portal-content">
|
|
394
394
|
Portal Content
|
|
395
395
|
</div>
|
|
@@ -403,7 +403,7 @@ describe('MagicPortal', () => {
|
|
|
403
403
|
contentRef.mockClear()
|
|
404
404
|
|
|
405
405
|
rerender(
|
|
406
|
-
<MagicPortal anchor="#position-ref-anchor" position="
|
|
406
|
+
<MagicPortal anchor="#position-ref-anchor" position="first">
|
|
407
407
|
<div ref={contentRef} data-testid="portal-content">
|
|
408
408
|
Portal Content
|
|
409
409
|
</div>
|
|
@@ -544,10 +544,10 @@ describe('MagicPortal', () => {
|
|
|
544
544
|
|
|
545
545
|
render(
|
|
546
546
|
<div>
|
|
547
|
-
<MagicPortal anchor="#multi-anchor" position="
|
|
547
|
+
<MagicPortal anchor="#multi-anchor" position="first">
|
|
548
548
|
<div data-testid="portal-1">Portal 1</div>
|
|
549
549
|
</MagicPortal>
|
|
550
|
-
<MagicPortal anchor="#multi-anchor" position="
|
|
550
|
+
<MagicPortal anchor="#multi-anchor" position="last">
|
|
551
551
|
<div data-testid="portal-2">Portal 2</div>
|
|
552
552
|
</MagicPortal>
|
|
553
553
|
</div>
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@ import React from "react";
|
|
|
3
3
|
//#region src/index.d.ts
|
|
4
4
|
interface MagicPortalProps {
|
|
5
5
|
anchor: string | (() => Element | null) | Element | React.RefObject<Element | null> | null;
|
|
6
|
-
position?: '
|
|
6
|
+
position?: 'last' | 'first' | 'before' | 'after';
|
|
7
7
|
root?: Element;
|
|
8
8
|
children?: React.ReactElement | null;
|
|
9
9
|
onMount?: (anchor: Element, container: Element) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;UAGiB,gBAAA;0BACS,kBAAkB,UAAU,KAAA,CAAM,UAAU;EADrD,QAAA,CAAA,EAAA,
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;UAGiB,gBAAA;0BACS,kBAAkB,UAAU,KAAA,CAAM,UAAU;EADrD,QAAA,CAAA,EAAA,MAAA,GAAgB,OAAA,GAAA,QAAA,GAAA,OAAA;EAAA,IAAA,CAAA,EAGxB,OAHwB;UACP,CAAA,EAGb,KAAA,CAAM,YAHO,GAAA,IAAA;SAAkB,CAAA,EAAA,CAAA,MAAA,EAIvB,OAJuB,EAAA,SAAA,EAIH,OAJG,EAAA,GAAA,IAAA;WAA0B,CAAA,EAAA,CAAA,MAAA,EAK/C,OAL+C,EAAA,SAAA,EAK3B,OAL2B,EAAA,GAAA,IAAA;;cAoEhE,WAlEG,EAAA;;IACI,MAAM;IAAA,QAAA;IAAA,IAAA;IAAA,QAAA;IAAA,OAAA;IAAA;EAAA,CAAA,EAwEhB,gBAxEgB,CAAA,EAwEA,KAAA,CAAA,WAxEA,GAAA,IAAA;aACE,EAAA,MAAA"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import e,{useCallback as t,useEffect as n,useLayoutEffect as r,useMemo as i,useRef as a,useState as o}from"react";import{createPortal as s}from"react-dom";const c=e=>{let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)},l=e=>typeof e==`string`?document.querySelector(e):typeof e==`function`?e():e&&`current`in e?e.current:e,u=(e,t)=>e?t===`
|
|
1
|
+
import e,{useCallback as t,useEffect as n,useLayoutEffect as r,useMemo as i,useRef as a,useState as o}from"react";import{createPortal as s}from"react-dom";const c=e=>{let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)},l=e=>typeof e==`string`?document.querySelector(e):typeof e==`function`?e():e&&`current`in e?e.current:e,u=(e,t)=>e?t===`first`||t===`last`?e:e.parentElement:null,d=(e,t)=>{if(typeof e==`function`)return e(t);e!=null&&(e.current=t)},f=(...e)=>t=>{let n=e.map(e=>d(e,t));return()=>n.forEach((t,n)=>typeof t==`function`?t():d(e[n],null))},p=({anchor:d,position:p=`last`,root:m=document.body,children:h,onMount:g,onUnmount:_})=>{let v=a(null),[y,b]=o(null),x=t(e=>{if(!e)return;let t=v.current;if(!t)return;let n=u(t,p);if(!n)return;let r=!1;switch(p){case`last`:r=e.parentElement===n&&n.lastChild===e;break;case`first`:r=e.parentElement===n&&n.firstChild===e;break;case`before`:r=e.parentElement===n&&t.previousSibling===e;break;case`after`:r=e.parentElement===n&&t.nextSibling===e;break}r||t.insertAdjacentElement({before:`beforebegin`,first:`afterbegin`,last:`beforeend`,after:`afterend`}[p],e)},[p]),S=i(()=>{if(e.Children.count(h)>1)return console.error(`[react-magic-portal] Multiple children are not supported, expected to receive a single React element child.`),null;if(!e.isValidElement(h))return null;if(h.type===e.Fragment)return console.error(`[react-magic-portal] Fragment children are not supported, expected to receive a single React element child.`),null;let t=c(h);return e.cloneElement(h,{ref:f(t,x)})},[h,x]),C=t(()=>{v.current=l(d);let e=u(v.current,p);e&&(e.__reactWarnedAboutChildrenConflict=!0),b(e)},[d,p]);return r(()=>{C();let e=new MutationObserver(e=>{!e.flatMap(({addedNodes:e,removedNodes:t})=>[...e,...t]).some(e=>y?.contains(e))&&C()});return e.observe(m,{childList:!0,subtree:!0}),()=>e.disconnect()},[C,d,y,m]),n(()=>{if(y&&v.current)return g?.(v.current,y),()=>{_?.(v.current,y)}},[g,_,y]),y&&S?s(S,y):null};p.displayName=`MagicPortal`;var m=p;export{m as default};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import React, { useEffect, useState, useRef, useCallback, useLayoutEffect, useMemo } from 'react'\nimport { createPortal } from 'react-dom'\n\nexport interface MagicPortalProps {\n anchor: string | (() => Element | null) | Element | React.RefObject<Element | null> | null\n position?: '
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import React, { useEffect, useState, useRef, useCallback, useLayoutEffect, useMemo } from 'react'\nimport { createPortal } from 'react-dom'\n\nexport interface MagicPortalProps {\n anchor: string | (() => Element | null) | Element | React.RefObject<Element | null> | null\n position?: 'last' | 'first' | 'before' | 'after'\n root?: Element\n children?: React.ReactElement | null\n onMount?: (anchor: Element, container: Element) => void\n onUnmount?: (anchor: Element, container: Element) => void\n}\n\n/**\n * https://github.com/radix-ui/primitives/blob/36d954d3c1b41c96b1d2e875b93fc9362c8c09e6/packages/react/slot/src/slot.tsx#L166\n */\nconst getElementRef = (element: React.ReactElement) => {\n // React <=18 in DEV\n let getter = Object.getOwnPropertyDescriptor(element.props, 'ref')?.get\n let mayWarn = getter && 'isReactWarning' in getter && getter.isReactWarning\n if (mayWarn) {\n return (element as any).ref as React.Ref<Element>\n }\n // React 19 in DEV\n getter = Object.getOwnPropertyDescriptor(element, 'ref')?.get\n mayWarn = getter && 'isReactWarning' in getter && getter.isReactWarning\n if (mayWarn) {\n return (element.props as { ref?: React.Ref<Element> }).ref\n }\n\n // Not DEV\n return (element.props as { ref?: React.Ref<Element> }).ref || ((element as any).ref as React.Ref<Element>)\n}\n\nconst resolveAnchor = (anchor: MagicPortalProps['anchor']) => {\n if (typeof anchor === 'string') {\n return document.querySelector(anchor)\n } else if (typeof anchor === 'function') {\n return anchor()\n } else if (anchor && 'current' in anchor) {\n return anchor.current\n } else {\n return anchor\n }\n}\n\nconst resolveContainer = (anchor: Element | null, position: MagicPortalProps['position']): Element | null => {\n if (!anchor) {\n return null\n }\n\n return position === 'first' || position === 'last' ? anchor : anchor.parentElement\n}\n\n/**\n * https://github.com/facebook/react/blob/d91d28c8ba6fe7c96e651f82fc47c9d5481bf5f9/packages/react-reconciler/src/ReactFiberHooks.js#L2792\n */\nconst setRef = <T>(ref: React.Ref<T> | undefined, value: T) => {\n if (typeof ref === 'function') {\n return ref(value)\n } else if (ref !== null && ref !== undefined) {\n ref.current = value\n }\n}\n\nconst mergeRef = <T extends Element | null>(...refs: (React.Ref<T> | undefined)[]) => {\n return (node: T) => {\n const cleanups = refs.map((ref) => setRef(ref, node))\n return () =>\n cleanups.forEach((cleanup, index) => (typeof cleanup === 'function' ? cleanup() : setRef(refs[index], null)))\n }\n}\n\nconst MagicPortal = ({\n anchor,\n position = 'last',\n root = document.body,\n children,\n onMount,\n onUnmount\n}: MagicPortalProps) => {\n const anchorRef = useRef<Element | null>(null)\n const [container, setContainer] = useState<Element | null>(null)\n\n const insertNode = useCallback(\n (node: Element | null) => {\n if (!node) {\n return\n }\n\n const anchorElement = anchorRef.current\n if (!anchorElement) {\n return\n }\n\n const containerElement = resolveContainer(anchorElement, position)\n if (!containerElement) {\n return\n }\n\n let alreadyPlaced = false\n\n switch (position) {\n case 'last':\n alreadyPlaced = node.parentElement === containerElement && containerElement.lastChild === node\n break\n case 'first':\n alreadyPlaced = node.parentElement === containerElement && containerElement.firstChild === node\n break\n case 'before':\n alreadyPlaced = node.parentElement === containerElement && anchorElement.previousSibling === node\n break\n case 'after':\n alreadyPlaced = node.parentElement === containerElement && anchorElement.nextSibling === node\n break\n }\n\n if (!alreadyPlaced) {\n const positionMap = {\n before: 'beforebegin',\n first: 'afterbegin',\n last: 'beforeend',\n after: 'afterend'\n } as const\n anchorElement.insertAdjacentElement(positionMap[position], node)\n }\n },\n [position]\n )\n\n const child = useMemo(() => {\n if (React.Children.count(children) > 1) {\n console.error(\n '[react-magic-portal] Multiple children are not supported, expected to receive a single React element child.'\n )\n return null\n }\n\n if (!React.isValidElement(children)) {\n return null\n }\n\n if (children.type === React.Fragment) {\n console.error(\n '[react-magic-portal] Fragment children are not supported, expected to receive a single React element child.'\n )\n return null\n }\n\n const originalRef = getElementRef(children)\n return React.cloneElement(children as React.ReactElement<any>, {\n ref: mergeRef(originalRef, insertNode)\n })\n }, [children, insertNode])\n\n const update = useCallback(() => {\n anchorRef.current = resolveAnchor(anchor)\n const nextContainer = resolveContainer(anchorRef.current, position)\n /**\n * React 19 in DEV\n * Suppress DevTools warning from React runtime about conflicting container children.\n * @see https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js#L973\n */\n if (nextContainer) {\n ;(nextContainer as { __reactWarnedAboutChildrenConflict?: boolean }).__reactWarnedAboutChildrenConflict = true\n }\n\n setContainer(nextContainer)\n }, [anchor, position])\n\n useLayoutEffect(() => {\n update()\n\n const observer = new MutationObserver((mutations) => {\n const isSelfMutation = mutations\n .flatMap(({ addedNodes, removedNodes }) => [...addedNodes, ...removedNodes])\n .some((node) => container?.contains(node))\n !isSelfMutation && update()\n })\n\n observer.observe(root, {\n childList: true,\n subtree: true\n })\n return () => observer.disconnect()\n }, [update, anchor, container, root])\n\n useEffect(() => {\n if (container && anchorRef.current) {\n onMount?.(anchorRef.current, container)\n return () => {\n onUnmount?.(anchorRef.current!, container)\n }\n }\n }, [onMount, onUnmount, container])\n\n return container && child ? createPortal(child, container) : null\n}\n\nMagicPortal.displayName = 'MagicPortal'\n\nexport default MagicPortal\n"],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import { createPortal } from 'react-dom'
|
|
|
3
3
|
|
|
4
4
|
export interface MagicPortalProps {
|
|
5
5
|
anchor: string | (() => Element | null) | Element | React.RefObject<Element | null> | null
|
|
6
|
-
position?: '
|
|
6
|
+
position?: 'last' | 'first' | 'before' | 'after'
|
|
7
7
|
root?: Element
|
|
8
8
|
children?: React.ReactElement | null
|
|
9
9
|
onMount?: (anchor: Element, container: Element) => void
|
|
@@ -48,7 +48,7 @@ const resolveContainer = (anchor: Element | null, position: MagicPortalProps['po
|
|
|
48
48
|
return null
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
return position === '
|
|
51
|
+
return position === 'first' || position === 'last' ? anchor : anchor.parentElement
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/**
|
|
@@ -72,7 +72,7 @@ const mergeRef = <T extends Element | null>(...refs: (React.Ref<T> | undefined)[
|
|
|
72
72
|
|
|
73
73
|
const MagicPortal = ({
|
|
74
74
|
anchor,
|
|
75
|
-
position = '
|
|
75
|
+
position = 'last',
|
|
76
76
|
root = document.body,
|
|
77
77
|
children,
|
|
78
78
|
onMount,
|
|
@@ -100,10 +100,10 @@ const MagicPortal = ({
|
|
|
100
100
|
let alreadyPlaced = false
|
|
101
101
|
|
|
102
102
|
switch (position) {
|
|
103
|
-
case '
|
|
103
|
+
case 'last':
|
|
104
104
|
alreadyPlaced = node.parentElement === containerElement && containerElement.lastChild === node
|
|
105
105
|
break
|
|
106
|
-
case '
|
|
106
|
+
case 'first':
|
|
107
107
|
alreadyPlaced = node.parentElement === containerElement && containerElement.firstChild === node
|
|
108
108
|
break
|
|
109
109
|
case 'before':
|
|
@@ -117,8 +117,8 @@ const MagicPortal = ({
|
|
|
117
117
|
if (!alreadyPlaced) {
|
|
118
118
|
const positionMap = {
|
|
119
119
|
before: 'beforebegin',
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
first: 'afterbegin',
|
|
121
|
+
last: 'beforeend',
|
|
122
122
|
after: 'afterend'
|
|
123
123
|
} as const
|
|
124
124
|
anchorElement.insertAdjacentElement(positionMap[position], node)
|