@lion/ui 0.0.9 → 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/core/src/SlotMixin.js +2 -1
- package/components/dialog/src/LionDialog.js +0 -28
- package/components/dialog/test/lion-dialog.test.js +11 -10
- package/components/form-integrations/test/form-group-methods.test.js +6 -4
- package/components/form-integrations/test/form-integrations.test.js +10 -5
- package/components/form-integrations/test/helpers/umbrella-form.js +10 -0
- package/components/form-integrations/test/model-value-consistency.test.js +29 -20
- package/components/input-datepicker/src/LionInputDatepicker.js +66 -104
- package/components/input-datepicker/src/localizeNamespaceLoader.js +188 -0
- package/components/input-datepicker/test/lion-input-datepicker.test.js +15 -20
- package/components/input-datepicker/test-helpers/DatepickerInputObject.js +3 -3
- package/components/input-tel/src/parsers.js +1 -1
- package/components/listbox/test-suites/ListboxMixin.suite.js +3 -3
- package/components/localize/src/singleton.js +1 -1
- package/components/localize/test/number/formatNumber.test.js +2 -6
- package/components/overlays/src/OverlayController.js +292 -291
- package/components/overlays/src/OverlayMixin.js +17 -18
- package/components/overlays/src/OverlaysManager.js +28 -53
- 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/src/overlayDocumentStyle.js +24 -0
- package/components/overlays/src/overlayShadowDomStyle.js +138 -0
- package/components/overlays/src/singleton.js +3 -1
- package/components/overlays/test/ArrowMixin.test.js +0 -1
- package/components/overlays/test/OverlayController.test.js +372 -274
- package/components/overlays/test/OverlaysManager.test.js +23 -32
- package/components/overlays/test/global-positioning.test.js +4 -15
- package/components/overlays/test/local-positioning.test.js +65 -38
- package/components/overlays/test-suites/OverlayMixin.suite.js +18 -26
- package/components/overlays/types/OverlayConfig.ts +38 -11
- package/components/overlays/types/OverlayMixinTypes.ts +2 -2
- package/components/select-rich/src/LionSelectInvoker.js +2 -3
- package/components/select-rich/src/LionSelectRich.js +22 -20
- package/components/select-rich/test/lion-select-rich.test.js +5 -7
- package/components/tooltip/src/LionTooltip.js +3 -90
- package/components/tooltip/test/lion-tooltip.test.js +52 -20
- package/custom-elements.json +535 -503
- package/dist-types/components/core/src/SlotMixin.d.ts.map +1 -1
- 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/form-integrations/test/helpers/umbrella-form.d.ts +4 -0
- package/dist-types/components/form-integrations/test/helpers/umbrella-form.d.ts.map +1 -1
- package/dist-types/components/form-integrations/test/model-value-consistency.test.d.ts.map +1 -1
- package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts +3 -3
- package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts.map +1 -1
- package/dist-types/components/input-datepicker/src/localizeNamespaceLoader.d.ts +4 -0
- package/dist-types/components/input-datepicker/src/localizeNamespaceLoader.d.ts.map +1 -0
- package/dist-types/components/overlays/src/OverlayController.d.ts +56 -101
- 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/OverlaysManager.d.ts +0 -7
- package/dist-types/components/overlays/src/OverlaysManager.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/src/overlayDocumentStyle.d.ts +2 -0
- package/dist-types/components/overlays/src/overlayDocumentStyle.d.ts.map +1 -0
- package/dist-types/components/overlays/src/overlayShadowDomStyle.d.ts +2 -0
- package/dist-types/components/overlays/src/overlayShadowDomStyle.d.ts.map +1 -0
- package/dist-types/components/overlays/src/singleton.d.ts +2 -1
- package/dist-types/components/overlays/src/singleton.d.ts.map +1 -1
- package/dist-types/components/overlays/test-suites/OverlayMixin.suite.d.ts.map +1 -1
- package/dist-types/components/overlays/types/OverlayConfig.d.ts +19 -10
- package/dist-types/components/overlays/types/OverlayConfig.d.ts.map +1 -1
- package/dist-types/components/overlays/types/OverlayMixinTypes.d.ts +2 -2
- package/dist-types/components/overlays/types/OverlayMixinTypes.d.ts.map +1 -1
- package/dist-types/components/select-rich/src/LionSelectInvoker.d.ts +3 -27
- package/dist-types/components/select-rich/src/LionSelectInvoker.d.ts.map +1 -1
- package/dist-types/components/select-rich/src/LionSelectRich.d.ts.map +1 -1
- package/dist-types/components/select-rich/test/lion-select-rich.test.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/input-datepicker.d.ts +0 -1
- package/dist-types/exports/overlays.d.ts +1 -1
- package/dist-types/exports/types/overlays.d.ts +1 -0
- package/dist-types/exports/types/overlays.d.ts.map +1 -1
- 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 +5 -9
- package/docs/components/dialog/use-cases.md +43 -87
- 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/use-cases.md +4 -0
- package/docs/fundamentals/systems/form/validate.md +1 -1
- package/docs/fundamentals/systems/localize/overview.md +4 -4
- package/docs/fundamentals/systems/overlays/assets/{applyDemoOverlayStyles.js → applyDemoOverlayStyles.mjs} +2 -1
- package/docs/fundamentals/systems/overlays/assets/demo-el-using-overlaymixin.mjs +119 -0
- package/docs/fundamentals/systems/overlays/assets/{demo-overlay-backdrop.js → demo-overlay-backdrop.mjs} +3 -4
- package/docs/fundamentals/systems/overlays/assets/demo-overlay-positioning.mjs +537 -0
- package/docs/fundamentals/systems/overlays/assets/umbrella-form.js +9 -2
- package/docs/fundamentals/systems/overlays/configuration.md +56 -54
- package/docs/fundamentals/systems/overlays/overview.md +5 -5
- package/docs/fundamentals/systems/overlays/use-cases.md +83 -77
- 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 +6 -6
- package/docs/guides/how-to/extend-a-native-input.md +3 -3
- 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/input-datepicker.js +0 -1
- package/exports/overlays.js +1 -1
- package/exports/types/overlays.ts +1 -0
- package/package.json +2 -1
- package/components/input-datepicker/src/LionCalendarOverlayFrame.js +0 -125
- package/components/overlays/src/globalOverlaysStyle.js +0 -139
- package/dist-types/components/input-datepicker/src/LionCalendarOverlayFrame.d.ts +0 -10
- package/dist-types/components/input-datepicker/src/LionCalendarOverlayFrame.d.ts.map +0 -1
- package/dist-types/components/overlays/src/globalOverlaysStyle.d.ts +0 -2
- package/dist-types/components/overlays/src/globalOverlaysStyle.d.ts.map +0 -1
- package/dist-types/exports/define/lion-calendar-overlay-frame.d.ts +0 -2
- package/dist-types/exports/define/lion-calendar-overlay-frame.d.ts.map +0 -1
- package/docs/fundamentals/systems/overlays/assets/demo-overlay-system.js +0 -42
- package/docs/fundamentals/systems/overlays/form-integration.md +0 -65
- package/docs/fundamentals/systems/overlays/scope.md +0 -176
- package/exports/define/lion-calendar-overlay-frame.js +0 -3
|
@@ -1,89 +1,133 @@
|
|
|
1
1
|
import { EventTargetShim } from '@lion/ui/core.js';
|
|
2
|
+
import { adoptStyles } from 'lit';
|
|
2
3
|
import { overlays } from './singleton.js';
|
|
3
4
|
import { containFocus } from './utils/contain-focus.js';
|
|
5
|
+
import { overlayShadowDomStyle } from './overlayShadowDomStyle.js';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
|
-
* @typedef {import('
|
|
7
|
-
* @typedef {import('
|
|
8
|
-
* @typedef {import('@popperjs/core
|
|
9
|
-
* @typedef {import('@popperjs/core
|
|
10
|
-
* @typedef {import('@popperjs/core
|
|
8
|
+
* @typedef {import('@lion/ui/types/overlays.js').OverlayConfig} OverlayConfig
|
|
9
|
+
* @typedef {import('@lion/ui/types/overlays.js').ViewportConfig} ViewportConfig
|
|
10
|
+
* @typedef {import('@popperjs/core').createPopper} Popper
|
|
11
|
+
* @typedef {import('@popperjs/core').Options} PopperOptions
|
|
12
|
+
* @typedef {import('@popperjs/core').Placement} Placement
|
|
11
13
|
* @typedef {{ createPopper: Popper }} PopperModule
|
|
12
14
|
* @typedef {'setup'|'init'|'teardown'|'before-show'|'show'|'hide'|'add'|'remove'} OverlayPhase
|
|
13
15
|
*/
|
|
14
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
|
+
|
|
33
|
+
/**
|
|
34
|
+
* From:
|
|
35
|
+
* - wrappingDialogNodeL1: `<dialog role="none"/>`
|
|
36
|
+
* - contentWrapperNodeL2: `<div id="content-wrapper-node"/>`
|
|
37
|
+
* - contentNodeL3: `<div slot="my-content"/>`
|
|
38
|
+
* To:
|
|
39
|
+
* ```html
|
|
40
|
+
* <dialog role="none">
|
|
41
|
+
* <div id="content-wrapper-node">
|
|
42
|
+
* <!-- this was the (slot for) original content node -->
|
|
43
|
+
* <slot name="my-content"></slot>
|
|
44
|
+
* </div>
|
|
45
|
+
* </dialog>
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* `<slot name="my-content">` belonging to `<div slot="content"/>` will be wrapped with wrappingDialogNodeL1 and contentWrapperNodeL2
|
|
49
|
+
* inside shadow dom. With the help of temp markers, `<slot name="my-content">`'s original position will be respected.
|
|
50
|
+
*
|
|
51
|
+
* @param {{ wrappingDialogNodeL1:HTMLDialogElement|HTMLDivElement; contentWrapperNodeL2:Element; contentNodeL3: Element }} opts
|
|
52
|
+
*/
|
|
53
|
+
function rearrangeNodes({ wrappingDialogNodeL1, contentWrapperNodeL2, contentNodeL3 }) {
|
|
54
|
+
if (!(contentWrapperNodeL2.isConnected || contentNodeL3.isConnected)) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
'[OverlayController] Could not find a render target, since the provided contentNode is not connected to the DOM. Make sure that it is connected, e.g. by doing "document.body.appendChild(contentNode)", before passing it on.',
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let parentElement;
|
|
61
|
+
const tempMarker = document.createComment('tempMarker');
|
|
62
|
+
|
|
63
|
+
if (contentWrapperNodeL2.isConnected) {
|
|
64
|
+
// This is the case when contentWrapperNode (living in shadow dom, wrapping <slot name="my-content-outlet">) is already provided via controller.
|
|
65
|
+
parentElement = contentWrapperNodeL2.parentElement || contentWrapperNodeL2.getRootNode();
|
|
66
|
+
parentElement.insertBefore(tempMarker, contentWrapperNodeL2);
|
|
67
|
+
// Wrap...
|
|
68
|
+
wrappingDialogNodeL1.appendChild(contentWrapperNodeL2);
|
|
69
|
+
}
|
|
70
|
+
// if contentNodeL3.isConnected
|
|
71
|
+
else {
|
|
72
|
+
const contentIsProjected = contentNodeL3.assignedSlot;
|
|
73
|
+
if (contentIsProjected) {
|
|
74
|
+
parentElement =
|
|
75
|
+
contentNodeL3.assignedSlot.parentElement || contentNodeL3.assignedSlot.getRootNode();
|
|
76
|
+
parentElement.insertBefore(tempMarker, contentNodeL3.assignedSlot);
|
|
77
|
+
wrappingDialogNodeL1.appendChild(contentWrapperNodeL2);
|
|
78
|
+
// Important: we do not move around contentNodeL3, but the assigned slot
|
|
79
|
+
contentWrapperNodeL2.appendChild(contentNodeL3.assignedSlot);
|
|
80
|
+
} else {
|
|
81
|
+
parentElement = contentNodeL3.parentElement || contentNodeL3.getRootNode();
|
|
82
|
+
parentElement.insertBefore(tempMarker, contentNodeL3);
|
|
83
|
+
wrappingDialogNodeL1.appendChild(contentWrapperNodeL2);
|
|
84
|
+
contentWrapperNodeL2.appendChild(contentNodeL3);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* From:
|
|
90
|
+
* ```html
|
|
91
|
+
* #shadow-root:
|
|
92
|
+
* <div>
|
|
93
|
+
* <!-- tempMarker -->
|
|
94
|
+
* <slot name="x"/>
|
|
95
|
+
* </div>
|
|
96
|
+
* ```
|
|
97
|
+
*
|
|
98
|
+
* To:
|
|
99
|
+
* ```html
|
|
100
|
+
* #shadow-root:
|
|
101
|
+
* <div>
|
|
102
|
+
* <!-- tempMarker -->
|
|
103
|
+
* <dialog role="none">
|
|
104
|
+
* <div id="content-wrapper-node">
|
|
105
|
+
* <slot name="x"/>
|
|
106
|
+
* </div>
|
|
107
|
+
* </dialog>
|
|
108
|
+
* </div>
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
parentElement.insertBefore(wrappingDialogNodeL1, tempMarker);
|
|
112
|
+
parentElement?.removeChild(tempMarker);
|
|
113
|
+
}
|
|
114
|
+
|
|
15
115
|
/**
|
|
16
116
|
* @returns {Promise<PopperModule>}
|
|
17
117
|
*/
|
|
18
118
|
async function preloadPopper() {
|
|
19
|
-
|
|
20
|
-
// @ts-ignore
|
|
119
|
+
// @ts-ignore [external]: import complains about untyped module, but we typecast it ourselves
|
|
21
120
|
return /** @type {* & Promise<PopperModule>} */ (import('@popperjs/core/dist/esm/popper.js'));
|
|
22
|
-
/* eslint-enable import/no-extraneous-dependencies */
|
|
23
121
|
}
|
|
24
122
|
|
|
25
|
-
const GLOBAL_OVERLAYS_CONTAINER_CLASS = 'global-overlays__overlay-container';
|
|
26
|
-
const GLOBAL_OVERLAYS_CLASS = 'global-overlays__overlay';
|
|
27
123
|
// @ts-expect-error [external]: CSS not yet typed
|
|
28
124
|
const supportsCSSTypedObject = window.CSS?.number && document.body.attributeStyleMap?.set;
|
|
29
125
|
|
|
30
126
|
/**
|
|
31
|
-
*
|
|
127
|
+
* OverlayController is the fundament for every single type of overlay. With the right
|
|
32
128
|
* configuration, it can be used to build (modal) dialogs, tooltips, dropdowns, popovers,
|
|
33
129
|
* bottom/top/left/right sheets etc.
|
|
34
130
|
*
|
|
35
|
-
* ### About contentNode, contentWrapperNode and renderTarget.
|
|
36
|
-
*
|
|
37
|
-
* #### contentNode
|
|
38
|
-
* Node containing actual overlay contents.
|
|
39
|
-
* It will not be touched by the OverlayController, it will only set attributes needed
|
|
40
|
-
* for accessibility.
|
|
41
|
-
*
|
|
42
|
-
* #### contentWrapperNode
|
|
43
|
-
* The 'positioning' element.
|
|
44
|
-
* For local overlays, this node will be provided to Popper and all
|
|
45
|
-
* inline positioning styles will be added here. It will also act as the container of an arrow
|
|
46
|
-
* element (the arrow needs to be a sibling of contentNode for Popper to work correctly).
|
|
47
|
-
* When projecting a contentNode from a shadowRoot, it is essential to have the wrapper in
|
|
48
|
-
* shadow dom, so that contentNode can be styled via `::slotted` from the shadow root.
|
|
49
|
-
* The Popper arrow can then be styled from that same shadow root as well.
|
|
50
|
-
* For global overlays, the contentWrapperNode will be appended to the globalRootNode structure.
|
|
51
|
-
*
|
|
52
|
-
* #### renderTarget
|
|
53
|
-
* Usually the parent node of contentWrapperNode that either exists locally or globally.
|
|
54
|
-
* When a responsive scenario is created (in which we switch from global to local or vice versa)
|
|
55
|
-
* we need to know where we should reappend contentWrapperNode (or contentNode in case it's projected)
|
|
56
|
-
*
|
|
57
|
-
* So a regular flow can be summarized as follows:
|
|
58
|
-
* 1. Application Developer spawns an OverlayController with a contentNode reference
|
|
59
|
-
* 2. OverlayController will create a contentWrapperNode around contentNode (or consumes when provided)
|
|
60
|
-
* 3. contentWrapperNode will be appended to the right renderTarget
|
|
61
|
-
*
|
|
62
|
-
* There are subtle differences depending on the following factors:
|
|
63
|
-
* - whether in global/local placement mode
|
|
64
|
-
* - whether contentNode projected
|
|
65
|
-
* - whether an arrow is provided
|
|
66
|
-
*
|
|
67
|
-
* This leads to the following possible combinations:
|
|
68
|
-
* - [l1]. local + no content projection + no arrow
|
|
69
|
-
* - [l2]. local + content projection + no arrow
|
|
70
|
-
* - [l3]. local + no content projection + arrow
|
|
71
|
-
* - [l4]. local + content projection + arrow
|
|
72
|
-
* - [g1]. global
|
|
73
|
-
*
|
|
74
|
-
* #### html structure for a content projected node
|
|
75
|
-
* <div id="contentWrapperNode">
|
|
76
|
-
* <slot name="contentNode"></slot>
|
|
77
|
-
* <div x-arrow></div>
|
|
78
|
-
* </div>
|
|
79
|
-
*
|
|
80
|
-
* Structure above depicts [l4]
|
|
81
|
-
* So in case of [l1] and [l3], the <slot> element would be a regular element
|
|
82
|
-
* In case of [l1] and [l2], there would be no arrow.
|
|
83
|
-
* Note that a contentWrapperNode should be provided for [l2], [l3] and [l4]
|
|
84
|
-
* In case of a global overlay ([g1]), it's enough to provide just the contentNode.
|
|
85
|
-
* In case of a local overlay or a responsive overlay switching from placementMode, one should
|
|
86
|
-
* always configure as if it were a local overlay.
|
|
87
131
|
*/
|
|
88
132
|
export class OverlayController extends EventTargetShim {
|
|
89
133
|
/**
|
|
@@ -97,6 +141,9 @@ export class OverlayController extends EventTargetShim {
|
|
|
97
141
|
/** @private */
|
|
98
142
|
this.__sharedConfig = config;
|
|
99
143
|
|
|
144
|
+
/** @type {OverlayConfig} */
|
|
145
|
+
this.config = {};
|
|
146
|
+
|
|
100
147
|
/**
|
|
101
148
|
* @type {OverlayConfig}
|
|
102
149
|
* @protected
|
|
@@ -119,11 +166,11 @@ export class OverlayController extends EventTargetShim {
|
|
|
119
166
|
hidesOnOutsideClick: false,
|
|
120
167
|
isTooltip: false,
|
|
121
168
|
invokerRelation: 'description',
|
|
122
|
-
|
|
169
|
+
visibilityTriggerFunction: undefined,
|
|
123
170
|
handlesAccessibility: false,
|
|
124
171
|
popperConfig: {
|
|
125
172
|
placement: 'top',
|
|
126
|
-
strategy: '
|
|
173
|
+
strategy: 'fixed',
|
|
127
174
|
modifiers: [
|
|
128
175
|
{
|
|
129
176
|
name: 'preventOverflow',
|
|
@@ -156,6 +203,7 @@ export class OverlayController extends EventTargetShim {
|
|
|
156
203
|
viewportConfig: {
|
|
157
204
|
placement: 'center',
|
|
158
205
|
},
|
|
206
|
+
zIndex: 9999,
|
|
159
207
|
};
|
|
160
208
|
|
|
161
209
|
this.manager.add(this);
|
|
@@ -163,25 +211,11 @@ export class OverlayController extends EventTargetShim {
|
|
|
163
211
|
this._contentId = `overlay-content--${Math.random().toString(36).substr(2, 10)}`;
|
|
164
212
|
/** @private */
|
|
165
213
|
this.__originalAttrs = new Map();
|
|
166
|
-
if (this._defaultConfig.contentNode) {
|
|
167
|
-
if (!this._defaultConfig.contentNode.isConnected) {
|
|
168
|
-
throw new Error(
|
|
169
|
-
'[OverlayController] Could not find a render target, since the provided contentNode is not connected to the DOM. Make sure that it is connected, e.g. by doing "document.body.appendChild(contentNode)", before passing it on.',
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
this.__isContentNodeProjected = Boolean(this._defaultConfig.contentNode.assignedSlot);
|
|
173
|
-
}
|
|
174
214
|
this.updateConfig(config);
|
|
175
215
|
/** @private */
|
|
176
216
|
this.__hasActiveTrapsKeyboardFocus = false;
|
|
177
217
|
/** @private */
|
|
178
218
|
this.__hasActiveBackdrop = true;
|
|
179
|
-
/**
|
|
180
|
-
* @type {HTMLElement | undefined}
|
|
181
|
-
* @private
|
|
182
|
-
*/
|
|
183
|
-
this.__backdropNodeToBeTornDown = undefined;
|
|
184
|
-
|
|
185
219
|
/** @private */
|
|
186
220
|
this.__escKeyHandler = this.__escKeyHandler.bind(this);
|
|
187
221
|
}
|
|
@@ -196,10 +230,10 @@ export class OverlayController extends EventTargetShim {
|
|
|
196
230
|
|
|
197
231
|
/**
|
|
198
232
|
* The contentWrapperNode
|
|
199
|
-
* @type {
|
|
233
|
+
* @type {HTMLDialogElement | HTMLDivElement}
|
|
200
234
|
*/
|
|
201
235
|
get content() {
|
|
202
|
-
return /** @type {
|
|
236
|
+
return /** @type {HTMLDialogElement | HTMLDivElement} */ (this.__wrappingDialogNode);
|
|
203
237
|
}
|
|
204
238
|
|
|
205
239
|
/**
|
|
@@ -385,26 +419,8 @@ export class OverlayController extends EventTargetShim {
|
|
|
385
419
|
return /** @type {ViewportConfig} */ (this.config?.viewportConfig);
|
|
386
420
|
}
|
|
387
421
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
* When a responsive scenario is created (in which we switch from global to local or vice versa)
|
|
391
|
-
* we need to know where we should reappend contentWrapperNode (or contentNode in case it's
|
|
392
|
-
* projected).
|
|
393
|
-
* @type {HTMLElement}
|
|
394
|
-
* @protected
|
|
395
|
-
*/
|
|
396
|
-
get _renderTarget() {
|
|
397
|
-
/** config [g1] */
|
|
398
|
-
if (this.placementMode === 'global') {
|
|
399
|
-
return this.manager.globalRootNode;
|
|
400
|
-
}
|
|
401
|
-
/** config [l2] or [l4] */
|
|
402
|
-
if (this.__isContentNodeProjected) {
|
|
403
|
-
// @ts-expect-error [external]: fix Node types
|
|
404
|
-
return this.__originalContentParent?.getRootNode().host;
|
|
405
|
-
}
|
|
406
|
-
/** config [l1] or [l3] */
|
|
407
|
-
return /** @type {HTMLElement} */ (this.__originalContentParent);
|
|
422
|
+
get visibilityTriggerFunction() {
|
|
423
|
+
return /** @type {function} */ (this.config?.visibilityTriggerFunction);
|
|
408
424
|
}
|
|
409
425
|
|
|
410
426
|
/**
|
|
@@ -420,12 +436,8 @@ export class OverlayController extends EventTargetShim {
|
|
|
420
436
|
* @param {number} value
|
|
421
437
|
*/
|
|
422
438
|
set elevation(value) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
}
|
|
426
|
-
if (this.backdropNode) {
|
|
427
|
-
this.backdropNode.style.zIndex = `${value}`;
|
|
428
|
-
}
|
|
439
|
+
// @ts-expect-error find out why config would/could be undfined
|
|
440
|
+
this.__wrappingDialogNode.style.zIndex = `${this.config.zIndex + value}`;
|
|
429
441
|
}
|
|
430
442
|
|
|
431
443
|
/**
|
|
@@ -450,7 +462,7 @@ export class OverlayController extends EventTargetShim {
|
|
|
450
462
|
* @type {OverlayConfig}
|
|
451
463
|
* @private
|
|
452
464
|
*/
|
|
453
|
-
this.__prevConfig = this.config
|
|
465
|
+
this.__prevConfig = this.config;
|
|
454
466
|
|
|
455
467
|
/** @type {OverlayConfig} */
|
|
456
468
|
this.config = {
|
|
@@ -462,19 +474,17 @@ export class OverlayController extends EventTargetShim {
|
|
|
462
474
|
...(this.__sharedConfig.popperConfig || {}),
|
|
463
475
|
...(cfgToAdd.popperConfig || {}),
|
|
464
476
|
modifiers: [
|
|
465
|
-
...(
|
|
466
|
-
|
|
467
|
-
...(
|
|
468
|
-
[]),
|
|
469
|
-
...((cfgToAdd.popperConfig && cfgToAdd.popperConfig.modifiers) || []),
|
|
477
|
+
...(this._defaultConfig.popperConfig?.modifiers || []),
|
|
478
|
+
...(this.__sharedConfig.popperConfig?.modifiers || []),
|
|
479
|
+
...(cfgToAdd.popperConfig?.modifiers || []),
|
|
470
480
|
],
|
|
471
481
|
},
|
|
472
482
|
};
|
|
473
483
|
|
|
474
484
|
/** @private */
|
|
475
|
-
this.__validateConfiguration(
|
|
485
|
+
this.__validateConfiguration(this.config);
|
|
476
486
|
/** @protected */
|
|
477
|
-
this._init(
|
|
487
|
+
this._init();
|
|
478
488
|
/** @private */
|
|
479
489
|
this.__elementToFocusAfterHide = undefined;
|
|
480
490
|
}
|
|
@@ -498,16 +508,6 @@ export class OverlayController extends EventTargetShim {
|
|
|
498
508
|
if (!newConfig.contentNode) {
|
|
499
509
|
throw new Error('[OverlayController] You need to provide a .contentNode');
|
|
500
510
|
}
|
|
501
|
-
if (this.__isContentNodeProjected && !newConfig.contentWrapperNode) {
|
|
502
|
-
throw new Error(
|
|
503
|
-
'[OverlayController] You need to provide a .contentWrapperNode when .contentNode is projected',
|
|
504
|
-
);
|
|
505
|
-
}
|
|
506
|
-
if (newConfig.isTooltip && newConfig.placementMode !== 'local') {
|
|
507
|
-
throw new Error(
|
|
508
|
-
'[OverlayController] .isTooltip should be configured with .placementMode "local"',
|
|
509
|
-
);
|
|
510
|
-
}
|
|
511
511
|
if (newConfig.isTooltip && !newConfig.handlesAccessibility) {
|
|
512
512
|
throw new Error(
|
|
513
513
|
'[OverlayController] .isTooltip only takes effect when .handlesAccessibility is enabled',
|
|
@@ -519,85 +519,103 @@ export class OverlayController extends EventTargetShim {
|
|
|
519
519
|
}
|
|
520
520
|
|
|
521
521
|
/**
|
|
522
|
-
* @param {{ cfgToAdd: OverlayConfig }} options
|
|
523
522
|
* @protected
|
|
524
523
|
*/
|
|
525
|
-
_init(
|
|
526
|
-
this.
|
|
527
|
-
|
|
524
|
+
_init() {
|
|
525
|
+
if (!this.__contentHasBeenInitialized) {
|
|
526
|
+
this.__initContentDomStructure();
|
|
527
|
+
this.__contentHasBeenInitialized = true;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Reset all positioning styles (local, c.q. Popper) and classes (global)
|
|
531
|
+
this.contentWrapperNode.removeAttribute('style');
|
|
532
|
+
this.contentWrapperNode.removeAttribute('class');
|
|
528
533
|
|
|
529
534
|
if (this.placementMode === 'local') {
|
|
530
|
-
// Lazily load Popper
|
|
535
|
+
// Lazily load Popper as soon as the first local overlay is used...
|
|
531
536
|
if (!OverlayController.popperModule) {
|
|
532
|
-
// a@ts-expect-error FIXME: for some reason createPopper is missing here
|
|
533
537
|
OverlayController.popperModule = preloadPopper();
|
|
534
538
|
}
|
|
535
539
|
}
|
|
540
|
+
this.__initOverlayStyles();
|
|
536
541
|
this._handleFeatures({ phase: 'init' });
|
|
537
542
|
}
|
|
538
543
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
(this.contentWrapperNode).appendChild(this.contentNode);
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
if (!this._renderTarget) {
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
if (this.__isContentNodeProjected && this.placementMode === 'local') {
|
|
554
|
-
// We add the contentNode in its slot, so that it will be projected by contentWrapperNode
|
|
555
|
-
this._renderTarget.appendChild(this.contentNode);
|
|
556
|
-
} else {
|
|
557
|
-
const isInsideRenderTarget = this._renderTarget === this.contentWrapperNode.parentNode;
|
|
558
|
-
const nodeContainsTarget = this.contentWrapperNode.contains(this._renderTarget);
|
|
559
|
-
if (!isInsideRenderTarget && !nodeContainsTarget) {
|
|
560
|
-
// contentWrapperNode becomes the direct (non projected) parent of contentNode
|
|
561
|
-
this._renderTarget.appendChild(this.contentWrapperNode);
|
|
562
|
-
}
|
|
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);
|
|
563
550
|
}
|
|
564
551
|
}
|
|
565
552
|
|
|
566
553
|
/**
|
|
567
|
-
*
|
|
568
|
-
*
|
|
569
|
-
*
|
|
554
|
+
* Here we arrange our content node via:
|
|
555
|
+
* 1. HTMLDialogElement: the content will always be painted to the browser's top layer
|
|
556
|
+
* - no matter what context the contentNode lives in, the overlay will be painted correctly via the <dialog> element,
|
|
557
|
+
* even if 'overflow:hidden' or a css transform is applied in its parent hierarchy.
|
|
558
|
+
* - the dialog element will be unstyled, but will span the whole screen
|
|
559
|
+
* - a backdrop element will be a child of the dialog element, so it leverages the capabilities of the parent
|
|
560
|
+
* (filling the whole screen if wanted an always painted to top layer)
|
|
561
|
+
* 2. ContentWrapper: the content receives the right positioning styles in a clean/non conflicting way:
|
|
562
|
+
* - local positioning: receive inline (position) styling that can never conflict with the already existing computed styles
|
|
563
|
+
* - global positioning: receive flex (child) classes that position the content correctly relative to the viewport
|
|
564
|
+
*
|
|
565
|
+
* The resulting structure that will be created looks like this:
|
|
566
|
+
*
|
|
567
|
+
* ...
|
|
568
|
+
* <dialog role="none">
|
|
569
|
+
* <div id="optional-backdrop"></div>
|
|
570
|
+
* <div id="content-wrapper-node">
|
|
571
|
+
* <!-- this was the (slot for) original content node -->
|
|
572
|
+
* <slot name="content"></slot>
|
|
573
|
+
* </div>
|
|
574
|
+
* </dialog>
|
|
575
|
+
* ...
|
|
576
|
+
*
|
|
570
577
|
* @private
|
|
571
578
|
*/
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
579
|
+
__initContentDomStructure() {
|
|
580
|
+
const wrappingDialogElement = document.createElement(
|
|
581
|
+
this.config?._noDialogEl ? 'div' : 'dialog',
|
|
582
|
+
);
|
|
583
|
+
// We use a dialog for its visual capabilities: it renders to the top layer.
|
|
584
|
+
// A11y will depend on the type of overlay and is arranged on contentNode level.
|
|
585
|
+
// Also see: https://www.scottohara.me/blog/2019/03/05/open-dialog.html
|
|
586
|
+
wrappingDialogElement.setAttribute('role', 'none');
|
|
587
|
+
wrappingDialogElement.setAttribute('data-overlay-outer-wrapper', '');
|
|
588
|
+
// N.B. position: fixed is needed to escape out of 'overflow: hidden'
|
|
589
|
+
// We give a high z-index for non-modal dialogs, so that we at least win from all siblings of our
|
|
590
|
+
// parent stacking context
|
|
591
|
+
wrappingDialogElement.style.cssText = `display:none; z-index: ${this.config.zIndex};`;
|
|
592
|
+
this.__wrappingDialogNode = wrappingDialogElement;
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Based on the configuration of the developer, multiple scenarios are accounted for
|
|
596
|
+
* A. We already have a contentWrapperNode ()
|
|
597
|
+
*/
|
|
598
|
+
if (!this.config?.contentWrapperNode) {
|
|
578
599
|
this.__contentWrapperNode = document.createElement('div');
|
|
579
600
|
}
|
|
601
|
+
this.contentWrapperNode.setAttribute('data-id', 'content-wrapper');
|
|
602
|
+
|
|
603
|
+
rearrangeNodes({
|
|
604
|
+
wrappingDialogNodeL1: wrappingDialogElement,
|
|
605
|
+
contentWrapperNodeL2: this.contentWrapperNode,
|
|
606
|
+
contentNodeL3: this.contentNode,
|
|
607
|
+
});
|
|
608
|
+
// @ts-ignore
|
|
609
|
+
wrappingDialogElement.open = true;
|
|
580
610
|
|
|
581
|
-
this.
|
|
582
|
-
this.contentWrapperNode.style.
|
|
611
|
+
this.__wrappingDialogNode.style.display = 'none';
|
|
612
|
+
this.contentWrapperNode.style.zIndex = '1';
|
|
583
613
|
|
|
584
614
|
if (getComputedStyle(this.contentNode).position === 'absolute') {
|
|
585
615
|
// Having a _contWrapperNode and a contentNode with 'position:absolute' results in
|
|
586
616
|
// computed height of 0...
|
|
587
617
|
this.contentNode.style.position = 'static';
|
|
588
618
|
}
|
|
589
|
-
|
|
590
|
-
if (this.__isContentNodeProjected && this.contentWrapperNode.isConnected) {
|
|
591
|
-
// We need to keep track of the original local context.
|
|
592
|
-
/** config [l2], [l4] */
|
|
593
|
-
this.__originalContentParent = /** @type {HTMLElement} */ (
|
|
594
|
-
this.contentWrapperNode.parentNode
|
|
595
|
-
);
|
|
596
|
-
} else if (cfgToAdd.contentNode && cfgToAdd.contentNode.isConnected) {
|
|
597
|
-
// We need to keep track of the original local context.
|
|
598
|
-
/** config [l1], [l3], [g1] */
|
|
599
|
-
this.__originalContentParent = /** @type {HTMLElement} */ (this.contentNode?.parentNode);
|
|
600
|
-
}
|
|
601
619
|
}
|
|
602
620
|
|
|
603
621
|
/**
|
|
@@ -613,7 +631,7 @@ export class OverlayController extends EventTargetShim {
|
|
|
613
631
|
if (phase === 'setup') {
|
|
614
632
|
const zIndexNumber = Number(getComputedStyle(this.contentNode).zIndex);
|
|
615
633
|
if (zIndexNumber < 1 || Number.isNaN(zIndexNumber)) {
|
|
616
|
-
this.
|
|
634
|
+
this.contentNode.style.zIndex = '1';
|
|
617
635
|
}
|
|
618
636
|
}
|
|
619
637
|
}
|
|
@@ -686,7 +704,7 @@ export class OverlayController extends EventTargetShim {
|
|
|
686
704
|
}
|
|
687
705
|
|
|
688
706
|
get isShown() {
|
|
689
|
-
return Boolean(this.
|
|
707
|
+
return Boolean(this.__wrappingDialogNode?.style.display !== 'none');
|
|
690
708
|
}
|
|
691
709
|
|
|
692
710
|
/**
|
|
@@ -717,7 +735,11 @@ export class OverlayController extends EventTargetShim {
|
|
|
717
735
|
const event = new CustomEvent('before-show', { cancelable: true });
|
|
718
736
|
this.dispatchEvent(event);
|
|
719
737
|
if (!event.defaultPrevented) {
|
|
720
|
-
this.
|
|
738
|
+
if (this.__wrappingDialogNode instanceof HTMLDialogElement) {
|
|
739
|
+
this.__wrappingDialogNode.open = true;
|
|
740
|
+
}
|
|
741
|
+
// @ts-ignore
|
|
742
|
+
this.__wrappingDialogNode.style.display = '';
|
|
721
743
|
this._keepBodySize({ phase: 'before-show' });
|
|
722
744
|
await this._handleFeatures({ phase: 'show' });
|
|
723
745
|
this._keepBodySize({ phase: 'show' });
|
|
@@ -739,11 +761,17 @@ export class OverlayController extends EventTargetShim {
|
|
|
739
761
|
*/
|
|
740
762
|
async _handlePosition({ phase }) {
|
|
741
763
|
if (this.placementMode === 'global') {
|
|
742
|
-
const
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
764
|
+
const placementClass = `overlays__overlay-container--${this.viewportConfig.placement}`;
|
|
765
|
+
|
|
766
|
+
if (phase === 'show') {
|
|
767
|
+
this.contentWrapperNode.classList.add('overlays__overlay-container');
|
|
768
|
+
this.contentWrapperNode.classList.add(placementClass);
|
|
769
|
+
this.contentNode.classList.add('overlays__overlay');
|
|
770
|
+
} else if (phase === 'hide') {
|
|
771
|
+
this.contentWrapperNode.classList.remove('overlays__overlay-container');
|
|
772
|
+
this.contentWrapperNode.classList.remove(placementClass);
|
|
773
|
+
this.contentNode.classList.remove('overlays__overlay');
|
|
774
|
+
}
|
|
747
775
|
} else if (this.placementMode === 'local' && phase === 'show') {
|
|
748
776
|
/**
|
|
749
777
|
* Popper is weird about properly positioning the popper element when it is recreated so
|
|
@@ -753,7 +781,7 @@ export class OverlayController extends EventTargetShim {
|
|
|
753
781
|
* This is however necessary for initial placement.
|
|
754
782
|
*/
|
|
755
783
|
await this.__createPopperInstance();
|
|
756
|
-
|
|
784
|
+
this._popper.forceUpdate();
|
|
757
785
|
}
|
|
758
786
|
}
|
|
759
787
|
|
|
@@ -829,7 +857,11 @@ export class OverlayController extends EventTargetShim {
|
|
|
829
857
|
contentNode: this.contentNode,
|
|
830
858
|
});
|
|
831
859
|
|
|
832
|
-
this.
|
|
860
|
+
if (this.__wrappingDialogNode instanceof HTMLDialogElement) {
|
|
861
|
+
this.__wrappingDialogNode.close();
|
|
862
|
+
}
|
|
863
|
+
// @ts-ignore
|
|
864
|
+
this.__wrappingDialogNode.style.display = 'none';
|
|
833
865
|
this._handleFeatures({ phase: 'hide' });
|
|
834
866
|
this._keepBodySize({ phase: 'hide' });
|
|
835
867
|
this.dispatchEvent(new Event('hide'));
|
|
@@ -843,6 +875,7 @@ export class OverlayController extends EventTargetShim {
|
|
|
843
875
|
*
|
|
844
876
|
* @param {{backdropNode:HTMLElement, contentNode:HTMLElement}} hideConfig
|
|
845
877
|
*/
|
|
878
|
+
// @ts-ignore
|
|
846
879
|
// eslint-disable-next-line class-methods-use-this, no-empty-function, no-unused-vars
|
|
847
880
|
async transitionHide(hideConfig) {}
|
|
848
881
|
|
|
@@ -851,48 +884,27 @@ export class OverlayController extends EventTargetShim {
|
|
|
851
884
|
* @protected
|
|
852
885
|
*/
|
|
853
886
|
// eslint-disable-next-line class-methods-use-this, no-empty-function, no-unused-vars
|
|
854
|
-
async _transitionHide(
|
|
887
|
+
async _transitionHide({ backdropNode, contentNode }) {
|
|
855
888
|
// `this.transitionHide` is a hook for our users
|
|
856
|
-
await this.transitionHide({ backdropNode
|
|
857
|
-
|
|
858
|
-
if (
|
|
859
|
-
|
|
860
|
-
`${this.placementMode}-overlays__backdrop--animation-in`,
|
|
861
|
-
);
|
|
862
|
-
/** @type {() => void} */
|
|
863
|
-
let afterFadeOut = () => {};
|
|
864
|
-
hideConfig.backdropNode.classList.add(
|
|
865
|
-
`${this.placementMode}-overlays__backdrop--animation-out`,
|
|
866
|
-
);
|
|
867
|
-
this.__backdropAnimation = new Promise(resolve => {
|
|
868
|
-
afterFadeOut = () => {
|
|
869
|
-
if (hideConfig.backdropNode) {
|
|
870
|
-
hideConfig.backdropNode.classList.remove(
|
|
871
|
-
`${this.placementMode}-overlays__backdrop--animation-out`,
|
|
872
|
-
);
|
|
873
|
-
hideConfig.backdropNode.classList.remove(
|
|
874
|
-
`${this.placementMode}-overlays__backdrop--visible`,
|
|
875
|
-
);
|
|
876
|
-
hideConfig.backdropNode.removeEventListener('animationend', afterFadeOut);
|
|
877
|
-
}
|
|
878
|
-
resolve(undefined);
|
|
879
|
-
};
|
|
880
|
-
});
|
|
881
|
-
|
|
882
|
-
hideConfig.backdropNode.addEventListener('animationend', afterFadeOut);
|
|
889
|
+
await this.transitionHide({ backdropNode, contentNode });
|
|
890
|
+
this._handlePosition({ phase: 'hide' });
|
|
891
|
+
if (!backdropNode) {
|
|
892
|
+
return;
|
|
883
893
|
}
|
|
894
|
+
backdropNode.classList.remove(`overlays__backdrop--animation-in`);
|
|
884
895
|
}
|
|
885
896
|
|
|
886
897
|
/**
|
|
887
898
|
* To be overridden by subclassers
|
|
888
899
|
*
|
|
889
|
-
* @param {{backdropNode:HTMLElement
|
|
900
|
+
* @param {{backdropNode:HTMLElement; contentNode:HTMLElement}} showConfig
|
|
890
901
|
*/
|
|
902
|
+
// @ts-ignore
|
|
891
903
|
// eslint-disable-next-line class-methods-use-this, no-empty-function, no-unused-vars
|
|
892
904
|
async transitionShow(showConfig) {}
|
|
893
905
|
|
|
894
906
|
/**
|
|
895
|
-
* @param {{backdropNode:HTMLElement
|
|
907
|
+
* @param {{backdropNode:HTMLElement; contentNode:HTMLElement}} showConfig
|
|
896
908
|
*/
|
|
897
909
|
// eslint-disable-next-line class-methods-use-this, no-empty-function, no-unused-vars
|
|
898
910
|
async _transitionShow(showConfig) {
|
|
@@ -900,28 +912,25 @@ export class OverlayController extends EventTargetShim {
|
|
|
900
912
|
await this.transitionShow({ backdropNode: this.backdropNode, contentNode: this.contentNode });
|
|
901
913
|
|
|
902
914
|
if (showConfig.backdropNode) {
|
|
903
|
-
showConfig.backdropNode.classList.add(
|
|
904
|
-
`${this.placementMode}-overlays__backdrop--animation-in`,
|
|
905
|
-
);
|
|
915
|
+
showConfig.backdropNode.classList.add(`overlays__backdrop--animation-in`);
|
|
906
916
|
}
|
|
907
917
|
}
|
|
908
918
|
|
|
909
919
|
/** @protected */
|
|
910
920
|
_restoreFocus() {
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
if (
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
}
|
|
921
|
+
// We only are allowed to move focus if we (still) 'own' the active element.
|
|
922
|
+
// Otherwise we assume the 'outside world' has purposefully taken over
|
|
923
|
+
const { activeElement } = /** @type {ShadowRoot} */ (this.contentNode.getRootNode());
|
|
924
|
+
const weStillOwnActiveElement =
|
|
925
|
+
activeElement instanceof HTMLElement && this.contentNode.contains(activeElement);
|
|
926
|
+
if (!weStillOwnActiveElement) {
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
if (this.elementToFocusAfterHide) {
|
|
931
|
+
this.elementToFocusAfterHide.focus();
|
|
932
|
+
} else {
|
|
933
|
+
activeElement.blur();
|
|
925
934
|
}
|
|
926
935
|
}
|
|
927
936
|
|
|
@@ -964,6 +973,24 @@ export class OverlayController extends EventTargetShim {
|
|
|
964
973
|
if (this.inheritsReferenceWidth) {
|
|
965
974
|
this._handleInheritsReferenceWidth();
|
|
966
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
|
+
}
|
|
967
994
|
}
|
|
968
995
|
|
|
969
996
|
/**
|
|
@@ -1010,55 +1037,28 @@ export class OverlayController extends EventTargetShim {
|
|
|
1010
1037
|
* @protected
|
|
1011
1038
|
*/
|
|
1012
1039
|
_handleBackdrop({ phase }) {
|
|
1040
|
+
// eslint-disable-next-line default-case
|
|
1013
1041
|
switch (phase) {
|
|
1014
1042
|
case 'init': {
|
|
1015
|
-
if (!this.
|
|
1016
|
-
this.
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
let insertionBefore = this.contentNode;
|
|
1025
|
-
if (this.placementMode === 'global') {
|
|
1026
|
-
insertionAnchor = /** @type {HTMLElement} */ (this.contentWrapperNode.parentElement);
|
|
1027
|
-
insertionBefore = this.contentWrapperNode;
|
|
1043
|
+
if (!this.__backdropInitialized) {
|
|
1044
|
+
if (!this.config?.backdropNode) {
|
|
1045
|
+
this.__backdropNode = document.createElement('div');
|
|
1046
|
+
// If backdropNode existed in config, styles are applied by implementing party
|
|
1047
|
+
this.__backdropNode.classList.add(`overlays__backdrop`);
|
|
1048
|
+
}
|
|
1049
|
+
// @ts-ignore
|
|
1050
|
+
this.__wrappingDialogNode.prepend(this.backdropNode);
|
|
1051
|
+
this.__backdropInitialized = true;
|
|
1028
1052
|
}
|
|
1029
|
-
insertionAnchor.insertBefore(this.backdropNode, insertionBefore);
|
|
1030
1053
|
break;
|
|
1031
1054
|
}
|
|
1032
1055
|
case 'show':
|
|
1033
|
-
this.backdropNode.classList.add(
|
|
1056
|
+
this.backdropNode.classList.add(`overlays__backdrop--visible`);
|
|
1034
1057
|
this.__hasActiveBackdrop = true;
|
|
1035
1058
|
break;
|
|
1036
1059
|
case 'hide':
|
|
1037
|
-
if (!this.backdropNode) {
|
|
1038
|
-
return;
|
|
1039
|
-
}
|
|
1040
1060
|
this.__hasActiveBackdrop = false;
|
|
1041
1061
|
break;
|
|
1042
|
-
case 'teardown':
|
|
1043
|
-
if (!this.backdropNode || !this.backdropNode.parentNode) {
|
|
1044
|
-
return;
|
|
1045
|
-
}
|
|
1046
|
-
if (this.__backdropAnimation) {
|
|
1047
|
-
this.__backdropNodeToBeTornDown = this.backdropNode;
|
|
1048
|
-
|
|
1049
|
-
this.__backdropAnimation.then(() => {
|
|
1050
|
-
if (this.__backdropNodeToBeTornDown && this.__backdropNodeToBeTornDown.parentNode) {
|
|
1051
|
-
this.__backdropNodeToBeTornDown.parentNode.removeChild(
|
|
1052
|
-
this.__backdropNodeToBeTornDown,
|
|
1053
|
-
);
|
|
1054
|
-
}
|
|
1055
|
-
});
|
|
1056
|
-
} else {
|
|
1057
|
-
this.backdropNode.parentNode.removeChild(this.backdropNode);
|
|
1058
|
-
}
|
|
1059
|
-
this.__backdropNode = undefined;
|
|
1060
|
-
break;
|
|
1061
|
-
/* no default */
|
|
1062
1062
|
}
|
|
1063
1063
|
}
|
|
1064
1064
|
|
|
@@ -1072,7 +1072,16 @@ export class OverlayController extends EventTargetShim {
|
|
|
1072
1072
|
*/
|
|
1073
1073
|
_handleTrapsKeyboardFocus({ phase }) {
|
|
1074
1074
|
if (phase === 'show') {
|
|
1075
|
+
// @ts-ignore
|
|
1076
|
+
if ('showModal' in this.__wrappingDialogNode) {
|
|
1077
|
+
// @ts-ignore
|
|
1078
|
+
this.__wrappingDialogNode.close();
|
|
1079
|
+
// @ts-ignore
|
|
1080
|
+
this.__wrappingDialogNode.showModal();
|
|
1081
|
+
}
|
|
1082
|
+
// else {
|
|
1075
1083
|
this.enableTrapsKeyboardFocus();
|
|
1084
|
+
// }
|
|
1076
1085
|
} else if (phase === 'hide' || phase === 'teardown') {
|
|
1077
1086
|
this.disableTrapsKeyboardFocus();
|
|
1078
1087
|
}
|
|
@@ -1271,24 +1280,6 @@ export class OverlayController extends EventTargetShim {
|
|
|
1271
1280
|
|
|
1272
1281
|
teardown() {
|
|
1273
1282
|
this._handleFeatures({ phase: 'teardown' });
|
|
1274
|
-
|
|
1275
|
-
if (this.placementMode === 'global' && this.__isContentNodeProjected) {
|
|
1276
|
-
/** @type {HTMLElement} */ (this.__originalContentParent).appendChild(this.contentNode);
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
// Remove the content node wrapper from the global rootnode
|
|
1280
|
-
this._teardownContentWrapperNode();
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
/** @protected */
|
|
1284
|
-
_teardownContentWrapperNode() {
|
|
1285
|
-
if (
|
|
1286
|
-
this.placementMode === 'global' &&
|
|
1287
|
-
this.contentWrapperNode &&
|
|
1288
|
-
this.contentWrapperNode.parentNode
|
|
1289
|
-
) {
|
|
1290
|
-
this.contentWrapperNode.parentNode.removeChild(this.contentWrapperNode);
|
|
1291
|
-
}
|
|
1292
1283
|
}
|
|
1293
1284
|
|
|
1294
1285
|
/** @private */
|
|
@@ -1305,6 +1296,16 @@ export class OverlayController extends EventTargetShim {
|
|
|
1305
1296
|
});
|
|
1306
1297
|
}
|
|
1307
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
|
+
}
|
|
1308
1309
|
}
|
|
1309
1310
|
/** @type {Promise<PopperModule> | undefined} */
|
|
1310
1311
|
OverlayController.popperModule = undefined;
|