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.js CHANGED
@@ -11478,6 +11478,7 @@ var colWidthFromDef = (c) => {
11478
11478
  };
11479
11479
  var STable = /* @__PURE__ */ forwardRef(function STable2({
11480
11480
  columns = [],
11481
+ headerGroups,
11481
11482
  rows = [],
11482
11483
  rowKey = "id",
11483
11484
  selectable = false,
@@ -11525,6 +11526,49 @@ var STable = /* @__PURE__ */ forwardRef(function STable2({
11525
11526
  onDenseChange?.(next);
11526
11527
  };
11527
11528
  const visibleCols = useMemo(() => columns.filter((c) => c.visible !== false), [columns]);
11529
+ const headerGroupInfo = useMemo(() => {
11530
+ if (!headerGroups || headerGroups.length === 0) return null;
11531
+ const indexByName = new Map(visibleCols.map((col, index) => [col.name, index]));
11532
+ const covered = /* @__PURE__ */ new Set();
11533
+ let previousLast = -1;
11534
+ const cells = headerGroups.map((group) => {
11535
+ if (group.columnNames.length === 0) {
11536
+ throw new Error("[STable] headerGroups\uC758 columnNames\uB294 \uBE44\uC5B4 \uC788\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
11537
+ }
11538
+ const indexes = group.columnNames.map((name) => {
11539
+ const index = indexByName.get(name);
11540
+ if (index == null) {
11541
+ throw new Error(`[STable] headerGroups\uC5D0 \uC874\uC7AC\uD558\uC9C0 \uC54A\uB294 \uCEEC\uB7FC\uC774 \uC9C0\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${name}`);
11542
+ }
11543
+ if (covered.has(name)) {
11544
+ throw new Error(`[STable] headerGroups\uC5D0\uC11C \uCEEC\uB7FC\uC774 \uC911\uBCF5 \uC9C0\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${name}`);
11545
+ }
11546
+ covered.add(name);
11547
+ return index;
11548
+ });
11549
+ const first = indexes[0];
11550
+ const last = indexes[indexes.length - 1];
11551
+ if (last - first + 1 !== indexes.length || indexes.some((index, i) => index !== first + i)) {
11552
+ throw new Error(
11553
+ "[STable] headerGroups\uC758 columnNames\uB294 \uC778\uC811\uD55C \uCEEC\uB7FC \uC21C\uC11C\uB85C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4."
11554
+ );
11555
+ }
11556
+ if (first <= previousLast) {
11557
+ throw new Error("[STable] headerGroups\uB294 \uCEEC\uB7FC \uC21C\uC11C\uB300\uB85C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.");
11558
+ }
11559
+ previousLast = last;
11560
+ return { ...group, firstIndex: first, colSpan: indexes.length };
11561
+ });
11562
+ if (covered.size !== visibleCols.length) {
11563
+ throw new Error("[STable] headerGroups\uC758 columnNames\uAC00 \uBAA8\uB4E0 visible \uCEEC\uB7FC\uC744 \uD3EC\uD568\uD574\uC57C \uD569\uB2C8\uB2E4.");
11564
+ }
11565
+ return {
11566
+ cells,
11567
+ rowSpanNames: new Set(
11568
+ cells.filter((cell) => cell.rowSpan === 2).flatMap((cell) => cell.columnNames)
11569
+ )
11570
+ };
11571
+ }, [headerGroups, visibleCols]);
11528
11572
  const stickyLeft = stickyColumn?.left ?? 0;
11529
11573
  const stickyRight = stickyColumn?.right ?? 0;
11530
11574
  const effectiveRowHeight = rowHeight ?? (innerDense ? DENSE_ROW_HEIGHT : DEFAULT_ROW_HEIGHT);
@@ -11787,6 +11831,35 @@ var STable = /* @__PURE__ */ forwardRef(function STable2({
11787
11831
  isRightEdge && scrolledRight && "shadow-[-5px_0_8px_0_rgba(34,34,34,0.1)]"
11788
11832
  );
11789
11833
  };
11834
+ const headerResizeHitArea = (i, key) => {
11835
+ const col = visibleCols[i];
11836
+ if (!headerGroupInfo || !resizable || !col || col.autoWidth || i >= visibleCols.length - 1) {
11837
+ return null;
11838
+ }
11839
+ return /* @__PURE__ */ jsx(
11840
+ "span",
11841
+ {
11842
+ onMouseDown: (e) => startResize(i, e),
11843
+ className: "absolute inset-y-0 right-0 z-[5] w-[9px] cursor-col-resize",
11844
+ "aria-hidden": true
11845
+ },
11846
+ key
11847
+ );
11848
+ };
11849
+ const headerResizer = (i, key) => {
11850
+ const col = visibleCols[i];
11851
+ if (!resizable || !col || col.autoWidth || i >= visibleCols.length - 1) return null;
11852
+ return /* @__PURE__ */ jsx(
11853
+ "span",
11854
+ {
11855
+ onMouseDown: (e) => startResize(i, e),
11856
+ 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",
11857
+ "aria-hidden": true,
11858
+ children: /* @__PURE__ */ jsx("span", { className: "h-full w-[4px] border-x border-solid border-[color:var(--cmp-table-header-resizingBar-color)]" })
11859
+ },
11860
+ key
11861
+ );
11862
+ };
11790
11863
  const bodyScrollClass = cn(
11791
11864
  "min-h-0 flex-1 overflow-auto border-t border-solid border-[color:var(--cmp-table-border-color)]",
11792
11865
  SCROLLBAR_CLASS,
@@ -11851,145 +11924,179 @@ var STable = /* @__PURE__ */ forwardRef(function STable2({
11851
11924
  style: { fontSize: 12 },
11852
11925
  children: [
11853
11926
  colgroup,
11854
- /* @__PURE__ */ jsx("thead", { className: cn(isNoData && "opacity-40"), children: /* @__PURE__ */ jsxs("tr", { className: "h-[var(--cmp-table-header-height)] bg-[var(--cmp-table-header-bg)]", children: [
11855
- selectable && /* @__PURE__ */ jsxs(
11856
- "th",
11857
- {
11858
- className: cn(
11859
- // 원본 .th--selected: 48px 고정폭, 항상 left sticky, 체크박스 중앙정렬
11860
- // 하단 border는 헤더 래퍼가 연속으로 그리므로 셀에는 두지 않는다.
11861
- "sticky left-0 z-[7] bg-[var(--cmp-table-header-bg)] text-center align-middle",
11862
- stickyLeft === 0 && scrolledLeft && "shadow-[5px_0_8px_0_rgba(34,34,34,0.1)]"
11863
- ),
11864
- style: {
11865
- width: SELECTABLE_COLUMN_WIDTH,
11866
- minWidth: SELECTABLE_COLUMN_WIDTH,
11867
- maxWidth: SELECTABLE_COLUMN_WIDTH
11927
+ /* @__PURE__ */ jsxs("thead", { className: cn(isNoData && "opacity-40"), children: [
11928
+ headerGroupInfo && /* @__PURE__ */ jsxs("tr", { className: "h-[var(--cmp-table-header-height)] bg-[var(--cmp-table-header-bg)]", children: [
11929
+ selectable && /* @__PURE__ */ jsx(
11930
+ "th",
11931
+ {
11932
+ rowSpan: 2,
11933
+ className: cn(
11934
+ "sticky left-0 z-[7] bg-[var(--cmp-table-header-bg)] text-center align-middle",
11935
+ stickyLeft === 0 && scrolledLeft && "shadow-[5px_0_8px_0_rgba(34,34,34,0.1)]"
11936
+ ),
11937
+ style: {
11938
+ width: SELECTABLE_COLUMN_WIDTH,
11939
+ minWidth: SELECTABLE_COLUMN_WIDTH,
11940
+ maxWidth: SELECTABLE_COLUMN_WIDTH
11941
+ },
11942
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx(SCheckbox, { value: headerCheck, onValueChange: toggleAll }) })
11943
+ }
11944
+ ),
11945
+ headerGroupInfo.cells.map((group) => /* @__PURE__ */ jsxs(
11946
+ "th",
11947
+ {
11948
+ colSpan: group.colSpan,
11949
+ rowSpan: group.rowSpan,
11950
+ className: cn(
11951
+ "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)]",
11952
+ group.firstIndex + group.colSpan === visibleCols.length && "border-r-0",
11953
+ group.rowSpan === 2 && "border-b-0",
11954
+ group.className
11955
+ ),
11956
+ style: { textAlign: group.align ?? "left", ...group.style },
11957
+ children: [
11958
+ group.render ? group.render() : group.label,
11959
+ group.rowSpan === 2 && group.colSpan === 1 ? headerResizeHitArea(
11960
+ group.firstIndex,
11961
+ `group-resizer-${group.columnNames[0]}`
11962
+ ) : null
11963
+ ]
11868
11964
  },
11869
- children: [
11870
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx(SCheckbox, { value: headerCheck, onValueChange: toggleAll }) }),
11871
- /* @__PURE__ */ jsx(
11872
- "span",
11873
- {
11874
- "aria-hidden": true,
11875
- 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)]"
11876
- }
11877
- )
11878
- ]
11879
- }
11880
- ),
11881
- visibleCols.map((col, i) => {
11882
- const isSortable = !!col.sortable && !col.renderHeader;
11883
- const sortDir = isSortable && sort?.name === col.name ? sort.dir : void 0;
11884
- return /* @__PURE__ */ jsxs(
11965
+ group.columnNames.join("|")
11966
+ ))
11967
+ ] }),
11968
+ /* @__PURE__ */ jsxs("tr", { className: "h-[var(--cmp-table-header-height)] bg-[var(--cmp-table-header-bg)]", children: [
11969
+ selectable && !headerGroupInfo && /* @__PURE__ */ jsxs(
11885
11970
  "th",
11886
11971
  {
11887
11972
  className: cn(
11888
- // 하단 border는 헤더 래퍼가 연속으로 그린다 (거터 영역까지 이어짐).
11889
- // leading-[0]: th 의 스트럿(line-height 만큼의 빈 인라인 박스)을 없앤다.
11890
- // 내용이 inline-flex 라 스트럿이 남아 있으면 라벨보다 큰 컨트롤(정렬 버튼 등)이
11891
- // 들어올 때 줄 상자가 베이스라인 기준으로 늘어나 헤더 높이를 넘기고
11892
- // 세로 가운데도 어긋난다. 글자 줄높이는 아래 th__content 가 다시 준다.
11893
- "relative bg-[var(--cmp-table-header-bg)] px-[16px] text-[12px] font-medium leading-[0] text-[color:var(--sys-color-fg-primary)]",
11894
- stickyInfo(i).isSticky && "z-[6]",
11895
- stickyShadowClass(i),
11896
- col.thClass
11973
+ // 원본 .th--selected: 48px 고정폭, 항상 left sticky, 체크박스 중앙정렬
11974
+ // 하단 border는 헤더 래퍼가 연속으로 그리므로 셀에는 두지 않는다.
11975
+ "sticky left-0 z-[7] bg-[var(--cmp-table-header-bg)] text-center align-middle",
11976
+ stickyLeft === 0 && scrolledLeft && "shadow-[5px_0_8px_0_rgba(34,34,34,0.1)]"
11897
11977
  ),
11898
- style: { textAlign: col.align ?? "left", ...cellStyle(i), ...col.thStyle },
11899
- "aria-sort": isSortable ? sortDir === "asc" ? "ascending" : sortDir === "desc" ? "descending" : "none" : void 0,
11978
+ style: {
11979
+ width: SELECTABLE_COLUMN_WIDTH,
11980
+ minWidth: SELECTABLE_COLUMN_WIDTH,
11981
+ maxWidth: SELECTABLE_COLUMN_WIDTH
11982
+ },
11900
11983
  children: [
11984
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx(SCheckbox, { value: headerCheck, onValueChange: toggleAll }) }),
11901
11985
  /* @__PURE__ */ jsx(
11902
11986
  "span",
11903
11987
  {
11904
- className: cn(
11905
- // 원본 th__content: align에 따라 justify-content 적용 (th도 정렬 반영)
11906
- // th 가 leading-[0] 이므로 글자 줄높이는 여기서 준다 (truncate 가
11907
- // overflow-hidden 이라 줄높이가 0이면 라벨이 잘려 사라진다).
11908
- "inline-flex w-full min-w-0 items-center gap-[4px] leading-[20px]",
11909
- col.align === "center" && "justify-center",
11910
- col.align === "right" && "justify-end"
11911
- ),
11912
- children: col.renderHeader ? col.renderHeader(col, i) : /* @__PURE__ */ jsxs(Fragment, { children: [
11913
- /* @__PURE__ */ jsx("span", { className: "truncate", children: col.label }),
11914
- col.helpText && col.helpText.length > 0 && // 라벨이 좁아져 truncate 되더라도 아이콘까지 함께 줄어들면 안 되므로
11915
- // shrink-0 래퍼로 감싼다 (STooltip 의 className 은 본문 쪽으로 간다).
11916
- // 아이콘·크기·색은 SKeyValueTable 헤더 도움말과 동일하게 맞춘다.
11917
- /* @__PURE__ */ jsx("span", { className: "inline-flex shrink-0", children: /* @__PURE__ */ jsx(
11918
- STooltip2,
11919
- {
11920
- trigger: "hover",
11921
- icon: "helpOutline",
11922
- iconSize: 12,
11923
- color: "var(--sys-color-fg-success)",
11924
- placement: "top",
11925
- message: col.helpText
11926
- }
11927
- ) }),
11928
- col.editable && /* @__PURE__ */ jsx(
11929
- SIcon,
11930
- {
11931
- name: "pageEdit",
11932
- size: 12,
11933
- color: "var(--sys-color-link-accent)",
11934
- label: "\uC218\uC815 \uAC00\uB2A5",
11935
- className: "shrink-0"
11936
- }
11937
- ),
11938
- col.navigable && /* @__PURE__ */ jsx(
11939
- SIcon,
11940
- {
11941
- name: "pageMove",
11942
- size: 12,
11943
- color: "var(--sys-color-link-accent)",
11944
- label: "\uB2E4\uB978 \uD654\uBA74\uC73C\uB85C \uC774\uB3D9",
11945
- className: "shrink-0"
11946
- }
11947
- ),
11948
- col.required && // 라벨이 truncate 돼도 필수 표시는 남아야 하므로 shrink-0.
11949
- // 아이콘·크기·색은 SKeyValueTable 헤더 필수 표시와 동일하게 맞춘다.
11950
- /* @__PURE__ */ jsx(
11951
- SIcon,
11952
- {
11953
- name: "asterisk",
11954
- size: 12,
11955
- color: "var(--sys-color-fg-accent)",
11956
- className: "shrink-0"
11957
- }
11958
- ),
11959
- isSortable && // 미정렬은 updown 을 default(회색)로 — 정렬할 수 있다는 것 자체가
11960
- // 헤더에 보여야 한다. 정렬 중이면 action 색으로 올려 활성임을 알린다.
11961
- // 라벨이 truncate 돼도 버튼은 남도록 shrink-0.
11962
- /* @__PURE__ */ jsx("span", { className: "inline-flex shrink-0", children: /* @__PURE__ */ jsx(
11963
- SGhostButton,
11964
- {
11965
- size: "xxs",
11966
- intent: sortDir ? "action" : "default",
11967
- icon: sortDir === "asc" ? "arrowUp" : sortDir === "desc" ? "arrowDown" : "updown",
11968
- ariaLabel: `${col.label} \uC815\uB82C`,
11969
- onClick: () => toggleSort(col)
11970
- }
11971
- ) })
11972
- ] })
11973
- }
11974
- ),
11975
- resizable && !col.autoWidth && i < visibleCols.length - 1 && // 원본 th__resizer: 셀 우측 경계 안쪽 4px 그립(좌우 1px #CCCCCC 라인, 세로 16px).
11976
- // 셀 밖으로 넘치면 다음 셀에 가려져 클릭이 안 먹으므로 translate 없이 셀 내부에 배치.
11977
- // 겉보기 그립은 4px(원본과 동일), 클릭 영역은 9px로 넓혀 사용성 확보.
11978
- /* @__PURE__ */ jsx(
11979
- "span",
11980
- {
11981
- onMouseDown: (e) => startResize(i, e),
11982
- 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",
11983
11988
  "aria-hidden": true,
11984
- children: /* @__PURE__ */ jsx("span", { className: "h-full w-[4px] border-x border-solid border-[color:var(--cmp-table-header-resizingBar-color)]" })
11989
+ 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)]"
11985
11990
  }
11986
11991
  )
11987
11992
  ]
11988
- },
11989
- col.name
11990
- );
11991
- })
11992
- ] }) })
11993
+ }
11994
+ ),
11995
+ visibleCols.map((col, i) => {
11996
+ if (headerGroupInfo?.rowSpanNames.has(col.name)) return null;
11997
+ const isSortable = !!col.sortable && !col.renderHeader;
11998
+ const sortDir = isSortable && sort?.name === col.name ? sort.dir : void 0;
11999
+ return /* @__PURE__ */ jsxs(
12000
+ "th",
12001
+ {
12002
+ className: cn(
12003
+ // 하단 border는 헤더 래퍼가 연속으로 그린다 (거터 영역까지 이어짐).
12004
+ // leading-[0]: th 의 스트럿(line-height 만큼의 빈 인라인 박스)을 없앤다.
12005
+ // 내용이 inline-flex 라 스트럿이 남아 있으면 라벨보다 큰 컨트롤(정렬 버튼 등)이
12006
+ // 들어올 때 줄 상자가 베이스라인 기준으로 늘어나 헤더 높이를 넘기고
12007
+ // 세로 가운데도 어긋난다. 글자 줄높이는 아래 th__content 가 다시 준다.
12008
+ headerGroupInfo && "border-r border-solid border-[color:var(--cmp-table-border-color)]",
12009
+ headerGroupInfo && i === visibleCols.length - 1 && "border-r-0",
12010
+ "relative bg-[var(--cmp-table-header-bg)] px-[16px] text-[12px] font-medium leading-[0] text-[color:var(--sys-color-fg-primary)]",
12011
+ stickyInfo(i).isSticky && "z-[6]",
12012
+ stickyShadowClass(i),
12013
+ col.thClass
12014
+ ),
12015
+ style: { textAlign: col.align ?? "left", ...cellStyle(i), ...col.thStyle },
12016
+ "aria-sort": isSortable ? sortDir === "asc" ? "ascending" : sortDir === "desc" ? "descending" : "none" : void 0,
12017
+ children: [
12018
+ /* @__PURE__ */ jsx(
12019
+ "span",
12020
+ {
12021
+ className: cn(
12022
+ // 원본 th__content: align에 따라 justify-content 적용 (th도 정렬 반영)
12023
+ // th 가 leading-[0] 이므로 글자 줄높이는 여기서 준다 (truncate 가
12024
+ // overflow-hidden 이라 줄높이가 0이면 라벨이 잘려 사라진다).
12025
+ "inline-flex w-full min-w-0 items-center gap-[4px] leading-[20px]",
12026
+ col.align === "center" && "justify-center",
12027
+ col.align === "right" && "justify-end"
12028
+ ),
12029
+ children: col.renderHeader ? col.renderHeader(col, i) : /* @__PURE__ */ jsxs(Fragment, { children: [
12030
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: col.label }),
12031
+ col.helpText && col.helpText.length > 0 && // 라벨이 좁아져 truncate 되더라도 아이콘까지 함께 줄어들면 안 되므로
12032
+ // shrink-0 래퍼로 감싼다 (STooltip 의 className 은 본문 쪽으로 간다).
12033
+ // 아이콘·크기·색은 SKeyValueTable 헤더 도움말과 동일하게 맞춘다.
12034
+ /* @__PURE__ */ jsx("span", { className: "inline-flex shrink-0", children: /* @__PURE__ */ jsx(
12035
+ STooltip2,
12036
+ {
12037
+ trigger: "hover",
12038
+ icon: "helpOutline",
12039
+ iconSize: 12,
12040
+ color: "var(--sys-color-fg-success)",
12041
+ placement: "top",
12042
+ message: col.helpText
12043
+ }
12044
+ ) }),
12045
+ col.editable && /* @__PURE__ */ jsx(
12046
+ SIcon,
12047
+ {
12048
+ name: "pageEdit",
12049
+ size: 12,
12050
+ color: "var(--sys-color-link-accent)",
12051
+ label: "\uC218\uC815 \uAC00\uB2A5",
12052
+ className: "shrink-0"
12053
+ }
12054
+ ),
12055
+ col.navigable && /* @__PURE__ */ jsx(
12056
+ SIcon,
12057
+ {
12058
+ name: "pageMove",
12059
+ size: 12,
12060
+ color: "var(--sys-color-link-accent)",
12061
+ label: "\uB2E4\uB978 \uD654\uBA74\uC73C\uB85C \uC774\uB3D9",
12062
+ className: "shrink-0"
12063
+ }
12064
+ ),
12065
+ col.required && // 라벨이 truncate 돼도 필수 표시는 남아야 하므로 shrink-0.
12066
+ // 아이콘·크기·색은 SKeyValueTable 헤더 필수 표시와 동일하게 맞춘다.
12067
+ /* @__PURE__ */ jsx(
12068
+ SIcon,
12069
+ {
12070
+ name: "asterisk",
12071
+ size: 12,
12072
+ color: "var(--sys-color-fg-accent)",
12073
+ className: "shrink-0"
12074
+ }
12075
+ ),
12076
+ isSortable && // 미정렬은 updown 을 default(회색)로 — 정렬할 수 있다는 것 자체가
12077
+ // 헤더에 보여야 한다. 정렬 중이면 action 색으로 올려 활성임을 알린다.
12078
+ // 라벨이 truncate 돼도 버튼은 남도록 shrink-0.
12079
+ /* @__PURE__ */ jsx("span", { className: "inline-flex shrink-0", children: /* @__PURE__ */ jsx(
12080
+ SGhostButton,
12081
+ {
12082
+ size: "xxs",
12083
+ intent: sortDir ? "action" : "default",
12084
+ icon: sortDir === "asc" ? "arrowUp" : sortDir === "desc" ? "arrowDown" : "updown",
12085
+ ariaLabel: `${col.label} \uC815\uB82C`,
12086
+ onClick: () => toggleSort(col)
12087
+ }
12088
+ ) })
12089
+ ] })
12090
+ }
12091
+ ),
12092
+ headerGroupInfo ? headerResizeHitArea(i, `resizer-${col.name}`) : headerResizer(i, `resizer-${col.name}`)
12093
+ ]
12094
+ },
12095
+ col.name
12096
+ );
12097
+ })
12098
+ ] })
12099
+ ] })
11993
12100
  ]
