@wipco/sdui 0.33.0 → 0.35.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,8 +1,25 @@
1
+ import { useSduiAdapter, hostFetchOf } from './chunk-DKPNFEVU.js';
1
2
  import { Badge, Tag } from './chunk-WIDDHR7L.js';
2
3
  import { boxed } from './chunk-SAN4IX6N.js';
3
- import { useMemo, useState } from 'react';
4
+ import { useRef, useEffect, useMemo, useState } from 'react';
4
5
  import { jsx } from 'react/jsx-runtime';
5
6
 
7
+ function pickSeedValues(payload, valueKey) {
8
+ let rows = [];
9
+ if (Array.isArray(payload)) {
10
+ rows = payload;
11
+ } else if (payload !== null && typeof payload === "object") {
12
+ const body = payload;
13
+ const arrayKeys = Object.keys(body).filter((key) => Array.isArray(body[key]));
14
+ if (arrayKeys.length === 1) rows = body[arrayKeys[0]];
15
+ }
16
+ return rows.flatMap((row) => {
17
+ if (typeof row === "string") return row ? [row] : [];
18
+ if (row === null || typeof row !== "object") return [];
19
+ const value = row[valueKey];
20
+ return typeof value === "string" && value ? [value] : [];
21
+ });
22
+ }
6
23
  function foldOptions(options, valueKey, labelKey) {
7
24
  if (!Array.isArray(options)) return [];
8
25
  return options.flatMap((raw) => {
@@ -15,6 +32,8 @@ function foldOptions(options, valueKey, labelKey) {
15
32
  });
16
33
  }
17
34
  function TagMultiSelectImpl({
35
+ sourceEndpoint,
36
+ keepUnknownValues = true,
18
37
  options,
19
38
  valueKey,
20
39
  labelKey,
@@ -23,10 +42,35 @@ function TagMultiSelectImpl({
23
42
  disabled = false,
24
43
  emptyText = "\uC120\uD0DD\uD560 \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4"
25
44
  }) {
26
- const safeOptions = useMemo(() => foldOptions(options, valueKey, labelKey), [options, valueKey, labelKey]);
45
+ const adapter = useSduiAdapter();
46
+ const onChangeRef = useRef(onChange);
47
+ onChangeRef.current = onChange;
48
+ const seededFor = useRef(null);
49
+ useEffect(() => {
50
+ if (!sourceEndpoint || seededFor.current === sourceEndpoint) return;
51
+ seededFor.current = sourceEndpoint;
52
+ const hostFetch = hostFetchOf(adapter);
53
+ if (!hostFetch) return;
54
+ let alive = true;
55
+ hostFetch(sourceEndpoint, { method: "GET" }).then((payload) => {
56
+ if (!alive) return;
57
+ onChangeRef.current?.(pickSeedValues(payload, valueKey ?? "value"));
58
+ }).catch(() => {
59
+ });
60
+ return () => {
61
+ alive = false;
62
+ };
63
+ }, [sourceEndpoint, adapter, valueKey]);
64
+ const folded = useMemo(() => foldOptions(options, valueKey, labelKey), [options, valueKey, labelKey]);
27
65
  const isControlled = values !== void 0 && typeof onChange === "function";
28
66
  const [internalValues, setInternalValues] = useState(values ?? []);
29
67
  const current = isControlled ? values : internalValues;
68
+ const safeOptions = useMemo(() => {
69
+ if (!keepUnknownValues) return folded;
70
+ const known = new Set(folded.map((option) => option.value));
71
+ const unknown = (current ?? []).filter((value) => value && !known.has(value)).map((value) => ({ value, label: value }));
72
+ return unknown.length > 0 ? [...folded, ...unknown] : folded;
73
+ }, [folded, current, keepUnknownValues]);
30
74
  const currentSet = useMemo(() => new Set(current), [current]);
31
75
  const toggle = (value) => {
32
76
  const set = new Set(currentSet);
@@ -71,4 +115,4 @@ function TagMultiSelectImpl({
71
115
  }
72
116
  var TagMultiSelect = boxed(TagMultiSelectImpl, "TagMultiSelect");
73
117
 
74
- export { TagMultiSelect };
118
+ export { TagMultiSelect, pickSeedValues };
@@ -0,0 +1,159 @@
1
+ import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DKPNFEVU.js';
2
+ import { Select, Button } from './chunk-WIDDHR7L.js';
3
+ import { boxed } from './chunk-SAN4IX6N.js';
4
+ import { useState, useEffect, useMemo } from 'react';
5
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
+
7
+ var EMPTY_VALUE = "__wipco_position_none__";
8
+ var LOADING_VALUE = "__wipco_position_loading__";
9
+ var toNumber = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
10
+ function parseRow(raw) {
11
+ if (typeof raw !== "object" || raw === null) return null;
12
+ const row = raw;
13
+ if (typeof row.name !== "string") return null;
14
+ return {
15
+ name: row.name,
16
+ rankOrder: toNumber(row.rank_order),
17
+ sortOrder: toNumber(row.sort_order),
18
+ // is_active 결측은 true 폴백 — 파서 사정으로 목록이 전부 사라지는 사고 방지(v2와 동일).
19
+ isActive: row.is_active === void 0 || row.is_active === null ? true : row.is_active === true
20
+ };
21
+ }
22
+ async function fetchAllPositions(api, endpoint) {
23
+ const LIMIT = 500;
24
+ const MAX_PAGES = 10;
25
+ const rows = [];
26
+ let offset = 0;
27
+ for (let page = 0; page < MAX_PAGES; page += 1) {
28
+ const sep = endpoint.includes("?") ? "&" : "?";
29
+ const res = await api(`${endpoint}${sep}limit=${LIMIT}&offset=${offset}`, {
30
+ method: "GET"
31
+ });
32
+ const data = Array.isArray(res?.data) ? res.data : [];
33
+ for (const raw of data) {
34
+ const parsed = parseRow(raw);
35
+ if (parsed) rows.push(parsed);
36
+ }
37
+ const total = typeof res?.total === "number" ? res.total : rows.length;
38
+ offset += LIMIT;
39
+ if (data.length === 0 || rows.length >= total) break;
40
+ }
41
+ return rows;
42
+ }
43
+ var inflight = /* @__PURE__ */ new Map();
44
+ function loadPositions(api, endpoint) {
45
+ const pending = inflight.get(endpoint);
46
+ if (pending) return pending;
47
+ const next = fetchAllPositions(api, endpoint).finally(() => {
48
+ inflight.delete(endpoint);
49
+ });
50
+ inflight.set(endpoint, next);
51
+ return next;
52
+ }
53
+ function PositionSelectImpl({
54
+ endpoint = "/job-positions",
55
+ value,
56
+ onChange,
57
+ allowEmpty = false,
58
+ disabled = false,
59
+ size,
60
+ invalid,
61
+ placeholder = "\uC9C1\uC704\uB97C \uC120\uD0DD\uD558\uC138\uC694",
62
+ id
63
+ }) {
64
+ const adapter = useSduiAdapter();
65
+ const api = hostFetchOf(adapter) ?? defaultAdapter.fetch;
66
+ const [positions, setPositions] = useState(null);
67
+ const [failed, setFailed] = useState(false);
68
+ const [attempt, setAttempt] = useState(0);
69
+ useEffect(() => {
70
+ let alive = true;
71
+ loadPositions(api, endpoint).then((rows2) => {
72
+ if (!alive) return;
73
+ setPositions(rows2);
74
+ setFailed(false);
75
+ }).catch(() => {
76
+ if (!alive) return;
77
+ setPositions([]);
78
+ setFailed(true);
79
+ });
80
+ return () => {
81
+ alive = false;
82
+ };
83
+ }, [attempt]);
84
+ const isLoading = positions === null && !failed;
85
+ const rows = useMemo(() => positions ?? [], [positions]);
86
+ const activeByName = useMemo(() => {
87
+ const map = /* @__PURE__ */ new Map();
88
+ for (const position of rows) {
89
+ if (position.name.trim() === "") continue;
90
+ map.set(position.name, (map.get(position.name) ?? false) || position.isActive);
91
+ }
92
+ return map;
93
+ }, [rows]);
94
+ const activeNames = useMemo(() => {
95
+ const sorted = [...rows].sort(
96
+ (a, b) => a.rankOrder - b.rankOrder || a.sortOrder - b.sortOrder || a.name.localeCompare(b.name, "ko")
97
+ );
98
+ const seen = /* @__PURE__ */ new Set();
99
+ const names = [];
100
+ for (const position of sorted) {
101
+ if (!position.isActive) continue;
102
+ if (position.name.trim() === "" || seen.has(position.name)) continue;
103
+ seen.add(position.name);
104
+ names.push(position.name);
105
+ }
106
+ return names;
107
+ }, [rows]);
108
+ const current = typeof value === "string" ? value : "";
109
+ const isOffList = current !== "" && !activeNames.includes(current);
110
+ const suffix = isLoading || failed ? "" : activeByName.has(current) ? " (\uBBF8\uC0AC\uC6A9)" : " (\uBBF8\uB4F1\uB85D \uC9C1\uC704)";
111
+ const options = useMemo(() => {
112
+ const list = [];
113
+ if (allowEmpty) list.push({ value: EMPTY_VALUE, label: "\uC120\uD0DD \uC548 \uD568" });
114
+ if (isOffList) list.push({ value: current, label: `${current}${suffix}` });
115
+ if (isLoading) list.push({ value: LOADING_VALUE, label: "\uBD88\uB7EC\uC624\uB294 \uC911\u2026", disabled: true });
116
+ for (const name of activeNames) list.push({ value: name, label: name });
117
+ return list;
118
+ }, [allowEmpty, isOffList, current, suffix, isLoading, activeNames]);
119
+ return /* @__PURE__ */ jsxs("div", { className: "wp-col", style: { gap: "var(--wp-space-1)" }, children: [
120
+ /* @__PURE__ */ jsx(
121
+ Select,
122
+ {
123
+ id,
124
+ size,
125
+ invalid,
126
+ disabled,
127
+ value: current,
128
+ options,
129
+ placeholder,
130
+ onChange: (event) => {
131
+ const next = event.target.value;
132
+ if (next === LOADING_VALUE) return;
133
+ onChange?.(next === EMPTY_VALUE ? "" : next);
134
+ }
135
+ }
136
+ ),
137
+ !isLoading && failed && /* @__PURE__ */ jsxs("div", { className: "wp-row", style: { alignItems: "center", gap: "var(--wp-space-2)" }, children: [
138
+ /* @__PURE__ */ jsx("span", { className: "wp-caption", style: { color: "var(--wp-text-muted)" }, children: "\uC9C1\uC704 \uBAA9\uB85D\uC744 \uBD88\uB7EC\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4" }),
139
+ /* @__PURE__ */ jsx(
140
+ Button,
141
+ {
142
+ type: "button",
143
+ size: "sm",
144
+ variant: "secondary",
145
+ onClick: () => {
146
+ setPositions(null);
147
+ setFailed(false);
148
+ setAttempt((n) => n + 1);
149
+ },
150
+ children: "\uB2E4\uC2DC \uC2DC\uB3C4"
151
+ }
152
+ )
153
+ ] }),
154
+ !isLoading && !failed && activeNames.length === 0 && !isOffList && /* @__PURE__ */ jsx("span", { className: "wp-caption", style: { color: "var(--wp-text-muted)" }, children: "\uB4F1\uB85D\uB41C \uC9C1\uC704\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uC778\uC0AC > \uC9C1\uC704 \uAD00\uB9AC\uC5D0\uC11C \uB4F1\uB85D\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4." })
155
+ ] });
156
+ }
157
+ var PositionSelect = boxed(PositionSelectImpl, "PositionSelect");
158
+
159
+ export { PositionSelect };
@@ -0,0 +1,167 @@
1
+ import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DKPNFEVU.js';
2
+ import { Spinner, Badge, Icon } from './chunk-WIDDHR7L.js';
3
+ import { boxed } from './chunk-SAN4IX6N.js';
4
+ import { useState, useEffect } from 'react';
5
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
+
7
+ var text = (value) => {
8
+ if (value === null || value === void 0) return "";
9
+ if (typeof value === "string") return value.trim();
10
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
11
+ return "";
12
+ };
13
+ var period = (item, fromKey, toKey) => {
14
+ const from = text(item[fromKey]);
15
+ const to = text(item[toKey]);
16
+ if (!from && !to) return "";
17
+ return `${from || "?"} ~ ${to || "\uD604\uC7AC"}`;
18
+ };
19
+ function summarizeItem(kind, raw) {
20
+ if (typeof raw !== "object" || raw === null) return "";
21
+ const item = raw;
22
+ const parts = [];
23
+ switch (kind) {
24
+ case "degrees":
25
+ parts.push(text(item.institution), text(item.degree), text(item.field_of_study), period(item, "start_date", "end_date"));
26
+ break;
27
+ case "certifications":
28
+ parts.push(text(item.name), text(item.authority), text(item.certification_date));
29
+ break;
30
+ case "careers":
31
+ parts.push(text(item.company), text(item.title) || text(item.duty), period(item, "start_date", "end_date"));
32
+ break;
33
+ case "awards":
34
+ parts.push(text(item.title), text(item.authority), text(item.award_date));
35
+ break;
36
+ case "dispatches":
37
+ parts.push(text(item.destination), text(item.purpose), period(item, "start_date", "end_date"));
38
+ break;
39
+ case "licenses":
40
+ parts.push(text(item.license), text(item.qualification), text(item.grade), text(item.association));
41
+ break;
42
+ default: {
43
+ for (const [key, value] of Object.entries(item)) {
44
+ if (["id", "employee_id", "created_at", "updated_at", "deleted_at"].includes(key)) continue;
45
+ const v = text(value);
46
+ if (v) parts.push(v);
47
+ if (parts.length >= 3) break;
48
+ }
49
+ }
50
+ }
51
+ return parts.filter(Boolean).join(" \xB7 ");
52
+ }
53
+ var noticeStyle = {
54
+ border: "var(--wp-border-w) dashed var(--wp-border-default)",
55
+ borderRadius: "var(--wp-radius-lg)",
56
+ padding: "var(--wp-space-4)",
57
+ textAlign: "center",
58
+ color: "var(--wp-text-muted)"
59
+ };
60
+ var cellStyle = {
61
+ flex: 1,
62
+ minWidth: 0,
63
+ padding: "var(--wp-space-2)",
64
+ borderRadius: "var(--wp-radius-md)",
65
+ background: "var(--wp-surface-sunken)",
66
+ whiteSpace: "pre-wrap",
67
+ overflowWrap: "anywhere"
68
+ };
69
+ function ListCell({ kind, value }) {
70
+ const items = Array.isArray(value) ? value : [];
71
+ return /* @__PURE__ */ jsxs("div", { style: cellStyle, className: "wp-body-sm", children: [
72
+ /* @__PURE__ */ jsxs("span", { className: "wp-caption", style: { color: "var(--wp-text-muted)" }, children: [
73
+ items.length,
74
+ "\uAC74"
75
+ ] }),
76
+ items.map((item, index) => /* @__PURE__ */ jsx("div", { style: { marginTop: "var(--wp-space-1)" }, children: summarizeItem(kind, item) || "(\uB0B4\uC6A9 \uC5C6\uC74C)" }, index))
77
+ ] });
78
+ }
79
+ function ProfileEditDiffCardImpl({
80
+ scalarLabels = {},
81
+ relationLabels = {},
82
+ sourceEndpoint
83
+ }) {
84
+ const adapter = useSduiAdapter();
85
+ const api = hostFetchOf(adapter) ?? defaultAdapter.fetch;
86
+ const [detail, setDetail] = useState(null);
87
+ const [loading, setLoading] = useState(false);
88
+ const [failed, setFailed] = useState(false);
89
+ useEffect(() => {
90
+ if (!sourceEndpoint) return;
91
+ let alive = true;
92
+ setLoading(true);
93
+ setFailed(false);
94
+ api(sourceEndpoint, { method: "GET" }).then((response) => {
95
+ if (!alive) return;
96
+ setDetail(response ?? null);
97
+ setLoading(false);
98
+ }).catch(() => {
99
+ if (!alive) return;
100
+ setDetail(null);
101
+ setFailed(true);
102
+ setLoading(false);
103
+ });
104
+ return () => {
105
+ alive = false;
106
+ };
107
+ }, [sourceEndpoint]);
108
+ if (!sourceEndpoint) {
109
+ return /* @__PURE__ */ jsx("div", { className: "wp-body-sm", style: noticeStyle, children: "sourceEndpoint \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4." });
110
+ }
111
+ if (loading) return /* @__PURE__ */ jsx(Spinner, { size: "sm", label: "\uBCC0\uACBD \uB0B4\uC6A9\uC744 \uBD88\uB7EC\uC624\uB294 \uC911" });
112
+ if (failed || !detail) {
113
+ return /* @__PURE__ */ jsx("div", { className: "wp-body-sm", style: noticeStyle, children: "\uBCC0\uACBD \uB0B4\uC6A9\uC744 \uBD88\uB7EC\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4 \u2014 \uC774\uB825\uCE74\uB4DC \uC218\uC815 \uACB0\uC7AC\uC5D0\uC11C\uB9CC \uD45C\uC2DC\uB429\uB2C8\uB2E4." });
114
+ }
115
+ const payload = detail.request && typeof detail.request.payload === "object" && detail.request.payload !== null ? detail.request.payload : {};
116
+ const current = typeof detail.current === "object" && detail.current !== null ? detail.current : {};
117
+ const keys = Object.keys(payload);
118
+ const applied = Boolean(detail.request?.applied_at);
119
+ if (keys.length === 0) {
120
+ return /* @__PURE__ */ jsx("div", { className: "wp-body-sm", style: noticeStyle, children: "\uC2E0\uCCAD\uB41C \uBCC0\uACBD \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." });
121
+ }
122
+ return /* @__PURE__ */ jsxs("div", { className: "wp-col", style: { gap: "var(--wp-space-3)" }, children: [
123
+ /* @__PURE__ */ jsxs("div", { className: "wp-row", style: { alignItems: "center", gap: "var(--wp-space-2)" }, children: [
124
+ /* @__PURE__ */ jsxs("span", { className: "wp-caption", style: { color: "var(--wp-text-muted)" }, children: [
125
+ "\uBCC0\uACBD \uD56D\uBAA9 ",
126
+ keys.length,
127
+ "\uAC1C \xB7 \uBAA9\uB85D \uD56D\uBAA9(\uD559\uB825\xB7\uC790\uACA9\uC99D \uB4F1)\uC740 \uBAA9\uB85D \uC804\uCCB4\uAC00 \uAD50\uCCB4\uB429\uB2C8\uB2E4"
128
+ ] }),
129
+ applied ? /* @__PURE__ */ jsx(Badge, { tone: "success", children: "\uBC18\uC601 \uC644\uB8CC \u2014 \uBCC0\uACBD \uC804\uC740 \uBC18\uC601 \uC9C1\uC804 \uAC12" }) : null
130
+ ] }),
131
+ /* @__PURE__ */ jsxs("div", { className: "wp-row wp-caption", style: { alignItems: "center", color: "var(--wp-text-muted)" }, children: [
132
+ /* @__PURE__ */ jsx("span", { style: { flex: "none", width: 120 }, children: "\uD56D\uBAA9" }),
133
+ /* @__PURE__ */ jsx("span", { style: { flex: 1 }, children: "\uBCC0\uACBD \uC804" }),
134
+ /* @__PURE__ */ jsx("span", { style: { flex: "none", width: 22 } }),
135
+ /* @__PURE__ */ jsx("span", { style: { flex: 1 }, children: "\uBCC0\uACBD \uD6C4" })
136
+ ] }),
137
+ keys.map((key) => {
138
+ const isList = key in relationLabels || Array.isArray(payload[key]);
139
+ const label = scalarLabels[key] ?? relationLabels[key] ?? key;
140
+ return /* @__PURE__ */ jsxs("div", { className: "wp-row", style: { alignItems: "flex-start", gap: "var(--wp-space-2)" }, children: [
141
+ /* @__PURE__ */ jsx("span", { className: "wp-label", style: { flex: "none", width: 120, paddingTop: "var(--wp-space-2)" }, children: label }),
142
+ isList ? /* @__PURE__ */ jsx(ListCell, { kind: key, value: current[key] }) : /* @__PURE__ */ jsx("span", { className: "wp-body-sm", style: cellStyle, children: text(current[key]) || "-" }),
143
+ /* @__PURE__ */ jsx(
144
+ "span",
145
+ {
146
+ style: {
147
+ flex: "none",
148
+ width: 22,
149
+ display: "inline-flex",
150
+ justifyContent: "center",
151
+ paddingTop: "var(--wp-space-2)",
152
+ color: "var(--wp-text-muted)"
153
+ },
154
+ children: /* @__PURE__ */ jsx(Icon, { name: "arrow-right", size: 14 })
155
+ }
156
+ ),
157
+ isList ? /* @__PURE__ */ jsx(ListCell, { kind: key, value: payload[key] }) : /* @__PURE__ */ jsx("span", { className: "wp-body-sm", style: { ...cellStyle, background: "var(--wp-surface-brand-tint)" }, children: text(payload[key]) || "-" })
158
+ ] }, key);
159
+ })
160
+ ] });
161
+ }
162
+ var ProfileEditDiffCard = boxed(
163
+ ProfileEditDiffCardImpl,
164
+ "ProfileEditDiffCard"
165
+ );
166
+
167
+ export { ProfileEditDiffCard };
@@ -1,4 +1,4 @@
1
- import { TagMultiSelect } from './chunk-QDEZXA34.js';
1
+ import { TagMultiSelect } from './chunk-C7VAWTEP.js';
2
2
  import { ApprovalLineEditor, stepTypeIcon, stepLabel } from './chunk-S4LOH2TU.js';
3
3
  import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DKPNFEVU.js';
4
4
  import { Card, Icon, Input, IconButton, Field, Button, Badge } from './chunk-WIDDHR7L.js';
@@ -51,10 +51,17 @@ function DiffViewImpl({ fields, lists }) {
51
51
  const added = list.added ?? [];
52
52
  const removed = list.removed ?? [];
53
53
  const kept = list.kept ?? [];
54
- const isEmpty = added.length === 0 && removed.length === 0 && kept.length === 0;
54
+ const isReplace = list.before !== void 0 || list.after !== void 0;
55
+ const before = list.before ?? [];
56
+ const after = list.after ?? [];
57
+ const isEmpty = isReplace ? before.length === 0 && after.length === 0 : added.length === 0 && removed.length === 0 && kept.length === 0;
55
58
  return /* @__PURE__ */ jsxs(React.Fragment, { children: [
56
59
  /* @__PURE__ */ jsx("span", { className: "erp-diff__label", children: list.label }),
57
- /* @__PURE__ */ jsx("span", { className: "erp-diff__badges", children: isEmpty ? /* @__PURE__ */ jsx("span", { className: "wp-caption", children: "\uBCC0\uACBD \uC5C6\uC74C" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
60
+ /* @__PURE__ */ jsx("span", { className: "erp-diff__badges", children: isEmpty ? /* @__PURE__ */ jsx("span", { className: "wp-caption", children: "\uBCC0\uACBD \uC5C6\uC74C" }) : isReplace ? /* @__PURE__ */ jsxs(Fragment, { children: [
61
+ before.map((value, i) => /* @__PURE__ */ jsx(Badge, { tone: "neutral", children: value }, `b-${value}-${i}`)),
62
+ /* @__PURE__ */ jsx("span", { className: "erp-diff__arrow", "aria-hidden": "true", children: /* @__PURE__ */ jsx(Icon, { name: "arrow-right", size: 14 }) }),
63
+ after.length === 0 ? /* @__PURE__ */ jsx("span", { className: "wp-caption", children: "\uBE44\uC6C0" }) : after.map((value, i) => /* @__PURE__ */ jsx(Badge, { tone: "success", children: value }, `f-${value}-${i}`))
64
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
58
65
  added.map((value, i) => /* @__PURE__ */ jsxs(Badge, { tone: "success", children: [
59
66
  "+ ",
60
67
  value
package/dist/meta.d.ts CHANGED
@@ -436,6 +436,8 @@ declare const OptionSchemaEditorMeta: SduiComponentMeta;
436
436
  declare const OptionValueFieldsMeta: SduiComponentMeta;
437
437
  declare const StagePeriodEditorMeta: SduiComponentMeta;
438
438
  declare const QRDisplayMeta: SduiComponentMeta;
439
+ declare const PositionSelectMeta: SduiComponentMeta;
440
+ declare const ProfileEditDiffCardMeta: SduiComponentMeta;
439
441
  declare const AutocompleteFieldMeta: SduiComponentMeta;
440
442
  declare const InlineEditMeta: SduiComponentMeta;
441
443
  declare const JsonFieldMeta: SduiComponentMeta;
@@ -472,6 +474,8 @@ declare const widgetComponentMeta: {
472
474
  ApprovalLineEditor: SduiComponentMeta;
473
475
  ApprovalOverridesEditor: SduiComponentMeta;
474
476
  ApprovalStepper: SduiComponentMeta;
477
+ ProfileEditDiffCard: SduiComponentMeta;
478
+ PositionSelect: SduiComponentMeta;
475
479
  QRDisplay: SduiComponentMeta;
476
480
  OptionSchemaEditor: SduiComponentMeta;
477
481
  OptionValueFields: SduiComponentMeta;
@@ -614,4 +618,4 @@ declare function paletteGroups(catalog?: Record<string, SduiComponentMeta>): Arr
614
618
  items: SduiComponentMeta[];
615
619
  }>;
616
620
 
617
- 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, OptionSchemaEditorMeta, OptionValueFieldsMeta, OrgChartBoardMeta, OrgChartMeta, PageHeadMeta, PaginationMeta, PeriodFilterMeta, PermissionMatrixEditorMeta, PrintSheetMeta, ProgressBarMeta, QRDisplayMeta, 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, StagePeriodEditorMeta, 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 };
621
+ 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, OptionSchemaEditorMeta, OptionValueFieldsMeta, OrgChartBoardMeta, OrgChartMeta, PageHeadMeta, PaginationMeta, PeriodFilterMeta, PermissionMatrixEditorMeta, PositionSelectMeta, PrintSheetMeta, ProfileEditDiffCardMeta, ProgressBarMeta, QRDisplayMeta, 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, StagePeriodEditorMeta, 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
@@ -4229,6 +4229,82 @@ var QRDisplayMeta = {
4229
4229
  // 죽어 있었다. resize.ts의 "같은 프롭이면 축을 합친다" 분기를 그때 넣어 참으로 만들었다.
4230
4230
  resize: { kind: "numeric", widthProp: "size", heightProp: "size", unit: "px", axis: "both" }
4231
4231
  };
4232
+ var PositionSelectMeta = {
4233
+ name: "PositionSelect",
4234
+ label: "\uC9C1\uC704 \uC120\uD0DD",
4235
+ category: "input",
4236
+ icon: "circle-user",
4237
+ description: '\uC9C1\uC704 \uB9C8\uC2A4\uD130(/job-positions) \uC140\uB809\uD2B8. \uD65C\uC131 \uC9C1\uC704\uB97C \uC11C\uC5F4(rank_order\u2192sort_order) \uC21C\uC73C\uB85C \uBCF4\uC5EC\uC8FC\uACE0, \uBAA9\uB85D \uBC16 \uD604\uC7AC \uAC12\uC740 " (\uBBF8\uC0AC\uC6A9)"/" (\uBBF8\uB4F1\uB85D \uC9C1\uC704)" \uC811\uBBF8\uC0AC\uB85C \uC5B9\uC5B4 \uBCF4\uC874\uD55C\uB2E4. \uAC12\uC740 employees.grade \uBB38\uC790\uC5F4 \uC0AC\uBCF8 \uADF8\uB300\uB85C\uB2E4.',
4238
+ props: [
4239
+ { name: "value", kind: "string", label: "\uD604\uC7AC \uC9C1\uC704", required: true, bindable: true, description: "\uC9C1\uC704 \uC774\uB984 \uBB38\uC790\uC5F4(employees.grade).", group: "data" },
4240
+ { name: "onChange", kind: "action", label: "\uC9C1\uC704 \uBCC0\uACBD", required: true, eventArg: '\uBC14\uB010 \uC9C1\uC704 \uC774\uB984 \uBB38\uC790\uC5F4 \u2014 "\uC120\uD0DD \uC548 \uD568"\uC740 \uBE48 \uBB38\uC790\uC5F4', group: "behavior" },
4241
+ { name: "allowEmpty", kind: "boolean", label: "\uC120\uD0DD \uC548 \uD568 \uB178\uCD9C", default: false, description: '\uB9E8 \uC704\uC5D0 "\uC120\uD0DD \uC548 \uD568" \uD56D\uBAA9\uC744 \uB178\uCD9C\uD55C\uB2E4. \uC120\uD0DD \uC2DC \uBE48 \uBB38\uC790\uC5F4\uC744 \uD758\uB9B0\uB2E4.', group: "behavior" },
4242
+ { name: "placeholder", kind: "string", label: "\uD50C\uB808\uC774\uC2A4\uD640\uB354", default: "\uC9C1\uC704\uB97C \uC120\uD0DD\uD558\uC138\uC694", group: "content" },
4243
+ { name: "disabled", kind: "boolean", label: "\uBE44\uD65C\uC131", default: false, bindable: true, group: "behavior" },
4244
+ { name: "invalid", kind: "boolean", label: "\uC624\uB958 \uD45C\uC2DC", bindable: true, group: "appearance", advanced: true },
4245
+ { name: "size", kind: "enum", label: "\uD06C\uAE30", options: [{ value: "sm", label: "\uC791\uAC8C" }, { value: "md", label: "\uBCF4\uD1B5" }, { value: "lg", label: "\uD06C\uAC8C" }], group: "appearance", advanced: true },
4246
+ { name: "id", kind: "string", label: "\uC694\uC18C id", group: "behavior", advanced: true },
4247
+ {
4248
+ name: "endpoint",
4249
+ kind: "string",
4250
+ label: "\uC9C1\uC704 \uBAA9\uB85D \uACBD\uB85C",
4251
+ description: "\uC751\uB2F5 {data:[\u2026], total} \uC744 500\uAC74\uC529 \uC218\uC9D1\uD55C\uB2E4. \uAE30\uBCF8 '/job-positions'.",
4252
+ group: "data",
4253
+ advanced: true
4254
+ }
4255
+ ],
4256
+ children: { accepts: false },
4257
+ dom: { element: "div" },
4258
+ // 루트 <div class="wp-col"> (Select + 안내문)
4259
+ // 마운트 즉시 GET /job-positions 1회가 나간다 — RoleTagPicker.sourceEndpoint와 같은 결(캔버스에서는 조용히 실패).
4260
+ preset: {
4261
+ component: "PositionSelect",
4262
+ props: {
4263
+ value: { $state: "empForm.grade", default: "" },
4264
+ onChange: { action: "setState", key: "empForm.grade", fromEvent: true },
4265
+ allowEmpty: true
4266
+ }
4267
+ },
4268
+ resize: { kind: "none" }
4269
+ };
4270
+ var ProfileEditDiffCardMeta = {
4271
+ name: "ProfileEditDiffCard",
4272
+ label: "\uC774\uB825\uCE74\uB4DC \uBCC0\uACBD \uBE44\uAD50",
4273
+ category: "display",
4274
+ icon: "files",
4275
+ description: "\uC774\uB825\uCE74\uB4DC \uC218\uC815 \uC2E0\uCCAD\uC758 \uBCC0\uACBD \uC804/\uD6C4 \uBE44\uAD50 \uCE74\uB4DC(\uD45C\uC2DC \uC804\uC6A9). sourceEndpoint(/employee-profile-edit-requests/{approvalId} \u2014 \uC2E0\uCCAD id \uB610\uB294 \uACB0\uC7AC id)\uB97C \uC2A4\uC2A4\uB85C \uC870\uD68C\uD574 payload\uC640 current\uB97C \uC9DD\uC9C0\uC5B4 \uADF8\uB9B0\uB2E4. \uBAA9\uB85D \uD56D\uBAA9(\uD559\uB825\xB7\uC790\uACA9\uC99D \uB4F1)\uC740 \uC885\uB958 \uB2E8\uC704 \uC804\uCCB4 \uAD50\uCCB4\uB85C \uD45C\uC2DC\uD55C\uB2E4.",
4276
+ props: [
4277
+ { name: "sourceEndpoint", kind: "string", label: "\uC2E0\uCCAD \uC0C1\uC138 \uACBD\uB85C", description: "/employee-profile-edit-requests/{approvalId} \u2014 \uACBD\uB85C\uAC00 \uBC14\uB014 \uB54C\uB9C8\uB2E4 \uC7AC\uC870\uD68C\uD55C\uB2E4(\uC0C1\uD0DC\uB97C \uCFFC\uB9AC\uB85C \uBD99\uC774\uBA74 \uCC98\uB9AC \uC9C1\uD6C4 \uC7AC\uC870\uD68C\uB97C \uC720\uB3C4\uD560 \uC218 \uC788\uB2E4).", group: "data" },
4278
+ {
4279
+ name: "scalarLabels",
4280
+ kind: "object",
4281
+ label: "\uD56D\uBAA9 \uD45C\uC2DC\uBA85",
4282
+ bindable: true,
4283
+ description: "{\uD0A4: \uB77C\uBCA8}. \uC548 \uC8FC\uBA74 \uC11C\uBC84\uAC00 \uC900 \uD0A4\uB97C \uADF8\uB300\uB85C \uBCF4\uC5EC\uC900\uB2E4.",
4284
+ group: "content",
4285
+ advanced: true
4286
+ },
4287
+ {
4288
+ name: "relationLabels",
4289
+ kind: "object",
4290
+ label: "\uBAA9\uB85D \uD56D\uBAA9 \uD45C\uC2DC\uBA85",
4291
+ bindable: true,
4292
+ description: "\uC5EC\uAE30 \uB4F1\uC7A5\uD558\uB294 \uD0A4\uB294 \uC885\uB958 \uB2E8\uC704 \uC804\uCCB4 \uAD50\uCCB4\uB85C \uADF8\uB9B0\uB2E4.",
4293
+ group: "content",
4294
+ advanced: true
4295
+ }
4296
+ ],
4297
+ children: { accepts: false },
4298
+ dom: { element: "div" },
4299
+ // 루트 <div class="wp-col"> (비교 행 목록) 또는 안내문 <div>
4300
+ // 마운트 즉시 GET sourceEndpoint 1회 — 캔버스 샌드박스에서는 조용히 실패하고 안내문만 남는다(RoleTagPicker와 같은 결).
4301
+ preset: {
4302
+ component: "ProfileEditDiffCard",
4303
+ // 빈 props 는 저장 왕복에서 사라진다(편집기 계약) — 프리셋은 실제로 채운다.
4304
+ props: { sourceEndpoint: "" }
4305
+ },
4306
+ resize: { kind: "none" }
4307
+ };
4232
4308
  var AutocompleteFieldMeta = {
4233
4309
  name: "AutocompleteField",
4234
4310
  label: "\uC790\uB3D9\uC644\uC131",
@@ -4600,7 +4676,25 @@ var TagMultiSelectMeta = {
4600
4676
  },
4601
4677
  { name: "onChange", kind: "action", label: "\uBCC0\uACBD", eventArg: "\uC120\uD0DD\uB41C \uAC12 \uBC30\uC5F4", group: "behavior" },
4602
4678
  { name: "emptyText", kind: "string", label: "\uC635\uC158 \uC5C6\uC74C \uBB38\uAD6C", default: "\uC120\uD0DD\uD560 \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4", group: "content" },
4603
- { name: "disabled", kind: "boolean", label: "\uBE44\uD65C\uC131", default: false, bindable: true, group: "behavior" }
4679
+ { name: "disabled", kind: "boolean", label: "\uBE44\uD65C\uC131", default: false, bindable: true, group: "behavior" },
4680
+ {
4681
+ name: "sourceEndpoint",
4682
+ kind: "string",
4683
+ label: "\uD604\uC7AC \uAC12 \uC2DC\uB529 \uACBD\uB85C",
4684
+ bindable: true,
4685
+ description: "\uC8FC\uBA74 \uADF8 \uACBD\uB85C\uB97C \uC77D\uC5B4 \uD604\uC7AC \uAC12\uC744 onChange \uB85C \uD55C \uBC88 \uD758\uB9B0\uB2E4. \uACBD\uB85C\uAC00 \uBC14\uB014 \uB54C\uB9C8\uB2E4 \uB2E4\uC2DC \uC2DC\uB529\uD558\uBBC0\uB85C, \uC5EC\uB294 \uCABD\uC774 \uC5EC\uB294 \uD69F\uC218\uB97C \uBD99\uC774\uBA74 \uC7AC\uC5F4\uB9BC\uC5D0\uB3C4 \uCD08\uAE30\uD654\uB41C\uB2E4.",
4686
+ group: "data",
4687
+ advanced: true
4688
+ },
4689
+ {
4690
+ name: "keepUnknownValues",
4691
+ kind: "boolean",
4692
+ label: "\uBAA9\uB85D \uBC16 \uAC12 \uC720\uC9C0",
4693
+ default: true,
4694
+ description: "\uC800\uC7A5\uB3FC \uC788\uB294\uB370 \uC635\uC158\uC5D0 \uC5C6\uB294 \uAC12\uB3C4 \uD0DC\uADF8\uB85C \uB0A8\uAE34\uB2E4 \u2014 \uB044\uBA74 \uC800\uC7A5 \uC2DC \uC870\uC6A9\uD788 \uC0AC\uB77C\uC9C4\uB2E4.",
4695
+ group: "behavior",
4696
+ advanced: true
4697
+ }
4604
4698
  ],
4605
4699
  children: { accepts: false },
4606
4700
  dom: { element: "div" },
@@ -5041,7 +5135,16 @@ var DiffViewMeta = {
5041
5135
  { name: "label", kind: "string", label: "\uD56D\uBAA9\uBA85", required: true },
5042
5136
  { name: "added", kind: "array", label: "\uCD94\uAC00\uB428", item: [], itemKind: "string" },
5043
5137
  { name: "removed", kind: "array", label: "\uC0AD\uC81C\uB428", item: [], itemKind: "string" },
5044
- { name: "kept", kind: "array", label: "\uC720\uC9C0\uB428", item: [], itemKind: "string" }
5138
+ { name: "kept", kind: "array", label: "\uC720\uC9C0\uB428", item: [], itemKind: "string" },
5139
+ {
5140
+ name: "before",
5141
+ kind: "array",
5142
+ label: "\uBCC0\uACBD \uC804(\uC804\uCCB4 \uAD50\uCCB4)",
5143
+ item: [],
5144
+ itemKind: "string",
5145
+ description: '\uC774 \uCD95\uC744 \uC8FC\uBA74 added/removed/kept \uB300\uC2E0 "before \u2192 after" \uB85C \uADF8\uB9B0\uB2E4 \u2014 \uC885\uB958 \uB2E8\uC704 \uC804\uCCB4 \uAD50\uCCB4.'
5146
+ },
5147
+ { name: "after", kind: "array", label: "\uBCC0\uACBD \uD6C4(\uC804\uCCB4 \uAD50\uCCB4)", item: [], itemKind: "string" }
5045
5148
  ]
5046
5149
  }
5047
5150
  ],
@@ -5649,6 +5752,8 @@ var widgetComponentMeta = {
5649
5752
  ApprovalLineEditor: ApprovalLineEditorMeta,
5650
5753
  ApprovalOverridesEditor: ApprovalOverridesEditorMeta,
5651
5754
  ApprovalStepper: ApprovalStepperMeta,
5755
+ ProfileEditDiffCard: ProfileEditDiffCardMeta,
5756
+ PositionSelect: PositionSelectMeta,
5652
5757
  QRDisplay: QRDisplayMeta,
5653
5758
  OptionSchemaEditor: OptionSchemaEditorMeta,
5654
5759
  OptionValueFields: OptionValueFieldsMeta,
@@ -5716,4 +5821,4 @@ function paletteGroups(catalog = allComponentMeta) {
5716
5821
  })).filter((group) => group.items.length > 0);
5717
5822
  }
5718
5823
 
5719
- 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, OptionSchemaEditorMeta, OptionValueFieldsMeta, OrgChartBoardMeta, OrgChartMeta, PageHeadMeta, PaginationMeta, PeriodFilterMeta, PermissionMatrixEditorMeta, PrintSheetMeta, ProgressBarMeta, QRDisplayMeta, RadioMeta, RankBoardMeta, RecordSheetMeta, RemoteSearchResultsMeta, RoleMemberPickerMeta, RowMeta, SDUI_CATEGORIES, SELF_AXIS_PROPS, SearchResultsMeta, SearchableMultiPickerMeta, SectionMeta, SecureScreenMeta, SegmentedControlMeta, SelectMeta, ShowMeta, SidebarNavMeta, SortHandleMeta, SortableListMeta, SpacerMeta, SpinnerMeta, StackedMeterMeta, StagePeriodEditorMeta, 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 };
5824
+ 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, OptionSchemaEditorMeta, OptionValueFieldsMeta, OrgChartBoardMeta, OrgChartMeta, PageHeadMeta, PaginationMeta, PeriodFilterMeta, PermissionMatrixEditorMeta, PositionSelectMeta, PrintSheetMeta, ProfileEditDiffCardMeta, ProgressBarMeta, QRDisplayMeta, RadioMeta, RankBoardMeta, RecordSheetMeta, RemoteSearchResultsMeta, RoleMemberPickerMeta, RowMeta, SDUI_CATEGORIES, SELF_AXIS_PROPS, SearchResultsMeta, SearchableMultiPickerMeta, SectionMeta, SecureScreenMeta, SegmentedControlMeta, SelectMeta, ShowMeta, SidebarNavMeta, SortHandleMeta, SortableListMeta, SpacerMeta, SpinnerMeta, StackedMeterMeta, StagePeriodEditorMeta, 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 };
@@ -1,5 +1,5 @@
1
- export { ApprovalOverridesEditor } from '../chunk-OR27SRKL.js';
2
- import '../chunk-QDEZXA34.js';
1
+ export { ApprovalOverridesEditor } from '../chunk-G7JKDAS5.js';
2
+ import '../chunk-C7VAWTEP.js';
3
3
  import '../chunk-S4LOH2TU.js';
4
4
  import '../chunk-BD75U73S.js';
5
5
  import '../chunk-EFMKWPZ3.js';
@@ -11,6 +11,17 @@ interface DiffList {
11
11
  added?: string[];
12
12
  removed?: string[];
13
13
  kept?: string[];
14
+ /**
15
+ * **종류 단위 전체 교체**를 그린다 — `before`/`after` 중 하나라도 있으면
16
+ * added/removed/kept 대신 이쪽으로 그린다.
17
+ *
18
+ * 항목별 추가·삭제가 아니라 "이 종류가 통째로 이 목록으로 바뀐다"가 참인 자리가
19
+ * 있다(학력·자격처럼 서버가 종류째 갈아 끼우는 관계). 그걸 added/removed 로 그리면
20
+ * **의미가 달라진다** — 같은 값이 양쪽에 있어도 "유지"가 아니라 "지웠다 다시 넣음"이
21
+ * 서버에서 실제로 일어나기 때문이다.
22
+ */
23
+ before?: string[];
24
+ after?: string[];
14
25
  }
15
26
  interface DiffViewProps {
16
27
  fields?: DiffField[];
@@ -1,3 +1,3 @@
1
- export { DiffView } from '../chunk-LDJ2CFFH.js';
1
+ export { DiffView } from '../chunk-XAYXHY7R.js';
2
2
  import '../chunk-WIDDHR7L.js';
3
3
  import '../chunk-SAN4IX6N.js';
@@ -0,0 +1,44 @@
1
+ import * as React from 'react';
2
+ import { S as SduiBoxProps } from '../layout-B1QrIBm6.js';
3
+
4
+ /**
5
+ * PositionSelect — 직위 마스터(/job-positions) 셀렉트. v2 position-select.tsx 이식.
6
+ *
7
+ * 옵션은 **활성 직위만**, 마스터 서열(rank_order → sort_order → 이름 ko) 순서 그대로다 —
8
+ * 서열이 곧 표시 순서라 가나다 재정렬로 덮으면 안 된다. 같은 이름이 중복되면
9
+ * 첫 등장(서열 높은 쪽)만 남긴다(같은 value 옵션이 둘 생기는 사고 방지).
10
+ *
11
+ * **off-list 값 처리**: 현재 값이 활성 목록에 없으면(미사용 전환·예전 자유 입력) 그 값을
12
+ * 항목으로 얹어 표시를 유지한다 — 안 얹으면 셀렉트가 비어 보이고 저장 시 기존 값이
13
+ * 날아간 것처럼 오해된다(v2가 고친 문제). 접미사: 마스터에 있으나 미사용이면 " (미사용)",
14
+ * 마스터에 아예 없으면 " (미등록 직위)". 로딩/에러 중에는 접미사를 생략한다 —
15
+ * 조회 한 번 실패했다고 멀쩡한 값이 "미등록"으로 낙인찍히면 안 된다.
16
+ *
17
+ * **조회는 매 마운트 1회**(limit 500씩 total까지 페이지 수집) — v2의 60초 모듈 캐시 +
18
+ * 세대 카운터 대신 단순화했다. 직위 관리 화면 변경 뒤 폼 다이얼로그가 다시 열리면
19
+ * (Show 언마운트→재마운트) 새로 읽으므로 invalidate 별도 배선이 필요 없다.
20
+ * 한 화면 다중 인스턴스(등록 폼+필터)의 중복 조회만 모듈 인플라이트 공유로 억제한다.
21
+ *
22
+ * **onChange는 문자열**을 흘린다 — "선택 안 함"은 ''(빈 문자열). v2 계약은 null이었지만
23
+ * 호출부가 전부 null→''로 접어 저장했고, SDUI setState(fromEvent)에는 값 변환 어휘가
24
+ * 없어 그 접기를 위젯이 대신한다. employees.grade는 문자열 사본 컬럼이라 ''가 "미지정"이다.
25
+ */
26
+ interface PositionSelectProps {
27
+ /** 직위 목록 경로. 응답 `{data:[…], total}` 을 500건씩 수집한다. 기본 '/job-positions'. */
28
+ endpoint?: string;
29
+ /** 현재 직위 이름(employees.grade 문자열 사본). */
30
+ value?: string | null;
31
+ /** 바뀐 직위 이름. "선택 안 함"은 ''(빈 문자열). */
32
+ onChange?: (value: string) => void;
33
+ /** "선택 안 함" 항목을 맨 위에 노출한다. 선택 시 onChange(''). */
34
+ allowEmpty?: boolean;
35
+ disabled?: boolean;
36
+ size?: 'sm' | 'md' | 'lg';
37
+ invalid?: boolean;
38
+ placeholder?: string;
39
+ id?: string;
40
+ }
41
+ /** 바깥 style·className을 뿌리에 얹는다 — 자기 축(폭·여백) 통로. */
42
+ declare const PositionSelect: React.FC<PositionSelectProps & SduiBoxProps>;
43
+
44
+ export { PositionSelect, type PositionSelectProps };
@@ -0,0 +1,4 @@
1
+ export { PositionSelect } from '../chunk-D4AILKSJ.js';
2
+ import '../chunk-DKPNFEVU.js';
3
+ import '../chunk-WIDDHR7L.js';
4
+ import '../chunk-SAN4IX6N.js';
@@ -0,0 +1,24 @@
1
+ import { S as SduiBoxProps } from '../layout-B1QrIBm6.js';
2
+ import React__default from 'react';
3
+
4
+ interface ProfileEditDiffCardProps {
5
+ /**
6
+ * 신청 상세 경로 — `/employee-profile-edit-requests/{approvalId}`.
7
+ * 경로가 바뀔 때마다 다시 조회한다.
8
+ */
9
+ sourceEndpoint?: string;
10
+ /**
11
+ * 스칼라 항목의 표시명(`{키: 라벨}`). 안 주면 **키를 그대로 보여준다** —
12
+ * 어떤 키가 오는지는 서버가 정하므로 라이브러리가 미리 알 수 없다.
13
+ */
14
+ scalarLabels?: Record<string, string>;
15
+ /**
16
+ * 목록 항목의 표시명. 여기 등장하는 키는 **종류 단위 전체 교체**로 그린다
17
+ * (`DiffView.lists` 의 before/after 축). 안 주면 값이 배열인 키를 목록으로 본다.
18
+ */
19
+ relationLabels?: Record<string, string>;
20
+ }
21
+ /** 바깥 style·className 을 뿌리에 얹는다 — 자기 축(폭·여백) 통로. */
22
+ declare const ProfileEditDiffCard: React__default.FC<ProfileEditDiffCardProps & SduiBoxProps>;
23
+
24
+ export { ProfileEditDiffCard, type ProfileEditDiffCardProps };
@@ -0,0 +1,4 @@
1
+ export { ProfileEditDiffCard } from '../chunk-FS65US2B.js';
2
+ import '../chunk-DKPNFEVU.js';
3
+ import '../chunk-WIDDHR7L.js';
4
+ import '../chunk-SAN4IX6N.js';
@@ -28,11 +28,39 @@ interface TagMultiSelectProps {
28
28
  disabled?: boolean;
29
29
  /** 옵션이 하나도 없을 때 보여줄 안내 문구. */
30
30
  emptyText?: string;
31
+ /**
32
+ * 현재 값을 읽어 와 `onChange` 로 **한 번** 흘리는 시딩 경로.
33
+ *
34
+ * 다이얼로그가 열릴 때 "지금 배정된 것"을 폼 상태로 채우는 자리다 — 선언으로는
35
+ * 표현할 수 없어(폼의 initial 은 마운트 1회라 늦게 온 값을 못 받는다) 여기서 받는다.
36
+ * 경로가 **바뀔 때마다** 다시 시딩하므로, 여는 쪽이 경로에 여는 횟수를 붙이면
37
+ * 재열림에도 초기화된다.
38
+ *
39
+ * 응답은 배열이거나 `{data|items|results|rows: 배열}` 봉투다. 원소가 객체면
40
+ * `valueKey`(기본 'value')로 값을 뽑는다 — 문자열 배열이면 그대로 쓴다.
41
+ */
42
+ sourceEndpoint?: string;
43
+ /**
44
+ * 옵션 목록에 없는 값도 태그로 남긴다(해제할 수 있게). 기본 켜짐 —
45
+ * 저장돼 있는데 목록에서 사라진 값이 조용히 지워지면 저장 시 유실된다.
46
+ */
47
+ keepUnknownValues?: boolean;
31
48
  }
49
+ /** valueKey/labelKey 계약으로 임의 행을 {value,label}로 접는다 — `normalizeSearchOptions`와 같은 규칙. */
50
+ /**
51
+ * 시딩 응답에서 값 배열을 뽑는다.
52
+ *
53
+ * 봉투는 **이름이 아니라 모양으로** 벗긴다 — 배열 값을 가진 키가 정확히 하나면 그것이
54
+ * 목록이다. 이름 허용목록(`data`·`items`…)을 두면 `{roles:[…]}`·`{members:[…]}` 처럼
55
+ * 자원 이름을 쓰는 서버가 전부 빈 목록이 되고, 결국 소비 앱이 위젯을 하나 더 만들게
56
+ * 된다(FileDropzone 의 업로드 봉투와 같은 판단).
57
+ * 키가 둘 이상이면 벗기지 않는다 — 엉뚱한 배열을 집는 것보다 비는 편이 낫다.
58
+ */
59
+ declare function pickSeedValues(payload: unknown, valueKey: string): string[];
32
60
  /**
33
61
  * 바깥에서 온 `style`·`className`을 뿌리에 얹어 내보낸다 — 자식 축(`grow`·`width` …)이
34
62
  * 이 컴포넌트에서도 동작하게 하는 유일한 통로다. 까닭은 {@link ../boxed.tsx}에 있다.
35
63
  */
36
64
  declare const TagMultiSelect: React.FC<TagMultiSelectProps & SduiBoxProps>;
37
65
 
38
- export { TagMultiSelect, type TagMultiSelectOption, type TagMultiSelectProps };
66
+ export { TagMultiSelect, type TagMultiSelectOption, type TagMultiSelectProps, pickSeedValues };
@@ -1,3 +1,4 @@
1
- export { TagMultiSelect } from '../chunk-QDEZXA34.js';
1
+ export { TagMultiSelect, pickSeedValues } from '../chunk-C7VAWTEP.js';
2
+ import '../chunk-DKPNFEVU.js';
2
3
  import '../chunk-WIDDHR7L.js';
3
4
  import '../chunk-SAN4IX6N.js';
@@ -5,7 +5,7 @@ export { TreePicker } from './TreePicker.js';
5
5
  import { TimelineProps } from './Timeline.js';
6
6
  export { Timeline, TimelineItem } from './Timeline.js';
7
7
  import { TagMultiSelectProps } from './TagMultiSelect.js';
8
- export { TagMultiSelect, TagMultiSelectOption } from './TagMultiSelect.js';
8
+ export { TagMultiSelect, TagMultiSelectOption, pickSeedValues } from './TagMultiSelect.js';
9
9
  import { SummaryTableProps } from './SummaryTable.js';
10
10
  export { SummaryTable, SummaryTableColumn } from './SummaryTable.js';
11
11
  import { StripProps } from './Strip.js';
@@ -26,8 +26,12 @@ import { RankBoardProps } from './RankBoard.js';
26
26
  export { RankBoard } from './RankBoard.js';
27
27
  import { QRDisplayProps } from './QRDisplay.js';
28
28
  export { QRDisplay } from './QRDisplay.js';
29
+ import { ProfileEditDiffCardProps } from './ProfileEditDiffCard.js';
30
+ export { ProfileEditDiffCard } from './ProfileEditDiffCard.js';
29
31
  import { PrintSheetProps } from './PrintSheet.js';
30
32
  export { PrintSheet } from './PrintSheet.js';
33
+ import { PositionSelectProps } from './PositionSelect.js';
34
+ export { PositionSelect } from './PositionSelect.js';
31
35
  import { PeriodFilterProps } from './PeriodFilter.js';
32
36
  export { Period, PeriodFilter, PeriodMode, PeriodModeItem } from './PeriodFilter.js';
33
37
  import { PermissionMatrixEditorProps } from './PermissionMatrixEditor.js';
@@ -92,7 +96,9 @@ declare const widgetComponents: {
92
96
  OrgChartBoard: React.FC<OrgChartBoardProps & SduiBoxProps>;
93
97
  PermissionMatrixEditor: React.FC<PermissionMatrixEditorProps & SduiBoxProps>;
94
98
  PeriodFilter: React.FC<PeriodFilterProps & SduiBoxProps>;
99
+ PositionSelect: React.FC<PositionSelectProps & SduiBoxProps>;
95
100
  PrintSheet: React.FC<PrintSheetProps & SduiBoxProps>;
101
+ ProfileEditDiffCard: React.FC<ProfileEditDiffCardProps & SduiBoxProps>;
96
102
  QRDisplay: React.FC<QRDisplayProps & SduiBoxProps>;
97
103
  RankBoard: React.FC<RankBoardProps & SduiBoxProps>;
98
104
  RecordSheet: React.FC<RecordSheetProps & SduiBoxProps>;
@@ -111,4 +117,4 @@ declare const widgetComponents: {
111
117
  /** 위젯 어휘의 컴포넌트 이름 union — 메타데이터 대조에 쓴다(`@wipco/sdui/meta`). */
112
118
  type SduiWidgetComponentName = keyof typeof widgetComponents;
113
119
 
114
- export { ApprovalLineEditorProps, ApprovalOverridesEditorProps, ApprovalStepperProps, ColumnBoardProps, DiffViewProps, DisclosureProps, EventCalendarProps, ImageLightboxProps, InlineEditProps, JsonFieldProps, LiveClockProps, OptionSchemaEditorProps, OptionValueFieldsProps, OrgChartBoardProps, OrgChartProps, PeriodFilterProps, PermissionMatrixEditorProps, PrintSheetProps, QRDisplayProps, RankBoardProps, RecordSheetProps, RoleMemberPickerProps, type SduiWidgetComponentName, SearchableMultiPickerProps, SecureScreenProps, StackedMeterProps, StagePeriodEditorProps, StripProps, SummaryTableProps, TagMultiSelectProps, TimelineProps, TreePickerProps, TreeViewProps, widgetComponents };
120
+ export { ApprovalLineEditorProps, ApprovalOverridesEditorProps, ApprovalStepperProps, ColumnBoardProps, DiffViewProps, DisclosureProps, EventCalendarProps, ImageLightboxProps, InlineEditProps, JsonFieldProps, LiveClockProps, OptionSchemaEditorProps, OptionValueFieldsProps, OrgChartBoardProps, OrgChartProps, PeriodFilterProps, PermissionMatrixEditorProps, PositionSelectProps, PrintSheetProps, ProfileEditDiffCardProps, QRDisplayProps, RankBoardProps, RecordSheetProps, RoleMemberPickerProps, type SduiWidgetComponentName, SearchableMultiPickerProps, SecureScreenProps, StackedMeterProps, StagePeriodEditorProps, StripProps, SummaryTableProps, TagMultiSelectProps, TimelineProps, TreePickerProps, TreeViewProps, widgetComponents };
@@ -1,3 +1,7 @@
1
+ import { SecureScreen } from '../chunk-IIRENCDS.js';
2
+ export { SecureScreen } from '../chunk-IIRENCDS.js';
3
+ import { StackedMeter } from '../chunk-BMZKZGJF.js';
4
+ export { StackedMeter } from '../chunk-BMZKZGJF.js';
1
5
  import { StagePeriodEditor } from '../chunk-KVFHPBAY.js';
2
6
  export { StagePeriodEditor } from '../chunk-KVFHPBAY.js';
3
7
  import { Strip } from '../chunk-ZH5MLOZS.js';
@@ -10,10 +14,14 @@ import { TreePicker } from '../chunk-U3HYQCFA.js';
10
14
  export { TreePicker } from '../chunk-U3HYQCFA.js';
11
15
  import { TreeView } from '../chunk-X5R2HHEN.js';
12
16
  export { TreeView, buildTreeNodes } from '../chunk-X5R2HHEN.js';
17
+ import { PositionSelect } from '../chunk-D4AILKSJ.js';
18
+ export { PositionSelect } from '../chunk-D4AILKSJ.js';
13
19
  import { PrintSheet } from '../chunk-MYJ2KEIP.js';
14
20
  export { PrintSheet } from '../chunk-MYJ2KEIP.js';
15
21
  import { QRDisplay } from '../chunk-PCMJE2GO.js';
16
22
  export { QRDisplay } from '../chunk-PCMJE2GO.js';
23
+ import { ProfileEditDiffCard } from '../chunk-FS65US2B.js';
24
+ export { ProfileEditDiffCard } from '../chunk-FS65US2B.js';
17
25
  import { RankBoard } from '../chunk-U6H7YN4I.js';
18
26
  export { RankBoard } from '../chunk-U6H7YN4I.js';
19
27
  import { RecordSheet } from '../chunk-K3JBSWFI.js';
@@ -22,10 +30,6 @@ import { RoleMemberPicker } from '../chunk-WXZDYDXG.js';
22
30
  export { RoleMemberPicker } from '../chunk-WXZDYDXG.js';
23
31
  import { SearchableMultiPicker } from '../chunk-C53NGSA3.js';
24
32
  export { SearchableMultiPicker } from '../chunk-C53NGSA3.js';
25
- import { SecureScreen } from '../chunk-IIRENCDS.js';
26
- export { SecureScreen } from '../chunk-IIRENCDS.js';
27
- import { StackedMeter } from '../chunk-BMZKZGJF.js';
28
- export { StackedMeter } from '../chunk-BMZKZGJF.js';
29
33
  import { JsonField } from '../chunk-JVBZRDC2.js';
30
34
  export { JsonField } from '../chunk-JVBZRDC2.js';
31
35
  import { LiveClock } from '../chunk-Z3WGIFJP.js';
@@ -45,8 +49,8 @@ export { PeriodFilter } from '../chunk-INWASR2G.js';
45
49
  export { AutocompleteField, toAutocompleteOptions } from '../chunk-CVCP52OD.js';
46
50
  import { ColumnBoard } from '../chunk-C4JRSZGS.js';
47
51
  export { ColumnBoard } from '../chunk-C4JRSZGS.js';
48
- import { DiffView } from '../chunk-LDJ2CFFH.js';
49
- export { DiffView } from '../chunk-LDJ2CFFH.js';
52
+ import { DiffView } from '../chunk-XAYXHY7R.js';
53
+ export { DiffView } from '../chunk-XAYXHY7R.js';
50
54
  import { Disclosure } from '../chunk-HIZ2OVJA.js';
51
55
  export { Disclosure } from '../chunk-HIZ2OVJA.js';
52
56
  import { EventCalendar } from '../chunk-C3JY6GA3.js';
@@ -60,10 +64,10 @@ import '../chunk-QU7YVMFA.js';
60
64
  import '../chunk-QO6UJAQN.js';
61
65
  import '../chunk-THEP3VWY.js';
62
66
  import '../chunk-SNRKPEWT.js';
63
- import { ApprovalOverridesEditor } from '../chunk-OR27SRKL.js';
64
- export { ApprovalOverridesEditor } from '../chunk-OR27SRKL.js';
65
- import { TagMultiSelect } from '../chunk-QDEZXA34.js';
66
- export { TagMultiSelect } from '../chunk-QDEZXA34.js';
67
+ import { ApprovalOverridesEditor } from '../chunk-G7JKDAS5.js';
68
+ export { ApprovalOverridesEditor } from '../chunk-G7JKDAS5.js';
69
+ import { TagMultiSelect } from '../chunk-C7VAWTEP.js';
70
+ export { TagMultiSelect, pickSeedValues } from '../chunk-C7VAWTEP.js';
67
71
  import { ApprovalLineEditor } from '../chunk-S4LOH2TU.js';
68
72
  export { ApprovalLineEditor } from '../chunk-S4LOH2TU.js';
69
73
  import '../chunk-BD75U73S.js';
@@ -94,7 +98,9 @@ var widgetComponents = {
94
98
  OrgChartBoard,
95
99
  PermissionMatrixEditor,
96
100
  PeriodFilter,
101
+ PositionSelect,
97
102
  PrintSheet,
103
+ ProfileEditDiffCard,
98
104
  QRDisplay,
99
105
  RankBoard,
100
106
  RecordSheet,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipco/sdui",
3
- "version": "0.33.0",
3
+ "version": "0.35.0",
4
4
  "description": "WIPCO 서버 주도 UI(SDUI) 렌더러 — JSON 페이지 스펙을 React 컴포넌트로 렌더링",
5
5
  "keywords": [
6
6
  "sdui",
@@ -116,6 +116,10 @@
116
116
  "types": "./dist/widgets/PeriodFilter.d.ts",
117
117
  "default": "./dist/widgets/PeriodFilter.js"
118
118
  },
119
+ "./widgets/PositionSelect": {
120
+ "types": "./dist/widgets/PositionSelect.d.ts",
121
+ "default": "./dist/widgets/PositionSelect.js"
122
+ },
119
123
  "./widgets/PrintSheet": {
120
124
  "types": "./dist/widgets/PrintSheet.d.ts",
121
125
  "default": "./dist/widgets/PrintSheet.js"
@@ -124,6 +128,10 @@
124
128
  "types": "./dist/widgets/QRDisplay.d.ts",
125
129
  "default": "./dist/widgets/QRDisplay.js"
126
130
  },
131
+ "./widgets/ProfileEditDiffCard": {
132
+ "types": "./dist/widgets/ProfileEditDiffCard.d.ts",
133
+ "default": "./dist/widgets/ProfileEditDiffCard.js"
134
+ },
127
135
  "./widgets/RankBoard": {
128
136
  "types": "./dist/widgets/RankBoard.d.ts",
129
137
  "default": "./dist/widgets/RankBoard.js"