@statistikzh/leu 0.26.0 → 0.27.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 (115) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +21 -0
  3. package/dist/{Accordion-B04QkmHz.js → Accordion-DLsqXcK8.js} +1 -1
  4. package/dist/Accordion.js +2 -2
  5. package/dist/{Button-BkhqVjug.js → Button-BSyDL_cV.js} +3 -3
  6. package/dist/Button.js +4 -4
  7. package/dist/{ButtonGroup-B8U9fDvM.js → ButtonGroup-BmSvl-Oc.js} +2 -2
  8. package/dist/ButtonGroup.js +5 -5
  9. package/dist/{ChartWrapper-CSMFwz9e.js → ChartWrapper-CvDvQsd5.js} +2 -2
  10. package/dist/ChartWrapper.js +3 -3
  11. package/dist/{Checkbox-Dd1QLpfn.js → Checkbox-Cl_X6gBJ.js} +2 -2
  12. package/dist/Checkbox.js +3 -3
  13. package/dist/{CheckboxGroup-Bz2eWEFL.js → CheckboxGroup-BKhOmZYX.js} +2 -2
  14. package/dist/CheckboxGroup.js +4 -4
  15. package/dist/{Chip-XAQIIsXq.js → Chip-McVP3N_x.js} +1 -1
  16. package/dist/Chip.js +2 -2
  17. package/dist/{ChipGroup-DLqfK2kn.js → ChipGroup-DUGavZeU.js} +1 -1
  18. package/dist/ChipGroup.js +3 -3
  19. package/dist/ChipLink.js +2 -2
  20. package/dist/ChipRemovable.js +3 -3
  21. package/dist/ChipSelectable.js +2 -2
  22. package/dist/{Dialog-DHuXR_oo.js → Dialog-BlDd4T2u.js} +2 -2
  23. package/dist/Dialog.js +3 -3
  24. package/dist/{Dropdown-DtFTePbc.js → Dropdown-BLxSIe6p.js} +5 -5
  25. package/dist/Dropdown.js +8 -8
  26. package/dist/{FileInput-b8sbLDPI.js → FileInput-DntYrpZ-.js} +22 -7
  27. package/dist/FileInput.d.ts +11 -0
  28. package/dist/FileInput.js +6 -6
  29. package/dist/{Icon-C_yYuynf.js → Icon-CbZXpyHU.js} +1 -1
  30. package/dist/Icon.js +2 -2
  31. package/dist/{Input-D2THgo7c.d.ts → Input-CeaAOB4p.d.ts} +6 -2
  32. package/dist/{Input-DEOVocTa.js → Input-DBXX7ev8.js} +32 -11
  33. package/dist/Input.d.ts +1 -1
  34. package/dist/Input.js +3 -3
  35. package/dist/{LeuElement-BeFrgKes.js → LeuElement-k4RjIeoG.js} +1 -1
  36. package/dist/{Menu-BeqqtCw6.js → Menu-Cu8eIF1T.js} +2 -2
  37. package/dist/Menu.js +4 -4
  38. package/dist/{MenuItem-DVg8-1Bq.js → MenuItem-Cs3KFhJm.js} +2 -2
  39. package/dist/MenuItem.js +3 -3
  40. package/dist/{Message-BhknWvAF.js → Message-C6Zlk_2p.js} +2 -2
  41. package/dist/Message.js +3 -3
  42. package/dist/{Pagination-DJI5MIi_.js → Pagination-CB2eVlXk.js} +4 -4
  43. package/dist/Pagination.js +6 -6
  44. package/dist/{Placeholder-BJybFwSg.js → Placeholder-DHMexMhK.js} +1 -1
  45. package/dist/Placeholder.js +2 -2
  46. package/dist/{Popup-DNlm_9AA.js → Popup-8jhVy8gB.js} +1 -1
  47. package/dist/Popup.js +2 -2
  48. package/dist/{ProgressBar-B0wYj1KF.js → ProgressBar-CG0_lHfS.js} +1 -1
  49. package/dist/ProgressBar.js +2 -2
  50. package/dist/{Radio-DMCL8c4D.js → Radio-DG3xqP3s.js} +1 -1
  51. package/dist/Radio.js +2 -2
  52. package/dist/{RadioGroup-CM6IyBlq.js → RadioGroup-BKCp9ICX.js} +2 -2
  53. package/dist/RadioGroup.js +3 -3
  54. package/dist/{Range-B72rtfln.js → Range-7LrESv4K.js} +1 -1
  55. package/dist/Range.js +2 -2
  56. package/dist/{ScrollTop-BFAqBVDR.js → ScrollTop-CJJsfniA.js} +20 -20
  57. package/dist/ScrollTop.d.ts +5 -5
  58. package/dist/ScrollTop.js +5 -5
  59. package/dist/{Select-vxl3BvD4.js → Select-CxEDXIBn.js} +153 -133
  60. package/dist/Select.d.ts +73 -71
  61. package/dist/Select.js +9 -9
  62. package/dist/{Spinner-DDTqijTO.js → Spinner-VhKfzI3Q.js} +1 -1
  63. package/dist/Spinner.js +2 -2
  64. package/dist/{Table-BgCxfBcm.js → Table-rg_JCtsA.js} +3 -3
  65. package/dist/Table.js +7 -7
  66. package/dist/{Tag-DK2KkPIQ.js → Tag-BROUaDAZ.js} +1 -1
  67. package/dist/Tag.js +2 -2
  68. package/dist/{VisuallyHidden-pll3amXE.js → VisuallyHidden-Co_txzxB.js} +1 -1
  69. package/dist/VisuallyHidden.js +2 -2
  70. package/dist/index.d.ts +1 -1
  71. package/dist/index.js +30 -30
  72. package/dist/leu-accordion.js +2 -2
  73. package/dist/leu-button-group.js +5 -5
  74. package/dist/leu-button.js +4 -4
  75. package/dist/leu-chart-wrapper.js +3 -3
  76. package/dist/leu-checkbox-group.js +4 -4
  77. package/dist/leu-checkbox.js +3 -3
  78. package/dist/leu-chip-group.js +3 -3
  79. package/dist/leu-chip-link.js +2 -2
  80. package/dist/leu-chip-removable.js +3 -3
  81. package/dist/leu-chip-selectable.js +2 -2
  82. package/dist/leu-dialog.js +3 -3
  83. package/dist/leu-dropdown.js +8 -8
  84. package/dist/leu-file-input.js +6 -6
  85. package/dist/leu-icon.js +2 -2
  86. package/dist/leu-input.d.ts +1 -1
  87. package/dist/leu-input.js +3 -3
  88. package/dist/leu-menu-item.js +3 -3
  89. package/dist/leu-menu.js +4 -4
  90. package/dist/leu-message.js +3 -3
  91. package/dist/leu-pagination.js +6 -6
  92. package/dist/leu-placeholder.js +2 -2
  93. package/dist/leu-popup.js +2 -2
  94. package/dist/leu-progress-bar.js +2 -2
  95. package/dist/leu-radio-group.js +3 -3
  96. package/dist/leu-radio.js +2 -2
  97. package/dist/leu-range.js +2 -2
  98. package/dist/leu-scroll-top.js +5 -5
  99. package/dist/leu-select.js +9 -9
  100. package/dist/leu-spinner.js +2 -2
  101. package/dist/leu-table.js +7 -7
  102. package/dist/leu-tag.js +2 -2
  103. package/dist/leu-visually-hidden.js +2 -2
  104. package/dist/vscode.html-custom-data.json +14 -27
  105. package/dist/vue/index.d.ts +16 -24
  106. package/dist/web-types.json +41 -60
  107. package/package.json +1 -1
  108. package/src/components/file-input/FileInput.ts +24 -5
  109. package/src/components/input/Input.ts +43 -8
  110. package/src/components/input/test/input.test.ts +106 -1
  111. package/src/components/scroll-top/ScrollTop.ts +18 -16
  112. package/src/components/select/Select.ts +198 -124
  113. package/src/components/select/select.css +4 -0
  114. package/src/components/select/stories/select.stories.ts +10 -0
  115. package/src/components/select/test/select.test.ts +440 -35
