@lion/ui 0.7.5 → 0.7.7
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 +1 -2
- package/components/button/test-suites/LionButton.suite.js +3 -1
- package/components/combobox/src/LionCombobox.js +33 -9
- package/components/combobox/test/lion-combobox.test.js +134 -7
- package/components/dialog/test/lion-dialog.test.js +59 -1
- package/components/input-datepicker/test/lion-input-datepicker.test.js +3 -3
- package/components/input-file/src/FileHandle.js +1 -1
- package/components/input-file/src/LionInputFile.js +1 -3
- package/components/input-file/test/lion-input-file.test.js +11 -14
- package/components/input-stepper/src/LionInputStepper.js +6 -5
- package/components/input-stepper/test/lion-input-stepper-integrations.test.js +20 -0
- package/components/input-stepper/test/lion-input-stepper.test.js +48 -3
- package/components/overlays/src/OverlayController.js +29 -7
- package/components/overlays/src/OverlayMixin.js +5 -1
- package/components/overlays/src/utils/deep-contains.js +134 -3
- package/components/overlays/test/OverlayController.test.js +57 -0
- package/components/overlays/test/utils-tests/deep-contains.test.js +98 -0
- package/components/select-rich/src/LionSelectRich.js +12 -0
- package/components/select-rich/test/lion-select-rich.test.js +26 -0
- package/custom-elements.json +140 -115
- package/dist-types/components/button/test-suites/LionButton.suite.d.ts.map +1 -1
- package/dist-types/components/combobox/src/LionCombobox.d.ts +9 -0
- package/dist-types/components/combobox/src/LionCombobox.d.ts.map +1 -1
- package/dist-types/components/input-file/src/LionInputFile.d.ts.map +1 -1
- package/dist-types/components/input-stepper/src/LionInputStepper.d.ts +4 -1
- package/dist-types/components/input-stepper/src/LionInputStepper.d.ts.map +1 -1
- package/dist-types/components/input-stepper/test/lion-input-stepper-integrations.test.d.ts +2 -0
- package/dist-types/components/input-stepper/test/lion-input-stepper-integrations.test.d.ts.map +1 -0
- package/dist-types/components/input-stepper/test/lion-input-stepper.test.d.ts.map +1 -1
- package/dist-types/components/overlays/src/OverlayController.d.ts +7 -0
- package/dist-types/components/overlays/src/OverlayController.d.ts.map +1 -1
- package/dist-types/components/overlays/src/OverlayMixin.d.ts.map +1 -1
- package/dist-types/components/overlays/src/utils/deep-contains.d.ts +15 -1
- package/dist-types/components/overlays/src/utils/deep-contains.d.ts.map +1 -1
- package/dist-types/components/select-rich/src/LionSelectRich.d.ts.map +1 -1
- package/dist-types/tsconfig.tsbuildinfo +1 -1
- package/docs/components/button/use-cases.md +2 -2
- package/docs/components/calendar/use-cases.md +4 -4
- package/docs/components/checkbox-group/api-table.md +49 -55
- package/docs/components/checkbox-group/overview.md +5 -5
- package/docs/components/checkbox-group/use-cases.md +33 -33
- package/docs/components/collapsible/overview.md +1 -1
- package/docs/components/collapsible/use-cases.md +3 -3
- package/docs/components/combobox/api-table.md +84 -89
- package/docs/components/combobox/extensions.md +20 -20
- package/docs/components/combobox/overview.md +1 -1
- package/docs/components/combobox/use-cases.md +6 -6
- package/docs/components/dialog/api-table.md +3 -3
- package/docs/components/dialog/overview.md +2 -2
- package/docs/components/dialog/src/external-dialog.js +32 -0
- package/docs/components/dialog/src/slots-dialog-content.js +1 -1
- package/docs/components/dialog/src/styled-dialog-content.js +1 -1
- package/docs/components/dialog/use-cases.md +49 -3
- package/docs/components/drawer/use-cases.md +3 -3
- package/docs/components/fieldset/api-table.md +45 -51
- package/docs/components/fieldset/overview.md +7 -7
- package/docs/components/fieldset/use-cases.md +15 -15
- package/docs/components/form/overview.md +7 -7
- package/docs/components/form/use-cases.md +4 -4
- package/docs/components/icon/use-cases.md +1 -1
- package/docs/components/input/overview.md +1 -1
- package/docs/components/input/use-cases.md +3 -3
- package/docs/components/input-amount/overview.md +3 -3
- package/docs/components/input-amount/use-cases.md +8 -8
- package/docs/components/input-date/overview.md +3 -3
- package/docs/components/input-date/use-cases.md +8 -8
- package/docs/components/input-datepicker/api-table.md +1 -1
- package/docs/components/input-datepicker/overview.md +5 -5
- package/docs/components/input-datepicker/use-cases.md +5 -5
- package/docs/components/input-email/overview.md +2 -2
- package/docs/components/input-email/use-cases.md +3 -3
- package/docs/components/input-file/overview.md +1 -1
- package/docs/components/input-iban/overview.md +2 -2
- package/docs/components/input-iban/use-cases.md +8 -8
- package/docs/components/input-range/overview.md +2 -2
- package/docs/components/input-stepper/api-table.md +65 -69
- package/docs/components/input-stepper/overview.md +1 -1
- package/docs/components/input-stepper/use-cases.md +21 -0
- package/docs/components/input-tel/overview.md +1 -1
- package/docs/components/input-tel/use-cases.md +9 -9
- package/docs/components/input-tel-dropdown/api-table.md +70 -73
- package/docs/components/input-tel-dropdown/extensions.md +1 -1
- package/docs/components/input-tel-dropdown/overview.md +1 -1
- package/docs/components/input-tel-dropdown/use-cases.md +5 -5
- package/docs/components/listbox/api-table.md +64 -70
- package/docs/components/listbox/overview.md +5 -5
- package/docs/components/listbox/use-cases.md +61 -61
- package/docs/components/pagination/use-cases.md +5 -5
- package/docs/components/radio-group/api-table.md +49 -55
- package/docs/components/radio-group/overview.md +6 -6
- package/docs/components/radio-group/use-cases.md +18 -18
- package/docs/components/select/use-cases.md +2 -2
- package/docs/components/select-rich/api-table.md +79 -85
- package/docs/components/select-rich/overview.md +3 -3
- package/docs/components/select-rich/use-cases.md +33 -33
- package/docs/components/steps/overview.md +4 -4
- package/docs/components/steps/use-cases.md +7 -7
- package/docs/components/tabs/examples.md +1 -1
- package/docs/components/tabs/use-cases.md +1 -1
- package/docs/components/textarea/use-cases.md +3 -3
- package/docs/components/tooltip/api-table.md +3 -3
- package/docs/components/tooltip/use-cases.md +6 -6
- package/docs/fundamentals/index.md +4 -4
- package/docs/fundamentals/node-tools/providence-analytics/LocalConfiguration.md +1 -1
- package/docs/fundamentals/node-tools/providence-analytics/QueryResult.md +1 -1
- package/docs/fundamentals/node-tools/providence-analytics/analyzer.md +1 -1
- package/docs/fundamentals/node-tools/providence-analytics/dashboard.md +1 -1
- package/docs/fundamentals/node-tools/providence-analytics/overview.md +3 -3
- package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
- package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
- package/docs/fundamentals/rationales/accessibility.md +1 -1
- package/docs/fundamentals/systems/core/SlotMixin.md +1 -1
- package/docs/fundamentals/systems/core/overview.md +1 -1
- package/docs/fundamentals/systems/form/formatting-and-parsing.md +8 -8
- package/docs/fundamentals/systems/form/model-value.md +1 -1
- package/docs/fundamentals/systems/form/overview.md +28 -28
- package/docs/fundamentals/systems/form/use-cases.md +19 -19
- package/docs/fundamentals/systems/form/validate.md +50 -50
- package/docs/fundamentals/systems/localize/api-table.md +10 -17
- package/docs/fundamentals/systems/localize/overview.md +4 -4
- package/docs/fundamentals/systems/localize/use-cases.md +4 -4
- package/docs/fundamentals/systems/overlays/_configuration-positioning.md +4 -4
- package/docs/fundamentals/systems/overlays/api-table.md +34 -90
- package/docs/fundamentals/systems/overlays/assets/umbrella-form.js +9 -9
- package/docs/fundamentals/systems/overlays/configuration.md +55 -29
- package/docs/fundamentals/systems/overlays/overview.md +6 -6
- package/docs/fundamentals/systems/overlays/use-cases.md +30 -30
- package/docs/fundamentals/tools/ajax/use-cases.md +20 -20
- package/docs/fundamentals/tools/helpers/action-logger.md +3 -3
- package/docs/fundamentals/tools/helpers/overview.md +1 -1
- package/docs/fundamentals/tools/singleton-manager/example-complex/demo-app.js +2 -2
- package/docs/fundamentals/tools/singleton-manager/example-fail/demo-app.js +2 -2
- package/docs/fundamentals/tools/singleton-manager/example-success/demo-app.js +2 -2
- package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
- package/docs/guides/how-to/create-a-custom-field.md +7 -7
- package/docs/guides/how-to/create-a-form.md +4 -4
- package/docs/guides/how-to/extend-a-native-input.md +2 -2
- package/docs/guides/index.md +8 -8
- package/docs/guides/principles/index.md +5 -5
- package/docs/guides/principles/scoped-elements.md +1 -1
- package/docs/guides/principles/styling.md +1 -1
- package/package.json +1 -1
|
@@ -201,6 +201,8 @@ export class OverlayController extends EventTarget {
|
|
|
201
201
|
this.__hasActiveBackdrop = true;
|
|
202
202
|
/** @private */
|
|
203
203
|
this.__escKeyHandler = this.__escKeyHandler.bind(this);
|
|
204
|
+
/** @private */
|
|
205
|
+
this.__cancelHandler = this.__cancelHandler.bind(this);
|
|
204
206
|
}
|
|
205
207
|
|
|
206
208
|
/**
|
|
@@ -510,6 +512,8 @@ export class OverlayController extends EventTarget {
|
|
|
510
512
|
this.__contentHasBeenInitialized = true;
|
|
511
513
|
}
|
|
512
514
|
|
|
515
|
+
this.__wrappingDialogNode?.addEventListener('cancel', this.__cancelHandler);
|
|
516
|
+
|
|
513
517
|
// Reset all positioning styles (local, c.q. Popper) and classes (global)
|
|
514
518
|
this.contentWrapperNode.removeAttribute('style');
|
|
515
519
|
this.contentWrapperNode.removeAttribute('class');
|
|
@@ -636,13 +640,14 @@ export class OverlayController extends EventTarget {
|
|
|
636
640
|
__setupTeardownAccessibility({ phase }) {
|
|
637
641
|
if (phase === 'init') {
|
|
638
642
|
this.__storeOriginalAttrs(this.contentNode, ['role', 'id']);
|
|
643
|
+
const isModal = this.trapsKeyboardFocus;
|
|
639
644
|
|
|
640
645
|
if (this.invokerNode) {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
'aria-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
+
const attributesToStore = ['aria-labelledby', 'aria-describedby'];
|
|
647
|
+
if (!isModal) {
|
|
648
|
+
attributesToStore.push('aria-expanded');
|
|
649
|
+
}
|
|
650
|
+
this.__storeOriginalAttrs(this.invokerNode, attributesToStore);
|
|
646
651
|
}
|
|
647
652
|
|
|
648
653
|
if (!this.contentNode.id) {
|
|
@@ -657,7 +662,7 @@ export class OverlayController extends EventTarget {
|
|
|
657
662
|
}
|
|
658
663
|
this.contentNode.setAttribute('role', 'tooltip');
|
|
659
664
|
} else {
|
|
660
|
-
if (this.invokerNode) {
|
|
665
|
+
if (this.invokerNode && !isModal) {
|
|
661
666
|
this.invokerNode.setAttribute('aria-expanded', `${this.isShown}`);
|
|
662
667
|
}
|
|
663
668
|
if (!this.contentNode.getAttribute('role')) {
|
|
@@ -783,6 +788,10 @@ export class OverlayController extends EventTarget {
|
|
|
783
788
|
* @protected
|
|
784
789
|
*/
|
|
785
790
|
_keepBodySize({ phase }) {
|
|
791
|
+
if (!this.preventsScroll) {
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
|
|
786
795
|
switch (phase) {
|
|
787
796
|
case 'before-show':
|
|
788
797
|
this.__bodyClientWidth = document.body.clientWidth;
|
|
@@ -1126,6 +1135,17 @@ export class OverlayController extends EventTarget {
|
|
|
1126
1135
|
}
|
|
1127
1136
|
}
|
|
1128
1137
|
|
|
1138
|
+
/**
|
|
1139
|
+
* When the overlay is a modal dialog hidesOnEsc works out of the box, so we prevent that.
|
|
1140
|
+
*
|
|
1141
|
+
* There is currently a bug in chrome that makes the dialog close when pressing Esc the second time
|
|
1142
|
+
* @private
|
|
1143
|
+
*/
|
|
1144
|
+
// eslint-disable-next-line class-methods-use-this
|
|
1145
|
+
__cancelHandler(/** @type {Event} */ ev) {
|
|
1146
|
+
ev.preventDefault();
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1129
1149
|
/** @private */
|
|
1130
1150
|
__escKeyHandler(/** @type {KeyboardEvent} */ ev) {
|
|
1131
1151
|
return ev.key === 'Escape' && this.hide();
|
|
@@ -1284,7 +1304,8 @@ export class OverlayController extends EventTarget {
|
|
|
1284
1304
|
if (phase === 'init' || phase === 'teardown') {
|
|
1285
1305
|
this.__setupTeardownAccessibility({ phase });
|
|
1286
1306
|
}
|
|
1287
|
-
|
|
1307
|
+
const isModal = this.trapsKeyboardFocus;
|
|
1308
|
+
if (this.invokerNode && !this.isTooltip && !isModal) {
|
|
1288
1309
|
this.invokerNode.setAttribute('aria-expanded', `${phase === 'show'}`);
|
|
1289
1310
|
}
|
|
1290
1311
|
}
|
|
@@ -1292,6 +1313,7 @@ export class OverlayController extends EventTarget {
|
|
|
1292
1313
|
teardown() {
|
|
1293
1314
|
this.__handleOverlayStyles({ phase: 'teardown' });
|
|
1294
1315
|
this._handleFeatures({ phase: 'teardown' });
|
|
1316
|
+
this.__wrappingDialogNode?.removeEventListener('cancel', this.__cancelHandler);
|
|
1295
1317
|
}
|
|
1296
1318
|
|
|
1297
1319
|
/** @private */
|
|
@@ -72,7 +72,11 @@ export const OverlayMixinImplementation = superclass =>
|
|
|
72
72
|
requestUpdate(name, oldValue, options) {
|
|
73
73
|
super.requestUpdate(name, oldValue, options);
|
|
74
74
|
if (name === 'opened' && this.opened !== oldValue) {
|
|
75
|
-
this.dispatchEvent(
|
|
75
|
+
this.dispatchEvent(
|
|
76
|
+
new CustomEvent('opened-changed', {
|
|
77
|
+
detail: { opened: this.opened },
|
|
78
|
+
}),
|
|
79
|
+
);
|
|
76
80
|
}
|
|
77
81
|
}
|
|
78
82
|
|
|
@@ -1,20 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A number, or a string containing a number.
|
|
3
|
+
* @typedef {{element: HTMLElement; deepContains: boolean} | null} CacheItem
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
7
|
* Whether first element contains the second element, also goes through shadow roots
|
|
3
8
|
* @param {HTMLElement|ShadowRoot} el
|
|
4
9
|
* @param {HTMLElement|ShadowRoot} targetEl
|
|
10
|
+
* @param {{[key: string]: CacheItem[]}} cache
|
|
5
11
|
* @returns {boolean}
|
|
6
12
|
*/
|
|
7
|
-
export function deepContains(el, targetEl) {
|
|
13
|
+
export function deepContains(el, targetEl, cache = {}) {
|
|
14
|
+
/**
|
|
15
|
+
* @description A `Typescript` `type guard` for `HTMLElement`
|
|
16
|
+
* @param {Element|ShadowRoot} htmlElement
|
|
17
|
+
* @returns {htmlElement is HTMLElement}
|
|
18
|
+
*/
|
|
19
|
+
function isHTMLElement(htmlElement) {
|
|
20
|
+
return 'getAttribute' in htmlElement;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @description Returns a cached item for the given element or null otherwise
|
|
25
|
+
* @param {HTMLElement|ShadowRoot} element
|
|
26
|
+
* @returns {CacheItem|null}
|
|
27
|
+
*/
|
|
28
|
+
function getCachedItem(element) {
|
|
29
|
+
if (!isHTMLElement(element)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const slotName = element.getAttribute('slot');
|
|
33
|
+
/** @type {CacheItem|null} */
|
|
34
|
+
let result = null;
|
|
35
|
+
if (slotName) {
|
|
36
|
+
const cachedItemsWithSameName = cache[slotName];
|
|
37
|
+
if (cachedItemsWithSameName) {
|
|
38
|
+
result = cachedItemsWithSameName.filter(item => item?.element === element)[0] || null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const cachedItem = getCachedItem(el);
|
|
45
|
+
if (cachedItem) {
|
|
46
|
+
return cachedItem.deepContains;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @description Cache an html element and its `deepContains` status
|
|
51
|
+
* @param {boolean} contains The `deepContains` status for the element
|
|
52
|
+
* @returns {void}
|
|
53
|
+
*/
|
|
54
|
+
function cacheItem(contains) {
|
|
55
|
+
if (!isHTMLElement(el)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const slotName = el.getAttribute('slot');
|
|
59
|
+
if (slotName) {
|
|
60
|
+
// eslint-disable-next-line no-param-reassign
|
|
61
|
+
cache[slotName] = cache[slotName] || [];
|
|
62
|
+
cache[slotName].push({ element: el, deepContains: contains });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
8
66
|
let containsTarget = el.contains(targetEl);
|
|
9
67
|
if (containsTarget) {
|
|
68
|
+
cacheItem(true);
|
|
10
69
|
return true;
|
|
11
70
|
}
|
|
12
71
|
|
|
72
|
+
/**
|
|
73
|
+
* A `Typescript` `type guard` for `HTMLSlotElement`
|
|
74
|
+
* @param {HTMLElement|HTMLSlotElement} htmlElement
|
|
75
|
+
* @returns {htmlElement is HTMLSlotElement}
|
|
76
|
+
*/
|
|
77
|
+
function isSlot(htmlElement) {
|
|
78
|
+
return htmlElement.tagName === 'SLOT';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Returns a slot projection or it returns `null` if `htmlElement` is not an `HTMLSlotElement`
|
|
83
|
+
* @example
|
|
84
|
+
* Let's say this is a custom element declared as follows:
|
|
85
|
+
* ```
|
|
86
|
+
* <custom-element>
|
|
87
|
+
* shadowRoot
|
|
88
|
+
* <div id="dialog-wrapper">
|
|
89
|
+
* <div id="dialog-header">Header</div>
|
|
90
|
+
* <div id="dialog-content">
|
|
91
|
+
* <slot id="dialog-content-slot" name="content"></slot>
|
|
92
|
+
* </div>
|
|
93
|
+
* </div>
|
|
94
|
+
* <!-- Light DOM -->
|
|
95
|
+
* <div id="my-slot-content" slot="content">my content</div>
|
|
96
|
+
* </custom-element>
|
|
97
|
+
* ```
|
|
98
|
+
* Then for `slot#dialog-content-slot` which is defined in the ShadowDom the function returns `div#my-slot-content` which is defined in the LightDom
|
|
99
|
+
* @param {HTMLElement|HTMLSlotElement} htmlElement
|
|
100
|
+
* @returns {Element[]}
|
|
101
|
+
* */
|
|
102
|
+
function getSlotProjections(htmlElement) {
|
|
103
|
+
return isSlot(htmlElement) ? /** @type {Element[]} */ (htmlElement.assignedElements()) : [];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @description A `Typescript` `type guard` for `ShadowRoot`
|
|
108
|
+
* @param {Element|ShadowRoot} htmlElement
|
|
109
|
+
* @returns {htmlElement is ShadowRoot}
|
|
110
|
+
*/
|
|
111
|
+
function isShadowRoot(htmlElement) {
|
|
112
|
+
return htmlElement.nodeType === Node.DOCUMENT_FRAGMENT_NODE;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Check whether any element contains target
|
|
117
|
+
* @param {(Element|ShadowRoot|null)[]} elements
|
|
118
|
+
* */
|
|
119
|
+
function checkElements(elements) {
|
|
120
|
+
let contains = false;
|
|
121
|
+
for (let i = 0; i < elements.length; i += 1) {
|
|
122
|
+
const element = elements[i];
|
|
123
|
+
if (
|
|
124
|
+
element &&
|
|
125
|
+
(isHTMLElement(element) || isShadowRoot(element)) &&
|
|
126
|
+
deepContains(element, targetEl, cache)
|
|
127
|
+
) {
|
|
128
|
+
contains = true;
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return contains;
|
|
133
|
+
}
|
|
134
|
+
|
|
13
135
|
/** @param {HTMLElement|ShadowRoot} elem */
|
|
14
136
|
function checkChildren(elem) {
|
|
15
137
|
for (let i = 0; i < elem.children.length; i += 1) {
|
|
16
138
|
const child = /** @type {HTMLElement} */ (elem.children[i]);
|
|
17
|
-
|
|
139
|
+
const cachedChild = getCachedItem(child);
|
|
140
|
+
if (cachedChild) {
|
|
141
|
+
containsTarget = cachedChild.deepContains || containsTarget;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
const slotProjections = getSlotProjections(child);
|
|
145
|
+
const childSubElements = [child.shadowRoot, ...slotProjections];
|
|
146
|
+
if (checkElements(childSubElements)) {
|
|
18
147
|
containsTarget = true;
|
|
19
148
|
break;
|
|
20
149
|
}
|
|
@@ -26,11 +155,13 @@ export function deepContains(el, targetEl) {
|
|
|
26
155
|
|
|
27
156
|
// If element is not shadowRoot itself
|
|
28
157
|
if (el instanceof HTMLElement && el.shadowRoot) {
|
|
29
|
-
containsTarget = deepContains(el.shadowRoot, targetEl);
|
|
158
|
+
containsTarget = deepContains(el.shadowRoot, targetEl, cache);
|
|
30
159
|
if (containsTarget) {
|
|
160
|
+
cacheItem(true);
|
|
31
161
|
return true;
|
|
32
162
|
}
|
|
33
163
|
}
|
|
34
164
|
checkChildren(el);
|
|
165
|
+
cacheItem(containsTarget);
|
|
35
166
|
return containsTarget;
|
|
36
167
|
}
|
|
@@ -585,6 +585,17 @@ describe('OverlayController', () => {
|
|
|
585
585
|
expect(ctrl.isShown).to.be.false;
|
|
586
586
|
});
|
|
587
587
|
|
|
588
|
+
it("doesn't hide when [escape] is pressed and hidesOnEsc is set to false", async () => {
|
|
589
|
+
const ctrl = new OverlayController({
|
|
590
|
+
...withGlobalTestConfig(),
|
|
591
|
+
hidesOnEsc: false,
|
|
592
|
+
});
|
|
593
|
+
await ctrl.show();
|
|
594
|
+
ctrl.contentNode.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape' }));
|
|
595
|
+
await aTimeout(0);
|
|
596
|
+
expect(ctrl.isShown).to.be.true;
|
|
597
|
+
});
|
|
598
|
+
|
|
588
599
|
it('stays shown when [escape] is pressed on outside element', async () => {
|
|
589
600
|
const ctrl = new OverlayController({
|
|
590
601
|
...withGlobalTestConfig(),
|
|
@@ -1517,6 +1528,23 @@ describe('OverlayController', () => {
|
|
|
1517
1528
|
expect(ctrl.invokerNode?.getAttribute('aria-expanded')).to.equal('false');
|
|
1518
1529
|
});
|
|
1519
1530
|
|
|
1531
|
+
it('does not synchronize [aria-expanded] on invoker when the overlay is modal', async () => {
|
|
1532
|
+
const invokerNode = /** @type {HTMLElement} */ (
|
|
1533
|
+
await fixture('<div role="button">invoker</div>')
|
|
1534
|
+
);
|
|
1535
|
+
const ctrl = new OverlayController({
|
|
1536
|
+
...withLocalTestConfig(),
|
|
1537
|
+
trapsKeyboardFocus: true,
|
|
1538
|
+
handlesAccessibility: true,
|
|
1539
|
+
invokerNode,
|
|
1540
|
+
});
|
|
1541
|
+
expect(ctrl.invokerNode?.getAttribute('aria-expanded')).to.equal(null);
|
|
1542
|
+
await ctrl.show();
|
|
1543
|
+
expect(ctrl.invokerNode?.getAttribute('aria-expanded')).to.equal(null);
|
|
1544
|
+
await ctrl.hide();
|
|
1545
|
+
expect(ctrl.invokerNode?.getAttribute('aria-expanded')).to.equal(null);
|
|
1546
|
+
});
|
|
1547
|
+
|
|
1520
1548
|
it('creates unique id for content', async () => {
|
|
1521
1549
|
const ctrl = new OverlayController({
|
|
1522
1550
|
...withLocalTestConfig(),
|
|
@@ -1753,4 +1781,33 @@ describe('OverlayController', () => {
|
|
|
1753
1781
|
);
|
|
1754
1782
|
});
|
|
1755
1783
|
});
|
|
1784
|
+
|
|
1785
|
+
describe('run _keepBodySize only with scroll prevention', () => {
|
|
1786
|
+
/**
|
|
1787
|
+
* @type {OverlayController}
|
|
1788
|
+
*/
|
|
1789
|
+
const overlayControllerNoPrevent = new OverlayController({
|
|
1790
|
+
...withLocalTestConfig(),
|
|
1791
|
+
preventsScroll: false,
|
|
1792
|
+
});
|
|
1793
|
+
|
|
1794
|
+
const overlayControllerPreventsScroll = new OverlayController({
|
|
1795
|
+
...withLocalTestConfig(),
|
|
1796
|
+
preventsScroll: true,
|
|
1797
|
+
});
|
|
1798
|
+
|
|
1799
|
+
it('should not run with scroll prevention', async () => {
|
|
1800
|
+
await overlayControllerNoPrevent.show();
|
|
1801
|
+
|
|
1802
|
+
expect(overlayControllerNoPrevent.__bodyMarginRightInline).to.equal(undefined);
|
|
1803
|
+
expect(overlayControllerNoPrevent.__bodyMarginRight).to.equal(undefined);
|
|
1804
|
+
});
|
|
1805
|
+
|
|
1806
|
+
it('should run with scroll prevention', async () => {
|
|
1807
|
+
await overlayControllerPreventsScroll.show();
|
|
1808
|
+
|
|
1809
|
+
expect(overlayControllerPreventsScroll.__bodyMarginRightInline).to.not.equal(undefined);
|
|
1810
|
+
expect(overlayControllerPreventsScroll.__bodyMarginRight).to.not.equal(undefined);
|
|
1811
|
+
});
|
|
1812
|
+
});
|
|
1756
1813
|
});
|
|
@@ -124,4 +124,102 @@ describe('deepContains()', () => {
|
|
|
124
124
|
expect(deepContains(element, elementFirstChildShadowChildShadow)).to.be.true;
|
|
125
125
|
expect(deepContains(element, elementFirstChildShadowChildShadowLastChild)).to.be.true;
|
|
126
126
|
});
|
|
127
|
+
|
|
128
|
+
it('returns true if the element, which is located in ShadowsRoot, contains a target element, located in the LightDom', async () => {
|
|
129
|
+
const mainElement = /** @type {HTMLElement} */ (await fixture('<div id="main"></div>'));
|
|
130
|
+
mainElement.innerHTML = `
|
|
131
|
+
<div slot="content" id="light-el-content">
|
|
132
|
+
<input type="text" id="light-el-input-1"></input>
|
|
133
|
+
</div>
|
|
134
|
+
<div slot="content" id="light-el-content">
|
|
135
|
+
<input type="text" id="light-el-input-2"></input>
|
|
136
|
+
</div>
|
|
137
|
+
`;
|
|
138
|
+
const shadowRoot = mainElement.attachShadow({ mode: 'open' });
|
|
139
|
+
shadowRoot.innerHTML = `
|
|
140
|
+
<div id="dialog-wrapper">
|
|
141
|
+
<div id="dialog-header">
|
|
142
|
+
Header
|
|
143
|
+
</div>
|
|
144
|
+
<div id="dialog-content">
|
|
145
|
+
<slot name="content" id="shadow-el-content"></slot>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
`;
|
|
149
|
+
const inputElement = /** @type {HTMLElement} */ (
|
|
150
|
+
mainElement.querySelector('#light-el-input-2')
|
|
151
|
+
);
|
|
152
|
+
const dialogWrapperElement = /** @type {HTMLElement} */ (
|
|
153
|
+
shadowRoot.querySelector('#dialog-wrapper')
|
|
154
|
+
);
|
|
155
|
+
expect(deepContains(dialogWrapperElement, inputElement)).to.be.true;
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it(`returns true if the element, which is located in ShadowRoot, contains a target element, located in the ShadowRoot element of the LightDom element `, async () => {
|
|
159
|
+
/**
|
|
160
|
+
* The DOM for the `main` element looks as follows:
|
|
161
|
+
*
|
|
162
|
+
* <div id="main">
|
|
163
|
+
* #shadow-root
|
|
164
|
+
* <div id="dialog-wrapper"> // dialogWrapperElement
|
|
165
|
+
* <div id="dialog-header">
|
|
166
|
+
* Header
|
|
167
|
+
* </div>
|
|
168
|
+
* <div id="dialog-content">
|
|
169
|
+
* <slot name="content" id="shadow-el-content"></slot>
|
|
170
|
+
* </div>
|
|
171
|
+
* </div>
|
|
172
|
+
* <div slot="content" id="light-el-content">
|
|
173
|
+
* <div id="conent-wrapper">
|
|
174
|
+
* #shadow-root
|
|
175
|
+
* <div id="conent-wrapper-sub">
|
|
176
|
+
* #shadow-root
|
|
177
|
+
* <input type="type" id="content-input"></input> //inputElement
|
|
178
|
+
* </div>
|
|
179
|
+
* </div>
|
|
180
|
+
* </div>
|
|
181
|
+
* </div>
|
|
182
|
+
*/
|
|
183
|
+
const mainElement = /** @type {HTMLElement} */ (await fixture('<div id="main"></div>'));
|
|
184
|
+
mainElement.innerHTML = `
|
|
185
|
+
<div slot="content" id="light-el-content">
|
|
186
|
+
<div id="content-wrapper"></div>
|
|
187
|
+
</div>
|
|
188
|
+
<div slot="content" id="light-el-content">
|
|
189
|
+
<div id="content-wrapper"></div>
|
|
190
|
+
</div>
|
|
191
|
+
`;
|
|
192
|
+
const contentWrapper = /** @type {HTMLElement} */ (
|
|
193
|
+
mainElement.querySelector('#content-wrapper')
|
|
194
|
+
);
|
|
195
|
+
const contentWrapperShadowRoot = contentWrapper.attachShadow({ mode: 'open' });
|
|
196
|
+
contentWrapperShadowRoot.innerHTML = `
|
|
197
|
+
<div id="conent-wrapper-sub"></div>
|
|
198
|
+
`;
|
|
199
|
+
const contentWrapperSub = /** @type {HTMLElement} */ (
|
|
200
|
+
contentWrapperShadowRoot.querySelector('#conent-wrapper-sub')
|
|
201
|
+
);
|
|
202
|
+
const contentWrapperSubShadowRoot = contentWrapperSub.attachShadow({ mode: 'open' });
|
|
203
|
+
contentWrapperSubShadowRoot.innerHTML = `
|
|
204
|
+
<input type="type" id="content-input"></input>
|
|
205
|
+
`;
|
|
206
|
+
const inputElement = /** @type {HTMLElement} */ (
|
|
207
|
+
contentWrapperSubShadowRoot.querySelector('#content-input')
|
|
208
|
+
);
|
|
209
|
+
const mainElementShadowRoot = mainElement.attachShadow({ mode: 'open' });
|
|
210
|
+
mainElementShadowRoot.innerHTML = `
|
|
211
|
+
<div id="dialog-wrapper">
|
|
212
|
+
<div id="dialog-header">
|
|
213
|
+
Header
|
|
214
|
+
</div>
|
|
215
|
+
<div id="dialog-content">
|
|
216
|
+
<slot name="content" id="shadow-el-content"></slot>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
`;
|
|
220
|
+
const dialogWrapperElement = /** @type {HTMLElement} */ (
|
|
221
|
+
mainElementShadowRoot.querySelector('#dialog-wrapper')
|
|
222
|
+
);
|
|
223
|
+
expect(deepContains(dialogWrapperElement, inputElement)).to.be.true;
|
|
224
|
+
});
|
|
127
225
|
});
|
|
@@ -196,6 +196,18 @@ export class LionSelectRich extends SlotMixin(ScopedElementsMixin(OverlayMixin(L
|
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
if (changedProperties.has('singleOption')) {
|
|
200
|
+
if (this.singleOption) {
|
|
201
|
+
this._invokerNode.removeAttribute('role');
|
|
202
|
+
this._invokerNode.removeAttribute('aria-haspopup');
|
|
203
|
+
this._invokerNode.removeAttribute('aria-expanded');
|
|
204
|
+
} else {
|
|
205
|
+
this._invokerNode.setAttribute('role', 'button');
|
|
206
|
+
this._invokerNode.setAttribute('aria-haspopup', 'listbox');
|
|
207
|
+
this._invokerNode.setAttribute('aria-expanded', `${this.opened}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
199
211
|
if (this._inputNode && this._invokerNode) {
|
|
200
212
|
if (changedProperties.has('_ariaLabelledNodes')) {
|
|
201
213
|
this._invokerNode.setAttribute(
|
|
@@ -148,6 +148,32 @@ describe('lion-select-rich', () => {
|
|
|
148
148
|
expect(_invokerNode.hasAttribute('single-option')).to.be.true;
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
+
it('sets and removes the button role and aria attributes on change of singleOption', async () => {
|
|
152
|
+
const el = await fixture(html`
|
|
153
|
+
<lion-select-rich>
|
|
154
|
+
<lion-option .choiceValue=${10}>Item 1</lion-option>
|
|
155
|
+
<lion-option .choiceValue=${20}>Item 2</lion-option>
|
|
156
|
+
</lion-select-rich>
|
|
157
|
+
`);
|
|
158
|
+
const { _invokerNode } = getSelectRichMembers(el);
|
|
159
|
+
|
|
160
|
+
expect(_invokerNode.hasAttribute('role')).to.be.true;
|
|
161
|
+
expect(_invokerNode.hasAttribute('aria-haspopup')).to.be.true;
|
|
162
|
+
expect(_invokerNode.hasAttribute('aria-expanded')).to.be.true;
|
|
163
|
+
|
|
164
|
+
el.singleOption = true;
|
|
165
|
+
await el.updateComplete;
|
|
166
|
+
expect(_invokerNode.hasAttribute('role')).to.be.false;
|
|
167
|
+
expect(_invokerNode.hasAttribute('aria-haspopup')).to.be.false;
|
|
168
|
+
expect(_invokerNode.hasAttribute('aria-expanded')).to.be.false;
|
|
169
|
+
|
|
170
|
+
el.singleOption = false;
|
|
171
|
+
await el.updateComplete;
|
|
172
|
+
expect(_invokerNode.hasAttribute('role')).to.be.true;
|
|
173
|
+
expect(_invokerNode.hasAttribute('aria-haspopup')).to.be.true;
|
|
174
|
+
expect(_invokerNode.hasAttribute('aria-expanded')).to.be.true;
|
|
175
|
+
});
|
|
176
|
+
|
|
151
177
|
it('updates the invoker when the selected element is the same but the modelValue was updated asynchronously', async () => {
|
|
152
178
|
const tagString = defineCE(
|
|
153
179
|
class LionCustomOption extends LionOption {
|