@x-plat/design-system 0.14.13 → 0.14.15

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.
@@ -55,26 +55,27 @@ var findScrollParent = (el) => {
55
55
  }
56
56
  return null;
57
57
  };
58
+ var TRIGGER_GAP = 8;
58
59
  var computePlacement = (triggerRect, popW, popH, clip) => {
59
- const roomBelow = clip.bottom - triggerRect.bottom;
60
- const roomAbove = triggerRect.top - clip.top;
60
+ const roomBelow = clip.bottom - triggerRect.bottom - TRIGGER_GAP;
61
+ const roomAbove = triggerRect.top - clip.top - TRIGGER_GAP;
61
62
  let direction;
62
63
  let top;
63
64
  let maxHeight = null;
64
65
  if (popH <= roomBelow) {
65
66
  direction = "bottom";
66
- top = triggerRect.bottom;
67
+ top = triggerRect.bottom + TRIGGER_GAP;
67
68
  } else if (popH <= roomAbove) {
68
69
  direction = "top";
69
- top = triggerRect.top - popH;
70
+ top = triggerRect.top - TRIGGER_GAP - popH;
70
71
  } else {
71
72
  const room = Math.max(roomBelow, roomAbove, 0);
72
73
  if (roomBelow >= roomAbove) {
73
74
  direction = "bottom";
74
- top = triggerRect.bottom;
75
+ top = triggerRect.bottom + TRIGGER_GAP;
75
76
  } else {
76
77
  direction = "top";
77
- top = triggerRect.top - room;
78
+ top = triggerRect.top - TRIGGER_GAP - room;
78
79
  }
79
80
  maxHeight = room;
80
81
  }
@@ -229,22 +230,22 @@ var useEscapeKey = (enabled, onEscape) => {
229
230
  return () => document.removeEventListener("keydown", onKey);
230
231
  }, [enabled, onEscape]);
231
232
  };
232
- var useCloseOnContainerScroll = (enabled, popRef, onClose) => {
233
+ var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
233
234
  const onCloseRef = import_react3.default.useRef(onClose);
234
235
  onCloseRef.current = onClose;
235
236
  import_react3.default.useEffect(() => {
236
- if (!enabled) return;
237
- const onScroll = (e) => {
238
- const target = e.target;
239
- if (target === document || target === document.documentElement || target === document.body) {
240
- return;
241
- }
242
- if (target instanceof Node && popRef.current?.contains(target)) return;
243
- onCloseRef.current();
244
- };
245
- window.addEventListener("scroll", onScroll, { passive: true, capture: true });
246
- return () => window.removeEventListener("scroll", onScroll, true);
247
- }, [enabled, popRef]);
237
+ const trigger = triggerRef.current;
238
+ if (!enabled || !trigger) return;
239
+ const observer = new IntersectionObserver(
240
+ ([entry]) => {
241
+ if (!entry.isIntersecting) onCloseRef.current();
242
+ },
243
+ // 그릇이 없으면 root: null → 뷰포트 기준
244
+ { root: findScrollParent(trigger), threshold: 0 }
245
+ );
246
+ observer.observe(trigger);
247
+ return () => observer.disconnect();
248
+ }, [enabled, triggerRef]);
248
249
  };
249
250
 
250
251
  // ../../node_modules/clsx/dist/clsx.mjs
