wave-ui 2.29.0 → 2.31.2

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": "2.29.0",
3
+ "version": "2.31.2",
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",
@@ -43,29 +43,32 @@
43
43
  "*.vue"
44
44
  ],
45
45
  "devDependencies": {
46
- "@babel/core": "^7.16.5",
47
- "@babel/eslint-parser": "^7.16.5",
48
- "@babel/plugin-proposal-class-properties": "^7.16.5",
46
+ "@babel/core": "^7.17.0",
47
+ "@babel/eslint-parser": "^7.17.0",
48
+ "@babel/plugin-proposal-class-properties": "^7.16.7",
49
49
  "@mdi/font": "^5.9.55",
50
50
  "@vitejs/plugin-vue": "^1.10.2",
51
51
  "@vue/compiler-sfc": "3.1.5",
52
- "autoprefixer": "^10.4.1",
53
- "axios": "^0.21.4",
52
+ "autoprefixer": "^10.4.2",
53
+ "axios": "^0.25.0",
54
54
  "eslint": "^7.32.0",
55
55
  "font-awesome": "^4.7.0",
56
56
  "gsap": "^3.9.1",
57
57
  "ionicons": "^4.6.3",
58
58
  "material-design-icons": "^3.0.1",
59
59
  "rollup-plugin-delete": "^2.0.0",
60
- "sass": "^1.45.1",
60
+ "sass": "^1.49.7",
61
61
  "simple-syntax-highlighter": "^2.2.0",
62
62
  "splitpanes": "^3.0.6",
63
- "vite": "^2.7.9",
63
+ "vite": "^2.7.13",
64
64
  "vite-plugin-pug": "^0.3.0",
65
- "vue": "^3.2.26",
65
+ "vue": "^3.2.31",
66
66
  "vue-cal": "^4.2.0",
67
67
  "vue-router": "^4.0.12",
68
68
  "vueperslides": "^3.3.2",
69
69
  "vuex": "^4.0.2"
70
+ },
71
+ "dependencies": {
72
+ "postcss": "^8.4.6"
70
73
  }
71
74
  }
@@ -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-bind="{ ...$attrs, ...buttonProps, ...on }")
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,35 +65,45 @@ 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
+ :model-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
108
  i.w-icon.wi-spinner.w-icon--spin.size--sm.w-input__file-preview.primary(
92
109
  v-if="file.progress < 100"
@@ -96,20 +113,23 @@ component(
96
113
  :key="`${i}b`"
97
114
  :src="file.preview"
98
115
  alt="")
99
- i.w-icon.wi-file.w-input__file-preview.primary(v-else :key="`${i}c`")
116
+ i.w-icon.w-input__file-preview.primary.size--md(
117
+ v-else
118
+ :key="`${i}c`"
119
+ :class="preview && typeof preview === 'string' ? preview : 'wi-file'")
100
120
 
101
121
  //- Right label.
102
122
  template(v-if="labelPosition === 'right'")
103
123
  label.w-input__label.w-input__label--right.w-form-el-shakable(
104
124
  v-if="$slots.default"
105
- :for="`w-input--${_.uid}`")
125
+ :for="`w-input--${_.uid}`"
126
+ :class="validationClasses")
106
127
  slot
107
128
  label.w-input__label.w-input__label--right.w-form-el-shakable(
108
129
  v-else-if="label"
109
130
  :for="`w-input--${_.uid}`"
131
+ :class="validationClasses"
110
132
  v-html="label")
111
-
112
- w-progress.fill-width(v-if="loading" size="2" :color="progressColor || color")
113
133
  </template>
114
134
 
115
135
  <script>
@@ -148,13 +168,17 @@ export default {
148
168
  shadow: { type: Boolean },
149
169
  tile: { type: Boolean },
150
170
  multiple: { type: Boolean }, // Only for file uploads.
151
- preview: { type: Boolean }, // Only for file uploads.
152
- loading: { type: Boolean }
171
+ preview: { type: [Boolean, String], default: true }, // Only for file uploads.
172
+ loading: { type: [Boolean, Number], default: false }, // If a number is given, it will be the value of the progress.
173
+ showProgress: { type: [Boolean] }, // Only for file uploads.
174
+ // Allow syncing the files 1 way: prefilling a file is not possible.
175
+ // https://stackoverflow.com/questions/16365668/pre-populate-html-form-file-input
176
+ files: { type: Array }
153
177
  // Props from mixin: name, disabled, readonly, required, tabindex, validators.
154
178
  // Computed from mixin: inputName, isDisabled & isReadonly.
155
179
  },
