@wipco/sdui-editor 0.17.0 → 0.18.0

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
@@ -1,5 +1,5 @@
1
1
  import React, { lazy, Suspense, useState, useCallback, useEffect, useMemo, useRef, useLayoutEffect, useDeferredValue, useReducer, useId } from 'react';
2
- import { defaultRegistry, createRegistry, Tooltip, SidebarNav, SegmentedControl, Radio, Breadcrumb, Avatar, Alert, Icon, IconButton, Spinner, Button, Card, Tabs, Input, Badge, EmptyState, Table, SduiDialog, Field, Select, validateSpec, SduiRenderer, Checkbox, Switch, Dialog, Textarea } from '@wipco/sdui';
2
+ import { defaultRegistry, createRegistry, Tooltip, SidebarNav, SegmentedControl, Radio, Breadcrumb, Avatar, Alert, Icon, IconButton, Spinner, Button, Card, Tabs, Input, Badge, EmptyState, Table, SduiDialog, Field, Select, validateSpec, SduiRenderer, Checkbox, Switch, OverlayContainerContext, Dialog, Textarea } from '@wipco/sdui';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import { EditorView, basicSetup } from 'codemirror';
5
5
  import { EditorState } from '@codemirror/state';
@@ -10091,6 +10091,11 @@ function EditorView2({
10091
10091
  const [corrupt, setCorrupt] = useState(false);
10092
10092
  const [draftRestored, setDraftRestored] = useState(false);
10093
10093
  const [helpOpen, setHelpOpen] = useState(false);
10094
+ const previewBodyRef = useRef(null);
10095
+ const [previewBody, setPreviewBody] = useState(null);
10096
+ useEffect(() => {
10097
+ setPreviewBody(previewBodyRef.current);
10098
+ }, []);
10094
10099
  const [previewKey, setPreviewKey] = useState(0);
10095
10100
  const [previewNotice, setPreviewNotice] = useState(null);
10096
10101
  const noticeTimer = useRef(null);
@@ -10118,8 +10123,10 @@ function EditorView2({
10118
10123
  alive = false;
10119
10124
  };
10120
10125
  }, [store]);
10126
+ const docIdRef = useRef(null);
10121
10127
  useEffect(() => {
10122
10128
  if (!id) return;
10129
+ docIdRef.current = null;
10123
10130
  setLoaded(null);
10124
10131
  setLoadError(null);
10125
10132
  setConflict(false);
@@ -10147,6 +10154,7 @@ function EditorView2({
10147
10154
  setDraftRestored(false);
10148
10155
  setBaseVersion(record.version);
10149
10156
  }
10157
+ docIdRef.current = id;
10150
10158
  let openedSpec;
10151
10159
  try {
10152
10160
  openedSpec = JSON.parse(opened);
@@ -10373,7 +10381,9 @@ function EditorView2({
10373
10381
  const parsedRef = useRef(parsed);
10374
10382
  parsedRef.current = parsed;
10375
10383
  const overrideSpec = useCallback(
10376
- (specId) => specId === id ? parsedRef.current.spec ?? void 0 : void 0,
10384
+ // docIdRef 가드 — 문서 전환 1프레임의 "새 id + 이전 파싱본"을 조각으로 내보내지
10385
+ // 않는다(위 docIdRef 주석). 그동안은 스토어 read가 맡는다.
10386
+ (specId) => specId === id && docIdRef.current === id ? parsedRef.current.spec ?? void 0 : void 0,
10377
10387
  [id]
10378
10388
  );
10379
10389
  const useSpec = useMemo(() => createEditorUseSpec(store, overrideSpec), [store, overrideSpec]);
@@ -10572,7 +10582,7 @@ function EditorView2({
10572
10582
  onClose: () => setSeedPanelOpen(false)
10573
10583
  }
10574
10584
  ) : null,
10575
- /* @__PURE__ */ jsx("div", { className: "sdui-editor-preview__body", children: previewPage ? /* @__PURE__ */ jsx(
10585
+ /* @__PURE__ */ jsx("div", { className: "sdui-editor-preview__body", ref: previewBodyRef, children: /* @__PURE__ */ jsx(OverlayContainerContext.Provider, { value: previewBody, children: previewPage ? /* @__PURE__ */ jsx(
10576
10586
  PreviewBoundary,
10577
10587
  {
10578
10588
  resetKey: previewKey,
@@ -10589,7 +10599,7 @@ function EditorView2({
10589
10599
  `${previewKey}:${seedSignature(seeds)}`
10590
10600
  )
10591
10601
  }
10592
- ) : /* @__PURE__ */ jsx(Alert, { tone: parsed.error ? "warning" : "neutral", children: parsed.error ? "JSON\uC744 \uACE0\uCE58\uBA74 \uBBF8\uB9AC\uBCF4\uAE30\uAC00 \uB2E4\uC2DC \uADF8\uB824\uC9D1\uB2C8\uB2E4." : "\uC2A4\uD399\uC744 \uC785\uB825\uD558\uC138\uC694." }) })
10602
+ ) : /* @__PURE__ */ jsx(Alert, { tone: parsed.error ? "warning" : "neutral", children: parsed.error ? "JSON\uC744 \uACE0\uCE58\uBA74 \uBBF8\uB9AC\uBCF4\uAE30\uAC00 \uB2E4\uC2DC \uADF8\uB824\uC9D1\uB2C8\uB2E4." : "\uC2A4\uD399\uC744 \uC785\uB825\uD558\uC138\uC694." }) }) })
10593
10603
  ] }),
10594
10604
  helpOpen ? /* @__PURE__ */ jsx(HelpPanel, { onClose: () => setHelpOpen(false) }) : null
10595
10605
  ] })
@@ -10627,12 +10637,14 @@ function ViewerView({
10627
10637
  error: null,
10628
10638
  seq: 0
10629
10639
  });
10640
+ const loadedForRef = useRef(null);
10630
10641
  const load = useCallback(() => {
10631
- store.read(id).then(
10632
- (record) => setState(
10642
+ store.read(id).then((record) => {
10643
+ loadedForRef.current = record ? id : null;
10644
+ setState(
10633
10645
  (prev) => record ? { spec: record.spec, error: null, seq: prev.seq + 1 } : { spec: null, error: "\uD398\uC774\uC9C0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4", seq: prev.seq + 1 }
10634
- )
10635
- ).catch(
10646
+ );
10647
+ }).catch(
10636
10648
  (err) => setState((prev) => ({ spec: null, error: messageOf(err, "\uD398\uC774\uC9C0\uB97C \uBD88\uB7EC\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4"), seq: prev.seq + 1 }))
10637
10649
  );
10638
10650
  }, [store, id]);
@@ -10641,10 +10653,16 @@ function ViewerView({
10641
10653
  setState((prev) => ({ spec: null, error: "\uD398\uC774\uC9C0 id\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.", seq: prev.seq + 1 }));
10642
10654
  return;
10643
10655
  }
10656
+ loadedForRef.current = null;
10644
10657
  setState((prev) => ({ spec: null, error: null, seq: prev.seq + 1 }));
10645
10658
  load();
10646
10659
  }, [id, load]);
10647
10660
  const [blocked, setBlocked] = useState(null);
10661
+ const viewerBodyRef = useRef(null);
10662
+ const [viewerBody, setViewerBody] = useState(null);
10663
+ useEffect(() => {
10664
+ setViewerBody(viewerBodyRef.current);
10665
+ }, []);
10648
10666
  const blockedTimer = useRef(null);
10649
10667
  const notifyBlocked = useCallback((what) => {
10650
10668
  setBlocked(`${what} \u2014 \uBCF4\uAE30\uC5D0\uC11C\uB294 \uC2E4\uD589\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4`);
@@ -10678,7 +10696,8 @@ function ViewerView({
10678
10696
  const specRef = useRef(state.spec);
10679
10697
  specRef.current = state.spec;
10680
10698
  const overrideSpec = useCallback(
10681
- (specId) => specId === id ? specRef.current ?? void 0 : void 0,
10699
+ // loadedForRef 가드 — id 전환 1프레임의 "새 id + 이전 스펙"을 조각으로 내보내지 않는다.
10700
+ (specId) => specId === id && loadedForRef.current === id ? specRef.current ?? void 0 : void 0,
10682
10701
  [id]
10683
10702
  );
10684
10703
  const useSpec = useMemo(() => createEditorUseSpec(store, overrideSpec), [store, overrideSpec]);
@@ -10732,7 +10751,7 @@ function ViewerView({
10732
10751
  onClose: () => setSeedPanelOpen(false)
10733
10752
  }
10734
10753
  ) : null,
10735
- /* @__PURE__ */ jsx("div", { className: "sdui-editor-viewer__body", children: state.error ? /* @__PURE__ */ jsx(Alert, { tone: "danger", title: "\uD398\uC774\uC9C0\uB97C \uD45C\uC2DC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4", children: state.error }) : !state.spec ? /* @__PURE__ */ jsx("div", { className: "sdui-editor-center", style: { padding: 64 }, children: /* @__PURE__ */ jsx(Spinner, { size: "lg", label: "\uBD88\uB7EC\uC624\uB294 \uC911" }) }) : /* @__PURE__ */ jsx(
10754
+ /* @__PURE__ */ jsx("div", { className: "sdui-editor-viewer__body", ref: viewerBodyRef, children: /* @__PURE__ */ jsx(OverlayContainerContext.Provider, { value: viewerBody, children: state.error ? /* @__PURE__ */ jsx(Alert, { tone: "danger", title: "\uD398\uC774\uC9C0\uB97C \uD45C\uC2DC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4", children: state.error }) : !state.spec ? /* @__PURE__ */ jsx("div", { className: "sdui-editor-center", style: { padding: 64 }, children: /* @__PURE__ */ jsx(Spinner, { size: "lg", label: "\uBD88\uB7EC\uC624\uB294 \uC911" }) }) : /* @__PURE__ */ jsx(
10736
10755
  ViewerBoundary,
10737
10756
  {
10738
10757
  resetKey: `${id}#${state.seq}`,
@@ -10765,7 +10784,7 @@ function ViewerView({
10765
10784
  `${id}#${state.seq}:${seedSignature(seeds)}`
10766
10785
  )
10767
10786
  }
10768
- ) })
10787
+ ) }) })
10769
10788
  ] });
10770
10789
  }
10771
10790
  function SduiEditor({
package/dist/styles.css CHANGED
@@ -122,7 +122,11 @@
122
122
  background: var(--wp-slate-25);
123
123
  }
124
124
  .sdui-editor-preview__notice { font-size: 12px; color: var(--wp-text-info); }
125
- .sdui-editor-preview__body { flex: 1; overflow-y: auto; padding: 24px; position: relative; }
125
+ /* contain: paint — 미리본 다이얼로그의 오버레이(SduiDialog 래퍼가 position:fixed로
126
+ 뷰포트에 고정)를 이 상자의 컨테이닝 블록에 가둔다. 없으면 오버레이가 편집기
127
+ 전체를 덮어 시딩 버튼·패널이 마우스로 조작 불가였다(0.17.1 검수 실측:
128
+ elementFromPoint=wp-overlay, 클릭 타임아웃). */
129
+ .sdui-editor-preview__body { flex: 1; overflow-y: auto; padding: 24px; position: relative; contain: paint; }
126
130
  .sdui-editor-preview__body .sdui-root { display: flex; flex-direction: column; gap: var(--wp-space-4); }
127
131
 
128
132
  /* ---------- 도움말 패널 ---------- */
@@ -164,7 +168,9 @@
164
168
  border-bottom: 1px solid var(--wp-border-subtle);
165
169
  }
166
170
  .sdui-editor-viewer__id { color: var(--wp-text-subtle); }
167
- .sdui-editor-viewer__body { flex: 1; width: 100%; max-width: 1080px; margin: 0 auto; padding: 28px 24px 48px; }
171
+ /* contain: paint — 위 .sdui-editor-preview__body와 같은 이유. 뷰어도 다이얼로그
172
+ 스펙을 열린 채 그리므로 오버레이가 상단 바·시딩 패널을 덮으면 안 된다. */
173
+ .sdui-editor-viewer__body { flex: 1; width: 100%; max-width: 1080px; margin: 0 auto; padding: 28px 24px 48px; contain: paint; }
168
174
  .sdui-editor-viewer__body .sdui-root { display: flex; flex-direction: column; gap: var(--wp-space-4); }
169
175
 
170
176
  @media (max-width: 900px) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipco/sdui-editor",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "SDUI 페이지 스펙 편집기 — 소비 프로젝트가 자기 라우트에 마운트하는 React 컴포넌트",
5
5
  "keywords": [
6
6
  "sdui",
@@ -58,14 +58,14 @@
58
58
  "codemirror": "^6.0.1"
59
59
  },
60
60
  "peerDependencies": {
61
- "@wipco/sdui": "^0.17.0",
61
+ "@wipco/sdui": "^0.18.0",
62
62
  "react": ">=18",
63
63
  "react-dom": ">=18"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@types/react": "^19.0.0",
67
67
  "@types/react-dom": "^19.0.0",
68
- "@wipco/sdui": "^0.17.0",
68
+ "@wipco/sdui": "^0.18.0",
69
69
  "tsup": "^8.5.0",
70
70
  "tsx": "^4.20.0",
71
71
  "typescript": "^5.7.0"