@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,142 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
import { expect, html, fixture, unsafeStatic, defineCE } from '@open-wc/testing';
|
|
3
|
+
import { InteractiveListMixin } from '../src/InteractiveListMixin.js';
|
|
4
|
+
|
|
5
|
+
class InteractiveListClass extends InteractiveListMixin(LitElement) {}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {import('../types/InteractiveListMixinTypes.js').InteractiveListItemRole} InteractiveListItemRole
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** @type {InteractiveListItemRole[]} */
|
|
12
|
+
const supportedRoles = [
|
|
13
|
+
'menuitem',
|
|
14
|
+
'menuitemcheckbox',
|
|
15
|
+
'menuitemradio',
|
|
16
|
+
'option',
|
|
17
|
+
'treeitem',
|
|
18
|
+
'radio',
|
|
19
|
+
'checkbox',
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Object} TestConfig
|
|
24
|
+
* @property {string} tagString
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {TestConfig} [customConfig]
|
|
29
|
+
*/
|
|
30
|
+
export function runInteractiveListMixinSuite(customConfig) {
|
|
31
|
+
const cfg = {
|
|
32
|
+
tagString: customConfig?.tagString || defineCE(InteractiveListClass),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const { tagString } = cfg;
|
|
36
|
+
const tag = unsafeStatic(tagString);
|
|
37
|
+
|
|
38
|
+
describe('InteractiveListMixin', () => {
|
|
39
|
+
it(`supports items with role="${supportedRoles.join('|')}"`, async () => {
|
|
40
|
+
const el = await fixture(html`
|
|
41
|
+
<${tag} name="foo">
|
|
42
|
+
${supportedRoles.map(role => html` <div role="${role}"></div> `)}
|
|
43
|
+
</${tag}>
|
|
44
|
+
`);
|
|
45
|
+
// @ts-ignore - test element with InteractiveListMixin properties
|
|
46
|
+
expect(el.listItems.length).to.equal(supportedRoles.length);
|
|
47
|
+
supportedRoles.forEach((role, i) => {
|
|
48
|
+
// @ts-ignore - test element with InteractiveListMixin properties
|
|
49
|
+
expect(el.listItems[i].getAttribute('role')).to.equal(role);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('moves elements to a private [slot=list] for optimal DX and a11y', async () => {
|
|
54
|
+
const el = await fixture(html`
|
|
55
|
+
<${tag} name="foo">
|
|
56
|
+
<div role="menuitem" id="item1">Item 1</div>
|
|
57
|
+
<div role="menuitem" id="item2">Item 2</div>
|
|
58
|
+
</${tag}>
|
|
59
|
+
`);
|
|
60
|
+
// @ts-ignore - test element with InteractiveListMixin properties
|
|
61
|
+
const listSlotNode = Array.from(el.children).find(c => c.slot === 'list');
|
|
62
|
+
// @ts-ignore - accessing children property
|
|
63
|
+
const listItemEls = Array.from(listSlotNode.children);
|
|
64
|
+
// @ts-ignore - test element with InteractiveListMixin properties
|
|
65
|
+
expect(listItemEls.includes(el.listItems[0])).to.be.true;
|
|
66
|
+
// @ts-ignore - test element with InteractiveListMixin properties
|
|
67
|
+
expect(listItemEls.includes(el.listItems[1])).to.be.true;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('inherits from DisclosureMixin', async () => {});
|
|
71
|
+
|
|
72
|
+
describe('Active mode', async () => {
|
|
73
|
+
it('supports "activedescendant" pattern', async () => {
|
|
74
|
+
const el = await fixture(html`
|
|
75
|
+
<${tag} name="foo" ._activeMode="${'activedescendant'}">
|
|
76
|
+
<div role="menuitem" id="item1">Item 1</div>
|
|
77
|
+
<div role="menuitem" id="item2">Item 2</div>
|
|
78
|
+
</${tag}>
|
|
79
|
+
`);
|
|
80
|
+
// @ts-ignore - test element with InteractiveListMixin properties
|
|
81
|
+
el.checkedIndex = 0;
|
|
82
|
+
// @ts-ignore - test element with InteractiveListMixin properties
|
|
83
|
+
const listSlotNode = Array.from(el.children).find(c => c.slot === 'list');
|
|
84
|
+
|
|
85
|
+
// @ts-ignore - test element with InteractiveListMixin properties
|
|
86
|
+
// @ts-ignore - test element with InteractiveListMixin properties
|
|
87
|
+
expect(el.listItems[0].hasAttribute('id')).to.be.true;
|
|
88
|
+
// @ts-ignore - test element with InteractiveListMixin properties
|
|
89
|
+
expect(el.listItems[1].hasAttribute('id')).to.be.true;
|
|
90
|
+
expect(listSlotNode?.hasAttribute('aria-activedescendant')).to.be.false;
|
|
91
|
+
// @ts-ignore - test element with InteractiveListMixin properties
|
|
92
|
+
el.activeIndex = 1;
|
|
93
|
+
expect(listSlotNode?.hasAttribute('aria-activedescendant')).to.be.true;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('supports "roving-tabindex" pattern', async () => {
|
|
97
|
+
const el = await fixture(html`
|
|
98
|
+
<${tag} name="foo" ._activeMode="${'roving-tabindex'}">
|
|
99
|
+
<div role="menuitem" id="item1">Item 1</div>
|
|
100
|
+
<div role="menuitem" id="item2">Item 2</div>
|
|
101
|
+
</${tag}>
|
|
102
|
+
`);
|
|
103
|
+
|
|
104
|
+
// @ts-ignore - test element with InteractiveListMixin properties
|
|
105
|
+
el.activeIndex = 0;
|
|
106
|
+
// @ts-ignore - test element with InteractiveListMixin properties
|
|
107
|
+
expect(el.listItems[0].getAttribute('tabindex')).to.equal('0');
|
|
108
|
+
// @ts-ignore - test element with InteractiveListMixin properties
|
|
109
|
+
expect(el.listItems[1].getAttribute('tabindex')).to.equal('-1');
|
|
110
|
+
|
|
111
|
+
// @ts-ignore - test element with InteractiveListMixin properties
|
|
112
|
+
el.activeIndex = 1;
|
|
113
|
+
// @ts-ignore - test element with InteractiveListMixin properties
|
|
114
|
+
expect(el.listItems[0].getAttribute('tabindex')).to.equal('-1');
|
|
115
|
+
// @ts-ignore - test element with InteractiveListMixin properties
|
|
116
|
+
expect(el.listItems[1].getAttribute('tabindex')).to.equal('0');
|
|
117
|
+
|
|
118
|
+
// @ts-ignore - test element with InteractiveListMixin properties
|
|
119
|
+
el.activeIndex = 1;
|
|
120
|
+
// @ts-ignore - test element with InteractiveListMixin properties
|
|
121
|
+
expect(document.activeElement).to.equal(el.listItems[1]);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Copy tests from ListboxMixin. Later, make this mixin a fundament of the ListboxMixin
|
|
126
|
+
|
|
127
|
+
// orientation
|
|
128
|
+
// multipleChoice
|
|
129
|
+
// selectionFollowsFocus
|
|
130
|
+
// rotateKeyboardNavigation
|
|
131
|
+
// noPreselect
|
|
132
|
+
// _scrollTargetNode
|
|
133
|
+
|
|
134
|
+
describe('Widget extensions', () => {
|
|
135
|
+
it('can be extended to [role=listbox]', async () => {});
|
|
136
|
+
it('can be extended to [role=menu]', async () => {});
|
|
137
|
+
it('can be extended to [role=menubar]', async () => {});
|
|
138
|
+
it('can be extended to [role=toolbar]', async () => {});
|
|
139
|
+
it('can be extended to [role=tree]', async () => {});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Constructor } from '@open-wc/dedupe-mixin';
|
|
2
|
+
import { LitElement } from 'lit';
|
|
3
|
+
import { SlotHost } from '../../core/types/SlotMixinTypes.js';
|
|
4
|
+
import { DisabledHost } from '../../core/types/DisabledMixinTypes.js';
|
|
5
|
+
|
|
6
|
+
export type InteractiveListItemRole =
|
|
7
|
+
| 'menuitem'
|
|
8
|
+
| 'menuitemcheckbox'
|
|
9
|
+
| 'menuitemradio'
|
|
10
|
+
| 'option'
|
|
11
|
+
| 'treeitem'
|
|
12
|
+
| 'radio'
|
|
13
|
+
| 'checkbox';
|
|
14
|
+
|
|
15
|
+
export declare class InteractiveListHost {
|
|
16
|
+
/**
|
|
17
|
+
* When true, will synchronize activedescendant and selected element on
|
|
18
|
+
* arrow key navigation.
|
|
19
|
+
* This behavior can usually be seen on <select> on the Windows platform.
|
|
20
|
+
* Note that this behavior cannot be used when multiple-choice is true.
|
|
21
|
+
* See: https://www.w3.org/TR/wai-aria-practices/#kbd_selection_follows_focus
|
|
22
|
+
*/
|
|
23
|
+
public selectionFollowsFocus: Boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Will give first option active state when navigated to the next option from
|
|
26
|
+
* last option.
|
|
27
|
+
*/
|
|
28
|
+
public rotateKeyboardNavigation: Boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Informs screen reader and affects keyboard navigation.
|
|
31
|
+
* By default 'vertical'
|
|
32
|
+
*/
|
|
33
|
+
public orientation: 'vertical' | 'horizontal';
|
|
34
|
+
|
|
35
|
+
public hasNoDefaultSelected: boolean;
|
|
36
|
+
|
|
37
|
+
public singleOption: boolean;
|
|
38
|
+
|
|
39
|
+
public checkedIndex: number | number[];
|
|
40
|
+
|
|
41
|
+
public activeIndex: number;
|
|
42
|
+
|
|
43
|
+
public listItems: HTMLElement[];
|
|
44
|
+
|
|
45
|
+
public setCheckedIndex(index: number): void;
|
|
46
|
+
|
|
47
|
+
protected _scrollTargetNode: HTMLElement;
|
|
48
|
+
|
|
49
|
+
protected _listNode: HTMLElement;
|
|
50
|
+
|
|
51
|
+
// private __setupListboxNode(): void;
|
|
52
|
+
|
|
53
|
+
protected _getPreviousEnabledOption(currentIndex: number, offset?: number): number;
|
|
54
|
+
|
|
55
|
+
protected _getNextEnabledOption(currentIndex: number, offset?: number): number;
|
|
56
|
+
|
|
57
|
+
protected _onListKeyDown(ev: KeyboardEvent): void;
|
|
58
|
+
|
|
59
|
+
protected _onListKeyUp(ev: KeyboardEvent): void;
|
|
60
|
+
|
|
61
|
+
// protected _setupListboxNode(): void;
|
|
62
|
+
|
|
63
|
+
// protected _teardownListboxNode(): void;
|
|
64
|
+
|
|
65
|
+
protected _onListClick(ev: MouseEvent): void;
|
|
66
|
+
|
|
67
|
+
// protected _setupListboxInteractions(): void;
|
|
68
|
+
|
|
69
|
+
// protected _onChildActiveChanged(ev: Event): void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export declare function InteractiveListHostImplementation<T extends Constructor<LitElement>>(
|
|
73
|
+
superclass: T,
|
|
74
|
+
): T &
|
|
75
|
+
Constructor<InteractiveListHost> &
|
|
76
|
+
InteractiveListHost &
|
|
77
|
+
Constructor<DisabledHost> &
|
|
78
|
+
typeof DisabledHost &
|
|
79
|
+
Constructor<SlotHost> &
|
|
80
|
+
typeof SlotHost;
|
|
81
|
+
|
|
82
|
+
export type InteractiveListMixin = typeof InteractiveListHostImplementation;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
N.B. api is very very beta
|
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import { LionMenuHybrid } from '@lion/ui/menu.js';
|
|
3
|
+
import { ScopedElementsMixin } from '../../core/src/ScopedElementsMixin.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {import('./types.js').NavBarResponsiveMode} NavBarResponsiveMode
|
|
7
|
+
* @typedef {import('./types.js').MenuItem} MenuItem
|
|
8
|
+
* @typedef {import('./types.js').CtaLink} CtaLink
|
|
9
|
+
* @typedef {import('./types.js').Logo} Logo
|
|
10
|
+
* @typedef {import('./types.js').LevelConfig} LevelConfig
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export class LionNavigationBar extends ScopedElementsMixin(LitElement) {
|
|
14
|
+
static get properties() {
|
|
15
|
+
return {
|
|
16
|
+
responsiveMode: { type: String, reflect: true, attribute: 'responsive-mode' },
|
|
17
|
+
// logo: { type: String },
|
|
18
|
+
menuItems: { type: Array },
|
|
19
|
+
menuSupportItems: { type: Array },
|
|
20
|
+
ctaPrimary: { type: Object },
|
|
21
|
+
ctaSecondary: { type: Object },
|
|
22
|
+
suggestions: { type: Array },
|
|
23
|
+
showOnMobile: { type: Array, attribute: 'show-on-mobile', reflect: true },
|
|
24
|
+
|
|
25
|
+
_levelCfg: { state: true },
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static get scopedElements() {
|
|
30
|
+
return {
|
|
31
|
+
// 'lion-menu-overlay': LionMenuOverlay,
|
|
32
|
+
'lion-menu-hybrid': LionMenuHybrid,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static get styles() {
|
|
37
|
+
return [
|
|
38
|
+
css`
|
|
39
|
+
/** specific styles */
|
|
40
|
+
|
|
41
|
+
:host([responsive-mode='desktop']) [data-has-full-width-flyout] > [slot='list'] {
|
|
42
|
+
/** make sure that we give the right styles */
|
|
43
|
+
position: relative;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
:host([responsive-mode='desktop'])
|
|
47
|
+
[data-has-full-width-flyout]
|
|
48
|
+
> [slot='list']
|
|
49
|
+
[level='2']
|
|
50
|
+
> [slot='list'] {
|
|
51
|
+
position: absolute;
|
|
52
|
+
width: 100%;
|
|
53
|
+
left: 0;
|
|
54
|
+
right: 0;
|
|
55
|
+
min-height: 300px;
|
|
56
|
+
padding: 1em;
|
|
57
|
+
background-color: white;
|
|
58
|
+
border: 1px solid gray;
|
|
59
|
+
box-sizing: border-box;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
:host([responsive-mode='desktop']) [level='3'] > [slot='list'] {
|
|
63
|
+
position: absolute;
|
|
64
|
+
left: 33.333%;
|
|
65
|
+
width: 33.333%;
|
|
66
|
+
top: 0;
|
|
67
|
+
padding: 1em;
|
|
68
|
+
background-color: white;
|
|
69
|
+
box-sizing: border-box;
|
|
70
|
+
height: 100%;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
:host([responsive-mode='mobile']) nav {
|
|
74
|
+
border: 1px solid black;
|
|
75
|
+
display: block;
|
|
76
|
+
position: fixed;
|
|
77
|
+
top: 0;
|
|
78
|
+
bottom: 0;
|
|
79
|
+
right: 0;
|
|
80
|
+
min-width: 500px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
:host([responsive-mode='mobile']) [level][opened] > [slot='list'] {
|
|
84
|
+
left: 0;
|
|
85
|
+
width: 100%;
|
|
86
|
+
top: 0;
|
|
87
|
+
box-sizing: border-box;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
:host([responsive-mode='mobile']) [slot='list'] {
|
|
91
|
+
position: relative;
|
|
92
|
+
padding: 1em;
|
|
93
|
+
background-color: white;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
:host([responsive-mode='mobile']) [level='2'] > [slot='list'],
|
|
97
|
+
:host([responsive-mode='mobile']) [level='3'] > [slot='list'] {
|
|
98
|
+
position: absolute;
|
|
99
|
+
left: 100%;
|
|
100
|
+
top: 0;
|
|
101
|
+
box-sizing: border-box;
|
|
102
|
+
padding-bottom: 200%;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
:host([responsive-mode='mobile']) nav {
|
|
106
|
+
display: flex;
|
|
107
|
+
flex-direction: column;
|
|
108
|
+
/* height: 100%;
|
|
109
|
+
justify-content: space-between; */
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* :host([responsive-mode='mobile']) .cta-items {
|
|
113
|
+
position: absolute;
|
|
114
|
+
bottom: 0;
|
|
115
|
+
width: 100%;
|
|
116
|
+
padding: 1em;
|
|
117
|
+
background-color: white;
|
|
118
|
+
box-sizing: border-box;
|
|
119
|
+
} */
|
|
120
|
+
|
|
121
|
+
:host([responsive-mode='mobile']) .logo-and-close {
|
|
122
|
+
padding: 1em;
|
|
123
|
+
}
|
|
124
|
+
`,
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
constructor() {
|
|
129
|
+
super();
|
|
130
|
+
|
|
131
|
+
/** @type {Logo} */
|
|
132
|
+
this.logo = { type: 'ing', alt: 'ING homepage', href: '/' };
|
|
133
|
+
/** @type {MenuItem[]} */
|
|
134
|
+
this.menuSupportItems = [];
|
|
135
|
+
// /**
|
|
136
|
+
// * @type {string[]}
|
|
137
|
+
// * @description Choose which items to show on mobile when in default mode, maximum of two. Possible values: 'search', 'ctaPrimary', 'ctaSecondary', 'avatarMenu'
|
|
138
|
+
// */
|
|
139
|
+
// this.showOnMobile = [];
|
|
140
|
+
/**
|
|
141
|
+
* @type {CtaLink}
|
|
142
|
+
*/
|
|
143
|
+
// @ts-ignore
|
|
144
|
+
this.ctaPrimary = {};
|
|
145
|
+
/**
|
|
146
|
+
* @type {CtaLink}
|
|
147
|
+
*/
|
|
148
|
+
// @ts-ignore
|
|
149
|
+
this.ctaSecondary = {};
|
|
150
|
+
/** @type {MenuItem[]} */
|
|
151
|
+
this.menuItems = [];
|
|
152
|
+
// /** @type {Boolean} */
|
|
153
|
+
// this.searchDisabled = false;
|
|
154
|
+
this.breakpointMin = 1400; // force desktop
|
|
155
|
+
/** @type {string[]} */
|
|
156
|
+
this.suggestions = [];
|
|
157
|
+
|
|
158
|
+
this.prefilledSuggestion = '';
|
|
159
|
+
|
|
160
|
+
// Assign styles as a property, so that subclasses can augment it
|
|
161
|
+
// this.styles = styles;
|
|
162
|
+
// this.menuOpened = false;
|
|
163
|
+
|
|
164
|
+
const mql = window.matchMedia(`(width <= ${this.breakpointMin}px)`);
|
|
165
|
+
/** @type {NavBarResponsiveMode} */
|
|
166
|
+
this.responsiveMode = mql.matches ? 'mobile' : 'desktop';
|
|
167
|
+
this._levelCfg = LionNavigationBar.#getLevelCfg(this.responsiveMode);
|
|
168
|
+
this._levelSecondaryCfg = this.#getSecondaryLevelCfg();
|
|
169
|
+
|
|
170
|
+
mql.addEventListener('change', () => {
|
|
171
|
+
this.responsiveMode = mql.matches ? 'mobile' : 'desktop';
|
|
172
|
+
this._levelCfg = LionNavigationBar.#getLevelCfg(this.responsiveMode);
|
|
173
|
+
this._levelSecondaryCfg = this.#getSecondaryLevelCfg();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
this.#storeLatestFocusedElementId();
|
|
177
|
+
this.#delegateAnalyticsEvents();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#getSecondaryLevelCfg() {
|
|
181
|
+
if (this.responsiveMode === 'mobile') {
|
|
182
|
+
return {
|
|
183
|
+
l1: {
|
|
184
|
+
isBar: true,
|
|
185
|
+
},
|
|
186
|
+
l2: {
|
|
187
|
+
isBar: false,
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
l1: {
|
|
194
|
+
isBar: true,
|
|
195
|
+
},
|
|
196
|
+
l2: {
|
|
197
|
+
isBar: false,
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** @type {string | null} */
|
|
203
|
+
#latestFocusedElementId = null;
|
|
204
|
+
|
|
205
|
+
#storeLatestFocusedElementId() {
|
|
206
|
+
this.addEventListener('focusin', ({ target }) => {
|
|
207
|
+
// @ts-ignore
|
|
208
|
+
this.#latestFocusedElementId = target.id;
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
#syncLatestFocusedElementForNewResponsiveMode() {
|
|
213
|
+
// We might show/hide or rerender some elements (with same ids) in different positions in the DOM.
|
|
214
|
+
// As these are physically different elements, we must move focus (as otherwise it would be reset to body)
|
|
215
|
+
if (!this.#latestFocusedElementId) return;
|
|
216
|
+
const newFocusedElement = this.shadowRoot?.querySelector(`#${this.#latestFocusedElementId}`);
|
|
217
|
+
if (newFocusedElement instanceof HTMLElement) {
|
|
218
|
+
newFocusedElement.focus();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
#delegateAnalyticsEvents() {
|
|
223
|
+
this.addEventListener('click', ({ target }) => {
|
|
224
|
+
if (!target || !(target instanceof HTMLElement)) return;
|
|
225
|
+
|
|
226
|
+
// @ts-expect-error
|
|
227
|
+
const isButton = target.tagName === 'BUTTON' || target.constructor._$isLionButton$;
|
|
228
|
+
const isAnchor = target.tagName === 'A';
|
|
229
|
+
if (!isButton && !isAnchor) return;
|
|
230
|
+
|
|
231
|
+
if (!target.id) return;
|
|
232
|
+
|
|
233
|
+
// TODO: do in extension layer
|
|
234
|
+
this.dispatchEvent(
|
|
235
|
+
new CustomEvent('nav-item-click', {
|
|
236
|
+
// N.B. we can also retrive the item
|
|
237
|
+
detail: {
|
|
238
|
+
item: {
|
|
239
|
+
id: target.id,
|
|
240
|
+
label: target.textContent?.trim() || '',
|
|
241
|
+
href: isAnchor ? target.getAttribute('href') : undefined,
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
// bubbles: true,
|
|
245
|
+
// composed: true,
|
|
246
|
+
}),
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* @param {NavBarResponsiveMode} responsiveMode
|
|
253
|
+
*/
|
|
254
|
+
static #getLevelCfg(responsiveMode) {
|
|
255
|
+
// N.B. we're fighting some of the overlay configs provided by LionMenuOverlay here,
|
|
256
|
+
// the idea is to take over all of that when depending on LionMenu, after removing LonMenuOverlay and LionMenuHybrid components altogether.
|
|
257
|
+
|
|
258
|
+
if (responsiveMode === 'mobile') {
|
|
259
|
+
return {
|
|
260
|
+
l1: {
|
|
261
|
+
isBar: false,
|
|
262
|
+
openableConfig: {
|
|
263
|
+
// We want to do the absolute positioning ourselves, popper is not suited for this...
|
|
264
|
+
placementMode: 'custom',
|
|
265
|
+
isOpenable: false,
|
|
266
|
+
hidesOnOutsideClick: false,
|
|
267
|
+
inheritsReferenceWidth: 'none',
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
l2: {
|
|
271
|
+
openableConfig: {
|
|
272
|
+
// We want disclosure behavior
|
|
273
|
+
placementMode: 'custom',
|
|
274
|
+
hidesOnOutsideClick: false,
|
|
275
|
+
inheritsReferenceWidth: 'none',
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
l3: {
|
|
279
|
+
openableConfig: {
|
|
280
|
+
// We want disclosure behavior
|
|
281
|
+
placementMode: 'custom',
|
|
282
|
+
hidesOnOutsideClick: false,
|
|
283
|
+
inheritsReferenceWidth: 'none',
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
l1: {
|
|
291
|
+
isBar: true,
|
|
292
|
+
hasFullWidthFlyout: true,
|
|
293
|
+
openableConfig: {
|
|
294
|
+
// We want disclosure behavior
|
|
295
|
+
placementMode: 'custom',
|
|
296
|
+
// N.B. we don't have an invoker...
|
|
297
|
+
isOpenable: false,
|
|
298
|
+
inheritsReferenceWidth: 'none',
|
|
299
|
+
focusContentOnOpen: true,
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
l2: {
|
|
303
|
+
openableConfig: {
|
|
304
|
+
// We want disclosure behavior
|
|
305
|
+
placementMode: 'custom',
|
|
306
|
+
hidesOnOutsideClick: true,
|
|
307
|
+
hidesOnEscape: true,
|
|
308
|
+
inheritsReferenceWidth: 'none',
|
|
309
|
+
focusContentOnOpen: true,
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
l3: {
|
|
313
|
+
openableConfig: {
|
|
314
|
+
// We want disclosure behavior
|
|
315
|
+
placementMode: 'custom',
|
|
316
|
+
inheritsReferenceWidth: 'none',
|
|
317
|
+
focusContentOnOpen: true,
|
|
318
|
+
// TODO: implement in OverlayController
|
|
319
|
+
hideVisually: true,
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* @param {import('lit').PropertyValues} changedProperties
|
|
327
|
+
*/
|
|
328
|
+
updated(changedProperties) {
|
|
329
|
+
if (changedProperties.has('responsiveMode')) {
|
|
330
|
+
this._levelCfg = LionNavigationBar.#getLevelCfg(this.responsiveMode);
|
|
331
|
+
this.#syncLatestFocusedElementForNewResponsiveMode();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
render() {
|
|
336
|
+
return html`
|
|
337
|
+
<nav>
|
|
338
|
+
<div class="nav-header">
|
|
339
|
+
<div class="logo-and-close">
|
|
340
|
+
<a href="${this.logo.href || '#'}">
|
|
341
|
+
<img src="${this.logo.src || ''}" alt="${this.logo.alt || ''}" />
|
|
342
|
+
</a>
|
|
343
|
+
${this.responsiveMode === 'mobile'
|
|
344
|
+
? html`<button data-close data-level="1">✖️</button>`
|
|
345
|
+
: ''}
|
|
346
|
+
</div>
|
|
347
|
+
${LionNavigationBar._searchTemplate(this.suggestions, this.prefilledSuggestion)}
|
|
348
|
+
</div>
|
|
349
|
+
<div class="nav-body">
|
|
350
|
+
${this._menulevelTemplate(this.menuSupportItems, 1, this._levelSecondaryCfg.l1, '')}
|
|
351
|
+
${this.responsiveMode === 'desktop'
|
|
352
|
+
? LionNavigationBar._ctaTemplate(this.ctaPrimary, this.ctaSecondary)
|
|
353
|
+
: ''}
|
|
354
|
+
${this._menulevelTemplate(this.menuItems, 1, this._levelCfg.l1, '')}
|
|
355
|
+
</div>
|
|
356
|
+
${this.responsiveMode === 'mobile'
|
|
357
|
+
? html`<div class="nav-footer">
|
|
358
|
+
${LionNavigationBar._ctaTemplate(this.ctaPrimary, this.ctaSecondary)}
|
|
359
|
+
</div>`
|
|
360
|
+
: ''}
|
|
361
|
+
</nav>
|
|
362
|
+
`;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* @param {string[]} suggestions
|
|
367
|
+
* @returns {import('lit').TemplateResult}
|
|
368
|
+
*/
|
|
369
|
+
static _searchTemplate(suggestions, prefilledSuggestion = '') {
|
|
370
|
+
return html` <form role="search">
|
|
371
|
+
<input
|
|
372
|
+
type="search"
|
|
373
|
+
list="search-list"
|
|
374
|
+
placeholder="What are you looking for?"
|
|
375
|
+
.value="${prefilledSuggestion}"
|
|
376
|
+
/>
|
|
377
|
+
<datalist id="search-list">
|
|
378
|
+
${suggestions.map(suggestion => html`<option value="${suggestion}"></option>`)}
|
|
379
|
+
</datalist>
|
|
380
|
+
<input type="submit" value="Search" />
|
|
381
|
+
</form>`;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* @param {CtaLink} ctaPrimary
|
|
386
|
+
* @param {CtaLink} ctaSecondary
|
|
387
|
+
* @returns {import('lit').TemplateResult}
|
|
388
|
+
*/
|
|
389
|
+
static _ctaTemplate(ctaPrimary, ctaSecondary) {
|
|
390
|
+
return html`<div class="cta-items">
|
|
391
|
+
${ctaSecondary?.href
|
|
392
|
+
? html`<div class="cta-secondary">
|
|
393
|
+
<a id="${ctaSecondary.id}" href="${ctaSecondary.href}">${ctaSecondary.label} </a>
|
|
394
|
+
</div>`
|
|
395
|
+
: ''}
|
|
396
|
+
${ctaPrimary?.href
|
|
397
|
+
? html`<div class="cta-primary">
|
|
398
|
+
<a id="${ctaPrimary.id}" href="${ctaPrimary.href}">${ctaPrimary.label} </a>
|
|
399
|
+
</div>`
|
|
400
|
+
: ''}
|
|
401
|
+
</div>`;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* @param {MenuItem[]} menuItemsForLevel
|
|
406
|
+
* @param {number} level
|
|
407
|
+
* @param {LevelConfig} cfgForLevel
|
|
408
|
+
* @returns {import('lit').TemplateResult}
|
|
409
|
+
*/
|
|
410
|
+
_menulevelTemplate(menuItemsForLevel, level, cfgForLevel, prevText = '') {
|
|
411
|
+
// @ts-ignore
|
|
412
|
+
const cfgForNextLevel = this._levelCfg[`l${level + 1}`];
|
|
413
|
+
|
|
414
|
+
const getId = (/** @type {import("./types.js").MenuItem} */ menuItem) =>
|
|
415
|
+
menuItem.id || `l${level}-${menuItem.title}`;
|
|
416
|
+
|
|
417
|
+
return html`<lion-menu-hybrid
|
|
418
|
+
.config="${cfgForLevel.openableConfig || {}}"
|
|
419
|
+
.bar="${cfgForLevel.isBar}"
|
|
420
|
+
?data-has-full-width-flyout="${cfgForLevel.hasFullWidthFlyout}"
|
|
421
|
+
._activeMode="${'tabbable-disclosure'}"
|
|
422
|
+
>
|
|
423
|
+
${level > 1 && this.responsiveMode === 'mobile'
|
|
424
|
+
? html` <div role="listitem">
|
|
425
|
+
<button data-close data-level="${level}">< ${prevText}</button>
|
|
426
|
+
</div>`
|
|
427
|
+
: ''}
|
|
428
|
+
${menuItemsForLevel.map(
|
|
429
|
+
menuItem => html`
|
|
430
|
+
<div role="listitem">
|
|
431
|
+
${menuItem.sub
|
|
432
|
+
? html` <button id="${getId(menuItem)}" data-invoker>${menuItem.title}</button>
|
|
433
|
+
${menuItem.sub
|
|
434
|
+
? this._menulevelTemplate(
|
|
435
|
+
menuItem.sub,
|
|
436
|
+
level + 1,
|
|
437
|
+
cfgForNextLevel,
|
|
438
|
+
menuItem.title,
|
|
439
|
+
)
|
|
440
|
+
: ''}`
|
|
441
|
+
: html` <a id="${getId(menuItem)}" href="${menuItem.link || ''}"
|
|
442
|
+
>${menuItem.title}</a
|
|
443
|
+
>`}
|
|
444
|
+
</div>
|
|
445
|
+
`,
|
|
446
|
+
)}
|
|
447
|
+
</lion-menu-hybrid>`;
|
|
448
|
+
}
|
|
449
|
+
}
|