@statistikzh/leu 0.0.2

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 (141) hide show
  1. package/.editorconfig +29 -0
  2. package/.eslintrc.json +27 -0
  3. package/.github/workflows/publish.yml +19 -0
  4. package/.github/workflows/release-please.yml +19 -0
  5. package/.github/workflows/test.yml +38 -0
  6. package/.husky/commit-msg +4 -0
  7. package/.husky/pre-commit +4 -0
  8. package/.prettierignore +1 -0
  9. package/.storybook/main.js +11 -0
  10. package/.storybook/preview-head.html +5 -0
  11. package/.storybook/preview.js +23 -0
  12. package/CHANGELOG.md +63 -0
  13. package/CODE_OF_CONDUCT.md +128 -0
  14. package/CONTRIBUTING.md +31 -0
  15. package/LICENSE +21 -0
  16. package/README.md +170 -0
  17. package/commitlint.config.cjs +1 -0
  18. package/dist/Button-83c6df93.js +403 -0
  19. package/dist/Checkbox.js +144 -0
  20. package/dist/CheckboxGroup.js +82 -0
  21. package/dist/Chip-60af1402.js +162 -0
  22. package/dist/ChipGroup.js +79 -0
  23. package/dist/ChipLink.js +46 -0
  24. package/dist/ChipRemovable.js +43 -0
  25. package/dist/ChipSelectable.js +92 -0
  26. package/dist/Input.js +686 -0
  27. package/dist/Radio.js +156 -0
  28. package/dist/RadioGroup.js +194 -0
  29. package/dist/Select.js +859 -0
  30. package/dist/Table-72d305d7.js +506 -0
  31. package/dist/Table.js +8 -0
  32. package/dist/defineElement-47d4f665.js +15 -0
  33. package/dist/icon-b68c7e1e.js +202 -0
  34. package/dist/index.js +21 -0
  35. package/dist/leu-checkbox-group.js +6 -0
  36. package/dist/leu-checkbox.js +6 -0
  37. package/dist/leu-chip-group.js +5 -0
  38. package/dist/leu-chip-link.js +6 -0
  39. package/dist/leu-chip-removable.js +7 -0
  40. package/dist/leu-chip-selectable.js +6 -0
  41. package/dist/leu-input.js +9 -0
  42. package/dist/leu-radio-group.js +6 -0
  43. package/dist/leu-radio.js +5 -0
  44. package/dist/leu-select.js +12 -0
  45. package/dist/leu-table.js +10 -0
  46. package/dist/theme.css +51 -0
  47. package/index.js +10 -0
  48. package/package.json +85 -0
  49. package/postcss.config.cjs +14 -0
  50. package/rollup.config.js +54 -0
  51. package/scripts/generate-component/generate.js +167 -0
  52. package/scripts/generate-component/templates/[Name].js +33 -0
  53. package/scripts/generate-component/templates/[name].css +11 -0
  54. package/scripts/generate-component/templates/[namespace]-[name].js +3 -0
  55. package/scripts/generate-component/templates/stories/[name].stories.js +13 -0
  56. package/scripts/generate-component/templates/test/[name].test.js +22 -0
  57. package/src/components/button/Button.js +150 -0
  58. package/src/components/button/button.css +232 -0
  59. package/src/components/button/leu-button.js +3 -0
  60. package/src/components/button/stories/button.stories.js +333 -0
  61. package/src/components/button/test/button.test.js +22 -0
  62. package/src/components/button-group/ButtonGroup.js +63 -0
  63. package/src/components/button-group/button-group.css +10 -0
  64. package/src/components/button-group/leu-button-group.js +3 -0
  65. package/src/components/button-group/stories/button-group.stories.js +41 -0
  66. package/src/components/button-group/test/button-group.test.js +22 -0
  67. package/src/components/checkbox/Checkbox.js +142 -0
  68. package/src/components/checkbox/CheckboxGroup.js +80 -0
  69. package/src/components/checkbox/leu-checkbox-group.js +3 -0
  70. package/src/components/checkbox/leu-checkbox.js +3 -0
  71. package/src/components/checkbox/stories/checkbox-group.stories.js +52 -0
  72. package/src/components/checkbox/stories/checkbox.stories.js +43 -0
  73. package/src/components/checkbox/test/checkbox.test.js +101 -0
  74. package/src/components/chip/Chip.js +24 -0
  75. package/src/components/chip/ChipGroup.js +71 -0
  76. package/src/components/chip/ChipLink.js +45 -0
  77. package/src/components/chip/ChipRemovable.js +42 -0
  78. package/src/components/chip/ChipSelectable.js +91 -0
  79. package/src/components/chip/chip-group.css +5 -0
  80. package/src/components/chip/chip.css +130 -0
  81. package/src/components/chip/exports.js +10 -0
  82. package/src/components/chip/leu-chip-group.js +3 -0
  83. package/src/components/chip/leu-chip-link.js +3 -0
  84. package/src/components/chip/leu-chip-removable.js +3 -0
  85. package/src/components/chip/leu-chip-selectable.js +3 -0
  86. package/src/components/chip/stories/chip-group.stories.js +99 -0
  87. package/src/components/chip/stories/chip-link.stories.js +37 -0
  88. package/src/components/chip/stories/chip-removable.stories.js +28 -0
  89. package/src/components/chip/stories/chip-selectable.stories.js +46 -0
  90. package/src/components/chip/test/chip.test.js +22 -0
  91. package/src/components/dropdown/Dropdown.js +55 -0
  92. package/src/components/dropdown/dropdown.css +17 -0
  93. package/src/components/dropdown/leu-dropdown.js +3 -0
  94. package/src/components/dropdown/stories/dropdown.stories.js +25 -0
  95. package/src/components/dropdown/test/dropdown.test.js +31 -0
  96. package/src/components/icon/icon.js +201 -0
  97. package/src/components/input/Input.js +421 -0
  98. package/src/components/input/input.css +231 -0
  99. package/src/components/input/leu-input.js +3 -0
  100. package/src/components/input/stories/input.stories.js +185 -0
  101. package/src/components/input/test/input.test.js +22 -0
  102. package/src/components/menu/Menu.js +18 -0
  103. package/src/components/menu/MenuItem.js +95 -0
  104. package/src/components/menu/leu-menu-item.js +3 -0
  105. package/src/components/menu/leu-menu.js +3 -0
  106. package/src/components/menu/menu-item.css +72 -0
  107. package/src/components/menu/menu.css +14 -0
  108. package/src/components/menu/stories/menu-item.stories.js +51 -0
  109. package/src/components/menu/stories/menu.stories.js +21 -0
  110. package/src/components/menu/test/menu.test.js +22 -0
  111. package/src/components/pagination/Pagination.js +152 -0
  112. package/src/components/pagination/leu-pagination.js +3 -0
  113. package/src/components/pagination/pagination.css +49 -0
  114. package/src/components/pagination/stories/pagination.stories.js +82 -0
  115. package/src/components/pagination/test/pagination.test.js +22 -0
  116. package/src/components/radio/Radio.js +62 -0
  117. package/src/components/radio/RadioGroup.js +193 -0
  118. package/src/components/radio/leu-radio-group.js +3 -0
  119. package/src/components/radio/leu-radio.js +3 -0
  120. package/src/components/radio/radio.css +76 -0
  121. package/src/components/radio/stories/radio-group.stories.js +49 -0
  122. package/src/components/radio/stories/radio.stories.js +48 -0
  123. package/src/components/radio/test/radio.test.js +38 -0
  124. package/src/components/select/Select.js +350 -0
  125. package/src/components/select/leu-select.js +3 -0
  126. package/src/components/select/select.css +215 -0
  127. package/src/components/select/stories/select.stories.js +302 -0
  128. package/src/components/select/test/select.test.js +29 -0
  129. package/src/components/table/Table.js +301 -0
  130. package/src/components/table/leu-table.js +3 -0
  131. package/src/components/table/stories/table.stories.js +116 -0
  132. package/src/components/table/test/table.test.js +36 -0
  133. package/src/lib/defineElement.js +13 -0
  134. package/src/lib/hasSlotController.js +85 -0
  135. package/src/styles/custom-media.css +5 -0
  136. package/src/styles/custom-properties.css +51 -0
  137. package/src/styles/theme.css +1 -0
  138. package/stat_zh.png +0 -0
  139. package/stylelint.config.mjs +21 -0
  140. package/web-dev-server-storybook.config.mjs +19 -0
  141. package/web-test-runner.config.mjs +49 -0
