@x-plat/design-system 0.14.14 → 0.14.16

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.
@@ -230,22 +230,25 @@ var useEscapeKey = (enabled, onEscape) => {
230
230
  return () => document.removeEventListener("keydown", onKey);
231
231
  }, [enabled, onEscape]);
232
232
  };
233
- var useCloseOnContainerScroll = (enabled, popRef, onClose) => {
233
+ var useCloseWhenFocusHidden = (enabled, triggerRef, popRef, onClose) => {
234
234
  const onCloseRef = import_react3.default.useRef(onClose);
235
235
  onCloseRef.current = onClose;
236
236
  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]);
237
+ const trigger = triggerRef.current;
238
+ if (!enabled || !trigger) return;
239
+ const observer = new IntersectionObserver(
240
+ ([entry]) => {
241
+ if (entry.isIntersecting) return;
242
+ if (!popRef.current?.contains(document.activeElement)) return;
243
+ onCloseRef.current();
244
+ trigger.focus({ preventScroll: true });
245
+ },
246
+ // 그릇이 없으면 root: null → 뷰포트 기준
247
+ { root: findScrollParent(trigger), threshold: 0 }
248
+ );
249
+ observer.observe(trigger);
250
+ return () => observer.disconnect();
251
+ }, [enabled, triggerRef, popRef]);
249
252
  };
250
253
 
251
254
  // ../../node_modules/clsx/dist/clsx.mjs
@@ -275,7 +278,7 @@ var Dropdown = (props) => {
275
278
  const menuRef = import_react4.default.useRef(null);
276
279
  useAutoPosition_default(triggerRef, menuRef, mounted);
277
280
  useClickOutside_default([triggerRef, menuRef], () => setIsOpen(false), isOpen);
278
- useCloseOnContainerScroll(isOpen, menuRef, () => setIsOpen(false));
281
+ useCloseWhenFocusHidden(isOpen, triggerRef, menuRef, () => setIsOpen(false));
279
282
  useEscapeKey(isOpen, () => {
280
283
  setIsOpen(false);
281
284
  triggerRef.current?.focus({ preventScroll: true });
@@ -194,22 +194,25 @@ var useEscapeKey = (enabled, onEscape) => {
194
194
  return () => document.removeEventListener("keydown", onKey);
195
195
  }, [enabled, onEscape]);
196
196
  };
197
- var useCloseOnContainerScroll = (enabled, popRef, onClose) => {
197
+ var useCloseWhenFocusHidden = (enabled, triggerRef, popRef, onClose) => {
198
198
  const onCloseRef = React3.useRef(onClose);
199
199
  onCloseRef.current = onClose;
200
200
  React3.useEffect(() => {
201
- if (!enabled) return;
202
- const onScroll = (e) => {
203
- const target = e.target;
204
- if (target === document || target === document.documentElement || target === document.body) {
205
- return;
206
- }
207
- if (target instanceof Node && popRef.current?.contains(target)) return;
208
- onCloseRef.current();
209
- };
210
- window.addEventListener("scroll", onScroll, { passive: true, capture: true });
211
- return () => window.removeEventListener("scroll", onScroll, true);
212
- }, [enabled, popRef]);
201
+ const trigger = triggerRef.current;
202
+ if (!enabled || !trigger) return;
203
+ const observer = new IntersectionObserver(
204
+ ([entry]) => {
205
+ if (entry.isIntersecting) return;
206
+ if (!popRef.current?.contains(document.activeElement)) return;
207
+ onCloseRef.current();
208
+ trigger.focus({ preventScroll: true });
209
+ },
210
+ // 그릇이 없으면 root: null → 뷰포트 기준
211
+ { root: findScrollParent(trigger), threshold: 0 }
212
+ );
213
+ observer.observe(trigger);
214
+ return () => observer.disconnect();
215
+ }, [enabled, triggerRef, popRef]);
213
216
  };
214
217
 
215
218
  // ../../node_modules/clsx/dist/clsx.mjs
@@ -239,7 +242,7 @@ var Dropdown = (props) => {
239
242
  const menuRef = React4.useRef(null);
240
243
  useAutoPosition_default(triggerRef, menuRef, mounted);
241
244
  useClickOutside_default([triggerRef, menuRef], () => setIsOpen(false), isOpen);
242
- useCloseOnContainerScroll(isOpen, menuRef, () => setIsOpen(false));
245
+ useCloseWhenFocusHidden(isOpen, triggerRef, menuRef, () => setIsOpen(false));
243
246
  useEscapeKey(isOpen, () => {
244
247
  setIsOpen(false);
245
248
  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);
@@ -230,22 +230,25 @@ var useEscapeKey = (enabled, onEscape) => {
230
230
  return () => document.removeEventListener("keydown", onKey);
231
231
  }, [enabled, onEscape]);
232
232
  };
