sellmate-design-system-react 9.0.0-beta.104 → 9.0.0-beta.107

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.
package/dist/index.cjs CHANGED
@@ -3107,43 +3107,52 @@ var Icon_helpFill = (p) => {
3107
3107
  }
3108
3108
  );
3109
3109
  };
3110
+ var HELP_OUTLINE_VARIANTS = {
3111
+ 16: {
3112
+ stroke: 1.25,
3113
+ ring: "M14.6666 8.00004C14.6666 4.31814 11.6819 1.33337 7.99998 1.33337C4.31808 1.33337 1.33331 4.31814 1.33331 8.00004C1.33331 11.6819 4.31808 14.6667 7.99998 14.6667C11.6819 14.6667 14.6666 11.6819 14.6666 8.00004Z",
3114
+ mark: "M5.99988 6.06806C5.99988 5.462 6.39988 4.24988 7.99988 4.24988C9.59988 4.24988 9.99988 5.462 9.99988 6.06806C9.99988 6.58025 9.71419 7.52529 8.65996 7.80576C8.30414 7.90042 7.99988 8.18472 7.99988 8.55291V9.24988",
3115
+ dot: "M7.99986 10.4999C8.36805 10.4999 8.66652 10.7984 8.66652 11.1665C8.66652 11.5347 8.36805 11.8332 7.99986 11.8332C7.63167 11.8332 7.33319 11.5347 7.33319 11.1665C7.33319 10.7984 7.63167 10.4999 7.99986 10.4999Z"
3116
+ },
3117
+ 20: {
3118
+ stroke: 1.25,
3119
+ ring: "M18.3334 9.99996C18.3334 5.39759 14.6024 1.66663 10 1.66663C5.39765 1.66663 1.66669 5.39759 1.66669 9.99996C1.66669 14.6023 5.39765 18.3333 10 18.3333C14.6024 18.3333 18.3334 14.6023 18.3334 9.99996Z",
3120
+ mark: "M7.49988 7.58498C7.49988 6.82741 7.99988 5.31226 9.99988 5.31226C11.9999 5.31226 12.4999 6.82741 12.4999 7.58498C12.4999 8.22522 12.1428 9.40652 10.825 9.75711C10.3802 9.87543 9.99988 10.2308 9.99988 10.691V11.5623",
3121
+ dot: "M9.99984 13.1248C10.4601 13.1248 10.8332 13.4979 10.8332 13.9581C10.8332 14.4183 10.4601 14.7914 9.99984 14.7914C9.5396 14.7914 9.1665 14.4183 9.1665 13.9581C9.1665 13.4979 9.5396 13.1248 9.99984 13.1248Z"
3122
+ },
3123
+ 24: {
3124
+ stroke: 1.5,
3125
+ ring: "M22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12Z",
3126
+ mark: "M8.99982 9.10203C8.99982 8.19294 9.59982 6.37476 11.9998 6.37476C14.3998 6.37476 14.9998 8.19294 14.9998 9.10203C14.9998 9.87031 14.5713 11.2879 12.9899 11.7086C12.4562 11.8506 11.9998 12.277 11.9998 12.8293V13.8748",
3127
+ dot: "M11.9998 15.7498C12.5521 15.7498 12.9998 16.1975 12.9998 16.7498C12.9998 17.302 12.5521 17.7498 11.9998 17.7498C11.4475 17.7498 10.9998 17.302 10.9998 16.7498C10.9998 16.1975 11.4475 15.7498 11.9998 15.7498Z"
3128
+ }
3129
+ };
3130
+ var helpOutlineGrid = (size) => {
3131
+ const px = typeof size === "number" ? size : /^\d+(\.\d+)?(px)?$/.test(size) ? parseFloat(size) : void 0;
3132
+ if (px === void 0) return 24;
3133
+ if (px <= 16) return 16;
3134
+ if (px <= 20) return 20;
3135
+ return 24;
3136
+ };
3110
3137
  var Icon_helpOutline = (p) => {
3111
3138
  const { size = 24, strokeWidth, ...rest } = p;
3112
- const stroke = strokeProps(strokeWidth);
3139
+ const grid = helpOutlineGrid(size);
3140
+ const v = HELP_OUTLINE_VARIANTS[grid];
3141
+ const native = Number(size) === grid || size === `${grid}px`;
3142
+ const stroke = native ? { stroke: "currentColor", strokeWidth: v.stroke } : { ...strokeProps(strokeWidth), strokeWidth: strokeWidth ?? v.stroke };
3113
3143
  return /* @__PURE__ */ jsxRuntime.jsxs(
3114
3144
  "svg",
3115
3145
  {
3116
3146
  width: size,
3117
3147
  height: size,
3118
- viewBox: "0 0 24 24",
3148
+ viewBox: `0 0 ${grid} ${grid}`,
3119
3149
  fill: "none",
3120
3150
  xmlns: "http://www.w3.org/2000/svg",
3121
3151
  ...rest,
3122
3152
  children: [
3123
- /* @__PURE__ */ jsxRuntime.jsx(
3124
- "path",
3125
- {
3126
- d: "M22.1998 11.9998C22.1998 6.3665 17.6331 1.7998 11.9998 1.7998C6.3665 1.7998 1.7998 6.3665 1.7998 11.9998C1.7998 17.6331 6.3665 22.1998 11.9998 22.1998C17.6331 22.1998 22.1998 17.6331 22.1998 11.9998Z",
3127
- strokeLinecap: "round",
3128
- ...stroke
3129
- }
3130
- ),
3131
- /* @__PURE__ */ jsxRuntime.jsx(
3132
- "path",
3133
- {
3134
- d: "M8.99982 9.10203C8.99982 8.19294 9.59982 6.37476 11.9998 6.37476C14.3998 6.37476 14.9998 8.19294 14.9998 9.10203C14.9998 9.87031 14.5713 11.2879 12.9899 11.7086C12.4562 11.8506 11.9998 12.277 11.9998 12.8293V13.8748",
3135
- strokeLinecap: "round",
3136
- strokeLinejoin: "round",
3137
- ...stroke
3138
- }
3139
- ),
3140
- /* @__PURE__ */ jsxRuntime.jsx(
3141
- "path",
3142
- {
3143
- d: "M11.9998 15.7498C12.5521 15.7498 12.9998 16.1975 12.9998 16.7498C12.9998 17.302 12.5521 17.7498 11.9998 17.7498C11.4475 17.7498 10.9998 17.302 10.9998 16.7498C10.9998 16.1975 11.4475 15.7498 11.9998 15.7498Z",
3144
- fill: "currentColor"
3145
- }
3146
- )
3153
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: v.ring, strokeLinecap: "round", ...stroke }),
3154
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: v.mark, strokeLinecap: "round", strokeLinejoin: "round", ...stroke }),
3155
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: v.dot, fill: "currentColor" })
3147
3156
  ]
3148
3157
  }
3149
3158
  );
@@ -5652,7 +5661,11 @@ var STag = /* @__PURE__ */ react.forwardRef(function STag2({
5652
5661
  // 레이블에 line-height 가 필요 없다. 오히려 line-height 를 주면 브라우저가
5653
5662
  // 폰트 ascent/descent 를 정수 px 로 반올림한 뒤 남는 half-leading 을 내림 처리해
5654
5663
  // 베이스라인이 최대 1px 위로 밀린다(xs 에서 특히 눈에 띈다).
5655
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex min-w-0 items-center justify-center leading-none", children: label }),
5664
+ // truncate: 폭이 모자라면(`width` 지정, 부모의 max-w 등) 글자에 말줄임이 걸린다. text-overflow 는
5665
+ // inline-flex 에서 듣지 않아 block 으로 둔다 — flex 아이템이라 폭·정렬은 그대로다.
5666
+ // overflow:hidden 이 line-height:1 의 줄 상자 밖으로 나온 글자(디센더)를 자르지 않도록
5667
+ // py 로 잘리는 영역만 넓히고 -my 로 레이아웃 높이는 되돌린다.
5668
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "-my-[2px] block min-w-0 truncate py-[2px] leading-none", children: label }),
5656
5669
  icon && !iconLeft && iconNode
5657
5670
  ]
5658
5671
  }
