इस पेज का अनुवाद मशीन द्वारा किया गया है और इसमें त्रुटियाँ हो सकती हैं। मूल अंग्रेज़ी पढ़ें

एलिमेंट

NumberInput

नंबर इनपुट एक ऐसी संख्या रखता है जिसे उपयोगकर्ता टाइप करता है या स्टेप से बदलता है: मात्रा, कीमत, आकार। उसका व्यवहार उसी के पास है: उसका फ़ील्ड एक संख्या लेता है, या उस संख्या की ओर बढ़ता टेक्स्ट (-, 1.), और बाकी सब अस्वीकार कर देता है, दूसरे कीबोर्ड के अंक और चिह्न भी; उसके बटन, Up और Down, और स्क्रीन रीडर के इंक्रीमेंट और डिक्रीमेंट संख्या को step से स्टेप करते हैं, उसके दशमलव बनाए रखते हुए, और min और max पर रुक जाते हैं; फ़ील्ड छोड़ने पर टाइप की गई संख्या इन सीमाओं में बाँध दी जाती है। रूप-रंग ऐप का है: NumberInput के अपने कोई रंग, आकार, बॉर्डर, आइकन या स्ट्रिंग नहीं होते। वह डिक्रीमेंट बटन, ऐप के prefix और suffix के बीच के फ़ील्ड, और इंक्रीमेंट बटन से बना है (या उनके बीच के फ़ील्ड और दोनों बटनों वाले एक कॉलम से, controlsRight), और ऐप हर भाग को स्टाइल करता है और बटनों में सामग्री भरता है।

el-number-input.tsx
el-number-input.tsxLive
import { type JSX, mount, signal, Text, View } from "nsh";
import { NumberInput, Switch } from "nsh/base";
import { desktop, focusRing, hoverFill, Log, Panel, size, Split, ui } from "./palette";

// The parts: geometry and states in classes, colors from the theme's
// tokens. The input is one recessed well (a stronger border under the
// pointer, the focus ring while its field edits: `focus-within:`); the
// buttons light up under the pointer and while pressed (a transition the
// reduced-motion setting stills) and fade where a step would do nothing (at
// `min` / `max`) or while the input is disabled, without their hover. A
// pointer gets the compact sizes. The price stacks its buttons on the right
// with its currency beside the field.
const frame = [
  desktop ? "h-8 rounded-lg" : "h-11 rounded-xl",
  "flex-row items-stretch overflow-hidden border border-input bg-field",
  "enabled:hover:border-faint focus-within:outline-2 focus-within:outline-info disabled:opacity-50",
].join(" ");
const stepButton = [
  desktop ? "w-8" : "w-11",
  "items-center justify-center transition-colors duration-100 motion-reduce:transition-none",
  "enabled:hover:bg-accent active:bg-track disabled:opacity-35",
].join(" ");
const stackedButton = "flex-1 items-center justify-center transition-colors duration-100 motion-reduce:transition-none enabled:hover:bg-accent active:bg-track disabled:opacity-35";
const field = () => ({
  flex: 1,
  minWidth: 0,
  paddingHorizontal: desktop ? 8 : 10,
  fontSize: desktop ? 14 : 16,
  color: ui().text,
  placeholderColor: ui().faint,
  fontVariant: ["tabular-nums" as const],
});

/** A minus or a plus drawn with two bars in the text color. */
function Glyph(props: { plus?: boolean; small?: boolean }) {
  const length = props.small ? 8 : desktop ? 10 : 12;
  const bar = () => ({ position: "absolute" as const, borderRadius: 1, backgroundColor: ui().text });
  return (
    <View style={{ pointerEvents: "none", width: length, height: length }}>
      <View style={{ ...bar(), left: 0, right: 0, top: length / 2 - 0.75, height: 1.5 }} />
      {props.plus ? <View style={{ ...bar(), top: 0, bottom: 0, left: length / 2 - 0.75, width: 1.5 }} /> : null}
    </View>
  );
}

