@wipco/sdui 0.17.1 → 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.
@@ -1,3 +1,4 @@
1
+ import { OverlayLayer } from './chunk-JOAUXZZA.js';
1
2
  import { includesLooseSearch } from './chunk-GZB6YLUI.js';
2
3
  import { Button, Dialog, Input, Tag, Checkbox } from './chunk-WIDDHR7L.js';
3
4
  import { boxed } from './chunk-SAN4IX6N.js';
@@ -102,85 +103,74 @@ function SearchableMultiPickerImpl({
102
103
  }
103
104
  return /* @__PURE__ */ jsxs(Fragment, { children: [
104
105
  triggerNode,
105
- open ? /* @__PURE__ */ jsx(
106
- "div",
106
+ open ? /* @__PURE__ */ jsx(OverlayLayer, { onClose: closeDialog, children: /* @__PURE__ */ jsx(
107
+ Dialog,
107
108
  {
108
- style: { position: "fixed", inset: 0, zIndex: "var(--wp-z-dialog)" },
109
- onClick: (event) => {
110
- if (event.target instanceof HTMLElement && event.target.classList.contains("wp-overlay")) {
111
- closeDialog();
112
- }
113
- },
114
- children: /* @__PURE__ */ jsx(
115
- Dialog,
116
- {
117
- title: title ?? "\uD56D\uBAA9 \uC120\uD0DD",
118
- subtitle: `\uC804\uCCB4 ${safeOptions.length}\uAC1C \uC911\uC5D0\uC11C \uC120\uD0DD`,
119
- icon: "list-filter",
120
- size: "md",
121
- onClose: closeDialog,
122
- footer: /* @__PURE__ */ jsxs(Fragment, { children: [
123
- /* @__PURE__ */ jsx("div", { style: { marginRight: "auto" }, children: /* @__PURE__ */ jsx(
124
- Button,
125
- {
126
- type: "button",
127
- variant: "ghost",
128
- size: "sm",
129
- icon: "rotate-ccw",
130
- disabled: draft.length === 0,
131
- onClick: () => setDraft([]),
132
- children: "\uC804\uCCB4 \uD574\uC81C"
133
- }
134
- ) }),
135
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "primary", size: "sm", onClick: commit, children: "\uC644\uB8CC" })
109
+ title: title ?? "\uD56D\uBAA9 \uC120\uD0DD",
110
+ subtitle: `\uC804\uCCB4 ${safeOptions.length}\uAC1C \uC911\uC5D0\uC11C \uC120\uD0DD`,
111
+ icon: "list-filter",
112
+ size: "md",
113
+ onClose: closeDialog,
114
+ footer: /* @__PURE__ */ jsxs(Fragment, { children: [
115
+ /* @__PURE__ */ jsx("div", { style: { marginRight: "auto" }, children: /* @__PURE__ */ jsx(
116
+ Button,
117
+ {
118
+ type: "button",
119
+ variant: "ghost",
120
+ size: "sm",
121
+ icon: "rotate-ccw",
122
+ disabled: draft.length === 0,
123
+ onClick: () => setDraft([]),
124
+ children: "\uC804\uCCB4 \uD574\uC81C"
125
+ }
126
+ ) }),
127
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "primary", size: "sm", onClick: commit, children: "\uC644\uB8CC" })
128
+ ] }),
129
+ children: /* @__PURE__ */ jsxs("div", { className: "wp-col", style: { gap: "var(--wp-space-3)" }, children: [
130
+ /* @__PURE__ */ jsx(
131
+ Input,
132
+ {
133
+ icon: "search",
134
+ placeholder: "\uC774\uB984\xB7\uC124\uBA85\uC73C\uB85C \uAC80\uC0C9",
135
+ autoFocus: true,
136
+ value: query,
137
+ onChange: (event) => setQuery(event.target.value)
138
+ }
139
+ ),
140
+ /* @__PURE__ */ jsxs("div", { className: "wp-row", style: { flexWrap: "wrap", gap: "var(--wp-space-1)" }, children: [
141
+ /* @__PURE__ */ jsxs("span", { className: "wp-caption", style: { flex: "none" }, children: [
142
+ "\uC120\uD0DD ",
143
+ draft.length,
144
+ "\uAC1C"
136
145
  ] }),
137
- children: /* @__PURE__ */ jsxs("div", { className: "wp-col", style: { gap: "var(--wp-space-3)" }, children: [
138
- /* @__PURE__ */ jsx(
139
- Input,
146
+ draftChips.map((chip) => /* @__PURE__ */ jsx(Tag, { onRemove: () => toggle(chip.value, false), children: chip.label }, chip.value))
147
+ ] }),
148
+ /* @__PURE__ */ jsx(
149
+ "div",
150
+ {
151
+ className: "wp-col",
152
+ style: {
153
+ maxHeight: 320,
154
+ overflowY: "auto",
155
+ gap: "var(--wp-space-1)",
156
+ borderTop: "var(--wp-border-w) solid var(--wp-divider)",
157
+ paddingTop: "var(--wp-space-2)"
158
+ },
159
+ children: visibleOptions.length === 0 ? /* @__PURE__ */ jsx("div", { className: "wp-caption", style: { padding: "var(--wp-space-2) 0" }, children: "\uACB0\uACFC \uC5C6\uC74C" }) : visibleOptions.map((option) => /* @__PURE__ */ jsx(
160
+ Checkbox,
140
161
  {
141
- icon: "search",
142
- placeholder: "\uC774\uB984\xB7\uC124\uBA85\uC73C\uB85C \uAC80\uC0C9",
143
- autoFocus: true,
144
- value: query,
145
- onChange: (event) => setQuery(event.target.value)
146
- }
147
- ),
148
- /* @__PURE__ */ jsxs("div", { className: "wp-row", style: { flexWrap: "wrap", gap: "var(--wp-space-1)" }, children: [
149
- /* @__PURE__ */ jsxs("span", { className: "wp-caption", style: { flex: "none" }, children: [
150
- "\uC120\uD0DD ",
151
- draft.length,
152
- "\uAC1C"
153
- ] }),
154
- draftChips.map((chip) => /* @__PURE__ */ jsx(Tag, { onRemove: () => toggle(chip.value, false), children: chip.label }, chip.value))
155
- ] }),
156
- /* @__PURE__ */ jsx(
157
- "div",
158
- {
159
- className: "wp-col",
160
- style: {
161
- maxHeight: 320,
162
- overflowY: "auto",
163
- gap: "var(--wp-space-1)",
164
- borderTop: "var(--wp-border-w) solid var(--wp-divider)",
165
- paddingTop: "var(--wp-space-2)"
166
- },
167
- children: visibleOptions.length === 0 ? /* @__PURE__ */ jsx("div", { className: "wp-caption", style: { padding: "var(--wp-space-2) 0" }, children: "\uACB0\uACFC \uC5C6\uC74C" }) : visibleOptions.map((option) => /* @__PURE__ */ jsx(
168
- Checkbox,
169
- {
170
- label: option.label,
171
- description: option.sub,
172
- checked: draft.includes(option.value),
173
- onChange: (event) => toggle(option.value, event.target.checked)
174
- },
175
- option.value
176
- ))
177
- }
178
- )
179
- ] })
180
- }
181
- )
162
+ label: option.label,
163
+ description: option.sub,
164
+ checked: draft.includes(option.value),
165
+ onChange: (event) => toggle(option.value, event.target.checked)
166
+ },
167
+ option.value
168
+ ))
169
+ }
170
+ )
171
+ ] })
182
172
  }
183
- ) : null
173
+ ) }) : null
184
174
  ] });
