@tensor_1/react 2.35.0 → 2.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -15
- package/dist/components/Avatar.d.ts +26 -4
- package/dist/components/Avatar.js +26 -3
- package/dist/components/CommandPalette.d.ts +6 -1
- package/dist/components/CommandPalette.js +9 -3
- package/dist/components/DateFieldParts.d.ts +61 -0
- package/dist/components/DateFieldParts.js +195 -0
- package/dist/components/DatePicker.d.ts +129 -0
- package/dist/components/DatePicker.js +340 -0
- package/dist/components/DateRangePicker.d.ts +34 -3
- package/dist/components/DateRangePicker.js +120 -17
- package/dist/components/EntityPicker.d.ts +29 -4
- package/dist/components/EntityPicker.js +20 -8
- package/dist/components/InlineEdit.d.ts +74 -0
- package/dist/components/InlineEdit.js +176 -0
- package/dist/components/Radio.d.ts +23 -2
- package/dist/components/Radio.js +18 -3
- package/dist/components/SchedulingTimeline.d.ts +130 -15
- package/dist/components/SchedulingTimeline.js +765 -141
- package/dist/components/SearchSuggest.d.ts +12 -1
- package/dist/components/SearchSuggest.js +7 -2
- package/dist/components/Tree.d.ts +30 -2
- package/dist/components/Tree.js +135 -10
- package/dist/date-field-model.d.ts +40 -0
- package/dist/date-field-model.js +79 -0
- package/dist/index.cjs +3854 -2068
- package/dist/index.d.cts +808 -262
- package/dist/index.d.ts +9 -6
- package/dist/index.js +40 -4
- package/dist/labels-context.d.ts +14 -1
- package/dist/scheduling-timeline-model.d.ts +61 -11
- package/dist/scheduling-timeline-model.js +85 -26
- package/dist/scheduling-timeline-rows.d.ts +40 -0
- package/dist/scheduling-timeline-rows.js +42 -0
- package/package.json +5 -3
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { AriaAttributes, CSSProperties } from 'react';
|
|
3
|
+
import { CalendarWeekday } from '../calendar-model.js';
|
|
4
|
+
import { Placement } from '../overlay-position.js';
|
|
5
|
+
|
|
6
|
+
/** Every word `DatePicker` renders or announces. */
|
|
7
|
+
interface DatePickerLabels {
|
|
8
|
+
/** The calendar button's name while no date is set. */
|
|
9
|
+
open: string;
|
|
10
|
+
/** The calendar button's name with a date set; `{date}` is the date in full. */
|
|
11
|
+
openSelected: string;
|
|
12
|
+
/** The calendar popover's name. */
|
|
13
|
+
calendar: string;
|
|
14
|
+
previousMonth: string;
|
|
15
|
+
nextMonth: string;
|
|
16
|
+
/**
|
|
17
|
+
* Shown when the typed text is not a date; `{pattern}` is the expected shape: the
|
|
18
|
+
* `placeholder` when one is given, else the locale's (`DD.MM.YYYY`).
|
|
19
|
+
*/
|
|
20
|
+
invalidDate: string;
|
|
21
|
+
/** The same, when the shape is not known: a custom `parseDate` and no `placeholder`. */
|
|
22
|
+
notADate: string;
|
|
23
|
+
/** Shown when the typed date is outside `min` / `max` or refused by `isUnavailable`. */
|
|
24
|
+
unavailable: string;
|
|
25
|
+
}
|
|
26
|
+
declare const DEFAULT_DATE_PICKER_LABELS: DatePickerLabels;
|
|
27
|
+
/** Why typed text was refused: not a date, or a date that cannot be chosen. */
|
|
28
|
+
type DateInputProblem = 'format' | 'unavailable';
|
|
29
|
+
interface DatePickerProps {
|
|
30
|
+
/** The date as `YYYY-MM-DD`, or `null` for none. */
|
|
31
|
+
value: string | null;
|
|
32
|
+
onValueChange: (value: string | null) => void;
|
|
33
|
+
/** First and last selectable day (`YYYY-MM-DD`). The calendar does not go past them. */
|
|
34
|
+
min?: string;
|
|
35
|
+
max?: string;
|
|
36
|
+
/** Days inside `min` / `max` that cannot be chosen (weekends, a freeze, days already taken). */
|
|
37
|
+
isUnavailable?: (date: string) => boolean;
|
|
38
|
+
/**
|
|
39
|
+
* How the field writes a date. Default: the locale's numeric date (22.11.2026, 11/22/2026).
|
|
40
|
+
* Pass `parseDate` with it when your format is not three numbers, so typing still works.
|
|
41
|
+
*/
|
|
42
|
+
formatDate?: (date: string) => string;
|
|
43
|
+
/** Read typed text into `YYYY-MM-DD`, or `null` when it is not a date. Default: ISO or the locale's order. */
|
|
44
|
+
parseDate?: (text: string) => string | null;
|
|
45
|
+
locale?: string;
|
|
46
|
+
/** First column of the calendar, in `Date.getUTCDay()` numbering (0 = Sunday). Default 1, Monday. */
|
|
47
|
+
weekStartsOn?: CalendarWeekday;
|
|
48
|
+
/**
|
|
49
|
+
* The ISO date that gets the today ring. Defaults to the viewer's local date; pass it when
|
|
50
|
+
* the product works in another zone, or `null` for no today marker.
|
|
51
|
+
*/
|
|
52
|
+
today?: string | null;
|
|
53
|
+
/** Default: the locale's date shape, e.g. `DD.MM.YYYY`. */
|
|
54
|
+
placeholder?: string;
|
|
55
|
+
size?: 'sm' | 'md';
|
|
56
|
+
disabled?: boolean;
|
|
57
|
+
readOnly?: boolean;
|
|
58
|
+
required?: boolean;
|
|
59
|
+
invalid?: boolean;
|
|
60
|
+
/** Submits the ISO value under this name (a hidden input), whatever the field shows. */
|
|
61
|
+
name?: string;
|
|
62
|
+
/** Lands on the text input, so a `<label htmlFor>` or `<Field>` names it. */
|
|
63
|
+
id?: string;
|
|
64
|
+
'aria-label'?: string;
|
|
65
|
+
'aria-labelledby'?: string;
|
|
66
|
+
'aria-describedby'?: string;
|
|
67
|
+
'aria-invalid'?: AriaAttributes['aria-invalid'];
|
|
68
|
+
'aria-required'?: AriaAttributes['aria-required'];
|
|
69
|
+
/** Where the calendar opens. Default `'bottom-start'`; it flips and shifts to stay on screen. */
|
|
70
|
+
placement?: Placement;
|
|
71
|
+
/** Told when typed text is refused, and `null` once the field is fine again. */
|
|
72
|
+
onInputProblem?: (problem: DateInputProblem | null) => void;
|
|
73
|
+
onOpenChange?: (open: boolean) => void;
|
|
74
|
+
/** Translatable words; English defaults in {@link DEFAULT_DATE_PICKER_LABELS}. */
|
|
75
|
+
labels?: Partial<DatePickerLabels>;
|
|
76
|
+
className?: string;
|
|
77
|
+
style?: CSSProperties;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A single date as a form field (HAR-1383): a text input that shows the formatted date and
|
|
81
|
+
* accepts a typed one, and a calendar button that opens one month in a popover. The value is an
|
|
82
|
+
* ISO `YYYY-MM-DD` string. Typed text is read on Enter and when focus leaves the field: ISO or
|
|
83
|
+
* the locale's own order (`3.4.2026` in German, `4/3/2026` in en-US); text that is not a date,
|
|
84
|
+
* or a date that cannot be chosen, marks the field invalid, says why, and leaves `value` alone.
|
|
85
|
+
* ArrowDown in the field opens the calendar; Escape closes it and focus returns to the field.
|
|
86
|
+
*/
|
|
87
|
+
declare const DatePicker: react.ForwardRefExoticComponent<DatePickerProps & react.RefAttributes<HTMLInputElement>>;
|
|
88
|
+
/** The words `DateTimePicker` adds to the date field's own ({@link DatePickerLabels}). */
|
|
89
|
+
interface DateTimePickerLabels extends DatePickerLabels {
|
|
90
|
+
/** Name of the date input when the picker has its own `aria-label`. */
|
|
91
|
+
date: string;
|
|
92
|
+
/** Name of the time input. */
|
|
93
|
+
time: string;
|
|
94
|
+
/** Name of the time input inside a labelled field; `{label}` is the field's label. */
|
|
95
|
+
timeOf: string;
|
|
96
|
+
/** Read after the time; `{zone}` is the zone shown beside it. */
|
|
97
|
+
timeZone: string;
|
|
98
|
+
}
|
|
99
|
+
declare const DEFAULT_DATE_TIME_PICKER_LABELS: DateTimePickerLabels;
|
|
100
|
+
interface DateTimePickerProps extends Omit<DatePickerProps, 'value' | 'onValueChange' | 'labels' | 'min' | 'max'> {
|
|
101
|
+
/** A wall-clock date and time, `YYYY-MM-DDTHH:mm` (what `datetime-local` holds), or `null`. */
|
|
102
|
+
value: string | null;
|
|
103
|
+
onValueChange: (value: string | null) => void;
|
|
104
|
+
/** First and last selectable day; a date-time is read by its date. */
|
|
105
|
+
min?: string;
|
|
106
|
+
max?: string;
|
|
107
|
+
/**
|
|
108
|
+
* The IANA zone the wall clock is in (`Europe/Vienna`). Its short name on the chosen day is
|
|
109
|
+
* shown beside the time. Without it the viewer's own zone is shown. The value itself carries
|
|
110
|
+
* no zone: turning it into an instant is the product's job.
|
|
111
|
+
*/
|
|
112
|
+
timeZone?: string;
|
|
113
|
+
/** Text shown instead of the zone's short name ("Vienna time", "UTC+2"); `null` shows none. */
|
|
114
|
+
timeZoneLabel?: string | null;
|
|
115
|
+
/** Minutes between time steps. Default 1. */
|
|
116
|
+
minuteStep?: number;
|
|
117
|
+
/** The time a date gets when it is chosen before any time. Default `'00:00'`. */
|
|
118
|
+
defaultTime?: string;
|
|
119
|
+
labels?: Partial<DateTimePickerLabels>;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* A date and a time as one field (HAR-1383): the {@link DatePicker} and a time input, with the
|
|
123
|
+
* time zone the pair is in shown beside them. The value is `YYYY-MM-DDTHH:mm`, the shape
|
|
124
|
+
* `datetime-local` uses, so it replaces that input without changing what a form submits.
|
|
125
|
+
* Clearing the date clears the value; a time alone is not a value.
|
|
126
|
+
*/
|
|
127
|
+
declare const DateTimePicker: react.ForwardRefExoticComponent<DateTimePickerProps & react.RefAttributes<HTMLInputElement>>;
|
|
128
|
+
|
|
129
|
+
export { DEFAULT_DATE_PICKER_LABELS, DEFAULT_DATE_TIME_PICKER_LABELS, type DateInputProblem, DatePicker, type DatePickerLabels, type DatePickerProps, DateTimePicker, type DateTimePickerLabels, type DateTimePickerProps };
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef, useEffect, useId, useImperativeHandle, useRef, useState } from "react";
|
|
4
|
+
import { isDateUnavailable } from "../calendar-model.js";
|
|
5
|
+
import { cx } from "../cx.js";
|
|
6
|
+
import { datePattern, formatDateKey, isValidDateKey, joinDateTime, parseDateInput, splitDateTime, timeZoneName } from "../date-field-model.js";
|
|
7
|
+
import { fillLabel } from "../fill-label.js";
|
|
8
|
+
import { CalendarIcon } from "../icons/components.js";
|
|
9
|
+
import { useUixLabels } from "../labels-context.js";
|
|
10
|
+
import { fullDateLabel, localDateKey, MonthCalendar, useFieldPopover } from "./DateFieldParts.js";
|
|
11
|
+
import { Popover } from "./Popover.js";
|
|
12
|
+
const DEFAULT_DATE_PICKER_LABELS = {
|
|
13
|
+
open: "Choose date",
|
|
14
|
+
openSelected: "Change date, {date}",
|
|
15
|
+
calendar: "Calendar",
|
|
16
|
+
previousMonth: "Show previous month",
|
|
17
|
+
nextMonth: "Show next month",
|
|
18
|
+
invalidDate: "Enter a date as {pattern}.",
|
|
19
|
+
notADate: "That is not a date.",
|
|
20
|
+
unavailable: "That date is not available."
|
|
21
|
+
};
|
|
22
|
+
const DatePicker = forwardRef(function DatePicker2({
|
|
23
|
+
value,
|
|
24
|
+
onValueChange,
|
|
25
|
+
min,
|
|
26
|
+
max,
|
|
27
|
+
isUnavailable,
|
|
28
|
+
formatDate,
|
|
29
|
+
parseDate,
|
|
30
|
+
locale,
|
|
31
|
+
weekStartsOn = 1,
|
|
32
|
+
today,
|
|
33
|
+
placeholder,
|
|
34
|
+
size = "md",
|
|
35
|
+
disabled,
|
|
36
|
+
readOnly,
|
|
37
|
+
required,
|
|
38
|
+
invalid,
|
|
39
|
+
name,
|
|
40
|
+
id: idProp,
|
|
41
|
+
"aria-label": ariaLabel,
|
|
42
|
+
"aria-labelledby": ariaLabelledBy,
|
|
43
|
+
"aria-describedby": ariaDescribedBy,
|
|
44
|
+
"aria-invalid": ariaInvalid,
|
|
45
|
+
"aria-required": ariaRequired,
|
|
46
|
+
placement = "bottom-start",
|
|
47
|
+
onInputProblem,
|
|
48
|
+
onOpenChange,
|
|
49
|
+
labels: labelOverrides,
|
|
50
|
+
className,
|
|
51
|
+
style
|
|
52
|
+
}, forwardedRef) {
|
|
53
|
+
const uid = useId();
|
|
54
|
+
const id = idProp ?? `${uid}-input`;
|
|
55
|
+
const popoverId = `${uid}-calendar`;
|
|
56
|
+
const errorId = `${uid}-error`;
|
|
57
|
+
const labels = { ...DEFAULT_DATE_PICKER_LABELS, ...useUixLabels().datePicker, ...labelOverrides };
|
|
58
|
+
const anchorRef = useRef(null);
|
|
59
|
+
const inputRef = useRef(null);
|
|
60
|
+
useImperativeHandle(forwardedRef, () => inputRef.current);
|
|
61
|
+
const format = (date) => formatDate?.(date) ?? formatDateKey(date, locale);
|
|
62
|
+
const [text, setText] = useState(() => value ? format(value) : "");
|
|
63
|
+
const [problem, setProblemState] = useState(null);
|
|
64
|
+
const reported = useRef(null);
|
|
65
|
+
const setProblem = (next) => {
|
|
66
|
+
if (next !== reported.current) {
|
|
67
|
+
reported.current = next;
|
|
68
|
+
onInputProblem?.(next);
|
|
69
|
+
}
|
|
70
|
+
setProblemState(next);
|
|
71
|
+
};
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
setText(value ? format(value) : "");
|
|
74
|
+
setProblem(null);
|
|
75
|
+
}, [value, locale]);
|
|
76
|
+
const { open, show, hide, triggerProps } = useFieldPopover(popoverId, () => inputRef.current?.focus({ preventScroll: true }), onOpenChange);
|
|
77
|
+
const locked = !!disabled || !!readOnly;
|
|
78
|
+
const commit = () => {
|
|
79
|
+
const typed = text.trim();
|
|
80
|
+
if (!typed) {
|
|
81
|
+
setProblem(null);
|
|
82
|
+
if (text) setText("");
|
|
83
|
+
if (value !== null) onValueChange(null);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
if (value && typed === format(value)) {
|
|
87
|
+
setProblem(null);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
const parsed = parseDate ? parseDate(typed) : parseDateInput(typed, locale);
|
|
91
|
+
if (!parsed || !isValidDateKey(parsed)) {
|
|
92
|
+
setProblem("format");
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
if (isDateUnavailable(parsed, { min, max, isDisabled: isUnavailable })) {
|
|
96
|
+
setProblem("unavailable");
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
setProblem(null);
|
|
100
|
+
setText(format(parsed));
|
|
101
|
+
if (parsed !== value) onValueChange(parsed);
|
|
102
|
+
return true;
|
|
103
|
+
};
|
|
104
|
+
const onInputKeyDown = (event) => {
|
|
105
|
+
if (locked) return;
|
|
106
|
+
if (event.key === "ArrowDown") {
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
show();
|
|
109
|
+
} else if (event.key === "Enter" && text.trim() !== (value ? format(value) : "")) {
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
commit();
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const choose = (date) => {
|
|
115
|
+
setProblem(null);
|
|
116
|
+
setText(format(date));
|
|
117
|
+
if (date !== value) onValueChange(date);
|
|
118
|
+
hide(false);
|
|
119
|
+
inputRef.current?.focus({ preventScroll: true });
|
|
120
|
+
};
|
|
121
|
+
const todayDate = today === void 0 ? localDateKey() : today;
|
|
122
|
+
const pattern = datePattern(locale);
|
|
123
|
+
const expected = placeholder ?? (parseDate ? null : pattern);
|
|
124
|
+
const describedBy = [ariaDescribedBy, problem ? errorId : void 0].filter(Boolean).join(" ") || void 0;
|
|
125
|
+
const isInvalid = !!invalid || !!problem || ariaInvalid === true || ariaInvalid === "true";
|
|
126
|
+
return /* @__PURE__ */ jsxs(
|
|
127
|
+
"div",
|
|
128
|
+
{
|
|
129
|
+
ref: anchorRef,
|
|
130
|
+
className: cx("uix-date-picker", size === "sm" && "uix-date-picker--sm", className),
|
|
131
|
+
style,
|
|
132
|
+
onKeyDown: (event) => {
|
|
133
|
+
if (event.key !== "Escape" || !open) return;
|
|
134
|
+
event.stopPropagation();
|
|
135
|
+
event.preventDefault();
|
|
136
|
+
hide();
|
|
137
|
+
},
|
|
138
|
+
children: [
|
|
139
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-date-picker__control", children: [
|
|
140
|
+
/* @__PURE__ */ jsx(
|
|
141
|
+
"input",
|
|
142
|
+
{
|
|
143
|
+
ref: inputRef,
|
|
144
|
+
id,
|
|
145
|
+
type: "text",
|
|
146
|
+
autoComplete: "off",
|
|
147
|
+
spellCheck: false,
|
|
148
|
+
className: cx("uix-input", size === "sm" && "uix-input--sm", "uix-date-picker__input"),
|
|
149
|
+
value: text,
|
|
150
|
+
placeholder: placeholder ?? (formatDate ? void 0 : pattern),
|
|
151
|
+
disabled,
|
|
152
|
+
readOnly,
|
|
153
|
+
required,
|
|
154
|
+
"aria-label": ariaLabel,
|
|
155
|
+
"aria-labelledby": ariaLabelledBy,
|
|
156
|
+
"aria-describedby": describedBy,
|
|
157
|
+
"aria-invalid": isInvalid || void 0,
|
|
158
|
+
"aria-required": ariaRequired,
|
|
159
|
+
onChange: (event) => {
|
|
160
|
+
setText(event.target.value);
|
|
161
|
+
if (problem) setProblem(null);
|
|
162
|
+
},
|
|
163
|
+
onBlur: () => {
|
|
164
|
+
if (!locked) commit();
|
|
165
|
+
},
|
|
166
|
+
onKeyDown: onInputKeyDown
|
|
167
|
+
}
|
|
168
|
+
),
|
|
169
|
+
/* @__PURE__ */ jsx(
|
|
170
|
+
"button",
|
|
171
|
+
{
|
|
172
|
+
type: "button",
|
|
173
|
+
className: "uix-date-picker__toggle",
|
|
174
|
+
disabled: locked,
|
|
175
|
+
"aria-label": value && isValidDateKey(value) ? fillLabel(labels.openSelected, { date: fullDateLabel(value, locale) }) : labels.open,
|
|
176
|
+
...triggerProps,
|
|
177
|
+
children: /* @__PURE__ */ jsx(CalendarIcon, { size: "sm" })
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
] }),
|
|
181
|
+
name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: value ?? "", disabled }),
|
|
182
|
+
problem && /* @__PURE__ */ jsx("span", { id: errorId, className: "uix-field__error uix-date-picker__error", role: "alert", children: problem !== "format" ? labels.unavailable : expected ? fillLabel(labels.invalidDate, { pattern: expected }) : labels.notADate }),
|
|
183
|
+
/* @__PURE__ */ jsx(
|
|
184
|
+
Popover,
|
|
185
|
+
{
|
|
186
|
+
id: popoverId,
|
|
187
|
+
anchor: anchorRef,
|
|
188
|
+
placement,
|
|
189
|
+
className: "uix-date-picker__popover",
|
|
190
|
+
role: "dialog",
|
|
191
|
+
"aria-label": labels.calendar,
|
|
192
|
+
closeWhenAnchorHidden: true,
|
|
193
|
+
children: open && /* @__PURE__ */ jsx(
|
|
194
|
+
MonthCalendar,
|
|
195
|
+
{
|
|
196
|
+
value,
|
|
197
|
+
onSelect: choose,
|
|
198
|
+
initialDate: value && isValidDateKey(value) ? value : todayDate ?? localDateKey(),
|
|
199
|
+
min,
|
|
200
|
+
max,
|
|
201
|
+
isUnavailable,
|
|
202
|
+
locale,
|
|
203
|
+
weekStartsOn,
|
|
204
|
+
today: todayDate,
|
|
205
|
+
labels,
|
|
206
|
+
autoFocus: true
|
|
207
|
+
}
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
)
|
|
211
|
+
]
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
});
|
|
215
|
+
const DEFAULT_DATE_TIME_PICKER_LABELS = {
|
|
216
|
+
...DEFAULT_DATE_PICKER_LABELS,
|
|
217
|
+
date: "Date",
|
|
218
|
+
time: "Time",
|
|
219
|
+
timeOf: "{label}, time",
|
|
220
|
+
timeZone: "Time zone: {zone}"
|
|
221
|
+
};
|
|
222
|
+
const DateTimePicker = forwardRef(function DateTimePicker2({
|
|
223
|
+
value,
|
|
224
|
+
onValueChange,
|
|
225
|
+
timeZone,
|
|
226
|
+
timeZoneLabel,
|
|
227
|
+
minuteStep = 1,
|
|
228
|
+
defaultTime = "00:00",
|
|
229
|
+
min,
|
|
230
|
+
max,
|
|
231
|
+
name,
|
|
232
|
+
id: idProp,
|
|
233
|
+
"aria-label": ariaLabel,
|
|
234
|
+
"aria-labelledby": ariaLabelledBy,
|
|
235
|
+
"aria-describedby": ariaDescribedBy,
|
|
236
|
+
labels: labelOverrides,
|
|
237
|
+
locale,
|
|
238
|
+
size = "md",
|
|
239
|
+
disabled,
|
|
240
|
+
readOnly,
|
|
241
|
+
required,
|
|
242
|
+
className,
|
|
243
|
+
style,
|
|
244
|
+
...dateProps
|
|
245
|
+
}, forwardedRef) {
|
|
246
|
+
const uid = useId();
|
|
247
|
+
const id = idProp ?? `${uid}-date`;
|
|
248
|
+
const zoneId = `${uid}-zone`;
|
|
249
|
+
const labels = { ...DEFAULT_DATE_TIME_PICKER_LABELS, ...useUixLabels().dateTimePicker, ...labelOverrides };
|
|
250
|
+
const { date, time } = splitDateTime(value);
|
|
251
|
+
const dateRef = useRef(null);
|
|
252
|
+
useImperativeHandle(forwardedRef, () => dateRef.current);
|
|
253
|
+
const [pendingTime, setPendingTime] = useState("");
|
|
254
|
+
const shownTime = date ? time : pendingTime;
|
|
255
|
+
const [fieldLabel, setFieldLabel] = useState("");
|
|
256
|
+
const [viewerZone, setViewerZone] = useState(null);
|
|
257
|
+
const grouped = !!ariaLabel || !!ariaLabelledBy;
|
|
258
|
+
useEffect(() => {
|
|
259
|
+
if (!grouped) setFieldLabel(dateRef.current?.labels?.[0]?.textContent?.trim() ?? "");
|
|
260
|
+
if (!timeZone) {
|
|
261
|
+
try {
|
|
262
|
+
setViewerZone(new Intl.DateTimeFormat().resolvedOptions().timeZone);
|
|
263
|
+
} catch {
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}, [grouped, timeZone]);
|
|
267
|
+
const zone = timeZone ?? viewerZone;
|
|
268
|
+
const zoneText = timeZoneLabel === null ? null : timeZoneLabel ?? (zone ? timeZoneName(zone, date, locale) : null);
|
|
269
|
+
const timeName = grouped ? labels.time : fieldLabel ? fillLabel(labels.timeOf, { label: fieldLabel }) : labels.time;
|
|
270
|
+
const step = Math.max(1, Math.round(minuteStep)) * 60;
|
|
271
|
+
return /* @__PURE__ */ jsxs(
|
|
272
|
+
"div",
|
|
273
|
+
{
|
|
274
|
+
className: cx("uix-date-time-picker", size === "sm" && "uix-date-time-picker--sm", className),
|
|
275
|
+
style,
|
|
276
|
+
role: "group",
|
|
277
|
+
"aria-label": ariaLabel,
|
|
278
|
+
"aria-labelledby": ariaLabelledBy,
|
|
279
|
+
children: [
|
|
280
|
+
/* @__PURE__ */ jsx(
|
|
281
|
+
DatePicker,
|
|
282
|
+
{
|
|
283
|
+
...dateProps,
|
|
284
|
+
ref: dateRef,
|
|
285
|
+
id,
|
|
286
|
+
value: date,
|
|
287
|
+
locale,
|
|
288
|
+
size,
|
|
289
|
+
min: min?.slice(0, 10),
|
|
290
|
+
max: max?.slice(0, 10),
|
|
291
|
+
disabled,
|
|
292
|
+
readOnly,
|
|
293
|
+
required,
|
|
294
|
+
"aria-label": grouped ? labels.date : void 0,
|
|
295
|
+
"aria-describedby": ariaDescribedBy,
|
|
296
|
+
labels,
|
|
297
|
+
onValueChange: (next) => {
|
|
298
|
+
if (!next) {
|
|
299
|
+
setPendingTime("");
|
|
300
|
+
onValueChange(null);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
onValueChange(joinDateTime(next, shownTime, defaultTime));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
),
|
|
307
|
+
/* @__PURE__ */ jsx(
|
|
308
|
+
"input",
|
|
309
|
+
{
|
|
310
|
+
type: "time",
|
|
311
|
+
className: cx("uix-input", size === "sm" && "uix-input--sm", "uix-date-time-picker__time"),
|
|
312
|
+
value: shownTime,
|
|
313
|
+
step,
|
|
314
|
+
disabled,
|
|
315
|
+
readOnly,
|
|
316
|
+
required: required && !!date,
|
|
317
|
+
"aria-label": timeName,
|
|
318
|
+
"aria-describedby": zoneText ? zoneId : void 0,
|
|
319
|
+
onChange: (event) => {
|
|
320
|
+
const next = event.target.value;
|
|
321
|
+
if (date) onValueChange(joinDateTime(date, next, defaultTime));
|
|
322
|
+
else setPendingTime(next);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
),
|
|
326
|
+
zoneText && /* @__PURE__ */ jsxs("span", { className: "uix-date-time-picker__zone", children: [
|
|
327
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: zoneText }),
|
|
328
|
+
/* @__PURE__ */ jsx("span", { id: zoneId, className: "uix-visually-hidden", children: fillLabel(labels.timeZone, { zone: zoneText }) })
|
|
329
|
+
] }),
|
|
330
|
+
name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: value ?? "", disabled })
|
|
331
|
+
]
|
|
332
|
+
}
|
|
333
|
+
);
|
|
334
|
+
});
|
|
335
|
+
export {
|
|
336
|
+
DEFAULT_DATE_PICKER_LABELS,
|
|
337
|
+
DEFAULT_DATE_TIME_PICKER_LABELS,
|
|
338
|
+
DatePicker,
|
|
339
|
+
DateTimePicker
|
|
340
|
+
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { AriaAttributes } from 'react';
|
|
3
|
+
import { DateRangeValue, CalendarWeekday } from '../calendar-model.js';
|
|
4
|
+
import { Placement } from '../overlay-position.js';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Every word the picker renders or announces (TENSOR RX-125, UIX-13). `{start}` and
|
|
@@ -16,6 +18,12 @@ interface DateRangePickerLabels {
|
|
|
16
18
|
rangeStart: string;
|
|
17
19
|
rangeEnd: string;
|
|
18
20
|
inRange: string;
|
|
21
|
+
/** Field mode: what the trigger shows while no range is set. */
|
|
22
|
+
placeholder: string;
|
|
23
|
+
/** Field mode: the trigger's text for a start without an end; `{start}`. */
|
|
24
|
+
fieldStart: string;
|
|
25
|
+
/** Field mode: the trigger's text for a range; `{start}`, `{end}`. */
|
|
26
|
+
fieldRange: string;
|
|
19
27
|
}
|
|
20
28
|
declare const DEFAULT_DATE_RANGE_PICKER_LABELS: DateRangePickerLabels;
|
|
21
29
|
interface DateRangePickerProps {
|
|
@@ -41,8 +49,31 @@ interface DateRangePickerProps {
|
|
|
41
49
|
* clock and zone may differ from the viewer's), or `null` for no today marker.
|
|
42
50
|
*/
|
|
43
51
|
today?: string | null;
|
|
52
|
+
/** First column of each month, in `Date.getUTCDay()` numbering (0 = Sunday). Default 1, Monday. */
|
|
53
|
+
weekStartsOn?: CalendarWeekday;
|
|
54
|
+
/**
|
|
55
|
+
* `'inline'` (default) draws the months in the page. `'field'` draws a form control that
|
|
56
|
+
* shows the range and opens the same months in a popover (HAR-1383): for forms and filter
|
|
57
|
+
* bars. It closes once the range has both ends; Escape closes it and returns focus.
|
|
58
|
+
*/
|
|
59
|
+
mode?: 'inline' | 'field';
|
|
60
|
+
/** Field mode: how the trigger writes a date. Default: the locale's numeric date. */
|
|
61
|
+
formatDate?: (date: string) => string;
|
|
62
|
+
/** Field mode: lands on the trigger, so a `<label htmlFor>` or `<Field>` names it. */
|
|
63
|
+
id?: string;
|
|
64
|
+
/** Field mode. */
|
|
65
|
+
size?: 'sm' | 'md';
|
|
66
|
+
'aria-describedby'?: string;
|
|
67
|
+
'aria-invalid'?: AriaAttributes['aria-invalid'];
|
|
68
|
+
/** Field mode: where the popover opens. Default `'bottom-start'`. */
|
|
69
|
+
placement?: Placement;
|
|
70
|
+
/** Field mode. */
|
|
71
|
+
onOpenChange?: (open: boolean) => void;
|
|
44
72
|
}
|
|
45
|
-
/**
|
|
46
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Controlled date-range picker using the UIx calendar language: two months inline, or with
|
|
75
|
+
* `mode="field"` a form control that opens them in a popover.
|
|
76
|
+
*/
|
|
77
|
+
declare function DateRangePicker({ labels: labelOverrides, ...props }: DateRangePickerProps): react.JSX.Element;
|
|
47
78
|
|
|
48
79
|
export { DEFAULT_DATE_RANGE_PICKER_LABELS, DateRangePicker, type DateRangePickerLabels, type DateRangePickerProps };
|