clapton 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,1592 +0,0 @@
1
- var Clapton = (function (exports) {
2
- 'use strict';
3
-
4
- const htmlAttributes = (params) => {
5
- const customDataAttributes = params.data || {};
6
- const others = Object.keys(params).filter(key => key !== "data");
7
- const flattenDataAttributes = (data, prefix = "data") => {
8
- return Object.keys(data).reduce((acc, key) => {
9
- const value = data[key];
10
- if (typeof value === "object" && value !== null) {
11
- acc.push(...flattenDataAttributes(value, `${prefix}-${key}`));
12
- }
13
- else {
14
- acc.push(`${prefix}-${key}='${escapeHtml(value)}'`);
15
- }
16
- return acc;
17
- }, []);
18
- };
19
- return [
20
- others.map(key => {
21
- if (key === "disabled") {
22
- if (params[key] === false) {
23
- return "";
24
- }
25
- else {
26
- return `${key}`;
27
- }
28
- }
29
- return `${key}='${escapeHtml(params[key])}'`;
30
- }).join(" "),
31
- flattenDataAttributes(customDataAttributes).join(" ")
32
- ].filter(Boolean).join(" ");
33
- };
34
- const escapeHtml = (unsafe) => {
35
- if (typeof unsafe !== "string") {
36
- return "";
37
- }
38
- return unsafe
39
- .replace(/&/g, "&")
40
- .replace(/</g, "&lt;")
41
- .replace(/>/g, "&gt;")
42
- .replace(/"/g, "&quot;")
43
- .replace(/'/g, "&#039;");
44
- };
45
-
46
- class Box {
47
- constructor(attributes = {}) {
48
- this.children = [];
49
- this.attributes = attributes;
50
- }
51
- add(child) {
52
- this.children.push(child);
53
- return this;
54
- }
55
- get render() {
56
- return `<div ${htmlAttributes(this.attributes)}>${this.children.map(child => child.render).join("")}</div>`;
57
- }
58
- }
59
-
60
- class Component {
61
- constructor(state = {}, id = Math.random().toString(36).substring(2, 10), errors = []) {
62
- this._state = state;
63
- this.id = id;
64
- this._errors = errors;
65
- this._root = new Box({ data: { component: this.constructor.name, state: JSON.stringify(this._state), id: this.id, errors: this._errors } });
66
- }
67
- get render() {
68
- return this._root.render;
69
- }
70
- }
71
-
72
- class Text {
73
- constructor(value) {
74
- this.value = value;
75
- }
76
- get render() {
77
- return this.value;
78
- }
79
- }
80
-
81
- class TextField {
82
- constructor(state, attribute, attributes = {}) {
83
- this.state = state;
84
- this.attributes = attributes;
85
- this.attribute = attribute;
86
- this.attributes["data-attribute"] = attribute;
87
- }
88
- get render() {
89
- return `<input type='text' ${htmlAttributes(this.attributes)} value='${this.state[this.attribute] || ""}'/>`;
90
- }
91
- add_action(event, klass, fn, options = {}) {
92
- this.attributes["data-action"] = `${this.attributes["data-action"] || ""} ${event}->${klass}#${fn}@${options.debounce || 0}`;
93
- return this;
94
- }
95
- }
96
-
97
- class Link {
98
- constructor(attributes = {}) {
99
- this.children = [];
100
- this.attributes = attributes;
101
- }
102
- get render() {
103
- return `<a ${htmlAttributes(this.attributes)}>${this.children.map(child => child.render).join("")}</a>`;
104
- }
105
- add(child) {
106
- this.children.push(child);
107
- return this;
108
- }
109
- }
110
-
111
- class Button {
112
- constructor(attributes = {}) {
113
- this.attributes = attributes;
114
- this.children = [];
115
- }
116
- add(child) {
117
- this.children.push(child);
118
- return this;
119
- }
120
- get render() {
121
- return `<button ${htmlAttributes(this.attributes)}>${this.children.map(child => child.render).join("")}</button>`;
122
- }
123
- add_action(event, klass, fn, options = {}) {
124
- this.attributes["data-action"] = `${this.attributes["data-action"] || ""} ${event}->${klass}#${fn}@${options.debounce || 0}`;
125
- return this;
126
- }
127
- }
128
-
129
- class DateTimeField {
130
- constructor(state, attribute, attributes = {}) {
131
- this.attributes = {};
132
- this.state = state;
133
- this.attribute = attribute;
134
- this.attributes = attributes;
135
- this.attributes["data-attribute"] = attribute;
136
- }
137
- get render() {
138
- const value = this.state[this.attribute] ? this.datetime_local_value(this.state[this.attribute]) : "";
139
- return `<input type='datetime-local' ${htmlAttributes(this.attributes)} value='${value}'/>`;
140
- }
141
- add_action(event, klass, fn, options = {}) {
142
- this.attributes["data-action"] = `${this.attributes["data-action"] || ""} ${event}->${klass}#${fn}@${options.debounce || 0}`;
143
- return this;
144
- }
145
- datetime_local_value(value) {
146
- if (!value) {
147
- return "";
148
- }
149
- const date = new Date(value);
150
- date.setMinutes(date.getMinutes() - date.getTimezoneOffset());
151
- let month = date.getMonth() + 1;
152
- let day = date.getDate();
153
- let hours = date.getHours();
154
- let minutes = date.getMinutes();
155
- if (month < 10) {
156
- month = `0${month}`;
157
- }
158
- if (day < 10) {
159
- day = `0${day}`;
160
- }
161
- if (hours < 10) {
162
- hours = `0${hours}`;
163
- }
164
- if (minutes < 10) {
165
- minutes = `0${minutes}`;
166
- }
167
- return `${date.getFullYear()}-${month}-${day}T${hours}:${minutes}`;
168
- }
169
- }
170
-
171
- const splitActionAttribute = (action) => {
172
- const match = action.match(/^(.+)->(.+)#(.+)@(\d+)$/);
173
- const componentName = match?.[2].replace("State", "Component");
174
- if (!match)
175
- return { eventType: "", componentName: "", stateName: "", fnName: "", bounceTime: 0 };
176
- return { eventType: match[1], componentName: componentName, stateName: match[2], fnName: match[3], bounceTime: parseInt(match[4] || "0") };
177
- };
178
-
179
- var DOCUMENT_FRAGMENT_NODE = 11;
180
-
181
- function morphAttrs(fromNode, toNode) {
182
- var toNodeAttrs = toNode.attributes;
183
- var attr;
184
- var attrName;
185
- var attrNamespaceURI;
186
- var attrValue;
187
- var fromValue;
188
-
189
- // document-fragments dont have attributes so lets not do anything
190
- if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE || fromNode.nodeType === DOCUMENT_FRAGMENT_NODE) {
191
- return;
192
- }
193
-
194
- // update attributes on original DOM element
195
- for (var i = toNodeAttrs.length - 1; i >= 0; i--) {
196
- attr = toNodeAttrs[i];
197
- attrName = attr.name;
198
- attrNamespaceURI = attr.namespaceURI;
199
- attrValue = attr.value;
200
-
201
- if (attrNamespaceURI) {
202
- attrName = attr.localName || attrName;
203
- fromValue = fromNode.getAttributeNS(attrNamespaceURI, attrName);
204
-
205
- if (fromValue !== attrValue) {
206
- if (attr.prefix === 'xmlns'){
207
- attrName = attr.name; // It's not allowed to set an attribute with the XMLNS namespace without specifying the `xmlns` prefix
208
- }
209
- fromNode.setAttributeNS(attrNamespaceURI, attrName, attrValue);
210
- }
211
- } else {
212
- fromValue = fromNode.getAttribute(attrName);
213
-
214
- if (fromValue !== attrValue) {
215
- fromNode.setAttribute(attrName, attrValue);
216
- }
217
- }
218
- }
219
-
220
- // Remove any extra attributes found on the original DOM element that
221
- // weren't found on the target element.
222
- var fromNodeAttrs = fromNode.attributes;
223
-
224
- for (var d = fromNodeAttrs.length - 1; d >= 0; d--) {
225
- attr = fromNodeAttrs[d];
226
- attrName = attr.name;
227
- attrNamespaceURI = attr.namespaceURI;
228
-
229
- if (attrNamespaceURI) {
230
- attrName = attr.localName || attrName;
231
-
232
- if (!toNode.hasAttributeNS(attrNamespaceURI, attrName)) {
233
- fromNode.removeAttributeNS(attrNamespaceURI, attrName);
234
- }
235
- } else {
236
- if (!toNode.hasAttribute(attrName)) {
237
- fromNode.removeAttribute(attrName);
238
- }
239
- }
240
- }
241
- }
242
-
243
- var range; // Create a range object for efficently rendering strings to elements.
244
- var NS_XHTML = 'http://www.w3.org/1999/xhtml';
245
-
246
- var doc = typeof document === 'undefined' ? undefined : document;
247
- var HAS_TEMPLATE_SUPPORT = !!doc && 'content' in doc.createElement('template');
248
- var HAS_RANGE_SUPPORT = !!doc && doc.createRange && 'createContextualFragment' in doc.createRange();
249
-
250
- function createFragmentFromTemplate(str) {
251
- var template = doc.createElement('template');
252
- template.innerHTML = str;
253
- return template.content.childNodes[0];
254
- }
255
-
256
- function createFragmentFromRange(str) {
257
- if (!range) {
258
- range = doc.createRange();
259
- range.selectNode(doc.body);
260
- }
261
-
262
- var fragment = range.createContextualFragment(str);
263
- return fragment.childNodes[0];
264
- }
265
-
266
- function createFragmentFromWrap(str) {
267
- var fragment = doc.createElement('body');
268
- fragment.innerHTML = str;
269
- return fragment.childNodes[0];
270
- }
271
-
272
- /**
273
- * This is about the same
274
- * var html = new DOMParser().parseFromString(str, 'text/html');
275
- * return html.body.firstChild;
276
- *
277
- * @method toElement
278
- * @param {String} str
279
- */
280
- function toElement(str) {
281
- str = str.trim();
282
- if (HAS_TEMPLATE_SUPPORT) {
283
- // avoid restrictions on content for things like `<tr><th>Hi</th></tr>` which
284
- // createContextualFragment doesn't support
285
- // <template> support not available in IE
286
- return createFragmentFromTemplate(str);
287
- } else if (HAS_RANGE_SUPPORT) {
288
- return createFragmentFromRange(str);
289
- }
290
-
291
- return createFragmentFromWrap(str);
292
- }
293
-
294
- /**
295
- * Returns true if two node's names are the same.
296
- *
297
- * NOTE: We don't bother checking `namespaceURI` because you will never find two HTML elements with the same
298
- * nodeName and different namespace URIs.
299
- *
300
- * @param {Element} a
301
- * @param {Element} b The target element
302
- * @return {boolean}
303
- */
304
- function compareNodeNames(fromEl, toEl) {
305
- var fromNodeName = fromEl.nodeName;
306
- var toNodeName = toEl.nodeName;
307
- var fromCodeStart, toCodeStart;
308
-
309
- if (fromNodeName === toNodeName) {
310
- return true;
311
- }
312
-
313
- fromCodeStart = fromNodeName.charCodeAt(0);
314
- toCodeStart = toNodeName.charCodeAt(0);
315
-
316
- // If the target element is a virtual DOM node or SVG node then we may
317
- // need to normalize the tag name before comparing. Normal HTML elements that are
318
- // in the "http://www.w3.org/1999/xhtml"
319
- // are converted to upper case
320
- if (fromCodeStart <= 90 && toCodeStart >= 97) { // from is upper and to is lower
321
- return fromNodeName === toNodeName.toUpperCase();
322
- } else if (toCodeStart <= 90 && fromCodeStart >= 97) { // to is upper and from is lower
323
- return toNodeName === fromNodeName.toUpperCase();
324
- } else {
325
- return false;
326
- }
327
- }
328
-
329
- /**
330
- * Create an element, optionally with a known namespace URI.
331
- *
332
- * @param {string} name the element name, e.g. 'div' or 'svg'
333
- * @param {string} [namespaceURI] the element's namespace URI, i.e. the value of
334
- * its `xmlns` attribute or its inferred namespace.
335
- *
336
- * @return {Element}
337
- */
338
- function createElementNS(name, namespaceURI) {
339
- return !namespaceURI || namespaceURI === NS_XHTML ?
340
- doc.createElement(name) :
341
- doc.createElementNS(namespaceURI, name);
342
- }
343
-
344
- /**
345
- * Copies the children of one DOM element to another DOM element
346
- */
347
- function moveChildren(fromEl, toEl) {
348
- var curChild = fromEl.firstChild;
349
- while (curChild) {
350
- var nextChild = curChild.nextSibling;
351
- toEl.appendChild(curChild);
352
- curChild = nextChild;
353
- }
354
- return toEl;
355
- }
356
-
357
- function syncBooleanAttrProp(fromEl, toEl, name) {
358
- if (fromEl[name] !== toEl[name]) {
359
- fromEl[name] = toEl[name];
360
- if (fromEl[name]) {
361
- fromEl.setAttribute(name, '');
362
- } else {
363
- fromEl.removeAttribute(name);
364
- }
365
- }
366
- }
367
-
368
- var specialElHandlers = {
369
- OPTION: function(fromEl, toEl) {
370
- var parentNode = fromEl.parentNode;
371
- if (parentNode) {
372
- var parentName = parentNode.nodeName.toUpperCase();
373
- if (parentName === 'OPTGROUP') {
374
- parentNode = parentNode.parentNode;
375
- parentName = parentNode && parentNode.nodeName.toUpperCase();
376
- }
377
- if (parentName === 'SELECT' && !parentNode.hasAttribute('multiple')) {
378
- if (fromEl.hasAttribute('selected') && !toEl.selected) {
379
- // Workaround for MS Edge bug where the 'selected' attribute can only be
380
- // removed if set to a non-empty value:
381
- // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12087679/
382
- fromEl.setAttribute('selected', 'selected');
383
- fromEl.removeAttribute('selected');
384
- }
385
- // We have to reset select element's selectedIndex to -1, otherwise setting
386
- // fromEl.selected using the syncBooleanAttrProp below has no effect.
387
- // The correct selectedIndex will be set in the SELECT special handler below.
388
- parentNode.selectedIndex = -1;
389
- }
390
- }
391
- syncBooleanAttrProp(fromEl, toEl, 'selected');
392
- },
393
- /**
394
- * The "value" attribute is special for the <input> element since it sets
395
- * the initial value. Changing the "value" attribute without changing the
396
- * "value" property will have no effect since it is only used to the set the
397
- * initial value. Similar for the "checked" attribute, and "disabled".
398
- */
399
- INPUT: function(fromEl, toEl) {
400
- syncBooleanAttrProp(fromEl, toEl, 'checked');
401
- syncBooleanAttrProp(fromEl, toEl, 'disabled');
402
-
403
- if (fromEl.value !== toEl.value) {
404
- fromEl.value = toEl.value;
405
- }
406
-
407
- if (!toEl.hasAttribute('value')) {
408
- fromEl.removeAttribute('value');
409
- }
410
- },
411
-
412
- TEXTAREA: function(fromEl, toEl) {
413
- var newValue = toEl.value;
414
- if (fromEl.value !== newValue) {
415
- fromEl.value = newValue;
416
- }
417
-
418
- var firstChild = fromEl.firstChild;
419
- if (firstChild) {
420
- // Needed for IE. Apparently IE sets the placeholder as the
421
- // node value and vise versa. This ignores an empty update.
422
- var oldValue = firstChild.nodeValue;
423
-
424
- if (oldValue == newValue || (!newValue && oldValue == fromEl.placeholder)) {
425
- return;
426
- }
427
-
428
- firstChild.nodeValue = newValue;
429
- }
430
- },
431
- SELECT: function(fromEl, toEl) {
432
- if (!toEl.hasAttribute('multiple')) {
433
- var selectedIndex = -1;
434
- var i = 0;
435
- // We have to loop through children of fromEl, not toEl since nodes can be moved
436
- // from toEl to fromEl directly when morphing.
437
- // At the time this special handler is invoked, all children have already been morphed
438
- // and appended to / removed from fromEl, so using fromEl here is safe and correct.
439
- var curChild = fromEl.firstChild;
440
- var optgroup;
441
- var nodeName;
442
- while(curChild) {
443
- nodeName = curChild.nodeName && curChild.nodeName.toUpperCase();
444
- if (nodeName === 'OPTGROUP') {
445
- optgroup = curChild;
446
- curChild = optgroup.firstChild;
447
- } else {
448
- if (nodeName === 'OPTION') {
449
- if (curChild.hasAttribute('selected')) {
450
- selectedIndex = i;
451
- break;
452
- }
453
- i++;
454
- }
455
- curChild = curChild.nextSibling;
456
- if (!curChild && optgroup) {
457
- curChild = optgroup.nextSibling;
458
- optgroup = null;
459
- }
460
- }
461
- }
462
-
463
- fromEl.selectedIndex = selectedIndex;
464
- }
465
- }
466
- };
467
-
468
- var ELEMENT_NODE = 1;
469
- var DOCUMENT_FRAGMENT_NODE$1 = 11;
470
- var TEXT_NODE = 3;
471
- var COMMENT_NODE = 8;
472
-
473
- function noop() {}
474
-
475
- function defaultGetNodeKey(node) {
476
- if (node) {
477
- return (node.getAttribute && node.getAttribute('id')) || node.id;
478
- }
479
- }
480
-
481
- function morphdomFactory(morphAttrs) {
482
-
483
- return function morphdom(fromNode, toNode, options) {
484
- if (!options) {
485
- options = {};
486
- }
487
-
488
- if (typeof toNode === 'string') {
489
- if (fromNode.nodeName === '#document' || fromNode.nodeName === 'HTML' || fromNode.nodeName === 'BODY') {
490
- var toNodeHtml = toNode;
491
- toNode = doc.createElement('html');
492
- toNode.innerHTML = toNodeHtml;
493
- } else {
494
- toNode = toElement(toNode);
495
- }
496
- } else if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE$1) {
497
- toNode = toNode.firstElementChild;
498
- }
499
-
500
- var getNodeKey = options.getNodeKey || defaultGetNodeKey;
501
- var onBeforeNodeAdded = options.onBeforeNodeAdded || noop;
502
- var onNodeAdded = options.onNodeAdded || noop;
503
- var onBeforeElUpdated = options.onBeforeElUpdated || noop;
504
- var onElUpdated = options.onElUpdated || noop;
505
- var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop;
506
- var onNodeDiscarded = options.onNodeDiscarded || noop;
507
- var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || noop;
508
- var skipFromChildren = options.skipFromChildren || noop;
509
- var addChild = options.addChild || function(parent, child){ return parent.appendChild(child); };
510
- var childrenOnly = options.childrenOnly === true;
511
-
512
- // This object is used as a lookup to quickly find all keyed elements in the original DOM tree.
513
- var fromNodesLookup = Object.create(null);
514
- var keyedRemovalList = [];
515
-
516
- function addKeyedRemoval(key) {
517
- keyedRemovalList.push(key);
518
- }
519
-
520
- function walkDiscardedChildNodes(node, skipKeyedNodes) {
521
- if (node.nodeType === ELEMENT_NODE) {
522
- var curChild = node.firstChild;
523
- while (curChild) {
524
-
525
- var key = undefined;
526
-
527
- if (skipKeyedNodes && (key = getNodeKey(curChild))) {
528
- // If we are skipping keyed nodes then we add the key
529
- // to a list so that it can be handled at the very end.
530
- addKeyedRemoval(key);
531
- } else {
532
- // Only report the node as discarded if it is not keyed. We do this because
533
- // at the end we loop through all keyed elements that were unmatched
534
- // and then discard them in one final pass.
535
- onNodeDiscarded(curChild);
536
- if (curChild.firstChild) {
537
- walkDiscardedChildNodes(curChild, skipKeyedNodes);
538
- }
539
- }
540
-
541
- curChild = curChild.nextSibling;
542
- }
543
- }
544
- }
545
-
546
- /**
547
- * Removes a DOM node out of the original DOM
548
- *
549
- * @param {Node} node The node to remove
550
- * @param {Node} parentNode The nodes parent
551
- * @param {Boolean} skipKeyedNodes If true then elements with keys will be skipped and not discarded.
552
- * @return {undefined}
553
- */
554
- function removeNode(node, parentNode, skipKeyedNodes) {
555
- if (onBeforeNodeDiscarded(node) === false) {
556
- return;
557
- }
558
-
559
- if (parentNode) {
560
- parentNode.removeChild(node);
561
- }
562
-
563
- onNodeDiscarded(node);
564
- walkDiscardedChildNodes(node, skipKeyedNodes);
565
- }
566
-
567
- // // TreeWalker implementation is no faster, but keeping this around in case this changes in the future
568
- // function indexTree(root) {
569
- // var treeWalker = document.createTreeWalker(
570
- // root,
571
- // NodeFilter.SHOW_ELEMENT);
572
- //
573
- // var el;
574
- // while((el = treeWalker.nextNode())) {
575
- // var key = getNodeKey(el);
576
- // if (key) {
577
- // fromNodesLookup[key] = el;
578
- // }
579
- // }
580
- // }
581
-
582
- // // NodeIterator implementation is no faster, but keeping this around in case this changes in the future
583
- //
584
- // function indexTree(node) {
585
- // var nodeIterator = document.createNodeIterator(node, NodeFilter.SHOW_ELEMENT);
586
- // var el;
587
- // while((el = nodeIterator.nextNode())) {
588
- // var key = getNodeKey(el);
589
- // if (key) {
590
- // fromNodesLookup[key] = el;
591
- // }
592
- // }
593
- // }
594
-
595
- function indexTree(node) {
596
- if (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE$1) {
597
- var curChild = node.firstChild;
598
- while (curChild) {
599
- var key = getNodeKey(curChild);
600
- if (key) {
601
- fromNodesLookup[key] = curChild;
602
- }
603
-
604
- // Walk recursively
605
- indexTree(curChild);
606
-
607
- curChild = curChild.nextSibling;
608
- }
609
- }
610
- }
611
-
612
- indexTree(fromNode);
613
-
614
- function handleNodeAdded(el) {
615
- onNodeAdded(el);
616
-
617
- var curChild = el.firstChild;
618
- while (curChild) {
619
- var nextSibling = curChild.nextSibling;
620
-
621
- var key = getNodeKey(curChild);
622
- if (key) {
623
- var unmatchedFromEl = fromNodesLookup[key];
624
- // if we find a duplicate #id node in cache, replace `el` with cache value
625
- // and morph it to the child node.
626
- if (unmatchedFromEl && compareNodeNames(curChild, unmatchedFromEl)) {
627
- curChild.parentNode.replaceChild(unmatchedFromEl, curChild);
628
- morphEl(unmatchedFromEl, curChild);
629
- } else {
630
- handleNodeAdded(curChild);
631
- }
632
- } else {
633
- // recursively call for curChild and it's children to see if we find something in
634
- // fromNodesLookup
635
- handleNodeAdded(curChild);
636
- }
637
-
638
- curChild = nextSibling;
639
- }
640
- }
641
-
642
- function cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey) {
643
- // We have processed all of the "to nodes". If curFromNodeChild is
644
- // non-null then we still have some from nodes left over that need
645
- // to be removed
646
- while (curFromNodeChild) {
647
- var fromNextSibling = curFromNodeChild.nextSibling;
648
- if ((curFromNodeKey = getNodeKey(curFromNodeChild))) {
649
- // Since the node is keyed it might be matched up later so we defer
650
- // the actual removal to later
651
- addKeyedRemoval(curFromNodeKey);
652
- } else {
653
- // NOTE: we skip nested keyed nodes from being removed since there is
654
- // still a chance they will be matched up later
655
- removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);
656
- }
657
- curFromNodeChild = fromNextSibling;
658
- }
659
- }
660
-
661
- function morphEl(fromEl, toEl, childrenOnly) {
662
- var toElKey = getNodeKey(toEl);
663
-
664
- if (toElKey) {
665
- // If an element with an ID is being morphed then it will be in the final
666
- // DOM so clear it out of the saved elements collection
667
- delete fromNodesLookup[toElKey];
668
- }
669
-
670
- if (!childrenOnly) {
671
- // optional
672
- var beforeUpdateResult = onBeforeElUpdated(fromEl, toEl);
673
- if (beforeUpdateResult === false) {
674
- return;
675
- } else if (beforeUpdateResult instanceof HTMLElement) {
676
- fromEl = beforeUpdateResult;
677
- // reindex the new fromEl in case it's not in the same
678
- // tree as the original fromEl
679
- // (Phoenix LiveView sometimes returns a cloned tree,
680
- // but keyed lookups would still point to the original tree)
681
- indexTree(fromEl);
682
- }
683
-
684
- // update attributes on original DOM element first
685
- morphAttrs(fromEl, toEl);
686
- // optional
687
- onElUpdated(fromEl);
688
-
689
- if (onBeforeElChildrenUpdated(fromEl, toEl) === false) {
690
- return;
691
- }
692
- }
693
-
694
- if (fromEl.nodeName !== 'TEXTAREA') {
695
- morphChildren(fromEl, toEl);
696
- } else {
697
- specialElHandlers.TEXTAREA(fromEl, toEl);
698
- }
699
- }
700
-
701
- function morphChildren(fromEl, toEl) {
702
- var skipFrom = skipFromChildren(fromEl, toEl);
703
- var curToNodeChild = toEl.firstChild;
704
- var curFromNodeChild = fromEl.firstChild;
705
- var curToNodeKey;
706
- var curFromNodeKey;
707
-
708
- var fromNextSibling;
709
- var toNextSibling;
710
- var matchingFromEl;
711
-
712
- // walk the children
713
- outer: while (curToNodeChild) {
714
- toNextSibling = curToNodeChild.nextSibling;
715
- curToNodeKey = getNodeKey(curToNodeChild);
716
-
717
- // walk the fromNode children all the way through
718
- while (!skipFrom && curFromNodeChild) {
719
- fromNextSibling = curFromNodeChild.nextSibling;
720
-
721
- if (curToNodeChild.isSameNode && curToNodeChild.isSameNode(curFromNodeChild)) {
722
- curToNodeChild = toNextSibling;
723
- curFromNodeChild = fromNextSibling;
724
- continue outer;
725
- }
726
-
727
- curFromNodeKey = getNodeKey(curFromNodeChild);
728
-
729
- var curFromNodeType = curFromNodeChild.nodeType;
730
-
731
- // this means if the curFromNodeChild doesnt have a match with the curToNodeChild
732
- var isCompatible = undefined;
733
-
734
- if (curFromNodeType === curToNodeChild.nodeType) {
735
- if (curFromNodeType === ELEMENT_NODE) {
736
- // Both nodes being compared are Element nodes
737
-
738
- if (curToNodeKey) {
739
- // The target node has a key so we want to match it up with the correct element
740
- // in the original DOM tree
741
- if (curToNodeKey !== curFromNodeKey) {
742
- // The current element in the original DOM tree does not have a matching key so
743
- // let's check our lookup to see if there is a matching element in the original
744
- // DOM tree
745
- if ((matchingFromEl = fromNodesLookup[curToNodeKey])) {
746
- if (fromNextSibling === matchingFromEl) {
747
- // Special case for single element removals. To avoid removing the original
748
- // DOM node out of the tree (since that can break CSS transitions, etc.),
749
- // we will instead discard the current node and wait until the next
750
- // iteration to properly match up the keyed target element with its matching
751
- // element in the original tree
752
- isCompatible = false;
753
- } else {
754
- // We found a matching keyed element somewhere in the original DOM tree.
755
- // Let's move the original DOM node into the current position and morph
756
- // it.
757
-
758
- // NOTE: We use insertBefore instead of replaceChild because we want to go through
759
- // the `removeNode()` function for the node that is being discarded so that
760
- // all lifecycle hooks are correctly invoked
761
- fromEl.insertBefore(matchingFromEl, curFromNodeChild);
762
-
763
- // fromNextSibling = curFromNodeChild.nextSibling;
764
-
765
- if (curFromNodeKey) {
766
- // Since the node is keyed it might be matched up later so we defer
767
- // the actual removal to later
768
- addKeyedRemoval(curFromNodeKey);
769
- } else {
770
- // NOTE: we skip nested keyed nodes from being removed since there is
771
- // still a chance they will be matched up later
772
- removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);
773
- }
774
-
775
- curFromNodeChild = matchingFromEl;
776
- curFromNodeKey = getNodeKey(curFromNodeChild);
777
- }
778
- } else {
779
- // The nodes are not compatible since the "to" node has a key and there
780
- // is no matching keyed node in the source tree
781
- isCompatible = false;
782
- }
783
- }
784
- } else if (curFromNodeKey) {
785
- // The original has a key
786
- isCompatible = false;
787
- }
788
-
789
- isCompatible = isCompatible !== false && compareNodeNames(curFromNodeChild, curToNodeChild);
790
- if (isCompatible) {
791
- // We found compatible DOM elements so transform
792
- // the current "from" node to match the current
793
- // target DOM node.
794
- // MORPH
795
- morphEl(curFromNodeChild, curToNodeChild);
796
- }
797
-
798
- } else if (curFromNodeType === TEXT_NODE || curFromNodeType == COMMENT_NODE) {
799
- // Both nodes being compared are Text or Comment nodes
800
- isCompatible = true;
801
- // Simply update nodeValue on the original node to
802
- // change the text value
803
- if (curFromNodeChild.nodeValue !== curToNodeChild.nodeValue) {
804
- curFromNodeChild.nodeValue = curToNodeChild.nodeValue;
805
- }
806
-
807
- }
808
- }
809
-
810
- if (isCompatible) {
811
- // Advance both the "to" child and the "from" child since we found a match
812
- // Nothing else to do as we already recursively called morphChildren above
813
- curToNodeChild = toNextSibling;
814
- curFromNodeChild = fromNextSibling;
815
- continue outer;
816
- }
817
-
818
- // No compatible match so remove the old node from the DOM and continue trying to find a
819
- // match in the original DOM. However, we only do this if the from node is not keyed
820
- // since it is possible that a keyed node might match up with a node somewhere else in the
821
- // target tree and we don't want to discard it just yet since it still might find a
822
- // home in the final DOM tree. After everything is done we will remove any keyed nodes
823
- // that didn't find a home
824
- if (curFromNodeKey) {
825
- // Since the node is keyed it might be matched up later so we defer
826
- // the actual removal to later
827
- addKeyedRemoval(curFromNodeKey);
828
- } else {
829
- // NOTE: we skip nested keyed nodes from being removed since there is
830
- // still a chance they will be matched up later
831
- removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);
832
- }
833
-
834
- curFromNodeChild = fromNextSibling;
835
- } // END: while(curFromNodeChild) {}
836
-
837
- // If we got this far then we did not find a candidate match for
838
- // our "to node" and we exhausted all of the children "from"
839
- // nodes. Therefore, we will just append the current "to" node
840
- // to the end
841
- if (curToNodeKey && (matchingFromEl = fromNodesLookup[curToNodeKey]) && compareNodeNames(matchingFromEl, curToNodeChild)) {
842
- // MORPH
843
- if(!skipFrom){ addChild(fromEl, matchingFromEl); }
844
- morphEl(matchingFromEl, curToNodeChild);
845
- } else {
846
- var onBeforeNodeAddedResult = onBeforeNodeAdded(curToNodeChild);
847
- if (onBeforeNodeAddedResult !== false) {
848
- if (onBeforeNodeAddedResult) {
849
- curToNodeChild = onBeforeNodeAddedResult;
850
- }
851
-
852
- if (curToNodeChild.actualize) {
853
- curToNodeChild = curToNodeChild.actualize(fromEl.ownerDocument || doc);
854
- }
855
- addChild(fromEl, curToNodeChild);
856
- handleNodeAdded(curToNodeChild);
857
- }
858
- }
859
-
860
- curToNodeChild = toNextSibling;
861
- curFromNodeChild = fromNextSibling;
862
- }
863
-
864
- cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey);
865
-
866
- var specialElHandler = specialElHandlers[fromEl.nodeName];
867
- if (specialElHandler) {
868
- specialElHandler(fromEl, toEl);
869
- }
870
- } // END: morphChildren(...)
871
-
872
- var morphedNode = fromNode;
873
- var morphedNodeType = morphedNode.nodeType;
874
- var toNodeType = toNode.nodeType;
875
-
876
- if (!childrenOnly) {
877
- // Handle the case where we are given two DOM nodes that are not
878
- // compatible (e.g. <div> --> <span> or <div> --> TEXT)
879
- if (morphedNodeType === ELEMENT_NODE) {
880
- if (toNodeType === ELEMENT_NODE) {
881
- if (!compareNodeNames(fromNode, toNode)) {
882
- onNodeDiscarded(fromNode);
883
- morphedNode = moveChildren(fromNode, createElementNS(toNode.nodeName, toNode.namespaceURI));
884
- }
885
- } else {
886
- // Going from an element node to a text node
887
- morphedNode = toNode;
888
- }
889
- } else if (morphedNodeType === TEXT_NODE || morphedNodeType === COMMENT_NODE) { // Text or comment node
890
- if (toNodeType === morphedNodeType) {
891
- if (morphedNode.nodeValue !== toNode.nodeValue) {
892
- morphedNode.nodeValue = toNode.nodeValue;
893
- }
894
-
895
- return morphedNode;
896
- } else {
897
- // Text node to something else
898
- morphedNode = toNode;
899
- }
900
- }
901
- }
902
-
903
- if (morphedNode === toNode) {
904
- // The "to node" was not compatible with the "from node" so we had to
905
- // toss out the "from node" and use the "to node"
906
- onNodeDiscarded(fromNode);
907
- } else {
908
- if (toNode.isSameNode && toNode.isSameNode(morphedNode)) {
909
- return;
910
- }
911
-
912
- morphEl(morphedNode, toNode, childrenOnly);
913
-
914
- // We now need to loop over any keyed nodes that might need to be
915
- // removed. We only do the removal if we know that the keyed node
916
- // never found a match. When a keyed node is matched up we remove
917
- // it out of fromNodesLookup and we use fromNodesLookup to determine
918
- // if a keyed node has been matched up or not
919
- if (keyedRemovalList) {
920
- for (var i=0, len=keyedRemovalList.length; i<len; i++) {
921
- var elToRemove = fromNodesLookup[keyedRemovalList[i]];
922
- if (elToRemove) {
923
- removeNode(elToRemove, elToRemove.parentNode, false);
924
- }
925
- }
926
- }
927
- }
928
-
929
- if (!childrenOnly && morphedNode !== fromNode && fromNode.parentNode) {
930
- if (morphedNode.actualize) {
931
- morphedNode = morphedNode.actualize(fromNode.ownerDocument || doc);
932
- }
933
- // If we had to swap out the from node with a new node because the old
934
- // node was not compatible with the target node then we need to
935
- // replace the old DOM node in the original DOM tree. This is only
936
- // possible if the original DOM node was part of a DOM tree which
937
- // we know is the case if it has a parent node.
938
- fromNode.parentNode.replaceChild(morphedNode, fromNode);
939
- }
940
-
941
- return morphedNode;
942
- };
943
- }
944
-
945
- var morphdom = morphdomFactory(morphAttrs);
946
-
947
- const updateComponent = (component, state, property, target) => {
948
- state[property] = target.value;
949
- component.setAttribute("data-state", JSON.stringify(state));
950
- const componentName = component.getAttribute("data-component");
951
- const ComponentClass = window[componentName];
952
- const instance = new ComponentClass(state, component.dataset.id);
953
- morphdom(component, instance.render);
954
- };
955
-
956
- var adapters = {
957
- logger: typeof console !== "undefined" ? console : undefined,
958
- WebSocket: typeof WebSocket !== "undefined" ? WebSocket : undefined
959
- };
960
-
961
- var logger = {
962
- log(...messages) {
963
- if (this.enabled) {
964
- messages.push(Date.now());
965
- adapters.logger.log("[ActionCable]", ...messages);
966
- }
967
- }
968
- };
969
-
970
- const now = () => (new Date).getTime();
971
-
972
- const secondsSince = time => (now() - time) / 1e3;
973
-
974
- class ConnectionMonitor {
975
- constructor(connection) {
976
- this.visibilityDidChange = this.visibilityDidChange.bind(this);
977
- this.connection = connection;
978
- this.reconnectAttempts = 0;
979
- }
980
- start() {
981
- if (!this.isRunning()) {
982
- this.startedAt = now();
983
- delete this.stoppedAt;
984
- this.startPolling();
985
- addEventListener("visibilitychange", this.visibilityDidChange);
986
- logger.log(`ConnectionMonitor started. stale threshold = ${this.constructor.staleThreshold} s`);
987
- }
988
- }
989
- stop() {
990
- if (this.isRunning()) {
991
- this.stoppedAt = now();
992
- this.stopPolling();
993
- removeEventListener("visibilitychange", this.visibilityDidChange);
994
- logger.log("ConnectionMonitor stopped");
995
- }
996
- }
997
- isRunning() {
998
- return this.startedAt && !this.stoppedAt;
999
- }
1000
- recordMessage() {
1001
- this.pingedAt = now();
1002
- }
1003
- recordConnect() {
1004
- this.reconnectAttempts = 0;
1005
- delete this.disconnectedAt;
1006
- logger.log("ConnectionMonitor recorded connect");
1007
- }
1008
- recordDisconnect() {
1009
- this.disconnectedAt = now();
1010
- logger.log("ConnectionMonitor recorded disconnect");
1011
- }
1012
- startPolling() {
1013
- this.stopPolling();
1014
- this.poll();
1015
- }
1016
- stopPolling() {
1017
- clearTimeout(this.pollTimeout);
1018
- }
1019
- poll() {
1020
- this.pollTimeout = setTimeout((() => {
1021
- this.reconnectIfStale();
1022
- this.poll();
1023
- }), this.getPollInterval());
1024
- }
1025
- getPollInterval() {
1026
- const {staleThreshold: staleThreshold, reconnectionBackoffRate: reconnectionBackoffRate} = this.constructor;
1027
- const backoff = Math.pow(1 + reconnectionBackoffRate, Math.min(this.reconnectAttempts, 10));
1028
- const jitterMax = this.reconnectAttempts === 0 ? 1 : reconnectionBackoffRate;
1029
- const jitter = jitterMax * Math.random();
1030
- return staleThreshold * 1e3 * backoff * (1 + jitter);
1031
- }
1032
- reconnectIfStale() {
1033
- if (this.connectionIsStale()) {
1034
- logger.log(`ConnectionMonitor detected stale connection. reconnectAttempts = ${this.reconnectAttempts}, time stale = ${secondsSince(this.refreshedAt)} s, stale threshold = ${this.constructor.staleThreshold} s`);
1035
- this.reconnectAttempts++;
1036
- if (this.disconnectedRecently()) {
1037
- logger.log(`ConnectionMonitor skipping reopening recent disconnect. time disconnected = ${secondsSince(this.disconnectedAt)} s`);
1038
- } else {
1039
- logger.log("ConnectionMonitor reopening");
1040
- this.connection.reopen();
1041
- }
1042
- }
1043
- }
1044
- get refreshedAt() {
1045
- return this.pingedAt ? this.pingedAt : this.startedAt;
1046
- }
1047
- connectionIsStale() {
1048
- return secondsSince(this.refreshedAt) > this.constructor.staleThreshold;
1049
- }
1050
- disconnectedRecently() {
1051
- return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold;
1052
- }
1053
- visibilityDidChange() {
1054
- if (document.visibilityState === "visible") {
1055
- setTimeout((() => {
1056
- if (this.connectionIsStale() || !this.connection.isOpen()) {
1057
- logger.log(`ConnectionMonitor reopening stale connection on visibilitychange. visibilityState = ${document.visibilityState}`);
1058
- this.connection.reopen();
1059
- }
1060
- }), 200);
1061
- }
1062
- }
1063
- }
1064
-
1065
- ConnectionMonitor.staleThreshold = 6;
1066
-
1067
- ConnectionMonitor.reconnectionBackoffRate = .15;
1068
-
1069
- var INTERNAL = {
1070
- message_types: {
1071
- welcome: "welcome",
1072
- disconnect: "disconnect",
1073
- ping: "ping",
1074
- confirmation: "confirm_subscription",
1075
- rejection: "reject_subscription"
1076
- },
1077
- disconnect_reasons: {
1078
- unauthorized: "unauthorized",
1079
- invalid_request: "invalid_request",
1080
- server_restart: "server_restart",
1081
- remote: "remote"
1082
- },
1083
- default_mount_path: "/cable",
1084
- protocols: [ "actioncable-v1-json", "actioncable-unsupported" ]
1085
- };
1086
-
1087
- const {message_types: message_types, protocols: protocols} = INTERNAL;
1088
-
1089
- const supportedProtocols = protocols.slice(0, protocols.length - 1);
1090
-
1091
- const indexOf = [].indexOf;
1092
-
1093
- class Connection {
1094
- constructor(consumer) {
1095
- this.open = this.open.bind(this);
1096
- this.consumer = consumer;
1097
- this.subscriptions = this.consumer.subscriptions;
1098
- this.monitor = new ConnectionMonitor(this);
1099
- this.disconnected = true;
1100
- }
1101
- send(data) {
1102
- if (this.isOpen()) {
1103
- this.webSocket.send(JSON.stringify(data));
1104
- return true;
1105
- } else {
1106
- return false;
1107
- }
1108
- }
1109
- open() {
1110
- if (this.isActive()) {
1111
- logger.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`);
1112
- return false;
1113
- } else {
1114
- const socketProtocols = [ ...protocols, ...this.consumer.subprotocols || [] ];
1115
- logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${socketProtocols}`);
1116
- if (this.webSocket) {
1117
- this.uninstallEventHandlers();
1118
- }
1119
- this.webSocket = new adapters.WebSocket(this.consumer.url, socketProtocols);
1120
- this.installEventHandlers();
1121
- this.monitor.start();
1122
- return true;
1123
- }
1124
- }
1125
- close({allowReconnect: allowReconnect} = {
1126
- allowReconnect: true
1127
- }) {
1128
- if (!allowReconnect) {
1129
- this.monitor.stop();
1130
- }
1131
- if (this.isOpen()) {
1132
- return this.webSocket.close();
1133
- }
1134
- }
1135
- reopen() {
1136
- logger.log(`Reopening WebSocket, current state is ${this.getState()}`);
1137
- if (this.isActive()) {
1138
- try {
1139
- return this.close();
1140
- } catch (error) {
1141
- logger.log("Failed to reopen WebSocket", error);
1142
- } finally {
1143
- logger.log(`Reopening WebSocket in ${this.constructor.reopenDelay}ms`);
1144
- setTimeout(this.open, this.constructor.reopenDelay);
1145
- }
1146
- } else {
1147
- return this.open();
1148
- }
1149
- }
1150
- getProtocol() {
1151
- if (this.webSocket) {
1152
- return this.webSocket.protocol;
1153
- }
1154
- }
1155
- isOpen() {
1156
- return this.isState("open");
1157
- }
1158
- isActive() {
1159
- return this.isState("open", "connecting");
1160
- }
1161
- triedToReconnect() {
1162
- return this.monitor.reconnectAttempts > 0;
1163
- }
1164
- isProtocolSupported() {
1165
- return indexOf.call(supportedProtocols, this.getProtocol()) >= 0;
1166
- }
1167
- isState(...states) {
1168
- return indexOf.call(states, this.getState()) >= 0;
1169
- }
1170
- getState() {
1171
- if (this.webSocket) {
1172
- for (let state in adapters.WebSocket) {
1173
- if (adapters.WebSocket[state] === this.webSocket.readyState) {
1174
- return state.toLowerCase();
1175
- }
1176
- }
1177
- }
1178
- return null;
1179
- }
1180
- installEventHandlers() {
1181
- for (let eventName in this.events) {
1182
- const handler = this.events[eventName].bind(this);
1183
- this.webSocket[`on${eventName}`] = handler;
1184
- }
1185
- }
1186
- uninstallEventHandlers() {
1187
- for (let eventName in this.events) {
1188
- this.webSocket[`on${eventName}`] = function() {};
1189
- }
1190
- }
1191
- }
1192
-
1193
- Connection.reopenDelay = 500;
1194
-
1195
- Connection.prototype.events = {
1196
- message(event) {
1197
- if (!this.isProtocolSupported()) {
1198
- return;
1199
- }
1200
- const {identifier: identifier, message: message, reason: reason, reconnect: reconnect, type: type} = JSON.parse(event.data);
1201
- this.monitor.recordMessage();
1202
- switch (type) {
1203
- case message_types.welcome:
1204
- if (this.triedToReconnect()) {
1205
- this.reconnectAttempted = true;
1206
- }
1207
- this.monitor.recordConnect();
1208
- return this.subscriptions.reload();
1209
-
1210
- case message_types.disconnect:
1211
- logger.log(`Disconnecting. Reason: ${reason}`);
1212
- return this.close({
1213
- allowReconnect: reconnect
1214
- });
1215
-
1216
- case message_types.ping:
1217
- return null;
1218
-
1219
- case message_types.confirmation:
1220
- this.subscriptions.confirmSubscription(identifier);
1221
- if (this.reconnectAttempted) {
1222
- this.reconnectAttempted = false;
1223
- return this.subscriptions.notify(identifier, "connected", {
1224
- reconnected: true
1225
- });
1226
- } else {
1227
- return this.subscriptions.notify(identifier, "connected", {
1228
- reconnected: false
1229
- });
1230
- }
1231
-
1232
- case message_types.rejection:
1233
- return this.subscriptions.reject(identifier);
1234
-
1235
- default:
1236
- return this.subscriptions.notify(identifier, "received", message);
1237
- }
1238
- },
1239
- open() {
1240
- logger.log(`WebSocket onopen event, using '${this.getProtocol()}' subprotocol`);
1241
- this.disconnected = false;
1242
- if (!this.isProtocolSupported()) {
1243
- logger.log("Protocol is unsupported. Stopping monitor and disconnecting.");
1244
- return this.close({
1245
- allowReconnect: false
1246
- });
1247
- }
1248
- },
1249
- close(event) {
1250
- logger.log("WebSocket onclose event");
1251
- if (this.disconnected) {
1252
- return;
1253
- }
1254
- this.disconnected = true;
1255
- this.monitor.recordDisconnect();
1256
- return this.subscriptions.notifyAll("disconnected", {
1257
- willAttemptReconnect: this.monitor.isRunning()
1258
- });
1259
- },
1260
- error() {
1261
- logger.log("WebSocket onerror event");
1262
- }
1263
- };
1264
-
1265
- const extend = function(object, properties) {
1266
- if (properties != null) {
1267
- for (let key in properties) {
1268
- const value = properties[key];
1269
- object[key] = value;
1270
- }
1271
- }
1272
- return object;
1273
- };
1274
-
1275
- class Subscription {
1276
- constructor(consumer, params = {}, mixin) {
1277
- this.consumer = consumer;
1278
- this.identifier = JSON.stringify(params);
1279
- extend(this, mixin);
1280
- }
1281
- perform(action, data = {}) {
1282
- data.action = action;
1283
- return this.send(data);
1284
- }
1285
- send(data) {
1286
- return this.consumer.send({
1287
- command: "message",
1288
- identifier: this.identifier,
1289
- data: JSON.stringify(data)
1290
- });
1291
- }
1292
- unsubscribe() {
1293
- return this.consumer.subscriptions.remove(this);
1294
- }
1295
- }
1296
-
1297
- class SubscriptionGuarantor {
1298
- constructor(subscriptions) {
1299
- this.subscriptions = subscriptions;
1300
- this.pendingSubscriptions = [];
1301
- }
1302
- guarantee(subscription) {
1303
- if (this.pendingSubscriptions.indexOf(subscription) == -1) {
1304
- logger.log(`SubscriptionGuarantor guaranteeing ${subscription.identifier}`);
1305
- this.pendingSubscriptions.push(subscription);
1306
- } else {
1307
- logger.log(`SubscriptionGuarantor already guaranteeing ${subscription.identifier}`);
1308
- }
1309
- this.startGuaranteeing();
1310
- }
1311
- forget(subscription) {
1312
- logger.log(`SubscriptionGuarantor forgetting ${subscription.identifier}`);
1313
- this.pendingSubscriptions = this.pendingSubscriptions.filter((s => s !== subscription));
1314
- }
1315
- startGuaranteeing() {
1316
- this.stopGuaranteeing();
1317
- this.retrySubscribing();
1318
- }
1319
- stopGuaranteeing() {
1320
- clearTimeout(this.retryTimeout);
1321
- }
1322
- retrySubscribing() {
1323
- this.retryTimeout = setTimeout((() => {
1324
- if (this.subscriptions && typeof this.subscriptions.subscribe === "function") {
1325
- this.pendingSubscriptions.map((subscription => {
1326
- logger.log(`SubscriptionGuarantor resubscribing ${subscription.identifier}`);
1327
- this.subscriptions.subscribe(subscription);
1328
- }));
1329
- }
1330
- }), 500);
1331
- }
1332
- }
1333
-
1334
- class Subscriptions {
1335
- constructor(consumer) {
1336
- this.consumer = consumer;
1337
- this.guarantor = new SubscriptionGuarantor(this);
1338
- this.subscriptions = [];
1339
- }
1340
- create(channelName, mixin) {
1341
- const channel = channelName;
1342
- const params = typeof channel === "object" ? channel : {
1343
- channel: channel
1344
- };
1345
- const subscription = new Subscription(this.consumer, params, mixin);
1346
- return this.add(subscription);
1347
- }
1348
- add(subscription) {
1349
- this.subscriptions.push(subscription);
1350
- this.consumer.ensureActiveConnection();
1351
- this.notify(subscription, "initialized");
1352
- this.subscribe(subscription);
1353
- return subscription;
1354
- }
1355
- remove(subscription) {
1356
- this.forget(subscription);
1357
- if (!this.findAll(subscription.identifier).length) {
1358
- this.sendCommand(subscription, "unsubscribe");
1359
- }
1360
- return subscription;
1361
- }
1362
- reject(identifier) {
1363
- return this.findAll(identifier).map((subscription => {
1364
- this.forget(subscription);
1365
- this.notify(subscription, "rejected");
1366
- return subscription;
1367
- }));
1368
- }
1369
- forget(subscription) {
1370
- this.guarantor.forget(subscription);
1371
- this.subscriptions = this.subscriptions.filter((s => s !== subscription));
1372
- return subscription;
1373
- }
1374
- findAll(identifier) {
1375
- return this.subscriptions.filter((s => s.identifier === identifier));
1376
- }
1377
- reload() {
1378
- return this.subscriptions.map((subscription => this.subscribe(subscription)));
1379
- }
1380
- notifyAll(callbackName, ...args) {
1381
- return this.subscriptions.map((subscription => this.notify(subscription, callbackName, ...args)));
1382
- }
1383
- notify(subscription, callbackName, ...args) {
1384
- let subscriptions;
1385
- if (typeof subscription === "string") {
1386
- subscriptions = this.findAll(subscription);
1387
- } else {
1388
- subscriptions = [ subscription ];
1389
- }
1390
- return subscriptions.map((subscription => typeof subscription[callbackName] === "function" ? subscription[callbackName](...args) : undefined));
1391
- }
1392
- subscribe(subscription) {
1393
- if (this.sendCommand(subscription, "subscribe")) {
1394
- this.guarantor.guarantee(subscription);
1395
- }
1396
- }
1397
- confirmSubscription(identifier) {
1398
- logger.log(`Subscription confirmed ${identifier}`);
1399
- this.findAll(identifier).map((subscription => this.guarantor.forget(subscription)));
1400
- }
1401
- sendCommand(subscription, command) {
1402
- const {identifier: identifier} = subscription;
1403
- return this.consumer.send({
1404
- command: command,
1405
- identifier: identifier
1406
- });
1407
- }
1408
- }
1409
-
1410
- class Consumer {
1411
- constructor(url) {
1412
- this._url = url;
1413
- this.subscriptions = new Subscriptions(this);
1414
- this.connection = new Connection(this);
1415
- this.subprotocols = [];
1416
- }
1417
- get url() {
1418
- return createWebSocketURL(this._url);
1419
- }
1420
- send(data) {
1421
- return this.connection.send(data);
1422
- }
1423
- connect() {
1424
- return this.connection.open();
1425
- }
1426
- disconnect() {
1427
- return this.connection.close({
1428
- allowReconnect: false
1429
- });
1430
- }
1431
- ensureActiveConnection() {
1432
- if (!this.connection.isActive()) {
1433
- return this.connection.open();
1434
- }
1435
- }
1436
- addSubProtocol(subprotocol) {
1437
- this.subprotocols = [ ...this.subprotocols, subprotocol ];
1438
- }
1439
- }
1440
-
1441
- function createWebSocketURL(url) {
1442
- if (typeof url === "function") {
1443
- url = url();
1444
- }
1445
- if (url && !/^wss?:/i.test(url)) {
1446
- const a = document.createElement("a");
1447
- a.href = url;
1448
- a.href = a.href;
1449
- a.protocol = a.protocol.replace("http", "ws");
1450
- return a.href;
1451
- } else {
1452
- return url;
1453
- }
1454
- }
1455
-
1456
- function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) {
1457
- return new Consumer(url);
1458
- }
1459
-
1460
- function getConfig(name) {
1461
- const element = document.head.querySelector(`meta[name='action-cable-${name}']`);
1462
- if (element) {
1463
- return element.getAttribute("content");
1464
- }
1465
- }
1466
-
1467
- const consumer = createConsumer();
1468
-
1469
- const claptonChannel = consumer.subscriptions.create("Clapton::ClaptonChannel", {
1470
- connected() {},
1471
-
1472
- disconnected() {},
1473
-
1474
- received(response) {
1475
- const { data, errors } = response;
1476
- const component = document.querySelector(`[data-id="${data.component.id}"]`);
1477
- const instance = new window[data.component.name](data.state, data.component.id, errors);
1478
- morphdom(component, instance.render, {
1479
- onBeforeElUpdated: (_fromEl, toEl) => {
1480
- toEl.setAttribute("data-set-event-handler", "true");
1481
- return true;
1482
- }
1483
- });
1484
- initializeInputs();
1485
- initializeActions();
1486
- }
1487
- });
1488
-
1489
- const handleAction = async (target, stateName, fn) => {
1490
- const targetComponent = target.closest(`[data-component="${stateName.replace("State", "Component")}"]`);
1491
- if (!targetComponent)
1492
- return;
1493
- const component = target.closest(`[data-component]`);
1494
- const attribute = target.getAttribute("data-attribute");
1495
- if (attribute) {
1496
- const state = JSON.parse(component.getAttribute("data-state") || "{}");
1497
- if (target.tagName === "INPUT") {
1498
- state[attribute] = target.value;
1499
- component.setAttribute("data-state", JSON.stringify(state));
1500
- }
1501
- }
1502
- claptonChannel.perform("action", {
1503
- data: {
1504
- component: {
1505
- name: stateName.replace("State", "Component"),
1506
- id: targetComponent.getAttribute("data-id"),
1507
- },
1508
- state: {
1509
- name: stateName,
1510
- action: fn,
1511
- attributes: JSON.parse(targetComponent.getAttribute("data-state") || "{}"),
1512
- },
1513
- params: JSON.parse(component.getAttribute("data-state") || "{}")
1514
- }
1515
- });
1516
- };
1517
-
1518
- const debounce = (fn, delay) => {
1519
- let timer = undefined;
1520
- return (...args) => {
1521
- clearTimeout(timer);
1522
- timer = setTimeout(() => fn(...args), delay);
1523
- };
1524
- };
1525
- const initializeComponents = () => {
1526
- const components = document.querySelector("#clapton")?.getAttribute("data-clapton") || "[]";
1527
- JSON.parse(components).forEach(createAndAppendComponent);
1528
- };
1529
- const createAndAppendComponent = (component) => {
1530
- const componentDom = document.createElement('div');
1531
- const instance = new window[component.component](component.state);
1532
- componentDom.innerHTML = instance.render;
1533
- const firstChild = componentDom.firstChild;
1534
- if (firstChild) {
1535
- document.querySelector("#clapton")?.appendChild(firstChild);
1536
- }
1537
- };
1538
- const initializeActions = () => {
1539
- const actionElements = document.querySelectorAll("[data-action]");
1540
- actionElements.forEach((element) => initializeActionsForElement(element));
1541
- };
1542
- const initializeActionsForElement = (element) => {
1543
- if (element.getAttribute("data-set-event-handler"))
1544
- return;
1545
- const actions = element.getAttribute("data-action")?.split(" ") || [];
1546
- actions.forEach(action => {
1547
- const { eventType, componentName, stateName, fnName, bounceTime } = splitActionAttribute(action);
1548
- if (!eventType || !componentName || !fnName)
1549
- return;
1550
- if (bounceTime > 0) {
1551
- element.addEventListener(eventType, debounce((event) => handleAction(event.target, stateName, fnName), bounceTime));
1552
- }
1553
- else {
1554
- element.addEventListener(eventType, (event) => handleAction(event.target, stateName, fnName));
1555
- }
1556
- element.setAttribute("data-set-event-handler", "true");
1557
- });
1558
- };
1559
- const initializeInputs = () => {
1560
- const inputElements = document.querySelectorAll("[data-attribute]");
1561
- inputElements.forEach((element) => {
1562
- const attribute = element.getAttribute("data-attribute");
1563
- const component = element.closest(`[data-component]`);
1564
- const state = JSON.parse(component.getAttribute("data-state") || "{}");
1565
- if (!attribute || !component)
1566
- return;
1567
- if (element.tagName === "INPUT") {
1568
- element.addEventListener("input", (event) => {
1569
- updateComponent(component, state, attribute, event.target);
1570
- });
1571
- }
1572
- });
1573
- };
1574
- document.addEventListener("DOMContentLoaded", () => {
1575
- initializeComponents();
1576
- initializeActions();
1577
- initializeInputs();
1578
- });
1579
-
1580
- exports.Box = Box;
1581
- exports.Button = Button;
1582
- exports.Component = Component;
1583
- exports.DateTimeField = DateTimeField;
1584
- exports.Link = Link;
1585
- exports.Text = Text;
1586
- exports.TextField = TextField;
1587
- exports.initializeActions = initializeActions;
1588
- exports.initializeInputs = initializeInputs;
1589
-
1590
- return exports;
1591
-
1592
- })({});