lively 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c7e42819f7548d59e893c416f38537446a4f64cdba0753cbca1ace1317f1a75f
4
- data.tar.gz: ac926c52189a1d7ef707a1c2fb83e47aed149477de1021e0af5485a801063250
3
+ metadata.gz: f9d360f337452c370cc2c6272ef7cf93c2897cce1b026e6771c44f155fcbbc4a
4
+ data.tar.gz: 949a67a705fa676cdb7456d9174282b461b7b2cc05105e1ca2b6024d697f0c73
5
5
  SHA512:
6
- metadata.gz: 50f2c6841152c19b7a5198ec84f4ecd3a266a2f46ef02ff556b562535751608fce3e26d28a23acdc56ec1115671215fd0ed18f23f800cf37384c3a2b8615c6c4
7
- data.tar.gz: f5bc7a16d4aab99165e6c739e12e5b94ceba62fff7f0859e262690cc89ab440ad5167df61f38bb0fba03dab86b0344eeb095c5524788c5e10ad87294c2263dab
6
+ metadata.gz: 3b5e129a9e6a2f4480f67404e4d54002f628348a73379bde01b65856c0fda49efca55e8ba63e53a0bdd2f0f6ac0b7368c7d60b6f5a213b3f3fc9c0923088fa6f
7
+ data.tar.gz: 68d6b61e6edcbd585e54d2f0d1c8fca3509cc1b8513d1536eaa15c18ecb4acbd0faeb4308cdc0abdd6563fc6815e67e8007b40da50843e1c8e3146392cdfad59
@@ -31,6 +31,7 @@ module Lively
31
31
  # @attribute [Protocol::HTTP::Middleware]
32
32
  middleware do
33
33
  ::Protocol::HTTP::Middleware.build do |builder|
34
+ builder.use Assets, root: File.expand_path('public', @root)
34
35
  builder.use Assets, root: File.expand_path('../../../public', __dir__)
35
36
  builder.use ::Application
36
37
  end
@@ -8,6 +8,7 @@
8
8
 
9
9
  <link rel="icon" type="image/png" href="/_static/icon.png" />
10
10
  <link rel="stylesheet" href="/_static/site.css" type="text/css" media="screen" />
11
+ <link rel="stylesheet" href="/_static/index.css" type="text/css" media="screen" />
11
12
 
12
13
  <script src="/_components/morphdom/morphdom-umd.js"></script>
