clapton 0.0.13 → 0.0.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -0
  3. data/app/helpers/clapton/clapton_helper.rb +16 -1
  4. data/lib/clapton/engine.rb +20 -10
  5. data/lib/clapton/javascripts/dist/client.js +38 -27
  6. data/lib/clapton/javascripts/dist/components-for-test.js +439 -0
  7. data/lib/clapton/javascripts/dist/components.js +356 -382
  8. data/lib/clapton/javascripts/node_modules/diff-dom/LICENSE.txt +165 -0
  9. data/lib/clapton/javascripts/node_modules/diff-dom/README.md +224 -0
  10. data/lib/clapton/javascripts/node_modules/diff-dom/browser/diffDOM.js +2 -0
  11. data/lib/clapton/javascripts/node_modules/diff-dom/browser/diffDOM.js.map +1 -0
  12. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/TraceLogger.d.ts +28 -0
  13. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/dom/apply.d.ts +4 -0
  14. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/dom/fromVirtual.d.ts +2 -0
  15. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/dom/index.d.ts +2 -0
  16. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/dom/undo.d.ts +3 -0
  17. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/helpers.d.ts +11 -0
  18. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/index.d.ts +10 -0
  19. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/types.d.ts +104 -0
  20. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/apply.d.ts +3 -0
  21. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/diff.d.ts +22 -0
  22. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/fromDOM.d.ts +2 -0
  23. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/fromString.d.ts +2 -0
  24. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/helpers.d.ts +40 -0
  25. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/index.d.ts +3 -0
  26. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/index.d.ts +2 -0
  27. data/lib/clapton/javascripts/node_modules/diff-dom/dist/index.d.ts +136 -0
  28. data/lib/clapton/javascripts/node_modules/diff-dom/dist/index.js +1996 -0
  29. data/lib/clapton/javascripts/node_modules/diff-dom/dist/index.js.map +1 -0
  30. data/lib/clapton/javascripts/node_modules/diff-dom/dist/index.min.js +2 -0
  31. data/lib/clapton/javascripts/node_modules/diff-dom/dist/index.min.js.map +1 -0
  32. data/lib/clapton/javascripts/node_modules/diff-dom/dist/module.js +1991 -0
  33. data/lib/clapton/javascripts/node_modules/diff-dom/dist/module.js.map +1 -0
  34. data/lib/clapton/javascripts/node_modules/diff-dom/index.html +62 -0
  35. data/lib/clapton/javascripts/node_modules/diff-dom/package.json +54 -0
  36. data/lib/clapton/javascripts/node_modules/diff-dom/rollup.config.mjs +67 -0
  37. data/lib/clapton/javascripts/node_modules/diff-dom/src/TraceLogger.ts +143 -0
  38. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/dom/apply.ts +227 -0
  39. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/dom/fromVirtual.ts +83 -0
  40. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/dom/index.ts +2 -0
  41. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/dom/undo.ts +90 -0
  42. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/helpers.ts +40 -0
  43. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/index.ts +121 -0
  44. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/types.ts +154 -0
  45. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/apply.ts +349 -0
  46. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/diff.ts +855 -0
  47. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/fromDOM.ts +74 -0
  48. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/fromString.ts +239 -0
  49. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/helpers.ts +461 -0
  50. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/index.ts +3 -0
  51. data/lib/clapton/javascripts/node_modules/diff-dom/src/index.ts +2 -0
  52. data/lib/clapton/javascripts/node_modules/diff-dom/tsconfig.json +103 -0
  53. data/lib/clapton/javascripts/rollup.config.mjs +17 -2
  54. data/lib/clapton/javascripts/src/actions/handle-action.ts +6 -6
  55. data/lib/clapton/javascripts/src/actions/initialize-actions.ts +6 -3
  56. data/lib/clapton/javascripts/src/channel/clapton-channel.js +6 -3
  57. data/lib/clapton/javascripts/src/client.ts +15 -15
  58. data/lib/clapton/javascripts/src/components-for-test.ts +29 -0
  59. data/lib/clapton/javascripts/src/components.ts +4 -1
  60. data/lib/clapton/javascripts/src/dom/update-component.ts +4 -4
  61. data/lib/clapton/javascripts/src/inputs/initialize-inputs.ts +2 -2
  62. data/lib/clapton/test_helper/base.rb +1 -1
  63. data/lib/clapton/version.rb +1 -2
  64. metadata +49 -3
  65. data/lib/clapton/javascripts/src/dom/update-component.spec.ts +0 -32
