stimulus_reflex 3.5.0.pre9 → 3.5.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -1
  3. data/Gemfile.lock +122 -127
  4. data/README.md +13 -19
  5. data/app/assets/javascripts/stimulus_reflex.js +1017 -523
  6. data/app/assets/javascripts/stimulus_reflex.umd.js +940 -496
  7. data/app/channels/stimulus_reflex/channel.rb +9 -24
  8. data/bin/console +0 -2
  9. data/bin/standardize +2 -1
  10. data/lib/generators/stimulus_reflex/stimulus_reflex_generator.rb +68 -9
  11. data/lib/generators/stimulus_reflex/templates/app/controllers/examples_controller.rb.tt +9 -0
  12. data/lib/generators/stimulus_reflex/templates/app/javascript/channels/consumer.js.tt +6 -0
  13. data/lib/generators/stimulus_reflex/templates/app/javascript/channels/index.js.esbuild.tt +4 -0
  14. data/lib/generators/stimulus_reflex/templates/app/javascript/channels/index.js.importmap.tt +2 -0
  15. data/lib/generators/stimulus_reflex/templates/app/javascript/channels/index.js.shakapacker.tt +5 -0
  16. data/lib/generators/stimulus_reflex/templates/app/javascript/channels/index.js.vite.tt +1 -0
  17. data/lib/generators/stimulus_reflex/templates/app/javascript/channels/index.js.webpacker.tt +5 -0
  18. data/lib/generators/stimulus_reflex/templates/app/javascript/config/cable_ready.js.tt +4 -0
  19. data/lib/generators/stimulus_reflex/templates/app/javascript/config/index.js.tt +2 -0
  20. data/lib/generators/stimulus_reflex/templates/app/javascript/config/mrujs.js.tt +9 -0
  21. data/lib/generators/stimulus_reflex/templates/app/javascript/config/stimulus_reflex.js.tt +5 -0
  22. data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/%file_name%_controller.js.tt +141 -0
  23. data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/application.js.tt +11 -0
  24. data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/application_controller.js.tt +74 -0
  25. data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/index.js.esbuild.tt +7 -0
  26. data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/index.js.importmap.tt +5 -0
  27. data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/index.js.shakapacker.tt +5 -0
  28. data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/index.js.vite.tt +5 -0
  29. data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/index.js.webpacker.tt +5 -0
  30. data/{test/tmp/app/reflexes/user_reflex.rb → lib/generators/stimulus_reflex/templates/app/reflexes/%file_name%_reflex.rb.tt} +38 -9
  31. data/lib/generators/stimulus_reflex/templates/app/reflexes/application_reflex.rb.tt +27 -0
  32. data/lib/generators/stimulus_reflex/templates/app/views/examples/show.html.erb.tt +207 -0
  33. data/lib/generators/stimulus_reflex/templates/config/initializers/cable_ready.rb +27 -0
  34. data/lib/generators/stimulus_reflex/templates/config/initializers/stimulus_reflex.rb +18 -13
  35. data/lib/generators/stimulus_reflex/templates/esbuild.config.mjs.tt +94 -0
  36. data/lib/install/action_cable.rb +155 -0
  37. data/lib/install/broadcaster.rb +90 -0
  38. data/lib/install/bundle.rb +56 -0
  39. data/lib/install/compression.rb +41 -0
  40. data/lib/install/config.rb +87 -0
  41. data/lib/install/development.rb +110 -0
  42. data/lib/install/esbuild.rb +114 -0
  43. data/lib/install/example.rb +22 -0
  44. data/lib/install/importmap.rb +133 -0
  45. data/lib/install/initializers.rb +25 -0
  46. data/lib/install/mrujs.rb +133 -0
  47. data/lib/install/npm_packages.rb +25 -0
  48. data/lib/install/reflexes.rb +25 -0
  49. data/lib/install/shakapacker.rb +64 -0
  50. data/lib/install/spring.rb +54 -0
  51. data/lib/install/updatable.rb +34 -0
  52. data/lib/install/vite.rb +64 -0
  53. data/lib/install/webpacker.rb +90 -0
  54. data/lib/install/yarn.rb +55 -0
  55. data/lib/stimulus_reflex/broadcasters/broadcaster.rb +15 -8
  56. data/lib/stimulus_reflex/broadcasters/page_broadcaster.rb +7 -8
  57. data/lib/stimulus_reflex/broadcasters/selector_broadcaster.rb +10 -10
  58. data/lib/stimulus_reflex/broadcasters/update.rb +3 -0
  59. data/lib/stimulus_reflex/cable_readiness.rb +29 -0
  60. data/lib/stimulus_reflex/cable_ready_channels.rb +6 -5
  61. data/lib/stimulus_reflex/callbacks.rb +17 -1
  62. data/lib/stimulus_reflex/concern_enhancer.rb +6 -4
  63. data/lib/stimulus_reflex/configuration.rb +12 -2
  64. data/lib/stimulus_reflex/dataset.rb +11 -1
  65. data/lib/stimulus_reflex/engine.rb +16 -9
  66. data/lib/stimulus_reflex/html/document.rb +59 -0
  67. data/lib/stimulus_reflex/html/document_fragment.rb +13 -0
  68. data/lib/stimulus_reflex/importmap.rb +6 -3
  69. data/lib/stimulus_reflex/installer.rb +274 -0
  70. data/lib/stimulus_reflex/open_struct_fix.rb +2 -0
  71. data/lib/stimulus_reflex/reflex.rb +40 -31
  72. data/lib/stimulus_reflex/reflex_data.rb +19 -3
  73. data/lib/stimulus_reflex/reflex_factory.rb +6 -3
  74. data/lib/stimulus_reflex/request_parameters.rb +2 -0
  75. data/lib/stimulus_reflex/utils/logger.rb +10 -0
  76. data/lib/stimulus_reflex/utils/sanity_checker.rb +8 -48
  77. data/lib/stimulus_reflex/version.rb +1 -1
  78. data/lib/stimulus_reflex/version_checker.rb +54 -0
  79. data/lib/stimulus_reflex.rb +2 -0
  80. data/lib/tasks/stimulus_reflex/stimulus_reflex.rake +250 -0
  81. data/package.json +36 -28
  82. data/{rollup.config.js → rollup.config.mjs} +6 -24
  83. data/stimulus_reflex.gemspec +16 -19
  84. data/yarn.lock +1331 -748
  85. metadata +129 -79
  86. data/LATEST +0 -1
  87. data/app/assets/javascripts/stimulus_reflex.min.js +0 -2
  88. data/app/assets/javascripts/stimulus_reflex.min.js.map +0 -1
  89. data/app/assets/javascripts/stimulus_reflex.umd.min.js +0 -905
  90. data/app/assets/javascripts/stimulus_reflex.umd.min.js.map +0 -1
  91. data/lib/generators/stimulus_reflex/initializer_generator.rb +0 -14
  92. data/test/broadcasters/broadcaster_test.rb +0 -11
  93. data/test/broadcasters/broadcaster_test_case.rb +0 -39
  94. data/test/broadcasters/nothing_broadcaster_test.rb +0 -31
  95. data/test/broadcasters/page_broadcaster_test.rb +0 -79
  96. data/test/broadcasters/selector_broadcaster_test.rb +0 -173
  97. data/test/callbacks_test.rb +0 -652
  98. data/test/concern_enhancer_test.rb +0 -54
  99. data/test/element_test.rb +0 -254
  100. data/test/generators/stimulus_reflex_generator_test.rb +0 -58
  101. data/test/reflex_test.rb +0 -43
  102. data/test/test_helper.rb +0 -71
  103. data/test/tmp/app/reflexes/application_reflex.rb +0 -12
  104. data/yarn-error.log +0 -4964
@@ -1,9 +1,329 @@
1
1
  import { Controller } from "@hotwired/stimulus";
2
2
 
3
- import CableReady from "cable_ready";
3
+ import CableReady, { Utils } from "cable_ready";
4
4
 
5
5
  import { createConsumer } from "@rails/actioncable";
6
6
 
