@lion/ui 0.0.10 → 0.0.11
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 +65 -11
- 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 +72 -7
- package/components/overlays/types/OverlayConfig.ts +6 -0
- package/components/tooltip/src/LionTooltip.js +3 -90
- package/components/tooltip/test/lion-tooltip.test.js +52 -20
- package/custom-elements.json +296 -153
- 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 +10 -0
- 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/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',
|
|
@@ -14,6 +14,22 @@ import { overlayShadowDomStyle } from './overlayShadowDomStyle.js';
|
|
|
14
14
|
* @typedef {'setup'|'init'|'teardown'|'before-show'|'show'|'hide'|'add'|'remove'} OverlayPhase
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
const rootNodeStylesMap = new WeakSet();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Returns element that adopts stylesheet
|
|
21
|
+
* @param {Element} shadowOrBodyEl
|
|
22
|
+
* @returns {ShadowRoot}
|
|
23
|
+
*/
|
|
24
|
+
function getRootNodeOrBodyElThatAdoptsStylesheet(shadowOrBodyEl) {
|
|
25
|
+
const rootNode = /** @type {* & DocumentOrShadowRoot} */ (shadowOrBodyEl.getRootNode());
|
|
26
|
+
if (rootNode === document) {
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
return document.body;
|
|
29
|
+
}
|
|
30
|
+
return rootNode;
|
|
31
|
+
}
|
|
32
|
+
|
|
17
33
|
/**
|
|
18
34
|
* From:
|
|
19
35
|
* - wrappingDialogNodeL1: `<dialog role="none"/>`
|
|
@@ -42,7 +58,7 @@ function rearrangeNodes({ wrappingDialogNodeL1, contentWrapperNodeL2, contentNod
|
|
|
42
58
|
}
|
|
43
59
|
|
|
44
60
|
let parentElement;
|
|
45
|
-
const tempMarker = document.createComment('
|
|
61
|
+
const tempMarker = document.createComment('tempMarker');
|
|
46
62
|
|
|
47
63
|
if (contentWrapperNodeL2.isConnected) {
|
|
48
64
|
// This is the case when contentWrapperNode (living in shadow dom, wrapping <slot name="my-content-outlet">) is already provided via controller.
|
|
@@ -150,7 +166,7 @@ export class OverlayController extends EventTargetShim {
|
|
|
150
166
|
hidesOnOutsideClick: false,
|
|
151
167
|
isTooltip: false,
|
|
152
168
|
invokerRelation: 'description',
|
|
153
|
-
|
|
169
|
+
visibilityTriggerFunction: undefined,
|
|
154
170
|
handlesAccessibility: false,
|
|
155
171
|
popperConfig: {
|
|
156
172
|
placement: 'top',
|
|
@@ -403,6 +419,10 @@ export class OverlayController extends EventTargetShim {
|
|
|
403
419
|
return /** @type {ViewportConfig} */ (this.config?.viewportConfig);
|
|
404
420
|
}
|
|
405
421
|
|
|
422
|
+
get visibilityTriggerFunction() {
|
|
423
|
+
return /** @type {function} */ (this.config?.visibilityTriggerFunction);
|
|
424
|
+
}
|
|
425
|
+
|
|
406
426
|
/**
|
|
407
427
|
* @desc The element our local overlay will be positioned relative to.
|
|
408
428
|
* @type {HTMLElement | undefined}
|
|
@@ -454,11 +474,9 @@ export class OverlayController extends EventTargetShim {
|
|
|
454
474
|
...(this.__sharedConfig.popperConfig || {}),
|
|
455
475
|
...(cfgToAdd.popperConfig || {}),
|
|
456
476
|
modifiers: [
|
|
457
|
-
...(
|
|
458
|
-
|
|
459
|
-
...(
|
|
460
|
-
[]),
|
|
461
|
-
...((cfgToAdd.popperConfig && cfgToAdd.popperConfig.modifiers) || []),
|
|
477
|
+
...(this._defaultConfig.popperConfig?.modifiers || []),
|
|
478
|
+
...(this.__sharedConfig.popperConfig?.modifiers || []),
|
|
479
|
+
...(cfgToAdd.popperConfig?.modifiers || []),
|
|
462
480
|
],
|
|
463
481
|
},
|
|
464
482
|
};
|
|
@@ -514,16 +532,24 @@ export class OverlayController extends EventTargetShim {
|
|
|
514
532
|
this.contentWrapperNode.removeAttribute('class');
|
|
515
533
|
|
|
516
534
|
if (this.placementMode === 'local') {
|
|
517
|
-
// Lazily load Popper
|
|
535
|
+
// Lazily load Popper as soon as the first local overlay is used...
|
|
518
536
|
if (!OverlayController.popperModule) {
|
|
519
537
|
OverlayController.popperModule = preloadPopper();
|
|
520
538
|
}
|
|
521
539
|
}
|
|
522
|
-
|
|
523
|
-
adoptStyles(rootNode, [...(rootNode.adoptedStyleSheets || []), overlayShadowDomStyle]);
|
|
540
|
+
this.__initOverlayStyles();
|
|
524
541
|
this._handleFeatures({ phase: 'init' });
|
|
525
542
|
}
|
|
526
543
|
|
|
544
|
+
__initOverlayStyles() {
|
|
545
|
+
const rootNode = getRootNodeOrBodyElThatAdoptsStylesheet(this.contentWrapperNode);
|
|
546
|
+
if (!rootNodeStylesMap.has(rootNode)) {
|
|
547
|
+
// TODO: ideally we should also support a teardown
|
|
548
|
+
adoptStyles(rootNode, [...(rootNode.adoptedStyleSheets || []), overlayShadowDomStyle]);
|
|
549
|
+
rootNodeStylesMap.add(rootNode);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
527
553
|
/**
|
|
528
554
|
* Here we arrange our content node via:
|
|
529
555
|
* 1. HTMLDialogElement: the content will always be painted to the browser's top layer
|
|
@@ -710,7 +736,7 @@ export class OverlayController extends EventTargetShim {
|
|
|
710
736
|
this.dispatchEvent(event);
|
|
711
737
|
if (!event.defaultPrevented) {
|
|
712
738
|
if (this.__wrappingDialogNode instanceof HTMLDialogElement) {
|
|
713
|
-
this.__wrappingDialogNode.
|
|
739
|
+
this.__wrappingDialogNode.open = true;
|
|
714
740
|
}
|
|
715
741
|
// @ts-ignore
|
|
716
742
|
this.__wrappingDialogNode.style.display = '';
|
|
@@ -947,6 +973,24 @@ export class OverlayController extends EventTargetShim {
|
|
|
947
973
|
if (this.inheritsReferenceWidth) {
|
|
948
974
|
this._handleInheritsReferenceWidth();
|
|
949
975
|
}
|
|
976
|
+
|
|
977
|
+
if (this.visibilityTriggerFunction) {
|
|
978
|
+
this._handleUserInteraction({ phase });
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* @param {{ phase: OverlayPhase }} config
|
|
984
|
+
*/
|
|
985
|
+
_handleUserInteraction({ phase }) {
|
|
986
|
+
if (typeof this.visibilityTriggerFunction === 'function') {
|
|
987
|
+
if (phase === 'init') {
|
|
988
|
+
this.__userInteractionHandler = this.visibilityTriggerFunction({ phase, controller: this });
|
|
989
|
+
}
|
|
990
|
+
if (this.__userInteractionHandler[phase]) {
|
|
991
|
+
this.__userInteractionHandler[phase]();
|
|
992
|
+
}
|
|
993
|
+
}
|
|
950
994
|
}
|
|
951
995
|
|
|
952
996
|
/**
|
|
@@ -1252,6 +1296,16 @@ export class OverlayController extends EventTargetShim {
|
|
|
1252
1296
|
});
|
|
1253
1297
|
}
|
|
1254
1298
|
}
|
|
1299
|
+
|
|
1300
|
+
_hasDisabledInvoker() {
|
|
1301
|
+
if (this.invokerNode) {
|
|
1302
|
+
return (
|
|
1303
|
+
/** @type {HTMLElement & { disabled: boolean }} */ (this.invokerNode).disabled ||
|
|
1304
|
+
this.invokerNode.getAttribute('aria-disabled') === 'true'
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1307
|
+
return false;
|
|
1308
|
+
}
|
|
1255
1309
|
}
|
|
1256
1310
|
/** @type {Promise<PopperModule> | undefined} */
|
|
1257
1311
|
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 () => {
|
|
@@ -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 =
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { css, LitElement } from 'lit';
|
|
2
|
-
import { ArrowMixin, OverlayMixin } from '@lion/ui/overlays.js';
|
|
2
|
+
import { ArrowMixin, OverlayMixin, withTooltipConfig } from '@lion/ui/overlays.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* @typedef {import('
|
|
5
|
+
* @typedef {import('@lion/ui/types/overlays.js').OverlayConfig} OverlayConfig
|
|
6
6
|
* @typedef {import('lit').CSSResult} CSSResult
|
|
7
7
|
* @typedef {import('lit').CSSResultArray} CSSResultArray
|
|
8
8
|
*/
|
|
@@ -49,10 +49,6 @@ export class LionTooltip extends ArrowMixin(OverlayMixin(LitElement)) {
|
|
|
49
49
|
* @type {'label'|'description'}
|
|
50
50
|
*/
|
|
51
51
|
this.invokerRelation = 'description';
|
|
52
|
-
/** @protected */
|
|
53
|
-
this._mouseActive = false;
|
|
54
|
-
/** @protected */
|
|
55
|
-
this._keyActive = false;
|
|
56
52
|
}
|
|
57
53
|
|
|
58
54
|
/** @protected */
|
|
@@ -60,90 +56,7 @@ export class LionTooltip extends ArrowMixin(OverlayMixin(LitElement)) {
|
|
|
60
56
|
_defineOverlayConfig() {
|
|
61
57
|
return /** @type {OverlayConfig} */ ({
|
|
62
58
|
...super._defineOverlayConfig(),
|
|
63
|
-
|
|
64
|
-
elementToFocusAfterHide: undefined,
|
|
65
|
-
hidesOnEsc: true,
|
|
66
|
-
hidesOnOutsideEsc: true,
|
|
67
|
-
handlesAccessibility: true,
|
|
68
|
-
isTooltip: true,
|
|
69
|
-
invokerRelation: this.invokerRelation,
|
|
59
|
+
...withTooltipConfig({ invokerRelation: this.invokerRelation }),
|
|
70
60
|
});
|
|
71
61
|
}
|
|
72
|
-
|
|
73
|
-
/** @protected */
|
|
74
|
-
_hasDisabledInvoker() {
|
|
75
|
-
if (this._overlayCtrl && this._overlayCtrl.invoker) {
|
|
76
|
-
return (
|
|
77
|
-
/** @type {HTMLElement & { disabled: boolean }} */ (this._overlayCtrl.invoker).disabled ||
|
|
78
|
-
this._overlayCtrl.invoker.getAttribute('aria-disabled') === 'true'
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/** @protected */
|
|
85
|
-
_setupOpenCloseListeners() {
|
|
86
|
-
super._setupOpenCloseListeners();
|
|
87
|
-
this.__resetActive = this.__resetActive.bind(this);
|
|
88
|
-
this._overlayCtrl.addEventListener('hide', this.__resetActive);
|
|
89
|
-
|
|
90
|
-
this.addEventListener('mouseenter', this._showMouse);
|
|
91
|
-
this.addEventListener('mouseleave', this._hideMouse);
|
|
92
|
-
|
|
93
|
-
this._showKey = this._showKey.bind(this);
|
|
94
|
-
this._overlayInvokerNode.addEventListener('focusin', this._showKey);
|
|
95
|
-
|
|
96
|
-
this._hideKey = this._hideKey.bind(this);
|
|
97
|
-
this._overlayInvokerNode.addEventListener('focusout', this._hideKey);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/** @protected */
|
|
101
|
-
_teardownOpenCloseListeners() {
|
|
102
|
-
super._teardownOpenCloseListeners();
|
|
103
|
-
this._overlayCtrl.removeEventListener('hide', this.__resetActive);
|
|
104
|
-
this.removeEventListener('mouseenter', this._showMouse);
|
|
105
|
-
this.removeEventListener('mouseleave', this._hideMouse);
|
|
106
|
-
this._overlayInvokerNode.removeEventListener('focusin', this._showKey);
|
|
107
|
-
this._overlayInvokerNode.removeEventListener('focusout', this._hideKey);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/** @private */
|
|
111
|
-
__resetActive() {
|
|
112
|
-
this._mouseActive = false;
|
|
113
|
-
this._keyActive = false;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** @protected */
|
|
117
|
-
_showMouse() {
|
|
118
|
-
if (!this._keyActive) {
|
|
119
|
-
this._mouseActive = true;
|
|
120
|
-
if (!this._hasDisabledInvoker()) {
|
|
121
|
-
this.opened = true;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/** @protected */
|
|
127
|
-
_hideMouse() {
|
|
128
|
-
if (!this._keyActive) {
|
|
129
|
-
this.opened = false;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/** @protected */
|
|
134
|
-
_showKey() {
|
|
135
|
-
if (!this._mouseActive) {
|
|
136
|
-
this._keyActive = true;
|
|
137
|
-
if (!this._hasDisabledInvoker()) {
|
|
138
|
-
this.opened = true;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/** @protected */
|
|
144
|
-
_hideKey() {
|
|
145
|
-
if (!this._mouseActive) {
|
|
146
|
-
this.opened = false;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
62
|
}
|