このページは機械翻訳のため、誤りを含む場合があります。 英語の原文を読む

要素

Input

Input はネイティブのテキストフィールドです。iOS と Mac Catalyst では UITextField(単一行)または UITextView(複数行)、Android では EditText が使われます。入力、キャレット、選択、自動修正、オートフィルはプラットフォーム本来の動作です。Rust では nsh_base::input()、TSX では nsh/base の <Input> で作成します。

el-input.tsx
el-input.tsxLive
import { type InputRef, type JSX, mount, signal, Text, View } from "nsh";
import { Input } from "nsh/base";
import { button, buttonText, field, Inspector, Panel, Stage, ui, wide } from "./palette";

function App() {
  const name = signal("");
  const password = signal("");
  const notes = signal("");
  const submitted = signal("");
  let passwordInput!: InputRef;
  const signIn = () => submitted.set(`Signed in as ${name() || "nobody"}`);
  const nameField = () => (
    <Input
      {...field()}
      value={name()}
      onChange={({ value }) => name.set(value)}
      placeholder="Ada Lovelace"
      autoCapitalize="words"
      maxLength={20}
      returnKeyType="next"
      onSubmit={() => passwordInput.focus()}
    />
  );
  const count = () => <Text style={{ fontSize: 12, color: ui().muted }}>{name().length} / 20</Text>;
  const passwordField = () => (
    <Input
      ref={passwordInput}
      {...field()}
      value={password()}
      onChange={({ value }) => password.set(value)}
      masked
      autoComplete="current-password"
      returnKeyType="done"
      onSubmit={signIn}
    />
  );
  const notesField = () => (
    <Input
      {...field({ style: { height: wide() ? 80 : 100 } })}
      multiline
      value={notes()}
      onChange={({ value }) => notes.set(value)}
      placeholder="Anything else?"
    />
  );
  const signInButton = () => (
    <View onPress={signIn} disabled={password().length === 0} {...button("primary", { style: { marginTop: 4 } })}>
      <Text style={buttonText("primary")}>Sign in</Text>
    </View>
  );
  /** A label above its field, and what describes it below. */
  const group = (label: string, input: JSX.Element, after?: JSX.Element) => (
    <View style={{ gap: 6 }}>
      <Text style={{ fontSize: 13, color: ui().muted }}>{label}</Text>
      {input}
      {after}
    </View>
  );

  return (
    <View style={{ flex: 1, backgroundColor: ui().page }}>
      {() =>
        wide() ? (
          <View style={{ flex: 1, padding: 20, paddingTop: 24, gap: 14 }}>
            <Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>input</Text>
            <Text style={{ color: ui().muted }}>Tab moves between the fields; Return in the name goes to the password</Text>
            <View style={{ flex: 1, flexDirection: "row", gap: 16 }}>
              <Stage width={340} gap={16}>
                {group("Name (controlled, maxLength 20)", nameField(), count())}
                {group("Password", passwordField())}
                {group("Notes (multiline)", notesField())}
                {signInButton()}
              </Stage>
              <Inspector>
                <Panel title="value">
                  <Text style={{ fontSize: 13, color: ui().text }}>name {name() ? `“${name()}”` : "—"}</Text>
                  <Text style={{ fontSize: 13, color: ui().text, fontVariant: ["tabular-nums"] }}>password {password().length} characters</Text>
                  <Text style={{ fontSize: 13, color: ui().text, fontVariant: ["tabular-nums"] }}>notes {notes().length} characters</Text>
                </Panel>
                <Panel title="onSubmit" grow>
                  <Text style={{ fontSize: 13, color: ui().up }}>{submitted()}</Text>
                </Panel>
              </Inspector>
            </View>
          </View>
        ) : (
          <View style={{ flex: 1, width: "100%", maxWidth: 480, alignSelf: "center", padding: 20, paddingTop: 24, gap: 10 }}>
            <Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>input</Text>

            <Text style={{ color: ui().muted }}>Name (controlled, maxLength 20)</Text>
            {nameField()}
            {count()}

            <Text style={{ color: ui().muted }}>Password</Text>
            {passwordField()}

            <Text style={{ color: ui().muted }}>Notes (multiline)</Text>
            {notesField()}

            {signInButton()}
            <Text style={{ color: ui().up }}>{submitted()}</Text>
          </View>
        )
      }
    </View>
  );
}

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

fn name_field() -> impl IntoElement {
    let name = signal(String::new());
    input()
        .value(name)
        .on_change(move |text: &str| name.set(text.to_string()))
        .placeholder("Your name")
        .auto_capitalize(AutoCapitalize::Words)
        .return_key_type(ReturnKeyType::Done)
        .text_base()
        .p_3()
        .border_1()
        .border_color(rgb(0xd0d7de))
        .rounded(px(8.0))
}

