cable_ready 5.0.0.pre8 → 5.0.0.pre9

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.
@@ -0,0 +1,959 @@
1
+ import morphdom from "morphdom";
2
+
3
+ var name = "cable_ready";
4
+
5
+ var version = "5.0.0-pre9";
6
+
7
+ var description = "CableReady helps you create great real-time user experiences by making it simple to trigger client-side DOM changes from server-side Ruby.";
8
+
9
+ var keywords = [ "ruby", "rails", "websockets", "actioncable", "cable", "ssr", "stimulus_reflex", "client-side", "dom" ];
10
+
11
+ var homepage = "https://cableready.stimulusreflex.com/";
12
+
13
+ var bugs = {
14
+ url: "https://github.com/stimulusreflex/cable_ready/issues"
15
+ };
16
+
17
+ var repository = {
18
+ type: "git",
19
+ url: "git+https://github.com:stimulusreflex/cable_ready.git"
20
+ };
21
+
22
+ var license = "MIT";
23
+
24
+ var author = "Nathan Hopkins <natehop@gmail.com>";
25
+
26
+ var main = "./dist/cable_ready.umd.min.js";
27
+
28
+ var module = "./dist/cable_ready.min.js";
29
+
30
+ var files = [ "dist/*", "javascript/*" ];
31
+
32
+ var scripts = {
33
+ lint: "yarn run prettier-standard:check",
34
+ format: "yarn run prettier-standard:format",
35
+ "prettier-standard:check": "yarn run prettier-standard --check ./javascript/**/*.js rollup.config.js",
36
+ "prettier-standard:format": "yarn run prettier-standard ./javascript/**/*.js rollup.config.js",
37
+ build: "yarn rollup -c",
38
+ watch: "yarn rollup -wc"
39
+ };
40
+
41
+ var dependencies = {
42
+ morphdom: "^2.6.1"
43
+ };
44
+
45
+ var devDependencies = {
46
+ "@rollup/plugin-commonjs": "^21.0.3",
47
+ "@rollup/plugin-json": "^4.1.0",
48
+ "@rollup/plugin-node-resolve": "^13.1.3",
49
+ "prettier-standard": "^16.4.1",
50
+ rollup: "^2.70.1",
51
+ "rollup-plugin-terser": "^7.0.2"
52
+ };
53
+
54
+ var packageInfo = {
55
+ name: name,
56
+ version: version,
57
+ description: description,
58
+ keywords: keywords,
59
+ homepage: homepage,
60
+ bugs: bugs,
61
+ repository: repository,
62
+ license: license,
63
+ author: author,
64
+ main: main,
65
+ module: module,
66
+ files: files,
67
+ scripts: scripts,
68
+ dependencies: dependencies,
69
+ devDependencies: devDependencies
70
+ };
71
+
72
+ const inputTags = {
73
+ INPUT: true,
74
+ TEXTAREA: true,
75
+ SELECT: true
76
+ };
77
+
78
+ const mutableTags = {
79
+ INPUT: true,
80
+ TEXTAREA: true,
81
+ OPTION: true
82
+ };
83
+
84
+ const textInputTypes = {
85
+ "datetime-local": true,
86
+ "select-multiple": true,
87
+ "select-one": true,
88
+ color: true,
89
+ date: true,
90
+ datetime: true,
91
+ email: true,
92
+ month: true,
93
+ number: true,
94
+ password: true,
95
+ range: true,
96
+ search: true,
97
+ tel: true,
98
+ text: true,
99
+ textarea: true,
100
+ time: true,
101
+ url: true,
102
+ week: true
103
+ };
104
+
105
+ let activeElement;
106
+
107
+ var ActiveElement = {
108
+ get element() {
109
+ return activeElement;
110
+ },
111
+ set(element) {
112
+ activeElement = element;
113
+ }
114
+ };
115
+
116
+ const isTextInput = element => inputTags[element.tagName] && textInputTypes[element.type];
117
+
118
+ const assignFocus = selector => {
119
+ const element = selector && selector.nodeType === Node.ELEMENT_NODE ? selector : document.querySelector(selector);
120
+ const focusElement = element || ActiveElement.element;
121
+ if (focusElement && focusElement.focus) focusElement.focus();
122
+ };
123
+
124
+ const dispatch = (element, name, detail = {}) => {
125
+ const init = {
126
+ bubbles: true,
127
+ cancelable: true,
128
+ detail: detail
129
+ };
130
+ const evt = new CustomEvent(name, init);
131
+ element.dispatchEvent(evt);
132
+ if (window.jQuery) window.jQuery(element).trigger(name, detail);
133
+ };
134
+
135
+ const xpathToElement = xpath => document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
136
+
137
+ const getClassNames = names => Array(names).flat();
138
+
139
+ const processElements = (operation, callback) => {
140
+ Array.from(operation.selectAll ? operation.element : [ operation.element ]).forEach(callback);
141
+ };
142
+
143
+ const kebabize = str => str.split("").map(((letter, idx) => letter.toUpperCase() === letter ? `${idx !== 0 ? "-" : ""}${letter.toLowerCase()}` : letter)).join("");
144
+
145
+ const operate = (operation, callback) => {
146
+ if (!operation.cancel) {
147
+ operation.delay ? setTimeout(callback, operation.delay) : callback();
148
+ return true;
149
+ }
150
+ return false;
151
+ };
152
+
153
+ const before = (target, operation) => dispatch(target, `cable-ready:before-${kebabize(operation.operation)}`, operation);
154
+
155
+ const after = (target, operation) => dispatch(target, `cable-ready:after-${kebabize(operation.operation)}`, operation);
156
+
157
+ function debounce(func, timeout) {
158
+ let timer;
159
+ return (...args) => {
160
+ clearTimeout(timer);
161
+ timer = setTimeout((() => func.apply(this, args)), timeout);
162
+ };
163
+ }
164
+
165
+ function handleErrors(response) {
166
+ if (!response.ok) throw Error(response.statusText);
167
+ return response;
168
+ }
169
+
170
+ async function graciouslyFetch(url, additionalHeaders) {
171
+ try {
172
+ const response = await fetch(url, {
173
+ headers: {
174
+ "X-REQUESTED-WITH": "XmlHttpRequest",
175
+ ...additionalHeaders
176
+ }
177
+ });
178
+ if (response == undefined) return;
179
+ handleErrors(response);
180
+ return response;
181
+ } catch (e) {
182
+ console.error(`Could not fetch ${url}`);
183
+ }
184
+ }
185
+
186
+ var utils = Object.freeze({
187
+ __proto__: null,
188
+ isTextInput: isTextInput,
189
+ assignFocus: assignFocus,
190
+ dispatch: dispatch,
191
+ xpathToElement: xpathToElement,
192
+ getClassNames: getClassNames,
193
+ processElements: processElements,
194
+ operate: operate,
195
+ before: before,
196
+ after: after,
197
+ debounce: debounce,
198
+ handleErrors: handleErrors,
199
+ graciouslyFetch: graciouslyFetch,
200
+ kebabize: kebabize
201
+ });
202
+
203
+ const shouldMorph = operation => (fromEl, toEl) => !shouldMorphCallbacks.map((callback => typeof callback === "function" ? callback(operation, fromEl, toEl) : true)).includes(false);
204
+
205
+ const didMorph = operation => el => {
206
+ didMorphCallbacks.forEach((callback => {
207
+ if (typeof callback === "function") callback(operation, el);
208
+ }));
209
+ };
210
+
211
+ const verifyNotMutable = (detail, fromEl, toEl) => {
212
+ if (!mutableTags[fromEl.tagName] && fromEl.isEqualNode(toEl)) return false;
213
+ return true;
214
+ };
215
+
216
+ const verifyNotContentEditable = (detail, fromEl, toEl) => {
217
+ if (fromEl === ActiveElement.element && fromEl.isContentEditable) return false;
218
+ return true;
219
+ };
220
+
221
+ const verifyNotPermanent = (detail, fromEl, toEl) => {
222
+ const {permanentAttributeName: permanentAttributeName} = detail;
223
+ if (!permanentAttributeName) return true;
224
+ const permanent = fromEl.closest(`[${permanentAttributeName}]`);
225
+ if (!permanent && fromEl === ActiveElement.element && isTextInput(fromEl)) {
226
+ const ignore = {
227
+ value: true
228
+ };
229
+ Array.from(toEl.attributes).forEach((attribute => {
230
+ if (!ignore[attribute.name]) fromEl.setAttribute(attribute.name, attribute.value);
231
+ }));
232
+ return false;
233
+ }
234
+ return !permanent;
235
+ };
236
+
237
+ const shouldMorphCallbacks = [ verifyNotMutable, verifyNotPermanent, verifyNotContentEditable ];
238
+
239
+ const didMorphCallbacks = [];
240
+
241
+ var morph_callbacks = Object.freeze({
242
+ __proto__: null,
243
+ shouldMorphCallbacks: shouldMorphCallbacks,
244
+ didMorphCallbacks: didMorphCallbacks,
245
+ shouldMorph: shouldMorph,
246
+ didMorph: didMorph,
247
+ verifyNotMutable: verifyNotMutable,
248
+ verifyNotContentEditable: verifyNotContentEditable,
249
+ verifyNotPermanent: verifyNotPermanent
250
+ });
251
+
252
+ var Operations = {
253
+ append: operation => {
254
+ processElements(operation, (element => {
255
+ before(element, operation);
256
+ operate(operation, (() => {
257
+ const {html: html, focusSelector: focusSelector} = operation;
258
+ element.insertAdjacentHTML("beforeend", html || "");
259
+ assignFocus(focusSelector);
260
+ }));
261
+ after(element, operation);
262
+ }));
263
+ },
264
+ graft: operation => {
265
+ processElements(operation, (element => {
266
+ before(element, operation);
267
+ operate(operation, (() => {
268
+ const {parent: parent, focusSelector: focusSelector} = operation;
269
+ const parentElement = document.querySelector(parent);
270
+ if (parentElement) {
271
+ parentElement.appendChild(element);
272
+ assignFocus(focusSelector);
273
+ }
274
+ }));
275
+ after(element, operation);
276
+ }));
277
+ },
278
+ innerHtml: operation => {
279
+ processElements(operation, (element => {
280
+ before(element, operation);
281
+ operate(operation, (() => {
282
+ const {html: html, focusSelector: focusSelector} = operation;
283
+ element.innerHTML = html || "";
284
+ assignFocus(focusSelector);
285
+ }));
286
+ after(element, operation);
287
+ }));
288
+ },
289
+ insertAdjacentHtml: operation => {
290
+ processElements(operation, (element => {
291
+ before(element, operation);
292
+ operate(operation, (() => {
293
+ const {html: html, position: position, focusSelector: focusSelector} = operation;
294
+ element.insertAdjacentHTML(position || "beforeend", html || "");
295
+ assignFocus(focusSelector);
296
+ }));
297
+ after(element, operation);
298
+ }));
299
+ },
300
+ insertAdjacentText: operation => {
301
+ processElements(operation, (element => {
302
+ before(element, operation);
303
+ operate(operation, (() => {
304
+ const {text: text, position: position, focusSelector: focusSelector} = operation;
305
+ element.insertAdjacentText(position || "beforeend", text || "");
306
+ assignFocus(focusSelector);
307
+ }));
308
+ after(element, operation);
309
+ }));
310
+ },
311
+ morph: operation => {
312
+ processElements(operation, (element => {
313
+ const {html: html} = operation;
314
+ const template = document.createElement("template");
315
+ template.innerHTML = String(html).trim();
316
+ operation.content = template.content;
317
+ const parent = element.parentElement;
318
+ const ordinal = Array.from(parent.children).indexOf(element);
319
+ before(element, operation);
320
+ operate(operation, (() => {
321
+ const {childrenOnly: childrenOnly, focusSelector: focusSelector} = operation;
322
+ morphdom(element, childrenOnly ? template.content : template.innerHTML, {
323
+ childrenOnly: !!childrenOnly,
324
+ onBeforeElUpdated: shouldMorph(operation),
325
+ onElUpdated: didMorph(operation)
326
+ });
327
+ assignFocus(focusSelector);
328
+ }));
329
+ after(parent.children[ordinal], operation);
330
+ }));
331
+ },
332
+ outerHtml: operation => {
333
+ processElements(operation, (element => {
334
+ const parent = element.parentElement;
335
+ const ordinal = Array.from(parent.children).indexOf(element);
336
+ before(element, operation);
337
+ operate(operation, (() => {
338
+ const {html: html, focusSelector: focusSelector} = operation;
339
+ element.outerHTML = html || "";
340
+ assignFocus(focusSelector);
341
+ }));
342
+ after(parent.children[ordinal], operation);
343
+ }));
344
+ },
345
+ prepend: operation => {
346
+ processElements(operation, (element => {
347
+ before(element, operation);
348
+ operate(operation, (() => {
349
+ const {html: html, focusSelector: focusSelector} = operation;
350
+ element.insertAdjacentHTML("afterbegin", html || "");
351
+ assignFocus(focusSelector);
352
+ }));
353
+ after(element, operation);
354
+ }));
355
+ },
356
+ remove: operation => {
357
+ processElements(operation, (element => {
358
+ before(element, operation);
359
+ operate(operation, (() => {
360
+ const {focusSelector: focusSelector} = operation;
361
+ element.remove();
362
+ assignFocus(focusSelector);
363
+ }));
364
+ after(document, operation);
365
+ }));
366
+ },
367
+ replace: operation => {
368
+ processElements(operation, (element => {
369
+ const parent = element.parentElement;
370
+ const ordinal = Array.from(parent.children).indexOf(element);
371
+ before(element, operation);
372
+ operate(operation, (() => {
373
+ const {html: html, focusSelector: focusSelector} = operation;
374
+ element.outerHTML = html || "";
375
+ assignFocus(focusSelector);
376
+ }));
377
+ after(parent.children[ordinal], operation);
378
+ }));
379
+ },
380
+ textContent: operation => {
381
+ processElements(operation, (element => {
382
+ before(element, operation);
383
+ operate(operation, (() => {
384
+ const {text: text, focusSelector: focusSelector} = operation;
385
+ element.textContent = text != null ? text : "";
386
+ assignFocus(focusSelector);
387
+ }));
388
+ after(element, operation);
389
+ }));
390
+ },
391
+ addCssClass: operation => {
392
+ processElements(operation, (element => {
393
+ before(element, operation);
394
+ operate(operation, (() => {
395
+ const {name: name} = operation;
396
+ element.classList.add(...getClassNames(name || ""));
397
+ }));
398
+ after(element, operation);
399
+ }));
400
+ },
401
+ removeAttribute: operation => {
402
+ processElements(operation, (element => {
403
+ before(element, operation);
404
+ operate(operation, (() => {
405
+ const {name: name} = operation;
406
+ element.removeAttribute(name);
407
+ }));
408
+ after(element, operation);
409
+ }));
410
+ },
411
+ removeCssClass: operation => {
412
+ processElements(operation, (element => {
413
+ before(element, operation);
414
+ operate(operation, (() => {
415
+ const {name: name} = operation;
416
+ element.classList.remove(...getClassNames(name));
417
+ }));
418
+ after(element, operation);
419
+ }));
420
+ },
421
+ setAttribute: operation => {
422
+ processElements(operation, (element => {
423
+ before(element, operation);
424
+ operate(operation, (() => {
425
+ const {name: name, value: value} = operation;
426
+ element.setAttribute(name, value || "");
427
+ }));
428
+ after(element, operation);
429
+ }));
430
+ },
431
+ setDatasetProperty: operation => {
432
+ processElements(operation, (element => {
433
+ before(element, operation);
434
+ operate(operation, (() => {
435
+ const {name: name, value: value} = operation;
436
+ element.dataset[name] = value || "";
437
+ }));
438
+ after(element, operation);
439
+ }));
440
+ },
441
+ setProperty: operation => {
442
+ processElements(operation, (element => {
443
+ before(element, operation);
444
+ operate(operation, (() => {
445
+ const {name: name, value: value} = operation;
446
+ if (name in element) element[name] = value || "";
447
+ }));
448
+ after(element, operation);
449
+ }));
450
+ },
451
+ setStyle: operation => {
452
+ processElements(operation, (element => {
453
+ before(element, operation);
454
+ operate(operation, (() => {
455
+ const {name: name, value: value} = operation;
456
+ element.style[name] = value || "";
457
+ }));
458
+ after(element, operation);
459
+ }));
460
+ },
461
+ setStyles: operation => {
462
+ processElements(operation, (element => {
463
+ before(element, operation);
464
+ operate(operation, (() => {
465
+ const {styles: styles} = operation;
466
+ for (let [name, value] of Object.entries(styles)) element.style[name] = value || "";
467
+ }));
468
+ after(element, operation);
469
+ }));
470
+ },
471
+ setValue: operation => {
472
+ processElements(operation, (element => {
473
+ before(element, operation);
474
+ operate(operation, (() => {
475
+ const {value: value} = operation;
476
+ element.value = value || "";
477
+ }));
478
+ after(element, operation);
479
+ }));
480
+ },
481
+ dispatchEvent: operation => {
482
+ processElements(operation, (element => {
483
+ before(element, operation);
484
+ operate(operation, (() => {
485
+ const {name: name, detail: detail} = operation;
486
+ dispatch(element, name, detail);
487
+ }));
488
+ after(element, operation);
489
+ }));
490
+ },
491
+ setMeta: operation => {
492
+ before(document, operation);
493
+ operate(operation, (() => {
494
+ const {name: name, content: content} = operation;
495
+ let meta = document.head.querySelector(`meta[name='${name}']`);
496
+ if (!meta) {
497
+ meta = document.createElement("meta");
498
+ meta.name = name;
499
+ document.head.appendChild(meta);
500
+ }
501
+ meta.content = content;
502
+ }));
503
+ after(document, operation);
504
+ },
505
+ clearStorage: operation => {
506
+ before(document, operation);
507
+ operate(operation, (() => {
508
+ const {type: type} = operation;
509
+ const storage = type === "session" ? sessionStorage : localStorage;
510
+ storage.clear();
511
+ }));
512
+ after(document, operation);
513
+ },
514
+ go: operation => {
515
+ before(window, operation);
516
+ operate(operation, (() => {
517
+ const {delta: delta} = operation;
518
+ history.go(delta);
519
+ }));
520
+ after(window, operation);
521
+ },
522
+ pushState: operation => {
523
+ before(window, operation);
524
+ operate(operation, (() => {
525
+ const {state: state, title: title, url: url} = operation;
526
+ history.pushState(state || {}, title || "", url);
527
+ }));
528
+ after(window, operation);
529
+ },
530
+ redirectTo: operation => {
531
+ before(window, operation);
532
+ operate(operation, (() => {
533
+ let {url: url, action: action} = operation;
534
+ action = action || "advance";
535
+ if (window.Turbo) window.Turbo.visit(url, {
536
+ action: action
537
+ });
538
+ if (window.Turbolinks) window.Turbolinks.visit(url, {
539
+ action: action
540
+ });
541
+ if (!window.Turbo && !window.Turbolinks) window.location.href = url;
542
+ }));
543
+ after(window, operation);
544
+ },
545
+ reload: operation => {
546
+ before(window, operation);
547
+ operate(operation, (() => {
548
+ window.location.reload();
549
+ }));
550
+ after(window, operation);
551
+ },
552
+ removeStorageItem: operation => {
553
+ before(document, operation);
554
+ operate(operation, (() => {
555
+ const {key: key, type: type} = operation;
556
+ const storage = type === "session" ? sessionStorage : localStorage;
557
+ storage.removeItem(key);
558
+ }));
559
+ after(document, operation);
560
+ },
561
+ replaceState: operation => {
562
+ before(window, operation);
563
+ operate(operation, (() => {
564
+ const {state: state, title: title, url: url} = operation;
565
+ history.replaceState(state || {}, title || "", url);
566
+ }));
567
+ after(window, operation);
568
+ },
569
+ scrollIntoView: operation => {
570
+ const {element: element} = operation;
571
+ before(element, operation);
572
+ operate(operation, (() => {
573
+ element.scrollIntoView(operation);
574
+ }));
575
+ after(element, operation);
576
+ },
577
+ setCookie: operation => {
578
+ before(document, operation);
579
+ operate(operation, (() => {
580
+ const {cookie: cookie} = operation;
581
+ document.cookie = cookie || "";
582
+ }));
583
+ after(document, operation);
584
+ },
585
+ setFocus: operation => {
586
+ const {element: element} = operation;
587
+ before(element, operation);
588
+ operate(operation, (() => {
589
+ assignFocus(element);
590
+ }));
591
+ after(element, operation);
592
+ },
593
+ setStorageItem: operation => {
594
+ before(document, operation);
595
+ operate(operation, (() => {
596
+ const {key: key, value: value, type: type} = operation;
597
+ const storage = type === "session" ? sessionStorage : localStorage;
598
+ storage.setItem(key, value || "");
599
+ }));
600
+ after(document, operation);
601
+ },
602
+ consoleLog: operation => {
603
+ before(document, operation);
604
+ operate(operation, (() => {
605
+ const {message: message, level: level} = operation;
606
+ level && [ "warn", "info", "error" ].includes(level) ? console[level](message || "") : console.log(message || "");
607
+ }));
608
+ after(document, operation);
609
+ },
610
+ consoleTable: operation => {
611
+ before(document, operation);
612
+ operate(operation, (() => {
613
+ const {data: data, columns: columns} = operation;
614
+ console.table(data, columns || []);
615
+ }));
616
+ after(document, operation);
617
+ },
618
+ notification: operation => {
619
+ before(document, operation);
620
+ operate(operation, (() => {
621
+ const {title: title, options: options} = operation;
622
+ Notification.requestPermission().then((result => {
623
+ operation.permission = result;
624
+ if (result === "granted") new Notification(title || "", options);
625
+ }));
626
+ }));
627
+ after(document, operation);
628
+ }
629
+ };
630
+
631
+ let operations = Operations;
632
+
633
+ const add = newOperations => {
634
+ operations = {
635
+ ...operations,
636
+ ...newOperations
637
+ };
638
+ };
639
+
640
+ const addOperations = operations => {
641
+ add(operations);
642
+ };
643
+
644
+ const addOperation = (name, operation) => {
645
+ const operations = {};
646
+ operations[name] = operation;
647
+ add(operations);
648
+ };
649
+
650
+ var OperationStore = {
651
+ get all() {
652
+ return operations;
653
+ }
654
+ };
655
+
656
+ const perform = (operations, options = {
657
+ emitMissingElementWarnings: true
658
+ }) => {
659
+ const batches = {};
660
+ operations.forEach((operation => {
661
+ if (!!operation.batch) batches[operation.batch] = batches[operation.batch] ? ++batches[operation.batch] : 1;
662
+ }));
663
+ operations.forEach((operation => {
664
+ const name = operation.operation;
665
+ try {
666
+ if (operation.selector) {
667
+ operation.element = operation.xpath ? xpathToElement(operation.selector) : document[operation.selectAll ? "querySelectorAll" : "querySelector"](operation.selector);
668
+ } else {
669
+ operation.element = document;
670
+ }
671
+ if (operation.element || options.emitMissingElementWarnings) {
672
+ ActiveElement.set(document.activeElement);
673
+ const cableReadyOperation = OperationStore.all[name];
674
+ if (cableReadyOperation) {
675
+ cableReadyOperation(operation);
676
+ if (!!operation.batch && --batches[operation.batch] === 0) dispatch(document, "cable-ready:batch-complete", {
677
+ batch: operation.batch
678
+ });
679
+ } else {
680
+ console.error(`CableReady couldn't find the "${name}" operation. Make sure you use the camelized form when calling an operation method.`);
681
+ }
682
+ }
683
+ } catch (e) {
684
+ if (operation.element) {
685
+ console.error(`CableReady detected an error in ${name || "operation"}: ${e.message}. If you need to support older browsers make sure you've included the corresponding polyfills. https://docs.stimulusreflex.com/setup#polyfills-for-ie11.`);
686
+ console.error(e);
687
+ } else {
688
+ console.warn(`CableReady ${name || "operation"} failed due to missing DOM element for selector: '${operation.selector}'`);
689
+ }
690
+ }
691
+ }));
692
+ };
693
+
694
+ const performAsync = (operations, options = {
695
+ emitMissingElementWarnings: true
696
+ }) => new Promise(((resolve, reject) => {
697
+ try {
698
+ resolve(perform(operations, options));
699
+ } catch (err) {
700
+ reject(err);
701
+ }
702
+ }));
703
+
704
+ let consumer;
705
+
706
+ const BACKOFF = [ 25, 50, 75, 100, 200, 250, 500, 800, 1e3, 2e3 ];
707
+
708
+ const wait = ms => new Promise((resolve => setTimeout(resolve, ms)));
709
+
710
+ const getConsumerWithRetry = async (retry = 0) => {
711
+ if (consumer) return consumer;
712
+ if (retry >= BACKOFF.length) {
713
+ throw new Error("Couldn't obtain a Action Cable consumer within 5s");
714
+ }
715
+ await wait(BACKOFF[retry]);
716
+ return await getConsumerWithRetry(retry + 1);
717
+ };
718
+
719
+ var CableConsumer = {
720
+ setConsumer(value) {
721
+ consumer = value;
722
+ },
723
+ get consumer() {
724
+ return consumer;
725
+ },
726
+ async getConsumer() {
727
+ return await getConsumerWithRetry();
728
+ }
729
+ };
730
+
731
+ class SubscribingElement extends HTMLElement {
732
+ disconnectedCallback() {
733
+ if (this.channel) this.channel.unsubscribe();
734
+ }
735
+ createSubscription(consumer, channel, receivedCallback) {
736
+ this.channel = consumer.subscriptions.create({
737
+ channel: channel,
738
+ identifier: this.identifier
739
+ }, {
740
+ received: receivedCallback
741
+ });
742
+ }
743
+ get preview() {
744
+ return document.documentElement.hasAttribute("data-turbolinks-preview") || document.documentElement.hasAttribute("data-turbo-preview");
745
+ }
746
+ get identifier() {
747
+ return this.getAttribute("identifier");
748
+ }
749
+ }
750
+
751
+ class StreamFromElement extends SubscribingElement {
752
+ async connectedCallback() {
753
+ if (this.preview) return;
754
+ const consumer = await CableConsumer.getConsumer();
755
+ if (consumer) {
756
+ this.createSubscription(consumer, "CableReady::Stream", this.performOperations);
757
+ } else {
758
+ console.error("The `stream_from` helper cannot connect without an ActionCable consumer.\nPlease run `rails generate cable_ready:helpers` to fix this.");
759
+ }
760
+ }
761
+ performOperations(data) {
762
+ if (data.cableReady) perform(data.operations);
763
+ }
764
+ }
765
+
766
+ const template = `\n<style>\n :host {\n display: block;\n }\n</style>\n<slot></slot>\n`;
767
+
768
+ class UpdatesForElement extends SubscribingElement {
769
+ constructor() {
770
+ super();
771
+ const shadowRoot = this.attachShadow({
772
+ mode: "open"
773
+ });
774
+ shadowRoot.innerHTML = template;
775
+ }
776
+ async connectedCallback() {
777
+ if (this.preview) return;
778
+ this.update = debounce(this.update.bind(this), this.debounce);
779
+ const consumer = await CableConsumer.getConsumer();
780
+ if (consumer) {
781
+ this.createSubscription(consumer, "CableReady::Stream", this.update);
782
+ } else {
783
+ console.error("The `updates-for` helper cannot connect without an ActionCable consumer.\nPlease run `rails generate cable_ready:helpers` to fix this.");
784
+ }
785
+ }
786
+ async update(data) {
787
+ const blocks = Array.from(document.querySelectorAll(this.query), (element => new Block(element)));
788
+ if (blocks[0].element !== this) return;
789
+ ActiveElement.set(document.activeElement);
790
+ this.html = {};
791
+ const uniqueUrls = [ ...new Set(blocks.map((block => block.url))) ];
792
+ await Promise.all(uniqueUrls.map((async url => {
793
+ if (!this.html.hasOwnProperty(url)) {
794
+ const response = await graciouslyFetch(url, {
795
+ "X-Cable-Ready": "update"
796
+ });
797
+ this.html[url] = await response.text();
798
+ }
799
+ })));
800
+ this.index = {};
801
+ blocks.forEach((block => {
802
+ this.index.hasOwnProperty(block.url) ? this.index[block.url]++ : this.index[block.url] = 0;
803
+ block.process(data, this.html, this.index);
804
+ }));
805
+ }
806
+ get query() {
807
+ return `updates-for[identifier="${this.identifier}"]`;
808
+ }
809
+ get identifier() {
810
+ return this.getAttribute("identifier");
811
+ }
812
+ get debounce() {
813
+ return this.hasAttribute("debounce") ? parseInt(this.getAttribute("debounce")) : 20;
814
+ }
815
+ }
816
+
817
+ class Block {
818
+ constructor(element) {
819
+ this.element = element;
820
+ }
821
+ async process(data, html, index) {
822
+ if (!this.shouldUpdate(data)) return;
823
+ const blockIndex = index[this.url];
824
+ const template = document.createElement("template");
825
+ this.element.setAttribute("updating", "updating");
826
+ template.innerHTML = String(html[this.url]).trim();
827
+ await this.resolveTurboFrames(template.content);
828
+ const fragments = template.content.querySelectorAll(this.query);
829
+ if (fragments.length <= blockIndex) {
830
+ console.warn(`Update aborted due to insufficient number of elements. The offending url is ${this.url}.`);
831
+ return;
832
+ }
833
+ const operation = {
834
+ element: this.element,
835
+ html: fragments[blockIndex],
836
+ permanentAttributeName: "data-ignore-updates"
837
+ };
838
+ dispatch(this.element, "cable-ready:before-update", operation);
839
+ morphdom(this.element, fragments[blockIndex], {
840
+ childrenOnly: true,
841
+ onBeforeElUpdated: shouldMorph(operation),
842
+ onElUpdated: _ => {
843
+ this.element.removeAttribute("updating");
844
+ dispatch(this.element, "cable-ready:after-update", operation);
845
+ assignFocus(operation.focusSelector);
846
+ }
847
+ });
848
+ }
849
+ async resolveTurboFrames(documentFragment) {
850
+ const reloadingTurboFrames = [ ...documentFragment.querySelectorAll('turbo-frame[src]:not([loading="lazy"])') ];
851
+ return Promise.all(reloadingTurboFrames.map((frame => new Promise((async resolve => {
852
+ const frameResponse = await graciouslyFetch(frame.getAttribute("src"), {
853
+ "Turbo-Frame": frame.id,
854
+ "X-Cable-Ready": "update"
855
+ });
856
+ const frameTemplate = document.createElement("template");
857
+ frameTemplate.innerHTML = await frameResponse.text();
858
+ await this.resolveTurboFrames(frameTemplate.content);
859
+ documentFragment.querySelector(`turbo-frame#${frame.id}`).innerHTML = String(frameTemplate.content.querySelector(`turbo-frame#${frame.id}`).innerHTML).trim();
860
+ resolve();
861
+ })))));
862
+ }
863
+ shouldUpdate(data) {
864
+ return !this.ignoresInnerUpdates && this.hasChangesSelectedForUpdate(data);
865
+ }
866
+ hasChangesSelectedForUpdate(data) {
867
+ const only = this.element.getAttribute("only");
868
+ return !(only && data.changed && !only.split(" ").some((attribute => data.changed.includes(attribute))));
869
+ }
870
+ get ignoresInnerUpdates() {
871
+ return this.element.hasAttribute("ignore-inner-updates") && this.element.hasAttribute("performing-inner-update");
872
+ }
873
+ get url() {
874
+ return this.element.hasAttribute("url") ? this.element.getAttribute("url") : location.href;
875
+ }
876
+ get identifier() {
877
+ return this.element.identifier;
878
+ }
879
+ get query() {
880
+ return this.element.query;
881
+ }
882
+ }
883
+
884
+ const registerInnerUpdates = () => {
885
+ document.addEventListener("stimulus-reflex:before", (event => {
886
+ recursiveMarkUpdatesForElements(event.detail.element);
887
+ }));
888
+ document.addEventListener("stimulus-reflex:after", (event => {
889
+ setTimeout((() => {
890
+ recursiveUnmarkUpdatesForElements(event.detail.element);
891
+ }));
892
+ }));
893
+ document.addEventListener("turbo:submit-start", (event => {
894
+ recursiveMarkUpdatesForElements(event.target);
895
+ }));
896
+ document.addEventListener("turbo:submit-end", (event => {
897
+ setTimeout((() => {
898
+ recursiveUnmarkUpdatesForElements(event.target);
899
+ }));
900
+ }));
901
+ };
902
+
903
+ const recursiveMarkUpdatesForElements = leaf => {
904
+ const closestUpdatesFor = leaf && leaf.parentElement.closest("updates-for");
905
+ if (closestUpdatesFor) {
906
+ closestUpdatesFor.setAttribute("performing-inner-update", "");
907
+ recursiveMarkUpdatesForElements(closestUpdatesFor);
908
+ }
909
+ };
910
+
911
+ const recursiveUnmarkUpdatesForElements = leaf => {
912
+ const closestUpdatesFor = leaf && leaf.parentElement.closest("updates-for");
913
+ if (closestUpdatesFor) {
914
+ closestUpdatesFor.removeAttribute("performing-inner-update");
915
+ recursiveUnmarkUpdatesForElements(closestUpdatesFor);
916
+ }
917
+ };
918
+
919
+ const initialize = (initializeOptions = {}) => {
920
+ const {consumer: consumer} = initializeOptions;
921
+ registerInnerUpdates();
922
+ if (consumer) {
923
+ CableConsumer.setConsumer(consumer);
924
+ } else {
925
+ console.error("CableReady requires a reference to your Action Cable `consumer` for its helpers to function.\nEnsure that you have imported the `CableReady` package as well as `consumer` from your `channels` folder, then call `CableReady.initialize({ consumer })`.");
926
+ }
927
+ if (!customElements.get("stream-from")) {
928
+ customElements.define("stream-from", StreamFromElement);
929
+ }
930
+ if (!customElements.get("updates-for")) {
931
+ customElements.define("updates-for", UpdatesForElement);
932
+ }
933
+ };
934
+
935
+ const global = {
936
+ perform: perform,
937
+ performAsync: performAsync,
938
+ shouldMorphCallbacks: shouldMorphCallbacks,
939
+ didMorphCallbacks: didMorphCallbacks,
940
+ initialize: initialize,
941
+ addOperation: addOperation,
942
+ addOperations: addOperations,
943
+ version: packageInfo.version,
944
+ cable: CableConsumer,
945
+ get DOMOperations() {
946
+ console.warn("DEPRECATED: Please use `CableReady.operations` instead of `CableReady.DOMOperations`");
947
+ return OperationStore.all;
948
+ },
949
+ get operations() {
950
+ return OperationStore.all;
951
+ },
952
+ get consumer() {
953
+ return CableConsumer.consumer;
954
+ }
955
+ };
956
+
957
+ window.CableReady = global;
958
+
959
+ export { morph_callbacks as MorphCallbacks, StreamFromElement, SubscribingElement, UpdatesForElement, utils as Utils, global as default };