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

العناصر

تحديد النص

يجعل View مع selectable النصوص الموجودة بداخله تُحدَّد معًا، كما في صفحات الويب: يمتد تحديد واحد من موضع في نص إلى موضع في نص آخر، عبر العروض (views) وعناصر Text وعناصر TextView، بترتيب القراءة. اجعل سجل محادثة قابلًا للتحديد، فيمكن أن يبدأ التحديد في رسالة وينتهي في التالية؛ ويضع النسخ (Copy) النص المحدد في الحافظة، مع ربط النصوص المتجاورة جنبًا إلى جنب بمسافة جدولة (tab) وربط البقية بفاصل أسطر. يُضبط ذلك في Rust باستخدام view().selectable(true)، وفي TSX باستخدام <View selectable>؛ أما إمكانية تحديد نص منفرد فيحددها النمط userSelect.

el-text-selection.tsx
el-text-selection.tsxLive
import { mount, Scroll, signal, Text, View } from "nsh";
import { Input } from "nsh/base";
import { TextView } from "nsh/base";
import { platform } from "nsh";
import { field, ui } from "./palette";

const NOTES = "- Sync runs **in the background**\n- Conflicts merge *per field*";

function App() {
  const pasted = signal("");
  return (
    <Scroll style={{ flex: 1, backgroundColor: ui().page }}>
      <View style={{ width: "100%", maxWidth: 560, alignSelf: "center", padding: 20, paddingTop: 24, gap: 14 }}>
        <Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>selectable view</Text>
        <Text style={{ color: ui().muted, lineHeight: 20 }}>
          {platform.desktop
            ? "Drag with the mouse from the title into the list: one selection runs across them. Double click a word, triple click a paragraph, Ctrl / Cmd+C copies."
            : "Long press a word, then drag the handles across the texts: one selection runs across them. Copy is in the menu."}
        </Text>

        <View selectable style={{ ...ui().card, borderRadius: 16, padding: 16, gap: 10 }}>
          <Text style={{ fontSize: 17, fontWeight: "600", color: ui().text }}>Release notes</Text>
          <Text style={{ color: ui().text, lineHeight: 22 }}>Version 2.4 moves the app to offline sync.</Text>
          <TextView markdown={NOTES} />
          <View style={{ flexDirection: "row", gap: 8 }}>
            <Text style={{ userSelect: "none", color: ui().faint }}>
              Updated (userSelect none)
            </Text>
            <Text style={{ color: ui().text }}>October 21</Text>
          </View>
        </View>

        <Text style={{ fontSize: 12, color: ui().muted }}>Paste what you copied: the label marked userSelect none stays out of it.</Text>
        <Input
          multiline
          value={pasted()}
          onChange={({ value }) => pasted.set(value)}
          placeholder="Paste here"
          {...field({ style: { height: 110 } })}
        />
        <Text style={{ fontSize: 12, color: ui().muted, fontVariant: ["tabular-nums"] }}>
          {pasted().length} characters, {pasted() ? pasted().split("\n").length : 0} lines
        </Text>
      </View>
    </Scroll>
  );
}

mount(App);
use nsh_base::text_view;
use nsh_core::prelude::*;

fn release_notes(notes: &'static str) -> impl IntoElement {
    view()
        .selectable(true)
        .gap_3()
        .p_4()
        .child(text("Release notes").font_weight(FontWeight::SEMIBOLD))
        .child(text_view().markdown(notes))
        .child(
            view()
                .flex_row()
                .gap_2()
                .child(text("Updated").select_none().text_color(rgb(0x737373)))
                .child(text("October 21")),
        )
}

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

#سجل محادثة

يُحدَّد سجل المحادثة داخل عرض واحد قابل للتحديد كأنه صفحة واحدة: يبدأ السحب في رد وينتهي في رد آخر، وينسخ Copy نص كل رسالة بينهما.

use nsh_base::text_view;
use nsh_core::prelude::*;

fn transcript(replies: Signal<Vec<SharedString>>) -> impl IntoElement {
    scroll().flex_1().child(
        view().selectable(true).p_4().gap_4().child(each(
            replies,
            |reply: &SharedString| reply.clone(),
            |reply, _index| text_view().markdown(move || reply.get()),
        )),
    )
}

#كيف يتم التحديد

المنصةكيف يتم التحديد
macOS, Windows, Linux, GPUIاسحب عبر النصوص؛ النقر المزدوج يحدد كلمة، والنقر الثلاثي يحدد فقرة، والسحب بعدهما يستمر كلمةً كلمة أو فقرةً فقرة. Shift+click يمدد التحديد. Ctrl / Cmd+A يحدد كل شيء في العرض، وCtrl / Cmd+C ينسخ، وEscape أو الضغط خارج العرض يلغي التحديد.
iOS, Androidالضغط المطوّل يحدد كلمة؛ وتمدده مقابض المنصة، وتتضمن قائمة التحرير فيها Copy وSelect All.
Webتحديد المتصفح نفسه، بإيماءاته وقائمته واختصاراته.

#ما الذي يحدده

  • النصوص المستبعدة: النص الذي يحمل userSelect: "none" (.select_none() في Rust، والفئة select-none؛ مثل التسمية (label) أو عنوان الزر) يتجاوزه التحديد وإيماءاته.
  • العروض المتداخلة: العرض القابل للتحديد داخل عرض آخر يُعد جزءًا من العرض الخارجي، فيشملهما تحديد واحد.
  • TextView: يُحدَّد TextView مع selectable كوحدة مستقلة: تُحدَّد فقراته وعناصر قوائمه وخلاياه وكتل الشيفرة فيه معًا. وداخل عرض قابل للتحديد ينضم إلى التحديد الخارجي.
  • النصوص المنفردة: النص الذي يحمل userSelect: "text" وليس داخل أي عرض قابل للتحديد يُحدَّد وحده، كما يصف Text.
  • المظهر هو تمييز المنصة في selectionColor الخاص بكل نص (selection:text-*).
  • القوائم: في List الافتراضية (virtualized)، يشمل التحديد الصفوف المركّبة (mounted)؛ أما الصفوف التي أُعيد تدويرها وخرجت من مجال الرؤية فتغادره.

#ذات صلة

  • View: الخاصية selectable.
  • Text: userSelect وselectionColor وتحديد نص منفرد.
  • TextView: مستندات تُحدَّد كتلها كوحدة واحدة.