stimulus-rails 1.2.0 → 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.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);