function Row(props: { label: string; detail: string; children: JSX.Element }) {
  return (
    <View class={`flex-row items-center gap-3 ${desktop ? "px-3 py-2.5" : "px-4 py-3"}`}>
      <View style={{ flex: 1, gap: 2 }}>
        <Text style={{ fontSize: size.label, fontWeight: "500", color: ui().text }}>{props.label}</Text>
        <Text style={{ fontSize: size.detail, color: ui().muted }}>{props.detail}</Text>
      </View>
      <View style={{ width: desktop ? 148 : 168 }}>{props.children}</View>
    </View>
  );
}

const show = (v: number | null) => (v === null ? "null" : `${v}`);

function App() {
  const tickets = signal<number | null>(2);
  const price = signal<number | null>(19.95);
  const temperature = signal<number | null>(null);
  const disabled = signal(false);
  const events = signal<string[]>([]);
  const change = (name: string, value: { set: (v: number | null) => void }) => ({ value: next }: { value: number | null }) => {
    value.set(next);
    events.set([`${name} → ${show(next)}`, ...events()].slice(0, 5));
  };
  const line = () => <View style={{ height: 1, marginLeft: desktop ? 12 : 16, backgroundColor: ui().line }} />;
  const sides = () => ({
    disabled: disabled(),
    class: frame,
    inputStyle: { ...field(), textAlign: "center" as const },
    decrementButtonClass: `${stepButton} border-r border-line`,
    incrementButtonClass: `${stepButton} border-l border-line`,
    decrementButton: <Glyph />,
    incrementButton: <Glyph plus />,
  });
  return (
    <View style={{ flex: 1, backgroundColor: ui().page, padding: 20, paddingTop: 24, gap: 14 }}>
      <Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>number-input</Text>
      <Text style={{ color: ui().muted }}>
        {desktop ? "Type a number, click − and +, or press ↑ ↓ (Page Up / Down: ten steps) in the field" : "Type a number, or tap − and +"}
      </Text>

      <Split
        gap={14}
        inspector={
          <>
            <Panel title="value · disabled">
              <Text style={{ fontSize: 13, color: ui().text, fontVariant: ["tabular-nums"] }}>
                tickets {show(tickets())} · price {show(price())} · °C {show(temperature())}
                {disabled() ? " (disabled)" : ""}
              </Text>
              <Switch
                checked={disabled()}
                onChange={({ checked }) => disabled.set(checked)}
                class={`-mx-2 flex-row-reverse items-center justify-between gap-3 rounded-lg px-2 ${desktop ? "py-1" : "py-2"} active:opacity-70`}
                hoverStyle={hoverFill()}
                focusStyle={focusRing()}
                trackClass={`${desktop ? "w-9" : "w-12"} rounded-full p-0.5 transition-colors motion-reduce:transition-none`}
                trackStyle={disabled() ? ui().accentFill : ui().inset}
                thumbClass={desktop ? "size-4 rounded-full transition-transform motion-reduce:transition-none checked:translate-x-4" : "size-5.5 rounded-full transition-transform motion-reduce:transition-none checked:translate-x-5"}
                thumbStyle={disabled() ? { backgroundColor: ui().onAccent } : ui().raised}
              >
                <Text style={{ flex: 1, fontSize: size.label, color: ui().text }}>Disabled</Text>
              </Switch>
            </Panel>
            <Log title="onChange({ value })" lines={events()} minHeight={92} />
          </>
        }
      >
        <View style={{ ...ui().card, borderRadius: 16, overflow: "hidden" }}>
          <Row label="Tickets" detail="1 to 10">
            <NumberInput
              value={tickets()}
              onChange={change("tickets", tickets)}
              min={1}
              max={10}
              accessibilityLabel="Tickets"
              {...sides()}
            />
          </Row>
          {line()}
          <Row label="Price" detail="Steps of 0.05">
            <NumberInput
              value={price()}
              onChange={change("price", price)}
              min={0}
              step={0.05}
              accessibilityLabel="Price"
              controlsRight
              disabled={disabled()}
              class={frame}
              inputStyle={field()}
              controlsClass={`${desktop ? "w-6" : "w-9"} border-l border-line`}
              incrementButtonClass={`${stackedButton} border-b border-line`}
              decrementButtonClass={stackedButton}
              incrementButton={<Glyph plus small />}
              decrementButton={<Glyph small />}
              suffix={<Text style={{ alignSelf: "center", paddingRight: 8, fontSize: size.detail, color: ui().muted }}>USD</Text>}
            />
          </Row>
          {line()}
          <Row label="Temperature" detail="−20 to 40 °C, halves">
            <NumberInput
              value={temperature()}
              onChange={change("temperature", temperature)}
              min={-20}
              max={40}
              step={0.5}
              placeholder="Not set"
              accessibilityLabel="Temperature"
              {...sides()}
            />
          </Row>
        </View>
      </Split>
    </View>
  );
}

