تُرجمت هذه الصفحة آليًا وقد تحتوي على أخطاء. اقرأ الأصل الإنجليزي

العناصر

العناصر

يبني Native Shell كل شاشة من مجموعة صغيرة من العناصر: عناصر أولية (primitives) مثل View وText وImage، ومكوّنات أساسية (base components) مثل Button وDialog وNavStack. كل عنصر أولي هو عرض أصلي (native view): صنف فرعي من UIView على iOS وMac Catalyst، أو View على Android، أو عرض AppKit أو GTK أو Win32 على أنظمة سطح المكتب، وينشئه بيئة تشغيل NSH ويتولى تخطيطه. كل ما يعرضه التطبيق مصنوع منها: أزرار التطبيق وبطاقاته وصفوفه وشاشاته هي مكوّنات خاصة به، تُرسم بهذه العناصر ويُنسّقها التطبيق، فيكون مظهره من صنعه بالكامل.

كل مثال في هذه الصفحات متاح بلغتي Rust وTSX: اختر تبويبًا، وستتبعه جميع الأمثلة في الصفحة.

use nsh_core::prelude::*;

fn app() -> impl IntoElement {
    let count = signal(0_u32);
    view()
        .flex_1()
        .items_center()
        .justify_center()
        .gap_3()
        .child(text(move || format!("Tapped {} times", count.get())).text_2xl())
        .child(
            view()
                .rounded_lg()
                .bg(rgb(0x2563eb))
                .px_4()
                .py_2()
                .active(|s| s.opacity(0.6))
                .on_press(move || count.update(|n| *n += 1))
                .child(text("Tap").text_color(white())),
        )
}

في Rust، العناصر الأولية هي دوال من nsh_core::prelude (view() وtext(..) وimage(..))، وتأتي المكوّنات الأساسية من الحزمة nsh_base. الأنماط هي دوال مكتوبة الأنواع (.flex_1() و.gap_3() و.px(px(20.0)) و.bg(..))، وأنماط الحالات تأخذ closure ‏(.active(|s| s.opacity(0.6)))، والمستمعات هي closures تستقبل حدثها (.on_press(move || ..) و.on_change(move |text: &str| ..))، وتضيف .child(..) العناصر الأبناء. قطعة الواجهة هي دالة عادية تعيد impl IntoElement؛ تُنفَّذ مرة واحدة، وتحدّث الإشارات (signals) التي تقرؤها داخل closures ‏(text(move || ..) و.bg(move || ..)) ما ترتبط به. راجع البناء باستخدام Rust.

في TSX، كل وسم مكتوب بنمط PascalCase ويسمّي ارتباطًا (binding) موجودًا في النطاق: تأتي العناصر الأولية من nsh والمكوّنات الأساسية من nsh/base. <If> و<Each> فقط عنصران عامّان لا يحتاجان إلى استيراد. الوسم بأحرف صغيرة مثل <view> يسبب خطأ في البناء، لأن TypeScript كان سيفحصه مقابل نوع عام واحد لكل اسم، ولن تتمكن المكوّنات العامة (generic) مثل List وEach من استنتاج أنواع عناصرها. يتعرف المترجم على العناصر من خلال عمليات الاستيراد وينشئ العروض الأصلية في موضعها، لذلك لا يكلّف استيرادها شيئًا وقت التشغيل. راجع JSX والمكوّنات.

import { Canvas, Image, List, Overlay, Scroll, Shader, Text, Video, View, WebView } from "nsh";
import { Button, Dialog, Menu, NavPage, NavStack, Popover, Sheet, Tooltip, Input } from "nsh/base";

#العناصر الأولية

تُستورد من nsh في TSX، ومن nsh_core::prelude في Rust.

العنصرRustالعرض الأصلي (iOS / Android)الصفحة
<View>view()UIView / ViewGroup؛ ومع selectable تُحدَّد النصوص بداخله معًاView، تحديد النص
<Text>text(content)عرض CoreText / عرض StaticLayoutText
<Image>image(src)عرض بطبقة صورة / عرض صورةImage
<Scroll>scroll()UIScrollView / ScrollViewScroll
<List>virtual_list(count, |at| row)UIScrollView / ScrollView مع خلايا معاد تدويرهاList
<Canvas>canvas(painter)عرض يعيد تشغيل رسم ثنائي الأبعادCanvas
<Shader>shader(program)عرض يرسم برنامج WGSL مربعه على GPU، أسفل عناصره الأبناءShader
<WebView>web_view(url)، web_view_html(html, base)WKWebView / android.webkit.WebViewWebView
<Video>video(url)AVPlayerLayer / MediaPlayer على TextureViewVideo
<Overlay>overlay()عرض فوق النافذة (أو الحوار الموجود فيه)، يوضع بالنسبة إلى نقطة ارتكازOverlay
<If>، <Each>when(..)، each(..)، dynamic(..)تدفق التحكم: العناصر التي يختارانهاتدفق التحكم
<InputAccessoryView> (nsh/mobile)input_accessory_view(id)شريط فوق لوحة المفاتيح على iOSInputAccessoryView

