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