From b8d1d8be35b95c2d1e12bcb2eb196905167aa385 Mon Sep 17 00:00:00 2001 From: omarluq Date: Thu, 15 Feb 2024 11:15:41 -0600 Subject: [PATCH] limit morph action data atrributes to morphStyle only and add morph lifecycle events --- src/core/streams/stream_actions.js | 84 ++++++++++++++++++-------- src/tests/unit/stream_element_tests.js | 27 +++++++-- 2 files changed, 82 insertions(+), 29 deletions(-) diff --git a/src/core/streams/stream_actions.js b/src/core/streams/stream_actions.js index a343e4333..8dcac5803 100644 --- a/src/core/streams/stream_actions.js +++ b/src/core/streams/stream_actions.js @@ -1,8 +1,7 @@ import { session } from "../" -import { morph } from "idiomorph" +import { Idiomorph } from "idiomorph/dist/idiomorph.esm.js" import { dispatch } from "../../util" - export const StreamActions = { after() { this.targetElements.forEach((e) => e.parentElement?.insertBefore(this.templateContent, e.nextSibling)) @@ -39,34 +38,28 @@ export const StreamActions = { refresh() { session.refresh(this.baseURI, this.requestId) - } + }, morph() { this.targetElements.forEach((targetElement) => { try { - const morphStyle = this.getAttribute("data-turbo-morph-style") || "outerHTML" - const ignoreActive = this.getAttribute("data-turbo-morph-ignore-active") || true - const ignoreActiveValue = this.getAttribute("data-turbo-morph-ignore-active-value") || true - const head = this.getAttribute("data-turbo-morph-head") || 'merge' - morph(targetElement, this.templateContent, { + const morphStyle = targetElement.getAttribute("data-turbo-morph-style") || "outerHTML" + Idiomorph.morph(targetElement, this.templateContent, { morphStyle: morphStyle, - ignoreActive: ignoreActive, - ignoreActiveValue: ignoreActiveValue, - head: head, + ignoreActiveValue: true, callbacks: { - beforeNodeAdded: (node) => { - return !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id)) - }, - afterNodeMorphed: (oldNode, newNode) => { - if (newNode instanceof HTMLElement) { - dispatch("turbo:morph-element", { - target: oldNode, - detail: { - newElement: newNode - } - }) - } - } + beforeNodeAdded, + beforeNodeMorphed, + beforeAttributeUpdated, + beforeNodeRemoved, + afterNodeMorphed + } + }) + + dispatch("turbo:morph", { + detail: { + currentElement: targetElement, + newElement: this.templateContent } }) } catch (error) { @@ -75,3 +68,46 @@ export const StreamActions = { }) } } + +const beforeNodeAdded = (node) => { + return !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id)) +} + +const beforeNodeMorphed = (target, newElement) => { + if (target instanceof HTMLElement && !target.hasAttribute("data-turbo-permanent")) { + const event = dispatch("turbo:before-morph-element", { + cancelable: true, + detail: { + target, + newElement + } + }) + return !event.defaultPrevented + } + return false +} + +const beforeAttributeUpdated = (attributeName, target, mutationType) => { + const event = dispatch("turbo:before-morph-attribute", { + cancelable: true, + target, + detail: { + attributeName, + mutationType + } + }) + return !event.defaultPrevented +} + +const beforeNodeRemoved = beforeNodeMorphed + +const afterNodeMorphed = (target, newElement) => { + if (newElement instanceof HTMLElement) { + dispatch("turbo:morph-element", { + target, + detail: { + newElement + } + }) + } +} diff --git a/src/tests/unit/stream_element_tests.js b/src/tests/unit/stream_element_tests.js index c084c8e9f..f717c02c6 100644 --- a/src/tests/unit/stream_element_tests.js +++ b/src/tests/unit/stream_element_tests.js @@ -198,14 +198,31 @@ test("test action=refresh discarded when matching request id", async () => { }) test("action=morph", async () => { - const templateElement = createTemplateElement(`
Hello Turbo Morphed
`) + const templateElement = createTemplateElement(`

Hello Turbo Morphed

`) const element = createStreamElement("morph", "hello", templateElement) - - assert.equal(subject.find("#hello")?.textContent, "Hello Turbo") + + assert.equal(subject.find("div#hello")?.textContent, "Hello Turbo") subject.append(element) await nextAnimationFrame() - assert.notOk(subject.find("#hello")?.textContent, "Hello Turbo") - assert.equal(subject.find("#hello")?.textContent, "Hello Turbo Morphed") + assert.notOk(subject.find("div#hello")) + assert.equal(subject.find("h1#hello")?.textContent, "Hello Turbo Morphed") }) + +test("action=morph with data-turbo-morph-style='innerHTML'", async () => { + const templateElement = createTemplateElement(`

Hello Turbo Morphed

`) + const element = createStreamElement("morph", "hello", templateElement) + const target = subject.find("div#hello") + assert.equal(target?.textContent, "Hello Turbo") + target.setAttribute("data-turbo-morph-style", "innerHTML") + + subject.append(element) + + await nextAnimationFrame() + + assert.ok(subject.find("div#hello")) + assert.ok(subject.find("div#hello > h1#hello-child-element")) + assert.equal(subject.find("div#hello > h1#hello-child-element").textContent, "Hello Turbo Morphed") +}) +