UIDex

لوحة الأوامر

Command Palette

role="dialog" + role="combobox"

اسمه كمانكوماند باليت، شريط الأوامر، صندوق الأوامر السريع، قائمة الإجراءات السريعة، نافذة Cmd+K، التنقّل السريع، command bar، command menu، quick open، Cmd+K menu، quick switcher، action launcher

لوحة الأوامر نافذة حوارية حاجبة تُفتح باختصار من لوحة المفاتيح، وفيها حقل واحد يكتب فيه المستخدم فتضيق تحته قائمة أوامر تُنفَّذ في مكانها. ومخرجها فعل لا قيمة: عند Enter يقع شيء في التطبيق (يُفتح ملفّ، تتبدّل السمة، تُنشأ مهمّة) ولا يبقى في الحقل نصّ. وهي ليست حقل بحث، لأن حقل البحث يردّ محتوى يقرؤه المستخدم ثم يقرّر بنفسه، بينما اللوحة تنفّذ نيابةً عنه. ولا هي قائمة (menu)، فالقائمة تعرض بنودها كلها ويُتنقَّل بينها بالأسهم بلا كتابة، أما اللوحة فتبدأ من فرض أن الأوامر أكثر من أن تُعرض دفعة واحدة. ولا هي حقل إكمال تلقائي (combobox)، فذاك يودع قيمة مختارة داخل حقل ثم يعيد المستخدم إلى النموذج ليُكمل، واللوحة تغلق نفسها لأن الأمر انتهى. والحكم سؤال واحد: ماذا يبقى بعد Enter؟ صفحة نتائج تعني بحثاً، وقيمة تستقرّ في حقل تعني إكمالاً تلقائياً، وحالة تغيّرت في التطبيق تعني لوحة أوامر.

لو قلت عليه…

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

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

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

لوحة المشروع
RTLالتلميح عند النهاية المنطقية، وترتيب مفاتيحه يبقى لاتينياً

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

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

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

ابنِ لوحة أوامر تُفتح باختصار من لوحة المفاتيح. سجّل مستمع keydown واحداً على document وطابق على event.code === "KeyK" مع metaKey أو ctrlKey لا على event.key وحده، لأن المفتاح نفسه يعطي «ن» على التخطيط العربي، ثم استدعِ preventDefault لأن المتصفّح يربط Ctrl+K بالبحث من شريط العنوان. الغلاف عنصر role="dialog" بـ aria-modal="true" واسم مقروء، ومعه حبس تركيز مكتوب بيدك لأن aria-modal لا يحبس شيئاً، و Escape يغلق ويعيد التركيز إلى العنصر الذي فُتحت منه. داخله <input> واحد يحمل dir="auto" و role="combobox" و aria-expanded و aria-autocomplete="list" و aria-controls على معرّف اللائحة و aria-activedescendant يشير إلى معرّف الصفّ المميّز، ولا تنقل التركيز إلى الصفوف إطلاقاً. اللائحة role="listbox" وصفوفها role="option" لكل صفّ معرّف مشتقّ من معرّف الأمر، والمجموعات عناصر role="group" مربوطة بعناوينها عبر aria-labelledby، وتُخفى المجموعة كلها متى فرغت بعد التصفية. رتّب النتائج بالاستخدام الأخير والتكرار قبل قوّة المطابقة الحرفية، ووحّد النصّ قبل المقارنة: انزع التشكيل والتطويل وردّ صور الهمزة إلى ا. ضع منطقة aria-live="polite" موجودة منذ الفتح تعلن عدد النتائج بعد تصفية مؤخَّرة، واجعل الحالة الفارغة تعرض إجراءً قابلاً للتنفيذ لا اعتذاراً، ولا تبنِها بـ role="option". اعرض اختصار كل أمر في طرف صفّه المنطقي داخل <kbd> ضمن حاوية dir="ltr" حتى لا تنفصل ⌘ عن الحرف بعدها، ولفّ كل تسمية أمر في <bdi>. واحرص أن يكون لكل أمر مدخل ظاهر خارج اللوحة. واستعمل الخصائص المنطقية في كل شيء: margin-inline-start: auto لتلميح الاختصار، و border-inline-start لشريط الصفّ النشط، و padding-inline للحشو، و text-align: start للعناوين، ولا تكتب left ولا right ولا تستعمل box-shadow: inset لرسم المؤشّر.

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

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

عزل اسم الأمر

لا ينعكس

الصفوف عربية الاتجاه وأسماء الأوامر تأتي مختلطة: «فتح Pull Request جديد» أو «تصدير التقرير إلى PDF». والمحارف المحايدة الملاصقة للمقطع اللاتيني (النقطتان، القوسان، النقاط الثلاث، الشرطة المائلة) تلتحق بالنصّ العربي حولها فتنتقل إلى الطرف الآخر من السطر. لفّ كل تسمية في <bdi>، فهو يحمل dir="auto" و unicode-bidi: isolate معاً ويجعل التسمية وحدة اتجاه مستقلّة لا يُعاد ترتيب ما بداخلها مع ما حولها. ولا تكتفِ بـ dir="auto" على حاوية اللائحة كلها: الخوارزمية تحسب الاتجاه من أوّل محرف قويّ في العنصر، فيرث كل صفّ اتجاه الصفّ الأوّل.