mount(App);
use nsh_base::{number_input, theme};
use nsh_core::prelude::*;

fn tickets(count: Signal<Option<f64>>) -> impl IntoElement {
    number_input()
        .value(move || count.get())
        .min(1.0)
        .max(10.0)
        .accessibility_label("Tickets")
        .on_change(move |next: &Option<f64>| count.set(*next))
        .h(px(32.0))
        .flex_row()
        .rounded(px(8.0))
        .border_1()
        .border_color(move || theme().input)
        .input_style(|s| s.flex_1().text_center().text_color(move || theme().foreground))
        .decrement_button_style(|s| s.w(px(32.0)).items_center().justify_center())
        .increment_button_style(|s| s.w(px(32.0)).items_center().justify_center())
        .decrement_button(text("−"))
        .increment_button(text("+"))
}

NumberInput nsh/base से आता है, जो Rust में nsh-base क्रेट है। Rust में number_input() एक इनपुट बनाता है, जिसमें हर प्रॉप के लिए एक सेटर (.value(..), .input_style(..)), हर इवेंट के लिए एक लिस्नर मेथड (.on_change(..)), इनपुट के अपने लिए टाइप्ड स्टाइल मेथड, और फ़ील्ड के आसपास की चीज़ों के लिए .prefix(..) / .suffix(..) होते हैं। इसका मान एक Option<f64> है।

#मान

मान एक संख्या है, या खाली फ़ील्ड के लिए null। ऐप इसे value से अपने पास रखता है और वही मान सेट करता है जो onChange रिपोर्ट करता है, जैसा nsh/base के हर कंट्रोल में होता है; या वह मान को defaultValue से इनपुट पर छोड़ देता है, और फिर भी बदलाव सुनता है।

