@statistikzh/leu 0.25.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 (136) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +28 -0
  3. package/CONTRIBUTING.md +19 -8
  4. package/dist/{Accordion-CDNyrB8d.js → Accordion-DLsqXcK8.js} +1 -1
  5. package/dist/Accordion.js +2 -2
  6. package/dist/{Button-EdS9xr2J.js → Button-BSyDL_cV.js} +57 -17
  7. package/dist/{Button-DSGPIcjm.d.ts → Button-BgNUxmo_.d.ts} +6 -0
  8. package/dist/Button.d.ts +1 -1
  9. package/dist/Button.js +5 -4
  10. package/dist/{ButtonGroup-BQqf8o_d.js → ButtonGroup-BmSvl-Oc.js} +2 -2
  11. package/dist/ButtonGroup.js +6 -5
  12. package/dist/{ChartWrapper-LiNHTNRw.js → ChartWrapper-CvDvQsd5.js} +3 -3
  13. package/dist/ChartWrapper.d.ts +2 -2
  14. package/dist/ChartWrapper.js +3 -3
  15. package/dist/{Checkbox-BtDWmPab.js → Checkbox-Cl_X6gBJ.js} +3 -3
  16. package/dist/Checkbox.js +4 -4
  17. package/dist/{CheckboxGroup-C8MbwW9u.js → CheckboxGroup-BKhOmZYX.js} +2 -2
  18. package/dist/CheckboxGroup.js +5 -5
  19. package/dist/{Chip-Ch09jjYi.js → Chip-McVP3N_x.js} +1 -1
  20. package/dist/Chip.js +2 -2
  21. package/dist/{ChipGroup-PvqVW-tm.js → ChipGroup-DUGavZeU.js} +1 -1
  22. package/dist/ChipGroup.js +3 -3
  23. package/dist/ChipLink.js +2 -2
  24. package/dist/ChipRemovable.js +3 -3
  25. package/dist/ChipSelectable.js +2 -2
  26. package/dist/{Dialog-CV1JTkCn.js → Dialog-BlDd4T2u.js} +3 -3
  27. package/dist/Dialog.d.ts +1 -1
  28. package/dist/Dialog.js +3 -3
  29. package/dist/{Dropdown-DpFdFbA1.js → Dropdown-BLxSIe6p.js} +6 -6
  30. package/dist/Dropdown.d.ts +2 -2
  31. package/dist/Dropdown.js +9 -8
  32. package/dist/{FileInput-5apX17JT.js → FileInput-DntYrpZ-.js} +23 -8
  33. package/dist/FileInput.d.ts +12 -1
  34. package/dist/FileInput.js +7 -6
  35. package/dist/{Icon-DhAvH0XM.js → Icon-CbZXpyHU.js} +1 -1
  36. package/dist/Icon.js +2 -2
  37. package/dist/{Input-D2THgo7c.d.ts → Input-CeaAOB4p.d.ts} +6 -2
  38. package/dist/{Input-CnEz-2dK.js → Input-DBXX7ev8.js} +33 -12
  39. package/dist/Input.d.ts +1 -1
  40. package/dist/Input.js +4 -4
  41. package/dist/{LeuElement-B7NJzWwP.js → LeuElement-k4RjIeoG.js} +1 -1
  42. package/dist/{Menu-DpiheIPk.js → Menu-Cu8eIF1T.js} +2 -2
  43. package/dist/Menu.js +4 -4
  44. package/dist/{MenuItem-CZTqGg5R.js → MenuItem-Cs3KFhJm.js} +2 -2
  45. package/dist/MenuItem.js +3 -3
  46. package/dist/{Message-J4Kj7yHE.js → Message-C6Zlk_2p.js} +3 -3
  47. package/dist/Message.js +3 -3
  48. package/dist/{Pagination-CWqgusWZ.js → Pagination-CB2eVlXk.js} +4 -4
  49. package/dist/{Pagination-Be8TcBoC.d.ts → Pagination-CqkHh-Vd.d.ts} +1 -1
  50. package/dist/Pagination.d.ts +1 -1
  51. package/dist/Pagination.js +7 -6
  52. package/dist/{Placeholder-DMN6sMbp.js → Placeholder-DHMexMhK.js} +1 -1
  53. package/dist/Placeholder.js +2 -2
  54. package/dist/{Popup-JQjuj26v.js → Popup-8jhVy8gB.js} +1 -1
  55. package/dist/Popup.js +2 -2
  56. package/dist/{ProgressBar-CzN3fqiH.js → ProgressBar-CG0_lHfS.js} +1 -1
  57. package/dist/ProgressBar.js +2 -2
  58. package/dist/{Radio-CX8aCsff.js → Radio-DG3xqP3s.js} +1 -1
  59. package/dist/Radio.js +2 -2
  60. package/dist/{RadioGroup-CgEWQnC4.js → RadioGroup-BKCp9ICX.js} +2 -2
  61. package/dist/RadioGroup.js +3 -3
  62. package/dist/{Range-DoW_ZdKm.js → Range-7LrESv4K.js} +1 -1
  63. package/dist/Range.js +2 -2
  64. package/dist/{ScrollTop-DxChetWq.js → ScrollTop-CJJsfniA.js} +20 -20
  65. package/dist/ScrollTop.d.ts +6 -6
  66. package/dist/ScrollTop.js +6 -5
  67. package/dist/{Select-BCx79gOH.js → Select-CxEDXIBn.js} +154 -134
  68. package/dist/Select.d.ts +75 -73
  69. package/dist/Select.js +10 -9
  70. package/dist/{Spinner-DJR4gv3Y.js → Spinner-VhKfzI3Q.js} +1 -1
  71. package/dist/Spinner.d.ts +1 -1
  72. package/dist/Spinner.js +2 -2
  73. package/dist/{Table-DZz1ic3j.js → Table-rg_JCtsA.js} +3 -3
  74. package/dist/Table.d.ts +1 -1
  75. package/dist/Table.js +8 -7
  76. package/dist/{Tag-DsZS_8pl.js → Tag-BROUaDAZ.js} +1 -1
  77. package/dist/Tag.js +2 -2
  78. package/dist/{VisuallyHidden-BkllVjlz.js → VisuallyHidden-Co_txzxB.js} +1 -1
  79. package/dist/VisuallyHidden.js +2 -2
  80. package/dist/index.d.ts +4 -4
  81. package/dist/index.js +31 -31
  82. package/dist/leu-accordion.js +2 -2
  83. package/dist/leu-button-group.js +6 -5
  84. package/dist/leu-button.d.ts +1 -1
  85. package/dist/leu-button.js +5 -4
  86. package/dist/leu-chart-wrapper.js +3 -3
  87. package/dist/leu-checkbox-group.js +5 -5
  88. package/dist/leu-checkbox.js +4 -4
  89. package/dist/leu-chip-group.js +3 -3
  90. package/dist/leu-chip-link.js +2 -2
  91. package/dist/leu-chip-removable.js +3 -3
  92. package/dist/leu-chip-selectable.js +2 -2
  93. package/dist/leu-dialog.js +3 -3
  94. package/dist/leu-dropdown.js +9 -8
  95. package/dist/leu-file-input.js +7 -6
  96. package/dist/leu-icon.js +2 -2
  97. package/dist/leu-input.d.ts +1 -1
  98. package/dist/leu-input.js +4 -4
  99. package/dist/leu-menu-item.js +3 -3
  100. package/dist/leu-menu.js +4 -4
  101. package/dist/leu-message.js +3 -3
  102. package/dist/leu-pagination.d.ts +1 -1
  103. package/dist/leu-pagination.js +7 -6
  104. package/dist/leu-placeholder.js +2 -2
  105. package/dist/leu-popup.js +2 -2
  106. package/dist/leu-progress-bar.js +2 -2
  107. package/dist/leu-radio-group.js +3 -3
  108. package/dist/leu-radio.js +2 -2
  109. package/dist/leu-range.js +2 -2
  110. package/dist/leu-scroll-top.js +6 -5
  111. package/dist/leu-select.js +10 -9
  112. package/dist/leu-spinner.d.ts +1 -1
  113. package/dist/leu-spinner.js +2 -2
  114. package/dist/leu-table.js +8 -7
  115. package/dist/leu-tag.js +2 -2
  116. package/dist/leu-visually-hidden.js +2 -2
  117. package/dist/vscode.html-custom-data.json +19 -27
  118. package/dist/vue/index.d.ts +18 -24
  119. package/dist/web-types.json +51 -60
  120. package/package.json +1 -1
  121. package/src/components/button/Button.ts +15 -3
  122. package/src/components/button/button.css +37 -9
  123. package/src/components/button/stories/button.stories.ts +23 -0
  124. package/src/components/button/test/button.test.ts +30 -3
  125. package/src/components/file-input/FileInput.ts +24 -5
  126. package/src/components/input/Input.ts +43 -8
  127. package/src/components/input/test/input.test.ts +106 -1
  128. package/src/components/scroll-top/ScrollTop.ts +18 -16
  129. package/src/components/select/Select.ts +198 -124
  130. package/src/components/select/select.css +4 -0
  131. package/src/components/select/stories/select.stories.ts +10 -0
  132. package/src/components/select/test/select.test.ts +440 -35
  133. /package/dist/{FormAssociatedMixin-BbFlza53.js → FormAssociatedMixin-DLPvFtbT.js} +0 -0
  134. /package/dist/{Spinner-CMo_o6Fy.d.ts → Spinner-CrM1enM0.d.ts} +0 -0
  135. /package/dist/{hasSlotController-DjdfnOQp.js → hasSlotController-DSBCVzPD.js} +0 -0
  136. /package/dist/{hasSlotController-BLtZurRh.d.ts → hasSlotController-DWPyZ52b.d.ts} +0 -0
