feat: introduce <Teleport> component with zero-reset re-parenting#14
feat: introduce <Teleport> component with zero-reset re-parenting#14ahmetbilgay wants to merge 5 commits intodashersw:mainfrom
Conversation
Add complete teleport functionality to GEA framework enabling content rendering at different DOM locations while preserving component state and event handling. Key features: - JSX <Teleport to-selector="#target"> component syntax - Robust event delegation for teleported elements - Conditional teleporting via disabled prop - Automatic cleanup on component disposal - Zero breaking changes to existing APIs Technical implementation: - Add teleport handling in component render lifecycle - Fix event registration timing in ComponentManager - Implement dual element marking for reliable event delegation - JSX transform integration for clean syntax - TypeScript definitions and proper exports Bundle impact: +0.2kB gzipped Test coverage: 9/9 teleport tests passing Demo: Comprehensive showcase with modal/sidebar/notifications Closes dashersw#4
📝 WalkthroughWalkthroughThis PR implements a Changes
Sequence DiagramsequenceDiagram
participant Component as GEA Component
participant Handler as Teleport Handler
participant DOM as DOM Tree
participant Target as Target Element
participant Events as Event System
Component->>Handler: render() with teleport directives
Handler->>DOM: discover teleport elements (data-gea-teleport)
Handler->>Handler: resolve target selector (data-gea-teleport-to)
Handler->>Target: insert origin placeholder
Handler->>DOM: move child nodes to target
Handler->>DOM: annotate moved nodes with teleport metadata
rect rgba(200, 150, 100, 0.5)
Note over Events,Target: State updates trigger rerender
Component->>Handler: trigger rerender via observable
Handler->>DOM: preserve teleported nodes temporarily
Handler->>Handler: cleanup previous teleport state
Handler->>DOM: restore preserved nodes to target
Handler->>DOM: restore focus if in teleported subtree
end
rect rgba(100, 150, 200, 0.5)
Note over Events,DOM: Event delegation
Events->>DOM: click on teleported element
DOM->>Component: event bubbles through handlers
Component->>Component: execute event listener
end
Component->>Handler: dispose()
Handler->>DOM: move nodes back to origin placeholder
Handler->>DOM: remove origin placeholder & metadata
Handler->>Target: clean up target element
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (3)
examples/teleport-demo/src/main.ts (1)
1-1: Unused import:Componentis imported but never used.The
Componentimport from@geajs/coreis not referenced in this file. Consider removing it to keep the code clean.♻️ Suggested fix
-import { Component } from '@geajs/core' import TeleportDemo from './TeleportDemo.tsx'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/teleport-demo/src/main.ts` at line 1, Remove the unused import by deleting the import specifier "Component" from the top-level import in main.ts (the line importing from '@geajs/core'); ensure no other code references the symbol Component and keep the import statement only if other exported names from '@geajs/core' are needed.packages/gea/src/lib/base/component.tsx (1)
427-438: Stored children array includes nodes that weren't moved.Line 427 captures all childNodes, but line 430 only moves non-comment nodes. The stored
__geaTeleportChildrenarray includes comment nodes that were never moved to the target. In__cleanupSingleTeleport(line 607), the code checksif (child.parentNode === target)which handles this correctly, but storing unmoved nodes adds unnecessary iteration.♻️ Consider storing only moved children
// Move all child nodes to the target element - const children = Array.from(teleportEl.childNodes) - - children.forEach((child) => { + const movedChildren: Node[] = [] + Array.from(teleportEl.childNodes).forEach((child) => { if (child && child.nodeType !== Node.COMMENT_NODE) { targetElement.appendChild(child) + movedChildren.push(child) } }) // Store teleport info for cleanup ;(teleportEl as any).__geaTeleportTarget = targetElement ;(teleportEl as any).__geaTeleportPlaceholder = placeholder - ;(teleportEl as any).__geaTeleportChildren = children + ;(teleportEl as any).__geaTeleportChildren = movedChildren🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/gea/src/lib/base/component.tsx` around lines 427 - 438, The stored __geaTeleportChildren currently captures all teleportEl.childNodes (including comment nodes) but only non-comment nodes are actually moved; change the capture to only include moved nodes by filtering out comment nodes when building the array (use teleportEl.childNodes -> Array.from(...).filter(child => child.nodeType !== Node.COMMENT_NODE)) so __geaTeleportChildren only contains nodes appended to __geaTeleportTarget; this keeps __cleanupSingleTeleport logic (which checks child.parentNode === target) efficient and consistent with the moved set.packages/gea/tests/teleport.test.ts (1)
113-144: Console mock should use try/finally to ensure restoration on test failure.If an assertion fails before line 143,
console.warnremains mocked, potentially affecting subsequent tests.♻️ Suggested improvement
it('should handle missing target gracefully', () => { document.body.innerHTML = '<div id="app"></div>' // Mock console.warn to capture warning const originalWarn = console.warn let warningMessage = '' console.warn = (msg: string) => { warningMessage = msg } - class TestComponent extends Component { - template() { - return ` - <div> - <div data-gea-teleport="true" data-gea-teleport-to="#missing-target" style="display: none;"> - <div class="modal">Content</div> - </div> - </div> - ` - } - } - - const component = new TestComponent() - const appRoot = document.getElementById('app')! - component.render(appRoot) - - // Should emit warning and keep content in place - assert(warningMessage.includes('Target element not found: `#missing-target`')) - - // Content should remain in original location - const teleportDiv = appRoot.querySelector('[data-gea-teleport="true"]')! - assert.strictEqual(teleportDiv.querySelector('.modal')?.textContent, 'Content') - - // Restore console.warn - console.warn = originalWarn + try { + class TestComponent extends Component { + template() { + return ` + <div> + <div data-gea-teleport="true" data-gea-teleport-to="#missing-target" style="display: none;"> + <div class="modal">Content</div> + </div> + </div> + ` + } + } + + const component = new TestComponent() + const appRoot = document.getElementById('app')! + component.render(appRoot) + + // Should emit warning and keep content in place + assert(warningMessage.includes('Target element not found: `#missing-target`')) + + // Content should remain in original location + const teleportDiv = appRoot.querySelector('[data-gea-teleport="true"]')! + assert.strictEqual(teleportDiv.querySelector('.modal')?.textContent, 'Content') + } finally { + // Restore console.warn + console.warn = originalWarn + } })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/gea/tests/teleport.test.ts` around lines 113 - 144, Wrap the console.warn mocking and assertions in a try/finally so originalWarn is always restored: capture originalWarn, assign the mock to console.warn and run the TestComponent creation, component.render(...) and assertions inside a try block, then restore console.warn = originalWarn in the finally block; reference the existing variables originalWarn, warningMessage, console.warn, TestComponent and the call to component.render to locate the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/api-reference.md`:
- Line 615: The phrase "Target elements can be anywhere in the document,
including outside the React root" incorrectly references React; update that
sentence in docs/api-reference.md (search for that exact sentence) to use the
correct framework term such as "outside the app root" or "outside the Gea root"
(e.g., "Target elements can be anywhere in the document, including outside the
Gea root"). Ensure any nearby occurrences of "React root" are similarly
replaced.
In `@packages/gea/CHANGELOG.md`:
- Around line 3-6: The changelog shows a "Minor Changes" header but the version
was bumped only as a patch (1.0.8); revert direct changelog/version edits and
create a proper changeset instead: run npx changeset, choose a "minor" bump and
include the new feature (e.g., Teleport) so a .changeset/*.md is created, which
will update package versions (ensure `@geajs/core` and `@geajs/vite-plugin` are
included) and let the release tooling produce 1.1.0; remove or replace the
manual CHANGELOG.md edit so the release is driven by the generated changeset per
.changeset/config.json.
In `@packages/gea/src/lib/base/component.tsx`:
- Around line 422-424: The code creates a comment node named placeholder and
calls teleportEl.parentNode?.insertBefore(placeholder, teleportEl) but doesn't
handle the case where parentNode is null; update the logic around the
placeholder creation (the placeholder variable and the teleportEl insertion
block) to check the result of teleportEl.parentNode (or the return value of
insertBefore) and if it's null/undefined, abort the teleport setup—either return
early or mark the teleport as inactive—so you don't proceed to move children or
store metadata that assumes placeholder exists and will be used later in
cleanup; ensure the later cleanup code (which expects placeholder) is guarded or
only runs when the placeholder insertion succeeded.
- Around line 622-632: Cleanup currently removes the __geaOriginalComponent
property from teleported children but leaves the data-gea-teleport-component
attribute on the DOM nodes; update the cleanup loop in the component's teardown
(the code manipulating __teleportedElements and deleting __geaOriginalComponent)
to also remove the attribute by calling
removeAttribute('data-gea-teleport-component') on each child HTMLElement (use
the same child cast used for __teleportedElements), ensuring the attribute is
cleared whenever the property is deleted and the element is spliced out.
---
Nitpick comments:
In `@examples/teleport-demo/src/main.ts`:
- Line 1: Remove the unused import by deleting the import specifier "Component"
from the top-level import in main.ts (the line importing from '@geajs/core');
ensure no other code references the symbol Component and keep the import
statement only if other exported names from '@geajs/core' are needed.
In `@packages/gea/src/lib/base/component.tsx`:
- Around line 427-438: The stored __geaTeleportChildren currently captures all
teleportEl.childNodes (including comment nodes) but only non-comment nodes are
actually moved; change the capture to only include moved nodes by filtering out
comment nodes when building the array (use teleportEl.childNodes ->
Array.from(...).filter(child => child.nodeType !== Node.COMMENT_NODE)) so
__geaTeleportChildren only contains nodes appended to __geaTeleportTarget; this
keeps __cleanupSingleTeleport logic (which checks child.parentNode === target)
efficient and consistent with the moved set.
In `@packages/gea/tests/teleport.test.ts`:
- Around line 113-144: Wrap the console.warn mocking and assertions in a
try/finally so originalWarn is always restored: capture originalWarn, assign the
mock to console.warn and run the TestComponent creation, component.render(...)
and assertions inside a try block, then restore console.warn = originalWarn in
the finally block; reference the existing variables originalWarn,
warningMessage, console.warn, TestComponent and the call to component.render to
locate the change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9d974f89-ea9a-41e2-89e6-fddf1934b49e
📒 Files selected for processing (19)
.cursor/skills/gea-framework/reference.mddocs/api-reference.mdexamples/teleport-demo/index.htmlexamples/teleport-demo/package.jsonexamples/teleport-demo/src/TeleportDemo.tsxexamples/teleport-demo/src/main.tsexamples/teleport-demo/tsconfig.jsonexamples/teleport-demo/vite.config.tspackage.jsonpackages/gea/CHANGELOG.mdpackages/gea/README.mdpackages/gea/src/index.tspackages/gea/src/jsx.tspackages/gea/src/lib/base/component-manager.tspackages/gea/src/lib/base/component.tsxpackages/gea/src/lib/types.tspackages/gea/tests/component-manager-coverage.test.tspackages/gea/tests/teleport.test.tspackages/vite-plugin-gea/src/transform-jsx.ts
packages/gea/CHANGELOG.md
Outdated
| ## 1.0.8 | ||
|
|
||
| ### Minor Changes | ||
|
|
There was a problem hiding this comment.
Version bump inconsistency: "Minor Changes" labeled but patch version used.
The changelog labels this as "Minor Changes" (which typically corresponds to a minor semver bump), but the version incremented from 1.0.7 to 1.0.8 (a patch bump). A new feature like <Teleport> typically warrants a minor version bump (e.g., 1.1.0) according to semver conventions.
Based on learnings, version changes should be declared via npx changeset to create a .md file in .changeset/ with the appropriate bump type. This ensures linked packages @geajs/core and @geajs/vite-plugin receive the same minor/major bump level as configured in .changeset/config.json.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/gea/CHANGELOG.md` around lines 3 - 6, The changelog shows a "Minor
Changes" header but the version was bumped only as a patch (1.0.8); revert
direct changelog/version edits and create a proper changeset instead: run npx
changeset, choose a "minor" bump and include the new feature (e.g., Teleport) so
a .changeset/*.md is created, which will update package versions (ensure
`@geajs/core` and `@geajs/vite-plugin` are included) and let the release tooling
produce 1.1.0; remove or replace the manual CHANGELOG.md edit so the release is
driven by the generated changeset per .changeset/config.json.
…renting and robust event delegation support.
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
packages/gea/tests/teleport.test.ts (1)
178-221: Strengthen the zero-reset regression here.This only checks updated text, so it still passes if the teleported node is remounted and loses focus/value/media state. Please add an input/video-style assertion that the same DOM node survives rerender or target changes.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/gea/tests/teleport.test.ts` around lines 178 - 221, Add an assertion that the teleported DOM node itself is preserved across the re-render rather than only its text: inside TestComponent.template include a focusable/input or video element (e.g., <input id="tele-input" value="initial"> or <video id="tele-video" currentTime="1">) inside the teleported .modal, capture a reference to that element from modalRoot right after initial render (const before = modalRoot.querySelector('#tele-input')!), then after updating state and awaiting the async re-render capture the element again (const after = modalRoot.querySelector('#tele-input')!) and assert the same node instance is preserved (before === after or before.isSameNode(after)) and that its mutable state (value, focused state or currentTime) remains unchanged; keep references to TestComponent, template, data-gea-teleport and modal-root to locate where to add these checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/gea/src/lib/base/component.tsx`:
- Around line 164-165: mountCompiledChildComponents_() and __geaSwapChild()
replay the render lifecycle but never invoke __handleTeleportElements(), so
swapped-in and compiled children don't run their teleport setup; update both
methods to call this.__handleTeleportElements() after they reattach or swap
child DOM (and likewise add the same call in the other replay entry point
referenced around the __geaSwapChild() area) so each replay path performs the
teleport handling immediately after the child subtree is mounted.
- Line 306: The call to __cleanupTeleports() during the rerender path causes
teleported DOM (inputs/videos/iframes) to be moved back into the old tree and
then destroyed/recreated by template(), causing remounts; stop doing that by
deferring teleport cleanup until unmount (or after the new tree has been
reconciled) so teleported nodes are reparented rather than recreated.
Concretely: remove or guard the invocation of __cleanupTeleports() in the
rerender codepath that calls template(), and only call __cleanupTeleports() from
the unmount flow (or after the new DOM is inserted and placeholders reconciled);
adjust __cleanupTeleports() semantics if needed to perform true teardown-only
behavior and ensure __geaRequestRender()-triggered rerenders do not invoke it.
- Around line 404-412: The code calls document.querySelector(toSelector) which
throws for malformed selectors; wrap that call in a try/catch around the lookup
(the block that assigns targetElement from document.querySelector(toSelector))
and on catch call console.warn with the broken selector and the thrown error,
then skip teleporting (return) so the content remains in place; keep the
existing warning/return for null targetElement and reference the existing
identifiers teleportEl, toSelector and targetElement when adding the guard.
---
Nitpick comments:
In `@packages/gea/tests/teleport.test.ts`:
- Around line 178-221: Add an assertion that the teleported DOM node itself is
preserved across the re-render rather than only its text: inside
TestComponent.template include a focusable/input or video element (e.g., <input
id="tele-input" value="initial"> or <video id="tele-video" currentTime="1">)
inside the teleported .modal, capture a reference to that element from modalRoot
right after initial render (const before =
modalRoot.querySelector('#tele-input')!), then after updating state and awaiting
the async re-render capture the element again (const after =
modalRoot.querySelector('#tele-input')!) and assert the same node instance is
preserved (before === after or before.isSameNode(after)) and that its mutable
state (value, focused state or currentTime) remains unchanged; keep references
to TestComponent, template, data-gea-teleport and modal-root to locate where to
add these checks.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: bf1ee4dd-410d-47b2-92dc-2a75497b146c
📒 Files selected for processing (5)
.changeset/hip-papayas-rest.mddocs/api-reference.mdexamples/teleport-demo/src/main.tspackages/gea/src/lib/base/component.tsxpackages/gea/tests/teleport.test.ts
✅ Files skipped from review due to trivial changes (3)
- .changeset/hip-papayas-rest.md
- examples/teleport-demo/src/main.ts
- docs/api-reference.md
Fixes teleported elements losing state and focus during component updates. Adds proper lifecycle integration and error handling for production use.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/gea/src/lib/base/component-manager.ts`:
- Around line 243-252: The teleport lookup currently returns after finding the
first teleported owner, collapsing parentComps to a single id; change it so that
when you detect (currentNode as any).__geaOriginalComponent and fetch
originalComponent from this.componentRegistry, you push the originalComponent id
onto parentComps and then walk originalComponent.parentComponent repeatedly to
append its ancestor component ids (matching the normal DOM-walk order like
[leaf, parent, ...]) before returning; make the same fix for the duplicate block
at the later occurrence (lines ~256-263) so teleported elements preserve the
full ancestor component chain.
In `@packages/gea/src/lib/base/component.tsx`:
- Around line 506-517: The teleport wrapper currently stays hidden because early
returns on invalid selector, missing target, or disabled teleport never remove
the wrapper's inline style; update the code in the teleport handling (the query
block using toSelector/targetElement and the later disabled check around lines
handling the teleport wrapper) to clear the wrapper's display:none before each
return — e.g., find the teleport wrapper element (the wrapper created for
teleported children) and remove or reset its style.display (or remove the inline
style attribute) just prior to each early return in the try/catch for
document.querySelector(toSelector), the if (!targetElement) branch, and the
disabled branch so the fallback children render in place.
- Around line 328-350: The current preservation scans all descendants for
[data-gea-teleport-component="${this.id}"] and skips nodes without ids, which
detaches nested teleported children and lets __cleanupTeleports() remove
bookkeeping before the restored swap; instead, change the preservation in the
component (where preservedTeleports Map is created) to locate and detach only
the wrapper's top-level teleported children (i.e., direct children of the
component root that carry the data-gea-teleport-component attr tied to this.id),
do not require element.id (assign a stable temporary key if needed), record
their original wrapper bookkeeping so it is not stripped, and postpone or adjust
__cleanupTeleports() so it does not clear teleport metadata for entries present
in preservedTeleports until after restore/replaceChild completes; reference
preservedTeleports, __cleanupTeleports, and the data-gea-teleport-component
attribute on this.id when implementing these changes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 517c2637-55b4-447e-b076-a611ceb203a4
📒 Files selected for processing (3)
packages/gea/src/index.tspackages/gea/src/lib/base/component-manager.tspackages/gea/src/lib/base/component.tsx
✅ Files skipped from review due to trivial changes (1)
- packages/gea/src/index.ts
| // Check if this is a teleported element or descendant of teleported element | ||
| let currentNode: HTMLElement | null = child | ||
| while (currentNode) { | ||
| const originalComponentId = (currentNode as any).__geaOriginalComponent | ||
| if (originalComponentId) { | ||
| const originalComponent = this.componentRegistry[originalComponentId] | ||
| if (originalComponent) { | ||
| parentComps.push(originalComponent) | ||
| child.parentComps = originalComponentId | ||
| return parentComps |
There was a problem hiding this comment.
Teleport ownership lookup should keep the ancestor component chain.
This returns on the first teleported owner hit, so parentComps collapses to a single component id. The normal DOM walk below collects [leaf, parent, ...], so ancestor component handlers stop seeing events from teleported descendants. Build the cached list from the owning component's parentComponent chain instead of returning only the leaf owner.
Also applies to: 256-263
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/gea/src/lib/base/component-manager.ts` around lines 243 - 252, The
teleport lookup currently returns after finding the first teleported owner,
collapsing parentComps to a single id; change it so that when you detect
(currentNode as any).__geaOriginalComponent and fetch originalComponent from
this.componentRegistry, you push the originalComponent id onto parentComps and
then walk originalComponent.parentComponent repeatedly to append its ancestor
component ids (matching the normal DOM-walk order like [leaf, parent, ...])
before returning; make the same fix for the duplicate block at the later
occurrence (lines ~256-263) so teleported elements preserve the full ancestor
component chain.
| // Preserve teleported elements before rerender by temporarily detaching them | ||
| const preservedTeleports = new Map< | ||
| string, | ||
| { element: HTMLElement; target: Element; placeholder: Comment; wasFocused: boolean } | ||
| >() | ||
|
|
||
| // Find and temporarily detach all teleported elements for this component | ||
| document.querySelectorAll(`[data-gea-teleport-component="${this.id}"]`).forEach((element: HTMLElement) => { | ||
| const target = element.parentElement | ||
| if (target && element.id) { | ||
| // Check if this element or any of its children has focus | ||
| const wasFocused = element === document.activeElement || element.contains(document.activeElement) | ||
|
|
||
| // Create a placeholder to mark where this element should be restored | ||
| const placeholder = document.createComment(`preserved-${element.id}`) | ||
| target.insertBefore(placeholder, element) | ||
| target.removeChild(element) | ||
| preservedTeleports.set(element.id, { element, target, placeholder, wasFocused }) | ||
| } | ||
| }) | ||
|
|
||
| // Clean up any remaining teleport state | ||
| this.__cleanupTeleports() |
There was a problem hiding this comment.
The rerender preservation path still loses the live teleported subtree.
This snapshots every [data-gea-teleport-component="${this.id}"] match even though the marker is applied to every descendant, and it skips any node without an id. Then __cleanupTeleports() strips the teleport bookkeeping from those preserved nodes before replaceChild(element, newElement) swaps them back, so the wrapper still tracks the discarded node. Nested descendants can be detached out of their parent, anonymous teleported nodes are still recreated, and later cleanup/dispose no longer follows the live subtree. Preserve the wrapper's top-level teleported children instead of rescanning marker attributes.
Also applies to: 442-449, 551-565
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/gea/src/lib/base/component.tsx` around lines 328 - 350, The current
preservation scans all descendants for
[data-gea-teleport-component="${this.id}"] and skips nodes without ids, which
detaches nested teleported children and lets __cleanupTeleports() remove
bookkeeping before the restored swap; instead, change the preservation in the
component (where preservedTeleports Map is created) to locate and detach only
the wrapper's top-level teleported children (i.e., direct children of the
component root that carry the data-gea-teleport-component attr tied to this.id),
do not require element.id (assign a stable temporary key if needed), record
their original wrapper bookkeeping so it is not stripped, and postpone or adjust
__cleanupTeleports() so it does not clear teleport metadata for entries present
in preservedTeleports until after restore/replaceChild completes; reference
preservedTeleports, __cleanupTeleports, and the data-gea-teleport-component
attribute on this.id when implementing these changes.
| // Safe selector query with error handling | ||
| let targetElement: Element | null = null | ||
| try { | ||
| targetElement = document.querySelector(toSelector) | ||
| } catch (error) { | ||
| console.warn(`[Teleport] Invalid target selector: ${toSelector}`, error) | ||
| return | ||
| } | ||
|
|
||
| if (!targetElement) { | ||
| console.warn(`[Teleport] Target element not found: ${toSelector}`) | ||
| return |
There was a problem hiding this comment.
Disabled or unresolved teleports currently render nothing.
packages/vite-plugin-gea/src/transform-jsx.ts:1360-1428 always emits the teleport wrapper with style="display: none;". Every early return here (catch, missing target, or disabled) leaves the children inside that hidden wrapper, so the fallback is invisible instead of rendering in place. At minimum, clear that inline style before returning when teleporting is skipped.
Also applies to: 523-525
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/gea/src/lib/base/component.tsx` around lines 506 - 517, The teleport
wrapper currently stays hidden because early returns on invalid selector,
missing target, or disabled teleport never remove the wrapper's inline style;
update the code in the teleport handling (the query block using
toSelector/targetElement and the later disabled check around lines handling the
teleport wrapper) to clear the wrapper's display:none before each return — e.g.,
find the teleport wrapper element (the wrapper created for teleported children)
and remove or reset its style.display (or remove the inline style attribute)
just prior to each early return in the try/catch for
document.querySelector(toSelector), the if (!targetElement) branch, and the
disabled branch so the fallback children render in place.
Add complete teleport functionality to GEA framework enabling content rendering at different DOM locations while preserving component state and event handling.
Key features:
Technical implementation:
Bundle impact: +0.2kB gzipped
Test coverage: 9/9 teleport tests passing
Demo: Comprehensive showcase with modal/sidebar/notifications
Closes #4
Summary by CodeRabbit
Release Notes
New Features
Teleportcomponent enabling UI rendering to alternate DOM locations via CSS selectors while preserving component state and event handling.Documentation
Teleportwith configuration options.Tests