{"_id":"@charley-kim/react-preserve-portal","_rev":"5-bacf93f8c3449664b8dcf215bc7049bb","name":"@charley-kim/react-preserve-portal","dist-tags":{"latest":"0.2.5"},"versions":{"0.2.1":{"name":"@charley-kim/react-preserve-portal","version":"0.2.1","_id":"@charley-kim/react-preserve-portal@0.2.1","maintainers":[{"name":"kaycekim","email":"kickbelldev@gmail.com"}],"dist":{"shasum":"948a9a1bc9fec3238eac93729a31d73391c246f1","tarball":"https://registry.npmjs.org/@charley-kim/react-preserve-portal/-/react-preserve-portal-0.2.1.tgz","fileCount":7,"integrity":"sha512-er/5sIXHE+IO4ahkb+BCOEBoHRYLccW0wR2nH5ZA56gC+UKr9s0XzrVIiGoIVMoCIT7SvKYBN36UfvDUUXVcFQ==","signatures":[{"sig":"MEYCIQDDI6NOjNPWYNpCA8b7JR4hXz8U+Wmuo6hzml5HvcswkAIhAJU7PYvs964Fg0BWhXJV+3cYe+2kRrVwRv0yihu51rGC","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":21347},"main":"./dist/index.cjs","type":"module","_from":"file:charley-kim-react-preserve-portal-0.2.1.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"dev":"nx vite:build --watch","lint":"nx lint","test":"nx test","build":"nx vite:build"},"_npmUser":{"name":"kaycekim","email":"kickbelldev@gmail.com"},"_resolved":"/tmp/a35fbd9f6ba651a0e0dae894e76e2ba8/charley-kim-react-preserve-portal-0.2.1.tgz","_integrity":"sha512-er/5sIXHE+IO4ahkb+BCOEBoHRYLccW0wR2nH5ZA56gC+UKr9s0XzrVIiGoIVMoCIT7SvKYBN36UfvDUUXVcFQ==","_npmVersion":"10.9.2","description":"[![npm version](https://img.shields.io/npm/v/@charley-kim/react-preserve-portal.svg)](https://www.npmjs.com/package/@charley-kim/react-preserve-portal) [![bundle size](https://img.shields.io/bundlephobia/minzip/@charley-kim/react-preserve-portal)](https:/","directories":{},"_nodeVersion":"22.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^7.2.4","jsdom":"^25.0.1","react":"^19.2.0","vitest":"^4.0.0","react-dom":"^19.2.0","@vitest/ui":"^4.0.0","typescript":"~5.9.3","@types/react":"^19.2.5","vite-plugin-dts":"^4.5.4","@types/react-dom":"^19.2.3","@vitejs/plugin-react":"^5.1.1","@testing-library/react":"^16.1.0","@testing-library/jest-dom":"^6.6.3","@testing-library/user-event":"^14.5.2"},"peerDependencies":{"react":">=18.0.0","react-dom":">=18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/react-preserve-portal_0.2.1_1767283429127_0.5826563910840232","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"name":"@charley-kim/react-preserve-portal","version":"0.2.2","keywords":["react","portal","dom","preserve","stateful","video-player","webgl","threejs","maps","iframe","canvas","dom-instance","unmanaged-dom","state-preservation","type-safe","typescript","react-portal","dom-manipulation","mini-player","pip","rich-text-editor","charts","data-visualization"],"_id":"@charley-kim/react-preserve-portal@0.2.2","maintainers":[{"name":"kaycekim","email":"kickbelldev@gmail.com"}],"dist":{"shasum":"1802bcea4d0a7d5bd815a47ef5f9c4f77b7da8d0","tarball":"https://registry.npmjs.org/@charley-kim/react-preserve-portal/-/react-preserve-portal-0.2.2.tgz","fileCount":7,"integrity":"sha512-pDzIgLziIhCQRhpy5mYzO0FEmwkPZv3boCl/Mzz196WIrw2KY+BoM8ed29Lk6hNRCguM9abvn7HMwBIWRAZuEA==","signatures":[{"sig":"MEUCIQDsfMQ0S+oiqmXL9aJHs3Y3axYSV6XXc8uoDKhExf/v5AIgZxzPyLVfc+FS+omh3sYx9uBBeV2WF6dt1SzmaHFj/JE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":21344},"main":"./dist/index.cjs","type":"module","_from":"file:charley-kim-react-preserve-portal-0.2.2.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"dev":"nx vite:build --watch","lint":"nx lint","test":"nx test","build":"nx vite:build"},"_npmUser":{"name":"kaycekim","email":"kickbelldev@gmail.com"},"_resolved":"/tmp/eb2e129fd37749ee6a8f5584c8efb5c6/charley-kim-react-preserve-portal-0.2.2.tgz","_integrity":"sha512-pDzIgLziIhCQRhpy5mYzO0FEmwkPZv3boCl/Mzz196WIrw2KY+BoM8ed29Lk6hNRCguM9abvn7HMwBIWRAZuEA==","_npmVersion":"10.9.2","description":"A Type-safe React portal library that preserves DOM instances when moving content between slots, perfect for video players, WebGL scenes, and maps.","directories":{},"_nodeVersion":"22.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^7.2.4","jsdom":"^25.0.1","react":"^19.2.0","vitest":"^4.0.0","react-dom":"^19.2.0","@vitest/ui":"^4.0.0","typescript":"~5.9.3","@types/react":"^19.2.5","vite-plugin-dts":"^4.5.4","@types/react-dom":"^19.2.3","@vitejs/plugin-react":"^5.1.1","@testing-library/react":"^16.1.0","@testing-library/jest-dom":"^6.6.3","@testing-library/user-event":"^14.5.2"},"peerDependencies":{"react":">=18.0.0","react-dom":">=18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/react-preserve-portal_0.2.2_1767284905106_0.5392626209622673","host":"s3://npm-registry-packages-npm-production"}},"0.2.3":{"name":"@charley-kim/react-preserve-portal","version":"0.2.3","keywords":["react","portal","dom","preserve","stateful","video-player","webgl","threejs","maps","iframe","canvas","dom-instance","unmanaged-dom","state-preservation","type-safe","typescript","react-portal","dom-manipulation","mini-player","pip","rich-text-editor","charts","data-visualization"],"_id":"@charley-kim/react-preserve-portal@0.2.3","maintainers":[{"name":"kaycekim","email":"kickbelldev@gmail.com"}],"dist":{"shasum":"e417039d0aec2cdc34431b61621a930899f641fe","tarball":"https://registry.npmjs.org/@charley-kim/react-preserve-portal/-/react-preserve-portal-0.2.3.tgz","fileCount":7,"integrity":"sha512-oKN2Wo+Kr9vXt0ma5fvLAixsW6g4gQTh27T3jyNSIai24dkkikGZI1XReloz/crPl5uiNrQDsxMRBEfptIzcZA==","signatures":[{"sig":"MEMCHytgE6EJ7e29twYKPj7ajkG1uLXOKOknyHEXC8rq8ZECIAoSpZq/fRLjyJ+2x11Bz7zVE2liinfitR+W5YF3HKtG","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":21344},"main":"./dist/index.cjs","type":"module","_from":"file:charley-kim-react-preserve-portal-0.2.3.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"dev":"nx vite:build --watch","lint":"nx lint","test":"nx test","build":"nx vite:build"},"_npmUser":{"name":"kaycekim","email":"kickbelldev@gmail.com"},"_resolved":"/tmp/b9198af30423d72ca352133c83a286ff/charley-kim-react-preserve-portal-0.2.3.tgz","_integrity":"sha512-oKN2Wo+Kr9vXt0ma5fvLAixsW6g4gQTh27T3jyNSIai24dkkikGZI1XReloz/crPl5uiNrQDsxMRBEfptIzcZA==","_npmVersion":"10.9.2","description":"A Type-safe React portal library that preserves DOM instances when moving content between slots, perfect for video players, WebGL scenes, and maps.","directories":{},"_nodeVersion":"22.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^7.2.4","jsdom":"^25.0.1","react":"^19.2.0","vitest":"^4.0.0","react-dom":"^19.2.0","@vitest/ui":"^4.0.0","typescript":"~5.9.3","@types/react":"^19.2.5","vite-plugin-dts":"^4.5.4","@types/react-dom":"^19.2.3","@vitejs/plugin-react":"^5.1.1","@testing-library/react":"^16.1.0","@testing-library/jest-dom":"^6.6.3","@testing-library/user-event":"^14.5.2"},"peerDependencies":{"react":">=18.0.0","react-dom":">=18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/react-preserve-portal_0.2.3_1767312872003_0.9141247049678085","host":"s3://npm-registry-packages-npm-production"}},"0.2.4":{"name":"@charley-kim/react-preserve-portal","version":"0.2.4","keywords":["react","portal","dom","preserve","stateful","video-player","webgl","threejs","maps","iframe","canvas","dom-instance","unmanaged-dom","state-preservation","type-safe","typescript","react-portal","dom-manipulation","mini-player","pip","rich-text-editor","charts","data-visualization"],"_id":"@charley-kim/react-preserve-portal@0.2.4","maintainers":[{"name":"kaycekim","email":"kickbelldev@gmail.com"}],"dist":{"shasum":"c2c1dfc3445c676f31be0676862b9e80e523a705","tarball":"https://registry.npmjs.org/@charley-kim/react-preserve-portal/-/react-preserve-portal-0.2.4.tgz","fileCount":5,"integrity":"sha512-IumqawW2OiJ+bOm6/riTmXwLFv59XCiv+xun3E1O27ut+0BYVEpCE/y/bsisT2Y0RDoZ+AFjuDeON16jn9l6Lw==","signatures":[{"sig":"MEQCIFsW7czUO5jzWN9y7VBbjJ0ojDM2aX8yDs9uUj504mnsAiB40fACf36HxqspP9c6Lws+9wmV4cYgDSfCyL0ARfLDQQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":18171},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"1ba3cea2d40cd68b59353cf45c3a3bdff6b761fb","scripts":{"dev":"nx vite:build --watch","lint":"nx lint","test":"nx test","build":"nx vite:build"},"_npmUser":{"name":"kaycekim","email":"kickbelldev@gmail.com"},"_npmVersion":"10.9.2","description":"A Type-safe React portal library that preserves DOM instances when moving content between slots, perfect for video players, WebGL scenes, and maps.","directories":{},"_nodeVersion":"22.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^7.2.4","jsdom":"^25.0.1","react":"^19.2.0","vitest":"^4.0.0","react-dom":"^19.2.0","@vitest/ui":"^4.0.0","typescript":"~5.9.3","@types/react":"^19.2.5","vite-plugin-dts":"^4.5.4","@types/react-dom":"^19.2.3","@vitejs/plugin-react":"^5.1.1","@testing-library/react":"^16.1.0","@testing-library/jest-dom":"^6.6.3","@testing-library/user-event":"^14.5.2"},"peerDependencies":{"react":">=18.0.0","react-dom":">=18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/react-preserve-portal_0.2.4_1767314513044_0.731369274564799","host":"s3://npm-registry-packages-npm-production"}},"0.2.5":{"name":"@charley-kim/react-preserve-portal","description":"A Type-safe React portal library that preserves DOM instances when moving content between slots, perfect for video players, WebGL scenes, and maps.","keywords":["react","portal","dom","preserve","stateful","video-player","webgl","threejs","maps","iframe","canvas","dom-instance","unmanaged-dom","state-preservation","type-safe","typescript","react-portal","dom-manipulation","mini-player","pip","rich-text-editor","charts","data-visualization"],"version":"0.2.5","license":"MIT","author":{"name":"Charley Kim"},"repository":{"type":"git","url":"git+https://github.com/kickbelldev/react-preserve-portal.git"},"homepage":"https://github.com/kickbelldev/react-preserve-portal#readme","bugs":{"url":"https://github.com/kickbelldev/react-preserve-portal/issues"},"type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"publishConfig":{"access":"public"},"peerDependencies":{"react":">=18.0.0","react-dom":">=18.0.0"},"devDependencies":{"@testing-library/jest-dom":"^6.6.3","@testing-library/react":"^16.1.0","@testing-library/user-event":"^14.5.2","@types/react":"^19.2.5","@types/react-dom":"^19.2.3","@vitejs/plugin-react":"^5.1.1","@vitest/ui":"^4.0.0","jsdom":"^25.0.1","react":"^19.2.0","react-dom":"^19.2.0","typescript":"~5.9.3","vite":"^7.2.4","vite-plugin-dts":"^4.5.4","vitest":"^4.0.0"},"scripts":{"build":"nx vite:build","dev":"nx vite:build --watch","lint":"nx lint","test":"nx test"},"_id":"@charley-kim/react-preserve-portal@0.2.5","_integrity":"sha512-PHL1BPa31NLBxx1vt0go3S5gdQ2ED6H8YayoUbhIlTUt6lfNn0XXlwhxPCQ2SFJ0LHtE/edABhI/ZOryE9MfpA==","_resolved":"/tmp/57c88a7bafe38271c41e93b6197f022a/charley-kim-react-preserve-portal-0.2.5.tgz","_from":"file:charley-kim-react-preserve-portal-0.2.5.tgz","_nodeVersion":"22.16.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-PHL1BPa31NLBxx1vt0go3S5gdQ2ED6H8YayoUbhIlTUt6lfNn0XXlwhxPCQ2SFJ0LHtE/edABhI/ZOryE9MfpA==","shasum":"e128b994fc060acf643533dde8a7004b1d03e053","tarball":"https://registry.npmjs.org/@charley-kim/react-preserve-portal/-/react-preserve-portal-0.2.5.tgz","fileCount":6,"unpackedSize":19558,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIAOCYfnXXS3biM84cV9wLw/hsQBvhb3NuyjG2r7tW64YAiEA7sKGq1gZl21E7WJ35U3IsKovBLRK2CagH+EvLC6Zei8="}]},"_npmUser":{"name":"kaycekim","email":"kickbelldev@gmail.com"},"directories":{},"maintainers":[{"name":"kaycekim","email":"kickbelldev@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/react-preserve-portal_0.2.5_1767530986740_0.6427419129909497"},"_hasShrinkwrap":false}},"time":{"created":"2026-01-01T16:03:49.016Z","modified":"2026-01-04T12:49:47.129Z","0.2.1":"2026-01-01T16:03:49.272Z","0.2.2":"2026-01-01T16:28:25.253Z","0.2.3":"2026-01-02T00:14:32.142Z","0.2.4":"2026-01-02T00:41:53.200Z","0.2.5":"2026-01-04T12:49:46.887Z"},"keywords":["react","portal","dom","preserve","stateful","video-player","webgl","threejs","maps","iframe","canvas","dom-instance","unmanaged-dom","state-preservation","type-safe","typescript","react-portal","dom-manipulation","mini-player","pip","rich-text-editor","charts","data-visualization"],"description":"A Type-safe React portal library that preserves DOM instances when moving content between slots, perfect for video players, WebGL scenes, and maps.","maintainers":[{"name":"kaycekim","email":"kickbelldev@gmail.com"}],"readme":"# @charley-kim/react-preserve-portal\n\n[![npm version](https://badgen.net/npm/v/@charley-kim/react-preserve-portal)](https://www.npmjs.com/package/@charley-kim/react-preserve-portal)\n[![bundle size](https://badgen.net/bundlephobia/minzip/@charley-kim/react-preserve-portal)](https://bundlephobia.com/package/@charley-kim/react-preserve-portal)\n[![license](https://badgen.net/npm/license/@charley-kim/react-preserve-portal)](https://github.com/kickbelldev/react-preserve-portal/blob/main/LICENSE)\n\nA React portal library that leverages Unmanaged DOM to dynamically move portal content while **preserving the actual DOM instances**.\n\n## Installation\n\n```bash\nnpm install @charley-kim/react-preserve-portal\n# or\npnpm add @charley-kim/react-preserve-portal\n# or\nyarn add @charley-kim/react-preserve-portal\n```\n\n## Core Concept: Unmanaged DOM Node\n\nReact's `createPortal` alone cannot preserve DOM instances because it is bound to the React lifecycle. When the portal target changes, React unmounts the existing DOM and creates a new one.\n\n```tsx\n// This causes the video to be recreated every time the target changes\ncreatePortal(<video />, slotKey === 'main' ? mainRef : miniRef)\n```\n\nThis library solves this problem by placing an **Unmanaged DOM node** (not managed by React) in between:\n\n```\nReact -> createPortal -> Unmanaged Node (div) -> Slot targets\n                              |\n                    Outside React control\n                              |\n              Only uses appendChild/removeChild\n```\n\nThis approach bypasses React's lifecycle, physically moving the DOM node.\n\n## Use Cases\n\nUseful for any DOM element where recreation is expensive or causes state loss:\n\n### Video / Audio Players\n\n- Playback position and buffer preserved\n- Network connections maintained\n- No re-loading or buffering when moving between slots\n\n### Canvas / WebGL\n\n- 3D scenes (Three.js, Babylon.js) don't need re-initialization\n- Game state preserved\n- WebGL context maintained (context loss is expensive)\n\n### Maps\n\n- Google Maps, Mapbox, Kakao Map instances preserved\n- Map position, zoom level, markers maintained\n- Avoids re-fetching map tiles\n\n### iframe\n\n- Embedded content state preserved\n- No page reload when moving\n- Login sessions in embedded widgets maintained\n\n### Rich Text Editors\n\n- Editor state (undo history, cursor position) preserved\n- Plugin initialization maintained\n- CKEditor, Quill, TipTap, etc.\n\n### Charts / Data Visualizations\n\n- D3, Chart.js, ECharts instances preserved\n- Animation states maintained\n- Avoids expensive re-rendering of large datasets\n\n### Third-party Widgets\n\n- Chat widgets, payment forms\n- SDK initialization preserved\n- User input state maintained\n\n## Quick Start\n\n```tsx\nimport { createPortal } from '@charley-kim/react-preserve-portal'\n\n// 1. Create a typed portal\nconst VideoPortal = createPortal({\n  id: 'video',\n  slots: ['main', 'mini', 'pip'],\n} as const)\n\n// 2. Use components and hooks\nfunction App() {\n  return (\n    <>\n      {/* Portal content */}\n      <VideoPortal.Host node={<video src=\"...\" />} />\n\n      {/* Portal slots */}\n      <div className=\"main-area\">\n        <VideoPortal.Slot slotKey=\"main\" />\n      </div>\n      <div className=\"mini-area\">\n        <VideoPortal.Slot slotKey=\"mini\" />\n      </div>\n    </>\n  )\n}\n\nfunction Controls() {\n  const { slotKey, setSlotKey } = VideoPortal.usePortal()\n\n  return (\n    <button onClick={() => setSlotKey(slotKey === 'main' ? 'mini' : 'main')}>\n      Toggle\n    </button>\n  )\n}\n```\n\n## Type Safety\n\nThe library provides full type safety for slot keys:\n\n```tsx\nconst VideoPortal = createPortal({\n  id: 'video',\n  slots: ['main', 'mini', 'pip'],\n} as const)\n\n// Type-safe slot keys\n<VideoPortal.Slot slotKey=\"main\" />     // OK\n<VideoPortal.Slot slotKey=\"mini\" />     // OK\n<VideoPortal.Slot slotKey=\"wrong\" />    // TypeScript Error!\n\n// Type-safe setSlotKey\nconst { setSlotKey } = VideoPortal.usePortal()\nsetSlotKey('main')   // OK\nsetSlotKey('wrong')  // TypeScript Error!\n```\n\n## API\n\n### `createPortal(options)`\n\nCreates a typed portal instance.\n\n**Options:**\n\n| Option  | Type                | Description              |\n| ------- | ------------------- | ------------------------ |\n| `id`    | `string`            | Unique portal identifier |\n| `slots` | `readonly string[]` | Array of valid slot keys |\n\n**Returns:**\n\n| Property    | Type                              | Description     |\n| ----------- | --------------------------------- | --------------- |\n| `id`        | `string`                          | Portal ID       |\n| `slots`     | `readonly string[]`               | Valid slot keys |\n| `Host`      | `(props: HostProps) => ReactNode` | Host component  |\n| `Slot`      | `(props: SlotProps) => ReactNode` | Slot component  |\n| `usePortal` | `() => UsePortalReturn`           | Portal hook     |\n\n### Host Component\n\nRenders portal content using an Unmanaged DOM node.\n\n**Props:**\n\n| Prop   | Type                          | Default | Description            |\n| ------ | ----------------------------- | ------- | ---------------------- |\n| `node` | `ReactNode`                   | -       | **Required** Content   |\n| `as`   | `keyof HTMLElementTagNameMap` | `'div'` | Container element type |\n\n**Example:**\n\n```tsx\n<VideoPortal.Host node={<video src=\"video.mp4\" />} />\n<VideoPortal.Host as=\"section\" node={<CustomComponent />} />\n```\n\n### Slot Component\n\nSpecifies where portal content should be rendered.\n\n**Props:**\n\n| Prop       | Type                          | Default | Description                      |\n| ---------- | ----------------------------- | ------- | -------------------------------- |\n| `slotKey`  | `TSlot`                       | -       | **Required** Target slot (typed) |\n| `as`       | `keyof HTMLElementTagNameMap` | `'div'` | Container element type           |\n| `...props` | `HTMLAttributes`              | -       | HTML element attributes          |\n\n**Example:**\n\n```tsx\n<VideoPortal.Slot slotKey=\"main\" />\n<VideoPortal.Slot slotKey=\"mini\" className=\"mini-player\" />\n<VideoPortal.Slot slotKey=\"pip\" as=\"section\" id=\"pip-container\" />\n```\n\n### usePortal Hook\n\nReturns portal state and actions with typed slot keys.\n\n**Returns:**\n\n| Property           | Type                                            | Description                   |\n| ------------------ | ----------------------------------------------- | ----------------------------- |\n| `slotKey`          | `TSlot \\| null`                                 | Currently active slot (typed) |\n| `returnPath`       | `string \\| null`                                | Portal return path            |\n| `targets`          | `Map<TSlot, HTMLElement>`                       | Registered targets            |\n| `setSlotKey`       | `(key: TSlot \\| null) => void`                  | Set active slot (typed)       |\n| `setReturnPath`    | `(path: string \\| null) => void`                | Set return path               |\n| `reset`            | `() => void`                                    | Reset portal state            |\n| `registerTarget`   | `(slotKey: TSlot, target: HTMLElement) => void` | Manually register target      |\n| `unregisterTarget` | `(slotKey: TSlot) => void`                      | Manually unregister target    |\n\n**Example:**\n\n```tsx\nfunction VideoControls() {\n  const { slotKey, setSlotKey, targets } = VideoPortal.usePortal()\n\n  return (\n    <div>\n      <p>Current slot: {slotKey || 'none'}</p>\n      <p>Available: {Array.from(targets.keys()).join(', ')}</p>\n      <button onClick={() => setSlotKey('main')}>Main</button>\n      <button onClick={() => setSlotKey('mini')}>Mini</button>\n      <button onClick={() => setSlotKey(null)}>Hide</button>\n    </div>\n  )\n}\n```\n\n## Usage Examples\n\n### Video Player (Main <-> Mini Player)\n\n```tsx\nimport { createPortal } from '@charley-kim/react-preserve-portal'\n\nconst VideoPortal = createPortal({\n  id: 'video',\n  slots: ['main', 'mini'],\n} as const)\n\nfunction VideoApp() {\n  const { slotKey, setSlotKey } = VideoPortal.usePortal()\n\n  return (\n    <>\n      <VideoPortal.Host node={<video src=\"video.mp4\" controls />} />\n\n      <main>\n        <VideoPortal.Slot slotKey=\"main\" />\n        <button onClick={() => setSlotKey('mini')}>Minimize</button>\n      </main>\n\n      <aside>\n        <VideoPortal.Slot slotKey=\"mini\" />\n        <button onClick={() => setSlotKey('main')}>Maximize</button>\n      </aside>\n    </>\n  )\n}\n```\n\n### Multiple Portal Instances\n\nCreate independent portals for different use cases:\n\n```tsx\nconst VideoPortal = createPortal({\n  id: 'video',\n  slots: ['main', 'mini'],\n} as const)\n\nconst ModalPortal = createPortal({\n  id: 'modal',\n  slots: ['center', 'fullscreen'],\n} as const)\n\nfunction App() {\n  return (\n    <>\n      {/* Video portal */}\n      <VideoPortal.Host node={<VideoElement />} />\n      <VideoPortal.Slot slotKey=\"main\" />\n      <VideoPortal.Slot slotKey=\"mini\" />\n\n      {/* Modal portal (completely independent) */}\n      <ModalPortal.Host node={<ModalContent />} />\n      <ModalPortal.Slot slotKey=\"center\" />\n    </>\n  )\n}\n```\n\n### Usage with Routing\n\nMaintain video state across page transitions:\n\n```tsx\nimport { BrowserRouter, Routes, Route } from 'react-router-dom'\nimport { createPortal } from '@charley-kim/react-preserve-portal'\n\nconst VideoPortal = createPortal({\n  id: 'video',\n  slots: ['main', 'mini'],\n} as const)\n\nfunction App() {\n  return (\n    <BrowserRouter>\n      <Routes>\n        <Route path=\"/\" element={<HomePage />} />\n        <Route path=\"/video/:id\" element={<VideoPage />} />\n      </Routes>\n\n      {/* Video state maintained across all pages */}\n      <VideoPortal.Host node={<VideoElement />} />\n      <MiniPlayer />\n    </BrowserRouter>\n  )\n}\n```\n\n## Requirements\n\n- React >= 18.0.0\n- React DOM >= 18.0.0\n\n## License\n\nMIT\n\n## Contributing\n\nIssues and PRs are welcome!\n\n---\n\n**Need more help?** If you'd like me to draft a more technical \"Deep Dive\" section or a specific guide for Next.js SSR, just let me know!\n","readmeFilename":"README.md","homepage":"https://github.com/kickbelldev/react-preserve-portal#readme","repository":{"type":"git","url":"git+https://github.com/kickbelldev/react-preserve-portal.git"},"author":{"name":"Charley Kim"},"bugs":{"url":"https://github.com/kickbelldev/react-preserve-portal/issues"},"license":"MIT"}