vueless 1.2.15-beta.3 → 1.2.15-beta.4
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/locales/en.json +1 -1
- package/package.json +1 -1
- package/ui.container-accordion-item/UAccordionItem.vue +1 -5
- package/ui.container-accordion-item/tests/UAccordionItem.test.ts +5 -5
- package/ui.data-table/config.ts +2 -0
- package/ui.data-table/storybook/stories.ts +3 -1
- package/ui.form-calendar/UCalendar.vue +4 -0
- package/ui.form-calendar/config.ts +5 -0
- package/ui.form-checkbox/UCheckbox.vue +23 -4
- package/ui.form-checkbox/config.ts +4 -0
- package/ui.form-checkbox/tests/UCheckbox.test.ts +1 -1
- package/ui.form-date-picker/config.ts +5 -0
- package/ui.form-input/UInput.vue +9 -3
- package/ui.form-input-counter/UInputCounter.vue +9 -0
- package/ui.form-input-counter/config.ts +5 -0
- package/ui.form-input-password/UInputPassword.vue +11 -12
- package/ui.form-input-password/tests/UInputPassword.test.ts +2 -2
- package/ui.form-label/ULabel.vue +5 -1
- package/ui.form-label/types.ts +5 -0
- package/ui.form-listbox/UListbox.vue +37 -11
- package/ui.form-select/USelect.vue +37 -2
- package/ui.form-select/tests/USelect.test.ts +3 -1
- package/ui.form-switch/USwitch.vue +20 -7
- package/ui.form-switch/config.ts +2 -0
- package/ui.form-switch/tests/USwitch.test.ts +6 -4
- package/ui.form-textarea/UTextarea.vue +15 -5
- package/ui.form-textarea/tests/UTextarea.test.ts +1 -1
- package/ui.navigation-pagination/UPagination.vue +14 -0
- package/ui.navigation-pagination/config.ts +9 -0
- package/ui.text-alert/UAlert.vue +8 -0
- package/ui.text-alert/config.ts +4 -0
package/locales/en.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"USelect":{"listIsEmpty":"List is empty.","noDataToShow":"No data to show.","clear":"clear","addMore":"Add more..."},"USwitch":{"inactive":"Off","active":"On"},"UInputFile":{"sizeError":"File size is too big.","formatError":"Format is not supported.","noFile":"No file selected","uploadFile":"Choose file"},"UListbox":{"noDataToShow":"No data to show.","add":"Add","search":"Search..."},"UModalConfirm":{"confirm":"Confirm","cancel":"Cancel"},"UTable":{"noData":"There is no data in the table."},"UCalendar":{"today":"Today","yesterday":"Yesterday","tomorrow":"Tomorrow","weekdays":{"shorthand":{"sunday":"Sun","monday":"Mon","tuesday":"Tue","wednesday":"Wed","thursday":"Thu","friday":"Fri","saturday":"Sat"},"longhand":{"sunday":"Sunday","monday":"Monday","tuesday":"Tuesday","wednesday":"Wednesday","thursday":"Thursday","friday":"Friday","saturday":"Saturday"},"userFormat":{"sunday":"Sunday","monday":"Monday","tuesday":"Tuesday","wednesday":"Wednesday","thursday":"Thursday","friday":"Friday","saturday":"Saturday"}},"months":{"shorthand":{"january":"Jan","february":"Feb","march":"Mar","april":"Apr","may":"May","june":"Jun","july":"Jul","august":"Aug","september":"Sep","october":"Oct","november":"Nov","december":"Dec"},"longhand":{"january":"January","february":"February","march":"March","april":"April","may":"May","june":"June","july":"July","august":"August","september":"September","october":"October","november":"November","december":"December"},"userFormat":{"january":"January","february":"February","march":"March","april":"April","may":"May","june":"June","july":"July","august":"August","september":"September","october":"October","november":"November","december":"December"}},"timeLabel":"Time","okLabel":"Ok"},"UDatePicker":{"today":"Today","yesterday":"Yesterday","tomorrow":"Tomorrow","weekdays":{"shorthand":{"sunday":"Sun","monday":"Mon","tuesday":"Tue","wednesday":"Wed","thursday":"Thu","friday":"Fri","saturday":"Sat"},"longhand":{"sunday":"Sunday","monday":"Monday","tuesday":"Tuesday","wednesday":"Wednesday","thursday":"Thursday","friday":"Friday","saturday":"Saturday"},"userFormat":{"sunday":"Sunday","monday":"Monday","tuesday":"Tuesday","wednesday":"Wednesday","thursday":"Thursday","friday":"Friday","saturday":"Saturday"}},"months":{"shorthand":{"january":"Jan","february":"Feb","march":"Mar","april":"Apr","may":"May","june":"Jun","july":"Jul","august":"Aug","september":"Sep","october":"Oct","november":"Nov","december":"Dec"},"longhand":{"january":"January","february":"February","march":"March","april":"April","may":"May","june":"June","july":"July","august":"August","september":"September","october":"October","november":"November","december":"December"},"userFormat":{"january":"January","february":"February","march":"March","april":"April","may":"May","june":"June","july":"July","august":"August","september":"September","october":"October","november":"November","december":"December"}},"timeLabel":"Time","okLabel":"Ok"},"UDatePickerRange":{"ownRange":"Own range","week":"Week","month":"Month","quarter":"Quarter","year":"Year","dateFormatWithDot":"The date should be in format 'dd.mm.yyyy'.","notCorrectMonthNumber":"Wrong month number.","notCorrectDayNumber":"Wrong day in month.","fromDateGraterThanSecond":"The 'from' date should be less than the 'to' date.","toDateSmallerThanFirst":"The 'to' date should be greater than the 'from' date.","today":"Today","yesterday":"Yesterday","tomorrow":"Tomorrow","weekdays":{"shorthand":{"sunday":"Sun","monday":"Mon","tuesday":"Tue","wednesday":"Wed","thursday":"Thu","friday":"Fri","saturday":"Sat"},"longhand":{"sunday":"Sunday","monday":"Monday","tuesday":"Tuesday","wednesday":"Wednesday","thursday":"Thursday","friday":"Friday","saturday":"Saturday"},"userFormat":{"sunday":"Sunday","monday":"Monday","tuesday":"Tuesday","wednesday":"Wednesday","thursday":"Thursday","friday":"Friday","saturday":"Saturday"}},"months":{"shorthand":{"january":"Jan","february":"Feb","march":"Mar","april":"Apr","may":"May","june":"Jun","july":"Jul","august":"Aug","september":"Sep","october":"Oct","november":"Nov","december":"Dec"},"longhand":{"january":"January","february":"February","march":"March","april":"April","may":"May","june":"June","july":"July","august":"August","september":"September","october":"October","november":"November","december":"December"},"userFormat":{"january":"January","february":"February","march":"March","april":"April","may":"May","june":"June","july":"July","august":"August","september":"September","october":"October","november":"November","december":"December"}}},"UDataList":{"emptyTitle":"","emptyDescription":"There is no data in the list."},"UNotify":{"success":{"default":"Operation successful."},"warning":{"default":"Operation warning."},"error":{"default":"Operation error."}}}
|
|
1
|
+
{"USelect":{"listIsEmpty":"List is empty.","noDataToShow":"No data to show.","clear":"clear","addMore":"Add more..."},"USwitch":{"switch":"Switch","inactive":"Off","active":"On"},"UInputFile":{"sizeError":"File size is too big.","formatError":"Format is not supported.","noFile":"No file selected","uploadFile":"Choose file"},"UListbox":{"noDataToShow":"No data to show.","add":"Add","search":"Search..."},"UModalConfirm":{"confirm":"Confirm","cancel":"Cancel"},"UTable":{"noData":"There is no data in the table.","checkbox":"Checkbox"},"UCalendar":{"today":"Today","yesterday":"Yesterday","tomorrow":"Tomorrow","weekdays":{"shorthand":{"sunday":"Sun","monday":"Mon","tuesday":"Tue","wednesday":"Wed","thursday":"Thu","friday":"Fri","saturday":"Sat"},"longhand":{"sunday":"Sunday","monday":"Monday","tuesday":"Tuesday","wednesday":"Wednesday","thursday":"Thursday","friday":"Friday","saturday":"Saturday"},"userFormat":{"sunday":"Sunday","monday":"Monday","tuesday":"Tuesday","wednesday":"Wednesday","thursday":"Thursday","friday":"Friday","saturday":"Saturday"}},"months":{"shorthand":{"january":"Jan","february":"Feb","march":"Mar","april":"Apr","may":"May","june":"Jun","july":"Jul","august":"Aug","september":"Sep","october":"Oct","november":"Nov","december":"Dec"},"longhand":{"january":"January","february":"February","march":"March","april":"April","may":"May","june":"June","july":"July","august":"August","september":"September","october":"October","november":"November","december":"December"},"userFormat":{"january":"January","february":"February","march":"March","april":"April","may":"May","june":"June","july":"July","august":"August","september":"September","october":"October","november":"November","december":"December"}},"timeLabel":"Time","okLabel":"Ok","previousYear":"Previous Year","nextYear":"Next Year","previousMonth":"Previous Month","nextMonth":"Next Month"},"UDatePicker":{"today":"Today","yesterday":"Yesterday","tomorrow":"Tomorrow","weekdays":{"shorthand":{"sunday":"Sun","monday":"Mon","tuesday":"Tue","wednesday":"Wed","thursday":"Thu","friday":"Fri","saturday":"Sat"},"longhand":{"sunday":"Sunday","monday":"Monday","tuesday":"Tuesday","wednesday":"Wednesday","thursday":"Thursday","friday":"Friday","saturday":"Saturday"},"userFormat":{"sunday":"Sunday","monday":"Monday","tuesday":"Tuesday","wednesday":"Wednesday","thursday":"Thursday","friday":"Friday","saturday":"Saturday"}},"months":{"shorthand":{"january":"Jan","february":"Feb","march":"Mar","april":"Apr","may":"May","june":"Jun","july":"Jul","august":"Aug","september":"Sep","october":"Oct","november":"Nov","december":"Dec"},"longhand":{"january":"January","february":"February","march":"March","april":"April","may":"May","june":"June","july":"July","august":"August","september":"September","october":"October","november":"November","december":"December"},"userFormat":{"january":"January","february":"February","march":"March","april":"April","may":"May","june":"June","july":"July","august":"August","september":"September","october":"October","november":"November","december":"December"}},"timeLabel":"Time","okLabel":"Ok","previousYear":"Previous Year","nextYear":"Next Year","previousMonth":"Previous Month","nextMonth":"Next Month"},"UDatePickerRange":{"ownRange":"Own range","week":"Week","month":"Month","quarter":"Quarter","year":"Year","dateFormatWithDot":"The date should be in format 'dd.mm.yyyy'.","notCorrectMonthNumber":"Wrong month number.","notCorrectDayNumber":"Wrong day in month.","fromDateGraterThanSecond":"The 'from' date should be less than the 'to' date.","toDateSmallerThanFirst":"The 'to' date should be greater than the 'from' date.","today":"Today","yesterday":"Yesterday","tomorrow":"Tomorrow","weekdays":{"shorthand":{"sunday":"Sun","monday":"Mon","tuesday":"Tue","wednesday":"Wed","thursday":"Thu","friday":"Fri","saturday":"Sat"},"longhand":{"sunday":"Sunday","monday":"Monday","tuesday":"Tuesday","wednesday":"Wednesday","thursday":"Thursday","friday":"Friday","saturday":"Saturday"},"userFormat":{"sunday":"Sunday","monday":"Monday","tuesday":"Tuesday","wednesday":"Wednesday","thursday":"Thursday","friday":"Friday","saturday":"Saturday"}},"months":{"shorthand":{"january":"Jan","february":"Feb","march":"Mar","april":"Apr","may":"May","june":"Jun","july":"Jul","august":"Aug","september":"Sep","october":"Oct","november":"Nov","december":"Dec"},"longhand":{"january":"January","february":"February","march":"March","april":"April","may":"May","june":"June","july":"July","august":"August","september":"September","october":"October","november":"November","december":"December"},"userFormat":{"january":"January","february":"February","march":"March","april":"April","may":"May","june":"June","july":"July","august":"August","september":"September","october":"October","november":"November","december":"December"}}},"UDataList":{"emptyTitle":"","emptyDescription":"There is no data in the list."},"UNotify":{"success":{"default":"Operation successful."},"warning":{"default":"Operation warning."},"error":{"default":"Operation error."}}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vueless",
|
|
3
|
-
"version": "1.2.15-beta.
|
|
3
|
+
"version": "1.2.15-beta.4",
|
|
4
4
|
"description": "Vue Styleless UI Component Library, powered by Tailwind CSS.",
|
|
5
5
|
"author": "Johnny Grid <hello@vueless.com> (https://vueless.com)",
|
|
6
6
|
"homepage": "https://vueless.com",
|
|
@@ -145,11 +145,7 @@ const {
|
|
|
145
145
|
</slot>
|
|
146
146
|
</div>
|
|
147
147
|
|
|
148
|
-
<div
|
|
149
|
-
v-if="description || hasSlotContent(slots['description'])"
|
|
150
|
-
:id="`description-${elementId}`"
|
|
151
|
-
v-bind="descriptionAttrs"
|
|
152
|
-
>
|
|
148
|
+
<div v-if="description || hasSlotContent(slots['description'])" v-bind="descriptionAttrs">
|
|
153
149
|
<!--
|
|
154
150
|
@slot Use it to add custom description content.
|
|
155
151
|
@binding {string} description
|
|
@@ -91,7 +91,7 @@ describe("UAccordionItem", () => {
|
|
|
91
91
|
},
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
-
expect(component.find(`[
|
|
94
|
+
expect(component.find(`[vl-key='description']`).exists()).toBe(true);
|
|
95
95
|
});
|
|
96
96
|
|
|
97
97
|
// DataTest prop
|
|
@@ -381,13 +381,13 @@ describe("UAccordionItem", () => {
|
|
|
381
381
|
},
|
|
382
382
|
});
|
|
383
383
|
|
|
384
|
-
expect(component.find("[
|
|
384
|
+
expect(component.find("[vl-key='description']").classes()).not.toContain("opacity-100");
|
|
385
385
|
|
|
386
386
|
await component.find(`.${slotClass}`).trigger("click");
|
|
387
|
-
expect(component.find("[
|
|
387
|
+
expect(component.find("[vl-key='description']").classes()).toContain("opacity-100");
|
|
388
388
|
|
|
389
389
|
await component.find(`.${slotClass}`).trigger("click");
|
|
390
|
-
expect(component.find("[
|
|
390
|
+
expect(component.find("[vl-key='description']").classes()).not.toContain("opacity-100");
|
|
391
391
|
});
|
|
392
392
|
|
|
393
393
|
it("description slot content is always visible when slot is provided", () => {
|
|
@@ -461,7 +461,7 @@ describe("UAccordionItem", () => {
|
|
|
461
461
|
},
|
|
462
462
|
});
|
|
463
463
|
|
|
464
|
-
const descriptionElement = component.find("[
|
|
464
|
+
const descriptionElement = component.find("[vl-key='description']");
|
|
465
465
|
|
|
466
466
|
// Initially not opened
|
|
467
467
|
expect(descriptionElement.classes()).not.toContain(openedClass);
|
package/ui.data-table/config.ts
CHANGED
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
} from "../../utils/storybook";
|
|
8
8
|
import { getRandomId } from "../../utils/helper";
|
|
9
9
|
|
|
10
|
+
import defaultConfig from "../config";
|
|
11
|
+
|
|
10
12
|
import UTable from "../UTable.vue";
|
|
11
13
|
import UButton from "../../ui.button/UButton.vue";
|
|
12
14
|
import ULink from "../../ui.button-link/ULink.vue";
|
|
@@ -731,7 +733,7 @@ export const EmptyStateSlot = DefaultTemplate.bind({});
|
|
|
731
733
|
EmptyStateSlot.args = {
|
|
732
734
|
rows: [],
|
|
733
735
|
config: {
|
|
734
|
-
i18n: { noData: "Fetching data..." },
|
|
736
|
+
i18n: { ...defaultConfig.i18n, noData: "Fetching data..." },
|
|
735
737
|
bodyEmptyStateCell: "py-10",
|
|
736
738
|
},
|
|
737
739
|
slotTemplate: `
|
|
@@ -829,6 +829,7 @@ const {
|
|
|
829
829
|
color="grayscale"
|
|
830
830
|
variant="ghost"
|
|
831
831
|
:icon="config.defaults.prevYearIcon"
|
|
832
|
+
:aria-label="localeMessages.previousYear"
|
|
832
833
|
v-bind="nextPrevButtonAttrs"
|
|
833
834
|
:data-test="getDataTest('prev-year')"
|
|
834
835
|
@mousedown.prevent.capture
|
|
@@ -841,6 +842,7 @@ const {
|
|
|
841
842
|
color="grayscale"
|
|
842
843
|
variant="ghost"
|
|
843
844
|
:icon="config.defaults.prevIcon"
|
|
845
|
+
:aria-label="localeMessages.previousMonth"
|
|
844
846
|
v-bind="nextPrevButtonAttrs"
|
|
845
847
|
:data-test="getDataTest('prev')"
|
|
846
848
|
@mousedown.prevent.capture
|
|
@@ -866,6 +868,7 @@ const {
|
|
|
866
868
|
color="grayscale"
|
|
867
869
|
variant="ghost"
|
|
868
870
|
:icon="config.defaults.nextIcon"
|
|
871
|
+
:aria-label="localeMessages.nextMonth"
|
|
869
872
|
v-bind="nextPrevButtonAttrs"
|
|
870
873
|
:data-test="getDataTest('next')"
|
|
871
874
|
@mousedown.prevent.capture
|
|
@@ -879,6 +882,7 @@ const {
|
|
|
879
882
|
color="grayscale"
|
|
880
883
|
variant="ghost"
|
|
881
884
|
:icon="config.defaults.nextYearIcon"
|
|
885
|
+
:aria-label="localeMessages.nextYear"
|
|
882
886
|
v-bind="nextPrevButtonAttrs"
|
|
883
887
|
:data-test="getDataTest('next-year')"
|
|
884
888
|
@mousedown.prevent.capture
|
|
@@ -144,6 +144,11 @@ export default /*tw*/ {
|
|
|
144
144
|
},
|
|
145
145
|
timeLabel: "Time",
|
|
146
146
|
okLabel: "Ok",
|
|
147
|
+
/* These are used for a11y. */
|
|
148
|
+
previousYear: "Previous Year",
|
|
149
|
+
nextYear: "Next Year",
|
|
150
|
+
previousMonth: "Previous Month",
|
|
151
|
+
nextMonth: "Next Month",
|
|
147
152
|
},
|
|
148
153
|
defaults: {
|
|
149
154
|
userDateFormat: "j F, Y",
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { inject, ref, onMounted, computed, watchEffect, toValue, useId } from "vue";
|
|
2
|
+
import { inject, ref, onMounted, computed, watchEffect, toValue, useId, useSlots } from "vue";
|
|
3
3
|
import { isEqual } from "lodash-es";
|
|
4
4
|
|
|
5
5
|
import { useUI } from "../composables/useUI";
|
|
6
6
|
import { getDefaults } from "../utils/ui";
|
|
7
|
+
import { useComponentLocaleMessages } from "../composables/useComponentLocaleMassages";
|
|
7
8
|
|
|
8
9
|
import UIcon from "../ui.image-icon/UIcon.vue";
|
|
9
10
|
import ULabel from "../ui.form-label/ULabel.vue";
|
|
@@ -48,12 +49,24 @@ const emit = defineEmits([
|
|
|
48
49
|
"input",
|
|
49
50
|
]);
|
|
50
51
|
|
|
52
|
+
const { localeMessages } = useComponentLocaleMessages<typeof defaultConfig.i18n>(
|
|
53
|
+
COMPONENT_NAME,
|
|
54
|
+
defaultConfig.i18n,
|
|
55
|
+
props?.config?.i18n,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const slots = useSlots();
|
|
59
|
+
|
|
51
60
|
const checkboxName = ref("");
|
|
52
61
|
const checkboxSize = ref(props.size);
|
|
53
62
|
const checkboxColor = ref(props.color);
|
|
54
63
|
|
|
55
64
|
const elementId = props.id || useId();
|
|
56
65
|
|
|
66
|
+
const hasLabel = computed(() => Boolean(props.label || slots.label));
|
|
67
|
+
|
|
68
|
+
const inputAriaLabelledBy = computed(() => (hasLabel.value ? elementId : undefined));
|
|
69
|
+
|
|
57
70
|
const isBinary = computed(() => !Array.isArray(props.modelValue));
|
|
58
71
|
const isCheckboxInGroup = computed(() => Boolean(toValue(getCheckboxGroupName)));
|
|
59
72
|
|
|
@@ -108,6 +121,10 @@ function onChange() {
|
|
|
108
121
|
emit("input", newModelValue);
|
|
109
122
|
}
|
|
110
123
|
|
|
124
|
+
function onIconClick() {
|
|
125
|
+
document.getElementById(elementId)?.click();
|
|
126
|
+
}
|
|
127
|
+
|
|
111
128
|
/**
|
|
112
129
|
* Get element / nested component attributes for each config token ✨
|
|
113
130
|
* Applies: `class`, `config`, redefined default `props` and dev `vl-...` attributes.
|
|
@@ -161,15 +178,17 @@ const {
|
|
|
161
178
|
:name="checkboxName"
|
|
162
179
|
:checked="isChecked"
|
|
163
180
|
:disabled="disabled"
|
|
181
|
+
:aria-labelledby="inputAriaLabelledBy"
|
|
182
|
+
:aria-label="!hasLabel ? localeMessages.checkbox : undefined"
|
|
164
183
|
v-bind="checkboxAttrs"
|
|
165
184
|
:data-test="getDataTest()"
|
|
166
185
|
@change="onChange"
|
|
167
186
|
/>
|
|
168
187
|
|
|
169
|
-
<
|
|
188
|
+
<div
|
|
170
189
|
v-if="isChecked"
|
|
171
190
|
v-bind="partial ? partiallyCheckedAttrs : checkedAttrs"
|
|
172
|
-
|
|
191
|
+
@click="onIconClick"
|
|
173
192
|
>
|
|
174
193
|
<UIcon
|
|
175
194
|
v-if="partial"
|
|
@@ -179,7 +198,7 @@ const {
|
|
|
179
198
|
/>
|
|
180
199
|
|
|
181
200
|
<UIcon v-else :name="config.defaults.checkedIcon" color="inherit" v-bind="checkedIconAttrs" />
|
|
182
|
-
</
|
|
201
|
+
</div>
|
|
183
202
|
|
|
184
203
|
<template #bottom>
|
|
185
204
|
<!-- @slot Use it to add something below the checkbox. -->
|
|
@@ -125,6 +125,11 @@ export default /*tw*/ {
|
|
|
125
125
|
},
|
|
126
126
|
timeLabel: "Time",
|
|
127
127
|
okLabel: "Ok",
|
|
128
|
+
/* These are used for a11y. */
|
|
129
|
+
previousYear: "Previous Year",
|
|
130
|
+
nextYear: "Next Year",
|
|
131
|
+
previousMonth: "Previous Month",
|
|
132
|
+
nextMonth: "Next Month",
|
|
128
133
|
},
|
|
129
134
|
defaults: {
|
|
130
135
|
size: "md",
|
package/ui.form-input/UInput.vue
CHANGED
|
@@ -91,7 +91,7 @@ const VALIDATION_RULES_REG_EX = {
|
|
|
91
91
|
|
|
92
92
|
const slots = useSlots();
|
|
93
93
|
|
|
94
|
-
const wrapperRef = useTemplateRef<
|
|
94
|
+
const wrapperRef = useTemplateRef<HTMLDivElement>("wrapper");
|
|
95
95
|
const inputRef = useTemplateRef<HTMLInputElement>("input");
|
|
96
96
|
const leftSlotWrapperRef = useTemplateRef<HTMLSpanElement>("leftSlotWrapper");
|
|
97
97
|
const labelComponentRef = useTemplateRef<InstanceType<typeof ULabel>>("labelComponent");
|
|
@@ -176,6 +176,10 @@ function onKeydown(event: KeyboardEvent) {
|
|
|
176
176
|
emit("keydown", event);
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
function onSlotClick() {
|
|
180
|
+
inputRef.value?.focus();
|
|
181
|
+
}
|
|
182
|
+
|
|
179
183
|
/**
|
|
180
184
|
* This trick prevents default browser autocomplete behavior.
|
|
181
185
|
* @param toggleState { boolean }
|
|
@@ -286,11 +290,12 @@ const {
|
|
|
286
290
|
<slot name="label" :label="label" />
|
|
287
291
|
</template>
|
|
288
292
|
|
|
289
|
-
<
|
|
293
|
+
<div ref="wrapper" v-bind="wrapperAttrs">
|
|
290
294
|
<span
|
|
291
295
|
v-if="hasSlotContent($slots['left'], { iconName: leftIcon }) || leftIcon"
|
|
292
296
|
v-bind="leftSlotAttrs"
|
|
293
297
|
ref="leftSlotWrapper"
|
|
298
|
+
@click="onSlotClick"
|
|
294
299
|
>
|
|
295
300
|
<!--
|
|
296
301
|
@slot Use it to add something before the text.
|
|
@@ -328,6 +333,7 @@ const {
|
|
|
328
333
|
<span
|
|
329
334
|
v-if="hasSlotContent($slots['right'], { iconName: rightIcon }) || rightIcon"
|
|
330
335
|
v-bind="rightSlotAttrs"
|
|
336
|
+
@click="onSlotClick"
|
|
331
337
|
>
|
|
332
338
|
<!--
|
|
333
339
|
@slot Use it to add something after the text.
|
|
@@ -337,6 +343,6 @@ const {
|
|
|
337
343
|
<UIcon v-if="rightIcon" color="neutral" :name="rightIcon" v-bind="rightIconAttrs" />
|
|
338
344
|
</slot>
|
|
339
345
|
</span>
|
|
340
|
-
</
|
|
346
|
+
</div>
|
|
341
347
|
</ULabel>
|
|
342
348
|
</template>
|
|
@@ -3,6 +3,7 @@ import { ref, computed, useTemplateRef, watch } from "vue";
|
|
|
3
3
|
|
|
4
4
|
import { useUI } from "../composables/useUI";
|
|
5
5
|
import { getDefaults } from "../utils/ui";
|
|
6
|
+
import { useComponentLocaleMessages } from "../composables/useComponentLocaleMassages";
|
|
6
7
|
|
|
7
8
|
import UIcon from "../ui.image-icon/UIcon.vue";
|
|
8
9
|
import UButton from "../ui.button/UButton.vue";
|
|
@@ -41,6 +42,12 @@ const emit = defineEmits([
|
|
|
41
42
|
"blur",
|
|
42
43
|
]);
|
|
43
44
|
|
|
45
|
+
const { localeMessages } = useComponentLocaleMessages<typeof defaultConfig.i18n>(
|
|
46
|
+
COMPONENT_NAME,
|
|
47
|
+
defaultConfig.i18n,
|
|
48
|
+
props?.config?.i18n,
|
|
49
|
+
);
|
|
50
|
+
|
|
44
51
|
const inputComponentRef = useTemplateRef<InstanceType<typeof UInput>>("inputComponent");
|
|
45
52
|
|
|
46
53
|
const addIntervalId = ref<number | null>(null);
|
|
@@ -181,6 +188,7 @@ const {
|
|
|
181
188
|
:size="size"
|
|
182
189
|
variant="outlined"
|
|
183
190
|
:disabled="isSubtractButtonDisabled || disabled"
|
|
191
|
+
:aria-label="localeMessages.subtract"
|
|
184
192
|
v-bind="subtractButtonAttrs"
|
|
185
193
|
:data-test="getDataTest('subtract')"
|
|
186
194
|
@click.prevent
|
|
@@ -221,6 +229,7 @@ const {
|
|
|
221
229
|
square
|
|
222
230
|
variant="outlined"
|
|
223
231
|
:disabled="isAddButtonDisabled || disabled"
|
|
232
|
+
:aria-label="localeMessages.add"
|
|
224
233
|
v-bind="addButtonAttrs"
|
|
225
234
|
:data-test="getDataTest('add')"
|
|
226
235
|
@click.prevent
|
|
@@ -31,6 +31,11 @@ export default /*tw*/ {
|
|
|
31
31
|
subtractButton: "{>actionButton}",
|
|
32
32
|
addIcon: "{UIcon}",
|
|
33
33
|
subtractIcon: "{UIcon}",
|
|
34
|
+
/* These are used for a11y. */
|
|
35
|
+
i18n: {
|
|
36
|
+
add: "Add",
|
|
37
|
+
subtract: "Subtract",
|
|
38
|
+
},
|
|
34
39
|
defaults: {
|
|
35
40
|
size: "md",
|
|
36
41
|
decimalSeparator: ",",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { ref, computed, useId } from "vue";
|
|
2
|
+
import { ref, computed, useId, useTemplateRef, nextTick } from "vue";
|
|
3
3
|
|
|
4
4
|
import { useUI } from "../composables/useUI";
|
|
5
5
|
import { getDefaults } from "../utils/ui";
|
|
@@ -42,6 +42,7 @@ const emit = defineEmits([
|
|
|
42
42
|
|
|
43
43
|
const elementId = props.id || useId();
|
|
44
44
|
|
|
45
|
+
const inputRef = useTemplateRef<InstanceType<typeof UInput>>("input");
|
|
45
46
|
const isShownPassword = ref(false);
|
|
46
47
|
|
|
47
48
|
const localValue = computed({
|
|
@@ -59,8 +60,12 @@ const passwordIcon = computed(() => {
|
|
|
59
60
|
: config.value.defaults.passwordHiddenIcon || "";
|
|
60
61
|
});
|
|
61
62
|
|
|
62
|
-
function onClickShowPassword() {
|
|
63
|
+
async function onClickShowPassword() {
|
|
63
64
|
isShownPassword.value = !isShownPassword.value;
|
|
65
|
+
|
|
66
|
+
await nextTick();
|
|
67
|
+
|
|
68
|
+
inputRef.value?.inputRef?.focus();
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
function onFocus(event: FocusEvent) {
|
|
@@ -86,6 +91,7 @@ const { getDataTest, config, passwordInputAttrs, passwordIconAttrs, passwordIcon
|
|
|
86
91
|
<template>
|
|
87
92
|
<UInput
|
|
88
93
|
:id="elementId"
|
|
94
|
+
ref="input"
|
|
89
95
|
v-model="localValue"
|
|
90
96
|
:type="inputType"
|
|
91
97
|
:label="label"
|
|
@@ -120,29 +126,22 @@ const { getDataTest, config, passwordInputAttrs, passwordIconAttrs, passwordIcon
|
|
|
120
126
|
</template>
|
|
121
127
|
|
|
122
128
|
<template #right>
|
|
123
|
-
<
|
|
129
|
+
<div v-bind="passwordIconWrapperAttrs" @click="onClickShowPassword">
|
|
124
130
|
<!--
|
|
125
131
|
@slot Use it to add something instead of the password icon.
|
|
126
132
|
@binding {string} icon-name
|
|
127
133
|
@binding {boolean} visible
|
|
128
|
-
@binding {function} toggle
|
|
129
134
|
-->
|
|
130
|
-
<slot
|
|
131
|
-
name="right"
|
|
132
|
-
:icon-name="passwordIcon"
|
|
133
|
-
:visible="isShownPassword"
|
|
134
|
-
:toggle="onClickShowPassword"
|
|
135
|
-
>
|
|
135
|
+
<slot name="right" :icon-name="passwordIcon" :visible="isShownPassword">
|
|
136
136
|
<UIcon
|
|
137
137
|
:name="passwordIcon"
|
|
138
138
|
color="neutral"
|
|
139
139
|
interactive
|
|
140
140
|
v-bind="passwordIconAttrs"
|
|
141
141
|
:data-test="getDataTest('password-icon')"
|
|
142
|
-
@click="onClickShowPassword"
|
|
143
142
|
/>
|
|
144
143
|
</slot>
|
|
145
|
-
</
|
|
144
|
+
</div>
|
|
146
145
|
</template>
|
|
147
146
|
</UInput>
|
|
148
147
|
</template>
|
|
@@ -190,14 +190,14 @@ describe("UInputPassword.vue", () => {
|
|
|
190
190
|
expect(input.attributes("type")).toBe("password");
|
|
191
191
|
expect(passwordIcon.props("name")).toBe("visibility_off-fill");
|
|
192
192
|
|
|
193
|
-
await flushPromises();
|
|
194
193
|
await passwordIcon.trigger("click");
|
|
194
|
+
await flushPromises();
|
|
195
195
|
|
|
196
196
|
expect(input.attributes("type")).toBe("text");
|
|
197
197
|
expect(passwordIcon.props("name")).toBe("visibility-fill");
|
|
198
198
|
|
|
199
|
-
await flushPromises();
|
|
200
199
|
await passwordIcon.trigger("click");
|
|
200
|
+
await flushPromises();
|
|
201
201
|
|
|
202
202
|
expect(input.attributes("type")).toBe("password");
|
|
203
203
|
expect(passwordIcon.props("name")).toBe("visibility_off-fill");
|
package/ui.form-label/ULabel.vue
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed, useTemplateRef, useSlots } from "vue";
|
|
2
|
+
import { computed, useTemplateRef, useSlots, useId } from "vue";
|
|
3
3
|
|
|
4
4
|
import { useUI } from "../composables/useUI";
|
|
5
5
|
import { getDefaults } from "../utils/ui";
|
|
@@ -26,6 +26,8 @@ const emit = defineEmits([
|
|
|
26
26
|
|
|
27
27
|
const slots = useSlots();
|
|
28
28
|
|
|
29
|
+
const elementId = props.id || useId();
|
|
30
|
+
|
|
29
31
|
const wrapperRef = useTemplateRef<HTMLDivElement>("wrapper");
|
|
30
32
|
const labelRef = useTemplateRef<HTMLLabelElement>("label");
|
|
31
33
|
|
|
@@ -102,6 +104,7 @@ const { getDataTest, wrapperAttrs, contentAttrs, labelAttrs, descriptionAttrs, e
|
|
|
102
104
|
<component
|
|
103
105
|
:is="tag"
|
|
104
106
|
v-if="label || hasSlotContent(slots['label'], { label })"
|
|
107
|
+
:id="elementId"
|
|
105
108
|
ref="label"
|
|
106
109
|
:for="props.for"
|
|
107
110
|
v-bind="labelAttrs"
|
|
@@ -140,6 +143,7 @@ const { getDataTest, wrapperAttrs, contentAttrs, labelAttrs, descriptionAttrs, e
|
|
|
140
143
|
<component
|
|
141
144
|
:is="tag"
|
|
142
145
|
v-if="label || hasSlotContent(slots['label'], { label })"
|
|
146
|
+
:id="elementId"
|
|
143
147
|
v-bind="labelAttrs"
|
|
144
148
|
ref="label"
|
|
145
149
|
:for="props.for"
|
package/ui.form-label/types.ts
CHANGED
|
@@ -110,6 +110,19 @@ const addOptionKeyCombination = computed(() => {
|
|
|
110
110
|
return isMac ? "(⌘ + Enter)" : "(Ctrl + Enter)";
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
+
const listboxAriaMultiselectable = computed(() => props.multiple || undefined);
|
|
114
|
+
|
|
115
|
+
const listboxAriaActivedescendant = computed(() =>
|
|
116
|
+
pointer.value >= 0 ? `${elementId}-${pointer.value}` : undefined,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const getOptionAriaSelected = (option: Option) => {
|
|
120
|
+
if (option && option.groupLabel) return undefined;
|
|
121
|
+
if (option.divider) return undefined;
|
|
122
|
+
|
|
123
|
+
return !!isSelectedOption(option);
|
|
124
|
+
};
|
|
125
|
+
|
|
113
126
|
const filteredOptions = computed(() => {
|
|
114
127
|
const normalizedSearch = searchModel.value.toLowerCase().trim();
|
|
115
128
|
|
|
@@ -337,6 +350,12 @@ function onInputSearchBlur(event: FocusEvent) {
|
|
|
337
350
|
}
|
|
338
351
|
|
|
339
352
|
defineExpose({
|
|
353
|
+
/**
|
|
354
|
+
* Current pointer index value.
|
|
355
|
+
* @property {Ref<number>}
|
|
356
|
+
*/
|
|
357
|
+
pointer,
|
|
358
|
+
|
|
340
359
|
/**
|
|
341
360
|
* Allows setting the pointer to a specific index.
|
|
342
361
|
* @property {Function}
|
|
@@ -442,14 +461,20 @@ const {
|
|
|
442
461
|
@update:model-value="onSearchChange"
|
|
443
462
|
/>
|
|
444
463
|
|
|
445
|
-
<ul
|
|
464
|
+
<ul
|
|
465
|
+
v-bind="listAttrs"
|
|
466
|
+
role="listbox"
|
|
467
|
+
:aria-multiselectable="listboxAriaMultiselectable"
|
|
468
|
+
:aria-activedescendant="listboxAriaActivedescendant"
|
|
469
|
+
>
|
|
446
470
|
<li
|
|
447
471
|
v-for="(option, index) of filteredOptions"
|
|
448
|
-
:id="`${elementId}-${index}`"
|
|
449
472
|
:key="index"
|
|
450
473
|
v-bind="listItemAttrs"
|
|
451
474
|
ref="option"
|
|
452
475
|
:role="!(option && option.groupLabel) ? 'option' : undefined"
|
|
476
|
+
:aria-selected="getOptionAriaSelected(option)"
|
|
477
|
+
:aria-disabled="Boolean(option.disabled) || undefined"
|
|
453
478
|
:data-group-label="Boolean(option.groupLabel)"
|
|
454
479
|
>
|
|
455
480
|
<UDivider v-if="option.divider" v-bind="optionDividerAttrs" />
|
|
@@ -505,10 +530,18 @@ const {
|
|
|
505
530
|
|
|
506
531
|
<!-- group title -->
|
|
507
532
|
<template v-if="option && (option.groupLabel || option.isSubGroup) && !option.isHidden">
|
|
508
|
-
<div
|
|
533
|
+
<div
|
|
534
|
+
v-if="option.groupLabel"
|
|
535
|
+
role="group"
|
|
536
|
+
:aria-label="option.groupLabel"
|
|
537
|
+
v-bind="groupAttrs"
|
|
538
|
+
v-text="option.groupLabel"
|
|
539
|
+
/>
|
|
509
540
|
|
|
510
541
|
<div
|
|
511
542
|
v-else-if="option.isSubGroup"
|
|
543
|
+
role="group"
|
|
544
|
+
:aria-label="String(option[labelKey])"
|
|
512
545
|
:style="getMarginForSubCategory(option.level)"
|
|
513
546
|
v-bind="subGroupAttrs"
|
|
514
547
|
v-text="option[labelKey]"
|
|
@@ -516,13 +549,7 @@ const {
|
|
|
516
549
|
</template>
|
|
517
550
|
</li>
|
|
518
551
|
|
|
519
|
-
<li
|
|
520
|
-
v-if="!filteredOptions.length"
|
|
521
|
-
:id="`${elementId}-empty`"
|
|
522
|
-
ref="empty-option"
|
|
523
|
-
role="option"
|
|
524
|
-
v-bind="optionAttrs"
|
|
525
|
-
>
|
|
552
|
+
<li v-if="!filteredOptions.length" ref="empty-option" role="option" v-bind="optionAttrs">
|
|
526
553
|
<!-- @slot Use it to add something instead of empty state. -->
|
|
527
554
|
<slot name="empty">
|
|
528
555
|
<span v-bind="optionContentAttrs" v-text="localeMessages.noDataToShow" />
|
|
@@ -532,7 +559,6 @@ const {
|
|
|
532
559
|
<!-- Add button -->
|
|
533
560
|
<template v-if="addOption">
|
|
534
561
|
<li
|
|
535
|
-
:id="`${elementId}-addOption`"
|
|
536
562
|
ref="add-option"
|
|
537
563
|
role="option"
|
|
538
564
|
v-bind="addOptionLabelWrapperAttrs"
|
|
@@ -218,6 +218,28 @@ const toggleIconName = computed(() => {
|
|
|
218
218
|
return props.toggleIcon ? config.value.defaults.toggleIcon : "";
|
|
219
219
|
});
|
|
220
220
|
|
|
221
|
+
const ariaExpanded = computed(() => isOpen.value);
|
|
222
|
+
|
|
223
|
+
const ariaActiveDescendant = computed(() => {
|
|
224
|
+
if (!isOpen.value || !listboxRef.value) return undefined;
|
|
225
|
+
const pointer = listboxRef.value.pointer;
|
|
226
|
+
|
|
227
|
+
if (pointer === undefined || pointer < 0) return undefined;
|
|
228
|
+
|
|
229
|
+
return `${elementId}-${pointer}`;
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const ariaInvalid = computed(() => Boolean(props.error) ?? undefined);
|
|
233
|
+
|
|
234
|
+
const ariaLabelledBy = computed(() => (props.label ? elementId : undefined));
|
|
235
|
+
|
|
236
|
+
const ariaDescribedBy = computed(() => {
|
|
237
|
+
if (props.error) return `error-${elementId}`;
|
|
238
|
+
if (props.description) return `description-${elementId}`;
|
|
239
|
+
|
|
240
|
+
return undefined;
|
|
241
|
+
});
|
|
242
|
+
|
|
221
243
|
watch(localValue, setLabelPosition, { deep: true });
|
|
222
244
|
|
|
223
245
|
onMounted(() => {
|
|
@@ -232,6 +254,10 @@ function onSearchChange(query: string) {
|
|
|
232
254
|
emit("searchChange", query);
|
|
233
255
|
}
|
|
234
256
|
|
|
257
|
+
function onSearchUpdate(query: string) {
|
|
258
|
+
emit("update:search", query);
|
|
259
|
+
}
|
|
260
|
+
|
|
235
261
|
function onKeydownAddOption(event: KeyboardEvent) {
|
|
236
262
|
if (!isOpen.value) return;
|
|
237
263
|
|
|
@@ -484,6 +510,7 @@ const {
|
|
|
484
510
|
<template>
|
|
485
511
|
<ULabel
|
|
486
512
|
ref="labelComponent"
|
|
513
|
+
:for="elementId"
|
|
487
514
|
:size="size"
|
|
488
515
|
:label="label"
|
|
489
516
|
:error="error"
|
|
@@ -515,7 +542,15 @@ const {
|
|
|
515
542
|
ref="wrapper"
|
|
516
543
|
:tabindex="searchable || disabled ? -1 : 0"
|
|
517
544
|
role="combobox"
|
|
518
|
-
:aria-
|
|
545
|
+
:aria-expanded="ariaExpanded"
|
|
546
|
+
aria-haspopup="listbox"
|
|
547
|
+
:aria-controls="`listbox-${elementId}`"
|
|
548
|
+
:aria-activedescendant="ariaActiveDescendant"
|
|
549
|
+
:aria-disabled="disabled || undefined"
|
|
550
|
+
:aria-invalid="ariaInvalid"
|
|
551
|
+
:aria-labelledby="ariaLabelledBy"
|
|
552
|
+
:aria-describedby="ariaDescribedBy"
|
|
553
|
+
:aria-autocomplete="searchable ? 'list' : undefined"
|
|
519
554
|
v-bind="wrapperAttrs"
|
|
520
555
|
@focus="activate"
|
|
521
556
|
@blur="onBlur"
|
|
@@ -830,7 +865,7 @@ const {
|
|
|
830
865
|
@blur="onListboxBlur"
|
|
831
866
|
@search-blur="onListboxSearchBlur"
|
|
832
867
|
@search-change="onSearchChange"
|
|
833
|
-
@update:search="
|
|
868
|
+
@update:search="onSearchUpdate"
|
|
834
869
|
>
|
|
835
870
|
<template #before-option="{ option, index }">
|
|
836
871
|
<!--
|
|
@@ -448,7 +448,9 @@ describe("USelect.vue", () => {
|
|
|
448
448
|
},
|
|
449
449
|
});
|
|
450
450
|
|
|
451
|
-
expect(component.find("[vl-key='wrapper']").attributes("aria-
|
|
451
|
+
expect(component.find("[vl-key='wrapper']").attributes("aria-controls")).toBe(
|
|
452
|
+
`listbox-${id}`,
|
|
453
|
+
);
|
|
452
454
|
});
|
|
453
455
|
|
|
454
456
|
it("Data Test – applies the correct data-test attributes", async () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed, useId, useTemplateRef } from "vue";
|
|
2
|
+
import { computed, useId, useSlots, useTemplateRef } from "vue";
|
|
3
3
|
|
|
4
4
|
import { useUI } from "../composables/useUI";
|
|
5
5
|
import { getDefaults } from "../utils/ui";
|
|
@@ -29,7 +29,7 @@ const emit = defineEmits([
|
|
|
29
29
|
"update:modelValue",
|
|
30
30
|
]);
|
|
31
31
|
|
|
32
|
-
const wrapperRef = useTemplateRef<
|
|
32
|
+
const wrapperRef = useTemplateRef<HTMLDivElement>("wrapper");
|
|
33
33
|
|
|
34
34
|
const { localeMessages } = useComponentLocaleMessages<typeof defaultConfig.i18n>(
|
|
35
35
|
COMPONENT_NAME,
|
|
@@ -42,8 +42,14 @@ const checkedValue = computed({
|
|
|
42
42
|
set: (value) => emit("update:modelValue", value),
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
+
const slots = useSlots();
|
|
46
|
+
|
|
45
47
|
const elementId = props.id || useId();
|
|
46
48
|
|
|
49
|
+
const hasLabel = computed(() => Boolean(props.label || slots.label));
|
|
50
|
+
|
|
51
|
+
const inputAriaLabelledBy = computed(() => (hasLabel.value ? elementId : undefined));
|
|
52
|
+
|
|
47
53
|
const switchLabel = computed(() => {
|
|
48
54
|
return checkedValue.value ? localeMessages.value.active : localeMessages.value.inactive;
|
|
49
55
|
});
|
|
@@ -62,7 +68,8 @@ function toggle() {
|
|
|
62
68
|
}
|
|
63
69
|
}
|
|
64
70
|
|
|
65
|
-
function onClickToggle() {
|
|
71
|
+
function onClickToggle(event: Event) {
|
|
72
|
+
event.stopPropagation();
|
|
66
73
|
toggle();
|
|
67
74
|
}
|
|
68
75
|
|
|
@@ -70,10 +77,14 @@ function onKeydownSpace() {
|
|
|
70
77
|
toggle();
|
|
71
78
|
}
|
|
72
79
|
|
|
80
|
+
function onClickWrapper() {
|
|
81
|
+
toggle();
|
|
82
|
+
}
|
|
83
|
+
|
|
73
84
|
defineExpose({
|
|
74
85
|
/**
|
|
75
86
|
* A reference to the component's wrapper element for direct DOM manipulation.
|
|
76
|
-
* @property {
|
|
87
|
+
* @property {HTMLDivElement}
|
|
77
88
|
*/
|
|
78
89
|
wrapperRef,
|
|
79
90
|
});
|
|
@@ -118,13 +129,13 @@ const {
|
|
|
118
129
|
<slot name="label" :label="label" />
|
|
119
130
|
</template>
|
|
120
131
|
|
|
121
|
-
<
|
|
132
|
+
<div
|
|
122
133
|
ref="wrapper"
|
|
123
134
|
tabindex="0"
|
|
124
|
-
:for="elementId"
|
|
125
135
|
v-bind="wrapperAttrs"
|
|
126
136
|
@keydown.enter="onKeydownSpace"
|
|
127
137
|
@keydown.space.prevent="onKeydownSpace"
|
|
138
|
+
@click="onClickWrapper"
|
|
128
139
|
>
|
|
129
140
|
<input
|
|
130
141
|
:id="elementId"
|
|
@@ -132,6 +143,8 @@ const {
|
|
|
132
143
|
tabindex="-1"
|
|
133
144
|
type="checkbox"
|
|
134
145
|
:disabled="disabled"
|
|
146
|
+
:aria-labelledby="inputAriaLabelledBy"
|
|
147
|
+
:aria-label="!hasLabel ? localeMessages.switch : undefined"
|
|
135
148
|
v-bind="inputAttrs"
|
|
136
149
|
@click="onClickToggle"
|
|
137
150
|
/>
|
|
@@ -146,6 +159,6 @@ const {
|
|
|
146
159
|
</span>
|
|
147
160
|
|
|
148
161
|
<span v-if="toggleLabel" v-bind="toggleLabelAttrs" v-text="switchLabel" />
|
|
149
|
-
</
|
|
162
|
+
</div>
|
|
150
163
|
</ULabel>
|
|
151
164
|
</template>
|
package/ui.form-switch/config.ts
CHANGED
|
@@ -106,7 +106,7 @@ describe("USwitch.vue", () => {
|
|
|
106
106
|
},
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
-
expect(component.
|
|
109
|
+
expect(component.get("[vl-key='wrapper']").attributes("class")).toContain(`bg-${color}`);
|
|
110
110
|
});
|
|
111
111
|
});
|
|
112
112
|
|
|
@@ -169,7 +169,9 @@ describe("USwitch.vue", () => {
|
|
|
169
169
|
const labelComponent = component.findComponent(ULabel);
|
|
170
170
|
|
|
171
171
|
expect(labelComponent.props("disabled")).toBe(true);
|
|
172
|
-
expect(component.
|
|
172
|
+
expect(component.get("[vl-key='wrapper']").attributes("class")).toContain(
|
|
173
|
+
"pointer-events-none",
|
|
174
|
+
);
|
|
173
175
|
});
|
|
174
176
|
|
|
175
177
|
it("Id – applies the correct id attribute", () => {
|
|
@@ -210,7 +212,7 @@ describe("USwitch.vue", () => {
|
|
|
210
212
|
},
|
|
211
213
|
});
|
|
212
214
|
|
|
213
|
-
expect(component.
|
|
215
|
+
expect(component.find("label").text()).toBe(customLabelContent);
|
|
214
216
|
});
|
|
215
217
|
|
|
216
218
|
it("Label – exposes label prop to slot", () => {
|
|
@@ -225,7 +227,7 @@ describe("USwitch.vue", () => {
|
|
|
225
227
|
},
|
|
226
228
|
});
|
|
227
229
|
|
|
228
|
-
expect(component.
|
|
230
|
+
expect(component.find("label").text()).toBe(`Modified ${defaultLabel}`);
|
|
229
231
|
});
|
|
230
232
|
});
|
|
231
233
|
|
|
@@ -63,7 +63,7 @@ const elementId = props.id || useId();
|
|
|
63
63
|
const textareaRef = useTemplateRef<HTMLTextAreaElement>("textarea");
|
|
64
64
|
const labelComponentRef = useTemplateRef<InstanceType<typeof ULabel>>("labelComponent");
|
|
65
65
|
const leftSlotWrapperRef = useTemplateRef<HTMLDivElement>("leftSlotWrapper");
|
|
66
|
-
const wrapperRef = useTemplateRef<
|
|
66
|
+
const wrapperRef = useTemplateRef<HTMLDivElement>("wrapper");
|
|
67
67
|
|
|
68
68
|
const currentRows = ref(Number(props.rows));
|
|
69
69
|
|
|
@@ -192,10 +192,14 @@ function onMousedown() {
|
|
|
192
192
|
emit("mousedown");
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
function onSlotClick() {
|
|
196
|
+
textareaRef.value?.focus();
|
|
197
|
+
}
|
|
198
|
+
|
|
195
199
|
defineExpose({
|
|
196
200
|
/**
|
|
197
201
|
* A reference to the component's wrapper element for direct DOM manipulation.
|
|
198
|
-
* @property {
|
|
202
|
+
* @property {HTMLDivElement}
|
|
199
203
|
*/
|
|
200
204
|
wrapperRef,
|
|
201
205
|
|
|
@@ -246,12 +250,13 @@ const {
|
|
|
246
250
|
<slot name="label" :label="label" />
|
|
247
251
|
</template>
|
|
248
252
|
|
|
249
|
-
<
|
|
253
|
+
<div ref="wrapper" v-bind="wrapperAttrs">
|
|
250
254
|
<span
|
|
251
255
|
v-if="hasSlotContent($slots['left'])"
|
|
252
256
|
ref="leftSlotWrapper"
|
|
253
257
|
:for="elementId"
|
|
254
258
|
v-bind="leftSlotAttrs"
|
|
259
|
+
@click="onSlotClick"
|
|
255
260
|
>
|
|
256
261
|
<!-- @slot Use it to add something before the text. -->
|
|
257
262
|
<slot name="left" />
|
|
@@ -277,10 +282,15 @@ const {
|
|
|
277
282
|
@click="onClick"
|
|
278
283
|
/>
|
|
279
284
|
|
|
280
|
-
<span
|
|
285
|
+
<span
|
|
286
|
+
v-if="hasSlotContent($slots['right'])"
|
|
287
|
+
:for="elementId"
|
|
288
|
+
v-bind="rightSlotAttrs"
|
|
289
|
+
@click="onSlotClick"
|
|
290
|
+
>
|
|
281
291
|
<!-- @slot Use it to add something after the text. -->
|
|
282
292
|
<slot name="right" />
|
|
283
293
|
</span>
|
|
284
|
-
</
|
|
294
|
+
</div>
|
|
285
295
|
</ULabel>
|
|
286
296
|
</template>
|
|
@@ -528,7 +528,7 @@ describe("UTextarea.vue", () => {
|
|
|
528
528
|
});
|
|
529
529
|
|
|
530
530
|
expect(component.vm.wrapperRef).toBeDefined();
|
|
531
|
-
expect(component.vm.wrapperRef!.tagName).toBe("
|
|
531
|
+
expect(component.vm.wrapperRef!.tagName).toBe("DIV");
|
|
532
532
|
});
|
|
533
533
|
|
|
534
534
|
it("Textarea Element – exposes textarea element ref", () => {
|
|
@@ -4,6 +4,7 @@ import { range } from "lodash-es";
|
|
|
4
4
|
|
|
5
5
|
import { useUI } from "../composables/useUI";
|
|
6
6
|
import { getDefaults } from "../utils/ui";
|
|
7
|
+
import { useComponentLocaleMessages } from "../composables/useComponentLocaleMassages";
|
|
7
8
|
|
|
8
9
|
import UButton from "../ui.button/UButton.vue";
|
|
9
10
|
import UIcon from "../ui.image-icon/UIcon.vue";
|
|
@@ -32,6 +33,12 @@ const emit = defineEmits([
|
|
|
32
33
|
"update:modelValue",
|
|
33
34
|
]);
|
|
34
35
|
|
|
36
|
+
const { localeMessages } = useComponentLocaleMessages<typeof defaultConfig.i18n>(
|
|
37
|
+
COMPONENT_NAME,
|
|
38
|
+
defaultConfig.i18n,
|
|
39
|
+
props?.config?.i18n,
|
|
40
|
+
);
|
|
41
|
+
|
|
35
42
|
const paginationRef = useTemplateRef<HTMLDivElement>("pagination");
|
|
36
43
|
|
|
37
44
|
const currentPage = computed({
|
|
@@ -133,6 +140,7 @@ const {
|
|
|
133
140
|
:label="firstLabel"
|
|
134
141
|
:square="!firstLabel"
|
|
135
142
|
:disabled="prevIsDisabled"
|
|
143
|
+
:aria-label="firstLabel || localeMessages.first"
|
|
136
144
|
v-bind="firstButtonAttrs"
|
|
137
145
|
:data-test="getDataTest('first')"
|
|
138
146
|
@click="goToFirstPage"
|
|
@@ -156,6 +164,7 @@ const {
|
|
|
156
164
|
:label="prevLabel"
|
|
157
165
|
:square="!prevLabel"
|
|
158
166
|
:disabled="prevIsDisabled"
|
|
167
|
+
:aria-label="prevLabel || localeMessages.prev"
|
|
159
168
|
v-bind="prevButtonAttrs"
|
|
160
169
|
:data-test="getDataTest('prev')"
|
|
161
170
|
@click="goToPrevPage"
|
|
@@ -185,6 +194,8 @@ const {
|
|
|
185
194
|
:variant="variant"
|
|
186
195
|
:label="String(page.number)"
|
|
187
196
|
:disabled="disabled"
|
|
197
|
+
:aria-label="`${localeMessages.currentPage} ${page.number}`"
|
|
198
|
+
:aria-current="true"
|
|
188
199
|
v-bind="activeButtonAttrs"
|
|
189
200
|
:data-test="getDataTest('active')"
|
|
190
201
|
/>
|
|
@@ -194,6 +205,7 @@ const {
|
|
|
194
205
|
variant="ghost"
|
|
195
206
|
:label="String(page.number)"
|
|
196
207
|
:disabled="disabled"
|
|
208
|
+
:aria-label="`${localeMessages.goToPage} ${page.number}`"
|
|
197
209
|
v-bind="inactiveButtonAttrs"
|
|
198
210
|
:data-test="getDataTest('inactive')"
|
|
199
211
|
@click="selectPage(page.number)"
|
|
@@ -205,6 +217,7 @@ const {
|
|
|
205
217
|
:label="nextLabel"
|
|
206
218
|
:square="!nextLabel"
|
|
207
219
|
:disabled="nextIsDisabled"
|
|
220
|
+
:aria-label="nextLabel || localeMessages.next"
|
|
208
221
|
v-bind="nextButtonAttrs"
|
|
209
222
|
:data-test="getDataTest('next')"
|
|
210
223
|
@click="goToNextPage"
|
|
@@ -229,6 +242,7 @@ const {
|
|
|
229
242
|
:label="lastLabel"
|
|
230
243
|
:square="!lastLabel"
|
|
231
244
|
:disabled="nextIsDisabled"
|
|
245
|
+
:aria-label="lastLabel || localeMessages.last"
|
|
232
246
|
v-bind="lastButtonAttrs"
|
|
233
247
|
:data-test="getDataTest('last')"
|
|
234
248
|
@click="goToLastPage"
|
|
@@ -34,6 +34,15 @@ export default /*tw*/ {
|
|
|
34
34
|
firstIcon: "{>paginationIcon}",
|
|
35
35
|
prevIcon: "{>paginationIcon}",
|
|
36
36
|
nextIcon: "{>paginationIcon}",
|
|
37
|
+
/* These are used for a11y. */
|
|
38
|
+
i18n: {
|
|
39
|
+
first: "Go to first page",
|
|
40
|
+
last: "Go to last page",
|
|
41
|
+
prev: "Go to previous page",
|
|
42
|
+
next: "Go to next page",
|
|
43
|
+
currentPage: "Current page, page",
|
|
44
|
+
goToPage: "Go to page",
|
|
45
|
+
},
|
|
37
46
|
defaults: {
|
|
38
47
|
variant: "solid",
|
|
39
48
|
size: "md",
|
package/ui.text-alert/UAlert.vue
CHANGED
|
@@ -4,6 +4,7 @@ import { onMounted, ref, computed, useTemplateRef } from "vue";
|
|
|
4
4
|
import { useUI } from "../composables/useUI";
|
|
5
5
|
import { getDefaults } from "../utils/ui";
|
|
6
6
|
import { hasSlotContent } from "../utils/helper";
|
|
7
|
+
import { useComponentLocaleMessages } from "../composables/useComponentLocaleMassages";
|
|
7
8
|
|
|
8
9
|
import UIcon from "../ui.image-icon/UIcon.vue";
|
|
9
10
|
import UButton from "../ui.button/UButton.vue";
|
|
@@ -27,6 +28,12 @@ const emit = defineEmits([
|
|
|
27
28
|
"hide",
|
|
28
29
|
]);
|
|
29
30
|
|
|
31
|
+
const { localeMessages } = useComponentLocaleMessages<typeof defaultConfig.i18n>(
|
|
32
|
+
COMPONENT_NAME,
|
|
33
|
+
defaultConfig.i18n,
|
|
34
|
+
props?.config?.i18n,
|
|
35
|
+
);
|
|
36
|
+
|
|
30
37
|
const wrapperRef = useTemplateRef<HTMLDivElement>("wrapper");
|
|
31
38
|
|
|
32
39
|
const isShownAlert = ref(true);
|
|
@@ -126,6 +133,7 @@ const {
|
|
|
126
133
|
size="xs"
|
|
127
134
|
:color="closeButtonColor"
|
|
128
135
|
variant="ghost"
|
|
136
|
+
:aria-label="localeMessages.closeAlert"
|
|
129
137
|
v-bind="closeButtonAttrs"
|
|
130
138
|
:data-test="getDataTest('button')"
|
|
131
139
|
@click="onClickClose"
|
package/ui.text-alert/config.ts
CHANGED