@devslab/dds-react 0.21.1 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +18 -0
- package/README.md +46 -0
- package/dist/calendar-view-I_sNlioP.js +145 -0
- package/dist/calendar-view-I_sNlioP.js.map +1 -0
- package/dist/calendar-view.d.ts +16 -0
- package/dist/calendar-view.d.ts.map +1 -0
- package/dist/calendar.d.ts +19 -0
- package/dist/calendar.d.ts.map +1 -0
- package/dist/calendar.js +51 -0
- package/dist/calendar.js.map +1 -0
- package/dist/choice-B3vNMfKK.js +300 -0
- package/dist/choice-B3vNMfKK.js.map +1 -0
- package/dist/choice-types.d.ts +56 -0
- package/dist/choice-types.d.ts.map +1 -0
- package/dist/combobox.d.ts +4 -0
- package/dist/combobox.d.ts.map +1 -0
- package/dist/combobox.js +13 -0
- package/dist/combobox.js.map +1 -0
- package/dist/controls-BajigKUv.js +139 -0
- package/dist/controls-BajigKUv.js.map +1 -0
- package/dist/date-only.d.ts +36 -0
- package/dist/date-only.d.ts.map +1 -0
- package/dist/date-picker.d.ts +12 -1
- package/dist/date-picker.d.ts.map +1 -1
- package/dist/date-picker.js +198 -0
- package/dist/date-picker.js.map +1 -0
- package/dist/dropdown-menu.d.ts +31 -0
- package/dist/dropdown-menu.d.ts.map +1 -0
- package/dist/dropdown-menu.js +73 -0
- package/dist/dropdown-menu.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +201 -454
- package/dist/index.js.map +1 -1
- package/dist/input.d.ts +22 -0
- package/dist/input.d.ts.map +1 -0
- package/dist/input.js +92 -0
- package/dist/input.js.map +1 -0
- package/dist/internal/choice.d.ts +5 -0
- package/dist/internal/choice.d.ts.map +1 -0
- package/dist/popover.d.ts +21 -0
- package/dist/popover.d.ts.map +1 -0
- package/dist/popover.js +49 -0
- package/dist/popover.js.map +1 -0
- package/dist/select-picker.d.ts +4 -0
- package/dist/select-picker.d.ts.map +1 -0
- package/dist/select-picker.js +13 -0
- package/dist/select-picker.js.map +1 -0
- package/package.json +46 -10
- package/styles.css +10 -1
package/README.ko.md
CHANGED
|
@@ -47,3 +47,21 @@ const StatusPill = createStatusPill({
|
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
Unknown closed-domain status values throw to expose incomplete catalogs. Domains listed in `openDomains` instead show the unknown value as escaped text.
|
|
50
|
+
|
|
51
|
+
### DatePicker 직접 입력과 저장
|
|
52
|
+
|
|
53
|
+
`value`/`onChange`는 기존과 같이 날짜 전용 `YYYY-MM-DD` 문자열과 명시적 지우기 `''`만 전달합니다. 입력한 날짜가 완전하고 실재하면 즉시 반영하며 잘못되거나 미완성인 입력을 이전 날짜로 치환하지 않습니다. `required`는 실제 입력의 필수 상태로 전달하고, `readOnly`는 편집과 달력 열기를 막습니다. 잠금 전환 시 열린 달력을 닫습니다. 잘못된 저장 기본값은 원문과 오류를 표시합니다.
|
|
54
|
+
|
|
55
|
+
`onValidityChange(state: DatePickerValidity)`는 원문 `inputValue`, `valid`, 사유(`valid`, `required`, `incomplete`, `invalid`, `rangeUnderflow`, `rangeOverflow`)를 전달합니다. 실제 form은 보이는 입력의 custom validity를 사용합니다. API·버튼·단축키 저장은 반드시 이 콜백의 `valid`를 검사하고 canonical `value`를 저장해야 합니다. 첫 콜백 전에는 저장을 막으세요. 잘못된 draft를 표시하는 동안 부모 `value`에는 이전 유효 날짜가 남을 수 있으므로 `value`만으로 유효성을 판단하면 안 됩니다.
|
|
56
|
+
|
|
57
|
+
엄격한 Gregorian 날짜와 양 끝을 포함한 min/max를 날짜 전용 값으로 검사하며 조용히 보정하지 않습니다. 필수 입력을 비워도 `onChange('')`는 전달하며 validity는 false입니다. 서버의 최종 검증은 유지해야 합니다. 라이브러리 테스트 통과만으로 제품의 저장 요청이 이 검사를 사용하는지 보장하지 않습니다.
|
|
58
|
+
기본 한국어(`ko`, `ko-KR`)의 표시 형식은 `YYYY/MM/DD`이며 `value`, `onChange`, min/max, `DatePickerValidity.value`는 `YYYY-MM-DD`를 유지합니다. `inputValue`는 화면 원문이므로 API에 저장하거나 소비자에서 구분자를 치환하지 마세요. 유효하지 않거나 미완성인 입력과 필수 빈 값의 callback `value`는 null, 선택 빈 값은 ''입니다. 표시/저장 변환은 DDS 공통에서만 처리하며 시간대 때문에 날짜를 이동하지 않습니다.
|
|
59
|
+
|
|
60
|
+
validity callback은 최초 mount, 의미 있는 입력·제약·외부 값 변경을 보고합니다. invalid/partial 입력은 `onChange`를 호출하지 않으므로 dirty/이탈 방지와 발행 확인 취소도 validity callback에 연결해야 합니다. callback 함수 identity만 바뀌면 같은 결과를 재보고하지 않아 inline setState 루프를 막습니다. 새 편집기는 문서 ID·명시적 reset revision을 key로 새로 mount하고 useCallback으로 안정적인 함수를 사용하세요. 같은 날짜여도 새 keyed 편집기는 초기 결과를 받습니다. 유효할 때 canonical value, invalid일 때 raw inputValue로 pristine 상태를 비교하면 표시 언어만 바뀔 때의 오탐과 소비자 포맷 코드를 피할 수 있습니다. 영문 예제의 keyed editor·baseline·previous 패턴을 참고하세요.
|
|
61
|
+
|
|
62
|
+
아직 반영되지 않은 로컬 값과 일치하는 부모 값은 해당 발생 건의 응답으로 보고 최신 draft를 유지합니다. B/C/B 반복을 각각 보존하고 blur/Enter 중복 commit을 막습니다. 미반영 목록에 없는 다른 외부 값은 reset으로 처리합니다. 같은 문자열만으로는 미반영 값과 같은 의도적 reset을 응답과 구별할 수 없으므로 명시적 reset은 keyed editor remount를 사용하세요. 로컬 controlled state는 즉시 갱신하고 서버 저장을 분리하세요.
|
|
63
|
+
## 입력·선택·메뉴·공유 달력
|
|
64
|
+
|
|
65
|
+
Input/InputGroup은 검색 전용이 아닌 일반 입력 기반이며 라벨 배치·내용 지우기를 지원합니다. Combobox/SelectPicker는 가상화를, DropdownMenu/Popover는 접근 가능한 팝업을, Calendar/DatePicker는 공유 달력을 제공합니다.
|
|
66
|
+
|
|
67
|
+
상세 import: `@devslab/dds-react/input`, `/combobox`, `/select-picker`, `/dropdown-menu`, `/popover`, `/calendar`, `/date-picker`. 기존 루트 import와 `styles.css`는 유지합니다. [공통 사용 가이드](https://github.com/devslab-kr/dds/blob/main/docs/components-input-choice-calendar.ko.md)를 참고하세요.
|
package/README.md
CHANGED
|
@@ -53,3 +53,49 @@ const StatusPill = createStatusPill({
|
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
Unknown closed-domain status values throw to expose incomplete catalogs. Domains listed in `openDomains` instead show the unknown value as escaped text.
|
|
56
|
+
|
|
57
|
+
### DatePicker manual input and saving
|
|
58
|
+
|
|
59
|
+
`value`/`onChange` remain canonical date-only `YYYY-MM-DD` strings (or intentional clear `''`). Complete real dates commit while typing; invalid/incomplete text stays visible and never becomes a replacement date. `required` forwards native/accessible required state; `readOnly` prevents edits/calendar opening. Locked transitions close the portal. Invalid persisted values render an error without being parsed or cleared.
|
|
60
|
+
|
|
61
|
+
Subscribe to `onValidityChange(state: DatePickerValidity)` for the exact displayed `inputValue`, canonical `value` (or null while invalid), `valid`, and reason (`valid`, `required`, `incomplete`, `invalid`, `rangeUnderflow`, `rangeOverflow`). Native forms use the visible input's custom validity. API/button/shortcut saves MUST also check the callback state; a canonical parent value may still contain the last valid selection while the visible draft is invalid. Start unknown/blocked until the initial callback. Do not infer validity from `value` alone.
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
const [date, setDate] = useState('2028-02-28');
|
|
65
|
+
const [dateValidity, setDateValidity] = useState<DatePickerValidity | null>(null);
|
|
66
|
+
<DatePicker label="Date" value={date} onChange={setDate}
|
|
67
|
+
required onValidityChange={setDateValidity} />
|
|
68
|
+
// In every save handler (including shortcuts):
|
|
69
|
+
if (!dateValidity?.valid || dateValidity.value === null) return;
|
|
70
|
+
await save({ date: dateValidity.value });
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Strict Gregorian dates and inclusive min/max are validated without timezone normalization or silent clamping. Clearing always reports `onChange('')`; required clear remains invalid. Keep authoritative server validation. Library tests alone do not establish that a consumer's request handler uses this guard.
|
|
74
|
+
Korean (`ko`, `ko-KR`, including the default locale) displays `YYYY/MM/DD`; `value`, `onChange`, min/max and `DatePickerValidity.value` remain `YYYY-MM-DD`. Consumers must not replace separators or persist `inputValue`. ISO paste is accepted by the common parser and normalized only once it is a complete valid date. Date-only values do not shift with timeZone.
|
|
75
|
+
|
|
76
|
+
`onValidityChange` reports initially and on semantic draft/constraint/controlled-value changes, including invalid/partial edits that do not emit `onChange`. Use it for dirty/navigation guards and to cancel a pending publish confirmation when input changes. Replacing only the callback function does not replay an identical report: this prevents inline setState render loops. Key the owning editor by document identity/reset revision, and keep callbacks stable with useCallback. A new keyed instance receives a fresh initial report even for an identical date. Compare canonical value for valid dates and the raw inputValue for invalid drafts when tracking the pristine baseline; do not generate a consumer-side display formatter. Remount/reset that baseline only on an intentional editor change/reset.
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
<PolicyDateEditor key={`${document.id}:${document.revision}`} document={document} />
|
|
80
|
+
// Inside PolicyDateEditor (one editor identity):
|
|
81
|
+
const baseline = useRef<string | undefined>(undefined);
|
|
82
|
+
const previous = useRef<string | undefined>(undefined);
|
|
83
|
+
const onDateValidity = useCallback((state: DatePickerValidity) => {
|
|
84
|
+
setDateValidity(state);
|
|
85
|
+
const stateKey = JSON.stringify(state.valid ? [true, state.value] : [false, state.inputValue]);
|
|
86
|
+
if (baseline.current === undefined) baseline.current = stateKey;
|
|
87
|
+
setDirty(stateKey !== baseline.current);
|
|
88
|
+
if (previous.current !== undefined && previous.current !== stateKey) {
|
|
89
|
+
closePublishConfirmation();
|
|
90
|
+
}
|
|
91
|
+
previous.current = stateKey;
|
|
92
|
+
}, [closePublishConfirmation]); // Keep closePublishConfirmation stable too.
|
|
93
|
+
// Every button/API/shortcut Save checks valid and persists state.value only.
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Delayed controlled echoes matching an outstanding local emission acknowledge that occurrence without replacing a newer draft. Repeated B/C/B is retained as three occurrences; duplicate blur/Enter does not emit again. A changed value not among pending emissions is an external reset and clears pending history. The string-only API cannot distinguish an intentional reset to a still-outstanding identical value from its acknowledgement; use a keyed editor remount for an explicit reset. Keep local controlled state synchronous and persist to the server separately. Unacknowledged emissions are retained until acknowledgement, external reset or unmount.
|
|
97
|
+
## Inputs, choices, overlays and shared calendars
|
|
98
|
+
|
|
99
|
+
Input/InputGroup are general-purpose native inputs, not search-only, with label placement and clear actions. Combobox/SelectPicker support fixed-row virtualization; DropdownMenu/Popover provide accessible overlays; Calendar/DatePicker share a calendar view.
|
|
100
|
+
|
|
101
|
+
Use explicit subpaths `@devslab/dds-react/input`, `/combobox`, `/select-picker`, `/dropdown-menu`, `/popover`, `/calendar`, `/date-picker`, or the existing root exports. Keep importing `styles.css` once. See the [common usage guide](https://github.com/devslab-kr/dds/blob/main/docs/components-input-choice-calendar.md).
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { t as e } from "./controls-BajigKUv.js";
|
|
2
|
+
import { Fragment as t, jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
3
|
+
import { DatePicker as i } from "@ark-ui/react/date-picker";
|
|
4
|
+
//#region src/date-only.ts
|
|
5
|
+
function a(e) {
|
|
6
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(e)) return !1;
|
|
7
|
+
let t = Number(e.slice(0, 4)), n = Number(e.slice(5, 7)), r = Number(e.slice(8, 10));
|
|
8
|
+
return t < 1 || n < 1 || n > 12 || r < 1 ? !1 : r <= [
|
|
9
|
+
31,
|
|
10
|
+
t % 4 == 0 && (t % 100 != 0 || t % 400 == 0) ? 29 : 28,
|
|
11
|
+
31,
|
|
12
|
+
30,
|
|
13
|
+
31,
|
|
14
|
+
30,
|
|
15
|
+
31,
|
|
16
|
+
31,
|
|
17
|
+
30,
|
|
18
|
+
31,
|
|
19
|
+
30,
|
|
20
|
+
31
|
|
21
|
+
][n - 1];
|
|
22
|
+
}
|
|
23
|
+
function o(e) {
|
|
24
|
+
return /^ko(?:-|$)/i.test(e);
|
|
25
|
+
}
|
|
26
|
+
function s(e, t) {
|
|
27
|
+
return o(t) && a(e) ? e.replaceAll("-", "/") : e;
|
|
28
|
+
}
|
|
29
|
+
function c(e, t) {
|
|
30
|
+
return o(t) && /^\d{4}\/\d{2}\/\d{2}$/.test(e) ? e.replaceAll("/", "-") : e;
|
|
31
|
+
}
|
|
32
|
+
function l(e, t, n, r, i = "en-US") {
|
|
33
|
+
let s = c(e, i), l = e.length < 10 && (/^\d{0,4}(?:-\d{0,2}(?:-\d{0,2})?)?$/.test(e) || o(i) && /^\d{0,4}(?:\/\d{0,2}(?:\/\d{0,2})?)?$/.test(e)), u = "valid";
|
|
34
|
+
return e === "" ? u = t ? "required" : "valid" : a(s) ? n && !a(n) || r && !a(r) || n && r && n > r ? u = "invalid" : n && s < n ? u = "rangeUnderflow" : r && s > r && (u = "rangeOverflow") : u = l ? "incomplete" : "invalid", {
|
|
35
|
+
inputValue: e,
|
|
36
|
+
value: u === "valid" ? s : null,
|
|
37
|
+
valid: u === "valid",
|
|
38
|
+
reason: u
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function u(e, t = {}) {
|
|
42
|
+
let n = o(e);
|
|
43
|
+
return {
|
|
44
|
+
previousMonth: n ? "이전 달" : "Previous month",
|
|
45
|
+
nextMonth: n ? "다음 달" : "Next month",
|
|
46
|
+
today: n ? "오늘" : "Today",
|
|
47
|
+
clear: n ? "선택 날짜 지우기" : "Clear selected date",
|
|
48
|
+
openCalendar: n ? "달력 열기" : "Open calendar",
|
|
49
|
+
selectDate: (e) => n ? `${e} 선택` : `Choose ${e}`,
|
|
50
|
+
required: n ? "날짜를 입력해 주세요." : "Enter a date.",
|
|
51
|
+
invalid: n ? "YYYY/MM/DD 형식의 유효한 범위 내 날짜를 입력해 주세요." : "Enter a valid date within the allowed range (YYYY-MM-DD).",
|
|
52
|
+
...t
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function d(e) {
|
|
56
|
+
let t = new Intl.DateTimeFormat("en-US-u-ca-gregory-nu-latn", {
|
|
57
|
+
timeZone: e,
|
|
58
|
+
year: "numeric",
|
|
59
|
+
month: "2-digit",
|
|
60
|
+
day: "2-digit"
|
|
61
|
+
}).formatToParts(/* @__PURE__ */ new Date()), n = (e) => t.find((t) => t.type === e).value;
|
|
62
|
+
return `${n("year").padStart(4, "0")}-${n("month")}-${n("day")}`;
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/calendar-view.tsx
|
|
66
|
+
function f({ next: e = !1 }) {
|
|
67
|
+
return /* @__PURE__ */ n("svg", {
|
|
68
|
+
width: "18",
|
|
69
|
+
height: "18",
|
|
70
|
+
viewBox: "0 0 24 24",
|
|
71
|
+
fill: "none",
|
|
72
|
+
stroke: "currentColor",
|
|
73
|
+
strokeWidth: "1.5",
|
|
74
|
+
"aria-hidden": "true",
|
|
75
|
+
children: /* @__PURE__ */ n("path", { d: e ? "m9 5 7 7-7 7" : "m15 5-7 7 7 7" })
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function p({ locale: a = "ko-KR", timeZone: o = "Asia/Seoul", min: s, max: c, messages: p, disabled: m, readOnly: h, showToday: g = !0, showClear: _ = !0, onClear: v }) {
|
|
79
|
+
let y = u(a, p), b = m || h;
|
|
80
|
+
return /* @__PURE__ */ n(i.View, {
|
|
81
|
+
view: "day",
|
|
82
|
+
children: /* @__PURE__ */ n(i.Context, { children: (u) => /* @__PURE__ */ r(t, { children: [
|
|
83
|
+
/* @__PURE__ */ r(i.ViewControl, {
|
|
84
|
+
className: "dds-date-picker-header",
|
|
85
|
+
children: [
|
|
86
|
+
/* @__PURE__ */ n(i.PrevTrigger, {
|
|
87
|
+
asChild: !0,
|
|
88
|
+
children: /* @__PURE__ */ n(e, {
|
|
89
|
+
type: "button",
|
|
90
|
+
tone: "secondary",
|
|
91
|
+
"aria-label": y.previousMonth,
|
|
92
|
+
children: /* @__PURE__ */ n(f, {})
|
|
93
|
+
})
|
|
94
|
+
}),
|
|
95
|
+
/* @__PURE__ */ n(i.RangeText, {}),
|
|
96
|
+
/* @__PURE__ */ n(i.NextTrigger, {
|
|
97
|
+
asChild: !0,
|
|
98
|
+
children: /* @__PURE__ */ n(e, {
|
|
99
|
+
type: "button",
|
|
100
|
+
tone: "secondary",
|
|
101
|
+
"aria-label": y.nextMonth,
|
|
102
|
+
children: /* @__PURE__ */ n(f, { next: !0 })
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
]
|
|
106
|
+
}),
|
|
107
|
+
/* @__PURE__ */ r(i.Table, {
|
|
108
|
+
className: "dds-date-picker-table",
|
|
109
|
+
children: [/* @__PURE__ */ n(i.TableHead, { children: /* @__PURE__ */ n(i.TableRow, { children: u.weekDays.map((e, t) => /* @__PURE__ */ n(i.TableHeader, { children: e.short }, t)) }) }), /* @__PURE__ */ n(i.TableBody, { children: u.weeks.map((e, t) => /* @__PURE__ */ n(i.TableRow, { children: e.map((e) => /* @__PURE__ */ n(i.TableCell, {
|
|
110
|
+
value: e,
|
|
111
|
+
children: /* @__PURE__ */ n(i.TableCellTrigger, { children: e.day })
|
|
112
|
+
}, e.toString())) }, t)) })]
|
|
113
|
+
}),
|
|
114
|
+
/* @__PURE__ */ r("div", {
|
|
115
|
+
className: "dds-date-picker-footer",
|
|
116
|
+
children: [g && /* @__PURE__ */ n(e, {
|
|
117
|
+
type: "button",
|
|
118
|
+
tone: "secondary",
|
|
119
|
+
disabled: b || !l(d(o), !1, s, c, a).valid,
|
|
120
|
+
onClick: () => {
|
|
121
|
+
!b && l(d(o), !1, s, c, a).valid && u.selectToday();
|
|
122
|
+
},
|
|
123
|
+
children: y.today
|
|
124
|
+
}), _ && /* @__PURE__ */ n(i.ClearTrigger, {
|
|
125
|
+
hidden: !1,
|
|
126
|
+
asChild: !0,
|
|
127
|
+
children: /* @__PURE__ */ n(e, {
|
|
128
|
+
type: "button",
|
|
129
|
+
tone: "secondary",
|
|
130
|
+
disabled: b,
|
|
131
|
+
onPointerDown: (e) => e.preventDefault(),
|
|
132
|
+
onClick: () => {
|
|
133
|
+
b || v?.();
|
|
134
|
+
},
|
|
135
|
+
children: y.clear
|
|
136
|
+
})
|
|
137
|
+
})]
|
|
138
|
+
})
|
|
139
|
+
] }) })
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
//#endregion
|
|
143
|
+
export { a, s as i, c as n, o, u as r, l as s, p as t };
|
|
144
|
+
|
|
145
|
+
//# sourceMappingURL=calendar-view-I_sNlioP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calendar-view-I_sNlioP.js","names":[],"sources":["../src/date-only.ts","../src/calendar-view.tsx"],"sourcesContent":["export interface DatePickerValidity {\n /** Exact displayed draft; never persist this display text as an API date. */\n inputValue: string;\n /** Valid ISO date, optional empty string, or null when invalid/incomplete. */\n value: string | null;\n valid: boolean;\n reason:\n | \"valid\"\n | \"required\"\n | \"incomplete\"\n | \"invalid\"\n | \"rangeUnderflow\"\n | \"rangeOverflow\";\n}\n// Date-only Gregorian validation: never let a permissive parser repair user text.\nexport function isCalendarDate(value: string) {\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) return false;\n const year = Number(value.slice(0, 4)),\n month = Number(value.slice(5, 7)),\n day = Number(value.slice(8, 10));\n if (year < 1 || month < 1 || month > 12 || day < 1) return false;\n const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);\n return (\n day <=\n [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1]!\n );\n}\nexport function isKorean(locale: string) {\n return /^ko(?:-|$)/i.test(locale);\n}\nexport function displayDate(value: string, locale: string) {\n return isKorean(locale) && isCalendarDate(value)\n ? value.replaceAll(\"-\", \"/\")\n : value;\n}\nexport function canonicalInput(inputValue: string, locale: string) {\n return isKorean(locale) && /^\\d{4}\\/\\d{2}\\/\\d{2}$/.test(inputValue)\n ? inputValue.replaceAll(\"/\", \"-\")\n : inputValue;\n}\nexport function validateInput(\n inputValue: string,\n required: boolean,\n min?: string,\n max?: string,\n locale = \"en-US\",\n): DatePickerValidity {\n const canonical = canonicalInput(inputValue, locale);\n const incomplete =\n inputValue.length < 10 &&\n (/^\\d{0,4}(?:-\\d{0,2}(?:-\\d{0,2})?)?$/.test(inputValue) ||\n (isKorean(locale) &&\n /^\\d{0,4}(?:\\/\\d{0,2}(?:\\/\\d{0,2})?)?$/.test(inputValue)));\n let reason: DatePickerValidity[\"reason\"] = \"valid\";\n if (inputValue === \"\") reason = required ? \"required\" : \"valid\";\n else if (!isCalendarDate(canonical))\n reason = incomplete ? \"incomplete\" : \"invalid\";\n else if (\n (min && !isCalendarDate(min)) ||\n (max && !isCalendarDate(max)) ||\n (min && max && min > max)\n )\n reason = \"invalid\";\n else if (min && canonical < min) reason = \"rangeUnderflow\";\n else if (max && canonical > max) reason = \"rangeOverflow\";\n return {\n inputValue,\n value: reason === \"valid\" ? canonical : null,\n valid: reason === \"valid\",\n reason,\n };\n}\n\nexport interface CalendarMessages {\n previousMonth?: string;\n nextMonth?: string;\n today?: string;\n clear?: string;\n openCalendar?: string;\n selectDate?: (dateText: string) => string;\n required?: string;\n invalid?: string;\n}\nexport function dateMessages(locale: string, messages: CalendarMessages = {}) {\n const ko = isKorean(locale);\n return {\n previousMonth: ko ? \"이전 달\" : \"Previous month\",\n nextMonth: ko ? \"다음 달\" : \"Next month\",\n today: ko ? \"오늘\" : \"Today\",\n clear: ko ? \"선택 날짜 지우기\" : \"Clear selected date\",\n openCalendar: ko ? \"달력 열기\" : \"Open calendar\",\n selectDate: (text: string) => (ko ? `${text} 선택` : `Choose ${text}`),\n required: ko ? \"날짜를 입력해 주세요.\" : \"Enter a date.\",\n invalid: ko\n ? \"YYYY/MM/DD 형식의 유효한 범위 내 날짜를 입력해 주세요.\"\n : \"Enter a valid date within the allowed range (YYYY-MM-DD).\",\n ...messages,\n };\n}\n\n/** Today is the sole timestamp boundary; stored dates remain Gregorian date-only strings. */\nexport function todayDate(timeZone: string) {\n const parts = new Intl.DateTimeFormat(\"en-US-u-ca-gregory-nu-latn\", {\n timeZone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n }).formatToParts(new Date());\n const part = (type: string) =>\n parts.find((item) => item.type === type)!.value;\n return `${part(\"year\").padStart(4, \"0\")}-${part(\"month\")}-${part(\"day\")}`;\n}\n","import { DatePicker as ArkDatePicker } from \"@ark-ui/react/date-picker\";\nimport { Button } from \"./controls\";\nimport {\n dateMessages,\n todayDate,\n validateInput,\n type CalendarMessages,\n} from \"./date-only\";\nfunction Chevron({ next = false }: { next?: boolean }) {\n return (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n aria-hidden=\"true\"\n >\n <path d={next ? \"m9 5 7 7-7 7\" : \"m15 5-7 7 7 7\"} />\n </svg>\n );\n}\nexport interface CalendarViewProps {\n timeZone?: string | undefined;\n min?: string | undefined;\n max?: string | undefined;\n locale?: string | undefined;\n messages?: CalendarMessages | undefined;\n disabled?: boolean | undefined;\n readOnly?: boolean | undefined;\n showToday?: boolean | undefined;\n showClear?: boolean | undefined;\n onClear?: (() => void) | undefined;\n}\n/** Internal view: the caller owns the single Ark root and its selection. */\nexport function CalendarView({\n locale = \"ko-KR\",\n timeZone = \"Asia/Seoul\",\n min,\n max,\n messages,\n disabled,\n readOnly,\n showToday = true,\n showClear = true,\n onClear,\n}: CalendarViewProps) {\n const text = dateMessages(locale, messages),\n locked = disabled || readOnly;\n return (\n <ArkDatePicker.View view=\"day\">\n <ArkDatePicker.Context>\n {(picker) => (\n <>\n <ArkDatePicker.ViewControl className=\"dds-date-picker-header\">\n <ArkDatePicker.PrevTrigger asChild>\n <Button\n type=\"button\"\n tone=\"secondary\"\n aria-label={text.previousMonth}\n >\n <Chevron />\n </Button>\n </ArkDatePicker.PrevTrigger>\n <ArkDatePicker.RangeText />\n <ArkDatePicker.NextTrigger asChild>\n <Button\n type=\"button\"\n tone=\"secondary\"\n aria-label={text.nextMonth}\n >\n <Chevron next />\n </Button>\n </ArkDatePicker.NextTrigger>\n </ArkDatePicker.ViewControl>\n <ArkDatePicker.Table className=\"dds-date-picker-table\">\n <ArkDatePicker.TableHead>\n <ArkDatePicker.TableRow>\n {picker.weekDays.map((day, index) => (\n <ArkDatePicker.TableHeader key={index}>\n {day.short}\n </ArkDatePicker.TableHeader>\n ))}\n </ArkDatePicker.TableRow>\n </ArkDatePicker.TableHead>\n <ArkDatePicker.TableBody>\n {picker.weeks.map((week, index) => (\n <ArkDatePicker.TableRow key={index}>\n {week.map((day) => (\n <ArkDatePicker.TableCell key={day.toString()} value={day}>\n <ArkDatePicker.TableCellTrigger>\n {day.day}\n </ArkDatePicker.TableCellTrigger>\n </ArkDatePicker.TableCell>\n ))}\n </ArkDatePicker.TableRow>\n ))}\n </ArkDatePicker.TableBody>\n </ArkDatePicker.Table>\n <div className=\"dds-date-picker-footer\">\n {showToday && (\n <Button\n type=\"button\"\n tone=\"secondary\"\n disabled={\n locked ||\n !validateInput(todayDate(timeZone), false, min, max, locale)\n .valid\n }\n onClick={() => {\n if (\n !locked &&\n validateInput(\n todayDate(timeZone),\n false,\n min,\n max,\n locale,\n ).valid\n )\n picker.selectToday();\n }}\n >\n {text.today}\n </Button>\n )}\n {showClear && (\n <ArkDatePicker.ClearTrigger hidden={false} asChild>\n <Button\n type=\"button\"\n tone=\"secondary\"\n disabled={locked}\n onPointerDown={(event) => event.preventDefault()}\n onClick={() => {\n if (!locked) onClear?.();\n }}\n >\n {text.clear}\n </Button>\n </ArkDatePicker.ClearTrigger>\n )}\n </div>\n </>\n )}\n </ArkDatePicker.Context>\n </ArkDatePicker.View>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type CalendarMessages } from "./date-only";
|
|
2
|
+
export interface CalendarViewProps {
|
|
3
|
+
timeZone?: string | undefined;
|
|
4
|
+
min?: string | undefined;
|
|
5
|
+
max?: string | undefined;
|
|
6
|
+
locale?: string | undefined;
|
|
7
|
+
messages?: CalendarMessages | undefined;
|
|
8
|
+
disabled?: boolean | undefined;
|
|
9
|
+
readOnly?: boolean | undefined;
|
|
10
|
+
showToday?: boolean | undefined;
|
|
11
|
+
showClear?: boolean | undefined;
|
|
12
|
+
onClear?: (() => void) | undefined;
|
|
13
|
+
}
|
|
14
|
+
/** Internal view: the caller owns the single Ark root and its selection. */
|
|
15
|
+
export declare function CalendarView({ locale, timeZone, min, max, messages, disabled, readOnly, showToday, showClear, onClear, }: CalendarViewProps): import("react").JSX.Element;
|
|
16
|
+
//# sourceMappingURL=calendar-view.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calendar-view.d.ts","sourceRoot":"","sources":["../src/calendar-view.tsx"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,gBAAgB,EACtB,MAAM,aAAa,CAAC;AAgBrB,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,QAAQ,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/B,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAChC,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAChC,OAAO,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;CACpC;AACD,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,EAC3B,MAAgB,EAChB,QAAuB,EACvB,GAAG,EACH,GAAG,EACH,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,SAAgB,EAChB,SAAgB,EAChB,OAAO,GACR,EAAE,iBAAiB,+BAqGnB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type CalendarMessages } from "./date-only";
|
|
2
|
+
export interface CalendarProps {
|
|
3
|
+
label: string;
|
|
4
|
+
value: string;
|
|
5
|
+
onChange: (value: string) => void;
|
|
6
|
+
locale?: string;
|
|
7
|
+
timeZone?: string;
|
|
8
|
+
min?: string;
|
|
9
|
+
max?: string;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
readOnly?: boolean;
|
|
12
|
+
startOfWeek?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
13
|
+
messages?: CalendarMessages;
|
|
14
|
+
showToday?: boolean;
|
|
15
|
+
clearable?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export type { CalendarMessages } from "./date-only";
|
|
18
|
+
export declare function Calendar({ label, value, onChange, locale, timeZone, min, max, disabled, readOnly, startOfWeek, messages, showToday, clearable, }: CalendarProps): import("react").JSX.Element;
|
|
19
|
+
//# sourceMappingURL=calendar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../src/calendar.tsx"],"names":[],"mappings":"AAKA,OAAO,EAIL,KAAK,gBAAgB,EACtB,MAAM,aAAa,CAAC;AACrB,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACxC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AACD,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,KAAK,EACL,QAAQ,EACR,MAAgB,EAChB,QAAuB,EACvB,GAAG,EACH,GAAG,EACH,QAAgB,EAChB,QAAgB,EAChB,WAAe,EACf,QAAQ,EACR,SAAgB,EAChB,SAAgB,GACjB,EAAE,aAAa,+BAmDf"}
|
package/dist/calendar.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { a as e, r as t, s as n, t as r } from "./calendar-view-I_sNlioP.js";
|
|
2
|
+
import { jsx as i } from "react/jsx-runtime";
|
|
3
|
+
import { DatePicker as a, parseDate as o } from "@ark-ui/react/date-picker";
|
|
4
|
+
//#region src/calendar.tsx
|
|
5
|
+
function s({ label: s, value: c, onChange: l, locale: u = "ko-KR", timeZone: d = "Asia/Seoul", min: f, max: p, disabled: m = !1, readOnly: h = !1, startOfWeek: g = 0, messages: _, showToday: v = !0, clearable: y = !0 }) {
|
|
6
|
+
let b = t(u, _);
|
|
7
|
+
return /* @__PURE__ */ i(a.Root, {
|
|
8
|
+
className: "dds-calendar",
|
|
9
|
+
"aria-label": s,
|
|
10
|
+
role: "group",
|
|
11
|
+
inline: !0,
|
|
12
|
+
locale: u,
|
|
13
|
+
timeZone: d,
|
|
14
|
+
disabled: m,
|
|
15
|
+
readOnly: h,
|
|
16
|
+
min: f && e(f) ? o(f) : void 0,
|
|
17
|
+
max: p && e(p) ? o(p) : void 0,
|
|
18
|
+
startOfWeek: g,
|
|
19
|
+
minView: "day",
|
|
20
|
+
maxView: "day",
|
|
21
|
+
fixedWeeks: !0,
|
|
22
|
+
value: e(c) ? [o(c)] : [],
|
|
23
|
+
translations: {
|
|
24
|
+
dayCell: (e) => b.selectDate(e.valueText),
|
|
25
|
+
clearTrigger: b.clear
|
|
26
|
+
},
|
|
27
|
+
onValueChange: (e) => {
|
|
28
|
+
let t = e.value[0]?.toString() ?? "";
|
|
29
|
+
!m && !h && n(t, !1, f, p, u).valid && l(t);
|
|
30
|
+
},
|
|
31
|
+
children: /* @__PURE__ */ i(a.Content, {
|
|
32
|
+
className: "dds-date-picker-content",
|
|
33
|
+
"aria-label": s,
|
|
34
|
+
children: /* @__PURE__ */ i(r, {
|
|
35
|
+
timeZone: d,
|
|
36
|
+
min: f,
|
|
37
|
+
max: p,
|
|
38
|
+
locale: u,
|
|
39
|
+
messages: _,
|
|
40
|
+
disabled: m,
|
|
41
|
+
readOnly: h,
|
|
42
|
+
showToday: v,
|
|
43
|
+
showClear: y
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { s as Calendar };
|
|
50
|
+
|
|
51
|
+
//# sourceMappingURL=calendar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calendar.js","names":[],"sources":["../src/calendar.tsx"],"sourcesContent":["import {\n DatePicker as ArkDatePicker,\n parseDate,\n} from \"@ark-ui/react/date-picker\";\nimport { CalendarView } from \"./calendar-view\";\nimport {\n isCalendarDate,\n validateInput,\n dateMessages,\n type CalendarMessages,\n} from \"./date-only\";\nexport interface CalendarProps {\n label: string;\n value: string;\n onChange: (value: string) => void;\n locale?: string;\n timeZone?: string;\n min?: string;\n max?: string;\n disabled?: boolean;\n readOnly?: boolean;\n startOfWeek?: 0 | 1 | 2 | 3 | 4 | 5 | 6;\n messages?: CalendarMessages;\n showToday?: boolean;\n clearable?: boolean;\n}\nexport type { CalendarMessages } from \"./date-only\";\nexport function Calendar({\n label,\n value,\n onChange,\n locale = \"ko-KR\",\n timeZone = \"Asia/Seoul\",\n min,\n max,\n disabled = false,\n readOnly = false,\n startOfWeek = 0,\n messages,\n showToday = true,\n clearable = true,\n}: CalendarProps) {\n const text = dateMessages(locale, messages);\n return (\n <ArkDatePicker.Root\n className=\"dds-calendar\"\n aria-label={label}\n role=\"group\"\n inline\n locale={locale}\n timeZone={timeZone}\n disabled={disabled}\n readOnly={readOnly}\n min={min && isCalendarDate(min) ? parseDate(min) : undefined}\n max={max && isCalendarDate(max) ? parseDate(max) : undefined}\n startOfWeek={startOfWeek}\n minView=\"day\"\n maxView=\"day\"\n fixedWeeks\n value={isCalendarDate(value) ? [parseDate(value)] : []}\n translations={{\n dayCell: (state) => text.selectDate(state.valueText),\n clearTrigger: text.clear,\n }}\n onValueChange={(details) => {\n const next = details.value[0]?.toString() ?? \"\";\n if (\n !disabled &&\n !readOnly &&\n validateInput(next, false, min, max, locale).valid\n )\n onChange(next);\n }}\n >\n <ArkDatePicker.Content\n className=\"dds-date-picker-content\"\n aria-label={label}\n >\n <CalendarView\n timeZone={timeZone}\n min={min}\n max={max}\n locale={locale}\n messages={messages}\n disabled={disabled}\n readOnly={readOnly}\n showToday={showToday}\n showClear={clearable}\n />\n </ArkDatePicker.Content>\n </ArkDatePicker.Root>\n );\n}\n"],"mappings":";;;;AA2BA,SAAgB,EAAS,EACvB,UACA,UACA,aACA,YAAS,SACT,cAAW,cACX,QACA,QACA,cAAW,IACX,cAAW,IACX,iBAAc,GACd,aACA,eAAY,IACZ,eAAY,MACI;CAChB,IAAM,IAAO,EAAa,GAAQ,CAAQ;CAC1C,OACE,kBAAC,EAAc,MAAf;EACE,WAAU;EACV,cAAY;EACZ,MAAK;EACL,QAAA;EACQ;EACE;EACA;EACA;EACV,KAAK,KAAO,EAAe,CAAG,IAAI,EAAU,CAAG,IAAI,KAAA;EACnD,KAAK,KAAO,EAAe,CAAG,IAAI,EAAU,CAAG,IAAI,KAAA;EACtC;EACb,SAAQ;EACR,SAAQ;EACR,YAAA;EACA,OAAO,EAAe,CAAK,IAAI,CAAC,EAAU,CAAK,CAAC,IAAI,CAAC;EACrD,cAAc;GACZ,UAAU,MAAU,EAAK,WAAW,EAAM,SAAS;GACnD,cAAc,EAAK;EACrB;EACA,gBAAgB,MAAY;GAC1B,IAAM,IAAO,EAAQ,MAAM,EAAE,EAAE,SAAS,KAAK;GAC7C,AACE,CAAC,KACD,CAAC,KACD,EAAc,GAAM,IAAO,GAAK,GAAK,CAAM,CAAC,CAAC,SAE7C,EAAS,CAAI;EACjB;EAEA,UAAA,kBAAC,EAAc,SAAf;GACE,WAAU;GACV,cAAY;GAEZ,UAAA,kBAAC,GAAD;IACY;IACL;IACA;IACG;IACE;IACA;IACA;IACC;IACX,WAAW;GACZ,CAAA;EACoB,CAAA;CACL,CAAA;AAExB"}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { useEffect as e, useMemo as t, useRef as n, useState as r } from "react";
|
|
2
|
+
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
3
|
+
import { Portal as o } from "@ark-ui/react/portal";
|
|
4
|
+
import { Combobox as s, createListCollection as c } from "@ark-ui/react/combobox";
|
|
5
|
+
import { Select as l } from "@ark-ui/react/select";
|
|
6
|
+
import { defaultRangeExtractor as u, useVirtualizer as d } from "@tanstack/react-virtual";
|
|
7
|
+
//#region src/choice-types.ts
|
|
8
|
+
function f(e) {
|
|
9
|
+
let t = /* @__PURE__ */ new Set();
|
|
10
|
+
for (let n of e) {
|
|
11
|
+
if (!n.value || t.has(n.value)) throw Error(`DDS choice: option IDs must be nonempty and unique (${JSON.stringify(n.value)})`);
|
|
12
|
+
t.add(n.value);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
var p = {
|
|
16
|
+
open: "선택 열기",
|
|
17
|
+
clear: "선택 지우기",
|
|
18
|
+
loading: "불러오는 중",
|
|
19
|
+
empty: "결과 없음",
|
|
20
|
+
error: "불러오지 못했습니다",
|
|
21
|
+
retry: "다시 시도"
|
|
22
|
+
};
|
|
23
|
+
function m() {
|
|
24
|
+
return globalThis.process?.env?.NODE_ENV !== "production";
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/internal/choice.tsx
|
|
28
|
+
var h = {
|
|
29
|
+
position: "absolute",
|
|
30
|
+
width: 1,
|
|
31
|
+
height: 1,
|
|
32
|
+
padding: 0,
|
|
33
|
+
border: 0,
|
|
34
|
+
clipPath: "inset(50%)",
|
|
35
|
+
overflow: "hidden",
|
|
36
|
+
whiteSpace: "nowrap"
|
|
37
|
+
};
|
|
38
|
+
function g(g) {
|
|
39
|
+
f(g.items);
|
|
40
|
+
let [_, v] = r(g.defaultValue ?? ""), y = g.value ?? _, b = g.items.find((e) => e.value === y) ?? (g.selectedOption?.value === y ? g.selectedOption : void 0), x = b?.label ?? y, [S, C] = r(g.defaultInputValue ?? x), w = g.inputValue ?? S, [T, E] = r(!1), [D, O] = r(null), k = n(null), A = n(null), j = n(null), [M, N] = r(null);
|
|
41
|
+
e(() => {
|
|
42
|
+
let e = k.current;
|
|
43
|
+
e && N(e.closest("[role=\"dialog\"], [role=\"alertdialog\"]") ?? e.ownerDocument.body);
|
|
44
|
+
}, []);
|
|
45
|
+
let P = n(!1), F = n({
|
|
46
|
+
value: y,
|
|
47
|
+
label: x
|
|
48
|
+
}), I = n(null), L = (e) => g.items.find((t) => t.value === e)?.label ?? (g.selectedOption?.value === e ? g.selectedOption.label : e), R = {
|
|
49
|
+
...p,
|
|
50
|
+
...g.messages
|
|
51
|
+
}, z = g.disabled || g.readOnly, B = (e) => {
|
|
52
|
+
(I.current?.from !== w || I.current.to !== e) && (I.current = null, g.inputValue === void 0 && C(e), g.onInputValueChange?.(e));
|
|
53
|
+
}, V = (e) => {
|
|
54
|
+
e !== w && (I.current?.from !== w || I.current.to !== e) && (I.current = {
|
|
55
|
+
from: w,
|
|
56
|
+
to: e
|
|
57
|
+
}, g.inputValue === void 0 ? C(e) : g.onInputValueChange?.(e));
|
|
58
|
+
}, H = (e) => {
|
|
59
|
+
g.value === void 0 && v(e), g.onValueChange?.(e);
|
|
60
|
+
};
|
|
61
|
+
e(() => {
|
|
62
|
+
(F.current.value !== y || F.current.label !== x) && (F.current = {
|
|
63
|
+
value: y,
|
|
64
|
+
label: x
|
|
65
|
+
}, V(x));
|
|
66
|
+
}, [y, x]), e(() => {
|
|
67
|
+
m() && y && !b && console.warn(`DDS choice: no label supplied for selected ID ${JSON.stringify(y)}; pass selectedOption when the selection is outside items.`);
|
|
68
|
+
}, [y, b]), e(() => {
|
|
69
|
+
let e = A.current?.form;
|
|
70
|
+
if (!e) return;
|
|
71
|
+
let t = (e) => queueMicrotask(() => {
|
|
72
|
+
if (e.defaultPrevented) return;
|
|
73
|
+
let t = g.value ?? g.defaultValue ?? "", n = L(t);
|
|
74
|
+
F.current = {
|
|
75
|
+
value: t,
|
|
76
|
+
label: n
|
|
77
|
+
}, g.value === void 0 && v(t);
|
|
78
|
+
let r = g.defaultInputValue ?? n;
|
|
79
|
+
I.current = null, g.inputValue === void 0 ? C(r) : V(r), E(!1), k.current instanceof HTMLInputElement && (k.current.value = g.inputValue ?? r), A.current && (A.current.value = t);
|
|
80
|
+
});
|
|
81
|
+
return e.addEventListener("reset", t), () => e.removeEventListener("reset", t);
|
|
82
|
+
}, [
|
|
83
|
+
g.defaultValue,
|
|
84
|
+
g.defaultInputValue,
|
|
85
|
+
g.value,
|
|
86
|
+
g.inputValue,
|
|
87
|
+
g.items,
|
|
88
|
+
g.selectedOption,
|
|
89
|
+
g.form
|
|
90
|
+
]);
|
|
91
|
+
let U = t(() => g.searchable && g.filterMode !== "external" && w !== x ? g.items.filter((e) => e.label.toLocaleLowerCase().includes(w.toLocaleLowerCase())) : g.items, [
|
|
92
|
+
g.items,
|
|
93
|
+
g.searchable,
|
|
94
|
+
g.filterMode,
|
|
95
|
+
w,
|
|
96
|
+
x
|
|
97
|
+
]), W = t(() => c({
|
|
98
|
+
items: U,
|
|
99
|
+
itemToString: (e) => e.label,
|
|
100
|
+
itemToValue: (e) => e.value,
|
|
101
|
+
isItemDisabled: (e) => !!e.disabled
|
|
102
|
+
}), [U]), G = !!g.virtualization, K = g.virtualization ? g.virtualization.itemHeight : void 0, q = Number.isFinite(K) ? Math.max(40, K) : 40, J = U.findIndex((e) => e.value === D), Y = d({
|
|
103
|
+
count: U.length,
|
|
104
|
+
getScrollElement: () => j.current,
|
|
105
|
+
estimateSize: () => q,
|
|
106
|
+
overscan: g.virtualization ? g.virtualization.overscan ?? 5 : 5,
|
|
107
|
+
enabled: G && T,
|
|
108
|
+
initialRect: {
|
|
109
|
+
width: 0,
|
|
110
|
+
height: 320
|
|
111
|
+
},
|
|
112
|
+
rangeExtractor: (e) => {
|
|
113
|
+
let t = u(e);
|
|
114
|
+
return J >= 0 && !t.includes(J) && t.push(J), t.sort((e, t) => e - t);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
e(() => {
|
|
118
|
+
O(null), G && T && Y.scrollToIndex(Math.max(0, U.findIndex((e) => e.value === y)), { align: "auto" });
|
|
119
|
+
}, [U, T]);
|
|
120
|
+
let X = g.searchable ? s : l, Z = G ? Y.getVirtualItems().map((e) => ({
|
|
121
|
+
index: e.index,
|
|
122
|
+
start: e.start
|
|
123
|
+
})) : U.map((e, t) => ({
|
|
124
|
+
index: t,
|
|
125
|
+
start: 0
|
|
126
|
+
}));
|
|
127
|
+
G && J >= 0 && !Z.some((e) => e.index === J) && Z.push({
|
|
128
|
+
index: J,
|
|
129
|
+
start: J * q
|
|
130
|
+
});
|
|
131
|
+
let Q = g.loading ? R.loading : g.error ? typeof g.error == "string" ? g.error : R.error : U.length === 0 ? R.empty : "", $ = {
|
|
132
|
+
collection: W,
|
|
133
|
+
value: y ? [y] : [],
|
|
134
|
+
open: T && !z,
|
|
135
|
+
disabled: g.disabled,
|
|
136
|
+
readOnly: g.readOnly,
|
|
137
|
+
onOpenChange: (e) => {
|
|
138
|
+
z || E(e.open);
|
|
139
|
+
},
|
|
140
|
+
onValueChange: (e) => {
|
|
141
|
+
if (z || P.current) return;
|
|
142
|
+
let t = e.value[0] ?? "";
|
|
143
|
+
t !== y && H(t);
|
|
144
|
+
},
|
|
145
|
+
onHighlightChange: (e) => O(e.highlightedValue),
|
|
146
|
+
scrollToIndexFn: G ? ({ index: e }) => Y.scrollToIndex(e, { align: "auto" }) : void 0,
|
|
147
|
+
positioning: {
|
|
148
|
+
placement: "bottom-start",
|
|
149
|
+
sameWidth: !0,
|
|
150
|
+
gutter: 4,
|
|
151
|
+
strategy: M?.matches("[role=\"dialog\"], [role=\"alertdialog\"]") ? "fixed" : "absolute",
|
|
152
|
+
hideWhenDetached: !!M?.matches("[role=\"dialog\"], [role=\"alertdialog\"]")
|
|
153
|
+
}
|
|
154
|
+
}, ee = g.searchable ? {
|
|
155
|
+
inputValue: w,
|
|
156
|
+
onInputValueChange: (e) => {
|
|
157
|
+
!z && e.inputValue !== w && B(e.inputValue);
|
|
158
|
+
},
|
|
159
|
+
selectionBehavior: "preserve"
|
|
160
|
+
} : {};
|
|
161
|
+
return /* @__PURE__ */ a(X.Root, {
|
|
162
|
+
...$,
|
|
163
|
+
...ee,
|
|
164
|
+
id: g.id,
|
|
165
|
+
className: "dds-choice",
|
|
166
|
+
"data-label-placement": g.labelPlacement ?? "outside",
|
|
167
|
+
"data-has-value": y || w ? "" : void 0,
|
|
168
|
+
children: [
|
|
169
|
+
g.label && /* @__PURE__ */ i(X.Label, {
|
|
170
|
+
className: "dds-choice-label",
|
|
171
|
+
onClick: () => k.current?.focus(),
|
|
172
|
+
children: g.label
|
|
173
|
+
}),
|
|
174
|
+
/* @__PURE__ */ a(X.Control, {
|
|
175
|
+
className: "dds-choice-control",
|
|
176
|
+
children: [
|
|
177
|
+
g.searchable ? /* @__PURE__ */ i(s.Input, {
|
|
178
|
+
ref: (e) => {
|
|
179
|
+
k.current = e;
|
|
180
|
+
},
|
|
181
|
+
className: "dds-choice-input",
|
|
182
|
+
"aria-label": g["aria-label"],
|
|
183
|
+
"aria-labelledby": g["aria-labelledby"],
|
|
184
|
+
"aria-describedby": g["aria-describedby"],
|
|
185
|
+
"aria-required": g.required,
|
|
186
|
+
placeholder: g.placeholder,
|
|
187
|
+
onCompositionStart: () => {
|
|
188
|
+
P.current = !0;
|
|
189
|
+
},
|
|
190
|
+
onCompositionEnd: () => {
|
|
191
|
+
P.current = !1;
|
|
192
|
+
},
|
|
193
|
+
onKeyDown: (e) => {
|
|
194
|
+
P.current && e.key === "Enter" && (e.preventDefault(), e.stopPropagation());
|
|
195
|
+
},
|
|
196
|
+
onBlur: () => {
|
|
197
|
+
y && V(x);
|
|
198
|
+
}
|
|
199
|
+
}) : null,
|
|
200
|
+
/* @__PURE__ */ i(X.Trigger, {
|
|
201
|
+
ref: (e) => {
|
|
202
|
+
g.searchable || (k.current = e);
|
|
203
|
+
},
|
|
204
|
+
className: "dds-choice-trigger",
|
|
205
|
+
"aria-label": g.searchable ? R.open : g["aria-label"],
|
|
206
|
+
"aria-labelledby": g["aria-labelledby"],
|
|
207
|
+
"aria-describedby": g["aria-describedby"],
|
|
208
|
+
"aria-required": g.required,
|
|
209
|
+
children: g.searchable ? "⌄" : /* @__PURE__ */ i("span", {
|
|
210
|
+
className: "dds-choice-value",
|
|
211
|
+
children: x || g.placeholder || R.open
|
|
212
|
+
})
|
|
213
|
+
}),
|
|
214
|
+
g.clearable && !z && (y || w) ? /* @__PURE__ */ i("button", {
|
|
215
|
+
className: "dds-choice-clear",
|
|
216
|
+
type: "button",
|
|
217
|
+
"aria-label": R.clear,
|
|
218
|
+
onPointerDown: (e) => e.preventDefault(),
|
|
219
|
+
onClick: () => {
|
|
220
|
+
H(""), B(""), E(!1), k.current?.focus();
|
|
221
|
+
},
|
|
222
|
+
children: "×"
|
|
223
|
+
}) : null
|
|
224
|
+
]
|
|
225
|
+
}),
|
|
226
|
+
/* @__PURE__ */ i("input", {
|
|
227
|
+
ref: A,
|
|
228
|
+
className: "dds-choice-validation",
|
|
229
|
+
style: h,
|
|
230
|
+
tabIndex: -1,
|
|
231
|
+
"aria-hidden": "true",
|
|
232
|
+
name: g.name,
|
|
233
|
+
form: g.form,
|
|
234
|
+
value: y,
|
|
235
|
+
required: g.required,
|
|
236
|
+
disabled: g.disabled,
|
|
237
|
+
onChange: () => {},
|
|
238
|
+
onInvalid: (e) => {
|
|
239
|
+
e.preventDefault(), k.current?.focus();
|
|
240
|
+
}
|
|
241
|
+
}),
|
|
242
|
+
/* @__PURE__ */ a("div", {
|
|
243
|
+
className: "dds-choice-status",
|
|
244
|
+
role: "status",
|
|
245
|
+
"aria-live": "polite",
|
|
246
|
+
children: [Q, g.error && g.onRetry ? /* @__PURE__ */ i("button", {
|
|
247
|
+
className: "dds-choice-retry",
|
|
248
|
+
type: "button",
|
|
249
|
+
onClick: g.onRetry,
|
|
250
|
+
children: R.retry
|
|
251
|
+
}) : null]
|
|
252
|
+
}),
|
|
253
|
+
M && /* @__PURE__ */ i(o, {
|
|
254
|
+
container: { current: M },
|
|
255
|
+
children: /* @__PURE__ */ i(X.Positioner, {
|
|
256
|
+
className: "dds-choice-positioner",
|
|
257
|
+
children: /* @__PURE__ */ i(X.Content, {
|
|
258
|
+
ref: j,
|
|
259
|
+
className: "dds-choice-content",
|
|
260
|
+
style: {
|
|
261
|
+
maxHeight: "min(320px, var(--available-height, 320px))",
|
|
262
|
+
overflow: "auto"
|
|
263
|
+
},
|
|
264
|
+
children: /* @__PURE__ */ i("div", {
|
|
265
|
+
style: G ? {
|
|
266
|
+
height: Y.getTotalSize(),
|
|
267
|
+
position: "relative"
|
|
268
|
+
} : void 0,
|
|
269
|
+
children: Z.map((e) => {
|
|
270
|
+
let t = U[e.index];
|
|
271
|
+
return /* @__PURE__ */ a(X.Item, {
|
|
272
|
+
item: t,
|
|
273
|
+
className: "dds-choice-item",
|
|
274
|
+
"aria-posinset": e.index + 1,
|
|
275
|
+
"aria-setsize": U.length,
|
|
276
|
+
style: G ? {
|
|
277
|
+
position: "absolute",
|
|
278
|
+
top: 0,
|
|
279
|
+
left: 0,
|
|
280
|
+
width: "100%",
|
|
281
|
+
height: q,
|
|
282
|
+
transform: `translateY(${e.start}px)`
|
|
283
|
+
} : void 0,
|
|
284
|
+
children: [/* @__PURE__ */ i(X.ItemText, { children: t.label }), t.description ? /* @__PURE__ */ i("span", {
|
|
285
|
+
className: "dds-choice-description",
|
|
286
|
+
children: t.description
|
|
287
|
+
}) : null]
|
|
288
|
+
}, t.value);
|
|
289
|
+
})
|
|
290
|
+
})
|
|
291
|
+
})
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
]
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
//#endregion
|
|
298
|
+
export { g as t };
|
|
299
|
+
|
|
300
|
+
//# sourceMappingURL=choice-B3vNMfKK.js.map
|