@statistikzh/leu 0.5.1 → 0.6.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 (236) hide show
  1. package/.husky/commit-msg +0 -3
  2. package/.husky/pre-commit +0 -3
  3. package/CHANGELOG.md +40 -0
  4. package/dist/Accordion.d.ts +10 -9
  5. package/dist/Accordion.d.ts.map +1 -1
  6. package/dist/Accordion.js +12 -11
  7. package/dist/Breadcrumb.d.ts +4 -4
  8. package/dist/Breadcrumb.d.ts.map +1 -1
  9. package/dist/Breadcrumb.js +28 -26
  10. package/dist/{Button-5326c982.d.ts → Button-9692e403.d.ts} +10 -11
  11. package/dist/Button-9692e403.d.ts.map +1 -0
  12. package/dist/{Button-5326c982.js → Button-9692e403.js} +57 -67
  13. package/dist/Button.d.ts +1 -1
  14. package/dist/Button.js +3 -3
  15. package/dist/ButtonGroup.d.ts +2 -2
  16. package/dist/ButtonGroup.d.ts.map +1 -1
  17. package/dist/ButtonGroup.js +3 -3
  18. package/dist/Checkbox.d.ts +4 -3
  19. package/dist/Checkbox.d.ts.map +1 -1
  20. package/dist/Checkbox.js +14 -19
  21. package/dist/CheckboxGroup.d.ts +2 -2
  22. package/dist/CheckboxGroup.d.ts.map +1 -1
  23. package/dist/CheckboxGroup.js +4 -6
  24. package/dist/Chip.d.ts +2 -2
  25. package/dist/Chip.d.ts.map +1 -1
  26. package/dist/Chip.js +6 -13
  27. package/dist/ChipGroup.d.ts +9 -7
  28. package/dist/ChipGroup.d.ts.map +1 -1
  29. package/dist/ChipGroup.js +8 -5
  30. package/dist/ChipLink.d.ts +2 -1
  31. package/dist/ChipLink.d.ts.map +1 -1
  32. package/dist/ChipLink.js +4 -7
  33. package/dist/ChipRemovable.d.ts +0 -2
  34. package/dist/ChipRemovable.d.ts.map +1 -1
  35. package/dist/ChipRemovable.js +8 -11
  36. package/dist/ChipSelectable.d.ts +9 -1
  37. package/dist/ChipSelectable.d.ts.map +1 -1
  38. package/dist/ChipSelectable.js +12 -16
  39. package/dist/Dropdown.d.ts +9 -5
  40. package/dist/Dropdown.d.ts.map +1 -1
  41. package/dist/Dropdown.js +68 -32
  42. package/dist/Icon.d.ts +20 -0
  43. package/dist/Icon.d.ts.map +1 -0
  44. package/dist/{icon-03e86700.js → Icon.js} +61 -32
  45. package/dist/Input.d.ts +7 -16
  46. package/dist/Input.d.ts.map +1 -1
  47. package/dist/Input.js +24 -28
  48. package/dist/LeuElement-6de6f209.d.ts +7 -0
  49. package/dist/LeuElement-6de6f209.d.ts.map +1 -0
  50. package/dist/LeuElement-6de6f209.js +43 -0
  51. package/dist/Menu.d.ts +24 -2
  52. package/dist/Menu.d.ts.map +1 -1
  53. package/dist/Menu.js +120 -3
  54. package/dist/MenuItem.d.ts +28 -11
  55. package/dist/MenuItem.d.ts.map +1 -1
  56. package/dist/MenuItem.js +110 -63
  57. package/dist/Pagination.d.ts +10 -3
  58. package/dist/Pagination.d.ts.map +1 -1
  59. package/dist/Pagination.js +24 -21
  60. package/dist/Popup.d.ts +21 -3
  61. package/dist/Popup.d.ts.map +1 -1
  62. package/dist/Popup.js +44 -19
  63. package/dist/Radio.d.ts +4 -2
  64. package/dist/Radio.d.ts.map +1 -1
  65. package/dist/Radio.js +9 -16
  66. package/dist/RadioGroup.d.ts +2 -2
  67. package/dist/RadioGroup.d.ts.map +1 -1
  68. package/dist/RadioGroup.js +4 -6
  69. package/dist/ScrollTop.d.ts +2 -2
  70. package/dist/ScrollTop.d.ts.map +1 -1
  71. package/dist/ScrollTop.js +10 -8
  72. package/dist/Select.d.ts +75 -37
  73. package/dist/Select.d.ts.map +1 -1
  74. package/dist/Select.js +279 -183
  75. package/dist/Table.d.ts +2 -6
  76. package/dist/Table.d.ts.map +1 -1
  77. package/dist/Table.js +17 -18
  78. package/dist/VisuallyHidden.d.ts +2 -2
  79. package/dist/VisuallyHidden.d.ts.map +1 -1
  80. package/dist/VisuallyHidden.js +5 -7
  81. package/dist/index.d.ts +2 -2
  82. package/dist/index.js +5 -14
  83. package/dist/leu-accordion.d.ts.map +1 -1
  84. package/dist/leu-accordion.js +2 -3
  85. package/dist/leu-breadcrumb.d.ts.map +1 -1
  86. package/dist/leu-breadcrumb.js +4 -10
  87. package/dist/leu-button-group.d.ts.map +1 -1
  88. package/dist/leu-button-group.js +2 -3
  89. package/dist/leu-button.d.ts +1 -1
  90. package/dist/leu-button.d.ts.map +1 -1
  91. package/dist/leu-button.js +4 -5
  92. package/dist/leu-checkbox-group.d.ts.map +1 -1
  93. package/dist/leu-checkbox-group.js +2 -3
  94. package/dist/leu-checkbox.d.ts.map +1 -1
  95. package/dist/leu-checkbox.js +3 -4
  96. package/dist/leu-chip-group.d.ts.map +1 -1
  97. package/dist/leu-chip-group.js +2 -3
  98. package/dist/leu-chip-link.d.ts.map +1 -1
  99. package/dist/leu-chip-link.js +2 -3
  100. package/dist/leu-chip-removable.d.ts.map +1 -1
  101. package/dist/leu-chip-removable.js +3 -4
  102. package/dist/leu-chip-selectable.d.ts.map +1 -1
  103. package/dist/leu-chip-selectable.js +2 -3
  104. package/dist/leu-dropdown.d.ts.map +1 -1
  105. package/dist/leu-dropdown.js +5 -10
  106. package/dist/leu-icon.d.ts +3 -0
  107. package/dist/leu-icon.d.ts.map +1 -0
  108. package/dist/leu-icon.js +7 -0
  109. package/dist/leu-input.d.ts.map +1 -1
  110. package/dist/leu-input.js +3 -4
  111. package/dist/leu-menu-item.d.ts.map +1 -1
  112. package/dist/leu-menu-item.js +3 -5
  113. package/dist/leu-menu.d.ts.map +1 -1
  114. package/dist/leu-menu.js +5 -3
  115. package/dist/leu-pagination.d.ts.map +1 -1
  116. package/dist/leu-pagination.js +4 -7
  117. package/dist/leu-popup.d.ts.map +1 -1
  118. package/dist/leu-popup.js +2 -3
  119. package/dist/leu-radio-group.d.ts.map +1 -1
  120. package/dist/leu-radio-group.js +2 -3
  121. package/dist/leu-radio.d.ts.map +1 -1
  122. package/dist/leu-radio.js +2 -3
  123. package/dist/leu-scroll-top.d.ts.map +1 -1
  124. package/dist/leu-scroll-top.js +4 -6
  125. package/dist/leu-select.d.ts.map +1 -1
  126. package/dist/leu-select.js +5 -13
  127. package/dist/leu-table.d.ts.map +1 -1
  128. package/dist/leu-table.js +4 -8
  129. package/dist/leu-visually-hidden.d.ts.map +1 -1
  130. package/dist/leu-visually-hidden.js +2 -3
  131. package/dist/theme.css +2 -0
  132. package/dist/vscode.html-custom-data.json +116 -79
  133. package/dist/vue/index.d.ts +80 -76
  134. package/dist/web-types.json +405 -270
  135. package/package.json +9 -12
  136. package/scripts/generate-component/templates/[Name].js +6 -3
  137. package/scripts/generate-component/templates/test/[name].test.js +1 -1
  138. package/src/components/accordion/Accordion.js +13 -10
  139. package/src/components/accordion/leu-accordion.js +1 -2
  140. package/src/components/breadcrumb/Breadcrumb.js +31 -18
  141. package/src/components/breadcrumb/leu-breadcrumb.js +1 -2
  142. package/src/components/button/Button.js +45 -71
  143. package/src/components/button/button.css +11 -9
  144. package/src/components/button/leu-button.js +1 -2
  145. package/src/components/button/stories/button.stories.js +60 -19
  146. package/src/components/button/test/button.test.js +26 -63
  147. package/src/components/button-group/ButtonGroup.js +4 -2
  148. package/src/components/button-group/leu-button-group.js +1 -2
  149. package/src/components/checkbox/Checkbox.js +17 -11
  150. package/src/components/checkbox/CheckboxGroup.js +6 -3
  151. package/src/components/checkbox/leu-checkbox-group.js +1 -2
  152. package/src/components/checkbox/leu-checkbox.js +1 -2
  153. package/src/components/checkbox/stories/checkbox-group.stories.js +10 -26
  154. package/src/components/checkbox/stories/checkbox.stories.js +2 -7
  155. package/src/components/checkbox/test/checkbox-group.test.js +6 -21
  156. package/src/components/checkbox/test/checkbox.test.js +1 -12
  157. package/src/components/chip/Chip.js +5 -4
  158. package/src/components/chip/ChipGroup.js +10 -4
  159. package/src/components/chip/ChipLink.js +3 -7
  160. package/src/components/chip/ChipRemovable.js +8 -11
  161. package/src/components/chip/ChipSelectable.js +11 -17
  162. package/src/components/chip/chip.css +3 -4
  163. package/src/components/chip/leu-chip-group.js +1 -2
  164. package/src/components/chip/leu-chip-link.js +1 -2
  165. package/src/components/chip/leu-chip-removable.js +1 -2
  166. package/src/components/chip/leu-chip-selectable.js +1 -2
  167. package/src/components/chip/stories/chip-link.stories.js +3 -5
  168. package/src/components/chip/stories/chip-removable.stories.js +3 -4
  169. package/src/components/chip/stories/chip-selectable.stories.js +2 -2
  170. package/src/components/chip/test/chip-group.test.js +15 -30
  171. package/src/components/chip/test/chip-link.test.js +2 -6
  172. package/src/components/chip/test/chip-removable.test.js +4 -10
  173. package/src/components/chip/test/chip-selectable.test.js +3 -5
  174. package/src/components/dropdown/Dropdown.js +79 -26
  175. package/src/components/dropdown/leu-dropdown.js +1 -2
  176. package/src/components/dropdown/stories/dropdown.stories.js +30 -7
  177. package/src/components/dropdown/test/dropdown.test.js +5 -5
  178. package/src/components/icon/Icon.js +55 -0
  179. package/src/components/icon/icon.css +6 -0
  180. package/src/components/icon/leu-icon.js +5 -0
  181. package/src/components/icon/{icon.js → paths.js} +4 -37
  182. package/src/components/icon/stories/icon.stories.js +47 -0
  183. package/src/components/icon/test/icon.test.js +23 -40
  184. package/src/components/input/Input.js +21 -23
  185. package/src/components/input/input.css +4 -2
  186. package/src/components/input/leu-input.js +1 -2
  187. package/src/components/input/stories/input.stories.js +2 -2
  188. package/src/components/input/test/input.test.js +2 -0
  189. package/src/components/menu/Menu.js +143 -2
  190. package/src/components/menu/MenuItem.js +104 -52
  191. package/src/components/menu/leu-menu-item.js +1 -2
  192. package/src/components/menu/leu-menu.js +1 -2
  193. package/src/components/menu/menu-item.css +11 -4
  194. package/src/components/menu/stories/menu-item.stories.js +15 -4
  195. package/src/components/menu/stories/menu.stories.js +34 -7
  196. package/src/components/menu/test/menu-item.test.js +88 -82
  197. package/src/components/menu/test/menu.test.js +101 -8
  198. package/src/components/pagination/Pagination.js +27 -18
  199. package/src/components/pagination/leu-pagination.js +1 -2
  200. package/src/components/popup/Popup.js +39 -16
  201. package/src/components/popup/leu-popup.js +1 -2
  202. package/src/components/popup/popup.css +1 -0
  203. package/src/components/radio/Radio.js +12 -7
  204. package/src/components/radio/RadioGroup.js +6 -3
  205. package/src/components/radio/leu-radio-group.js +1 -2
  206. package/src/components/radio/leu-radio.js +1 -2
  207. package/src/components/radio/stories/radio-group.stories.js +5 -19
  208. package/src/components/radio/stories/radio.stories.js +2 -7
  209. package/src/components/radio/test/radio-group.test.js +6 -9
  210. package/src/components/radio/test/radio.test.js +3 -13
  211. package/src/components/scroll-top/ScrollTop.js +15 -5
  212. package/src/components/scroll-top/leu-scroll-top.js +1 -2
  213. package/src/components/select/Select.js +279 -175
  214. package/src/components/select/leu-select.js +1 -2
  215. package/src/components/select/select.css +20 -12
  216. package/src/components/select/stories/select.stories.js +16 -2
  217. package/src/components/select/test/select.test.js +191 -37
  218. package/src/components/table/Table.js +15 -9
  219. package/src/components/table/leu-table.js +1 -2
  220. package/src/components/table/table.css +3 -1
  221. package/src/components/visually-hidden/VisuallyHidden.js +6 -2
  222. package/src/components/visually-hidden/leu-visually-hidden.js +1 -2
  223. package/src/lib/LeuElement.js +23 -0
  224. package/src/lib/a11y.js +26 -0
  225. package/src/styles/custom-properties.css +2 -0
  226. package/web-test-runner.config.mjs +2 -0
  227. package/dist/Button-5326c982.d.ts.map +0 -1
  228. package/dist/_rollupPluginBabelHelpers-20f659f4.d.ts +0 -3
  229. package/dist/_rollupPluginBabelHelpers-20f659f4.d.ts.map +0 -1
  230. package/dist/_rollupPluginBabelHelpers-20f659f4.js +0 -30
  231. package/dist/defineElement-40372b4b.d.ts +0 -9
  232. package/dist/defineElement-40372b4b.d.ts.map +0 -1
  233. package/dist/defineElement-40372b4b.js +0 -15
  234. package/dist/icon-03e86700.d.ts +0 -11
  235. package/dist/icon-03e86700.d.ts.map +0 -1
  236. package/src/lib/defineElement.js +0 -13
