@statistikzh/leu 0.7.0 → 0.9.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 (132) hide show
  1. package/.eslintrc.json +4 -1
  2. package/.github/workflows/release-please.yml +3 -3
  3. package/CHANGELOG.md +35 -0
  4. package/dist/Accordion.d.ts +1 -1
  5. package/dist/Accordion.js +1 -1
  6. package/dist/Breadcrumb.d.ts +1 -1
  7. package/dist/Breadcrumb.js +1 -1
  8. package/dist/{Button-7370f901.d.ts → Button-5a8009c5.d.ts} +2 -2
  9. package/dist/{Button-7370f901.d.ts.map → Button-5a8009c5.d.ts.map} +1 -1
  10. package/dist/{Button-7370f901.js → Button-5a8009c5.js} +3 -4
  11. package/dist/Button.d.ts +1 -1
  12. package/dist/Button.js +2 -2
  13. package/dist/ButtonGroup.d.ts +1 -1
  14. package/dist/ButtonGroup.js +1 -1
  15. package/dist/Checkbox.d.ts +1 -1
  16. package/dist/Checkbox.js +2 -2
  17. package/dist/CheckboxGroup.d.ts +3 -2
  18. package/dist/CheckboxGroup.d.ts.map +1 -1
  19. package/dist/CheckboxGroup.js +4 -3
  20. package/dist/Chip.d.ts +1 -1
  21. package/dist/Chip.js +1 -1
  22. package/dist/ChipGroup.d.ts +1 -1
  23. package/dist/ChipGroup.js +2 -2
  24. package/dist/ChipLink.js +1 -1
  25. package/dist/ChipRemovable.d.ts +7 -0
  26. package/dist/ChipRemovable.d.ts.map +1 -1
  27. package/dist/ChipRemovable.js +23 -3
  28. package/dist/ChipSelectable.d.ts +6 -0
  29. package/dist/ChipSelectable.d.ts.map +1 -1
  30. package/dist/ChipSelectable.js +12 -2
  31. package/dist/Dropdown.d.ts +1 -1
  32. package/dist/Dropdown.js +3 -3
  33. package/dist/Icon.d.ts +1 -1
  34. package/dist/Icon.js +1 -1
  35. package/dist/Input.d.ts +1 -1
  36. package/dist/Input.js +1 -1
  37. package/dist/{LeuElement-ba5ea33d.d.ts → LeuElement-7ab5ef5e.d.ts} +1 -1
  38. package/dist/LeuElement-7ab5ef5e.d.ts.map +1 -0
  39. package/dist/{LeuElement-ba5ea33d.js → LeuElement-7ab5ef5e.js} +12 -9
  40. package/dist/Menu.d.ts +1 -1
  41. package/dist/Menu.js +1 -1
  42. package/dist/MenuItem.d.ts +5 -1
  43. package/dist/MenuItem.d.ts.map +1 -1
  44. package/dist/MenuItem.js +21 -6
  45. package/dist/Pagination.d.ts +1 -1
  46. package/dist/Pagination.js +2 -2
  47. package/dist/Popup.d.ts +1 -1
  48. package/dist/Popup.js +1 -1
  49. package/dist/Radio.d.ts +1 -1
  50. package/dist/Radio.js +2 -2
  51. package/dist/RadioGroup.d.ts +1 -1
  52. package/dist/RadioGroup.d.ts.map +1 -1
  53. package/dist/RadioGroup.js +4 -3
  54. package/dist/ScrollTop.d.ts +1 -1
  55. package/dist/ScrollTop.js +2 -2
  56. package/dist/Select.d.ts +1 -1
  57. package/dist/Select.d.ts.map +1 -1
  58. package/dist/Select.js +9 -5
  59. package/dist/Spinner.d.ts +9 -0
  60. package/dist/Spinner.d.ts.map +1 -0
  61. package/dist/Spinner.js +53 -0
  62. package/dist/Table.d.ts +1 -1
  63. package/dist/Table.js +2 -2
  64. package/dist/VisuallyHidden.d.ts +1 -1
  65. package/dist/VisuallyHidden.js +1 -1
  66. package/dist/index.d.ts +1 -1
  67. package/dist/index.js +2 -2
  68. package/dist/leu-accordion.js +1 -1
  69. package/dist/leu-breadcrumb.js +1 -1
  70. package/dist/leu-button-group.js +1 -1
  71. package/dist/leu-button.d.ts +1 -1
  72. package/dist/leu-button.js +2 -2
  73. package/dist/leu-checkbox-group.js +1 -1
  74. package/dist/leu-checkbox.js +1 -1
  75. package/dist/leu-chip-group.js +1 -1
  76. package/dist/leu-chip-link.js +1 -1
  77. package/dist/leu-chip-removable.js +1 -1
  78. package/dist/leu-chip-selectable.js +1 -1
  79. package/dist/leu-dropdown.js +2 -2
  80. package/dist/leu-icon.js +1 -1
  81. package/dist/leu-input.js +1 -1
  82. package/dist/leu-menu-item.js +1 -1
  83. package/dist/leu-menu.js +1 -1
  84. package/dist/leu-pagination.js +2 -2
  85. package/dist/leu-popup.js +1 -1
  86. package/dist/leu-radio-group.js +1 -1
  87. package/dist/leu-radio.js +1 -1
  88. package/dist/leu-scroll-top.js +2 -2
  89. package/dist/leu-select.js +2 -2
  90. package/dist/leu-spinner.d.ts +3 -0
  91. package/dist/leu-spinner.d.ts.map +1 -0
  92. package/dist/leu-spinner.js +7 -0
  93. package/dist/leu-table.js +2 -2
  94. package/dist/leu-visually-hidden.js +1 -1
  95. package/dist/theme.css +52 -25
  96. package/dist/vscode.html-custom-data.json +31 -4
  97. package/dist/vue/index.d.ts +23 -0
  98. package/dist/web-types.json +48 -8
  99. package/package.json +2 -1
  100. package/rollup.config.js +9 -0
  101. package/scripts/generate-component/templates/[namespace]-[name].js +1 -2
  102. package/src/components/button/button.css +2 -3
  103. package/src/components/checkbox/Checkbox.js +1 -1
  104. package/src/components/checkbox/CheckboxGroup.js +3 -2
  105. package/src/components/checkbox/stories/checkbox-group.stories.js +3 -3
  106. package/src/components/checkbox/stories/checkbox.stories.js +7 -2
  107. package/src/components/chip/ChipGroup.js +1 -1
  108. package/src/components/chip/ChipRemovable.js +18 -0
  109. package/src/components/chip/ChipSelectable.js +13 -1
  110. package/src/components/chip/stories/chip-removable.stories.js +6 -2
  111. package/src/components/chip/test/chip-removable.test.js +36 -2
  112. package/src/components/chip/test/chip-selectable.test.js +11 -1
  113. package/src/components/dropdown/Dropdown.js +1 -1
  114. package/src/components/dropdown/test/dropdown.test.js +20 -3
  115. package/src/components/menu/MenuItem.js +22 -3
  116. package/src/components/menu/menu-item.css +2 -2
  117. package/src/components/menu/stories/menu-item.stories.js +18 -8
  118. package/src/components/menu/test/menu-item.test.js +23 -0
  119. package/src/components/radio/Radio.js +1 -1
  120. package/src/components/radio/RadioGroup.js +3 -2
  121. package/src/components/radio/stories/radio-group.stories.js +3 -3
  122. package/src/components/radio/stories/radio.stories.js +7 -1
  123. package/src/components/select/Select.js +8 -6
  124. package/src/components/select/test/select.test.js +30 -0
  125. package/src/components/spinner/Spinner.js +31 -0
  126. package/src/components/spinner/leu-spinner.js +5 -0
  127. package/src/components/spinner/spinner.css +20 -0
  128. package/src/components/spinner/stories/spinner.stories.js +29 -0
  129. package/src/components/spinner/test/spinner.test.js +30 -0
  130. package/src/lib/LeuElement.js +19 -11
  131. package/src/styles/custom-properties.css +8 -3
  132. package/dist/LeuElement-ba5ea33d.d.ts.map +0 -1
