stimulus_reflex 3.5.0.pre6 → 3.5.0.pre9

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of stimulus_reflex might be problematic. Click here for more details.

Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2 -1177
  3. data/Gemfile.lock +123 -100
  4. data/README.md +47 -6
  5. data/app/assets/javascripts/stimulus_reflex.js +969 -0
  6. data/app/assets/javascripts/stimulus_reflex.min.js +2 -0
  7. data/app/assets/javascripts/stimulus_reflex.min.js.map +1 -0
  8. data/app/assets/javascripts/stimulus_reflex.umd.js +904 -0
  9. data/app/assets/javascripts/stimulus_reflex.umd.min.js +905 -0
  10. data/app/assets/javascripts/stimulus_reflex.umd.min.js.map +1 -0
  11. data/app/channels/stimulus_reflex/channel.rb +21 -2
  12. data/lib/stimulus_reflex/broadcasters/nothing_broadcaster.rb +1 -0
  13. data/lib/stimulus_reflex/engine.rb +29 -0
  14. data/lib/stimulus_reflex/importmap.rb +4 -0
  15. data/lib/stimulus_reflex/open_struct_fix.rb +29 -0
  16. data/lib/stimulus_reflex/reflex.rb +10 -3
  17. data/lib/stimulus_reflex/reflex_data.rb +8 -0
  18. data/lib/stimulus_reflex/reflex_factory.rb +3 -1
  19. data/lib/stimulus_reflex/utils/logger.rb +2 -0
  20. data/lib/stimulus_reflex/utils/sanity_checker.rb +0 -58
  21. data/lib/stimulus_reflex/version.rb +1 -1
  22. data/lib/stimulus_reflex.rb +2 -6
  23. data/package.json +67 -0
  24. data/rollup.config.js +85 -0
  25. data/stimulus_reflex.gemspec +63 -0
  26. data/test/broadcasters/broadcaster_test_case.rb +1 -1
  27. data/test/broadcasters/nothing_broadcaster_test.rb +3 -1
  28. data/test/broadcasters/page_broadcaster_test.rb +4 -2
  29. data/test/broadcasters/selector_broadcaster_test.rb +12 -6
  30. data/test/callbacks_test.rb +23 -23
  31. data/test/reflex_test.rb +2 -2
  32. data/test/tmp/app/reflexes/application_reflex.rb +3 -10
  33. data/test/tmp/app/reflexes/{demo_reflex.rb → user_reflex.rb} +10 -2
  34. data/web-test-runner.config.mjs +12 -0
  35. data/yarn-error.log +4964 -0
  36. data/yarn.lock +4520 -0
  37. metadata +118 -38
  38. data/lib/generators/USAGE +0 -14
  39. data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/%file_name%_controller.js.tt +0 -101
  40. data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/application_controller.js.tt +0 -60
  41. data/lib/generators/stimulus_reflex/templates/app/reflexes/%file_name%_reflex.rb.tt +0 -41
  42. data/lib/generators/stimulus_reflex/templates/app/reflexes/application_reflex.rb.tt +0 -19
  43. data/lib/tasks/stimulus_reflex/install.rake +0 -116
