@stackoverflow/stacks 2.0.0-rc.0 → 2.0.0-rc.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.
Files changed (47) hide show
  1. package/README.md +5 -0
  2. package/dist/css/stacks.css +582 -532
  3. package/dist/css/stacks.min.css +1 -1
  4. package/dist/js/stacks.js +265 -110
  5. package/dist/js/stacks.min.js +1 -1
  6. package/lib/atomic/typography.less +4 -0
  7. package/lib/components/activity-indicator/activity-indicator.a11y.test.ts +1 -1
  8. package/lib/components/activity-indicator/activity-indicator.less +17 -4
  9. package/lib/components/anchor/anchor.visual.test.ts +1 -5
  10. package/lib/components/avatar/avatar.visual.test.ts +1 -4
  11. package/lib/components/badge/badge.a11y.test.ts +2 -2
  12. package/lib/components/badge/badge.less +15 -23
  13. package/lib/components/block-link/block-link.less +5 -4
  14. package/lib/components/button/button.a11y.test.ts +2 -5
  15. package/lib/components/button/button.less +28 -58
  16. package/lib/components/button/button.visual.test.ts +2 -5
  17. package/lib/components/card/card.less +8 -0
  18. package/lib/components/description/description.a11y.test.ts +1 -0
  19. package/lib/components/expandable/expandable.a11y.test.ts +27 -0
  20. package/lib/components/expandable/expandable.visual.test.ts +27 -0
  21. package/lib/components/input-icon/input-icon.less +1 -1
  22. package/lib/components/input-message/input-message.less +4 -3
  23. package/lib/components/label/label.less +3 -13
  24. package/lib/components/link-preview/link-preview.a11y.test.ts +48 -0
  25. package/lib/components/link-preview/link-preview.less +13 -4
  26. package/lib/components/link-preview/link-preview.visual.test.ts +52 -0
  27. package/lib/components/notice/notice.a11y.test.ts +17 -0
  28. package/lib/components/notice/notice.less +44 -81
  29. package/lib/components/notice/notice.visual.test.ts +26 -0
  30. package/lib/components/pagination/pagination.a11y.test.ts +20 -0
  31. package/lib/components/pagination/pagination.visual.test.ts +26 -0
  32. package/lib/components/post-summary/post-summary.less +3 -3
  33. package/lib/components/sidebar-widget/sidebar-widget.less +2 -2
  34. package/lib/components/spinner/spinner.a11y.test.ts +15 -0
  35. package/lib/components/spinner/spinner.visual.test.ts +43 -0
  36. package/lib/components/toast/toast.a11y.test.ts +30 -0
  37. package/lib/components/toast/toast.visual.test.ts +10 -6
  38. package/lib/components/toggle-switch/toggle-switch.less +2 -5
  39. package/lib/components/uploader/uploader.less +19 -13
  40. package/lib/exports/color-sets.less +127 -78
  41. package/lib/exports/theme.less +3 -3
  42. package/lib/input-utils.less +1 -1
  43. package/lib/test/axe-apca/README.md +19 -0
  44. package/lib/test/axe-apca/src/axe-apca.test.ts +77 -1
  45. package/lib/test/axe-apca/src/axe-apca.ts +16 -8
  46. package/lib/test/test-utils.ts +7 -3
  47. package/package.json +12 -12
