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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/stimulus.js +508 -136
- data/app/assets/javascripts/stimulus.min.js +1 -1
- data/app/assets/javascripts/stimulus.min.js.map +1 -1
- data/lib/stimulus/version.rb +1 -1
- metadata +3 -3
@@ -3,7 +3,7 @@
|
|
3
3
|
//= link ./stimulus-loading.js
|
4
4
|
|
5
5
|
/*
|
6
|
-
Stimulus 3.
|
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 = /^((.+?)(
|
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[
|
178
|
-
eventName: matches[
|
179
|
-
eventOptions: matches[
|
180
|
-
identifier: matches[
|
181
|
-
methodName: matches[
|
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
|
234
|
-
|
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
|
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 = [
|
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 };
|