@wipco/sdui 0.14.2 → 0.16.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 (45) hide show
  1. package/dist/{chunk-6NAJ2D4A.js → chunk-3MS4UWWZ.js} +2 -2
  2. package/dist/{chunk-CXZYC7IN.js → chunk-C3JY6GA3.js} +1 -1
  3. package/dist/{chunk-G4QIJHEP.js → chunk-C4JRSZGS.js} +1 -1
  4. package/dist/{chunk-VBCUIM7N.js → chunk-EFYEOT47.js} +2 -2
  5. package/dist/{chunk-FC2F37QH.js → chunk-GZB6YLUI.js} +1 -1
  6. package/dist/{chunk-YGSU34B3.js → chunk-HIZ2OVJA.js} +1 -1
  7. package/dist/{chunk-5C4SIRSW.js → chunk-IIRENCDS.js} +1 -1
  8. package/dist/{chunk-K5ZJTLFP.js → chunk-J6OHRAPS.js} +2 -2
  9. package/dist/{chunk-X5X3FKCQ.js → chunk-JVBZRDC2.js} +1 -1
  10. package/dist/{chunk-BOSSWJTN.js → chunk-KT3ZVBLN.js} +1 -1
  11. package/dist/{chunk-LN6IOMCK.js → chunk-LDJ2CFFH.js} +1 -1
  12. package/dist/{chunk-4NPTFNAL.js → chunk-M3TDEPD2.js} +1 -1
  13. package/dist/{chunk-5YVXVGQ2.js → chunk-MYJ2KEIP.js} +1 -1
  14. package/dist/{chunk-BS4LGGQV.js → chunk-PYSTET2C.js} +2 -2
  15. package/dist/{chunk-HINKJD6C.js → chunk-Q2EUB5UV.js} +1 -1
  16. package/dist/{chunk-K55ODWRY.js → chunk-QDEZXA34.js} +1 -1
  17. package/dist/{chunk-F2MKI3W5.js → chunk-QWJ3OBBL.js} +1 -1
  18. package/dist/{chunk-R3IK6EPA.js → chunk-THEP3VWY.js} +22 -58
  19. package/dist/{chunk-LMHIRQZ6.js → chunk-WIDDHR7L.js} +2 -0
  20. package/dist/{chunk-JND5NXUT.js → chunk-WVBU3SZM.js} +2 -2
  21. package/dist/chunk-XPBZMECW.js +73 -0
  22. package/dist/index.d.ts +83 -3
  23. package/dist/index.js +87 -7
  24. package/dist/meta.js +30 -1
  25. package/dist/rich.js +3 -3
  26. package/dist/styles.css +21 -0
  27. package/dist/widgets/AutocompleteField.js +3 -3
  28. package/dist/widgets/ColumnBoard.js +2 -2
  29. package/dist/widgets/DiffView.js +2 -2
  30. package/dist/widgets/Disclosure.js +2 -2
  31. package/dist/widgets/EventCalendar.js +2 -2
  32. package/dist/widgets/ImageLightbox.js +3 -2
  33. package/dist/widgets/InlineEdit.js +2 -2
  34. package/dist/widgets/JsonField.js +2 -2
  35. package/dist/widgets/OrgChart.js +2 -2
  36. package/dist/widgets/PeriodFilter.js +2 -2
  37. package/dist/widgets/PrintSheet.js +2 -2
  38. package/dist/widgets/SearchableMultiPicker.js +3 -3
  39. package/dist/widgets/SecureScreen.js +2 -2
  40. package/dist/widgets/TagMultiSelect.js +2 -2
  41. package/dist/widgets/Timeline.js +3 -3
  42. package/dist/widgets/TreePicker.js +4 -4
  43. package/dist/widgets/TreeView.js +3 -3
  44. package/dist/widgets/index.js +36 -35
  45. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
- import { includesLooseSearch } from './chunk-FC2F37QH.js';
2
- import { Icon, Button, Input, Spinner } from './chunk-LMHIRQZ6.js';
1
+ import { includesLooseSearch } from './chunk-GZB6YLUI.js';
2
+ import { Icon, Button, Input, Spinner } from './chunk-WIDDHR7L.js';
3
3
  import { boxed } from './chunk-SAN4IX6N.js';
4
4
  import { useId, useRef, useState, useEffect, useMemo } from 'react';
5
5
  import { jsxs, jsx } from 'react/jsx-runtime';
@@ -1,4 +1,4 @@
1
- import { SegmentedControl, IconButton, Button, Tag, EmptyState, Icon, Badge } from './chunk-LMHIRQZ6.js';
1
+ import { SegmentedControl, IconButton, Button, Tag, EmptyState, Icon, Badge } from './chunk-WIDDHR7L.js';
2
2
  import { boxed } from './chunk-SAN4IX6N.js';
3
3
  import { useState, useMemo } from 'react';
4
4
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
@@ -1,4 +1,4 @@
1
- import { Badge, Checkbox, IconButton, Icon } from './chunk-LMHIRQZ6.js';
1
+ import { Badge, Checkbox, IconButton, Icon } from './chunk-WIDDHR7L.js';
2
2
  import { boxed } from './chunk-SAN4IX6N.js';
3
3
  import { useState, useEffect } from 'react';
4
4
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
@@ -1,5 +1,5 @@
1
- import { Disclosure } from './chunk-YGSU34B3.js';
2
- import { Icon } from './chunk-LMHIRQZ6.js';
1
+ import { Disclosure } from './chunk-HIZ2OVJA.js';
2
+ import { Icon } from './chunk-WIDDHR7L.js';
3
3
  import { boxed } from './chunk-SAN4IX6N.js';
4
4
  import { jsx, jsxs } from 'react/jsx-runtime';
5
5
 
@@ -1,4 +1,4 @@
1
- import { Input, Button, IconButton, Tag, Field, Checkbox } from './chunk-LMHIRQZ6.js';
1
+ import { Input, Button, IconButton, Tag, Field, Checkbox } from './chunk-WIDDHR7L.js';
2
2
  import { useRef, useEffect, useState, useMemo } from 'react';
3
3
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
4
 
@@ -1,4 +1,4 @@
1
- import { Icon } from './chunk-LMHIRQZ6.js';
1
+ import { Icon } from './chunk-WIDDHR7L.js';
2
2
  import { boxed } from './chunk-SAN4IX6N.js';
3
3
  import { useState, useId, useCallback } from 'react';
4
4
  import { jsxs, jsx } from 'react/jsx-runtime';
@@ -1,4 +1,4 @@
1
- import { Icon } from './chunk-LMHIRQZ6.js';
1
+ import { Icon } from './chunk-WIDDHR7L.js';
2
2
  import { boxed } from './chunk-SAN4IX6N.js';
3
3
  import { useRef, useState, useEffect } from 'react';
4
4
  import { jsxs, jsx } from 'react/jsx-runtime';
@@ -1,5 +1,5 @@
1
- import { includesLooseSearch } from './chunk-FC2F37QH.js';
2
- import { Button, Dialog, Input, Tag, Checkbox } from './chunk-LMHIRQZ6.js';
1
+ import { includesLooseSearch } from './chunk-GZB6YLUI.js';
2
+ import { Button, Dialog, Input, Tag, Checkbox } from './chunk-WIDDHR7L.js';
3
3
  import { boxed } from './chunk-SAN4IX6N.js';
4
4
  import React, { useMemo, useState, useEffect } from 'react';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -1,4 +1,4 @@
1
- import { Textarea, Icon } from './chunk-LMHIRQZ6.js';
1
+ import { Textarea, Icon } from './chunk-WIDDHR7L.js';
2
2
  import { boxed } from './chunk-SAN4IX6N.js';
3
3
  import { useState, useRef, useId, useEffect, useCallback } from 'react';
4
4
  import { jsxs, jsx } from 'react/jsx-runtime';
@@ -1,4 +1,4 @@
1
- import { IconButton, Input } from './chunk-LMHIRQZ6.js';
1
+ import { IconButton, Input } from './chunk-WIDDHR7L.js';
2
2
  import { boxed } from './chunk-SAN4IX6N.js';
3
3
  import { useState, useRef, useEffect, useCallback } from 'react';
4
4
  import { jsxs, jsx } from 'react/jsx-runtime';
@@ -1,4 +1,4 @@
1
- import { Icon, Badge } from './chunk-LMHIRQZ6.js';
1
+ import { Icon, Badge } from './chunk-WIDDHR7L.js';
2
2
  import { boxed } from './chunk-SAN4IX6N.js';
3
3
  import React from 'react';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -1,4 +1,4 @@
