cable_ready 4.5.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2 -376
  3. data/Gemfile +4 -1
  4. data/Gemfile.lock +146 -144
  5. data/README.md +54 -20
  6. data/Rakefile +8 -8
  7. data/app/assets/javascripts/cable_ready.js +1269 -0
  8. data/app/assets/javascripts/cable_ready.umd.js +1190 -0
  9. data/app/channels/cable_ready/stream.rb +14 -0
  10. data/app/helpers/cable_ready/view_helper.rb +58 -0
  11. data/app/jobs/cable_ready/broadcast_job.rb +15 -0
  12. data/app/models/concerns/cable_ready/updatable/collection_updatable_callbacks.rb +21 -0
  13. data/app/models/concerns/cable_ready/updatable/collections_registry.rb +59 -0
  14. data/app/models/concerns/cable_ready/updatable/memory_cache_debounce_adapter.rb +24 -0
  15. data/app/models/concerns/cable_ready/updatable/model_updatable_callbacks.rb +33 -0
  16. data/app/models/concerns/cable_ready/updatable.rb +211 -0
  17. data/app/models/concerns/extend_has_many.rb +15 -0
  18. data/bin/standardize +1 -1
  19. data/cable_ready.gemspec +20 -6
  20. data/lib/cable_ready/broadcaster.rb +4 -3
  21. data/lib/cable_ready/cable_car.rb +19 -0
  22. data/lib/cable_ready/channel.rb +29 -31
  23. data/lib/cable_ready/channels.rb +4 -5
  24. data/lib/cable_ready/compoundable.rb +11 -0
  25. data/lib/cable_ready/config.rb +28 -1
  26. data/lib/cable_ready/engine.rb +59 -0
  27. data/lib/cable_ready/identifiable.rb +48 -0
  28. data/lib/cable_ready/importmap.rb +4 -0
  29. data/lib/cable_ready/installer.rb +224 -0
  30. data/lib/cable_ready/operation_builder.rb +80 -0
  31. data/lib/cable_ready/sanity_checker.rb +63 -0
  32. data/lib/cable_ready/stream_identifier.rb +13 -0
  33. data/lib/cable_ready/version.rb +1 -1
  34. data/lib/cable_ready.rb +23 -10
  35. data/lib/cable_ready_helper.rb +13 -0
  36. data/lib/generators/cable_ready/channel_generator.rb +110 -0
  37. data/lib/generators/cable_ready/templates/app/javascript/channels/consumer.js.tt +6 -0
  38. data/lib/generators/cable_ready/templates/app/javascript/channels/index.js.esbuild.tt +4 -0
  39. data/lib/generators/cable_ready/templates/app/javascript/channels/index.js.importmap.tt +2 -0
  40. data/lib/generators/cable_ready/templates/app/javascript/channels/index.js.shakapacker.tt +5 -0
  41. data/lib/generators/cable_ready/templates/app/javascript/channels/index.js.vite.tt +1 -0
  42. data/lib/generators/cable_ready/templates/app/javascript/channels/index.js.webpacker.tt +5 -0
  43. data/lib/generators/cable_ready/templates/app/javascript/config/cable_ready.js.tt +4 -0
  44. data/lib/generators/cable_ready/templates/app/javascript/config/index.js.tt +1 -0
  45. data/lib/generators/cable_ready/templates/app/javascript/config/mrujs.js.tt +9 -0
  46. data/lib/generators/cable_ready/templates/app/javascript/controllers/%file_name%_controller.js.tt +38 -0
  47. data/lib/generators/cable_ready/templates/app/javascript/controllers/application.js.tt +11 -0
  48. data/lib/generators/cable_ready/templates/app/javascript/controllers/index.js.esbuild.tt +7 -0
  49. data/lib/generators/cable_ready/templates/app/javascript/controllers/index.js.importmap.tt +5 -0
  50. data/lib/generators/cable_ready/templates/app/javascript/controllers/index.js.shakapacker.tt +5 -0
  51. data/lib/generators/cable_ready/templates/app/javascript/controllers/index.js.vite.tt +5 -0
  52. data/lib/generators/cable_ready/templates/app/javascript/controllers/index.js.webpacker.tt +5 -0
  53. data/lib/generators/cable_ready/templates/config/initializers/cable_ready.rb +27 -0
  54. data/lib/generators/cable_ready/templates/esbuild.config.mjs.tt +94 -0
  55. data/lib/install/action_cable.rb +144 -0
  56. data/lib/install/broadcaster.rb +109 -0
  57. data/lib/install/bundle.rb +54 -0
  58. data/lib/install/compression.rb +51 -0
  59. data/lib/install/config.rb +39 -0
  60. data/lib/install/development.rb +34 -0
  61. data/lib/install/esbuild.rb +101 -0
  62. data/lib/install/importmap.rb +96 -0
  63. data/lib/install/initializers.rb +15 -0
  64. data/lib/install/mrujs.rb +121 -0
  65. data/lib/install/npm_packages.rb +13 -0
  66. data/lib/install/shakapacker.rb +65 -0
  67. data/lib/install/spring.rb +54 -0
  68. data/lib/install/updatable.rb +34 -0
  69. data/lib/install/vite.rb +66 -0
  70. data/lib/install/webpacker.rb +93 -0
  71. data/lib/install/yarn.rb +56 -0
  72. data/lib/tasks/cable_ready/cable_ready.rake +247 -0
  73. data/package.json +42 -13
  74. data/rollup.config.mjs +57 -0
  75. data/web-test-runner.config.mjs +12 -0
  76. data/yarn.lock +3252 -327
  77. metadata +138 -9
  78. data/tags +0 -62
