clapton 0.0.13 → 0.0.15
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -0
- data/app/helpers/clapton/clapton_helper.rb +16 -1
- data/lib/clapton/engine.rb +20 -10
- data/lib/clapton/javascripts/dist/client.js +38 -27
- data/lib/clapton/javascripts/dist/components-for-test.js +439 -0
- data/lib/clapton/javascripts/dist/components.js +356 -382
- data/lib/clapton/javascripts/node_modules/diff-dom/LICENSE.txt +165 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/README.md +224 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/browser/diffDOM.js +2 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/browser/diffDOM.js.map +1 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/TraceLogger.d.ts +28 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/dom/apply.d.ts +4 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/dom/fromVirtual.d.ts +2 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/dom/index.d.ts +2 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/dom/undo.d.ts +3 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/helpers.d.ts +11 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/index.d.ts +10 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/types.d.ts +104 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/apply.d.ts +3 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/diff.d.ts +22 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/fromDOM.d.ts +2 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/fromString.d.ts +2 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/helpers.d.ts +40 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/index.d.ts +3 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/index.d.ts +2 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/index.d.ts +136 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/index.js +1996 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/index.js.map +1 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/index.min.js +2 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/index.min.js.map +1 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/module.js +1991 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/module.js.map +1 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/index.html +62 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/package.json +54 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/rollup.config.mjs +67 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/TraceLogger.ts +143 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/dom/apply.ts +227 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/dom/fromVirtual.ts +83 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/dom/index.ts +2 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/dom/undo.ts +90 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/helpers.ts +40 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/index.ts +121 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/types.ts +154 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/apply.ts +349 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/diff.ts +855 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/fromDOM.ts +74 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/fromString.ts +239 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/helpers.ts +461 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/index.ts +3 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/index.ts +2 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/tsconfig.json +103 -0
- data/lib/clapton/javascripts/rollup.config.mjs +17 -2
- data/lib/clapton/javascripts/src/actions/handle-action.ts +6 -6
- data/lib/clapton/javascripts/src/actions/initialize-actions.ts +6 -3
- data/lib/clapton/javascripts/src/channel/clapton-channel.js +6 -3
- data/lib/clapton/javascripts/src/client.ts +15 -15
- data/lib/clapton/javascripts/src/components-for-test.ts +29 -0
- data/lib/clapton/javascripts/src/components.ts +4 -1
- data/lib/clapton/javascripts/src/dom/update-component.ts +4 -4
- data/lib/clapton/javascripts/src/inputs/initialize-inputs.ts +2 -2
- data/lib/clapton/test_helper/base.rb +1 -1
- data/lib/clapton/version.rb +1 -2
- metadata +49 -3
- data/lib/clapton/javascripts/src/dom/update-component.spec.ts +0 -32
@@ -0,0 +1,67 @@
|
|
1
|
+
import typescript from "@rollup/plugin-typescript"
|
2
|
+
import dts from "rollup-plugin-dts"
|
3
|
+
import buble from "@rollup/plugin-buble"
|
4
|
+
import terser from "@rollup/plugin-terser"
|
5
|
+
|
6
|
+
export default [
|
7
|
+
{
|
8
|
+
input: "src/index.ts",
|
9
|
+
output: [
|
10
|
+
{
|
11
|
+
file: "dist/index.js",
|
12
|
+
format: "cjs",
|
13
|
+
sourcemap: true,
|
14
|
+
},
|
15
|
+
{
|
16
|
+
file: "dist/module.js",
|
17
|
+
format: "es",
|
18
|
+
sourcemap: true,
|
19
|
+
},
|
20
|
+
],
|
21
|
+
plugins: [
|
22
|
+
typescript(),
|
23
|
+
buble(),
|
24
|
+
],
|
25
|
+
},
|
26
|
+
{
|
27
|
+
input: "src/index.ts",
|
28
|
+
output: [
|
29
|
+
{
|
30
|
+
file: "dist/index.min.js",
|
31
|
+
format: "cjs",
|
32
|
+
sourcemap: true,
|
33
|
+
},
|
34
|
+
],
|
35
|
+
plugins: [
|
36
|
+
typescript(),
|
37
|
+
buble(),
|
38
|
+
terser(),
|
39
|
+
],
|
40
|
+
},
|
41
|
+
{
|
42
|
+
input: './dist/dts/index.d.ts',
|
43
|
+
output: [{ file: 'dist/index.d.ts', format: 'es' }],
|
44
|
+
plugins: [dts()],
|
45
|
+
},
|
46
|
+
{
|
47
|
+
input: "src/index.ts",
|
48
|
+
output: [
|
49
|
+
{
|
50
|
+
file: "browser/diffDOM.js",
|
51
|
+
format: "iife",
|
52
|
+
name: "diffDOM",
|
53
|
+
sourcemap: true,
|
54
|
+
},
|
55
|
+
],
|
56
|
+
plugins: [
|
57
|
+
typescript({
|
58
|
+
compilerOptions: {
|
59
|
+
declaration: false,
|
60
|
+
declarationDir: undefined
|
61
|
+
}
|
62
|
+
}),
|
63
|
+
buble(),
|
64
|
+
terser()
|
65
|
+
],
|
66
|
+
},
|
67
|
+
]
|
@@ -0,0 +1,143 @@
|
|
1
|
+
import { checkElementType } from "./diffDOM/helpers"
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Use TraceLogger to figure out function calls inside
|
5
|
+
* JS objects by wrapping an object with a TraceLogger
|
6
|
+
* instance.
|
7
|
+
*
|
8
|
+
* Pretty-prints the call trace (using unicode box code)
|
9
|
+
* when tracelogger.toString() is called.
|
10
|
+
*/
|
11
|
+
|
12
|
+
/**
|
13
|
+
* Wrap an object by calling new TraceLogger(obj)
|
14
|
+
*
|
15
|
+
* If you're familiar with Python decorators, this
|
16
|
+
* does roughly the same thing, adding pre/post
|
17
|
+
* call hook logging calls so that you can see
|
18
|
+
* what's going on.
|
19
|
+
*/
|
20
|
+
export class TraceLogger {
|
21
|
+
messages: string[]
|
22
|
+
pad: string
|
23
|
+
padding: string
|
24
|
+
tick: number
|
25
|
+
constructor(obj = {}) {
|
26
|
+
this.pad = "│ "
|
27
|
+
this.padding = ""
|
28
|
+
this.tick = 1
|
29
|
+
this.messages = []
|
30
|
+
const wrapkey = (obj: object, key: string) => {
|
31
|
+
// trace this function
|
32
|
+
const oldfn = obj[key]
|
33
|
+
obj[key] = (
|
34
|
+
...args: ((
|
35
|
+
...args: (
|
36
|
+
| string
|
37
|
+
| HTMLElement
|
38
|
+
| number
|
39
|
+
| boolean
|
40
|
+
| false
|
41
|
+
| (string | HTMLElement | number | boolean | false)[]
|
42
|
+
)[]
|
43
|
+
) => void)[]
|
44
|
+
) => {
|
45
|
+
this.fin(key, Array.prototype.slice.call(args))
|
46
|
+
const result = oldfn.apply(obj, args)
|
47
|
+
this.fout(key, result)
|
48
|
+
return result
|
49
|
+
}
|
50
|
+
}
|
51
|
+
// can't use Object.keys for prototype walking
|
52
|
+
for (let key in obj) {
|
53
|
+
if (typeof obj[key] === "function") {
|
54
|
+
wrapkey(obj, key)
|
55
|
+
}
|
56
|
+
}
|
57
|
+
this.log("┌ TRACELOG START")
|
58
|
+
}
|
59
|
+
// called when entering a function
|
60
|
+
fin(
|
61
|
+
fn: string,
|
62
|
+
args:
|
63
|
+
| string
|
64
|
+
| HTMLElement
|
65
|
+
| number
|
66
|
+
| boolean
|
67
|
+
| false
|
68
|
+
| (string | HTMLElement | number | boolean | false)[],
|
69
|
+
) {
|
70
|
+
this.padding += this.pad
|
71
|
+
this.log(`├─> entering ${fn}`, args)
|
72
|
+
}
|
73
|
+
// called when exiting a function
|
74
|
+
fout(
|
75
|
+
fn: string,
|
76
|
+
result:
|
77
|
+
| string
|
78
|
+
| HTMLElement
|
79
|
+
| number
|
80
|
+
| boolean
|
81
|
+
| false
|
82
|
+
| (string | HTMLElement | number | boolean | false)[],
|
83
|
+
) {
|
84
|
+
this.log("│<──┘ generated return value", result)
|
85
|
+
this.padding = this.padding.substring(
|
86
|
+
0,
|
87
|
+
this.padding.length - this.pad.length,
|
88
|
+
)
|
89
|
+
}
|
90
|
+
// log message formatting
|
91
|
+
format(s: string, tick: number) {
|
92
|
+
let nf = function (t: number) {
|
93
|
+
let tStr = `${t}`
|
94
|
+
while (tStr.length < 4) {
|
95
|
+
tStr = `0${t}`
|
96
|
+
}
|
97
|
+
return tStr
|
98
|
+
}
|
99
|
+
return `${nf(tick)}> ${this.padding}${s}`
|
100
|
+
}
|
101
|
+
// log a trace message
|
102
|
+
log(...args) {
|
103
|
+
const stringCollapse = function (
|
104
|
+
v:
|
105
|
+
| string
|
106
|
+
| HTMLElement
|
107
|
+
| number
|
108
|
+
| boolean
|
109
|
+
| false
|
110
|
+
| (string | HTMLElement | number | boolean | false)[],
|
111
|
+
) {
|
112
|
+
if (!v) {
|
113
|
+
return "<falsey>"
|
114
|
+
}
|
115
|
+
if (typeof v === "string") {
|
116
|
+
return v
|
117
|
+
}
|
118
|
+
if (checkElementType(v, "HTMLElement")) {
|
119
|
+
return (v as HTMLElement).outerHTML || "<empty>"
|
120
|
+
}
|
121
|
+
if (v instanceof Array) {
|
122
|
+
return `[${v.map(stringCollapse).join(",")}]`
|
123
|
+
}
|
124
|
+
return v.toString() || v.valueOf() || "<unknown>"
|
125
|
+
}
|
126
|
+
const s = args.map(stringCollapse).join(", ")
|
127
|
+
this.messages.push(this.format(s, this.tick++))
|
128
|
+
}
|
129
|
+
// turn the log into a structured string with
|
130
|
+
// unicode box codes to make it a sensible trace.
|
131
|
+
toString() {
|
132
|
+
let cap = "× "
|
133
|
+
let terminator = "└───"
|
134
|
+
while (terminator.length <= this.padding.length + this.pad.length) {
|
135
|
+
terminator += cap
|
136
|
+
}
|
137
|
+
let _ = this.padding
|
138
|
+
this.padding = ""
|
139
|
+
terminator = this.format(terminator, this.tick)
|
140
|
+
this.padding = _
|
141
|
+
return `${this.messages.join("\n")}\n${terminator}`
|
142
|
+
}
|
143
|
+
}
|
@@ -0,0 +1,227 @@
|
|
1
|
+
import { DiffDOMOptions, diffType, nodeType } from "../types"
|
2
|
+
import { Diff, checkElementType } from "../helpers"
|
3
|
+
|
4
|
+
import { objToNode } from "./fromVirtual"
|
5
|
+
|
6
|
+
// ===== Apply a diff =====
|
7
|
+
|
8
|
+
const getFromRoute = (
|
9
|
+
node: Element,
|
10
|
+
route: number[],
|
11
|
+
): Element | Text | false => {
|
12
|
+
route = route.slice()
|
13
|
+
while (route.length > 0) {
|
14
|
+
const c = route.splice(0, 1)[0]
|
15
|
+
node = node.childNodes[c] as Element
|
16
|
+
}
|
17
|
+
return node
|
18
|
+
}
|
19
|
+
|
20
|
+
export function applyDiff(
|
21
|
+
tree: Element,
|
22
|
+
diff: diffType,
|
23
|
+
options: DiffDOMOptions, // {preDiffApply, postDiffApply, textDiff, valueDiffing, _const}
|
24
|
+
) {
|
25
|
+
const action = diff[options._const.action] as string | number
|
26
|
+
const route = diff[options._const.route] as number[]
|
27
|
+
let node
|
28
|
+
|
29
|
+
if (
|
30
|
+
![options._const.addElement, options._const.addTextElement].includes(
|
31
|
+
action,
|
32
|
+
)
|
33
|
+
) {
|
34
|
+
// For adding nodes, we calculate the route later on. It's different because it includes the position of the newly added item.
|
35
|
+
node = getFromRoute(tree, route)
|
36
|
+
}
|
37
|
+
|
38
|
+
let newNode
|
39
|
+
let reference: Element
|
40
|
+
let nodeArray
|
41
|
+
|
42
|
+
// pre-diff hook
|
43
|
+
const info = {
|
44
|
+
diff,
|
45
|
+
node,
|
46
|
+
}
|
47
|
+
|
48
|
+
if (options.preDiffApply(info)) {
|
49
|
+
return true
|
50
|
+
}
|
51
|
+
|
52
|
+
switch (action) {
|
53
|
+
case options._const.addAttribute:
|
54
|
+
if (!node || !checkElementType(node, "Element")) {
|
55
|
+
return false
|
56
|
+
}
|
57
|
+
node.setAttribute(
|
58
|
+
diff[options._const.name] as string,
|
59
|
+
diff[options._const.value] as string,
|
60
|
+
)
|
61
|
+
break
|
62
|
+
case options._const.modifyAttribute:
|
63
|
+
if (!node || !checkElementType(node, "Element")) {
|
64
|
+
return false
|
65
|
+
}
|
66
|
+
node.setAttribute(
|
67
|
+
diff[options._const.name] as string,
|
68
|
+
diff[options._const.newValue] as string,
|
69
|
+
)
|
70
|
+
if (
|
71
|
+
checkElementType(node, "HTMLInputElement") &&
|
72
|
+
diff[options._const.name] === "value"
|
73
|
+
) {
|
74
|
+
node.value = diff[options._const.newValue] as string
|
75
|
+
}
|
76
|
+
break
|
77
|
+
case options._const.removeAttribute:
|
78
|
+
if (!node || !checkElementType(node, "Element")) {
|
79
|
+
return false
|
80
|
+
}
|
81
|
+
node.removeAttribute(diff[options._const.name] as string)
|
82
|
+
break
|
83
|
+
case options._const.modifyTextElement:
|
84
|
+
if (!node || !checkElementType(node, "Text")) {
|
85
|
+
return false
|
86
|
+
}
|
87
|
+
options.textDiff(
|
88
|
+
node,
|
89
|
+
node.data,
|
90
|
+
diff[options._const.oldValue] as string,
|
91
|
+
diff[options._const.newValue] as string,
|
92
|
+
)
|
93
|
+
if (checkElementType(node.parentNode, "HTMLTextAreaElement")) {
|
94
|
+
node.parentNode.value = diff[options._const.newValue] as string
|
95
|
+
}
|
96
|
+
break
|
97
|
+
case options._const.modifyValue:
|
98
|
+
if (!node || typeof node.value === "undefined") {
|
99
|
+
return false
|
100
|
+
}
|
101
|
+
node.value = diff[options._const.newValue]
|
102
|
+
break
|
103
|
+
case options._const.modifyComment:
|
104
|
+
if (!node || !checkElementType(node, "Comment")) {
|
105
|
+
return false
|
106
|
+
}
|
107
|
+
options.textDiff(
|
108
|
+
node,
|
109
|
+
node.data,
|
110
|
+
diff[options._const.oldValue] as string,
|
111
|
+
diff[options._const.newValue] as string,
|
112
|
+
)
|
113
|
+
break
|
114
|
+
case options._const.modifyChecked:
|
115
|
+
if (!node || typeof node.checked === "undefined") {
|
116
|
+
return false
|
117
|
+
}
|
118
|
+
node.checked = diff[options._const.newValue]
|
119
|
+
break
|
120
|
+
case options._const.modifySelected:
|
121
|
+
if (!node || typeof node.selected === "undefined") {
|
122
|
+
return false
|
123
|
+
}
|
124
|
+
node.selected = diff[options._const.newValue]
|
125
|
+
break
|
126
|
+
case options._const.replaceElement: {
|
127
|
+
const insideSvg =
|
128
|
+
(
|
129
|
+
diff[options._const.newValue] as nodeType
|
130
|
+
).nodeName.toLowerCase() === "svg" ||
|
131
|
+
node.parentNode.namespaceURI === "http://www.w3.org/2000/svg"
|
132
|
+
node.parentNode.replaceChild(
|
133
|
+
objToNode(
|
134
|
+
diff[options._const.newValue] as nodeType,
|
135
|
+
insideSvg,
|
136
|
+
options,
|
137
|
+
),
|
138
|
+
node,
|
139
|
+
)
|
140
|
+
break
|
141
|
+
}
|
142
|
+
case options._const.relocateGroup:
|
143
|
+
nodeArray = [...new Array(diff[options._const.groupLength])].map(
|
144
|
+
() =>
|
145
|
+
node.removeChild(
|
146
|
+
node.childNodes[diff[options._const.from] as number],
|
147
|
+
),
|
148
|
+
)
|
149
|
+
nodeArray.forEach((childNode, index) => {
|
150
|
+
if (index === 0) {
|
151
|
+
reference =
|
152
|
+
node.childNodes[diff[options._const.to] as number]
|
153
|
+
}
|
154
|
+
node.insertBefore(childNode, reference || null)
|
155
|
+
})
|
156
|
+
break
|
157
|
+
case options._const.removeElement:
|
158
|
+
node.parentNode.removeChild(node)
|
159
|
+
break
|
160
|
+
case options._const.addElement: {
|
161
|
+
const parentRoute = route.slice()
|
162
|
+
const c: number = parentRoute.splice(parentRoute.length - 1, 1)[0]
|
163
|
+
node = getFromRoute(tree, parentRoute)
|
164
|
+
if (!checkElementType(node, "Element")) {
|
165
|
+
return false
|
166
|
+
}
|
167
|
+
node.insertBefore(
|
168
|
+
objToNode(
|
169
|
+
diff[options._const.element] as nodeType,
|
170
|
+
node.namespaceURI === "http://www.w3.org/2000/svg",
|
171
|
+
options,
|
172
|
+
),
|
173
|
+
node.childNodes[c] || null,
|
174
|
+
)
|
175
|
+
break
|
176
|
+
}
|
177
|
+
case options._const.removeTextElement: {
|
178
|
+
if (!node || node.nodeType !== 3) {
|
179
|
+
return false
|
180
|
+
}
|
181
|
+
const parentNode = node.parentNode
|
182
|
+
parentNode.removeChild(node)
|
183
|
+
if (checkElementType(parentNode, "HTMLTextAreaElement")) {
|
184
|
+
parentNode.value = ""
|
185
|
+
}
|
186
|
+
break
|
187
|
+
}
|
188
|
+
case options._const.addTextElement: {
|
189
|
+
const parentRoute = route.slice()
|
190
|
+
const c: number = parentRoute.splice(parentRoute.length - 1, 1)[0]
|
191
|
+
newNode = options.document.createTextNode(
|
192
|
+
diff[options._const.value] as string,
|
193
|
+
)
|
194
|
+
node = getFromRoute(tree, parentRoute)
|
195
|
+
if (!node.childNodes) {
|
196
|
+
return false
|
197
|
+
}
|
198
|
+
node.insertBefore(newNode, node.childNodes[c] || null)
|
199
|
+
if (checkElementType(node.parentNode, "HTMLTextAreaElement")) {
|
200
|
+
node.parentNode.value = diff[options._const.value] as string
|
201
|
+
}
|
202
|
+
break
|
203
|
+
}
|
204
|
+
default:
|
205
|
+
console.log("unknown action")
|
206
|
+
}
|
207
|
+
|
208
|
+
// if a new node was created, we might be interested in its
|
209
|
+
// post diff hook
|
210
|
+
options.postDiffApply({
|
211
|
+
diff: info.diff,
|
212
|
+
node: info.node,
|
213
|
+
newNode,
|
214
|
+
})
|
215
|
+
|
216
|
+
return true
|
217
|
+
}
|
218
|
+
|
219
|
+
export function applyDOM(
|
220
|
+
tree: Element,
|
221
|
+
diffs: (Diff | diffType)[],
|
222
|
+
options: DiffDOMOptions,
|
223
|
+
) {
|
224
|
+
return diffs.every((diff: Diff | diffType) =>
|
225
|
+
applyDiff(tree, diff as diffType, options),
|
226
|
+
)
|
227
|
+
}
|
@@ -0,0 +1,83 @@
|
|
1
|
+
import { DiffDOMOptions, elementNodeType, textNodeType } from "../types"
|
2
|
+
import { checkElementType } from "../helpers"
|
3
|
+
|
4
|
+
export function objToNode(
|
5
|
+
objNode: elementNodeType,
|
6
|
+
insideSvg: boolean,
|
7
|
+
options: DiffDOMOptions,
|
8
|
+
) {
|
9
|
+
let node: Element | Text | Comment
|
10
|
+
if (objNode.nodeName === "#text") {
|
11
|
+
node = options.document.createTextNode((objNode as textNodeType).data)
|
12
|
+
} else if (objNode.nodeName === "#comment") {
|
13
|
+
node = options.document.createComment((objNode as textNodeType).data)
|
14
|
+
} else {
|
15
|
+
if (insideSvg) {
|
16
|
+
node = options.document.createElementNS(
|
17
|
+
"http://www.w3.org/2000/svg",
|
18
|
+
objNode.nodeName,
|
19
|
+
)
|
20
|
+
if (objNode.nodeName === "foreignObject") {
|
21
|
+
insideSvg = false
|
22
|
+
}
|
23
|
+
} else if (objNode.nodeName.toLowerCase() === "svg") {
|
24
|
+
node = options.document.createElementNS(
|
25
|
+
"http://www.w3.org/2000/svg",
|
26
|
+
"svg",
|
27
|
+
)
|
28
|
+
insideSvg = true
|
29
|
+
} else {
|
30
|
+
node = options.document.createElement(objNode.nodeName)
|
31
|
+
}
|
32
|
+
if (objNode.attributes) {
|
33
|
+
Object.entries(objNode.attributes).forEach(([key, value]) =>
|
34
|
+
(node as Element).setAttribute(key, value),
|
35
|
+
)
|
36
|
+
}
|
37
|
+
if (objNode.childNodes) {
|
38
|
+
node = node as Element
|
39
|
+
objNode.childNodes.forEach(
|
40
|
+
(childNode: elementNodeType | textNodeType) =>
|
41
|
+
node.appendChild(objToNode(childNode, insideSvg, options)),
|
42
|
+
)
|
43
|
+
}
|
44
|
+
if (options.valueDiffing) {
|
45
|
+
if (
|
46
|
+
objNode.value &&
|
47
|
+
checkElementType(
|
48
|
+
node,
|
49
|
+
"HTMLButtonElement",
|
50
|
+
"HTMLDataElement",
|
51
|
+
"HTMLInputElement",
|
52
|
+
"HTMLLIElement",
|
53
|
+
"HTMLMeterElement",
|
54
|
+
"HTMLOptionElement",
|
55
|
+
"HTMLProgressElement",
|
56
|
+
"HTMLParamElement",
|
57
|
+
)
|
58
|
+
) {
|
59
|
+
;(
|
60
|
+
node as
|
61
|
+
| HTMLButtonElement
|
62
|
+
| HTMLDataElement
|
63
|
+
| HTMLInputElement
|
64
|
+
| HTMLLIElement
|
65
|
+
| HTMLMeterElement
|
66
|
+
| HTMLOptionElement
|
67
|
+
| HTMLProgressElement
|
68
|
+
| HTMLParamElement
|
69
|
+
).value = objNode.value
|
70
|
+
}
|
71
|
+
if (objNode.checked && checkElementType(node, "HTMLInputElement")) {
|
72
|
+
;(node as HTMLInputElement).checked = objNode.checked
|
73
|
+
}
|
74
|
+
if (
|
75
|
+
objNode.selected &&
|
76
|
+
checkElementType(node, "HTMLOptionElement")
|
77
|
+
) {
|
78
|
+
;(node as HTMLOptionElement).selected = objNode.selected
|
79
|
+
}
|
80
|
+
}
|
81
|
+
}
|
82
|
+
return node
|
83
|
+
}
|
@@ -0,0 +1,90 @@
|
|
1
|
+
import { DiffDOMOptions, diffType } from "../types"
|
2
|
+
import { Diff } from "../helpers"
|
3
|
+
import { applyDiff } from "./apply"
|
4
|
+
|
5
|
+
// ===== Undo a diff =====
|
6
|
+
|
7
|
+
function swap(obj: object, p1: string | number, p2: string | number) {
|
8
|
+
const tmp = obj[p1]
|
9
|
+
obj[p1] = obj[p2]
|
10
|
+
obj[p2] = tmp
|
11
|
+
}
|
12
|
+
|
13
|
+
function undoDiff(
|
14
|
+
tree: Element,
|
15
|
+
diff: diffType,
|
16
|
+
options: DiffDOMOptions, // {preDiffApply, postDiffApply, textDiff, valueDiffing, _const}
|
17
|
+
) {
|
18
|
+
switch (diff[options._const.action]) {
|
19
|
+
case options._const.addAttribute:
|
20
|
+
diff[options._const.action] = options._const.removeAttribute
|
21
|
+
applyDiff(tree, diff, options)
|
22
|
+
break
|
23
|
+
case options._const.modifyAttribute:
|
24
|
+
swap(diff, options._const.oldValue, options._const.newValue)
|
25
|
+
applyDiff(tree, diff, options)
|
26
|
+
break
|
27
|
+
case options._const.removeAttribute:
|
28
|
+
diff[options._const.action] = options._const.addAttribute
|
29
|
+
applyDiff(tree, diff, options)
|
30
|
+
break
|
31
|
+
case options._const.modifyTextElement:
|
32
|
+
swap(diff, options._const.oldValue, options._const.newValue)
|
33
|
+
applyDiff(tree, diff, options)
|
34
|
+
break
|
35
|
+
case options._const.modifyValue:
|
36
|
+
swap(diff, options._const.oldValue, options._const.newValue)
|
37
|
+
applyDiff(tree, diff, options)
|
38
|
+
break
|
39
|
+
case options._const.modifyComment:
|
40
|
+
swap(diff, options._const.oldValue, options._const.newValue)
|
41
|
+
applyDiff(tree, diff, options)
|
42
|
+
break
|
43
|
+
case options._const.modifyChecked:
|
44
|
+
swap(diff, options._const.oldValue, options._const.newValue)
|
45
|
+
applyDiff(tree, diff, options)
|
46
|
+
break
|
47
|
+
case options._const.modifySelected:
|
48
|
+
swap(diff, options._const.oldValue, options._const.newValue)
|
49
|
+
applyDiff(tree, diff, options)
|
50
|
+
break
|
51
|
+
case options._const.replaceElement:
|
52
|
+
swap(diff, options._const.oldValue, options._const.newValue)
|
53
|
+
applyDiff(tree, diff, options)
|
54
|
+
break
|
55
|
+
case options._const.relocateGroup:
|
56
|
+
swap(diff, options._const.from, options._const.to)
|
57
|
+
applyDiff(tree, diff, options)
|
58
|
+
break
|
59
|
+
case options._const.removeElement:
|
60
|
+
diff[options._const.action] = options._const.addElement
|
61
|
+
applyDiff(tree, diff, options)
|
62
|
+
break
|
63
|
+
case options._const.addElement:
|
64
|
+
diff[options._const.action] = options._const.removeElement
|
65
|
+
applyDiff(tree, diff, options)
|
66
|
+
break
|
67
|
+
case options._const.removeTextElement:
|
68
|
+
diff[options._const.action] = options._const.addTextElement
|
69
|
+
applyDiff(tree, diff, options)
|
70
|
+
break
|
71
|
+
case options._const.addTextElement:
|
72
|
+
diff[options._const.action] = options._const.removeTextElement
|
73
|
+
applyDiff(tree, diff, options)
|
74
|
+
break
|
75
|
+
default:
|
76
|
+
console.log("unknown action")
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
export function undoDOM(
|
81
|
+
tree: Element,
|
82
|
+
diffs: (diffType | Diff)[],
|
83
|
+
options: DiffDOMOptions,
|
84
|
+
) {
|
85
|
+
diffs = diffs.slice()
|
86
|
+
diffs.reverse()
|
87
|
+
diffs.forEach((diff: diffType | Diff) => {
|
88
|
+
undoDiff(tree, diff as diffType, options)
|
89
|
+
})
|
90
|
+
}
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import { elementNodeType } from "./types"
|
2
|
+
|
3
|
+
export class Diff {
|
4
|
+
constructor(options = {}) {
|
5
|
+
Object.entries(options).forEach(([key, value]) => (this[key] = value))
|
6
|
+
}
|
7
|
+
|
8
|
+
toString() {
|
9
|
+
return JSON.stringify(this)
|
10
|
+
}
|
11
|
+
|
12
|
+
setValue(
|
13
|
+
aKey: string | number,
|
14
|
+
aValue:
|
15
|
+
| string
|
16
|
+
| number
|
17
|
+
| boolean
|
18
|
+
| number[]
|
19
|
+
| { [key: string]: string | { [key: string]: string } }
|
20
|
+
| elementNodeType,
|
21
|
+
) {
|
22
|
+
this[aKey] = aValue
|
23
|
+
return this
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
export function checkElementType(element, ...elementTypeNames: string[]) {
|
28
|
+
if (typeof element === "undefined" || element === null) {
|
29
|
+
return false
|
30
|
+
}
|
31
|
+
return elementTypeNames.some(
|
32
|
+
(elementTypeName) =>
|
33
|
+
// We need to check if the specified type is defined
|
34
|
+
// because otherwise instanceof throws an exception.
|
35
|
+
typeof element?.ownerDocument?.defaultView?.[elementTypeName] ===
|
36
|
+
"function" &&
|
37
|
+
element instanceof
|
38
|
+
element.ownerDocument.defaultView[elementTypeName],
|
39
|
+
)
|
40
|
+
}
|