元素
SelectionArea
SelectionArea 让其中的文字一起选择,就像网页上的文字一样:一个选区可以从一段文字中的某处延伸到另一段文字中的某处,跨越视图、Text 元素和 TextView,按阅读顺序排列。把聊天记录包在它里面,选区就能从一条消息开始、在下一条消息结束;复制会把选中的文字放进剪贴板,并排的文字之间用制表符连接,其他文字之间用换行连接。Rust 用 nsh_base::selection_area() 创建它,TSX 用从 nsh/base 导入的 <SelectionArea>。
import { Input, mount, Scroll, signal, Text, View } from "nsh";
import { SelectionArea, TextView } from "nsh/base";
import { platform } from "nsh/device";
import { 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 }}>selection area</Text>
<Text style={{ color: ui().muted, lineHeight: 20 }}>
{platform.idiom === "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>
<SelectionArea 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 selectable={false} style={{ color: ui().faint }}>
Updated (selectable=false)
</Text>
<Text style={{ color: ui().text }}>October 21</Text>
</View>
</SelectionArea>
<Text style={{ fontSize: 12, color: ui().muted }}>Paste what you copied: the label marked selectable=false stays out of it.</Text>
<Input
multiline
value={pasted()}
onChange={(text) => pasted.set(text)}
placeholder="Paste here"
style={{ ...ui().inset, height: 110, borderRadius: 12, padding: 12, fontSize: 15, color: ui().text, placeholderColor: ui().faint }}
/>
<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::{selection_area, text_view};
use nsh_core::ui::prelude::*;
fn release_notes(notes: &'static str) -> impl IntoElement {
selection_area()
.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").selectable(false).text_color(rgb(0x737373)))
.child(text("October 21")),
)
}import { Text, View } from "nsh";
import { SelectionArea, TextView } from "nsh/base";
function ReleaseNotes(props: { notes: string }) {
return (
<SelectionArea class="gap-3 p-4">
<Text class="font-semibold">Release notes</Text>
<TextView markdown={props.notes} />
<View class="flex-row gap-2">
<Text selectable={false} class="text-neutral-500">Updated</Text>
<Text>October 21</Text>
</View>
</SelectionArea>
);
}SelectionArea 来自 nsh/base,在 Rust 中来自 nsh-base crate。它是一个视图:布局和外观是应用的样式,内容是它的子元素。选区本身是平台的:区域不绘制任何自己的东西。
#聊天记录
放在同一个区域里的聊天记录像一个页面一样选择:拖动可以从一条回复开始、在另一条回复结束,复制会取得其间每条消息的文字。
use nsh_base::{selection_area, text_view};
use nsh_core::ui::prelude::*;
fn transcript(replies: Signal<Vec<SharedString>>) -> impl IntoElement {
scroll().flex_1().child(
selection_area().p_4().gap_4().child(each(
replies,
|reply: &SharedString| reply.clone(),
|reply, _index| text_view().markdown(move || reply.get()),
)),
)
}import { Scroll } from "nsh";
import { SelectionArea, TextView } from "nsh/base";
<Scroll class="flex-1">
<SelectionArea class="gap-4 p-4">
<Each of={replies}>{(reply) => <TextView markdown={reply()} />}</Each>
</SelectionArea>
</Scroll>#如何选择
| 平台 | 如何选择 |
|---|---|
| macOS、Windows、Linux、GPUI | 在文字上拖动;双击选中一个词,三击选中一个段落,之后的拖动按词或段落继续。Shift+点击扩展选区。Ctrl / Cmd+A 选中区域内的全部文字,Ctrl / Cmd+C 复制,Esc 或在区域外按下会清除选区。 |
| iOS、Android | 长按选中一个词;用平台的选择手柄扩展选区,平台的编辑菜单提供“拷贝”和“全选”。 |
| Web | 浏览器自己的选择,使用浏览器的手势、菜单和快捷键。 |
#选择的范围
- 不参与选择的文字:设置了
selectable={false}(Rust 中是.selectable(false))的文字(标签、按钮上的文字)会被选区及其手势跳过。 - 嵌套的区域:一个区域在另一个区域里面时属于外层区域,一个选区覆盖两者。
- TextView:设置了
selectable的TextView本身就是一个选择区域:它的段落、列表项、单元格和代码块一起选择。放在SelectionArea里时,它加入外层的选区。 - 区域之外:不在任何区域里的
selectable文字单独选择,见 Text。 - 外观是平台的高亮,使用每段文字的
selectionColor(selection:text-*)。 - 列表:在虚拟化的 List 中,选区覆盖已挂载的行;滚出视野被回收的行会离开选区。
#属性
| 属性 | Rust | 类型 | 说明 |
|---|---|---|---|
children | .child(..) | JSX.Element | 内容:任意视图,文字可以在任意层级。 |
class、style | 样式方法 | string、StyleProp<ViewStyle> | 它的布局和外观,与 View 相同。 |