stimulus-rails 1.2.0 → 1.2.2

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.2.0
7
- Copyright © 2022 Basecamp, LLC
6
+ Stimulus 3.2.2
7
+ Copyright © 2023 Basecamp, LLC
8
8
  */
9
9
  class EventListener {
10
10
  constructor(eventTarget, eventName, eventOptions) {
@@ -169,17 +169,23 @@ const defaultActionDescriptorFilters = {
169
169
  }
170
170
  },
171
171
  };
172
- const descriptorPattern = /^(?:(.+?)(?:\.(.+?))?(?:@(window|document))?->)?(.+?)(?:#([^:]+?))(?::(.+))?$/;
172
+ const descriptorPattern = /^(?:(?:([^.]+?)\+)?(.+?)(?:\.(.+?))?(?:@(window|document))?->)?(.+?)(?:#([^:]+?))(?::(.+))?$/;
173
173
  function parseActionDescriptorString(descriptorString) {
174
174
  const source = descriptorString.trim();
175
175
  const matches = source.match(descriptorPattern) || [];
176
+ let eventName = matches[2];
177
+ let keyFilter = matches[3];
178
+ if (keyFilter && !["keydown", "keyup", "keypress"].includes(eventName)) {
179
+ eventName += `.${keyFilter}`;
180
+ keyFilter = "";
181
+ }
176
182
  return {
177
- eventTarget: parseEventTarget(matches[3]),
178
- eventName: matches[1],
179
- eventOptions: matches[6] ? parseEventOptions(matches[6]) : {},
180
- identifier: matches[4],
181
- methodName: matches[5],
182
- keyFilter: matches[2],
183
+ eventTarget: parseEventTarget(matches[4]),
184
+ eventName,
185
+ eventOptions: matches[7] ? parseEventOptions(matches[7]) : {},
186
+ identifier: matches[5],
187
+ methodName: matches[6],
188
+ keyFilter: matches[1] || keyFilter,
183
189
  };
184
190
  }
185
191
  function parseEventTarget(eventTargetName) {
@@ -220,6 +226,14 @@ function tokenize(value) {
220
226
  return value.match(/[^\s]+/g) || [];
221
227
  }
222
228
 
229
+ function isSomething(object) {
230
+ return object !== null && object !== undefined;
231
+ }
232
+ function hasProperty(object, property) {
233
+ return Object.prototype.hasOwnProperty.call(object, property);
234
+ }
235
+
236
+ const allModifiers = ["meta", "ctrl", "alt", "shift"];
223
237
  class Action {
224
238
  constructor(element, index, descriptor, schema) {
225
239
  this.element = element;
@@ -240,25 +254,33 @@ class Action {
240
254
  const eventTarget = this.eventTargetName ? `@${this.eventTargetName}` : "";
241
255
  return `${this.eventName}${eventFilter}${eventTarget}->${this.identifier}#${this.methodName}`;
242
256
  }
243
- isFilterTarget(event) {
257
+ shouldIgnoreKeyboardEvent(event) {
244
258
  if (!this.keyFilter) {
245
259
  return false;
246
260
  }
247
- const filteres = this.keyFilter.split("+");
248
- const modifiers = ["meta", "ctrl", "alt", "shift"];
249
- const [meta, ctrl, alt, shift] = modifiers.map((modifier) => filteres.includes(modifier));
250
- if (event.metaKey !== meta || event.ctrlKey !== ctrl || event.altKey !== alt || event.shiftKey !== shift) {
261
+ const filters = this.keyFilter.split("+");
262
+ if (this.keyFilterDissatisfied(event, filters)) {
251
263
  return true;
252
264
  }
253
- const standardFilter = filteres.filter((key) => !modifiers.includes(key))[0];
265
+ const standardFilter = filters.filter((key) => !allModifiers.includes(key))[0];
254
266
  if (!standardFilter) {
255
267
  return false;
256
268
  }
257
- if (!Object.prototype.hasOwnProperty.call(this.keyMappings, standardFilter)) {
258
- error(`contains unkown key filter: ${this.keyFilter}`);
269
+ if (!hasProperty(this.keyMappings, standardFilter)) {
270
+ error(`contains unknown key filter: ${this.keyFilter}`);
259
271
  }
260
272
  return this.keyMappings[standardFilter].toLowerCase() !== event.key.toLowerCase();
261
273
  }
274
+ shouldIgnoreMouseEvent(event) {
275
+ if (!this.keyFilter) {
276
+ return false;
277
+ }
278
+ const filters = [this.keyFilter];
279
+ if (this.keyFilterDissatisfied(event, filters)) {
280
+ return true;
281
+ }
282
+ return false;
283
+ }
262
284
  get params() {
263
285
  const params = {};
264
286
  const pattern = new RegExp(`^data-${this.identifier}-(.+)-param$`, "i");
@@ -277,6 +299,10 @@ class Action {
277
299
  get keyMappings() {
278
300
  return this.schema.keyMappings;
279
301
  }
302
+ keyFilterDissatisfied(event, filters) {
303
+ const [meta, ctrl, alt, shift] = allModifiers.map((modifier) => filters.includes(modifier));
304
+ return event.metaKey !== meta || event.ctrlKey !== ctrl || event.altKey !== alt || event.shiftKey !== shift;
305
+ }
280
306
  }
281
307
  const defaultEventNames = {
282
308
  a: () => "click",
@@ -323,8 +349,9 @@ class Binding {
323
349
  return this.context.identifier;
324
350
  }
325
351
  handleEvent(event) {
326
- if (this.willBeInvokedByEvent(event) && this.applyEventModifiers(event)) {
327
- this.invokeWithEvent(event);
352
+ const actionEvent = this.prepareActionEvent(event);
353
+ if (this.willBeInvokedByEvent(event) && this.applyEventModifiers(actionEvent)) {
354
+ this.invokeWithEvent(actionEvent);
328
355
  }
329
356
  }
330
357
  get eventName() {
@@ -340,11 +367,12 @@ class Binding {
340
367
  applyEventModifiers(event) {
341
368
  const { element } = this.action;
342
369
  const { actionDescriptorFilters } = this.context.application;
370
+ const { controller } = this.context;
343
371
  let passes = true;
344
372
  for (const [name, value] of Object.entries(this.eventOptions)) {
345
373
  if (name in actionDescriptorFilters) {
346
374
  const filter = actionDescriptorFilters[name];
347
- passes = passes && filter({ name, value, event, element });
375
+ passes = passes && filter({ name, value, event, element, controller });
348
376
  }
349
377
  else {
350
378
  continue;
@@ -352,12 +380,13 @@ class Binding {
352
380
  }
353
381
  return passes;
354
382
  }
383
+ prepareActionEvent(event) {
384
+ return Object.assign(event, { params: this.action.params });
385
+ }
355
386
  invokeWithEvent(event) {
356
387
  const { target, currentTarget } = event;
357
388
  try {
358
- const { params } = this.action;
359
- const actionEvent = Object.assign(event, { params });
360
- this.method.call(this.controller, actionEvent);
389
+ this.method.call(this.controller, event);
361
390
  this.context.logDebugActivity(this.methodName, { event, target, currentTarget, action: this.methodName });
362
391
  }
363
392
  catch (error) {
@@ -368,7 +397,10 @@ class Binding {
368
397
  }
369
398
  willBeInvokedByEvent(event) {
370
399
  const eventTarget = event.target;
371
- if (event instanceof KeyboardEvent && this.action.isFilterTarget(event)) {
400
+ if (event instanceof KeyboardEvent && this.action.shouldIgnoreKeyboardEvent(event)) {
401
+ return false;
402
+ }
403
+ if (event instanceof MouseEvent && this.action.shouldIgnoreMouseEvent(event)) {
372
404
  return false;
373
405
  }
374
406
  if (this.element === eventTarget) {
@@ -458,8 +490,7 @@ class ElementObserver {
458
490
  this.processAddedNodes(mutation.addedNodes);
459
491
  }
460
492
  }
461
- processAttributeChange(node, attributeName) {
462
- const element = node;
493
+ processAttributeChange(element, attributeName) {
463
494
  if (this.elements.has(element)) {
464
495
  if (this.delegate.elementAttributeChanged && this.matchElement(element)) {
465
496
  this.delegate.elementAttributeChanged(element, attributeName);
@@ -675,8 +706,8 @@ class IndexedMultimap extends Multimap {
675
706
  }
676
707
 
677
708
  class SelectorObserver {
678
- constructor(element, selector, delegate, details = {}) {
679
- this.selector = selector;
709
+ constructor(element, selector, delegate, details) {
710
+ this._selector = selector;
680
711
  this.details = details;
681
712
  this.elementObserver = new ElementObserver(element, this);
682
713
  this.delegate = delegate;
@@ -685,6 +716,13 @@ class SelectorObserver {
685
716
  get started() {
686
717
  return this.elementObserver.started;
687
718
  }
719
+ get selector() {
720
+ return this._selector;
721
+ }
722
+ set selector(selector) {
723
+ this._selector = selector;
724
+ this.refresh();
725
+ }
688
726
  start() {
689
727
  this.elementObserver.start();
690
728
  }
@@ -701,39 +739,61 @@ class SelectorObserver {
701
739
  return this.elementObserver.element;
702
740
  }
703
741
  matchElement(element) {
704
- const matches = element.matches(this.selector);
705
- if (this.delegate.selectorMatchElement) {
706
- return matches && this.delegate.selectorMatchElement(element, this.details);
742
+ const { selector } = this;
743
+ if (selector) {
744
+ const matches = element.matches(selector);
745
+ if (this.delegate.selectorMatchElement) {
746
+ return matches && this.delegate.selectorMatchElement(element, this.details);
747
+ }
748
+ return matches;
749
+ }
750
+ else {
751
+ return false;
707
752
  }
708
- return matches;
709
753
  }
710
754
  matchElementsInTree(tree) {
711
- const match = this.matchElement(tree) ? [tree] : [];
712
- const matches = Array.from(tree.querySelectorAll(this.selector)).filter((match) => this.matchElement(match));
713
- return match.concat(matches);
755
+ const { selector } = this;
756
+ if (selector) {
757
+ const match = this.matchElement(tree) ? [tree] : [];
758
+ const matches = Array.from(tree.querySelectorAll(selector)).filter((match) => this.matchElement(match));
759
+ return match.concat(matches);
760
+ }
761
+ else {
762
+ return [];
763
+ }
714
764
  }
715
765
  elementMatched(element) {
716
- this.selectorMatched(element);
766
+ const { selector } = this;
767
+ if (selector) {
768
+ this.selectorMatched(element, selector);
769
+ }
717
770
  }
718
771
  elementUnmatched(element) {
719
- this.selectorUnmatched(element);
772
+ const selectors = this.matchesByElement.getKeysForValue(element);
773
+ for (const selector of selectors) {
774
+ this.selectorUnmatched(element, selector);
775
+ }
720
776
  }
721
777
  elementAttributeChanged(element, _attributeName) {
722
- const matches = this.matchElement(element);
723
- const matchedBefore = this.matchesByElement.has(this.selector, element);
724
- if (!matches && matchedBefore) {
725
- this.selectorUnmatched(element);
778
+ const { selector } = this;
779
+ if (selector) {
780
+ const matches = this.matchElement(element);
781
+ const matchedBefore = this.matchesByElement.has(selector, element);
782
+ if (matches && !matchedBefore) {
783
+ this.selectorMatched(element, selector);
784
+ }
785
+ else if (!matches && matchedBefore) {
786
+ this.selectorUnmatched(element, selector);
787
+ }
726
788
  }
727
789
  }
728
- selectorMatched(element) {
729
- if (this.delegate.selectorMatched) {
730
- this.delegate.selectorMatched(element, this.selector, this.details);
731
- this.matchesByElement.add(this.selector, element);
732
- }
790
+ selectorMatched(element, selector) {
791
+ this.delegate.selectorMatched(element, selector, this.details);
792
+ this.matchesByElement.add(selector, element);
733
793
  }
734
- selectorUnmatched(element) {
735
- this.delegate.selectorUnmatched(element, this.selector, this.details);
736
- this.matchesByElement.delete(this.selector, element);
794
+ selectorUnmatched(element, selector) {
795
+ this.delegate.selectorUnmatched(element, selector, this.details);
796
+ this.matchesByElement.delete(selector, element);
737
797
  }
738
798
  }
739
799
 
@@ -1230,34 +1290,47 @@ function getOwnStaticObjectPairs(constructor, propertyName) {
1230
1290
 
1231
1291
  class OutletObserver {
1232
1292
  constructor(context, delegate) {
1293
+ this.started = false;
1233
1294
  this.context = context;
1234
1295
  this.delegate = delegate;
1235
1296
  this.outletsByName = new Multimap();
1236
1297
  this.outletElementsByName = new Multimap();
1237
1298
  this.selectorObserverMap = new Map();
1299
+ this.attributeObserverMap = new Map();
1238
1300
  }
1239
1301
  start() {
1240
- if (this.selectorObserverMap.size === 0) {
1302
+ if (!this.started) {
1241
1303
  this.outletDefinitions.forEach((outletName) => {
1242
- const selector = this.selector(outletName);
1243
- const details = { outletName };
1244
- if (selector) {
1245
- this.selectorObserverMap.set(outletName, new SelectorObserver(document.body, selector, this, details));
1246
- }
1304
+ this.setupSelectorObserverForOutlet(outletName);
1305
+ this.setupAttributeObserverForOutlet(outletName);
1247
1306
  });
1248
- this.selectorObserverMap.forEach((observer) => observer.start());
1307
+ this.started = true;
1308
+ this.dependentContexts.forEach((context) => context.refresh());
1249
1309
  }
1250
- this.dependentContexts.forEach((context) => context.refresh());
1310
+ }
1311
+ refresh() {
1312
+ this.selectorObserverMap.forEach((observer) => observer.refresh());
1313
+ this.attributeObserverMap.forEach((observer) => observer.refresh());
1251
1314
  }
1252
1315
  stop() {
1253
- if (this.selectorObserverMap.size > 0) {
1316
+ if (this.started) {
1317
+ this.started = false;
1254
1318
  this.disconnectAllOutlets();
1319
+ this.stopSelectorObservers();
1320
+ this.stopAttributeObservers();
1321
+ }
1322
+ }
1323
+ stopSelectorObservers() {
1324
+ if (this.selectorObserverMap.size > 0) {
1255
1325
  this.selectorObserverMap.forEach((observer) => observer.stop());
1256
1326
  this.selectorObserverMap.clear();
1257
1327
  }
1258
1328
  }
1259
- refresh() {
1260
- this.selectorObserverMap.forEach((observer) => observer.refresh());
1329
+ stopAttributeObservers() {
1330
+ if (this.attributeObserverMap.size > 0) {
1331
+ this.attributeObserverMap.forEach((observer) => observer.stop());
1332
+ this.attributeObserverMap.clear();
1333
+ }
1261
1334
  }
1262
1335
  selectorMatched(element, _selector, { outletName }) {
1263
1336
  const outlet = this.getOutlet(element, outletName);
@@ -1272,8 +1345,33 @@ class OutletObserver {
1272
1345
  }
1273
1346
  }
1274
1347
  selectorMatchElement(element, { outletName }) {
1275
- return (this.hasOutlet(element, outletName) &&
1276
- element.matches(`[${this.context.application.schema.controllerAttribute}~=${outletName}]`));
1348
+ const selector = this.selector(outletName);
1349
+ const hasOutlet = this.hasOutlet(element, outletName);
1350
+ const hasOutletController = element.matches(`[${this.schema.controllerAttribute}~=${outletName}]`);
1351
+ if (selector) {
1352
+ return hasOutlet && hasOutletController && element.matches(selector);
1353
+ }
1354
+ else {
1355
+ return false;
1356
+ }
1357
+ }
1358
+ elementMatchedAttribute(_element, attributeName) {
1359
+ const outletName = this.getOutletNameFromOutletAttributeName(attributeName);
1360
+ if (outletName) {
1361
+ this.updateSelectorObserverForOutlet(outletName);
1362
+ }
1363
+ }
1364
+ elementAttributeValueChanged(_element, attributeName) {
1365
+ const outletName = this.getOutletNameFromOutletAttributeName(attributeName);
1366
+ if (outletName) {
1367
+ this.updateSelectorObserverForOutlet(outletName);
1368
+ }
1369
+ }
1370
+ elementUnmatchedAttribute(_element, attributeName) {
1371
+ const outletName = this.getOutletNameFromOutletAttributeName(attributeName);
1372
+ if (outletName) {
1373
+ this.updateSelectorObserverForOutlet(outletName);
1374
+ }
1277
1375
  }
1278
1376
  connectOutlet(outlet, element, outletName) {
1279
1377
  var _a;
@@ -1301,9 +1399,33 @@ class OutletObserver {
1301
1399
  }
1302
1400
  }
1303
1401
  }
1402
+ updateSelectorObserverForOutlet(outletName) {
1403
+ const observer = this.selectorObserverMap.get(outletName);
1404
+ if (observer) {
1405
+ observer.selector = this.selector(outletName);
1406
+ }
1407
+ }
1408
+ setupSelectorObserverForOutlet(outletName) {
1409
+ const selector = this.selector(outletName);
1410
+ const selectorObserver = new SelectorObserver(document.body, selector, this, { outletName });
1411
+ this.selectorObserverMap.set(outletName, selectorObserver);
1412
+ selectorObserver.start();
1413
+ }
1414
+ setupAttributeObserverForOutlet(outletName) {
1415
+ const attributeName = this.attributeNameForOutletName(outletName);
1416
+ const attributeObserver = new AttributeObserver(this.scope.element, attributeName, this);
1417
+ this.attributeObserverMap.set(outletName, attributeObserver);
1418
+ attributeObserver.start();
1419
+ }
1304
1420
  selector(outletName) {
1305
1421
  return this.scope.outlets.getSelectorForOutletName(outletName);
1306
1422
  }
1423
+ attributeNameForOutletName(outletName) {
1424
+ return this.scope.schema.outletAttributeForScope(this.identifier, outletName);
1425
+ }
1426
+ getOutletNameFromOutletAttributeName(attributeName) {
1427
+ return this.outletDefinitions.find((outletName) => this.attributeNameForOutletName(outletName) === attributeName);
1428
+ }
1307
1429
  get outletDependencies() {
1308
1430
  const dependencies = new Multimap();
1309
1431
  this.router.modules.forEach((module) => {
@@ -1335,6 +1457,9 @@ class OutletObserver {
1335
1457
  get scope() {
1336
1458
  return this.context.scope;
1337
1459
  }
1460
+ get schema() {
1461
+ return this.context.schema;
1462
+ }
1338
1463
  get identifier() {
1339
1464
  return this.context.identifier;
1340
1465
  }
@@ -1822,6 +1947,9 @@ class ScopeObserver {
1822
1947
  }
1823
1948
  parseValueForToken(token) {
1824
1949
  const { element, content: identifier } = token;
1950
+ return this.parseValueForElementAndIdentifier(element, identifier);
1951
+ }
1952
+ parseValueForElementAndIdentifier(element, identifier) {
1825
1953
  const scopesByIdentifier = this.fetchScopesByIdentifierForElement(element);
1826
1954
  let scope = scopesByIdentifier.get(identifier);
1827
1955
  if (!scope) {
@@ -1893,7 +2021,7 @@ class Router {
1893
2021
  this.connectModule(module);
1894
2022
  const afterLoad = definition.controllerConstructor.afterLoad;
1895
2023
  if (afterLoad) {
1896
- afterLoad(definition.identifier, this.application);
2024
+ afterLoad.call(definition.controllerConstructor, definition.identifier, this.application);
1897
2025
  }
1898
2026
  }
1899
2027
  unloadIdentifier(identifier) {
@@ -1908,6 +2036,15 @@ class Router {
1908
2036
  return module.contexts.find((context) => context.element == element);
1909
2037
  }
1910
2038
  }
2039
+ proposeToConnectScopeForElementAndIdentifier(element, identifier) {
2040
+ const scope = this.scopeObserver.parseValueForElementAndIdentifier(element, identifier);
2041
+ if (scope) {
2042
+ this.scopeObserver.elementMatchedValue(scope.element, scope);
2043
+ }
2044
+ else {
2045
+ console.error(`Couldn't find or create scope for identifier: "${identifier}" and element:`, element);
2046
+ }
2047
+ }
1911
2048
  handleError(error, message, detail) {
1912
2049
  this.application.handleError(error, message, detail);
1913
2050
  }
@@ -1946,7 +2083,7 @@ const defaultSchema = {
1946
2083
  targetAttribute: "data-target",
1947
2084
  targetAttributeForScope: (identifier) => `data-${identifier}-target`,
1948
2085
  outletAttributeForScope: (identifier, outlet) => `data-${identifier}-${outlet}-outlet`,
1949
- keyMappings: Object.assign(Object.assign({ enter: "Enter", tab: "Tab", esc: "Escape", space: " ", up: "ArrowUp", down: "ArrowDown", left: "ArrowLeft", right: "ArrowRight", home: "Home", end: "End" }, objectFromEntries("abcdefghijklmnopqrstuvwxyz".split("").map((c) => [c, c]))), objectFromEntries("0123456789".split("").map((n) => [n, n]))),
2086
+ keyMappings: Object.assign(Object.assign({ enter: "Enter", tab: "Tab", esc: "Escape", space: " ", up: "ArrowUp", down: "ArrowDown", left: "ArrowLeft", right: "ArrowRight", home: "Home", end: "End", page_up: "PageUp", page_down: "PageDown" }, objectFromEntries("abcdefghijklmnopqrstuvwxyz".split("").map((c) => [c, c]))), objectFromEntries("0123456789".split("").map((n) => [n, n]))),
1950
2087
  };
1951
2088
  function objectFromEntries(array) {
1952
2089
  return array.reduce((memo, [k, v]) => (Object.assign(Object.assign({}, memo), { [k]: v })), {});
@@ -2072,22 +2209,32 @@ function OutletPropertiesBlessing(constructor) {
2072
2209
  return Object.assign(properties, propertiesForOutletDefinition(outletDefinition));
2073
2210
  }, {});
2074
2211
  }
2212
+ function getOutletController(controller, element, identifier) {
2213
+ return controller.application.getControllerForElementAndIdentifier(element, identifier);
2214
+ }
2215
+ function getControllerAndEnsureConnectedScope(controller, element, outletName) {
2216
+ let outletController = getOutletController(controller, element, outletName);
2217
+ if (outletController)
2218
+ return outletController;
2219
+ controller.application.router.proposeToConnectScopeForElementAndIdentifier(element, outletName);
2220
+ outletController = getOutletController(controller, element, outletName);
2221
+ if (outletController)
2222
+ return outletController;
2223
+ }
2075
2224
  function propertiesForOutletDefinition(name) {
2076
2225
  const camelizedName = namespaceCamelize(name);
2077
2226
  return {
2078
2227
  [`${camelizedName}Outlet`]: {
2079
2228
  get() {
2080
- const outlet = this.outlets.find(name);
2081
- if (outlet) {
2082
- const outletController = this.application.getControllerForElementAndIdentifier(outlet, name);
2083
- if (outletController) {
2229
+ const outletElement = this.outlets.find(name);
2230
+ const selector = this.outlets.getSelectorForOutletName(name);
2231
+ if (outletElement) {
2232
+ const outletController = getControllerAndEnsureConnectedScope(this, outletElement, name);
2233
+ if (outletController)
2084
2234
  return outletController;
2085
- }
2086
- else {
2087
- throw new Error(`Missing "data-controller=${name}" attribute on outlet element for "${this.identifier}" controller`);
2088
- }
2235
+ throw new Error(`The provided outlet element is missing an outlet controller "${name}" instance for host controller "${this.identifier}"`);
2089
2236
  }
2090
- throw new Error(`Missing outlet element "${name}" for "${this.identifier}" controller`);
2237
+ throw new Error(`Missing outlet element "${name}" for host controller "${this.identifier}". Stimulus couldn't find a matching outlet element using selector "${selector}".`);
2091
2238
  },
2092
2239
  },
2093
2240
  [`${camelizedName}Outlets`]: {
@@ -2095,14 +2242,11 @@ function propertiesForOutletDefinition(name) {
2095
2242
  const outlets = this.outlets.findAll(name);
2096
2243
  if (outlets.length > 0) {
2097
2244
  return outlets
2098
- .map((outlet) => {
2099
- const controller = this.application.getControllerForElementAndIdentifier(outlet, name);
2100
- if (controller) {
2101
- return controller;
2102
- }
2103
- else {
2104
- console.warn(`The provided outlet element is missing the outlet controller "${name}" for "${this.identifier}"`, outlet);
2105
- }
2245
+ .map((outletElement) => {
2246
+ const outletController = getControllerAndEnsureConnectedScope(this, outletElement, name);
2247
+ if (outletController)
2248
+ return outletController;
2249
+ console.warn(`The provided outlet element is missing an outlet controller "${name}" instance for host controller "${this.identifier}"`, outletElement);
2106
2250
  })
2107
2251
  .filter((controller) => controller);
2108
2252
  }
@@ -2111,12 +2255,13 @@ function propertiesForOutletDefinition(name) {
2111
2255
  },
2112
2256
  [`${camelizedName}OutletElement`]: {
2113
2257
  get() {
2114
- const outlet = this.outlets.find(name);
2115
- if (outlet) {
2116
- return outlet;
2258
+ const outletElement = this.outlets.find(name);
2259
+ const selector = this.outlets.getSelectorForOutletName(name);
2260
+ if (outletElement) {
2261
+ return outletElement;
2117
2262
  }
2118
2263
  else {
2119
- throw new Error(`Missing outlet element "${name}" for "${this.identifier}" controller`);
2264
+ throw new Error(`Missing outlet element "${name}" for host controller "${this.identifier}". Stimulus couldn't find a matching outlet element using selector "${selector}".`);
2120
2265
  }
2121
2266
  },
2122
2267
  },
@@ -2248,51 +2393,67 @@ function parseValueTypeDefault(defaultValue) {
2248
2393
  return "object";
2249
2394
  }
2250
2395
  function parseValueTypeObject(payload) {
2251
- const typeFromObject = parseValueTypeConstant(payload.typeObject.type);
2252
- if (!typeFromObject)
2253
- return;
2254
- const defaultValueType = parseValueTypeDefault(payload.typeObject.default);
2255
- if (typeFromObject !== defaultValueType) {
2256
- const propertyPath = payload.controller ? `${payload.controller}.${payload.token}` : payload.token;
2257
- 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}".`);
2258
- }
2259
- return typeFromObject;
2396
+ const { controller, token, typeObject } = payload;
2397
+ const hasType = isSomething(typeObject.type);
2398
+ const hasDefault = isSomething(typeObject.default);
2399
+ const fullObject = hasType && hasDefault;
2400
+ const onlyType = hasType && !hasDefault;
2401
+ const onlyDefault = !hasType && hasDefault;
2402
+ const typeFromObject = parseValueTypeConstant(typeObject.type);
2403
+ const typeFromDefaultValue = parseValueTypeDefault(payload.typeObject.default);
2404
+ if (onlyType)
2405
+ return typeFromObject;
2406
+ if (onlyDefault)
2407
+ return typeFromDefaultValue;
2408
+ if (typeFromObject !== typeFromDefaultValue) {
2409
+ const propertyPath = controller ? `${controller}.${token}` : token;
2410
+ throw new Error(`The specified default value for the Stimulus Value "${propertyPath}" must match the defined type "${typeFromObject}". The provided default value of "${typeObject.default}" is of type "${typeFromDefaultValue}".`);
2411
+ }
2412
+ if (fullObject)
2413
+ return typeFromObject;
2260
2414
  }
2261
2415
  function parseValueTypeDefinition(payload) {
2262
- const typeFromObject = parseValueTypeObject({
2263
- controller: payload.controller,
2264
- token: payload.token,
2265
- typeObject: payload.typeDefinition,
2266
- });
2267
- const typeFromDefaultValue = parseValueTypeDefault(payload.typeDefinition);
2268
- const typeFromConstant = parseValueTypeConstant(payload.typeDefinition);
2416
+ const { controller, token, typeDefinition } = payload;
2417
+ const typeObject = { controller, token, typeObject: typeDefinition };
2418
+ const typeFromObject = parseValueTypeObject(typeObject);
2419
+ const typeFromDefaultValue = parseValueTypeDefault(typeDefinition);
2420
+ const typeFromConstant = parseValueTypeConstant(typeDefinition);
2269
2421
  const type = typeFromObject || typeFromDefaultValue || typeFromConstant;
2270
2422
  if (type)
2271
2423
  return type;
2272
- const propertyPath = payload.controller ? `${payload.controller}.${payload.typeDefinition}` : payload.token;
2273
- throw new Error(`Unknown value type "${propertyPath}" for "${payload.token}" value`);
2424
+ const propertyPath = controller ? `${controller}.${typeDefinition}` : token;
2425
+ throw new Error(`Unknown value type "${propertyPath}" for "${token}" value`);
2274
2426
  }
2275
2427
  function defaultValueForDefinition(typeDefinition) {
2276
2428
  const constant = parseValueTypeConstant(typeDefinition);
2277
2429
  if (constant)
2278
2430
  return defaultValuesByType[constant];
2279
- const defaultValue = typeDefinition.default;
2280
- if (defaultValue !== undefined)
2281
- return defaultValue;
2431
+ const hasDefault = hasProperty(typeDefinition, "default");
2432
+ const hasType = hasProperty(typeDefinition, "type");
2433
+ const typeObject = typeDefinition;
2434
+ if (hasDefault)
2435
+ return typeObject.default;
2436
+ if (hasType) {
2437
+ const { type } = typeObject;
2438
+ const constantFromType = parseValueTypeConstant(type);
2439
+ if (constantFromType)
2440
+ return defaultValuesByType[constantFromType];
2441
+ }
2282
2442
  return typeDefinition;
2283
2443
  }
2284
2444
  function valueDescriptorForTokenAndTypeDefinition(payload) {
2285
- const key = `${dasherize(payload.token)}-value`;
2445
+ const { token, typeDefinition } = payload;
2446
+ const key = `${dasherize(token)}-value`;
2286
2447
  const type = parseValueTypeDefinition(payload);
2287
2448
  return {
2288
2449
  type,
2289
2450
  key,
2290
2451
  name: camelize(key),
2291
2452
  get defaultValue() {
2292
- return defaultValueForDefinition(payload.typeDefinition);
2453
+ return defaultValueForDefinition(typeDefinition);
2293
2454
  },
2294
2455
  get hasCustomDefaultValue() {
2295
- return parseValueTypeDefault(payload.typeDefinition) !== undefined;
2456
+ return parseValueTypeDefault(typeDefinition) !== undefined;
2296
2457
  },
2297
2458
  reader: readers[type],
2298
2459
  writer: writers[type] || writers.default,
@@ -2321,7 +2482,7 @@ const readers = {
2321
2482
  return !(value == "0" || String(value).toLowerCase() == "false");
2322
2483
  },
2323
2484
  number(value) {
2324
- return Number(value);
2485
+ return Number(value.replace(/_/g, ""));
2325
2486
  },
2326
2487
  object(value) {
2327
2488
  const object = JSON.parse(value);
@@ -2386,7 +2547,7 @@ class Controller {
2386
2547
  }
2387
2548
  disconnect() {
2388
2549
  }
2389
- dispatch(eventName, { target = this.element, detail = {}, prefix = this.identifier, bubbles = true, cancelable = true } = {}) {
2550
+ dispatch(eventName, { target = this.element, detail = {}, prefix = this.identifier, bubbles = true, cancelable = true, } = {}) {
2390
2551
  const type = prefix ? `${prefix}:${eventName}` : eventName;
2391
2552
  const event = new CustomEvent(type, { detail, bubbles, cancelable });
2392
2553
  target.dispatchEvent(event);