sellmate-design-system-react 9.0.0-beta.2 → 9.0.0-beta.4

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
@@ -5120,6 +5120,10 @@ var BODY_PADDING_CLASS = {
5120
5120
  wide: "p-(--sys-space-panel-padding-wide)",
5121
5121
  none: ""
5122
5122
  };
5123
+ var BODY_BACKGROUND_CLASS = {
5124
+ frame: "",
5125
+ neutral: "bg-(--sys-color-bg-neutralLight)"
5126
+ };
5123
5127
  var topBorderColorClass = {
5124
5128
  default: "bg-(--cmp-sectionHeaderCard-topBorder-color-default)",
5125
5129
  accent: "bg-(--cmp-sectionHeaderCard-topBorder-color-accent)"
@@ -5129,8 +5133,8 @@ var titleSizeClass = {
5129
5133
  sm: "typo-heading-sm"
5130
5134
  };
5131
5135
  var SSectionHeaderCardBodyImpl = /* @__PURE__ */ forwardRef(
5132
- function SSectionHeaderCardBody({ padding = "default", className, children, ...rest }, ref) {
5133
- return /* @__PURE__ */ jsx("div", { ref, className: cn(BODY_PADDING_CLASS[padding], className), ...rest, children });
5136
+ function SSectionHeaderCardBody({ padding = "default", background = "frame", children }, ref) {
5137
+ return /* @__PURE__ */ jsx("div", { ref, className: cn(BODY_PADDING_CLASS[padding], BODY_BACKGROUND_CLASS[background]), children });
5134
5138
  }
5135
5139
  );
5136
5140
  var SSectionHeaderCardHeaderImpl = /* @__PURE__ */ forwardRef(
@@ -5143,8 +5147,7 @@ var SSectionHeaderCardHeaderImpl = /* @__PURE__ */ forwardRef(
5143
5147
  subtitle,
5144
5148
  slot,
5145
5149
  thickness = false,
5146
- className,
5147
- ...rest
5150
+ className
5148
5151
  }, ref) {
5149
5152
  const hasHelp = helpText != null && helpText.length > 0;
5150
5153
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -5165,7 +5168,6 @@ var SSectionHeaderCardHeaderImpl = /* @__PURE__ */ forwardRef(
5165
5168
  "box-border flex min-h-(--cmp-sectionHeaderCard-header-minHeight) w-full shrink-0 items-center gap-(--cmp-sectionHeaderCard-header-gap) overflow-hidden border-b border-(--cmp-sectionHeaderCard-header-border) bg-(--cmp-sectionHeaderCard-header-bg) px-(--cmp-sectionHeaderCard-header-paddingX) py-(--cmp-sectionHeaderCard-header-paddingY)",
5166
5169
  className
5167
5170
  ),
5168
- ...rest,
5169
5171
  children: [
5170
5172
  marker && /* @__PURE__ */ jsx(SBadge, { color: "darkblue", className: "shrink-0" }),
5171
5173
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 shrink-0 items-center gap-[8px]", children: [
@@ -5209,8 +5211,24 @@ var SSectionHeaderCardHeaderImpl = /* @__PURE__ */ forwardRef(
5209
5211
  }
5210
5212
  );
5211
5213
  var SSectionHeaderCardRootImpl = /* @__PURE__ */ forwardRef(
5212
- function SSectionHeaderCard({ className, children, ...rest }, ref) {
5213
- return /* @__PURE__ */ jsx(
5214
+ function SSectionHeaderCard({
5215
+ title,
5216
+ titleSize,
5217
+ marker,
5218
+ required,
5219
+ helpText,
5220
+ subtitle,
5221
+ slot,
5222
+ thickness,
5223
+ headerClassName,
5224
+ padding,
5225
+ background,
5226
+ className,
5227
+ children,
5228
+ ...rest
5229
+ }, ref) {
5230
+ const hasHeader = title != null;
5231
+ return /* @__PURE__ */ jsxs(
5214
5232
  "div",
5215
5233
  {
5216
5234
  ref,
@@ -5219,24 +5237,28 @@ var SSectionHeaderCardRootImpl = /* @__PURE__ */ forwardRef(
5219
5237
  className
5220
5238
  ),
5221
5239
  ...rest,
5222
- children
5240
+ children: [
5241
+ hasHeader && /* @__PURE__ */ jsx(
5242
+ SSectionHeaderCardHeaderImpl,
5243
+ {
5244
+ title,
5245
+ titleSize,
5246
+ marker,
5247
+ required,
5248
+ helpText,
5249
+ subtitle,
5250
+ slot,
5251
+ thickness,
5252
+ className: headerClassName
5253
+ }
5254
+ ),
5255
+ /* @__PURE__ */ jsx(SSectionHeaderCardBodyImpl, { padding, background, children })
5256
+ ]
5223
5257
  }
5224
5258
  );
5225
5259
  }
5226
5260
  );
5227
- var SSectionHeaderCardRoot = /* @__PURE__ */ withDisplayName(SSectionHeaderCardRootImpl, "SSectionHeaderCard");
5228
- var SSectionHeaderCardHeader2 = /* @__PURE__ */ withDisplayName(
5229
- SSectionHeaderCardHeaderImpl,
5230
- "SSectionHeaderCard.Header"
5231
- );
5232
- var SSectionHeaderCardBody2 = /* @__PURE__ */ withDisplayName(
5233
- SSectionHeaderCardBodyImpl,
5234
- "SSectionHeaderCard.Body"
5235
- );
5236
- var SSectionHeaderCard2 = /* @__PURE__ */ Object.assign(SSectionHeaderCardRoot, {
5237
- Header: SSectionHeaderCardHeader2,
5238
- Body: SSectionHeaderCardBody2
5239
- });
5261
+ var SSectionHeaderCard2 = /* @__PURE__ */ withDisplayName(SSectionHeaderCardRootImpl, "SSectionHeaderCard");
5240
5262
  var SDivider = /* @__PURE__ */ forwardRef(function SDivider2({ vertical = false, className, style, ...rest }, ref) {
5241
5263
  return /* @__PURE__ */ jsx(
5242
5264
  "hr",
@@ -6867,7 +6889,7 @@ function SModalContainer({
6867
6889
  "div",
6868
6890
  {
6869
6891
  className: cn(
6870
- "relative flex max-h-[calc(100dvh-48px)] max-w-[calc(100dvw-48px)] flex-col overflow-hidden rounded-[var(--cmp-modal-radius)] bg-[var(--cmp-modal-bg)] shadow-normal-md",
6892
+ "relative flex max-h-[85dvh] max-w-[calc(100dvw-48px)] flex-col overflow-hidden rounded-[var(--cmp-modal-radius)] bg-[var(--cmp-modal-bg)] shadow-normal-md",
6871
6893
  shaking && "animate-modal-shake",
6872
6894
  className
6873
6895
  ),
@@ -7124,7 +7146,7 @@ function SActionModal({
7124
7146
  showClose: true,
7125
7147
  width,
7126
7148
  height,
7127
- className: "w-fit min-w-[min(480px,calc(100dvw-48px))] min-h-[min(320px,calc(100dvh-48px))]",
7149
+ className: "w-fit min-w-[min(480px,calc(100dvw-48px))] min-h-[min(320px,85dvh)]",
7128
7150
  children: /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-auto flex-col", children: [
7129
7151
  /* @__PURE__ */ jsx(SOverlayHeader, { title: modalTitle, description, reserveCloseSpace: true }),
7130
7152
  /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-auto px-[var(--cmp-modal-action-body-paddingX)] pb-[var(--cmp-modal-action-body-paddingBottom)]", children }),
@@ -8846,6 +8868,7 @@ var BACKGROUND_CLASS = {
8846
8868
  var SPage = /* @__PURE__ */ forwardRef(function SPage2({
8847
8869
  background = "frame",
8848
8870
  scrollEndSpacing = true,
8871
+ contentHeight = "auto",
8849
8872
  header,
8850
8873
  className,
8851
8874
  style,
@@ -8860,13 +8883,14 @@ var SPage = /* @__PURE__ */ forwardRef(function SPage2({
8860
8883
  onScroll?.(e);
8861
8884
  if (header != null && !header.fix) setScrolled(e.currentTarget.scrollTop > 0);
8862
8885
  };
8886
+ const fill = contentHeight === "fill";
8863
8887
  const main = /* @__PURE__ */ jsx(
8864
8888
  "main",
8865
8889
  {
8866
8890
  ref,
8867
8891
  className: cn(
8868
8892
  "box-border min-h-0 min-w-0 flex-1 overflow-auto p-[var(--cmp-pageBody-padding-default)]",
8869
- scrollEndSpacing && "pb-[var(--cmp-pageBody-padding-scrollEnd)]",
8893
+ !fill && scrollEndSpacing && "pb-[var(--cmp-pageBody-padding-scrollEnd)]",
8870
8894
  SCROLLBAR_CLASS,
8871
8895
  BACKGROUND_CLASS[background],
8872
8896
  className
@@ -8874,7 +8898,7 @@ var SPage = /* @__PURE__ */ forwardRef(function SPage2({
8874
8898
  style: header == null ? { ...gridStyle, ...style } : style,
8875
8899
  onScroll: handleScroll,
8876
8900
  ...rest,
8877
- children: /* @__PURE__ */ jsx("div", { style: { minWidth: MIN_CONTENT_WIDTH }, children })
8901
+ children: /* @__PURE__ */ jsx("div", { className: cn(fill && "h-full"), style: { minWidth: MIN_CONTENT_WIDTH }, children })
8878
8902
  }
8879
8903
  );
8880
8904
  if (header == null) return main;
@@ -8898,6 +8922,7 @@ var SPage = /* @__PURE__ */ forwardRef(function SPage2({
8898
8922
 
8899
8923
  // src/components/SPage/page.config.ts
8900
8924
  var PAGE_BACKGROUNDS = ["frame", "neutral", "screen"];
8925
+ var PAGE_CONTENT_HEIGHTS = ["auto", "fill"];
8901
8926
 
8902
8927
  // src/components/STabs/tabs.config.ts
8903
8928
  var TAB_MAIN_SIZE = {
@@ -9715,6 +9740,7 @@ var SField = /* @__PURE__ */ forwardRef(function SField2({
9715
9740
  readOnly = false,
9716
9741
  width,
9717
9742
  minWidth,
9743
+ maxWidth,
9718
9744
  multiline = false,
9719
9745
  borderless = false,
9720
9746
  children,
@@ -9750,7 +9776,10 @@ var SField = /* @__PURE__ */ forwardRef(function SField2({
9750
9776
  const resolvedSuccess = status === "pass";
9751
9777
  const showError = resolvedError && errorMessage !== "";
9752
9778
  const interactive = !disabled && !readOnly;
9753
- const hug = cssLen(width) !== void 0;
9779
+ const isPercentWidth = typeof width === "string" && width.trim().endsWith("%");
9780
+ const hug = cssLen(width) !== void 0 && !isPercentWidth;
9781
+ const addonWidth = effectiveAddonLabel !== "" ? cssLen(labelWidth) : void 0;
9782
+ const resolvedMaxWidth = cssLen(maxWidth) === void 0 || effectiveAddonLabel === "" ? cssLen(maxWidth) : addonWidth ? `calc(${cssLen(maxWidth)} + ${addonWidth})` : void 0;
9754
9783
  const borderColor = borderless ? "transparent" : disabled ? "var(--color-grey-45)" : interactive && focused ? "var(--sys-color-field-border-focus)" : resolvedSuccess ? "var(--sys-color-field-border-success)" : resolvedError ? "var(--sys-color-field-border-danger)" : "var(--sys-color-field-border-default)";
9755
9784
  const bgColor = borderless ? "transparent" : disabled ? "var(--color-grey-30)" : readOnly ? "var(--sys-color-field-bg-readOnly)" : "var(--sys-color-field-bg-default)";
9756
9785
  const textColor = disabled ? "var(--color-grey-65)" : readOnly ? "var(--sys-color-field-text-readOnly)" : "var(--sys-color-field-text-default)";
@@ -9806,8 +9835,12 @@ var SField = /* @__PURE__ */ forwardRef(function SField2({
9806
9835
  /* @__PURE__ */ jsxs(
9807
9836
  "div",
9808
9837
  {
9809
- className: cn("flex min-w-0 flex-col", !hug && "flex-1"),
9810
- style: { width: cssLen(width), minWidth: cssLen(minWidth) },
9838
+ className: cn("flex min-w-0 flex-col", !hug && !isPercentWidth && "flex-1"),
9839
+ style: {
9840
+ width: cssLen(width),
9841
+ minWidth: cssLen(minWidth),
9842
+ maxWidth: resolvedMaxWidth
9843
+ },
9811
9844
  children: [
9812
9845
  /* @__PURE__ */ jsxs(
9813
9846
  "div",
@@ -10562,6 +10595,7 @@ var STable = /* @__PURE__ */ forwardRef(function STable2({
10562
10595
  height,
10563
10596
  stickyColumn,
10564
10597
  radius = "default",
10598
+ bordered = true,
10565
10599
  noDataLabel = "\uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
10566
10600
  noDataSlot,
10567
10601
  isLoading = false,
@@ -10820,7 +10854,7 @@ var STable = /* @__PURE__ */ forwardRef(function STable2({
10820
10854
  };
10821
10855
  const radiusClass = radius === "full" ? "rounded-none" : radius === "useTop" ? "rounded-b-[var(--cmp-table-radius)] rounded-t-none" : "rounded-[var(--cmp-table-radius)]";
10822
10856
  const cellPadY = dense ? "var(--cmp-table-body-paddingY-dense)" : "var(--cmp-table-body-paddingY-default)";
10823
- const cellPadX = "var(--cmp-table-body-paddingX-wide)";
10857
+ const cellPadXOf = (col) => col.contentType === "control" ? "var(--cmp-table-body-paddingX-default)" : "var(--cmp-table-body-paddingX-wide)";
10824
10858
  const rowH = dense ? "var(--cmp-table-body-dense-height)" : "var(--cmp-table-body-default-height)";
10825
10859
  const totalColSpan = visibleCols.length + (selectable ? 1 : 0);
10826
10860
  const stickyInfo = (i) => {
@@ -10874,7 +10908,8 @@ var STable = /* @__PURE__ */ forwardRef(function STable2({
10874
10908
  // 만으로는 스태킹 컨텍스트가 생기지 않아, 모달 안에 들어가면 30·31이 모달 Content
10875
10909
  // 레벨까지 올라가 같은 컨텍스트의 floating portal 밴드(z-2)를 덮는다.
10876
10910
  // 자손은 이미 overflow-hidden으로 클리핑되므로 테이블 내부 순서는 달라지지 않는다.
10877
- "bg-white relative isolate flex flex-col overflow-hidden border border-solid border-[color:var(--cmp-table-border-color)]",
10911
+ "bg-white relative isolate flex flex-col overflow-hidden",
10912
+ bordered && "border border-solid border-[color:var(--cmp-table-border-color)]",
10878
10913
  // height가 없으면 루트가 받은 높이를 끝까지 채운다. 호스트가 className으로
10879
10914
  // `flex-1 min-h-0` 등을 줘 루트 높이가 확정된 경우, 행이 적어도 테두리 박스가
10880
10915
  // 남은 영역을 그대로 차지한다. basis는 auto로 둬 높이가 확정되지 않은
@@ -11044,9 +11079,10 @@ var STable = /* @__PURE__ */ forwardRef(function STable2({
11044
11079
  stickyShadowClass(i),
11045
11080
  typeof col.tdClass === "function" ? col.tdClass(row) : col.tdClass
11046
11081
  );
11082
+ const tdPadX = cellPadXOf(col);
11047
11083
  const tdStyle = {
11048
- paddingLeft: cellPadX,
11049
- paddingRight: cellPadX,
11084
+ paddingLeft: tdPadX,
11085
+ paddingRight: tdPadX,
11050
11086
  paddingTop: cellPadY,
11051
11087
  paddingBottom: cellPadY,
11052
11088
  textAlign: col.align ?? "left",
@@ -11099,19 +11135,29 @@ var STable = /* @__PURE__ */ forwardRef(function STable2({
11099
11135
  // SPagination은 전체 폭 기준 중앙, rows-per-page SSelect는 우측에 absolute 배치해 중앙 정렬을 유지한다.
11100
11136
  // 루트 높이가 확정된 경우(테이블이 남은 영역을 채우는 배치) 페이지네이션 바가
11101
11137
  // 눌리지 않도록 shrink-0으로 48px를 보장한다.
11102
- /* @__PURE__ */ jsxs("div", { className: "relative -mt-px flex h-[48px] shrink-0 items-center justify-center rounded-[var(--cmp-table-radius)] border border-solid border-[color:var(--cmp-table-border-color)] bg-[var(--color-grey-05)]", children: [
11103
- /* @__PURE__ */ jsx(SPagination, { currentPage: displayPage, lastPage, onPageChange: changePage }),
11104
- useRowsPerPageSelect && /* @__PURE__ */ jsx("div", { className: "absolute right-[10px] top-1/2 -translate-y-1/2", children: /* @__PURE__ */ jsx(
11105
- SSelect2,
11106
- {
11107
- options: rowsPerPageOption,
11108
- value: useInternalPagination ? innerRowsPerPage : pagination?.rowsPerPage,
11109
- emitValue: true,
11110
- onValueChange: (v) => changeRowsPerPage(Number(v)),
11111
- width: 128
11112
- }
11113
- ) })
11114
- ] })
11138
+ /* @__PURE__ */ jsxs(
11139
+ "div",
11140
+ {
11141
+ className: cn(
11142
+ "relative flex h-[48px] shrink-0 items-center justify-center rounded-[var(--cmp-table-radius)] bg-[var(--color-grey-05)]",
11143
+ // 테두리를 끄면 겹칠 대상이 없어진다 — -mt-px 를 남기면 1px 어긋남만 생긴다.
11144
+ bordered && "-mt-px border border-solid border-[color:var(--cmp-table-border-color)]"
11145
+ ),
11146
+ children: [
11147
+ /* @__PURE__ */ jsx(SPagination, { currentPage: displayPage, lastPage, onPageChange: changePage }),
11148
+ useRowsPerPageSelect && /* @__PURE__ */ jsx("div", { className: "absolute right-[10px] top-1/2 -translate-y-1/2", children: /* @__PURE__ */ jsx(
11149
+ SSelect2,
11150
+ {
11151
+ options: rowsPerPageOption,
11152
+ value: useInternalPagination ? innerRowsPerPage : pagination?.rowsPerPage,
11153
+ emitValue: true,
11154
+ onValueChange: (v) => changeRowsPerPage(Number(v)),
11155
+ width: 128
11156
+ }
11157
+ ) })
11158
+ ]
11159
+ }
11160
+ )
11115
11161
  ]
11116
11162
  }
11117
11163
  );
@@ -12128,12 +12174,275 @@ function SCalendar({
12128
12174
  }
12129
12175
  );
12130
12176
  }
12177
+ var DATEPICKER_MODE_LIST_HEIGHT = 240;
12178
+ var DATEPICKER_MODE_ITEM_HEIGHT = 28;
12179
+ var YEAR_LIST_EDGE_BUFFER = 24;
12180
+ var YEAR_LIST_EXPAND_SIZE = 80;
12181
+ var MONTHS = Array.from({ length: 12 }, (_, i) => i + 1);
12182
+ var getTodayParts = () => {
12183
+ const today = /* @__PURE__ */ new Date();
12184
+ return {
12185
+ year: today.getFullYear(),
12186
+ month: today.getMonth() + 1
12187
+ };
12188
+ };
12189
+ var parseYear = (value) => {
12190
+ const year = Number(value?.slice(0, 4));
12191
+ return Number.isFinite(year) && year > 0 ? year : void 0;
12192
+ };
12193
+ var parseMonth = (value) => {
12194
+ const month = Number(value?.slice(5, 7));
12195
+ return Number.isFinite(month) && month >= 1 && month <= 12 ? month : void 0;
12196
+ };
12197
+ var formatMonthValue = (year, month) => `${year}-${String(month).padStart(2, "0")}`;
12198
+ var isYearDisabled = (year, selectable) => {
12199
+ if (!selectable) return false;
12200
+ const [start, end] = selectable;
12201
+ if (start !== "" && year < Number(start.slice(0, 4))) return true;
12202
+ if (end !== "" && year > Number(end.slice(0, 4))) return true;
12203
+ return false;
12204
+ };
12205
+ var isMonthDisabled = (year, month, selectable) => {
12206
+ if (!selectable) return false;
12207
+ const value = formatMonthValue(year, month);
12208
+ const [start, end] = selectable;
12209
+ if (start !== "" && value < start.slice(0, 7)) return true;
12210
+ if (end !== "" && value > end.slice(0, 7)) return true;
12211
+ return false;
12212
+ };
12213
+ function YearWheel({ value, anchorYear, selectable, onCommit }) {
12214
+ const listRef = useRef(null);
12215
+ const syncingScrollRef = useRef(false);
12216
+ const anchorYearRef = useRef(anchorYear);
12217
+ const [range, setRange] = useState(() => ({
12218
+ start: anchorYear - YEAR_LIST_EXPAND_SIZE,
12219
+ end: anchorYear + YEAR_LIST_EXPAND_SIZE
12220
+ }));
12221
+ const rangeStartRef = useRef(range.start);
12222
+ rangeStartRef.current = range.start;
12223
+ const years = Array.from({ length: range.end - range.start + 1 }, (_, i) => range.start + i);
12224
+ useLayoutEffect(() => {
12225
+ const list = listRef.current;
12226
+ if (!list) return;
12227
+ const index = anchorYearRef.current - rangeStartRef.current;
12228
+ syncingScrollRef.current = true;
12229
+ list.scrollTop = index * DATEPICKER_MODE_ITEM_HEIGHT;
12230
+ requestAnimationFrame(() => {
12231
+ syncingScrollRef.current = false;
12232
+ });
12233
+ }, []);
12234
+ const handleScroll = () => {
12235
+ const list = listRef.current;
12236
+ if (!list) return;
12237
+ if (syncingScrollRef.current) return;
12238
+ if (list.scrollTop < DATEPICKER_MODE_ITEM_HEIGHT * YEAR_LIST_EDGE_BUFFER) {
12239
+ const previousHeight = list.scrollHeight;
12240
+ setRange((current) => ({
12241
+ start: current.start - YEAR_LIST_EXPAND_SIZE,
12242
+ end: current.end
12243
+ }));
12244
+ requestAnimationFrame(() => {
12245
+ if (!listRef.current) return;
12246
+ syncingScrollRef.current = true;
12247
+ listRef.current.scrollTop += listRef.current.scrollHeight - previousHeight;
12248
+ requestAnimationFrame(() => {
12249
+ syncingScrollRef.current = false;
12250
+ });
12251
+ });
12252
+ return;
12253
+ }
12254
+ if (list.scrollHeight - list.scrollTop - list.clientHeight < DATEPICKER_MODE_ITEM_HEIGHT * YEAR_LIST_EDGE_BUFFER) {
12255
+ setRange((current) => ({
12256
+ start: current.start,
12257
+ end: current.end + YEAR_LIST_EXPAND_SIZE
12258
+ }));
12259
+ }
12260
+ };
12261
+ return /* @__PURE__ */ jsx("div", { className: "h-full w-full overflow-hidden bg-[var(--cmp-datepicker-mode-bg)]", children: /* @__PURE__ */ jsx(
12262
+ "div",
12263
+ {
12264
+ ref: listRef,
12265
+ role: "listbox",
12266
+ "aria-label": "year",
12267
+ className: "h-full overflow-y-auto overscroll-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
12268
+ style: {
12269
+ paddingTop: `calc((${DATEPICKER_MODE_LIST_HEIGHT}px - ${DATEPICKER_MODE_ITEM_HEIGHT}px) / 2)`,
12270
+ paddingBottom: `calc((${DATEPICKER_MODE_LIST_HEIGHT}px - ${DATEPICKER_MODE_ITEM_HEIGHT}px) / 2)`
12271
+ },
12272
+ onScroll: handleScroll,
12273
+ children: years.map((year) => {
12274
+ const selected = year === value;
12275
+ const disabled = isYearDisabled(year, selectable);
12276
+ return /* @__PURE__ */ jsx(
12277
+ "button",
12278
+ {
12279
+ type: "button",
12280
+ role: "option",
12281
+ "aria-selected": selected,
12282
+ disabled,
12283
+ className: cn(
12284
+ "flex w-full items-center justify-center gap-[var(--cmp-datepicker-mode-item-gap)] border-0 px-[var(--cmp-datepicker-mode-item-paddingX)] py-[var(--cmp-datepicker-mode-item-paddingY)] text-center font-base text-12 leading-[20px]",
12285
+ disabled && "cursor-not-allowed bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-disabled)]",
12286
+ !disabled && !selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-default)] hover:bg-[var(--cmp-datepicker-mode-item-bg-hover)] hover:text-[color:var(--cmp-datepicker-mode-item-content-hover)]",
12287
+ !disabled && selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-bold text-[color:var(--cmp-datepicker-mode-item-content-selected)]"
12288
+ ),
12289
+ onClick: () => !disabled && onCommit(year),
12290
+ children: year
12291
+ },
12292
+ year
12293
+ );
12294
+ })
12295
+ }
12296
+ ) });
12297
+ }
12298
+ function SDatePickerYearListbox({
12299
+ value,
12300
+ anchorYear,
12301
+ selectable,
12302
+ onValueChange,
12303
+ className,
12304
+ style
12305
+ }) {
12306
+ const today = getTodayParts();
12307
+ const resolvedAnchorYear = anchorYear ?? today.year;
12308
+ const [year, setYear] = useState(() => parseYear(value) ?? resolvedAnchorYear);
12309
+ useEffect(() => {
12310
+ setYear(parseYear(value) ?? resolvedAnchorYear);
12311
+ }, [value, resolvedAnchorYear]);
12312
+ const commitYear = (nextYear) => {
12313
+ setYear(nextYear);
12314
+ onValueChange?.(String(nextYear));
12315
+ };
12316
+ return /* @__PURE__ */ jsx(
12317
+ "div",
12318
+ {
12319
+ className: cn(
12320
+ "inline-flex h-[240px] w-[var(--cmp-datepicker-mode-year-width)] overflow-hidden rounded-[var(--cmp-datepicker-mode-radius)] bg-[var(--cmp-datepicker-mode-bg)] shadow-normal-sm",
12321
+ className
12322
+ ),
12323
+ style,
12324
+ children: /* @__PURE__ */ jsx(
12325
+ YearWheel,
12326
+ {
12327
+ value: year,
12328
+ anchorYear: resolvedAnchorYear,
12329
+ selectable,
12330
+ onCommit: commitYear
12331
+ }
12332
+ )
12333
+ }
12334
+ );
12335
+ }
12336
+ function MonthWheel({ year, value, selectable, onCommit }) {
12337
+ const listRef = useRef(null);
12338
+ const initialValueRef = useRef(value);
12339
+ useLayoutEffect(() => {
12340
+ const list = listRef.current;
12341
+ if (!list) return;
12342
+ list.scrollTop = Math.max(
12343
+ 0,
12344
+ (initialValueRef.current - 1) * DATEPICKER_MODE_ITEM_HEIGHT - (list.clientHeight - DATEPICKER_MODE_ITEM_HEIGHT) / 2
12345
+ );
12346
+ }, []);
12347
+ return /* @__PURE__ */ jsx("div", { className: "h-full w-full overflow-hidden bg-[var(--cmp-datepicker-mode-bg)]", children: /* @__PURE__ */ jsx(
12348
+ "div",
12349
+ {
12350
+ ref: listRef,
12351
+ role: "listbox",
12352
+ "aria-label": "month",
12353
+ className: "h-full overflow-y-auto overscroll-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
12354
+ children: MONTHS.map((month) => {
12355
+ const selected = month === value;
12356
+ const disabled = isMonthDisabled(year, month, selectable);
12357
+ return /* @__PURE__ */ jsxs(
12358
+ "button",
12359
+ {
12360
+ type: "button",
12361
+ role: "option",
12362
+ "aria-selected": selected,
12363
+ disabled,
12364
+ className: cn(
12365
+ "flex w-full items-center justify-center gap-[var(--cmp-datepicker-mode-item-gap)] border-0 px-[var(--cmp-datepicker-mode-item-paddingX)] py-[var(--cmp-datepicker-mode-item-paddingY)] text-center font-base text-12 leading-[20px]",
12366
+ disabled && "cursor-not-allowed bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-disabled)]",
12367
+ !disabled && !selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-default)] hover:bg-[var(--cmp-datepicker-mode-item-bg-hover)] hover:text-[color:var(--cmp-datepicker-mode-item-content-hover)]",
12368
+ !disabled && selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-bold text-[color:var(--cmp-datepicker-mode-item-content-selected)]"
12369
+ ),
12370
+ onClick: () => !disabled && onCommit(month),
12371
+ children: [
12372
+ month,
12373
+ "\uC6D4"
12374
+ ]
12375
+ },
12376
+ month
12377
+ );
12378
+ })
12379
+ }
12380
+ ) });
12381
+ }
12382
+ function SDatePickerMonthListbox({
12383
+ value,
12384
+ anchorYear,
12385
+ selectable,
12386
+ onValueChange,
12387
+ onMonthSelect,
12388
+ className,
12389
+ style
12390
+ }) {
12391
+ const today = getTodayParts();
12392
+ const resolvedAnchorYear = anchorYear ?? today.year;
12393
+ const [year, setYear] = useState(() => parseYear(value) ?? resolvedAnchorYear);
12394
+ const [month, setMonth] = useState(() => parseMonth(value) ?? today.month);
12395
+ useEffect(() => {
12396
+ setYear(parseYear(value) ?? resolvedAnchorYear);
12397
+ setMonth(parseMonth(value) ?? today.month);
12398
+ }, [value, resolvedAnchorYear, today.month]);
12399
+ const commitYear = (nextYear) => {
12400
+ setYear(nextYear);
12401
+ onValueChange?.(formatMonthValue(nextYear, month));
12402
+ };
12403
+ const commitMonth = (nextMonth) => {
12404
+ setMonth(nextMonth);
12405
+ const nextValue = formatMonthValue(year, nextMonth);
12406
+ onValueChange?.(nextValue);
12407
+ onMonthSelect?.(nextValue);
12408
+ };
12409
+ return /* @__PURE__ */ jsxs(
12410
+ "div",
12411
+ {
12412
+ className: cn(
12413
+ "inline-flex h-[240px] gap-[var(--cmp-datepicker-mode-yearMonth-gap)] overflow-hidden rounded-[var(--cmp-datepicker-mode-radius)] bg-[var(--cmp-datepicker-mode-bg)] shadow-normal-sm",
12414
+ className
12415
+ ),
12416
+ style,
12417
+ children: [
12418
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 w-[var(--cmp-datepicker-mode-yearMonth-column-width)]", children: /* @__PURE__ */ jsx(
12419
+ YearWheel,
12420
+ {
12421
+ value: year,
12422
+ anchorYear: resolvedAnchorYear,
12423
+ selectable,
12424
+ onCommit: commitYear
12425
+ }
12426
+ ) }),
12427
+ /* @__PURE__ */ jsx(
12428
+ "div",
12429
+ {
12430
+ "aria-hidden": true,
12431
+ className: "w-[var(--border-width-1)] self-stretch bg-[var(--sys-color-divider-default)]"
12432
+ }
12433
+ ),
12434
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 w-[var(--cmp-datepicker-mode-yearMonth-column-width)]", children: /* @__PURE__ */ jsx(MonthWheel, { year, value: month, selectable, onCommit: commitMonth }) })
12435
+ ]
12436
+ }
12437
+ );
12438
+ }
12131
12439
  function SDatePicker({
12132
12440
  value,
12133
12441
  onValueChange,
12134
12442
  onViewChange,
12443
+ mode = "date",
12135
12444
  size = "sm",
12136
- placeholder = "YYYY-MM-DD",
12445
+ placeholder,
12137
12446
  selectable,
12138
12447
  disabled = false,
12139
12448
  width,
@@ -12159,6 +12468,7 @@ function SDatePicker({
12159
12468
  const [focused, setFocused] = useState(false);
12160
12469
  const [ruleError, setRuleError] = useState("");
12161
12470
  const hasValue = value != null && value !== "";
12471
+ const resolvedPlaceholder = placeholder ?? (mode === "year" ? "YYYY" : mode === "month" ? "YYYY-MM" : "YYYY-MM-DD");
12162
12472
  useFormField({
12163
12473
  name,
12164
12474
  validate: () => {
@@ -12170,12 +12480,39 @@ function SDatePicker({
12170
12480
  });
12171
12481
  const resolvedError = error || ruleError !== "";
12172
12482
  const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
12173
- const dims = size === "md" ? { padX: 16, gap: 12, icon: 20, minW: 104, fontSize: 14, lineHeight: 24, radius: 6 } : { padX: 12, gap: 8, icon: 16, minW: 80, fontSize: 12, lineHeight: 20, radius: 4 };
12483
+ const dims = size === "md" ? {
12484
+ padX: 16,
12485
+ gap: 12,
12486
+ icon: 20,
12487
+ minW: 104,
12488
+ fieldMinW: 168,
12489
+ // 상한 등급 lg — 값 길이가 YYYY-MM-DD 로 정해져 있어, width="100%" 를 받아도
12490
+ // 행 전체로 늘어나지 않게 막는다 (SKeyValueTable 이 모든 컨트롤에 100% 를 넘긴다)
12491
+ fieldMaxW: 240,
12492
+ fontSize: 14,
12493
+ lineHeight: 24,
12494
+ radius: 6
12495
+ } : {
12496
+ padX: 12,
12497
+ gap: 8,
12498
+ icon: 16,
12499
+ minW: 80,
12500
+ fieldMinW: 128,
12501
+ // 상한 등급 md
12502
+ fieldMaxW: 160,
12503
+ fontSize: 12,
12504
+ lineHeight: 20,
12505
+ radius: 4
12506
+ };
12174
12507
  const select = (date) => {
12175
12508
  onValueChange?.(date);
12176
12509
  if (rules && rules.length > 0) setRuleError(runRules(date, rules));
12177
12510
  setOpen(false);
12178
12511
  };
12512
+ const selectListboxValue = (nextValue) => {
12513
+ onValueChange?.(nextValue);
12514
+ if (rules && rules.length > 0) setRuleError(runRules(nextValue, rules));
12515
+ };
12179
12516
  return /* @__PURE__ */ jsx(
12180
12517
  SField,
12181
12518
  {
@@ -12194,6 +12531,8 @@ function SDatePicker({
12194
12531
  focused: focused || open,
12195
12532
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
12196
12533
  width,
12534
+ minWidth: dims.fieldMinW,
12535
+ maxWidth: dims.fieldMaxW,
12197
12536
  disabled,
12198
12537
  className,
12199
12538
  style,
@@ -12228,7 +12567,7 @@ function SDatePicker({
12228
12567
  lineHeight: `${dims.lineHeight}px`,
12229
12568
  color: disabled ? "var(--cmp-datepicker-text-disabled)" : hasValue ? "var(--cmp-datepicker-text-default)" : "var(--cmp-field-hint-color)"
12230
12569
  },
12231
- children: hasValue ? value : placeholder
12570
+ children: hasValue ? value : resolvedPlaceholder
12232
12571
  }
12233
12572
  )
12234
12573
  ]
@@ -12238,13 +12577,13 @@ function SDatePicker({
12238
12577
  RPopover9.Content,
12239
12578
  {
12240
12579
  side: "bottom",
12241
- align: "start",
12580
+ align: mode === "date" ? "start" : "center",
12242
12581
  sideOffset: 4,
12243
12582
  className: cn(
12244
12583
  "z-50 max-w-[var(--radix-popover-content-available-width)]",
12245
12584
  FLOATING_SLIDE_ANIM
12246
12585
  ),
12247
- children: /* @__PURE__ */ jsx(
12586
+ children: mode === "date" ? /* @__PURE__ */ jsx(
12248
12587
  SCalendar,
12249
12588
  {
12250
12589
  value: value ?? void 0,
@@ -12253,6 +12592,24 @@ function SDatePicker({
12253
12592
  onValueChange: select,
12254
12593
  onViewChange
12255
12594
  }
12595
+ ) : mode === "year" ? /* @__PURE__ */ jsx(
12596
+ SDatePickerYearListbox,
12597
+ {
12598
+ value,
12599
+ selectable,
12600
+ onValueChange: (nextValue) => {
12601
+ selectListboxValue(nextValue);
12602
+ setOpen(false);
12603
+ }
12604
+ }
12605
+ ) : /* @__PURE__ */ jsx(
12606
+ SDatePickerMonthListbox,
12607
+ {
12608
+ value,
12609
+ selectable,
12610
+ onValueChange: selectListboxValue,
12611
+ onMonthSelect: () => setOpen(false)
12612
+ }
12256
12613
  )
12257
12614
  }
12258
12615
  ) })
@@ -12634,6 +12991,9 @@ function SDateRangePicker({
12634
12991
  icon: 20,
12635
12992
  minW: 104,
12636
12993
  fieldMinW: 260,
12994
+ // 상한 등급 xl — 값이 `YYYY-MM-DD ~ YYYY-MM-DD` 라 단일 날짜의 두 배가 넘는다.
12995
+ // 최소너비(260)가 이미 lg(240)를 넘으므로 상한도 한 등급 위여야 한다
12996
+ fieldMaxW: 480,
12637
12997
  fontSize: 14,
12638
12998
  lineHeight: 24,
12639
12999
  radius: 6
@@ -12643,6 +13003,8 @@ function SDateRangePicker({
12643
13003
  icon: 16,
12644
13004
  minW: 80,
12645
13005
  fieldMinW: 216,
13006
+ // 상한 등급 lg — 최소너비 216 이 md(160)를 넘어 md 로는 담기지 않는다
13007
+ fieldMaxW: 240,
12646
13008
  fontSize: 12,
12647
13009
  lineHeight: 20,
12648
13010
  radius: 4
@@ -12673,6 +13035,7 @@ function SDateRangePicker({
12673
13035
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
12674
13036
  width,
12675
13037
  minWidth: dims.fieldMinW,
13038
+ maxWidth: dims.fieldMaxW,
12676
13039
  disabled,
12677
13040
  className,
12678
13041
  style,
@@ -13137,6 +13500,7 @@ function SKeyValueTable({
13137
13500
  values = {},
13138
13501
  search = false,
13139
13502
  radius = "default",
13503
+ bordered = true,
13140
13504
  onChange,
13141
13505
  onSearch,
13142
13506
  className,
@@ -13201,7 +13565,8 @@ function SKeyValueTable({
13201
13565
  });
13202
13566
  const radiusClass = radius === "full" ? "rounded-none" : radius === "useTop" ? "rounded-b-[8px] rounded-t-none" : "rounded-[8px]";
13203
13567
  const frameClass = cn(
13204
- "overflow-hidden border border-solid border-[color:var(--cmp-table-border-color)]",
13568
+ "overflow-hidden",
13569
+ bordered && "border border-solid border-[color:var(--cmp-table-border-color)]",
13205
13570
  radiusClass
13206
13571
  );
13207
13572
  const table = /* @__PURE__ */ jsxs(
@@ -13786,7 +14151,16 @@ var CHIPFILTER = {
13786
14151
  itemTextDefault: "var(--cmp-chipFilter-item-text-default)",
13787
14152
  itemTextSelected: "var(--cmp-chipFilter-item-text-selected)"
13788
14153
  };
14154
+ var DEFAULT_PERIOD_PRESETS = [
14155
+ { value: "day", label: "\uC77C\uBCC4" },
14156
+ { value: "month", label: "\uC6D4\uBCC4" },
14157
+ { value: "quarter", label: "\uBD84\uAE30\uBCC4" },
14158
+ { value: "half", label: "\uBC18\uAE30\uBCC4" },
14159
+ { value: "year", label: "\uC5F0\uB3C4\uBCC4" },
14160
+ { value: "custom", label: "\uC0AC\uC6A9\uC790 \uC9C0\uC815", custom: true }
14161
+ ];
13789
14162
  var isKeywordValue = (value) => typeof value === "object" && value != null && !Array.isArray(value) && "keywords" in value;
14163
+ var isPeriodValue = (value) => typeof value === "object" && value != null && !Array.isArray(value) && "unit" in value;
13790
14164
  var keywordEntries = (value) => {
13791
14165
  if (isKeywordValue(value)) return value.keywords;
13792
14166
  if (Array.isArray(value)) {
@@ -13810,12 +14184,33 @@ var todayIso = () => {
13810
14184
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
13811
14185
  };
13812
14186
  var isInlineDateField = (field) => field.type === "date" && field.radioButton === true && (field.presets?.length ?? 0) > 0;
14187
+ var getFieldPresets = (field) => {
14188
+ if (field.type === "period") return field.presets ?? DEFAULT_PERIOD_PRESETS;
14189
+ return field.presets ?? [];
14190
+ };
14191
+ var getDefaultPeriodInputValue = (unit) => {
14192
+ const today = todayIso();
14193
+ if (unit === "day") return today;
14194
+ if (unit === "month") return today.slice(0, 7);
14195
+ if (unit === "quarter") return 1;
14196
+ if (unit === "half") return "first";
14197
+ if (unit === "year") return today.slice(0, 4);
14198
+ return [today, today];
14199
+ };
14200
+ var createPeriodValue = (unit) => ({
14201
+ unit,
14202
+ value: getDefaultPeriodInputValue(unit)
14203
+ });
13813
14204
  var resolveFieldDefault = (field) => {
13814
14205
  if (field.defaultValue !== void 0) return field.defaultValue;
13815
14206
  if (isInlineDateField(field)) return todayIso();
13816
14207
  if (!field.required) return void 0;
13817
14208
  if (field.type === "single") return field.options?.[0]?.value;
13818
14209
  if (field.type === "date") return todayIso();
14210
+ if (field.type === "period") {
14211
+ const unit = getFieldPresets(field).find((preset) => !preset.custom)?.value;
14212
+ return unit ? createPeriodValue(unit) : void 0;
14213
+ }
13819
14214
  return void 0;
13820
14215
  };
13821
14216
  var valuesEqual = (a, b) => {
@@ -13839,6 +14234,8 @@ var isAtDefaultValue = (field, value) => {
13839
14234
  return def !== void 0 && valuesEqual(value, def);
13840
14235
  };
13841
14236
  var isInlineField = (field) => isInlineDateField(field) || field.type === "custom";
14237
+ var isFixedField = (field) => field.fix === true || field.required === true;
14238
+ var isAddableField = (field) => !isFixedField(field);
13842
14239
  var isGroupedFields = (fields) => fields.length > 0 && "fields" in fields[0];
13843
14240
  var isGroupSatisfied = (group, values) => {
13844
14241
  if (!group.rule) return true;
@@ -13865,9 +14262,10 @@ var defaultGroupTooltipMessage = (rule, fields) => {
13865
14262
  };
13866
14263
  var matchPresetId = (presets, value) => {
13867
14264
  if (isEmptyValue(value)) return null;
14265
+ if (isPeriodValue(value)) return value.unit;
13868
14266
  for (const preset of presets) {
13869
- if (preset.custom || !preset.resolve) continue;
13870
- const resolved = preset.resolve();
14267
+ if (preset.custom) continue;
14268
+ const resolved = preset.resolve ? preset.resolve() : preset.value;
13871
14269
  if (Array.isArray(resolved) && Array.isArray(value)) {
13872
14270
  if (resolved[0] === value[0] && resolved[1] === value[1]) return preset.value;
13873
14271
  } else if (!Array.isArray(resolved) && !Array.isArray(value) && resolved === value) {
@@ -13883,18 +14281,36 @@ var formatValueList = (labels) => {
13883
14281
  };
13884
14282
  var displayValue = (field, value) => {
13885
14283
  if (isEmptyValue(value)) return "";
13886
- if (field.type === "multi")
13887
- return formatValueList(toArrayValue(value).map((v) => optionLabel(field, v)));
14284
+ if (field.type === "multi") {
14285
+ const values = toArrayValue(value);
14286
+ const selectableValues = (field.options ?? []).filter((option) => !option.disabled).map((option) => option.value);
14287
+ if (selectableValues.length > 0 && selectableValues.every((selectableValue) => values.includes(selectableValue))) {
14288
+ return "\uC804\uCCB4";
14289
+ }
14290
+ return formatValueList(values.map((v) => optionLabel(field, v)));
14291
+ }
13888
14292
  if (field.type === "single") return optionLabel(field, value);
13889
14293
  if (field.type === "date")
13890
14294
  return Array.isArray(value) ? `${value[0]} ~ ${value[1]}` : String(value);
14295
+ if (field.type === "period") {
14296
+ if (!isPeriodValue(value)) {
14297
+ const preset2 = getFieldPresets(field).find((p) => p.value === value);
14298
+ return preset2?.label ?? String(value);
14299
+ }
14300
+ const preset = getFieldPresets(field).find((p) => p.value === value.unit);
14301
+ const label = preset?.label ?? value.unit;
14302
+ if (value.unit === "quarter") return `${label}(${value.value ?? 1}\uBD84\uAE30)`;
14303
+ if (value.unit === "half") return `${label}(${value.value === "second" ? "\uD558\uBC18\uAE30" : "\uC0C1\uBC18\uAE30"})`;
14304
+ if (Array.isArray(value.value)) return `${value.value[0]} ~ ${value.value[1]}`;
14305
+ return value.value != null && value.value !== "" ? String(value.value) : label;
14306
+ }
13891
14307
  if (field.type === "keyword") return formatValueList(keywordEntries(value));
13892
14308
  return String(value);
13893
14309
  };
13894
14310
  var chipDisplayValue = (field, value) => {
13895
14311
  const text = displayValue(field, value);
13896
14312
  if (field.type !== "date" || isEmptyValue(value)) return text;
13897
- const presets = field.presets ?? [];
14313
+ const presets = getFieldPresets(field);
13898
14314
  if (presets.length === 0) return text;
13899
14315
  const preset = presets.find((p) => p.value === matchPresetId(presets, value));
13900
14316
  return preset && !preset.custom ? `${preset.label}(${text})` : text;
@@ -13911,7 +14327,6 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
13911
14327
  onAddFilter,
13912
14328
  label = "\uAC80\uC0C9 \uD544\uD130",
13913
14329
  showLabel = false,
13914
- showAddButton = true,
13915
14330
  showReset = true,
13916
14331
  disabled = false,
13917
14332
  className,
@@ -13926,7 +14341,7 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
13926
14341
  const values = normalizeValues(value);
13927
14342
  const portalContainer = usePortalContainer();
13928
14343
  const [internalActiveKeys, setInternalActiveKeys] = useState(
13929
- () => fields.filter((field) => field.defaultActive !== false).map((field) => field.key)
14344
+ () => fields.filter((field) => isFixedField(field) || field.defaultActive !== false).map((field) => field.key)
13930
14345
  );
13931
14346
  const [openKey, setOpenKey] = useState(null);
13932
14347
  const [addOpen, setAddOpen] = useState(false);
@@ -13968,7 +14383,9 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
13968
14383
  setInternalActiveKeys((current) => {
13969
14384
  const known = new Set(fields.map((field) => field.key));
13970
14385
  const kept = current.filter((key) => known.has(key));
13971
- const additions = fields.filter((field) => field.defaultActive !== false && !kept.includes(field.key)).map((field) => field.key);
14386
+ const additions = fields.filter(
14387
+ (field) => (isFixedField(field) || field.defaultActive !== false) && !kept.includes(field.key)
14388
+ ).map((field) => field.key);
13972
14389
  return [...kept, ...additions];
13973
14390
  });
13974
14391
  }, [activeKeys, fields]);
@@ -13989,25 +14406,31 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
13989
14406
  });
13990
14407
  if (changed) onValueChange?.({ ...values, ...patch });
13991
14408
  }, [fields, values, onValueChange]);
13992
- const resolvedActiveKeys = activeKeys ?? internalActiveKeys;
14409
+ const fixedKeys = fields.filter(isFixedField).map((field) => field.key);
14410
+ const resolvedActiveKeys = Array.from(
14411
+ /* @__PURE__ */ new Set([...activeKeys ?? internalActiveKeys, ...fixedKeys])
14412
+ );
13993
14413
  const activeFields = fields.filter((field) => resolvedActiveKeys.includes(field.key));
13994
- const inactiveFields = fields.filter((field) => !resolvedActiveKeys.includes(field.key));
14414
+ const inactiveFields = fields.filter(
14415
+ (field) => isAddableField(field) && !resolvedActiveKeys.includes(field.key)
14416
+ );
13995
14417
  const groupIndexByKey = /* @__PURE__ */ new Map();
13996
14418
  groups.forEach((group, groupIndex) => {
13997
14419
  group.fields.forEach((field) => groupIndexByKey.set(field.key, groupIndex));
13998
14420
  });
13999
14421
  const hasValue = fields.some((field) => !isEmptyValue(values[field.key]));
14000
- const hasRightActions = showAddButton || showReset;
14422
+ const hasAddButton = fields.some(isAddableField);
14423
+ const hasRightActions = hasAddButton || showReset;
14001
14424
  const currentOpenField = fields.find((field) => field.key === openKey);
14002
14425
  const editorPopoverStyle = useMemo(() => {
14003
14426
  if (currentOpenField?.type === "keyword") {
14004
14427
  return { width: CHIPFILTER_LISTBOX_WIDTH };
14005
14428
  }
14006
- if (currentOpenField?.type === "date" && (currentOpenField.presets?.length ?? 0) > 0) {
14429
+ if ((currentOpenField?.type === "date" || currentOpenField?.type === "period") && getFieldPresets(currentOpenField).length > 0) {
14007
14430
  return { width: CHIPFILTER_DATE_PRESET_LISTBOX_WIDTH };
14008
14431
  }
14009
14432
  return { width: CHIPFILTER_LISTBOX_WIDTH };
14010
- }, [currentOpenField?.type, currentOpenField?.presets?.length]);
14433
+ }, [currentOpenField]);
14011
14434
  const setActive = (next) => {
14012
14435
  if (!activeKeys) setInternalActiveKeys(next);
14013
14436
  onActiveKeysChange?.(next);
@@ -14026,6 +14449,12 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
14026
14449
  const def = field ? resolveFieldDefault(field) : void 0;
14027
14450
  runSearch(emitValue(key, def !== void 0 ? def : null));
14028
14451
  };
14452
+ const removeField = (key) => {
14453
+ const nextValues = emitValue(key, null);
14454
+ setActive(resolvedActiveKeys.filter((activeKey) => activeKey !== key));
14455
+ if (openKey === key) setOpenKey(null);
14456
+ runSearch(nextValues);
14457
+ };
14029
14458
  const reset = useCallback(() => {
14030
14459
  const next = fields.reduce((acc, field) => {
14031
14460
  const def = resolveFieldDefault(field);
@@ -14116,7 +14545,15 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
14116
14545
  flushRun();
14117
14546
  runGroupIndex = groupIndex;
14118
14547
  }
14119
- const showClear = !isAtDefaultValue(field, values[field.key]);
14548
+ const addable = isAddableField(field);
14549
+ const showClear = addable || !isAtDefaultValue(field, values[field.key]);
14550
+ const handleClear = () => {
14551
+ if (addable) {
14552
+ removeField(field.key);
14553
+ return;
14554
+ }
14555
+ clearValue(field.key);
14556
+ };
14120
14557
  const node = isInlineDateField(field) ? /* @__PURE__ */ jsx(
14121
14558
  InlineDatePresetFilter,
14122
14559
  {
@@ -14126,15 +14563,27 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
14126
14563
  warning,
14127
14564
  showClear,
14128
14565
  onValueChange: (next) => emitValueAndSearch(field.key, next),
14129
- onClear: () => clearValue(field.key)
14566
+ onClear: handleClear
14130
14567
  },
14131
14568
  field.key
14132
- ) : field.type === "custom" ? /* @__PURE__ */ jsx(Fragment$1, { children: field.render?.({
14133
- value: values[field.key],
14134
- disabled: disabled || field.disabled,
14135
- warning,
14136
- onValueChange: (next) => emitValueAndSearch(field.key, next)
14137
- }) }, field.key) : /* @__PURE__ */ jsx(
14569
+ ) : field.type === "custom" ? /* @__PURE__ */ jsxs("div", { className: "inline-flex shrink-0 items-center gap-[4px]", children: [
14570
+ field.render?.({
14571
+ value: values[field.key],
14572
+ disabled: disabled || field.disabled,
14573
+ warning,
14574
+ onValueChange: (next) => emitValueAndSearch(field.key, next)
14575
+ }),
14576
+ addable && !disabled && !field.disabled && showClear && /* @__PURE__ */ jsx(
14577
+ "button",
14578
+ {
14579
+ type: "button",
14580
+ "aria-label": `${field.label} \uD544\uD130 \uC81C\uAC70`,
14581
+ className: "inline-flex size-[16px] shrink-0 items-center justify-center rounded-[var(--cmp-ghostButton-radius-sm)] border-none bg-transparent p-0",
14582
+ onClick: handleClear,
14583
+ children: /* @__PURE__ */ jsx(SIcon, { name: "close", size: 12, color: "var(--cmp-ghostButton-default-content)" })
14584
+ }
14585
+ )
14586
+ ] }, field.key) : /* @__PURE__ */ jsx(
14138
14587
  FilterChip,
14139
14588
  {
14140
14589
  field,
@@ -14144,9 +14593,10 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
14144
14593
  disabled: disabled || field.disabled,
14145
14594
  warning,
14146
14595
  showClear,
14596
+ showClearWhenEmpty: addable,
14147
14597
  refCallback: (node2) => triggerRefs.current.set(field.key, node2),
14148
14598
  onOpen: () => setOpenKey(openKey === field.key ? null : field.key),
14149
- onClear: () => clearValue(field.key)
14599
+ onClear: handleClear
14150
14600
  },
14151
14601
  field.key
14152
14602
  );
@@ -14167,7 +14617,7 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
14167
14617
  })(),
14168
14618
  hasRightActions && /* @__PURE__ */ jsxs(Fragment, { children: [
14169
14619
  /* @__PURE__ */ jsx(Divider, { inset: true }),
14170
- showAddButton && /* @__PURE__ */ jsxs(RPopover9.Root, { open: addOpen, onOpenChange: setAddOpen, children: [
14620
+ hasAddButton && /* @__PURE__ */ jsxs(RPopover9.Root, { open: addOpen, onOpenChange: setAddOpen, children: [
14171
14621
  /* @__PURE__ */ jsx(RPopover9.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
14172
14622
  "button",
14173
14623
  {
@@ -14214,7 +14664,7 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
14214
14664
  "inline-flex shrink-0 cursor-pointer items-center gap-[4px] border-none bg-transparent p-0 text-[12px] font-medium leading-[20px] disabled:cursor-not-allowed",
14215
14665
  // 부모 flex gap(6px)은 칩 사이 간격 기준이라, 필터 추가 버튼과의 12px 간격을 맞추려면
14216
14666
  // 부족한 만큼(12px - gap)을 추가로 띄운다. 필터 추가가 없을 땐 divider와 이미 12px이라 불필요.
14217
- showAddButton && "ml-[calc(12px-var(--cmp-chipFilter-chip-group))]"
14667
+ hasAddButton && "ml-[calc(12px-var(--cmp-chipFilter-chip-group))]"
14218
14668
  ),
14219
14669
  style: {
14220
14670
  color: disabled || !hasValue ? "var(--cmp-textLink-content-disabled)" : "var(--cmp-textLink-content-default)"
@@ -14298,6 +14748,7 @@ function FilterChip({
14298
14748
  disabled,
14299
14749
  warning,
14300
14750
  showClear,
14751
+ showClearWhenEmpty,
14301
14752
  refCallback,
14302
14753
  onOpen,
14303
14754
  onClear
@@ -14367,7 +14818,7 @@ function FilterChip({
14367
14818
  }
14368
14819
  )
14369
14820
  ] }),
14370
- applied && !disabled && showClear && /* @__PURE__ */ jsx(
14821
+ !disabled && showClear && (applied || showClearWhenEmpty) && /* @__PURE__ */ jsx(
14371
14822
  "span",
14372
14823
  {
14373
14824
  role: "button",
@@ -14399,7 +14850,7 @@ function EditorPanel({
14399
14850
  onValueChange,
14400
14851
  onClose
14401
14852
  }) {
14402
- const wrapsInPaddedTop = field.type === "date";
14853
+ const wrapsInPaddedTop = field.type === "date" || field.type === "period";
14403
14854
  const isMulti = field.type === "multi";
14404
14855
  const selectableValues = isMulti ? (field.options ?? []).filter((option) => !option.disabled).map((option) => option.value) : [];
14405
14856
  const selectedValues = isMulti ? toArrayValue(value) : [];
@@ -14478,6 +14929,17 @@ function FilterEditor({
14478
14929
  disabled,
14479
14930
  onValueChange
14480
14931
  }) {
14932
+ if (field.type === "period") {
14933
+ return /* @__PURE__ */ jsx(
14934
+ PeriodPresetEditor,
14935
+ {
14936
+ field,
14937
+ value,
14938
+ disabled,
14939
+ onValueChange
14940
+ }
14941
+ );
14942
+ }
14481
14943
  if (field.type === "date") {
14482
14944
  return /* @__PURE__ */ jsx(
14483
14945
  DatePresetEditor,
@@ -14517,7 +14979,7 @@ function DatePresetEditor({
14517
14979
  disabled,
14518
14980
  onValueChange
14519
14981
  }) {
14520
- const presets = field.presets ?? [];
14982
+ const presets = getFieldPresets(field);
14521
14983
  const [selectedId, setSelectedId] = useState(() => matchPresetId(presets, value));
14522
14984
  const syncedInitialValueRef = useRef(!isEmptyValue(value));
14523
14985
  useEffect(() => {
@@ -14528,9 +14990,9 @@ function DatePresetEditor({
14528
14990
  const selectedPreset = presets.find((preset) => preset.value === selectedId);
14529
14991
  const selectPreset = (preset) => {
14530
14992
  setSelectedId(preset.value);
14531
- if (!preset.custom) onValueChange(preset.resolve ? preset.resolve() : null);
14993
+ if (!preset.custom) onValueChange(preset.resolve ? preset.resolve() : preset.value);
14532
14994
  };
14533
- if (presets.length === 0) {
14995
+ if (field.type === "date" && presets.length === 0) {
14534
14996
  return /* @__PURE__ */ jsx(
14535
14997
  SDatePicker,
14536
14998
  {
@@ -14566,6 +15028,130 @@ function DatePresetEditor({
14566
15028
  )
14567
15029
  ] });
14568
15030
  }
15031
+ function PeriodPresetEditor({
15032
+ field,
15033
+ value,
15034
+ disabled,
15035
+ onValueChange
15036
+ }) {
15037
+ const presets = getFieldPresets(field);
15038
+ const firstUnit = presets.find((preset) => !preset.custom)?.value ?? "day";
15039
+ const [selectedId, setSelectedId] = useState(() => matchPresetId(presets, value) ?? firstUnit);
15040
+ const periodValue = isPeriodValue(value) ? value : createPeriodValue(selectedId ?? firstUnit);
15041
+ const selectedUnit = selectedId ?? periodValue.unit;
15042
+ useEffect(() => {
15043
+ if (isEmptyValue(value)) return;
15044
+ setSelectedId(matchPresetId(presets, value) ?? firstUnit);
15045
+ }, [firstUnit, presets, value]);
15046
+ const selectPreset = (preset) => {
15047
+ const nextUnit = preset.value;
15048
+ setSelectedId(nextUnit);
15049
+ onValueChange(createPeriodValue(nextUnit));
15050
+ };
15051
+ const updateInputValue = (nextValue) => {
15052
+ onValueChange({ unit: selectedUnit, value: nextValue });
15053
+ };
15054
+ const renderInput = () => {
15055
+ if (selectedUnit === "day") {
15056
+ return /* @__PURE__ */ jsx(
15057
+ SDatePicker,
15058
+ {
15059
+ value: typeof periodValue.value === "string" ? periodValue.value : null,
15060
+ onValueChange: updateInputValue,
15061
+ selectable: field.selectable,
15062
+ disabled,
15063
+ width: "100%"
15064
+ }
15065
+ );
15066
+ }
15067
+ if (selectedUnit === "month") {
15068
+ return /* @__PURE__ */ jsx(
15069
+ SDatePicker,
15070
+ {
15071
+ mode: "month",
15072
+ value: typeof periodValue.value === "string" ? periodValue.value : null,
15073
+ onValueChange: updateInputValue,
15074
+ selectable: field.selectable,
15075
+ disabled,
15076
+ width: "100%"
15077
+ }
15078
+ );
15079
+ }
15080
+ if (selectedUnit === "quarter") {
15081
+ return /* @__PURE__ */ jsx(
15082
+ SSelect2,
15083
+ {
15084
+ value: typeof periodValue.value === "number" ? periodValue.value : 1,
15085
+ onValueChange: updateInputValue,
15086
+ emitValue: true,
15087
+ disabled,
15088
+ width: "100%",
15089
+ options: [
15090
+ { value: 1, label: "1\uBD84\uAE30" },
15091
+ { value: 2, label: "2\uBD84\uAE30" },
15092
+ { value: 3, label: "3\uBD84\uAE30" },
15093
+ { value: 4, label: "4\uBD84\uAE30" }
15094
+ ]
15095
+ }
15096
+ );
15097
+ }
15098
+ if (selectedUnit === "half") {
15099
+ return /* @__PURE__ */ jsx(
15100
+ SSelect2,
15101
+ {
15102
+ value: typeof periodValue.value === "string" ? periodValue.value : "first",
15103
+ onValueChange: updateInputValue,
15104
+ emitValue: true,
15105
+ disabled,
15106
+ width: "100%",
15107
+ options: [
15108
+ { value: "first", label: "\uC0C1\uBC18\uAE30" },
15109
+ { value: "second", label: "\uD558\uBC18\uAE30" }
15110
+ ]
15111
+ }
15112
+ );
15113
+ }
15114
+ if (selectedUnit === "year") {
15115
+ return /* @__PURE__ */ jsx(
15116
+ SDatePicker,
15117
+ {
15118
+ mode: "year",
15119
+ value: typeof periodValue.value === "string" ? periodValue.value : null,
15120
+ onValueChange: updateInputValue,
15121
+ selectable: field.selectable,
15122
+ disabled,
15123
+ width: "100%"
15124
+ }
15125
+ );
15126
+ }
15127
+ return /* @__PURE__ */ jsx(
15128
+ SDateRangePicker,
15129
+ {
15130
+ value: Array.isArray(periodValue.value) && periodValue.value.length === 2 ? periodValue.value : null,
15131
+ onValueChange: updateInputValue,
15132
+ selectable: field.selectable,
15133
+ maxRange: field.maxRange,
15134
+ disabled,
15135
+ width: "100%"
15136
+ }
15137
+ );
15138
+ };
15139
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-[var(--cmp-chipFilter-listbox-top-gap)]", children: [
15140
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-[8px]", children: presets.map((preset) => /* @__PURE__ */ jsx(
15141
+ SRadio,
15142
+ {
15143
+ val: preset.value,
15144
+ value: selectedId,
15145
+ label: preset.label,
15146
+ disabled,
15147
+ onValueChange: () => selectPreset(preset)
15148
+ },
15149
+ preset.value
15150
+ )) }),
15151
+ /* @__PURE__ */ jsx("div", { "aria-hidden": true, className: "h-px w-full", style: { background: CHIPFILTER.listboxBorder } }),
15152
+ /* @__PURE__ */ jsx("div", { className: "w-full [&>*]:w-full", children: renderInput() })
15153
+ ] });
15154
+ }
14569
15155
  function InlineDatePresetFilter({
14570
15156
  field,
14571
15157
  value,
@@ -15570,7 +16156,9 @@ var TIMEPICKER_SIZE_CONFIG = {
15570
16156
  fontSize: 12,
15571
16157
  lineHeight: 20,
15572
16158
  minWidth: 80,
15573
- fieldMinWidth: 128
16159
+ fieldMinWidth: 128,
16160
+ // 상한 등급 md
16161
+ fieldMaxWidth: 160
15574
16162
  },
15575
16163
  md: {
15576
16164
  height: V3("md-height"),
@@ -15582,7 +16170,9 @@ var TIMEPICKER_SIZE_CONFIG = {
15582
16170
  fontSize: 14,
15583
16171
  lineHeight: 24,
15584
16172
  minWidth: 96,
15585
- fieldMinWidth: 160
16173
+ fieldMinWidth: 160,
16174
+ // 상한 등급 lg
16175
+ fieldMaxWidth: 240
15586
16176
  }
15587
16177
  };
15588
16178
  function clampTimePart(value, min, max) {
@@ -15906,6 +16496,7 @@ function STimePicker({
15906
16496
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
15907
16497
  width,
15908
16498
  minWidth: dims.fieldMinWidth,
16499
+ maxWidth: dims.fieldMaxWidth,
15909
16500
  disabled,
15910
16501
  className,
15911
16502
  style,
@@ -16022,6 +16613,10 @@ var MIDDAY_FIELD_MIN_WIDTH = {
16022
16613
  sm: 178,
16023
16614
  md: 216
16024
16615
  };
16616
+ var MIDDAY_FIELD_MAX_WIDTH = {
16617
+ sm: 240,
16618
+ md: 240
16619
+ };
16025
16620
  function RangeTimeSelector({
16026
16621
  value,
16027
16622
  useMeridiem,
@@ -16368,6 +16963,7 @@ function STimeRangePicker({
16368
16963
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
16369
16964
  width,
16370
16965
  minWidth: effectiveUseMeridiem ? MIDDAY_FIELD_MIN_WIDTH[size] : dims.fieldMinWidth,
16966
+ maxWidth: effectiveUseMeridiem ? MIDDAY_FIELD_MAX_WIDTH[size] : dims.fieldMaxWidth,
16371
16967
  disabled,
16372
16968
  className,
16373
16969
  style,
@@ -16460,6 +17056,6 @@ function STimeRangePicker({
16460
17056
  );
16461
17057
  }
16462
17058
 
16463
- export { BADGE_COLORS, BUTTON_COLORS, BUTTON_SIZES, COLOR_KEYS, GNB_COLORS, GNB_COMMON, GNB_FOLD_MS, GNB_FULL_LOGO_WIDTH, GNB_HEADER, GNB_MENU, GNB_MENU_WIDTH, GNB_RAIL, GNB_RAIL_WIDTH, ICONS, IMAGE_FITS, PAGE_BACKGROUNDS, RangeCalendar, SActionModal, SBadge, SBarcodeInput, SButton, SCROLL_AREA_AXES, SCalendar, SCallout, SCard, SCheckbox, SChip2 as SChip, SChipFilter, SChipInput, SCircleProgress, SConfirmModal, SDatePicker, SDateRangePicker, SDivider, SDraggableGroup, SDraggableItem2 as SDraggableItem, SDraggableList, SDraggableProvider, SDrawer, SDropdownButton2 as SDropdownButton, SExpansionItem, SExpansionList2 as SExpansionList, SField, SFilePicker, SForm, SGhostButton, SGnb, SGuide, SIcon, SImage, SInput, SKeyValueTable, SLAYOUT_MIN_WIDTH, SLayout, SLayoutContext, SLinearProgress, SList2 as SList, SListItem2 as SListItem, SLoadingContainer, SLoadingModal, SLoadingViewport, SModal, SModalOutlet, SNumberInput, SPage, SPagination, SPopover, SPopup, SPortal, SRadio, SRadioButton, SRadioGroup, SSPLITTER_UNITS, SScrollArea, SSectionHeaderCard2 as SSectionHeaderCard, SSelect2 as SSelect, SSplitter2 as SSplitter, SStepper, SSwitch, STEPPER_SIZES, STable, STableBar, STabs, STag, STextLink, STextarea, STimePicker, STimeRangePicker, SToast, SToastContainer, SToggle, STooltip2 as STooltip, STree2 as STree, STreeItem2 as STreeItem, TAG_COLORS, TAG_SHAPES, TAG_SIZES, TIMEPICKER_SIZES, TIMEPICKER_SIZE_CONFIG, buttonPreset, cn, findGnbAncestors, findGnbRailValue, isColorKey, loading, resolveColor, useSLayout };
17059
+ export { BADGE_COLORS, BUTTON_COLORS, BUTTON_SIZES, COLOR_KEYS, GNB_COLORS, GNB_COMMON, GNB_FOLD_MS, GNB_FULL_LOGO_WIDTH, GNB_HEADER, GNB_MENU, GNB_MENU_WIDTH, GNB_RAIL, GNB_RAIL_WIDTH, ICONS, IMAGE_FITS, PAGE_BACKGROUNDS, PAGE_CONTENT_HEIGHTS, RangeCalendar, SActionModal, SBadge, SBarcodeInput, SButton, SCROLL_AREA_AXES, SCalendar, SCallout, SCard, SCheckbox, SChip2 as SChip, SChipFilter, SChipInput, SCircleProgress, SConfirmModal, SDatePicker, SDatePickerMonthListbox, SDatePickerYearListbox, SDateRangePicker, SDivider, SDraggableGroup, SDraggableItem2 as SDraggableItem, SDraggableList, SDraggableProvider, SDrawer, SDropdownButton2 as SDropdownButton, SExpansionItem, SExpansionList2 as SExpansionList, SField, SFilePicker, SForm, SGhostButton, SGnb, SGuide, SIcon, SImage, SInput, SKeyValueTable, SLAYOUT_MIN_WIDTH, SLayout, SLayoutContext, SLinearProgress, SList2 as SList, SListItem2 as SListItem, SLoadingContainer, SLoadingModal, SLoadingViewport, SModal, SModalOutlet, SNumberInput, SPage, SPagination, SPopover, SPopup, SPortal, SRadio, SRadioButton, SRadioGroup, SSPLITTER_UNITS, SScrollArea, SSectionHeaderCard2 as SSectionHeaderCard, SSelect2 as SSelect, SSplitter2 as SSplitter, SStepper, SSwitch, STEPPER_SIZES, STable, STableBar, STabs, STag, STextLink, STextarea, STimePicker, STimeRangePicker, SToast, SToastContainer, SToggle, STooltip2 as STooltip, STree2 as STree, STreeItem2 as STreeItem, TAG_COLORS, TAG_SHAPES, TAG_SIZES, TIMEPICKER_SIZES, TIMEPICKER_SIZE_CONFIG, buttonPreset, cn, findGnbAncestors, findGnbRailValue, isColorKey, loading, resolveColor, useSLayout };
16464
17060
  //# sourceMappingURL=index.js.map
16465
17061
  //# sourceMappingURL=index.js.map