@stackoverflow/stacks 2.0.0-rc.0 → 2.0.0-rc.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.
- package/README.md +5 -0
- package/dist/css/stacks.css +582 -532
- package/dist/css/stacks.min.css +1 -1
- package/dist/js/stacks.js +265 -110
- package/dist/js/stacks.min.js +1 -1
- package/lib/atomic/typography.less +4 -0
- package/lib/components/activity-indicator/activity-indicator.a11y.test.ts +1 -1
- package/lib/components/activity-indicator/activity-indicator.less +17 -4
- package/lib/components/anchor/anchor.visual.test.ts +1 -5
- package/lib/components/avatar/avatar.visual.test.ts +1 -4
- package/lib/components/badge/badge.a11y.test.ts +2 -2
- package/lib/components/badge/badge.less +15 -23
- package/lib/components/block-link/block-link.less +5 -4
- package/lib/components/button/button.a11y.test.ts +2 -5
- package/lib/components/button/button.less +28 -58
- package/lib/components/button/button.visual.test.ts +2 -5
- package/lib/components/card/card.less +8 -0
- package/lib/components/description/description.a11y.test.ts +1 -0
- package/lib/components/expandable/expandable.a11y.test.ts +27 -0
- package/lib/components/expandable/expandable.visual.test.ts +27 -0
- package/lib/components/input-icon/input-icon.less +1 -1
- package/lib/components/input-message/input-message.less +4 -3
- package/lib/components/label/label.less +3 -13
- package/lib/components/link-preview/link-preview.a11y.test.ts +48 -0
- package/lib/components/link-preview/link-preview.less +13 -4
- package/lib/components/link-preview/link-preview.visual.test.ts +52 -0
- package/lib/components/notice/notice.a11y.test.ts +17 -0
- package/lib/components/notice/notice.less +44 -81
- package/lib/components/notice/notice.visual.test.ts +26 -0
- package/lib/components/pagination/pagination.a11y.test.ts +20 -0
- package/lib/components/pagination/pagination.visual.test.ts +26 -0
- package/lib/components/post-summary/post-summary.less +3 -3
- package/lib/components/sidebar-widget/sidebar-widget.less +2 -2
- package/lib/components/spinner/spinner.a11y.test.ts +15 -0
- package/lib/components/spinner/spinner.visual.test.ts +43 -0
- package/lib/components/toast/toast.a11y.test.ts +30 -0
- package/lib/components/toast/toast.visual.test.ts +10 -6
- package/lib/components/toggle-switch/toggle-switch.less +2 -5
- package/lib/components/uploader/uploader.less +19 -13
- package/lib/exports/color-sets.less +127 -78
- package/lib/exports/theme.less +3 -3
- package/lib/input-utils.less +1 -1
- package/lib/test/axe-apca/README.md +19 -0
- package/lib/test/axe-apca/src/axe-apca.test.ts +77 -1
- package/lib/test/axe-apca/src/axe-apca.ts +16 -8
- package/lib/test/test-utils.ts +7 -3
- package/package.json +12 -12
package/dist/js/stacks.js
CHANGED
|
@@ -81,7 +81,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
81
81
|
;// CONCATENATED MODULE: ./node_modules/@hotwired/stimulus/dist/stimulus.js
|
|
82
82
|
/*
|
|
83
83
|
Stimulus 3.2.1
|
|
84
|
-
Copyright ©
|
|
84
|
+
Copyright © 2023 Basecamp, LLC
|
|
85
85
|
*/
|
|
86
86
|
class EventListener {
|
|
87
87
|
constructor(eventTarget, eventName, eventOptions) {
|
|
@@ -246,23 +246,23 @@ const defaultActionDescriptorFilters = {
|
|
|
246
246
|
}
|
|
247
247
|
},
|
|
248
248
|
};
|
|
249
|
-
const descriptorPattern = /^(?:(.+?)(?:\.(.+?))?(?:@(window|document))?->)?(.+?)(?:#([^:]+?))(?::(.+))?$/;
|
|
249
|
+
const descriptorPattern = /^(?:(?:([^.]+?)\+)?(.+?)(?:\.(.+?))?(?:@(window|document))?->)?(.+?)(?:#([^:]+?))(?::(.+))?$/;
|
|
250
250
|
function parseActionDescriptorString(descriptorString) {
|
|
251
251
|
const source = descriptorString.trim();
|
|
252
252
|
const matches = source.match(descriptorPattern) || [];
|
|
253
|
-
let eventName = matches[
|
|
254
|
-
let keyFilter = matches[
|
|
253
|
+
let eventName = matches[2];
|
|
254
|
+
let keyFilter = matches[3];
|
|
255
255
|
if (keyFilter && !["keydown", "keyup", "keypress"].includes(eventName)) {
|
|
256
256
|
eventName += `.${keyFilter}`;
|
|
257
257
|
keyFilter = "";
|
|
258
258
|
}
|
|
259
259
|
return {
|
|
260
|
-
eventTarget: parseEventTarget(matches[
|
|
260
|
+
eventTarget: parseEventTarget(matches[4]),
|
|
261
261
|
eventName,
|
|
262
|
-
eventOptions: matches[
|
|
263
|
-
identifier: matches[
|
|
264
|
-
methodName: matches[
|
|
265
|
-
keyFilter,
|
|
262
|
+
eventOptions: matches[7] ? parseEventOptions(matches[7]) : {},
|
|
263
|
+
identifier: matches[5],
|
|
264
|
+
methodName: matches[6],
|
|
265
|
+
keyFilter: matches[1] || keyFilter,
|
|
266
266
|
};
|
|
267
267
|
}
|
|
268
268
|
function parseEventTarget(eventTargetName) {
|
|
@@ -303,6 +303,14 @@ function tokenize(value) {
|
|
|
303
303
|
return value.match(/[^\s]+/g) || [];
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
+
function isSomething(object) {
|
|
307
|
+
return object !== null && object !== undefined;
|
|
308
|
+
}
|
|
309
|
+
function hasProperty(object, property) {
|
|
310
|
+
return Object.prototype.hasOwnProperty.call(object, property);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const allModifiers = ["meta", "ctrl", "alt", "shift"];
|
|
306
314
|
class Action {
|
|
307
315
|
constructor(element, index, descriptor, schema) {
|
|
308
316
|
this.element = element;
|
|
@@ -323,25 +331,33 @@ class Action {
|
|
|
323
331
|
const eventTarget = this.eventTargetName ? `@${this.eventTargetName}` : "";
|
|
324
332
|
return `${this.eventName}${eventFilter}${eventTarget}->${this.identifier}#${this.methodName}`;
|
|
325
333
|
}
|
|
326
|
-
|
|
334
|
+
shouldIgnoreKeyboardEvent(event) {
|
|
327
335
|
if (!this.keyFilter) {
|
|
328
336
|
return false;
|
|
329
337
|
}
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
const [meta, ctrl, alt, shift] = modifiers.map((modifier) => filteres.includes(modifier));
|
|
333
|
-
if (event.metaKey !== meta || event.ctrlKey !== ctrl || event.altKey !== alt || event.shiftKey !== shift) {
|
|
338
|
+
const filters = this.keyFilter.split("+");
|
|
339
|
+
if (this.keyFilterDissatisfied(event, filters)) {
|
|
334
340
|
return true;
|
|
335
341
|
}
|
|
336
|
-
const standardFilter =
|
|
342
|
+
const standardFilter = filters.filter((key) => !allModifiers.includes(key))[0];
|
|
337
343
|
if (!standardFilter) {
|
|
338
344
|
return false;
|
|
339
345
|
}
|
|
340
|
-
if (!
|
|
346
|
+
if (!hasProperty(this.keyMappings, standardFilter)) {
|
|
341
347
|
error(`contains unknown key filter: ${this.keyFilter}`);
|
|
342
348
|
}
|
|
343
349
|
return this.keyMappings[standardFilter].toLowerCase() !== event.key.toLowerCase();
|
|
344
350
|
}
|
|
351
|
+
shouldIgnoreMouseEvent(event) {
|
|
352
|
+
if (!this.keyFilter) {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
const filters = [this.keyFilter];
|
|
356
|
+
if (this.keyFilterDissatisfied(event, filters)) {
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
345
361
|
get params() {
|
|
346
362
|
const params = {};
|
|
347
363
|
const pattern = new RegExp(`^data-${this.identifier}-(.+)-param$`, "i");
|
|
@@ -360,6 +376,10 @@ class Action {
|
|
|
360
376
|
get keyMappings() {
|
|
361
377
|
return this.schema.keyMappings;
|
|
362
378
|
}
|
|
379
|
+
keyFilterDissatisfied(event, filters) {
|
|
380
|
+
const [meta, ctrl, alt, shift] = allModifiers.map((modifier) => filters.includes(modifier));
|
|
381
|
+
return event.metaKey !== meta || event.ctrlKey !== ctrl || event.altKey !== alt || event.shiftKey !== shift;
|
|
382
|
+
}
|
|
363
383
|
}
|
|
364
384
|
const defaultEventNames = {
|
|
365
385
|
a: () => "click",
|
|
@@ -406,8 +426,9 @@ class Binding {
|
|
|
406
426
|
return this.context.identifier;
|
|
407
427
|
}
|
|
408
428
|
handleEvent(event) {
|
|
409
|
-
|
|
410
|
-
|
|
429
|
+
const actionEvent = this.prepareActionEvent(event);
|
|
430
|
+
if (this.willBeInvokedByEvent(event) && this.applyEventModifiers(actionEvent)) {
|
|
431
|
+
this.invokeWithEvent(actionEvent);
|
|
411
432
|
}
|
|
412
433
|
}
|
|
413
434
|
get eventName() {
|
|
@@ -423,11 +444,12 @@ class Binding {
|
|
|
423
444
|
applyEventModifiers(event) {
|
|
424
445
|
const { element } = this.action;
|
|
425
446
|
const { actionDescriptorFilters } = this.context.application;
|
|
447
|
+
const { controller } = this.context;
|
|
426
448
|
let passes = true;
|
|
427
449
|
for (const [name, value] of Object.entries(this.eventOptions)) {
|
|
428
450
|
if (name in actionDescriptorFilters) {
|
|
429
451
|
const filter = actionDescriptorFilters[name];
|
|
430
|
-
passes = passes && filter({ name, value, event, element });
|
|
452
|
+
passes = passes && filter({ name, value, event, element, controller });
|
|
431
453
|
}
|
|
432
454
|
else {
|
|
433
455
|
continue;
|
|
@@ -435,12 +457,13 @@ class Binding {
|
|
|
435
457
|
}
|
|
436
458
|
return passes;
|
|
437
459
|
}
|
|
460
|
+
prepareActionEvent(event) {
|
|
461
|
+
return Object.assign(event, { params: this.action.params });
|
|
462
|
+
}
|
|
438
463
|
invokeWithEvent(event) {
|
|
439
464
|
const { target, currentTarget } = event;
|
|
440
465
|
try {
|
|
441
|
-
|
|
442
|
-
const actionEvent = Object.assign(event, { params });
|
|
443
|
-
this.method.call(this.controller, actionEvent);
|
|
466
|
+
this.method.call(this.controller, event);
|
|
444
467
|
this.context.logDebugActivity(this.methodName, { event, target, currentTarget, action: this.methodName });
|
|
445
468
|
}
|
|
446
469
|
catch (error) {
|
|
@@ -451,7 +474,10 @@ class Binding {
|
|
|
451
474
|
}
|
|
452
475
|
willBeInvokedByEvent(event) {
|
|
453
476
|
const eventTarget = event.target;
|
|
454
|
-
if (event instanceof KeyboardEvent && this.action.
|
|
477
|
+
if (event instanceof KeyboardEvent && this.action.shouldIgnoreKeyboardEvent(event)) {
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
if (event instanceof MouseEvent && this.action.shouldIgnoreMouseEvent(event)) {
|
|
455
481
|
return false;
|
|
456
482
|
}
|
|
457
483
|
if (this.element === eventTarget) {
|
|
@@ -541,8 +567,7 @@ class ElementObserver {
|
|
|
541
567
|
this.processAddedNodes(mutation.addedNodes);
|
|
542
568
|
}
|
|
543
569
|
}
|
|
544
|
-
processAttributeChange(
|
|
545
|
-
const element = node;
|
|
570
|
+
processAttributeChange(element, attributeName) {
|
|
546
571
|
if (this.elements.has(element)) {
|
|
547
572
|
if (this.delegate.elementAttributeChanged && this.matchElement(element)) {
|
|
548
573
|
this.delegate.elementAttributeChanged(element, attributeName);
|
|
@@ -758,8 +783,8 @@ class IndexedMultimap extends (/* unused pure expression or super */ null && (Mu
|
|
|
758
783
|
}
|
|
759
784
|
|
|
760
785
|
class SelectorObserver {
|
|
761
|
-
constructor(element, selector, delegate, details
|
|
762
|
-
this.
|
|
786
|
+
constructor(element, selector, delegate, details) {
|
|
787
|
+
this._selector = selector;
|
|
763
788
|
this.details = details;
|
|
764
789
|
this.elementObserver = new ElementObserver(element, this);
|
|
765
790
|
this.delegate = delegate;
|
|
@@ -768,6 +793,13 @@ class SelectorObserver {
|
|
|
768
793
|
get started() {
|
|
769
794
|
return this.elementObserver.started;
|
|
770
795
|
}
|
|
796
|
+
get selector() {
|
|
797
|
+
return this._selector;
|
|
798
|
+
}
|
|
799
|
+
set selector(selector) {
|
|
800
|
+
this._selector = selector;
|
|
801
|
+
this.refresh();
|
|
802
|
+
}
|
|
771
803
|
start() {
|
|
772
804
|
this.elementObserver.start();
|
|
773
805
|
}
|
|
@@ -784,39 +816,61 @@ class SelectorObserver {
|
|
|
784
816
|
return this.elementObserver.element;
|
|
785
817
|
}
|
|
786
818
|
matchElement(element) {
|
|
787
|
-
const
|
|
788
|
-
if (
|
|
789
|
-
|
|
819
|
+
const { selector } = this;
|
|
820
|
+
if (selector) {
|
|
821
|
+
const matches = element.matches(selector);
|
|
822
|
+
if (this.delegate.selectorMatchElement) {
|
|
823
|
+
return matches && this.delegate.selectorMatchElement(element, this.details);
|
|
824
|
+
}
|
|
825
|
+
return matches;
|
|
826
|
+
}
|
|
827
|
+
else {
|
|
828
|
+
return false;
|
|
790
829
|
}
|
|
791
|
-
return matches;
|
|
792
830
|
}
|
|
793
831
|
matchElementsInTree(tree) {
|
|
794
|
-
const
|
|
795
|
-
|
|
796
|
-
|
|
832
|
+
const { selector } = this;
|
|
833
|
+
if (selector) {
|
|
834
|
+
const match = this.matchElement(tree) ? [tree] : [];
|
|
835
|
+
const matches = Array.from(tree.querySelectorAll(selector)).filter((match) => this.matchElement(match));
|
|
836
|
+
return match.concat(matches);
|
|
837
|
+
}
|
|
838
|
+
else {
|
|
839
|
+
return [];
|
|
840
|
+
}
|
|
797
841
|
}
|
|
798
842
|
elementMatched(element) {
|
|
799
|
-
this
|
|
843
|
+
const { selector } = this;
|
|
844
|
+
if (selector) {
|
|
845
|
+
this.selectorMatched(element, selector);
|
|
846
|
+
}
|
|
800
847
|
}
|
|
801
848
|
elementUnmatched(element) {
|
|
802
|
-
this.
|
|
849
|
+
const selectors = this.matchesByElement.getKeysForValue(element);
|
|
850
|
+
for (const selector of selectors) {
|
|
851
|
+
this.selectorUnmatched(element, selector);
|
|
852
|
+
}
|
|
803
853
|
}
|
|
804
854
|
elementAttributeChanged(element, _attributeName) {
|
|
805
|
-
const
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
this.
|
|
855
|
+
const { selector } = this;
|
|
856
|
+
if (selector) {
|
|
857
|
+
const matches = this.matchElement(element);
|
|
858
|
+
const matchedBefore = this.matchesByElement.has(selector, element);
|
|
859
|
+
if (matches && !matchedBefore) {
|
|
860
|
+
this.selectorMatched(element, selector);
|
|
861
|
+
}
|
|
862
|
+
else if (!matches && matchedBefore) {
|
|
863
|
+
this.selectorUnmatched(element, selector);
|
|
864
|
+
}
|
|
809
865
|
}
|
|
810
866
|
}
|
|
811
|
-
selectorMatched(element) {
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
this.matchesByElement.add(this.selector, element);
|
|
815
|
-
}
|
|
867
|
+
selectorMatched(element, selector) {
|
|
868
|
+
this.delegate.selectorMatched(element, selector, this.details);
|
|
869
|
+
this.matchesByElement.add(selector, element);
|
|
816
870
|
}
|
|
817
|
-
selectorUnmatched(element) {
|
|
818
|
-
this.delegate.selectorUnmatched(element,
|
|
819
|
-
this.matchesByElement.delete(
|
|
871
|
+
selectorUnmatched(element, selector) {
|
|
872
|
+
this.delegate.selectorUnmatched(element, selector, this.details);
|
|
873
|
+
this.matchesByElement.delete(selector, element);
|
|
820
874
|
}
|
|
821
875
|
}
|
|
822
876
|
|
|
@@ -1313,34 +1367,47 @@ function getOwnStaticObjectPairs(constructor, propertyName) {
|
|
|
1313
1367
|
|
|
1314
1368
|
class OutletObserver {
|
|
1315
1369
|
constructor(context, delegate) {
|
|
1370
|
+
this.started = false;
|
|
1316
1371
|
this.context = context;
|
|
1317
1372
|
this.delegate = delegate;
|
|
1318
1373
|
this.outletsByName = new Multimap();
|
|
1319
1374
|
this.outletElementsByName = new Multimap();
|
|
1320
1375
|
this.selectorObserverMap = new Map();
|
|
1376
|
+
this.attributeObserverMap = new Map();
|
|
1321
1377
|
}
|
|
1322
1378
|
start() {
|
|
1323
|
-
if (this.
|
|
1379
|
+
if (!this.started) {
|
|
1324
1380
|
this.outletDefinitions.forEach((outletName) => {
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
if (selector) {
|
|
1328
|
-
this.selectorObserverMap.set(outletName, new SelectorObserver(document.body, selector, this, details));
|
|
1329
|
-
}
|
|
1381
|
+
this.setupSelectorObserverForOutlet(outletName);
|
|
1382
|
+
this.setupAttributeObserverForOutlet(outletName);
|
|
1330
1383
|
});
|
|
1331
|
-
this.
|
|
1384
|
+
this.started = true;
|
|
1385
|
+
this.dependentContexts.forEach((context) => context.refresh());
|
|
1332
1386
|
}
|
|
1333
|
-
|
|
1387
|
+
}
|
|
1388
|
+
refresh() {
|
|
1389
|
+
this.selectorObserverMap.forEach((observer) => observer.refresh());
|
|
1390
|
+
this.attributeObserverMap.forEach((observer) => observer.refresh());
|
|
1334
1391
|
}
|
|
1335
1392
|
stop() {
|
|
1336
|
-
if (this.
|
|
1393
|
+
if (this.started) {
|
|
1394
|
+
this.started = false;
|
|
1337
1395
|
this.disconnectAllOutlets();
|
|
1396
|
+
this.stopSelectorObservers();
|
|
1397
|
+
this.stopAttributeObservers();
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
stopSelectorObservers() {
|
|
1401
|
+
if (this.selectorObserverMap.size > 0) {
|
|
1338
1402
|
this.selectorObserverMap.forEach((observer) => observer.stop());
|
|
1339
1403
|
this.selectorObserverMap.clear();
|
|
1340
1404
|
}
|
|
1341
1405
|
}
|
|
1342
|
-
|
|
1343
|
-
this.
|
|
1406
|
+
stopAttributeObservers() {
|
|
1407
|
+
if (this.attributeObserverMap.size > 0) {
|
|
1408
|
+
this.attributeObserverMap.forEach((observer) => observer.stop());
|
|
1409
|
+
this.attributeObserverMap.clear();
|
|
1410
|
+
}
|
|
1344
1411
|
}
|
|
1345
1412
|
selectorMatched(element, _selector, { outletName }) {
|
|
1346
1413
|
const outlet = this.getOutlet(element, outletName);
|
|
@@ -1355,8 +1422,33 @@ class OutletObserver {
|
|
|
1355
1422
|
}
|
|
1356
1423
|
}
|
|
1357
1424
|
selectorMatchElement(element, { outletName }) {
|
|
1358
|
-
|
|
1359
|
-
|
|
1425
|
+
const selector = this.selector(outletName);
|
|
1426
|
+
const hasOutlet = this.hasOutlet(element, outletName);
|
|
1427
|
+
const hasOutletController = element.matches(`[${this.schema.controllerAttribute}~=${outletName}]`);
|
|
1428
|
+
if (selector) {
|
|
1429
|
+
return hasOutlet && hasOutletController && element.matches(selector);
|
|
1430
|
+
}
|
|
1431
|
+
else {
|
|
1432
|
+
return false;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
elementMatchedAttribute(_element, attributeName) {
|
|
1436
|
+
const outletName = this.getOutletNameFromOutletAttributeName(attributeName);
|
|
1437
|
+
if (outletName) {
|
|
1438
|
+
this.updateSelectorObserverForOutlet(outletName);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
elementAttributeValueChanged(_element, attributeName) {
|
|
1442
|
+
const outletName = this.getOutletNameFromOutletAttributeName(attributeName);
|
|
1443
|
+
if (outletName) {
|
|
1444
|
+
this.updateSelectorObserverForOutlet(outletName);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
elementUnmatchedAttribute(_element, attributeName) {
|
|
1448
|
+
const outletName = this.getOutletNameFromOutletAttributeName(attributeName);
|
|
1449
|
+
if (outletName) {
|
|
1450
|
+
this.updateSelectorObserverForOutlet(outletName);
|
|
1451
|
+
}
|
|
1360
1452
|
}
|
|
1361
1453
|
connectOutlet(outlet, element, outletName) {
|
|
1362
1454
|
var _a;
|
|
@@ -1384,9 +1476,33 @@ class OutletObserver {
|
|
|
1384
1476
|
}
|
|
1385
1477
|
}
|
|
1386
1478
|
}
|
|
1479
|
+
updateSelectorObserverForOutlet(outletName) {
|
|
1480
|
+
const observer = this.selectorObserverMap.get(outletName);
|
|
1481
|
+
if (observer) {
|
|
1482
|
+
observer.selector = this.selector(outletName);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
setupSelectorObserverForOutlet(outletName) {
|
|
1486
|
+
const selector = this.selector(outletName);
|
|
1487
|
+
const selectorObserver = new SelectorObserver(document.body, selector, this, { outletName });
|
|
1488
|
+
this.selectorObserverMap.set(outletName, selectorObserver);
|
|
1489
|
+
selectorObserver.start();
|
|
1490
|
+
}
|
|
1491
|
+
setupAttributeObserverForOutlet(outletName) {
|
|
1492
|
+
const attributeName = this.attributeNameForOutletName(outletName);
|
|
1493
|
+
const attributeObserver = new AttributeObserver(this.scope.element, attributeName, this);
|
|
1494
|
+
this.attributeObserverMap.set(outletName, attributeObserver);
|
|
1495
|
+
attributeObserver.start();
|
|
1496
|
+
}
|
|
1387
1497
|
selector(outletName) {
|
|
1388
1498
|
return this.scope.outlets.getSelectorForOutletName(outletName);
|
|
1389
1499
|
}
|
|
1500
|
+
attributeNameForOutletName(outletName) {
|
|
1501
|
+
return this.scope.schema.outletAttributeForScope(this.identifier, outletName);
|
|
1502
|
+
}
|
|
1503
|
+
getOutletNameFromOutletAttributeName(attributeName) {
|
|
1504
|
+
return this.outletDefinitions.find((outletName) => this.attributeNameForOutletName(outletName) === attributeName);
|
|
1505
|
+
}
|
|
1390
1506
|
get outletDependencies() {
|
|
1391
1507
|
const dependencies = new Multimap();
|
|
1392
1508
|
this.router.modules.forEach((module) => {
|
|
@@ -1418,6 +1534,9 @@ class OutletObserver {
|
|
|
1418
1534
|
get scope() {
|
|
1419
1535
|
return this.context.scope;
|
|
1420
1536
|
}
|
|
1537
|
+
get schema() {
|
|
1538
|
+
return this.context.schema;
|
|
1539
|
+
}
|
|
1421
1540
|
get identifier() {
|
|
1422
1541
|
return this.context.identifier;
|
|
1423
1542
|
}
|
|
@@ -1905,6 +2024,9 @@ class ScopeObserver {
|
|
|
1905
2024
|
}
|
|
1906
2025
|
parseValueForToken(token) {
|
|
1907
2026
|
const { element, content: identifier } = token;
|
|
2027
|
+
return this.parseValueForElementAndIdentifier(element, identifier);
|
|
2028
|
+
}
|
|
2029
|
+
parseValueForElementAndIdentifier(element, identifier) {
|
|
1908
2030
|
const scopesByIdentifier = this.fetchScopesByIdentifierForElement(element);
|
|
1909
2031
|
let scope = scopesByIdentifier.get(identifier);
|
|
1910
2032
|
if (!scope) {
|
|
@@ -1976,7 +2098,7 @@ class Router {
|
|
|
1976
2098
|
this.connectModule(module);
|
|
1977
2099
|
const afterLoad = definition.controllerConstructor.afterLoad;
|
|
1978
2100
|
if (afterLoad) {
|
|
1979
|
-
afterLoad(definition.identifier, this.application);
|
|
2101
|
+
afterLoad.call(definition.controllerConstructor, definition.identifier, this.application);
|
|
1980
2102
|
}
|
|
1981
2103
|
}
|
|
1982
2104
|
unloadIdentifier(identifier) {
|
|
@@ -1991,6 +2113,15 @@ class Router {
|
|
|
1991
2113
|
return module.contexts.find((context) => context.element == element);
|
|
1992
2114
|
}
|
|
1993
2115
|
}
|
|
2116
|
+
proposeToConnectScopeForElementAndIdentifier(element, identifier) {
|
|
2117
|
+
const scope = this.scopeObserver.parseValueForElementAndIdentifier(element, identifier);
|
|
2118
|
+
if (scope) {
|
|
2119
|
+
this.scopeObserver.elementMatchedValue(scope.element, scope);
|
|
2120
|
+
}
|
|
2121
|
+
else {
|
|
2122
|
+
console.error(`Couldn't find or create scope for identifier: "${identifier}" and element:`, element);
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
1994
2125
|
handleError(error, message, detail) {
|
|
1995
2126
|
this.application.handleError(error, message, detail);
|
|
1996
2127
|
}
|
|
@@ -2029,7 +2160,7 @@ const defaultSchema = {
|
|
|
2029
2160
|
targetAttribute: "data-target",
|
|
2030
2161
|
targetAttributeForScope: (identifier) => `data-${identifier}-target`,
|
|
2031
2162
|
outletAttributeForScope: (identifier, outlet) => `data-${identifier}-${outlet}-outlet`,
|
|
2032
|
-
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]))),
|
|
2163
|
+
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]))),
|
|
2033
2164
|
};
|
|
2034
2165
|
function objectFromEntries(array) {
|
|
2035
2166
|
return array.reduce((memo, [k, v]) => (Object.assign(Object.assign({}, memo), { [k]: v })), {});
|
|
@@ -2155,22 +2286,32 @@ function OutletPropertiesBlessing(constructor) {
|
|
|
2155
2286
|
return Object.assign(properties, propertiesForOutletDefinition(outletDefinition));
|
|
2156
2287
|
}, {});
|
|
2157
2288
|
}
|
|
2289
|
+
function getOutletController(controller, element, identifier) {
|
|
2290
|
+
return controller.application.getControllerForElementAndIdentifier(element, identifier);
|
|
2291
|
+
}
|
|
2292
|
+
function getControllerAndEnsureConnectedScope(controller, element, outletName) {
|
|
2293
|
+
let outletController = getOutletController(controller, element, outletName);
|
|
2294
|
+
if (outletController)
|
|
2295
|
+
return outletController;
|
|
2296
|
+
controller.application.router.proposeToConnectScopeForElementAndIdentifier(element, outletName);
|
|
2297
|
+
outletController = getOutletController(controller, element, outletName);
|
|
2298
|
+
if (outletController)
|
|
2299
|
+
return outletController;
|
|
2300
|
+
}
|
|
2158
2301
|
function propertiesForOutletDefinition(name) {
|
|
2159
2302
|
const camelizedName = namespaceCamelize(name);
|
|
2160
2303
|
return {
|
|
2161
2304
|
[`${camelizedName}Outlet`]: {
|
|
2162
2305
|
get() {
|
|
2163
|
-
const
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2306
|
+
const outletElement = this.outlets.find(name);
|
|
2307
|
+
const selector = this.outlets.getSelectorForOutletName(name);
|
|
2308
|
+
if (outletElement) {
|
|
2309
|
+
const outletController = getControllerAndEnsureConnectedScope(this, outletElement, name);
|
|
2310
|
+
if (outletController)
|
|
2167
2311
|
return outletController;
|
|
2168
|
-
}
|
|
2169
|
-
else {
|
|
2170
|
-
throw new Error(`Missing "data-controller=${name}" attribute on outlet element for "${this.identifier}" controller`);
|
|
2171
|
-
}
|
|
2312
|
+
throw new Error(`The provided outlet element is missing an outlet controller "${name}" instance for host controller "${this.identifier}"`);
|
|
2172
2313
|
}
|
|
2173
|
-
throw new Error(`Missing outlet element "${name}" for "${this.identifier}"
|
|
2314
|
+
throw new Error(`Missing outlet element "${name}" for host controller "${this.identifier}". Stimulus couldn't find a matching outlet element using selector "${selector}".`);
|
|
2174
2315
|
},
|
|
2175
2316
|
},
|
|
2176
2317
|
[`${camelizedName}Outlets`]: {
|
|
@@ -2178,14 +2319,11 @@ function propertiesForOutletDefinition(name) {
|
|
|
2178
2319
|
const outlets = this.outlets.findAll(name);
|
|
2179
2320
|
if (outlets.length > 0) {
|
|
2180
2321
|
return outlets
|
|
2181
|
-
.map((
|
|
2182
|
-
const
|
|
2183
|
-
if (
|
|
2184
|
-
return
|
|
2185
|
-
}
|
|
2186
|
-
else {
|
|
2187
|
-
console.warn(`The provided outlet element is missing the outlet controller "${name}" for "${this.identifier}"`, outlet);
|
|
2188
|
-
}
|
|
2322
|
+
.map((outletElement) => {
|
|
2323
|
+
const outletController = getControllerAndEnsureConnectedScope(this, outletElement, name);
|
|
2324
|
+
if (outletController)
|
|
2325
|
+
return outletController;
|
|
2326
|
+
console.warn(`The provided outlet element is missing an outlet controller "${name}" instance for host controller "${this.identifier}"`, outletElement);
|
|
2189
2327
|
})
|
|
2190
2328
|
.filter((controller) => controller);
|
|
2191
2329
|
}
|
|
@@ -2194,12 +2332,13 @@ function propertiesForOutletDefinition(name) {
|
|
|
2194
2332
|
},
|
|
2195
2333
|
[`${camelizedName}OutletElement`]: {
|
|
2196
2334
|
get() {
|
|
2197
|
-
const
|
|
2198
|
-
|
|
2199
|
-
|
|
2335
|
+
const outletElement = this.outlets.find(name);
|
|
2336
|
+
const selector = this.outlets.getSelectorForOutletName(name);
|
|
2337
|
+
if (outletElement) {
|
|
2338
|
+
return outletElement;
|
|
2200
2339
|
}
|
|
2201
2340
|
else {
|
|
2202
|
-
throw new Error(`Missing outlet element "${name}" for "${this.identifier}"
|
|
2341
|
+
throw new Error(`Missing outlet element "${name}" for host controller "${this.identifier}". Stimulus couldn't find a matching outlet element using selector "${selector}".`);
|
|
2203
2342
|
}
|
|
2204
2343
|
},
|
|
2205
2344
|
},
|
|
@@ -2331,51 +2470,67 @@ function parseValueTypeDefault(defaultValue) {
|
|
|
2331
2470
|
return "object";
|
|
2332
2471
|
}
|
|
2333
2472
|
function parseValueTypeObject(payload) {
|
|
2334
|
-
const
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
const
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2473
|
+
const { controller, token, typeObject } = payload;
|
|
2474
|
+
const hasType = isSomething(typeObject.type);
|
|
2475
|
+
const hasDefault = isSomething(typeObject.default);
|
|
2476
|
+
const fullObject = hasType && hasDefault;
|
|
2477
|
+
const onlyType = hasType && !hasDefault;
|
|
2478
|
+
const onlyDefault = !hasType && hasDefault;
|
|
2479
|
+
const typeFromObject = parseValueTypeConstant(typeObject.type);
|
|
2480
|
+
const typeFromDefaultValue = parseValueTypeDefault(payload.typeObject.default);
|
|
2481
|
+
if (onlyType)
|
|
2482
|
+
return typeFromObject;
|
|
2483
|
+
if (onlyDefault)
|
|
2484
|
+
return typeFromDefaultValue;
|
|
2485
|
+
if (typeFromObject !== typeFromDefaultValue) {
|
|
2486
|
+
const propertyPath = controller ? `${controller}.${token}` : token;
|
|
2487
|
+
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}".`);
|
|
2488
|
+
}
|
|
2489
|
+
if (fullObject)
|
|
2490
|
+
return typeFromObject;
|
|
2343
2491
|
}
|
|
2344
2492
|
function parseValueTypeDefinition(payload) {
|
|
2345
|
-
const
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
const typeFromDefaultValue = parseValueTypeDefault(payload.typeDefinition);
|
|
2351
|
-
const typeFromConstant = parseValueTypeConstant(payload.typeDefinition);
|
|
2493
|
+
const { controller, token, typeDefinition } = payload;
|
|
2494
|
+
const typeObject = { controller, token, typeObject: typeDefinition };
|
|
2495
|
+
const typeFromObject = parseValueTypeObject(typeObject);
|
|
2496
|
+
const typeFromDefaultValue = parseValueTypeDefault(typeDefinition);
|
|
2497
|
+
const typeFromConstant = parseValueTypeConstant(typeDefinition);
|
|
2352
2498
|
const type = typeFromObject || typeFromDefaultValue || typeFromConstant;
|
|
2353
2499
|
if (type)
|
|
2354
2500
|
return type;
|
|
2355
|
-
const propertyPath =
|
|
2356
|
-
throw new Error(`Unknown value type "${propertyPath}" for "${
|
|
2501
|
+
const propertyPath = controller ? `${controller}.${typeDefinition}` : token;
|
|
2502
|
+
throw new Error(`Unknown value type "${propertyPath}" for "${token}" value`);
|
|
2357
2503
|
}
|
|
2358
2504
|
function defaultValueForDefinition(typeDefinition) {
|
|
2359
2505
|
const constant = parseValueTypeConstant(typeDefinition);
|
|
2360
2506
|
if (constant)
|
|
2361
2507
|
return defaultValuesByType[constant];
|
|
2362
|
-
const
|
|
2363
|
-
|
|
2364
|
-
|
|
2508
|
+
const hasDefault = hasProperty(typeDefinition, "default");
|
|
2509
|
+
const hasType = hasProperty(typeDefinition, "type");
|
|
2510
|
+
const typeObject = typeDefinition;
|
|
2511
|
+
if (hasDefault)
|
|
2512
|
+
return typeObject.default;
|
|
2513
|
+
if (hasType) {
|
|
2514
|
+
const { type } = typeObject;
|
|
2515
|
+
const constantFromType = parseValueTypeConstant(type);
|
|
2516
|
+
if (constantFromType)
|
|
2517
|
+
return defaultValuesByType[constantFromType];
|
|
2518
|
+
}
|
|
2365
2519
|
return typeDefinition;
|
|
2366
2520
|
}
|
|
2367
2521
|
function valueDescriptorForTokenAndTypeDefinition(payload) {
|
|
2368
|
-
const
|
|
2522
|
+
const { token, typeDefinition } = payload;
|
|
2523
|
+
const key = `${dasherize(token)}-value`;
|
|
2369
2524
|
const type = parseValueTypeDefinition(payload);
|
|
2370
2525
|
return {
|
|
2371
2526
|
type,
|
|
2372
2527
|
key,
|
|
2373
2528
|
name: camelize(key),
|
|
2374
2529
|
get defaultValue() {
|
|
2375
|
-
return defaultValueForDefinition(
|
|
2530
|
+
return defaultValueForDefinition(typeDefinition);
|
|
2376
2531
|
},
|
|
2377
2532
|
get hasCustomDefaultValue() {
|
|
2378
|
-
return parseValueTypeDefault(
|
|
2533
|
+
return parseValueTypeDefault(typeDefinition) !== undefined;
|
|
2379
2534
|
},
|
|
2380
2535
|
reader: readers[type],
|
|
2381
2536
|
writer: writers[type] || writers.default,
|
|
@@ -2404,7 +2559,7 @@ const readers = {
|
|
|
2404
2559
|
return !(value == "0" || String(value).toLowerCase() == "false");
|
|
2405
2560
|
},
|
|
2406
2561
|
number(value) {
|
|
2407
|
-
return Number(value);
|
|
2562
|
+
return Number(value.replace(/_/g, ""));
|
|
2408
2563
|
},
|
|
2409
2564
|
object(value) {
|
|
2410
2565
|
const object = JSON.parse(value);
|
|
@@ -2469,7 +2624,7 @@ class Controller {
|
|
|
2469
2624
|
}
|
|
2470
2625
|
disconnect() {
|
|
2471
2626
|
}
|
|
2472
|
-
dispatch(eventName, { target = this.element, detail = {}, prefix = this.identifier, bubbles = true, cancelable = true } = {}) {
|
|
2627
|
+
dispatch(eventName, { target = this.element, detail = {}, prefix = this.identifier, bubbles = true, cancelable = true, } = {}) {
|
|
2473
2628
|
const type = prefix ? `${prefix}:${eventName}` : eventName;
|
|
2474
2629
|
const event = new CustomEvent(type, { detail, bubbles, cancelable });
|
|
2475
2630
|
target.dispatchEvent(event);
|