@wipco/sdui 0.18.1 → 0.19.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.
@@ -0,0 +1,778 @@
1
+ import { Icon } from './chunk-WIDDHR7L.js';
2
+ import { boxed } from './chunk-SAN4IX6N.js';
3
+ import React, { useState, useRef, useMemo, useEffect, useCallback, useLayoutEffect, useSyncExternalStore } from 'react';
4
+ import { createPortal } from 'react-dom';
5
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
+
7
+ var OVERLAY_ROOT_ID = "wp-overlay-root";
8
+ var DOCUMENT_POSITION_FOLLOWING = 4;
9
+ function getOverlayRoot() {
10
+ if (typeof document === "undefined") return null;
11
+ const existing = document.getElementById(OVERLAY_ROOT_ID);
12
+ if (existing) return existing;
13
+ const created = document.createElement("div");
14
+ created.id = OVERLAY_ROOT_ID;
15
+ document.body.appendChild(created);
16
+ return created;
17
+ }
18
+ var layers = [];
19
+ var nextId = 0;
20
+ var subscribers = /* @__PURE__ */ new Set();
21
+ var keyListenerBound = false;
22
+ function emit() {
23
+ layers = layers.slice();
24
+ subscribers.forEach((fn) => fn());
25
+ }
26
+ function topLayer(list) {
27
+ let best;
28
+ let bestNode = null;
29
+ for (const layer of list) {
30
+ const node = layer.node();
31
+ if (!best) {
32
+ best = layer;
33
+ bestNode = node;
34
+ continue;
35
+ }
36
+ if (!node) continue;
37
+ if (!bestNode) {
38
+ best = layer;
39
+ bestNode = node;
40
+ continue;
41
+ }
42
+ if (bestNode.compareDocumentPosition(node) & DOCUMENT_POSITION_FOLLOWING) {
43
+ best = layer;
44
+ bestNode = node;
45
+ }
46
+ }
47
+ return best;
48
+ }
49
+ function layerHoldsTop(list, id) {
50
+ const top = topLayer(list);
51
+ if (!top) return false;
52
+ if (top.id === id) return true;
53
+ const mine = list.find((l) => l.id === id)?.node();
54
+ const topNode = top.node();
55
+ return !!mine && !!topNode && mine.contains(topNode);
56
+ }
57
+ function escapeTopmost() {
58
+ topLayer(layers)?.onEscape();
59
+ }
60
+ function handleKeyDown(event) {
61
+ if (event.key !== "Escape") return;
62
+ escapeTopmost();
63
+ }
64
+ function push(layer) {
65
+ layers.push(layer);
66
+ if (!keyListenerBound && typeof document !== "undefined") {
67
+ document.addEventListener("keydown", handleKeyDown);
68
+ keyListenerBound = true;
69
+ }
70
+ emit();
71
+ }
72
+ function remove(id) {
73
+ layers = layers.filter((l) => l.id !== id);
74
+ emit();
75
+ }
76
+ function subscribe(fn) {
77
+ subscribers.add(fn);
78
+ return () => {
79
+ subscribers.delete(fn);
80
+ };
81
+ }
82
+ var snapshot = () => layers;
83
+ var serverSnapshot = () => [];
84
+ function useOverlayLayer(active, onEscape, nodeRef) {
85
+ const idRef = useRef(0);
86
+ if (idRef.current === 0) idRef.current = ++nextId;
87
+ const escapeRef = useRef(onEscape);
88
+ escapeRef.current = onEscape;
89
+ const nodeRefRef = useRef(nodeRef);
90
+ nodeRefRef.current = nodeRef;
91
+ useEffect(() => {
92
+ if (!active) return;
93
+ const id2 = idRef.current;
94
+ push({
95
+ id: id2,
96
+ onEscape: () => escapeRef.current?.(),
97
+ node: () => nodeRefRef.current?.current ?? null
98
+ });
99
+ return () => remove(id2);
100
+ }, [active]);
101
+ const current = useSyncExternalStore(subscribe, snapshot, serverSnapshot);
102
+ const id = idRef.current;
103
+ return {
104
+ isTop: active && topLayer(current)?.id === id,
105
+ holdsTop: active && layerHoldsTop(current, id)
106
+ };
107
+ }
108
+ var OverlayContainerContext = React.createContext(null);
109
+ function OverlayLayer({
110
+ onClose,
111
+ children
112
+ }) {
113
+ const ref = useRef(null);
114
+ const container = React.useContext(OverlayContainerContext);
115
+ const { holdsTop } = useOverlayLayer(true, onClose, ref);
116
+ useEffect(() => {
117
+ ref.current?.toggleAttribute("inert", !holdsTop);
118
+ }, [holdsTop]);
119
+ const layer = /* @__PURE__ */ jsx(
120
+ "div",
121
+ {
122
+ ref,
123
+ className: "wp-overlay-layer",
124
+ onClick: (event) => {
125
+ if (onClose && event.target instanceof HTMLElement && event.target.classList.contains("wp-overlay")) {
126
+ onClose();
127
+ }
128
+ },
129
+ children
130
+ }
131
+ );
132
+ const root = container ?? getOverlayRoot();
133
+ return root ? createPortal(layer, root) : layer;
134
+ }
135
+ function HoistedStyle({ id, css }) {
136
+ return /* @__PURE__ */ jsx("style", { href: id, precedence: "medium", children: css });
137
+ }
138
+ var YMD_RE = /^\d{4}-\d{2}-\d{2}$/;
139
+ var HM_RE = /^\d{2}:\d{2}$/;
140
+ function parseYmd(value) {
141
+ if (typeof value !== "string" || !YMD_RE.test(value)) return void 0;
142
+ const y = Number(value.slice(0, 4));
143
+ const m = Number(value.slice(5, 7));
144
+ const d = Number(value.slice(8, 10));
145
+ const date = new Date(y, m - 1, d, 12, 0, 0, 0);
146
+ if (date.getFullYear() !== y || date.getMonth() !== m - 1 || date.getDate() !== d) return void 0;
147
+ return date;
148
+ }
149
+ function formatYmd(date) {
150
+ const y = String(date.getFullYear()).padStart(4, "0");
151
+ const m = String(date.getMonth() + 1).padStart(2, "0");
152
+ const d = String(date.getDate()).padStart(2, "0");
153
+ return `${y}-${m}-${d}`;
154
+ }
155
+ function addDays(date, days) {
156
+ const next = new Date(date);
157
+ next.setDate(next.getDate() + days);
158
+ return next;
159
+ }
160
+ function addMonths(date, months) {
161
+ const next = new Date(date.getFullYear(), date.getMonth() + months, 1, 12, 0, 0, 0);
162
+ const lastDay = new Date(next.getFullYear(), next.getMonth() + 1, 0).getDate();
163
+ next.setDate(Math.min(date.getDate(), lastDay));
164
+ return next;
165
+ }
166
+ function clampDate(date, min, max) {
167
+ if (min && date.getTime() < min.getTime()) return min;
168
+ if (max && date.getTime() > max.getTime()) return max;
169
+ return date;
170
+ }
171
+ function monthIndex(date) {
172
+ return date.getFullYear() * 12 + date.getMonth();
173
+ }
174
+ function monthStart(index) {
175
+ return new Date(Math.floor(index / 12), index % 12, 1, 12, 0, 0, 0);
176
+ }
177
+ function isSameDay(a, b) {
178
+ return a !== void 0 && b !== void 0 && formatYmd(a) === formatYmd(b);
179
+ }
180
+ function withinRange(date, min, max) {
181
+ if (min && date.getTime() < min.getTime()) return false;
182
+ if (max && date.getTime() > max.getTime()) return false;
183
+ return true;
184
+ }
185
+ function viewFromDigits(digits, current, min, max) {
186
+ if (digits.length < 4) return void 0;
187
+ const year = Number(digits.slice(0, 4));
188
+ if (!Number.isFinite(year) || year < 1) return void 0;
189
+ const month = digits.length >= 6 ? Number(digits.slice(4, 6)) : current.getMonth() + 1;
190
+ if (!(month >= 1 && month <= 12)) return void 0;
191
+ return clampDate(new Date(year, month - 1, 1, 12, 0, 0, 0), min, max);
192
+ }
193
+ function maskYmd(text) {
194
+ const digits = text.replace(/\D/g, "").slice(0, 8);
195
+ if (digits.length <= 4) return digits;
196
+ if (digits.length <= 6) return `${digits.slice(0, 4)}-${digits.slice(4)}`;
197
+ return `${digits.slice(0, 4)}-${digits.slice(4, 6)}-${digits.slice(6)}`;
198
+ }
199
+ function maskYmdHm(text) {
200
+ const digits = text.replace(/\D/g, "").slice(0, 12);
201
+ const date = maskYmd(digits.slice(0, 8));
202
+ if (digits.length <= 8) return date;
203
+ const time = digits.slice(8);
204
+ return time.length <= 2 ? `${date} ${time}` : `${date} ${time.slice(0, 2)}:${time.slice(2)}`;
205
+ }
206
+ function isValidHm(text) {
207
+ if (!HM_RE.test(text)) return false;
208
+ return Number(text.slice(0, 2)) <= 23 && Number(text.slice(3, 5)) <= 59;
209
+ }
210
+ function splitDateTime(value) {
211
+ if (!value) return ["", ""];
212
+ const [date = "", time = ""] = value.split(/[T ]/, 2);
213
+ return [date, time.slice(0, 5)];
214
+ }
215
+ function toFieldText(value, withTime) {
216
+ if (!withTime) return splitDateTime(value)[0];
217
+ const [date, time] = splitDateTime(value);
218
+ return date && time ? `${date} ${time}` : date;
219
+ }
220
+ function toFieldValue(text, withTime) {
221
+ if (text === "") return "";
222
+ if (!withTime) return YMD_RE.test(text) && parseYmd(text) ? text : void 0;
223
+ const [date, time] = splitDateTime(text);
224
+ if (!parseYmd(date) || !isValidHm(time)) return void 0;
225
+ return `${date}T${time}`;
226
+ }
227
+ var WEEKDAYS_KO = ["\uC77C", "\uC6D4", "\uD654", "\uC218", "\uBAA9", "\uAE08", "\uD1A0"];
228
+ var WEEKS_PER_MONTH = 6;
229
+ function buildMonthCells(index, min, max) {
230
+ const first = monthStart(index);
231
+ const gridStart = addDays(first, -first.getDay());
232
+ const weeks = [];
233
+ for (let w = 0; w < WEEKS_PER_MONTH; w += 1) {
234
+ const row = [];
235
+ for (let d = 0; d < 7; d += 1) {
236
+ const date = addDays(gridStart, w * 7 + d);
237
+ row.push({
238
+ date,
239
+ ymd: formatYmd(date),
240
+ outside: monthIndex(date) !== index,
241
+ disabled: min !== void 0 && date.getTime() < min.getTime() || max !== void 0 && date.getTime() > max.getTime()
242
+ });
243
+ }
244
+ weeks.push(row);
245
+ }
246
+ return weeks;
247
+ }
248
+ function Calendar({
249
+ monthCount,
250
+ minDate,
251
+ maxDate,
252
+ dayState,
253
+ onPick,
254
+ focusDate,
255
+ onFocusDateChange,
256
+ autoFocus = false
257
+ }) {
258
+ const focusIdx = monthIndex(focusDate);
259
+ const [viewIdx, setViewIdx] = useState(focusIdx);
260
+ const gridRef = useRef(null);
261
+ const focusYmd = formatYmd(focusDate);
262
+ const visibleIdx = focusIdx < viewIdx ? focusIdx : focusIdx > viewIdx + monthCount - 1 ? focusIdx - monthCount + 1 : viewIdx;
263
+ if (visibleIdx !== viewIdx) setViewIdx(visibleIdx);
264
+ const claimFocusRef = useRef(autoFocus);
265
+ useLayoutEffect(() => {
266
+ const grid = gridRef.current;
267
+ if (!grid) return;
268
+ const holdsFocus = document.activeElement instanceof Node && grid.contains(document.activeElement);
269
+ const claim = claimFocusRef.current || holdsFocus;
270
+ claimFocusRef.current = false;
271
+ if (claim) grid.querySelector('[tabindex="0"]')?.focus();
272
+ }, [focusYmd]);
273
+ const todayYmd = formatYmd(/* @__PURE__ */ new Date());
274
+ const minIdx = minDate ? monthIndex(minDate) : -Infinity;
275
+ const maxIdx = maxDate ? monthIndex(maxDate) : Infinity;
276
+ const prevDisabled = visibleIdx <= minIdx;
277
+ const nextDisabled = visibleIdx + monthCount - 1 >= maxIdx;
278
+ const moveFocus = (next) => {
279
+ onFocusDateChange(clampDate(next, minDate, maxDate));
280
+ };
281
+ const handleKeyDown2 = (event) => {
282
+ const deltaDays = event.key === "ArrowLeft" ? -1 : event.key === "ArrowRight" ? 1 : event.key === "ArrowUp" ? -7 : event.key === "ArrowDown" ? 7 : 0;
283
+ if (deltaDays !== 0) {
284
+ event.preventDefault();
285
+ moveFocus(addDays(focusDate, deltaDays));
286
+ return;
287
+ }
288
+ if (event.key === "Home" || event.key === "End") {
289
+ event.preventDefault();
290
+ const offset = event.key === "Home" ? -focusDate.getDay() : 6 - focusDate.getDay();
291
+ moveFocus(addDays(focusDate, offset));
292
+ return;
293
+ }
294
+ if (event.key === "PageUp" || event.key === "PageDown") {
295
+ event.preventDefault();
296
+ moveFocus(addMonths(focusDate, event.key === "PageUp" ? -1 : 1));
297
+ }
298
+ };
299
+ return /* @__PURE__ */ jsx("div", { className: "wp-dp", children: /* @__PURE__ */ jsxs("div", { className: "wp-dp__months", ref: gridRef, onKeyDown: handleKeyDown2, children: [
300
+ /* @__PURE__ */ jsxs("div", { className: "wp-dp__nav", children: [
301
+ /* @__PURE__ */ jsx(
302
+ "button",
303
+ {
304
+ type: "button",
305
+ className: "wp-dp__navbtn",
306
+ "aria-label": "\uC774\uC804 \uB2EC",
307
+ "aria-disabled": prevDisabled || void 0,
308
+ disabled: prevDisabled,
309
+ onClick: () => setViewIdx(visibleIdx - 1),
310
+ children: /* @__PURE__ */ jsx(Icon, { name: "chevron-left", size: 16 })
311
+ }
312
+ ),
313
+ /* @__PURE__ */ jsx(
314
+ "button",
315
+ {
316
+ type: "button",
317
+ className: "wp-dp__navbtn",
318
+ "aria-label": "\uB2E4\uC74C \uB2EC",
319
+ "aria-disabled": nextDisabled || void 0,
320
+ disabled: nextDisabled,
321
+ onClick: () => setViewIdx(visibleIdx + 1),
322
+ children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 16 })
323
+ }
324
+ )
325
+ ] }),
326
+ Array.from({ length: monthCount }, (_, offset) => {
327
+ const idx = visibleIdx + offset;
328
+ const first = monthStart(idx);
329
+ const weeks = buildMonthCells(idx, minDate, maxDate);
330
+ const caption = `${first.getFullYear()}\uB144 ${first.getMonth() + 1}\uC6D4`;
331
+ return /* @__PURE__ */ jsxs("div", { className: "wp-dp__month", children: [
332
+ /* @__PURE__ */ jsx("div", { className: "wp-dp__caption", children: /* @__PURE__ */ jsx("span", { className: "wp-dp__caption-label", children: caption }) }),
333
+ /* @__PURE__ */ jsxs("table", { className: "wp-dp__grid", role: "grid", "aria-label": caption, children: [
334
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { className: "wp-dp__weekdays", children: WEEKDAYS_KO.map((name) => /* @__PURE__ */ jsx("th", { scope: "col", className: "wp-dp__weekday", abbr: name, children: name }, name)) }) }),
335
+ /* @__PURE__ */ jsx("tbody", { className: "wp-dp__weeks", children: weeks.map((week, w) => /* @__PURE__ */ jsx("tr", { className: "wp-dp__week", children: week.map((cell) => {
336
+ const state = cell.disabled ? {} : dayState(cell.date);
337
+ const classes = ["wp-dp__day"];
338
+ if (cell.outside) classes.push("wp-dp__day--outside");
339
+ if (cell.disabled) classes.push("wp-dp__day--disabled");
340
+ if (cell.ymd === todayYmd) classes.push("wp-dp__day--today");
341
+ if (state.selected) classes.push("wp-dp__day--selected");
342
+ if (state.rangeStart) classes.push("wp-dp__day--range-start");
343
+ if (state.rangeMiddle) classes.push("wp-dp__day--range-middle");
344
+ if (state.rangeEnd) classes.push("wp-dp__day--range-end");
345
+ return /* @__PURE__ */ jsx("td", { className: classes.join(" "), children: /* @__PURE__ */ jsx(
346
+ "button",
347
+ {
348
+ type: "button",
349
+ className: "wp-dp__day-btn",
350
+ "data-ymd": cell.ymd,
351
+ tabIndex: cell.ymd === focusYmd && !cell.outside ? 0 : -1,
352
+ disabled: cell.disabled,
353
+ "aria-selected": state.selected || state.rangeStart || state.rangeEnd || void 0,
354
+ "aria-label": cell.ymd,
355
+ onClick: () => {
356
+ onFocusDateChange(cell.date);
357
+ onPick(cell.date);
358
+ },
359
+ children: cell.date.getDate()
360
+ }
361
+ ) }, cell.ymd);
362
+ }) }, w)) })
363
+ ] })
364
+ ] }, idx);
365
+ })
366
+ ] }) });
367
+ }
368
+ function useDismiss(open, hostRef, onDismiss) {
369
+ useEffect(() => {
370
+ if (!open) return;
371
+ const handlePointerDown = (event) => {
372
+ if (event.target instanceof Node && hostRef.current?.contains(event.target)) return;
373
+ onDismiss("outside");
374
+ };
375
+ document.addEventListener("pointerdown", handlePointerDown);
376
+ return () => {
377
+ document.removeEventListener("pointerdown", handlePointerDown);
378
+ };
379
+ }, [open, hostRef, onDismiss]);
380
+ useOverlayLayer(open, () => onDismiss("escape"), hostRef);
381
+ }
382
+ function DateInput({
383
+ inputRef,
384
+ value,
385
+ placeholder,
386
+ size,
387
+ invalid,
388
+ disabled,
389
+ maxLength,
390
+ lead,
391
+ expanded,
392
+ ariaLabel,
393
+ onValueChange,
394
+ onKeyDown,
395
+ onFocus,
396
+ onPointerDown
397
+ }) {
398
+ const classes = ["wp-input", "wp-input--mono", "wp-dp-input"];
399
+ if (maxLength > 10) classes.push("wp-dp-input--time");
400
+ if (size !== "md") classes.push(`wp-input--${size}`);
401
+ if (invalid) classes.push("wp-input--invalid");
402
+ const input = /* @__PURE__ */ jsx(
403
+ "input",
404
+ {
405
+ ref: inputRef,
406
+ type: "text",
407
+ className: classes.join(" "),
408
+ inputMode: "numeric",
409
+ autoComplete: "off",
410
+ spellCheck: false,
411
+ maxLength,
412
+ placeholder,
413
+ value,
414
+ disabled,
415
+ "aria-label": ariaLabel,
416
+ "aria-invalid": invalid || void 0,
417
+ "aria-haspopup": "dialog",
418
+ "aria-expanded": expanded,
419
+ onChange: (event) => onValueChange(event.target.value),
420
+ onKeyDown,
421
+ onFocus,
422
+ onPointerDown
423
+ }
424
+ );
425
+ if (!lead) return input;
426
+ return /* @__PURE__ */ jsxs("div", { className: "wp-inputwrap wp-inputwrap--lead", children: [
427
+ /* @__PURE__ */ jsx("span", { className: "wp-inputwrap__lead", children: /* @__PURE__ */ jsx(Icon, { name: "calendar", size: size === "lg" ? 16 : 15 }) }),
428
+ input
429
+ ] });
430
+ }
431
+ function DatePickerImpl({
432
+ value,
433
+ onChange,
434
+ min,
435
+ max,
436
+ placeholder,
437
+ invalid = false,
438
+ disabled = false,
439
+ size = "md",
440
+ withTime = false
441
+ }) {
442
+ const [open, setOpen] = useState(false);
443
+ const [innerValue, setInnerValue] = useState("");
444
+ const hostRef = useRef(null);
445
+ const popRef = useRef(null);
446
+ const inputRef = useRef(null);
447
+ const isControlled = value !== void 0;
448
+ const currentValue = (isControlled ? value : innerValue) ?? "";
449
+ const fieldText = useMemo(() => toFieldText(currentValue, withTime), [currentValue, withTime]);
450
+ const [text, setText] = useState(fieldText);
451
+ useEffect(() => {
452
+ setText(fieldText);
453
+ }, [fieldText]);
454
+ const selectedDate = useMemo(() => parseYmd(splitDateTime(currentValue)[0]), [currentValue]);
455
+ const minDate = useMemo(() => parseYmd(min), [min]);
456
+ const maxDate = useMemo(() => parseYmd(max), [max]);
457
+ const [focusDate, setFocusDate] = useState(() => selectedDate ?? clampDate(/* @__PURE__ */ new Date(), minDate, maxDate));
458
+ const [grabFocus, setGrabFocus] = useState(false);
459
+ const dismiss = useCallback((reason) => {
460
+ setOpen(false);
461
+ if (reason === "escape") inputRef.current?.focus();
462
+ }, []);
463
+ useDismiss(open, hostRef, dismiss);
464
+ const isInvalid = Boolean(invalid);
465
+ const commit = (next) => {
466
+ if (!isControlled) setInnerValue(next);
467
+ onChange?.(next);
468
+ };
469
+ const openCalendar = (claimFocus) => {
470
+ setFocusDate(selectedDate ?? clampDate(/* @__PURE__ */ new Date(), minDate, maxDate));
471
+ setGrabFocus(claimFocus);
472
+ setOpen(true);
473
+ };
474
+ const handleTextChange = (raw) => {
475
+ const masked = withTime ? maskYmdHm(raw) : maskYmd(raw);
476
+ setText(masked);
477
+ const view = viewFromDigits(masked.replace(/\D/g, "").slice(0, 8), focusDate, minDate, maxDate);
478
+ if (view) setFocusDate(view);
479
+ const next = toFieldValue(masked, withTime);
480
+ if (next === void 0) return;
481
+ const parsed = parseYmd(splitDateTime(next)[0]);
482
+ if (parsed) {
483
+ if (!withinRange(parsed, minDate, maxDate)) return;
484
+ setFocusDate(parsed);
485
+ }
486
+ commit(next);
487
+ };
488
+ const handleKeyDown2 = (event) => {
489
+ if (event.key === "ArrowDown") {
490
+ event.preventDefault();
491
+ if (open) popRef.current?.querySelector('[tabindex="0"]')?.focus();
492
+ else openCalendar(true);
493
+ return;
494
+ }
495
+ if (event.key === "Enter" && open) {
496
+ event.preventDefault();
497
+ setOpen(false);
498
+ }
499
+ };
500
+ const handleBlur = (event) => {
501
+ const next = event.relatedTarget;
502
+ if (next instanceof Node && hostRef.current?.contains(next)) return;
503
+ if (text !== fieldText) setText(fieldText);
504
+ };
505
+ return /* @__PURE__ */ jsxs("div", { ref: hostRef, className: "wp-dp-host", onBlur: handleBlur, children: [
506
+ /* @__PURE__ */ jsx(HoistedStyle, { id: "wp-datepicker", css: DP_CSS }),
507
+ /* @__PURE__ */ jsx(
508
+ DateInput,
509
+ {
510
+ inputRef,
511
+ value: text,
512
+ placeholder: placeholder ?? (withTime ? "YYYY-MM-DD HH:MM" : "YYYY-MM-DD"),
513
+ size,
514
+ invalid: isInvalid,
515
+ disabled,
516
+ maxLength: withTime ? 16 : 10,
517
+ lead: true,
518
+ expanded: open,
519
+ onValueChange: handleTextChange,
520
+ onKeyDown: handleKeyDown2,
521
+ onPointerDown: () => {
522
+ if (!disabled && !open) openCalendar(false);
523
+ }
524
+ }
525
+ ),
526
+ open ? /* @__PURE__ */ jsx("div", { ref: popRef, className: "wp-dp-pop", role: "dialog", "aria-label": "\uB0A0\uC9DC \uC120\uD0DD \uB2EC\uB825", children: /* @__PURE__ */ jsx("div", { className: "wp-dp-pop__body", children: /* @__PURE__ */ jsx(
527
+ Calendar,
528
+ {
529
+ monthCount: 1,
530
+ minDate,
531
+ maxDate,
532
+ focusDate,
533
+ onFocusDateChange: setFocusDate,
534
+ autoFocus: grabFocus,
535
+ dayState: (date) => ({ selected: isSameDay(date, selectedDate) }),
536
+ onPick: (date) => {
537
+ const ymd = formatYmd(date);
538
+ const time = withTime ? splitDateTime(text)[1] : "";
539
+ const next = withTime ? `${ymd}T${isValidHm(time) ? time : "00:00"}` : ymd;
540
+ setText(toFieldText(next, withTime));
541
+ commit(next);
542
+ setOpen(false);
543
+ inputRef.current?.focus();
544
+ }
545
+ }
546
+ ) }) }) : null
547
+ ] });
548
+ }
549
+ function DateRangePickerImpl({
550
+ value,
551
+ onChange,
552
+ min,
553
+ max,
554
+ invalid = false,
555
+ disabled = false,
556
+ size = "md"
557
+ }) {
558
+ const [open, setOpen] = useState(false);
559
+ const [innerRange, setInnerRange] = useState({});
560
+ const hostRef = useRef(null);
561
+ const popRef = useRef(null);
562
+ const fromRef = useRef(null);
563
+ const toRef = useRef(null);
564
+ const isControlled = value !== void 0;
565
+ const currentRange = (isControlled ? value : innerRange) ?? {};
566
+ const fromValue = currentRange.from ?? "";
567
+ const toValue = currentRange.to ?? "";
568
+ const [fromText, setFromText] = useState(fromValue);
569
+ const [toText, setToText] = useState(toValue);
570
+ useEffect(() => {
571
+ setFromText(fromValue);
572
+ }, [fromValue]);
573
+ useEffect(() => {
574
+ setToText(toValue);
575
+ }, [toValue]);
576
+ const fromDate = useMemo(() => parseYmd(fromValue), [fromValue]);
577
+ const toDate = useMemo(() => parseYmd(toValue), [toValue]);
578
+ const minDate = useMemo(() => parseYmd(min), [min]);
579
+ const maxDate = useMemo(() => parseYmd(max), [max]);
580
+ const [focusDate, setFocusDate] = useState(() => fromDate ?? clampDate(/* @__PURE__ */ new Date(), minDate, maxDate));
581
+ const [grabFocus, setGrabFocus] = useState(false);
582
+ const [end, setEnd] = useState("from");
583
+ const dismiss = useCallback((reason) => {
584
+ setOpen(false);
585
+ if (reason === "escape") (end === "to" ? toRef : fromRef).current?.focus();
586
+ }, [end]);
587
+ useDismiss(open, hostRef, dismiss);
588
+ const isInvalid = Boolean(invalid);
589
+ const commit = (nextFrom, nextTo) => {
590
+ const next = { from: nextFrom || void 0, to: nextTo || void 0 };
591
+ const a = parseYmd(nextFrom);
592
+ const b = parseYmd(nextTo);
593
+ if (a && b) next.days = Math.round((b.getTime() - a.getTime()) / 864e5) + 1;
594
+ if (!isControlled) setInnerRange(next);
595
+ onChange?.(next);
596
+ };
597
+ const openCalendar = (which, claimFocus) => {
598
+ setEnd(which);
599
+ setFocusDate(
600
+ (which === "to" ? toDate ?? fromDate : fromDate ?? toDate) ?? clampDate(/* @__PURE__ */ new Date(), minDate, maxDate)
601
+ );
602
+ setGrabFocus(claimFocus);
603
+ setOpen(true);
604
+ };
605
+ const handleTextChange = (which, raw) => {
606
+ const masked = maskYmd(raw);
607
+ if (which === "from") setFromText(masked);
608
+ else setToText(masked);
609
+ const view = viewFromDigits(masked.replace(/\D/g, ""), focusDate, minDate, maxDate);
610
+ if (view) setFocusDate(view);
611
+ if (masked === "") {
612
+ commit(which === "from" ? "" : fromValue, which === "to" ? "" : toValue);
613
+ return;
614
+ }
615
+ if (masked.length !== 10) return;
616
+ const parsed = parseYmd(masked);
617
+ if (!parsed || !withinRange(parsed, minDate, maxDate)) return;
618
+ setFocusDate(parsed);
619
+ if (which === "from") {
620
+ commit(masked, toDate && parsed.getTime() > toDate.getTime() ? "" : toValue);
621
+ } else {
622
+ commit(fromDate && parsed.getTime() < fromDate.getTime() ? "" : fromValue, masked);
623
+ }
624
+ };
625
+ const handleKeyDown2 = (which) => (event) => {
626
+ if (event.key === "ArrowDown") {
627
+ event.preventDefault();
628
+ if (open) popRef.current?.querySelector('[tabindex="0"]')?.focus();
629
+ else openCalendar(which, true);
630
+ return;
631
+ }
632
+ if (event.key === "Enter" && open) {
633
+ event.preventDefault();
634
+ setOpen(false);
635
+ }
636
+ };
637
+ const handleBlur = (event) => {
638
+ const next = event.relatedTarget;
639
+ if (next instanceof Node && hostRef.current?.contains(next)) return;
640
+ if (fromText !== fromValue) setFromText(fromValue);
641
+ if (toText !== toValue) setToText(toValue);
642
+ };
643
+ const pick = (date) => {
644
+ const ymd = formatYmd(date);
645
+ if (end === "from") {
646
+ const keepTo = toDate && date.getTime() > toDate.getTime() ? "" : toValue;
647
+ commit(ymd, keepTo);
648
+ setEnd("to");
649
+ toRef.current?.focus();
650
+ return;
651
+ }
652
+ if (fromDate && date.getTime() < fromDate.getTime()) {
653
+ commit(ymd, "");
654
+ setEnd("to");
655
+ toRef.current?.focus();
656
+ return;
657
+ }
658
+ commit(fromValue, ymd);
659
+ setOpen(false);
660
+ toRef.current?.focus();
661
+ };
662
+ return /* @__PURE__ */ jsxs("div", { ref: hostRef, className: "wp-dp-host wp-dp-host--range", onBlur: handleBlur, children: [
663
+ /* @__PURE__ */ jsx(HoistedStyle, { id: "wp-datepicker", css: DP_CSS }),
664
+ /* @__PURE__ */ jsxs("div", { className: "wp-dp-range", children: [
665
+ /* @__PURE__ */ jsx(
666
+ DateInput,
667
+ {
668
+ inputRef: fromRef,
669
+ value: fromText,
670
+ placeholder: "YYYY-MM-DD",
671
+ size,
672
+ invalid: isInvalid,
673
+ disabled,
674
+ maxLength: 10,
675
+ lead: true,
676
+ expanded: open,
677
+ ariaLabel: "\uC2DC\uC791\uC77C",
678
+ onValueChange: (raw) => handleTextChange("from", raw),
679
+ onKeyDown: handleKeyDown2("from"),
680
+ onFocus: () => setEnd("from"),
681
+ onPointerDown: () => {
682
+ if (!disabled && !open) openCalendar("from", false);
683
+ }
684
+ }
685
+ ),
686
+ /* @__PURE__ */ jsx("span", { className: "wp-dp-range__sep", "aria-hidden": true, children: "~" }),
687
+ /* @__PURE__ */ jsx(
688
+ DateInput,
689
+ {
690
+ inputRef: toRef,
691
+ value: toText,
692
+ placeholder: "YYYY-MM-DD",
693
+ size,
694
+ invalid: isInvalid,
695
+ disabled,
696
+ maxLength: 10,
697
+ lead: false,
698
+ expanded: open,
699
+ ariaLabel: "\uC885\uB8CC\uC77C",
700
+ onValueChange: (raw) => handleTextChange("to", raw),
701
+ onKeyDown: handleKeyDown2("to"),
702
+ onFocus: () => setEnd("to"),
703
+ onPointerDown: () => {
704
+ if (!disabled && !open) openCalendar("to", false);
705
+ }
706
+ }
707
+ )
708
+ ] }),
709
+ open ? /* @__PURE__ */ jsx("div", { ref: popRef, className: "wp-dp-pop wp-dp-pop--wide", role: "dialog", "aria-label": "\uAE30\uAC04 \uC120\uD0DD \uB2EC\uB825", children: /* @__PURE__ */ jsx("div", { className: "wp-dp-pop__body", children: /* @__PURE__ */ jsx(
710
+ Calendar,
711
+ {
712
+ monthCount: 2,
713
+ minDate,
714
+ maxDate,
715
+ focusDate,
716
+ onFocusDateChange: setFocusDate,
717
+ autoFocus: grabFocus,
718
+ dayState: (date) => {
719
+ const t = date.getTime();
720
+ const start = fromDate?.getTime();
721
+ const end2 = toDate?.getTime();
722
+ if (start !== void 0 && end2 !== void 0 && t > start && t < end2) return { rangeMiddle: true };
723
+ return {
724
+ rangeStart: start !== void 0 && t === start && end2 !== void 0,
725
+ rangeEnd: end2 !== void 0 && t === end2,
726
+ // 한쪽 끝만 정해진 상태는 단일 선택처럼 보인다
727
+ selected: start !== void 0 && t === start && end2 === void 0 || end2 !== void 0 && t === end2 && start === void 0 || void 0
728
+ };
729
+ },
730
+ onPick: pick
731
+ }
732
+ ) }) }) : null
733
+ ] });
734
+ }
735
+ var DP_CSS = `
736
+ .wp-dp-host{position:relative}
737
+ .wp-dp-pop{position:absolute;top:calc(100% + var(--wp-space-2));left:0;min-width:100%;width:max-content;background:var(--wp-surface-card);border:1px solid var(--wp-border-subtle);border-radius:var(--wp-radius-lg);box-shadow:var(--wp-shadow-lg);z-index:300}
738
+ .wp-dp-pop--wide{max-width:min(640px,90vw)}
739
+ .wp-dp-pop__body{padding:var(--wp-space-4);max-height:60vh;overflow:auto}
740
+ .wp-dp-range{display:flex;align-items:center;gap:var(--wp-space-1)}
741
+ .wp-dp-range>*{flex:1 1 0;min-width:0}
742
+ .wp-dp-range__sep{flex:none;color:var(--wp-text-subtle)}
743
+ /* \uC5F4 \uD3ED\uC774 \uC881\uC740 \uC790\uB9AC(\uC0C1\uC138\uAC80\uC0C9 2\uB2E8 \uACA9\uC790)\uC5D0\uC11C\uB3C4 'YYYY-MM-DD' \uC5F4 \uAE00\uC790\uAC00 \uC798\uB9AC\uC9C0 \uC54A\uAC8C \u2014
744
+ \uC120\uD589 \uC544\uC774\uCF58 \uC5EC\uBC31\uC744 \uC870\uC778\uB2E4. \uD074\uB798\uC2A4\uB97C \uB450 \uBC88 \uC801\uC5B4 DS\uC758 wp-inputwrap--lead \uADDC\uCE59\uC744 \uB118\uB294\uB2E4. */
745
+ .wp-dp-input{padding-right:6px;min-width:calc(10ch + 14px)}
746
+ .wp-dp-input--time{min-width:calc(16ch + 14px)}
747
+ .wp-inputwrap--lead .wp-dp-input.wp-dp-input{padding-left:26px;min-width:calc(10ch + 36px)}
748
+ .wp-inputwrap--lead .wp-dp-input--time.wp-dp-input--time{min-width:calc(16ch + 36px)}
749
+ .wp-dp{font-family:inherit;font-size:13px;color:var(--wp-text-default)}
750
+ .wp-dp__months{position:relative;display:flex;flex-wrap:wrap;gap:var(--wp-space-5)}
751
+ .wp-dp__nav{position:absolute;top:0;right:0;display:flex;gap:var(--wp-space-1);z-index:1}
752
+ .wp-dp__navbtn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:none;background:transparent;border-radius:var(--wp-radius-sm);color:var(--wp-text-muted);cursor:pointer;transition:background 0.12s,color 0.12s}
753
+ .wp-dp__navbtn:hover:not(:disabled){background:var(--wp-surface-hover);color:var(--wp-text-strong)}
754
+ .wp-dp__navbtn:focus-visible{outline:2px solid var(--wp-border-focus);outline-offset:1px}
755
+ .wp-dp__navbtn:disabled{color:var(--wp-text-disabled);cursor:default;background:transparent}
756
+ .wp-dp__caption{display:flex;align-items:center;height:28px;margin-bottom:var(--wp-space-2);padding-left:var(--wp-space-1)}
757
+ .wp-dp__caption-label{font-size:13px;font-weight:600;color:var(--wp-text-strong)}
758
+ .wp-dp__grid{border-collapse:collapse}
759
+ .wp-dp__weekday{width:32px;height:28px;padding:0;font-size:12px;font-weight:500;color:var(--wp-text-muted);text-align:center}
760
+ .wp-dp__weekday:first-child{color:var(--wp-text-danger)}
761
+ .wp-dp__day{padding:0;text-align:center}
762
+ .wp-dp__day-btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:30px;padding:0;border:none;background:transparent;border-radius:var(--wp-radius-sm);font:inherit;font-variant-numeric:tabular-nums;color:inherit;cursor:pointer;transition:background 0.12s,color 0.12s}
763
+ .wp-dp__day:not(.wp-dp__day--selected):not(.wp-dp__day--disabled) .wp-dp__day-btn:hover{background:var(--wp-surface-hover)}
764
+ .wp-dp__day-btn:focus-visible{outline:2px solid var(--wp-border-focus);outline-offset:-1px}
765
+ .wp-dp__day--today .wp-dp__day-btn{box-shadow:inset 0 0 0 1px var(--wp-border-focus)}
766
+ .wp-dp__day--outside{color:var(--wp-text-subtle)}
767
+ .wp-dp__day--disabled{color:var(--wp-text-disabled)}
768
+ .wp-dp__day--disabled .wp-dp__day-btn{cursor:default}
769
+ .wp-dp__day--selected .wp-dp__day-btn{background:var(--wp-action-primary-bg);color:var(--wp-action-primary-fg);font-weight:500}
770
+ .wp-dp__day--selected.wp-dp__day--today .wp-dp__day-btn{box-shadow:none}
771
+ .wp-dp__day--range-middle .wp-dp__day-btn{background:var(--wp-surface-selected);color:var(--wp-text-default);border-radius:0;font-weight:400}
772
+ .wp-dp__day--range-start .wp-dp__day-btn,.wp-dp__day--range-end .wp-dp__day-btn{background:var(--wp-action-primary-bg);color:var(--wp-action-primary-fg);font-weight:500}
773
+ .wp-dp__day--range-start.wp-dp__day--today .wp-dp__day-btn,.wp-dp__day--range-end.wp-dp__day--today .wp-dp__day-btn{box-shadow:none}
774
+ `;
775
+ var DatePicker = boxed(DatePickerImpl, "DatePicker");
776
+ var DateRangePicker = boxed(DateRangePickerImpl, "DateRangePicker");
777
+
778
+ export { DatePicker, DateRangePicker, HoistedStyle, OverlayContainerContext, OverlayLayer, getOverlayRoot, useOverlayLayer };