फ़ील्ड का टेक्स्ट और मान वहाँ अलग रखे जाते हैं जहाँ ज़रूरी है, ताकि उपयोगकर्ता खुलकर टाइप कर सके:

  • onChange तब वह संख्या रिपोर्ट करता है जो टेक्स्ट पढ़ने पर बनती है, जब भी वह संख्या बदलती है: जब तक टेक्स्ट किसी संख्या के रूप में नहीं पढ़ा जाता (खाली, -, .) तब तक null; 1 के बाद 1. टाइप करने पर, या 1.5 के बाद 1.50 टाइप करने पर, कुछ रिपोर्ट नहीं होता।
  • जब तक टेक्स्ट उसी मान के रूप में पढ़ा जाता है जो इनपुट के पास है, तब तक टेक्स्ट वैसा ही रहता है जैसा उपयोगकर्ता ने टाइप किया, इसलिए जो कंट्रोल्ड इनपुट हर रिपोर्ट किया गया मान वापस दे देता है वह 1. और 2.50 को बनाए रखता है। ऐप द्वारा सेट किया गया ऐसा मान जो इनपुट ने रिपोर्ट नहीं किया, फ़ील्ड में सबसे छोटे ऐसे टेक्स्ट के रूप में दिखता है जो उसे पढ़ने पर वही मान दे (0.3, 12)।
  • टाइप करते समय संख्या min और max के बाहर हो सकती है (min={10} के साथ 15 की ओर जाता 1), और उसे वैसे ही रिपोर्ट किया जाता है जैसी टाइप की गई। फ़ील्ड छोड़ने पर, या Return / Enter पर, उसे सीमा में बाँध दिया जाता है और वही रिपोर्ट होता है; जो टेक्स्ट किसी संख्या के रूप में नहीं पढ़ा जाता वह जैसा है वैसा रहता है।
  • एक स्टेप (बटन, कुंजी, स्क्रीन रीडर की क्रिया) अगला मान onChange से माँगता है; कंट्रोल्ड मान के साथ फ़ील्ड तब बदलता है जब ऐप उसे वापस देता है, स्टेप के दशमलव बनाए रखते हुए (max={100} तक 99.5 ऊपर करने पर 100.0 दिखता है), इसलिए जो ऐप किसी स्टेप को अस्वीकार करता है उसमें फ़ील्ड वहीं रहता है। ऐप द्वारा अस्वीकार किया गया संपादन फ़ील्ड में तब तक रहता है जब तक वह संपादित हो रहा है, और फ़ील्ड छोड़ने पर चला जाता है।
const price = signal<number | null>(19.95);

<NumberInput
  value={price()}
  onChange={({ value }) => price.set(value)}
  onSubmit={({ value }) => save(value)}
  min={0}
  step={0.05}
  placeholder="Price"
  accessibilityLabel="Price"
/>;

फ़ील्ड ये लेता है:

  • अंक, एक दशमलव बिंदु और सबसे पहले एक चिह्न (+ या -); अक्षर, दूसरा बिंदु या चिह्न, या कहीं और आया चिह्न अस्वीकार कर दिया जाता है, और फ़ील्ड अपने टेक्स्ट पर लौट जाता है।
  • फ़ुल-विड्थ अंक, चिह्न और बिंदु, माइनस चिह्न (U+2212) और आइडियोग्राफ़िक फ़ुल स्टॉप (。), जिन्हें उनके ASCII रूप में पढ़ा जाता है, जैसा चीनी और जापानी इनपुट मेथड टाइप करते हैं।
  • दशमलव बिंदु के रूप में अल्पविराम, जब तक टेक्स्ट में कोई बिंदु नहीं है (3,25 यानी 3.25), जैसा कई भाषाओं के कीबोर्ड की दशमलव कुंजी टाइप करती है।

#प्रॉप्स

