stimulus-rails 1.1.0 → 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.0
6
+ Stimulus 3.2.0
7
7
  Copyright © 2022 Basecamp, LLC
8
8
  */
9
9
  class EventListener {
@@ -36,6 +36,9 @@ class EventListener {
36
36
  }
37
37
  }
38
38
  }
39
+ hasBindings() {
40
+ return this.unorderedBindings.size > 0;
41
+ }
39
42
  get bindings() {
40
43
  return Array.from(this.unorderedBindings).sort((left, right) => {
41
44
  const leftIndex = left.index, rightIndex = right.index;
@@ -54,7 +57,7 @@ function extendEvent(event) {
54
57
  stopImmediatePropagation() {
55
58
  this.immediatePropagationStopped = true;
56
59
  stopImmediatePropagation.call(this);
57
- }
60
+ },
58
61
  });
59
62
  }
60
63
  }
@@ -62,34 +65,50 @@ function extendEvent(event) {
62
65
  class Dispatcher {
63
66
  constructor(application) {
64
67
  this.application = application;
65
- this.eventListenerMaps = new Map;
68
+ this.eventListenerMaps = new Map();
66
69
  this.started = false;
67
70
  }
68
71
  start() {
69
72
  if (!this.started) {
70
73
  this.started = true;
71
- this.eventListeners.forEach(eventListener => eventListener.connect());
74
+ this.eventListeners.forEach((eventListener) => eventListener.connect());
72
75
  }
73
76
  }
74
77
  stop() {
75
78
  if (this.started) {
76
79
  this.started = false;
77
- this.eventListeners.forEach(eventListener => eventListener.disconnect());
80
+ this.eventListeners.forEach((eventListener) => eventListener.disconnect());
78
81
  }
79
82
  }
80
83
  get eventListeners() {
81
- return Array.from(this.eventListenerMaps.values())
82
- .reduce((listeners, map) => listeners.concat(Array.from(map.values())), []);
84
+ return Array.from(this.eventListenerMaps.values()).reduce((listeners, map) => listeners.concat(Array.from(map.values())), []);
83
85
  }
84
86
  bindingConnected(binding) {
85
87
  this.fetchEventListenerForBinding(binding).bindingConnected(binding);
86
88
  }
87
- bindingDisconnected(binding) {
89
+ bindingDisconnected(binding, clearEventListeners = false) {
88
90
  this.fetchEventListenerForBinding(binding).bindingDisconnected(binding);
91
+ if (clearEventListeners)
92
+ this.clearEventListenersForBinding(binding);
89
93
  }
90
94
  handleError(error, message, detail = {}) {
91
95
  this.application.handleError(error, `Error ${message}`, detail);
92
96
  }
97
+ clearEventListenersForBinding(binding) {
98
+ const eventListener = this.fetchEventListenerForBinding(binding);
99
+ if (!eventListener.hasBindings()) {
100
+ eventListener.disconnect();
101
+ this.removeMappedEventListenerFor(binding);
102
+ }
103
+ }
104
+ removeMappedEventListenerFor(binding) {
105
+ const { eventTarget, eventName, eventOptions } = binding;
106
+ const eventListenerMap = this.fetchEventListenerMapForEventTarget(eventTarget);
107
+ const cacheKey = this.cacheKey(eventName, eventOptions);
108
+ eventListenerMap.delete(cacheKey);
109
+ if (eventListenerMap.size == 0)
110
+ this.eventListenerMaps.delete(eventTarget);
111
+ }
93
112
  fetchEventListenerForBinding(binding) {
94
113
  const { eventTarget, eventName, eventOptions } = binding;
95
114
  return this.fetchEventListener(eventTarget, eventName, eventOptions);
@@ -114,30 +133,53 @@ class Dispatcher {
114
133
  fetchEventListenerMapForEventTarget(eventTarget) {
115
134
  let eventListenerMap = this.eventListenerMaps.get(eventTarget);
116
135
  if (!eventListenerMap) {
117
- eventListenerMap = new Map;
136
+ eventListenerMap = new Map();
118
137
  this.eventListenerMaps.set(eventTarget, eventListenerMap);
119
138
  }
120
139
  return eventListenerMap;
121
140
  }
122
141
  cacheKey(eventName, eventOptions) {
123
142
  const parts = [eventName];
124
- Object.keys(eventOptions).sort().forEach(key => {
143
+ Object.keys(eventOptions)
144
+ .sort()
145
+ .forEach((key) => {
125
146
  parts.push(`${eventOptions[key] ? "" : "!"}${key}`);
126
147
  });
127
148
  return parts.join(":");
128
149
  }
129
150
  }
130
151
 
131
- const descriptorPattern = /^((.+?)(@(window|document))?->)?(.+?)(#([^:]+?))(:(.+))?$/;
152
+ const defaultActionDescriptorFilters = {
153
+ stop({ event, value }) {
154
+ if (value)
155
+ event.stopPropagation();
156
+ return true;
157
+ },
158
+ prevent({ event, value }) {
159
+ if (value)
160
+ event.preventDefault();
161
+ return true;
162
+ },
163
+ self({ event, value, element }) {
164
+ if (value) {
165
+ return element === event.target;
166
+ }
167
+ else {
168
+ return true;
169
+ }
170
+ },
171
+ };
172
+ const descriptorPattern = /^(?:(.+?)(?:\.(.+?))?(?:@(window|document))?->)?(.+?)(?:#([^:]+?))(?::(.+))?$/;
132
173
  function parseActionDescriptorString(descriptorString) {
133
174
  const source = descriptorString.trim();
134
175
  const matches = source.match(descriptorPattern) || [];
135
176
  return {
136
- eventTarget: parseEventTarget(matches[4]),
137
- eventName: matches[2],
138
- eventOptions: matches[9] ? parseEventOptions(matches[9]) : {},
139
- identifier: matches[5],
140
- 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],
141
183
  };
142
184
  }
143
185
  function parseEventTarget(eventTargetName) {
@@ -149,7 +191,9 @@ function parseEventTarget(eventTargetName) {
149
191
  }
150
192
  }
151
193
  function parseEventOptions(eventOptions) {
152
- return eventOptions.split(":").reduce((options, token) => Object.assign(options, { [token.replace(/^!/, "")]: !/^!/.test(token) }), {});
194
+ return eventOptions
195
+ .split(":")
196
+ .reduce((options, token) => Object.assign(options, { [token.replace(/^!/, "")]: !/^!/.test(token) }), {});
153
197
  }
154
198
  function stringifyEventTarget(eventTarget) {
155
199
  if (eventTarget == window) {
@@ -163,6 +207,9 @@ function stringifyEventTarget(eventTarget) {
163
207
  function camelize(value) {
164
208
  return value.replace(/(?:[_-])([a-z0-9])/g, (_, char) => char.toUpperCase());
165
209
  }
210
+ function namespaceCamelize(value) {
211
+ return camelize(value.replace(/--/g, "-").replace(/__/g, "_"));
212
+ }
166
213
  function capitalize(value) {
167
214
  return value.charAt(0).toUpperCase() + value.slice(1);
168
215
  }
@@ -174,7 +221,7 @@ function tokenize(value) {
174
221
  }
175
222
 
176
223
  class Action {
177
- constructor(element, index, descriptor) {
224
+ constructor(element, index, descriptor, schema) {
178
225
  this.element = element;
179
226
  this.index = index;
180
227
  this.eventTarget = descriptor.eventTarget || element;
@@ -182,17 +229,39 @@ class Action {
182
229
  this.eventOptions = descriptor.eventOptions || {};
183
230
  this.identifier = descriptor.identifier || error("missing identifier");
184
231
  this.methodName = descriptor.methodName || error("missing method name");
232
+ this.keyFilter = descriptor.keyFilter || "";
233
+ this.schema = schema;
185
234
  }
186
- static forToken(token) {
187
- 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);
188
237
  }
189
238
  toString() {
190
- const eventNameSuffix = this.eventTargetName ? `@${this.eventTargetName}` : "";
191
- 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();
192
261
  }
193
262
  get params() {
194
263
  const params = {};
195
- const pattern = new RegExp(`^data-${this.identifier}-(.+)-param$`);
264
+ const pattern = new RegExp(`^data-${this.identifier}-(.+)-param$`, "i");
196
265
  for (const { name, value } of Array.from(this.element.attributes)) {
197
266
  const match = name.match(pattern);
198
267
  const key = match && match[1];
@@ -205,15 +274,18 @@ class Action {
205
274
  get eventTargetName() {
206
275
  return stringifyEventTarget(this.eventTarget);
207
276
  }
277
+ get keyMappings() {
278
+ return this.schema.keyMappings;
279
+ }
208
280
  }
209
281
  const defaultEventNames = {
210
- "a": e => "click",
211
- "button": e => "click",
212
- "form": e => "submit",
213
- "details": e => "toggle",
214
- "input": e => e.getAttribute("type") == "submit" ? "click" : "input",
215
- "select": e => "change",
216
- "textarea": e => "input"
282
+ a: () => "click",
283
+ button: () => "click",
284
+ form: () => "submit",
285
+ details: () => "toggle",
286
+ input: (e) => (e.getAttribute("type") == "submit" ? "click" : "input"),
287
+ select: () => "change",
288
+ textarea: () => "input",
217
289
  };
218
290
  function getDefaultEventNameForElement(element) {
219
291
  const tagName = element.tagName.toLowerCase();
@@ -251,9 +323,7 @@ class Binding {
251
323
  return this.context.identifier;
252
324
  }
253
325
  handleEvent(event) {
254
- if (this.willBeInvokedByEvent(event) && this.shouldBeInvokedPerSelf(event)) {
255
- this.processStopPropagation(event);
256
- this.processPreventDefault(event);
326
+ if (this.willBeInvokedByEvent(event) && this.applyEventModifiers(event)) {
257
327
  this.invokeWithEvent(event);
258
328
  }
259
329
  }
@@ -267,15 +337,20 @@ class Binding {
267
337
  }
268
338
  throw new Error(`Action "${this.action}" references undefined method "${this.methodName}"`);
269
339
  }
270
- processStopPropagation(event) {
271
- if (this.eventOptions.stop) {
272
- event.stopPropagation();
273
- }
274
- }
275
- processPreventDefault(event) {
276
- if (this.eventOptions.prevent) {
277
- event.preventDefault();
340
+ applyEventModifiers(event) {
341
+ const { element } = this.action;
342
+ const { actionDescriptorFilters } = this.context.application;
343
+ let passes = true;
344
+ for (const [name, value] of Object.entries(this.eventOptions)) {
345
+ if (name in actionDescriptorFilters) {
346
+ const filter = actionDescriptorFilters[name];
347
+ passes = passes && filter({ name, value, event, element });
348
+ }
349
+ else {
350
+ continue;
351
+ }
278
352
  }
353
+ return passes;
279
354
  }
280
355
  invokeWithEvent(event) {
281
356
  const { target, currentTarget } = event;
@@ -291,16 +366,11 @@ class Binding {
291
366
  this.context.handleError(error, `invoking action "${this.action}"`, detail);
292
367
  }
293
368
  }
294
- shouldBeInvokedPerSelf(event) {
295
- if (this.action.eventOptions.self === true) {
296
- return this.action.element === event.target;
297
- }
298
- else {
299
- return true;
300
- }
301
- }
302
369
  willBeInvokedByEvent(event) {
303
370
  const eventTarget = event.target;
371
+ if (event instanceof KeyboardEvent && this.action.isFilterTarget(event)) {
372
+ return false;
373
+ }
304
374
  if (this.element === eventTarget) {
305
375
  return true;
306
376
  }
@@ -331,7 +401,7 @@ class ElementObserver {
331
401
  this.element = element;
332
402
  this.started = false;
333
403
  this.delegate = delegate;
334
- this.elements = new Set;
404
+ this.elements = new Set();
335
405
  this.mutationObserver = new MutationObserver((mutations) => this.processMutations(mutations));
336
406
  }
337
407
  start() {
@@ -514,95 +584,6 @@ class AttributeObserver {
514
584
  }
515
585
  }
516
586
 
517
- class StringMapObserver {
518
- constructor(element, delegate) {
519
- this.element = element;
520
- this.delegate = delegate;
521
- this.started = false;
522
- this.stringMap = new Map;
523
- this.mutationObserver = new MutationObserver(mutations => this.processMutations(mutations));
524
- }
525
- start() {
526
- if (!this.started) {
527
- this.started = true;
528
- this.mutationObserver.observe(this.element, { attributes: true, attributeOldValue: true });
529
- this.refresh();
530
- }
531
- }
532
- stop() {
533
- if (this.started) {
534
- this.mutationObserver.takeRecords();
535
- this.mutationObserver.disconnect();
536
- this.started = false;
537
- }
538
- }
539
- refresh() {
540
- if (this.started) {
541
- for (const attributeName of this.knownAttributeNames) {
542
- this.refreshAttribute(attributeName, null);
543
- }
544
- }
545
- }
546
- processMutations(mutations) {
547
- if (this.started) {
548
- for (const mutation of mutations) {
549
- this.processMutation(mutation);
550
- }
551
- }
552
- }
553
- processMutation(mutation) {
554
- const attributeName = mutation.attributeName;
555
- if (attributeName) {
556
- this.refreshAttribute(attributeName, mutation.oldValue);
557
- }
558
- }
559
- refreshAttribute(attributeName, oldValue) {
560
- const key = this.delegate.getStringMapKeyForAttribute(attributeName);
561
- if (key != null) {
562
- if (!this.stringMap.has(attributeName)) {
563
- this.stringMapKeyAdded(key, attributeName);
564
- }
565
- const value = this.element.getAttribute(attributeName);
566
- if (this.stringMap.get(attributeName) != value) {
567
- this.stringMapValueChanged(value, key, oldValue);
568
- }
569
- if (value == null) {
570
- const oldValue = this.stringMap.get(attributeName);
571
- this.stringMap.delete(attributeName);
572
- if (oldValue)
573
- this.stringMapKeyRemoved(key, attributeName, oldValue);
574
- }
575
- else {
576
- this.stringMap.set(attributeName, value);
577
- }
578
- }
579
- }
580
- stringMapKeyAdded(key, attributeName) {
581
- if (this.delegate.stringMapKeyAdded) {
582
- this.delegate.stringMapKeyAdded(key, attributeName);
583
- }
584
- }
585
- stringMapValueChanged(value, key, oldValue) {
586
- if (this.delegate.stringMapValueChanged) {
587
- this.delegate.stringMapValueChanged(value, key, oldValue);
588
- }
589
- }
590
- stringMapKeyRemoved(key, attributeName, oldValue) {
591
- if (this.delegate.stringMapKeyRemoved) {
592
- this.delegate.stringMapKeyRemoved(key, attributeName, oldValue);
593
- }
594
- }
595
- get knownAttributeNames() {
596
- return Array.from(new Set(this.currentAttributeNames.concat(this.recordedAttributeNames)));
597
- }
598
- get currentAttributeNames() {
599
- return Array.from(this.element.attributes).map(attribute => attribute.name);
600
- }
601
- get recordedAttributeNames() {
602
- return Array.from(this.stringMap.keys());
603
- }
604
- }
605
-
606
587
  function add(map, key, value) {
607
588
  fetch(map, key).add(value);
608
589
  }
@@ -655,7 +636,7 @@ class Multimap {
655
636
  }
656
637
  hasValue(value) {
657
638
  const sets = Array.from(this.valuesByKey.values());
658
- return sets.some(set => set.has(value));
639
+ return sets.some((set) => set.has(value));
659
640
  }
660
641
  getValuesForKey(key) {
661
642
  const values = this.valuesByKey.get(key);
@@ -663,15 +644,15 @@ class Multimap {
663
644
  }
664
645
  getKeysForValue(value) {
665
646
  return Array.from(this.valuesByKey)
666
- .filter(([key, values]) => values.has(value))
667
- .map(([key, values]) => key);
647
+ .filter(([_key, values]) => values.has(value))
648
+ .map(([key, _values]) => key);
668
649
  }
669
650
  }
670
651
 
671
652
  class IndexedMultimap extends Multimap {
672
653
  constructor() {
673
654
  super();
674
- this.keysByValue = new Map;
655
+ this.keysByValue = new Map();
675
656
  }
676
657
  get values() {
677
658
  return Array.from(this.keysByValue.keys());
@@ -693,11 +674,163 @@ class IndexedMultimap extends Multimap {
693
674
  }
694
675
  }
695
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
+
696
829
  class TokenListObserver {
697
830
  constructor(element, attributeName, delegate) {
698
831
  this.attributeObserver = new AttributeObserver(element, attributeName, this);
699
832
  this.delegate = delegate;
700
- this.tokensByElement = new Multimap;
833
+ this.tokensByElement = new Multimap();
701
834
  }
702
835
  get started() {
703
836
  return this.attributeObserver.started;
@@ -732,10 +865,10 @@ class TokenListObserver {
732
865
  this.tokensUnmatched(this.tokensByElement.getValuesForKey(element));
733
866
  }
734
867
  tokensMatched(tokens) {
735
- tokens.forEach(token => this.tokenMatched(token));
868
+ tokens.forEach((token) => this.tokenMatched(token));
736
869
  }
737
870
  tokensUnmatched(tokens) {
738
- tokens.forEach(token => this.tokenUnmatched(token));
871
+ tokens.forEach((token) => this.tokenUnmatched(token));
739
872
  }
740
873
  tokenMatched(token) {
741
874
  this.delegate.tokenMatched(token);
@@ -748,8 +881,7 @@ class TokenListObserver {
748
881
  refreshTokensForElement(element) {
749
882
  const previousTokens = this.tokensByElement.getValuesForKey(element);
750
883
  const currentTokens = this.readTokensForElement(element);
751
- const firstDifferingIndex = zip(previousTokens, currentTokens)
752
- .findIndex(([previousToken, currentToken]) => !tokensAreEqual(previousToken, currentToken));
884
+ const firstDifferingIndex = zip(previousTokens, currentTokens).findIndex(([previousToken, currentToken]) => !tokensAreEqual(previousToken, currentToken));
753
885
  if (firstDifferingIndex == -1) {
754
886
  return [[], []];
755
887
  }
@@ -764,7 +896,10 @@ class TokenListObserver {
764
896
  }
765
897
  }
766
898
  function parseTokenString(tokenString, element, attributeName) {
767
- return tokenString.trim().split(/\s+/).filter(content => content.length)
899
+ return tokenString
900
+ .trim()
901
+ .split(/\s+/)
902
+ .filter((content) => content.length)
768
903
  .map((content, index) => ({ element, attributeName, content, index }));
769
904
  }
770
905
  function zip(left, right) {
@@ -779,8 +914,8 @@ class ValueListObserver {
779
914
  constructor(element, attributeName, delegate) {
780
915
  this.tokenListObserver = new TokenListObserver(element, attributeName, this);
781
916
  this.delegate = delegate;
782
- this.parseResultsByToken = new WeakMap;
783
- this.valuesByTokenByElement = new WeakMap;
917
+ this.parseResultsByToken = new WeakMap();
918
+ this.valuesByTokenByElement = new WeakMap();
784
919
  }
785
920
  get started() {
786
921
  return this.tokenListObserver.started;
@@ -827,7 +962,7 @@ class ValueListObserver {
827
962
  fetchValuesByTokenForElement(element) {
828
963
  let valuesByToken = this.valuesByTokenByElement.get(element);
829
964
  if (!valuesByToken) {
830
- valuesByToken = new Map;
965
+ valuesByToken = new Map();
831
966
  this.valuesByTokenByElement.set(element, valuesByToken);
832
967
  }
833
968
  return valuesByToken;
@@ -847,7 +982,7 @@ class BindingObserver {
847
982
  constructor(context, delegate) {
848
983
  this.context = context;
849
984
  this.delegate = delegate;
850
- this.bindingsByAction = new Map;
985
+ this.bindingsByAction = new Map();
851
986
  }
852
987
  start() {
853
988
  if (!this.valueListObserver) {
@@ -890,11 +1025,11 @@ class BindingObserver {
890
1025
  }
891
1026
  }
892
1027
  disconnectAllActions() {
893
- this.bindings.forEach(binding => this.delegate.bindingDisconnected(binding));
1028
+ this.bindings.forEach((binding) => this.delegate.bindingDisconnected(binding, true));
894
1029
  this.bindingsByAction.clear();
895
1030
  }
896
1031
  parseValueForToken(token) {
897
- const action = Action.forToken(token);
1032
+ const action = Action.forToken(token, this.schema);
898
1033
  if (action.identifier == this.identifier) {
899
1034
  return action;
900
1035
  }
@@ -977,19 +1112,20 @@ class ValueObserver {
977
1112
  changedMethod.call(this.receiver, value, oldValue);
978
1113
  }
979
1114
  catch (error) {
980
- if (!(error instanceof TypeError))
981
- throw error;
982
- throw new TypeError(`Stimulus Value "${this.context.identifier}.${descriptor.name}" - ${error.message}`);
1115
+ if (error instanceof TypeError) {
1116
+ error.message = `Stimulus Value "${this.context.identifier}.${descriptor.name}" - ${error.message}`;
1117
+ }
1118
+ throw error;
983
1119
  }
984
1120
  }
985
1121
  }
986
1122
  get valueDescriptors() {
987
1123
  const { valueDescriptorMap } = this;
988
- return Object.keys(valueDescriptorMap).map(key => valueDescriptorMap[key]);
1124
+ return Object.keys(valueDescriptorMap).map((key) => valueDescriptorMap[key]);
989
1125
  }
990
1126
  get valueDescriptorNameMap() {
991
1127
  const descriptors = {};
992
- Object.keys(this.valueDescriptorMap).forEach(key => {
1128
+ Object.keys(this.valueDescriptorMap).forEach((key) => {
993
1129
  const descriptor = this.valueDescriptorMap[key];
994
1130
  descriptors[descriptor.name] = descriptor;
995
1131
  });
@@ -1006,7 +1142,7 @@ class TargetObserver {
1006
1142
  constructor(context, delegate) {
1007
1143
  this.context = context;
1008
1144
  this.delegate = delegate;
1009
- this.targetsByName = new Multimap;
1145
+ this.targetsByName = new Multimap();
1010
1146
  }
1011
1147
  start() {
1012
1148
  if (!this.tokenListObserver) {
@@ -1061,6 +1197,155 @@ class TargetObserver {
1061
1197
  }
1062
1198
  }
1063
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
+
1064
1349
  class Context {
1065
1350
  constructor(module, scope) {
1066
1351
  this.logDebugActivity = (functionName, detail = {}) => {
@@ -1074,6 +1359,7 @@ class Context {
1074
1359
  this.bindingObserver = new BindingObserver(this, this.dispatcher);
1075
1360
  this.valueObserver = new ValueObserver(this, this.controller);
1076
1361
  this.targetObserver = new TargetObserver(this, this);
1362
+ this.outletObserver = new OutletObserver(this, this);
1077
1363
  try {
1078
1364
  this.controller.initialize();
1079
1365
  this.logDebugActivity("initialize");
@@ -1086,6 +1372,7 @@ class Context {
1086
1372
  this.bindingObserver.start();
1087
1373
  this.valueObserver.start();
1088
1374
  this.targetObserver.start();
1375
+ this.outletObserver.start();
1089
1376
  try {
1090
1377
  this.controller.connect();
1091
1378
  this.logDebugActivity("connect");
@@ -1094,6 +1381,9 @@ class Context {
1094
1381
  this.handleError(error, "connecting controller");
1095
1382
  }
1096
1383
  }
1384
+ refresh() {
1385
+ this.outletObserver.refresh();
1386
+ }
1097
1387
  disconnect() {
1098
1388
  try {
1099
1389
  this.controller.disconnect();
@@ -1102,6 +1392,7 @@ class Context {
1102
1392
  catch (error) {
1103
1393
  this.handleError(error, "disconnecting controller");
1104
1394
  }
1395
+ this.outletObserver.stop();
1105
1396
  this.targetObserver.stop();
1106
1397
  this.valueObserver.stop();
1107
1398
  this.bindingObserver.stop();
@@ -1135,6 +1426,12 @@ class Context {
1135
1426
  targetDisconnected(element, name) {
1136
1427
  this.invokeControllerMethod(`${name}TargetDisconnected`, element);
1137
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
+ }
1138
1435
  invokeControllerMethod(methodName, ...args) {
1139
1436
  const controller = this.controller;
1140
1437
  if (typeof controller[methodName] == "function") {
@@ -1143,37 +1440,6 @@ class Context {
1143
1440
  }
1144
1441
  }
1145
1442
 
1146
- function readInheritableStaticArrayValues(constructor, propertyName) {
1147
- const ancestors = getAncestorsForConstructor(constructor);
1148
- return Array.from(ancestors.reduce((values, constructor) => {
1149
- getOwnStaticArrayValues(constructor, propertyName).forEach(name => values.add(name));
1150
- return values;
1151
- }, new Set));
1152
- }
1153
- function readInheritableStaticObjectPairs(constructor, propertyName) {
1154
- const ancestors = getAncestorsForConstructor(constructor);
1155
- return ancestors.reduce((pairs, constructor) => {
1156
- pairs.push(...getOwnStaticObjectPairs(constructor, propertyName));
1157
- return pairs;
1158
- }, []);
1159
- }
1160
- function getAncestorsForConstructor(constructor) {
1161
- const ancestors = [];
1162
- while (constructor) {
1163
- ancestors.push(constructor);
1164
- constructor = Object.getPrototypeOf(constructor);
1165
- }
1166
- return ancestors.reverse();
1167
- }
1168
- function getOwnStaticArrayValues(constructor, propertyName) {
1169
- const definition = constructor[propertyName];
1170
- return Array.isArray(definition) ? definition : [];
1171
- }
1172
- function getOwnStaticObjectPairs(constructor, propertyName) {
1173
- const definition = constructor[propertyName];
1174
- return definition ? Object.keys(definition).map(key => [key, definition[key]]) : [];
1175
- }
1176
-
1177
1443
  function bless(constructor) {
1178
1444
  return shadow(constructor, getBlessedProperties(constructor));
1179
1445
  }
@@ -1217,10 +1483,7 @@ function getShadowedDescriptor(prototype, properties, key) {
1217
1483
  }
1218
1484
  const getOwnKeys = (() => {
1219
1485
  if (typeof Object.getOwnPropertySymbols == "function") {
1220
- return (object) => [
1221
- ...Object.getOwnPropertyNames(object),
1222
- ...Object.getOwnPropertySymbols(object)
1223
- ];
1486
+ return (object) => [...Object.getOwnPropertyNames(object), ...Object.getOwnPropertySymbols(object)];
1224
1487
  }
1225
1488
  else {
1226
1489
  return Object.getOwnPropertyNames;
@@ -1232,16 +1495,18 @@ const extend = (() => {
1232
1495
  return Reflect.construct(constructor, arguments, new.target);
1233
1496
  }
1234
1497
  extended.prototype = Object.create(constructor.prototype, {
1235
- constructor: { value: extended }
1498
+ constructor: { value: extended },
1236
1499
  });
1237
1500
  Reflect.setPrototypeOf(extended, constructor);
1238
1501
  return extended;
1239
1502
  }
1240
1503
  function testReflectExtension() {
1241
- const a = function () { this.a.call(this); };
1504
+ const a = function () {
1505
+ this.a.call(this);
1506
+ };
1242
1507
  const b = extendWithReflect(a);
1243
1508
  b.prototype.a = function () { };
1244
- return new b;
1509
+ return new b();
1245
1510
  }
1246
1511
  try {
1247
1512
  testReflectExtension();
@@ -1256,7 +1521,7 @@ const extend = (() => {
1256
1521
  function blessDefinition(definition) {
1257
1522
  return {
1258
1523
  identifier: definition.identifier,
1259
- controllerConstructor: bless(definition.controllerConstructor)
1524
+ controllerConstructor: bless(definition.controllerConstructor),
1260
1525
  };
1261
1526
  }
1262
1527
 
@@ -1264,8 +1529,8 @@ class Module {
1264
1529
  constructor(application, definition) {
1265
1530
  this.application = application;
1266
1531
  this.definition = blessDefinition(definition);
1267
- this.contextsByScope = new WeakMap;
1268
- this.connectedContexts = new Set;
1532
+ this.contextsByScope = new WeakMap();
1533
+ this.connectedContexts = new Set();
1269
1534
  }
1270
1535
  get identifier() {
1271
1536
  return this.definition.identifier;
@@ -1363,13 +1628,13 @@ class DataMap {
1363
1628
 
1364
1629
  class Guide {
1365
1630
  constructor(logger) {
1366
- this.warnedKeysByObject = new WeakMap;
1631
+ this.warnedKeysByObject = new WeakMap();
1367
1632
  this.logger = logger;
1368
1633
  }
1369
1634
  warn(object, key, message) {
1370
1635
  let warnedKeys = this.warnedKeysByObject.get(object);
1371
1636
  if (!warnedKeys) {
1372
- warnedKeys = new Set;
1637
+ warnedKeys = new Set();
1373
1638
  this.warnedKeysByObject.set(object, warnedKeys);
1374
1639
  }
1375
1640
  if (!warnedKeys.has(key)) {
@@ -1400,15 +1665,13 @@ class TargetSet {
1400
1665
  return this.find(targetName) != null;
1401
1666
  }
1402
1667
  find(...targetNames) {
1403
- return targetNames.reduce((target, targetName) => target
1404
- || this.findTarget(targetName)
1405
- || this.findLegacyTarget(targetName), undefined);
1668
+ return targetNames.reduce((target, targetName) => target || this.findTarget(targetName) || this.findLegacyTarget(targetName), undefined);
1406
1669
  }
1407
1670
  findAll(...targetNames) {
1408
1671
  return targetNames.reduce((targets, targetName) => [
1409
1672
  ...targets,
1410
1673
  ...this.findAllTargets(targetName),
1411
- ...this.findAllLegacyTargets(targetName)
1674
+ ...this.findAllLegacyTargets(targetName),
1412
1675
  ], []);
1413
1676
  }
1414
1677
  findTarget(targetName) {
@@ -1429,7 +1692,7 @@ class TargetSet {
1429
1692
  }
1430
1693
  findAllLegacyTargets(targetName) {
1431
1694
  const selector = this.getLegacySelectorForTargetName(targetName);
1432
- return this.scope.findAllElements(selector).map(element => this.deprecate(element, targetName));
1695
+ return this.scope.findAllElements(selector).map((element) => this.deprecate(element, targetName));
1433
1696
  }
1434
1697
  getLegacySelectorForTargetName(targetName) {
1435
1698
  const targetDescriptor = `${this.identifier}.${targetName}`;
@@ -1450,6 +1713,56 @@ class TargetSet {
1450
1713
  }
1451
1714
  }
1452
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
+
1453
1766
  class Scope {
1454
1767
  constructor(schema, element, identifier, logger) {
1455
1768
  this.targets = new TargetSet(this);
@@ -1462,16 +1775,15 @@ class Scope {
1462
1775
  this.element = element;
1463
1776
  this.identifier = identifier;
1464
1777
  this.guide = new Guide(logger);
1778
+ this.outlets = new OutletSet(this.documentScope, element);
1465
1779
  }
1466
1780
  findElement(selector) {
1467
- return this.element.matches(selector)
1468
- ? this.element
1469
- : this.queryElements(selector).find(this.containsElement);
1781
+ return this.element.matches(selector) ? this.element : this.queryElements(selector).find(this.containsElement);
1470
1782
  }
1471
1783
  findAllElements(selector) {
1472
1784
  return [
1473
- ...this.element.matches(selector) ? [this.element] : [],
1474
- ...this.queryElements(selector).filter(this.containsElement)
1785
+ ...(this.element.matches(selector) ? [this.element] : []),
1786
+ ...this.queryElements(selector).filter(this.containsElement),
1475
1787
  ];
1476
1788
  }
1477
1789
  queryElements(selector) {
@@ -1480,6 +1792,14 @@ class Scope {
1480
1792
  get controllerSelector() {
1481
1793
  return attributeValueContainsToken(this.schema.controllerAttribute, this.identifier);
1482
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
+ }
1483
1803
  }
1484
1804
 
1485
1805
  class ScopeObserver {
@@ -1488,8 +1808,8 @@ class ScopeObserver {
1488
1808
  this.schema = schema;
1489
1809
  this.delegate = delegate;
1490
1810
  this.valueListObserver = new ValueListObserver(this.element, this.controllerAttribute, this);
1491
- this.scopesByIdentifierByElement = new WeakMap;
1492
- this.scopeReferenceCounts = new WeakMap;
1811
+ this.scopesByIdentifierByElement = new WeakMap();
1812
+ this.scopeReferenceCounts = new WeakMap();
1493
1813
  }
1494
1814
  start() {
1495
1815
  this.valueListObserver.start();
@@ -1529,7 +1849,7 @@ class ScopeObserver {
1529
1849
  fetchScopesByIdentifierForElement(element) {
1530
1850
  let scopesByIdentifier = this.scopesByIdentifierByElement.get(element);
1531
1851
  if (!scopesByIdentifier) {
1532
- scopesByIdentifier = new Map;
1852
+ scopesByIdentifier = new Map();
1533
1853
  this.scopesByIdentifierByElement.set(element, scopesByIdentifier);
1534
1854
  }
1535
1855
  return scopesByIdentifier;
@@ -1540,8 +1860,8 @@ class Router {
1540
1860
  constructor(application) {
1541
1861
  this.application = application;
1542
1862
  this.scopeObserver = new ScopeObserver(this.element, this.schema, this);
1543
- this.scopesByIdentifier = new Multimap;
1544
- this.modulesByIdentifier = new Map;
1863
+ this.scopesByIdentifier = new Multimap();
1864
+ this.modulesByIdentifier = new Map();
1545
1865
  }
1546
1866
  get element() {
1547
1867
  return this.application.element;
@@ -1571,6 +1891,10 @@ class Router {
1571
1891
  this.unloadIdentifier(definition.identifier);
1572
1892
  const module = new Module(this.application, definition);
1573
1893
  this.connectModule(module);
1894
+ const afterLoad = definition.controllerConstructor.afterLoad;
1895
+ if (afterLoad) {
1896
+ afterLoad(definition.identifier, this.application);
1897
+ }
1574
1898
  }
1575
1899
  unloadIdentifier(identifier) {
1576
1900
  const module = this.modulesByIdentifier.get(identifier);
@@ -1581,7 +1905,7 @@ class Router {
1581
1905
  getContextForElementAndIdentifier(element, identifier) {
1582
1906
  const module = this.modulesByIdentifier.get(identifier);
1583
1907
  if (module) {
1584
- return module.contexts.find(context => context.element == element);
1908
+ return module.contexts.find((context) => context.element == element);
1585
1909
  }
1586
1910
  }
1587
1911
  handleError(error, message, detail) {
@@ -1607,12 +1931,12 @@ class Router {
1607
1931
  connectModule(module) {
1608
1932
  this.modulesByIdentifier.set(module.identifier, module);
1609
1933
  const scopes = this.scopesByIdentifier.getValuesForKey(module.identifier);
1610
- scopes.forEach(scope => module.connectContextForScope(scope));
1934
+ scopes.forEach((scope) => module.connectContextForScope(scope));
1611
1935
  }
1612
1936
  disconnectModule(module) {
1613
1937
  this.modulesByIdentifier.delete(module.identifier);
1614
1938
  const scopes = this.scopesByIdentifier.getValuesForKey(module.identifier);
1615
- scopes.forEach(scope => module.disconnectContextForScope(scope));
1939
+ scopes.forEach((scope) => module.disconnectContextForScope(scope));
1616
1940
  }
1617
1941
  }
1618
1942
 
@@ -1620,8 +1944,13 @@ const defaultSchema = {
1620
1944
  controllerAttribute: "data-controller",
1621
1945
  actionAttribute: "data-action",
1622
1946
  targetAttribute: "data-target",
1623
- targetAttributeForScope: identifier => `data-${identifier}-target`
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]))),
1624
1950
  };
1951
+ function objectFromEntries(array) {
1952
+ return array.reduce((memo, [k, v]) => (Object.assign(Object.assign({}, memo), { [k]: v })), {});
1953
+ }
1625
1954
 
1626
1955
  class Application {
1627
1956
  constructor(element = document.documentElement, schema = defaultSchema) {
@@ -1636,9 +1965,10 @@ class Application {
1636
1965
  this.schema = schema;
1637
1966
  this.dispatcher = new Dispatcher(this);
1638
1967
  this.router = new Router(this);
1968
+ this.actionDescriptorFilters = Object.assign({}, defaultActionDescriptorFilters);
1639
1969
  }
1640
1970
  static start(element, schema) {
1641
- const application = new Application(element, schema);
1971
+ const application = new this(element, schema);
1642
1972
  application.start();
1643
1973
  return application;
1644
1974
  }
@@ -1658,9 +1988,12 @@ class Application {
1658
1988
  register(identifier, controllerConstructor) {
1659
1989
  this.load({ identifier, controllerConstructor });
1660
1990
  }
1991
+ registerActionOption(name, filter) {
1992
+ this.actionDescriptorFilters[name] = filter;
1993
+ }
1661
1994
  load(head, ...rest) {
1662
1995
  const definitions = Array.isArray(head) ? head : [head, ...rest];
1663
- definitions.forEach(definition => {
1996
+ definitions.forEach((definition) => {
1664
1997
  if (definition.controllerConstructor.shouldLoad) {
1665
1998
  this.router.loadDefinition(definition);
1666
1999
  }
@@ -1668,10 +2001,10 @@ class Application {
1668
2001
  }
1669
2002
  unload(head, ...rest) {
1670
2003
  const identifiers = Array.isArray(head) ? head : [head, ...rest];
1671
- identifiers.forEach(identifier => this.router.unloadIdentifier(identifier));
2004
+ identifiers.forEach((identifier) => this.router.unloadIdentifier(identifier));
1672
2005
  }
1673
2006
  get controllers() {
1674
- return this.router.contexts.map(context => context.controller);
2007
+ return this.router.contexts.map((context) => context.controller);
1675
2008
  }
1676
2009
  getControllerForElementAndIdentifier(element, identifier) {
1677
2010
  const context = this.router.getContextForElementAndIdentifier(element, identifier);
@@ -1690,7 +2023,7 @@ class Application {
1690
2023
  }
1691
2024
  }
1692
2025
  function domReady() {
1693
- return new Promise(resolve => {
2026
+ return new Promise((resolve) => {
1694
2027
  if (document.readyState == "loading") {
1695
2028
  document.addEventListener("DOMContentLoaded", () => resolve());
1696
2029
  }
@@ -1718,18 +2051,85 @@ function propertiesForClassDefinition(key) {
1718
2051
  const attribute = classes.getAttributeName(key);
1719
2052
  throw new Error(`Missing attribute "${attribute}"`);
1720
2053
  }
1721
- }
2054
+ },
1722
2055
  },
1723
2056
  [`${key}Classes`]: {
1724
2057
  get() {
1725
2058
  return this.classes.getAll(key);
1726
- }
2059
+ },
1727
2060
  },
1728
2061
  [`has${capitalize(key)}Class`]: {
1729
2062
  get() {
1730
2063
  return this.classes.has(key);
1731
- }
1732
- }
2064
+ },
2065
+ },
2066
+ };
2067
+ }
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
+ },
1733
2133
  };
1734
2134
  }
1735
2135
 
@@ -1750,18 +2150,18 @@ function propertiesForTargetDefinition(name) {
1750
2150
  else {
1751
2151
  throw new Error(`Missing target element "${name}" for "${this.identifier}" controller`);
1752
2152
  }
1753
- }
2153
+ },
1754
2154
  },
1755
2155
  [`${name}Targets`]: {
1756
2156
  get() {
1757
2157
  return this.targets.findAll(name);
1758
- }
2158
+ },
1759
2159
  },
1760
2160
  [`has${capitalize(name)}Target`]: {
1761
2161
  get() {
1762
2162
  return this.targets.has(name);
1763
- }
1764
- }
2163
+ },
2164
+ },
1765
2165
  };
1766
2166
  }
1767
2167
 
@@ -1775,8 +2175,8 @@ function ValuePropertiesBlessing(constructor) {
1775
2175
  const attributeName = this.data.getAttributeNameForKey(valueDescriptor.key);
1776
2176
  return Object.assign(result, { [attributeName]: valueDescriptor });
1777
2177
  }, {});
1778
- }
1779
- }
2178
+ },
2179
+ },
1780
2180
  };
1781
2181
  return valueDefinitionPairs.reduce((properties, valueDefinitionPair) => {
1782
2182
  return Object.assign(properties, propertiesForValueDefinitionPair(valueDefinitionPair));
@@ -1803,13 +2203,13 @@ function propertiesForValueDefinitionPair(valueDefinitionPair, controller) {
1803
2203
  else {
1804
2204
  this.data.set(key, write(value));
1805
2205
  }
1806
- }
2206
+ },
1807
2207
  },
1808
2208
  [`has${capitalize(name)}`]: {
1809
2209
  get() {
1810
2210
  return this.data.has(key) || definition.hasCustomDefaultValue;
1811
- }
1812
- }
2211
+ },
2212
+ },
1813
2213
  };
1814
2214
  }
1815
2215
  function parseValueDefinitionPair([token, typeDefinition], controller) {
@@ -1821,18 +2221,26 @@ function parseValueDefinitionPair([token, typeDefinition], controller) {
1821
2221
  }
1822
2222
  function parseValueTypeConstant(constant) {
1823
2223
  switch (constant) {
1824
- case Array: return "array";
1825
- case Boolean: return "boolean";
1826
- case Number: return "number";
1827
- case Object: return "object";
1828
- case String: return "string";
2224
+ case Array:
2225
+ return "array";
2226
+ case Boolean:
2227
+ return "boolean";
2228
+ case Number:
2229
+ return "number";
2230
+ case Object:
2231
+ return "object";
2232
+ case String:
2233
+ return "string";
1829
2234
  }
1830
2235
  }
1831
2236
  function parseValueTypeDefault(defaultValue) {
1832
2237
  switch (typeof defaultValue) {
1833
- case "boolean": return "boolean";
1834
- case "number": return "number";
1835
- case "string": return "string";
2238
+ case "boolean":
2239
+ return "boolean";
2240
+ case "number":
2241
+ return "number";
2242
+ case "string":
2243
+ return "string";
1836
2244
  }
1837
2245
  if (Array.isArray(defaultValue))
1838
2246
  return "array";
@@ -1854,7 +2262,7 @@ function parseValueTypeDefinition(payload) {
1854
2262
  const typeFromObject = parseValueTypeObject({
1855
2263
  controller: payload.controller,
1856
2264
  token: payload.token,
1857
- typeObject: payload.typeDefinition
2265
+ typeObject: payload.typeDefinition,
1858
2266
  });
1859
2267
  const typeFromDefaultValue = parseValueTypeDefault(payload.typeDefinition);
1860
2268
  const typeFromConstant = parseValueTypeConstant(payload.typeDefinition);
@@ -1880,18 +2288,26 @@ function valueDescriptorForTokenAndTypeDefinition(payload) {
1880
2288
  type,
1881
2289
  key,
1882
2290
  name: camelize(key),
1883
- get defaultValue() { return defaultValueForDefinition(payload.typeDefinition); },
1884
- get hasCustomDefaultValue() { return parseValueTypeDefault(payload.typeDefinition) !== undefined; },
2291
+ get defaultValue() {
2292
+ return defaultValueForDefinition(payload.typeDefinition);
2293
+ },
2294
+ get hasCustomDefaultValue() {
2295
+ return parseValueTypeDefault(payload.typeDefinition) !== undefined;
2296
+ },
1885
2297
  reader: readers[type],
1886
- writer: writers[type] || writers.default
2298
+ writer: writers[type] || writers.default,
1887
2299
  };
1888
2300
  }
1889
2301
  const defaultValuesByType = {
1890
- get array() { return []; },
2302
+ get array() {
2303
+ return [];
2304
+ },
1891
2305
  boolean: false,
1892
2306
  number: 0,
1893
- get object() { return {}; },
1894
- string: ""
2307
+ get object() {
2308
+ return {};
2309
+ },
2310
+ string: "",
1895
2311
  };
1896
2312
  const readers = {
1897
2313
  array(value) {
@@ -1916,12 +2332,12 @@ const readers = {
1916
2332
  },
1917
2333
  string(value) {
1918
2334
  return value;
1919
- }
2335
+ },
1920
2336
  };
1921
2337
  const writers = {
1922
2338
  default: writeString,
1923
2339
  array: writeJSON,
1924
- object: writeJSON
2340
+ object: writeJSON,
1925
2341
  };
1926
2342
  function writeJSON(value) {
1927
2343
  return JSON.stringify(value);
@@ -1937,6 +2353,9 @@ class Controller {
1937
2353
  static get shouldLoad() {
1938
2354
  return true;
1939
2355
  }
2356
+ static afterLoad(_identifier, _application) {
2357
+ return;
2358
+ }
1940
2359
  get application() {
1941
2360
  return this.context.application;
1942
2361
  }
@@ -1952,6 +2371,9 @@ class Controller {
1952
2371
  get targets() {
1953
2372
  return this.scope.targets;
1954
2373
  }
2374
+ get outlets() {
2375
+ return this.scope.outlets;
2376
+ }
1955
2377
  get classes() {
1956
2378
  return this.scope.classes;
1957
2379
  }
@@ -1971,8 +2393,14 @@ class Controller {
1971
2393
  return event;
1972
2394
  }
1973
2395
  }
1974
- Controller.blessings = [ClassPropertiesBlessing, TargetPropertiesBlessing, ValuePropertiesBlessing];
2396
+ Controller.blessings = [
2397
+ ClassPropertiesBlessing,
2398
+ TargetPropertiesBlessing,
2399
+ ValuePropertiesBlessing,
2400
+ OutletPropertiesBlessing,
2401
+ ];
1975
2402
  Controller.targets = [];
2403
+ Controller.outlets = [];
1976
2404
  Controller.values = {};
1977
2405
 
1978
- 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 };