wave-ui 4.2.1 → 4.2.2

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 (31) hide show
  1. package/dist/types/types/components/WBadge.d.ts +7 -0
  2. package/dist/types/types/components/WCard.d.ts +7 -0
  3. package/dist/types/types/components/WDrawer.d.ts +7 -0
  4. package/dist/types/types/components/WIcon.d.ts +7 -0
  5. package/dist/types/types/components/WProgress.d.ts +7 -0
  6. package/dist/types/types/components/WRating.d.ts +7 -0
  7. package/dist/types/types/components/WSpinner.d.ts +6 -0
  8. package/dist/types/types/components/WToolbar.d.ts +7 -0
  9. package/dist/wave-ui.cjs.js +2 -2
  10. package/dist/wave-ui.esm.js +478 -392
  11. package/dist/wave-ui.umd.js +2 -2
  12. package/package.json +13 -16
  13. package/src/wave-ui/components/w-alert.vue +7 -1
  14. package/src/wave-ui/components/w-badge.vue +6 -4
  15. package/src/wave-ui/components/w-breadcrumbs.vue +2 -2
  16. package/src/wave-ui/components/w-card.vue +3 -0
  17. package/src/wave-ui/components/w-checkbox.vue +1 -0
  18. package/src/wave-ui/components/w-dialog.vue +5 -0
  19. package/src/wave-ui/components/w-drawer.vue +7 -0
  20. package/src/wave-ui/components/w-icon.vue +4 -2
  21. package/src/wave-ui/components/w-input.vue +1 -0
  22. package/src/wave-ui/components/w-menu.vue +1 -1
  23. package/src/wave-ui/components/w-progress.vue +10 -1
  24. package/src/wave-ui/components/w-radio.vue +1 -0
  25. package/src/wave-ui/components/w-rating.vue +22 -19
  26. package/src/wave-ui/components/w-select.vue +11 -2
  27. package/src/wave-ui/components/w-spinner.vue +2 -1
  28. package/src/wave-ui/components/w-tag.vue +3 -3
  29. package/src/wave-ui/components/w-textarea.vue +1 -0
  30. package/src/wave-ui/components/w-toolbar.vue +2 -1
  31. package/src/wave-ui/components/w-tooltip.vue +3 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-ui",
3
- "version": "4.2.1",
3
+ "version": "4.2.2",
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",
@@ -48,18 +48,6 @@
48
48
  "vue framework",
49
49
  "ui"
50
50
  ],
51
- "scripts": {
52
- "dev": "vite",
53
- "build": "vite build --base /wave-ui/",
54
- "build-types": "tsc -p ./tsconfig.json",
55
- "build-bundle": "BUNDLE=true vite build && npm run build-types",
56
- "preview": "vite preview --base /wave-ui/",
57
- "lint": "biome check .",
58
- "lint:fix": "biome check --apply .",
59
- "format": "biome format .",
60
- "format:fix": "biome format --write .",
61
- "publish-doc": "npm run build && npm run build-bundle && git add . && git commit -m 'Publish documentation on Github.' && git push && git push --tag"
62
- },
63
51
  "devDependencies": {
64
52
  "@babel/core": "^7.29.0",
65
53
  "@biomejs/biome": "^2.4.13",
@@ -95,7 +83,16 @@
95
83
  "node": ">=16.0.0",
96
84
  "pnpm": ">=8.0.0"
97
85
  },
98
- "pnpm": {
99
- "strictPeerDependencies": false
86
+ "scripts": {
87
+ "dev": "vite",
88
+ "build": "vite build --base /wave-ui/",
89
+ "build-types": "tsc -p ./tsconfig.json",
90
+ "build-bundle": "BUNDLE=true vite build && npm run build-types",
91
+ "preview": "vite preview --base /wave-ui/",
92
+ "lint": "biome check .",
93
+ "lint:fix": "biome check --apply .",
94
+ "format": "biome format .",
95
+ "format:fix": "biome format --write .",
96
+ "publish-doc": "npm run build && npm run build-bundle && git add . && git commit -m 'Publish documentation on Github.' && git push && git push --tag"
100
97
  }
101
- }
98
+ }
@@ -1,5 +1,5 @@
1
1
  <template lang="pug">
2
- .w-alert(v-if="show" :class="classes")
2
+ .w-alert(v-if="show" :role="ariaRole" aria-atomic="true" :class="classes")
3
3
  //- Add a wrapper around the content when needed.
