diff --git a/src/core/frames/frame_controller.js b/src/core/frames/frame_controller.js index 07764fc86..c876cfc72 100644 --- a/src/core/frames/frame_controller.js +++ b/src/core/frames/frame_controller.js @@ -250,10 +250,10 @@ export class FrameController { // View delegate - allowsImmediateRender({ element: newFrame }, options) { + allowsImmediateRender({ element: newFrame }, renderer, options) { const event = dispatch("turbo:before-frame-render", { target: this.element, - detail: { newFrame, ...options }, + detail: { newFrame, renderMethod: renderer.renderMethod, ...options }, cancelable: true }) const { @@ -261,14 +261,20 @@ export class FrameController { detail: { render } } = event - if (this.view.renderer && render) { - this.view.renderer.renderElement = render + if (renderer && render) { + renderer.renderElement = render } return !defaultPrevented } - viewRenderedSnapshot(_snapshot, _isPreview, _renderMethod) {} + viewRenderedSnapshot({ currentSnapshot: { element: frame }, renderMethod }) { + return dispatch("turbo:frame-render", { + detail: { renderMethod }, + target: frame, + cancelable: true + }) + } preloadOnLoadLinksForView(element) { session.preloadOnLoadLinksForView(element) @@ -303,9 +309,11 @@ export class FrameController { if (this.view.renderPromise) await this.view.renderPromise this.changeHistory() - await this.view.render(renderer) + await mergeIntoNext("turbo:frame-render", { on: this.element, detail: { fetchResponse } }, async () => { + await this.view.render(renderer) + }) + this.complete = true - session.frameRendered(fetchResponse, this.element) session.frameLoaded(this.element) await this.fetchResponseLoaded(fetchResponse) } else if (this.#willHandleFrameMissingFromResponse(fetchResponse)) { @@ -580,3 +588,15 @@ function activateElement(element, currentURL) { } } } + +async function mergeIntoNext(eventName, { on: target, detail }, callback) { + const listener = (event) => Object.assign(event.detail, detail) + const listenerOptions = { once: true, capture: true } + target.addEventListener(eventName, listener, listenerOptions) + + try { + await callback() + } finally { + target.removeEventListener(eventName, listener, listenerOptions) + } +} diff --git a/src/core/session.js b/src/core/session.js index cdb978348..c1c278d41 100644 --- a/src/core/session.js +++ b/src/core/session.js @@ -312,8 +312,8 @@ export class Session { } } - allowsImmediateRender({ element }, options) { - const event = this.notifyApplicationBeforeRender(element, options) + allowsImmediateRender({ element }, renderer, options) { + const event = this.notifyApplicationBeforeRender(element, renderer, options) const { defaultPrevented, detail: { render } @@ -326,9 +326,9 @@ export class Session { return !defaultPrevented } - viewRenderedSnapshot(_snapshot, _isPreview, renderMethod) { + viewRenderedSnapshot(renderer) { this.view.lastRenderedLocation = this.history.location - this.notifyApplicationAfterRender(renderMethod) + this.notifyApplicationAfterRender(renderer) } preloadOnLoadLinksForView(element) { @@ -345,10 +345,6 @@ export class Session { this.notifyApplicationAfterFrameLoad(frame) } - frameRendered(fetchResponse, frame) { - this.notifyApplicationAfterFrameRender(fetchResponse, frame) - } - // Application events applicationAllowsFollowingLinkToLocation(link, location, ev) { @@ -384,15 +380,15 @@ export class Session { return dispatch("turbo:before-cache") } - notifyApplicationBeforeRender(newBody, options) { + notifyApplicationBeforeRender(newBody, { renderMethod }, options) { return dispatch("turbo:before-render", { - detail: { newBody, ...options }, + detail: { newBody, renderMethod, ...options }, cancelable: true }) } - notifyApplicationAfterRender(renderMethod) { - return dispatch("turbo:render", { detail: { renderMethod } }) + notifyApplicationAfterRender({ isPreview, renderMethod }) { + return dispatch("turbo:render", { detail: { isPreview, renderMethod } }) } notifyApplicationAfterPageLoad(timing = {}) { @@ -414,14 +410,6 @@ export class Session { return dispatch("turbo:frame-load", { target: frame }) } - notifyApplicationAfterFrameRender(fetchResponse, frame) { - return dispatch("turbo:frame-render", { - detail: { fetchResponse }, - target: frame, - cancelable: true - }) - } - // Helpers submissionIsNavigatable(form, submitter) { diff --git a/src/core/view.js b/src/core/view.js index 7b562563d..b9a000e25 100644 --- a/src/core/view.js +++ b/src/core/view.js @@ -56,7 +56,7 @@ export class View { // Rendering async render(renderer) { - const { isPreview, shouldRender, willRender, newSnapshot: snapshot } = renderer + const { shouldRender, willRender, newSnapshot: snapshot } = renderer // A workaround to ignore tracked element mismatch reloads when performing // a promoted Visit from a frame navigation @@ -69,12 +69,12 @@ export class View { await this.prepareToRenderSnapshot(renderer) const renderInterception = new Promise((resolve) => (this.#resolveInterceptionPromise = resolve)) - const options = { resume: this.#resolveInterceptionPromise, render: this.renderer.renderElement, renderMethod: this.renderer.renderMethod } - const immediateRender = this.delegate.allowsImmediateRender(snapshot, options) + const options = { resume: this.#resolveInterceptionPromise, render: this.renderer.renderElement } + const immediateRender = this.delegate.allowsImmediateRender(snapshot, renderer, options) if (!immediateRender) await renderInterception await this.renderSnapshot(renderer) - this.delegate.viewRenderedSnapshot(snapshot, isPreview, this.renderer.renderMethod) + this.delegate.viewRenderedSnapshot(renderer) this.delegate.preloadOnLoadLinksForView(this.element) this.finishRenderingSnapshot(renderer) } finally { diff --git a/src/tests/functional/frame_tests.js b/src/tests/functional/frame_tests.js index f1925908b..2b2914442 100644 --- a/src/tests/functional/frame_tests.js +++ b/src/tests/functional/frame_tests.js @@ -449,7 +449,7 @@ test("'turbo:frame-render' is triggered after frame has finished rendering", asy await page.click("#frame-part") await nextEventNamed(page, "turbo:frame-render") // recursive - const { fetchResponse } = await nextEventNamed(page, "turbo:frame-render") + const { fetchResponse } = await nextEventNamed(page, "turbo:frame-render", { renderMethod: "replace" }) assert.include(fetchResponse.response.url, "/src/tests/fixtures/frames/part.html") }) diff --git a/src/tests/functional/rendering_tests.js b/src/tests/functional/rendering_tests.js index 66a9d1af0..93826bf59 100644 --- a/src/tests/functional/rendering_tests.js +++ b/src/tests/functional/rendering_tests.js @@ -32,7 +32,7 @@ test("triggers before-render and render events", async ({ page }) => { assert.equal(await page.textContent("h1"), "One") - await nextEventNamed(page, "turbo:render") + await nextEventNamed(page, "turbo:render", { renderMethod: "replace" }) assert.equal(await newBody, await page.evaluate(() => document.body.outerHTML)) })