156
180
 
157
- emits: ['input', 'update:modelValue', 'focus', 'blur', 'click:inner-icon-left', 'click:inner-icon-right'],
181
+ emits: ['input', 'update:modelValue', 'focus', 'blur', 'click:inner-icon-left', 'click:inner-icon-right', 'update:overallProgress'],
158
182
 
159
183
  data () {
160
184
  return {
@@ -183,29 +207,62 @@ export default {
183
207
  const { input, focus, blur, ...listeners } = this.$attrs
184
208
  return listeners
185
209
  },
210
+
186
211
  attrs () {
187
212
  // eslint-disable-next-line no-unused-vars
188
213
  const { class: Class, ...htmlAttrs } = this.$attrs
189
214
  return htmlAttrs
190
215
  },
216
+
191
217
  hasValue () {
192
- return (
193
- this.inputValue ||
194
- this.inputValue === 0 ||
195
- ['date', 'time'].includes(this.type) ||
196
- (this.type === 'number' && this.inputNumberError) ||
197
- (this.type === 'file' && this.inputFiles.length)
198
- )
218
+ switch (this.type) {
219
+ case 'file': return !!this.inputFiles.length
220
+ case 'number': return this.inputValue || this.inputValue === 0 || this.inputNumberError
221
+ case 'date':
222
+ case 'time':
223
+ return true
224
+ default:
225
+ return this.inputValue || this.inputValue === 0
226
+ }
199
227
  },
200
228
 
201
229
  hasLabel () {
202
230
  return this.label || this.$slots.default
203
231
  },
204
232
 
233
+ hasLoading () {
234
+ return ![undefined, false].includes(this.loading)
235
+ },
236
+
237
+ loadingValue () {
238
+ let value
239
+ if (typeof this.loading === 'number') value = this.loading
240
+ else if (this.loading) {
241
+ value = this.type === 'file' && this.overallFilesProgress ? this.overallFilesProgress : undefined
242
+ }
243
+ return value
244
+ },
245
+
205
246
  showLabelInside () {
206
247
  return !this.staticLabel || (!this.hasValue && !this.placeholder)
207
248
  },
208
249
 
250
+ overallFilesProgress () {
251
+ const progress = this.inputFiles.reduce((total, file) => total + file.progress, 0)
252
+ const total = progress / this.inputFiles.length
253
+ this.$emit('update:overallProgress', this.inputFiles.length ? total : undefined)
254
+
255
+ return total
256
+ },
257
+
258
+ uploadInProgress () {
259
+ return this.overallFilesProgress > 0 && this.overallFilesProgress < 100
260
+ },
261
+
262
+ uploadComplete () {
263
+ return this.overallFilesProgress === 100
264
+ },
265
+
209
266
  classes () {
210
267
  return {
211
268
  'w-input': true,
@@ -223,6 +280,12 @@ export default {
223
280
  }
224
281
  },
225
282
 
283
+ validationClasses () {
284
+ return this.isFocused && {
285
+ [this.valid === false ? 'error' : this.color]: this.color || this.valid === false
286
+ }
287
+ },
288
+
226
289
  inputWrapClasses () {
227
290
  return {
228
291
  [this.valid === false ? 'error' : this.color]: this.color || this.valid === false,
@@ -235,7 +298,8 @@ export default {
235
298
  'w-input__input-wrap--underline': !this.outline,
236
299
  'w-input__input-wrap--shadow': this.shadow,
237
300
  'w-input__input-wrap--no-padding': !this.outline && !this.bgColor && !this.shadow && !this.round,
238
- 'w-input__input-wrap--loading': this.loading
301
+ 'w-input__input-wrap--loading': this.loading || (this.showProgress && this.uploadInProgress),
302
+ 'w-input__input-wrap--upload-complete': this.uploadComplete
239
303
  }
240
304
  }
241
305
  },
@@ -260,35 +324,44 @@ export default {
260
324
  // For file input.
261
325
  onFileChange (e) {
262
326
  this.inputFiles = [...e.target.files].map(original => {
263
- const [, base, extension] = original.name.match(/^(.*)(\..*?)$/)
327
+ // `full` if there is no filename but only an extension.
328
+ const [, base = '', extension = '', full = ''] = original.name.match(/^(.*?)\.([^.]*)$|(.*)/)
329
+
264
330
  const file = reactive({
265
331
  name: original.name,
266
- base,
332
+ base: base || full,
267
333
  extension,
268
334
  type: original.type,
269
335
  size: original.size,
270
336
  lastModified: original.lastModified,
271
337
  preview: null,
272
- progress: 0
338
+ progress: 0,
339
+ file: original
273
340
  })
274
341
 
275
- this.filePreview(original, file)
342
+ this.readFile(original, file)
276
343
 
277
344
  return file
278
345
  })
279
346
  this.$emit('update:modelValue', this.inputFiles)
347
+ this.$emit('input', this.inputFiles)
280
348
  },
281
349
 
282
350
  // For file input.
283
- filePreview (original, file) {
351
+ readFile (original, file) {
284
352
  const reader = new FileReader()
285
353
 
354
+ // If the preview prop is a string, the user is setting the preview to an icon and
355
+ // don't need the actual file preview.
356
+ const isPreviewAnIcon = typeof this.preview === 'string'
357
+ const isFileAnImage = original.type && original.type.startsWith('image/')
286
358
  // Check if the file is an image and set a preview image.
287
- if (original.type && original.type.startsWith('image/')) {
359
+ if (this.preview && !isPreviewAnIcon && isFileAnImage) {
288
360
  reader.addEventListener('load', e => {
289
361
  file.preview = e.target.result
290
362
  })
291
363
  }
364
+ else delete file.preview
292
365
 
293
366
  // Used to display a spinner while the file is loading.
294
367
  reader.addEventListener('progress', e => {
@@ -303,7 +376,7 @@ export default {
303
376
  // On page load, check if the field is autofilled by the browser.
304
377
  // 20211229. Only a problem on Chrome. Firefox ok, Safari always prompts before filling up.
305
378
  setTimeout(() => {
306
- if (this.$refs.input.matches(':-webkit-autofill')) this.isAutofilled = true
379
+ if (this.$refs.input && this.$refs.input.matches(':-webkit-autofill')) this.isAutofilled = true
307
380
  }, 400) // Can't be less than 350: time for the browser to autofill.
308
381
  },
309
382
 
@@ -331,6 +404,8 @@ $inactive-color: #777;
331
404
  &--file {
332
405
  flex-wrap: nowrap;
333
406
  align-items: flex-end;
407
+
408
+ span.fade-leave-to {position: absolute;}
334
409
  }
335
410
 
336
411
  // Input field wrapper.
@@ -360,7 +435,10 @@ $inactive-color: #777;
360
435
  &--round {border-radius: 99em;}
361
436
  &--tile {border-radius: initial;}
362
437
  &--shadow {box-shadow: $box-shadow;}
363
- &--loading {border-bottom-color: transparent;}
438
+ &--loading, &--upload-complete {
439
+ border-bottom-color: transparent;
440
+ flex-wrap: wrap;
441
+ }
364
442
  &--loading ~ .w-progress {
365
443
  height: 2px;
366
444
  position: absolute;
@@ -369,7 +447,8 @@ $inactive-color: #777;
369
447
  }
370
448
 
371
449
  .w-input--focused & {border-color: currentColor;}
372
- .w-input--focused &--loading {border-bottom-color: transparent;}
450
+ .w-input--focused &--loading,
451
+ .w-input--focused &--upload-complete {border-bottom-color: transparent;}
373
452
 
374
453
  // Underline.
375
454
  &--underline:after {
@@ -480,6 +559,8 @@ $inactive-color: #777;
480
559
  margin-left: 4px;
481
560
  max-height: 2em;
482
561
  align-self: flex-end;
562
+
563
+ &.w-icon {margin-bottom: 4px;}
483
564
  }
484
565
 
485
566
  // Icons inside.