@@ -0,0 +1,1190 @@
1
+ (function(global, factory) {
2
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("morphdom")) : typeof define === "function" && define.amd ? define([ "exports", "morphdom" ], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self,
3
+ factory(global.CableReady = {}, global.morphdom));
4
+ })(this, (function(exports, morphdom) {
5
+ "use strict";
6
+ var name = "cable_ready";
7
+ var version = "5.0.0";
8
+ 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.";
9
+ var keywords = [ "ruby", "rails", "websockets", "actioncable", "cable", "ssr", "stimulus_reflex", "client-side", "dom" ];
10
+ var homepage = "https://cableready.stimulusreflex.com";
11
+ var bugs = "https://github.com/stimulusreflex/cable_ready/issues";
12
+ var repository = "https://github.com/stimulusreflex/cable_ready";
13
+ var license = "MIT";
14
+ var author = "Nathan Hopkins <natehop@gmail.com>";
15
+ var contributors = [ "Andrew Mason <andrewmcodes@protonmail.com>", "Julian Rubisch <julian@julianrubisch.at>", "Marco Roth <marco.roth@intergga.ch>", "Nathan Hopkins <natehop@gmail.com>" ];
16
+ var main = "./dist/cable_ready.js";
17
+ var module = "./dist/cable_ready.js";
18
+ var browser = "./dist/cable_ready.js";
19
+ var unpkg = "./dist/cable_ready.umd.js";
20
+ var umd = "./dist/cable_ready.umd.js";
21
+ var files = [ "dist/*", "javascript/*" ];
22
+ var scripts = {
23
+ lint: "yarn run format --check",
24
+ format: "yarn run prettier-standard ./javascript/**/*.js rollup.config.mjs",
25
+ build: "yarn rollup -c",
26
+ watch: "yarn rollup -wc",
27
+ test: "web-test-runner javascript/test/**/*.test.js",
28
+ "docs:dev": "vitepress dev docs",
29
+ "docs:build": "vitepress build docs && cp ./docs/_redirects ./docs/.vitepress/dist",
30
+ "docs:preview": "vitepress preview docs"
31
+ };
32
+ var dependencies = {
33
+ morphdom: "2.6.1"
34
+ };
35
+ var devDependencies = {
36
+ "@open-wc/testing": "^3.1.7",
37
+ "@rollup/plugin-json": "^6.0.0",
38
+ "@rollup/plugin-node-resolve": "^15.0.1",
39
+ "@rollup/plugin-terser": "^0.4.0",
40
+ "@web/dev-server-esbuild": "^0.3.3",
41
+ "@web/dev-server-rollup": "^0.3.21",
42
+ "@web/test-runner": "^0.15.1",
43
+ "prettier-standard": "^16.4.1",
44
+ rollup: "^3.19.1",
45
+ sinon: "^15.0.2",
46
+ vite: "^4.1.4",
47
+ vitepress: "^1.0.0-alpha.56",
48
+ "vitepress-plugin-search": "^1.0.4-alpha.19"
49
+ };
50
+ var packageInfo = {
51
+ name: name,
52
+ version: version,
53
+ description: description,
54
+ keywords: keywords,
55
+ homepage: homepage,
56
+ bugs: bugs,
57
+ repository: repository,
58
+ license: license,
59
+ author: author,
60
+ contributors: contributors,
61
+ main: main,
62
+ module: module,
63
+ browser: browser,
64
+ import: "./dist/cable_ready.js",
65
+ unpkg: unpkg,
66
+ umd: umd,
67
+ files: files,
68
+ scripts: scripts,
69
+ dependencies: dependencies,
70
+ devDependencies: devDependencies
71
+ };
72
+ const inputTags = {
73
+ INPUT: true,
74
+ TEXTAREA: true,
75
+ SELECT: true
76
+ };
77
+ const mutableTags = {
78
+ INPUT: true,
79
+ TEXTAREA: true,
80
+ OPTION: true
81
+ };
82
+ const textInputTypes = {
83
+ "datetime-local": true,
84
+ "select-multiple": true,
85
+ "select-one": true,
86
+ color: true,
87
+ date: true,
88
+ datetime: true,
89
+ email: true,
90
+ month: true,
91
+ number: true,
92
+ password: true,
93
+ range: true,
94
+ search: true,
95
+ tel: true,
96
+ text: true,
97
+ textarea: true,
98
+ time: true,
99
+ url: true,
100
+ week: true
101
+ };
102
+ let activeElement;
103
+ var ActiveElement = {
104
+ get element() {
105
+ return activeElement;
106
+ },
107
+ set(element) {
108
+ activeElement = element;
109
+ }
110
+ };
111
+ // Indicates if the passed element is considered a text input.
112
+
113
+ const isTextInput = element => inputTags[element.tagName] && textInputTypes[element.type];
114
+ // Assigns focus to the appropriate element... preferring the explicitly passed selector
115
+
116
+ // * selector - a CSS selector for the element that should have focus
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
+ // Dispatches an event on the passed element
124
+
125
+ // * element - the element
126
+ // * name - the name of the event
127
+ // * detail - the event detail
128
+
129
+ const dispatch = (element, name, detail = {}) => {
130
+ const init = {
131
+ bubbles: true,
132
+ cancelable: true,
133
+ detail: detail
134
+ };
135
+ const event = new CustomEvent(name, init);
136
+ element.dispatchEvent(event);
137
+ if (window.jQuery) window.jQuery(element).trigger(name, detail);
138
+ };
139
+ // Accepts an xPath query and returns the element found at that position in the DOM
140
+
141
+ const xpathToElement = xpath => document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
142
+ // Accepts an xPath query and returns all matching elements in the DOM
143
+
144
+ const xpathToElementArray = (xpath, reverse = false) => {
145
+ const snapshotList = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
146
+ const snapshots = [];
147
+ for (let i = 0; i < snapshotList.snapshotLength; i++) {
148
+ snapshots.push(snapshotList.snapshotItem(i));
149
+ }
150
+ return reverse ? snapshots.reverse() : snapshots;
151
+ };
152
+ // Return an array with the class names to be used
153
+
154
+ // * names - could be a string or an array of strings for multiple classes.
155
+
156
+ const getClassNames = names => Array.from(names).flat()
157
+ // Perform operation for either the first or all of the elements returned by CSS selector
158
+
159
+ // * operation - the instruction payload from perform
160
+ // * callback - the operation function to run for each element
161
+
162
+ ;
163
+ const processElements = (operation, callback) => {
164
+ Array.from(operation.selectAll ? operation.element : [ operation.element ]).forEach(callback);
165
+ };
166
+ // convert string to kebab-case
167
+ // most other implementations (lodash) are focused on camelCase to kebab-case
168
+ // instead, this uses word token boundaries to produce readable URL slugs and keys
169
+ // this implementation will not support Emoji or other non-ASCII characters
170
+
171
+ const kebabize = createCompounder((function(result, word, index) {
172
+ return result + (index ? "-" : "") + word.toLowerCase();
173
+ }));
174
+ function createCompounder(callback) {
175
+ return function(str) {
176
+ return words(str).reduce(callback, "");
177
+ };
178
+ }
179
+ const words = str => {
180
+ str = str == null ? "" : str;
181
+ return str.match(/([A-Z]{2,}|[0-9]+|[A-Z]?[a-z]+|[A-Z])/g) || [];
182
+ };
183
+ // Provide a standardized pipeline of checks and modifications to all operations based on provided options
184
+ // Currently skips execution if cancelled and implements an optional delay
185
+
186
+ const operate = (operation, callback) => {
187
+ if (!operation.cancel) {
188
+ operation.delay ? setTimeout(callback, operation.delay) : callback();
189
+ return true;
190
+ }
191
+ return false;
192
+ };
193
+ // Dispatch life-cycle events with standardized naming
194
+ const before = (target, operation) => dispatch(target, `cable-ready:before-${kebabize(operation.operation)}`, operation);
195
+ const after = (target, operation) => dispatch(target, `cable-ready:after-${kebabize(operation.operation)}`, operation);
196
+ function debounce(fn, delay = 250) {
197
+ let timer;
198
+ return (...args) => {
199
+ const callback = () => fn.apply(this, args);
200
+ if (timer) clearTimeout(timer);
201
+ timer = setTimeout(callback, delay);
202
+ };
203
+ }
204
+ function handleErrors(response) {
205
+ if (!response.ok) throw Error(response.statusText);
206
+ return response;
207
+ }
208
+ function safeScalar(val) {
209
+ if (val !== undefined && ![ "string", "number", "boolean" ].includes(typeof val)) console.warn(`Operation expects a string, number or boolean, but got ${val} (${typeof val})`);
210
+ return val != null ? val : "";
211
+ }
212
+ function safeString(str) {
213
+ if (str !== undefined && typeof str !== "string") console.warn(`Operation expects a string, but got ${str} (${typeof str})`);
214
+ return str != null ? String(str) : "";
215
+ }
216
+ function safeArray(arr) {
217
+ if (arr !== undefined && !Array.isArray(arr)) console.warn(`Operation expects an array, but got ${arr} (${typeof arr})`);
218
+ return arr != null ? Array.from(arr) : [];
219
+ }
220
+ function safeObject(obj) {
221
+ if (obj !== undefined && typeof obj !== "object") console.warn(`Operation expects an object, but got ${obj} (${typeof obj})`);
222
+ return obj != null ? Object(obj) : {};
223
+ }
224
+ function safeStringOrArray(elem) {
225
+ if (elem !== undefined && !Array.isArray(elem) && typeof elem !== "string") console.warn(`Operation expects an Array or a String, but got ${elem} (${typeof elem})`);
226
+ return elem == null ? "" : Array.isArray(elem) ? Array.from(elem) : String(elem);
227
+ }
228
+ function fragmentToString(fragment) {
229
+ return (new XMLSerializer).serializeToString(fragment);
230
+ }
231
+ // A proxy method to wrap a fetch call in error handling
232
+
233
+ // * url - the URL to fetch
234
+ // * additionalHeaders - an object of additional headers passed to fetch
235
+
236
+ async function graciouslyFetch(url, additionalHeaders) {
237
+ try {
238
+ const response = await fetch(url, {
239
+ headers: {
240
+ "X-REQUESTED-WITH": "XmlHttpRequest",
241
+ ...additionalHeaders
242
+ }
243
+ });
244
+ if (response == undefined) return;
245
+ handleErrors(response);
246
+ return response;
247
+ } catch (e) {
248
+ console.error(`Could not fetch ${url}`);
249
+ }
250
+ }
251
+ var utils = Object.freeze({
252
+ __proto__: null,
253
+ after: after,
254
+ assignFocus: assignFocus,
255
+ before: before,
256
+ debounce: debounce,
257
+ dispatch: dispatch,
258
+ fragmentToString: fragmentToString,
259
+ getClassNames: getClassNames,
260
+ graciouslyFetch: graciouslyFetch,
261
+ handleErrors: handleErrors,
262
+ isTextInput: isTextInput,
263
+ kebabize: kebabize,
264
+ operate: operate,
265
+ processElements: processElements,
266
+ safeArray: safeArray,
267
+ safeObject: safeObject,
268
+ safeScalar: safeScalar,
269
+ safeString: safeString,
270
+ safeStringOrArray: safeStringOrArray,
271
+ xpathToElement: xpathToElement,
272
+ xpathToElementArray: xpathToElementArray
273
+ });
274
+ // Indicates whether or not we should morph an element via onBeforeElUpdated callback
275
+ // SEE: https://github.com/patrick-steele-idem/morphdom#morphdomfromnode-tonode-options--node
276
+
277
+ const shouldMorph = operation => (fromEl, toEl) => !shouldMorphCallbacks.map((callback => typeof callback === "function" ? callback(operation, fromEl, toEl) : true)).includes(false)
278
+ // Execute any pluggable functions that modify elements after morphing via onElUpdated callback
279
+
280
+ ;
281
+ const didMorph = operation => el => {
282
+ didMorphCallbacks.forEach((callback => {
283
+ if (typeof callback === "function") callback(operation, el);
284
+ }));
285
+ };
286
+ const verifyNotMutable = (detail, fromEl, toEl) => {
287
+ // Skip nodes that are equal:
288
+ // https://github.com/patrick-steele-idem/morphdom#can-i-make-morphdom-blaze-through-the-dom-tree-even-faster-yes
289
+ if (!mutableTags[fromEl.tagName] && fromEl.isEqualNode(toEl)) return false;
290
+ return true;
291
+ };
292
+ const verifyNotContentEditable = (detail, fromEl, toEl) => {
293
+ if (fromEl === ActiveElement.element && fromEl.isContentEditable) return false;
294
+ return true;
295
+ };
296
+ const verifyNotPermanent = (detail, fromEl, toEl) => {
297
+ const {permanentAttributeName: permanentAttributeName} = detail;
298
+ if (!permanentAttributeName) return true;
299
+ const permanent = fromEl.closest(`[${permanentAttributeName}]`);
300
+ // only morph attributes on the active non-permanent text input
301
+ if (!permanent && fromEl === ActiveElement.element && isTextInput(fromEl)) {
302
+ const ignore = {
303
+ value: true
304
+ };
305
+ Array.from(toEl.attributes).forEach((attribute => {
306
+ if (!ignore[attribute.name]) fromEl.setAttribute(attribute.name, attribute.value);
307
+ }));
308
+ return false;
309
+ }
310
+ return !permanent;
311
+ };
312
+ const shouldMorphCallbacks = [ verifyNotMutable, verifyNotPermanent, verifyNotContentEditable ];
313
+ const didMorphCallbacks = [];
314
+ var morph_callbacks = Object.freeze({
315
+ __proto__: null,
316
+ didMorph: didMorph,
317
+ didMorphCallbacks: didMorphCallbacks,
318
+ shouldMorph: shouldMorph,
319
+ shouldMorphCallbacks: shouldMorphCallbacks,
320
+ verifyNotContentEditable: verifyNotContentEditable,
321
+ verifyNotMutable: verifyNotMutable,
322
+ verifyNotPermanent: verifyNotPermanent
323
+ });
324
+ var Operations = {
325
+ // DOM Mutations
326
+ append: operation => {
327
+ processElements(operation, (element => {
328
+ before(element, operation);
329
+ operate(operation, (() => {
330
+ const {html: html, focusSelector: focusSelector} = operation;
331
+ element.insertAdjacentHTML("beforeend", safeScalar(html));
332
+ assignFocus(focusSelector);
333
+ }));
334
+ after(element, operation);
335
+ }));
336
+ },
337
+ graft: operation => {
338
+ processElements(operation, (element => {
339
+ before(element, operation);
340
+ operate(operation, (() => {
341
+ const {parent: parent, focusSelector: focusSelector} = operation;
342
+ const parentElement = document.querySelector(parent);
343
+ if (parentElement) {
344
+ parentElement.appendChild(element);
345
+ assignFocus(focusSelector);
346
+ }
347
+ }));
348
+ after(element, operation);
349
+ }));
350
+ },
351
+ innerHtml: operation => {
352
+ processElements(operation, (element => {
353
+ before(element, operation);
354
+ operate(operation, (() => {
355
+ const {html: html, focusSelector: focusSelector} = operation;
356
+ element.innerHTML = safeScalar(html);
357
+ assignFocus(focusSelector);
358
+ }));
359
+ after(element, operation);
360
+ }));
361
+ },
362
+ insertAdjacentHtml: operation => {
363
+ processElements(operation, (element => {
364
+ before(element, operation);
365
+ operate(operation, (() => {
366
+ const {html: html, position: position, focusSelector: focusSelector} = operation;
367
+ element.insertAdjacentHTML(position || "beforeend", safeScalar(html));
368
+ assignFocus(focusSelector);
369
+ }));
370
+ after(element, operation);
371
+ }));
372
+ },
373
+ insertAdjacentText: operation => {
374
+ processElements(operation, (element => {
375
+ before(element, operation);
376
+ operate(operation, (() => {
377
+ const {text: text, position: position, focusSelector: focusSelector} = operation;
378
+ element.insertAdjacentText(position || "beforeend", safeScalar(text));
379
+ assignFocus(focusSelector);
380
+ }));
381
+ after(element, operation);
382
+ }));
383
+ },
384
+ outerHtml: operation => {
385
+ processElements(operation, (element => {
386
+ const parent = element.parentElement;
387
+ const idx = parent && Array.from(parent.children).indexOf(element);
388
+ before(element, operation);
389
+ operate(operation, (() => {
390
+ const {html: html, focusSelector: focusSelector} = operation;
391
+ element.outerHTML = safeScalar(html);
392
+ assignFocus(focusSelector);
393
+ }));
394
+ after(parent ? parent.children[idx] : document.documentElement, operation);
395
+ }));
396
+ },
397
+ prepend: operation => {
398
+ processElements(operation, (element => {
399
+ before(element, operation);
400
+ operate(operation, (() => {
401
+ const {html: html, focusSelector: focusSelector} = operation;
402
+ element.insertAdjacentHTML("afterbegin", safeScalar(html));
403
+ assignFocus(focusSelector);
404
+ }));
405
+ after(element, operation);
406
+ }));
407
+ },
408
+ remove: operation => {
409
+ processElements(operation, (element => {
410
+ before(element, operation);
411
+ operate(operation, (() => {
412
+ const {focusSelector: focusSelector} = operation;
413
+ element.remove();
414
+ assignFocus(focusSelector);
415
+ }));
416
+ after(document, operation);
417
+ }));
418
+ },
419
+ replace: operation => {
420
+ processElements(operation, (element => {
421
+ const parent = element.parentElement;
422
+ const idx = parent && Array.from(parent.children).indexOf(element);
423
+ before(element, operation);
424
+ operate(operation, (() => {
425
+ const {html: html, focusSelector: focusSelector} = operation;
426
+ element.outerHTML = safeScalar(html);
427
+ assignFocus(focusSelector);
428
+ }));
429
+ after(parent ? parent.children[idx] : document.documentElement, operation);
430
+ }));
431
+ },
432
+ textContent: operation => {
433
+ processElements(operation, (element => {
434
+ before(element, operation);
435
+ operate(operation, (() => {
436
+ const {text: text, focusSelector: focusSelector} = operation;
437
+ element.textContent = safeScalar(text);
438
+ assignFocus(focusSelector);
439
+ }));
440
+ after(element, operation);
441
+ }));
442
+ },
443
+ // Element Property Mutations
444
+ addCssClass: operation => {
445
+ processElements(operation, (element => {
446
+ before(element, operation);
447
+ operate(operation, (() => {
448
+ const {name: name} = operation;
449
+ element.classList.add(...getClassNames([ safeStringOrArray(name) ]));
450
+ }));
451
+ after(element, operation);
452
+ }));
453
+ },
454
+ removeAttribute: operation => {
455
+ processElements(operation, (element => {
456
+ before(element, operation);
457
+ operate(operation, (() => {
458
+ const {name: name} = operation;
459
+ element.removeAttribute(safeString(name));
460
+ }));
461
+ after(element, operation);
462
+ }));
463
+ },
464
+ removeCssClass: operation => {
465
+ processElements(operation, (element => {
466
+ before(element, operation);
467
+ operate(operation, (() => {
468
+ const {name: name} = operation;
469
+ element.classList.remove(...getClassNames([ safeStringOrArray(name) ]));
470
+ if (element.classList.length === 0) element.removeAttribute("class");
471
+ }));
472
+ after(element, operation);
473
+ }));
474
+ },
475
+ setAttribute: operation => {
476
+ processElements(operation, (element => {
477
+ before(element, operation);
478
+ operate(operation, (() => {
479
+ const {name: name, value: value} = operation;
480
+ element.setAttribute(safeString(name), safeScalar(value));
481
+ }));
482
+ after(element, operation);
483
+ }));
484
+ },
485
+ setDatasetProperty: operation => {
486
+ processElements(operation, (element => {
487
+ before(element, operation);
488
+ operate(operation, (() => {
489
+ const {name: name, value: value} = operation;
490
+ element.dataset[safeString(name)] = safeScalar(value);
491
+ }));
492
+ after(element, operation);
493
+ }));
494
+ },
495
+ setProperty: operation => {
496
+ processElements(operation, (element => {
497
+ before(element, operation);
498
+ operate(operation, (() => {
499
+ const {name: name, value: value} = operation;
500
+ if (name in element) element[safeString(name)] = safeScalar(value);
501
+ }));
502
+ after(element, operation);
503
+ }));
504
+ },
505
+ setStyle: operation => {
506
+ processElements(operation, (element => {
507
+ before(element, operation);
508
+ operate(operation, (() => {
509
+ const {name: name, value: value} = operation;
510
+ element.style[safeString(name)] = safeScalar(value);
511
+ }));
512
+ after(element, operation);
513
+ }));
514
+ },
515
+ setStyles: operation => {
516
+ processElements(operation, (element => {
517
+ before(element, operation);
518
+ operate(operation, (() => {
519
+ const {styles: styles} = operation;
520
+ for (let [name, value] of Object.entries(styles)) element.style[safeString(name)] = safeScalar(value);
521
+ }));
522
+ after(element, operation);
523
+ }));
524
+ },
525
+ setValue: operation => {
526
+ processElements(operation, (element => {
527
+ before(element, operation);
528
+ operate(operation, (() => {
529
+ const {value: value} = operation;
530
+ element.value = safeScalar(value);
531
+ }));
532
+ after(element, operation);
533
+ }));
534
+ },
535
+ // DOM Events and Meta-Operations
536
+ dispatchEvent: operation => {
537
+ processElements(operation, (element => {
538
+ before(element, operation);
539
+ operate(operation, (() => {
540
+ const {name: name, detail: detail} = operation;
541
+ dispatch(element, safeString(name), safeObject(detail));
542
+ }));
543
+ after(element, operation);
544
+ }));
545
+ },
546
+ setMeta: operation => {
547
+ before(document, operation);
548
+ operate(operation, (() => {
549
+ const {name: name, content: content} = operation;
550
+ let meta = document.head.querySelector(`meta[name='${name}']`);
551
+ if (!meta) {
552
+ meta = document.createElement("meta");
553
+ meta.name = safeString(name);
554
+ document.head.appendChild(meta);
555
+ }
556
+ meta.content = safeScalar(content);
557
+ }));
558
+ after(document, operation);
559
+ },
560
+ setTitle: operation => {
561
+ before(document, operation);
562
+ operate(operation, (() => {
563
+ const {title: title} = operation;
564
+ document.title = safeScalar(title);
565
+ }));
566
+ after(document, operation);
567
+ },
568
+ // Browser Manipulations
569
+ clearStorage: operation => {
570
+ before(document, operation);
571
+ operate(operation, (() => {
572
+ const {type: type} = operation;
573
+ const storage = type === "session" ? sessionStorage : localStorage;
574
+ storage.clear();
575
+ }));
576
+ after(document, operation);
577
+ },
578
+ go: operation => {
579
+ before(window, operation);
580
+ operate(operation, (() => {
581
+ const {delta: delta} = operation;
582
+ history.go(delta);
583
+ }));
584
+ after(window, operation);
585
+ },
586
+ pushState: operation => {
587
+ before(window, operation);
588
+ operate(operation, (() => {
589
+ const {state: state, title: title, url: url} = operation;
590
+ history.pushState(safeObject(state), safeString(title), safeString(url));
591
+ }));
592
+ after(window, operation);
593
+ },
594
+ redirectTo: operation => {
595
+ before(window, operation);
596
+ operate(operation, (() => {
597
+ let {url: url, action: action, turbo: turbo} = operation;
598
+ action = action || "advance";
599
+ url = safeString(url);
600
+ if (turbo === undefined) turbo = true;
601
+ if (turbo) {
602
+ if (window.Turbo) window.Turbo.visit(url, {
603
+ action: action
604
+ });
605
+ if (window.Turbolinks) window.Turbolinks.visit(url, {
606
+ action: action
607
+ });
608
+ if (!window.Turbo && !window.Turbolinks) window.location.href = url;
609
+ } else {
610
+ window.location.href = url;
611
+ }
612
+ }));
613
+ after(window, operation);
614
+ },
615
+ reload: operation => {
616
+ before(window, operation);
617
+ operate(operation, (() => {
618
+ window.location.reload();
619
+ }));
620
+ after(window, operation);
621
+ },
622
+ removeStorageItem: operation => {
623
+ before(document, operation);
624
+ operate(operation, (() => {
625
+ const {key: key, type: type} = operation;
626
+ const storage = type === "session" ? sessionStorage : localStorage;
627
+ storage.removeItem(safeString(key));
628
+ }));
629
+ after(document, operation);
630
+ },
631
+ replaceState: operation => {
632
+ before(window, operation);
633
+ operate(operation, (() => {
634
+ const {state: state, title: title, url: url} = operation;
635
+ history.replaceState(safeObject(state), safeString(title), safeString(url));
636
+ }));
637
+ after(window, operation);
638
+ },
639
+ scrollIntoView: operation => {
640
+ const {element: element} = operation;
641
+ before(element, operation);
642
+ operate(operation, (() => {
643
+ element.scrollIntoView(operation);
644
+ }));
645
+ after(element, operation);
646
+ },
647
+ setCookie: operation => {
648
+ before(document, operation);
649
+ operate(operation, (() => {
650
+ const {cookie: cookie} = operation;
651
+ document.cookie = safeScalar(cookie);
652
+ }));
653
+ after(document, operation);
654
+ },
655
+ setFocus: operation => {
656
+ const {element: element} = operation;
657
+ before(element, operation);
658
+ operate(operation, (() => {
659
+ assignFocus(element);
660
+ }));
661
+ after(element, operation);
662
+ },
663
+ setStorageItem: operation => {
664
+ before(document, operation);
665
+ operate(operation, (() => {
666
+ const {key: key, value: value, type: type} = operation;
667
+ const storage = type === "session" ? sessionStorage : localStorage;
668
+ storage.setItem(safeString(key), safeScalar(value));
669
+ }));
670
+ after(document, operation);
671
+ },
672
+ // Notifications
673
+ consoleLog: operation => {
674
+ before(document, operation);
675
+ operate(operation, (() => {
676
+ const {message: message, level: level} = operation;
677
+ level && [ "warn", "info", "error" ].includes(level) ? console[level](message) : console.log(message);
678
+ }));
679
+ after(document, operation);
680
+ },
681
+ consoleTable: operation => {
682
+ before(document, operation);
683
+ operate(operation, (() => {
684
+ const {data: data, columns: columns} = operation;
685
+ console.table(data, safeArray(columns));
686
+ }));
687
+ after(document, operation);
688
+ },
689
+ notification: operation => {
690
+ before(document, operation);
691
+ operate(operation, (() => {
692
+ const {title: title, options: options} = operation;
693
+ Notification.requestPermission().then((result => {
694
+ operation.permission = result;
695
+ if (result === "granted") new Notification(safeString(title), safeObject(options));
696
+ }));
697
+ }));
698
+ after(document, operation);
699
+ },
700
+ // Morph operations
701
+ morph: operation => {
702
+ processElements(operation, (element => {
703
+ const {html: html} = operation;
704
+ const template = document.createElement("template");
705
+ template.innerHTML = String(safeScalar(html)).trim();
706
+ operation.content = template.content;
707
+ const parent = element.parentElement;
708
+ const idx = parent && Array.from(parent.children).indexOf(element);
709
+ before(element, operation);
710
+ operate(operation, (() => {
711
+ const {childrenOnly: childrenOnly, focusSelector: focusSelector} = operation;
712
+ morphdom(element, childrenOnly ? template.content : template.innerHTML, {
713
+ childrenOnly: !!childrenOnly,
714
+ onBeforeElUpdated: shouldMorph(operation),
715
+ onElUpdated: didMorph(operation)
716
+ });
717
+ assignFocus(focusSelector);
718
+ }));
719
+ after(parent ? parent.children[idx] : document.documentElement, operation);
720
+ }));
721
+ }
722
+ };
723
+ let operations = Operations;
724
+ const add = newOperations => {
725
+ operations = {
726
+ ...operations,
727
+ ...newOperations
728
+ };
729
+ };
730
+ const addOperations = operations => {
731
+ add(operations);
732
+ };
733
+ const addOperation = (name, operation) => {
734
+ const operations = {};
735
+ operations[name] = operation;
736
+ add(operations);
737
+ };
738
+ var OperationStore = {
739
+ get all() {
740
+ return operations;
741
+ }
742
+ };
743
+ let missingElement = "warn";
744
+ var MissingElement$1 = {
745
+ get behavior() {
746
+ return missingElement;
747
+ },
748
+ set(value) {
749
+ if ([ "warn", "ignore", "event", "exception" ].includes(value)) missingElement = value; else console.warn("Invalid 'onMissingElement' option. Defaulting to 'warn'.");
750
+ }
751
+ };
752
+ const perform = (operations, options = {
753
+ onMissingElement: MissingElement$1.behavior
754
+ }) => {
755
+ const batches = {};
756
+ operations.forEach((operation => {
757
+ if (!!operation.batch) batches[operation.batch] = batches[operation.batch] ? ++batches[operation.batch] : 1;
758
+ }));
759
+ operations.forEach((operation => {
760
+ const name = operation.operation;
761
+ try {
762
+ if (operation.selector) {
763
+ if (operation.xpath) {
764
+ operation.element = operation.selectAll ? xpathToElementArray(operation.selector) : xpathToElement(operation.selector);
765
+ } else {
766
+ operation.element = operation.selectAll ? document.querySelectorAll(operation.selector) : document.querySelector(operation.selector);
767
+ }
768
+ } else {
769
+ operation.element = document;
770
+ }
771
+ if (operation.element || options.onMissingElement !== "ignore") {
772
+ ActiveElement.set(document.activeElement);
773
+ const cableReadyOperation = OperationStore.all[name];
774
+ if (cableReadyOperation) {
775
+ cableReadyOperation(operation);
776
+ if (!!operation.batch && --batches[operation.batch] === 0) dispatch(document, "cable-ready:batch-complete", {
777
+ batch: operation.batch
778
+ });
779
+ } else {
780
+ console.error(`CableReady couldn't find the "${name}" operation. Make sure you use the camelized form when calling an operation method.`);
781
+ }
782
+ }
783
+ } catch (e) {
784
+ if (operation.element) {
785
+ 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.`);
786
+ console.error(e);
787
+ } else {
788
+ const warning = `CableReady ${name || ""} operation failed due to missing DOM element for selector: '${operation.selector}'`;
789
+ switch (options.onMissingElement) {
790
+ case "ignore":
791
+ break;
792
+
793
+ case "event":
794
+ dispatch(document, "cable-ready:missing-element", {
795
+ warning: warning,
796
+ operation: operation
797
+ });
798
+ break;
799
+
800
+ case "exception":
801
+ throw warning;
802
+
803
+ default:
804
+ console.warn(warning);
805
+ }
806
+ }
807
+ }
808
+ }));
809
+ };
810
+ const performAsync = (operations, options = {
811
+ onMissingElement: MissingElement$1.behavior
812
+ }) => new Promise(((resolve, reject) => {
813
+ try {
814
+ resolve(perform(operations, options));
815
+ } catch (err) {
816
+ reject(err);
817
+ }
818
+ }));
819
+ class SubscribingElement extends HTMLElement {
820
+ static get tagName() {
821
+ throw new Error("Implement the tagName() getter in the inheriting class");
822
+ }
823
+ static define() {
824
+ if (!customElements.get(this.tagName)) {
825
+ customElements.define(this.tagName, this);
826
+ }
827
+ }
828
+ disconnectedCallback() {
829
+ if (this.channel) this.channel.unsubscribe();
830
+ }
831
+ createSubscription(consumer, channel, receivedCallback) {
832
+ this.channel = consumer.subscriptions.create({
833
+ channel: channel,
834
+ identifier: this.identifier
835
+ }, {
836
+ received: receivedCallback
837
+ });
838
+ }
839
+ get preview() {
840
+ return document.documentElement.hasAttribute("data-turbolinks-preview") || document.documentElement.hasAttribute("data-turbo-preview");
841
+ }
842
+ get identifier() {
843
+ return this.getAttribute("identifier");
844
+ }
845
+ }
846
+ let consumer;
847
+ const BACKOFF = [ 25, 50, 75, 100, 200, 250, 500, 800, 1e3, 2e3 ];
848
+ const wait = ms => new Promise((resolve => setTimeout(resolve, ms)));
849
+ const getConsumerWithRetry = async (retry = 0) => {
850
+ if (consumer) return consumer;
851
+ if (retry >= BACKOFF.length) {
852
+ throw new Error("Couldn't obtain a Action Cable consumer within 5s");
853
+ }
854
+ await wait(BACKOFF[retry]);
855
+ return await getConsumerWithRetry(retry + 1);
856
+ };
857
+ var CableConsumer = {
858
+ setConsumer(value) {
859
+ consumer = value;
860
+ },
861
+ get consumer() {
862
+ return consumer;
863
+ },
864
+ async getConsumer() {
865
+ return await getConsumerWithRetry();
866
+ }
867
+ };
868
+ class StreamFromElement extends SubscribingElement {
869
+ static get tagName() {
870
+ return "cable-ready-stream-from";
871
+ }
872
+ async connectedCallback() {
873
+ if (this.preview) return;
874
+ const consumer = await CableConsumer.getConsumer();
875
+ if (consumer) {
876
+ this.createSubscription(consumer, "CableReady::Stream", this.performOperations.bind(this));
877
+ } else {
878
+ console.error("The `cable_ready_stream_from` helper cannot connect. You must initialize CableReady with an Action Cable consumer.");
879
+ }
880
+ }
881
+ performOperations(data) {
882
+ if (data.cableReady) perform(data.operations, {
883
+ onMissingElement: this.onMissingElement
884
+ });
885
+ }
886
+ get onMissingElement() {
887
+ const value = this.getAttribute("missing") || MissingElement$1.behavior;
888
+ // stream_from does not support raising exceptions on missing elements because there's no way to catch them
889
+ if ([ "warn", "ignore", "event" ].includes(value)) return value; else {
890
+ console.warn("Invalid 'missing' attribute. Defaulting to 'warn'.");
891
+ return "warn";
892
+ }
893
+ }
894
+ }
895
+ let debugging = false;
896
+ var Debug = {
897
+ get enabled() {
898
+ return debugging;
899
+ },
900
+ get disabled() {
901
+ return !debugging;
902
+ },
903
+ get value() {
904
+ return debugging;
905
+ },
906
+ set(value) {
907
+ debugging = !!value;
908
+ },
909
+ set debug(value) {
910
+ debugging = !!value;
911
+ }
912
+ };
913
+ const request = (data, blocks) => {
914
+ if (Debug.disabled) return;
915
+ console.log(`↑ Updatable request affecting ${blocks.length} element(s): `, {
916
+ elements: blocks.map((b => b.element)),
917
+ identifiers: blocks.map((b => b.element.getAttribute("identifier"))),
918
+ data: data
919
+ });
920
+ };
921
+ const cancel = (timestamp, reason) => {
922
+ if (Debug.disabled) return;
923
+ const duration = new Date - timestamp;
924
+ console.log(`❌ Updatable request canceled after ${duration}ms: ${reason}`);
925
+ };
926
+ const response = (timestamp, element, urls) => {
927
+ if (Debug.disabled) return;
928
+ const duration = new Date - timestamp;
929
+ console.log(`↓ Updatable response: All URLs fetched in ${duration}ms`, {
930
+ element: element,
931
+ urls: urls
932
+ });
933
+ };
934
+ const morphStart = (timestamp, element) => {
935
+ if (Debug.disabled) return;
936
+ const duration = new Date - timestamp;
937
+ console.log(`↻ Updatable morph: starting after ${duration}ms`, {
938
+ element: element
939
+ });
940
+ };
941
+ const morphEnd = (timestamp, element) => {
942
+ if (Debug.disabled) return;
943
+ const duration = new Date - timestamp;
944
+ console.log(`↺ Updatable morph: completed after ${duration}ms`, {
945
+ element: element
946
+ });
947
+ };
948
+ var Log = {
949
+ request: request,
950
+ cancel: cancel,
951
+ response: response,
952
+ morphStart: morphStart,
953
+ morphEnd: morphEnd
954
+ };
955
+ const template = `\n<style>\n :host {\n display: block;\n }\n</style>\n<slot></slot>\n`;
956
+ class UpdatesForElement extends SubscribingElement {
957
+ static get tagName() {
958
+ return "cable-ready-updates-for";
959
+ }
960
+ constructor() {
961
+ super();
962
+ const shadowRoot = this.attachShadow({
963
+ mode: "open"
964
+ });
965
+ shadowRoot.innerHTML = template;
966
+ }
967
+ async connectedCallback() {
968
+ if (this.preview) return;
969
+ this.update = debounce(this.update.bind(this), this.debounce);
970
+ const consumer = await CableConsumer.getConsumer();
971
+ if (consumer) {
972
+ this.createSubscription(consumer, "CableReady::Stream", this.update);
973
+ } else {
974
+ console.error("The `cable_ready_updates_for` helper cannot connect. You must initialize CableReady with an Action Cable consumer.");
975
+ }
976
+ }
977
+ async update(data) {
978
+ this.lastUpdateTimestamp = new Date;
979
+ const blocks = Array.from(document.querySelectorAll(this.query), (element => new Block(element))).filter((block => block.shouldUpdate(data)));
980
+ Log.request(data, blocks);
981
+ if (blocks.length === 0) {
982
+ Log.cancel(this.lastUpdateTimestamp, "All elements filtered out");
983
+ return;
984
+ }
985
+ // first <cable-ready-updates-for> element in the DOM *at any given moment* updates all of the others
986
+ if (blocks[0].element !== this) {
987
+ Log.cancel(this.lastUpdateTimestamp, "Update already requested");
988
+ return;
989
+ }
990
+ // hold a reference to the active element so that it can be restored after the morph
991
+ ActiveElement.set(document.activeElement);
992
+ // store all retrieved HTML in an object keyed by URL to minimize fetch calls
993
+ this.html = {};
994
+ const uniqueUrls = [ ...new Set(blocks.map((block => block.url))) ];
995
+ await Promise.all(uniqueUrls.map((async url => {
996
+ if (!this.html.hasOwnProperty(url)) {
997
+ const response = await graciouslyFetch(url, {
998
+ "X-Cable-Ready": "update"
999
+ });
1000
+ this.html[url] = await response.text();
1001
+ }
1002
+ })));
1003
+ Log.response(this.lastUpdateTimestamp, this, uniqueUrls);
1004
+ // track current block index for each URL; referred to as fragments
1005
+ this.index = {};
1006
+ blocks.forEach((block => {
1007
+ // if the block's URL is not in the index, initialize it to 0; otherwise, increment it
1008
+ this.index.hasOwnProperty(block.url) ? this.index[block.url]++ : this.index[block.url] = 0;
1009
+ block.process(data, this.html, this.index, this.lastUpdateTimestamp);
1010
+ }));
1011
+ }
1012
+ get query() {
1013
+ return `${this.tagName}[identifier="${this.identifier}"]`;
1014
+ }
1015
+ get identifier() {
1016
+ return this.getAttribute("identifier");
1017
+ }
1018
+ get debounce() {
1019
+ return this.hasAttribute("debounce") ? parseInt(this.getAttribute("debounce")) : 20;
1020
+ }
1021
+ }
1022
+ class Block {
1023
+ constructor(element) {
1024
+ this.element = element;
1025
+ }
1026
+ async process(data, html, index, startTimestamp) {
1027
+ const blockIndex = index[this.url];
1028
+ const template = document.createElement("template");
1029
+ this.element.setAttribute("updating", "updating");
1030
+ template.innerHTML = String(html[this.url]).trim();
1031
+ await this.resolveTurboFrames(template.content);
1032
+ const fragments = template.content.querySelectorAll(this.query);
1033
+ if (fragments.length <= blockIndex) {
1034
+ console.warn(`Update aborted due to insufficient number of elements. The offending url is ${this.url}.`);
1035
+ return;
1036
+ }
1037
+ const operation = {
1038
+ element: this.element,
1039
+ html: fragments[blockIndex],
1040
+ permanentAttributeName: "data-ignore-updates"
1041
+ };
1042
+ dispatch(this.element, "cable-ready:before-update", operation);
1043
+ Log.morphStart(startTimestamp, this.element);
1044
+ morphdom(this.element, fragments[blockIndex], {
1045
+ childrenOnly: true,
1046
+ onBeforeElUpdated: shouldMorph(operation),
1047
+ onElUpdated: _ => {
1048
+ this.element.removeAttribute("updating");
1049
+ dispatch(this.element, "cable-ready:after-update", operation);
1050
+ assignFocus(operation.focusSelector);
1051
+ }
1052
+ });
1053
+ Log.morphEnd(startTimestamp, this.element);
1054
+ }
1055
+ async resolveTurboFrames(documentFragment) {
1056
+ const reloadingTurboFrames = [ ...documentFragment.querySelectorAll('turbo-frame[src]:not([loading="lazy"])') ];
1057
+ return Promise.all(reloadingTurboFrames.map((frame => new Promise((async resolve => {
1058
+ const frameResponse = await graciouslyFetch(frame.getAttribute("src"), {
1059
+ "Turbo-Frame": frame.id,
1060
+ "X-Cable-Ready": "update"
1061
+ });
1062
+ const frameTemplate = document.createElement("template");
1063
+ frameTemplate.innerHTML = await frameResponse.text();
1064
+ // recurse here to get all nested eager loaded frames
1065
+ await this.resolveTurboFrames(frameTemplate.content);
1066
+ const selector = `turbo-frame#${frame.id}`;
1067
+ const frameContent = frameTemplate.content.querySelector(selector);
1068
+ const content = frameContent ? frameContent.innerHTML.trim() : "";
1069
+ documentFragment.querySelector(selector).innerHTML = content;
1070
+ resolve();
1071
+ })))));
1072
+ }
1073
+ shouldUpdate(data) {
1074
+ // if everything that could prevent an update is false, update this block
1075
+ return !this.ignoresInnerUpdates && this.hasChangesSelectedForUpdate(data);
1076
+ }
1077
+ hasChangesSelectedForUpdate(data) {
1078
+ // if there's an only attribute, only update if at least one of the attributes changed is in the allow list
1079
+ const only = this.element.getAttribute("only");
1080
+ return !(only && data.changed && !only.split(" ").some((attribute => data.changed.includes(attribute))));
1081
+ }
1082
+ get ignoresInnerUpdates() {
1083
+ // don't update during a Reflex or Turbolinks redraw
1084
+ return this.element.hasAttribute("ignore-inner-updates") && this.element.hasAttribute("performing-inner-update");
1085
+ }
1086
+ get url() {
1087
+ return this.element.hasAttribute("url") ? this.element.getAttribute("url") : location.href;
1088
+ }
1089
+ get identifier() {
1090
+ return this.element.identifier;
1091
+ }
1092
+ get query() {
1093
+ return this.element.query;
1094
+ }
1095
+ }
1096
+ const registerInnerUpdates = () => {
1097
+ document.addEventListener("stimulus-reflex:before", (event => {
1098
+ recursiveMarkUpdatesForElements(event.detail.element);
1099
+ }));
1100
+ document.addEventListener("stimulus-reflex:after", (event => {
1101
+ setTimeout((() => {
1102
+ recursiveUnmarkUpdatesForElements(event.detail.element);
1103
+ }));
1104
+ }));
1105
+ document.addEventListener("turbo:submit-start", (event => {
1106
+ recursiveMarkUpdatesForElements(event.target);
1107
+ }));
1108
+ document.addEventListener("turbo:submit-end", (event => {
1109
+ setTimeout((() => {
1110
+ recursiveUnmarkUpdatesForElements(event.target);
1111
+ }));
1112
+ }));
1113
+ document.addEventListener("turbo-boost:command:start", (event => {
1114
+ recursiveMarkUpdatesForElements(event.target);
1115
+ }));
1116
+ document.addEventListener("turbo-boost:command:finish", (event => {
1117
+ setTimeout((() => {
1118
+ recursiveUnmarkUpdatesForElements(event.target);
1119
+ }));
1120
+ }));
1121
+ document.addEventListener("turbo-boost:command:error", (event => {
1122
+ setTimeout((() => {
1123
+ recursiveUnmarkUpdatesForElements(event.target);
1124
+ }));
1125
+ }));
1126
+ };
1127
+ const recursiveMarkUpdatesForElements = leaf => {
1128
+ const closestUpdatesFor = leaf && leaf.parentElement && leaf.parentElement.closest("cable-ready-updates-for");
1129
+ if (closestUpdatesFor) {
1130
+ closestUpdatesFor.setAttribute("performing-inner-update", "");
1131
+ recursiveMarkUpdatesForElements(closestUpdatesFor);
1132
+ }
1133
+ };
1134
+ const recursiveUnmarkUpdatesForElements = leaf => {
1135
+ const closestUpdatesFor = leaf && leaf.parentElement && leaf.parentElement.closest("cable-ready-updates-for");
1136
+ if (closestUpdatesFor) {
1137
+ closestUpdatesFor.removeAttribute("performing-inner-update");
1138
+ recursiveUnmarkUpdatesForElements(closestUpdatesFor);
1139
+ }
1140
+ };
1141
+ const defineElements = () => {
1142
+ registerInnerUpdates();
1143
+ StreamFromElement.define();
1144
+ UpdatesForElement.define();
1145
+ };
1146
+ const initialize = (initializeOptions = {}) => {
1147
+ const {consumer: consumer, onMissingElement: onMissingElement, debug: debug} = initializeOptions;
1148
+ Debug.set(!!debug);
1149
+ if (consumer) {
1150
+ CableConsumer.setConsumer(consumer);
1151
+ } else {
1152
+ 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 })`.");
1153
+ }
1154
+ if (onMissingElement) {
1155
+ MissingElement.set(onMissingElement);
1156
+ }
1157
+ defineElements();
1158
+ };
1159
+ const global = {
1160
+ perform: perform,
1161
+ performAsync: performAsync,
1162
+ shouldMorphCallbacks: shouldMorphCallbacks,
1163
+ didMorphCallbacks: didMorphCallbacks,
1164
+ initialize: initialize,
1165
+ addOperation: addOperation,
1166
+ addOperations: addOperations,
1167
+ version: packageInfo.version,
1168
+ cable: CableConsumer,
1169
+ get DOMOperations() {
1170
+ console.warn("DEPRECATED: Please use `CableReady.operations` instead of `CableReady.DOMOperations`");
1171
+ return OperationStore.all;
1172
+ },
1173
+ get operations() {
1174
+ return OperationStore.all;
1175
+ },
1176
+ get consumer() {
1177
+ return CableConsumer.consumer;
1178
+ }
1179
+ };
1180
+ window.CableReady = global;
1181
+ exports.MorphCallbacks = morph_callbacks;
1182
+ exports.StreamFromElement = StreamFromElement;
1183
+ exports.SubscribingElement = SubscribingElement;
1184
+ exports.UpdatesForElement = UpdatesForElement;
1185
+ exports.Utils = utils;
1186
+ exports.default = global;
1187
+ Object.defineProperty(exports, "__esModule", {
1188
+ value: true
1189
+ });
1190
+ }));