@@ -155,16 +155,43 @@ describe("LeuButton", () => {
155
155
  })
156
156
 
157
157
  it("dispatches the click event", async () => {
158
- const el = await fixture<LeuButton>(html` <leu-button>Sichern</leu-button>`)
159
- const button = el.shadowRoot.querySelector("button")
158
+ const button = await fixture<LeuButton>(
159
+ html` <leu-button>Sichern</leu-button>`,
160
+ )
160
161
 
161
162
  setTimeout(() => button.click())
162
163
 
163
- const event = await oneEvent(el, "click")
164
+ const event = await oneEvent(button, "click")
164
165
 
165
166
  expect(event).to.exist
166
167
  })
167
168
 
169
+ it("disables the button when loading", async () => {
170
+ const el = await fixture<LeuButton>(
171
+ html` <leu-button loading>Sichern</leu-button>`,
172
+ )
173
+
174
+ const button = el.shadowRoot.querySelector("button")
175
+
176
+ expect(button).to.have.attribute("disabled")
177
+ })
178
+
179
+ it("doesn't dispatch click events when loading", async () => {
180
+ const button = await fixture<LeuButton>(
181
+ html` <leu-button loading>Sichern</leu-button>`,
182
+ )
183
+
184
+ let clicked = false
185
+
186
+ button.addEventListener("click", () => {
187
+ clicked = true
188
+ })
189
+
190
+ button.click()
191
+
192
+ expect(clicked).to.be.false
193
+ })
194
+
168
195
  describe("form association", () => {
169
196
  it("submits the form when type is submit", async () => {
170
197
  const form = await fixture<HTMLFormElement>(html`
@@ -145,16 +145,35 @@ export class LeuFileInput extends FormAssociatedMixin(LeuElement) {
145
145
  )
146
146
  }
147
147
 
148
+ /**
149
+ * This implementation Uses base-10 (decimal) units:
150
+ * 1 KB = 1_000 bytes
151
+ * 1 MB = 1_000_000 bytes
152
+ * 1 GB = 1_000_000_000 bytes
153
+ *
154
+ * To switch to base-2 (binary), use the following implementation:
155
+ * // const KB = 1024
156
+ * // const MB = 1024 * 1024
157
+ * // const GB = 1024 * 1024 * 1024
158
+ */
148
159
  protected static formatFileSize(size: number) {
149
- if (size < 1e3) {
150
- return html`${size}&nbsp;bytes`
160
+ const KB = 1e3
161
+ const MB = 1e6
162
+ const GB = 1e9
163
+
164
+ if (size >= GB) {
165
+ return html`${(size / GB).toFixed(1)}&nbsp;GB`
166
+ }
167
+
168
+ if (size >= MB) {
169
+ return html`${(size / MB).toFixed(1)}&nbsp;MB`
151
170
  }
152
171
 
153
- if (size >= 1e3 && size < 1e6) {
154
- return html`${(size / 1e3).toFixed(1)}&nbsp;KB`
172
+ if (size >= KB) {
173
+ return html`${(size / KB).toFixed(1)}&nbsp;KB`
155
174
  }
156
175
 
157
- return html`${(size / 1e6).toFixed(1)}&nbsp;MB`
176
+ return html`${size}&nbsp;bytes`
158
177
  }
159
178
 
160
179
  protected handleDragEnter = (event: DragEvent) => {
@@ -86,10 +86,6 @@ export class LeuInput extends FormAssociatedMixin(LeuElement) {
86
86
  @property({ type: Boolean, reflect: true })
87
87
  clearable: boolean = false
88
88
 
89
- /** The value of the input element. */
90
- @property({ type: String, reflect: true })
91
- value: string = ""
92
-
93
89
  /** A custom error that is completely independent of the validity state. Useful for displaying server side errors. */
94
90
  @property({ type: String, reflect: true })
95
91
  error: string = ""
@@ -150,6 +146,26 @@ export class LeuInput extends FormAssociatedMixin(LeuElement) {
150
146
  @property({ type: Boolean, reflect: true })
151
147
  novalidate: boolean = false
152
148
 
149
+ /** The default value of the input element. */
150
+ @property({ type: String, reflect: true, attribute: "value" })
151
+ defaultValue: string = ""
152
+
153
+ protected _value: string
154
+
155
+ /** The value of the input element. */
156
+ @property({ type: String, attribute: false })
157
+ set value(value: string) {
158
+ this._value = value
159
+ }
160
+
161
+ get value(): string {
162
+ if (typeof this._value === "string") {
163
+ return this._value
164
+ }
165
+
166
+ return this.defaultValue
167
+ }
168
+
153
169
  @state()
154
170
  _validity: ValidityState | null = null
155
171
 
@@ -171,15 +187,32 @@ export class LeuInput extends FormAssociatedMixin(LeuElement) {
171
187
  }
172
188
 
173
189
  formResetCallback() {
174
- this.value = ""
190
+ this.value = this.defaultValue
175
191
  }
176
192
 
177
193
  protected setFormValue(): void {
178
- this.internals.setFormValue(this.value)
194
+ this.internals.setFormValue(this.disabled ? null : this.value)
179
195
  }
180
196
 
181
197
  protected willUpdate(changedProperties: PropertyValues<this>): void {
182
- if (changedProperties.has("value")) {
198
+ super.willUpdate(changedProperties)
199
+ let valueChanged = false
200
+
201
+ if (
202
+ changedProperties.has("defaultValue") &&
203
+ !changedProperties.has("value") &&
204
+ !this.hasInteracted
205
+ ) {
206
+ this.value = this.defaultValue
207
+ valueChanged = true
208
+ }
209
+
210
+ if (
211
+ valueChanged ||
212
+ changedProperties.has("value") ||
213
+ changedProperties.has("name") ||
214
+ changedProperties.has("disabled")
215
+ ) {
183
216
  this.setFormValue()
184
217
  }
185
218
  }
@@ -225,6 +258,7 @@ export class LeuInput extends FormAssociatedMixin(LeuElement) {
225
258
  * @fires {CustomEvent} change
226
259
  */
227
260
  protected handleChange(event: Event & { target: HTMLInputElement }) {
261
+ this.hasInteracted = true
228
262
  if (event.target.validity.valid) {
229
263
  this.value = event.target.value
230
264
  }
@@ -239,6 +273,7 @@ export class LeuInput extends FormAssociatedMixin(LeuElement) {
239
273
  * the event can be handled outside the shadow DOM.
240
274
  */
241
275
  protected handleInput(event: Event & { target: HTMLInputElement }) {
276
+ this.hasInteracted = true
242
277
  this.value = event.target.value
243
278
 
244
279
  const customEvent = new CustomEvent("input", {
@@ -311,7 +346,7 @@ export class LeuInput extends FormAssociatedMixin(LeuElement) {
311
346
  return true
312
347
  }
313
348
 
314
- return this._validity === null || this.novalidate
349
+ return this._validity === null || this.novalidate || this.disabled
315
350
  ? false
316
351
  : !this._validity.valid
317
352
  }
@@ -5,9 +5,10 @@ import { sendKeys } from "@web/test-runner-commands"
5
5
  import { spy } from "sinon"
6
6
 
7
7
  import "../leu-input.js"
8
+ import { LeuInput } from "../leu-input.js"
8
9
 
9
10
  async function defaultFixture(args = {}) {
10
- return fixture(html`
11
+ return fixture<LeuInput>(html`
11
12
  <leu-input
12
13
  value=${ifDefined(args.value)}
13
14
  error=${ifDefined(args.error)}
@@ -495,4 +496,108 @@ describe("LeuInput", () => {
495
496
  expect(error).to.be.null
496
497
  }
497
498
  })
499
+
500
+ it("returns the defaultValue when no value has been explicitly set", async () => {
501
+ const el = await defaultFixture({ value: "John" })
502
+
503
+ expect(el.defaultValue).to.equal("John")
504
+ expect(el.value).to.equal("John")
505
+ })
506
+
507
+ it("value property overrides the defaultValue", async () => {
508
+ const el = await defaultFixture({ value: "John" })
509
+
510
+ el.value = "Jane"
511
+ await elementUpdated(el)
512
+
513
+ expect(el.defaultValue).to.equal("John")
514
+ expect(el.value).to.equal("Jane")
515
+ })
516
+
517
+ it("resets to the defaultValue when the form is reset", async () => {
518
+ const form = await fixture<HTMLFormElement>(html`
519
+ <form>
520
+ <leu-input name="name" value="John" label="Name"></leu-input>
521
+ </form>
522
+ `)
523
+
524
+ const input = form.querySelector<LeuInput>("leu-input")
525
+ input.value = "Jane"
526
+ await elementUpdated(input)
527
+
528
+ expect(input.value).to.equal("Jane")
529
+
530
+ form.reset()
531
+ await elementUpdated(input)
532
+
533
+ expect(input.value).to.equal("John")
534
+ })
535
+
536
+ it("updates the form data when the defaultValue changes before any interaction", async () => {
537
+ const form = await fixture<HTMLFormElement>(html`
538
+ <form>
539
+ <leu-input name="name" value="John" label="Name"></leu-input>
540
+ </form>
541
+ `)
542
+
543
+ const input = form.querySelector<LeuInput>("leu-input")
544
+
545
+ let formData = new FormData(form)
546
+ expect(formData.get("name")).to.equal("John")
547
+
548
+ // Changing defaultValue before interaction should update the value
549
+ input.defaultValue = "Jane"
550
+ await elementUpdated(input)
551
+
552
+ formData = new FormData(form)
553
+ expect(formData.get("name")).to.equal("Jane")
554
+ })
555
+
556
+ it("does not update the value when the defaultValue changes after interaction", async () => {
557
+ const form = await fixture<HTMLFormElement>(html`
558
+ <form>
559
+ <leu-input name="name" label="Name"></leu-input>
560
+ <div tabindex="0"></div>
561
+ </form>
562
+ `)
563
+
564
+ const input = form.querySelector<LeuInput>("leu-input")
565
+ input.focus()
566
+ await sendKeys({ type: "John" })
567
+ await elementUpdated(input)
568
+
569
+ // User has interacted, changing defaultValue should NOT override the typed value
570
+ input.defaultValue = "Jane"
571
+ await elementUpdated(input)
572
+
573
+ expect(input.value).to.equal("John")
574
+
575
+ const formData = new FormData(form)
576
+ expect(formData.get("name")).to.equal("John")
577
+ })
578
+
579
+ it("updates the form data when the value or disabled state changes", async () => {
580
+ const form = await fixture<HTMLFormElement>(html`
581
+ <form>
582
+ <leu-input name="name" value="John" label="Name"></leu-input>
583
+ </form>
584
+ `)
585
+
586
+ const input = form.querySelector<LeuInput>("leu-input")
587
+
588
+ let formData = new FormData(form)
589
+ expect(formData.get("name")).to.equal("John")
590
+
591
+ input.value = "Jane"
592
+ await elementUpdated(input)
593
+
594
+ formData = new FormData(form)
595
+ expect(formData.get("name")).to.equal("Jane")
596
+
597
+ input.disabled = true
598
+ await elementUpdated(input)
599
+
600
+ formData = new FormData(form)
601
+ expect(formData.get("name")).to.be.null
602
+ })
498
603
  })
@@ -22,23 +22,23 @@ export class LeuScrollTop extends LeuElement {
22
22
  static styles = [LeuElement.styles, styles]
23
23
 
24
24
  @state()
25
- protected _showButton: boolean = false
25
+ protected showButton: boolean = false
26
26
 
27
- protected _prevYPos: number = 0
27
+ protected prevYPos: number = 0
28
28
 
29
- protected _scrollDown: boolean = false
29
+ protected hasScrolledDown: boolean = false
30
30
 
31
- protected _scrollListener: EventListener
31
+ protected scrollListener: EventListener
32
32
 
33
33
  scroll = () => {
34
- const delta = window.scrollY - this._prevYPos
34
+ const delta = window.scrollY - this.prevYPos
35
35
 
36
- if (this._scrollDown) {
36
+ if (this.hasScrolledDown) {
37
37
  if (delta < 0) {
38
- this._scrollDown = false
38
+ this.hasScrolledDown = false
39
39
  }
40
40
  } else if (delta > 0) {
41
- this._scrollDown = true
41
+ this.hasScrolledDown = true
42
42
  }
43
43
 
44
44
  /**
@@ -46,22 +46,24 @@ export class LeuScrollTop extends LeuElement {
46
46
  * ... the current scroll position is greater than the window height (below-the-fold) and when
47
47
  * ... scrolling up
48
48
  */
49
- this._showButton = window.scrollY > window.innerHeight && !this._scrollDown
50
- this._prevYPos = window.scrollY
49
+ this.showButton =
50
+ window.scrollY > window.innerHeight && !this.hasScrolledDown
51
+ this.prevYPos = window.scrollY
51
52
  }
52
53
 
53
54
  connectedCallback() {
54
55
  super.connectedCallback()
55
- this._scrollListener = throttle(this.scroll, 100)
56
- document.addEventListener("scroll", this._scrollListener, true)
56
+ this.scrollListener = throttle(this.scroll, 100)
57
+ document.addEventListener("scroll", this.scrollListener, true)
57
58
  }
58
59
 
59
60
  disconnectedCallback() {
60
- document.removeEventListener("scroll", this._scrollListener, true)
61
+ document.removeEventListener("scroll", this.scrollListener, true)
61
62
  super.disconnectedCallback()
62
63
  }
63
64
 
64
- static scrollToTop() {
65
+ // eslint-disable-next-line class-methods-use-this
66
+ scrollToTop() {
65
67
  window.scrollTo({
66
68
  top: 0,
67
69
  left: 0,
@@ -72,14 +74,14 @@ export class LeuScrollTop extends LeuElement {
72
74
  render() {
73
75
  const cssClasses = {
74
76
  "scroll-top": true,
75
- hide: !this._showButton,
77
+ hide: !this.showButton,
76
78
  }
77
79
  return html`
78
80
  <div class=${classMap(cssClasses)}>
79
81
  <leu-button
80
82
  label="Zum Seitenanfang"
81
83
  round
82
- @click="${() => LeuScrollTop.scrollToTop()}"
84
+ @click="${() => this.scrollToTop()}"
83
85
  >
84
86
  <leu-icon name="arrowUp"></leu-icon>
85
87
  </leu-button>