@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,541 @@
1
+ import { Tag, Input, Button, IconButton, Field, Checkbox } from './chunk-ZUCI34QY.js';
2
+ import { useRef, useEffect, useState, useMemo } from 'react';
3
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
+
5
+ function Popover({
6
+ open,
7
+ onClose,
8
+ trigger,
9
+ footer,
10
+ alignLeft = true,
11
+ width,
12
+ children
13
+ }) {
14
+ const hostRef = useRef(null);
15
+ const closeRef = useRef(onClose);
16
+ closeRef.current = onClose;
17
+ useEffect(() => {
18
+ if (!open) return;
19
+ const closeOnOutside = (event) => {
20
+ if (event.target instanceof Node && hostRef.current?.contains(event.target)) return;
21
+ closeRef.current();
22
+ };
23
+ const closeOnEscape = (event) => {
24
+ if (event.key === "Escape") closeRef.current();
25
+ };
26
+ document.addEventListener("pointerdown", closeOnOutside);
27
+ document.addEventListener("keydown", closeOnEscape);
28
+ return () => {
29
+ document.removeEventListener("pointerdown", closeOnOutside);
30
+ document.removeEventListener("keydown", closeOnEscape);
31
+ };
32
+ }, [open]);
33
+ return (
34
+ // 트리거와 패널을 함께 감싸는 기준 상자 — .erp-popover-host의 position:relative가
35
+ // 패널 absolute의 기준이 된다
36
+ /* @__PURE__ */ jsxs("div", { ref: hostRef, className: "erp-popover-host", children: [
37
+ trigger,
38
+ open ? (
39
+ // 기본은 오른쪽 기준(.erp-popover), --left가 붙으면 왼쪽 기준으로 뒤집힌다
40
+ /* @__PURE__ */ jsxs(
41
+ "div",
42
+ {
43
+ className: alignLeft ? "erp-popover erp-popover--left" : "erp-popover",
44
+ style: width ? { width } : void 0,
45
+ children: [
46
+ /* @__PURE__ */ jsx("div", { className: "erp-popover__body", children }),
47
+ footer ? /* @__PURE__ */ jsx("div", { className: "erp-popover__foot", children: footer }) : null
48
+ ]
49
+ }
50
+ )
51
+ ) : null
52
+ ] })
53
+ );
54
+ }
55
+ function splitMultiValue(value) {
56
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
57
+ }
58
+ function joinMultiValue(values) {
59
+ return Array.from(new Set(values.map((item) => item.trim()).filter(Boolean))).sort().join(",");
60
+ }
61
+ function normalizeSearchText(value) {
62
+ return value.toLowerCase().replace(/\s+/g, "");
63
+ }
64
+ function includesLooseSearch(text, keyword) {
65
+ const normalizedKeyword = normalizeSearchText(keyword);
66
+ return normalizedKeyword.length === 0 || normalizeSearchText(text).includes(normalizedKeyword);
67
+ }
68
+ function searchFieldKeys(fields) {
69
+ return fields.flatMap((field) => {
70
+ if (field.type === "dateRange") {
71
+ return [field.fromKey ?? `${field.key}From`, field.toKey ?? `${field.key}To`];
72
+ }
73
+ if (field.type === "numberRange") {
74
+ return [field.fromKey ?? `${field.key}Min`, field.toKey ?? `${field.key}Max`];
75
+ }
76
+ return [field.key];
77
+ });
78
+ }
79
+ function searchValuesFromParams(fields, params) {
80
+ const values = {};
81
+ for (const key of searchFieldKeys(fields)) {
82
+ const value = params.get(key);
83
+ if (value) values[key] = value;
84
+ }
85
+ return values;
86
+ }
87
+ function cleanSearchValues(fields, values) {
88
+ const allowed = new Set(searchFieldKeys(fields));
89
+ const fieldsByKey = new Map(fields.map((field) => [field.key, field]));
90
+ return Object.fromEntries(
91
+ Object.entries(values).map(([key, value]) => {
92
+ const field = fieldsByKey.get(key);
93
+ const cleaned = field?.type === "select" || field?.type === "cascading" ? joinMultiValue(splitMultiValue(value)) : value.trim();
94
+ return [key, cleaned];
95
+ }).filter(([key, value]) => allowed.has(key) && value)
96
+ );
97
+ }
98
+ function cascadingPathMap(options) {
99
+ const paths = /* @__PURE__ */ new Map();
100
+ const visit = (option, parents) => {
101
+ const path = [...parents, option];
102
+ paths.set(option.value, path);
103
+ option.children?.forEach((child) => visit(child, path));
104
+ };
105
+ options.forEach((option) => visit(option, []));
106
+ return paths;
107
+ }
108
+ function cascadingValuePaths(options) {
109
+ const map = /* @__PURE__ */ new Map();
110
+ for (const [value, path] of cascadingPathMap(options)) {
111
+ map.set(
112
+ value,
113
+ path.map((option) => option.value)
114
+ );
115
+ }
116
+ return map;
117
+ }
118
+ function cascadingPathLabel(pathMap, value) {
119
+ const path = pathMap.get(value);
120
+ return path?.length ? path.map((option) => option.label).join(" > ") : value;
121
+ }
122
+ function activeSearchCriteria(fields, values) {
123
+ const criteria = [];
124
+ for (const field of fields) {
125
+ if (field.type === "select") {
126
+ for (const value2 of splitMultiValue(values[field.key] ?? "")) {
127
+ const label = field.options.find((o) => o.value === value2)?.label ?? value2;
128
+ criteria.push({
129
+ key: `${field.key}:${value2}`,
130
+ label: `${field.label}: ${label}`,
131
+ clearKeys: [field.key],
132
+ valueKey: field.key,
133
+ value: value2
134
+ });
135
+ }
136
+ continue;
137
+ }
138
+ if (field.type === "cascading") {
139
+ const pathMap = cascadingPathMap(field.options);
140
+ for (const value2 of splitMultiValue(values[field.key] ?? "")) {
141
+ criteria.push({
142
+ key: `${field.key}:${value2}`,
143
+ label: `${field.label}: ${cascadingPathLabel(pathMap, value2)}`,
144
+ clearKeys: [field.key],
145
+ valueKey: field.key,
146
+ value: value2
147
+ });
148
+ }
149
+ continue;
150
+ }
151
+ if (field.type === "dateRange" || field.type === "numberRange") {
152
+ const fromKey = field.fromKey ?? `${field.key}${field.type === "dateRange" ? "From" : "Min"}`;
153
+ const toKey = field.toKey ?? `${field.key}${field.type === "dateRange" ? "To" : "Max"}`;
154
+ const from = values[fromKey];
155
+ const to = values[toKey];
156
+ if (from || to) {
157
+ criteria.push({
158
+ key: field.key,
159
+ label: `${field.label}: ${from ?? ""}~${to ?? ""}`,
160
+ clearKeys: [fromKey, toKey]
161
+ });
162
+ }
163
+ continue;
164
+ }
165
+ const value = values[field.key];
166
+ if (value) {
167
+ criteria.push({
168
+ key: field.key,
169
+ label: `${field.label}: ${value}`,
170
+ clearKeys: [field.key]
171
+ });
172
+ }
173
+ }
174
+ return criteria;
175
+ }
176
+ var EMPTY_FIELDS = [];
177
+ var EMPTY_VALUES = {};
178
+ function SearchToolbar({
179
+ search,
180
+ onSearchChange,
181
+ onSearch,
182
+ advancedFields = EMPTY_FIELDS,
183
+ advancedValues = EMPTY_VALUES,
184
+ placeholder = "\uAC80\uC0C9",
185
+ className,
186
+ hideSearchInput = false
187
+ }) {
188
+ const [open, setOpen] = useState(false);
189
+ const [draft, setDraft] = useState(advancedValues);
190
+ const hasAdvanced = advancedFields.length > 0;
191
+ useEffect(() => {
192
+ if (!open) setDraft(advancedValues);
193
+ }, [advancedValues, open]);
194
+ const activeCriteria = useMemo(
195
+ () => activeSearchCriteria(advancedFields, advancedValues),
196
+ [advancedFields, advancedValues]
197
+ );
198
+ const submit = (event) => {
199
+ event.preventDefault();
200
+ onSearch(search.trim(), cleanSearchValues(advancedFields, advancedValues));
201
+ setOpen(false);
202
+ };
203
+ const applyAdvancedSearch = () => {
204
+ onSearch(search.trim(), cleanSearchValues(advancedFields, draft));
205
+ setOpen(false);
206
+ };
207
+ const resetAdvancedSearch = () => {
208
+ setDraft({});
209
+ onSearch(search.trim(), {});
210
+ setOpen(false);
211
+ };
212
+ const removeCriterion = (criterion) => {
213
+ const nextValues = { ...advancedValues };
214
+ if (criterion.valueKey && criterion.value !== void 0) {
215
+ const remaining = splitMultiValue(nextValues[criterion.valueKey] ?? "").filter(
216
+ (value) => value !== criterion.value
217
+ );
218
+ if (remaining.length > 0) nextValues[criterion.valueKey] = joinMultiValue(remaining);
219
+ else delete nextValues[criterion.valueKey];
220
+ } else {
221
+ criterion.clearKeys.forEach((key) => delete nextValues[key]);
222
+ }
223
+ setDraft(nextValues);
224
+ onSearch(search.trim(), cleanSearchValues(advancedFields, nextValues));
225
+ };
226
+ return /* @__PURE__ */ jsxs("form", { className: ["wp-row", className].filter(Boolean).join(" "), onSubmit: submit, children: [
227
+ activeCriteria.length > 0 ? /* @__PURE__ */ jsx("div", { className: "wp-row", style: { flexWrap: "wrap" }, children: activeCriteria.map((criterion) => /* @__PURE__ */ jsx(Tag, { onRemove: () => removeCriterion(criterion), children: criterion.label }, criterion.key)) }) : null,
228
+ !hideSearchInput ? /* @__PURE__ */ jsx(
229
+ Input,
230
+ {
231
+ icon: "search",
232
+ value: search,
233
+ onChange: (event) => onSearchChange(event.target.value),
234
+ placeholder
235
+ }
236
+ ) : null,
237
+ hasAdvanced ? /* @__PURE__ */ jsx(
238
+ Popover,
239
+ {
240
+ open,
241
+ onClose: () => setOpen(false),
242
+ alignLeft: false,
243
+ width: "min(560px, 80vw)",
244
+ trigger: (
245
+ // 검색 입력창 옆에 나란히 서는 자리라 테두리가 있어야 한 덩어리로 읽힌다.
246
+ // 열린 상태는 인라인 배경이 아니라 pressed(aria-pressed 틴트)로 알린다.
247
+ /* @__PURE__ */ jsx(
248
+ IconButton,
249
+ {
250
+ type: "button",
251
+ icon: "sliders-horizontal",
252
+ label: "\uC0C1\uC138\uAC80\uC0C9",
253
+ variant: "outlined",
254
+ pressed: open,
255
+ "aria-expanded": open,
256
+ onClick: () => setOpen((value) => !value)
257
+ }
258
+ )
259
+ ),
260
+ footer: /* @__PURE__ */ jsxs(Fragment, { children: [
261
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", icon: "refresh-cw", onClick: resetAdvancedSearch, children: "\uCD08\uAE30\uD654" }),
262
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "primary", size: "sm", onClick: applyAdvancedSearch, children: "\uC801\uC6A9" })
263
+ ] }),
264
+ children: /* @__PURE__ */ jsx("div", { className: "wp-grid wp-grid--2", children: advancedFields.map((field) => /* @__PURE__ */ jsx(
265
+ SearchFieldInput,
266
+ {
267
+ field,
268
+ values: draft,
269
+ onChange: (patch) => setDraft((current) => ({ ...current, ...patch }))
270
+ },
271
+ field.key
272
+ )) })
273
+ }
274
+ ) : null
275
+ ] });
276
+ }
277
+ function SearchFieldInput({
278
+ field,
279
+ values,
280
+ onChange
281
+ }) {
282
+ if (field.type === "select") {
283
+ return /* @__PURE__ */ jsx(Field, { label: field.label, children: /* @__PURE__ */ jsx(MultiSelectInput, { field, values, onChange }) });
284
+ }
285
+ if (field.type === "cascading") {
286
+ return /* @__PURE__ */ jsx(Field, { label: field.label, children: /* @__PURE__ */ jsx(CascadingInput, { field, values, onChange }) });
287
+ }
288
+ if (field.type === "dateRange") {
289
+ const fromKey = field.fromKey ?? `${field.key}From`;
290
+ const toKey = field.toKey ?? `${field.key}To`;
291
+ return /* @__PURE__ */ jsx(Field, { label: field.label, children: /* @__PURE__ */ jsxs("div", { className: "wp-row", children: [
292
+ /* @__PURE__ */ jsx(
293
+ Input,
294
+ {
295
+ type: "date",
296
+ mono: true,
297
+ value: values[fromKey] ?? "",
298
+ onChange: (e) => onChange({ [fromKey]: e.target.value })
299
+ }
300
+ ),
301
+ /* @__PURE__ */ jsx("span", { className: "wp-caption", children: "~" }),
302
+ /* @__PURE__ */ jsx(
303
+ Input,
304
+ {
305
+ type: "date",
306
+ mono: true,
307
+ value: values[toKey] ?? "",
308
+ onChange: (e) => onChange({ [toKey]: e.target.value })
309
+ }
310
+ )
311
+ ] }) });
312
+ }
313
+ if (field.type === "numberRange") {
314
+ const fromKey = field.fromKey ?? `${field.key}Min`;
315
+ const toKey = field.toKey ?? `${field.key}Max`;
316
+ return /* @__PURE__ */ jsx(Field, { label: field.label, children: /* @__PURE__ */ jsxs("div", { className: "wp-row", children: [
317
+ /* @__PURE__ */ jsx(
318
+ Input,
319
+ {
320
+ type: "number",
321
+ mono: true,
322
+ placeholder: field.minPlaceholder ?? "\uCD5C\uC18C",
323
+ value: values[fromKey] ?? "",
324
+ onChange: (e) => onChange({ [fromKey]: e.target.value })
325
+ }
326
+ ),
327
+ /* @__PURE__ */ jsx("span", { className: "wp-caption", children: "~" }),
328
+ /* @__PURE__ */ jsx(
329
+ Input,
330
+ {
331
+ type: "number",
332
+ mono: true,
333
+ placeholder: field.maxPlaceholder ?? "\uCD5C\uB300",
334
+ value: values[toKey] ?? "",
335
+ onChange: (e) => onChange({ [toKey]: e.target.value })
336
+ }
337
+ )
338
+ ] }) });
339
+ }
340
+ return /* @__PURE__ */ jsx(Field, { label: field.label, children: /* @__PURE__ */ jsx(
341
+ Input,
342
+ {
343
+ placeholder: field.placeholder,
344
+ value: values[field.key] ?? "",
345
+ onChange: (e) => onChange({ [field.key]: e.target.value })
346
+ }
347
+ ) });
348
+ }
349
+ function multiSelectLabel(field, values) {
350
+ if (values.length === 0) return field.allLabel ?? "\uC804\uCCB4";
351
+ const labels = values.map((value) => field.options.find((item) => item.value === value)?.label ?? value);
352
+ if (labels.length <= 2) return labels.join(", ");
353
+ return `${labels[0]} \uC678 ${labels.length - 1}`;
354
+ }
355
+ function MultiSelectInput({
356
+ field,
357
+ values,
358
+ onChange
359
+ }) {
360
+ const [open, setOpen] = useState(false);
361
+ const selected = splitMultiValue(values[field.key] ?? "");
362
+ const toggle = (value, checked) => {
363
+ const next = checked ? [...selected, value] : selected.filter((item) => item !== value);
364
+ onChange({ [field.key]: joinMultiValue(next) });
365
+ };
366
+ return /* @__PURE__ */ jsx(
367
+ Popover,
368
+ {
369
+ open,
370
+ onClose: () => setOpen(false),
371
+ trigger: /* @__PURE__ */ jsx(
372
+ Button,
373
+ {
374
+ type: "button",
375
+ variant: "secondary",
376
+ block: true,
377
+ iconEnd: "chevron-down",
378
+ onClick: () => setOpen((value) => !value),
379
+ children: multiSelectLabel(field, selected)
380
+ }
381
+ ),
382
+ children: /* @__PURE__ */ jsx("div", { className: "wp-col", children: field.options.map((option) => /* @__PURE__ */ jsx(
383
+ Checkbox,
384
+ {
385
+ label: option.label,
386
+ checked: selected.includes(option.value),
387
+ onChange: (e) => toggle(option.value, e.target.checked)
388
+ },
389
+ option.value
390
+ )) })
391
+ }
392
+ );
393
+ }
394
+ function uniqueCascadingOptions(options) {
395
+ const seen = /* @__PURE__ */ new Set();
396
+ return options.filter((option) => {
397
+ if (seen.has(option.value)) return false;
398
+ seen.add(option.value);
399
+ return true;
400
+ });
401
+ }
402
+ function cascadingLevels(options, selectedPaths) {
403
+ const levels = [];
404
+ let currentOptions = options;
405
+ let depth = 0;
406
+ while (currentOptions.length > 0) {
407
+ levels.push(currentOptions);
408
+ const selectedAtDepth = uniqueCascadingOptions(
409
+ selectedPaths.map((path) => path[depth]).filter(
410
+ (option) => Boolean(option && currentOptions.some((current) => current.value === option.value))
411
+ )
412
+ );
413
+ const nextOptions = uniqueCascadingOptions(selectedAtDepth.flatMap((option) => option.children ?? []));
414
+ if (nextOptions.length === 0) break;
415
+ currentOptions = nextOptions;
416
+ depth += 1;
417
+ }
418
+ return levels;
419
+ }
420
+ function selectedValuesForCascadingLevel(selectedPaths, depth, options) {
421
+ const optionValues = new Set(options.map((option) => option.value));
422
+ return Array.from(
423
+ new Set(
424
+ selectedPaths.map((path) => path[depth]?.value).filter((value) => Boolean(value) && optionValues.has(value))
425
+ )
426
+ );
427
+ }
428
+ function cascadingPathIncludes(pathMap, value, maybeAncestor) {
429
+ return pathMap.get(value)?.some((option) => option.value === maybeAncestor) ?? false;
430
+ }
431
+ function cascadingValuesOverlap(leftValue, rightValue, pathMap) {
432
+ return cascadingPathIncludes(pathMap, leftValue, rightValue) || cascadingPathIncludes(pathMap, rightValue, leftValue);
433
+ }
434
+ function cascadingLevelLabel(values, allLabel, pathMap, showPathLabels) {
435
+ if (values.length === 0) return allLabel;
436
+ const labels = values.map((value) => {
437
+ if (showPathLabels) return cascadingPathLabel(pathMap, value);
438
+ return pathMap.get(value)?.at(-1)?.label ?? value;
439
+ });
440
+ if (labels.length <= 2) return labels.join(", ");
441
+ return `${labels[0]} \uC678 ${labels.length - 1}`;
442
+ }
443
+ function cascadingOptionLabel(option, pathMap, showPathLabels) {
444
+ return showPathLabels ? cascadingPathLabel(pathMap, option.value) : option.label;
445
+ }
446
+ function CascadingInput({
447
+ field,
448
+ values,
449
+ onChange
450
+ }) {
451
+ const pathMap = useMemo(() => cascadingPathMap(field.options), [field.options]);
452
+ const selectedValues = useMemo(
453
+ () => splitMultiValue(values[field.key] ?? "").filter((value) => pathMap.has(value)),
454
+ [field.key, pathMap, values]
455
+ );
456
+ const selectedPaths = useMemo(
457
+ () => selectedValues.map((value) => pathMap.get(value)).filter((path) => Boolean(path)),
458
+ [pathMap, selectedValues]
459
+ );
460
+ const levels = useMemo(() => cascadingLevels(field.options, selectedPaths), [field.options, selectedPaths]);
461
+ const setSelectedValues = (nextValues) => {
462
+ onChange({ [field.key]: joinMultiValue(nextValues) });
463
+ };
464
+ const toggleOption = (option, nextChecked) => {
465
+ const withoutOverlap = selectedValues.filter(
466
+ (value) => !cascadingValuesOverlap(option.value, value, pathMap)
467
+ );
468
+ setSelectedValues(nextChecked ? [...withoutOverlap, option.value] : withoutOverlap);
469
+ };
470
+ const clearLevel = (depth) => {
471
+ if (depth === 0) {
472
+ setSelectedValues([]);
473
+ return;
474
+ }
475
+ const parents = selectedValuesForCascadingLevel(selectedPaths, depth - 1, levels[depth - 1] ?? []);
476
+ const rest = selectedValues.filter((value) => {
477
+ const parentValue = pathMap.get(value)?.[depth - 1]?.value;
478
+ return !parentValue || !parents.includes(parentValue);
479
+ });
480
+ setSelectedValues([...rest, ...parents]);
481
+ };
482
+ return /* @__PURE__ */ jsx("div", { className: "wp-col", children: levels.map((options, depth) => {
483
+ const levelValues = selectedValuesForCascadingLevel(selectedPaths, depth, options);
484
+ const showPathLabels = depth > 0 && selectedValuesForCascadingLevel(selectedPaths, depth - 1, levels[depth - 1] ?? []).length > 1;
485
+ return /* @__PURE__ */ jsx(
486
+ CascadingLevelSelect,
487
+ {
488
+ allLabel: depth === 0 ? field.allLabel ?? "\uC804\uCCB4" : "\uC804\uCCB4",
489
+ options,
490
+ pathMap,
491
+ selectedValues: levelValues,
492
+ showPathLabels,
493
+ onClear: () => clearLevel(depth),
494
+ onToggle: toggleOption
495
+ },
496
+ depth
497
+ );
498
+ }) });
499
+ }
500
+ function CascadingLevelSelect({
501
+ options,
502
+ pathMap,
503
+ selectedValues,
504
+ allLabel,
505
+ showPathLabels,
506
+ onClear,
507
+ onToggle
508
+ }) {
509
+ const [open, setOpen] = useState(false);
510
+ const selectedSet = new Set(selectedValues);
511
+ return /* @__PURE__ */ jsx(
512
+ Popover,
513
+ {
514
+ open,
515
+ onClose: () => setOpen(false),
516
+ trigger: /* @__PURE__ */ jsx(
517
+ Button,
518
+ {
519
+ type: "button",
520
+ variant: "secondary",
521
+ block: true,
522
+ iconEnd: "chevron-down",
523
+ onClick: () => setOpen((value) => !value),
524
+ children: cascadingLevelLabel(selectedValues, allLabel, pathMap, showPathLabels)
525
+ }
526
+ ),
527
+ footer: /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "xs", icon: "refresh-cw", onClick: onClear, children: allLabel }),
528
+ children: /* @__PURE__ */ jsx("div", { className: "wp-col", style: { minWidth: 200 }, children: options.map((option) => /* @__PURE__ */ jsx(
529
+ Checkbox,
530
+ {
531
+ label: cascadingOptionLabel(option, pathMap, showPathLabels),
532
+ checked: selectedSet.has(option.value),
533
+ onChange: (e) => onToggle(option, e.target.checked)
534
+ },
535
+ option.value
536
+ )) })
537
+ }
538
+ );
539
+ }
540
+
541
+ export { Popover, SearchToolbar, cascadingValuePaths, cleanSearchValues, includesLooseSearch, joinMultiValue, normalizeSearchText, searchFieldKeys, searchValuesFromParams, splitMultiValue };
@@ -0,0 +1,40 @@
1
+ import { Button } from './chunk-ZUCI34QY.js';
2
+ import { useEffect } from 'react';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+
5
+ var PRINT_CSS = `
6
+ .erp-print-shell{display:flex;flex-direction:column;align-items:center;gap:var(--wp-space-4);padding:var(--wp-space-6) var(--wp-space-4)}
7
+ .erp-print-toolbar{display:flex;justify-content:flex-end;gap:var(--wp-space-2);width:100%;max-width:210mm}
8
+ .erp-print-sheet{width:100%;max-width:210mm;box-sizing:border-box;background:var(--wp-surface-card);border:var(--wp-border-w) solid var(--wp-border-subtle);border-radius:var(--wp-radius-lg);box-shadow:var(--wp-shadow-lg);padding:var(--wp-space-7)}
9
+ .erp-print-sheet table{border-collapse:collapse;width:100%}
10
+ .erp-print-sheet th,.erp-print-sheet td{border:var(--wp-border-w) solid var(--wp-border-default);padding:var(--wp-space-1) var(--wp-space-2)}
11
+ @media print{
12
+ @page{size:A4;margin:1cm}
13
+ .erp-print-hidden{display:none !important}
14
+ .erp-print-shell{display:block;padding:0}
15
+ .erp-print-sheet{--wp-border-default:rgba(0,0,0,.85);--wp-border-subtle:rgba(0,0,0,.6);max-width:none;border:0;border-radius:0;box-shadow:none;padding:0;font-size:12pt;color:rgba(0,0,0,.92)}
16
+ }
17
+ `;
18
+ function PrintStyles() {
19
+ return /* @__PURE__ */ jsx("style", { href: "erp-print", precedence: "medium", children: PRINT_CSS });
20
+ }
21
+ function PrintSheet({ title, children, autoPrint = false }) {
22
+ useEffect(() => {
23
+ if (!autoPrint || !window.opener) return;
24
+ const timer = window.setTimeout(() => window.print(), 500);
25
+ return () => window.clearTimeout(timer);
26
+ }, [autoPrint]);
27
+ return /* @__PURE__ */ jsxs("div", { className: "erp-print-shell", children: [
28
+ /* @__PURE__ */ jsx(PrintStyles, {}),
29
+ /* @__PURE__ */ jsxs("div", { className: "erp-print-toolbar erp-print-hidden", children: [
30
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "primary", onClick: () => window.print(), children: "\uC778\uC1C4\uD558\uAE30" }),
31
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", onClick: () => window.close(), children: "\uB2EB\uAE30" })
32
+ ] }),
33
+ /* @__PURE__ */ jsxs("section", { className: "erp-print-sheet", children: [
34
+ title ? /* @__PURE__ */ jsx("h1", { className: "wp-h3", style: { margin: "0 0 var(--wp-space-5)" }, children: title }) : null,
35
+ children
36
+ ] })
37
+ ] });
38
+ }
39
+
40
+ export { PrintSheet };