@@ -0,0 +1,905 @@
1
+ (function(global, factory) {
2
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("@hotwired/stimulus"), require("cable_ready"), require("@rails/actioncable")) : typeof define === "function" && define.amd ? define([ "exports", "@hotwired/stimulus", "cable_ready", "@rails/actioncable" ], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self,
3
+ factory(global.StimulusReflex = {}, global.Stimulus, global.CableReady, global.actioncable));
4
+ })(this, (function(exports, stimulus, CableReady, actioncable) {
5
+ "use strict";
6
+ function _interopDefaultLegacy(e) {
7
+ return e && typeof e === "object" && "default" in e ? e : {
8
+ default: e
9
+ };
10
+ }
11
+ var CableReady__default = _interopDefaultLegacy(CableReady);
12
+ const defaultSchema = {
13
+ reflexAttribute: "data-reflex",
14
+ reflexPermanentAttribute: "data-reflex-permanent",
15
+ reflexRootAttribute: "data-reflex-root",
16
+ reflexSuppressLoggingAttribute: "data-reflex-suppress-logging",
17
+ reflexDatasetAttribute: "data-reflex-dataset",
18
+ reflexDatasetAllAttribute: "data-reflex-dataset-all",
19
+ reflexSerializeFormAttribute: "data-reflex-serialize-form",
20
+ reflexFormSelectorAttribute: "data-reflex-form-selector",
21
+ reflexIncludeInnerHtmlAttribute: "data-reflex-include-inner-html",
22
+ reflexIncludeTextContentAttribute: "data-reflex-include-text-content"
23
+ };
24
+ let schema = {};
25
+ var Schema = {
26
+ set(application) {
27
+ schema = {
28
+ ...defaultSchema,
29
+ ...application.schema
30
+ };
31
+ for (const attribute in schema) {
32
+ Object.defineProperty(this, attribute.slice(0, -9), {
33
+ get: () => schema[attribute]
34
+ });
35
+ }
36
+ }
37
+ };
38
+ let debugging = false;
39
+ var Debug$1 = {
40
+ get enabled() {
41
+ return debugging;
42
+ },
43
+ get disabled() {
44
+ return !debugging;
45
+ },
46
+ get value() {
47
+ return debugging;
48
+ },
49
+ set(value) {
50
+ debugging = !!value;
51
+ },
52
+ set debug(value) {
53
+ debugging = !!value;
54
+ }
55
+ };
56
+ const reflexes = {};
57
+ const request = (reflexId, target, args, controller, element, controllerElement) => {
58
+ const reflex = reflexes[reflexId];
59
+ if (Debug$1.disabled || reflex.promise.data.suppressLogging) return;
60
+ reflex.timestamp = new Date;
61
+ console.log(`↑ stimulus ↑ ${target}`, {
62
+ reflexId: reflexId,
63
+ args: args,
64
+ controller: controller,
65
+ element: element,
66
+ controllerElement: controllerElement
67
+ });
68
+ };
69
+ const success = (event, halted) => {
70
+ const {detail: detail} = event || {};
71
+ const {selector: selector, payload: payload} = detail || {};
72
+ const {reflexId: reflexId, target: target, morph: morph} = detail.stimulusReflex || {};
73
+ const reflex = reflexes[reflexId];
74
+ if (Debug$1.disabled || reflex.promise.data.suppressLogging) return;
75
+ const progress = reflex.totalOperations > 1 ? ` ${reflex.completedOperations}/${reflex.totalOperations}` : "";
76
+ const duration = reflex.timestamp ? `in ${new Date - reflex.timestamp}ms` : "CLONED";
77
+ const operation = event.type.split(":")[1].split("-").slice(1).join("_");
78
+ console.log(`↓ reflex ↓ ${target} → ${selector || "∞"}${progress} ${duration}`, {
79
+ reflexId: reflexId,
80
+ morph: morph,
81
+ operation: operation,
82
+ halted: halted,
83
+ payload: payload
84
+ });
85
+ };
86
+ const error$1 = event => {
87
+ const {detail: detail} = event || {};
88
+ const {reflexId: reflexId, target: target, payload: payload} = detail.stimulusReflex || {};
89
+ const reflex = reflexes[reflexId];
90
+ if (Debug$1.disabled || reflex.promise.data.suppressLogging) return;
91
+ const duration = reflex.timestamp ? `in ${new Date - reflex.timestamp}ms` : "CLONED";
92
+ console.log(`↓ reflex ↓ ${target} ${duration} %cERROR: ${event.detail.body}`, "color: #f00;", {
93
+ reflexId: reflexId,
94
+ payload: payload
95
+ });
96
+ };
97
+ var Log = {
98
+ request: request,
99
+ success: success,
100
+ error: error$1
101
+ };
102
+ let deprecationWarnings = true;
103
+ var Deprecate = {
104
+ get enabled() {
105
+ return deprecationWarnings;
106
+ },
107
+ get disabled() {
108
+ return !deprecationWarnings;
109
+ },
110
+ get value() {
111
+ return deprecationWarnings;
112
+ },
113
+ set(value) {
114
+ deprecationWarnings = !!value;
115
+ },
116
+ set deprecate(value) {
117
+ deprecationWarnings = !!value;
118
+ }
119
+ };
120
+ const uuidv4 = () => {
121
+ const crypto = window.crypto || window.msCrypto;
122
+ return ([ 1e7 ] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)));
123
+ };
124
+ const serializeForm = (form, options = {}) => {
125
+ if (!form) return "";
126
+ const w = options.w || window;
127
+ const {element: element} = options;
128
+ const formData = new w.FormData(form);
129
+ const data = Array.from(formData, (e => e.map(encodeURIComponent).join("=")));
130
+ const submitButton = form.querySelector("input[type=submit]");
131
+ if (element && element.name && element.nodeName === "INPUT" && element.type === "submit") {
132
+ data.push(`${encodeURIComponent(element.name)}=${encodeURIComponent(element.value)}`);
133
+ } else if (submitButton && submitButton.name) {
134
+ data.push(`${encodeURIComponent(submitButton.name)}=${encodeURIComponent(submitButton.value)}`);
135
+ }
136
+ return Array.from(data).join("&");
137
+ };
138
+ const camelize = (value, uppercaseFirstLetter = true) => {
139
+ if (typeof value !== "string") return "";
140
+ value = value.replace(/[\s_](.)/g, ($1 => $1.toUpperCase())).replace(/[\s_]/g, "").replace(/^(.)/, ($1 => $1.toLowerCase()));
141
+ if (uppercaseFirstLetter) value = value.substr(0, 1).toUpperCase() + value.substr(1);
142
+ return value;
143
+ };
144
+ const debounce = (callback, delay = 250) => {
145
+ let timeoutId;
146
+ return (...args) => {
147
+ clearTimeout(timeoutId);
148
+ timeoutId = setTimeout((() => {
149
+ timeoutId = null;
150
+ callback(...args);
151
+ }), delay);
152
+ };
153
+ };
154
+ const extractReflexName = reflexString => {
155
+ const match = reflexString.match(/(?:.*->)?(.*?)(?:Reflex)?#/);
156
+ return match ? match[1] : "";
157
+ };
158
+ const emitEvent = (event, detail) => {
159
+ document.dispatchEvent(new CustomEvent(event, {
160
+ bubbles: true,
161
+ cancelable: false,
162
+ detail: detail
163
+ }));
164
+ if (window.jQuery) window.jQuery(document).trigger(event, detail);
165
+ };
166
+ const elementToXPath = element => {
167
+ if (element.id !== "") return "//*[@id='" + element.id + "']";
168
+ if (element === document.body) return "/html/body";
169
+ let ix = 0;
170
+ const siblings = element?.parentNode ? element.parentNode.childNodes : [];
171
+ for (var i = 0; i < siblings.length; i++) {
172
+ const sibling = siblings[i];
173
+ if (sibling === element) {
174
+ const computedPath = elementToXPath(element.parentNode);
175
+ const tagName = element.tagName.toLowerCase();
176
+ const ixInc = ix + 1;
177
+ return `${computedPath}/${tagName}[${ixInc}]`;
178
+ }
179
+ if (sibling.nodeType === 1 && sibling.tagName === element.tagName) {
180
+ ix++;
181
+ }
182
+ }
183
+ };
184
+ const XPathToElement = xpath => document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
185
+ const XPathToArray = (xpath, reverse = false) => {
186
+ const snapshotList = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
187
+ const snapshots = [];
188
+ for (let i = 0; i < snapshotList.snapshotLength; i++) {
189
+ snapshots.push(snapshotList.snapshotItem(i));
190
+ }
191
+ return reverse ? snapshots.reverse() : snapshots;
192
+ };
193
+ const multipleInstances = element => {
194
+ if ([ "checkbox", "radio" ].includes(element.type)) {
195
+ return document.querySelectorAll(`input[type="${element.type}"][name="${element.name}"]`).length > 1;
196
+ }
197
+ return false;
198
+ };
199
+ const collectCheckedOptions = element => Array.from(element.querySelectorAll("option:checked")).concat(Array.from(document.querySelectorAll(`input[type="${element.type}"][name="${element.name}"]`)).filter((elem => elem.checked))).map((o => o.value));
200
+ const attributeValue = (values = []) => {
201
+ const value = values.filter((v => v && String(v).length)).map((v => v.trim())).join(" ").trim();
202
+ return value.length ? value : null;
203
+ };
204
+ const attributeValues = value => {
205
+ if (!value) return [];
206
+ if (!value.length) return [];
207
+ return value.split(" ").filter((v => v.trim().length));
208
+ };
209
+ const extractElementAttributes = element => {
210
+ let attrs = Array.from(element.attributes).reduce(((memo, attr) => {
211
+ memo[attr.name] = attr.value;
212
+ return memo;
213
+ }), {});
214
+ attrs.checked = !!element.checked;
215
+ attrs.selected = !!element.selected;
216
+ attrs.tag_name = element.tagName;
217
+ if (element.tagName.match(/select/i) || multipleInstances(element)) {
218
+ const collectedOptions = collectCheckedOptions(element);
219
+ attrs.values = collectedOptions;
220
+ attrs.value = collectedOptions.join(",");
221
+ } else {
222
+ attrs.value = element.value;
223
+ }
224
+ return attrs;
225
+ };
226
+ const getElementsFromTokens = (element, tokens) => {
227
+ if (!tokens || tokens.length === 0) return [];
228
+ let elements = [ element ];
229
+ const xPath = elementToXPath(element);
230
+ tokens.forEach((token => {
231
+ try {
232
+ switch (token) {
233
+ case "combined":
234
+ if (Deprecate.enabled) console.warn("In the next version of StimulusReflex, the 'combined' option to data-reflex-dataset will become 'ancestors'.");
235
+ elements = [ ...elements, ...XPathToArray(`${xPath}/ancestor::*`, true) ];
236
+ break;
237
+
238
+ case "ancestors":
239
+ elements = [ ...elements, ...XPathToArray(`${xPath}/ancestor::*`, true) ];
240
+ break;
241
+
242
+ case "parent":
243
+ elements = [ ...elements, ...XPathToArray(`${xPath}/parent::*`) ];
244
+ break;
245
+
246
+ case "siblings":
247
+ elements = [ ...elements, ...XPathToArray(`${xPath}/preceding-sibling::*|${xPath}/following-sibling::*`) ];
248
+ break;
249
+
250
+ case "children":
251
+ elements = [ ...elements, ...XPathToArray(`${xPath}/child::*`) ];
252
+ break;
253
+
254
+ case "descendants":
255
+ elements = [ ...elements, ...XPathToArray(`${xPath}/descendant::*`) ];
256
+ break;
257
+
258
+ default:
259
+ elements = [ ...elements, ...document.querySelectorAll(token) ];
260
+ }
261
+ } catch (error) {
262
+ if (Debug$1.enabled) console.error(error);
263
+ }
264
+ }));
265
+ return elements;
266
+ };
267
+ const extractElementDataset = element => {
268
+ const dataset = element.attributes[Schema.reflexDataset];
269
+ const allDataset = element.attributes[Schema.reflexDatasetAll];
270
+ const tokens = dataset && dataset.value.split(" ") || [];
271
+ const allTokens = allDataset && allDataset.value.split(" ") || [];
272
+ const datasetElements = getElementsFromTokens(element, tokens);
273
+ const datasetAllElements = getElementsFromTokens(element, allTokens);
274
+ const datasetAttributes = datasetElements.reduce(((acc, ele) => ({
275
+ ...extractDataAttributes(ele),
276
+ ...acc
277
+ })), {});
278
+ const reflexElementAttributes = extractDataAttributes(element);
279
+ const elementDataset = {
280
+ dataset: {
281
+ ...reflexElementAttributes,
282
+ ...datasetAttributes
283
+ },
284
+ datasetAll: {}
285
+ };
286
+ datasetAllElements.forEach((element => {
287
+ const elementAttributes = extractDataAttributes(element);
288
+ Object.keys(elementAttributes).forEach((key => {
289
+ const value = elementAttributes[key];
290
+ if (elementDataset.datasetAll[key] && Array.isArray(elementDataset.datasetAll[key])) {
291
+ elementDataset.datasetAll[key].push(value);
292
+ } else {
293
+ elementDataset.datasetAll[key] = [ value ];
294
+ }
295
+ }));
296
+ }));
297
+ return elementDataset;
298
+ };
299
+ const extractDataAttributes = element => {
300
+ let attrs = {};
301
+ if (element && element.attributes) {
302
+ Array.from(element.attributes).forEach((attr => {
303
+ if (attr.name.startsWith("data-")) {
304
+ attrs[attr.name] = attr.value;
305
+ }
306
+ }));
307
+ }
308
+ return attrs;
309
+ };
310
+ let isolationMode = false;
311
+ var IsolationMode = {
312
+ get disabled() {
313
+ return !isolationMode;
314
+ },
315
+ set(value) {
316
+ isolationMode = value;
317
+ }
318
+ };
319
+ const invokeLifecycleMethod = (stage, reflexElement, controllerElement, reflexId, payload) => {
320
+ if (!controllerElement || !controllerElement.reflexData[reflexId]) return;
321
+ const controller = controllerElement.reflexController[reflexId];
322
+ const reflex = controllerElement.reflexData[reflexId].target;
323
+ const reflexMethodName = reflex.split("#")[1];
324
+ const specificLifecycleMethodName = [ "before", "after", "finalize" ].includes(stage) ? `${stage}${camelize(reflexMethodName)}` : `${camelize(reflexMethodName, false)}${camelize(stage)}`;
325
+ const specificLifecycleMethod = controller[specificLifecycleMethodName];
326
+ const genericLifecycleMethodName = [ "before", "after", "finalize" ].includes(stage) ? `${stage}Reflex` : `reflex${camelize(stage)}`;
327
+ const genericLifecycleMethod = controller[genericLifecycleMethodName];
328
+ if (typeof specificLifecycleMethod === "function") {
329
+ specificLifecycleMethod.call(controller, reflexElement, reflex, controllerElement.reflexError[reflexId], reflexId, payload);
330
+ }
331
+ if (typeof genericLifecycleMethod === "function") {
332
+ genericLifecycleMethod.call(controller, reflexElement, reflex, controllerElement.reflexError[reflexId], reflexId, payload);
333
+ }
334
+ if (reflexes[reflexId] && stage === reflexes[reflexId].finalStage) {
335
+ Reflect.deleteProperty(controllerElement.reflexController, reflexId);
336
+ Reflect.deleteProperty(controllerElement.reflexData, reflexId);
337
+ Reflect.deleteProperty(controllerElement.reflexError, reflexId);
338
+ }
339
+ };
340
+ document.addEventListener("stimulus-reflex:before", (event => invokeLifecycleMethod("before", event.detail.element, event.detail.controller.element, event.detail.reflexId, event.detail.payload)), true);
341
+ document.addEventListener("stimulus-reflex:success", (event => {
342
+ invokeLifecycleMethod("success", event.detail.element, event.detail.controller.element, event.detail.reflexId, event.detail.payload);
343
+ dispatchLifecycleEvent("after", event.detail.element, event.detail.controller.element, event.detail.reflexId, event.detail.payload);
344
+ }), true);
345
+ document.addEventListener("stimulus-reflex:nothing", (event => {
346
+ dispatchLifecycleEvent("success", event.detail.element, event.detail.controller.element, event.detail.reflexId, event.detail.payload);
347
+ }), true);
348
+ document.addEventListener("stimulus-reflex:error", (event => {
349
+ invokeLifecycleMethod("error", event.detail.element, event.detail.controller.element, event.detail.reflexId, event.detail.payload);
350
+ dispatchLifecycleEvent("after", event.detail.element, event.detail.controller.element, event.detail.reflexId, event.detail.payload);
351
+ }), true);
352
+ document.addEventListener("stimulus-reflex:halted", (event => invokeLifecycleMethod("halted", event.detail.element, event.detail.controller.element, event.detail.reflexId, event.detail.payload)), true);
353
+ document.addEventListener("stimulus-reflex:after", (event => invokeLifecycleMethod("after", event.detail.element, event.detail.controller.element, event.detail.reflexId, event.detail.payload)), true);
354
+ document.addEventListener("stimulus-reflex:finalize", (event => invokeLifecycleMethod("finalize", event.detail.element, event.detail.controller.element, event.detail.reflexId, event.detail.payload)), true);
355
+ const dispatchLifecycleEvent = (stage, reflexElement, controllerElement, reflexId, payload) => {
356
+ if (!controllerElement) {
357
+ if (Debug$1.enabled && !reflexes[reflexId].warned) {
358
+ console.warn(`StimulusReflex was not able execute callbacks or emit events for "${stage}" or later life-cycle stages for this Reflex. The StimulusReflex Controller Element is no longer present in the DOM. Could you move the StimulusReflex Controller to an element higher in your DOM?`);
359
+ reflexes[reflexId].warned = true;
360
+ }
361
+ return;
362
+ }
363
+ if (!controllerElement.reflexController || controllerElement.reflexController && !controllerElement.reflexController[reflexId]) {
364
+ if (Debug$1.enabled && !reflexes[reflexId].warned) {
365
+ console.warn(`StimulusReflex detected that the StimulusReflex Controller responsible for this Reflex has been replaced with a new instance. Callbacks and events for "${stage}" or later life-cycle stages cannot be executed.`);
366
+ reflexes[reflexId].warned = true;
367
+ }
368
+ return;
369
+ }
370
+ const {target: target} = controllerElement.reflexData[reflexId] || {};
371
+ const controller = controllerElement.reflexController[reflexId] || {};
372
+ const event = `stimulus-reflex:${stage}`;
373
+ const action = `${event}:${target.split("#")[1]}`;
374
+ const detail = {
375
+ reflex: target,
376
+ controller: controller,
377
+ reflexId: reflexId,
378
+ element: reflexElement,
379
+ payload: payload
380
+ };
381
+ const options = {
382
+ bubbles: true,
383
+ cancelable: false,
384
+ detail: detail
385
+ };
386
+ controllerElement.dispatchEvent(new CustomEvent(event, options));
387
+ controllerElement.dispatchEvent(new CustomEvent(action, options));
388
+ if (window.jQuery) {
389
+ window.jQuery(controllerElement).trigger(event, detail);
390
+ window.jQuery(controllerElement).trigger(action, detail);
391
+ }
392
+ };
393
+ const localReflexControllers = (app, element) => attributeValues(element.getAttribute(Schema.controller)).reduce(((memo, name) => {
394
+ const controller = app.getControllerForElementAndIdentifier(element, name);
395
+ if (controller && controller.StimulusReflex) memo.push(controller);
396
+ return memo;
397
+ }), []);
398
+ const allReflexControllers = (app, element) => {
399
+ let controllers = [];
400
+ while (element) {
401
+ controllers = controllers.concat(localReflexControllers(app, element));
402
+ element = element.parentElement;
403
+ }
404
+ return controllers;
405
+ };
406
+ const findControllerByReflexName = (reflexName, controllers) => {
407
+ const controller = controllers.find((controller => {
408
+ if (!controller.identifier) return;
409
+ return extractReflexName(reflexName).replace(/([a-z0–9])([A-Z])/g, "$1-$2").replace(/(::)/g, "--").toLowerCase() === controller.identifier;
410
+ }));
411
+ return controller || controllers[0];
412
+ };
413
+ const received = data => {
414
+ if (!data.cableReady) return;
415
+ if (data.version.replace(".pre", "-pre") !== CableReady__default["default"].version) {
416
+ if (Debug$1.enabled) console.error(`Reflex failed due to cable_ready gem/NPM package version mismatch. Package versions must match exactly.\nNote that if you are using pre-release builds, gems use the "x.y.z.preN" version format, while NPM packages use "x.y.z-preN".\n\ncable_ready gem: ${data.version}\ncable_ready NPM: ${CableReady__default["default"].version}`);
417
+ return;
418
+ }
419
+ let reflexOperations = [];
420
+ for (let i = data.operations.length - 1; i >= 0; i--) {
421
+ if (data.operations[i].stimulusReflex) {
422
+ reflexOperations.push(data.operations[i]);
423
+ data.operations.splice(i, 1);
424
+ }
425
+ }
426
+ if (reflexOperations.some((operation => operation.stimulusReflex.url !== location.href))) {
427
+ return;
428
+ }
429
+ let reflexData;
430
+ if (reflexOperations.length) {
431
+ reflexData = reflexOperations[0].stimulusReflex;
432
+ reflexData.payload = reflexOperations[0].payload;
433
+ }
434
+ if (reflexData) {
435
+ const {reflexId: reflexId, payload: payload} = reflexData;
436
+ if (!reflexes[reflexId] && IsolationMode.disabled) {
437
+ const controllerElement = XPathToElement(reflexData.xpathController);
438
+ const reflexElement = XPathToElement(reflexData.xpathElement);
439
+ controllerElement.reflexController = controllerElement.reflexController || {};
440
+ controllerElement.reflexData = controllerElement.reflexData || {};
441
+ controllerElement.reflexError = controllerElement.reflexError || {};
442
+ controllerElement.reflexController[reflexId] = reflexes.app.getControllerForElementAndIdentifier(controllerElement, reflexData.reflexController);
443
+ controllerElement.reflexData[reflexId] = reflexData;
444
+ dispatchLifecycleEvent("before", reflexElement, controllerElement, reflexId, payload);
445
+ registerReflex(reflexData);
446
+ }
447
+ if (reflexes[reflexId]) {
448
+ reflexes[reflexId].totalOperations = reflexOperations.length;
449
+ reflexes[reflexId].pendingOperations = reflexOperations.length;
450
+ reflexes[reflexId].completedOperations = 0;
451
+ reflexes[reflexId].piggybackOperations = data.operations;
452
+ CableReady__default["default"].perform(reflexOperations);
453
+ }
454
+ } else {
455
+ if (data.operations.length && reflexes[data.operations[0].reflexId]) {
456
+ CableReady__default["default"].perform(data.operations);
457
+ }
458
+ }
459
+ };
460
+ const registerReflex = data => {
461
+ const {reflexId: reflexId} = data;
462
+ reflexes[reflexId] = {
463
+ finalStage: "finalize"
464
+ };
465
+ const promise = new Promise(((resolve, reject) => {
466
+ reflexes[reflexId].promise = {
467
+ resolve: resolve,
468
+ reject: reject,
469
+ data: data
470
+ };
471
+ }));
472
+ promise.reflexId = reflexId;
473
+ if (Debug$1.enabled) promise.catch((() => {}));
474
+ return promise;
475
+ };
476
+ const getReflexRoots = element => {
477
+ let list = [];
478
+ while (list.length === 0 && element) {
479
+ let reflexRoot = element.getAttribute(Schema.reflexRoot);
480
+ if (reflexRoot) {
481
+ if (reflexRoot.length === 0 && element.id) reflexRoot = `#${element.id}`;
482
+ const selectors = reflexRoot.split(",").filter((s => s.trim().length));
483
+ if (Debug$1.enabled && selectors.length === 0) {
484
+ console.error(`No value found for ${Schema.reflexRoot}. Add an #id to the element or provide a value for ${Schema.reflexRoot}.`, element);
485
+ }
486
+ list = list.concat(selectors.filter((s => document.querySelector(s))));
487
+ }
488
+ element = element.parentElement ? element.parentElement.closest(`[${Schema.reflexRoot}]`) : null;
489
+ }
490
+ return list;
491
+ };
492
+ const setupDeclarativeReflexes = debounce((() => {
493
+ document.querySelectorAll(`[${Schema.reflex}]`).forEach((element => {
494
+ const controllers = attributeValues(element.getAttribute(Schema.controller));
495
+ const reflexAttributeNames = attributeValues(element.getAttribute(Schema.reflex));
496
+ const actions = attributeValues(element.getAttribute(Schema.action));
497
+ reflexAttributeNames.forEach((reflexName => {
498
+ const controller = findControllerByReflexName(reflexName, allReflexControllers(reflexes.app, element));
499
+ let action;
500
+ if (controller) {
501
+ action = `${reflexName.split("->")[0]}->${controller.identifier}#__perform`;
502
+ if (!actions.includes(action)) actions.push(action);
503
+ } else {
504
+ action = `${reflexName.split("->")[0]}->stimulus-reflex#__perform`;
505
+ if (!controllers.includes("stimulus-reflex")) {
506
+ controllers.push("stimulus-reflex");
507
+ }
508
+ if (!actions.includes(action)) actions.push(action);
509
+ }
510
+ }));
511
+ const controllerValue = attributeValue(controllers);
512
+ const actionValue = attributeValue(actions);
513
+ if (controllerValue && element.getAttribute(Schema.controller) != controllerValue) {
514
+ element.setAttribute(Schema.controller, controllerValue);
515
+ }
516
+ if (actionValue && element.getAttribute(Schema.action) != actionValue) element.setAttribute(Schema.action, actionValue);
517
+ }));
518
+ emitEvent("stimulus-reflex:ready");
519
+ }), 20);
520
+ var version = "3.5.0-pre9";
521
+ class ReflexData {
522
+ constructor(options, reflexElement, controllerElement, reflexController, permanentAttributeName, target, args, url, tabId) {
523
+ this.options = options;
524
+ this.reflexElement = reflexElement;
525
+ this.controllerElement = controllerElement;
526
+ this.reflexController = reflexController;
527
+ this.permanentAttributeName = permanentAttributeName;
528
+ this.target = target;
529
+ this.args = args;
530
+ this.url = url;
531
+ this.tabId = tabId;
532
+ }
533
+ get attrs() {
534
+ this._attrs = this._attrs || this.options["attrs"] || extractElementAttributes(this.reflexElement);
535
+ return this._attrs;
536
+ }
537
+ get reflexId() {
538
+ this._reflexId = this._reflexId || this.options["reflexId"] || uuidv4();
539
+ return this._reflexId;
540
+ }
541
+ get selectors() {
542
+ this._selectors = this._selectors || this.options["selectors"] || getReflexRoots(this.reflexElement);
543
+ return typeof this._selectors === "string" ? [ this._selectors ] : this._selectors;
544
+ }
545
+ get resolveLate() {
546
+ return this.options["resolveLate"] || false;
547
+ }
548
+ get dataset() {
549
+ this._dataset = this._dataset || extractElementDataset(this.reflexElement);
550
+ return this._dataset;
551
+ }
552
+ get innerHTML() {
553
+ return this.includeInnerHtml ? this.reflexElement.innerHTML : "";
554
+ }
555
+ get textContent() {
556
+ return this.includeTextContent ? this.reflexElement.textContent : "";
557
+ }
558
+ get xpathController() {
559
+ return elementToXPath(this.controllerElement);
560
+ }
561
+ get xpathElement() {
562
+ return elementToXPath(this.reflexElement);
563
+ }
564
+ get formSelector() {
565
+ const attr = this.reflexElement.attributes[Schema.reflexFormSelector] ? this.reflexElement.attributes[Schema.reflexFormSelector].value : undefined;
566
+ return this.options["formSelector"] || attr;
567
+ }
568
+ get includeInnerHtml() {
569
+ const attr = this.reflexElement.attributes[Schema.reflexIncludeInnerHtml] || false;
570
+ return this.options["includeInnerHTML"] || attr ? attr.value !== "false" : false;
571
+ }
572
+ get includeTextContent() {
573
+ const attr = this.reflexElement.attributes[Schema.reflexIncludeTextContent] || false;
574
+ return this.options["includeTextContent"] || attr ? attr.value !== "false" : false;
575
+ }
576
+ get suppressLogging() {
577
+ return this.options["suppressLogging"] || this.reflexElement.attributes[Schema.reflexSuppressLogging] || false;
578
+ }
579
+ valueOf() {
580
+ return {
581
+ attrs: this.attrs,
582
+ dataset: this.dataset,
583
+ selectors: this.selectors,
584
+ reflexId: this.reflexId,
585
+ resolveLate: this.resolveLate,
586
+ suppressLogging: this.suppressLogging,
587
+ xpathController: this.xpathController,
588
+ xpathElement: this.xpathElement,
589
+ inner_html: this.innerHTML,
590
+ text_content: this.textContent,
591
+ formSelector: this.formSelector,
592
+ reflexController: this.reflexController,
593
+ permanentAttributeName: this.permanentAttributeName,
594
+ target: this.target,
595
+ args: this.args,
596
+ url: this.url,
597
+ tabId: this.tabId,
598
+ version: version
599
+ };
600
+ }
601
+ }
602
+ let consumer;
603
+ let params;
604
+ let subscriptionActive;
605
+ const createSubscription = controller => {
606
+ consumer = consumer || controller.application.consumer || actioncable.createConsumer();
607
+ const {channel: channel} = controller.StimulusReflex;
608
+ const subscription = {
609
+ channel: channel,
610
+ ...params
611
+ };
612
+ const identifier = JSON.stringify(subscription);
613
+ controller.StimulusReflex.subscription = consumer.subscriptions.findAll(identifier)[0] || consumer.subscriptions.create(subscription, {
614
+ received: received,
615
+ connected: connected,
616
+ rejected: rejected,
617
+ disconnected: disconnected
618
+ });
619
+ };
620
+ const connected = () => {
621
+ subscriptionActive = true;
622
+ document.body.classList.replace("stimulus-reflex-disconnected", "stimulus-reflex-connected");
623
+ emitEvent("stimulus-reflex:connected");
624
+ emitEvent("stimulus-reflex:action-cable:connected");
625
+ };
626
+ const rejected = () => {
627
+ subscriptionActive = false;
628
+ document.body.classList.replace("stimulus-reflex-connected", "stimulus-reflex-disconnected");
629
+ emitEvent("stimulus-reflex:rejected");
630
+ emitEvent("stimulus-reflex:action-cable:rejected");
631
+ if (Debug.enabled) console.warn("Channel subscription was rejected.");
632
+ };
633
+ const disconnected = willAttemptReconnect => {
634
+ subscriptionActive = false;
635
+ document.body.classList.replace("stimulus-reflex-connected", "stimulus-reflex-disconnected");
636
+ emitEvent("stimulus-reflex:disconnected", willAttemptReconnect);
637
+ emitEvent("stimulus-reflex:action-cable:disconnected", willAttemptReconnect);
638
+ };
639
+ var ActionCableTransport = {
640
+ consumer: consumer,
641
+ params: params,
642
+ get subscriptionActive() {
643
+ return subscriptionActive;
644
+ },
645
+ createSubscription: createSubscription,
646
+ connected: connected,
647
+ rejected: rejected,
648
+ disconnected: disconnected,
649
+ set(consumerValue, paramsValue) {
650
+ consumer = consumerValue;
651
+ params = paramsValue;
652
+ }
653
+ };
654
+ const beforeDOMUpdate = event => {
655
+ const {stimulusReflex: stimulusReflex, payload: payload} = event.detail || {};
656
+ if (!stimulusReflex) return;
657
+ const {reflexId: reflexId, xpathElement: xpathElement, xpathController: xpathController} = stimulusReflex;
658
+ const controllerElement = XPathToElement(xpathController);
659
+ const reflexElement = XPathToElement(xpathElement);
660
+ const reflex = reflexes[reflexId];
661
+ const {promise: promise} = reflex;
662
+ reflex.pendingOperations--;
663
+ if (reflex.pendingOperations > 0) return;
664
+ if (!stimulusReflex.resolveLate) setTimeout((() => promise.resolve({
665
+ element: reflexElement,
666
+ event: event,
667
+ data: promise.data,
668
+ payload: payload,
669
+ reflexId: reflexId,
670
+ toString: () => ""
671
+ })));
672
+ setTimeout((() => dispatchLifecycleEvent("success", reflexElement, controllerElement, reflexId, payload)));
673
+ };
674
+ const afterDOMUpdate = event => {
675
+ const {stimulusReflex: stimulusReflex, payload: payload} = event.detail || {};
676
+ if (!stimulusReflex) return;
677
+ const {reflexId: reflexId, xpathElement: xpathElement, xpathController: xpathController} = stimulusReflex;
678
+ const controllerElement = XPathToElement(xpathController);
679
+ const reflexElement = XPathToElement(xpathElement);
680
+ const reflex = reflexes[reflexId];
681
+ const {promise: promise} = reflex;
682
+ reflex.completedOperations++;
683
+ Log.success(event, false);
684
+ if (reflex.completedOperations < reflex.totalOperations) return;
685
+ if (stimulusReflex.resolveLate) setTimeout((() => promise.resolve({
686
+ element: reflexElement,
687
+ event: event,
688
+ data: promise.data,
689
+ payload: payload,
690
+ reflexId: reflexId,
691
+ toString: () => ""
692
+ })));
693
+ setTimeout((() => dispatchLifecycleEvent("finalize", reflexElement, controllerElement, reflexId, payload)));
694
+ if (reflex.piggybackOperations.length) CableReady__default["default"].perform(reflex.piggybackOperations);
695
+ };
696
+ const routeReflexEvent = event => {
697
+ const {stimulusReflex: stimulusReflex, payload: payload, name: name, body: body} = event.detail || {};
698
+ const eventType = name.split("-")[2];
699
+ if (!stimulusReflex || ![ "nothing", "halted", "error" ].includes(eventType)) return;
700
+ const {reflexId: reflexId, xpathElement: xpathElement, xpathController: xpathController} = stimulusReflex;
701
+ const reflexElement = XPathToElement(xpathElement);
702
+ const controllerElement = XPathToElement(xpathController);
703
+ const reflex = reflexes[reflexId];
704
+ const {promise: promise} = reflex;
705
+ if (controllerElement) {
706
+ controllerElement.reflexError = controllerElement.reflexError || {};
707
+ if (eventType === "error") controllerElement.reflexError[reflexId] = body;
708
+ }
709
+ switch (eventType) {
710
+ case "nothing":
711
+ nothing(event, payload, promise, reflex, reflexElement);
712
+ break;
713
+
714
+ case "error":
715
+ error(event, payload, promise, reflex, reflexElement);
716
+ break;
717
+
718
+ case "halted":
719
+ halted(event, payload, promise, reflex, reflexElement);
720
+ break;
721
+ }
722
+ setTimeout((() => dispatchLifecycleEvent(eventType, reflexElement, controllerElement, reflexId, payload)));
723
+ if (reflex.piggybackOperations.length) CableReady__default["default"].perform(reflex.piggybackOperations);
724
+ };
725
+ const nothing = (event, payload, promise, reflex, reflexElement) => {
726
+ reflex.finalStage = "after";
727
+ Log.success(event, false);
728
+ setTimeout((() => promise.resolve({
729
+ data: promise.data,
730
+ element: reflexElement,
731
+ event: event,
732
+ payload: payload,
733
+ reflexId: promise.data.reflexId,
734
+ toString: () => ""
735
+ })));
736
+ };
737
+ const halted = (event, payload, promise, reflex, reflexElement) => {
738
+ reflex.finalStage = "halted";
739
+ Log.success(event, true);
740
+ setTimeout((() => promise.resolve({
741
+ data: promise.data,
742
+ element: reflexElement,
743
+ event: event,
744
+ payload: payload,
745
+ reflexId: promise.data.reflexId,
746
+ toString: () => ""
747
+ })));
748
+ };
749
+ const error = (event, payload, promise, reflex, reflexElement) => {
750
+ reflex.finalStage = "after";
751
+ Log.error(event);
752
+ setTimeout((() => promise.reject({
753
+ data: promise.data,
754
+ element: reflexElement,
755
+ event: event,
756
+ payload: payload,
757
+ reflexId: promise.data.reflexId,
758
+ error: event.detail.body,
759
+ toString: () => event.detail.body
760
+ })));
761
+ };
762
+ class StimulusReflexController extends stimulus.Controller {
763
+ constructor(...args) {
764
+ super(...args);
765
+ register(this);
766
+ }
767
+ }
768
+ const initialize = (application, {controller: controller, consumer: consumer, debug: debug, params: params, isolate: isolate, deprecate: deprecate} = {}) => {
769
+ ActionCableTransport.set(consumer, params);
770
+ document.addEventListener("DOMContentLoaded", (() => {
771
+ document.body.classList.remove("stimulus-reflex-connected");
772
+ document.body.classList.add("stimulus-reflex-disconnected");
773
+ if (Deprecate.enabled && consumer) console.warn("Deprecation warning: the next version of StimulusReflex will obtain a reference to consumer via the Stimulus application object.\nPlease add 'application.consumer = consumer' to your index.js after your Stimulus application has been established, and remove the consumer key from your StimulusReflex initialize() options object.");
774
+ if (Deprecate.enabled && IsolationMode.disabled) console.warn("Deprecation warning: the next version of StimulusReflex will standardize isolation mode, and the isolate option will be removed.\nPlease update your applications to assume that every tab will be isolated.");
775
+ }), {
776
+ once: true
777
+ });
778
+ IsolationMode.set(!!isolate);
779
+ reflexes.app = application;
780
+ Schema.set(application);
781
+ reflexes.app.register("stimulus-reflex", controller || StimulusReflexController);
782
+ Debug$1.set(!!debug);
783
+ if (typeof deprecate !== "undefined") Deprecate.set(deprecate);
784
+ const observer = new MutationObserver(setupDeclarativeReflexes);
785
+ observer.observe(document.documentElement, {
786
+ attributeFilter: [ Schema.reflex, Schema.action ],
787
+ childList: true,
788
+ subtree: true
789
+ });
790
+ };
791
+ const register = (controller, options = {}) => {
792
+ const channel = "StimulusReflex::Channel";
793
+ controller.StimulusReflex = {
794
+ ...options,
795
+ channel: channel
796
+ };
797
+ ActionCableTransport.createSubscription(controller);
798
+ Object.assign(controller, {
799
+ isActionCableConnectionOpen() {
800
+ return this.StimulusReflex.subscription.consumer.connection.isOpen();
801
+ },
802
+ stimulate() {
803
+ const url = location.href;
804
+ const args = Array.from(arguments);
805
+ const target = args.shift() || "StimulusReflex::Reflex#default_reflex";
806
+ const controllerElement = this.element;
807
+ const reflexElement = args[0] && args[0].nodeType === Node.ELEMENT_NODE ? args.shift() : controllerElement;
808
+ if (reflexElement.type === "number" && reflexElement.validity && reflexElement.validity.badInput) {
809
+ if (Debug$1.enabled) console.warn("Reflex aborted: invalid numeric input");
810
+ return;
811
+ }
812
+ const options = {};
813
+ if (args[0] && typeof args[0] === "object" && Object.keys(args[0]).filter((key => [ "attrs", "selectors", "reflexId", "resolveLate", "serializeForm", "suppressLogging", "includeInnerHTML", "includeTextContent" ].includes(key))).length) {
814
+ const opts = args.shift();
815
+ Object.keys(opts).forEach((o => options[o] = opts[o]));
816
+ }
817
+ const reflexData = new ReflexData(options, reflexElement, controllerElement, this.identifier, Schema.reflexPermanent, target, args, url, tabId);
818
+ const reflexId = reflexData.reflexId;
819
+ if (!this.isActionCableConnectionOpen()) throw "The ActionCable connection is not open! `this.isActionCableConnectionOpen()` must return true before calling `this.stimulate()`";
820
+ if (!ActionCableTransport.subscriptionActive) throw "The ActionCable channel subscription for StimulusReflex was rejected.";
821
+ controllerElement.reflexController = controllerElement.reflexController || {};
822
+ controllerElement.reflexData = controllerElement.reflexData || {};
823
+ controllerElement.reflexError = controllerElement.reflexError || {};
824
+ controllerElement.reflexController[reflexId] = this;
825
+ controllerElement.reflexData[reflexId] = reflexData.valueOf();
826
+ dispatchLifecycleEvent("before", reflexElement, controllerElement, reflexId);
827
+ setTimeout((() => {
828
+ const {params: params} = controllerElement.reflexData[reflexId] || {};
829
+ const check = reflexElement.attributes[Schema.reflexSerializeForm];
830
+ if (check) {
831
+ options["serializeForm"] = check.value !== "false";
832
+ }
833
+ const form = reflexElement.closest(reflexData.formSelector) || document.querySelector(reflexData.formSelector) || reflexElement.closest("form");
834
+ if (Deprecate.enabled && options["serializeForm"] === undefined && form) console.warn(`Deprecation warning: the next version of StimulusReflex will not serialize forms by default.\nPlease set ${Schema.reflexSerializeForm}="true" on your Reflex Controller Element or pass { serializeForm: true } as an option to stimulate.`);
835
+ const formData = options["serializeForm"] === false ? "" : serializeForm(form, {
836
+ element: reflexElement
837
+ });
838
+ controllerElement.reflexData[reflexId] = {
839
+ ...reflexData.valueOf(),
840
+ params: params,
841
+ formData: formData
842
+ };
843
+ this.StimulusReflex.subscription.send(controllerElement.reflexData[reflexId]);
844
+ }));
845
+ const promise = registerReflex(reflexData.valueOf());
846
+ Log.request(reflexId, target, args, this.context.scope.identifier, reflexElement, controllerElement);
847
+ return promise;
848
+ },
849
+ __perform(event) {
850
+ let element = event.target;
851
+ let reflex;
852
+ while (element && !reflex) {
853
+ reflex = element.getAttribute(Schema.reflex);
854
+ if (!reflex || !reflex.trim().length) element = element.parentElement;
855
+ }
856
+ const match = attributeValues(reflex).find((reflex => reflex.split("->")[0] === event.type));
857
+ if (match) {
858
+ event.preventDefault();
859
+ event.stopPropagation();
860
+ this.stimulate(match.split("->")[1], element);
861
+ }
862
+ }
863
+ });
864
+ };
865
+ const tabId = uuidv4();
866
+ const useReflex = (controller, options = {}) => {
867
+ register(controller, options);
868
+ };
869
+ document.addEventListener("cable-ready:after-dispatch-event", routeReflexEvent);
870
+ document.addEventListener("cable-ready:before-inner-html", beforeDOMUpdate);
871
+ document.addEventListener("cable-ready:before-morph", beforeDOMUpdate);
872
+ document.addEventListener("cable-ready:after-inner-html", afterDOMUpdate);
873
+ document.addEventListener("cable-ready:after-morph", afterDOMUpdate);
874
+ window.addEventListener("load", setupDeclarativeReflexes);
875
+ var StimulusReflex = Object.freeze({
876
+ __proto__: null,
877
+ initialize: initialize,
878
+ register: register,
879
+ useReflex: useReflex
880
+ });
881
+ const global = {
882
+ ...StimulusReflex,
883
+ get debug() {
884
+ return Debug$1.value;
885
+ },
886
+ set debug(value) {
887
+ Debug$1.set(!!value);
888
+ },
889
+ get deprecate() {
890
+ return Deprecate.value;
891
+ },
892
+ set deprecate(value) {
893
+ Deprecate.set(!!value);
894
+ }
895
+ };
896
+ window.StimulusReflex = global;
897
+ exports["default"] = global;
898
+ exports.initialize = initialize;
899
+ exports.register = register;
900
+ exports.useReflex = useReflex;
901
+ Object.defineProperty(exports, "__esModule", {
902
+ value: true
903
+ });
904
+ }));
905
+ //# sourceMappingURL=stimulus_reflex.umd.min.js.map