@wipco/sdui 0.50.1 → 1.0.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 (111) hide show
  1. package/dist/{DatePicker-BK_jsV7i.d.ts → DatePicker-C_TT_SOw.d.ts} +25 -0
  2. package/dist/{chunk-OEWTVLPZ.js → chunk-2HLXCKK7.js} +2 -2
  3. package/dist/{chunk-PWVHAUJC.js → chunk-2YP5N4VV.js} +6 -4
  4. package/dist/{chunk-QTHYLNJW.js → chunk-37SFFW37.js} +2 -13
  5. package/dist/{chunk-YQOMSWW5.js → chunk-5WW4MBRO.js} +1 -1
  6. package/dist/{chunk-LIOS3PF2.js → chunk-5XPZ3RSA.js} +3 -4
  7. package/dist/{chunk-ZD3VNVRF.js → chunk-7BGDSSAN.js} +5 -13
  8. package/dist/{chunk-Y2LIEWQX.js → chunk-7FZTUI6U.js} +6 -5
  9. package/dist/{chunk-HJ5VSXR4.js → chunk-7P7N43JV.js} +2 -2
  10. package/dist/{chunk-55VB6SDU.js → chunk-A5XQOEVH.js} +23 -20
  11. package/dist/{chunk-77LKJCMM.js → chunk-A6LAIGUS.js} +20 -108
  12. package/dist/{chunk-YXS5FXVS.js → chunk-AC5TI3BE.js} +7 -16
  13. package/dist/{chunk-AIOJYVJ2.js → chunk-AXGF4M57.js} +1 -1
  14. package/dist/{chunk-5MPK3NZK.js → chunk-B6WMC2AN.js} +3 -3
  15. package/dist/{chunk-C6ZV4VBN.js → chunk-C37U3GCC.js} +4 -4
  16. package/dist/{chunk-265YPW4W.js → chunk-DMLDKW7K.js} +1 -1
  17. package/dist/{chunk-FZ72IDVF.js → chunk-E6ISNQK3.js} +2 -2
  18. package/dist/{chunk-STX2AX6Y.js → chunk-FLAN7CV3.js} +7 -39
  19. package/dist/{chunk-IV5YTVHR.js → chunk-GE6BYNQJ.js} +18 -33
  20. package/dist/{chunk-ZX36LKJ7.js → chunk-GTGS2QRL.js} +7 -6
  21. package/dist/{chunk-NL3I6F2I.js → chunk-HBBTENSV.js} +3 -3
  22. package/dist/{chunk-RUU7WBGR.js → chunk-I45EUZC6.js} +19 -9
  23. package/dist/{chunk-JBKNIALK.js → chunk-I5SD7XRA.js} +9 -6
  24. package/dist/{chunk-X7GHY2RI.js → chunk-IFKYGJXP.js} +14 -42
  25. package/dist/{chunk-J3YMUR6R.js → chunk-IZ3JA2BR.js} +3 -3
  26. package/dist/{chunk-AUW5RP24.js → chunk-JIV4YHCZ.js} +7 -6
  27. package/dist/{chunk-M6KM3FJS.js → chunk-JYAR7D5U.js} +2 -2
  28. package/dist/{chunk-DKPNFEVU.js → chunk-KO6OEY3G.js} +19 -6
  29. package/dist/{chunk-JHHMFL5D.js → chunk-LFEJLOFN.js} +28 -16
  30. package/dist/chunk-MMIGOPO5.js +111 -0
  31. package/dist/{chunk-YSSNP6JM.js → chunk-N25YP7ID.js} +6 -21
  32. package/dist/{chunk-F2W2FWOT.js → chunk-N5RKM6IC.js} +7 -7
  33. package/dist/{chunk-C4HYWZML.js → chunk-O4IISNE7.js} +1 -1
  34. package/dist/chunk-Q4SWUINA.js +51 -0
  35. package/dist/{chunk-TUOG7EEI.js → chunk-RNOUXUBO.js} +418 -22
  36. package/dist/{chunk-4VZI3PLI.js → chunk-RYHEV5E3.js} +15 -39
  37. package/dist/{chunk-AVRNQOB5.js → chunk-SAPBDJJQ.js} +14 -13
  38. package/dist/{chunk-OPJ4DYNI.js → chunk-SJ3737RA.js} +156 -158
  39. package/dist/{chunk-UMANGYRD.js → chunk-TFGNKFL2.js} +1 -1
  40. package/dist/{chunk-KYGSMUUU.js → chunk-VY7MVBSE.js} +2 -2
  41. package/dist/{chunk-VDBUXYEB.js → chunk-WOYMEEPH.js} +8 -8
  42. package/dist/{chunk-I5Z2RVDF.js → chunk-WSLT32Q4.js} +278 -30
  43. package/dist/{chunk-NYUHSMUU.js → chunk-XGFDY43I.js} +2 -2
  44. package/dist/{chunk-ODKZF4IT.js → chunk-YOZN35HD.js} +17 -9
  45. package/dist/{chunk-EKP6Z3G6.js → chunk-YVTV4ZV6.js} +1 -1
  46. package/dist/{chunk-6BCWFPNJ.js → chunk-YWUDBLO6.js} +2 -2
  47. package/dist/chunk-Z4PYMTTS.js +41 -0
  48. package/dist/{chunk-WXIQLWIX.js → chunk-ZFNFPXPR.js} +2 -2
  49. package/dist/{chunk-7GOWOSX6.js → chunk-ZORQPB6B.js} +11 -5
  50. package/dist/index.d.ts +1096 -941
  51. package/dist/index.js +369 -610
  52. package/dist/meta.d.ts +13 -12
  53. package/dist/meta.js +84 -16
  54. package/dist/rich/ExcelUploadCard.d.ts +7 -0
  55. package/dist/rich/ExcelUploadCard.js +17 -13
  56. package/dist/rich.d.ts +1 -1
  57. package/dist/rich.js +25 -12
  58. package/dist/styles.css +10 -2
  59. package/dist/{types-D14eqLpf.d.ts → types-C7y9CpgF.d.ts} +7 -5
  60. package/dist/widgets/ApprovalLineEditor.js +7 -6
  61. package/dist/widgets/ApprovalOverridesEditor.d.ts +11 -1
  62. package/dist/widgets/ApprovalOverridesEditor.js +9 -8
  63. package/dist/widgets/ApprovalStepper.js +3 -3
  64. package/dist/widgets/AutocompleteField.d.ts +1 -0
  65. package/dist/widgets/AutocompleteField.js +7 -6
  66. package/dist/widgets/ColumnBoard.js +3 -3
  67. package/dist/widgets/DiffView.js +3 -3
  68. package/dist/widgets/Disclosure.js +3 -3
  69. package/dist/widgets/EventCalendar.d.ts +16 -1
  70. package/dist/widgets/EventCalendar.js +3 -3
  71. package/dist/widgets/FileDropzone.js +7 -6
  72. package/dist/widgets/ImageLightbox.js +5 -4
  73. package/dist/widgets/InlineEdit.js +3 -3
  74. package/dist/widgets/JsonField.js +3 -3
  75. package/dist/widgets/LiveClock.js +2 -2
  76. package/dist/widgets/OptionSchemaEditor.js +4 -4
  77. package/dist/widgets/OptionValueFields.d.ts +5 -0
  78. package/dist/widgets/OptionValueFields.js +4 -4
  79. package/dist/widgets/OrgChart.js +4 -3
  80. package/dist/widgets/OrgChartBoard.d.ts +93 -4
  81. package/dist/widgets/OrgChartBoard.js +9 -6
  82. package/dist/widgets/PeriodFilter.js +4 -4
  83. package/dist/widgets/PermissionMatrixEditor.d.ts +16 -0
  84. package/dist/widgets/PermissionMatrixEditor.js +4 -4
  85. package/dist/widgets/PositionSelect.d.ts +6 -1
  86. package/dist/widgets/PositionSelect.js +5 -4
  87. package/dist/widgets/PrintSheet.js +3 -3
  88. package/dist/widgets/ProfileEditDiffCard.d.ts +6 -0
  89. package/dist/widgets/ProfileEditDiffCard.js +4 -4
  90. package/dist/widgets/QRDisplay.js +4 -4
  91. package/dist/widgets/RankBoard.d.ts +6 -0
  92. package/dist/widgets/RankBoard.js +6 -5
  93. package/dist/widgets/RecordSheet.js +2 -2
  94. package/dist/widgets/RoleMemberPicker.d.ts +11 -5
  95. package/dist/widgets/RoleMemberPicker.js +5 -4
  96. package/dist/widgets/SearchableMultiPicker.js +5 -5
  97. package/dist/widgets/SecureScreen.d.ts +12 -0
  98. package/dist/widgets/SecureScreen.js +3 -3
  99. package/dist/widgets/StackedMeter.js +2 -2
  100. package/dist/widgets/StagePeriodEditor.js +4 -5
  101. package/dist/widgets/Strip.js +2 -2
  102. package/dist/widgets/SummaryTable.js +2 -2
  103. package/dist/widgets/TagMultiSelect.d.ts +0 -1
  104. package/dist/widgets/TagMultiSelect.js +4 -4
  105. package/dist/widgets/Timeline.js +4 -4
  106. package/dist/widgets/TreePicker.js +6 -6
  107. package/dist/widgets/TreeView.js +5 -5
  108. package/dist/widgets/index.d.ts +3 -3
  109. package/dist/widgets/index.js +81 -79
  110. package/package.json +1 -1
  111. package/dist/chunk-EKVA7T7P.js +0 -198
