Diese Seite wurde maschinell übersetzt und kann Fehler enthalten. Das englische Original lesen

Elemente

DatePicker

Ein Date Picker legt ein Datum über einen Calendar fest, der sich vom Trigger der App aus öffnet: ein Fälligkeitsdatum, ein Geburtstag, mit range die Tage einer Reise, mit timePrecision ein Datum samt Uhrzeit. Wie Select öffnet er sich so, wie es jedes Gerät erwartet:

el-date-picker.tsx
el-date-picker.tsxLive
import { type JSX, mount, signal, Text, View, windowSize } from "nsh";
import { type CalendarEntry, DatePicker, type DatePickerValue, type DateRangePreset, type HourCycle, type OpenChangeReason, type TimeFieldSegmentEntry } from "nsh/base";
import { button, desktop, Icon, Log, Panel, Segmented, size, Split, ui, wide } from "./palette";

// Three date pickers in a card: a due date (weekends disabled, presets
// beside the calendar, a clear button), a stay picked as a range over two
// months in a wide window, and a meeting's date and time with the hours
// on the clock the inspector picks. The trigger is the shared button look
// with the value and a chevron that turns while the calendar is open; the
// calendar is a popup at the trigger, a sheet on a touch screen. Every
// item is drawn by `renderItem` (month names, weekday initials, today on a
// quiet fill, the selected days filled, a range's band) and every segment
// of the time by `renderSegment`; the time's label starts its row and the
// field, a well as the number inputs', ends it.

const MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
const WEEKDAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];

/** A day's ISO date (`"2026-10-28"`), in local time as the calendar's days are. */
const iso = (d: Date) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
const addDays = (d: Date, days: number) => new Date(d.getFullYear(), d.getMonth(), d.getDate() + days);
/** An ISO date's month and day: `Oct 28`. */
const monthDay = (value: string) => `${MONTHS[Number(value.slice(5, 7)) - 1].slice(0, 3)} ${Number(value.slice(8, 10))}`;

/** A time `"HH:MM"` on the picked clock: `14:30`, or `2:30 PM`. */
function clock(time: string, cycle: HourCycle) {
  if (cycle === "h23") return time;
  const hour = Number(time.slice(0, 2));
  return `${hour % 12 === 0 ? 12 : hour % 12}:${time.slice(3, 5)} ${hour < 12 ? "AM" : "PM"}`;
}

/** The text of a control: 13 with a pointer, the touch label size otherwise. */
const controlText = desktop ? 13 : size.label;
/** A day's cell: seven across the popup with a pointer, across a phone's sheet otherwise. */
const cell = desktop ? 32 : 42;
/** The gap between the months of a calendar of two. */
const monthGap = 16;
/**
 * On a touch screen the calendar spans the sheet (the window less the sheet's padding), its columns sharing it: the
 * header's titles split what Previous and Next leave, so the month and the year meet in the middle, and each month's
 * title of two is over its month.
 */
const toggleWidth = () => Math.floor((windowSize().width - 2 * 10) / 2 - cell);

/** A popup at the trigger with a pointer, a sheet at the bottom on a touch screen. */
const surface = () =>
  desktop
    ? { ...ui().card, borderRadius: 12, padding: 8 }
    : { ...ui().card, borderTopLeftRadius: 20, borderTopRightRadius: 20, paddingHorizontal: 10, paddingTop: 14, paddingBottom: 30 };

const backdrop = { backgroundColor: "rgba(0,0,0,0.45)" };

/** The popup's layout: presets in a column beside the calendar with a pointer, a row of chips above it on a phone. */
const content = desktop ? { flexDirection: "row" as const, gap: 10 } : { gap: 12 };
const presets = () =>
  desktop
    ? { gap: 2, minWidth: 104, paddingRight: 10, borderRightWidth: 1, borderColor: ui().border }
    : { flexDirection: "row" as const, flexWrap: "wrap" as const, gap: 6 };
