sellmate-design-system-react 9.0.0-beta.36 → 9.0.0-beta.38

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
@@ -6463,10 +6463,19 @@ var SLoadingContainer = /* @__PURE__ */ forwardRef(
6463
6463
  var state = { open: false, message: void 0 };
6464
6464
  var listeners = /* @__PURE__ */ new Set();
6465
6465
  var emit = () => listeners.forEach((l) => l());
6466
- var setState = (patch) => {
6467
- state = { ...state, ...patch };
6466
+ var entries = [];
6467
+ var seq = 0;
6468
+ var sync = () => {
6469
+ const top = entries[entries.length - 1];
6470
+ state = { open: entries.length > 0, message: top?.message };
6468
6471
  emit();
6469
6472
  };
6473
+ var release = (id) => {
6474
+ const next = entries.filter((e) => e.id !== id);
6475
+ if (next.length === entries.length) return;
6476
+ entries = next;
6477
+ sync();
6478
+ };
6470
6479
  var subscribe = (cb) => {
6471
6480
  listeners.add(cb);
6472
6481
  return () => listeners.delete(cb);
@@ -6486,14 +6495,41 @@ function ensureMounted() {
6486
6495
  createRoot(host).render(/* @__PURE__ */ jsx(SLoadingViewport, {}));
6487
6496
  }
6488
6497
  var loading = {
6489
- /** 로딩 오버레이 표시 (메시지 갱신도 동일 호출) */
6498
+ /**
6499
+ * 로딩 오버레이 표시. 겹쳐 부르면 그만큼 쌓이고 **마지막 하나가 걷힐 때** 닫힌다.
6500
+ * 반환값은 이 호출 하나만 걷는 함수다(두 번 불러도 한 번만 걷힌다) — `hide()` 대신 이것을
6501
+ * 쓰면 짝이 어긋나지 않는다.
6502
+ *
6503
+ * ⚠️ 메시지만 바꾸려면 `update()` 를 쓴다. `show()` 를 다시 부르면 하나 더 쌓여
6504
+ * `hide()` 한 번으로는 닫히지 않는다.
6505
+ */
6490
6506
  show(options = {}) {
6491
6507
  ensureMounted();
6492
- setState({ open: true, message: options.message });
6508
+ const id = ++seq;
6509
+ entries = [...entries, { id, message: options.message }];
6510
+ sync();
6511
+ return () => release(id);
6512
+ },
6513
+ /** 표시 중인 메시지 갱신 — 쌓인 수는 그대로다 */
6514
+ update(options) {
6515
+ const top = entries[entries.length - 1];
6516
+ if (!top) return;
6517
+ entries = [...entries.slice(0, -1), { ...top, message: options.message }];
6518
+ sync();
6493
6519
  },
6494
- /** 로딩 오버레이 숨김 */
6520
+ /** 가장 나중의 show 하나를 걷는다. 남은 것이 없을 때만 오버레이가 닫힌다 */
6495
6521
  hide() {
6496
- setState({ open: false });
6522
+ const top = entries[entries.length - 1];
6523
+ if (top) release(top.id);
6524
+ },
6525
+ /**
6526
+ * 쌓인 것을 모두 걷고 즉시 닫는다. 화면 전환·에러 복구처럼 진행 중이던 작업의 짝을 더는
6527
+ * 맞출 수 없을 때만 쓴다 — 평소에는 `hide()` 나 `show()` 가 준 함수로 하나씩 걷는다.
6528
+ */
6529
+ hideAll() {
6530
+ if (entries.length === 0) return;
6531
+ entries = [];
6532
+ sync();
6497
6533
  }
6498
6534
  };
6499
6535
  var SIZE_CONFIG = {
@@ -7912,7 +7948,7 @@ var MODAL_HOST_Z_INDEX = Z_INDEX.modalHost;
7912
7948
  var MODAL_HOST_ATTRIBUTE = "data-s-modal-host";
7913
7949
  var nextId = 0;
7914
7950
  var nextLayerIndex = 0;
7915
- var entries = [];
7951
+ var entries2 = [];
7916
7952
  function getModalHost() {
7917
7953
  if (typeof document === "undefined" || !document.body) return void 0;
7918
7954
  const existing = document.body.querySelector(`[${MODAL_HOST_ATTRIBUTE}]`);
@@ -7933,16 +7969,16 @@ function registerModalStackEntry() {
7933
7969
  id: ++nextId,
7934
7970
  layerIndex: nextLayerIndex++
7935
7971
  };
7936
- entries.push(entry);
7972
+ entries2.push(entry);
7937
7973
  let registered = true;
7938
7974
  return {
7939
7975
  layerIndex: entry.layerIndex,
7940
- isTop: () => entries[entries.length - 1] === entry,
7976
+ isTop: () => entries2[entries2.length - 1] === entry,
7941
7977
  unregister: () => {
7942
7978
  if (!registered) return;
7943
7979
  registered = false;
7944
- const index = entries.indexOf(entry);
7945
- if (index !== -1) entries.splice(index, 1);
7980
+ const index = entries2.indexOf(entry);
7981
+ if (index !== -1) entries2.splice(index, 1);
7946
7982
  }
7947
7983
  };
7948
7984
  }
@@ -7966,8 +8002,10 @@ function SModalContainer({
7966
8002
  const contentRef = useRef(null);
7967
8003
  const stackRegistrationRef = useRef(null);
7968
8004
  const [layerIndex, setLayerIndex] = useState(null);
8005
+ const closeRequested = useRef(false);
7969
8006
  useEffect(() => {
7970
8007
  if (open) {
8008
+ closeRequested.current = false;
7971
8009
  setRendered(true);
7972
8010
  return;
7973
8011
  }
@@ -8023,7 +8061,7 @@ function SModalContainer({
8023
8061
  {
8024
8062
  forceMount: rendered ? true : void 0,
8025
8063
  ref: contentRef,
8026
- className: "pointer-events-auto outline-none data-[state=open]:animate-modal-in data-[state=closed]:animate-modal-out",
8064
+ className: "pointer-events-auto outline-none data-[state=open]:animate-modal-in data-[state=closed]:pointer-events-none data-[state=closed]:animate-modal-out",
8027
8065
  style: { animationFillMode: "forwards" },
8028
8066
  onOpenAutoFocus: (e) => {
8029
8067
  e.preventDefault();
@@ -8054,6 +8092,8 @@ function SModalContainer({
8054
8092
  icon: "close",
8055
8093
  ariaLabel: "\uB2EB\uAE30",
8056
8094
  onClick: () => {
8095
+ if (closeRequested.current) return;
8096
+ closeRequested.current = true;
8057
8097
  onClose?.();
8058
8098
  onOpenChange?.(false);
8059
8099
  },
@@ -8127,6 +8167,16 @@ function SConfirmModal({
8127
8167
  }) {
8128
8168
  const iconName = TYPE_ICON[type];
8129
8169
  const mainStyle = MAIN_BUTTON_STYLE[mainButtonName ?? DEFAULT_MAIN_BUTTON[type]];
8170
+ const [settled, setSettled] = useState(false);
8171
+ useEffect(() => {
8172
+ if (open) setSettled(false);
8173
+ }, [open]);
8174
+ const settle = (emit3) => {
8175
+ if (settled) return;
8176
+ setSettled(true);
8177
+ emit3?.();
8178
+ onOpenChange?.(false);
8179
+ };
8130
8180
  const tagNode = tagSlot ?? (tagLabel ? /* @__PURE__ */ jsx(STag, { shape: tagShape, size: tagSize, color: tagColor, label: tagLabel }) : null);
8131
8181
  const optionNode = optionSlot ?? (slotLabel ? /* @__PURE__ */ jsx("span", { children: slotLabel }) : null);
8132
8182
  const hasContentBox = tagNode != null || optionNode != null;
@@ -8139,7 +8189,7 @@ function SConfirmModal({
8139
8189
  persistent,
8140
8190
  ariaTitle: modalTitle,
8141
8191
  showClose: true,
8142
- onClose,
8192
+ onClose: () => settle(onClose),
8143
8193
  className: "w-fit min-w-[min(520px,calc(100dvw-48px))]",
8144
8194
  children: /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-auto flex-col items-center gap-[40px] px-[32px] py-[40px]", children: [
8145
8195
  /* @__PURE__ */ jsxs("div", { className: "flex w-full min-h-0 flex-col items-center gap-[20px] overflow-y-auto", children: [
@@ -8189,10 +8239,8 @@ function SConfirmModal({
8189
8239
  color: "neutral",
8190
8240
  outline: true,
8191
8241
  size: "md",
8192
- onClick: () => {
8193
- onCancel?.();
8194
- onOpenChange?.(false);
8195
- },
8242
+ disabled: settled,
8243
+ onClick: () => settle(onCancel),
8196
8244
  label: subButtonLabel
8197
8245
  }
8198
8246
  ),
@@ -8201,10 +8249,8 @@ function SConfirmModal({
8201
8249
  {
8202
8250
  ...mainStyle,
8203
8251
  size: "md",
8204
- onClick: () => {
8205
- onOk?.();
8206
- onOpenChange?.(false);
8207
- },
8252
+ disabled: settled,
8253
+ onClick: () => settle(onOk),
8208
8254
  label: mainButtonLabel
8209
8255
  }
8210
8256
  )
@@ -8447,10 +8493,10 @@ function SDrawer({
8447
8493
  }, []);
8448
8494
  useEffect(() => {
8449
8495
  if (!rendered) return;
8450
- const sync = () => setViewportWidth(window.innerWidth);
8451
- sync();
8452
- window.addEventListener("resize", sync);
8453
- return () => window.removeEventListener("resize", sync);
8496
+ const sync2 = () => setViewportWidth(window.innerWidth);
8497
+ sync2();
8498
+ window.addEventListener("resize", sync2);
8499
+ return () => window.removeEventListener("resize", sync2);
8454
8500
  }, [rendered]);
8455
8501
  useEffect(() => {
8456
8502
  const panel2 = panelRef.current;
@@ -8696,7 +8742,7 @@ function SDrawer({
8696
8742
 
8697
8743
  // src/lib/modal-outlet.ts
8698
8744
  var EMPTY = [];
8699
- var entries2 = EMPTY;
8745
+ var entries3 = EMPTY;
8700
8746
  var listeners2 = /* @__PURE__ */ new Set();
8701
8747
  var outletRendered = false;
8702
8748
  var outletCount = 0;
@@ -8711,7 +8757,7 @@ function subscribeModalEntries(listener) {
8711
8757
  };
8712
8758
  }
8713
8759
  function getModalEntries() {
8714
- return entries2;
8760
+ return entries3;
8715
8761
  }
8716
8762
  function getServerModalEntries() {
8717
8763
  return EMPTY;
@@ -8730,9 +8776,9 @@ function registerModalOutlet() {
8730
8776
  function sweepOrphans() {
8731
8777
  if (outletCount > 0) return;
8732
8778
  outletRendered = false;
8733
- if (entries2 === EMPTY) return;
8734
- const orphans = entries2;
8735
- entries2 = EMPTY;
8779
+ if (entries3 === EMPTY) return;
8780
+ const orphans = entries3;
8781
+ entries3 = EMPTY;
8736
8782
  emit2();
8737
8783
  for (const entry of orphans) entry.onOrphan();
8738
8784
  }
@@ -8744,14 +8790,14 @@ function isModalOutletMounted() {
8744
8790
  }
8745
8791
  function addModalEntry(host, node, onOrphan) {
8746
8792
  const id = ++nextId2;
8747
- entries2 = [...entries2, { id, host, node, onOrphan }];
8793
+ entries3 = [...entries3, { id, host, node, onOrphan }];
8748
8794
  emit2();
8749
8795
  return id;
8750
8796
  }
8751
8797
  function removeModalEntry(id) {
8752
- const next = entries2.filter((entry) => entry.id !== id);
8753
- if (next.length === entries2.length) return;
8754
- entries2 = next;
8798
+ const next = entries3.filter((entry) => entry.id !== id);
8799
+ if (next.length === entries3.length) return;
8800
+ entries3 = next;
8755
8801
  emit2();
8756
8802
  }
8757
8803
  var EXIT_DURATION = 300;
@@ -8792,11 +8838,22 @@ var SModalRefImpl = class {
8792
8838
  else this.pendingPatches.push(patch);
8793
8839
  return this;
8794
8840
  }
8795
- // 닫힘 트리거 — 이미 닫힘이 시작됐으면 무시(중복 콜백 방지)
8796
- settle(emit3) {
8797
- if (this.dismissRequested) return;
8841
+ /**
8842
+ * 확정 콜백 1회 발화 — 이미 닫힘이 시작됐으면 무시한다. 발화했으면 true.
8843
+ *
8844
+ * 한 번 띄운 모달의 결말은 하나다(ok 이거나 cancel 이거나 close 다). 닫힘은 시작만 하고
8845
+ * 퇴장 애니메이션 동안 카드가 남아 있으므로, 그동안의 재클릭·키 반복이 두 번째 결말을
8846
+ * 만들지 않도록 여기서 막는다.
8847
+ */
8848
+ settleEmit(emit3) {
8849
+ if (this.dismissRequested) return false;
8798
8850
  this.dismissRequested = true;
8799
8851
  emit3();
8852
+ return true;
8853
+ }
8854
+ // 닫힘 트리거 — 이미 닫힘이 시작됐으면 무시(중복 콜백 방지)
8855
+ settle(emit3) {
8856
+ if (!this.settleEmit(emit3)) return;
8800
8857
  if (this.closeBinding) this.closeBinding();
8801
8858
  else this.pendingClose = true;
8802
8859
  }
@@ -8849,17 +8906,18 @@ var SModalRefImpl = class {
8849
8906
  _markDismissed() {
8850
8907
  this.dismissRequested = true;
8851
8908
  }
8852
- /** @internal confirm 버튼 경로에서 onOk 발화 */
8909
+ // 아래 세 경로는 컴포넌트가 스스로 닫으므로(onOpenChange(false)) 발화 가드만 태운다.
8910
+ /** @internal confirm 버튼 경로에서 onOk 발화 (닫히는 중의 재클릭·키 반복은 무시) */
8853
8911
  _emitOk() {
8854
- this.okFn?.();
8912
+ this.settleEmit(() => this.okFn?.());
8855
8913
  }
8856
- /** @internal confirm 버튼 경로에서 onCancel 발화 */
8914
+ /** @internal confirm 버튼 경로에서 onCancel 발화 (닫히는 중의 재클릭·키 반복은 무시) */
8857
8915
  _emitCancel() {
8858
- this.cancelFn?.();
8916
+ this.settleEmit(() => this.cancelFn?.());
8859
8917
  }
8860
- /** @internal 닫기(X) 경로에서 onClose 발화 */
8918
+ /** @internal 닫기(X) 경로에서 onClose 발화 (닫히는 중의 재클릭·키 반복은 무시) */
8861
8919
  _emitClose() {
8862
- this.closeFn?.();
8920
+ this.settleEmit(() => this.closeFn?.());
8863
8921
  }
8864
8922
  /** @internal loading 버튼 경로에서 onClick 발화 */
8865
8923
  _emitClick() {
@@ -9055,7 +9113,7 @@ function SModalEntryPortal({ entry }) {
9055
9113
  }
9056
9114
  function SModalOutlet() {
9057
9115
  markModalOutletRendered();
9058
- const entries3 = useSyncExternalStore(
9116
+ const entries4 = useSyncExternalStore(
9059
9117
  subscribeModalEntries,
9060
9118
  getModalEntries,
9061
9119
  getServerModalEntries
@@ -9068,7 +9126,7 @@ function SModalOutlet() {
9068
9126
  }
9069
9127
  return registerModalOutlet();
9070
9128
  }, []);
9071
- return /* @__PURE__ */ jsx(PortalContainerProvider, { value: null, children: /* @__PURE__ */ jsx(InsideModalProvider, { value: false, children: entries3.map((entry) => /* @__PURE__ */ jsx(SModalEntryPortal, { entry }, entry.id)) }) });
9129
+ return /* @__PURE__ */ jsx(PortalContainerProvider, { value: null, children: /* @__PURE__ */ jsx(InsideModalProvider, { value: false, children: entries4.map((entry) => /* @__PURE__ */ jsx(SModalEntryPortal, { entry }, entry.id)) }) });
9072
9130
  }
9073
9131
  var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
9074
9132
  var SPageColumnContext = /* @__PURE__ */ createContext(null);
@@ -17842,10 +17900,10 @@ var useIsMobile = () => {
17842
17900
  useEffect(() => {
17843
17901
  if (typeof window.matchMedia !== "function") return;
17844
17902
  const query = window.matchMedia(`(max-width: ${MOBILE_MAX_WIDTH - 1}px)`);
17845
- const sync = () => setIsMobile(query.matches);
17846
- sync();
17847
- query.addEventListener("change", sync);
17848
- return () => query.removeEventListener("change", sync);
17903
+ const sync2 = () => setIsMobile(query.matches);
17904
+ sync2();
17905
+ query.addEventListener("change", sync2);
17906
+ return () => query.removeEventListener("change", sync2);
17849
17907
  }, []);
17850
17908
  return isMobile;
17851
17909
  };
@@ -19345,9 +19403,9 @@ var keywordEntries = (value) => {
19345
19403
  return [];
19346
19404
  };
19347
19405
  var CSV_PASTE_SEPARATOR = /[,\r\n\t]+/;
19348
- var newKeywordEntries = (entries3, candidates) => candidates.reduce((acc, raw) => {
19406
+ var newKeywordEntries = (entries4, candidates) => candidates.reduce((acc, raw) => {
19349
19407
  const next = raw.trim();
19350
- if (next === "" || entries3.includes(next) || acc.includes(next)) return acc;
19408
+ if (next === "" || entries4.includes(next) || acc.includes(next)) return acc;
19351
19409
  return [...acc, next];
19352
19410
  }, []);
19353
19411
  var withKeywordEntries = (field, current, nextEntries) => field.matchModes ? { keywords: nextEntries, mode: isKeywordValue(current) ? current.mode : "contains" } : nextEntries;
@@ -19611,13 +19669,13 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
19611
19669
  const field = fields.find((f) => f.key === key);
19612
19670
  if (field == null || field.type !== "keyword" || disabled || field.disabled) return null;
19613
19671
  const current = valuesRef.current[key];
19614
- const entries3 = keywordEntries(current);
19672
+ const entries4 = keywordEntries(current);
19615
19673
  const added = newKeywordEntries(
19616
- entries3,
19674
+ entries4,
19617
19675
  field.input === "csv" ? draft.text.split(CSV_PASTE_SEPARATOR) : [draft.text]
19618
19676
  );
19619
19677
  if (added.length === 0) return null;
19620
- const nextValue = withKeywordEntries(field, current, [...entries3, ...added]);
19678
+ const nextValue = withKeywordEntries(field, current, [...entries4, ...added]);
19621
19679
  const next = { ...valuesRef.current, [key]: nextValue };
19622
19680
  onValueChange?.(next);
19623
19681
  onFilterChange?.({ key, value: nextValue, values: next });
@@ -20638,7 +20696,7 @@ function KeywordEditor({
20638
20696
  onValueCommit,
20639
20697
  onDraftChange
20640
20698
  }) {
20641
- const entries3 = keywordEntries(value);
20699
+ const entries4 = keywordEntries(value);
20642
20700
  const [draftText, setDraftText] = useState("");
20643
20701
  const [mode, setMode] = useState(
20644
20702
  isKeywordValue(value) ? value.mode : "contains"
@@ -20660,16 +20718,16 @@ function KeywordEditor({
20660
20718
  };
20661
20719
  const changeMode = (next) => {
20662
20720
  setMode(next);
20663
- emit3(entries3, next);
20721
+ emit3(entries4, next);
20664
20722
  };
20665
- const newEntries = (candidates) => newKeywordEntries(entries3, candidates);
20723
+ const newEntries = (candidates) => newKeywordEntries(entries4, candidates);
20666
20724
  const addEntry = (raw) => {
20667
20725
  const added = newEntries([raw]);
20668
20726
  if (added.length === 0) {
20669
20727
  setDraft("");
20670
20728
  return;
20671
20729
  }
20672
- commit([...entries3, ...added]);
20730
+ commit([...entries4, ...added]);
20673
20731
  setDraft("");
20674
20732
  };
20675
20733
  const changeDraft = (next) => {
@@ -20680,7 +20738,7 @@ function KeywordEditor({
20680
20738
  const parts = next.split(",");
20681
20739
  const tail = parts.pop() ?? "";
20682
20740
  const added = newEntries(parts);
20683
- if (added.length > 0) commit([...entries3, ...added]);
20741
+ if (added.length > 0) commit([...entries4, ...added]);
20684
20742
  setDraft(tail);
20685
20743
  };
20686
20744
  const onPaste = (event) => {
@@ -20689,11 +20747,11 @@ function KeywordEditor({
20689
20747
  if (!CSV_PASTE_SEPARATOR.test(text)) return;
20690
20748
  event.preventDefault();
20691
20749
  const added = newEntries((draftText + text).split(CSV_PASTE_SEPARATOR));
20692
- if (added.length > 0) commit([...entries3, ...added]);
20750
+ if (added.length > 0) commit([...entries4, ...added]);
20693
20751
  setDraft("");
20694
20752
  };
20695
20753
  const removeEntry = (entry) => {
20696
- emit3(entries3.filter((e) => e !== entry));
20754
+ emit3(entries4.filter((e) => e !== entry));
20697
20755
  };
20698
20756
  const onKeyDown = (event) => {
20699
20757
  if (event.key === "Enter") {
@@ -20742,12 +20800,12 @@ function KeywordEditor({
20742
20800
  )
20743
20801
  ] })
20744
20802
  ] }),
20745
- entries3.length > 0 && /* @__PURE__ */ jsx(
20803
+ entries4.length > 0 && /* @__PURE__ */ jsx(
20746
20804
  "div",
20747
20805
  {
20748
20806
  className: "min-h-0 flex-1 overflow-auto border-t border-solid py-[var(--cmp-chipFilter-listbox-list-paddingY)]",
20749
20807
  style: { borderColor: CHIPFILTER.listboxBorder },
20750
- children: entries3.map((entry) => /* @__PURE__ */ jsx(
20808
+ children: entries4.map((entry) => /* @__PURE__ */ jsx(
20751
20809
  KeywordEntryRow,
20752
20810
  {
20753
20811
  label: optionLabel(field, entry),