@@ -1,7 +1,405 @@
1
- import { mergeStyle, containerLayoutStyle, joinClass } from './chunk-YQOMSWW5.js';
1
+ import { mergeStyle, containerLayoutStyle, joinClass } from './chunk-5WW4MBRO.js';
2
2
  import React from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
 
5
+ // src/warn-once.ts
6
+ var warned = /* @__PURE__ */ new Set();
7
+ function warnOnce(key, message, level = "error") {
8
+ if (warned.has(key)) return;
9
+ warned.add(key);
10
+ if (level === "warn") console.warn(message);
11
+ else console.error(message);
12
+ }
13
+
14
+ // src/plain-object.ts
15
+ function isPlainObject(v) {
16
+ return typeof v === "object" && v !== null && !Array.isArray(v);
17
+ }
18
+
19
+ // src/calc.ts
20
+ function toNumber(v) {
21
+ if (typeof v === "number") return v;
22
+ if (typeof v === "string") {
23
+ const n = Number(v.replace(/,/g, ""));
24
+ return Number.isFinite(n) ? n : 0;
25
+ }
26
+ return 0;
27
+ }
28
+ function isNumericText(v) {
29
+ if (typeof v === "number") return true;
30
+ if (typeof v !== "string" || v === "") return false;
31
+ return Number.isFinite(Number(v.replace(/,/g, "")));
32
+ }
33
+ function orderingCompare(a, b) {
34
+ if (typeof a === "string" && typeof b === "string" && !isNumericText(a) && !isNumericText(b)) {
35
+ return a < b ? -1 : a > b ? 1 : 0;
36
+ }
37
+ const x = toNumber(a);
38
+ const y = toNumber(b);
39
+ return x < y ? -1 : x > y ? 1 : 0;
40
+ }
41
+ function looseEquals(a, b) {
42
+ return String(a ?? "") === String(b ?? "");
43
+ }
44
+ function isBlank(v) {
45
+ return v === null || v === void 0 || v === "";
46
+ }
47
+ function isEmptyValue(v) {
48
+ if (isBlank(v)) return true;
49
+ if (Array.isArray(v)) return v.length === 0;
50
+ if (isPlainObject(v)) return Object.keys(v).length === 0;
51
+ return false;
52
+ }
53
+ function fieldOf(row, field) {
54
+ if (!field) return row;
55
+ if (!isPlainObject(row)) return void 0;
56
+ if (Object.hasOwn(row, field)) return row[field];
57
+ if (!field.includes(".")) return void 0;
58
+ let current = row;
59
+ for (const segment of field.split(".")) {
60
+ if (!isPlainObject(current) || !Object.hasOwn(current, segment)) return void 0;
61
+ current = current[segment];
62
+ }
63
+ return current;
64
+ }
65
+ var FIELD_OPS = /* @__PURE__ */ new Set([
66
+ "eq",
67
+ "ne",
68
+ "gt",
69
+ "gte",
70
+ "lt",
71
+ "lte",
72
+ "in",
73
+ "notIn",
74
+ "contains",
75
+ "startsWith",
76
+ "endsWith",
77
+ "isNull",
78
+ "isEmpty",
79
+ "intersects"
80
+ ]);
81
+ function isFieldOpObject(v) {
82
+ return isPlainObject(v) && Object.keys(v).some((key) => FIELD_OPS.has(key));
83
+ }
84
+ function matchesFieldOps(actual, ops, resolve) {
85
+ return Object.entries(ops).every(([op, rawExpected]) => {
86
+ if (!FIELD_OPS.has(op)) return true;
87
+ const expected = resolve(rawExpected);
88
+ switch (op) {
89
+ case "eq":
90
+ return looseEquals(actual, expected);
91
+ case "ne":
92
+ return !looseEquals(actual, expected);
93
+ case "gt":
94
+ return orderingCompare(actual, expected) > 0;
95
+ case "gte":
96
+ return orderingCompare(actual, expected) >= 0;
97
+ case "lt":
98
+ return orderingCompare(actual, expected) < 0;
99
+ case "lte":
100
+ return orderingCompare(actual, expected) <= 0;
101
+ case "in":
102
+ return Array.isArray(expected) && expected.some((item) => looseEquals(actual, item));
103
+ case "notIn":
104
+ return !Array.isArray(expected) || !expected.some((item) => looseEquals(actual, item));
105
+ case "contains":
106
+ return String(actual ?? "").includes(String(expected ?? ""));
107
+ case "startsWith":
108
+ return String(actual ?? "").startsWith(String(expected ?? ""));
109
+ case "endsWith":
110
+ return String(actual ?? "").endsWith(String(expected ?? ""));
111
+ case "isNull":
112
+ return isBlank(actual) === Boolean(expected);
113
+ case "intersects": {
114
+ const mine = Array.isArray(actual) ? actual : [];
115
+ const theirs = Array.isArray(expected) ? expected : [];
116
+ if (mine.length === 0 || theirs.length === 0) return false;
117
+ const set = new Set(theirs.map((item) => String(item ?? "")));
118
+ return mine.some((item) => set.has(String(item ?? "")));
119
+ }
120
+ case "isEmpty":
121
+ return isEmptyValue(actual) === Boolean(expected);
122
+ default:
123
+ return true;
124
+ }
125
+ });
126
+ }
127
+ function matchesWhere(row, where, resolve) {
128
+ if (!isPlainObject(where)) return true;
129
+ return Object.entries(where).every(([key, condition]) => {
130
+ if (key === "AND") {
131
+ return Array.isArray(condition) && condition.every((sub) => matchesWhere(row, sub, resolve));
132
+ }
133
+ if (key === "OR") {
134
+ return Array.isArray(condition) && condition.some((sub) => matchesWhere(row, sub, resolve));
135
+ }
136
+ if (key === "NOT") {
137
+ return !matchesWhere(row, condition, resolve);
138
+ }
139
+ const actual = fieldOf(row, key);
140
+ if (isFieldOpObject(condition)) return matchesFieldOps(actual, condition, resolve);
141
+ return looseEquals(actual, resolve(condition));
142
+ });
143
+ }
144
+ var CALC_OPS = /* @__PURE__ */ new Set([
145
+ // 산술 (기존)
146
+ "sum",
147
+ "sub",
148
+ "mul",
149
+ "div",
150
+ "percent",
151
+ "count",
152
+ // 문자 (기존)
153
+ "concat",
154
+ "encode",
155
+ // 비교·논리 (기존)
156
+ "eq",
157
+ "gt",
158
+ "gte",
159
+ "lt",
160
+ "lte",
161
+ "not",
162
+ // 논리 (신규) — 예전에는 스펙이 eq(concat(eq(a),eq(b)),"falsefalse")로 우회했다
163
+ "and",
164
+ "or",
165
+ // 값 고르기 (신규)
166
+ "if",
167
+ "match",
168
+ "coalesce",
169
+ // 목록·객체 (신규)
170
+ "get",
171
+ "pluck",
172
+ "filter",
173
+ "some",
174
+ "every",
175
+ "includes",
176
+ "distinct",
177
+ "except",
178
+ "groupBy",
179
+ "groupCount",
180
+ "entries",
181
+ "fromEntries",
182
+ "length",
183
+ "join",
184
+ "split",
185
+ "sortBy",
186
+ "map"
187
+ ]);
188
+ function isFieldRef(value) {
189
+ return isPlainObject(value) && "$field" in value;
190
+ }
191
+ function substituteFields(template, row) {
192
+ if (isFieldRef(template)) {
193
+ return template.$field === true ? row : fieldOf(row, String(template.$field)) ?? null;
194
+ }
195
+ if (Array.isArray(template)) return template.map((item) => substituteFields(item, row));
196
+ if (isPlainObject(template)) {
197
+ const nested = isPlainObject(template.$calc) ? template.$calc : null;
198
+ if (nested && nested.op === "map") {
199
+ const inner = {};
200
+ for (const [key, value] of Object.entries(nested)) {
201
+ inner[key] = key === "select" ? value : substituteFields(value, row);
202
+ }
203
+ return { ...template, $calc: inner };
204
+ }
205
+ const out = {};
206
+ for (const [key, value] of Object.entries(template)) out[key] = substituteFields(value, row);
207
+ return out;
208
+ }
209
+ return template;
210
+ }
211
+ function evaluateCalc(calc, resolved, resolve) {
212
+ const op = typeof calc.op === "string" ? calc.op : "";
213
+ const field = typeof calc.field === "string" ? calc.field : void 0;
214
+ const first = resolved[0];
215
+ const list = Array.isArray(first) ? first : void 0;
216
+ const listOrEmpty = () => {
217
+ if (list) return list;
218
+ if (first !== void 0 && first !== null) {
219
+ warnOnce(
220
+ `calc:${op}:list`,
221
+ `[sdui] $calc(${op}): \uCCAB \uC778\uC790\uAC00 \uBC30\uC5F4\uC774 \uC544\uB2D9\uB2C8\uB2E4 \u2014 \uBE48 \uBAA9\uB85D\uC73C\uB85C \uCC98\uB9AC\uD569\uB2C8\uB2E4`
222
+ );
223
+ }
224
+ return [];
225
+ };
226
+ const nums = (v) => {
227
+ if (Array.isArray(v)) return v.map((row) => toNumber(fieldOf(row, field)));
228
+ return [toNumber(v)];
229
+ };
230
+ switch (op) {
231
+ /* ── 산술 ── */
232
+ case "sum":
233
+ return resolved.flatMap(nums).reduce((a, b) => a + b, 0);
234
+ case "count":
235
+ return resolved.reduce(
236
+ (a, v) => a + (Array.isArray(v) ? v.length : v == null ? 0 : 1),
237
+ 0
238
+ );
239
+ case "sub": {
240
+ const items = resolved.map((v) => toNumber(v));
241
+ return items.length === 0 ? 0 : items.reduce((a, b) => a - b);
242
+ }
243
+ case "mul":
244
+ return resolved.map((v) => toNumber(v)).reduce((a, b) => a * b, 1);
245
+ case "div": {
246
+ const [a, b] = resolved.map((v) => toNumber(v));
247
+ return b === 0 ? 0 : a / b;
248
+ }
249
+ case "percent": {
250
+ const [a, b] = resolved.map((v) => toNumber(v));
251
+ return b === 0 ? 0 : Math.round(a / b * 1e3) / 10;
252
+ }
253
+ /* ── 문자 ── */
254
+ case "concat":
255
+ return resolved.map((v) => v == null ? "" : String(v)).join("");
256
+ case "encode":
257
+ return encodeURIComponent(resolved.map((v) => v == null ? "" : String(v)).join(""));
258
+ /* ── 비교·논리 ── */
259
+ case "eq":
260
+ return looseEquals(resolved[0], resolved[1]);
261
+ case "gt":
262
+ return orderingCompare(resolved[0], resolved[1]) > 0;
263
+ case "gte":
264
+ return orderingCompare(resolved[0], resolved[1]) >= 0;
265
+ case "lt":
266
+ return orderingCompare(resolved[0], resolved[1]) < 0;
267
+ case "lte":
268
+ return orderingCompare(resolved[0], resolved[1]) <= 0;
269
+ case "not":
270
+ return !resolved[0];
271
+ case "and":
272
+ return resolved.every(Boolean);
273
+ case "or":
274
+ return resolved.some(Boolean);
275
+ /* ── 값 고르기 ── */
276
+ case "if":
277
+ return resolved[0] ? resolve(calc.then ?? null) : resolve(calc.else ?? null);
278
+ case "match": {
279
+ const key = String(resolved[0] ?? "");
280
+ const cases = isPlainObject(calc.cases) ? calc.cases : {};
281
+ if (Object.hasOwn(cases, key)) return resolve(cases[key]);
282
+ return resolve(calc.default ?? null);
283
+ }
284
+ case "coalesce":
285
+ return resolved.find((v) => !isBlank(v)) ?? null;
286
+ /* ── 목록·객체 ── */
287
+ case "get": {
288
+ const [container, rawKey] = resolved;
289
+ if (Array.isArray(container)) {
290
+ if (typeof rawKey !== "number" && (typeof rawKey !== "string" || rawKey.trim() === "")) {
291
+ return null;
292
+ }
293
+ const index = Number(rawKey);
294
+ if (!Number.isInteger(index) || index < 0 || index >= container.length) return null;
295
+ return container[index] ?? null;
296
+ }
297
+ if (isPlainObject(container)) {
298
+ const key = rawKey == null ? "" : String(rawKey);
299
+ return Object.hasOwn(container, key) ? container[key] ?? null : null;
300
+ }
301
+ return null;
302
+ }
303
+ case "pluck":
304
+ return listOrEmpty().map((row) => fieldOf(row, field)).filter((v) => v !== null && v !== void 0);
305
+ case "filter":
306
+ return listOrEmpty().filter((row) => matchesWhere(row, calc.where, resolve));
307
+ case "some":
308
+ return listOrEmpty().some((row) => matchesWhere(row, calc.where, resolve));
309
+ case "every":
310
+ return listOrEmpty().every((row) => matchesWhere(row, calc.where, resolve));
311
+ case "includes": {
312
+ const needle = resolved[1];
313
+ if (typeof first === "string") return first.includes(String(needle ?? ""));
314
+ return listOrEmpty().some((row) => looseEquals(fieldOf(row, field), needle));
315
+ }
316
+ case "distinct": {
317
+ const seen = /* @__PURE__ */ new Set();
318
+ return listOrEmpty().filter((row) => {
319
+ const key = String(fieldOf(row, field) ?? "");
320
+ if (seen.has(key)) return false;
321
+ seen.add(key);
322
+ return true;
323
+ });
324
+ }
325
+ case "except": {
326
+ const remove = new Set(
327
+ (Array.isArray(resolved[1]) ? resolved[1] : []).map(
328
+ (row) => String(fieldOf(row, field) ?? "")
329
+ )
330
+ );
331
+ return listOrEmpty().filter(
332
+ (row) => !remove.has(String(fieldOf(row, field) ?? ""))
333
+ );
334
+ }
335
+ case "groupBy":
336
+ case "groupCount": {
337
+ const groups = /* @__PURE__ */ new Map();
338
+ for (const row of listOrEmpty()) {
339
+ const key = String(fieldOf(row, field) ?? "");
340
+ const bucket = groups.get(key);
341
+ if (bucket) bucket.push(row);
342
+ else groups.set(key, [row]);
343
+ }
344
+ const out = [...groups.entries()].map(
345
+ ([key, items]) => op === "groupCount" ? { key, count: items.length } : { key, items }
346
+ );
347
+ return out;
348
+ }
349
+ case "entries": {
350
+ if (!isPlainObject(first)) return [];
351
+ return Object.entries(first).map(([key, value]) => ({
352
+ key,
353
+ value
354
+ }));
355
+ }
356
+ case "fromEntries": {
357
+ const out = /* @__PURE__ */ Object.create(null);
358
+ for (const row of listOrEmpty()) {
359
+ if (!isPlainObject(row)) continue;
360
+ const key = row.key;
361
+ if (key === null || key === void 0 || key === "") continue;
362
+ out[String(key)] = row.value ?? null;
363
+ }
364
+ return { ...out };
365
+ }
366
+ case "length":
367
+ if (Array.isArray(first)) return first.length;
368
+ if (typeof first === "string") return first.length;
369
+ return 0;
370
+ case "join": {
371
+ const sep = typeof calc.sep === "string" ? calc.sep : ",";
372
+ return listOrEmpty().map((row) => {
373
+ const value = fieldOf(row, field);
374
+ return value == null ? "" : String(value);
375
+ }).join(sep);
376
+ }
377
+ case "split": {
378
+ const sep = typeof calc.sep === "string" ? calc.sep : ",";
379
+ let parts = String(first ?? "").split(sep);
380
+ if (calc.trim === true) parts = parts.map((part) => part.trim());
381
+ if (calc.take === void 0) return parts;
382
+ const index = toNumber(calc.take);
383
+ const at = index < 0 ? parts.length + index : index;
384
+ return parts[at] ?? null;
385
+ }
386
+ case "map": {
387
+ const template = calc.select;
388
+ if (template === void 0) return listOrEmpty();
389
+ return listOrEmpty().map((row) => resolve(substituteFields(template, row)));
390
+ }
391
+ case "sortBy": {
392
+ const desc = Boolean(calc.desc);
393
+ return [...listOrEmpty()].sort((a, b) => {
394
+ const order = orderingCompare(fieldOf(a, field), fieldOf(b, field));
395
+ return desc ? -order : order;
396
+ });
397
+ }
398
+ default:
399
+ return null;
400
+ }
401
+ }
402
+
5
403
  // src/icons.ts
