wave-ui 3.27.1 → 3.28.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 (43) hide show
  1. package/dist/types/types/$waveui.d.ts +6 -0
  2. package/dist/types/types/components/WAccordion.d.ts +7 -0
  3. package/dist/types/types/components/WBreadcrumbs.d.ts +7 -0
  4. package/dist/types/types/components/WButton.d.ts +7 -0
  5. package/dist/types/types/components/WList.d.ts +7 -0
  6. package/dist/types/types/components/WScrollable.d.ts +143 -0
  7. package/dist/types/types/components/WScrollable.js +2 -0
  8. package/dist/types/types/components/WTabs.d.ts +7 -0
  9. package/dist/types/types/components/WTag.d.ts +7 -0
  10. package/dist/types/types/components/index.d.ts +1 -0
  11. package/dist/wave-ui.cjs.js +3 -3
  12. package/dist/wave-ui.css +1 -1
  13. package/dist/wave-ui.esm.js +1440 -939
  14. package/dist/wave-ui.umd.js +3 -3
  15. package/package.json +6 -6
  16. package/src/wave-ui/components/w-accordion/index.vue +5 -1
  17. package/src/wave-ui/components/w-accordion/item.vue +42 -12
  18. package/src/wave-ui/components/w-breadcrumbs.vue +13 -2
  19. package/src/wave-ui/components/w-button/button.vue +15 -1
  20. package/src/wave-ui/components/w-button/index.vue +2 -1
  21. package/src/wave-ui/components/w-checkbox.vue +5 -1
  22. package/src/wave-ui/components/w-checkboxes.vue +5 -1
  23. package/src/wave-ui/components/w-input.vue +5 -1
  24. package/src/wave-ui/components/w-list.vue +12 -0
  25. package/src/wave-ui/components/w-radio.vue +6 -1
  26. package/src/wave-ui/components/w-radios.vue +5 -1
  27. package/src/wave-ui/components/w-rating.vue +5 -1
  28. package/src/wave-ui/components/w-scrollable.vue +667 -94
  29. package/src/wave-ui/components/w-select.vue +11 -7
  30. package/src/wave-ui/components/w-slider.vue +5 -1
  31. package/src/wave-ui/components/w-switch.vue +5 -1
  32. package/src/wave-ui/components/w-tabs/index.vue +10 -0
  33. package/src/wave-ui/components/w-tag.vue +14 -0
  34. package/src/wave-ui/components/w-textarea.vue +5 -1
  35. package/src/wave-ui/core.js +2 -0
  36. package/src/wave-ui/mixins/form-elements.js +5 -8
  37. package/src/wave-ui/mixins/ripple.js +39 -0
  38. package/src/wave-ui/scss/_ripple.scss +37 -0
  39. package/src/wave-ui/scss/index.scss +1 -0
  40. package/src/wave-ui/scss/variables/_variables.scss +0 -2
  41. package/src/wave-ui/utils/config.js +2 -0
  42. package/src/wave-ui/utils/ripple.js +71 -0
  43. package/src/wave-ui/utils/wave-ripple-directive.js +40 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-ui",
3
- "version": "3.27.1",
3
+ "version": "3.28.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",
@@ -50,13 +50,13 @@
50
50
  ],
51
51
  "devDependencies": {
52
52
  "@babel/core": "^7.29.0",
53
- "@biomejs/biome": "^2.4.12",
53
+ "@biomejs/biome": "^2.4.13",
54
54
  "@faker-js/faker": "^10.4.0",
55
55
  "@mdi/font": "^7.4.47",
56
56
  "@tsconfig/recommended": "^1.0.13",
57
57
  "@vitejs/plugin-vue": "^6.0.6",
58
58
  "autoprefixer": "^10.5.0",
59
- "axios": "^1.15.1",
59
+ "axios": "^1.15.2",
60
60
  "font-awesome": "^4.7.0",
61
61
  "globals": "^17.5.0",
62
62
  "gsap": "^3.15.0",
@@ -69,10 +69,10 @@
69
69
  "simple-syntax-highlighter": "^3.1.1",
70
70
  "splitpanes": "^4.0.4",
71
71
  "typescript": "^6.0.3",
72
- "vite": "^8.0.9",
72
+ "vite": "^8.0.10",
73
73
  "vite-svg-loader": "^5.1.1",
74
- "vue": "^3.5.32",
75
- "vue-router": "^5.0.4",
74
+ "vue": "^3.5.33",
75
+ "vue-router": "^5.0.6",
76
76
  "vueperslides": "^3.6.0",
77
77
  "vuex": "^4.1.0"
78
78
  },
