एलिमेंट
WebView
वेब व्यू ऐप के अंदर कोई वेब पेज या आपके द्वारा दिया गया HTML दिखाता है: iOS और Mac Catalyst पर WKWebView, और Android पर android.webkit.WebView। क्योंकि NSH नेटिव व्यू के साथ रेंडर करता है, इसलिए वेब व्यू लेआउट में किसी भी अन्य व्यू की तरह अपने आसपास के व्यू के क्रम में रहता है। Rust में इसे web_view(url) या web_view_html(html, base_url) से बनाया जाता है, और TSX में nsh से <WebView> द्वारा।
import { mount, signal, Text, View, WebView, type WebViewNavigation, type WebViewRef } from "nsh";
import { platform, windowSize } from "nsh";
import { button, buttonText, ui } from "./palette";
// The page: a button that posts to the app, and a line that shows what the
// app posts back.
const page = (c: ReturnType<typeof ui>) => `<!doctype html>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Inline page</title>
<style>
body { font: 15px system-ui, sans-serif; margin: 0; padding: 16px; background: ${c.page}; color: ${c.text}; }
button { font: inherit; font-weight: 600; padding: 10px 14px; border-radius: 10px; border: 1px solid ${c.border}; background: ${c.accent}; color: ${c.page}; }
.muted { color: ${c.muted}; }
#got { margin-top: 12px; }
#box { margin-top: 12px; height: 48px; border-radius: 10px; background: ${c.border}; transition: background 0.3s; }
</style>
<p class="muted">This is HTML in a web view.</p>
<button id="post">postMessage to the app</button>
<p id="got" class="muted">Nothing from the app yet.</p>
<div id="box"></div>
<script>
let taps = 0;
document.getElementById("post").onclick = () =>
window.nsh.postMessage("tap " + ++taps + " from the page");
window.addEventListener("message", (e) => { document.getElementById("got").textContent = "App says: " + e.data; });
</script>`;
function Button(props: { label: string; onPress: () => void }) {
return (
<View
onPress={props.onPress}
{...button()}
>
<Text style={buttonText()}>{props.label}</Text>
</View>
);
}
const colors = () => [ui().accent, ui().info, ui().violet, ui().down, ui().warn];
function App() {
const messages = signal<string[]>([]);
const nav = signal<WebViewNavigation | null>(null);
const sent = signal(0);
let painted = 0;
let web!: WebViewRef;
const wide = () => windowSize().width >= 700;
return (
<View style={{ flex: 1, backgroundColor: ui().page, padding: 20, paddingTop: 24, gap: 14 }}>
<Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>web-view</Text>
<View style={{ flex: 1, flexDirection: wide() ? "row" : "column", gap: 16 }}>
<WebView
ref={web}
html={page(ui())}
// Runs after each load: the page reports its title.
injectedJavaScript={`window.nsh.postMessage("loaded: " + document.title)`}
onLoad={(e) => nav.set(e)}
onMessage={(e) => messages.set([e.data, ...messages()].slice(0, 4))}
style={{ flex: 1, minHeight: 240, borderRadius: 14, overflow: "hidden", borderWidth: 1, borderColor: ui().border }}
/>
<View style={{ gap: 12, width: wide() ? 320 : undefined }}>
<Text style={{ color: ui().muted }}>
{platform.desktop ? "Click" : "Tap"} the button in the page to post to the app; these buttons call the ref's methods.
</Text>
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
<Button
label="postMessage"
onPress={() => {
sent.update((n) => n + 1);
web.postMessage(`hello #${sent()}`);
}}
/>
<Button
label="injectJavaScript"
onPress={() => {
const color = colors()[painted++ % colors().length];
web.injectJavaScript(`document.getElementById("box").style.background = "${color}"`);
}}
/>
<Button label="reload" onPress={() => web.reload()} />
</View>
<View style={{ ...ui().card, borderRadius: 12, padding: 12, gap: 4 }}>
<Text style={{ color: ui().muted, fontSize: 12 }}>onLoad</Text>
<Text style={{ numberOfLines: 1, color: ui().text, fontFamily: "monospace", fontSize: 13 }}>
{nav() ? `title "${nav()!.title}"` : "…"}
</Text>
<Text style={{ color: ui().muted, fontSize: 12, marginTop: 6 }}>onMessage, newest first</Text>
<Each of={messages()}>
{(m) => <Text style={{ color: ui().text, fontFamily: "monospace", fontSize: 13 }}>{m()}</Text>}
</Each>
</View>
</View>
</View>
</View>
);
}
mount(App);use nsh_core::prelude::*;
fn browser() -> impl IntoElement {
let title = signal(SharedString::from("Loading…"));
view()
.flex_1()
.child(text(title).number_of_lines(1))
.child(
web_view("https://example.com")
.flex_1()
.on_navigation_state_change(move |e: &WebNavigation| title.set(e.title.clone().into())),
)
}import { signal, Text, View, WebView, type WebViewNavigation } from "nsh";
function Browser() {
const nav = signal<WebViewNavigation | null>(null);
return (
<View style={{ flex: 1 }}>
<Text style={{ numberOfLines: 1 }}>{nav()?.title ?? "Loading…"}</Text>
<WebView
src="https://example.com"
style={{ flex: 1 }}
onNavigationStateChange={(e) => nav.set(e)}
/>
</View>
);
}TSX में वेब व्यू src और html में से एक लेता है (देखें WebView API)। वेब व्यू और वीडियो गाइड में मैसेजिंग पैटर्न और कैपेबिलिटी की जानकारी है।
#Props
वेब व्यू एक व्यू के props लेता है (इसमें कोई चाइल्ड नहीं होता) और साथ में ये भी:
| Prop | Rust | Type | Default | विवरण |
|---|---|---|---|---|
src | web_view(url) | string | दिखाए जाने वाले पेज का URL। | |
html | web_view_html(html, base_url), .html(html, base_url) | string | इनलाइन HTML, जो src की जगह दिखाया जाता है। | |
baseUrl | web_view_html / .html का base_url | string | वह आधार जिसके विरुद्ध html के रिलेटिव लिंक हल किए जाते हैं। | |
injectedJavaScript | .injected_java_script(..) | string | हर पेज लोड होने के बाद चलता है। | |
injectedJavaScriptBeforeContentLoaded | .injected_java_script_before_content(..) | string | हर पेज की अपनी स्क्रिप्ट से पहले चलता है। | |
userAgent | .user_agent(..) | string | ब्राउज़र की user agent स्ट्रिंग। | |
javaScriptEnabled | .java_script_enabled(..) | boolean | true | पेज स्क्रिप्ट चलाते हैं। |
scrollable | .scrollable(..) | boolean | true | उपयोगकर्ता पेज को स्क्रॉल कर सकता है। |
ios key | Rust | Type | विवरण |
|---|---|---|---|
allowsBackForwardNavigationGestures | .back_forward_gestures(..) | boolean | पीछे और आगे जाने के लिए किनारों से स्वाइप करें। |
desktop key | Rust | Type | विवरण |
|---|---|---|---|
allowsBackForwardNavigationGestures | .back_forward_gestures(..) | boolean | macOS: पीछे और आगे जाने के लिए ट्रैकपैड पर दो उंगलियों से स्वाइप करें; Windows: टचस्क्रीन पर स्वाइप करें; Linux: टचपैड पर स्वाइप करें। |
#Events
| Event | Rust | Payload | कब |
|---|---|---|---|
onLoadStart | .on_load_start | WebViewNavigation | कोई पेज लोड होना शुरू हुआ। |
onLoad | .on_load | WebViewNavigation | कोई पेज लोड होना पूरा हुआ। |
onError | .on_error | { message, code? } (&str, संदेश) | लोडिंग विफल रही; किसी पेज के लिए code का मान "ERR_ACCESS_DENIED" होता है, या html जिसकी अनुमति ऐप की कैपेबिलिटी नहीं देतीं। |
onNavigationStateChange | .on_navigation_state_change | WebViewNavigation | URL, शीर्षक, लोडिंग की स्थिति या हिस्ट्री बदली। |
onMessage | .on_message | { data: string } (&str) | पेज ने window.nsh.postMessage(data) को कॉल किया। |
onDownload | WebViewDownload | कोई डाउनलोड शुरू हुआ, पूरा हुआ या विफल रहा (macOS)। |
WebViewNavigation का मान { url, title, loading, canGoBack, canGoForward } है; Rust में WebNavigation के साथ url, title, loading, can_go_back और can_go_forward होते हैं।
WebViewDownload का मान { state, id, url, filename, path, bytes?, message? } है: state का मान "started", "finished" (फ़ाइल के bytes के साथ) या "failed" (इसके message के साथ) होता है। onDownload लिसनर होने पर, पेज जो कुछ डाउनलोड करता है (अटैचमेंट, ऐसा टाइप जिसे वेब व्यू नहीं दिखाता, या download लिंक) वह ऐप की डेटा डायरेक्टरी में downloads/<id>/<filename> के रूप में सहेजा जाता है, जहाँ path इंगित करता है, और ऐप उसे fs/promises से पढ़ता है।
macOS पर, लिसनर जोड़ने से webview कैपेबिलिटी द्वारा अनुमत कोई भी लोड किया गया पेज डाउनलोड अपने-आप सहेज सकता है। स्टोरेज बजट की ज़िम्मेदारी ऐप की है, और पूरी हो चुकी फ़ाइलों का उपयोग समाप्त होने पर उन्हें downloads/ से हटाने की भी। वेब व्यू हटाने पर चल रहे डाउनलोड रद्द हो जाते हैं और उनकी अधूरी डायरेक्टरी हटा दी जाती हैं। डाउनलोड onDownload के माध्यम से रिपोर्ट होते हैं; किसी नेविगेशन को डाउनलोड में बदलने पर onError केवल वास्तविक नेविगेशन विफलताओं के लिए रखा जाता है। फ़ाइल नामों से कंट्रोल कैरेक्टर और द्विदिशीय फ़ॉर्मैटिंग कंट्रोल हटा दिए जाते हैं, और लंबे नाम 200 बाइट तक छोटे कर दिए जाते हैं, साथ ही जो एक्सटेंशन समा सके उसे सुरक्षित रखा जाता है।
#Ref methods
| Method | Rust | विवरण |
|---|---|---|
reload() | reload() | पेज को रीलोड करता है। |
goBack() / goForward() | go_back() / go_forward() | पेज की हिस्ट्री में आगे-पीछे जाता है। |
stopLoading() | stop_loading() | लोडिंग रोकता है। |
injectJavaScript(script) | inject_java_script(script) | पेज में JavaScript चलाता है। |
postMessage(data) | post_message(data) | पेज को एक स्ट्रिंग भेजता है, जिसे window पर message इवेंट के रूप में प्राप्त होती है। |
#Messaging
ऐप और पेज स्ट्रिंग का आदान-प्रदान करते हैं। पेज window.nsh.postMessage(data) से पोस्ट करता है; ऐप ref के postMessage(data) (Rust में post_message(..)) से पोस्ट करता है। संरचित डेटा के लिए JSON भेजें।
use nsh_core::prelude::*;
const PAGE: &str = r#"<!doctype html>
<meta name="viewport" content="width=device-width">
<button onclick="window.nsh.postMessage('hello from the page')">Post to the app</button>
<p id="got"></p>
<script>
window.addEventListener("message", (e) => { document.getElementById("got").textContent = e.data; });
</script>"#;
fn bridge() -> impl IntoElement {
let message = signal(String::new());
let web = element_ref();
view()
.gap_3()
.child(
web_view_html(PAGE.to_string(), None)
.element_ref(&web)
.h(px(200.0))
.on_message(move |data: &str| message.set(data.to_string())),
)
.child(
text(move || format!("Post to the page · got: {}", message.get()))
.on_press(move || web.post_message("hi from the app")),
)
}import { signal, Text, View, WebView, type WebViewRef } from "nsh";
const page = `<!doctype html>
<meta name="viewport" content="width=device-width">
<button onclick="window.nsh.postMessage('hello from the page')">Post to the app</button>
<p id="got"></p>
<script>
window.addEventListener("message", (e) => { document.getElementById("got").textContent = e.data; });
</script>`;
function Bridge() {
const message = signal("");
let web!: WebViewRef;
return (
<View style={{ gap: 12 }}>
<WebView ref={web} html={page} style={{ height: 200 }} onMessage={(e) => message.set(e.data)} />
<Text onPress={() => web.postMessage("hi from the app")}>Post to the page · got: {message()}</Text>
</View>
);
}#Capabilities
जब ऐप nsh.json में कैपेबिलिटी घोषित करता है, तो वेब व्यू webview के अंतर्गत सूचीबद्ध होस्ट ही दिखाता है और उन्हीं पर नेविगेट करता है। किसी अन्य होस्ट पर src, या webview के बिना ऐप में html, खाली व्यू दिखाता है; किसी अन्य होस्ट पर टॉप-लेवल नेविगेशन (उपयोगकर्ता द्वारा टैप किया गया लिंक) नेटिव स्तर पर रद्द कर दिया जाता है। हर स्थिति code "ERR_ACCESS_DENIED" के साथ onError रिपोर्ट करती है।
{
"capabilities": {
"webview": { "hosts": ["example.com", "*.example.com"] }
}
}
#Platform notes
- iOS पहली बार वेब व्यू बनने पर WebKit लोड करता है, इसलिए ऐप को इसकी कीमत तभी चुकानी पड़ती है जब वह वेब व्यू दिखाए।
- macOS पर
onDownloadडाउनलोड को WebKit केWKDownloadसे सहेजता है। - Android पर पेज की
postMessageकॉल बैकग्राउंड थ्रेड पर आती हैं और क्रम से ऐप को फ़ॉरवर्ड की जाती हैं। - वेब व्यू के कोने गोल करने के लिए उसे कॉर्नर रेडियस और
overflow: "hidden"(.overflow_hidden()) दें।