6
404
  var ICON_NODES = {
7
405
  "activity": [
@@ -687,14 +1085,8 @@ var ICON_NODES = {
687
1085
  { t: "line", x1: "8", x2: "14", y1: "11", y2: "11" }
688
1086
  ]
689
1087
  };
690
-
691
- // src/warn-once.ts
692
- var warned = /* @__PURE__ */ new Set();
693
- function warnOnce(key, message, level = "error") {
694
- if (warned.has(key)) return;
695
- warned.add(key);
696
- if (level === "warn") console.warn(message);
697
- else console.error(message);
1088
+ function asArray(value) {
1089
+ return Array.isArray(value) ? value : [];
698
1090
  }
699
1091
  function Icon({ name, size = 18, strokeWidth = 1.5, className = "", style, title, ...rest }) {
700
1092
  const nodes = Object.hasOwn(ICON_NODES, name) ? ICON_NODES[name] : void 0;
@@ -1000,7 +1392,7 @@ function Tabs({ items = [], value, onChange, size = "md", onChrome = false, clas
1000
1392
  className: joinClass("wp-tabs", size === "lg" && "wp-tabs--lg", onChrome && "wp-tabs--onchrome", className),
1001
1393
  role: "tablist",
1002
1394
  ...rest,
1003
- children: items.map((item) => /* @__PURE__ */ jsxs(
1395
+ children: asArray(items).map((item) => /* @__PURE__ */ jsxs(
1004
1396
  "button",
1005
1397
  {
1006
1398
  type: "button",
@@ -1039,9 +1431,12 @@ function Table({
1039
1431
  if (hover) classes.push("wp-table--hover");
1040
1432
  if (sticky) classes.push("wp-table--sticky");
1041
1433
  if (className) classes.push(className);
1042
- if (!rows.length && empty) return /* @__PURE__ */ jsx(Fragment, { children: empty });
1434
+ const columnList = asArray(columns);
1435
+ const rowList = asArray(rows);
1436
+ const selectedList = asArray(selectedIds);
1437
+ if (!rowList.length && empty) return /* @__PURE__ */ jsx(Fragment, { children: empty });
1043
1438
  return /* @__PURE__ */ jsxs("table", { className: classes.join(" "), ...rest, children: [
1044
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: columns.map((column) => /* @__PURE__ */ jsx("th", { style: { width: column.width, textAlign: column.align === "right" ? "right" : void 0 }, children: column.sortable && onSort ? /* @__PURE__ */ jsxs("button", { type: "button", className: "wp-table__sort", onClick: () => onSort(column.key), children: [
1439
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: columnList.map((column) => /* @__PURE__ */ jsx("th", { style: { width: column.width, textAlign: column.align === "right" ? "right" : void 0 }, children: column.sortable && onSort ? /* @__PURE__ */ jsxs("button", { type: "button", className: "wp-table__sort", onClick: () => onSort(column.key), children: [
1045
1440
  column.header,
1046
1441
  /* @__PURE__ */ jsx(
1047
1442
  Icon,
@@ -1051,18 +1446,18 @@ function Table({
1051
1446
  }
1052
1447
  )
1053
1448
  ] }) : column.header }, column.key)) }) }),
1054
- /* @__PURE__ */ jsx("tbody", { children: rows.map((row, index) => /* @__PURE__ */ jsx(
1449
+ /* @__PURE__ */ jsx("tbody", { children: rowList.map((row, index) => /* @__PURE__ */ jsx(
1055
1450
  "tr",
1056
1451
  {
1057
- "aria-selected": selectedIds.indexOf(row?.[rowKey]) > -1 || void 0,
1452
+ "aria-selected": selectedList.indexOf(row?.[rowKey]) > -1 || void 0,
1058
1453
  onClick: onRowClick ? () => onRowClick(row) : void 0,
1059
1454
  style: onRowClick ? { cursor: "pointer" } : void 0,
1060
- children: columns.map((column) => {
1455
+ children: columnList.map((column) => {
1061
1456
  const cls = [];
1062
1457
  if (column.align === "right") cls.push("wp-table__num");
1063
1458
  if (column.mono) cls.push("wp-table__mono");
1064
1459
  if (column.strong) cls.push("wp-table__strong");
1065
- const raw = row?.[column.key];
1460
+ const raw = column.key ? fieldOf(row, column.key) : void 0;
1066
1461
  let content;
1067
1462
  if (column.render) {
1068
1463
  content = column.render(row, index);
@@ -1224,7 +1619,7 @@ function SegmentedControl({ items = [], value, onChange, size = "md", className
1224
1619
  const classes = ["wp-seg"];
1225
1620
  if (size === "lg") classes.push("wp-seg--lg");
1226
1621
  if (className) classes.push(className);
1227
- return /* @__PURE__ */ jsx("div", { className: classes.join(" "), role: "tablist", ...rest, children: items.map((it) => /* @__PURE__ */ jsxs(
1622
+ return /* @__PURE__ */ jsx("div", { className: classes.join(" "), role: "tablist", ...rest, children: asArray(items).map((it) => /* @__PURE__ */ jsxs(
1228
1623
  "button",
1229
1624
  {
1230
1625
  type: "button",
@@ -1249,8 +1644,9 @@ function Avatar({ name = "", src, shape = "hex", size = "md", tone = "brand", cl
1249
1644
  return /* @__PURE__ */ jsx("span", { className: classes.join(" "), title: name || void 0, ...rest, children: src ? /* @__PURE__ */ jsx("img", { src, alt: name }) : initials });
1250
1645
  }
1251
1646
  function Breadcrumb({ items = [], className = "", ...rest }) {
1252
- return /* @__PURE__ */ jsx("nav", { className: ("wp-crumbs " + className).trim(), "aria-label": "\uC704\uCE58", ...rest, children: items.map((it, i) => {
1253
- const last = i === items.length - 1;
1647
+ const list = asArray(items);
1648
+ return /* @__PURE__ */ jsx("nav", { className: ("wp-crumbs " + className).trim(), "aria-label": "\uC704\uCE58", ...rest, children: list.map((it, i) => {
1649
+ const last = i === list.length - 1;
1254
1650
  return /* @__PURE__ */ jsxs(React.Fragment, { children: [
1255
1651
  last ? /* @__PURE__ */ jsx("span", { className: "wp-crumbs__current", "aria-current": "page", children: it.label }) : /* @__PURE__ */ jsxs("a", { className: "wp-crumbs__item", href: it.href || "#", onClick: (e) => {
1256
1652
  if (!it.href) e.preventDefault();
@@ -1281,9 +1677,9 @@ function SidebarNav({ sections = [], value, onSelect, collapsed = false, light =
1281
1677
  if (collapsed) classes.push("wp-nav--collapsed");
1282
1678
  if (light) classes.push("wp-nav--light");
1283
1679
  if (className) classes.push(className);
1284
- return /* @__PURE__ */ jsx("nav", { className: classes.join(" "), ...rest, children: sections.map((s, si) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
1680
+ return /* @__PURE__ */ jsx("nav", { className: classes.join(" "), ...rest, children: asArray(sections).map((s, si) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
1285
1681
  s.label ? /* @__PURE__ */ jsx("div", { className: "wp-nav__section", children: s.label }) : null,
1286
- (s.items || []).map((it) => /* @__PURE__ */ jsxs(
1682
+ asArray(s.items).map((it) => /* @__PURE__ */ jsxs(
1287
1683
  "a",
1288
1684
  {
1289
1685
  href: it.href || "#",
@@ -1334,4 +1730,4 @@ function Tooltip({ label, shortcut, placement = "top", wrap = false, children, c
1334
1730
  );
1335
1731
  }
1336
1732
 
1337
- 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, warnOnce };
1733
+ export { Alert, Avatar, Badge, Breadcrumb, Button, CALC_OPS, Card, Checkbox, Dialog, EmptyState, Field, ICON_NODES, Icon, IconButton, Input, Pagination, ProgressBar, Radio, SegmentedControl, Select, SidebarNav, Spinner, StatCard, Switch, Table, Tabs, Tag, Textarea, Toast, Tooltip, evaluateCalc, fieldOf, isPlainObject, toNumber, warnOnce };
@@ -1,41 +1,15 @@
1
- import { useSduiDispatch, useSduiCan, SduiDialog } from './chunk-UMANGYRD.js';
2
- import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DKPNFEVU.js';
3
- import { Card, Input, Button, Icon, Badge, IconButton } from './chunk-TUOG7EEI.js';
4
- import { boxed } from './chunk-YQOMSWW5.js';
1
+ import { fetchAllJobPositions } from './chunk-Z4PYMTTS.js';
2
+ import { useSduiDispatch, useSduiCan, SduiDialog } from './chunk-TFGNKFL2.js';
3
+ import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-KO6OEY3G.js';
4
+ import { Card, Input, Button, Icon, Badge, IconButton } from './chunk-RNOUXUBO.js';
5
+ import { boxed } from './chunk-5WW4MBRO.js';
5
6
  import React, { useState, useEffect, useCallback, useMemo } from 'react';
6
7
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
8
 
8
- var PAGE_LIMIT = 500;
9
- var MAX_PAGES = 10;
10
- function toJobPosition(row) {
11
- if (typeof row !== "object" || row === null) return [];
12
- const record = row;
13
- const { id, name } = record;
14
- if (typeof id !== "string" || typeof name !== "string") return [];
15
- const num = (a, b) => typeof a === "number" ? a : typeof b === "number" ? b : 0;
16
- const active = record.is_active ?? record.isActive;
17
- return [
18
- {
19
- id,
20
- name,
21
- rankOrder: num(record.rank_order, record.rankOrder),
22
- sortOrder: num(record.sort_order, record.sortOrder),
23
- isActive: typeof active === "boolean" ? active : true
24
- }
25
- ];
26
- }
9
+ var DELETE_CONFIRM_DEFAULT = "'{name}' \uC9C1\uC704\uB97C \uC0AD\uC81C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?";
27
10
  async function fetchAllPositions(api, endpoint) {
28
- const all = [];
29
- for (let page = 0; page < MAX_PAGES; page += 1) {
30
- const body = await api(`${endpoint}?limit=${PAGE_LIMIT}&offset=${page * PAGE_LIMIT}`, {
31
- method: "GET"
32
- });
33
- const rows = Array.isArray(body?.data) ? body.data : [];
34
- all.push(...rows.flatMap(toJobPosition));
35
- const total = typeof body?.total === "number" ? body.total : null;
36
- if (rows.length === 0 || total === null || all.length >= total) break;
37
- }
38
- return all;
11
+ const rows = await fetchAllJobPositions(api, endpoint);
12
+ return rows.flatMap((row) => row.id ? [{ ...row, id: row.id }] : []);
39
13
  }
40
14
  function groupByRank(positions) {
41
15
  const map = /* @__PURE__ */ new Map();
@@ -112,7 +86,8 @@ function RankBoardImpl({
112
86
  entity = "job_position",
113
87
  canCreate: canCreateProp,
114
88
  canUpdate: canUpdateProp,
115
- canDelete: canDeleteProp
89
+ canDelete: canDeleteProp,
90
+ deleteConfirmMessage
116
91
  }) {
117
92
  const dispatch = useSduiDispatch();
118
93
  const can = useSduiCan();
@@ -377,10 +352,11 @@ function RankBoardImpl({
377
352
  );
378
353
  };
379
354
  const handleDelete = (position) => {
380
- if (!window.confirm(
381
- `'${position.name}' \uC9C1\uC704\uB97C \uC0AD\uC81C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? \uC0AD\uC81C\uB41C \uC9C1\uC704\uB294 \uC124\uC815 > \uD734\uC9C0\uD1B5\uC5D0\uC11C \uBCF5\uAD6C\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`
382
- ))
383
- return;
355
+ const message = (deleteConfirmMessage || DELETE_CONFIRM_DEFAULT).replaceAll(
356
+ "{name}",
357
+ position.name
358
+ );
359
+ if (!window.confirm(message)) return;
384
360
  void runMutation(
385
361
  () => api(`${endpoint}/${encodeURIComponent(position.id)}`, {
386
362
  method: "DELETE"
@@ -1,5 +1,6 @@
1
- import { IconButton } from './chunk-TUOG7EEI.js';
2
- import { useState, useRef, useCallback, useEffect } from 'react';
1
+ import { useOverlayLayer, OverlayContainerContext, getOverlayRoot } from './chunk-XQPTSEJC.js';
2
+ import { IconButton } from './chunk-RNOUXUBO.js';
3
+ import { useState, useRef, useCallback, useEffect, useContext } from 'react';
3
4
  import { createPortal } from 'react-dom';
4
5
  import { jsxs, jsx } from 'react/jsx-runtime';
5
6
 
@@ -14,6 +15,7 @@ function clamp(value, min, max) {
14
15
  function ImageViewer({ src, alt, open, onClose }) {
15
16
  const [view, setView] = useState(INITIAL_VIEW);
16
17
  const [dragging, setDragging] = useState(false);
18
+ const rootRef = useRef(null);
17
19
  const stageRef = useRef(null);
18
20
  const imgRef = useRef(null);
19
21
  const viewRef = useRef(view);
@@ -69,22 +71,15 @@ function ImageViewer({ src, alt, open, onClose }) {
69
71
  useEffect(() => {
70
72
  if (open) setView(INITIAL_VIEW);
71
73
  }, [open]);
74
+ useOverlayLayer(open, close, rootRef);
72
75
  useEffect(() => {
73
76
  if (!open) return;
74
- const onKeyDown = (event) => {
75
- if (event.key === "Escape") {
76
- event.stopPropagation();
77
- close();
78
- }
79
- };
80
- document.addEventListener("keydown", onKeyDown);
81
77
  const previousOverflow = document.body.style.overflow;
82
78
  document.body.style.overflow = "hidden";
83
79
  return () => {
84
- document.removeEventListener("keydown", onKeyDown);
85
80
  document.body.style.overflow = previousOverflow;
86
81
  };
87
- }, [open, close]);
82
+ }, [open]);
88
83
  useEffect(() => {
89
84
  if (!open) return;
90
85
  const stage = stageRef.current;
@@ -140,13 +135,17 @@ function ImageViewer({ src, alt, open, onClose }) {
140
135
  if (dragRef.current?.moved) return;
141
136
  if (event.target === event.currentTarget) close();
142
137
  };
138
+ const container = useContext(OverlayContainerContext);
143
139
  const zoomPercent = Math.round(view.scale * 100);
144
140
  const isInitial = view.scale === 1 && view.x === 0 && view.y === 0;
145
141
  if (!open || typeof document === "undefined") return null;
142
+ const portalRoot = container ?? getOverlayRoot();
143
+ if (!portalRoot) return null;
146
144
  return createPortal(
147
145
  /* @__PURE__ */ jsxs(
148
146
  "div",
149
147
  {
148
+ ref: rootRef,
150
149
  role: "dialog",
151
150
  "aria-modal": "true",
152
151
  "aria-label": alt ?? "\uC774\uBBF8\uC9C0 \uBCF4\uAE30",
@@ -154,7 +153,9 @@ function ImageViewer({ src, alt, open, onClose }) {
154
153
  position: "fixed",
155
154
  inset: 0,
156
155
  zIndex: "var(--wp-z-dialog)",
157
- background: "var(--wp-surface-scrim)"
156
+ background: "var(--wp-surface-scrim)",
157
+ // 오버레이 루트가 pointer-events:none이다(styles.css) — 겹이 스스로 살린다
158
+ pointerEvents: "auto"
158
159
  },
159
160
  onClick: (event) => event.stopPropagation(),
160
161
  onPointerDown: (event) => event.stopPropagation(),
@@ -268,7 +269,7 @@ function ImageViewer({ src, alt, open, onClose }) {
268
269
  ]
269
270
  }
270
271
  ),
271
- document.body
272
+ portalRoot
272
273
  );
273
274
  }
274
275