يُبقي الحشو (padding) والهامش (margin) المحتوى بعيدًا عن واجهة النظام باستخدام env() من CSS: env(safe-area-inset-top) وبقية الحواف (المنطقة الآمنة) وenv(keyboard-inset-height) (لوحة المفاتيح).

#الأساسية

تُستورد من nsh/base في TSX، ومن الحزمة nsh_base في Rust. تمتلك هذه المكوّنات السلوك والحالة والتركيز وإمكانية الوصول، ويأتي مظهرها من التطبيق.

المكوّنRustوظيفتهالصفحة
AlertDialognsh_base::alert_dialog()تنبيه النظام وورقة الإجراءات (action sheet)AlertDialog
<Accordion> وأجزاؤهnsh_base::accordion()، accordion_item()، …أقسام متراصة تتوسع وتنطوي تحت عناوينها، قسم واحد في كل مرة أو عدة أقسامAccordion
<Avatar>، <AvatarGroup>nsh_base::avatar()، avatar_group()صورة شخص، أو بديل عنها أثناء التحميل أو عند الفشل، وصف منها يعرض الأولى فقطAvatar
<Button>nsh_base::button()عنصر تحكم قابل للضغط بحالاته ودورهButton
<Carousel> وأجزاؤهnsh_base::carousel()، carousel_content()، …شرائح تُعرض واحدة في كل مرة، تُمرَّر أصليًا، مع السابق / التالي وترقيم الصفحات ومفاتيح لوحة المفاتيح وتكرار سلسCarousel
<Checkbox>nsh_base::checkbox()عنصر تحكم محدد أو غير محدد أو غير حاسم، مع مؤشرCheckbox
<Collapsible>nsh_base::collapsible()منطقة يتوسع محتواها وينطويCollapsible
<Command>nsh_base::command()لوحة أوامر: حقل بحث فوق أوامر تُصفّى أصليًا، وقائمة افتراضية (virtualized)، ومفاتيح لوحة Cmd+KCommand
<DataTable>nsh_base::data_table()جدول بأي عدد من الصفوف والأعمدة، قابل للتحديد والفرز، مع أعمدة يمكن تغيير حجمها ونقلهاDataTable
<Dialog>nsh_base::dialog()يعرض عناصره الأبناء فوق كل شيء آخرDialog
<DiffView>nsh_base::diff_view()الفروق بين نصين أو ضمن تصحيح (patch)، بعرض مقسّم أو موحّدDiffView
<DockArea>nsh_base::dock_area()مساحة عمل من أقسام ومجموعات تبويبات ولوحات مثبّتة على الحواف يرتبها المستخدمDock
<Editor>nsh_base::editor()محرر شيفرة مع تمييز الصياغة وعدة مؤشرات إدخال وطيّ وبحثEditor
<HoverCard>nsh_base::hover_card()معاينة لما يشير إليه المشغّل (trigger)، بينما يستقر المؤشر عليهHoverCard
<Input>nsh_base::input()حقل إدخال النص الأصلي (UITextField أو UITextView / EditText)Input
<Link>nsh_base::link()هدف ضغط يفتح صفحة أو موقعًا أو مسارًا في التطبيقLink
<Menu>، <ContextMenu>، <MenuItem>، <MenuSeparator>، <Submenu>nsh_base::menu()، …القوائم: نوافذ منبثقة بقوائم فرعية متتالية على أنظمة سطح المكتب، وأوراق (sheets) على الهواتفMenu
<NavStack>، <NavPage>nsh_base::nav_stack()، nsh_base::nav_page()تنقّل أصلي: مكدّس من الصفحات مع ترويسة المنصة وإيماءات الرجوعNavStack
<NumberInput>nsh_base::number_input()رقم يُكتب في حقل أو يُزاد وينقص بالأزرار والمفاتيحNumberInput
<OtpInput>nsh_base::otp_input()رمز لمرة واحدة بعدد ثابت من الأرقام، يُكتب أو يُلصق أو يُملأ تلقائيًا في خلاياOtpInput
<Pagination>nsh_base::pagination()صفحات مجموعة: السابق، والصفحات المحيطة بالحالية، وعلامات الحذف، والتاليPagination
<Tree>nsh_base::tree()عناصر تحتوي عناصر، موسّعة ومطوية، يُحدَّد منها واحد: افتراضية (virtualized) وتشكّل محطة واحدة في تركيز لوحة المفاتيحTree
<Plot>، <Line>، <Area>، <Bar>، <Grid>، <Pie>، <Arc> (nsh/base/plot)nsh_base::plot::{plot, line, area, bar, grid, pie, arc}إطار مخطط ورموزه البيانية، تُرسم أصليًا من صفوف التطبيقPlot
<Popover>nsh_base::popover()محتوى مرتبط بمشغّل (trigger)، فوق كل شيءPopover
<Progress>nsh_base::progress()شريط تقدم: مدى تقدم مهمة، أو أنها قيد التنفيذProgress
<Radio>، <RadioGroup>nsh_base::radio()، radio_group()خيار واحد من مجموعة يختار المستخدم منها واحدًا، والمجموعة نفسهاRadio
<ResizablePanelGroup>، <ResizablePanel>nsh_base::h_resizable()، resizable_panel()لوحات متجاورة أو متراصة، مع مقابض لتغيير حجمهاResizable
<Scrollbar>nsh_base::scrollbar()شريط تمرير بمظهر خاص بالتطبيق لعرض تمرير أو قائمةScrollbar
<Select>nsh_base::select()قيمة واحدة تُختار من قائمة عناصر، مع لوحة المفاتيح والبحث والأقسامSelect
<Sheet>nsh_base::sheet()لوحة عند حافة الشاشة فوق خلفية معتمةSheet
<Slider>nsh_base::slider()قيمة، أو مدى من قيمتين، تُختار على امتداد مسارSlider
<Switch>nsh_base::switch()عنصر تحكم تشغيل / إيقاف بمسار ومقبضSwitch
<Table> وأجزاؤهnsh_base::table()، table_row()، …جدول من صفوف وخلايا التطبيق نفسه، مع أدوار الجدولTable
<Tab>، <Tabs>nsh_base::tab()، tabs()تبويب واحد من قائمة تبويبات، والقائمة نفسهاTabs
<TextView>nsh_base::text_view()يعرض مستند Markdown أو HTML بنصوص وعروضTextView
<Toast>، <ToastStack>nsh_base::toast()، toast_stack()الجذر الدلالي لإشعار منبثق (toast)؛ إشعارات تُرتَّب كطبقات انطلاقًا من حافةToast
<Toggle>، <ToggleGroup>nsh_base::toggle()، toggle_group()زر يكون في حالة تشغيل أو إيقاف، وشريط أدوات منهاToggle
<Toolbar>، <ToolbarGroup>، <ToolbarSeparator>nsh_base::toolbar()، toolbar_group()، toolbar_separator()شريط من الأزرار وأزرار التبديل والروابط، ومحطة واحدة في تركيز لوحة المفاتيحToolbar
<Tooltip>nsh_base::tooltip()تلميح بجوار مشغّله (trigger)، عند تمرير المؤشر أو إبقاء الإصبع مضغوطًاTooltip