185
175
  }
186
176
  var SearchableMultiPicker = boxed(SearchableMultiPickerImpl, "SearchableMultiPicker");
@@ -1,3 +1,4 @@
1
+ import { useOverlayLayer } from './chunk-JOAUXZZA.js';
1
2
  import { Icon } from './chunk-WIDDHR7L.js';
2
3
  import { boxed } from './chunk-SAN4IX6N.js';
3
4
  import { createContext, useState, useRef, useMemo, useCallback, useEffect, useLayoutEffect, useContext } from 'react';
@@ -281,16 +282,12 @@ function useDismiss(open, hostRef, onDismiss) {
281
282
  if (event.target instanceof Node && hostRef.current?.contains(event.target)) return;
282
283
  onDismiss("outside");
283
284
  };
284
- const handleKeyDown = (event) => {
285
- if (event.key === "Escape") onDismiss("escape");
286
- };
287
285
  document.addEventListener("pointerdown", handlePointerDown);
288
- document.addEventListener("keydown", handleKeyDown);
289
286
  return () => {
290
287
  document.removeEventListener("pointerdown", handlePointerDown);
291
- document.removeEventListener("keydown", handleKeyDown);
292
288
  };
293
289
  }, [open, hostRef, onDismiss]);
290
+ useOverlayLayer(open, () => onDismiss("escape"), hostRef);
294
291
  }
