Éléments
Sélection de texte
Une View avec selectable fait que les textes qu’elle contient se sélectionnent ensemble, comme ceux d’une page web : une même sélection va d’un endroit d’un texte à un endroit d’un autre, à travers des vues, des éléments Text et des TextView, dans l’ordre de lecture. Si vous rendez une conversation de chat sélectionnable, une sélection peut commencer dans un message et se terminer dans le suivant ; Copier place le texte sélectionné dans le presse-papiers, les textes placés côte à côte étant joints par une tabulation et les autres par un saut de ligne. En Rust, on l’active avec view().selectable(true), en TSX avec <View selectable> ; la possibilité de sélectionner un texte isolé relève de son style userSelect.
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")),
)
}import { Text, View } from "nsh";
import { TextView } from "nsh/base";
function ReleaseNotes(props: { notes: string }) {
return (
<View selectable class="gap-3 p-4">
<Text class="font-semibold">Release notes</Text>
<TextView markdown={props.notes} />
<View class="flex-row gap-2">
<Text style={{ userSelect: "none" }} class="text-neutral-500">Updated</Text>
<Text>October 21</Text>
</View>
</View>
);
}La mise en page et l’apparence de la vue relèvent des styles de l’application, et son contenu, de ses enfants. La sélection elle-même est celle de la plateforme : la vue ne dessine rien de propre pour elle.
#Une conversation de chat
Une conversation dans une seule vue sélectionnable se sélectionne comme une seule page : un glissement commence dans une réponse et se termine dans une autre, et Copier récupère le texte de chaque message intermédiaire.
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()),
)),
)
}import { Scroll, View } from "nsh";
import { TextView } from "nsh/base";
<Scroll class="flex-1">
<View selectable class="gap-4 p-4">
<Each of={replies}>{(reply) => <TextView markdown={reply()} />}</Each>
</View>
</Scroll>#Comment une sélection est faite
| Plateforme | Comment une sélection est faite |
|---|---|
| macOS, Windows, Linux, GPUI | Glissez sur les textes ; un double clic sélectionne un mot, un triple clic un paragraphe, et un glissement qui suit se poursuit mot par mot ou paragraphe par paragraphe. Maj+clic étend la sélection. Ctrl / Cmd+A sélectionne tout le contenu de la vue, Ctrl / Cmd+C copie, Échap ou un appui à l’extérieur efface la sélection. |
| iOS, Android | Un appui long sélectionne un mot ; les poignées de la plateforme permettent de l’étendre, et son menu d’édition propose Copier et Tout sélectionner. |
| Web | La sélection propre au navigateur, avec ses gestes, son menu et ses raccourcis. |
#Ce qui est sélectionné
- Textes exclus : un texte avec
userSelect: "none"(.select_none()en Rust, la classeselect-none; un libellé, la légende d’un bouton) est ignoré par la sélection et ses gestes. - Vues imbriquées : une vue sélectionnable à l’intérieur d’une autre fait partie de la vue extérieure, de sorte qu’une seule sélection couvre les deux.
- TextView : une
TextViewavecselectablese sélectionne comme un ensemble à part : ses paragraphes, éléments de liste, cellules et blocs de code se sélectionnent ensemble. À l’intérieur d’une vue sélectionnable, elle rejoint la sélection extérieure. - Textes isolés : un texte avec
userSelect: "text"qui ne se trouve dans aucune vue sélectionnable se sélectionne seul, comme le décrit Text. - L’apparence est la surbrillance de la plateforme, dans le
selectionColorde chaque texte (selection:text-*). - Listes : dans une List virtualisée, une sélection couvre les lignes montées ; les lignes recyclées hors de la zone visible quittent la sélection.