7
+ /*!
8
+ * Toastify js 1.12.0
9
+ * https://github.com/apvarun/toastify-js
10
+ * @license MIT licensed
11
+ *
12
+ * Copyright (C) 2018 Varun A P
13
+ */ class Toastify {
14
+ defaults={
15
+ oldestFirst: true,
16
+ text: "Toastify is awesome!",
17
+ node: undefined,
18
+ duration: 3e3,
19
+ selector: undefined,
20
+ callback: function() {},
21
+ destination: undefined,
22
+ newWindow: false,
23
+ close: false,
24
+ gravity: "toastify-top",
25
+ positionLeft: false,
26
+ position: "",
27
+ backgroundColor: "",
28
+ avatar: "",
29
+ className: "",
30
+ stopOnFocus: true,
31
+ onClick: function() {},
32
+ offset: {
33
+ x: 0,
34
+ y: 0
35
+ },
36
+ escapeMarkup: true,
37
+ ariaLive: "polite",
38
+ style: {
39
+ background: ""
40
+ }
41
+ };
42
+ constructor(options) {
43
+ this.version = "1.12.0";
44
+ this.options = {};
45
+ this.toastElement = null;
46
+ this._rootElement = document.body;
47
+ this._init(options);
48
+ }
49
+ showToast() {
50
+ this.toastElement = this._buildToast();
51
+ if (typeof this.options.selector === "string") {
52
+ this._rootElement = document.getElementById(this.options.selector);
53
+ } else if (this.options.selector instanceof HTMLElement || this.options.selector instanceof ShadowRoot) {
54
+ this._rootElement = this.options.selector;
55
+ } else {
56
+ this._rootElement = document.body;
57
+ }
58
+ if (!this._rootElement) {
59
+ throw "Root element is not defined";
60
+ }
61
+ this._rootElement.insertBefore(this.toastElement, this._rootElement.firstChild);
62
+ this._reposition();
63
+ if (this.options.duration > 0) {
64
+ this.toastElement.timeOutValue = window.setTimeout((() => {
65
+ this._removeElement(this.toastElement);
66
+ }), this.options.duration);
67
+ }
68
+ return this;
69
+ }
70
+ hideToast() {
71
+ if (this.toastElement.timeOutValue) {
72
+ clearTimeout(this.toastElement.timeOutValue);
73
+ }
74
+ this._removeElement(this.toastElement);
75
+ }
76
+ _init(options) {
77
+ this.options = Object.assign(this.defaults, options);
78
+ if (this.options.backgroundColor) {
79
+ console.warn('DEPRECATION NOTICE: "backgroundColor" is being deprecated. Please use the "style.background" property.');
80
+ }
81
+ this.toastElement = null;
82
+ this.options.gravity = options.gravity === "bottom" ? "toastify-bottom" : "toastify-top";
83
+ this.options.stopOnFocus = options.stopOnFocus === undefined ? true : options.stopOnFocus;
84
+ if (options.backgroundColor) {
85
+ this.options.style.background = options.backgroundColor;
86
+ }
87
+ }
88
+ _buildToast() {
89
+ if (!this.options) {
90
+ throw "Toastify is not initialized";
91
+ }
92
+ let divElement = document.createElement("div");
93
+ divElement.className = `toastify on ${this.options.className}`;
94
+ divElement.className += ` toastify-${this.options.position}`;
95
+ divElement.className += ` ${this.options.gravity}`;
96
+ for (const property in this.options.style) {
97
+ divElement.style[property] = this.options.style[property];
98
+ }
99
+ if (this.options.ariaLive) {
100
+ divElement.setAttribute("aria-live", this.options.ariaLive);
101
+ }
102
+ if (this.options.node && this.options.node.nodeType === Node.ELEMENT_NODE) {
103
+ divElement.appendChild(this.options.node);
104
+ } else {
105
+ if (this.options.escapeMarkup) {
106
+ divElement.innerText = this.options.text;
107
+ } else {
108
+ divElement.innerHTML = this.options.text;
109
+ }
110
+ if (this.options.avatar !== "") {
111
+ let avatarElement = document.createElement("img");
112
+ avatarElement.src = this.options.avatar;
113
+ avatarElement.className = "toastify-avatar";
114
+ if (this.options.position == "left") {
115
+ divElement.appendChild(avatarElement);
116
+ } else {
117
+ divElement.insertAdjacentElement("afterbegin", avatarElement);
118
+ }
119
+ }
120
+ }
121
+ if (this.options.close === true) {
122
+ let closeElement = document.createElement("button");
123
+ closeElement.type = "button";
124
+ closeElement.setAttribute("aria-label", "Close");
125
+ closeElement.className = "toast-close";
126
+ closeElement.innerHTML = "✖";
127
+ closeElement.addEventListener("click", (event => {
128
+ event.stopPropagation();
129
+ this._removeElement(this.toastElement);
130
+ window.clearTimeout(this.toastElement.timeOutValue);
131
+ }));
132
+ const width = window.innerWidth > 0 ? window.innerWidth : screen.width;
133
+ if (this.options.position == "left" && width > 360) {
134
+ divElement.insertAdjacentElement("afterbegin", closeElement);
135
+ } else {
136
+ divElement.appendChild(closeElement);
137
+ }
138
+ }
139
+ if (this.options.stopOnFocus && this.options.duration > 0) {
140
+ divElement.addEventListener("mouseover", (event => {
141
+ window.clearTimeout(divElement.timeOutValue);
142
+ }));
143
+ divElement.addEventListener("mouseleave", (() => {
144
+ divElement.timeOutValue = window.setTimeout((() => {
145
+ this._removeElement(divElement);
146
+ }), this.options.duration);
147
+ }));
148
+ }
149
+ if (typeof this.options.destination !== "undefined") {
150
+ divElement.addEventListener("click", (event => {
151
+ event.stopPropagation();
152
+ if (this.options.newWindow === true) {
153
+ window.open(this.options.destination, "_blank");
154
+ } else {
155
+ window.location = this.options.destination;
156
+ }
157
+ }));
158
+ }
159
+ if (typeof this.options.onClick === "function" && typeof this.options.destination === "undefined") {
160
+ divElement.addEventListener("click", (event => {
161
+ event.stopPropagation();
162
+ this.options.onClick();
163
+ }));
164
+ }
165
+ if (typeof this.options.offset === "object") {
166
+ const x = this._getAxisOffsetAValue("x", this.options);
167
+ const y = this._getAxisOffsetAValue("y", this.options);
168
+ const xOffset = this.options.position == "left" ? x : `-${x}`;
169
+ const yOffset = this.options.gravity == "toastify-top" ? y : `-${y}`;
170
+ divElement.style.transform = `translate(${xOffset},${yOffset})`;
171
+ }
172
+ return divElement;
173
+ }
174
+ _removeElement(toastElement) {
175
+ toastElement.className = toastElement.className.replace(" on", "");
176
+ window.setTimeout((() => {
177
+ if (this.options.node && this.options.node.parentNode) {
178
+ this.options.node.parentNode.removeChild(this.options.node);
179
+ }
180
+ if (toastElement.parentNode) {
181
+ toastElement.parentNode.removeChild(toastElement);
182
+ }
183
+ this.options.callback.call(toastElement);
184
+ this._reposition();
185
+ }), 400);
186
+ }
187
+ _reposition() {
188
+ let topLeftOffsetSize = {
189
+ top: 15,
190
+ bottom: 15
191
+ };
192
+ let topRightOffsetSize = {
193
+ top: 15,
194
+ bottom: 15
195
+ };
196
+ let offsetSize = {
197
+ top: 15,
198
+ bottom: 15
199
+ };
200
+ let allToasts = this._rootElement.querySelectorAll(".toastify");
201
+ let classUsed;
202
+ for (let i = 0; i < allToasts.length; i++) {
203
+ if (allToasts[i].classList.contains("toastify-top") === true) {
204
+ classUsed = "toastify-top";
205
+ } else {
206
+ classUsed = "toastify-bottom";
207
+ }
208
+ let height = allToasts[i].offsetHeight;
209
+ classUsed = classUsed.substr(9, classUsed.length - 1);
210
+ let offset = 15;
211
+ let width = window.innerWidth > 0 ? window.innerWidth : screen.width;
212
+ if (width <= 360) {
213
+ allToasts[i].style[classUsed] = `${offsetSize[classUsed]}px`;
214
+ offsetSize[classUsed] += height + offset;
215
+ } else {
216
+ if (allToasts[i].classList.contains("toastify-left") === true) {
217
+ allToasts[i].style[classUsed] = `${topLeftOffsetSize[classUsed]}px`;
218
+ topLeftOffsetSize[classUsed] += height + offset;
219
+ } else {
220
+ allToasts[i].style[classUsed] = `${topRightOffsetSize[classUsed]}px`;
221
+ topRightOffsetSize[classUsed] += height + offset;
222
+ }
223
+ }
224
+ }
225
+ }
226
+ _getAxisOffsetAValue(axis, options) {
227
+ if (options.offset[axis]) {
228
+ if (isNaN(options.offset[axis])) {
229
+ return options.offset[axis];
230
+ } else {
231
+ return `${options.offset[axis]}px`;
232
+ }
233
+ }
234
+ return "0px";
235
+ }
236
+ }
237
+
238
+ function StartToastifyInstance(options) {
239
+ return new Toastify(options);
240
+ }
241
+
242
+ CableReady.operations.stimulusReflexVersionMismatch = operation => {
243
+ const levels = {
244
+ info: {},
245
+ success: {
246
+ background: "#198754",
247
+ color: "white"
248
+ },
249
+ warn: {
250
+ background: "#ffc107",
251
+ color: "black"
252
+ },
253
+ error: {
254
+ background: "#dc3545",
255
+ color: "white"
256
+ }
257
+ };
258
+ const defaults = {
259
+ selector: setupToastify(),
260
+ close: true,
261
+ duration: 30 * 1e3,
262
+ gravity: "bottom",
263
+ position: "right",
264
+ newWindow: true,
265
+ style: levels[operation.level || "info"]
266
+ };
267
+ StartToastifyInstance({
268
+ ...defaults,
269
+ ...operation
270
+ }).showToast();
271
+ };
272
+
273
+ function setupToastify() {
274
+ const id = "stimulus-reflex-toast-element";
275
+ let element = document.querySelector(`#${id}`);
276
+ if (!element) {
277
+ element = document.createElement("div");
278
+ element.id = id;
279
+ document.documentElement.appendChild(element);
280
+ const styles = document.createElement("style");
281
+ styles.innerHTML = `\n #${id} .toastify {\n padding: 12px 20px;\n color: #ffffff;\n display: inline-block;\n background: -webkit-linear-gradient(315deg, #73a5ff, #5477f5);\n background: linear-gradient(135deg, #73a5ff, #5477f5);\n position: fixed;\n opacity: 0;\n transition: all 0.4s cubic-bezier(0.215, 0.61, 0.355, 1);\n border-radius: 2px;\n cursor: pointer;\n text-decoration: none;\n max-width: calc(50% - 20px);\n z-index: 2147483647;\n bottom: -150px;\n right: 15px;\n }\n\n #${id} .toastify.on {\n opacity: 1;\n }\n\n #${id} .toast-close {\n background: transparent;\n border: 0;\n color: white;\n cursor: pointer;\n font-family: inherit;\n font-size: 1em;\n opacity: 0.4;\n padding: 0 5px;\n }\n `;
282
+ document.head.appendChild(styles);
283
+ }
284
+ return element;
285
+ }
286
+
287
+ let deprecationWarnings = true;
288
+
289
+ var Deprecate = {
290
+ get enabled() {
291
+ return deprecationWarnings;
292
+ },
293
+ get disabled() {
294
+ return !deprecationWarnings;
295
+ },
296
+ get value() {
297
+ return deprecationWarnings;
298
+ },
299
+ set(value) {
300
+ deprecationWarnings = !!value;
301
+ },
302
+ set deprecate(value) {
303
+ deprecationWarnings = !!value;
304
+ }
305
+ };
306
+
307
+ let debugging = false;
308
+
309
+ var Debug$1 = {
310
+ get enabled() {
311
+ return debugging;
312
+ },
313
+ get disabled() {
314
+ return !debugging;
315
+ },
316
+ get value() {
317
+ return debugging;
318
+ },
319
+ set(value) {
320
+ debugging = !!value;
321
+ },
322
+ set debug(value) {
323
+ debugging = !!value;
324
+ }
325
+ };
326
+
7
327
  const defaultSchema = {
8
328
  reflexAttribute: "data-reflex",
9
329
  reflexPermanentAttribute: "data-reflex-permanent",
@@ -26,184 +346,469 @@ var Schema = {
26
346
  ...application.schema
27
347
  };
28
348
  for (const attribute in schema) {
29
- Object.defineProperty(this, attribute.slice(0, -9), {
30
- get: () => schema[attribute]
349
+ const attributeName = attribute.slice(0, -9);
350
+ Object.defineProperty(this, attributeName, {
351
+ get: () => schema[attribute],
352
+ configurable: true
31
353
  });
32
354
  }
33
355
  }
34
356
  };
35
357
 
36
- let debugging = false;
358
+ const {debounce: debounce, dispatch: dispatch, xpathToElement: xpathToElement, xpathToElementArray: xpathToElementArray} = Utils;
37
359
 
38
- var Debug$1 = {
39
- get enabled() {
40
- return debugging;
41
- },
42
- get disabled() {
43
- return !debugging;
44
- },
45
- get value() {
46
- return debugging;
47
- },
48
- set(value) {
49
- debugging = !!value;
50
- },
51
- set debug(value) {
52
- debugging = !!value;
360
+ const uuidv4 = () => {
361
+ const crypto = window.crypto || window.msCrypto;
362
+ return ([ 1e7 ] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)));
363
+ };
364
+
365
+ const serializeForm = (form, options = {}) => {
366
+ if (!form) return "";
367
+ const w = options.w || window;
368
+ const {element: element} = options;
369
+ const formData = new w.FormData(form);
370
+ const data = Array.from(formData, (e => e.map(encodeURIComponent).join("=")));
371
+ const submitButton = form.querySelector("input[type=submit]");
372
+ if (element && element.name && element.nodeName === "INPUT" && element.type === "submit") {
373
+ data.push(`${encodeURIComponent(element.name)}=${encodeURIComponent(element.value)}`);
374
+ } else if (submitButton && submitButton.name) {
375
+ data.push(`${encodeURIComponent(submitButton.name)}=${encodeURIComponent(submitButton.value)}`);
53
376
  }
377
+ return Array.from(data).join("&");
378
+ };
379
+
380
+ const camelize = (value, uppercaseFirstLetter = true) => {
381
+ if (typeof value !== "string") return "";
382
+ value = value.replace(/[\s_](.)/g, ($1 => $1.toUpperCase())).replace(/[\s_]/g, "").replace(/^(.)/, ($1 => $1.toLowerCase()));
383
+ if (uppercaseFirstLetter) value = value.substr(0, 1).toUpperCase() + value.substr(1);
384
+ return value;
54
385
  };
55
386
 
56
- const reflexes = {};
387
+ const XPathToElement = xpathToElement;
57
388
 
58
- const request = (reflexId, target, args, controller, element, controllerElement) => {
59
- const reflex = reflexes[reflexId];
60
- if (Debug$1.disabled || reflex.promise.data.suppressLogging) return;
61
- reflex.timestamp = new Date;
62
- console.log(`↑ stimulus ${target}`, {
63
- reflexId: reflexId,
64
- args: args,
65
- controller: controller,
66
- element: element,
67
- controllerElement: controllerElement
68
- });
389
+ const XPathToArray = xpathToElementArray;
390
+
391
+ const emitEvent = (name, detail = {}) => dispatch(document, name, detail);
392
+
393
+ const extractReflexName = reflexString => {
394
+ const match = reflexString.match(/(?:.*->)?(.*?)(?:Reflex)?#/);
395
+ return match ? match[1] : "";
69
396
  };
70
397
 
71
- const success = (event, halted) => {
72
- const {detail: detail} = event || {};
73
- const {selector: selector, payload: payload} = detail || {};
74
- const {reflexId: reflexId, target: target, morph: morph} = detail.stimulusReflex || {};
75
- const reflex = reflexes[reflexId];
76
- if (Debug$1.disabled || reflex.promise.data.suppressLogging) return;
77
- const progress = reflex.totalOperations > 1 ? ` ${reflex.completedOperations}/${reflex.totalOperations}` : "";
78
- const duration = reflex.timestamp ? `in ${new Date - reflex.timestamp}ms` : "CLONED";
79
- const operation = event.type.split(":")[1].split("-").slice(1).join("_");
80
- console.log(`↓ reflex ${target} → ${selector || "∞"}${progress} ${duration}`, {
81
- reflexId: reflexId,
82
- morph: morph,
83
- operation: operation,
84
- halted: halted,
85
- payload: payload
86
- });
398
+ const elementToXPath = element => {
399
+ if (element.id !== "") return "//*[@id='" + element.id + "']";
400
+ if (element === document.body) return "/html/body";
401
+ if (element.nodeName === "HTML") return "/html";
402
+ let ix = 0;
403
+ const siblings = element && element.parentNode ? element.parentNode.childNodes : [];
404
+ for (var i = 0; i < siblings.length; i++) {
405
+ const sibling = siblings[i];
406
+ if (sibling === element) {
407
+ const computedPath = elementToXPath(element.parentNode);
408
+ const tagName = element.tagName.toLowerCase();
409
+ const ixInc = ix + 1;
410
+ return `${computedPath}/${tagName}[${ixInc}]`;
411
+ }
412
+ if (sibling.nodeType === 1 && sibling.tagName === element.tagName) {
413
+ ix++;
414
+ }
415
+ }
87
416
  };
88
417
 
89
- const error$1 = event => {
90
- const {detail: detail} = event || {};
91
- const {reflexId: reflexId, target: target, payload: payload} = detail.stimulusReflex || {};
92
- const reflex = reflexes[reflexId];
93
- if (Debug$1.disabled || reflex.promise.data.suppressLogging) return;
94
- const duration = reflex.timestamp ? `in ${new Date - reflex.timestamp}ms` : "CLONED";
95
- console.log(`↓ reflex ${target} ${duration} %cERROR: ${event.detail.body}`, "color: #f00;", {
96
- reflexId: reflexId,
97
- payload: payload
98
- });
418
+ const elementInvalid = element => element.type === "number" && element.validity && element.validity.badInput;
419
+
420
+ const getReflexElement = (args, element) => args[0] && args[0].nodeType === Node.ELEMENT_NODE ? args.shift() : element;
421
+
422
+ const getReflexOptions = args => {
423
+ const options = {};
424
+ if (args[0] && typeof args[0] === "object" && Object.keys(args[0]).filter((key => [ "id", "attrs", "selectors", "reflexId", "resolveLate", "serializeForm", "suppressLogging", "includeInnerHTML", "includeTextContent" ].includes(key))).length) {
425
+ const opts = args.shift();
426
+ Object.keys(opts).forEach((o => {
427
+ if (o === "reflexId") {
428
+ if (Deprecate.enabled) console.warn("reflexId option will be removed in v4. Use id instead.");
429
+ options["id"] = opts["reflexId"];
430
+ } else options[o] = opts[o];
431
+ }));
432
+ }
433
+ return options;
99
434
  };
100
435
 
101
- var Log = {
102
- request: request,
103
- success: success,
104
- error: error$1
436
+ const getReflexRoots = element => {
437
+ let list = [];
438
+ while (list.length === 0 && element) {
439
+ let reflexRoot = element.getAttribute(Schema.reflexRoot);
440
+ if (reflexRoot) {
441
+ if (reflexRoot.length === 0 && element.id) reflexRoot = `#${element.id}`;
442
+ const selectors = reflexRoot.split(",").filter((s => s.trim().length));
443
+ if (Debug$1.enabled && selectors.length === 0) {
444
+ console.error(`No value found for ${Schema.reflexRoot}. Add an #id to the element or provide a value for ${Schema.reflexRoot}.`, element);
445
+ }
446
+ list = list.concat(selectors.filter((s => document.querySelector(s))));
447
+ }
448
+ element = element.parentElement ? element.parentElement.closest(`[${Schema.reflexRoot}]`) : null;
449
+ }
450
+ return list;
105
451
  };
106
452
 
107
- let deprecationWarnings = true;
453
+ const reflexNameToControllerIdentifier = reflexName => reflexName.replace(/([a-z0–9])([A-Z])/g, "$1-$2").replace(/(::)/g, "--").replace(/-reflex$/gi, "").toLowerCase();
108
454
 
109
- var Deprecate = {
110
- get enabled() {
111
- return deprecationWarnings;
455
+ const stages = [ "created", "before", "delivered", "queued", "after", "finalized", "success", "error", "halted", "forbidden" ];
456
+
457
+ let lastReflex;
458
+
459
+ const reflexes = new Proxy({}, {
460
+ get: function(target, prop) {
461
+ if (stages.includes(prop)) return Object.fromEntries(Object.entries(target).filter((([_, reflex]) => reflex.stage === prop))); else if (prop === "last") return lastReflex; else if (prop === "all") return target;
462
+ return Reflect.get(...arguments);
112
463
  },
113
- get disabled() {
114
- return !deprecationWarnings;
464
+ set: function(target, prop, value) {
465
+ target[prop] = value;
466
+ lastReflex = value;
467
+ return true;
468
+ }
469
+ });
470
+
471
+ const invokeLifecycleMethod = (reflex, stage) => {
472
+ const specificLifecycleMethod = reflex.controller[[ "before", "after", "finalize" ].includes(stage) ? `${stage}${camelize(reflex.action)}` : `${camelize(reflex.action, false)}${camelize(stage)}`];
473
+ const genericLifecycleMethod = reflex.controller[[ "before", "after", "finalize" ].includes(stage) ? `${stage}Reflex` : `reflex${camelize(stage)}`];
474
+ if (typeof specificLifecycleMethod === "function") {
475
+ specificLifecycleMethod.call(reflex.controller, reflex.element, reflex.target, reflex.error, reflex.id, reflex.payload);
476
+ }
477
+ if (typeof genericLifecycleMethod === "function") {
478
+ genericLifecycleMethod.call(reflex.controller, reflex.element, reflex.target, reflex.error, reflex.id, reflex.payload);
479
+ }
480
+ };
481
+
482
+ const dispatchLifecycleEvent = (reflex, stage) => {
483
+ if (!reflex.controller.element.parentElement) {
484
+ if (Debug$1.enabled && !reflex.warned) {
485
+ 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?`);
486
+ reflex.warned = true;
487
+ }
488
+ return;
489
+ }
490
+ reflex.stage = stage;
491
+ reflex.lifecycle.push(stage);
492
+ const event = `stimulus-reflex:${stage}`;
493
+ const action = `${event}:${reflex.action}`;
494
+ const detail = {
495
+ reflex: reflex.target,
496
+ controller: reflex.controller,
497
+ id: reflex.id,
498
+ element: reflex.element,
499
+ payload: reflex.payload
500
+ };
501
+ const options = {
502
+ bubbles: true,
503
+ cancelable: false,
504
+ detail: detail
505
+ };
506
+ reflex.controller.element.dispatchEvent(new CustomEvent(event, options));
507
+ reflex.controller.element.dispatchEvent(new CustomEvent(action, options));
508
+ if (window.jQuery) {
509
+ window.jQuery(reflex.controller.element).trigger(event, detail);
510
+ window.jQuery(reflex.controller.element).trigger(action, detail);
511
+ }
512
+ };
513
+
514
+ document.addEventListener("stimulus-reflex:before", (event => invokeLifecycleMethod(reflexes[event.detail.id], "before")), true);
515
+
516
+ document.addEventListener("stimulus-reflex:queued", (event => invokeLifecycleMethod(reflexes[event.detail.id], "queued")), true);
517
+
518
+ document.addEventListener("stimulus-reflex:delivered", (event => invokeLifecycleMethod(reflexes[event.detail.id], "delivered")), true);
519
+
520
+ document.addEventListener("stimulus-reflex:success", (event => {
521
+ const reflex = reflexes[event.detail.id];
522
+ invokeLifecycleMethod(reflex, "success");
523
+ dispatchLifecycleEvent(reflex, "after");
524
+ }), true);
525
+
526
+ document.addEventListener("stimulus-reflex:nothing", (event => dispatchLifecycleEvent(reflexes[event.detail.id], "success")), true);
527
+
528
+ document.addEventListener("stimulus-reflex:error", (event => {
529
+ const reflex = reflexes[event.detail.id];
530
+ invokeLifecycleMethod(reflex, "error");
531
+ dispatchLifecycleEvent(reflex, "after");
532
+ }), true);
533
+
534
+ document.addEventListener("stimulus-reflex:halted", (event => invokeLifecycleMethod(reflexes[event.detail.id], "halted")), true);
535
+
536
+ document.addEventListener("stimulus-reflex:forbidden", (event => invokeLifecycleMethod(reflexes[event.detail.id], "forbidden")), true);
537
+
538
+ document.addEventListener("stimulus-reflex:after", (event => invokeLifecycleMethod(reflexes[event.detail.id], "after")), true);
539
+
540
+ document.addEventListener("stimulus-reflex:finalize", (event => invokeLifecycleMethod(reflexes[event.detail.id], "finalize")), true);
541
+
542
+ let app = {};
543
+
544
+ var App = {
545
+ get app() {
546
+ return app;
115
547
  },
116
- get value() {
117
- return deprecationWarnings;
548
+ set(application) {
549
+ app = application;
550
+ }
551
+ };
552
+
553
+ let isolationMode = false;
554
+
555
+ var IsolationMode = {
556
+ get disabled() {
557
+ return !isolationMode;
118
558
  },
119
559
  set(value) {
120
- deprecationWarnings = !!value;
121
- },
122
- set deprecate(value) {
123
- deprecationWarnings = !!value;
560
+ isolationMode = value;
561
+ if (Deprecate.enabled && !isolationMode) {
562
+ document.addEventListener("DOMContentLoaded", (() => 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. Use CableReady operations to broadcast updates to other tabs and users.")), {
563
+ once: true
564
+ });
565
+ }
566
+ }
567
+ };
568
+
569
+ class Reflex {
570
+ constructor(data, controller) {
571
+ this.data = data.valueOf();
572
+ this.controller = controller;
573
+ this.element = data.reflexElement;
574
+ this.id = data.id;
575
+ this.error = null;
576
+ this.payload = null;
577
+ this.stage = "created";
578
+ this.lifecycle = [ "created" ];
579
+ this.warned = false;
580
+ this.target = data.target;
581
+ this.action = data.target.split("#")[1];
582
+ this.selector = null;
583
+ this.morph = null;
584
+ this.operation = null;
585
+ this.timestamp = new Date;
586
+ this.cloned = false;
587
+ }
588
+ get getPromise() {
589
+ const promise = new Promise(((resolve, reject) => {
590
+ this.promise = {
591
+ resolve: resolve,
592
+ reject: reject,
593
+ data: this.data
594
+ };
595
+ }));
596
+ promise.id = this.id;
597
+ Object.defineProperty(promise, "reflexId", {
598
+ get() {
599
+ if (Deprecate.enabled) console.warn("reflexId is deprecated and will be removed from v4. Use id instead.");
600
+ return this.id;
601
+ }
602
+ });
603
+ promise.reflex = this;
604
+ if (Debug$1.enabled) promise.catch((() => {}));
605
+ return promise;
606
+ }
607
+ }
608
+
609
+ const received = data => {
610
+ if (!data.cableReady) return;
611
+ if (data.version.replace(".pre", "-pre") !== CableReady.version) {
612
+ 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.version}`);
613
+ return;
614
+ }
615
+ let reflexOperations = [];
616
+ for (let i = data.operations.length - 1; i >= 0; i--) {
617
+ if (data.operations[i].stimulusReflex) {
618
+ reflexOperations.push(data.operations[i]);
619
+ data.operations.splice(i, 1);
620
+ }
621
+ }
622
+ if (reflexOperations.some((operation => operation.stimulusReflex.url !== location.href))) {
623
+ if (Debug$1.enabled) {
624
+ console.error("Reflex failed due to mismatched URL.");
625
+ return;
626
+ }
627
+ }
628
+ let reflexData;
629
+ if (reflexOperations.length) {
630
+ reflexData = reflexOperations[0].stimulusReflex;
631
+ reflexData.payload = reflexOperations[0].payload;
632
+ }
633
+ if (reflexData) {
634
+ const {id: id, payload: payload} = reflexData;
635
+ let reflex;
636
+ if (!reflexes[id] && IsolationMode.disabled) {
637
+ const controllerElement = XPathToElement(reflexData.xpathController);
638
+ const reflexElement = XPathToElement(reflexData.xpathElement);
639
+ controllerElement.reflexController = controllerElement.reflexController || {};
640
+ controllerElement.reflexData = controllerElement.reflexData || {};
641
+ controllerElement.reflexError = controllerElement.reflexError || {};
642
+ const controller = App.app.getControllerForElementAndIdentifier(controllerElement, reflexData.reflexController);
643
+ controllerElement.reflexController[id] = controller;
644
+ controllerElement.reflexData[id] = reflexData;
645
+ reflex = new Reflex(reflexData, controller);
646
+ reflexes[id] = reflex;
647
+ reflex.cloned = true;
648
+ reflex.element = reflexElement;
649
+ controller.lastReflex = reflex;
650
+ dispatchLifecycleEvent(reflex, "before");
651
+ reflex.getPromise;
652
+ } else {
653
+ reflex = reflexes[id];
654
+ }
655
+ if (reflex) {
656
+ reflex.payload = payload;
657
+ reflex.totalOperations = reflexOperations.length;
658
+ reflex.pendingOperations = reflexOperations.length;
659
+ reflex.completedOperations = 0;
660
+ reflex.piggybackOperations = data.operations;
661
+ CableReady.perform(reflexOperations);
662
+ }
663
+ } else {
664
+ if (data.operations.length && reflexes[data.operations[0].reflexId]) {
665
+ CableReady.perform(data.operations);
666
+ }
124
667
  }
125
668
  };
126
669
 
127
- const uuidv4 = () => {
128
- const crypto = window.crypto || window.msCrypto;
129
- return ([ 1e7 ] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)));
670
+ let consumer;
671
+
672
+ let params;
673
+
674
+ let subscription;
675
+
676
+ let active;
677
+
678
+ const initialize$1 = (consumerValue, paramsValue) => {
679
+ consumer = consumerValue;
680
+ params = paramsValue;
681
+ document.addEventListener("DOMContentLoaded", (() => {
682
+ active = false;
683
+ connectionStatusClass();
684
+ if (Deprecate.enabled && consumerValue) 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.");
685
+ }));
686
+ document.addEventListener("turbolinks:load", connectionStatusClass);
687
+ document.addEventListener("turbo:load", connectionStatusClass);
688
+ };
689
+
690
+ const subscribe = controller => {
691
+ if (subscription) return;
692
+ consumer = consumer || controller.application.consumer || createConsumer();
693
+ const {channel: channel} = controller.StimulusReflex;
694
+ const request = {
695
+ channel: channel,
696
+ ...params
697
+ };
698
+ const identifier = JSON.stringify(request);
699
+ subscription = consumer.subscriptions.findAll(identifier)[0] || consumer.subscriptions.create(request, {
700
+ received: received,
701
+ connected: connected,
702
+ rejected: rejected,
703
+ disconnected: disconnected
704
+ });
705
+ };
706
+
707
+ const connected = () => {
708
+ active = true;
709
+ connectionStatusClass();
710
+ emitEvent("stimulus-reflex:connected");
711
+ Object.values(reflexes.queued).forEach((reflex => {
712
+ subscription.send(reflex.data);
713
+ dispatchLifecycleEvent(reflex, "delivered");
714
+ }));
715
+ };
716
+
717
+ const rejected = () => {
718
+ active = false;
719
+ connectionStatusClass();
720
+ emitEvent("stimulus-reflex:rejected");
721
+ if (Debug.enabled) console.warn("Channel subscription was rejected.");
722
+ };
723
+
724
+ const disconnected = willAttemptReconnect => {
725
+ active = false;
726
+ connectionStatusClass();
727
+ emitEvent("stimulus-reflex:disconnected", willAttemptReconnect);
130
728
  };
131
729
 
132
- const serializeForm = (form, options = {}) => {
133
- if (!form) return "";
134
- const w = options.w || window;
135
- const {element: element} = options;
136
- const formData = new w.FormData(form);
137
- const data = Array.from(formData, (e => e.map(encodeURIComponent).join("=")));
138
- const submitButton = form.querySelector("input[type=submit]");
139
- if (element && element.name && element.nodeName === "INPUT" && element.type === "submit") {
140
- data.push(`${encodeURIComponent(element.name)}=${encodeURIComponent(element.value)}`);
141
- } else if (submitButton && submitButton.name) {
142
- data.push(`${encodeURIComponent(submitButton.name)}=${encodeURIComponent(submitButton.value)}`);
730
+ const deliver = reflex => {
731
+ if (active) {
732
+ subscription.send(reflex.data);
733
+ dispatchLifecycleEvent(reflex, "delivered");
734
+ } else dispatchLifecycleEvent(reflex, "queued");
735
+ };
736
+
737
+ const connectionStatusClass = () => {
738
+ const list = document.body.classList;
739
+ if (!(list.contains("stimulus-reflex-connected") || list.contains("stimulus-reflex-disconnected"))) {
740
+ list.add(active ? "stimulus-reflex-connected" : "stimulus-reflex-disconnected");
741
+ return;
742
+ }
743
+ if (active) {
744
+ list.replace("stimulus-reflex-disconnected", "stimulus-reflex-connected");
745
+ } else {
746
+ list.replace("stimulus-reflex-connected", "stimulus-reflex-disconnected");
143
747
  }
144
- return Array.from(data).join("&");
145
748
  };
146
749
 
147
- const camelize = (value, uppercaseFirstLetter = true) => {
148
- if (typeof value !== "string") return "";
149
- value = value.replace(/[\s_](.)/g, ($1 => $1.toUpperCase())).replace(/[\s_]/g, "").replace(/^(.)/, ($1 => $1.toLowerCase()));
150
- if (uppercaseFirstLetter) value = value.substr(0, 1).toUpperCase() + value.substr(1);
151
- return value;
750
+ var ActionCableTransport = {
751
+ subscribe: subscribe,
752
+ deliver: deliver,
753
+ initialize: initialize$1
754
+ };
755
+
756
+ const request = reflex => {
757
+ if (Debug$1.disabled || reflex.data.suppressLogging) return;
758
+ console.log(`↑ stimulus ↑ ${reflex.target}`, {
759
+ id: reflex.id,
760
+ args: reflex.data.args,
761
+ controller: reflex.controller.identifier,
762
+ element: reflex.element,
763
+ controllerElement: reflex.controller.element
764
+ });
152
765
  };
153
766
 
154
- const debounce = (callback, delay = 250) => {
155
- let timeoutId;
156
- return (...args) => {
157
- clearTimeout(timeoutId);
158
- timeoutId = setTimeout((() => {
159
- timeoutId = null;
160
- callback(...args);
161
- }), delay);
767
+ const success = reflex => {
768
+ if (Debug$1.disabled || reflex.data.suppressLogging) return;
769
+ const output = {
770
+ id: reflex.id,
771
+ morph: reflex.morph,
772
+ payload: reflex.payload
162
773
  };
774
+ if (reflex.operation !== "dispatch_event") output.operation = reflex.operation;
775
+ console.log(`↓ reflex ↓ ${reflex.target} → ${reflex.selector || "∞"}${progress(reflex)} ${duration(reflex)}`, output);
163
776
  };
164
777
 
165
- const extractReflexName = reflexString => {
166
- const match = reflexString.match(/(?:.*->)?(.*?)(?:Reflex)?#/);
167
- return match ? match[1] : "";
778
+ const halted$1 = reflex => {
779
+ if (Debug$1.disabled || reflex.data.suppressLogging) return;
780
+ console.log(`↓ reflex ${reflex.target} ${duration(reflex)} %cHALTED`, "color: #ffa500;", {
781
+ id: reflex.id,
782
+ payload: reflex.payload
783
+ });
168
784
  };
169
785
 
170
- const emitEvent = (event, detail) => {
171
- document.dispatchEvent(new CustomEvent(event, {
172
- bubbles: true,
173
- cancelable: false,
174
- detail: detail
175
- }));
176
- if (window.jQuery) window.jQuery(document).trigger(event, detail);
786
+ const forbidden$1 = reflex => {
787
+ if (Debug$1.disabled || reflex.data.suppressLogging) return;
788
+ console.log(`↓ reflex ↓ ${reflex.target} ${duration(reflex)} %cFORBIDDEN`, "color: #BF40BF;", {
789
+ id: reflex.id,
790
+ payload: reflex.payload
791
+ });
177
792
  };
178
793
 
179
- const elementToXPath = element => {
180
- if (element.id !== "") return "//*[@id='" + element.id + "']";
181
- if (element === document.body) return "/html/body";
182
- let ix = 0;
183
- const siblings = element?.parentNode ? element.parentNode.childNodes : [];
184
- for (var i = 0; i < siblings.length; i++) {
185
- const sibling = siblings[i];
186
- if (sibling === element) {
187
- const computedPath = elementToXPath(element.parentNode);
188
- const tagName = element.tagName.toLowerCase();
189
- const ixInc = ix + 1;
190
- return `${computedPath}/${tagName}[${ixInc}]`;
191
- }
192
- if (sibling.nodeType === 1 && sibling.tagName === element.tagName) {
193
- ix++;
194
- }
195
- }
794
+ const error$1 = reflex => {
795
+ if (Debug$1.disabled || reflex.data.suppressLogging) return;
796
+ console.log(`↓ reflex ↓ ${reflex.target} ${duration(reflex)} %cERROR: ${reflex.error}`, "color: #f00;", {
797
+ id: reflex.id,
798
+ payload: reflex.payload
799
+ });
196
800
  };
197
801
 
198
- const XPathToElement = xpath => document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
802
+ const duration = reflex => !reflex.cloned ? `in ${new Date - reflex.timestamp}ms` : "CLONED";
199
803
 
200
- const XPathToArray = (xpath, reverse = false) => {
201
- const snapshotList = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
202
- const snapshots = [];
203
- for (let i = 0; i < snapshotList.snapshotLength; i++) {
204
- snapshots.push(snapshotList.snapshotItem(i));
205
- }
206
- return reverse ? snapshots.reverse() : snapshots;
804
+ const progress = reflex => reflex.totalOperations > 1 ? ` ${reflex.completedOperations}/${reflex.totalOperations}` : "";
805
+
806
+ var Log = {
807
+ request: request,
808
+ success: success,
809
+ halted: halted$1,
810
+ forbidden: forbidden$1,
811
+ error: error$1
207
812
  };
208
813
 
209
814
  const multipleInstances = element => {
@@ -216,8 +821,8 @@ const multipleInstances = element => {
216
821
  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));
217
822
 
218
823
  const attributeValue = (values = []) => {
219
- const value = values.filter((v => v && String(v).length)).map((v => v.trim())).join(" ").trim();
220
- return value.length ? value : null;
824
+ const value = Array.from(new Set(values.filter((v => v && String(v).length)).map((v => v.trim())))).join(" ").trim();
825
+ return value.length > 0 ? value : null;
221
826
  };
222
827
 
223
828
  const attributeValues = value => {
@@ -331,235 +936,98 @@ const extractDataAttributes = element => {
331
936
  return attrs;
332
937
  };
333
938
 
334
- let isolationMode = false;
939
+ var name = "stimulus_reflex";
335
940
 
336
- var IsolationMode = {
337
- get disabled() {
338
- return !isolationMode;
339
- },
340
- set(value) {
341
- isolationMode = value;
342
- }
343
- };
941
+ var version = "3.5.0-rc1";
344
942
 
345
- const invokeLifecycleMethod = (stage, reflexElement, controllerElement, reflexId, payload) => {
346
- if (!controllerElement || !controllerElement.reflexData[reflexId]) return;
347
- const controller = controllerElement.reflexController[reflexId];
348
- const reflex = controllerElement.reflexData[reflexId].target;
349
- const reflexMethodName = reflex.split("#")[1];
350
- const specificLifecycleMethodName = [ "before", "after", "finalize" ].includes(stage) ? `${stage}${camelize(reflexMethodName)}` : `${camelize(reflexMethodName, false)}${camelize(stage)}`;
351
- const specificLifecycleMethod = controller[specificLifecycleMethodName];
352
- const genericLifecycleMethodName = [ "before", "after", "finalize" ].includes(stage) ? `${stage}Reflex` : `reflex${camelize(stage)}`;
353
- const genericLifecycleMethod = controller[genericLifecycleMethodName];
354
- if (typeof specificLifecycleMethod === "function") {
355
- specificLifecycleMethod.call(controller, reflexElement, reflex, controllerElement.reflexError[reflexId], reflexId, payload);
356
- }
357
- if (typeof genericLifecycleMethod === "function") {
358
- genericLifecycleMethod.call(controller, reflexElement, reflex, controllerElement.reflexError[reflexId], reflexId, payload);
359
- }
360
- if (reflexes[reflexId] && stage === reflexes[reflexId].finalStage) {
361
- Reflect.deleteProperty(controllerElement.reflexController, reflexId);
362
- Reflect.deleteProperty(controllerElement.reflexData, reflexId);
363
- Reflect.deleteProperty(controllerElement.reflexError, reflexId);
364
- }
365
- };
943
+ var description = "Build reactive applications with the Rails tooling you already know and love.";
366
944
 
367
- document.addEventListener("stimulus-reflex:before", (event => invokeLifecycleMethod("before", event.detail.element, event.detail.controller.element, event.detail.reflexId, event.detail.payload)), true);
945
+ var keywords = [ "ruby", "rails", "websockets", "actioncable", "turbolinks", "reactive", "cable", "ujs", "ssr", "stimulus", "reflex", "stimulus_reflex", "dom", "morphdom" ];
368
946
 
369
- document.addEventListener("stimulus-reflex:success", (event => {
370
- invokeLifecycleMethod("success", event.detail.element, event.detail.controller.element, event.detail.reflexId, event.detail.payload);
371
- dispatchLifecycleEvent("after", event.detail.element, event.detail.controller.element, event.detail.reflexId, event.detail.payload);
372
- }), true);
947
+ var homepage = "https://docs.stimulusreflex.com";
373
948
 
374
- document.addEventListener("stimulus-reflex:nothing", (event => {
375
- dispatchLifecycleEvent("success", event.detail.element, event.detail.controller.element, event.detail.reflexId, event.detail.payload);
376
- }), true);
949
+ var bugs = "https://github.com/stimulusreflex/stimulus_reflex/issues";
377
950
 
378
- document.addEventListener("stimulus-reflex:error", (event => {
379
- invokeLifecycleMethod("error", event.detail.element, event.detail.controller.element, event.detail.reflexId, event.detail.payload);
380
- dispatchLifecycleEvent("after", event.detail.element, event.detail.controller.element, event.detail.reflexId, event.detail.payload);
381
- }), true);
951
+ var repository = "https://github.com/stimulusreflex/stimulus_reflex";
382
952
 
383
- document.addEventListener("stimulus-reflex:halted", (event => invokeLifecycleMethod("halted", event.detail.element, event.detail.controller.element, event.detail.reflexId, event.detail.payload)), true);
953
+ var license = "MIT";
384
954
 
385
- document.addEventListener("stimulus-reflex:after", (event => invokeLifecycleMethod("after", event.detail.element, event.detail.controller.element, event.detail.reflexId, event.detail.payload)), true);
955
+ var author = "Nathan Hopkins <natehop@gmail.com>";
386
956
 
387
- document.addEventListener("stimulus-reflex:finalize", (event => invokeLifecycleMethod("finalize", event.detail.element, event.detail.controller.element, event.detail.reflexId, event.detail.payload)), true);
957
+ var contributors = [ "Andrew Mason <andrewmcodes@protonmail.com>", "Julian Rubisch <julian@julianrubisch.at>", "Marco Roth <marco.roth@intergga.ch>", "Nathan Hopkins <natehop@gmail.com>" ];
388
958
 
389
- const dispatchLifecycleEvent = (stage, reflexElement, controllerElement, reflexId, payload) => {
390
- if (!controllerElement) {
391
- if (Debug$1.enabled && !reflexes[reflexId].warned) {
392
- 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?`);
393
- reflexes[reflexId].warned = true;
394
- }
395
- return;
396
- }
397
- if (!controllerElement.reflexController || controllerElement.reflexController && !controllerElement.reflexController[reflexId]) {
398
- if (Debug$1.enabled && !reflexes[reflexId].warned) {
399
- 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.`);
400
- reflexes[reflexId].warned = true;
401
- }
402
- return;
403
- }
404
- const {target: target} = controllerElement.reflexData[reflexId] || {};
405
- const controller = controllerElement.reflexController[reflexId] || {};
406
- const event = `stimulus-reflex:${stage}`;
407
- const action = `${event}:${target.split("#")[1]}`;
408
- const detail = {
409
- reflex: target,
410
- controller: controller,
411
- reflexId: reflexId,
412
- element: reflexElement,
413
- payload: payload
414
- };
415
- const options = {
416
- bubbles: true,
417
- cancelable: false,
418
- detail: detail
419
- };
420
- controllerElement.dispatchEvent(new CustomEvent(event, options));
421
- controllerElement.dispatchEvent(new CustomEvent(action, options));
422
- if (window.jQuery) {
423
- window.jQuery(controllerElement).trigger(event, detail);
424
- window.jQuery(controllerElement).trigger(action, detail);
425
- }
426
- };
959
+ var main = "./dist/stimulus_reflex.js";
427
960
 
428
- const localReflexControllers = (app, element) => attributeValues(element.getAttribute(Schema.controller)).reduce(((memo, name) => {
429
- const controller = app.getControllerForElementAndIdentifier(element, name);
430
- if (controller && controller.StimulusReflex) memo.push(controller);
431
- return memo;
432
- }), []);
961
+ var module = "./dist/stimulus_reflex.js";
433
962
 
434
- const allReflexControllers = (app, element) => {
435
- let controllers = [];
436
- while (element) {
437
- controllers = controllers.concat(localReflexControllers(app, element));
438
- element = element.parentElement;
439
- }
440
- return controllers;
441
- };
963
+ var browser = "./dist/stimulus_reflex.js";
442
964
 
443
- const findControllerByReflexName = (reflexName, controllers) => {
444
- const controller = controllers.find((controller => {
445
- if (!controller.identifier) return;
446
- return extractReflexName(reflexName).replace(/([a-z0–9])([A-Z])/g, "$1-$2").replace(/(::)/g, "--").toLowerCase() === controller.identifier;
447
- }));
448
- return controller || controllers[0];
449
- };
965
+ var unpkg = "./dist/stimulus_reflex.umd.js";
450
966
 
451
- const received = data => {
452
- if (!data.cableReady) return;
453
- if (data.version.replace(".pre", "-pre") !== CableReady.version) {
454
- 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.version}`);
455
- return;
456
- }
457
- let reflexOperations = [];
458
- for (let i = data.operations.length - 1; i >= 0; i--) {
459
- if (data.operations[i].stimulusReflex) {
460
- reflexOperations.push(data.operations[i]);
461
- data.operations.splice(i, 1);
462
- }
463
- }
464
- if (reflexOperations.some((operation => operation.stimulusReflex.url !== location.href))) {
465
- return;
466
- }
467
- let reflexData;
468
- if (reflexOperations.length) {
469
- reflexData = reflexOperations[0].stimulusReflex;
470
- reflexData.payload = reflexOperations[0].payload;
471
- }
472
- if (reflexData) {
473
- const {reflexId: reflexId, payload: payload} = reflexData;
474
- if (!reflexes[reflexId] && IsolationMode.disabled) {
475
- const controllerElement = XPathToElement(reflexData.xpathController);
476
- const reflexElement = XPathToElement(reflexData.xpathElement);
477
- controllerElement.reflexController = controllerElement.reflexController || {};
478
- controllerElement.reflexData = controllerElement.reflexData || {};
479
- controllerElement.reflexError = controllerElement.reflexError || {};
480
- controllerElement.reflexController[reflexId] = reflexes.app.getControllerForElementAndIdentifier(controllerElement, reflexData.reflexController);
481
- controllerElement.reflexData[reflexId] = reflexData;
482
- dispatchLifecycleEvent("before", reflexElement, controllerElement, reflexId, payload);
483
- registerReflex(reflexData);
484
- }
485
- if (reflexes[reflexId]) {
486
- reflexes[reflexId].totalOperations = reflexOperations.length;
487
- reflexes[reflexId].pendingOperations = reflexOperations.length;
488
- reflexes[reflexId].completedOperations = 0;
489
- reflexes[reflexId].piggybackOperations = data.operations;
490
- CableReady.perform(reflexOperations);
491
- }
492
- } else {
493
- if (data.operations.length && reflexes[data.operations[0].reflexId]) {
494
- CableReady.perform(data.operations);
495
- }
496
- }
967
+ var umd = "./dist/stimulus_reflex.umd.js";
968
+
969
+ var files = [ "dist/*", "javascript/*" ];
970
+
971
+ var scripts = {
972
+ lint: "yarn run format --check",
973
+ format: "yarn run prettier-standard ./javascript/**/*.js rollup.config.mjs",
974
+ build: "yarn rollup -c",
975
+ "build:watch": "yarn rollup -wc",
976
+ watch: "yarn build:watch",
977
+ test: "web-test-runner javascript/test/**/*.test.js",
978
+ "test:watch": "yarn test --watch",
979
+ "docs:dev": "vitepress dev docs",
980
+ "docs:build": "vitepress build docs && cp docs/_redirects docs/.vitepress/dist",
981
+ "docs:preview": "vitepress preview docs"
497
982
  };
498
983
 
499
- const registerReflex = data => {
500
- const {reflexId: reflexId} = data;
501
- reflexes[reflexId] = {
502
- finalStage: "finalize"
503
- };
504
- const promise = new Promise(((resolve, reject) => {
505
- reflexes[reflexId].promise = {
506
- resolve: resolve,
507
- reject: reject,
508
- data: data
509
- };
510
- }));
511
- promise.reflexId = reflexId;
512
- if (Debug$1.enabled) promise.catch((() => {}));
513
- return promise;
984
+ var peerDependencies = {
985
+ "@hotwired/stimulus": ">= 3.0"
514
986
  };
515
987
 
516
- const getReflexRoots = element => {
517
- let list = [];
518
- while (list.length === 0 && element) {
519
- let reflexRoot = element.getAttribute(Schema.reflexRoot);
520
- if (reflexRoot) {
521
- if (reflexRoot.length === 0 && element.id) reflexRoot = `#${element.id}`;
522
- const selectors = reflexRoot.split(",").filter((s => s.trim().length));
523
- if (Debug$1.enabled && selectors.length === 0) {
524
- console.error(`No value found for ${Schema.reflexRoot}. Add an #id to the element or provide a value for ${Schema.reflexRoot}.`, element);
525
- }
526
- list = list.concat(selectors.filter((s => document.querySelector(s))));
527
- }
528
- element = element.parentElement ? element.parentElement.closest(`[${Schema.reflexRoot}]`) : null;
529
- }
530
- return list;
988
+ var dependencies = {
989
+ "@hotwired/stimulus": "^3",
990
+ "@rails/actioncable": "^6 || ^7",
991
+ cable_ready: "5.0.0-rc1"
531
992
  };
532
993
 
533
- const setupDeclarativeReflexes = debounce((() => {
534
- document.querySelectorAll(`[${Schema.reflex}]`).forEach((element => {
535
- const controllers = attributeValues(element.getAttribute(Schema.controller));
536
- const reflexAttributeNames = attributeValues(element.getAttribute(Schema.reflex));
537
- const actions = attributeValues(element.getAttribute(Schema.action));
538
- reflexAttributeNames.forEach((reflexName => {
539
- const controller = findControllerByReflexName(reflexName, allReflexControllers(reflexes.app, element));
540
- let action;
541
- if (controller) {
542
- action = `${reflexName.split("->")[0]}->${controller.identifier}#__perform`;
543
- if (!actions.includes(action)) actions.push(action);
544
- } else {
545
- action = `${reflexName.split("->")[0]}->stimulus-reflex#__perform`;
546
- if (!controllers.includes("stimulus-reflex")) {
547
- controllers.push("stimulus-reflex");
548
- }
549
- if (!actions.includes(action)) actions.push(action);
550
- }
551
- }));
552
- const controllerValue = attributeValue(controllers);
553
- const actionValue = attributeValue(actions);
554
- if (controllerValue && element.getAttribute(Schema.controller) != controllerValue) {
555
- element.setAttribute(Schema.controller, controllerValue);
556
- }
557
- if (actionValue && element.getAttribute(Schema.action) != actionValue) element.setAttribute(Schema.action, actionValue);
558
- }));
559
- emitEvent("stimulus-reflex:ready");
560
- }), 20);
994
+ var devDependencies = {
995
+ "@open-wc/testing": "^3.1.7",
996
+ "@rollup/plugin-json": "^6.0.0",
997
+ "@rollup/plugin-node-resolve": "^15.0.1",
998
+ "@rollup/plugin-terser": "^0.4.0",
999
+ "@web/dev-server-esbuild": "^0.3.3",
1000
+ "@web/dev-server-rollup": "^0.3.21",
1001
+ "@web/test-runner": "^0.15.1",
1002
+ "prettier-standard": "^16.4.1",
1003
+ rollup: "^3.19.1",
1004
+ "toastify-js": "^1.12.0",
1005
+ vitepress: "^1.0.0-alpha.56"
1006
+ };
561
1007
 
562
- var version = "3.5.0-pre9";
1008
+ var packageInfo = {
1009
+ name: name,
1010
+ version: version,
1011
+ description: description,
1012
+ keywords: keywords,
1013
+ homepage: homepage,
1014
+ bugs: bugs,
1015
+ repository: repository,
1016
+ license: license,
1017
+ author: author,
1018
+ contributors: contributors,
1019
+ main: main,
1020
+ module: module,
1021
+ browser: browser,
1022
+ import: "./dist/stimulus_reflex.js",
1023
+ unpkg: unpkg,
1024
+ umd: umd,
1025
+ files: files,
1026
+ scripts: scripts,
1027
+ peerDependencies: peerDependencies,
1028
+ dependencies: dependencies,
1029
+ devDependencies: devDependencies
1030
+ };
563
1031
 
564
1032
  class ReflexData {
565
1033
  constructor(options, reflexElement, controllerElement, reflexController, permanentAttributeName, target, args, url, tabId) {
@@ -577,9 +1045,9 @@ class ReflexData {
577
1045
  this._attrs = this._attrs || this.options["attrs"] || extractElementAttributes(this.reflexElement);
578
1046
  return this._attrs;
579
1047
  }
580
- get reflexId() {
581
- this._reflexId = this._reflexId || this.options["reflexId"] || uuidv4();
582
- return this._reflexId;
1048
+ get id() {
1049
+ this._id = this._id || this.options["id"] || uuidv4();
1050
+ return this._id;
583
1051
  }
584
1052
  get selectors() {
585
1053
  this._selectors = this._selectors || this.options["selectors"] || getReflexRoots(this.reflexElement);
@@ -624,7 +1092,7 @@ class ReflexData {
624
1092
  attrs: this.attrs,
625
1093
  dataset: this.dataset,
626
1094
  selectors: this.selectors,
627
- reflexId: this.reflexId,
1095
+ id: this.id,
628
1096
  resolveLate: this.resolveLate,
629
1097
  suppressLogging: this.suppressLogging,
630
1098
  xpathController: this.xpathController,
@@ -638,185 +1106,199 @@ class ReflexData {
638
1106
  args: this.args,
639
1107
  url: this.url,
640
1108
  tabId: this.tabId,
641
- version: version
1109
+ version: packageInfo.version
642
1110
  };
643
1111
  }
644
1112
  }
645
1113
 
646
- let consumer;
647
-
648
- let params;
649
-
650
- let subscriptionActive;
651
-
652
- const createSubscription = controller => {
653
- consumer = consumer || controller.application.consumer || createConsumer();
654
- const {channel: channel} = controller.StimulusReflex;
655
- const subscription = {
656
- channel: channel,
657
- ...params
658
- };
659
- const identifier = JSON.stringify(subscription);
660
- controller.StimulusReflex.subscription = consumer.subscriptions.findAll(identifier)[0] || consumer.subscriptions.create(subscription, {
661
- received: received,
662
- connected: connected,
663
- rejected: rejected,
664
- disconnected: disconnected
665
- });
666
- };
667
-
668
- const connected = () => {
669
- subscriptionActive = true;
670
- document.body.classList.replace("stimulus-reflex-disconnected", "stimulus-reflex-connected");
671
- emitEvent("stimulus-reflex:connected");
672
- emitEvent("stimulus-reflex:action-cable:connected");
673
- };
674
-
675
- const rejected = () => {
676
- subscriptionActive = false;
677
- document.body.classList.replace("stimulus-reflex-connected", "stimulus-reflex-disconnected");
678
- emitEvent("stimulus-reflex:rejected");
679
- emitEvent("stimulus-reflex:action-cable:rejected");
680
- if (Debug.enabled) console.warn("Channel subscription was rejected.");
681
- };
682
-
683
- const disconnected = willAttemptReconnect => {
684
- subscriptionActive = false;
685
- document.body.classList.replace("stimulus-reflex-connected", "stimulus-reflex-disconnected");
686
- emitEvent("stimulus-reflex:disconnected", willAttemptReconnect);
687
- emitEvent("stimulus-reflex:action-cable:disconnected", willAttemptReconnect);
688
- };
1114
+ let transport = {};
689
1115
 
690
- var ActionCableTransport = {
691
- consumer: consumer,
692
- params: params,
693
- get subscriptionActive() {
694
- return subscriptionActive;
1116
+ var Transport = {
1117
+ get plugin() {
1118
+ return transport;
695
1119
  },
696
- createSubscription: createSubscription,
697
- connected: connected,
698
- rejected: rejected,
699
- disconnected: disconnected,
700
- set(consumerValue, paramsValue) {
701
- consumer = consumerValue;
702
- params = paramsValue;
1120
+ set(newTransport) {
1121
+ transport = newTransport;
703
1122
  }
704
1123
  };
705
1124
 
706
1125
  const beforeDOMUpdate = event => {
707
- const {stimulusReflex: stimulusReflex, payload: payload} = event.detail || {};
1126
+ const {stimulusReflex: stimulusReflex} = event.detail || {};
708
1127
  if (!stimulusReflex) return;
709
- const {reflexId: reflexId, xpathElement: xpathElement, xpathController: xpathController} = stimulusReflex;
710
- const controllerElement = XPathToElement(xpathController);
711
- const reflexElement = XPathToElement(xpathElement);
712
- const reflex = reflexes[reflexId];
713
- const {promise: promise} = reflex;
1128
+ const reflex = reflexes[stimulusReflex.id];
714
1129
  reflex.pendingOperations--;
715
1130
  if (reflex.pendingOperations > 0) return;
716
- if (!stimulusReflex.resolveLate) setTimeout((() => promise.resolve({
717
- element: reflexElement,
1131
+ if (!stimulusReflex.resolveLate) setTimeout((() => reflex.promise.resolve({
1132
+ element: reflex.element,
718
1133
  event: event,
719
- data: promise.data,
720
- payload: payload,
721
- reflexId: reflexId,
1134
+ data: reflex.data,
1135
+ payload: reflex.payload,
1136
+ id: reflex.id,
722
1137
  toString: () => ""
723
1138
  })));
724
- setTimeout((() => dispatchLifecycleEvent("success", reflexElement, controllerElement, reflexId, payload)));
1139
+ setTimeout((() => dispatchLifecycleEvent(reflex, "success")));
725
1140
  };
726
1141
 
727
1142
  const afterDOMUpdate = event => {
728
- const {stimulusReflex: stimulusReflex, payload: payload} = event.detail || {};
1143
+ const {stimulusReflex: stimulusReflex} = event.detail || {};
729
1144
  if (!stimulusReflex) return;
730
- const {reflexId: reflexId, xpathElement: xpathElement, xpathController: xpathController} = stimulusReflex;
731
- const controllerElement = XPathToElement(xpathController);
732
- const reflexElement = XPathToElement(xpathElement);
733
- const reflex = reflexes[reflexId];
734
- const {promise: promise} = reflex;
1145
+ const reflex = reflexes[stimulusReflex.id];
735
1146
  reflex.completedOperations++;
736
- Log.success(event, false);
1147
+ reflex.selector = event.detail.selector;
1148
+ reflex.morph = event.detail.stimulusReflex.morph;
1149
+ reflex.operation = event.type.split(":")[1].split("-").slice(1).join("_");
1150
+ Log.success(reflex);
737
1151
  if (reflex.completedOperations < reflex.totalOperations) return;
738
- if (stimulusReflex.resolveLate) setTimeout((() => promise.resolve({
739
- element: reflexElement,
1152
+ if (stimulusReflex.resolveLate) setTimeout((() => reflex.promise.resolve({
1153
+ element: reflex.element,
740
1154
  event: event,
741
- data: promise.data,
742
- payload: payload,
743
- reflexId: reflexId,
1155
+ data: reflex.data,
1156
+ payload: reflex.payload,
1157
+ id: reflex.id,
744
1158
  toString: () => ""
745
1159
  })));
746
- setTimeout((() => dispatchLifecycleEvent("finalize", reflexElement, controllerElement, reflexId, payload)));
1160
+ setTimeout((() => dispatchLifecycleEvent(reflex, "finalize")));
747
1161
  if (reflex.piggybackOperations.length) CableReady.perform(reflex.piggybackOperations);
748
1162
  };
749
1163
 
750
1164
  const routeReflexEvent = event => {
751
- const {stimulusReflex: stimulusReflex, payload: payload, name: name, body: body} = event.detail || {};
1165
+ const {stimulusReflex: stimulusReflex, name: name} = event.detail || {};
752
1166
  const eventType = name.split("-")[2];
753
- if (!stimulusReflex || ![ "nothing", "halted", "error" ].includes(eventType)) return;
754
- const {reflexId: reflexId, xpathElement: xpathElement, xpathController: xpathController} = stimulusReflex;
755
- const reflexElement = XPathToElement(xpathElement);
756
- const controllerElement = XPathToElement(xpathController);
757
- const reflex = reflexes[reflexId];
758
- const {promise: promise} = reflex;
759
- if (controllerElement) {
760
- controllerElement.reflexError = controllerElement.reflexError || {};
761
- if (eventType === "error") controllerElement.reflexError[reflexId] = body;
762
- }
763
- switch (eventType) {
764
- case "nothing":
765
- nothing(event, payload, promise, reflex, reflexElement);
766
- break;
767
-
768
- case "error":
769
- error(event, payload, promise, reflex, reflexElement);
770
- break;
771
-
772
- case "halted":
773
- halted(event, payload, promise, reflex, reflexElement);
774
- break;
775
- }
776
- setTimeout((() => dispatchLifecycleEvent(eventType, reflexElement, controllerElement, reflexId, payload)));
1167
+ const eventTypes = {
1168
+ nothing: nothing,
1169
+ halted: halted,
1170
+ forbidden: forbidden,
1171
+ error: error
1172
+ };
1173
+ if (!stimulusReflex || !Object.keys(eventTypes).includes(eventType)) return;
1174
+ const reflex = reflexes[stimulusReflex.id];
1175
+ reflex.completedOperations++;
1176
+ reflex.pendingOperations--;
1177
+ reflex.selector = event.detail.selector;
1178
+ reflex.morph = event.detail.stimulusReflex.morph;
1179
+ reflex.operation = event.type.split(":")[1].split("-").slice(1).join("_");
1180
+ if (eventType === "error") reflex.error = event.detail.error;
1181
+ eventTypes[eventType](reflex, event);
1182
+ setTimeout((() => dispatchLifecycleEvent(reflex, eventType)));
777
1183
  if (reflex.piggybackOperations.length) CableReady.perform(reflex.piggybackOperations);
778
1184
  };
779
1185
 
780
- const nothing = (event, payload, promise, reflex, reflexElement) => {
781
- reflex.finalStage = "after";
782
- Log.success(event, false);
783
- setTimeout((() => promise.resolve({
784
- data: promise.data,
785
- element: reflexElement,
1186
+ const nothing = (reflex, event) => {
1187
+ Log.success(reflex);
1188
+ setTimeout((() => reflex.promise.resolve({
1189
+ data: reflex.data,
1190
+ element: reflex.element,
1191
+ event: event,
1192
+ payload: reflex.payload,
1193
+ id: reflex.id,
1194
+ toString: () => ""
1195
+ })));
1196
+ };
1197
+
1198
+ const halted = (reflex, event) => {
1199
+ Log.halted(reflex, event);
1200
+ setTimeout((() => reflex.promise.resolve({
1201
+ data: reflex.data,
1202
+ element: reflex.element,
786
1203
  event: event,
787
- payload: payload,
788
- reflexId: promise.data.reflexId,
1204
+ payload: reflex.payload,
1205
+ id: reflex.id,
789
1206
  toString: () => ""
790
1207
  })));
791
1208
  };
792
1209
 
793
- const halted = (event, payload, promise, reflex, reflexElement) => {
794
- reflex.finalStage = "halted";
795
- Log.success(event, true);
796
- setTimeout((() => promise.resolve({
797
- data: promise.data,
798
- element: reflexElement,
1210
+ const forbidden = (reflex, event) => {
1211
+ Log.forbidden(reflex, event);
1212
+ setTimeout((() => reflex.promise.resolve({
1213
+ data: reflex.data,
1214
+ element: reflex.element,
799
1215
  event: event,
800
- payload: payload,
801
- reflexId: promise.data.reflexId,
1216
+ payload: reflex.payload,
1217
+ id: reflex.id,
802
1218
  toString: () => ""
803
1219
  })));
804
1220
  };
805
1221
 
806
- const error = (event, payload, promise, reflex, reflexElement) => {
807
- reflex.finalStage = "after";
808
- Log.error(event);
809
- setTimeout((() => promise.reject({
810
- data: promise.data,
811
- element: reflexElement,
1222
+ const error = (reflex, event) => {
1223
+ Log.error(reflex, event);
1224
+ setTimeout((() => reflex.promise.reject({
1225
+ data: reflex.data,
1226
+ element: reflex.element,
812
1227
  event: event,
813
- payload: payload,
814
- reflexId: promise.data.reflexId,
815
- error: event.detail.body,
816
- toString: () => event.detail.body
1228
+ payload: reflex.payload,
1229
+ id: reflex.id,
1230
+ error: reflex.error,
1231
+ toString: () => reflex.error
817
1232
  })));
818
1233
  };
819
1234
 
1235
+ const localReflexControllers = element => {
1236
+ const potentialIdentifiers = attributeValues(element.getAttribute(Schema.controller));
1237
+ const potentialControllers = potentialIdentifiers.map((identifier => App.app.getControllerForElementAndIdentifier(element, identifier)));
1238
+ return potentialControllers.filter((controller => controller && controller.StimulusReflex));
1239
+ };
1240
+
1241
+ const allReflexControllers = element => {
1242
+ let controllers = [];
1243
+ while (element) {
1244
+ controllers = controllers.concat(localReflexControllers(element));
1245
+ element = element.parentElement;
1246
+ }
1247
+ return controllers;
1248
+ };
1249
+
1250
+ const findControllerByReflexName = (reflexName, controllers) => {
1251
+ const controller = controllers.find((controller => {
1252
+ if (!controller || !controller.identifier) return;
1253
+ const identifier = reflexNameToControllerIdentifier(extractReflexName(reflexName));
1254
+ return identifier === controller.identifier;
1255
+ }));
1256
+ return controller || controllers[0];
1257
+ };
1258
+
1259
+ const scanForReflexes = debounce((() => {
1260
+ const reflexElements = document.querySelectorAll(`[${Schema.reflex}]`);
1261
+ reflexElements.forEach((element => scanForReflexesOnElement(element)));
1262
+ }), 20);
1263
+
1264
+ const scanForReflexesOnElement = (element, controller = null) => {
1265
+ const controllerAttribute = element.getAttribute(Schema.controller);
1266
+ const controllers = attributeValues(controllerAttribute).filter((controller => controller !== "stimulus-reflex"));
1267
+ const reflexAttribute = element.getAttribute(Schema.reflex);
1268
+ const reflexAttributeNames = attributeValues(reflexAttribute);
1269
+ const actionAttribute = element.getAttribute(Schema.action);
1270
+ const actions = attributeValues(actionAttribute).filter((action => !action.includes("#__perform")));
1271
+ reflexAttributeNames.forEach((reflexName => {
1272
+ const potentialControllers = [ controller ].concat(allReflexControllers(element));
1273
+ controller = findControllerByReflexName(reflexName, potentialControllers);
1274
+ const controllerName = controller ? controller.identifier : "stimulus-reflex";
1275
+ actions.push(`${reflexName.split("->")[0]}->${controllerName}#__perform`);
1276
+ const parentControllerElement = element.closest(`[data-controller~=${controllerName}]`);
1277
+ if (!parentControllerElement) {
1278
+ controllers.push(controllerName);
1279
+ }
1280
+ }));
1281
+ const controllerValue = attributeValue(controllers);
1282
+ const actionValue = attributeValue(actions);
1283
+ let emitReadyEvent = false;
1284
+ if (controllerValue && element.getAttribute(Schema.controller) != controllerValue) {
1285
+ element.setAttribute(Schema.controller, controllerValue);
1286
+ emitReadyEvent = true;
1287
+ }
1288
+ if (actionValue && element.getAttribute(Schema.action) != actionValue) {
1289
+ element.setAttribute(Schema.action, actionValue);
1290
+ emitReadyEvent = true;
1291
+ }
1292
+ if (emitReadyEvent) {
1293
+ dispatch(element, "stimulus-reflex:ready", {
1294
+ reflex: reflexAttribute,
1295
+ controller: controllerValue,
1296
+ action: actionValue,
1297
+ element: element
1298
+ });
1299
+ }
1300
+ };
1301
+
820
1302
  class StimulusReflexController extends Controller {
821
1303
  constructor(...args) {
822
1304
  super(...args);
@@ -824,28 +1306,24 @@ class StimulusReflexController extends Controller {
824
1306
  }
825
1307
  }
826
1308
 
827
- const initialize = (application, {controller: controller, consumer: consumer, debug: debug, params: params, isolate: isolate, deprecate: deprecate} = {}) => {
828
- ActionCableTransport.set(consumer, params);
829
- document.addEventListener("DOMContentLoaded", (() => {
830
- document.body.classList.remove("stimulus-reflex-connected");
831
- document.body.classList.add("stimulus-reflex-disconnected");
832
- 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.");
833
- 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.");
834
- }), {
835
- once: true
836
- });
1309
+ const tabId = uuidv4();
1310
+
1311
+ const initialize = (application, {controller: controller, consumer: consumer, debug: debug, params: params, isolate: isolate, deprecate: deprecate, transport: transport} = {}) => {
1312
+ Transport.set(transport || ActionCableTransport);
1313
+ Transport.plugin.initialize(consumer, params);
837
1314
  IsolationMode.set(!!isolate);
838
- reflexes.app = application;
1315
+ App.set(application);
839
1316
  Schema.set(application);
840
- reflexes.app.register("stimulus-reflex", controller || StimulusReflexController);
1317
+ App.app.register("stimulus-reflex", controller || StimulusReflexController);
841
1318
  Debug$1.set(!!debug);
842
1319
  if (typeof deprecate !== "undefined") Deprecate.set(deprecate);
843
- const observer = new MutationObserver(setupDeclarativeReflexes);
1320
+ const observer = new MutationObserver(scanForReflexes);
844
1321
  observer.observe(document.documentElement, {
845
1322
  attributeFilter: [ Schema.reflex, Schema.action ],
846
1323
  childList: true,
847
1324
  subtree: true
848
1325
  });
1326
+ emitEvent("stimulus-reflex:initialized");
849
1327
  };
850
1328
 
851
1329
  const register = (controller, options = {}) => {
@@ -854,38 +1332,32 @@ const register = (controller, options = {}) => {
854
1332
  ...options,
855
1333
  channel: channel
856
1334
  };
857
- ActionCableTransport.createSubscription(controller);
1335
+ Transport.plugin.subscribe(controller);
858
1336
  Object.assign(controller, {
859
- isActionCableConnectionOpen() {
860
- return this.StimulusReflex.subscription.consumer.connection.isOpen();
861
- },
862
1337
  stimulate() {
863
1338
  const url = location.href;
1339
+ const controllerElement = this.element;
864
1340
  const args = Array.from(arguments);
865
1341
  const target = args.shift() || "StimulusReflex::Reflex#default_reflex";
866
- const controllerElement = this.element;
867
- const reflexElement = args[0] && args[0].nodeType === Node.ELEMENT_NODE ? args.shift() : controllerElement;
868
- if (reflexElement.type === "number" && reflexElement.validity && reflexElement.validity.badInput) {
1342
+ const reflexElement = getReflexElement(args, controllerElement);
1343
+ if (elementInvalid(reflexElement)) {
869
1344
  if (Debug$1.enabled) console.warn("Reflex aborted: invalid numeric input");
870
1345
  return;
871
1346
  }
872
- const options = {};
873
- if (args[0] && typeof args[0] === "object" && Object.keys(args[0]).filter((key => [ "attrs", "selectors", "reflexId", "resolveLate", "serializeForm", "suppressLogging", "includeInnerHTML", "includeTextContent" ].includes(key))).length) {
874
- const opts = args.shift();
875
- Object.keys(opts).forEach((o => options[o] = opts[o]));
876
- }
1347
+ const options = getReflexOptions(args);
877
1348
  const reflexData = new ReflexData(options, reflexElement, controllerElement, this.identifier, Schema.reflexPermanent, target, args, url, tabId);
878
- const reflexId = reflexData.reflexId;
879
- if (!this.isActionCableConnectionOpen()) throw "The ActionCable connection is not open! `this.isActionCableConnectionOpen()` must return true before calling `this.stimulate()`";
880
- if (!ActionCableTransport.subscriptionActive) throw "The ActionCable channel subscription for StimulusReflex was rejected.";
1349
+ const id = reflexData.id;
881
1350
  controllerElement.reflexController = controllerElement.reflexController || {};
882
1351
  controllerElement.reflexData = controllerElement.reflexData || {};
883
1352
  controllerElement.reflexError = controllerElement.reflexError || {};
884
- controllerElement.reflexController[reflexId] = this;
885
- controllerElement.reflexData[reflexId] = reflexData.valueOf();
886
- dispatchLifecycleEvent("before", reflexElement, controllerElement, reflexId);
1353
+ controllerElement.reflexController[id] = this;
1354
+ controllerElement.reflexData[id] = reflexData.valueOf();
1355
+ const reflex = new Reflex(reflexData, this);
1356
+ reflexes[id] = reflex;
1357
+ this.lastReflex = reflex;
1358
+ dispatchLifecycleEvent(reflex, "before");
887
1359
  setTimeout((() => {
888
- const {params: params} = controllerElement.reflexData[reflexId] || {};
1360
+ const {params: params} = controllerElement.reflexData[id] || {};
889
1361
  const check = reflexElement.attributes[Schema.reflexSerializeForm];
890
1362
  if (check) {
891
1363
  options["serializeForm"] = check.value !== "false";
@@ -895,16 +1367,16 @@ const register = (controller, options = {}) => {
895
1367
  const formData = options["serializeForm"] === false ? "" : serializeForm(form, {
896
1368
  element: reflexElement
897
1369
  });
898
- controllerElement.reflexData[reflexId] = {
1370
+ reflex.data = {
899
1371
  ...reflexData.valueOf(),
900
1372
  params: params,
901
1373
  formData: formData
902
1374
  };
903
- this.StimulusReflex.subscription.send(controllerElement.reflexData[reflexId]);
1375
+ controllerElement.reflexData[id] = reflex.data;
1376
+ Transport.plugin.deliver(reflex);
904
1377
  }));
905
- const promise = registerReflex(reflexData.valueOf());
906
- Log.request(reflexId, target, args, this.context.scope.identifier, reflexElement, controllerElement);
907
- return promise;
1378
+ Log.request(reflex);
1379
+ return reflex.getPromise;
908
1380
  },
909
1381
  __perform(event) {
910
1382
  let element = event.target;
@@ -921,10 +1393,24 @@ const register = (controller, options = {}) => {
921
1393
  }
922
1394
  }
923
1395
  });
1396
+ if (!controller.reflexes) Object.defineProperty(controller, "reflexes", {
1397
+ get() {
1398
+ return new Proxy(reflexes, {
1399
+ get: function(target, prop) {
1400
+ if (prop === "last") return this.lastReflex;
1401
+ return Object.fromEntries(Object.entries(target[prop]).filter((([_, reflex]) => reflex.controller === this)));
1402
+ }.bind(this)
1403
+ });
1404
+ }
1405
+ });
1406
+ scanForReflexesOnElement(controller.element, controller);
1407
+ emitEvent("stimulus-reflex:controller-registered", {
1408
+ detail: {
1409
+ controller: controller
1410
+ }
1411
+ });
924
1412
  };
925
1413
 
926
- const tabId = uuidv4();
927
-
928
1414
  const useReflex = (controller, options = {}) => {
929
1415
  register(controller, options);
930
1416
  };
@@ -939,16 +1425,24 @@ document.addEventListener("cable-ready:after-inner-html", afterDOMUpdate);
939
1425
 
940
1426
  document.addEventListener("cable-ready:after-morph", afterDOMUpdate);
941
1427
 
942
- window.addEventListener("load", setupDeclarativeReflexes);
1428
+ document.addEventListener("readystatechange", (() => {
1429
+ if (document.readyState === "complete") {
1430
+ scanForReflexes();
1431
+ }
1432
+ }));
943
1433
 
944
1434
  var StimulusReflex = Object.freeze({
945
1435
  __proto__: null,
946
1436
  initialize: initialize,
1437
+ reflexes: reflexes,
947
1438
  register: register,
1439
+ scanForReflexes: scanForReflexes,
1440
+ scanForReflexesOnElement: scanForReflexesOnElement,
948
1441
  useReflex: useReflex
949
1442
  });
950
1443
 
951
1444
  const global = {
1445
+ version: packageInfo.version,
952
1446
  ...StimulusReflex,
953
1447
  get debug() {
954
1448
  return Debug$1.value;
@@ -966,4 +1460,4 @@ const global = {
966
1460
 
967
1461
  window.StimulusReflex = global;
968
1462
 
969
- export { global as default, initialize, register, useReflex };
1463
+ export { global as default, initialize, reflexes, register, scanForReflexes, scanForReflexesOnElement, useReflex };