प्रॉपRustप्रकारडिफ़ॉल्टविवरण
value.value(..)number | nullकोई नहींमान (कंट्रोल्ड): एक संख्या, या खाली फ़ील्ड के लिए null। ऐप वही मान सेट करता है जो onChange रिपोर्ट करता है।
defaultValue.default_value(..)number | nullकोई नहींशुरुआती मान, जब तक value सेट नहीं है; तब इनपुट अपना मान खुद रखता है।
min.min(..)numberकोई नहींसबसे छोटा मान: स्टेप यहीं रुक जाते हैं, इससे नीचे टाइप किया गया मान फ़ील्ड छोड़ने पर इसी तक बाँध दिया जाता है।
max.max(..)numberकोई नहींसबसे बड़ा मान, जैसा min ऊपर से करता है; min से नीचे का मान min होता है।
step.step(..)number1एक स्टेप कितना जाता है (Page Up / Page Down: दस स्टेप); जो धनात्मक संख्या नहीं है वह 1 होता है।
disabled.disabled(..)booleanfalseटाइपिंग, स्टेप या फ़ोकस नहीं; disabled: क्लासेस इनपुट और उसके भागों पर लागू होती हैं।
readonly.readonly(..)booleanfalseटेक्स्ट को चुना और कॉपी किया जा सकता है पर बदला नहीं जा सकता, न टाइपिंग से न स्टेप से; बटन अक्षम होते हैं।
placeholder.placeholder(..)stringकोई नहींफ़ील्ड खाली होने पर दिखता है।
accessibilityLabel.accessibility_label(..)stringकोई नहींफ़ील्ड के लिए स्क्रीन रीडर क्या बोलते हैं, जब कोई दिखने वाला लेबल उसे नाम नहीं देता।
controlsRight.controls_right(..)booleanfalseदोनों बटन फ़ील्ड के दाईं ओर एक कॉलम में, इंक्रीमेंट डिक्रीमेंट के ऊपर। इनपुट बनते समय पढ़ा जाता है।
inputClass, inputStyle.input_style(|s| ..)string, StyleProp<InputStyle>कोई नहींफ़ील्ड: उसका बॉक्स और उसका टेक्स्ट (color, fontSize, textAlign, placeholderColor, caretColor)।
decrementButtonClass, decrementButtonStyle.decrement_button_style(|s| ..)string, StyleProp<ViewStyle>कोई नहींडिक्रीमेंट बटन।
incrementButtonClass, incrementButtonStyle.increment_button_style(|s| ..)string, StyleProp<ViewStyle>कोई नहींइंक्रीमेंट बटन।
decrementButton, incrementButton.decrement_button(..), .increment_button(..)JSX.Elementकोई नहींबटनों की सामग्री: एक चिह्न, एक आइकन।
controlsClass, controlsStyle.controls_style(|s| ..)string, StyleProp<ViewStyle>कोई नहींवह कॉलम जो controlsRight के साथ दोनों बटन रखता है।
prefix.prefix(..)JSX.Elementकोई नहींफ़ील्ड से पहले क्या आता है, उसके बाईं ओर के बटन के बाद: मुद्रा चिह्न, आइकन।
suffix.suffix(..)JSX.Elementकोई नहींफ़ील्ड के बाद क्या आता है, उसके दाईं ओर के बटनों से पहले: इकाई, मुद्रा।
class, styleस्टाइल मेथडstring, StyleProp<ViewStyle>कोई नहींइनपुट स्वयं: फ़ील्ड और बटनों के आसपास की पंक्ति।

#इवेंट

इवेंटRustपेलोडकब
onChange.on_change(|value: &Option<f64>| ..){ value: number | null }वह संख्या बदली जो फ़ील्ड पढ़ने पर देता है (टाइपिंग, स्टेप, स्क्रीन रीडर का इंक्रीमेंट या डिक्रीमेंट, सीमा से बाहर मान के साथ फ़ील्ड छोड़ना)।
onSubmit.on_submit(|value: &Option<f64>| ..){ value: number | null }फ़ील्ड में Return / Enter: मान, min और max में बाँधा हुआ।
onFocus.on_focus(..)कोई नहींफ़ील्ड को कीबोर्ड फ़ोकस मिला।
onBlur.on_blur(..)कोई नहींफ़ील्ड का कीबोर्ड फ़ोकस चला गया (उसका मान पहले min और max में बाँधा गया)।

#भाग

भागकहाँस्टाइल किसे से होता है
इनपुटclass, style
डिक्रीमेंट बटनइनपुट में, पहला (controlsRight के साथ: कंट्रोल कॉलम में, दूसरा)decrementButtonClass, decrementButtonStyle; उसकी सामग्री decrementButton
फ़ील्डइनपुट मेंinputClass, inputStyle
प्रीफ़िक्सइनपुट में, फ़ील्ड से पहलेअपने तत्व (prefix)
सफ़िक्सइनपुट में, फ़ील्ड के बादअपने तत्व (suffix)
इंक्रीमेंट बटनइनपुट में, आख़िरी (controlsRight के साथ: कंट्रोल कॉलम में, पहला)incrementButtonClass, incrementButtonStyle; उसकी सामग्री incrementButton
कंट्रोल कॉलमइनपुट में, आख़िरी, controlsRight के साथcontrolsClass, controlsStyle

