stimulus-rails 1.2.1 → 1.2.2

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.2.1
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,23 +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[1];
177
- let keyFilter = matches[2];
176
+ let eventName = matches[2];
177
+ let keyFilter = matches[3];
178
178
  if (keyFilter && !["keydown", "keyup", "keypress"].includes(eventName)) {
179
179
  eventName += `.${keyFilter}`;
180
180
  keyFilter = "";
181
181
  }
182
182
  return {
183
- eventTarget: parseEventTarget(matches[3]),
183
+ eventTarget: parseEventTarget(matches[4]),
184
184
  eventName,
185
- eventOptions: matches[6] ? parseEventOptions(matches[6]) : {},
186
- identifier: matches[4],
187
- methodName: matches[5],
188
- keyFilter,
185
+ eventOptions: matches[7] ? parseEventOptions(matches[7]) : {},
186
+ identifier: matches[5],
187
+ methodName: matches[6],
188
+ keyFilter: matches[1] || keyFilter,
189
189
  };
190
190
  }
191
191
  function parseEventTarget(eventTargetName) {
@@ -226,6 +226,14 @@ function tokenize(value) {
226
226
  return value.match(/[^\s]+/g) || [];
227
227
  }
228
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"];
229
237
  class Action {
230
238
  constructor(element, index, descriptor, schema) {
231
239
  this.element = element;
@@ -246,25 +254,33 @@ class Action {
246
254
  const eventTarget = this.eventTargetName ? `@${this.eventTargetName}` : "";
247
255
  return `${this.eventName}${eventFilter}${eventTarget}->${this.identifier}#${this.methodName}`;
248
256
  }
249
- isFilterTarget(event) {
257
+ shouldIgnoreKeyboardEvent(event) {
250
258
  if (!this.keyFilter) {
251
259
  return false;
252
260
  }
253
- const filteres = this.keyFilter.split("+");
254
- const modifiers = ["meta", "ctrl", "alt", "shift"];
255
- const [meta, ctrl, alt, shift] = modifiers.map((modifier) => filteres.includes(modifier));
256
- 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)) {
257
263
  return true;
258
264
  }
259
- const standardFilter = filteres.filter((key) => !modifiers.includes(key))[0];
265
+ const standardFilter = filters.filter((key) => !allModifiers.includes(key))[0];
260
266
  if (!standardFilter) {
261
267
  return false;
262
268
  }
263
- if (!Object.prototype.hasOwnProperty.call(this.keyMappings, standardFilter)) {
269
+ if (!hasProperty(this.keyMappings, standardFilter)) {
264
270
  error(`contains unknown key filter: ${this.keyFilter}`);
265
271
  }
266
272
  return this.keyMappings[standardFilter].toLowerCase() !== event.key.toLowerCase();
267
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
+ }
268
284
  get params() {
269
285
  const params = {};
270
286
  const pattern = new RegExp(`^data-${this.identifier}-(.+)-param$`, "i");
@@ -283,6 +299,10 @@ class Action {
283
299
  get keyMappings() {
284
300
  return this.schema.keyMappings;
285
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
+ }
286
306
  }
287
307
  const defaultEventNames = {
288
308
  a: () => "click",
@@ -329,8 +349,9 @@ class Binding {
329
349
  return this.context.identifier;
330
350
  }
331
351
  handleEvent(event) {
332
- if (this.willBeInvokedByEvent(event) && this.applyEventModifiers(event)) {
333
- this.invokeWithEvent(event);
352
+ const actionEvent = this.prepareActionEvent(event);
353
+ if (this.willBeInvokedByEvent(event) && this.applyEventModifiers(actionEvent)) {
354
+ this.invokeWithEvent(actionEvent);
334
355
  }
335
356
  }
336
357
  get eventName() {
@@ -346,11 +367,12 @@ class Binding {
346
367
  applyEventModifiers(event) {
347
368
  const { element } = this.action;
348
369
  const { actionDescriptorFilters } = this.context.application;
370
+ const { controller } = this.context;
349
371
  let passes = true;
350
372
  for (const [name, value] of Object.entries(this.eventOptions)) {
351
373
  if (name in actionDescriptorFilters) {
352
374
  const filter = actionDescriptorFilters[name];
353
- passes = passes && filter({ name, value, event, element });
375
+ passes = passes && filter({ name, value, event, element, controller });
354
376
  }
355
377
  else {
356
378
  continue;
@@ -358,12 +380,13 @@ class Binding {
358
380
  }
359
381
  return passes;
360
382
  }
383
+ prepareActionEvent(event) {
384
+ return Object.assign(event, { params: this.action.params });
385
+ }
361
386
  invokeWithEvent(event) {
362
387
  const { target, currentTarget } = event;
363
388
  try {
364
- const { params } = this.action;
365
- const actionEvent = Object.assign(event, { params });
366
- this.method.call(this.controller, actionEvent);
389
+ this.method.call(this.controller, event);
367
390
  this.context.logDebugActivity(this.methodName, { event, target, currentTarget, action: this.methodName });
368
391
  }
369
392
  catch (error) {
@@ -374,7 +397,10 @@ class Binding {
374
397
  }
375
398
  willBeInvokedByEvent(event) {
376
399
  const eventTarget = event.target;
377
- 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)) {
378
404
  return false;
379
405
  }
380
406
  if (this.element === eventTarget) {
@@ -464,8 +490,7 @@ class ElementObserver {
464
490
  this.processAddedNodes(mutation.addedNodes);
465
491
  }
466
492
  }
467
- processAttributeChange(node, attributeName) {
468
- const element = node;
493
+ processAttributeChange(element, attributeName) {
469
494
  if (this.elements.has(element)) {
470
495
  if (this.delegate.elementAttributeChanged && this.matchElement(element)) {
471
496
  this.delegate.elementAttributeChanged(element, attributeName);
@@ -681,8 +706,8 @@ class IndexedMultimap extends Multimap {
681
706
  }
682
707
 
683
708
  class SelectorObserver {
684
- constructor(element, selector, delegate, details = {}) {
685
- this.selector = selector;
709
+ constructor(element, selector, delegate, details) {
710
+ this._selector = selector;
686
711
  this.details = details;
687
712
  this.elementObserver = new ElementObserver(element, this);
688
713
  this.delegate = delegate;
@@ -691,6 +716,13 @@ class SelectorObserver {
691
716
  get started() {
692
717
  return this.elementObserver.started;
693
718
  }
719
+ get selector() {
720
+ return this._selector;
721
+ }
722
+ set selector(selector) {
723
+ this._selector = selector;
724
+ this.refresh();
725
+ }
694
726
  start() {
695
727
  this.elementObserver.start();
696
728
  }
@@ -707,39 +739,61 @@ class SelectorObserver {
707
739
  return this.elementObserver.element;
708
740
  }
709
741
  matchElement(element) {
710
- const matches = element.matches(this.selector);
711
- if (this.delegate.selectorMatchElement) {
712
- 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;
713
752
  }
714
- return matches;
715
753
  }
716
754
  matchElementsInTree(tree) {
717
- const match = this.matchElement(tree) ? [tree] : [];
718
- const matches = Array.from(tree.querySelectorAll(this.selector)).filter((match) => this.matchElement(match));
719
- 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
+ }
720
764
  }
721
765
  elementMatched(element) {
722
- this.selectorMatched(element);
766
+ const { selector } = this;
767
+ if (selector) {
768
+ this.selectorMatched(element, selector);
769
+ }
723
770
  }
724
771
  elementUnmatched(element) {
725
- this.selectorUnmatched(element);
772
+ const selectors = this.matchesByElement.getKeysForValue(element);
773
+ for (const selector of selectors) {
774
+ this.selectorUnmatched(element, selector);
775
+ }
726
776
  }
727
777
  elementAttributeChanged(element, _attributeName) {
728
- const matches = this.matchElement(element);
729
- const matchedBefore = this.matchesByElement.has(this.selector, element);
730
- if (!matches && matchedBefore) {
731
- 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
+ }
732
788
  }
733
789
  }
734
- selectorMatched(element) {
735
- if (this.delegate.selectorMatched) {
736
- this.delegate.selectorMatched(element, this.selector, this.details);
737
- this.matchesByElement.add(this.selector, element);
738
- }
790
+ selectorMatched(element, selector) {
791
+ this.delegate.selectorMatched(element, selector, this.details);
792
+ this.matchesByElement.add(selector, element);
739
793
  }
740
- selectorUnmatched(element) {
741
- this.delegate.selectorUnmatched(element, this.selector, this.details);
742
- this.matchesByElement.delete(this.selector, element);
794
+ selectorUnmatched(element, selector) {
795
+ this.delegate.selectorUnmatched(element, selector, this.details);
796
+ this.matchesByElement.delete(selector, element);
743
797
  }
744
798
  }
745
799
 
@@ -1236,34 +1290,47 @@ function getOwnStaticObjectPairs(constructor, propertyName) {
1236
1290
 
1237
1291
  class OutletObserver {
1238
1292
  constructor(context, delegate) {
1293
+ this.started = false;
1239
1294
  this.context = context;
1240
1295
  this.delegate = delegate;
1241
1296
  this.outletsByName = new Multimap();
1242
1297
  this.outletElementsByName = new Multimap();
1243
1298
  this.selectorObserverMap = new Map();
1299
+ this.attributeObserverMap = new Map();
1244
1300
  }
1245
1301
  start() {
1246
- if (this.selectorObserverMap.size === 0) {
1302
+ if (!this.started) {
1247
1303
  this.outletDefinitions.forEach((outletName) => {
1248
- const selector = this.selector(outletName);
1249
- const details = { outletName };
1250
- if (selector) {
1251
- this.selectorObserverMap.set(outletName, new SelectorObserver(document.body, selector, this, details));
1252
- }
1304
+ this.setupSelectorObserverForOutlet(outletName);
1305
+ this.setupAttributeObserverForOutlet(outletName);
1253
1306
  });
1254
- this.selectorObserverMap.forEach((observer) => observer.start());
1307
+ this.started = true;
1308
+ this.dependentContexts.forEach((context) => context.refresh());
1255
1309
  }
1256
- this.dependentContexts.forEach((context) => context.refresh());
1310
+ }
1311
+ refresh() {
1312
+ this.selectorObserverMap.forEach((observer) => observer.refresh());
1313
+ this.attributeObserverMap.forEach((observer) => observer.refresh());
1257
1314
  }
1258
1315
  stop() {
1259
- if (this.selectorObserverMap.size > 0) {
1316
+ if (this.started) {
1317
+ this.started = false;
1260
1318
  this.disconnectAllOutlets();
1319
+ this.stopSelectorObservers();
1320
+ this.stopAttributeObservers();
1321
+ }
1322
+ }
1323
+ stopSelectorObservers() {
1324
+ if (this.selectorObserverMap.size > 0) {
1261
1325
  this.selectorObserverMap.forEach((observer) => observer.stop());
1262
1326
  this.selectorObserverMap.clear();
1263
1327
  }
1264
1328
  }
1265
- refresh() {
1266
- 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
+ }
1267
1334
  }
1268
1335
  selectorMatched(element, _selector, { outletName }) {
1269
1336
  const outlet = this.getOutlet(element, outletName);
@@ -1278,8 +1345,33 @@ class OutletObserver {
1278
1345
  }
1279
1346
  }
1280
1347
  selectorMatchElement(element, { outletName }) {
1281
- return (this.hasOutlet(element, outletName) &&
1282
- 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
+ }
1283
1375
  }
1284
1376
  connectOutlet(outlet, element, outletName) {
1285
1377
  var _a;
@@ -1307,9 +1399,33 @@ class OutletObserver {
1307
1399
  }
1308
1400
  }
1309
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
+ }
1310
1420
  selector(outletName) {
1311
1421
  return this.scope.outlets.getSelectorForOutletName(outletName);
1312
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
+ }
1313
1429
  get outletDependencies() {
1314
1430
  const dependencies = new Multimap();
1315
1431
  this.router.modules.forEach((module) => {
@@ -1341,6 +1457,9 @@ class OutletObserver {
1341
1457
  get scope() {
1342
1458
  return this.context.scope;
1343
1459
  }
1460
+ get schema() {
1461
+ return this.context.schema;
1462
+ }
1344
1463
  get identifier() {
1345
1464
  return this.context.identifier;
1346
1465
  }
@@ -1828,6 +1947,9 @@ class ScopeObserver {
1828
1947
  }
1829
1948
  parseValueForToken(token) {
1830
1949
  const { element, content: identifier } = token;
1950
+ return this.parseValueForElementAndIdentifier(element, identifier);
1951
+ }
1952
+ parseValueForElementAndIdentifier(element, identifier) {
1831
1953
  const scopesByIdentifier = this.fetchScopesByIdentifierForElement(element);
1832
1954
  let scope = scopesByIdentifier.get(identifier);
1833
1955
  if (!scope) {
@@ -1899,7 +2021,7 @@ class Router {
1899
2021
  this.connectModule(module);
1900
2022
  const afterLoad = definition.controllerConstructor.afterLoad;
1901
2023
  if (afterLoad) {
1902
- afterLoad(definition.identifier, this.application);
2024
+ afterLoad.call(definition.controllerConstructor, definition.identifier, this.application);
1903
2025
  }
1904
2026
  }
1905
2027
  unloadIdentifier(identifier) {
@@ -1914,6 +2036,15 @@ class Router {
1914
2036
  return module.contexts.find((context) => context.element == element);
1915
2037
  }
1916
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
+ }
1917
2048
  handleError(error, message, detail) {
1918
2049
  this.application.handleError(error, message, detail);
1919
2050
  }
@@ -1952,7 +2083,7 @@ const defaultSchema = {
1952
2083
  targetAttribute: "data-target",
1953
2084
  targetAttributeForScope: (identifier) => `data-${identifier}-target`,
1954
2085
  outletAttributeForScope: (identifier, outlet) => `data-${identifier}-${outlet}-outlet`,
1955
- 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]))),
1956
2087
  };
1957
2088
  function objectFromEntries(array) {
1958
2089
  return array.reduce((memo, [k, v]) => (Object.assign(Object.assign({}, memo), { [k]: v })), {});
@@ -2078,22 +2209,32 @@ function OutletPropertiesBlessing(constructor) {
2078
2209
  return Object.assign(properties, propertiesForOutletDefinition(outletDefinition));
2079
2210
  }, {});
2080
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
+ }
2081
2224
  function propertiesForOutletDefinition(name) {
2082
2225
  const camelizedName = namespaceCamelize(name);
2083
2226
  return {
2084
2227
  [`${camelizedName}Outlet`]: {
2085
2228
  get() {
2086
- const outlet = this.outlets.find(name);
2087
- if (outlet) {
2088
- const outletController = this.application.getControllerForElementAndIdentifier(outlet, name);
2089
- 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)
2090
2234
  return outletController;
2091
- }
2092
- else {
2093
- throw new Error(`Missing "data-controller=${name}" attribute on outlet element for "${this.identifier}" controller`);
2094
- }
2235
+ throw new Error(`The provided outlet element is missing an outlet controller "${name}" instance for host controller "${this.identifier}"`);
2095
2236
  }
2096
- 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}".`);
2097
2238
  },
2098
2239
  },
2099
2240
  [`${camelizedName}Outlets`]: {
@@ -2101,14 +2242,11 @@ function propertiesForOutletDefinition(name) {
2101
2242
  const outlets = this.outlets.findAll(name);
2102
2243
  if (outlets.length > 0) {
2103
2244
  return outlets
2104
- .map((outlet) => {
2105
- const controller = this.application.getControllerForElementAndIdentifier(outlet, name);
2106
- if (controller) {
2107
- return controller;
2108
- }
2109
- else {
2110
- console.warn(`The provided outlet element is missing the outlet controller "${name}" for "${this.identifier}"`, outlet);
2111
- }
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);
2112
2250
  })
2113
2251
  .filter((controller) => controller);
2114
2252
  }
@@ -2117,12 +2255,13 @@ function propertiesForOutletDefinition(name) {
2117
2255
  },
2118
2256
  [`${camelizedName}OutletElement`]: {
2119
2257
  get() {
2120
- const outlet = this.outlets.find(name);
2121
- if (outlet) {
2122
- return outlet;
2258
+ const outletElement = this.outlets.find(name);
2259
+ const selector = this.outlets.getSelectorForOutletName(name);
2260
+ if (outletElement) {
2261
+ return outletElement;
2123
2262
  }
2124
2263
  else {
2125
- 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}".`);
2126
2265
  }
2127
2266
  },
2128
2267
  },
@@ -2254,51 +2393,67 @@ function parseValueTypeDefault(defaultValue) {
2254
2393
  return "object";
2255
2394
  }
2256
2395
  function parseValueTypeObject(payload) {
2257
- const typeFromObject = parseValueTypeConstant(payload.typeObject.type);
2258
- if (!typeFromObject)
2259
- return;
2260
- const defaultValueType = parseValueTypeDefault(payload.typeObject.default);
2261
- if (typeFromObject !== defaultValueType) {
2262
- const propertyPath = payload.controller ? `${payload.controller}.${payload.token}` : payload.token;
2263
- 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}".`);
2264
- }
2265
- 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;
2266
2414
  }
2267
2415
  function parseValueTypeDefinition(payload) {
2268
- const typeFromObject = parseValueTypeObject({
2269
- controller: payload.controller,
2270
- token: payload.token,
2271
- typeObject: payload.typeDefinition,
2272
- });
2273
- const typeFromDefaultValue = parseValueTypeDefault(payload.typeDefinition);
2274
- 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);
2275
2421
  const type = typeFromObject || typeFromDefaultValue || typeFromConstant;
2276
2422
  if (type)
2277
2423
  return type;
2278
- const propertyPath = payload.controller ? `${payload.controller}.${payload.typeDefinition}` : payload.token;
2279
- 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`);
2280
2426
  }
2281
2427
  function defaultValueForDefinition(typeDefinition) {
2282
2428
  const constant = parseValueTypeConstant(typeDefinition);
2283
2429
  if (constant)
2284
2430
  return defaultValuesByType[constant];
2285
- const defaultValue = typeDefinition.default;
2286
- if (defaultValue !== undefined)
2287
- 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
+ }
2288
2442
  return typeDefinition;
2289
2443
  }
2290
2444
  function valueDescriptorForTokenAndTypeDefinition(payload) {
2291
- const key = `${dasherize(payload.token)}-value`;
2445
+ const { token, typeDefinition } = payload;
2446
+ const key = `${dasherize(token)}-value`;
2292
2447
  const type = parseValueTypeDefinition(payload);
2293
2448
  return {
2294
2449
  type,
2295
2450
  key,
2296
2451
  name: camelize(key),
2297
2452
  get defaultValue() {
2298
- return defaultValueForDefinition(payload.typeDefinition);
2453
+ return defaultValueForDefinition(typeDefinition);
2299
2454
  },
2300
2455
  get hasCustomDefaultValue() {
2301
- return parseValueTypeDefault(payload.typeDefinition) !== undefined;
2456
+ return parseValueTypeDefault(typeDefinition) !== undefined;
2302
2457
  },
2303
2458
  reader: readers[type],
2304
2459
  writer: writers[type] || writers.default,
@@ -2327,7 +2482,7 @@ const readers = {
2327
2482
  return !(value == "0" || String(value).toLowerCase() == "false");
2328
2483
  },
2329
2484
  number(value) {
2330
- return Number(value);
2485
+ return Number(value.replace(/_/g, ""));
2331
2486
  },
2332
2487
  object(value) {
2333
2488
  const object = JSON.parse(value);
@@ -2392,7 +2547,7 @@ class Controller {
2392
2547
  }
2393
2548
  disconnect() {
2394
2549
  }
2395
- 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, } = {}) {
2396
2551
  const type = prefix ? `${prefix}:${eventName}` : eventName;
2397
2552
  const event = new CustomEvent(type, { detail, bubbles, cancelable });
2398
2553
  target.dispatchEvent(event);