#الخصائص المشتركة

يقبل كل عنصر أولي الخصائص التالية (النوع CommonProps في TSX؛ والسمات Styled وInteractive وAccessible في Rust). تسرد صفحات العناصر خصائصها الخاصة فقط. يذكر عمود Rust اسم دالة البناء.

#التنسيق

الخاصيةRustالنوعالوصف
styleدوال Styled مكتوبة الأنواع؛ .inline_style(style)StyleProp<ViewStyle> (TextStyle لـ <Text> و<Input>، ImageStyle لـ <Image>)نمط مضمّن، أو مصفوفة أنماط تُدمج من اليسار إلى اليمين (تُتجاهل القيم الخاطئة falsy؛ ويمكن تداخل المصفوفات، لذا يقبل style={[base, props.style]} قيمة props.style هي نفسها مصفوفة). له الأولوية على class. راجع مرجع الأنماط.
classدوال Styled مكتوبة الأنواع (.flex_row().gap_2().p_4())stringأصناف Tailwind، تُحلّ أصليًا ("flex-row gap-2 p-4 dark:bg-zinc-900"). الأصناف اللاحقة لها الأولوية على السابقة. راجع Tailwind.
classList.style_when(when, |s| ..)Record<string, boolean | null | undefined>أصناف تُطبَّق ما دامت قيمتها صحيحة (truthy) ({ "opacity-50": busy() }).
disabled.disabled(..)booleanيتجاهل الضغطات (دون أي استجابة بصرية للضغط أيضًا)، ويطبّق أنماط disabled:، ويعطّل عناصر التحكم مثل حقول الإدخال، وتعلن قارئات الشاشة أنه معطّل (accessibilityState.disabled).

#الضغطات والاستجابة

تجري معالجة الضغط أصليًا: تتعرف المنصة على الضغطة وتطبّق أنماط الضغط الخاصة بالتطبيق، ولا تعلم شيفرة التطبيق بها إلا بعد ذلك. لا يستجيب إلا أعمق عرض قابل للضغط؛ وداخل عروض التمرير ينتظر بدء الضغط (press-in) نحو 130 ms ليتمكن التمرير من البدء أولًا.

الخاصيةRustالنوعالافتراضيالوصف
onPress.on_press(..)(e: PressEvent) => voidانتهت نقرة داخل العرض.
onLongPress.on_long_press(..)(e: PressEvent) => voidبقي الإصبع مضغوطًا مدة delayLongPress.
onPressDown.on_press_down(..)(e: PressEvent) => voidلمس إصبع العرض.
onPressUp.on_press_up(..)(e: PressEvent) => voidرُفع الإصبع أو غادر العرض.
delayLongPress.long_press_delay(ms)number500الميلي ثانية قبل أن تصبح الضغطة ضغطة مطوّلة.
hitSlop.hit_slop(EdgeInsets::all(px(8.0)))number | Partial<Edges>يوسّع منطقة اللمس خارج الحدود، بالنقاط (points).

PressEvent هو { position, pagePosition, modifiers, clickCount }: موضع المؤشر داخل العرض وداخل عرضه الجذر (Point)، ومفاتيح التعديل المضغوطة عند بدء الضغطة، وعدد النقرات المتتابعة (2 للنقر المزدوج). وعندما لا تُبلغ المنصة عن مؤشر، فهو نقطة أصل العرض دون مفاتيح ونقرة واحدة.