295
292
  function triggerClassName(size, invalid) {
296
293
  const classes = ["wp-input", "wp-dp-trigger"];
@@ -0,0 +1,123 @@
1
+ import React, { useRef, useEffect, useSyncExternalStore } from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ // src/overlay.tsx
6
+ var OVERLAY_ROOT_ID = "wp-overlay-root";
7
+ var DOCUMENT_POSITION_FOLLOWING = 4;
8
+ function getOverlayRoot() {
9
+ if (typeof document === "undefined") return null;
10
+ const existing = document.getElementById(OVERLAY_ROOT_ID);
11
+ if (existing) return existing;
12
+ const created = document.createElement("div");
13
+ created.id = OVERLAY_ROOT_ID;
14
+ document.body.appendChild(created);
15
+ return created;
16
+ }
17
+ var layers = [];
18
+ var nextId = 0;
19
+ var subscribers = /* @__PURE__ */ new Set();
20
+ var keyListenerBound = false;
21
+ function emit() {
22
+ layers = layers.slice();
23
+ subscribers.forEach((fn) => fn());
24
+ }
25
+ function topLayer(list) {
26
+ let best;
27
+ let bestNode = null;
28
+ for (const layer of list) {
29
+ const node = layer.node();
30
+ if (!best) {
31
+ best = layer;
32
+ bestNode = node;
33
+ continue;
34
+ }
35
+ if (!node) continue;
36
+ if (!bestNode) {
37
+ best = layer;
38
+ bestNode = node;
39
+ continue;
40
+ }
41
+ if (bestNode.compareDocumentPosition(node) & DOCUMENT_POSITION_FOLLOWING) {
42
+ best = layer;
43
+ bestNode = node;
44
+ }
45
+ }
46
+ return best;
47
+ }
48
+ function escapeTopmost() {
49
+ topLayer(layers)?.onEscape();
50
+ }
51
+ function handleKeyDown(event) {
52
+ if (event.key !== "Escape") return;
53
+ escapeTopmost();
54
+ }
55
+ function push(layer) {
56
+ layers.push(layer);
57
+ if (!keyListenerBound && typeof document !== "undefined") {
58
+ document.addEventListener("keydown", handleKeyDown);
59
+ keyListenerBound = true;
60
+ }
61
+ emit();
62
+ }
63
+ function remove(id) {
64
+ layers = layers.filter((l) => l.id !== id);
65
+ emit();
66
+ }
67
+ function subscribe(fn) {
68
+ subscribers.add(fn);
69
+ return () => {
70
+ subscribers.delete(fn);
71
+ };
72
+ }
73
+ var snapshot = () => layers;
74
+ var serverSnapshot = () => [];
75
+ function useOverlayLayer(active, onEscape, nodeRef) {
76
+ const idRef = useRef(0);
77
+ if (idRef.current === 0) idRef.current = ++nextId;
78
+ const escapeRef = useRef(onEscape);
79
+ escapeRef.current = onEscape;
80
+ const nodeRefRef = useRef(nodeRef);
81
+ nodeRefRef.current = nodeRef;
82
+ useEffect(() => {
83
+ if (!active) return;
84
+ const id = idRef.current;
85
+ push({
86
+ id,
87
+ onEscape: () => escapeRef.current?.(),
88
+ node: () => nodeRefRef.current?.current ?? null
89
+ });
90
+ return () => remove(id);
91
+ }, [active]);
92
+ const current = useSyncExternalStore(subscribe, snapshot, serverSnapshot);
93
+ return { isTop: active && topLayer(current)?.id === idRef.current };
94
+ }
95
+ var OverlayContainerContext = React.createContext(null);
96
+ function OverlayLayer({
97
+ onClose,
98
+ children
99
+ }) {
100
+ const ref = useRef(null);
101
+ const container = React.useContext(OverlayContainerContext);
102
+ const { isTop } = useOverlayLayer(true, onClose, ref);
103
+ useEffect(() => {
104
+ ref.current?.toggleAttribute("inert", !isTop);
105
+ }, [isTop]);
106
+ const layer = /* @__PURE__ */ jsx(
107
+ "div",
108
+ {
109
+ ref,
110
+ className: "wp-overlay-layer",
111
+ onClick: (event) => {
112
+ if (onClose && event.target instanceof HTMLElement && event.target.classList.contains("wp-overlay")) {
113
+ onClose();
114
+ }
115
+ },
116
+ children
117
+ }
118
+ );
119
+ const root = container ?? getOverlayRoot();
120
+ return root ? createPortal(layer, root) : layer;
121
+ }
122
+
123
+ export { OverlayContainerContext, OverlayLayer, getOverlayRoot, useOverlayLayer };
@@ -1,4 +1,5 @@
1
1
  import { resolveTreeNodes, TreeView } from './chunk-PYSTET2C.js';
2
+ import { OverlayLayer } from './chunk-JOAUXZZA.js';
2
3
  import { Field, Dialog, Icon, Button, Input } from './chunk-WIDDHR7L.js';
3
4
  import { boxed } from './chunk-SAN4IX6N.js';
4
5
  import { useState, useMemo, useEffect } from 'react';
@@ -99,98 +100,87 @@ function TreePickerImpl({
99
100
  return /* @__PURE__ */ jsxs("div", { className: "erp-treepicker", style: { minWidth: 0 }, children: [
100
101
  /* @__PURE__ */ jsx("style", { children: PICKER_CSS }),
101
102
  label ? /* @__PURE__ */ jsx(Field, { label, children: trigger }) : trigger,
102
- open ? /* @__PURE__ */ jsx(
103
- "div",
103
+ open ? /* @__PURE__ */ jsx(OverlayLayer, { onClose: closeDialog, children: /* @__PURE__ */ jsx(
104
+ Dialog,
104
105
  {
105
- style: { position: "fixed", inset: 0, zIndex: "var(--wp-z-dialog)" },
106
- onClick: (event) => {
107
- if (event.target instanceof HTMLElement && event.target.classList.contains("wp-overlay")) {
108
- closeDialog();
109
- }
110
- },
111
- children: /* @__PURE__ */ jsx(
112
- Dialog,
113
- {
114
- title: label ?? "\uD56D\uBAA9 \uC120\uD0DD",
115
- subtitle: "\uD2B8\uB9AC\uC5D0\uC11C \uD56D\uBAA9\uC744 \uC120\uD0DD\uD55C \uB4A4 [\uC120\uD0DD]\uC744 \uB204\uB974\uC138\uC694.",
116
- icon: "git-branch",
117
- size: "md",
118
- onClose: closeDialog,
119
- footer: /* @__PURE__ */ jsxs(Fragment, { children: [
120
- /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: closeDialog, children: "\uCDE8\uC18C" }),
121
- /* @__PURE__ */ jsx(
122
- Button,
123
- {
124
- variant: "primary",
125
- onClick: commit,
126
- disabled: commitDisabled,
127
- title: commitDisabled ? "\uC120\uD0DD\uD560 \uD56D\uBAA9\uC744 \uC9C0\uC815\uD558\uC138\uC694" : void 0,
128
- children: "\uC120\uD0DD"
129
- }
130
- )
131
- ] }),
132
- children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "var(--wp-space-2)", minWidth: 0 }, children: [
133
- allowEmpty ? /* @__PURE__ */ jsxs(
134
- "div",
135
- {
136
- role: "radio",
137
- "aria-checked": noneSelected,
138
- "aria-current": noneSelected ? "true" : void 0,
139
- tabIndex: 0,
140
- className: "erp-treepicker__none",
141
- style: {
142
- display: "flex",
143
- alignItems: "center",
144
- gap: "var(--wp-space-2)",
145
- minHeight: "var(--wp-row-h-dense)",
146
- padding: "0 var(--wp-space-2)",
147
- borderRadius: "var(--wp-radius-sm)",
148
- cursor: "pointer",
149
- userSelect: "none"
150
- },
151
- onClick: () => setPendingId(null),
152
- onKeyDown: (event) => {
153
- if (event.key === "Enter" || event.key === " ") {
154
- event.preventDefault();
155
- setPendingId(null);
156
- }
157
- },
158
- children: [
159
- /* @__PURE__ */ jsx(
160
- Icon,
161
- {
162
- name: noneSelected ? "circle-dot" : "circle-x",
163
- size: 16,
164
- style: { flexShrink: 0, color: noneSelected ? "var(--wp-text-brand)" : "var(--wp-text-muted)" }
165
- }
166
- ),
167
- /* @__PURE__ */ jsx(
168
- "span",
169
- {
170
- className: "wp-body-sm",
171
- style: { color: noneSelected ? "var(--wp-text-strong)" : "var(--wp-text-muted)" },
172
- children: "(\uC5C6\uC74C)"
173
- }
174
- )
175
- ]
176
- }
177
- ) : null,
178
- /* @__PURE__ */ jsx(
179
- TreeView,
180
- {
181
- nodes: sourceNodes,
182
- selectedId: pendingId,
183
- onSelect: (id) => setPendingId(id),
184
- searchable: true,
185
- excludeIds,
186
- maxHeight: "320px"
106
+ title: label ?? "\uD56D\uBAA9 \uC120\uD0DD",
107
+ subtitle: "\uD2B8\uB9AC\uC5D0\uC11C \uD56D\uBAA9\uC744 \uC120\uD0DD\uD55C \uB4A4 [\uC120\uD0DD]\uC744 \uB204\uB974\uC138\uC694.",
108
+ icon: "git-branch",
109
+ size: "md",
110
+ onClose: closeDialog,
111
+ footer: /* @__PURE__ */ jsxs(Fragment, { children: [
112
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: closeDialog, children: "\uCDE8\uC18C" }),
113
+ /* @__PURE__ */ jsx(
114
+ Button,
115
+ {
116
+ variant: "primary",
117
+ onClick: commit,
118
+ disabled: commitDisabled,
119
+ title: commitDisabled ? "\uC120\uD0DD\uD560 \uD56D\uBAA9\uC744 \uC9C0\uC815\uD558\uC138\uC694" : void 0,
120
+ children: "\uC120\uD0DD"
121
+ }
122
+ )
123
+ ] }),
124
+ children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "var(--wp-space-2)", minWidth: 0 }, children: [
125
+ allowEmpty ? /* @__PURE__ */ jsxs(
126
+ "div",
127
+ {
128
+ role: "radio",
129
+ "aria-checked": noneSelected,
130
+ "aria-current": noneSelected ? "true" : void 0,
131
+ tabIndex: 0,
132
+ className: "erp-treepicker__none",
133
+ style: {
134
+ display: "flex",
135
+ alignItems: "center",
136
+ gap: "var(--wp-space-2)",
137
+ minHeight: "var(--wp-row-h-dense)",
138
+ padding: "0 var(--wp-space-2)",
139
+ borderRadius: "var(--wp-radius-sm)",
140
+ cursor: "pointer",
141
+ userSelect: "none"
142
+ },
143
+ onClick: () => setPendingId(null),
144
+ onKeyDown: (event) => {
145
+ if (event.key === "Enter" || event.key === " ") {
146
+ event.preventDefault();
147
+ setPendingId(null);
187
148
  }
188
- )
189
- ] })
190
- }
191
- )
149
+ },
150
+ children: [
151
+ /* @__PURE__ */ jsx(
152
+ Icon,
153
+ {
154
+ name: noneSelected ? "circle-dot" : "circle-x",
155
+ size: 16,
156
+ style: { flexShrink: 0, color: noneSelected ? "var(--wp-text-brand)" : "var(--wp-text-muted)" }
157
+ }
158
+ ),
159
+ /* @__PURE__ */ jsx(
160
+ "span",
161
+ {
162
+ className: "wp-body-sm",
163
+ style: { color: noneSelected ? "var(--wp-text-strong)" : "var(--wp-text-muted)" },
164
+ children: "(\uC5C6\uC74C)"
165
+ }
166
+ )
167
+ ]
168
+ }
169
+ ) : null,
170
+ /* @__PURE__ */ jsx(
171
+ TreeView,
172
+ {
173
+ nodes: sourceNodes,
174
+ selectedId: pendingId,
175
+ onSelect: (id) => setPendingId(id),
176
+ searchable: true,
177
+ excludeIds,
178
+ maxHeight: "320px"
179
+ }
180
+ )
181
+ ] })
192
182
  }
193
- ) : null
183
+ ) }) : null
194
184
  ] });
