Esta página se tradujo automáticamente y puede contener errores. Leer el original en inglés

Elementos

Teclado

env(keyboard-inset-height) es la altura del teclado de software sobre la superficie, y vale 0 mientras está oculto. Usado como padding o margin inferior de una vista, mantiene el contenido por encima del teclado: cuando el teclado aparece o se oculta, la vista hace espacio según su altura, y el cambio se anima de forma nativa junto con el teclado. Úsalo para contenido fuera de los scroll views, como una barra de mensajes en la parte inferior de una pantalla de chat; los inputs dentro de un scroll view o de un List se desplazan de forma nativa hasta quedar a la vista. Junto con él vienen Keyboard.dismiss(), que oculta el teclado, y la altura del teclado, para tus propios layouts.

el-keyboard.tsx
el-keyboard.tsxLive
import { mount, Scroll, signal, Text, View } from "nsh";
import { Input } from "nsh/base";
import { Button, Switch } from "nsh/base";
import { Keyboard, keyboardFrame } from "nsh";
import { button, buttonText, desktop, field, tint, ui } from "./palette";

function App() {
  const draft = signal("");
  const messages = signal(["Type below: the bar stays above the keyboard."]);
  const behavior = signal<"padding" | "height">("padding");
  const enabled = signal(true);
  const send = () => {
    if (!draft().trim()) return;
    messages.update((list) => [...list, draft().trim()]);
    draft.set("");
  };
  const chip = (on: () => boolean) => button("secondary", { on, size: "sm" });
  const chipText = (on: () => boolean) => buttonText("secondary", { on });
  const isPadding = () => behavior() === "padding";
  const isHeight = () => behavior() === "height";
  return (
    <View
      style={{
        flex: 1,
        backgroundColor: behavior() === "height" ? tint(ui().info, 0.12) : ui().page,
        // Bottom padding (or margin) by the keyboard's height, while enabled.
        ...(!enabled() ? {} : behavior() === "padding" ? { paddingBottom: "env(keyboard-inset-height)" } : { marginBottom: "env(keyboard-inset-height)" }),
      }}
    >
      <View style={{ padding: 20, paddingTop: 24, paddingBottom: 8, gap: 8 }}>
        <Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>keyboard inset</Text>
        <Text style={{ color: ui().muted }}>
          {desktop
            ? "Desktops type with a hardware keyboard: its height stays 0 and the view keeps its layout. Open this on a phone to see it move."
            : "Tap the message field: the bar moves up with the keyboard."}
        </Text>
        <View style={{ flexDirection: "row", flexWrap: "wrap", gap: 6 }}>
          <Button onPress={() => behavior.set("padding")} selected={isPadding()} {...chip(isPadding)}>
            <Text style={chipText(isPadding)}>padding</Text>
          </Button>
          <Button onPress={() => behavior.set("height")} selected={isHeight()} {...chip(isHeight)}>
            <Text style={chipText(isHeight)}>height</Text>
          </Button>
          <Switch
            checked={enabled()}
            onChange={({ checked }) => enabled.set(checked)}
            class="flex-row items-center gap-2 px-1 active:opacity-70"
            trackClass="w-9 rounded-full p-0.5 transition-colors motion-reduce:transition-none"
            trackStyle={enabled() ? ui().accentFill : ui().inset}
            thumbClass="size-4 rounded-full transition-transform motion-reduce:transition-none checked:translate-x-4"
            thumbStyle={enabled() ? { backgroundColor: ui().onAccent } : ui().raised}
          >
            <Text style={{ color: ui().text, fontSize: 13, fontWeight: "600" }}>enabled {String(enabled())}</Text>
          </Switch>
        </View>
        <Text style={{ color: ui().accent, fontSize: 13 }}>
          keyboardFrame(): height {Math.round(keyboardFrame().height)} · duration {Math.round(keyboardFrame().duration)} ms
        </Text>
      </View>

      <Scroll style={{ flex: 1 }} keyboardDismissMode="interactive">
        <View style={{ padding: 20, paddingTop: 4, gap: 8 }}>
          <Each of={messages()}>
            {(text) => (
              <View style={{ ...ui().row, alignSelf: "flex-start", maxWidth: 320, borderRadius: 14, paddingHorizontal: 12, paddingVertical: 8 }}>
                <Text style={{ color: ui().text }}>{text()}</Text>
              </View>
            )}
          </Each>
        </View>
      </Scroll>

      <View style={{ flexDirection: "row", gap: 8, padding: 12, paddingHorizontal: desktop ? 20 : 12, borderTopWidth: 1, borderTopColor: ui().border, backgroundColor: ui().page }}>
        <Input
          value={draft()}
          onChange={({ value }) => draft.set(value)}
          onSubmit={send}
          testID="message"
          returnKeyType="send"
          placeholder="Message"
          {...field({ style: { flex: 1 } })}
        />
        <Button onPress={send} {...button("primary")}>
          <Text style={buttonText("primary")}>Send</Text>
        </Button>
        <Button onPress={() => Keyboard.dismiss()} {...button()}>
          <Text style={buttonText()}>Done</Text>
        </Button>
      </View>
    </View>
  );
}

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

