@wipco/sdui 0.29.0 → 0.31.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,5 +1,5 @@
1
1
  import { includesLooseSearch } from './chunk-BD75U73S.js';
2
- import { useSduiAdapter, hostFetchOf } from './chunk-DVOPZFQS.js';
2
+ import { useSduiAdapter, hostFetchOf } from './chunk-DKPNFEVU.js';
3
3
  import { Icon, Button, Input, Spinner } from './chunk-WIDDHR7L.js';
4
4
  import { boxed } from './chunk-SAN4IX6N.js';
5
5
  import { useId, useRef, useMemo, useState, useEffect } from 'react';
@@ -96,5 +96,10 @@ function hostFetchOf(adapter) {
96
96
  if (!hostFetch || hostFetch === defaultAdapter.fetch) return null;
97
97
  return hostFetch;
98
98
  }
99
+ function hostRawFetchOf(adapter) {
100
+ const hostRawFetch = adapter?.rawFetch;
101
+ if (!hostRawFetch || hostRawFetch === defaultAdapter.rawFetch) return null;
102
+ return hostRawFetch;
103
+ }
99
104
 
100
- export { DEV, SduiAdapterContext, assetResolverOf, defaultAdapter, hostFetchOf, isSafeNavigationTarget, resolveAdapter, useSduiAdapter, useSduiAssetResolver, useSduiSandbox };
105
+ export { DEV, SduiAdapterContext, assetResolverOf, defaultAdapter, hostFetchOf, hostRawFetchOf, isSafeNavigationTarget, resolveAdapter, useSduiAdapter, useSduiAssetResolver, useSduiSandbox };
@@ -0,0 +1,400 @@
1
+ import { ImageLightbox } from './chunk-XPBZMECW.js';
2
+ import { useSduiSandbox, useSduiAdapter, hostRawFetchOf, defaultAdapter, useSduiAssetResolver } from './chunk-DKPNFEVU.js';
3
+ import { Spinner, Icon, IconButton } from './chunk-WIDDHR7L.js';
4
+ import { boxed } from './chunk-SAN4IX6N.js';
5
+ import { useState, useRef, useEffect, useCallback } from 'react';
6
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
+
8
+ var THUMB_SIZE = 72;
9
+ function matchesAccept(file, accept) {
10
+ const rules = accept.split(",").map((rule) => rule.trim().toLowerCase()).filter(Boolean);
11
+ if (rules.length === 0) return true;
12
+ const type = file.type.toLowerCase();
13
+ const name = file.name.toLowerCase();
14
+ return rules.some((rule) => {
15
+ if (rule.startsWith(".")) return name.endsWith(rule);
16
+ if (rule.endsWith("/*")) return type.startsWith(rule.slice(0, -1));
17
+ return type === rule;
18
+ });
19
+ }
20
+ function localId() {
21
+ return `local-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
22
+ }
23
+ function unwrapUploaded(payload) {
24
+ if (typeof payload !== "object" || payload === null || Array.isArray(payload)) return null;
25
+ const body = payload;
26
+ if ("id" in body || "url" in body) return body;
27
+ const objectKeys = Object.keys(body).filter(
28
+ (key) => typeof body[key] === "object" && body[key] !== null && !Array.isArray(body[key])
29
+ );
30
+ if (objectKeys.length !== 1) return body;
31
+ return body[objectKeys[0]];
32
+ }
33
+ function FileDropzoneImpl({
34
+ upload,
35
+ fileField = "file",
36
+ localFallbackOn503 = false,
37
+ endpoint,
38
+ value,
39
+ onChange,
40
+ onBusyChange,
41
+ maxCount,
42
+ accept = "image/*",
43
+ disabled = false,
44
+ hint,
45
+ deleteEndpoint,
46
+ onRemove
47
+ }) {
48
+ const sandbox = useSduiSandbox();
49
+ const adapter = useSduiAdapter();
50
+ const hostRaw = hostRawFetchOf(adapter);
51
+ const rawFetch = hostRaw ?? defaultAdapter.rawFetch;
52
+ const canUpload = hostRaw !== null || upload !== void 0;
53
+ const resolveAsset = useSduiAssetResolver();
54
+ const [internalItems, setInternalItems] = useState(value ?? []);
55
+ const items = value ?? internalItems;
56
+ const itemsRef = useRef(items);
57
+ itemsRef.current = items;
58
+ const [busy, setBusy] = useState(false);
59
+ const [progress, setProgress] = useState(null);
60
+ const [warnings, setWarnings] = useState([]);
61
+ const [localMode, setLocalMode] = useState(!endpoint);
62
+ const [dragOver, setDragOver] = useState(false);
63
+ const [hovering, setHovering] = useState(false);
64
+ const [hoverItemId, setHoverItemId] = useState(null);
65
+ const inputRef = useRef(null);
66
+ const dragDepthRef = useRef(0);
67
+ const createdUrlsRef = useRef(/* @__PURE__ */ new Set());
68
+ useEffect(() => {
69
+ const created = createdUrlsRef.current;
70
+ return () => created.forEach((url) => URL.revokeObjectURL(url));
71
+ }, []);
72
+ const atCapacity = maxCount !== void 0 && items.length >= maxCount;
73
+ const canAdd = !disabled && !busy && !atCapacity;
74
+ const commit = useCallback(
75
+ (next) => {
76
+ itemsRef.current = next;
77
+ setInternalItems(next);
78
+ onChange?.(next);
79
+ },
80
+ [onChange]
81
+ );
82
+ const runQueue = useCallback(
83
+ async (queue, baseWarnings) => {
84
+ setBusy(true);
85
+ onBusyChange?.(true);
86
+ const failures = [];
87
+ let local = localMode || !endpoint || sandbox || !canUpload;
88
+ try {
89
+ for (let index = 0; index < queue.length; index += 1) {
90
+ const file = queue[index];
91
+ setProgress({ current: index + 1, total: queue.length });
92
+ if (!local && upload) {
93
+ try {
94
+ const item = await upload(file);
95
+ if (item && typeof item.id === "string" && typeof item.url === "string") {
96
+ commit([...itemsRef.current, { name: file.name, ...item }]);
97
+ } else {
98
+ failures.push(`${file.name} (\uC5C5\uB85C\uB354\uAC00 \uD56D\uBAA9\uC744 \uC8FC\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4)`);
99
+ }
100
+ } catch (error) {
101
+ failures.push(`${file.name} (${error instanceof Error ? error.message : "\uC5C5\uB85C\uB4DC \uC2E4\uD328"})`);
102
+ }
103
+ continue;
104
+ }
105
+ if (!local && endpoint) {
106
+ try {
107
+ const formData = new FormData();
108
+ formData.append(fileField, file, file.name);
109
+ const res = await rawFetch(endpoint, { method: "POST", body: formData });
110
+ if (res.status === 503 && localFallbackOn503) {
111
+ local = true;
112
+ setLocalMode(true);
113
+ } else if (!res.ok) {
114
+ failures.push(`${file.name} (${res.status})`);
115
+ continue;
116
+ } else {
117
+ const json = await res.json().catch(() => null);
118
+ const body = unwrapUploaded(json);
119
+ const id = body?.id;
120
+ const url2 = body?.url;
121
+ if ((typeof id === "string" || typeof id === "number") && typeof url2 === "string") {
122
+ commit([...itemsRef.current, { id: String(id), url: url2, name: file.name }]);
123
+ } else {
124
+ failures.push(`${file.name} (\uC751\uB2F5 \uD615\uC2DD \uC624\uB958)`);
125
+ }
126
+ continue;
127
+ }
128
+ } catch {
129
+ failures.push(`${file.name} (\uB124\uD2B8\uC6CC\uD06C \uC624\uB958)`);
130
+ continue;
131
+ }
132
+ }
133
+ const url = URL.createObjectURL(file);
134
+ createdUrlsRef.current.add(url);
135
+ commit([...itemsRef.current, { id: localId(), url, name: file.name }]);
136
+ }
137
+ } finally {
138
+ setProgress(null);
139
+ setBusy(false);
140
+ onBusyChange?.(false);
141
+ }
142
+ if (failures.length > 0) {
143
+ setWarnings([...baseWarnings, `\uC5C5\uB85C\uB4DC \uC2E4\uD328: ${failures.join(", ")}`]);
144
+ }
145
+ },
146
+ [commit, endpoint, localMode, onBusyChange, sandbox, upload, rawFetch, canUpload, fileField, localFallbackOn503]
147
+ );
148
+ const handleFiles = useCallback(
149
+ (fileList) => {
150
+ const files = Array.from(fileList);
151
+ if (files.length === 0) return;
152
+ const nextWarnings = [];
153
+ const matched = files.filter((file) => matchesAccept(file, accept));
154
+ const rejectedCount = files.length - matched.length;
155
+ if (rejectedCount > 0) {
156
+ nextWarnings.push(
157
+ `\uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 \uD615\uC2DD\uC774\uB77C ${rejectedCount}\uAC1C \uD30C\uC77C\uC744 \uC81C\uC678\uD588\uC2B5\uB2C8\uB2E4 (\uD5C8\uC6A9: ${accept})`
158
+ );
159
+ }
160
+ let queue = matched;
161
+ if (maxCount !== void 0) {
162
+ const remaining = Math.max(0, maxCount - itemsRef.current.length);
163
+ if (queue.length > remaining) {
164
+ nextWarnings.push(
165
+ `\uCD5C\uB300 ${maxCount}\uAC1C\uAE4C\uC9C0 \uCCA8\uBD80\uD560 \uC218 \uC788\uC5B4 ${queue.length - remaining}\uAC1C\uB97C \uC81C\uC678\uD588\uC2B5\uB2C8\uB2E4`
166
+ );
167
+ queue = queue.slice(0, remaining);
168
+ }
169
+ }
170
+ setWarnings(nextWarnings);
171
+ if (queue.length === 0) return;
172
+ void runQueue(queue, nextWarnings);
173
+ },
174
+ [accept, maxCount, runQueue]
175
+ );
176
+ const removeItem = useCallback(
177
+ (item) => {
178
+ const forget = () => {
179
+ commit(itemsRef.current.filter((current) => current.id !== item.id));
180
+ if (createdUrlsRef.current.has(item.url)) {
181
+ URL.revokeObjectURL(item.url);
182
+ createdUrlsRef.current.delete(item.url);
183
+ }
184
+ };
185
+ if (deleteEndpoint && !item.id.startsWith("local-")) {
186
+ void (async () => {
187
+ try {
188
+ const res = await rawFetch(`${deleteEndpoint}/${item.id}`, { method: "DELETE" });
189
+ if (!res.ok && res.status !== 404) {
190
+ setWarnings([`${item.name ?? "\uCCA8\uBD80"} \uC0AD\uC81C\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4 (${res.status})`]);
191
+ return;
192
+ }
193
+ } catch {
194
+ setWarnings([`${item.name ?? "\uCCA8\uBD80"} \uC0AD\uC81C\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4 \u2014 \uC5F0\uACB0\uC744 \uD655\uC778\uD558\uC138\uC694`]);
195
+ return;
196
+ }
197
+ forget();
198
+ onRemove?.();
199
+ })();
200
+ return;
201
+ }
202
+ forget();
203
+ },
204
+ [commit, deleteEndpoint, onRemove]
205
+ );
206
+ const openPicker = () => {
207
+ if (canAdd) inputRef.current?.click();
208
+ };
209
+ const showLocalCaption = localMode || items.some((item) => item.id.startsWith("local-"));
210
+ const dropTinted = canAdd && (dragOver || hovering);
211
+ return /* @__PURE__ */ jsxs("div", { className: "wp-col", style: { gap: "var(--wp-space-2)" }, children: [
212
+ /* @__PURE__ */ jsx(
213
+ "input",
214
+ {
215
+ ref: inputRef,
216
+ type: "file",
217
+ accept,
218
+ multiple: maxCount !== 1,
219
+ hidden: true,
220
+ disabled: !canAdd,
221
+ onChange: (event) => {
222
+ const selected = event.currentTarget.files;
223
+ if (selected && selected.length > 0) handleFiles(selected);
224
+ event.currentTarget.value = "";
225
+ }
226
+ }
227
+ ),
228
+ /* @__PURE__ */ jsx(
229
+ "div",
230
+ {
231
+ role: "button",
232
+ tabIndex: canAdd ? 0 : -1,
233
+ "aria-disabled": !canAdd,
234
+ "aria-label": "\uC774\uBBF8\uC9C0 \uCCA8\uBD80",
235
+ onClick: openPicker,
236
+ onKeyDown: (event) => {
237
+ if (canAdd && (event.key === "Enter" || event.key === " ")) {
238
+ event.preventDefault();
239
+ openPicker();
240
+ }
241
+ },
242
+ onMouseEnter: () => setHovering(true),
243
+ onMouseLeave: () => setHovering(false),
244
+ onDragEnter: (event) => {
245
+ event.preventDefault();
246
+ if (!canAdd) return;
247
+ dragDepthRef.current += 1;
248
+ setDragOver(true);
249
+ },
250
+ onDragOver: (event) => event.preventDefault(),
251
+ onDragLeave: () => {
252
+ dragDepthRef.current = Math.max(0, dragDepthRef.current - 1);
253
+ if (dragDepthRef.current === 0) setDragOver(false);
254
+ },
255
+ onDrop: (event) => {
256
+ event.preventDefault();
257
+ dragDepthRef.current = 0;
258
+ setDragOver(false);
259
+ if (!canAdd) return;
260
+ handleFiles(event.dataTransfer.files);
261
+ },
262
+ style: {
263
+ display: "flex",
264
+ flexDirection: "column",
265
+ alignItems: "center",
266
+ justifyContent: "center",
267
+ gap: "var(--wp-space-2)",
268
+ padding: "var(--wp-space-6)",
269
+ border: `var(--wp-border-w) dashed ${dragOver ? "var(--wp-border-brand)" : "var(--wp-border-subtle)"}`,
270
+ borderRadius: "var(--wp-radius-md)",
271
+ background: dropTinted ? "var(--wp-surface-brand-tint)" : "var(--wp-surface-card)",
272
+ cursor: canAdd ? "pointer" : "not-allowed",
273
+ opacity: disabled ? 0.55 : 1,
274
+ transition: "var(--wp-transition-surface)",
275
+ textAlign: "center"
276
+ },
277
+ title: disabled ? "\uBE44\uD65C\uC131\uD654\uB41C \uC0C1\uD0DC\uC785\uB2C8\uB2E4" : atCapacity ? `\uCD5C\uB300 ${maxCount}\uAC1C\uAE4C\uC9C0 \uCCA8\uBD80\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4` : void 0,
278
+ children: busy && progress ? /* @__PURE__ */ jsx(Spinner, { label: `(${progress.current}/${progress.total}) \uC5C5\uB85C\uB4DC \uC911` }) : /* @__PURE__ */ jsxs(Fragment, { children: [
279
+ /* @__PURE__ */ jsx(
280
+ Icon,
281
+ {
282
+ name: "cloud-upload",
283
+ size: 24,
284
+ style: { color: dropTinted ? "var(--wp-text-brand)" : "var(--wp-text-muted)" }
285
+ }
286
+ ),
287
+ /* @__PURE__ */ jsx("span", { className: "wp-body-sm", style: { color: "var(--wp-text-default)" }, children: atCapacity ? `\uCD5C\uB300 ${maxCount}\uAC1C\uAE4C\uC9C0 \uCCA8\uBD80\uD588\uC2B5\uB2C8\uB2E4` : "\uD074\uB9AD\uD558\uAC70\uB098 \uC774\uBBF8\uC9C0\uB97C \uB04C\uC5B4\uB2E4 \uB193\uC73C\uC138\uC694" }),
288
+ hint ? /* @__PURE__ */ jsx("span", { className: "wp-caption", children: hint }) : null
289
+ ] })
290
+ }
291
+ ),
292
+ warnings.map((warning) => /* @__PURE__ */ jsxs(
293
+ "span",
294
+ {
295
+ className: "wp-caption",
296
+ style: {
297
+ color: "var(--wp-text-warning)",
298
+ display: "inline-flex",
299
+ alignItems: "center",
300
+ gap: "var(--wp-space-1)"
301
+ },
302
+ children: [
303
+ /* @__PURE__ */ jsx(Icon, { name: "triangle-alert", size: 14 }),
304
+ warning
305
+ ]
306
+ },
307
+ warning
308
+ )),
309
+ showLocalCaption ? /* @__PURE__ */ jsxs(
310
+ "span",
311
+ {
312
+ className: "wp-caption",
313
+ style: { display: "inline-flex", alignItems: "center", gap: "var(--wp-space-1)" },
314
+ children: [
315
+ /* @__PURE__ */ jsx(Icon, { name: "unplug", size: 14 }),
316
+ "\uBC31\uC5D4\uB4DC \uBBF8\uC5F0\uB3D9 \u2014 \uC784\uC2DC \uBBF8\uB9AC\uBCF4\uAE30"
317
+ ]
318
+ }
319
+ ) : null,
320
+ items.length > 0 ? /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: "var(--wp-space-2)" }, children: items.map((item) => {
321
+ const thumbSrc = resolveAsset(item.url, {
322
+ component: "FileDropzone",
323
+ prop: "src",
324
+ kind: "attribute"
325
+ });
326
+ return /* @__PURE__ */ jsxs(
327
+ "div",
328
+ {
329
+ style: { position: "relative", width: THUMB_SIZE, height: THUMB_SIZE },
330
+ onMouseEnter: () => setHoverItemId(item.id),
331
+ onMouseLeave: () => setHoverItemId((current) => current === item.id ? null : current),
332
+ children: [
333
+ thumbSrc === null ? /* @__PURE__ */ jsx(
334
+ "span",
335
+ {
336
+ "aria-label": item.name ?? "\uCCA8\uBD80 \uBBF8\uB9AC\uBCF4\uAE30 \uC5C6\uC74C",
337
+ role: "img",
338
+ style: {
339
+ display: "block",
340
+ width: THUMB_SIZE,
341
+ height: THUMB_SIZE,
342
+ borderRadius: "var(--wp-radius-md)",
343
+ border: "var(--wp-border-w) solid var(--wp-border-subtle)",
344
+ background: "var(--wp-surface-sunken)"
345
+ }
346
+ }
347
+ ) : /* @__PURE__ */ jsx(
348
+ ImageLightbox,
349
+ {
350
+ src: thumbSrc,
351
+ alt: item.name,
352
+ thumbStyle: {
353
+ width: THUMB_SIZE,
354
+ height: THUMB_SIZE,
355
+ objectFit: "cover",
356
+ display: "block",
357
+ borderRadius: "var(--wp-radius-md)",
358
+ border: "var(--wp-border-w) solid var(--wp-border-subtle)"
359
+ }
360
+ }
361
+ ),
362
+ /* @__PURE__ */ jsx(
363
+ "span",
364
+ {
365
+ style: {
366
+ position: "absolute",
367
+ top: "var(--wp-space-1)",
368
+ right: "var(--wp-space-1)",
369
+ background: "var(--wp-surface-card)",
370
+ borderRadius: "var(--wp-radius-sm)",
371
+ boxShadow: "var(--wp-shadow-sm)",
372
+ opacity: hoverItemId === item.id ? 1 : 0,
373
+ transition: "opacity var(--wp-duration-fast) var(--wp-ease-out)"
374
+ },
375
+ children: /* @__PURE__ */ jsx(
376
+ IconButton,
377
+ {
378
+ icon: "trash-2",
379
+ label: `\uC0AD\uC81C${item.name ? ` \u2014 ${item.name}` : ""}`,
380
+ variant: "danger",
381
+ size: "xs",
382
+ onClick: (event) => {
383
+ event.stopPropagation();
384
+ removeItem(item);
385
+ },
386
+ onFocus: () => setHoverItemId(item.id)
387
+ }
388
+ )
389
+ }
390
+ )
391
+ ]
392
+ },
393
+ item.id
394
+ );
395
+ }) }) : null
396
+ ] });
397
+ }
398
+ var FileDropzone = boxed(FileDropzoneImpl, "FileDropzone");
399
+
400
+ export { FileDropzone, unwrapUploaded };
@@ -1,4 +1,4 @@
1
- import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DVOPZFQS.js';
1
+ import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DKPNFEVU.js';
2
2
  import { Icon, Badge, Checkbox, Select, Button } from './chunk-WIDDHR7L.js';
3
3
  import { boxed } from './chunk-SAN4IX6N.js';
4
4
  import { useRef, useEffect, useMemo, useState } from 'react';
@@ -1,5 +1,5 @@
1
1
  import { ImageLightbox } from './chunk-XPBZMECW.js';
2
- import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DVOPZFQS.js';
2
+ import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DKPNFEVU.js';
3
3
  import { Badge, Button, IconButton, Icon } from './chunk-WIDDHR7L.js';
4
4
  import { boxed } from './chunk-SAN4IX6N.js';
5
5
  import { useState, useCallback, useEffect, useMemo, useRef } from 'react';
@@ -1,6 +1,6 @@
1
1
  import { TagMultiSelect } from './chunk-QDEZXA34.js';
2
- import { ApprovalLineEditor, stepTypeIcon, stepLabel } from './chunk-SAPJVLZM.js';
3
- import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DVOPZFQS.js';
2
+ import { ApprovalLineEditor, stepTypeIcon, stepLabel } from './chunk-S4LOH2TU.js';
3
+ import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DKPNFEVU.js';
4
4
  import { Card, Icon, Input, IconButton, Field, Button, Badge } from './chunk-WIDDHR7L.js';
5
5
  import { boxed } from './chunk-SAN4IX6N.js';
6
6
  import React, { useState, useEffect } from 'react';
@@ -0,0 +1,67 @@
1
+ import { Spinner, EmptyState } from './chunk-WIDDHR7L.js';
2
+ import { useState, useEffect } from 'react';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+
5
+ function useOptionalModule(load) {
6
+ const [state, setState] = useState({ status: "loading" });
7
+ useEffect(() => {
8
+ let alive = true;
9
+ load().then(
10
+ (module) => {
11
+ if (alive) setState({ status: "ready", module });
12
+ },
13
+ (error) => {
14
+ if (!alive) return;
15
+ console.warn("[sdui] \uC120\uD0DD \uC758\uC874\uC131 \uB85C\uB4DC \uC2E4\uD328 \u2014 \uD50C\uB808\uC774\uC2A4\uD640\uB354\uB85C \uB300\uCCB4\uD569\uB2C8\uB2E4", error);
16
+ setState({ status: "missing" });
17
+ }
18
+ );
19
+ return () => {
20
+ alive = false;
21
+ };
22
+ }, [load]);
23
+ return state;
24
+ }
25
+ var optionalModuleCache = /* @__PURE__ */ new WeakMap();
26
+ function loadOptionalModule(load, packageName) {
27
+ const cached = optionalModuleCache.get(load);
28
+ if (cached) return cached;
29
+ const pending = load().catch((error) => {
30
+ console.warn(`[sdui] \uC120\uD0DD \uC758\uC874\uC131 '${packageName}' \uB85C\uB4DC \uC2E4\uD328`, error);
31
+ optionalModuleCache.delete(load);
32
+ throw new Error(`${packageName} \uD328\uD0A4\uC9C0\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4 \u2014 \uD504\uB85C\uC81D\uD2B8\uC5D0 \uC124\uCE58\uD574 \uC8FC\uC138\uC694`);
33
+ });
34
+ optionalModuleCache.set(load, pending);
35
+ return pending;
36
+ }
37
+ function DependencyPlaceholder({
38
+ packageName,
39
+ purpose,
40
+ height,
41
+ loading = false
42
+ }) {
43
+ return /* @__PURE__ */ jsx(
44
+ "div",
45
+ {
46
+ style: {
47
+ minHeight: height,
48
+ display: "flex",
49
+ alignItems: "center",
50
+ justifyContent: "center",
51
+ border: "1px solid var(--wp-border-subtle)",
52
+ borderRadius: "var(--wp-radius-md)",
53
+ background: "var(--wp-surface-sunken)"
54
+ },
55
+ children: loading ? /* @__PURE__ */ jsx(Spinner, { label: `${purpose} \uC900\uBE44 \uC911` }) : /* @__PURE__ */ jsxs(EmptyState, { tight: true, icon: "box", title: `${purpose}\uB97C \uADF8\uB9AC\uB824\uBA74 ${packageName} \uD328\uD0A4\uC9C0\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4`, children: [
56
+ "\uC120\uD0DD \uC758\uC874\uC131\uC774\uB77C \uAE30\uBCF8 \uC124\uCE58\uC5D0\uB294 \uD3EC\uD568\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 ",
57
+ /* @__PURE__ */ jsxs("code", { children: [
58
+ "pnpm add ",
59
+ packageName
60
+ ] }),
61
+ " \uD6C4 \uB2E4\uC2DC \uC5F4\uC5B4 \uC8FC\uC138\uC694."
62
+ ] })
63
+ }
64
+ );
65
+ }
66
+
67
+ export { DependencyPlaceholder, loadOptionalModule, useOptionalModule };
@@ -1,5 +1,5 @@
1
1
  import { includesLooseSearch } from './chunk-BD75U73S.js';
2
- import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DVOPZFQS.js';
2
+ import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DKPNFEVU.js';
3
3
  import { Icon, Badge, IconButton, Field, Select, Input, Tag, Spinner, Button } from './chunk-WIDDHR7L.js';
4
4
  import { boxed } from './chunk-SAN4IX6N.js';
5
5
  import React, { useState, useId, useRef, useEffect } from 'react';
@@ -1,5 +1,5 @@
1
1
  import { useDismiss, usePopoverAnchor, usePortalTo, HoistedStyle, AnchoredPopoverStyles, anchorStyle, focusStaysInside } from './chunk-EFMKWPZ3.js';
2
- import { Icon, Spinner, EmptyState } from './chunk-WIDDHR7L.js';
2
+ import { Icon } from './chunk-WIDDHR7L.js';
3
3
  import { boxed } from './chunk-SAN4IX6N.js';
4
4
  import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
5
5
  import { jsxs, jsx } from 'react/jsx-runtime';
@@ -194,66 +194,5 @@ var TP_CSS = `
194
194
  .wp-tp-opt--on{background:var(--wp-action-primary-bg);color:var(--wp-action-primary-fg);font-weight:500}
195
195
  `;
196
196
  var TimePicker = boxed(TimePickerImpl, "TimePicker");
197
- function useOptionalModule(load) {
198
- const [state, setState] = useState({ status: "loading" });
199
- useEffect(() => {
200
- let alive = true;
201
- load().then(
202
- (module) => {
203
- if (alive) setState({ status: "ready", module });
204
- },
205
- (error) => {
206
- if (!alive) return;
207
- console.warn("[sdui] \uC120\uD0DD \uC758\uC874\uC131 \uB85C\uB4DC \uC2E4\uD328 \u2014 \uD50C\uB808\uC774\uC2A4\uD640\uB354\uB85C \uB300\uCCB4\uD569\uB2C8\uB2E4", error);
208
- setState({ status: "missing" });
209
- }
210
- );
211
- return () => {
212
- alive = false;
213
- };
214
- }, [load]);
215
- return state;
216
- }
217
- var optionalModuleCache = /* @__PURE__ */ new WeakMap();
218
- function loadOptionalModule(load, packageName) {
219
- const cached = optionalModuleCache.get(load);
220
- if (cached) return cached;
221
- const pending = load().catch((error) => {
222
- console.warn(`[sdui] \uC120\uD0DD \uC758\uC874\uC131 '${packageName}' \uB85C\uB4DC \uC2E4\uD328`, error);
223
- optionalModuleCache.delete(load);
224
- throw new Error(`${packageName} \uD328\uD0A4\uC9C0\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4 \u2014 \uD504\uB85C\uC81D\uD2B8\uC5D0 \uC124\uCE58\uD574 \uC8FC\uC138\uC694`);
225
- });
226
- optionalModuleCache.set(load, pending);
227
- return pending;
228
- }
229
- function DependencyPlaceholder({
230
- packageName,
231
- purpose,
232
- height,
233
- loading = false
234
- }) {
235
- return /* @__PURE__ */ jsx(
236
- "div",
237
- {
238
- style: {
239
- minHeight: height,
240
- display: "flex",
241
- alignItems: "center",
242
- justifyContent: "center",
243
- border: "1px solid var(--wp-border-subtle)",
244
- borderRadius: "var(--wp-radius-md)",
245
- background: "var(--wp-surface-sunken)"
246
- },
247
- children: loading ? /* @__PURE__ */ jsx(Spinner, { label: `${purpose} \uC900\uBE44 \uC911` }) : /* @__PURE__ */ jsxs(EmptyState, { tight: true, icon: "box", title: `${purpose}\uB97C \uADF8\uB9AC\uB824\uBA74 ${packageName} \uD328\uD0A4\uC9C0\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4`, children: [
248
- "\uC120\uD0DD \uC758\uC874\uC131\uC774\uB77C \uAE30\uBCF8 \uC124\uCE58\uC5D0\uB294 \uD3EC\uD568\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 ",
249
- /* @__PURE__ */ jsxs("code", { children: [
250
- "pnpm add ",
251
- packageName
252
- ] }),
253
- " \uD6C4 \uB2E4\uC2DC \uC5F4\uC5B4 \uC8FC\uC138\uC694."
254
- ] })
255
- }
256
- );
257
- }
258
197
 
259
- export { DependencyPlaceholder, TimePicker, loadOptionalModule, useOptionalModule };
198
+ export { TimePicker };
@@ -1,5 +1,5 @@
1
1
  import { useSduiDispatch, useSduiCan, SduiDialog } from './chunk-QU7YVMFA.js';
2
- import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DVOPZFQS.js';
2
+ import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DKPNFEVU.js';
3
3
  import { Card, Input, Button, Icon, Badge, IconButton } from './chunk-WIDDHR7L.js';
4
4
  import { boxed } from './chunk-SAN4IX6N.js';
5
5
  import React, { useState, useEffect, useCallback, useMemo } from 'react';
@@ -1,4 +1,4 @@
1
- import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DVOPZFQS.js';
1
+ import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DKPNFEVU.js';
2
2
  import { Alert, Badge, Spinner, Button, Input, Checkbox } from './chunk-WIDDHR7L.js';
3
3
  import { boxed } from './chunk-SAN4IX6N.js';
4
4
  import { useMemo, useState, useRef, useCallback, useEffect } from 'react';
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
- import { TimePicker } from './chunk-WXOU7QTN.js';
2
- export { TimePicker, loadOptionalModule } from './chunk-WXOU7QTN.js';
1
+ import { TimePicker } from './chunk-SNRKPEWT.js';
2
+ export { TimePicker } from './chunk-SNRKPEWT.js';
3
+ export { loadOptionalModule } from './chunk-RRMKCZYQ.js';
3
4
  import { useSduiDispatch, SduiDialog, useSduiCan, SduiDispatchContext, SduiPermissionProvider } from './chunk-QU7YVMFA.js';
4
5
  export { SduiDialog, SduiPermissionProvider, useSduiCan, useSduiDispatch } from './chunk-QU7YVMFA.js';
5
6
  import { ImageViewer } from './chunk-THEP3VWY.js';
@@ -9,8 +10,8 @@ export { SearchToolbar, cascadingValuePaths, cleanSearchValues, includesLooseSea
9
10
  import { HoistedStyle, DateRangePicker, DatePicker } from './chunk-EFMKWPZ3.js';
10
11
  export { DatePicker, DateRangePicker } from './chunk-EFMKWPZ3.js';
11
12
  export { OverlayContainerContext, OverlayLayer, getOverlayRoot, useOverlayLayer } from './chunk-XQPTSEJC.js';
12
- import { DEV, isSafeNavigationTarget, useSduiAdapter, hostFetchOf, useSduiSandbox, resolveAdapter, assetResolverOf, SduiAdapterContext } from './chunk-DVOPZFQS.js';
13
- export { defaultAdapter, useSduiAssetResolver, useSduiSandbox } from './chunk-DVOPZFQS.js';
13
+ import { DEV, isSafeNavigationTarget, useSduiAdapter, hostFetchOf, useSduiSandbox, resolveAdapter, assetResolverOf, SduiAdapterContext } from './chunk-DKPNFEVU.js';
14
+ export { defaultAdapter, useSduiAssetResolver, useSduiSandbox } from './chunk-DKPNFEVU.js';
14
15
  import { EmptyState, Button, Checkbox, SegmentedControl, Spinner, Select, Pagination, Icon, Card, Badge, ProgressBar, Tag, Toast, Textarea, Tabs, Table, Switch, StatCard, Input, IconButton, Field, Alert, warnOnce } from './chunk-WIDDHR7L.js';
15
16
  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';
16
17
  import { boxed, mergeStyle, containerLayoutStyle, joinClass, columnsClass, typographyStyle, LAYOUT_CSS, childLayoutFrom, CHILD_LAYOUT_PROPS } from './chunk-SAN4IX6N.js';
package/dist/meta.d.ts CHANGED
@@ -430,6 +430,8 @@ declare const ApprovalOverridesEditorMeta: SduiComponentMeta;
430
430
  declare const OrgChartBoardMeta: SduiComponentMeta;
431
431
  declare const PermissionMatrixEditorMeta: SduiComponentMeta;
432
432
  declare const RoleMemberPickerMeta: SduiComponentMeta;
433
+ declare const FileDropzoneMeta: SduiComponentMeta;
434
+ declare const ExcelUploadCardMeta: SduiComponentMeta;
433
435
  declare const AutocompleteFieldMeta: SduiComponentMeta;
434
436
  declare const InlineEditMeta: SduiComponentMeta;
435
437
  declare const JsonFieldMeta: SduiComponentMeta;
@@ -455,7 +457,7 @@ declare const StripMeta: SduiComponentMeta;
455
457
  /**
456
458
  * 위젯 어휘의 메타데이터.
457
459
  *
458
- * 키 타입이 `SduiWidgetComponentName | 'AutocompleteField'`인 이유:
460
+ * 키 타입이 `SduiWidgetComponentName | 'AutocompleteField' | 'FileDropzone' | 'ExcelUploadCard'`인 이유:
459
461
  * 앞의 20종은 `widgetComponents`와 키가 어긋나면 컴파일 에러가 나야 하고(core·rich와 같다),
460
462
  * `AutocompleteField`는 그 묶음에 **일부러 빠져 있는** 이름이라 union에 직접 적어야 한다.
461
463
  * 표시 계층만 라이브러리에 있고 조회는 앱 어댑터가 얹는 구조인데(widgets/index.ts 참고),
@@ -466,6 +468,8 @@ declare const widgetComponentMeta: {
466
468
  ApprovalLineEditor: SduiComponentMeta;
467
469
  ApprovalOverridesEditor: SduiComponentMeta;
468
470
  ApprovalStepper: SduiComponentMeta;
471
+ ExcelUploadCard: SduiComponentMeta;
472
+ FileDropzone: SduiComponentMeta;
469
473
  PermissionMatrixEditor: SduiComponentMeta;
470
474
  RoleMemberPicker: SduiComponentMeta;
471
475
  OrgChartBoard: SduiComponentMeta;
@@ -602,4 +606,4 @@ declare function paletteGroups(catalog?: Record<string, SduiComponentMeta>): Arr
602
606
  items: SduiComponentMeta[];
603
607
  }>;
604
608
 
605
- export { ActionsMeta, AlertMeta, ApprovalLineEditorMeta, ApprovalOverridesEditorMeta, ApprovalStepperMeta, 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, OrgChartBoardMeta, OrgChartMeta, PageHeadMeta, PaginationMeta, PeriodFilterMeta, PermissionMatrixEditorMeta, PrintSheetMeta, ProgressBarMeta, RadioMeta, RankBoardMeta, RecordSheetMeta, RemoteSearchResultsMeta, RoleMemberPickerMeta, 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 };
609
+ export { ActionsMeta, AlertMeta, ApprovalLineEditorMeta, ApprovalOverridesEditorMeta, ApprovalStepperMeta, AutocompleteFieldMeta, AvatarMeta, BadgeMeta, BreadcrumbMeta, ButtonMeta, CanMeta, CardMeta, ChartMeta, CheckboxMeta, ColMeta, ColumnBoardMeta, DatePickerMeta, DateRangePickerMeta, DialogMeta, DiffViewMeta, DisclosureMeta, DividerMeta, EmptyStateMeta, EventCalendarMeta, ExcelUploadCardMeta, FieldMeta, FileDropzoneMeta, ForEachMeta, FormMeta, FragmentMeta, GridMeta, IconButtonMeta, IconMeta, ImageLightboxMeta, InlineEditMeta, InputMeta, JsonFieldMeta, KeyValueListMeta, LiveClockMeta, MapPreviewMeta, OrgChartBoardMeta, OrgChartMeta, PageHeadMeta, PaginationMeta, PeriodFilterMeta, PermissionMatrixEditorMeta, PrintSheetMeta, ProgressBarMeta, RadioMeta, RankBoardMeta, RecordSheetMeta, RemoteSearchResultsMeta, RoleMemberPickerMeta, 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 };