package/dist/Select.js CHANGED
@@ -1,23 +1,15 @@
1
- import { _ as _defineProperty } from './_rollupPluginBabelHelpers-20f659f4.js';
2
- import { css, LitElement, html, nothing } from 'lit';
1
+ import { _ as _defineProperty, L as LeuElement } from './LeuElement-6de6f209.js';
2
+ import { css, nothing, html } from 'lit';
3
3
  import { classMap } from 'lit/directives/class-map.js';
4
- import { map } from 'lit/directives/map.js';
5
- import { ifDefined } from 'lit/directives/if-defined.js';
6
4
  import { createRef, ref } from 'lit/directives/ref.js';
7
- import { I as Icon } from './icon-03e86700.js';
8
- import { H as HasSlotController } from './Button-5326c982.js';
9
- import './leu-button.js';
10
- import './leu-menu.js';
11
- import './leu-menu-item.js';
12
- import './leu-input.js';
13
- import './leu-popup.js';
14
- import './defineElement-40372b4b.js';
15
- import './Menu.js';
16
- import './MenuItem.js';
17
- import 'lit/static-html.js';
18
- import './Input.js';
5
+ import { ifDefined } from 'lit/directives/if-defined.js';
6
+ import { H as HasSlotController, L as LeuButton } from './Button-9692e403.js';
7
+ import { LeuMenu } from './Menu.js';
8
+ import { LeuMenuItem } from './MenuItem.js';
9
+ import { LeuIcon } from './Icon.js';
10
+ import { LeuInput } from './Input.js';
11
+ import { LeuPopup } from './Popup.js';
19
12
  import 'lit/directives/live.js';
