stimulus-rails 1.0.4 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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);