@statistikzh/leu 0.18.0 → 0.19.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/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +15 -0
- package/dist/Accordion.js +1 -1
- package/dist/Button.js +1 -1
- package/dist/ButtonGroup.js +1 -1
- package/dist/ChartWrapper.js +1 -1
- package/dist/Checkbox.js +1 -1
- package/dist/CheckboxGroup.js +1 -1
- package/dist/Chip.js +1 -1
- package/dist/ChipGroup.js +1 -1
- package/dist/ChipLink.js +1 -1
- package/dist/ChipRemovable.js +1 -1
- package/dist/ChipSelectable.js +1 -1
- package/dist/Dialog.js +1 -1
- package/dist/Dropdown.js +1 -1
- package/dist/FileInput.d.ts +59 -0
- package/dist/FileInput.js +383 -0
- package/dist/Icon.js +1 -1
- package/dist/Input.d.ts +0 -9
- package/dist/Input.js +4 -25
- package/dist/{LeuElement-DlQXnrk8.js → LeuElement-CKq5epyY.js} +1 -1
- package/dist/Menu.js +1 -1
- package/dist/MenuItem.js +1 -1
- package/dist/Message.js +1 -1
- package/dist/Pagination.js +1 -1
- package/dist/Placeholder.js +1 -1
- package/dist/Popup.js +1 -1
- package/dist/ProgressBar.d.ts +29 -0
- package/dist/ProgressBar.js +166 -0
- package/dist/Radio.js +1 -1
- package/dist/RadioGroup.js +1 -1
- package/dist/Range.js +1 -1
- package/dist/ScrollTop.js +1 -1
- package/dist/Select.js +1 -1
- package/dist/Spinner.js +1 -1
- package/dist/Table.js +1 -1
- package/dist/Tag.js +1 -1
- package/dist/VisuallyHidden.js +1 -1
- package/dist/components/file-input/FileInput.d.ts +54 -0
- package/dist/components/file-input/FileInput.d.ts.map +1 -0
- package/dist/components/file-input/leu-file-input.d.ts +3 -0
- package/dist/components/file-input/leu-file-input.d.ts.map +1 -0
- package/dist/components/file-input/stories/file-input.stories.d.ts +30 -0
- package/dist/components/file-input/stories/file-input.stories.d.ts.map +1 -0
- package/dist/components/file-input/test/file-input.test.d.ts +2 -0
- package/dist/components/file-input/test/file-input.test.d.ts.map +1 -0
- package/dist/components/input/Input.d.ts +0 -9
- package/dist/components/input/Input.d.ts.map +1 -1
- package/dist/components/progress-bar/ProgressBar.d.ts +25 -0
- package/dist/components/progress-bar/ProgressBar.d.ts.map +1 -0
- package/dist/components/progress-bar/leu-progress-bar.d.ts +3 -0
- package/dist/components/progress-bar/leu-progress-bar.d.ts.map +1 -0
- package/dist/components/progress-bar/stories/progress-bar.stories.d.ts +48 -0
- package/dist/components/progress-bar/stories/progress-bar.stories.d.ts.map +1 -0
- package/dist/components/progress-bar/test/progress-bar.test.d.ts +2 -0
- package/dist/components/progress-bar/test/progress-bar.test.d.ts.map +1 -0
- package/dist/index.js +1 -1
- package/dist/leu-accordion.js +1 -1
- package/dist/leu-button-group.js +1 -1
- package/dist/leu-button.js +1 -1
- package/dist/leu-chart-wrapper.js +1 -1
- package/dist/leu-checkbox-group.js +1 -1
- package/dist/leu-checkbox.js +1 -1
- package/dist/leu-chip-group.js +1 -1
- package/dist/leu-chip-link.js +1 -1
- package/dist/leu-chip-removable.js +1 -1
- package/dist/leu-chip-selectable.js +1 -1
- package/dist/leu-dialog.js +1 -1
- package/dist/leu-dropdown.js +1 -1
- package/dist/leu-file-input.d.ts +8 -0
- package/dist/leu-file-input.js +42 -0
- package/dist/leu-icon.js +1 -1
- package/dist/leu-input.js +1 -1
- package/dist/leu-menu-item.js +1 -1
- package/dist/leu-menu.js +1 -1
- package/dist/leu-message.js +1 -1
- package/dist/leu-pagination.js +1 -1
- package/dist/leu-placeholder.js +1 -1
- package/dist/leu-popup.js +1 -1
- package/dist/leu-progress-bar.d.ts +4 -0
- package/dist/leu-progress-bar.js +10 -0
- package/dist/leu-radio-group.js +1 -1
- package/dist/leu-radio.js +1 -1
- package/dist/leu-range.js +1 -1
- package/dist/leu-scroll-top.js +1 -1
- package/dist/leu-select.js +1 -1
- package/dist/leu-spinner.js +1 -1
- package/dist/leu-table.js +1 -1
- package/dist/leu-tag.js +1 -1
- package/dist/leu-visually-hidden.js +1 -1
- package/dist/vscode.html-custom-data.json +65 -0
- package/dist/vue/index.d.ts +48 -0
- package/dist/web-types.json +119 -1
- package/package.json +1 -1
- package/scripts/generate-component/templates/test/[name].test.ts +4 -3
- package/src/components/file-input/FileInput.ts +266 -0
- package/src/components/file-input/file-input.css +118 -0
- package/src/components/file-input/leu-file-input.ts +5 -0
- package/src/components/file-input/stories/file-input.stories.ts +38 -0
- package/src/components/file-input/test/file-input.test.ts +25 -0
- package/src/components/input/Input.ts +3 -29
- package/src/components/progress-bar/ProgressBar.ts +52 -0
- package/src/components/progress-bar/leu-progress-bar.ts +5 -0
- package/src/components/progress-bar/progress-bar.css +97 -0
- package/src/components/progress-bar/stories/progress-bar.stories.ts +39 -0
- package/src/components/progress-bar/test/progress-bar.test.ts +61 -0
package/dist/web-types.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json",
|
|
3
3
|
"name": "@statistikzh/leu",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.19.0",
|
|
5
5
|
"description-markup": "markdown",
|
|
6
6
|
"contributions": {
|
|
7
7
|
"html": {
|
|
@@ -595,6 +595,81 @@
|
|
|
595
595
|
"events": []
|
|
596
596
|
}
|
|
597
597
|
},
|
|
598
|
+
{
|
|
599
|
+
"name": "leu-file-input",
|
|
600
|
+
"description": "\n---\n",
|
|
601
|
+
"doc-url": "",
|
|
602
|
+
"attributes": [
|
|
603
|
+
{
|
|
604
|
+
"name": "label",
|
|
605
|
+
"value": { "type": "string", "default": "\"\"" }
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
"name": "accept",
|
|
609
|
+
"description": "A list of acceptable file types. Must be a comma-separated list of [unique file type specifiers](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers).",
|
|
610
|
+
"value": { "type": "string", "default": "\"\"" }
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
"name": "disabled",
|
|
614
|
+
"description": "Whether the file input is disabled.",
|
|
615
|
+
"value": { "type": "boolean", "default": "false" }
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
"name": "multiple",
|
|
619
|
+
"description": "Whether the file input allows multiple files.",
|
|
620
|
+
"value": { "type": "boolean", "default": "false" }
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
"name": "required",
|
|
624
|
+
"description": "Whether the file input is required.",
|
|
625
|
+
"value": { "type": "boolean", "default": "false" }
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
"name": "variant",
|
|
629
|
+
"description": "The variant of the file list item. `filled` renders a gray background.",
|
|
630
|
+
"value": {
|
|
631
|
+
"type": "\"filled\" | \"transparent\"",
|
|
632
|
+
"default": "\"filled\""
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
],
|
|
636
|
+
"events": [],
|
|
637
|
+
"js": {
|
|
638
|
+
"properties": [
|
|
639
|
+
{ "name": "label", "type": "string" },
|
|
640
|
+
{
|
|
641
|
+
"name": "accept",
|
|
642
|
+
"description": "A list of acceptable file types. Must be a comma-separated list of [unique file type specifiers](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers).",
|
|
643
|
+
"type": "string"
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
"name": "disabled",
|
|
647
|
+
"description": "Whether the file input is disabled.",
|
|
648
|
+
"type": "boolean"
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
"name": "multiple",
|
|
652
|
+
"description": "Whether the file input allows multiple files.",
|
|
653
|
+
"type": "boolean"
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
"name": "required",
|
|
657
|
+
"description": "Whether the file input is required.",
|
|
658
|
+
"type": "boolean"
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
"name": "variant",
|
|
662
|
+
"description": "The variant of the file list item. `filled` renders a gray background.",
|
|
663
|
+
"type": "\"filled\" | \"transparent\""
|
|
664
|
+
},
|
|
665
|
+
{ "name": "files", "type": "File[]" },
|
|
666
|
+
{ "name": "input", "type": "HTMLInputElement" },
|
|
667
|
+
{ "name": "form" },
|
|
668
|
+
{ "name": "name" }
|
|
669
|
+
],
|
|
670
|
+
"events": []
|
|
671
|
+
}
|
|
672
|
+
},
|
|
598
673
|
{
|
|
599
674
|
"name": "leu-icon",
|
|
600
675
|
"description": "A component to render all defined zhWeb icons.\nThe `fill` of the icon is set to `currentColor` and\ncan be overriden by setting the css `color` property.\nIf the icon name is not found, a placeholder will be displayed.\n---\n\n\n### **CSS Properties:**\n - **--leu-icon-size** - The size of the icon. _(default: undefined)_",
|
|
@@ -1199,6 +1274,49 @@
|
|
|
1199
1274
|
"events": []
|
|
1200
1275
|
}
|
|
1201
1276
|
},
|
|
1277
|
+
{
|
|
1278
|
+
"name": "leu-progress-bar",
|
|
1279
|
+
"description": "An indicator showing the completion progress of a task\n---\n",
|
|
1280
|
+
"doc-url": "",
|
|
1281
|
+
"attributes": [
|
|
1282
|
+
{
|
|
1283
|
+
"name": "value",
|
|
1284
|
+
"description": "Progress as a percentage from 0 to 100",
|
|
1285
|
+
"value": { "type": "number", "default": "0" }
|
|
1286
|
+
},
|
|
1287
|
+
{
|
|
1288
|
+
"name": "label",
|
|
1289
|
+
"description": "Label that is displayed below the progress bar",
|
|
1290
|
+
"value": { "type": "string", "default": "\"\"" }
|
|
1291
|
+
},
|
|
1292
|
+
{
|
|
1293
|
+
"name": "indeterminate",
|
|
1294
|
+
"description": "Whether the progress bar is in indeterminate state.",
|
|
1295
|
+
"value": { "type": "boolean", "default": "false" }
|
|
1296
|
+
}
|
|
1297
|
+
],
|
|
1298
|
+
"events": [],
|
|
1299
|
+
"js": {
|
|
1300
|
+
"properties": [
|
|
1301
|
+
{
|
|
1302
|
+
"name": "value",
|
|
1303
|
+
"description": "Progress as a percentage from 0 to 100",
|
|
1304
|
+
"type": "number"
|
|
1305
|
+
},
|
|
1306
|
+
{
|
|
1307
|
+
"name": "label",
|
|
1308
|
+
"description": "Label that is displayed below the progress bar",
|
|
1309
|
+
"type": "string"
|
|
1310
|
+
},
|
|
1311
|
+
{
|
|
1312
|
+
"name": "indeterminate",
|
|
1313
|
+
"description": "Whether the progress bar is in indeterminate state.",
|
|
1314
|
+
"type": "boolean"
|
|
1315
|
+
}
|
|
1316
|
+
],
|
|
1317
|
+
"events": []
|
|
1318
|
+
}
|
|
1319
|
+
},
|
|
1202
1320
|
{
|
|
1203
1321
|
"name": "leu-radio",
|
|
1204
1322
|
"description": "\n---\n",
|
package/package.json
CHANGED
|
@@ -2,16 +2,17 @@ import { html } from "lit"
|
|
|
2
2
|
import { fixture, expect } from "@open-wc/testing"
|
|
3
3
|
|
|
4
4
|
import "../[namespace]-[name].js"
|
|
5
|
+
import { type [Namespace][Name] } from "../[Name].js"
|
|
5
6
|
|
|
6
7
|
async function defaultFixture() {
|
|
7
|
-
return fixture(html`<[namespace]-[name]></[namespace]-[name]>`)
|
|
8
|
+
return fixture<[Namespace][Name]>(html`<[namespace]-[name]></[namespace]-[name]>`)
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
describe("[Namespace][Name]", () => {
|
|
11
12
|
it("is a defined element", async () => {
|
|
12
|
-
const el =
|
|
13
|
+
const el = customElements.get("[namespace]-[name]")
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
expect(el).not.to.be.undefined
|
|
15
16
|
})
|
|
16
17
|
|
|
17
18
|
it("passes the a11y audit", async () => {
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { html, nothing } from "lit"
|
|
2
|
+
import { property, query, state } from "lit/decorators.js"
|
|
3
|
+
import { ifDefined } from "lit/directives/if-defined.js"
|
|
4
|
+
import { classMap } from "lit/directives/class-map.js"
|
|
5
|
+
|
|
6
|
+
import { LeuElement } from "../../lib/LeuElement.js"
|
|
7
|
+
|
|
8
|
+
import styles from "./file-input.css"
|
|
9
|
+
import { LeuButton } from "../../index.js"
|
|
10
|
+
import { LeuIcon } from "../icon/leu-icon.js"
|
|
11
|
+
import { LeuVisuallyHidden } from "../visually-hidden/VisuallyHidden.js"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @tagname leu-file-input
|
|
15
|
+
*/
|
|
16
|
+
export class LeuFileInput extends LeuElement {
|
|
17
|
+
static dependencies = {
|
|
18
|
+
"leu-icon": LeuIcon,
|
|
19
|
+
"leu-button": LeuButton,
|
|
20
|
+
"leu-visually-hidden": LeuVisuallyHidden,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static styles = [LeuElement.styles, styles]
|
|
24
|
+
|
|
25
|
+
static shadowRootOptions = {
|
|
26
|
+
...LeuElement.shadowRootOptions,
|
|
27
|
+
delegatesFocus: true,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static formAssociated = true
|
|
31
|
+
|
|
32
|
+
protected internals: ElementInternals
|
|
33
|
+
|
|
34
|
+
@property({ type: String })
|
|
35
|
+
label: string = ""
|
|
36
|
+
|
|
37
|
+
/** A list of acceptable file types. Must be a comma-separated list of [unique file type specifiers](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers). */
|
|
38
|
+
@property({ type: String })
|
|
39
|
+
accept: string = ""
|
|
40
|
+
|
|
41
|
+
/** Whether the file input is disabled. */
|
|
42
|
+
@property({ type: Boolean, reflect: true })
|
|
43
|
+
disabled: boolean = false
|
|
44
|
+
|
|
45
|
+
/** Whether the file input allows multiple files. */
|
|
46
|
+
@property({ type: Boolean, reflect: true })
|
|
47
|
+
multiple: boolean = false
|
|
48
|
+
|
|
49
|
+
/** Whether the file input is required. */
|
|
50
|
+
@property({ type: Boolean, reflect: true })
|
|
51
|
+
required: boolean = false
|
|
52
|
+
|
|
53
|
+
/** The variant of the file list item. `filled` renders a gray background. */
|
|
54
|
+
@property({ type: String, reflect: true })
|
|
55
|
+
variant: "filled" | "transparent" = "filled"
|
|
56
|
+
|
|
57
|
+
@state()
|
|
58
|
+
public files: File[] = []
|
|
59
|
+
|
|
60
|
+
@state()
|
|
61
|
+
private isDragging: boolean = false
|
|
62
|
+
|
|
63
|
+
@query('input[type="file"]')
|
|
64
|
+
input: HTMLInputElement
|
|
65
|
+
|
|
66
|
+
constructor() {
|
|
67
|
+
super()
|
|
68
|
+
// Initialize the ElementInternals for form association
|
|
69
|
+
this.internals = this.attachInternals()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get form() {
|
|
73
|
+
return this.internals.form
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get name() {
|
|
77
|
+
return this.getAttribute("name")
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
updated(changedProperties) {
|
|
81
|
+
if (
|
|
82
|
+
changedProperties.has("files") ||
|
|
83
|
+
changedProperties.has("disabled") ||
|
|
84
|
+
changedProperties.has("multiple")
|
|
85
|
+
) {
|
|
86
|
+
this.updateFormValue()
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
protected handleInput() {
|
|
91
|
+
// Append selected files
|
|
92
|
+
if (this.input.files) {
|
|
93
|
+
const acceptableFiles = [...this.input.files].filter((file) =>
|
|
94
|
+
this.isAcceptedFile(file),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
this.files = this.multiple
|
|
98
|
+
? this.files.concat(acceptableFiles)
|
|
99
|
+
: acceptableFiles.slice(0, 1)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
protected updateFormValue() {
|
|
104
|
+
const formData = new FormData()
|
|
105
|
+
|
|
106
|
+
const files = this.multiple ? this.files : this.files.slice(0, 1)
|
|
107
|
+
|
|
108
|
+
files.forEach((file) => {
|
|
109
|
+
formData.append(this.name, file)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
this.internals.setFormValue(formData)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
protected removeFile(fileToRemove: File) {
|
|
116
|
+
this.files = this.files.filter((file) => file !== fileToRemove)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
protected static formatFileSize(size: number) {
|
|
120
|
+
if (size < 1e3) {
|
|
121
|
+
return html`${size} bytes`
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (size >= 1e3 && size < 1e6) {
|
|
125
|
+
return html`${(size / 1e3).toFixed(1)} KB`
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return html`${(size / 1e6).toFixed(1)} MB`
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
protected handleDragEnter = (event: DragEvent) => {
|
|
132
|
+
if (this.disabled) return
|
|
133
|
+
|
|
134
|
+
event.preventDefault()
|
|
135
|
+
event.stopPropagation()
|
|
136
|
+
this.isDragging = [...event.dataTransfer.items].some(
|
|
137
|
+
(item) => item.kind === "file",
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// eslint-disable-next-line class-methods-use-this
|
|
142
|
+
protected handleDragOver = (event: DragEvent) => {
|
|
143
|
+
if (this.disabled) return
|
|
144
|
+
|
|
145
|
+
event.preventDefault()
|
|
146
|
+
event.stopPropagation()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
protected handleDragLeave = (event: DragEvent) => {
|
|
150
|
+
if (this.disabled) return
|
|
151
|
+
|
|
152
|
+
event.preventDefault()
|
|
153
|
+
event.stopPropagation()
|
|
154
|
+
this.isDragging = false
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
protected handleDrop = (event: DragEvent) => {
|
|
158
|
+
if (this.disabled) return
|
|
159
|
+
|
|
160
|
+
event.preventDefault()
|
|
161
|
+
event.stopPropagation()
|
|
162
|
+
|
|
163
|
+
const dt = event.dataTransfer
|
|
164
|
+
const files = dt.files
|
|
165
|
+
const acceptedFiles = [...files].filter((file) => this.isAcceptedFile(file))
|
|
166
|
+
|
|
167
|
+
this.files = this.multiple
|
|
168
|
+
? this.files.concat(acceptedFiles)
|
|
169
|
+
: acceptedFiles.slice(0, 1)
|
|
170
|
+
this.isDragging = false
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
isAcceptedFile(file: File): boolean {
|
|
174
|
+
const acceptedTypes = this.accept.split(",").map((type) => type.trim())
|
|
175
|
+
const mimeType = file.type
|
|
176
|
+
|
|
177
|
+
for (const acceptedType of acceptedTypes) {
|
|
178
|
+
// Handle file extensions (e.g. .jpg, .png)
|
|
179
|
+
if (acceptedType.startsWith(".")) {
|
|
180
|
+
const name = file.name.toLowerCase()
|
|
181
|
+
const extension = acceptedType.toLowerCase()
|
|
182
|
+
|
|
183
|
+
if (name.endsWith(extension)) {
|
|
184
|
+
return true
|
|
185
|
+
}
|
|
186
|
+
// Handle wildcard types (e.g. image/*)
|
|
187
|
+
} else if (/^\w+\/\*$/.test(acceptedType)) {
|
|
188
|
+
if (mimeType.split("/")[0] === acceptedType.split("/")[0]) {
|
|
189
|
+
return true
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (mimeType === acceptedType) {
|
|
194
|
+
return true
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return false
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
render() {
|
|
202
|
+
const dropzoneClasses = {
|
|
203
|
+
dropzone: true,
|
|
204
|
+
"dropzone--dragging": this.isDragging,
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return html`
|
|
208
|
+
<div class="container">
|
|
209
|
+
<label class="label" for="input">${this.label}</label>
|
|
210
|
+
<leu-visually-hidden>
|
|
211
|
+
<input
|
|
212
|
+
id="input"
|
|
213
|
+
type="file"
|
|
214
|
+
?multiple=${this.multiple}
|
|
215
|
+
accept=${ifDefined(this.accept)}
|
|
216
|
+
?disabled=${this.disabled}
|
|
217
|
+
@input=${this.handleInput}
|
|
218
|
+
/>
|
|
219
|
+
</leu-visually-hidden>
|
|
220
|
+
<div
|
|
221
|
+
class=${classMap(dropzoneClasses)}
|
|
222
|
+
@dragenter=${this.handleDragEnter}
|
|
223
|
+
@dragover=${this.handleDragOver}
|
|
224
|
+
@dragleave=${this.handleDragLeave}
|
|
225
|
+
@drop=${this.handleDrop}
|
|
226
|
+
>
|
|
227
|
+
<slot class="dropzone__text"
|
|
228
|
+
><p>Zum Hochladen Dateien ziehen und hier ablegen.</p></slot
|
|
229
|
+
>
|
|
230
|
+
<leu-button
|
|
231
|
+
variant="secondary"
|
|
232
|
+
?disabled=${this.disabled}
|
|
233
|
+
@click=${() => this.input.click()}
|
|
234
|
+
>
|
|
235
|
+
Datei auswählen
|
|
236
|
+
<leu-icon name="upload" slot="after"></leu-icon>
|
|
237
|
+
</leu-button>
|
|
238
|
+
</div>
|
|
239
|
+
${this.files.length > 0
|
|
240
|
+
? html`<ul class="file-list">
|
|
241
|
+
${this.files.map(
|
|
242
|
+
(file) =>
|
|
243
|
+
html`<li class="file">
|
|
244
|
+
<strong class="file__name">${file.name}</strong>
|
|
245
|
+
<p class="file__size">
|
|
246
|
+
<span class="file__size-label">Grösse:</span>
|
|
247
|
+
${LeuFileInput.formatFileSize(file.size)}
|
|
248
|
+
</p>
|
|
249
|
+
<leu-button
|
|
250
|
+
round
|
|
251
|
+
class="file__button"
|
|
252
|
+
label="Datei entfernen"
|
|
253
|
+
size="small"
|
|
254
|
+
variant="secondary"
|
|
255
|
+
?disabled=${this.disabled}
|
|
256
|
+
@click=${() => this.removeFile(file)}
|
|
257
|
+
><leu-icon name="delete"></leu-icon
|
|
258
|
+
></leu-button>
|
|
259
|
+
</li>`,
|
|
260
|
+
)}
|
|
261
|
+
</ul>`
|
|
262
|
+
: nothing}
|
|
263
|
+
</div>
|
|
264
|
+
`
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
@import url("../../styles/custom-media.css");
|
|
2
|
+
|
|
3
|
+
:host {
|
|
4
|
+
--file-input-font-regular: var(--leu-font-family-regular);
|
|
5
|
+
--file-input-font-black: var(--leu-font-family-black);
|
|
6
|
+
|
|
7
|
+
font-family: var(--file-input-font-regular);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.label {
|
|
11
|
+
display: block;
|
|
12
|
+
margin-bottom: 0.5rem;
|
|
13
|
+
font: var(--leu-t-curve-regular-black-font);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
:host([required]) .label::after {
|
|
17
|
+
content: "*";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.dropzone {
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
align-items: center;
|
|
24
|
+
text-align: center;
|
|
25
|
+
|
|
26
|
+
border: 2px dashed var(--leu-color-black-20);
|
|
27
|
+
border-radius: 0.25rem;
|
|
28
|
+
|
|
29
|
+
padding: 2rem 1.5rem;
|
|
30
|
+
|
|
31
|
+
@media (--viewport-regular) {
|
|
32
|
+
padding: 2.5rem 2rem;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@media (--viewport-xlarge) {
|
|
36
|
+
padding: 3.5rem 2.5rem;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.dropzone--dragging {
|
|
41
|
+
border-color: var(--leu-color-black-60);
|
|
42
|
+
background-color: var(--leu-color-black-transp-5);
|
|
43
|
+
cursor: copy;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.dropzone__text {
|
|
47
|
+
font: var(--leu-t-curve-small-regular-font);
|
|
48
|
+
color: var(--leu-color-black-60);
|
|
49
|
+
|
|
50
|
+
margin-bottom: 0.75rem;
|
|
51
|
+
|
|
52
|
+
@media (--viewport-small) {
|
|
53
|
+
margin-bottom: 2rem;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@media (--viewport-regular) {
|
|
57
|
+
margin-bottom: 1.25rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@media (--viewport-xlarge) {
|
|
61
|
+
margin-bottom: 1.5rem;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.file-list {
|
|
66
|
+
list-style: none;
|
|
67
|
+
padding: 0;
|
|
68
|
+
|
|
69
|
+
display: flex;
|
|
70
|
+
flex-direction: column;
|
|
71
|
+
gap: 0.125rem;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.file {
|
|
75
|
+
display: grid;
|
|
76
|
+
grid-template-columns: minmax(0, 1fr) min-content;
|
|
77
|
+
gap: 0.25rem 0.5rem;
|
|
78
|
+
|
|
79
|
+
place-items: start;
|
|
80
|
+
|
|
81
|
+
background-color: var(--leu-color-black-10);
|
|
82
|
+
padding: 0.75rem 1.25rem;
|
|
83
|
+
|
|
84
|
+
@media (--viewport-regular) {
|
|
85
|
+
column-gap: 0.75rem;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@media (--viewport-large) {
|
|
89
|
+
column-gap: 1rem;
|
|
90
|
+
padding: 1.5rem 1rem;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.file__name {
|
|
95
|
+
grid-column: 1 / 2;
|
|
96
|
+
font: var(--leu-t-regular-black-font);
|
|
97
|
+
text-overflow: ellipsis;
|
|
98
|
+
overflow: hidden;
|
|
99
|
+
max-width: 100%;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.file__size {
|
|
103
|
+
grid-column: 1 / 2;
|
|
104
|
+
display: flex;
|
|
105
|
+
gap: 0.25rem;
|
|
106
|
+
margin: 0;
|
|
107
|
+
font: var(--leu-t-tiny-regular-font);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.file__size-label {
|
|
111
|
+
color: var(--leu-color-black-60);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.file__button {
|
|
115
|
+
grid-column: 2 / 3;
|
|
116
|
+
grid-row: 1 / 3;
|
|
117
|
+
align-self: center;
|
|
118
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/web-components"
|
|
2
|
+
import { html } from "lit"
|
|
3
|
+
import { ifDefined } from "lit/directives/if-defined.js"
|
|
4
|
+
|
|
5
|
+
import "../leu-file-input.js"
|
|
6
|
+
import { LeuFileInput } from "../FileInput.js"
|
|
7
|
+
|
|
8
|
+
type StoryArgs = LeuFileInput
|
|
9
|
+
type Story = StoryObj<StoryArgs>
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
title: "Components/FileInput",
|
|
13
|
+
component: "leu-file-input",
|
|
14
|
+
} satisfies Meta<StoryArgs>
|
|
15
|
+
|
|
16
|
+
const Template: Story = {
|
|
17
|
+
render: (args: StoryArgs) => html`
|
|
18
|
+
<leu-file-input
|
|
19
|
+
name="file"
|
|
20
|
+
label=${ifDefined(args.label)}
|
|
21
|
+
accept=${ifDefined(args.accept)}
|
|
22
|
+
?disabled=${args.disabled}
|
|
23
|
+
?multiple=${args.multiple}
|
|
24
|
+
?required=${args.required}
|
|
25
|
+
></leu-file-input>
|
|
26
|
+
`,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const Regular = {
|
|
30
|
+
...Template,
|
|
31
|
+
args: {
|
|
32
|
+
accept: "image/*",
|
|
33
|
+
disabled: false,
|
|
34
|
+
multiple: false,
|
|
35
|
+
label: "Dokumente hochladen",
|
|
36
|
+
// Add default args here
|
|
37
|
+
},
|
|
38
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { html } from "lit"
|
|
2
|
+
import { fixture, expect } from "@open-wc/testing"
|
|
3
|
+
|
|
4
|
+
import "../leu-file-input.js"
|
|
5
|
+
import { type LeuFileInput } from "../FileInput.js"
|
|
6
|
+
|
|
7
|
+
async function defaultFixture() {
|
|
8
|
+
return fixture<LeuFileInput>(
|
|
9
|
+
html`<leu-file-input label="File upload"></leu-file-input>`,
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe("LeuFileInput", () => {
|
|
14
|
+
it("is a defined element", async () => {
|
|
15
|
+
const el = customElements.get("leu-file-input")
|
|
16
|
+
|
|
17
|
+
expect(el).not.to.be.undefined
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it("passes the a11y audit", async () => {
|
|
21
|
+
const el = await defaultFixture()
|
|
22
|
+
|
|
23
|
+
await expect(el).shadowDom.to.be.accessible()
|
|
24
|
+
})
|
|
25
|
+
})
|
|
@@ -132,9 +132,6 @@ export class LeuInput extends LeuElement {
|
|
|
132
132
|
this.novalidate = false
|
|
133
133
|
this.value = ""
|
|
134
134
|
|
|
135
|
-
/** @internal */
|
|
136
|
-
this._identifier = ""
|
|
137
|
-
|
|
138
135
|
/**
|
|
139
136
|
* @internal
|
|
140
137
|
* @type {import("lit/directives/ref.js").Ref<HTMLInputElement>}
|
|
@@ -250,29 +247,6 @@ export class LeuInput extends LeuElement {
|
|
|
250
247
|
)
|
|
251
248
|
}
|
|
252
249
|
|
|
253
|
-
/**
|
|
254
|
-
* Method for getting the id of the input element.
|
|
255
|
-
* If the id attribute is set, the value of the id attribute is returned.
|
|
256
|
-
* Otherwise a random id is generated and returned.
|
|
257
|
-
*
|
|
258
|
-
* @private
|
|
259
|
-
* @returns {string} id
|
|
260
|
-
*/
|
|
261
|
-
getId() {
|
|
262
|
-
const id = this.getAttribute("id")
|
|
263
|
-
|
|
264
|
-
if (id !== null && id !== "") {
|
|
265
|
-
return id
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (this._identifier !== "") {
|
|
269
|
-
return this._identifier
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
this._identifier = crypto.randomUUID()
|
|
273
|
-
return this._identifier
|
|
274
|
-
}
|
|
275
|
-
|
|
276
250
|
/**
|
|
277
251
|
* Merge custom and default validation messages.
|
|
278
252
|
* A validation message can be a function or a string.
|
|
@@ -352,7 +326,7 @@ export class LeuInput extends LeuElement {
|
|
|
352
326
|
}
|
|
353
327
|
|
|
354
328
|
return html`
|
|
355
|
-
<ul class="error" aria-errormessage
|
|
329
|
+
<ul class="error" aria-errormessage="input">
|
|
356
330
|
${errorMessages.map(
|
|
357
331
|
(message) => html`<li class="error-message">${message}</li>`,
|
|
358
332
|
)}
|
|
@@ -412,7 +386,7 @@ export class LeuInput extends LeuElement {
|
|
|
412
386
|
>
|
|
413
387
|
<input
|
|
414
388
|
class="input"
|
|
415
|
-
id="input
|
|
389
|
+
id="input"
|
|
416
390
|
type=${this.type}
|
|
417
391
|
name=${this.name}
|
|
418
392
|
@change=${this.handleChange}
|
|
@@ -431,7 +405,7 @@ export class LeuInput extends LeuElement {
|
|
|
431
405
|
ref=${ref(this._inputRef)}
|
|
432
406
|
aria-invalid=${isInvalid}
|
|
433
407
|
/>
|
|
434
|
-
<label for="input
|
|
408
|
+
<label for="input" class="label">${this.label}</label>
|
|
435
409
|
${this.prefix
|
|
436
410
|
? html`<div class="prefix" .aria-hidden=${true}>${this.prefix}</div>`
|
|
437
411
|
: nothing}
|