a11y —— منشئ سمات aria
تهجئة سمات aria وقيمها عمل مُمِل: هل نكتب aria-haspopup أم ariaHaspopup؟ وهل 'true' أم true؟ وهل تُرضَع القيمة الكاذبة؟ لو أن كل مكوّن يجمعها بنفسه، فإمّا أن يُخطئ في التهجئة أو ينسى الحذف عندما تكون القيمة فارغة (فيظهر للمستخدم قارئ الشاشة كومة من aria-controls="undefined"). يُثبِّت shared/a11y هذه الاتفاقيات بمجموعة من الدوال النقية.
الاتفاقية الجوهرية: القيم الكاذبة تُحذف
Section titled “الاتفاقية الجوهرية: القيم الكاذبة تُحذف”جميع منشئات aria المنطقية تتبع القاعدة نفسها:
| الخاصية | سلوك القيمة الكاذبة | السبب |
|---|---|---|
aria-required: false |
تُعيد {} ولا تُعرَض |
التصريح الصريح بـ «غير إلزامي» هو ضجيج بالنسبة لقارئ الشاشة |
aria-haspopup: false |
تُعيد {} ولا تُعرَض |
نفس ما سبق |
aria-current: false |
لا تظهر أصلًا | نوع المنشئ لا يقبل false أصلًا (معظم قراء الشاشة يتجاهلونه) |
aria-controls / aria-labelledby / aria-describedby |
تُعيد {} عندما يكون id فارغًا |
عرض aria-controls="undefined" خطأ شائع |
aria-expanded: false |
تُخرَج كما هي | حالة الطي يجب أن تُذاع صراحةً |
aria-invalid: false |
تُخرَج كما هي | «اجتاز التحقق» يجب أن يُذاع صراحةً |
«الاستثناءان» مقصودان: قيمة aria-expanded / aria-invalid الكاذبة تحمل معلومات دلالية، بناءً عليها يقول قارئ الشاشة «مطوي» أو «إدخال صالح».
طريقة الاستيراد
Section titled “طريقة الاستيراد”import { generateAriaId, ariaExpanded, ariaControls, ariaLabelledby, ariaDescribedby, ariaInvalid, ariaRequired, ariaCurrent, ariaHaspopup, getPopupTriggerAria, getDecorativeAria, type LhAriaAttrs, type LhAriaHaspopup,} from '@lh-ui/share/core/shared/a11y'الأنواع
Section titled “الأنواع”// شكل حزمة سمات aria: المفاتيح هي 'aria-*' أو 'role'، تُستخدم مباشرة في v-bind / {...spread}export type LhAriaAttrs = Record<string, string | number | boolean | undefined>
// القيم الشرعية لـ aria-haspopup (APG)export type LhAriaHaspopup = | 'false' | 'true' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog' | 'combobox'جدول الدوال
Section titled “جدول الدوال”| الدالة | التوقيع | الدور |
|---|---|---|
generateAriaId |
(prefix = 'lh-aria') => string |
توليد id فريد لاقترانات aria |
ariaExpanded |
(expanded: boolean) => LhAriaAttrs |
حالة التوسيع / الطي |
ariaControls |
(id: string | undefined) => LhAriaAttrs |
الإشارة إلى اللوحة المُتحكَّم بها |
ariaLabelledby |
(id: string | undefined) => LhAriaAttrs |
الإشارة إلى نص مرئي ليكون الاسم القابل للوصول |
ariaDescribedby |
(id: string | undefined) => LhAriaAttrs |
الإشارة إلى وصف تكميلي |
ariaInvalid |
(invalid: boolean) => LhAriaAttrs |
علامة فشل التحقق |
ariaRequired |
(required: boolean) => LhAriaAttrs |
دلالة الإلزام |
ariaCurrent |
(current: 'page' | 'step' | 'location' | 'date' | 'time' | true | undefined) => LhAriaAttrs |
علامة الموضع الحالي |
ariaHaspopup |
(kind: LhAriaHaspopup | boolean) => LhAriaAttrs |
التصريح بنوع الطبقة المنبثقة |
getPopupTriggerAria |
(options: { expanded; popupId?; haspopup? }) => LhAriaAttrs |
حزمة السمات العامة لمُطلِق الطبقة المنبثقة |
getDecorativeAria |
(decorative = true) => LhAriaAttrs |
إخفاء العناصر الزخرفية عن التقنيات المساعدة |
حزمتا السمات المركبة
Section titled “حزمتا السمات المركبة”getPopupTriggerAria
Section titled “getPopupTriggerAria”مُطلِقات popover / popconfirm / select / cascader / date-picker / dropdown جميعها تستخدمه، فتحصل على ثلاثة أمور دفعة واحدة:
getPopupTriggerAria({ expanded: true, popupId: 'lh-popover-1' })// {// 'aria-expanded': true,// 'aria-controls': 'lh-popover-1',// 'aria-haspopup': 'true', // القيمة الافتراضية// }
getPopupTriggerAria({ expanded: false, popupId: 'lh-popover-1', haspopup: 'menu' })// { 'aria-expanded': false, 'aria-controls': 'lh-popover-1', 'aria-haspopup': 'menu' }
getPopupTriggerAria({ expanded: false }) // لم يُمرَّر popupId// { 'aria-expanded': false, 'aria-haspopup': 'true' } —— لا يُخرج aria-controlsافتراضيًا قيمة haspopup هي 'true'. والقيم الأدق دلاليًا (القائمة المنسدلة listbox، والمتتالية menu، والتقويم dialog) تجعل إذاعة قارئ الشاشة أدق، ويمررها كل مكوّن وفق دلالته الخاصة.
getDecorativeAria
Section titled “getDecorativeAria”getDecorativeAria() // { 'aria-hidden': true }getDecorativeAria(false) // {} —— إلغاء الإخفاء صراحةًالعناصر الزخرفية (الفواصل الصرفة، وزخرفة loading، والأشكال الهندسية للهيكل العظمي) هي ضجيج بالنسبة للتقنيات المساعدة. انتبه إلى أنها تُستخدم فقط مع العناصر الخالية من المعلومات فعلًا—فإضافة aria-hidden إلى عنصر يحوي نصًّا مخالفة صريحة لإ accessibility. وتمرير false يُعيد كائنًا فارغًا، ويُستخدم في الفروع الشرطية من نوع «مخفي افتراضيًا، لكنه مرئي في بعض المشاهد».
generateAriaId
Section titled “generateAriaId”const id = generateAriaId('lh-popover') // 'lh-popover-1'، 'lh-popover-2' ……تُثبَّت البادئة على القيمة الممررة، ويضمن العداد المتزايد داخل الوحدة ألا تتصادم ids بين النسخ المتعددة في العملية الواحدة. وهو آمن في SSR (بلا وصول إلى DOM).
تنبيه: العداد هو حالة متغيرة على مستوى الوحدة، وهذه الدالة ليست دالة نقية—المدخل نفسه يُعيد مخرجات مختلفة في لحظات مختلفة من العملية. ففي SSR يبدأ العد من 0، وعند hydration في العميل يبدأ كذلك من 0، فما دام ترتيب العرض متطابقًا تتطابق ids أيضًا؛ ولكن اختلف ترتيب عرض SSR / CSR (مكونات غير متزامنة، أو تباين في العرض الشرطي) فقد تظهر ids غير متطابقة. ولهذا فإن مكوّنات الطبقة المنبثقة في كل مكتبات واجهة المستخدم لا تولّد id إلا في العميل (بعد onMounted)، فتتجنب هذه المشكلة.
أمثلة الاستخدام
Section titled “أمثلة الاستخدام”<template> <button v-bind="getPopupTriggerAria({ expanded: open, popupId: panelId, haspopup: 'dialog' })" @click="toggle" > فتح </button> <div :id="panelId" role="dialog" v-show="open">…</div></template>
<script setup lang="ts">const panelId = generateAriaId('lh-popover')</script>استخدام v-bind مع دالة تُعيد كائنًا هو أسلوب Vue المعتاد—فالقيم undefined داخل حزمة السمات يتخطاها Vue تلقائيًا، وهذا يتطابق طبيعيًا مع اتفاقية «القيم الكاذبة تُحذف».
const panelId = generateAriaId('lh-popover')
return ( <> <button {...getPopupTriggerAria({ expanded: open, popupId: panelId })} onClick={toggle} /> {open && <div id={panelId} role="dialog">…</div>} </>)التحقق من النماذج
Section titled “التحقق من النماذج”// مكوّن inputconst aria = { ...ariaRequired(props.required), // حين لا يكون إلزاميًا لا تُعرَض الخاصية بأكملها ...ariaInvalid(!!errorMessage.value), // اجتياز التحقق يُذاع صراحةً أيضًا ...ariaDescribedby(errorMessage.value ? errorId : undefined),}الموضع الحالي في الترقيم / مسار التنقل
Section titled “الموضع الحالي في الترقيم / مسار التنقل”ariaCurrent(isActive ? 'page' : undefined) // لغير الصفحة الحالية لا يُخرَج شيء، تجنبًا للضجيجariaCurrent(isActive ? 'page' : undefined) // الترقيم يستخدم 'page'، وخطوات 'step'، والتواريخ 'date'ملاحظات
Section titled “ملاحظات”- يجب أن يكون id المُشار إليه بـ
aria-controlsموجودًا فعلًا: هذه الوحدة تجمع السمات فقط، وصحة id هي مسؤولية المكوّن. وعندما تُعرَض الطبقة المنبثقة بتأجيل عبرv-if، فاللوحة غير موجودة في DOM في الحالة المغلقة، فيشيرaria-controlsإلى عنصر غير موجود—وهذا مقبول (قارئ الشاشة يتجاهله)، ولكنه خير من الإشارة إلى عنصر خاطئ. - لا تضف
aria-hiddenإلى عنصر يحوي نصًّا: هذه مخالفة صريحة لقواعد accessibility. - اختر قيمة
ariaCurrentوفق الدلالة: الترقيم'page'، وخطوات'step'، ومسار التنقل'location'، والتقويم'date'. ويقبل النوع هذه القيم الحرفية +true. - لا تخلط الحزمة المركبة مع الدوال الذرية بطريقة تغطّي بعضها بعضًا:
getPopupTriggerAriaيحتوي أصلًا علىaria-haspopup، فإن أضفتariaHaspopup(...)خارجًا عبر spread فستُغطّى بالقيمة المنشورة لاحقًا—فاختر إحداهما حسب الحاجة.
@lh-ui/share/core/a11y → getPopupTriggerAria( )(填好参数后点运行)Copyright © 2024-2026 FengMo WenXue