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.
Files changed (77) hide show
  1. package/dist/types/$waveui.d.ts +2 -2
  2. package/dist/types/components/WAccordion.d.ts +2 -2
  3. package/dist/types/components/WAlert.d.ts +2 -2
  4. package/dist/types/components/WApp.d.ts +3 -4
  5. package/dist/types/components/WBadge.d.ts +3 -4
  6. package/dist/types/components/WBreadcrumbs.d.ts +3 -4
  7. package/dist/types/components/WButton.d.ts +3 -4
  8. package/dist/types/components/WCard.d.ts +3 -4
  9. package/dist/types/components/WCheckbox.d.ts +2 -2
  10. package/dist/types/components/WCheckboxes.d.ts +2 -2
  11. package/dist/types/components/WConfirm.d.ts +2 -2
  12. package/dist/types/components/WDialog.d.ts +2 -2
  13. package/dist/types/components/WDivider.d.ts +3 -4
  14. package/dist/types/components/WDrawer.d.ts +5 -5
  15. package/dist/types/components/WFlex.d.ts +3 -4
  16. package/dist/types/components/WForm.d.ts +2 -2
  17. package/dist/types/components/WFormElement.d.ts +2 -2
  18. package/dist/types/components/WGrid.d.ts +3 -4
  19. package/dist/types/components/WIcon.d.ts +3 -4
  20. package/dist/types/components/WImage.d.ts +2 -2
  21. package/dist/types/components/WInput.d.ts +2 -2
  22. package/dist/types/components/WList.d.ts +2 -2
  23. package/dist/types/components/WMenu.d.ts +2 -2
  24. package/dist/types/components/WNotification.d.ts +2 -2
  25. package/dist/types/components/WNotificationManager.d.ts +4 -6
  26. package/dist/types/components/WOverlay.d.ts +2 -2
  27. package/dist/types/components/WProgress.d.ts +3 -4
  28. package/dist/types/components/WRadio.d.ts +2 -2
  29. package/dist/types/components/WRadios.d.ts +2 -2
  30. package/dist/types/components/WRating.d.ts +2 -2
  31. package/dist/types/components/WSelect.d.ts +4 -4
  32. package/dist/types/components/WSlider.d.ts +2 -2
  33. package/dist/types/components/WSpinner.d.ts +3 -4
  34. package/dist/types/components/WSteps.d.ts +3 -4
  35. package/dist/types/components/WSwitch.d.ts +2 -2
  36. package/dist/types/components/WTable.d.ts +2 -2
  37. package/dist/types/components/WTabs.d.ts +2 -2
  38. package/dist/types/components/WTag.d.ts +2 -2
  39. package/dist/types/components/WTextarea.d.ts +2 -2
  40. package/dist/types/components/WTimeline.d.ts +3 -4
  41. package/dist/types/components/WToolbar.d.ts +3 -4
  42. package/dist/types/components/WTooltip.d.ts +2 -2
  43. package/dist/types/components/WTree.d.ts +2 -2
  44. package/dist/types/extra-vue-types.d.ts +1 -1
  45. package/dist/types/plugin.d.ts +2 -2
  46. package/dist/wave-ui.cjs.js +1 -1
  47. package/dist/wave-ui.css +1 -1
  48. package/dist/wave-ui.es.js +1215 -1212
  49. package/dist/wave-ui.umd.js +1 -1
  50. package/package.json +25 -30
  51. package/src/wave-ui/components/transitions/w-transition-expand.vue +3 -3
  52. package/src/wave-ui/components/w-autocomplete.vue +3 -3
  53. package/src/wave-ui/components/w-badge.vue +2 -2
  54. package/src/wave-ui/components/w-card.vue +2 -2
  55. package/src/wave-ui/components/w-checkboxes.vue +1 -1
  56. package/src/wave-ui/components/w-icon.vue +1 -1
  57. package/src/wave-ui/components/w-image.vue +1 -1
  58. package/src/wave-ui/components/w-input.vue +2 -2
  59. package/src/wave-ui/components/w-list.vue +2 -2
  60. package/src/wave-ui/components/w-menu.vue +13 -5
  61. package/src/wave-ui/components/w-scrollable.vue +184 -174
  62. package/src/wave-ui/components/w-table.vue +7 -7
  63. package/src/wave-ui/components/w-tabs/index.vue +2 -2
  64. package/src/wave-ui/components/w-textarea.vue +1 -1
  65. package/src/wave-ui/components/w-toolbar.vue +2 -2
  66. package/src/wave-ui/components/w-tooltip.vue +1 -1
  67. package/src/wave-ui/components/w-tree.vue +1 -1
  68. package/src/wave-ui/core.js +25 -5
  69. package/src/wave-ui/index.d.ts +1 -1
  70. package/src/wave-ui/mixins/detachable.js +1 -1
  71. package/src/wave-ui/scss/_base.scss +10 -0
  72. package/src/wave-ui/scss/_colors.scss +7 -0
  73. package/src/wave-ui/scss/_layout.scss +9 -0
  74. package/src/wave-ui/scss/variables/_variables.scss +7 -0
  75. package/src/wave-ui/utils/colors.js +5 -5
  76. package/src/wave-ui/utils/dynamic-css.js +2 -2
  77. 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.19.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",
@@ -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
- "@eslint/js": "^9.21.0",
47
- "@faker-js/faker": "^9.5.1",
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
- "@typescript-eslint/eslint-plugin": "^8.25.0",
51
- "@typescript-eslint/parser": "^8.25.0",
52
- "@vitejs/plugin-vue": "^5.2.1",
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.0",
69
- "sass": "^1.85.1",
73
+ "rollup-plugin-delete": "^3.0.1",
74
+ "sass": "^1.86.3",
70
75
  "simple-syntax-highlighter": "^3.1.1",
71
- "splitpanes": "^3.1.8",
72
- "typescript": "^5.8.2",
73
- "typescript-eslint": "^8.25.0",
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 + 'px'
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 + 'px'
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 + 'ms ease-in-out'
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
- else if (this.multiple && !this.allowDuplicates) return isItemNotSelected(item)
103
- else return true
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
- else if (this.highlightedItem === 'extra-item') return this.filteredItems.length
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 && this.$slots.badge().map(item => item.children).join('')
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 + '' || '').length < 2,
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 && title().map(vnode => vnode.type.name).join('').includes('w-toolbar')
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 && actions().map(vnode => vnode.type.name).join('').includes('w-toolbar')
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 && this.modelValue.includes(itemValue)
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].children.trim().split(' ')) || []
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] && entry[0].isIntersecting) {
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 && original.type.startsWith('image/')
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 && this.$refs.input.matches(':-webkit-autofill')) this.isAutofilled = true
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
- else return item[this.itemLabelKey] !== undefined ? item[this.itemLabelKey] : item.index
136
+ return item[this.itemLabelKey] !== undefined ? item[this.itemLabelKey] : item.index
137
137
  }
138
- else return item
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
- else return isNaN(this.minWidth) ? this.minWidth : (this.minWidth ? `${this.minWidth}px` : 0)
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
- else return isNaN(this.maxWidth) ? this.maxWidth : (this.maxWidth ? `${this.maxWidth}px` : 0)
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: e => {
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
- @include triangle(var(--w-menu-bg-color), '.w-menu', 9px);
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
- 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 (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
- 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;