From b9b0805d21fe7fc9f027c20bc9868adc78c2b2db Mon Sep 17 00:00:00 2001 From: Rocco <544022268@qq.com> Date: Fri, 26 Apr 2024 21:00:51 +0800 Subject: [PATCH] feat: improve form control components a11y (#470) * wip: add aria-labelledby * wip: date and time * feat: a11y * feat: locale --- common/config/src/locale/ar-EG.ts | 56 +++- common/config/src/locale/de-DE.ts | 56 +++- common/config/src/locale/en-US.ts | 56 +++- common/config/src/locale/fr-FR.ts | 56 +++- common/config/src/locale/helper.ts | 60 ++++- common/config/src/locale/ta-IN.ts | 56 +++- common/config/src/locale/zh-CN.ts | 56 +++- common/config/src/locale/zh-HK.ts | 56 +++- common/config/src/locale/zh-TW.ts | 56 +++- common/config/src/props.ts | 33 ++- common/utils/src/date.ts | 38 ++- common/utils/src/dom.ts | 10 +- components/auto-complete/auto-complete.vue | 2 + components/calendar/calendar-cell.vue | 2 +- components/captcha/captcha-slider.vue | 4 +- components/captcha/captcha.tsx | 11 +- components/cascader/cascader-panel.vue | 8 +- components/cascader/cascader.vue | 21 +- components/checkbox/checkbox-group.vue | 21 +- components/checkbox/checkbox.vue | 15 +- components/color-picker/color-alpha.vue | 7 +- components/color-picker/color-hue.vue | 7 +- components/color-picker/color-palette.vue | 5 +- components/color-picker/color-picker.vue | 17 +- components/date-picker/date-control.vue | 113 +++++++- components/date-picker/date-panel.vue | 9 +- components/date-picker/date-picker.vue | 30 ++- components/date-picker/time-control.vue | 57 +++- components/date-picker/time-picker.vue | 23 +- components/form/form-item.vue | 14 +- components/form/helper.ts | 6 +- components/form/symbol.ts | 1 + components/input/input.tsx | 12 +- components/number-input/number-input.vue | 25 +- components/number-input/props.ts | 2 +- components/option/option-group.vue | 28 +- components/option/option.vue | 71 +++-- components/popper/popper.vue | 8 +- components/popper/props.ts | 1 + components/progress/progress.vue | 222 +++++++--------- components/progress/symbol.ts | 9 + components/radio/radio-group.vue | 21 +- components/select/select.vue | 35 ++- components/slider/slider.vue | 4 +- components/switch/switch.vue | 286 ++++++++++----------- components/textarea/textarea.vue | 3 +- components/tooltip/tooltip.tsx | 41 +-- components/transfer/transfer.tsx | 19 +- components/upload/upload-file.vue | 3 + components/upload/upload.tsx | 185 ++++++------- components/wheel/wheel-item.vue | 143 +++++------ components/wheel/wheel.vue | 8 +- style/cascader.scss | 4 + style/color-picker.scss | 4 + style/date-picker.scss | 4 + style/input.scss | 4 + style/number-input.scss | 4 + style/select.scss | 8 +- style/shared/mixins.scss | 9 + style/time-picker.scss | 4 + style/upload.scss | 20 +- 61 files changed, 1458 insertions(+), 691 deletions(-) diff --git a/common/config/src/locale/ar-EG.ts b/common/config/src/locale/ar-EG.ts index 121ba4c93..c6ebd4c25 100644 --- a/common/config/src/locale/ar-EG.ts +++ b/common/config/src/locale/ar-EG.ts @@ -31,7 +31,9 @@ export function arEGLocale() { week6: 'Sa', week7: 'Su', - label: { + ariaLabel: { + year: 'سنة', + month: 'شهر', month1: 'يناير', month2: 'فبراير', month3: 'مارس', @@ -71,7 +73,11 @@ export function arEGLocale() { colorPicker: { confirm: 'موافق', - cancel: 'الغاء' + cancel: 'الغاء', + + ariaLabel: { + clear: 'مسح اللون' + } }, confirm: { @@ -92,7 +98,17 @@ export function arEGLocale() { select: 'اختر' }, startTime: 'وقت البدء', - endTime: 'وقت الانتهاء' + endTime: 'وقت الانتهاء', + + ariaLabel: { + clear: 'مسح التاريخ', + quarter: 'ربع', + week: 'أسبوع', + date: 'يوم', + hour: 'ساعة', + minute: 'دقيقة', + second: 'ثانية' + } }, drawer: { @@ -113,7 +129,11 @@ export function arEGLocale() { }, input: { - placeholder: 'ادخل بيانات' + placeholder: 'ادخل بيانات', + + ariaLabel: { + clear: 'مسح البيانات' + } }, layout: { @@ -130,7 +150,13 @@ export function arEGLocale() { numberInput: { placeholder: 'ادخل قيم رقمية', - outOfRange: 'خارج النطاق' + outOfRange: 'خارج النطاق', + + ariaLabel: { + clear: 'مسح الرقم', + increase: 'زيادة', + decrease: 'نقص' + } }, pagination: { @@ -147,7 +173,11 @@ export function arEGLocale() { select: { placeholder: 'اختر', - empty: 'لا توجد بيانات' + empty: 'لا توجد بيانات', + + ariaLabel: { + clear: 'مسح الاختيار' + } }, table: { @@ -180,6 +210,13 @@ export function arEGLocale() { start: 'يبدأ بـ', end: 'ينتهى بـ', select: 'اختر' + }, + + ariaLabel: { + clear: 'مسح الوقت', + hour: 'ساعة', + minute: 'دقيقة', + second: 'ثانية' } }, @@ -205,7 +242,12 @@ export function arEGLocale() { upload: { upload: 'تحميل ملفات', uploading: 'جار التحميل', - dragOrClick: 'اسحب وضع الملقات هنا, او اضغط تحميل ملفات' + dragOrClick: 'اسحب وضع الملقات هنا, او اضغط تحميل ملفات', + + ariaLabel: { + preview: 'معاينة الملفات', + delete: 'حذف الملفات' + } }, video: { diff --git a/common/config/src/locale/de-DE.ts b/common/config/src/locale/de-DE.ts index 4f008a6a4..0bf66bbac 100644 --- a/common/config/src/locale/de-DE.ts +++ b/common/config/src/locale/de-DE.ts @@ -31,7 +31,9 @@ export function deDELocale() { week6: 'Sa', week7: 'So', - label: { + ariaLabel: { + year: 'Jahr', + month: 'Monat', month1: 'Januar', month2: 'Februar', month3: 'März', @@ -71,7 +73,11 @@ export function deDELocale() { colorPicker: { confirm: 'Bestätigen', - cancel: 'Abbrechen' + cancel: 'Abbrechen', + + ariaLabel: { + clear: 'Farbe löschen' + } }, confirm: { @@ -92,7 +98,17 @@ export function deDELocale() { select: 'Bitte auswählen' }, startTime: 'Start Zeit', - endTime: 'Ende Zeit' + endTime: 'Ende Zeit', + + ariaLabel: { + clear: 'Datum löschen', + quarter: 'Quartal', + week: 'Woche', + date: 'Tag', + hour: 'Stunde', + minute: 'Minute', + second: 'Sekunde' + } }, drawer: { @@ -113,7 +129,11 @@ export function deDELocale() { }, input: { - placeholder: 'Platzhalter' + placeholder: 'Platzhalter', + + ariaLabel: { + clear: 'Löschen' + } }, layout: { @@ -130,7 +150,13 @@ export function deDELocale() { numberInput: { placeholder: 'Bitte Nummer eingeben', - outOfRange: 'Außerhalb des definierten Bereichs' + outOfRange: 'Außerhalb des definierten Bereichs', + + ariaLabel: { + clear: 'Löschen', + increase: 'Erhöhen', + decrease: 'Verringern' + } }, pagination: { @@ -147,7 +173,11 @@ export function deDELocale() { select: { placeholder: 'Bitte auswählen', - empty: 'Keine Daten' + empty: 'Keine Daten', + + ariaLabel: { + clear: 'Löschen' + } }, table: { @@ -180,6 +210,13 @@ export function deDELocale() { start: 'Start', end: 'Ende', select: 'Bitte auswählen' + }, + + ariaLabel: { + clear: 'Zeit löschen', + hour: 'Stunde', + minute: 'Minute', + second: 'Sekunde' } }, @@ -205,7 +242,12 @@ export function deDELocale() { upload: { upload: 'Dateiupload', uploading: 'Hochladen', - dragOrClick: 'Ziehen Sie Dateien hierher oder klicken Sie zum Hochladen' + dragOrClick: 'Ziehen Sie Dateien hierher oder klicken Sie zum Hochladen', + + ariaLabel: { + preview: 'Dateivorschau', + delete: 'Datei löschen' + } }, video: { diff --git a/common/config/src/locale/en-US.ts b/common/config/src/locale/en-US.ts index 5ba08799c..0b181f103 100644 --- a/common/config/src/locale/en-US.ts +++ b/common/config/src/locale/en-US.ts @@ -31,7 +31,9 @@ export function enUSLocale() { week6: 'Sa', week7: 'Su', - label: { + ariaLabel: { + year: 'Year', + month: 'Month', month1: 'January', month2: 'February', month3: 'March', @@ -71,7 +73,11 @@ export function enUSLocale() { colorPicker: { confirm: 'Confirm', - cancel: 'Cancel' + cancel: 'Cancel', + + ariaLabel: { + clear: 'Clear color' + } }, confirm: { @@ -92,7 +98,17 @@ export function enUSLocale() { select: 'Please select' }, startTime: 'Start Time', - endTime: 'End Time' + endTime: 'End Time', + + ariaLabel: { + clear: 'Clear date', + quarter: 'Quarter', + week: 'Week', + date: 'Date', + hour: 'Hour', + minute: 'Minute', + second: 'Second' + } }, drawer: { @@ -113,7 +129,11 @@ export function enUSLocale() { }, input: { - placeholder: 'Please input' + placeholder: 'Please input', + + ariaLabel: { + clear: 'Clear input' + } }, layout: { @@ -130,7 +150,13 @@ export function enUSLocale() { numberInput: { placeholder: 'Please input number', - outOfRange: 'Out of range' + outOfRange: 'Out of range', + + ariaLabel: { + clear: 'Clear number', + increase: 'Increase', + decrease: 'Decrease' + } }, pagination: { @@ -147,7 +173,11 @@ export function enUSLocale() { select: { placeholder: 'Please select', - empty: 'No data' + empty: 'No data', + + ariaLabel: { + clear: 'Clear select' + } }, table: { @@ -180,6 +210,13 @@ export function enUSLocale() { start: 'Start', end: 'End', select: 'Please select' + }, + + ariaLabel: { + clear: 'Clear time', + hour: 'Hour', + minute: 'Minute', + second: 'Second' } }, @@ -205,7 +242,12 @@ export function enUSLocale() { upload: { upload: 'Upload files', uploading: 'Uploading', - dragOrClick: 'Drag files here, or click to upload' + dragOrClick: 'Drag files here, or click to upload', + + ariaLabel: { + preview: 'Preview file', + delete: 'Delete file' + } }, video: { diff --git a/common/config/src/locale/fr-FR.ts b/common/config/src/locale/fr-FR.ts index 01a21eb70..17dc5f1ef 100644 --- a/common/config/src/locale/fr-FR.ts +++ b/common/config/src/locale/fr-FR.ts @@ -31,7 +31,9 @@ export function frFRLocale() { week6: 'Sa', week7: 'Di', - label: { + ariaLabel: { + year: 'Année', + month: 'Mois', month1: 'Janvier', month2: 'Février', month3: 'Mars', @@ -71,7 +73,11 @@ export function frFRLocale() { colorPicker: { confirm: 'Confirmer', - cancel: 'Annuler' + cancel: 'Annuler', + + ariaLabel: { + clear: 'Effacer la couleur' + } }, confirm: { @@ -92,7 +98,17 @@ export function frFRLocale() { select: 'Sélectionner une' }, startTime: 'Heure de début', - endTime: 'Heure de fin' + endTime: 'Heure de fin', + + ariaLabel: { + clear: 'Effacer la date', + quarter: 'Trimestre', + week: 'Semaine', + date: 'Jour', + hour: 'Heure', + minute: 'Minute', + second: 'Seconde' + } }, drawer: { @@ -113,7 +129,11 @@ export function frFRLocale() { }, input: { - placeholder: 'Veuillez saisir' + placeholder: 'Veuillez saisir', + + ariaLabel: { + clear: 'Effacer le texte' + } }, layout: { @@ -130,7 +150,13 @@ export function frFRLocale() { numberInput: { placeholder: 'Veuillez saisir le numéro', - outOfRange: 'En dehors de la zone définie' + outOfRange: 'En dehors de la zone définie', + + ariaLabel: { + clear: 'Effacer le numéro', + increase: 'Augmenter', + decrease: 'Diminuer' + } }, pagination: { @@ -147,7 +173,11 @@ export function frFRLocale() { select: { placeholder: 'Veuillez sélectionner', - empty: 'Pas de données' + empty: 'Pas de données', + + ariaLabel: { + clear: 'Effacer la sélection' + } }, table: { @@ -180,6 +210,13 @@ export function frFRLocale() { start: 'Début', end: 'Fin', select: 'Veuillez sélectionner' + }, + + ariaLabel: { + clear: "Effacer l'heure", + hour: 'Heure', + minute: 'Minute', + second: 'Seconde' } }, @@ -205,7 +242,12 @@ export function frFRLocale() { upload: { upload: 'Télécharger des fichiers', uploading: 'Téléchargement', - dragOrClick: 'Faites glisser les fichiers ici, ou cliquez pour les télécharger' + dragOrClick: 'Faites glisser les fichiers ici, ou cliquez pour les télécharger', + + ariaLabel: { + preview: 'Aperçu du fichier', + delete: 'Supprimer le fichier' + } }, video: { diff --git a/common/config/src/locale/helper.ts b/common/config/src/locale/helper.ts index 82c47118b..17aef7764 100644 --- a/common/config/src/locale/helper.ts +++ b/common/config/src/locale/helper.ts @@ -3,7 +3,9 @@ import type { DeepPartial } from '../types' export interface LocaleConfig { locale: string, - // if false, the spaces will be removed when make sentence + /** + * if false, the spaces will be removed when make sentence + */ wordSpace: boolean, calendar: { @@ -29,7 +31,9 @@ export interface LocaleConfig { week6: string, week7: string, - label: { + ariaLabel: { + year: string, + month: string, month1: string, month2: string, month3: string, @@ -69,7 +73,11 @@ export interface LocaleConfig { colorPicker: { confirm: string, - cancel: string + cancel: string, + + ariaLabel: { + clear: string + } }, confirm: { @@ -90,7 +98,17 @@ export interface LocaleConfig { select: string }, startTime: string, - endTime: string + endTime: string, + + ariaLabel: { + clear: string, + quarter: string, + week: string, + date: string, + hour: string, + minute: string, + second: string + } }, drawer: { @@ -111,7 +129,11 @@ export interface LocaleConfig { }, input: { - placeholder: string + placeholder: string, + + ariaLabel: { + clear: string + } }, layout: { @@ -128,7 +150,13 @@ export interface LocaleConfig { numberInput: { placeholder: string, - outOfRange: string + outOfRange: string, + + ariaLabel: { + clear: string, + increase: string, + decrease: string + } }, pagination: { @@ -145,7 +173,11 @@ export interface LocaleConfig { select: { placeholder: string, - empty: string + empty: string, + + ariaLabel: { + clear: string + } }, table: { @@ -178,6 +210,13 @@ export interface LocaleConfig { start: string, end: string, select: string + }, + + ariaLabel: { + clear: string, + hour: string, + minute: string, + second: string } }, @@ -203,7 +242,12 @@ export interface LocaleConfig { upload: { upload: string, uploading: string, - dragOrClick: string + dragOrClick: string, + + ariaLabel: { + preview: string, + delete: string + } }, video: { diff --git a/common/config/src/locale/ta-IN.ts b/common/config/src/locale/ta-IN.ts index 65d234f98..676adcdab 100644 --- a/common/config/src/locale/ta-IN.ts +++ b/common/config/src/locale/ta-IN.ts @@ -31,7 +31,9 @@ export function taINLocale() { week6: 'Sa', week7: 'Su', - label: { + ariaLabel: { + year: 'ஆண்டு', + month: 'மாதம்', month1: 'ஜனவரி', month2: 'பிப்ரவரி', month3: 'மார்ச்', @@ -71,7 +73,11 @@ export function taINLocale() { colorPicker: { confirm: 'உறுதிபடுத்து', - cancel: 'நிராகரி' + cancel: 'நிராகரி', + + ariaLabel: { + clear: 'தெளிவாக்கு' + } }, confirm: { @@ -92,7 +98,17 @@ export function taINLocale() { select: 'தயவுசெய்து தேர்ந்தெடுக்கவும்' }, startTime: 'தொடக்கம் நேரம்', - endTime: 'முடிவு நேரம்' + endTime: 'முடிவு நேரம்', + + ariaLabel: { + clear: 'தெளிவாக்கு', + quarter: 'பருவம்', + week: 'வாரம்', + date: 'தேதி', + hour: 'மணி', + minute: 'நிமிடம்', + second: 'வினாடி' + } }, drawer: { @@ -113,7 +129,11 @@ export function taINLocale() { }, input: { - placeholder: 'தயவுசெய்து உள்ளிடவும்' + placeholder: 'தயவுசெய்து உள்ளிடவும்', + + ariaLabel: { + clear: 'தெளிவாக்கு' + } }, layout: { @@ -130,7 +150,13 @@ export function taINLocale() { numberInput: { placeholder: 'தயவுசெய்து எண் உள்ளிடவும்', - outOfRange: 'வரம்புக்கு வெளியே' + outOfRange: 'வரம்புக்கு வெளியே', + + ariaLabel: { + clear: 'தெளிவாக்கு', + increase: 'அதிகரிக்க', + decrease: 'குறைக்க' + } }, pagination: { @@ -147,7 +173,11 @@ export function taINLocale() { select: { placeholder: 'தயவுசெய்து தேர்ந்தெடுகக்கவும்', - empty: 'தரவு இல்லை' + empty: 'தரவு இல்லை', + + ariaLabel: { + clear: 'தெளிவாக்கு' + } }, table: { @@ -180,6 +210,13 @@ export function taINLocale() { start: 'தொடக்கம்', end: 'முடிவு', select: 'தயவுசெய்து தேர்ந்தெடுக்கவும்' + }, + + ariaLabel: { + clear: 'தெளிவாக்கு', + hour: 'மணி', + minute: 'நிமிடம்', + second: 'வினாடி' } }, @@ -205,7 +242,12 @@ export function taINLocale() { upload: { upload: 'கோப்புகளை பதிவேற்று', uploading: 'பதிவேற்றுகிறது...', - dragOrClick: 'கோப்புகளை இங்கே விடவும் அல்லது பதிவேற்ற அழுத்தவும்' + dragOrClick: 'கோப்புகளை இங்கே விடவும் அல்லது பதிவேற்ற அழுத்தவும்', + + ariaLabel: { + preview: 'முன்னோட்டம்', + delete: 'கோப்பை நீக்கு' + } }, video: { diff --git a/common/config/src/locale/zh-CN.ts b/common/config/src/locale/zh-CN.ts index a91b7ec81..1e1a8c224 100644 --- a/common/config/src/locale/zh-CN.ts +++ b/common/config/src/locale/zh-CN.ts @@ -31,7 +31,9 @@ export function zhCNLocale() { week6: '六', week7: '日', - label: { + ariaLabel: { + year: '年', + month: '月', month1: '一月', month2: '二月', month3: '三月', @@ -71,7 +73,11 @@ export function zhCNLocale() { colorPicker: { confirm: '确定', - cancel: '重置' + cancel: '重置', + + ariaLabel: { + clear: '清空颜色' + } }, confirm: { @@ -92,7 +98,17 @@ export function zhCNLocale() { select: '请选择' }, startTime: '开始时间', - endTime: '结束时间' + endTime: '结束时间', + + ariaLabel: { + clear: '清空日期', + quarter: '季度', + week: '周', + date: '日', + hour: '时', + minute: '分', + second: '秒' + } }, drawer: { @@ -113,7 +129,11 @@ export function zhCNLocale() { }, input: { - placeholder: '请输入' + placeholder: '请输入', + + ariaLabel: { + clear: '清空文本' + } }, layout: { @@ -130,7 +150,13 @@ export function zhCNLocale() { numberInput: { placeholder: '请输入数字', - outOfRange: '超出范围' + outOfRange: '超出范围', + + ariaLabel: { + clear: '清空数字', + increase: '增加', + decrease: '减少' + } }, pagination: { @@ -147,7 +173,11 @@ export function zhCNLocale() { select: { placeholder: '请选择', - empty: '暂无数据' + empty: '暂无数据', + + ariaLabel: { + clear: '清空选项' + } }, table: { @@ -180,6 +210,13 @@ export function zhCNLocale() { start: '开始', end: '结束', select: '请选择' + }, + + ariaLabel: { + clear: '清空时间', + hour: '时', + minute: '分', + second: '秒' } }, @@ -205,7 +242,12 @@ export function zhCNLocale() { upload: { upload: '上传文件', uploading: '上传中', - dragOrClick: '将文件拖到此处, 或点击上传' + dragOrClick: '将文件拖到此处, 或点击上传', + + ariaLabel: { + preview: '预览文件', + delete: '删除文件' + } }, video: { diff --git a/common/config/src/locale/zh-HK.ts b/common/config/src/locale/zh-HK.ts index 64de35345..2827c354a 100644 --- a/common/config/src/locale/zh-HK.ts +++ b/common/config/src/locale/zh-HK.ts @@ -31,7 +31,9 @@ export function zhHKLocale() { week6: '六', week7: '日', - label: { + ariaLabel: { + year: '年', + month: '月', month1: '一月', month2: '二月', month3: '三月', @@ -71,7 +73,11 @@ export function zhHKLocale() { colorPicker: { confirm: '確定', - cancel: '重置' + cancel: '重置', + + ariaLabel: { + clear: '清空顏色' + } }, confirm: { @@ -92,7 +98,17 @@ export function zhHKLocale() { select: '請選擇' }, startTime: '開始時間', - endTime: '結束時間' + endTime: '結束時間', + + ariaLabel: { + clear: '清空日期', + quarter: '季度', + week: '周', + date: '日', + hour: '時', + minute: '分', + second: '秒' + } }, drawer: { @@ -113,7 +129,11 @@ export function zhHKLocale() { }, input: { - placeholder: '請輸入' + placeholder: '請輸入', + + ariaLabel: { + clear: '清空文本' + } }, layout: { @@ -130,7 +150,13 @@ export function zhHKLocale() { numberInput: { placeholder: '請輸入數字', - outOfRange: '超出範圍' + outOfRange: '超出範圍', + + ariaLabel: { + clear: '清空數字', + increase: '增加', + decrease: '減少' + } }, pagination: { @@ -147,7 +173,11 @@ export function zhHKLocale() { select: { placeholder: '請選擇', - empty: '暫無數據' + empty: '暫無數據', + + ariaLabel: { + clear: '清空選項' + } }, table: { @@ -180,6 +210,13 @@ export function zhHKLocale() { start: '開始', end: '結束', select: '請選擇' + }, + + ariaLabel: { + clear: '清空時間', + hour: '時', + minute: '分', + second: '秒' } }, @@ -205,7 +242,12 @@ export function zhHKLocale() { upload: { upload: '上傳文件', uploading: '上傳中', - dragOrClick: '將文件拖到此處, 或點擊上傳' + dragOrClick: '將文件拖到此處, 或點擊上傳', + + ariaLabel: { + preview: '預覽文件', + delete: '刪除文件' + } }, video: { diff --git a/common/config/src/locale/zh-TW.ts b/common/config/src/locale/zh-TW.ts index f89bd6df2..7d0d49cbd 100644 --- a/common/config/src/locale/zh-TW.ts +++ b/common/config/src/locale/zh-TW.ts @@ -31,7 +31,9 @@ export function zhTWLocale() { week6: '六', week7: '日', - label: { + ariaLabel: { + year: '年', + month: '月', month1: '一月', month2: '二月', month3: '三月', @@ -71,7 +73,11 @@ export function zhTWLocale() { colorPicker: { confirm: '確定', - cancel: '重置' + cancel: '重置', + + ariaLabel: { + clear: '清除顏色' + } }, confirm: { @@ -92,7 +98,17 @@ export function zhTWLocale() { select: '請選擇' }, startTime: '開始時間', - endTime: '結束時間' + endTime: '結束時間', + + ariaLabel: { + clear: '清除日期', + quarter: '季度', + week: '周', + date: '日', + hour: '時', + minute: '分', + second: '秒' + } }, drawer: { @@ -113,7 +129,11 @@ export function zhTWLocale() { }, input: { - placeholder: '請輸入' + placeholder: '請輸入', + + ariaLabel: { + clear: '清空內容' + } }, layout: { @@ -130,7 +150,13 @@ export function zhTWLocale() { numberInput: { placeholder: '請輸入數字', - outOfRange: '超出範圍' + outOfRange: '超出範圍', + + ariaLabel: { + clear: '清空數字', + increase: '增加', + decrease: '減少' + } }, pagination: { @@ -147,7 +173,11 @@ export function zhTWLocale() { select: { placeholder: '請選擇', - empty: '暫無資料' + empty: '暫無資料', + + ariaLabel: { + clear: '清空選項' + } }, table: { @@ -180,6 +210,13 @@ export function zhTWLocale() { start: '開始', end: '結束', select: '請選擇' + }, + + ariaLabel: { + clear: '清除時間', + hour: '時', + minute: '分', + second: '秒' } }, @@ -205,7 +242,12 @@ export function zhTWLocale() { upload: { upload: '上傳檔案', uploading: '上傳中', - dragOrClick: '將檔案拖到此處, 或點選上傳' + dragOrClick: '將檔案拖到此處, 或點選上傳', + + ariaLabel: { + preview: '預覽檔案', + delete: '刪除檔案' + } }, video: { diff --git a/common/config/src/props.ts b/common/config/src/props.ts index 893cb48af..5d3c0130f 100644 --- a/common/config/src/props.ts +++ b/common/config/src/props.ts @@ -166,6 +166,21 @@ export function useProps>( } } +export function useHookProps>( + props: T, + defaults: { [K in keyof T]: T[K] | null } +) { + const propsWithDefault: { [P in keyof T]?: ComputedRef } = {} + + for (const key of Object.keys(props) as (keyof T)[]) { + propsWithDefault[key] = computed(() => props[key] ?? defaults[key]!) + } + + return reactive(propsWithDefault) as { + [P in keyof T]-?: Exclude + } +} + function toWarnPrefix(name: string) { return `[vexip-ui:${name.charAt(0).toLocaleUpperCase() + name.substring(1)}]` } @@ -343,13 +358,12 @@ type GenerateEvent = T extends u * | ((value: boolean) => void) * ``` */ -export type EventListener = Expand< - ForceBooleanDeep[0]>> -> extends infer F extends any[] - ? Parameters extends [unknown, ...infer Others] - ? GenerateEvent> - : GenerateEvent> - : never +export type EventListener = + Expand[0]>>> extends infer F extends any[] + ? Parameters extends [unknown, ...infer Others] + ? GenerateEvent> + : GenerateEvent> + : never const eventTypes = [Function, Array] @@ -357,7 +371,10 @@ export function eventProp() { return eventTypes as PropType> } -export function emitEvent(handlers: MaybeArray<(...args: A) => void>, ...args: A) { +export function emitEvent( + handlers: MaybeArray<(...args: A) => void> | undefined, + ...args: A +) { if (Array.isArray(handlers)) { for (let i = 0, len = handlers.length; i < len; ++i) { const handler = handlers[i] diff --git a/common/utils/src/date.ts b/common/utils/src/date.ts index c28fc06a2..2e12f83aa 100644 --- a/common/utils/src/date.ts +++ b/common/utils/src/date.ts @@ -507,21 +507,15 @@ export function isLeapYear(year: number) { } /** - * 获取给定的日期所在月份的第一天的开始日期 + * 获取给定年份和月份的最后一天 * - * @param date 原始日期 - * @param startOn 设定一个月的第一天,默认为 1 号 + * @param year 原始年份 + * @param month 原始月份 * - * @returns 新的开始日期 + * @returns 最后一天 */ -export function startOfMonth(date: Dateable, startOn = 1) { - date = toDate(date) - - const year = date.getFullYear() - const month = date.getMonth() + 1 - const day = date.getDate() - - let lastDay +export function getLastDayOfMonth(year: number, month: number) { + let lastDay: number if (month < 7) { if (month !== 2) { @@ -537,6 +531,26 @@ export function startOfMonth(date: Dateable, startOn = 1) { lastDay = 31 - (month % 2) } + return lastDay +} + +/** + * 获取给定的日期所在月份的第一天的开始日期 + * + * @param date 原始日期 + * @param startOn 设定一个月的第一天,默认为 1 号 + * + * @returns 新的开始日期 + */ +export function startOfMonth(date: Dateable, startOn = 1) { + date = toDate(date) + + const year = date.getFullYear() + const month = date.getMonth() + 1 + const day = date.getDate() + + const lastDay = getLastDayOfMonth(year, month) + startOn = startOn % lastDay if (startOn < 0) { diff --git a/common/utils/src/dom.ts b/common/utils/src/dom.ts index b2e596fde..2d668ff69 100644 --- a/common/utils/src/dom.ts +++ b/common/utils/src/dom.ts @@ -1,4 +1,4 @@ -import { isClient } from './common' +import { isClient, isDefined } from './common' import { isValidNumber, toNumber } from './number' import type { TransferNode } from './dom-event' @@ -270,8 +270,8 @@ export function toCssSize(value: number | string) { * * @returns 转换后的 HTML 属性值 */ -export function toAttrValue(value: boolean): 'true' | undefined -export function toAttrValue(value: string | number): string | undefined -export function toAttrValue(value: string | number | boolean) { - return value !== false ? String(value) : undefined +export function toAttrValue(value?: boolean | null): 'true' | undefined +export function toAttrValue(value?: string | number | null): string | undefined +export function toAttrValue(value?: string | number | boolean | null) { + return isDefined(value) && value !== false ? String(value) : undefined } diff --git a/components/auto-complete/auto-complete.vue b/components/auto-complete/auto-complete.vue index 59241fe6c..28d51c380 100644 --- a/components/auto-complete/auto-complete.vue +++ b/components/auto-complete/auto-complete.vue @@ -32,6 +32,7 @@ const control = ref() const { idFor, + labelId, state, disabled, loading, @@ -452,6 +453,7 @@ function handleCompositionEnd() { v-model:visible="currentVisible" :class="[nh.b(), props.inherit && nh.bm('inherit')]" :inherit="props.inherit" + :label-id="labelId" :list-class="nh.be('list')" :value="currentValue" :size="props.size" diff --git a/components/calendar/calendar-cell.vue b/components/calendar/calendar-cell.vue index 253c0224a..da61c7794 100644 --- a/components/calendar/calendar-cell.vue +++ b/components/calendar/calendar-cell.vue @@ -66,7 +66,7 @@ defineSlots<{ const nh = useNameHelper('calendar') const label = computed(() => { - const label = props.locale.label + const label = props.locale.ariaLabel const year = props.date.getFullYear() const month = (props.date.getMonth() + 1) as MonthIndex const day = props.date.getDate() diff --git a/components/captcha/captcha-slider.vue b/components/captcha/captcha-slider.vue index a8548ca07..5fbe0bb58 100644 --- a/components/captcha/captcha-slider.vue +++ b/components/captcha/captcha-slider.vue @@ -19,7 +19,7 @@ import { captchaSliderProps } from './props' defineOptions({ name: 'CaptchaSlider' }) -const { idFor, disabled, loading, size, validateField, getFieldValue, setFieldValue } = +const { idFor, labelId, disabled, loading, size, validateField, getFieldValue, setFieldValue } = useFieldStore(focus) const _props = defineProps(captchaSliderProps) @@ -230,6 +230,8 @@ function focus(options?: FocusOptions) { ref="wrapper" :class="className" tabindex="-1" + role="group" + :aria-labelledby="labelId" >
@@ -150,6 +152,10 @@ export default defineComponent({ visible: { type: Boolean, default: false + }, + labeledBy: { + type: String, + default: undefined } }, emits: ['select', 'check', 'hover', 'open', 'back', 'close'], diff --git a/components/cascader/cascader.vue b/components/cascader/cascader.vue index b04c10e83..1c6dee917 100644 --- a/components/cascader/cascader.vue +++ b/components/cascader/cascader.vue @@ -3,6 +3,11 @@ :id="idFor" ref="wrapper" :class="className" + role="group" + :aria-disabled="toAttrValue(props.disabled)" + :aria-expanded="toAttrValue(currentVisible)" + aria-haspopup="dialog" + :aria-labelledby="labelId" @click="toggleVisible()" >
-
+
+
-
+
{{ controlLabel }} @@ -58,8 +63,17 @@ export default defineComponent({ props: checkboxGroupProps, emits: ['update:value'], setup(_props, { emit }) { - const { idFor, state, disabled, loading, size, validateField, getFieldValue, setFieldValue } = - useFieldStore<(string | number)[]>(focus) + const { + idFor, + labelId, + state, + disabled, + loading, + size, + validateField, + getFieldValue, + setFieldValue + } = useFieldStore<(string | number)[]>(focus) const props = useProps('checkboxGroup', _props, { size: createSizeProp(size), @@ -259,6 +273,7 @@ export default defineComponent({ return { props, idFor, + labelId, className, controlLabel, diff --git a/components/checkbox/checkbox.vue b/components/checkbox/checkbox.vue index ec681f006..cee76b797 100644 --- a/components/checkbox/checkbox.vue +++ b/components/checkbox/checkbox.vue @@ -4,6 +4,7 @@ :class="className" :style="style" :aria-disabled="isDisabled" + :aria-labelledby="labelId" @click="handleClick" > (() => input.value?.focus()) + const { + idFor, + labelId, + state, + disabled, + loading, + size, + validateField, + getFieldValue, + setFieldValue + } = useFieldStore(() => input.value?.focus()) const props = useProps('checkbox', _props, { size: createSizeProp(size), @@ -262,6 +272,7 @@ export default defineComponent({ props, nh, idFor, + labelId, currentChecked, readonly, diff --git a/components/color-picker/color-alpha.vue b/components/color-picker/color-alpha.vue index d4f8c0e44..2ff5e60b1 100644 --- a/components/color-picker/color-alpha.vue +++ b/components/color-picker/color-alpha.vue @@ -1,5 +1,10 @@ diff --git a/components/color-picker/color-palette.vue b/components/color-picker/color-palette.vue index 7d7b01f9c..f22ad2d02 100644 --- a/components/color-picker/color-palette.vue +++ b/components/color-picker/color-palette.vue @@ -3,12 +3,13 @@ ref="wrapper" :class="nh.be('palette')" tabindex="-1" + role="group" :style="{ backgroundColor: `hsl(${hue}, 100%, 50%)` }" > -
-
+
+
-
+
+
, + default: () => ({}) } }) @@ -92,6 +101,7 @@ const nh = useNameHelper('date-picker') const wrapper = ref() +const label = computed(() => props.locale.ariaLabel ?? {}) const isActivated = computed(() => { return (Object.keys(props.enabled) as DateTimeType[]).every(type => { return !props.enabled[type] || props.activated[type] @@ -125,6 +135,9 @@ const formattedMinute = computed(() => { const formattedSecond = computed(() => { return formatValue('second') }) +const maxDateCount = computed(() => { + return getLastDayOfMonth(props.dateValue.year, props.dateValue.month) +}) defineExpose({ isActivated, @@ -220,6 +233,7 @@ function handleBlur() {
{{ formattedYear }}
-
+
{{ labels.year }}
@@ -269,38 +322,74 @@ function handleBlur() {
{{ formattedHour }}
-
+
{{ labels.hour }}
diff --git a/components/date-picker/date-panel.vue b/components/date-picker/date-panel.vue index afb8dd9ee..1c1a28217 100644 --- a/components/date-picker/date-panel.vue +++ b/components/date-picker/date-panel.vue @@ -102,7 +102,7 @@ const props = defineProps({ }, locale: { type: Object as PropType, - default: null + default: () => ({}) }, selectingType: { type: String as PropType<'start' | 'end'>, @@ -119,6 +119,10 @@ const props = defineProps({ shortcutsPlacement: { type: String as PropType, default: 'left' + }, + labeledBy: { + type: String, + default: undefined } }) @@ -486,6 +490,7 @@ function refreshCalendar(valueType: 'start' | 'end') { [nh.bem('panel', 'vertical')]: shortcuts.length && (shortcutsPlacement === 'top' || shortcutsPlacement === 'bottom') }" + :aria-labelledby="labeledBy" @click="handleClick" >
-
+
diff --git a/components/date-picker/date-picker.vue b/components/date-picker/date-picker.vue index c01341a89..0635131ed 100644 --- a/components/date-picker/date-picker.vue +++ b/components/date-picker/date-picker.vue @@ -33,7 +33,10 @@ import { format, getTime, isLeapYear, + isObject, + mergeObjects, startOfMonth, + toAttrValue, toDate, toFalse } from '@vexip-ui/utils' @@ -49,6 +52,7 @@ defineOptions({ name: 'DatePicker' }) const { idFor, + labelId, state, disabled, loading, @@ -170,11 +174,9 @@ const endInput = ref>() const datePanel = ref>() const mergedLocale = computed(() => { - return { - ...calendarLocale.value, - ...datePickerLocale.value, - ...(props.locale ?? {}) - } + const locale = mergeObjects(calendarLocale.value, datePickerLocale.value, true) + + return isObject(props.locale) ? mergeObjects(locale, props.locale) : locale }) const startPlaceholder = computed(() => { if (props.placeholder) { @@ -1206,6 +1208,11 @@ function handleClickOutside() { :id="idFor" ref="wrapper" :class="className" + role="group" + :aria-disabled="toAttrValue(props.disabled)" + :aria-expanded="toAttrValue(currentVisible)" + aria-haspopup="dialog" + :aria-labelledby="labelId" @click="showPanel" >
-
+
+
, + default: () => ({}) } }) @@ -87,6 +96,7 @@ const nh = useNameHelper('time-picker') const wrapper = ref() +const label = computed(() => props.locale.ariaLabel ?? {}) const isActivated = computed(() => { return (Object.keys(props.enabled) as TimeType[]).every(type => { return !props.enabled[type] || props.activated[type] @@ -199,6 +209,7 @@ function handleBlur() {
{{ formattedHour }}
-
+
{{ labels.hour }}
diff --git a/components/date-picker/time-picker.vue b/components/date-picker/time-picker.vue index af69e6ee1..43e91607f 100644 --- a/components/date-picker/time-picker.vue +++ b/components/date-picker/time-picker.vue @@ -27,7 +27,7 @@ import { usePopper, useSetTimeout } from '@vexip-ui/hooks' -import { USE_TOUCH, boundRange, callIfFunc, doubleDigits } from '@vexip-ui/utils' +import { USE_TOUCH, boundRange, callIfFunc, doubleDigits, toAttrValue } from '@vexip-ui/utils' import { timePickerProps } from './props' import { useColumn, useTimeBound } from './helper' import { TIME_REG } from './symbol' @@ -39,6 +39,7 @@ defineOptions({ name: 'TimePicker' }) const { idFor, + labelId, state, disabled, loading, @@ -695,6 +696,11 @@ function handleClickOutside() { :id="idFor" ref="wrapper" :class="className" + role="group" + :aria-disabled="toAttrValue(props.disabled)" + :aria-expanded="toAttrValue(currentVisible)" + aria-haspopup="dialog" + :aria-labelledby="labelId" @click="showPanel" >
-
+
+
() +const labelId = computed(() => nh.bs(`${idIndex}__label`)) const isRequired = computed(() => formProps.allRequired || props.required) const requiredTip = computed(() => { return makeSentence(`${props.label || props.prop} ${locale.value.notNullable}`, wordSpace.value) @@ -187,6 +197,7 @@ const instances = new Set() const fieldObject = Object.freeze({ prop: computed(() => props.prop), idFor: computed(() => props.prop), + labelId, state: computed(() => (isError.value ? 'error' : 'default')), disabled: computed(() => !!formProps.disabled), loading: computed(() => !!formProps.loading), @@ -384,6 +395,7 @@ const isNative = computed(() => !!(formProps.action && formProps.method)) />