195
185
  }
196
186
  var TreePicker = boxed(TreePickerImpl, "TreePicker");
package/dist/index.d.ts CHANGED
@@ -1361,6 +1361,9 @@ declare function Divider({ style, className }: SduiBoxProps): React__default.JSX
1361
1361
 
1362
1362
  /**
1363
1363
  * Dialog + 표준 닫기 UX(Escape·배경 클릭). 레지스트리의 "Dialog"는 이 래퍼다.
1364
+ *
1365
+ * 겹침 처리는 전부 OverlayLayer가 맡는다 — 단일 오버레이 루트로 포털해 조상
1366
+ * CSS에 휘둘리지 않고, Escape는 맨 위 한 겹만 닫는다(overlay.tsx 주석 참고).
1364
1367
  */
1365
1368
  declare function SduiDialog(props: DialogProps): React.JSX.Element;
1366
1369
 
@@ -1746,6 +1749,37 @@ declare function useSduiRuntime(): SduiRuntime;
1746
1749
 
1747
1750
  declare function boxed<P extends object>(Inner: React__default.ComponentType<P>, displayName?: string): React__default.FC<P & SduiBoxProps>;
1748
1751
 
1752
+ /** 모달이 사는 단일 컨테이너. 없으면 만든다(비어 있으면 CSS가 display:none). */
1753
+ declare function getOverlayRoot(): HTMLElement | null;
1754
+ /**
1755
+ * 이 겹이 스택에 있는 동안 Escape를 받을 자격을 관리한다.
1756
+ * `active`가 참인 동안만 스택에 오르고, **맨 위일 때만** `onEscape`가 불린다.
1757
+ *
1758
+ * 포털을 쓰지 않는 팝오버(DatePicker)도 이 훅만 쓰면 순서가 맞는다.
1759
+ * `nodeRef`를 주면 문서 순서 판정에 쓰고, 없으면 등록 순서로 떨어진다.
1760
+ */
1761
+ declare function useOverlayLayer(active: boolean, onEscape?: () => void, nodeRef?: React__default.RefObject<HTMLElement | null>): {
1762
+ isTop: boolean;
1763
+ };
1764
+ /**
1765
+ * 겹을 담을 곳을 바꾼다. 기본은 body 밑 단일 루트지만, **화면 일부에 페이지를
1766
+ * 그려 보이는 곳**(편집기 미리보기·뷰어)은 자기 상자를 지정해야 한다 — 안 그러면
1767
+ * 미리본 다이얼로그의 오버레이가 편집기 전체를 덮는다(0.17.1에서 겪은 그 문제).
1768
+ * 그 상자에 `contain: paint`가 걸려 있으면 오버레이는 상자 안에 갇힌다.
1769
+ */
1770
+ declare const OverlayContainerContext: React__default.Context<HTMLElement | null>;
1771
+ /**
1772
+ * 모달 한 겹. 자식(보통 `<Dialog>`)을 단일 오버레이 루트로 포털한다.
1773
+ *
1774
+ * - 배경(.wp-overlay) 클릭 시 닫기 — 내용 클릭은 무시한다
1775
+ * - Escape는 맨 위 겹에서만 먹는다
1776
+ * - 아래 겹은 `inert`로 잠근다 — 탭·클릭이 가려진 창으로 새지 않게
1777
+ */
1778
+ declare function OverlayLayer({ onClose, children, }: {
1779
+ onClose?: () => void;
1780
+ children: React__default.ReactNode;
1781
+ }): React__default.ReactElement | null;
1782
+
1749
1783
  interface Props {
1750
1784
  name: string;
1751
1785
  children: React__default.ReactNode;
@@ -1795,4 +1829,4 @@ interface ImageViewerProps {
1795
1829
  */
1796
1830
  declare function ImageViewer({ src, alt, open, onClose }: ImageViewerProps): React__default.ReactPortal | null;
1797
1831
 
1798
- export { Actions, type ActionsProps, Alert, type AlertProps, Avatar, type AvatarProps, Badge, type BadgeProps, type BadgeTone, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Button, type ButtonProps, Can, type CanProps, Card, type CardProps, type CascadingSearchOption, Checkbox, type CheckboxProps, Col, type ColProps, type CustomActionHandler, type CustomValidator, DatePickerProps, DateRangePickerProps, Dialog, type DialogProps, Divider, EmptyState, type EmptyStateProps, Field, type FieldProps, Grid, type GridCardConfig, type GridCardRow, type GridProps, Icon, IconButton, type IconButtonProps, type IconProps, ImageViewer, type ImageViewerProps, Input, type InputProps, type Justify, type KeyValueItem, KeyValueList, type KeyValueListProps, type MeterTone, NodeErrorBoundary, PageHead, type PageHeadProps, Pagination, type PaginationProps, ProgressBar, type ProgressBarProps, Radio, type RadioProps, RemoteSearchResults, type RemoteSearchResultsProps, ResultTable, type ResultTableColumn, type ResultTableProps, Row, type RowProps, SduiAction, type SduiAdapter, type SduiAssetContext, type SduiAssetResolver, type SduiBadgeCell, SduiBoxProps, type SduiCanFn, type SduiCanQuery, type SduiCanSetting, type SduiCell, SduiColumnsScale, SduiContainerLayoutProps, type SduiDateCell, SduiDialog, type SduiDispatch, SduiForm, type SduiFormConfig, type SduiFormProps, SduiGapScale, type SduiGridCard, type SduiGridCardAction, type SduiGridCardRow, type SduiIconCell, type SduiImageCell, type SduiLinkCell, type SduiNumberCell, SduiPage, SduiPermissionProvider, type SduiProgressCell, SduiPropValue, type SduiRegistry, SduiRenderer, type SduiRendererProps, type SduiRowCondition, SduiRule, type SduiRuntime, type SduiSearchAction, type SduiSearchColumn, SduiSearchDataProvider, SduiSearchResults, type SduiSearchResultsProps, type SduiTagCell, SduiTextAlignScale, type SduiTextCell, type SduiUseSpec, SduiWeightScale, type SearchAction, type SearchColumn, type SearchFetcher, type SearchField, type SearchFilter, type SearchOption, type SearchPage, SearchResults, type SearchResultsProps, type SearchRow, SearchToolbar, type SearchValues, type SearchViewItem, Section, type SectionProps, SegmentedControl, type SegmentedControlProps, type SegmentedItem, Select, type SelectOption, type SelectOptionRecord, type SelectProps, Show, type ShowProps, SidebarNav, type SidebarNavItem, type SidebarNavProps, type SidebarNavSection, type SortDir, SortHandle, type SortHandleProps, SortableList, type SortableListProps, Spacer, type SpacerProps, type SpecIssue, Spinner, type SpinnerProps, StatCard, type StatCardProps, Switch, type SwitchProps, type TabItem, Table, type TableColumn, type TableProps, Tabs, type TabsProps, Tag, type TagProps, Text, type TextPreset, type TextProps, Textarea, type TextareaProps, Toast, type ToastProps, Tooltip, type TooltipProps, boxed, cascadingValuePaths, cellRenderer, cleanSearchValues, createRegistry, defaultAdapter, defaultRegistry, imageRenderer, includesLooseSearch, joinMultiValue, normalizePageSizes, normalizeSearchText, searchFieldKeys, searchValuesFromParams, sortableDropIndex, sortableMove, splitMultiValue, useRemoteSearchPage, useSduiAssetResolver, useSduiCan, useSduiDispatch, useSduiRuntime, useSduiSandbox, useSearchFetcher, validateSpec };
1832
+ export { Actions, type ActionsProps, Alert, type AlertProps, Avatar, type AvatarProps, Badge, type BadgeProps, type BadgeTone, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Button, type ButtonProps, Can, type CanProps, Card, type CardProps, type CascadingSearchOption, Checkbox, type CheckboxProps, Col, type ColProps, type CustomActionHandler, type CustomValidator, DatePickerProps, DateRangePickerProps, Dialog, type DialogProps, Divider, EmptyState, type EmptyStateProps, Field, type FieldProps, Grid, type GridCardConfig, type GridCardRow, type GridProps, Icon, IconButton, type IconButtonProps, type IconProps, ImageViewer, type ImageViewerProps, Input, type InputProps, type Justify, type KeyValueItem, KeyValueList, type KeyValueListProps, type MeterTone, NodeErrorBoundary, OverlayContainerContext, OverlayLayer, PageHead, type PageHeadProps, Pagination, type PaginationProps, ProgressBar, type ProgressBarProps, Radio, type RadioProps, RemoteSearchResults, type RemoteSearchResultsProps, ResultTable, type ResultTableColumn, type ResultTableProps, Row, type RowProps, SduiAction, type SduiAdapter, type SduiAssetContext, type SduiAssetResolver, type SduiBadgeCell, SduiBoxProps, type SduiCanFn, type SduiCanQuery, type SduiCanSetting, type SduiCell, SduiColumnsScale, SduiContainerLayoutProps, type SduiDateCell, SduiDialog, type SduiDispatch, SduiForm, type SduiFormConfig, type SduiFormProps, SduiGapScale, type SduiGridCard, type SduiGridCardAction, type SduiGridCardRow, type SduiIconCell, type SduiImageCell, type SduiLinkCell, type SduiNumberCell, SduiPage, SduiPermissionProvider, type SduiProgressCell, SduiPropValue, type SduiRegistry, SduiRenderer, type SduiRendererProps, type SduiRowCondition, SduiRule, type SduiRuntime, type SduiSearchAction, type SduiSearchColumn, SduiSearchDataProvider, SduiSearchResults, type SduiSearchResultsProps, type SduiTagCell, SduiTextAlignScale, type SduiTextCell, type SduiUseSpec, SduiWeightScale, type SearchAction, type SearchColumn, type SearchFetcher, type SearchField, type SearchFilter, type SearchOption, type SearchPage, SearchResults, type SearchResultsProps, type SearchRow, SearchToolbar, type SearchValues, type SearchViewItem, Section, type SectionProps, SegmentedControl, type SegmentedControlProps, type SegmentedItem, Select, type SelectOption, type SelectOptionRecord, type SelectProps, Show, type ShowProps, SidebarNav, type SidebarNavItem, type SidebarNavProps, type SidebarNavSection, type SortDir, SortHandle, type SortHandleProps, SortableList, type SortableListProps, Spacer, type SpacerProps, type SpecIssue, Spinner, type SpinnerProps, StatCard, type StatCardProps, Switch, type SwitchProps, type TabItem, Table, type TableColumn, type TableProps, Tabs, type TabsProps, Tag, type TagProps, Text, type TextPreset, type TextProps, Textarea, type TextareaProps, Toast, type ToastProps, Tooltip, type TooltipProps, boxed, cascadingValuePaths, cellRenderer, cleanSearchValues, createRegistry, defaultAdapter, defaultRegistry, getOverlayRoot, imageRenderer, includesLooseSearch, joinMultiValue, normalizePageSizes, normalizeSearchText, searchFieldKeys, searchValuesFromParams, sortableDropIndex, sortableMove, splitMultiValue, useOverlayLayer, useRemoteSearchPage, useSduiAssetResolver, useSduiCan, useSduiDispatch, useSduiRuntime, useSduiSandbox, useSearchFetcher, validateSpec };
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
- import { DEV, HoistedStyle, isSafeNavigationTarget, useSduiAdapter, hostFetchOf, DateRangePicker, DatePicker, useSduiSandbox, resolveAdapter, assetResolverOf, SduiAdapterContext } from './chunk-Q2EUB5UV.js';
2
- export { DatePicker, DateRangePicker, defaultAdapter, useSduiAssetResolver, useSduiSandbox } from './chunk-Q2EUB5UV.js';
1
+ import { DEV, HoistedStyle, isSafeNavigationTarget, useSduiAdapter, hostFetchOf, DateRangePicker, DatePicker, useSduiSandbox, resolveAdapter, assetResolverOf, SduiAdapterContext } from './chunk-HYSYJO3Q.js';
2
+ export { DatePicker, DateRangePicker, defaultAdapter, useSduiAssetResolver, useSduiSandbox } from './chunk-HYSYJO3Q.js';
3
3
  import { ImageViewer } from './chunk-THEP3VWY.js';
4
4
  export { ImageViewer } from './chunk-THEP3VWY.js';
5
+ import { OverlayLayer } from './chunk-JOAUXZZA.js';
6
+ export { OverlayContainerContext, OverlayLayer, getOverlayRoot, useOverlayLayer } from './chunk-JOAUXZZA.js';
5
7
  import { normalizeSearchFields, searchValuesFromParams, splitMultiValue, includesLooseSearch, cascadingValuePaths, SearchToolbar, searchFieldKeys, Popover } from './chunk-GZB6YLUI.js';
6
8
  export { SearchToolbar, cascadingValuePaths, cleanSearchValues, includesLooseSearch, joinMultiValue, normalizeSearchText, searchFieldKeys, searchValuesFromParams, splitMultiValue } from './chunk-GZB6YLUI.js';
7
9
  import { EmptyState, Button, Checkbox, SegmentedControl, Spinner, Select, Pagination, Icon, Card, Badge, ProgressBar, Tag, Toast, Textarea, Tabs, Table, Switch, StatCard, Input, IconButton, Field, Alert, warnOnce, Dialog } from './chunk-WIDDHR7L.js';
@@ -1356,27 +1358,7 @@ function RemoteSearchResultsImpl({
1356
1358
  var SduiSearchResults = boxed(SduiSearchResultsImpl, "SduiSearchResults");
1357
1359
  var RemoteSearchResults = boxed(RemoteSearchResultsImpl, "RemoteSearchResults");
1358
1360
  function SduiDialog(props) {
1359
- const { onClose } = props;
1360
- useEffect(() => {
1361
- if (!onClose) return;
1362
- const onKeyDown = (event) => {
1363
- if (event.key === "Escape") onClose();
1364
- };
1365
- document.addEventListener("keydown", onKeyDown);
1366
- return () => document.removeEventListener("keydown", onKeyDown);
1367
- }, [onClose]);
1368
- return /* @__PURE__ */ jsx(
1369
- "div",
1370
- {
1371
- style: { position: "fixed", inset: 0, zIndex: "var(--wp-z-dialog)" },
1372
- onClick: (event) => {
1373
- if (onClose && event.target instanceof HTMLElement && event.target.classList.contains("wp-overlay")) {
1374
- onClose();
1375
- }
1376
- },
1377
- children: /* @__PURE__ */ jsx(Dialog, { ...props })
1378
- }
1379
- );
1361
+ return /* @__PURE__ */ jsx(OverlayLayer, { onClose: props.onClose, children: /* @__PURE__ */ jsx(Dialog, { ...props }) });
1380
1362
  }
1381
1363
  var SduiRuntimeContext = createContext(null);
1382
1364
  function useSduiRuntime() {
package/dist/rich.js CHANGED
@@ -1,5 +1,6 @@
1
- import { useSduiAssetResolver, HoistedStyle } from './chunk-Q2EUB5UV.js';
2
- export { DatePicker, DateRangePicker } from './chunk-Q2EUB5UV.js';
1
+ import { useSduiAssetResolver, HoistedStyle } from './chunk-HYSYJO3Q.js';
2
+ export { DatePicker, DateRangePicker } from './chunk-HYSYJO3Q.js';
3
+ import './chunk-JOAUXZZA.js';
3
4
  import { Spinner, EmptyState } from './chunk-WIDDHR7L.js';
4
5
  import './chunk-SAN4IX6N.js';
5
6
  import { useRef, useCallback, useMemo, useState, useEffect, useLayoutEffect } from 'react';
package/dist/styles.css CHANGED
@@ -794,6 +794,16 @@
794
794
  .wp-pager__gap { color: var(--wp-text-subtle); padding: 0 4px; }
795
795
 
796
796
  /* ---------- 다이얼로그 ---------- */
797
+ /* 겹쳐 뜨는 것들이 모이는 단일 컨테이너(overlay.tsx가 body에 만든다).
798
+ 비어 있으면 아예 없는 것처럼 굴어야 페이지가 평소대로 보인다 — :empty로 끄고,
799
+ 떠 있을 때도 pointer-events는 겹(.wp-overlay-layer)에서만 살린다.
800
+ z-index는 여기 한 번만 건다. 겹끼리는 문서 순서대로 쌓인다(나중 것이 위). */
801
+ #wp-overlay-root {
802
+ position: fixed; inset: 0; z-index: var(--wp-z-dialog); pointer-events: none;
803
+ }
804
+ #wp-overlay-root:empty { display: none; }
805
+ .wp-overlay-layer { position: fixed; inset: 0; pointer-events: auto; }
806
+
797
807
  .wp-overlay {
798
808
  position: absolute; inset: 0; background: var(--wp-surface-overlay);
799
809
  display: flex; align-items: flex-start; justify-content: center;
@@ -1,4 +1,5 @@
1
- export { SearchableMultiPicker } from '../chunk-J6OHRAPS.js';
1
+ export { SearchableMultiPicker } from '../chunk-54UHTU6R.js';
2
+ import '../chunk-JOAUXZZA.js';
2
3
  import '../chunk-GZB6YLUI.js';
3
4
  import '../chunk-WIDDHR7L.js';
4
5
  import '../chunk-SAN4IX6N.js';
@@ -1,5 +1,6 @@
1
- export { TreePicker } from '../chunk-WVBU3SZM.js';
1
+ export { TreePicker } from '../chunk-RK64BOF5.js';
2
2
  import '../chunk-PYSTET2C.js';
3
+ import '../chunk-JOAUXZZA.js';
3
4
  import '../chunk-GZB6YLUI.js';
4
5
  import '../chunk-WIDDHR7L.js';
5
6
  import '../chunk-SAN4IX6N.js';
@@ -1,5 +1,5 @@
1
- import { SearchableMultiPicker } from '../chunk-J6OHRAPS.js';
2
- export { SearchableMultiPicker } from '../chunk-J6OHRAPS.js';
1
+ import { SearchableMultiPicker } from '../chunk-54UHTU6R.js';
2
+ export { SearchableMultiPicker } from '../chunk-54UHTU6R.js';
3
3
  import { SecureScreen } from '../chunk-IIRENCDS.js';
4
4
  export { SecureScreen } from '../chunk-IIRENCDS.js';
5
5
  import { StackedMeter } from '../chunk-BMZKZGJF.js';
@@ -12,8 +12,8 @@ import { TagMultiSelect } from '../chunk-QDEZXA34.js';
12
12
  export { TagMultiSelect } from '../chunk-QDEZXA34.js';
13
13
  import { Timeline } from '../chunk-EFYEOT47.js';
14
14
  export { Timeline } from '../chunk-EFYEOT47.js';
15
- import { TreePicker } from '../chunk-WVBU3SZM.js';
16
- export { TreePicker } from '../chunk-WVBU3SZM.js';
15
+ import { TreePicker } from '../chunk-RK64BOF5.js';
16
+ export { TreePicker } from '../chunk-RK64BOF5.js';
17
17
  import { TreeView } from '../chunk-PYSTET2C.js';
18
18
  export { TreeView, buildTreeNodes } from '../chunk-PYSTET2C.js';
19
19
  import { EventCalendar } from '../chunk-C3JY6GA3.js';
@@ -33,6 +33,7 @@ export { PeriodFilter } from '../chunk-M3TDEPD2.js';
33
33
  import { PrintSheet } from '../chunk-MYJ2KEIP.js';
34
34
  export { PrintSheet } from '../chunk-MYJ2KEIP.js';
35
35
  import '../chunk-THEP3VWY.js';
36
+ import '../chunk-JOAUXZZA.js';
36
37
  export { AutocompleteField, toAutocompleteOptions } from '../chunk-3MS4UWWZ.js';
37
38
  import '../chunk-GZB6YLUI.js';
38
39
  import { ColumnBoard } from '../chunk-C4JRSZGS.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipco/sdui",
3
- "version": "0.17.1",
3
+ "version": "0.18.0",
4
4
  "description": "WIPCO 서버 주도 UI(SDUI) 렌더러 — JSON 페이지 스펙을 React 컴포넌트로 렌더링",
5
5
  "keywords": [
6
6
  "sdui",