@lion/ui 0.22.0 → 0.23.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.
Files changed (218) hide show
  1. package/README.md +35 -0
  2. package/components/combobox/src/LionCombobox.js +10 -2
  3. package/components/combobox/test/lion-combobox.test.js +31 -0
  4. package/components/form-core/src/choice-group/ChoiceGroupMixin.js +7 -0
  5. package/components/form-core/types/choice-group/ChoiceGroupMixinTypes.ts +5 -0
  6. package/components/input-amount/src/LionInputAmount.js +17 -6
  7. package/components/input-amount/src/formatters.js +5 -3
  8. package/components/input-amount/test/formatters.test.js +88 -74
  9. package/components/input-amount/test/lion-input-amount.test.js +51 -36
  10. package/components/input-tel/test/validators.test.js +9 -0
  11. package/components/listbox/src/ListboxMixin.js +19 -3
  12. package/components/listbox/types/ListboxMixinTypes.ts +7 -1
  13. package/components/menu/README.md +360 -0
  14. package/components/menu/src/InteractiveListMixin.js +867 -0
  15. package/components/menu/src/LionMenu.js +436 -0
  16. package/components/menu/src/MoreButtonMenuMixin.js +295 -0
  17. package/components/menu/src/MultiLevelListMixin.js +312 -0
  18. package/components/menu/src/utils/isInView.js +26 -0
  19. package/components/menu/src/utils/listItemInteractions.js +124 -0
  20. package/components/menu/test/InteractiveListMixin.test.js +3 -0
  21. package/components/menu/test/MoreButtonMenuMixin.test.js +4 -0
  22. package/components/menu/test/MultiLevelListMixin.test.js +3 -0
  23. package/components/menu/test/lion-menu-interactions.test.js +3 -0
  24. package/components/menu/test/lion-menu.test.js +14 -0
  25. package/components/menu/test-helpers/mimicKeyPress.js +9 -0
  26. package/components/menu/test-suites/InteractiveListMixin.suite.js +537 -0
  27. package/components/menu/test-suites/LionMenu.suite.js +114 -0
  28. package/components/menu/test-suites/LionMenuInteractions.suite.js +583 -0
  29. package/components/menu/test-suites/MoreButtonMenuMixin.suite.js +372 -0
  30. package/components/menu/test-suites/MultiLevelListMixin.suite.js +309 -0
  31. package/components/menu/types/InteractiveListMixinTypes.ts +126 -0
  32. package/components/menu/types/MoreButtonMenuMixinTypes.ts +51 -0
  33. package/components/menu/types/MultiLevelListMixinTypes.ts +28 -0
  34. package/components/menu/types/OverlayWithListInvokerMixinTypes.ts +61 -0
  35. package/components/overlays/NEXT.md +49 -0
  36. package/components/overlays/src/OverlayController.js +304 -125
  37. package/components/overlays/src/OverlayMixin.js +81 -17
  38. package/components/overlays/src/utils/adopt-styles.js +1 -1
  39. package/components/overlays/src/utils/is-equal-config.js +15 -6
  40. package/components/overlays/test/OverlayController.test.js +223 -25
  41. package/components/overlays/test-suites/OverlayMixin.suite.js +8 -8
  42. package/components/overlays/types/ArrowMixinTypes.ts +1 -3
  43. package/components/overlays/types/OverlayConfig.ts +20 -5
  44. package/components/overlays/types/OverlayMixinTypes.ts +2 -0
  45. package/components/select-rich/src/LionSelectRich.js +12 -0
  46. package/components/select-rich/test/lion-select-rich-interaction.test.js +53 -0
  47. package/components/select-rich/test/lion-select-rich-upgrade.test.js +30 -0
  48. package/custom-elements.json +3120 -149
  49. package/dist-types/components/combobox/src/LionCombobox.d.ts +1 -1
  50. package/dist-types/components/combobox/src/LionCombobox.d.ts.map +1 -1
  51. package/dist-types/components/dialog/src/LionDialog.d.ts +6 -2
  52. package/dist-types/components/dialog/src/LionDialog.d.ts.map +1 -1
  53. package/dist-types/components/form-core/src/choice-group/ChoiceGroupMixin.d.ts.map +1 -1
  54. package/dist-types/components/form-core/types/choice-group/ChoiceGroupMixinTypes.d.ts +5 -0
  55. package/dist-types/components/form-core/types/choice-group/ChoiceGroupMixinTypes.d.ts.map +1 -1
  56. package/dist-types/components/input-amount/src/LionInputAmount.d.ts +3 -1
  57. package/dist-types/components/input-amount/src/LionInputAmount.d.ts.map +1 -1
  58. package/dist-types/components/input-amount/src/formatters.d.ts +6 -4
  59. package/dist-types/components/input-amount/src/formatters.d.ts.map +1 -1
  60. package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts +1 -1
  61. package/dist-types/components/listbox/src/ListboxMixin.d.ts.map +1 -1
  62. package/dist-types/components/listbox/types/ListboxMixinTypes.d.ts +7 -1
  63. package/dist-types/components/listbox/types/ListboxMixinTypes.d.ts.map +1 -1
  64. package/dist-types/components/menu/src/InteractiveListMixin.d.ts +35 -0
  65. package/dist-types/components/menu/src/InteractiveListMixin.d.ts.map +1 -0
  66. package/dist-types/components/menu/src/LionMenu.d.ts +129 -0
  67. package/dist-types/components/menu/src/LionMenu.d.ts.map +1 -0
  68. package/dist-types/components/menu/src/MoreButtonMenuMixin.d.ts +8 -0
  69. package/dist-types/components/menu/src/MoreButtonMenuMixin.d.ts.map +1 -0
  70. package/dist-types/components/menu/src/MultiLevelListMixin.d.ts +7 -0
  71. package/dist-types/components/menu/src/MultiLevelListMixin.d.ts.map +1 -0
  72. package/dist-types/components/menu/src/utils/isInView.d.ts +9 -0
  73. package/dist-types/components/menu/src/utils/isInView.d.ts.map +1 -0
  74. package/dist-types/components/menu/src/utils/listItemInteractions.d.ts +48 -0
  75. package/dist-types/components/menu/src/utils/listItemInteractions.d.ts.map +1 -0
  76. package/dist-types/components/menu/test/InteractiveListMixin.test.d.ts +2 -0
  77. package/dist-types/components/menu/test/InteractiveListMixin.test.d.ts.map +1 -0
  78. package/dist-types/components/menu/test/MoreButtonMenuMixin.test.d.ts +2 -0
  79. package/dist-types/components/menu/test/MoreButtonMenuMixin.test.d.ts.map +1 -0
  80. package/dist-types/components/menu/test/MultiLevelListMixin.test.d.ts +2 -0
  81. package/dist-types/components/menu/test/MultiLevelListMixin.test.d.ts.map +1 -0
  82. package/dist-types/components/menu/test/lion-menu-interactions.test.d.ts +2 -0
  83. package/dist-types/components/menu/test/lion-menu-interactions.test.d.ts.map +1 -0
  84. package/dist-types/components/menu/test/lion-menu.test.d.ts +2 -0
  85. package/dist-types/components/menu/test/lion-menu.test.d.ts.map +1 -0
  86. package/dist-types/components/menu/test-helpers/mimicKeyPress.d.ts +7 -0
  87. package/dist-types/components/menu/test-helpers/mimicKeyPress.d.ts.map +1 -0
  88. package/dist-types/components/menu/test-suites/InteractiveListMixin.suite.d.ts +11 -0
  89. package/dist-types/components/menu/test-suites/InteractiveListMixin.suite.d.ts.map +1 -0
  90. package/dist-types/components/menu/test-suites/LionMenu.suite.d.ts +9 -0
  91. package/dist-types/components/menu/test-suites/LionMenu.suite.d.ts.map +1 -0
  92. package/dist-types/components/menu/test-suites/LionMenuInteractions.suite.d.ts +5 -0
  93. package/dist-types/components/menu/test-suites/LionMenuInteractions.suite.d.ts.map +1 -0
  94. package/dist-types/components/menu/test-suites/MoreButtonMenuMixin.suite.d.ts +11 -0
  95. package/dist-types/components/menu/test-suites/MoreButtonMenuMixin.suite.d.ts.map +1 -0
  96. package/dist-types/components/menu/test-suites/MultiLevelListMixin.suite.d.ts +11 -0
  97. package/dist-types/components/menu/test-suites/MultiLevelListMixin.suite.d.ts.map +1 -0
  98. package/dist-types/components/menu/types/InteractiveListMixinTypes.d.ts +72 -0
  99. package/dist-types/components/menu/types/InteractiveListMixinTypes.d.ts.map +1 -0
  100. package/dist-types/components/menu/types/MoreButtonMenuMixinTypes.d.ts +30 -0
  101. package/dist-types/components/menu/types/MoreButtonMenuMixinTypes.d.ts.map +1 -0
  102. package/dist-types/components/menu/types/MultiLevelListMixinTypes.d.ts +16 -0
  103. package/dist-types/components/menu/types/MultiLevelListMixinTypes.d.ts.map +1 -0
  104. package/dist-types/components/menu/types/OverlayWithListInvokerMixinTypes.d.ts +35 -0
  105. package/dist-types/components/menu/types/OverlayWithListInvokerMixinTypes.d.ts.map +1 -0
  106. package/dist-types/components/overlays/src/OverlayController.d.ts +22 -5
  107. package/dist-types/components/overlays/src/OverlayController.d.ts.map +1 -1
  108. package/dist-types/components/overlays/src/OverlayMixin.d.ts.map +1 -1
  109. package/dist-types/components/overlays/src/utils/is-equal-config.d.ts.map +1 -1
  110. package/dist-types/components/overlays/types/ArrowMixinTypes.d.ts.map +1 -1
  111. package/dist-types/components/overlays/types/OverlayConfig.d.ts +12 -2
  112. package/dist-types/components/overlays/types/OverlayConfig.d.ts.map +1 -1
  113. package/dist-types/components/overlays/types/OverlayMixinTypes.d.ts +1 -0
  114. package/dist-types/components/overlays/types/OverlayMixinTypes.d.ts.map +1 -1
  115. package/dist-types/components/select-rich/src/LionSelectRich.d.ts +13 -2
  116. package/dist-types/components/select-rich/src/LionSelectRich.d.ts.map +1 -1
  117. package/dist-types/components/select-rich/test/lion-select-rich-interaction.test.d.ts.map +1 -1
  118. package/dist-types/components/select-rich/test/lion-select-rich-upgrade.test.d.ts +2 -0
  119. package/dist-types/components/select-rich/test/lion-select-rich-upgrade.test.d.ts.map +1 -0
  120. package/dist-types/components/tooltip/src/LionTooltip.d.ts +1 -1
  121. package/dist-types/exports/define/lion-item.d.ts +2 -0
  122. package/dist-types/exports/define/lion-item.d.ts.map +1 -0
  123. package/dist-types/exports/define/lion-menu.d.ts +2 -0
  124. package/dist-types/exports/define/lion-menu.d.ts.map +1 -0
  125. package/dist-types/exports/define/lion-menuitem.d.ts +2 -0
  126. package/dist-types/exports/define/lion-menuitem.d.ts.map +1 -0
  127. package/dist-types/exports/menu-test-suites.d.ts +4 -0
  128. package/dist-types/exports/menu-test-suites.d.ts.map +1 -0
  129. package/dist-types/exports/menu.d.ts +3 -0
  130. package/dist-types/exports/menu.d.ts.map +1 -0
  131. package/dist-types/exports/overlays.d.ts +6 -0
  132. package/dist-types/exports/overlays.d.ts.map +1 -1
  133. package/dist-types/tsconfig.tsbuildinfo +1 -1
  134. package/docs/components/checkbox-group/overview.md +2 -2
  135. package/docs/components/collapsible/overview.md +1 -1
  136. package/docs/components/combobox/api-table.md +53 -39
  137. package/docs/components/combobox/overview.md +1 -1
  138. package/docs/components/combobox/use-cases.md +1 -1
  139. package/docs/components/dialog/api-table.md +32 -6
  140. package/docs/components/dialog/overview.md +1 -1
  141. package/docs/components/fieldset/overview.md +5 -5
  142. package/docs/components/form/overview.md +3 -3
  143. package/docs/components/form/use-cases.md +2 -2
  144. package/docs/components/icon/use-cases.md +1 -1
  145. package/docs/components/input/overview.md +1 -1
  146. package/docs/components/input-amount/api-table.md +9 -7
  147. package/docs/components/input-amount/overview.md +3 -3
  148. package/docs/components/input-amount/use-cases.md +15 -4
  149. package/docs/components/input-amount-dropdown/api-table.md +2 -0
  150. package/docs/components/input-date/overview.md +3 -3
  151. package/docs/components/input-datepicker/api-table.md +63 -45
  152. package/docs/components/input-datepicker/overview.md +5 -5
  153. package/docs/components/input-email/overview.md +2 -2
  154. package/docs/components/input-file/overview.md +1 -1
  155. package/docs/components/input-iban/overview.md +2 -2
  156. package/docs/components/input-range/overview.md +2 -2
  157. package/docs/components/input-stepper/overview.md +1 -1
  158. package/docs/components/input-tel/overview.md +1 -1
  159. package/docs/components/input-tel/use-cases.md +6 -6
  160. package/docs/components/input-tel-dropdown/overview.md +1 -1
  161. package/docs/components/listbox/use-cases.md +1 -1
  162. package/docs/components/menu/api-table.md +44 -0
  163. package/docs/components/menu/applyMenuDemoStyles.js +173 -0
  164. package/docs/components/menu/extensions.md +262 -0
  165. package/docs/components/menu/index.md +13 -0
  166. package/docs/components/menu/migration.md +129 -0
  167. package/docs/components/menu/overview.md +448 -0
  168. package/docs/components/menu/src/DemoNavigationBar.js +441 -0
  169. package/docs/components/menu/src/DemoSpinButton.js +215 -0
  170. package/docs/components/menu/src/DemoToolbar.js +101 -0
  171. package/docs/components/menu/src/DemoTree.js +55 -0
  172. package/docs/components/menu/src/navigation-bar-types.js +41 -0
  173. package/docs/components/menu/use-cases.md +74 -0
  174. package/docs/components/radio-group/overview.md +3 -3
  175. package/docs/components/select/use-cases.md +1 -1
  176. package/docs/components/select-rich/api-table.md +51 -33
  177. package/docs/components/select-rich/use-cases.md +2 -2
  178. package/docs/components/tabs/examples.md +1 -1
  179. package/docs/components/tooltip/api-table.md +33 -7
  180. package/docs/fundamentals/index.md +4 -4
  181. package/docs/fundamentals/node-tools/providence-analytics/overview.md +1 -1
  182. package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
  183. package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
  184. package/docs/fundamentals/rationales/accessibility.md +1 -1
  185. package/docs/fundamentals/systems/core/SlotMixin.md +1 -1
  186. package/docs/fundamentals/systems/core/overview.md +1 -1
  187. package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
  188. package/docs/fundamentals/systems/form/model-value.md +1 -1
  189. package/docs/fundamentals/systems/form/overview.md +28 -28
  190. package/docs/fundamentals/systems/form/validate.md +2 -2
  191. package/docs/fundamentals/systems/localize/overview.md +4 -4
  192. package/docs/fundamentals/systems/overlays/api-table.md +46 -43
  193. package/docs/fundamentals/systems/overlays/configuration.md +2 -2
  194. package/docs/fundamentals/systems/overlays/overview.md +5 -5
  195. package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
  196. package/docs/fundamentals/tools/helpers/overview.md +1 -1
  197. package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
  198. package/docs/guides/how-to/create-a-custom-field.md +5 -5
  199. package/docs/guides/how-to/create-a-form.md +4 -4
  200. package/docs/guides/how-to/extend-a-native-input.md +2 -2
  201. package/docs/guides/how-to/index.md +1 -1
  202. package/docs/guides/index.md +8 -8
  203. package/docs/guides/principles/index.md +5 -5
  204. package/docs/guides/principles/styling.md +1 -1
  205. package/exports/define/lion-item.js +3 -0
  206. package/exports/define/lion-menu.js +3 -0
  207. package/exports/define/lion-menuitem.js +3 -0
  208. package/exports/menu-test-suites.js +4 -0
  209. package/exports/menu.js +3 -0
  210. package/exports/overlays.js +6 -0
  211. package/menu-test-suites.js +4 -0
  212. package/menu.js +3 -0
  213. package/overlays.js +6 -0
  214. package/package.json +11 -4
  215. package/vendor/README.md +61 -0
  216. package/vendor/awesome-phonenumber-7.9.0-tlouisse.libphonenumber-9.0.41.tgz +0 -0
  217. package/vscode.html-custom-data.json +76 -8
  218. package/web-types.json +202 -17
