cable_ready 5.0.0.pre8 → 5.0.0.pre10

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