इनपुट एक पंक्ति है जिसका लेआउट ऐप तय करता है: class पर flex-row, और फ़ील्ड पर flex-1 ताकि बटनों के बाद बची जगह उसे मिल जाए। भाग जो स्टेट दिखाते हैं:

  • इनपुट: जब उसका फ़ील्ड संपादित हो रहा हो तब focus-within: (पूरे इनपुट के चारों ओर एक रिंग), disabled:।
  • फ़ील्ड: संपादन के दौरान focus:, disabled:।
  • बटन: पॉइंटर के नीचे hover:, दबे होने पर active:, जब इनपुट अक्षम या रीड-ओनली हो और जहाँ कोई स्टेप कुछ नहीं करेगा वहाँ disabled: (min पर डिक्रीमेंट, max पर इंक्रीमेंट)। हover को enabled: (enabled:hover:bg-accent) के पीछे रखें ताकि अक्षम बटन हाइलाइट न हो।

स्टेट की गति का चुनाव ऐप का है (transition-colors), और motion-reduce: उसे उन लोगों के लिए स्थिर रखता है जो कम गति माँगते हैं।

#कीबोर्ड

जब फ़ील्ड संपादित हो रहा हो, इनपुट WAI-ARIA की स्पिन बटन कुंजियाँ लेता है, जो दबाए रखने पर दोहराई जाती हैं:

कुंजीक्या करती है
Upएक स्टेप ऊपर।
Downएक स्टेप नीचे।
Page Up, Page Downदस स्टेप ऊपर या नीचे।

Home और End फ़ील्ड के ही रहते हैं: वे टेक्स्ट में कैरेट को ले जाते हैं। बटन कीबोर्ड फ़ोकस के पड़ाव नहीं हैं: Tab फ़ील्ड से सीधे इनपुट के बाद की चीज़ पर जाता है, और बटन पर प्रेस फ़ोकस को फ़ील्ड में ही रहने देता है (डेस्कटॉप पर, अगर फ़ोकस कहीं और था तो प्रेस उसे फ़ील्ड पर ले आता है; टच स्क्रीन पर वह उसे वहीं छोड़ देता है, ताकि कीबोर्ड न खुले)।

#एक्सेसिबिलिटी

  • फ़ील्ड एक स्पिन बटन ("spinbutton") है, जिसके साथ उसका लेबल, उसका मान (वह संख्या जो वह पढ़ने पर देता है, min और max के बीच) और उसका टेक्स्ट होता है।
  • स्क्रीन रीडर की इंक्रीमेंट और डिक्रीमेंट क्रियाएँ (VoiceOver का ऊपर और नीचे स्वाइप) उसे वैसे ही स्टेप करती हैं जैसे Up और Down करते हैं।
  • बटन स्क्रीन रीडर से छिपे होते हैं, जैसे ब्राउज़र के नंबर फ़ील्ड के होते हैं: फ़ील्ड के अपने इंक्रीमेंट और डिक्रीमेंट उसे स्टेप करते हैं।
  • जब disabled हो, स्क्रीन रीडर बताते हैं कि फ़ील्ड धुंधला (अक्षम) है।

सामान्य रूप से लेबल, रोल और क्रियाओं के लिए एक्सेसिबिलिटी देखें।

#प्लैटफ़ॉर्म नोट्स

  • फ़ील्ड प्लैटफ़ॉर्म का टेक्स्ट फ़ील्ड है (Input): कीबोर्ड, कैरेट, चयन, कॉपी और पेस्ट, और इनपुट मेथड सिस्टम के हैं। फ़ोन दशमलव कीबोर्ड दिखाते हैं, और जब min 0 से कम हो या सेट न हो तब चिह्न के साथ।
  • हर प्लैटफ़ॉर्म वही बनाता है जो ऐप स्टाइल करता है। Up / Down और Page Up / Page Down हर प्लैटफ़ॉर्म पर हार्डवेयर कीबोर्ड से इनपुट तक पहुँचते हैं।