sellmate-design-system-react 9.0.0-beta.25 → 9.0.0-beta.26

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
@@ -11506,6 +11506,7 @@ var colWidthFromDef = (c) => {
11506
11506
  };
11507
11507
  var STable = /* @__PURE__ */ react.forwardRef(function STable2({
11508
11508
  columns = [],
11509
+ headerGroups,
11509
11510
  rows = [],
11510
11511
  rowKey = "id",
11511
11512
  selectable = false,
@@ -11553,6 +11554,49 @@ var STable = /* @__PURE__ */ react.forwardRef(function STable2({
11553
11554
  onDenseChange?.(next);
11554
11555
  };
11555
11556
  const visibleCols = react.useMemo(() => columns.filter((c) => c.visible !== false), [columns]);
11557
+ const headerGroupInfo = react.useMemo(() => {
11558
+ if (!headerGroups || headerGroups.length === 0) return null;
11559
+ const indexByName = new Map(visibleCols.map((col, index) => [col.name, index]));
11560
+ const covered = /* @__PURE__ */ new Set();
11561
+ let previousLast = -1;
11562
+ const cells = headerGroups.map((group) => {
11563
+ if (group.columnNames.length === 0) {
11564
+ throw new Error("[STable] headerGroups\uC758 columnNames\uB294 \uBE44\uC5B4 \uC788\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
11565
+ }
11566
+ const indexes = group.columnNames.map((name) => {
11567
+ const index = indexByName.get(name);
11568
+ if (index == null) {
11569
+ throw new Error(`[STable] headerGroups\uC5D0 \uC874\uC7AC\uD558\uC9C0 \uC54A\uB294 \uCEEC\uB7FC\uC774 \uC9C0\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${name}`);
11570
+ }
11571
+ if (covered.has(name)) {
11572
+ throw new Error(`[STable] headerGroups\uC5D0\uC11C \uCEEC\uB7FC\uC774 \uC911\uBCF5 \uC9C0\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${name}`);
11573
+ }
11574
+ covered.add(name);
11575
+ return index;
11576
+ });
11577
+ const first = indexes[0];
11578
+ const last = indexes[indexes.length - 1];
11579
+ if (last - first + 1 !== indexes.length || indexes.some((index, i) => index !== first + i)) {
11580
+ throw new Error(
11581
+ "[STable] headerGroups\uC758 columnNames\uB294 \uC778\uC811\uD55C \uCEEC\uB7FC \uC21C\uC11C\uB85C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4."
11582
+ );
11583
+ }
11584
+ if (first <= previousLast) {
11585
+ throw new Error("[STable] headerGroups\uB294 \uCEEC\uB7FC \uC21C\uC11C\uB300\uB85C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.");
11586
+ }
11587
+ previousLast = last;
11588
+ return { ...group, firstIndex: first, colSpan: indexes.length };
11589
+ });
11590
+ if (covered.size !== visibleCols.length) {
11591
+ throw new Error("[STable] headerGroups\uC758 columnNames\uAC00 \uBAA8\uB4E0 visible \uCEEC\uB7FC\uC744 \uD3EC\uD568\uD574\uC57C \uD569\uB2C8\uB2E4.");
11592
+ }
11593
+ return {
11594
+ cells,
11595
+ rowSpanNames: new Set(
11596
+ cells.filter((cell) => cell.rowSpan === 2).flatMap((cell) => cell.columnNames)
11597
+ )
11598
+ };
11599
+ }, [headerGroups, visibleCols]);
11556
11600
  const stickyLeft = stickyColumn?.left ?? 0;
11557
11601
  const stickyRight = stickyColumn?.right ?? 0;
11558
11602
  const effectiveRowHeight = rowHeight ?? (innerDense ? DENSE_ROW_HEIGHT : DEFAULT_ROW_HEIGHT);
@@ -11815,6 +11859,35 @@ var STable = /* @__PURE__ */ react.forwardRef(function STable2({
11815
11859
  isRightEdge && scrolledRight && "shadow-[-5px_0_8px_0_rgba(34,34,34,0.1)]"
11816
11860
  );
11817
11861
  };
11862
+ const headerResizeHitArea = (i, key) => {
11863
+ const col = visibleCols[i];
11864
+ if (!headerGroupInfo || !resizable || !col || col.autoWidth || i >= visibleCols.length - 1) {
11865
+ return null;
11866
+ }
11867
+ return /* @__PURE__ */ jsxRuntime.jsx(
11868
+ "span",
11869
+ {
11870
+ onMouseDown: (e) => startResize(i, e),
11871
+ className: "absolute inset-y-0 right-0 z-[5] w-[9px] cursor-col-resize",
11872
+ "aria-hidden": true
11873
+ },
11874
+ key
11875
+ );
11876
+ };
11877
+ const headerResizer = (i, key) => {
11878
+ const col = visibleCols[i];
11879
+ if (!resizable || !col || col.autoWidth || i >= visibleCols.length - 1) return null;
11880
+ return /* @__PURE__ */ jsxRuntime.jsx(
11881
+ "span",
11882
+ {
11883
+ onMouseDown: (e) => startResize(i, e),
11884
+ className: "absolute right-0 top-1/2 z-[5] flex h-[16px] w-[9px] -translate-y-1/2 cursor-col-resize items-center justify-end",
11885
+ "aria-hidden": true,
11886
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-full w-[4px] border-x border-solid border-[color:var(--cmp-table-header-resizingBar-color)]" })
11887
+ },
11888
+ key
11889
+ );
11890
+ };
11818
11891
  const bodyScrollClass = cn(
11819
11892
  "min-h-0 flex-1 overflow-auto border-t border-solid border-[color:var(--cmp-table-border-color)]",
11820
11893
  SCROLLBAR_CLASS,
@@ -11879,145 +11952,179 @@ var STable = /* @__PURE__ */ react.forwardRef(function STable2({
11879
11952
  style: { fontSize: 12 },
11880
11953
  children: [
11881
11954
  colgroup,
11882
- /* @__PURE__ */ jsxRuntime.jsx("thead", { className: cn(isNoData && "opacity-40"), children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "h-[var(--cmp-table-header-height)] bg-[var(--cmp-table-header-bg)]", children: [
11883
- selectable && /* @__PURE__ */ jsxRuntime.jsxs(
11884
- "th",
11885
- {
11886
- className: cn(
11887
- // 원본 .th--selected: 48px 고정폭, 항상 left sticky, 체크박스 중앙정렬
11888
- // 하단 border는 헤더 래퍼가 연속으로 그리므로 셀에는 두지 않는다.
11889
- "sticky left-0 z-[7] bg-[var(--cmp-table-header-bg)] text-center align-middle",
11890
- stickyLeft === 0 && scrolledLeft && "shadow-[5px_0_8px_0_rgba(34,34,34,0.1)]"
11891
- ),
11892
- style: {
11893
- width: SELECTABLE_COLUMN_WIDTH,
11894
- minWidth: SELECTABLE_COLUMN_WIDTH,
11895
- maxWidth: SELECTABLE_COLUMN_WIDTH
11955
+ /* @__PURE__ */ jsxRuntime.jsxs("thead", { className: cn(isNoData && "opacity-40"), children: [
11956
+ headerGroupInfo && /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "h-[var(--cmp-table-header-height)] bg-[var(--cmp-table-header-bg)]", children: [
11957
+ selectable && /* @__PURE__ */ jsxRuntime.jsx(
11958
+ "th",
11959
+ {
11960
+ rowSpan: 2,
11961
+ className: cn(
11962
+ "sticky left-0 z-[7] bg-[var(--cmp-table-header-bg)] text-center align-middle",
11963
+ stickyLeft === 0 && scrolledLeft && "shadow-[5px_0_8px_0_rgba(34,34,34,0.1)]"
11964
+ ),
11965
+ style: {
11966
+ width: SELECTABLE_COLUMN_WIDTH,
11967
+ minWidth: SELECTABLE_COLUMN_WIDTH,
11968
+ maxWidth: SELECTABLE_COLUMN_WIDTH
11969
+ },
11970
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(SCheckbox, { value: headerCheck, onValueChange: toggleAll }) })
11971
+ }
11972
+ ),
11973
+ headerGroupInfo.cells.map((group) => /* @__PURE__ */ jsxRuntime.jsxs(
11974
+ "th",
11975
+ {
11976
+ colSpan: group.colSpan,
11977
+ rowSpan: group.rowSpan,
11978
+ className: cn(
11979
+ "relative border-b border-r border-solid border-[color:var(--cmp-table-border-color)] bg-[var(--cmp-table-header-bg)] px-[16px] text-[12px] font-medium leading-[20px] text-[color:var(--sys-color-fg-primary)]",
11980
+ group.firstIndex + group.colSpan === visibleCols.length && "border-r-0",
11981
+ group.rowSpan === 2 && "border-b-0",
11982
+ group.className
11983
+ ),
11984
+ style: { textAlign: group.align ?? "left", ...group.style },
11985
+ children: [
11986
+ group.render ? group.render() : group.label,
11987
+ group.rowSpan === 2 && group.colSpan === 1 ? headerResizeHitArea(
11988
+ group.firstIndex,
11989
+ `group-resizer-${group.columnNames[0]}`
11990
+ ) : null
11991
+ ]
11896
11992
  },
11897
- children: [
11898
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(SCheckbox, { value: headerCheck, onValueChange: toggleAll }) }),
11899
- /* @__PURE__ */ jsxRuntime.jsx(
11900
- "span",
11901
- {
11902
- "aria-hidden": true,
11903
- className: "pointer-events-none absolute right-0 top-1/2 h-[16px] w-px -translate-y-1/2 bg-[var(--cmp-table-header-resizingBar-color)]"
11904
- }
11905
- )
11906
- ]
11907
- }
11908
- ),
11909
- visibleCols.map((col, i) => {
11910
- const isSortable = !!col.sortable && !col.renderHeader;
11911
- const sortDir = isSortable && sort?.name === col.name ? sort.dir : void 0;
11912
- return /* @__PURE__ */ jsxRuntime.jsxs(
11993
+ group.columnNames.join("|")
11994
+ ))
11995
+ ] }),
11996
+ /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "h-[var(--cmp-table-header-height)] bg-[var(--cmp-table-header-bg)]", children: [
11997
+ selectable && !headerGroupInfo && /* @__PURE__ */ jsxRuntime.jsxs(
11913
11998
  "th",
11914
11999
  {
11915
12000
  className: cn(
11916
- // 하단 border는 헤더 래퍼가 연속으로 그린다 (거터 영역까지 이어짐).
11917
- // leading-[0]: th 의 스트럿(line-height 만큼의 빈 인라인 박스)을 없앤다.
11918
- // 내용이 inline-flex 라 스트럿이 남아 있으면 라벨보다 큰 컨트롤(정렬 버튼 등)이
11919
- // 들어올 때 줄 상자가 베이스라인 기준으로 늘어나 헤더 높이를 넘기고
11920
- // 세로 가운데도 어긋난다. 글자 줄높이는 아래 th__content 가 다시 준다.
11921
- "relative bg-[var(--cmp-table-header-bg)] px-[16px] text-[12px] font-medium leading-[0] text-[color:var(--sys-color-fg-primary)]",
11922
- stickyInfo(i).isSticky && "z-[6]",
11923
- stickyShadowClass(i),
11924
- col.thClass
12001
+ // 원본 .th--selected: 48px 고정폭, 항상 left sticky, 체크박스 중앙정렬
12002
+ // 하단 border는 헤더 래퍼가 연속으로 그리므로 셀에는 두지 않는다.
12003
+ "sticky left-0 z-[7] bg-[var(--cmp-table-header-bg)] text-center align-middle",
12004
+ stickyLeft === 0 && scrolledLeft && "shadow-[5px_0_8px_0_rgba(34,34,34,0.1)]"
11925
12005
  ),
11926
- style: { textAlign: col.align ?? "left", ...cellStyle(i), ...col.thStyle },
11927
- "aria-sort": isSortable ? sortDir === "asc" ? "ascending" : sortDir === "desc" ? "descending" : "none" : void 0,
12006
+ style: {
12007
+ width: SELECTABLE_COLUMN_WIDTH,
12008
+ minWidth: SELECTABLE_COLUMN_WIDTH,
12009
+ maxWidth: SELECTABLE_COLUMN_WIDTH
12010
+ },
11928
12011
  children: [
12012
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(SCheckbox, { value: headerCheck, onValueChange: toggleAll }) }),
11929
12013
  /* @__PURE__ */ jsxRuntime.jsx(
11930
12014
  "span",
11931
12015
  {
11932
- className: cn(
11933
- // 원본 th__content: align에 따라 justify-content 적용 (th도 정렬 반영)
11934
- // th 가 leading-[0] 이므로 글자 줄높이는 여기서 준다 (truncate 가
11935
- // overflow-hidden 이라 줄높이가 0이면 라벨이 잘려 사라진다).
11936
- "inline-flex w-full min-w-0 items-center gap-[4px] leading-[20px]",
11937
- col.align === "center" && "justify-center",
11938
- col.align === "right" && "justify-end"
11939
- ),
11940
- children: col.renderHeader ? col.renderHeader(col, i) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
11941
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: col.label }),
11942
- col.helpText && col.helpText.length > 0 && // 라벨이 좁아져 truncate 되더라도 아이콘까지 함께 줄어들면 안 되므로
11943
- // shrink-0 래퍼로 감싼다 (STooltip 의 className 은 본문 쪽으로 간다).
11944
- // 아이콘·크기·색은 SKeyValueTable 헤더 도움말과 동일하게 맞춘다.
11945
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
11946
- STooltip2,
11947
- {
11948
- trigger: "hover",
11949
- icon: "helpOutline",
11950
- iconSize: 12,
11951
- color: "var(--sys-color-fg-success)",
11952
- placement: "top",
11953
- message: col.helpText
11954
- }
11955
- ) }),
11956
- col.editable && /* @__PURE__ */ jsxRuntime.jsx(
11957
- SIcon,
11958
- {
11959
- name: "pageEdit",
11960
- size: 12,
11961
- color: "var(--sys-color-link-accent)",
11962
- label: "\uC218\uC815 \uAC00\uB2A5",
11963
- className: "shrink-0"
11964
- }
11965
- ),
11966
- col.navigable && /* @__PURE__ */ jsxRuntime.jsx(
11967
- SIcon,
11968
- {
11969
- name: "pageMove",
11970
- size: 12,
11971
- color: "var(--sys-color-link-accent)",
11972
- label: "\uB2E4\uB978 \uD654\uBA74\uC73C\uB85C \uC774\uB3D9",
11973
- className: "shrink-0"
11974
- }
11975
- ),
11976
- col.required && // 라벨이 truncate 돼도 필수 표시는 남아야 하므로 shrink-0.
11977
- // 아이콘·크기·색은 SKeyValueTable 헤더 필수 표시와 동일하게 맞춘다.
11978
- /* @__PURE__ */ jsxRuntime.jsx(
11979
- SIcon,
11980
- {
11981
- name: "asterisk",
11982
- size: 12,
11983
- color: "var(--sys-color-fg-accent)",
11984
- className: "shrink-0"
11985
- }
11986
- ),
11987
- isSortable && // 미정렬은 updown 을 default(회색)로 — 정렬할 수 있다는 것 자체가
11988
- // 헤더에 보여야 한다. 정렬 중이면 action 색으로 올려 활성임을 알린다.
11989
- // 라벨이 truncate 돼도 버튼은 남도록 shrink-0.
11990
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
11991
- SGhostButton,
11992
- {
11993
- size: "xxs",
11994
- intent: sortDir ? "action" : "default",
11995
- icon: sortDir === "asc" ? "arrowUp" : sortDir === "desc" ? "arrowDown" : "updown",
11996
- ariaLabel: `${col.label} \uC815\uB82C`,
11997
- onClick: () => toggleSort(col)
11998
- }
11999
- ) })
12000
- ] })
12001
- }
12002
- ),
12003
- resizable && !col.autoWidth && i < visibleCols.length - 1 && // 원본 th__resizer: 셀 우측 경계 안쪽 4px 그립(좌우 1px #CCCCCC 라인, 세로 16px).
12004
- // 셀 밖으로 넘치면 다음 셀에 가려져 클릭이 안 먹으므로 translate 없이 셀 내부에 배치.
12005
- // 겉보기 그립은 4px(원본과 동일), 클릭 영역은 9px로 넓혀 사용성 확보.
12006
- /* @__PURE__ */ jsxRuntime.jsx(
12007
- "span",
12008
- {
12009
- onMouseDown: (e) => startResize(i, e),
12010
- className: "absolute right-0 top-1/2 z-[5] flex h-[16px] w-[9px] -translate-y-1/2 cursor-col-resize items-center justify-end",
12011
12016
  "aria-hidden": true,
12012
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-full w-[4px] border-x border-solid border-[color:var(--cmp-table-header-resizingBar-color)]" })
12017
+ className: "pointer-events-none absolute right-0 top-1/2 h-[16px] w-px -translate-y-1/2 bg-[var(--cmp-table-header-resizingBar-color)]"
12013
12018
  }
12014
12019
  )
12015
12020
  ]
12016
- },
12017
- col.name
12018
- );
12019
- })
12020
- ] }) })
12021
+ }
12022
+ ),
12023
+ visibleCols.map((col, i) => {
12024
+ if (headerGroupInfo?.rowSpanNames.has(col.name)) return null;
12025
+ const isSortable = !!col.sortable && !col.renderHeader;
12026
+ const sortDir = isSortable && sort?.name === col.name ? sort.dir : void 0;
12027
+ return /* @__PURE__ */ jsxRuntime.jsxs(
12028
+ "th",
12029
+ {
12030
+ className: cn(
12031
+ // 하단 border는 헤더 래퍼가 연속으로 그린다 (거터 영역까지 이어짐).
12032
+ // leading-[0]: th 의 스트럿(line-height 만큼의 빈 인라인 박스)을 없앤다.
12033
+ // 내용이 inline-flex 라 스트럿이 남아 있으면 라벨보다 큰 컨트롤(정렬 버튼 등)이
12034
+ // 들어올 때 줄 상자가 베이스라인 기준으로 늘어나 헤더 높이를 넘기고
12035
+ // 세로 가운데도 어긋난다. 글자 줄높이는 아래 th__content 가 다시 준다.
12036
+ headerGroupInfo && "border-r border-solid border-[color:var(--cmp-table-border-color)]",
12037
+ headerGroupInfo && i === visibleCols.length - 1 && "border-r-0",
12038
+ "relative bg-[var(--cmp-table-header-bg)] px-[16px] text-[12px] font-medium leading-[0] text-[color:var(--sys-color-fg-primary)]",
12039
+ stickyInfo(i).isSticky && "z-[6]",
12040
+ stickyShadowClass(i),
12041
+ col.thClass
12042
+ ),
12043
+ style: { textAlign: col.align ?? "left", ...cellStyle(i), ...col.thStyle },
12044
+ "aria-sort": isSortable ? sortDir === "asc" ? "ascending" : sortDir === "desc" ? "descending" : "none" : void 0,
12045
+ children: [
12046
+ /* @__PURE__ */ jsxRuntime.jsx(
12047
+ "span",
12048
+ {
12049
+ className: cn(
12050
+ // 원본 th__content: align에 따라 justify-content 적용 (th도 정렬 반영)
12051
+ // th 가 leading-[0] 이므로 글자 줄높이는 여기서 준다 (truncate 가
12052
+ // overflow-hidden 이라 줄높이가 0이면 라벨이 잘려 사라진다).
12053
+ "inline-flex w-full min-w-0 items-center gap-[4px] leading-[20px]",
12054
+ col.align === "center" && "justify-center",
12055
+ col.align === "right" && "justify-end"
12056
+ ),
12057
+ children: col.renderHeader ? col.renderHeader(col, i) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
12058
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: col.label }),
12059
+ col.helpText && col.helpText.length > 0 && // 라벨이 좁아져 truncate 되더라도 아이콘까지 함께 줄어들면 안 되므로
12060
+ // shrink-0 래퍼로 감싼다 (STooltip 의 className 은 본문 쪽으로 간다).
12061
+ // 아이콘·크기·색은 SKeyValueTable 헤더 도움말과 동일하게 맞춘다.
12062
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
12063
+ STooltip2,
12064
+ {
12065
+ trigger: "hover",
12066
+ icon: "helpOutline",
12067
+ iconSize: 12,
12068
+ color: "var(--sys-color-fg-success)",
12069
+ placement: "top",
12070
+ message: col.helpText
12071
+ }
12072
+ ) }),
12073
+ col.editable && /* @__PURE__ */ jsxRuntime.jsx(
12074
+ SIcon,
12075
+ {
12076
+ name: "pageEdit",
12077
+ size: 12,
12078
+ color: "var(--sys-color-link-accent)",
12079
+ label: "\uC218\uC815 \uAC00\uB2A5",
12080
+ className: "shrink-0"
12081
+ }
12082
+ ),
12083
+ col.navigable && /* @__PURE__ */ jsxRuntime.jsx(
12084
+ SIcon,
12085
+ {
12086
+ name: "pageMove",
12087
+ size: 12,
12088
+ color: "var(--sys-color-link-accent)",
12089
+ label: "\uB2E4\uB978 \uD654\uBA74\uC73C\uB85C \uC774\uB3D9",
12090
+ className: "shrink-0"
12091
+ }
12092
+ ),
12093
+ col.required && // 라벨이 truncate 돼도 필수 표시는 남아야 하므로 shrink-0.
12094
+ // 아이콘·크기·색은 SKeyValueTable 헤더 필수 표시와 동일하게 맞춘다.
12095
+ /* @__PURE__ */ jsxRuntime.jsx(
12096
+ SIcon,
12097
+ {
12098
+ name: "asterisk",
12099
+ size: 12,
12100
+ color: "var(--sys-color-fg-accent)",
12101
+ className: "shrink-0"
12102
+ }
12103
+ ),
12104
+ isSortable && // 미정렬은 updown 을 default(회색)로 — 정렬할 수 있다는 것 자체가
12105
+ // 헤더에 보여야 한다. 정렬 중이면 action 색으로 올려 활성임을 알린다.
12106
+ // 라벨이 truncate 돼도 버튼은 남도록 shrink-0.
12107
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
12108
+ SGhostButton,
12109
+ {
12110
+ size: "xxs",
12111
+ intent: sortDir ? "action" : "default",
12112
+ icon: sortDir === "asc" ? "arrowUp" : sortDir === "desc" ? "arrowDown" : "updown",
12113
+ ariaLabel: `${col.label} \uC815\uB82C`,
12114
+ onClick: () => toggleSort(col)
12115
+ }
12116
+ ) })
12117
+ ] })
12118
+ }
12119
+ ),
12120
+ headerGroupInfo ? headerResizeHitArea(i, `resizer-${col.name}`) : headerResizer(i, `resizer-${col.name}`)
12121
+ ]
12122
+ },
12123
+ col.name
12124
+ );
12125
+ })
12126
+ ] })
12127
+ ] })
12021
12128
  ]
