wave-ui 3.20.0 → 3.21.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": "3.20.0",
3
+ "version": "3.21.0",
4
4
  "description": "A UI framework for Vue.js 3 (and 2) with only the bright side. :sunny:",
5
5
  "author": "Antoni Andre <antoniandre.web@gmail.com>",
6
6
  "homepage": "https://antoniandre.github.io/wave-ui",
@@ -57,7 +57,7 @@
57
57
  "devDependencies": {
58
58
  "@babel/core": "^7.26.10",
59
59
  "@biomejs/biome": "^1.9.4",
60
- "@faker-js/faker": "^9.6.0",
60
+ "@faker-js/faker": "^9.7.0",
61
61
  "@mdi/font": "^7.4.47",
62
62
  "@tsconfig/recommended": "^1.0.8",
63
63
  "@vitejs/plugin-vue": "^5.2.3",
@@ -71,11 +71,11 @@
71
71
  "postcss": "^8.5.3",
72
72
  "pug": "^3.0.3",
73
73
  "rollup-plugin-delete": "^3.0.1",
74
- "sass": "^1.86.2",
74
+ "sass": "^1.86.3",
75
75
  "simple-syntax-highlighter": "^3.1.1",
76
76
  "splitpanes": "^4.0.3",
77
- "typescript": "^5.8.2",
78
- "vite": "^6.2.4",
77
+ "typescript": "^5.8.3",
78
+ "vite": "^6.2.6",
79
79
  "vite-svg-loader": "^5.1.0",
80
80
  "vue": "^3.5.13",
81
81
  "vue-router": "^4.5.0",
@@ -1,18 +1,31 @@
1
1
  <template lang="pug">
2
2
  .w-scrollable(
3
- ref="scrollable"
4
3
  @mouseenter="onMouseEnter"
5
4
  @mouseleave="onMouseLeave"
6
5
  @mousewheel="onMouseWheel"
7
6
  :class="scrollableClasses"
8
7
  v-bind="$attrs"
9
8
  :style="scrollableStyles")
10
- slot
11
- .w-scrollbar(ref="track" @mousedown="onTrackMouseDown" :class="scrollbarClasses")
12
- .w-scrollbar__thumb(ref="thumb" :style="thumbStyles")
9
+ .w-scrollable__content(ref="scrollableEl")
10
+ slot
11
+ .w-scrollable__scrollbar(ref="trackEl" @mousedown="onTrackMouseDown" :class="scrollbarClasses")
12
+ .w-scrollable__scrollbar-thumb(ref="thumbEl" :style="thumbStyles")
13
13
  </template>
14
14
 
15
- <script>
15
+ <script setup>
16
+ import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
17
+
18
+ defineOptions({ name: 'WScrollable' })
19
+
20
+ const props = defineProps({
21
+ color: { type: String, default: 'primary' },
22
+ bgColor: { type: String },
23
+ width: { type: [Number, String] },
24
+ height: { type: [Number, String] }
25
+ })
26
+
27
+ const emit = defineEmits([])
28
+
16
29
  const domProps = {
17
30
  h: {
18
31
  direction: 'horizontal',
@@ -38,201 +51,198 @@ const domProps = {
38
51
  }
39
52
  }
40
53
 
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.direction}`]: true
76
- }
77
- },
78
-
79
- scrollbarClasses () {
80
- return {
81
- [`w-scrollbar--${this.m.direction}`]: true
82
- }
83
- },
84
-
85
- thumbSizePercent () {
86
- if (!this.mounted) return 0
87
- const size = this[this.m.size] ?? this.$refs.scrollable[[this.m.offsetSize]]
88
- // if (size === undefined) size = this.$refs.scrollable.offsetSize
89
- return (size * 100 / this.$refs.scrollable?.[this.m.scrollSize]) || 0
90
- },
91
-
92
- scrollableStyles () {
93
- return {
94
- [this.m.maxSize]: `${this[this.m.size]}px`
95
- }
96
- },
97
-
98
- thumbStyles () {
99
- let topOrLeftValue = this.scrollValuePercent
100
- topOrLeftValue = Math.max(0, Math.min(topOrLeftValue, 100 - this.thumbSizePercent))
101
- return {
102
- [this.m.size]: `${this.thumbSizePercent}%`,
103
- [this.m.topOrLeft]: `${topOrLeftValue}%`
104
- }
105
- }
106
- },
54
+ // Refs used in template and functions.
55
+ const mounted = ref(false)
56
+ const scrollableEl = ref(null)
57
+ const trackEl = ref(null)
58
+ const thumbEl = ref(null)
59
+ const scrollableState = ref({
60
+ top: null,
61
+ left: null,
62
+ hovered: false
63
+ })
64
+ const scrollValuePercent = ref(0)
65
+ const dragging = ref(false)
66
+
67
+ const isHorizontal = computed(() => {
68
+ if (!mounted.value) return false
69
+ console.log('💂‍♂️', scrollableEl.value?.scrollWidth, scrollableEl.value?.offsetWidth)
70
+ return (props.width && !props.height) || (scrollableEl.value?.scrollWidth > scrollableEl.value?.offsetWidth)
71
+ })
72
+
73
+ const m = computed(() => domProps[isHorizontal.value ? 'h' : 'v'])
74
+
75
+ const scrollableClasses = computed(() => ({
76
+ [`w-scrollable--${m.value.direction}`]: true
77
+ }))
78
+
79
+ const scrollbarClasses = computed(() => ({
80
+ [`w-scrollable__scrollbar--${m.value.direction}`]: true
81
+ }))
82
+
83
+ const refreshThumb = ref(0)
84
+
85
+ const thumbSizePercent = computed(() => {
86
+ refreshThumb.value // Dependency to force re-evaluation.
87
+ if (!mounted.value) return 0
88
+ const size = props[m.value.size] ?? scrollableEl.value?.[m.value.offsetSize]
89
+ return (size * 100 / scrollableEl.value?.[m.value.scrollSize]) || 0
90
+ })
91
+
92
+ function forceRefreshThumb () {
93
+ refreshThumb.value++
94
+ }
107
95
 
108
- methods: {
109
- onTrackMouseDown (e) {
110
- if (this.isDisabled || this.isReadonly) return
111
- // On touch screen don't listen for both touchstart & mousedown.
112
- if ('ontouchstart' in window && e.type === 'mousedown') return
113
-
114
- const { top, left, width, height } = this.$refs.track.getBoundingClientRect()
115
- if (this.isHorizontal) {
116
- this.$refs.track.width = width
117
- this.$refs.track.left = left
118
- }
119
- else {
120
- this.$refs.track.height = height
121
- this.$refs.track.top = top
122
- }
123
- this.dragging = true
124
-
125
- this.computeScroll(e.type === 'touchstart' ? e.touches[0][this.m.clientXorY] : e[this.m.clientXorY])
126
- this.scroll()
127
-
128
- document.addEventListener(e.type === 'touchstart' ? 'touchmove' : 'mousemove', this.onDrag)
129
- document.addEventListener(e.type === 'touchstart' ? 'touchend' : 'mouseup', this.onMouseUp, { once: true })
130
- },
131
-
132
- onDrag (e) {
133
- this.computeScroll((e.type === 'touchmove' ? e.touches[0][this.m.clientXorY] : e[this.m.clientXorY]))
134
- this.scroll()
135
- },
136
-
137
- onMouseUp (e) {
138
- this.dragging = false
139
- document.removeEventListener(e.type === 'touchend' ? 'touchmove' : 'mousemove', this.onDrag)
140
- if (this.$refs.thumb) this.$refs.thumb.focus()
141
- },
142
-
143
- onMouseEnter () {
144
- this.scrollable.hovered = true
145
- },
146
-
147
- onMouseLeave () {
148
- this.scrollable.hovered = false
149
- },
150
-
151
- onResize () {
152
- },
153
-
154
- onMouseWheel (e) {
155
- if (!this.scrollable.hovered) return // Only scroll a w-scrollable element that is being hovered.
156
-
157
- // When scrolling beyond limits, release the mousewheel and scroll the parent.
158
- if (this.scrollValuePercent <= 0 && e[this.m.deltaXorY] < 0) return
159
- if (this.scrollValuePercent >= 100 - this.thumbSizePercent && e[this.m.deltaXorY] > 0) return
160
-
161
- e.preventDefault() // Hold the scroll in the hovered w-scrollable element.
162
-
163
- this.scrollValuePercent += e[this.m.deltaXorY] * 0.05
164
- this.scrollValuePercent = Math.max(0, Math.min(this.scrollValuePercent, 100))
165
- this.scroll()
166
- },
167
-
168
- computeScroll (cursorPositionXorY) {
169
- const { top, left, width, height } = this.$refs.scrollable.getBoundingClientRect()
170
- const topOrLeft = this.isHorizontal ? left : top
171
- const size = this.isHorizontal ? width : height
172
- this.scrollValuePercent = Math.max(0, Math.min(((cursorPositionXorY - topOrLeft) / size) * 100, 100))
173
- },
174
-
175
- scroll () {
176
- this.$refs.scrollable[this.m.scrollTopOrLeft] = this.scrollValuePercent * this.$refs.scrollable?.[this.m.scrollSize] / 100
177
- this.updateThumbPosition()
178
- },
179
-
180
- updateThumbPosition () {
181
- this.$refs.thumb.style[this.m.topOrLeft] = this.scrollValuePercent
182
- }
183
- },
96
+ const scrollableStyles = computed(() => ({
97
+ [m.value.maxSize]: props[m.value.size] ? `${props[m.value.size]}px` : undefined
98
+ }))
184
99
 
185
- mounted () {
186
- this.mounted = true
187
- const { top, left } = this.$refs.scrollable.getBoundingClientRect()
188
- this.scrollable.top = top
189
- this.scrollable.left = left
100
+ const thumbStyles = computed(() => {
101
+ let topOrLeftValue = scrollValuePercent.value
102
+ topOrLeftValue = Math.max(0, Math.min(topOrLeftValue, 100 - thumbSizePercent.value))
103
+ return {
104
+ [m.value.size]: `${thumbSizePercent.value}%`,
105
+ [m.value.topOrLeft]: `${topOrLeftValue}%`
106
+ }
107
+ })
190
108
 
191
- this.$el.parentNode.style.position = 'relative'
192
- this.$el.parentNode.style.padding = 0
109
+ function onTrackMouseDown (e) {
110
+ if (props.isDisabled || props.isReadonly) return
111
+ // On touch screen don't listen for both touchstart & mousedown.
112
+ if ('ontouchstart' in window && e.type === 'mousedown') return
193
113
 
194
- window.addEventListener('resize', this.onResize)
114
+ const { top, left, width, height } = trackEl.value.getBoundingClientRect()
115
+ if (isHorizontal.value) {
116
+ trackEl.value.width = width
117
+ trackEl.value.left = left
118
+ }
119
+ else {
120
+ trackEl.value.height = height
121
+ trackEl.value.top = top
195
122
  }
123
+ dragging.value = true
124
+
125
+ computeScroll(e.type === 'touchstart' ? e.touches[0][m.value.clientXorY] : e[m.value.clientXorY])
126
+ scroll()
127
+
128
+ document.addEventListener(e.type === 'touchstart' ? 'touchmove' : 'mousemove', onDrag)
129
+ document.addEventListener(e.type === 'touchstart' ? 'touchend' : 'mouseup', onMouseUp, { once: true })
130
+ }
131
+
132
+ function onDrag (e) {
133
+ computeScroll((e.type === 'touchmove' ? e.touches[0][m.value.clientXorY] : e[m.value.clientXorY]))
134
+ scroll()
196
135
  }
136
+
137
+ function onMouseUp (e) {
138
+ dragging.value = false
139
+ document.removeEventListener(e.type === 'touchend' ? 'touchmove' : 'mousemove', onDrag)
140
+ if (thumbEl.value) thumbEl.value.focus()
141
+ }
142
+
143
+ function onMouseEnter () {
144
+ scrollableState.value.hovered = true
145
+ }
146
+
147
+ function onMouseLeave () {
148
+ scrollableState.value.hovered = false
149
+ }
150
+
151
+ function onMouseWheel (e) {
152
+ if (!scrollableState.value.hovered) return // Only scroll a w-scrollable element that is being hovered.
153
+
154
+ // When scrolling beyond limits, release the mousewheel and scroll the parent.
155
+ if (scrollValuePercent.value <= 0 && e[m.value.deltaXorY] < 0) return
156
+ if (scrollValuePercent.value >= 100 - thumbSizePercent.value && e[m.value.deltaXorY] > 0) return
157
+
158
+ e.preventDefault() // Hold the scroll in the hovered w-scrollable element.
159
+
160
+ scrollValuePercent.value += e[m.value.deltaXorY] * 0.05
161
+ scrollValuePercent.value = Math.max(0, Math.min(scrollValuePercent.value, 100))
162
+ scroll()
163
+ }
164
+
165
+ function computeScroll (cursorPositionXorY) {
166
+ const { top, left, width, height } = scrollableEl.value.getBoundingClientRect()
167
+ const topOrLeft = isHorizontal.value ? left : top
168
+ const size = isHorizontal.value ? width : height
169
+ scrollValuePercent.value = Math.max(0, Math.min(((cursorPositionXorY - topOrLeft) / size) * 100, 100))
170
+ }
171
+
172
+ function scroll () {
173
+ scrollableEl.value[m.value.scrollTopOrLeft] = scrollValuePercent.value * scrollableEl.value?.[m.value.scrollSize] / 100
174
+ updateThumbPosition()
175
+ }
176
+
177
+ function updateThumbPosition () {
178
+ thumbEl.value.style[m.value.topOrLeft] = scrollValuePercent.value
179
+ }
180
+
181
+ onMounted(() => {
182
+ mounted.value = true
183
+ const { top, left } = scrollableEl.value.getBoundingClientRect()
184
+ scrollableState.value.top = top
185
+ scrollableState.value.left = left
186
+
187
+ window.addEventListener('resize', forceRefreshThumb)
188
+ })
189
+
190
+ // Clean up event listener.
191
+ onBeforeUnmount(() => {
192
+ window.removeEventListener('resize', forceRefreshThumb)
193
+ })
194
+
195
+ defineExpose({ scroll })
197
196
  </script>
198
197
 
199
198
  <style lang="scss">
200
199
  .w-scrollable {
201
- position: relative;
202
- overflow: hidden;
203
- }
204
-
205
- .w-scrollbar {
206
- position: absolute;
207
- background: #000;
208
- user-select: none;
200
+ display: flex;
201
+ border-radius: inherit;
209
202
 
210
- &--horizontal {
211
- inset: auto 0 0;
212
- height: 8px;
203
+ &__content {
204
+ padding: 0;
205
+ flex: 1 1 auto;
206
+ overflow: hidden;
213
207
  }
214
- &--vertical {
215
- inset: 0 0 0 auto;
216
- width: 8px;
208
+
209
+ &__scrollbar {
210
+ position: relative;
211
+ flex: 0 0 auto;
212
+ background: $scrollbar-bg-color;
213
+ user-select: none;
214
+
215
+ &--horizontal {
216
+ inset: auto 0 0;
217
+ border-bottom-left-radius: inherit;
218
+ border-bottom-right-radius: inherit;
219
+ height: $scrollbar-size;
220
+ }
221
+ &--vertical {
222
+ inset: 0 0 0 auto;
223
+ border-top-right-radius: inherit;
224
+ border-bottom-right-radius: inherit;
225
+ width: $scrollbar-size;
226
+ }
217
227
  }
218
228
 
219
- &__thumb {
229
+ &__scrollbar-thumb {
220
230
  position: absolute;
221
- background: #333;
231
+ background: $scrollbar-thumb-color;
222
232
  border-radius: $border-radius;
223
233
  z-index: 1;
224
234
  will-change: top left;
225
235
 
226
- &:hover {background: #444;}
236
+ &:hover {background: $scrollbar-thumb-color;}
227
237
  }
228
- &--horizontal &__thumb {
238
+ &--horizontal &__scrollbar-thumb {
229
239
  height: 6px;
230
240
  left: 0;
231
241
  right: 0;
232
242
  margin-top: 1px;
233
243
  margin-bottom: 1px;
234
244
  }
235
- &--vertical &__thumb {
245
+ &--vertical &__scrollbar-thumb {
236
246
  width: 6px;
237
247
  top: 0;
238
248
  bottom: 0;
@@ -6,6 +6,16 @@
6
6
  :root {
7
7
  --w-base-increment: #{$base-increment};
8
8
  --w-css-scope: #{$css-scope};
9
+ --w-contrast-bg-o05-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 5%, transparent)};
10
+ --w-contrast-bg-o1-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 10%, transparent)};
11
+ --w-contrast-bg-o2-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 20%, transparent)};
12
+ --w-contrast-bg-o3-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 30%, transparent)};
13
+ --w-contrast-bg-o4-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 40%, transparent)};
14
+ --w-contrast-bg-o5-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 50%, transparent)};
15
+ --w-contrast-bg-o6-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 60%, transparent)};
16
+ --w-contrast-bg-o7-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 70%, transparent)};
17
+ --w-contrast-bg-o8-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 80%, transparent)};
18
+ --w-contrast-bg-o9-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 90%, transparent)};
9
19
 
10
20
  background-color: var(--w-base-bg-color);
11
21
  color: var(--w-base-color);
@@ -126,6 +126,13 @@
126
126
  .base-color--bg {background-color: var(--w-base-bg-color);}
127
127
  .contrast-color {color: color-mix(in srgb, var(--w-contrast-color) 75%, transparent);}
128
128
  .contrast-color--bg {background-color: var(--w-contrast-bg-color);}
129
+
130
+ .contrast-o05 {color: var(--w-contrast-bg-o05-color);}
131
+ .contrast-o05--bg {background-color: var(--w-contrast-bg-o05-color);}
132
+ @for $i from 1 through 9 {
133
+ .contrast-o#{$i} {color: var(--w-contrast-bg-o#{$i}-color);}
134
+ .contrast-o#{$i}--bg {background-color: var(--w-contrast-bg-o#{$i}-color);}
135
+ }
129
136
  // ------------------------------------------------------
130
137
 
131
138
  // Status colors - must stay last and have highest priority.
@@ -52,6 +52,15 @@
52
52
  .d-iflex {display: inline-flex;}
53
53
  .d-block {display: block;}
54
54
  .d-iblock {display: inline-block;}
55
+
56
+ .ovh {overflow: hidden;}
57
+ .ovv {overflow: visible;}
58
+ .ova {overflow: auto;}
59
+
60
+ .op05 {opacity: 0.05;}
61
+ @for $i from 0 through 9 {
62
+ .op#{$i} {opacity: $i * 0.1;}
63
+ }
55
64
  // ----------------------------------------------
56
65
 
57
66
  // Borders (from 0 to 6), radii (from 0 to 6 + `m` for max).
@@ -148,3 +148,10 @@ $tooltip-bg-color: $detachable-bg-color !default;
148
148
  $tooltip-color: $detachable-color !default;
149
149
  $tooltip-border-color: $border-color !default;
150
150
  // --------------------------------------------------------
151
+
152
+ // w-scrollable.
153
+ // --------------------------------------------------------
154
+ $scrollbar-size: 8px !default;
155
+ $scrollbar-bg-color: color-mix(in srgb, var(--w-contrast-bg-color) 8%, var(--w-base-bg-color)) !default;
156
+ $scrollbar-thumb-color: color-mix(in srgb, var(--w-contrast-bg-color) 25%, var(--w-base-bg-color)) !default;
157
+ // --------------------------------------------------------
@@ -205,12 +205,12 @@ export function mixColors (color1, color2, weight = 50) {
205
205
  const red = clamp(Math.round(c1.red * c1Weight + c2.red * c2Weight), 0, 255)
206
206
  const green = clamp(Math.round(c1.green * c1Weight + c2.green * c2Weight), 0, 255)
207
207
  const blue = clamp(Math.round(c1.blue * c1Weight + c2.blue * c2Weight), 0, 255)
208
-
209
208
  const alpha = c1.alpha * percentage + c2.alpha * (1 - percentage)
210
209
 
211
- return c1.hasAlpha || c2.hasAlpha || alpha !== 1
212
- ? hexFromRGB(red, green, blue, alpha)
213
- : hexFromRGB(red, green, blue)
210
+ const channels = [red, green, blue]
211
+ if (c1.hasAlpha || c2.hasAlpha || alpha !== 1) channels.push(alpha)
212
+
213
+ return hexFromRGB(...channels)
214
214
  }
215
215
 
216
216
  /**
@@ -1,5 +1,5 @@
1
- // Defaults CSS variables. Will be overridden in the main function of this file (the export default)
2
- // which fetches the CSS :root variables generated by _layout.scss.
1
+ // Defaults CSS variables. Will be overridden in the doDynamicCSS() function
2
+ // which fetches the CSS :root variables generated by _base.scss.
3
3
  const cssVars = {
4
4
  cssScope: '.w-app',
5
5
  baseIncrement: 4