11994
12101
  }
11995
12102
  )
@@ -17352,7 +17459,8 @@ var resolveFieldDefault = (field) => {
17352
17459
  if (field.type === "datepicker-day") return todayIso();
17353
17460
  if (field.type === "select") return field.options?.[0]?.value;
17354
17461
  if (field.type === "datepicker-statistics") {
17355
- const unit = getFieldPresets(field).find((preset) => !preset.custom)?.value;
17462
+ const presets = getFieldPresets(field).filter((preset) => !preset.custom);
17463
+ const unit = presets.some((preset) => preset.value === "day") ? "day" : presets[0]?.value;
17356
17464
  return unit ? createPeriodValue(unit) : void 0;
17357
17465
  }
17358
17466
  return void 0;
@@ -17482,8 +17590,7 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
17482
17590
  onActiveKeysChange,
17483
17591
  onReset,
17484
17592
  onAddFilter,
17485
- label = "\uAC80\uC0C9 \uD544\uD130",
17486
- showLabel = false,
17593
+ label,
17487
17594
  showReset = true,
17488
17595
  disabled = false,
17489
17596
  className,
@@ -17500,6 +17607,7 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
17500
17607
  const pendingOpenKeyRef = useRef(null);
17501
17608
  const triggerRefs = useRef(/* @__PURE__ */ new Map());
17502
17609
  const initializedDefaultsRef = useRef(/* @__PURE__ */ new Set());
17610
+ const misuseWarningSignatureRef = useRef(null);
17503
17611
  const prevOpenKeyRef = useRef(null);
17504
17612
  const valuesRef = useRef(values);
17505
17613
  valuesRef.current = values;
@@ -17515,14 +17623,19 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
17515
17623
  (current) => groups.every((group) => isGroupSatisfied(group, current)),
17516
17624
  [groups]
17517
17625
  );
