wave-ui 1.68.0 → 1.69.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.68.0",
3
+ "version": "1.69.0",
4
4
  "description": "An emerging UI framework for Vue.js (2 & 3) with only the bright side. :sunny:",
5
5
  "author": "Antoni Andre <antoniandre.web@gmail.com>",
6
6
  "homepage": "https://antoniandre.github.io/wave-ui",
@@ -50,38 +50,38 @@
50
50
  "publish-doc": "npm run build && npm run build-bundle && git add . && git commit -m 'Publish documentation on Github.' && git push && git push --tag"
51
51
  },
52
52
  "devDependencies": {
53
- "@babel/core": "^7.22.10",
54
- "@babel/eslint-parser": "^7.22.10",
53
+ "@babel/core": "^7.23.2",
54
+ "@babel/eslint-parser": "^7.22.15",
55
55
  "@babel/plugin-proposal-class-properties": "^7.18.6",
56
- "@faker-js/faker": "^8.0.2",
56
+ "@faker-js/faker": "^8.2.0",
57
57
  "@mdi/font": "^6.9.96",
58
58
  "@vitejs/plugin-vue": "^1.10.2",
59
- "autoprefixer": "^10.4.13",
59
+ "autoprefixer": "^10.4.16",
60
60
  "axios": "^0.21.4",
61
- "core-js": "^3.26.1",
61
+ "core-js": "^3.33.2",
62
62
  "eslint": "^7.32.0",
63
63
  "eslint-config-standard": "^16.0.3",
64
- "eslint-plugin-import": "^2.26.0",
64
+ "eslint-plugin-import": "^2.29.0",
65
65
  "eslint-plugin-node": "^11.1.0",
66
66
  "eslint-plugin-promise": "^5.2.0",
67
67
  "eslint-plugin-vue": "^7.20.0",
68
68
  "font-awesome": "^4.7.0",
69
69
  "ghspa": "^1.0.0",
70
- "gsap": "^3.11.3",
70
+ "gsap": "^3.12.2",
71
71
  "ionicons": "^4.6.3",
72
72
  "material-design-icons": "^3.0.1",
73
- "postcss": "^8.4.27",
73
+ "postcss": "^8.4.31",
74
74
  "pug": "^3.0.2",
75
75
  "rollup-plugin-delete": "^2.0.0",
76
- "sass": "^1.56.1",
77
- "simple-syntax-highlighter": "^1.5.1",
76
+ "sass": "^1.69.5",
77
+ "simple-syntax-highlighter": "^1.6.2",
78
78
  "splitpanes": "^2.4.1",
79
- "vite": "^2.9.15",
79
+ "vite": "^2.9.16",
80
80
  "vite-plugin-vue2": "^1.9.3",
81
- "vue": "^2.7.14",
81
+ "vue": "^2.7.15",
82
82
  "vue-router": "^3.6.5",
83
83
  "vue-svg-loader": "^0.16.0",
84
- "vue-template-compiler": "^2.7.14",
84
+ "vue-template-compiler": "^2.7.15",
85
85
  "vueperslides": "^2.16.0",
86
86
  "vuex": "^3.6.2"
87
87
  }
@@ -281,7 +281,10 @@ $spinner-size: 40;
281
281
 
282
282
  // Active state.
283
283
  &:active {transform: scale(1.02);}
284
- &:active:before {opacity: 0.3;}
284
+ &:active:before {
285
+ opacity: 0.3;
286
+ @include default-transition($fast-transition-duration);
287
+ }
285
288
  &--dark:active:before, &.primary--bg:active:before {opacity: 0.35;}
286
289
 
287
290
  // Disable visual feedback on loading and disabled buttons.
@@ -6,6 +6,8 @@ component(v-if="tooltip" is="w-tooltip" v-bind="tooltipProps || {}")
6
6
  div(v-html="tooltip")
7
7
  button-partial(v-else v-bind="buttonProps" v-on="$listeners")
8
8
  slot
