UIDex

اللوحة المنبثقة

Popover

popover="auto" + role="dialog"

اسمه كمانبوب أوفر، بوب أب، صندوق منبثق، نافذة طافية، لوحة عائمة، فقاعة منبثقة، popup panel، floating panel، anchored overlay، flyout، popover panel

اللوحة المنبثقة طبقة صغيرة تُفتح بالضغط على عنصر مُشغِّل وترسو عليه، وتحمل محتوى تفاعلياً حقيقياً: حقول وأزرار ومفاتيح تبديل. وهي غير حاجبة، فالصفحة تحتها تبقى قابلة للضغط، والتركيز لا يُحبس بداخلها، ويغلقها النقر في الخارج أو مفتاح Escape. وأربعة عناصر يصفها الناس بالجملة نفسها «صندوق صغير بيطلع»، ويفرّق بينها سؤالان: ما الذي يفتحها، وهل تحجب ما تحتها. فالتلميح (tooltip) يفتحه مرور المؤشّر أو وصول التركيز بلوحة المفاتيح، ومحتواه نصّ يصف العنصر، ولا يجوز أن يحمل زرّاً لأن لوحة المفاتيح لا تجد إليه طريقاً. والقائمة (menu) صفّ أوامر يُختار منه واحد فتُغلق، ويتنقّل المستخدم فيها بالأسهم لا بـ Tab، ودورها role="menu" مع role="menuitem" لا role="dialog". والنافذة الحوارية (modal) تحجب ما تحتها وتحبس التركيز وتعلن ذلك بـ aria-modal="true"، ولا تنصرف إلا بقرار. وبطاقة التمرير (hover card) تحمل محتوى مثل اللوحة المنبثقة، غير أن فتحها لم يقصده المستخدم، فلا تضع فيها شيئاً لا يوجد في مكان آخر، لأن شاشة اللمس لا «مرور مؤشّر» فيها أصلاً.

لو قلت عليه…

«الصندوق اللي بيطلع فوق الزرار لما أدوس عليه»«اللوحة الصغيرة اللي فيها فلاتر وبتقفل لما أدوس بره»«زيّ التولتيب بس ينفع أضغط جوّاه»«الكارت اللي بيظهر جنب الأيقونة وفيه زراير»«البوب أب اللي مش بيقفل الصفحة»

العيّنة الحيّة

تفاعل مع الديمو. كل الأجزاء حقيقية ومرقّمة.

٤٢ نتيجة
اللوحة تتّجه حيث توجد المساحة
RTLspan-inline-start يمتدّ يميناً في العربية ويساراً في الإنجليزية

تشريح العنصر: كل جزء واسمه

مرّر على أي سطر ليتحدّد مكانه في الديمو فوق.

برومبت البناء

ابنِ لوحة منبثقة غير حاجبة. اجعل المُشغِّل <button> حقيقياً يحمل aria-expanded تتبدّل قيمته مع كل فتح وإغلاق، وأعطه popovertarget يشير إلى معرّف اللوحة. واجعل اللوحة عنصراً بـ popover="auto" ليعمل الإغلاق الخفيف بالنقر الخارجي و Escape بلا JavaScript، وأعطها role="dialog" مع aria-labelledby يشير إلى عنوانها، ولا تكتب aria-modal="true" ولا تحبس التركيز بداخلها إطلاقاً. لا تنقل التركيز إليها إلا إن كان فيها عنصر يستقبله، واستعمل autofocus على أوّل حقل حين تحمل نموذجاً. ضع زرّ إغلاق صريحاً بـ popovertargetaction="hide" واسم مقروء، وأعِد التركيز إلى المُشغِّل بعد كل إغلاق. ثبّتها بالتموضع المرتكز: anchor-name على المُشغِّل، و position-anchor على اللوحة، و position-area: block-end span-inline-start، و position-try-fallbacks: flip-block, flip-inline ليعمل الانقلاب عند حافّة الشاشة، وأضف قاعدة @position-try من عندك إن احتجت إزاحة على المحور نفسه، فليس في CSS ما يقابلها جاهزاً. اجعل السهم مطلق التموضع بـ position-anchor نفسه ثم justify-self: anchor-center، واجعله aria-hidden="true". نسّق الحالة المفتوحة بـ :popover-open، وأضف @starting-style للقطة الدخول الأولى و transition-behavior: allow-discrete على display و overlay لحركة الخروج. واكتب كل مقاس على المحور المضمّن بخاصّية منطقية: inset-inline-start و margin-inline و padding-inline و border-inline، ولا تكتب left ولا right ولا margin-left في أي موضع، حتى تنعكس اللوحة كاملة حين تنقلب الصفحة إلى RTL. وإن استعملت مكتبة تموضع بدل CSS فأبقِ إزاحة السهم القادمة منها على left لأنها رقم فيزيائي، واستدعِ autoUpdate حتى تتبع اللوحة مُشغِّلها أثناء التمرير وتغيير مقاس النافذة.

سلوكه في الاتجاه من اليمين لليسار

الجزء ده حصري عندنا.

كلمات الجهة: المنطقي والفيزيائي

ينعكس