17626
+ const isRequiredSatisfied = useCallback(
17627
+ (current) => fields.every((field) => !field.required || !isEmptyValue(current[field.key])),
17628
+ [fields]
17629
+ );
17518
17630
  const runSearch = useCallback(
17519
17631
  (next) => {
17520
17632
  if (valuesMapEqual(lastSearchedValuesRef.current, next)) return;
17521
17633
  if (!isGroupsValid(next)) return;
17634
+ if (!isRequiredSatisfied(next)) return;
17522
17635
  lastSearchedValuesRef.current = next;
17523
17636
  onSearch?.(next);
17524
17637
  },
17525
- [isGroupsValid, onSearch]
17638
+ [isGroupsValid, isRequiredSatisfied, onSearch]
17526
17639
  );
17527
17640
  useEffect(() => {
17528
17641
  const prevKey = prevOpenKeyRef.current;
@@ -17557,14 +17670,24 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
17557
17670
  });
17558
17671
  if (changed) onValueChange?.({ ...values, ...patch });
17559
17672
  }, [fields, values, onValueChange]);
17560
- if (typeof console !== "undefined") {
17561
- const misused = fields.filter((f) => f.defaultValue !== void 0 && !isFixedField(f));
17562
- if (misused.length > 0) {
17673
+ useEffect(() => {
17674
+ if (typeof console === "undefined") return;
17675
+ const misusedKeys = fields.filter((f) => f.defaultValue !== void 0 && !isFixedField(f)).map((f) => f.key);
17676
+ const requiredWithoutDefaultKeys = fields.filter((f) => f.required === true && resolveFieldDefault(f) === void 0).map((f) => f.key);
17677
+ const signature = JSON.stringify([misusedKeys, requiredWithoutDefaultKeys]);
17678
+ if (signature === misuseWarningSignatureRef.current) return;
17679
+ misuseWarningSignatureRef.current = signature;
17680
+ if (misusedKeys.length > 0) {
17563
17681
  console.warn(
17564
- `[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(", ")})`
17682
+ `[SChipFilter] defaultValue \uB294 fixed\xB7required \uD544\uD130\uC5D0\uC11C\uB9CC \uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uBB34\uC2DC\uD569\uB2C8\uB2E4. (${misusedKeys.join(", ")})`
17565
17683
  );
17566
17684
  }
17567
- }
17685
+ if (requiredWithoutDefaultKeys.length > 0) {
17686
+ console.warn(
17687
+ `[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(", ")})`
17688
+ );
17689
+ }
17690
+ }, [fields]);
17568
17691
  const fixedKeys = fields.filter(isFixedField).map((field) => field.key);
17569
17692
  const resolvedActiveKeys = Array.from(
17570
17693
  /* @__PURE__ */ new Set([...activeKeys ?? internalActiveKeys, ...fixedKeys])
@@ -17632,7 +17755,10 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
17632
17755
  },
17633
17756
  [fields]
17634
17757
  );
