Skip to content

Commit

Permalink
Merge branch 'hotwired:main' into ishtml_refresh
Browse files Browse the repository at this point in the history
  • Loading branch information
edwinv authored Aug 7, 2024
2 parents 09b1e36 + f88bfe4 commit fe00499
Show file tree
Hide file tree
Showing 41 changed files with 516 additions and 427 deletions.
3 changes: 2 additions & 1 deletion .prettierrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"singleQuote": false,
"printWidth": 120,
"semi": false
"semi": false,
"trailingComma" : "none"
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hotwired/turbo",
"version": "8.0.4",
"version": "8.0.5",
"description": "The speed of a single-page web application without having to write any JavaScript",
"module": "dist/turbo.es2017-esm.js",
"main": "dist/turbo.es2017-umd.js",
Expand Down
118 changes: 0 additions & 118 deletions src/core/drive/morph_renderer.js

This file was deleted.

48 changes: 48 additions & 0 deletions src/core/drive/morphing_page_renderer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { FrameElement } from "../../elements/frame_element"
import { MorphingFrameRenderer } from "../frames/morphing_frame_renderer"
import { PageRenderer } from "./page_renderer"
import { dispatch } from "../../util"
import { morphElements } from "../morphing"

export class MorphingPageRenderer extends PageRenderer {
static renderElement(currentElement, newElement) {
morphElements(currentElement, newElement, {
callbacks: {
beforeNodeMorphed: element => !canRefreshFrame(element)
}
})

for (const frame of currentElement.querySelectorAll("turbo-frame")) {
if (canRefreshFrame(frame)) refreshFrame(frame)
}

dispatch("turbo:morph", { detail: { currentElement, newElement } })
}

async preservingPermanentElements(callback) {
return await callback()
}

get renderMethod() {
return "morph"
}

get shouldAutofocus() {
return false
}
}

function canRefreshFrame(frame) {
return frame instanceof FrameElement &&
frame.src &&
frame.refresh === "morph" &&
!frame.closest("[data-turbo-permanent]")
}