4
4
  template(v-if="type || icon || dismiss")
5
5
  w-icon.w-alert__icon(v-if="type || icon") {{ type ? typeIcon : icon }}
@@ -8,6 +8,7 @@
8
8
  w-button.w-alert__dismiss(
9
9
  v-if="dismiss"
10
10
  @click="$emit('update:modelValue', show = false);$emit('input', false);$emit('close', false)"
11
+ aria-label="Dismiss"
11
12
  icon="wi-cross"
12
13
  color="inherit"
13
14
  sm
@@ -80,6 +81,11 @@ export default {
80
81
  )
81
82
  },
82
83
 
84
+ // error/warning demand immediate attention; success/info/plain are polite.
85
+ ariaRole () {
86
+ return (this.error || this.warning) ? 'alert' : 'status'
87
+ },
88
+
83
89
  presetSize () {
84
90
  return (
85
91
  (this.xs && 'xs') ||
@@ -6,10 +6,11 @@
6
6
  v-if="modelValue"
7
7
  :class="classes"
8
8
  :style="styles"
9
- aria-atomic="true"
10
- aria-label="Badge"
11
- aria-live="polite"
12
- role="status")
9
+ :aria-hidden="dot ? 'true' : undefined"
10
+ :aria-atomic="dot ? undefined : 'true'"
11
+ :aria-label="dot ? undefined : (ariaLabel || (modelValue === true ? 'Badge' : String(modelValue)))"
12
+ :aria-live="dot ? undefined : 'polite'"
13
+ :role="dot ? undefined : 'status'")
13
14
  slot(v-if="!dot" name="badge") {{ modelValue === true ? '' : (modelValue || '') }}
14
15
  </template>
15
16
 