@@ -47,11 +47,14 @@
47
47
  <script>
48
48
  import { objectifyClasses } from '../../utils/index'
49
49
  import { consoleError } from '../../utils/console'
50
+ import RippleMixin from '../../mixins/ripple'
50
51
  import WAccordionItem from './item.vue'
51
52
 
52
53
  export default {
53
54
  name: 'w-accordion',
54
55
 
56
+ mixins: [RippleMixin],
57
+
55
58
  props: {
56
59
  modelValue: { type: Array },
57
60
  color: { type: String },
@@ -94,7 +97,8 @@ export default {
94
97
  options: this.$props,
95
98
  registerItem: this.registerItem,
96
99
  unregisterItem: this.unregisterItem,
97
- getAccordionItem: this.getAccordionItem
100
+ getAccordionItem: this.getAccordionItem,
101
+ getAccordionNoRipple: () => this.noRipple
98
102
  }
99
103
  },
100
104
 
@@ -2,10 +2,11 @@
2
2
  .w-accordion__item(:class="itemClasses" :aria-expanded="accordionItem._expanded ? 'true' : 'false'")
3
3
  .w-accordion__item-title(
4
4
  @click="!accordionItem._disabled && toggleItem(accordionItem, $event)"
5
+ @pointerdown="onAccordionTitlePointerDown"
5
6
  @focus="$emit('focus', (accordionItem))"
6
7
  @keypress.enter="!accordionItem._disabled && toggleItem(accordionItem, $event)"
7
8
  :tabindex="!accordionItem._disabled && 0"
8
- :class="titleClasses")
9
+ :class="accordionTitleClasses")
9
10
  //- Expand icon on left.