233
- var useCloseOnContainerScroll = (enabled, popRef, onClose) => {
233
+ var useCloseWhenFocusHidden = (enabled, triggerRef, popRef, onClose) => {
234
234
  const onCloseRef = import_react3.default.useRef(onClose);
235
235
  onCloseRef.current = onClose;
236
236
  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]);
237
+ const trigger = triggerRef.current;
238
+ if (!enabled || !trigger) return;
239
+ const observer = new IntersectionObserver(
240
+ ([entry]) => {
241
+ if (entry.isIntersecting) return;
242
+ if (!popRef.current?.contains(document.activeElement)) return;
243
+ onCloseRef.current();
244
+ trigger.focus({ preventScroll: true });
245
+ },
246
+ // 그릇이 없으면 root: null → 뷰포트 기준
247
+ { root: findScrollParent(trigger), threshold: 0 }
248
+ );
249
+ observer.observe(trigger);
250
+ return () => observer.disconnect();
251
+ }, [enabled, triggerRef, popRef]);
249
252
  };
250
253
 
251
254
  // ../../node_modules/clsx/dist/clsx.mjs
@@ -273,7 +276,7 @@ var PopOver = (props) => {
273
276
  const [localOpen, setLocalOpen] = import_react4.default.useState(false);
274
277
  const [eventTrigger, setEventTrigger] = import_react4.default.useState(false);
275
278
  useClickOutside_default([popRef, triggerRef], onClose, isOpen);
276
- useCloseOnContainerScroll(isOpen, popRef, onClose);
279
+ useCloseWhenFocusHidden(isOpen, triggerRef, popRef, onClose);
277
280
  useEscapeKey(isOpen, () => {
278
281
  onClose();
279
282
  triggerRef.current?.focus({ preventScroll: true });
@@ -194,22 +194,25 @@ var useEscapeKey = (enabled, onEscape) => {
194
194
  return () => document.removeEventListener("keydown", onKey);
195
195
  }, [enabled, onEscape]);
196
196
  };
197
- var useCloseOnContainerScroll = (enabled, popRef, onClose) => {
197
+ var useCloseWhenFocusHidden = (enabled, triggerRef, popRef, onClose) => {
198
198
  const onCloseRef = React3.useRef(onClose);
199
199
  onCloseRef.current = onClose;
200
200
  React3.useEffect(() => {
201
- if (!enabled) return;
202
- const onScroll = (e) => {
203
- const target = e.target;
204
- if (target === document || target === document.documentElement || target === document.body) {
205
- return;
206
- }
207
- if (target instanceof Node && popRef.current?.contains(target)) return;
208
- onCloseRef.current();
209
- };
210
- window.addEventListener("scroll", onScroll, { passive: true, capture: true });
211
- return () => window.removeEventListener("scroll", onScroll, true);
212
- }, [enabled, popRef]);
201
+ const trigger = triggerRef.current;
202
+ if (!enabled || !trigger) return;
203
+ const observer = new IntersectionObserver(
204
+ ([entry]) => {
205
+ if (entry.isIntersecting) return;
206
+ if (!popRef.current?.contains(document.activeElement)) return;
207
+ onCloseRef.current();
208
+ trigger.focus({ preventScroll: true });
209
+ },
210
+ // 그릇이 없으면 root: null → 뷰포트 기준
211
+ { root: findScrollParent(trigger), threshold: 0 }
212
+ );
213
+ observer.observe(trigger);
214
+ return () => observer.disconnect();
215
+ }, [enabled, triggerRef, popRef]);
213
216
  };
214
217
 
215
218
  // ../../node_modules/clsx/dist/clsx.mjs