@@ -13825,6 +13838,11 @@ var GNB_RAIL = {
13825
13838
  var GNB_RAIL_WIDTH = 68;
13826
13839
  var GNB_FOLD_MS = 200;
13827
13840
  var GNB_EXPAND_MS = 200;
13841
+ var GNB_PEEK_ENTER_DELAY_MS = 120;
13842
+ var GNB_PEEK_LEAVE_DELAY_MS = 250;
13843
+ var GNB_PEEK_EDGE_WIDTH = 12;
13844
+ var GNB_PEEK_Z_INDEX = 40;
13845
+ var GNB_PEEK_SHADOW = "0px 0px 64px 0px rgba(34,34,34,0.12), 0px 0px 24px 0px rgba(34,34,34,0.08)";
13828
13846
  var GNB_MENU_WIDTH = 214;
13829
13847
  var GNB_MENU_MIN_WIDTH = 200;
13830
13848
  var GNB_MENU_MAX_WIDTH = 240;
@@ -13921,6 +13939,10 @@ var SGnb = /* @__PURE__ */ react.forwardRef(function SGnb2({
13921
13939
  ariaLabel = "global navigation",
13922
13940
  className,
13923
13941
  style,
13942
+ onPointerEnter: onPointerEnterProp,
13943
+ onPointerLeave: onPointerLeaveProp,
13944
+ onFocus: onFocusProp,
13945
+ onBlur: onBlurProp,
13924
13946
  ...rest
13925
13947
  }, ref) {
13926
13948
  const layout = react.useContext(SLayoutContext);
@@ -14277,7 +14299,80 @@ var SGnb = /* @__PURE__ */ react.forwardRef(function SGnb2({
14277
14299
  event.preventDefault();
14278
14300
  commitMenuWidth(next);
14279
14301
  };
14280
- const railFolded = folded && header === "fix";
14302
+ const [peeking, setPeeking] = react.useState(false);
14303
+ const peekingRef = react.useRef(false);
14304
+ const peekHoverRef = react.useRef(false);
14305
+ const peekFocusRef = react.useRef(false);
14306
+ const peekTimerRef = react.useRef(null);
14307
+ const clearPeekTimer = () => {
14308
+ if (peekTimerRef.current === null) return;
14309
+ clearTimeout(peekTimerRef.current);
14310
+ peekTimerRef.current = null;
14311
+ };
14312
+ const commitPeek = (next) => {
14313
+ peekingRef.current = next;
14314
+ if (next) peekFocusRef.current = false;
14315
+ setPeeking(next);
14316
+ };
14317
+ const schedulePeek = () => {
14318
+ clearPeekTimer();
14319
+ const want = peekHoverRef.current || peekingRef.current && peekFocusRef.current;
14320
+ if (want === peekingRef.current) return;
14321
+ peekTimerRef.current = setTimeout(
14322
+ () => {
14323
+ peekTimerRef.current = null;
14324
+ commitPeek(want);
14325
+ },
14326
+ want ? GNB_PEEK_ENTER_DELAY_MS : GNB_PEEK_LEAVE_DELAY_MS
14327
+ );
14328
+ };
14329
+ react.useEffect(() => {
14330
+ if (folded) return;
14331
+ clearPeekTimer();
14332
+ peekHoverRef.current = false;
14333
+ peekFocusRef.current = false;
14334
+ commitPeek(false);
14335
+ }, [folded]);
14336
+ react.useEffect(() => clearPeekTimer, []);
14337
+ const handlePeekEdgeEnter = (event) => {
14338
+ if (!folded || event.pointerType === "touch") return;
14339
+ peekHoverRef.current = true;
14340
+ schedulePeek();
14341
+ };
14342
+ const handlePeekEnter = (event) => {
14343
+ if (!peekingRef.current) return;
14344
+ handlePeekEdgeEnter(event);
14345
+ };
14346
+ const handlePeekLeave = () => {
14347
+ peekHoverRef.current = false;
14348
+ schedulePeek();
14349
+ };
14350
+ const handlePeekFocus = (event) => {
14351
+ peekFocusRef.current = event.target.matches(":focus-visible");
14352
+ schedulePeek();
14353
+ };
14354
+ const handlePeekBlur = (event) => {
14355
+ if (event.currentTarget.contains(event.relatedTarget)) return;
14356
+ peekFocusRef.current = false;
14357
+ schedulePeek();
14358
+ };
14359
+ const [peekClosing, setPeekClosing] = react.useState(false);
14360
+ const wasPeekingRef = react.useRef(false);
14361
+ react.useEffect(() => {
14362
+ if (peeking) {
14363
+ wasPeekingRef.current = true;
14364
+ setPeekClosing(false);
14365
+ return void 0;
14366
+ }
14367
+ if (!wasPeekingRef.current) return void 0;
14368
+ wasPeekingRef.current = false;
14369
+ setPeekClosing(true);
14370
+ const timer = setTimeout(() => setPeekClosing(false), GNB_FOLD_MS);
14371
+ return () => clearTimeout(timer);
14372
+ }, [peeking]);
14373
+ const peekOverlay = folded && peeking;
14374
+ const visuallyFolded = folded && !peeking;
14375
+ const railFolded = visuallyFolded && header === "fix";
14281
14376
  react.useEffect(() => {
14282
14377
  if (!railFolded || !launcherOpen) return;
14283
14378
  setLauncherOpen(false);
@@ -14324,14 +14419,50 @@ var SGnb = /* @__PURE__ */ react.forwardRef(function SGnb2({
14324
14419
  { width: "100%", ...style }
14325
14420
  ) : {
14326
14421
  // fix 를 닫으면 48px 레일로 좁혀진다.
14327
- width: folded ? GNB_COMMON.foldWidth : bodyWidth,
14422
+ width: visuallyFolded ? GNB_COMMON.foldWidth : bodyWidth,
14423
+ // 호버 오버레이: 폭은 펼치되 음수 margin 으로 레이아웃 자리는 레일 폭 그대로 둔다 — 페이지가 밀리지 않는다.
14424
+ // 폭과 margin 이 같은 길이·곡선으로 움직여 둘의 합(=페이지가 보는 자리)은 전환 내내 변하지 않는다.
14425
+ marginRight: peekOverlay ? `calc(${GNB_COMMON.foldWidth} - ${bodyWidth}px)` : "0px",
14426
+ // 페이지 위에 뜬다(접히는 전환이 끝날 때까지). 그 밖에는 주지 않는다 — 주면 펼친 GNB 에도 stacking context 가 새로 생긴다.
14427
+ // (폴드 버튼으로 펴서 folded 가 풀리면 접히는 중이 아니다 — 펼친 GNB 에는 주지 않는다)
14428
+ zIndex: peekOverlay || peekClosing && folded ? GNB_PEEK_Z_INDEX : void 0,
14429
+ boxShadow: peekOverlay ? GNB_PEEK_SHADOW : "none",
14328
14430
  // 폭을 끄는 중에는 전환을 끈다 — 접힘용 전환이 걸려 있으면 컬럼이 손보다 늦게 따라온다.
14329
- transition: resizing ? "none" : `width ${GNB_FOLD_MS}ms ease-out`,
14431
+ transition: resizing ? "none" : `width ${GNB_FOLD_MS}ms ease-out, margin-right ${GNB_FOLD_MS}ms ease-out, box-shadow ${GNB_FOLD_MS}ms ease-out`,
14330
14432
  backgroundColor: C.panelBg,
14331
14433
  borderRight: rightBorder,
14332
14434
  ...style
14333
14435
  };
14334
14436
  const hideAfterSlide = (hidden) => `visibility 0s linear ${hidden ? `${GNB_FOLD_MS}ms` : "0s"}`;
14437
+ const peekEdge = visuallyFolded ? /* @__PURE__ */ jsxRuntime.jsx(
14438
+ "div",
14439
+ {
14440
+ "aria-hidden": true,
14441
+ "data-gnb-peek-edge": "",
14442
+ className: "absolute inset-y-0 left-0 z-[2]",
14443
+ style: { width: GNB_PEEK_EDGE_WIDTH },
14444
+ onPointerEnter: handlePeekEdgeEnter,
14445
+ onPointerLeave: handlePeekLeave
14446
+ }
14447
+ ) : null;
14448
+ const rootPeekProps = {
14449
+ onPointerEnter: (event) => {
14450
+ onPointerEnterProp?.(event);
14451
+ if (!isFull) handlePeekEnter(event);
14452
+ },
14453
+ onPointerLeave: (event) => {
14454
+ onPointerLeaveProp?.(event);
14455
+ if (!isFull) handlePeekLeave();
14456
+ },
14457
+ onFocus: (event) => {
14458
+ onFocusProp?.(event);
14459
+ if (!isFull) handlePeekFocus(event);
14460
+ },
14461
+ onBlur: (event) => {
14462
+ onBlurProp?.(event);
14463
+ if (!isFull) handlePeekBlur(event);
14464
+ }
14465
+ };
14335
14466
  const sideFoldStyle = railFolded ? {
14336
14467
  opacity: 0,
14337
14468
  position: "absolute",
@@ -14481,7 +14612,9 @@ var SGnb = /* @__PURE__ */ react.forwardRef(function SGnb2({
14481
14612
  "data-header": header,
14482
14613
  "data-color": color,
14483
14614
  ...rest,
14615
+ ...rootPeekProps,
14484
14616
  children: [
14617
+ !isFull && peekEdge,
14485
14618
  /* @__PURE__ */ jsxRuntime.jsx(
14486
14619
  "div",
14487
14620
  {
@@ -14516,19 +14649,33 @@ var SGnb = /* @__PURE__ */ react.forwardRef(function SGnb2({
14516
14649
  /* @__PURE__ */ jsxRuntime.jsxs(
14517
14650
  "div",
14518
14651
  {
14519
- className: cn("relative overflow-hidden", !isFullFrame && "min-h-0 flex-1"),
14520
- style: isFullFrame ? { gridArea: "nav" } : void 0,
14652
+ className: cn(
14653
+ "relative",
14654
+ (!isFull || !folded) && "overflow-hidden",
14655
+ !isFullFrame && "min-h-0 flex-1"
14656
+ ),
14657
+ style: {
14658
+ ...isFullFrame ? { gridArea: "nav" } : null,
14659
+ // 접힌 동안 띠와 펼쳐진 본문이 페이지 위에 서도록 이 칸을 올린다
14660
+ ...isFull && folded ? { zIndex: GNB_PEEK_Z_INDEX } : null
14661
+ },
14521
14662
  children: [
14663
+ isFull && peekEdge,
14522
14664
  /* @__PURE__ */ jsxRuntime.jsxs(
14523
14665
  "div",
14524
14666
  {
14525
14667
  className: "flex h-full flex-row",
14526
14668
  style: {
14527
14669
  width: bodyWidth,
14528
- transform: folded ? "translateX(-100%)" : "none",
14529
- visibility: folded ? "hidden" : "visible",
14530
- transition: `transform ${GNB_FOLD_MS}ms ease-out, ${hideAfterSlide(folded)}`
14670
+ transform: visuallyFolded ? "translateX(-100%)" : "none",
14671
+ visibility: visuallyFolded ? "hidden" : "visible",
14672
+ boxShadow: peekOverlay ? GNB_PEEK_SHADOW : "none",
14673
+ transition: `transform ${GNB_FOLD_MS}ms ease-out, box-shadow ${GNB_FOLD_MS}ms ease-out, ${hideAfterSlide(visuallyFolded)}`
14531
14674
  },
14675
+ onPointerEnter: isFull ? handlePeekEnter : void 0,
14676
+ onPointerLeave: isFull ? handlePeekLeave : void 0,
14677
+ onFocus: isFull ? handlePeekFocus : void 0,
14678
+ onBlur: isFull ? handlePeekBlur : void 0,
14532
14679
  children: [
14533
14680
  showRail && // 레일 컬럼 — 스크롤되는 아이템 목록(nav)을 가운데 두고 위아래에 고정 슬롯(top/footer)을 붙인다.
14534
14681
  // 두 슬롯은 내비게이션이 아니므로 nav(landmark) 밖에 두고, 컬럼 폭·구분선·배경은 이 래퍼가
@@ -17321,6 +17468,48 @@ function indexAtOffset(metrics, y) {
17321
17468
  return lo;
17322
17469
  }
17323
17470
 
17471
+ // src/lib/grid-key-nav.ts
17472
+ var GRID_NAV_ATTR = "data-grid-nav";
17473
+ var TEXT_INPUT = [
17474
+ "input:not([type])",
17475
+ ...["text", "number", "search", "tel", "url", "email", "password"].map((t) => `input[type="${t}"]`)
17476
+ ];
17477
+ var FOCUSABLE_INPUT = TEXT_INPUT.map(
17478
+ (s) => `${s}:not(:disabled):not([readonly]):not([aria-disabled="true"])`
17479
+ ).join(",");
17480
+ function isTextInput(el) {
17481
+ return el.matches(TEXT_INPUT.join(","));
17482
+ }
17483
+ function focusInput(cell) {
17484
+ const input = cell.querySelector(FOCUSABLE_INPUT);
17485
+ if (!input) return false;
17486
+ if (input.closest('[inert],[aria-disabled="true"]')) return false;
17487
+ input.focus();
17488
+ if (input.ownerDocument.activeElement !== input) return false;
17489
+ input.select();
17490
+ return true;
17491
+ }
17492
+ function handleGridKeyNav(e) {
17493
+ if (e.key !== "Enter" || e.defaultPrevented) return;
17494
+ if (e.nativeEvent.isComposing || e.keyCode === 229 || e.ctrlKey || e.altKey || e.metaKey) return;
17495
+ const target = e.target;
17496
+ if (!isTextInput(target)) return;
17497
+ if (target.getAttribute("aria-expanded") === "true") return;
17498
+ const root = e.currentTarget;
17499
+ const cell = target.closest(`[${GRID_NAV_ATTR}]`);
17500
+ if (!cell || !root.contains(cell)) return;
17501
+ const column = cell.getAttribute(GRID_NAV_ATTR) ?? "";
17502
+ e.preventDefault();
17503
+ const table = cell.closest("table");
17504
+ const cells = [...root.querySelectorAll(`[${GRID_NAV_ATTR}]`)].filter(
17505
+ (c) => c.getAttribute(GRID_NAV_ATTR) === column && c.closest("table") === table
17506
+ );
17507
+ const step = e.shiftKey ? -1 : 1;
17508
+ for (let i = cells.indexOf(cell) + step; i >= 0 && i < cells.length; i += step) {
17509
+ if (focusInput(cells[i])) return;
17510
+ }
17511
+ }
17512
+
17324
17513
  // src/lib/depth-filter.ts
17325
17514
  function filterDepthRows(rows, matches) {
17326
17515
  const selfMatch = rows.map(matches);
@@ -17488,6 +17677,7 @@ function flattenOptions(options, depth = 1, inheritDisabled = true) {
17488
17677
  out.push({
17489
17678
  value: String(o.value),
17490
17679
  label: o.label,
17680
+ tag: o.tag,
17491
17681
  disabled: !!o.disabled || allChildrenDisabled,
17492
17682
  depth,
17493
17683
  isGroup: true,
@@ -17498,6 +17688,7 @@ function flattenOptions(options, depth = 1, inheritDisabled = true) {
17498
17688
  out.push({
17499
17689
  value: String(o.value),
17500
17690
  label: o.label,
17691
+ tag: o.tag,
17501
17692
  disabled: !!o.disabled,
17502
17693
  depth,
17503
17694
  isGroup: false,
@@ -17702,9 +17893,15 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
17702
17893
  const effectiveDisabled = disabled || allOptionsDisabled;
17703
17894
  const searchVisible = showSearch && (partialOptions || selectable.length >= SEARCH_THRESHOLD);
17704
17895
  const labelByValue = react.useMemo(() => new Map(flat.map((o) => [o.value, o.label])), [flat]);
17896
+ const tagByValue = react.useMemo(() => new Map(flat.map((o) => [o.value, o.tag])), [flat]);
17705
17897
  const rawByValue = react.useMemo(() => new Map(flat.map((o) => [o.value, o.raw])), [flat]);
17706
17898
  const optionByValue = react.useMemo(
17707
- () => new Map(flat.map((o) => [o.value, { value: o.raw, label: o.label }])),
17899
+ () => new Map(
17900
+ flat.map((o) => [
17901
+ o.value,
17902
+ { value: o.raw, label: o.label, ...o.tag && { tag: o.tag } }
17903
+ ])
17904
+ ),
17708
17905
  [flat]
17709
17906
  );
17710
17907
  const selected = react.useMemo(() => {
@@ -17717,7 +17914,11 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
17717
17914
  if (value == null) return map;
17718
17915
  for (const v of Array.isArray(value) ? value : [value]) {
17719
17916
  if (v != null && typeof v === "object") {
17720
- map.set(String(v.value), { value: v.value, label: v.label });
17917
+ map.set(String(v.value), {
17918
+ value: v.value,
17919
+ label: v.label,
17920
+ ...v.tag && { tag: v.tag }
17921
+ });
17721
17922
  }
17722
17923
  }
17723
17924
  return map;
@@ -18058,6 +18259,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
18058
18259
  });
18059
18260
  }
18060
18261
  }
18262
+ const tagSize = size === "md" ? "sm" : "xs";
18061
18263
  const isPlaceholder = selected.length === 0;
18062
18264
  const showTriggerSpinner = loading2 && !effectiveDisabled;
18063
18265
  const showClear = clearable && !effectiveDisabled && !isPlaceholder;
@@ -18402,7 +18604,15 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
18402
18604
  ))
18403
18605
  }
18404
18606
  )
18405
- ) : /* @__PURE__ */ jsxRuntime.jsx(TriggerLabel, { label: triggerText }),
18607
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
18608
+ TriggerLabel,
18609
+ {
18610
+ label: triggerText,
18611
+ tag: multi ? void 0 : tagByValue.get(selected[0]) ?? valueOptions.get(selected[0])?.tag,
18612
+ tagSize,
18613
+ tagDisabled: effectiveDisabled
18614
+ }
18615
+ ),
18406
18616
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex shrink-0 items-center gap-[8px]", children: [
18407
18617
  showClear && /* @__PURE__ */ jsxRuntime.jsx(TriggerRemoveButton, { ariaLabel: t.clear, onRemove: handleClear }),
18408
18618
  showTriggerSpinner ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "inline-flex shrink-0", "data-select-trigger-spinner": true, children: /* @__PURE__ */ jsxRuntime.jsx(SCircleProgress, { indeterminate: true, type: "neutral", size: 12 }) }) : /* @__PURE__ */ jsxRuntime.jsx(
@@ -18495,6 +18705,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
18495
18705
  hasItems ? /* @__PURE__ */ jsxRuntime.jsx(
18496
18706
  ItemList,
18497
18707
  {
18708
+ tagSize,
18498
18709
  listboxId,
18499
18710
  activeOptionId,
18500
18711
  items: visibleItems,
@@ -18552,6 +18763,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
18552
18763
  });
18553
18764
  var SSelect2 = /* @__PURE__ */ withDisplayName(SSelectImpl, "SSelect");
18554
18765
  function ItemList({
18766
+ tagSize,
18555
18767
  listboxId,
18556
18768
  activeOptionId,
18557
18769
  items,
@@ -18734,6 +18946,9 @@ function ItemList({
18734
18946
  OptionLabel,
18735
18947
  {
18736
18948
  label: o.label,
18949
+ tag: o.tag,
18950
+ tagSize,
18951
+ tagDisabled: rowDisabled,
18737
18952
  className: treeLabelClass("sm", {
18738
18953
  // 고른 줄은 굵게 — multi_tree 는 일부만 고른 그룹도 굵다 (STree 와 같다)
18739
18954
  emphasized: checkState !== false,
@@ -18868,7 +19083,16 @@ function ItemList({
18868
19083
  focused: focusedValue === o.value
18869
19084
  }
18870
19085
  ),
18871
- /* @__PURE__ */ jsxRuntime.jsx(OptionLabel, { label: o.label, className: "flex-1 truncate" })
19086
+ /* @__PURE__ */ jsxRuntime.jsx(
19087
+ OptionLabel,
19088
+ {
19089
+ label: o.label,
19090
+ tag: o.tag,
19091
+ tagSize,
19092
+ tagDisabled: o.disabled,
19093
+ className: "flex-1 truncate"
19094
+ }
19095
+ )
18872
19096
  ]
18873
19097
  },
18874
19098
  rowKey
@@ -18900,11 +19124,52 @@ function TreeRowsMotion({ opening, children }) {
18900
19124
  react.useEffect(() => () => animationRef.current?.cancel(), []);
18901
19125
  return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: "overflow-hidden", "aria-hidden": opening ? void 0 : true, children });
18902
19126
  }
18903
- function OptionLabel({ label, className }) {
19127
+ function OptionLabel({
19128
+ label,
19129
+ tag,
19130
+ tagSize,
19131
+ tagDisabled,
19132
+ className
19133
+ }) {
19134
+ if (tag) {
19135
+ return /* @__PURE__ */ jsxRuntime.jsx(
19136
+ "span",
19137
+ {
19138
+ className: cn(
19139
+ className,
19140
+ "min-w-0 overflow-hidden",
19141
+ tagDisabled && "opacity-(--opacity-050)"
19142
+ ),
19143
+ children: /* @__PURE__ */ jsxRuntime.jsx(
19144
+ STag,
19145
+ {
19146
+ ...tag,
19147
+ size: tagSize,
19148
+ label: isHtmlLabel(label) ? extractText(label) : label,
19149
+ className: cn("max-w-full min-w-0", tag.className)
19150
+ }
19151
+ )
19152
+ }
19153
+ );
19154
+ }
18904
19155
  return isHtmlLabel(label) ? /* @__PURE__ */ jsxRuntime.jsx("span", { className, dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(label) } }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className, children: label });
18905
19156
  }
18906
- function TriggerLabel({ label }) {
18907
- return /* @__PURE__ */ jsxRuntime.jsx(OptionLabel, { label, className: "truncate" });
19157
+ function TriggerLabel({
19158
+ label,
19159
+ tag,
19160
+ tagSize,
19161
+ tagDisabled
19162
+ }) {
19163
+ return /* @__PURE__ */ jsxRuntime.jsx(
19164
+ OptionLabel,
19165
+ {
19166
+ label,
19167
+ tag,
19168
+ tagSize,
19169
+ tagDisabled,
19170
+ className: "truncate"
19171
+ }
19172
+ );
18908
19173
  }
18909
19174
  function TriggerRemoveButton({ ariaLabel, onRemove }) {
18910
19175
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -19911,365 +20176,381 @@ var STableImpl = /* @__PURE__ */ react.forwardRef(function STable({
19911
20176
  ]
19912
20177
  }
19913
20178
  );
19914
- return /* @__PURE__ */ jsxRuntime.jsxs(
19915
- "div",
19916
- {
19917
- className: cn("flex flex-col", className),
19918
- style: { width, ...style },
19919
- "data-table-id": resolvedTableId,
19920
- children: [
19921
- /* @__PURE__ */ jsxRuntime.jsxs(
19922
- "div",
19923
- {
19924
- className: cn(
19925
- // isolate: no-data/loading 오버레이(z-30·31)를 테이블 안에서 끝낸다. relative + z-auto
19926
- // 만으로는 스태킹 컨텍스트가 생기지 않아, 모달 안에 들어가면 30·31이 모달 Content
19927
- // 레벨까지 올라가 같은 컨텍스트의 floating portal 밴드(z-2)를 덮는다.
19928
- // 자손은 이미 overflow-hidden으로 클리핑되므로 테이블 내부 순서는 달라지지 않는다.
19929
- "bg-white relative isolate flex flex-col overflow-hidden",
19930
- bordered && "border border-solid border-[color:var(--cmp-table-border-color)]",
19931
- // height가 없으면 루트가 받은 높이를 끝까지 채운다. 호스트가 className으로
19932
- // `flex-1 min-h-0` 등을 줘 루트 높이가 확정된 경우, 행이 적어도 테두리 박스가
19933
- // 남은 영역을 그대로 차지한다. basis는 auto로 둬 높이가 확정되지 않은
19934
- // 기본 사용(내용만큼 커지는 케이스)에서는 동작이 바뀌지 않는다.
19935
- !height && "min-h-0 grow",
19936
- radiusClass,
19937
- // 원본 sd-table__scroll-container--loading/--no-data: 로딩·데이터 없음 시 인터랙션 차단
19938
- (loading2 || rowCount === 0) && "pointer-events-none"
19939
- ),
19940
- style: {
19941
- height,
19942
- minHeight: isNoData ? "calc(var(--cmp-table-header-height) + 60px)" : void 0
19943
- },
19944
- children: [
19945
- showNoDataSlot ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
19946
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-none overflow-hidden", children: headerTable }),
19947
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(bodyScrollClass, "pointer-events-auto bg-white"), children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full min-h-[60px] w-full items-center justify-center", children: emptySlot }) })
19948
- ] }) : (
19949
- /**
19950
- * 헤더와 body 가 한 스크롤 컨테이너에 있다 — 헤더는 위에 붙어(sticky) 세로로는 서 있고
19951
- * 가로로는 body 와 같은 스크롤로 움직인다. 헤더를 따로 두고 scroll 이벤트에서
19952
- * scrollLeft 를 맞추면, 브라우저가 body 스크롤을 먼저 그리고 이벤트를 나중에 돌려
19953
- * 헤더가 한 프레임 이상 늦게 따라온다.
19954
- *
19955
- * 행 좌표는 body 표 기준 그대로다 — `scrollTop` 이 y 이면 y 에 있는 행이 헤더 바로
19956
- * 아래에 선다. 보이는 높이만 헤더 높이만큼 줄어든다(`headerHeight`).
19957
- */
19958
- /* @__PURE__ */ jsxRuntime.jsxs(
19959
- "div",
19960
- {
19961
- ref: scrollRef,
19962
- onScroll: handleScrollEvent,
19963
- className: cn(
19964
- bodyScrollClass,
19965
- // 원본 sd-table__scroll-container--loading/--no-data: 로딩·데이터 없음 시 스크롤 차단
19966
- (loading2 || rowCount === 0) && "overflow-hidden"
19967
- ),
19968
- style: { scrollPaddingTop: headerHeight },
19969
- children: [
19970
- /* @__PURE__ */ jsxRuntime.jsx("div", { ref: headerRef, className: "sticky top-0 z-[8]", children: headerTable }),
19971
- /* @__PURE__ */ jsxRuntime.jsxs(
19972
- "table",
19973
- {
19974
- className: "w-full table-fixed border-separate border-spacing-0 text-left",
19975
- style: { fontSize: 12 },
19976
- children: [
19977
- colgroup,
19978
- /* @__PURE__ */ jsxRuntime.jsxs("tbody", { children: [
19979
- virtualScroll && topSpacer > 0 && /* @__PURE__ */ jsxRuntime.jsx("tr", { "aria-hidden": true, style: { height: topSpacer }, children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan: totalColSpan, className: "p-0" }) }),
19980
- rowCount === 0 ? null : bodyRows.map((row, ri) => {
19981
- const rowIndex = rowOffset + ri;
19982
- const isSel = isViewSelected(row);
19983
- const dragSel = dragSelectOn && isSel;
19984
- const dragTop = dragSel && !isViewSelected(rows[rowIndex - 1]);
19985
- const dragBottom = dragSel && !isViewSelected(rows[rowIndex + 1]);
19986
- const rowLocked = (selectable || dragSelectable) && !canSelectRow(row);
19987
- const rowBg = "bg-white";
19988
- const rowHover = hoverable && !rowLocked;
19989
- const expansionShown = expansionOn && shownExpansionSet.has(row[rowKey]);
19990
- return /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
19991
- /* @__PURE__ */ jsxRuntime.jsxs(
19992
- "tr",
19993
- {
19994
- "data-row-index": rowIndex,
19995
- onClick: onRowClick ? (event) => {
19996
- const target = event.target;
19997
- if (target.closest(ROW_INTERACTIVE_SELECTOR)) return;
19998
- onRowClick(row);
19999
- } : void 0,
20000
- onDoubleClick: onRowDoubleClick ? (event) => {
20001
- const target = event.target;
20002
- if (target.closest(ROW_INTERACTIVE_SELECTOR)) return;
20003
- onRowDoubleClick(row);
20004
- } : void 0,
20005
- onMouseDown: dragSelectOn ? (event) => startDragSelect(rowIndex, event) : void 0,
20006
- onMouseEnter: dragSelectOn ? () => extendDragSelect(rowIndex) : void 0,
20007
- onContextMenu: contextMenuOn ? (event) => openContextMenu(row, event) : void 0,
20008
- className: cn(
20009
- "group/table-row",
20010
- rowHover && "hover:bg-[var(--color-grey-05)]",
20011
- (onRowClick || onRowDoubleClick) && "cursor-pointer",
20012
- dragSelectOn && "select-none",
20013
- rowClassName?.(row)
20014
- ),
20015
- style: { height: virtualScroll ? effectiveRowHeight : rowH },
20016
- children: [
20017
- selectable && /* @__PURE__ */ jsxRuntime.jsx(
20018
- "td",
20019
- {
20020
- className: cn(
20021
- // 원본 .td--selected: 48px 고정폭, 항상 left sticky, 체크박스 중앙정렬
20022
- "sticky left-0 z-[2] border-b border-solid border-[color:var(--cmp-table-border-color)] align-middle",
20179
+ const gridNavOn = columns.some((col) => col.gridNav);
20180
+ return (
20181
+ // 키 이벤트를 모아 받을 뿐 이 요소 자체는 상호작용 요소가 아니다 — role 을 바꾸지 않는다
20182
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions
20183
+ /* @__PURE__ */ jsxRuntime.jsxs(
20184
+ "div",
20185
+ {
20186
+ className: cn("flex flex-col", className),
20187
+ style: { width, ...style },
20188
+ "data-table-id": resolvedTableId,
20189
+ onKeyDown: gridNavOn ? handleGridKeyNav : void 0,
20190
+ children: [
20191
+ /* @__PURE__ */ jsxRuntime.jsxs(
20192
+ "div",
20193
+ {
20194
+ className: cn(
20195
+ // isolate: no-data/loading 오버레이(z-30·31)를 테이블 안에서 끝낸다. relative + z-auto
20196
+ // 만으로는 스태킹 컨텍스트가 생기지 않아, 모달 안에 들어가면 30·31이 모달 Content
20197
+ // 레벨까지 올라가 같은 컨텍스트의 floating portal 밴드(z-2)를 덮는다.
20198
+ // 자손은 이미 overflow-hidden으로 클리핑되므로 테이블 내부 순서는 달라지지 않는다.
20199
+ "bg-white relative isolate flex flex-col overflow-hidden",
20200
+ bordered && "border border-solid border-[color:var(--cmp-table-border-color)]",
20201
+ // height가 없으면 루트가 받은 높이를 끝까지 채운다. 호스트가 className으로
20202
+ // `flex-1 min-h-0` 등을 줘 루트 높이가 확정된 경우, 행이 적어도 테두리 박스가
20203
+ // 남은 영역을 그대로 차지한다. basis는 auto로 둬 높이가 확정되지 않은
20204
+ // 기본 사용(내용만큼 커지는 케이스)에서는 동작이 바뀌지 않는다.
20205
+ !height && "min-h-0 grow",
20206
+ radiusClass,
20207
+ // 원본 sd-table__scroll-container--loading/--no-data: 로딩·데이터 없음 시 인터랙션 차단
20208
+ (loading2 || rowCount === 0) && "pointer-events-none"
20209
+ ),
20210
+ style: {
20211
+ height,
20212
+ minHeight: isNoData ? "calc(var(--cmp-table-header-height) + 60px)" : void 0
20213
+ },
20214
+ children: [
20215
+ showNoDataSlot ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
20216
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-none overflow-hidden", children: headerTable }),
20217
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(bodyScrollClass, "pointer-events-auto bg-white"), children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full min-h-[60px] w-full items-center justify-center", children: emptySlot }) })
20218
+ ] }) : (
20219
+ /**
20220
+ * 헤더와 body 가 한 스크롤 컨테이너에 있다 — 헤더는 위에 붙어(sticky) 세로로는 서 있고
20221
+ * 가로로는 body 와 같은 스크롤로 움직인다. 헤더를 따로 두고 scroll 이벤트에서
20222
+ * scrollLeft 를 맞추면, 브라우저가 body 스크롤을 먼저 그리고 이벤트를 나중에 돌려
20223
+ * 헤더가 한 프레임 이상 늦게 따라온다.
20224
+ *
20225
+ * 행 좌표는 body 표 기준 그대로다 — `scrollTop` 이 y 이면 y 에 있는 행이 헤더 바로
20226
+ * 아래에 선다. 보이는 높이만 헤더 높이만큼 줄어든다(`headerHeight`).
20227
+ */
20228
+ /* @__PURE__ */ jsxRuntime.jsxs(
20229
+ "div",
20230
+ {
20231
+ ref: scrollRef,
20232
+ onScroll: handleScrollEvent,
20233
+ className: cn(
20234
+ bodyScrollClass,
20235
+ // 원본 sd-table__scroll-container--loading/--no-data: 로딩·데이터 없음 시 스크롤 차단
20236
+ (loading2 || rowCount === 0) && "overflow-hidden"
20237
+ ),
20238
+ style: { scrollPaddingTop: headerHeight },
20239
+ children: [
20240
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: headerRef, className: "sticky top-0 z-[8]", children: headerTable }),
20241
+ /* @__PURE__ */ jsxRuntime.jsxs(
20242
+ "table",
20243
+ {
20244
+ className: "w-full table-fixed border-separate border-spacing-0 text-left",
20245
+ style: { fontSize: 12 },
20246
+ children: [
20247
+ colgroup,
20248
+ /* @__PURE__ */ jsxRuntime.jsxs("tbody", { children: [
20249
+ virtualScroll && topSpacer > 0 && /* @__PURE__ */ jsxRuntime.jsx("tr", { "aria-hidden": true, style: { height: topSpacer }, children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan: totalColSpan, className: "p-0" }) }),
20250
+ rowCount === 0 ? null : bodyRows.map((row, ri) => {
20251
+ const rowIndex = rowOffset + ri;
20252
+ const isSel = isViewSelected(row);
20253
+ const dragSel = dragSelectOn && isSel;
20254
+ const dragTop = dragSel && !isViewSelected(rows[rowIndex - 1]);
20255
+ const dragBottom = dragSel && !isViewSelected(rows[rowIndex + 1]);
20256
+ const rowLocked = (selectable || dragSelectable) && !canSelectRow(row);
20257
+ const rowBg = "bg-white";
20258
+ const rowHover = hoverable && !rowLocked;
20259
+ const expansionShown = expansionOn && shownExpansionSet.has(row[rowKey]);
20260
+ return /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
20261
+ /* @__PURE__ */ jsxRuntime.jsxs(
20262
+ "tr",
20263
+ {
20264
+ "data-row-index": rowIndex,
20265
+ onClick: onRowClick ? (event) => {
20266
+ const target = event.target;
20267
+ if (target.closest(ROW_INTERACTIVE_SELECTOR)) return;
20268
+ onRowClick(row);
20269
+ } : void 0,
20270
+ onDoubleClick: onRowDoubleClick ? (event) => {
20271
+ const target = event.target;
20272
+ if (target.closest(ROW_INTERACTIVE_SELECTOR)) return;
20273
+ onRowDoubleClick(row);
20274
+ } : void 0,
20275
+ onMouseDown: dragSelectOn ? (event) => startDragSelect(rowIndex, event) : void 0,
20276
+ onMouseEnter: dragSelectOn ? () => extendDragSelect(rowIndex) : void 0,
20277
+ onContextMenu: contextMenuOn ? (event) => openContextMenu(row, event) : void 0,
20278
+ className: cn(
20279
+ "group/table-row",
20280
+ rowHover && "hover:bg-[var(--color-grey-05)]",
20281
+ (onRowClick || onRowDoubleClick) && "cursor-pointer",
20282
+ dragSelectOn && "select-none",
20283
+ rowClassName?.(row)
20284
+ ),
20285
+ style: { height: virtualScroll ? effectiveRowHeight : rowH },
20286
+ children: [
20287
+ selectable && /* @__PURE__ */ jsxRuntime.jsx(
20288
+ "td",
20289
+ {
20290
+ className: cn(
20291
+ // 원본 .td--selected: 48px 고정폭, 항상 left sticky, 체크박스 중앙정렬
20292
+ "sticky left-0 z-[2] border-b border-solid border-[color:var(--cmp-table-border-color)] align-middle",
20293
+ rowBg,
20294
+ rowHover && "group-hover/table-row:bg-[var(--color-grey-05)]",
20295
+ stickyLeft === 0 && scrolledLeft && "shadow-[5px_0_8px_0_rgba(34,34,34,0.1)]"
20296
+ ),
20297
+ style: {
20298
+ width: SELECTABLE_COLUMN_WIDTH,
20299
+ minWidth: SELECTABLE_COLUMN_WIDTH,
20300
+ maxWidth: SELECTABLE_COLUMN_WIDTH,
20301
+ // 선택 열도 행의 일부다 — 여기만 희지 않으면 회색 줄이 끊긴다
20302
+ background: rowLocked ? LOCKED_BG : void 0
20303
+ },
20304
+ onClick: (e) => e.stopPropagation(),
20305
+ onDoubleClick: (e) => e.stopPropagation(),
20306
+ onClickCapture: (e) => {
20307
+ shiftPressedRef.current = e.shiftKey;
20308
+ if (e.shiftKey) window.getSelection()?.removeAllRanges();
20309
+ },
20310
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex select-none items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
20311
+ SCheckbox,
20312
+ {
20313
+ value: isSel || !isRowIndeterminate?.(row) ? isSel : null,
20314
+ disabled: !canSelectRow(row),
20315
+ onValueChange: () => toggleRow(row, rowOffset + ri),
20316
+ ariaLabel: rowSelectLabel?.(row) ?? t.selectRow
20317
+ }
20318
+ ) })
20319
+ }
20320
+ ),
20321
+ visibleCols.map((col, i) => {
20322
+ const tdClassName = cn(
20323
+ // 원본 .td: 컬럼 폭에 맞춰 내용 말줄임(overflow hidden + ellipsis) + 행 구분선(border-b)
20324
+ // 셀 배경은 흰색으로 채워 뒤 배경이 비치지 않게 하고, hover 시 행 배경(grey_05)을 따른다.
20325
+ "overflow-hidden text-ellipsis whitespace-nowrap border-b border-solid border-[color:var(--cmp-table-border-color)] text-[12px] leading-[20px] text-[color:var(--sys-color-fg-primary)]",
20023
20326
  rowBg,
20024
20327
  rowHover && "group-hover/table-row:bg-[var(--color-grey-05)]",
20025
- stickyLeft === 0 && scrolledLeft && "shadow-[5px_0_8px_0_rgba(34,34,34,0.1)]"
20026
- ),
20027
- style: {
20028
- width: SELECTABLE_COLUMN_WIDTH,
20029
- minWidth: SELECTABLE_COLUMN_WIDTH,
20030
- maxWidth: SELECTABLE_COLUMN_WIDTH,
20031
- // 선택 열도 행의 일부다 — 여기만 희지 않으면 회색 줄이 끊긴다
20032
- background: rowLocked ? LOCKED_BG : void 0
20033
- },
20034
- onClick: (e) => e.stopPropagation(),
20035
- onDoubleClick: (e) => e.stopPropagation(),
20036
- onClickCapture: (e) => {
20037
- shiftPressedRef.current = e.shiftKey;
20038
- if (e.shiftKey) window.getSelection()?.removeAllRanges();
20039
- },
20040
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex select-none items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
20041
- SCheckbox,
20042
- {
20043
- value: isSel || !isRowIndeterminate?.(row) ? isSel : null,
20044
- disabled: !canSelectRow(row),
20045
- onValueChange: () => toggleRow(row, rowOffset + ri),
20046
- ariaLabel: rowSelectLabel?.(row) ?? t.selectRow
20047
- }
20048
- ) })
20049
- }
20050
- ),
20051
- visibleCols.map((col, i) => {
20052
- const tdClassName = cn(
20053
- // 원본 .td: 컬럼 폭에 맞춰 내용 말줄임(overflow hidden + ellipsis) + 행 구분선(border-b)
20054
- // 셀 배경은 흰색으로 채워 뒤 배경이 비치지 않게 하고, hover 시 행 배경(grey_05)을 따른다.
20055
- "overflow-hidden text-ellipsis whitespace-nowrap border-b border-solid border-[color:var(--cmp-table-border-color)] text-[12px] leading-[20px] text-[color:var(--sys-color-fg-primary)]",
20056
- rowBg,
20057
- rowHover && "group-hover/table-row:bg-[var(--color-grey-05)]",
20058
- typeof col.tdClass === "function" ? col.tdClass(row) : col.tdClass
20059
- );
20060
- const tdPadX = cellPadXOf(col);
20061
- const dragEdges = dragSel ? [
20062
- dragTop && `inset 0 ${DRAG_EDGE_W} 0 0 ${DRAG_EDGE_C}`,
20063
- dragBottom && `inset 0 ${DRAG_EDGE_NEG_W} 0 0 ${DRAG_EDGE_C}`,
20064
- i === 0 && `inset ${DRAG_EDGE_W} 0 0 0 ${DRAG_EDGE_C}`,
20065
- i === visibleCols.length - 1 && `inset ${DRAG_EDGE_NEG_W} 0 0 0 ${DRAG_EDGE_C}`
20066
- ].filter(Boolean) : [];
20067
- const shadows = [...dragEdges, stickyShadowValue(i)].filter(Boolean);
20068
- const tdStyle = {
20069
- paddingLeft: tdPadX,
20070
- paddingRight: tdPadX,
20071
- paddingTop: cellPadY,
20072
- paddingBottom: cellPadY,
20073
- textAlign: col.align ?? "left",
20074
- // 인라인이라 hover 배경보다 세다 — 고른 행은 hover 로 흐려지지 않는다.
20075
- background: dragSel ? DRAG_BG : rowLocked ? LOCKED_BG : void 0,
20076
- // 클래스의 기본 글자색을 덮는다. 셀이 자기 색을 정한 내용(SIcon 의
20077
- // color, STag 등)까지는 닿지 않는다 — 그건 소비 앱 몫이다.
20078
- color: rowLocked ? LOCKED_CONTENT : void 0,
20079
- boxShadow: shadows.length > 0 ? shadows.join(", ") : void 0,
20080
- ...cellStyle(i)
20081
- };
20082
- if (col.renderCell) {
20083
- const cell = col.renderCell({
20084
- row,
20085
- value: cellValue(col, row),
20086
- column: col,
20087
- colIndex: i,
20088
- rowIndex: ri,
20089
- isSelected: isSel,
20090
- className: tdClassName,
20091
- style: tdStyle
20092
- });
20093
- if (cell == null || cell === false) return null;
20094
- return /* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: cell }, col.name);
20095
- }
20096
- const content = col.render ? col.render(row, cellValue(col, row)) : cellValue(col, row);
20097
- if (i === expansionColIndex) {
20328
+ typeof col.tdClass === "function" ? col.tdClass(row) : col.tdClass
20329
+ );
20330
+ const tdPadX = cellPadXOf(col);
20331
+ const dragEdges = dragSel ? [
20332
+ dragTop && `inset 0 ${DRAG_EDGE_W} 0 0 ${DRAG_EDGE_C}`,
20333
+ dragBottom && `inset 0 ${DRAG_EDGE_NEG_W} 0 0 ${DRAG_EDGE_C}`,
20334
+ i === 0 && `inset ${DRAG_EDGE_W} 0 0 0 ${DRAG_EDGE_C}`,
20335
+ i === visibleCols.length - 1 && `inset ${DRAG_EDGE_NEG_W} 0 0 0 ${DRAG_EDGE_C}`
20336
+ ].filter(Boolean) : [];
20337
+ const shadows = [...dragEdges, stickyShadowValue(i)].filter(Boolean);
20338
+ const tdStyle = {
20339
+ paddingLeft: tdPadX,
20340
+ paddingRight: tdPadX,
20341
+ paddingTop: cellPadY,
20342
+ paddingBottom: cellPadY,
20343
+ textAlign: col.align ?? "left",
20344
+ // 인라인이라 hover 배경보다 세다 — 고른 행은 hover 로 흐려지지 않는다.
20345
+ background: dragSel ? DRAG_BG : rowLocked ? LOCKED_BG : void 0,
20346
+ // 클래스의 기본 글자색을 덮는다. 셀이 자기 색을 정한 내용(SIcon 의
20347
+ // color, STag 등)까지는 닿지 않는다 — 그건 소비 앱 몫이다.
20348
+ color: rowLocked ? LOCKED_CONTENT : void 0,
20349
+ boxShadow: shadows.length > 0 ? shadows.join(", ") : void 0,
20350
+ ...cellStyle(i)
20351
+ };
20352
+ if (col.renderCell) {
20353
+ const cell = col.renderCell({
20354
+ row,
20355
+ value: cellValue(col, row),
20356
+ column: col,
20357
+ colIndex: i,
20358
+ rowIndex: ri,
20359
+ isSelected: isSel,
20360
+ className: tdClassName,
20361
+ style: tdStyle
20362
+ });
20363
+ if (cell == null || cell === false) return null;
20364
+ return /* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: cell }, col.name);
20365
+ }
20366
+ const content = col.render ? col.render(row, cellValue(col, row)) : cellValue(col, row);
20367
+ if (i === expansionColIndex) {
20368
+ return /* @__PURE__ */ jsxRuntime.jsx(
20369
+ "td",
20370
+ {
20371
+ className: cn(tdClassName, "cursor-pointer"),
20372
+ style: tdStyle,
20373
+ "data-grid-nav": col.gridNav ? col.name : void 0,
20374
+ onClick: (event) => {
20375
+ if (isOwnControl(event.target, event.currentTarget)) return;
20376
+ event.stopPropagation();
20377
+ toggleExpanded(row);
20378
+ },
20379
+ onDoubleClick: (event) => event.stopPropagation(),
20380
+ children: /* @__PURE__ */ jsxRuntime.jsx(ExpansionToggleCell, { expanded: isRowExpanded(row), children: content })
20381
+ },
20382
+ col.name
20383
+ );
20384
+ }
20098
20385
  return /* @__PURE__ */ jsxRuntime.jsx(
20099
20386
  "td",
20100
20387
  {
20101
- className: cn(tdClassName, "cursor-pointer"),
20388
+ className: tdClassName,
20102
20389
  style: tdStyle,
20103
- onClick: (event) => {
20104
- if (isOwnControl(event.target, event.currentTarget)) return;
20105
- event.stopPropagation();
20106
- toggleExpanded(row);
20107
- },
20108
- onDoubleClick: (event) => event.stopPropagation(),
20109
- children: /* @__PURE__ */ jsxRuntime.jsx(ExpansionToggleCell, { expanded: isRowExpanded(row), children: content })
20390
+ "data-grid-nav": col.gridNav ? col.name : void 0,
20391
+ children: content
20110
20392
  },
20111
20393
  col.name
20112
20394
  );
20395
+ })
20396
+ ]
20397
+ }
20398
+ ),
20399
+ expansionShown && // 상세 영역 — 행이 아니므로 data-row-index 를 달지 않는다 (행 수·선택·실측 대상 밖)
20400
+ /* @__PURE__ */ jsxRuntime.jsx(
20401
+ "tr",
20402
+ {
20403
+ "data-expansion-row": "",
20404
+ "data-expansion-key": String(row[rowKey]),
20405
+ className: expandedSet.has(row[rowKey]) ? void 0 : "pointer-events-none",
20406
+ children: /* @__PURE__ */ jsxRuntime.jsx(
20407
+ "td",
20408
+ {
20409
+ colSpan: totalColSpan,
20410
+ className: "whitespace-normal border-b border-solid border-[color:var(--cmp-table-border-color)] bg-white p-0 text-[12px] leading-[20px] text-[color:var(--sys-color-fg-primary)]",
20411
+ children: /* @__PURE__ */ jsxRuntime.jsx(
20412
+ "div",
20413
+ {
20414
+ className: "sticky left-0 box-border min-w-0",
20415
+ style: {
20416
+ // 가로 스크롤이 있어도 보이는 폭에 머문다. 재기 전(0)에는 표 폭을 따른다
20417
+ width: viewportWidth > 0 ? viewportWidth : void 0,
20418
+ paddingLeft: "var(--cmp-table-body-paddingX-wide)",
20419
+ paddingRight: "var(--cmp-table-body-paddingX-wide)",
20420
+ paddingTop: cellPadY,
20421
+ paddingBottom: cellPadY
20422
+ },
20423
+ children: renderExpansion?.(row)
20424
+ }
20425
+ )
20113
20426
  }
20114
- return /* @__PURE__ */ jsxRuntime.jsx("td", { className: tdClassName, style: tdStyle, children: content }, col.name);
20115
- })
20116
- ]
20117
- }
20118
- ),
20119
- expansionShown && // 상세 영역 — 행이 아니므로 data-row-index 를 달지 않는다 (행 수·선택·실측 대상 밖)
20120
- /* @__PURE__ */ jsxRuntime.jsx(
20121
- "tr",
20122
- {
20123
- "data-expansion-row": "",
20124
- "data-expansion-key": String(row[rowKey]),
20125
- className: expandedSet.has(row[rowKey]) ? void 0 : "pointer-events-none",
20126
- children: /* @__PURE__ */ jsxRuntime.jsx(
20127
- "td",
20128
- {
20129
- colSpan: totalColSpan,
20130
- className: "whitespace-normal border-b border-solid border-[color:var(--cmp-table-border-color)] bg-white p-0 text-[12px] leading-[20px] text-[color:var(--sys-color-fg-primary)]",
20131
- children: /* @__PURE__ */ jsxRuntime.jsx(
20132
- "div",
20133
- {
20134
- className: "sticky left-0 box-border min-w-0",
20135
- style: {
20136
- // 가로 스크롤이 있어도 보이는 폭에 머문다. 재기 전(0)에는 표 폭을 따른다
20137
- width: viewportWidth > 0 ? viewportWidth : void 0,
20138
- paddingLeft: "var(--cmp-table-body-paddingX-wide)",
20139
- paddingRight: "var(--cmp-table-body-paddingX-wide)",
20140
- paddingTop: cellPadY,
20141
- paddingBottom: cellPadY
20142
- },
20143
- children: renderExpansion?.(row)
20144
- }
20145
- )
20146
- }
20147
- )
20148
- }
20149
- )
20150
- ] }, row[rowKey] ?? ri);
20151
- }),
20152
- virtualScroll && bottomSpacer > 0 && /* @__PURE__ */ jsxRuntime.jsx("tr", { "aria-hidden": true, style: { height: bottomSpacer }, children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan: totalColSpan, className: "p-0" }) })
20153
- ] })
20154
- ]
20155
- }
20156
- )
20157
- ]
20158
- }
20159
- )
20160
- ),
20161
- isNoData && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
20162
- /* @__PURE__ */ jsxRuntime.jsx(
20163
- "div",
20164
- {
20165
- "aria-hidden": true,
20166
- className: "pointer-events-none absolute left-0 right-0 top-0 z-[31] h-[var(--cmp-table-header-height)] bg-white/60"
20167
- }
20168
- ),
20169
- !showNoDataSlot && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute bottom-0 left-0 right-0 top-[var(--cmp-table-header-height)] z-[30] flex items-center justify-center bg-white/60 text-[12px] text-[color:var(--color-grey-65)]", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[60px] w-full items-center justify-center", children: emptyLabel }) })
20170
- ] }),
20171
- loading2 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-30 flex items-center justify-center bg-white/60", children: /* @__PURE__ */ jsxRuntime.jsx(SCircleProgress, { indeterminate: true }) })
20172
- ]
20173
- }
20174
- ),
20175
- (showPagination || showRowsPerPageSelect) && // 원본 sd-table__pagination: 테이블 하단에 이어붙는 회색 바(높이 48px, margin-top:-1px로 테두리 겹침).
20176
- // SPagination은 전체 폭 기준 중앙, rows-per-page SSelect는 우측에 absolute 배치해 중앙 정렬을 유지한다.
20177
- // 루트 높이가 확정된 경우(테이블이 남은 영역을 채우는 배치) 페이지네이션 바가
20178
- // 눌리지 않도록 shrink-0으로 48px를 보장한다.
20179
- // 페이지네이션이 없어도 rows-per-page 셀렉트가 있으면 이 바를 그린다 — 셀렉트와
20180
- // 밀도 토글이 사는 곳이 여기다. dense 만으로는 그리지 않는다(빈 바가 남는다).
20181
- /* @__PURE__ */ jsxRuntime.jsxs(
20182
- "div",
20183
- {
20184
- className: cn(
20185
- "relative flex h-[48px] shrink-0 items-center justify-center rounded-[var(--cmp-table-radius)] bg-[var(--color-grey-05)]",
20186
- // 테두리를 끄면 겹칠 대상이 없어진다 — -mt-px 를 남기면 1px 어긋남만 생긴다.
20187
- bordered && "-mt-px border border-solid border-[color:var(--cmp-table-border-color)]"
20188
- ),
20189
- children: [
20190
- showPagination && /* @__PURE__ */ jsxRuntime.jsx(SPagination, { currentPage: displayPage, lastPage, onPageChange: changePage }),
20191
- showRowsPerPageSelect && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-[10px] top-1/2 flex -translate-y-1/2 items-center gap-[12px]", children: [
20192
- dense && /* @__PURE__ */ jsxRuntime.jsx(
20193
- STextLink,
20194
- {
20195
- size: "sm",
20196
- icon: innerDense ? "row" : "rowDense",
20197
- iconRotate: 90,
20198
- iconColor: "var(--sys-color-fg-quaternary)",
20199
- label: innerDense ? t.denseOff : t.denseOn,
20200
- onClick: () => changeDense(!innerDense)
20201
- }
20427
+ )
20428
+ }
20429
+ )
20430
+ ] }, row[rowKey] ?? ri);
20431
+ }),
20432
+ virtualScroll && bottomSpacer > 0 && /* @__PURE__ */ jsxRuntime.jsx("tr", { "aria-hidden": true, style: { height: bottomSpacer }, children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan: totalColSpan, className: "p-0" }) })
20433
+ ] })
20434
+ ]
20435
+ }
20436
+ )
20437
+ ]
20438
+ }
20439
+ )
20202
20440
  ),
20203
- dense && /* @__PURE__ */ jsxRuntime.jsx(SDivider, { vertical: true, className: "my-[6px]" }),
20204
- /* @__PURE__ */ jsxRuntime.jsx(
20205
- SSelect2,
20206
- {
20207
- options: rowsPerPageOption,
20208
- value: internalPagination ? innerRowsPerPage : pagination?.rowsPerPage,
20209
- valueAsPrimitive: true,
20210
- onValueChange: (v) => changeRowsPerPage(Number(v)),
20211
- width: 128
20212
- }
20213
- )
20214
- ] })
20215
- ]
20216
- }
20217
- ),
20218
- contextMenuOn && typeof document !== "undefined" && reactDom.createPortal(
20441
+ isNoData && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
20442
+ /* @__PURE__ */ jsxRuntime.jsx(
20443
+ "div",
20444
+ {
20445
+ "aria-hidden": true,
20446
+ className: "pointer-events-none absolute left-0 right-0 top-0 z-[31] h-[var(--cmp-table-header-height)] bg-white/60"
20447
+ }
20448
+ ),
20449
+ !showNoDataSlot && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute bottom-0 left-0 right-0 top-[var(--cmp-table-header-height)] z-[30] flex items-center justify-center bg-white/60 text-[12px] text-[color:var(--color-grey-65)]", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[60px] w-full items-center justify-center", children: emptyLabel }) })
20450
+ ] }),
20451
+ loading2 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-30 flex items-center justify-center bg-white/60", children: /* @__PURE__ */ jsxRuntime.jsx(SCircleProgress, { indeterminate: true }) })
20452
+ ]
20453
+ }
20454
+ ),
20455
+ (showPagination || showRowsPerPageSelect) && // 원본 sd-table__pagination: 테이블 하단에 이어붙는 회색 바(높이 48px, margin-top:-1px로 테두리 겹침).
20456
+ // SPagination은 전체 폭 기준 중앙, rows-per-page SSelect는 우측에 absolute 배치해 중앙 정렬을 유지한다.
20457
+ // 루트 높이가 확정된 경우(테이블이 남은 영역을 채우는 배치) 페이지네이션 바가
20458
+ // 눌리지 않도록 shrink-0으로 48px를 보장한다.
20459
+ // 페이지네이션이 없어도 rows-per-page 셀렉트가 있으면 이 바를 그린다 — 셀렉트와
20460
+ // 밀도 토글이 사는 곳이 여기다. dense 만으로는 그리지 않는다(빈 바가 남는다).
20219
20461
  /* @__PURE__ */ jsxRuntime.jsxs(
20220
- RDropdown5__namespace.Root,
20462
+ "div",
20221
20463
  {
20222
- modal: false,
20223
- open: contextMenuOpen,
20224
- onOpenChange: (open) => {
20225
- if (!open) setContextMenu((m) => m == null ? null : { ...m, open: false });
20226
- },
20464
+ className: cn(
20465
+ "relative flex h-[48px] shrink-0 items-center justify-center rounded-[var(--cmp-table-radius)] bg-[var(--color-grey-05)]",
20466
+ // 테두리를 끄면 겹칠 대상이 없어진다 — -mt-px 를 남기면 1px 어긋남만 생긴다.
20467
+ bordered && "-mt-px border border-solid border-[color:var(--cmp-table-border-color)]"
20468
+ ),
20227
20469
  children: [
20228
- /* @__PURE__ */ jsxRuntime.jsx(RDropdown5__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
20229
- "span",
20230
- {
20231
- "aria-hidden": true,
20232
- className: "pointer-events-none fixed block h-0 w-0",
20233
- style: { left: contextMenu?.x ?? 0, top: contextMenu?.y ?? 0 }
20234
- }
20235
- ) }),
20236
- /* @__PURE__ */ jsxRuntime.jsx(
20237
- RDropdown5__namespace.Content,
20238
- {
20239
- align: "start",
20240
- side: "bottom",
20241
- sideOffset: CONTEXT_MENU_CURSOR_GAP,
20242
- alignOffset: CONTEXT_MENU_CURSOR_GAP,
20243
- collisionPadding: CONTEXT_MENU_VIEWPORT_PADDING,
20244
- style: contextMenuArmed ? void 0 : { pointerEvents: "none" },
20245
- className: cn(
20246
- "z-50 overflow-hidden rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] shadow-normal-sm",
20247
- "max-w-[var(--radix-dropdown-menu-content-available-width)]",
20248
- FLOATING_SLIDE_ANIM
20249
- ),
20250
- children: contextMenuItems?.map((item4) => /* @__PURE__ */ jsxRuntime.jsxs(
20251
- RDropdown5__namespace.Item,
20252
- {
20253
- disabled: item4.disabled,
20254
- onSelect: () => onContextMenuItemClick?.(item4.value, contextMenu?.rows ?? []),
20255
- className: CONTEXT_MENU_ITEM_CLASS,
20256
- children: [
20257
- item4.icon && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: item4.icon, size: 12 }),
20258
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 break-words", children: item4.label })
20259
- ]
20260
- },
20261
- item4.value
20262
- ))
20263
- }
20264
- )
20470
+ showPagination && /* @__PURE__ */ jsxRuntime.jsx(SPagination, { currentPage: displayPage, lastPage, onPageChange: changePage }),
20471
+ showRowsPerPageSelect && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-[10px] top-1/2 flex -translate-y-1/2 items-center gap-[12px]", children: [
20472
+ dense && /* @__PURE__ */ jsxRuntime.jsx(
20473
+ STextLink,
20474
+ {
20475
+ size: "sm",
20476
+ icon: innerDense ? "row" : "rowDense",
20477
+ iconRotate: 90,
20478
+ iconColor: "var(--sys-color-fg-quaternary)",
20479
+ label: innerDense ? t.denseOff : t.denseOn,
20480
+ onClick: () => changeDense(!innerDense)
20481
+ }
20482
+ ),
20483
+ dense && /* @__PURE__ */ jsxRuntime.jsx(SDivider, { vertical: true, className: "my-[6px]" }),
20484
+ /* @__PURE__ */ jsxRuntime.jsx(
20485
+ SSelect2,
20486
+ {
20487
+ options: rowsPerPageOption,
20488
+ value: internalPagination ? innerRowsPerPage : pagination?.rowsPerPage,
20489
+ valueAsPrimitive: true,
20490
+ onValueChange: (v) => changeRowsPerPage(Number(v)),
20491
+ width: 128
20492
+ }
20493
+ )
20494
+ ] })
20265
20495
  ]
