@wipco/sdui 0.1.0 → 0.3.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.
Files changed (78) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +27 -0
  3. package/dist/chunk-4SU2KYPU.js +59 -0
  4. package/dist/chunk-56KKWLNI.js +186 -0
  5. package/dist/chunk-7Y4J7EOK.js +115 -0
  6. package/dist/chunk-APCXOCAO.js +110 -0
  7. package/dist/chunk-AROOQ5W6.js +119 -0
  8. package/dist/chunk-DGPRX4MU.js +251 -0
  9. package/dist/chunk-IL2KS4WS.js +25 -0
  10. package/dist/chunk-KADFN76X.js +541 -0
  11. package/dist/chunk-KBHI3IUR.js +40 -0
  12. package/dist/chunk-LZJTKVMO.js +767 -0
  13. package/dist/chunk-MOIU7MUA.js +282 -0
  14. package/dist/chunk-MSAV4J4I.js +77 -0
  15. package/dist/chunk-OOHHW4K4.js +159 -0
  16. package/dist/chunk-Q6SBH63R.js +190 -0
  17. package/dist/chunk-QZQDHSMD.js +78 -0
  18. package/dist/chunk-RH7ESUE6.js +309 -0
  19. package/dist/chunk-SEBPBUZR.js +260 -0
  20. package/dist/chunk-USKRM22W.js +39 -0
  21. package/dist/chunk-WNYWTFGW.js +61 -0
  22. package/dist/chunk-WVRCIVAD.js +72 -0
  23. package/dist/chunk-YJMD6MOE.js +404 -0
  24. package/dist/chunk-ZNITJ4KK.js +433 -0
  25. package/dist/chunk-ZQR3ULVH.js +193 -0
  26. package/dist/chunk-ZUCI34QY.js +1290 -0
  27. package/dist/index.d.ts +391 -79
  28. package/dist/index.js +277 -758
  29. package/dist/meta.d.ts +10 -2
  30. package/dist/meta.js +268 -27
  31. package/dist/rich.js +3 -2
  32. package/dist/styles.css +334 -13
  33. package/dist/widgets/AutocompleteField.d.ts +96 -0
  34. package/dist/widgets/AutocompleteField.js +3 -0
  35. package/dist/widgets/ColumnBoard.d.ts +47 -0
  36. package/dist/widgets/ColumnBoard.js +2 -0
  37. package/dist/widgets/DiffView.d.ts +30 -0
  38. package/dist/widgets/DiffView.js +2 -0
  39. package/dist/widgets/Disclosure.d.ts +20 -0
  40. package/dist/widgets/Disclosure.js +2 -0
  41. package/dist/widgets/EventCalendar.d.ts +59 -0
  42. package/dist/widgets/EventCalendar.js +2 -0
  43. package/dist/widgets/ImageLightbox.d.ts +25 -0
  44. package/dist/widgets/ImageLightbox.js +2 -0
  45. package/dist/widgets/InlineEdit.d.ts +23 -0
  46. package/dist/widgets/InlineEdit.js +2 -0
  47. package/dist/widgets/JsonField.d.ts +24 -0
  48. package/dist/widgets/JsonField.js +2 -0
  49. package/dist/widgets/LiveClock.d.ts +21 -0
  50. package/dist/widgets/LiveClock.js +1 -0
  51. package/dist/widgets/OrgChart.d.ts +32 -0
  52. package/dist/widgets/OrgChart.js +2 -0
  53. package/dist/widgets/PeriodFilter.d.ts +48 -0
  54. package/dist/widgets/PeriodFilter.js +2 -0
  55. package/dist/widgets/PrintSheet.d.ts +23 -0
  56. package/dist/widgets/PrintSheet.js +2 -0
  57. package/dist/widgets/SearchableMultiPicker.d.ts +29 -0
  58. package/dist/widgets/SearchableMultiPicker.js +3 -0
  59. package/dist/widgets/SecureScreen.d.ts +25 -0
  60. package/dist/widgets/SecureScreen.js +2 -0
  61. package/dist/widgets/StackedMeter.d.ts +26 -0
  62. package/dist/widgets/StackedMeter.js +1 -0
  63. package/dist/widgets/Strip.d.ts +20 -0
  64. package/dist/widgets/Strip.js +1 -0
  65. package/dist/widgets/SummaryTable.d.ts +31 -0
  66. package/dist/widgets/SummaryTable.js +1 -0
  67. package/dist/widgets/TagMultiSelect.d.ts +25 -0
  68. package/dist/widgets/TagMultiSelect.js +2 -0
  69. package/dist/widgets/Timeline.d.ts +30 -0
  70. package/dist/widgets/Timeline.js +3 -0
  71. package/dist/widgets/TreePicker.d.ts +21 -0
  72. package/dist/widgets/TreePicker.js +3 -0
  73. package/dist/widgets/TreeView.d.ts +26 -0
  74. package/dist/widgets/TreeView.js +2 -0
  75. package/dist/widgets/index.d.ts +76 -0
  76. package/dist/widgets/index.js +69 -0
  77. package/package.json +93 -3
  78. package/dist/chunk-AR2JYE75.js +0 -889