@@ -7,6 +7,7 @@ import { LeuIcon } from "../icon/Icon.js"
7
7
  * @slot - The content of the chip
8
8
  * @tagname leu-chip-removable
9
9
  * @fires remove - Dispatched when the user clicks on the chip
10
+ * @prop {string} value - The value of the chip.
10
11
  */
11
12
  export class LeuChipRemovable extends LeuChipBase {
12
13
  static dependencies = {
@@ -15,12 +16,29 @@ export class LeuChipRemovable extends LeuChipBase {
15
16
 
16
17
  static properties = {
17
18
  ...LeuChipBase.properties,
19
+ value: { type: String, reflect: true },
20
+ }
21
+
22
+ constructor() {
23
+ super()
24
+ this.value = ""
25
+ }
26
+
27
+ /**
28
+ * Returns the value of the chip. If `value` is not set, it will return the text content
29
+ * @returns {string}
30
+ */
31
+ getValue() {
32
+ return this.value || this.textContent.trim()
18
33
  }
19
34
 
20
35
  handleClick() {
21
36
  const customEvent = new CustomEvent("leu:remove", {
22
37
  bubbles: true,
23
38
  composed: true,
39
+ detail: {
40
+ value: this.getValue(),
41
+ },
24
42
  })
25
43
  this.dispatchEvent(customEvent)
26
44
  }
@@ -43,6 +43,7 @@ export class LeuChipSelectable extends LeuChipBase {
43
43
  */
44
44
  this.variant = VARIANTS.toggle
45
45
  this.checked = false
46
+ this.value = ""
46
47
 
47
48
  if (this.variant === VARIANTS.radio && this.size === SIZES.small) {
48
49
  console.warn("Small size has no effect on radio variant")
@@ -62,7 +63,10 @@ export class LeuChipSelectable extends LeuChipBase {
62
63
  this.checked = nextcheckedState
63
64
  this.dispatchEvent(
64
65
  new CustomEvent("input", {
65
- detail: { checked: this.checked },
66
+ detail: {
67
+ checked: this.checked,
68
+ value: this.getValue(),
69
+ },
66
70
  bubbles: true,
67
71
  composed: true,
68
72
  })
@@ -70,6 +74,14 @@ export class LeuChipSelectable extends LeuChipBase {
70
74
  }
71
75
  }
72
76
 
77
+ /**
78
+ * Returns the value of the chip. If `value` is not set, it will return the text content
79
+ * @returns {string}
80
+ */
81
+ getValue() {
82
+ return this.value || this.textContent.trim()
83
+ }
84
+
73
85
  render() {
74
86
  return html`<button
75
87
  @click=${() => this.handleClick()}
@@ -1,4 +1,5 @@
1
1
  import { html } from "lit"
2
+ import { action } from "@storybook/addon-actions"
2
3
 
3
4
  import "../leu-chip-removable.js"
4
5
 
@@ -6,7 +7,8 @@ export default {
6
7
  title: "Chip/Removable",
7
8
  component: "leu-chip-removable",
8
9
  args: {
9
- label: "Publikationen",
10
+ label: "Daten",
11
+ onRemove: action("leu:remove"),
10
12
  },
11
13
  parameters: {
12
14
  design: {
@@ -27,7 +29,9 @@ function Template(args) {
27
29
  : "var(--leu-color-black-5)"}; padding: 1rem;"
28
30
  data-root
29
31
  >
30
- <leu-chip-removable ?inverted=${args.inverted}
32
+ <leu-chip-removable
33
+ @leu:remove=${args.onRemove}
34
+ ?inverted=${args.inverted}
31
35
  >${args.label}</leu-chip-removable
32
36
  >
33
37
  </div>
@@ -1,11 +1,18 @@
1
1
  import { html } from "lit"
2
2
  import { fixture, expect, oneEvent } from "@open-wc/testing"
3
3
  import { sendKeys } from "@web/test-runner-commands"
4
+ import { ifDefined } from "lit/directives/if-defined.js"
4
5
 
5
6
  import "../leu-chip-removable.js"
6
7
 
7
- async function defaultFixture() {
8
- return fixture(html` <leu-chip-removable>Daten</leu-chip-removable> `)
8
+ async function defaultFixture(args = {}) {
9
+ return fixture(
10
+ html`
11
+ <leu-chip-removable value=${ifDefined(args.value)}
12
+ >${args.label ?? "Daten"}</leu-chip-removable
13
+ >
14
+ `
15
+ )
9
16
  }
10
17
 
11
18
  describe("LeuChipRemovable", () => {
@@ -70,4 +77,31 @@ describe("LeuChipRemovable", () => {
70
77
 
71
78
  expect(event).to.exist
72
79
  })
80
+
81
+ it("sends the value in the remove event", async () => {
82
+ const el = await defaultFixture({ label: `Daten  ` }) // eslint-disable-line no-irregular-whitespace
83
+ const button = el.shadowRoot.querySelector("button")
84
+
85
+ setTimeout(() => button.click())
86
+ const event = await oneEvent(el, "leu:remove")
87
+
88
+ expect(event.detail.value).to.equal("Daten")
89
+
90
+ el.value = "test"
91
+
92
+ setTimeout(() => button.click())
93
+ const event2 = await oneEvent(el, "leu:remove")
94
+
95
+ expect(event2.detail.value).to.equal("test")
96
+ })
97
+
98
+ it("returns the value or label when getValue is called", async () => {
99
+ const el = await defaultFixture({ label: `Daten  ` }) // eslint-disable-line no-irregular-whitespace
100
+
101
+ expect(el.getValue()).to.equal("Daten")
102
+
103
+ el.value = "daten-01"
104
+
105
+ expect(el.getValue()).to.equal("daten-01")
106
+ })
73
107
  })
@@ -9,7 +9,7 @@ async function defaultFixture(args = {}) {
9
9
  return fixture(
10
10
  html`
11
11
  <leu-chip-selectable
12
- value="Publikationen"
12
+ value=${ifDefined(args.value)}
13
13
  variant=${ifDefined(args.variant)}
14
14
  ?checked=${args.checked}
15
15
  >Publikationen</leu-chip-selectable
@@ -90,4 +90,14 @@ describe("LeuChipSelectable", () => {
90
90
 
91
91
  expect(el.checked).to.be.true
92
92
  })
93
+
94
+ it("returns the value or label when getValue is called", async () => {
95
+ const el = await defaultFixture()
96
+
97
+ expect(el.getValue()).to.equal("Publikationen")
98
+
99
+ el.value = "publikationen-01"
100
+
101
+ expect(el.getValue()).to.equal("publikationen-01")
102
+ })
93
103
  })
@@ -65,7 +65,7 @@ export class LeuDropdown extends LeuElement {
65
65
  }
66
66
 
67
67
  _documentClickHandler = (event) => {
68
- if (!this.contains(event.target)) {
68
+ if (!event.composedPath().includes(this)) {
69
69
  this.expanded = false
70
70
  }
71
71
  }
@@ -1,10 +1,13 @@
1
1
  import { html } from "lit"
2
- import { fixture, expect } from "@open-wc/testing"
2
+ import { fixture, expect, elementUpdated } from "@open-wc/testing"
3
3
 
4
4
  import "../leu-dropdown.js"
5
5
 
6
- async function defaultFixture() {
7
- return fixture(html` <leu-dropdown label="Download">
6
+ async function defaultFixture(args = { expanded: false }) {
7
+ return fixture(html` <leu-dropdown
8
+ label="Download"
9
+ ?expanded=${args.expanded}
10
+ >
8
11
  <leu-menu>
9
12
  <leu-menu-item>Als CSV Tabelle</leu-menu-item>
10
13
  <leu-menu-item>Als XLS Tabelle</leu-menu-item>
@@ -28,4 +31,18 @@ describe("LeuDropdown", () => {
28
31
 
29
32
  await expect(el).shadowDom.to.be.accessible()
30
33
  })
34
+
35
+ it("closes the popup when the document is clicked outside the component", async () => {
36
+ const el = await defaultFixture()
37
+
38
+ const toggleButton = el.shadowRoot.querySelector("leu-button")
39
+ toggleButton.click()
40
+ await elementUpdated(el)
41
+
42
+ expect(el.expanded).to.be.true
43
+
44
+ document.body.click()
45
+
46
+ expect(el.expanded).to.be.false
47
+ })
31
48
  })
@@ -1,4 +1,4 @@
1
- import { html } from "lit"
1
+ import { html, nothing } from "lit"
2
2
  import { ifDefined } from "lit/directives/if-defined.js"
3
3
 
4
4
  import { LeuElement } from "../../lib/LeuElement.js"
@@ -15,6 +15,7 @@ import styles from "./menu-item.css"
15
15
  * @tagname leu-menu-item
16
16
  * @slot - The label of the menu item
17
17
  * @property {boolean} active - Defines if the item is selected or checked
18
+ * @property {boolean} multipleSelection - If the item is part of a multiple selection. Renders a checkmark before the label when active
18
19
  * @property {boolean} disabled - Disables the underlying button or link
19
20
  * @property {string} value - The value of the item. It must not contain commas. See `getValue()`
20
21
  * @property {string} href - The href of the underlying link
@@ -38,6 +39,11 @@ export class LeuMenuItem extends LeuElement {
38
39
 
39
40
  static properties = {
40
41
  active: { type: Boolean, reflect: true },
42
+ multipleSelection: {
43
+ type: Boolean,
44
+ reflect: true,
45
+ attr: "multiple-selection",
46
+ },
41
47
  disabled: { type: Boolean, reflect: true },
42
48
  tabbable: { type: Boolean, reflect: true },
43
49
  href: { type: String, reflect: true },
@@ -50,6 +56,7 @@ export class LeuMenuItem extends LeuElement {
50
56
 
51
57
  this.active = false
52
58
  this.disabled = false
59
+ this.multipleSelection = false
53
60
  this.value = undefined
54
61
  this.href = undefined
55
62
  this.tabbable = undefined
@@ -80,7 +87,7 @@ export class LeuMenuItem extends LeuElement {
80
87
  * @returns {string}
81
88
  */
82
89
  getValue() {
83
- return this.value || this.innerText
90
+ return this.value || this.textContent.trim()
84
91
  }
85
92
 
86
93
  _getAria() {
@@ -142,9 +149,21 @@ export class LeuMenuItem extends LeuElement {
142
149
  </button>`
143
150
  }
144
151
 
152
+ _renderBeforeSlotDefault() {
153
+ if (!this.multipleSelection) {
154
+ return nothing
155
+ }
156
+
157
+ return this.active
158
+ ? html`<leu-icon name="check"></leu-icon>`
159
+ : html`<leu-icon></leu-icon>`
160
+ }
161
+
145
162
  render() {
146
163
  const content = html`
147
- <slot class="before" name="before"></slot>
164
+ <slot class="before" name="before"
165
+ >${this._renderBeforeSlotDefault()}</slot
166
+ >
148
167
  <span class="label"><slot></slot></span>
149
168
  <slot class="after" name="after"></slot>
150
169
  `
@@ -6,10 +6,10 @@
6
6
  :host {
7
7
  --background: var(--leu-color-black-0);
8
8
  --background-hover: var(--leu-color-black-10);
9
- --background-active: var(--leu-color-accent-blue);
9
+ --background-active: var(--leu-color-func-cyan);
10
10
  --background-disabled: var(--leu-color-black-black-0);
11
11
  --color: var(--leu-color-black-transp-60);
12
- --color-active: var(--leu-color-black-0);
12
+ --color-active: var(--leu-color-black-100);
13
13
  --color-disabled: var(--leu-color-black-transp-20);
14
14
  --font-regular: var(--leu-font-family-regular);
15
15
  --font-black: var(--leu-font-family-black);
@@ -1,4 +1,4 @@
1
- import { html } from "lit"
1
+ import { html, nothing } from "lit"
2
2
  import { ifDefined } from "lit/directives/if-defined.js"
3
3
 
4
4
  import "../leu-menu-item.js"
@@ -29,14 +29,19 @@ function Template(args) {
29
29
  href=${ifDefined(args.href)}
30
30
  ?active=${args.active}
31
31
  ?disabled=${args.disabled}
32
+ ?multipleSelection=${args.multipleSelection}
32
33
  >
33
- ${isIcon(args.before)
34
- ? html`<leu-icon slot="before" name=${args.before}></leu-icon>`
35
- : html`<span slot="before">${args.before}</span>`}
34
+ ${args.before
35
+ ? isIcon(args.before)
36
+ ? html`<leu-icon slot="before" name=${args.before}></leu-icon>`
37
+ : html`<span slot="before">${args.before}</span>`
38
+ : nothing}
36
39
  ${args.label}
37
- ${isIcon(args.after)
38
- ? html`<leu-icon slot="after" name=${args.after}></leu-icon>`
39
- : html`<span slot="after">${args.after}</span>`}
40
+ ${args.after
41
+ ? isIcon(args.after)
42
+ ? html`<leu-icon slot="after" name=${args.after}></leu-icon>`
43
+ : html`<span slot="after">${args.after}</span>`
44
+ : null}
40
45
  </leu-menu-item>
41
46
  `
42
47
  }
@@ -50,7 +55,7 @@ Active.args = {
50
55
 
51
56
  export const IconBefore = Template.bind({})
52
57
  IconBefore.args = {
53
- before: "check",
58
+ before: "download",
54
59
  }
55
60
 
56
61
  export const IconAfterLink = Template.bind({})
@@ -69,3 +74,8 @@ export const IconPlaceholder = Template.bind({})
69
74
  IconPlaceholder.args = {
70
75
  before: "EMPTY",
71
76
  }
77
+
78
+ export const MultipleSelection = Template.bind({})
79
+ MultipleSelection.args = {
80
+ multipleSelection: true,
81
+ }
@@ -15,6 +15,7 @@ async function defaultFixture(args = {}) {
15
15
  ?active=${args.active}
16
16
  ?disabled=${args.disabled}
17
17
  ?tabbable=${args.tabbable}
18
+ ?multipleSelection=${args.multipleSelection}
18
19
  >
19
20
  ${args.label}
20
21
  </leu-menu-item>
@@ -183,4 +184,26 @@ describe("LeuMenuItem", () => {
183
184
 
184
185
  expect(el.getValue()).to.equal("download-01")
185
186
  })
187
+
188
+ it("renders a palceholder icon when the menu item is part of multiple selection but not active", async () => {
189
+ const el = await defaultFixture({
190
+ label: "Download",
191
+ multipleSelection: true,
192
+ })
193
+
194
+ const icon = el.shadowRoot.querySelector("leu-icon")
195
+ expect(icon).to.exist
196
+ expect(icon).to.have.attribute("name", "EMPTY")
197
+ })
198
+
199
+ it("renders a check icon when the menu item is part of multiple selection and is active", async () => {
200
+ const el = await defaultFixture({
201
+ label: "Download",
202
+ multipleSelection: true,
203
+ })
204
+
205
+ const icon = el.shadowRoot.querySelector("leu-icon")
206
+ expect(icon).to.exist
207
+ expect(icon).to.have.attribute("name", "check")
208
+ })
186
209
  })
@@ -54,7 +54,7 @@ export class LeuRadio extends LeuElement {
54
54
  @input=${this.handleInput}
55
55
  .checked=${this.checked}
56
56
  ?disabled=${this.disabled}
57
- .value=${this.value}
57
+ .value=${this.value ?? ""}
58
58
  />
59
59
  <label for=${`radio-${this.name}`} class="label"><slot></slot></label>
60
60
  `
@@ -19,7 +19,8 @@ export class LeuRadioGroup extends LeuElement {
19
19
 
20
20
  constructor() {
21
21
  super()
22
- this.orientation = "HORIZONTAL"
22
+ /** @type {"horizontal" | "vertical"} */
23
+ this.orientation = "horizontal"
23
24
  this._currentIndex = 0
24
25
  this.items = []
25
26
  }
@@ -154,7 +155,7 @@ export class LeuRadioGroup extends LeuElement {
154
155
  render() {
155
156
  const fieldsetClasses = {
156
157
  fieldset: "true",
157
- "fieldset--vertical": this.orientation === "VERTICAL",
158
+ "fieldset--vertical": this.orientation === "vertical",
158
159
  }
159
160
 
160
161
  return html`
@@ -9,7 +9,7 @@ export default {
9
9
  argTypes: {
10
10
  label: { control: "text" },
11
11
  orientation: {
12
- options: ["VERTICAL", "HORIZONTAL"],
12
+ options: ["vertical", "horizontal"],
13
13
  control: { type: "radio" },
14
14
  },
15
15
  },
@@ -44,11 +44,11 @@ HorizontalLabel.args = {
44
44
 
45
45
  export const Vertical = Template.bind({})
46
46
  Vertical.args = {
47
- orientation: "VERTICAL",
47
+ orientation: "vertical",
48
48
  }
49
49
 
50
50
  export const VerticalLabel = Template.bind({})
51
51
  VerticalLabel.args = {
52
- orientation: "VERTICAL",
52
+ orientation: "vertical",
53
53
  label: "Anrede",
54
54
  }
@@ -22,9 +22,15 @@ function Template({
22
22
  value = "",
23
23
  checked = false,
24
24
  disabled = false,
25
+ name = "",
25
26
  }) {
26
27
  return html`
27
- <leu-radio .value=${value} ?checked=${checked} ?disabled=${disabled}>
28
+ <leu-radio
29
+ .value=${value}
30
+ ?checked=${checked}
31
+ ?disabled=${disabled}
32
+ name=${name}
33
+ >
28
34
  ${label}
29
35
  </leu-radio>
30
36
  `
@@ -146,11 +146,13 @@ export class LeuSelect extends LeuElement {
146
146
 
147
147
  if (
148
148
  changedProperties.has("value") ||
149
- changedProperties.has("_optionFilter")
149
+ changedProperties.has("_optionFilter") ||
150
+ changedProperties.has("multiple")
150
151
  ) {
151
152
  this._updateMenuItems({
152
153
  value: changedProperties.has("value"),
153
154
  optionFilter: changedProperties.has("_optionFilter"),
155
+ multiple: changedProperties.has("multiple"),
154
156
  })
155
157
  }
156
158
  }
@@ -171,6 +173,10 @@ export class LeuSelect extends LeuElement {
171
173
 
172
174
  /* eslint-disable no-param-reassign */
173
175
  menuItems.forEach((menuItem) => {
176
+ if (changed.multiple) {
177
+ menuItem.multipleSelection = this.multiple
178
+ }
179
+
174
180
  if (changed.optionFilter) {
175
181
  menuItem.hidden =
176
182
  this._optionFilter !== "" &&
@@ -203,11 +209,7 @@ export class LeuSelect extends LeuElement {
203
209
  * @param {MouseEvent} event
204
210
  */
205
211
  _handleDocumentClick = (event) => {
206
- if (
207
- event.target instanceof Node &&
208
- !this.contains(event.target) &&
209
- this.open
210
- ) {
212
+ if (!event.composedPath().includes(this) && this.open) {
211
213
  this._closeDropdown()
212
214
  }
213
215
  }
@@ -404,4 +404,34 @@ describe("LeuSelect", () => {
404
404
  const popup = el.shadowRoot.querySelector("leu-popup")
405
405
  expect(popup.active).to.not.be.true
406
406
  })
407
+
408
+ it("sets the multipleSelection property on the menu items when multiple selection is allowed", async () => {
409
+ const el = await defaultFixture({
410
+ options: MUNICIPALITIES,
411
+ label: "Gemeinde",
412
+ multiple: true,
413
+ })
414
+
415
+ const menuItems = Array.from(el.querySelectorAll("leu-menu-item"))
416
+ expect(menuItems.every((item) => item.multipleSelection)).to.be.true
417
+
418
+ el.multiple = false
419
+ await elementUpdated(el)
420
+
421
+ expect(menuItems.every((item) => !item.multipleSelection)).to.be.true
422
+
423
+ it("closes the popup when the document is clicked outside the component", async () => {
424
+ const el = await defaultFixture({
425
+ options: MUNICIPALITIES,
426
+ label: "Gemeinde",
427
+ })
428
+
429
+ const toggleButton = el.shadowRoot.querySelector(".select-toggle")
430
+ toggleButton.click()
431
+
432
+ document.body.click()
433
+
434
+ const popup = el.shadowRoot.querySelector("leu-popup")
435
+ expect(popup.active).to.not.be.true
436
+ })
407
437
  })
@@ -0,0 +1,31 @@
1
+ import { html } from "lit"
2
+
3
+ import { LeuElement } from "../../lib/LeuElement.js"
4
+
5
+ import styles from "./spinner.css"
6
+
7
+ /**
8
+ * @tagname leu-spinner
9
+ * @cssprop --leu-spinner-size - The size of the spinner.
10
+ */
11
+ export class LeuSpinner extends LeuElement {
12
+ static styles = styles
13
+
14
+ render() {
15
+ return html`
16
+ <svg
17
+ class="spinner"
18
+ fill="none"
19
+ xmlns="http://www.w3.org/2000/svg"
20
+ viewBox="0 0 56 56"
21
+ role="presentation"
22
+ >
23
+ <path
24
+ d="M13.8579 13.858c7.8105-7.8105 20.4737-7.8105 28.2842 0 7.8105 7.8104 7.8105 20.4737 0 28.2842-7.8105 7.8105-20.4737 7.8105-28.2842 0-4.3487-4.3486-6.2761-10.2016-5.7824-15.8838"
25
+ stroke="currentColor"
26
+ stroke-width="3"
27
+ />
28
+ </svg>
29
+ `
30
+ }
31
+ }
@@ -0,0 +1,5 @@
1
+ import { LeuSpinner } from "./Spinner.js"
2
+
3
+ export { LeuSpinner }
4
+
5
+ LeuSpinner.define("leu-spinner")
@@ -0,0 +1,20 @@
1
+ @keyframes leu-spinner-rotate {
2
+ from {
3
+ transform: rotate(0deg);
4
+ }
5
+
6
+ to {
7
+ transform: rotate(360deg);
8
+ }
9
+ }
10
+
11
+ :host {
12
+ color: var(--leu-color-func-cyan);
13
+ }
14
+
15
+ .spinner {
16
+ display: block;
17
+ width: var(--leu-spinner-size, 3.5rem);
18
+ height: var(--leu-spinner-size, 3.5rem);
19
+ animation: leu-spinner-rotate 1s cubic-bezier(0.49, 0.12, 0.56, 0.91) infinite;
20
+ }
@@ -0,0 +1,29 @@
1
+ import { html } from "lit"
2
+ import "../leu-spinner.js"
3
+ import { styleMap } from "lit/directives/style-map.js"
4
+
5
+ export default {
6
+ title: "Spinner",
7
+ component: "leu-spinner",
8
+ argTypes: {
9
+ color: {
10
+ control: {
11
+ type: "color",
12
+ presetColors: ["#009ee0", "#d93c1a", "#1a7f1f"],
13
+ },
14
+ },
15
+ },
16
+ }
17
+
18
+ function Template({ size, color }) {
19
+ const styles = styleMap({
20
+ color,
21
+ "--leu-spinner-size": size ? `${size}px` : null,
22
+ })
23
+ return html` <leu-spinner style=${styles}></leu-spinner> `
24
+ }
25
+
26
+ export const Regular = Template.bind({})
27
+ Regular.args = {
28
+ size: 56,
29
+ }
@@ -0,0 +1,30 @@
1
+ import { html } from "lit"
2
+ import { fixture, expect } from "@open-wc/testing"
3
+
4
+ import "../leu-spinner.js"
5
+
6
+ async function defaultFixture() {
7
+ return fixture(html`<leu-spinner></leu-spinner>`)
8
+ }
9
+
10
+ describe("LeuSpinner", () => {
11
+ it("is a defined element", async () => {
12
+ const el = await customElements.get("leu-spinner")
13
+
14
+ await expect(el).not.to.be.undefined
15
+ })
16
+
17
+ it("passes the a11y audit", async () => {
18
+ const el = await defaultFixture()
19
+
20
+ await expect(el).shadowDom.to.be.accessible()
21
+ })
22
+
23
+ it("renders a svg element with a 56x56 viewbox", async () => {
24
+ const el = await defaultFixture()
25
+
26
+ const svg = el.shadowRoot.querySelector("svg")
27
+
28
+ expect(svg).to.have.attribute("viewBox", "0 0 56 56")
29
+ })
30
+ })