@statistikzh/leu 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.husky/commit-msg +0 -3
- package/.husky/pre-commit +0 -3
- package/CHANGELOG.md +54 -0
- package/dist/Accordion.d.ts +10 -9
- package/dist/Accordion.d.ts.map +1 -1
- package/dist/Accordion.js +12 -11
- package/dist/Breadcrumb.d.ts +4 -4
- package/dist/Breadcrumb.d.ts.map +1 -1
- package/dist/Breadcrumb.js +28 -24
- package/dist/{Button-5326c982.d.ts → Button-7370f901.d.ts} +10 -11
- package/dist/Button-7370f901.d.ts.map +1 -0
- package/dist/{Button-5326c982.js → Button-7370f901.js} +57 -67
- package/dist/Button.d.ts +1 -1
- package/dist/Button.js +3 -3
- package/dist/ButtonGroup.d.ts +2 -2
- package/dist/ButtonGroup.d.ts.map +1 -1
- package/dist/ButtonGroup.js +3 -3
- package/dist/Checkbox.d.ts +4 -3
- package/dist/Checkbox.d.ts.map +1 -1
- package/dist/Checkbox.js +14 -17
- package/dist/CheckboxGroup.d.ts +2 -2
- package/dist/CheckboxGroup.d.ts.map +1 -1
- package/dist/CheckboxGroup.js +4 -4
- package/dist/Chip.d.ts +2 -2
- package/dist/Chip.d.ts.map +1 -1
- package/dist/Chip.js +23 -28
- package/dist/ChipGroup.d.ts +16 -8
- package/dist/ChipGroup.d.ts.map +1 -1
- package/dist/ChipGroup.js +39 -9
- package/dist/ChipLink.d.ts +2 -1
- package/dist/ChipLink.d.ts.map +1 -1
- package/dist/ChipLink.js +4 -7
- package/dist/ChipRemovable.d.ts +0 -2
- package/dist/ChipRemovable.d.ts.map +1 -1
- package/dist/ChipRemovable.js +8 -11
- package/dist/ChipSelectable.d.ts +12 -2
- package/dist/ChipSelectable.d.ts.map +1 -1
- package/dist/ChipSelectable.js +24 -26
- package/dist/Dropdown.d.ts +9 -5
- package/dist/Dropdown.d.ts.map +1 -1
- package/dist/Dropdown.js +68 -32
- package/dist/Icon.d.ts +116 -0
- package/dist/Icon.d.ts.map +1 -0
- package/dist/{icon-03e86700.js → Icon.js} +61 -32
- package/dist/Input.d.ts +13 -17
- package/dist/Input.d.ts.map +1 -1
- package/dist/Input.js +33 -24
- package/dist/LeuElement-ba5ea33d.d.ts +7 -0
- package/dist/LeuElement-ba5ea33d.d.ts.map +1 -0
- package/dist/{_rollupPluginBabelHelpers-20f659f4.js → LeuElement-ba5ea33d.js} +20 -1
- package/dist/Menu.d.ts +24 -2
- package/dist/Menu.d.ts.map +1 -1
- package/dist/Menu.js +120 -3
- package/dist/MenuItem.d.ts +28 -11
- package/dist/MenuItem.d.ts.map +1 -1
- package/dist/MenuItem.js +110 -63
- package/dist/Pagination.d.ts +10 -3
- package/dist/Pagination.d.ts.map +1 -1
- package/dist/Pagination.js +24 -21
- package/dist/Popup.d.ts +21 -3
- package/dist/Popup.d.ts.map +1 -1
- package/dist/Popup.js +44 -17
- package/dist/Radio.d.ts +4 -2
- package/dist/Radio.d.ts.map +1 -1
- package/dist/Radio.js +9 -14
- package/dist/RadioGroup.d.ts +2 -2
- package/dist/RadioGroup.d.ts.map +1 -1
- package/dist/RadioGroup.js +20 -11
- package/dist/ScrollTop.d.ts +2 -2
- package/dist/ScrollTop.d.ts.map +1 -1
- package/dist/ScrollTop.js +10 -8
- package/dist/Select.d.ts +75 -37
- package/dist/Select.d.ts.map +1 -1
- package/dist/Select.js +279 -181
- package/dist/Table.d.ts +2 -6
- package/dist/Table.d.ts.map +1 -1
- package/dist/Table.js +16 -16
- package/dist/VisuallyHidden.d.ts +2 -2
- package/dist/VisuallyHidden.d.ts.map +1 -1
- package/dist/VisuallyHidden.js +3 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -14
- package/dist/leu-accordion.d.ts.map +1 -1
- package/dist/leu-accordion.js +2 -3
- package/dist/leu-breadcrumb.d.ts.map +1 -1
- package/dist/leu-breadcrumb.js +4 -10
- package/dist/leu-button-group.d.ts.map +1 -1
- package/dist/leu-button-group.js +2 -3
- package/dist/leu-button.d.ts +1 -1
- package/dist/leu-button.d.ts.map +1 -1
- package/dist/leu-button.js +4 -5
- package/dist/leu-checkbox-group.d.ts.map +1 -1
- package/dist/leu-checkbox-group.js +2 -3
- package/dist/leu-checkbox.d.ts.map +1 -1
- package/dist/leu-checkbox.js +3 -4
- package/dist/leu-chip-group.d.ts.map +1 -1
- package/dist/leu-chip-group.js +2 -3
- package/dist/leu-chip-link.d.ts.map +1 -1
- package/dist/leu-chip-link.js +2 -3
- package/dist/leu-chip-removable.d.ts.map +1 -1
- package/dist/leu-chip-removable.js +3 -4
- package/dist/leu-chip-selectable.d.ts.map +1 -1
- package/dist/leu-chip-selectable.js +2 -3
- package/dist/leu-dropdown.d.ts.map +1 -1
- package/dist/leu-dropdown.js +5 -10
- package/dist/leu-icon.d.ts +3 -0
- package/dist/leu-icon.d.ts.map +1 -0
- package/dist/leu-icon.js +7 -0
- package/dist/leu-input.d.ts.map +1 -1
- package/dist/leu-input.js +3 -4
- package/dist/leu-menu-item.d.ts.map +1 -1
- package/dist/leu-menu-item.js +3 -5
- package/dist/leu-menu.d.ts.map +1 -1
- package/dist/leu-menu.js +5 -3
- package/dist/leu-pagination.d.ts.map +1 -1
- package/dist/leu-pagination.js +4 -7
- package/dist/leu-popup.d.ts.map +1 -1
- package/dist/leu-popup.js +2 -3
- package/dist/leu-radio-group.d.ts.map +1 -1
- package/dist/leu-radio-group.js +2 -3
- package/dist/leu-radio.d.ts.map +1 -1
- package/dist/leu-radio.js +2 -3
- package/dist/leu-scroll-top.d.ts.map +1 -1
- package/dist/leu-scroll-top.js +4 -6
- package/dist/leu-select.d.ts.map +1 -1
- package/dist/leu-select.js +5 -13
- package/dist/leu-table.d.ts.map +1 -1
- package/dist/leu-table.js +4 -8
- package/dist/leu-visually-hidden.d.ts.map +1 -1
- package/dist/leu-visually-hidden.js +2 -3
- package/dist/theme.css +2 -0
- package/dist/vscode.html-custom-data.json +124 -74
- package/dist/vue/index.d.ts +83 -67
- package/dist/web-types.json +256 -142
- package/package.json +9 -12
- package/scripts/generate-component/templates/[Name].js +6 -3
- package/scripts/generate-component/templates/test/[name].test.js +1 -1
- package/src/components/accordion/Accordion.js +13 -10
- package/src/components/accordion/leu-accordion.js +1 -2
- package/src/components/breadcrumb/Breadcrumb.js +31 -18
- package/src/components/breadcrumb/leu-breadcrumb.js +1 -2
- package/src/components/button/Button.js +45 -71
- package/src/components/button/button.css +11 -9
- package/src/components/button/leu-button.js +1 -2
- package/src/components/button/stories/button.stories.js +60 -19
- package/src/components/button/test/button.test.js +26 -63
- package/src/components/button-group/ButtonGroup.js +4 -2
- package/src/components/button-group/leu-button-group.js +1 -2
- package/src/components/checkbox/Checkbox.js +17 -11
- package/src/components/checkbox/CheckboxGroup.js +6 -3
- package/src/components/checkbox/leu-checkbox-group.js +1 -2
- package/src/components/checkbox/leu-checkbox.js +1 -2
- package/src/components/checkbox/stories/checkbox-group.stories.js +10 -26
- package/src/components/checkbox/stories/checkbox.stories.js +2 -7
- package/src/components/checkbox/test/checkbox-group.test.js +6 -21
- package/src/components/checkbox/test/checkbox.test.js +1 -12
- package/src/components/chip/Chip.js +5 -4
- package/src/components/chip/ChipGroup.js +38 -8
- package/src/components/chip/ChipLink.js +3 -7
- package/src/components/chip/ChipRemovable.js +8 -11
- package/src/components/chip/ChipSelectable.js +23 -27
- package/src/components/chip/chip.css +19 -20
- package/src/components/chip/leu-chip-group.js +1 -2
- package/src/components/chip/leu-chip-link.js +1 -2
- package/src/components/chip/leu-chip-removable.js +1 -2
- package/src/components/chip/leu-chip-selectable.js +1 -2
- package/src/components/chip/stories/chip-group.stories.js +6 -9
- package/src/components/chip/stories/chip-link.stories.js +3 -5
- package/src/components/chip/stories/chip-removable.stories.js +3 -4
- package/src/components/chip/stories/chip-selectable.stories.js +3 -3
- package/src/components/chip/test/chip-group.test.js +82 -30
- package/src/components/chip/test/chip-link.test.js +2 -6
- package/src/components/chip/test/chip-removable.test.js +4 -10
- package/src/components/chip/test/chip-selectable.test.js +10 -12
- package/src/components/dropdown/Dropdown.js +79 -26
- package/src/components/dropdown/leu-dropdown.js +1 -2
- package/src/components/dropdown/stories/dropdown.stories.js +30 -7
- package/src/components/dropdown/test/dropdown.test.js +5 -5
- package/src/components/icon/Icon.js +55 -0
- package/src/components/icon/icon.css +6 -0
- package/src/components/icon/leu-icon.js +5 -0
- package/src/components/icon/{icon.js → paths.js} +4 -37
- package/src/components/icon/stories/icon.stories.js +47 -0
- package/src/components/icon/test/icon.test.js +23 -40
- package/src/components/input/Input.js +31 -20
- package/src/components/input/input.css +4 -2
- package/src/components/input/leu-input.js +1 -2
- package/src/components/input/stories/input.stories.js +5 -5
- package/src/components/input/test/input.test.js +22 -0
- package/src/components/menu/Menu.js +143 -2
- package/src/components/menu/MenuItem.js +104 -52
- package/src/components/menu/leu-menu-item.js +1 -2
- package/src/components/menu/leu-menu.js +1 -2
- package/src/components/menu/menu-item.css +11 -4
- package/src/components/menu/stories/menu-item.stories.js +15 -4
- package/src/components/menu/stories/menu.stories.js +34 -7
- package/src/components/menu/test/menu-item.test.js +88 -82
- package/src/components/menu/test/menu.test.js +101 -8
- package/src/components/pagination/Pagination.js +27 -18
- package/src/components/pagination/leu-pagination.js +1 -2
- package/src/components/popup/Popup.js +39 -16
- package/src/components/popup/leu-popup.js +1 -2
- package/src/components/popup/popup.css +1 -0
- package/src/components/radio/Radio.js +12 -7
- package/src/components/radio/RadioGroup.js +18 -12
- package/src/components/radio/leu-radio-group.js +1 -2
- package/src/components/radio/leu-radio.js +1 -2
- package/src/components/radio/stories/radio-group.stories.js +5 -19
- package/src/components/radio/stories/radio.stories.js +2 -7
- package/src/components/radio/test/radio-group.test.js +6 -9
- package/src/components/radio/test/radio.test.js +3 -13
- package/src/components/scroll-top/ScrollTop.js +15 -5
- package/src/components/scroll-top/leu-scroll-top.js +1 -2
- package/src/components/select/Select.js +279 -175
- package/src/components/select/leu-select.js +1 -2
- package/src/components/select/select.css +20 -12
- package/src/components/select/stories/select.stories.js +16 -2
- package/src/components/select/test/select.test.js +191 -37
- package/src/components/table/Table.js +15 -9
- package/src/components/table/leu-table.js +1 -2
- package/src/components/table/table.css +3 -1
- package/src/components/visually-hidden/VisuallyHidden.js +6 -2
- package/src/components/visually-hidden/leu-visually-hidden.js +1 -2
- package/src/lib/LeuElement.js +23 -0
- package/src/lib/a11y.js +26 -0
- package/src/styles/custom-properties.css +2 -0
- package/web-test-runner.config.mjs +2 -0
- package/dist/Button-5326c982.d.ts.map +0 -1
- package/dist/_rollupPluginBabelHelpers-20f659f4.d.ts +0 -3
- package/dist/_rollupPluginBabelHelpers-20f659f4.d.ts.map +0 -1
- package/dist/defineElement-40372b4b.d.ts +0 -9
- package/dist/defineElement-40372b4b.d.ts.map +0 -1
- package/dist/defineElement-40372b4b.js +0 -15
- package/dist/icon-03e86700.d.ts +0 -11
- package/dist/icon-03e86700.d.ts.map +0 -1
- package/src/lib/defineElement.js +0 -13
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import { html,
|
|
1
|
+
import { html, nothing } from "lit"
|
|
2
2
|
import { classMap } from "lit/directives/class-map.js"
|
|
3
3
|
import { ifDefined } from "lit/directives/if-defined.js"
|
|
4
4
|
import { live } from "lit/directives/live.js"
|
|
5
5
|
import { createRef, ref } from "lit/directives/ref.js"
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { LeuElement } from "../../lib/LeuElement.js"
|
|
8
|
+
import { LeuIcon } from "../icon/Icon.js"
|
|
8
9
|
|
|
10
|
+
// @ts-ignore
|
|
9
11
|
import styles from "./input.css"
|
|
10
12
|
|
|
11
|
-
export const
|
|
13
|
+
export const SIZES = Object.freeze({
|
|
12
14
|
SMALL: "small",
|
|
13
15
|
REGULAR: "regular",
|
|
14
|
-
}
|
|
16
|
+
})
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* TODO:
|
|
@@ -63,14 +65,18 @@ const VALIDATION_MESSAGES = {
|
|
|
63
65
|
*
|
|
64
66
|
* @tagname leu-input
|
|
65
67
|
*/
|
|
66
|
-
export class LeuInput extends
|
|
68
|
+
export class LeuInput extends LeuElement {
|
|
69
|
+
static dependencies = {
|
|
70
|
+
"leu-icon": LeuIcon,
|
|
71
|
+
}
|
|
72
|
+
|
|
67
73
|
static styles = styles
|
|
68
74
|
|
|
69
75
|
/**
|
|
70
76
|
* @internal
|
|
71
77
|
*/
|
|
72
78
|
static shadowRootOptions = {
|
|
73
|
-
...
|
|
79
|
+
...LeuElement.shadowRootOptions,
|
|
74
80
|
delegatesFocus: true,
|
|
75
81
|
}
|
|
76
82
|
|
|
@@ -100,7 +106,6 @@ export class LeuInput extends LitElement {
|
|
|
100
106
|
novalidate: { type: Boolean, reflect: true },
|
|
101
107
|
step: { type: String, reflect: true },
|
|
102
108
|
|
|
103
|
-
/** @type {ValidityState} */
|
|
104
109
|
_validity: { state: true },
|
|
105
110
|
}
|
|
106
111
|
|
|
@@ -119,20 +124,18 @@ export class LeuInput extends LitElement {
|
|
|
119
124
|
this.required = false
|
|
120
125
|
this.clearable = false
|
|
121
126
|
|
|
122
|
-
/** @type {
|
|
123
|
-
this.size =
|
|
127
|
+
/** @type {"small" | "regular"} */
|
|
128
|
+
this.size = SIZES.REGULAR
|
|
124
129
|
|
|
125
130
|
this.type = "text"
|
|
126
131
|
this._validity = null
|
|
127
132
|
this.validationMessages = {}
|
|
128
133
|
this.novalidate = false
|
|
134
|
+
this.value = ""
|
|
129
135
|
|
|
130
136
|
/** @internal */
|
|
131
137
|
this._identifier = ""
|
|
132
138
|
|
|
133
|
-
/** @internal */
|
|
134
|
-
this._clearIcon = Icon("clear")
|
|
135
|
-
|
|
136
139
|
/**
|
|
137
140
|
* @internal
|
|
138
141
|
* @type {import("lit/directives/ref.js").Ref<HTMLInputElement>}
|
|
@@ -140,6 +143,13 @@ export class LeuInput extends LitElement {
|
|
|
140
143
|
this._inputRef = createRef()
|
|
141
144
|
}
|
|
142
145
|
|
|
146
|
+
get valueAsNumber() {
|
|
147
|
+
if (this.value === "") {
|
|
148
|
+
return NaN
|
|
149
|
+
}
|
|
150
|
+
return Number(this.value)
|
|
151
|
+
}
|
|
152
|
+
|
|
143
153
|
/**
|
|
144
154
|
* Method for handling the click event of the wrapper element.
|
|
145
155
|
* Redirect every click on the wrapper to the input element.
|
|
@@ -306,10 +316,7 @@ export class LeuInput extends LitElement {
|
|
|
306
316
|
|
|
307
317
|
/**
|
|
308
318
|
* Creates an error list with an item for the given validity state.
|
|
309
|
-
* @
|
|
310
|
-
* @param {Object} validationMessages
|
|
311
|
-
* @param {String} idRef
|
|
312
|
-
* @returns
|
|
319
|
+
* @returns {import("lit").TemplateResult | nothing}
|
|
313
320
|
*/
|
|
314
321
|
renderErrorMessages() {
|
|
315
322
|
if (!this.isInvalid()) {
|
|
@@ -341,11 +348,13 @@ export class LeuInput extends LitElement {
|
|
|
341
348
|
* This can be either an icon, a clear button or an error indicator icon.
|
|
342
349
|
*
|
|
343
350
|
* @private
|
|
344
|
-
* @returns {TemplateResult}
|
|
351
|
+
* @returns {import("lit").TemplateResult | nothing}
|
|
345
352
|
*/
|
|
346
353
|
renderAfterContent() {
|
|
347
354
|
if (this.isInvalid()) {
|
|
348
|
-
return html`<div class="error-icon"
|
|
355
|
+
return html`<div class="error-icon">
|
|
356
|
+
<leu-icon name="caution"></leu-icon>
|
|
357
|
+
</div>`
|
|
349
358
|
}
|
|
350
359
|
|
|
351
360
|
if (this.clearable && this.value) {
|
|
@@ -355,12 +364,14 @@ export class LeuInput extends LitElement {
|
|
|
355
364
|
aria-label="Eingabefeld zurücksetzen"
|
|
356
365
|
?disabled=${this.disabled}
|
|
357
366
|
>
|
|
358
|
-
|
|
367
|
+
<leu-icon name="clear"></leu-icon>
|
|
359
368
|
</button>`
|
|
360
369
|
}
|
|
361
370
|
|
|
362
371
|
if (this.icon) {
|
|
363
|
-
return html`<div class="icon"
|
|
372
|
+
return html`<div class="icon">
|
|
373
|
+
<leu-icon name=${this.icon}></leu-icon>
|
|
374
|
+
</div>`
|
|
364
375
|
}
|
|
365
376
|
|
|
366
377
|
return nothing
|
|
@@ -154,7 +154,9 @@
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
/* is size small AND has no focus AND is empty */
|
|
157
|
-
:host(:not(:focus-within)[size="small"])
|
|
157
|
+
:host(:not(:focus-within)[size="small"])
|
|
158
|
+
.input-wrapper--empty:not(.input-wrapper--invalid)
|
|
159
|
+
.label {
|
|
158
160
|
top: calc(0.75rem - var(--input-border-width));
|
|
159
161
|
opacity: 1;
|
|
160
162
|
visibility: visible;
|
|
@@ -229,6 +231,6 @@
|
|
|
229
231
|
color: var(--input-color-invalid);
|
|
230
232
|
}
|
|
231
233
|
|
|
232
|
-
:is(.icon, .error-icon)
|
|
234
|
+
:is(.icon, .error-icon) leu-icon {
|
|
233
235
|
display: block;
|
|
234
236
|
}
|
|
@@ -3,8 +3,8 @@ import { ifDefined } from "lit/directives/if-defined.js"
|
|
|
3
3
|
|
|
4
4
|
import "../leu-input.js"
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { SIZES } from "../Input.js"
|
|
7
|
+
import { paths as iconPaths } from "../../icon/paths.js"
|
|
8
8
|
|
|
9
9
|
export default {
|
|
10
10
|
title: "Input",
|
|
@@ -15,9 +15,9 @@ export default {
|
|
|
15
15
|
control: {
|
|
16
16
|
type: "select",
|
|
17
17
|
},
|
|
18
|
-
options: Object.values(
|
|
18
|
+
options: Object.values(SIZES),
|
|
19
19
|
},
|
|
20
|
-
icon: { control: "select", options:
|
|
20
|
+
icon: { control: "select", options: Object.keys(iconPaths) },
|
|
21
21
|
},
|
|
22
22
|
parameters: {
|
|
23
23
|
design: {
|
|
@@ -165,7 +165,7 @@ export const Search = Template.bind({})
|
|
|
165
165
|
Search.args = {
|
|
166
166
|
label: "Suchen",
|
|
167
167
|
clearable: true,
|
|
168
|
-
size:
|
|
168
|
+
size: SIZES.SMALL,
|
|
169
169
|
icon: "search",
|
|
170
170
|
novalidate: true,
|
|
171
171
|
}
|
|
@@ -29,6 +29,8 @@ async function defaultFixture(args = {}) {
|
|
|
29
29
|
?novalidate=${args.novalidate}
|
|
30
30
|
>
|
|
31
31
|
</leu-input>
|
|
32
|
+
<!-- Firefox needs an other focusable element. Otherwise, sendKeys({press: "Tab"}) will have no effect -->
|
|
33
|
+
<div tabindex="0"></div>
|
|
32
34
|
`)
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -447,4 +449,24 @@ describe("LeuInput", () => {
|
|
|
447
449
|
expect(getClearButton()).to.be.null
|
|
448
450
|
expect(getIcon()).to.be.null
|
|
449
451
|
})
|
|
452
|
+
|
|
453
|
+
it("returns the value as a number when it is possible", async () => {
|
|
454
|
+
const el = await defaultFixture({ label: "Länge", type: "number" })
|
|
455
|
+
|
|
456
|
+
expect(el.valueAsNumber).to.be.NaN
|
|
457
|
+
|
|
458
|
+
el.focus()
|
|
459
|
+
|
|
460
|
+
await sendKeys({ type: "123" })
|
|
461
|
+
|
|
462
|
+
expect(el.valueAsNumber).to.equal(123)
|
|
463
|
+
|
|
464
|
+
el.type = "text"
|
|
465
|
+
await elementUpdated(el)
|
|
466
|
+
|
|
467
|
+
el.focus()
|
|
468
|
+
await sendKeys({ type: "abc" })
|
|
469
|
+
|
|
470
|
+
expect(el.valueAsNumber).to.be.NaN
|
|
471
|
+
})
|
|
450
472
|
})
|
|
@@ -1,12 +1,153 @@
|
|
|
1
|
-
import { html
|
|
1
|
+
import { html } from "lit"
|
|
2
|
+
|
|
3
|
+
import { LeuElement } from "../../lib/LeuElement.js"
|
|
4
|
+
|
|
5
|
+
import { LeuMenuItem } from "./MenuItem.js"
|
|
6
|
+
|
|
7
|
+
// @ts-ignore
|
|
2
8
|
import styles from "./menu.css"
|
|
3
9
|
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {'single' | 'multiple' | 'none'} SelectsType
|
|
12
|
+
*/
|
|
13
|
+
|
|
4
14
|
/**
|
|
5
15
|
* @tagname leu-menu
|
|
16
|
+
* @property {SelectsType} selects - This has only an effect when the role is 'menu'. It defines which role the menu items will get. Default is 'none'.
|
|
6
17
|
*/
|
|
7
|
-
export class LeuMenu extends
|
|
18
|
+
export class LeuMenu extends LeuElement {
|
|
8
19
|
static styles = styles
|
|
9
20
|
|
|
21
|
+
static shadowRootOptions = {
|
|
22
|
+
...LeuElement.shadowRootOptions,
|
|
23
|
+
delegatesFocus: true,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static properties = {
|
|
27
|
+
selects: { type: String, reflect: true },
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
constructor() {
|
|
31
|
+
super()
|
|
32
|
+
|
|
33
|
+
/** @type {SelectsType} */
|
|
34
|
+
this.selects = "none"
|
|
35
|
+
|
|
36
|
+
this.value = undefined
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
connectedCallback() {
|
|
40
|
+
super.connectedCallback()
|
|
41
|
+
|
|
42
|
+
if (!this.getAttribute("role")) {
|
|
43
|
+
this.setAttribute("role", "menu")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
this.addEventListener("keydown", this._handleKeyDown)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
disconnectedCallback() {
|
|
50
|
+
super.disconnectedCallback()
|
|
51
|
+
this.removeEventListener("keydown", this._handleKeyDown)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
_handleSlotChange() {
|
|
55
|
+
this.setCurrentItem(0)
|
|
56
|
+
this._setMenuItemRoles()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
_setMenuItemRoles() {
|
|
60
|
+
const menuRole = this.getAttribute("role")
|
|
61
|
+
let menuItemRole
|
|
62
|
+
|
|
63
|
+
if (menuRole === "menu") {
|
|
64
|
+
if (this.selects === "multiple") {
|
|
65
|
+
menuItemRole = "menuitemcheckbox"
|
|
66
|
+
} else if (this.selects === "single") {
|
|
67
|
+
menuItemRole = "menuitemradio"
|
|
68
|
+
} else {
|
|
69
|
+
menuItemRole = "menuitem"
|
|
70
|
+
}
|
|
71
|
+
} else if (menuRole === "listbox") {
|
|
72
|
+
menuItemRole = "option"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (menuItemRole) {
|
|
76
|
+
this.getMenuItems().forEach((menuItem) => {
|
|
77
|
+
menuItem.componentRole = menuItemRole // eslint-disable-line no-param-reassign
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
*
|
|
84
|
+
* @returns {import("./MenuItem").LeuMenuItem[]}
|
|
85
|
+
*/
|
|
86
|
+
getMenuItems() {
|
|
87
|
+
const slot = this.shadowRoot.querySelector("slot")
|
|
88
|
+
return slot
|
|
89
|
+
.assignedElements({ flatten: true })
|
|
90
|
+
.filter((el) => el instanceof LeuMenuItem)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getVisibleMenuItems() {
|
|
94
|
+
return this.getMenuItems().filter((menuItem) => !menuItem.hidden)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
_handleKeyDown(event) {
|
|
98
|
+
if (["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
|
|
99
|
+
event.preventDefault()
|
|
100
|
+
|
|
101
|
+
const menuItems = this.getVisibleMenuItems()
|
|
102
|
+
let index = menuItems.findIndex((menuItem) => menuItem.tabbable)
|
|
103
|
+
|
|
104
|
+
if (event.key === "ArrowDown") {
|
|
105
|
+
index += 1
|
|
106
|
+
} else if (event.key === "ArrowUp") {
|
|
107
|
+
index -= 1
|
|
108
|
+
} else if (event.key === "Home") {
|
|
109
|
+
index = 0
|
|
110
|
+
} else if (event.key === "End") {
|
|
111
|
+
index = menuItems.length - 1
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this.focusItem(index)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
setCurrentItem(index) {
|
|
119
|
+
const menuItems = this.getVisibleMenuItems()
|
|
120
|
+
let currentItem = null
|
|
121
|
+
|
|
122
|
+
const currentItemIndex = (index + menuItems.length) % menuItems.length
|
|
123
|
+
|
|
124
|
+
menuItems.forEach((menuItem, i) => {
|
|
125
|
+
if (i === currentItemIndex) {
|
|
126
|
+
currentItem = menuItem
|
|
127
|
+
menuItem.tabbable = true // eslint-disable-line no-param-reassign
|
|
128
|
+
} else {
|
|
129
|
+
menuItem.tabbable = false // eslint-disable-line no-param-reassign
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
return currentItem
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
focusItem(index) {
|
|
137
|
+
const currentItem = this.setCurrentItem(index)
|
|
138
|
+
currentItem.focus()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
firstUpdated() {
|
|
142
|
+
this.setCurrentItem(0)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
updated(changedProperties) {
|
|
146
|
+
if (changedProperties.has("selects")) {
|
|
147
|
+
this._setMenuItemRoles()
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
10
151
|
render() {
|
|
11
152
|
return html`<slot></slot>`
|
|
12
153
|
}
|
|
@@ -1,44 +1,48 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { html, unsafeStatic } from "lit/static-html.js"
|
|
1
|
+
import { html } from "lit"
|
|
3
2
|
import { ifDefined } from "lit/directives/if-defined.js"
|
|
4
3
|
|
|
4
|
+
import { LeuElement } from "../../lib/LeuElement.js"
|
|
5
|
+
import { LeuIcon } from "../icon/Icon.js"
|
|
6
|
+
|
|
7
|
+
// @ts-ignore
|
|
5
8
|
import styles from "./menu-item.css"
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {'menuitem' | 'menuitemcheckbox' | 'menuitemradio' | 'option' | 'none'} MenuItemRole
|
|
12
|
+
*/
|
|
8
13
|
|
|
9
14
|
/**
|
|
10
15
|
* @tagname leu-menu-item
|
|
11
16
|
* @slot - The label of the menu item
|
|
17
|
+
* @property {boolean} active - Defines if the item is selected or checked
|
|
18
|
+
* @property {boolean} disabled - Disables the underlying button or link
|
|
19
|
+
* @property {string} value - The value of the item. It must not contain commas. See `getValue()`
|
|
20
|
+
* @property {string} href - The href of the underlying link
|
|
21
|
+
* @property {boolean} tabbable - If the item should be focusable. Will be reflected as `tabindex` to the underlying button or link
|
|
22
|
+
* @property {MenuItemRole} componentRole - The role of the item. This will be reflected as `role` to the underlying button or link. Default is `'menuitem'.`
|
|
12
23
|
*/
|
|
13
|
-
export class LeuMenuItem extends
|
|
24
|
+
export class LeuMenuItem extends LeuElement {
|
|
25
|
+
static dependencies = {
|
|
26
|
+
"leu-icon": LeuIcon,
|
|
27
|
+
}
|
|
28
|
+
|
|
14
29
|
static styles = styles
|
|
15
30
|
|
|
16
31
|
/**
|
|
17
32
|
* @internal
|
|
18
33
|
*/
|
|
19
34
|
static shadowRootOptions = {
|
|
20
|
-
...
|
|
35
|
+
...LeuElement.shadowRootOptions,
|
|
21
36
|
delegatesFocus: true,
|
|
22
37
|
}
|
|
23
38
|
|
|
24
39
|
static properties = {
|
|
25
|
-
/**
|
|
26
|
-
* Can be either an icon name or a text
|
|
27
|
-
* If no icon with this value is found, it will be displayed as text.
|
|
28
|
-
* If the value is "EMPTY", an empty placeholder with the size of an icon will be displayed.
|
|
29
|
-
*/
|
|
30
|
-
before: { type: String, reflect: true },
|
|
31
|
-
/**
|
|
32
|
-
* Can be either an icon name or a text
|
|
33
|
-
* If no icon with this value is found, it will be displayed as text
|
|
34
|
-
* If the value is "EMPTY", an empty placeholder with the size of an icon will be displayed.
|
|
35
|
-
*/
|
|
36
|
-
after: { type: String, reflect: true },
|
|
37
40
|
active: { type: Boolean, reflect: true },
|
|
38
|
-
highlighted: { type: Boolean, reflect: true },
|
|
39
41
|
disabled: { type: Boolean, reflect: true },
|
|
40
|
-
|
|
42
|
+
tabbable: { type: Boolean, reflect: true },
|
|
41
43
|
href: { type: String, reflect: true },
|
|
44
|
+
value: { type: String, reflect: true },
|
|
45
|
+
componentRole: { type: String, reflect: true },
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
constructor() {
|
|
@@ -46,57 +50,105 @@ export class LeuMenuItem extends LitElement {
|
|
|
46
50
|
|
|
47
51
|
this.active = false
|
|
48
52
|
this.disabled = false
|
|
53
|
+
this.value = undefined
|
|
54
|
+
this.href = undefined
|
|
55
|
+
this.tabbable = undefined
|
|
49
56
|
|
|
50
|
-
/**
|
|
51
|
-
|
|
52
|
-
* This is just a visual effect and does not change the active state.
|
|
53
|
-
*/
|
|
54
|
-
this.highlighted = false
|
|
57
|
+
/** @type {MenuItemRole} */
|
|
58
|
+
this.componentRole = "menuitem"
|
|
55
59
|
}
|
|
56
60
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
connectedCallback() {
|
|
62
|
+
super.connectedCallback()
|
|
63
|
+
this.addEventListener("click", this._handleClick, true)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
disconnectedCallback() {
|
|
67
|
+
super.disconnectedCallback()
|
|
68
|
+
this.removeEventListener("click", this._handleClick, true)
|
|
69
|
+
}
|
|
61
70
|
|
|
62
|
-
|
|
63
|
-
|
|
71
|
+
_handleClick(event) {
|
|
72
|
+
if (this.disabled) {
|
|
73
|
+
event.stopPropagation()
|
|
74
|
+
event.preventDefault()
|
|
64
75
|
}
|
|
76
|
+
}
|
|
65
77
|
|
|
66
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Returns the value of the item. If `value` is not set, it will return the inner text
|
|
80
|
+
* @returns {string}
|
|
81
|
+
*/
|
|
82
|
+
getValue() {
|
|
83
|
+
return this.value || this.innerText
|
|
67
84
|
}
|
|
68
85
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
86
|
+
_getAria() {
|
|
87
|
+
const commonAttributes = {
|
|
88
|
+
disabled: this.disabled,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (this.href) {
|
|
92
|
+
return commonAttributes
|
|
73
93
|
}
|
|
74
94
|
|
|
75
|
-
return
|
|
95
|
+
return {
|
|
96
|
+
...commonAttributes,
|
|
97
|
+
checked:
|
|
98
|
+
this.componentRole === "menuitemcheckbox" ||
|
|
99
|
+
this.componentRole === "menuitemradio"
|
|
100
|
+
? this.active
|
|
101
|
+
: undefined,
|
|
102
|
+
selected: this.componentRole === "option" ? this.active : undefined,
|
|
103
|
+
role: this.componentRole === "none" ? undefined : this.componentRole,
|
|
104
|
+
}
|
|
76
105
|
}
|
|
77
106
|
|
|
78
|
-
|
|
79
|
-
if (this.
|
|
80
|
-
|
|
81
|
-
return html`<span class="after">${content}</span>`
|
|
107
|
+
_getTabIndex() {
|
|
108
|
+
if (typeof this.tabbable === "boolean") {
|
|
109
|
+
return this.tabbable ? 0 : -1
|
|
82
110
|
}
|
|
83
111
|
|
|
84
|
-
return
|
|
112
|
+
return undefined
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
_renderLink(content) {
|
|
116
|
+
const aria = this._getAria()
|
|
117
|
+
|
|
118
|
+
return html`<a
|
|
119
|
+
class="button"
|
|
120
|
+
href=${this.href}
|
|
121
|
+
aria-disabled=${ifDefined(aria.disabled)}
|
|
122
|
+
aria-checked=${ifDefined(aria.checked)}
|
|
123
|
+
aria-selected=${ifDefined(aria.selected)}
|
|
124
|
+
role=${ifDefined(aria.role)}
|
|
125
|
+
tabindex=${ifDefined(this._getTabIndex())}
|
|
126
|
+
>${content}</a
|
|
127
|
+
>`
|
|
85
128
|
}
|
|
86
129
|
|
|
87
|
-
|
|
88
|
-
|
|
130
|
+
_renderButton(content) {
|
|
131
|
+
const aria = this._getAria()
|
|
132
|
+
|
|
133
|
+
return html`<button
|
|
134
|
+
class="button"
|
|
135
|
+
aria-disabled=${ifDefined(aria.disabled)}
|
|
136
|
+
aria-checked=${ifDefined(aria.checked)}
|
|
137
|
+
aria-selected=${ifDefined(aria.selected)}
|
|
138
|
+
role=${ifDefined(aria.role)}
|
|
139
|
+
tabindex=${ifDefined(this._getTabIndex())}
|
|
140
|
+
>
|
|
141
|
+
${content}
|
|
142
|
+
</button>`
|
|
89
143
|
}
|
|
90
144
|
|
|
91
145
|
render() {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
</${unsafeStatic(this.getTagName())}>`
|
|
100
|
-
/* eslint-enable lit/binding-positions, lit/no-invalid-html */
|
|
146
|
+
const content = html`
|
|
147
|
+
<slot class="before" name="before"></slot>
|
|
148
|
+
<span class="label"><slot></slot></span>
|
|
149
|
+
<slot class="after" name="after"></slot>
|
|
150
|
+
`
|
|
151
|
+
|
|
152
|
+
return this.href ? this._renderLink(content) : this._renderButton(content)
|
|
101
153
|
}
|
|
102
154
|
}
|
|
@@ -6,9 +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-
|
|
9
|
+
--background-active: var(--leu-color-accent-blue);
|
|
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
13
|
--color-disabled: var(--leu-color-black-transp-20);
|
|
13
14
|
--font-regular: var(--leu-font-family-regular);
|
|
14
15
|
--font-black: var(--leu-font-family-black);
|
|
@@ -29,6 +30,7 @@
|
|
|
29
30
|
|
|
30
31
|
padding: 0.75rem;
|
|
31
32
|
|
|
33
|
+
font-family: inherit;
|
|
32
34
|
font-size: 1rem;
|
|
33
35
|
line-height: 1.5;
|
|
34
36
|
text-align: left;
|
|
@@ -43,21 +45,26 @@
|
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
.button:hover,
|
|
46
|
-
|
|
48
|
+
.button:focus-visible {
|
|
47
49
|
--background: var(--background-hover);
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
/*
|
|
53
|
+
* These colors do not match with the design system (yet).
|
|
54
|
+
* But at least they are compliant with the WCAG AA contrast ratio.
|
|
55
|
+
*/
|
|
50
56
|
:host([active]) .button {
|
|
51
57
|
--background: var(--background-active);
|
|
58
|
+
--color: var(--color-active);
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
:host([disabled]) .button {
|
|
55
62
|
--background: var(--background-disabled);
|
|
56
63
|
--color: var(--color-disabled);
|
|
57
|
-
cursor:
|
|
64
|
+
cursor: not-allowed;
|
|
58
65
|
}
|
|
59
66
|
|
|
60
|
-
:is(.before, .after)
|
|
67
|
+
:is(.before, .after) leu-icon {
|
|
61
68
|
display: block;
|
|
62
69
|
}
|
|
63
70
|
|
|
@@ -2,6 +2,12 @@ import { html } from "lit"
|
|
|
2
2
|
import { ifDefined } from "lit/directives/if-defined.js"
|
|
3
3
|
|
|
4
4
|
import "../leu-menu-item.js"
|
|
5
|
+
import "../../icon/leu-icon.js"
|
|
6
|
+
import { paths as iconPaths } from "../../icon/paths.js"
|
|
7
|
+
|
|
8
|
+
function isIcon(name) {
|
|
9
|
+
return name === "EMPTY" || Object.keys(iconPaths).includes(name)
|
|
10
|
+
}
|
|
5
11
|
|
|
6
12
|
export default {
|
|
7
13
|
title: "Menu/Item",
|
|
@@ -20,13 +26,18 @@ export default {
|
|
|
20
26
|
function Template(args) {
|
|
21
27
|
return html`
|
|
22
28
|
<leu-menu-item
|
|
23
|
-
label=${args.label}
|
|
24
|
-
before=${ifDefined(args.before)}
|
|
25
|
-
after=${ifDefined(args.after)}
|
|
26
29
|
href=${ifDefined(args.href)}
|
|
27
30
|
?active=${args.active}
|
|
28
31
|
?disabled=${args.disabled}
|
|
29
|
-
|
|
32
|
+
>
|
|
33
|
+
${isIcon(args.before)
|
|
34
|
+
? html`<leu-icon slot="before" name=${args.before}></leu-icon>`
|
|
35
|
+
: html`<span slot="before">${args.before}</span>`}
|
|
36
|
+
${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
|
+
</leu-menu-item>
|
|
30
41
|
`
|
|
31
42
|
}
|
|
32
43
|
|