stimulus-rails 1.0.4 → 1.1.1
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 +244 -158
- data/app/assets/javascripts/stimulus.min.js +1 -1
- data/app/assets/javascripts/stimulus.min.js.map +1 -1
- data/lib/stimulus/engine.rb +9 -1
- data/lib/stimulus/manifest.rb +1 -1
- data/lib/stimulus/version.rb +1 -1
- metadata +3 -3
@@ -3,8 +3,8 @@
|
|
3
3
|
//= link ./stimulus-loading.js
|
4
4
|
|
5
5
|
/*
|
6
|
-
Stimulus 3.
|
7
|
-
Copyright ©
|
6
|
+
Stimulus 3.1.1
|
7
|
+
Copyright © 2022 Basecamp, LLC
|
8
8
|
*/
|
9
9
|
class EventListener {
|
10
10
|
constructor(eventTarget, eventName, eventOptions) {
|
@@ -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,20 +133,42 @@ 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)
|
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
|
|
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
|
+
};
|
131
172
|
const descriptorPattern = /^((.+?)(@(window|document))?->)?(.+?)(#([^:]+?))(:(.+))?$/;
|
132
173
|
function parseActionDescriptorString(descriptorString) {
|
133
174
|
const source = descriptorString.trim();
|
@@ -137,7 +178,7 @@ function parseActionDescriptorString(descriptorString) {
|
|
137
178
|
eventName: matches[2],
|
138
179
|
eventOptions: matches[9] ? parseEventOptions(matches[9]) : {},
|
139
180
|
identifier: matches[5],
|
140
|
-
methodName: matches[7]
|
181
|
+
methodName: matches[7],
|
141
182
|
};
|
142
183
|
}
|
143
184
|
function parseEventTarget(eventTargetName) {
|
@@ -149,7 +190,9 @@ function parseEventTarget(eventTargetName) {
|
|
149
190
|
}
|
150
191
|
}
|
151
192
|
function parseEventOptions(eventOptions) {
|
152
|
-
return eventOptions
|
193
|
+
return eventOptions
|
194
|
+
.split(":")
|
195
|
+
.reduce((options, token) => Object.assign(options, { [token.replace(/^!/, "")]: !/^!/.test(token) }), {});
|
153
196
|
}
|
154
197
|
function stringifyEventTarget(eventTarget) {
|
155
198
|
if (eventTarget == window) {
|
@@ -191,24 +234,15 @@ class Action {
|
|
191
234
|
return `${this.eventName}${eventNameSuffix}->${this.identifier}#${this.methodName}`;
|
192
235
|
}
|
193
236
|
get params() {
|
194
|
-
if (this.eventTarget instanceof Element) {
|
195
|
-
return this.getParamsFromEventTargetAttributes(this.eventTarget);
|
196
|
-
}
|
197
|
-
else {
|
198
|
-
return {};
|
199
|
-
}
|
200
|
-
}
|
201
|
-
getParamsFromEventTargetAttributes(eventTarget) {
|
202
237
|
const params = {};
|
203
|
-
const pattern = new RegExp(`^data-${this.identifier}-(.+)-param
|
204
|
-
const
|
205
|
-
attributes.forEach(({ name, value }) => {
|
238
|
+
const pattern = new RegExp(`^data-${this.identifier}-(.+)-param$`, "i");
|
239
|
+
for (const { name, value } of Array.from(this.element.attributes)) {
|
206
240
|
const match = name.match(pattern);
|
207
241
|
const key = match && match[1];
|
208
242
|
if (key) {
|
209
|
-
|
243
|
+
params[camelize(key)] = typecast(value);
|
210
244
|
}
|
211
|
-
}
|
245
|
+
}
|
212
246
|
return params;
|
213
247
|
}
|
214
248
|
get eventTargetName() {
|
@@ -216,13 +250,13 @@ class Action {
|
|
216
250
|
}
|
217
251
|
}
|
218
252
|
const defaultEventNames = {
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
253
|
+
a: () => "click",
|
254
|
+
button: () => "click",
|
255
|
+
form: () => "submit",
|
256
|
+
details: () => "toggle",
|
257
|
+
input: (e) => (e.getAttribute("type") == "submit" ? "click" : "input"),
|
258
|
+
select: () => "change",
|
259
|
+
textarea: () => "input",
|
226
260
|
};
|
227
261
|
function getDefaultEventNameForElement(element) {
|
228
262
|
const tagName = element.tagName.toLowerCase();
|
@@ -260,7 +294,7 @@ class Binding {
|
|
260
294
|
return this.context.identifier;
|
261
295
|
}
|
262
296
|
handleEvent(event) {
|
263
|
-
if (this.willBeInvokedByEvent(event)) {
|
297
|
+
if (this.willBeInvokedByEvent(event) && this.applyEventModifiers(event)) {
|
264
298
|
this.invokeWithEvent(event);
|
265
299
|
}
|
266
300
|
}
|
@@ -274,6 +308,21 @@ class Binding {
|
|
274
308
|
}
|
275
309
|
throw new Error(`Action "${this.action}" references undefined method "${this.methodName}"`);
|
276
310
|
}
|
311
|
+
applyEventModifiers(event) {
|
312
|
+
const { element } = this.action;
|
313
|
+
const { actionDescriptorFilters } = this.context.application;
|
314
|
+
let passes = true;
|
315
|
+
for (const [name, value] of Object.entries(this.eventOptions)) {
|
316
|
+
if (name in actionDescriptorFilters) {
|
317
|
+
const filter = actionDescriptorFilters[name];
|
318
|
+
passes = passes && filter({ name, value, event, element });
|
319
|
+
}
|
320
|
+
else {
|
321
|
+
continue;
|
322
|
+
}
|
323
|
+
}
|
324
|
+
return passes;
|
325
|
+
}
|
277
326
|
invokeWithEvent(event) {
|
278
327
|
const { target, currentTarget } = event;
|
279
328
|
try {
|
@@ -320,7 +369,7 @@ class ElementObserver {
|
|
320
369
|
this.element = element;
|
321
370
|
this.started = false;
|
322
371
|
this.delegate = delegate;
|
323
|
-
this.elements = new Set;
|
372
|
+
this.elements = new Set();
|
324
373
|
this.mutationObserver = new MutationObserver((mutations) => this.processMutations(mutations));
|
325
374
|
}
|
326
375
|
start() {
|
@@ -508,8 +557,8 @@ class StringMapObserver {
|
|
508
557
|
this.element = element;
|
509
558
|
this.delegate = delegate;
|
510
559
|
this.started = false;
|
511
|
-
this.stringMap = new Map;
|
512
|
-
this.mutationObserver = new MutationObserver(mutations => this.processMutations(mutations));
|
560
|
+
this.stringMap = new Map();
|
561
|
+
this.mutationObserver = new MutationObserver((mutations) => this.processMutations(mutations));
|
513
562
|
}
|
514
563
|
start() {
|
515
564
|
if (!this.started) {
|
@@ -585,7 +634,7 @@ class StringMapObserver {
|
|
585
634
|
return Array.from(new Set(this.currentAttributeNames.concat(this.recordedAttributeNames)));
|
586
635
|
}
|
587
636
|
get currentAttributeNames() {
|
588
|
-
return Array.from(this.element.attributes).map(attribute => attribute.name);
|
637
|
+
return Array.from(this.element.attributes).map((attribute) => attribute.name);
|
589
638
|
}
|
590
639
|
get recordedAttributeNames() {
|
591
640
|
return Array.from(this.stringMap.keys());
|
@@ -644,7 +693,7 @@ class Multimap {
|
|
644
693
|
}
|
645
694
|
hasValue(value) {
|
646
695
|
const sets = Array.from(this.valuesByKey.values());
|
647
|
-
return sets.some(set => set.has(value));
|
696
|
+
return sets.some((set) => set.has(value));
|
648
697
|
}
|
649
698
|
getValuesForKey(key) {
|
650
699
|
const values = this.valuesByKey.get(key);
|
@@ -652,15 +701,15 @@ class Multimap {
|
|
652
701
|
}
|
653
702
|
getKeysForValue(value) {
|
654
703
|
return Array.from(this.valuesByKey)
|
655
|
-
.filter(([
|
656
|
-
.map(([key,
|
704
|
+
.filter(([_key, values]) => values.has(value))
|
705
|
+
.map(([key, _values]) => key);
|
657
706
|
}
|
658
707
|
}
|
659
708
|
|
660
709
|
class IndexedMultimap extends Multimap {
|
661
710
|
constructor() {
|
662
711
|
super();
|
663
|
-
this.keysByValue = new Map;
|
712
|
+
this.keysByValue = new Map();
|
664
713
|
}
|
665
714
|
get values() {
|
666
715
|
return Array.from(this.keysByValue.keys());
|
@@ -686,7 +735,7 @@ class TokenListObserver {
|
|
686
735
|
constructor(element, attributeName, delegate) {
|
687
736
|
this.attributeObserver = new AttributeObserver(element, attributeName, this);
|
688
737
|
this.delegate = delegate;
|
689
|
-
this.tokensByElement = new Multimap;
|
738
|
+
this.tokensByElement = new Multimap();
|
690
739
|
}
|
691
740
|
get started() {
|
692
741
|
return this.attributeObserver.started;
|
@@ -721,10 +770,10 @@ class TokenListObserver {
|
|
721
770
|
this.tokensUnmatched(this.tokensByElement.getValuesForKey(element));
|
722
771
|
}
|
723
772
|
tokensMatched(tokens) {
|
724
|
-
tokens.forEach(token => this.tokenMatched(token));
|
773
|
+
tokens.forEach((token) => this.tokenMatched(token));
|
725
774
|
}
|
726
775
|
tokensUnmatched(tokens) {
|
727
|
-
tokens.forEach(token => this.tokenUnmatched(token));
|
776
|
+
tokens.forEach((token) => this.tokenUnmatched(token));
|
728
777
|
}
|
729
778
|
tokenMatched(token) {
|
730
779
|
this.delegate.tokenMatched(token);
|
@@ -737,8 +786,7 @@ class TokenListObserver {
|
|
737
786
|
refreshTokensForElement(element) {
|
738
787
|
const previousTokens = this.tokensByElement.getValuesForKey(element);
|
739
788
|
const currentTokens = this.readTokensForElement(element);
|
740
|
-
const firstDifferingIndex = zip(previousTokens, currentTokens)
|
741
|
-
.findIndex(([previousToken, currentToken]) => !tokensAreEqual(previousToken, currentToken));
|
789
|
+
const firstDifferingIndex = zip(previousTokens, currentTokens).findIndex(([previousToken, currentToken]) => !tokensAreEqual(previousToken, currentToken));
|
742
790
|
if (firstDifferingIndex == -1) {
|
743
791
|
return [[], []];
|
744
792
|
}
|
@@ -753,7 +801,10 @@ class TokenListObserver {
|
|
753
801
|
}
|
754
802
|
}
|
755
803
|
function parseTokenString(tokenString, element, attributeName) {
|
756
|
-
return tokenString
|
804
|
+
return tokenString
|
805
|
+
.trim()
|
806
|
+
.split(/\s+/)
|
807
|
+
.filter((content) => content.length)
|
757
808
|
.map((content, index) => ({ element, attributeName, content, index }));
|
758
809
|
}
|
759
810
|
function zip(left, right) {
|
@@ -768,8 +819,8 @@ class ValueListObserver {
|
|
768
819
|
constructor(element, attributeName, delegate) {
|
769
820
|
this.tokenListObserver = new TokenListObserver(element, attributeName, this);
|
770
821
|
this.delegate = delegate;
|
771
|
-
this.parseResultsByToken = new WeakMap;
|
772
|
-
this.valuesByTokenByElement = new WeakMap;
|
822
|
+
this.parseResultsByToken = new WeakMap();
|
823
|
+
this.valuesByTokenByElement = new WeakMap();
|
773
824
|
}
|
774
825
|
get started() {
|
775
826
|
return this.tokenListObserver.started;
|
@@ -816,7 +867,7 @@ class ValueListObserver {
|
|
816
867
|
fetchValuesByTokenForElement(element) {
|
817
868
|
let valuesByToken = this.valuesByTokenByElement.get(element);
|
818
869
|
if (!valuesByToken) {
|
819
|
-
valuesByToken = new Map;
|
870
|
+
valuesByToken = new Map();
|
820
871
|
this.valuesByTokenByElement.set(element, valuesByToken);
|
821
872
|
}
|
822
873
|
return valuesByToken;
|
@@ -836,7 +887,7 @@ class BindingObserver {
|
|
836
887
|
constructor(context, delegate) {
|
837
888
|
this.context = context;
|
838
889
|
this.delegate = delegate;
|
839
|
-
this.bindingsByAction = new Map;
|
890
|
+
this.bindingsByAction = new Map();
|
840
891
|
}
|
841
892
|
start() {
|
842
893
|
if (!this.valueListObserver) {
|
@@ -879,7 +930,7 @@ class BindingObserver {
|
|
879
930
|
}
|
880
931
|
}
|
881
932
|
disconnectAllActions() {
|
882
|
-
this.bindings.forEach(binding => this.delegate.bindingDisconnected(binding));
|
933
|
+
this.bindings.forEach((binding) => this.delegate.bindingDisconnected(binding, true));
|
883
934
|
this.bindingsByAction.clear();
|
884
935
|
}
|
885
936
|
parseValueForToken(token) {
|
@@ -902,10 +953,10 @@ class ValueObserver {
|
|
902
953
|
this.receiver = receiver;
|
903
954
|
this.stringMapObserver = new StringMapObserver(this.element, this);
|
904
955
|
this.valueDescriptorMap = this.controller.valueDescriptorMap;
|
905
|
-
this.invokeChangedCallbacksForDefaultValues();
|
906
956
|
}
|
907
957
|
start() {
|
908
958
|
this.stringMapObserver.start();
|
959
|
+
this.invokeChangedCallbacksForDefaultValues();
|
909
960
|
}
|
910
961
|
stop() {
|
911
962
|
this.stringMapObserver.stop();
|
@@ -957,21 +1008,29 @@ class ValueObserver {
|
|
957
1008
|
const changedMethod = this.receiver[changedMethodName];
|
958
1009
|
if (typeof changedMethod == "function") {
|
959
1010
|
const descriptor = this.valueDescriptorNameMap[name];
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
1011
|
+
try {
|
1012
|
+
const value = descriptor.reader(rawValue);
|
1013
|
+
let oldValue = rawOldValue;
|
1014
|
+
if (rawOldValue) {
|
1015
|
+
oldValue = descriptor.reader(rawOldValue);
|
1016
|
+
}
|
1017
|
+
changedMethod.call(this.receiver, value, oldValue);
|
1018
|
+
}
|
1019
|
+
catch (error) {
|
1020
|
+
if (error instanceof TypeError) {
|
1021
|
+
error.message = `Stimulus Value "${this.context.identifier}.${descriptor.name}" - ${error.message}`;
|
1022
|
+
}
|
1023
|
+
throw error;
|
964
1024
|
}
|
965
|
-
changedMethod.call(this.receiver, value, oldValue);
|
966
1025
|
}
|
967
1026
|
}
|
968
1027
|
get valueDescriptors() {
|
969
1028
|
const { valueDescriptorMap } = this;
|
970
|
-
return Object.keys(valueDescriptorMap).map(key => valueDescriptorMap[key]);
|
1029
|
+
return Object.keys(valueDescriptorMap).map((key) => valueDescriptorMap[key]);
|
971
1030
|
}
|
972
1031
|
get valueDescriptorNameMap() {
|
973
1032
|
const descriptors = {};
|
974
|
-
Object.keys(this.valueDescriptorMap).forEach(key => {
|
1033
|
+
Object.keys(this.valueDescriptorMap).forEach((key) => {
|
975
1034
|
const descriptor = this.valueDescriptorMap[key];
|
976
1035
|
descriptors[descriptor.name] = descriptor;
|
977
1036
|
});
|
@@ -988,7 +1047,7 @@ class TargetObserver {
|
|
988
1047
|
constructor(context, delegate) {
|
989
1048
|
this.context = context;
|
990
1049
|
this.delegate = delegate;
|
991
|
-
this.targetsByName = new Multimap;
|
1050
|
+
this.targetsByName = new Multimap();
|
992
1051
|
}
|
993
1052
|
start() {
|
994
1053
|
if (!this.tokenListObserver) {
|
@@ -1128,9 +1187,9 @@ class Context {
|
|
1128
1187
|
function readInheritableStaticArrayValues(constructor, propertyName) {
|
1129
1188
|
const ancestors = getAncestorsForConstructor(constructor);
|
1130
1189
|
return Array.from(ancestors.reduce((values, constructor) => {
|
1131
|
-
getOwnStaticArrayValues(constructor, propertyName).forEach(name => values.add(name));
|
1190
|
+
getOwnStaticArrayValues(constructor, propertyName).forEach((name) => values.add(name));
|
1132
1191
|
return values;
|
1133
|
-
}, new Set));
|
1192
|
+
}, new Set()));
|
1134
1193
|
}
|
1135
1194
|
function readInheritableStaticObjectPairs(constructor, propertyName) {
|
1136
1195
|
const ancestors = getAncestorsForConstructor(constructor);
|
@@ -1153,7 +1212,7 @@ function getOwnStaticArrayValues(constructor, propertyName) {
|
|
1153
1212
|
}
|
1154
1213
|
function getOwnStaticObjectPairs(constructor, propertyName) {
|
1155
1214
|
const definition = constructor[propertyName];
|
1156
|
-
return definition ? Object.keys(definition).map(key => [key, definition[key]]) : [];
|
1215
|
+
return definition ? Object.keys(definition).map((key) => [key, definition[key]]) : [];
|
1157
1216
|
}
|
1158
1217
|
|
1159
1218
|
function bless(constructor) {
|
@@ -1199,10 +1258,7 @@ function getShadowedDescriptor(prototype, properties, key) {
|
|
1199
1258
|
}
|
1200
1259
|
const getOwnKeys = (() => {
|
1201
1260
|
if (typeof Object.getOwnPropertySymbols == "function") {
|
1202
|
-
return (object) => [
|
1203
|
-
...Object.getOwnPropertyNames(object),
|
1204
|
-
...Object.getOwnPropertySymbols(object)
|
1205
|
-
];
|
1261
|
+
return (object) => [...Object.getOwnPropertyNames(object), ...Object.getOwnPropertySymbols(object)];
|
1206
1262
|
}
|
1207
1263
|
else {
|
1208
1264
|
return Object.getOwnPropertyNames;
|
@@ -1214,16 +1270,18 @@ const extend = (() => {
|
|
1214
1270
|
return Reflect.construct(constructor, arguments, new.target);
|
1215
1271
|
}
|
1216
1272
|
extended.prototype = Object.create(constructor.prototype, {
|
1217
|
-
constructor: { value: extended }
|
1273
|
+
constructor: { value: extended },
|
1218
1274
|
});
|
1219
1275
|
Reflect.setPrototypeOf(extended, constructor);
|
1220
1276
|
return extended;
|
1221
1277
|
}
|
1222
1278
|
function testReflectExtension() {
|
1223
|
-
const a = function () {
|
1279
|
+
const a = function () {
|
1280
|
+
this.a.call(this);
|
1281
|
+
};
|
1224
1282
|
const b = extendWithReflect(a);
|
1225
1283
|
b.prototype.a = function () { };
|
1226
|
-
return new b;
|
1284
|
+
return new b();
|
1227
1285
|
}
|
1228
1286
|
try {
|
1229
1287
|
testReflectExtension();
|
@@ -1238,7 +1296,7 @@ const extend = (() => {
|
|
1238
1296
|
function blessDefinition(definition) {
|
1239
1297
|
return {
|
1240
1298
|
identifier: definition.identifier,
|
1241
|
-
controllerConstructor: bless(definition.controllerConstructor)
|
1299
|
+
controllerConstructor: bless(definition.controllerConstructor),
|
1242
1300
|
};
|
1243
1301
|
}
|
1244
1302
|
|
@@ -1246,8 +1304,8 @@ class Module {
|
|
1246
1304
|
constructor(application, definition) {
|
1247
1305
|
this.application = application;
|
1248
1306
|
this.definition = blessDefinition(definition);
|
1249
|
-
this.contextsByScope = new WeakMap;
|
1250
|
-
this.connectedContexts = new Set;
|
1307
|
+
this.contextsByScope = new WeakMap();
|
1308
|
+
this.connectedContexts = new Set();
|
1251
1309
|
}
|
1252
1310
|
get identifier() {
|
1253
1311
|
return this.definition.identifier;
|
@@ -1345,13 +1403,13 @@ class DataMap {
|
|
1345
1403
|
|
1346
1404
|
class Guide {
|
1347
1405
|
constructor(logger) {
|
1348
|
-
this.warnedKeysByObject = new WeakMap;
|
1406
|
+
this.warnedKeysByObject = new WeakMap();
|
1349
1407
|
this.logger = logger;
|
1350
1408
|
}
|
1351
1409
|
warn(object, key, message) {
|
1352
1410
|
let warnedKeys = this.warnedKeysByObject.get(object);
|
1353
1411
|
if (!warnedKeys) {
|
1354
|
-
warnedKeys = new Set;
|
1412
|
+
warnedKeys = new Set();
|
1355
1413
|
this.warnedKeysByObject.set(object, warnedKeys);
|
1356
1414
|
}
|
1357
1415
|
if (!warnedKeys.has(key)) {
|
@@ -1382,15 +1440,13 @@ class TargetSet {
|
|
1382
1440
|
return this.find(targetName) != null;
|
1383
1441
|
}
|
1384
1442
|
find(...targetNames) {
|
1385
|
-
return targetNames.reduce((target, targetName) => target
|
1386
|
-
|| this.findTarget(targetName)
|
1387
|
-
|| this.findLegacyTarget(targetName), undefined);
|
1443
|
+
return targetNames.reduce((target, targetName) => target || this.findTarget(targetName) || this.findLegacyTarget(targetName), undefined);
|
1388
1444
|
}
|
1389
1445
|
findAll(...targetNames) {
|
1390
1446
|
return targetNames.reduce((targets, targetName) => [
|
1391
1447
|
...targets,
|
1392
1448
|
...this.findAllTargets(targetName),
|
1393
|
-
...this.findAllLegacyTargets(targetName)
|
1449
|
+
...this.findAllLegacyTargets(targetName),
|
1394
1450
|
], []);
|
1395
1451
|
}
|
1396
1452
|
findTarget(targetName) {
|
@@ -1411,7 +1467,7 @@ class TargetSet {
|
|
1411
1467
|
}
|
1412
1468
|
findAllLegacyTargets(targetName) {
|
1413
1469
|
const selector = this.getLegacySelectorForTargetName(targetName);
|
1414
|
-
return this.scope.findAllElements(selector).map(element => this.deprecate(element, targetName));
|
1470
|
+
return this.scope.findAllElements(selector).map((element) => this.deprecate(element, targetName));
|
1415
1471
|
}
|
1416
1472
|
getLegacySelectorForTargetName(targetName) {
|
1417
1473
|
const targetDescriptor = `${this.identifier}.${targetName}`;
|
@@ -1446,14 +1502,12 @@ class Scope {
|
|
1446
1502
|
this.guide = new Guide(logger);
|
1447
1503
|
}
|
1448
1504
|
findElement(selector) {
|
1449
|
-
return this.element.matches(selector)
|
1450
|
-
? this.element
|
1451
|
-
: this.queryElements(selector).find(this.containsElement);
|
1505
|
+
return this.element.matches(selector) ? this.element : this.queryElements(selector).find(this.containsElement);
|
1452
1506
|
}
|
1453
1507
|
findAllElements(selector) {
|
1454
1508
|
return [
|
1455
|
-
...this.element.matches(selector) ? [this.element] : [],
|
1456
|
-
...this.queryElements(selector).filter(this.containsElement)
|
1509
|
+
...(this.element.matches(selector) ? [this.element] : []),
|
1510
|
+
...this.queryElements(selector).filter(this.containsElement),
|
1457
1511
|
];
|
1458
1512
|
}
|
1459
1513
|
queryElements(selector) {
|
@@ -1470,8 +1524,8 @@ class ScopeObserver {
|
|
1470
1524
|
this.schema = schema;
|
1471
1525
|
this.delegate = delegate;
|
1472
1526
|
this.valueListObserver = new ValueListObserver(this.element, this.controllerAttribute, this);
|
1473
|
-
this.scopesByIdentifierByElement = new WeakMap;
|
1474
|
-
this.scopeReferenceCounts = new WeakMap;
|
1527
|
+
this.scopesByIdentifierByElement = new WeakMap();
|
1528
|
+
this.scopeReferenceCounts = new WeakMap();
|
1475
1529
|
}
|
1476
1530
|
start() {
|
1477
1531
|
this.valueListObserver.start();
|
@@ -1511,7 +1565,7 @@ class ScopeObserver {
|
|
1511
1565
|
fetchScopesByIdentifierForElement(element) {
|
1512
1566
|
let scopesByIdentifier = this.scopesByIdentifierByElement.get(element);
|
1513
1567
|
if (!scopesByIdentifier) {
|
1514
|
-
scopesByIdentifier = new Map;
|
1568
|
+
scopesByIdentifier = new Map();
|
1515
1569
|
this.scopesByIdentifierByElement.set(element, scopesByIdentifier);
|
1516
1570
|
}
|
1517
1571
|
return scopesByIdentifier;
|
@@ -1522,8 +1576,8 @@ class Router {
|
|
1522
1576
|
constructor(application) {
|
1523
1577
|
this.application = application;
|
1524
1578
|
this.scopeObserver = new ScopeObserver(this.element, this.schema, this);
|
1525
|
-
this.scopesByIdentifier = new Multimap;
|
1526
|
-
this.modulesByIdentifier = new Map;
|
1579
|
+
this.scopesByIdentifier = new Multimap();
|
1580
|
+
this.modulesByIdentifier = new Map();
|
1527
1581
|
}
|
1528
1582
|
get element() {
|
1529
1583
|
return this.application.element;
|
@@ -1563,7 +1617,7 @@ class Router {
|
|
1563
1617
|
getContextForElementAndIdentifier(element, identifier) {
|
1564
1618
|
const module = this.modulesByIdentifier.get(identifier);
|
1565
1619
|
if (module) {
|
1566
|
-
return module.contexts.find(context => context.element == element);
|
1620
|
+
return module.contexts.find((context) => context.element == element);
|
1567
1621
|
}
|
1568
1622
|
}
|
1569
1623
|
handleError(error, message, detail) {
|
@@ -1589,12 +1643,12 @@ class Router {
|
|
1589
1643
|
connectModule(module) {
|
1590
1644
|
this.modulesByIdentifier.set(module.identifier, module);
|
1591
1645
|
const scopes = this.scopesByIdentifier.getValuesForKey(module.identifier);
|
1592
|
-
scopes.forEach(scope => module.connectContextForScope(scope));
|
1646
|
+
scopes.forEach((scope) => module.connectContextForScope(scope));
|
1593
1647
|
}
|
1594
1648
|
disconnectModule(module) {
|
1595
1649
|
this.modulesByIdentifier.delete(module.identifier);
|
1596
1650
|
const scopes = this.scopesByIdentifier.getValuesForKey(module.identifier);
|
1597
|
-
scopes.forEach(scope => module.disconnectContextForScope(scope));
|
1651
|
+
scopes.forEach((scope) => module.disconnectContextForScope(scope));
|
1598
1652
|
}
|
1599
1653
|
}
|
1600
1654
|
|
@@ -1602,7 +1656,7 @@ const defaultSchema = {
|
|
1602
1656
|
controllerAttribute: "data-controller",
|
1603
1657
|
actionAttribute: "data-action",
|
1604
1658
|
targetAttribute: "data-target",
|
1605
|
-
targetAttributeForScope: identifier => `data-${identifier}-target
|
1659
|
+
targetAttributeForScope: (identifier) => `data-${identifier}-target`,
|
1606
1660
|
};
|
1607
1661
|
|
1608
1662
|
class Application {
|
@@ -1618,6 +1672,7 @@ class Application {
|
|
1618
1672
|
this.schema = schema;
|
1619
1673
|
this.dispatcher = new Dispatcher(this);
|
1620
1674
|
this.router = new Router(this);
|
1675
|
+
this.actionDescriptorFilters = Object.assign({}, defaultActionDescriptorFilters);
|
1621
1676
|
}
|
1622
1677
|
static start(element, schema) {
|
1623
1678
|
const application = new Application(element, schema);
|
@@ -1638,20 +1693,25 @@ class Application {
|
|
1638
1693
|
this.logDebugActivity("application", "stop");
|
1639
1694
|
}
|
1640
1695
|
register(identifier, controllerConstructor) {
|
1641
|
-
|
1642
|
-
|
1643
|
-
|
1696
|
+
this.load({ identifier, controllerConstructor });
|
1697
|
+
}
|
1698
|
+
registerActionOption(name, filter) {
|
1699
|
+
this.actionDescriptorFilters[name] = filter;
|
1644
1700
|
}
|
1645
1701
|
load(head, ...rest) {
|
1646
1702
|
const definitions = Array.isArray(head) ? head : [head, ...rest];
|
1647
|
-
definitions.forEach(definition =>
|
1703
|
+
definitions.forEach((definition) => {
|
1704
|
+
if (definition.controllerConstructor.shouldLoad) {
|
1705
|
+
this.router.loadDefinition(definition);
|
1706
|
+
}
|
1707
|
+
});
|
1648
1708
|
}
|
1649
1709
|
unload(head, ...rest) {
|
1650
1710
|
const identifiers = Array.isArray(head) ? head : [head, ...rest];
|
1651
|
-
identifiers.forEach(identifier => this.router.unloadIdentifier(identifier));
|
1711
|
+
identifiers.forEach((identifier) => this.router.unloadIdentifier(identifier));
|
1652
1712
|
}
|
1653
1713
|
get controllers() {
|
1654
|
-
return this.router.contexts.map(context => context.controller);
|
1714
|
+
return this.router.contexts.map((context) => context.controller);
|
1655
1715
|
}
|
1656
1716
|
getControllerForElementAndIdentifier(element, identifier) {
|
1657
1717
|
const context = this.router.getContextForElementAndIdentifier(element, identifier);
|
@@ -1670,7 +1730,7 @@ class Application {
|
|
1670
1730
|
}
|
1671
1731
|
}
|
1672
1732
|
function domReady() {
|
1673
|
-
return new Promise(resolve => {
|
1733
|
+
return new Promise((resolve) => {
|
1674
1734
|
if (document.readyState == "loading") {
|
1675
1735
|
document.addEventListener("DOMContentLoaded", () => resolve());
|
1676
1736
|
}
|
@@ -1698,18 +1758,18 @@ function propertiesForClassDefinition(key) {
|
|
1698
1758
|
const attribute = classes.getAttributeName(key);
|
1699
1759
|
throw new Error(`Missing attribute "${attribute}"`);
|
1700
1760
|
}
|
1701
|
-
}
|
1761
|
+
},
|
1702
1762
|
},
|
1703
1763
|
[`${key}Classes`]: {
|
1704
1764
|
get() {
|
1705
1765
|
return this.classes.getAll(key);
|
1706
|
-
}
|
1766
|
+
},
|
1707
1767
|
},
|
1708
1768
|
[`has${capitalize(key)}Class`]: {
|
1709
1769
|
get() {
|
1710
1770
|
return this.classes.has(key);
|
1711
|
-
}
|
1712
|
-
}
|
1771
|
+
},
|
1772
|
+
},
|
1713
1773
|
};
|
1714
1774
|
}
|
1715
1775
|
|
@@ -1730,18 +1790,18 @@ function propertiesForTargetDefinition(name) {
|
|
1730
1790
|
else {
|
1731
1791
|
throw new Error(`Missing target element "${name}" for "${this.identifier}" controller`);
|
1732
1792
|
}
|
1733
|
-
}
|
1793
|
+
},
|
1734
1794
|
},
|
1735
1795
|
[`${name}Targets`]: {
|
1736
1796
|
get() {
|
1737
1797
|
return this.targets.findAll(name);
|
1738
|
-
}
|
1798
|
+
},
|
1739
1799
|
},
|
1740
1800
|
[`has${capitalize(name)}Target`]: {
|
1741
1801
|
get() {
|
1742
1802
|
return this.targets.has(name);
|
1743
|
-
}
|
1744
|
-
}
|
1803
|
+
},
|
1804
|
+
},
|
1745
1805
|
};
|
1746
1806
|
}
|
1747
1807
|
|
@@ -1751,19 +1811,19 @@ function ValuePropertiesBlessing(constructor) {
|
|
1751
1811
|
valueDescriptorMap: {
|
1752
1812
|
get() {
|
1753
1813
|
return valueDefinitionPairs.reduce((result, valueDefinitionPair) => {
|
1754
|
-
const valueDescriptor = parseValueDefinitionPair(valueDefinitionPair);
|
1814
|
+
const valueDescriptor = parseValueDefinitionPair(valueDefinitionPair, this.identifier);
|
1755
1815
|
const attributeName = this.data.getAttributeNameForKey(valueDescriptor.key);
|
1756
1816
|
return Object.assign(result, { [attributeName]: valueDescriptor });
|
1757
1817
|
}, {});
|
1758
|
-
}
|
1759
|
-
}
|
1818
|
+
},
|
1819
|
+
},
|
1760
1820
|
};
|
1761
1821
|
return valueDefinitionPairs.reduce((properties, valueDefinitionPair) => {
|
1762
1822
|
return Object.assign(properties, propertiesForValueDefinitionPair(valueDefinitionPair));
|
1763
1823
|
}, propertyDescriptorMap);
|
1764
1824
|
}
|
1765
|
-
function propertiesForValueDefinitionPair(valueDefinitionPair) {
|
1766
|
-
const definition = parseValueDefinitionPair(valueDefinitionPair);
|
1825
|
+
function propertiesForValueDefinitionPair(valueDefinitionPair, controller) {
|
1826
|
+
const definition = parseValueDefinitionPair(valueDefinitionPair, controller);
|
1767
1827
|
const { key, name, reader: read, writer: write } = definition;
|
1768
1828
|
return {
|
1769
1829
|
[name]: {
|
@@ -1783,56 +1843,74 @@ function propertiesForValueDefinitionPair(valueDefinitionPair) {
|
|
1783
1843
|
else {
|
1784
1844
|
this.data.set(key, write(value));
|
1785
1845
|
}
|
1786
|
-
}
|
1846
|
+
},
|
1787
1847
|
},
|
1788
1848
|
[`has${capitalize(name)}`]: {
|
1789
1849
|
get() {
|
1790
1850
|
return this.data.has(key) || definition.hasCustomDefaultValue;
|
1791
|
-
}
|
1792
|
-
}
|
1851
|
+
},
|
1852
|
+
},
|
1793
1853
|
};
|
1794
1854
|
}
|
1795
|
-
function parseValueDefinitionPair([token, typeDefinition]) {
|
1796
|
-
return valueDescriptorForTokenAndTypeDefinition(
|
1855
|
+
function parseValueDefinitionPair([token, typeDefinition], controller) {
|
1856
|
+
return valueDescriptorForTokenAndTypeDefinition({
|
1857
|
+
controller,
|
1858
|
+
token,
|
1859
|
+
typeDefinition,
|
1860
|
+
});
|
1797
1861
|
}
|
1798
1862
|
function parseValueTypeConstant(constant) {
|
1799
1863
|
switch (constant) {
|
1800
|
-
case Array:
|
1801
|
-
|
1802
|
-
case
|
1803
|
-
|
1804
|
-
case
|
1864
|
+
case Array:
|
1865
|
+
return "array";
|
1866
|
+
case Boolean:
|
1867
|
+
return "boolean";
|
1868
|
+
case Number:
|
1869
|
+
return "number";
|
1870
|
+
case Object:
|
1871
|
+
return "object";
|
1872
|
+
case String:
|
1873
|
+
return "string";
|
1805
1874
|
}
|
1806
1875
|
}
|
1807
1876
|
function parseValueTypeDefault(defaultValue) {
|
1808
1877
|
switch (typeof defaultValue) {
|
1809
|
-
case "boolean":
|
1810
|
-
|
1811
|
-
case "
|
1878
|
+
case "boolean":
|
1879
|
+
return "boolean";
|
1880
|
+
case "number":
|
1881
|
+
return "number";
|
1882
|
+
case "string":
|
1883
|
+
return "string";
|
1812
1884
|
}
|
1813
1885
|
if (Array.isArray(defaultValue))
|
1814
1886
|
return "array";
|
1815
1887
|
if (Object.prototype.toString.call(defaultValue) === "[object Object]")
|
1816
1888
|
return "object";
|
1817
1889
|
}
|
1818
|
-
function parseValueTypeObject(
|
1819
|
-
const typeFromObject = parseValueTypeConstant(typeObject.type);
|
1820
|
-
if (typeFromObject)
|
1821
|
-
|
1822
|
-
|
1823
|
-
|
1824
|
-
}
|
1825
|
-
|
1826
|
-
}
|
1827
|
-
|
1828
|
-
|
1829
|
-
|
1830
|
-
const
|
1831
|
-
|
1890
|
+
function parseValueTypeObject(payload) {
|
1891
|
+
const typeFromObject = parseValueTypeConstant(payload.typeObject.type);
|
1892
|
+
if (!typeFromObject)
|
1893
|
+
return;
|
1894
|
+
const defaultValueType = parseValueTypeDefault(payload.typeObject.default);
|
1895
|
+
if (typeFromObject !== defaultValueType) {
|
1896
|
+
const propertyPath = payload.controller ? `${payload.controller}.${payload.token}` : payload.token;
|
1897
|
+
throw new Error(`The specified default value for the Stimulus Value "${propertyPath}" must match the defined type "${typeFromObject}". The provided default value of "${payload.typeObject.default}" is of type "${defaultValueType}".`);
|
1898
|
+
}
|
1899
|
+
return typeFromObject;
|
1900
|
+
}
|
1901
|
+
function parseValueTypeDefinition(payload) {
|
1902
|
+
const typeFromObject = parseValueTypeObject({
|
1903
|
+
controller: payload.controller,
|
1904
|
+
token: payload.token,
|
1905
|
+
typeObject: payload.typeDefinition,
|
1906
|
+
});
|
1907
|
+
const typeFromDefaultValue = parseValueTypeDefault(payload.typeDefinition);
|
1908
|
+
const typeFromConstant = parseValueTypeConstant(payload.typeDefinition);
|
1832
1909
|
const type = typeFromObject || typeFromDefaultValue || typeFromConstant;
|
1833
1910
|
if (type)
|
1834
1911
|
return type;
|
1835
|
-
|
1912
|
+
const propertyPath = payload.controller ? `${payload.controller}.${payload.typeDefinition}` : payload.token;
|
1913
|
+
throw new Error(`Unknown value type "${propertyPath}" for "${payload.token}" value`);
|
1836
1914
|
}
|
1837
1915
|
function defaultValueForDefinition(typeDefinition) {
|
1838
1916
|
const constant = parseValueTypeConstant(typeDefinition);
|
@@ -1843,36 +1921,44 @@ function defaultValueForDefinition(typeDefinition) {
|
|
1843
1921
|
return defaultValue;
|
1844
1922
|
return typeDefinition;
|
1845
1923
|
}
|
1846
|
-
function valueDescriptorForTokenAndTypeDefinition(
|
1847
|
-
const key = `${dasherize(token)}-value`;
|
1848
|
-
const type = parseValueTypeDefinition(
|
1924
|
+
function valueDescriptorForTokenAndTypeDefinition(payload) {
|
1925
|
+
const key = `${dasherize(payload.token)}-value`;
|
1926
|
+
const type = parseValueTypeDefinition(payload);
|
1849
1927
|
return {
|
1850
1928
|
type,
|
1851
1929
|
key,
|
1852
1930
|
name: camelize(key),
|
1853
|
-
get defaultValue() {
|
1854
|
-
|
1931
|
+
get defaultValue() {
|
1932
|
+
return defaultValueForDefinition(payload.typeDefinition);
|
1933
|
+
},
|
1934
|
+
get hasCustomDefaultValue() {
|
1935
|
+
return parseValueTypeDefault(payload.typeDefinition) !== undefined;
|
1936
|
+
},
|
1855
1937
|
reader: readers[type],
|
1856
|
-
writer: writers[type] || writers.default
|
1938
|
+
writer: writers[type] || writers.default,
|
1857
1939
|
};
|
1858
1940
|
}
|
1859
1941
|
const defaultValuesByType = {
|
1860
|
-
get array() {
|
1942
|
+
get array() {
|
1943
|
+
return [];
|
1944
|
+
},
|
1861
1945
|
boolean: false,
|
1862
1946
|
number: 0,
|
1863
|
-
get object() {
|
1864
|
-
|
1947
|
+
get object() {
|
1948
|
+
return {};
|
1949
|
+
},
|
1950
|
+
string: "",
|
1865
1951
|
};
|
1866
1952
|
const readers = {
|
1867
1953
|
array(value) {
|
1868
1954
|
const array = JSON.parse(value);
|
1869
1955
|
if (!Array.isArray(array)) {
|
1870
|
-
throw new TypeError("
|
1956
|
+
throw new TypeError(`expected value of type "array" but instead got value "${value}" of type "${parseValueTypeDefault(array)}"`);
|
1871
1957
|
}
|
1872
1958
|
return array;
|
1873
1959
|
},
|
1874
1960
|
boolean(value) {
|
1875
|
-
return !(value == "0" || value == "false");
|
1961
|
+
return !(value == "0" || String(value).toLowerCase() == "false");
|
1876
1962
|
},
|
1877
1963
|
number(value) {
|
1878
1964
|
return Number(value);
|
@@ -1880,18 +1966,18 @@ const readers = {
|
|
1880
1966
|
object(value) {
|
1881
1967
|
const object = JSON.parse(value);
|
1882
1968
|
if (object === null || typeof object != "object" || Array.isArray(object)) {
|
1883
|
-
throw new TypeError("
|
1969
|
+
throw new TypeError(`expected value of type "object" but instead got value "${value}" of type "${parseValueTypeDefault(object)}"`);
|
1884
1970
|
}
|
1885
1971
|
return object;
|
1886
1972
|
},
|
1887
1973
|
string(value) {
|
1888
1974
|
return value;
|
1889
|
-
}
|
1975
|
+
},
|
1890
1976
|
};
|
1891
1977
|
const writers = {
|
1892
1978
|
default: writeString,
|
1893
1979
|
array: writeJSON,
|
1894
|
-
object: writeJSON
|
1980
|
+
object: writeJSON,
|
1895
1981
|
};
|
1896
1982
|
function writeJSON(value) {
|
1897
1983
|
return JSON.stringify(value);
|