13
14
  <script src="/_components/@socketry/live/live.js"></script>
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lively
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -0,0 +1,157 @@
1
+ var live = (function () {
2
+ 'use strict';
3
+
4
+ class Live {
5
+ constructor(document, url) {
6
+ this.document = document;
7
+
8
+ this.url = url;
9
+ this.events = [];
10
+
11
+ this.failures = 0;
12
+
13
+ // Track visibility state and connect if required:
14
+ this.document.addEventListener("visibilitychange", () => this.handleVisibilityChange());
15
+ this.handleVisibilityChange();
16
+ }
17
+
18
+ connect() {
19
+ if (this.server) return;
20
+
21
+ let server = this.server = new WebSocket(this.url.href);
22
+
23
+ server.onopen = () => {
24
+ this.failures = 0;
25
+ this.attach();
26
+ };
27
+
28
+ server.onmessage = (message) => {
29
+ const [name, _arguments] = JSON.parse(message.data);
30
+
31
+ this[name](..._arguments);
32
+ };
33
+
34
+ server.onerror = () => {
35
+ this.failures += 1;
36
+ };
37
+
38
+ // The remote end has disconnected:
39
+ server.onclose = () => {
40
+ this.server = null;
41
+
42
+ // We need a minimum delay otherwise this can end up immediately invoking the callback:
43
+ let delay = 100 * (this.failures + 1) ** 2;
44
+ setTimeout(() => this.connect(), delay > 60000 ? 60000 : delay);
45
+ };
46
+ }
47
+
48
+ disconnect() {
49
+ if (this.server) {
50
+ this.server.close();
51
+ this.server = null;
52
+ }
53
+ }
54
+
55
+ createDocumentFragment(html) {
56
+ return this.document.createRange().createContextualFragment(html);
57
+ }
58
+
59
+ // These methods are designed for RPC.
60
+ replace(id, html) {
61
+ let element = document.getElementById(id);
62
+
63
+ morphdom(element, html);
64
+ }
65
+
66
+ prepend(id, html) {
67
+ let element = document.getElementById(id);
68
+ let fragment = this.createDocumentFragment(html);
69
+
70
+ element.prepend(fragment);
71
+ }
72
+
73
+ append(id, html) {
74
+ let element = document.getElementById(id);
75
+ let fragment = this.createDocumentFragment(html);
76
+
77
+ element.append(fragment);
78
+ }
79
+
80
+ dispatch(id, type, details) {
81
+ let element = document.getElementById(id);
82
+
83
+ element.dispatchEvent(
84
+ new CustomEvent(type, details)
85
+ );
86
+ }
87
+
88
+ trigger(id, event) {
89
+ this.connect();
90
+
91
+ this.send(
92
+ JSON.stringify({id: id, event: event})
93
+ );
94
+ }
95
+
96
+ forward(id, event, details) {
97
+ this.trigger(id, {type: event.type, details: details});
98
+ }
99
+
100
+ send(message) {
101
+ try {
102
+ this.server.send(message);
103
+ } catch (error) {
104
+ this.events.push(message);
105
+ }
106
+ }
107
+
108
+ flush() {
109
+ if (this.events.length === 0) return;
110
+
111
+ let events = this.events;
112
+ this.events = [];
113
+
114
+ for (var event of events) {
115
+ this.send(event);
116
+ }
117
+ }
118
+
119
+ bind(elements) {
120
+ for (var element of elements) {
121
+ this.send(JSON.stringify({bind: element.id, data: element.dataset}));
122
+ }
123
+ }
124
+
125
+ bindElementsByClassName(selector = 'live') {
126
+ this.bind(
127
+ this.document.getElementsByClassName(selector)
128
+ );
129
+
130
+ this.flush();
131
+ }
132
+
133
+ handleVisibilityChange() {
134
+ if (document.hidden) {
135
+ this.disconnect();
136
+ } else {
137
+ this.connect();
138
+ }
139
+ }
140
+
141
+ attach() {
142
+ if (this.document.readyState === 'loading') {
143
+ this.document.addEventListener('DOMContentLoaded', () => this.bindElementsByClassName());
144
+ } else {
145
+ this.bindElementsByClassName();
146
+ }
147
+ }
148
+ }
149
+
150
+ let url = new URL('live', location.href);
151
+ url.protocol = url.protocol.replace('http', 'ws');
152
+
153
+ let live = new Live(document, url);
154
+
155
+ return live;
156
+
157
+ }());
@@ -0,0 +1 @@
1
+ var live=function(){"use strict";class Live{constructor(document,url){this.document=document;this.url=url;this.events=[];this.failures=0;this.document.addEventListener("visibilitychange",()=>this.handleVisibilityChange());this.handleVisibilityChange()}connect(){if(this.server)return;let server=this.server=new WebSocket(this.url.href);server.onopen=()=>{this.failures=0;this.attach()};server.onmessage=message=>{const[name,_arguments]=JSON.parse(message.data);this[name](..._arguments)};server.onerror=()=>{this.failures+=1};server.onclose=()=>{this.server=null;let delay=100*(this.failures+1)**2;setTimeout(()=>this.connect(),delay>6e4?6e4:delay)}}disconnect(){if(this.server){this.server.close();this.server=null}}createDocumentFragment(html){return this.document.createRange().createContextualFragment(html)}replace(id,html){let element=document.getElementById(id);morphdom(element,html)}prepend(id,html){let element=document.getElementById(id);let fragment=this.createDocumentFragment(html);element.prepend(fragment)}append(id,html){let element=document.getElementById(id);let fragment=this.createDocumentFragment(html);element.append(fragment)}dispatch(id,type,details){let element=document.getElementById(id);element.dispatchEvent(new CustomEvent(type,details))}trigger(id,event){this.connect();this.send(JSON.stringify({id:id,event:event}))}forward(id,event,details){this.trigger(id,{type:event.type,details:details})}send(message){try{this.server.send(message)}catch(error){this.events.push(message)}}flush(){if(this.events.length===0)return;let events=this.events;this.events=[];for(var event of events){this.send(event)}}bind(elements){for(var element of elements){this.send(JSON.stringify({bind:element.id,data:element.dataset}))}}bindElementsByClassName(selector="live"){this.bind(this.document.getElementsByClassName(selector));this.flush()}handleVisibilityChange(){if(document.hidden){this.disconnect()}else{this.connect()}}attach(){if(this.document.readyState==="loading"){this.document.addEventListener("DOMContentLoaded",()=>this.bindElementsByClassName())}else{this.bindElementsByClassName()}}}let url=new URL("live",location.href);url.protocol=url.protocol.replace("http","ws");let live=new Live(document,url);return live}();
@@ -0,0 +1,755 @@
1
+ var DOCUMENT_FRAGMENT_NODE = 11;
2
+
3
+ function morphAttrs(fromNode, toNode) {
4
+ var toNodeAttrs = toNode.attributes;
5
+ var attr;
6
+ var attrName;
7
+ var attrNamespaceURI;
8
+ var attrValue;
9
+ var fromValue;
10
+
11
+ // document-fragments dont have attributes so lets not do anything
12
+ if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE || fromNode.nodeType === DOCUMENT_FRAGMENT_NODE) {
13
+ return;
14
+ }
15
+
16
+ // update attributes on original DOM element
17
+ for (var i = toNodeAttrs.length - 1; i >= 0; i--) {
18
+ attr = toNodeAttrs[i];
19
+ attrName = attr.name;
20
+ attrNamespaceURI = attr.namespaceURI;
21
+ attrValue = attr.value;
22
+
23
+ if (attrNamespaceURI) {
24
+ attrName = attr.localName || attrName;
25
+ fromValue = fromNode.getAttributeNS(attrNamespaceURI, attrName);
26
+
27
+ if (fromValue !== attrValue) {
28
+ if (attr.prefix === 'xmlns'){
29
+ attrName = attr.name; // It's not allowed to set an attribute with the XMLNS namespace without specifying the `xmlns` prefix
30
+ }
31
+ fromNode.setAttributeNS(attrNamespaceURI, attrName, attrValue);
32
+ }
33
+ } else {
34
+ fromValue = fromNode.getAttribute(attrName);
35
+
36
+ if (fromValue !== attrValue) {
37
+ fromNode.setAttribute(attrName, attrValue);
38
+ }
39
+ }
40
+ }
41
+
42
+ // Remove any extra attributes found on the original DOM element that
43
+ // weren't found on the target element.
44
+ var fromNodeAttrs = fromNode.attributes;
45
+
46
+ for (var d = fromNodeAttrs.length - 1; d >= 0; d--) {
47
+ attr = fromNodeAttrs[d];
48
+ attrName = attr.name;
49
+ attrNamespaceURI = attr.namespaceURI;
50
+
51
+ if (attrNamespaceURI) {
52
+ attrName = attr.localName || attrName;
53
+
54
+ if (!toNode.hasAttributeNS(attrNamespaceURI, attrName)) {
55
+ fromNode.removeAttributeNS(attrNamespaceURI, attrName);
56
+ }
57
+ } else {
58
+ if (!toNode.hasAttribute(attrName)) {
59
+ fromNode.removeAttribute(attrName);
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ var range; // Create a range object for efficently rendering strings to elements.
66
+ var NS_XHTML = 'http://www.w3.org/1999/xhtml';
67
+
68
+ var doc = typeof document === 'undefined' ? undefined : document;
69
+ var HAS_TEMPLATE_SUPPORT = !!doc && 'content' in doc.createElement('template');
70
+ var HAS_RANGE_SUPPORT = !!doc && doc.createRange && 'createContextualFragment' in doc.createRange();
71
+
72
+ function createFragmentFromTemplate(str) {
73
+ var template = doc.createElement('template');
74
+ template.innerHTML = str;
75
+ return template.content.childNodes[0];
76
+ }
77
+
78
+ function createFragmentFromRange(str) {
79
+ if (!range) {
80
+ range = doc.createRange();
81
+ range.selectNode(doc.body);
82
+ }
83
+
84
+ var fragment = range.createContextualFragment(str);
85
+ return fragment.childNodes[0];
86
+ }
87
+
88
+ function createFragmentFromWrap(str) {
89
+ var fragment = doc.createElement('body');
90
+ fragment.innerHTML = str;
91
+ return fragment.childNodes[0];
92
+ }
93
+
94
+ /**
95
+ * This is about the same
96
+ * var html = new DOMParser().parseFromString(str, 'text/html');
97
+ * return html.body.firstChild;
98
+ *
99
+ * @method toElement
100
+ * @param {String} str
101
+ */
102
+ function toElement(str) {
103
+ str = str.trim();
104
+ if (HAS_TEMPLATE_SUPPORT) {
105
+ // avoid restrictions on content for things like `<tr><th>Hi</th></tr>` which
106
+ // createContextualFragment doesn't support
107
+ // <template> support not available in IE
108
+ return createFragmentFromTemplate(str);
109
+ } else if (HAS_RANGE_SUPPORT) {
110
+ return createFragmentFromRange(str);
111
+ }
112
+
113
+ return createFragmentFromWrap(str);
114
+ }
115
+
116
+ /**
117
+ * Returns true if two node's names are the same.
118
+ *
119
+ * NOTE: We don't bother checking `namespaceURI` because you will never find two HTML elements with the same
120
+ * nodeName and different namespace URIs.
121
+ *
122
+ * @param {Element} a
123
+ * @param {Element} b The target element
124
+ * @return {boolean}
125
+ */
126
+ function compareNodeNames(fromEl, toEl) {
127
+ var fromNodeName = fromEl.nodeName;
128
+ var toNodeName = toEl.nodeName;
129
+ var fromCodeStart, toCodeStart;
130
+
131
+ if (fromNodeName === toNodeName) {
132
+ return true;
133
+ }
134
+
135
+ fromCodeStart = fromNodeName.charCodeAt(0);
136
+ toCodeStart = toNodeName.charCodeAt(0);
137
+
138
+ // If the target element is a virtual DOM node or SVG node then we may
139
+ // need to normalize the tag name before comparing. Normal HTML elements that are
140
+ // in the "http://www.w3.org/1999/xhtml"
141
+ // are converted to upper case
142
+ if (fromCodeStart <= 90 && toCodeStart >= 97) { // from is upper and to is lower
143
+ return fromNodeName === toNodeName.toUpperCase();
144
+ } else if (toCodeStart <= 90 && fromCodeStart >= 97) { // to is upper and from is lower
145
+ return toNodeName === fromNodeName.toUpperCase();
146
+ } else {
147
+ return false;
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Create an element, optionally with a known namespace URI.
153
+ *
154
+ * @param {string} name the element name, e.g. 'div' or 'svg'
155
+ * @param {string} [namespaceURI] the element's namespace URI, i.e. the value of
156
+ * its `xmlns` attribute or its inferred namespace.
157
+ *
158
+ * @return {Element}
159
+ */
160
+ function createElementNS(name, namespaceURI) {
161
+ return !namespaceURI || namespaceURI === NS_XHTML ?
162
+ doc.createElement(name) :
163
+ doc.createElementNS(namespaceURI, name);
164
+ }
165
+
166
+ /**
167
+ * Copies the children of one DOM element to another DOM element
168
+ */
169
+ function moveChildren(fromEl, toEl) {
170
+ var curChild = fromEl.firstChild;
171
+ while (curChild) {
172
+ var nextChild = curChild.nextSibling;
173
+ toEl.appendChild(curChild);
174
+ curChild = nextChild;
175
+ }
176
+ return toEl;
177
+ }
178
+
179
+ function syncBooleanAttrProp(fromEl, toEl, name) {
180
+ if (fromEl[name] !== toEl[name]) {
181
+ fromEl[name] = toEl[name];
182
+ if (fromEl[name]) {
183
+ fromEl.setAttribute(name, '');
184
+ } else {
185
+ fromEl.removeAttribute(name);
186
+ }
187
+ }
188
+ }
189
+
190
+ var specialElHandlers = {
191
+ OPTION: function(fromEl, toEl) {
192
+ var parentNode = fromEl.parentNode;
193
+ if (parentNode) {
194
+ var parentName = parentNode.nodeName.toUpperCase();
195
+ if (parentName === 'OPTGROUP') {
196
+ parentNode = parentNode.parentNode;
197
+ parentName = parentNode && parentNode.nodeName.toUpperCase();
198
+ }
199
+ if (parentName === 'SELECT' && !parentNode.hasAttribute('multiple')) {
200
+ if (fromEl.hasAttribute('selected') && !toEl.selected) {
201
+ // Workaround for MS Edge bug where the 'selected' attribute can only be
202
+ // removed if set to a non-empty value:
203
+ // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12087679/
204
+ fromEl.setAttribute('selected', 'selected');
205
+ fromEl.removeAttribute('selected');
206
+ }
207
+ // We have to reset select element's selectedIndex to -1, otherwise setting
208
+ // fromEl.selected using the syncBooleanAttrProp below has no effect.
209
+ // The correct selectedIndex will be set in the SELECT special handler below.
210
+ parentNode.selectedIndex = -1;
211
+ }
212
+ }
213
+ syncBooleanAttrProp(fromEl, toEl, 'selected');
214
+ },
215
+ /**
216
+ * The "value" attribute is special for the <input> element since it sets
217
+ * the initial value. Changing the "value" attribute without changing the
218
+ * "value" property will have no effect since it is only used to the set the
219
+ * initial value. Similar for the "checked" attribute, and "disabled".
220
+ */
221
+ INPUT: function(fromEl, toEl) {
222
+ syncBooleanAttrProp(fromEl, toEl, 'checked');
223
+ syncBooleanAttrProp(fromEl, toEl, 'disabled');
224
+
225
+ if (fromEl.value !== toEl.value) {
226
+ fromEl.value = toEl.value;
227
+ }
228
+
229
+ if (!toEl.hasAttribute('value')) {
230
+ fromEl.removeAttribute('value');
231
+ }
232
+ },
233
+
234
+ TEXTAREA: function(fromEl, toEl) {
235
+ var newValue = toEl.value;
236
+ if (fromEl.value !== newValue) {
237
+ fromEl.value = newValue;
238
+ }
239
+
240
+ var firstChild = fromEl.firstChild;
241
+ if (firstChild) {
242
+ // Needed for IE. Apparently IE sets the placeholder as the
243
+ // node value and vise versa. This ignores an empty update.
244
+ var oldValue = firstChild.nodeValue;
245
+
246
+ if (oldValue == newValue || (!newValue && oldValue == fromEl.placeholder)) {
247
+ return;
248
+ }
249
+
250
+ firstChild.nodeValue = newValue;
251
+ }
252
+ },
253
+ SELECT: function(fromEl, toEl) {
254
+ if (!toEl.hasAttribute('multiple')) {
255
+ var selectedIndex = -1;
256
+ var i = 0;
257
+ // We have to loop through children of fromEl, not toEl since nodes can be moved
258
+ // from toEl to fromEl directly when morphing.
259
+ // At the time this special handler is invoked, all children have already been morphed
260
+ // and appended to / removed from fromEl, so using fromEl here is safe and correct.
261
+ var curChild = fromEl.firstChild;
262
+ var optgroup;
263
+ var nodeName;
264
+ while(curChild) {
265
+ nodeName = curChild.nodeName && curChild.nodeName.toUpperCase();
266
+ if (nodeName === 'OPTGROUP') {
267
+ optgroup = curChild;
268
+ curChild = optgroup.firstChild;
269
+ } else {
270
+ if (nodeName === 'OPTION') {
271
+ if (curChild.hasAttribute('selected')) {
272
+ selectedIndex = i;
273
+ break;
274
+ }
275
+ i++;
276
+ }
277
+ curChild = curChild.nextSibling;
278
+ if (!curChild && optgroup) {
279
+ curChild = optgroup.nextSibling;
280
+ optgroup = null;
281
+ }
282
+ }
283
+ }
284
+
285
+ fromEl.selectedIndex = selectedIndex;
286
+ }
287
+ }
288
+ };
289
+
290
+ var ELEMENT_NODE = 1;
291
+ var DOCUMENT_FRAGMENT_NODE$1 = 11;
292
+ var TEXT_NODE = 3;
293
+ var COMMENT_NODE = 8;
294
+
295
+ function noop() {}
296
+
297
+ function defaultGetNodeKey(node) {
298
+ if (node) {
299
+ return (node.getAttribute && node.getAttribute('id')) || node.id;
300
+ }
301
+ }
302
+
303
+ function morphdomFactory(morphAttrs) {
304
+
305
+ return function morphdom(fromNode, toNode, options) {
306
+ if (!options) {
307
+ options = {};
308
+ }
309
+
310
+ if (typeof toNode === 'string') {
311
+ if (fromNode.nodeName === '#document' || fromNode.nodeName === 'HTML' || fromNode.nodeName === 'BODY') {
312
+ var toNodeHtml = toNode;
313
+ toNode = doc.createElement('html');
314
+ toNode.innerHTML = toNodeHtml;
315
+ } else {
316
+ toNode = toElement(toNode);
317
+ }
318
+ }
319
+
320
+ var getNodeKey = options.getNodeKey || defaultGetNodeKey;
321
+ var onBeforeNodeAdded = options.onBeforeNodeAdded || noop;
322
+ var onNodeAdded = options.onNodeAdded || noop;
323
+ var onBeforeElUpdated = options.onBeforeElUpdated || noop;
324
+ var onElUpdated = options.onElUpdated || noop;
325
+ var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop;
326
+ var onNodeDiscarded = options.onNodeDiscarded || noop;
327
+ var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || noop;
328
+ var childrenOnly = options.childrenOnly === true;
329
+
330
+ // This object is used as a lookup to quickly find all keyed elements in the original DOM tree.
331
+ var fromNodesLookup = Object.create(null);
332
+ var keyedRemovalList = [];
333
+
334
+ function addKeyedRemoval(key) {
335
+ keyedRemovalList.push(key);
336
+ }
337
+
338
+ function walkDiscardedChildNodes(node, skipKeyedNodes) {
339
+ if (node.nodeType === ELEMENT_NODE) {
340
+ var curChild = node.firstChild;
341
+ while (curChild) {
342
+
343
+ var key = undefined;
344
+
345
+ if (skipKeyedNodes && (key = getNodeKey(curChild))) {
346
+ // If we are skipping keyed nodes then we add the key
347
+ // to a list so that it can be handled at the very end.
348
+ addKeyedRemoval(key);
349
+ } else {
350
+ // Only report the node as discarded if it is not keyed. We do this because
351
+ // at the end we loop through all keyed elements that were unmatched
352
+ // and then discard them in one final pass.
353
+ onNodeDiscarded(curChild);
354
+ if (curChild.firstChild) {
355
+ walkDiscardedChildNodes(curChild, skipKeyedNodes);
356
+ }
357
+ }
358
+
359
+ curChild = curChild.nextSibling;
360
+ }
361
+ }
362
+ }
363
+
364
+ /**
365
+ * Removes a DOM node out of the original DOM
366
+ *
367
+ * @param {Node} node The node to remove
368
+ * @param {Node} parentNode The nodes parent
369
+ * @param {Boolean} skipKeyedNodes If true then elements with keys will be skipped and not discarded.
370
+ * @return {undefined}
371
+ */
372
+ function removeNode(node, parentNode, skipKeyedNodes) {
373
+ if (onBeforeNodeDiscarded(node) === false) {
374
+ return;
375
+ }
376
+
377
+ if (parentNode) {
378
+ parentNode.removeChild(node);
379
+ }
380
+
381
+ onNodeDiscarded(node);
382
+ walkDiscardedChildNodes(node, skipKeyedNodes);
383
+ }
384
+
385
+ // // TreeWalker implementation is no faster, but keeping this around in case this changes in the future
386
+ // function indexTree(root) {
387
+ // var treeWalker = document.createTreeWalker(
388
+ // root,
389
+ // NodeFilter.SHOW_ELEMENT);
390
+ //
391
+ // var el;
392
+ // while((el = treeWalker.nextNode())) {
393
+ // var key = getNodeKey(el);
394
+ // if (key) {
395
+ // fromNodesLookup[key] = el;
396
+ // }
397
+ // }
398
+ // }
399
+
400
+ // // NodeIterator implementation is no faster, but keeping this around in case this changes in the future
401
+ //
402
+ // function indexTree(node) {
403
+ // var nodeIterator = document.createNodeIterator(node, NodeFilter.SHOW_ELEMENT);
404
+ // var el;
405
+ // while((el = nodeIterator.nextNode())) {
406
+ // var key = getNodeKey(el);
407
+ // if (key) {
408
+ // fromNodesLookup[key] = el;
409
+ // }
410
+ // }
411
+ // }
412
+
413
+ function indexTree(node) {
414
+ if (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE$1) {
415
+ var curChild = node.firstChild;
416
+ while (curChild) {
417
+ var key = getNodeKey(curChild);
418
+ if (key) {
419
+ fromNodesLookup[key] = curChild;
420
+ }
421
+
422
+ // Walk recursively
423
+ indexTree(curChild);
424
+
425
+ curChild = curChild.nextSibling;
426
+ }
427
+ }
428
+ }
429
+
430
+ indexTree(fromNode);
431
+
432
+ function handleNodeAdded(el) {
433
+ onNodeAdded(el);
434
+
435
+ var curChild = el.firstChild;
436
+ while (curChild) {
437
+ var nextSibling = curChild.nextSibling;
438
+
439
+ var key = getNodeKey(curChild);
440
+ if (key) {
441
+ var unmatchedFromEl = fromNodesLookup[key];
442
+ // if we find a duplicate #id node in cache, replace `el` with cache value
443
+ // and morph it to the child node.
444
+ if (unmatchedFromEl && compareNodeNames(curChild, unmatchedFromEl)) {
445
+ curChild.parentNode.replaceChild(unmatchedFromEl, curChild);
446
+ morphEl(unmatchedFromEl, curChild);
447
+ } else {
448
+ handleNodeAdded(curChild);
449
+ }
450
+ } else {
451
+ // recursively call for curChild and it's children to see if we find something in
452
+ // fromNodesLookup
453
+ handleNodeAdded(curChild);
454
+ }
455
+
456
+ curChild = nextSibling;
457
+ }
458
+ }
459
+
460
+ function cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey) {
461
+ // We have processed all of the "to nodes". If curFromNodeChild is
462
+ // non-null then we still have some from nodes left over that need
463
+ // to be removed
464
+ while (curFromNodeChild) {
465
+ var fromNextSibling = curFromNodeChild.nextSibling;
466
+ if ((curFromNodeKey = getNodeKey(curFromNodeChild))) {
467
+ // Since the node is keyed it might be matched up later so we defer
468
+ // the actual removal to later
469
+ addKeyedRemoval(curFromNodeKey);
470
+ } else {
471
+ // NOTE: we skip nested keyed nodes from being removed since there is
472
+ // still a chance they will be matched up later
473
+ removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);
474
+ }
475
+ curFromNodeChild = fromNextSibling;
476
+ }
477
+ }
478
+
479
+ function morphEl(fromEl, toEl, childrenOnly) {
480
+ var toElKey = getNodeKey(toEl);
481
+
482
+ if (toElKey) {
483
+ // If an element with an ID is being morphed then it will be in the final
484
+ // DOM so clear it out of the saved elements collection
485
+ delete fromNodesLookup[toElKey];
486
+ }
487
+
488
+ if (!childrenOnly) {
489
+ // optional
490
+ if (onBeforeElUpdated(fromEl, toEl) === false) {
491
+ return;
492
+ }
493
+
494
+ // update attributes on original DOM element first
495
+ morphAttrs(fromEl, toEl);
496
+ // optional
497
+ onElUpdated(fromEl);
498
+
499
+ if (onBeforeElChildrenUpdated(fromEl, toEl) === false) {
500
+ return;
501
+ }
502
+ }
503
+
504
+ if (fromEl.nodeName !== 'TEXTAREA') {
505
+ morphChildren(fromEl, toEl);
506
+ } else {
507
+ specialElHandlers.TEXTAREA(fromEl, toEl);
508
+ }
509
+ }
510
+
511
+ function morphChildren(fromEl, toEl) {
512
+ var curToNodeChild = toEl.firstChild;
513
+ var curFromNodeChild = fromEl.firstChild;
514
+ var curToNodeKey;
515
+ var curFromNodeKey;
516
+
517
+ var fromNextSibling;
518
+ var toNextSibling;
519
+ var matchingFromEl;
520
+
521
+ // walk the children
522
+ outer: while (curToNodeChild) {
523
+ toNextSibling = curToNodeChild.nextSibling;
524
+ curToNodeKey = getNodeKey(curToNodeChild);
525
+
526
+ // walk the fromNode children all the way through
527
+ while (curFromNodeChild) {
528
+ fromNextSibling = curFromNodeChild.nextSibling;
529
+
530
+ if (curToNodeChild.isSameNode && curToNodeChild.isSameNode(curFromNodeChild)) {
531
+ curToNodeChild = toNextSibling;
532
+ curFromNodeChild = fromNextSibling;
533
+ continue outer;
534
+ }
535
+
536
+ curFromNodeKey = getNodeKey(curFromNodeChild);
537
+
538
+ var curFromNodeType = curFromNodeChild.nodeType;
539
+
540
+ // this means if the curFromNodeChild doesnt have a match with the curToNodeChild
541
+ var isCompatible = undefined;
542
+
543
+ if (curFromNodeType === curToNodeChild.nodeType) {
544
+ if (curFromNodeType === ELEMENT_NODE) {
545
+ // Both nodes being compared are Element nodes
546
+
547
+ if (curToNodeKey) {
548
+ // The target node has a key so we want to match it up with the correct element
549
+ // in the original DOM tree
550
+ if (curToNodeKey !== curFromNodeKey) {
551
+ // The current element in the original DOM tree does not have a matching key so
552
+ // let's check our lookup to see if there is a matching element in the original
553
+ // DOM tree
554
+ if ((matchingFromEl = fromNodesLookup[curToNodeKey])) {
555
+ if (fromNextSibling === matchingFromEl) {
556
+ // Special case for single element removals. To avoid removing the original
557
+ // DOM node out of the tree (since that can break CSS transitions, etc.),
558
+ // we will instead discard the current node and wait until the next
559
+ // iteration to properly match up the keyed target element with its matching
560
+ // element in the original tree
561
+ isCompatible = false;
562
+ } else {
563
+ // We found a matching keyed element somewhere in the original DOM tree.
564
+ // Let's move the original DOM node into the current position and morph
565
+ // it.
566
+
567
+ // NOTE: We use insertBefore instead of replaceChild because we want to go through
568
+ // the `removeNode()` function for the node that is being discarded so that
569
+ // all lifecycle hooks are correctly invoked
570
+ fromEl.insertBefore(matchingFromEl, curFromNodeChild);
571
+
572
+ // fromNextSibling = curFromNodeChild.nextSibling;
573
+
574
+ if (curFromNodeKey) {
575
+ // Since the node is keyed it might be matched up later so we defer
576
+ // the actual removal to later
577
+ addKeyedRemoval(curFromNodeKey);
578
+ } else {
579
+ // NOTE: we skip nested keyed nodes from being removed since there is
580
+ // still a chance they will be matched up later
581
+ removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);
582
+ }
583
+
584
+ curFromNodeChild = matchingFromEl;
585
+ }
586
+ } else {
587
+ // The nodes are not compatible since the "to" node has a key and there
588
+ // is no matching keyed node in the source tree
589
+ isCompatible = false;
590
+ }
591
+ }
592
+ } else if (curFromNodeKey) {
593
+ // The original has a key
594
+ isCompatible = false;
595
+ }
596
+
597
+ isCompatible = isCompatible !== false && compareNodeNames(curFromNodeChild, curToNodeChild);
598
+ if (isCompatible) {
599
+ // We found compatible DOM elements so transform
600
+ // the current "from" node to match the current
601
+ // target DOM node.
602
+ // MORPH
603
+ morphEl(curFromNodeChild, curToNodeChild);
604
+ }
605
+
606
+ } else if (curFromNodeType === TEXT_NODE || curFromNodeType == COMMENT_NODE) {
607
+ // Both nodes being compared are Text or Comment nodes
608
+ isCompatible = true;
609
+ // Simply update nodeValue on the original node to
610
+ // change the text value
611
+ if (curFromNodeChild.nodeValue !== curToNodeChild.nodeValue) {
612
+ curFromNodeChild.nodeValue = curToNodeChild.nodeValue;
613
+ }
614
+
615
+ }
616
+ }
617
+
618
+ if (isCompatible) {
619
+ // Advance both the "to" child and the "from" child since we found a match
620
+ // Nothing else to do as we already recursively called morphChildren above
621
+ curToNodeChild = toNextSibling;
622
+ curFromNodeChild = fromNextSibling;
623
+ continue outer;
624
+ }
625
+
626
+ // No compatible match so remove the old node from the DOM and continue trying to find a
627
+ // match in the original DOM. However, we only do this if the from node is not keyed
628
+ // since it is possible that a keyed node might match up with a node somewhere else in the
629
+ // target tree and we don't want to discard it just yet since it still might find a
630
+ // home in the final DOM tree. After everything is done we will remove any keyed nodes
631
+ // that didn't find a home
632
+ if (curFromNodeKey) {
633
+ // Since the node is keyed it might be matched up later so we defer
634
+ // the actual removal to later
635
+ addKeyedRemoval(curFromNodeKey);
636
+ } else {
637
+ // NOTE: we skip nested keyed nodes from being removed since there is
638
+ // still a chance they will be matched up later
639
+ removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);
640
+ }
641
+
642
+ curFromNodeChild = fromNextSibling;
643
+ } // END: while(curFromNodeChild) {}
644
+
645
+ // If we got this far then we did not find a candidate match for
646
+ // our "to node" and we exhausted all of the children "from"
647
+ // nodes. Therefore, we will just append the current "to" node
648
+ // to the end
649
+ if (curToNodeKey && (matchingFromEl = fromNodesLookup[curToNodeKey]) && compareNodeNames(matchingFromEl, curToNodeChild)) {
650
+ fromEl.appendChild(matchingFromEl);
651
+ // MORPH
652
+ morphEl(matchingFromEl, curToNodeChild);
653
+ } else {
654
+ var onBeforeNodeAddedResult = onBeforeNodeAdded(curToNodeChild);
655
+ if (onBeforeNodeAddedResult !== false) {
656
+ if (onBeforeNodeAddedResult) {
657
+ curToNodeChild = onBeforeNodeAddedResult;
658
+ }
659
+
660
+ if (curToNodeChild.actualize) {
661
+ curToNodeChild = curToNodeChild.actualize(fromEl.ownerDocument || doc);
662
+ }
663
+ fromEl.appendChild(curToNodeChild);
664
+ handleNodeAdded(curToNodeChild);
665
+ }
666
+ }
667
+
668
+ curToNodeChild = toNextSibling;
669
+ curFromNodeChild = fromNextSibling;
670
+ }
671
+
672
+ cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey);
673
+
674
+ var specialElHandler = specialElHandlers[fromEl.nodeName];
675
+ if (specialElHandler) {
676
+ specialElHandler(fromEl, toEl);
677
+ }
678
+ } // END: morphChildren(...)
679
+
680
+ var morphedNode = fromNode;
681
+ var morphedNodeType = morphedNode.nodeType;
682
+ var toNodeType = toNode.nodeType;
683
+
684
+ if (!childrenOnly) {
685
+ // Handle the case where we are given two DOM nodes that are not
686
+ // compatible (e.g. <div> --> <span> or <div> --> TEXT)
687
+ if (morphedNodeType === ELEMENT_NODE) {
688
+ if (toNodeType === ELEMENT_NODE) {
689
+ if (!compareNodeNames(fromNode, toNode)) {
690
+ onNodeDiscarded(fromNode);
691
+ morphedNode = moveChildren(fromNode, createElementNS(toNode.nodeName, toNode.namespaceURI));
692
+ }
693
+ } else {
694
+ // Going from an element node to a text node
695
+ morphedNode = toNode;
696
+ }
697
+ } else if (morphedNodeType === TEXT_NODE || morphedNodeType === COMMENT_NODE) { // Text or comment node
698
+ if (toNodeType === morphedNodeType) {
699
+ if (morphedNode.nodeValue !== toNode.nodeValue) {
700
+ morphedNode.nodeValue = toNode.nodeValue;
701
+ }
702
+
703
+ return morphedNode;
704
+ } else {
705
+ // Text node to something else
706
+ morphedNode = toNode;
707
+ }
708
+ }
709
+ }
710
+
711
+ if (morphedNode === toNode) {
712
+ // The "to node" was not compatible with the "from node" so we had to
713
+ // toss out the "from node" and use the "to node"
714
+ onNodeDiscarded(fromNode);
715
+ } else {
716
+ if (toNode.isSameNode && toNode.isSameNode(morphedNode)) {
717
+ return;
718
+ }
719
+
720
+ morphEl(morphedNode, toNode, childrenOnly);
721
+
722
+ // We now need to loop over any keyed nodes that might need to be
723
+ // removed. We only do the removal if we know that the keyed node
724
+ // never found a match. When a keyed node is matched up we remove
725
+ // it out of fromNodesLookup and we use fromNodesLookup to determine
726
+ // if a keyed node has been matched up or not
727
+ if (keyedRemovalList) {
728
+ for (var i=0, len=keyedRemovalList.length; i<len; i++) {
729
+ var elToRemove = fromNodesLookup[keyedRemovalList[i]];
730
+ if (elToRemove) {
731
+ removeNode(elToRemove, elToRemove.parentNode, false);
732
+ }
733
+ }
734
+ }
735
+ }
736
+
737
+ if (!childrenOnly && morphedNode !== fromNode && fromNode.parentNode) {
738
+ if (morphedNode.actualize) {
739
+ morphedNode = morphedNode.actualize(fromNode.ownerDocument || doc);
740
+ }
741
+ // If we had to swap out the from node with a new node because the old
742
+ // node was not compatible with the target node then we need to
743
+ // replace the old DOM node in the original DOM tree. This is only
744
+ // possible if the original DOM node was part of a DOM tree which
745
+ // we know is the case if it has a parent node.
746
+ fromNode.parentNode.replaceChild(morphedNode, fromNode);
747
+ }
748
+
749
+ return morphedNode;
750
+ };
751
+ }
752
+
753
+ var morphdom = morphdomFactory(morphAttrs);
754
+
755
+ export default morphdom;