wave-ui 1.45.14 → 1.48.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-ui",
3
- "version": "1.45.14",
3
+ "version": "1.48.0",
4
4
  "description": "An emerging UI framework for Vue.js & Vue 3 with only the bright side. :sunny:",
5
5
  "author": "Antoni Andre <antoniandre.web@gmail.com>",
6
6
  "main": "./dist/wave-ui.umd.js",
@@ -46,7 +46,6 @@
46
46
  "@babel/core": "^7.16.0",
47
47
  "@babel/eslint-parser": "^7.16.3",
48
48
  "@babel/plugin-proposal-class-properties": "^7.16.0",
49
- "@mdi/font": "^5.9.55",
50
49
  "@vitejs/plugin-vue": "^1.10.1",
51
50
  "autoprefixer": "^10.4.0",
52
51
  "axios": "^0.21.4",
@@ -75,5 +74,8 @@
75
74
  "vue-template-compiler": "^2.6.14",
76
75
  "vueperslides": "^2.15.2",
77
76
  "vuex": "^3.6.2"
77
+ },
78
+ "dependencies": {
79
+ "@mdi/font": "^6.5.95"
78
80
  }
79
81
  }
@@ -229,7 +229,6 @@ export default {
229
229
  &--icon-outside &__icon {
230
230
  position: absolute;
231
231
  opacity: 1;
232
- // top: 2 * $base-increment - 1px; // Needed for IE 11, but dropping support.
233
232
  left: 1px;
234
233
  z-index: 1;
235
234
  transform: translateX(-50%);
@@ -2,23 +2,23 @@
2
2
  .w-confirm
3
3
  w-menu(v-model="showPopup" v-bind="wMenuProps")
4
4
  template(#activator="{ on }")
5
- w-button.w-confirm__button(v-on="on" v-bind="buttonProps")
5
+ w-button.w-confirm__button(v-on="{ ...$listeners, ...on }" v-bind="buttonProps")
6
6
  slot
7
7
  w-flex(:column="!inline" align-center)
8
8
  div
9
- slot(name="question") Are you sure?
9
+ slot(name="question") {{ question }}
10
10
  .w-flex.justify-end(:class="inline ? 'ml2' : 'mt2'")
11
11
  w-button.mr2(
12
- v-if="!noCancel"
13
- v-bind="cancelButton"
12
+ v-if="cancel !== false"
13
+ v-bind="cancelButtonProps"
14
14
  :bg-color="(cancelButton || {}).bgColor || 'error'"
15
15
  @click="onCancel")
16
- slot(name="cancel") Cancel
16
+ slot(name="cancel") {{ cancelButton.label }}
17
17
  w-button(
18
- v-bind="confirmButton"
18
+ v-bind="confirmButtonProps"
19
19
  :bg-color="(confirmButton || {}).bgColor || 'success'"
20
20
  @click="onConfirm")
21
- slot(name="confirm") Confirm
21
+ slot(name="confirm") {{ confirmButton.label }}
22
22
  </template>
23
23
 
24
24
  <script>
@@ -30,11 +30,16 @@ export default {
30
30
  color: { type: String },
31
31
  icon: { type: String },
32
32
  mainButton: { type: Object }, // Allow passing down an object of props to the w-button component.
33
+ question: { type: String, default: 'Are you sure?' },
33
34
 
34
35
  // Cancel & confirm buttons props.
35
- noCancel: { type: Boolean }, // Removes the cancel button.
36
- cancelButton: { type: [Boolean, Object] }, // Allow passing down an object of props to the w-button component.
37
- confirmButton: { type: Object }, // Allow passing down an object of props to the w-button component.
36
+ // Allow passing down an object of props to the w-button component.
37
+ // If a string is given, that will be the label of the button.
38
+ // If false, no cancel button.
39
+ cancel: { type: [Boolean, Object, String], default: undefined },
40
+ // Allow passing down an object of props to the w-button component.
41
+ // If a string is given, that will be the label of the button.
42
+ confirm: { type: [Object, String] },
38
43
 
39
44
  // global menu props.
40
45
  inline: { type: Boolean }, // The layout inside the menu.
@@ -63,6 +68,26 @@ export default {
63
68
  }),
64
69
 
65
70
  computed: {
71
+ cancelButton () {
72
+ let button = { label: typeof this.cancel === 'string' ? this.cancel : 'Cancel' }
73
+ if (typeof this.cancel === 'object') button = Object.assign({}, button, this.cancel)
74
+ return button
75
+ },
76
+ // Props to pass down to the w-button component.
77
+ cancelButtonProps () {
78
+ const { label, ...props } = this.cancelButton // Everything except label.
79
+ return props
80
+ },
81
+ confirmButton () {
82
+ let button = { label: typeof this.confirm === 'string' ? this.confirm : 'Confirm' }
83
+ if (typeof this.confirm === 'object') button = Object.assign({}, button, this.confirm)
84
+ return button
85
+ },
86
+ // Props to pass down to the w-button component.
87
+ confirmButtonProps () {
88
+ const { label, ...props } = this.confirmButton // Everything except label.
89
+ return props
90
+ },
66
91
  wMenuProps () {
67
92
  return {
68
93
  top: this.top,
@@ -12,9 +12,16 @@ component(
12
12
  template(v-else)
13
13
  //- Left label.
14
14
  template(v-if="labelPosition === 'left'")
15
- label.w-input__label.w-input__label--left.w-form-el-shakable(v-if="$slots.default" :for="`w-input--${_uid}`")
15
+ label.w-input__label.w-input__label--left.w-form-el-shakable(
16
+ v-if="$slots.default"
17
+ :for="`w-input--${_uid}`"
18
+ :class="validationClasses")
16
19
  slot
17
- label.w-input__label.w-input__label--left.w-form-el-shakable(v-else-if="label" :for="`w-input--${_uid}`" v-html="label")
20
+ label.w-input__label.w-input__label--left.w-form-el-shakable(
21
+ v-else-if="label"
22
+ :for="`w-input--${_uid}`"
23
+ :class="validationClasses"
24
+ v-html="label")
18
25
 
19
26
  //- Input wrapper.
20
27
  .w-input__input-wrap(:class="inputWrapClasses")
@@ -58,47 +65,67 @@ component(
58
65
  @blur="onBlur"
59
66
  @change="onFileChange"
60
67
  :multiple="multiple || null"
61
- v-bind="attrs")
62
- transition-group.w-input__input.w-input__input--file(tag="label" name="fade" :for="`w-input--${_uid}`")
68
+ v-bind="attrs"
69
+ :data-progress="overallFilesProgress /* Needed to emit the overallProgress. */")
70
+ transition-group.w-input__input.w-input__input--file(
71
+ tag="label"
72
+ name="fade"
73
+ :for="`w-input--${_uid}`")
63
74
  span.w-input__no-file(v-if="!inputFiles.length && isFocused" key="no-file")
64
75
  slot(name="no-file")
65
76
  template(v-if="$slots['no-file'] === undefined") No file
66
77
  span(v-for="(file, i) in inputFiles" :key="file.lastModified")
67
78
  | {{ i ? ', ': '' }}
68
79
  span.filename(:key="`${i}b`") {{ file.base }}
69
- | {{ file.extension }}
80
+ | {{ file.extension ? `.${file.extension}` : '' }}
70
81
 
71
82
  template(v-if="labelPosition === 'inside' && showLabelInside")
72
83
  label.w-input__label.w-input__label--inside.w-form-el-shakable(
73
84
  v-if="$slots.default"
74
85
  :for="`w-input--${_uid}`"
75
- :class="isFocused && { [valid === false ? 'error' : color]: color || valid === false }")
86
+ :class="validationClasses")
76
87
  slot
77
88
  label.w-input__label.w-input__label--inside.w-form-el-shakable(
78
89
  v-else-if="label"
79
90
  :for="`w-input--${_uid}`"
80
91
  v-html="label"
81
- :class="isFocused && { [valid === false ? 'error' : color]: color || valid === false }")
92
+ :class="validationClasses")
82
93
  w-icon.w-input__icon.w-input__icon--inner-right(
83
94
  v-if="innerIconRight"
84
95
  tag="label"
85
96
  :for="`w-input--${_uid}`"
86
97
  @click="$emit('click:inner-icon-right', $event)") {{ innerIconRight }}
87
98
 
99
+ w-progress.fill-width(
100
+ v-if="hasLoading || (showProgress && (uploadInProgress || uploadComplete))"
101
+ size="2"
102
+ :color="progressColor || color"
103
+ :value="showProgress ? (uploadInProgress || uploadComplete) && overallFilesProgress : loadingValue")
104
+
88
105
  //- Files preview.
89
- label.d-flex(v-if="type === 'file' && inputFiles.length" :for="`w-input--${_uid}`")
106
+ label.d-flex(v-if="type === 'file' && preview && inputFiles.length" :for="`w-input--${_uid}`")
90
107
  template(v-for="(file, i) in inputFiles")
91
- i.w-icon.wi-spinner.w-icon--spin.size--sm.w-input__file-preview.primary(v-if="file.progress < 100" :key="i")
108
+ i.w-icon.wi-spinner.w-icon--spin.size--sm.w-input__file-preview.primary(
109
+ v-if="file.progress < 100"
110
+ :key="i")
92
111
  img.w-input__file-preview(v-else-if="file.preview" :key="i" :src="file.preview" alt="")
93
- i.w-icon.wi-file.w-input__file-preview.primary(v-else :key="i")
112
+ i.w-icon.w-input__file-preview.primary.size--md(
113
+ v-else
114
+ :key="i"
115
+ :class="preview && typeof preview === 'string' ? preview : 'wi-file'")
94
116
 
95
117
  //- Right label.
96
118
  template(v-if="labelPosition === 'right'")
97
- label.w-input__label.w-input__label--right.w-form-el-shakable(v-if="$slots.default" :for="`w-input--${_uid}`")
119
+ label.w-input__label.w-input__label--right.w-form-el-shakable(
120
+ v-if="$slots.default"
121
+ :for="`w-input--${_uid}`"
122
+ :class="validationClasses")
98
123
  slot
99
- label.w-input__label.w-input__label--right.w-form-el-shakable(v-else-if="label" :for="`w-input--${_uid}`" v-html="label")
100
-
101
- w-progress.fill-width(v-if="loading" size="2" :color="progressColor || color")
124
+ label.w-input__label.w-input__label--right.w-form-el-shakable(
125
+ v-else-if="label"
126
+ :for="`w-input--${_uid}`"
127
+ :class="validationClasses"
128
+ v-html="label")
102
129
  </template>
103
130
 
104
131
  <script>
@@ -136,13 +163,17 @@ export default {
136
163
  shadow: { type: Boolean },
137
164
  tile: { type: Boolean },
138
165
  multiple: { type: Boolean }, // Only for file uploads.
139
- preview: { type: Boolean }, // Only for file uploads.
140
- loading: { type: Boolean }
166
+ preview: { type: [Boolean, String], default: true }, // Only for file uploads.
167
+ loading: { type: [Boolean, Number], default: false }, // If a number is given, it will be the value of the progress.
168
+ showProgress: { type: [Boolean] }, // Only for file uploads.
169
+ // Allow syncing the files 1 way: prefilling a file is not possible.
170
+ // https://stackoverflow.com/questions/16365668/pre-populate-html-form-file-input
171
+ files: { type: Array }
141
172
  // Props from mixin: name, disabled, readonly, required, tabindex, validators.
142
173
  // Computed from mixin: inputName, isDisabled & isReadonly.
143
174
  },
144
175
 
145
- emits: ['input', 'update:modelValue', 'focus', 'blur', 'click:inner-icon-left', 'click:inner-icon-right'],
176
+ emits: ['input', 'update:modelValue', 'focus', 'blur', 'click:inner-icon-left', 'click:inner-icon-right', 'update:overallProgress'],
146
177
 
147
178
  data () {
148
179
  return {
@@ -173,23 +204,54 @@ export default {
173
204
  },
174
205
 
175
206
  hasValue () {
176
- return (
177
- this.inputValue ||
178
- this.inputValue === 0 ||
179
- ['date', 'time'].includes(this.type) ||
180
- (this.type === 'number' && this.inputNumberError) ||
181
- (this.type === 'file' && this.inputFiles.length)
182
- )
207
+ switch (this.type) {
208
+ case 'file': return !!this.inputFiles.length
209
+ case 'number': return this.inputNumberError
210
+ case 'date':
211
+ case 'time':
212
+ return true
213
+ default:
214
+ return this.inputValue || this.inputValue === 0
215
+ }
183
216
  },
184
217
 
185
218
  hasLabel () {
186
219
  return this.label || this.$slots.default
187
220
  },
188
221
 
222
+ hasLoading () {
223
+ return ![undefined, false].includes(this.loading)
224
+ },
225
+
226
+ loadingValue () {
227
+ let value
228
+ if (typeof this.loading === 'number') value = this.loading
229
+ else if (this.loading) {
230
+ value = this.type === 'file' && this.overallFilesProgress ? this.overallFilesProgress : undefined
231
+ }
232
+ return value
233
+ },
234
+
189
235
  showLabelInside () {
190
236
  return !this.staticLabel || (!this.hasValue && !this.placeholder)
191
237
  },
192
238
 
239
+ overallFilesProgress () {
240
+ const progress = this.inputFiles.reduce((total, file) => total + file.progress, 0)
241
+ const total = progress / this.inputFiles.length
242
+ this.$emit('update:overallProgress', this.inputFiles.length ? total : undefined)
243
+
244
+ return total
245
+ },
246
+
247
+ uploadInProgress () {
248
+ return this.overallFilesProgress > 0 && this.overallFilesProgress < 100
249
+ },
250
+
251
+ uploadComplete () {
252
+ return this.overallFilesProgress === 100
253
+ },
254
+
193
255
  classes () {
194
256
  return {
195
257
  'w-input': true,
@@ -207,6 +269,12 @@ export default {
207
269
  }
208
270
  },
209
271
 
272
+ validationClasses () {
273
+ return this.isFocused && {
274
+ [this.valid === false ? 'error' : this.color]: this.color || this.valid === false
275
+ }
276
+ },
277
+
210
278
  inputWrapClasses () {
211
279
  return {
212
280
  [this.valid === false ? 'error' : this.color]: this.color || this.valid === false,
@@ -219,7 +287,8 @@ export default {
219
287
  'w-input__input-wrap--underline': !this.outline,
220
288
  'w-input__input-wrap--shadow': this.shadow,
221
289
  'w-input__input-wrap--no-padding': !this.outline && !this.bgColor && !this.shadow && !this.round,
222
- 'w-input__input-wrap--loading': this.loading
290
+ 'w-input__input-wrap--loading': this.loading || (this.showProgress && this.uploadInProgress),
291
+ 'w-input__input-wrap--upload-complete': this.uploadComplete
223
292
  }
224
293
  }
225
294
  },
@@ -244,35 +313,43 @@ export default {
244
313
  // For file input.
245
314
  onFileChange (e) {
246
315
  this.$set(this, 'inputFiles', [...e.target.files].map(original => {
247
- const [, base, extension] = original.name.match(/^(.*)(\..*?)$/)
316
+ // `full` if there is no filename but only an extension.
317
+ const [, base = '', extension = '', full = ''] = original.name.match(/^(.*?)\.([^.]*)$|(.*)/)
248
318
  const file = Object.assign({}, {
249
319
  name: original.name,
250
- base,
320
+ base: base || full,
251
321
  extension,
252
322
  type: original.type,
253
323
  size: original.size,
254
324
  lastModified: original.lastModified,
255
325
  preview: null,
256
- progress: 0
326
+ progress: 0,
327
+ file: original
257
328
  })
258
329
 
259
- this.filePreview(original, file)
330
+ this.readFile(original, file)
260
331
 
261
332
  return file
262
333
  }))
263
334
  this.$emit('update:modelValue', this.inputFiles)
335
+ this.$emit('input', this.inputFiles)
264
336
  },
265
337
 
266
338
  // For file input.
267
- filePreview (original, file) {
339
+ readFile (original, file) {
268
340
  const reader = new FileReader()
269
341
 
342
+ // If the preview prop is a string, the user is setting the preview to an icon and
343
+ // don't need the actual file preview.
344
+ const isPreviewAnIcon = typeof this.preview === 'string'
345
+ const isFileAnImage = original.type && original.type.startsWith('image/')
270
346
  // Check if the file is an image and set a preview image.
271
- if (original.type && original.type.startsWith('image/')) {
347
+ if (this.preview && !isPreviewAnIcon && isFileAnImage) {
272
348
  reader.addEventListener('load', e => {
273
349
  this.$set(file, 'preview', e.target.result)
274
350
  })
275
351
  }
352
+ else delete file.preview
276
353
 
277
354
  // Used to display a spinner while the file is loading.
278
355
  reader.addEventListener('progress', event => {
@@ -289,7 +366,7 @@ export default {
289
366
  // On page load, check if the field is autofilled by the browser.
290
367
  // 20211229. Only a problem on Chrome. Firefox ok, Safari always prompts before filling up.
291
368
  setTimeout(() => {
292
- if (this.$refs.input.matches(':-webkit-autofill')) this.isAutofilled = true
369
+ if (this.$refs.input && this.$refs.input.matches(':-webkit-autofill')) this.isAutofilled = true
293
370
  }, 400) // Can't be less than 350: time for the browser to autofill.
294
371
  },
295
372
 
@@ -317,6 +394,8 @@ $inactive-color: #777;
317
394
  &--file {
318
395
  flex-wrap: nowrap;
319
396
  align-items: flex-end;
397
+
398
+ span.fade-leave-to {position: absolute;}
320
399
  }
321
400
 
322
401
  // Input field wrapper.
@@ -346,7 +425,10 @@ $inactive-color: #777;
346
425
  &--round {border-radius: 99em;}
347
426
  &--tile {border-radius: initial;}
348
427
  &--shadow {box-shadow: $box-shadow;}
349
- &--loading {border-bottom-color: transparent;}
428
+ &--loading, &--upload-complete {
429
+ border-bottom-color: transparent;
430
+ flex-wrap: wrap;
431
+ }
350
432
  &--loading ~ .w-progress {
351
433
  height: 2px;
352
434
  position: absolute;
@@ -355,7 +437,8 @@ $inactive-color: #777;
355
437
  }
356
438
 
357
439
  .w-input--focused & {border-color: currentColor;}
358
- .w-input--focused &--loading {border-bottom-color: transparent;}
440
+ .w-input--focused &--loading,
441
+ .w-input--focused &--upload-complete {border-bottom-color: transparent;}
359
442
 
360
443
  // Underline.
361
444
  &--underline:after {
@@ -466,6 +549,8 @@ $inactive-color: #777;
466
549
  margin-left: 4px;
467
550
  max-height: 2em;
468
551
  align-self: flex-end;
552
+
553
+ &.w-icon {margin-bottom: 4px;}
469
554
  }
470
555
 
471
556
  // Icons inside.