एलिमेंट
कीबोर्ड
env(keyboard-inset-height) सतह (surface) के ऊपर सॉफ़्टवेयर कीबोर्ड की ऊँचाई है, और कीबोर्ड छिपा होने पर यह 0 रहती है। किसी view की नीचे की padding या margin के रूप में यह कंटेंट को कीबोर्ड के ऊपर रखती है: जब कीबोर्ड दिखता या छिपता है, तो view उसकी ऊँचाई के बराबर जगह बना लेता है, और यह बदलाव कीबोर्ड के साथ नेटिव रूप से एनिमेट होता है। इसे scroll view के बाहर के कंटेंट के लिए उपयोग करें, जैसे चैट स्क्रीन के नीचे का मैसेज बार; scroll view या List के अंदर के इनपुट नेटिव रूप से दृश्य में स्क्रॉल हो जाते हैं। इसके साथ Keyboard.dismiss() मिलता है, जो कीबोर्ड को छिपाता है, और कीबोर्ड की ऊँचाई भी, जिसे आप अपने खुद के लेआउट के लिए उपयोग कर सकते हैं।
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(),
),
)
}import { mount, Scroll, signal, Text, View } from "nsh";
import { Input } from "nsh/base";
function App() {
const draft = signal("");
return (
<View style={{ flex: 1, backgroundColor: "#ffffff", paddingBottom: "env(keyboard-inset-height)" }}>
<Scroll style={{ flex: 1 }} keyboardDismissMode="interactive">
<Text style={{ padding: 16, paddingTop: 48 }}>Messages…</Text>
</Scroll>
<View style={{ flexDirection: "row", gap: 8, padding: 8, borderTopWidth: 1, borderTopColor: "#e5e5e5" }}>
<Input
value={draft()}
onChange={({ value }) => draft.set(value)}
placeholder="Message"
style={{ flex: 1, fontSize: 16, padding: 8 }}
/>
</View>
</View>
);
}
mount(App);कीबोर्ड की पूरी ऊँचाई ऐसे view के लिए उपयुक्त है जो स्क्रीन के निचले किनारे तक पहुँचता है, जैसे किसी स्क्रीन का कंटेंट। जब उसके नीचे कुछ हो, जैसे टैब बार, तो उसे किसी length से घटाएँ: calc(env(keyboard-inset-height) + -49px) किसी view को 49 pt के टैब बार के ऊपर रखता है, और calc(env(keyboard-inset-height) + 12px) कीबोर्ड के ऊपर अतिरिक्त स्पेसिंग जोड़ता है। Padding कम से कम 0 होती है।
#Padding या margin
| स्टाइल | जगह कैसे बनती है |
|---|---|
paddingBottom: "env(keyboard-inset-height)" (Tailwind pb-[env(keyboard-inset-height)], Rust .pb_keyboard()) | ऊपर का कंटेंट सिकुड़ जाता है; बैकग्राउंड अब भी स्क्रीन के निचले किनारे तक पहुँचता है। |
marginBottom: "env(keyboard-inset-height)" (Tailwind mb-[env(keyboard-inset-height)], Rust .mb_keyboard()) | पूरा view, अपने बैकग्राउंड सहित, कीबोर्ड के ऊपरी किनारे पर समाप्त होता है। |
कीबोर्ड की ऊँचाई केवल उसी declaration की length में जुड़ती है, इसलिए view अपनी स्पेसिंग को इसके साथ calc() में जोड़ सकता है। Rust में .pb_keyboard_plus(px(..)) और .mb_keyboard_plus(px(..)) एक length जोड़ते हैं।
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))
}<View class="flex-1 gap-3 p-4 mb-[calc(env(keyboard-inset-height)+-49px)]">
<Input placeholder="E-mail" keyboardType="email-address" class="rounded-lg border border-neutral-300 px-3 py-2" />
</View>यहाँ view के नीचे मौजूद 49 pt का टैब बार कीबोर्ड की ऊँचाई के एक हिस्से को पहले से ढक लेता है। जगह बनाना बंद करने के लिए, वेरिएबल को स्टाइल से हटा दें, उदाहरण के लिए किसी signal पर condition लगाकर।
#कीबोर्ड छिपाना
nsh से Keyboard.dismiss() फ़ोकस वाले इनपुट को unfocus करता है, जिससे कीबोर्ड छिप जाता है। यदि कोई इनपुट फ़ोकस में नहीं है, तो यह सब कुछ जैसा है वैसा ही छोड़ देता है। Rust कॉम्पोनेंट 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()))
}import { Text, Keyboard } from "nsh";
import { Button } from "nsh/base";
function Done() {
return (
<Button onPress={() => Keyboard.dismiss()} class="rounded-lg bg-blue-600 px-4 py-3 active:bg-blue-700">
<Text class="text-center font-semibold text-white">Done</Text>
</Button>
);
}#कीबोर्ड की ऊँचाई
| TSX | Rust | लौटाता है |
|---|---|---|
keyboardFrame() | keyboard_height(), keyboard_duration() | NSH सतह के ऊपर सॉफ़्टवेयर कीबोर्ड की ऊँचाई, पॉइंट्स में (छिपे होने पर 0), और उसके दिखने या छिपने के एनीमेशन की अवधि, मिलीसेकंड में। |
दोनों reactive हैं: जो effect, JSX एक्सप्रेशन या स्टाइल क्लोज़र इन्हें पढ़ता है, वह कीबोर्ड के दिखने, छिपने या ऊँचाई बदलने पर दोबारा चलता है।
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())
}import { View, keyboardFrame, safeArea } from "nsh";
import { Input } from "nsh/base";
function MessageBar() {
const insets = safeArea();
return (
<View
style={{
flexDirection: "row",
padding: 8,
// The home indicator's inset only matters while the keyboard is hidden.
paddingBottom: 8 + (keyboardFrame().height > 0 ? 0 : insets().bottom),
}}
>
<Input placeholder="Message" style={{ flex: 1 }} />
</View>
);
}यह ऊँचाई कीबोर्ड का वह हिस्सा है जो NSH सतह के ऊपर आता है। जब विंडो स्वयं कीबोर्ड के लिए सिकुड़ जाती है (जैसे Android की कोई activity जो resize होती है), या जब NSH ऐसे view में एम्बेड हो जो कीबोर्ड के ऊपर ही समाप्त हो जाता है, तो ऊँचाई 0 होती है।
#प्लेटफ़ॉर्म नोट्स
- iOS: जगह कीबोर्ड की अपनी अवधि और ease-in-ease-out कर्व के साथ एनिमेट होती है।
- Android: जहाँ विंडो कीबोर्ड के लिए सिकुड़ती है (resize होने वाली activity), वहाँ कंटेंट पहले से कीबोर्ड के ऊपर समाप्त होता है और वेरिएबल 0 होता है। जहाँ कीबोर्ड view के ऊपर आता है, वहाँ यह iOS की तरह जगह बनाता है।
- डेस्कटॉप और Mac Catalyst में हार्डवेयर कीबोर्ड से टाइप किया जाता है: वेरिएबल 0 होता है और view अपना लेआउट बनाए रखता है।
- Web: वेरिएबल ब्राउज़र का अपना (VirtualKeyboard API का) होता है।
फ़ोकस क्रम, रिटर्न कीज़ और इनपुट accessories के लिए Forms और कीबोर्ड देखें, और अन्य environment वेरिएबल के लिए Safe area देखें।