react-magic-portal 1.1.4 → 1.1.6
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 +14 -0
- package/README.md +2 -9
- package/__tests__/README.md +6 -0
- package/__tests__/package.json +2 -2
- package/__tests__/src/magic-portal.test.tsx +23 -0
- package/package.json +3 -3
- 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/package.json +3 -3
- package/packages/component/src/index.ts +23 -28
- package/packages/example/dist/assets/index-BeXVndGe.js +49 -0
- package/packages/example/dist/index.html +1 -1
- package/packages/example/package.json +2 -2
- package/packages/example/src/App.tsx +4 -4
- package/packages/example/dist/assets/index-BXJSx7fv.js +0 -49
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [1.1.6](https://github.com/molvqingtai/react-magic-portal/compare/v1.1.5...v1.1.6) (2025-09-27)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* support Cleanup functions for refs ([a31b2cb](https://github.com/molvqingtai/react-magic-portal/commit/a31b2cb6323b1fc2d38836ec22ef99c9456901e2))
|
|
7
|
+
|
|
8
|
+
## [1.1.5](https://github.com/molvqingtai/react-magic-portal/compare/v1.1.4...v1.1.5) (2025-09-26)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Performance Improvements
|
|
12
|
+
|
|
13
|
+
* expand the anchor point mutation detection range ([110229f](https://github.com/molvqingtai/react-magic-portal/commit/110229f438cff2674625ab49438b9cb41eab76ee))
|
|
14
|
+
|
|
1
15
|
## [1.1.4](https://github.com/molvqingtai/react-magic-portal/compare/v1.1.3...v1.1.4) (2025-09-26)
|
|
2
16
|
|
|
3
17
|
|
package/README.md
CHANGED
|
@@ -257,21 +257,14 @@ const MyComponent = () => {
|
|
|
257
257
|
return <div>My Component Content</div>
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
// Wrap the component in a DOM element
|
|
260
|
+
// Wrap the component in a transparent DOM element
|
|
261
261
|
<MagicPortal anchor="#target">
|
|
262
|
-
<div>
|
|
262
|
+
<div style={{ display: 'contents' }}>
|
|
263
263
|
<MyComponent />
|
|
264
264
|
</div>
|
|
265
265
|
</MagicPortal>
|
|
266
266
|
```
|
|
267
267
|
|
|
268
|
-
### Browser Extension Development Tips
|
|
269
|
-
|
|
270
|
-
- **Test dynamic content scenarios** - Many web pages load content asynchronously
|
|
271
|
-
- **Handle multiple SPA navigation** - Single Page Applications may recreate elements frequently
|
|
272
|
-
- **Monitor for anchor disappearance** - Use `onUnmount` callback to handle cleanup
|
|
273
|
-
- **Use specific selectors** - Avoid overly generic CSS selectors that might match unintended elements
|
|
274
|
-
|
|
275
268
|
## License
|
|
276
269
|
|
|
277
270
|
MIT © [molvqingtai](https://github.com/molvqingtai)
|
package/__tests__/package.json
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"@eslint/js": "^9.36.0",
|
|
22
22
|
"@testing-library/react": "^16.3.0",
|
|
23
23
|
"@testing-library/user-event": "^14.6.1",
|
|
24
|
-
"@types/react": "^19.1.
|
|
24
|
+
"@types/react": "^19.1.14",
|
|
25
25
|
"@types/react-dom": "^19.1.9",
|
|
26
26
|
"@vitejs/plugin-react": "^5.0.3",
|
|
27
27
|
"@vitest/coverage-v8": "^3.2.4",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"eslint": "^9.36.0",
|
|
30
30
|
"eslint-plugin-prettier": "^5.5.4",
|
|
31
31
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
32
|
-
"eslint-plugin-react-refresh": "^0.4.
|
|
32
|
+
"eslint-plugin-react-refresh": "^0.4.22",
|
|
33
33
|
"globals": "^16.4.0",
|
|
34
34
|
"happy-dom": "^18.0.1",
|
|
35
35
|
"react": "^19.1.1",
|
|
@@ -768,4 +768,27 @@ describe('MagicPortal', () => {
|
|
|
768
768
|
expect(screen.queryByTestId('portal-content')).toBeNull()
|
|
769
769
|
})
|
|
770
770
|
})
|
|
771
|
+
it('should call ref cleanup function on unmount', () => {
|
|
772
|
+
const cleanupFn = vi.fn()
|
|
773
|
+
// React 19: Cleanup functions for refs
|
|
774
|
+
const customRef = () => () => cleanupFn()
|
|
775
|
+
|
|
776
|
+
const anchor = document.createElement('div')
|
|
777
|
+
anchor.id = 'cleanup-ref-anchor'
|
|
778
|
+
document.body.appendChild(anchor)
|
|
779
|
+
|
|
780
|
+
const { unmount } = render(
|
|
781
|
+
<MagicPortal anchor="#cleanup-ref-anchor">
|
|
782
|
+
<div ref={customRef} data-testid="portal-content">
|
|
783
|
+
Portal Content
|
|
784
|
+
</div>
|
|
785
|
+
</MagicPortal>
|
|
786
|
+
)
|
|
787
|
+
|
|
788
|
+
expect(screen.getByTestId('portal-content')).toBeTruthy()
|
|
789
|
+
|
|
790
|
+
unmount()
|
|
791
|
+
|
|
792
|
+
expect(cleanupFn).toHaveBeenCalledTimes(1)
|
|
793
|
+
})
|
|
771
794
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-magic-portal",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "React Portal with dynamic mounting support",
|
|
5
5
|
"main": "packages/component/dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
},
|
|
52
52
|
"homepage": "https://github.com/molvqingtai/react-magic-portal#readme",
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@commitlint/cli": "^
|
|
55
|
-
"@commitlint/config-conventional": "^
|
|
54
|
+
"@commitlint/cli": "^20.0.0",
|
|
55
|
+
"@commitlint/config-conventional": "^20.0.0",
|
|
56
56
|
"@semantic-release/changelog": "^6.0.3",
|
|
57
57
|
"@semantic-release/git": "^10.0.1",
|
|
58
58
|
"husky": "^9.1.7",
|
|
@@ -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,QAAgB,GAAA,SAAA,GAAA,QAAA,GAAA,OAAA;EAAA,QAAA,CAAA,EAGpB,KAAA,CAAM,YAHc,GAGC,KAAA,CAAM,YAHP,EAAA;SACP,CAAA,EAAA,CAAA,MAAA,EAGL,OAHK,EAAA,SAAA,EAGe,OAHf,EAAA,GAAA,IAAA;WAAkB,CAAA,EAAA,CAAA,MAAA,EAIrB,OAJqB,EAAA,SAAA,EAID,OAJC,EAAA,GAAA,IAAA;KAA0B,CAAA,EAK9D,KAAA,CAAM,GALwD;;
|
|
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,QAAgB,GAAA,SAAA,GAAA,QAAA,GAAA,OAAA;EAAA,QAAA,CAAA,EAGpB,KAAA,CAAM,YAHc,GAGC,KAAA,CAAM,YAHP,EAAA;SACP,CAAA,EAAA,CAAA,MAAA,EAGL,OAHK,EAAA,SAAA,EAGe,OAHf,EAAA,GAAA,IAAA;WAAkB,CAAA,EAAA,CAAA,MAAA,EAIrB,OAJqB,EAAA,SAAA,EAID,OAJC,EAAA,GAAA,IAAA;KAA0B,CAAA,EAK9D,KAAA,CAAM,GALwD;;cA4DhE,WA1Da,EAAA;;IAAe,MAAM;IAAA,QAAA;IAAA,QAAA;IAAA,OAAA;IAAA,SAAA;IAAA;EAAA,CAAA,EA0DiD,gBA1DjD,CAAA,EA0DiE,KAAA,CAAA,WA1DjE,GAAA,IAAA;aACnB,EAAA,MAAA"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import e,{useCallback as t,useEffect as n,useLayoutEffect as r,useRef as i,useState as a}from"react";import o from"react-dom";const s=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)},c=e=>typeof e==`string`?document.querySelector(e):typeof e==`function`?e():e&&`current`in e?e.current:e,l=(
|
|
1
|
+
import e,{useCallback as t,useEffect as n,useLayoutEffect as r,useRef as i,useState as a}from"react";import o from"react-dom";const s=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)},c=e=>typeof e==`string`?document.querySelector(e):typeof e==`function`?e():e&&`current`in e?e.current:e,l=(e,t)=>{if(typeof e==`function`)return e(t);e!=null&&(e.current=t)},u=(...e)=>t=>{let n=e.map(e=>l(e,t));return()=>n.forEach((t,n)=>typeof t==`function`?t():l(e[n],null))},d=({anchor:l,position:d=`append`,children:f,onMount:p,onUnmount:m,key:h})=>{let g=i(null),[_,v]=a(null),y=e.Children.map(f,t=>{if(!e.isValidElement(t))return null;let n=s(t);return e.cloneElement(t,{ref:u(n,e=>{e&&g.current?.insertAdjacentElement({before:`beforebegin`,prepend:`afterbegin`,append:`beforeend`,after:`afterend`}[d],e)})})}),b=t(()=>{g.current=c(l),v(d===`prepend`||d===`append`?g.current:g.current?.parentElement??null)},[l,d]);return r(()=>{b();let e=new MutationObserver(e=>{!e.flatMap(({addedNodes:e,removedNodes:t})=>[...e,...t]).some(e=>_?.contains(e))&&b()});return e.observe(document.body,{childList:!0,subtree:!0}),()=>e.disconnect()},[b,l,_]),n(()=>{if(_&&g.current)return p?.(g.current,_),()=>{m?.(g.current,_)}},[p,m,_]),_&&o.createPortal(y,_,h)};d.displayName=`MagicPortal`;var f=d;export{f as default};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import React, { useEffect, useState, useRef, useCallback, useLayoutEffect } from 'react'\nimport ReactDOM from 'react-dom'\n\nexport interface MagicPortalProps {\n anchor: string | (() => Element | null) | Element | React.RefObject<Element | null> | null\n position?: 'append' | 'prepend' | 'before' | 'after'\n children?: React.ReactElement | React.ReactElement[]\n onMount?: (anchor: Element, container: Element) => void\n onUnmount?: (anchor: Element, container: Element) => void\n key?: React.Key\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\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 = ({ anchor, position = 'append', children, onMount, onUnmount, key }: MagicPortalProps) => {\n const anchorRef = useRef<Element | null>(null)\n const [container, setContainer] = useState<Element | null>(null)\n\n const nodes = React.Children.map(children, (item) => {\n if (!React.isValidElement(item)) {\n return null\n }\n const originalRef = getElementRef(item)\n return React.cloneElement(item as React.ReactElement<any>, {\n ref: mergeRef(originalRef, (node) => {\n const positionMap = {\n before: 'beforebegin',\n prepend: 'afterbegin',\n append: 'beforeend',\n after: 'afterend'\n } as const\n node && anchorRef.current?.insertAdjacentElement(positionMap[position], node)\n })\n })\n })\n\n const update = useCallback(() => {\n anchorRef.current = resolveAnchor(anchor)\n setContainer(\n position === 'prepend' || position === 'append' ? anchorRef.current : (anchorRef.current?.parentElement ?? null)\n )\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(document.body, {\n childList: true,\n subtree: true\n })\n return () => observer.disconnect()\n }, [update, anchor, container])\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 && ReactDOM.createPortal(nodes, container, key)\n}\n\nMagicPortal.displayName = 'MagicPortal'\n\nexport default MagicPortal\n"],"mappings":"8HAeA,MAAM,EAAiB,GAAgC,CAErD,IAAI,EAAS,OAAO,yBAAyB,EAAQ,MAAO,MAAM,EAAE,IAChE,EAAU,GAAU,mBAAoB,GAAU,EAAO,eAY7D,OAXI,EACM,EAAgB,KAG1B,EAAS,OAAO,yBAAyB,EAAS,MAAM,EAAE,IAC1D,EAAU,GAAU,mBAAoB,GAAU,EAAO,eACrD,EACM,EAAQ,MAAuC,IAIjD,EAAQ,MAAuC,KAAS,EAAgB,MAG5E,EAAiB,GACjB,OAAO,GAAW,SACb,SAAS,cAAc,EAAO,CAC5B,OAAO,GAAW,WACpB,GAAQ,CACN,GAAU,YAAa,EACzB,EAAO,QAEP,EAOL,GAAa,EAA+B,IAAa,CAC7D,GAAI,OAAO,GAAQ,WACjB,OAAO,EAAI,EAAM,CACR,GAAQ,OACjB,EAAI,QAAU,IAIZ,GAAsC,GAAG,IACrC,GAAY,CAClB,IAAM,EAAW,EAAK,IAAK,GAAQ,EAAO,EAAK,EAAK,CAAC,CACrD,UACE,EAAS,SAAS,EAAS,IAAW,OAAO,GAAY,WAAa,GAAS,CAAG,EAAO,EAAK,GAAQ,KAAK,CAAE,EAI7G,GAAe,CAAE,SAAQ,WAAW,SAAU,WAAU,UAAS,YAAW,SAA4B,CAC5G,IAAM,EAAY,EAAuB,KAAK,CACxC,CAAC,EAAW,GAAgB,EAAyB,KAAK,CAE1D,EAAQ,EAAM,SAAS,IAAI,EAAW,GAAS,CACnD,GAAI,CAAC,EAAM,eAAe,EAAK,CAC7B,OAAO,KAET,IAAM,EAAc,EAAc,EAAK,CACvC,OAAO,EAAM,aAAa,EAAiC,CACzD,IAAK,EAAS,EAAc,GAAS,CAOnC,GAAQ,EAAU,SAAS,sBANP,CAClB,OAAQ,cACR,QAAS,aACT,OAAQ,YACR,MAAO,WACR,CAC4D,GAAW,EAAK,EAC7E,CACH,CAAC,EACF,CAEI,EAAS,MAAkB,CAC/B,EAAU,QAAU,EAAc,EAAO,CACzC,EACE,IAAa,WAAa,IAAa,SAAW,EAAU,QAAW,EAAU,SAAS,eAAiB,KAC5G,EACA,CAAC,EAAQ,EAAS,CAAC,CA4BtB,OA1BA,MAAsB,CACpB,GAAQ,CAER,IAAM,EAAW,IAAI,iBAAkB,GAAc,CAInD,CAHuB,EACpB,SAAS,CAAE,aAAY,kBAAmB,CAAC,GAAG,EAAY,GAAG,EAAa,CAAC,CAC3E,KAAM,GAAS,GAAW,SAAS,EAAK,CAAC,EACzB,GAAQ,EAC3B,CAMF,OAJA,EAAS,QAAQ,SAAS,KAAM,CAC9B,UAAW,GACX,QAAS,GACV,CAAC,KACW,EAAS,YAAY,EACjC,CAAC,EAAQ,EAAQ,EAAU,CAAC,CAE/B,MAAgB,CACd,GAAI,GAAa,EAAU,QAEzB,OADA,IAAU,EAAU,QAAS,EAAU,KAC1B,CACX,IAAY,EAAU,QAAU,EAAU,GAG7C,CAAC,EAAS,EAAW,EAAU,CAAC,CAE5B,GAAa,EAAS,aAAa,EAAO,EAAW,EAAI,EAGlE,EAAY,YAAc,cAE1B,IAAA,EAAe"}
|
|
@@ -49,18 +49,18 @@
|
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@eslint/js": "^9.36.0",
|
|
51
51
|
"@types/node": "^24.5.2",
|
|
52
|
-
"@types/react": "^19.1.
|
|
52
|
+
"@types/react": "^19.1.14",
|
|
53
53
|
"@types/react-dom": "^19.1.9",
|
|
54
54
|
"eslint": "^9.36.0",
|
|
55
55
|
"eslint-config-prettier": "^10.1.8",
|
|
56
56
|
"eslint-plugin-prettier": "^5.5.4",
|
|
57
57
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
58
|
-
"eslint-plugin-react-refresh": "^0.4.
|
|
58
|
+
"eslint-plugin-react-refresh": "^0.4.22",
|
|
59
59
|
"globals": "^16.4.0",
|
|
60
60
|
"prettier": "^3.6.2",
|
|
61
61
|
"react": "^19.1.1",
|
|
62
62
|
"react-dom": "^19.1.1",
|
|
63
|
-
"tsdown": "^0.15.
|
|
63
|
+
"tsdown": "^0.15.5",
|
|
64
64
|
"typescript": "^5.9.2",
|
|
65
65
|
"typescript-eslint": "^8.44.1"
|
|
66
66
|
},
|
|
@@ -43,15 +43,23 @@ const resolveAnchor = (anchor: MagicPortalProps['anchor']) => {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* https://github.com/facebook/react/blob/d91d28c8ba6fe7c96e651f82fc47c9d5481bf5f9/packages/react-reconciler/src/ReactFiberHooks.js#L2792
|
|
48
|
+
*/
|
|
49
|
+
const setRef = <T>(ref: React.Ref<T> | undefined, value: T) => {
|
|
50
|
+
if (typeof ref === 'function') {
|
|
51
|
+
return ref(value)
|
|
52
|
+
} else if (ref !== null && ref !== undefined) {
|
|
53
|
+
ref.current = value
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
46
57
|
const mergeRef = <T extends Element | null>(...refs: (React.Ref<T> | undefined)[]) => {
|
|
47
|
-
return (node: T) =>
|
|
48
|
-
refs.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
ref.current = node
|
|
53
|
-
}
|
|
54
|
-
})
|
|
58
|
+
return (node: T) => {
|
|
59
|
+
const cleanups = refs.map((ref) => setRef(ref, node))
|
|
60
|
+
return () =>
|
|
61
|
+
cleanups.forEach((cleanup, index) => (typeof cleanup === 'function' ? cleanup() : setRef(refs[index], null)))
|
|
62
|
+
}
|
|
55
63
|
}
|
|
56
64
|
|
|
57
65
|
const MagicPortal = ({ anchor, position = 'append', children, onMount, onUnmount, key }: MagicPortalProps) => {
|
|
@@ -78,40 +86,27 @@ const MagicPortal = ({ anchor, position = 'append', children, onMount, onUnmount
|
|
|
78
86
|
|
|
79
87
|
const update = useCallback(() => {
|
|
80
88
|
anchorRef.current = resolveAnchor(anchor)
|
|
81
|
-
|
|
89
|
+
setContainer(
|
|
82
90
|
position === 'prepend' || position === 'append' ? anchorRef.current : (anchorRef.current?.parentElement ?? null)
|
|
83
|
-
|
|
91
|
+
)
|
|
84
92
|
}, [anchor, position])
|
|
85
93
|
|
|
86
94
|
useLayoutEffect(() => {
|
|
87
95
|
update()
|
|
88
96
|
|
|
89
97
|
const observer = new MutationObserver((mutations) => {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return true
|
|
95
|
-
}
|
|
96
|
-
// Only check added nodes when anchor is a string selector
|
|
97
|
-
if (
|
|
98
|
-
typeof anchor === 'string' &&
|
|
99
|
-
[...addedNodes].some((node) => node instanceof Element && node.matches?.(anchor))
|
|
100
|
-
) {
|
|
101
|
-
return true
|
|
102
|
-
}
|
|
103
|
-
return false
|
|
104
|
-
})
|
|
105
|
-
shouldUpdate && update()
|
|
98
|
+
const isSelfMutation = mutations
|
|
99
|
+
.flatMap(({ addedNodes, removedNodes }) => [...addedNodes, ...removedNodes])
|
|
100
|
+
.some((node) => container?.contains(node))
|
|
101
|
+
!isSelfMutation && update()
|
|
106
102
|
})
|
|
107
103
|
|
|
108
104
|
observer.observe(document.body, {
|
|
109
105
|
childList: true,
|
|
110
106
|
subtree: true
|
|
111
107
|
})
|
|
112
|
-
|
|
113
108
|
return () => observer.disconnect()
|
|
114
|
-
}, [update, anchor])
|
|
109
|
+
}, [update, anchor, container])
|
|
115
110
|
|
|
116
111
|
useEffect(() => {
|
|
117
112
|
if (container && anchorRef.current) {
|