wave-ui 3.19.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/dist/types/$waveui.d.ts +2 -2
- package/dist/types/components/WAccordion.d.ts +2 -2
- package/dist/types/components/WAlert.d.ts +2 -2
- package/dist/types/components/WApp.d.ts +3 -4
- package/dist/types/components/WBadge.d.ts +3 -4
- package/dist/types/components/WBreadcrumbs.d.ts +3 -4
- package/dist/types/components/WButton.d.ts +3 -4
- package/dist/types/components/WCard.d.ts +3 -4
- package/dist/types/components/WCheckbox.d.ts +2 -2
- package/dist/types/components/WCheckboxes.d.ts +2 -2
- package/dist/types/components/WConfirm.d.ts +2 -2
- package/dist/types/components/WDialog.d.ts +2 -2
- package/dist/types/components/WDivider.d.ts +3 -4
- package/dist/types/components/WDrawer.d.ts +5 -5
- package/dist/types/components/WFlex.d.ts +3 -4
- package/dist/types/components/WForm.d.ts +2 -2
- package/dist/types/components/WFormElement.d.ts +2 -2
- package/dist/types/components/WGrid.d.ts +3 -4
- package/dist/types/components/WIcon.d.ts +3 -4
- package/dist/types/components/WImage.d.ts +2 -2
- package/dist/types/components/WInput.d.ts +2 -2
- package/dist/types/components/WList.d.ts +2 -2
- package/dist/types/components/WMenu.d.ts +2 -2
- package/dist/types/components/WNotification.d.ts +2 -2
- package/dist/types/components/WNotificationManager.d.ts +4 -6
- package/dist/types/components/WOverlay.d.ts +2 -2
- package/dist/types/components/WProgress.d.ts +3 -4
- package/dist/types/components/WRadio.d.ts +2 -2
- package/dist/types/components/WRadios.d.ts +2 -2
- package/dist/types/components/WRating.d.ts +2 -2
- package/dist/types/components/WSelect.d.ts +4 -4
- package/dist/types/components/WSlider.d.ts +2 -2
- package/dist/types/components/WSpinner.d.ts +3 -4
- package/dist/types/components/WSteps.d.ts +3 -4
- package/dist/types/components/WSwitch.d.ts +2 -2
- package/dist/types/components/WTable.d.ts +2 -2
- package/dist/types/components/WTabs.d.ts +2 -2
- package/dist/types/components/WTag.d.ts +2 -2
- package/dist/types/components/WTextarea.d.ts +2 -2
- package/dist/types/components/WTimeline.d.ts +3 -4
- package/dist/types/components/WToolbar.d.ts +3 -4
- package/dist/types/components/WTooltip.d.ts +2 -2
- package/dist/types/components/WTree.d.ts +2 -2
- package/dist/types/extra-vue-types.d.ts +1 -1
- package/dist/types/plugin.d.ts +2 -2
- package/dist/wave-ui.cjs.js +1 -1
- package/dist/wave-ui.css +1 -1
- package/dist/wave-ui.es.js +1215 -1212
- package/dist/wave-ui.umd.js +1 -1
- package/package.json +25 -30
- package/src/wave-ui/components/transitions/w-transition-expand.vue +3 -3
- package/src/wave-ui/components/w-autocomplete.vue +3 -3
- package/src/wave-ui/components/w-badge.vue +2 -2
- package/src/wave-ui/components/w-card.vue +2 -2
- package/src/wave-ui/components/w-checkboxes.vue +1 -1
- package/src/wave-ui/components/w-icon.vue +1 -1
- package/src/wave-ui/components/w-image.vue +1 -1
- package/src/wave-ui/components/w-input.vue +2 -2
- package/src/wave-ui/components/w-list.vue +2 -2
- package/src/wave-ui/components/w-menu.vue +13 -5
- package/src/wave-ui/components/w-scrollable.vue +184 -174
- package/src/wave-ui/components/w-table.vue +7 -7
- package/src/wave-ui/components/w-tabs/index.vue +2 -2
- package/src/wave-ui/components/w-textarea.vue +1 -1
- package/src/wave-ui/components/w-toolbar.vue +2 -2
- package/src/wave-ui/components/w-tooltip.vue +1 -1
- package/src/wave-ui/components/w-tree.vue +1 -1
- package/src/wave-ui/core.js +25 -5
- package/src/wave-ui/index.d.ts +1 -1
- package/src/wave-ui/mixins/detachable.js +1 -1
- package/src/wave-ui/scss/_base.scss +10 -0
- package/src/wave-ui/scss/_colors.scss +7 -0
- package/src/wave-ui/scss/_layout.scss +9 -0
- package/src/wave-ui/scss/variables/_variables.scss +7 -0
- package/src/wave-ui/utils/colors.js +5 -5
- package/src/wave-ui/utils/dynamic-css.js +2 -2
- package/src/wave-ui/utils/notification-manager.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-ui",
|
|
3
|
-
"version": "3.
|
|
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",
|
|
@@ -42,22 +42,27 @@
|
|
|
42
42
|
"vue framework",
|
|
43
43
|
"ui"
|
|
44
44
|
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"dev": "vite",
|
|
47
|
+
"build": "vite build --base /wave-ui/",
|
|
48
|
+
"build-types": "tsc -p ./tsconfig.json",
|
|
49
|
+
"build-bundle": "BUNDLE=true vite build && npm run build-types",
|
|
50
|
+
"preview": "vite preview --base /wave-ui/",
|
|
51
|
+
"lint": "biome check .",
|
|
52
|
+
"lint:fix": "biome check --apply .",
|
|
53
|
+
"format": "biome format .",
|
|
54
|
+
"format:fix": "biome format --write .",
|
|
55
|
+
"publish-doc": "npm run build && npm run build-bundle && git add . && git commit -m 'Publish documentation on Github.' && git push && git push --tag"
|
|
56
|
+
},
|
|
45
57
|
"devDependencies": {
|
|
46
|
-
"@
|
|
47
|
-
"@
|
|
58
|
+
"@babel/core": "^7.26.10",
|
|
59
|
+
"@biomejs/biome": "^1.9.4",
|
|
60
|
+
"@faker-js/faker": "^9.7.0",
|
|
48
61
|
"@mdi/font": "^7.4.47",
|
|
49
62
|
"@tsconfig/recommended": "^1.0.8",
|
|
50
|
-
"@
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"autoprefixer": "^10.4.20",
|
|
54
|
-
"axios": "^1.8.1",
|
|
55
|
-
"eslint": "^9.21.0",
|
|
56
|
-
"eslint-config-standard": "^17.1.0",
|
|
57
|
-
"eslint-plugin-import": "^2.31.0",
|
|
58
|
-
"eslint-plugin-n": "^17.15.1",
|
|
59
|
-
"eslint-plugin-promise": "^7.2.1",
|
|
60
|
-
"eslint-plugin-vue": "^9.32.0",
|
|
63
|
+
"@vitejs/plugin-vue": "^5.2.3",
|
|
64
|
+
"autoprefixer": "^10.4.21",
|
|
65
|
+
"axios": "^1.8.4",
|
|
61
66
|
"font-awesome": "^4.7.0",
|
|
62
67
|
"globals": "^16.0.0",
|
|
63
68
|
"gsap": "^3.12.7",
|
|
@@ -65,13 +70,12 @@
|
|
|
65
70
|
"material-design-icons": "^3.0.1",
|
|
66
71
|
"postcss": "^8.5.3",
|
|
67
72
|
"pug": "^3.0.3",
|
|
68
|
-
"rollup-plugin-delete": "^3.0.
|
|
69
|
-
"sass": "^1.
|
|
73
|
+
"rollup-plugin-delete": "^3.0.1",
|
|
74
|
+
"sass": "^1.86.3",
|
|
70
75
|
"simple-syntax-highlighter": "^3.1.1",
|
|
71
|
-
"splitpanes": "^
|
|
72
|
-
"typescript": "^5.8.
|
|
73
|
-
"
|
|
74
|
-
"vite": "^6.2.0",
|
|
76
|
+
"splitpanes": "^4.0.3",
|
|
77
|
+
"typescript": "^5.8.3",
|
|
78
|
+
"vite": "^6.2.6",
|
|
75
79
|
"vite-svg-loader": "^5.1.0",
|
|
76
80
|
"vue": "^3.5.13",
|
|
77
81
|
"vue-router": "^4.5.0",
|
|
@@ -84,14 +88,5 @@
|
|
|
84
88
|
"engines": {
|
|
85
89
|
"node": ">=16.0.0",
|
|
86
90
|
"pnpm": ">=8.0.0"
|
|
87
|
-
},
|
|
88
|
-
"scripts": {
|
|
89
|
-
"dev": "vite",
|
|
90
|
-
"build": "vite build --base /wave-ui/",
|
|
91
|
-
"build-types": "tsc -p ./tsconfig.json",
|
|
92
|
-
"build-bundle": "BUNDLE=true vite build && npm run build-types",
|
|
93
|
-
"preview": "vite preview --base /wave-ui/",
|
|
94
|
-
"lint": "vite lint",
|
|
95
|
-
"publish-doc": "npm run build && npm run build-bundle && git add . && git commit -m 'Publish documentation on Github.' && git push && git push --tag"
|
|
96
91
|
}
|
|
97
|
-
}
|
|
92
|
+
}
|
|
@@ -135,7 +135,7 @@ export default {
|
|
|
135
135
|
},
|
|
136
136
|
applyShowStyles (el) {
|
|
137
137
|
if (this.animX) {
|
|
138
|
-
el.style.width = this.el.width
|
|
138
|
+
el.style.width = `${this.el.width}px`
|
|
139
139
|
el.style.marginLeft = this.el.marginLeft
|
|
140
140
|
el.style.marginRight = this.el.marginRight
|
|
141
141
|
el.style.paddingLeft = this.el.paddingLeft
|
|
@@ -144,7 +144,7 @@ export default {
|
|
|
144
144
|
el.style.borderRightWidth = this.el.borderRightWidth
|
|
145
145
|
}
|
|
146
146
|
if (this.animY) {
|
|
147
|
-
el.style.height = this.el.height
|
|
147
|
+
el.style.height = `${this.el.height}px`
|
|
148
148
|
el.style.marginTop = this.el.marginTop
|
|
149
149
|
el.style.marginBottom = this.el.marginBottom
|
|
150
150
|
el.style.paddingTop = this.el.paddingTop
|
|
@@ -153,7 +153,7 @@ export default {
|
|
|
153
153
|
el.style.borderBottomWidth = this.el.borderBottomWidth
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
el.style.transition = this.duration
|
|
156
|
+
el.style.transition = `${this.duration}ms ease-in-out`
|
|
157
157
|
},
|
|
158
158
|
applyOriginalStyles (el) {
|
|
159
159
|
el.style.cssText = this.el.originalStyles
|
|
@@ -99,8 +99,8 @@ export default {
|
|
|
99
99
|
if (this.keywords) {
|
|
100
100
|
items = items.filter(item => {
|
|
101
101
|
if (!item.searchable.includes(this.normalizedKeywords)) return false
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
if (this.multiple && !this.allowDuplicates) return isItemNotSelected(item)
|
|
103
|
+
return true
|
|
104
104
|
})
|
|
105
105
|
}
|
|
106
106
|
|
|
@@ -111,7 +111,7 @@ export default {
|
|
|
111
111
|
|
|
112
112
|
highlightedItemIndex () {
|
|
113
113
|
if (this.highlightedItem === null) return -1
|
|
114
|
-
|
|
114
|
+
if (this.highlightedItem === 'extra-item') return this.filteredItems.length
|
|
115
115
|
|
|
116
116
|
return this.filteredItems.findIndex(item => item.uid === this.highlightedItem)
|
|
117
117
|
},
|
|
@@ -66,13 +66,13 @@ export default {
|
|
|
66
66
|
]
|
|
67
67
|
},
|
|
68
68
|
classes () {
|
|
69
|
-
const slotText = this.$slots.badge
|
|
69
|
+
const slotText = this.$slots.badge?.().map(item => item.children).join('')
|
|
70
70
|
|
|
71
71
|
return {
|
|
72
72
|
[this.color]: this.color,
|
|
73
73
|
[`${this.bgColor}--bg`]: this.bgColor,
|
|
74
74
|
[this.badgeClass]: this.badgeClass || null,
|
|
75
|
-
'w-badge--round': this.round || (slotText || this.modelValue
|
|
75
|
+
'w-badge--round': this.round || (slotText || `${this.modelValue}` || '').length < 2,
|
|
76
76
|
'w-badge--dark': this.dark,
|
|
77
77
|
'w-badge--light': this.light,
|
|
78
78
|
'w-badge--outline': this.outline,
|
|
@@ -52,12 +52,12 @@ export default {
|
|
|
52
52
|
|
|
53
53
|
titleHasToolbar () {
|
|
54
54
|
const { title } = this.$slots
|
|
55
|
-
return title
|
|
55
|
+
return title?.()?.map(vnode => vnode.type.name).join('').includes('w-toolbar')
|
|
56
56
|
},
|
|
57
57
|
|
|
58
58
|
actionsHasToolbar () {
|
|
59
59
|
const { actions } = this.$slots
|
|
60
|
-
return actions
|
|
60
|
+
return actions?.()?.map(vnode => vnode.type.name).join('').includes('w-toolbar')
|
|
61
61
|
},
|
|
62
62
|
|
|
63
63
|
imgProps () {
|
|
@@ -75,7 +75,7 @@ export default {
|
|
|
75
75
|
_index: i,
|
|
76
76
|
value: itemValue, // If no value is set then add one to prevent error.
|
|
77
77
|
color: item[this.itemColorKey] || this.color,
|
|
78
|
-
_isChecked: this.modelValue
|
|
78
|
+
_isChecked: this.modelValue?.includes(itemValue)
|
|
79
79
|
})
|
|
80
80
|
})
|
|
81
81
|
},
|
|
@@ -87,7 +87,7 @@ export default {
|
|
|
87
87
|
methods: {
|
|
88
88
|
readIcon () {
|
|
89
89
|
const { default: slot } = this.$slots
|
|
90
|
-
const [fontName = '', icon = ''] = (typeof slot === 'function' && slot()[0]
|
|
90
|
+
const [fontName = '', icon = ''] = (typeof slot === 'function' && slot()?.[0]?.children?.trim()?.split(' ')) || []
|
|
91
91
|
this.fontName = fontName
|
|
92
92
|
this.icon = icon
|
|
93
93
|
|
|
@@ -183,7 +183,7 @@ export default {
|
|
|
183
183
|
|
|
184
184
|
if (this.lazy) {
|
|
185
185
|
const IntersectObserver = new IntersectionObserver(entry => {
|
|
186
|
-
if (entry[0]
|
|
186
|
+
if (entry[0]?.isIntersecting) {
|
|
187
187
|
this.loadImage()
|
|
188
188
|
IntersectObserver.disconnect()
|
|
189
189
|
}
|
|
@@ -335,7 +335,7 @@ export default {
|
|
|
335
335
|
// If the preview prop is a string, the user is setting the preview to an icon and
|
|
336
336
|
// don't need the actual file preview.
|
|
337
337
|
const isPreviewAnIcon = typeof this.preview === 'string'
|
|
338
|
-
const isFileAnImage = original.type
|
|
338
|
+
const isFileAnImage = original.type?.startsWith('image/')
|
|
339
339
|
// Check if the file is an image and set a preview image.
|
|
340
340
|
if (this.preview && !isPreviewAnIcon && isFileAnImage) {
|
|
341
341
|
reader.addEventListener('load', e => {
|
|
@@ -357,7 +357,7 @@ export default {
|
|
|
357
357
|
// On page load, check if the field is autofilled by the browser.
|
|
358
358
|
// 20211229. Only a problem on Chrome. Firefox ok, Safari always prompts before filling up.
|
|
359
359
|
setTimeout(() => {
|
|
360
|
-
if (this.$refs.input
|
|
360
|
+
if (this.$refs.input?.matches(':-webkit-autofill')) this.isAutofilled = true
|
|
361
361
|
}, 400) // Can't be less than 350: time for the browser to autofill.
|
|
362
362
|
},
|
|
363
363
|
|
|
@@ -133,9 +133,9 @@ export default {
|
|
|
133
133
|
getItemValue (item) {
|
|
134
134
|
if (item && typeof item === 'object') {
|
|
135
135
|
if (item[this.itemValueKey] !== undefined) return item[this.itemValueKey]
|
|
136
|
-
|
|
136
|
+
return item[this.itemLabelKey] !== undefined ? item[this.itemLabelKey] : item.index
|
|
137
137
|
}
|
|
138
|
-
|
|
138
|
+
return item
|
|
139
139
|
},
|
|
140
140
|
|
|
141
141
|
// Action of selecting 1 item.
|
|
@@ -122,12 +122,12 @@ export default {
|
|
|
122
122
|
|
|
123
123
|
menuMinWidth () {
|
|
124
124
|
if (this.minWidth === 'activator') return this.activatorWidth ? `${this.activatorWidth}px` : 0
|
|
125
|
-
|
|
125
|
+
return isNaN(this.minWidth) ? this.minWidth : (this.minWidth ? `${this.minWidth}px` : 0)
|
|
126
126
|
},
|
|
127
127
|
|
|
128
128
|
menuMaxWidth () {
|
|
129
129
|
if (this.maxWidth === 'activator') return this.activatorWidth ? `${this.activatorWidth}px` : 0
|
|
130
|
-
|
|
130
|
+
return isNaN(this.maxWidth) ? this.maxWidth : (this.maxWidth ? `${this.maxWidth}px` : 0)
|
|
131
131
|
},
|
|
132
132
|
|
|
133
133
|
menuClasses () {
|
|
@@ -190,7 +190,7 @@ export default {
|
|
|
190
190
|
this.hoveringActivator = true
|
|
191
191
|
this.open(e)
|
|
192
192
|
},
|
|
193
|
-
mouseleave:
|
|
193
|
+
mouseleave: () => {
|
|
194
194
|
this.hoveringActivator = false
|
|
195
195
|
// Wait 10ms, the time to get the hoveringMenu updated on mouseenter on the menu.
|
|
196
196
|
setTimeout(() => {
|
|
@@ -313,7 +313,15 @@ export default {
|
|
|
313
313
|
&.w-menu--left {margin-left: -4 * $base-increment;}
|
|
314
314
|
&.w-menu--right {margin-left: 4 * $base-increment;}
|
|
315
315
|
|
|
316
|
-
|
|
317
|
-
|
|
316
|
+
// Menu without border.
|
|
317
|
+
&.w-menu--no-border {
|
|
318
|
+
@include triangle(var(--w-menu-bg-color), '.w-menu', 9px, 0);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Menu with border.
|
|
322
|
+
&:not(.w-menu--no-border) {
|
|
323
|
+
@include triangle(var(--w-menu-bg-color), '.w-menu', 9px);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
318
326
|
}
|
|
319
327
|
</style>
|
|
@@ -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
|
-
|
|
11
|
-
|
|
12
|
-
.w-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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 (e) {
|
|
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
|
-
else 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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
192
|
-
|
|
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
|
-
|
|
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
|
-
|
|
202
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
203
|
+
&__content {
|
|
204
|
+
padding: 0;
|
|
205
|
+
flex: 1 1 auto;
|
|
206
|
+
overflow: hidden;
|
|
213
207
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
&
|
|
229
|
+
&__scrollbar-thumb {
|
|
220
230
|
position: absolute;
|
|
221
|
-
background:
|
|
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:
|
|
236
|
+
&:hover {background: $scrollbar-thumb-color;}
|
|
227
237
|
}
|
|
228
|
-
&--horizontal &
|
|
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 &
|
|
245
|
+
&--vertical &__scrollbar-thumb {
|
|
236
246
|
width: 6px;
|
|
237
247
|
top: 0;
|
|
238
248
|
bottom: 0;
|