@wipco/sdui 0.19.2 → 0.20.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.
@@ -1,6 +1,24 @@
1
1
  import { S as SduiBoxProps } from './layout-B1QrIBm6.js';
2
2
  import React__default from 'react';
3
3
 
4
+ interface TimePickerProps {
5
+ /** 선택된 시각 (HH:MM, 24시간제). 주어지면 controlled로 동작. */
6
+ value?: string;
7
+ onChange?: (value: string) => void;
8
+ /** 고를 수 있는 최소 시각 (HH:MM, 포함). */
9
+ min?: string;
10
+ /** 고를 수 있는 최대 시각 (HH:MM, 포함). */
11
+ max?: string;
12
+ /** 목록 눈금 간격(분). 기본 30 */
13
+ step?: number;
14
+ placeholder?: string;
15
+ invalid?: boolean;
16
+ disabled?: boolean;
17
+ size?: 'sm' | 'md' | 'lg';
18
+ }
19
+ /** 바깥에서 온 `style`·`className`을 뿌리에 얹어 내보낸다. 까닭은 {@link ./boxed.tsx}에 있다. */
20
+ declare const TimePicker: React__default.FC<TimePickerProps & SduiBoxProps>;
21
+
4
22
  interface DatePickerProps {
5
23
  /** 선택된 날짜 (YYYY-MM-DD, `withTime`이면 YYYY-MM-DDTHH:mm). 주어지면 controlled로 동작. */
6
24
  value?: string;
@@ -45,4 +63,4 @@ declare const DatePicker: React__default.FC<DatePickerProps & SduiBoxProps>;
45
63
  /** 바깥에서 온 `style`·`className`을 뿌리에 얹어 내보낸다. 까닭은 {@link ./boxed.tsx}에 있다. */
46
64
  declare const DateRangePicker: React__default.FC<DateRangePickerProps & SduiBoxProps>;
47
65
 
48
- export { type DatePickerProps as D, type DateRangePickerProps as a, DatePicker as b, DateRangePicker as c, type DateRangeValue as d };
66
+ export { type DatePickerProps as D, type TimePickerProps as T, type DateRangePickerProps as a, DatePicker as b, DateRangePicker as c, type DateRangeValue as d, TimePicker as e };
@@ -1,5 +1,5 @@
1
- import { resolveTreeNodes, TreeView } from './chunk-TUR4EBKX.js';
2
- import { OverlayLayer } from './chunk-IY7SMV6V.js';
1
+ import { resolveTreeNodes, TreeView } from './chunk-IDUN2ESE.js';
2
+ import { OverlayLayer } from './chunk-XKLUCUVA.js';
3
3
  import { Field, Dialog, Icon, Button, Input } from './chunk-WIDDHR7L.js';
4
4
  import { boxed } from './chunk-SAN4IX6N.js';
5
5
  import { useState, useMemo, useEffect } from 'react';
@@ -0,0 +1,291 @@
1
+ import { useDismiss, usePopoverAnchor, usePortalTo, HoistedStyle, AnchoredPopoverStyles, anchorStyle, focusStaysInside } from './chunk-XKLUCUVA.js';
2
+ import { Icon } from './chunk-WIDDHR7L.js';
3
+ import { boxed } from './chunk-SAN4IX6N.js';
4
+ import { createContext, useState, useRef, useEffect, useMemo, useCallback, useContext } from 'react';
5
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
+
7
+ // src/env.ts
8
+ var DEV = (() => {
9
+ try {
10
+ const meta = import.meta;
11
+ return Boolean(meta.env?.DEV);
12
+ } catch {
13
+ return false;
14
+ }
15
+ })();
16
+
17
+ // src/adapter.ts
18
+ function isSafeNavigationTarget(to) {
19
+ if (typeof to !== "string") return false;
20
+ const value = to.replace(/[\u0000-\u0020]/g, "");
21
+ if (value === "") return false;
22
+ if (value.startsWith("//")) return false;
23
+ if (value.startsWith("/") || value.startsWith("?") || value.startsWith("#")) return true;
24
+ return /^https?:\/\//i.test(value);
25
+ }
26
+ async function defaultFetch(path, init) {
27
+ const response = await fetch(path, {
28
+ method: init.method,
29
+ headers: { "content-type": "application/json", accept: "application/json" },
30
+ credentials: "include",
31
+ body: init.body === void 0 ? void 0 : JSON.stringify(init.body),
32
+ signal: init.signal
33
+ });
34
+ const text = await response.text();
35
+ let json = null;
36
+ if (text) {
37
+ try {
38
+ json = JSON.parse(text);
39
+ } catch {
40
+ json = null;
41
+ }
42
+ }
43
+ if (!response.ok) {
44
+ const message = json && typeof json === "object" && typeof json.message === "string" ? json.message : `\uC694\uCCAD\uC774 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4 (${response.status})`;
45
+ throw new Error(message);
46
+ }
47
+ return json;
48
+ }
49
+ var defaultAdapter = {
50
+ navigate: (to) => {
51
+ if (!isSafeNavigationTarget(to)) {
52
+ console.error(`[sdui] \uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 navigate \uB300\uC0C1: ${String(to)}`);
53
+ return;
54
+ }
55
+ window.location.assign(to);
56
+ },
57
+ fetch: defaultFetch,
58
+ rawFetch: (path) => fetch(path, { credentials: "include" }),
59
+ // 항등함수. 계약을 문서화하고, 컴포넌트가 `useSduiAssetResolver()`를 조건 없이
60
+ // 부를 수 있게 하는 것이 전부다. **이 함수 참조 자체가 표식**이기도 하다 —
61
+ // `assetResolverOf`가 이것과 같은지 보고 "주입 없음"을 판정한다.
62
+ resolveAsset: (url) => url,
63
+ invalidate: (prefix) => {
64
+ if (DEV) console.warn(`[sdui] invalidate \uC561\uC158\uC774 \uC5B4\uB311\uD130 \uC5C6\uC774 \uD638\uCD9C\uB428 (prefix: ${prefix})`);
65
+ },
66
+ refresh: () => {
67
+ if (DEV) console.warn("[sdui] refresh \uC561\uC158\uC774 \uC5B4\uB311\uD130 \uC5C6\uC774 \uD638\uCD9C\uB428");
68
+ }
69
+ };
70
+ function resolveAdapter(adapter) {
71
+ const merged = { ...defaultAdapter };
72
+ if (adapter) {
73
+ for (const [key, value] of Object.entries(adapter)) {
74
+ if (value !== void 0) merged[key] = value;
75
+ }
76
+ }
77
+ return merged;
78
+ }
79
+ var SduiAdapterContext = createContext(null);
80
+ function useSduiAdapter() {
81
+ return useContext(SduiAdapterContext);
82
+ }
83
+ function assetResolverOf(adapter) {
84
+ const resolve = adapter?.resolveAsset;
85
+ if (!resolve || resolve === defaultAdapter.resolveAsset) return null;
86
+ return resolve;
87
+ }
88
+ function useSduiAssetResolver() {
89
+ const adapter = useContext(SduiAdapterContext);
90
+ return adapter?.resolveAsset ?? defaultAdapter.resolveAsset;
91
+ }
92
+ function useSduiSandbox() {
93
+ return useContext(SduiAdapterContext)?.sandbox === true;
94
+ }
95
+ function hostFetchOf(adapter) {
96
+ const hostFetch = adapter?.fetch;
97
+ if (!hostFetch || hostFetch === defaultAdapter.fetch) return null;
98
+ return hostFetch;
99
+ }
100
+ var HM_RE = /^\d{2}:\d{2}$/;
101
+ var LIST_HEIGHT = 260;
102
+ var MINUTES_PER_DAY = 24 * 60;
103
+ function maskHm(text) {
104
+ const digits = text.replace(/\D/g, "").slice(0, 4);
105
+ if (digits.length <= 2) return digits;
106
+ return `${digits.slice(0, 2)}:${digits.slice(2)}`;
107
+ }
108
+ function parseHm(value) {
109
+ if (typeof value !== "string" || !HM_RE.test(value)) return void 0;
110
+ const h = Number(value.slice(0, 2));
111
+ const m = Number(value.slice(3, 5));
112
+ if (h > 23 || m > 59) return void 0;
113
+ return h * 60 + m;
114
+ }
115
+ function formatHm(minutes) {
116
+ const h = Math.floor(minutes / 60) % 24;
117
+ const m = minutes % 60;
118
+ return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
119
+ }
120
+ function buildOptions(step, min, max) {
121
+ const safeStep = Number.isFinite(step) && step >= 1 ? Math.min(Math.floor(step), 720) : 30;
122
+ const from = min ?? 0;
123
+ const to = max ?? MINUTES_PER_DAY - 1;
124
+ const out = [];
125
+ for (let m = Math.ceil(from / safeStep) * safeStep; m <= to; m += safeStep) out.push(formatHm(m));
126
+ return out;
127
+ }
128
+ function TimePickerImpl({
129
+ value,
130
+ onChange,
131
+ min,
132
+ max,
133
+ step = 30,
134
+ placeholder,
135
+ invalid = false,
136
+ disabled = false,
137
+ size = "md"
138
+ }) {
139
+ const [open, setOpen] = useState(false);
140
+ const [innerValue, setInnerValue] = useState("");
141
+ const hostRef = useRef(null);
142
+ const popRef = useRef(null);
143
+ const inputRef = useRef(null);
144
+ const isControlled = value !== void 0;
145
+ const currentValue = (isControlled ? value : innerValue) ?? "";
146
+ const [text, setText] = useState(currentValue);
147
+ useEffect(() => {
148
+ setText(currentValue);
149
+ }, [currentValue]);
150
+ const minMinutes = useMemo(() => parseHm(min), [min]);
151
+ const maxMinutes = useMemo(() => parseHm(max), [max]);
152
+ const options = useMemo(() => buildOptions(step, minMinutes, maxMinutes), [step, minMinutes, maxMinutes]);
153
+ const dismiss = useCallback((reason) => {
154
+ setOpen(false);
155
+ if (reason === "escape") inputRef.current?.focus();
156
+ }, []);
157
+ useDismiss(open, hostRef, popRef, dismiss);
158
+ const anchor = usePopoverAnchor(open, hostRef, popRef, LIST_HEIGHT);
159
+ const portal = usePortalTo();
160
+ const isInvalid = Boolean(invalid);
161
+ const commit = (next) => {
162
+ if (!isControlled) setInnerValue(next);
163
+ onChange?.(next);
164
+ };
165
+ const handleTextChange = (raw) => {
166
+ const masked = maskHm(raw);
167
+ setText(masked);
168
+ if (masked === "") {
169
+ commit("");
170
+ return;
171
+ }
172
+ const minutes = parseHm(masked);
173
+ if (minutes === void 0) return;
174
+ if (minMinutes !== void 0 && minutes < minMinutes) return;
175
+ if (maxMinutes !== void 0 && minutes > maxMinutes) return;
176
+ commit(masked);
177
+ };
178
+ const focusList = () => {
179
+ const list = popRef.current;
180
+ if (!list) return;
181
+ (list.querySelector('[aria-selected="true"]') ?? list.querySelector('[role="option"]'))?.focus();
182
+ };
183
+ const handleKeyDown = (event) => {
184
+ if (event.key === "ArrowDown") {
185
+ event.preventDefault();
186
+ if (open) focusList();
187
+ else setOpen(true);
188
+ return;
189
+ }
190
+ if (event.key === "Enter" && open) {
191
+ event.preventDefault();
192
+ setOpen(false);
193
+ }
194
+ };
195
+ const handleOptionKeyDown = (index) => (event) => {
196
+ const step2 = event.key === "ArrowDown" ? 1 : event.key === "ArrowUp" ? -1 : 0;
197
+ if (step2 !== 0) {
198
+ event.preventDefault();
199
+ const next = Math.min(Math.max(index + step2, 0), options.length - 1);
200
+ popRef.current?.querySelectorAll('[role="option"]')[next]?.focus();
201
+ return;
202
+ }
203
+ if (event.key === "Home" || event.key === "End") {
204
+ event.preventDefault();
205
+ const target = event.key === "Home" ? 0 : options.length - 1;
206
+ popRef.current?.querySelectorAll('[role="option"]')[target]?.focus();
207
+ }
208
+ };
209
+ const pick = (option) => {
210
+ setText(option);
211
+ commit(option);
212
+ setOpen(false);
213
+ inputRef.current?.focus();
214
+ };
215
+ const handleBlur = (event) => {
216
+ if (focusStaysInside(event.relatedTarget, hostRef, popRef)) return;
217
+ if (text !== currentValue) setText(currentValue);
218
+ setOpen(false);
219
+ };
220
+ const classes = ["wp-input", "wp-input--mono", "wp-tp-input"];
221
+ if (size !== "md") classes.push(`wp-input--${size}`);
222
+ if (isInvalid) classes.push("wp-input--invalid");
223
+ return /* @__PURE__ */ jsxs("div", { ref: hostRef, className: "wp-tp-host", onBlur: handleBlur, children: [
224
+ /* @__PURE__ */ jsx(HoistedStyle, { id: "wp-timepicker", css: TP_CSS }),
225
+ /* @__PURE__ */ jsx(AnchoredPopoverStyles, {}),
226
+ /* @__PURE__ */ jsxs("div", { className: "wp-inputwrap wp-inputwrap--lead", children: [
227
+ /* @__PURE__ */ jsx("span", { className: "wp-inputwrap__lead", children: /* @__PURE__ */ jsx(Icon, { name: "clock", size: size === "lg" ? 16 : 15 }) }),
228
+ /* @__PURE__ */ jsx(
229
+ "input",
230
+ {
231
+ ref: inputRef,
232
+ type: "text",
233
+ className: classes.join(" "),
234
+ inputMode: "numeric",
235
+ autoComplete: "off",
236
+ spellCheck: false,
237
+ maxLength: 5,
238
+ placeholder: placeholder ?? "HH:MM",
239
+ value: text,
240
+ disabled,
241
+ "aria-invalid": isInvalid || void 0,
242
+ "aria-haspopup": "listbox",
243
+ "aria-expanded": open,
244
+ onChange: (event) => handleTextChange(event.target.value),
245
+ onKeyDown: handleKeyDown,
246
+ onPointerDown: () => {
247
+ if (!disabled && !open) setOpen(true);
248
+ }
249
+ }
250
+ )
251
+ ] }),
252
+ portal(
253
+ anchor,
254
+ /* @__PURE__ */ jsx(
255
+ "div",
256
+ {
257
+ ref: popRef,
258
+ className: "wp-anchored-pop wp-tp-pop",
259
+ style: anchorStyle(anchor),
260
+ role: "listbox",
261
+ "aria-label": "\uC2DC\uAC01 \uC120\uD0DD",
262
+ children: options.map((option, index) => /* @__PURE__ */ jsx(
263
+ "button",
264
+ {
265
+ type: "button",
266
+ role: "option",
267
+ "aria-selected": option === currentValue,
268
+ className: option === currentValue ? "wp-tp-opt wp-tp-opt--on" : "wp-tp-opt",
269
+ onKeyDown: handleOptionKeyDown(index),
270
+ onClick: () => pick(option),
271
+ children: option
272
+ },
273
+ option
274
+ ))
275
+ }
276
+ )
277
+ )
278
+ ] });
279
+ }
280
+ var TP_CSS = `
281
+ .wp-tp-host{position:relative}
282
+ .wp-tp-input{padding-right:6px;min-width:calc(5ch + 36px)}
283
+ .wp-tp-pop{padding:var(--wp-space-1);max-height:260px;overflow-y:auto;overscroll-behavior:contain}
284
+ .wp-tp-opt{display:block;width:100%;padding:6px 10px;border:none;background:transparent;border-radius:var(--wp-radius-sm);font:inherit;font-family:var(--wp-font-mono);font-size:12.5px;color:var(--wp-text-default);text-align:left;cursor:pointer}
285
+ .wp-tp-opt:hover{background:var(--wp-surface-hover)}
286
+ .wp-tp-opt:focus-visible{outline:2px solid var(--wp-border-focus);outline-offset:-2px}
287
+ .wp-tp-opt--on{background:var(--wp-action-primary-bg);color:var(--wp-action-primary-fg);font-weight:500}
288
+ `;
289
+ var TimePicker = boxed(TimePickerImpl, "TimePicker");
290
+
291
+ export { DEV, SduiAdapterContext, TimePicker, assetResolverOf, defaultAdapter, hostFetchOf, isSafeNavigationTarget, resolveAdapter, useSduiAdapter, useSduiAssetResolver, useSduiSandbox };
@@ -1,4 +1,4 @@
1
- import { buildCascadingTree } from './chunk-GUFIEWW2.js';
1
+ import { buildCascadingTree } from './chunk-U7ZK7WYH.js';
2
2
  import { Input, IconButton, EmptyState, Icon, Badge } from './chunk-WIDDHR7L.js';
3
3
  import { boxed } from './chunk-SAN4IX6N.js';
4
4
  import React, { useMemo, useState, useRef, useEffect } from 'react';
@@ -1,5 +1,5 @@
1
- import { includesLooseSearch } from './chunk-GUFIEWW2.js';
2
- import { OverlayLayer } from './chunk-IY7SMV6V.js';
1
+ import { includesLooseSearch } from './chunk-U7ZK7WYH.js';
2
+ import { OverlayLayer } from './chunk-XKLUCUVA.js';
3
3
  import { Button, Dialog, Input, Tag, Checkbox } from './chunk-WIDDHR7L.js';
4
4
  import { boxed } from './chunk-SAN4IX6N.js';
5
5
  import React, { useMemo, useState, useEffect } from 'react';
@@ -1,4 +1,4 @@
1
- import { DateRangePicker } from './chunk-IY7SMV6V.js';
1
+ import { DateRangePicker } from './chunk-XKLUCUVA.js';
2
2
  import { SegmentedControl, Select } from './chunk-WIDDHR7L.js';
3
3
  import { boxed } from './chunk-SAN4IX6N.js';
4
4
  import { useState, useMemo } from 'react';
@@ -1,4 +1,4 @@
1
- import { includesLooseSearch } from './chunk-GUFIEWW2.js';
1
+ import { includesLooseSearch } from './chunk-U7ZK7WYH.js';
2
2
  import { Icon, Button, Input, Spinner } from './chunk-WIDDHR7L.js';
3
3
  import { boxed } from './chunk-SAN4IX6N.js';
4
4
  import { useId, useRef, useState, useEffect, useMemo } from 'react';
@@ -1,4 +1,4 @@
1
- import { useOverlayLayer, DateRangePicker } from './chunk-IY7SMV6V.js';
1
+ import { useOverlayLayer, DateRangePicker } from './chunk-XKLUCUVA.js';
2
2
  import { Input, Button, IconButton, Tag, Field, Checkbox } from './chunk-WIDDHR7L.js';
3
3
  import { useRef, useEffect, useState, useMemo } from 'react';
4
4
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
@@ -135,6 +135,85 @@ function OverlayLayer({
135
135
  function HoistedStyle({ id, css }) {
136
136
  return /* @__PURE__ */ jsx("style", { href: id, precedence: "medium", children: css });
137
137
  }
138
+ var ANCHOR_GAP = 8;
139
+ var VIEWPORT_EDGE = 8;
140
+ function anchorTo(host, height) {
141
+ if (!host || typeof window === "undefined") return null;
142
+ const rect = host.getBoundingClientRect();
143
+ const below = window.innerHeight - rect.bottom;
144
+ const up = below < height && rect.top > below;
145
+ return {
146
+ left: rect.left,
147
+ top: up ? void 0 : rect.bottom + ANCHOR_GAP,
148
+ bottom: up ? window.innerHeight - rect.top + ANCHOR_GAP : void 0,
149
+ minWidth: rect.width
150
+ };
151
+ }
152
+ function usePopoverAnchor(open, hostRef, popRef, height) {
153
+ const [anchor, setAnchor] = useState(null);
154
+ useLayoutEffect(() => {
155
+ if (!open) {
156
+ setAnchor(null);
157
+ return;
158
+ }
159
+ const place = () => setAnchor(anchorTo(hostRef.current, height));
160
+ place();
161
+ window.addEventListener("scroll", place, true);
162
+ window.addEventListener("resize", place);
163
+ return () => {
164
+ window.removeEventListener("scroll", place, true);
165
+ window.removeEventListener("resize", place);
166
+ };
167
+ }, [open, hostRef, height]);
168
+ useLayoutEffect(() => {
169
+ const el = popRef.current;
170
+ if (!open || !el || !anchor) return;
171
+ const overflow = el.getBoundingClientRect().right - (window.innerWidth - VIEWPORT_EDGE);
172
+ if (overflow <= 1) return;
173
+ setAnchor((prev) => {
174
+ if (!prev) return prev;
175
+ const left = Math.max(VIEWPORT_EDGE, prev.left - overflow);
176
+ return Math.abs(left - prev.left) < 1 ? prev : { ...prev, left };
177
+ });
178
+ }, [open, anchor, popRef]);
179
+ return anchor;
180
+ }
181
+ function AnchoredPopoverStyles() {
182
+ return /* @__PURE__ */ jsx(HoistedStyle, { id: "wp-anchored-pop", css: ANCHORED_POP_CSS });
183
+ }
184
+ var ANCHORED_POP_CSS = `
185
+ .wp-anchored-pop{position:fixed;width:max-content;max-width:min(92vw,400px);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:var(--wp-z-dialog);pointer-events:auto}
186
+ `;
187
+ function anchorStyle(anchor) {
188
+ if (!anchor) return void 0;
189
+ return { left: anchor.left, top: anchor.top, bottom: anchor.bottom, minWidth: anchor.minWidth };
190
+ }
191
+ function usePortalTo() {
192
+ const container = React.useContext(OverlayContainerContext);
193
+ return (anchor, node) => {
194
+ if (!anchor) return null;
195
+ const root = container ?? getOverlayRoot();
196
+ return root ? createPortal(node, root) : node;
197
+ };
198
+ }
199
+ function useDismiss(open, hostRef, popRef, onDismiss) {
200
+ useEffect(() => {
201
+ if (!open) return;
202
+ const handlePointerDown = (event) => {
203
+ const target = event.target;
204
+ if (target instanceof Node && (hostRef.current?.contains(target) || popRef.current?.contains(target))) return;
205
+ onDismiss("outside");
206
+ };
207
+ document.addEventListener("pointerdown", handlePointerDown);
208
+ return () => {
209
+ document.removeEventListener("pointerdown", handlePointerDown);
210
+ };
211
+ }, [open, hostRef, popRef, onDismiss]);
212
+ useOverlayLayer(open, () => onDismiss("escape"), hostRef);
213
+ }
214
+ function focusStaysInside(next, hostRef, popRef) {
215
+ return next instanceof Node && Boolean(hostRef.current?.contains(next) || popRef.current?.contains(next));
216
+ }
138
217
  var YMD_RE = /^\d{4}-\d{2}-\d{2}$/;
139
218
  var HM_RE = /^\d{2}:\d{2}$/;
140
219
  function parseYmd(value) {
@@ -365,71 +444,7 @@ function Calendar({
365
444
  })
366
445
  ] }) });
