wave-ui 1.66.0 → 1.67.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.66.0",
3
+ "version": "1.67.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",
@@ -46,12 +46,14 @@
46
46
  "build": "vite build --base /wave-ui/",
47
47
  "build-bundle": "BUNDLE=true vite build && mv ./dist/style.css ./dist/wave-ui.css",
48
48
  "serve": "vite preview --base /wave-ui/",
49
- "lint": "vite lint"
49
+ "lint": "vite lint",
50
+ "publish": "npm run build && npm run build-bundle && git add . && git commit -m 'Publish documentation on Github.' && git push && git push --tag && npm publish --tag latest"
50
51
  },
51
52
  "devDependencies": {
52
- "@babel/core": "^7.20.5",
53
- "@babel/eslint-parser": "^7.19.1",
53
+ "@babel/core": "^7.22.10",
54
+ "@babel/eslint-parser": "^7.22.10",
54
55
  "@babel/plugin-proposal-class-properties": "^7.18.6",
56
+ "@faker-js/faker": "^8.0.2",
55
57
  "@mdi/font": "^6.9.96",
56
58
  "@vitejs/plugin-vue": "^1.10.2",
57
59
  "autoprefixer": "^10.4.13",
@@ -68,7 +70,7 @@
68
70
  "gsap": "^3.11.3",
69
71
  "ionicons": "^4.6.3",
70
72
  "material-design-icons": "^3.0.1",
71
- "postcss": "^8.4.19",
73
+ "postcss": "^8.4.27",
72
74
  "pug": "^3.0.2",
73
75
  "rollup-plugin-delete": "^2.0.0",
74
76
  "sass": "^1.56.1",
@@ -28,7 +28,7 @@ export { default as WProgress } from './w-progress.vue'
28
28
  export { default as WRadio } from './w-radio.vue'
29
29
  export { default as WRadios } from './w-radios.vue'
30
30
  export { default as WRating } from './w-rating.vue'
31
- export { default as WScrollbar } from './w-scrollbar.vue'
31
+ export { default as WScrollable } from './w-scrollable.vue'
32
32
  export { default as WSelect } from './w-select.vue'
33
33
  export { default as WSlider } from './w-slider.vue'
34
34
  export { default as WSpinner } from './w-spinner.vue'
@@ -163,7 +163,7 @@ $spinner-size: 40;
163
163
  z-index: 1;
164
164
  // Background-color must not transition to not affect the hover & focus states
165
165
  // in :before & :after.
166
- transition: $transition-duration, background-color 0s, padding 0s;
166
+ transition: all $transition-duration, background-color 0s, padding 0s;
167
167
  -webkit-tap-highlight-color: transparent;
168
168
 
169
169
  // In w-flex wrapper, allow overriding the default `align-self: center`.
@@ -252,10 +252,10 @@ $spinner-size: 40;
252
252
  // Button states.
253
253
  // ------------------------------------------------------
254
254
  // Hover & focus states - inside button.
255
- &:hover:before, &:focus:before {opacity: 0.08;}
256
- &--dark:hover:before, &--dark:focus:before {opacity: 0.2;}
257
- &--outline:hover:before, &--outline:focus:before,
258
- &--text:hover:before, &--text:focus:before {opacity: 0.12;}
255
+ &:hover:before, &:focus-visible:before {opacity: 0.2;}
256
+ &--dark:hover:before, &--dark:focus-visible:before {opacity: 0.4;}
257
+ &--outline:hover:before, &--outline:focus-visible:before,
258
+ &--text:hover:before, &--text:focus-visible:before {opacity: 0.12;}
259
259
 
260
260
  // Focus state - outside button.
261
261
  &:after {
@@ -272,22 +272,25 @@ $spinner-size: 40;
272
272
  transition: opacity 0.2s cubic-bezier(0.45, 0.05, 0.55, 0.95), transform 0.25s ease-in;
273
273
  transform: scale(0.85, 0.7);
274
274
  }
275
- &:focus:after {
275
+ &:focus-visible:after {
276
276
  opacity: 0.4;
277
277
  transform: scale(1);
278
278
  transition: opacity 0.2s cubic-bezier(0.45, 0.05, 0.55, 0.95), transform 0.25s ease-out;
279
279
  }
280
- &--dark:focus:after {opacity: 0.2;}
280
+ &--dark:focus-visible:after {opacity: 0.2;}
281
281
 
282
282
  // Active state.
283
- &:active:before {opacity: 0.2;}
284
- &--dark:active:before, &.primary--bg:active:before {opacity: 0.25;}
283
+ &:active {transform: scale(1.02);}
284
+ &:active:before {opacity: 0.3;}
285
+ &--dark:active:before, &.primary--bg:active:before {opacity: 0.35;}
285
286
 
286
287
  // Disable visual feedback on loading and disabled buttons.
287
288
  &--loading:hover:before,
288
- &--loading:focus:before,
289
+ &--loading:focus-visible:before,
289
290
  &--loading:active:before,
290
291
  &[disabled]:before {opacity: 0;}
292
+ &--loading:active,
293
+ &[disabled] {transform: none;}
291
294
  // ------------------------------------------------------
292
295
 
293
296
  // Disable events binding on nested content.
@@ -303,6 +306,7 @@ $spinner-size: 40;
303
306
  align-items: center;
304
307
  justify-content: center;
305
308
  background: inherit;
309
+ border-radius: inherit;
306
310
 
307
311
  svg {height: 75%;}
308
312
  circle {
@@ -2,9 +2,12 @@
2
2
  .w-card(:class="classes")
3
3
  .w-card__title(
4
4
  v-if="$slots.title"
5
- :class="{ 'w-card__title--has-toolbar': titleHasToolbar, ...titleClasses }")
5
+ :class="{ 'w-card__title--has-toolbar': $slots.title && titleHasToolbar, ...titleClasses }")
6
6
  slot(name="title")
7
- .w-card__title(v-else-if="title" :class="titleClasses" v-html="title")
7
+ .w-card__title(
8
+ v-else-if="title"
9
+ v-html="title"
10
+ :class="{ 'w-card__title--has-toolbar': title && titleHasToolbar, ...titleClasses }")
8
11
  w-image.w-card__image(v-if="image" :src="image" v-bind="imgProps")
9
12
  slot(name="image-content")
10
13
  .w-card__content(:class="contentClasses")
@@ -94,12 +97,17 @@ export default {
94
97
  padding: (2 * $base-increment) (3 * $base-increment);
95
98
  font-size: 1.3em;
96
99
  border-bottom: $border;
97
- border-top-left-radius: inherit;
98
- border-top-right-radius: inherit;
99
100
 
100
101
  &--has-toolbar {padding: 0;border-bottom: none;}
101
102
  }
102
103
 
104
+ // When there is no title apply the border radius to the image.
105
+ &__image:first-child {
106
+ border-top-left-radius: inherit;
107
+ border-top-right-radius: inherit;
108
+ overflow: hidden;
109
+ }
110
+
103
111
  &__content {
104
112
  padding: 3 * $base-increment;
105
113
  flex-grow: 1;
@@ -128,7 +128,13 @@ export default {
128
128
  icon: this.icon,
129
129
  tooltip: label,
130
130
  tooltipProps,
131
- ...this.mainButton
131
+ ...this.mainButton,
132
+ ...this.$attrs,
133
+ // Vue 2 specific:
134
+ // The classes and styles are not in $attrs. Add them from $vnode.data.staticClass, so
135
+ // the button gets these classes wen used with tooltip.
136
+ // https://v2.vuejs.org/v2/guide/components-props.html#Disabling-Attribute-Inheritance
137
+ class: [this.$vnode.data.staticClass, this.$vnode.data.class]
132
138
  }
133
139
  }
134
140
  },
@@ -11,7 +11,7 @@
11
11
  //- Background first, in SVG there is no z-index.
12
12
  circle.bg(
13
13
  v-if="bgColor || this.progressValue > -1"
14
- :class="bgColor"
14
+ :class="bgColor || null"
15
15
  :cx="circleCenter"
16
16
  :cy="circleCenter"
17
17
  :r="circleRadius"
@@ -145,7 +145,8 @@ export default {
145
145
  'w-rating__button--half': isHalf,
146
146
  [this.icon]: true,
147
147
  [`size--${this.size}`]: true,
148
- [isOn ? this.color : this.bgColor]: true
148
+ [this.color]: isOn,
149
+ [this.bgColor]: this.bgColor && !isOn
149
150
  }
150
151
  }
151
152
  },