const preset = () =>
  desktop
    ? { height: 28, paddingHorizontal: 8, borderRadius: 6, justifyContent: "center" as const, fontSize: 13, color: ui().text }
    : { height: 34, paddingHorizontal: 12, borderRadius: 17, borderWidth: 1, borderColor: ui().border, justifyContent: "center" as const, fontSize: 14, color: ui().text };
/** The calendar: in the popup with a pointer; across the sheet on a touch screen, as the time's row under it. */
const calendar = desktop ? { padding: 2 } : {};
/** The time's row under a hairline, its label at the start and the field at the end, on the calendar's edges. */
const time = () => ({
  gap: 10,
  marginTop: 8,
  paddingTop: 8,
  borderTopWidth: 1,
  borderColor: ui().border,
  ...(desktop ? { paddingHorizontal: 2 } : {}),
});
/** The time's field: a recessed well as the number inputs', with their hover and the keyboard's ring while it edits. */
const timeField = [
  desktop ? "h-8 rounded-lg px-2" : "h-11 rounded-xl px-3",
  "items-center border border-input bg-field",
  "enabled:hover:border-faint focus-within:outline-2 focus-within:outline-info",
].join(" ");

/** The trigger: the shared button's look, the value at the start and a chevron in a lane as wide as it. */
function Trigger(props: { title?: string; placeholder: string; open: boolean; clearable?: boolean }) {
  const look = button("secondary", {
    style: () => ({
      flexDirection: "row",
      justifyContent: "space-between",
      gap: 8,
      ...(desktop ? { width: 260 } : {}),
      ...(props.open ? { borderColor: ui().faint } : {}),
    }),
  });
  return (
    <View {...look}>
      <Text style={{ numberOfLines: 1, flex: 1, fontSize: controlText, color: props.title ? ui().text : ui().muted }}>
        {props.title ?? props.placeholder}
      </Text>
      {/* With a clear button over the end (`cleanable`), an empty lane as wide as the button in place of the chevron. */}
      <View
        class="transition-transform duration-150 motion-reduce:transition-none"
        style={{
          width: props.clearable ? 20 : 10,
          height: 20,
          alignItems: "center",
          justifyContent: "center",
          opacity: props.clearable ? 0 : 1,
          transform: [{ rotate: props.open ? "270deg" : "90deg" }],
        }}
      >
        <Text style={{ fontSize: 18, color: ui().muted }}>›</Text>
      </View>
    </View>
  );
}

/**
 * The calendar's items (`itemClass`): pressed at 70%; `group`, so the
 * drawing inside follows the item's states.
 */
const itemClass = "group active:opacity-70";

/**
 * An item of the calendar, drawn once from what it is, its states the
 * item's classes: Previous and Next as arrows (faint while they take no
 * press), the month and year toggles (or each month's title, with `months`
 * over one) by name, the open view's toggle in the accent, the weekdays'
 * initials, and the days: today on the theme's quiet `accent` fill
 * (gpui-component's), the selected date and a range's ends filled, the days
 * of a complete range on a band from end to end (`data-range-*`), the other
 * months' days and the disabled ones faint, the keyboard's day ringed.
 */