12022
12129
  }
12023
12130
  )
@@ -17380,7 +17487,8 @@ var resolveFieldDefault = (field) => {
17380
17487
  if (field.type === "datepicker-day") return todayIso();
17381
17488
  if (field.type === "select") return field.options?.[0]?.value;
17382
17489
  if (field.type === "datepicker-statistics") {
17383
- const unit = getFieldPresets(field).find((preset) => !preset.custom)?.value;
17490
+ const presets = getFieldPresets(field).filter((preset) => !preset.custom);
17491
+ const unit = presets.some((preset) => preset.value === "day") ? "day" : presets[0]?.value;
17384
17492
  return unit ? createPeriodValue(unit) : void 0;
17385
17493
  }
17386
17494
  return void 0;
@@ -17510,8 +17618,7 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
17510
17618
  onActiveKeysChange,
17511
17619
  onReset,
17512
17620
  onAddFilter,
17513
- label = "\uAC80\uC0C9 \uD544\uD130",
17514
- showLabel = false,
17621
+ label,
17515
17622
  showReset = true,
17516
17623
  disabled = false,
17517
17624
  className,
@@ -17528,6 +17635,7 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
17528
17635
  const pendingOpenKeyRef = react.useRef(null);
17529
17636
  const triggerRefs = react.useRef(/* @__PURE__ */ new Map());
17530
17637
  const initializedDefaultsRef = react.useRef(/* @__PURE__ */ new Set());
17638
+ const misuseWarningSignatureRef = react.useRef(null);
17531
17639
  const prevOpenKeyRef = react.useRef(null);
17532
17640
  const valuesRef = react.useRef(values);
17533
17641
  valuesRef.current = values;
@@ -17543,14 +17651,19 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
17543
17651
  (current) => groups.every((group) => isGroupSatisfied(group, current)),
17544
17652
  [groups]
17545
17653
  );
