@wipco/sdui 1.6.0 → 1.7.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 (98) hide show
  1. package/dist/{DatePicker-DX9juElE.d.ts → DatePicker-BOOKdd5x.d.ts} +6 -6
  2. package/dist/calendar-nav-BSGsgJc4.d.ts +54 -0
  3. package/dist/{chunk-53KS4E5N.js → chunk-27ILQUWE.js} +121 -28
  4. package/dist/{chunk-AY4GM6KQ.js → chunk-466W3XNK.js} +1 -1
  5. package/dist/{chunk-X2NIRVLN.js → chunk-4VQIKSDS.js} +157 -190
  6. package/dist/{chunk-LIZTK3HO.js → chunk-5FZBH3T5.js} +1 -1
  7. package/dist/{chunk-NA232CBX.js → chunk-5LIFU57A.js} +2 -2
  8. package/dist/{chunk-LKQJJSVJ.js → chunk-63GM4S25.js} +1 -1
  9. package/dist/{chunk-CSE3PZOZ.js → chunk-7EDFYBUA.js} +4 -4
  10. package/dist/{chunk-MN4RZ2WP.js → chunk-7YTZB5LQ.js} +3 -3
  11. package/dist/{chunk-WEG3V7ZB.js → chunk-7ZCM6XEM.js} +2 -2
  12. package/dist/{chunk-6XI2VF2O.js → chunk-A22QR6PM.js} +2 -2
  13. package/dist/{chunk-CEVZAFPO.js → chunk-A3SO6H47.js} +2 -2
  14. package/dist/{chunk-2KQL6DYG.js → chunk-AP33B24H.js} +65 -9
  15. package/dist/{chunk-FEKKOBPX.js → chunk-B5MB64YQ.js} +209 -25
  16. package/dist/{chunk-IQXLNICY.js → chunk-DCSUBFFG.js} +4 -4
  17. package/dist/{chunk-OUEJWC2Z.js → chunk-DILQ2DJV.js} +5 -5
  18. package/dist/{chunk-NAIWQQPW.js → chunk-ENZBX4AR.js} +1 -1
  19. package/dist/{chunk-WTIYILY3.js → chunk-FGKBXUUA.js} +1 -1
  20. package/dist/{chunk-CI5OZ2QT.js → chunk-FUNCQCKN.js} +2 -2
  21. package/dist/{chunk-XG22XKKD.js → chunk-GWT5UF5B.js} +2 -2
  22. package/dist/{chunk-K6RE23XX.js → chunk-H5QRRPBJ.js} +3 -3
  23. package/dist/{chunk-AZSLSAJB.js → chunk-JCG4575S.js} +2 -2
  24. package/dist/{chunk-YZTTFEL4.js → chunk-JMNUFA5M.js} +1 -1
  25. package/dist/{chunk-KZ6U6FKB.js → chunk-L6ODTHTX.js} +1 -1
  26. package/dist/{chunk-W4VA76QO.js → chunk-LKA43GYA.js} +1 -1
  27. package/dist/{chunk-6TI4WF5Y.js → chunk-LKL3SCBJ.js} +2 -2
  28. package/dist/{chunk-4OABUPIU.js → chunk-LN4KPZIX.js} +3 -3
  29. package/dist/chunk-MUXGTVEA.js +421 -0
  30. package/dist/{chunk-7C54CKFZ.js → chunk-MWBCQHKQ.js} +3 -3
  31. package/dist/{chunk-7JP52QQO.js → chunk-NI3LMETK.js} +2 -2
  32. package/dist/{chunk-HXLQSO66.js → chunk-NJTIJAFU.js} +2 -2
  33. package/dist/{chunk-YN7X7BEO.js → chunk-NQDHGK7C.js} +3 -3
  34. package/dist/{chunk-BLFJMMBW.js → chunk-OWM7E55U.js} +25 -2
  35. package/dist/{chunk-VPOOIEKT.js → chunk-P62INTQ5.js} +1 -1
  36. package/dist/{chunk-QVS6K4FP.js → chunk-Q56LZJFX.js} +1 -1
  37. package/dist/{chunk-BITS7UJY.js → chunk-R2ORCOTG.js} +2 -2
  38. package/dist/{chunk-2QXA52DG.js → chunk-RA74PUMP.js} +3 -3
  39. package/dist/{chunk-GSM664TX.js → chunk-TAVKGYH5.js} +24 -1
  40. package/dist/{chunk-OYDJHQNZ.js → chunk-UBVYPV7H.js} +2 -2
  41. package/dist/chunk-VR4DQTWR.js +391 -0
  42. package/dist/{chunk-M6H3SHIS.js → chunk-WPAZBAUW.js} +1 -1
  43. package/dist/{chunk-NWYYNE3Q.js → chunk-WTQH2NI2.js} +1 -1
  44. package/dist/{chunk-HRHKQOOF.js → chunk-X7RSKFWF.js} +1 -1
  45. package/dist/{chunk-CSZCWBOE.js → chunk-XDR6HC4U.js} +14 -7
  46. package/dist/{chunk-RU5U2YMT.js → chunk-Z4JZUIKE.js} +3 -3
  47. package/dist/{chunk-V7BWMOHO.js → chunk-Z4LFZWHH.js} +2 -2
  48. package/dist/{chunk-YNEEJCSZ.js → chunk-ZBX2CAOO.js} +2 -2
  49. package/dist/index.d.ts +57 -7
  50. package/dist/index.js +93 -37
  51. package/dist/meta.d.ts +1 -1
  52. package/dist/meta.js +129 -4
  53. package/dist/rich/ExcelUploadCard.js +4 -4
  54. package/dist/rich.d.ts +4 -2
  55. package/dist/rich.js +36 -8
  56. package/dist/styles.css +27 -0
  57. package/dist/{types-CYfIthux.d.ts → types-CMikpoN5.d.ts} +8 -1
  58. package/dist/widgets/ApprovalLineEditor.js +9 -8
  59. package/dist/widgets/ApprovalOverridesEditor.js +11 -10
  60. package/dist/widgets/ApprovalStepper.js +2 -2
  61. package/dist/widgets/AutocompleteField.js +9 -8
  62. package/dist/widgets/ColumnBoard.js +2 -2
  63. package/dist/widgets/DiffView.js +2 -2
  64. package/dist/widgets/Disclosure.js +2 -2
  65. package/dist/widgets/EventCalendar.d.ts +14 -10
  66. package/dist/widgets/EventCalendar.js +5 -3
  67. package/dist/widgets/FileDropzone.js +7 -7
  68. package/dist/widgets/ImageLightbox.js +5 -5
  69. package/dist/widgets/InlineEdit.js +2 -2
  70. package/dist/widgets/JsonField.js +2 -2
  71. package/dist/widgets/OptionSchemaEditor.js +3 -3
  72. package/dist/widgets/OptionValueFields.js +3 -3
  73. package/dist/widgets/OrgChart.js +3 -3
  74. package/dist/widgets/OrgChartBoard.d.ts +57 -4
  75. package/dist/widgets/OrgChartBoard.js +8 -8
  76. package/dist/widgets/PeriodFilter.js +6 -5
  77. package/dist/widgets/PermissionMatrixEditor.js +4 -4
  78. package/dist/widgets/PositionSelect.js +4 -4
  79. package/dist/widgets/PrintSheet.js +2 -2
  80. package/dist/widgets/ProfileEditDiffCard.js +4 -4
  81. package/dist/widgets/QRDisplay.js +4 -4
  82. package/dist/widgets/RankBoard.js +6 -6
  83. package/dist/widgets/RecordSheet.d.ts +48 -1
  84. package/dist/widgets/RecordSheet.js +2 -1
  85. package/dist/widgets/RoleMemberPicker.js +5 -5
  86. package/dist/widgets/SearchableMultiPicker.js +7 -6
  87. package/dist/widgets/SecureScreen.js +2 -2
  88. package/dist/widgets/StagePeriodEditor.js +6 -5
  89. package/dist/widgets/TagInput.js +2 -2
  90. package/dist/widgets/TagMultiSelect.js +4 -4
  91. package/dist/widgets/Timeline.js +3 -3
  92. package/dist/widgets/TreePicker.js +8 -7
  93. package/dist/widgets/TreeView.d.ts +16 -2
  94. package/dist/widgets/TreeView.js +7 -6
  95. package/dist/widgets/index.d.ts +1 -0
  96. package/dist/widgets/index.js +76 -75
  97. package/package.json +1 -1
  98. package/dist/chunk-IB2ZPUTF.js +0 -172