@@ -19,6 +20,7 @@ export default {
19
20
 
20
21
  props: {
21
22
  modelValue: { default: true },
23
+ ariaLabel: { type: String }, // Override the default aria-label (defaults to the badge value).
22
24
  xs: { type: Boolean },
23
25
  sm: { type: Boolean },
24
26
  md: { type: Boolean },
@@ -1,5 +1,5 @@
1
1
  <template lang="pug">
2
- .w-breadcrumbs(:class="classes")
2
+ nav.w-breadcrumbs(:class="classes" aria-label="Breadcrumb")
3
3
  template(v-for="(item, i) in items")
4
4
  //- Separator.
5
5
  span.w-breadcrumbs__separator(
@@ -41,7 +41,7 @@
41
41
  :item="item"
42
42
  :index="i + 1"
43
43
  :is-last="i === items.length - 1")
44
- span(v-else :key="`${i}f`" v-html="item[itemLabelKey]")
44
+ span(v-else :key="`${i}f`" aria-current="page" v-html="item[itemLabelKey]")
45
45
  </template>
46
46
 
47
47
  <script>
@@ -2,11 +2,13 @@
2
2
  .w-card(:class="classes")
3
3
  .w-card__title(
4
4
  v-if="$slots.title"
5
+ :id="titleId || undefined"
5
6
  :class="{ 'w-card__title--has-toolbar': $slots.title && titleHasToolbar, ...titleClasses }")
6
7
  slot(name="title")
7
8
  .w-card__title(
8
9
  v-else-if="title"
9
10
  v-html="title"
11
+ :id="titleId || undefined"
10
12
  :class="{ 'w-card__title--has-toolbar': $slots.title && titleHasToolbar, ...titleClasses }")
11
13
  w-image.w-card__image(v-if="image" :src="image" v-bind="imgProps")
12
14
  slot(name="image-content")
@@ -35,6 +37,7 @@ export default {
35
37
  imageProps: { type: Object },
36
38
  titleClass: { type: [String, Object, Array] },
37
39
  contentClass: { type: [String, Object, Array] },
40
+ titleId: { type: String },
38
41
  dark: { type: Boolean },
39
42
  light: { type: Boolean }
40
43
  },
@@ -20,6 +20,7 @@ component(
20
20
  @change="onInput() /* Edge doesn't emit an `input` event on checkbox/radio/select change */"
21
21
  @keypress.enter="onInput"
22
22
  :aria-checked="isChecked || 'false'"
23
+ :aria-invalid="valid === false ? 'true' : undefined"
23
24
  role="checkbox")
24
25
  template(v-if="hasLabel && labelOnLeft")
25
26
  label.w-checkbox__label.w-form-el-shakable.pr2(
@@ -20,6 +20,10 @@ w-overlay.w-dialog(
20
20
  :title-class="titleClass"
21
21
  :content-class="contentClass"
22
22
  :title="title || undefined"
23
+ :title-id="(title || $slots.title) ? titleId : undefined"
24
+ role="dialog"
25
+ aria-modal="true"
26
+ :aria-labelledby="(title || $slots.title) ? titleId : undefined"
23
27
  :style="contentStyles")
24
28
  template(#title v-if="$slots.title")
25
29
  slot(name="title")
@@ -67,6 +71,7 @@ export default {
67
71
 
68
72
  data () {
69
73
  return {
74
+ titleId: `w-dialog-title-${Math.random().toString(36).slice(2, 9)}`,
70
75
  showWrapper: this.modelValue,
71
76
  showContent: this.modelValue
72
77
  }
@@ -21,6 +21,9 @@
21
21
  v-if="showDrawer"
22
22
  ref="drawer"
23
23
  :is="tag || 'aside'"
24
+ role="dialog"
25
+ :aria-modal="!noOverlay ? 'true' : undefined"
26
+ :aria-label="ariaLabel || undefined"
24
27
  :class="drawerClasses"
25
28
  :style="styles"
26
29
  :tabindex="noOverlay ? 0 : null"
@@ -46,6 +49,9 @@
46
49
  v-if="showDrawer"
47
50
  ref="drawer"
48
51
  :is="tag || 'aside'"
52
+ role="dialog"
53
+ :aria-modal="!noOverlay ? 'true' : undefined"
54
+ :aria-label="ariaLabel || undefined"
49
55
  :class="drawerClasses"
50
56
  :style="styles"
51
57
  :tabindex="noOverlay ? 0 : null"
@@ -85,6 +91,7 @@ export default {
85
91
  overlayColor: { type: String },
86
92
  overlayOpacity: { type: [Number, String, Boolean] },
87
93
  drawerClass: { type: String },
94
+ ariaLabel: { type: String }, // Accessible label for the drawer dialog (recommended).
88
95
  tag: { type: String, default: 'aside' },
89
96
  dark: { type: Boolean },
90
97
  light: { type: Boolean }
@@ -2,8 +2,9 @@
2
2
  component.w-icon(
3
3
  :is="tag || 'i'"
4
4
  :class="classes"
5
- role="icon"
6
- aria-hidden="true"
5
+ :role="ariaLabel ? 'img' : undefined"
6
+ :aria-label="ariaLabel || undefined"
7
+ :aria-hidden="ariaLabel ? undefined : 'true'"
7
8
  :style="readIcon() /* Always reacting to slot change when called from template. */ && styles")
8
9
  template(v-if="hasLigature") {{ icon }}
9
10
  </template>
@@ -14,6 +15,7 @@ export default {
14
15
 
15
16
  props: {
16
17
  tag: { type: String, default: 'i' },
18
+ ariaLabel: { type: String }, // When set, exposes the icon as role="img" with this accessible label.
17
19
  color: { type: String },
18
20
  bgColor: { type: String },
19
21
  xs: { type: Boolean },
@@ -46,6 +46,7 @@ component(
46
46
  :maxlength="maxlength || null"
47
47
  :readonly="isReadonly || null"
48
48
  :aria-readonly="isReadonly ? 'true' : 'false'"
49
+ :aria-invalid="valid === false ? 'true' : undefined"
49
50
  :disabled="isDisabled || null"
50
51
  :required="required || null"
51
52
  :tabindex="tabindex || null"
@@ -1,5 +1,5 @@
1
1
  <template lang="pug">
2
- slot(name="activator")
2
+ slot(name="activator" :is-open="detachableVisible")
3
3
  slot(v-if="!$slots.activator")
4
4
  teleport(v-if="detachableDomReady" :to="teleportTarget" :disabled="!teleportTarget")
5
5
  transition(:name="transitionName" appear @after-enter="onDetachableAfterEnter" @after-leave="onAfterLeave")
@@ -1,5 +1,13 @@
1
1
  <template lang="pug">
2
- .w-progress(:class="classes" :style="styles")
2
+ .w-progress(
3
+ role="progressbar"
4
+ :aria-valuenow="progressValue > -1 ? progressValue : undefined"
5
+ :aria-valuemin="progressValue > -1 ? 0 : undefined"
6
+ :aria-valuemax="progressValue > -1 ? 100 : undefined"
7
+ :aria-valuetext="progressValue === -1 ? 'Loading' : undefined"
8
+ :aria-label="ariaLabel || undefined"
9
+ :class="classes"
10
+ :style="styles")
3
11
  //- Linear progress.
4
12
  .w-progress__progress(
5
13
  v-if="!circle"
@@ -43,6 +51,7 @@ export default {
43
51
 
44
52
  props: {
45
53
  modelValue: { type: [Number, String, Boolean], default: -1 },
54
+ ariaLabel: { type: String }, // Accessible name for the progress bar (e.g. 'Upload progress').
46
55
  label: { type: Boolean },
47
56
  roundCap: { type: Boolean },
48
57
  color: { type: String, default: 'primary' },
@@ -18,6 +18,7 @@ component(
18
18
  @focus="$emit('focus', $event)"
19
19
  @change="onInput($event) /* Edge doesn't emit an `input` event on checkbox/radio/select change */"
20
20
  :aria-checked="inputValue || 'false'"
21
+ :aria-invalid="valid === false ? 'true' : undefined"
21
22
  role="radio")
22
23
  template(v-if="hasLabel && labelOnLeft")
23
24
  label.w-radio__label.w-form-el-shakable.pr2(
@@ -7,25 +7,27 @@ component(
7
7
  @reset="$emit('update:modelValue', rating = null);$emit('input', null)"
8
8
  :class="classes")
9
9
  input(:id="inputId" :name="inputName" type="hidden" :value="rating")
10
- template(v-for="i in max" :key="i")
11
- slot(v-if="$slots.item" name="item" :index="i + 1")
12
- button.w-rating__button(
13
- :disabled="isDisabled || isReadonly"
14
- @mouseenter="hover = i"
15
- @mouseleave="hover = 0"
16
- @click="onButtonClick(i)"
17
- @focus="onFocus"
18
- @blur="onBlur"
19
- @keydown="onKeydown"
20
- :class="buttonClasses(i)"
21
- type="button"
22
- :tabindex="i === 1 ? 0 : -1")
23
- i.w-icon(
24
- v-if="i - 1 === ~~rating && rating - ~~rating"
25
- role="icon"
26
- :class="`${icon} ${color}`"
27
- aria-hidden="true"
28
- :style="halfStarStyle")
10
+ div(role="group" :aria-label="ariaLabel || 'Rating'")
11
+ template(v-for="i in max" :key="i")
12
+ slot(v-if="$slots.item" name="item" :index="i + 1")
13
+ button.w-rating__button(
14
+ :disabled="isDisabled || isReadonly"
15
+ @mouseenter="hover = i"
16
+ @mouseleave="hover = 0"
17
+ @click="onButtonClick(i)"
18
+ @focus="onFocus"
19
+ @blur="onBlur"
20
+ @keydown="onKeydown"
21
+ :aria-label="`${i} of ${max}`"
22
+ :aria-pressed="(rating >= i).toString()"
23
+ :class="buttonClasses(i)"
24
+ type="button"
25
+ :tabindex="i === 1 ? 0 : -1")
26
+ i.w-icon(
27
+ v-if="i - 1 === ~~rating && rating - ~~rating"
28
+ :class="`${icon} ${color}`"
29
+ aria-hidden="true"
30
+ :style="halfStarStyle")
29
31
  </template>
30
32
 
31
33
  <script>
@@ -44,6 +46,7 @@ export default {
44
46
 
45
47
  props: {
46
48
  modelValue: {},
49
+ ariaLabel: { type: String }, // Accessible group label (default: 'Rating').
47
50
  max: { type: [Number, String], default: 5 },
48
51
  color: { type: String, default: 'primary' },
49
52
  bgColor: { type: String },
@@ -31,7 +31,7 @@ component(
31
31
  aria-haspopup="listbox"
32
32
  :aria-expanded="showMenu ? 'true' : 'false'"
33
33
  :aria-owns="selectListId"
34
- :aria-activedescendant="`${selectListId}_item-1`"
34
+ :aria-activedescendant="activeDescendantId"
35
35
  :class="inputWrapClasses")
36
36
  slot(name="icon-left")
37
37
  w-icon.w-select__icon.w-select__icon--inner-left(
@@ -208,7 +208,7 @@ export default {
208
208
  class: { 'w-select__selection--placeholder': !this.$slots.selection && !this.selectionString && this.placeholder },
209
209
  disabled: this.isDisabled || null,
210
210
  readonly: true,
211
- ariareadonly: 'true',
211
+ 'aria-readonly': 'true',
212
212
  tabindex: this.tabindex ?? null,
213
213
  contenteditable: this.isDisabled || this.isReadonly ? 'false' : 'true'
214
214
  }
@@ -218,6 +218,15 @@ export default {
218
218
  item => item[this.itemValueKey] !== undefined ? item[this.itemLabelKey] : (item[this.itemLabelKey] ?? item)
219
219
  ).join(', ')
220
220
  },
221
+
222
+ // Points to the focused/selected list item when the menu is open.
223
+ activeDescendantId () {
224
+ if (!this.showMenu || !this.selectListId) return undefined
225
+ const first = this.inputValue[0]
226
+ if (!first) return `${this.selectListId}_item-1`
227
+ const idx = this.selectItems.findIndex(item => item.value === first[this.itemValueKey] || item.value === first._value)
228
+ return `${this.selectListId}_item-${idx >= 0 ? idx + 1 : 1}`
229
+ },
221
230
  selectionHtml () {
222
231
  if (!this.inputValue.length) return this.placeholder || ''
223
232
  if (this.$slots.selection) return ''
@@ -1,5 +1,5 @@
1
1
  <template lang="pug">
2
- .w-spinner(v-if="modelValue || modelValue === undefined" :class="classes" :style="styles")
2
+ .w-spinner(v-if="modelValue || modelValue === undefined" role="status" :aria-label="ariaLabel || 'Loading'" :class="classes" :style="styles")
3
3
  span(v-if="isThreeDots")
4
4
  </template>
5
5
 
@@ -8,6 +8,7 @@ export default {
8
8
  name: 'w-spinner',
9
9
  props: {
10
10
  modelValue: {},
11
+ ariaLabel: { type: String }, // Accessible label announced by screen readers (default: 'Loading').
11
12
  color: { type: String, default: 'primary' },
12
13
  xs: { type: Boolean },
13
14
  sm: { type: Boolean },
@@ -9,11 +9,11 @@ span.w-tag(
9
9
  :tabindex="modelValue !== -1 && 0"
10
10
  :style="styles")
11
11
  slot
12
- i(
12
+ button(
13
13
  v-if="closable && modelValue"
14
14
  @click.stop="$emit('update:modelValue', false);$emit('input', false)"
15
- role="icon"
16
- aria-hidden="true"
15
+ type="button"
16
+ aria-label="Remove"
17
17
  class="w-icon w-tag__closable wi-cross")
18
18
  </template>
19
19
 
@@ -38,6 +38,7 @@ component(
38
38
  :cols="cols || null"
39
39
  :readonly="isReadonly || null"
40
40
  :aria-readonly="isReadonly ? 'true' : 'false'"
41
+ :aria-invalid="valid === false ? 'true' : undefined"
41
42
  :disabled="isDisabled || null"
42
43
  :required="required || null"
43
44
  :tabindex="tabindex || null")
@@ -1,5 +1,5 @@
1
1
  <template lang="pug">
2
- .w-toolbar(:class="classes" :style="styles")
2
+ .w-toolbar(role="toolbar" :aria-label="ariaLabel || undefined" :class="classes" :style="styles")
3
3
  slot
4
4
  </template>
5
5
 
@@ -8,6 +8,7 @@ export default {
8
8
  name: 'w-toolbar',
9
9
 
10
10
  props: {
11
+ ariaLabel: { type: String }, // Accessible label for the toolbar (recommended when multiple toolbars are present).
11
12
  color: { type: String },
12
13
  bgColor: { type: String },
13
14
  absolute: { type: Boolean },
@@ -1,11 +1,13 @@
1
1
  <template lang="pug">
2
- slot(name="activator")
2
+ slot(name="activator" :tooltip-id="tooltipInstanceId")
3
3
  slot(v-if="!$slots.activator")
4
4
  teleport(v-if="detachableDomReady" :to="teleportTarget" :disabled="!teleportTarget")
5
5
  transition(:name="transitionName" appear @after-enter="onDetachableAfterEnter" @after-leave="onAfterLeave")
6
6
  .w-tooltip(
7
7
  v-if="detachableVisible"
8
8
  ref="detachable"
9
+ :id="tooltipInstanceId"
10
+ role="tooltip"
9
11
  :key="tooltipInstanceId"
10
12
  :class="classes"
11
13
  :style="styles")