20266
- },
20267
- contextMenu == null ? "closed" : `${contextMenu.x},${contextMenu.y}`
20496
+ }
20268
20497
  ),
20269
- portalContainer ?? document.body
20270
- )
20271
- ]
20272
- }
20498
+ contextMenuOn && typeof document !== "undefined" && reactDom.createPortal(
20499
+ /* @__PURE__ */ jsxRuntime.jsxs(
20500
+ RDropdown5__namespace.Root,
20501
+ {
20502
+ modal: false,
20503
+ open: contextMenuOpen,
20504
+ onOpenChange: (open) => {
20505
+ if (!open) setContextMenu((m) => m == null ? null : { ...m, open: false });
20506
+ },
20507
+ children: [
20508
+ /* @__PURE__ */ jsxRuntime.jsx(RDropdown5__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
20509
+ "span",
20510
+ {
20511
+ "aria-hidden": true,
20512
+ className: "pointer-events-none fixed block h-0 w-0",
20513
+ style: { left: contextMenu?.x ?? 0, top: contextMenu?.y ?? 0 }
20514
+ }
20515
+ ) }),
20516
+ /* @__PURE__ */ jsxRuntime.jsx(
20517
+ RDropdown5__namespace.Content,
20518
+ {
20519
+ align: "start",
20520
+ side: "bottom",
20521
+ sideOffset: CONTEXT_MENU_CURSOR_GAP,
20522
+ alignOffset: CONTEXT_MENU_CURSOR_GAP,
20523
+ collisionPadding: CONTEXT_MENU_VIEWPORT_PADDING,
20524
+ style: contextMenuArmed ? void 0 : { pointerEvents: "none" },
20525
+ className: cn(
20526
+ "z-50 overflow-hidden rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] shadow-normal-sm",
20527
+ "max-w-[var(--radix-dropdown-menu-content-available-width)]",
20528
+ FLOATING_SLIDE_ANIM
20529
+ ),
20530
+ children: contextMenuItems?.map((item4) => /* @__PURE__ */ jsxRuntime.jsxs(
20531
+ RDropdown5__namespace.Item,
20532
+ {
20533
+ disabled: item4.disabled,
20534
+ onSelect: () => onContextMenuItemClick?.(item4.value, contextMenu?.rows ?? []),
20535
+ className: CONTEXT_MENU_ITEM_CLASS,
20536
+ children: [
20537
+ item4.icon && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: item4.icon, size: 12 }),
20538
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 break-words", children: item4.label })
20539
+ ]
20540
+ },
20541
+ item4.value
20542
+ ))
20543
+ }
20544
+ )
20545
+ ]
20546
+ },
20547
+ contextMenu == null ? "closed" : `${contextMenu.x},${contextMenu.y}`
20548
+ ),
20549
+ portalContainer ?? document.body
20550
+ )
20551
+ ]
20552
+ }
20553
+ )
20273
20554
  );
20274
20555
  });
20275
20556
  var STable2 = STableImpl;