@@ -0,0 +1,441 @@
1
+ // @ts-nocheck
2
+ /* eslint-disable class-methods-use-this */
3
+ /* eslint-disable import/no-relative-packages */
4
+ import { LitElement, html, css, nothing } from 'lit';
5
+ import { LionMenu } from '@lion/ui/menu.js';
6
+ import { ScopedElementsMixin } from '../../../../packages/ui/components/core/src/ScopedElementsMixin.js';
7
+
8
+ /**
9
+ * @typedef {import('./navigation-bar-types.js').NavBarResponsiveMode} NavBarResponsiveMode
10
+ * @typedef {import('./navigation-bar-types.js').MenuItem} MenuItem
11
+ * @typedef {import('./navigation-bar-types.js').CtaLink} CtaLink
12
+ * @typedef {import('./navigation-bar-types.js').Logo} Logo
13
+ * @typedef {import('./navigation-bar-types.js').LevelConfig} LevelConfig
14
+ */
15
+
16
+ export class DemoNavigationBar extends ScopedElementsMixin(LitElement) {
17
+ static get properties() {
18
+ return {
19
+ responsiveMode: { type: String, reflect: true, attribute: 'responsive-mode' },
20
+ // logo: { type: String },
21
+ menuItems: { type: Array },
22
+ menuSupportItems: { type: Array },
23
+ ctaPrimary: { type: Object },
24
+ ctaSecondary: { type: Object },
25
+ suggestions: { type: Array },
26
+ showOnMobile: { type: Array, attribute: 'show-on-mobile', reflect: true },
27
+
28
+ _levelCfg: { state: true },
29
+ };
30
+ }
31
+
32
+ static get scopedElements() {
33
+ return {
34
+ 'lion-menu': LionMenu,
35
+ };
36
+ }
37
+
38
+ static get styles() {
39
+ return [
40
+ css`
41
+ /** specific styles */
42
+
43
+ :host([responsive-mode='desktop']) [data-has-full-width-flyout] > [slot='list'] {
44
+ /** make sure that we give the right styles */
45
+ position: relative;
46
+ }
47
+
48
+ :host([responsive-mode='desktop'])
49
+ [data-has-full-width-flyout]
50
+ > [slot='list']
51
+ [level='2']
52
+ > [slot='list'] {
53
+ position: absolute;
54
+ width: 100%;
55
+ left: 0;
56
+ right: 0;
57
+ min-height: 300px;
58
+ padding: 1em;
59
+ background-color: white;
60
+ border: 1px solid gray;
61
+ box-sizing: border-box;
62
+ }
63
+
64
+ :host([responsive-mode='desktop']) [level='3'] > [slot='list'] {
65
+ position: absolute;
66
+ left: 33.333%;
67
+ width: 33.333%;
68
+ top: 0;
69
+ padding: 1em;
70
+ background-color: white;
71
+ box-sizing: border-box;
72
+ height: 100%;
73
+ }
74
+
75
+ :host([responsive-mode='mobile']) nav {
76
+ border: 1px solid black;
77
+ display: block;
78
+ position: fixed;
79
+ top: 0;
80
+ bottom: 0;
81
+ right: 0;
82
+ min-width: 500px;
83
+ }
84
+
85
+ :host([responsive-mode='mobile']) [level][opened] > [slot='list'] {
86
+ left: 0;
87
+ width: 100%;
88
+ top: 0;
89
+ box-sizing: border-box;
90
+ }
91
+
92
+ :host([responsive-mode='mobile']) [slot='list'] {
93
+ position: relative;
94
+ padding: 1em;
95
+ background-color: white;
96
+ }
97
+
98
+ :host([responsive-mode='mobile']) [level='2'] > [slot='list'],
99
+ :host([responsive-mode='mobile']) [level='3'] > [slot='list'] {
100
+ position: absolute;
101
+ left: 100%;
102
+ top: 0;
103
+ box-sizing: border-box;
104
+ padding-bottom: 200%;
105
+ }
106
+
107
+ /**
108
+ * TODO remove it. Use it now for testing More button feature
109
+ */
110
+ :host([responsive-mode='desktop']) [level='1'] > [slot='list'] > [role='listitem'],
111
+ :host([responsive-mode='desktop'])
112
+ [level='1']
113
+ > [slot='list']
114
+ > [data-more-button-wrapper] {
115
+ margin-right: 180px;
116
+ }
117
+
118
+ :host([responsive-mode='mobile']) nav {
119
+ display: flex;
120
+ flex-direction: column;
121
+ /* height: 100%;
122
+ justify-content: space-between; */
123
+ }
124
+
125
+ /* :host([responsive-mode='mobile']) .cta-items {
126
+ position: absolute;
127
+ bottom: 0;
128
+ width: 100%;
129
+ padding: 1em;
130
+ background-color: white;
131
+ box-sizing: border-box;
132
+ } */
133
+
134
+ :host([responsive-mode='mobile']) .logo-and-close {
135
+ padding: 1em;
136
+ }
137
+ `,
138
+ ];
139
+ }
140
+
141
+ constructor() {
142
+ super();
143
+
144
+ /** @type {Logo} */
145
+ this.logo = { type: 'ing', alt: 'ING homepage', href: '/' };
146
+ /** @type {MenuItem[]} */
147
+ this.menuSupportItems = [];
148
+ // /**
149
+ // * @type {string[]}
150
+ // * @description Choose which items to show on mobile when in default mode, maximum of two. Possible values: 'search', 'ctaPrimary', 'ctaSecondary', 'avatarMenu'
151
+ // */
152
+ // this.showOnMobile = [];
153
+ /**
154
+ * @type {CtaLink}
155
+ */
156
+ // @ts-ignore
157
+ this.ctaPrimary = {};
158
+ /**
159
+ * @type {CtaLink}
160
+ */
161
+ // @ts-ignore
162
+ this.ctaSecondary = {};
163
+ /** @type {MenuItem[]} */
164
+ this.menuItems = [];
165
+ // /** @type {Boolean} */
166
+ // this.searchDisabled = false;
167
+ this.breakpointMin = 1400; // force desktop
168
+ /** @type {string[]} */
169
+ this.suggestions = [];
170
+
171
+ this.prefilledSuggestion = '';
172
+
173
+ const mql = window.matchMedia(`(width <= ${this.breakpointMin}px)`);
174
+ /** @type {NavBarResponsiveMode} */
175
+ this.responsiveMode = mql.matches ? 'mobile' : 'desktop';
176
+ this._levelCfg = this._getLevelCfg(this.responsiveMode);
177
+ this._levelSecondaryCfg = this._getSecondaryLevelCfg(this.responsiveMode);
178
+
179
+ mql.addEventListener('change', () => {
180
+ this.responsiveMode = mql.matches ? 'mobile' : 'desktop';
181
+ this._levelCfg = this._getLevelCfg(this.responsiveMode);
182
+ this._levelSecondaryCfg = this._getSecondaryLevelCfg(this.responsiveMode);
183
+ });
184
+
185
+ this.#storeLatestFocusedElementId();
186
+ }
187
+
188
+ /**
189
+ * @param {NavBarResponsiveMode} responsiveMode
190
+ */
191
+ _getLevelCfg(responsiveMode) {
192
+ // N.B. we're fighting some of the overlay configs provided by LionMenu here,
193
+ // the idea is to take over all of that when depending on LionMenu.
194
+
195
+ if (responsiveMode === 'mobile') {
196
+ return {
197
+ l1: {
198
+ isBar: false,
199
+ openableConfig: {
200
+ // We want to do the absolute positioning ourselves, popper is not suited for this...
201
+ placementMode: 'custom',
202
+ isActivated: false,
203
+ hidesOnOutsideClick: false,
204
+ inheritsReferenceWidth: 'none',
205
+ },
206
+ },
207
+ l2: {
208
+ openableConfig: {
209
+ // We want disclosure behavior
210
+ placementMode: 'custom',
211
+ hidesOnOutsideClick: false,
212
+ inheritsReferenceWidth: 'none',
213
+ },
214
+ },
215
+ l3: {
216
+ openableConfig: {
217
+ // We want disclosure behavior
218
+ placementMode: 'custom',
219
+ hidesOnOutsideClick: false,
220
+ inheritsReferenceWidth: 'none',
221
+ },
222
+ },
223
+ };
224
+ }
225
+
226
+ return {
227
+ l1: {
228
+ isBar: true,
229
+ hasFullWidthFlyout: true,
230
+ openableConfig: {
231
+ // We want disclosure behavior
232
+ placementMode: 'custom',
233
+ // N.B. we don't have an invoker...
234
+ isActivated: false,
235
+ inheritsReferenceWidth: 'none',
236
+ elementToFocusOnShow: true,
237
+ },
238
+ },
239
+ l2: {
240
+ openableConfig: {
241
+ // We want disclosure behavior
242
+ placementMode: 'custom',
243
+ hidesOnOutsideClick: true,
244
+ hidesOnEscape: true,
245
+ inheritsReferenceWidth: 'none',
246
+ elementToFocusOnShow: true,
247
+ },
248
+ },
249
+ l3: {
250
+ openableConfig: {
251
+ // We want disclosure behavior
252
+ placementMode: 'custom',
253
+ inheritsReferenceWidth: 'none',
254
+ elementToFocusOnShow: true,
255
+ // TODO: implement in OverlayController
256
+ hideVisually: true,
257
+ },
258
+ },
259
+ };
260
+ }
261
+
262
+ _getSecondaryLevelCfg(responsiveMode) {
263
+ if (responsiveMode === 'mobile') {
264
+ return {
265
+ l1: {
266
+ isBar: true,
267
+ },
268
+ l2: {
269
+ isBar: false,
270
+ },
271
+ };
272
+ }
273
+
274
+ return {
275
+ l1: {
276
+ isBar: true,
277
+ },
278
+ l2: {
279
+ isBar: false,
280
+ },
281
+ };
282
+ }
283
+
284
+ /** @type {string | null} */
285
+ #latestFocusedElementId = null;
286
+
287
+ #storeLatestFocusedElementId() {
288
+ this.addEventListener('focusin', ({ target }) => {
289
+ // @ts-ignore
290
+ this.#latestFocusedElementId = target.id;
291
+ });
292
+ }
293
+
294
+ #syncLatestFocusedElementForNewResponsiveMode() {
295
+ // We might show/hide or rerender some elements (with same ids) in different positions in the DOM.
296
+ // As these are physically different elements, we must move focus (as otherwise it would be reset to body)
297
+ if (!this.#latestFocusedElementId) return;
298
+ const newFocusedElement = this.shadowRoot?.querySelector(`#${this.#latestFocusedElementId}`);
299
+ if (newFocusedElement instanceof HTMLElement) {
300
+ newFocusedElement.focus();
301
+ }
302
+ }
303
+
304
+ /**
305
+ * @param {import("./types.js").MenuItem}
306
+ * @param {number} level
307
+ */
308
+ _getId(menuItem, level) {
309
+ if (menuItem.id) return menuItem.id;
310
+ const title = menuItem.title || menuItem.label || 'item';
311
+ return `${
312
+ Number.isInteger(level) ? `l${level}-${title}` : title
313
+ }-${Math.random().toString(16).slice(2)}`;
314
+ }
315
+
316
+ /**
317
+ * @param {import('lit').PropertyValues} changedProperties
318
+ */
319
+ updated(changedProperties) {
320
+ if (changedProperties.has('responsiveMode')) {
321
+ this._levelCfg = this._getLevelCfg(this.responsiveMode);
322
+ this.#syncLatestFocusedElementForNewResponsiveMode();
323
+ }
324
+ }
325
+
326
+ render() {
327
+ return html`
328
+ <nav>
329
+ <div class="nav-header">
330
+ <div class="logo-and-close">
331
+ <a href="${this.logo.href || '#'}">
332
+ <img src="${this.logo.src || ''}" alt="${this.logo.alt || ''}" />
333
+ </a>
334
+ ${this.responsiveMode === 'mobile'
335
+ ? html`<button data-close data-level="1">✖️</button>`
336
+ : ''}
337
+ </div>
338
+ ${this._searchTemplate()}
339
+ </div>
340
+ <div class="nav-body">
341
+ ${this._menulevelTemplate(this.menuSupportItems, 1, '')}
342
+ ${this.responsiveMode === 'desktop'
343
+ ? this._ctasTemplate(this.ctaPrimary, this.ctaSecondary)
344
+ : ''}
345
+ ${this._menulevelTemplate(this.menuItems, 1, '', true)}
346
+ </div>
347
+ ${this.responsiveMode === 'mobile'
348
+ ? html`<div class="nav-footer">
349
+ ${this._ctasTemplate(this.ctaPrimary, this.ctaSecondary)}
350
+ </div>`
351
+ : ''}
352
+ </nav>
353
+ `;
354
+ }
355
+
356
+ /**
357
+ * @param {string[]} suggestions
358
+ * @returns {import('lit').TemplateResult}
359
+ */
360
+ _searchTemplate(suggestions = this.suggestions, prefilledSuggestion = this.prefilledSuggestion) {
361
+ return html` <form role="search">
362
+ <input
363
+ type="search"
364
+ list="search-list"
365
+ placeholder="What are you looking for?"
366
+ .value="${prefilledSuggestion}"
367
+ />
368
+ <datalist id="search-list">
369
+ ${suggestions.map(suggestion => html`<option value="${suggestion}"></option>`)}
370
+ </datalist>
371
+ <input type="submit" value="Search" />
372
+ </form>`;
373
+ }
374
+
375
+ /**
376
+ * @param {CtaLink} ctaPrimary
377
+ * @param {CtaLink} ctaSecondary
378
+ * @returns {import('lit').TemplateResult}
379
+ */
380
+ _ctasTemplate(ctaPrimary, ctaSecondary) {
381
+ return html`<div class="cta-items">
382
+ ${ctaSecondary?.href
383
+ ? html`<div class="cta-secondary">
384
+ <a id="${this._getId(ctaSecondary)}" href="${ctaSecondary.href}"
385
+ >${ctaSecondary.label}
386
+ </a>
387
+ </div>`
388
+ : ''}
389
+ ${ctaPrimary?.href
390
+ ? html`<div class="cta-primary">
391
+ <a id="${this._getId(ctaPrimary)}" href="${ctaPrimary.href}"> ${ctaPrimary.label}</a>
392
+ </div>`
393
+ : ''}
394
+ </div>`;
395
+ }
396
+
397
+ /**
398
+ * @param {MenuItem[]} menuItemsForLevel
399
+ * @param {number} level
400
+ * @returns {import('lit').TemplateResult}
401
+ */
402
+ _menulevelTemplate(menuItemsForLevel, level, prevText = '', itemWrap = false) {
403
+ // @ts-ignore
404
+ const cfgForLevel = this._levelCfg[`l${level}`];
405
+
406
+ return html`<lion-menu
407
+ ?item-wrap=${itemWrap}
408
+ .config="${cfgForLevel.openableConfig || {}}"
409
+ .bar="${cfgForLevel.isBar}"
410
+ ?data-has-full-width-flyout="${cfgForLevel.hasFullWidthFlyout}"
411
+ ._activeMode="${'tabbable-disclosure'}"
412
+ >
413
+ ${level > 1 && this.responsiveMode === 'mobile'
414
+ ? html` <div role="listitem">
415
+ <button data-close data-level="${level}">&lt; ${prevText}</button>
416
+ </div>`
417
+ : ''}
418
+ ${menuItemsForLevel.map(
419
+ menuItem => html`
420
+ <div role="listitem">
421
+ ${menuItem.sub
422
+ ? html` <button id="${this._getId(menuItem)}" data-invoker>${menuItem.title}</button>
423
+ ${menuItem.sub
424
+ ? this._menulevelTemplate(menuItem.sub, level + 1, menuItem.title)
425
+ : ''}`
426
+ : html` <a id="${this._getId(menuItem)}" href="${menuItem.link || ''}"
427
+ >${menuItem.title}</a
428
+ >`}
429
+ </div>
430
+ `,
431
+ )}
432
+ ${itemWrap
433
+ ? html`<div slot="more-button">
434
+ <button>More</button>
435
+ </div>`
436
+ : nothing}
437
+ </lion-menu> `;
438
+ }
439
+ }
440
+
441
+ customElements.define('demo-navigation-bar', DemoNavigationBar);
@@ -0,0 +1,215 @@
1
+ /* eslint-disable import/no-extraneous-dependencies */
2
+ import { LitElement, html, css } from 'lit';
3
+
4
+ export class DemoSpinButton extends LitElement {
5
+ static get properties() {
6
+ return {
7
+ now: { type: Number },
8
+ min: { type: Number },
9
+ max: { type: Number },
10
+ unit: { type: String },
11
+ step: { type: Number },
12
+ };
13
+ }
14
+
15
+ get _decreaseNode() {
16
+ return this.shadowRoot?.querySelector('[data-ref=decrease]');
17
+ }
18
+
19
+ get _increaseNode() {
20
+ return this.shadowRoot?.querySelector('[data-ref=increase]');
21
+ }
22
+
23
+ static get styles() {
24
+ return css`
25
+ :host {
26
+ display: flex;
27
+ align-items: center;
28
+ cursor: default;
29
+ }
30
+ `;
31
+ }
32
+
33
+ render() {
34
+ return this._mainTemplate({ ariaText: this._getAriaText() });
35
+ }
36
+
37
+ /**
38
+ * @param {{ ariaText: string }} props
39
+ */
40
+ _mainTemplate({ ariaText }) {
41
+ return html`${ariaText}${this._decreaseBtnTemplate()}${this._increaseBtnTemplate()}`;
42
+ }
43
+
44
+ // eslint-disable-next-line class-methods-use-this
45
+ _decreaseBtnTemplate() {
46
+ return html`<span data-ref="decrease">▼</span>`;
47
+ }
48
+
49
+ // eslint-disable-next-line class-methods-use-this
50
+ _increaseBtnTemplate() {
51
+ return html`<span data-ref="increase">▲</span>`;
52
+ }
53
+
54
+ constructor() {
55
+ super();
56
+ this.unit = '';
57
+
58
+ this.step = 1;
59
+
60
+ this.__decrease = this.__decrease.bind(this);
61
+ this.__increase = this.__increase.bind(this);
62
+ }
63
+
64
+ connectedCallback() {
65
+ super.connectedCallback();
66
+
67
+ if (this.__isSetup) {
68
+ return;
69
+ }
70
+
71
+ this.setAttribute('tabindex', '0');
72
+ this.__isSetup = true;
73
+ }
74
+
75
+ /**
76
+ * @param {import('lit').PropertyValues} changedProperties
77
+ */
78
+ firstUpdated(changedProperties) {
79
+ super.firstUpdated(changedProperties);
80
+
81
+ // @ts-ignore - event listener type compatibility
82
+ this._decreaseNode?.addEventListener('mousedown', this.__decrease);
83
+ // @ts-ignore - event listener type compatibility
84
+ this._increaseNode?.addEventListener('mousedown', this.__increase);
85
+ this.addEventListener('keydown', this._onKeydown);
86
+
87
+ this.addEventListener('mousedown', () => {
88
+ this.__isMousedown = true;
89
+ });
90
+ this.addEventListener('mouseup', () => {
91
+ this.__isMousedown = false;
92
+ });
93
+ }
94
+
95
+ /**
96
+ * @param {import('lit').PropertyValues} changedProperties
97
+ */
98
+ updated(changedProperties) {
99
+ super.updated(changedProperties);
100
+
101
+ if (changedProperties.has('now')) {
102
+ this.setAttribute('aria-valuenow', `${this.now}`);
103
+ }
104
+
105
+ if (changedProperties.has('now') || changedProperties.has('unit')) {
106
+ this.setAttribute('aria-valuetext', this._getAriaText());
107
+ }
108
+
109
+ if (changedProperties.has('min')) {
110
+ // @ts-ignore - min property from properties
111
+ this.setAttribute('aria-valuemin', `${this.min}`);
112
+ }
113
+
114
+ if (changedProperties.has('max')) {
115
+ // @ts-ignore - max property from properties
116
+ this.setAttribute('aria-valuemax', `${this.max}`);
117
+ }
118
+ }
119
+
120
+ /**
121
+ * @overridable
122
+ */
123
+ _getAriaText() {
124
+ return `${this.now}${this.unit ? this.unit : ''}`;
125
+ }
126
+
127
+ /**
128
+ * @param {MouseEvent | null} ev
129
+ * @param {number} step
130
+ * @param {number} timeout
131
+ */
132
+ __decrease(ev, step = this.step, timeout = 100) {
133
+ // @ts-ignore - min property from properties
134
+ if (this.min !== undefined && this.now - 1 >= this.min) {
135
+ this.now -= 1; // TODO: support step/increment?
136
+ }
137
+ // @ts-ignore - ev can be null
138
+ if (ev.type === 'mousedown') {
139
+ setTimeout(() => {
140
+ if (this.__isMousedown) {
141
+ this.__decrease(ev, step, timeout / 1.3);
142
+ }
143
+ }, timeout);
144
+ }
145
+ }
146
+
147
+ /**
148
+ * @param {MouseEvent | null} ev
149
+ * @param {number} step
150
+ * @param {number} timeout
151
+ */
152
+ __increase(ev, step = this.step, timeout = 100) {
153
+ // @ts-ignore - max property from properties
154
+ if (this.max !== undefined && this.now + 1 <= this.max) {
155
+ this.now += 1; // TODO: support step/increment?
156
+ }
157
+ // @ts-ignore - ev can be null
158
+ if (ev.type === 'mousedown') {
159
+ setTimeout(() => {
160
+ if (this.__isMousedown) {
161
+ this.__increase(ev, step, timeout / 1.3);
162
+ }
163
+ }, timeout);
164
+ }
165
+ }
166
+
167
+ /**
168
+ * @param {KeyboardEvent} ev
169
+ */
170
+ _onKeydown(ev) {
171
+ const { key } = ev;
172
+
173
+ // eslint-disable-next-line default-case
174
+ switch (key) {
175
+ case 'ArrowDown':
176
+ ev.stopPropagation(); // In case we are inside a menu
177
+ ev.preventDefault(); // Prevent scroll
178
+ // @ts-ignore - passing null for ev parameter
179
+ this.__decrease();
180
+ break;
181
+ case 'ArrowUp':
182
+ ev.stopPropagation(); // In case we are inside a menu
183
+ ev.preventDefault(); // Prevent scroll
184
+ // @ts-ignore - passing null for ev parameter
185
+ this.__increase();
186
+ break;
187
+ case 'Home':
188
+ ev.stopPropagation(); // In case we are inside a menu
189
+ ev.preventDefault(); // Prevent scroll
190
+ // @ts-ignore - min property from properties
191
+ if (this.min !== undefined) {
192
+ // @ts-ignore
193
+ this.now = this.min;
194
+ }
195
+ break;
196
+ case 'End':
197
+ ev.stopPropagation(); // In case we are inside a menu
198
+ ev.preventDefault(); // Prevent scroll
199
+ // @ts-ignore - max property from properties
200
+ if (this.max !== undefined) {
201
+ // @ts-ignore
202
+ this.now = this.max;
203
+ }
204
+ break;
205
+ case 'PageDown':
206
+ this.__decrease(null, this.step * 2);
207
+ break;
208
+ case 'PageUp':
209
+ this.__increase(null, this.step * 2);
210
+ break;
211
+ }
212
+ }
213
+ }
214
+
215
+ customElements.define('demo-spin-button', DemoSpinButton);