@lion/ui 0.0.10 → 0.0.12
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/components/accordion/src/LionAccordion.js +2 -2
- package/components/dialog/src/LionDialog.js +0 -28
- package/components/dialog/test/lion-dialog.test.js +10 -5
- package/components/input-datepicker/src/LionInputDatepicker.js +2 -4
- package/components/localize/src/singleton.js +1 -1
- package/components/overlays/src/OverlayController.js +73 -14
- package/components/overlays/src/configurations/visibility-trigger-partials/withClickInteraction.js +32 -0
- package/components/overlays/src/configurations/visibility-trigger-partials/withHoverInteraction.js +76 -0
- package/components/overlays/src/configurations/withBottomSheetConfig.js +3 -0
- package/components/overlays/src/configurations/withDropdownConfig.js +3 -0
- package/components/overlays/src/configurations/withModalDialogConfig.js +3 -0
- package/components/overlays/src/configurations/withTooltipConfig.js +29 -0
- package/components/overlays/test/OverlayController.test.js +88 -7
- package/components/overlays/types/OverlayConfig.ts +6 -0
- package/components/select-rich/src/LionSelectRich.js +2 -0
- package/components/tooltip/src/LionTooltip.js +18 -91
- package/components/tooltip/test/lion-tooltip.test.js +50 -19
- package/custom-elements.json +307 -220
- package/dist-types/components/dialog/src/LionDialog.d.ts +0 -2
- package/dist-types/components/dialog/src/LionDialog.d.ts.map +1 -1
- package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts.map +1 -1
- package/dist-types/components/overlays/src/OverlayController.d.ts +11 -2
- package/dist-types/components/overlays/src/OverlayController.d.ts.map +1 -1
- package/dist-types/components/overlays/src/configurations/visibility-trigger-partials/withClickInteraction.d.ts +12 -0
- package/dist-types/components/overlays/src/configurations/visibility-trigger-partials/withClickInteraction.d.ts.map +1 -0
- package/dist-types/components/overlays/src/configurations/visibility-trigger-partials/withHoverInteraction.d.ts +18 -0
- package/dist-types/components/overlays/src/configurations/visibility-trigger-partials/withHoverInteraction.d.ts.map +1 -0
- package/dist-types/components/overlays/src/configurations/withBottomSheetConfig.d.ts.map +1 -1
- package/dist-types/components/overlays/src/configurations/withDropdownConfig.d.ts.map +1 -1
- package/dist-types/components/overlays/src/configurations/withModalDialogConfig.d.ts.map +1 -1
- package/dist-types/components/overlays/src/configurations/withTooltipConfig.d.ts +8 -0
- package/dist-types/components/overlays/src/configurations/withTooltipConfig.d.ts.map +1 -0
- package/dist-types/components/overlays/types/OverlayConfig.d.ts +5 -0
- package/dist-types/components/overlays/types/OverlayConfig.d.ts.map +1 -1
- package/dist-types/components/select-rich/src/LionSelectRich.d.ts +32 -0
- package/dist-types/components/select-rich/src/LionSelectRich.d.ts.map +1 -1
- package/dist-types/components/tooltip/src/LionTooltip.d.ts +4 -20
- package/dist-types/components/tooltip/src/LionTooltip.d.ts.map +1 -1
- package/dist-types/components/tooltip/test/lion-tooltip.test.d.ts.map +1 -1
- package/dist-types/exports/overlays.d.ts +1 -0
- package/dist-types/tsconfig.tsbuildinfo +1 -1
- package/docs/components/checkbox-group/overview.md +2 -2
- package/docs/components/collapsible/overview.md +1 -1
- package/docs/components/combobox/overview.md +1 -1
- package/docs/components/combobox/use-cases.md +1 -1
- package/docs/components/dialog/overview.md +1 -1
- package/docs/components/fieldset/overview.md +5 -5
- package/docs/components/form/overview.md +3 -3
- package/docs/components/icon/use-cases.md +1 -1
- package/docs/components/input/overview.md +1 -1
- package/docs/components/input-amount/overview.md +3 -3
- package/docs/components/input-date/overview.md +3 -3
- package/docs/components/input-datepicker/overview.md +5 -5
- package/docs/components/input-email/overview.md +2 -2
- package/docs/components/input-iban/overview.md +2 -2
- package/docs/components/input-range/overview.md +2 -2
- package/docs/components/input-stepper/overview.md +1 -1
- package/docs/components/input-tel/overview.md +1 -1
- package/docs/components/input-tel/use-cases.md +6 -6
- package/docs/components/input-tel-dropdown/overview.md +1 -1
- package/docs/components/radio-group/overview.md +3 -3
- package/docs/components/select-rich/use-cases.md +1 -1
- package/docs/components/tabs/examples.md +1 -1
- 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/systems/core/overview.md +1 -1
- package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
- package/docs/fundamentals/systems/form/model-value.md +1 -1
- package/docs/fundamentals/systems/form/overview.md +28 -28
- package/docs/fundamentals/systems/form/validate.md +1 -1
- package/docs/fundamentals/systems/localize/overview.md +4 -4
- package/docs/fundamentals/systems/overlays/configuration.md +2 -2
- package/docs/fundamentals/systems/overlays/overview.md +5 -5
- package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
- package/docs/fundamentals/tools/helpers/overview.md +1 -1
- package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
- package/docs/guides/how-to/create-a-custom-field.md +5 -5
- 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/styling.md +1 -1
- package/exports/overlays.js +1 -0
- package/package.json +1 -1
|
@@ -210,10 +210,10 @@ export class LionAccordion extends LitElement {
|
|
|
210
210
|
*/
|
|
211
211
|
__rearrangeInvokersAndContent() {
|
|
212
212
|
const invokers = /** @type {HTMLElement[]} */ (
|
|
213
|
-
Array.from(this.
|
|
213
|
+
Array.from(this.children).filter(child => child.slot === 'invoker')
|
|
214
214
|
);
|
|
215
215
|
const contents = /** @type {HTMLElement[]} */ (
|
|
216
|
-
Array.from(this.
|
|
216
|
+
Array.from(this.children).filter(child => child.slot === 'content')
|
|
217
217
|
);
|
|
218
218
|
const accordion = this.shadowRoot?.querySelector('slot[name=_accordion]');
|
|
219
219
|
if (accordion) {
|
|
@@ -2,14 +2,6 @@ import { html, LitElement } from 'lit';
|
|
|
2
2
|
import { OverlayMixin, withModalDialogConfig } from '@lion/ui/overlays.js';
|
|
3
3
|
|
|
4
4
|
export class LionDialog extends OverlayMixin(LitElement) {
|
|
5
|
-
constructor() {
|
|
6
|
-
super();
|
|
7
|
-
/** @private */
|
|
8
|
-
this.__toggle = () => {
|
|
9
|
-
this.opened = !this.opened;
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
|
|
13
5
|
/**
|
|
14
6
|
* @protected
|
|
15
7
|
*/
|
|
@@ -20,26 +12,6 @@ export class LionDialog extends OverlayMixin(LitElement) {
|
|
|
20
12
|
};
|
|
21
13
|
}
|
|
22
14
|
|
|
23
|
-
/**
|
|
24
|
-
* @protected
|
|
25
|
-
*/
|
|
26
|
-
_setupOpenCloseListeners() {
|
|
27
|
-
super._setupOpenCloseListeners();
|
|
28
|
-
if (this._overlayInvokerNode) {
|
|
29
|
-
this._overlayInvokerNode.addEventListener('click', this.__toggle);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* @protected
|
|
35
|
-
*/
|
|
36
|
-
_teardownOpenCloseListeners() {
|
|
37
|
-
super._teardownOpenCloseListeners();
|
|
38
|
-
if (this._overlayInvokerNode) {
|
|
39
|
-
this._overlayInvokerNode.removeEventListener('click', this.__toggle);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
15
|
render() {
|
|
44
16
|
return html`
|
|
45
17
|
<slot name="invoker"></slot>
|
|
@@ -39,7 +39,8 @@ describe('lion-dialog', () => {
|
|
|
39
39
|
`);
|
|
40
40
|
const invoker = /** @type {HTMLElement} */ (el.querySelector('[slot="invoker"]'));
|
|
41
41
|
invoker.click();
|
|
42
|
-
|
|
42
|
+
// @ts-expect-error [allow-protected-in-tests]
|
|
43
|
+
await el._overlayCtrl._showComplete;
|
|
43
44
|
expect(el.opened).to.be.true;
|
|
44
45
|
});
|
|
45
46
|
|
|
@@ -57,14 +58,18 @@ describe('lion-dialog', () => {
|
|
|
57
58
|
</lion-dialog>
|
|
58
59
|
`);
|
|
59
60
|
|
|
60
|
-
// @ts-expect-error [allow-protected
|
|
61
|
+
// @ts-expect-error [allow-protected-in-tests]
|
|
61
62
|
el._overlayInvokerNode.click();
|
|
63
|
+
// @ts-expect-error [allow-protected-in-tests]
|
|
64
|
+
await el._overlayCtrl._showComplete;
|
|
62
65
|
expect(el.opened).to.be.true;
|
|
63
66
|
|
|
64
|
-
const
|
|
67
|
+
const nestedDialogEl = /** @type {LionDialog} */ (el.querySelector('lion-dialog'));
|
|
65
68
|
// @ts-expect-error you're not allowed to call protected _overlayInvokerNode in public context, but for testing it's okay
|
|
66
|
-
|
|
67
|
-
expect
|
|
69
|
+
nestedDialogEl?._overlayInvokerNode.click();
|
|
70
|
+
// @ts-expect-error [allow-protected-in-tests]
|
|
71
|
+
await nestedDialogEl._overlayCtrl._showComplete;
|
|
72
|
+
expect(nestedDialogEl.opened).to.be.true;
|
|
68
73
|
});
|
|
69
74
|
});
|
|
70
75
|
});
|
|
@@ -305,6 +305,7 @@ export class LionInputDatepicker extends ScopedElementsMixin(
|
|
|
305
305
|
return {
|
|
306
306
|
...withModalDialogConfig(),
|
|
307
307
|
hidesOnOutsideClick: true,
|
|
308
|
+
visibilityTriggerFunction: undefined,
|
|
308
309
|
...super._defineOverlayConfig(),
|
|
309
310
|
popperConfig: {
|
|
310
311
|
...super._defineOverlayConfig().popperConfig,
|
|
@@ -379,10 +380,7 @@ export class LionInputDatepicker extends ScopedElementsMixin(
|
|
|
379
380
|
// On every validator change, synchronize disabled dates: this means
|
|
380
381
|
// we need to extract minDate, maxDate, minMaxDate and disabledDates validators
|
|
381
382
|
validators.forEach(v => {
|
|
382
|
-
const vctor =
|
|
383
|
-
/** @type {typeof import('../../form-core/src/validate/Validator.js').Validator} */ (
|
|
384
|
-
v.constructor
|
|
385
|
-
);
|
|
383
|
+
const vctor = /** @type {typeof import('@lion/ui/form-core.js').Validator} */ (v.constructor);
|
|
386
384
|
if (vctor.validatorName === 'MinDate') {
|
|
387
385
|
this.__calendarMinDate = v.param;
|
|
388
386
|
} else if (vctor.validatorName === 'MaxDate') {
|
|
@@ -4,7 +4,7 @@ import { LocalizeManager } from './LocalizeManager.js';
|
|
|
4
4
|
/** @type {LocalizeManager} */
|
|
5
5
|
// eslint-disable-next-line import/no-mutable-exports
|
|
6
6
|
export const localize =
|
|
7
|
-
singletonManager.get('@lion/ui::localize::0.x') ||
|
|
7
|
+
/** @type {LocalizeManager} */ (singletonManager.get('@lion/ui::localize::0.x')) ||
|
|
8
8
|
new LocalizeManager({
|
|
9
9
|
autoLoadOnLocaleChange: true,
|
|
10
10
|
fallbackLocale: 'en-GB',
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { EventTargetShim } from '@lion/ui/core.js';
|
|
2
1
|
import { adoptStyles } from 'lit';
|
|
3
2
|
import { overlays } from './singleton.js';
|
|
4
3
|
import { containFocus } from './utils/contain-focus.js';
|
|
@@ -14,6 +13,22 @@ import { overlayShadowDomStyle } from './overlayShadowDomStyle.js';
|
|
|
14
13
|
* @typedef {'setup'|'init'|'teardown'|'before-show'|'show'|'hide'|'add'|'remove'} OverlayPhase
|
|
15
14
|
*/
|
|
16
15
|
|
|
16
|
+
const rootNodeStylesMap = new WeakSet();
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Returns element that adopts stylesheet
|
|
20
|
+
* @param {Element} shadowOrBodyEl
|
|
21
|
+
* @returns {ShadowRoot}
|
|
22
|
+
*/
|
|
23
|
+
function getRootNodeOrBodyElThatAdoptsStylesheet(shadowOrBodyEl) {
|
|
24
|
+
const rootNode = /** @type {* & DocumentOrShadowRoot} */ (shadowOrBodyEl.getRootNode());
|
|
25
|
+
if (rootNode === document) {
|
|
26
|
+
// @ts-ignore
|
|
27
|
+
return document.body;
|
|
28
|
+
}
|
|
29
|
+
return rootNode;
|
|
30
|
+
}
|
|
31
|
+
|
|
17
32
|
/**
|
|
18
33
|
* From:
|
|
19
34
|
* - wrappingDialogNodeL1: `<dialog role="none"/>`
|
|
@@ -42,7 +57,7 @@ function rearrangeNodes({ wrappingDialogNodeL1, contentWrapperNodeL2, contentNod
|
|
|
42
57
|
}
|
|
43
58
|
|
|
44
59
|
let parentElement;
|
|
45
|
-
const tempMarker = document.createComment('
|
|
60
|
+
const tempMarker = document.createComment('tempMarker');
|
|
46
61
|
|
|
47
62
|
if (contentWrapperNodeL2.isConnected) {
|
|
48
63
|
// This is the case when contentWrapperNode (living in shadow dom, wrapping <slot name="my-content-outlet">) is already provided via controller.
|
|
@@ -113,7 +128,7 @@ const supportsCSSTypedObject = window.CSS?.number && document.body.attributeStyl
|
|
|
113
128
|
* bottom/top/left/right sheets etc.
|
|
114
129
|
*
|
|
115
130
|
*/
|
|
116
|
-
export class OverlayController extends
|
|
131
|
+
export class OverlayController extends EventTarget {
|
|
117
132
|
/**
|
|
118
133
|
* @constructor
|
|
119
134
|
* @param {OverlayConfig} config initial config. Will be remembered as shared config
|
|
@@ -150,7 +165,7 @@ export class OverlayController extends EventTargetShim {
|
|
|
150
165
|
hidesOnOutsideClick: false,
|
|
151
166
|
isTooltip: false,
|
|
152
167
|
invokerRelation: 'description',
|
|
153
|
-
|
|
168
|
+
visibilityTriggerFunction: undefined,
|
|
154
169
|
handlesAccessibility: false,
|
|
155
170
|
popperConfig: {
|
|
156
171
|
placement: 'top',
|
|
@@ -403,6 +418,10 @@ export class OverlayController extends EventTargetShim {
|
|
|
403
418
|
return /** @type {ViewportConfig} */ (this.config?.viewportConfig);
|
|
404
419
|
}
|
|
405
420
|
|
|
421
|
+
get visibilityTriggerFunction() {
|
|
422
|
+
return /** @type {function} */ (this.config?.visibilityTriggerFunction);
|
|
423
|
+
}
|
|
424
|
+
|
|
406
425
|
/**
|
|
407
426
|
* @desc The element our local overlay will be positioned relative to.
|
|
408
427
|
* @type {HTMLElement | undefined}
|
|
@@ -454,11 +473,9 @@ export class OverlayController extends EventTargetShim {
|
|
|
454
473
|
...(this.__sharedConfig.popperConfig || {}),
|
|
455
474
|
...(cfgToAdd.popperConfig || {}),
|
|
456
475
|
modifiers: [
|
|
457
|
-
...(
|
|
458
|
-
|
|
459
|
-
...(
|
|
460
|
-
[]),
|
|
461
|
-
...((cfgToAdd.popperConfig && cfgToAdd.popperConfig.modifiers) || []),
|
|
476
|
+
...(this._defaultConfig.popperConfig?.modifiers || []),
|
|
477
|
+
...(this.__sharedConfig.popperConfig?.modifiers || []),
|
|
478
|
+
...(cfgToAdd.popperConfig?.modifiers || []),
|
|
462
479
|
],
|
|
463
480
|
},
|
|
464
481
|
};
|
|
@@ -514,16 +531,24 @@ export class OverlayController extends EventTargetShim {
|
|
|
514
531
|
this.contentWrapperNode.removeAttribute('class');
|
|
515
532
|
|
|
516
533
|
if (this.placementMode === 'local') {
|
|
517
|
-
// Lazily load Popper
|
|
534
|
+
// Lazily load Popper as soon as the first local overlay is used...
|
|
518
535
|
if (!OverlayController.popperModule) {
|
|
519
536
|
OverlayController.popperModule = preloadPopper();
|
|
520
537
|
}
|
|
521
538
|
}
|
|
522
|
-
|
|
523
|
-
adoptStyles(rootNode, [...(rootNode.adoptedStyleSheets || []), overlayShadowDomStyle]);
|
|
539
|
+
this.__initOverlayStyles();
|
|
524
540
|
this._handleFeatures({ phase: 'init' });
|
|
525
541
|
}
|
|
526
542
|
|
|
543
|
+
__initOverlayStyles() {
|
|
544
|
+
const rootNode = getRootNodeOrBodyElThatAdoptsStylesheet(this.contentWrapperNode);
|
|
545
|
+
if (!rootNodeStylesMap.has(rootNode)) {
|
|
546
|
+
// TODO: ideally we should also support a teardown
|
|
547
|
+
adoptStyles(rootNode, [...(rootNode.adoptedStyleSheets || []), overlayShadowDomStyle]);
|
|
548
|
+
rootNodeStylesMap.add(rootNode);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
527
552
|
/**
|
|
528
553
|
* Here we arrange our content node via:
|
|
529
554
|
* 1. HTMLDialogElement: the content will always be painted to the browser's top layer
|
|
@@ -710,7 +735,7 @@ export class OverlayController extends EventTargetShim {
|
|
|
710
735
|
this.dispatchEvent(event);
|
|
711
736
|
if (!event.defaultPrevented) {
|
|
712
737
|
if (this.__wrappingDialogNode instanceof HTMLDialogElement) {
|
|
713
|
-
this.__wrappingDialogNode.
|
|
738
|
+
this.__wrappingDialogNode.open = true;
|
|
714
739
|
}
|
|
715
740
|
// @ts-ignore
|
|
716
741
|
this.__wrappingDialogNode.style.display = '';
|
|
@@ -947,6 +972,26 @@ export class OverlayController extends EventTargetShim {
|
|
|
947
972
|
if (this.inheritsReferenceWidth) {
|
|
948
973
|
this._handleInheritsReferenceWidth();
|
|
949
974
|
}
|
|
975
|
+
if (this.visibilityTriggerFunction) {
|
|
976
|
+
this._handleVisibilityTriggers({ phase });
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* @param {{ phase: OverlayPhase }} config
|
|
982
|
+
*/
|
|
983
|
+
_handleVisibilityTriggers({ phase }) {
|
|
984
|
+
if (typeof this.visibilityTriggerFunction === 'function') {
|
|
985
|
+
if (phase === 'init') {
|
|
986
|
+
this.__visibilityTriggerHandler = this.visibilityTriggerFunction({
|
|
987
|
+
phase,
|
|
988
|
+
controller: this,
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
if (this.__visibilityTriggerHandler[phase]) {
|
|
992
|
+
this.__visibilityTriggerHandler[phase]();
|
|
993
|
+
}
|
|
994
|
+
}
|
|
950
995
|
}
|
|
951
996
|
|
|
952
997
|
/**
|
|
@@ -1009,10 +1054,14 @@ export class OverlayController extends EventTargetShim {
|
|
|
1009
1054
|
break;
|
|
1010
1055
|
}
|
|
1011
1056
|
case 'show':
|
|
1012
|
-
this.
|
|
1057
|
+
if (this.config.hasBackdrop) {
|
|
1058
|
+
this.backdropNode.classList.add(`overlays__backdrop--visible`);
|
|
1059
|
+
}
|
|
1013
1060
|
this.__hasActiveBackdrop = true;
|
|
1014
1061
|
break;
|
|
1015
1062
|
case 'hide':
|
|
1063
|
+
case 'teardown':
|
|
1064
|
+
this.backdropNode.classList.remove(`overlays__backdrop--visible`);
|
|
1016
1065
|
this.__hasActiveBackdrop = false;
|
|
1017
1066
|
break;
|
|
1018
1067
|
}
|
|
@@ -1252,6 +1301,16 @@ export class OverlayController extends EventTargetShim {
|
|
|
1252
1301
|
});
|
|
1253
1302
|
}
|
|
1254
1303
|
}
|
|
1304
|
+
|
|
1305
|
+
_hasDisabledInvoker() {
|
|
1306
|
+
if (this.invokerNode) {
|
|
1307
|
+
return (
|
|
1308
|
+
/** @type {HTMLElement & { disabled: boolean }} */ (this.invokerNode).disabled ||
|
|
1309
|
+
this.invokerNode.getAttribute('aria-disabled') === 'true'
|
|
1310
|
+
);
|
|
1311
|
+
}
|
|
1312
|
+
return false;
|
|
1313
|
+
}
|
|
1255
1314
|
}
|
|
1256
1315
|
/** @type {Promise<PopperModule> | undefined} */
|
|
1257
1316
|
OverlayController.popperModule = undefined;
|
package/components/overlays/src/configurations/visibility-trigger-partials/withClickInteraction.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('@lion/ui/types/overlays.js').OverlayConfig} OverlayConfig
|
|
3
|
+
* @typedef {import('@lion/ui/overlays.js').OverlayController} OverlayController
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Use for popovers/dropdowns, (modal) dialogs etc...
|
|
8
|
+
* @returns {Partial<OverlayConfig>}
|
|
9
|
+
*/
|
|
10
|
+
export function withClickInteraction() {
|
|
11
|
+
return {
|
|
12
|
+
visibilityTriggerFunction: (
|
|
13
|
+
/** @type {{ controller: OverlayController }} */ { controller },
|
|
14
|
+
) => {
|
|
15
|
+
function handleOpenClosed() {
|
|
16
|
+
if (controller._hasDisabledInvoker()) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
controller.toggle();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
init: () => {
|
|
24
|
+
controller.invokerNode?.addEventListener('click', handleOpenClosed);
|
|
25
|
+
},
|
|
26
|
+
teardown: () => {
|
|
27
|
+
controller.invokerNode?.removeEventListener('click', handleOpenClosed);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
package/components/overlays/src/configurations/visibility-trigger-partials/withHoverInteraction.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('@lion/ui/types/overlays.js').OverlayConfig} OverlayConfig
|
|
3
|
+
* @typedef {import('@lion/ui/overlays.js').OverlayController} OverlayController
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// N.B. Below logic is tested in LionTooltip
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Use for tooltips and [flyout menus](https://www.w3.org/WAI/tutorials/menus/flyout/).
|
|
10
|
+
* Note that it handles both mouse hover and focus interaction.
|
|
11
|
+
* Provide delayIn and delayOut when the content needs a small delay before being showed
|
|
12
|
+
* @param {{ delayIn?: number, delayOut?: number }} options
|
|
13
|
+
* @returns {Partial<OverlayConfig>}
|
|
14
|
+
*/
|
|
15
|
+
export function withHoverInteraction({ delayIn = 0, delayOut = 300 }) {
|
|
16
|
+
return {
|
|
17
|
+
visibilityTriggerFunction: (
|
|
18
|
+
/** @type {{ controller: OverlayController }} */ { controller },
|
|
19
|
+
) => {
|
|
20
|
+
let isFocused = false;
|
|
21
|
+
let isHovered = false;
|
|
22
|
+
/** @type {NodeJS.Timeout} */
|
|
23
|
+
let delayTimeout;
|
|
24
|
+
|
|
25
|
+
function resetActive() {
|
|
26
|
+
isFocused = false;
|
|
27
|
+
isHovered = false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {Event} event
|
|
32
|
+
*/
|
|
33
|
+
function handleOpenClosed(event) {
|
|
34
|
+
const { type } = event;
|
|
35
|
+
if (controller._hasDisabledInvoker()) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
clearTimeout(delayTimeout);
|
|
39
|
+
isFocused = type === 'focusout' ? false : isFocused || type === 'focusin';
|
|
40
|
+
isHovered = type === 'mouseleave' ? false : isHovered || type === 'mouseenter';
|
|
41
|
+
const shouldOpen = isFocused || isHovered;
|
|
42
|
+
|
|
43
|
+
if (shouldOpen) {
|
|
44
|
+
delayTimeout = setTimeout(() => {
|
|
45
|
+
controller.show();
|
|
46
|
+
}, delayIn);
|
|
47
|
+
} else {
|
|
48
|
+
delayTimeout = setTimeout(() => {
|
|
49
|
+
controller.hide();
|
|
50
|
+
}, delayOut);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
init: () => {
|
|
56
|
+
controller.addEventListener('hide', resetActive);
|
|
57
|
+
controller.contentNode?.addEventListener('mouseenter', handleOpenClosed);
|
|
58
|
+
controller.contentNode?.addEventListener('mouseleave', handleOpenClosed);
|
|
59
|
+
controller.invokerNode?.addEventListener('mouseenter', handleOpenClosed);
|
|
60
|
+
controller.invokerNode?.addEventListener('mouseleave', handleOpenClosed);
|
|
61
|
+
controller.invokerNode?.addEventListener('focusin', handleOpenClosed);
|
|
62
|
+
controller.invokerNode?.addEventListener('focusout', handleOpenClosed);
|
|
63
|
+
},
|
|
64
|
+
teardown: () => {
|
|
65
|
+
controller.removeEventListener('hide', resetActive);
|
|
66
|
+
controller.contentNode?.removeEventListener('mouseenter', handleOpenClosed);
|
|
67
|
+
controller.contentNode?.removeEventListener('mouseleave', handleOpenClosed);
|
|
68
|
+
controller.invokerNode?.removeEventListener('mouseenter', handleOpenClosed);
|
|
69
|
+
controller.invokerNode?.removeEventListener('mouseleave', handleOpenClosed);
|
|
70
|
+
controller.invokerNode?.removeEventListener('focusin', handleOpenClosed);
|
|
71
|
+
controller.invokerNode?.removeEventListener('focusout', handleOpenClosed);
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { withClickInteraction } from './visibility-trigger-partials/withClickInteraction.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* @typedef {import('../../types/OverlayConfig.js').OverlayConfig} OverlayConfig
|
|
3
5
|
*/
|
|
@@ -13,4 +15,5 @@ export const withBottomSheetConfig = () =>
|
|
|
13
15
|
placement: 'bottom',
|
|
14
16
|
},
|
|
15
17
|
handlesAccessibility: true,
|
|
18
|
+
...withClickInteraction(),
|
|
16
19
|
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { withClickInteraction } from './visibility-trigger-partials/withClickInteraction.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* @typedef {import('../../types/OverlayConfig.js').OverlayConfig} OverlayConfig
|
|
3
5
|
*/
|
|
@@ -19,4 +21,5 @@ export const withDropdownConfig = () =>
|
|
|
19
21
|
],
|
|
20
22
|
},
|
|
21
23
|
handlesAccessibility: true,
|
|
24
|
+
...withClickInteraction(),
|
|
22
25
|
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { withClickInteraction } from './visibility-trigger-partials/withClickInteraction.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* @typedef {import('../../types/OverlayConfig.js').OverlayConfig} OverlayConfig
|
|
3
5
|
*/
|
|
@@ -13,4 +15,5 @@ export const withModalDialogConfig = () =>
|
|
|
13
15
|
trapsKeyboardFocus: true,
|
|
14
16
|
hidesOnEsc: true,
|
|
15
17
|
handlesAccessibility: true,
|
|
18
|
+
...withClickInteraction(),
|
|
16
19
|
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { withHoverInteraction } from './visibility-trigger-partials/withHoverInteraction.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('@lion/ui/types/overlays.js').OverlayConfig} OverlayConfig
|
|
5
|
+
* @typedef {import('@lion/ui/overlays.js').OverlayController} OverlayController
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
*
|
|
10
|
+
* @param {{invokerRelation?: 'description'| 'label', delayIn?: number, delayOut?: number }} options
|
|
11
|
+
*/
|
|
12
|
+
export const withTooltipConfig = ({
|
|
13
|
+
invokerRelation = 'description',
|
|
14
|
+
delayIn = 300,
|
|
15
|
+
delayOut = 300,
|
|
16
|
+
} = {}) =>
|
|
17
|
+
/** @type {OverlayConfig} */ ({
|
|
18
|
+
placementMode: 'local',
|
|
19
|
+
elementToFocusAfterHide: undefined,
|
|
20
|
+
hidesOnEsc: true,
|
|
21
|
+
hidesOnOutsideEsc: true,
|
|
22
|
+
handlesAccessibility: true,
|
|
23
|
+
isTooltip: true,
|
|
24
|
+
invokerRelation,
|
|
25
|
+
popperConfig: {
|
|
26
|
+
strategy: 'absolute',
|
|
27
|
+
},
|
|
28
|
+
...withHoverInteraction({ delayIn, delayOut }),
|
|
29
|
+
});
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import sinon from 'sinon';
|
|
12
12
|
import { OverlayController, overlays } from '@lion/ui/overlays.js';
|
|
13
13
|
import { mimicClick } from '@lion/ui/overlays-test-helpers.js';
|
|
14
|
-
|
|
14
|
+
import { overlayShadowDomStyle } from '../src/overlayShadowDomStyle.js';
|
|
15
15
|
import { keyCodes } from '../src/utils/key-codes.js';
|
|
16
16
|
import { simulateTab } from '../src/utils/simulate-tab.js';
|
|
17
17
|
|
|
@@ -23,6 +23,22 @@ import { simulateTab } from '../src/utils/simulate-tab.js';
|
|
|
23
23
|
const wrappingDialogNodeStyle = 'display: none; z-index: 9999;';
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
+
* Returns element that adopts stylesheet
|
|
27
|
+
* @param {Element} shadowOrBodyEl
|
|
28
|
+
* @returns {ShadowRoot}
|
|
29
|
+
*/
|
|
30
|
+
function getRootNodeOrBodyElThatAdoptsStylesheet(shadowOrBodyEl) {
|
|
31
|
+
const rootNode = /** @type {* & DocumentOrShadowRoot} */ (shadowOrBodyEl.getRootNode());
|
|
32
|
+
if (rootNode === document) {
|
|
33
|
+
// @ts-ignore
|
|
34
|
+
return document.body;
|
|
35
|
+
}
|
|
36
|
+
return rootNode;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Make sure that all browsers serialize html in a similar way
|
|
41
|
+
* (Firefox tends to output empty style attrs)
|
|
26
42
|
* @param {HTMLElement} node
|
|
27
43
|
*/
|
|
28
44
|
function normalizeOverlayContentWapper(node) {
|
|
@@ -80,6 +96,55 @@ describe('OverlayController', () => {
|
|
|
80
96
|
expect(ctrl.contentNode.parentElement).to.equal(ctrl.contentWrapperNode);
|
|
81
97
|
});
|
|
82
98
|
|
|
99
|
+
describe('Stylesheets', () => {
|
|
100
|
+
it('adds a stylesheet to the body when contentWrapper is located there', async () => {
|
|
101
|
+
new OverlayController({
|
|
102
|
+
...withLocalTestConfig(),
|
|
103
|
+
});
|
|
104
|
+
// @ts-ignore
|
|
105
|
+
if (document.body.adoptedStyleSheets) {
|
|
106
|
+
// @ts-ignore
|
|
107
|
+
expect(document.body.adoptedStyleSheets).to.include(overlayShadowDomStyle.styleSheet);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('adds a stylesheet to the shadowRoot when contentWrappeNode is located there', async () => {
|
|
112
|
+
const contentNode = /** @type {HTMLElement} */ (await fixture('<div>contentful</div>'));
|
|
113
|
+
const shadowHost = document.createElement('div');
|
|
114
|
+
shadowHost.attachShadow({ mode: 'open' });
|
|
115
|
+
/** @type {ShadowRoot} */ (shadowHost.shadowRoot).innerHTML = `<slot></slot>`;
|
|
116
|
+
shadowHost.appendChild(contentNode);
|
|
117
|
+
document.body.appendChild(shadowHost);
|
|
118
|
+
const ctrl = new OverlayController({
|
|
119
|
+
...withLocalTestConfig(),
|
|
120
|
+
contentNode,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const rootNodeOrBody = getRootNodeOrBodyElThatAdoptsStylesheet(ctrl.contentWrapperNode);
|
|
124
|
+
expect(rootNodeOrBody).to.not.equal(document.body);
|
|
125
|
+
|
|
126
|
+
if (rootNodeOrBody.adoptedStyleSheets) {
|
|
127
|
+
expect(rootNodeOrBody.adoptedStyleSheets).to.include(overlayShadowDomStyle.styleSheet);
|
|
128
|
+
}
|
|
129
|
+
document.body.removeChild(shadowHost);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('does not add same stylesheet twice', async () => {
|
|
133
|
+
// @ts-ignore
|
|
134
|
+
if (!document.body.adoptedStyleSheets) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
new OverlayController({ ...withLocalTestConfig() });
|
|
139
|
+
// @ts-ignore
|
|
140
|
+
const amountOfStylesheetsAfterOneInit = document.body.adoptedStyleSheets.length;
|
|
141
|
+
|
|
142
|
+
new OverlayController({ ...withLocalTestConfig() });
|
|
143
|
+
// @ts-ignore
|
|
144
|
+
expect(document.body.adoptedStyleSheets.length).to.equal(amountOfStylesheetsAfterOneInit);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
83
148
|
describe('Z-index on local overlays', () => {
|
|
84
149
|
/** @type {HTMLElement} */
|
|
85
150
|
let contentNode;
|
|
@@ -895,11 +960,11 @@ describe('OverlayController', () => {
|
|
|
895
960
|
await fixture('<div><textarea></textarea></div>')
|
|
896
961
|
);
|
|
897
962
|
|
|
898
|
-
const
|
|
899
|
-
|
|
900
|
-
/** @type {ShadowRoot} */ (
|
|
901
|
-
|
|
902
|
-
document.body.appendChild(
|
|
963
|
+
const shadowHost = document.createElement('div');
|
|
964
|
+
shadowHost.attachShadow({ mode: 'open' });
|
|
965
|
+
/** @type {ShadowRoot} */ (shadowHost.shadowRoot).innerHTML = `<slot></slot>`;
|
|
966
|
+
shadowHost.appendChild(contentNode);
|
|
967
|
+
document.body.appendChild(shadowHost);
|
|
903
968
|
|
|
904
969
|
const ctrl = new OverlayController({
|
|
905
970
|
...withGlobalTestConfig(),
|
|
@@ -915,7 +980,7 @@ describe('OverlayController', () => {
|
|
|
915
980
|
await ctrl.hide();
|
|
916
981
|
expect(document.activeElement).to.equal(input);
|
|
917
982
|
|
|
918
|
-
document.body.removeChild(
|
|
983
|
+
document.body.removeChild(shadowHost);
|
|
919
984
|
});
|
|
920
985
|
|
|
921
986
|
it(`only sets focus when outside world didn't take over already`, async () => {
|
|
@@ -1450,6 +1515,22 @@ describe('OverlayController', () => {
|
|
|
1450
1515
|
expect(ctrl.contentWrapperNode.classList.contains('overlays__overlay-container--top-right'));
|
|
1451
1516
|
expect(ctrl.isShown).to.be.true;
|
|
1452
1517
|
});
|
|
1518
|
+
|
|
1519
|
+
it('disables backdrop when switching to hasBackrop "false"', async () => {
|
|
1520
|
+
const ctrl = new OverlayController({
|
|
1521
|
+
...withLocalTestConfig(),
|
|
1522
|
+
contentNode: /** @type {HTMLElement} */ (await fixture(html`<div>content1</div>`)),
|
|
1523
|
+
hasBackdrop: true,
|
|
1524
|
+
});
|
|
1525
|
+
await ctrl.show(); // Popper adds inline styles
|
|
1526
|
+
expect(ctrl.backdropNode).not.to.be.undefined;
|
|
1527
|
+
expect(Array.from(ctrl.backdropNode.classList)).to.include('overlays__backdrop--visible');
|
|
1528
|
+
|
|
1529
|
+
ctrl.updateConfig({
|
|
1530
|
+
hasBackdrop: false,
|
|
1531
|
+
});
|
|
1532
|
+
expect(Array.from(ctrl.backdropNode.classList)).to.not.include('overlays__backdrop--visible');
|
|
1533
|
+
});
|
|
1453
1534
|
});
|
|
1454
1535
|
|
|
1455
1536
|
describe('Accessibility', () => {
|
|
@@ -72,6 +72,12 @@ export interface OverlayConfig {
|
|
|
72
72
|
|
|
73
73
|
/** render a div instead of dialog */
|
|
74
74
|
_noDialogEl?: Boolean;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Determines the conditions hiding/showing should be based on. It gets the OverlayController as input and returns an object with
|
|
78
|
+
* functions with Overlay phases as keys
|
|
79
|
+
*/
|
|
80
|
+
visibilityTriggerFunction?: Function;
|
|
75
81
|
}
|
|
76
82
|
|
|
77
83
|
export type ViewportPlacement =
|
|
@@ -336,6 +336,8 @@ export class LionSelectRich extends SlotMixin(ScopedElementsMixin(OverlayMixin(L
|
|
|
336
336
|
_defineOverlayConfig() {
|
|
337
337
|
return {
|
|
338
338
|
...withDropdownConfig(),
|
|
339
|
+
// Needs more advanced behavior (see `__invokerOnClick`)
|
|
340
|
+
visibilityTriggerFunction: undefined,
|
|
339
341
|
};
|
|
340
342
|
}
|
|
341
343
|
|