vueless 1.2.15-beta.3 → 1.2.15-beta.5
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-list/config.ts +1 -1
- package/ui.data-list/storybook/stories.ts +24 -24
- package/ui.data-list/tests/UDataList.test.ts +7 -7
- package/ui.data-table/config.ts +2 -0
- package/ui.data-table/storybook/stories.ts +3 -1
- package/ui.dropdown-badge/config.ts +1 -1
- package/ui.dropdown-badge/storybook/stories.ts +3 -3
- package/ui.dropdown-badge/tests/UDropdownBadge.test.ts +5 -5
- package/ui.dropdown-button/config.ts +1 -1
- package/ui.dropdown-button/storybook/stories.ts +7 -7
- package/ui.dropdown-button/tests/UDropdownButton.test.ts +5 -5
- package/ui.dropdown-link/config.ts +1 -1
- package/ui.dropdown-link/storybook/stories.ts +3 -3
- package/ui.dropdown-link/tests/UDropdownLink.test.ts +5 -5
- 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-checkbox-group/UCheckboxGroup.vue +2 -2
- package/ui.form-checkbox-group/config.ts +2 -0
- package/ui.form-checkbox-group/storybook/stories.ts +46 -9
- package/ui.form-checkbox-group/tests/UCheckboxGroup.test.ts +65 -0
- package/ui.form-checkbox-group/types.ts +10 -0
- 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-listbox/config.ts +1 -1
- package/ui.form-listbox/storybook/stories.ts +25 -25
- package/ui.form-listbox/tests/UListbox.test.ts +6 -6
- package/ui.form-radio-group/URadioGroup.vue +2 -2
- package/ui.form-radio-group/config.ts +2 -0
- package/ui.form-radio-group/storybook/stories.ts +48 -8
- package/ui.form-radio-group/tests/URadioGroup.test.ts +68 -0
- package/ui.form-radio-group/types.ts +18 -4
- package/ui.form-select/USelect.vue +37 -2
- package/ui.form-select/config.ts +1 -1
- package/ui.form-select/tests/USelect.test.ts +6 -4
- 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/icons/storybook/notifications.svg +0 -1
|
@@ -9,9 +9,9 @@ import type { Props } from "../types";
|
|
|
9
9
|
|
|
10
10
|
describe("UListbox.vue", () => {
|
|
11
11
|
const defaultOptions = [
|
|
12
|
-
{ label: "Option 1",
|
|
13
|
-
{ label: "Option 2",
|
|
14
|
-
{ label: "Option 3",
|
|
12
|
+
{ label: "Option 1", value: "option1" },
|
|
13
|
+
{ label: "Option 2", value: "option2" },
|
|
14
|
+
{ label: "Option 3", value: "option3" },
|
|
15
15
|
];
|
|
16
16
|
|
|
17
17
|
const highlightedClass = "bg-primary/5";
|
|
@@ -140,10 +140,10 @@ describe("UListbox.vue", () => {
|
|
|
140
140
|
|
|
141
141
|
await options[1].trigger("click");
|
|
142
142
|
|
|
143
|
-
expect(component.emitted("update:modelValue")![0][0]).toEqual([defaultOptions[0].
|
|
143
|
+
expect(component.emitted("update:modelValue")![0][0]).toEqual([defaultOptions[0].value]);
|
|
144
144
|
expect(component.emitted("update:modelValue")![1][0]).toEqual([
|
|
145
|
-
defaultOptions[0].
|
|
146
|
-
defaultOptions[1].
|
|
145
|
+
defaultOptions[0].value,
|
|
146
|
+
defaultOptions[1].value,
|
|
147
147
|
]);
|
|
148
148
|
});
|
|
149
149
|
|
|
@@ -86,8 +86,8 @@ const { getDataTest, groupLabelAttrs, listAttrs, groupRadioAttrs } = useUI<Confi
|
|
|
86
86
|
v-for="(option, index) in options"
|
|
87
87
|
:key="index"
|
|
88
88
|
:model-value="selectedItem"
|
|
89
|
-
:value="option
|
|
90
|
-
:label="option
|
|
89
|
+
:value="option[valueKey]"
|
|
90
|
+
:label="String(option[labelKey] || undefined)"
|
|
91
91
|
:description="option.description"
|
|
92
92
|
:disabled="disabled"
|
|
93
93
|
v-bind="groupRadioAttrs"
|
|
@@ -8,10 +8,11 @@ import {
|
|
|
8
8
|
|
|
9
9
|
import URadioGroup from "../../ui.form-radio-group/URadioGroup.vue";
|
|
10
10
|
import URadio from "../../ui.form-radio/URadio.vue";
|
|
11
|
+
import UAlert from "../../ui.text-alert/UAlert.vue";
|
|
11
12
|
import UCol from "../../ui.container-col/UCol.vue";
|
|
13
|
+
import URow from "../../ui.container-row/URow.vue";
|
|
12
14
|
import UBadge from "../../ui.text-badge/UBadge.vue";
|
|
13
15
|
import UText from "../../ui.text-block/UText.vue";
|
|
14
|
-
import URow from "../../ui.container-row/URow.vue";
|
|
15
16
|
|
|
16
17
|
import type { Meta, StoryFn } from "@storybook/vue3-vite";
|
|
17
18
|
import type { Props } from "../types";
|
|
@@ -28,9 +29,9 @@ export default {
|
|
|
28
29
|
args: {
|
|
29
30
|
label: "Select your preferred delivery option:",
|
|
30
31
|
options: [
|
|
31
|
-
{
|
|
32
|
-
{
|
|
33
|
-
{
|
|
32
|
+
{ id: "standard", label: "Standard Shipping (3-5 business days)" },
|
|
33
|
+
{ id: "express", label: "Express Shipping (1-2 business days)" },
|
|
34
|
+
{ id: "pickup", label: "In-Store Pickup (Available same day)" },
|
|
34
35
|
],
|
|
35
36
|
},
|
|
36
37
|
argTypes: {
|
|
@@ -54,16 +55,17 @@ const DefaultTemplate: StoryFn<URadioGroupArgs> = (args: URadioGroupArgs) => ({
|
|
|
54
55
|
});
|
|
55
56
|
|
|
56
57
|
const EnumTemplate: StoryFn<URadioGroupArgs> = (args: URadioGroupArgs, { argTypes }) => ({
|
|
57
|
-
components: { URadioGroup,
|
|
58
|
+
components: { URadioGroup, URow },
|
|
58
59
|
setup: () => ({ args, argTypes, getArgs }),
|
|
59
60
|
template: `
|
|
60
|
-
<
|
|
61
|
+
<URow>
|
|
61
62
|
<URadioGroup
|
|
62
63
|
v-for="option in argTypes?.[args.enum]?.options"
|
|
63
64
|
v-bind="getArgs(args, option)"
|
|
64
65
|
:key="option"
|
|
66
|
+
:name="option"
|
|
65
67
|
/>
|
|
66
|
-
</
|
|
68
|
+
</URow>
|
|
67
69
|
`,
|
|
68
70
|
});
|
|
69
71
|
|
|
@@ -125,8 +127,46 @@ LabelSlot.args = {
|
|
|
125
127
|
name: "LabelSlot",
|
|
126
128
|
slotTemplate: `
|
|
127
129
|
<template #label="{ label }">
|
|
128
|
-
<span class="text-red-500">*</span>
|
|
129
130
|
{{ label }}
|
|
131
|
+
<span class="text-red-500">*</span>
|
|
130
132
|
</template>
|
|
131
133
|
`,
|
|
132
134
|
};
|
|
135
|
+
|
|
136
|
+
export const CustomKeys: StoryFn<URadioGroupArgs> = (args: URadioGroupArgs) => ({
|
|
137
|
+
components: { URadioGroup, UCol, UAlert },
|
|
138
|
+
setup: () => ({ args }),
|
|
139
|
+
template: `
|
|
140
|
+
<UCol>
|
|
141
|
+
<URadioGroup v-bind="args" v-model="args.modelValue" />
|
|
142
|
+
|
|
143
|
+
<UAlert
|
|
144
|
+
:description="args.modelValue"
|
|
145
|
+
size="sm"
|
|
146
|
+
variant="soft"
|
|
147
|
+
color="success"
|
|
148
|
+
bordered
|
|
149
|
+
/>
|
|
150
|
+
</UCol>
|
|
151
|
+
`,
|
|
152
|
+
});
|
|
153
|
+
CustomKeys.args = {
|
|
154
|
+
name: "CustomKeys",
|
|
155
|
+
label: "Select your subscription plan:",
|
|
156
|
+
labelKey: "title",
|
|
157
|
+
valueKey: "id",
|
|
158
|
+
options: [
|
|
159
|
+
{ id: "basic", title: "Basic Plan - $9.99/month" },
|
|
160
|
+
{ id: "pro", title: "Pro Plan - $19.99/month" },
|
|
161
|
+
{ id: "enterprise", title: "Enterprise Plan - $49.99/month" },
|
|
162
|
+
],
|
|
163
|
+
};
|
|
164
|
+
CustomKeys.parameters = {
|
|
165
|
+
docs: {
|
|
166
|
+
description: {
|
|
167
|
+
story:
|
|
168
|
+
"Use `labelKey` and `valueKey` props to specify custom keys for label and value in option objects. " +
|
|
169
|
+
"This is useful when working with data from APIs that use different property names.",
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
};
|
|
@@ -194,6 +194,74 @@ describe("URadioGroup.vue", () => {
|
|
|
194
194
|
expect(radio.attributes("data-test")).toBe(`${dataTestValue}-item-${idx}-label`);
|
|
195
195
|
});
|
|
196
196
|
});
|
|
197
|
+
|
|
198
|
+
it("LabelKey – uses custom label key for option labels", () => {
|
|
199
|
+
const customOptions = [
|
|
200
|
+
{ value: "option-1", name: "Option 1" },
|
|
201
|
+
{ value: "option-2", name: "Option 2" },
|
|
202
|
+
{ value: "option-3", name: "Option 3" },
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
const component = mount(URadioGroup, {
|
|
206
|
+
props: {
|
|
207
|
+
name: defaultName,
|
|
208
|
+
options: customOptions,
|
|
209
|
+
labelKey: "name",
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const radios = component.findAllComponents(URadio);
|
|
214
|
+
|
|
215
|
+
radios.forEach((radio, index) => {
|
|
216
|
+
expect(radio.props("label")).toBe(customOptions[index].name);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("ValueKey – uses custom value key for option values", () => {
|
|
221
|
+
const customOptions = [
|
|
222
|
+
{ id: "option-1", label: "Option 1" },
|
|
223
|
+
{ id: "option-2", label: "Option 2" },
|
|
224
|
+
{ id: "option-3", label: "Option 3" },
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
const component = mount(URadioGroup, {
|
|
228
|
+
props: {
|
|
229
|
+
name: defaultName,
|
|
230
|
+
options: customOptions,
|
|
231
|
+
valueKey: "id",
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const radios = component.findAllComponents(URadio);
|
|
236
|
+
|
|
237
|
+
radios.forEach((radio, index) => {
|
|
238
|
+
expect(radio.props("value")).toBe(customOptions[index].id);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("LabelKey and ValueKey – works with complex objects", async () => {
|
|
243
|
+
const customOptions = [
|
|
244
|
+
{ planId: "basic", title: "Basic Plan" },
|
|
245
|
+
{ planId: "pro", title: "Pro Plan" },
|
|
246
|
+
];
|
|
247
|
+
|
|
248
|
+
const component = mount(URadioGroup, {
|
|
249
|
+
props: {
|
|
250
|
+
name: defaultName,
|
|
251
|
+
options: customOptions,
|
|
252
|
+
labelKey: "title",
|
|
253
|
+
valueKey: "planId",
|
|
254
|
+
modelValue: "",
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const radios = component.findAllComponents(URadio);
|
|
259
|
+
|
|
260
|
+
expect(radios[0].props("label")).toBe("Basic Plan");
|
|
261
|
+
expect(radios[0].props("value")).toBe("basic");
|
|
262
|
+
expect(radios[1].props("label")).toBe("Pro Plan");
|
|
263
|
+
expect(radios[1].props("value")).toBe("pro");
|
|
264
|
+
});
|
|
197
265
|
});
|
|
198
266
|
|
|
199
267
|
describe("Slots", () => {
|
|
@@ -4,12 +4,16 @@ import type { UnknownObject, UnknownArray, ComponentConfig } from "../types";
|
|
|
4
4
|
|
|
5
5
|
export type Config = typeof defaultConfig;
|
|
6
6
|
|
|
7
|
-
export interface
|
|
8
|
-
value
|
|
9
|
-
label
|
|
7
|
+
export interface BaseOption {
|
|
8
|
+
value?: string | number | boolean | UnknownArray | UnknownObject;
|
|
9
|
+
label?: string;
|
|
10
10
|
description?: string;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
export interface Option extends BaseOption {
|
|
14
|
+
[key: string]: string | number | boolean | UnknownArray | UnknownObject | undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
13
17
|
export type SetRadioGroupSelectedItem =
|
|
14
18
|
| ((value: string | number | boolean | UnknownArray | UnknownObject) => void)
|
|
15
19
|
| null;
|
|
@@ -23,7 +27,17 @@ export interface Props {
|
|
|
23
27
|
/**
|
|
24
28
|
* Radio group options.
|
|
25
29
|
*/
|
|
26
|
-
options?:
|
|
30
|
+
options?: Option[];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Label key in the item object of options.
|
|
34
|
+
*/
|
|
35
|
+
labelKey?: string;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Value key in the item object of options.
|
|
39
|
+
*/
|
|
40
|
+
valueKey?: string;
|
|
27
41
|
|
|
28
42
|
/**
|
|
29
43
|
* Radio group label.
|
|
@@ -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
|
<!--
|
package/ui.form-select/config.ts
CHANGED
|
@@ -11,9 +11,9 @@ import type { Props } from "../types";
|
|
|
11
11
|
|
|
12
12
|
describe("USelect.vue", () => {
|
|
13
13
|
const defaultOptions = [
|
|
14
|
-
{ label: "Option 1",
|
|
15
|
-
{ label: "Option 2",
|
|
16
|
-
{ label: "Option 3",
|
|
14
|
+
{ label: "Option 1", value: "option1" },
|
|
15
|
+
{ label: "Option 2", value: "option2" },
|
|
16
|
+
{ label: "Option 3", value: "option3" },
|
|
17
17
|
];
|
|
18
18
|
|
|
19
19
|
describe("Props", () => {
|
|
@@ -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",
|