@devslab/dds-react 0.24.0 → 0.25.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.
Files changed (62) hide show
  1. package/README.ko.md +3 -2
  2. package/README.md +3 -2
  3. package/dist/calendar-view-BxjCQV9k.js +119 -0
  4. package/dist/calendar-view-BxjCQV9k.js.map +1 -0
  5. package/dist/calendar-view.d.ts +11 -1
  6. package/dist/calendar-view.d.ts.map +1 -1
  7. package/dist/calendar.js +5 -4
  8. package/dist/calendar.js.map +1 -1
  9. package/dist/choice-N-5K_ggs.js +309 -0
  10. package/dist/choice-N-5K_ggs.js.map +1 -0
  11. package/dist/choice-types-kSaEnZPr.js +23 -0
  12. package/dist/choice-types-kSaEnZPr.js.map +1 -0
  13. package/dist/combobox.js +1 -1
  14. package/dist/date-only-Bu4UmsTP.js +151 -0
  15. package/dist/date-only-Bu4UmsTP.js.map +1 -0
  16. package/dist/date-only.d.ts +92 -0
  17. package/dist/date-only.d.ts.map +1 -1
  18. package/dist/date-picker.js +69 -68
  19. package/dist/date-picker.js.map +1 -1
  20. package/dist/date-range-picker.d.ts +41 -0
  21. package/dist/date-range-picker.d.ts.map +1 -0
  22. package/dist/date-range-picker.js +327 -0
  23. package/dist/date-range-picker.js.map +1 -0
  24. package/dist/direction-DbZcVPsZ.js +21 -0
  25. package/dist/direction-DbZcVPsZ.js.map +1 -0
  26. package/dist/direction.d.ts +5 -0
  27. package/dist/direction.d.ts.map +1 -0
  28. package/dist/index.d.ts +5 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +108 -103
  31. package/dist/index.js.map +1 -1
  32. package/dist/input.d.ts.map +1 -1
  33. package/dist/input.js +1 -1
  34. package/dist/input.js.map +1 -1
  35. package/dist/internal/choice.d.ts +27 -3
  36. package/dist/internal/choice.d.ts.map +1 -1
  37. package/dist/month-picker.d.ts +33 -0
  38. package/dist/month-picker.d.ts.map +1 -0
  39. package/dist/month-picker.js +242 -0
  40. package/dist/month-picker.js.map +1 -0
  41. package/dist/number-input.d.ts +53 -0
  42. package/dist/number-input.d.ts.map +1 -0
  43. package/dist/number-input.js +254 -0
  44. package/dist/number-input.js.map +1 -0
  45. package/dist/segmented-control.d.ts +40 -0
  46. package/dist/segmented-control.d.ts.map +1 -0
  47. package/dist/segmented-control.js +73 -0
  48. package/dist/segmented-control.js.map +1 -0
  49. package/dist/select-picker.js +1 -1
  50. package/dist/time-only.d.ts +32 -0
  51. package/dist/time-only.d.ts.map +1 -0
  52. package/dist/time-picker-BQ7BT6-u.js +100 -0
  53. package/dist/time-picker-BQ7BT6-u.js.map +1 -0
  54. package/dist/time-picker.d.ts +34 -0
  55. package/dist/time-picker.d.ts.map +1 -0
  56. package/dist/time-picker.js +2 -0
  57. package/package.json +29 -4
  58. package/styles.css +31 -1
  59. package/dist/calendar-view-I_sNlioP.js +0 -145
  60. package/dist/calendar-view-I_sNlioP.js.map +0 -1
  61. package/dist/choice-B3vNMfKK.js +0 -300
  62. package/dist/choice-B3vNMfKK.js.map +0 -1
