stimulus-rails 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,7 +3,7 @@
3
3
  //= link ./stimulus-loading.js
4
4
 
5
5
  /*
6
- Stimulus 3.1.1
6
+ Stimulus 3.2.0
7
7
  Copyright © 2022 Basecamp, LLC
8
8
  */
9
9
  class EventListener {
@@ -169,16 +169,17 @@ 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
176
  return {
177
- eventTarget: parseEventTarget(matches[4]),
178
- eventName: matches[2],
179
- eventOptions: matches[9] ? parseEventOptions(matches[9]) : {},
180
- identifier: matches[5],
181
- methodName: matches[7],
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],
182
183
  };
183
184
  }
184
185
  function parseEventTarget(eventTargetName) {
@@ -206,6 +207,9 @@ function stringifyEventTarget(eventTarget) {
206
207
  function camelize(value) {
207
208
  return value.replace(/(?:[_-])([a-z0-9])/g, (_, char) => char.toUpperCase());
208
209
  }
210
+ function namespaceCamelize(value) {
211
+ return camelize(value.replace(/--/g, "-").replace(/__/g, "_"));
212
+ }
209
213
  function capitalize(value) {
210
214
  return value.charAt(0).toUpperCase() + value.slice(1);
211
215
  }
@@ -217,7 +221,7 @@ function tokenize(value) {
217
221
  }
218
222
 
219
223
  class Action {
220
- constructor(element, index, descriptor) {
224
+ constructor(element, index, descriptor, schema) {
221
225
  this.element = element;
222
226
  this.index = index;
223
227
  this.eventTarget = descriptor.eventTarget || element;
@@ -225,13 +229,35 @@ class Action {
225
229
  this.eventOptions = descriptor.eventOptions || {};
226
230
  this.identifier = descriptor.identifier || error("missing identifier");
227
231
  this.methodName = descriptor.methodName || error("missing method name");
232
+ this.keyFilter = descriptor.keyFilter || "";
233
+ this.schema = schema;
228
234
  }
229
- static forToken(token) {
230
- return new this(token.element, token.index, parseActionDescriptorString(token.content));
235
+ static forToken(token, schema) {
236
+ return new this(token.element, token.index, parseActionDescriptorString(token.content), schema);
231
237
  }
232
238
  toString() {
233
- const eventNameSuffix = this.eventTargetName ? `@${this.eventTargetName}` : "";
234
- return `${this.eventName}${eventNameSuffix}->${this.identifier}#${this.methodName}`;
239
+ const eventFilter = this.keyFilter ? `.${this.keyFilter}` : "";
240
+ const eventTarget = this.eventTargetName ? `@${this.eventTargetName}` : "";
241
+ return `${this.eventName}${eventFilter}${eventTarget}->${this.identifier}#${this.methodName}`;
242
+ }
243
+ isFilterTarget(event) {
244
+ if (!this.keyFilter) {
245
+ return false;
246
+ }
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) {
251
+ return true;
252
+ }
253
+ const standardFilter = filteres.filter((key) => !modifiers.includes(key))[0];
254
+ if (!standardFilter) {
255
+ return false;
256
+ }
257
+ if (!Object.prototype.hasOwnProperty.call(this.keyMappings, standardFilter)) {
258
+ error(`contains unkown key filter: ${this.keyFilter}`);
259
+ }
260
+ return this.keyMappings[standardFilter].toLowerCase() !== event.key.toLowerCase();
235
261
  }
236
262
  get params() {
237
263
  const params = {};
@@ -248,6 +274,9 @@ class Action {
248
274
  get eventTargetName() {
249
275
  return stringifyEventTarget(this.eventTarget);
250
276
  }
277
+ get keyMappings() {
278
+ return this.schema.keyMappings;
279
+ }
251
280
  }
252
281
  const defaultEventNames = {
253
282
  a: () => "click",
@@ -339,6 +368,9 @@ class Binding {
339
368
  }
340
369
  willBeInvokedByEvent(event) {
341
370
  const eventTarget = event.target;
371
+ if (event instanceof KeyboardEvent && this.action.isFilterTarget(event)) {
372
+ return false;
373
+ }
342
374
  if (this.element === eventTarget) {
343
375
  return true;
344
376
  }
@@ -552,95 +584,6 @@ class AttributeObserver {
552
584
  }
553
585
  }
554
586
 
555
- class StringMapObserver {
556
- constructor(element, delegate) {
557
- this.element = element;
558
- this.delegate = delegate;
559
- this.started = false;
560
- this.stringMap = new Map();
561
- this.mutationObserver = new MutationObserver((mutations) => this.processMutations(mutations));
562
- }
563
- start() {
564
- if (!this.started) {
565
- this.started = true;
566
- this.mutationObserver.observe(this.element, { attributes: true, attributeOldValue: true });
567
- this.refresh();
568
- }
569
- }
570
- stop() {
571
- if (this.started) {
572
- this.mutationObserver.takeRecords();
573
- this.mutationObserver.disconnect();
574
- this.started = false;
575
- }
576
- }
577
- refresh() {
578
- if (this.started) {
579
- for (const attributeName of this.knownAttributeNames) {
580
- this.refreshAttribute(attributeName, null);
581
- }
582
- }
583
- }
584
- processMutations(mutations) {
585
- if (this.started) {
586
- for (const mutation of mutations) {
587
- this.processMutation(mutation);
588
- }
589
- }
590
- }
591
- processMutation(mutation) {
592
- const attributeName = mutation.attributeName;
593
- if (attributeName) {
594
- this.refreshAttribute(attributeName, mutation.oldValue);
595
- }
596
- }
597
- refreshAttribute(attributeName, oldValue) {
598
- const key = this.delegate.getStringMapKeyForAttribute(attributeName);
599
- if (key != null) {
600
- if (!this.stringMap.has(attributeName)) {
601
- this.stringMapKeyAdded(key, attributeName);
602
- }
603
- const value = this.element.getAttribute(attributeName);
604
- if (this.stringMap.get(attributeName) != value) {
605
- this.stringMapValueChanged(value, key, oldValue);
606
- }
607
- if (value == null) {
608
- const oldValue = this.stringMap.get(attributeName);
609
- this.stringMap.delete(attributeName);
610
- if (oldValue)
611
- this.stringMapKeyRemoved(key, attributeName, oldValue);
612
- }
613
- else {
614
- this.stringMap.set(attributeName, value);
615
- }
616
- }
617
- }
618
- stringMapKeyAdded(key, attributeName) {
619
- if (this.delegate.stringMapKeyAdded) {
620
- this.delegate.stringMapKeyAdded(key, attributeName);
621
- }
622
- }
623
- stringMapValueChanged(value, key, oldValue) {
624
- if (this.delegate.stringMapValueChanged) {
625
- this.delegate.stringMapValueChanged(value, key, oldValue);
626
- }
627
- }
628
- stringMapKeyRemoved(key, attributeName, oldValue) {
629
- if (this.delegate.stringMapKeyRemoved) {
630
- this.delegate.stringMapKeyRemoved(key, attributeName, oldValue);
631
- }
632
- }
633
- get knownAttributeNames() {
634
- return Array.from(new Set(this.currentAttributeNames.concat(this.recordedAttributeNames)));
635
- }
636
- get currentAttributeNames() {
637
- return Array.from(this.element.attributes).map((attribute) => attribute.name);
638
- }
639
- get recordedAttributeNames() {
640
- return Array.from(this.stringMap.keys());
641
- }
642
- }
643
-
644
587
  function add(map, key, value) {
645
588
  fetch(map, key).add(value);
646
589
  }
@@ -731,6 +674,158 @@ class IndexedMultimap extends Multimap {
731
674
  }
732
675
  }
733
676
 
677
+ class SelectorObserver {
678
+ constructor(element, selector, delegate, details = {}) {
679
+ this.selector = selector;
680
+ this.details = details;
681
+ this.elementObserver = new ElementObserver(element, this);
682
+ this.delegate = delegate;
683
+ this.matchesByElement = new Multimap();
684
+ }
685
+ get started() {
686
+ return this.elementObserver.started;
687
+ }
688
+ start() {
689
+ this.elementObserver.start();
690
+ }
691
+ pause(callback) {
692
+ this.elementObserver.pause(callback);
693
+ }
694
+ stop() {
695
+ this.elementObserver.stop();
696
+ }
697
+ refresh() {
698
+ this.elementObserver.refresh();
699
+ }
700
+ get element() {
701
+ return this.elementObserver.element;
702
+ }
703
+ matchElement(element) {
704
+ const matches = element.matches(this.selector);
705
+ if (this.delegate.selectorMatchElement) {
706
+ return matches && this.delegate.selectorMatchElement(element, this.details);
707
+ }
708
+ return matches;
709
+ }
710
+ 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);
714
+ }
715
+ elementMatched(element) {
716
+ this.selectorMatched(element);
717
+ }
718
+ elementUnmatched(element) {
719
+ this.selectorUnmatched(element);
720
+ }
721
+ 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);
726
+ }
727
+ }
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
+ }
733
+ }
734
+ selectorUnmatched(element) {
735
+ this.delegate.selectorUnmatched(element, this.selector, this.details);
736
+ this.matchesByElement.delete(this.selector, element);
737
+ }
738
+ }
739
+
740
+ class StringMapObserver {
741
+ constructor(element, delegate) {
742
+ this.element = element;
743
+ this.delegate = delegate;
744
+ this.started = false;
745
+ this.stringMap = new Map();
746
+ this.mutationObserver = new MutationObserver((mutations) => this.processMutations(mutations));
747
+ }
748
+ start() {
749
+ if (!this.started) {
750
+ this.started = true;
751
+ this.mutationObserver.observe(this.element, { attributes: true, attributeOldValue: true });
752
+ this.refresh();
753
+ }
754
+ }
755
+ stop() {
756
+ if (this.started) {
757
+ this.mutationObserver.takeRecords();
758
+ this.mutationObserver.disconnect();
759
+ this.started = false;
760
+ }
761
+ }
762
+ refresh() {
763
+ if (this.started) {
764
+ for (const attributeName of this.knownAttributeNames) {
765
+ this.refreshAttribute(attributeName, null);
766
+ }
767
+ }
768
+ }
769
+ processMutations(mutations) {
770
+ if (this.started) {
771
+ for (const mutation of mutations) {
772
+ this.processMutation(mutation);
773
+ }
774
+ }
775
+ }
776
+ processMutation(mutation) {
777
+ const attributeName = mutation.attributeName;
778
+ if (attributeName) {
779
+ this.refreshAttribute(attributeName, mutation.oldValue);
780
+ }
781
+ }
782
+ refreshAttribute(attributeName, oldValue) {
783
+ const key = this.delegate.getStringMapKeyForAttribute(attributeName);
784
+ if (key != null) {
785
+ if (!this.stringMap.has(attributeName)) {
786
+ this.stringMapKeyAdded(key, attributeName);
787
+ }
788
+ const value = this.element.getAttribute(attributeName);
789
+ if (this.stringMap.get(attributeName) != value) {
790
+ this.stringMapValueChanged(value, key, oldValue);
791
+ }
792
+ if (value == null) {
793
+ const oldValue = this.stringMap.get(attributeName);
794
+ this.stringMap.delete(attributeName);
795
+ if (oldValue)
796
+ this.stringMapKeyRemoved(key, attributeName, oldValue);
797
+ }
798
+ else {
799
+ this.stringMap.set(attributeName, value);
800
+ }
801
+ }
802
+ }
803
+ stringMapKeyAdded(key, attributeName) {
804
+ if (this.delegate.stringMapKeyAdded) {
805
+ this.delegate.stringMapKeyAdded(key, attributeName);
806
+ }
807
+ }
808
+ stringMapValueChanged(value, key, oldValue) {
809
+ if (this.delegate.stringMapValueChanged) {
810
+ this.delegate.stringMapValueChanged(value, key, oldValue);
811
+ }
812
+ }
813
+ stringMapKeyRemoved(key, attributeName, oldValue) {
814
+ if (this.delegate.stringMapKeyRemoved) {
815
+ this.delegate.stringMapKeyRemoved(key, attributeName, oldValue);
816
+ }
817
+ }
818
+ get knownAttributeNames() {
819
+ return Array.from(new Set(this.currentAttributeNames.concat(this.recordedAttributeNames)));
820
+ }
821
+ get currentAttributeNames() {
822
+ return Array.from(this.element.attributes).map((attribute) => attribute.name);
823
+ }
824
+ get recordedAttributeNames() {
825
+ return Array.from(this.stringMap.keys());
826
+ }
827
+ }
828
+
734
829
  class TokenListObserver {
735
830
  constructor(element, attributeName, delegate) {
736
831
  this.attributeObserver = new AttributeObserver(element, attributeName, this);
@@ -934,7 +1029,7 @@ class BindingObserver {
934
1029
  this.bindingsByAction.clear();
935
1030
  }
936
1031
  parseValueForToken(token) {
937
- const action = Action.forToken(token);
1032
+ const action = Action.forToken(token, this.schema);
938
1033
  if (action.identifier == this.identifier) {
939
1034
  return action;
940
1035
  }
@@ -1102,6 +1197,155 @@ class TargetObserver {
1102
1197
  }
1103
1198
  }
1104
1199
 
1200
+ function readInheritableStaticArrayValues(constructor, propertyName) {
1201
+ const ancestors = getAncestorsForConstructor(constructor);
1202
+ return Array.from(ancestors.reduce((values, constructor) => {
1203
+ getOwnStaticArrayValues(constructor, propertyName).forEach((name) => values.add(name));
1204
+ return values;
1205
+ }, new Set()));
1206
+ }
1207
+ function readInheritableStaticObjectPairs(constructor, propertyName) {
1208
+ const ancestors = getAncestorsForConstructor(constructor);
1209
+ return ancestors.reduce((pairs, constructor) => {
1210
+ pairs.push(...getOwnStaticObjectPairs(constructor, propertyName));
1211
+ return pairs;
1212
+ }, []);
1213
+ }
1214
+ function getAncestorsForConstructor(constructor) {
1215
+ const ancestors = [];
1216
+ while (constructor) {
1217
+ ancestors.push(constructor);
1218
+ constructor = Object.getPrototypeOf(constructor);
1219
+ }
1220
+ return ancestors.reverse();
1221
+ }
1222
+ function getOwnStaticArrayValues(constructor, propertyName) {
1223
+ const definition = constructor[propertyName];
1224
+ return Array.isArray(definition) ? definition : [];
1225
+ }
1226
+ function getOwnStaticObjectPairs(constructor, propertyName) {
1227
+ const definition = constructor[propertyName];
1228
+ return definition ? Object.keys(definition).map((key) => [key, definition[key]]) : [];
1229
+ }
1230
+
1231
+ class OutletObserver {
1232
+ constructor(context, delegate) {
1233
+ this.context = context;
1234
+ this.delegate = delegate;
1235
+ this.outletsByName = new Multimap();
1236
+ this.outletElementsByName = new Multimap();
1237
+ this.selectorObserverMap = new Map();
1238
+ }
1239
+ start() {
1240
+ if (this.selectorObserverMap.size === 0) {
1241
+ 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
+ }
1247
+ });
1248
+ this.selectorObserverMap.forEach((observer) => observer.start());
1249
+ }
1250
+ this.dependentContexts.forEach((context) => context.refresh());
1251
+ }
1252
+ stop() {
1253
+ if (this.selectorObserverMap.size > 0) {
1254
+ this.disconnectAllOutlets();
1255
+ this.selectorObserverMap.forEach((observer) => observer.stop());
1256
+ this.selectorObserverMap.clear();
1257
+ }
1258
+ }
1259
+ refresh() {
1260
+ this.selectorObserverMap.forEach((observer) => observer.refresh());
1261
+ }
1262
+ selectorMatched(element, _selector, { outletName }) {
1263
+ const outlet = this.getOutlet(element, outletName);
1264
+ if (outlet) {
1265
+ this.connectOutlet(outlet, element, outletName);
1266
+ }
1267
+ }
1268
+ selectorUnmatched(element, _selector, { outletName }) {
1269
+ const outlet = this.getOutletFromMap(element, outletName);
1270
+ if (outlet) {
1271
+ this.disconnectOutlet(outlet, element, outletName);
1272
+ }
1273
+ }
1274
+ selectorMatchElement(element, { outletName }) {
1275
+ return (this.hasOutlet(element, outletName) &&
1276
+ element.matches(`[${this.context.application.schema.controllerAttribute}~=${outletName}]`));
1277
+ }
1278
+ connectOutlet(outlet, element, outletName) {
1279
+ var _a;
1280
+ if (!this.outletElementsByName.has(outletName, element)) {
1281
+ this.outletsByName.add(outletName, outlet);
1282
+ this.outletElementsByName.add(outletName, element);
1283
+ (_a = this.selectorObserverMap.get(outletName)) === null || _a === void 0 ? void 0 : _a.pause(() => this.delegate.outletConnected(outlet, element, outletName));
1284
+ }
1285
+ }
1286
+ disconnectOutlet(outlet, element, outletName) {
1287
+ var _a;
1288
+ if (this.outletElementsByName.has(outletName, element)) {
1289
+ this.outletsByName.delete(outletName, outlet);
1290
+ this.outletElementsByName.delete(outletName, element);
1291
+ (_a = this.selectorObserverMap
1292
+ .get(outletName)) === null || _a === void 0 ? void 0 : _a.pause(() => this.delegate.outletDisconnected(outlet, element, outletName));
1293
+ }
1294
+ }
1295
+ disconnectAllOutlets() {
1296
+ for (const outletName of this.outletElementsByName.keys) {
1297
+ for (const element of this.outletElementsByName.getValuesForKey(outletName)) {
1298
+ for (const outlet of this.outletsByName.getValuesForKey(outletName)) {
1299
+ this.disconnectOutlet(outlet, element, outletName);
1300
+ }
1301
+ }
1302
+ }
1303
+ }
1304
+ selector(outletName) {
1305
+ return this.scope.outlets.getSelectorForOutletName(outletName);
1306
+ }
1307
+ get outletDependencies() {
1308
+ const dependencies = new Multimap();
1309
+ this.router.modules.forEach((module) => {
1310
+ const constructor = module.definition.controllerConstructor;
1311
+ const outlets = readInheritableStaticArrayValues(constructor, "outlets");
1312
+ outlets.forEach((outlet) => dependencies.add(outlet, module.identifier));
1313
+ });
1314
+ return dependencies;
1315
+ }
1316
+ get outletDefinitions() {
1317
+ return this.outletDependencies.getKeysForValue(this.identifier);
1318
+ }
1319
+ get dependentControllerIdentifiers() {
1320
+ return this.outletDependencies.getValuesForKey(this.identifier);
1321
+ }
1322
+ get dependentContexts() {
1323
+ const identifiers = this.dependentControllerIdentifiers;
1324
+ return this.router.contexts.filter((context) => identifiers.includes(context.identifier));
1325
+ }
1326
+ hasOutlet(element, outletName) {
1327
+ return !!this.getOutlet(element, outletName) || !!this.getOutletFromMap(element, outletName);
1328
+ }
1329
+ getOutlet(element, outletName) {
1330
+ return this.application.getControllerForElementAndIdentifier(element, outletName);
1331
+ }
1332
+ getOutletFromMap(element, outletName) {
1333
+ return this.outletsByName.getValuesForKey(outletName).find((outlet) => outlet.element === element);
1334
+ }
1335
+ get scope() {
1336
+ return this.context.scope;
1337
+ }
1338
+ get identifier() {
1339
+ return this.context.identifier;
1340
+ }
1341
+ get application() {
1342
+ return this.context.application;
1343
+ }
1344
+ get router() {
1345
+ return this.application.router;
1346
+ }
1347
+ }
1348
+
1105
1349
  class Context {
1106
1350
  constructor(module, scope) {
1107
1351
  this.logDebugActivity = (functionName, detail = {}) => {
@@ -1115,6 +1359,7 @@ class Context {
1115
1359
  this.bindingObserver = new BindingObserver(this, this.dispatcher);
1116
1360
  this.valueObserver = new ValueObserver(this, this.controller);
1117
1361
  this.targetObserver = new TargetObserver(this, this);
1362
+ this.outletObserver = new OutletObserver(this, this);
1118
1363
  try {
1119
1364
  this.controller.initialize();
1120
1365
  this.logDebugActivity("initialize");
@@ -1127,6 +1372,7 @@ class Context {
1127
1372
  this.bindingObserver.start();
1128
1373
  this.valueObserver.start();
1129
1374
  this.targetObserver.start();
1375
+ this.outletObserver.start();
1130
1376
  try {
1131
1377
  this.controller.connect();
1132
1378
  this.logDebugActivity("connect");
@@ -1135,6 +1381,9 @@ class Context {
1135
1381
  this.handleError(error, "connecting controller");
1136
1382
  }
1137
1383
  }
1384
+ refresh() {
1385
+ this.outletObserver.refresh();
1386
+ }
1138
1387
  disconnect() {
1139
1388
  try {
1140
1389
  this.controller.disconnect();
@@ -1143,6 +1392,7 @@ class Context {
1143
1392
  catch (error) {
1144
1393
  this.handleError(error, "disconnecting controller");
1145
1394
  }
1395
+ this.outletObserver.stop();
1146
1396
  this.targetObserver.stop();
1147
1397
  this.valueObserver.stop();
1148
1398
  this.bindingObserver.stop();
@@ -1176,6 +1426,12 @@ class Context {
1176
1426
  targetDisconnected(element, name) {
1177
1427
  this.invokeControllerMethod(`${name}TargetDisconnected`, element);
1178
1428
  }
1429
+ outletConnected(outlet, element, name) {
1430
+ this.invokeControllerMethod(`${namespaceCamelize(name)}OutletConnected`, outlet, element);
1431
+ }
1432
+ outletDisconnected(outlet, element, name) {
1433
+ this.invokeControllerMethod(`${namespaceCamelize(name)}OutletDisconnected`, outlet, element);
1434
+ }
1179
1435
  invokeControllerMethod(methodName, ...args) {
1180
1436
  const controller = this.controller;
1181
1437
  if (typeof controller[methodName] == "function") {
@@ -1184,37 +1440,6 @@ class Context {
1184
1440
  }
1185
1441
  }
1186
1442
 
1187
- function readInheritableStaticArrayValues(constructor, propertyName) {
1188
- const ancestors = getAncestorsForConstructor(constructor);
1189
- return Array.from(ancestors.reduce((values, constructor) => {
1190
- getOwnStaticArrayValues(constructor, propertyName).forEach((name) => values.add(name));
1191
- return values;
1192
- }, new Set()));
1193
- }
1194
- function readInheritableStaticObjectPairs(constructor, propertyName) {
1195
- const ancestors = getAncestorsForConstructor(constructor);
1196
- return ancestors.reduce((pairs, constructor) => {
1197
- pairs.push(...getOwnStaticObjectPairs(constructor, propertyName));
1198
- return pairs;
1199
- }, []);
1200
- }
1201
- function getAncestorsForConstructor(constructor) {
1202
- const ancestors = [];
1203
- while (constructor) {
1204
- ancestors.push(constructor);
1205
- constructor = Object.getPrototypeOf(constructor);
1206
- }
1207
- return ancestors.reverse();
1208
- }
1209
- function getOwnStaticArrayValues(constructor, propertyName) {
1210
- const definition = constructor[propertyName];
1211
- return Array.isArray(definition) ? definition : [];
1212
- }
1213
- function getOwnStaticObjectPairs(constructor, propertyName) {
1214
- const definition = constructor[propertyName];
1215
- return definition ? Object.keys(definition).map((key) => [key, definition[key]]) : [];
1216
- }
1217
-
1218
1443
  function bless(constructor) {
1219
1444
  return shadow(constructor, getBlessedProperties(constructor));
1220
1445
  }
@@ -1488,6 +1713,56 @@ class TargetSet {
1488
1713
  }
1489
1714
  }
1490
1715
 
1716
+ class OutletSet {
1717
+ constructor(scope, controllerElement) {
1718
+ this.scope = scope;
1719
+ this.controllerElement = controllerElement;
1720
+ }
1721
+ get element() {
1722
+ return this.scope.element;
1723
+ }
1724
+ get identifier() {
1725
+ return this.scope.identifier;
1726
+ }
1727
+ get schema() {
1728
+ return this.scope.schema;
1729
+ }
1730
+ has(outletName) {
1731
+ return this.find(outletName) != null;
1732
+ }
1733
+ find(...outletNames) {
1734
+ return outletNames.reduce((outlet, outletName) => outlet || this.findOutlet(outletName), undefined);
1735
+ }
1736
+ findAll(...outletNames) {
1737
+ return outletNames.reduce((outlets, outletName) => [...outlets, ...this.findAllOutlets(outletName)], []);
1738
+ }
1739
+ getSelectorForOutletName(outletName) {
1740
+ const attributeName = this.schema.outletAttributeForScope(this.identifier, outletName);
1741
+ return this.controllerElement.getAttribute(attributeName);
1742
+ }
1743
+ findOutlet(outletName) {
1744
+ const selector = this.getSelectorForOutletName(outletName);
1745
+ if (selector)
1746
+ return this.findElement(selector, outletName);
1747
+ }
1748
+ findAllOutlets(outletName) {
1749
+ const selector = this.getSelectorForOutletName(outletName);
1750
+ return selector ? this.findAllElements(selector, outletName) : [];
1751
+ }
1752
+ findElement(selector, outletName) {
1753
+ const elements = this.scope.queryElements(selector);
1754
+ return elements.filter((element) => this.matchesElement(element, selector, outletName))[0];
1755
+ }
1756
+ findAllElements(selector, outletName) {
1757
+ const elements = this.scope.queryElements(selector);
1758
+ return elements.filter((element) => this.matchesElement(element, selector, outletName));
1759
+ }
1760
+ matchesElement(element, selector, outletName) {
1761
+ const controllerAttribute = element.getAttribute(this.scope.schema.controllerAttribute) || "";
1762
+ return element.matches(selector) && controllerAttribute.split(" ").includes(outletName);
1763
+ }
1764
+ }
1765
+
1491
1766
  class Scope {
1492
1767
  constructor(schema, element, identifier, logger) {
1493
1768
  this.targets = new TargetSet(this);
@@ -1500,6 +1775,7 @@ class Scope {
1500
1775
  this.element = element;
1501
1776
  this.identifier = identifier;
1502
1777
  this.guide = new Guide(logger);
1778
+ this.outlets = new OutletSet(this.documentScope, element);
1503
1779
  }
1504
1780
  findElement(selector) {
1505
1781
  return this.element.matches(selector) ? this.element : this.queryElements(selector).find(this.containsElement);
@@ -1516,6 +1792,14 @@ class Scope {
1516
1792
  get controllerSelector() {
1517
1793
  return attributeValueContainsToken(this.schema.controllerAttribute, this.identifier);
1518
1794
  }
1795
+ get isDocumentScope() {
1796
+ return this.element === document.documentElement;
1797
+ }
1798
+ get documentScope() {
1799
+ return this.isDocumentScope
1800
+ ? this
1801
+ : new Scope(this.schema, document.documentElement, this.identifier, this.guide.logger);
1802
+ }
1519
1803
  }
1520
1804
 
1521
1805
  class ScopeObserver {
@@ -1607,6 +1891,10 @@ class Router {
1607
1891
  this.unloadIdentifier(definition.identifier);
1608
1892
  const module = new Module(this.application, definition);
1609
1893
  this.connectModule(module);
1894
+ const afterLoad = definition.controllerConstructor.afterLoad;
1895
+ if (afterLoad) {
1896
+ afterLoad(definition.identifier, this.application);
1897
+ }
1610
1898
  }
1611
1899
  unloadIdentifier(identifier) {
1612
1900
  const module = this.modulesByIdentifier.get(identifier);
@@ -1657,7 +1945,12 @@ const defaultSchema = {
1657
1945
  actionAttribute: "data-action",
1658
1946
  targetAttribute: "data-target",
1659
1947
  targetAttributeForScope: (identifier) => `data-${identifier}-target`,
1948
+ 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]))),
1660
1950
  };
1951
+ function objectFromEntries(array) {
1952
+ return array.reduce((memo, [k, v]) => (Object.assign(Object.assign({}, memo), { [k]: v })), {});
1953
+ }
1661
1954
 
1662
1955
  class Application {
1663
1956
  constructor(element = document.documentElement, schema = defaultSchema) {
@@ -1675,7 +1968,7 @@ class Application {
1675
1968
  this.actionDescriptorFilters = Object.assign({}, defaultActionDescriptorFilters);
1676
1969
  }
1677
1970
  static start(element, schema) {
1678
- const application = new Application(element, schema);
1971
+ const application = new this(element, schema);
1679
1972
  application.start();
1680
1973
  return application;
1681
1974
  }
@@ -1773,6 +2066,73 @@ function propertiesForClassDefinition(key) {
1773
2066
  };
1774
2067
  }
1775
2068
 
2069
+ function OutletPropertiesBlessing(constructor) {
2070
+ const outlets = readInheritableStaticArrayValues(constructor, "outlets");
2071
+ return outlets.reduce((properties, outletDefinition) => {
2072
+ return Object.assign(properties, propertiesForOutletDefinition(outletDefinition));
2073
+ }, {});
2074
+ }
2075
+ function propertiesForOutletDefinition(name) {
2076
+ const camelizedName = namespaceCamelize(name);
2077
+ return {
2078
+ [`${camelizedName}Outlet`]: {
2079
+ get() {
2080
+ const outlet = this.outlets.find(name);
2081
+ if (outlet) {
2082
+ const outletController = this.application.getControllerForElementAndIdentifier(outlet, name);
2083
+ if (outletController) {
2084
+ return outletController;
2085
+ }
2086
+ else {
2087
+ throw new Error(`Missing "data-controller=${name}" attribute on outlet element for "${this.identifier}" controller`);
2088
+ }
2089
+ }
2090
+ throw new Error(`Missing outlet element "${name}" for "${this.identifier}" controller`);
2091
+ },
2092
+ },
2093
+ [`${camelizedName}Outlets`]: {
2094
+ get() {
2095
+ const outlets = this.outlets.findAll(name);
2096
+ if (outlets.length > 0) {
2097
+ 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
+ }
2106
+ })
2107
+ .filter((controller) => controller);
2108
+ }
2109
+ return [];
2110
+ },
2111
+ },
2112
+ [`${camelizedName}OutletElement`]: {
2113
+ get() {
2114
+ const outlet = this.outlets.find(name);
2115
+ if (outlet) {
2116
+ return outlet;
2117
+ }
2118
+ else {
2119
+ throw new Error(`Missing outlet element "${name}" for "${this.identifier}" controller`);
2120
+ }
2121
+ },
2122
+ },
2123
+ [`${camelizedName}OutletElements`]: {
2124
+ get() {
2125
+ return this.outlets.findAll(name);
2126
+ },
2127
+ },
2128
+ [`has${capitalize(camelizedName)}Outlet`]: {
2129
+ get() {
2130
+ return this.outlets.has(name);
2131
+ },
2132
+ },
2133
+ };
2134
+ }
2135
+
1776
2136
  function TargetPropertiesBlessing(constructor) {
1777
2137
  const targets = readInheritableStaticArrayValues(constructor, "targets");
1778
2138
  return targets.reduce((properties, targetDefinition) => {
@@ -1993,6 +2353,9 @@ class Controller {
1993
2353
  static get shouldLoad() {
1994
2354
  return true;
1995
2355
  }
2356
+ static afterLoad(_identifier, _application) {
2357
+ return;
2358
+ }
1996
2359
  get application() {
1997
2360
  return this.context.application;
1998
2361
  }
@@ -2008,6 +2371,9 @@ class Controller {
2008
2371
  get targets() {
2009
2372
  return this.scope.targets;
2010
2373
  }
2374
+ get outlets() {
2375
+ return this.scope.outlets;
2376
+ }
2011
2377
  get classes() {
2012
2378
  return this.scope.classes;
2013
2379
  }
@@ -2027,8 +2393,14 @@ class Controller {
2027
2393
  return event;
2028
2394
  }
2029
2395
  }
2030
- Controller.blessings = [ClassPropertiesBlessing, TargetPropertiesBlessing, ValuePropertiesBlessing];
2396
+ Controller.blessings = [
2397
+ ClassPropertiesBlessing,
2398
+ TargetPropertiesBlessing,
2399
+ ValuePropertiesBlessing,
2400
+ OutletPropertiesBlessing,
2401
+ ];
2031
2402
  Controller.targets = [];
2403
+ Controller.outlets = [];
2032
2404
  Controller.values = {};
2033
2405
 
2034
- export { Application, AttributeObserver, Context, Controller, ElementObserver, IndexedMultimap, Multimap, StringMapObserver, TokenListObserver, ValueListObserver, add, defaultSchema, del, fetch, prune };
2406
+ export { Application, AttributeObserver, Context, Controller, ElementObserver, IndexedMultimap, Multimap, SelectorObserver, StringMapObserver, TokenListObserver, ValueListObserver, add, defaultSchema, del, fetch, prune };