367
446
  }
368
- function useDismiss(open, hostRef, popRef, onDismiss) {
369
- useEffect(() => {
370
- if (!open) return;
371
- const handlePointerDown = (event) => {
372
- const target = event.target;
373
- if (target instanceof Node && (hostRef.current?.contains(target) || popRef.current?.contains(target))) return;
374
- onDismiss("outside");
375
- };
376
- document.addEventListener("pointerdown", handlePointerDown);
377
- return () => {
378
- document.removeEventListener("pointerdown", handlePointerDown);
379
- };
380
- }, [open, hostRef, popRef, onDismiss]);
381
- useOverlayLayer(open, () => onDismiss("escape"), hostRef);
382
- }
383
447
  var CALENDAR_HEIGHT = 340;
384
- var ANCHOR_GAP = 8;
385
- var VIEWPORT_EDGE = 8;
386
- function anchorTo(host) {
387
- if (!host || typeof window === "undefined") return null;
388
- const rect = host.getBoundingClientRect();
389
- const below = window.innerHeight - rect.bottom;
390
- const up = below < CALENDAR_HEIGHT && rect.top > below;
391
- return {
392
- left: rect.left,
393
- top: up ? void 0 : rect.bottom + ANCHOR_GAP,
394
- bottom: up ? window.innerHeight - rect.top + ANCHOR_GAP : void 0,
395
- minWidth: rect.width
396
- };
397
- }
398
- function usePopoverAnchor(open, hostRef, popRef) {
399
- const [anchor, setAnchor] = useState(null);
400
- useLayoutEffect(() => {
401
- if (!open) {
402
- setAnchor(null);
403
- return;
404
- }
405
- const place = () => setAnchor(anchorTo(hostRef.current));
406
- place();
407
- window.addEventListener("scroll", place, true);
408
- window.addEventListener("resize", place);
409
- return () => {
410
- window.removeEventListener("scroll", place, true);
411
- window.removeEventListener("resize", place);
412
- };
413
- }, [open, hostRef]);
414
- useLayoutEffect(() => {
415
- const el = popRef.current;
416
- if (!open || !el || !anchor) return;
417
- const overflow = el.getBoundingClientRect().right - (window.innerWidth - VIEWPORT_EDGE);
418
- if (overflow > 1) {
419
- setAnchor((prev) => prev ? { ...prev, left: Math.max(VIEWPORT_EDGE, prev.left - overflow) } : prev);
420
- }
421
- }, [open, anchor, popRef]);
422
- return anchor;
423
- }
424
- function anchorStyle(anchor) {
425
- if (!anchor) return void 0;
426
- return { left: anchor.left, top: anchor.top, bottom: anchor.bottom, minWidth: anchor.minWidth };
427
- }
428
- function portal(anchor, container, node) {
429
- if (!anchor) return null;
430
- const root = container ?? getOverlayRoot();
431
- return root ? createPortal(node, root) : node;
432
- }
433
448
  function DateInput({
434
449
  inputRef,
435
450
  value,
@@ -512,8 +527,8 @@ function DatePickerImpl({
512
527
  if (reason === "escape") inputRef.current?.focus();
513
528
  }, []);
514
529
  useDismiss(open, hostRef, popRef, dismiss);
515
- const anchor = usePopoverAnchor(open, hostRef, popRef);
516
- const overlayContainer = React.useContext(OverlayContainerContext);
530
+ const anchor = usePopoverAnchor(open, hostRef, popRef, CALENDAR_HEIGHT);
531
+ const portal = usePortalTo();
517
532
  const isInvalid = Boolean(invalid);
518
533
  const commit = (next) => {
519
534
  if (!isControlled) setInnerValue(next);
@@ -551,13 +566,13 @@ function DatePickerImpl({
551
566
  }
552
567
  };
553
568
  const handleBlur = (event) => {
554
- const next = event.relatedTarget;
555
- if (next instanceof Node && (hostRef.current?.contains(next) || popRef.current?.contains(next))) return;
569
+ if (focusStaysInside(event.relatedTarget, hostRef, popRef)) return;
556
570
  if (text !== fieldText) setText(fieldText);
557
571
  setOpen(false);
558
572
  };
559
573
  return /* @__PURE__ */ jsxs("div", { ref: hostRef, className: "wp-dp-host", onBlur: handleBlur, children: [
560
574
  /* @__PURE__ */ jsx(HoistedStyle, { id: "wp-datepicker", css: DP_CSS }),
575
+ /* @__PURE__ */ jsx(AnchoredPopoverStyles, {}),
561
576
  /* @__PURE__ */ jsx(
562
577
  DateInput,
563
578
  {
@@ -579,12 +594,11 @@ function DatePickerImpl({
579
594
  ),
580
595
  portal(
581
596
  anchor,
582
- overlayContainer,
583
597
  /* @__PURE__ */ jsx(
584
598
  "div",
585
599
  {
586
600
  ref: popRef,
587
- className: "wp-dp-pop",
601
+ className: "wp-anchored-pop wp-dp-pop",
588
602
  style: anchorStyle(anchor),
589
603
  role: "dialog",
590
604
  "aria-label": "\uB0A0\uC9DC \uC120\uD0DD \uB2EC\uB825",
@@ -653,8 +667,8 @@ function DateRangePickerImpl({
653
667
  if (reason === "escape") (end === "to" ? toRef : fromRef).current?.focus();
654
668
  }, [end]);
655
669
  useDismiss(open, hostRef, popRef, dismiss);
656
- const anchor = usePopoverAnchor(open, hostRef, popRef);
657
- const overlayContainer = React.useContext(OverlayContainerContext);
670
+ const anchor = usePopoverAnchor(open, hostRef, popRef, CALENDAR_HEIGHT);
671
+ const portal = usePortalTo();
658
672
  const isInvalid = Boolean(invalid);
659
673
  const commit = (nextFrom, nextTo) => {
660
674
  const next = { from: nextFrom || void 0, to: nextTo || void 0 };
@@ -705,8 +719,7 @@ function DateRangePickerImpl({
705
719
  }
706
720
  };
707
721
  const handleBlur = (event) => {
708
- const next = event.relatedTarget;
709
- if (next instanceof Node && (hostRef.current?.contains(next) || popRef.current?.contains(next))) return;
722
+ if (focusStaysInside(event.relatedTarget, hostRef, popRef)) return;
710
723
  if (fromText !== fromValue) setFromText(fromValue);
711
724
  if (toText !== toValue) setToText(toValue);
712
725
  setOpen(false);
@@ -732,6 +745,7 @@ function DateRangePickerImpl({
732
745
  };
733
746
  return /* @__PURE__ */ jsxs("div", { ref: hostRef, className: "wp-dp-host wp-dp-host--range", onBlur: handleBlur, children: [
734
747
  /* @__PURE__ */ jsx(HoistedStyle, { id: "wp-datepicker", css: DP_CSS }),
748
+ /* @__PURE__ */ jsx(AnchoredPopoverStyles, {}),
735
749
  /* @__PURE__ */ jsxs("div", { className: "wp-dp-range", children: [
736
750
  /* @__PURE__ */ jsx(
737
751
  DateInput,
@@ -779,12 +793,11 @@ function DateRangePickerImpl({
779
793
  ] }),
780
794
  portal(
781
795
  anchor,
782
- overlayContainer,
783
796
  /* @__PURE__ */ jsx(
784
797
  "div",
785
798
  {
786
799
  ref: popRef,
787
- className: "wp-dp-pop wp-dp-pop--wide",
800
+ className: "wp-anchored-pop wp-dp-pop wp-dp-pop--wide",
788
801
  style: anchorStyle(anchor),
789
802
  role: "dialog",
790
803
  "aria-label": "\uAE30\uAC04 \uC120\uD0DD \uB2EC\uB825",
@@ -819,7 +832,6 @@ function DateRangePickerImpl({
819
832
  }
820
833
  var DP_CSS = `
821
834
  .wp-dp-host{position:relative}
822
- .wp-dp-pop{position:fixed;width:max-content;max-width:min(92vw,400px);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:var(--wp-z-dialog);pointer-events:auto}
823
835
  .wp-dp-pop--wide{max-width:min(92vw,720px)}
824
836
  .wp-dp-pop__body{padding:var(--wp-space-4);max-height:60vh;overflow:auto}
825
837
  .wp-dp-range{display:flex;align-items:center;gap:var(--wp-space-1)}
@@ -860,4 +872,4 @@ var DP_CSS = `
860
872
  var DatePicker = boxed(DatePickerImpl, "DatePicker");
861
873
  var DateRangePicker = boxed(DateRangePickerImpl, "DateRangePicker");
862
874
 
863
- export { DatePicker, DateRangePicker, HoistedStyle, OverlayContainerContext, OverlayLayer, getOverlayRoot, useOverlayLayer };
875
+ export { AnchoredPopoverStyles, DatePicker, DateRangePicker, HoistedStyle, OverlayContainerContext, OverlayLayer, anchorStyle, focusStaysInside, getOverlayRoot, useDismiss, useOverlayLayer, usePopoverAnchor, usePortalTo };
package/dist/index.d.ts CHANGED
@@ -4,8 +4,8 @@ import { S as SduiPage, a as SduiAction, b as SduiPropValue, c as SduiRule } fro
4
4
  export { d as SduiArgRef, e as SduiCalcRef, f as SduiChild, g as SduiIndexRef, h as SduiItemRef, i as SduiNode, j as SduiPageQuery, k as SduiScalar, l as SduiStateRef } from './types-opTPYysN.js';
5
5
  import { S as SduiBoxProps, a as SduiContainerLayoutProps, b as SduiColumnsScale, c as SduiGapScale, d as SduiWeightScale, e as SduiTextAlignScale } from './layout-B1QrIBm6.js';
6
6
  export { C as CHILD_LAYOUT_PROPS, L as LAYOUT_CSS, f as SduiAlignScale, g as SduiChildLayoutProps, h as SduiSizeScale, i as SduiSpanScale, j as childLayoutFrom, k as columnsClass, l as containerLayoutStyle, m as joinClass, n as mergeStyle, t as typographyStyle } from './layout-B1QrIBm6.js';
7
- import { D as DatePickerProps, a as DateRangePickerProps } from './DatePicker-CPD2cfmG.js';
8
- export { b as DatePicker, c as DateRangePicker, d as DateRangeValue } from './DatePicker-CPD2cfmG.js';
7
+ import { D as DatePickerProps, a as DateRangePickerProps, T as TimePickerProps } from './DatePicker-D2EtpvkQ.js';
8
+ export { b as DatePicker, c as DateRangePicker, d as DateRangeValue, e as TimePicker } from './DatePicker-D2EtpvkQ.js';
9
9
 
10
10
  /**
11
11
  * 호스트 앱 연결점 — ERP에서는 라우터·erpFetch·SSE가 하던 일을
@@ -1541,6 +1541,7 @@ declare const defaultRegistry: {
1541
1541
  Checkbox: typeof Checkbox;
1542
1542
  DatePicker: React.FC<DatePickerProps & SduiBoxProps>;
1543
1543
  DateRangePicker: React.FC<DateRangePickerProps & SduiBoxProps>;
1544
+ TimePicker: React.FC<TimePickerProps & SduiBoxProps>;
1544
1545
  Dialog: typeof SduiDialog;
1545
1546
  EmptyState: typeof EmptyState;
1546
1547
  Field: typeof Field;
@@ -1830,4 +1831,4 @@ interface ImageViewerProps {
1830
1831
  */
1831
1832
  declare function ImageViewer({ src, alt, open, onClose }: ImageViewerProps): React__default.ReactPortal | null;
1832
1833
 
1833
- export { Actions, type ActionsProps, Alert, type AlertProps, Avatar, type AvatarProps, Badge, type BadgeProps, type BadgeTone, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Button, type ButtonProps, Can, type CanProps, Card, type CardProps, type CascadingSearchOption, Checkbox, type CheckboxProps, Col, type ColProps, type CustomActionHandler, type CustomValidator, DatePickerProps, DateRangePickerProps, Dialog, type DialogProps, Divider, EmptyState, type EmptyStateProps, Field, type FieldProps, Grid, type GridCardConfig, type GridCardRow, type GridProps, Icon, IconButton, type IconButtonProps, type IconProps, ImageViewer, type ImageViewerProps, Input, type InputProps, type Justify, type KeyValueItem, KeyValueList, type KeyValueListProps, type MeterTone, NodeErrorBoundary, OverlayContainerContext, OverlayLayer, PageHead, type PageHeadProps, Pagination, type PaginationProps, ProgressBar, type ProgressBarProps, Radio, type RadioProps, RemoteSearchResults, type RemoteSearchResultsProps, ResultTable, type ResultTableColumn, type ResultTableProps, Row, type RowProps, SduiAction, type SduiAdapter, type SduiAssetContext, type SduiAssetResolver, type SduiBadgeCell, SduiBoxProps, type SduiCanFn, type SduiCanQuery, type SduiCanSetting, type SduiCell, SduiColumnsScale, SduiContainerLayoutProps, type SduiDateCell, SduiDialog, type SduiDispatch, SduiForm, type SduiFormConfig, type SduiFormProps, SduiGapScale, type SduiGridCard, type SduiGridCardAction, type SduiGridCardRow, type SduiIconCell, type SduiImageCell, type SduiLinkCell, type SduiNumberCell, SduiPage, SduiPermissionProvider, type SduiProgressCell, SduiPropValue, type SduiRegistry, SduiRenderer, type SduiRendererProps, type SduiRowCondition, SduiRule, type SduiRuntime, type SduiSearchAction, type SduiSearchColumn, SduiSearchDataProvider, SduiSearchResults, type SduiSearchResultsProps, type SduiTagCell, SduiTextAlignScale, type SduiTextCell, type SduiUseSpec, SduiWeightScale, type SearchAction, type SearchColumn, type SearchFetcher, type SearchField, type SearchFilter, type SearchOption, type SearchPage, SearchResults, type SearchResultsProps, type SearchRow, SearchToolbar, type SearchValues, type SearchViewItem, Section, type SectionProps, SegmentedControl, type SegmentedControlProps, type SegmentedItem, Select, type SelectOption, type SelectOptionRecord, type SelectProps, Show, type ShowProps, SidebarNav, type SidebarNavItem, type SidebarNavProps, type SidebarNavSection, type SortDir, SortHandle, type SortHandleProps, SortableList, type SortableListProps, Spacer, type SpacerProps, type SpecIssue, Spinner, type SpinnerProps, StatCard, type StatCardProps, Switch, type SwitchProps, type TabItem, Table, type TableColumn, type TableProps, Tabs, type TabsProps, Tag, type TagProps, Text, type TextPreset, type TextProps, Textarea, type TextareaProps, Toast, type ToastProps, Tooltip, type TooltipProps, boxed, cascadingValuePaths, cellRenderer, cleanSearchValues, createRegistry, defaultAdapter, defaultRegistry, getOverlayRoot, imageRenderer, includesLooseSearch, joinMultiValue, normalizePageSizes, normalizeSearchText, searchFieldKeys, searchValuesFromParams, sortableDropIndex, sortableMove, splitMultiValue, useOverlayLayer, useRemoteSearchPage, useSduiAssetResolver, useSduiCan, useSduiDispatch, useSduiRuntime, useSduiSandbox, useSearchFetcher, validateSpec };
1834
+ export { Actions, type ActionsProps, Alert, type AlertProps, Avatar, type AvatarProps, Badge, type BadgeProps, type BadgeTone, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Button, type ButtonProps, Can, type CanProps, Card, type CardProps, type CascadingSearchOption, Checkbox, type CheckboxProps, Col, type ColProps, type CustomActionHandler, type CustomValidator, DatePickerProps, DateRangePickerProps, Dialog, type DialogProps, Divider, EmptyState, type EmptyStateProps, Field, type FieldProps, Grid, type GridCardConfig, type GridCardRow, type GridProps, Icon, IconButton, type IconButtonProps, type IconProps, ImageViewer, type ImageViewerProps, Input, type InputProps, type Justify, type KeyValueItem, KeyValueList, type KeyValueListProps, type MeterTone, NodeErrorBoundary, OverlayContainerContext, OverlayLayer, PageHead, type PageHeadProps, Pagination, type PaginationProps, ProgressBar, type ProgressBarProps, Radio, type RadioProps, RemoteSearchResults, type RemoteSearchResultsProps, ResultTable, type ResultTableColumn, type ResultTableProps, Row, type RowProps, SduiAction, type SduiAdapter, type SduiAssetContext, type SduiAssetResolver, type SduiBadgeCell, SduiBoxProps, type SduiCanFn, type SduiCanQuery, type SduiCanSetting, type SduiCell, SduiColumnsScale, SduiContainerLayoutProps, type SduiDateCell, SduiDialog, type SduiDispatch, SduiForm, type SduiFormConfig, type SduiFormProps, SduiGapScale, type SduiGridCard, type SduiGridCardAction, type SduiGridCardRow, type SduiIconCell, type SduiImageCell, type SduiLinkCell, type SduiNumberCell, SduiPage, SduiPermissionProvider, type SduiProgressCell, SduiPropValue, type SduiRegistry, SduiRenderer, type SduiRendererProps, type SduiRowCondition, SduiRule, type SduiRuntime, type SduiSearchAction, type SduiSearchColumn, SduiSearchDataProvider, SduiSearchResults, type SduiSearchResultsProps, type SduiTagCell, SduiTextAlignScale, type SduiTextCell, type SduiUseSpec, SduiWeightScale, type SearchAction, type SearchColumn, type SearchFetcher, type SearchField, type SearchFilter, type SearchOption, type SearchPage, SearchResults, type SearchResultsProps, type SearchRow, SearchToolbar, type SearchValues, type SearchViewItem, Section, type SectionProps, SegmentedControl, type SegmentedControlProps, type SegmentedItem, Select, type SelectOption, type SelectOptionRecord, type SelectProps, Show, type ShowProps, SidebarNav, type SidebarNavItem, type SidebarNavProps, type SidebarNavSection, type SortDir, SortHandle, type SortHandleProps, SortableList, type SortableListProps, Spacer, type SpacerProps, type SpecIssue, Spinner, type SpinnerProps, StatCard, type StatCardProps, Switch, type SwitchProps, type TabItem, Table, type TableColumn, type TableProps, Tabs, type TabsProps, Tag, type TagProps, Text, type TextPreset, type TextProps, Textarea, type TextareaProps, TimePickerProps, Toast, type ToastProps, Tooltip, type TooltipProps, boxed, cascadingValuePaths, cellRenderer, cleanSearchValues, createRegistry, defaultAdapter, defaultRegistry, getOverlayRoot, imageRenderer, includesLooseSearch, joinMultiValue, normalizePageSizes, normalizeSearchText, searchFieldKeys, searchValuesFromParams, sortableDropIndex, sortableMove, splitMultiValue, useOverlayLayer, useRemoteSearchPage, useSduiAssetResolver, useSduiCan, useSduiDispatch, useSduiRuntime, useSduiSandbox, useSearchFetcher, validateSpec };
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
- import { DEV, isSafeNavigationTarget, useSduiAdapter, hostFetchOf, useSduiSandbox, resolveAdapter, assetResolverOf, SduiAdapterContext } from './chunk-WGRB5UL4.js';
2
- export { defaultAdapter, useSduiAssetResolver, useSduiSandbox } from './chunk-WGRB5UL4.js';
1
+ import { DEV, isSafeNavigationTarget, useSduiAdapter, hostFetchOf, TimePicker, useSduiSandbox, resolveAdapter, assetResolverOf, SduiAdapterContext } from './chunk-HPZTFZZN.js';
2
+ export { TimePicker, defaultAdapter, useSduiAssetResolver, useSduiSandbox } from './chunk-HPZTFZZN.js';
3
3
  import { ImageViewer } from './chunk-THEP3VWY.js';
4
4
  export { ImageViewer } from './chunk-THEP3VWY.js';
5
- import { normalizeSearchFields, searchValuesFromParams, splitMultiValue, includesLooseSearch, cascadingValuePaths, SearchToolbar, searchFieldKeys, Popover } from './chunk-GUFIEWW2.js';
6
- export { SearchToolbar, cascadingValuePaths, cleanSearchValues, includesLooseSearch, joinMultiValue, normalizeSearchText, searchFieldKeys, searchValuesFromParams, splitMultiValue } from './chunk-GUFIEWW2.js';
7
- import { HoistedStyle, DateRangePicker, DatePicker, OverlayLayer } from './chunk-IY7SMV6V.js';
8
- export { DatePicker, DateRangePicker, OverlayContainerContext, OverlayLayer, getOverlayRoot, useOverlayLayer } from './chunk-IY7SMV6V.js';
5
+ import { normalizeSearchFields, searchValuesFromParams, splitMultiValue, includesLooseSearch, cascadingValuePaths, SearchToolbar, searchFieldKeys, Popover } from './chunk-U7ZK7WYH.js';
6
+ export { SearchToolbar, cascadingValuePaths, cleanSearchValues, includesLooseSearch, joinMultiValue, normalizeSearchText, searchFieldKeys, searchValuesFromParams, splitMultiValue } from './chunk-U7ZK7WYH.js';
7
+ import { HoistedStyle, DateRangePicker, DatePicker, OverlayLayer } from './chunk-XKLUCUVA.js';
8
+ export { DatePicker, DateRangePicker, OverlayContainerContext, OverlayLayer, getOverlayRoot, useOverlayLayer } from './chunk-XKLUCUVA.js';
9
9
  import { EmptyState, Button, Checkbox, SegmentedControl, Spinner, Select, Pagination, Icon, Card, Badge, ProgressBar, Tag, Toast, Textarea, Tabs, Table, Switch, StatCard, Input, IconButton, Field, Alert, warnOnce, Dialog } from './chunk-WIDDHR7L.js';
10
10
  export { Alert, Avatar, Badge, Breadcrumb, Button, Card, Checkbox, Dialog, EmptyState, Field, Icon, IconButton, Input, Pagination, ProgressBar, Radio, SegmentedControl, Select, SidebarNav, Spinner, StatCard, Switch, Table, Tabs, Tag, Textarea, Toast, Tooltip } from './chunk-WIDDHR7L.js';
11
11
  import { boxed, mergeStyle, containerLayoutStyle, joinClass, columnsClass, typographyStyle, LAYOUT_CSS, childLayoutFrom, CHILD_LAYOUT_PROPS } from './chunk-SAN4IX6N.js';
@@ -1725,6 +1725,7 @@ var defaultRegistry = {
1725
1725
  Checkbox,
1726
1726
  DatePicker,
1727
1727
  DateRangePicker,
1728
+ TimePicker,
1728
1729
  // Dialog는 표준 닫기 UX(Escape·배경 클릭)가 붙은 래퍼로 등록한다
1729
1730
  Dialog: SduiDialog,
1730
1731
  EmptyState,
package/dist/meta.d.ts CHANGED
@@ -285,6 +285,7 @@ declare const ButtonMeta: SduiComponentMeta;
285
285
  declare const CheckboxMeta: SduiComponentMeta;
286
286
  declare const DatePickerMeta: SduiComponentMeta;
287
287
  declare const DateRangePickerMeta: SduiComponentMeta;
288
+ declare const TimePickerMeta: SduiComponentMeta;
288
289
  declare const FieldMeta: SduiComponentMeta;
289
290
  declare const FormMeta: SduiComponentMeta;
290
291
  declare const IconButtonMeta: SduiComponentMeta;
@@ -343,6 +344,7 @@ declare const coreComponentMeta: {
343
344
  Checkbox: SduiComponentMeta;
344
345
  Col: SduiComponentMeta;
345
346
  DatePicker: SduiComponentMeta;
347
+ TimePicker: SduiComponentMeta;
346
348
  DateRangePicker: SduiComponentMeta;
347
349
  Dialog: SduiComponentMeta;
348
350
  Divider: SduiComponentMeta;
@@ -584,4 +586,4 @@ declare function paletteGroups(catalog?: Record<string, SduiComponentMeta>): Arr
584
586
  items: SduiComponentMeta[];
585
587
  }>;
586
588
 
587
- export { ActionsMeta, AlertMeta, AutocompleteFieldMeta, AvatarMeta, BadgeMeta, BreadcrumbMeta, ButtonMeta, CanMeta, CardMeta, ChartMeta, CheckboxMeta, ColMeta, ColumnBoardMeta, DatePickerMeta, DateRangePickerMeta, DialogMeta, DiffViewMeta, DisclosureMeta, DividerMeta, EmptyStateMeta, EventCalendarMeta, FieldMeta, ForEachMeta, FormMeta, FragmentMeta, GridMeta, IconButtonMeta, IconMeta, ImageLightboxMeta, InlineEditMeta, InputMeta, JsonFieldMeta, KeyValueListMeta, LiveClockMeta, MapPreviewMeta, OrgChartMeta, PageHeadMeta, PaginationMeta, PeriodFilterMeta, PrintSheetMeta, ProgressBarMeta, RadioMeta, RemoteSearchResultsMeta, RowMeta, SDUI_CATEGORIES, SELF_AXIS_PROPS, type SduiCategory, type SduiChildrenMeta, type SduiComponentMeta, type SduiDomMeta, type SduiEnumOption, type SduiPropGroup, type SduiPropKind, type SduiPropMeta, type SduiResizeMeta, type SduiSlotMeta, SearchResultsMeta, SearchableMultiPickerMeta, SectionMeta, SecureScreenMeta, SegmentedControlMeta, SelectMeta, ShowMeta, SidebarNavMeta, SortHandleMeta, SortableListMeta, SpacerMeta, SpinnerMeta, StackedMeterMeta, StatCardMeta, StripMeta, SummaryTableMeta, SwitchMeta, TableMeta, TabsMeta, TagMeta, TagMultiSelectMeta, TextMeta, TextareaMeta, TimelineMeta, ToastMeta, TooltipMeta, TreePickerMeta, TreeViewMeta, allComponentMeta, builtinComponentMeta, coreComponentMeta, dsComponentMeta, getComponentMeta, paletteGroups, richComponentMeta, slotNamesOf, widgetComponentMeta, withSelfAxis, withSelfAxisAll };
589
+ export { ActionsMeta, AlertMeta, AutocompleteFieldMeta, AvatarMeta, BadgeMeta, BreadcrumbMeta, ButtonMeta, CanMeta, CardMeta, ChartMeta, CheckboxMeta, ColMeta, ColumnBoardMeta, DatePickerMeta, DateRangePickerMeta, DialogMeta, DiffViewMeta, DisclosureMeta, DividerMeta, EmptyStateMeta, EventCalendarMeta, FieldMeta, ForEachMeta, FormMeta, FragmentMeta, GridMeta, IconButtonMeta, IconMeta, ImageLightboxMeta, InlineEditMeta, InputMeta, JsonFieldMeta, KeyValueListMeta, LiveClockMeta, MapPreviewMeta, OrgChartMeta, PageHeadMeta, PaginationMeta, PeriodFilterMeta, PrintSheetMeta, ProgressBarMeta, RadioMeta, RemoteSearchResultsMeta, RowMeta, SDUI_CATEGORIES, SELF_AXIS_PROPS, type SduiCategory, type SduiChildrenMeta, type SduiComponentMeta, type SduiDomMeta, type SduiEnumOption, type SduiPropGroup, type SduiPropKind, type SduiPropMeta, type SduiResizeMeta, type SduiSlotMeta, SearchResultsMeta, SearchableMultiPickerMeta, SectionMeta, SecureScreenMeta, SegmentedControlMeta, SelectMeta, ShowMeta, SidebarNavMeta, SortHandleMeta, SortableListMeta, SpacerMeta, SpinnerMeta, StackedMeterMeta, StatCardMeta, StripMeta, SummaryTableMeta, SwitchMeta, TableMeta, TabsMeta, TagMeta, TagMultiSelectMeta, TextMeta, TextareaMeta, TimePickerMeta, TimelineMeta, ToastMeta, TooltipMeta, TreePickerMeta, TreeViewMeta, allComponentMeta, builtinComponentMeta, coreComponentMeta, dsComponentMeta, getComponentMeta, paletteGroups, richComponentMeta, slotNamesOf, widgetComponentMeta, withSelfAxis, withSelfAxisAll };
package/dist/meta.js CHANGED
@@ -991,6 +991,46 @@ var DateRangePickerMeta = {
991
991
  },
992
992
  resize: { kind: "none" }
993
993
  };
994
+ var TimePickerMeta = {
995
+ name: "TimePicker",
996
+ label: "\uC2DC\uAC01 \uC120\uD0DD",
997
+ category: "input",
998
+ icon: "clock",
999
+ description: "\uC2DC\uAC01(HH:MM, 24\uC2DC\uAC04\uC81C) \u2014 \uD0A4\uBCF4\uB4DC\uB85C \uCCD0\uB3C4 \uB418\uACE0 \uB208\uAE08 \uBAA9\uB85D\uC5D0\uC11C \uACE8\uB77C\uB3C4 \uB41C\uB2E4.",
1000
+ props: [
1001
+ { name: "value", kind: "string", label: "\uAC12(HH:MM)", bindable: true, group: "data" },
1002
+ { name: "onChange", kind: "action", label: "\uBCC0\uACBD", eventArg: "HH:MM \uBB38\uC790\uC5F4", group: "behavior" },
1003
+ { name: "min", kind: "string", label: "\uCD5C\uC18C \uC2DC\uAC01", description: "HH:MM, \uD3EC\uD568", bindable: true, group: "validation" },
1004
+ { name: "max", kind: "string", label: "\uCD5C\uB300 \uC2DC\uAC01", description: "HH:MM, \uD3EC\uD568", bindable: true, group: "validation" },
1005
+ { name: "step", kind: "number", label: "\uB208\uAE08 \uAC04\uACA9(\uBD84)", default: 30, group: "behavior" },
1006
+ { name: "placeholder", kind: "string", label: "\uC548\uB0B4 \uBB38\uAD6C", description: "\uAE30\uBCF8\uAC12\uC740 HH:MM", group: "content" },
1007
+ { name: "invalid", kind: "boolean", label: "\uC624\uB958 \uD45C\uC2DC", default: false, bindable: true, group: "validation" },
1008
+ { name: "disabled", kind: "boolean", label: "\uBE44\uD65C\uC131", default: false, bindable: true, group: "behavior" },
1009
+ {
1010
+ name: "size",
1011
+ kind: "enum",
1012
+ label: "\uD06C\uAE30",
1013
+ default: "md",
1014
+ options: [
1015
+ { value: "sm", label: "sm 28px" },
1016
+ { value: "md", label: "md 32px" },
1017
+ { value: "lg", label: "lg 40px" }
1018
+ ],
1019
+ group: "appearance"
1020
+ }
1021
+ ],
1022
+ children: { accepts: false },
1023
+ dom: { element: "div" },
1024
+ preset: {
1025
+ component: "TimePicker",
1026
+ props: {
1027
+ placeholder: "HH:MM",
1028
+ value: { $state: "time", default: "" },
1029
+ onChange: { action: "setState", key: "time", fromEvent: true }
1030
+ }
1031
+ },
1032
+ resize: { kind: "enum", prop: "size", values: ["sm", "md", "lg"] }
1033
+ };
994
1034
  var FieldMeta = {
995
1035
  name: "Field",
996
1036
  label: "\uD3FC \uD544\uB4DC",
@@ -2665,6 +2705,7 @@ var coreComponentMeta = {
2665
2705
  Checkbox: CheckboxMeta,
2666
2706
  Col: ColMeta,
2667
2707
  DatePicker: DatePickerMeta,
2708
+ TimePicker: TimePickerMeta,
2668
2709
  DateRangePicker: DateRangePickerMeta,
2669
2710
  Dialog: DialogMeta,
2670
2711
  Divider: DividerMeta,
@@ -4579,4 +4620,4 @@ function paletteGroups(catalog = allComponentMeta) {
4579
4620
  })).filter((group) => group.items.length > 0);
4580
4621
  }
4581
4622
 
4582
- export { ActionsMeta, AlertMeta, AutocompleteFieldMeta, AvatarMeta, BadgeMeta, BreadcrumbMeta, ButtonMeta, CanMeta, CardMeta, ChartMeta, CheckboxMeta, ColMeta, ColumnBoardMeta, DatePickerMeta, DateRangePickerMeta, DialogMeta, DiffViewMeta, DisclosureMeta, DividerMeta, EmptyStateMeta, EventCalendarMeta, FieldMeta, ForEachMeta, FormMeta, FragmentMeta, GridMeta, IconButtonMeta, IconMeta, ImageLightboxMeta, InlineEditMeta, InputMeta, JsonFieldMeta, KeyValueListMeta, LiveClockMeta, MapPreviewMeta, OrgChartMeta, PageHeadMeta, PaginationMeta, PeriodFilterMeta, PrintSheetMeta, ProgressBarMeta, RadioMeta, RemoteSearchResultsMeta, RowMeta, SDUI_CATEGORIES, SELF_AXIS_PROPS, SearchResultsMeta, SearchableMultiPickerMeta, SectionMeta, SecureScreenMeta, SegmentedControlMeta, SelectMeta, ShowMeta, SidebarNavMeta, SortHandleMeta, SortableListMeta, SpacerMeta, SpinnerMeta, StackedMeterMeta, StatCardMeta, StripMeta, SummaryTableMeta, SwitchMeta, TableMeta, TabsMeta, TagMeta, TagMultiSelectMeta, TextMeta, TextareaMeta, TimelineMeta, ToastMeta, TooltipMeta, TreePickerMeta, TreeViewMeta, allComponentMeta, builtinComponentMeta, coreComponentMeta, dsComponentMeta, getComponentMeta, paletteGroups, richComponentMeta, slotNamesOf, widgetComponentMeta, withSelfAxis, withSelfAxisAll };
4623
+ export { ActionsMeta, AlertMeta, AutocompleteFieldMeta, AvatarMeta, BadgeMeta, BreadcrumbMeta, ButtonMeta, CanMeta, CardMeta, ChartMeta, CheckboxMeta, ColMeta, ColumnBoardMeta, DatePickerMeta, DateRangePickerMeta, DialogMeta, DiffViewMeta, DisclosureMeta, DividerMeta, EmptyStateMeta, EventCalendarMeta, FieldMeta, ForEachMeta, FormMeta, FragmentMeta, GridMeta, IconButtonMeta, IconMeta, ImageLightboxMeta, InlineEditMeta, InputMeta, JsonFieldMeta, KeyValueListMeta, LiveClockMeta, MapPreviewMeta, OrgChartMeta, PageHeadMeta, PaginationMeta, PeriodFilterMeta, PrintSheetMeta, ProgressBarMeta, RadioMeta, RemoteSearchResultsMeta, RowMeta, SDUI_CATEGORIES, SELF_AXIS_PROPS, SearchResultsMeta, SearchableMultiPickerMeta, SectionMeta, SecureScreenMeta, SegmentedControlMeta, SelectMeta, ShowMeta, SidebarNavMeta, SortHandleMeta, SortableListMeta, SpacerMeta, SpinnerMeta, StackedMeterMeta, StatCardMeta, StripMeta, SummaryTableMeta, SwitchMeta, TableMeta, TabsMeta, TagMeta, TagMultiSelectMeta, TextMeta, TextareaMeta, TimePickerMeta, TimelineMeta, ToastMeta, TooltipMeta, TreePickerMeta, TreeViewMeta, allComponentMeta, builtinComponentMeta, coreComponentMeta, dsComponentMeta, getComponentMeta, paletteGroups, richComponentMeta, slotNamesOf, widgetComponentMeta, withSelfAxis, withSelfAxisAll };
package/dist/rich.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import React__default from 'react';
2
- export { b as DatePicker, D as DatePickerProps, c as DateRangePicker, a as DateRangePickerProps, d as DateRangeValue } from './DatePicker-CPD2cfmG.js';
2
+ export { b as DatePicker, D as DatePickerProps, c as DateRangePicker, a as DateRangePickerProps, d as DateRangeValue, e as TimePicker, T as TimePickerProps } from './DatePicker-D2EtpvkQ.js';
3
3
  import './layout-B1QrIBm6.js';
4
4
 
5
5
  /**
package/dist/rich.js CHANGED
@@ -1,6 +1,7 @@
1
- import { useSduiAssetResolver } from './chunk-WGRB5UL4.js';
2
- import { HoistedStyle } from './chunk-IY7SMV6V.js';
3
- export { DatePicker, DateRangePicker } from './chunk-IY7SMV6V.js';
1
+ import { useSduiAssetResolver } from './chunk-HPZTFZZN.js';
2
+ export { TimePicker } from './chunk-HPZTFZZN.js';
3
+ import { HoistedStyle } from './chunk-XKLUCUVA.js';
4
+ export { DatePicker, DateRangePicker } from './chunk-XKLUCUVA.js';
4
5
  import { Spinner, EmptyState } from './chunk-WIDDHR7L.js';
5
6
  import './chunk-SAN4IX6N.js';
6
7
  import { useRef, useCallback, useMemo, useState, useEffect, useLayoutEffect } from 'react';
@@ -1,5 +1,5 @@
1
- export { AutocompleteField, toAutocompleteOptions } from '../chunk-YKROA4XK.js';
2
- import '../chunk-GUFIEWW2.js';
3
- import '../chunk-IY7SMV6V.js';
1
+ export { AutocompleteField, toAutocompleteOptions } from '../chunk-SRGXODY2.js';
2
+ import '../chunk-U7ZK7WYH.js';
3
+ import '../chunk-XKLUCUVA.js';
4
4
  import '../chunk-WIDDHR7L.js';
5
5
  import '../chunk-SAN4IX6N.js';
@@ -1,4 +1,4 @@
1
- export { PeriodFilter } from '../chunk-44VLH3I2.js';
2
- import '../chunk-IY7SMV6V.js';
1
+ export { PeriodFilter } from '../chunk-PFUULIZ2.js';
2
+ import '../chunk-XKLUCUVA.js';
3
3
  import '../chunk-WIDDHR7L.js';
4
4
  import '../chunk-SAN4IX6N.js';
@@ -1,5 +1,5 @@
1
- export { SearchableMultiPicker } from '../chunk-J5QWCUDJ.js';
2
- import '../chunk-GUFIEWW2.js';
3
- import '../chunk-IY7SMV6V.js';
1
+ export { SearchableMultiPicker } from '../chunk-N5IATDUF.js';
2
+ import '../chunk-U7ZK7WYH.js';
3
+ import '../chunk-XKLUCUVA.js';
4
4
  import '../chunk-WIDDHR7L.js';
5
5
  import '../chunk-SAN4IX6N.js';
@@ -1,6 +1,6 @@
1
- export { TreePicker } from '../chunk-F5BAER4V.js';
2
- import '../chunk-TUR4EBKX.js';
3
- import '../chunk-GUFIEWW2.js';
4
- import '../chunk-IY7SMV6V.js';
1
+ export { TreePicker } from '../chunk-EQW24SJC.js';
2
+ import '../chunk-IDUN2ESE.js';
3
+ import '../chunk-U7ZK7WYH.js';
4
+ import '../chunk-XKLUCUVA.js';
5
5
  import '../chunk-WIDDHR7L.js';
6
6
  import '../chunk-SAN4IX6N.js';
@@ -1,5 +1,5 @@
1
- export { TreeView, buildTreeNodes, pruneExcluded, resolveTreeNodes } from '../chunk-TUR4EBKX.js';
2
- import '../chunk-GUFIEWW2.js';
3
- import '../chunk-IY7SMV6V.js';
1
+ export { TreeView, buildTreeNodes, pruneExcluded, resolveTreeNodes } from '../chunk-IDUN2ESE.js';
2
+ import '../chunk-U7ZK7WYH.js';
3
+ import '../chunk-XKLUCUVA.js';
4
4
  import '../chunk-WIDDHR7L.js';
5
5
  import '../chunk-SAN4IX6N.js';
@@ -1,5 +1,5 @@
1
- import { SearchableMultiPicker } from '../chunk-J5QWCUDJ.js';
2
- export { SearchableMultiPicker } from '../chunk-J5QWCUDJ.js';
1
+ import { SearchableMultiPicker } from '../chunk-N5IATDUF.js';
2
+ export { SearchableMultiPicker } from '../chunk-N5IATDUF.js';
3
3
  import { SecureScreen } from '../chunk-IIRENCDS.js';
4
4
  export { SecureScreen } from '../chunk-IIRENCDS.js';
5
5
  import { StackedMeter } from '../chunk-BMZKZGJF.js';
@@ -12,10 +12,10 @@ import { TagMultiSelect } from '../chunk-QDEZXA34.js';
12
12
  export { TagMultiSelect } from '../chunk-QDEZXA34.js';
13
13
  import { Timeline } from '../chunk-EFYEOT47.js';
14
14
  export { Timeline } from '../chunk-EFYEOT47.js';
15
- import { TreePicker } from '../chunk-F5BAER4V.js';
16
- export { TreePicker } from '../chunk-F5BAER4V.js';
17
- import { TreeView } from '../chunk-TUR4EBKX.js';
18
- export { TreeView, buildTreeNodes } from '../chunk-TUR4EBKX.js';
15
+ import { TreePicker } from '../chunk-EQW24SJC.js';
16
+ export { TreePicker } from '../chunk-EQW24SJC.js';
17
+ import { TreeView } from '../chunk-IDUN2ESE.js';
18
+ export { TreeView, buildTreeNodes } from '../chunk-IDUN2ESE.js';
19
19
  import { EventCalendar } from '../chunk-C3JY6GA3.js';
20
20
  export { EventCalendar } from '../chunk-C3JY6GA3.js';
21
21
  import { ImageLightbox } from '../chunk-XPBZMECW.js';
@@ -28,14 +28,14 @@ import { LiveClock } from '../chunk-Z3WGIFJP.js';
28
28
  export { LiveClock } from '../chunk-Z3WGIFJP.js';
29
29
  import { OrgChart } from '../chunk-QWJ3OBBL.js';
30
30
  export { OrgChart } from '../chunk-QWJ3OBBL.js';
31
- import { PeriodFilter } from '../chunk-44VLH3I2.js';
32
- export { PeriodFilter } from '../chunk-44VLH3I2.js';
31
+ import { PeriodFilter } from '../chunk-PFUULIZ2.js';
32
+ export { PeriodFilter } from '../chunk-PFUULIZ2.js';
33
33
  import { PrintSheet } from '../chunk-MYJ2KEIP.js';
34
34
  export { PrintSheet } from '../chunk-MYJ2KEIP.js';
35
35
  import '../chunk-THEP3VWY.js';
36
- export { AutocompleteField, toAutocompleteOptions } from '../chunk-YKROA4XK.js';
37
- import '../chunk-GUFIEWW2.js';
38
- import '../chunk-IY7SMV6V.js';
36
+ export { AutocompleteField, toAutocompleteOptions } from '../chunk-SRGXODY2.js';
37
+ import '../chunk-U7ZK7WYH.js';
38
+ import '../chunk-XKLUCUVA.js';
39
39
  import { ColumnBoard } from '../chunk-C4JRSZGS.js';
40
40
  export { ColumnBoard } from '../chunk-C4JRSZGS.js';
41
41
  import { DiffView } from '../chunk-LDJ2CFFH.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipco/sdui",
3
- "version": "0.19.2",
3
+ "version": "0.20.0",
4
4
  "description": "WIPCO 서버 주도 UI(SDUI) 렌더러 — JSON 페이지 스펙을 React 컴포넌트로 렌더링",
5
5
  "keywords": [
6
6
  "sdui",
@@ -1,99 +0,0 @@
1
- import { createContext, useContext } from 'react';
2
-
3
- // src/adapter.ts
4
-
5
- // src/env.ts
6
- var DEV = (() => {
7
- try {
8
- const meta = import.meta;
9
- return Boolean(meta.env?.DEV);
10
- } catch {
11
- return false;
12
- }
13
- })();
14
-
15
- // src/adapter.ts
16
- function isSafeNavigationTarget(to) {
17
- if (typeof to !== "string") return false;
18
- const value = to.replace(/[\u0000-\u0020]/g, "");
19
- if (value === "") return false;
20
- if (value.startsWith("//")) return false;
21
- if (value.startsWith("/") || value.startsWith("?") || value.startsWith("#")) return true;
22
- return /^https?:\/\//i.test(value);
23
- }
24
- async function defaultFetch(path, init) {
25
- const response = await fetch(path, {
26
- method: init.method,
27
- headers: { "content-type": "application/json", accept: "application/json" },
28
- credentials: "include",
29
- body: init.body === void 0 ? void 0 : JSON.stringify(init.body),
30
- signal: init.signal
31
- });
32
- const text = await response.text();
33
- let json = null;
34
- if (text) {
35
- try {
36
- json = JSON.parse(text);
37
- } catch {
38
- json = null;
39
- }
40
- }
41
- if (!response.ok) {
42
- const message = json && typeof json === "object" && typeof json.message === "string" ? json.message : `\uC694\uCCAD\uC774 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4 (${response.status})`;
43
- throw new Error(message);
44
- }
45
- return json;
46
- }
47
- var defaultAdapter = {
48
- navigate: (to) => {
49
- if (!isSafeNavigationTarget(to)) {
50
- console.error(`[sdui] \uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 navigate \uB300\uC0C1: ${String(to)}`);
51
- return;
52
- }
53
- window.location.assign(to);
54
- },
55
- fetch: defaultFetch,
56
- rawFetch: (path) => fetch(path, { credentials: "include" }),
57
- // 항등함수. 계약을 문서화하고, 컴포넌트가 `useSduiAssetResolver()`를 조건 없이
58
- // 부를 수 있게 하는 것이 전부다. **이 함수 참조 자체가 표식**이기도 하다 —
59
- // `assetResolverOf`가 이것과 같은지 보고 "주입 없음"을 판정한다.
60
- resolveAsset: (url) => url,
61
- invalidate: (prefix) => {
62
- if (DEV) console.warn(`[sdui] invalidate \uC561\uC158\uC774 \uC5B4\uB311\uD130 \uC5C6\uC774 \uD638\uCD9C\uB428 (prefix: ${prefix})`);
63
- },
64
- refresh: () => {
65
- if (DEV) console.warn("[sdui] refresh \uC561\uC158\uC774 \uC5B4\uB311\uD130 \uC5C6\uC774 \uD638\uCD9C\uB428");
66
- }
67
- };
68
- function resolveAdapter(adapter) {
69
- const merged = { ...defaultAdapter };
70
- if (adapter) {
71
- for (const [key, value] of Object.entries(adapter)) {
72
- if (value !== void 0) merged[key] = value;
73
- }
74
- }
75
- return merged;
76
- }
77
- var SduiAdapterContext = createContext(null);
78
- function useSduiAdapter() {
79
- return useContext(SduiAdapterContext);
80
- }
81
- function assetResolverOf(adapter) {
82
- const resolve = adapter?.resolveAsset;
83
- if (!resolve || resolve === defaultAdapter.resolveAsset) return null;
84
- return resolve;
85
- }
86
- function useSduiAssetResolver() {
87
- const adapter = useContext(SduiAdapterContext);
88
- return adapter?.resolveAsset ?? defaultAdapter.resolveAsset;
89
- }
90
- function useSduiSandbox() {
91
- return useContext(SduiAdapterContext)?.sandbox === true;
92
- }
93
- function hostFetchOf(adapter) {
94
- const hostFetch = adapter?.fetch;
95
- if (!hostFetch || hostFetch === defaultAdapter.fetch) return null;
96
- return hostFetch;
97
- }
98
-
99
- export { DEV, SduiAdapterContext, assetResolverOf, defaultAdapter, hostFetchOf, isSafeNavigationTarget, resolveAdapter, useSduiAdapter, useSduiAssetResolver, useSduiSandbox };