package/dist/js/stacks.js CHANGED
@@ -81,7 +81,7 @@ __webpack_require__.d(__webpack_exports__, {
81
81
  ;// CONCATENATED MODULE: ./node_modules/@hotwired/stimulus/dist/stimulus.js
82
82
  /*
83
83
  Stimulus 3.2.1
84
- Copyright © 2022 Basecamp, LLC
84
+ Copyright © 2023 Basecamp, LLC
85
85
  */
86
86
  class EventListener {
87
87
  constructor(eventTarget, eventName, eventOptions) {
@@ -246,23 +246,23 @@ const defaultActionDescriptorFilters = {
246
246
  }
247
247
  },
248
248
  };
249
- const descriptorPattern = /^(?:(.+?)(?:\.(.+?))?(?:@(window|document))?->)?(.+?)(?:#([^:]+?))(?::(.+))?$/;
249
+ const descriptorPattern = /^(?:(?:([^.]+?)\+)?(.+?)(?:\.(.+?))?(?:@(window|document))?->)?(.+?)(?:#([^:]+?))(?::(.+))?$/;
250
250
  function parseActionDescriptorString(descriptorString) {
251
251
  const source = descriptorString.trim();
252
252
  const matches = source.match(descriptorPattern) || [];
253
- let eventName = matches[1];
254
- let keyFilter = matches[2];
253
+ let eventName = matches[2];
254
+ let keyFilter = matches[3];
255
255
  if (keyFilter && !["keydown", "keyup", "keypress"].includes(eventName)) {
256
256
  eventName += `.${keyFilter}`;
257
257
  keyFilter = "";
258
258
  }
259
259
  return {
260
- eventTarget: parseEventTarget(matches[3]),
260
+ eventTarget: parseEventTarget(matches[4]),
261
261
  eventName,
262
- eventOptions: matches[6] ? parseEventOptions(matches[6]) : {},
263
- identifier: matches[4],
264
- methodName: matches[5],
265
- keyFilter,
262
+ eventOptions: matches[7] ? parseEventOptions(matches[7]) : {},
263
+ identifier: matches[5],
264
+ methodName: matches[6],
265
+ keyFilter: matches[1] || keyFilter,
266
266
  };
267
267
  }
268
268
  function parseEventTarget(eventTargetName) {
@@ -303,6 +303,14 @@ function tokenize(value) {
303
303
  return value.match(/[^\s]+/g) || [];
304
304
  }
305
305
 
306
+ function isSomething(object) {
307
+ return object !== null && object !== undefined;
308
+ }
309
+ function hasProperty(object, property) {
310
+ return Object.prototype.hasOwnProperty.call(object, property);
311
+ }
312
+
313
+ const allModifiers = ["meta", "ctrl", "alt", "shift"];
306
314
  class Action {
307
315
  constructor(element, index, descriptor, schema) {
308
316
  this.element = element;
@@ -323,25 +331,33 @@ class Action {
323
331
  const eventTarget = this.eventTargetName ? `@${this.eventTargetName}` : "";
324
332
  return `${this.eventName}${eventFilter}${eventTarget}->${this.identifier}#${this.methodName}`;
325
333
  }
326
- isFilterTarget(event) {
334
+ shouldIgnoreKeyboardEvent(event) {
327
335
  if (!this.keyFilter) {
328
336
  return false;
329
337
  }
330
- const filteres = this.keyFilter.split("+");
331
- const modifiers = ["meta", "ctrl", "alt", "shift"];
332
- const [meta, ctrl, alt, shift] = modifiers.map((modifier) => filteres.includes(modifier));
333
- if (event.metaKey !== meta || event.ctrlKey !== ctrl || event.altKey !== alt || event.shiftKey !== shift) {
338
+ const filters = this.keyFilter.split("+");
339
+ if (this.keyFilterDissatisfied(event, filters)) {
334
340
  return true;
335
341
  }
336
- const standardFilter = filteres.filter((key) => !modifiers.includes(key))[0];
342
+ const standardFilter = filters.filter((key) => !allModifiers.includes(key))[0];
337
343
  if (!standardFilter) {
338
344
  return false;
339
345
  }
340
- if (!Object.prototype.hasOwnProperty.call(this.keyMappings, standardFilter)) {
346
+ if (!hasProperty(this.keyMappings, standardFilter)) {
341
347
  error(`contains unknown key filter: ${this.keyFilter}`);
342
348
  }
343
349
  return this.keyMappings[standardFilter].toLowerCase() !== event.key.toLowerCase();
344
350
  }
351
+ shouldIgnoreMouseEvent(event) {
352
+ if (!this.keyFilter) {
353
+ return false;
354
+ }
355
+ const filters = [this.keyFilter];
356
+ if (this.keyFilterDissatisfied(event, filters)) {
357
+ return true;
358
+ }
359
+ return false;
360
+ }
345
361
  get params() {
346
362
  const params = {};
347
363
  const pattern = new RegExp(`^data-${this.identifier}-(.+)-param$`, "i");
@@ -360,6 +376,10 @@ class Action {
360
376
  get keyMappings() {
361
377
  return this.schema.keyMappings;
362
378
  }
379
+ keyFilterDissatisfied(event, filters) {
380
+ const [meta, ctrl, alt, shift] = allModifiers.map((modifier) => filters.includes(modifier));
381
+ return event.metaKey !== meta || event.ctrlKey !== ctrl || event.altKey !== alt || event.shiftKey !== shift;
382
+ }
363
383
  }
364
384
  const defaultEventNames = {
365
385
  a: () => "click",
@@ -406,8 +426,9 @@ class Binding {
406
426
  return this.context.identifier;
407
427
  }
408
428
  handleEvent(event) {
409
- if (this.willBeInvokedByEvent(event) && this.applyEventModifiers(event)) {
410
- this.invokeWithEvent(event);
429
+ const actionEvent = this.prepareActionEvent(event);
430
+ if (this.willBeInvokedByEvent(event) && this.applyEventModifiers(actionEvent)) {
431
+ this.invokeWithEvent(actionEvent);
411
432
  }
412
433
  }
413
434
  get eventName() {
@@ -423,11 +444,12 @@ class Binding {
423
444
  applyEventModifiers(event) {
424
445
  const { element } = this.action;
425
446
  const { actionDescriptorFilters } = this.context.application;
447
+ const { controller } = this.context;
426
448
  let passes = true;
427
449
  for (const [name, value] of Object.entries(this.eventOptions)) {
428
450
  if (name in actionDescriptorFilters) {
429
451
  const filter = actionDescriptorFilters[name];
430
- passes = passes && filter({ name, value, event, element });
452
+ passes = passes && filter({ name, value, event, element, controller });
431
453
  }
432
454
  else {
433
455
  continue;
@@ -435,12 +457,13 @@ class Binding {
435
457
  }
436
458
  return passes;
437
459
  }
460
+ prepareActionEvent(event) {
461
+ return Object.assign(event, { params: this.action.params });
462
+ }
438
463
  invokeWithEvent(event) {
439
464
  const { target, currentTarget } = event;
440
465
  try {
441
- const { params } = this.action;
442
- const actionEvent = Object.assign(event, { params });
443
- this.method.call(this.controller, actionEvent);
466
+ this.method.call(this.controller, event);
444
467
  this.context.logDebugActivity(this.methodName, { event, target, currentTarget, action: this.methodName });
445
468
  }
446
469
  catch (error) {
@@ -451,7 +474,10 @@ class Binding {
451
474
  }
452
475
  willBeInvokedByEvent(event) {
453
476
  const eventTarget = event.target;
454
- if (event instanceof KeyboardEvent && this.action.isFilterTarget(event)) {
477
+ if (event instanceof KeyboardEvent && this.action.shouldIgnoreKeyboardEvent(event)) {
478
+ return false;
479
+ }
480
+ if (event instanceof MouseEvent && this.action.shouldIgnoreMouseEvent(event)) {
455
481
  return false;
456
482
  }
457
483
  if (this.element === eventTarget) {
@@ -541,8 +567,7 @@ class ElementObserver {
541
567
  this.processAddedNodes(mutation.addedNodes);
542
568
  }
543
569
  }
544
- processAttributeChange(node, attributeName) {
545
- const element = node;
570
+ processAttributeChange(element, attributeName) {
546
571
  if (this.elements.has(element)) {
547
572
  if (this.delegate.elementAttributeChanged && this.matchElement(element)) {
548
573
  this.delegate.elementAttributeChanged(element, attributeName);
@@ -758,8 +783,8 @@ class IndexedMultimap extends (/* unused pure expression or super */ null && (Mu
758
783
  }
759
784
 
760
785
  class SelectorObserver {
761
- constructor(element, selector, delegate, details = {}) {
762
- this.selector = selector;
786
+ constructor(element, selector, delegate, details) {
787
+ this._selector = selector;
763
788
  this.details = details;
764
789
  this.elementObserver = new ElementObserver(element, this);
765
790
  this.delegate = delegate;
@@ -768,6 +793,13 @@ class SelectorObserver {
768
793
  get started() {
769
794
  return this.elementObserver.started;
770
795
  }
796
+ get selector() {
797
+ return this._selector;
798
+ }
799
+ set selector(selector) {
800
+ this._selector = selector;
801
+ this.refresh();
802
+ }
771
803
  start() {
772
804
  this.elementObserver.start();
773
805
  }
@@ -784,39 +816,61 @@ class SelectorObserver {
784
816
  return this.elementObserver.element;
785
817
  }
786
818
  matchElement(element) {
787
- const matches = element.matches(this.selector);
788
- if (this.delegate.selectorMatchElement) {
789
- return matches && this.delegate.selectorMatchElement(element, this.details);
819
+ const { selector } = this;
820
+ if (selector) {
821
+ const matches = element.matches(selector);
822
+ if (this.delegate.selectorMatchElement) {
823
+ return matches && this.delegate.selectorMatchElement(element, this.details);
824
+ }
825
+ return matches;
826
+ }
827
+ else {
828
+ return false;
790
829
  }
791
- return matches;
792
830
  }
793
831
  matchElementsInTree(tree) {
794
- const match = this.matchElement(tree) ? [tree] : [];
795
- const matches = Array.from(tree.querySelectorAll(this.selector)).filter((match) => this.matchElement(match));
796
- return match.concat(matches);
832
+ const { selector } = this;
833
+ if (selector) {
834
+ const match = this.matchElement(tree) ? [tree] : [];
835
+ const matches = Array.from(tree.querySelectorAll(selector)).filter((match) => this.matchElement(match));
836
+ return match.concat(matches);
837
+ }
838
+ else {
839
+ return [];
840
+ }
797
841
  }
798
842
  elementMatched(element) {
799
- this.selectorMatched(element);
843
+ const { selector } = this;
844
+ if (selector) {
845
+ this.selectorMatched(element, selector);
846
+ }
800
847
  }
801
848
  elementUnmatched(element) {
802
- this.selectorUnmatched(element);
849
+ const selectors = this.matchesByElement.getKeysForValue(element);
850
+ for (const selector of selectors) {
851
+ this.selectorUnmatched(element, selector);
852
+ }
803
853
  }
804
854
  elementAttributeChanged(element, _attributeName) {
805
- const matches = this.matchElement(element);
806
- const matchedBefore = this.matchesByElement.has(this.selector, element);
807
- if (!matches && matchedBefore) {
808
- this.selectorUnmatched(element);
855
+ const { selector } = this;
856
+ if (selector) {
857
+ const matches = this.matchElement(element);
858
+ const matchedBefore = this.matchesByElement.has(selector, element);
859
+ if (matches && !matchedBefore) {
860
+ this.selectorMatched(element, selector);
861
+ }
862
+ else if (!matches && matchedBefore) {
863
+ this.selectorUnmatched(element, selector);
864
+ }
809
865
  }
810
866
  }
811
- selectorMatched(element) {
812
- if (this.delegate.selectorMatched) {
813
- this.delegate.selectorMatched(element, this.selector, this.details);
814
- this.matchesByElement.add(this.selector, element);
815
- }
867
+ selectorMatched(element, selector) {
868
+ this.delegate.selectorMatched(element, selector, this.details);
869
+ this.matchesByElement.add(selector, element);
816
870
  }
817
- selectorUnmatched(element) {
818
- this.delegate.selectorUnmatched(element, this.selector, this.details);
819
- this.matchesByElement.delete(this.selector, element);
871
+ selectorUnmatched(element, selector) {
872
+ this.delegate.selectorUnmatched(element, selector, this.details);
873
+ this.matchesByElement.delete(selector, element);
820
874
  }
821
875
  }
822
876
 
@@ -1313,34 +1367,47 @@ function getOwnStaticObjectPairs(constructor, propertyName) {
1313
1367
 
1314
1368
  class OutletObserver {
1315
1369
  constructor(context, delegate) {
1370
+ this.started = false;
1316
1371
  this.context = context;
1317
1372
  this.delegate = delegate;
1318
1373
  this.outletsByName = new Multimap();
1319
1374
  this.outletElementsByName = new Multimap();
1320
1375
  this.selectorObserverMap = new Map();
1376
+ this.attributeObserverMap = new Map();
1321
1377
  }
1322
1378
  start() {
1323
- if (this.selectorObserverMap.size === 0) {
1379
+ if (!this.started) {
1324
1380
  this.outletDefinitions.forEach((outletName) => {
1325
- const selector = this.selector(outletName);
1326
- const details = { outletName };
1327
- if (selector) {
1328
- this.selectorObserverMap.set(outletName, new SelectorObserver(document.body, selector, this, details));
1329
- }
1381
+ this.setupSelectorObserverForOutlet(outletName);
1382
+ this.setupAttributeObserverForOutlet(outletName);
1330
1383
  });
1331
- this.selectorObserverMap.forEach((observer) => observer.start());
1384
+ this.started = true;
1385
+ this.dependentContexts.forEach((context) => context.refresh());
1332
1386
  }
1333
- this.dependentContexts.forEach((context) => context.refresh());
1387
+ }
1388
+ refresh() {
1389
+ this.selectorObserverMap.forEach((observer) => observer.refresh());
1390
+ this.attributeObserverMap.forEach((observer) => observer.refresh());
1334
1391
  }
1335
1392
  stop() {
1336
- if (this.selectorObserverMap.size > 0) {
1393
+ if (this.started) {
1394
+ this.started = false;
1337
1395
  this.disconnectAllOutlets();
1396
+ this.stopSelectorObservers();
1397
+ this.stopAttributeObservers();
1398
+ }
1399
+ }
1400
+ stopSelectorObservers() {
1401
+ if (this.selectorObserverMap.size > 0) {
1338
1402
  this.selectorObserverMap.forEach((observer) => observer.stop());
1339
1403
  this.selectorObserverMap.clear();
1340
1404
  }
1341
1405
  }
1342
- refresh() {
1343
- this.selectorObserverMap.forEach((observer) => observer.refresh());
1406
+ stopAttributeObservers() {
1407
+ if (this.attributeObserverMap.size > 0) {
1408
+ this.attributeObserverMap.forEach((observer) => observer.stop());
1409
+ this.attributeObserverMap.clear();
1410
+ }
1344
1411
  }
1345
1412
  selectorMatched(element, _selector, { outletName }) {
1346
1413
  const outlet = this.getOutlet(element, outletName);
@@ -1355,8 +1422,33 @@ class OutletObserver {
1355
1422
  }
1356
1423
  }
1357
1424
  selectorMatchElement(element, { outletName }) {
1358
- return (this.hasOutlet(element, outletName) &&
1359
- element.matches(`[${this.context.application.schema.controllerAttribute}~=${outletName}]`));
1425
+ const selector = this.selector(outletName);
1426
+ const hasOutlet = this.hasOutlet(element, outletName);
1427
+ const hasOutletController = element.matches(`[${this.schema.controllerAttribute}~=${outletName}]`);
1428
+ if (selector) {
1429
+ return hasOutlet && hasOutletController && element.matches(selector);
1430
+ }
1431
+ else {
1432
+ return false;
1433
+ }
1434
+ }
1435
+ elementMatchedAttribute(_element, attributeName) {
1436
+ const outletName = this.getOutletNameFromOutletAttributeName(attributeName);
1437
+ if (outletName) {
1438
+ this.updateSelectorObserverForOutlet(outletName);
1439
+ }
1440
+ }
1441
+ elementAttributeValueChanged(_element, attributeName) {
1442
+ const outletName = this.getOutletNameFromOutletAttributeName(attributeName);
1443
+ if (outletName) {
1444
+ this.updateSelectorObserverForOutlet(outletName);
1445
+ }
1446
+ }
1447
+ elementUnmatchedAttribute(_element, attributeName) {
1448
+ const outletName = this.getOutletNameFromOutletAttributeName(attributeName);
1449
+ if (outletName) {
1450
+ this.updateSelectorObserverForOutlet(outletName);
1451
+ }
1360
1452
  }
1361
1453
  connectOutlet(outlet, element, outletName) {
1362
1454
  var _a;
@@ -1384,9 +1476,33 @@ class OutletObserver {
1384
1476
  }
1385
1477
  }
1386
1478
  }
1479
+ updateSelectorObserverForOutlet(outletName) {
1480
+ const observer = this.selectorObserverMap.get(outletName);
1481
+ if (observer) {
1482
+ observer.selector = this.selector(outletName);
1483
+ }
1484
+ }
1485
+ setupSelectorObserverForOutlet(outletName) {
1486
+ const selector = this.selector(outletName);
1487
+ const selectorObserver = new SelectorObserver(document.body, selector, this, { outletName });
1488
+ this.selectorObserverMap.set(outletName, selectorObserver);
1489
+ selectorObserver.start();
1490
+ }
1491
+ setupAttributeObserverForOutlet(outletName) {
1492
+ const attributeName = this.attributeNameForOutletName(outletName);
1493
+ const attributeObserver = new AttributeObserver(this.scope.element, attributeName, this);
1494
+ this.attributeObserverMap.set(outletName, attributeObserver);
1495
+ attributeObserver.start();
1496
+ }
1387
1497
  selector(outletName) {
1388
1498
  return this.scope.outlets.getSelectorForOutletName(outletName);
1389
1499
  }
1500
+ attributeNameForOutletName(outletName) {
1501
+ return this.scope.schema.outletAttributeForScope(this.identifier, outletName);
1502
+ }
1503
+ getOutletNameFromOutletAttributeName(attributeName) {
1504
+ return this.outletDefinitions.find((outletName) => this.attributeNameForOutletName(outletName) === attributeName);
1505
+ }
1390
1506
  get outletDependencies() {
1391
1507
  const dependencies = new Multimap();
1392
1508
  this.router.modules.forEach((module) => {
@@ -1418,6 +1534,9 @@ class OutletObserver {
1418
1534
  get scope() {
1419
1535
  return this.context.scope;
1420
1536
  }
1537
+ get schema() {
1538
+ return this.context.schema;
1539
+ }
1421
1540
  get identifier() {
1422
1541
  return this.context.identifier;
1423
1542
  }
@@ -1905,6 +2024,9 @@ class ScopeObserver {
1905
2024
  }
1906
2025
  parseValueForToken(token) {
1907
2026
  const { element, content: identifier } = token;
2027
+ return this.parseValueForElementAndIdentifier(element, identifier);
2028
+ }
2029
+ parseValueForElementAndIdentifier(element, identifier) {
1908
2030
  const scopesByIdentifier = this.fetchScopesByIdentifierForElement(element);
1909
2031
  let scope = scopesByIdentifier.get(identifier);
1910
2032
  if (!scope) {
@@ -1976,7 +2098,7 @@ class Router {
1976
2098
  this.connectModule(module);
1977
2099
  const afterLoad = definition.controllerConstructor.afterLoad;
1978
2100
  if (afterLoad) {
1979
- afterLoad(definition.identifier, this.application);
2101
+ afterLoad.call(definition.controllerConstructor, definition.identifier, this.application);
1980
2102
  }
1981
2103
  }
1982
2104
  unloadIdentifier(identifier) {
@@ -1991,6 +2113,15 @@ class Router {
1991
2113
  return module.contexts.find((context) => context.element == element);
1992
2114
  }
1993
2115
  }
2116
+ proposeToConnectScopeForElementAndIdentifier(element, identifier) {
2117
+ const scope = this.scopeObserver.parseValueForElementAndIdentifier(element, identifier);
2118
+ if (scope) {
2119
+ this.scopeObserver.elementMatchedValue(scope.element, scope);
2120
+ }
2121
+ else {
2122
+ console.error(`Couldn't find or create scope for identifier: "${identifier}" and element:`, element);
2123
+ }
2124
+ }
1994
2125
  handleError(error, message, detail) {
1995
2126
  this.application.handleError(error, message, detail);
1996
2127
  }
@@ -2029,7 +2160,7 @@ const defaultSchema = {
2029
2160
  targetAttribute: "data-target",
2030
2161
  targetAttributeForScope: (identifier) => `data-${identifier}-target`,
2031
2162
  outletAttributeForScope: (identifier, outlet) => `data-${identifier}-${outlet}-outlet`,
2032
- 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]))),
2163
+ 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]))),
2033
2164
  };
2034
2165
  function objectFromEntries(array) {
2035
2166
  return array.reduce((memo, [k, v]) => (Object.assign(Object.assign({}, memo), { [k]: v })), {});
@@ -2155,22 +2286,32 @@ function OutletPropertiesBlessing(constructor) {
2155
2286
  return Object.assign(properties, propertiesForOutletDefinition(outletDefinition));
2156
2287
  }, {});
2157
2288
  }
2289
+ function getOutletController(controller, element, identifier) {
2290
+ return controller.application.getControllerForElementAndIdentifier(element, identifier);
2291
+ }
2292
+ function getControllerAndEnsureConnectedScope(controller, element, outletName) {
2293
+ let outletController = getOutletController(controller, element, outletName);
2294
+ if (outletController)
2295
+ return outletController;
2296
+ controller.application.router.proposeToConnectScopeForElementAndIdentifier(element, outletName);
2297
+ outletController = getOutletController(controller, element, outletName);
2298
+ if (outletController)
2299
+ return outletController;
2300
+ }
2158
2301
  function propertiesForOutletDefinition(name) {
2159
2302
  const camelizedName = namespaceCamelize(name);
2160
2303
  return {
2161
2304
  [`${camelizedName}Outlet`]: {
2162
2305
  get() {
2163
- const outlet = this.outlets.find(name);
2164
- if (outlet) {
2165
- const outletController = this.application.getControllerForElementAndIdentifier(outlet, name);
2166
- if (outletController) {
2306
+ const outletElement = this.outlets.find(name);
2307
+ const selector = this.outlets.getSelectorForOutletName(name);
2308
+ if (outletElement) {
2309
+ const outletController = getControllerAndEnsureConnectedScope(this, outletElement, name);
2310
+ if (outletController)
2167
2311
  return outletController;
2168
- }
2169
- else {
2170
- throw new Error(`Missing "data-controller=${name}" attribute on outlet element for "${this.identifier}" controller`);
2171
- }
2312
+ throw new Error(`The provided outlet element is missing an outlet controller "${name}" instance for host controller "${this.identifier}"`);
2172
2313
  }
2173
- throw new Error(`Missing outlet element "${name}" for "${this.identifier}" controller`);
2314
+ throw new Error(`Missing outlet element "${name}" for host controller "${this.identifier}". Stimulus couldn't find a matching outlet element using selector "${selector}".`);
2174
2315
  },
2175
2316
  },
2176
2317
  [`${camelizedName}Outlets`]: {
@@ -2178,14 +2319,11 @@ function propertiesForOutletDefinition(name) {
2178
2319
  const outlets = this.outlets.findAll(name);
2179
2320
  if (outlets.length > 0) {
2180
2321
  return outlets
2181
- .map((outlet) => {
2182
- const controller = this.application.getControllerForElementAndIdentifier(outlet, name);
2183
- if (controller) {
2184
- return controller;
2185
- }
2186
- else {
2187
- console.warn(`The provided outlet element is missing the outlet controller "${name}" for "${this.identifier}"`, outlet);
2188
- }
2322
+ .map((outletElement) => {
2323
+ const outletController = getControllerAndEnsureConnectedScope(this, outletElement, name);
2324
+ if (outletController)
2325
+ return outletController;
2326
+ console.warn(`The provided outlet element is missing an outlet controller "${name}" instance for host controller "${this.identifier}"`, outletElement);
2189
2327
  })
2190
2328
  .filter((controller) => controller);
2191
2329
  }
@@ -2194,12 +2332,13 @@ function propertiesForOutletDefinition(name) {
2194
2332
  },
2195
2333
  [`${camelizedName}OutletElement`]: {
2196
2334
  get() {
2197
- const outlet = this.outlets.find(name);
2198
- if (outlet) {
2199
- return outlet;
2335
+ const outletElement = this.outlets.find(name);
2336
+ const selector = this.outlets.getSelectorForOutletName(name);
2337
+ if (outletElement) {
2338
+ return outletElement;
2200
2339
  }
2201
2340
  else {
2202
- throw new Error(`Missing outlet element "${name}" for "${this.identifier}" controller`);
2341
+ throw new Error(`Missing outlet element "${name}" for host controller "${this.identifier}". Stimulus couldn't find a matching outlet element using selector "${selector}".`);
2203
2342
  }
2204
2343
  },
2205
2344
  },
@@ -2331,51 +2470,67 @@ function parseValueTypeDefault(defaultValue) {
2331
2470
  return "object";
2332
2471
  }
2333
2472
  function parseValueTypeObject(payload) {
2334
- const typeFromObject = parseValueTypeConstant(payload.typeObject.type);
2335
- if (!typeFromObject)
2336
- return;
2337
- const defaultValueType = parseValueTypeDefault(payload.typeObject.default);
2338
- if (typeFromObject !== defaultValueType) {
2339
- const propertyPath = payload.controller ? `${payload.controller}.${payload.token}` : payload.token;
2340
- 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}".`);
2341
- }
2342
- return typeFromObject;
2473
+ const { controller, token, typeObject } = payload;
2474
+ const hasType = isSomething(typeObject.type);
2475
+ const hasDefault = isSomething(typeObject.default);
2476
+ const fullObject = hasType && hasDefault;
2477
+ const onlyType = hasType && !hasDefault;
2478
+ const onlyDefault = !hasType && hasDefault;
2479
+ const typeFromObject = parseValueTypeConstant(typeObject.type);
2480
+ const typeFromDefaultValue = parseValueTypeDefault(payload.typeObject.default);
2481
+ if (onlyType)
2482
+ return typeFromObject;
2483
+ if (onlyDefault)
2484
+ return typeFromDefaultValue;
2485
+ if (typeFromObject !== typeFromDefaultValue) {
2486
+ const propertyPath = controller ? `${controller}.${token}` : token;
2487
+ 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}".`);
2488
+ }
2489
+ if (fullObject)
2490
+ return typeFromObject;
2343
2491
  }
2344
2492
  function parseValueTypeDefinition(payload) {
2345
- const typeFromObject = parseValueTypeObject({
2346
- controller: payload.controller,
2347
- token: payload.token,
2348
- typeObject: payload.typeDefinition,
2349
- });
2350
- const typeFromDefaultValue = parseValueTypeDefault(payload.typeDefinition);
2351
- const typeFromConstant = parseValueTypeConstant(payload.typeDefinition);
2493
+ const { controller, token, typeDefinition } = payload;
2494
+ const typeObject = { controller, token, typeObject: typeDefinition };
2495
+ const typeFromObject = parseValueTypeObject(typeObject);
2496
+ const typeFromDefaultValue = parseValueTypeDefault(typeDefinition);
2497
+ const typeFromConstant = parseValueTypeConstant(typeDefinition);
2352
2498
  const type = typeFromObject || typeFromDefaultValue || typeFromConstant;
2353
2499
  if (type)
2354
2500
  return type;
2355
- const propertyPath = payload.controller ? `${payload.controller}.${payload.typeDefinition}` : payload.token;
2356
- throw new Error(`Unknown value type "${propertyPath}" for "${payload.token}" value`);
2501
+ const propertyPath = controller ? `${controller}.${typeDefinition}` : token;
2502
+ throw new Error(`Unknown value type "${propertyPath}" for "${token}" value`);
2357
2503
  }
2358
2504
  function defaultValueForDefinition(typeDefinition) {
2359
2505
  const constant = parseValueTypeConstant(typeDefinition);
2360
2506
  if (constant)
2361
2507
  return defaultValuesByType[constant];
2362
- const defaultValue = typeDefinition.default;
2363
- if (defaultValue !== undefined)
2364
- return defaultValue;
2508
+ const hasDefault = hasProperty(typeDefinition, "default");
2509
+ const hasType = hasProperty(typeDefinition, "type");
2510
+ const typeObject = typeDefinition;
2511
+ if (hasDefault)
2512
+ return typeObject.default;
2513
+ if (hasType) {
2514
+ const { type } = typeObject;
2515
+ const constantFromType = parseValueTypeConstant(type);
2516
+ if (constantFromType)
2517
+ return defaultValuesByType[constantFromType];
2518
+ }
2365
2519
  return typeDefinition;
2366
2520
  }
2367
2521
  function valueDescriptorForTokenAndTypeDefinition(payload) {
2368
- const key = `${dasherize(payload.token)}-value`;
2522
+ const { token, typeDefinition } = payload;
2523
+ const key = `${dasherize(token)}-value`;
2369
2524
  const type = parseValueTypeDefinition(payload);
2370
2525
  return {
2371
2526
  type,
2372
2527
  key,
2373
2528
  name: camelize(key),
2374
2529
  get defaultValue() {
2375
- return defaultValueForDefinition(payload.typeDefinition);
2530
+ return defaultValueForDefinition(typeDefinition);
2376
2531
  },
2377
2532
  get hasCustomDefaultValue() {
2378
- return parseValueTypeDefault(payload.typeDefinition) !== undefined;
2533
+ return parseValueTypeDefault(typeDefinition) !== undefined;
2379
2534
  },
2380
2535
  reader: readers[type],
2381
2536
  writer: writers[type] || writers.default,
@@ -2404,7 +2559,7 @@ const readers = {
2404
2559
  return !(value == "0" || String(value).toLowerCase() == "false");
2405
2560
  },
2406
2561
  number(value) {
2407
- return Number(value);
2562
+ return Number(value.replace(/_/g, ""));
2408
2563
  },
2409
2564
  object(value) {
2410
2565
  const object = JSON.parse(value);
@@ -2469,7 +2624,7 @@ class Controller {
2469
2624
  }
2470
2625
  disconnect() {
2471
2626
  }
2472
- dispatch(eventName, { target = this.element, detail = {}, prefix = this.identifier, bubbles = true, cancelable = true } = {}) {
2627
+ dispatch(eventName, { target = this.element, detail = {}, prefix = this.identifier, bubbles = true, cancelable = true, } = {}) {
2473
2628
  const type = prefix ? `${prefix}:${eventName}` : eventName;
2474
2629
  const event = new CustomEvent(type, { detail, bubbles, cancelable });
2475
2630
  target.dispatchEvent(event);