stimulus-rails 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/javascripts/stimulus-loading.js +7 -5
- data/app/assets/javascripts/stimulus.js +272 -111
- data/app/assets/javascripts/stimulus.min.js +1 -1
- data/app/assets/javascripts/stimulus.min.js.map +1 -1
- data/lib/generators/stimulus/stimulus_generator.rb +3 -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.2.
|
7
|
-
Copyright ©
|
6
|
+
Stimulus 3.2.2
|
7
|
+
Copyright © 2023 Basecamp, LLC
|
8
8
|
*/
|
9
9
|
class EventListener {
|
10
10
|
constructor(eventTarget, eventName, eventOptions) {
|
@@ -169,17 +169,23 @@ const defaultActionDescriptorFilters = {
|
|
169
169
|
}
|
170
170
|
},
|
171
171
|
};
|
172
|
-
const descriptorPattern = /^(?:(.+?)(?:\.(.+?))?(?:@(window|document))?->)?(.+?)(?:#([^:]+?))(?::(.+))?$/;
|
172
|
+
const descriptorPattern = /^(?:(?:([^.]+?)\+)?(.+?)(?:\.(.+?))?(?:@(window|document))?->)?(.+?)(?:#([^:]+?))(?::(.+))?$/;
|
173
173
|
function parseActionDescriptorString(descriptorString) {
|
174
174
|
const source = descriptorString.trim();
|
175
175
|
const matches = source.match(descriptorPattern) || [];
|
176
|
+
let eventName = matches[2];
|
177
|
+
let keyFilter = matches[3];
|
178
|
+
if (keyFilter && !["keydown", "keyup", "keypress"].includes(eventName)) {
|
179
|
+
eventName += `.${keyFilter}`;
|
180
|
+
keyFilter = "";
|
181
|
+
}
|
176
182
|
return {
|
177
|
-
eventTarget: parseEventTarget(matches[
|
178
|
-
eventName
|
179
|
-
eventOptions: matches[
|
180
|
-
identifier: matches[
|
181
|
-
methodName: matches[
|
182
|
-
keyFilter: matches[
|
183
|
+
eventTarget: parseEventTarget(matches[4]),
|
184
|
+
eventName,
|
185
|
+
eventOptions: matches[7] ? parseEventOptions(matches[7]) : {},
|
186
|
+
identifier: matches[5],
|
187
|
+
methodName: matches[6],
|
188
|
+
keyFilter: matches[1] || keyFilter,
|
183
189
|
};
|
184
190
|
}
|
185
191
|
function parseEventTarget(eventTargetName) {
|
@@ -220,6 +226,14 @@ function tokenize(value) {
|
|
220
226
|
return value.match(/[^\s]+/g) || [];
|
221
227
|
}
|
222
228
|
|
229
|
+
function isSomething(object) {
|
230
|
+
return object !== null && object !== undefined;
|
231
|
+
}
|
232
|
+
function hasProperty(object, property) {
|
233
|
+
return Object.prototype.hasOwnProperty.call(object, property);
|
234
|
+
}
|
235
|
+
|
236
|
+
const allModifiers = ["meta", "ctrl", "alt", "shift"];
|
223
237
|
class Action {
|
224
238
|
constructor(element, index, descriptor, schema) {
|
225
239
|
this.element = element;
|
@@ -240,25 +254,33 @@ class Action {
|
|
240
254
|
const eventTarget = this.eventTargetName ? `@${this.eventTargetName}` : "";
|
241
255
|
return `${this.eventName}${eventFilter}${eventTarget}->${this.identifier}#${this.methodName}`;
|
242
256
|
}
|
243
|
-
|
257
|
+
shouldIgnoreKeyboardEvent(event) {
|
244
258
|
if (!this.keyFilter) {
|
245
259
|
return false;
|
246
260
|
}
|
247
|
-
const
|
248
|
-
|
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) {
|
261
|
+
const filters = this.keyFilter.split("+");
|
262
|
+
if (this.keyFilterDissatisfied(event, filters)) {
|
251
263
|
return true;
|
252
264
|
}
|
253
|
-
const standardFilter =
|
265
|
+
const standardFilter = filters.filter((key) => !allModifiers.includes(key))[0];
|
254
266
|
if (!standardFilter) {
|
255
267
|
return false;
|
256
268
|
}
|
257
|
-
if (!
|
258
|
-
error(`contains
|
269
|
+
if (!hasProperty(this.keyMappings, standardFilter)) {
|
270
|
+
error(`contains unknown key filter: ${this.keyFilter}`);
|
259
271
|
}
|
260
272
|
return this.keyMappings[standardFilter].toLowerCase() !== event.key.toLowerCase();
|
261
273
|
}
|
274
|
+
shouldIgnoreMouseEvent(event) {
|
275
|
+
if (!this.keyFilter) {
|
276
|
+
return false;
|
277
|
+
}
|
278
|
+
const filters = [this.keyFilter];
|
279
|
+
if (this.keyFilterDissatisfied(event, filters)) {
|
280
|
+
return true;
|
281
|
+
}
|
282
|
+
return false;
|
283
|
+
}
|
262
284
|
get params() {
|
263
285
|
const params = {};
|
264
286
|
const pattern = new RegExp(`^data-${this.identifier}-(.+)-param$`, "i");
|
@@ -277,6 +299,10 @@ class Action {
|
|
277
299
|
get keyMappings() {
|
278
300
|
return this.schema.keyMappings;
|
279
301
|
}
|
302
|
+
keyFilterDissatisfied(event, filters) {
|
303
|
+
const [meta, ctrl, alt, shift] = allModifiers.map((modifier) => filters.includes(modifier));
|
304
|
+
return event.metaKey !== meta || event.ctrlKey !== ctrl || event.altKey !== alt || event.shiftKey !== shift;
|
305
|
+
}
|
280
306
|
}
|
281
307
|
const defaultEventNames = {
|
282
308
|
a: () => "click",
|
@@ -323,8 +349,9 @@ class Binding {
|
|
323
349
|
return this.context.identifier;
|
324
350
|
}
|
325
351
|
handleEvent(event) {
|
326
|
-
|
327
|
-
|
352
|
+
const actionEvent = this.prepareActionEvent(event);
|
353
|
+
if (this.willBeInvokedByEvent(event) && this.applyEventModifiers(actionEvent)) {
|
354
|
+
this.invokeWithEvent(actionEvent);
|
328
355
|
}
|
329
356
|
}
|
330
357
|
get eventName() {
|
@@ -340,11 +367,12 @@ class Binding {
|
|
340
367
|
applyEventModifiers(event) {
|
341
368
|
const { element } = this.action;
|
342
369
|
const { actionDescriptorFilters } = this.context.application;
|
370
|
+
const { controller } = this.context;
|
343
371
|
let passes = true;
|
344
372
|
for (const [name, value] of Object.entries(this.eventOptions)) {
|
345
373
|
if (name in actionDescriptorFilters) {
|
346
374
|
const filter = actionDescriptorFilters[name];
|
347
|
-
passes = passes && filter({ name, value, event, element });
|
375
|
+
passes = passes && filter({ name, value, event, element, controller });
|
348
376
|
}
|
349
377
|
else {
|
350
378
|
continue;
|
@@ -352,12 +380,13 @@ class Binding {
|
|
352
380
|
}
|
353
381
|
return passes;
|
354
382
|
}
|
383
|
+
prepareActionEvent(event) {
|
384
|
+
return Object.assign(event, { params: this.action.params });
|
385
|
+
}
|
355
386
|
invokeWithEvent(event) {
|
356
387
|
const { target, currentTarget } = event;
|
357
388
|
try {
|
358
|
-
|
359
|
-
const actionEvent = Object.assign(event, { params });
|
360
|
-
this.method.call(this.controller, actionEvent);
|
389
|
+
this.method.call(this.controller, event);
|
361
390
|
this.context.logDebugActivity(this.methodName, { event, target, currentTarget, action: this.methodName });
|
362
391
|
}
|
363
392
|
catch (error) {
|
@@ -368,7 +397,10 @@ class Binding {
|
|
368
397
|
}
|
369
398
|
willBeInvokedByEvent(event) {
|
370
399
|
const eventTarget = event.target;
|
371
|
-
if (event instanceof KeyboardEvent && this.action.
|
400
|
+
if (event instanceof KeyboardEvent && this.action.shouldIgnoreKeyboardEvent(event)) {
|
401
|
+
return false;
|
402
|
+
}
|
403
|
+
if (event instanceof MouseEvent && this.action.shouldIgnoreMouseEvent(event)) {
|
372
404
|
return false;
|
373
405
|
}
|
374
406
|
if (this.element === eventTarget) {
|
@@ -458,8 +490,7 @@ class ElementObserver {
|
|
458
490
|
this.processAddedNodes(mutation.addedNodes);
|
459
491
|
}
|
460
492
|
}
|
461
|
-
processAttributeChange(
|
462
|
-
const element = node;
|
493
|
+
processAttributeChange(element, attributeName) {
|
463
494
|
if (this.elements.has(element)) {
|
464
495
|
if (this.delegate.elementAttributeChanged && this.matchElement(element)) {
|
465
496
|
this.delegate.elementAttributeChanged(element, attributeName);
|
@@ -675,8 +706,8 @@ class IndexedMultimap extends Multimap {
|
|
675
706
|
}
|
676
707
|
|
677
708
|
class SelectorObserver {
|
678
|
-
constructor(element, selector, delegate, details
|
679
|
-
this.
|
709
|
+
constructor(element, selector, delegate, details) {
|
710
|
+
this._selector = selector;
|
680
711
|
this.details = details;
|
681
712
|
this.elementObserver = new ElementObserver(element, this);
|
682
713
|
this.delegate = delegate;
|
@@ -685,6 +716,13 @@ class SelectorObserver {
|
|
685
716
|
get started() {
|
686
717
|
return this.elementObserver.started;
|
687
718
|
}
|
719
|
+
get selector() {
|
720
|
+
return this._selector;
|
721
|
+
}
|
722
|
+
set selector(selector) {
|
723
|
+
this._selector = selector;
|
724
|
+
this.refresh();
|
725
|
+
}
|
688
726
|
start() {
|
689
727
|
this.elementObserver.start();
|
690
728
|
}
|
@@ -701,39 +739,61 @@ class SelectorObserver {
|
|
701
739
|
return this.elementObserver.element;
|
702
740
|
}
|
703
741
|
matchElement(element) {
|
704
|
-
const
|
705
|
-
if (
|
706
|
-
|
742
|
+
const { selector } = this;
|
743
|
+
if (selector) {
|
744
|
+
const matches = element.matches(selector);
|
745
|
+
if (this.delegate.selectorMatchElement) {
|
746
|
+
return matches && this.delegate.selectorMatchElement(element, this.details);
|
747
|
+
}
|
748
|
+
return matches;
|
749
|
+
}
|
750
|
+
else {
|
751
|
+
return false;
|
707
752
|
}
|
708
|
-
return matches;
|
709
753
|
}
|
710
754
|
matchElementsInTree(tree) {
|
711
|
-
const
|
712
|
-
|
713
|
-
|
755
|
+
const { selector } = this;
|
756
|
+
if (selector) {
|
757
|
+
const match = this.matchElement(tree) ? [tree] : [];
|
758
|
+
const matches = Array.from(tree.querySelectorAll(selector)).filter((match) => this.matchElement(match));
|
759
|
+
return match.concat(matches);
|
760
|
+
}
|
761
|
+
else {
|
762
|
+
return [];
|
763
|
+
}
|
714
764
|
}
|
715
765
|
elementMatched(element) {
|
716
|
-
this
|
766
|
+
const { selector } = this;
|
767
|
+
if (selector) {
|
768
|
+
this.selectorMatched(element, selector);
|
769
|
+
}
|
717
770
|
}
|
718
771
|
elementUnmatched(element) {
|
719
|
-
this.
|
772
|
+
const selectors = this.matchesByElement.getKeysForValue(element);
|
773
|
+
for (const selector of selectors) {
|
774
|
+
this.selectorUnmatched(element, selector);
|
775
|
+
}
|
720
776
|
}
|
721
777
|
elementAttributeChanged(element, _attributeName) {
|
722
|
-
const
|
723
|
-
|
724
|
-
|
725
|
-
this.
|
778
|
+
const { selector } = this;
|
779
|
+
if (selector) {
|
780
|
+
const matches = this.matchElement(element);
|
781
|
+
const matchedBefore = this.matchesByElement.has(selector, element);
|
782
|
+
if (matches && !matchedBefore) {
|
783
|
+
this.selectorMatched(element, selector);
|
784
|
+
}
|
785
|
+
else if (!matches && matchedBefore) {
|
786
|
+
this.selectorUnmatched(element, selector);
|
787
|
+
}
|
726
788
|
}
|
727
789
|
}
|
728
|
-
selectorMatched(element) {
|
729
|
-
|
730
|
-
|
731
|
-
this.matchesByElement.add(this.selector, element);
|
732
|
-
}
|
790
|
+
selectorMatched(element, selector) {
|
791
|
+
this.delegate.selectorMatched(element, selector, this.details);
|
792
|
+
this.matchesByElement.add(selector, element);
|
733
793
|
}
|
734
|
-
selectorUnmatched(element) {
|
735
|
-
this.delegate.selectorUnmatched(element,
|
736
|
-
this.matchesByElement.delete(
|
794
|
+
selectorUnmatched(element, selector) {
|
795
|
+
this.delegate.selectorUnmatched(element, selector, this.details);
|
796
|
+
this.matchesByElement.delete(selector, element);
|
737
797
|
}
|
738
798
|
}
|
739
799
|
|
@@ -1230,34 +1290,47 @@ function getOwnStaticObjectPairs(constructor, propertyName) {
|
|
1230
1290
|
|
1231
1291
|
class OutletObserver {
|
1232
1292
|
constructor(context, delegate) {
|
1293
|
+
this.started = false;
|
1233
1294
|
this.context = context;
|
1234
1295
|
this.delegate = delegate;
|
1235
1296
|
this.outletsByName = new Multimap();
|
1236
1297
|
this.outletElementsByName = new Multimap();
|
1237
1298
|
this.selectorObserverMap = new Map();
|
1299
|
+
this.attributeObserverMap = new Map();
|
1238
1300
|
}
|
1239
1301
|
start() {
|
1240
|
-
if (this.
|
1302
|
+
if (!this.started) {
|
1241
1303
|
this.outletDefinitions.forEach((outletName) => {
|
1242
|
-
|
1243
|
-
|
1244
|
-
if (selector) {
|
1245
|
-
this.selectorObserverMap.set(outletName, new SelectorObserver(document.body, selector, this, details));
|
1246
|
-
}
|
1304
|
+
this.setupSelectorObserverForOutlet(outletName);
|
1305
|
+
this.setupAttributeObserverForOutlet(outletName);
|
1247
1306
|
});
|
1248
|
-
this.
|
1307
|
+
this.started = true;
|
1308
|
+
this.dependentContexts.forEach((context) => context.refresh());
|
1249
1309
|
}
|
1250
|
-
|
1310
|
+
}
|
1311
|
+
refresh() {
|
1312
|
+
this.selectorObserverMap.forEach((observer) => observer.refresh());
|
1313
|
+
this.attributeObserverMap.forEach((observer) => observer.refresh());
|
1251
1314
|
}
|
1252
1315
|
stop() {
|
1253
|
-
if (this.
|
1316
|
+
if (this.started) {
|
1317
|
+
this.started = false;
|
1254
1318
|
this.disconnectAllOutlets();
|
1319
|
+
this.stopSelectorObservers();
|
1320
|
+
this.stopAttributeObservers();
|
1321
|
+
}
|
1322
|
+
}
|
1323
|
+
stopSelectorObservers() {
|
1324
|
+
if (this.selectorObserverMap.size > 0) {
|
1255
1325
|
this.selectorObserverMap.forEach((observer) => observer.stop());
|
1256
1326
|
this.selectorObserverMap.clear();
|
1257
1327
|
}
|
1258
1328
|
}
|
1259
|
-
|
1260
|
-
this.
|
1329
|
+
stopAttributeObservers() {
|
1330
|
+
if (this.attributeObserverMap.size > 0) {
|
1331
|
+
this.attributeObserverMap.forEach((observer) => observer.stop());
|
1332
|
+
this.attributeObserverMap.clear();
|
1333
|
+
}
|
1261
1334
|
}
|
1262
1335
|
selectorMatched(element, _selector, { outletName }) {
|
1263
1336
|
const outlet = this.getOutlet(element, outletName);
|
@@ -1272,8 +1345,33 @@ class OutletObserver {
|
|
1272
1345
|
}
|
1273
1346
|
}
|
1274
1347
|
selectorMatchElement(element, { outletName }) {
|
1275
|
-
|
1276
|
-
|
1348
|
+
const selector = this.selector(outletName);
|
1349
|
+
const hasOutlet = this.hasOutlet(element, outletName);
|
1350
|
+
const hasOutletController = element.matches(`[${this.schema.controllerAttribute}~=${outletName}]`);
|
1351
|
+
if (selector) {
|
1352
|
+
return hasOutlet && hasOutletController && element.matches(selector);
|
1353
|
+
}
|
1354
|
+
else {
|
1355
|
+
return false;
|
1356
|
+
}
|
1357
|
+
}
|
1358
|
+
elementMatchedAttribute(_element, attributeName) {
|
1359
|
+
const outletName = this.getOutletNameFromOutletAttributeName(attributeName);
|
1360
|
+
if (outletName) {
|
1361
|
+
this.updateSelectorObserverForOutlet(outletName);
|
1362
|
+
}
|
1363
|
+
}
|
1364
|
+
elementAttributeValueChanged(_element, attributeName) {
|
1365
|
+
const outletName = this.getOutletNameFromOutletAttributeName(attributeName);
|
1366
|
+
if (outletName) {
|
1367
|
+
this.updateSelectorObserverForOutlet(outletName);
|
1368
|
+
}
|
1369
|
+
}
|
1370
|
+
elementUnmatchedAttribute(_element, attributeName) {
|
1371
|
+
const outletName = this.getOutletNameFromOutletAttributeName(attributeName);
|
1372
|
+
if (outletName) {
|
1373
|
+
this.updateSelectorObserverForOutlet(outletName);
|
1374
|
+
}
|
1277
1375
|
}
|
1278
1376
|
connectOutlet(outlet, element, outletName) {
|
1279
1377
|
var _a;
|
@@ -1301,9 +1399,33 @@ class OutletObserver {
|
|
1301
1399
|
}
|
1302
1400
|
}
|
1303
1401
|
}
|
1402
|
+
updateSelectorObserverForOutlet(outletName) {
|
1403
|
+
const observer = this.selectorObserverMap.get(outletName);
|
1404
|
+
if (observer) {
|
1405
|
+
observer.selector = this.selector(outletName);
|
1406
|
+
}
|
1407
|
+
}
|
1408
|
+
setupSelectorObserverForOutlet(outletName) {
|
1409
|
+
const selector = this.selector(outletName);
|
1410
|
+
const selectorObserver = new SelectorObserver(document.body, selector, this, { outletName });
|
1411
|
+
this.selectorObserverMap.set(outletName, selectorObserver);
|
1412
|
+
selectorObserver.start();
|
1413
|
+
}
|
1414
|
+
setupAttributeObserverForOutlet(outletName) {
|
1415
|
+
const attributeName = this.attributeNameForOutletName(outletName);
|
1416
|
+
const attributeObserver = new AttributeObserver(this.scope.element, attributeName, this);
|
1417
|
+
this.attributeObserverMap.set(outletName, attributeObserver);
|
1418
|
+
attributeObserver.start();
|
1419
|
+
}
|
1304
1420
|
selector(outletName) {
|
1305
1421
|
return this.scope.outlets.getSelectorForOutletName(outletName);
|
1306
1422
|
}
|
1423
|
+
attributeNameForOutletName(outletName) {
|
1424
|
+
return this.scope.schema.outletAttributeForScope(this.identifier, outletName);
|
1425
|
+
}
|
1426
|
+
getOutletNameFromOutletAttributeName(attributeName) {
|
1427
|
+
return this.outletDefinitions.find((outletName) => this.attributeNameForOutletName(outletName) === attributeName);
|
1428
|
+
}
|
1307
1429
|
get outletDependencies() {
|
1308
1430
|
const dependencies = new Multimap();
|
1309
1431
|
this.router.modules.forEach((module) => {
|
@@ -1335,6 +1457,9 @@ class OutletObserver {
|
|
1335
1457
|
get scope() {
|
1336
1458
|
return this.context.scope;
|
1337
1459
|
}
|
1460
|
+
get schema() {
|
1461
|
+
return this.context.schema;
|
1462
|
+
}
|
1338
1463
|
get identifier() {
|
1339
1464
|
return this.context.identifier;
|
1340
1465
|
}
|
@@ -1822,6 +1947,9 @@ class ScopeObserver {
|
|
1822
1947
|
}
|
1823
1948
|
parseValueForToken(token) {
|
1824
1949
|
const { element, content: identifier } = token;
|
1950
|
+
return this.parseValueForElementAndIdentifier(element, identifier);
|
1951
|
+
}
|
1952
|
+
parseValueForElementAndIdentifier(element, identifier) {
|
1825
1953
|
const scopesByIdentifier = this.fetchScopesByIdentifierForElement(element);
|
1826
1954
|
let scope = scopesByIdentifier.get(identifier);
|
1827
1955
|
if (!scope) {
|
@@ -1893,7 +2021,7 @@ class Router {
|
|
1893
2021
|
this.connectModule(module);
|
1894
2022
|
const afterLoad = definition.controllerConstructor.afterLoad;
|
1895
2023
|
if (afterLoad) {
|
1896
|
-
afterLoad(definition.identifier, this.application);
|
2024
|
+
afterLoad.call(definition.controllerConstructor, definition.identifier, this.application);
|
1897
2025
|
}
|
1898
2026
|
}
|
1899
2027
|
unloadIdentifier(identifier) {
|
@@ -1908,6 +2036,15 @@ class Router {
|
|
1908
2036
|
return module.contexts.find((context) => context.element == element);
|
1909
2037
|
}
|
1910
2038
|
}
|
2039
|
+
proposeToConnectScopeForElementAndIdentifier(element, identifier) {
|
2040
|
+
const scope = this.scopeObserver.parseValueForElementAndIdentifier(element, identifier);
|
2041
|
+
if (scope) {
|
2042
|
+
this.scopeObserver.elementMatchedValue(scope.element, scope);
|
2043
|
+
}
|
2044
|
+
else {
|
2045
|
+
console.error(`Couldn't find or create scope for identifier: "${identifier}" and element:`, element);
|
2046
|
+
}
|
2047
|
+
}
|
1911
2048
|
handleError(error, message, detail) {
|
1912
2049
|
this.application.handleError(error, message, detail);
|
1913
2050
|
}
|
@@ -1946,7 +2083,7 @@ const defaultSchema = {
|
|
1946
2083
|
targetAttribute: "data-target",
|
1947
2084
|
targetAttributeForScope: (identifier) => `data-${identifier}-target`,
|
1948
2085
|
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]))),
|
2086
|
+
keyMappings: Object.assign(Object.assign({ enter: "Enter", tab: "Tab", esc: "Escape", space: " ", up: "ArrowUp", down: "ArrowDown", left: "ArrowLeft", right: "ArrowRight", home: "Home", end: "End", page_up: "PageUp", page_down: "PageDown" }, objectFromEntries("abcdefghijklmnopqrstuvwxyz".split("").map((c) => [c, c]))), objectFromEntries("0123456789".split("").map((n) => [n, n]))),
|
1950
2087
|
};
|
1951
2088
|
function objectFromEntries(array) {
|
1952
2089
|
return array.reduce((memo, [k, v]) => (Object.assign(Object.assign({}, memo), { [k]: v })), {});
|
@@ -2072,22 +2209,32 @@ function OutletPropertiesBlessing(constructor) {
|
|
2072
2209
|
return Object.assign(properties, propertiesForOutletDefinition(outletDefinition));
|
2073
2210
|
}, {});
|
2074
2211
|
}
|
2212
|
+
function getOutletController(controller, element, identifier) {
|
2213
|
+
return controller.application.getControllerForElementAndIdentifier(element, identifier);
|
2214
|
+
}
|
2215
|
+
function getControllerAndEnsureConnectedScope(controller, element, outletName) {
|
2216
|
+
let outletController = getOutletController(controller, element, outletName);
|
2217
|
+
if (outletController)
|
2218
|
+
return outletController;
|
2219
|
+
controller.application.router.proposeToConnectScopeForElementAndIdentifier(element, outletName);
|
2220
|
+
outletController = getOutletController(controller, element, outletName);
|
2221
|
+
if (outletController)
|
2222
|
+
return outletController;
|
2223
|
+
}
|
2075
2224
|
function propertiesForOutletDefinition(name) {
|
2076
2225
|
const camelizedName = namespaceCamelize(name);
|
2077
2226
|
return {
|
2078
2227
|
[`${camelizedName}Outlet`]: {
|
2079
2228
|
get() {
|
2080
|
-
const
|
2081
|
-
|
2082
|
-
|
2083
|
-
|
2229
|
+
const outletElement = this.outlets.find(name);
|
2230
|
+
const selector = this.outlets.getSelectorForOutletName(name);
|
2231
|
+
if (outletElement) {
|
2232
|
+
const outletController = getControllerAndEnsureConnectedScope(this, outletElement, name);
|
2233
|
+
if (outletController)
|
2084
2234
|
return outletController;
|
2085
|
-
}
|
2086
|
-
else {
|
2087
|
-
throw new Error(`Missing "data-controller=${name}" attribute on outlet element for "${this.identifier}" controller`);
|
2088
|
-
}
|
2235
|
+
throw new Error(`The provided outlet element is missing an outlet controller "${name}" instance for host controller "${this.identifier}"`);
|
2089
2236
|
}
|
2090
|
-
throw new Error(`Missing outlet element "${name}" for "${this.identifier}"
|
2237
|
+
throw new Error(`Missing outlet element "${name}" for host controller "${this.identifier}". Stimulus couldn't find a matching outlet element using selector "${selector}".`);
|
2091
2238
|
},
|
2092
2239
|
},
|
2093
2240
|
[`${camelizedName}Outlets`]: {
|
@@ -2095,14 +2242,11 @@ function propertiesForOutletDefinition(name) {
|
|
2095
2242
|
const outlets = this.outlets.findAll(name);
|
2096
2243
|
if (outlets.length > 0) {
|
2097
2244
|
return outlets
|
2098
|
-
.map((
|
2099
|
-
const
|
2100
|
-
if (
|
2101
|
-
return
|
2102
|
-
}
|
2103
|
-
else {
|
2104
|
-
console.warn(`The provided outlet element is missing the outlet controller "${name}" for "${this.identifier}"`, outlet);
|
2105
|
-
}
|
2245
|
+
.map((outletElement) => {
|
2246
|
+
const outletController = getControllerAndEnsureConnectedScope(this, outletElement, name);
|
2247
|
+
if (outletController)
|
2248
|
+
return outletController;
|
2249
|
+
console.warn(`The provided outlet element is missing an outlet controller "${name}" instance for host controller "${this.identifier}"`, outletElement);
|
2106
2250
|
})
|
2107
2251
|
.filter((controller) => controller);
|
2108
2252
|
}
|
@@ -2111,12 +2255,13 @@ function propertiesForOutletDefinition(name) {
|
|
2111
2255
|
},
|
2112
2256
|
[`${camelizedName}OutletElement`]: {
|
2113
2257
|
get() {
|
2114
|
-
const
|
2115
|
-
|
2116
|
-
|
2258
|
+
const outletElement = this.outlets.find(name);
|
2259
|
+
const selector = this.outlets.getSelectorForOutletName(name);
|
2260
|
+
if (outletElement) {
|
2261
|
+
return outletElement;
|
2117
2262
|
}
|
2118
2263
|
else {
|
2119
|
-
throw new Error(`Missing outlet element "${name}" for "${this.identifier}"
|
2264
|
+
throw new Error(`Missing outlet element "${name}" for host controller "${this.identifier}". Stimulus couldn't find a matching outlet element using selector "${selector}".`);
|
2120
2265
|
}
|
2121
2266
|
},
|
2122
2267
|
},
|
@@ -2248,51 +2393,67 @@ function parseValueTypeDefault(defaultValue) {
|
|
2248
2393
|
return "object";
|
2249
2394
|
}
|
2250
2395
|
function parseValueTypeObject(payload) {
|
2251
|
-
const
|
2252
|
-
|
2253
|
-
|
2254
|
-
const
|
2255
|
-
|
2256
|
-
|
2257
|
-
|
2258
|
-
|
2259
|
-
|
2396
|
+
const { controller, token, typeObject } = payload;
|
2397
|
+
const hasType = isSomething(typeObject.type);
|
2398
|
+
const hasDefault = isSomething(typeObject.default);
|
2399
|
+
const fullObject = hasType && hasDefault;
|
2400
|
+
const onlyType = hasType && !hasDefault;
|
2401
|
+
const onlyDefault = !hasType && hasDefault;
|
2402
|
+
const typeFromObject = parseValueTypeConstant(typeObject.type);
|
2403
|
+
const typeFromDefaultValue = parseValueTypeDefault(payload.typeObject.default);
|
2404
|
+
if (onlyType)
|
2405
|
+
return typeFromObject;
|
2406
|
+
if (onlyDefault)
|
2407
|
+
return typeFromDefaultValue;
|
2408
|
+
if (typeFromObject !== typeFromDefaultValue) {
|
2409
|
+
const propertyPath = controller ? `${controller}.${token}` : token;
|
2410
|
+
throw new Error(`The specified default value for the Stimulus Value "${propertyPath}" must match the defined type "${typeFromObject}". The provided default value of "${typeObject.default}" is of type "${typeFromDefaultValue}".`);
|
2411
|
+
}
|
2412
|
+
if (fullObject)
|
2413
|
+
return typeFromObject;
|
2260
2414
|
}
|
2261
2415
|
function parseValueTypeDefinition(payload) {
|
2262
|
-
const
|
2263
|
-
|
2264
|
-
|
2265
|
-
|
2266
|
-
|
2267
|
-
const typeFromDefaultValue = parseValueTypeDefault(payload.typeDefinition);
|
2268
|
-
const typeFromConstant = parseValueTypeConstant(payload.typeDefinition);
|
2416
|
+
const { controller, token, typeDefinition } = payload;
|
2417
|
+
const typeObject = { controller, token, typeObject: typeDefinition };
|
2418
|
+
const typeFromObject = parseValueTypeObject(typeObject);
|
2419
|
+
const typeFromDefaultValue = parseValueTypeDefault(typeDefinition);
|
2420
|
+
const typeFromConstant = parseValueTypeConstant(typeDefinition);
|
2269
2421
|
const type = typeFromObject || typeFromDefaultValue || typeFromConstant;
|
2270
2422
|
if (type)
|
2271
2423
|
return type;
|
2272
|
-
const propertyPath =
|
2273
|
-
throw new Error(`Unknown value type "${propertyPath}" for "${
|
2424
|
+
const propertyPath = controller ? `${controller}.${typeDefinition}` : token;
|
2425
|
+
throw new Error(`Unknown value type "${propertyPath}" for "${token}" value`);
|
2274
2426
|
}
|
2275
2427
|
function defaultValueForDefinition(typeDefinition) {
|
2276
2428
|
const constant = parseValueTypeConstant(typeDefinition);
|
2277
2429
|
if (constant)
|
2278
2430
|
return defaultValuesByType[constant];
|
2279
|
-
const
|
2280
|
-
|
2281
|
-
|
2431
|
+
const hasDefault = hasProperty(typeDefinition, "default");
|
2432
|
+
const hasType = hasProperty(typeDefinition, "type");
|
2433
|
+
const typeObject = typeDefinition;
|
2434
|
+
if (hasDefault)
|
2435
|
+
return typeObject.default;
|
2436
|
+
if (hasType) {
|
2437
|
+
const { type } = typeObject;
|
2438
|
+
const constantFromType = parseValueTypeConstant(type);
|
2439
|
+
if (constantFromType)
|
2440
|
+
return defaultValuesByType[constantFromType];
|
2441
|
+
}
|
2282
2442
|
return typeDefinition;
|
2283
2443
|
}
|
2284
2444
|
function valueDescriptorForTokenAndTypeDefinition(payload) {
|
2285
|
-
const
|
2445
|
+
const { token, typeDefinition } = payload;
|
2446
|
+
const key = `${dasherize(token)}-value`;
|
2286
2447
|
const type = parseValueTypeDefinition(payload);
|
2287
2448
|
return {
|
2288
2449
|
type,
|
2289
2450
|
key,
|
2290
2451
|
name: camelize(key),
|
2291
2452
|
get defaultValue() {
|
2292
|
-
return defaultValueForDefinition(
|
2453
|
+
return defaultValueForDefinition(typeDefinition);
|
2293
2454
|
},
|
2294
2455
|
get hasCustomDefaultValue() {
|
2295
|
-
return parseValueTypeDefault(
|
2456
|
+
return parseValueTypeDefault(typeDefinition) !== undefined;
|
2296
2457
|
},
|
2297
2458
|
reader: readers[type],
|
2298
2459
|
writer: writers[type] || writers.default,
|
@@ -2321,7 +2482,7 @@ const readers = {
|
|
2321
2482
|
return !(value == "0" || String(value).toLowerCase() == "false");
|
2322
2483
|
},
|
2323
2484
|
number(value) {
|
2324
|
-
return Number(value);
|
2485
|
+
return Number(value.replace(/_/g, ""));
|
2325
2486
|
},
|
2326
2487
|
object(value) {
|
2327
2488
|
const object = JSON.parse(value);
|
@@ -2386,7 +2547,7 @@ class Controller {
|
|
2386
2547
|
}
|
2387
2548
|
disconnect() {
|
2388
2549
|
}
|
2389
|
-
dispatch(eventName, { target = this.element, detail = {}, prefix = this.identifier, bubbles = true, cancelable = true } = {}) {
|
2550
|
+
dispatch(eventName, { target = this.element, detail = {}, prefix = this.identifier, bubbles = true, cancelable = true, } = {}) {
|
2390
2551
|
const type = prefix ? `${prefix}:${eventName}` : eventName;
|
2391
2552
|
const event = new CustomEvent(type, { detail, bubbles, cancelable });
|
2392
2553
|
target.dispatchEvent(event);
|