@@ -237,7 +240,7 @@ var PopOver = (props) => {
237
240
  const [localOpen, setLocalOpen] = React4.useState(false);
238
241
  const [eventTrigger, setEventTrigger] = React4.useState(false);
239
242
  useClickOutside_default([popRef, triggerRef], onClose, isOpen);
240
- useCloseOnContainerScroll(isOpen, popRef, onClose);
243
+ useCloseWhenFocusHidden(isOpen, triggerRef, popRef, onClose);
241
244
  useEscapeKey(isOpen, () => {
242
245
  onClose();
243
246
  triggerRef.current?.focus({ preventScroll: true });
@@ -231,22 +231,25 @@ var useEscapeKey = (enabled, onEscape) => {
231
231
  return () => document.removeEventListener("keydown", onKey);
232
232
  }, [enabled, onEscape]);
233
233
  };
234
- var useCloseOnContainerScroll = (enabled, popRef, onClose) => {
234
+ var useCloseWhenFocusHidden = (enabled, triggerRef, popRef, onClose) => {
235
235
  const onCloseRef = import_react3.default.useRef(onClose);
236
236
  onCloseRef.current = onClose;
237
237
  import_react3.default.useEffect(() => {
238
- if (!enabled) return;
239
- const onScroll = (e) => {
240
- const target = e.target;
241
- if (target === document || target === document.documentElement || target === document.body) {
242
- return;
243
- }
244
- if (target instanceof Node && popRef.current?.contains(target)) return;
245
- onCloseRef.current();
246
- };
247
- window.addEventListener("scroll", onScroll, { passive: true, capture: true });
248
- return () => window.removeEventListener("scroll", onScroll, true);
249
- }, [enabled, popRef]);
238
+ const trigger = triggerRef.current;
239
+ if (!enabled || !trigger) return;
240
+ const observer = new IntersectionObserver(
241
+ ([entry]) => {
242
+ if (entry.isIntersecting) return;
243
+ if (!popRef.current?.contains(document.activeElement)) return;
244
+ onCloseRef.current();
245
+ trigger.focus({ preventScroll: true });
246
+ },
247
+ // 그릇이 없으면 root: null → 뷰포트 기준
248
+ { root: findScrollParent(trigger), threshold: 0 }
249
+ );
250
+ observer.observe(trigger);
251
+ return () => observer.disconnect();
252
+ }, [enabled, triggerRef, popRef]);
250
253
  };
251
254
 
252
255
  // src/tokens/svg/action/CopyIcon.tsx
@@ -1404,7 +1407,7 @@ var SelectRoot = (props) => {
1404
1407
  }, []);
1405
1408
  const toggle = import_react6.default.useCallback(() => setOpen((prev) => !prev), []);
1406
1409
  useClickOutside_default([contentRef, triggerRef], close, isOpen);
1407
- useCloseOnContainerScroll(isOpen, contentRef, close);
1410
+ useCloseWhenFocusHidden(isOpen, triggerRef, contentRef, close);
1408
1411
  useEscapeKey(isOpen, () => {
1409
1412
  close();
1410
1413
  triggerRef.current?.focus({ preventScroll: true });
@@ -194,22 +194,25 @@ var useEscapeKey = (enabled, onEscape) => {
194
194
  return () => document.removeEventListener("keydown", onKey);
195
195
  }, [enabled, onEscape]);
196
196
  };
197
- var useCloseOnContainerScroll = (enabled, popRef, onClose) => {
197
+ var useCloseWhenFocusHidden = (enabled, triggerRef, popRef, onClose) => {
198
198
  const onCloseRef = React3.useRef(onClose);
199
199
  onCloseRef.current = onClose;
200
200
  React3.useEffect(() => {
201
- if (!enabled) return;
202
- const onScroll = (e) => {
203
- const target = e.target;
204
- if (target === document || target === document.documentElement || target === document.body) {
205
- return;
206
- }
207
- if (target instanceof Node && popRef.current?.contains(target)) return;
208
- onCloseRef.current();
209
- };
210
- window.addEventListener("scroll", onScroll, { passive: true, capture: true });
211
- return () => window.removeEventListener("scroll", onScroll, true);
212
- }, [enabled, popRef]);
201
+ const trigger = triggerRef.current;
202
+ if (!enabled || !trigger) return;
203
+ const observer = new IntersectionObserver(
204
+ ([entry]) => {
205
+ if (entry.isIntersecting) return;
206
+ if (!popRef.current?.contains(document.activeElement)) return;
207
+ onCloseRef.current();
208
+ trigger.focus({ preventScroll: true });
209
+ },
210
+ // 그릇이 없으면 root: null → 뷰포트 기준
211
+ { root: findScrollParent(trigger), threshold: 0 }
212
+ );
213
+ observer.observe(trigger);
214
+ return () => observer.disconnect();
215
+ }, [enabled, triggerRef, popRef]);
213
216
  };
214
217
 
215
218
  // src/tokens/svg/action/CopyIcon.tsx
@@ -1367,7 +1370,7 @@ var SelectRoot = (props) => {
1367
1370
  }, []);
1368
1371
  const toggle = React6.useCallback(() => setOpen((prev) => !prev), []);
1369
1372
  useClickOutside_default([contentRef, triggerRef], close, isOpen);