17635
- const validate = useCallback(() => invalidGroups.size === 0, [invalidGroups]);
17758
+ const validate = useCallback(
17759
+ () => invalidGroups.size === 0 && isRequiredSatisfied(values),
17760
+ [invalidGroups, isRequiredSatisfied, values]
17761
+ );
17636
17762
  useImperativeHandle(ref, () => ({ open, reset, validate }), [open, reset, validate]);
17637
17763
  const addField = (key) => {
17638
17764
  const next = [...resolvedActiveKeys, key];
@@ -17669,7 +17795,7 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
17669
17795
  ),
17670
17796
  style: { ...rootStyle, borderColor: "var(--chipfilter-bar-border)" },
17671
17797
  children: [
17672
- showLabel && /* @__PURE__ */ jsxs(Fragment, { children: [
17798
+ label && /* @__PURE__ */ jsxs(Fragment, { children: [
17673
17799
  /* @__PURE__ */ jsx(STag, { className: "shrink-0", shape: "square", size: "sm", color: "darkblue", label }),
17674
17800
  /* @__PURE__ */ jsx(Divider, { top: true })
17675
17801
  ] }),
@@ -18379,6 +18505,10 @@ function InlineDatePresetFilter({
18379
18505
  setSelectedId(preset.value);
18380
18506
  onValueChange(preset.resolve ? preset.resolve() : null);
18381
18507
  };
18508
+ const badgeBorder = disabled ? CHIPFILTER.chipDisabledBorder : warning ? CHIPFILTER.chipWarningBorder : CHIPFILTER.chipAppliedBorder;
18509
+ const badgeBackground = disabled ? CHIPFILTER.chipDisabledBg : CHIPFILTER.chipAppliedBg;
18510
+ const badgeLabelColor = disabled ? CHIPFILTER.chipDisabledText : CHIPFILTER.chipAppliedLabel;
18511
+ const badgeValueColor = disabled ? CHIPFILTER.chipDisabledText : CHIPFILTER.chipAppliedValue;
18382
18512
  const badge = applied && /* @__PURE__ */ jsxs(
18383
18513
  "span",
18384
18514
  {
@@ -18386,17 +18516,17 @@ function InlineDatePresetFilter({
18386
18516
  style: {
18387
18517
  minHeight: CHIPFILTER.chipHeight,
18388
18518
  borderRadius: CHIPFILTER.chipRadius,
18389
- borderColor: warning ? CHIPFILTER.chipWarningBorder : CHIPFILTER.chipAppliedBorder,
18390
- background: CHIPFILTER.chipAppliedBg,
18391
- color: CHIPFILTER.chipAppliedLabel,
18392
- cursor: rangeValue ? "pointer" : void 0
18519
+ borderColor: badgeBorder,
18520
+ background: badgeBackground,
18521
+ color: badgeLabelColor,
18522
+ cursor: rangeValue && !disabled ? "pointer" : void 0
18393
18523
  },
18394
18524
  children: [
18395
18525
  /* @__PURE__ */ jsxs("span", { className: "font-bold", children: [
18396
18526
  field.label,
18397
18527
  ":"
18398
18528
  ] }),
18399
- /* @__PURE__ */ jsx("span", { className: "font-bold", style: { color: CHIPFILTER.chipAppliedValue }, children: displayValue(field, value) }),
18529
+ /* @__PURE__ */ jsx("span", { className: "font-bold", style: { color: badgeValueColor }, children: displayValue(field, value) }),
18400
18530
  !disabled && showClear && /* @__PURE__ */ jsx(
18401
18531
  "span",
18402
18532
  {