@antadesign/anta 0.3.3 → 0.3.5
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/dist/anta_helpers.d.ts +0 -4
- package/dist/anta_helpers.js +0 -7
- package/dist/components/Calendar.d.ts +9 -3
- package/dist/components/Calendar.js +24 -7
- package/dist/components/InputDate.d.ts +9 -6
- package/dist/components/InputDate.js +83 -143
- package/dist/components/InputDate.module.css +1 -1
- package/dist/components/InputTime.d.ts +105 -0
- package/dist/components/InputTime.js +110 -0
- package/dist/components/Menu.d.ts +15 -1
- package/dist/components/Menu.js +2 -0
- package/dist/components/MenuItem.js +1 -7
- package/dist/components/Select.d.ts +96 -14
- package/dist/components/Select.js +69 -8
- package/dist/components/Select.module.css +1 -1
- package/dist/components/SelectFaceted.d.ts +184 -0
- package/dist/components/SelectFaceted.js +331 -0
- package/dist/components/SelectFaceted.module.css +1 -0
- package/dist/components/TabPanel.d.ts +21 -13
- package/dist/components/TabPanel.js +13 -2
- package/dist/components/Tabs.d.ts +76 -52
- package/dist/components/Tabs.js +18 -86
- package/dist/elements/a-button.css +1 -1
- package/dist/elements/a-calendar.css +1 -1
- package/dist/elements/a-checkbox.css +1 -1
- package/dist/elements/a-input-time.css +1 -0
- package/dist/elements/a-input-time.d.ts +27 -0
- package/dist/elements/a-input-time.js +856 -0
- package/dist/elements/a-input.css +1 -1
- package/dist/elements/a-input.js +8 -1
- package/dist/elements/a-menu-item.css +1 -1
- package/dist/elements/a-menu-item.d.ts +8 -6
- package/dist/elements/a-menu.d.ts +13 -15
- package/dist/elements/a-menu.js +71 -25
- package/dist/elements/a-radio.css +1 -1
- package/dist/elements/a-tab.css +1 -1
- package/dist/elements/a-tabpanel.css +1 -1
- package/dist/elements/a-tabpanel.d.ts +17 -0
- package/dist/elements/a-tabpanel.js +66 -0
- package/dist/elements/a-tabs.css +1 -1
- package/dist/elements/a-tooltip.js +1 -1
- package/dist/elements/index.d.ts +2 -1
- package/dist/elements/index.js +6 -1
- package/dist/general_types.d.ts +83 -11
- package/dist/index.d.ts +7 -5
- package/dist/index.js +6 -3
- package/dist/jsx-runtime.d.ts +2 -1
- package/dist/reset.css +1 -1
- package/dist/tokens.css +1 -1
- package/package.json +1 -1
package/dist/anta_helpers.d.ts
CHANGED
|
@@ -22,10 +22,6 @@ export declare function nativeStateChange<D>(e: CustomEvent<D> | {
|
|
|
22
22
|
event: CustomEvent<D>;
|
|
23
23
|
detail?: D;
|
|
24
24
|
};
|
|
25
|
-
export declare const TABS_KIND: unique symbol;
|
|
26
|
-
export declare function markTabsKind<F>(component: F & {
|
|
27
|
-
[TABS_KIND]?: "tab" | "panel";
|
|
28
|
-
}, kind: "tab" | "panel"): F;
|
|
29
25
|
/** The six named tones every toned component shares. Anything else is a literal
|
|
30
26
|
* CSS colour the element resolves through its `--{component}-tone-source` var. */
|
|
31
27
|
export declare const NAMED_TONES: Set<string>;
|
package/dist/anta_helpers.js
CHANGED
|
@@ -16,11 +16,6 @@ function nativeStateChange(e) {
|
|
|
16
16
|
const event = "nativeEvent" in e ? e.nativeEvent : e;
|
|
17
17
|
return { event, detail: event?.detail };
|
|
18
18
|
}
|
|
19
|
-
const TABS_KIND = Symbol.for("@antadesign/anta/tabs.kind");
|
|
20
|
-
function markTabsKind(component, kind) {
|
|
21
|
-
component[TABS_KIND] = kind;
|
|
22
|
-
return component;
|
|
23
|
-
}
|
|
24
19
|
const NAMED_TONES = /* @__PURE__ */ new Set([
|
|
25
20
|
"brand",
|
|
26
21
|
"neutral",
|
|
@@ -112,12 +107,10 @@ export {
|
|
|
112
107
|
HTMLElementBase,
|
|
113
108
|
NAMED_TONES,
|
|
114
109
|
SelectableChildElement,
|
|
115
|
-
TABS_KIND,
|
|
116
110
|
anchorRect,
|
|
117
111
|
hasChildren,
|
|
118
112
|
isInsideOpenMenu,
|
|
119
113
|
isMenuOpen,
|
|
120
|
-
markTabsKind,
|
|
121
114
|
nativeStateChange,
|
|
122
115
|
roundStyle,
|
|
123
116
|
setMenuPresence,
|
|
@@ -36,6 +36,11 @@ export interface CalendarProps extends Omit<BaseProps, "children" | "onChange">
|
|
|
36
36
|
size?: "small" | "medium" | "large";
|
|
37
37
|
/** Disable the whole calendar (not focusable or selectable). */
|
|
38
38
|
disabled?: boolean;
|
|
39
|
+
/** Move keyboard focus onto the active day. Change this to a new value (e.g.
|
|
40
|
+
* increment a counter) to focus the cursor cell — `InputDate` bumps it when
|
|
41
|
+
* the calendar is opened from the field by keyboard (ArrowDown), so focus
|
|
42
|
+
* lands in the grid. The initial value never focuses; only a change does. */
|
|
43
|
+
focusSignal?: number;
|
|
39
44
|
/** Accessible name for the grid (defaults to the visible month heading). */
|
|
40
45
|
"aria-label"?: string;
|
|
41
46
|
/** Fired whenever the selection changes — event-first. `detail` is
|
|
@@ -56,8 +61,9 @@ export interface CalendarProps extends Omit<BaseProps, "children" | "onChange">
|
|
|
56
61
|
* `<Calendar>` — a single-date month grid you pick a day from, **composed from
|
|
57
62
|
* Anta components in light DOM** (nothing is hidden in a shadow root): days and
|
|
58
63
|
* the prev/next chevrons are `<Button>`s, so they inherit the design system's
|
|
59
|
-
* states for free — a selected day is literally a `
|
|
60
|
-
* `selected` state
|
|
64
|
+
* states for free — a selected day is literally a `tertiary` Button in its
|
|
65
|
+
* `selected` state, toned `brand` so the active date reads in the brand color.
|
|
66
|
+
* All date math runs on the Temporal engine
|
|
61
67
|
* (`@antadesign/anta` exports `buildMonth` & friends), and the value is an ISO
|
|
62
68
|
* `YYYY-MM-DD` string.
|
|
63
69
|
*
|
|
@@ -76,5 +82,5 @@ export interface CalendarProps extends Omit<BaseProps, "children" | "onChange">
|
|
|
76
82
|
* hydration mismatch. Render it client-side only (Astro `client:only`, or a
|
|
77
83
|
* dynamic import inside `useEffect`).
|
|
78
84
|
*/
|
|
79
|
-
export declare const Calendar: ({ value, defaultValue, min, max, locale, name, size, disabled, onStateChange, onChange, onValueChange, className, style, "aria-label": ariaLabel, ...rest }: CalendarProps) => any;
|
|
85
|
+
export declare const Calendar: ({ value, defaultValue, min, max, locale, name, size, disabled, focusSignal, onStateChange, onChange, onValueChange, className, style, "aria-label": ariaLabel, ...rest }: CalendarProps) => any;
|
|
80
86
|
export {};
|
|
@@ -17,6 +17,7 @@ const Calendar = ({
|
|
|
17
17
|
name,
|
|
18
18
|
size,
|
|
19
19
|
disabled,
|
|
20
|
+
focusSignal,
|
|
20
21
|
onStateChange,
|
|
21
22
|
onChange,
|
|
22
23
|
onValueChange,
|
|
@@ -37,11 +38,26 @@ const Calendar = ({
|
|
|
37
38
|
const [cursor, setCursor] = useState(
|
|
38
39
|
() => clampDate(parseISODate(value ?? defaultValue) ?? today, minD, maxD)
|
|
39
40
|
);
|
|
41
|
+
const [lastSyncedValue, setLastSyncedValue] = useState(value);
|
|
42
|
+
let effectiveCursor = cursor;
|
|
43
|
+
if (value !== lastSyncedValue) {
|
|
44
|
+
setLastSyncedValue(value);
|
|
45
|
+
const d = parseISODate(value);
|
|
46
|
+
if (d) {
|
|
47
|
+
effectiveCursor = clampDate(d, minD, maxD);
|
|
48
|
+
setCursor(effectiveCursor);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
40
51
|
const [focusReq, setFocusReq] = useState(null);
|
|
41
52
|
const [jumpOpen, setJumpOpen] = useState(false);
|
|
42
53
|
const headingId = useId();
|
|
43
|
-
const month = buildMonth({ anchor:
|
|
44
|
-
const cursorIso =
|
|
54
|
+
const month = buildMonth({ anchor: effectiveCursor, locale: resolvedLocale, min: minD, max: maxD, selected, today });
|
|
55
|
+
const cursorIso = effectiveCursor.toString();
|
|
56
|
+
const [lastFocusSignal, setLastFocusSignal] = useState(focusSignal);
|
|
57
|
+
if (focusSignal !== lastFocusSignal) {
|
|
58
|
+
setLastFocusSignal(focusSignal);
|
|
59
|
+
if (focusSignal !== void 0) setFocusReq({ iso: cursorIso, n: focusSignal });
|
|
60
|
+
}
|
|
45
61
|
const minYear = minD ? minD.year : today.year - 3;
|
|
46
62
|
const maxYear = maxD ? maxD.year : today.year + 3;
|
|
47
63
|
const years = [];
|
|
@@ -67,24 +83,25 @@ const Calendar = ({
|
|
|
67
83
|
const jumpItems = useMemo(() => {
|
|
68
84
|
const monthItem = (y, i) => {
|
|
69
85
|
const m = i + 1;
|
|
86
|
+
const isCurrent = y === cursor.year && m === cursor.month;
|
|
70
87
|
return /* @__PURE__ */ jsx(
|
|
71
88
|
MenuItem,
|
|
72
89
|
{
|
|
73
90
|
label: monthNames[i],
|
|
74
|
-
|
|
75
|
-
selected: y === cursor.year && m === cursor.month,
|
|
91
|
+
selected: isCurrent,
|
|
76
92
|
disabled: monthOutOfRange(y, m) || void 0,
|
|
77
93
|
"data-menu-open": "",
|
|
78
94
|
onSelect: () => {
|
|
79
95
|
pickMonth(y, m);
|
|
80
96
|
setJumpOpen(false);
|
|
81
|
-
}
|
|
97
|
+
},
|
|
98
|
+
children: isCurrent && /* @__PURE__ */ jsx("span", { className: "jump-dot", "aria-hidden": "true" })
|
|
82
99
|
},
|
|
83
100
|
`${y}-${m}`
|
|
84
101
|
);
|
|
85
102
|
};
|
|
86
103
|
return years.length === 1 ? monthNames.map((_, i) => monthItem(years[0], i)) : years.map((y) => /* @__PURE__ */ jsxs(MenuItem, { submenu: true, label: String(y), selected: y === cursor.year, children: [
|
|
87
|
-
y === cursor.year && /* @__PURE__ */ jsx("span", { className: "
|
|
104
|
+
y === cursor.year && /* @__PURE__ */ jsx("span", { className: "jump-dot", "aria-hidden": "true" }),
|
|
88
105
|
/* @__PURE__ */ jsx(Menu, { children: monthNames.map((_, i) => monthItem(y, i)) })
|
|
89
106
|
] }, y));
|
|
90
107
|
}, [monthNames, cursor.year, cursor.month, minYear, maxYear, min, max, disabled]);
|
|
@@ -186,7 +203,7 @@ const Calendar = ({
|
|
|
186
203
|
`wd${i}`
|
|
187
204
|
)),
|
|
188
205
|
month.weeks.flat().map((d) => {
|
|
189
|
-
const variant = d.today && !d.selected ? { priority: "secondary" } : { priority: "tertiary", selected: d.selected };
|
|
206
|
+
const variant = d.today && !d.selected ? { priority: "secondary" } : { priority: "tertiary", selected: d.selected, tone: d.selected ? "brand" : void 0 };
|
|
190
207
|
return /* @__PURE__ */ jsxs(
|
|
191
208
|
Button,
|
|
192
209
|
{
|
|
@@ -45,8 +45,10 @@ export interface InputDateProps extends Omit<BaseProps, 'children'> {
|
|
|
45
45
|
round?: boolean | number | string;
|
|
46
46
|
/** Show a clear button once the field has a value. */
|
|
47
47
|
clearable?: boolean;
|
|
48
|
-
/** Leading icon at the start of the field.
|
|
49
|
-
|
|
48
|
+
/** Leading icon at the start of the field — the calendar affordance. Pass
|
|
49
|
+
* another shape to change it, or `false` to drop it.
|
|
50
|
+
* @defaultValue calendar-days */
|
|
51
|
+
icon?: IconShape | false;
|
|
50
52
|
/** Include a time. The value becomes ISO `YYYY-MM-DDTHH:mm`, the field parses a
|
|
51
53
|
* trailing time after a space (`06/15/2026 14:30`, `… 2:30pm`), and the menu
|
|
52
54
|
* shows a time row (hours : minutes, an AM/PM toggle in 12-hour locales, then a
|
|
@@ -67,10 +69,11 @@ export interface InputDateProps extends Omit<BaseProps, 'children'> {
|
|
|
67
69
|
* `Menu` + `Calendar` (no `a-inputdate` element; the wrapper is the coordinator).
|
|
68
70
|
* The field accepts free text and resolves it on commit (blur / Enter) with a
|
|
69
71
|
* lenient, locale-aware parser, then rewrites the entry to the canonical format;
|
|
70
|
-
* an unrecognized entry marks the field `critical` and keeps the text.
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
72
|
+
* an unrecognized entry marks the field `critical` and keeps the text. Clicking the
|
|
73
|
+
* field (or pressing ArrowDown) opens a `Calendar` in a menu; a leading calendar
|
|
74
|
+
* icon marks the affordance. Mouse-open keeps focus in the field so you can keep
|
|
75
|
+
* typing; ArrowDown moves focus into the grid. With `time`, a time row sits under
|
|
76
|
+
* the grid. The value is an ISO `YYYY-MM-DD` string, or `YYYY-MM-DDTHH:mm` with `time`.
|
|
74
77
|
*
|
|
75
78
|
* Controlled (`value` + `onValueChange`) or uncontrolled (`defaultValue`).
|
|
76
79
|
* Requires `@antadesign/anta/elements` (client-side only).
|
|
@@ -17,7 +17,7 @@ import { Menu } from "./Menu";
|
|
|
17
17
|
import { Calendar } from "./Calendar";
|
|
18
18
|
import { Button } from "./Button";
|
|
19
19
|
import { Icon } from "./Icon";
|
|
20
|
-
import {
|
|
20
|
+
import { InputTime } from "./InputTime";
|
|
21
21
|
import styles from "./InputDate.module.css";
|
|
22
22
|
const InputDate = ({
|
|
23
23
|
value,
|
|
@@ -67,46 +67,21 @@ const InputDate = ({
|
|
|
67
67
|
const hasTime = !!current && current.length >= 16 && current[10] === "T";
|
|
68
68
|
const h24 = hasTime ? parseInt(current.slice(11, 13), 10) || 0 : 0;
|
|
69
69
|
const curM = hasTime ? current.slice(14, 16) : "00";
|
|
70
|
-
const curMer = h24 < 12 ? "AM" : "PM";
|
|
71
|
-
const curHourShown = twelveHour ? String((h24 + 11) % 12 + 1).padStart(2, "0") : String(h24).padStart(2, "0");
|
|
72
70
|
const [text, setText] = useState(() => fmt(current));
|
|
73
71
|
const [open, setOpen] = useState(false);
|
|
74
72
|
const [invalid, setInvalid] = useState(false);
|
|
75
|
-
const [
|
|
76
|
-
const [minuteText, setMinuteText] = useState(curM);
|
|
77
|
-
const meridiem = curMer;
|
|
73
|
+
const [focusNonce, setFocusNonce] = useState(0);
|
|
78
74
|
const [lastValue, setLastValue] = useState(current);
|
|
79
75
|
if (current !== lastValue) {
|
|
80
76
|
setLastValue(current);
|
|
81
77
|
setText(fmt(current));
|
|
82
78
|
setInvalid(false);
|
|
83
79
|
}
|
|
84
|
-
const [lastTime, setLastTime] = useState(`${h24}:${curM}`);
|
|
85
|
-
if (time && `${h24}:${curM}` !== lastTime) {
|
|
86
|
-
setLastTime(`${h24}:${curM}`);
|
|
87
|
-
setHourText(curHourShown);
|
|
88
|
-
setMinuteText(curM);
|
|
89
|
-
}
|
|
90
80
|
const commit = (iso) => {
|
|
91
81
|
if (!controlled) setInternal(iso || void 0);
|
|
92
82
|
onValueChange?.(iso, { value: iso, name });
|
|
93
83
|
};
|
|
94
|
-
const
|
|
95
|
-
const resolveHour = (raw, mer) => {
|
|
96
|
-
let n = parseInt(String(raw || "0"), 10) || 0;
|
|
97
|
-
if (!twelveHour) {
|
|
98
|
-
const h = Math.min(Math.max(n, 0), 23);
|
|
99
|
-
return { h24: h, shown: String(h).padStart(2, "0"), mer };
|
|
100
|
-
}
|
|
101
|
-
if (n === 0) return { h24: 0, shown: "12", mer: "AM" };
|
|
102
|
-
if (n > 23) n = 23;
|
|
103
|
-
if (n > 12) return { h24: n, shown: String(n - 12).padStart(2, "0"), mer: "PM" };
|
|
104
|
-
return { h24: mer === "AM" ? n % 12 : n % 12 + 12, shown: String(n).padStart(2, "0"), mer };
|
|
105
|
-
};
|
|
106
|
-
const withTime = (d) => {
|
|
107
|
-
const { h24: h } = resolveHour(hourText, meridiem);
|
|
108
|
-
return `${d}T${String(h).padStart(2, "0")}:${clampPad(minuteText, 59)}`;
|
|
109
|
-
};
|
|
84
|
+
const withTime = (d) => `${d}T${String(h24).padStart(2, "0")}:${curM}`;
|
|
110
85
|
const apply = (iso) => {
|
|
111
86
|
setInvalid(false);
|
|
112
87
|
if (iso === (current ?? "")) setText(fmt(iso));
|
|
@@ -118,24 +93,25 @@ const InputDate = ({
|
|
|
118
93
|
setInvalid(false);
|
|
119
94
|
if (current) commit("");
|
|
120
95
|
else setText("");
|
|
121
|
-
return;
|
|
96
|
+
return true;
|
|
122
97
|
}
|
|
123
98
|
const parsed = time ? parseDateTimeInput(t, resolvedLocale, { min: minD, max: maxD }) : parseDateInput(t, resolvedLocale, { min: minD, max: maxD });
|
|
124
99
|
if (!parsed) {
|
|
125
100
|
setInvalid(true);
|
|
126
|
-
return;
|
|
101
|
+
return false;
|
|
127
102
|
}
|
|
128
103
|
const iso = time ? parsed.toString({ smallestUnit: "minute" }) : parsed.toString();
|
|
129
104
|
apply(iso);
|
|
105
|
+
return true;
|
|
130
106
|
};
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
107
|
+
const parsedPreview = useMemo(() => {
|
|
108
|
+
const t = text.trim();
|
|
109
|
+
if (!t) return dateISO;
|
|
110
|
+
const parsed = time ? parseDateTimeInput(t, resolvedLocale, { min: minD, max: maxD }) : parseDateInput(t, resolvedLocale, { min: minD, max: maxD });
|
|
111
|
+
return parsed ? parsed.toString().slice(0, 10) : null;
|
|
112
|
+
}, [text, time, resolvedLocale, dateISO, min, max]);
|
|
113
|
+
const [previewISO, setPreviewISO] = useState(dateISO);
|
|
114
|
+
if (parsedPreview !== null && parsedPreview !== previewISO) setPreviewISO(parsedPreview);
|
|
139
115
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
140
116
|
/* @__PURE__ */ jsx(
|
|
141
117
|
Input,
|
|
@@ -147,127 +123,91 @@ const InputDate = ({
|
|
|
147
123
|
round,
|
|
148
124
|
disabled,
|
|
149
125
|
clearable,
|
|
150
|
-
leading: icon ? /* @__PURE__ */ jsx(Icon, { shape: icon })
|
|
126
|
+
leading: icon === false ? void 0 : /* @__PURE__ */ jsx(Icon, { shape: icon ?? "calendar-days" }),
|
|
151
127
|
value: text,
|
|
152
128
|
placeholder: pattern,
|
|
153
129
|
inputMode: time ? "text" : "numeric",
|
|
154
130
|
autoComplete: "off",
|
|
131
|
+
"aria-haspopup": "dialog",
|
|
132
|
+
"aria-expanded": open ? "true" : "false",
|
|
155
133
|
onInput: (e) => {
|
|
156
134
|
setText(e.currentTarget.value);
|
|
157
135
|
if (invalid) setInvalid(false);
|
|
158
136
|
},
|
|
159
|
-
onChange: (e) =>
|
|
160
|
-
|
|
161
|
-
|
|
137
|
+
onChange: (e) => resolve(e.currentTarget.value),
|
|
138
|
+
onKeyDown: (e) => {
|
|
139
|
+
if (e.key === "ArrowDown" && !disabled) {
|
|
140
|
+
e.preventDefault();
|
|
141
|
+
if (!open) e.currentTarget.click();
|
|
142
|
+
setFocusNonce((n) => n + 1);
|
|
143
|
+
}
|
|
144
|
+
if (e.key === "Enter" && !disabled) {
|
|
145
|
+
e.preventDefault();
|
|
146
|
+
if (resolve(e.currentTarget.value)) setOpen(false);
|
|
147
|
+
}
|
|
162
148
|
},
|
|
163
149
|
className: cn(styles.dateField, className),
|
|
164
150
|
style,
|
|
165
|
-
|
|
151
|
+
...rest
|
|
152
|
+
}
|
|
153
|
+
),
|
|
154
|
+
/* @__PURE__ */ jsx(
|
|
155
|
+
Menu,
|
|
156
|
+
{
|
|
157
|
+
open,
|
|
158
|
+
placement: "bottom-start",
|
|
159
|
+
autoWidth: true,
|
|
160
|
+
className: styles.calendarMenu,
|
|
161
|
+
onStateChange: (_e, { next }) => setOpen(next),
|
|
162
|
+
children: /* @__PURE__ */ jsxs("div", { "data-menu-open": "", children: [
|
|
166
163
|
/* @__PURE__ */ jsx(
|
|
167
|
-
|
|
164
|
+
Calendar,
|
|
168
165
|
{
|
|
169
|
-
|
|
166
|
+
value: previewISO,
|
|
167
|
+
min,
|
|
168
|
+
max,
|
|
169
|
+
locale,
|
|
170
170
|
size,
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
171
|
+
disabled,
|
|
172
|
+
focusSignal: focusNonce,
|
|
173
|
+
onStateChange: (_e, { next, reason }) => {
|
|
174
|
+
if (reason !== "user") return;
|
|
175
|
+
const d = next ?? "";
|
|
176
|
+
apply(time && d ? withTime(d) : d);
|
|
177
|
+
if (!time) setOpen(false);
|
|
178
|
+
}
|
|
175
179
|
}
|
|
176
180
|
),
|
|
177
|
-
/* @__PURE__ */
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
{
|
|
208
|
-
size,
|
|
209
|
-
className: styles.timeField,
|
|
210
|
-
value: hourText,
|
|
211
|
-
placeholder: "HH",
|
|
212
|
-
type: "number",
|
|
213
|
-
inputMode: "numeric",
|
|
214
|
-
"aria-label": "Hours",
|
|
215
|
-
disabled,
|
|
216
|
-
onInput: (e) => setHourText(e.currentTarget.value.replace(/\D/g, "").slice(0, 2)),
|
|
217
|
-
onChange: (e) => commitTime(e.currentTarget.value, minuteText, meridiem)
|
|
218
|
-
}
|
|
219
|
-
),
|
|
220
|
-
/* @__PURE__ */ jsx("span", { className: styles.sep, "aria-hidden": "true", children: ":" }),
|
|
221
|
-
/* @__PURE__ */ jsx(
|
|
222
|
-
Input,
|
|
223
|
-
{
|
|
224
|
-
size,
|
|
225
|
-
className: styles.timeField,
|
|
226
|
-
value: minuteText,
|
|
227
|
-
placeholder: "MM",
|
|
228
|
-
type: "number",
|
|
229
|
-
inputMode: "numeric",
|
|
230
|
-
"aria-label": "Minutes",
|
|
231
|
-
disabled,
|
|
232
|
-
onInput: (e) => setMinuteText(e.currentTarget.value.replace(/\D/g, "").slice(0, 2)),
|
|
233
|
-
onChange: (e) => commitTime(hourText, e.currentTarget.value, meridiem)
|
|
234
|
-
}
|
|
235
|
-
),
|
|
236
|
-
twelveHour && /* @__PURE__ */ jsx(
|
|
237
|
-
Tabs,
|
|
238
|
-
{
|
|
239
|
-
className: styles.meridiem,
|
|
240
|
-
size,
|
|
241
|
-
priority: "primary",
|
|
242
|
-
"aria-label": "AM or PM",
|
|
243
|
-
options: [
|
|
244
|
-
{ value: "AM", label: "AM" },
|
|
245
|
-
{ value: "PM", label: "PM" }
|
|
246
|
-
],
|
|
247
|
-
value: meridiem,
|
|
248
|
-
onStateChange: (_e, { next }) => {
|
|
249
|
-
if (next === "AM" || next === "PM") commitTime(hourText, minuteText, next);
|
|
250
|
-
},
|
|
251
|
-
disabled
|
|
252
|
-
}
|
|
253
|
-
)
|
|
254
|
-
] }),
|
|
255
|
-
/* @__PURE__ */ jsx(
|
|
256
|
-
Button,
|
|
257
|
-
{
|
|
258
|
-
priority: "tertiary",
|
|
259
|
-
size,
|
|
260
|
-
icon: "check",
|
|
261
|
-
"aria-label": "Done",
|
|
262
|
-
onClick: () => setOpen(false)
|
|
263
|
-
}
|
|
264
|
-
)
|
|
265
|
-
] })
|
|
266
|
-
] })
|
|
267
|
-
}
|
|
268
|
-
)
|
|
269
|
-
] }),
|
|
270
|
-
...rest
|
|
181
|
+
time && /* @__PURE__ */ jsxs("div", { className: styles.timeRow, children: [
|
|
182
|
+
/* @__PURE__ */ jsx(
|
|
183
|
+
InputTime,
|
|
184
|
+
{
|
|
185
|
+
size,
|
|
186
|
+
locale,
|
|
187
|
+
hour12,
|
|
188
|
+
disabled,
|
|
189
|
+
"aria-label": "Time",
|
|
190
|
+
value: hasTime ? `${String(h24).padStart(2, "0")}:${curM}` : "",
|
|
191
|
+
onValueChange: (e, { value: t }) => {
|
|
192
|
+
const ev = "nativeEvent" in e ? e.nativeEvent : e;
|
|
193
|
+
if (ev?.type !== "change") return;
|
|
194
|
+
const base = dateISO || Temporal.Now.plainDateISO().toString();
|
|
195
|
+
commit(t ? `${base}T${t}` : base);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
),
|
|
199
|
+
/* @__PURE__ */ jsx(
|
|
200
|
+
Button,
|
|
201
|
+
{
|
|
202
|
+
priority: "tertiary",
|
|
203
|
+
size,
|
|
204
|
+
icon: "check",
|
|
205
|
+
"aria-label": "Done",
|
|
206
|
+
onClick: () => setOpen(false)
|
|
207
|
+
}
|
|
208
|
+
)
|
|
209
|
+
] })
|
|
210
|
+
] })
|
|
271
211
|
}
|
|
272
212
|
),
|
|
273
213
|
name ? /* @__PURE__ */ jsx("input", { type: "hidden", name, value: current ?? "" }) : null
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.calendarMenu{--menu-padding: 8px}.timeRow{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:6px;padding-top:8px;border-top:1px solid var(--border-3)}.
|
|
1
|
+
.calendarMenu{--menu-padding: 8px}.timeRow{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:6px;padding-top:8px;border-top:1px solid var(--border-3)}.dateField::part(input){text-overflow:ellipsis}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { BaseProps, DOMEventHandlers } from '../general_types';
|
|
2
|
+
import type { IconShape } from '../elements/a-icon.shapes';
|
|
3
|
+
/** Convenience snapshot passed as the 2nd argument to `onValueChange`. */
|
|
4
|
+
export interface InputTimeChangeAttrs {
|
|
5
|
+
/** Current value — 24-hour `"HH:mm"`, `''` when incomplete. */
|
|
6
|
+
value: string;
|
|
7
|
+
/** The field's `name`, for keyed updates: `s => ({ ...s, [name]: value })`. */
|
|
8
|
+
name?: string;
|
|
9
|
+
/** `true` when the value is empty / incomplete. */
|
|
10
|
+
empty: boolean;
|
|
11
|
+
/** Whether the field currently passes validation. `undefined` where
|
|
12
|
+
* `ElementInternals` is unsupported. */
|
|
13
|
+
valid?: boolean;
|
|
14
|
+
/** Current validation message (`''` when valid). */
|
|
15
|
+
validationMessage: string;
|
|
16
|
+
}
|
|
17
|
+
export interface InputTimeProps extends BaseProps, DOMEventHandlers {
|
|
18
|
+
/** Extra content rendered under the field, above the hint (a no-box child like
|
|
19
|
+
* a `<Tooltip>` takes no space and just anchors to the field). */
|
|
20
|
+
children?: React.ReactNode;
|
|
21
|
+
/** Field label, shown above the control and used as the segment group's
|
|
22
|
+
* accessible name. */
|
|
23
|
+
label?: React.ReactNode;
|
|
24
|
+
/** Message below the field. `status` recolors it and prefixes a glyph. */
|
|
25
|
+
hint?: React.ReactNode;
|
|
26
|
+
/** Validation / feedback tone. Only `critical` marks the field invalid; the
|
|
27
|
+
* others are advisory. Omit (or `neutral`) for a plain field. */
|
|
28
|
+
status?: 'neutral' | 'brand' | 'info' | 'success' | 'warning' | 'critical';
|
|
29
|
+
/** Glyph before the `hint` when `status` is set (per-status default; pass a
|
|
30
|
+
* shape to override, or `false` to drop it). */
|
|
31
|
+
statusIcon?: IconShape | (string & {}) | false;
|
|
32
|
+
/** Custom accent colour — any literal CSS colour tints the resting + hover
|
|
33
|
+
* border (focus ring stays `--focus-ring`); `status` overrides for validation. */
|
|
34
|
+
tone?: string;
|
|
35
|
+
/** Size variant. small=24px, medium=28px, large=32px tall.
|
|
36
|
+
* @defaultValue medium */
|
|
37
|
+
size?: 'small' | 'medium' | 'large';
|
|
38
|
+
/** Controlled value — 24-hour `"HH:mm"`. Pair with `onValueChange`. */
|
|
39
|
+
value?: string;
|
|
40
|
+
/** Initial value for the uncontrolled case (24-hour `"HH:mm"`). */
|
|
41
|
+
defaultValue?: string;
|
|
42
|
+
/** BCP-47 locale driving the clock (12h vs 24h), segment order, separator, and
|
|
43
|
+
* the AM/PM text.
|
|
44
|
+
* @defaultValue navigator.language */
|
|
45
|
+
locale?: string;
|
|
46
|
+
/** Force the clock: `true` = 12-hour (AM/PM), `false` = 24-hour. Omit to follow
|
|
47
|
+
* the locale. */
|
|
48
|
+
hour12?: boolean;
|
|
49
|
+
/** Earliest allowed time, 24-hour `"HH:mm"`. A complete value below it is
|
|
50
|
+
* clamped up (on step / blur) and flagged `rangeUnderflow` for form validity. */
|
|
51
|
+
min?: string;
|
|
52
|
+
/** Latest allowed time, 24-hour `"HH:mm"`. A complete value above it is clamped
|
|
53
|
+
* down (on step / blur) and flagged `rangeOverflow`. */
|
|
54
|
+
max?: string;
|
|
55
|
+
/** Form field name — the 24-hour value submits under this key. */
|
|
56
|
+
name?: string;
|
|
57
|
+
/** Disable the field. */
|
|
58
|
+
disabled?: boolean;
|
|
59
|
+
/** Mark the field required (drives native validity). */
|
|
60
|
+
required?: boolean;
|
|
61
|
+
/** Leading icon at the start of the field — the clock affordance. Pass another
|
|
62
|
+
* shape to change it, or `false` to drop it.
|
|
63
|
+
* @defaultValue clock */
|
|
64
|
+
icon?: IconShape | false;
|
|
65
|
+
/** Show a clear button once the field has a value. */
|
|
66
|
+
clearable?: boolean;
|
|
67
|
+
/** Dim the trailing adornments at rest; they brighten on hover / focus. */
|
|
68
|
+
dimActions?: boolean;
|
|
69
|
+
/** Content pinned to the end of the field (after the clear button). */
|
|
70
|
+
trailing?: React.ReactNode;
|
|
71
|
+
/** Fully-round the field, or a custom radius (`number` px / CSS length). */
|
|
72
|
+
round?: boolean | number | string;
|
|
73
|
+
/** Fires on every edit (`input`), with the native event + an `attrs` snapshot
|
|
74
|
+
* (`value`, `name`, `empty`, `valid`, `validationMessage`). Also fires on
|
|
75
|
+
* `change` (blur) and on clear. */
|
|
76
|
+
onValueChange?: (event: any, attrs: InputTimeChangeAttrs) => void;
|
|
77
|
+
/** Fires after the built-in clear button has cleared the field. */
|
|
78
|
+
onClearInput?: (e: CustomEvent) => void;
|
|
79
|
+
/** Fires when the field gains focus. */
|
|
80
|
+
onFocus?: (e: any) => void;
|
|
81
|
+
/** Fires when the field loses focus. */
|
|
82
|
+
onBlur?: (e: any) => void;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* `<InputTime>` — a segmented wall-clock time field. One boxed input (matching
|
|
86
|
+
* the other inputs) with separate hour / minute / (12-hour) AM-PM sections that
|
|
87
|
+
* behave as one control: each is a `role="spinbutton"` — ↑/↓ steps with wrap,
|
|
88
|
+
* ←/→ moves between them, typing digits auto-advances, and AM/PM toggles with
|
|
89
|
+
* ↑/↓ or `a`/`p`. Renders an `<a-input-time>` web component that owns the
|
|
90
|
+
* segments, keyboard, and form value (via `ElementInternals`).
|
|
91
|
+
*
|
|
92
|
+
* The clock and layout follow the locale (`en-US` → 12-hour, most others 24),
|
|
93
|
+
* overridable with `hour12`. The value is a 24-hour `"HH:mm"` string; controlled
|
|
94
|
+
* (`value` + `onValueChange`) or uncontrolled (`defaultValue`), submitting under
|
|
95
|
+
* `name`.
|
|
96
|
+
*
|
|
97
|
+
* Requires `@antadesign/anta/elements` (client-side only). Reads
|
|
98
|
+
* `navigator.language` at render, so render client-side (not SSR-safe).
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```tsx
|
|
102
|
+
* <InputTime label="Start" defaultValue="09:30" onValueChange={(_, { value }) => save(value)} />
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export declare const InputTime: ({ label, hint, status, statusIcon, tone, size, round, value, defaultValue, locale, hour12, min, max, name, disabled, required, icon, clearable, dimActions, trailing, onValueChange, onClearInput, children, className, style, ...rest }: InputTimeProps) => any;
|