17654
+ const isRequiredSatisfied = react.useCallback(
17655
+ (current) => fields.every((field) => !field.required || !isEmptyValue(current[field.key])),
17656
+ [fields]
17657
+ );
17546
17658
  const runSearch = react.useCallback(
17547
17659
  (next) => {
17548
17660
  if (valuesMapEqual(lastSearchedValuesRef.current, next)) return;
17549
17661
  if (!isGroupsValid(next)) return;
17662
+ if (!isRequiredSatisfied(next)) return;
17550
17663
  lastSearchedValuesRef.current = next;
17551
17664
  onSearch?.(next);
17552
17665
  },
17553
- [isGroupsValid, onSearch]
17666
+ [isGroupsValid, isRequiredSatisfied, onSearch]
17554
17667
  );
17555
17668
  react.useEffect(() => {
17556
17669
  const prevKey = prevOpenKeyRef.current;
@@ -17585,14 +17698,24 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
17585
17698
  });
17586
17699
  if (changed) onValueChange?.({ ...values, ...patch });
17587
17700
  }, [fields, values, onValueChange]);
17588
- if (typeof console !== "undefined") {
17589
- const misused = fields.filter((f) => f.defaultValue !== void 0 && !isFixedField(f));
17590
- if (misused.length > 0) {
17701
+ react.useEffect(() => {
17702
+ if (typeof console === "undefined") return;
17703
+ const misusedKeys = fields.filter((f) => f.defaultValue !== void 0 && !isFixedField(f)).map((f) => f.key);
17704
+ const requiredWithoutDefaultKeys = fields.filter((f) => f.required === true && resolveFieldDefault(f) === void 0).map((f) => f.key);
17705
+ const signature = JSON.stringify([misusedKeys, requiredWithoutDefaultKeys]);
17706
+ if (signature === misuseWarningSignatureRef.current) return;
17707
+ misuseWarningSignatureRef.current = signature;
17708
+ if (misusedKeys.length > 0) {
17591
17709
  console.warn(
17592
- `[SChipFilter] defaultValue \uB294 fixed\xB7required \uD544\uD130\uC5D0\uC11C\uB9CC \uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uBB34\uC2DC\uD569\uB2C8\uB2E4. (${misused.map((f) => f.key).join(", ")})`
17710
+ `[SChipFilter] defaultValue \uB294 fixed\xB7required \uD544\uD130\uC5D0\uC11C\uB9CC \uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uBB34\uC2DC\uD569\uB2C8\uB2E4. (${misusedKeys.join(", ")})`
17593
17711
  );
17594
17712
  }
17595
- }
17713
+ if (requiredWithoutDefaultKeys.length > 0) {
17714
+ console.warn(
17715
+ `[SChipFilter] required \uD544\uD130\uC5D0 defaultValue \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uC774 \uD0C0\uC785\uC740 \uB0B4\uC7A5 \uAE30\uBCF8\uAC12\uC774 \uC5C6\uC5B4 "\uAC80\uC0C9 \uCD08\uAE30\uD654" \uC2DC \uAC12\uC774 \uBE44\uC6CC\uC9C0\uACE0 \uAC80\uC0C9\uB3C4 \uB9C9\uD799\uB2C8\uB2E4. defaultValue \uB97C \uC9C0\uC815\uD558\uC138\uC694. (${requiredWithoutDefaultKeys.join(", ")})`
17716
+ );
17717
+ }
17718
+ }, [fields]);
17596
17719
  const fixedKeys = fields.filter(isFixedField).map((field) => field.key);
17597
17720
  const resolvedActiveKeys = Array.from(
17598
17721
  /* @__PURE__ */ new Set([...activeKeys ?? internalActiveKeys, ...fixedKeys])
@@ -17660,7 +17783,10 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
17660
17783
  },
17661
17784
  [fields]
17662
17785
  );
