@wipco/sdui 0.29.0 → 0.30.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';
@@ -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 { 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
@@ -9,8 +9,8 @@ export { SearchToolbar, cascadingValuePaths, cleanSearchValues, includesLooseSea
9
9
  import { HoistedStyle, DateRangePicker, DatePicker } from './chunk-EFMKWPZ3.js';
10
10
  export { DatePicker, DateRangePicker } from './chunk-EFMKWPZ3.js';
11
11
  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';
12
+ import { DEV, isSafeNavigationTarget, useSduiAdapter, hostFetchOf, useSduiSandbox, resolveAdapter, assetResolverOf, SduiAdapterContext } from './chunk-DKPNFEVU.js';
13
+ export { defaultAdapter, useSduiAssetResolver, useSduiSandbox } from './chunk-DKPNFEVU.js';
14
14
  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
15
  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
16
  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,7 @@ 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;
433
434
  declare const AutocompleteFieldMeta: SduiComponentMeta;
434
435
  declare const InlineEditMeta: SduiComponentMeta;
435
436
  declare const JsonFieldMeta: SduiComponentMeta;
@@ -455,7 +456,7 @@ declare const StripMeta: SduiComponentMeta;
455
456
  /**
456
457
  * 위젯 어휘의 메타데이터.
457
458
  *
458
- * 키 타입이 `SduiWidgetComponentName | 'AutocompleteField'`인 이유:
459
+ * 키 타입이 `SduiWidgetComponentName | 'AutocompleteField' | 'FileDropzone'`인 이유:
459
460
  * 앞의 20종은 `widgetComponents`와 키가 어긋나면 컴파일 에러가 나야 하고(core·rich와 같다),
460
461
  * `AutocompleteField`는 그 묶음에 **일부러 빠져 있는** 이름이라 union에 직접 적어야 한다.
461
462
  * 표시 계층만 라이브러리에 있고 조회는 앱 어댑터가 얹는 구조인데(widgets/index.ts 참고),
@@ -466,6 +467,7 @@ declare const widgetComponentMeta: {
466
467
  ApprovalLineEditor: SduiComponentMeta;
467
468
  ApprovalOverridesEditor: SduiComponentMeta;
468
469
  ApprovalStepper: SduiComponentMeta;
470
+ FileDropzone: SduiComponentMeta;
469
471
  PermissionMatrixEditor: SduiComponentMeta;
470
472
  RoleMemberPicker: SduiComponentMeta;
471
473
  OrgChartBoard: SduiComponentMeta;
@@ -602,4 +604,4 @@ declare function paletteGroups(catalog?: Record<string, SduiComponentMeta>): Arr
602
604
  items: SduiComponentMeta[];
603
605
  }>;
604
606
 
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 };
607
+ 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, 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 };
package/dist/meta.js CHANGED
@@ -3839,6 +3839,116 @@ var RoleMemberPickerMeta = {
3839
3839
  },
3840
3840
  resize: { kind: "none" }
3841
3841
  };
3842
+ var FileDropzoneMeta = {
3843
+ name: "FileDropzone",
3844
+ label: "\uD30C\uC77C \uCCA8\uBD80",
3845
+ category: "input",
3846
+ icon: "paperclip",
3847
+ description: "\uB4DC\uB798\uADF8 \uC564 \uB4DC\uB86D \uB2E4\uC911 \uCCA8\uBD80. \uC5C5\uB85C\uB4DC \uACBD\uB85C\uAC00 \uC5C6\uAC70\uB098 \uBC31\uC5D4\uB4DC\uAC00 503\uC774\uBA74 object URL \uB85C\uCEEC \uBBF8\uB9AC\uBCF4\uAE30\uB85C \uB5A8\uC5B4\uC9C4\uB2E4.",
3848
+ props: [
3849
+ {
3850
+ name: "value",
3851
+ kind: "array",
3852
+ label: "\uCCA8\uBD80 \uBAA9\uB85D",
3853
+ bindable: true,
3854
+ description: "\uC8FC\uBA74 controlled. \uC2E4\uCE21 6/7\uC774 \uC804\uBD80 $state \uBC14\uC778\uB529\uC774\uACE0 default\uAC00 \uBE48 \uBC30\uC5F4\uC774\uB2E4.",
3855
+ group: "data",
3856
+ // 실측 6/7. 원소 리터럴은 스펙에 한 건도 없어(전부 빈 배열) 형태의 근거는 타입뿐이다.
3857
+ item: [
3858
+ { name: "id", kind: "string", label: "\uC2DD\uBCC4\uC790", required: true },
3859
+ { name: "url", kind: "string", label: "URL", required: true },
3860
+ { name: "name", kind: "string", label: "\uD30C\uC77C\uBA85" }
3861
+ ]
3862
+ },
3863
+ {
3864
+ name: "onChange",
3865
+ kind: "action",
3866
+ label: "\uCCA8\uBD80 \uBCC0\uACBD",
3867
+ eventArg: "\uBCC0\uACBD\uB41C \uCCA8\uBD80 \uBAA9\uB85D \uBC30\uC5F4",
3868
+ group: "behavior"
3869
+ },
3870
+ // 실측 6/7 — 전부 setState + fromEvent
3871
+ {
3872
+ name: "endpoint",
3873
+ kind: "string",
3874
+ label: "\uC5C5\uB85C\uB4DC \uACBD\uB85C",
3875
+ description: '\uD30C\uC77C\uB9C8\uB2E4 FormData("file")\uB97C POST\uD558\uACE0 {id,url} \uC751\uB2F5\uC744 \uAE30\uB300\uD55C\uB2E4. \uC5C6\uC73C\uBA74 \uB85C\uCEEC \uBBF8\uB9AC\uBCF4\uAE30 \uBAA8\uB4DC\uB85C\uB9CC \uB3D9\uC791\uD55C\uB2E4.',
3876
+ group: "data"
3877
+ },
3878
+ // 실측 1/7 ("/asset/attachments")
3879
+ { name: "maxCount", kind: "number", label: "\uCD5C\uB300 \uCCA8\uBD80 \uC218", group: "behavior" },
3880
+ // 실측 7/7 (5×4, 8×3)
3881
+ {
3882
+ name: "accept",
3883
+ kind: "string",
3884
+ label: "\uD5C8\uC6A9 \uD615\uC2DD",
3885
+ default: "image/*",
3886
+ description: "input accept \uBB38\uBC95. \uB9DE\uC9C0 \uC54A\uB294 \uD30C\uC77C\uC740 \uC81C\uC678\uD558\uACE0 \uC778\uB77C\uC778 \uACBD\uACE0\uB85C \uC548\uB0B4\uD55C\uB2E4.",
3887
+ group: "behavior"
3888
+ },
3889
+ // 실측 7/7 ("image/*,.pdf"×4, "image/*"×3)
3890
+ { name: "hint", kind: "string", label: "\uBCF4\uC870 \uBB38\uAD6C", description: "\uB4DC\uB86D \uC601\uC5ED \uC544\uB798\uC5D0 \uBD99\uB294\uB2E4.", group: "content" },
3891
+ // 실측 7/7
3892
+ {
3893
+ name: "deleteEndpoint",
3894
+ kind: "string",
3895
+ label: "\uC0AD\uC81C \uACBD\uB85C \uAE30\uC900",
3896
+ description: "\uC8FC\uBA74 \uC378\uB124\uC77C \uC0AD\uC81C\uAC00 DELETE {\uC774 \uAC12}/{id}\uAE4C\uC9C0 \uBCF4\uB0B8\uB2E4(\uC608: /attachments). \uC774\uBBF8 \uC62C\uB77C\uAC04 \uC0AC\uC9C4\uC744 \uC774 \uCE78\uC5D0 \uCC44\uC6CC \uB458 \uB54C \uD544\uC218 \u2014 \uC5C6\uC73C\uBA74 \uC9C0\uC6B4 \uAC83\uCC98\uB7FC \uBCF4\uC774\uB294\uB370 \uC11C\uBC84\uC5D0\uB294 \uB0A8\uB294\uB2E4.",
3897
+ group: "data"
3898
+ },
3899
+ {
3900
+ name: "onRemove",
3901
+ kind: "action",
3902
+ label: "\uC0AD\uC81C \uD6C4",
3903
+ description: "\uC11C\uBC84\uC5D0\uC11C \uC2E4\uC81C\uB85C \uC9C0\uC6B4 \uB4A4 \uD55C \uBC88. \uBCF4\uD1B5 \uBAA9\uB85D\xB7\uC0C1\uC138\uB97C \uB2E4\uC2DC \uC77D\uB294 invalidate\uB97C \uAC74\uB2E4.",
3904
+ group: "behavior"
3905
+ },
3906
+ { name: "disabled", kind: "boolean", label: "\uBE44\uD65C\uC131", default: false, bindable: true, group: "behavior" },
3907
+ // 실측 0/7 — 구현에는 있다
3908
+ {
3909
+ name: "onBusyChange",
3910
+ kind: "action",
3911
+ label: "\uC5C5\uB85C\uB4DC \uC9C4\uD589 \uC54C\uB9BC",
3912
+ eventArg: "\uC5C5\uB85C\uB4DC \uD050\uAC00 \uB3C4\uB294 \uB3D9\uC548 true, \uB05D\uB098\uBA74 false",
3913
+ description: "\uCCA8\uBD80\uAC00 \uC62C\uB77C\uAC00\uB294 \uB3D9\uC548 \uC800\uC7A5 \uBC84\uD2BC\uC744 \uC7A0\uAE00 \uB54C \uC4F4\uB2E4.",
3914
+ group: "behavior",
3915
+ advanced: true
3916
+ },
3917
+ // 실측 0/7 — 구현에는 있다
3918
+ {
3919
+ name: "fileField",
3920
+ kind: "string",
3921
+ label: "multipart \uD544\uB4DC \uC774\uB984",
3922
+ description: "\uAE30\uBCF8 'file'. \uC11C\uBC84\uAC00 \uB2E4\uB978 \uC774\uB984\uC744 \uBC1B\uC73C\uBA74 \uBC14\uAFBC\uB2E4.",
3923
+ group: "data",
3924
+ advanced: true
3925
+ },
3926
+ {
3927
+ name: "localFallbackOn503",
3928
+ kind: "boolean",
3929
+ label: "503\uC774\uBA74 \uB85C\uCEEC \uBBF8\uB9AC\uBCF4\uAE30",
3930
+ description: '503\uC744 "\uBC31\uC5D4\uB4DC \uBBF8\uC5F0\uB3D9"\uC73C\uB85C \uC77D\uC5B4 \uAC15\uB4F1\uD55C\uB2E4. \uAE30\uBCF8 \uAEBC\uC9D0 \u2014 \uC77C\uBC18 \uC11C\uBC84\uC758 \uACFC\uBD80\uD558 503\uC5D0\uC11C "\uC62C\uB9B0 \uAC83 \uAC19\uC740\uB370 \uC0C8\uB85C\uACE0\uCE68\uD558\uBA74 \uC5C6\uB294" \uD56D\uBAA9\uC744 \uB9CC\uB4E4\uBA74 \uC548 \uB418\uAE30 \uB54C\uBB38\uC774\uB2E4.',
3931
+ group: "behavior",
3932
+ advanced: true
3933
+ }
3934
+ ],
3935
+ children: { accepts: false },
3936
+ dom: { element: "div" },
3937
+ // 실측: 루트가 <div class="wp-col">
3938
+ // 프리셋에 endpoint를 넣지 않는다 — 넣으면 캔버스에서 파일을 놓는 순간 실제 업로드가 나간다.
3939
+ // 없으면 업로드 큐가 object URL로만 돌아 바깥으로 나가는 요청이 0건이다.
3940
+ preset: {
3941
+ component: "FileDropzone",
3942
+ props: {
3943
+ value: { $state: "form.attachments", default: [] },
3944
+ onChange: { action: "setState", key: "form.attachments", fromEvent: true },
3945
+ accept: "image/*",
3946
+ maxCount: 5,
3947
+ hint: "\uC774\uBBF8\uC9C0 \uD30C\uC77C\uC744 \uB04C\uC5B4\uB2E4 \uB193\uAC70\uB098 \uD074\uB9AD\uD574 \uC120\uD0DD\uD558\uC138\uC694"
3948
+ }
3949
+ },
3950
+ resize: { kind: "none" }
3951
+ };
3842
3952
  var AutocompleteFieldMeta = {
3843
3953
  name: "AutocompleteField",
3844
3954
  label: "\uC790\uB3D9\uC644\uC131",
@@ -5259,6 +5369,7 @@ var widgetComponentMeta = {
5259
5369
  ApprovalLineEditor: ApprovalLineEditorMeta,
5260
5370
  ApprovalOverridesEditor: ApprovalOverridesEditorMeta,
5261
5371
  ApprovalStepper: ApprovalStepperMeta,
5372
+ FileDropzone: FileDropzoneMeta,
5262
5373
  PermissionMatrixEditor: PermissionMatrixEditorMeta,
5263
5374
  RoleMemberPicker: RoleMemberPickerMeta,
5264
5375
  OrgChartBoard: OrgChartBoardMeta,
@@ -5320,4 +5431,4 @@ function paletteGroups(catalog = allComponentMeta) {
5320
5431
  })).filter((group) => group.items.length > 0);
5321
5432
  }
5322
5433
 
5323
- 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, 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 };
5434
+ 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, 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, 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.js CHANGED
@@ -3,7 +3,7 @@ export { TimePicker } from './chunk-WXOU7QTN.js';
3
3
  import { HoistedStyle } from './chunk-EFMKWPZ3.js';
4
4
  export { DatePicker, DateRangePicker } from './chunk-EFMKWPZ3.js';
5
5
  import './chunk-XQPTSEJC.js';
6
- import { useSduiAssetResolver } from './chunk-DVOPZFQS.js';
6
+ import { useSduiAssetResolver } from './chunk-DKPNFEVU.js';
7
7
  import { Spinner, EmptyState } from './chunk-WIDDHR7L.js';
8
8
  import './chunk-SAN4IX6N.js';
9
9
  import { useRef, useCallback, useMemo, useState, useEffect, useLayoutEffect } from 'react';
@@ -1,7 +1,7 @@
1
- export { ApprovalLineEditor, stepLabel, stepTypeIcon } from '../chunk-SAPJVLZM.js';
1
+ export { ApprovalLineEditor, stepLabel, stepTypeIcon } from '../chunk-S4LOH2TU.js';
2
2
  import '../chunk-BD75U73S.js';
3
3
  import '../chunk-EFMKWPZ3.js';
4
4
  import '../chunk-XQPTSEJC.js';
5
- import '../chunk-DVOPZFQS.js';
5
+ import '../chunk-DKPNFEVU.js';
6
6
  import '../chunk-WIDDHR7L.js';
7
7
  import '../chunk-SAN4IX6N.js';
@@ -1,9 +1,9 @@
1
- export { ApprovalOverridesEditor } from '../chunk-ZDRERW4G.js';
1
+ export { ApprovalOverridesEditor } from '../chunk-OR27SRKL.js';
2
2
  import '../chunk-QDEZXA34.js';
3
- import '../chunk-SAPJVLZM.js';
3
+ import '../chunk-S4LOH2TU.js';
4
4
  import '../chunk-BD75U73S.js';
5
5
  import '../chunk-EFMKWPZ3.js';
6
6
  import '../chunk-XQPTSEJC.js';
7
- import '../chunk-DVOPZFQS.js';
7
+ import '../chunk-DKPNFEVU.js';
8
8
  import '../chunk-WIDDHR7L.js';
9
9
  import '../chunk-SAN4IX6N.js';
@@ -1,7 +1,7 @@
1
- export { AutocompleteField, endpointSearchFn, toAutocompleteOptions } from '../chunk-6S5CFNFM.js';
1
+ export { AutocompleteField, endpointSearchFn, toAutocompleteOptions } from '../chunk-CVCP52OD.js';
2
2
  import '../chunk-BD75U73S.js';
3
3
  import '../chunk-EFMKWPZ3.js';
4
4
  import '../chunk-XQPTSEJC.js';
5
- import '../chunk-DVOPZFQS.js';
5
+ import '../chunk-DKPNFEVU.js';
6
6
  import '../chunk-WIDDHR7L.js';
7
7
  import '../chunk-SAN4IX6N.js';
@@ -0,0 +1,65 @@
1
+ import { S as SduiBoxProps } from '../layout-B1QrIBm6.js';
2
+ import React__default from 'react';
3
+
4
+ type FileDropzoneItem = {
5
+ id: string;
6
+ url: string;
7
+ name?: string;
8
+ };
9
+ interface FileDropzoneProps {
10
+ /** 백엔드 업로드 경로 (예: "/attachments"). 없으면 로컬 미리보기 모드 */
11
+ endpoint?: string;
12
+ /** 첨부 목록 — 주면 controlled */
13
+ value?: FileDropzoneItem[];
14
+ onChange?: (files: FileDropzoneItem[]) => void;
15
+ /** 업로드 큐 시작/종료 알림 */
16
+ onBusyChange?: (busy: boolean) => void;
17
+ /** 최대 첨부 수 */
18
+ maxCount?: number;
19
+ /** 허용 형식 (input accept 문법) */
20
+ accept?: string;
21
+ disabled?: boolean;
22
+ /** 드롭 영역 아래 보조 문구 */
23
+ hint?: string;
24
+ /**
25
+ * 첨부 삭제 경로의 **기준**(예: "/attachments"). 주면 썸네일의 삭제가 목록에서
26
+ * 빼는 데 그치지 않고 `DELETE {deleteEndpoint}/{id}` 까지 보낸다.
27
+ *
28
+ * 이게 없으면 이미 올라간 사진을 이 칸에 채워 둘 수 없다 — 지운 것처럼 보이는데
29
+ * 서버에는 그대로 남는 함정이 되기 때문이다(그래서 예전에는 "추가 전용"이었다).
30
+ * 아직 업로드 중이거나 로컬 미리보기(id가 `local-`)인 항목은 보낼 곳이 없어 건너뛴다.
31
+ */
32
+ /**
33
+ * 업로드를 직접 맡는다. 주면 `endpoint` 대신 이 함수가 불린다 — 위젯은 응답을
34
+ * 해석하지 않고 항목을 그대로 받는다(서버 봉투가 특수한 앱의 탈출구).
35
+ */
36
+ upload?: (file: File) => Promise<FileDropzoneItem>;
37
+ /** multipart 필드 이름. 기본 'file'. */
38
+ fileField?: string;
39
+ /**
40
+ * 503을 "백엔드 미연동"으로 읽어 로컬 미리보기로 강등한다. 기본 false —
41
+ * 일반 서버의 과부하 503에서 "올린 것처럼 보이는데 새로고침하면 없는" 항목을
42
+ * 만들면 안 되기 때문이다. 데모 모드를 503으로 합성하는 앱만 켠다.
43
+ */
44
+ localFallbackOn503?: boolean;
45
+ deleteEndpoint?: string;
46
+ /** 서버에서 실제로 지운 뒤 한 번 — 보통 목록·상세를 다시 읽는 invalidate를 건다 */
47
+ onRemove?: () => void;
48
+ }
49
+ /**
50
+ * 업로드 응답에서 `{id, url}` 을 꺼낸다 — **도메인 명사를 알지 않는다.**
51
+ *
52
+ * 단일 자원을 한 겹 감싸 주는 것은 REST 관례다(`{attachment:{…}}`·`{data:{…}}`).
53
+ * 그래서 이름 목록을 두는 대신 **모양**으로 판정한다: 최상위에 id·url 이 없고
54
+ * 객체 값을 가진 키가 **정확히 하나**면 그 안으로 한 겹 내려간다.
55
+ * 그 조건을 벗어나면(키가 둘 이상, 값이 객체가 아님) 벗기지 않는다 — 잘못 벗겨
56
+ * 엉뚱한 값을 집는 것보다 "응답 형식 오류"로 드러나는 편이 낫다.
57
+ */
58
+ declare function unwrapUploaded(payload: unknown): {
59
+ id?: unknown;
60
+ url?: unknown;
61
+ } | null;
62
+ /** 바깥에서 온 `style`·`className`을 뿌리에 얹어 내보낸다 — 자기 축(폭·여백)이 닿게 하는 통로. */
63
+ declare const FileDropzone: React__default.FC<FileDropzoneProps & SduiBoxProps>;
64
+
65
+ export { FileDropzone, type FileDropzoneItem, type FileDropzoneProps, unwrapUploaded };
@@ -0,0 +1,6 @@
1
+ export { FileDropzone, unwrapUploaded } from '../chunk-DQVDS2VP.js';
2
+ import '../chunk-XPBZMECW.js';
3
+ import '../chunk-THEP3VWY.js';
4
+ import '../chunk-DKPNFEVU.js';
5
+ import '../chunk-WIDDHR7L.js';
6
+ import '../chunk-SAN4IX6N.js';
@@ -1,6 +1,6 @@
1
- export { OrgChartBoard } from '../chunk-KBLFOC37.js';
1
+ export { OrgChartBoard } from '../chunk-NGZI4V3O.js';
2
2
  import '../chunk-XPBZMECW.js';
3
3
  import '../chunk-THEP3VWY.js';
4
- import '../chunk-DVOPZFQS.js';
4
+ import '../chunk-DKPNFEVU.js';
5
5
  import '../chunk-WIDDHR7L.js';
6
6
  import '../chunk-SAN4IX6N.js';
@@ -1,4 +1,4 @@
1
- export { PermissionMatrixEditor } from '../chunk-WLQ7PLHB.js';
2
- import '../chunk-DVOPZFQS.js';
1
+ export { PermissionMatrixEditor } from '../chunk-N24VF6KI.js';
2
+ import '../chunk-DKPNFEVU.js';
3
3
  import '../chunk-WIDDHR7L.js';
4
4
  import '../chunk-SAN4IX6N.js';
@@ -1,6 +1,6 @@
1
- export { RankBoard } from '../chunk-DIVN4TBW.js';
1
+ export { RankBoard } from '../chunk-U6H7YN4I.js';
2
2
  import '../chunk-QU7YVMFA.js';
3
3
  import '../chunk-XQPTSEJC.js';
4
- import '../chunk-DVOPZFQS.js';
4
+ import '../chunk-DKPNFEVU.js';
5
5
  import '../chunk-WIDDHR7L.js';
6
6
  import '../chunk-SAN4IX6N.js';
@@ -1,4 +1,4 @@
1
- export { RoleMemberPicker } from '../chunk-XSCFMCLH.js';
2
- import '../chunk-DVOPZFQS.js';
1
+ export { RoleMemberPicker } from '../chunk-WXZDYDXG.js';
2
+ import '../chunk-DKPNFEVU.js';
3
3
  import '../chunk-WIDDHR7L.js';
4
4
  import '../chunk-SAN4IX6N.js';
@@ -57,6 +57,7 @@ import { S as SduiBoxProps } from '../layout-B1QrIBm6.js';
57
57
  import { ApprovalLineEditorProps } from './ApprovalLineEditor.js';
58
58
  export { ApprovalLineEditor, ApprovalLineStep, CustomApprover } from './ApprovalLineEditor.js';
59
59
  export { AutocompleteField, AutocompleteFieldProps, AutocompleteOption, AutocompleteSearchFn, AutocompleteSearchResult, toAutocompleteOptions } from './AutocompleteField.js';
60
+ export { FileDropzone, FileDropzoneItem, FileDropzoneProps, unwrapUploaded } from './FileDropzone.js';
60
61
 
61
62
  /**
62
63
  * `createRegistry(widgetComponents)`로 어휘에 얹는 묶음.
@@ -1,3 +1,9 @@
1
+ import { TreePicker } from '../chunk-U3HYQCFA.js';
2
+ export { TreePicker } from '../chunk-U3HYQCFA.js';
3
+ import { TreeView } from '../chunk-X5R2HHEN.js';
4
+ export { TreeView, buildTreeNodes } from '../chunk-X5R2HHEN.js';
5
+ import { RoleMemberPicker } from '../chunk-WXZDYDXG.js';
6
+ export { RoleMemberPicker } from '../chunk-WXZDYDXG.js';
1
7
  import { SearchableMultiPicker } from '../chunk-C53NGSA3.js';
2
8
  export { SearchableMultiPicker } from '../chunk-C53NGSA3.js';
3
9
  import { SecureScreen } from '../chunk-IIRENCDS.js';
@@ -10,26 +16,22 @@ import { SummaryTable } from '../chunk-O4U7Y3XL.js';
10
16
  export { SummaryTable } from '../chunk-O4U7Y3XL.js';
11
17
  import { Timeline } from '../chunk-EFYEOT47.js';
12
18
  export { Timeline } from '../chunk-EFYEOT47.js';
13
- import { TreePicker } from '../chunk-U3HYQCFA.js';
14
- export { TreePicker } from '../chunk-U3HYQCFA.js';
15
- import { TreeView } from '../chunk-X5R2HHEN.js';
16
- export { TreeView, buildTreeNodes } from '../chunk-X5R2HHEN.js';
19
+ import { LiveClock } from '../chunk-Z3WGIFJP.js';
20
+ export { LiveClock } from '../chunk-Z3WGIFJP.js';
17
21
  import { OrgChart } from '../chunk-QWJ3OBBL.js';
18
22
  export { OrgChart } from '../chunk-QWJ3OBBL.js';
19
- import { OrgChartBoard } from '../chunk-KBLFOC37.js';
20
- export { OrgChartBoard } from '../chunk-KBLFOC37.js';
21
- import { PermissionMatrixEditor } from '../chunk-WLQ7PLHB.js';
22
- export { PermissionMatrixEditor } from '../chunk-WLQ7PLHB.js';
23
+ import { OrgChartBoard } from '../chunk-NGZI4V3O.js';
24
+ export { OrgChartBoard } from '../chunk-NGZI4V3O.js';
25
+ import { PermissionMatrixEditor } from '../chunk-N24VF6KI.js';
26
+ export { PermissionMatrixEditor } from '../chunk-N24VF6KI.js';
23
27
  import { PeriodFilter } from '../chunk-INWASR2G.js';
24
28
  export { PeriodFilter } from '../chunk-INWASR2G.js';
25
29
  import { PrintSheet } from '../chunk-MYJ2KEIP.js';
26
30
  export { PrintSheet } from '../chunk-MYJ2KEIP.js';
27
- import { RankBoard } from '../chunk-DIVN4TBW.js';
28
- export { RankBoard } from '../chunk-DIVN4TBW.js';
31
+ import { RankBoard } from '../chunk-U6H7YN4I.js';
32
+ export { RankBoard } from '../chunk-U6H7YN4I.js';
29
33
  import { RecordSheet } from '../chunk-K3JBSWFI.js';
30
34
  export { RecordSheet } from '../chunk-K3JBSWFI.js';
31
- import { RoleMemberPicker } from '../chunk-XSCFMCLH.js';
32
- export { RoleMemberPicker } from '../chunk-XSCFMCLH.js';
33
35
  import { ColumnBoard } from '../chunk-C4JRSZGS.js';
34
36
  export { ColumnBoard } from '../chunk-C4JRSZGS.js';
35
37
  import { DiffView } from '../chunk-LDJ2CFFH.js';
@@ -38,29 +40,28 @@ import { Disclosure } from '../chunk-HIZ2OVJA.js';
38
40
  export { Disclosure } from '../chunk-HIZ2OVJA.js';
39
41
  import { EventCalendar } from '../chunk-C3JY6GA3.js';
40
42
  export { EventCalendar } from '../chunk-C3JY6GA3.js';
43
+ export { FileDropzone, unwrapUploaded } from '../chunk-DQVDS2VP.js';
41
44
  import { ImageLightbox } from '../chunk-XPBZMECW.js';
42
45
  export { ImageLightbox } from '../chunk-XPBZMECW.js';
43
46
  import { InlineEdit } from '../chunk-KT3ZVBLN.js';
44
47
  export { InlineEdit } from '../chunk-KT3ZVBLN.js';
45
48
  import { JsonField } from '../chunk-JVBZRDC2.js';
46
49
  export { JsonField } from '../chunk-JVBZRDC2.js';
47
- import { LiveClock } from '../chunk-Z3WGIFJP.js';
48
- export { LiveClock } from '../chunk-Z3WGIFJP.js';
49
50
  import '../chunk-QU7YVMFA.js';
50
51
  import '../chunk-THEP3VWY.js';
51
- import { ApprovalOverridesEditor } from '../chunk-ZDRERW4G.js';
52
- export { ApprovalOverridesEditor } from '../chunk-ZDRERW4G.js';
52
+ import { ApprovalOverridesEditor } from '../chunk-OR27SRKL.js';
53
+ export { ApprovalOverridesEditor } from '../chunk-OR27SRKL.js';
53
54
  import { TagMultiSelect } from '../chunk-QDEZXA34.js';
54
55
  export { TagMultiSelect } from '../chunk-QDEZXA34.js';
55
- import { ApprovalLineEditor } from '../chunk-SAPJVLZM.js';
56
- export { ApprovalLineEditor } from '../chunk-SAPJVLZM.js';
56
+ import { ApprovalLineEditor } from '../chunk-S4LOH2TU.js';
57
+ export { ApprovalLineEditor } from '../chunk-S4LOH2TU.js';
57
58
  import { ApprovalStepper } from '../chunk-ZTQEP3TR.js';
58
59
  export { ApprovalStepper } from '../chunk-ZTQEP3TR.js';
59
- export { AutocompleteField, toAutocompleteOptions } from '../chunk-6S5CFNFM.js';
60
+ export { AutocompleteField, toAutocompleteOptions } from '../chunk-CVCP52OD.js';
60
61
  import '../chunk-BD75U73S.js';
61
62
  import '../chunk-EFMKWPZ3.js';
62
63
  import '../chunk-XQPTSEJC.js';
63
- import '../chunk-DVOPZFQS.js';
64
+ import '../chunk-DKPNFEVU.js';
64
65
  import '../chunk-WIDDHR7L.js';
65
66
  import '../chunk-SAN4IX6N.js';
66
67
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipco/sdui",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "description": "WIPCO 서버 주도 UI(SDUI) 렌더러 — JSON 페이지 스펙을 React 컴포넌트로 렌더링",
5
5
  "keywords": [
6
6
  "sdui",
@@ -68,6 +68,10 @@
68
68
  "types": "./dist/widgets/EventCalendar.d.ts",
69
69
  "default": "./dist/widgets/EventCalendar.js"
70
70
  },
71
+ "./widgets/FileDropzone": {
72
+ "types": "./dist/widgets/FileDropzone.d.ts",
73
+ "default": "./dist/widgets/FileDropzone.js"
74
+ },
71
75
  "./widgets/ImageLightbox": {
72
76
  "types": "./dist/widgets/ImageLightbox.d.ts",
73
77
  "default": "./dist/widgets/ImageLightbox.js"