10
11
  w-button.w-accordion__expand-icon(
11
12
  v-if="options.expandIcon && !options.expandIconRight"
@@ -70,11 +71,15 @@
70
71
 
71
72
  <script>
72
73
  import { useId } from 'vue'
74
+ import RippleMixin from '../../mixins/ripple'
75
+ import { isRippleEnabled } from '../../utils/ripple'
73
76
  import AccordionContent from './accordion-content.vue'
74
77
 
75
78
  export default {
76
79
  name: 'w-accordion-item',
77
80
 
81
+ mixins: [RippleMixin],
82
+
78
83
  setup () {
79
84
  return { accordionItemUid: useId() }
80
85
  },
@@ -88,21 +93,40 @@ export default {
88
93
  disabled: { type: Boolean }
89
94
  },
90
95
 
91
- inject: [
92
- 'options',
93
- 'titleClasses',
94
- 'contentClasses',
95
- 'onItemToggle',
96
- 'onEndOfCollapse',
97
- 'getOriginalItem',
98
- 'getAccordionItem',
99
- 'registerItem',
100
- 'unregisterItem'
101
- ],
96
+ inject: {
97
+ options: { from: 'options' },
98
+ titleClasses: { from: 'titleClasses' },
99
+ contentClasses: { from: 'contentClasses' },
100
+ onItemToggle: { from: 'onItemToggle' },
101
+ onEndOfCollapse: { from: 'onEndOfCollapse' },
102
+ getOriginalItem: { from: 'getOriginalItem' },
103
+ getAccordionItem: { from: 'getAccordionItem' },
104
+ registerItem: { from: 'registerItem' },
105
+ unregisterItem: { from: 'unregisterItem' },
106
+ getAccordionNoRipple: { from: 'getAccordionNoRipple', default: () => undefined }
107
+ },
102
108
 
103
109
  emits: ['focus'],
104
110
 
105
111
  computed: {
112
+ /** Per-item noRipple override, else parent `w-accordion` noRipple, else global config. */
113
+ rippleActive () {
114
+ let resolvedNoRipple
115
+ if (typeof this.noRipple === 'boolean') resolvedNoRipple = this.noRipple
116
+ else {
117
+ const p = this.getAccordionNoRipple?.()
118
+ resolvedNoRipple = typeof p === 'boolean' ? p : undefined
119
+ }
120
+ return isRippleEnabled(resolvedNoRipple ? false : undefined, this.$waveui)
121
+ },
122
+
123
+ accordionTitleClasses () {
124
+ return {
125
+ ...this.titleClasses,
126
+ 'w-ripple': this.rippleActive
127
+ }
128
+ },
129
+
106
130
  accordionItem: {
107
131
  get () {
108
132
  return this.getAccordionItem(this.accordionItemUid)
@@ -119,6 +143,12 @@ export default {
119
143
  },
120
144
 
121
145
  methods: {
146
+ onAccordionTitlePointerDown (e) {
147
+ if (this.accordionItem._disabled) return
148
+ if (e.target.closest?.('.w-accordion__expand-icon')) return
149
+ this.onRipple(e)
150
+ },
151
+
122
152
  toggleItem (item, e) {
123
153
  item._expanded = !item._expanded
124
154
 
@@ -20,7 +20,8 @@
20
20
  :is="hasRouter ? 'router-link' : 'a'"
21
21
  :to="hasRouter && item[itemRouteKey]"
22
22
  :href="item[itemRouteKey]"
23
- :class="color || null")
23
+ :class="breadcrumbLinkClasses"
24
+ @pointerdown="onRipple")
24
25
  slot(name="item" :item="item" :index="i + 1" :isLast="i === items.length - 1")
25
26
  component.w-breadcrumbs__item(
26
27
  v-else
@@ -29,7 +30,8 @@
29
30
  :to="hasRouter && item[itemRouteKey]"
30
31
  :href="item[itemRouteKey]"
31
32
  v-html="item[itemLabelKey]"
32
- :class="color || null")
33
+ :class="breadcrumbLinkClasses"
34
+ @pointerdown="onRipple")
33
35
 
34
36
  //- Current page when linkLastItem is false.
35
37
  slot(
@@ -43,8 +45,13 @@
43
45
  </template>
44
46
 
45
47
  <script>
48
+ import RippleMixin from '../mixins/ripple'
49
+
46
50
  export default {
47
51
  name: 'w-breadcrumbs',
52
+
53
+ mixins: [RippleMixin],
54
+
48
55
  props: {
49
56
  items: { type: Array, required: true },
50
57
  linkLastItem: { type: Boolean },
@@ -63,6 +70,10 @@ export default {
63
70
  emits: [],
64
71
 
65
72
  computed: {
73
+ breadcrumbLinkClasses () {
74
+ return [this.color || null, { 'w-ripple': this.rippleActive }]
75
+ },
76
+
66
77
  hasRouter () {
67
78
  return '$router' in this
68
79
  },
@@ -6,7 +6,8 @@ component.w-button(
6
6
  :class="classes"
7
7
  :disabled="!!disabled || null"
8
8
  v-bind="attrs"
9
- :style="styles")
9
+ :style="styles"
10
+ @pointerdown="onPointerDownRipple")
10
11
  w-icon(v-if="icon" v-bind="iconProps || {}") {{ icon }}
11
12
  slot(v-else)
12
13
  transition(name="scale-fade")
@@ -22,11 +23,15 @@ component.w-button(
22
23
  </template>
23
24
 
24
25
  <script>
26
+ import RippleMixin from '../../mixins/ripple'
27
+
25
28
  export default {
26
29
  // Fully handle the attrs and listeners manually for the case of a router link that has both a
27
30
  // route and onClick.
28
31
  inheritAttrs: false,
29
32
 
33
+ mixins: [RippleMixin],
34
+
30
35
  props: {
31
36
  color: { type: String },
32
37
  bgColor: { type: String },
@@ -67,6 +72,13 @@ export default {
67
72
 
68
73
  emits: [],
69
74
 
75
+ methods: {
76
+ onPointerDownRipple (e) {
77
+ if (this.disabled || this.loading || !this.rippleActive) return
78
+ this.onRipple(e)
79
+ }
80
+ },
81
+
70
82
  computed: {
71
83
  hasRouter () {
72
84
  return '$router' in this
@@ -112,6 +124,7 @@ export default {
112
124
  },
113
125
  classes () {
114
126
  return {
127
+ 'w-ripple': this.rippleActive,
115
128
  // If no color / bg color is set, set a primary color by default.
116
129
  'primary--bg': !this.bgColor && !this.color && !(this.outline || this.text),
117
130
  primary: !this.bgColor && !this.color && !this.dark && (this.outline || this.text),
@@ -246,6 +259,7 @@ $spinner-size: 40;
246
259
  content: '';
247
260
  position: absolute;
248
261
  inset: 0;
262
+ z-index: 0;
249
263
  opacity: 0;
250
264
  background-color: #000;
251
265
  border-radius: inherit;
@@ -53,7 +53,8 @@ export default {
53
53
  sm: { type: Boolean },
54
54
  md: { type: Boolean },
55
55
  lg: { type: Boolean },
56
- xl: { type: Boolean }
56
+ xl: { type: Boolean },
57
+ noRipple: { type: Boolean, default: undefined }
57
58
  },
58
59
 
59
60
  components: { ButtonPartial },
@@ -49,12 +49,16 @@ component(
49
49
  </template>
50
50
 
51
51
  <script>
52
- import FormElementMixin from '../mixins/form-elements'
52
+ import FormElementMixin, { useWaveUiFormIds } from '../mixins/form-elements'
53
53
 
54
54
  export default {
55
55
  name: 'w-checkbox',
56
56
  mixins: [FormElementMixin],
57
57
 
58
+ setup () {
59
+ return useWaveUiFormIds()
60
+ },
61
+
58
62
  inject: {
59
63
  wCheckboxes: { default: null }
60
64
  },
@@ -31,12 +31,16 @@ component(
31
31
 
32
32
  <script>
33
33
  import { reactive } from 'vue'
34
- import FormElementMixin from '../mixins/form-elements'
34
+ import FormElementMixin, { useWaveUiFormIds } from '../mixins/form-elements'
35
35
 
36
36
  export default {
37
37
  name: 'w-checkboxes',
38
38
  mixins: [FormElementMixin],
39
39
 
40
+ setup () {
41
+ return useWaveUiFormIds()
42
+ },
43
+
40
44
  props: {
41
45
  items: { type: Array, required: true }, // All the possible options.
42
46
  modelValue: { type: Array }, // v-model on selected option.
@@ -128,7 +128,7 @@ component(
128
128
  * - option to fit to the content using contenteditable div
129
129
  **/
130
130
 
131
- import FormElementMixin from '../mixins/form-elements'
131
+ import FormElementMixin, { useWaveUiFormIds } from '../mixins/form-elements'
132
132
  import { reactive } from 'vue'
133
133
 
134
134
  export default {
@@ -136,6 +136,10 @@ export default {
136
136
  mixins: [FormElementMixin],
137
137
  inheritAttrs: false, // The attrs should only be added to the input not the wrapper.
138
138
 
139
+ setup () {
140
+ return useWaveUiFormIds()
141
+ },
142
+
139
143
  props: {
140
144
  modelValue: { default: '' },
141
145
  type: { type: String, default: 'text' },
@@ -44,10 +44,13 @@ ul.w-list(:class="classes")
44
44
 
45
45
  <script>
46
46
  import { useId } from 'vue'
47
+ import RippleMixin from '../mixins/ripple'
47
48
 
48
49
  export default {
49
50
  name: 'w-list',
50
51
 
52
+ mixins: [RippleMixin],
53
+
51
54
  setup () {
52
55
  return { waveUiUseId: useId() }
53
56
  },
@@ -161,7 +164,13 @@ export default {
161
164
  },
162
165
 
163
166
  liLabelClasses (item) {
167
+ const rippleHost =
168
+ this.rippleActive &&
169
+ !item.disabled &&
170
+ (this.checklist || this.isSelectable || (this.nav && item[this.itemRouteKey]))
171
+
164
172
  return {
173
+ 'w-ripple': rippleHost,
165
174
  'w-list__item-label--disabled': item.disabled || (this.nav && !item[this.itemRouteKey] && !item.children),
166
175
  'w-list__item-label--active': (this.isSelectable && item._selected) || null,
167
176
  'w-list__item-label--focused': item._focused,
@@ -188,6 +197,7 @@ export default {
188
197
  // If selectable list, on mousedown select the item.
189
198
  const mousedown = this.isSelectable && (e => {
190
199
  e.stopPropagation()
200
+ this.onRipple(e)
191
201
  !li.disabled && this.selectItem(li)
192
202
  })
193
203
  // If selectable list, on enter key press select item.
@@ -230,6 +240,7 @@ export default {
230
240
  props.onFocus = () => (li._focused = true)
231
241
  props.onBlur = () => (li._focused = false)
232
242
  props.onInput = value => this.selectItem(li, value)
243
+ props.onPointerdown = e => this.onRipple(e)
233
244
  // When clicking on the checkbox component wrapper, trigger a focus & click on the checkbox.
234
245
  props.onClick = e => {
235
246
  const checkbox = e.target.querySelector('input[type="checkbox"]')
@@ -250,6 +261,7 @@ export default {
250
261
  if (!li.disabled && li[this.itemRouteKey]) {
251
262
  props.onKeydown = keydown
252
263
  props.onMousedown = mousedown
264
+ props.onPointerdown = e => this.onRipple(e)
253
265
 
254
266
  if (this.$router) {
255
267
  props.to = li[this.itemRouteKey]
@@ -47,11 +47,16 @@ component(
47
47
  </template>
48
48
 
49
49
  <script>
50
- import FormElementMixin from '../mixins/form-elements'
50
+ import FormElementMixin, { useWaveUiFormIds } from '../mixins/form-elements'
51
51
 
52
52
  export default {
53
53
  name: 'w-radio',
54
54
  mixins: [FormElementMixin],
55
+
56
+ setup () {
57
+ return useWaveUiFormIds()
58
+ },
59
+
55
60
  inject: { wRadios: { default: null } },
56
61
 
57
62
  props: {
@@ -30,12 +30,16 @@ component(
30
30
  </template>
31
31
 
32
32
  <script>
33
- import FormElementMixin from '../mixins/form-elements'
33
+ import FormElementMixin, { useWaveUiFormIds } from '../mixins/form-elements'
34
34
 
35
35
  export default {
36
36
  name: 'w-radios',
37
37
  mixins: [FormElementMixin],
38
38
 
39
+ setup () {
40
+ return useWaveUiFormIds()
41
+ },
42
+
39
43
  props: {
40
44
  items: { type: Array, required: true }, // All the possible options.
41
45
  modelValue: { type: [String, Number, Boolean] }, // v-model on selected option.
@@ -29,12 +29,16 @@ component(
29
29
  </template>
30
30
 
31
31
  <script>
32
- import FormElementMixin from '../mixins/form-elements'
32
+ import FormElementMixin, { useWaveUiFormIds } from '../mixins/form-elements'
33
33
 
34
34
  export default {
35
35
  name: 'w-rating',
36
36
  mixins: [FormElementMixin],
37
37
 
38
+ setup () {
39
+ return useWaveUiFormIds()
40
+ },
41
+
38
42
  props: {
39
43
  modelValue: {},
40
44
  max: { type: [Number, String], default: 5 },