function Item(props: { item: CalendarEntry; months?: number }) {
  const kind = props.item.kind;
  if (kind === "previous" || kind === "next") {
    return (
      <View class="group-disabled:opacity-30" style={{ width: cell, height: cell, alignItems: "center", justifyContent: "center" }}>
        <Text style={{ fontSize: 20, color: ui().muted }}>{kind === "previous" ? "‹" : "›"}</Text>
      </View>
    );
  }
  if (kind === "monthToggle" || kind === "yearToggle") {
    // A month's title in a calendar of several takes no press: the month and its year, over the month.
    const title = kind === "monthToggle" && (props.months ?? 1) > 1;
    return (
      <Text
        class="text-foreground group-selected:text-brand"
        style={{
          width: !desktop ? toggleWidth() : title ? 6 * cell + monthGap / 2 : kind === "monthToggle" ? 3 * cell : 2 * cell,
          paddingHorizontal: 3,
          textAlign: title ? "center" : kind === "monthToggle" ? "right" : "left",
          fontSize: size.label,
          fontWeight: "600",
        }}
      >
        {title ? `${MONTHS[props.item.value - 1]} ${props.item.year}` : kind === "monthToggle" ? MONTHS[props.item.value - 1] : String(props.item.value)}
      </Text>
    );
  }
  if (kind === "weekday") {
    return <Text style={{ minWidth: cell, paddingVertical: 4, textAlign: "center", fontSize: 11, color: ui().faint }}>{WEEKDAYS[props.item.value]}</Text>;
  }
  if (kind === "month" || kind === "year") {
    // The month view's months (rows of 3) and the year view's years (rows of 5), as wide as a month; the shown one
    // filled.
    return (
      <View
        class="rounded-lg group-selected:bg-brand-fill"
        style={{ minWidth: (7 * cell) / (kind === "month" ? 3 : 5), height: cell + 6, alignItems: "center", justifyContent: "center" }}
      >
        <Text class="text-foreground group-selected:text-on-brand" style={{ fontSize: controlText }}>
          {kind === "month" ? MONTHS[props.item.value - 1].slice(0, 3) : String(props.item.value)}
        </Text>
      </View>
    );
  }
  // A day, as wide as its column (the calendar's columns share its width). The band spans a range's middle days and
  // reaches from its ends' middles; a range of one day has none. The circle fills for the selected date and a
  // range's ends (not its middle days), else for today.
  const own = props.item.outside ? "text-faint" : "text-foreground";
  return (
    <View style={{ minWidth: cell, height: cell, alignItems: "center", justifyContent: "center" }}>
      <View
        class={
          "absolute inset-y-0.5 left-0 right-0 hidden bg-brand-tint group-data-range-middle:flex group-data-range-start:flex " +
          "group-data-range-start:left-1/2 group-data-range-end:flex group-data-range-end:right-1/2 group-data-range-start:group-data-range-end:hidden"
        }
      />
      <View
        class={
          `${props.item.today ? "bg-accent" : ""} border-[1.5px] border-transparent group-focus:border-info ` +
          "group-selected:bg-brand-fill group-data-range-middle:bg-transparent"
        }
        style={{ width: cell - 4, height: cell - 4, borderRadius: (cell - 4) / 2, alignItems: "center", justifyContent: "center" }}
      >
        <Text
          class={
            `${own} ${props.item.today ? "font-semibold" : "font-normal"} group-selected:font-semibold group-selected:text-on-brand ` +
            `group-data-range-middle:${own} group-disabled:text-faint group-disabled:opacity-50`
          }
          style={{ fontSize: controlText, fontVariant: ["tabular-nums"] }}
        >
          {String(props.item.value)}
        </Text>
      </View>
    </View>
  );
}

/**
 * A segment of the time: filled while it has the keys (`focus:` on the
 * segment, `segmentClass`), its digits or AM / PM in the color on the fill
 * (`group-focus:`), so moving between the segments draws nothing.
 */
const segmentClass = "group rounded active:opacity-70 focus:bg-brand-fill";
function Segment(props: { segment: TimeFieldSegmentEntry }) {
  return (
    <Text class="text-foreground group-focus:text-on-brand" style={{ paddingHorizontal: 3, fontSize: controlText, fontVariant: ["tabular-nums"] }}>
      {props.segment.label}
    </Text>
  );
}