@@ -0,0 +1,254 @@
1
+ import { c as e } from "./controls-BajigKUv.js";
2
+ import { c as t } from "./date-only-Bu4UmsTP.js";
3
+ import { t as n } from "./direction-DbZcVPsZ.js";
4
+ import { useEffect as r, useId as i, useLayoutEffect as a, useMemo as o, useRef as s, useState as c } from "react";
5
+ import { jsx as l, jsxs as u } from "react/jsx-runtime";
6
+ import { NumberInput as d, useNumberInput as f } from "@ark-ui/react/number-input";
7
+ //#region src/number-input.tsx
8
+ function p(e, n = {}) {
9
+ let r = t(e);
10
+ return {
11
+ decrease: r ? "값 줄이기" : "Decrease",
12
+ increase: r ? "값 늘리기" : "Increase",
13
+ ...n
14
+ };
15
+ }
16
+ var m = ["M5 12h14"], h = ["M12 5v14", "M5 12h14"];
17
+ function g({ path: e }) {
18
+ return /* @__PURE__ */ l("svg", {
19
+ width: "16",
20
+ height: "16",
21
+ viewBox: "0 0 24 24",
22
+ fill: "none",
23
+ stroke: "currentColor",
24
+ strokeWidth: "1.6",
25
+ strokeLinecap: "round",
26
+ strokeLinejoin: "round",
27
+ "aria-hidden": "true",
28
+ children: e.map((e) => /* @__PURE__ */ l("path", { d: e }, e))
29
+ });
30
+ }
31
+ var _ = typeof document > "u" ? r : a, v = {
32
+ ",": ",",
33
+ ".": ".",
34
+ "-": "-",
35
+ "−": "-"
36
+ };
37
+ function y(e) {
38
+ return e.replace(/[0-9,.-−]/g, (e) => e >= "0" && e <= "9" ? String.fromCharCode(e.charCodeAt(0) - 65296 + 48) : v[e]);
39
+ }
40
+ function b(e) {
41
+ let t = [...e.formatToParts(-1234567.5), ...e.formatToParts(1234567.5)], n = (e) => t.find((t) => t.type === e)?.value, r = /* @__PURE__ */ new Set();
42
+ for (let e of t) if (![
43
+ "integer",
44
+ "group",
45
+ "decimal",
46
+ "fraction",
47
+ "minusSign"
48
+ ].includes(e.type)) for (let t of e.value) r.add(t);
49
+ return {
50
+ group: n("group") ?? "",
51
+ decimal: n("decimal") ?? ".",
52
+ minus: n("minusSign") ?? "-",
53
+ affixes: r
54
+ };
55
+ }
56
+ function x(t) {
57
+ let a = i(), v = t.id ?? `dds-number-input-${a}`, x = `${v}-suffix`, S = t.locale ?? "ko-KR", C = p(S, t.messages), ee = JSON.stringify(t.formatOptions ?? null), w = o(() => t.formatOptions ?? { maximumFractionDigits: 0 }, [ee]), T = o(() => new Intl.NumberFormat(S, w), [S, w]), E = o(() => b(T), [T]), D = (T.resolvedOptions().maximumFractionDigits ?? 0) > 0, O = (e) => e === null || !Number.isFinite(e) ? "" : T.format(e), k = t.value !== void 0, [A, j] = c(t.defaultValue ?? null), M = k ? t.value ?? null : A, [N, P] = c(() => O(M)), [F, I] = c(M), L = s({
58
+ draft: N,
59
+ draftNumber: F,
60
+ current: M,
61
+ internal: A
62
+ });
63
+ L.current.current = k ? t.value ?? null : L.current.internal;
64
+ let R = (e, t) => {
65
+ L.current.draft = e, L.current.draftNumber = t, P(e), I(t);
66
+ }, z = s(N).current, te = F !== null && (t.min !== void 0 && F < t.min || t.max !== void 0 && F > t.max), B = !!t.invalid || te, V = s(null), H = s(null), U = s(!1), W = s(!1), G = n(V, t.dir), K = (e) => {
67
+ e !== L.current.current && (k || (L.current.internal = e, L.current.current = e, j(e)), t.onValueChange?.(e));
68
+ }, q = f({
69
+ id: `${v}-root`,
70
+ ids: {
71
+ root: `${v}-root`,
72
+ input: v
73
+ },
74
+ dir: G,
75
+ locale: S,
76
+ formatOptions: w,
77
+ value: N,
78
+ step: t.step ?? 1,
79
+ ...t.min === void 0 ? {} : { min: t.min },
80
+ ...t.max === void 0 ? {} : { max: t.max },
81
+ allowMouseWheel: !1,
82
+ clampValueOnBlur: !0,
83
+ spinOnPress: !0,
84
+ inputMode: D ? "decimal" : "numeric",
85
+ disabled: t.disabled,
86
+ readOnly: t.readOnly,
87
+ required: t.required,
88
+ invalid: B,
89
+ translations: {
90
+ decrementLabel: C.decrease,
91
+ incrementLabel: C.increase,
92
+ valueText: (e) => e && t.suffix ? `${e} ${t.suffix}` : e
93
+ },
94
+ onValueChange: (e) => {
95
+ if (W.current) return;
96
+ let t = Number.isNaN(e.valueAsNumber) ? null : e.valueAsNumber;
97
+ R(e.value, t), K(t);
98
+ }
99
+ }), J = s(!1);
100
+ r(() => {
101
+ if (!J.current) {
102
+ J.current = !0;
103
+ return;
104
+ }
105
+ M !== L.current.draftNumber && R(O(M), M);
106
+ }, [M]), _(() => {
107
+ let e = H.current;
108
+ e && e.value !== N && (e.value = N);
109
+ }, [N]);
110
+ let Y = t.defaultValue, X = s({
111
+ controlled: k,
112
+ value: t.value,
113
+ defaultValue: Y,
114
+ format: O
115
+ });
116
+ X.current = {
117
+ controlled: k,
118
+ value: t.value,
119
+ defaultValue: Y,
120
+ format: O
121
+ }, r(() => {
122
+ let e = H.current?.form;
123
+ if (!e) return;
124
+ let t, n = (e) => {
125
+ W.current = !0, t = setTimeout(() => {
126
+ if (W.current = !1, e.defaultPrevented) return;
127
+ let t = X.current, n;
128
+ t.controlled ? n = t.value ?? null : (n = t.defaultValue ?? null, L.current.internal = n, L.current.current = n, j(n));
129
+ let r = t.format(n);
130
+ R(r, n), H.current && (H.current.value = r);
131
+ });
132
+ };
133
+ return e.addEventListener("reset", n, { capture: !0 }), () => {
134
+ e.removeEventListener("reset", n, { capture: !0 }), t !== void 0 && clearTimeout(t), W.current = !1;
135
+ };
136
+ }, [t.form]);
137
+ let Z = (e) => {
138
+ let { group: n, decimal: r, minus: i, affixes: a } = E, o = 0;
139
+ for (let s of e) if (!(s >= "0" && s <= "9") && !(s === n || n.trim() === "" && s === " ")) {
140
+ if (s === r) {
141
+ if (o += 1, !D || o > 1) return !1;
142
+ continue;
143
+ }
144
+ if (s === i || s === "-") {
145
+ if (t.min !== void 0 && t.min >= 0) return !1;
146
+ continue;
147
+ }
148
+ if (!a.has(s)) return !1;
149
+ }
150
+ return !0;
151
+ }, Q = s(Z);
152
+ Q.current = Z, r(() => {
153
+ let e = H.current;
154
+ if (!e) return;
155
+ let t = (e) => {
156
+ if (U.current || e.isComposing) return;
157
+ let t = e.currentTarget, n = y(e.data ?? ""), r = t.value.slice(0, t.selectionStart ?? t.value.length) + n + t.value.slice(t.selectionEnd ?? t.value.length);
158
+ Q.current(y(r)) || e.preventDefault();
159
+ };
160
+ return e.addEventListener("beforeinput", t), () => e.removeEventListener("beforeinput", t);
161
+ }, []);
162
+ let ne = (e) => {
163
+ let n = (L.current.draftNumber ?? 0) + e;
164
+ t.min !== void 0 && (n = Math.max(t.min, n)), t.max !== void 0 && (n = Math.min(t.max, n)), q.setValue(n);
165
+ }, { onKeyDown: re, onBeforeInput: ie, onInput: $, onBlur: ae, value: oe, defaultValue: se, ...ce } = q.getInputProps();
166
+ return /* @__PURE__ */ u(d.RootProvider, {
167
+ ref: V,
168
+ value: q,
169
+ className: e("dds-number-input", t.className),
170
+ "data-label-placement": t.labelPlacement ?? "outside",
171
+ "data-readonly": t.readOnly ? "" : void 0,
172
+ "data-stepper": t.stepper === !1 ? void 0 : "",
173
+ children: [
174
+ t.label ? /* @__PURE__ */ l(d.Label, {
175
+ className: "dds-number-input__label",
176
+ children: t.label
177
+ }) : null,
178
+ /* @__PURE__ */ u(d.Control, {
179
+ className: "dds-number-input__control",
180
+ children: [
181
+ /* @__PURE__ */ l("input", {
182
+ ...ce,
183
+ ref: H,
184
+ className: "dds-number-input__field",
185
+ defaultValue: z,
186
+ placeholder: t.placeholder,
187
+ "aria-label": t.label ? void 0 : t["aria-label"],
188
+ "aria-labelledby": t.label ? void 0 : t["aria-labelledby"],
189
+ "aria-describedby": [t.suffix ? x : void 0, t["aria-describedby"]].filter(Boolean).join(" ") || void 0,
190
+ onCompositionStart: () => {
191
+ U.current = !0;
192
+ },
193
+ onCompositionEnd: (e) => {
194
+ U.current = !1, e.currentTarget.dispatchEvent(new InputEvent("input", { bubbles: !0 }));
195
+ },
196
+ onInput: (e) => {
197
+ if (U.current) return;
198
+ let t = e.currentTarget, n = y(t.value);
199
+ if (!Z(n)) {
200
+ t.value = L.current.draft;
201
+ return;
202
+ }
203
+ if (n !== t.value) {
204
+ let e = t.selectionStart, r = t.selectionEnd;
205
+ t.value = n, t.setSelectionRange(e, r);
206
+ }
207
+ $?.(e);
208
+ },
209
+ onKeyDown: (e) => {
210
+ if (t.readOnly || t.disabled || e.nativeEvent.isComposing || e.keyCode === 229) return;
211
+ let n = t.step ?? 1;
212
+ if (e.key === "PageUp" || e.key === "PageDown") {
213
+ e.preventDefault(), ne((e.key === "PageUp" ? 10 : -10) * n);
214
+ return;
215
+ }
216
+ (e.key !== "Home" || t.min !== void 0) && (e.key !== "End" || t.max !== void 0) && re?.(e);
217
+ },
218
+ onBlur: (e) => {
219
+ ae?.(e);
220
+ let { draftNumber: t, current: n } = L.current;
221
+ k && t !== n && R(O(n), n);
222
+ }
223
+ }),
224
+ t.suffix ? /* @__PURE__ */ l("span", {
225
+ id: x,
226
+ className: "dds-number-input__suffix",
227
+ children: t.suffix
228
+ }) : null,
229
+ t.stepper === !1 ? null : /* @__PURE__ */ u("span", {
230
+ className: "dds-number-input__steppers",
231
+ children: [/* @__PURE__ */ l(d.DecrementTrigger, {
232
+ className: "dds-number-input__step",
233
+ children: /* @__PURE__ */ l(g, { path: m })
234
+ }), /* @__PURE__ */ l(d.IncrementTrigger, {
235
+ className: "dds-number-input__step",
236
+ children: /* @__PURE__ */ l(g, { path: h })
237
+ })]
238
+ })
239
+ ]
240
+ }),
241
+ /* @__PURE__ */ l("input", {
242
+ type: "hidden",
243
+ name: t.name,
244
+ form: t.form,
245
+ value: F === null ? "" : String(F),
246
+ disabled: t.disabled
247
+ })
248
+ ]
249
+ });
250
+ }
251
+ //#endregion
252
+ export { x as NumberInput, y as normalizeNumberText, p as numberInputMessages };
253
+
254
+ //# sourceMappingURL=number-input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"number-input.js","names":[],"sources":["../src/number-input.tsx"],"sourcesContent":["import {\n NumberInput as ArkNumberInput,\n useNumberInput,\n} from \"@ark-ui/react/number-input\";\nimport {\n useEffect,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type FormEvent,\n type KeyboardEvent,\n type FocusEvent,\n} from \"react\";\nimport { classes } from \"./controls\";\nimport { isKorean } from \"./date-only\";\nimport { useDirection, type Direction } from \"./direction\";\n\nexport interface NumberInputMessages {\n /** Accessible name of the − button. */\n decrease?: string;\n /** Accessible name of the + button. */\n increase?: string;\n}\n\nexport function numberInputMessages(\n locale: string,\n messages: NumberInputMessages = {},\n): Required<NumberInputMessages> {\n const ko = isKorean(locale);\n return {\n decrease: ko ? \"값 줄이기\" : \"Decrease\",\n increase: ko ? \"값 늘리기\" : \"Increase\",\n ...messages,\n };\n}\n\nexport interface NumberInputProps {\n /** null means empty. Controlled when not undefined. */\n value?: number | null;\n defaultValue?: number | null;\n /** Called with the new number, or null when emptied, only after a user change. */\n onValueChange?: (next: number | null) => void;\n min?: number;\n max?: number;\n /** Arrow keys and the stepper buttons move by step; PageUp/PageDown by ten steps. */\n step?: number;\n /** Formatting and parsing locale. Defaults to ko-KR. */\n locale?: string;\n /** Defaults to whole numbers with thousands separators. */\n formatOptions?: Intl.NumberFormatOptions;\n /** Show the − / + buttons. Defaults to true. */\n stepper?: boolean;\n /** Unit shown after the digits, for example 원, 개 or 분. */\n suffix?: string;\n label?: string;\n labelPlacement?: \"outside\" | \"hidden\";\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n \"aria-describedby\"?: string;\n id?: string;\n /** Submits the plain number (1234.5), never the formatted text. */\n name?: string;\n form?: string;\n required?: boolean;\n disabled?: boolean;\n readOnly?: boolean;\n /** Also invalid on its own while the number is outside min/max. */\n invalid?: boolean;\n placeholder?: string;\n messages?: NumberInputMessages;\n dir?: Direction;\n className?: string;\n}\n\n// Inlined from @devslab/dds-icons (minus/plus): importing the shared Icon here\n// would move the whole icon set into a chunk shared with the root entry.\nconst MINUS = [\"M5 12h14\"];\nconst PLUS = [\"M12 5v14\", \"M5 12h14\"];\nfunction StepIcon({ path }: { path: string[] }) {\n return (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.6\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n {path.map((d) => (\n <path key={d} d={d} />\n ))}\n </svg>\n );\n}\n\n// Layout timing in the browser; the server has no field to write to.\nconst useClientLayoutEffect =\n typeof document === \"undefined\" ? useEffect : useLayoutEffect;\n\nconst FULL_WIDTH: Record<string, string> = {\n \",\": \",\",\n \".\": \".\",\n \"-\": \"-\",\n \"−\": \"-\",\n};\n/** Full-width digits from Japanese/Chinese IMEs become ASCII; other text is left for validation. */\nexport function normalizeNumberText(text: string) {\n return text.replace(/[0-9,.-−]/g, (char) =>\n char >= \"0\" && char <= \"9\"\n ? String.fromCharCode(char.charCodeAt(0) - 0xff10 + 48)\n : FULL_WIDTH[char]!,\n );\n}\n\nfunction separators(formatter: Intl.NumberFormat) {\n const parts = [\n ...formatter.formatToParts(-1234567.5),\n ...formatter.formatToParts(1234567.5),\n ];\n const find = (type: string) => parts.find((part) => part.type === type)?.value;\n const affixes = new Set<string>();\n for (const part of parts)\n if (![\"integer\", \"group\", \"decimal\", \"fraction\", \"minusSign\"].includes(part.type))\n for (const char of part.value) affixes.add(char);\n return {\n group: find(\"group\") ?? \"\",\n decimal: find(\"decimal\") ?? \".\",\n minus: find(\"minusSign\") ?? \"-\",\n affixes,\n };\n}\n\n/**\n * A localized number field. Values are numbers (or null for empty); display\n * text is formatted with Intl and never leaves the component.\n */\nexport function NumberInput(props: NumberInputProps) {\n const generated = useId();\n const id = props.id ?? `dds-number-input-${generated}`;\n const suffixId = `${id}-suffix`;\n const locale = props.locale ?? \"ko-KR\";\n const messages = numberInputMessages(locale, props.messages);\n // Inline option literals are new objects each render; key on their content.\n const optionsKey = JSON.stringify(props.formatOptions ?? null);\n const formatOptions = useMemo<Intl.NumberFormatOptions>(\n () => props.formatOptions ?? { maximumFractionDigits: 0 },\n [optionsKey],\n );\n const formatter = useMemo(\n () => new Intl.NumberFormat(locale, formatOptions),\n [locale, formatOptions],\n );\n const syntax = useMemo(() => separators(formatter), [formatter]);\n const fractions =\n (formatter.resolvedOptions().maximumFractionDigits ?? 0) > 0;\n const format = (next: number | null) =>\n next === null || !Number.isFinite(next) ? \"\" : formatter.format(next);\n\n const controlled = props.value !== undefined;\n const [internal, setInternal] = useState<number | null>(\n props.defaultValue ?? null,\n );\n const current = controlled ? (props.value ?? null) : internal;\n // draft is the text in the field; draftNumber is what that text means.\n const [draft, setDraftState] = useState(() => format(current));\n const [draftNumber, setDraftNumberState] = useState(current);\n // Zag and DOM handlers can run several times before React re-renders, so\n // they read and write these mirrors synchronously (as Solid signals do).\n const live = useRef({ draft, draftNumber, current, internal });\n live.current.current = controlled ? (props.value ?? null) : live.current.internal;\n const setDraft = (text: string, value: number | null) => {\n live.current.draft = text;\n live.current.draftNumber = value;\n setDraftState(text);\n setDraftNumberState(value);\n };\n const initialDraft = useRef(draft).current;\n\n const outOfRange =\n draftNumber !== null &&\n ((props.min !== undefined && draftNumber < props.min) ||\n (props.max !== undefined && draftNumber > props.max));\n const invalid = !!props.invalid || outOfRange;\n\n const root = useRef<HTMLDivElement | null>(null);\n const input = useRef<HTMLInputElement | null>(null);\n const composing = useRef(false);\n const resetting = useRef(false);\n const direction = useDirection(root, props.dir);\n\n const commit = (next: number | null) => {\n if (next === live.current.current) return;\n if (!controlled) {\n live.current.internal = next;\n live.current.current = next;\n setInternal(next);\n }\n props.onValueChange?.(next);\n };\n\n const api = useNumberInput({\n id: `${id}-root`,\n // Zag prefixes part IDs with \"number-input:\"; pin the two DDS documents.\n ids: { root: `${id}-root`, input: id },\n // Ark's React hook types dir as coming from LocaleProvider, but spreads\n // these props last, so an explicit direction still reaches Zag.\n ...({ dir: direction } as object),\n locale,\n formatOptions,\n value: draft,\n step: props.step ?? 1,\n // Unset bounds stay absent: Zag's defaults are the safe-integer limits.\n ...(props.min !== undefined ? { min: props.min } : {}),\n ...(props.max !== undefined ? { max: props.max } : {}),\n allowMouseWheel: false,\n clampValueOnBlur: true,\n spinOnPress: true,\n inputMode: fractions ? \"decimal\" : \"numeric\",\n disabled: props.disabled,\n readOnly: props.readOnly,\n required: props.required,\n invalid,\n translations: {\n decrementLabel: messages.decrease,\n incrementLabel: messages.increase,\n valueText: (text: string) =>\n text && props.suffix ? `${text} ${props.suffix}` : text,\n },\n onValueChange: (details) => {\n if (resetting.current) return;\n const next = Number.isNaN(details.valueAsNumber)\n ? null\n : details.valueAsNumber;\n setDraft(details.value, next);\n commit(next);\n },\n });\n\n // A parent-owned value that changes from outside replaces the field text.\n const mounted = useRef(false);\n useEffect(() => {\n if (!mounted.current) {\n mounted.current = true;\n return;\n }\n if (current === live.current.draftNumber) return;\n setDraft(format(current), current);\n }, [current]);\n\n // The field is uncontrolled for React (Zag and IME composition write to it\n // directly); a new draft is written only when it actually changes.\n useClientLayoutEffect(() => {\n const field = input.current;\n if (field && field.value !== draft) field.value = draft;\n }, [draft]);\n\n const defaultValue = props.defaultValue;\n const resetState = useRef({ controlled, value: props.value, defaultValue, format });\n resetState.current = { controlled, value: props.value, defaultValue, format };\n useEffect(() => {\n const form = input.current?.form;\n if (!form) return;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const reset = (event: Event) => {\n resetting.current = true;\n // A task, not a microtask: after a reset-button click, microtasks run\n // before the browser resets the controls and would be overwritten.\n timer = setTimeout(() => {\n resetting.current = false;\n if (event.defaultPrevented) return;\n const state = resetState.current;\n let next: number | null;\n if (state.controlled) next = state.value ?? null;\n else {\n next = state.defaultValue ?? null;\n live.current.internal = next;\n live.current.current = next;\n setInternal(next);\n }\n const text = state.format(next);\n setDraft(text, next);\n if (input.current) input.current.value = text;\n });\n };\n // Capture runs before Zag's own reset listener, which would emit a change.\n form.addEventListener(\"reset\", reset, { capture: true });\n return () => {\n form.removeEventListener(\"reset\", reset, { capture: true });\n if (timer !== undefined) clearTimeout(timer);\n resetting.current = false;\n };\n }, [props.form]);\n\n const acceptable = (text: string) => {\n const { group, decimal, minus, affixes } = syntax;\n let decimals = 0;\n for (const char of text) {\n if (char >= \"0\" && char <= \"9\") continue;\n if (char === group || (group.trim() === \"\" && char === \" \")) continue;\n if (char === decimal) {\n decimals += 1;\n if (!fractions || decimals > 1) return false;\n continue;\n }\n if (char === minus || char === \"-\") {\n if (props.min !== undefined && props.min >= 0) return false;\n continue;\n }\n if (affixes.has(char)) continue;\n return false;\n }\n return true;\n };\n const acceptableRef = useRef(acceptable);\n acceptableRef.current = acceptable;\n\n // React's onBeforeInput is synthesized from other events; the native one\n // carries the inserted text for every input method.\n useEffect(() => {\n const field = input.current;\n if (!field) return;\n const beforeInput = (event: InputEvent) => {\n if (composing.current || event.isComposing) return;\n const target = event.currentTarget as HTMLInputElement;\n const data = normalizeNumberText(event.data ?? \"\");\n const next =\n target.value.slice(0, target.selectionStart ?? target.value.length) +\n data +\n target.value.slice(target.selectionEnd ?? target.value.length);\n if (!acceptableRef.current(normalizeNumberText(next)))\n event.preventDefault();\n };\n field.addEventListener(\"beforeinput\", beforeInput);\n return () => field.removeEventListener(\"beforeinput\", beforeInput);\n }, []);\n\n const stepBy = (delta: number) => {\n let next = (live.current.draftNumber ?? 0) + delta;\n if (props.min !== undefined) next = Math.max(props.min, next);\n if (props.max !== undefined) next = Math.min(props.max, next);\n api.setValue(next);\n };\n\n // Keep everything from Zag except the handlers DDS composes below.\n const {\n onKeyDown: zagKeyDown,\n onBeforeInput: _beforeInput,\n onInput: zagInput,\n onBlur: zagBlur,\n value: _value,\n defaultValue: _defaultValue,\n ...inputAttributes\n } = api.getInputProps() as Record<string, unknown> & {\n onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;\n onInput?: (event: FormEvent<HTMLInputElement>) => void;\n onBlur?: (event: FocusEvent<HTMLInputElement>) => void;\n };\n\n return (\n <ArkNumberInput.RootProvider\n ref={root}\n value={api}\n className={classes(\"dds-number-input\", props.className)}\n data-label-placement={props.labelPlacement ?? \"outside\"}\n data-readonly={props.readOnly ? \"\" : undefined}\n data-stepper={props.stepper === false ? undefined : \"\"}\n >\n {props.label ? (\n <ArkNumberInput.Label className=\"dds-number-input__label\">\n {props.label}\n </ArkNumberInput.Label>\n ) : null}\n <ArkNumberInput.Control className=\"dds-number-input__control\">\n <input\n {...inputAttributes}\n ref={input}\n className=\"dds-number-input__field\"\n defaultValue={initialDraft}\n placeholder={props.placeholder}\n aria-label={props.label ? undefined : props[\"aria-label\"]}\n aria-labelledby={props.label ? undefined : props[\"aria-labelledby\"]}\n aria-describedby={\n [props.suffix ? suffixId : undefined, props[\"aria-describedby\"]]\n .filter(Boolean)\n .join(\" \") || undefined\n }\n onCompositionStart={() => {\n composing.current = true;\n }}\n onCompositionEnd={(event) => {\n composing.current = false;\n // Hand the finished composition to the normal input path.\n event.currentTarget.dispatchEvent(\n new InputEvent(\"input\", { bubbles: true }),\n );\n }}\n onInput={(event) => {\n if (composing.current) return;\n const target = event.currentTarget;\n const normalized = normalizeNumberText(target.value);\n if (!acceptable(normalized)) {\n // Letters, pasted units and the like are refused, not guessed at.\n target.value = live.current.draft;\n return;\n }\n if (normalized !== target.value) {\n const start = target.selectionStart;\n const end = target.selectionEnd;\n target.value = normalized;\n target.setSelectionRange(start, end);\n }\n zagInput?.(event);\n }}\n onKeyDown={(event) => {\n if (props.readOnly || props.disabled) return;\n if (event.nativeEvent.isComposing || event.keyCode === 229) return;\n const step = props.step ?? 1;\n if (event.key === \"PageUp\" || event.key === \"PageDown\") {\n event.preventDefault();\n stepBy((event.key === \"PageUp\" ? 10 : -10) * step);\n return;\n }\n // Home/End jump to a bound only when that bound exists;\n // otherwise they keep moving the caret.\n if (event.key === \"Home\" && props.min === undefined) return;\n if (event.key === \"End\" && props.max === undefined) return;\n zagKeyDown?.(event);\n }}\n onBlur={(event) => {\n zagBlur?.(event);\n // A parent that declined the typed number gets its own value back.\n const { draftNumber: typed, current: owned } = live.current;\n if (controlled && typed !== owned) setDraft(format(owned), owned);\n }}\n />\n {props.suffix ? (\n <span id={suffixId} className=\"dds-number-input__suffix\">\n {props.suffix}\n </span>\n ) : null}\n {props.stepper !== false ? (\n <span className=\"dds-number-input__steppers\">\n <ArkNumberInput.DecrementTrigger className=\"dds-number-input__step\">\n <StepIcon path={MINUS} />\n </ArkNumberInput.DecrementTrigger>\n <ArkNumberInput.IncrementTrigger className=\"dds-number-input__step\">\n <StepIcon path={PLUS} />\n </ArkNumberInput.IncrementTrigger>\n </span>\n ) : null}\n </ArkNumberInput.Control>\n <input\n type=\"hidden\"\n name={props.name}\n form={props.form}\n value={draftNumber === null ? \"\" : String(draftNumber)}\n disabled={props.disabled}\n />\n </ArkNumberInput.RootProvider>\n );\n}\n"],"mappings":";;;;;;;AA0BA,SAAgB,EACd,GACA,IAAgC,CAAC,GACF;CAC/B,IAAM,IAAK,EAAS,CAAM;CAC1B,OAAO;EACL,UAAU,IAAK,UAAU;EACzB,UAAU,IAAK,UAAU;EACzB,GAAG;CACL;AACF;AA0CA,IAAM,IAAQ,CAAC,UAAU,GACnB,IAAO,CAAC,YAAY,UAAU;AACpC,SAAS,EAAS,EAAE,WAA4B;CAC9C,OACE,kBAAC,OAAD;EACE,OAAM;EACN,QAAO;EACP,SAAQ;EACR,MAAK;EACL,QAAO;EACP,aAAY;EACZ,eAAc;EACd,gBAAe;EACf,eAAY;EAEX,UAAA,EAAK,KAAK,MACT,kBAAC,QAAD,EAAiB,KAAI,GAAV,CAAU,CACtB;CACE,CAAA;AAET;AAGA,IAAM,IACJ,OAAO,WAAa,MAAc,IAAY,GAE1C,IAAqC;CACzC,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;AACP;AAEA,SAAgB,EAAoB,GAAc;CAChD,OAAO,EAAK,QAAQ,eAAe,MACjC,KAAQ,OAAO,KAAQ,MACnB,OAAO,aAAa,EAAK,WAAW,CAAC,IAAI,QAAS,EAAE,IACpD,EAAW,EACjB;AACF;AAEA,SAAS,EAAW,GAA8B;CAChD,IAAM,IAAQ,CACZ,GAAG,EAAU,cAAc,UAAU,GACrC,GAAG,EAAU,cAAc,SAAS,CACtC,GACM,KAAQ,MAAiB,EAAM,MAAM,MAAS,EAAK,SAAS,CAAI,CAAC,EAAE,OACnE,oBAAU,IAAI,IAAY;CAChC,KAAK,IAAM,KAAQ,GACjB,IAAI,CAAC;EAAC;EAAW;EAAS;EAAW;EAAY;CAAW,CAAC,CAAC,SAAS,EAAK,IAAI,GAC9E,KAAK,IAAM,KAAQ,EAAK,OAAO,EAAQ,IAAI,CAAI;CACnD,OAAO;EACL,OAAO,EAAK,OAAO,KAAK;EACxB,SAAS,EAAK,SAAS,KAAK;EAC5B,OAAO,EAAK,WAAW,KAAK;EAC5B;CACF;AACF;AAMA,SAAgB,EAAY,GAAyB;CACnD,IAAM,IAAY,EAAM,GAClB,IAAK,EAAM,MAAM,oBAAoB,KACrC,IAAW,GAAG,EAAG,UACjB,IAAS,EAAM,UAAU,SACzB,IAAW,EAAoB,GAAQ,EAAM,QAAQ,GAErD,KAAa,KAAK,UAAU,EAAM,iBAAiB,IAAI,GACvD,IAAgB,QACd,EAAM,iBAAiB,EAAE,uBAAuB,EAAE,GACxD,CAAC,EAAU,CACb,GACM,IAAY,QACV,IAAI,KAAK,aAAa,GAAQ,CAAa,GACjD,CAAC,GAAQ,CAAa,CACxB,GACM,IAAS,QAAc,EAAW,CAAS,GAAG,CAAC,CAAS,CAAC,GACzD,KACH,EAAU,gBAAgB,CAAC,CAAC,yBAAyB,KAAK,GACvD,KAAU,MACd,MAAS,QAAQ,CAAC,OAAO,SAAS,CAAI,IAAI,KAAK,EAAU,OAAO,CAAI,GAEhE,IAAa,EAAM,UAAU,KAAA,GAC7B,CAAC,GAAU,KAAe,EAC9B,EAAM,gBAAgB,IACxB,GACM,IAAU,IAAc,EAAM,SAAS,OAAQ,GAE/C,CAAC,GAAO,KAAiB,QAAe,EAAO,CAAO,CAAC,GACvD,CAAC,GAAa,KAAuB,EAAS,CAAO,GAGrD,IAAO,EAAO;EAAE;EAAO;EAAa;EAAS;CAAS,CAAC;CAC7D,EAAK,QAAQ,UAAU,IAAc,EAAM,SAAS,OAAQ,EAAK,QAAQ;CACzE,IAAM,KAAY,GAAc,MAAyB;EAIvD,AAHA,EAAK,QAAQ,QAAQ,GACrB,EAAK,QAAQ,cAAc,GAC3B,EAAc,CAAI,GAClB,EAAoB,CAAK;CAC3B,GACM,IAAe,EAAO,CAAK,CAAC,CAAC,SAE7B,KACJ,MAAgB,SACd,EAAM,QAAQ,KAAA,KAAa,IAAc,EAAM,OAC9C,EAAM,QAAQ,KAAA,KAAa,IAAc,EAAM,MAC9C,IAAU,CAAC,CAAC,EAAM,WAAW,IAE7B,IAAO,EAA8B,IAAI,GACzC,IAAQ,EAAgC,IAAI,GAC5C,IAAY,EAAO,EAAK,GACxB,IAAY,EAAO,EAAK,GACxB,IAAY,EAAa,GAAM,EAAM,GAAG,GAExC,KAAU,MAAwB;EAClC,MAAS,EAAK,QAAQ,YACrB,MACH,EAAK,QAAQ,WAAW,GACxB,EAAK,QAAQ,UAAU,GACvB,EAAY,CAAI,IAElB,EAAM,gBAAgB,CAAI;CAC5B,GAEM,IAAM,EAAe;EACzB,IAAI,GAAG,EAAG;EAEV,KAAK;GAAE,MAAM,GAAG,EAAG;GAAQ,OAAO;EAAG;EAG/B,KAAK;EACX;EACA;EACA,OAAO;EACP,MAAM,EAAM,QAAQ;EAEpB,GAAI,EAAM,QAAQ,KAAA,IAAiC,CAAC,IAAtB,EAAE,KAAK,EAAM,IAAI;EAC/C,GAAI,EAAM,QAAQ,KAAA,IAAiC,CAAC,IAAtB,EAAE,KAAK,EAAM,IAAI;EAC/C,iBAAiB;EACjB,kBAAkB;EAClB,aAAa;EACb,WAAW,IAAY,YAAY;EACnC,UAAU,EAAM;EAChB,UAAU,EAAM;EAChB,UAAU,EAAM;EAChB;EACA,cAAc;GACZ,gBAAgB,EAAS;GACzB,gBAAgB,EAAS;GACzB,YAAY,MACV,KAAQ,EAAM,SAAS,GAAG,EAAK,GAAG,EAAM,WAAW;EACvD;EACA,gBAAgB,MAAY;GAC1B,IAAI,EAAU,SAAS;GACvB,IAAM,IAAO,OAAO,MAAM,EAAQ,aAAa,IAC3C,OACA,EAAQ;GAEZ,AADA,EAAS,EAAQ,OAAO,CAAI,GAC5B,EAAO,CAAI;EACb;CACF,CAAC,GAGK,IAAU,EAAO,EAAK;CAY5B,AAXA,QAAgB;EACd,IAAI,CAAC,EAAQ,SAAS;GACpB,EAAQ,UAAU;GAClB;EACF;EACI,MAAY,EAAK,QAAQ,eAC7B,EAAS,EAAO,CAAO,GAAG,CAAO;CACnC,GAAG,CAAC,CAAO,CAAC,GAIZ,QAA4B;EAC1B,IAAM,IAAQ,EAAM;EACpB,AAAI,KAAS,EAAM,UAAU,MAAO,EAAM,QAAQ;CACpD,GAAG,CAAC,CAAK,CAAC;CAEV,IAAM,IAAe,EAAM,cACrB,IAAa,EAAO;EAAE;EAAY,OAAO,EAAM;EAAO;EAAc;CAAO,CAAC;CAElF,AADA,EAAW,UAAU;EAAE;EAAY,OAAO,EAAM;EAAO;EAAc;CAAO,GAC5E,QAAgB;EACd,IAAM,IAAO,EAAM,SAAS;EAC5B,IAAI,CAAC,GAAM;EACX,IAAI,GACE,KAAS,MAAiB;GAI9B,AAHA,EAAU,UAAU,IAGpB,IAAQ,iBAAiB;IAEvB,IADA,EAAU,UAAU,IAChB,EAAM,kBAAkB;IAC5B,IAAM,IAAQ,EAAW,SACrB;IACJ,AAAI,EAAM,aAAY,IAAO,EAAM,SAAS,QAE1C,IAAO,EAAM,gBAAgB,MAC7B,EAAK,QAAQ,WAAW,GACxB,EAAK,QAAQ,UAAU,GACvB,EAAY,CAAI;IAElB,IAAM,IAAO,EAAM,OAAO,CAAI;IAE9B,AADA,EAAS,GAAM,CAAI,GACf,EAAM,YAAS,EAAM,QAAQ,QAAQ;GAC3C,CAAC;EACH;EAGA,OADA,EAAK,iBAAiB,SAAS,GAAO,EAAE,SAAS,GAAK,CAAC,SAC1C;GAGX,AAFA,EAAK,oBAAoB,SAAS,GAAO,EAAE,SAAS,GAAK,CAAC,GACtD,MAAU,KAAA,KAAW,aAAa,CAAK,GAC3C,EAAU,UAAU;EACtB;CACF,GAAG,CAAC,EAAM,IAAI,CAAC;CAEf,IAAM,KAAc,MAAiB;EACnC,IAAM,EAAE,UAAO,YAAS,UAAO,eAAY,GACvC,IAAW;EACf,KAAK,IAAM,KAAQ,GACb,WAAQ,OAAO,KAAQ,QACvB,QAAS,KAAU,EAAM,KAAK,MAAM,MAAM,MAAS,MACvD;OAAI,MAAS,GAAS;IAEpB,IADA,KAAY,GACR,CAAC,KAAa,IAAW,GAAG,OAAO;IACvC;GACF;GACA,IAAI,MAAS,KAAS,MAAS,KAAK;IAClC,IAAI,EAAM,QAAQ,KAAA,KAAa,EAAM,OAAO,GAAG,OAAO;IACtD;GACF;GACI,OAAQ,IAAI,CAAI,GACpB,OAAO;EANP;EAQF,OAAO;CACT,GACM,IAAgB,EAAO,CAAU;CAKvC,AAJA,EAAc,UAAU,GAIxB,QAAgB;EACd,IAAM,IAAQ,EAAM;EACpB,IAAI,CAAC,GAAO;EACZ,IAAM,KAAe,MAAsB;GACzC,IAAI,EAAU,WAAW,EAAM,aAAa;GAC5C,IAAM,IAAS,EAAM,eACf,IAAO,EAAoB,EAAM,QAAQ,EAAE,GAC3C,IACJ,EAAO,MAAM,MAAM,GAAG,EAAO,kBAAkB,EAAO,MAAM,MAAM,IAClE,IACA,EAAO,MAAM,MAAM,EAAO,gBAAgB,EAAO,MAAM,MAAM;GAC/D,AAAK,EAAc,QAAQ,EAAoB,CAAI,CAAC,KAClD,EAAM,eAAe;EACzB;EAEA,OADA,EAAM,iBAAiB,eAAe,CAAW,SACpC,EAAM,oBAAoB,eAAe,CAAW;CACnE,GAAG,CAAC,CAAC;CAEL,IAAM,MAAU,MAAkB;EAChC,IAAI,KAAQ,EAAK,QAAQ,eAAe,KAAK;EAG7C,AAFI,EAAM,QAAQ,KAAA,MAAW,IAAO,KAAK,IAAI,EAAM,KAAK,CAAI,IACxD,EAAM,QAAQ,KAAA,MAAW,IAAO,KAAK,IAAI,EAAM,KAAK,CAAI,IAC5D,EAAI,SAAS,CAAI;CACnB,GAGM,EACJ,WAAW,IACX,eAAe,IACf,SAAS,GACT,QAAQ,IACR,OAAO,IACP,cAAc,IACd,GAAG,OACD,EAAI,cAAc;CAMtB,OACE,kBAAC,EAAe,cAAhB;EACE,KAAK;EACL,OAAO;EACP,WAAW,EAAQ,oBAAoB,EAAM,SAAS;EACtD,wBAAsB,EAAM,kBAAkB;EAC9C,iBAAe,EAAM,WAAW,KAAK,KAAA;EACrC,gBAAc,EAAM,YAAY,KAAQ,KAAA,IAAY;EANtD,UAAA;GAQG,EAAM,QACL,kBAAC,EAAe,OAAhB;IAAsB,WAAU;IAC7B,UAAA,EAAM;GACa,CAAA,IACpB;GACJ,kBAAC,EAAe,SAAhB;IAAwB,WAAU;IAAlC,UAAA;KACE,kBAAC,SAAD;MACE,GAAI;MACJ,KAAK;MACL,WAAU;MACV,cAAc;MACd,aAAa,EAAM;MACnB,cAAY,EAAM,QAAQ,KAAA,IAAY,EAAM;MAC5C,mBAAiB,EAAM,QAAQ,KAAA,IAAY,EAAM;MACjD,oBACE,CAAC,EAAM,SAAS,IAAW,KAAA,GAAW,EAAM,mBAAmB,CAAC,CAC7D,OAAO,OAAO,CAAC,CACf,KAAK,GAAG,KAAK,KAAA;MAElB,0BAA0B;OACxB,EAAU,UAAU;MACtB;MACA,mBAAmB,MAAU;OAG3B,AAFA,EAAU,UAAU,IAEpB,EAAM,cAAc,cAClB,IAAI,WAAW,SAAS,EAAE,SAAS,GAAK,CAAC,CAC3C;MACF;MACA,UAAU,MAAU;OAClB,IAAI,EAAU,SAAS;OACvB,IAAM,IAAS,EAAM,eACf,IAAa,EAAoB,EAAO,KAAK;OACnD,IAAI,CAAC,EAAW,CAAU,GAAG;QAE3B,EAAO,QAAQ,EAAK,QAAQ;QAC5B;OACF;OACA,IAAI,MAAe,EAAO,OAAO;QAC/B,IAAM,IAAQ,EAAO,gBACf,IAAM,EAAO;QAEnB,AADA,EAAO,QAAQ,GACf,EAAO,kBAAkB,GAAO,CAAG;OACrC;OACA,IAAW,CAAK;MAClB;MACA,YAAY,MAAU;OAEpB,IADI,EAAM,YAAY,EAAM,YACxB,EAAM,YAAY,eAAe,EAAM,YAAY,KAAK;OAC5D,IAAM,IAAO,EAAM,QAAQ;OAC3B,IAAI,EAAM,QAAQ,YAAY,EAAM,QAAQ,YAAY;QAEtD,AADA,EAAM,eAAe,GACrB,IAAQ,EAAM,QAAQ,WAAW,KAAK,OAAO,CAAI;QACjD;OACF;OAGI,GAAM,QAAQ,UAAU,EAAM,QAAQ,KAAA,OACtC,EAAM,QAAQ,SAAS,EAAM,QAAQ,KAAA,MACzC,KAAa,CAAK;MACpB;MACA,SAAS,MAAU;OACjB,KAAU,CAAK;OAEf,IAAM,EAAE,aAAa,GAAO,SAAS,MAAU,EAAK;OACpD,AAAI,KAAc,MAAU,KAAO,EAAS,EAAO,CAAK,GAAG,CAAK;MAClE;KACD,CAAA;KACA,EAAM,SACL,kBAAC,QAAD;MAAM,IAAI;MAAU,WAAU;MAC3B,UAAA,EAAM;KACH,CAAA,IACJ;KACH,EAAM,YAAY,KASf,OARF,kBAAC,QAAD;MAAM,WAAU;MAAhB,UAAA,CACE,kBAAC,EAAe,kBAAhB;OAAiC,WAAU;OACzC,UAAA,kBAAC,GAAD,EAAU,MAAM,EAAQ,CAAA;MACO,CAAA,GACjC,kBAAC,EAAe,kBAAhB;OAAiC,WAAU;OACzC,UAAA,kBAAC,GAAD,EAAU,MAAM,EAAO,CAAA;MACQ,CAAA,CAC7B;KACJ,CAAA;IACkB;;GACxB,kBAAC,SAAD;IACE,MAAK;IACL,MAAM,EAAM;IACZ,MAAM,EAAM;IACZ,OAAO,MAAgB,OAAO,KAAK,OAAO,CAAW;IACrD,UAAU,EAAM;GACjB,CAAA;EAC0B;;AAEjC"}
@@ -0,0 +1,40 @@
1
+ import { type Direction } from "./direction";
2
+ export interface SegmentedControlItem {
3
+ /** Nonempty ID, unique within the control. This is what callbacks and forms receive. */
4
+ value: string;
5
+ label: string;
6
+ disabled?: boolean;
7
+ }
8
+ export interface SegmentedControlProps {
9
+ items: readonly SegmentedControlItem[];
10
+ value?: string;
11
+ defaultValue?: string;
12
+ /** Called with the chosen item ID, only after a user choice. */
13
+ onValueChange?: (value: string) => void;
14
+ /** Visible group label. Without it, supply aria-label or aria-labelledby. */
15
+ label?: string;
16
+ "aria-label"?: string;
17
+ "aria-labelledby"?: string;
18
+ "aria-describedby"?: string;
19
+ id?: string;
20
+ name?: string;
21
+ form?: string;
22
+ required?: boolean;
23
+ disabled?: boolean;
24
+ readOnly?: boolean;
25
+ invalid?: boolean;
26
+ /** md is 40px tall like other DDS controls, sm is 32px. */
27
+ size?: "md" | "sm";
28
+ /** Segments share the available width equally instead of sizing to their labels. */
29
+ fullWidth?: boolean;
30
+ dir?: Direction;
31
+ className?: string;
32
+ }
33
+ export declare function validateSegmentedItems(items: readonly SegmentedControlItem[]): void;
34
+ /**
35
+ * Single choice among a few visible options (view switch, filter).
36
+ * Radio-group semantics come from Ark: one tab stop, arrow keys move and select,
37
+ * and each segment submits through a native radio input.
38
+ */
39
+ export declare function SegmentedControl(props: SegmentedControlProps): import("react").JSX.Element;
40
+ //# sourceMappingURL=segmented-control.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"segmented-control.d.ts","sourceRoot":"","sources":["../src/segmented-control.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAE3D,MAAM,WAAW,oBAAoB;IACnC,wFAAwF;IACxF,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2DAA2D;IAC3D,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,oFAAoF;IACpF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,SAAS,oBAAoB,EAAE,QAW5E;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,+BAsF5D"}
@@ -0,0 +1,73 @@
1
+ import { c as e } from "./controls-BajigKUv.js";
2
+ import { n as t } from "./choice-types-kSaEnZPr.js";
3
+ import { t as n } from "./direction-DbZcVPsZ.js";
4
+ import { useCallback as r, useEffect as i, useId as a, useRef as o } from "react";
5
+ import { jsx as s, jsxs as c } from "react/jsx-runtime";
6
+ import { SegmentGroup as l } from "@ark-ui/react/segment-group";
7
+ //#region src/segmented-control.tsx
8
+ function u(e) {
9
+ if (e.length === 0) throw Error("DDS SegmentedControl: items must not be empty");
10
+ let t = /* @__PURE__ */ new Set();
11
+ for (let n of e) {
12
+ if (!n.value || t.has(n.value)) throw Error(`DDS SegmentedControl: item values must be nonempty and unique (${JSON.stringify(n.value)})`);
13
+ t.add(n.value);
14
+ }
15
+ }
16
+ function d(d) {
17
+ let f = a(), p = o(null), m = n(p, d.dir), h = o(d.value ?? d.defaultValue).current, { items: g, label: _ } = d, v = d["aria-label"], y = d["aria-labelledby"];
18
+ i(() => u(g), [g]), i(() => {
19
+ t() && !_ && !v && !y && console.warn("DDS SegmentedControl: supply label, aria-label or aria-labelledby.");
20
+ }, [
21
+ _,
22
+ v,
23
+ y
24
+ ]);
25
+ let b = d.onValueChange, x = r((e) => {
26
+ e.value !== null && b?.(e.value);
27
+ }, [b]), S = r((e) => {
28
+ e && (e.defaultChecked = h === e.value);
29
+ }, [h]);
30
+ return /* @__PURE__ */ c(l.Root, {
31
+ ref: p,
32
+ id: d.id ?? `dds-segmented-${f}`,
33
+ ids: d.id ? { root: d.id } : void 0,
34
+ dir: m,
35
+ orientation: "horizontal",
36
+ value: d.value,
37
+ defaultValue: d.defaultValue,
38
+ name: d.name,
39
+ form: d.form,
40
+ required: d.required,
41
+ disabled: d.disabled,
42
+ readOnly: d.readOnly,
43
+ invalid: d.invalid,
44
+ onValueChange: x,
45
+ "aria-label": _ ? void 0 : v,
46
+ "aria-labelledby": _ ? void 0 : y ?? null,
47
+ "aria-describedby": d["aria-describedby"],
48
+ className: e("dds-segmented", d.className),
49
+ "data-size": d.size ?? "md",
50
+ "data-full-width": d.fullWidth ? "" : void 0,
51
+ children: [_ ? /* @__PURE__ */ s(l.Label, {
52
+ className: "dds-segmented__label",
53
+ children: _
54
+ }) : null, /* @__PURE__ */ s("div", {
55
+ className: "dds-segmented__track",
56
+ children: g.map((e) => /* @__PURE__ */ c(l.Item, {
57
+ value: e.value,
58
+ disabled: e.disabled,
59
+ className: "dds-segmented__item",
60
+ title: d.fullWidth ? e.label : void 0,
61
+ children: [/* @__PURE__ */ s(l.ItemText, {
62
+ className: "dds-segmented__text",
63
+ "data-text": e.label,
64
+ children: e.label
65
+ }), /* @__PURE__ */ s(l.ItemHiddenInput, { ref: S })]
66
+ }, e.value))
67
+ })]
68
+ });
69
+ }
70
+ //#endregion
71
+ export { d as SegmentedControl, u as validateSegmentedItems };
72
+
73
+ //# sourceMappingURL=segmented-control.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"segmented-control.js","names":[],"sources":["../src/segmented-control.tsx"],"sourcesContent":["import { SegmentGroup } from \"@ark-ui/react/segment-group\";\nimport { useCallback, useEffect, useId, useRef } from \"react\";\nimport { choiceWarningsEnabled } from \"./choice-types\";\nimport { classes } from \"./controls\";\nimport { useDirection, type Direction } from \"./direction\";\n\nexport interface SegmentedControlItem {\n /** Nonempty ID, unique within the control. This is what callbacks and forms receive. */\n value: string;\n label: string;\n disabled?: boolean;\n}\n\nexport interface SegmentedControlProps {\n items: readonly SegmentedControlItem[];\n value?: string;\n defaultValue?: string;\n /** Called with the chosen item ID, only after a user choice. */\n onValueChange?: (value: string) => void;\n /** Visible group label. Without it, supply aria-label or aria-labelledby. */\n label?: string;\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n \"aria-describedby\"?: string;\n id?: string;\n name?: string;\n form?: string;\n required?: boolean;\n disabled?: boolean;\n readOnly?: boolean;\n invalid?: boolean;\n /** md is 40px tall like other DDS controls, sm is 32px. */\n size?: \"md\" | \"sm\";\n /** Segments share the available width equally instead of sizing to their labels. */\n fullWidth?: boolean;\n dir?: Direction;\n className?: string;\n}\n\nexport function validateSegmentedItems(items: readonly SegmentedControlItem[]) {\n if (items.length === 0)\n throw new Error(\"DDS SegmentedControl: items must not be empty\");\n const ids = new Set<string>();\n for (const item of items) {\n if (!item.value || ids.has(item.value))\n throw new Error(\n `DDS SegmentedControl: item values must be nonempty and unique (${JSON.stringify(item.value)})`,\n );\n ids.add(item.value);\n }\n}\n\n/**\n * Single choice among a few visible options (view switch, filter).\n * Radio-group semantics come from Ark: one tab stop, arrow keys move and select,\n * and each segment submits through a native radio input.\n */\nexport function SegmentedControl(props: SegmentedControlProps) {\n // Ark derives every part ID (label, radios) from the root ID; an undefined\n // id prop would override its generated one and collide across instances.\n const generated = useId();\n const root = useRef<HTMLDivElement | null>(null);\n const direction = useDirection(root, props.dir);\n // Native form reset returns to the first rendered choice.\n const initialValue = useRef(props.value ?? props.defaultValue).current;\n const { items, label } = props;\n const ariaLabel = props[\"aria-label\"];\n const ariaLabelledby = props[\"aria-labelledby\"];\n useEffect(() => validateSegmentedItems(items), [items]);\n useEffect(() => {\n if (choiceWarningsEnabled() && !label && !ariaLabel && !ariaLabelledby)\n console.warn(\n \"DDS SegmentedControl: supply label, aria-label or aria-labelledby.\",\n );\n }, [label, ariaLabel, ariaLabelledby]);\n const onValueChange = props.onValueChange;\n const handleValueChange = useCallback(\n (details: { value: string | null }) => {\n if (details.value !== null) onValueChange?.(details.value);\n },\n [onValueChange],\n );\n const setDefaultChecked = useCallback(\n (input: HTMLInputElement | null) => {\n if (input) input.defaultChecked = initialValue === input.value;\n },\n [initialValue],\n );\n return (\n <SegmentGroup.Root\n ref={root}\n id={props.id ?? `dds-segmented-${generated}`}\n // A consumer id lands on the group element itself, not on Zag's prefixed root ID.\n ids={props.id ? { root: props.id } : undefined}\n dir={direction}\n orientation=\"horizontal\"\n value={props.value}\n defaultValue={props.defaultValue}\n name={props.name}\n form={props.form}\n required={props.required}\n disabled={props.disabled}\n readOnly={props.readOnly}\n invalid={props.invalid}\n onValueChange={handleValueChange}\n // Without a visible label the group is named by the consumer's attributes;\n // Ark's own aria-labelledby would otherwise point at a label that is not rendered.\n aria-label={label ? undefined : ariaLabel}\n // (null removes Ark's attribute; undefined would let it through the merge.)\n aria-labelledby={\n label ? undefined : ((ariaLabelledby ?? null) as unknown as string)\n }\n aria-describedby={props[\"aria-describedby\"]}\n className={classes(\"dds-segmented\", props.className)}\n data-size={props.size ?? \"md\"}\n data-full-width={props.fullWidth ? \"\" : undefined}\n >\n {label ? (\n <SegmentGroup.Label className=\"dds-segmented__label\">\n {label}\n </SegmentGroup.Label>\n ) : null}\n <div className=\"dds-segmented__track\">\n {items.map((item) => (\n <SegmentGroup.Item\n key={item.value}\n value={item.value}\n disabled={item.disabled}\n className=\"dds-segmented__item\"\n title={props.fullWidth ? item.label : undefined}\n >\n <SegmentGroup.ItemText\n className=\"dds-segmented__text\"\n data-text={item.label}\n >\n {item.label}\n </SegmentGroup.ItemText>\n <SegmentGroup.ItemHiddenInput ref={setDefaultChecked} />\n </SegmentGroup.Item>\n ))}\n </div>\n </SegmentGroup.Root>\n );\n}\n"],"mappings":";;;;;;;AAuCA,SAAgB,EAAuB,GAAwC;CAC7E,IAAI,EAAM,WAAW,GACnB,MAAU,MAAM,+CAA+C;CACjE,IAAM,oBAAM,IAAI,IAAY;CAC5B,KAAK,IAAM,KAAQ,GAAO;EACxB,IAAI,CAAC,EAAK,SAAS,EAAI,IAAI,EAAK,KAAK,GACnC,MAAU,MACR,kEAAkE,KAAK,UAAU,EAAK,KAAK,EAAE,EAC/F;EACF,EAAI,IAAI,EAAK,KAAK;CACpB;AACF;AAOA,SAAgB,EAAiB,GAA8B;CAG7D,IAAM,IAAY,EAAM,GAClB,IAAO,EAA8B,IAAI,GACzC,IAAY,EAAa,GAAM,EAAM,GAAG,GAExC,IAAe,EAAO,EAAM,SAAS,EAAM,YAAY,CAAC,CAAC,SACzD,EAAE,UAAO,aAAU,GACnB,IAAY,EAAM,eAClB,IAAiB,EAAM;CAE7B,AADA,QAAgB,EAAuB,CAAK,GAAG,CAAC,CAAK,CAAC,GACtD,QAAgB;EACd,AAAI,EAAsB,KAAK,CAAC,KAAS,CAAC,KAAa,CAAC,KACtD,QAAQ,KACN,oEACF;CACJ,GAAG;EAAC;EAAO;EAAW;CAAc,CAAC;CACrC,IAAM,IAAgB,EAAM,eACtB,IAAoB,GACvB,MAAsC;EACrC,AAAI,EAAQ,UAAU,QAAM,IAAgB,EAAQ,KAAK;CAC3D,GACA,CAAC,CAAa,CAChB,GACM,IAAoB,GACvB,MAAmC;EAClC,AAAI,MAAO,EAAM,iBAAiB,MAAiB,EAAM;CAC3D,GACA,CAAC,CAAY,CACf;CACA,OACE,kBAAC,EAAa,MAAd;EACE,KAAK;EACL,IAAI,EAAM,MAAM,iBAAiB;EAEjC,KAAK,EAAM,KAAK,EAAE,MAAM,EAAM,GAAG,IAAI,KAAA;EACrC,KAAK;EACL,aAAY;EACZ,OAAO,EAAM;EACb,cAAc,EAAM;EACpB,MAAM,EAAM;EACZ,MAAM,EAAM;EACZ,UAAU,EAAM;EAChB,UAAU,EAAM;EAChB,UAAU,EAAM;EAChB,SAAS,EAAM;EACf,eAAe;EAGf,cAAY,IAAQ,KAAA,IAAY;EAEhC,mBACE,IAAQ,KAAA,IAAc,KAAkB;EAE1C,oBAAkB,EAAM;EACxB,WAAW,EAAQ,iBAAiB,EAAM,SAAS;EACnD,aAAW,EAAM,QAAQ;EACzB,mBAAiB,EAAM,YAAY,KAAK,KAAA;EA1B1C,UAAA,CA4BG,IACC,kBAAC,EAAa,OAAd;GAAoB,WAAU;GAC3B,UAAA;EACiB,CAAA,IAClB,MACJ,kBAAC,OAAD;GAAK,WAAU;GACZ,UAAA,EAAM,KAAK,MACV,kBAAC,EAAa,MAAd;IAEE,OAAO,EAAK;IACZ,UAAU,EAAK;IACf,WAAU;IACV,OAAO,EAAM,YAAY,EAAK,QAAQ,KAAA;IALxC,UAAA,CAOE,kBAAC,EAAa,UAAd;KACE,WAAU;KACV,aAAW,EAAK;KAEf,UAAA,EAAK;IACe,CAAA,GACvB,kBAAC,EAAa,iBAAd,EAA8B,KAAK,EAAoB,CAAA,CACtC;GAbZ,GAAA,EAAK,KAaO,CACpB;EACE,CAAA,CACY;;AAEvB"}
@@ -1,4 +1,4 @@
1
- import { t as e } from "./choice-B3vNMfKK.js";
1
+ import { t as e } from "./choice-N-5K_ggs.js";
2
2
  import { jsx as t } from "react/jsx-runtime";