1370
- useCloseOnContainerScroll(isOpen, contentRef, close);
1373
+ useCloseWhenFocusHidden(isOpen, triggerRef, contentRef, close);
1371
1374
  useEscapeKey(isOpen, () => {
1372
1375
  close();
1373
1376
  triggerRef.current?.focus({ preventScroll: true });
@@ -230,22 +230,25 @@ var useEscapeKey = (enabled, onEscape) => {
230
230
  return () => document.removeEventListener("keydown", onKey);
231
231
  }, [enabled, onEscape]);
232
232
  };
233
- var useCloseOnContainerScroll = (enabled, popRef, onClose) => {
233
+ var useCloseWhenFocusHidden = (enabled, triggerRef, popRef, onClose) => {
234
234
  const onCloseRef = import_react3.default.useRef(onClose);
235
235
  onCloseRef.current = onClose;
236
236
  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]);
237
+ const trigger = triggerRef.current;
238
+ if (!enabled || !trigger) return;
239
+ const observer = new IntersectionObserver(
240
+ ([entry]) => {
241
+ if (entry.isIntersecting) return;
242
+ if (!popRef.current?.contains(document.activeElement)) return;
243
+ onCloseRef.current();
244
+ trigger.focus({ preventScroll: true });
245
+ },
246
+ // 그릇이 없으면 root: null → 뷰포트 기준
247
+ { root: findScrollParent(trigger), threshold: 0 }
248
+ );
249
+ observer.observe(trigger);
250
+ return () => observer.disconnect();
251
+ }, [enabled, triggerRef, popRef]);
249
252
  };
250
253
 
251
254
  // ../../node_modules/clsx/dist/clsx.mjs
@@ -456,7 +459,7 @@ var TimePicker = (props) => {
456
459
  }, [step, min, max]);
457
460
  const close = import_react4.default.useCallback(() => setOpen(false), []);
458
461
  useClickOutside_default([triggerRef, contentRef], close, isOpen);
459
- useCloseOnContainerScroll(isOpen, contentRef, close);
462
+ useCloseWhenFocusHidden(isOpen, triggerRef, contentRef, close);
460
463
  useEscapeKey(isOpen, () => {
461
464
  close();
462
465
  triggerRef.current?.focus({ preventScroll: true });
@@ -194,22 +194,25 @@ var useEscapeKey = (enabled, onEscape) => {
194
194
  return () => document.removeEventListener("keydown", onKey);
195
195
  }, [enabled, onEscape]);
196
196
  };
197
- var useCloseOnContainerScroll = (enabled, popRef, onClose) => {
197
+ var useCloseWhenFocusHidden = (enabled, triggerRef, popRef, onClose) => {
198
198
  const onCloseRef = React3.useRef(onClose);
199
199
  onCloseRef.current = onClose;
200
200
  React3.useEffect(() => {
201
- if (!enabled) return;
202
- const onScroll = (e) => {
203
- const target = e.target;
204
- if (target === document || target === document.documentElement || target === document.body) {
205
- return;
206
- }
207
- if (target instanceof Node && popRef.current?.contains(target)) return;
208
- onCloseRef.current();
209
- };
210
- window.addEventListener("scroll", onScroll, { passive: true, capture: true });
211
- return () => window.removeEventListener("scroll", onScroll, true);
212
- }, [enabled, popRef]);
201
+ const trigger = triggerRef.current;
202
+ if (!enabled || !trigger) return;
203
+ const observer = new IntersectionObserver(
204
+ ([entry]) => {
205
+ if (entry.isIntersecting) return;
206
+ if (!popRef.current?.contains(document.activeElement)) return;
207
+ onCloseRef.current();
208
+ trigger.focus({ preventScroll: true });
209
+ },
210
+ // 그릇이 없으면 root: null → 뷰포트 기준
211
+ { root: findScrollParent(trigger), threshold: 0 }
212
+ );
213
+ observer.observe(trigger);
214
+ return () => observer.disconnect();
215
+ }, [enabled, triggerRef, popRef]);
213
216
  };
214
217
 
215
218
  // ../../node_modules/clsx/dist/clsx.mjs
@@ -420,7 +423,7 @@ var TimePicker = (props) => {
420
423
  }, [step, min, max]);
421
424
  const close = React4.useCallback(() => setOpen(false), []);
422
425
  useClickOutside_default([triggerRef, contentRef], close, isOpen);
423
- useCloseOnContainerScroll(isOpen, contentRef, close);
426
+ useCloseWhenFocusHidden(isOpen, triggerRef, contentRef, close);
424
427
  useEscapeKey(isOpen, () => {
425
428
  close();
426
429
  triggerRef.current?.focus({ preventScroll: true });