#制御コンポーネントと非制御コンポーネント

  • 制御: value を渡し、onChange で更新します。アプリはユーザーが入力した内容を書き換えられます(トリムや整形など)。テキストには編集が即座に反映され、新しい value が届いた時点で修正されます。ネイティブでの各編集にはカウントが付き、アプリからの value は最新の編集に基づいている場合にのみ適用されるため、遅い更新よりも新しい入力が常に優先されます。
  • 非制御: defaultValue(.default_value(..)、または何も指定しない)を渡し、テキストは onChange または onSubmit から読み取ります。

ユーザーの入力中に常に守られるべきルールは、props としてネイティブ側で処理されます。maxLength は長さを制限し、submitBehavior は return キーの動作を決めます。フォームとキーボードを参照してください。

#Props

共通 propsに加えて(style は TextStyle 型です。フォント、色、配置、ビューのスタイルが適用されます):

#テキスト

PropRust型デフォルト説明
value.value(..)stringアプリが制御するテキスト。
defaultValue.default_value(..)string初期テキスト。
placeholder.placeholder(..)string入力が空の間に表示されます。
multiline.multiline(..)booleanfalse複数行。submitBehavior で別の指定をしない限り、return は改行を挿入します。
scrollbar.scrollbar(..)boolean | ScrollbarThemetrue複数行入力のスクロールバー。プラットフォームのインジケーター、またはアプリが設定した場合はアプリ組み込みのバー(Scrollbar)。オブジェクトを渡すと独自のテーマになり、false は表示なしです。
maxLength.max_length(..)numberユーザーが入力できる最大文字数。
readonly.readonly(..)booleanfalseユーザーはテキストを選択してコピーできますが、変更はできません。
masked.masked(..)booleanfalseテキストを隠します(パスワード用)。
allowFontScalingbooleantrueシステムのテキストサイズに従います。
maxFontSizeMultipliernumber0システムスケールの上限(0 は上限なし)。

#キーボード

PropRust型デフォルト説明
keyboardType.keyboard_type(KeyboardType::Email)"default" | "number-pad" | "decimal-pad" | "numeric" | "numbers-and-punctuation" | "email-address" | "phone-pad" | "url""default"キーボードレイアウト(値の一覧)。numbers-and-punctuation には iOS ではマイナス記号があります。
returnKeyType.return_key_type(ReturnKeyType::Next)"default" | "done" | "go" | "next" | "search" | "send""default"return キーのラベル。
autoCapitalize.auto_capitalize(AutoCapitalize::None)"none" | "sentences" | "words" | "characters""sentences"自動大文字化。
autoCorrect.auto_correct(..)booleantrueシステムの修正機能。
autoComplete.auto_fill(AutoFill::OneTimeCode)AutoComplete | TextContentType入力内容の種類。iOS、Android、Web でのオートフィルに使われます。"username"、"current-password"、"new-password"、"one-time-code"、"email"、"tel"、"postal-code"、"cc-number"、"off"、…
pasteBehavior.paste_behavior(PasteBehavior::Report)"insert" | "report""insert"ペーストの動作。"insert" はペーストされたテキストを挿入し、"report" は何も挿入せず、onPaste が報告する内容をアプリが処理できるようにします。画像とファイルは挿入されません。
submitBehavior.submit_behavior(SubmitBehavior::Submit)"submit" | "blurAndSubmit" | "newline""blurAndSubmit"(単一行)、"newline"(複数行)return キーの動作。"submit" は onSubmit を報告してフォーカスを維持し、"blurAndSubmit" はさらにフォーカスも外し、"newline" は改行を挿入します(複数行のみ。単一行では送信されます)。送信する複数行入力でも、ハードウェアキーボードでは Shift+Return または Option / Alt+Return で改行できます。"submit" はキーボードを表示したままにします(例: 複数のメッセージを続けて送信する場合)。
autoFocus.auto_focus(..)booleanfalse表示時にフォーカスされます。

#キャレットと選択

PropRust型説明
caret.caret(..)booleanキャレットを描画します(デフォルトは true。false で非表示)。
selectedRange.selected_range((start, end)){ start: number; end?: number }制御された選択範囲(UTF-16 単位)。end のデフォルトは start(キャレット)です。

キャレットは、ウィンドウが表示されアクティブな間は点滅します。Linux の GPUI ウィンドウでは、GTK アプリと同様に、入力や移動がないまま 10 秒が経過すると表示されたままになるため、フォーカスされたまま放置されたフィールドがフレームを消費することはありません。

#色

プレースホルダー、キャレット、選択ハイライトの色はスタイルキーで、対応する Tailwind クラスと Rust のメソッドがあります(UIKit ではキャレットと選択を 1 つの色で着色し、設定されている場合はキャレットの色が使われます):