مفتاح النمط pointerEvents (.pointer_events(PointerEvents::BoxNone) في Rust، والأصناف pointer-events-*) يحدد أي اللمسات يستقبلها العرض والعروض الموجودة بداخله: "none" لا هذا ولا تلك (ويستعيدها أي عرض بداخله يضبط "auto")، "box-none" العروض الموجودة بداخله فقط، "box-only" العرض نفسه فقط (اللمس والإيماءات).

استجابة الضغط من شأن التطبيق: يُطبَّق activeStyle (أو أصناف active:، أو .active(..) في Rust) أصليًا ما دام العرض مضغوطًا، دون أي ذهاب وإياب إلى شيفرة التطبيق (انظر أدناه).

#أنماط الحالات

أنماط تُطبَّق ما دام العرض في حالة معينة، فوق أنماطه الخاصة، وتتم أصليًا. كل منها هو الصيغة الكائنية لمتغيّر (variant) في Tailwind. الحالات اللاحقة لها الأولوية (selected وchecked وindeterminate، ثم hover وfocus وactive)؛ ولـ disabledStyle الأولوية على الجميع، ولا تُطبَّق الحالات الأخرى ما دام العرض معطّلًا. يأخذ كل منها نمط العنصر نفسه: TextStyle على <Text>، وInputStyle على <Input>، وImageStyle على <Image>، وViewStyle في غير ذلك.

الخاصيةRustمتغيّر الصنفيُطبَّق
activeStyle.active(|s| ..)active:أثناء الضغط.
hoverStyle.hover(|s| ..)hover:ما دام مؤشر فوق العرض.
focusStyle.focus(|s| ..)focus:ما دام التركيز على العرض.
focusWithinStyle.focus_within(|s| ..)focus-within:ما دام التركيز على العرض أو على عرض بداخله (يُعدّ محتوى Overlay داخل العرض الذي يوجد فيه).
disabledStyle.disabled_style(|s| ..)disabled:ما دام disabled.
checkedStyle.checked(|s| ..)، .checked_style(style)checked:ما دامت accessibilityState.checked تساوي true (مربع اختيار، أو مفتاح تبديل، أو زر راديو؛ "mixed" ليس محددًا).
indeterminateStyle.indeterminate(|s| ..)، .indeterminate_style(style)indeterminate:ما دامت accessibilityState.checked تساوي "mixed" (مربع اختيار ليس محددًا ولا غير محدد).
selectedStyle.selected(|s| ..)selected:ما دامت accessibilityState.selected تساوي true (Button محدد، أو تبويب).
expandedStyle.expanded(|s| ..)، .expanded_style(style)aria-expanded:ما دامت accessibilityState.expanded تساوي true (مشغّل Popover أو Menu ما دامت نافذته المنبثقة مفتوحة).
pressedStyle.aria_pressed(|s| ..)، .pressed_style(style)aria-pressed:ما دامت accessibilityState.pressed تساوي true (زر تبديل في حالة التشغيل؛ "mixed" ليس مضغوطًا). وليس activeStyle، أي ضغطة جارية.
view()
    .rounded_lg()
    .bg(rgb(0x2563eb))
    .px_4()
    .py_2()
    .active(|s| s.opacity(0.6))
    .hover(|s| s.bg(rgb(0x1d4ed8)))
    .on_press(save)
    .child(text("Save").text_color(white()))

#التحويم (Hover)

يسمع التطبيق دخول الفأرة إلى عنصر عرض (view) وحركتها فوقه ومغادرتها له: على macOS وWindows وLinux وواجهة GPUI الخلفية ومتصفحات سطح المكتب، وبمؤشر iPad أو فأرة Mac على iOS، وبفأرة أو قلم على Android. أنماط التحويم (hover:، .hover(..)) تنسّق العناصر المحوَّم فوقها في الأماكن نفسها.

الخاصيةRustالنوعالوصف
onMouseEnter.on_mouse_enter(..)(e: MouseEvent) => voidدخلت الفأرة إلى العنصر أو إلى عنصر بداخله (يقابل mouseenter في DOM).
onMouseMove.on_mouse_move(..)(e: MouseEvent) => voidتحركت الفأرة فوق العنصر (أو فوق عنصر بداخله)، سواء كان هناك زر مضغوط أم لا، أو ضُغط مفتاح تعديل أو حُرر بينما تستقر الفأرة فوقه دون زر مضغوط (يقابل mousemove في DOM).
onMouseLeave.on_mouse_leave(..)(e: MouseEvent) => voidغادرت الفأرة العنصر والعناصر التي بداخله، أو النافذة؛ e هو آخر موضع لها (يقابل mouseleave في DOM).

مثل :hover في CSS وmouseenter في DOM، يُعدّ العنصر محوَّمًا فوقه ما دامت الفأرة فوقه أو فوق أي شيء بداخله، لذا تسمع العناصر المحيطة بالعنصر الذي تحت الفأرة onMouseEnter أيضًا (من الخارج إلى الداخل؛ onMouseLeave من الداخل إلى الخارج). تصل الأحداث بعد وقوع الفعل ولا تصل أبدًا عند اللمس. العنصر الذي يُزال من تحت الفأرة يسمع onMouseLeave، وذلك على Linux وWindows عند حركة الفأرة التالية.

