@lion/ui 0.16.1 → 0.17.0-menu-system.0
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 +275 -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 +449 -0
- package/components/navigation-bar/src/types.js +41 -0
- package/components/overlays/NEXT.md +49 -0
- package/components/overlays/src/OverlayController.js +165 -115
- package/components/overlays/src/OverlayMixin.js +75 -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 +4 -1
- package/components/overlays/test/OverlayController.test.js +117 -6
- package/components/overlays/types/OverlayConfig.ts +19 -4
- package/components/select-rich/test/lion-select-rich.test.js +3 -0
- package/custom-elements.json +15785 -5337
- 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 +2 -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 +1006 -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 +226 -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 +7 -13
- 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 +6 -2
- package/dist-types/components/overlays/types/OverlayConfig.d.ts.map +1 -1
- package/dist-types/components/select-rich/src/LionSelectRich.d.ts +11 -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 +2 -0
- 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 +2 -0
- package/exports/types/overlays.ts +1 -0
- package/menu.js +6 -0
- package/navigation-bar.js +1 -0
- package/overlays.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {'mobile' | 'desktop'} NavBarResponsiveMode
|
|
3
|
+
* Responsive mode for the navigation bar
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {Object} MenuItem
|
|
8
|
+
* Navigation menu item
|
|
9
|
+
* @property {string} [id] - Optional unique identifier
|
|
10
|
+
* @property {string} title - Display title of the menu item
|
|
11
|
+
* @property {string} [link] - URL for the menu item (for leaf items)
|
|
12
|
+
* @property {MenuItem[]} [sub] - Submenu items (for parent items)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {Object} CtaLink
|
|
17
|
+
* Call-to-action link
|
|
18
|
+
* @property {string} [id] - Optional unique identifier
|
|
19
|
+
* @property {string} [href] - URL for the CTA
|
|
20
|
+
* @property {string} [text] - Display text for the CTA
|
|
21
|
+
* @property {string} [label] - Accessible label for the CTA
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {Object} Logo
|
|
26
|
+
* Logo configuration for the navigation bar
|
|
27
|
+
* @property {string} type - Logo type (e.g., 'ing')
|
|
28
|
+
* @property {string} alt - Alternative text for the logo
|
|
29
|
+
* @property {string} href - URL the logo links to
|
|
30
|
+
* @property {string} [src] - Optional custom logo source URL
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @typedef {Object} LevelConfig
|
|
35
|
+
* Configuration for a menu level
|
|
36
|
+
* @property {boolean} [isBar] - Whether this level is displayed as a bar
|
|
37
|
+
* @property {boolean} [hasFullWidthFlyout] - Whether this level has a full-width flyout
|
|
38
|
+
* @property {object} [openableConfig] - Configuration for the openable overlay
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
export {};
|
|
@@ -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
|
|
@@ -5,7 +5,7 @@ import { _adoptStyleUtils } from './utils/adopt-styles.js';
|
|
|
5
5
|
import { getFocusableElements } from './utils/get-focusable-elements.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* @typedef {'
|
|
8
|
+
* @typedef {import('@lion/ui/types/overlays.js').OverlayPhase} OverlayPhase
|
|
9
9
|
* @typedef {import('@lion/ui/types/overlays.js').ViewportConfig} ViewportConfig
|
|
10
10
|
* @typedef {import('@lion/ui/types/overlays.js').OverlayConfig} OverlayConfig
|
|
11
11
|
* @typedef {import('@popperjs/core').Options} PopperOptions
|
|
@@ -106,6 +106,21 @@ async function preloadPopper() {
|
|
|
106
106
|
|
|
107
107
|
const childDialogsClosedInEventLoopWeakmap = new WeakMap();
|
|
108
108
|
|
|
109
|
+
/**
|
|
110
|
+
* @param {HTMLElement | null} el
|
|
111
|
+
* @param {string} selector
|
|
112
|
+
* @returns {HTMLElement | null}
|
|
113
|
+
*/
|
|
114
|
+
function deepClosest(el, selector) {
|
|
115
|
+
// @ts-ignore - comparing HTMLElement to document/window for safety
|
|
116
|
+
return (
|
|
117
|
+
// @ts-ignore - type comparison check
|
|
118
|
+
(el && el !== document && el !== window && el.closest(selector)) ||
|
|
119
|
+
// @ts-ignore - host property on ShadowRoot
|
|
120
|
+
deepClosest(el.getRootNode().host, selector)
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
109
124
|
/**
|
|
110
125
|
* OverlayController is the fundament for every single type of overlay. With the right
|
|
111
126
|
* configuration, it can be used to build (modal) dialogs, tooltips, dropdowns, popovers,
|
|
@@ -196,10 +211,19 @@ export class OverlayController extends EventTarget {
|
|
|
196
211
|
placement: 'center',
|
|
197
212
|
},
|
|
198
213
|
zIndex: 9999,
|
|
214
|
+
isOpenable: true,
|
|
215
|
+
focusContentOnOpen: false,
|
|
216
|
+
// This means that content will have visually-hidden / sr-only styles. This is handy for:
|
|
217
|
+
// - 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)
|
|
218
|
+
// - "more" menus that put content in a dropdown that do not fit in the current row
|
|
219
|
+
// - menus that should generally open on tab.
|
|
220
|
+
// @ts-ignore - hideVisually is an extension property
|
|
221
|
+
hideVisually: false,
|
|
199
222
|
};
|
|
200
223
|
|
|
201
224
|
/** @protected */
|
|
202
225
|
this._contentId = `overlay-content--${Math.random().toString(36).slice(2, 10)}`;
|
|
226
|
+
|
|
203
227
|
/** @private */
|
|
204
228
|
this.__originalAttrs = new Map();
|
|
205
229
|
/** @private */
|
|
@@ -220,6 +244,8 @@ export class OverlayController extends EventTarget {
|
|
|
220
244
|
this.__escKeyHandlerCalled = false;
|
|
221
245
|
}
|
|
222
246
|
|
|
247
|
+
#hasSetup = false;
|
|
248
|
+
|
|
223
249
|
/**
|
|
224
250
|
* The invokerNode
|
|
225
251
|
* @type {HTMLElement | undefined}
|
|
@@ -238,7 +264,7 @@ export class OverlayController extends EventTarget {
|
|
|
238
264
|
|
|
239
265
|
/**
|
|
240
266
|
* Determines the connection point in DOM (body vs next to invoker).
|
|
241
|
-
* @type {'global' | 'local' | undefined}
|
|
267
|
+
* @type {'global' | 'local' | 'custom' | undefined}
|
|
242
268
|
*/
|
|
243
269
|
get placementMode() {
|
|
244
270
|
return this.config?.placementMode;
|
|
@@ -493,8 +519,6 @@ export class OverlayController extends EventTarget {
|
|
|
493
519
|
this.__validateConfiguration(this.config);
|
|
494
520
|
/** @protected */
|
|
495
521
|
this._init();
|
|
496
|
-
/** @private */
|
|
497
|
-
this.__elementToFocusAfterHide = undefined;
|
|
498
522
|
|
|
499
523
|
if (!this.#isRegisteredOnManager()) {
|
|
500
524
|
this.manager.add(this);
|
|
@@ -516,7 +540,7 @@ export class OverlayController extends EventTarget {
|
|
|
516
540
|
'[OverlayController] You need to provide a .placementMode ("global"|"local")',
|
|
517
541
|
);
|
|
518
542
|
}
|
|
519
|
-
if (!['global', 'local'].includes(newConfig.placementMode)) {
|
|
543
|
+
if (!['global', 'local', 'custom'].includes(newConfig.placementMode)) {
|
|
520
544
|
throw new Error(
|
|
521
545
|
`[OverlayController] "${newConfig.placementMode}" is not a valid .placementMode, use ("global"|"local")`,
|
|
522
546
|
);
|
|
@@ -535,6 +559,8 @@ export class OverlayController extends EventTarget {
|
|
|
535
559
|
* @protected
|
|
536
560
|
*/
|
|
537
561
|
_init() {
|
|
562
|
+
if (!this.config.isOpenable) return;
|
|
563
|
+
|
|
538
564
|
if (!this.__contentHasBeenInitialized) {
|
|
539
565
|
this.__initContentDomStructure();
|
|
540
566
|
this.__contentHasBeenInitialized = true;
|
|
@@ -552,8 +578,27 @@ export class OverlayController extends EventTarget {
|
|
|
552
578
|
}
|
|
553
579
|
this.__handleOverlayStyles({ phase: 'init' });
|
|
554
580
|
this._handleFeatures({ phase: 'init' });
|
|
581
|
+
|
|
582
|
+
/** @private */
|
|
583
|
+
this.__elementToFocusAfterHide = undefined;
|
|
584
|
+
|
|
585
|
+
this.contentNode.dataset.content = '';
|
|
586
|
+
|
|
587
|
+
// Use event delegation to listen for clicks on close buttons...
|
|
588
|
+
this.contentNode.addEventListener('click', this.#hideOnCloseButtonClick);
|
|
589
|
+
|
|
590
|
+
this.#hasSetup = true;
|
|
555
591
|
}
|
|
556
592
|
|
|
593
|
+
#hideOnCloseButtonClick = (/** @type {{ target: any; }} */ ev) => {
|
|
594
|
+
const isOurCloseButton =
|
|
595
|
+
ev.target.hasAttribute('data-close') &&
|
|
596
|
+
deepClosest(ev.target, '[data-content]') === this.contentNode;
|
|
597
|
+
if (!isOurCloseButton) return;
|
|
598
|
+
|
|
599
|
+
this.hide();
|
|
600
|
+
};
|
|
601
|
+
|
|
557
602
|
/**
|
|
558
603
|
* @param {{ phase: OverlayPhase }} config
|
|
559
604
|
* @private
|
|
@@ -594,82 +639,97 @@ export class OverlayController extends EventTarget {
|
|
|
594
639
|
* @private
|
|
595
640
|
*/
|
|
596
641
|
__initContentDomStructure() {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
642
|
+
if (this.placementMode !== 'custom') {
|
|
643
|
+
const wrappingDialogElement = document.createElement('dialog');
|
|
644
|
+
// We use a dialog for its visual capabilities: it renders to the top layer.
|
|
645
|
+
// A11y will depend on the type of overlay and is arranged on contentNode level.
|
|
646
|
+
// Also see: https://www.scottohara.me/blog/2019/03/05/open-dialog.html
|
|
647
|
+
//
|
|
648
|
+
// The role="dialog" is set on the contentNode (or another role), so role="none"
|
|
649
|
+
// is valid here, although AXE complains about this setup.
|
|
650
|
+
// For now we need to add `ignoredRules: ['aria-allowed-role']` in your AXE tests.
|
|
651
|
+
// see: https://lion.js.org/fundamentals/systems/overlays/rationale/#considerations
|
|
652
|
+
wrappingDialogElement.setAttribute('role', 'none');
|
|
653
|
+
wrappingDialogElement.setAttribute('data-overlay-outer-wrapper', '');
|
|
654
|
+
// N.B. position: fixed is needed to escape out of 'overflow: hidden'
|
|
655
|
+
// We give a high z-index for non-modal dialogs, so that we at least win from all siblings of our
|
|
656
|
+
// parent stacking context
|
|
657
|
+
// padding reset so we don't get a weird dialog visual square showing up
|
|
658
|
+
wrappingDialogElement.style.cssText = `display:none; z-index: ${this.config.zIndex}; padding: 0;`;
|
|
659
|
+
// @ts-ignore - 'custom' is a valid extension of placementMode
|
|
660
|
+
if (this.config.placementMode === 'custom') {
|
|
661
|
+
// The user should have full freedom to control the content node, so its wrapper nodes should remain neutral.
|
|
662
|
+
wrappingDialogElement.style.cssText += `position: static;`;
|
|
663
|
+
}
|
|
664
|
+
this.__wrappingDialogNode = wrappingDialogElement;
|
|
614
665
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
666
|
+
/**
|
|
667
|
+
* Based on the configuration of the developer, multiple scenarios are accounted for
|
|
668
|
+
* A. We already have a contentWrapperNode ()
|
|
669
|
+
*/
|
|
670
|
+
if (!this.config?.contentWrapperNode) {
|
|
671
|
+
this.__contentWrapperNode = document.createElement('div');
|
|
672
|
+
}
|
|
673
|
+
this.contentWrapperNode.setAttribute('data-id', 'content-wrapper');
|
|
674
|
+
// 'hack' that makes sure popperjs (that is applied one level lower) works correctly in deeply nested shadow roots
|
|
675
|
+
this.contentWrapperNode.style.transform = 'translateZ(0px)';
|
|
676
|
+
|
|
677
|
+
rearrangeNodes({
|
|
678
|
+
wrappingDialogNodeL1: wrappingDialogElement,
|
|
679
|
+
contentWrapperNodeL2: this.contentWrapperNode,
|
|
680
|
+
contentNodeL3: this.contentNode,
|
|
681
|
+
});
|
|
682
|
+
wrappingDialogElement.open = true;
|
|
623
683
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
684
|
+
if (this.isTooltip) {
|
|
685
|
+
// needed to prevent tooltip getting focus in Safari and Firefox
|
|
686
|
+
wrappingDialogElement.setAttribute('tabindex', '-1');
|
|
687
|
+
}
|
|
688
|
+
this.contentWrapperNode.style.zIndex = '1';
|
|
689
|
+
if (getComputedStyle(this.contentNode).position === 'absolute') {
|
|
690
|
+
// Having a _contWrapperNode and a contentNode with 'position:absolute' results in
|
|
691
|
+
// computed height of 0...
|
|
692
|
+
this.contentNode.style.position = 'static';
|
|
693
|
+
}
|
|
630
694
|
|
|
631
|
-
|
|
632
|
-
//
|
|
633
|
-
|
|
695
|
+
// Here we prevent any interference of the native <dialog> element with the keyboard behavior
|
|
696
|
+
// as defined by the OverlayController. This is needed until we can configure `closedby="none"`
|
|
697
|
+
// on the native dialog for all browsers: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog#closedby
|
|
698
|
+
const hasClosedBySupport = HTMLDialogElement && 'closedBy' in HTMLDialogElement.prototype;
|
|
699
|
+
if (hasClosedBySupport) {
|
|
700
|
+
// @ts-ignore - closedBy is a newer property not in all TypeScript versions
|
|
701
|
+
wrappingDialogElement.closedBy = 'none';
|
|
702
|
+
} else {
|
|
703
|
+
wrappingDialogElement.addEventListener(
|
|
704
|
+
'keydown',
|
|
705
|
+
(/** @type {* & KeyboardEvent} */ event) => {
|
|
706
|
+
if (event.key === 'Escape') {
|
|
707
|
+
event.preventDefault();
|
|
708
|
+
}
|
|
709
|
+
},
|
|
710
|
+
);
|
|
711
|
+
wrappingDialogElement.addEventListener(
|
|
712
|
+
'keyup',
|
|
713
|
+
(/** @type {* & KeyboardEvent} */ event) => {
|
|
714
|
+
if (event.key === 'Escape') {
|
|
715
|
+
event.preventDefault();
|
|
716
|
+
}
|
|
717
|
+
},
|
|
718
|
+
);
|
|
719
|
+
wrappingDialogElement.addEventListener('cancel', event => {
|
|
720
|
+
event.stopPropagation();
|
|
721
|
+
});
|
|
722
|
+
wrappingDialogElement.addEventListener('close', event => {
|
|
723
|
+
event.stopPropagation();
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
} else {
|
|
727
|
+
// quick hack to make none-overlay flows work...
|
|
728
|
+
this.__contentWrapperNode = this.contentNode;
|
|
729
|
+
this.__wrappingDialogNode = this.contentNode;
|
|
634
730
|
}
|
|
635
731
|
|
|
636
732
|
this.__wrappingDialogNode.style.display = 'none';
|
|
637
|
-
this.contentWrapperNode.style.zIndex = '1';
|
|
638
|
-
|
|
639
|
-
if (getComputedStyle(this.contentNode).position === 'absolute') {
|
|
640
|
-
// Having a _contWrapperNode and a contentNode with 'position:absolute' results in
|
|
641
|
-
// computed height of 0...
|
|
642
|
-
this.contentNode.style.position = 'static';
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
// Here we prevent any interference of the native <dialog> element with the keyboard behavior
|
|
646
|
-
// as defined by the OverlayController. This is needed until we can configure `closedby="none"`
|
|
647
|
-
// on the native dialog for all browsers: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog#closedby
|
|
648
|
-
const hasClosedBySupport = HTMLDialogElement && 'closedBy' in HTMLDialogElement.prototype;
|
|
649
|
-
if (hasClosedBySupport) {
|
|
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
|
-
}
|
|
673
733
|
}
|
|
674
734
|
|
|
675
735
|
/**
|
|
@@ -740,6 +800,7 @@ export class OverlayController extends EventTarget {
|
|
|
740
800
|
* @private
|
|
741
801
|
*/
|
|
742
802
|
__storeOriginalAttrs(node, attrs) {
|
|
803
|
+
/** @type {Record<string, string | null>} */
|
|
743
804
|
const attrMap = {};
|
|
744
805
|
attrs.forEach(attrName => {
|
|
745
806
|
attrMap[attrName] = node.getAttribute(attrName);
|
|
@@ -809,6 +870,11 @@ export class OverlayController extends EventTarget {
|
|
|
809
870
|
contentNode: this.contentNode,
|
|
810
871
|
});
|
|
811
872
|
}
|
|
873
|
+
|
|
874
|
+
if (this.config.focusContentOnOpen) {
|
|
875
|
+
this.contentNode.focus();
|
|
876
|
+
}
|
|
877
|
+
|
|
812
878
|
/** @type {function} */
|
|
813
879
|
(this._showResolve)();
|
|
814
880
|
}
|
|
@@ -841,6 +907,7 @@ export class OverlayController extends EventTarget {
|
|
|
841
907
|
await this.__createPopperInstance();
|
|
842
908
|
this._popper.forceUpdate();
|
|
843
909
|
}
|
|
910
|
+
// N.B. leave it up to the user if placementMode === 'custom'
|
|
844
911
|
}
|
|
845
912
|
|
|
846
913
|
/**
|
|
@@ -852,46 +919,7 @@ export class OverlayController extends EventTarget {
|
|
|
852
919
|
return;
|
|
853
920
|
}
|
|
854
921
|
|
|
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
|
-
}
|
|
922
|
+
this.manager.requestToKeepBodySize({ phase });
|
|
895
923
|
}
|
|
896
924
|
|
|
897
925
|
/**
|
|
@@ -899,6 +927,9 @@ export class OverlayController extends EventTarget {
|
|
|
899
927
|
* @event hide right after the overlay is hidden
|
|
900
928
|
*/
|
|
901
929
|
async hide() {
|
|
930
|
+
// Function like a no-op for dynamic edge cases...
|
|
931
|
+
if (!this.config.isOpenable) return;
|
|
932
|
+
|
|
902
933
|
this._hideComplete = new Promise(resolve => {
|
|
903
934
|
this._hideResolve = resolve;
|
|
904
935
|
});
|
|
@@ -1050,6 +1081,15 @@ export class OverlayController extends EventTarget {
|
|
|
1050
1081
|
if (this.visibilityTriggerFunction) {
|
|
1051
1082
|
this._handleVisibilityTriggers({ phase });
|
|
1052
1083
|
}
|
|
1084
|
+
|
|
1085
|
+
if (phase === 'init' && this.config.focusContentOnOpen) {
|
|
1086
|
+
this.contentNode?.setAttribute('tabindex', '-1');
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
if (phase === 'teardown' && this.config.focusContentOnOpen) {
|
|
1090
|
+
// TODO: capture initial attr, use Resettable mechanism of VisibilityToggleCtrl in whole Controller
|
|
1091
|
+
this.contentNode?.removeAttribute('tabindex');
|
|
1092
|
+
}
|
|
1053
1093
|
}
|
|
1054
1094
|
|
|
1055
1095
|
/**
|
|
@@ -1223,7 +1263,9 @@ export class OverlayController extends EventTarget {
|
|
|
1223
1263
|
if (phase === 'show') {
|
|
1224
1264
|
this.#handleShiftKeyPress();
|
|
1225
1265
|
this.#handleFocusInsideDialog();
|
|
1266
|
+
// @ts-ignore - HTMLDialogElement methods
|
|
1226
1267
|
this.__wrappingDialogNode?.close();
|
|
1268
|
+
// @ts-ignore - HTMLDialogElement methods
|
|
1227
1269
|
this.__wrappingDialogNode?.showModal();
|
|
1228
1270
|
/**
|
|
1229
1271
|
* At this moment `#handleFocusInsideDialog` should handle the focus.
|
|
@@ -1474,11 +1516,19 @@ export class OverlayController extends EventTarget {
|
|
|
1474
1516
|
}
|
|
1475
1517
|
|
|
1476
1518
|
teardown() {
|
|
1519
|
+
if (!this.#hasSetup) return;
|
|
1477
1520
|
this.__handleOverlayStyles({ phase: 'teardown' });
|
|
1521
|
+
if (this.isShown) {
|
|
1522
|
+
this._keepBodySize({ phase: 'teardown' });
|
|
1523
|
+
}
|
|
1478
1524
|
this._handleFeatures({ phase: 'teardown' });
|
|
1479
1525
|
if (this.#isRegisteredOnManager()) {
|
|
1480
1526
|
this.manager.remove(this);
|
|
1481
1527
|
}
|
|
1528
|
+
|
|
1529
|
+
this.contentNode?.removeEventListener('click', this.#hideOnCloseButtonClick);
|
|
1530
|
+
|
|
1531
|
+
this.#hasSetup = false;
|
|
1482
1532
|
}
|
|
1483
1533
|
|
|
1484
1534
|
/** @private */
|
|
@@ -2,6 +2,18 @@ import { dedupeMixin } from '@open-wc/dedupe-mixin';
|
|
|
2
2
|
import { OverlayController } from './OverlayController.js';
|
|
3
3
|
import { isEqualConfig } from './utils/is-equal-config.js';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {<T>(ingOverlayHost:T) => void} PostProcessor
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** @type {PostProcessor[]} */
|
|
10
|
+
const overlayMixinPostProcessors = [];
|
|
11
|
+
|
|
12
|
+
/** @param {PostProcessor} postProcessor */
|
|
13
|
+
export function _addOverlayMixinPostProcessor(postProcessor) {
|
|
14
|
+
overlayMixinPostProcessors.push(postProcessor);
|
|
15
|
+
}
|
|
16
|
+
|
|
5
17
|
/**
|
|
6
18
|
* @typedef {import('../types/OverlayConfig.js').OverlayConfig} OverlayConfig
|
|
7
19
|
* @typedef {import('../types/OverlayMixinTypes.js').DefineOverlayConfig} DefineOverlayConfig
|
|
@@ -16,15 +28,12 @@ import { isEqualConfig } from './utils/is-equal-config.js';
|
|
|
16
28
|
* @type {OverlayMixin}
|
|
17
29
|
* @param {import('@open-wc/dedupe-mixin').Constructor<import('lit').LitElement>} superclass
|
|
18
30
|
*/
|
|
19
|
-
export const OverlayMixinImplementation = superclass =>
|
|
31
|
+
export const OverlayMixinImplementation = superclass => {
|
|
20
32
|
// @ts-ignore https://github.com/microsoft/TypeScript/issues/36821#issuecomment-588375051
|
|
21
33
|
class OverlayMixin extends superclass {
|
|
22
34
|
static get properties() {
|
|
23
35
|
return {
|
|
24
|
-
opened: {
|
|
25
|
-
type: Boolean,
|
|
26
|
-
reflect: true,
|
|
27
|
-
},
|
|
36
|
+
opened: { type: Boolean, reflect: true },
|
|
28
37
|
};
|
|
29
38
|
}
|
|
30
39
|
|
|
@@ -50,6 +59,17 @@ export const OverlayMixinImplementation = superclass =>
|
|
|
50
59
|
this.open = this.open.bind(this);
|
|
51
60
|
/** @type {EventListener} */
|
|
52
61
|
this.close = this.close.bind(this);
|
|
62
|
+
|
|
63
|
+
// // By default, we go for disclosure behavior
|
|
64
|
+
// // TODO: in the future, bring disclosure behavior to a controller (and therefore directive).
|
|
65
|
+
// // Take inspiration from VisibilityToggleCtrl of portal elements
|
|
66
|
+
// /**
|
|
67
|
+
// * Terminology aligned with https://open-ui.org/components/openable.explainer/
|
|
68
|
+
// * @type {'disclosure'|'overlay'}
|
|
69
|
+
// */
|
|
70
|
+
// this.openableMode = 'disclosure';
|
|
71
|
+
|
|
72
|
+
// allow hybrid disclosure/overlay components
|
|
53
73
|
}
|
|
54
74
|
|
|
55
75
|
get config() {
|
|
@@ -149,6 +169,8 @@ export const OverlayMixinImplementation = superclass =>
|
|
|
149
169
|
*/
|
|
150
170
|
// eslint-disable-next-line class-methods-use-this
|
|
151
171
|
_setupOpenCloseListeners() {
|
|
172
|
+
// Keeps the close-overlay event private for the controller.
|
|
173
|
+
// (we already have )
|
|
152
174
|
/**
|
|
153
175
|
* @param {{ stopPropagation: () => void; }} ev
|
|
154
176
|
*/
|
|
@@ -206,11 +228,11 @@ export const OverlayMixinImplementation = superclass =>
|
|
|
206
228
|
// @ts-expect-error
|
|
207
229
|
static enabledWarnings = super.enabledWarnings?.filter(w => w !== 'change-in-update') || [];
|
|
208
230
|
|
|
209
|
-
get _overlayInvokerNode() {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
231
|
+
// get _overlayInvokerNode() {
|
|
232
|
+
// return /** @type {HTMLElement | undefined} */ (
|
|
233
|
+
// Array.from(this.children).find(child => child.slot === 'invoker')
|
|
234
|
+
// );
|
|
235
|
+
// }
|
|
214
236
|
|
|
215
237
|
/**
|
|
216
238
|
* @overridable
|
|
@@ -244,13 +266,46 @@ export const OverlayMixinImplementation = superclass =>
|
|
|
244
266
|
);
|
|
245
267
|
}
|
|
246
268
|
|
|
269
|
+
/**
|
|
270
|
+
* @param {Element} focusableElOrWrapper
|
|
271
|
+
* @returns {Element|null}
|
|
272
|
+
*/
|
|
273
|
+
static _getFocusableInvokerEl(focusableElOrWrapper) {
|
|
274
|
+
// return focusableElOrWrapper;
|
|
275
|
+
return (
|
|
276
|
+
focusableElOrWrapper &&
|
|
277
|
+
(focusableElOrWrapper.hasAttribute('tabindex') || focusableElOrWrapper.tagName === 'BUTTON'
|
|
278
|
+
? focusableElOrWrapper
|
|
279
|
+
: focusableElOrWrapper.querySelector('[role=button], button'))
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
get _overlayInvokerNode() {
|
|
284
|
+
const ctor = /** @type {typeof OverlayMixin} */ (this.constructor);
|
|
285
|
+
if (!this.__invokerNode) {
|
|
286
|
+
const slottedNode = Array.from(this.children).find(child => child.slot === 'invoker');
|
|
287
|
+
if (slottedNode) {
|
|
288
|
+
this.__invokerNode = ctor._getFocusableInvokerEl(slottedNode);
|
|
289
|
+
} else {
|
|
290
|
+
// Look for preceeding sibling with [data-invoker] attribute, and try to find a focusable element in it (either itself or a child)
|
|
291
|
+
this.__invokerNode =
|
|
292
|
+
this.previousElementSibling?.hasAttribute('data-invoker') &&
|
|
293
|
+
ctor._getFocusableInvokerEl(this.previousElementSibling);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return this.__invokerNode;
|
|
297
|
+
}
|
|
298
|
+
|
|
247
299
|
/** @protected */
|
|
248
300
|
_setupOverlayCtrl() {
|
|
249
301
|
if (this.#hasSetup) return;
|
|
302
|
+
|
|
303
|
+
const invokerNode = this._overlayInvokerNode;
|
|
304
|
+
|
|
250
305
|
const config = {
|
|
251
306
|
contentNode: this._overlayContentNode,
|
|
252
307
|
contentWrapperNode: this._overlayContentWrapperNode,
|
|
253
|
-
invokerNode:
|
|
308
|
+
invokerNode: invokerNode && invokerNode instanceof HTMLElement ? invokerNode : undefined,
|
|
254
309
|
referenceNode: this._overlayReferenceNode,
|
|
255
310
|
backdropNode: this._overlayBackdropNode,
|
|
256
311
|
};
|
|
@@ -270,6 +325,8 @@ export const OverlayMixinImplementation = superclass =>
|
|
|
270
325
|
|
|
271
326
|
/** @protected */
|
|
272
327
|
_teardownOverlayCtrl() {
|
|
328
|
+
// Make sure that dynamic behavior (e.g. responsive) is possible by allowing multiple setups and teardowns of the overlay controller.
|
|
329
|
+
this.#hasSetup = false;
|
|
273
330
|
if (!this._overlayCtrl) return;
|
|
274
331
|
|
|
275
332
|
this._teardownOpenCloseListeners();
|
|
@@ -418,5 +475,11 @@ export const OverlayMixinImplementation = superclass =>
|
|
|
418
475
|
await this.updateComplete;
|
|
419
476
|
return !this.isConnected;
|
|
420
477
|
}
|
|
421
|
-
}
|
|
478
|
+
}
|
|
479
|
+
for (const postProcessor of overlayMixinPostProcessors) {
|
|
480
|
+
postProcessor(OverlayMixin);
|
|
481
|
+
}
|
|
482
|
+
// @ts-ignore https://github.com/microsoft/TypeScript/issues/36821#issuecomment-588375051
|
|
483
|
+
return OverlayMixin;
|
|
484
|
+
};
|
|
422
485
|
export const OverlayMixin = dedupeMixin(OverlayMixinImplementation);
|