-
Notifications
You must be signed in to change notification settings - Fork 436
/
Copy pathrenderer.ts
124 lines (103 loc) · 3.88 KB
/
renderer.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import { Snapshot } from "./snapshot"
type ResolvingFunctions<T = unknown> = {
resolve(value: T | PromiseLike<T>): void
reject(reason?: any): void
}
export type PermanentElement = Element & { id: string }
export type Placeholder = { element: Element, permanentElement: PermanentElement }
export abstract class Renderer<E extends Element, S extends Snapshot<E> = Snapshot<E>> {
readonly currentSnapshot: S
readonly newSnapshot: S
readonly isPreview: boolean
readonly promise: Promise<void>
private resolvingFunctions?: ResolvingFunctions<void>
constructor(currentSnapshot: S, newSnapshot: S, isPreview: boolean) {
this.currentSnapshot = currentSnapshot
this.newSnapshot = newSnapshot
this.isPreview = isPreview
this.promise = new Promise((resolve, reject) => this.resolvingFunctions = { resolve, reject })
}
get shouldRender() {
return true
}
prepareToRender() {
return
}
abstract render(): Promise<void>
finishRendering() {
if (this.resolvingFunctions) {
this.resolvingFunctions.resolve()
delete this.resolvingFunctions
}
}
createScriptElement(element: Element) {
if (element.getAttribute("data-turbo-eval") == "false") {
return element
} else {
const createdScriptElement = document.createElement("script")
createdScriptElement.textContent = element.textContent
createdScriptElement.async = false
copyElementAttributes(createdScriptElement, element)
return createdScriptElement
}
}
preservingPermanentElements(callback: () => void) {
const placeholders = relocatePermanentElements(this.currentSnapshot, this.newSnapshot)
callback()
replacePlaceholderElementsWithClonedPermanentElements(placeholders)
}
focusFirstAutofocusableElement() {
const element = this.connectedSnapshot.firstAutofocusableElement
if (elementIsFocusable(element)) {
element.focus()
}
}
get connectedSnapshot() {
return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot
}
get currentElement() {
return this.currentSnapshot.element
}
get newElement() {
return this.newSnapshot.element
}
}
export function replaceElementWithElement(fromElement: Element, toElement: Element) {
const parentElement = fromElement.parentElement
if (parentElement) {
return parentElement.replaceChild(toElement, fromElement)
}
}
function copyElementAttributes(destinationElement: Element, sourceElement: Element) {
for (const { name, value } of [ ...sourceElement.attributes ]) {
destinationElement.setAttribute(name, value)
}
}
function createPlaceholderForPermanentElement(permanentElement: PermanentElement) {
const element = document.createElement("meta")
element.setAttribute("name", "turbo-permanent-placeholder")
element.setAttribute("content", permanentElement.id)
return { element, permanentElement }
}
function replacePlaceholderElementsWithClonedPermanentElements(placeholders: Placeholder[]) {
for (const { element, permanentElement } of placeholders) {
const clonedElement = permanentElement.cloneNode(true)
replaceElementWithElement(element, clonedElement)
}
}
function relocatePermanentElements(currentSnapshot: Snapshot, newSnapshot: Snapshot) {
return currentSnapshot.getPermanentElementsPresentInSnapshot(newSnapshot).reduce((placeholders, permanentElement) => {
const newElement = newSnapshot.getPermanentElementById(permanentElement.id)
if (newElement) {
const placeholder = createPlaceholderForPermanentElement(permanentElement)
replaceElementWithElement(permanentElement, placeholder.element)
replaceElementWithElement(newElement, permanentElement)
return [ ...placeholders, placeholder ]
} else {
return placeholders
}
}, [] as Placeholder[])
}
function elementIsFocusable(element: any): element is { focus: () => void } {
return element && typeof element.focus == "function"
}