/** A label over its control, a quieter line under the label. */
function Field(props: { label: string; detail: string; children: JSX.Element }) {
  return (
    <View style={{ gap: 8 }}>
      <View style={{ 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>
      {props.children}
    </View>
  );
}

function App() {
  const due = signal<string | null>(null);
  const stay = signal<DatePickerValue>(null);
  const meeting = signal<string | null>(null);
  const cycle = signal<HourCycle>("h12");
  const dueOpen = signal(false);
  const stayOpen = signal(false);
  const meetingOpen = signal(false);
  const events = signal<string[]>([]);
  const log = (line: string) => events.set([line, ...events()].slice(0, 8));
  const opened = (open: (next: boolean) => void) => ({ open: next }: { open: boolean; reason: OpenChangeReason }) => open(next);
  // The presets from today.
  const today = new Date();
  const monday = addDays(today, ((8 - today.getDay()) % 7) || 7);
  const dues: DateRangePreset[] = [
    { label: "Today", value: iso(today) },
    { label: "Tomorrow", value: iso(addDays(today, 1)) },
    { label: "Next Monday", value: iso(monday) },
  ];
  const stays: DateRangePreset[] = [
    { label: "This week", value: [iso(today), iso(addDays(today, (7 - today.getDay()) % 7))] },
    { label: "Next 7 nights", value: [iso(today), iso(addDays(today, 7))] },
  ];
  const stayTitle = () => {
    const r = stay();
    return Array.isArray(r) && r[0] && r[1] ? `${monthDay(r[0])} – ${monthDay(r[1])}, ${r[1].slice(0, 4)}` : undefined;
  };
  const meetingTitle = () => {
    const v = meeting();
    return v ? `${monthDay(v)}, ${v.slice(0, 4)} · ${clock(v.slice(11, 16), cycle())}` : undefined;
  };
  return (
    <View style={{ flex: 1, backgroundColor: ui().page, padding: 20, paddingTop: 24, gap: 14 }}>
      <Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>date-picker</Text>
      <Text style={{ color: ui().muted }}>
        {desktop ? "Arrows move between days, Return picks; the trigger takes Delete to clear" : "Tap a day; the meeting's time takes digits"}
      </Text>

      <Split
        gap={14}
        inspector={
          <>
            <Panel title="value">
              <Text style={{ fontSize: 13, color: ui().text }}>{`due ${JSON.stringify(due())}`}</Text>
              <Text style={{ fontSize: 13, color: ui().text }}>{`stay ${JSON.stringify(stay())}`}</Text>
              <Text style={{ fontSize: 13, color: ui().text }}>{`meeting ${JSON.stringify(meeting())}`}</Text>
            </Panel>
            <Segmented label="hourCycle" options={["h12", "h23"] as const} value={cycle()} onChange={(v) => cycle.set(v)} />
            <Log title="onChange" lines={events()} minHeight={120} />
          </>
        }
      >
        <View style={{ ...ui().card, borderRadius: 16, padding: 16, gap: 20 }}>
          <Field label="Due" detail="Weekdays only, presets beside the calendar, a clear button">
            <DatePicker
              value={due()}
              onChange={({ value }) => {
                due.set(typeof value === "string" ? value : null);
                log(`Due → ${JSON.stringify(value)}`);
              }}
              onOpenChange={opened((next) => dueOpen.set(next))}
              accessibilityLabel="Due"
              disabledMatcher={[0, 6]}
              presets={dues}
              offset={4}
              margin={8}
              popupStyle={surface()}
              backdropStyle={backdrop}
              contentStyle={content}
              presetsStyle={presets()}
              presetStyle={preset()}
              presetClass="active:opacity-70"
              calendarStyle={calendar}
              headerStyle={{ marginBottom: 4 }}
              itemClass={itemClass}
              renderItem={(item) => <Item item={item} />}
              style={desktop ? { width: 260 } : undefined}
              cleanable
              clearButtonAccessibilityLabel="Clear the due date"
              clearButtonClass="absolute items-center justify-center rounded-full enabled:hover:opacity-70"
              clearButtonStyle={{ top: (size.control - 20) / 2, right: 10, width: 20, height: 20 }}
              clearButton={<Icon name="close" size={14} tint={ui().muted} />}
              trigger={<Trigger title={due() ? `${monthDay(due()!)}, ${due()!.slice(0, 4)}` : undefined} placeholder="No due date" open={dueOpen()} clearable={due() !== null} />}
            />
          </Field>
          <Field label="Stay" detail={wide() ? "A range over two months: the first press starts it" : "A range: the first press starts it, the second ends it"}>
            <DatePicker
              range
              value={stay()}
              onChange={({ value }) => {
                stay.set(value);
                log(`Stay → ${JSON.stringify(value)}`);
              }}
              onOpenChange={opened((next) => stayOpen.set(next))}
              accessibilityLabel="Stay"
              numberOfMonths={wide() ? 2 : 1}
              firstDayOfWeek={1}
              presets={stays}
              offset={4}
              margin={8}
              popupStyle={surface()}
              backdropStyle={backdrop}
              contentStyle={content}
              presetsStyle={presets()}
              presetStyle={preset()}
              presetClass="active:opacity-70"
              calendarStyle={calendar}
              headerStyle={{ marginBottom: 4 }}
              bodyStyle={{ gap: monthGap }}
              itemClass={itemClass}
              renderItem={(item) => <Item item={item} months={wide() ? 2 : 1} />}
              trigger={<Trigger title={stayTitle()} placeholder="Check-in – check-out" open={stayOpen()} />}
            />
          </Field>
          <Field label="Meeting" detail={desktop ? "A day, then its time; the day again closes the calendar" : "A tap picks the day; open it again for the time"}>
            <DatePicker
              value={meeting()}
              onChange={({ value }) => {
                meeting.set(typeof value === "string" ? value : null);
                log(`Meeting → ${JSON.stringify(value)}`);
              }}
              onOpenChange={opened((next) => meetingOpen.set(next))}
              accessibilityLabel="Meeting"
              timePrecision="minute"
              hourCycle={cycle()}
              defaultTime="09:00"
              offset={4}
              margin={8}
              popupStyle={surface()}
              backdropStyle={backdrop}
              calendarStyle={calendar}
              headerStyle={{ marginBottom: 4 }}
              itemClass={itemClass}
              renderItem={(item) => <Item item={item} />}
              timeStyle={time()}
              timeLabel={<Text style={{ fontSize: controlText, color: ui().muted }}>Time</Text>}
              timeAccessibilityLabel="Meeting time"
              timeFieldClass={timeField}
              segmentClass={segmentClass}
              separatorStyle={{ fontSize: controlText, color: ui().faint }}
              renderSegment={(segment) => <Segment segment={segment} />}
              trigger={<Trigger title={meetingTitle()} placeholder="Not scheduled" open={meetingOpen()} />}
            />
          </Field>
        </View>
      </Split>
    </View>
  );
}

mount(App);
  • Auf Desktops und in Desktop-Browsern als Popup am Trigger; beim Öffnen liegt der Tastaturfokus auf den Tagen.
  • Auf Smartphones und Tablets als Sheet am unteren Bildschirmrand über einem Backdrop.

Der Picker besitzt das Verhalten: den Wert (den der App oder seinen eigenen), den Öffnungszustand, den Kalender, die Uhrzeit, die Presets, die Tastatur und das, was Screenreader vorlesen. Das Aussehen liegt bei der App: Sie zeichnet den Trigger (mit dem Wert, einem Platzhalter, einem Icon) und gestaltet das Popup, die Teile des Kalenders, die Presets und das Zeitfeld.

use chrono::NaiveDate;
use nsh_base::{DateRangePreset, DateTime, PickedDate, date_picker, theme};
use nsh_core::prelude::*;

fn trip_dates() -> impl IntoElement {
    let trip = signal(DateTime::Range(None, None));
    let title = move || {
        trip.get()
            .format("%b %-d")
            .map(|s| s.to_string())
            .unwrap_or_else(|| "Pick dates".to_string())
    };
    let day = |m, d| NaiveDate::from_ymd_opt(2026, m, d).unwrap();
    date_picker()
        .range(true)
        .number_of_months(2)
        .value(move || trip.get())
        .on_change(move |next: &PickedDate| trip.set(next.value))
        .presets(vec![
            DateRangePreset::range("This week", day(10, 25), day(10, 31)),
            DateRangePreset::range("Next week", day(11, 1), day(11, 7)),
        ])
        .accessibility_label("Trip")
        .offset(px(4.0))
        .popup_style(|s| {
            s.p_3()
                .rounded_lg()
                .border_1()
                .border_color(move || theme().border)
                .bg(move || theme().popover)
                .shadow_md()
        })
        .trigger(
            view()
                .h_9()
                .w_56()
                .px_3()
                .justify_center()
                .rounded_md()
                .border_1()
                .border_color(move || theme().input)
                .child(text(title)),
        )
}

DatePicker stammt aus nsh/base, dem nsh-base-Crate in Rust. In Rust erzeugt date_picker() einen Picker mit einem Setter pro Prop (.value(..), .range(..), .time_precision(..)) und einem Listener pro Event (.on_change(..)). Sein Wert ist ein DateTime (DateTime::Single, DateTime::Range, aus chrono Datumsangaben oder Datums- und Zeitangaben); onChange liefert einen PickedDate, also den Wert mit der Zeitgenauigkeit des Pickers.

#Wert

Der Wert hat die Formen der HTML-Inputs: ein ISO-Datum, "2026-10-28", wie das von <input type="date">; mit range [start, end]; mit timePrecision Datum und Uhrzeit, "2026-10-28T09:30" ("2026-10-28T09:30:15" bei Sekunden), wie das von <input type="datetime-local">; null bedeutet keinen Wert. Mit value gehört der Wert der App: Der Picker zeigt ihn an und meldet jede Änderung mit onChange({ value }); die App setzt value, um ihr zu folgen. Wird er weggelassen, behält der Picker seinen eigenen Wert, zunächst aus defaultValue.

  • Einzelnes Datum: Ein Druck auf einen Tag setzt den Wert und schließt das Popup.
  • Mit range beginnt der erste Druck den Bereich und der zweite beendet ihn, wie bei einem Calendar; der vollständige Bereich setzt den Wert und schließt das Popup.
  • Mit timePrecision (nur bei einem einzelnen Datum) erscheint unter dem Kalender ein Zeitfeld, und jede Änderung der Uhrzeit wird gemeldet. Auf Desktops lässt die Auswahl eines Tages das Popup für die Uhrzeit geöffnet und meldet das Datum mit seiner Uhrzeit; ein erneuter Druck auf den ausgewählten Tag schließt das Popup. Auf Smartphones und Tablets wählt ein Tipp auf einen Tag diesen mit seiner Uhrzeit aus und schließt das Sheet, wie bei einem einzelnen Datum; für die Uhrzeit öffnet sich das Sheet erneut. Ein Tag, der vor jeder Uhrzeit gewählt wird, erhält defaultTime (Standard 00:00). hourCycle legt fest, wie das Feld die Stunden zählt.

cleanable fügt eine Clear-Schaltfläche hinzu, solange ein Wert vorhanden ist: Ein Druck darauf löscht ihn (onChange({ value: null })). Sie steht nach dem Trigger, nicht darin, sodass ein Druck darauf nie das Popup öffnet; die App platziert und befüllt sie (clearButton), wie bei der Clear-Schaltfläche eines Select.

#Presets

presets sind Schaltflächen neben dem Kalender, die den Wert setzen: „Heute“, „Letzte 7 Tage“, „Morgen um 9“. Jedes ist { label, value }, value ein Datum, einen Bereich oder Datums- und Zeitangaben, unverändert übernommen; ein Druck setzt es und schließt das Popup. contentClass ordnet die Spalte der Presets und den Kalender mit der Uhrzeit an (eine Zeile, flex-row); presetsClass gestaltet die Spalte, presetClass jede Schaltfläche.

#Der Kalender

Jede Prop von Calendar außer dem Wert wird an die des Pickers weitergereicht: numberOfMonths, firstDayOfWeek, disabledMatcher, yearRange, previousAccessibilityLabel, nextAccessibilityLabel, die Parts (headerClass bis itemStyle) und renderItem. calendarClass und calendarStyle gestalten den Kalender selbst. Seine Ansichten und Tasten sind die eines Calendar.

Die Zeitzeile (timeClass, timeStyle) enthält an ihrem Anfang timeLabel, die Beschriftung der App dafür („Uhrzeit“), und an ihrem Ende das Zeitfeld (timeFieldClass, timeFieldStyle, timeAccessibilityLabel): eine Zeile, deren Elemente zentriert und auseinandergezogen sind (flex-row items-center justify-between, wie bei gpui-component). Die Segmente des Feldes entsprechen denen eines TimeField (segmentClass, separatorClass, renderSegment).

#Props

Prop Rust Typ Standard Beschreibung
value .value(..) DatePickerValue keiner Der Wert, der der App gehört.
defaultValue .default_value(..) DatePickerValue null Der Anfangswert, ohne value.
range .range(..) boolean false Einen Datumsbereich auswählen.
open, defaultOpen .open(..), .default_open(..) boolean false Der Öffnungszustand, von der App oder vom Picker selbst verwaltet.
disabled .disabled(..) boolean false Er bleibt geschlossen und nimmt weder Drücke noch Tastaturfokus an.
accessibilityLabel .accessibility_label(..) string keiner Wie Screenreader ihn nennen.
dateFormat .date_format(..) string "%Y/%m/%d" und die Uhrzeit Wie Screenreader den Wert vorlesen, im Format von chrono.
timePrecision .time_precision(..) "minute" | "second" keiner Auch die Uhrzeit bearbeiten, bis hinunter zu dieser Genauigkeit.
hourCycle .hour_cycle(..) "h23" | "h12" "h23" Wie das Zeitfeld die Stunden zählt.
defaultTime .default_time(..) string "00:00" Die Uhrzeit, die ein Datum erhält, bevor der Benutzer eine festlegt.
cleanable .cleanable(..) boolean false Eine Clear-Schaltfläche, solange ein Wert vorhanden ist.
presets .presets(..) DateRangePreset[] [] Schaltflächen im Popup, die den Wert setzen.
numberOfMonths, firstDayOfWeek, disabledMatcher, yearRange wie bei Calendar wie bei Calendar wie bei Calendar Die Monate, Wochen, deaktivierten Daten und Jahre des Kalenders.
previousAccessibilityLabel, nextAccessibilityLabel wie bei Calendar string keiner Was Screenreader für „Zurück“ und „Weiter“ des Kalenders ansagen.
placement, align, offset, margin .placement(..), … wie bei Select "bottom", "start", 0, 0 Desktops: Wo das Popup relativ zum Trigger erscheint.
popupClass, popupStyle .popup_class(..), .popup_style(..) string, StyleProp<ViewStyle> keiner Das Popup (Desktops) oder das Sheet (Touchscreens).
backdropClass, backdropStyle .backdrop_class(..), .backdrop_style(..) string, StyleProp<ViewStyle> keiner Das Backdrop hinter dem Sheet.
contentClass, contentStyle .content_class(..), .content_style(..) string, StyleProp<ViewStyle> keiner Der Inhalt des Popups: die Presets neben dem Kalender und die Uhrzeit.
presetsClass, presetsStyle .presets_class(..), .presets_style(..) string, StyleProp<ViewStyle> keiner Die Spalte der Presets.
presetClass, presetStyle .preset_class(..), .preset_style(..) string, StyleProp<TextStyle> keiner Die Schaltfläche jedes Presets.
calendarClass, calendarStyle .calendar_class(..), .calendar_style(..) string, StyleProp<ViewStyle> keiner Der Kalender.
headerClass … itemStyle wie bei Calendar wie bei Calendar keiner Header, Body, Monate, Zeilen und Elemente des Kalenders.
renderItem .render_item(..) (item: CalendarEntry) => JSX.Element keiner Jedes Element des Kalenders, von der App anhand dessen gezeichnet, was es ist; seine Zustände sind die Klassen der Elemente, wie bei Calendar.
timeClass, timeStyle .time_class(..), .time_style(..) string, StyleProp<ViewStyle> keiner Die Zeile der Uhrzeit: ihre Beschriftung am Anfang, das Feld am Ende.
timeLabel .time_label(..) JSX.Element keiner Was die Zeile der Uhrzeit vor dem Feld zeigt.
timeFieldClass, timeFieldStyle .time_field_class(..), .time_field_style(..) string, StyleProp<ViewStyle> keiner Das Zeitfeld.
timeAccessibilityLabel .time_accessibility_label(..) string keiner Wie Screenreader das Zeitfeld nennen.
segmentClass, segmentStyle, separatorClass, separatorStyle, renderSegment wie bei TimeField wie bei TimeField keiner Die Segmente des Zeitfeldes und :.
clearButtonClass, clearButtonStyle .clear_button_class(..), .clear_button_style(..) string, StyleProp<ViewStyle> keiner Die Clear-Schaltfläche (cleanable).
clearButton .clear_button(..) JSX.Element keiner Der Inhalt der Clear-Schaltfläche.
clearButtonAccessibilityLabel .clear_button_accessibility_label(..) string keiner Wie Screenreader die Clear-Schaltfläche nennen.
trigger .trigger(..) JSX.Element keiner Was beim Drücken das Popup öffnet; das Popup wird relativ dazu platziert.
class, style Style-Methoden string, StyleProp<ViewStyle> keiner Die View um den Trigger.

#Events

Event Rust Beschreibung
onChange .on_change(|value| ..) Der Wert hat sich geändert (ein Tag, das Ende eines Bereichs, ein Preset, die Uhrzeit, die Clear-Schaltfläche): { value }. Setze value, um ihm zu folgen.
onOpenChange .on_open_change(|e| ..) Das Popup wurde geöffnet oder geschlossen, mit dem Grund ({ open, reason }). Setze open, um ihm zu folgen.
onOpen, onClose .on_open(..), .on_close(..) Das Popup wird angezeigt bzw. ist verschwunden.
onCancel .on_cancel(..) Der Benutzer hat das Popup ohne Auswahl geschlossen (Escape oder Zurück, ein Druck außerhalb oder auf das Backdrop).

#Parts

Part Wo Gestaltet mit
Der Trigger die View des Pickers dem eigenen Element der App (trigger)
Die Clear-Schaltfläche die View des Pickers, nach dem Trigger (cleanable, solange ein Wert vorhanden ist) clearButtonClass, clearButtonStyle; ihr Inhalt clearButton
Das Popup oder Sheet über allem, am Trigger (Desktops) oder am unteren Rand (Touchscreens) popupClass, popupStyle
Das Backdrop hinter dem Sheet (Touchscreens) backdropClass, backdropStyle
Der Inhalt das Popup contentClass, contentStyle
Die Presets der Inhalt, zuerst (presets) presetsClass, presetsStyle; jedes presetClass, presetStyle
Der Kalender der Inhalt calendarClass, calendarStyle und den Parts von Calendar
Die Zeile der Uhrzeit unter dem Kalender (timePrecision) timeClass, timeStyle; ihre Beschriftung timeLabel
Das Zeitfeld die Zeile der Uhrzeit timeFieldClass, timeFieldStyle und den Parts von TimeField

#Tastatur

Taste Aktion
Return (auf dem Trigger) Öffnet das Popup oder schließt es.
Delete, Backspace (auf dem Trigger) Löscht den Wert, solange das Popup geschlossen ist.
Escape Schließt das Popup ohne Auswahl.

Im Popup bewegen die Tasten des Kalenders zwischen den Tagen und wählen einen aus (siehe Tastatur), und die Tasten des Zeitfeldes bearbeiten die Uhrzeit (siehe Tastatur).

#Barrierefreiheit

  • Der Trigger ist für Screenreader eine Combobox: Ihr Label ist accessibilityLabel, ihr Wert das Datum, wie dateFormat es schreibt (standardmäßig 2026/10/28, mit der Uhrzeit bei timePrecision), und sie gibt an, ob das Popup erweitert ist. disabled blendet sie ab.
  • Die Tage des Kalenders sind Schaltflächen, die nach ihren ISO-Daten benannt sind (siehe Barrierefreiheit); die Presets sind Schaltflächen, die nach ihren Beschriftungen benannt sind; das Zeitfeld wird durch timeAccessibilityLabel benannt.
  • Die Clear-Schaltfläche ist eine Schaltfläche, die durch clearButtonAccessibilityLabel benannt wird.

Siehe Barrierefreiheit für Labels, Rollen und Ansagen im Allgemeinen.