position-area تقبل كلمات فيزيائية (top و left و right و bottom) وكلمات منطقية (block-start و inline-start و inline-end)، والمنطقية وحدها تُحسب من direction. فـ position-area: block-end span-inline-start يضع اللوحة تحت المُشغِّل ممتدّة نحو اليسار في الإنجليزية ونحو اليمين في العربية، بينما position-area: bottom left يبقى يساراً في اللغتين. ومكتبات التموضع تسير على القسمة نفسها: Floating UI يحسب لاحقتَي ‎-start و ‎-end من direction المحسوب على اللوحة نفسها، فـ bottom-start يحاذي حافّتها اليمنى في صفحة RTL، أمّا الجهة الأساسية (top أو bottom أو left أو right) فلا تنعكس أبداً مهما انقلبت الصفحة.

إزاحة السهم

ينعكس

مكتبة التموضع تسلّمك إزاحة السهم رقماً فيزيائياً: قيمة middlewareData.arrow.x في Floating UI مقيسة من الحافّة اليسرى للوحة مهما كان اتجاه الصفحة. فإن أسندتها إلى inset-inline-start قاسها المتصفّح من اليمين داخل [dir="rtl"] وذهب السهم إلى الطرف المقابل، فأسندها إلى left كما وصلتك واحصر inset-inline-start على الإزاحات التي تكتبها بنفسك. أمّا في CSS الخالصة فالحساب كلّه يسقط: أعطِ السهم المطلق position-anchor نفسه الذي على اللوحة ثم justify-self: anchor-center، فيتوسّط المرساة ولا يعرف يميناً من يسار.

اللوحة المُرحَّلة تفقد dir

ينعكس

اللوحة الأصلية تبقى في مكانها من DOM ولا تُرسم إلا في الطبقة العليا، فترث dir من آبائها كأي عنصر آخر. أمّا createPortal إلى document.body فيُخرجها من الشجرة العربية: إذا كان dir="rtl" مكتوباً على <div id="app"> لا على <html>، ورثت اللوحة LTR فالتصق النصّ العربي بالحافّة اليسرى، وانتقلت علامات الترقيم إلى الطرف الخطأ، وانقلبت كل خاصّية منطقية بداخلها. ضع dir على <html>، أو اكتبه صراحة على جذر البوابة لحظة إنشائه، فبغير ذلك يقرأ Floating UI الاتجاه اللاتيني نفسه ويحاذي ‎bottom-start إلى الحافّة الخطأ زيادة على ما سبق.

الانقلاب عند حافّة الشاشة

ينعكس

الحافّة التي تنفد عندها المساحة تتبدّل مع الاتجاه: لوحة موضوعة بـ span-inline-start تمتدّ يساراً في الإنجليزية ويميناً في العربية، فالمُشغِّل الذي لم ينقلب قطّ في الإنجليزية يبدأ الانقلاب في العربية. والكلمة flip-inline داخل position-try-fallbacks تعمل على المحور المضمّن فتتبع direction وحدها، أمّا فحص يدوي مثل if (rect.right > innerWidth) فيختبر حافّة فيزيائية واحدة ولا يرى الفيضان من الجهة الأخرى. والقياس نفسه ينقلب: في حاوية تمرير أفقية بـ RTL تبدأ scrollLeft من صفر عند أقصى اليمين وتصير سالبة كلّما تحرّك المستخدم يساراً في المتصفّحات الحالية، فحساب مثل trigger.offsetLeft - scroller.scrollLeft يدفع اللوحة في الاتجاه الخطأ ما دام يفترضها موجبة.

المحور الكتلي لا ينعكس

لا ينعكس

direction يعمل على المحور المضمّن وحده، فاللوحة المفتوحة تحت مُشغّلها تبقى تحته في العربية ولا يحرّكها إلا writing-mode، والكلمة block-end في position-area لا تحتاج أي استثناء داخل [dir="rtl"]. وقاعدة شاملة مثل [dir="rtl"] .popover { transform: scaleX(-1) } تقلب النصّ والأيقونات مع الصندوق وتترك اللوحة مرسومة بالمقلوب. والموضع الوحيد الذي يحتاج تدخّلاً يدوياً هو transform-origin في حركة الفتح: الخاصّية لا تقبل كلمة منطقية على أيّ من محوريها، فما تكتبه transform-origin: top left تعيد كتابته top right داخل [dir="rtl"] ويبقى النصف الرأسي top كما هو.

أسماؤه في الكود

كل سطر هو كلمة مكتبة واحدة عن نفس الشيء. خد السطر اللي بيكلّم مشروعك.

HTMLpopover="auto" + popovertargetربط أصلي بين الزرّ واللوحة يعطيك الطبقة العليا والإغلاق الخفيف بلا سطر JavaScript واحد.
CSS:popover-openالمُحدِّد الذي يمسك الحالة المفتوحة وحدها، فاللوحة display: none فيما عدا ذلك.
CSSanchor-name + position-anchorيربط اللوحة بمشغّلها بلا مكتبة تموضع ولا حساب في JavaScript.
CSSposition-try-fallbacks: flip-block, flip-inlineالانقلاب عند حافّة الشاشة، والنصف inline منه يتبع اتجاه الصفحة وحده.
ARIArole="dialog" without aria-modalالدور يسمّي الطبقة، و aria-modal هنا ادّعاء يخفي بقيّة الصفحة عن قارئ الشاشة.
Radix UI<Popover.Root modal={false}>false هي القيمة الافتراضية: بلا حبس تركيز، و <Popover.Anchor> يفصل المرساة عن المُشغِّل.
Floating UIflip() + shift() + arrow()flip و shift يحفظان اللوحة داخل الشاشة، و arrow يضع رأس السهم، و autoUpdate يتبع المُشغِّل أثناء التمرير.

شوف كمان

آخر تحديث · 2026-08-19