17663
- const validate = react.useCallback(() => invalidGroups.size === 0, [invalidGroups]);
17786
+ const validate = react.useCallback(
17787
+ () => invalidGroups.size === 0 && isRequiredSatisfied(values),
17788
+ [invalidGroups, isRequiredSatisfied, values]
17789
+ );
17664
17790
  react.useImperativeHandle(ref, () => ({ open, reset, validate }), [open, reset, validate]);
17665
17791
  const addField = (key) => {
17666
17792
  const next = [...resolvedActiveKeys, key];
@@ -17697,7 +17823,7 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
17697
17823
  ),
17698
17824
  style: { ...rootStyle, borderColor: "var(--chipfilter-bar-border)" },
17699
17825
  children: [
17700
- showLabel && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
17826
+ label && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
17701
17827
  /* @__PURE__ */ jsxRuntime.jsx(STag, { className: "shrink-0", shape: "square", size: "sm", color: "darkblue", label }),
17702
17828
  /* @__PURE__ */ jsxRuntime.jsx(Divider, { top: true })
17703
17829
  ] }),
@@ -18407,6 +18533,10 @@ function InlineDatePresetFilter({
18407
18533
  setSelectedId(preset.value);
18408
18534
  onValueChange(preset.resolve ? preset.resolve() : null);
18409
18535
  };
18536
+ const badgeBorder = disabled ? CHIPFILTER.chipDisabledBorder : warning ? CHIPFILTER.chipWarningBorder : CHIPFILTER.chipAppliedBorder;
18537
+ const badgeBackground = disabled ? CHIPFILTER.chipDisabledBg : CHIPFILTER.chipAppliedBg;
18538
+ const badgeLabelColor = disabled ? CHIPFILTER.chipDisabledText : CHIPFILTER.chipAppliedLabel;
18539
+ const badgeValueColor = disabled ? CHIPFILTER.chipDisabledText : CHIPFILTER.chipAppliedValue;
18410
18540
  const badge = applied && /* @__PURE__ */ jsxRuntime.jsxs(
18411
18541
  "span",
18412
18542
  {
@@ -18414,17 +18544,17 @@ function InlineDatePresetFilter({
18414
18544
  style: {
18415
18545
  minHeight: CHIPFILTER.chipHeight,
18416
18546
  borderRadius: CHIPFILTER.chipRadius,
18417
- borderColor: warning ? CHIPFILTER.chipWarningBorder : CHIPFILTER.chipAppliedBorder,
18418
- background: CHIPFILTER.chipAppliedBg,
18419
- color: CHIPFILTER.chipAppliedLabel,
18420
- cursor: rangeValue ? "pointer" : void 0
18547
+ borderColor: badgeBorder,
18548
+ background: badgeBackground,
18549
+ color: badgeLabelColor,
18550
+ cursor: rangeValue && !disabled ? "pointer" : void 0
18421
18551
  },
18422
18552
  children: [
18423
18553
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-bold", children: [
18424
18554
  field.label,
18425
18555
  ":"
18426
18556
  ] }),
18427
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-bold", style: { color: CHIPFILTER.chipAppliedValue }, children: displayValue(field, value) }),
18557
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-bold", style: { color: badgeValueColor }, children: displayValue(field, value) }),
18428
18558
  !disabled && showClear && /* @__PURE__ */ jsxRuntime.jsx(
18429
18559
  "span",
18430
18560
  {