3
3
  //#region src/select-picker.tsx
4
4
  function n(n) {
@@ -0,0 +1,32 @@
1
+ /** Canonical "HH:MM" for an API value, or null when it is not a time. */
2
+ export declare function normalizeTime(value: string): string | null;
3
+ /**
4
+ * What a person typed, read as a complete time: "9:05", "09:05", "09:05:00",
5
+ * "0905", or "905" when the first two digits cannot be an hour. Full-width
6
+ * colons and dots count as colons. Partial text ("10", "10:4", and "103",
7
+ * which is still on its way to 10:30) is not a time.
8
+ */
9
+ export declare function parseTypedTime(text: string): string | null;
10
+ /** 00:00 up to the last step before 24:00. Steps below one minute become one; a non-number becomes 10. */
11
+ export declare function timeOptions(step: number): string[];
12
+ /**
13
+ * Whether a "HH:MM" option matches partial typing. Digits read as the start of
14
+ * the time ("10" → 10:00–10:59, "1030" → 10:30); an odd digit count or an
15
+ * impossible hour also tries a leading zero ("9" → 09:xx, "930" → 09:30).
16
+ * With a colon the hour may be one digit ("9:3" → 09:30–09:39).
17
+ */
18
+ export declare function matchesTimeQuery(option: string, query: string): boolean;
19
+ export interface TimeMessages {
20
+ /** Accessible name of the clock button that opens the list. */
21
+ open?: string;
22
+ /** Accessible name of the clear button. */
23
+ clear?: string;
24
+ /** Status when nothing in the list matches the typed text. */
25
+ empty?: string;
26
+ }
27
+ export declare function timeMessages(locale: string, messages?: TimeMessages): {
28
+ open: string;
29
+ clear: string;
30
+ empty: string;
31
+ };
32
+ //# sourceMappingURL=time-only.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-only.d.ts","sourceRoot":"","sources":["../src/time-only.ts"],"names":[],"mappings":"AAKA,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG1D;AAMD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAS1D;AAED,0GAA0G;AAC1G,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,YAMvC;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,WAkB7D;AAED,MAAM,WAAW,YAAY;IAC3B,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AACD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAE,YAAiB;;;;EAQvE"}
@@ -0,0 +1,100 @@
1
+ import { t as e } from "./choice-N-5K_ggs.js";
2
+ import { useMemo as t, useState as n } from "react";
3
+ import { jsx as r, jsxs as i } from "react/jsx-runtime";
4
+ //#region src/time-only.ts
5
+ var a = /^([01]\d|2[0-3]):([0-5]\d)(?::[0-5]\d(?:\.\d+)?)?$/;
6
+ function o(e) {
7
+ let t = a.exec(e);
8
+ return t ? `${t[1]}:${t[2]}` : null;
9
+ }
10
+ var s = (e) => String(e).padStart(2, "0"), c = (e, t) => e <= 23 && t <= 59 ? `${s(e)}:${s(t)}` : null;
11
+ function l(e) {
12
+ let t = e.trim().replace(/[:.]/g, ":"), n = /^(\d{1,2}):(\d{2})(?::\d{2})?$/.exec(t);
13
+ return n ? c(Number(n[1]), Number(n[2])) : /^\d{4}$/.test(t) ? c(Number(t.slice(0, 2)), Number(t.slice(2))) : /^\d{3}$/.test(t) && Number(t.slice(0, 2)) > 23 ? c(Number(t[0]), Number(t.slice(1))) : null;
14
+ }
15
+ function u(e) {
16
+ let t = Number.isFinite(e) ? Math.max(1, Math.floor(e)) : 10, n = [];
17
+ for (let e = 0; e < 1440; e += t) n.push(`${s(Math.floor(e / 60))}:${s(e % 60)}`);
18
+ return n;
19
+ }
20
+ function d(e, t) {
21
+ let n = t.trim().replace(/[:.]/g, ":");
22
+ if (n === "") return !0;
23
+ let r = l(n);
24
+ if (r) return e === r;
25
+ if (n.includes(":")) {
26
+ let [t = "", r = "", ...i] = n.split(":");
27
+ return i.length || !/^\d{1,2}$/.test(t) || !/^\d{0,2}$/.test(r) ? !1 : e.startsWith(`${t.padStart(2, "0")}:${r}`);
28
+ }
29
+ if (!/^\d{1,4}$/.test(n)) return !1;
30
+ let i = e.replace(":", "");
31
+ return i.startsWith(n) ? !0 : (n.length % 2 == 1 || Number(n.slice(0, 2)) > 23) && i.startsWith(`0${n}`);
32
+ }
33
+ function f(e, t = {}) {
34
+ let n = /^ko(?:-|$)/i.test(e);
35
+ return {
36
+ open: n ? "시간 목록 열기" : "Open time list",
37
+ clear: n ? "시간 지우기" : "Clear time",
38
+ empty: n ? "맞는 시간이 없습니다" : "No matching time",
39
+ ...t
40
+ };
41
+ }
42
+ //#endregion
43
+ //#region src/time-picker.tsx
44
+ var p = 300, m = /* @__PURE__ */ i("svg", {
45
+ width: "20",
46
+ height: "20",
47
+ viewBox: "0 0 24 24",
48
+ fill: "none",
49
+ stroke: "currentColor",
50
+ strokeWidth: "1.6",
51
+ strokeLinecap: "round",
52
+ strokeLinejoin: "round",
53
+ "aria-hidden": "true",
54
+ children: [/* @__PURE__ */ r("path", { d: "M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18z" }), /* @__PURE__ */ r("path", { d: "M12 7v5l3 2" })]
55
+ }), h = (e, t) => d(e.value, t), g = (e) => e.trim() === "" ? "" : l(e);
56
+ function _({ value: i, defaultValue: a, onValueChange: s, step: c, placeholder: d, invalid: _, locale: v, messages: y, ...b }) {
57
+ let x = o(a ?? "") ?? "", [S, C] = n(x), w = i ?? S, T = w === "" ? "" : o(w), E = T ?? w, [D, O] = n(E), k = l(D), A = t(() => {
58
+ let e = new Set(u(c ?? 10));
59
+ return T && e.add(T), k && e.add(k), [...e].sort().map((e) => ({
60
+ value: e,
61
+ label: e
62
+ }));
63
+ }, [
64
+ c,
65
+ T,
66
+ k
67
+ ]);
68
+ return /* @__PURE__ */ r(e, {
69
+ ...b,
70
+ searchable: !0,
71
+ rootClass: "dds-time-picker",
72
+ items: A,
73
+ value: E,
74
+ selectedOption: {
75
+ value: E,
76
+ label: E
77
+ },
78
+ onValueChange: (e) => {
79
+ i === void 0 && C(e), s?.(e);
80
+ },
81
+ onFormReset: () => {
82
+ i === void 0 && C(x);
83
+ },
84
+ onDraftChange: O,
85
+ matchItem: h,
86
+ commitDraft: g,
87
+ triggerContent: m,
88
+ autoHighlight: !0,
89
+ centerSelected: !0,
90
+ inputMode: "numeric",
91
+ virtualization: A.length > p && {},
92
+ messages: f(v ?? "ko-KR", y),
93
+ placeholder: d ?? "HH:MM",
94
+ invalid: _ === !0 || T === null
95
+ });
96
+ }
97
+ //#endregion
98
+ export { _ as t };
99
+
100
+ //# sourceMappingURL=time-picker-BQ7BT6-u.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-picker-BQ7BT6-u.js","names":[],"sources":["../src/time-only.ts","../src/time-picker.tsx"],"sourcesContent":["// Time-of-day values are 24-hour \"HH:MM\" strings; \"\" is empty. Servers may send\n// \"HH:MM:SS\" — it is accepted and shown as HH:MM, but never rewritten until the\n// user picks a time.\nconst API_TIME = /^([01]\\d|2[0-3]):([0-5]\\d)(?::[0-5]\\d(?:\\.\\d+)?)?$/;\n\n/** Canonical \"HH:MM\" for an API value, or null when it is not a time. */\nexport function normalizeTime(value: string): string | null {\n const match = API_TIME.exec(value);\n return match ? `${match[1]}:${match[2]}` : null;\n}\n\nconst pad = (value: number) => String(value).padStart(2, \"0\");\nconst fromParts = (hour: number, minute: number) =>\n hour <= 23 && minute <= 59 ? `${pad(hour)}:${pad(minute)}` : null;\n\n/**\n * What a person typed, read as a complete time: \"9:05\", \"09:05\", \"09:05:00\",\n * \"0905\", or \"905\" when the first two digits cannot be an hour. Full-width\n * colons and dots count as colons. Partial text (\"10\", \"10:4\", and \"103\",\n * which is still on its way to 10:30) is not a time.\n */\nexport function parseTypedTime(text: string): string | null {\n const value = text.trim().replace(/[:.]/g, \":\");\n const separated = /^(\\d{1,2}):(\\d{2})(?::\\d{2})?$/.exec(value);\n if (separated) return fromParts(Number(separated[1]), Number(separated[2]));\n if (/^\\d{4}$/.test(value))\n return fromParts(Number(value.slice(0, 2)), Number(value.slice(2)));\n if (/^\\d{3}$/.test(value) && Number(value.slice(0, 2)) > 23)\n return fromParts(Number(value[0]), Number(value.slice(1)));\n return null;\n}\n\n/** 00:00 up to the last step before 24:00. Steps below one minute become one; a non-number becomes 10. */\nexport function timeOptions(step: number) {\n const minutes = Number.isFinite(step) ? Math.max(1, Math.floor(step)) : 10;\n const values: string[] = [];\n for (let at = 0; at < 24 * 60; at += minutes)\n values.push(`${pad(Math.floor(at / 60))}:${pad(at % 60)}`);\n return values;\n}\n\n/**\n * Whether a \"HH:MM\" option matches partial typing. Digits read as the start of\n * the time (\"10\" → 10:00–10:59, \"1030\" → 10:30); an odd digit count or an\n * impossible hour also tries a leading zero (\"9\" → 09:xx, \"930\" → 09:30).\n * With a colon the hour may be one digit (\"9:3\" → 09:30–09:39).\n */\nexport function matchesTimeQuery(option: string, query: string) {\n const text = query.trim().replace(/[:.]/g, \":\");\n if (text === \"\") return true;\n const typed = parseTypedTime(text);\n if (typed) return option === typed;\n if (text.includes(\":\")) {\n const [hour = \"\", minute = \"\", ...rest] = text.split(\":\");\n if (rest.length || !/^\\d{1,2}$/.test(hour) || !/^\\d{0,2}$/.test(minute))\n return false;\n return option.startsWith(`${hour.padStart(2, \"0\")}:${minute}`);\n }\n if (!/^\\d{1,4}$/.test(text)) return false;\n const digits = option.replace(\":\", \"\");\n if (digits.startsWith(text)) return true;\n return (\n (text.length % 2 === 1 || Number(text.slice(0, 2)) > 23) &&\n digits.startsWith(`0${text}`)\n );\n}\n\nexport interface TimeMessages {\n /** Accessible name of the clock button that opens the list. */\n open?: string;\n /** Accessible name of the clear button. */\n clear?: string;\n /** Status when nothing in the list matches the typed text. */\n empty?: string;\n}\nexport function timeMessages(locale: string, messages: TimeMessages = {}) {\n const ko = /^ko(?:-|$)/i.test(locale);\n return {\n open: ko ? \"시간 목록 열기\" : \"Open time list\",\n clear: ko ? \"시간 지우기\" : \"Clear time\",\n empty: ko ? \"맞는 시간이 없습니다\" : \"No matching time\",\n ...messages,\n };\n}\n","import { useMemo, useState } from \"react\";\nimport type { ChoiceOption } from \"./choice-types\";\nimport { Choice } from \"./internal/choice\";\nimport {\n matchesTimeQuery,\n normalizeTime,\n parseTypedTime,\n timeMessages,\n timeOptions,\n type TimeMessages,\n} from \"./time-only\";\nexport type { TimeMessages } from \"./time-only\";\n\nexport interface TimePickerProps {\n /** 24-hour \"HH:MM\" (\"\" is empty). \"HH:MM:SS\" is accepted and shown as HH:MM. */\n value?: string;\n defaultValue?: string;\n /** Receives \"HH:MM\" or \"\" — only when the person picks, types or clears a time. */\n onValueChange?: (value: string) => void;\n /** Minutes between listed times, from 00:00 (default 10). Typed times off the step are still accepted. */\n step?: number;\n label?: string;\n labelPlacement?: \"outside\" | \"inside\" | \"hidden\";\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n \"aria-describedby\"?: string;\n id?: string;\n name?: string;\n form?: string;\n placeholder?: string;\n clearable?: boolean;\n disabled?: boolean;\n readOnly?: boolean;\n required?: boolean;\n invalid?: boolean;\n /** Chooses the built-in Korean or English text (default \"ko-KR\"). */\n locale?: string;\n messages?: TimeMessages;\n}\n\n/** Above this many rows the list is virtualized (1-minute steps list 1,440 times). */\nconst VIRTUALIZE_AFTER = 300;\n\n// The dds-icons \"clock\" glyph, inlined like DatePicker's calendar so this\n// subpath does not pull in the whole icon set.\nconst clockIcon = (\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.6\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18z\" />\n <path d=\"M12 7v5l3 2\" />\n </svg>\n);\n\nconst matchItem = (item: ChoiceOption, query: string) =>\n matchesTimeQuery(item.value, query);\nconst commitDraft = (text: string) =>\n text.trim() === \"\" ? \"\" : parseTypedTime(text);\n\n/**\n * A time-of-day field: type a time or pick one from the list. Built on the\n * same Ark combobox as Combobox, so list, keyboard and form behavior match.\n */\nexport function TimePicker({\n value,\n defaultValue,\n onValueChange,\n step,\n placeholder,\n invalid,\n locale,\n messages,\n ...shared\n}: TimePickerProps) {\n const initial = normalizeTime(defaultValue ?? \"\") ?? \"\";\n const [internal, setInternal] = useState(initial);\n const raw = value ?? internal;\n const canonical = raw === \"\" ? \"\" : normalizeTime(raw);\n // A value that is not a time is shown as it came, marked invalid, and never rewritten.\n const current = canonical ?? raw;\n const [draft, setDraft] = useState(current);\n const typed = parseTypedTime(draft);\n const items = useMemo<ChoiceOption[]>(() => {\n const values = new Set(timeOptions(step ?? 10));\n if (canonical) values.add(canonical);\n if (typed) values.add(typed);\n return [...values].sort().map((time) => ({ value: time, label: time }));\n }, [step, canonical, typed]);\n return (\n <Choice\n {...shared}\n searchable\n rootClass=\"dds-time-picker\"\n items={items}\n value={current}\n // Only read when the value is not a listed time (an invalid server value).\n selectedOption={{ value: current, label: current }}\n onValueChange={(next) => {\n if (value === undefined) setInternal(next);\n onValueChange?.(next);\n }}\n onFormReset={() => {\n if (value === undefined) setInternal(initial);\n }}\n // Choice owns the text (a React round-trip per keystroke drops fast typing); mirror it.\n onDraftChange={setDraft}\n matchItem={matchItem}\n commitDraft={commitDraft}\n triggerContent={clockIcon}\n autoHighlight\n centerSelected\n inputMode=\"numeric\"\n virtualization={items.length > VIRTUALIZE_AFTER ? {} : false}\n messages={timeMessages(locale ?? \"ko-KR\", messages)}\n placeholder={placeholder ?? \"HH:MM\"}\n invalid={invalid === true || canonical === null}\n />\n );\n}\n"],"mappings":";;;;AAGA,IAAM,IAAW;AAGjB,SAAgB,EAAc,GAA8B;CAC1D,IAAM,IAAQ,EAAS,KAAK,CAAK;CACjC,OAAO,IAAQ,GAAG,EAAM,GAAG,GAAG,EAAM,OAAO;AAC7C;AAEA,IAAM,KAAO,MAAkB,OAAO,CAAK,CAAC,CAAC,SAAS,GAAG,GAAG,GACtD,KAAa,GAAc,MAC/B,KAAQ,MAAM,KAAU,KAAK,GAAG,EAAI,CAAI,EAAE,GAAG,EAAI,CAAM,MAAM;AAQ/D,SAAgB,EAAe,GAA6B;CAC1D,IAAM,IAAQ,EAAK,KAAK,CAAC,CAAC,QAAQ,SAAS,GAAG,GACxC,IAAY,iCAAiC,KAAK,CAAK;CAM7D,OALI,IAAkB,EAAU,OAAO,EAAU,EAAE,GAAG,OAAO,EAAU,EAAE,CAAC,IACtE,UAAU,KAAK,CAAK,IACf,EAAU,OAAO,EAAM,MAAM,GAAG,CAAC,CAAC,GAAG,OAAO,EAAM,MAAM,CAAC,CAAC,CAAC,IAChE,UAAU,KAAK,CAAK,KAAK,OAAO,EAAM,MAAM,GAAG,CAAC,CAAC,IAAI,KAChD,EAAU,OAAO,EAAM,EAAE,GAAG,OAAO,EAAM,MAAM,CAAC,CAAC,CAAC,IACpD;AACT;AAGA,SAAgB,EAAY,GAAc;CACxC,IAAM,IAAU,OAAO,SAAS,CAAI,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,CAAI,CAAC,IAAI,IAClE,IAAmB,CAAC;CAC1B,KAAK,IAAI,IAAK,GAAG,IAAK,MAAS,KAAM,GACnC,EAAO,KAAK,GAAG,EAAI,KAAK,MAAM,IAAK,EAAE,CAAC,EAAE,GAAG,EAAI,IAAK,EAAE,GAAG;CAC3D,OAAO;AACT;AAQA,SAAgB,EAAiB,GAAgB,GAAe;CAC9D,IAAM,IAAO,EAAM,KAAK,CAAC,CAAC,QAAQ,SAAS,GAAG;CAC9C,IAAI,MAAS,IAAI,OAAO;CACxB,IAAM,IAAQ,EAAe,CAAI;CACjC,IAAI,GAAO,OAAO,MAAW;CAC7B,IAAI,EAAK,SAAS,GAAG,GAAG;EACtB,IAAM,CAAC,IAAO,IAAI,IAAS,IAAI,GAAG,KAAQ,EAAK,MAAM,GAAG;EAGxD,OAFI,EAAK,UAAU,CAAC,YAAY,KAAK,CAAI,KAAK,CAAC,YAAY,KAAK,CAAM,IAC7D,KACF,EAAO,WAAW,GAAG,EAAK,SAAS,GAAG,GAAG,EAAE,GAAG,GAAQ;CAC/D;CACA,IAAI,CAAC,YAAY,KAAK,CAAI,GAAG,OAAO;CACpC,IAAM,IAAS,EAAO,QAAQ,KAAK,EAAE;CAErC,OADI,EAAO,WAAW,CAAI,IAAU,MAEjC,EAAK,SAAS,KAAM,KAAK,OAAO,EAAK,MAAM,GAAG,CAAC,CAAC,IAAI,OACrD,EAAO,WAAW,IAAI,GAAM;AAEhC;AAUA,SAAgB,EAAa,GAAgB,IAAyB,CAAC,GAAG;CACxE,IAAM,IAAK,cAAc,KAAK,CAAM;CACpC,OAAO;EACL,MAAM,IAAK,aAAa;EACxB,OAAO,IAAK,WAAW;EACvB,OAAO,IAAK,gBAAgB;EAC5B,GAAG;CACL;AACF;;;AC1CA,IAAM,IAAmB,KAInB,IACJ,kBAAC,OAAD;CACE,OAAM;CACN,QAAO;CACP,SAAQ;CACR,MAAK;CACL,QAAO;CACP,aAAY;CACZ,eAAc;CACd,gBAAe;CACf,eAAY;CATd,UAAA,CAWE,kBAAC,QAAD,EAAM,GAAE,wCAAyC,CAAA,GACjD,kBAAC,QAAD,EAAM,GAAE,cAAe,CAAA,CACpB;IAGD,KAAa,GAAoB,MACrC,EAAiB,EAAK,OAAO,CAAK,GAC9B,KAAe,MACnB,EAAK,KAAK,MAAM,KAAK,KAAK,EAAe,CAAI;AAM/C,SAAgB,EAAW,EACzB,UACA,iBACA,kBACA,SACA,gBACA,YACA,WACA,aACA,GAAG,KACe;CAClB,IAAM,IAAU,EAAc,KAAgB,EAAE,KAAK,IAC/C,CAAC,GAAU,KAAe,EAAS,CAAO,GAC1C,IAAM,KAAS,GACf,IAAY,MAAQ,KAAK,KAAK,EAAc,CAAG,GAE/C,IAAU,KAAa,GACvB,CAAC,GAAO,KAAY,EAAS,CAAO,GACpC,IAAQ,EAAe,CAAK,GAC5B,IAAQ,QAA8B;EAC1C,IAAM,IAAS,IAAI,IAAI,EAAY,KAAQ,EAAE,CAAC;EAG9C,OAFI,KAAW,EAAO,IAAI,CAAS,GAC/B,KAAO,EAAO,IAAI,CAAK,GACpB,CAAC,GAAG,CAAM,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,OAAU;GAAE,OAAO;GAAM,OAAO;EAAK,EAAE;CACxE,GAAG;EAAC;EAAM;EAAW;CAAK,CAAC;CAC3B,OACE,kBAAC,GAAD;EACE,GAAI;EACJ,YAAA;EACA,WAAU;EACH;EACP,OAAO;EAEP,gBAAgB;GAAE,OAAO;GAAS,OAAO;EAAQ;EACjD,gBAAgB,MAAS;GAEvB,AADI,MAAU,KAAA,KAAW,EAAY,CAAI,GACzC,IAAgB,CAAI;EACtB;EACA,mBAAmB;GACjB,AAAI,MAAU,KAAA,KAAW,EAAY,CAAO;EAC9C;EAEA,eAAe;EACJ;EACE;EACb,gBAAgB;EAChB,eAAA;EACA,gBAAA;EACA,WAAU;EACV,gBAAgB,EAAM,SAAS,KAAmB,CAAC;EACnD,UAAU,EAAa,KAAU,SAAS,CAAQ;EAClD,aAAa,KAAe;EAC5B,SAAS,MAAY,MAAQ,MAAc;CAC5C,CAAA;AAEL"}
@@ -0,0 +1,34 @@
1
+ import { type TimeMessages } from "./time-only";
2
+ export type { TimeMessages } from "./time-only";
3
+ export interface TimePickerProps {
4
+ /** 24-hour "HH:MM" ("" is empty). "HH:MM:SS" is accepted and shown as HH:MM. */
5
+ value?: string;
6
+ defaultValue?: string;
7
+ /** Receives "HH:MM" or "" — only when the person picks, types or clears a time. */
8
+ onValueChange?: (value: string) => void;
9
+ /** Minutes between listed times, from 00:00 (default 10). Typed times off the step are still accepted. */
10
+ step?: number;
11
+ label?: string;
12
+ labelPlacement?: "outside" | "inside" | "hidden";
13
+ "aria-label"?: string;
14
+ "aria-labelledby"?: string;
15
+ "aria-describedby"?: string;
16
+ id?: string;
17
+ name?: string;
18
+ form?: string;
19
+ placeholder?: string;
20
+ clearable?: boolean;
21
+ disabled?: boolean;
22
+ readOnly?: boolean;
23
+ required?: boolean;
24
+ invalid?: boolean;
25
+ /** Chooses the built-in Korean or English text (default "ko-KR"). */
26
+ locale?: string;
27
+ messages?: TimeMessages;
28
+ }
29
+ /**
30
+ * A time-of-day field: type a time or pick one from the list. Built on the
31
+ * same Ark combobox as Combobox, so list, keyboard and form behavior match.
32
+ */
33
+ export declare function TimePicker({ value, defaultValue, onValueChange, step, placeholder, invalid, locale, messages, ...shared }: TimePickerProps): import("react").JSX.Element;
34
+ //# sourceMappingURL=time-picker.d.ts.map