@@ -274,7 +275,7 @@ var Dropdown = (props) => {
274
275
  const menuRef = import_react4.default.useRef(null);
275
276
  useAutoPosition_default(triggerRef, menuRef, mounted);
276
277
  useClickOutside_default([triggerRef, menuRef], () => setIsOpen(false), isOpen);
277
- useCloseOnContainerScroll(isOpen, menuRef, () => setIsOpen(false));
278
+ useCloseWhenTriggerHidden(isOpen, triggerRef, () => setIsOpen(false));
278
279
  useEscapeKey(isOpen, () => {
279
280
  setIsOpen(false);
280
281
  triggerRef.current?.focus({ preventScroll: true });
@@ -19,11 +19,11 @@
19
19
  box-shadow: var(--semantic-shadow-md);
20
20
  padding: var(--spacing-space-1) 0;
21
21
  opacity: 0;
22
- transform: translateY(-4px);
22
+ transform: translateY(4px);
23
23
  transition: opacity 0.15s ease, transform 0.15s ease;
24
24
  }
25
25
  .lib-xplat-dropdown-menu.bottom {
26
- transform: translateY(4px);
26
+ transform: translateY(-4px);
27
27
  }
28
28
  .lib-xplat-dropdown-menu.visible {
29
29
  opacity: 1;
@@ -19,26 +19,27 @@ var findScrollParent = (el) => {
19
19
  }
20
20
  return null;
21
21
  };
22
+ var TRIGGER_GAP = 8;
22
23
  var computePlacement = (triggerRect, popW, popH, clip) => {
23
- const roomBelow = clip.bottom - triggerRect.bottom;
24
- const roomAbove = triggerRect.top - clip.top;
24
+ const roomBelow = clip.bottom - triggerRect.bottom - TRIGGER_GAP;
25
+ const roomAbove = triggerRect.top - clip.top - TRIGGER_GAP;
25
26
  let direction;
26
27
  let top;
27
28
  let maxHeight = null;
28
29
  if (popH <= roomBelow) {
29
30
  direction = "bottom";
30
- top = triggerRect.bottom;
31
+ top = triggerRect.bottom + TRIGGER_GAP;
31
32
  } else if (popH <= roomAbove) {
32
33
  direction = "top";
33
- top = triggerRect.top - popH;
34
+ top = triggerRect.top - TRIGGER_GAP - popH;
34
35
  } else {
35
36
  const room = Math.max(roomBelow, roomAbove, 0);
36
37
  if (roomBelow >= roomAbove) {
37
38
  direction = "bottom";
38
- top = triggerRect.bottom;
39
+ top = triggerRect.bottom + TRIGGER_GAP;
39
40
  } else {
40
41
  direction = "top";
41
- top = triggerRect.top - room;
42
+ top = triggerRect.top - TRIGGER_GAP - room;
42
43
  }
43
44
  maxHeight = room;
44
45
  }
@@ -193,22 +194,22 @@ var useEscapeKey = (enabled, onEscape) => {
193
194
  return () => document.removeEventListener("keydown", onKey);
194
195
  }, [enabled, onEscape]);
195
196
  };
196
- var useCloseOnContainerScroll = (enabled, popRef, onClose) => {
197
+ var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
197
198
  const onCloseRef = React3.useRef(onClose);
198
199
  onCloseRef.current = onClose;
199
200
  React3.useEffect(() => {
200
- if (!enabled) return;
201
- const onScroll = (e) => {
202
- const target = e.target;
203
- if (target === document || target === document.documentElement || target === document.body) {
204
- return;
205
- }
206
- if (target instanceof Node && popRef.current?.contains(target)) return;
207
- onCloseRef.current();
208
- };
209
- window.addEventListener("scroll", onScroll, { passive: true, capture: true });
210
- return () => window.removeEventListener("scroll", onScroll, true);
211
- }, [enabled, popRef]);
201
+ const trigger = triggerRef.current;
202
+ if (!enabled || !trigger) return;
203
+ const observer = new IntersectionObserver(
204
+ ([entry]) => {
205
+ if (!entry.isIntersecting) onCloseRef.current();
206
+ },
207
+ // 그릇이 없으면 root: null → 뷰포트 기준
208
+ { root: findScrollParent(trigger), threshold: 0 }
209
+ );
210
+ observer.observe(trigger);
211
+ return () => observer.disconnect();
212
+ }, [enabled, triggerRef]);
212
213
  };
213
214
 
214
215
  // ../../node_modules/clsx/dist/clsx.mjs
