@brightspace-ui/labs 2.15.0 → 2.16.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/package.json CHANGED
@@ -12,6 +12,7 @@
12
12
  "./components/card-overlay.js": "./src/components/card-overlay/card-overlay.js",
13
13
  "./components/community-button.js": "./src/components/community/community-button.js",
14
14
  "./components/community-link.js": "./src/components/community/community-link.js",
15
+ "./components/file-uploader.js": "./src/components/file-uploader/file-uploader.js",
15
16
  "./components/community-url-factory.js": "./src/components/community/community-url-factory.js",
16
17
  "./components/navigation/navigation-band.js": "./src/components/navigation/navigation-band.js",
17
18
  "./components/navigation/navigation-button-icon.js": "./src/components/navigation/navigation-button-icon.js",
@@ -82,5 +83,5 @@
82
83
  "@lit/context": "^1.1.3",
83
84
  "lit": "^3"
84
85
  },
85
- "version": "2.15.0"
86
+ "version": "2.16.0"
86
87
  }
@@ -0,0 +1,83 @@
1
+ # File Uploader
2
+ Lit component for uploading files with drag and drop capability. This component does not perform the actual uploading work, it simply provides visual cues and exposes an event when files have been uploaded.
3
+
4
+
5
+ ## Usage
6
+
7
+ ```html
8
+ <head>
9
+ <script type="module" src="@brightspace-ui/labs/components/file-uploader.js"></script>
10
+ </head>
11
+ ```
12
+
13
+ ### Basic Usage with Accessible Label
14
+
15
+ It's important to always provide an accessible label which describes the purpose of the uploader using the `label` attribute. The label will be hidden visually but associated with the upload input for those using assistive technologies such as a screen reader.
16
+
17
+ ```html
18
+ <d2l-labs-file-uploader label="profile picture"></d2l-labs-file-uploader>
19
+ ```
20
+
21
+ ### Multi-file Uploads
22
+
23
+ To allow for multiple files to be uploaded, add the `multiple` attribute:
24
+
25
+ ```html
26
+ <d2l-labs-file-uploader multiple ...></d2l-labs-file-uploader>
27
+ ```
28
+
29
+ ### Localization
30
+
31
+ The file uploader will automatically render using the language found on the HTML element -- i.e. `<html lang="fr">`. If the language attribute is not present or isn't supported, the uploader will render in English.
32
+
33
+
34
+ ### Feedback Messages
35
+
36
+ If you encounter a scenario where you'd like to display feedback about the uploaded file(s) -- like a warning or an error -- use the `feedback` and `feedback-type` attributes.
37
+
38
+ The `feedback-type` defaults to "warning"(using `--d2l-color-feedback-warning`), but can also be set to "error"(using `--d2l-color-feedback-error`):
39
+ ```html
40
+ <d2l-labs-file-uploader
41
+ feedback="Sorry, we cannot upload files larger than 1GB.">
42
+ </d2l-labs-file-uploader>
43
+ ```
44
+
45
+ ```html
46
+ <d2l-labs-file-uploader
47
+ feedback="An error occurred occurred processing the upload."
48
+ feedback-type="error"></d2l-labs-file-uploader>
49
+ ```
50
+
51
+ #### Feedback Changed Event
52
+
53
+ To listen for when feedback changes within the uploader, register for the `feedback-changed` event.
54
+
55
+ Vanilla JavaScript:
56
+
57
+ ```html
58
+ <d2l-labs-file-uploader id="my-uploader" ...></d2l-labs-file-uploader>
59
+ <script>
60
+ document.getElementById('my-uploader')
61
+ .addEventListener('feedback-changed', function(evt) {
62
+ var feedbackMessage = evt.detail.value;
63
+ console.log(feedbackMessage);
64
+ });
65
+ </script>
66
+ ```
67
+
68
+ ### Handling Uploaded Files
69
+
70
+ When the user uploads one or more files, a `d2l-file-uploader-files-added` event is fired. To listen for this event, wire up an event listener to the `<d2l-labs-file-uploader>` element. The listener will be passed an event with an array of [File](https://developer.mozilla.org/en-US/docs/Web/API/File) objects from the [File API](https://developer.mozilla.org/en/docs/Web/API/File).
71
+
72
+ Vanilla JavaScript:
73
+
74
+ ```html
75
+ <d2l-labs-file-uploader id="my-uploader" ...></d2l-labs-file-uploader>
76
+ <script>
77
+ document.getElementById('my-uploader')
78
+ .addEventListener('d2l-file-uploader-files-added', function(evt) {
79
+ var files = evt.detail.files;
80
+ console.log(files);
81
+ });
82
+ </script>
83
+ ```
@@ -0,0 +1,340 @@
1
+ import '@brightspace-ui/core/components/colors/colors.js';
2
+ import '@brightspace-ui/core/components/offscreen/offscreen.js';
3
+ import { css, html, LitElement } from 'lit';
4
+ import { classMap } from 'lit/directives/class-map.js';
5
+ import { FocusMixin } from '@brightspace-ui/core/mixins/focus/focus-mixin.js';
6
+ import { LocalizeLabsElement } from '../localize-labs-element.js';
7
+
8
+ class FileUploader extends FocusMixin(LocalizeLabsElement(LitElement)) {
9
+ static get properties() {
10
+ return {
11
+ /**
12
+ * When set, displays a feedback message to the user. Used in conjunction with `feedback-type`.
13
+ */
14
+ feedback: { type: String, reflect: true },
15
+ /**
16
+ * The type of feedback to display. One of: "error", "warning"
17
+ */
18
+ feedbackType: { type: String, attribute: 'feedback-type', reflect: true },
19
+ /**
20
+ * A descriptive label to associate with the file input. Should
21
+ * be unique enough to distinguish the input from others on the page.
22
+ */
23
+ label: { type: String },
24
+ /**
25
+ * Whether multiple files can be uploaded.
26
+ */
27
+ multiple: { type: Boolean, reflect: true },
28
+ /**
29
+ * Collection of uploaded files, as [File](https://developer.mozilla.org/en-US/docs/Web/API/File) objects.
30
+ */
31
+ _files: { type: Object },
32
+ /**
33
+ * Whether a file is currently being dragged over the document.
34
+ */
35
+ _fileDragOver: { type: Boolean, attribute: '_file-drag-over', reflect: true },
36
+ _inputFocus: { type: Boolean }
37
+ };
38
+ }
39
+
40
+ static get styles() {
41
+ return css`
42
+ :host {
43
+ box-sizing: border-box;
44
+ display: block;
45
+ max-width: 27rem;
46
+ }
47
+ :host([feedback-type="warning"]) {
48
+ --d2l-file-uploader-feedback-color: var(--d2l-color-feedback-warning);
49
+ }
50
+ :host([feedback-type="error"]) {
51
+ --d2l-file-uploader-feedback-color: var(--d2l-color-feedback-error);
52
+ }
53
+
54
+ .d2l-file-uploader-drop-zone {
55
+ border: 2px dashed var(--d2l-color-ferrite);
56
+ border-radius: 0.3rem;
57
+ padding: 1.5rem;
58
+ text-align: center;
59
+ }
60
+
61
+ :host([_file-drag-over]) .d2l-file-uploader-drop-zone {
62
+ background-color: var(--d2l-color-celestine-plus-2);
63
+ border-color: var(--d2l-color-celestine);
64
+ }
65
+
66
+ .d2l-file-uploader-icon {
67
+ padding-bottom: 0.5rem;
68
+ }
69
+
70
+ :host([_file-drag-over]) svg path {
71
+ fill: var(--d2l-color-celestine);
72
+ }
73
+
74
+ .d2l-file-uploader-input {
75
+ display: inline-block;
76
+ height: 100%;
77
+ left: 0;
78
+ opacity: 0;
79
+ position: absolute;
80
+ top: 0;
81
+ width: 100%;
82
+ }
83
+
84
+ .d2l-file-uploader-browse-label {
85
+ background: none;
86
+ border: none;
87
+ color: var(--d2l-color-celestine);
88
+ overflow: hidden;
89
+ padding-right: 0;
90
+ position: relative;
91
+ }
92
+ .d2l-file-uploader-browse-label:hover,
93
+ .d2l-file-uploader-browse-label-focus {
94
+ color: var(--d2l-color-celestine-minus-1);
95
+ text-decoration: underline;
96
+ }
97
+
98
+ .d2l-file-uploader-browse-files {
99
+ display: none;
100
+ }
101
+
102
+ .d2l-file-uploader-feedback {
103
+ -webkit-animation-duration: 0.7s;
104
+ animation-duration: 0.7s;
105
+ -webkit-animation-name: feedbackIn;
106
+ animation-name: feedbackIn;
107
+ border: 1px solid var(--d2l-color-mica);
108
+ border-left-color: var(--d2l-file-uploader-feedback-color);
109
+ border-left-width: 0.5rem;
110
+ border-radius: 0.3rem;
111
+ display: none;
112
+ margin-bottom: 1.5rem;
113
+ padding: 0.7rem 1rem 0.7rem;
114
+ }
115
+ :host(:dir(rtl)) > .d2l-file-uploader-feedback {
116
+ border-left-color: var(--d2l-color-mica);
117
+ border-left-width: 1px;
118
+ border-right-color: var(--d2l-file-uploader-feedback-color);
119
+ border-right-width: 0.5rem;
120
+ }
121
+ :host([feedback]) > .d2l-file-uploader-feedback {
122
+ display: block;
123
+ }
124
+
125
+ @keyframes feedbackIn {
126
+ 0% {
127
+ max-height: 0.1rem;
128
+ opacity: 0;
129
+ }
130
+ 20% {
131
+ opacity: 0;
132
+ }
133
+ 80% {
134
+ max-height: 3rem;
135
+ }
136
+ 100% {
137
+ opacity: 1;
138
+ }
139
+ }
140
+
141
+ @media (max-width: 992px) {
142
+
143
+ .d2l-file-uploader-input-container {
144
+ display: block;
145
+ margin-top: 0.8rem;
146
+ }
147
+
148
+ .d2l-file-uploader-browse-files {
149
+ display: inline;
150
+ }
151
+
152
+ .d2l-file-uploader-browse {
153
+ display: none;
154
+ }
155
+
156
+ .d2l-file-uploader-browse-label {
157
+ background-color: var(--d2l-color-celestine);
158
+ border-color: var(--d2l-color-celestine-minus-1);
159
+ border-radius: 0.3rem;
160
+ border-style: none;
161
+ color: #ffffff;
162
+ display: inline-block;
163
+ font-size: 0.7rem;
164
+ padding: 0.35rem 1.5rem;
165
+ }
166
+
167
+ .d2l-file-uploader-browse-label:hover,
168
+ .d2l-file-uploader-browse-label-focus {
169
+ background-color: var(--d2l-color-celestine-minus-1);
170
+ color: #ffffff;
171
+ text-decoration: none;
172
+ }
173
+ .d2l-file-uploader-browse-label-focus {
174
+ box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px var(--d2l-color-celestine);
175
+ }
176
+
177
+ }
178
+ `;
179
+ }
180
+
181
+ constructor() {
182
+ super();
183
+ this.feedbackType = 'warning';
184
+ this.multiple = false;
185
+ this._fileDragOver = false;
186
+ this.__onDragOver = this.__onDragOver.bind(this);
187
+ this.__onDragLeave = this.__onDragLeave.bind(this);
188
+ this.__onDragEnd = this.__onDragEnd.bind(this);
189
+ this.__onDrop = this.__onDrop.bind(this);
190
+ }
191
+
192
+ static get focusElementSelector() {
193
+ return '.d2l-focusable';
194
+ }
195
+
196
+ connectedCallback() {
197
+ super.connectedCallback();
198
+ document.addEventListener('dragover', this.__onDragOver);
199
+ document.addEventListener('dragleave', this.__onDragLeave);
200
+ document.addEventListener('dragend', this.__onDragEnd);
201
+ document.addEventListener('drop', this.__onDrop);
202
+ }
203
+
204
+ disconnectedCallback() {
205
+ super.disconnectedCallback();
206
+ document.removeEventListener('dragover', this.__onDragOver);
207
+ document.removeEventListener('dragleave', this.__onDragLeave);
208
+ document.removeEventListener('dragend', this.__onDragEnd);
209
+ document.removeEventListener('drop', this.__onDrop);
210
+ }
211
+
212
+ render() {
213
+ const labelClasses = {
214
+ 'd2l-file-uploader-browse-label': true,
215
+ 'd2l-file-uploader-browse-label-focus': this._inputFocus,
216
+ };
217
+ return html`
218
+ <div class="d2l-file-uploader-feedback" role="alert">${this.feedback}</div>
219
+ <div class="d2l-file-uploader-drop-zone">
220
+ <svg width="78" height="78" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 78 78" class="d2l-file-uploader-icon">
221
+ <path fill="#565a5c" d="M54.76 36A3.992 3.992 0 0 1 51 38.64h-9v36a3 3 0 0 1-6 0v-36h-9a4 4 0 0 1-2.56-7.07l12-10a3.988 3.988 0 0 1 5.12 0l12 10a4.007 4.007 0 0 1 1.2 4.43zM46 53.55a1.333 1.333 0 0 0 .49.09M46.5 50.64a1.386 1.386 0 0 0-.5.09"></path>
222
+ <path fill="#565a5c" d="M78 36.14a17.52 17.52 0 0 1-17.5 17.5c-.17 0-1.33 0-1.5-.02l-12.51.02a1.333 1.333 0 0 1-.49-.09 1.494 1.494 0 0 1 0-2.82 1.386 1.386 0 0 1 .5-.09h14a14.5 14.5 0 0 0 1.58-28.92c-.52-.05-1.05-.08-1.58-.08h-.35a1.49 1.49 0 0 1-1-.4 2.258 2.258 0 0 1-.542-1.075c-.138-.462-.306-.916-.478-1.365a20.484 20.484 0 0 0-38.26 0q-.21.54-.39 1.08a3.353 3.353 0 0 1-.53 1.26 1.542 1.542 0 0 1-1.12.5h-.33c-.53 0-1.06.03-1.58.08a14.5 14.5 0 0 0 1.58 28.92h13.99a1.5 1.5 0 0 1 .01 3s-13.5 0-13.5-.02a4.176 4.176 0 0 1-.5.02 17.5 17.5 0 0 1-.77-34.98 23.49 23.49 0 0 1 44.54 0A17.52 17.52 0 0 1 78 36.14z"></path>
223
+ </svg>
224
+ <div>
225
+ <span>${this.localize('components:fileUploader:file_upload_text')}&nbsp;</span>
226
+ <span class="d2l-file-uploader-input-container">
227
+ <d2l-offscreen id="d2l-file-uploader-offscreen">${this.label}</d2l-offscreen>
228
+ <label class="${classMap(labelClasses)}">
229
+ <input class="d2l-file-uploader-input d2l-focusable" type="file" aria-describedby="d2l-file-uploader-offscreen" ?multiple=${this.multiple} @change=${this._fileSelectHandler} @focus=${this.__onInputFocus} @blur=${this.__onInputBlur}>
230
+ <span class="d2l-file-uploader-browse">${this.localize('components:fileUploader:browse')}</span>
231
+ <span class="d2l-file-uploader-browse-files">${this.localize('components:fileUploader:browse_files')}</span>
232
+ </label>
233
+ </span>
234
+ </div>
235
+ </div>
236
+
237
+ `;
238
+ }
239
+
240
+ willUpdate(changedProperties) {
241
+ super.willUpdate(changedProperties);
242
+ if (changedProperties.has('feedback')) {
243
+ this.dispatchEvent(new CustomEvent('feedback-changed',
244
+ { bubbles: true, composed: true, detail: { value: this.feedback } }
245
+ ));
246
+ }
247
+ if (changedProperties.has('_files')) {
248
+ this._fileChangeHandler(this._files);
249
+ }
250
+
251
+ }
252
+
253
+ __onDragEnd(event) {
254
+ this._fileDragOver = false;
255
+ const dataTransfer = event.dataTransfer;
256
+ if (dataTransfer.items) {
257
+ // Use DataTransferItemList interface to remove the drag data
258
+ for (let i = 0; i < dataTransfer.items.length; i++) {
259
+ dataTransfer.items.remove(i);
260
+ }
261
+ } else {
262
+ // Use DataTransfer interface to remove the drag data
263
+ event.dataTransfer.clearData();
264
+ }
265
+ }
266
+
267
+ __onDragLeave(event) {
268
+ event.preventDefault();
269
+ this._fileDragOver = false;
270
+ }
271
+
272
+ __onDragOver(event) {
273
+ event.preventDefault();
274
+ this._fileDragOver = true;
275
+ }
276
+
277
+ __onDrop(event) {
278
+ event.preventDefault();
279
+ this._fileDragOver = false;
280
+ const files = [];
281
+ const dataTransfer = event.dataTransfer;
282
+ if (dataTransfer.items) {
283
+ if (!this.multiple && dataTransfer.items.length > 1) {
284
+ this.feedback = this.localize('components:fileUploader:choose_one_file_to_upload');
285
+ this.feedbackType = 'warning';
286
+ return;
287
+ }
288
+ this.feedback = null;
289
+
290
+ // Use DataTransferItemList interface to access the file(s)
291
+ let count = 0;
292
+ for (let i = 0; i < dataTransfer.items.length; i++) {
293
+ if (dataTransfer.items[ i ].kind === 'file') {
294
+ files[count] = dataTransfer.items[i].getAsFile();
295
+ count++;
296
+ }
297
+ }
298
+ } else {
299
+ if (!this.multiple && dataTransfer.files.length > 1) {
300
+ this.feedback = this.localize('components:fileUploader:choose_one_file_to_upload');
301
+ this.feedbackType = 'warning';
302
+ return;
303
+ }
304
+ this.feedback = null;
305
+
306
+ // Use DataTransfer interface to access the file(s)
307
+ for (let j = 0; j < dataTransfer.files.length; j++) {
308
+ files[j] = dataTransfer.files[j];
309
+ }
310
+ }
311
+ this._files = files;
312
+ }
313
+
314
+ __onInputBlur() {
315
+ this._inputFocus = false;
316
+ }
317
+
318
+ __onInputFocus() {
319
+ this._inputFocus = true;
320
+ }
321
+
322
+ _fileChangeHandler(files) {
323
+ const evt = new CustomEvent(
324
+ 'd2l-file-uploader-files-added',
325
+ { detail: { files: files } }
326
+ );
327
+ this.dispatchEvent(evt);
328
+ }
329
+
330
+ _fileSelectHandler(event) {
331
+ const files = [];
332
+ for (let i = 0; i < event.target.files.length; i++) {
333
+ files[i] = event.target.files[i];
334
+ }
335
+ this._files = files;
336
+ event.target.value = '';
337
+ }
338
+ }
339
+
340
+ customElements.define('d2l-labs-file-uploader', FileUploader);