スタイルキーRustクラス説明
placeholderColor.placeholder_color(..)placeholder:text-*プレースホルダーの色。
caretColor.caret_color(..)caret-*キャレットの色。
selectionColor.selection_color(..)selection:text-*選択ハイライトの色。
input()
    .placeholder("Search")
    .text_color(rgb(0x18181b))
    .placeholder_color(rgb(0x9ca3af))
    .caret_color(rgb(0x2563eb))
    .selection_color(rgb(0x93c5fd))

#iOS

ios キーRust型説明
textContentType.auto_fill(..)AutoComplete | TextContentTypeautoComplete の iOS での名称("oneTimeCode"、"newPassword"、…)。
inputAccessoryViewID.input_accessory(id)string入力にフォーカスがある間、キーボード上に表示される InputAccessoryView の nativeID(InputAccessoryView)。
keyboardAppearance.keyboard_appearance(KeyboardAppearance::Dark)"default" | "light" | "dark"キーボードの外観。"default" はシステムの外観に従います。Android のキーボードはシステムのテーマに従います。
use nsh_core::element::AutoFill;

input().auto_fill(AutoFill::OneTimeCode).keyboard_type(KeyboardType::Number)

#イベント

イベントRustペイロード発生タイミング
onChange.on_change(|text: &str| ..){ value }ユーザーがテキストを変更したとき。value は現在のテキストです。
onSubmit.on_submit(|text: &str| ..){ text }送信を行う入力(単一行、または submitBehavior が "submit" / "blurAndSubmit")で return キーが押されたとき。
onFocus / onBlur.on_focus / .on_blurなし入力がフォーカスを得た / 失ったとき。
onSelectionChange.on_selection_change(|s: &Selection| ..){ start, end }キャレットまたは選択が移動したとき(UTF-16 単位)。
onPaste.on_paste(|e: &PasteEvent| ..){ text, html, files, uris }ユーザーがペーストしたとき。テキスト、その書式(HTML。Linux、Web)、画像(スクリーンショットなど)は File、ローカルファイルは file: URL として渡されます。挿入されるテキストの onChange より前に発生します(フォームとキーボード)。
onCaretMove.on_caret_move(|e: &CaretEvent| ..){ bounds, pageBounds }選択に伴って、またはテキストのスクロールや折り返しによってキャレットが移動したとき。bounds は入力内での位置、pageBounds はウィンドウ内での位置、Overlay の anchor です(フォームとキーボード)。
onKeyPress.on_key_press(|key: &str| ..){ key }テキストが変更される前。key は入力されたテキストで、"Backspace" または "Enter" です。
onContentSizeChange.on_content_size_change(|size: &Size<Points>| ..){ size }入力に伴ってテキストのサイズが変化したとき(複数行の場合は入力の幅に基づく)。空の入力は、プレースホルダーに関係なく空の 1 行として報告されます。
onCompositionChange.on_composition_change(|e: &Composition| ..){ composing, text }入力メソッドの変換が開始、変更、または終了したとき。text は変換中の内容(文字を選択する前のピンイン、変換前のかななど)で、終了時は "" です。確定したテキストは、終了の前に onChange に届きます。

テキストに合わせて伸びる複数行入力:

let height = signal(px(40.0));

input()
    .multiline(true)
    .on_content_size_change(move |size: &Size<Points>| {
        height.set(px((size.height.0 + 16.0).clamp(40.0, 120.0)));
    })
    .h(move || height.get())
    .px_3()
    .py_2()

#Ref メソッド

メソッドRust説明
focus()focus()入力にフォーカスを移し、キーボードを表示します。
blur()blur()フォーカスを外します。
setSelection(start, end?)set_selection(start, end)start..end を選択します(UTF-16 単位)。両者が等しい場合はキャレットになります。

return で次のフィールドにフォーカスを移す例:

use nsh_core::element::AutoFill;

let password = element_ref();

view()
    .child(input().return_key_type(ReturnKeyType::Next).on_submit(move |_: &str| password.focus()))
    .child(input().element_ref(&password).masked(true).auto_fill(AutoFill::Password))

nsh の Keyboard.dismiss()(Rust では dismiss_keyboard())は、どの入力がキーボードを使用していても、キーボードを閉じます。

#プラットフォームに関する注記

  • macOS では、単一行のテキスト、プレースホルダー、キャレットはネイティブフィールドのフォントメトリクスを共有し、コンテンツボックス内でまとめて中央に配置されます。lineHeight はレイアウトに影響し、複数行エディターでは段落間隔にも使われます。
  • スクロールビュー内の入力は、ネイティブ側でキーボードに隠れないよう調整されます。それ以外の場所では、env(keyboard-inset-height)を下側のパディングまたはマージンとして使用してください。
  • disabled は、無効時のスタイルを適用するだけでなく、入力を無効化します(フォーカス不可)。readonly の入力はフォーカスと選択を維持します。
  • inputAccessoryViewID は、キーボードにアクセサリーバーがある iOS で適用されます。