clapton 0.0.13 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) 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 +14 -10
  5. data/lib/clapton/javascripts/dist/client.js +31 -19
  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/initialize-actions.ts +6 -3
  55. data/lib/clapton/javascripts/src/channel/clapton-channel.js +6 -3
  56. data/lib/clapton/javascripts/src/client.ts +15 -15
  57. data/lib/clapton/javascripts/src/components-for-test.ts +29 -0
  58. data/lib/clapton/javascripts/src/components.ts +4 -1
  59. data/lib/clapton/javascripts/src/dom/update-component.ts +3 -2
  60. data/lib/clapton/javascripts/src/inputs/initialize-inputs.ts +2 -2
  61. data/lib/clapton/test_helper/base.rb +1 -1
  62. data/lib/clapton/version.rb +1 -1
  63. metadata +49 -3
  64. 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
+ }