يبيّن MouseEvent موضع الفأرة، كما يفعل MouseMoveEvent في GPUI: position داخل العنصر (Point بالنقاط، مع التراجع عن تحويلاته)، وpagePosition داخل العنصر الجذر الذي يوجد فيه، كما تعطيه اللمسات، وmodifiers المضغوطة (control، alt، shift، platform: Command على منصات Apple، ومفتاح Windows في غيرها)، وpressedButton، الزر المضغوط ("left"، "right"، "middle"، "back" أو "forward"، وهو MouseButton في GPUI) أو null؛ وفي Rust position، page_position، modifiers وpressed_button. على أنظمة سطح المكتب والويب يُعدّ السحب لمسة أيضًا (onTouchMove، المستجيب)، وتصل حركاته إلى onMouseMove مع ضبط pressedButton: التغذية الراجعة التي تتبع التحويم فقط تتجاهلها؛ وعلى iOS وAndroid يبلّغ التحويم عن المؤشر دون زر مضغوط. تصل الحركات مرة واحدة في الإطار كحد أقصى، وهي الأحدث، وتصل دائمًا قبل onMouseLeave الذي يليها، لذا يمكن للمعالج إعادة الرسم عند كل حركة:

// A crosshair over a chart.
let at = signal(None::<Points>);

view()
    .h(px(200.0))
    .on_mouse_move(move |e: &MouseEvent| at.set(Some(e.position.x)))
    .on_mouse_leave(move |_: &MouseEvent| at.set(None))
    .child(when(move || at.get().is_some(), move || {
        view()
            .absolute()
            .left(move || at.get().unwrap_or_default())
            .top_0()
            .bottom_0()
            .w(px(1.0))
            .bg(rgb(0x888888))
    }))

#التلميحات (Tooltips)

يمنح tooltip أي عنصر تلميحًا، يظهر فوقه على أنظمة سطح المكتب (macOS وWindows وLinux وواجهة GPUI الخلفية ومتصفحات سطح المكتب) بعد أن يستقر المؤشر عليه نصف ثانية، ويبقى حتى يغادره المؤشر أو يضغطه. لا تعرض الهواتف واللوحيات أي تلميح. لا يلتقط التلميح المؤشر ولا الضغطات أبدًا، فيعمل العنصر تمامًا كما يعمل بدونه.

الخاصيةRustالنوعالوصف
tooltip.tooltip(..)JSX.Elementالتلميح، وهو عنصر ينسّقه التطبيق: Tooltip من نوع nsh/base هو التلميح نفسه، مع موضعه وتأخيره وأحداثه؛ وأي عنصر آخر يُعرض كما هو.
view()
    .on_press(save)
    .tooltip(
        tooltip()
            .rounded_md()
            .bg(rgb(0x111827))
            .px_2()
            .py_1()
            .child(text("Save the document").text_color(white())),
    )
    .child(text("💾"))

يحدد Tooltip مظهره وموضعه وتأخيره وأحداثه بنفسه، كما يفعل .tooltip(..) في GPUI. التطبيق الذي فيه تلميحات كثيرة يحتفظ بمكوّن صغير خاص به يلفّ نصًا في Tooltip.

#قوائم السياق (Context menus)

يخبر onContextMenu العنصر بأن الزر الثانوي ضُغط فوقه: نقرة يمين، أو Control-click على macOS، أو نقرة بإصبعين على لوحة اللمس، أو النقرة الثانوية لمؤشر iPad؛ وفي المتصفحات مفتاح القائمة في لوحة المفاتيح أيضًا. افتح قائمة التطبيق الخاصة هناك؛ وفي TSX يعرض Menu.popup قائمة النظام:

// The app's own menu, placed where the click was.
let menu_at = signal(None::<Point<Points>>);

view()
    .on_context_menu(move |e: &ContextMenuEvent| menu_at.set(Some(e.page_position)))
    .child(text(row.name.clone()))
الخاصيةRustالنوعالوصف
onContextMenu.on_context_menu(..)(e: ContextMenuEvent) => voidضُغط الزر الثانوي فوق العنصر أو فوق عنصر بداخله.

يحتوي ContextMenuEvent على position داخل العنصر، وpagePosition داخل عنصره الجذر (حيث يضع Menu.popup قائمة: Menu.popup(items, e.pagePosition)) وmodifiers؛ وفي Rust position وpage_position وmodifiers. أقرب عنصر يستمع، بدءًا من العنصر الذي تحت المؤشر ثم نحو الخارج، هو الذي يسمعه. حقل إدخال نصي، أو نص قابل للتحديد له قائمة تحرير (Windows وواجهة GPUI الخلفية والمتصفحات)، إذا كان أقرب من ذلك العنصر فإنه يعرض قائمته الخاصة بدلًا منه. تقرر المنصة ذلك لحظة نزول الزر، بناءً على ما هو مركّب، فيُبلَّغ المعالج بعد وقوع الفعل ولا حاجة إلى إلغاء أي شيء. العنصر disabled يترك النقرة لأقرب مستمع مفعّل حوله. على الشاشات اللمسية يفتح الضغط المطوّل القوائم: عالج onLongPress بالدالة نفسها.