@@ -0,0 +1,391 @@
1
+ import { useIsMobile } from './chunk-TAVKGYH5.js';
2
+ import { boxed } from './chunk-3JTBOABA.js';
3
+ import React, { useRef, useState, useEffect } from 'react';
4
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
+
6
+ var RECORD_SHEET_CSS = `
7
+ .erp-recsheet__scroll{overflow-x:auto;min-width:0}
8
+ .erp-recsheet{width:100%;min-width:clamp(40rem,100%,44rem);border-collapse:collapse;table-layout:fixed;font-size:var(--wp-size-sm)}
9
+ .erp-recsheet td{height:var(--wp-row-h-dense);border:var(--wp-border-w) solid var(--wp-border-default);padding:var(--wp-space-1) var(--wp-space-2);color:var(--wp-text-default);vertical-align:middle;line-height:var(--wp-leading-snug);overflow-wrap:anywhere}
10
+ .erp-recsheet td.erp-recsheet__title{background:var(--wp-surface-inverse);color:var(--wp-text-inverse);text-align:center;font-size:var(--wp-size-lg);font-weight:var(--wp-weight-semibold);letter-spacing:var(--wp-tracking-heading);padding:var(--wp-space-3) var(--wp-space-2)}
11
+ .erp-recsheet td.erp-recsheet__label{background:var(--wp-surface-sunken);color:var(--wp-text-subtle);font-size:var(--wp-size-xs);font-weight:var(--wp-weight-semibold);text-align:center;white-space:nowrap}
12
+ .erp-recsheet td.erp-recsheet__section{background:var(--wp-surface-brand-tint);border-top:var(--wp-border-w-strong) solid var(--wp-border-brand);color:var(--wp-text-brand);font-size:var(--wp-size-xs);font-weight:var(--wp-weight-semibold);letter-spacing:var(--wp-tracking-heading)}
13
+ .erp-recsheet tr.erp-recsheet__gap td{border:0;padding:0;height:var(--wp-space-3)}
14
+ .erp-recsheet td.erp-recsheet__mono{font-family:var(--wp-font-mono);font-size:var(--wp-size-xs)}
15
+ .erp-recsheet td.erp-recsheet__photo{background:var(--wp-surface-sunken);padding:var(--wp-space-2);text-align:center}
16
+ .erp-recsheet__img{display:block;width:100%;height:11rem;object-fit:cover;border-radius:var(--wp-radius-sm)}
17
+ .erp-recsheet__noimg{display:flex;align-items:center;justify-content:center;width:100%;height:11rem;border:var(--wp-border-w) dashed var(--wp-border-default);border-radius:var(--wp-radius-sm);color:var(--wp-text-disabled);font-size:var(--wp-size-xs)}
18
+ .erp-recsheet__note{margin:var(--wp-space-2) 0 0;text-align:right;color:var(--wp-text-subtle);font-size:var(--wp-size-xs)}
19
+ .erp-recsheet__frame{position:relative;min-width:0}
20
+ .erp-recsheet__frame::before,.erp-recsheet__frame::after{content:"";position:absolute;top:0;bottom:0;z-index:1;width:var(--wp-space-4);pointer-events:none;opacity:0;transition:opacity var(--wp-duration-fast)}
21
+ .erp-recsheet__frame::before{left:0;background:linear-gradient(to right,color-mix(in srgb,var(--wp-text-strong) 16%,transparent),transparent)}
22
+ .erp-recsheet__frame::after{right:0;background:linear-gradient(to left,color-mix(in srgb,var(--wp-text-strong) 16%,transparent),transparent)}
23
+ .erp-recsheet__frame[data-more-left]::before,.erp-recsheet__frame[data-more-right]::after{opacity:1}
24
+ .erp-recsheet-stack{display:flex;flex-direction:column;gap:var(--wp-space-3);min-width:0;font-size:var(--wp-size-sm);color:var(--wp-text-default)}
25
+ .erp-recsheet-stack__title{padding:var(--wp-space-2) var(--wp-space-3);background:var(--wp-surface-inverse);color:var(--wp-text-inverse);font-size:var(--wp-size-lg);font-weight:var(--wp-weight-semibold);letter-spacing:var(--wp-tracking-heading);line-height:var(--wp-leading-snug);text-align:left}
26
+ .erp-recsheet-stack__block{display:flex;flex-direction:column;min-width:0}
27
+ .erp-recsheet-stack__head{padding:var(--wp-space-2);background:var(--wp-surface-brand-tint);border-top:var(--wp-border-w-strong) solid var(--wp-border-brand);color:var(--wp-text-brand);font-size:var(--wp-size-xs);font-weight:var(--wp-weight-semibold);letter-spacing:var(--wp-tracking-heading)}
28
+ .erp-recsheet-stack__pairs{width:100%;border-collapse:collapse;table-layout:fixed}
29
+ .erp-recsheet-stack__pairs :is(th,td){height:var(--wp-row-h-dense);border:var(--wp-border-w) solid var(--wp-border-default);padding:var(--wp-space-1) var(--wp-space-2);vertical-align:middle;line-height:var(--wp-leading-snug);text-align:left;overflow-wrap:anywhere}
30
+ .erp-recsheet-stack__pairs th{width:6.5rem;background:var(--wp-surface-sunken);color:var(--wp-text-subtle);font-size:var(--wp-size-xs);font-weight:var(--wp-weight-semibold);word-break:keep-all;overflow-wrap:break-word}
31
+ .erp-recsheet-stack__pairs td.erp-recsheet__mono{font-family:var(--wp-font-mono);font-size:var(--wp-size-xs)}
32
+ .erp-recsheet-stack__scroll{overflow-x:auto;min-width:0;overscroll-behavior-x:contain}
33
+ .erp-recsheet.erp-recsheet-stack__grid{min-width:0;width:100%;table-layout:auto}
34
+ .erp-recsheet.erp-recsheet-stack__grid td{word-break:keep-all;overflow-wrap:break-word}
35
+ .erp-recsheet.erp-recsheet-stack__grid td.erp-recsheet__label{white-space:normal;min-width:3.5rem}
36
+ .erp-recsheet.erp-recsheet-stack__grid td.erp-recsheet__mono{white-space:nowrap}
37
+ .erp-recsheet-stack__photo{align-self:center;width:6.5rem}
38
+ .erp-recsheet-stack__photo :is(.erp-recsheet__img,.erp-recsheet__noimg){height:8.5rem}
39
+ .erp-recsheet-stack .erp-recsheet__note{margin:0}
40
+ @media print{
41
+ .erp-recsheet__frame::before,.erp-recsheet__frame::after{display:none}
42
+ .erp-recsheet__scroll{overflow-x:visible}
43
+ .erp-recsheet{min-width:0}
44
+ .erp-recsheet tr{break-inside:avoid}
45
+ }
46
+ `;
47
+ function RecordSheetStyles() {
48
+ return /* @__PURE__ */ jsx("style", { href: "erp-recsheet", precedence: "medium", children: RECORD_SHEET_CSS });
49
+ }
50
+ function gridColumns(columns) {
51
+ if (typeof columns !== "number" || !Number.isFinite(columns)) return 24;
52
+ return Math.min(60, Math.max(1, Math.floor(columns)));
53
+ }
54
+ function cellClass(cell) {
55
+ const classes = [];
56
+ if (cell.kind === "label") classes.push("erp-recsheet__label");
57
+ else if (cell.kind === "section") classes.push("erp-recsheet__section");
58
+ else if (cell.kind === "photo") classes.push("erp-recsheet__photo");
59
+ if (cell.mono) classes.push("erp-recsheet__mono");
60
+ return classes.length > 0 ? classes.join(" ") : void 0;
61
+ }
62
+ function isBlank(value) {
63
+ return value === null || value === void 0 || value === "";
64
+ }
65
+ function pickValue(cell, item) {
66
+ if (!item || typeof item !== "object") return void 0;
67
+ const row = item;
68
+ if (cell.fields && cell.fields.length > 0) {
69
+ const parts = cell.fields.map((name) => isBlank(row[name]) ? "" : String(row[name]));
70
+ if (parts.every((part) => part === "")) return void 0;
71
+ return parts.join(cell.separator ?? " ~ ");
72
+ }
73
+ if (cell.field) return row[cell.field];
74
+ return void 0;
75
+ }
76
+ function PhotoCell({ src, alt }) {
77
+ const [failed, setFailed] = useState(false);
78
+ const label = alt ?? "\uC0AC\uC9C4";
79
+ if (!src || failed) return /* @__PURE__ */ jsx("span", { className: "erp-recsheet__noimg", children: label });
80
+ return /* @__PURE__ */ jsx("img", { className: "erp-recsheet__img", src, alt: label, onError: () => setFailed(true) });
81
+ }
82
+ function cellContent(cell, item, repeated) {
83
+ if (cell.kind === "photo") return /* @__PURE__ */ jsx(PhotoCell, { src: cell.src, alt: cell.alt });
84
+ if (!repeated) {
85
+ if (!isBlank(cell.text)) return cell.text;
86
+ if (cell.kind === "label" || cell.kind === "section") return null;
87
+ return cell.placeholder ?? "-";
88
+ }
89
+ const value = pickValue(cell, item);
90
+ if (!isBlank(value)) return value;
91
+ return item ? cell.placeholder ?? "-" : null;
92
+ }
93
+ function renderCells(cells, items, keyPrefix) {
94
+ return cells.map((cell, index) => {
95
+ const item = items ? items[cell.source ?? 0] ?? null : null;
96
+ return /* @__PURE__ */ jsx(
97
+ "td",
98
+ {
99
+ className: cellClass(cell),
100
+ colSpan: cell.span && cell.span > 1 ? cell.span : void 0,
101
+ rowSpan: cell.rowSpan && cell.rowSpan > 1 ? cell.rowSpan : void 0,
102
+ style: cell.align && cell.align !== "left" ? { textAlign: cell.align } : void 0,
103
+ children: cellContent(cell, item, items !== null)
104
+ },
105
+ `${keyPrefix}-${index}`
106
+ );
107
+ });
108
+ }
109
+ function repeatLength(sources, minRows) {
110
+ const longest = sources.reduce((max, source) => Math.max(max, source.length), 0);
111
+ return Math.max(longest, Math.max(0, Math.floor(minRows ?? 0)));
112
+ }
113
+ function spanOf(value, fallback = 1) {
114
+ return typeof value === "number" && Number.isFinite(value) && value >= 1 ? Math.floor(value) : fallback;
115
+ }
116
+ function placeRecordSheetCells(rows, width) {
117
+ const carry = new Array(width).fill(0);
118
+ return rows.map((row) => {
119
+ const cells = row && !row.gap && Array.isArray(row.cells) ? row.cells : [];
120
+ const placed = [];
121
+ let col = 0;
122
+ for (const cell of cells) {
123
+ while (col < width && carry[col] > 0) col += 1;
124
+ const start = Math.min(col, width);
125
+ const end = Math.min(width, start + spanOf(cell?.span));
126
+ placed.push({ cell: cell ?? {}, start, end });
127
+ const rowSpan = spanOf(cell?.rowSpan);
128
+ if (rowSpan > 1) for (let c = start; c < end; c += 1) carry[c] = rowSpan;
129
+ col = end;
130
+ }
131
+ for (let c = 0; c < width; c += 1) if (carry[c] > 0) carry[c] -= 1;
132
+ return placed;
133
+ });
134
+ }
135
+ var isLabelLike = (cell) => cell.kind === "label" || cell.kind === "section";
136
+ function pairsOf(cells) {
137
+ const pairs = [];
138
+ for (const cell of cells) {
139
+ if (isLabelLike(cell)) pairs.push({ label: cell, values: [] });
140
+ else if (pairs.length > 0) pairs[pairs.length - 1].values.push(cell);
141
+ else pairs.push({ label: null, values: [cell] });
142
+ }
143
+ return pairs;
144
+ }
145
+ function stackRecordSheet(rows, width) {
146
+ const list = Array.isArray(rows) ? rows : [];
147
+ const placedRows = placeRecordSheetCells(list, width);
148
+ const blocks = [];
149
+ let open = [];
150
+ const push = (block, part) => {
151
+ const last = block.parts[block.parts.length - 1];
152
+ if (last && last.kind === "pairs" && part.kind === "pairs") last.pairs.push(...part.pairs);
153
+ else if (last && last.kind === "grid" && part.kind === "grid") last.rows.push(...part.rows);
154
+ else block.parts.push(part);
155
+ };
156
+ list.forEach((row, index) => {
157
+ if (row?.gap) {
158
+ open = [];
159
+ return;
160
+ }
161
+ const placed = placedRows[index];
162
+ if (placed.length === 0) return;
163
+ if (placed.every((p) => p.cell.kind === "section")) {
164
+ open = placed.map((p) => ({ block: { section: p.cell, parts: [] }, start: p.start, end: p.end }));
165
+ blocks.push(...open.map((o) => o.block));
166
+ return;
167
+ }
168
+ if (open.length === 0) {
169
+ const block = { section: null, parts: [] };
170
+ blocks.push(block);
171
+ open = [{ block, start: 0, end: width }];
172
+ }
173
+ for (const target of open) {
174
+ const mine = placed.filter((p) => {
175
+ const owner = open.find((o) => p.start >= o.start && p.start < o.end) ?? open[0];
176
+ return owner === target;
177
+ });
178
+ if (mine.length === 0) continue;
179
+ const rest = [];
180
+ for (const p of mine) {
181
+ if (p.cell.kind === "photo") push(target.block, { kind: "photo", cell: p.cell });
182
+ else rest.push(p);
183
+ }
184
+ if (rest.length === 0) continue;
185
+ const repeated = Array.isArray(row.repeat);
186
+ const hasLabel = rest.some((p) => isLabelLike(p.cell));
187
+ const hasValue = rest.some((p) => !isLabelLike(p.cell));
188
+ if (!repeated && hasLabel && hasValue) {
189
+ push(target.block, { kind: "pairs", pairs: pairsOf(rest.map((p) => p.cell)) });
190
+ continue;
191
+ }
192
+ const cells = rest.map((p) => ({ ...p, start: p.start - target.start, end: p.end - target.start }));
193
+ push(target.block, {
194
+ kind: "grid",
195
+ rows: [
196
+ repeated ? {
197
+ cells,
198
+ repeat: row.repeat.map((source) => Array.isArray(source) ? source : []),
199
+ minRows: row.minRows
200
+ } : { cells }
201
+ ]
202
+ });
203
+ }
204
+ });
205
+ return blocks;
206
+ }
207
+ function gridBoundaries(rows) {
208
+ const set = /* @__PURE__ */ new Set();
209
+ for (const row of rows) for (const p of row.cells) set.add(p.start).add(p.end);
210
+ return [...set].sort((a, b) => a - b);
211
+ }
212
+ function StackGrid({ rows, keyPrefix }) {
213
+ const scrollRef = useRef(null);
214
+ const edges = useScrollEdges(scrollRef);
215
+ const bounds = gridBoundaries(rows);
216
+ const spanIn = (p) => {
217
+ const n = bounds.filter((b) => b >= p.start && b < p.end).length;
218
+ return n > 1 ? n : void 0;
219
+ };
220
+ const lines = [];
221
+ const cellsOf = (row, items, key) => row.cells.map((p, index) => {
222
+ const item = items ? items[p.cell.source ?? 0] ?? null : null;
223
+ return /* @__PURE__ */ jsx(
224
+ "td",
225
+ {
226
+ className: cellClass(p.cell),
227
+ colSpan: spanIn(p),
228
+ style: p.cell.align && p.cell.align !== "left" ? { textAlign: p.cell.align } : void 0,
229
+ children: cellContent(p.cell, item, items !== null)
230
+ },
231
+ `${key}-${index}`
232
+ );
233
+ });
234
+ rows.forEach((row, rowIndex) => {
235
+ if (!row.repeat) {
236
+ lines.push(/* @__PURE__ */ jsx("tr", { children: cellsOf(row, null, `${keyPrefix}-r${rowIndex}`) }, `${keyPrefix}-r${rowIndex}`));
237
+ return;
238
+ }
239
+ const sources = row.repeat;
240
+ const used = new Set(row.cells.map((p) => p.cell.source ?? 0));
241
+ const longest = [...used].reduce((max, i) => Math.max(max, sources[i]?.length ?? 0), 0);
242
+ const count = longest > 0 ? longest : Math.min(1, Math.max(0, Math.floor(row.minRows ?? 0)));
243
+ for (let line = 0; line < count; line += 1) {
244
+ const key = `${keyPrefix}-r${rowIndex}-${line}`;
245
+ lines.push(/* @__PURE__ */ jsx("tr", { children: cellsOf(row, sources.map((source) => source[line] ?? null), key) }, key));
246
+ }
247
+ });
248
+ return /* @__PURE__ */ jsx(
249
+ "div",
250
+ {
251
+ className: "erp-recsheet__frame",
252
+ "data-more-left": edges.left ? "" : void 0,
253
+ "data-more-right": edges.right ? "" : void 0,
254
+ children: /* @__PURE__ */ jsx("div", { ref: scrollRef, className: "erp-recsheet-stack__scroll", children: /* @__PURE__ */ jsx("table", { className: "erp-recsheet erp-recsheet-stack__grid", children: /* @__PURE__ */ jsx("tbody", { children: lines }) }) })
255
+ }
256
+ );
257
+ }
258
+ function StackPairs({ pairs, keyPrefix }) {
259
+ return /* @__PURE__ */ jsx("table", { className: "erp-recsheet-stack__pairs", children: /* @__PURE__ */ jsx("tbody", { children: pairs.map((pair, index) => {
260
+ const values = pair.values.map((cell, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
261
+ i > 0 ? " " : null,
262
+ cellContent(cell, null, false)
263
+ ] }, i));
264
+ const mono = pair.values.length > 0 && pair.values.every((cell) => cell.mono);
265
+ return /* @__PURE__ */ jsx("tr", { className: "erp-recsheet-stack__pair", children: pair.label ? /* @__PURE__ */ jsxs(Fragment, { children: [
266
+ /* @__PURE__ */ jsx("th", { scope: "row", children: pair.label.text }),
267
+ /* @__PURE__ */ jsx("td", { className: mono ? "erp-recsheet__mono" : void 0, children: values })
268
+ ] }) : /* @__PURE__ */ jsx("td", { colSpan: 2, className: mono ? "erp-recsheet__mono" : void 0, children: values }) }, `${keyPrefix}-${index}`);
269
+ }) }) });
270
+ }
271
+ function RecordSheetStack({
272
+ width,
273
+ title,
274
+ rows,
275
+ note,
276
+ className,
277
+ style
278
+ }) {
279
+ const blocks = stackRecordSheet(rows, width);
280
+ return /* @__PURE__ */ jsxs("div", { className: className ? `erp-recsheet__frame ${className}` : "erp-recsheet__frame", style, children: [
281
+ /* @__PURE__ */ jsx(RecordSheetStyles, {}),
282
+ /* @__PURE__ */ jsxs("div", { className: "erp-recsheet-stack", children: [
283
+ title ? /* @__PURE__ */ jsx("div", { className: "erp-recsheet-stack__title", children: title }) : null,
284
+ blocks.map((block, blockIndex) => /* @__PURE__ */ jsxs("section", { className: "erp-recsheet-stack__block", children: [
285
+ block.section ? /* @__PURE__ */ jsx("div", { className: "erp-recsheet-stack__head", children: block.section.text }) : null,
286
+ block.parts.map((part, partIndex) => {
287
+ const key = `b${blockIndex}-p${partIndex}`;
288
+ if (part.kind === "photo") {
289
+ return /* @__PURE__ */ jsx("div", { className: "erp-recsheet-stack__photo", children: /* @__PURE__ */ jsx(PhotoCell, { src: part.cell.src, alt: part.cell.alt }) }, key);
290
+ }
291
+ if (part.kind === "pairs") return /* @__PURE__ */ jsx(StackPairs, { pairs: part.pairs, keyPrefix: key }, key);
292
+ return /* @__PURE__ */ jsx(StackGrid, { rows: part.rows, keyPrefix: key }, key);
293
+ })
294
+ ] }, blockIndex)),
295
+ note ? /* @__PURE__ */ jsx("p", { className: "erp-recsheet__note", children: note }) : null
296
+ ] })
297
+ ] });
298
+ }
299
+ function RecordSheetImpl(props) {
300
+ const isMobile = useIsMobile();
301
+ if (isMobile) {
302
+ return /* @__PURE__ */ jsx(
303
+ RecordSheetStack,
304
+ {
305
+ width: gridColumns(props.columns),
306
+ title: props.title,
307
+ rows: Array.isArray(props.rows) ? props.rows : [],
308
+ note: props.note
309
+ }
310
+ );
311
+ }
312
+ return /* @__PURE__ */ jsx(RecordSheetGrid, { ...props });
313
+ }
314
+ function RecordSheetGrid({ columns, title, rows, note, className, style }) {
315
+ const scrollRef = useRef(null);
316
+ const edges = useScrollEdges(scrollRef);
317
+ const width = gridColumns(columns);
318
+ const list = Array.isArray(rows) ? rows : [];
319
+ const body = [];
320
+ list.forEach((row, rowIndex) => {
321
+ if (row?.gap) {
322
+ body.push(
323
+ /* @__PURE__ */ jsx("tr", { className: "erp-recsheet__gap", children: /* @__PURE__ */ jsx("td", { colSpan: width }) }, `gap-${rowIndex}`)
324
+ );
325
+ return;
326
+ }
327
+ const cells = Array.isArray(row?.cells) ? row.cells : [];
328
+ if (cells.length === 0) return;
329
+ if (Array.isArray(row.repeat)) {
330
+ const sources = row.repeat.map((source) => Array.isArray(source) ? source : []);
331
+ const count = repeatLength(sources, row.minRows);
332
+ for (let line = 0; line < count; line += 1) {
333
+ body.push(
334
+ /* @__PURE__ */ jsx("tr", { children: renderCells(
335
+ cells,
336
+ sources.map((source) => source[line] ?? null),
337
+ `rep-${rowIndex}-${line}`
338
+ ) }, `rep-${rowIndex}-${line}`)
339
+ );
340
+ }
341
+ return;
342
+ }
343
+ body.push(/* @__PURE__ */ jsx("tr", { children: renderCells(cells, null, `row-${rowIndex}`) }, `row-${rowIndex}`));
344
+ });
345
+ return /* @__PURE__ */ jsx(
346
+ "div",
347
+ {
348
+ className: className ? `erp-recsheet__frame ${className}` : "erp-recsheet__frame",
349
+ style,
350
+ "data-more-left": edges.left ? "" : void 0,
351
+ "data-more-right": edges.right ? "" : void 0,
352
+ children: /* @__PURE__ */ jsxs("div", { ref: scrollRef, className: "erp-recsheet__scroll", children: [
353
+ /* @__PURE__ */ jsx(RecordSheetStyles, {}),
354
+ /* @__PURE__ */ jsxs("table", { className: "erp-recsheet", children: [
355
+ /* @__PURE__ */ jsx("colgroup", { children: Array.from({ length: width }, (_, index) => /* @__PURE__ */ jsx("col", { style: { width: `${100 / width}%` } }, index)) }),
356
+ /* @__PURE__ */ jsxs("tbody", { children: [
357
+ title ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { className: "erp-recsheet__title", colSpan: width, children: title }) }) : null,
358
+ body
359
+ ] })
360
+ ] }),
361
+ note ? /* @__PURE__ */ jsx("p", { className: "erp-recsheet__note", children: note }) : null
362
+ ] })
363
+ }
364
+ );
365
+ }
366
+ function useScrollEdges(ref) {
367
+ const [edges, setEdges] = useState({ left: false, right: false });
368
+ useEffect(() => {
369
+ const el = ref.current;
370
+ if (!el) return;
371
+ const update = () => {
372
+ const left = el.scrollLeft > 1;
373
+ const right = el.scrollLeft + el.clientWidth < el.scrollWidth - 1;
374
+ setEdges((prev) => prev.left === left && prev.right === right ? prev : { left, right });
375
+ };
376
+ update();
377
+ el.addEventListener("scroll", update, { passive: true });
378
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(update);
379
+ observer?.observe(el);
380
+ const table = el.querySelector("table");
381
+ if (table) observer?.observe(table);
382
+ return () => {
383
+ el.removeEventListener("scroll", update);
384
+ observer?.disconnect();
385
+ };
386
+ }, [ref]);
387
+ return edges;
388
+ }
389
+ var RecordSheet = boxed(RecordSheetImpl, "RecordSheet");
390
+
391
+ export { RecordSheet, placeRecordSheetCells, stackRecordSheet };
@@ -1,4 +1,4 @@
1
- import { Button } from './chunk-BLFJMMBW.js';
1
+ import { Button } from './chunk-OWM7E55U.js';
2
2
  import { boxed } from './chunk-3JTBOABA.js';
