stimulus-rails 1.0.4 → 1.1.1

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.
@@ -3,8 +3,8 @@
3
3
  //= link ./stimulus-loading.js
4
4
 
5
5
  /*
6
- Stimulus 3.0.1
7
- Copyright © 2021 Basecamp, LLC
6
+ Stimulus 3.1.1
7
+ Copyright © 2022 Basecamp, LLC
8
8
  */
9
9
  class EventListener {
10
10
  constructor(eventTarget, eventName, eventOptions) {
@@ -36,6 +36,9 @@ class EventListener {
36
36
  }
37
37
  }
38
38
  }
39
+ hasBindings() {
40
+ return this.unorderedBindings.size > 0;
41
+ }
39
42
  get bindings() {
40
43
  return Array.from(this.unorderedBindings).sort((left, right) => {
41
44
  const leftIndex = left.index, rightIndex = right.index;
@@ -54,7 +57,7 @@ function extendEvent(event) {
54
57
  stopImmediatePropagation() {
55
58
  this.immediatePropagationStopped = true;
56
59
  stopImmediatePropagation.call(this);
57
- }
60
+ },
58
61
  });
59
62
  }
60
63
  }
@@ -62,34 +65,50 @@ function extendEvent(event) {
62
65
  class Dispatcher {
63
66
  constructor(application) {
64
67
  this.application = application;
65
- this.eventListenerMaps = new Map;
68
+ this.eventListenerMaps = new Map();
66
69
  this.started = false;
67
70
  }
68
71
  start() {
69
72
  if (!this.started) {
70
73
  this.started = true;
71
- this.eventListeners.forEach(eventListener => eventListener.connect());
74
+ this.eventListeners.forEach((eventListener) => eventListener.connect());
72
75
  }
73
76
  }
74
77
  stop() {
75
78
  if (this.started) {
76
79
  this.started = false;
77
- this.eventListeners.forEach(eventListener => eventListener.disconnect());
80
+ this.eventListeners.forEach((eventListener) => eventListener.disconnect());
78
81
  }
79
82
  }
80
83
  get eventListeners() {
81
- return Array.from(this.eventListenerMaps.values())
82
- .reduce((listeners, map) => listeners.concat(Array.from(map.values())), []);
84
+ return Array.from(this.eventListenerMaps.values()).reduce((listeners, map) => listeners.concat(Array.from(map.values())), []);
83
85
  }
84
86
  bindingConnected(binding) {
85
87
  this.fetchEventListenerForBinding(binding).bindingConnected(binding);
86
88
  }
87
- bindingDisconnected(binding) {
89
+ bindingDisconnected(binding, clearEventListeners = false) {
88
90
  this.fetchEventListenerForBinding(binding).bindingDisconnected(binding);
91
+ if (clearEventListeners)
92
+ this.clearEventListenersForBinding(binding);
89
93
  }
90
94
  handleError(error, message, detail = {}) {
91
95
  this.application.handleError(error, `Error ${message}`, detail);
92
96
  }
97
+ clearEventListenersForBinding(binding) {
98
+ const eventListener = this.fetchEventListenerForBinding(binding);
99
+ if (!eventListener.hasBindings()) {
100
+ eventListener.disconnect();
101
+ this.removeMappedEventListenerFor(binding);
102
+ }
103
+ }
104
+ removeMappedEventListenerFor(binding) {
105
+ const { eventTarget, eventName, eventOptions } = binding;
106
+ const eventListenerMap = this.fetchEventListenerMapForEventTarget(eventTarget);
107
+ const cacheKey = this.cacheKey(eventName, eventOptions);
108
+ eventListenerMap.delete(cacheKey);
109
+ if (eventListenerMap.size == 0)
110
+ this.eventListenerMaps.delete(eventTarget);
111
+ }
93
112
  fetchEventListenerForBinding(binding) {
94
113
  const { eventTarget, eventName, eventOptions } = binding;
95
114
  return this.fetchEventListener(eventTarget, eventName, eventOptions);
@@ -114,20 +133,42 @@ class Dispatcher {
114
133
  fetchEventListenerMapForEventTarget(eventTarget) {
115
134
  let eventListenerMap = this.eventListenerMaps.get(eventTarget);
116
135
  if (!eventListenerMap) {
117
- eventListenerMap = new Map;
136
+ eventListenerMap = new Map();
118
137
  this.eventListenerMaps.set(eventTarget, eventListenerMap);
119
138
  }
120
139
  return eventListenerMap;
121
140
  }
122
141
  cacheKey(eventName, eventOptions) {
123
142
  const parts = [eventName];
124
- Object.keys(eventOptions).sort().forEach(key => {
143
+ Object.keys(eventOptions)
144
+ .sort()
145
+ .forEach((key) => {
125
146
  parts.push(`${eventOptions[key] ? "" : "!"}${key}`);
126
147
  });
127
148
  return parts.join(":");
128
149
  }
129
150
  }
130
151
 
152
+ const defaultActionDescriptorFilters = {
153
+ stop({ event, value }) {
154
+ if (value)
155
+ event.stopPropagation();
156
+ return true;
157
+ },
158
+ prevent({ event, value }) {
159
+ if (value)
160
+ event.preventDefault();
161
+ return true;
162
+ },
163
+ self({ event, value, element }) {
164
+ if (value) {
165
+ return element === event.target;
166
+ }
167
+ else {
168
+ return true;
169
+ }
170
+ },
171
+ };
131
172
  const descriptorPattern = /^((.+?)(@(window|document))?->)?(.+?)(#([^:]+?))(:(.+))?$/;
132
173
  function parseActionDescriptorString(descriptorString) {
133
174
  const source = descriptorString.trim();
@@ -137,7 +178,7 @@ function parseActionDescriptorString(descriptorString) {
137
178
  eventName: matches[2],
138
179
  eventOptions: matches[9] ? parseEventOptions(matches[9]) : {},
139
180
  identifier: matches[5],
140
- methodName: matches[7]
181
+ methodName: matches[7],
141
182
  };
142
183
  }
143
184
  function parseEventTarget(eventTargetName) {
@@ -149,7 +190,9 @@ function parseEventTarget(eventTargetName) {
149
190
  }
150
191
  }
151
192
  function parseEventOptions(eventOptions) {
152
- return eventOptions.split(":").reduce((options, token) => Object.assign(options, { [token.replace(/^!/, "")]: !/^!/.test(token) }), {});
193
+ return eventOptions
194
+ .split(":")
195
+ .reduce((options, token) => Object.assign(options, { [token.replace(/^!/, "")]: !/^!/.test(token) }), {});
153
196
  }
154
197
  function stringifyEventTarget(eventTarget) {
155
198
  if (eventTarget == window) {
@@ -191,24 +234,15 @@ class Action {
191
234
  return `${this.eventName}${eventNameSuffix}->${this.identifier}#${this.methodName}`;
192
235
  }
193
236
  get params() {
194
- if (this.eventTarget instanceof Element) {
195
- return this.getParamsFromEventTargetAttributes(this.eventTarget);
196
- }
197
- else {
198
- return {};
199
- }
200
- }
201
- getParamsFromEventTargetAttributes(eventTarget) {
202
237
  const params = {};
203
- const pattern = new RegExp(`^data-${this.identifier}-(.+)-param$`);
204
- const attributes = Array.from(eventTarget.attributes);
205
- attributes.forEach(({ name, value }) => {
238
+ const pattern = new RegExp(`^data-${this.identifier}-(.+)-param$`, "i");
239
+ for (const { name, value } of Array.from(this.element.attributes)) {
206
240
  const match = name.match(pattern);
207
241
  const key = match && match[1];
208
242
  if (key) {
209
- Object.assign(params, { [camelize(key)]: typecast(value) });
243
+ params[camelize(key)] = typecast(value);
210
244
  }
211
- });
245
+ }
212
246
  return params;
213
247
  }
214
248
  get eventTargetName() {
@@ -216,13 +250,13 @@ class Action {
216
250
  }
217
251
  }
218
252
  const defaultEventNames = {
219
- "a": e => "click",
220
- "button": e => "click",
221
- "form": e => "submit",
222
- "details": e => "toggle",
223
- "input": e => e.getAttribute("type") == "submit" ? "click" : "input",
224
- "select": e => "change",
225
- "textarea": e => "input"
253
+ a: () => "click",
254
+ button: () => "click",
255
+ form: () => "submit",
256
+ details: () => "toggle",
257
+ input: (e) => (e.getAttribute("type") == "submit" ? "click" : "input"),
258
+ select: () => "change",
259
+ textarea: () => "input",
226
260
  };
227
261
  function getDefaultEventNameForElement(element) {
228
262
  const tagName = element.tagName.toLowerCase();
@@ -260,7 +294,7 @@ class Binding {
260
294
  return this.context.identifier;
261
295
  }
262
296
  handleEvent(event) {
263
- if (this.willBeInvokedByEvent(event)) {
297
+ if (this.willBeInvokedByEvent(event) && this.applyEventModifiers(event)) {
264
298
  this.invokeWithEvent(event);
265
299
  }
266
300
  }
@@ -274,6 +308,21 @@ class Binding {
274
308
  }
275
309
  throw new Error(`Action "${this.action}" references undefined method "${this.methodName}"`);
276
310
  }
311
+ applyEventModifiers(event) {
312
+ const { element } = this.action;
313
+ const { actionDescriptorFilters } = this.context.application;
314
+ let passes = true;
315
+ for (const [name, value] of Object.entries(this.eventOptions)) {
316
+ if (name in actionDescriptorFilters) {
317
+ const filter = actionDescriptorFilters[name];
318
+ passes = passes && filter({ name, value, event, element });
319
+ }
320
+ else {
321
+ continue;
322
+ }
323
+ }
324
+ return passes;
325
+ }
277
326
  invokeWithEvent(event) {
278
327
  const { target, currentTarget } = event;
279
328
  try {
@@ -320,7 +369,7 @@ class ElementObserver {
320
369
  this.element = element;
321
370
  this.started = false;
322
371
  this.delegate = delegate;
323
- this.elements = new Set;
372
+ this.elements = new Set();
324
373
  this.mutationObserver = new MutationObserver((mutations) => this.processMutations(mutations));
325
374
  }
326
375
  start() {
@@ -508,8 +557,8 @@ class StringMapObserver {
508
557
  this.element = element;
509
558
  this.delegate = delegate;
510
559
  this.started = false;
511
- this.stringMap = new Map;
512
- this.mutationObserver = new MutationObserver(mutations => this.processMutations(mutations));
560
+ this.stringMap = new Map();
561
+ this.mutationObserver = new MutationObserver((mutations) => this.processMutations(mutations));
513
562
  }
514
563
  start() {
515
564
  if (!this.started) {
@@ -585,7 +634,7 @@ class StringMapObserver {
585
634
  return Array.from(new Set(this.currentAttributeNames.concat(this.recordedAttributeNames)));
586
635
  }
587
636
  get currentAttributeNames() {
588
- return Array.from(this.element.attributes).map(attribute => attribute.name);
637
+ return Array.from(this.element.attributes).map((attribute) => attribute.name);
589
638
  }
590
639
  get recordedAttributeNames() {
591
640
  return Array.from(this.stringMap.keys());
@@ -644,7 +693,7 @@ class Multimap {
644
693
  }
645
694
  hasValue(value) {
646
695
  const sets = Array.from(this.valuesByKey.values());
647
- return sets.some(set => set.has(value));
696
+ return sets.some((set) => set.has(value));
648
697
  }
649
698
  getValuesForKey(key) {
650
699
  const values = this.valuesByKey.get(key);
@@ -652,15 +701,15 @@ class Multimap {
652
701
  }
653
702
  getKeysForValue(value) {
654
703
  return Array.from(this.valuesByKey)
655
- .filter(([key, values]) => values.has(value))
656
- .map(([key, values]) => key);
704
+ .filter(([_key, values]) => values.has(value))
705
+ .map(([key, _values]) => key);
657
706
  }
658
707
  }
659
708
 
660
709
  class IndexedMultimap extends Multimap {
661
710
  constructor() {
662
711
  super();
663
- this.keysByValue = new Map;
712
+ this.keysByValue = new Map();
664
713
  }
665
714
  get values() {
666
715
  return Array.from(this.keysByValue.keys());
@@ -686,7 +735,7 @@ class TokenListObserver {
686
735
  constructor(element, attributeName, delegate) {
687
736
  this.attributeObserver = new AttributeObserver(element, attributeName, this);
688
737
  this.delegate = delegate;
689
- this.tokensByElement = new Multimap;
738
+ this.tokensByElement = new Multimap();
690
739
  }
691
740
  get started() {
692
741
  return this.attributeObserver.started;
@@ -721,10 +770,10 @@ class TokenListObserver {
721
770
  this.tokensUnmatched(this.tokensByElement.getValuesForKey(element));
722
771
  }
723
772
  tokensMatched(tokens) {
724
- tokens.forEach(token => this.tokenMatched(token));
773
+ tokens.forEach((token) => this.tokenMatched(token));
725
774
  }
726
775
  tokensUnmatched(tokens) {
727
- tokens.forEach(token => this.tokenUnmatched(token));
776
+ tokens.forEach((token) => this.tokenUnmatched(token));
728
777
  }
729
778
  tokenMatched(token) {
730
779
  this.delegate.tokenMatched(token);
@@ -737,8 +786,7 @@ class TokenListObserver {
737
786
  refreshTokensForElement(element) {
738
787
  const previousTokens = this.tokensByElement.getValuesForKey(element);
739
788
  const currentTokens = this.readTokensForElement(element);
740
- const firstDifferingIndex = zip(previousTokens, currentTokens)
741
- .findIndex(([previousToken, currentToken]) => !tokensAreEqual(previousToken, currentToken));
789
+ const firstDifferingIndex = zip(previousTokens, currentTokens).findIndex(([previousToken, currentToken]) => !tokensAreEqual(previousToken, currentToken));
742
790
  if (firstDifferingIndex == -1) {
743
791
  return [[], []];
744
792
  }
@@ -753,7 +801,10 @@ class TokenListObserver {
753
801
  }
754
802
  }
755
803
  function parseTokenString(tokenString, element, attributeName) {
756
- return tokenString.trim().split(/\s+/).filter(content => content.length)
804
+ return tokenString
805
+ .trim()
806
+ .split(/\s+/)
807
+ .filter((content) => content.length)
757
808
  .map((content, index) => ({ element, attributeName, content, index }));
758
809
  }
759
810
  function zip(left, right) {
@@ -768,8 +819,8 @@ class ValueListObserver {
768
819
  constructor(element, attributeName, delegate) {
769
820
  this.tokenListObserver = new TokenListObserver(element, attributeName, this);
770
821
  this.delegate = delegate;
771
- this.parseResultsByToken = new WeakMap;
772
- this.valuesByTokenByElement = new WeakMap;
822
+ this.parseResultsByToken = new WeakMap();
823
+ this.valuesByTokenByElement = new WeakMap();
773
824
  }
774
825
  get started() {
775
826
  return this.tokenListObserver.started;
@@ -816,7 +867,7 @@ class ValueListObserver {
816
867
  fetchValuesByTokenForElement(element) {
817
868
  let valuesByToken = this.valuesByTokenByElement.get(element);
818
869
  if (!valuesByToken) {
819
- valuesByToken = new Map;
870
+ valuesByToken = new Map();
820
871
  this.valuesByTokenByElement.set(element, valuesByToken);
821
872
  }
822
873
  return valuesByToken;
@@ -836,7 +887,7 @@ class BindingObserver {
836
887
  constructor(context, delegate) {
837
888
  this.context = context;
838
889
  this.delegate = delegate;
839
- this.bindingsByAction = new Map;
890
+ this.bindingsByAction = new Map();
840
891
  }
841
892
  start() {
842
893
  if (!this.valueListObserver) {
@@ -879,7 +930,7 @@ class BindingObserver {
879
930
  }
880
931
  }
881
932
  disconnectAllActions() {
882
- this.bindings.forEach(binding => this.delegate.bindingDisconnected(binding));
933
+ this.bindings.forEach((binding) => this.delegate.bindingDisconnected(binding, true));
883
934
  this.bindingsByAction.clear();
884
935
  }
885
936
  parseValueForToken(token) {
@@ -902,10 +953,10 @@ class ValueObserver {
902
953
  this.receiver = receiver;
903
954
  this.stringMapObserver = new StringMapObserver(this.element, this);
904
955
  this.valueDescriptorMap = this.controller.valueDescriptorMap;
905
- this.invokeChangedCallbacksForDefaultValues();
906
956
  }
907
957
  start() {
908
958
  this.stringMapObserver.start();
959
+ this.invokeChangedCallbacksForDefaultValues();
909
960
  }
910
961
  stop() {
911
962
  this.stringMapObserver.stop();
@@ -957,21 +1008,29 @@ class ValueObserver {
957
1008
  const changedMethod = this.receiver[changedMethodName];
958
1009
  if (typeof changedMethod == "function") {
959
1010
  const descriptor = this.valueDescriptorNameMap[name];
960
- const value = descriptor.reader(rawValue);
961
- let oldValue = rawOldValue;
962
- if (rawOldValue) {
963
- oldValue = descriptor.reader(rawOldValue);
1011
+ try {
1012
+ const value = descriptor.reader(rawValue);
1013
+ let oldValue = rawOldValue;
1014
+ if (rawOldValue) {
1015
+ oldValue = descriptor.reader(rawOldValue);
1016
+ }
1017
+ changedMethod.call(this.receiver, value, oldValue);
1018
+ }
1019
+ catch (error) {
1020
+ if (error instanceof TypeError) {
1021
+ error.message = `Stimulus Value "${this.context.identifier}.${descriptor.name}" - ${error.message}`;
1022
+ }
1023
+ throw error;
964
1024
  }
965
- changedMethod.call(this.receiver, value, oldValue);
966
1025
  }
967
1026
  }
968
1027
  get valueDescriptors() {
969
1028
  const { valueDescriptorMap } = this;
970
- return Object.keys(valueDescriptorMap).map(key => valueDescriptorMap[key]);
1029
+ return Object.keys(valueDescriptorMap).map((key) => valueDescriptorMap[key]);
971
1030
  }
972
1031
  get valueDescriptorNameMap() {
973
1032
  const descriptors = {};
974
- Object.keys(this.valueDescriptorMap).forEach(key => {
1033
+ Object.keys(this.valueDescriptorMap).forEach((key) => {
975
1034
  const descriptor = this.valueDescriptorMap[key];
976
1035
  descriptors[descriptor.name] = descriptor;
977
1036
  });
@@ -988,7 +1047,7 @@ class TargetObserver {
988
1047
  constructor(context, delegate) {
989
1048
  this.context = context;
990
1049
  this.delegate = delegate;
991
- this.targetsByName = new Multimap;
1050
+ this.targetsByName = new Multimap();
992
1051
  }
993
1052
  start() {
994
1053
  if (!this.tokenListObserver) {
@@ -1128,9 +1187,9 @@ class Context {
1128
1187
  function readInheritableStaticArrayValues(constructor, propertyName) {
1129
1188
  const ancestors = getAncestorsForConstructor(constructor);
1130
1189
  return Array.from(ancestors.reduce((values, constructor) => {
1131
- getOwnStaticArrayValues(constructor, propertyName).forEach(name => values.add(name));
1190
+ getOwnStaticArrayValues(constructor, propertyName).forEach((name) => values.add(name));
1132
1191
  return values;
1133
- }, new Set));
1192
+ }, new Set()));
1134
1193
  }
1135
1194
  function readInheritableStaticObjectPairs(constructor, propertyName) {
1136
1195
  const ancestors = getAncestorsForConstructor(constructor);
@@ -1153,7 +1212,7 @@ function getOwnStaticArrayValues(constructor, propertyName) {
1153
1212
  }
1154
1213
  function getOwnStaticObjectPairs(constructor, propertyName) {
1155
1214
  const definition = constructor[propertyName];
1156
- return definition ? Object.keys(definition).map(key => [key, definition[key]]) : [];
1215
+ return definition ? Object.keys(definition).map((key) => [key, definition[key]]) : [];
1157
1216
  }
1158
1217
 
1159
1218
  function bless(constructor) {
@@ -1199,10 +1258,7 @@ function getShadowedDescriptor(prototype, properties, key) {
1199
1258
  }
1200
1259
  const getOwnKeys = (() => {
1201
1260
  if (typeof Object.getOwnPropertySymbols == "function") {
1202
- return (object) => [
1203
- ...Object.getOwnPropertyNames(object),
1204
- ...Object.getOwnPropertySymbols(object)
1205
- ];
1261
+ return (object) => [...Object.getOwnPropertyNames(object), ...Object.getOwnPropertySymbols(object)];
1206
1262
  }
1207
1263
  else {
1208
1264
  return Object.getOwnPropertyNames;
@@ -1214,16 +1270,18 @@ const extend = (() => {
1214
1270
  return Reflect.construct(constructor, arguments, new.target);
1215
1271
  }
1216
1272
  extended.prototype = Object.create(constructor.prototype, {
1217
- constructor: { value: extended }
1273
+ constructor: { value: extended },
1218
1274
  });
1219
1275
  Reflect.setPrototypeOf(extended, constructor);
1220
1276
  return extended;
1221
1277
  }
1222
1278
  function testReflectExtension() {
1223
- const a = function () { this.a.call(this); };
1279
+ const a = function () {
1280
+ this.a.call(this);
1281
+ };
1224
1282
  const b = extendWithReflect(a);
1225
1283
  b.prototype.a = function () { };
1226
- return new b;
1284
+ return new b();
1227
1285
  }
1228
1286
  try {
1229
1287
  testReflectExtension();
@@ -1238,7 +1296,7 @@ const extend = (() => {
1238
1296
  function blessDefinition(definition) {
1239
1297
  return {
1240
1298
  identifier: definition.identifier,
1241
- controllerConstructor: bless(definition.controllerConstructor)
1299
+ controllerConstructor: bless(definition.controllerConstructor),
1242
1300
  };
1243
1301
  }
1244
1302
 
@@ -1246,8 +1304,8 @@ class Module {
1246
1304
  constructor(application, definition) {
1247
1305
  this.application = application;
1248
1306
  this.definition = blessDefinition(definition);
1249
- this.contextsByScope = new WeakMap;
1250
- this.connectedContexts = new Set;
1307
+ this.contextsByScope = new WeakMap();
1308
+ this.connectedContexts = new Set();
1251
1309
  }
1252
1310
  get identifier() {
1253
1311
  return this.definition.identifier;
@@ -1345,13 +1403,13 @@ class DataMap {
1345
1403
 
1346
1404
  class Guide {
1347
1405
  constructor(logger) {
1348
- this.warnedKeysByObject = new WeakMap;
1406
+ this.warnedKeysByObject = new WeakMap();
1349
1407
  this.logger = logger;
1350
1408
  }
1351
1409
  warn(object, key, message) {
1352
1410
  let warnedKeys = this.warnedKeysByObject.get(object);
1353
1411
  if (!warnedKeys) {
1354
- warnedKeys = new Set;
1412
+ warnedKeys = new Set();
1355
1413
  this.warnedKeysByObject.set(object, warnedKeys);
1356
1414
  }
1357
1415
  if (!warnedKeys.has(key)) {
@@ -1382,15 +1440,13 @@ class TargetSet {
1382
1440
  return this.find(targetName) != null;
1383
1441
  }
1384
1442
  find(...targetNames) {
1385
- return targetNames.reduce((target, targetName) => target
1386
- || this.findTarget(targetName)
1387
- || this.findLegacyTarget(targetName), undefined);
1443
+ return targetNames.reduce((target, targetName) => target || this.findTarget(targetName) || this.findLegacyTarget(targetName), undefined);
1388
1444
  }
1389
1445
  findAll(...targetNames) {
1390
1446
  return targetNames.reduce((targets, targetName) => [
1391
1447
  ...targets,
1392
1448
  ...this.findAllTargets(targetName),
1393
- ...this.findAllLegacyTargets(targetName)
1449
+ ...this.findAllLegacyTargets(targetName),
1394
1450
  ], []);
1395
1451
  }
1396
1452
  findTarget(targetName) {
@@ -1411,7 +1467,7 @@ class TargetSet {
1411
1467
  }
1412
1468
  findAllLegacyTargets(targetName) {
1413
1469
  const selector = this.getLegacySelectorForTargetName(targetName);
1414
- return this.scope.findAllElements(selector).map(element => this.deprecate(element, targetName));
1470
+ return this.scope.findAllElements(selector).map((element) => this.deprecate(element, targetName));
1415
1471
  }
1416
1472
  getLegacySelectorForTargetName(targetName) {
1417
1473
  const targetDescriptor = `${this.identifier}.${targetName}`;
@@ -1446,14 +1502,12 @@ class Scope {
1446
1502
  this.guide = new Guide(logger);
1447
1503
  }
1448
1504
  findElement(selector) {
1449
- return this.element.matches(selector)
1450
- ? this.element
1451
- : this.queryElements(selector).find(this.containsElement);
1505
+ return this.element.matches(selector) ? this.element : this.queryElements(selector).find(this.containsElement);
1452
1506
  }
1453
1507
  findAllElements(selector) {
1454
1508
  return [
1455
- ...this.element.matches(selector) ? [this.element] : [],
1456
- ...this.queryElements(selector).filter(this.containsElement)
1509
+ ...(this.element.matches(selector) ? [this.element] : []),
1510
+ ...this.queryElements(selector).filter(this.containsElement),
1457
1511
  ];
1458
1512
  }
1459
1513
  queryElements(selector) {
@@ -1470,8 +1524,8 @@ class ScopeObserver {
1470
1524
  this.schema = schema;
1471
1525
  this.delegate = delegate;
1472
1526
  this.valueListObserver = new ValueListObserver(this.element, this.controllerAttribute, this);
1473
- this.scopesByIdentifierByElement = new WeakMap;
1474
- this.scopeReferenceCounts = new WeakMap;
1527
+ this.scopesByIdentifierByElement = new WeakMap();
1528
+ this.scopeReferenceCounts = new WeakMap();
1475
1529
  }
1476
1530
  start() {
1477
1531
  this.valueListObserver.start();
@@ -1511,7 +1565,7 @@ class ScopeObserver {
1511
1565
  fetchScopesByIdentifierForElement(element) {
1512
1566
  let scopesByIdentifier = this.scopesByIdentifierByElement.get(element);
1513
1567
  if (!scopesByIdentifier) {
1514
- scopesByIdentifier = new Map;
1568
+ scopesByIdentifier = new Map();
1515
1569
  this.scopesByIdentifierByElement.set(element, scopesByIdentifier);
1516
1570
  }
1517
1571
  return scopesByIdentifier;
@@ -1522,8 +1576,8 @@ class Router {
1522
1576
  constructor(application) {
1523
1577
  this.application = application;
1524
1578
  this.scopeObserver = new ScopeObserver(this.element, this.schema, this);
1525
- this.scopesByIdentifier = new Multimap;
1526
- this.modulesByIdentifier = new Map;
1579
+ this.scopesByIdentifier = new Multimap();
1580
+ this.modulesByIdentifier = new Map();
1527
1581
  }
1528
1582
  get element() {
1529
1583
  return this.application.element;
@@ -1563,7 +1617,7 @@ class Router {
1563
1617
  getContextForElementAndIdentifier(element, identifier) {
1564
1618
  const module = this.modulesByIdentifier.get(identifier);
1565
1619
  if (module) {
1566
- return module.contexts.find(context => context.element == element);
1620
+ return module.contexts.find((context) => context.element == element);
1567
1621
  }
1568
1622
  }
1569
1623
  handleError(error, message, detail) {
@@ -1589,12 +1643,12 @@ class Router {
1589
1643
  connectModule(module) {
1590
1644
  this.modulesByIdentifier.set(module.identifier, module);
1591
1645
  const scopes = this.scopesByIdentifier.getValuesForKey(module.identifier);
1592
- scopes.forEach(scope => module.connectContextForScope(scope));
1646
+ scopes.forEach((scope) => module.connectContextForScope(scope));
1593
1647
  }
1594
1648
  disconnectModule(module) {
1595
1649
  this.modulesByIdentifier.delete(module.identifier);
1596
1650
  const scopes = this.scopesByIdentifier.getValuesForKey(module.identifier);
1597
- scopes.forEach(scope => module.disconnectContextForScope(scope));
1651
+ scopes.forEach((scope) => module.disconnectContextForScope(scope));
1598
1652
  }
1599
1653
  }
1600
1654
 
@@ -1602,7 +1656,7 @@ const defaultSchema = {
1602
1656
  controllerAttribute: "data-controller",
1603
1657
  actionAttribute: "data-action",
1604
1658
  targetAttribute: "data-target",
1605
- targetAttributeForScope: identifier => `data-${identifier}-target`
1659
+ targetAttributeForScope: (identifier) => `data-${identifier}-target`,
1606
1660
  };
1607
1661
 
1608
1662
  class Application {
@@ -1618,6 +1672,7 @@ class Application {
1618
1672
  this.schema = schema;
1619
1673
  this.dispatcher = new Dispatcher(this);
1620
1674
  this.router = new Router(this);
1675
+ this.actionDescriptorFilters = Object.assign({}, defaultActionDescriptorFilters);
1621
1676
  }
1622
1677
  static start(element, schema) {
1623
1678
  const application = new Application(element, schema);
@@ -1638,20 +1693,25 @@ class Application {
1638
1693
  this.logDebugActivity("application", "stop");
1639
1694
  }
1640
1695
  register(identifier, controllerConstructor) {
1641
- if (controllerConstructor.shouldLoad) {
1642
- this.load({ identifier, controllerConstructor });
1643
- }
1696
+ this.load({ identifier, controllerConstructor });
1697
+ }
1698
+ registerActionOption(name, filter) {
1699
+ this.actionDescriptorFilters[name] = filter;
1644
1700
  }
1645
1701
  load(head, ...rest) {
1646
1702
  const definitions = Array.isArray(head) ? head : [head, ...rest];
1647
- definitions.forEach(definition => this.router.loadDefinition(definition));
1703
+ definitions.forEach((definition) => {
1704
+ if (definition.controllerConstructor.shouldLoad) {
1705
+ this.router.loadDefinition(definition);
1706
+ }
1707
+ });
1648
1708
  }
1649
1709
  unload(head, ...rest) {
1650
1710
  const identifiers = Array.isArray(head) ? head : [head, ...rest];
1651
- identifiers.forEach(identifier => this.router.unloadIdentifier(identifier));
1711
+ identifiers.forEach((identifier) => this.router.unloadIdentifier(identifier));
1652
1712
  }
1653
1713
  get controllers() {
1654
- return this.router.contexts.map(context => context.controller);
1714
+ return this.router.contexts.map((context) => context.controller);
1655
1715
  }
1656
1716
  getControllerForElementAndIdentifier(element, identifier) {
1657
1717
  const context = this.router.getContextForElementAndIdentifier(element, identifier);
@@ -1670,7 +1730,7 @@ class Application {
1670
1730
  }
1671
1731
  }
1672
1732
  function domReady() {
1673
- return new Promise(resolve => {
1733
+ return new Promise((resolve) => {
1674
1734
  if (document.readyState == "loading") {
1675
1735
  document.addEventListener("DOMContentLoaded", () => resolve());
1676
1736
  }
@@ -1698,18 +1758,18 @@ function propertiesForClassDefinition(key) {
1698
1758
  const attribute = classes.getAttributeName(key);
1699
1759
  throw new Error(`Missing attribute "${attribute}"`);
1700
1760
  }
1701
- }
1761
+ },
1702
1762
  },
1703
1763
  [`${key}Classes`]: {
1704
1764
  get() {
1705
1765
  return this.classes.getAll(key);
1706
- }
1766
+ },
1707
1767
  },
1708
1768
  [`has${capitalize(key)}Class`]: {
1709
1769
  get() {
1710
1770
  return this.classes.has(key);
1711
- }
1712
- }
1771
+ },
1772
+ },
1713
1773
  };
1714
1774
  }
1715
1775
 
@@ -1730,18 +1790,18 @@ function propertiesForTargetDefinition(name) {
1730
1790
  else {
1731
1791
  throw new Error(`Missing target element "${name}" for "${this.identifier}" controller`);
1732
1792
  }
1733
- }
1793
+ },
1734
1794
  },
1735
1795
  [`${name}Targets`]: {
1736
1796
  get() {
1737
1797
  return this.targets.findAll(name);
1738
- }
1798
+ },
1739
1799
  },
1740
1800
  [`has${capitalize(name)}Target`]: {
1741
1801
  get() {
1742
1802
  return this.targets.has(name);
1743
- }
1744
- }
1803
+ },
1804
+ },
1745
1805
  };
1746
1806
  }
1747
1807
 
@@ -1751,19 +1811,19 @@ function ValuePropertiesBlessing(constructor) {
1751
1811
  valueDescriptorMap: {
1752
1812
  get() {
1753
1813
  return valueDefinitionPairs.reduce((result, valueDefinitionPair) => {
1754
- const valueDescriptor = parseValueDefinitionPair(valueDefinitionPair);
1814
+ const valueDescriptor = parseValueDefinitionPair(valueDefinitionPair, this.identifier);
1755
1815
  const attributeName = this.data.getAttributeNameForKey(valueDescriptor.key);
1756
1816
  return Object.assign(result, { [attributeName]: valueDescriptor });
1757
1817
  }, {});
1758
- }
1759
- }
1818
+ },
1819
+ },
1760
1820
  };
1761
1821
  return valueDefinitionPairs.reduce((properties, valueDefinitionPair) => {
1762
1822
  return Object.assign(properties, propertiesForValueDefinitionPair(valueDefinitionPair));
1763
1823
  }, propertyDescriptorMap);
1764
1824
  }
1765
- function propertiesForValueDefinitionPair(valueDefinitionPair) {
1766
- const definition = parseValueDefinitionPair(valueDefinitionPair);
1825
+ function propertiesForValueDefinitionPair(valueDefinitionPair, controller) {
1826
+ const definition = parseValueDefinitionPair(valueDefinitionPair, controller);
1767
1827
  const { key, name, reader: read, writer: write } = definition;
1768
1828
  return {
1769
1829
  [name]: {
@@ -1783,56 +1843,74 @@ function propertiesForValueDefinitionPair(valueDefinitionPair) {
1783
1843
  else {
1784
1844
  this.data.set(key, write(value));
1785
1845
  }
1786
- }
1846
+ },
1787
1847
  },
1788
1848
  [`has${capitalize(name)}`]: {
1789
1849
  get() {
1790
1850
  return this.data.has(key) || definition.hasCustomDefaultValue;
1791
- }
1792
- }
1851
+ },
1852
+ },
1793
1853
  };
1794
1854
  }
1795
- function parseValueDefinitionPair([token, typeDefinition]) {
1796
- return valueDescriptorForTokenAndTypeDefinition(token, typeDefinition);
1855
+ function parseValueDefinitionPair([token, typeDefinition], controller) {
1856
+ return valueDescriptorForTokenAndTypeDefinition({
1857
+ controller,
1858
+ token,
1859
+ typeDefinition,
1860
+ });
1797
1861
  }
1798
1862
  function parseValueTypeConstant(constant) {
1799
1863
  switch (constant) {
1800
- case Array: return "array";
1801
- case Boolean: return "boolean";
1802
- case Number: return "number";
1803
- case Object: return "object";
1804
- case String: return "string";
1864
+ case Array:
1865
+ return "array";
1866
+ case Boolean:
1867
+ return "boolean";
1868
+ case Number:
1869
+ return "number";
1870
+ case Object:
1871
+ return "object";
1872
+ case String:
1873
+ return "string";
1805
1874
  }
1806
1875
  }
1807
1876
  function parseValueTypeDefault(defaultValue) {
1808
1877
  switch (typeof defaultValue) {
1809
- case "boolean": return "boolean";
1810
- case "number": return "number";
1811
- case "string": return "string";
1878
+ case "boolean":
1879
+ return "boolean";
1880
+ case "number":
1881
+ return "number";
1882
+ case "string":
1883
+ return "string";
1812
1884
  }
1813
1885
  if (Array.isArray(defaultValue))
1814
1886
  return "array";
1815
1887
  if (Object.prototype.toString.call(defaultValue) === "[object Object]")
1816
1888
  return "object";
1817
1889
  }
1818
- function parseValueTypeObject(typeObject) {
1819
- const typeFromObject = parseValueTypeConstant(typeObject.type);
1820
- if (typeFromObject) {
1821
- const defaultValueType = parseValueTypeDefault(typeObject.default);
1822
- if (typeFromObject !== defaultValueType) {
1823
- throw new Error(`Type "${typeFromObject}" must match the type of the default value. Given default value: "${typeObject.default}" as "${defaultValueType}"`);
1824
- }
1825
- return typeFromObject;
1826
- }
1827
- }
1828
- function parseValueTypeDefinition(typeDefinition) {
1829
- const typeFromObject = parseValueTypeObject(typeDefinition);
1830
- const typeFromDefaultValue = parseValueTypeDefault(typeDefinition);
1831
- const typeFromConstant = parseValueTypeConstant(typeDefinition);
1890
+ function parseValueTypeObject(payload) {
1891
+ const typeFromObject = parseValueTypeConstant(payload.typeObject.type);
1892
+ if (!typeFromObject)
1893
+ return;
1894
+ const defaultValueType = parseValueTypeDefault(payload.typeObject.default);
1895
+ if (typeFromObject !== defaultValueType) {
1896
+ const propertyPath = payload.controller ? `${payload.controller}.${payload.token}` : payload.token;
1897
+ throw new Error(`The specified default value for the Stimulus Value "${propertyPath}" must match the defined type "${typeFromObject}". The provided default value of "${payload.typeObject.default}" is of type "${defaultValueType}".`);
1898
+ }
1899
+ return typeFromObject;
1900
+ }
1901
+ function parseValueTypeDefinition(payload) {
1902
+ const typeFromObject = parseValueTypeObject({
1903
+ controller: payload.controller,
1904
+ token: payload.token,
1905
+ typeObject: payload.typeDefinition,
1906
+ });
1907
+ const typeFromDefaultValue = parseValueTypeDefault(payload.typeDefinition);
1908
+ const typeFromConstant = parseValueTypeConstant(payload.typeDefinition);
1832
1909
  const type = typeFromObject || typeFromDefaultValue || typeFromConstant;
1833
1910
  if (type)
1834
1911
  return type;
1835
- throw new Error(`Unknown value type "${typeDefinition}"`);
1912
+ const propertyPath = payload.controller ? `${payload.controller}.${payload.typeDefinition}` : payload.token;
1913
+ throw new Error(`Unknown value type "${propertyPath}" for "${payload.token}" value`);
1836
1914
  }
1837
1915
  function defaultValueForDefinition(typeDefinition) {
1838
1916
  const constant = parseValueTypeConstant(typeDefinition);
@@ -1843,36 +1921,44 @@ function defaultValueForDefinition(typeDefinition) {
1843
1921
  return defaultValue;
1844
1922
  return typeDefinition;
1845
1923
  }
1846
- function valueDescriptorForTokenAndTypeDefinition(token, typeDefinition) {
1847
- const key = `${dasherize(token)}-value`;
1848
- const type = parseValueTypeDefinition(typeDefinition);
1924
+ function valueDescriptorForTokenAndTypeDefinition(payload) {
1925
+ const key = `${dasherize(payload.token)}-value`;
1926
+ const type = parseValueTypeDefinition(payload);
1849
1927
  return {
1850
1928
  type,
1851
1929
  key,
1852
1930
  name: camelize(key),
1853
- get defaultValue() { return defaultValueForDefinition(typeDefinition); },
1854
- get hasCustomDefaultValue() { return parseValueTypeDefault(typeDefinition) !== undefined; },
1931
+ get defaultValue() {
1932
+ return defaultValueForDefinition(payload.typeDefinition);
1933
+ },
1934
+ get hasCustomDefaultValue() {
1935
+ return parseValueTypeDefault(payload.typeDefinition) !== undefined;
1936
+ },
1855
1937
  reader: readers[type],
1856
- writer: writers[type] || writers.default
1938
+ writer: writers[type] || writers.default,
1857
1939
  };
1858
1940
  }
1859
1941
  const defaultValuesByType = {
1860
- get array() { return []; },
1942
+ get array() {
1943
+ return [];
1944
+ },
1861
1945
  boolean: false,
1862
1946
  number: 0,
1863
- get object() { return {}; },
1864
- string: ""
1947
+ get object() {
1948
+ return {};
1949
+ },
1950
+ string: "",
1865
1951
  };
1866
1952
  const readers = {
1867
1953
  array(value) {
1868
1954
  const array = JSON.parse(value);
1869
1955
  if (!Array.isArray(array)) {
1870
- throw new TypeError("Expected array");
1956
+ throw new TypeError(`expected value of type "array" but instead got value "${value}" of type "${parseValueTypeDefault(array)}"`);
1871
1957
  }
1872
1958
  return array;
1873
1959
  },
1874
1960
  boolean(value) {
1875
- return !(value == "0" || value == "false");
1961
+ return !(value == "0" || String(value).toLowerCase() == "false");
1876
1962
  },
1877
1963
  number(value) {
1878
1964
  return Number(value);
@@ -1880,18 +1966,18 @@ const readers = {
1880
1966
  object(value) {
1881
1967
  const object = JSON.parse(value);
1882
1968
  if (object === null || typeof object != "object" || Array.isArray(object)) {
1883
- throw new TypeError("Expected object");
1969
+ throw new TypeError(`expected value of type "object" but instead got value "${value}" of type "${parseValueTypeDefault(object)}"`);
1884
1970
  }
1885
1971
  return object;
1886
1972
  },
1887
1973
  string(value) {
1888
1974
  return value;
1889
- }
1975
+ },
1890
1976
  };
1891
1977
  const writers = {
1892
1978
  default: writeString,
1893
1979
  array: writeJSON,
1894
- object: writeJSON
1980
+ object: writeJSON,
1895
1981
  };
1896
1982
  function writeJSON(value) {
1897
1983
  return JSON.stringify(value);