@@ -33,6 +33,7 @@ function Template({
33
33
  clearable = false,
34
34
  filterable = false,
35
35
  multiple = false,
36
+ required = false,
36
37
  before,
37
38
  after,
38
39
  }) {
@@ -40,8 +41,10 @@ function Template({
40
41
  <div style="margin-top: 50vh"></div>
41
42
  <leu-select
42
43
  class="dropdown"
44
+ name="select"
43
45
  label=${ifDefined(label)}
44
46
  .value=${ifDefined(value)}
47
+ ?required=${required}
45
48
  ?clearable=${clearable}
46
49
  ?disabled=${disabled}
47
50
  ?filterable=${filterable}
@@ -104,6 +107,13 @@ Clearable.args = {
104
107
  clearable: true,
105
108
  }
106
109
 
110
+ export const Required = Template.bind({})
111
+ Required.args = {
112
+ label: "Gemeinde",
113
+ options: OPTIONS_EXAMPLES,
114
+ required: true,
115
+ }
116
+
107
117
  export const Disabled = Template.bind({})
108
118
  Disabled.args = {
109
119
  label: "Gemeinde",
@@ -3,30 +3,45 @@ import { ifDefined } from "lit/directives/if-defined.js"
3
3
  import { fixture, expect, elementUpdated } from "@open-wc/testing"
4
4
  import { sendKeys } from "@web/test-runner-commands"
5
5
 
6
+ import type { LeuSelect } from "../Select.js"
6
7
  import "../leu-select.js"
7
8
  import "../../menu/leu-menu-item.js"
8
9
  import { MUNICIPALITIES } from "./fixtures.js"
9
10
 
10
- async function defaultFixture(args = {}) {
11
- return fixture(
11
+ async function defaultFixture(
12
+ args: {
13
+ options?: ReadonlyArray<string>
14
+ label?: string
15
+ value?: ReadonlyArray<string>
16
+ clearable?: boolean
17
+ disabled?: boolean
18
+ filterable?: boolean
19
+ multiple?: boolean
20
+ } = {},
21
+ ) {
22
+ const el = await fixture<LeuSelect>(
12
23
  html`<leu-select
13
24
  label=${ifDefined(args.label)}
14
- .value=${args.value ?? []}
15
- ?clearable=${args.clearable}
16
- ?disabled=${args.disabled}
17
- ?filterable=${args.filterable}
18
- ?multiple=${args.multiple}
25
+ value=${ifDefined(args.value?.join(","))}
26
+ ?clearable=${args.clearable ?? false}
27
+ ?disabled=${args.disabled ?? false}
28
+ ?filterable=${args.filterable ?? false}
29
+ ?multiple=${args.multiple ?? false}
19
30
  >
20
- ${args.options.map((o) => html`<leu-menu-item>${o}</leu-menu-item>`)}
31
+ ${(args.options ?? []).map(
32
+ (o) => html`<leu-menu-item>${o}</leu-menu-item>`,
33
+ )}
21
34
  </leu-select> `,
22
35
  )
36
+
37
+ return el
23
38
  }
24
39
 
25
40
  describe("LeuSelect", () => {
26
41
  it("is a defined element", async () => {
27
42
  const el = customElements.get("leu-select")
28
43
 
29
- await expect(el).not.to.be.undefined
44
+ expect(el).not.to.be.undefined
30
45
  })
31
46
 
32
47
  it("passes the a11y audit", async () => {
@@ -42,9 +57,11 @@ describe("LeuSelect", () => {
42
57
  const el = await defaultFixture({
43
58
  options: MUNICIPALITIES,
44
59
  label: "Gemeinde",
45
- open: true,
46
60
  })
47
61
 
62
+ el.click()
63
+ await elementUpdated(el)
64
+
48
65
  await expect(el).shadowDom.to.be.accessible()
49
66
  })
50
67
 
@@ -65,7 +82,8 @@ describe("LeuSelect", () => {
65
82
  label: "Gemeinde",
66
83
  })
67
84
 
68
- const toggleButton = el.shadowRoot.querySelector(".select-toggle")
85
+ const toggleButton =
86
+ el.shadowRoot.querySelector<HTMLButtonElement>(".select-toggle")
69
87
 
70
88
  expect(toggleButton).to.have.trimmed.text("Gemeinde")
71
89
  })
@@ -86,7 +104,8 @@ describe("LeuSelect", () => {
86
104
  label: "Gemeinde",
87
105
  })
88
106
 
89
- const toggleButton = el.shadowRoot.querySelector(".select-toggle")
107
+ const toggleButton =
108
+ el.shadowRoot.querySelector<HTMLButtonElement>(".select-toggle")
90
109
  toggleButton.click()
91
110
 
92
111
  const popup = el.shadowRoot.querySelector("leu-popup")
@@ -184,7 +203,8 @@ describe("LeuSelect", () => {
184
203
  filterable: true,
185
204
  })
186
205
 
187
- const filterInput = el.shadowRoot.querySelector(".select-search")
206
+ const filterInput =
207
+ el.shadowRoot.querySelector<HTMLInputElement>(".select-search")
188
208
 
189
209
  expect(filterInput).to.exist
190
210
  })
@@ -196,10 +216,12 @@ describe("LeuSelect", () => {
196
216
  filterable: true,
197
217
  })
198
218
 
199
- const toggleButton = el.shadowRoot.querySelector(".select-toggle")
219
+ const toggleButton =
220
+ el.shadowRoot.querySelector<HTMLButtonElement>(".select-toggle")
200
221
  toggleButton.click()
201
222
 
202
- const filterInput = el.shadowRoot.querySelector(".select-search")
223
+ const filterInput =
224
+ el.shadowRoot.querySelector<HTMLInputElement>(".select-search")
203
225
  filterInput.focus()
204
226
 
205
227
  await sendKeys({ type: "am albis" })
@@ -218,16 +240,18 @@ describe("LeuSelect", () => {
218
240
  filterable: true,
219
241
  })
220
242
 
221
- const toggleButton = el.shadowRoot.querySelector(".select-toggle")
243
+ const toggleButton =
244
+ el.shadowRoot.querySelector<HTMLButtonElement>(".select-toggle")
222
245
  toggleButton.click()
223
246
 
224
- const filterInput = el.shadowRoot.querySelector(".select-search")
247
+ const filterInput =
248
+ el.shadowRoot.querySelector<HTMLInputElement>(".select-search")
225
249
  filterInput.focus()
226
250
 
227
251
  await sendKeys({ type: "am albis" })
228
252
 
229
253
  const clearFilterButton =
230
- filterInput.shadowRoot.querySelector(".clear-button")
254
+ filterInput.shadowRoot.querySelector<HTMLButtonElement>(".clear-button")
231
255
  clearFilterButton.click()
232
256
  await elementUpdated(el)
233
257
 
@@ -246,10 +270,12 @@ describe("LeuSelect", () => {
246
270
  filterable: true,
247
271
  })
248
272
 
249
- const toggleButton = el.shadowRoot.querySelector(".select-toggle")
273
+ const toggleButton =
274
+ el.shadowRoot.querySelector<HTMLButtonElement>(".select-toggle")
250
275
  toggleButton.click()
251
276
 
252
- const filterInput = el.shadowRoot.querySelector(".select-search")
277
+ const filterInput =
278
+ el.shadowRoot.querySelector<HTMLInputElement>(".select-search")
253
279
  filterInput.focus()
254
280
 
255
281
  await sendKeys({ type: "am albissss" })
@@ -268,7 +294,8 @@ describe("LeuSelect", () => {
268
294
  filterable: true,
269
295
  })
270
296
 
271
- const toggleButton = el.shadowRoot.querySelector(".select-toggle")
297
+ const toggleButton =
298
+ el.shadowRoot.querySelector<HTMLButtonElement>(".select-toggle")
272
299
  toggleButton.click()
273
300
 
274
301
  const emptyMessage = el.shadowRoot.querySelector(".filter-message-empty")
@@ -283,10 +310,12 @@ describe("LeuSelect", () => {
283
310
  filterable: true,
284
311
  })
285
312
 
286
- const toggleButton = el.shadowRoot.querySelector(".select-toggle")
313
+ const toggleButton =
314
+ el.shadowRoot.querySelector<HTMLButtonElement>(".select-toggle")
287
315
  toggleButton.click()
288
316
 
289
- const filterInput = el.shadowRoot.querySelector(".select-search")
317
+ const filterInput =
318
+ el.shadowRoot.querySelector<HTMLInputElement>(".select-search")
290
319
  filterInput.focus()
291
320
 
292
321
  await sendKeys({ type: "am albis" })
@@ -335,7 +364,8 @@ describe("LeuSelect", () => {
335
364
  multiple: true,
336
365
  })
337
366
 
338
- const toggleButton = el.shadowRoot.querySelector(".select-toggle")
367
+ const toggleButton =
368
+ el.shadowRoot.querySelector<HTMLButtonElement>(".select-toggle")
339
369
  toggleButton.click()
340
370
 
341
371
  const applyButton = el.shadowRoot.querySelector(".apply-button")
@@ -351,7 +381,8 @@ describe("LeuSelect", () => {
351
381
  label: "Gemeinde",
352
382
  })
353
383
 
354
- const toggleButton = el.shadowRoot.querySelector(".select-toggle")
384
+ const toggleButton =
385
+ el.shadowRoot.querySelector<HTMLButtonElement>(".select-toggle")
355
386
  toggleButton.click()
356
387
 
357
388
  const menuItem = Array.from(el.querySelectorAll("leu-menu-item")).find(
@@ -368,7 +399,8 @@ describe("LeuSelect", () => {
368
399
  label: "Gemeinde",
369
400
  value: ["Maur"],
370
401
  })
371
- const toggleButton = el.shadowRoot.querySelector(".select-toggle")
402
+ const toggleButton =
403
+ el.shadowRoot.querySelector<HTMLButtonElement>(".select-toggle")
372
404
  toggleButton.click()
373
405
 
374
406
  const menuItem = Array.from(el.querySelectorAll("leu-menu-item")).find(
@@ -386,7 +418,8 @@ describe("LeuSelect", () => {
386
418
  value: ["Maur"],
387
419
  clearable: true,
388
420
  })
389
- const toggleButton = el.shadowRoot.querySelector(".select-toggle")
421
+ const toggleButton =
422
+ el.shadowRoot.querySelector<HTMLButtonElement>(".select-toggle")
390
423
  toggleButton.click()
391
424
 
392
425
  const menuItem = Array.from(el.querySelectorAll("leu-menu-item")).find(
@@ -404,7 +437,8 @@ describe("LeuSelect", () => {
404
437
  multiple: true,
405
438
  })
406
439
 
407
- const toggleButton = el.shadowRoot.querySelector(".select-toggle")
440
+ const toggleButton =
441
+ el.shadowRoot.querySelector<HTMLButtonElement>(".select-toggle")
408
442
  toggleButton.click()
409
443
 
410
444
  const menuItems = Array.from(el.querySelectorAll("leu-menu-item"))
@@ -421,7 +455,8 @@ describe("LeuSelect", () => {
421
455
  label: "Gemeinde",
422
456
  })
423
457
 
424
- const toggleButton = el.shadowRoot.querySelector(".select-toggle")
458
+ const toggleButton =
459
+ el.shadowRoot.querySelector<HTMLButtonElement>(".select-toggle")
425
460
  toggleButton.click()
426
461
 
427
462
  const menuItem = Array.from(el.querySelectorAll("leu-menu-item")).find(
@@ -440,7 +475,8 @@ describe("LeuSelect", () => {
440
475
  multiple: true,
441
476
  })
442
477
 
443
- const toggleButton = el.shadowRoot.querySelector(".select-toggle")
478
+ const toggleButton =
479
+ el.shadowRoot.querySelector<HTMLButtonElement>(".select-toggle")
444
480
  toggleButton.click()
445
481
 
446
482
  const menuItem = Array.from(el.querySelectorAll("leu-menu-item")).find(
@@ -459,12 +495,14 @@ describe("LeuSelect", () => {
459
495
  filterable: true,
460
496
  })
461
497
 
462
- const toggleButton = el.shadowRoot.querySelector(".select-toggle")
498
+ const toggleButton =
499
+ el.shadowRoot.querySelector<HTMLButtonElement>(".select-toggle")
463
500
  toggleButton.click()
464
501
 
465
502
  await elementUpdated(el)
466
503
 
467
- const filterInput = el.shadowRoot.querySelector(".select-search")
504
+ const filterInput =
505
+ el.shadowRoot.querySelector<HTMLInputElement>(".select-search")
468
506
  expect(filterInput).to.equal(el.shadowRoot.activeElement)
469
507
  })
470
508
 
@@ -474,7 +512,8 @@ describe("LeuSelect", () => {
474
512
  label: "Gemeinde",
475
513
  })
476
514
 
477
- const toggleButton = el.shadowRoot.querySelector(".select-toggle")
515
+ const toggleButton =
516
+ el.shadowRoot.querySelector<HTMLButtonElement>(".select-toggle")
478
517
  toggleButton.click()
479
518
 
480
519
  await elementUpdated(el)
@@ -490,7 +529,8 @@ describe("LeuSelect", () => {
490
529
  label: "Gemeinde",
491
530
  })
492
531
 
493
- const toggleButton = el.shadowRoot.querySelector(".select-toggle")
532
+ const toggleButton =
533
+ el.shadowRoot.querySelector<HTMLButtonElement>(".select-toggle")
494
534
  toggleButton.click()
495
535
 
496
536
  await sendKeys({ press: "Escape" })
@@ -521,7 +561,8 @@ describe("LeuSelect", () => {
521
561
  label: "Gemeinde",
522
562
  })
523
563
 
524
- const toggleButton = el.shadowRoot.querySelector(".select-toggle")
564
+ const toggleButton =
565
+ el.shadowRoot.querySelector<HTMLButtonElement>(".select-toggle")
525
566
  toggleButton.click()
526
567
 
527
568
  document.body.click()
@@ -529,4 +570,368 @@ describe("LeuSelect", () => {
529
570
  const popup = el.shadowRoot.querySelector("leu-popup")
530
571
  expect(popup.active).to.not.be.true
531
572
  })
573
+
574
+ describe("Form association", () => {
575
+ it("submits the selected value in a form", async () => {
576
+ const form = await fixture<HTMLFormElement>(html`
577
+ <form>
578
+ <leu-select name="city" label="Gemeinde">
579
+ ${MUNICIPALITIES.map(
580
+ (o) => html`<leu-menu-item>${o}</leu-menu-item>`,
581
+ )}
582
+ </leu-select>
583
+ </form>
584
+ `)
585
+
586
+ const el = form.querySelector("leu-select")
587
+ el.value = ["Maur"]
588
+ await elementUpdated(el)
589
+
590
+ const formData = new FormData(form)
591
+ expect(formData.get("city")).to.equal("Maur")
592
+ })
593
+
594
+ it("submits multiple selected values as separate form entries", async () => {
595
+ const form = await fixture<HTMLFormElement>(html`
596
+ <form>
597
+ <leu-select name="city" label="Gemeinde" ?multiple=${true}>
598
+ ${MUNICIPALITIES.map(
599
+ (o) => html`<leu-menu-item>${o}</leu-menu-item>`,
600
+ )}
601
+ </leu-select>
602
+ </form>
603
+ `)
604
+
605
+ const el = form.querySelector("leu-select")
606
+ el.value = ["Maur", "Zollikon"]
607
+ await elementUpdated(el)
608
+
609
+ const formData = new FormData(form)
610
+ expect(formData.getAll("city")).to.deep.equal(["Maur", "Zollikon"])
611
+ })
612
+
613
+ it("submits null when no value is selected", async () => {
614
+ const form = await fixture<HTMLFormElement>(html`
615
+ <form>
616
+ <leu-select name="city" label="Gemeinde">
617
+ ${MUNICIPALITIES.map(
618
+ (o) => html`<leu-menu-item>${o}</leu-menu-item>`,
619
+ )}
620
+ </leu-select>
621
+ </form>
622
+ `)
623
+
624
+ const formData = new FormData(form)
625
+ expect(formData.get("city")).to.be.null
626
+ })
627
+
628
+ it("uses the defaultValue as initial form value", async () => {
629
+ const form = await fixture<HTMLFormElement>(html`
630
+ <form>
631
+ <leu-select name="city" value="Maur" label="Gemeinde">
632
+ ${MUNICIPALITIES.map(
633
+ (o) => html`<leu-menu-item>${o}</leu-menu-item>`,
634
+ )}
635
+ </leu-select>
636
+ </form>
637
+ `)
638
+
639
+ const el = form.querySelector("leu-select")
640
+ await elementUpdated(el)
641
+
642
+ expect(el.defaultValue).to.deep.equal(["Maur"])
643
+ expect(el.value).to.deep.equal(["Maur"])
644
+
645
+ const formData = new FormData(form)
646
+ expect(formData.get("city")).to.equal("Maur")
647
+ })
648
+
649
+ it("resets to the defaultValue when the form is reset", async () => {
650
+ const form = await fixture<HTMLFormElement>(html`
651
+ <form>
652
+ <leu-select name="city" value="Maur" label="Gemeinde">
653
+ ${MUNICIPALITIES.map(
654
+ (o) => html`<leu-menu-item>${o}</leu-menu-item>`,
655
+ )}
656
+ </leu-select>
657
+ </form>
658
+ `)
659
+
660
+ const el = form.querySelector("leu-select")
661
+ el.value = ["Zollikon"]
662
+ await elementUpdated(el)
663
+
664
+ expect(el.value).to.deep.equal(["Zollikon"])
665
+
666
+ form.reset()
667
+ await elementUpdated(el)
668
+
669
+ expect(el.value).to.deep.equal(["Maur"])
670
+ })
671
+
672
+ it("resets to an empty value when no defaultValue is set", async () => {
673
+ const form = await fixture<HTMLFormElement>(html`
674
+ <form>
675
+ <leu-select name="city" label="Gemeinde">
676
+ ${MUNICIPALITIES.map(
677
+ (o) => html`<leu-menu-item>${o}</leu-menu-item>`,
678
+ )}
679
+ </leu-select>
680
+ </form>
681
+ `)
682
+
683
+ const el = form.querySelector("leu-select")
684
+ el.value = ["Maur"]
685
+ await elementUpdated(el)
686
+
687
+ form.reset()
688
+ await elementUpdated(el)
689
+
690
+ expect(el.value).to.deep.equal([])
691
+ })
692
+
693
+ it("updates the form data when the defaultValue changes before any interaction", async () => {
694
+ const form = await fixture<HTMLFormElement>(html`
695
+ <form>
696
+ <leu-select name="city" value="Maur" label="Gemeinde">
697
+ ${MUNICIPALITIES.map(
698
+ (o) => html`<leu-menu-item>${o}</leu-menu-item>`,
699
+ )}
700
+ </leu-select>
701
+ </form>
702
+ `)
703
+
704
+ const el = form.querySelector("leu-select")
705
+
706
+ let formData = new FormData(form)
707
+ expect(formData.get("city")).to.equal("Maur")
708
+
709
+ el.defaultValue = ["Zollikon"]
710
+ await elementUpdated(el)
711
+
712
+ formData = new FormData(form)
713
+ expect(formData.get("city")).to.equal("Zollikon")
714
+ })
715
+
716
+ it("does not override the value when defaultValue changes after interaction", async () => {
717
+ const form = await fixture<HTMLFormElement>(html`
718
+ <form>
719
+ <leu-select name="city" label="Gemeinde">
720
+ ${MUNICIPALITIES.map(
721
+ (o) => html`<leu-menu-item>${o}</leu-menu-item>`,
722
+ )}
723
+ </leu-select>
724
+ </form>
725
+ `)
726
+
727
+ const el = form.querySelector("leu-select")
728
+ // Simulate user interaction
729
+ const toggleButton =
730
+ el.shadowRoot.querySelector<HTMLButtonElement>(".select-toggle")
731
+ toggleButton.click()
732
+ const menuItem = Array.from(el.querySelectorAll("leu-menu-item")).find(
733
+ (item) => item.textContent === "Maur",
734
+ )
735
+ menuItem.click()
736
+ await elementUpdated(el)
737
+
738
+ expect(el.value).to.deep.equal(["Maur"])
739
+
740
+ el.defaultValue = ["Zollikon"]
741
+ await elementUpdated(el)
742
+
743
+ // User interaction has occurred, defaultValue change should NOT override value
744
+ expect(el.value).to.deep.equal(["Maur"])
745
+ })
746
+
747
+ it("is disabled by the form when the fieldset is disabled", async () => {
748
+ const form = await fixture<HTMLFormElement>(html`
749
+ <form>
750
+ <fieldset disabled>
751
+ <leu-select name="city" label="Gemeinde">
752
+ ${MUNICIPALITIES.map(
753
+ (o) => html`<leu-menu-item>${o}</leu-menu-item>`,
754
+ )}
755
+ </leu-select>
756
+ </fieldset>
757
+ </form>
758
+ `)
759
+
760
+ const el = form.querySelector("leu-select")
761
+ await elementUpdated(el)
762
+
763
+ expect(el.disabled).to.be.true
764
+ })
765
+ })
766
+
767
+ describe("Validity", () => {
768
+ it("is valid by default", async () => {
769
+ const el = await defaultFixture({
770
+ options: MUNICIPALITIES,
771
+ label: "Gemeinde",
772
+ })
773
+ expect(el.checkValidity()).to.be.true
774
+ expect(el.validity.valid).to.be.true
775
+ })
776
+
777
+ it("is always valid when not required", async () => {
778
+ const el = await defaultFixture({
779
+ options: MUNICIPALITIES,
780
+ label: "Gemeinde",
781
+ })
782
+ expect(el.checkValidity()).to.be.true
783
+
784
+ el.value = []
785
+ await elementUpdated(el)
786
+ expect(el.checkValidity()).to.be.true
787
+ })
788
+
789
+ it("is invalid when required and no value is selected", async () => {
790
+ const form = await fixture<HTMLFormElement>(html`
791
+ <form>
792
+ <leu-select name="city" label="Gemeinde" ?required=${true}>
793
+ ${MUNICIPALITIES.map(
794
+ (o) => html`<leu-menu-item>${o}</leu-menu-item>`,
795
+ )}
796
+ </leu-select>
797
+ </form>
798
+ `)
799
+
800
+ const el = form.querySelector<LeuSelect>("leu-select")
801
+ await elementUpdated(el)
802
+
803
+ expect(el.checkValidity()).to.be.false
804
+ expect(el.validity.valueMissing).to.be.true
805
+ })
806
+
807
+ it("is valid when required and a value is selected", async () => {
808
+ const form = await fixture<HTMLFormElement>(html`
809
+ <form>
810
+ <leu-select name="city" label="Gemeinde" ?required=${true}>
811
+ ${MUNICIPALITIES.map(
812
+ (o) => html`<leu-menu-item>${o}</leu-menu-item>`,
813
+ )}
814
+ </leu-select>
815
+ </form>
816
+ `)
817
+
818
+ const el = form.querySelector<LeuSelect>("leu-select")
819
+ el.value = ["Maur"]
820
+ await elementUpdated(el)
821
+
822
+ expect(el.checkValidity()).to.be.true
823
+ expect(el.validity.valid).to.be.true
824
+ })
825
+
826
+ it("becomes invalid when required is set after a value exists and is then cleared", async () => {
827
+ const el = await defaultFixture({
828
+ options: MUNICIPALITIES,
829
+ label: "Gemeinde",
830
+ value: ["Maur"],
831
+ })
832
+
833
+ el.required = true
834
+ await elementUpdated(el)
835
+ expect(el.checkValidity()).to.be.true
836
+
837
+ el.value = []
838
+ await elementUpdated(el)
839
+ expect(el.checkValidity()).to.be.false
840
+ expect(el.validity.valueMissing).to.be.true
841
+ })
842
+
843
+ it("becomes valid when a value is selected after being required and empty", async () => {
844
+ const form = await fixture<HTMLFormElement>(html`
845
+ <form>
846
+ <leu-select name="city" label="Gemeinde" ?required=${true}>
847
+ ${MUNICIPALITIES.map(
848
+ (o) => html`<leu-menu-item>${o}</leu-menu-item>`,
849
+ )}
850
+ </leu-select>
851
+ </form>
852
+ `)
853
+
854
+ const el = form.querySelector<LeuSelect>("leu-select")
855
+ await elementUpdated(el)
856
+ expect(el.checkValidity()).to.be.false
857
+
858
+ const toggleButton =
859
+ el.shadowRoot.querySelector<HTMLButtonElement>(".select-toggle")
860
+ toggleButton.click()
861
+ const menuItem = Array.from(el.querySelectorAll("leu-menu-item")).find(
862
+ (item) => item.textContent === "Maur",
863
+ )
864
+ menuItem.click()
865
+ await elementUpdated(el)
866
+
867
+ expect(el.checkValidity()).to.be.true
868
+ })
869
+
870
+ it("sets a validation message when required and empty", async () => {
871
+ const form = await fixture<HTMLFormElement>(html`
872
+ <form>
873
+ <leu-select name="city" label="Gemeinde" ?required=${true}>
874
+ ${MUNICIPALITIES.map(
875
+ (o) => html`<leu-menu-item>${o}</leu-menu-item>`,
876
+ )}
877
+ </leu-select>
878
+ </form>
879
+ `)
880
+
881
+ const el = form.querySelector<LeuSelect>("leu-select")
882
+ await elementUpdated(el)
883
+
884
+ expect(el.validationMessage).to.be.a("string").and.not.be.empty
885
+ })
886
+
887
+ it("has no validation message when valid", async () => {
888
+ const el = await defaultFixture({
889
+ options: MUNICIPALITIES,
890
+ label: "Gemeinde",
891
+ value: ["Maur"],
892
+ })
893
+ expect(el.validationMessage).to.equal("")
894
+ })
895
+
896
+ it("willValidate is true when not disabled", async () => {
897
+ const el = await defaultFixture({
898
+ options: MUNICIPALITIES,
899
+ label: "Gemeinde",
900
+ })
901
+ expect(el.willValidate).to.be.true
902
+ })
903
+
904
+ it("willValidate is false when disabled", async () => {
905
+ const el = await defaultFixture({
906
+ options: MUNICIPALITIES,
907
+ label: "Gemeinde",
908
+ disabled: true,
909
+ })
910
+ expect(el.willValidate).to.be.false
911
+ })
912
+
913
+ it("resets validity on form reset", async () => {
914
+ const form = await fixture<HTMLFormElement>(html`
915
+ <form>
916
+ <leu-select name="city" label="Gemeinde" ?required=${true}>
917
+ ${MUNICIPALITIES.map(
918
+ (o) => html`<leu-menu-item>${o}</leu-menu-item>`,
919
+ )}
920
+ </leu-select>
921
+ </form>
922
+ `)
923
+
924
+ const el = form.querySelector<LeuSelect>("leu-select")
925
+ el.value = ["Maur"]
926
+ await elementUpdated(el)
927
+ expect(el.checkValidity()).to.be.true
928
+
929
+ form.reset()
930
+ await elementUpdated(el)
931
+
932
+ // After reset value is empty again, so required makes it invalid
933
+ expect(el.checkValidity()).to.be.false
934
+ expect(el.validity.valueMissing).to.be.true
935
+ })
936
+ })
532
937
  })