@@ -238,7 +239,7 @@ var Dropdown = (props) => {
238
239
  const menuRef = React4.useRef(null);
239
240
  useAutoPosition_default(triggerRef, menuRef, mounted);
240
241
  useClickOutside_default([triggerRef, menuRef], () => setIsOpen(false), isOpen);
241
- useCloseOnContainerScroll(isOpen, menuRef, () => setIsOpen(false));
242
+ useCloseWhenTriggerHidden(isOpen, triggerRef, () => setIsOpen(false));
242
243
  useEscapeKey(isOpen, () => {
243
244
  setIsOpen(false);
244
245
  triggerRef.current?.focus({ preventScroll: true });
@@ -35,11 +35,16 @@ __export(Modal_exports, {
35
35
  module.exports = __toCommonJS(Modal_exports);
36
36
 
37
37
  // src/components/Modal/Modal.tsx
38
- var import_react2 = __toESM(require("react"), 1);
38
+ var import_react3 = __toESM(require("react"), 1);
39
39
  var import_react_dom = require("react-dom");
40
40
 
41
41
  // src/tokens/hooks/useOverlayKeyboard.ts
42
+ var import_react2 = __toESM(require("react"), 1);
43
+
44
+ // src/tokens/hooks/useAutoPosition.ts
42
45
  var import_react = __toESM(require("react"), 1);
46
+
47
+ // src/tokens/hooks/useOverlayKeyboard.ts
43
48
  var FOCUSABLE = [
44
49
  "a[href]",
45
50
  "button:not([disabled])",
@@ -53,7 +58,7 @@ var focusableIn = (el) => Array.from(el.querySelectorAll(FOCUSABLE)).filter(
53
58
  (n) => n.offsetWidth > 0 || n.offsetHeight > 0 || n === document.activeElement
54
59
  );
55
60
  var useEscapeKey = (enabled, onEscape) => {
56
- import_react.default.useEffect(() => {
61
+ import_react2.default.useEffect(() => {
57
62
  if (!enabled) return;
58
63
  const onKey = (e) => {
59
64
  if (e.key !== "Escape") return;
@@ -67,8 +72,8 @@ var useEscapeKey = (enabled, onEscape) => {
67
72
  };
68
73
  var useFocusTrap = (containerRef, enabled, options = {}) => {
69
74
  const { trap = false, initialFocus } = options;
70
- const restoreRef = import_react.default.useRef(null);
71
- import_react.default.useEffect(() => {
75
+ const restoreRef = import_react2.default.useRef(null);
76
+ import_react2.default.useEffect(() => {
72
77
  if (!enabled) return;
73
78
  restoreRef.current = document.activeElement;
74
79
  const raf = requestAnimationFrame(() => {
@@ -86,7 +91,7 @@ var useFocusTrap = (containerRef, enabled, options = {}) => {
86
91
  if (prev && document.contains(prev)) prev.focus({ preventScroll: true });
87
92
  };
88
93
  }, [enabled, containerRef, initialFocus]);
89
- import_react.default.useEffect(() => {
94
+ import_react2.default.useEffect(() => {
90
95
  if (!enabled || !trap) return;
91
96
  const onKey = (e) => {
92
97
  if (e.key !== "Tab") return;
@@ -134,13 +139,13 @@ var import_jsx_runtime = require("react/jsx-runtime");
134
139
  var ANIMATION_DURATION_MS = 200;
135
140
  var Modal = (props) => {
136
141
  const { isOpen, onClose, children } = props;
137
- const [mounted, setMounted] = import_react2.default.useState(false);
138
- const [visible, setVisible] = import_react2.default.useState(false);
139
- const [settled, setSettled] = import_react2.default.useState(false);
140
- const boxRef = import_react2.default.useRef(null);
142
+ const [mounted, setMounted] = import_react3.default.useState(false);
143
+ const [visible, setVisible] = import_react3.default.useState(false);
144
+ const [settled, setSettled] = import_react3.default.useState(false);
145
+ const boxRef = import_react3.default.useRef(null);
141
146
  useEscapeKey(isOpen, onClose);
142
147
  useFocusTrap(boxRef, isOpen && mounted, { trap: true });
143
- import_react2.default.useEffect(() => {
148
+ import_react3.default.useEffect(() => {
144
149
  if (isOpen) {
145
150
  setMounted(true);
146
151
  const show = setTimeout(() => setVisible(true), 1);
@@ -1,9 +1,14 @@
1
1
  // src/components/Modal/Modal.tsx
2
- import React2 from "react";
2
+ import React3 from "react";
3
3
  import { createPortal } from "react-dom";
4
4
 
5
5
  // src/tokens/hooks/useOverlayKeyboard.ts
6
+ import React2 from "react";
7
+
8
+ // src/tokens/hooks/useAutoPosition.ts
6
9
  import React from "react";
10
+
11
+ // src/tokens/hooks/useOverlayKeyboard.ts
7
12
  var FOCUSABLE = [
8
13
  "a[href]",
9
14
  "button:not([disabled])",
@@ -17,7 +22,7 @@ var focusableIn = (el) => Array.from(el.querySelectorAll(FOCUSABLE)).filter(
17
22
  (n) => n.offsetWidth > 0 || n.offsetHeight > 0 || n === document.activeElement
18
23
  );
19
24
  var useEscapeKey = (enabled, onEscape) => {
20
- React.useEffect(() => {
25
+ React2.useEffect(() => {
21
26
  if (!enabled) return;
22
27
  const onKey = (e) => {
23
28
  if (e.key !== "Escape") return;
@@ -31,8 +36,8 @@ var useEscapeKey = (enabled, onEscape) => {
31
36
  };
32
37
  var useFocusTrap = (containerRef, enabled, options = {}) => {
33
38
  const { trap = false, initialFocus } = options;
34
- const restoreRef = React.useRef(null);
35
- React.useEffect(() => {
39
+ const restoreRef = React2.useRef(null);
40
+ React2.useEffect(() => {
36
41
  if (!enabled) return;
37
42
  restoreRef.current = document.activeElement;
38
43
  const raf = requestAnimationFrame(() => {
@@ -50,7 +55,7 @@ var useFocusTrap = (containerRef, enabled, options = {}) => {
50
55
  if (prev && document.contains(prev)) prev.focus({ preventScroll: true });
51
56
  };
52
57
  }, [enabled, containerRef, initialFocus]);
53
- React.useEffect(() => {
58
+ React2.useEffect(() => {
54
59
  if (!enabled || !trap) return;
55
60
  const onKey = (e) => {
56
61
  if (e.key !== "Tab") return;
@@ -98,13 +103,13 @@ import { jsx } from "react/jsx-runtime";
98
103
  var ANIMATION_DURATION_MS = 200;
99
104
  var Modal = (props) => {
100
105
  const { isOpen, onClose, children } = props;
101
- const [mounted, setMounted] = React2.useState(false);
102
- const [visible, setVisible] = React2.useState(false);
103
- const [settled, setSettled] = React2.useState(false);
104
- const boxRef = React2.useRef(null);
106
+ const [mounted, setMounted] = React3.useState(false);
107
+ const [visible, setVisible] = React3.useState(false);
108
+ const [settled, setSettled] = React3.useState(false);
109
+ const boxRef = React3.useRef(null);
105
110
  useEscapeKey(isOpen, onClose);
106
111
  useFocusTrap(boxRef, isOpen && mounted, { trap: true });
107
- React2.useEffect(() => {
112
+ React3.useEffect(() => {
108
113
  if (isOpen) {
109
114
  setMounted(true);
110
115
  const show = setTimeout(() => setVisible(true), 1);
@@ -55,26 +55,27 @@ var findScrollParent = (el) => {
55
55
  }
56
56
  return null;
57
57
  };
58
+ var TRIGGER_GAP = 8;
58
59
  var computePlacement = (triggerRect, popW, popH, clip) => {
59
- const roomBelow = clip.bottom - triggerRect.bottom;
60
- const roomAbove = triggerRect.top - clip.top;
60
+ const roomBelow = clip.bottom - triggerRect.bottom - TRIGGER_GAP;
61
+ const roomAbove = triggerRect.top - clip.top - TRIGGER_GAP;
61
62
  let direction;
62
63
  let top;
63
64
  let maxHeight = null;
64
65
  if (popH <= roomBelow) {
65
66
  direction = "bottom";
66
- top = triggerRect.bottom;
67
+ top = triggerRect.bottom + TRIGGER_GAP;
67
68
  } else if (popH <= roomAbove) {
68
69
  direction = "top";
69
- top = triggerRect.top - popH;
70
+ top = triggerRect.top - TRIGGER_GAP - popH;
70
71
  } else {
71
72
  const room = Math.max(roomBelow, roomAbove, 0);
72
73
  if (roomBelow >= roomAbove) {
73
74
  direction = "bottom";
74
- top = triggerRect.bottom;
75
+ top = triggerRect.bottom + TRIGGER_GAP;
75
76
  } else {
76
77
  direction = "top";
77
- top = triggerRect.top - room;
78
+ top = triggerRect.top - TRIGGER_GAP - room;
78
79
  }
79
80
  maxHeight = room;
80
81
  }
@@ -229,22 +230,22 @@ var useEscapeKey = (enabled, onEscape) => {
229
230
  return () => document.removeEventListener("keydown", onKey);
230
231
  }, [enabled, onEscape]);
231
232
  };
232
- var useCloseOnContainerScroll = (enabled, popRef, onClose) => {
233
+ var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
233
234
  const onCloseRef = import_react3.default.useRef(onClose);
234
235
  onCloseRef.current = onClose;
235
236
  import_react3.default.useEffect(() => {
236
- if (!enabled) return;
237
- const onScroll = (e) => {
238
- const target = e.target;
239
- if (target === document || target === document.documentElement || target === document.body) {
240
- return;
241
- }
242
- if (target instanceof Node && popRef.current?.contains(target)) return;
243
- onCloseRef.current();
244
- };
245
- window.addEventListener("scroll", onScroll, { passive: true, capture: true });
246
- return () => window.removeEventListener("scroll", onScroll, true);
247
- }, [enabled, popRef]);
237
+ const trigger = triggerRef.current;
238
+ if (!enabled || !trigger) return;
239
+ const observer = new IntersectionObserver(
240
+ ([entry]) => {
241
+ if (!entry.isIntersecting) onCloseRef.current();
242
+ },
243
+ // 그릇이 없으면 root: null → 뷰포트 기준
244
+ { root: findScrollParent(trigger), threshold: 0 }
245
+ );
246
+ observer.observe(trigger);
247
+ return () => observer.disconnect();
248
+ }, [enabled, triggerRef]);
248
249
  };
249
250
 
250
251
  // ../../node_modules/clsx/dist/clsx.mjs
@@ -272,7 +273,7 @@ var PopOver = (props) => {
272
273
  const [localOpen, setLocalOpen] = import_react4.default.useState(false);
273
274
  const [eventTrigger, setEventTrigger] = import_react4.default.useState(false);
274
275
  useClickOutside_default([popRef, triggerRef], onClose, isOpen);
275
- useCloseOnContainerScroll(isOpen, popRef, onClose);
276
+ useCloseWhenTriggerHidden(isOpen, triggerRef, onClose);
276
277
  useEscapeKey(isOpen, () => {
277
278
  onClose();
278
279
  triggerRef.current?.focus({ preventScroll: true });
@@ -19,26 +19,27 @@ var findScrollParent = (el) => {
19
19
  }
20
20
  return null;
21
21
  };
22
+ var TRIGGER_GAP = 8;
22
23
  var computePlacement = (triggerRect, popW, popH, clip) => {
23
- const roomBelow = clip.bottom - triggerRect.bottom;
24
- const roomAbove = triggerRect.top - clip.top;
24
+ const roomBelow = clip.bottom - triggerRect.bottom - TRIGGER_GAP;
25
+ const roomAbove = triggerRect.top - clip.top - TRIGGER_GAP;
25
26
  let direction;
26
27
  let top;
27
28
  let maxHeight = null;
28
29
  if (popH <= roomBelow) {
29
30
  direction = "bottom";
30
- top = triggerRect.bottom;
31
+ top = triggerRect.bottom + TRIGGER_GAP;
31
32
  } else if (popH <= roomAbove) {
32
33
  direction = "top";
33
- top = triggerRect.top - popH;
34
+ top = triggerRect.top - TRIGGER_GAP - popH;
34
35
  } else {
35
36
  const room = Math.max(roomBelow, roomAbove, 0);
36
37
  if (roomBelow >= roomAbove) {
37
38
  direction = "bottom";
38
- top = triggerRect.bottom;
39
+ top = triggerRect.bottom + TRIGGER_GAP;
39
40
  } else {
40
41
  direction = "top";
41
- top = triggerRect.top - room;
42
+ top = triggerRect.top - TRIGGER_GAP - room;
42
43
  }
43
44
  maxHeight = room;
44
45
  }
@@ -193,22 +194,22 @@ var useEscapeKey = (enabled, onEscape) => {
193
194
  return () => document.removeEventListener("keydown", onKey);
194
195
  }, [enabled, onEscape]);
195
196
  };
196
- var useCloseOnContainerScroll = (enabled, popRef, onClose) => {
197
+ var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
197
198
  const onCloseRef = React3.useRef(onClose);
198
199
  onCloseRef.current = onClose;
199
200
  React3.useEffect(() => {
200
- if (!enabled) return;
201
- const onScroll = (e) => {
202
- const target = e.target;
203
- if (target === document || target === document.documentElement || target === document.body) {
204
- return;
205
- }
206
- if (target instanceof Node && popRef.current?.contains(target)) return;
207
- onCloseRef.current();
208
- };
209
- window.addEventListener("scroll", onScroll, { passive: true, capture: true });
210
- return () => window.removeEventListener("scroll", onScroll, true);
211
- }, [enabled, popRef]);
201
+ const trigger = triggerRef.current;
202
+ if (!enabled || !trigger) return;
203
+ const observer = new IntersectionObserver(
204
+ ([entry]) => {
205
+ if (!entry.isIntersecting) onCloseRef.current();
206
+ },
207
+ // 그릇이 없으면 root: null → 뷰포트 기준
208
+ { root: findScrollParent(trigger), threshold: 0 }
209
+ );
210
+ observer.observe(trigger);
211
+ return () => observer.disconnect();
212
+ }, [enabled, triggerRef]);
212
213
  };
213
214
 
214
215
  // ../../node_modules/clsx/dist/clsx.mjs
@@ -236,7 +237,7 @@ var PopOver = (props) => {
236
237
  const [localOpen, setLocalOpen] = React4.useState(false);
237
238
  const [eventTrigger, setEventTrigger] = React4.useState(false);
238
239
  useClickOutside_default([popRef, triggerRef], onClose, isOpen);
239
- useCloseOnContainerScroll(isOpen, popRef, onClose);
240
+ useCloseWhenTriggerHidden(isOpen, triggerRef, onClose);
240
241
  useEscapeKey(isOpen, () => {
241
242
  onClose();
242
243
  triggerRef.current?.focus({ preventScroll: true });
@@ -56,26 +56,27 @@ var findScrollParent = (el) => {
56
56
  }
57
57
  return null;
58
58
  };
59
+ var TRIGGER_GAP = 8;
59
60
  var computePlacement = (triggerRect, popW, popH, clip) => {
60
- const roomBelow = clip.bottom - triggerRect.bottom;
61
- const roomAbove = triggerRect.top - clip.top;
61
+ const roomBelow = clip.bottom - triggerRect.bottom - TRIGGER_GAP;
62
+ const roomAbove = triggerRect.top - clip.top - TRIGGER_GAP;
62
63
  let direction;
63
64
  let top;
64
65
  let maxHeight = null;
65
66
  if (popH <= roomBelow) {
66
67
  direction = "bottom";
67
- top = triggerRect.bottom;
68
+ top = triggerRect.bottom + TRIGGER_GAP;
68
69
  } else if (popH <= roomAbove) {
69
70
  direction = "top";
70
- top = triggerRect.top - popH;
71
+ top = triggerRect.top - TRIGGER_GAP - popH;
71
72
  } else {
72
73
  const room = Math.max(roomBelow, roomAbove, 0);
73
74
  if (roomBelow >= roomAbove) {
74
75
  direction = "bottom";
75
- top = triggerRect.bottom;
76
+ top = triggerRect.bottom + TRIGGER_GAP;
76
77
  } else {
77
78
  direction = "top";
78
- top = triggerRect.top - room;
79
+ top = triggerRect.top - TRIGGER_GAP - room;
79
80
  }
80
81
  maxHeight = room;
81
82
  }
@@ -230,22 +231,22 @@ var useEscapeKey = (enabled, onEscape) => {
230
231
  return () => document.removeEventListener("keydown", onKey);
231
232
  }, [enabled, onEscape]);
232
233
  };
233
- var useCloseOnContainerScroll = (enabled, popRef, onClose) => {
234
+ var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
234
235
  const onCloseRef = import_react3.default.useRef(onClose);
235
236
  onCloseRef.current = onClose;
236
237
  import_react3.default.useEffect(() => {
237
- if (!enabled) return;
238
- const onScroll = (e) => {
239
- const target = e.target;
240
- if (target === document || target === document.documentElement || target === document.body) {
241
- return;
242
- }
243
- if (target instanceof Node && popRef.current?.contains(target)) return;
244
- onCloseRef.current();
245
- };
246
- window.addEventListener("scroll", onScroll, { passive: true, capture: true });
247
- return () => window.removeEventListener("scroll", onScroll, true);
248
- }, [enabled, popRef]);
238
+ const trigger = triggerRef.current;
239
+ if (!enabled || !trigger) return;
240
+ const observer = new IntersectionObserver(
241
+ ([entry]) => {
242
+ if (!entry.isIntersecting) onCloseRef.current();
243
+ },
244
+ // 그릇이 없으면 root: null → 뷰포트 기준
245
+ { root: findScrollParent(trigger), threshold: 0 }
246
+ );
247
+ observer.observe(trigger);
248
+ return () => observer.disconnect();
249
+ }, [enabled, triggerRef]);
249
250
  };
250
251
 
251
252
  // src/tokens/svg/action/CopyIcon.tsx
@@ -1403,7 +1404,7 @@ var SelectRoot = (props) => {
1403
1404
  }, []);
1404
1405
  const toggle = import_react6.default.useCallback(() => setOpen((prev) => !prev), []);
1405
1406
  useClickOutside_default([contentRef, triggerRef], close, isOpen);
1406
- useCloseOnContainerScroll(isOpen, contentRef, close);
1407
+ useCloseWhenTriggerHidden(isOpen, triggerRef, close);
1407
1408
  useEscapeKey(isOpen, () => {
1408
1409
  close();
1409
1410
  triggerRef.current?.focus({ preventScroll: true });
@@ -212,7 +212,7 @@
212
212
  transition: transform 0.2s ease, opacity 0.2s ease;
213
213
  }
214
214
  .lib-xplat-select-content.bottom {
215
- transform: translateY(8px);
215
+ transform: translateY(-4px);
216
216
  }
217
217
  .lib-xplat-select-content.bottom.visible {
218
218
  opacity: 1;
@@ -220,12 +220,12 @@
220
220
  }
221
221
  .lib-xplat-select-content.bottom.exit {
222
222
  opacity: 0;
223
- transform: translateY(8px);
223
+ transform: translateY(-4px);
224
224
  }
225
225
  .lib-xplat-select-content {
226
226
  }
227
227
  .lib-xplat-select-content.top {
228
- transform: translateY(-8px);
228
+ transform: translateY(4px);
229
229
  }
230
230
  .lib-xplat-select-content.top.visible {
231
231
  opacity: 1;
@@ -233,7 +233,7 @@
233
233
  }
234
234
  .lib-xplat-select-content.top.exit {
235
235
  opacity: 0;
236
- transform: translateY(-8px);
236
+ transform: translateY(4px);
237
237
  }
238
238
  .lib-xplat-select-content.sm .select-item {
239
239
  padding: var(--spacing-space-2) var(--spacing-space-2);
@@ -19,26 +19,27 @@ var findScrollParent = (el) => {
19
19
  }
20
20
  return null;
21
21
  };
22
+ var TRIGGER_GAP = 8;
22
23
  var computePlacement = (triggerRect, popW, popH, clip) => {
23
- const roomBelow = clip.bottom - triggerRect.bottom;
24
- const roomAbove = triggerRect.top - clip.top;
24
+ const roomBelow = clip.bottom - triggerRect.bottom - TRIGGER_GAP;
25
+ const roomAbove = triggerRect.top - clip.top - TRIGGER_GAP;
25
26
  let direction;
26
27
  let top;
27
28
  let maxHeight = null;
28
29
  if (popH <= roomBelow) {
29
30
  direction = "bottom";
30
- top = triggerRect.bottom;
31
+ top = triggerRect.bottom + TRIGGER_GAP;
31
32
  } else if (popH <= roomAbove) {
32
33
  direction = "top";
33
- top = triggerRect.top - popH;
34
+ top = triggerRect.top - TRIGGER_GAP - popH;
34
35
  } else {
35
36
  const room = Math.max(roomBelow, roomAbove, 0);
36
37
  if (roomBelow >= roomAbove) {
37
38
  direction = "bottom";
38
- top = triggerRect.bottom;
39
+ top = triggerRect.bottom + TRIGGER_GAP;
39
40
  } else {
40
41
  direction = "top";
41
- top = triggerRect.top - room;
42
+ top = triggerRect.top - TRIGGER_GAP - room;
42
43
  }
43
44
  maxHeight = room;
44
45
  }
@@ -193,22 +194,22 @@ var useEscapeKey = (enabled, onEscape) => {
193
194
  return () => document.removeEventListener("keydown", onKey);
194
195
  }, [enabled, onEscape]);
195
196
  };
196
- var useCloseOnContainerScroll = (enabled, popRef, onClose) => {
197
+ var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
197
198
  const onCloseRef = React3.useRef(onClose);
198
199
  onCloseRef.current = onClose;
199
200
  React3.useEffect(() => {
200
- if (!enabled) return;
201
- const onScroll = (e) => {
202
- const target = e.target;
203
- if (target === document || target === document.documentElement || target === document.body) {
204
- return;
205
- }
206
- if (target instanceof Node && popRef.current?.contains(target)) return;
207
- onCloseRef.current();
208
- };
209
- window.addEventListener("scroll", onScroll, { passive: true, capture: true });
210
- return () => window.removeEventListener("scroll", onScroll, true);
211
- }, [enabled, popRef]);
201
+ const trigger = triggerRef.current;
202
+ if (!enabled || !trigger) return;
203
+ const observer = new IntersectionObserver(
204
+ ([entry]) => {
205
+ if (!entry.isIntersecting) onCloseRef.current();
206
+ },
207
+ // 그릇이 없으면 root: null → 뷰포트 기준
208
+ { root: findScrollParent(trigger), threshold: 0 }
209
+ );
210
+ observer.observe(trigger);
211
+ return () => observer.disconnect();
212
+ }, [enabled, triggerRef]);
212
213
  };
213
214
 
214
215
  // src/tokens/svg/action/CopyIcon.tsx
@@ -1366,7 +1367,7 @@ var SelectRoot = (props) => {
1366
1367
  }, []);
1367
1368
  const toggle = React6.useCallback(() => setOpen((prev) => !prev), []);
1368
1369
  useClickOutside_default([contentRef, triggerRef], close, isOpen);
1369
- useCloseOnContainerScroll(isOpen, contentRef, close);
1370
+ useCloseWhenTriggerHidden(isOpen, triggerRef, close);
1370
1371
  useEscapeKey(isOpen, () => {
1371
1372
  close();
1372
1373
  triggerRef.current?.focus({ preventScroll: true });