function refreshFrame(frame) {
frame.addEventListener("turbo:before-frame-render", ({ detail }) => {
detail.render = MorphingFrameRenderer.renderElement
}, { once: true })

frame.reload()
}
1 change: 1 addition & 0 deletions src/core/drive/navigator.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export class Navigator {

visitCompleted(visit) {
this.delegate.visitCompleted(visit)
delete this.currentVisit
}

locationWithActionIsSamePage(location, action) {
Expand Down
6 changes: 3 additions & 3 deletions src/core/drive/page_view.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { nextEventLoopTick } from "../../util"
import { View } from "../view"
import { ErrorRenderer } from "./error_renderer"
import { MorphRenderer } from "./morph_renderer"
import { MorphingPageRenderer } from "./morphing_page_renderer"
import { PageRenderer } from "./page_renderer"
import { PageSnapshot } from "./page_snapshot"
import { SnapshotCache } from "./snapshot_cache"
Expand All @@ -17,9 +17,9 @@ export class PageView extends View {

renderPage(snapshot, isPreview = false, willRender = true, visit) {
const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.shouldMorphPage
const rendererClass = shouldMorphPage ? MorphRenderer : PageRenderer
const rendererClass = shouldMorphPage ? MorphingPageRenderer : PageRenderer

const renderer = new rendererClass(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender)
const renderer = new rendererClass(this.snapshot, snapshot, rendererClass.renderElement, isPreview, willRender)

if (!renderer.shouldRender) {
this.forceReloaded = true
Expand Down
14 changes: 9 additions & 5 deletions src/core/frames/link_interceptor.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { findLinkFromClickTarget } from "../../util"

export class LinkInterceptor {
constructor(delegate, element) {
this.delegate = delegate
Expand All @@ -17,15 +19,15 @@ export class LinkInterceptor {
}

clickBubbled = (event) => {
if (this.respondsToEventTarget(event.target)) {
if (this.clickEventIsSignificant(event)) {
this.clickEvent = event
} else {
delete this.clickEvent
}
}

linkClicked = (event) => {
if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {
if (this.clickEvent && this.clickEventIsSignificant(event)) {
if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {
this.clickEvent.preventDefault()
event.preventDefault()
Expand All @@ -39,8 +41,10 @@ export class LinkInterceptor {
delete this.clickEvent
}

respondsToEventTarget(target) {
const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null
return element && element.closest("turbo-frame, html") == this.element
clickEventIsSignificant(event) {
const target = event.composed ? event.target?.parentElement : event.target
const element = findLinkFromClickTarget(target) || target

return element instanceof Element && element.closest("turbo-frame, html") == this.element
}
}
14 changes: 14 additions & 0 deletions src/core/frames/morphing_frame_renderer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { FrameRenderer } from "./frame_renderer"
import { morphChildren } from "../morphing"
import { dispatch } from "../../util"

export class MorphingFrameRenderer extends FrameRenderer {
static renderElement(currentElement, newElement) {
dispatch("turbo:before-frame-morph", {
target: currentElement,
detail: { currentElement, newElement }
})

morphChildren(currentElement, newElement)
}
}
66 changes: 66 additions & 0 deletions src/core/morphing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Idiomorph } from "idiomorph/dist/idiomorph.esm.js"
import { dispatch } from "../util"

export function morphElements(currentElement, newElement, { callbacks, ...options } = {}) {
Idiomorph.morph(currentElement, newElement, {
...options,
callbacks: new DefaultIdiomorphCallbacks(callbacks)
})
}

export function morphChildren(currentElement, newElement) {
morphElements(currentElement, newElement.children, {
morphStyle: "innerHTML"
})
}

class DefaultIdiomorphCallbacks {
#beforeNodeMorphed

constructor({ beforeNodeMorphed } = {}) {
this.#beforeNodeMorphed = beforeNodeMorphed || (() => true)
}

beforeNodeAdded = (node) => {
return !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id))
}

beforeNodeMorphed = (currentElement, newElement) => {
if (currentElement instanceof Element) {
if (!currentElement.hasAttribute("data-turbo-permanent") && this.#beforeNodeMorphed(currentElement, newElement)) {
const event = dispatch("turbo:before-morph-element", {
cancelable: true,
target: currentElement,
detail: { currentElement, newElement }
})

return !event.defaultPrevented
} else {
return false
}
}
}

beforeAttributeUpdated = (attributeName, target, mutationType) => {
const event = dispatch("turbo:before-morph-attribute", {
cancelable: true,
target,
detail: { attributeName, mutationType }
})

return !event.defaultPrevented
}

beforeNodeRemoved = (node) => {
return this.beforeNodeMorphed(node)
}

afterNodeMorphed = (currentElement, newElement) => {
if (currentElement instanceof Element) {
dispatch("turbo:morph-element", {
target: currentElement,
detail: { currentElement, newElement }
})
}
}
}
12 changes: 9 additions & 3 deletions src/core/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export class Renderer {
return true
}

get shouldAutofocus() {
return true
}

get reloadReason() {
return
}
Expand All @@ -40,9 +44,11 @@ export class Renderer {
}

focusFirstAutofocusableElement() {
const element = this.connectedSnapshot.firstAutofocusableElement
if (element) {
element.focus()
if (this.shouldAutofocus) {
const element = this.connectedSnapshot.firstAutofocusableElement
if (element) {
element.focus()
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/core/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export class Session {

refresh(url, requestId) {
const isRecentRequest = requestId && this.recentRequests.has(requestId)
if (!isRecentRequest) {
if (!isRecentRequest && !this.navigator.currentVisit) {
this.visit(url, { action: "replace", shouldCacheSnapshot: false })
}
}
Expand Down
Loading

0 comments on commit fe00499

Please sign in to comment.