@@ -0,0 +1,282 @@
1
+ import { includesLooseSearch } from './chunk-KADFN76X.js';
2
+ import { Icon, Button, Input, Spinner } from './chunk-ZUCI34QY.js';
3
+ import { useId, useRef, useState, useEffect, useMemo } from 'react';
4
+ import { jsxs, jsx } from 'react/jsx-runtime';
5
+
6
+ function isRecord(value) {
7
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8
+ }
9
+ function toAutocompleteOptions(payload) {
10
+ if (Array.isArray(payload)) return payload.filter(isRecord);
11
+ if (isRecord(payload)) {
12
+ for (const key of ["items", "data", "results", "rows"]) {
13
+ const inner = payload[key];
14
+ if (Array.isArray(inner)) return inner.filter(isRecord);
15
+ }
16
+ }
17
+ return [];
18
+ }
19
+ function pickText(option, key) {
20
+ if (!key) return "";
21
+ const raw = option[key];
22
+ if (raw === null || raw === void 0) return "";
23
+ return String(raw);
24
+ }
25
+ var ellipsisStyle = {
26
+ display: "block",
27
+ overflow: "hidden",
28
+ textOverflow: "ellipsis",
29
+ whiteSpace: "nowrap"
30
+ };
31
+ var statusRowStyle = {
32
+ padding: "var(--wp-space-2) var(--wp-space-3)"
33
+ };
34
+ function AutocompleteField({
35
+ search,
36
+ options,
37
+ labelKey = "label",
38
+ valueKey = "value",
39
+ subKey,
40
+ value,
41
+ selectedLabel,
42
+ onChange,
43
+ placeholder = "\uAC80\uC0C9\uC5B4 \uC785\uB825",
44
+ minChars = 1,
45
+ debounceMs = 300,
46
+ invalid = false,
47
+ disabled = false
48
+ }) {
49
+ const listId = useId();
50
+ const hostRef = useRef(null);
51
+ const seqRef = useRef(0);
52
+ const optionsRef = useRef(options);
53
+ optionsRef.current = options;
54
+ const searchRef = useRef(search);
55
+ searchRef.current = search;
56
+ const hasSearch = search !== void 0;
57
+ const isControlled = value !== void 0;
58
+ const [internalValue, setInternalValue] = useState("");
59
+ const committed = (isControlled ? value : internalValue) ?? "";
60
+ const hasSelection = committed !== "";
61
+ const [picked, setPicked] = useState(null);
62
+ const [query, setQuery] = useState("");
63
+ const [open, setOpen] = useState(false);
64
+ const [status, setStatus] = useState("idle");
65
+ const [results, setResults] = useState([]);
66
+ const [activeIndex, setActiveIndex] = useState(0);
67
+ const [focusPending, setFocusPending] = useState(false);
68
+ useEffect(() => {
69
+ if (disabled || hasSelection) return;
70
+ const keyword = query.trim();
71
+ if (keyword.length < minChars) {
72
+ seqRef.current += 1;
73
+ setStatus("idle");
74
+ setResults([]);
75
+ setOpen(false);
76
+ return;
77
+ }
78
+ setStatus("loading");
79
+ setOpen(true);
80
+ const seq = ++seqRef.current;
81
+ const controller = new AbortController();
82
+ const timer = setTimeout(() => {
83
+ const run = searchRef.current;
84
+ if (run) {
85
+ run(keyword, controller.signal).then((payload) => {
86
+ if (seqRef.current !== seq) return;
87
+ const next = Array.isArray(payload) ? { options: payload } : payload;
88
+ setResults(Array.isArray(next.options) ? next.options : []);
89
+ setActiveIndex(0);
90
+ setStatus(next.status === "offline" ? "offline" : "ready");
91
+ }).catch(() => {
92
+ if (controller.signal.aborted || seqRef.current !== seq) return;
93
+ setResults([]);
94
+ setStatus("error");
95
+ });
96
+ return;
97
+ }
98
+ const filtered = (optionsRef.current ?? []).filter(
99
+ (option) => includesLooseSearch(`${pickText(option, labelKey)} ${pickText(option, subKey)}`, keyword)
100
+ );
101
+ if (seqRef.current !== seq) return;
102
+ setResults(filtered);
103
+ setActiveIndex(0);
104
+ setStatus("ready");
105
+ }, debounceMs);
106
+ return () => {
107
+ clearTimeout(timer);
108
+ controller.abort();
109
+ };
110
+ }, [query, hasSearch, labelKey, subKey, minChars, debounceMs, disabled, hasSelection]);
111
+ useEffect(() => {
112
+ if (!open) return;
113
+ const closeOnOutsideClick = (event) => {
114
+ if (event.target instanceof Node && hostRef.current?.contains(event.target)) return;
115
+ setOpen(false);
116
+ };
117
+ const closeOnEscape = (event) => {
118
+ if (event.key === "Escape") setOpen(false);
119
+ };
120
+ document.addEventListener("pointerdown", closeOnOutsideClick);
121
+ document.addEventListener("keydown", closeOnEscape);
122
+ return () => {
123
+ document.removeEventListener("pointerdown", closeOnOutsideClick);
124
+ document.removeEventListener("keydown", closeOnEscape);
125
+ };
126
+ }, [open]);
127
+ useEffect(() => {
128
+ if (!focusPending || hasSelection) return;
129
+ hostRef.current?.querySelector("input")?.focus();
130
+ setFocusPending(false);
131
+ }, [focusPending, hasSelection]);
132
+ const selectOption = (option) => {
133
+ const nextValue = pickText(option, valueKey);
134
+ setPicked(option);
135
+ if (!isControlled) setInternalValue(nextValue);
136
+ setQuery("");
137
+ setOpen(false);
138
+ setStatus("idle");
139
+ setResults([]);
140
+ onChange?.(nextValue, option);
141
+ };
142
+ const clearSelection = () => {
143
+ setPicked(null);
144
+ if (!isControlled) setInternalValue("");
145
+ setQuery("");
146
+ setStatus("idle");
147
+ setResults([]);
148
+ setFocusPending(true);
149
+ onChange?.(null);
150
+ };
151
+ const handleKeyDown = (event) => {
152
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
153
+ event.preventDefault();
154
+ if (!open || results.length === 0) return;
155
+ setActiveIndex((index) => {
156
+ const delta = event.key === "ArrowDown" ? 1 : -1;
157
+ return (index + delta + results.length) % results.length;
158
+ });
159
+ return;
160
+ }
161
+ if (event.key === "Enter") {
162
+ if (!open) return;
163
+ event.preventDefault();
164
+ const active = status === "ready" ? results[activeIndex] : void 0;
165
+ if (active) selectOption(active);
166
+ return;
167
+ }
168
+ if (event.key === "Escape") setOpen(false);
169
+ };
170
+ const staticMatch = useMemo(() => {
171
+ if (!hasSelection) return null;
172
+ return (options ?? []).find((option) => pickText(option, valueKey) === committed) ?? null;
173
+ }, [options, valueKey, committed, hasSelection]);
174
+ const pickedMatches = picked !== null && pickText(picked, valueKey) === committed;
175
+ const cardOption = pickedMatches ? picked : staticMatch;
176
+ const cardLabel = (pickedMatches ? pickText(picked, labelKey) : "") || selectedLabel || (cardOption ? pickText(cardOption, labelKey) : "") || committed;
177
+ const cardSub = cardOption ? pickText(cardOption, subKey) : "";
178
+ if (hasSelection) {
179
+ return /* @__PURE__ */ jsxs(
180
+ "div",
181
+ {
182
+ ref: hostRef,
183
+ className: "wp-row wp-row--between",
184
+ style: {
185
+ border: `var(--wp-border-w) solid ${invalid ? "var(--wp-border-danger)" : "var(--wp-border-subtle)"}`,
186
+ borderRadius: "var(--wp-radius-control)",
187
+ background: "var(--wp-surface-card)",
188
+ padding: "var(--wp-space-2) var(--wp-space-3)"
189
+ },
190
+ children: [
191
+ /* @__PURE__ */ jsxs("div", { className: "wp-row", style: { minWidth: 0 }, children: [
192
+ /* @__PURE__ */ jsx(
193
+ Icon,
194
+ {
195
+ name: "circle-check",
196
+ size: 18,
197
+ title: "\uC120\uD0DD\uB428",
198
+ style: { color: "var(--wp-text-success)", flex: "none" }
199
+ }
200
+ ),
201
+ /* @__PURE__ */ jsxs("div", { style: { minWidth: 0 }, children: [
202
+ /* @__PURE__ */ jsx("div", { className: "wp-body", style: ellipsisStyle, children: cardLabel }),
203
+ cardSub ? /* @__PURE__ */ jsx("div", { className: "wp-caption", style: ellipsisStyle, children: cardSub }) : null
204
+ ] })
205
+ ] }),
206
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", disabled, onClick: clearSelection, children: "\uBCC0\uACBD" })
207
+ ]
208
+ }
209
+ );
210
+ }
211
+ const showActiveDescendant = open && status === "ready" && results.length > 0;
212
+ return /* @__PURE__ */ jsxs("div", { ref: hostRef, className: "erp-popover-host", children: [
213
+ /* @__PURE__ */ jsx(
214
+ Input,
215
+ {
216
+ role: "combobox",
217
+ "aria-expanded": open,
218
+ "aria-controls": listId,
219
+ "aria-autocomplete": "list",
220
+ "aria-activedescendant": showActiveDescendant ? `${listId}-opt-${activeIndex}` : void 0,
221
+ icon: "search",
222
+ invalid,
223
+ disabled,
224
+ placeholder,
225
+ value: query,
226
+ onChange: (event) => setQuery(event.target.value),
227
+ onFocus: () => {
228
+ if (query.trim().length >= minChars) setOpen(true);
229
+ },
230
+ onKeyDown: handleKeyDown
231
+ }
232
+ ),
233
+ open ? /* @__PURE__ */ jsx("div", { className: "erp-popover erp-popover--left", style: { width: "100%" }, children: /* @__PURE__ */ jsx(
234
+ "div",
235
+ {
236
+ id: listId,
237
+ role: "listbox",
238
+ className: "erp-popover__body",
239
+ style: { maxHeight: 280, padding: "var(--wp-space-1)" },
240
+ children: status === "loading" ? /* @__PURE__ */ jsx("div", { className: "wp-row", style: statusRowStyle, children: /* @__PURE__ */ jsx(Spinner, { size: "sm", label: "\uAC80\uC0C9 \uC911\u2026" }) }) : status === "offline" ? /* @__PURE__ */ jsxs("div", { className: "wp-row", style: statusRowStyle, children: [
241
+ /* @__PURE__ */ jsx(Icon, { name: "unplug", size: 16, style: { color: "var(--wp-text-muted)", flex: "none" } }),
242
+ /* @__PURE__ */ jsx("span", { className: "wp-caption", children: "\uBC31\uC5D4\uB4DC \uBBF8\uC5F0\uB3D9 \u2014 \uAC80\uC0C9 \uACB0\uACFC\uB97C \uBD88\uB7EC\uC62C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4" })
243
+ ] }) : status === "error" ? /* @__PURE__ */ jsxs("div", { className: "wp-row", style: statusRowStyle, children: [
244
+ /* @__PURE__ */ jsx(Icon, { name: "circle-alert", size: 16, style: { color: "var(--wp-text-danger)", flex: "none" } }),
245
+ /* @__PURE__ */ jsx("span", { className: "wp-caption", children: "\uAC80\uC0C9 \uC2E4\uD328 \u2014 \uC7A0\uC2DC \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694" })
246
+ ] }) : results.length === 0 ? /* @__PURE__ */ jsx("div", { className: "wp-caption", style: statusRowStyle, children: "\uACB0\uACFC \uC5C6\uC74C" }) : results.map((option, index) => {
247
+ const optionSub = pickText(option, subKey);
248
+ return /* @__PURE__ */ jsxs(
249
+ "button",
250
+ {
251
+ id: `${listId}-opt-${index}`,
252
+ type: "button",
253
+ role: "option",
254
+ "aria-selected": index === activeIndex,
255
+ onClick: () => selectOption(option),
256
+ onPointerEnter: () => setActiveIndex(index),
257
+ style: {
258
+ display: "block",
259
+ width: "100%",
260
+ textAlign: "left",
261
+ padding: "var(--wp-space-2) var(--wp-space-3)",
262
+ border: 0,
263
+ borderRadius: "var(--wp-radius-sm)",
264
+ background: index === activeIndex ? "var(--wp-surface-hover)" : "transparent",
265
+ color: "var(--wp-text-default)",
266
+ font: "inherit",
267
+ cursor: "pointer"
268
+ },
269
+ children: [
270
+ /* @__PURE__ */ jsx("span", { className: "wp-body-sm", style: ellipsisStyle, children: pickText(option, labelKey) || pickText(option, valueKey) }),
271
+ optionSub ? /* @__PURE__ */ jsx("span", { className: "wp-caption", style: ellipsisStyle, children: optionSub }) : null
272
+ ]
273
+ },
274
+ `${pickText(option, valueKey)}-${index}`
275
+ );
276
+ })
277
+ }
278
+ ) }) : null
279
+ ] });
280
+ }
281
+
282
+ export { AutocompleteField, toAutocompleteOptions };
@@ -0,0 +1,77 @@
1
+ import { Disclosure } from './chunk-WNYWTFGW.js';
2
+ import { Icon } from './chunk-ZUCI34QY.js';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+
5
+ var TYPE_META = {
6
+ create: { icon: "plus", tone: "success" },
7
+ update: { icon: "pencil", tone: "info" },
8
+ delete: { icon: "trash-2", tone: "danger" },
9
+ info: { icon: "info", tone: "neutral" }
10
+ };
11
+ var TIMELINE_CSS = `
12
+ .erp-timeline{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;min-width:0}
13
+ .erp-timeline__item{display:flex;gap:var(--wp-space-3);min-width:0}
14
+ .erp-timeline__rail{display:flex;flex-direction:column;align-items:center;flex:none;width:28px}
15
+ .erp-timeline__dot{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:50%;border:var(--wp-border-w) solid transparent;flex:none}
16
+ .erp-timeline__dot--success{background:var(--wp-status-success-bg);border-color:var(--wp-status-success-border);color:var(--wp-status-success-fg)}
17
+ .erp-timeline__dot--info{background:var(--wp-status-info-bg);border-color:var(--wp-status-info-border);color:var(--wp-status-info-fg)}
18
+ .erp-timeline__dot--danger{background:var(--wp-status-danger-bg);border-color:var(--wp-status-danger-border);color:var(--wp-status-danger-fg)}
19
+ .erp-timeline__dot--neutral{background:var(--wp-status-neutral-bg);border-color:var(--wp-status-neutral-border);color:var(--wp-status-neutral-fg)}
20
+ .erp-timeline__line{flex:1 1 auto;width:2px;min-height:var(--wp-space-3);margin:var(--wp-space-1) 0;background:var(--wp-border-subtle);border-radius:var(--wp-radius-pill)}
21
+ .erp-timeline__body{flex:1 1 auto;min-width:0;padding-bottom:var(--wp-space-5)}
22
+ .erp-timeline__item:last-child .erp-timeline__body{padding-bottom:0}
23
+ .erp-timeline__head{display:flex;align-items:center;gap:var(--wp-space-2);min-height:28px}
24
+ .erp-timeline__title{flex:1 1 auto;min-width:0;font-size:var(--wp-size-sm);font-weight:var(--wp-weight-medium);color:var(--wp-text-strong);overflow-wrap:anywhere}
25
+ .erp-timeline__time{flex:none;white-space:nowrap;font-variant-numeric:tabular-nums}
26
+ .erp-timeline__desc{margin-top:var(--wp-space-1);font-size:var(--wp-size-sm);color:var(--wp-text-muted);overflow-wrap:anywhere}
27
+ .erp-timeline__payload{margin-top:var(--wp-space-1)}
28
+ .erp-timeline__pre{margin:0;padding:var(--wp-space-2) var(--wp-space-3);background:var(--wp-surface-sunken);border:var(--wp-border-w) solid var(--wp-border-subtle);border-radius:var(--wp-radius-sm);color:var(--wp-text-default);overflow-x:auto;white-space:pre}
29
+ .erp-timeline__actions{display:flex;justify-content:flex-end;align-items:center;gap:var(--wp-space-2);margin-top:var(--wp-space-2)}
30
+ `;
31
+ function TimelineStyles() {
32
+ return /* @__PURE__ */ jsx("style", { href: "erp-timeline", precedence: "medium", children: TIMELINE_CSS });
33
+ }
34
+ function formatPayload(payload) {
35
+ try {
36
+ return JSON.stringify(payload, null, 2) ?? String(payload);
37
+ } catch {
38
+ return String(payload);
39
+ }
40
+ }
41
+ function Timeline({ items, maxHeight }) {
42
+ if (items.length === 0) {
43
+ return /* @__PURE__ */ jsx("p", { className: "wp-caption", style: { margin: 0, padding: "var(--wp-space-2) 0" }, children: "\uD45C\uC2DC\uD560 \uC774\uB825\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." });
44
+ }
45
+ return /* @__PURE__ */ jsxs(
46
+ "ol",
47
+ {
48
+ className: "erp-timeline",
49
+ style: maxHeight !== void 0 ? { maxHeight, overflowY: "auto", paddingRight: "var(--wp-space-1)" } : void 0,
50
+ children: [
51
+ /* @__PURE__ */ jsx(TimelineStyles, {}),
52
+ items.map((item, index) => {
53
+ const meta = TYPE_META[item.type ?? "info"];
54
+ const isLast = index === items.length - 1;
55
+ const hasPayload = item.payload !== void 0 && item.payload !== null;
56
+ return /* @__PURE__ */ jsxs("li", { className: "erp-timeline__item", children: [
57
+ /* @__PURE__ */ jsxs("div", { className: "erp-timeline__rail", "aria-hidden": "true", children: [
58
+ /* @__PURE__ */ jsx("span", { className: `erp-timeline__dot erp-timeline__dot--${meta.tone}`, children: /* @__PURE__ */ jsx(Icon, { name: meta.icon, size: 16 }) }),
59
+ !isLast ? /* @__PURE__ */ jsx("span", { className: "erp-timeline__line" }) : null
60
+ ] }),
61
+ /* @__PURE__ */ jsxs("div", { className: "erp-timeline__body", children: [
62
+ /* @__PURE__ */ jsxs("div", { className: "erp-timeline__head", children: [
63
+ /* @__PURE__ */ jsx("div", { className: "erp-timeline__title", children: item.title }),
64
+ item.time !== void 0 && item.time !== "" ? /* @__PURE__ */ jsx("span", { className: "wp-caption erp-timeline__time", children: item.time }) : null
65
+ ] }),
66
+ item.description !== void 0 && item.description !== null ? /* @__PURE__ */ jsx("div", { className: "erp-timeline__desc", children: item.description }) : null,
67
+ hasPayload ? /* @__PURE__ */ jsx("div", { className: "erp-timeline__payload", children: /* @__PURE__ */ jsx(Disclosure, { summary: /* @__PURE__ */ jsx("span", { className: "wp-caption", children: "\uD398\uC774\uB85C\uB4DC" }), children: /* @__PURE__ */ jsx("pre", { className: "wp-mono-xs erp-timeline__pre", children: formatPayload(item.payload) }) }) }) : null,
68
+ item.actions !== void 0 && item.actions !== null ? /* @__PURE__ */ jsx("div", { className: "erp-timeline__actions", children: item.actions }) : null
69
+ ] })
70
+ ] }, item.id);
71
+ })
72
+ ]
73
+ }
74
+ );
75
+ }
76
+
77
+ export { Timeline };
@@ -0,0 +1,159 @@
1
+ import { IconButton, Input } from './chunk-ZUCI34QY.js';
2
+ import { useState, useRef, useEffect, useCallback } from 'react';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+
5
+ var INLINE_EDIT_CSS = `
6
+ .erp-inline-edit{display:inline-flex;align-items:center;gap:var(--wp-space-1);max-width:100%;min-width:0;vertical-align:middle}
7
+ .erp-inline-edit__value{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
8
+ .erp-inline-edit__pencil{opacity:0;transition:opacity var(--wp-duration-fast) var(--wp-ease-standard)}
9
+ .erp-inline-edit:hover .erp-inline-edit__pencil,
10
+ .erp-inline-edit:focus-within .erp-inline-edit__pencil{opacity:1}
11
+ @media (hover:none){.erp-inline-edit__pencil{opacity:1}}
12
+ .erp-inline-edit-editor{display:inline-flex;flex-direction:column;gap:var(--wp-space-half);max-width:100%;min-width:0;vertical-align:middle}
13
+ .erp-inline-edit-editor__row{display:inline-flex;align-items:center;gap:var(--wp-space-1);min-width:0}
14
+ .erp-inline-edit__spin svg{animation:erp-inline-edit-spin .8s linear infinite}
15
+ @keyframes erp-inline-edit-spin{to{transform:rotate(360deg)}}
16
+ `;
17
+ function saveErrorMessage(err) {
18
+ const status = err?.status;
19
+ if (status === 503) return "\uBC31\uC5D4\uB4DC \uBBF8\uC5F0\uB3D9 \uC0C1\uD0DC\uB77C \uC800\uC7A5\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uB370\uBAA8 \uBAA8\uB4DC)";
20
+ if (err instanceof Error && err.message) return err.message;
21
+ return "\uC800\uC7A5\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694.";
22
+ }
23
+ function InlineEdit({
24
+ value,
25
+ onSave,
26
+ type = "text",
27
+ mono = false,
28
+ disabled = false,
29
+ emptyText = "\u2014"
30
+ }) {
31
+ const [editing, setEditing] = useState(false);
32
+ const [draft, setDraft] = useState(value);
33
+ const [saving, setSaving] = useState(false);
34
+ const [saveError, setSaveError] = useState(null);
35
+ const viewHostRef = useRef(null);
36
+ const wasEditingRef = useRef(false);
37
+ useEffect(() => {
38
+ if (wasEditingRef.current && !editing) {
39
+ viewHostRef.current?.querySelector("button")?.focus();
40
+ }
41
+ wasEditingRef.current = editing;
42
+ }, [editing]);
43
+ const startEdit = useCallback(() => {
44
+ setDraft(value);
45
+ setSaveError(null);
46
+ setEditing(true);
47
+ }, [value]);
48
+ const cancelEdit = useCallback(() => {
49
+ if (saving) return;
50
+ setSaveError(null);
51
+ setEditing(false);
52
+ }, [saving]);
53
+ const confirmEdit = useCallback(async () => {
54
+ if (saving) return;
55
+ if (draft === value) {
56
+ setSaveError(null);
57
+ setEditing(false);
58
+ return;
59
+ }
60
+ setSaving(true);
61
+ setSaveError(null);
62
+ try {
63
+ await onSave(draft);
64
+ setEditing(false);
65
+ } catch (err) {
66
+ setSaveError(saveErrorMessage(err));
67
+ } finally {
68
+ setSaving(false);
69
+ }
70
+ }, [saving, draft, value, onSave]);
71
+ const handleKeyDown = useCallback(
72
+ (e) => {
73
+ if (saving) return;
74
+ if (e.key === "Enter") {
75
+ e.preventDefault();
76
+ void confirmEdit();
77
+ } else if (e.key === "Escape") {
78
+ e.preventDefault();
79
+ e.stopPropagation();
80
+ cancelEdit();
81
+ }
82
+ },
83
+ [saving, confirmEdit, cancelEdit]
84
+ );
85
+ const style = /* @__PURE__ */ jsx("style", { href: "erp-inline-edit", precedence: "default", children: INLINE_EDIT_CSS });
86
+ if (!editing) {
87
+ const isEmpty = value === "";
88
+ return /* @__PURE__ */ jsxs("span", { className: "erp-inline-edit", ref: viewHostRef, children: [
89
+ style,
90
+ /* @__PURE__ */ jsx(
91
+ "span",
92
+ {
93
+ className: "erp-inline-edit__value" + (mono ? " wp-mono" : ""),
94
+ style: isEmpty ? { color: "var(--wp-text-subtle)" } : void 0,
95
+ children: isEmpty ? emptyText : value
96
+ }
97
+ ),
98
+ /* @__PURE__ */ jsx(
99
+ IconButton,
100
+ {
101
+ className: "erp-inline-edit__pencil",
102
+ icon: "pencil",
103
+ label: disabled ? "\uC218\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4" : "\uC218\uC815",
104
+ size: "xs",
105
+ variant: "ghost",
106
+ disabled,
107
+ onClick: startEdit
108
+ }
109
+ )
110
+ ] });
111
+ }
112
+ return /* @__PURE__ */ jsxs("span", { className: "erp-inline-edit-editor", children: [
113
+ style,
114
+ /* @__PURE__ */ jsxs("span", { className: "erp-inline-edit-editor__row", children: [
115
+ /* @__PURE__ */ jsx(
116
+ Input,
117
+ {
118
+ size: "sm",
119
+ type,
120
+ mono,
121
+ value: draft,
122
+ autoFocus: true,
123
+ disabled: saving,
124
+ invalid: saveError !== null,
125
+ "aria-label": "\uAC12 \uC218\uC815",
126
+ onFocus: (e) => e.currentTarget.select(),
127
+ onChange: (e) => setDraft(e.target.value),
128
+ onKeyDown: handleKeyDown
129
+ }
130
+ ),
131
+ /* @__PURE__ */ jsx(
132
+ IconButton,
133
+ {
134
+ className: saving ? "erp-inline-edit__spin" : "",
135
+ icon: saving ? "loader-circle" : "check",
136
+ label: saving ? "\uC800\uC7A5 \uC911\u2026" : "\uC800\uC7A5 (Enter)",
137
+ size: "xs",
138
+ variant: "ghost",
139
+ disabled: saving,
140
+ onClick: () => void confirmEdit()
141
+ }
142
+ ),
143
+ /* @__PURE__ */ jsx(
144
+ IconButton,
145
+ {
146
+ icon: "x",
147
+ label: "\uCDE8\uC18C (Esc)",
148
+ size: "xs",
149
+ variant: "ghost",
150
+ disabled: saving,
151
+ onClick: cancelEdit
152
+ }
153
+ )
154
+ ] }),
155
+ saveError !== null ? /* @__PURE__ */ jsx("span", { className: "wp-field__error", role: "alert", children: saveError }) : null
156
+ ] });
157
+ }
158
+
159
+ export { InlineEdit };