تلميح الاختصار داخل الصفّ

ينعكس

التلميح يجلس عند نهاية الصفّ المنطقية، فينتقل إلى يسار الصفّ في العربية: استعمل margin-inline-start: auto داخل صفّ flex، لا margin-left: auto ولا float. أما ترتيب المفاتيح داخل التلميح فلا ينعكس أبداً، والرموز ⌘ و⇧ و⌥ فئتها الاتجاهية في Unicode هي ON أي محايد آخر، فإذا وقعت في سياق عربي أخذت اتجاه الفقرة وانفصلت عن الحرف اللاتيني الذي تسبقه، فيظهر ⌘K بالرمز على يمين الحرف بدل يساره. اكتب على حاوية المفاتيح dir="ltr" أو لفّها بـ <bdi dir="ltr">. وإذا رسمت كل مفتاح في <kbd> مستقلّ داخل صفّ flex فالمحور المضمّن نفسه سينعكس ويعرض K قبل ⌘: العلاج direction: ltr على الحاوية لا flex-direction: row-reverse.

اتجاه حقل الأمر

ينعكس

لا تثبّت dir="rtl" على حقل الأمر. المستخدم في لوحة عربية يكتب settings و PDF بحروف لاتينية، وفي حقل مقفول على rtl تلتحق النقطة والشرطة المائلة والقوس بالفقرة العربية فتقفز إلى الطرف المقابل من الحقل. اكتب dir="auto" على الـ input نفسه: قاعدة HTML أن اتجاه الحقل يُحسب من أوّل محرف قويّ في قيمته، ويرث اتجاه العنصر الأب ما دامت القيمة فارغة، فيبقى النصّ الإرشادي العربي على اليمين وينقلب الحقل إلى ltr مع أوّل حرف لاتيني. ووضع dir="auto" على الغلاف حول الحقل لا يفعل شيئاً، لأن الحساب هناك يمرّ على النصّ الظاهر ولا يرى قيمة الـ input أصلاً.

شريط الصفّ المميّز

ينعكس

الشريط الرفيع الذي يعلّم الصفّ النشط يقف عند بداية الصفّ المنطقية: إما عنصر ::before بـ inset-inline-start: 0، وإما border-inline-start-width: 2px على الصفّ نفسه مع لون شفّاف في بقيّة الصفوف حتى لا يتزحزح النصّ عند انتقال التمييز. ولا ترسمه بـ box-shadow: inset 2px 0، فإزاحات الظلّ فيزيائية بحتة ولا مكافئ منطقي لها في CSS، فيبقى الشريط على الجهة الخطأ بعد الانعكاس. وأيقونة الأمر تتبع البداية المنطقية نفسها بفضل ترتيب flex، فلا تحتاج إلى margin-right أبداً.

الاختصار على تخطيط عربي

لا ينعكس

مطابقة الضغطة بـ event.key === "k" تسقط عند المستخدم العربي: event.key يحمل المحرف الذي ينتجه التخطيط، والمفتاح الذي عليه K في التخطيط العربي القياسي يعطي «ن». أما event.code فيصف الموضع الفيزيائي ويبقى "KeyK" مهما تبدّل التخطيط، فاعتمده أو اقبل القيمتين معاً. والوجه الآخر أن ما تعرضه في التلميح يجب أن يطابق ما هو مطبوع على المفتاح تحت يد المستخدم، فاقرأ الحرف من navigator.keyboard.getLayoutMap() حيث تتوفّر مع حرف لاتيني احتياطياً، ولا تشتقّه من الكود ثابتاً. وهذه المسألة ليست مسألة اتجاه: القاعدة نفسها تسري على الفارسية والعبرية وعلى كل تخطيط غير QWERTY.

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

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

ARIArole="dialog" aria-modal="true" + role="combobox"نمطان مركّبان في مكوّن واحد؛ تنفيذ أحدهما وحده يترك الآخر مكسوراً.
ARIAaria-activedescendantيشير من الحقل إلى معرّف الصفّ المميّز فيبقى التركيز في الحقل والكتابة مستمرّة، و aria-controls هي ما يجعل الإشارة إلى معرّف خارج الحقل مقبولة.
HTMLdialog.showModal()يعطيك الطبقة العليا وتعطيل ما خلفه و Escape بلا كود، لكنه ينقل التركيز إلى أوّل عنصر قابل للتركيز فضع autofocus على الحقل.
Radix UI<Dialog.Root> + cmdk <Command>التقسيم الشائع: راديكس للحجب وحبس التركيز، و cmdk للتصفية والتنقّل داخل اللائحة.
shadcn/ui<CommandDialog> + <Command shouldFilter={false}>غلاف جاهز فوق cmdk، لكن shouldFilter خاصّية الـ Command الداخلي لا الـ CommandDialog، فاضبطها هناك وصفِّ بنفسك حين تحتاج توحيد الهمزات وترتيب الأوامر الأخيرة.
HTML<bdi>يعزل اسم الأمر اللاتيني داخل صفّ عربي فلا تهاجر النقاط والأقواس إلى الطرف الآخر.
CSSborder-inline-startالبديل المنطقي لشريط الصفّ النشط؛ إزاحات box-shadow لا تقبل صيغة منطقية أصلاً.

شوف كمان

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