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