@@ -0,0 +1,855 @@
1
+ import {
2
+ DiffDOMOptions,
3
+ diffNodeType,
4
+ elementDiffNodeType,
5
+ elementNodeType,
6
+ subsetType,
7
+ textDiffNodeType,
8
+ } from "../types"
9
+ import {
10
+ DiffTracker,
11
+ cleanNode,
12
+ getGapInformation,
13
+ isEqual,
14
+ markSubTrees,
15
+ removeDone,
16
+ roughlyEqual,
17
+ } from "./helpers"
18
+ import { Diff, checkElementType } from "../helpers"
19
+ import { applyVirtual } from "./apply"
20
+ import { nodeToObj } from "./fromDOM"
21
+ import { stringToObj } from "./fromString"
22
+
23
+ // ===== Create a diff =====
24
+
25
+ export class DiffFinder {
26
+ debug: boolean
27
+ diffcount: number
28
+ foundAll: boolean
29
+ options: DiffDOMOptions
30
+ t1: elementDiffNodeType
31
+ t1Orig: elementNodeType
32
+ t2: elementDiffNodeType
33
+ t2Orig: elementNodeType
34
+ tracker: DiffTracker
35
+ constructor(
36
+ t1Node: string | elementNodeType | Element,
37
+ t2Node: string | elementNodeType | Element,
38
+ options: DiffDOMOptions,
39
+ ) {
40
+ this.options = options
41
+ this.t1 = (
42
+ typeof Element !== "undefined" &&
43
+ checkElementType(t1Node, "Element")
44
+ ? nodeToObj(t1Node as Element, this.options)
45
+ : typeof t1Node === "string"
46
+ ? stringToObj(t1Node, this.options)
47
+ : JSON.parse(JSON.stringify(t1Node))
48
+ ) as elementDiffNodeType
49
+ this.t2 = (
50
+ typeof Element !== "undefined" &&
51
+ checkElementType(t2Node, "Element")
52
+ ? nodeToObj(t2Node as Element, this.options)
53
+ : typeof t2Node === "string"
54
+ ? stringToObj(t2Node, this.options)
55
+ : JSON.parse(JSON.stringify(t2Node))
56
+ ) as elementDiffNodeType
57
+ this.diffcount = 0
58
+ this.foundAll = false
59
+ if (this.debug) {
60
+ this.t1Orig =
61
+ typeof Element !== "undefined" &&
62
+ checkElementType(t1Node, "Element")
63
+ ? nodeToObj(t1Node as Element, this.options)
64
+ : typeof t1Node === "string"
65
+ ? stringToObj(t1Node, this.options)
66
+ : JSON.parse(JSON.stringify(t1Node))
67
+ this.t2Orig =
68
+ typeof Element !== "undefined" &&
69
+ checkElementType(t2Node, "Element")
70
+ ? nodeToObj(t2Node as Element, this.options)
71
+ : typeof t2Node === "string"
72
+ ? stringToObj(t2Node, this.options)
73
+ : JSON.parse(JSON.stringify(t2Node))
74
+ }
75
+
76
+ this.tracker = new DiffTracker()
77
+ }
78
+
79
+ init() {
80
+ return this.findDiffs(this.t1, this.t2)
81
+ }
82
+
83
+ findDiffs(t1: elementDiffNodeType, t2: elementDiffNodeType) {
84
+ let diffs
85
+ do {
86
+ if (this.options.debug) {
87
+ this.diffcount += 1
88
+ if (this.diffcount > this.options.diffcap) {
89
+ throw new Error(
90
+ `surpassed diffcap:${JSON.stringify(
91
+ this.t1Orig,
92
+ )} -> ${JSON.stringify(this.t2Orig)}`,
93
+ )
94
+ }
95
+ }
96
+ diffs = this.findNextDiff(t1, t2, [])
97
+
98
+ if (diffs.length === 0) {
99
+ // Last check if the elements really are the same now.
100
+ // If not, remove all info about being done and start over.
101
+ // Sometimes a node can be marked as done, but the creation of subsequent diffs means that it has to be changed again.
102
+ if (!isEqual(t1, t2)) {
103
+ if (this.foundAll) {
104
+ console.error("Could not find remaining diffs!")
105
+ } else {
106
+ this.foundAll = true
107
+ removeDone(t1)
108
+ diffs = this.findNextDiff(t1, t2, [])
109
+ }
110
+ }
111
+ }
112
+ if (diffs.length > 0) {
113
+ this.foundAll = false
114
+ this.tracker.add(diffs)
115
+ applyVirtual(t1, diffs, this.options)
116
+ }
117
+ } while (diffs.length > 0)
118
+
119
+ return this.tracker.list
120
+ }
121
+
122
+ findNextDiff(t1: diffNodeType, t2: diffNodeType, route: number[]) {
123
+ let diffs
124
+ let fdiffs
125
+
126
+ if (this.options.maxDepth && route.length > this.options.maxDepth) {
127
+ return []
128
+ }
129
+ // outer differences?
130
+ if (!t1.outerDone) {
131
+ diffs = this.findOuterDiff(t1, t2, route)
132
+ if (this.options.filterOuterDiff) {
133
+ fdiffs = this.options.filterOuterDiff(t1, t2, diffs)
134
+ if (fdiffs) diffs = fdiffs
135
+ }
136
+ if (diffs.length > 0) {
137
+ t1.outerDone = true
138
+ return diffs
139
+ } else {
140
+ t1.outerDone = true
141
+ }
142
+ }
143
+ if (Object.prototype.hasOwnProperty.call(t1, "data")) {
144
+ // Comment or Text
145
+ return []
146
+ }
147
+ t1 = t1 as elementDiffNodeType
148
+ t2 = t2 as elementDiffNodeType
149
+
150
+ // inner differences?
151
+ if (!t1.innerDone) {
152
+ diffs = this.findInnerDiff(t1, t2, route)
153
+ if (diffs.length > 0) {
154
+ return diffs
155
+ } else {
156
+ t1.innerDone = true
157
+ }
158
+ }
159
+
160
+ if (this.options.valueDiffing && !t1.valueDone) {
161
+ // value differences?
162
+ diffs = this.findValueDiff(t1, t2, route)
163
+
164
+ if (diffs.length > 0) {
165
+ t1.valueDone = true
166
+ return diffs
167
+ } else {
168
+ t1.valueDone = true
169
+ }
170
+ }
171
+
172
+ // no differences
173
+ return []
174
+ }
175
+
176
+ findOuterDiff(t1: diffNodeType, t2: diffNodeType, route: number[]) {
177
+ const diffs = []
178
+ let attr
179
+ let attr1
180
+ let attr2
181
+ let attrLength
182
+ let pos
183
+ let i
184
+ if (t1.nodeName !== t2.nodeName) {
185
+ if (!route.length) {
186
+ throw new Error("Top level nodes have to be of the same kind.")
187
+ }
188
+ return [
189
+ new Diff()
190
+ .setValue(
191
+ this.options._const.action,
192
+ this.options._const.replaceElement,
193
+ )
194
+ .setValue(this.options._const.oldValue, cleanNode(t1))
195
+ .setValue(this.options._const.newValue, cleanNode(t2))
196
+ .setValue(this.options._const.route, route),
197
+ ]
198
+ }
199
+ if (
200
+ route.length &&
201
+ this.options.diffcap <
202
+ Math.abs(
203
+ (t1.childNodes || []).length - (t2.childNodes || []).length,
204
+ )
205
+ ) {
206
+ return [
207
+ new Diff()
208
+ .setValue(
209
+ this.options._const.action,
210
+ this.options._const.replaceElement,
211
+ )
212
+ .setValue(this.options._const.oldValue, cleanNode(t1))
213
+ .setValue(this.options._const.newValue, cleanNode(t2))
214
+ .setValue(this.options._const.route, route),
215
+ ]
216
+ }
217
+
218
+ if (
219
+ Object.prototype.hasOwnProperty.call(t1, "data") &&
220
+ (t1 as textDiffNodeType).data !== (t2 as textDiffNodeType).data
221
+ ) {
222
+ // Comment or text node.
223
+ if (t1.nodeName === "#text") {
224
+ return [
225
+ new Diff()
226
+ .setValue(
227
+ this.options._const.action,
228
+ this.options._const.modifyTextElement,
229
+ )
230
+ .setValue(this.options._const.route, route)
231
+ .setValue(
232
+ this.options._const.oldValue,
233
+ (t1 as textDiffNodeType).data,
234
+ )
235
+ .setValue(
236
+ this.options._const.newValue,
237
+ (t2 as textDiffNodeType).data,
238
+ ),
239
+ ]
240
+ } else {
241
+ return [
242
+ new Diff()
243
+ .setValue(
244
+ this.options._const.action,
245
+ this.options._const.modifyComment,
246
+ )
247
+ .setValue(this.options._const.route, route)
248
+ .setValue(
249
+ this.options._const.oldValue,
250
+ (t1 as textDiffNodeType).data,
251
+ )
252
+ .setValue(
253
+ this.options._const.newValue,
254
+ (t2 as textDiffNodeType).data,
255
+ ),
256
+ ]
257
+ }
258
+ }
259
+
260
+ t1 = t1 as elementDiffNodeType
261
+ t2 = t2 as elementDiffNodeType
262
+
263
+ attr1 = t1.attributes ? Object.keys(t1.attributes).sort() : []
264
+ attr2 = t2.attributes ? Object.keys(t2.attributes).sort() : []
265
+
266
+ attrLength = attr1.length
267
+ for (i = 0; i < attrLength; i++) {
268
+ attr = attr1[i]
269
+ pos = attr2.indexOf(attr)
270
+ if (pos === -1) {
271
+ diffs.push(
272
+ new Diff()
273
+ .setValue(
274
+ this.options._const.action,
275
+ this.options._const.removeAttribute,
276
+ )
277
+ .setValue(this.options._const.route, route)
278
+ .setValue(this.options._const.name, attr)
279
+ .setValue(
280
+ this.options._const.value,
281
+ t1.attributes[attr],
282
+ ),
283
+ )
284
+ } else {
285
+ attr2.splice(pos, 1)
286
+ if (t1.attributes[attr] !== t2.attributes[attr]) {
287
+ diffs.push(
288
+ new Diff()
289
+ .setValue(
290
+ this.options._const.action,
291
+ this.options._const.modifyAttribute,
292
+ )
293
+ .setValue(this.options._const.route, route)
294
+ .setValue(this.options._const.name, attr)
295
+ .setValue(
296
+ this.options._const.oldValue,
297
+ t1.attributes[attr],
298
+ )
299
+ .setValue(
300
+ this.options._const.newValue,
301
+ t2.attributes[attr],
302
+ ),
303
+ )
304
+ }
305
+ }
306
+ }
307
+
308
+ attrLength = attr2.length
309
+ for (i = 0; i < attrLength; i++) {
310
+ attr = attr2[i]
311
+ diffs.push(
312
+ new Diff()
313
+ .setValue(
314
+ this.options._const.action,
315
+ this.options._const.addAttribute,
316
+ )
317
+ .setValue(this.options._const.route, route)
318
+ .setValue(this.options._const.name, attr)
319
+ .setValue(this.options._const.value, t2.attributes[attr]),
320
+ )
321
+ }
322
+
323
+ return diffs
324
+ }
325
+
326
+ findInnerDiff(
327
+ t1: elementDiffNodeType,
328
+ t2: elementDiffNodeType,
329
+ route: number[],
330
+ ) {
331
+ const t1ChildNodes = t1.childNodes ? t1.childNodes.slice() : []
332
+ const t2ChildNodes = t2.childNodes ? t2.childNodes.slice() : []
333
+ const last = Math.max(t1ChildNodes.length, t2ChildNodes.length)
334
+ let childNodesLengthDifference = Math.abs(
335
+ t1ChildNodes.length - t2ChildNodes.length,
336
+ )
337
+ let diffs: Diff[] = []
338
+ let index = 0
339
+ if (!this.options.maxChildCount || last < this.options.maxChildCount) {
340
+ const cachedSubtrees = Boolean(t1.subsets && t1.subsetsAge--)
341
+ const subtrees = cachedSubtrees
342
+ ? t1.subsets
343
+ : t1.childNodes && t2.childNodes
344
+ ? markSubTrees(t1, t2)
345
+ : []
346
+ if (subtrees.length > 0) {
347
+ /* One or more groups have been identified among the childnodes of t1
348
+ * and t2.
349
+ */
350
+ diffs = this.attemptGroupRelocation(
351
+ t1,
352
+ t2,
353
+ subtrees,
354
+ route,
355
+ cachedSubtrees,
356
+ )
357
+ if (diffs.length > 0) {
358
+ return diffs
359
+ }
360
+ }
361
+ }
362
+
363
+ /* 0 or 1 groups of similar child nodes have been found
364
+ * for t1 and t2. 1 If there is 1, it could be a sign that the
365
+ * contents are the same. When the number of groups is below 2,
366
+ * t1 and t2 are made to have the same length and each of the
367
+ * pairs of child nodes are diffed.
368
+ */
369
+
370
+ for (let i = 0; i < last; i += 1) {
371
+ const e1 = t1ChildNodes[i]
372
+ const e2 = t2ChildNodes[i]
373
+
374
+ if (childNodesLengthDifference) {
375
+ /* t1 and t2 have different amounts of childNodes. Add
376
+ * and remove as necessary to obtain the same length */
377
+ if (e1 && !e2) {
378
+ if (e1.nodeName === "#text") {
379
+ diffs.push(
380
+ new Diff()
381
+ .setValue(
382
+ this.options._const.action,
383
+ this.options._const.removeTextElement,
384
+ )
385
+ .setValue(
386
+ this.options._const.route,
387
+ route.concat(index),
388
+ )
389
+ .setValue(
390
+ this.options._const.value,
391
+ (e1 as textDiffNodeType).data,
392
+ ),
393
+ )
394
+ index -= 1
395
+ } else {
396
+ diffs.push(
397
+ new Diff()
398
+ .setValue(
399
+ this.options._const.action,
400
+ this.options._const.removeElement,
401
+ )
402
+ .setValue(
403
+ this.options._const.route,
404
+ route.concat(index),
405
+ )
406
+ .setValue(
407
+ this.options._const.element,
408
+ cleanNode(e1),
409
+ ),
410
+ )
411
+ index -= 1
412
+ }
413
+ } else if (e2 && !e1) {
414
+ if (e2.nodeName === "#text") {
415
+ diffs.push(
416
+ new Diff()
417
+ .setValue(
418
+ this.options._const.action,
419
+ this.options._const.addTextElement,
420
+ )
421
+ .setValue(
422
+ this.options._const.route,
423
+ route.concat(index),
424
+ )
425
+ .setValue(
426
+ this.options._const.value,
427
+ (e2 as textDiffNodeType).data,
428
+ ),
429
+ )
430
+ } else {
431
+ diffs.push(
432
+ new Diff()
433
+ .setValue(
434
+ this.options._const.action,
435
+ this.options._const.addElement,
436
+ )
437
+ .setValue(
438
+ this.options._const.route,
439
+ route.concat(index),
440
+ )
441
+ .setValue(
442
+ this.options._const.element,
443
+ cleanNode(e2),
444
+ ),
445
+ )
446
+ }
447
+ }
448
+ }
449
+ /* We are now guaranteed that childNodes e1 and e2 exist,
450
+ * and that they can be diffed.
451
+ */
452
+ /* Diffs in child nodes should not affect the parent node,
453
+ * so we let these diffs be submitted together with other
454
+ * diffs.
455
+ */
456
+
457
+ if (e1 && e2) {
458
+ if (
459
+ !this.options.maxChildCount ||
460
+ last < this.options.maxChildCount
461
+ ) {
462
+ diffs = diffs.concat(
463
+ this.findNextDiff(e1, e2, route.concat(index)),
464
+ )
465
+ } else if (!isEqual(e1, e2)) {
466
+ if (t1ChildNodes.length > t2ChildNodes.length) {
467
+ if (e1.nodeName === "#text") {
468
+ diffs.push(
469
+ new Diff()
470
+ .setValue(
471
+ this.options._const.action,
472
+ this.options._const.removeTextElement,
473
+ )
474
+ .setValue(
475
+ this.options._const.route,
476
+ route.concat(index),
477
+ )
478
+ .setValue(
479
+ this.options._const.value,
480
+ (e1 as textDiffNodeType).data,
481
+ ),
482
+ )
483
+ } else {
484
+ diffs.push(
485
+ new Diff()
486
+ .setValue(
487
+ this.options._const.action,
488
+ this.options._const.removeElement,
489
+ )
490
+ .setValue(
491
+ this.options._const.element,
492
+ cleanNode(e1),
493
+ )
494
+ .setValue(
495
+ this.options._const.route,
496
+ route.concat(index),
497
+ ),
498
+ )
499
+ }
500
+ t1ChildNodes.splice(i, 1)
501
+ i -= 1
502
+ index -= 1
503
+
504
+ childNodesLengthDifference -= 1
505
+ } else if (t1ChildNodes.length < t2ChildNodes.length) {
506
+ diffs = diffs.concat([
507
+ new Diff()
508
+ .setValue(
509
+ this.options._const.action,
510
+ this.options._const.addElement,
511
+ )
512
+ .setValue(
513
+ this.options._const.element,
514
+ cleanNode(e2),
515
+ )
516
+ .setValue(
517
+ this.options._const.route,
518
+ route.concat(index),
519
+ ),
520
+ ])
521
+ t1ChildNodes.splice(i, 0, cleanNode(e2))
522
+ childNodesLengthDifference -= 1
523
+ } else {
524
+ diffs = diffs.concat([
525
+ new Diff()
526
+ .setValue(
527
+ this.options._const.action,
528
+ this.options._const.replaceElement,
529
+ )
530
+ .setValue(
531
+ this.options._const.oldValue,
532
+ cleanNode(e1),
533
+ )
534
+ .setValue(
535
+ this.options._const.newValue,
536
+ cleanNode(e2),
537
+ )
538
+ .setValue(
539
+ this.options._const.route,
540
+ route.concat(index),
541
+ ),
542
+ ])
543
+ }
544
+ }
545
+ }
546
+ index += 1
547
+ }
548
+ t1.innerDone = true
549
+ return diffs
550
+ }
551
+
552
+ attemptGroupRelocation(
553
+ t1: elementDiffNodeType,
554
+ t2: elementDiffNodeType,
555
+ subtrees: subsetType[],
556
+ route: number[],
557
+ cachedSubtrees: boolean,
558
+ ) {
559
+ /* Either t1.childNodes and t2.childNodes have the same length, or
560
+ * there are at least two groups of similar elements can be found.
561
+ * attempts are made at equalizing t1 with t2. First all initial
562
+ * elements with no group affiliation (gaps=true) are removed (if
563
+ * only in t1) or added (if only in t2). Then the creation of a group
564
+ * relocation diff is attempted.
565
+ */
566
+ const gapInformation = getGapInformation(t1, t2, subtrees)
567
+ const gaps1 = gapInformation.gaps1
568
+ const gaps2 = gapInformation.gaps2
569
+ const t1ChildNodes = t1.childNodes.slice()
570
+ const t2ChildNodes = t2.childNodes.slice()
571
+ let shortest = Math.min(gaps1.length, gaps2.length)
572
+ let destinationDifferent
573
+ let toGroup
574
+ let group
575
+ let node
576
+ let similarNode
577
+ const diffs = []
578
+ for (
579
+ let index2 = 0, index1 = 0;
580
+ index2 < shortest;
581
+ index1 += 1, index2 += 1
582
+ ) {
583
+ if (
584
+ cachedSubtrees &&
585
+ (gaps1[index2] === true || gaps2[index2] === true)
586
+ ) {
587
+ // pass
588
+ } else if (gaps1[index1] === true) {
589
+ node = t1ChildNodes[index1]
590
+ if (node.nodeName === "#text") {
591
+ if (t2ChildNodes[index2].nodeName === "#text") {
592
+ if (
593
+ (node as textDiffNodeType).data !==
594
+ (t2ChildNodes[index2] as textDiffNodeType).data
595
+ ) {
596
+ // Check whether a text node with the same value follows later on.
597
+ let testI = index1
598
+ while (
599
+ t1ChildNodes.length > testI + 1 &&
600
+ t1ChildNodes[testI + 1].nodeName === "#text"
601
+ ) {
602
+ testI += 1
603
+ if (
604
+ (t2ChildNodes[index2] as textDiffNodeType)
605
+ .data ===
606
+ (t1ChildNodes[testI] as textDiffNodeType)
607
+ .data
608
+ ) {
609
+ similarNode = true
610
+ break
611
+ }
612
+ }
613
+ if (!similarNode) {
614
+ diffs.push(
615
+ new Diff()
616
+ .setValue(
617
+ this.options._const.action,
618
+ this.options._const
619
+ .modifyTextElement,
620
+ )
621
+ .setValue(
622
+ this.options._const.route,
623
+ route.concat(index1),
624
+ )
625
+ .setValue(
626
+ this.options._const.oldValue,
627
+ node.data,
628
+ )
629
+ .setValue(
630
+ this.options._const.newValue,
631
+ (
632
+ t2ChildNodes[
633
+ index2
634
+ ] as textDiffNodeType
635
+ ).data,
636
+ ),
637
+ // t1ChildNodes at position index1 is not up-to-date, but that does not matter as
638
+ // index1 will increase +1
639
+ )
640
+ }
641
+ }
642
+ } else {
643
+ diffs.push(
644
+ new Diff()
645
+ .setValue(
646
+ this.options._const.action,
647
+ this.options._const.removeTextElement,
648
+ )
649
+ .setValue(
650
+ this.options._const.route,
651
+ route.concat(index1),
652
+ )
653
+ .setValue(this.options._const.value, node.data),
654
+ )
655
+ gaps1.splice(index1, 1)
656
+ t1ChildNodes.splice(index1, 1)
657
+ shortest = Math.min(gaps1.length, gaps2.length)
658
+ index1 -= 1
659
+ index2 -= 1
660
+ }
661
+ } else if (gaps2[index2] === true) {
662
+ // both gaps1[index1] and gaps2[index2] are true.
663
+ // We replace one element with another.
664
+ diffs.push(
665
+ new Diff()
666
+ .setValue(
667
+ this.options._const.action,
668
+ this.options._const.replaceElement,
669
+ )
670
+ .setValue(
671
+ this.options._const.oldValue,
672
+ cleanNode(node),
673
+ )
674
+ .setValue(
675
+ this.options._const.newValue,
676
+ cleanNode(t2ChildNodes[index2]),
677
+ )
678
+ .setValue(
679
+ this.options._const.route,
680
+ route.concat(index1),
681
+ ),
682
+ )
683
+ // t1ChildNodes at position index1 is not up-to-date, but that does not matter as
684
+ // index1 will increase +1
685
+ } else {
686
+ diffs.push(
687
+ new Diff()
688
+ .setValue(
689
+ this.options._const.action,
690
+ this.options._const.removeElement,
691
+ )
692
+ .setValue(
693
+ this.options._const.route,
694
+ route.concat(index1),
695
+ )
696
+ .setValue(
697
+ this.options._const.element,
698
+ cleanNode(node),
699
+ ),
700
+ )
701
+ gaps1.splice(index1, 1)
702
+ t1ChildNodes.splice(index1, 1)
703
+ shortest = Math.min(gaps1.length, gaps2.length)
704
+ index1 -= 1
705
+ index2 -= 1
706
+ }
707
+ } else if (gaps2[index2] === true) {
708
+ node = t2ChildNodes[index2]
709
+ if (node.nodeName === "#text") {
710
+ diffs.push(
711
+ new Diff()
712
+ .setValue(
713
+ this.options._const.action,
714
+ this.options._const.addTextElement,
715
+ )
716
+ .setValue(
717
+ this.options._const.route,
718
+ route.concat(index1),
719
+ )
720
+ .setValue(this.options._const.value, node.data),
721
+ )
722
+ gaps1.splice(index1, 0, true)
723
+ t1ChildNodes.splice(index1, 0, {
724
+ nodeName: "#text",
725
+ data: node.data,
726
+ })
727
+ shortest = Math.min(gaps1.length, gaps2.length)
728
+ //index1 += 1
729
+ } else {
730
+ diffs.push(
731
+ new Diff()
732
+ .setValue(
733
+ this.options._const.action,
734
+ this.options._const.addElement,
735
+ )
736
+ .setValue(
737
+ this.options._const.route,
738
+ route.concat(index1),
739
+ )
740
+ .setValue(
741
+ this.options._const.element,
742
+ cleanNode(node),
743
+ ),
744
+ )
745
+ gaps1.splice(index1, 0, true)
746
+ t1ChildNodes.splice(index1, 0, cleanNode(node))
747
+ shortest = Math.min(gaps1.length, gaps2.length)
748
+ //index1 += 1
749
+ }
750
+ } else if (gaps1[index1] !== gaps2[index2]) {
751
+ if (diffs.length > 0) {
752
+ return diffs
753
+ }
754
+ // group relocation
755
+ group = subtrees[gaps1[index1] as number]
756
+ toGroup = Math.min(
757
+ group.newValue,
758
+ t1ChildNodes.length - group.length,
759
+ )
760
+ if (toGroup !== group.oldValue && toGroup > -1) {
761
+ // Check whether destination nodes are different than originating ones.
762
+ destinationDifferent = false
763
+ for (let j = 0; j < group.length; j += 1) {
764
+ if (
765
+ !roughlyEqual(
766
+ t1ChildNodes[toGroup + j],
767
+ t1ChildNodes[group.oldValue + j],
768
+ {},
769
+ false,
770
+ true,
771
+ )
772
+ ) {
773
+ destinationDifferent = true
774
+ }
775
+ }
776
+ if (destinationDifferent) {
777
+ return [
778
+ new Diff()
779
+ .setValue(
780
+ this.options._const.action,
781
+ this.options._const.relocateGroup,
782
+ )
783
+ .setValue(
784
+ this.options._const.groupLength,
785
+ group.length,
786
+ )
787
+ .setValue(
788
+ this.options._const.from,
789
+ group.oldValue,
790
+ )
791
+ .setValue(this.options._const.to, toGroup)
792
+ .setValue(this.options._const.route, route),
793
+ ]
794
+ }
795
+ }
796
+ }
797
+ }
798
+ return diffs
799
+ }
800
+
801
+ findValueDiff(
802
+ t1: elementDiffNodeType,
803
+ t2: elementDiffNodeType,
804
+ route: number[],
805
+ ) {
806
+ // Differences of value. Only useful if the value/selection/checked value
807
+ // differs from what is represented in the DOM. For example in the case
808
+ // of filled out forms, etc.
809
+ const diffs = []
810
+
811
+ if (t1.selected !== t2.selected) {
812
+ diffs.push(
813
+ new Diff()
814
+ .setValue(
815
+ this.options._const.action,
816
+ this.options._const.modifySelected,
817
+ )
818
+ .setValue(this.options._const.oldValue, t1.selected)
819
+ .setValue(this.options._const.newValue, t2.selected)
820
+ .setValue(this.options._const.route, route),
821
+ )
822
+ }
823
+
824
+ if (
825
+ (t1.value || t2.value) &&
826
+ t1.value !== t2.value &&
827
+ t1.nodeName !== "OPTION"
828
+ ) {
829
+ diffs.push(
830
+ new Diff()
831
+ .setValue(
832
+ this.options._const.action,
833
+ this.options._const.modifyValue,
834
+ )
835
+ .setValue(this.options._const.oldValue, t1.value || "")
836
+ .setValue(this.options._const.newValue, t2.value || "")
837
+ .setValue(this.options._const.route, route),
838
+ )
839
+ }
840
+ if (t1.checked !== t2.checked) {
841
+ diffs.push(
842
+ new Diff()
843
+ .setValue(
844
+ this.options._const.action,
845
+ this.options._const.modifyChecked,
846
+ )
847
+ .setValue(this.options._const.oldValue, t1.checked)
848
+ .setValue(this.options._const.newValue, t2.checked)
849
+ .setValue(this.options._const.route, route),
850
+ )
851
+ }
852
+
853
+ return diffs
854
+ }
855
+ }