#تركيز لوحة المفاتيح (Keyboard focus)

على macOS والويب، ينقل Tab تركيز لوحة المفاتيح بين نقاط توقفه بترتيب الشجرة: حقول الإدخال، والعناصر التي تقبل الضغط، وكل عنصر focusable. أنماط التركيز (focus:، .focus(..)) تنسّق العنصر المركَّز، ويضغطه Return أو Space إذا كان يقبل الضغط. النقرة تضغط العنصر دون تركيزه، لذا تظهر أنماط التركيز لتركيز لوحة المفاتيح فقط.

التركيز والضغط منفصلان. العنصر الذي يستقبل المفاتيح وهو مركَّز (مخطط، أو منزلق، أو قائمة تحرّك تحديدها بمفاتيح الأسهم) يضبط focusable ويربط مفاتيحه أثناء التركيز (onFocus / onBlur مع keymap)؛ وتظل الضغطة عليه تصل إلى عنصر قابل للضغط حوله، مثل بطاقة تفتح صفحة.

يحرّك focus() الخاص بـ ref تركيز لوحة المفاتيح إلى نقطة توقف كما يفعل Tab (تظهر أنماط تركيزها ويُطلَق onFocus)، مثلًا لإعادته إلى مشغّل نافذة منبثقة أُغلقت، ويزيله blur().

يُنسّق العنصر المحيط بالتركيز نفسه بـ focus-within: (focusWithinStyle، .focus_within(..)) ما دام التركيز عليه أو على أي عنصر بداخله: إطار حقل حول الإدخال الذي يحرّر، أو شريط أدوات بينما أحد أزراره عليه التركيز. محتوى Overlay داخل العنصر الذي يوجد فيه التراكب، أينما ظهر، لذا تُعدّ النافذة المنبثقة جزءًا من العنصر الذي يحمل مشغّلها. في Rust يسمع المكوّن دخول التركيز إلى عنصر وخروجه منه عبر on_focus_in(..) وon_focus_out(..) الخاصين بـ ref: والتنقل بين العناصر داخله ليس دخولًا ولا خروجًا.

الخاصيةRustالنوعالافتراضيالوصف
focusable.focusable(..)booleantrue للعناصر التي تقبل الضغطما إذا كان العنصر نقطة توقف لمفتاح Tab. true يجعل أي عنصر نقطة توقف، سواء قبل الضغطات أو تركها للعناصر المحيطة به؛ false يترك العنصر القابل للضغط للمؤشر (خلفية تغلق نافذة منبثقة، وصفوف تتنقل بينها مفاتيح الأسهم).
onFocus.on_focus(..)() => voidمنح Tab (أو focus()) العنصر تركيز لوحة المفاتيح.
onBlur.on_blur(..)() => voidغادر تركيز لوحة المفاتيح العنصر.
view()
    .rounded_lg()
    .p_3()
    .focus(|s| s.bg(rgb(0xe0f2fe)))
    .on_press(open)
    .child(text("Open"))

// An overlay's backdrop: a click closes it, Tab goes past it to the content.
view().absolute().inset_0().focusable(false).on_press(close)

// A chart in a card: Tab stops at the chart, a click opens the card's page.
view().on_press(open_page).child(
    view()
        .h(px(160.0))
        .focusable(true)
        .focus(|s| s.bg(rgb(0xe0f2fe)))
        .on_focus(move || focused.set(true))
        .on_blur(move || focused.set(false)),
)

#التخطيط والهوية (Layout and identity)

الخاصيةRustالنوعالوصف
onLayout.on_layout(|frame: &Bounds<Points>| ..)(e: { bounds: Bounds }) => voidbounds هو إطار العنصر نسبةً إلى أصله، { origin, size }، بعد كل تخطيط يغيّره.
ref.element_ref(&handle)ViewRef | (node: ViewRef) => voidمرجع العنصر (انظر دوال ref): ViewRef، أو نوع العنصر نفسه فوقه. ref={el} يسند متغيرًا، وref={fn} يستدعي دالة.
nativeID.native_id(id)stringيسمّي العنصر لغيره، مثل InputAccessoryView الذي يشير إليه حقل إدخال.
testID.test_id(id)stringمعرّف لاختبارات واجهة المستخدم (على Android، معرّف مورد العنصر).
key.key(key)stringهوية العنصر بين أشقائه.

#الإيماءات التي تقود القيم المتحركة (Gestures driving animated values)

تربط هذه الخصائص إيماءة بـالقيم المتحركة الأصلية التي تتحرك على خيط واجهة المستخدم، دون أي عمل في شيفرة التطبيق عند كل إطار. انظر اللمس والإيماءات.

