popover —— الطبقة المنبثقة
Popover هو «tooltip يمكنه احتمال الأشياء»: يمكن أن يحوي التنبيه أزراراً، ونماذج، وقوائم، وصوراً. وهو لا يحلّ محل tooltip—فالأخير يحمل نصاً صرفاً ويحظر التفاعل؛ كما لا يحلّ محل dialog—فالأخير مشروط ويخطف التركيز. طبقة core/popover رقيقة كـ tooltip، والاختلاف كله في الوزن التفاعلي.
متى يُستخدم
Section titled “متى يُستخدم”- تنفّذ الأطراف المختلفة
<LhPopover>: تفاصيل على هيئة بطاقة، لوحات فلترة، نماذج مضمّنة، مجموعات أزرار عمليات. - المشاهد التي تحتاج من المستخدم البقاء داخل الطبقة والتفاعل معها.
- لا ينبغي استخدامه: تنبيه نصي من جملة واحدة (استخدم
tooltip، فهو أخف وأكثر ضبطاً)؛ التأكيدات القوية التي تقاطع تدفق المستخدم (استخدمdialogالمشروط)؛ عمليات التأكيد الثاني (استخدمpopconfirm، فهو أدق دلالة).
طريقة الاستيراد
Section titled “طريقة الاستيراد”import { LH_POPOVER_TRIGGERS, LH_POPOVER_PLACEMENT_DEFAULT, getPopoverClass, getPopoverWidthStyle, getPopoverTriggerAria, getPopoverPopperAria, getDefaultDelay, canCloseOnEscape,} from '@lh-ui/share/core/popover'import type { LhPopoverTrigger, LhPopoverState } from '@lh-ui/share/core/popover'القصد التصميمي
Section titled “القصد التصميمي”مبرر وجود تأخير الإخفاء 100ms: إخفاء tooltip يكون 0ms (يختفي فور مغادرة المؤشر)، بينما يترك popover 100ms—ليتمكن المؤشر من السير من العنصر المُطلِق إلى الطبقة. ففي أثناء انتقال المؤشر من المُطلِق إلى الطبقة يغادر العنصر المُطلِق لحظياً، وسيؤجل تأخير 0ms إلى إغلاق الطبقة في منتصف الطريق، فلا يستطيع المستخدم الدخول إليها أصلاً. هذا قيد مباشر يفرضه قابلية تفاعل popover.
LH_POPOVER_TRIGGERS كثابت تحقق: طرق التفعيل تعداد محدود (click/hover/focus/manual)، ويُصدَّر كمصفوفة readonly لكي تستخدمه طبقة UI في التحقق من props (الـ validator في Vue، أو تأكيد وقت التشغيل في React)، وهو في الوقت نفسه المقابل وقت التشغيل لنوع TS.
role="dialog" لا "tooltip": تنص WAI-ARIA على أنه لا يجوز أن يحوي tooltip عناصر تفاعلية، والطبقات العائمة القابلة للتفاعل يجب أن تكون بدلالة dialog. مع aria-labelledby يشير إلى عنصر العنوان، فيعلن قارئ الشاشة العنوان عند الفتح بدلاً من المحتوى كاملاً.
اعتراض disabled في canCloseOnEscape: الإغلاق بـ Esc مسموح افتراضياً، ويُعترض عند disabled—ففي الحالة المعطّلة لا ينبغي أن تُطلَق الطبقة أصلاً، ويمنع هذا الحارس طبقة مفتوحة من أن يُغلِقها Esc بعد تعطيل المكوّن (فالتعطيل في دلالات العمل يعني عادة «قفل العمليات»).
السهم اختياري: لا يُضاف الصنف --arrow إلا عندما يكون arrow !== false—فالسهم يحتاج حسابات تموضع إضافية (computeArrowOffset في shared/popper)، ويمكن للطبقات المنبثقة الإخبارية الاستغناء عنه.
جدول الدوال
Section titled “جدول الدوال”| الدالة | التوقيع | الإرجاع | الوظيفة |
|---|---|---|---|
getDefaultDelay |
(showAfter?, hideAfter?) => { show, hide } |
التأخير | افتراضياً 200 / 100 |
getPopoverClass |
(state: { placement, arrow?, popperClass? }) => string[] |
أسماء الأصناف | الموضع + السهم + مخصّص |
getPopoverWidthStyle |
(width?: string | number) => Record<string, string> |
أنماط مضمّنة | توحيد العرض |
getPopoverTriggerAria |
(visible, popperId?) => LhAriaAttrs |
حزمة aria | aria-expanded/controls/haspopup |
getPopoverPopperAria |
(id?, titleId?) => LhAriaAttrs |
حزمة aria | role=dialog + aria-labelledby |
canCloseOnEscape |
(state: { disabled }) => boolean |
هل يُسمح | الاعتراض عند disabled |
الثوابت: LH_POPOVER_TRIGGERS (4 طرق تفعيل)، LH_POPOVER_PLACEMENT_DEFAULT = 'top'.
تعريفات الأنواع
Section titled “تعريفات الأنواع”export type LhPopoverTrigger = 'click' | 'hover' | 'focus' | 'manual'
export interface LhPopoverState { placement?: LhPlacement // 12 موضعاً (shared/popper) trigger?: LhPopoverTrigger width?: string | number // عرض الطبقة المنبثقة title?: string visible?: boolean disabled?: boolean arrow?: boolean // افتراضياً true showAfter?: number hideAfter?: number}إمكانية الوصول
Section titled “إمكانية الوصول”getPopoverTriggerAria(true, 'pop-1')// { 'aria-expanded': true, 'aria-controls': 'pop-1', 'aria-haspopup': 'true' }getPopoverTriggerAria(false) // { 'aria-expanded': false, 'aria-haspopup': 'true' }
getPopoverPopperAria('pop-1', 'pop-1-title')// { role: 'dialog', id: 'pop-1', 'aria-labelledby': 'pop-1-title' }- ثلاثية العنصر المُطلِق: يُعلن
aria-haspopup«سأُظهر شيئاً»، ويُعلنaria-expanded«هل هو مفتوح الآن»، ويشيرaria-controlsإلى «أي عنصر ستظهر». ويعتمد مستخدم لوحة المفاتيح على حالة expanded لفهم الوضع الراهن. aria-controlsيحتاج وجود الطبقة: لا يوجد id إلا عندما تُعرَض الطبقة عبرv-if؛ وعند عدم تمرير popperId لا يُخرَج سوى expanded/haspopup. لا تدع controls يشير إلى عنصر أُزيل—فبعد الإغلاق تُحذف الطبقة، ويصبح controls الخاص بالمُطلِق معلَّقاً (يتسامح قارئ الشاشة، ولكن التحقق الصارم سيُخطئ).- العنوان هو الواجهة: يشير
aria-labelledbyإلى<div class="lh-popover__title" :id="titleId">. اكتب في العنوان ما هذه الطبقة المنبثقة بوضوح («شروط الفلترة»، «تفاصيل الطلب»)، ولا تكتب فعلاً («اضغط هنا»). - إدارة التركيز من شأن طبقة UI: دلالة
role="dialog"لا تأتي مع تقييد تركيز—هل يُقيَّد التركيز وأين يقع التركيز الابتدائي، أمرٌ تحدده طبقة UI باستخدامshared/focus-trap. والطبقات المنبثقة غير المشروطة عادة لا تُقيَّد (يبقى المستخدم قادراً على التعامل مع بقية الصفحة)، ولكن يجب أن يُغلق بالنقر خارجه. - الفتح بلوحة المفاتيح: في تفعيل click، يكفي أن يكون المُطلِق زراً ليُفتح بـ Enter/Space؛ أما تفعيل hover فلا يمكن الوصول إليه بمستخدم لوحة المفاتيح—لا توفّر العمليات الأساسية عبر hover وحده.
أمثلة الاستخدام
Section titled “أمثلة الاستخدام”import { getDefaultDelay, getPopoverClass, getPopoverWidthStyle, canCloseOnEscape,} from '@lh-ui/share/core/popover'
getDefaultDelay(undefined, undefined) // { show: 200, hide: 100 }getDefaultDelay(0, 0) // { show: 0, hide: 0 } (فوري صريح)
getPopoverClass({ placement: 'bottom' })// ['lh-popover__popper', 'lh-popover__popper--bottom', 'lh-popover__popper--arrow']getPopoverClass({ placement: 'top', arrow: false })// ['lh-popover__popper', 'lh-popover__popper--top']getPopoverClass({ placement: 'top', popperClass: 'my-pop' })// ['lh-popover__popper', '…--top', '…--arrow', 'my-pop']
getPopoverWidthStyle(240) // { width: '240px' }getPopoverWidthStyle('50%') // { width: '50%' }getPopoverWidthStyle(undefined) // {}
canCloseOnEscape({ disabled: false }) // truecanCloseOnEscape({ disabled: true }) // false<!-- Vue3 --><template> <LhPopover :width="280" trigger="click" title="شروط الفلترة"> <template #reference> <LhButton>فلترة</LhButton> </template> <div class="filter-body"> <LhCheckbox v-model="form.active">المُفعَّلة فقط</LhCheckbox> <LhButton type="primary" @click="apply">تطبيق</LhButton> </div> </LhPopover></template>// الإغلاق بالنقر خارج (طبقة UI): يستمع document إلى click، ويتحقق هل الهدف خارج المُطلِق والطبقةfunction onDocumentClick(e: MouseEvent) { if (!triggerEl.value?.contains(e.target as Node) && !popperEl.value?.contains(e.target as Node)) { state.visible = false }}watch(() => state.visible, (v) => { v ? document.addEventListener('click', onDocumentClick) : document.removeEventListener('click', onDocumentClick)})// React: useRef للحصول على عُقَد المُطلِق والطبقة؛ والنقر الخارجي عبر خطاف useClickOutside مخصّص؛// تُنقَل الطبقة (portal) إلى document.body كي لا تُقصّ بسبب overflow:hidden للعنصر الأب// uniapp: طبقة <view>؛ ويُنفَّذ «الإغلاق بالنقر الخارج» في التطبيقات المصغّرة عبر catchtouchstart مع قناع شاملالأنماط والرموز
Section titled “الأنماط والرموز”| اسم الصنف | الدلالة |
|---|---|
.lh-popover__popper |
حاوية الطبقة المنبثقة |
.lh-popover__popper--{top|top-start|…} |
12 موضعاً (تشمل الموضع الفعلي بعد القلب) |
.lh-popover__popper--arrow |
مع سهم |
.lh-popover__title |
العنوان |
.lh-popover__body |
منطقة المحتوى |
لون الخلفية --lh-bg-color، والإطار --lh-border-color، والظل --lh-box-shadow-popover، وزاوية الانحناء --lh-border-radius-base، وz-index هو --lh-z-index-popover.
ملاحظات
Section titled “ملاحظات”- يجب نقل الطبقة (teleport) إلى body: بقاؤها في موضع DOM الأصلي سيقع في فخاخ
overflow: hiddenوtransformوسياق التراصz-indexللعنصر الأب. وبعد النقل تُحسَب إحداثيات التموضع بواسطةcomputePositionفيshared/popperنسبةً إلى إطار العرض. - اسم صنف الموضع بعد القلب: ما يستهلكه
getPopoverClassهو الموضع الفعلي المُطبَّق (القيمة بعد قلب popper)، لا القيمة الأصلية للخاصية—وإلا فسيكون اتجاه السهم مخالفاً للرؤية. على طبقة UI تمرير الموضع الحقيقي الذي يُعيده popper. widthوالتكيّف مع المحتوى: عند ضبط عرض ثابت يُمَطّ المحتوى الضيق؛ وعند عدم الضبط قد يفيض المحتوى عن إطار العرض. للمحتوى المعقد يُستحسَن تمريرmin-widthبدلاً منwidth(عبر popperClass لتخصيص النمط).- تفعيل hover على الأجهزة المحمولة: شاشات اللمس بلا hover، فعلى الطبقات المنبثقة المُفعَّلة بـ hover أن تُنقر ضغطة واحدة لتدخل في حالة «توقف فوق» ثم تُنقر في مكان آخر لتُغلق—تجربة سيئة جداً. يُستحسَن للمكوّنات متعددة الأطر تبديل طريقة التفعيل حسب الطرف.
- الطبقات المتداخلة: عند فتح B داخل طبقة A، يجب إغلاق B تسلسلياً عند إغلاق A، وإلا بقيت B معلَّقة. انتبه عند مشاركة حالة popper.
- تفعيل
manual: يتحكم به خارجياً بالكامل عبرv-model:visible—ويُقرَن عادةً بمنطق مثل «تحديد صف يُوسّعه»، وهنا يجب كتابة منطق الإغلاق بالنقر الخارج بنفسك (فالإغلاق بالنقر الخارج في core أيضاً من تنفيذ طبقة UI). - إعادة استخدام الكود مع tooltip: يشتركان في
shared/popperوshared/delay، ولكن لا يمكن خلط أدوار aria—فلا تستبدل، من أجل إعادة الاستخدام،role="tooltip"الخاص بـ tooltip بطبقة قابلة للتفاعل، فهذا مخالف لـ ARIA وسلوك قارئ الشاشة لا يمكن التنبؤ به.
@lh-ui/share/core/popover → getDefaultDelay( )(填好参数后点运行)Copyright © 2024-2026 FengMo WenXue