9
+ template(#loading)
10
+ slot(name="loading")
9
11
  </template>
10
12
 
11
13
  <script>
@@ -105,8 +105,9 @@ export default {
105
105
  methods: {
106
106
  onInput () {
107
107
  this.isChecked = !this.isChecked
108
- this.$emit('update:modelValue', this.isChecked)
109
- this.$emit('input', this.isChecked)
108
+ const returnValue = this.isChecked && this.returnValue !== undefined ? this.returnValue : this.isChecked
109
+ this.$emit('update:modelValue', returnValue)
110
+ this.$emit('input', returnValue)
110
111
 
111
112
  if (!this.noRipple) {
112
113
  if (this.isChecked) {
@@ -143,11 +144,8 @@ $inactive-color: #666;
143
144
  vertical-align: middle;
144
145
  // Contain the hidden radio button, so browser doesn't pan to it when outside of the screen.
145
146
  position: relative;
146
- cursor: pointer;
147
147
  -webkit-tap-highlight-color: transparent;
148
148
 
149
- &--disabled {cursor: not-allowed;}
150
-
151
149
  // The hidden real checkbox.
152
150
  input[type="checkbox"] {
153
151
  position: absolute;
@@ -165,8 +163,10 @@ $inactive-color: #666;
165
163
  flex: 0 0 auto; // Prevent stretching width or height.
166
164
  align-items: center;
167
165
  justify-content: center;
168
- cursor: inherit;
166
+ cursor: pointer;
169
167
  z-index: 0;
168
+
169
+ .w-checkbox--disabled & {cursor: not-allowed;}
170
170
  }
171
171
 
172
172
  // The checkmark - visible when checked.
@@ -260,10 +260,13 @@ $inactive-color: #666;
260
260
  &__label {
261
261
  display: flex;
262
262
  align-items: center;
263
- cursor: inherit;
263
+ cursor: pointer;
264
264
  user-select: none;
265
265
 
266
- .w-checkbox--disabled & {opacity: 0.7;}
266
+ .w-checkbox--disabled & {
267
+ cursor: not-allowed;
268
+ opacity: 0.7;
269
+ }
267
270
  }
268
271
  }
269
272
 
@@ -38,6 +38,9 @@ export default {
38
38
  props: {
39
39
  items: { type: Array, required: true }, // All the possible options.
40
40
  value: { type: Array }, // v-model on selected option.
41
+ // If true, the returnValue set on each w-checkboxes item will be returned once the checkbox is
42
+ // checked. If false & by default, the return value of the w-checkboxes is an array of booleans.
43
+ returnValues: { type: Boolean },
41
44
  labelOnLeft: { type: Boolean },
42
45
  itemLabelKey: { type: String, default: 'label' },
43
46
  itemValueKey: { type: String, default: 'value' },
@@ -92,7 +95,7 @@ export default {
92
95
 
93
96
  toggleCheck (checkbox, isChecked) {
94
97
  checkbox._isChecked = isChecked
95
- const selection = this.checkboxItems.filter(item => item._isChecked).map(item => item.value)
98
+ const selection = this.checkboxItems.filter(item => item._isChecked).map(item => this.returnValues ? item.returnValue : item.value)
96
99
 
97
100
  this.$emit('update:modelValue', selection)
98
101
  this.$emit('input', selection)
@@ -61,6 +61,7 @@ component(
61
61
  @change="onFileChange"
62
62
  :multiple="multiple || null"
63
63
  v-bind="attrs"
64
+ :disabled="isDisabled || null"
64
65
  :data-progress="overallFilesProgress /* Needed to emit the overallProgress. */")
65
66
  transition-group.w-input__input.w-input__input--file(
66
67
  tag="label"
@@ -558,6 +559,7 @@ $inactive-color: #777;
558
559
  &__label {
559
560
  transition: color $transition-duration;
560
561
  cursor: pointer;
562
+ user-select: none;
561
563
 
562
564
  &--left {margin-right: 2 * $base-increment;}
563
565
  &--right {margin-left: 2 * $base-increment;}
@@ -229,8 +229,8 @@ export default {
229
229
  'w-list__item-label--focused': item._focused,
230
230
  'w-list__item-label--hoverable': this.hover,
231
231
  'w-list__item-label--selectable': this.isSelectable,
232
- [item.color]: !!item.color,
233
- [this.SelectionColor]: item._selected && !item.color && this.SelectionColor,
232
+ [item[this.itemColorKey]]: !!item[this.itemColorKey],
233
+ [this.SelectionColor]: item._selected && !item[this.itemColorKey] && this.SelectionColor,
234
234
  [item[this.itemClassKey] || this.itemClass]: item[this.itemClassKey] || this.itemClass
235
235
  }
236
236
  },
@@ -406,11 +406,11 @@ export default {
406
406
  // Reset the selections when single selection allowed for w-select.
407
407
  if (!this.isMultipleSelect) this.listItems.forEach(item => (item._selected = false))
408
408
 
409
- this.checkSelection(selection) // Create an array with the selected values.
410
- .forEach(val => {
411
- const foundItem = this.listItems.find(item => item._value === val)
412
- if (foundItem) foundItem._selected = true
413
- })
409
+ const selectedItems = this.checkSelection(selection) // Create an array with the selected values.
410
+ // Update which items are selected or not.
411
+ this.listItems.forEach(item => {
412
+ item._selected = selectedItems.find(val => item._value === val) !== undefined
413
+ })
414
414
  }
415
415
  },
416
416
 
@@ -79,6 +79,7 @@ export default {
79
79
  contentClass: { type: [String, Object, Array] },
80
80
  arrow: { type: Boolean }, // The small triangle pointing toward the activator.
81
81
  minWidth: { type: [Number, String] }, // can be like: `40`, `5em`, `activator`.
82
+ maxWidth: { type: [Number, String] }, // can be like: `40`, `5em`, `activator`.
82
83
  overlay: { type: Boolean },
83
84
  overlayClass: { type: [String, Object, Array] },
84
85
  overlayProps: { type: Object }, // Allow passing down an object of props to the w-overlay component.
@@ -132,6 +133,11 @@ export default {
132
133
  else return isNaN(this.minWidth) ? this.minWidth : (this.minWidth ? `${this.minWidth}px` : 0)
133
134
  },
134
135
 
136
+ menuMaxWidth () {
137
+ if (this.maxWidth === 'activator') return this.activatorWidth ? `${this.activatorWidth}px` : 0
138
+ else return isNaN(this.maxWidth) ? this.maxWidth : (this.maxWidth ? `${this.maxWidth}px` : 0)
139
+ },
140
+
135
141
  menuClasses () {
136
142
  return objectifyClasses(this.menuClass)
137
143
  },
@@ -174,7 +180,8 @@ export default {
174
180
  top: (this.detachableCoords.top && `${~~this.detachableCoords.top}px`) || null,
175
181
  left: (this.detachableCoords.left && `${~~this.detachableCoords.left}px`) || null,
176
182
  minWidth: (this.minWidth && this.menuMinWidth) || null,
177
- '--w-menu-bg-color': this.arrow && this.$waveui.colors[this.bgColor || 'white']
183
+ maxWidth: (this.maxWidth && this.menuMaxWidth) || null,
184
+ '--w-menu-bg-color': this.arrow && (this.$waveui.colors[this.bgColor] || 'rgb(var(--w-base-bg-color-rgb))')
178
185
  }
179
186
  },
180
187
 
@@ -254,6 +261,10 @@ export default {
254
261
  * even while hovering the menu.
255
262
  */
256
263
  async close (force = false) {
264
+ // The user may open and close the detachable so fast (like when toggling on hover) that it
265
+ // should not show up at all. This cancels the opening timer (if there is a set delay prop).
266
+ this.openTimeout = clearTimeout(this.openTimeout)
267
+
257
268
  // Might be already closed.
258
269
  // E.g. showOnHover & hideOnMenuClick: on click, force hide then mouseleave is also firing.
259
270
  if (!this.detachableVisible) return
@@ -10,13 +10,13 @@ component(
10
10
  template(v-if="labelPosition === 'left'")
11
11
  label.w-select__label.w-select__label--left.w-form-el-shakable(
12
12
  v-if="$slots.default || label"
13
- :for="`w-select--${_uid}`"
13
+ @click="$refs['selection-input'].click()"
14
14
  :class="labelClasses")
15
15
  slot {{ label }}
16
16
 
17
17
  w-menu(
18
18
  v-model="showMenu"
19
- @close="!$event && closeMenu()"
19
+ @close="closeMenu"
20
20
  :menu-class="`w-select__menu ${menuClass || ''}`"
21
21
  transition="slide-fade-down"
22
22
  :append-to="(menuProps || {}).appendTo !== undefined ? (menuProps || {}).appendTo : undefined"
@@ -47,7 +47,6 @@ component(
47
47
  @focus="!isDisabled && !isReadonly && onFocus($event)"
48
48
  @blur="onBlur"
49
49
  @keydown="!isDisabled && !isReadonly && onKeydown($event)"
50
- :id="`w-select--${_uid}`"
51
50
  :class="{ 'w-select__selection--placeholder': !$scopedSlots.selection && !selectionString && placeholder }"
52
51
  :disabled="isDisabled || null"
53
52
  readonly
@@ -64,7 +63,6 @@ component(
64
63
  template(v-if="labelPosition === 'inside' && showLabelInside")
65
64
  label.w-select__label.w-select__label--inside.w-form-el-shakable(
66
65
  v-if="$slots.default || label"
67
- :for="`w-select--${_uid}`"
68
66
  :class="labelClasses")
69
67
  slot {{ label }}
70
68
  w-icon.w-select__icon.w-select__icon--inner-right(
@@ -77,7 +75,7 @@ component(
77
75
  @item-click="$emit('item-click', $event)"
78
76
  @item-select="onListItemSelect"
79
77
  @keydown:enter="noUnselect && !multiple && closeMenu()"
80
- @keydown:escape="showMenu && (this.showMenu = false) /* Will call closeMenu() from w-menu(@close). */"
78
+ @keydown:escape="showMenu && (showMenu = false) /* Will call closeMenu() from w-menu(@close). */"
81
79
  :value="inputValue"
82
80
  :items="selectItems"
83
81
  :multiple="multiple"
@@ -103,7 +101,7 @@ component(
103
101
  template(v-if="labelPosition === 'right'")
104
102
  label.w-select__label.w-select__label--right.w-form-el-shakable(
105
103
  v-if="$slots.default || label"
106
- :for="`w-select--${_uid}`"
104
+ @click="$refs['selection-input'].click()"
107
105
  :class="labelClasses")
108
106
  slot {{ label }}
109
107
  </template>
@@ -252,7 +250,7 @@ export default {
252
250
  if (!e.metaKey && !e.ctrlKey && e.keyCode !== 9) e.preventDefault()
253
251
 
254
252
  if (e.keyCode === 27 && this.showMenu) this.closeMenu() // Escape.
255
- else if (e.keyCode === 13) this.openMenu() // Enter.
253
+ else if ([13, 32].includes(e.keyCode)) this.openMenu() // Enter or Space.
256
254
 
257
255
  // Up & down arrows.
258
256
  else if ([38, 40].includes(e.keyCode)) {
@@ -272,7 +270,25 @@ export default {
272
270
  index = (index + items.length + direction) % items.length
273
271
  }
274
272
 
275
- this.onInput(items[index])
273
+ // If the current item is disabled, find the next one enabled (forward or backward).
274
+ let allItemsAreDisabled = false
275
+ if (items[index].disabled) {
276
+ const direction = e.keyCode === 38 ? -1 : 1 // Prev or next.
277
+
278
+ // Modulo to prevent out of range; + items.length to also work with negative values.
279
+ let newIndex = (index + direction + items.length) % items.length
280
+ const itemsCount = items.length
281
+ let loop = 0 // While-safety: will always end at least after 1 full array cycle.
282
+ while (loop < itemsCount && items[newIndex].disabled) {
283
+ // Circle through the array of items forward or backward, and reloop when out of range.
284
+ newIndex = (newIndex + items.length + direction) % items.length
285
+ loop++
286
+ }
287
+ if (loop >= itemsCount) allItemsAreDisabled = true
288
+ index = newIndex
289
+ }
290
+
291
+ if (!allItemsAreDisabled) this.onInput(items[index])
276
292
  }
277
293
  }
278
294
  },
@@ -321,7 +337,7 @@ export default {
321
337
 
322
338
  return items.map(item => {
323
339
  let value = item
324
- if (typeof item === 'object') {
340
+ if (item && typeof item === 'object') { // `null` is also an object!
325
341
  value = item[this.itemValueKey] !== undefined ? item[this.itemValueKey] : (item[this.itemLabelKey] !== undefined ? item[this.itemLabelKey] : item)
326
342
  }
327
343
 
@@ -508,8 +524,8 @@ export default {
508
524
  -webkit-tap-highlight-color: transparent;
509
525
  }
510
526
 
511
- &--inner-left {left: 6px;}
512
- &--inner-right {right: 6px;}
527
+ &--inner-left {left: $base-increment;}
528
+ &--inner-right {right: $base-increment;}
513
529
  .w-select--no-padding &--inner-left {left: 1px;}
514
530
  .w-select--no-padding &--inner-right {right: 1px;}
515
531
 
@@ -526,6 +542,7 @@ export default {
526
542
  align-items: center;
527
543
  transition: color $transition-duration;
528
544
  cursor: pointer;
545
+ user-select: none;
529
546
 
530
547
  &--left {margin-right: 2 * $base-increment;}
531
548
  &--right {margin-left: 2 * $base-increment;}
@@ -553,7 +570,7 @@ export default {
553
570
  transform: translateY(-50%);
554
571
  pointer-events: none;
555
572
 
556
- .w-select--inner-icon-right & {padding-right: 22px;}
573
+ .w-select--inner-icon-right & {padding-right: 26px;}
557
574
 
558
575
  .w-select--no-padding & {
559
576
  left: 0;
@@ -570,8 +587,7 @@ export default {
570
587
  transition: $transition-duration ease;
571
588
  }
572
589
 
573
- // move label with underline style.
574
- .w-select--focused.w-select--floating-label &,
590
+ // Move label with underline style.
575
591
  .w-select--open.w-select--floating-label &,
576
592
  .w-select--filled.w-select--floating-label &,
577
593
  .w-select--has-placeholder.w-select--floating-label & {
@@ -582,13 +598,11 @@ export default {
582
598
  transform: translateY(-160%) scale(0.85);
583
599
  }
584
600
  // Move label with outline style or with shadow.
585
- .w-select--focused.w-select--floating-label .w-select__selection-wrap--box &,
586
601
  .w-select--open.w-select--floating-label .w-select__selection-wrap--box &,
587
602
  .w-select--filled.w-select--floating-label .w-select__selection-wrap--box &,
588
603
  .w-select--has-placeholder.w-select--floating-label .w-select__selection-wrap--box & {
589
604
  transform: translateY(-180%) scale(0.85);
590
605
  }
591
- .w-select--focused.w-select--floating-label.w-select--inner-icon-left &,
592
606
  .w-select--open.w-select--floating-label.w-select--inner-icon-left &,
593
607
  .w-select--filled.w-select--floating-label.w-select--inner-icon-left & {left: 0;}
594
608
  // Chrome & Safari - Must remain in a separated rule as Firefox discard the whole rule seeing -webkit-.
@@ -844,7 +844,7 @@ $tr-border-top: 1px;
844
844
  left: 0;
845
845
  right: 0;
846
846
  bottom: 0;
847
- background-color: var(--primary);
847
+ background-color: var(--w-primary-color);
848
848
  opacity: 0.2;
849
849
  pointer-events: none;
850
850
  }
@@ -326,6 +326,7 @@ $inactive-color: #777;
326
326
  transition: color $transition-duration;
327
327
  cursor: pointer;
328
328
  align-self: flex-start;
329
+ user-select: none;
329
330
 
330
331
  &--left {
331
332
  margin-top: $base-increment;
@@ -63,7 +63,7 @@ export default {
63
63
  .w-toolbar {
64
64
  position: relative;
65
65
  display: flex;
66
- flex: 1 1 auto;
66
+ flex: 0 1 auto; // No grow, so it doesn't stretch vertically in flex column.
67
67
  align-items: center;
68
68
  padding: (2 * $base-increment) (3 * $base-increment);
69
69
  background-color: #fff;
@@ -88,7 +88,6 @@ export default {
88
88
  &--vertical {
89
89
  padding: (2 * $base-increment);
90
90
  flex-direction: column;
91
- flex-grow: 0;
92
91
  flex-shrink: 0;
93
92
  }
94
93
 
@@ -42,7 +42,14 @@ export default {
42
42
  transition: { type: String },
43
43
  tooltipClass: { type: [String, Object, Array] },
44
44
  persistent: { type: Boolean },
45
- delay: { type: Number }
45
+ delay: { type: Number },
46
+ caption: { type: Boolean }, // Apply the caption class and style (grey, italic, small).
47
+ xs: { type: Boolean },
48
+ sm: { type: Boolean },
49
+ md: { type: Boolean },
50
+ lg: { type: Boolean },
51
+ xl: { type: Boolean },
52
+ enableTouch: { type: Boolean }
46
53
  // Other props in the detachable mixin:
47
54
  // appendTo, fixed, top, bottom, left, right, alignTop, alignBottom, alignLeft,
48
55
  // alignRight, noPosition, zIndex, activator.
@@ -81,6 +88,17 @@ export default {
81
88
  return this.transition || `w-tooltip-slide-fade-${direction}`
82
89
  },
83
90
 
91
+ size () {
92
+ return (
93
+ (this.xs && 'xs') ||
94
+ (this.sm && 'sm') ||
95
+ (this.sm && 'md') ||
96
+ (this.lg && 'lg') ||
97
+ (this.xl && 'xl') ||
98
+ (this.caption ? 'sm' : 'md') // if no size is set put md by default, or sm if caption is on.
99
+ )
100
+ },
101
+
84
102
  classes () {
85
103
  return {
86
104
  [this.color]: this.color,
@@ -90,6 +108,8 @@ export default {
90
108
  [`w-tooltip--align-${this.alignment}`]: !this.noPosition && this.alignment,
91
109
  'w-tooltip--tile': this.tile,
92
110
  'w-tooltip--round': this.round,
111
+ caption: this.caption,
112
+ [`size--${this.size}`]: true,
93
113
  'w-tooltip--shadow': this.shadow,
94
114
  'w-tooltip--fixed': this.fixed,
95
115
  'w-tooltip--no-border': this.noBorder || this.bgColor,
@@ -103,17 +123,21 @@ export default {
103
123
  zIndex: this.zIndex || this.zIndex === 0 || null,
104
124
  top: (this.detachableCoords.top && `${~~this.detachableCoords.top}px`) || null,
105
125
  left: (this.detachableCoords.left && `${~~this.detachableCoords.left}px`) || null,
106
- '--w-tooltip-bg-color': this.$waveui.colors[this.bgColor || 'white']
126
+ '--w-tooltip-bg-color': this.$waveui.colors[this.bgColor] || 'rgb(var(--w-base-bg-color-rgb))'
107
127
  }
108
128
  },
109
129
 
110
130
  activatorEventHandlers () {
111
131
  let handlers = {}
112
- if (this.showOnClick) handlers = { click: this.toggle }
113
- else {
132
+
133
+ // Check the window exists: SSR-proof.
134
+ const isTouchDevice = typeof window !== 'undefined' && 'ontouchstart' in window
135
+
136
+ // Toggling tooltip on mouseenter/mouseout (by default), and also show on focus, hide on blur.
137
+ if (!this.showOnClick && !isTouchDevice) {
114
138
  handlers = {
115
- focus: this.toggle,
116
- blur: this.toggle,
139
+ focus: this.open,
140
+ blur: this.close,
117
141
  mouseenter: e => {
118
142
  this.hoveringActivator = true
119
143
  this.open(e)
@@ -123,10 +147,10 @@ export default {
123
147
  this.close()
124
148
  }
125
149
  }
126
-
127
- // Check the window exists: SSR-proof.
128
- if (typeof window !== 'undefined' && 'ontouchstart' in window) handlers.click = this.toggle
129
150
  }
151
+ // Only bind a click event on mobile, or if showOnClick is set.
152
+ else if (this.enableTouch || this.showOnClick) handlers = { click: this.toggle }
153
+
130
154
  return handlers
131
155
  }
132
156
  },
@@ -146,8 +170,12 @@ export default {
146
170
  // ! \ This function uses the DOM - NO SSR (only trigger from beforeMount and later).
147
171
  toggle (e) {
148
172
  let shouldShowTooltip = this.detachableVisible
173
+
174
+ // For touch devices.
149
175
  if (typeof window !== 'undefined' && 'ontouchstart' in window) {
150
- if (e.type === 'click') shouldShowTooltip = !shouldShowTooltip
176
+ // disable tooltip opening for mouseenter activation.
177
+ if (!this.enableTouch && !this.showOnClick) shouldShowTooltip = false
178
+ else shouldShowTooltip = !shouldShowTooltip
151
179
  }
152
180
  else if (e.type === 'click' && this.showOnClick) shouldShowTooltip = !shouldShowTooltip
153
181
  else if (['mouseenter', 'focus'].includes(e.type) && !this.showOnClick) shouldShowTooltip = true
@@ -226,6 +254,12 @@ export default {
226
254
  &--left {margin-left: -3 * $base-increment;}
227
255
  &--right {margin-left: 3 * $base-increment;}
228
256
 
257
+ &.size--xs {font-size: 0.75rem;}
258
+ &.size--sm {font-size: 0.83rem;}
259
+ &.size--md {font-size: 0.9rem;}
260
+ &.size--lg {font-size: 1rem;}
261
+ &.size--xl {font-size: 1.1rem;}
262
+
229
263
  &--custom-transition {transform: none;}
230
264
 
231
265
  // Tooltip without border.
@@ -1,17 +1,51 @@
1
+ import { consoleWarn } from './utils/console'
1
2
  import { mergeConfig } from './utils/config'
2
3
  import NotificationManager from './utils/notification-manager'
3
4
  import { colorPalette, generateColorShades, flattenColors } from './utils/colors'
4
- // import * as directives from './directives'
5
+
6
+ // const detectOSDarkMode = $waveui => {
7
+ // const matchMedia = window.matchMedia('(prefers-color-scheme: dark)')
8
+ // $waveui.preferredTheme = matchMedia.matches ? 'dark' : 'light'
9
+ // $waveui.switchTheme($waveui.preferredTheme)
10
+
11
+ // matchMedia.addEventListener('change', event => {
12
+ // $waveui.preferredTheme = event.matches ? 'dark' : 'light'
13
+ // $waveui.switchTheme($waveui.preferredTheme)
14
+ // })
15
+ // }
5
16
 
6
17
  /**
7
18
  * Inject presets into a Vue component props defaults before its registration into the app.
19
+ * If a preset is not found in the given component props, try to find it in its mixins, if any.
8
20
  *
21
+ * @todo remove mixins-related code when stopping support for Vue 2.
9
22
  * @param {Object} component the Vue component to inject presets into.
10
23
  * @param {Object} presets the presets to inject. E.g. `{ bgColor: 'green' }`.
11
24
  */
12
25
  const injectPresets = (component, presets) => {
13
26
  for (const preset in presets) {
14
- component.props[preset].default = presets[preset]
27
+ // If we don't have the prop output a warning and continue.
28
+ if (!component.props?.[preset]) {
29
+ let foundProp = false
30
+ // Check to see if the prop exists on a mixin when it doesn't exist on the component.
31
+ // @todo: remove this check when there is no more Vue 2 and mixins: mixins are now deprecated.
32
+ if (Array.isArray(component.mixins) && component.mixins.length) {
33
+ // Loop through the array of mixin, and if we find the prop in one, update its default value.
34
+ for (const mixin of component.mixins) {
35
+ if (mixin?.props?.[preset]) {
36
+ mixin.props[preset].default = presets[preset]
37
+ foundProp = true
38
+ break
39
+ }
40
+ }
41
+
42
+ // If the given prop (= preset) is still not found in the mixins props raise warning.
43
+ if (!foundProp) consoleWarn(`Attempting to set a preset on a prop that doesn't exist: \`${component.name}.${preset}\`.`)
44
+ continue // Continue to the next preset.
45
+ }
46
+ }
47
+
48
+ else component.props[preset].default = presets[preset]
15
49
  }
16
50
  }
17
51
 
@@ -22,7 +22,8 @@ export default {
22
22
  noPosition: { type: Boolean },
23
23
  zIndex: { type: [Number, String, Boolean] },
24
24
  // Optionally designate an external activator.
25
- activator: { type: [String, Object] } // The activator can be a DOM string selector, a ref or a DOM node.
25
+ // The activator can be a DOM string selector, a ref or a DOM node.
26
+ activator: { type: [String, Object] }
26
27
  },
27
28
 
28
29
  inject: {
@@ -35,7 +36,11 @@ export default {
35
36
  // as is in an array so we can delete them on destroy.
36
37
  // This only applies to the activatorEventHandlers, the other events listeners can be removed
37
38
  // normally.
38
- docEventListenersHandlers: []
39
+ docEventListenersHandlers: [],
40
+ // The user may open and close the detachable so fast (like when toggling on hover) that it
41
+ // should not show up at all. Keep the ability to cancel the opening timer (if there is a set
42
+ // delay prop).
43
+ openTimeout: null
39
44
  }),
40
45
 
41
46
  computed: {
@@ -111,6 +116,13 @@ export default {
111
116
  (['left', 'right'].includes(this.position) && this.alignBottom && 'bottom') ||
112
117
  ''
113
118
  )
119
+ },
120
+
121
+ shouldShowOnClick () {
122
+ // For props simplicity, the w-tooltip component has the `showOnHover` prop,
123
+ // whereas the w-menu has `showOnClick`.
124
+ return (this.$options.props.showOnHover && !this.showOnHover) ||
125
+ (this.$options.props.showOnClick && this.showOnClick)
114
126
  }
115
127
  },
116
128
 
@@ -119,7 +131,10 @@ export default {
119
131
  async open (e) {
120
132
  // A tiny delay may help positioning the detachable correctly in case of multiple activators
121
133
  // with different menu contents.
122
- if (this.delay) await new Promise(resolve => setTimeout(resolve, this.delay))
134
+ if (this.delay) await new Promise(resolve => (this.openTimeout = setTimeout(resolve, this.delay)))
135
+
136
+ // Cancel opening if the timeout has been cancelled by blur event (when going fast).
137
+ if (this.delay && !this.openTimeout) return
123
138
 
124
139
  this.detachableVisible = true
125
140
 
@@ -336,7 +351,7 @@ export default {
336
351
  else {
337
352
  this.$nextTick(() => {
338
353
  if (this.activator) this.bindActivatorEvents()
339
- if (this.value) this.toggle({ type: 'click', target: this.activatorEl })
354
+ if (this.value) this.open({ target: this.activatorEl })
340
355
  })
341
356
  }
342
357
 
@@ -346,7 +361,10 @@ export default {
346
361
  wrapper.parentNode.insertBefore(this.overlayEl, wrapper)
347
362
  }
348
363
 
349
- if (this.value && this.activator) this.toggle({ type: 'click', target: this.activatorEl })
364
+ if (this.value && this.activator) {
365
+ this.toggle({ type: this.shouldShowOnClick ? 'click' : 'mouseenter', target: this.activatorEl })
366
+ }
367
+ else if (this.value) this.open({ target: this.activatorEl })
350
368
  },
351
369
 
352
370
  beforeDestroy () {
@@ -368,7 +386,10 @@ export default {
368
386
 
369
387
  watch: {
370
388
  value (bool) {
371
- if (!!bool !== this.detachableVisible) this.toggle({ type: 'click', target: this.activatorEl })
389
+ if (!!bool !== this.detachableVisible) {
390
+ if (bool) this.open({ target: this.activatorEl })
391
+ else this.close()
392
+ }
372
393
  },
373
394
  appendTo () {
374
395
  this.removeFromDOM()