@lion/ui 0.16.1 → 0.17.0-menu-system.1
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/collapsible.js +1 -0
- package/components/button/src/LionButton.js +5 -0
- package/components/button/test-suites/LionButton.suite.js +1 -0
- package/components/calendar/src/LionCalendar.js +2 -0
- package/components/collapsible/src/DisclosureMixin.js +313 -0
- package/components/collapsible/types/DisclosureMixinTypes.ts +48 -0
- package/components/combobox/src/utils/makeMatchingTextBold.js +3 -1
- package/components/combobox/test/lion-combobox.test.js +105 -0
- package/components/form-core/src/form-group/FormGroupMixin.js +7 -0
- package/components/form-core/src/registration/FormRegistrarMixin.js +11 -0
- package/components/form-core/test/FormControlMixin.test.js +1 -0
- package/components/form-core/test/validate/Validator.test.js +2 -0
- package/components/form-core/test-suites/ValidateMixin.suite.js +2 -0
- package/components/form-core/test-suites/ValidateMixinFeedbackPart.suite.js +1 -0
- package/components/input-datepicker/src/LionInputDatepicker.js +6 -2
- package/components/input-datepicker/src/localizeNamespaceLoader.js +1 -0
- package/components/input-datepicker/test/lion-input-datepicker.test.js +17 -2
- package/components/input-datepicker/translations/bg.js +1 -1
- package/components/input-datepicker/translations/cs.js +1 -1
- package/components/input-datepicker/translations/de.js +1 -1
- package/components/input-datepicker/translations/en.js +1 -1
- package/components/input-datepicker/translations/es.js +1 -1
- package/components/input-datepicker/translations/fr.js +1 -1
- package/components/input-datepicker/translations/hu.js +1 -1
- package/components/input-datepicker/translations/it.js +1 -1
- package/components/input-datepicker/translations/nl.js +1 -1
- package/components/input-datepicker/translations/pl.js +1 -1
- package/components/input-datepicker/translations/ro.js +1 -1
- package/components/input-datepicker/translations/ru.js +1 -1
- package/components/input-datepicker/translations/sk.js +1 -1
- package/components/input-datepicker/translations/tr.js +1 -1
- package/components/input-datepicker/translations/uk.js +1 -1
- package/components/input-datepicker/translations/zh.js +1 -1
- package/components/input-stepper/src/LionInputStepper.js +15 -7
- package/components/input-stepper/test-suites/lion-input-stepper.suite.js +28 -4
- package/components/localize/test/date/formatDate.test.js +1 -0
- package/components/menu/CHANGELOG.md +37 -0
- package/components/menu/README.md +335 -0
- package/components/menu/index.js +2 -0
- package/components/menu/lion-menu-overlay.js +3 -0
- package/components/menu/lion-menu.js +3 -0
- package/components/menu/lion-spinbutton.js +3 -0
- package/components/menu/lion-toolbar.js +3 -0
- package/components/menu/lion-tree.js +3 -0
- package/components/menu/package.json +46 -0
- package/components/menu/requirements.temp.md +12 -0
- package/components/menu/src/InteractiveListMixin.js +796 -0
- package/components/menu/src/LionMenu.js +250 -0
- package/components/menu/src/LionMenuHybrid.js +46 -0
- package/components/menu/src/LionMenuOverlay.js +155 -0
- package/components/menu/src/LionSpinbutton.js +213 -0
- package/components/menu/src/LionToolbar.js +94 -0
- package/components/menu/src/LionTree.js +52 -0
- package/components/menu/src/MultiLevelListMixin.js +274 -0
- package/components/menu/src/utils/isInView.js +26 -0
- package/components/menu/src/utils/listItemInteractions.js +104 -0
- package/components/menu/src/utils/uuid.js +3 -0
- package/components/menu/test/lion-menu.test.js +3 -0
- package/components/menu/test-suites/InteractiveListMixin.suite.js +142 -0
- package/components/menu/types/InteractiveListMixinTypes.d.ts +82 -0
- package/components/navigation-bar/README.md +1 -0
- package/components/navigation-bar/src/LionNavigationBar.js +422 -0
- package/components/navigation-bar/src/types.js +41 -0
- package/components/overlays/NEXT.md +49 -0
- package/components/overlays/src/OverlayController.js +249 -150
- package/components/overlays/src/OverlayMixin.js +76 -12
- package/components/overlays/src/OverlaysManager.js +98 -0
- package/components/overlays/src/overlayShadowDomStyle.js +2 -2
- package/components/overlays/src/utils/adopt-styles.js +2 -1
- package/components/overlays/src/utils/get-focusable-elements.js +4 -0
- package/components/overlays/src/utils/is-equal-config.js +10 -6
- package/components/overlays/test/OverlayController.test.js +120 -7
- package/components/overlays/test/utils-tests/is-equal-config.test.js +1 -1
- package/components/overlays/test-suites/OverlayMixin.suite.js +8 -8
- package/components/overlays/types/OverlayConfig.ts +25 -4
- package/components/select-rich/test/lion-select-rich.test.js +3 -0
- package/custom-elements.json +12950 -2486
- package/dist-types/components/button/src/LionButton.d.ts +1 -0
- package/dist-types/components/button/src/LionButton.d.ts.map +1 -1
- package/dist-types/components/button/test-suites/LionButton.suite.d.ts.map +1 -1
- package/dist-types/components/calendar/src/LionCalendar.d.ts.map +1 -1
- package/dist-types/components/collapsible/src/DisclosureMixin.d.ts +3 -0
- package/dist-types/components/collapsible/src/DisclosureMixin.d.ts.map +1 -0
- package/dist-types/components/collapsible/types/DisclosureMixinTypes.d.ts +24 -0
- package/dist-types/components/collapsible/types/DisclosureMixinTypes.d.ts.map +1 -0
- package/dist-types/components/combobox/src/utils/makeMatchingTextBold.d.ts.map +1 -1
- package/dist-types/components/dialog/src/LionDialog.d.ts +3 -1
- package/dist-types/components/dialog/src/LionDialog.d.ts.map +1 -1
- package/dist-types/components/form-core/src/form-group/FormGroupMixin.d.ts.map +1 -1
- package/dist-types/components/form-core/src/registration/FormRegistrarMixin.d.ts.map +1 -1
- package/dist-types/components/form-core/test-suites/ValidateMixin.suite.d.ts.map +1 -1
- package/dist-types/components/form-core/test-suites/ValidateMixinFeedbackPart.suite.d.ts.map +1 -1
- package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts.map +1 -1
- package/dist-types/components/input-datepicker/src/localizeNamespaceLoader.d.ts.map +1 -1
- package/dist-types/components/input-stepper/src/LionInputStepper.d.ts +4 -2
- package/dist-types/components/input-stepper/src/LionInputStepper.d.ts.map +1 -1
- package/dist-types/components/input-stepper/test-suites/lion-input-stepper.suite.d.ts.map +1 -1
- package/dist-types/components/menu/index.d.ts +3 -0
- package/dist-types/components/menu/index.d.ts.map +1 -0
- package/dist-types/components/menu/lion-menu-overlay.d.ts +2 -0
- package/dist-types/components/menu/lion-menu-overlay.d.ts.map +1 -0
- package/dist-types/components/menu/lion-menu.d.ts +2 -0
- package/dist-types/components/menu/lion-menu.d.ts.map +1 -0
- package/dist-types/components/menu/lion-spinbutton.d.ts +2 -0
- package/dist-types/components/menu/lion-spinbutton.d.ts.map +1 -0
- package/dist-types/components/menu/lion-toolbar.d.ts +2 -0
- package/dist-types/components/menu/lion-toolbar.d.ts.map +1 -0
- package/dist-types/components/menu/lion-tree.d.ts +2 -0
- package/dist-types/components/menu/lion-tree.d.ts.map +1 -0
- package/dist-types/components/menu/src/InteractiveListMixin.d.ts +611 -0
- package/dist-types/components/menu/src/InteractiveListMixin.d.ts.map +1 -0
- package/dist-types/components/menu/src/LionMenu.d.ts +563 -0
- package/dist-types/components/menu/src/LionMenu.d.ts.map +1 -0
- package/dist-types/components/menu/src/LionMenuHybrid.d.ts +19 -0
- package/dist-types/components/menu/src/LionMenuHybrid.d.ts.map +1 -0
- package/dist-types/components/menu/src/LionMenuOverlay.d.ts +1007 -0
- package/dist-types/components/menu/src/LionMenuOverlay.d.ts.map +1 -0
- package/dist-types/components/menu/src/LionSpinbutton.d.ts +58 -0
- package/dist-types/components/menu/src/LionSpinbutton.d.ts.map +1 -0
- package/dist-types/components/menu/src/LionToolbar.d.ts +8 -0
- package/dist-types/components/menu/src/LionToolbar.d.ts.map +1 -0
- package/dist-types/components/menu/src/LionTree.d.ts +445 -0
- package/dist-types/components/menu/src/LionTree.d.ts.map +1 -0
- package/dist-types/components/menu/src/MultiLevelListMixin.d.ts +493 -0
- package/dist-types/components/menu/src/MultiLevelListMixin.d.ts.map +1 -0
- package/dist-types/components/menu/src/utils/isInView.d.ts +9 -0
- package/dist-types/components/menu/src/utils/isInView.d.ts.map +1 -0
- package/dist-types/components/menu/src/utils/listItemInteractions.d.ts +42 -0
- package/dist-types/components/menu/src/utils/listItemInteractions.d.ts.map +1 -0
- package/dist-types/components/menu/src/utils/uuid.d.ts +2 -0
- package/dist-types/components/menu/src/utils/uuid.d.ts.map +1 -0
- package/dist-types/components/menu/test/lion-menu.test.d.ts +2 -0
- package/dist-types/components/menu/test/lion-menu.test.d.ts.map +1 -0
- package/dist-types/components/menu/test-suites/InteractiveListMixin.suite.d.ts +13 -0
- package/dist-types/components/menu/test-suites/InteractiveListMixin.suite.d.ts.map +1 -0
- package/dist-types/components/navigation-bar/src/LionNavigationBar.d.ts +238 -0
- package/dist-types/components/navigation-bar/src/LionNavigationBar.d.ts.map +1 -0
- package/dist-types/components/navigation-bar/src/types.d.ts +85 -0
- package/dist-types/components/navigation-bar/src/types.d.ts.map +1 -0
- package/dist-types/components/overlays/src/OverlayController.d.ts +10 -20
- package/dist-types/components/overlays/src/OverlayController.d.ts.map +1 -1
- package/dist-types/components/overlays/src/OverlayMixin.d.ts +3 -0
- package/dist-types/components/overlays/src/OverlayMixin.d.ts.map +1 -1
- package/dist-types/components/overlays/src/OverlaysManager.d.ts +31 -0
- package/dist-types/components/overlays/src/OverlaysManager.d.ts.map +1 -1
- package/dist-types/components/overlays/src/utils/get-focusable-elements.d.ts.map +1 -1
- package/dist-types/components/overlays/src/utils/is-equal-config.d.ts.map +1 -1
- package/dist-types/components/overlays/types/OverlayConfig.d.ts +11 -2
- package/dist-types/components/overlays/types/OverlayConfig.d.ts.map +1 -1
- package/dist-types/components/select-rich/src/LionSelectRich.d.ts +12 -2
- package/dist-types/components/select-rich/src/LionSelectRich.d.ts.map +1 -1
- package/dist-types/exports/define/lion-item.d.ts +2 -0
- package/dist-types/exports/define/lion-item.d.ts.map +1 -0
- package/dist-types/exports/define/lion-menu-overlay.d.ts +2 -0
- package/dist-types/exports/define/lion-menu-overlay.d.ts.map +1 -0
- package/dist-types/exports/define/lion-menu.d.ts +2 -0
- package/dist-types/exports/define/lion-menu.d.ts.map +1 -0
- package/dist-types/exports/define/lion-navigation-bar.d.ts +2 -0
- package/dist-types/exports/define/lion-navigation-bar.d.ts.map +1 -0
- package/dist-types/exports/define/lion-toolbar.d.ts +2 -0
- package/dist-types/exports/define/lion-toolbar.d.ts.map +1 -0
- package/dist-types/exports/define/lion-tree.d.ts +2 -0
- package/dist-types/exports/define/lion-tree.d.ts.map +1 -0
- package/dist-types/exports/menu.d.ts +7 -0
- package/dist-types/exports/menu.d.ts.map +1 -0
- package/dist-types/exports/navigation-bar.d.ts +2 -0
- package/dist-types/exports/navigation-bar.d.ts.map +1 -0
- package/dist-types/exports/overlays.d.ts +3 -0
- package/dist-types/exports/overlays.d.ts.map +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 +1 -1
- package/docs/components/fieldset/overview.md +5 -5
- package/docs/components/form/overview.md +3 -3
- package/docs/components/form/use-cases.md +2 -2
- package/docs/components/icon/use-cases.md +1 -1
- package/docs/components/index.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-datepicker/use-cases.md +16 -1
- package/docs/components/input-email/overview.md +2 -2
- package/docs/components/input-file/overview.md +1 -1
- package/docs/components/input-iban/overview.md +2 -2
- package/docs/components/input-range/api-table.md +1 -1
- package/docs/components/input-range/overview.md +2 -2
- package/docs/components/input-stepper/api-table.md +2 -2
- package/docs/components/input-stepper/overview.md +1 -1
- package/docs/components/input-stepper/use-cases.md +26 -0
- 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/listbox/use-cases.md +1 -1
- package/docs/components/menu/api-table.md +450 -0
- package/docs/components/menu/applyMenuDemoStyles.js +173 -0
- package/docs/components/menu/extensions.md +216 -0
- package/docs/components/menu/index.md +13 -0
- package/docs/components/menu/overview.md +422 -0
- package/docs/components/radio-group/overview.md +3 -3
- package/docs/components/select/use-cases.md +1 -1
- package/docs/components/select-rich/api-table.md +1 -1
- package/docs/components/select-rich/use-cases.md +2 -2
- package/docs/components/tabs/examples.md +1 -1
- package/docs/fundamentals/index.md +4 -4
- package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
- package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
- package/docs/fundamentals/rationales/accessibility.md +1 -1
- package/docs/fundamentals/systems/core/SlotMixin.md +18 -18
- 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/interaction-states.md +0 -1
- 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 +2 -2
- package/docs/fundamentals/systems/localize/overview.md +4 -4
- package/docs/fundamentals/systems/localize/text.md +1 -1
- package/docs/fundamentals/systems/overlays/api-table.md +92 -45
- 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/create-a-form.md +4 -4
- package/docs/guides/how-to/extend-a-native-input.md +2 -2
- package/docs/guides/how-to/index.md +14 -0
- package/docs/guides/index.md +8 -8
- package/docs/guides/principles/index.md +5 -5
- package/docs/guides/principles/styling.md +1 -2
- package/exports/collapsible.js +1 -0
- package/exports/define/lion-item.js +3 -0
- package/exports/define/lion-menu-overlay.js +3 -0
- package/exports/define/lion-menu.js +3 -0
- package/exports/define/lion-navigation-bar.js +3 -0
- package/exports/define/lion-toolbar.js +3 -0
- package/exports/define/lion-tree.js +3 -0
- package/exports/menu.js +6 -0
- package/exports/navigation-bar.js +1 -0
- package/exports/overlays.js +6 -0
- package/exports/types/overlays.ts +1 -0
- package/menu.js +6 -0
- package/navigation-bar.js +1 -0
- package/overlays.js +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
- Redesign goals OverlayController
|
|
2
|
+
- Simplicity
|
|
3
|
+
- One primitive to (controller to) rule them all
|
|
4
|
+
- Aligned api with all disclosure mechanisms (openables: <https://open-ui.org/components/openable.explainer/>)
|
|
5
|
+
- Aligned with platform overlay primitives (popover, dialog and tooltip)
|
|
6
|
+
- Controller will be elegantly aligned with host
|
|
7
|
+
- Controller props will be mapped to a host via a mixin, w/o requiring lots of boilerplate
|
|
8
|
+
- No race conditions due to unaligned heart beats: both hook in to the same "tick": the reactive update cycle of lit (which can theoretically be swapped by other cycles, see <https://lit.dev/docs/frameworks/react/#usecontroller>)
|
|
9
|
+
- In general, all property effects / events / reactivity should be aligned as much as possible with the update cycle (for predictability and perf. We only need
|
|
10
|
+
- All components will just be configuration (no need to redefine a lot of logic)
|
|
11
|
+
- OverlayController (or OpenableController, or DisclosureController, or VisitibilityToggleController) will be the biggest common denominator. Having all logic in one place, makes it a lot easier to erase about then having it scattered across many mixin and/or controller levels
|
|
12
|
+
- Even though the amount of logic is large (and might feel a bit monolithic), this is just needed when we want to have a stable, battle tested abstraction around all edge cases that building accessible disclosures should handle
|
|
13
|
+
- However, this does not come at an extra performance cost (see performance, you will only load what you need)
|
|
14
|
+
- The current OverlayController is a long file that can benefit from some modularity and cleanup
|
|
15
|
+
- Every overlay feature can be provided in a separate file with knowledge about the overlay lifecycle
|
|
16
|
+
- We can provide/reuse some utils like Resettable from VisibilityToggleCtrl ()
|
|
17
|
+
- Flexibility
|
|
18
|
+
- Re-use your markup for different screen sizes and/or devices (that may have different appearances, but the same functionality/semantics)
|
|
19
|
+
- Think of:
|
|
20
|
+
- a datepicker that opens as dropdown on desktop and as centered modal dialog on mobile
|
|
21
|
+
- a menubutton that opens as dropdown on desktop and as bottomsheet
|
|
22
|
+
- A menu that opens as dropdown on desktop and a collapsible (or even a plain list) on mobile
|
|
23
|
+
- As explained above, controllers are the most re-usable primitives, as a web component/mixin does not always provide the flexibility we need
|
|
24
|
+
- Controllers can be easliy mapped to directives (giving the same flexibility as "platform attributes" like popover). Forward compatible with <https://github.com/WICG/webcomponents/issues/1029>
|
|
25
|
+
- Controllers can be easily mapped to web components or mixins
|
|
26
|
+
- Contrary to mixins, it’s easy to conditionally load a controller
|
|
27
|
+
- Contrary to mixins, it’s easy to “undo” a controller (which is helpful in edge cases)
|
|
28
|
+
- Performance
|
|
29
|
+
- Will leverage the platform where possible:
|
|
30
|
+
- Use popover
|
|
31
|
+
- Forward compatibility
|
|
32
|
+
- Aligns as much as possible with (future) platform apis
|
|
33
|
+
- This leverages existing dev knowledge (api familiarity)
|
|
34
|
+
- This allows to build drop in replacements
|
|
35
|
+
- This allows to rely on the platform (reducing maintenance costs, increasing performance)
|
|
36
|
+
- Completeness
|
|
37
|
+
- Supports all use cases to build every UI thinkable:
|
|
38
|
+
- Will have optional dependency to menu controllers and interactive list controllers
|
|
39
|
+
- Supports visually hidden scenarios, hides
|
|
40
|
+
- Backward compatibility
|
|
41
|
+
- Since the amount of consumers of our overlay system (directly or indirectly) is large, we should provide a compatibility mechanism where possible. At the same time, we want to create an api as clean/aligned/forward compatible as possible. We could consider an optional compat-layer (a global import at the top of an app extending OverlayController prototype). Think of:
|
|
42
|
+
- Mapping popper api to popover concepts
|
|
43
|
+
-
|
|
44
|
+
|
|
45
|
+
# Why start with OverlayController?
|
|
46
|
+
|
|
47
|
+
- there is a lot of value in cleaning it up as there are many platform improvements
|
|
48
|
+
- it's used by many
|
|
49
|
+
- It is already a controller (we created it before Lit controllers existed) and therefore impact is minimal
|
|
@@ -3,9 +3,10 @@ import { deepContains } from './utils/deep-contains.js';
|
|
|
3
3
|
import { overlayShadowDomStyle } from './overlayShadowDomStyle.js';
|
|
4
4
|
import { _adoptStyleUtils } from './utils/adopt-styles.js';
|
|
5
5
|
import { getFocusableElements } from './utils/get-focusable-elements.js';
|
|
6
|
+
import { isEqualConfig } from './utils/is-equal-config.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
* @typedef {'
|
|
9
|
+
* @typedef {import('@lion/ui/types/overlays.js').OverlayPhase} OverlayPhase
|
|
9
10
|
* @typedef {import('@lion/ui/types/overlays.js').ViewportConfig} ViewportConfig
|
|
10
11
|
* @typedef {import('@lion/ui/types/overlays.js').OverlayConfig} OverlayConfig
|
|
11
12
|
* @typedef {import('@popperjs/core').Options} PopperOptions
|
|
@@ -32,27 +33,37 @@ import { getFocusableElements } from './utils/get-focusable-elements.js';
|
|
|
32
33
|
* `<slot name="my-content">` belonging to `<div slot="content"/>` will be wrapped with wrappingDialogNodeL1 and contentWrapperNodeL2
|
|
33
34
|
* inside shadow dom. With the help of temp markers, `<slot name="my-content">`'s original position will be respected.
|
|
34
35
|
*
|
|
35
|
-
* @param {{
|
|
36
|
+
* @param {{
|
|
37
|
+
* wrappingDialogNodeL1: HTMLDialogElement|HTMLDivElement;
|
|
38
|
+
* contentWrapperNodeL2:Element;
|
|
39
|
+
* contentNodeL3: Element;
|
|
40
|
+
* }} opts
|
|
36
41
|
*/
|
|
37
42
|
function rearrangeNodes({ wrappingDialogNodeL1, contentWrapperNodeL2, contentNodeL3 }) {
|
|
38
|
-
if
|
|
43
|
+
// if contentWrapperNode is provided by the user,
|
|
44
|
+
// we assume it lives in shadow dom around a slot.
|
|
45
|
+
const hasLegacyMethodOfProvidingWrapperNode = Boolean(contentWrapperNodeL2.isConnected);
|
|
46
|
+
// We could be initialized via a directive (in offline dom). It's important that we know about the parents,
|
|
47
|
+
// as we cannot deal with single content nodes
|
|
48
|
+
const hasContentNodeAttachmentPoints = Boolean(contentNodeL3.parentNode);
|
|
49
|
+
|
|
50
|
+
if (!hasLegacyMethodOfProvidingWrapperNode && !hasContentNodeAttachmentPoints) {
|
|
39
51
|
throw new Error(
|
|
40
|
-
'[OverlayController] Could not find a render target,
|
|
52
|
+
'[OverlayController] Could not find a render target, makes sure contentNode has a parent element (or contentWrapperNode is connected)',
|
|
41
53
|
);
|
|
42
54
|
}
|
|
43
55
|
|
|
56
|
+
/** @type {Node} */
|
|
44
57
|
let parentElement;
|
|
45
|
-
const tempMarker = document.createComment('
|
|
58
|
+
const tempMarker = document.createComment('overlay-insertion-marker');
|
|
46
59
|
|
|
47
|
-
if (
|
|
60
|
+
if (hasLegacyMethodOfProvidingWrapperNode) {
|
|
48
61
|
// This is the case when contentWrapperNode (living in shadow dom, wrapping <slot name="my-content-outlet">) is already provided via controller.
|
|
49
62
|
parentElement = contentWrapperNodeL2.parentElement || contentWrapperNodeL2.getRootNode();
|
|
50
63
|
parentElement.insertBefore(tempMarker, contentWrapperNodeL2);
|
|
51
64
|
// Wrap...
|
|
52
65
|
wrappingDialogNodeL1.appendChild(contentWrapperNodeL2);
|
|
53
|
-
}
|
|
54
|
-
// if contentNodeL3.isConnected
|
|
55
|
-
else {
|
|
66
|
+
} else {
|
|
56
67
|
const contentIsProjected = contentNodeL3.assignedSlot;
|
|
57
68
|
if (contentIsProjected) {
|
|
58
69
|
parentElement =
|
|
@@ -93,7 +104,22 @@ function rearrangeNodes({ wrappingDialogNodeL1, contentWrapperNodeL2, contentNod
|
|
|
93
104
|
* ```
|
|
94
105
|
*/
|
|
95
106
|
parentElement.insertBefore(wrappingDialogNodeL1, tempMarker);
|
|
96
|
-
|
|
107
|
+
|
|
108
|
+
return function cleanup() {
|
|
109
|
+
if (hasLegacyMethodOfProvidingWrapperNode) {
|
|
110
|
+
parentElement?.insertBefore(contentWrapperNodeL2, tempMarker);
|
|
111
|
+
} else {
|
|
112
|
+
parentElement?.insertBefore(contentNodeL3, tempMarker);
|
|
113
|
+
if (parentElement.contains(contentWrapperNodeL2)) {
|
|
114
|
+
contentWrapperNodeL2.remove();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
parentElement?.removeChild(tempMarker);
|
|
118
|
+
|
|
119
|
+
if (parentElement.contains(wrappingDialogNodeL1)) {
|
|
120
|
+
parentElement?.removeChild(wrappingDialogNodeL1);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
97
123
|
}
|
|
98
124
|
|
|
99
125
|
/**
|
|
@@ -106,6 +132,21 @@ async function preloadPopper() {
|
|
|
106
132
|
|
|
107
133
|
const childDialogsClosedInEventLoopWeakmap = new WeakMap();
|
|
108
134
|
|
|
135
|
+
/**
|
|
136
|
+
* @param {HTMLElement | null} el
|
|
137
|
+
* @param {string} selector
|
|
138
|
+
* @returns {HTMLElement | null}
|
|
139
|
+
*/
|
|
140
|
+
function deepClosest(el, selector) {
|
|
141
|
+
// @ts-ignore - comparing HTMLElement to document/window for safety
|
|
142
|
+
return (
|
|
143
|
+
// @ts-ignore - type comparison check
|
|
144
|
+
(el && el !== document && el !== window && el.closest(selector)) ||
|
|
145
|
+
// @ts-ignore - host property on ShadowRoot
|
|
146
|
+
deepClosest(el.getRootNode().host, selector)
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
109
150
|
/**
|
|
110
151
|
* OverlayController is the fundament for every single type of overlay. With the right
|
|
111
152
|
* configuration, it can be used to build (modal) dialogs, tooltips, dropdowns, popovers,
|
|
@@ -196,10 +237,21 @@ export class OverlayController extends EventTarget {
|
|
|
196
237
|
placement: 'center',
|
|
197
238
|
},
|
|
198
239
|
zIndex: 9999,
|
|
240
|
+
// TODO: rename to isActivated
|
|
241
|
+
isOpenable: true,
|
|
242
|
+
focusContentOnOpen: false,
|
|
243
|
+
// This means that content will have visually-hidden / sr-only styles. This is handy for:
|
|
244
|
+
// - hidden menus that should be "indexable" by screen readers (like the Links list in VO: https://support.apple.com/en-gb/guide/voiceover/mchlp2719/mac)
|
|
245
|
+
// - "more" menus that put content in a dropdown that do not fit in the current row
|
|
246
|
+
// - menus that should generally open on tab.
|
|
247
|
+
// @ts-ignore - hideVisually is an extension property
|
|
248
|
+
hideVisually: false,
|
|
249
|
+
requireConnectedNodes: true,
|
|
199
250
|
};
|
|
200
251
|
|
|
201
252
|
/** @protected */
|
|
202
253
|
this._contentId = `overlay-content--${Math.random().toString(36).slice(2, 10)}`;
|
|
254
|
+
|
|
203
255
|
/** @private */
|
|
204
256
|
this.__originalAttrs = new Map();
|
|
205
257
|
/** @private */
|
|
@@ -220,6 +272,8 @@ export class OverlayController extends EventTarget {
|
|
|
220
272
|
this.__escKeyHandlerCalled = false;
|
|
221
273
|
}
|
|
222
274
|
|
|
275
|
+
#hasSetup = false;
|
|
276
|
+
|
|
223
277
|
/**
|
|
224
278
|
* The invokerNode
|
|
225
279
|
* @type {HTMLElement | undefined}
|
|
@@ -238,7 +292,7 @@ export class OverlayController extends EventTarget {
|
|
|
238
292
|
|
|
239
293
|
/**
|
|
240
294
|
* Determines the connection point in DOM (body vs next to invoker).
|
|
241
|
-
* @type {'global' | 'local' | undefined}
|
|
295
|
+
* @type {'global' | 'local' | 'custom' | undefined}
|
|
242
296
|
*/
|
|
243
297
|
get placementMode() {
|
|
244
298
|
return this.config?.placementMode;
|
|
@@ -463,17 +517,14 @@ export class OverlayController extends EventTarget {
|
|
|
463
517
|
* @param { OverlayConfig } cfgToAdd
|
|
464
518
|
*/
|
|
465
519
|
updateConfig(cfgToAdd) {
|
|
466
|
-
// Teardown all previous configs
|
|
467
|
-
this.teardown();
|
|
468
|
-
|
|
469
520
|
/**
|
|
470
521
|
* @type {OverlayConfig}
|
|
471
522
|
* @private
|
|
472
523
|
*/
|
|
473
|
-
|
|
524
|
+
const prevConfig = this.config;
|
|
474
525
|
|
|
475
526
|
/** @type {OverlayConfig} */
|
|
476
|
-
|
|
527
|
+
const newConfig = {
|
|
477
528
|
...this._defaultConfig, // our basic ingredients
|
|
478
529
|
...this.__sharedConfig, // the initial configured overlayController
|
|
479
530
|
...cfgToAdd, // the added config
|
|
@@ -489,12 +540,18 @@ export class OverlayController extends EventTarget {
|
|
|
489
540
|
},
|
|
490
541
|
};
|
|
491
542
|
|
|
543
|
+
const shouldUpdate = !this.#hasSetup || !isEqualConfig(prevConfig, newConfig);
|
|
544
|
+
if (!shouldUpdate) return;
|
|
545
|
+
// Teardown all previous configs
|
|
546
|
+
|
|
547
|
+
this.teardown();
|
|
548
|
+
|
|
549
|
+
this.config = newConfig;
|
|
550
|
+
|
|
492
551
|
/** @private */
|
|
493
552
|
this.__validateConfiguration(this.config);
|
|
494
553
|
/** @protected */
|
|
495
554
|
this._init();
|
|
496
|
-
/** @private */
|
|
497
|
-
this.__elementToFocusAfterHide = undefined;
|
|
498
555
|
|
|
499
556
|
if (!this.#isRegisteredOnManager()) {
|
|
500
557
|
this.manager.add(this);
|
|
@@ -516,7 +573,7 @@ export class OverlayController extends EventTarget {
|
|
|
516
573
|
'[OverlayController] You need to provide a .placementMode ("global"|"local")',
|
|
517
574
|
);
|
|
518
575
|
}
|
|
519
|
-
if (!['global', 'local'].includes(newConfig.placementMode)) {
|
|
576
|
+
if (!['global', 'local', 'custom'].includes(newConfig.placementMode)) {
|
|
520
577
|
throw new Error(
|
|
521
578
|
`[OverlayController] "${newConfig.placementMode}" is not a valid .placementMode, use ("global"|"local")`,
|
|
522
579
|
);
|
|
@@ -535,14 +592,23 @@ export class OverlayController extends EventTarget {
|
|
|
535
592
|
* @protected
|
|
536
593
|
*/
|
|
537
594
|
_init() {
|
|
538
|
-
if (!this.
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
595
|
+
if (!this.config.isOpenable) return;
|
|
596
|
+
|
|
597
|
+
// TODO: should not be behind a flag when we are fully tearing down...
|
|
598
|
+
// TODO 2: When we move away from dialog (use popover to paint to top layer)
|
|
599
|
+
// and popper.js, (use popover and anchor positioning and popperjs as fallback, but we can put styles on contentNode),
|
|
600
|
+
// we dont need to create complex wrappers
|
|
601
|
+
// if (!this.__contentHasBeenInitialized) {
|
|
602
|
+
this.__initContentDomStructure();
|
|
603
|
+
// this.__contentHasBeenInitialized = true;
|
|
604
|
+
// }
|
|
605
|
+
this.__initVisibility();
|
|
542
606
|
|
|
543
607
|
// Reset all positioning styles (local, c.q. Popper) and classes (global)
|
|
544
|
-
this.contentWrapperNode.
|
|
545
|
-
|
|
608
|
+
if (this.contentWrapperNode !== this.contentNode) {
|
|
609
|
+
this.contentWrapperNode.removeAttribute('style');
|
|
610
|
+
this.contentWrapperNode.removeAttribute('class');
|
|
611
|
+
}
|
|
546
612
|
|
|
547
613
|
if (this.placementMode === 'local') {
|
|
548
614
|
// Lazily load Popper as soon as the first local overlay is used...
|
|
@@ -552,8 +618,27 @@ export class OverlayController extends EventTarget {
|
|
|
552
618
|
}
|
|
553
619
|
this.__handleOverlayStyles({ phase: 'init' });
|
|
554
620
|
this._handleFeatures({ phase: 'init' });
|
|
621
|
+
|
|
622
|
+
/** @private */
|
|
623
|
+
this.__elementToFocusAfterHide = undefined;
|
|
624
|
+
|
|
625
|
+
this.contentNode.dataset.content = '';
|
|
626
|
+
|
|
627
|
+
// Use event delegation to listen for clicks on close buttons...
|
|
628
|
+
this.contentNode.addEventListener('click', this.#hideOnCloseButtonClick);
|
|
629
|
+
|
|
630
|
+
this.#hasSetup = true;
|
|
555
631
|
}
|
|
556
632
|
|
|
633
|
+
#hideOnCloseButtonClick = (/** @type {{ target: any; }} */ ev) => {
|
|
634
|
+
const isOurCloseButton =
|
|
635
|
+
ev.target.hasAttribute('data-close') &&
|
|
636
|
+
deepClosest(ev.target, '[data-content]') === this.contentNode;
|
|
637
|
+
if (!isOurCloseButton) return;
|
|
638
|
+
|
|
639
|
+
this.hide();
|
|
640
|
+
};
|
|
641
|
+
|
|
557
642
|
/**
|
|
558
643
|
* @param {{ phase: OverlayPhase }} config
|
|
559
644
|
* @private
|
|
@@ -594,82 +679,108 @@ export class OverlayController extends EventTarget {
|
|
|
594
679
|
* @private
|
|
595
680
|
*/
|
|
596
681
|
__initContentDomStructure() {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
682
|
+
if (this.placementMode !== 'custom') {
|
|
683
|
+
const wrappingDialogElement = document.createElement('dialog');
|
|
684
|
+
// We use a dialog for its visual capabilities: it renders to the top layer.
|
|
685
|
+
// A11y will depend on the type of overlay and is arranged on contentNode level.
|
|
686
|
+
// Also see: https://www.scottohara.me/blog/2019/03/05/open-dialog.html
|
|
687
|
+
//
|
|
688
|
+
// The role="dialog" is set on the contentNode (or another role), so role="none"
|
|
689
|
+
// is valid here, although AXE complains about this setup.
|
|
690
|
+
// For now we need to add `ignoredRules: ['aria-allowed-role']` in your AXE tests.
|
|
691
|
+
// see: https://lion.js.org/fundamentals/systems/overlays/rationale/#considerations
|
|
692
|
+
wrappingDialogElement.setAttribute('role', 'none');
|
|
693
|
+
wrappingDialogElement.setAttribute('data-overlay-outer-wrapper', '');
|
|
694
|
+
// N.B. position: fixed is needed to escape out of 'overflow: hidden'
|
|
695
|
+
// We give a high z-index for non-modal dialogs, so that we at least win from all siblings of our
|
|
696
|
+
// parent stacking context
|
|
697
|
+
// padding reset so we don't get a weird dialog visual square showing up
|
|
698
|
+
wrappingDialogElement.style.cssText = `display:none; z-index: ${this.config.zIndex}; padding: 0;`;
|
|
699
|
+
// @ts-ignore - 'custom' is a valid extension of placementMode
|
|
700
|
+
if (this.config.placementMode === 'custom') {
|
|
701
|
+
// The user should have full freedom to control the content node, so its wrapper nodes should remain neutral.
|
|
702
|
+
wrappingDialogElement.style.cssText += `position: static;`;
|
|
703
|
+
}
|
|
704
|
+
this.__wrappingDialogNode = wrappingDialogElement;
|
|
614
705
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
706
|
+
/**
|
|
707
|
+
* Based on the configuration of the developer, multiple scenarios are accounted for
|
|
708
|
+
* A. We already have a contentWrapperNode ()
|
|
709
|
+
*/
|
|
710
|
+
if (!this.config?.contentWrapperNode) {
|
|
711
|
+
this.__contentWrapperNode = document.createElement('div');
|
|
712
|
+
}
|
|
713
|
+
this.contentWrapperNode.setAttribute('data-id', 'content-wrapper');
|
|
714
|
+
// 'hack' that makes sure popperjs (that is applied one level lower) works correctly in deeply nested shadow roots
|
|
715
|
+
this.contentWrapperNode.style.transform = 'translateZ(0px)';
|
|
716
|
+
|
|
717
|
+
this.__rearrangeNodesCleanup = rearrangeNodes({
|
|
718
|
+
wrappingDialogNodeL1: wrappingDialogElement,
|
|
719
|
+
contentWrapperNodeL2: this.contentWrapperNode,
|
|
720
|
+
contentNodeL3: this.contentNode,
|
|
721
|
+
// requireConnectedNodes: Boolean(this.config.requireConnectedNodes),
|
|
722
|
+
});
|
|
723
|
+
wrappingDialogElement.open = true;
|
|
623
724
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
725
|
+
if (this.isTooltip) {
|
|
726
|
+
// needed to prevent tooltip getting focus in Safari and Firefox
|
|
727
|
+
wrappingDialogElement.setAttribute('tabindex', '-1');
|
|
728
|
+
}
|
|
729
|
+
this.contentWrapperNode.style.zIndex = '1';
|
|
730
|
+
if (getComputedStyle(this.contentNode).position === 'absolute') {
|
|
731
|
+
// Having a _contWrapperNode and a contentNode with 'position:absolute' results in
|
|
732
|
+
// computed height of 0...
|
|
733
|
+
this.contentNode.style.position = 'static';
|
|
734
|
+
}
|
|
630
735
|
|
|
631
|
-
|
|
632
|
-
//
|
|
633
|
-
|
|
736
|
+
// Here we prevent any interference of the native <dialog> element with the keyboard behavior
|
|
737
|
+
// as defined by the OverlayController. This is needed until we can configure `closedby="none"`
|
|
738
|
+
// on the native dialog for all browsers: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog#closedby
|
|
739
|
+
const hasClosedBySupport = HTMLDialogElement && 'closedBy' in HTMLDialogElement.prototype;
|
|
740
|
+
if (hasClosedBySupport) {
|
|
741
|
+
// @ts-ignore - closedBy is a newer property not in all TypeScript versions
|
|
742
|
+
wrappingDialogElement.closedBy = 'none';
|
|
743
|
+
} else {
|
|
744
|
+
wrappingDialogElement.addEventListener(
|
|
745
|
+
'keydown',
|
|
746
|
+
(/** @type {* & KeyboardEvent} */ event) => {
|
|
747
|
+
if (event.key === 'Escape') {
|
|
748
|
+
event.preventDefault();
|
|
749
|
+
}
|
|
750
|
+
},
|
|
751
|
+
);
|
|
752
|
+
wrappingDialogElement.addEventListener(
|
|
753
|
+
'keyup',
|
|
754
|
+
(/** @type {* & KeyboardEvent} */ event) => {
|
|
755
|
+
if (event.key === 'Escape') {
|
|
756
|
+
event.preventDefault();
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
);
|
|
760
|
+
wrappingDialogElement.addEventListener('cancel', event => {
|
|
761
|
+
event.stopPropagation();
|
|
762
|
+
});
|
|
763
|
+
wrappingDialogElement.addEventListener('close', event => {
|
|
764
|
+
event.stopPropagation();
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
} else {
|
|
768
|
+
// quick hack to make none-popper/global flows work...
|
|
769
|
+
this.__contentWrapperNode = this.contentNode;
|
|
770
|
+
this.__wrappingDialogNode = this.contentNode;
|
|
634
771
|
}
|
|
772
|
+
}
|
|
635
773
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
// Having a _contWrapperNode and a contentNode with 'position:absolute' results in
|
|
641
|
-
// computed height of 0...
|
|
642
|
-
this.contentNode.style.position = 'static';
|
|
643
|
-
}
|
|
774
|
+
__initVisibility() {
|
|
775
|
+
// TODO: allow defaultOpen?
|
|
776
|
+
/** @type {HTMLDialogElement} */ (this.__wrappingDialogNode).style.display = 'none';
|
|
777
|
+
}
|
|
644
778
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
// @ts-expect-error
|
|
651
|
-
wrappingDialogElement.closedBy = 'none';
|
|
652
|
-
} else {
|
|
653
|
-
wrappingDialogElement.addEventListener(
|
|
654
|
-
'keydown',
|
|
655
|
-
(/** @type {* & KeyboardEvent} */ event) => {
|
|
656
|
-
if (event.key === 'Escape') {
|
|
657
|
-
event.preventDefault();
|
|
658
|
-
}
|
|
659
|
-
},
|
|
660
|
-
);
|
|
661
|
-
wrappingDialogElement.addEventListener('keyup', (/** @type {* & KeyboardEvent} */ event) => {
|
|
662
|
-
if (event.key === 'Escape') {
|
|
663
|
-
event.preventDefault();
|
|
664
|
-
}
|
|
665
|
-
});
|
|
666
|
-
wrappingDialogElement.addEventListener('cancel', event => {
|
|
667
|
-
event.stopPropagation();
|
|
668
|
-
});
|
|
669
|
-
wrappingDialogElement.addEventListener('close', event => {
|
|
670
|
-
event.stopPropagation();
|
|
671
|
-
});
|
|
672
|
-
}
|
|
779
|
+
// Clean up the DOM structure, leaving it as we found it.
|
|
780
|
+
// TODO: __teardownContentDomStructure method... use Resettable (rename ro Restorable) found in VisibilitToggleCtrl
|
|
781
|
+
__teardownVisibility() {
|
|
782
|
+
// TODO: full structure. For now we just reset display prop...
|
|
783
|
+
/** @type {HTMLDialogElement} */ (this.__wrappingDialogNode).style.display = '';
|
|
673
784
|
}
|
|
674
785
|
|
|
675
786
|
/**
|
|
@@ -740,6 +851,7 @@ export class OverlayController extends EventTarget {
|
|
|
740
851
|
* @private
|
|
741
852
|
*/
|
|
742
853
|
__storeOriginalAttrs(node, attrs) {
|
|
854
|
+
/** @type {Record<string, string | null>} */
|
|
743
855
|
const attrMap = {};
|
|
744
856
|
attrs.forEach(attrName => {
|
|
745
857
|
attrMap[attrName] = node.getAttribute(attrName);
|
|
@@ -796,6 +908,7 @@ export class OverlayController extends EventTarget {
|
|
|
796
908
|
if ('HTMLDialogElement' in window && this.__wrappingDialogNode instanceof HTMLDialogElement) {
|
|
797
909
|
this.__wrappingDialogNode.open = true;
|
|
798
910
|
}
|
|
911
|
+
|
|
799
912
|
// @ts-ignore
|
|
800
913
|
this.__wrappingDialogNode.style.display = '';
|
|
801
914
|
this._keepBodySize({ phase: 'before-show' });
|
|
@@ -809,6 +922,14 @@ export class OverlayController extends EventTarget {
|
|
|
809
922
|
contentNode: this.contentNode,
|
|
810
923
|
});
|
|
811
924
|
}
|
|
925
|
+
|
|
926
|
+
// a styling/debug hook for open state
|
|
927
|
+
this.contentNode.setAttribute('data-open', '');
|
|
928
|
+
|
|
929
|
+
if (this.config.focusContentOnOpen) {
|
|
930
|
+
this.contentNode.focus();
|
|
931
|
+
}
|
|
932
|
+
|
|
812
933
|
/** @type {function} */
|
|
813
934
|
(this._showResolve)();
|
|
814
935
|
}
|
|
@@ -841,6 +962,7 @@ export class OverlayController extends EventTarget {
|
|
|
841
962
|
await this.__createPopperInstance();
|
|
842
963
|
this._popper.forceUpdate();
|
|
843
964
|
}
|
|
965
|
+
// N.B. leave it up to the user if placementMode === 'custom'
|
|
844
966
|
}
|
|
845
967
|
|
|
846
968
|
/**
|
|
@@ -848,50 +970,9 @@ export class OverlayController extends EventTarget {
|
|
|
848
970
|
* @protected
|
|
849
971
|
*/
|
|
850
972
|
_keepBodySize({ phase }) {
|
|
851
|
-
if (!this.preventsScroll)
|
|
852
|
-
return;
|
|
853
|
-
}
|
|
973
|
+
if (!this.preventsScroll) return;
|
|
854
974
|
|
|
855
|
-
|
|
856
|
-
case 'before-show':
|
|
857
|
-
this.__bodyClientWidth = document.body.clientWidth;
|
|
858
|
-
this.__bodyClientHeight = document.body.clientHeight;
|
|
859
|
-
this.__bodyMarginRightInline = document.body.style.marginRight;
|
|
860
|
-
this.__bodyMarginBottomInline = document.body.style.marginBottom;
|
|
861
|
-
break;
|
|
862
|
-
case 'show': {
|
|
863
|
-
if (window.getComputedStyle) {
|
|
864
|
-
const bodyStyle = window.getComputedStyle(document.body);
|
|
865
|
-
this.__bodyMarginRight = parseInt(bodyStyle.getPropertyValue('margin-right'), 10);
|
|
866
|
-
this.__bodyMarginBottom = parseInt(bodyStyle.getPropertyValue('margin-bottom'), 10);
|
|
867
|
-
} else {
|
|
868
|
-
this.__bodyMarginRight = 0;
|
|
869
|
-
this.__bodyMarginBottom = 0;
|
|
870
|
-
}
|
|
871
|
-
const scrollbarWidth =
|
|
872
|
-
document.body.clientWidth - /** @type {number} */ (this.__bodyClientWidth);
|
|
873
|
-
const scrollbarHeight =
|
|
874
|
-
document.body.clientHeight - /** @type {number} */ (this.__bodyClientHeight);
|
|
875
|
-
const newMarginRight = this.__bodyMarginRight + scrollbarWidth;
|
|
876
|
-
const newMarginBottom = this.__bodyMarginBottom + scrollbarHeight;
|
|
877
|
-
// @ts-expect-error [external]: CSS not yet typed
|
|
878
|
-
if (window.CSS?.number && document.body.attributeStyleMap?.set) {
|
|
879
|
-
// @ts-expect-error [external]: types attributeStyleMap + CSS.px not available yet
|
|
880
|
-
document.body.attributeStyleMap.set('margin-right', CSS.px(newMarginRight));
|
|
881
|
-
// @ts-expect-error [external]: types attributeStyleMap + CSS.px not available yet
|
|
882
|
-
document.body.attributeStyleMap.set('margin-bottom', CSS.px(newMarginBottom));
|
|
883
|
-
} else {
|
|
884
|
-
document.body.style.marginRight = `${newMarginRight}px`;
|
|
885
|
-
document.body.style.marginBottom = `${newMarginBottom}px`;
|
|
886
|
-
}
|
|
887
|
-
break;
|
|
888
|
-
}
|
|
889
|
-
case 'hide':
|
|
890
|
-
document.body.style.marginRight = this.__bodyMarginRightInline || '';
|
|
891
|
-
document.body.style.marginBottom = this.__bodyMarginBottomInline || '';
|
|
892
|
-
break;
|
|
893
|
-
/* no default */
|
|
894
|
-
}
|
|
975
|
+
this.manager.requestToKeepBodySize({ phase });
|
|
895
976
|
}
|
|
896
977
|
|
|
897
978
|
/**
|
|
@@ -899,6 +980,9 @@ export class OverlayController extends EventTarget {
|
|
|
899
980
|
* @event hide right after the overlay is hidden
|
|
900
981
|
*/
|
|
901
982
|
async hide() {
|
|
983
|
+
// Function like a no-op for dynamic edge cases...
|
|
984
|
+
if (!this.config.isOpenable) return;
|
|
985
|
+
|
|
902
986
|
this._hideComplete = new Promise(resolve => {
|
|
903
987
|
this._hideResolve = resolve;
|
|
904
988
|
});
|
|
@@ -931,7 +1015,6 @@ export class OverlayController extends EventTarget {
|
|
|
931
1015
|
if ('HTMLDialogElement' in window && this.__wrappingDialogNode instanceof HTMLDialogElement) {
|
|
932
1016
|
this.__wrappingDialogNode.close();
|
|
933
1017
|
}
|
|
934
|
-
|
|
935
1018
|
// @ts-ignore
|
|
936
1019
|
this.__wrappingDialogNode.style.display = 'none';
|
|
937
1020
|
this._handleFeatures({ phase: 'hide' });
|
|
@@ -940,6 +1023,8 @@ export class OverlayController extends EventTarget {
|
|
|
940
1023
|
this._restoreFocus();
|
|
941
1024
|
}
|
|
942
1025
|
/** @type {function} */ (this._hideResolve)();
|
|
1026
|
+
|
|
1027
|
+
this.contentNode.removeAttribute('data-open');
|
|
943
1028
|
}
|
|
944
1029
|
|
|
945
1030
|
/**
|
|
@@ -1050,23 +1135,24 @@ export class OverlayController extends EventTarget {
|
|
|
1050
1135
|
if (this.visibilityTriggerFunction) {
|
|
1051
1136
|
this._handleVisibilityTriggers({ phase });
|
|
1052
1137
|
}
|
|
1138
|
+
|
|
1139
|
+
if (phase === 'init' && this.config.focusContentOnOpen) {
|
|
1140
|
+
this.contentNode?.setAttribute('tabindex', '-1');
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
if (phase === 'teardown' && this.config.focusContentOnOpen) {
|
|
1144
|
+
// TODO: capture initial attr, use Resettable mechanism of VisibilityToggleCtrl in whole Controller
|
|
1145
|
+
this.contentNode?.removeAttribute('tabindex');
|
|
1146
|
+
}
|
|
1053
1147
|
}
|
|
1054
1148
|
|
|
1055
1149
|
/**
|
|
1056
1150
|
* @param {{ phase: OverlayPhase }} config
|
|
1057
1151
|
*/
|
|
1058
1152
|
_handleVisibilityTriggers({ phase }) {
|
|
1059
|
-
if (typeof this.visibilityTriggerFunction
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
phase,
|
|
1063
|
-
controller: this,
|
|
1064
|
-
});
|
|
1065
|
-
}
|
|
1066
|
-
if (this.__visibilityTriggerHandler[phase]) {
|
|
1067
|
-
this.__visibilityTriggerHandler[phase]();
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1153
|
+
if (typeof this.visibilityTriggerFunction !== 'function') return;
|
|
1154
|
+
|
|
1155
|
+
this.visibilityTriggerFunction({ controller: this })?.[phase]?.();
|
|
1070
1156
|
}
|
|
1071
1157
|
|
|
1072
1158
|
/**
|
|
@@ -1223,7 +1309,9 @@ export class OverlayController extends EventTarget {
|
|
|
1223
1309
|
if (phase === 'show') {
|
|
1224
1310
|
this.#handleShiftKeyPress();
|
|
1225
1311
|
this.#handleFocusInsideDialog();
|
|
1312
|
+
// @ts-ignore - HTMLDialogElement methods
|
|
1226
1313
|
this.__wrappingDialogNode?.close();
|
|
1314
|
+
// @ts-ignore - HTMLDialogElement methods
|
|
1227
1315
|
this.__wrappingDialogNode?.showModal();
|
|
1228
1316
|
/**
|
|
1229
1317
|
* At this moment `#handleFocusInsideDialog` should handle the focus.
|
|
@@ -1474,11 +1562,22 @@ export class OverlayController extends EventTarget {
|
|
|
1474
1562
|
}
|
|
1475
1563
|
|
|
1476
1564
|
teardown() {
|
|
1565
|
+
if (!this.#hasSetup) return;
|
|
1477
1566
|
this.__handleOverlayStyles({ phase: 'teardown' });
|
|
1567
|
+
if (this.isShown) {
|
|
1568
|
+
this._keepBodySize({ phase: 'teardown' });
|
|
1569
|
+
}
|
|
1478
1570
|
this._handleFeatures({ phase: 'teardown' });
|
|
1479
1571
|
if (this.#isRegisteredOnManager()) {
|
|
1480
1572
|
this.manager.remove(this);
|
|
1481
1573
|
}
|
|
1574
|
+
|
|
1575
|
+
this.contentNode?.removeEventListener('click', this.#hideOnCloseButtonClick);
|
|
1576
|
+
|
|
1577
|
+
this.__rearrangeNodesCleanup?.();
|
|
1578
|
+
this.__teardownVisibility();
|
|
1579
|
+
|
|
1580
|
+
this.#hasSetup = false;
|
|
1482
1581
|
}
|
|
1483
1582
|
|
|
1484
1583
|
/** @private */
|