3
3
  import { useEffect, useState } from 'react';
4
4
  import { jsxs, jsx } from 'react/jsx-runtime';
@@ -1,4 +1,4 @@
1
- import { Spinner, EmptyState } from './chunk-BLFJMMBW.js';
1
+ import { Spinner, EmptyState } from './chunk-OWM7E55U.js';
2
2
  import { useState, useEffect } from 'react';
3
3
  import { jsx, jsxs } from 'react/jsx-runtime';
4
4
 
@@ -1,4 +1,4 @@
1
- import { Icon, Badge } from './chunk-BLFJMMBW.js';
1
+ import { Icon, Badge } from './chunk-OWM7E55U.js';
2
2
  import { boxed } from './chunk-3JTBOABA.js';
3
3
  import React from 'react';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -6,21 +6,25 @@ var MAX_ZOOM = 1.8;
6
6
  var PINCH_CLICK_GRACE_MS = 400;
7
7
  var PAN_SLOP_PX = 6;
8
8
  var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
9
- function applyZoom(current, nextScale, focal) {
10
- const scale = clamp(nextScale, MIN_ZOOM, MAX_ZOOM);
9
+ function applyZoom(current, nextScale, focal, minScale = MIN_ZOOM) {
10
+ const scale = clamp(nextScale, Math.min(MIN_ZOOM, minScale), MAX_ZOOM);
11
11
  return {
12
12
  scale,
13
13
  x: focal.x - (focal.x - current.x) / current.scale * scale,
14
14
  y: focal.y - (focal.y - current.y) / current.scale * scale
15
15
  };
16
16
  }
17
- function applyPinch(start, startDistance, startMid, distance, mid) {
17
+ function applyPinch(start, startDistance, startMid, distance, mid, minScale = MIN_ZOOM) {
18
18
  const ratio = startDistance > 0 ? distance / startDistance : 1;
19
- const zoomed = applyZoom(start, start.scale * ratio, startMid);
19
+ const zoomed = applyZoom(start, start.scale * ratio, startMid, minScale);
20
20
  return { ...zoomed, x: zoomed.x + mid.x - startMid.x, y: zoomed.y + mid.y - startMid.y };
21
21
  }
22
22
  function usePanZoomCanvas(options) {
23
23
  const { stopSelector, initialView } = options;
24
+ const minScaleRef = useRef(options.minScale ?? MIN_ZOOM);
25
+ minScaleRef.current = options.minScale ?? MIN_ZOOM;
26
+ const wheelNeedsCtrlRef = useRef(Boolean(options.wheelNeedsCtrl));
27
+ wheelNeedsCtrlRef.current = Boolean(options.wheelNeedsCtrl);
24
28
  const viewportRef = useRef(null);
25
29
  const dragRef = useRef(null);
26
30
  const interactedRef = useRef(false);
@@ -42,6 +46,8 @@ function usePanZoomCanvas(options) {
42
46
  const [nativeFullscreen, setNativeFullscreen] = useState(false);
43
47
  const [pseudoFullscreen, setPseudoFullscreen] = useState(false);
44
48
  const isFullscreen = nativeFullscreen || pseudoFullscreen;
49
+ const isFullscreenRef = useRef(isFullscreen);
50
+ isFullscreenRef.current = isFullscreen;
45
51
  useEffect(() => {
46
52
  const handleFullscreenChange = () => {
47
53
  setNativeFullscreen(document.fullscreenElement === viewportRef.current);
@@ -70,12 +76,13 @@ function usePanZoomCanvas(options) {
70
76
  const viewport = viewportRef.current;
71
77
  if (!viewport) return;
72
78
  const onWheel = (event) => {
79
+ if (wheelNeedsCtrlRef.current && !isFullscreenRef.current && !event.ctrlKey) return;
73
80
  event.preventDefault();
74
81
  interactedRef.current = true;
75
82
  const rect = viewport.getBoundingClientRect();
76
83
  const focal = { x: event.clientX - rect.left, y: event.clientY - rect.top };
77
84
  setView(
78
- (current) => applyZoom(current, current.scale * (event.deltaY > 0 ? 0.9 : 1.1), focal)
85
+ (current) => applyZoom(current, current.scale * (event.deltaY > 0 ? 0.9 : 1.1), focal, minScaleRef.current)
79
86
  );
80
87
  };
81
88
  viewport.addEventListener("wheel", onWheel, { passive: false });
@@ -88,7 +95,7 @@ function usePanZoomCanvas(options) {
88
95
  }, []);
89
96
  const zoomAt = useCallback((nextScale, focal) => {
90
97
  interactedRef.current = true;
91
- setView((current) => applyZoom(current, nextScale, focal));
98
+ setView((current) => applyZoom(current, nextScale, focal, minScaleRef.current));
92
99
  }, []);
93
100
  const localPoint = (event) => {
94
101
  const rect = event.currentTarget.getBoundingClientRect();
@@ -165,7 +172,7 @@ function usePanZoomCanvas(options) {
165
172
  const pinch = pinchRef.current;
166
173
  if (pinch && pointersRef.current.size >= 2) {
167
174
  const { distance, mid } = pinchGeometry();
168
- setView(applyPinch(pinch.start, pinch.distance, pinch.mid, distance, mid));
175
+ setView(applyPinch(pinch.start, pinch.distance, pinch.mid, distance, mid, minScaleRef.current));
169
176
  return;
170
177
  }
171
178
  const pending = pendingRef.current;
@@ -1,6 +1,6 @@
1
- import { ImageLightbox } from './chunk-KZ6U6FKB.js';
2
- import { useSduiSandbox, useSduiAdapter, hostRawFetchOf, defaultAdapter, useSduiAssetResolver } from './chunk-YNEEJCSZ.js';
3
- import { Spinner, Icon, IconButton } from './chunk-BLFJMMBW.js';
1
+ import { ImageLightbox } from './chunk-L6ODTHTX.js';
2
+ import { useSduiSandbox, useSduiAdapter, hostRawFetchOf, defaultAdapter, useSduiAssetResolver } from './chunk-ZBX2CAOO.js';
3
+ import { Spinner, Icon, IconButton } from './chunk-OWM7E55U.js';
4
4
  import { boxed } from './chunk-3JTBOABA.js';
5
5
  import { useState, useRef, useEffect, useCallback } from 'react';
6
6
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
@@ -1,5 +1,5 @@
1
- import { usePanZoomCanvas, MIN_ZOOM, MAX_ZOOM } from './chunk-CSZCWBOE.js';
2
- import { IconButton, Badge } from './chunk-BLFJMMBW.js';
1
+ import { usePanZoomCanvas, MIN_ZOOM, MAX_ZOOM } from './chunk-XDR6HC4U.js';
2
+ import { IconButton, Badge } from './chunk-OWM7E55U.js';
3
3
  import { boxed } from './chunk-3JTBOABA.js';
4
4
  import { useState, useMemo, useCallback, useEffect } from 'react';
5
5
  import { jsxs, jsx } from 'react/jsx-runtime';
@@ -1,5 +1,5 @@
1
- import { DEV } from './chunk-GSM664TX.js';
2
- import { warnOnce } from './chunk-BLFJMMBW.js';
1
+ import { DEV } from './chunk-TAVKGYH5.js';
2
+ import { warnOnce } from './chunk-OWM7E55U.js';
3
3
  import { createContext, useContext } from 'react';
4
4
 
5
5
  function isSafeNavigationTarget(to) {
package/dist/index.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import * as React from 'react';
2
2
  import React__default, { CSSProperties, ReactNode, ComponentType } from 'react';
3
- import { S as SduiPage, a as SduiAction, b as SduiPropValue, c as SduiRule } from './types-CYfIthux.js';
4
- export { d as SduiArgRef, e as SduiCalcRef, f as SduiChild, g as SduiIndexRef, h as SduiItemRef, i as SduiNode, j as SduiPageQuery, k as SduiScalar, l as SduiStateRef } from './types-CYfIthux.js';
3
+ import { S as SduiPage, a as SduiAction, b as SduiPropValue, c as SduiRule } from './types-CMikpoN5.js';
4
+ export { d as SduiArgRef, e as SduiCalcRef, f as SduiChild, g as SduiIndexRef, h as SduiItemRef, i as SduiNode, j as SduiPageQuery, k as SduiScalar, l as SduiStateRef } from './types-CMikpoN5.js';
5
5
  import { S as SduiBoxProps, a as SduiContainerLayoutProps, b as SduiColumnsScale, c as SduiGapScale, d as SduiWeightScale, e as SduiTextAlignScale } from './layout-DmRikwZb.js';
6
6
  export { C as CHILD_LAYOUT_PROPS, L as LAYOUT_CSS, f as SduiAlignScale, g as SduiChildLayoutProps, h as SduiSizeScale, i as SduiSpanScale, j as childLayoutFrom, k as columnsClass, l as containerLayoutStyle, m as joinClass, n as mergeStyle, t as typographyStyle } from './layout-DmRikwZb.js';
7
- import { D as DatePickerProps, a as DateRangePickerProps, T as TimePickerProps } from './DatePicker-DX9juElE.js';
8
- export { b as DatePicker, c as DateRangePicker, d as DateRangeValue, e as TimePicker } from './DatePicker-DX9juElE.js';
7
+ import { D as DatePickerProps, a as DateRangePickerProps, T as TimePickerProps } from './DatePicker-BOOKdd5x.js';
8
+ export { b as DatePicker, c as DateRangePicker, d as DateRangeValue, e as TimePicker } from './DatePicker-BOOKdd5x.js';
9
+ import './calendar-nav-BSGsgJc4.js';
9
10
 
10
11
  /**
11
12
  * 호스트 앱 연결점 — ERP에서는 라우터·erpFetch·SSE가 하던 일을
@@ -461,9 +462,14 @@ interface BadgeProps extends React__default.HTMLAttributes<HTMLSpanElement> {
461
462
  /** 선행 5px 점 — 라이브/진행 상태 */
462
463
  dot?: boolean;
463
464
  size?: 'md' | 'lg';
465
+ /**
466
+ * 긴 문구를 칸 폭 안에서 여러 줄로 감싼다(`wp-badge--wrap`, 1.6.1). 기본은 한 줄 고정 높이다.
467
+ * 예전에는 스펙이 style 로 height:auto·white-space:normal 등을 박아 흉내 냈다.
468
+ */
469
+ wrap?: boolean;
464
470
  children?: React__default.ReactNode;
465
471
  }
466
- declare function Badge({ tone, dot, size, children, className, ...rest }: BadgeProps): React__default.JSX.Element;
472
+ declare function Badge({ tone, dot, size, wrap, children, className, ...rest }: BadgeProps): React__default.JSX.Element;
467
473
  interface TagProps extends React__default.HTMLAttributes<HTMLSpanElement> {
468
474
  /** 선행 점의 CSS 색상 */
469
475
  color?: string;
@@ -768,6 +774,8 @@ interface ResultTableColumn<T> {
768
774
  strong?: boolean;
769
775
  /** CSS width — "180px", "22%" */
770
776
  width?: string;
777
+ /** 한 줄로 둔다 — `wp-table__nowrap`(white-space:nowrap). 날짜·번호처럼 갈라지면 읽기 어려운 짧은 값. */
778
+ nowrap?: boolean;
771
779
  /**
772
780
  * 칸 이름(문자열) — 각 `<td data-label>` 로 나간다. 좁은 화면에서 표를 행 카드로 쌓을 때
773
781
  * 칸마다 "무슨 값인지"를 CSS(`attr(data-label)`)가 붙인다. 생략하면 `header` 가 문자열일
@@ -779,6 +787,12 @@ interface ResultTableColumn<T> {
779
787
  * `title`: 카드 머리(라벨 없이 굵게), `hide`: 모바일 카드에서 숨김.
780
788
  */
781
789
  mobile?: 'title' | 'hide';
790
+ /**
791
+ * 태블릿(768–1023.98px) 표에서의 역할 — `data-tablet` 속성으로 th·td 둘 다에 나간다(모양은 소비자 CSS).
792
+ * `hide`: 태블릿 폭의 표에서 이 열을 뺀다. 모바일 행 카드(`mobile`)와는 따로다 — 태블릿에서 빼고 싶은 긴
793
+ * 목록 열이 폰 카드에서는 값이 있어야 하는 경우가 있다(예전에는 `mobile:'hide'` 를 태블릿에도 빌려 썼다).
794
+ */
795
+ tablet?: 'hide';
782
796
  render?: (row: T, index: number) => React__default.ReactNode;
783
797
  }
784
798
  type SortDir = 'asc' | 'desc';
@@ -793,7 +807,16 @@ interface ResultTableProps<T extends {
793
807
  };
794
808
  onSort?: (key: string) => void;
795
809
  onRowClick?: (row: T) => void;
810
+ /** 체크박스로 고른 행 — `aria-selected="true"`(선택 바탕)만 붙는다. SearchResults 의 일괄 선택이 여기로 온다. */
796
811
  selectedIds?: string[];
812
+ /**
813
+ * 표시 전용 강조 행(1.6.1) — `aria-current="true"` + `wp-row--selected` 클래스. 체크박스 선택과 별개다:
814
+ * 옆 창에 열어 둔 행처럼 "지금 보고 있는 것"을 가리킬 뿐, 일괄 액션의 대상이 되지 않는다.
815
+ * 그래서 체크 선택의 `aria-selected` 를 빌리지 않는다 — 같은 속성이면 스크린리더가 둘을 "선택됨"으로
816
+ * 똑같이 읽는다. 바탕·막대는 클래스가 칠한다(aria-selected 규칙에 기대지 않는다).
817
+ * SearchResults 의 `selectedIds` 가 이리로 온다. id 비교는 문자열로 한다(행 id 가 숫자인 JSON 도 맞게).
818
+ */
819
+ pickedIds?: readonly string[];
797
820
  /** rows가 비었을 때 표 대신 렌더 */
798
821
  empty?: React__default.ReactNode;
799
822
  /** true면 스크롤 창 안에서 보이는 행만 렌더한다 */
@@ -811,7 +834,7 @@ interface ResultTableProps<T extends {
811
834
  }
812
835
  declare function ResultTable<T extends {
813
836
  id: string;
814
- }>({ columns, rows, sort, onSort, onRowClick, selectedIds, empty, virtual, height, rowHeight, stackable, }: ResultTableProps<T>): React__default.JSX.Element;
837
+ }>({ columns, rows, sort, onSort, onRowClick, selectedIds, pickedIds, empty, virtual, height, rowHeight, stackable, }: ResultTableProps<T>): React__default.JSX.Element;
815
838
  interface ResultEmptyStateProps {
816
839
  /** 목록 이름 — 기본 문구에 들어간다("대여 이력이 없습니다") */
817
840
  title: string;
@@ -1139,6 +1162,10 @@ interface SearchColumn<T> {
1139
1162
  * 아무 열도 `title` 이 아니면 `primaryKey` 열, 그다음 첫 `strong` 열이 머리가 된다.
1140
1163
  */
1141
1164
  mobile?: 'title' | 'hide';
1165
+ /** 태블릿(768–1023.98px) 표에서 이 열을 뺀다(`hide`) — 모바일 카드의 `mobile` 과 따로 정한다. */
1166
+ tablet?: 'hide';
1167
+ /** 표 칸을 한 줄로 둔다(`wp-table__nowrap`). */
1168
+ nowrap?: boolean;
1142
1169
  accessor?: (item: T) => string | number | Date | null | undefined;
1143
1170
  render?: (item: T) => React__default.ReactNode;
1144
1171
  }
@@ -1277,6 +1304,17 @@ interface SearchResultsProps<T> {
1277
1304
  * (asset-18). endpoint 목록(RemoteSearchResults)은 이 값을 켠다.
1278
1305
  */
1279
1306
  serverFiltered?: boolean;
1307
+ /**
1308
+ * 표시 전용 강조(1.6.1) — 이 id 의 행(목록 보기)·카드(카드 보기)를 강조한다. 체크박스 선택(`actions`)과
1309
+ * **별개**다: 일괄 액션의 대상이 되지 않고, 선택 바도 세우지 않는다. 옆 창·상세에 열어 둔 행을 목록에서
1310
+ * 가리키는 자리다 — 스펙은 `"selectedIds": [{"$state":"rt.$picked.id"}]` 처럼 준다.
1311
+ *
1312
+ * 행은 `aria-current="true"` + `wp-row--selected`, 카드는 `wp-card--selected` + `is-selected` +
1313
+ * `aria-current="true"` 다. 체크 선택의 `aria-selected` 는 쓰지 않는다 — 스크린리더가 강조와 체크 선택을
1314
+ * 똑같이 "선택됨"으로 읽게 되고, 카드(버튼·일반 상자 역할)는 aria-selected 를 가질 수도 없다. 비교는
1315
+ * 문자열로 하고, 바인딩이 아직 비어 `null`·`undefined` 인 원소는 버린다.
1316
+ */
1317
+ selectedIds?: string[];
1280
1318
  }
1281
1319
  /**
1282
1320
  * 스펙이 준 페이지 크기 목록을 다듬는다 — 양의 정수만, 중복 제거, 오름차순.
@@ -1285,7 +1323,7 @@ interface SearchResultsProps<T> {
1285
1323
  declare function normalizePageSizes(sizes: number[] | undefined): number[] | undefined;
1286
1324
  declare function SearchResults<T extends {
1287
1325
  id: string;
1288
- }>({ item, columns, gridConfig, leftFilters, rightFilters, tabs, summary, initialView, views, pageSizes, className, style, searchParams, onParamsChange, actions, disableGrid, disableList, onRowClick, title, headerActions, isLoading, hideHeader, advancedSearchFields: rawAdvancedSearchFields, searchPlaceholder, virtual, serverUnsortable, primaryKey, emptyTitle, emptyText, serverFiltered, }: SearchResultsProps<T>): React__default.JSX.Element;
1326
+ }>({ item, columns, gridConfig, leftFilters, rightFilters, tabs, summary, initialView, views, pageSizes, className, style, searchParams, onParamsChange, actions, disableGrid, disableList, onRowClick, title, headerActions, isLoading, hideHeader, advancedSearchFields: rawAdvancedSearchFields, searchPlaceholder, virtual, serverUnsortable, primaryKey, emptyTitle, emptyText, serverFiltered, selectedIds, }: SearchResultsProps<T>): React__default.JSX.Element;
1289
1327
 
1290
1328
  /**
1291
1329
  * 셀 표현 어휘 — 목록 컬럼의 셀과 카드 행의 값이 **같은 규칙**을 쓴다.
@@ -1473,6 +1511,13 @@ interface SduiSearchColumn {
1473
1511
  * `hide`: 모바일에서 숨김(식별자·보조 열). 생략하면 보통 칸(라벨 + 값).
1474
1512
  */
1475
1513
  mobile?: 'title' | 'hide';
1514
+ /**
1515
+ * 태블릿(768–1023.98px) 표에서의 역할 — `hide`: 이 폭의 표에서 열을 뺀다. 모바일 카드(`mobile`)와 따로다:
1516
+ * 태블릿 표에서는 넘치는 긴 목록 열이라도 폰 카드에서는 보여 줄 수 있다. 생략하면 태블릿에서도 보인다.
1517
+ */
1518
+ tablet?: 'hide';
1519
+ /** 표 칸을 한 줄로 둔다(white-space:nowrap) — 날짜·번호처럼 두 줄로 갈라지면 읽기 어려운 짧은 값. */
1520
+ nowrap?: boolean;
1476
1521
  /** 셀 표현. 생략하면 row[key]를 그대로 출력한다 — 어휘는 `cells.tsx` 참고 */
1477
1522
  cell?: SduiCell;
1478
1523
  }
@@ -1577,6 +1622,11 @@ interface SduiSearchCommonProps {
1577
1622
  * 조건이 없으면 빈 문자열. 생략하면 아무것도 적지 않는다.
1578
1623
  */
1579
1624
  queryStateKey?: string;
1625
+ /**
1626
+ * 표시 전용 강조 — 이 id 의 행·카드를 강조한다(체크박스 선택과 별개, 일괄 액션 대상 아님).
1627
+ * 옆 창에 열어 둔 행을 가리키는 자리: `"selectedIds": [{"$state":"rt.$picked.id"}]`. 계약은 SearchResultsProps.selectedIds.
1628
+ */
1629
+ selectedIds?: string[];
1580
1630
  }
1581
1631
  interface SduiSearchResultsProps extends SduiSearchCommonProps {
1582
1632
  data: SearchRow[];