diff --git a/packages/@ember/-internals/glimmer/lib/base-renderer.ts b/packages/@ember/-internals/glimmer/lib/base-renderer.ts index ab9c459b72c..9b5ad2d12a7 100644 --- a/packages/@ember/-internals/glimmer/lib/base-renderer.ts +++ b/packages/@ember/-internals/glimmer/lib/base-renderer.ts @@ -22,11 +22,12 @@ import type { import { artifacts } from '@glimmer/program/lib/helpers'; import { RuntimeOpImpl } from '@glimmer/program/lib/opcode'; +import { liveParent } from '@glimmer/runtime/lib/bounds'; import { clientBuilder } from '@glimmer/runtime/lib/vm/element-builder'; import { inTransaction, runtimeOptions } from '@glimmer/runtime/lib/environment'; import { renderComponent as glimmerRenderComponent } from '@glimmer/runtime/lib/render'; import { CURRENT_TAG, validateTag, valueForTag } from '@glimmer/validator/lib/validators'; -import type { SimpleDocument, SimpleElement } from '@simple-dom/interface'; +import type { SimpleDocument, SimpleDocumentFragment, SimpleElement } from '@simple-dom/interface'; import { hasDOM } from '../../browser-environment'; import { EmberEnvironmentDelegate } from './environment'; import ResolverImpl from './resolver'; @@ -402,7 +403,7 @@ export class RendererState { } } -type IntoTarget = Cursor | Element | SimpleElement; +type IntoTarget = Cursor | Element | SimpleElement | DocumentFragment | SimpleDocumentFragment; /** * The returned object from `renderComponent` @@ -462,18 +463,12 @@ export function renderComponent( env, into, args, - appendIntoTarget = false, }: { /** * The element to render the component in to. */ into: IntoTarget; - /** - * Appends `into` without clearing the target first. Mimics the `appendTo` behavior for classic components. - */ - appendIntoTarget?: boolean; - /** * Optional owner. Defaults to `{}`, can be any object, but will need to implement the [Owner](https://api.emberjs.com/ember/release/classes/Owner) API for components within this render tree to access services. */ @@ -540,7 +535,7 @@ export function renderComponent( * Because destruction is async, it won't be safe to * do this again, and we'll have to rely on the above destroy. */ - if (!(appendIntoTarget || existing) && into instanceof Element) { + if (!existing && typeof Element !== 'undefined' && into instanceof Element) { into.innerHTML = ''; } @@ -558,10 +553,8 @@ export function renderComponent( */ let renderTarget: IntoTarget = into; if (existing?.glimmerResult) { - let parentElement = - into instanceof Element ? (into as unknown as SimpleElement) : (into as Cursor).element; let firstNode = existing.glimmerResult.firstNode(); - renderTarget = { element: parentElement, nextSibling: firstNode }; + renderTarget = { element: liveParent(existing.glimmerResult), nextSibling: firstNode }; } let innerResult = renderer.render(component, { into: renderTarget, args }).result; diff --git a/packages/@ember/-internals/glimmer/lib/renderer.ts b/packages/@ember/-internals/glimmer/lib/renderer.ts index 88166168d10..af28b6bbbb5 100644 --- a/packages/@ember/-internals/glimmer/lib/renderer.ts +++ b/packages/@ember/-internals/glimmer/lib/renderer.ts @@ -30,7 +30,12 @@ import { curry } from '@glimmer/runtime/lib/curried-value'; import { inTransaction } from '@glimmer/runtime/lib/environment'; import { renderMain } from '@glimmer/runtime/lib/render'; import { unwrapTemplate } from './component-managers/unwrap-template'; -import type { SimpleDocument, SimpleElement, SimpleNode } from '@simple-dom/interface'; +import type { + SimpleDocument, + SimpleDocumentFragment, + SimpleElement, + SimpleNode, +} from '@simple-dom/interface'; import type Component from './component'; import type ClassicComponent from './component'; import { BOUNDS } from './component-managers/curly'; @@ -309,7 +314,7 @@ export class Renderer extends BaseRenderer { } getBounds(component: View): { - parentElement: SimpleElement; + parentElement: SimpleElement | SimpleDocumentFragment; firstNode: SimpleNode; lastNode: SimpleNode; } { @@ -317,7 +322,7 @@ export class Renderer extends BaseRenderer { assert('object passed to getBounds must have the BOUNDS symbol as a property', bounds); - let parentElement = bounds.parentElement(); + let parentElement = bounds.parentNode(); let firstNode = bounds.firstNode(); let lastNode = bounds.lastNode(); diff --git a/packages/@ember/application/instance.ts b/packages/@ember/application/instance.ts index f1b57c7fad8..fd16998c308 100644 --- a/packages/@ember/application/instance.ts +++ b/packages/@ember/application/instance.ts @@ -19,7 +19,7 @@ import { assert } from '@ember/debug'; import Router from '@ember/routing/router'; import EventDispatcher from '@ember/-internals/views/lib/system/event_dispatcher'; import type Registry from '@ember/-internals/container/lib/registry'; -import type { SimpleElement } from '@simple-dom/interface'; +import type { SimpleDocumentFragment, SimpleElement } from '@simple-dom/interface'; /** The `ApplicationInstance` encapsulates all of the stateful aspects of a @@ -142,9 +142,23 @@ class ApplicationInstance extends EngineInstance { return this._router; } + #rootFragment?: DocumentFragment | SimpleDocumentFragment; + renderRootComponent(component: object) { setRenderer(this, this.lookup('renderer:-dom') as BaseRenderer); - renderComponent(component, { into: this.rootElement!, owner: this, appendIntoTarget: true }); + let env = this.lookup('-environment:main') as { _renderMode?: string }; + + if (env._renderMode === 'rehydrate') { + // Cursor form avoids clearing rootElement + renderComponent(component, { + into: { element: this.rootElement as SimpleElement, nextSibling: null }, + owner: this, + }); + } else { + this.#rootFragment ??= this.rootElement!.ownerDocument.createDocumentFragment(); + renderComponent(component, { into: this.#rootFragment, owner: this }); + (this.rootElement as Element).appendChild(this.#rootFragment as DocumentFragment); + } } /** diff --git a/packages/@ember/helper/index.ts b/packages/@ember/helper/index.ts index 945dcd1c469..946c20def65 100644 --- a/packages/@ember/helper/index.ts +++ b/packages/@ember/helper/index.ts @@ -303,7 +303,7 @@ export const setHelperManager = glimmerSetHelperManager; return getValue(this.plusOne); } } - + @@ -852,7 +852,7 @@ export interface NotHelper extends Opaque<'helper:not'> {} ```gjs {data-filename="app/templates/application.gjs"} import LabeledTextField from '../components/labeled-textfield'; - +