1
- import { SegmentedControl, Select, Input } from './chunk-LMHIRQZ6.js';
1
+ import { SegmentedControl, Select, Input } from './chunk-WIDDHR7L.js';
2
2
  import { boxed } from './chunk-SAN4IX6N.js';
3
3
  import { useState, useMemo } from 'react';
4
4
  import { jsxs, jsx } from 'react/jsx-runtime';
@@ -1,4 +1,4 @@
1
- import { Button } from './chunk-LMHIRQZ6.js';
1
+ import { Button } from './chunk-WIDDHR7L.js';
2
2
  import { boxed } from './chunk-SAN4IX6N.js';
3
3
  import { useEffect } from 'react';
4
4
  import { jsxs, jsx } from 'react/jsx-runtime';
@@ -1,5 +1,5 @@
1
- import { buildCascadingTree } from './chunk-FC2F37QH.js';
2
- import { Input, IconButton, EmptyState, Icon, Badge } from './chunk-LMHIRQZ6.js';
1
+ import { buildCascadingTree } from './chunk-GZB6YLUI.js';
2
+ import { Input, IconButton, EmptyState, Icon, Badge } from './chunk-WIDDHR7L.js';
3
3
  import { boxed } from './chunk-SAN4IX6N.js';
4
4
  import React, { useMemo, useState, useRef, useEffect } from 'react';
5
5
  import { jsxs, jsx } from 'react/jsx-runtime';
@@ -1,4 +1,4 @@
1
- import { Icon } from './chunk-LMHIRQZ6.js';
1
+ import { Icon } from './chunk-WIDDHR7L.js';
2
2
  import { boxed } from './chunk-SAN4IX6N.js';
3
3
  import { createContext, useState, useRef, useMemo, useCallback, useEffect, useLayoutEffect, useContext } from 'react';
4
4
  import { jsxs, jsx } from 'react/jsx-runtime';
@@ -1,4 +1,4 @@
1
- import { Badge, Tag } from './chunk-LMHIRQZ6.js';
1
+ import { Badge, Tag } from './chunk-WIDDHR7L.js';
2
2
  import { boxed } from './chunk-SAN4IX6N.js';
3
3
  import { useMemo, useState } from 'react';
4
4
  import { jsx } from 'react/jsx-runtime';
@@ -1,4 +1,4 @@
1
- import { IconButton, Badge } from './chunk-LMHIRQZ6.js';
1
+ import { IconButton, Badge } from './chunk-WIDDHR7L.js';
2
2
  import { boxed } from './chunk-SAN4IX6N.js';
3
3
  import { useRef, useState, useMemo, useCallback, useEffect } from 'react';
4
4
  import { jsxs, jsx } from 'react/jsx-runtime';
@@ -1,8 +1,7 @@
1
- import { IconButton } from './chunk-LMHIRQZ6.js';
2
- import { boxed } from './chunk-SAN4IX6N.js';
1
+ import { IconButton } from './chunk-WIDDHR7L.js';
3
2
  import { useState, useRef, useCallback, useEffect } from 'react';
4
3
  import { createPortal } from 'react-dom';
5
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
5
 
7
6
  var MIN_SCALE = 1;
8
7
  var MAX_SCALE = 8;
@@ -12,8 +11,7 @@ var INITIAL_VIEW = { scale: 1, x: 0, y: 0 };
12
11
  function clamp(value, min, max) {
13
12
  return Math.min(max, Math.max(min, value));
14
13
  }