الخاصيةRustالنوعالوصف
pan.pan_values(Some(&x), Some(&y)){ x?: AnimatedValue; y?: AnimatedValue }تقود عمليات السحب هذه القيم (بالنقاط).
onPanStart.on_pan_start(..)(e: PanEvent) => voidبدأ سحب.
onPanEnd.on_pan_end(..)(e: PanEvent) => voidانتهى سحب، مع سرعة الإفلات (مثلًا لبدء decay).
pinch.pinch_value(&scale){ scale?: AnimatedValue }تقود قرصات الإصبعين scale.
onPinchStart / onPinchEnd.on_pinch_start(..) / .on_pinch_end(..)(e: { scale: number; velocity: number }) => voidبدأت قرصة / انتهت.
rotation.rotation_value(&angle){ angle?: AnimatedValue }تقود تدويرات الإصبعين angle (بالدرجات).
onRotationStart / onRotationEnd.on_rotation_start(..) / .on_rotation_end(..)(e: { rotation: number; velocity: number }) => voidبدأ تدوير / انتهى.

PanEvent هو { cancelled, translation, velocity, value? } (وفي Rust cancelled، translation، velocity_x، velocity_y، value_x، value_y): هل ألغى النظام السحب، والإزاحة منذ البداية، والسرعة بالنقاط في الثانية (قيم Point)، والقيم المرتبطة في تلك اللحظة، { x?, y? } للمحاور التي يربطها pan (يمكن للسحب أن يمسك قيمة أثناء حركتها).

#اللمسات الخام ونظام المستجيب (Raw touches and the responder system)

للإيماءات التي لا تغطيها الخصائص أعلاه، يقبل كل عنصر خصائص اللمس والمستجيب: onTouchStart، onTouchMove، onTouchEnd، onTouchCancel (وصيغ …Capture الخاصة بها)، onStartShouldSetResponder، onMoveShouldSetResponder (و…Capture)، onResponderGrant، onResponderReject، onResponderStart، onResponderMove، onResponderEnd، onResponderRelease، onResponderTerminate وonResponderTerminationRequest. يستقبل كل معالج GestureResponderEvent ({ identifier, locationX, locationY, pageX, pageY, target, timestamp, touches, changedTouches, nativeEvent, stopPropagation() }); the …ShouldSetResponder handlers also get a gestureState (dx, dy, vx, vy, …). إرجاع true من onResponderGrant يمنع عروض التمرير الأصلية من أخذ اللمسات. panActivation ({ activeOffsetX, activeOffsetY, failOffsetX, failOffsetY }) يجعل العنصر مستجيبًا فقط بعد أن تفعّل المنصة حركة السحب الخاصة بها، فتترك عمليات السحب داخل عروض التمرير المحور الآخر لها (اللمس والإيماءات). يعتمد PanResponder في PanResponder عليها. في Rust يسمع .on_touch(..) كل لمسة (TouchEvent)، ويقرر .claim_touches(..) متى يأخذها العنصر، وتتبع .on_responder_grant(..) و.on_responder_move(..) و.on_responder_release(..) وغيرها المستجيب.

#إمكانية الوصول (Accessibility)

الخاصيةRustالنوعالوصف
accessible.accessible(..)booleanالعنصر عنصر واحد بالنسبة إلى التقنيات المساعدة؛ ولا يُكشف عن أبنائه بشكل منفصل.
accessibilityLabel.label(..)stringما تقرؤه قارئات الشاشة. العناصر التي بلا تسمية تقرأ نصوصها.
accessibilityHint.hint(..)stringما يفعله تنشيط العنصر.
role.role(AccessibilityRole::Button)AccessibilityRoleما هو العنصر ("button"، "header"، "link"، "switch"، …).
accessibilityState.accessibility_state(..){ disabled?, selected?, checked?, busy?, expanded?, pressed?, current? }الحالة التي تبلّغ عنها التقنيات المساعدة (يمكن أن يكون checked وpressed "mixed"؛ وcurrent هو true أو "page"، "step"، "location"، "date"، "time").
accessibilityLevel.accessibility_level(..)numberمستوى العنوان (1 هو الأعلى)، وعمق عنصر الشجرة (1 في الأعلى).
accessibilityValue.accessibility_value(..){ min?, max?, now?, text? }قيمة عنصر نطاقي أو نصي.
accessibilityElementsHiddenbooleanيخفي العنصر وشجرته الفرعية عن التقنيات المساعدة.
importantForAccessibility"auto" | "yes" | "no" | "no-hide-descendants"ما إذا كان العنصر مكشوفًا.
accessibilityActions{ name: string; label?: string }[]إجراءات مخصصة تُقدَّم للتقنيات المساعدة.
onAccessibilityAction.on_accessibility_action(..)(e: { actionName: string }) => voidاستدعى المستخدم أحد accessibilityActions.

انظر إمكانية الوصول.

#الخصائص الخاصة بمنصة واحدة (Platform-only props)

الخصائص الموجودة على منصة واحدة فقط توضع في كائن يحمل اسمها في TSX، لتبقى الخصائص المشتركة نفسها في كل مكان. يُرسل كائن المنصة الحالية فقط، وتُهمل الكائنات الأخرى. تتلقاها بُناة Rust كدوال تُطبَّق على منصتها.

