Skip to content

Commit d3fac6c

Browse files
authored
feat:添加无障碍信息 (#3974)
1 parent 0309314 commit d3fac6c

File tree

13 files changed

+203
-25
lines changed

13 files changed

+203
-25
lines changed

examples/sites/demos/pc/app/search/events.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ test('事件是否正常触发', async ({ page }) => {
55
await page.goto('search#events')
66

77
const modal = page.locator('.tiny-modal')
8-
const input = page.locator('.tiny-search').getByRole('textbox')
8+
const input = page.getByRole('searchbox', { name: 'search' })
99
const button = page.locator('.tiny-search__input-btn > a').first()
1010

1111
await button.click()

packages/renderless/src/search/vue.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ import {
3333
emitInput
3434
} from './index'
3535

36+
// 生成唯一 ID 的自定义方法
37+
const generateUniqueId = (): string => {
38+
return `search-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`
39+
}
40+
3641
export const api = [
3742
'state',
3843
'handleChange',
@@ -94,7 +99,8 @@ export const renderless = (
9499
...formatSearchTypes.state,
95100
showClear: computed(() => props.clearable && (state.focus || state.hovering) && state.currentValue),
96101
formItemSize: computed(() => (parent.formItem || {}).formItemSize),
97-
searchSize: computed(() => props.size || state.formItemSize)
102+
searchSize: computed(() => props.size || state.formItemSize),
103+
instanceId: generateUniqueId() // 生成唯一 ID,用于避免多个组件实例时 id 重复
98104
})
99105

100106
const api = {

packages/renderless/types/search.type.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface ISearchState {
1313
showClear: boolean
1414
formItemSize: string
1515
searchSize: string
16+
instanceId: string
1617
}
1718

1819
export type ISearchProps = ExtractPropTypes<typeof searchProps>

packages/vue/src/file-upload/src/mobile-first.vue

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,12 @@ export default defineComponent({
348348
<span class="ml-2">{t('ui.fileUpload.uploadFile')}</span>
349349
</div>
350350
</tiny-button>
351-
<icon-plus-circle custom-class="sm:hidden w-5 h-5 absolute top-0.5 right-5" />
351+
<icon-plus-circle
352+
role="button"
353+
tabindex="0"
354+
aria-label={t('ui.fileUpload.uploadFile')}
355+
custom-class="sm:hidden w-5 h-5 absolute top-0.5 right-5"
356+
/>
352357
</div>
353358
)
354359
} else if (listType === 'drag-single') {
@@ -359,7 +364,13 @@ export default defineComponent({
359364
listType === 'picture-single' && uploadFiles.length && (defaultClass += ' hidden')
360365
361366
defaultContent = (
362-
<div class={defaultClass}>
367+
<div
368+
class={defaultClass}
369+
role="button"
370+
tabindex="0"
371+
aria-label={t('ui.fileUpload.uploadFile')}
372+
onKeydown={(event) => event.key === 'Enter' && handleTriggerClick()}
373+
>
363374
<div class="absolute w-full top-1/2 left-0 -translate-y-1/2 z-[1] text-center">
364375
{defaultList[type || 'picture']}
365376
</div>
@@ -667,7 +678,13 @@ export default defineComponent({
667678
const attrs = a($attrs, ['^on[A-Z]'])
668679
669680
return (
670-
<div {...attrs} data-tag="tiny-file-upload" class={isDragSingle ? 'relative inline-block' : ''}>
681+
<div
682+
{...attrs}
683+
data-tag="tiny-file-upload"
684+
role="group"
685+
aria-label={title}
686+
class={isDragSingle ? 'relative inline-block' : ''}
687+
>
671688
{getDefaultTitle({ listType, title, showTitle, displayOnly, mode })}
672689
{noticePC}
673690
{isText && !displayOnly ? (slots.trigger ? [createUploadComponent()] : createUploadComponent()) : null}

packages/vue/src/file-upload/src/pc.vue

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ export default defineComponent({
173173
const getTriggerContent = (t: any, disabled: boolean) => {
174174
return (
175175
<div class="trigger-btn">
176-
<tiny-button disabled={disabled} onClick={handleTriggerClick}>
176+
<tiny-button disabled={disabled} aria-label={t('ui.fileUpload.uploadFile')} onClick={handleTriggerClick}>
177177
<TinyIconPlus />
178178
<span>{t('ui.fileUpload.uploadFile')}</span>
179179
</tiny-button>
@@ -225,17 +225,36 @@ export default defineComponent({
225225
226226
const getThumIcon = (file) => [
227227
showDownload && (
228-
<span class="thumb-icon" title={t('ui.fileUpload.downloadFile')} onClick={() => execDownload(file)}>
228+
<span
229+
class="thumb-icon"
230+
role="button"
231+
tabindex="0"
232+
aria-label={t('ui.fileUpload.downloadFile')}
233+
title={t('ui.fileUpload.downloadFile')}
234+
onClick={() => execDownload(file)}>
229235
<TinyIconDownload class="download-icon" />
230236
</span>
231237
),
232238
isEdm && !isFolder && showUpdate && (
233-
<span class="thumb-icon" title={t('ui.fileUpload.updateFile')} onClick={() => updateFile(file)}>
239+
<span
240+
class="thumb-icon"
241+
role="button"
242+
tabindex="0"
243+
aria-label={t('ui.fileUpload.updateFile')}
244+
title={t('ui.fileUpload.updateFile')}
245+
onClick={() => updateFile(file)}>
234246
<TinyIconFileCloudupload class="refres-icon" />
235247
</span>
236248
),
237249
showDel && (
238-
<span class="thumb-icon" title={t('ui.fileUpload.deleteFile')} onClick={() => handleRemove(file)}>
250+
<span
251+
class="thumb-icon"
252+
role="button"
253+
tabindex="0"
254+
aria-label={t('ui.fileUpload.deleteFile')}
255+
title={t('ui.fileUpload.deleteFile')}
256+
onClick={() => handleRemove(file)}
257+
onKeydown={(event) => handleEnter(event, () => handleRemove(file))}>
239258
<TinyIconClose class="close-icon" />
240259
</span>
241260
)
@@ -508,7 +527,7 @@ export default defineComponent({
508527
const attrs = a($attrs, ['^on[A-Z]'])
509528
510529
return (
511-
<div class="tiny-file-upload" {...attrs}>
530+
<div class="tiny-file-upload" role="group" aria-label={title} {...attrs}>
512531
{isSaasType ? getDefaultTitle(title, this.showTitle) : ''}
513532
{notice}
514533
{isPictureCard ? uploadList : ''}

packages/vue/src/numeric/src/mobile-first.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
"
2525
v-if="controls"
2626
role="button"
27+
:aria-disabled="state.minDisabled || state.inputDisabled"
28+
aria-label="decrease"
2729
v-repeat-click="decrease"
2830
@keydown.enter="decrease"
2931
>
@@ -51,6 +53,8 @@
5153
"
5254
v-if="controls"
5355
role="button"
56+
:aria-disabled="state.maxDisabled || state.inputDisabled"
57+
aria-label="increase"
5458
v-repeat-click="increase"
5559
@keydown.enter="increase"
5660
>
@@ -74,6 +78,11 @@
7478
>
7579
<input
7680
:tabindex="tabindex"
81+
role="spinbutton"
82+
:aria-valuemin="min"
83+
:aria-valuemax="max"
84+
:aria-valuenow="state.currentValue"
85+
:aria-disabled="state.inputDisabled"
7786
:class="
7887
m(
7988
gcls('numeric_input_inner'),

packages/vue/src/numeric/src/pc.vue

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@
6262
<span
6363
class="tiny-numeric__decrease"
6464
role="button"
65+
aria-label="decrease"
66+
:aria-disabled="state.minDisabled"
6567
v-if="state.controls && !unit"
6668
v-repeat-click="decrease"
6769
:class="{ 'is-disabled': state.minDisabled }"
@@ -72,6 +74,8 @@
7274
<span
7375
class="tiny-numeric__increase"
7476
role="button"
77+
aria-label="increase"
78+
:aria-disabled="state.maxDisabled"
7579
v-if="state.controls && !unit"
7680
v-repeat-click="increase"
7781
:class="{ 'is-disabled': state.maxDisabled }"
@@ -103,6 +107,11 @@
103107
</span>
104108
<input
105109
:tabindex="tabindex"
110+
role="spinbutton"
111+
:aria-valuemin="min"
112+
:aria-valuemax="max"
113+
:aria-valuenow="state.currentValue"
114+
:aria-disabled="state.inputDisabled"
106115
:class="[
107116
'tiny-numeric__input-inner',
108117
{ 'tiny-numeric__show-left': !state.controls && (showLeft || shape === 'filter') }
@@ -130,6 +139,8 @@
130139
<span
131140
class="tiny-numeric__decrease"
132141
role="button"
142+
aria-label="decrease"
143+
:aria-disabled="state.minDisabled"
133144
v-if="controls && !unit"
134145
v-repeat-click="decrease"
135146
:class="{ 'is-disabled': state.minDisabled }"
@@ -140,6 +151,8 @@
140151
<span
141152
class="tiny-numeric__increase"
142153
role="button"
154+
aria-label="increase"
155+
:aria-disabled="state.maxDisabled"
143156
v-if="controls && !unit"
144157
v-repeat-click="increase"
145158
:class="{ 'is-disabled': state.maxDisabled }"
@@ -171,6 +184,11 @@
171184
</span>
172185
<input
173186
:tabindex="tabindex"
187+
role="spinbutton"
188+
:aria-valuemin="min"
189+
:aria-valuemax="max"
190+
:aria-valuenow="state.currentValue"
191+
:aria-disabled="state.inputDisabled"
174192
:class="['tiny-numeric__input-inner', { 'tiny-numeric__show-left': !controls && showLeft }]"
175193
ref="input"
176194
:value="state.displayValue"

packages/vue/src/search/src/mobile-first.vue

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
<div
33
:class="m(gcls('search-default'))"
44
data-tag="tiny-search"
5+
role="search"
6+
:aria-label="placeholder || 'search'"
57
@mouseenter="state.hovering = true"
68
@mouseleave="state.hovering = false"
79
>
@@ -30,6 +32,10 @@
3032
gcls({ 'pc-search-present-unbig': size === 'small' && !big })
3133
)
3234
"
35+
role="button"
36+
tabindex="0"
37+
aria-haspopup="listbox"
38+
:aria-expanded="state.show"
3339
@click="showSelector"
3440
>
3541
<slot name="text" :slot-scope="state.searchValue">
@@ -70,6 +76,11 @@
7076
:placeholder="placeholder"
7177
type="text"
7278
data-tag="tiny-search__input"
79+
role="searchbox"
80+
:aria-label="placeholder || 'search input'"
81+
:aria-expanded="state.show"
82+
:aria-controls="state.show ? `tiny-search__selector-${state.instanceId}` : undefined"
83+
aria-haspopup="listbox"
7384
@keyup.enter="searchEnterKey"
7485
@input="handleInput"
7586
@change="handleChange"
@@ -91,7 +102,13 @@
91102
"
92103
v-if="state.showClear && !state.collapse"
93104
>
94-
<a :class="m(gcls('pc-search-input-btn-transtion-a'))" @click="clear">
105+
<a
106+
:class="m(gcls('pc-search-input-btn-transtion-a'))"
107+
role="button"
108+
tabindex="0"
109+
aria-label="clear search"
110+
@click="clear"
111+
>
95112
<icon-close
96113
@mousedown.prevent
97114
data-tag="tiny-svg-size"
@@ -111,7 +128,13 @@
111128
)
112129
"
113130
>
114-
<a :class="m(gcls('pc-search-input-btn-a'))" @click="searchClick">
131+
<a
132+
:class="m(gcls('pc-search-input-btn-a'))"
133+
role="button"
134+
tabindex="0"
135+
aria-label="submit search"
136+
@click="searchClick"
137+
>
115138
<icon-search
116139
:class="
117140
m(
@@ -129,14 +152,21 @@
129152
v-show="state.show && state.types.length"
130153
ref="selector"
131154
data-tag="tiny-search__selector"
155+
:id="`tiny-search__selector-${state.instanceId}`"
132156
:class="m(gcls('search-selector'))"
157+
role="listbox"
133158
>
134159
<div data-tag="tiny-search__selector-body" :class="m(gcls('search-selector-body'))">
135160
<ul data-tag="tiny-search__poplist">
136161
<li
137162
v-for="(item, index) in state.types"
138163
:key="index"
139164
data-tag="tiny-search__poplist-item"
165+
role="option"
166+
:aria-label="item.text || item.label"
167+
:aria-selected="item === state.searchValue"
168+
:id="`tiny-search__option-${state.instanceId}-${index}`"
169+
tabindex="-1"
140170
:class="
141171
m(
142172
gcls('search-selector-poplist-item'),

0 commit comments

Comments
 (0)