15
- function ImageLightboxImpl({ src, alt, thumbClassName, thumbStyle, children }) {
16
- const [open, setOpen] = useState(false);
14
+ function ImageViewer({ src, alt, open, onClose }) {
17
15
  const [view, setView] = useState(INITIAL_VIEW);
18
16
  const [dragging, setDragging] = useState(false);
19
17
  const stageRef = useRef(null);
@@ -21,16 +19,11 @@ function ImageLightboxImpl({ src, alt, thumbClassName, thumbStyle, children }) {
21
19
  const viewRef = useRef(view);
22
20
  viewRef.current = view;
23
21
  const dragRef = useRef(null);
24
- const openViewer = (event) => {
25
- event.stopPropagation();
26
- setView(INITIAL_VIEW);
27
- setOpen(true);
28
- };
29
22
  const close = useCallback(() => {
30
23
  dragRef.current = null;
31
24
  setDragging(false);
32
- setOpen(false);
33
- }, []);
25
+ onClose();
26
+ }, [onClose]);
34
27
  const clampOffset = useCallback((x, y, scale) => {
35
28
  const stage = stageRef.current;
36
29
  const img = imgRef.current;
@@ -54,7 +47,11 @@ function ImageLightboxImpl({ src, alt, thumbClassName, thumbStyle, children }) {
54
47
  const cy = clientY - rect.top - rect.height / 2;
55
48
  const px = (cx - prev.x) / prev.scale;
56
49
  const py = (cy - prev.y) / prev.scale;
57
- const next = clampOffset(cx - px * nextScale, cy - py * nextScale, nextScale);
50
+ const next = clampOffset(
51
+ cx - px * nextScale,
52
+ cy - py * nextScale,
53
+ nextScale
54
+ );
58
55
  setView({ scale: nextScale, x: next.x, y: next.y });
59
56
  },
60
57
  [clampOffset]
@@ -69,6 +66,9 @@ function ImageLightboxImpl({ src, alt, thumbClassName, thumbStyle, children }) {
69
66
  [zoomAt]
70
67
  );
71
68
  const resetView = useCallback(() => setView(INITIAL_VIEW), []);
69
+ useEffect(() => {
70
+ if (open) setView(INITIAL_VIEW);
71
+ }, [open]);
72
72
  useEffect(() => {
73
73
  if (!open) return;
74
74
  const onKeyDown = (event) => {
@@ -117,7 +117,11 @@ function ImageLightboxImpl({ src, alt, thumbClassName, thumbStyle, children }) {
117
117
  const dx = event.clientX - drag.startX;
118
118
  const dy = event.clientY - drag.startY;
119
119
  if (Math.abs(dx) + Math.abs(dy) > DRAG_CLOSE_THRESHOLD) drag.moved = true;
120
- const next = clampOffset(drag.originX + dx, drag.originY + dy, viewRef.current.scale);
120
+ const next = clampOffset(
121
+ drag.originX + dx,
122
+ drag.originY + dy,
123
+ viewRef.current.scale
124
+ );
121
125
  setView((prev) => ({ ...prev, x: next.x, y: next.y }));
122
126
  };
123
127
  const endDrag = (event) => {
@@ -138,7 +142,8 @@ function ImageLightboxImpl({ src, alt, thumbClassName, thumbStyle, children }) {
138
142
  };
139
143
  const zoomPercent = Math.round(view.scale * 100);
140
144
  const isInitial = view.scale === 1 && view.x === 0 && view.y === 0;
141
- const overlay = open ? createPortal(
145
+ if (!open || typeof document === "undefined") return null;
146
+ return createPortal(
142
147
  /* @__PURE__ */ jsxs(
143
148
  "div",
144
149
  {
@@ -264,48 +269,7 @@ function ImageLightboxImpl({ src, alt, thumbClassName, thumbStyle, children }) {
264
269
  }
265
270
  ),
266
271
  document.body
267
- ) : null;
268
- if (children) {
269
- return /* @__PURE__ */ jsxs(Fragment, { children: [
270
- /* @__PURE__ */ jsx(
271
- "span",
272
- {
273
- style: { display: "contents" },
274
- onClick: openViewer,
275
- onKeyDown: (event) => {
276
- if (event.key === "Enter" || event.key === " ") {
277
- event.preventDefault();
278
- openViewer(event);
279
- }
280
- },
281
- children
282
- }
283
- ),
284
- overlay
285
- ] });
286
- }
287
- return /* @__PURE__ */ jsxs(Fragment, { children: [
288
- /* @__PURE__ */ jsx(
289
- "img",
290
- {
291
- src,
292
- alt: alt ?? "",
293
- role: "button",
294
- tabIndex: 0,
295
- className: thumbClassName,
296
- style: { cursor: "zoom-in", ...thumbStyle },
297
- onClick: openViewer,
298
- onKeyDown: (event) => {
299
- if (event.key === "Enter" || event.key === " ") {
300
- event.preventDefault();
301
- openViewer(event);
302
- }
303
- }
304
- }
305
- ),
306
- overlay
307
- ] });
272
+ );
308
273
  }
309
- var ImageLightbox = boxed(ImageLightboxImpl, "ImageLightbox");
310
274
 
311
- export { ImageLightbox };
275
+ export { ImageViewer };
@@ -908,6 +908,7 @@ function Card({
908
908
  subtitle,
909
909
  actions,
910
910
  footer,
911
+ cover,
911
912
  variant = "default",
912
913
  selected = false,
913
914
  interactive = false,
@@ -923,6 +924,7 @@ function Card({
923
924
  if (className) classes.push(className);
924
925
  const bodyClass = padding === "tight" ? "wp-card__body wp-card__body--tight" : "wp-card__body";
925
926
  return /* @__PURE__ */ jsxs("div", { className: classes.join(" "), ...rest, children: [
927
+ cover ? /* @__PURE__ */ jsx("div", { className: "wp-card__cover", children: cover }) : null,
926
928
  title || actions ? /* @__PURE__ */ jsxs("div", { className: "wp-card__head", children: [
927
929
  /* @__PURE__ */ jsxs("div", { style: { minWidth: 0 }, children: [
928
930
  /* @__PURE__ */ jsx("div", { className: "wp-card__title", children: title }),
@@ -1,5 +1,5 @@
1
- import { resolveTreeNodes, TreeView } from './chunk-BS4LGGQV.js';
2
- import { Field, Dialog, Icon, Button, Input } from './chunk-LMHIRQZ6.js';
1
+ import { resolveTreeNodes, TreeView } from './chunk-PYSTET2C.js';
2
+ import { Field, Dialog, Icon, Button, Input } from './chunk-WIDDHR7L.js';
3
3
  import { boxed } from './chunk-SAN4IX6N.js';
4
4
  import { useState, useMemo, useEffect } from 'react';
5
5
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
@@ -0,0 +1,73 @@
1
+ import { ImageViewer } from './chunk-THEP3VWY.js';
2
+ import { boxed } from './chunk-SAN4IX6N.js';
3
+ import { useState } from 'react';
4
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
5
+
6
+ function ImageLightboxImpl({
7
+ src,
8
+ alt,
9
+ thumbClassName,
10
+ thumbStyle,
11
+ children
12
+ }) {
13
+ const [open, setOpen] = useState(false);
14
+ const openViewer = (event) => {
15
+ event.stopPropagation();
16
+ setOpen(true);
17
+ };
18
+ const overlay = /* @__PURE__ */ jsx(
19
+ ImageViewer,
20
+ {
21
+ src,
22
+ alt,
23
+ open,
24
+ onClose: () => setOpen(false)
25
+ }
26
+ );
27
+ if (children) {
28
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
29
+ /* @__PURE__ */ jsx(
30
+ "span",
31
+ {
32
+ style: { display: "contents" },
33
+ onClick: openViewer,
34
+ onKeyDown: (event) => {
35
+ if (event.key === "Enter" || event.key === " ") {
36
+ event.preventDefault();
37
+ openViewer(event);
38
+ }
39
+ },
40
+ children
41
+ }
42
+ ),
43
+ overlay
44
+ ] });
45
+ }
46
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
47
+ /* @__PURE__ */ jsx(
48
+ "img",
49
+ {
50
+ src,
51
+ alt: alt ?? "",
52
+ role: "button",
53
+ tabIndex: 0,
54
+ className: thumbClassName,
55
+ style: { cursor: "zoom-in", ...thumbStyle },
56
+ onClick: openViewer,
57
+ onKeyDown: (event) => {
58
+ if (event.key === "Enter" || event.key === " ") {
59
+ event.preventDefault();
60
+ openViewer(event);
61
+ }
62
+ }
63
+ }
64
+ ),
65
+ overlay
66
+ ] });
67
+ }
68
+ var ImageLightbox = boxed(
69
+ ImageLightboxImpl,
70
+ "ImageLightbox"
71
+ );
72
+
73
+ export { ImageLightbox };
package/dist/index.d.ts CHANGED
@@ -360,6 +360,8 @@ interface GridCardRow<T> {
360
360
  show?: (item: T) => boolean;
361
361
  }
362
362
  interface GridCardConfig<T> {
363
+ /** 머리 위 표지 — 보통 대표 사진(cells.tsx의 imageRenderer)을 넘긴다 */
364
+ cover?: (item: T) => React__default.ReactNode;
363
365
  title: (item: T) => React__default.ReactNode;
364
366
  subtitle?: (item: T) => React__default.ReactNode;
365
367
  badge?: (item: T) => {
@@ -500,12 +502,46 @@ interface SduiNumberCell {
500
502
  prefix?: string;
501
503
  suffix?: string;
502
504
  }
505
+ /**
506
+ * 값(이미지 URL)을 사진으로. 목록 행·카드 표지가 같은 규칙을 쓴다.
507
+ *
508
+ * **사진 값은 언제나 눌러서 크게 볼 수 있다.** 확대를 옵션으로 두면 스펙마다 켜고 끄는
509
+ * 자리가 생기고, 켜지 않은 화면에서는 목록의 작은 썸네일이 사실상 못 보는 사진이 된다.
510
+ * 원본 보기는 사진이라는 값 자체의 성질로 본다. 뷰어는 코어의 `ImageViewer` 하나뿐이라
511
+ * (휠 줌·드래그 패닝·툴바) 목록·카드 표지·상세 첨부가 전부 같게 동작한다.
512
+ *
513
+ * **크게 볼 수 있는 것은 실제로 뜬 사진뿐이다.** URL이 없으면, 그리고 URL이 있어도
514
+ * 로딩이 실패하면(끊긴 링크·삭제된 객체) 자리표시자 상자로 떨어지고 눌리지 않는다 —
515
+ * 빈 화면이 뜨는 다이얼로그를 열어 주는 것보다 낫다.
516
+ *
517
+ * **값이 비어도 상자 크기는 같다.** 칸을 비우면 사진 있는 행만 높이가 달라져 표가
518
+ * 흔들린다. 아이콘 이름은 도메인마다 다르므로(장비는 building-2, 사람은 user)
519
+ * `fallbackIcon`으로 받고 기본값만 중립적인 `image`로 둔다.
520
+ */
521
+ interface SduiImageCell {
522
+ type: 'image';
523
+ /** 기본 72 — 카드 커버로 쓰일 때는 컨테이너가 '100%'로 덮어쓴다 */
524
+ width?: number;
525
+ /** 기본 52 */
526
+ height?: number;
527
+ /** object-fit (기본 cover) */
528
+ fit?: 'cover' | 'contain';
529
+ /** 사진이 없을 때 그릴 아이콘 이름 (기본 image) */
530
+ fallbackIcon?: string;
531
+ /** 대체 텍스트를 담은 행의 필드 — 없으면 빈 alt(장식으로 취급) */
532
+ altKey?: string;
533
+ }
503
534
  /** 날짜 서식(ko-KR). 파싱되지 않는 값은 원문 그대로. */
504
535
  interface SduiDateCell {
505
536
  type: 'date';
506
537
  format?: 'date' | 'datetime' | 'time';
507
538
  }
508
- type SduiCell = SduiTextCell | SduiBadgeCell | SduiTagCell | SduiLinkCell | SduiProgressCell | SduiIconCell | SduiNumberCell | SduiDateCell;
539
+ type SduiCell = SduiTextCell | SduiImageCell | SduiBadgeCell | SduiTagCell | SduiLinkCell | SduiProgressCell | SduiIconCell | SduiNumberCell | SduiDateCell;
540
+ /**
541
+ * 사진 셀 — 카드 커버도 이 함수를 쓴다(`width: '100%'` 을 넘겨 폭만 늘린다).
542
+ * 상자 크기를 값이 있든 없든 똑같이 잡아 두는 것이 요점이다.
543
+ */
544
+ declare function imageRenderer(cell: SduiImageCell, key: string, width?: number | string): (row: SearchRow) => React__default.ReactNode;
509
545
  /**
510
546
  * 셀 스펙 → 렌더 함수. `undefined`를 돌려주면 호출자가 원문을 그대로 출력한다
511
547
  * (표에서는 `<td>{row[key]}</td>`, 카드에서는 값 문자열).
@@ -554,6 +590,15 @@ interface SduiGridCardAction {
554
590
  run: SduiAction | SduiAction[];
555
591
  }
556
592
  interface SduiGridCard {
593
+ /**
594
+ * 카드 표지로 쓸 사진 URL이 담긴 행의 필드. 주면 머리 위에 꽉 차게 깔린다.
595
+ * 사진이 없는 행은 자리표시자 상자가 대신 서서 카드 높이가 들쭉날쭉해지지 않는다.
596
+ */
597
+ imageKey?: string;
598
+ /** 표지 높이 (기본 150) */
599
+ imageHeight?: number;
600
+ /** 표지에 사진이 없을 때 그릴 아이콘 (기본 image) */
601
+ imageFallbackIcon?: string;
557
602
  titleKey: string;
558
603
  subtitleKey?: string;
559
604
  /** 카드 우상단 배지 — labelMap은 표시 텍스트만 바꾸고 톤 조회 키는 원래 값이다(셀 badge와 같은 규칙) */
@@ -874,6 +919,11 @@ interface CardProps extends Omit<React__default.HTMLAttributes<HTMLDivElement>,
874
919
  actions?: React__default.ReactNode;
875
920
  /** 우측 정렬 푸터 행 */
876
921
  footer?: React__default.ReactNode;
922
+ /**
923
+ * 머리 위에 꽉 차게 깔리는 표지 — 목록 카드의 대표 사진 자리다.
924
+ * 카드 안쪽 여백을 무시하고 위 두 모서리만 카드 곡률을 따른다.
925
+ */
926
+ cover?: React__default.ReactNode;
877
927
  variant?: 'default' | 'raised' | 'flat' | 'sunken';
878
928
  selected?: boolean;
879
929
  interactive?: boolean;
@@ -881,7 +931,7 @@ interface CardProps extends Omit<React__default.HTMLAttributes<HTMLDivElement>,
881
931
  padding?: 'default' | 'tight' | 'none';
882
932
  children?: React__default.ReactNode;
883
933
  }
884
- declare function Card({ title, subtitle, actions, footer, variant, selected, interactive, padding, children, className, ...rest }: CardProps): React__default.JSX.Element;
934
+ declare function Card({ title, subtitle, actions, footer, cover, variant, selected, interactive, padding, children, className, ...rest }: CardProps): React__default.JSX.Element;
885
935
  interface BadgeProps extends React__default.HTMLAttributes<HTMLSpanElement> {
886
936
  tone?: 'neutral' | 'info' | 'success' | 'warning' | 'danger' | 'brand';
887
937
  /** 선행 5px 점 — 라이브/진행 상태 */
@@ -1691,4 +1741,34 @@ declare class NodeErrorBoundary extends React__default.Component<Props, State> {
1691
1741
  render(): string | number | bigint | boolean | Iterable<React__default.ReactNode> | Promise<string | number | bigint | boolean | React__default.ReactPortal | React__default.ReactElement<unknown, string | React__default.JSXElementConstructor<any>> | Iterable<React__default.ReactNode> | null | undefined> | React__default.JSX.Element | null | undefined;
1692
1742
  }
1693
1743
 
1694
- export { Actions, type ActionsProps, Alert, type AlertProps, Avatar, type AvatarProps, Badge, type BadgeProps, type BadgeTone, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Button, type ButtonProps, Can, type CanProps, Card, type CardProps, type CascadingSearchOption, Checkbox, type CheckboxProps, Col, type ColProps, type CustomActionHandler, type CustomValidator, DatePickerProps, DateRangePickerProps, Dialog, type DialogProps, Divider, EmptyState, type EmptyStateProps, Field, type FieldProps, Grid, type GridCardConfig, type GridCardRow, type GridProps, Icon, IconButton, type IconButtonProps, type IconProps, Input, type InputProps, type Justify, type KeyValueItem, KeyValueList, type KeyValueListProps, type MeterTone, NodeErrorBoundary, PageHead, type PageHeadProps, Pagination, type PaginationProps, ProgressBar, type ProgressBarProps, Radio, type RadioProps, RemoteSearchResults, type RemoteSearchResultsProps, ResultTable, type ResultTableColumn, type ResultTableProps, Row, type RowProps, SduiAction, type SduiAdapter, type SduiAssetContext, type SduiAssetResolver, type SduiBadgeCell, SduiBoxProps, type SduiCanFn, type SduiCanQuery, type SduiCanSetting, type SduiCell, SduiColumnsScale, SduiContainerLayoutProps, type SduiDateCell, SduiDialog, type SduiDispatch, SduiForm, type SduiFormConfig, type SduiFormProps, SduiGapScale, type SduiGridCard, type SduiGridCardAction, type SduiGridCardRow, type SduiIconCell, type SduiLinkCell, type SduiNumberCell, SduiPage, SduiPermissionProvider, type SduiProgressCell, SduiPropValue, type SduiRegistry, SduiRenderer, type SduiRendererProps, type SduiRowCondition, SduiRule, type SduiRuntime, type SduiSearchAction, type SduiSearchColumn, SduiSearchDataProvider, SduiSearchResults, type SduiSearchResultsProps, type SduiTagCell, SduiTextAlignScale, type SduiTextCell, SduiWeightScale, type SearchAction, type SearchColumn, type SearchFetcher, type SearchField, type SearchFilter, type SearchOption, type SearchPage, SearchResults, type SearchResultsProps, type SearchRow, SearchToolbar, type SearchValues, type SearchViewItem, Section, type SectionProps, SegmentedControl, type SegmentedControlProps, type SegmentedItem, Select, type SelectOption, type SelectOptionRecord, type SelectProps, Show, type ShowProps, SidebarNav, type SidebarNavItem, type SidebarNavProps, type SidebarNavSection, type SortDir, SortHandle, type SortHandleProps, SortableList, type SortableListProps, Spacer, type SpacerProps, type SpecIssue, Spinner, type SpinnerProps, StatCard, type StatCardProps, Switch, type SwitchProps, type TabItem, Table, type TableColumn, type TableProps, Tabs, type TabsProps, Tag, type TagProps, Text, type TextPreset, type TextProps, Textarea, type TextareaProps, Toast, type ToastProps, Tooltip, type TooltipProps, boxed, cascadingValuePaths, cellRenderer, cleanSearchValues, createRegistry, defaultAdapter, defaultRegistry, includesLooseSearch, joinMultiValue, normalizePageSizes, normalizeSearchText, searchFieldKeys, searchValuesFromParams, sortableDropIndex, sortableMove, splitMultiValue, useRemoteSearchPage, useSduiAssetResolver, useSduiCan, useSduiDispatch, useSduiRuntime, useSduiSandbox, useSearchFetcher, validateSpec };
1744
+ /**
1745
+ * 원본 보기 오버레이 — 사진을 크게 보는 단 하나의 구현.
1746
+ *
1747
+ * 목록 셀(`search/cells.tsx`)·카드 표지·상세의 첨부 사진(`widgets/ImageLightbox`)이
1748
+ * 전부 이것을 쓴다. 예전에는 위젯에만 있어서 코어의 셀은 확대를 흉내조차 낼 수 없었고,
1749
+ * 그 자리를 다른 방식(다이얼로그)으로 메우자 **같은 사진인데 화면마다 확대가 달랐다.**
1750
+ */
1751
+
1752
+ interface ImageViewerProps {
1753
+ /** 원본 이미지 URL */
1754
+ src: string;
1755
+ alt?: string;
1756
+ /** 열림 여부 — 트리거는 부르는 쪽이 소유한다 */
1757
+ open: boolean;
1758
+ onClose: () => void;
1759
+ }
1760
+ /**
1761
+ * 원본 보기 오버레이. 트리거(썸네일·표 칸·카드 표지…)는 부르는 쪽이 그리고,
1762
+ * 여기서는 **열린 뒤의 전부**를 맡는다 — 휠 줌(커서 앵커)·드래그 패닝·툴바·
1763
+ * Escape·배경 클릭·본문 스크롤 잠금.
1764
+ *
1765
+ * 사진을 크게 보는 자리가 화면마다 다르면 안 되므로(목록 셀·카드 표지·상세 첨부)
1766
+ * 구현은 여기 하나뿐이다. `widgets/ImageLightbox`도 이것을 감싼 트리거일 뿐이다.
1767
+ *
1768
+ * **포털이지만 React 이벤트는 트리거 쪽 트리로 버블한다** — 그래서 오버레이 뿌리에서
1769
+ * click·pointerdown·dblclick 전파를 끊는다. 이게 없으면 목록에서 연 뷰어의 사진을
1770
+ * 눌렀을 때 그 행의 rowClick(상세 이동)이 함께 발동한다.
1771
+ */
1772
+ declare function ImageViewer({ src, alt, open, onClose }: ImageViewerProps): React__default.ReactPortal | null;
1773
+
1774
+ export { Actions, type ActionsProps, Alert, type AlertProps, Avatar, type AvatarProps, Badge, type BadgeProps, type BadgeTone, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Button, type ButtonProps, Can, type CanProps, Card, type CardProps, type CascadingSearchOption, Checkbox, type CheckboxProps, Col, type ColProps, type CustomActionHandler, type CustomValidator, DatePickerProps, DateRangePickerProps, Dialog, type DialogProps, Divider, EmptyState, type EmptyStateProps, Field, type FieldProps, Grid, type GridCardConfig, type GridCardRow, type GridProps, Icon, IconButton, type IconButtonProps, type IconProps, ImageViewer, type ImageViewerProps, Input, type InputProps, type Justify, type KeyValueItem, KeyValueList, type KeyValueListProps, type MeterTone, NodeErrorBoundary, PageHead, type PageHeadProps, Pagination, type PaginationProps, ProgressBar, type ProgressBarProps, Radio, type RadioProps, RemoteSearchResults, type RemoteSearchResultsProps, ResultTable, type ResultTableColumn, type ResultTableProps, Row, type RowProps, SduiAction, type SduiAdapter, type SduiAssetContext, type SduiAssetResolver, type SduiBadgeCell, SduiBoxProps, type SduiCanFn, type SduiCanQuery, type SduiCanSetting, type SduiCell, SduiColumnsScale, SduiContainerLayoutProps, type SduiDateCell, SduiDialog, type SduiDispatch, SduiForm, type SduiFormConfig, type SduiFormProps, SduiGapScale, type SduiGridCard, type SduiGridCardAction, type SduiGridCardRow, type SduiIconCell, type SduiImageCell, type SduiLinkCell, type SduiNumberCell, SduiPage, SduiPermissionProvider, type SduiProgressCell, SduiPropValue, type SduiRegistry, SduiRenderer, type SduiRendererProps, type SduiRowCondition, SduiRule, type SduiRuntime, type SduiSearchAction, type SduiSearchColumn, SduiSearchDataProvider, SduiSearchResults, type SduiSearchResultsProps, type SduiTagCell, SduiTextAlignScale, type SduiTextCell, SduiWeightScale, type SearchAction, type SearchColumn, type SearchFetcher, type SearchField, type SearchFilter, type SearchOption, type SearchPage, SearchResults, type SearchResultsProps, type SearchRow, SearchToolbar, type SearchValues, type SearchViewItem, Section, type SectionProps, SegmentedControl, type SegmentedControlProps, type SegmentedItem, Select, type SelectOption, type SelectOptionRecord, type SelectProps, Show, type ShowProps, SidebarNav, type SidebarNavItem, type SidebarNavProps, type SidebarNavSection, type SortDir, SortHandle, type SortHandleProps, SortableList, type SortableListProps, Spacer, type SpacerProps, type SpecIssue, Spinner, type SpinnerProps, StatCard, type StatCardProps, Switch, type SwitchProps, type TabItem, Table, type TableColumn, type TableProps, Tabs, type TabsProps, Tag, type TagProps, Text, type TextPreset, type TextProps, Textarea, type TextareaProps, Toast, type ToastProps, Tooltip, type TooltipProps, boxed, cascadingValuePaths, cellRenderer, cleanSearchValues, createRegistry, defaultAdapter, defaultRegistry, imageRenderer, includesLooseSearch, joinMultiValue, normalizePageSizes, normalizeSearchText, searchFieldKeys, searchValuesFromParams, sortableDropIndex, sortableMove, splitMultiValue, useRemoteSearchPage, useSduiAssetResolver, useSduiCan, useSduiDispatch, useSduiRuntime, useSduiSandbox, useSearchFetcher, validateSpec };
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
- import { DEV, HoistedStyle, isSafeNavigationTarget, useSduiAdapter, hostFetchOf, DateRangePicker, DatePicker, useSduiSandbox, resolveAdapter, assetResolverOf, SduiAdapterContext } from './chunk-HINKJD6C.js';
2
- export { DatePicker, DateRangePicker, defaultAdapter, useSduiAssetResolver, useSduiSandbox } from './chunk-HINKJD6C.js';
3
- import { normalizeSearchFields, searchValuesFromParams, splitMultiValue, includesLooseSearch, cascadingValuePaths, SearchToolbar, searchFieldKeys, Popover } from './chunk-FC2F37QH.js';
4
- export { SearchToolbar, cascadingValuePaths, cleanSearchValues, includesLooseSearch, joinMultiValue, normalizeSearchText, searchFieldKeys, searchValuesFromParams, splitMultiValue } from './chunk-FC2F37QH.js';
5
- import { EmptyState, Button, Checkbox, SegmentedControl, Spinner, Select, Pagination, Icon, Card, Badge, ProgressBar, Tag, Toast, Textarea, Tabs, Table, Switch, StatCard, Input, IconButton, Field, Alert, warnOnce, Dialog } from './chunk-LMHIRQZ6.js';
6
- 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 } from './chunk-LMHIRQZ6.js';
1
+ import { DEV, HoistedStyle, isSafeNavigationTarget, useSduiAdapter, hostFetchOf, DateRangePicker, DatePicker, useSduiSandbox, resolveAdapter, assetResolverOf, SduiAdapterContext } from './chunk-Q2EUB5UV.js';
2
+ export { DatePicker, DateRangePicker, defaultAdapter, useSduiAssetResolver, useSduiSandbox } from './chunk-Q2EUB5UV.js';
3
+ import { ImageViewer } from './chunk-THEP3VWY.js';
4
+ export { ImageViewer } from './chunk-THEP3VWY.js';
5
+ import { normalizeSearchFields, searchValuesFromParams, splitMultiValue, includesLooseSearch, cascadingValuePaths, SearchToolbar, searchFieldKeys, Popover } from './chunk-GZB6YLUI.js';
6
+ export { SearchToolbar, cascadingValuePaths, cleanSearchValues, includesLooseSearch, joinMultiValue, normalizeSearchText, searchFieldKeys, searchValuesFromParams, splitMultiValue } from './chunk-GZB6YLUI.js';
7
+ import { EmptyState, Button, Checkbox, SegmentedControl, Spinner, Select, Pagination, Icon, Card, Badge, ProgressBar, Tag, Toast, Textarea, Tabs, Table, Switch, StatCard, Input, IconButton, Field, Alert, warnOnce, Dialog } from './chunk-WIDDHR7L.js';
8
+ 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 } from './chunk-WIDDHR7L.js';
7
9
  import { boxed, mergeStyle, containerLayoutStyle, joinClass, columnsClass, typographyStyle, LAYOUT_CSS, childLayoutFrom, CHILD_LAYOUT_PROPS } from './chunk-SAN4IX6N.js';
8
10
  export { CHILD_LAYOUT_PROPS, LAYOUT_CSS, boxed, childLayoutFrom, columnsClass, containerLayoutStyle, joinClass, mergeStyle, typographyStyle } from './chunk-SAN4IX6N.js';
9
11
  import React, { createContext, useState, useMemo, useContext, useRef, useEffect, useCallback } from 'react';
@@ -319,6 +321,72 @@ function Show({ when, children }) {
319
321
  function Divider({ style, className }) {
320
322
  return /* @__PURE__ */ jsx("hr", { className: joinClass("wp-divider", className), style });
321
323
  }
324
+ function imageRenderer(cell, key, width = cell.width ?? 72) {
325
+ const height = cell.height ?? 52;
326
+ return (row) => /* @__PURE__ */ jsx(
327
+ ImageCellView,
328
+ {
329
+ url: typeof row[key] === "string" ? row[key] : "",
330
+ alt: cell.altKey ? String(row[cell.altKey] ?? "") : "",
331
+ width,
332
+ height,
333
+ fit: cell.fit ?? "cover",
334
+ fallbackIcon: cell.fallbackIcon ?? "image"
335
+ }
336
+ );
337
+ }
338
+ function ImageCellView({
339
+ url,
340
+ alt,
341
+ width,
342
+ height,
343
+ fit,
344
+ fallbackIcon
345
+ }) {
346
+ const [open, setOpen] = useState(false);
347
+ const [failed, setFailed] = useState(false);
348
+ const box = { width, height, borderRadius: "var(--wp-radius-lg)", flex: "none" };
349
+ if (!url || failed) {
350
+ return /* @__PURE__ */ jsx(
351
+ "span",
352
+ {
353
+ className: "wp-imagecell wp-imagecell--empty",
354
+ style: box,
355
+ "aria-hidden": alt ? void 0 : true,
356
+ role: alt ? "img" : void 0,
357
+ "aria-label": alt || void 0,
358
+ children: /* @__PURE__ */ jsx(Icon, { name: fallbackIcon, size: Math.round(height * 0.4) })
359
+ }
360
+ );
361
+ }
362
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
363
+ /* @__PURE__ */ jsx(
364
+ "button",
365
+ {
366
+ type: "button",
367
+ className: "wp-imagecell__btn",
368
+ style: box,
369
+ onClick: (event) => {
370
+ event.stopPropagation();
371
+ setOpen(true);
372
+ },
373
+ "aria-label": alt ? `${alt} \uC0AC\uC9C4 \uD06C\uAC8C \uBCF4\uAE30` : "\uC0AC\uC9C4 \uD06C\uAC8C \uBCF4\uAE30",
374
+ children: /* @__PURE__ */ jsx(
375
+ "img",
376
+ {
377
+ className: "wp-imagecell",
378
+ src: url,
379
+ alt,
380
+ loading: "lazy",
381
+ onError: () => setFailed(true),
382
+ style: { width: "100%", height: "100%", objectFit: fit }
383
+ }
384
+ )
385
+ }
386
+ ),
387
+ /* @__PURE__ */ jsx(ImageViewer, { src: url, alt, open, onClose: () => setOpen(false) })
388
+ ] });
389
+ }
322
390
  function toNumber(value) {
323
391
  if (typeof value === "number") return Number.isFinite(value) ? value : null;
324
392
  if (typeof value === "string") {
@@ -417,6 +485,8 @@ function cellRenderer(cell, key, dispatch) {
417
485
  const raw = String(row[key] ?? "");
418
486
  return raw ? formatDate(raw, cell.format) : "";
419
487
  };
488
+ case "image":
489
+ return imageRenderer(cell, key);
420
490
  case "text":
421
491
  return void 0;
422
492
  default:
@@ -1012,6 +1082,7 @@ function GridCard({
1012
1082
  Card,
1013
1083
  {
1014
1084
  interactive: Boolean(onClick),
1085
+ cover: config.cover?.(item),
1015
1086
  title: config.title(item),
1016
1087
  subtitle: config.subtitle?.(item),
1017
1088
  actions: badge || config.actions ? /* @__PURE__ */ jsxs("span", { className: "wp-row", children: [
@@ -1106,6 +1177,15 @@ function mapCardRows(rows, dispatch) {
1106
1177
  function mapGridCard(gridCard, dispatch) {
1107
1178
  if (!gridCard) return void 0;
1108
1179
  return {
1180
+ cover: gridCard.imageKey ? imageRenderer(
1181
+ {
1182
+ height: gridCard.imageHeight ?? 150,
1183
+ fallbackIcon: gridCard.imageFallbackIcon,
1184
+ altKey: gridCard.titleKey
1185
+ },
1186
+ gridCard.imageKey,
1187
+ "100%"
1188
+ ) : void 0,
1109
1189
  title: (row) => String(row[gridCard.titleKey] ?? ""),
1110
1190
  subtitle: gridCard.subtitleKey ? (row) => String(row[gridCard.subtitleKey] ?? "") : void 0,
1111
1191
  badge: gridCard.badge ? (row) => {
@@ -2784,4 +2864,4 @@ function SduiRenderer({
2784
2864
  );
2785
2865
  }
2786
2866
 
2787
- export { Actions, Can, Col, Divider, Grid, KeyValueList, NodeErrorBoundary, PageHead, RemoteSearchResults, ResultTable, Row, SduiDialog, SduiForm, SduiPermissionProvider, SduiRenderer, SduiSearchDataProvider, SduiSearchResults, SearchResults, Section, Show, SortHandle, SortableList, Spacer, Text, cellRenderer, createRegistry, defaultRegistry, normalizePageSizes, sortableDropIndex, sortableMove, useRemoteSearchPage, useSduiCan, useSduiDispatch, useSduiRuntime, useSearchFetcher, validateSpec };
2867
+ export { Actions, Can, Col, Divider, Grid, KeyValueList, NodeErrorBoundary, PageHead, RemoteSearchResults, ResultTable, Row, SduiDialog, SduiForm, SduiPermissionProvider, SduiRenderer, SduiSearchDataProvider, SduiSearchResults, SearchResults, Section, Show, SortHandle, SortableList, Spacer, Text, cellRenderer, createRegistry, defaultRegistry, imageRenderer, normalizePageSizes, sortableDropIndex, sortableMove, useRemoteSearchPage, useSduiCan, useSduiDispatch, useSduiRuntime, useSearchFetcher, validateSpec };
package/dist/meta.js CHANGED
@@ -1802,9 +1802,30 @@ var searchCellFields = [
1802
1802
  { value: "progress", label: "\uC9C4\uD589 \uB9C9\uB300" },
1803
1803
  { value: "icon", label: "\uC544\uC774\uCF58" },
1804
1804
  { value: "number", label: "\uC22B\uC790" },
1805
- { value: "date", label: "\uB0A0\uC9DC" }
1805
+ { value: "date", label: "\uB0A0\uC9DC" },
1806
+ { value: "image", label: "\uC0AC\uC9C4" }
1807
+ ]
1808
+ },
1809
+ { name: "width", kind: "number", label: "\uC0AC\uC9C4 \uAC00\uB85C(px)", description: "type=image (\uAE30\uBCF8 72)" },
1810
+ { name: "height", kind: "number", label: "\uC0AC\uC9C4 \uC138\uB85C(px)", description: "type=image (\uAE30\uBCF8 52)" },
1811
+ {
1812
+ name: "fit",
1813
+ kind: "enum",
1814
+ label: "\uC0AC\uC9C4 \uCC44\uC6C0",
1815
+ description: "type=image",
1816
+ default: "cover",
1817
+ options: [
1818
+ { value: "cover", label: "\uAF49 \uCC44\uC6C0(\uC798\uB9BC)" },
1819
+ { value: "contain", label: "\uC804\uCCB4 \uBCF4\uC784(\uC5EC\uBC31)" }
1806
1820
  ]
1807
1821
  },
1822
+ {
1823
+ name: "fallbackIcon",
1824
+ kind: "icon",
1825
+ label: "\uC0AC\uC9C4 \uC5C6\uC744 \uB54C \uC544\uC774\uCF58",
1826
+ description: "type=image (\uAE30\uBCF8 image)"
1827
+ },
1828
+ { name: "altKey", kind: "string", label: "\uB300\uCCB4 \uD14D\uC2A4\uD2B8 \uD544\uB4DC", description: "type=image" },
1808
1829
  { name: "toneMap", kind: "object", label: "\uAC12 \u2192 \uD1A4", description: "type=badge (\uC5C6\uB294 \uAC12\uC740 neutral)" },
1809
1830
  {
1810
1831
  name: "labelMap",
@@ -1959,6 +1980,14 @@ var searchCommonProps = [
1959
1980
  description: "\uC8FC\uBA74 \uACA9\uC790 \uBCF4\uAE30\uAC00 \uCF1C\uC9C4\uB2E4(disableGrid\uC758 \uAE30\uBCF8\uAC12\uC774 \uC774 \uAC12\uC758 \uC720\uBB34\uB2E4).",
1960
1981
  group: "data",
1961
1982
  fields: [
1983
+ {
1984
+ name: "imageKey",
1985
+ kind: "string",
1986
+ label: "\uD45C\uC9C0 \uC0AC\uC9C4 \uD544\uB4DC",
1987
+ description: "\uC8FC\uBA74 \uCE74\uB4DC \uBA38\uB9AC \uC704\uC5D0 \uAF49 \uCC28\uAC8C \uAE54\uB9B0\uB2E4. \uC0AC\uC9C4\uC774 \uC5C6\uB294 \uD589\uC740 \uC790\uB9AC\uD45C\uC2DC\uC790 \uC0C1\uC790\uAC00 \uC120\uB2E4."
1988
+ },
1989
+ { name: "imageHeight", kind: "number", label: "\uD45C\uC9C0 \uB192\uC774(px)", description: "\uAE30\uBCF8 150" },
1990
+ { name: "imageFallbackIcon", kind: "icon", label: "\uD45C\uC9C0 \uC790\uB9AC\uD45C\uC2DC\uC790 \uC544\uC774\uCF58", description: "\uAE30\uBCF8 image" },
1962
1991
  { name: "titleKey", kind: "string", label: "\uC81C\uBAA9 \uD544\uB4DC", required: true },
1963
1992
  { name: "subtitleKey", kind: "string", label: "\uBD80\uC81C \uD544\uB4DC" },
1964
1993
  { name: "badge", kind: "object", label: "\uBC30\uC9C0", description: "{ key, toneMap, labelMap } \u2014 labelMap\uC740 \uD45C\uC2DC \uBB38\uAD6C\uB9CC \uBC14\uAFBC\uB2E4" },
package/dist/rich.js CHANGED
@@ -1,6 +1,6 @@
1
- import { useSduiAssetResolver, HoistedStyle } from './chunk-HINKJD6C.js';
2
- export { DatePicker, DateRangePicker } from './chunk-HINKJD6C.js';
3
- import { Spinner, EmptyState } from './chunk-LMHIRQZ6.js';
1
+ import { useSduiAssetResolver, HoistedStyle } from './chunk-Q2EUB5UV.js';
2
+ export { DatePicker, DateRangePicker } from './chunk-Q2EUB5UV.js';
3
+ import { Spinner, EmptyState } from './chunk-WIDDHR7L.js';
4
4
  import './chunk-SAN4IX6N.js';
5
5
  import { useRef, useCallback, useMemo, useState, useEffect, useLayoutEffect } from 'react';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
package/dist/styles.css CHANGED
@@ -568,10 +568,31 @@
568
568
  .wp-card--selected { border-color: var(--wp-navy-600); }
569
569
  .wp-card--interactive { cursor: pointer; transition: box-shadow 0.12s, transform 0.12s; }
570
570
  .wp-card--interactive:hover { box-shadow: var(--wp-shadow-md); transform: translateY(-1px); }
571
+ /* 표지 — 머리 위에 꽉 차게 깔린다. 카드가 overflow를 자르지 않으므로 위 두 모서리는
572
+ 여기서 직접 깎는다(카드 곡률에서 테두리 1px을 뺀 값). */
573
+ .wp-card__cover {
574
+ overflow: hidden; background: var(--wp-surface-sunken); line-height: 0;
575
+ border-top-left-radius: calc(var(--wp-radius-lg) - 1px);
576
+ border-top-right-radius: calc(var(--wp-radius-lg) - 1px);
577
+ }
578
+ .wp-card__cover > .wp-imagecell,
579
+ .wp-card__cover > .wp-imagecell__btn { width: 100%; border-radius: 0; }
571
580
  .wp-card__head {
572
581
  display: flex; align-items: center; justify-content: space-between; gap: var(--wp-space-3);
573
582
  padding: 14px 20px; border-bottom: 1px solid var(--wp-border-subtle);
574
583
  }
584
+ /* 사진 셀 — 목록 칸과 카드 표지가 같은 상자를 쓴다(cells.tsx의 imageRenderer). */
585
+ .wp-imagecell { display: block; object-fit: cover; }
586
+ .wp-imagecell--empty {
587
+ display: inline-flex; align-items: center; justify-content: center;
588
+ background: var(--wp-surface-sunken); color: var(--wp-text-disabled);
589
+ }
590
+ /* 뜬 사진은 눌러서 원본을 연다 — 버튼이지만 생김새는 사진 그대로다. */
591
+ .wp-imagecell__btn {
592
+ display: block; padding: 0; border: 0; background: none; cursor: zoom-in; overflow: hidden;
593
+ border-radius: inherit;
594
+ }
595
+ .wp-imagecell__btn:focus-visible { outline: 2px solid var(--wp-navy-600); outline-offset: 2px; }
575
596
  .wp-card__title { font-size: 14px; font-weight: 700; color: var(--wp-text-strong); }
576
597
  .wp-card__sub { font-size: 12px; color: var(--wp-text-muted); margin-top: 2px; }
577
598
  .wp-card__body { padding: 20px; display: flex; flex-direction: column; gap: var(--wp-space-4); min-width: 0; }
@@ -1,4 +1,4 @@
1
- export { AutocompleteField, toAutocompleteOptions } from '../chunk-6NAJ2D4A.js';
2
- import '../chunk-FC2F37QH.js';
3
- import '../chunk-LMHIRQZ6.js';
1
+ export { AutocompleteField, toAutocompleteOptions } from '../chunk-3MS4UWWZ.js';
2
+ import '../chunk-GZB6YLUI.js';
3
+ import '../chunk-WIDDHR7L.js';
4
4
  import '../chunk-SAN4IX6N.js';
@@ -1,3 +1,3 @@
1
- export { ColumnBoard } from '../chunk-G4QIJHEP.js';
2
- import '../chunk-LMHIRQZ6.js';
1
+ export { ColumnBoard } from '../chunk-C4JRSZGS.js';
2
+ import '../chunk-WIDDHR7L.js';
3
3
  import '../chunk-SAN4IX6N.js';
@@ -1,3 +1,3 @@
1
- export { DiffView } from '../chunk-LN6IOMCK.js';
2
- import '../chunk-LMHIRQZ6.js';
1
+ export { DiffView } from '../chunk-LDJ2CFFH.js';
2
+ import '../chunk-WIDDHR7L.js';
3
3
  import '../chunk-SAN4IX6N.js';
@@ -1,3 +1,3 @@
1
- export { Disclosure } from '../chunk-YGSU34B3.js';
2
- import '../chunk-LMHIRQZ6.js';
1
+ export { Disclosure } from '../chunk-HIZ2OVJA.js';
2
+ import '../chunk-WIDDHR7L.js';
3
3
  import '../chunk-SAN4IX6N.js';
@@ -1,3 +1,3 @@
1
- export { EventCalendar } from '../chunk-CXZYC7IN.js';
2
- import '../chunk-LMHIRQZ6.js';
1
+ export { EventCalendar } from '../chunk-C3JY6GA3.js';
2
+ import '../chunk-WIDDHR7L.js';
3
3
  import '../chunk-SAN4IX6N.js';
@@ -1,3 +1,4 @@
1
- export { ImageLightbox } from '../chunk-R3IK6EPA.js';
2
- import '../chunk-LMHIRQZ6.js';
1
+ export { ImageLightbox } from '../chunk-XPBZMECW.js';
2
+ import '../chunk-THEP3VWY.js';
3
+ import '../chunk-WIDDHR7L.js';
3
4
  import '../chunk-SAN4IX6N.js';
@@ -1,3 +1,3 @@
1
- export { InlineEdit } from '../chunk-BOSSWJTN.js';
2
- import '../chunk-LMHIRQZ6.js';
1
+ export { InlineEdit } from '../chunk-KT3ZVBLN.js';
2
+ import '../chunk-WIDDHR7L.js';
3
3
  import '../chunk-SAN4IX6N.js';
@@ -1,3 +1,3 @@
1
- export { JsonField } from '../chunk-X5X3FKCQ.js';
2
- import '../chunk-LMHIRQZ6.js';
1
+ export { JsonField } from '../chunk-JVBZRDC2.js';
2
+ import '../chunk-WIDDHR7L.js';
3
3
  import '../chunk-SAN4IX6N.js';
@@ -1,3 +1,3 @@
1
- export { OrgChart } from '../chunk-F2MKI3W5.js';
2
- import '../chunk-LMHIRQZ6.js';
1
+ export { OrgChart } from '../chunk-QWJ3OBBL.js';
2
+ import '../chunk-WIDDHR7L.js';
3
3
  import '../chunk-SAN4IX6N.js';
@@ -1,3 +1,3 @@
1
- export { PeriodFilter } from '../chunk-4NPTFNAL.js';
2
- import '../chunk-LMHIRQZ6.js';
1
+ export { PeriodFilter } from '../chunk-M3TDEPD2.js';
2
+ import '../chunk-WIDDHR7L.js';
3
3
  import '../chunk-SAN4IX6N.js';
@@ -1,3 +1,3 @@
1
- export { PrintSheet } from '../chunk-5YVXVGQ2.js';
2
- import '../chunk-LMHIRQZ6.js';
1
+ export { PrintSheet } from '../chunk-MYJ2KEIP.js';
2
+ import '../chunk-WIDDHR7L.js';
3
3
  import '../chunk-SAN4IX6N.js';
@@ -1,4 +1,4 @@
1
- export { SearchableMultiPicker } from '../chunk-K5ZJTLFP.js';
2
- import '../chunk-FC2F37QH.js';
3
- import '../chunk-LMHIRQZ6.js';
1
+ export { SearchableMultiPicker } from '../chunk-J6OHRAPS.js';
2
+ import '../chunk-GZB6YLUI.js';
3
+ import '../chunk-WIDDHR7L.js';
4
4
  import '../chunk-SAN4IX6N.js';
@@ -1,3 +1,3 @@
1
- export { SecureScreen } from '../chunk-5C4SIRSW.js';
2
- import '../chunk-LMHIRQZ6.js';
1
+ export { SecureScreen } from '../chunk-IIRENCDS.js';
2
+ import '../chunk-WIDDHR7L.js';
3
3
  import '../chunk-SAN4IX6N.js';
@@ -1,3 +1,3 @@
1
- export { TagMultiSelect } from '../chunk-K55ODWRY.js';
2
- import '../chunk-LMHIRQZ6.js';
1
+ export { TagMultiSelect } from '../chunk-QDEZXA34.js';
2
+ import '../chunk-WIDDHR7L.js';
3
3
  import '../chunk-SAN4IX6N.js';
@@ -1,4 +1,4 @@
1
- export { Timeline } from '../chunk-VBCUIM7N.js';
2
- import '../chunk-YGSU34B3.js';
3
- import '../chunk-LMHIRQZ6.js';
1
+ export { Timeline } from '../chunk-EFYEOT47.js';
2
+ import '../chunk-HIZ2OVJA.js';
3
+ import '../chunk-WIDDHR7L.js';
4
4
  import '../chunk-SAN4IX6N.js';
@@ -1,5 +1,5 @@
1
- export { TreePicker } from '../chunk-JND5NXUT.js';
2
- import '../chunk-BS4LGGQV.js';
3
- import '../chunk-FC2F37QH.js';
4
- import '../chunk-LMHIRQZ6.js';
1
+ export { TreePicker } from '../chunk-WVBU3SZM.js';
2
+ import '../chunk-PYSTET2C.js';
3
+ import '../chunk-GZB6YLUI.js';
4
+ import '../chunk-WIDDHR7L.js';
5
5
  import '../chunk-SAN4IX6N.js';
@@ -1,4 +1,4 @@
1
- export { TreeView, buildTreeNodes, pruneExcluded, resolveTreeNodes } from '../chunk-BS4LGGQV.js';
2
- import '../chunk-FC2F37QH.js';
3
- import '../chunk-LMHIRQZ6.js';
1
+ export { TreeView, buildTreeNodes, pruneExcluded, resolveTreeNodes } from '../chunk-PYSTET2C.js';
2
+ import '../chunk-GZB6YLUI.js';
3
+ import '../chunk-WIDDHR7L.js';
4
4
  import '../chunk-SAN4IX6N.js';
@@ -1,46 +1,47 @@
1
- import { SearchableMultiPicker } from '../chunk-K5ZJTLFP.js';
2
- export { SearchableMultiPicker } from '../chunk-K5ZJTLFP.js';
3
- import { SecureScreen } from '../chunk-5C4SIRSW.js';
4
- export { SecureScreen } from '../chunk-5C4SIRSW.js';
1
+ import { SearchableMultiPicker } from '../chunk-J6OHRAPS.js';
2
+ export { SearchableMultiPicker } from '../chunk-J6OHRAPS.js';
3
+ import { SecureScreen } from '../chunk-IIRENCDS.js';
4
+ export { SecureScreen } from '../chunk-IIRENCDS.js';
5
5
  import { StackedMeter } from '../chunk-BMZKZGJF.js';
6
6
  export { StackedMeter } from '../chunk-BMZKZGJF.js';
7
7
  import { Strip } from '../chunk-ZH5MLOZS.js';
8
8
  export { Strip } from '../chunk-ZH5MLOZS.js';
9
9
  import { SummaryTable } from '../chunk-O4U7Y3XL.js';
10
10
  export { SummaryTable } from '../chunk-O4U7Y3XL.js';
11
- import { TagMultiSelect } from '../chunk-K55ODWRY.js';
12
- export { TagMultiSelect } from '../chunk-K55ODWRY.js';
13
- import { Timeline } from '../chunk-VBCUIM7N.js';
14
- export { Timeline } from '../chunk-VBCUIM7N.js';
15
- import { TreePicker } from '../chunk-JND5NXUT.js';
16
- export { TreePicker } from '../chunk-JND5NXUT.js';
17
- import { TreeView } from '../chunk-BS4LGGQV.js';
18
- export { TreeView, buildTreeNodes } from '../chunk-BS4LGGQV.js';
19
- import { EventCalendar } from '../chunk-CXZYC7IN.js';
20
- export { EventCalendar } from '../chunk-CXZYC7IN.js';
21
- import { ImageLightbox } from '../chunk-R3IK6EPA.js';
22
- export { ImageLightbox } from '../chunk-R3IK6EPA.js';
23
- import { InlineEdit } from '../chunk-BOSSWJTN.js';
24
- export { InlineEdit } from '../chunk-BOSSWJTN.js';
25
- import { JsonField } from '../chunk-X5X3FKCQ.js';
26
- export { JsonField } from '../chunk-X5X3FKCQ.js';
11
+ import { TagMultiSelect } from '../chunk-QDEZXA34.js';
12
+ export { TagMultiSelect } from '../chunk-QDEZXA34.js';
13
+ import { Timeline } from '../chunk-EFYEOT47.js';
14
+ export { Timeline } from '../chunk-EFYEOT47.js';
15
+ import { TreePicker } from '../chunk-WVBU3SZM.js';
16
+ export { TreePicker } from '../chunk-WVBU3SZM.js';
17
+ import { TreeView } from '../chunk-PYSTET2C.js';
18
+ export { TreeView, buildTreeNodes } from '../chunk-PYSTET2C.js';
19
+ import { EventCalendar } from '../chunk-C3JY6GA3.js';
20
+ export { EventCalendar } from '../chunk-C3JY6GA3.js';
21
+ import { ImageLightbox } from '../chunk-XPBZMECW.js';
22
+ export { ImageLightbox } from '../chunk-XPBZMECW.js';
23
+ import { InlineEdit } from '../chunk-KT3ZVBLN.js';
24
+ export { InlineEdit } from '../chunk-KT3ZVBLN.js';
25
+ import { JsonField } from '../chunk-JVBZRDC2.js';
26
+ export { JsonField } from '../chunk-JVBZRDC2.js';
27
27
  import { LiveClock } from '../chunk-Z3WGIFJP.js';
28
28
  export { LiveClock } from '../chunk-Z3WGIFJP.js';
29
- import { OrgChart } from '../chunk-F2MKI3W5.js';
30
- export { OrgChart } from '../chunk-F2MKI3W5.js';
31
- import { PeriodFilter } from '../chunk-4NPTFNAL.js';
32
- export { PeriodFilter } from '../chunk-4NPTFNAL.js';
33
- import { PrintSheet } from '../chunk-5YVXVGQ2.js';
34
- export { PrintSheet } from '../chunk-5YVXVGQ2.js';
35
- export { AutocompleteField, toAutocompleteOptions } from '../chunk-6NAJ2D4A.js';
36
- import '../chunk-FC2F37QH.js';
37
- import { ColumnBoard } from '../chunk-G4QIJHEP.js';
38
- export { ColumnBoard } from '../chunk-G4QIJHEP.js';
39
- import { DiffView } from '../chunk-LN6IOMCK.js';
40
- export { DiffView } from '../chunk-LN6IOMCK.js';
41
- import { Disclosure } from '../chunk-YGSU34B3.js';
42
- export { Disclosure } from '../chunk-YGSU34B3.js';
43
- import '../chunk-LMHIRQZ6.js';
29
+ import { OrgChart } from '../chunk-QWJ3OBBL.js';
30
+ export { OrgChart } from '../chunk-QWJ3OBBL.js';
31
+ import { PeriodFilter } from '../chunk-M3TDEPD2.js';
32
+ export { PeriodFilter } from '../chunk-M3TDEPD2.js';
33
+ import { PrintSheet } from '../chunk-MYJ2KEIP.js';
34
+ export { PrintSheet } from '../chunk-MYJ2KEIP.js';
35
+ import '../chunk-THEP3VWY.js';
36
+ export { AutocompleteField, toAutocompleteOptions } from '../chunk-3MS4UWWZ.js';
37
+ import '../chunk-GZB6YLUI.js';
38
+ import { ColumnBoard } from '../chunk-C4JRSZGS.js';
39
+ export { ColumnBoard } from '../chunk-C4JRSZGS.js';
40
+ import { DiffView } from '../chunk-LDJ2CFFH.js';
41
+ export { DiffView } from '../chunk-LDJ2CFFH.js';
42
+ import { Disclosure } from '../chunk-HIZ2OVJA.js';
43
+ export { Disclosure } from '../chunk-HIZ2OVJA.js';
44
+ import '../chunk-WIDDHR7L.js';
44
45
  import '../chunk-SAN4IX6N.js';
45
46
 
46
47
  // src/widgets/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipco/sdui",
3
- "version": "0.14.2",
3
+ "version": "0.16.0",
4
4
  "description": "WIPCO 서버 주도 UI(SDUI) 렌더러 — JSON 페이지 스펙을 React 컴포넌트로 렌더링",
5
5
  "keywords": [
6
6
  "sdui",