package/dist/Select.js ADDED
@@ -0,0 +1,859 @@
1
+ import { css, LitElement, html, nothing } from 'lit';
2
+ import { classMap } from 'lit/directives/class-map.js';
3
+ import { map } from 'lit/directives/map.js';
4
+ import { ifDefined } from 'lit/directives/if-defined.js';
5
+ import { createRef, ref } from 'lit/directives/ref.js';
6
+ import { a as ICON_NAMES, I as Icon } from './icon-b68c7e1e.js';
7
+ import { d as defineElement } from './defineElement-47d4f665.js';
8
+ import { d as defineButtonElements } from './Button-83c6df93.js';
9
+ import { defineInputElements } from './Input.js';
10
+
11
+ /**
12
+ * Thanks Shoelace!
13
+ * Source: https://github.com/shoelace-style/shoelace/blob/next/src/internal/slot.ts
14
+ */
15
+
16
+ /**
17
+ * A reactive controller that determines when slots exist.
18
+ * @implements { import("lit").ReactiveController }
19
+ */
20
+ class HasSlotController {
21
+ constructor(host, slotNames) {
22
+ this.host = host;
23
+ host.addController(this);
24
+
25
+ this.slotNames = slotNames;
26
+ }
27
+
28
+ /**
29
+ * @private
30
+ * @returns {Boolean}
31
+ */
32
+ hasDefaultSlot() {
33
+ return [...this.host.childNodes].some((node) => {
34
+ if (node.nodeType === node.TEXT_NODE && node.textContent.trim() !== "") {
35
+ return true
36
+ }
37
+
38
+ if (node.nodeType === node.ELEMENT_NODE) {
39
+ const el = node;
40
+
41
+ // If it doesn't have a slot attribute, it's part of the default slot
42
+ if (!el.hasAttribute("slot")) {
43
+ return true
44
+ }
45
+ }
46
+
47
+ return false
48
+ })
49
+ }
50
+
51
+ /**
52
+ * @private
53
+ * @param {String} name
54
+ * @returns {Boolean}
55
+ */
56
+ hasNamedSlot(name) {
57
+ return this.host.querySelector(`:scope > [slot="${name}"]`) !== null
58
+ }
59
+
60
+ /**
61
+ * @param {String} slotName
62
+ * @returns {Boolean}
63
+ */
64
+ test(slotName) {
65
+ return slotName === "[default]"
66
+ ? this.hasDefaultSlot()
67
+ : this.hasNamedSlot(slotName)
68
+ }
69
+
70
+ hostConnected() {
71
+ this.host.shadowRoot.addEventListener("slotchange", this.handleSlotChange);
72
+ }
73
+
74
+ hostDisconnected() {
75
+ this.host.shadowRoot.removeEventListener(
76
+ "slotchange",
77
+ this.handleSlotChange
78
+ );
79
+ }
80
+
81
+ /**
82
+ * @private
83
+ * @param {Event} event
84
+ */
85
+ handleSlotChange = (event) => {
86
+ const slot = event.target;
87
+
88
+ if (
89
+ (this.slotNames.includes("[default]") && !slot.name) ||
90
+ (slot.name && this.slotNames.includes(slot.name))
91
+ ) {
92
+ this.host.requestUpdate();
93
+ }
94
+ }
95
+ }
96
+
97
+ var css_248z$2 = css`:host,
98
+ :host * {
99
+ box-sizing: border-box;
100
+ }
101
+
102
+ :host {
103
+ --menu-divider-color: var(--leu-color-black-transp-20);
104
+ }
105
+
106
+ :host ::slotted(hr) {
107
+ border: 0;
108
+ border-top: 1px solid var(--menu-divider-color);
109
+ margin: 0.5rem 0;
110
+ }
111
+ `;
112
+
113
+ /**
114
+ * @tagname leu-menu
115
+ */
116
+ class LeuMenu extends LitElement {
117
+ static styles = css_248z$2
118
+
119
+ render() {
120
+ return html`<slot></slot>`
121
+ }
122
+ }
123
+
124
+ function defineMenuElements() {
125
+ defineElement("menu", LeuMenu);
126
+ }
127
+
128
+ var css_248z$1 = css`:host,
129
+ :host * {
130
+ box-sizing: border-box;
131
+ }
132
+
133
+ :host {
134
+ --background: var(--leu-color-black-0);
135
+ --background-hover: var(--leu-color-black-10);
136
+ --background-active: var(--leu-color-func-cyan);
137
+ --background-disabled: var(--leu-color-black-0);
138
+ --color: var(--leu-color-black-transp-60);
139
+ --font-regular: var(--leu-font-regular);
140
+ --font-black: var(--leu-font-black);
141
+
142
+ font-family: var(--chip-font-regular);
143
+ }
144
+
145
+ .button {
146
+ -webkit-appearance: none;
147
+ -moz-appearance: none;
148
+ appearance: none;
149
+ border: none;
150
+ cursor: pointer;
151
+
152
+ display: flex;
153
+ align-items: center;
154
+ gap: 0.5rem;
155
+ width: 100%;
156
+
157
+ padding: 0.75rem;
158
+
159
+ font-size: 1rem;
160
+ line-height: 1.5;
161
+ text-align: left;
162
+
163
+ background: var(--background);
164
+ color: var(--color);
165
+ }
166
+
167
+ .button:focus-visible {
168
+ outline: 2px solid var(--leu-color-func-cyan);
169
+ outline-offset: 2px;
170
+ }
171
+
172
+ .button:hover,
173
+ :host([highlighted]) .button {
174
+ --background: var(--background-hover);
175
+ }
176
+
177
+ :host([active]) .button {
178
+ --background: var(--background-active);
179
+ }
180
+
181
+ :host([disabled]) .button {
182
+ --background: var(--background-disabled);
183
+ cursor: default;
184
+ }
185
+
186
+ .before svg, .after svg {
187
+ display: block;
188
+ }
189
+
190
+ .label {
191
+ flex: 1;
192
+ overflow: hidden;
193
+ text-overflow: ellipsis;
194
+ white-space: nowrap;
195
+ }
196
+
197
+ .icon-placeholder {
198
+ display: block;
199
+ width: 1.5rem;
200
+ aspect-ratio: 1;
201
+ }
202
+ `;
203
+
204
+ /**
205
+ * @tagname leu-menu-item
206
+ * @slot - The label of the menu item
207
+ */
208
+ class LeuMenuItem extends LitElement {
209
+ static styles = css_248z$1
210
+
211
+ /**
212
+ * @internal
213
+ */
214
+ static shadowRootOptions = {
215
+ ...LitElement.shadowRootOptions,
216
+ delegatesFocus: true,
217
+ }
218
+
219
+ static properties = {
220
+ /**
221
+ * Can be either an icon name or a text
222
+ * If no icon with this value is found, it will be displayed as text.
223
+ * If the value is "EMPTY", an empty placeholder with the size of an icon will be displayed.
224
+ */
225
+ before: { type: String },
226
+ /**
227
+ * Can be either an icon name or a text
228
+ * If no icon with this value is found, it will be displayed as text
229
+ * If the value is "EMPTY", an empty placeholder with the size of an icon will be displayed.
230
+ */
231
+ after: { type: String },
232
+ active: { type: Boolean, reflect: true },
233
+ highlighted: { type: Boolean, reflect: true },
234
+ disabled: { type: Boolean, reflect: true },
235
+ }
236
+
237
+ constructor() {
238
+ super();
239
+
240
+ this.active = false;
241
+ this.disabled = false;
242
+ this.before = "";
243
+ this.after = "";
244
+
245
+ /**
246
+ * A programmatic way to highlight the menu item like it is hovered.
247
+ * This is just a visual effect and does not change the active state.
248
+ */
249
+ this.highlighted = false;
250
+ }
251
+
252
+ static getIconOrText(name) {
253
+ if (ICON_NAMES.includes(name)) {
254
+ return Icon(name)
255
+ }
256
+
257
+ if (name === "EMPTY") {
258
+ return html`<div class="icon-placeholder"></div>`
259
+ }
260
+
261
+ return name
262
+ }
263
+
264
+ renderBefore() {
265
+ if (this.before !== "") {
266
+ const content = LeuMenuItem.getIconOrText(this.before);
267
+ return html`<span class="before">${content}</span>`
268
+ }
269
+
270
+ return nothing
271
+ }
272
+
273
+ renderAfter() {
274
+ if (this.after !== "") {
275
+ const content = LeuMenuItem.getIconOrText(this.after);
276
+ return html`<span class="after">${content}</span>`
277
+ }
278
+
279
+ return nothing
280
+ }
281
+
282
+ render() {
283
+ return html`<button class="button" ?disabled=${this.disabled}>
284
+ ${this.renderBefore()}<span class="label"><slot></slot></span
285
+ >${this.renderAfter()}
286
+ </button>`
287
+ }
288
+ }
289
+
290
+ function defineMenuItemElements() {
291
+ defineElement("menu-item", LeuMenuItem);
292
+ }
293
+
294
+ var css_248z = css`:host,
295
+ :host * {
296
+ box-sizing: border-box;
297
+ }
298
+
299
+ :host {
300
+ --select-color: var(--leu-color-black-100);
301
+ --select-color-disabled: var(--leu-color-black-20);
302
+ --select-color-invalid: var(--leu-color-func-red);
303
+ --select-color-focus: var(--leu-color-func-cyan);
304
+ --select-border-width: 2px;
305
+
306
+ --select-label-color: var(--leu-color-black-100);
307
+ --select-label-color-disabled: var(--select-color-disabled);
308
+ --select-label-color-empty: var(--leu-color-black-60);
309
+
310
+ --select-option-color: var(--leu-color-black-60);
311
+ --select-option-color-focus: var(--select-color);
312
+
313
+ --select-border-color: var(--leu-color-black-40);
314
+ --select-border-color-focus: var(--select-color-focus);
315
+ --select-border-color-disabled: var(--leu-color-black-20);
316
+ --select-border-color-invalid: var(--select-color-invalid);
317
+
318
+ --select-error-color: var(--leu-color-black-0);
319
+
320
+ --select-clear-color: var(--leu-color-black-60);
321
+
322
+ --select-font-regular: var(--leu-font-regular);
323
+ --select-font-black: var(--leu-font-black);
324
+
325
+ --select-apply-button-color: var(--leu-color-black-100);
326
+ --select-apply-button-color-focus: var(--leu-color-black-80);
327
+ --select-apply-button-font-color: var(--leu-color-black-0);
328
+
329
+ --select-box-shadow-short: var(--leu-box-shadow-short);
330
+ --select-box-shadow-regular: var(--leu-box-shadow-regular);
331
+
332
+ position: relative;
333
+ display: block;
334
+
335
+ font-family: var(--leu-font-regular);
336
+
337
+ font-family: var(--select-font-regular);
338
+ }
339
+
340
+ .select[disabled] {
341
+ --select-color: var(--select-color-disabled);
342
+ --select-color-focus: var(--select-color-disabled);
343
+ --select-border-color: var(--select-border-color-disabled);
344
+ --select-label-color: var(--select-label-color-disabled);
345
+ --select-border-color-focus: var(--select-border-color-disabled);
346
+ --select-clear-color: var(--select-color-disabled);
347
+ }
348
+
349
+ .select-toggle {
350
+ min-height: 4.5rem;
351
+ display: block;
352
+ width: 100%;
353
+
354
+ -webkit-appearance: none;
355
+
356
+ -moz-appearance: none;
357
+
358
+ appearance: none;
359
+ border: var(--select-border-width) solid var(--select-border-color);
360
+ border-radius: 2px;
361
+ font-size: 1rem;
362
+ line-height: 1.5;
363
+ padding: 1.375rem 3rem 1.375rem 1rem;
364
+
365
+ background: none;
366
+ overflow: hidden;
367
+ text-overflow: ellipsis;
368
+ white-space: nowrap;
369
+
370
+ cursor: pointer;
371
+ text-align: left;
372
+ }
373
+
374
+ .select-toggle:hover,
375
+ .select-toggle:focus {
376
+ border-color: var(--select-border-color-focus);
377
+ }
378
+
379
+ .select-toggle.filled.labeled {
380
+ padding-bottom: 0.75rem;
381
+ padding-top: 2rem;
382
+ color: var(--select-color);
383
+ }
384
+
385
+ .select-toggle:focus-visible {
386
+ outline: var(--select-border-width) solid var(--select-border-color-focus);
387
+ outline-offset: 2px;
388
+ }
389
+
390
+ .label {
391
+ position: absolute;
392
+ top: 1.5rem;
393
+ transition: top 0.1s ease;
394
+ font-family: var(--select-font-regular);
395
+ }
396
+
397
+ .clear-button {
398
+ --_length: 1.5rem;
399
+
400
+ width: 1.5rem;
401
+
402
+ width: var(--_length);
403
+ height: 1.5rem;
404
+ height: var(--_length);
405
+ padding: 0;
406
+
407
+ position: absolute;
408
+ top: calc(50% - 1.5rem / 2);
409
+ top: calc(50% - var(--_length) / 2);
410
+ right: 2.6rem;
411
+
412
+ cursor: pointer;
413
+
414
+ background: none;
415
+ color: var(--select-clear-color);
416
+ border: none;
417
+
418
+ /* border-radius is only defined for a nice focus outline */
419
+ border-radius: 2px;
420
+ }
421
+
422
+ .clear-button:focus-visible {
423
+ outline: var(--select-border-width) solid var(--select-border-color-focus);
424
+ outline-offset: 2px;
425
+ }
426
+
427
+ .select[disabled] .select-toggle,
428
+ .select[disabled] .clear-button {
429
+ cursor: inherit;
430
+ }
431
+
432
+ .select[disabled] .label {
433
+ color: var(--select-label-color);
434
+ }
435
+
436
+ .select-toggle.open .label,
437
+ .select-toggle.filled .label,
438
+ .select:not([disabled]) .select-toggle:focus .label,
439
+ .select:not([disabled]) .select-toggle:active:not([disabled]) .label {
440
+ color: var(--select-label-color);
441
+ font-family: var(--select-font-black);
442
+ font-size: 0.75rem;
443
+
444
+ top: 0.875rem;
445
+
446
+ transition: top 0.1s ease;
447
+ }
448
+
449
+ .arrow-icon {
450
+ --_length: 1.5rem;
451
+
452
+ width: 1.5rem;
453
+
454
+ width: var(--_length);
455
+ height: 1.5rem;
456
+ height: var(--_length);
457
+ padding: 0;
458
+
459
+ position: absolute;
460
+ top: calc(50% - 1.5rem / 2);
461
+ top: calc(50% - var(--_length) / 2);
462
+ right: 1rem;
463
+
464
+ transform: rotate(0deg);
465
+ transition: transform 0.25s ease;
466
+
467
+ color: var(--select-color);
468
+ }
469
+
470
+ .select-toggle.open .arrow-icon {
471
+ transform: rotate(180deg);
472
+ }
473
+
474
+ .select-menu-container {
475
+ border-radius: 1px;
476
+ position: absolute;
477
+ left: 0;
478
+ width: 100%;
479
+ background-color: white;
480
+ border: 0 solid black;
481
+ padding: 0;
482
+ margin: 0;
483
+ top: calc(100% + 2px);
484
+ z-index: 10;
485
+ box-shadow: var(--select-box-shadow-regular), var(--select-box-shadow-short);
486
+ overflow: auto;
487
+ }
488
+
489
+ .select-menu {
490
+ display: block;
491
+ padding: 0;
492
+ margin: 0;
493
+ overflow: auto;
494
+ max-height: 24rem;
495
+ }
496
+
497
+ .select-menu.multiple {
498
+ max-height: 21rem;
499
+ }
500
+
501
+ .before,
502
+ .after {
503
+ display: block;
504
+ margin: 0.75rem;
505
+ }
506
+
507
+ .select:not(.select--has-before) .before {
508
+ display: none;
509
+ }
510
+
511
+ .select:not(.select--has-after) .after {
512
+ display: none;
513
+ }
514
+
515
+ .apply-button {
516
+ display: block;
517
+ margin: 0.75rem;
518
+ }
519
+
520
+ .select-search {
521
+ margin: 0.75rem;
522
+ }
523
+ `;
524
+
525
+ /**
526
+ * @tagname leu-select
527
+ * @slot before - Optional content the appears before the option list
528
+ * @slot after - Optional content the appears after the option list
529
+ */
530
+ class LeuSelect extends LitElement {
531
+ static styles = css_248z
532
+
533
+ static get properties() {
534
+ return {
535
+ open: { type: Boolean, attribute: "open" },
536
+
537
+ label: { type: String },
538
+ options: { type: Array },
539
+ value: { type: Array },
540
+ clearable: { type: Boolean, reflect: true },
541
+ disabled: { type: Boolean, reflect: true },
542
+ filterable: { type: Boolean, reflect: true },
543
+ multiple: { type: Boolean, reflect: true },
544
+ optionFilter: { type: String, state: true },
545
+ }
546
+ }
547
+
548
+ static getOptionLabel(option) {
549
+ if (typeof option === "object" && option !== null) {
550
+ return option.label
551
+ }
552
+ return option
553
+ }
554
+
555
+ /**
556
+ * @internal
557
+ */
558
+ hasSlotController = new HasSlotController(this, ["before", "after"])
559
+
560
+ constructor() {
561
+ super();
562
+ this.open = false;
563
+ this.clearable = false;
564
+ this.value = [];
565
+ this.options = [];
566
+
567
+ /** @internal */
568
+ this._arrowIcon = Icon("angleDropDown");
569
+
570
+ /** @internal */
571
+ this._clearIcon = Icon("clear");
572
+
573
+ /** @internal */
574
+ this.optionFilter = "";
575
+
576
+ /** @internal */
577
+ this.deferedChangeEvent = false;
578
+
579
+ this.menuRef = createRef();
580
+ this.optionFilterRef = createRef();
581
+ this.toggleButtonRef = createRef();
582
+ }
583
+
584
+ updated(changedProperties) {
585
+ if (changedProperties.has("open") && this.open) {
586
+ if (this.multiple) {
587
+ this.optionFilterRef.value.focus();
588
+ } else {
589
+ this.menuRef.value.focus();
590
+ }
591
+ } else if (changedProperties.has("open") && !this.open) {
592
+ this.toggleButtonRef.value.focus();
593
+ }
594
+ }
595
+
596
+ /**
597
+ * @internal
598
+ * @param {KeyboardEvent} e
599
+ */
600
+ handleKeyDown = (event) => {
601
+ if (event.key === "Escape") {
602
+ this.closeDropdown();
603
+ }
604
+ }
605
+
606
+ getDisplayValue(value) {
607
+ if (this.multiple) {
608
+ return value.length === 0 ? `` : `${value.length} gewählt`
609
+ }
610
+
611
+ return LeuSelect.getOptionLabel(value[0])
612
+ }
613
+
614
+ getFilteredOptions() {
615
+ return this.filterable && this.optionFilter.length > 0
616
+ ? this.options.filter((option) => {
617
+ const label = LeuSelect.getOptionLabel(option);
618
+ return label.toLowerCase().includes(this.optionFilter.toLowerCase())
619
+ })
620
+ : this.options
621
+ }
622
+
623
+ emitUpdateEvents() {
624
+ this.emitInputEvent();
625
+ this.emitChangeEvent();
626
+ }
627
+
628
+ emitInputEvent() {
629
+ const inputevent = new CustomEvent("input", {
630
+ composed: true,
631
+ bubbles: true,
632
+ });
633
+ this.dispatchEvent(inputevent);
634
+ }
635
+
636
+ emitChangeEvent() {
637
+ const changeevent = new CustomEvent("change", {
638
+ composed: true,
639
+ bubbles: true,
640
+ });
641
+ this.dispatchEvent(changeevent);
642
+ }
643
+
644
+ clearValue(event) {
645
+ if (!this.disabled) {
646
+ event.stopPropagation();
647
+ this.value = [];
648
+ }
649
+
650
+ this.emitUpdateEvents();
651
+ }
652
+
653
+ clearOptionFilter() {
654
+ // refocus before removing the button, otherwise closeDropdown is triggered
655
+ this.optionFilterRef.value.focus();
656
+ this.optionFilter = "";
657
+ }
658
+
659
+ toggleDropdown() {
660
+ if (!this.disabled) {
661
+ this.open = !this.open;
662
+ }
663
+ }
664
+
665
+ openDropdown() {
666
+ this.open = true;
667
+ }
668
+
669
+ closeDropdown() {
670
+ this.open = false;
671
+
672
+ if (this.deferedChangeEvent) {
673
+ this.emitChangeEvent();
674
+ this.deferedChangeEvent = false;
675
+ }
676
+ }
677
+
678
+ /**
679
+ * Adds or replaces the given option in the options array.
680
+ *
681
+ * @param {*} option
682
+ */
683
+ selectOption(option) {
684
+ const isSelected = this.isSelected(option);
685
+
686
+ if (this.multiple) {
687
+ this.value = isSelected
688
+ ? this.value.filter((v) => v !== option)
689
+ : this.value.concat(option);
690
+
691
+ this.deferedChangeEvent = true;
692
+ } else {
693
+ this.value = isSelected ? [] : [option];
694
+ }
695
+
696
+ this.emitInputEvent();
697
+
698
+ if (!this.multiple) {
699
+ this.closeDropdown();
700
+ }
701
+ }
702
+
703
+ handleApplyClick() {
704
+ this.closeDropdown();
705
+ }
706
+
707
+ handleFilterInput(event) {
708
+ this.optionFilter = event.target.value;
709
+ }
710
+
711
+ isSelected(option) {
712
+ return this.value.includes(option)
713
+ }
714
+
715
+ renderMenu() {
716
+ const menuClasses = {
717
+ "select-menu": true,
718
+ multiple: this.multiple,
719
+ };
720
+
721
+ const filteredOptions = this.getFilteredOptions();
722
+
723
+ return html`
724
+ <leu-menu
725
+ role="listbox"
726
+ class=${classMap(menuClasses)}
727
+ aria-multiselectable="${this.multiple}"
728
+ aria-labelledby="select-label"
729
+ ref=${ref(this.menuRef)}
730
+ >
731
+ ${filteredOptions.length > 0
732
+ ? map(this.getFilteredOptions(), (option) => {
733
+ const isSelected = this.isSelected(option);
734
+ let beforeIcon;
735
+
736
+ if (this.multiple && isSelected) {
737
+ beforeIcon = "check";
738
+ } else if (this.multiple) {
739
+ beforeIcon = "EMPTY";
740
+ }
741
+
742
+ return html`<leu-menu-item
743
+ before=${ifDefined(beforeIcon)}
744
+ @click=${() => this.selectOption(option)}
745
+ role="option"
746
+ ?active=${isSelected}
747
+ aria-selected=${isSelected}
748
+ >
749
+ ${LeuSelect.getOptionLabel(option)}
750
+ </leu-menu-item>`
751
+ })
752
+ : html`<leu-menu-item disabled>Keine Resultate</leu-menu-item>`}
753
+ </leu-menu>
754
+ `
755
+ }
756
+
757
+ renderFilterInput() {
758
+ if (this.filterable) {
759
+ return html` <leu-input
760
+ class="select-search"
761
+ size="small"
762
+ @input=${this.handleFilterInput}
763
+ clearable
764
+ >Nach Stichwort filtern</leu-input
765
+ >`
766
+ }
767
+
768
+ return nothing
769
+ }
770
+
771
+ renderApplyButton() {
772
+ if (this.multiple) {
773
+ return html`
774
+ <leu-button
775
+ type="button"
776
+ class="apply-button"
777
+ @click=${this.handleApplyClick}
778
+ label="Anwenden"
779
+ fluid
780
+ ></leu-button>
781
+ `
782
+ }
783
+
784
+ return nothing
785
+ }
786
+
787
+ renderToggleButton() {
788
+ const toggleClasses = {
789
+ "select-toggle": true,
790
+ open: this.open,
791
+ filled: this.value.length !== 0 && this.value !== null,
792
+ labeled: this.label !== "",
793
+ };
794
+
795
+ return html`<button
796
+ type="button"
797
+ class=${classMap(toggleClasses)}
798
+ @click=${this.toggleDropdown}
799
+ aria-controls="select-dialog"
800
+ aria-haspopup="dialog"
801
+ aria-expanded="${this.open}"
802
+ role="combobox"
803
+ ref=${ref(this.toggleButtonRef)}
804
+ >
805
+ <span class="label" id="select-label">${this.label}</span>
806
+ <span class="value"> ${this.getDisplayValue(this.value)} </span>
807
+ <span class="arrow-icon"> ${this._arrowIcon} </span>
808
+ ${this.clearable && this.value !== "" && this.value.length !== 0
809
+ ? html`<button
810
+ type="button"
811
+ class="clear-button"
812
+ @click=${this.clearValue}
813
+ aria-label=${`${this.label} zurücksetzen`}
814
+ ?disabled=${this.disabled}
815
+ >
816
+ ${this._clearIcon}
817
+ </button>`
818
+ : nothing}
819
+ </button>`
820
+ }
821
+
822
+ render() {
823
+ const selectClasses = {
824
+ select: true,
825
+ "select--has-before": this.hasSlotController.test("before"),
826
+ "select--has-after": this.hasSlotController.test("after"),
827
+ };
828
+
829
+ return html`<div
830
+ class=${classMap(selectClasses)}
831
+ ?disabled=${this.disabled}
832
+ aria-readonly="${this.disabled}"
833
+ aria-labelledby="select-label"
834
+ @keydown=${this.handleKeyDown}
835
+ >
836
+ ${this.renderToggleButton()}
837
+ <dialog
838
+ id="select-dialog"
839
+ class="select-menu-container"
840
+ ?open=${this.open}
841
+ >
842
+ <slot name="before" class="before"></slot>
843
+ ${this.renderFilterInput()} ${this.renderMenu()}
844
+ ${this.renderApplyButton()}
845
+ <slot name="after" class="after"></slot>
846
+ </dialog>
847
+ </div> `
848
+ }
849
+ }
850
+
851
+ function defineSelectElements() {
852
+ defineButtonElements();
853
+ defineMenuElements();
854
+ defineMenuItemElements();
855
+ defineInputElements();
856
+ defineElement("select", LeuSelect);
857
+ }
858
+
859
+ export { LeuSelect, defineSelectElements };