fn chat() -> impl IntoElement {
    let draft = signal(String::new());
    view()
        .flex_1()
        .bg(white())
        .pb_keyboard()
        .child(scroll().flex_1().child(text("Messages…").p_4()))
        .child(
            view()
                .flex_row()
                .gap_2()
                .p_2()
                .border_t_1()
                .border_color(rgb(0xe5e5e5))
                .child(
                    input()
                        .value(draft)
                        .on_change(move |v: &str| draft.set(v.to_owned()))
                        .placeholder("Message")
                        .flex_1()
                        .p_2(),
                ),
        )
}

La altura completa del teclado es adecuada para una vista que llega hasta el borde inferior de la pantalla, como el contenido de una pantalla. Cuando hay algo por debajo, como una tab bar, réstalo con una longitud: calc(env(keyboard-inset-height) + -49px) mantiene una vista por encima de una tab bar de 49 pt, y calc(env(keyboard-inset-height) + 12px) añade espacio sobre el teclado. Un padding es como mínimo 0.

#Padding o margin

Estilo Cómo hace espacio
paddingBottom: "env(keyboard-inset-height)" (Tailwind pb-[env(keyboard-inset-height)], Rust .pb_keyboard()) El contenido de arriba se reduce; el fondo sigue llegando hasta el borde inferior de la pantalla.
marginBottom: "env(keyboard-inset-height)" (Tailwind mb-[env(keyboard-inset-height)], Rust .mb_keyboard()) La vista completa, incluido su fondo, termina en la parte superior del teclado.

La altura del teclado se suma a la longitud solo dentro de la misma declaración, por lo que una vista la combina con su propio espaciado en calc(). En Rust, .pb_keyboard_plus(px(..)) y .mb_keyboard_plus(px(..)) añaden una longitud.

use nsh_core::prelude::*;

fn sign_in() -> impl IntoElement {
    view()
        .mb_keyboard_plus(px(-49.0))
        .flex_1()
        .p_4()
        .gap_3()
        .child(input().placeholder("E-mail").keyboard_type(KeyboardType::Email))
}

Aquí, una tab bar de 49 pt debajo de la vista ya cubre parte de la altura del teclado. Para desactivar el espacio, deja la variable fuera del estilo, por ejemplo con una condición sobre un signal.

#Ocultar el teclado

Keyboard.dismiss(), de nsh, quita el foco del input enfocado, lo que oculta el teclado. Si no hay ningún input enfocado, lo deja todo como está. Los componentes de Rust llaman a dismiss_keyboard().

use nsh_base::button;
use nsh_core::prelude::*;

fn done() -> impl IntoElement {
    button()
        .on_press(|| dismiss_keyboard())
        .rounded_lg()
        .bg(rgb(0x2563eb))
        .px_4()
        .py_3()
        .child(text("Done").text_color(white()))
}

#La altura del teclado

TSX Rust Devuelve
keyboardFrame() keyboard_height(), keyboard_duration() La altura del teclado de software sobre la superficie de NSH, en puntos (0 mientras está oculto), y la duración de su animación de aparición u ocultación, en milisegundos.

Ambos son reactivos: un effect, una expresión JSX o un closure de estilo que los lea se vuelve a ejecutar cuando el teclado aparece, se oculta o cambia de altura.

use nsh_core::prelude::*;

fn message_bar() -> impl IntoElement {
    view()
        .flex_row()
        .p_2()
        .pb(move || {
            // The home indicator's inset only matters while the keyboard is hidden.
            let home = if keyboard_height() > Points::ZERO {
                Points::ZERO
            } else {
                safe_area().bottom
            };
            px(8.0) + home
        })
        .child(input().placeholder("Message").flex_1())
}

La altura es la parte del teclado que se superpone a la superficie de NSH. Cuando la propia ventana se reduce por el teclado (una activity de Android que se redimensiona), o cuando NSH está incrustado en una vista que termina por encima del teclado, la altura es 0.

#Notas por plataforma

  • iOS: el espacio se anima con la duración propia del teclado y una curva ease-in-ease-out.
  • Android: cuando la ventana se reduce por el teclado (una activity que se redimensiona), el contenido ya termina por encima del teclado y la variable vale 0. Cuando el teclado se superpone a la vista, hace espacio igual que en iOS.
  • Escritorio y Mac Catalyst: se escribe con un teclado físico; la variable vale 0 y la vista mantiene su layout.
  • Web: la variable es la propia del navegador (la de la VirtualKeyboard API).

Consulta Forms and keyboard para el orden de foco, las teclas de retorno y los accesorios de input, y Safe area para las demás variables de entorno.