@@ -0,0 +1,243 @@
1
+ <template lang="pug">
2
+ .w-scrollable(
3
+ ref="scrollable"
4
+ @mouseenter="onMouseEnter"
5
+ @mouseleave="onMouseLeave"
6
+ @mousewheel="onMouseWheel"
7
+ :class="scrollableClasses"
8
+ v-bind="$attrs"
9
+ :style="scrollableStyles")
10
+ slot
11
+ .w-scrollbar(ref="track" @mousedown="onTrackMouseDown" :class="scrollbarClasses")
12
+ .w-scrollbar__thumb(ref="thumb" :style="thumbStyles")
13
+ </template>
14
+
15
+ <script>
16
+ const domProps = {
17
+ h: {
18
+ horizOrVert: 'horizontal',
19
+ topOrLeft: 'left',
20
+ widthOrHeight: 'width',
21
+ offsetWidthOrHeight: 'offsetWidth',
22
+ maxWidthOrHeight: 'max-width',
23
+ scrollWidthOrHeight: 'scrollWidth',
24
+ clientXorY: 'clientX',
25
+ deltaXorY: 'deltaX',
26
+ scrollTopOrLeft: 'scrollLeft'
27
+ },
28
+ v: {
29
+ horizOrVert: 'vertical',
30
+ topOrLeft: 'top',
31
+ widthOrHeight: 'height',
32
+ offsetWidthOrHeight: 'offsetHeight',
33
+ maxWidthOrHeight: 'max-height',
34
+ scrollWidthOrHeight: 'scrollHeight',
35
+ clientXorY: 'clientY',
36
+ deltaXorY: 'deltaY',
37
+ scrollTopOrLeft: 'scrollTop'
38
+ }
39
+ }
40
+
41
+ export default {
42
+ name: 'w-scrollable',
43
+ props: {
44
+ color: { type: String, default: 'primary' },
45
+ bgColor: { type: String },
46
+ width: { type: [Number, String] },
47
+ height: { type: [Number, String] }
48
+ },
49
+
50
+ emits: [],
51
+
52
+ data: () => ({
53
+ mounted: false,
54
+ scrollable: {
55
+ top: null,
56
+ left: null,
57
+ hovered: false
58
+ },
59
+ scrollValuePercent: 0
60
+ }),
61
+
62
+ computed: {
63
+ isHorizontal () {
64
+ if (!this.mounted) return false
65
+ console.log('💂‍♂️', this.$refs.scrollable?.scrollWidth, this.$refs.scrollable?.offsetWidth)
66
+ return (this.width && !this.height) || (this.$refs.scrollable?.scrollWidth > this.$refs.scrollable?.offsetWidth)
67
+ },
68
+
69
+ m () { // m = shorthand for map of DOM properties.
70
+ return domProps[this.isHorizontal ? 'h' : 'v']
71
+ },
72
+
73
+ scrollableClasses () {
74
+ return {
75
+ [`w-scrollable--${this.m.horizOrVert}`]: true
76
+ }
77
+ },
78
+
79
+ scrollbarClasses () {
80
+ return {
81
+ [`w-scrollbar--${this.m.horizOrVert}`]: true
82
+ }
83
+ },
84
+
85
+ thumbSizePercent () {
86
+ if (!this.mounted) return 0
87
+ console.log('😒', this[this.m.widthOrHeight], this.$refs.scrollable[[this.m.offsetWidthOrHeight]])
88
+ const widthOrHeight = this[this.m.widthOrHeight] ?? this.$refs.scrollable[[this.m.offsetWidthOrHeight]]
89
+ // if (widthOrHeight === undefined) widthOrHeight = this.$refs.scrollable.offsetWidthOrHeight
90
+ return (widthOrHeight * 100 / this.$refs.scrollable?.[this.m.scrollWidthOrHeight]) || 0
91
+ },
92
+
93
+ scrollableStyles () {
94
+ return {
95
+ [this.m.maxWidthOrHeight]: `${this[this.m.widthOrHeight]}px`
96
+ }
97
+ },
98
+
99
+ thumbStyles () {
100
+ let topOrLeftValue = this.scrollValuePercent
101
+ topOrLeftValue = Math.max(0, Math.min(topOrLeftValue, 100 - this.thumbSizePercent))
102
+ return {
103
+ [this.m.widthOrHeight]: `${this.thumbSizePercent}%`,
104
+ [this.m.topOrLeft]: `${topOrLeftValue}%`
105
+ }
106
+ }
107
+ },
108
+
109
+ methods: {
110
+ onTrackMouseDown (e) {
111
+ if (this.isDisabled || this.isReadonly) return
112
+ // On touch screen don't listen for both touchstart & mousedown.
113
+ if ('ontouchstart' in window && e.type === 'mousedown') return
114
+
115
+ const { top, left, width, height } = this.$refs.track.getBoundingClientRect()
116
+ if (this.isHorizontal) {
117
+ this.$refs.track.width = width
118
+ this.$refs.track.left = left
119
+ }
120
+ else {
121
+ this.$refs.track.height = height
122
+ this.$refs.track.top = top
123
+ }
124
+ this.dragging = true
125
+
126
+ this.computeScroll(e.type === 'touchstart' ? e.touches[0][this.m.clientXorY] : e[this.m.clientXorY])
127
+ this.scroll()
128
+
129
+ document.addEventListener(e.type === 'touchstart' ? 'touchmove' : 'mousemove', this.onDrag)
130
+ document.addEventListener(e.type === 'touchstart' ? 'touchend' : 'mouseup', this.onMouseUp, { once: true })
131
+ },
132
+
133
+ onDrag (e) {
134
+ this.computeScroll((e.type === 'touchmove' ? e.touches[0][this.m.clientXorY] : e[this.m.clientXorY]))
135
+ this.scroll()
136
+ },
137
+
138
+ onMouseUp (e) {
139
+ this.dragging = false
140
+ document.removeEventListener(e.type === 'touchend' ? 'touchmove' : 'mousemove', this.onDrag)
141
+ if (this.$refs.thumb) this.$refs.thumb.focus()
142
+ },
143
+
144
+ onMouseEnter () {
145
+ this.scrollable.hovered = true
146
+ },
147
+
148
+ onMouseLeave () {
149
+ this.scrollable.hovered = false
150
+ },
151
+
152
+ onMouseWheel (e) {
153
+ if (!this.scrollable.hovered) return // Only scroll a w-scrollable element that is being hovered.
154
+
155
+ // When scrolling beyond limits, release the mousewheel and scroll the parent.
156
+ if (this.scrollValuePercent <= 0 && e[this.m.deltaXorY] < 0) return
157
+ else if (this.scrollValuePercent >= 100 - this.thumbSizePercent && e[this.m.deltaXorY] > 0) return
158
+
159
+ e.preventDefault() // Hold the scroll in the hovered w-scrollable element.
160
+
161
+ this.scrollValuePercent += e[this.m.deltaXorY] * 0.05
162
+ this.scrollValuePercent = Math.max(0, Math.min(this.scrollValuePercent, 100))
163
+ this.scroll()
164
+ },
165
+
166
+ computeScroll (cursorPositionXorY) {
167
+ const { top, left, width, height } = this.$refs.scrollable.getBoundingClientRect()
168
+ const topOrLeft = this.isHorizontal ? left : top
169
+ const widthOrHeight = this.isHorizontal ? width : height
170
+ this.scrollValuePercent = Math.max(0, Math.min(((cursorPositionXorY - topOrLeft) / widthOrHeight) * 100, 100))
171
+ },
172
+
173
+ scroll () {
174
+ this.$refs.scrollable[this.m.scrollTopOrLeft] = this.scrollValuePercent * this.$refs.scrollable?.[this.m.scrollWidthOrHeight] / 100
175
+ this.updateThumbPosition()
176
+ },
177
+
178
+ updateThumbPosition () {
179
+ this.$refs.thumb.style[this.m.topOrLeft] = this.scrollValuePercent
180
+ }
181
+ },
182
+
183
+ mounted () {
184
+ this.mounted = true
185
+ const { top, left } = this.$refs.scrollable.getBoundingClientRect()
186
+ this.scrollable.top = top
187
+ this.scrollable.left = left
188
+
189
+ this.$el.parentNode.style.position = 'relative'
190
+ this.$el.parentNode.style[this.m.maxWidthOrHeight] = `${this[this.m.widthOrHeight]}px`
191
+ }
192
+ }
193
+ </script>
194
+
195
+ <style lang="scss">
196
+ .w-scrollable {
197
+ position: relative;
198
+ overflow: hidden;
199
+ }
200
+
201
+ .w-scrollbar {
202
+ position: absolute;
203
+ background: #000;
204
+ user-select: none;
205
+
206
+ &--horizontal {
207
+ left: 0;
208
+ right: 0;
209
+ bottom: 0;
210
+ height: 8px;
211
+ }
212
+ &--vertical {
213
+ top: 0;
214
+ bottom: 0;
215
+ right: 0;
216
+ width: 8px;
217
+ }
218
+
219
+ &__thumb {
220
+ position: absolute;
221
+ background: #333;
222
+ border-radius: $border-radius;
223
+ z-index: 1;
224
+ will-change: top left;
225
+
226
+ &:hover {background: #444;}
227
+ }
228
+ &--horizontal &__thumb {
229
+ height: 6px;
230
+ left: 0;
231
+ right: 0;
232
+ margin-top: 1px;
233
+ margin-bottom: 1px;
234
+ }
235
+ &--vertical &__thumb {
236
+ width: 6px;
237
+ top: 0;
238
+ bottom: 0;
239
+ margin-left: 1px;
240
+ margin-right: 1px;
241
+ }
242
+ }
243
+ </style>
@@ -2,7 +2,7 @@
2
2
  component(
3
3
  ref="formEl"
4
4
  :is="formRegister ? 'w-form-element' : 'div'"
5
- v-bind="formRegister && { validators, inputValue: selectionString, disabled: isDisabled, readonly: isReadonly }"
5
+ v-bind="formRegister && { validators, inputValue: selectionString, disabled: isDisabled, readonly: isReadonly, isFocused }"
6
6
  :valid.sync="valid"
7
7
  @reset="onReset"
8
8
  :wrap="hasLabel && labelPosition !== 'inside'"
@@ -16,6 +16,7 @@ component(
16
16
 
17
17
  w-menu(
18
18
  v-model="showMenu"
19
+ @close="!$event && closeMenu()"
19
20
  :menu-class="`w-select__menu ${menuClass || ''}`"
20
21
  transition="slide-fade-down"
21
22
  :append-to="(menuProps || {}).appendTo !== undefined ? (menuProps || {}).appendTo : undefined"
@@ -26,7 +27,7 @@ component(
26
27
  template(#activator="{ on }")
27
28
  //- Input wrapper.
28
29
  .w-select__selection-wrap(
29
- @click="!isDisabled && !isReadonly && (showMenu ? closeMenu : openMenu)()"
30
+ @click="!isDisabled && !isReadonly && onInputFieldClick()"
30
31
  role="button"
31
32
  aria-haspopup="listbox"
32
33
  :aria-expanded="showMenu ? 'true' : 'false'"
@@ -76,7 +77,7 @@ component(
76
77
  @item-click="$emit('item-click', $event)"
77
78
  @item-select="onListItemSelect"
78
79
  @keydown:enter="noUnselect && !multiple && closeMenu()"
79
- @keydown:escape="closeMenu"
80
+ @keydown:escape="showMenu && (this.showMenu = false) /* Will call closeMenu() from w-menu(@close). */"
80
81
  :value="inputValue"
81
82
  :items="selectItems"
82
83
  :multiple="multiple"
@@ -190,7 +191,7 @@ export default {
190
191
  },
191
192
  selectionString () {
192
193
  return this.inputValue && this.inputValue.map(
193
- item => item[this.itemValueKey] !== undefined ? item[this.itemLabelKey] : (item[this.itemLabelKey] !== undefined ? item[this.itemLabelKey] : item)
194
+ item => item[this.itemValueKey] !== undefined ? item[this.itemLabelKey] : (item[this.itemLabelKey] ?? item)
194
195
  ).join(', ')
195
196
  },
196
197
  classes () {
@@ -227,12 +228,18 @@ export default {
227
228
 
228
229
  methods: {
229
230
  onFocus (e) {
231
+ if (this.isFocused) return
232
+
230
233
  this.isFocused = true
231
234
  this.$emit('focus', e)
232
235
  return false
233
236
  },
234
237
 
235
238
  onBlur (e) {
239
+ // As long as the menu is open the focus is still on.
240
+ // When closing the menu, the focus is given back to the input (not blur yet).
241
+ if (this.showMenu) return
242
+
236
243
  this.isFocused = false
237
244
  this.$emit('blur', e)
238
245
  },
@@ -241,11 +248,10 @@ export default {
241
248
  // Forbid typing in contenteditable element.
242
249
  // Note: using contenteditable rather than input in order to be able to fit the select list
243
250
  // to its content with CSS. Only contenteditable divs/non-interactive elements can react to focus/blur ).
244
- e.preventDefault()
251
+ // still allow meta key & ctrl key combinations and the tab key (9).
252
+ if (!e.metaKey && !e.ctrlKey && e.keyCode !== 9) e.preventDefault()
245
253
 
246
- if ([13, 27, 38, 40].includes(e.keyCode)) e.preventDefault()
247
-
248
- if (e.keyCode === 27) this.closeMenu() // Escape.
254
+ if (e.keyCode === 27 && this.showMenu) this.closeMenu() // Escape.
249
255
  else if (e.keyCode === 13) this.openMenu() // Enter.
250
256
 
251
257
  // Up & down arrows.
@@ -284,12 +290,16 @@ export default {
284
290
  this.$emit('update:modelValue', selection)
285
291
  this.$emit('input', selection)
286
292
  },
293
+ onInputFieldClick () {
294
+ if (this.showMenu) this.showMenu = false // Will call `closeMenu()` from w-menu(@close).
295
+ else this.openMenu()
296
+ },
287
297
 
288
298
  // Called on item selection: on click & `enter` keydown.
289
299
  onListItemSelect (e) {
290
300
  this.$emit('item-select', e)
291
301
  // Close menu after selection on single select, but keep open if multiple.
292
- if (!this.multiple) this.closeMenu()
302
+ if (!this.multiple) this.showMenu = false // Will call `closeMenu()` from w-menu(@close).
293
303
  },
294
304
 
295
305
  onReset () {
@@ -332,7 +342,7 @@ export default {
332
342
 
333
343
  // Close the dropdown selection list.
334
344
  closeMenu () {
335
- if ((this.menuProps || {}).hideOnMenuClick === false) return
345
+ if (this.menuProps?.hideOnMenuClick === false) return
336
346
 
337
347
  this.showMenu = false
338
348
  // Set the focus back on the main w-select input.