الخاصيةالوصف
ios={{…}}iOS وMac Catalyst. <Input> وDialog وWebView تضيف مفاتيحها الخاصة.
android={{…}}Android. يقبل كل عنصر ripple وelevation وaccessibilityLiveRegion (أدناه).
desktop={{…}}منصات سطح المكتب ومتصفحات سطح المكتب؛ تُهمل على الأجهزة المحمولة. يقبل كل عنصر windowDrag (أدناه).
web={{…}}المتصفحات؛ تُهمل في غيرها.
مفتاح androidRustالنوعالوصف
ripple.ripple(Ripple { .. }){ color?, borderless?, radius?, foreground? }تموّج Material أثناء الضغط.
elevation.elevation(px(2.0))numberظل Material (بالنقاط)، من شكل العنصر.
accessibilityLiveRegion"none" | "polite" | "assertive"كيفية الإعلان عن تغيرات العنصر إلى TalkBack.
مفتاح desktopRustالنوعالوصف
windowDrag.window_drag(..)booleanضغط العنصر يسحب النافذة، كما يفعل شريط عنوانها؛ false يُبقي جزءًا من عنصر قابل للسحب قابلًا للنقر. انظر شريط عنوان خاص بك.
use nsh_core::Ripple;

view()
    .rounded_lg()
    .bg(white())
    .p_4()
    .elevation(px(2.0))
    .ripple(Ripple { color: Some(rgba(0x00000022).into()), ..Ripple::default() })
    .on_press(save)
    .child(text("Save"))

انظر الشيفرة الخاصة بكل منصة.

#طرق الـ Ref

في TSX، يستقبل ref الـ ref الخاص بالعنصر، ونوعه هو نوع العنصر نفسه: ViewRef (focus، blur، measure) للعناصر View وText وImage وCanvas وOverlay وShader، ونوع مبني على ViewRef مع طرق العنصر بالنسبة إلى البقية: InputRef (Input)، ScrollRef (Scroll)، ListRef (List)، WebViewRef (WebView)، VideoRef (Video) وNativeComponentRef (المكوّنات الأصلية native components). في Rust، ينشئ element_ref() مقبضًا من النوع ElementRef يقوم .element_ref(&handle) بربطه. ترسل طرقه أوامر إلى الـ view الأصلي، بالترتيب نفسه مع التغييرات التي أُجريت قبلها في الدورة نفسها. توضح صفحة كل عنصر الطرق التي تنطبق عليه. اكتب نوع متغير الـ ref بنوع عنصره نفسه (let field!: InputRef للعنصر <Input>): فالمتغير المكتوب بالنوع ViewRef لا يناسب عنصرًا يحتوي الـ ref الخاص به على طرق خاصة به.

الطريقةRustRefالوصف
focus() / blur()focus() / blur()ViewRefتمنح التركيز لعنصر <Input> (وتُظهر لوحة المفاتيح)، أو تمنح تركيز لوحة المفاتيح لـ view يستقبل الضغطات / تزيل التركيز.
measure()measure()ViewRefمكان الـ view.
setSelection(start, end?)set_selection(start, end)InputRefتحدد start..end بوحدات UTF-16؛ ومؤشر إدراج (caret) بدون end.
scrollTo({ x?, y?, animated? })scroll_to(point, animated)ScrollRef، ListRefتمرّر إلى إزاحة محددة.
zoomTo({ scale, x?, y?, animated? })zoom_to(scale, center, animated)ScrollRef (عنصر <Scroll> قابل للتكبير)تكبّر إلى scale حول نقطة في المحتوى.
scrollToOffset({ offset, animated? })scroll_to_offset(offset, animated)ListRefتمرّر إلى إزاحة محددة على محور القائمة.
scrollToIndex({ index, animated?, viewPosition?, viewOffset? })scroll_to_index(index, position, offset, animated)ListRefتمرّر عنصرًا ليصبح ظاهرًا.
scrollToEnd({ animated? })scroll_to_end(animated)ScrollRef، ListRefتمرّر إلى نهاية المحتوى.
reload()، goBack()، goForward()، stopLoading()، injectJavaScript(script)، postMessage(data)reload()، go_back()، go_forward()، stop_loading()، inject_java_script(..)، post_message(..)WebViewRefتتحكم في الصفحة.
seek(seconds)seek(seconds)VideoRefتنتقل إلى وقت محدد.
command(name, args?)command(name, args)NativeComponentRefتستدعي أمرًا من مكوّن أصلي مسجَّل (nativeComponent).
let field = element_ref();

view()
    .on_press(move || field.focus())
    .child(input().element_ref(&field).placeholder("Tap the row to type"))

#لماذا لا تحجب المعالجات واجهة المستخدم أبدًا

لا ينتظر الخيط الرئيسي (main thread) شيفرة التطبيق أبدًا. فعندما يحتاج النظام إلى إجابة فورية (هل يجوز تغيير هذا النص، وهل يجوز للزر الرجوع أن يُخرج الصفحة من المكدس، وهل يجوز إغلاق هذه الورقة السفلية)، يجيب NSH عنها محليًا اعتمادًا على props جرى ضبطها مسبقًا (maxLength، popOnBack، closeOnSwipe، …)، ثم يصل الحدث إلى التطبيق بعد ذلك. ولهذا السبب يكون هذا السلوك prop وليس قيمة مُعادة من معالج.