20
- import './Popup.js';
21
13
  import '@floating-ui/dom';
22
14
 
23
15
  var css_248z = css`:host,
@@ -61,12 +53,10 @@ var css_248z = css`:host,
61
53
  position: relative;
62
54
  display: block;
63
55
 
64
- font-family: var(--leu-font-family-regular);
65
-
66
56
  font-family: var(--select-font-regular);
67
57
  }
68
58
 
69
- .select[disabled] {
59
+ :host([disabled]) {
70
60
  --select-color: var(--select-color-disabled);
71
61
  --select-color-focus: var(--select-color-disabled);
72
62
  --select-border-color: var(--select-border-color-disabled);
@@ -153,19 +143,18 @@ var css_248z = css`:host,
153
143
  outline-offset: 2px;
154
144
  }
155
145
 
156
- .select[disabled] .select-toggle,
157
- .select[disabled] .clear-button {
146
+ .select-toggle[disabled],
147
+ .select-toggle[disabled] .clear-button {
158
148
  cursor: inherit;
159
149
  }
160
150
 
161
- .select[disabled] .label {
151
+ .select-toggle[disabled] .label {
162
152
  color: var(--select-label-color);
163
153
  }
164
154
 
165
155
  .select-toggle.open .label,
166
156
  .select-toggle.filled .label,
167
- .select:not([disabled]) .select-toggle:focus .label,
168
- .select:not([disabled]) .select-toggle:active:not([disabled]) .label {
157
+ .select-toggle:focus .label {
169
158
  color: var(--select-label-color);
170
159
  font-family: var(--select-font-black);
171
160
  font-size: 0.75rem;
@@ -208,7 +197,7 @@ var css_248z = css`:host,
208
197
  flex-direction: column;
209
198
 
210
199
  width: 100%;
211
- max-height: var(--auto-size-available-height);
200
+ max-height: min(var(--auto-size-available-height), 24rem);
212
201
 
213
202
  padding: 0;
214
203
  margin: 0;
@@ -219,12 +208,9 @@ var css_248z = css`:host,
219
208
  box-shadow: var(--select-box-shadow-regular), var(--select-box-shadow-short);
220
209
  }
221
210
 
222
- .select-menu {
211
+ .menu {
223
212
  display: block;
224
- padding: 0;
225
- margin: 0;
226
213
  overflow: auto;
227
- max-height: 100%;
228
214
  }
229
215
 
230
216
  .before,
@@ -241,24 +227,49 @@ var css_248z = css`:host,
241
227
  display: none;
242
228
  }
243
229
 
230
+ .apply-button-wrapper {
231
+ background-color: var(--leu-color-black-10);
232
+ padding: 0.75rem;
233
+ }
234
+
244
235
  .apply-button {
245
236
  display: block;
246
- margin: 0.75rem;
247
237
  }
248
238
 
249
239
  .select-search {
250
240
  margin: 0.75rem;
251
241
  }
242
+
243
+ .filter-message-empty {
244
+ background: var(--leu-color-black-0);
245
+ color: var(--leu-color-black-transp-60);
246
+
247
+ padding: 0.75rem;
248
+ margin: 0;
249
+ }
252
250
  `;
253
251
 
254
252
  /**
255
253
  * @tagname leu-select
256
254
  * @slot before - Optional content the appears before the option list
257
255
  * @slot after - Optional content the appears after the option list
256
+ * @property {string} name - Reflects to the name attribute of the hidden input field that would be used in a form
257
+ * @property {boolean} open - The expanded state of the popup
258
+ * @property {string} label - The label of the select
259
+ * @property {array} value - List of selected values. If they're set from outside the component, the select element tries to find all the options with the given values and selects them.
260
+ * @property {boolean} clearable - Show a clearable button to reset the value
261
+ * @property {boolean} disabled - If the select should be disabled
262
+ * @property {boolean} filterable - Show an input field to filter the options inside the popup
263
+ * @property {boolean} multiple - Allow multiple selections
264
+ * @attribute {string} value - The selected values separated by commas.
258
265
  */
259
- class LeuSelect extends LitElement {
266
+ class LeuSelect extends LeuElement {
260
267
  static get properties() {
261
268
  return {
269
+ name: {
270
+ type: String,
271
+ reflect: true
272
+ },
262
273
  open: {
263
274
  type: Boolean,
264
275
  reflect: true
@@ -267,11 +278,16 @@ class LeuSelect extends LitElement {
267
278
  type: String,
268
279
  reflect: true
269
280
  },
270
- options: {
271
- type: Array
272
- },
273
281
  value: {
274
- type: Array
282
+ type: Array,
283
+ converter: {
284
+ fromAttribute(value) {
285
+ if (value) {
286
+ return value.split(",").map(v => v.trim());
287
+ }
288
+ return value;
289
+ }
290
+ }
275
291
  },
276
292
  clearable: {
277
293
  type: Boolean,
@@ -289,7 +305,13 @@ class LeuSelect extends LitElement {
289
305
  type: Boolean,
290
306
  reflect: true
291
307
  },
292
- optionFilter: {
308
+ _optionFilter: {
309
+ state: true
310
+ },
311
+ _hasFilterResults: {
312
+ state: true
313
+ },
314
+ _displayValue: {
293
315
  state: true
294
316
  }
295
317
  };
@@ -313,18 +335,18 @@ class LeuSelect extends LitElement {
313
335
  * @internal
314
336
  * @param {MouseEvent} event
315
337
  */
316
- _defineProperty(this, "handleDocumentClick", event => {
338
+ _defineProperty(this, "_handleDocumentClick", event => {
317
339
  if (event.target instanceof Node && !this.contains(event.target) && this.open) {
318
- this.closeDropdown();
340
+ this._closeDropdown();
319
341
  }
320
342
  });
321
343
  /**
322
344
  * @internal
323
- * @param {KeyboardEvent} e
345
+ * @param {KeyboardEvent} event
324
346
  */
325
- _defineProperty(this, "handleKeyDown", event => {
347
+ _defineProperty(this, "_handleKeyDown", event => {
326
348
  if (event.key === "Escape") {
327
- this.closeDropdown();
349
+ this._closeDropdown();
328
350
  }
329
351
  });
330
352
  this.open = false;
@@ -334,199 +356,244 @@ class LeuSelect extends LitElement {
334
356
  this.clearable = false;
335
357
  this.filterable = false;
336
358
  this.value = [];
337
- this.options = [];
338
359
  this.label = "";
360
+ this.name = "";
339
361
 
340
362
  /** @internal */
341
- this._arrowIcon = Icon("angleDropDown");
363
+ this._optionFilter = "";
342
364
 
343
365
  /** @internal */
344
- this._clearIcon = Icon("clear");
366
+ this._hasFilterResults = true;
345
367
 
346
368
  /** @internal */
347
- this.optionFilter = "";
369
+ this._deferedChangeEvent = false;
348
370
 
349
371
  /** @internal */
350
- this.deferedChangeEvent = false;
372
+ this._displayValue = "";
351
373
 
352
- /**
353
- * @type {import("lit/directives/ref").Ref<import("../menu/Menu").LeuMenu>}
354
- */
355
- this.menuRef = createRef();
356
374
  /**
357
375
  * @type {import("lit/directives/ref").Ref<import("../input/Input").LeuInput>}
358
376
  */
359
- this.optionFilterRef = createRef();
377
+ this._optionFilterRef = createRef();
360
378
  /**
361
379
  * @type {import("lit/directives/ref").Ref<HTMLButtonElement>}
362
380
  */
363
- this.toggleButtonRef = createRef();
381
+ this._toggleButtonRef = createRef();
382
+
383
+ /**
384
+ * @type {import("lit/directives/ref").Ref<import("../menu/Menu").LeuMenu>}
385
+ */
386
+ this._menuRef = createRef();
364
387
  }
365
388
  connectedCallback() {
366
389
  super.connectedCallback();
367
- document.addEventListener("click", this.handleDocumentClick);
390
+ document.addEventListener("click", this._handleDocumentClick);
368
391
  }
369
392
  disconnectedCallback() {
370
393
  super.disconnectedCallback();
371
- document.removeEventListener("click", this.handleDocumentClick);
394
+ document.removeEventListener("click", this._handleDocumentClick);
372
395
  }
373
396
  updated(changedProperties) {
374
397
  if (changedProperties.has("open") && this.open) {
375
398
  if (this.filterable) {
376
- this.optionFilterRef.value.focus();
399
+ this._optionFilterRef.value.focus();
377
400
  } else {
378
- this.menuRef.value.focus();
401
+ this._menuRef.value.focusItem(0);
379
402
  }
380
403
  } else if (changedProperties.has("open") && !this.open) {
381
- this.toggleButtonRef.value.focus();
404
+ // TODO: Check if the ref is guaranteed to be set
405
+ // in the updated method.
406
+ // According to the lit documentation, a ref callback
407
+ // CAN be called with undefined.
408
+ this._toggleButtonRef.value?.focus();
409
+ }
410
+ if (changedProperties.has("value") || changedProperties.has("_optionFilter")) {
411
+ this._updateMenuItems({
412
+ value: changedProperties.has("value"),
413
+ optionFilter: changedProperties.has("_optionFilter")
414
+ });
382
415
  }
383
416
  }
384
- getDisplayValue(value) {
385
- if (this.multiple) {
386
- return value.length === 0 ? `` : `${value.length} gewählt`;
417
+
418
+ /**
419
+ * Apply the current state to the menu items.
420
+ * - Set the active property when the value property has changed.
421
+ * - Hide menu items that do not match the filter.
422
+ */
423
+ async _updateMenuItems(changed) {
424
+ /** @type {LeuMenu} */
425
+ const menu = this._menuRef.value;
426
+ await menu.updateComplete;
427
+ const menuItems = menu.getMenuItems();
428
+ let hasFilterResults = false;
429
+
430
+ /* eslint-disable no-param-reassign */
431
+ menuItems.forEach(menuItem => {
432
+ if (changed.optionFilter) {
433
+ menuItem.hidden = this._optionFilter !== "" && !menuItem.textContent.toLowerCase().includes(this._optionFilter.toLowerCase());
434
+ hasFilterResults = hasFilterResults || !menuItem.hidden;
435
+ }
436
+ if (changed.value) {
437
+ menuItem.active = this._isSelected(menuItem.getValue());
438
+ if (!this.multiple && menuItem.active) {
439
+ this._displayValue = menuItem.textContent;
440
+ }
441
+ }
442
+ });
443
+ /* eslint-enable no-param-reassign */
444
+
445
+ if (changed.optionFilter) {
446
+ this._hasFilterResults = hasFilterResults;
447
+ menu.setCurrentItem(0);
448
+ }
449
+ }
450
+ /**
451
+ * @internal
452
+ * @param {KeyboardEvent} event
453
+ */
454
+ async _handleToggleKeyDown(event) {
455
+ if (["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
456
+ event.preventDefault();
457
+ const menu = this._menuRef.value;
458
+ this.open = true;
459
+ await this.updateComplete;
460
+ if (event.key === "ArrowDown" || event.key === "Home") {
461
+ menu.focusItem(0);
462
+ } else if (event.key === "ArrowUp" || event.key === "End") {
463
+ menu.focusItem(-1);
464
+ }
387
465
  }
388
- return LeuSelect.getOptionLabel(value[0]);
389
466
  }
390
- getFilteredOptions() {
391
- return this.filterable && this.optionFilter.length > 0 ? this.options.filter(option => {
392
- const label = LeuSelect.getOptionLabel(option);
393
- return label.toLowerCase().includes(this.optionFilter.toLowerCase());
394
- }) : this.options;
467
+
468
+ /**
469
+ * @internal
470
+ * @param {KeyboardEvent} event
471
+ */
472
+ _handleFilterInputKeyDown(event) {
473
+ if (event.key === "ArrowDown") {
474
+ this._menuRef.value.focusItem(0);
475
+ } else if (event.key === "ArrowUp") {
476
+ this._menuRef.value.focusItem(-1);
477
+ }
395
478
  }
396
- emitUpdateEvents() {
397
- this.emitInputEvent();
398
- this.emitChangeEvent();
479
+
480
+ /**
481
+ * Determines the value or label that should be displayed inside the toggle button.
482
+ * @returns {String | nothing}
483
+ */
484
+ _getDisplayValue() {
485
+ if (this.multiple) {
486
+ return this.value.length === 0 ? `` : `${this.value.length} gewählt`;
487
+ }
488
+ return this._displayValue ?? nothing;
399
489
  }
400
- emitInputEvent() {
490
+ _emitInputEvent() {
401
491
  const inputevent = new CustomEvent("input", {
402
492
  composed: true,
403
493
  bubbles: true
404
494
  });
405
495
  this.dispatchEvent(inputevent);
406
496
  }
407
- emitChangeEvent() {
497
+ _emitChangeEvent() {
408
498
  const changeevent = new CustomEvent("change", {
409
499
  composed: true,
410
500
  bubbles: true
411
501
  });
412
502
  this.dispatchEvent(changeevent);
413
503
  }
414
- clearValue(event) {
504
+ _clearValue(event) {
415
505
  if (!this.disabled) {
416
506
  event.stopPropagation();
417
507
  this.value = [];
418
508
  }
419
- this.emitUpdateEvents();
509
+ this._emitInputEvent();
510
+ this._emitChangeEvent();
420
511
  }
421
- toggleDropdown() {
512
+ _toggleDropdown() {
422
513
  if (!this.disabled) {
423
514
  this.open = !this.open;
424
515
  }
425
516
  }
426
- openDropdown() {
427
- this.open = true;
428
- }
429
- closeDropdown() {
517
+ _closeDropdown() {
430
518
  this.open = false;
431
- if (this.deferedChangeEvent) {
432
- this.emitChangeEvent();
433
- this.deferedChangeEvent = false;
519
+ if (this._deferedChangeEvent) {
520
+ this._emitChangeEvent();
521
+ this._deferedChangeEvent = false;
434
522
  }
435
523
  }
524
+ _handleFilterInput(event) {
525
+ this._optionFilter = event.target.value;
526
+ }
436
527
 
437
528
  /**
438
- * Adds or replaces the given option in the options array.
439
- *
440
- * @param {*} option
529
+ * Checks if the given value is selected.
530
+ * @param {String} menuItemValue
531
+ * @returns {Boolean}
441
532
  */
442
- selectOption(option) {
443
- const isSelected = this.isSelected(option);
533
+ _isSelected(menuItemValue) {
534
+ return this.value.includes(menuItemValue);
535
+ }
536
+ _handleMenuItemClick(event) {
537
+ if (!(event.target instanceof LeuMenuItem) || event.target.disabled) {
538
+ return;
539
+ }
540
+
541
+ /** @type {LeuMenuItem} */
542
+ const menuItem = event.target;
543
+ const value = menuItem.getValue();
544
+ const isSelected = this._isSelected(value);
444
545
  if (this.multiple) {
445
- this.value = isSelected ? this.value.filter(v => v !== option) : this.value.concat(option);
446
- this.deferedChangeEvent = true;
546
+ this.value = isSelected ? this.value.filter(v => v !== value) : this.value.concat(value);
547
+ this._deferedChangeEvent = true;
447
548
  } else {
448
- this.value = isSelected ? [] : [option];
549
+ this.value = isSelected ? [] : [value];
550
+ this._displayValue = isSelected ? "" : menuItem.textContent;
449
551
  }
450
- this.emitInputEvent();
552
+ this._emitInputEvent();
451
553
  if (!this.multiple) {
452
- this.closeDropdown();
554
+ this._closeDropdown();
453
555
  }
454
556
  }
455
- handleApplyClick() {
456
- this.closeDropdown();
457
- }
458
- handleFilterInput(event) {
459
- this.optionFilter = event.target.value;
460
- }
461
- isSelected(option) {
462
- return this.value.includes(option);
463
- }
464
- renderMenu() {
465
- const menuClasses = {
466
- "select-menu": true,
467
- multiple: this.multiple
468
- };
469
- const filteredOptions = this.getFilteredOptions();
470
- return html`
471
- <leu-menu
472
- role="listbox"
473
- class=${classMap(menuClasses)}
474
- aria-multiselectable="${this.multiple}"
475
- aria-labelledby="select-label"
476
- ref=${ref(this.menuRef)}
477
- >
478
- ${filteredOptions.length > 0 ? map(this.getFilteredOptions(), option => {
479
- const isSelected = this.isSelected(option);
480
- let beforeIcon;
481
- if (this.multiple && isSelected) {
482
- beforeIcon = "check";
483
- } else if (this.multiple) {
484
- beforeIcon = "EMPTY";
485
- }
486
- return html`<leu-menu-item
487
- before=${ifDefined(beforeIcon)}
488
- @click=${() => this.selectOption(option)}
489
- role="option"
490
- label=${LeuSelect.getOptionLabel(option)}
491
- ?active=${isSelected}
492
- aria-selected=${isSelected}
493
- >
494
- </leu-menu-item>`;
495
- }) : html`<leu-menu-item
496
- label=${this.optionFilter === "" ? "Keine Optionen" : "Keine Resultate"}
497
- disabled
498
- ></leu-menu-item>`}
499
- </leu-menu>
500
- `;
557
+
558
+ /**
559
+ * Close the dropdown if the focus moves outside the component.
560
+ */
561
+ _handlePopupFocusOut(event) {
562
+ if (!this.contains(event.relatedTarget) && !this.shadowRoot.contains(event.relatedTarget)) {
563
+ this._closeDropdown();
564
+ }
501
565
  }
502
- renderFilterInput() {
566
+ _renderFilterInput() {
503
567
  if (this.filterable) {
504
568
  return html` <leu-input
505
569
  class="select-search"
506
570
  size="small"
507
- @input=${this.handleFilterInput}
571
+ @input=${this._handleFilterInput}
572
+ @keydown=${this._handleFilterInputKeyDown}
508
573
  clearable
509
- ref=${ref(this.optionFilterRef)}
574
+ ref=${ref(this._optionFilterRef)}
510
575
  label="Nach Stichwort filtern"
511
576
  ></leu-input>`;
512
577
  }
513
578
  return nothing;
514
579
  }
515
- renderApplyButton() {
580
+ _renderApplyButton() {
516
581
  if (this.multiple) {
517
582
  return html`
518
- <leu-button
519
- type="button"
520
- class="apply-button"
521
- @click=${this.handleApplyClick}
522
- fluid
523
- >Anwenden</leu-button
524
- >
583
+ <div class="apply-button-wrapper">
584
+ <leu-button
585
+ type="button"
586
+ class="apply-button"
587
+ @click=${this._closeDropdown}
588
+ fluid
589
+ >Anwenden</leu-button
590
+ >
591
+ </div>
525
592
  `;
526
593
  }
527
594
  return nothing;
528
595
  }
529
- renderToggleButton() {
596
+ _renderToggleButton() {
530
597
  const toggleClasses = {
531
598
  "select-toggle": true,
532
599
  open: this.open,
@@ -534,27 +601,31 @@ class LeuSelect extends LitElement {
534
601
  labeled: this.label !== ""
535
602
  };
536
603
  return html`<button
604
+ ${ref(this._toggleButtonRef)}
537
605
  type="button"
538
606
  class=${classMap(toggleClasses)}
539
- @click=${this.toggleDropdown}
540
- aria-controls="select-dialog"
541
- aria-haspopup="dialog"
607
+ @click=${this._toggleDropdown}
608
+ @keydown=${this._handleToggleKeyDown}
609
+ ?disabled=${this.disabled}
610
+ aria-controls="select-popup"
542
611
  aria-expanded="${this.open}"
612
+ aria-labelledby="select-label"
543
613
  role="combobox"
544
- ref=${ref(this.toggleButtonRef)}
545
614
  slot="anchor"
546
615
  >
547
616
  <span class="label" id="select-label">${this.label}</span>
548
- <span class="value"> ${this.getDisplayValue(this.value)} </span>
549
- <span class="arrow-icon"> ${this._arrowIcon} </span>
617
+ <span class="value"> ${this._getDisplayValue()} </span>
618
+ <span class="arrow-icon">
619
+ <leu-icon name="angleDropDown"></leu-icon>
620
+ </span>
550
621
  ${this.clearable && this.value.length !== 0 ? html`<button
551
622
  type="button"
552
623
  class="clear-button"
553
- @click=${this.clearValue}
624
+ @click=${this._clearValue}
554
625
  aria-label=${`${this.label} zurücksetzen`}
555
626
  ?disabled=${this.disabled}
556
627
  >
557
- ${this._clearIcon}
628
+ <leu-icon name="clear"></leu-icon>
558
629
  </button>` : nothing}
559
630
  </button>`;
560
631
  }
@@ -564,36 +635,61 @@ class LeuSelect extends LitElement {
564
635
  "select--has-before": this.hasSlotController.test("before"),
565
636
  "select--has-after": this.hasSlotController.test("after")
566
637
  };
638
+
639
+ /*
640
+ * We use the click event listener with the event delegation pattern
641
+ * so this is not a violation of the rule.
642
+ */
643
+ /* eslint-disable lit-a11y/click-events-have-key-events */
567
644
  return html`<div
568
- class=${classMap(selectClasses)}
569
- ?disabled=${this.disabled}
570
- aria-readonly="${this.disabled}"
571
- aria-labelledby="select-label"
572
- @keydown=${this.handleKeyDown}
573
- >
574
- <leu-popup
575
- ?active=${this.open}
576
- placement="bottom-start"
577
- flip
578
- matchSize="width"
579
- autoSize="height"
580
- autoSizePadding="8"
645
+ class=${classMap(selectClasses)}
646
+ @keydown=${this._handleKeyDown}
581
647
  >
582
- ${this.renderToggleButton()}
583
- <dialog
584
- id="select-dialog"
585
- class="select-menu-container"
586
- ?open=${this.open}
648
+ <leu-popup
649
+ ?active=${this.open}
650
+ placement="bottom-start"
651
+ flip
652
+ matchSize="width"
653
+ autoSize="height"
654
+ autoSizePadding="8"
587
655
  >
588
- <slot name="before" class="before"></slot>
589
- ${this.renderFilterInput()} ${this.renderMenu()}
590
- ${this.renderApplyButton()}
591
- <slot name="after" class="after"></slot>
592
- </dialog>
593
- </leu-popup>
594
- </div> `;
656
+ ${this._renderToggleButton()}
657
+ <div
658
+ id="select-popup"
659
+ class="select-menu-container"
660
+ @focusout=${this._handlePopupFocusOut}
661
+ >
662
+ <slot name="before" class="before"></slot>
663
+ ${this._renderFilterInput()}
664
+ <leu-menu
665
+ ref=${ref(this._menuRef)}
666
+ role="listbox"
667
+ aria-multiselectable=${ifDefined(this.multiple ? "true" : undefined)}
668
+ class="menu"
669
+ @click=${this._handleMenuItemClick}
670
+ >
671
+ <slot></slot>
672
+ </leu-menu>
673
+ ${this._hasFilterResults ? nothing : html` <p class="filter-message-empty" aria-live="polite">
674
+ Keine Resultate
675
+ </p>`}
676
+ ${this._renderApplyButton()}
677
+ <slot name="after" class="after"></slot>
678
+ </div>
679
+ </leu-popup>
680
+ </div>
681
+ <input type="hidden" name=${this.name} .value=${this.value.join(",")} />`;
682
+ /* eslint-enable lit-a11y/click-events-have-key-events */
595
683
  }
596
684
  }
685
+ _defineProperty(LeuSelect, "dependencies", {
686
+ "leu-button": LeuButton,
687
+ "leu-menu": LeuMenu,
688
+ "leu-menu-item": LeuMenuItem,
689
+ "leu-icon": LeuIcon,
690
+ "leu-input": LeuInput,
691
+ "leu-popup": LeuPopup
692
+ });
597
693
  _defineProperty(LeuSelect, "styles", css_248z);
598
694
 
599
695
  export { LeuSelect };