@x-plat/design-system 0.14.15 → 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,7 +230,7 @@ var useEscapeKey = (enabled, onEscape) => {
230
230
  return () => document.removeEventListener("keydown", onKey);
231
231
  }, [enabled, onEscape]);
232
232
  };
233
- var useCloseWhenTriggerHidden = (enabled, triggerRef, 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(() => {
@@ -238,14 +238,17 @@ var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
238
238
  if (!enabled || !trigger) return;
239
239
  const observer = new IntersectionObserver(
240
240
  ([entry]) => {
241
- if (!entry.isIntersecting) onCloseRef.current();
241
+ if (entry.isIntersecting) return;
242
+ if (!popRef.current?.contains(document.activeElement)) return;
243
+ onCloseRef.current();
244
+ trigger.focus({ preventScroll: true });
242
245
  },
243
246
  // 그릇이 없으면 root: null → 뷰포트 기준
244
247
  { root: findScrollParent(trigger), threshold: 0 }
245
248
  );
246
249
  observer.observe(trigger);
247
250
  return () => observer.disconnect();
248
- }, [enabled, triggerRef]);
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
- useCloseWhenTriggerHidden(isOpen, triggerRef, () => 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,7 +194,7 @@ var useEscapeKey = (enabled, onEscape) => {
194
194
  return () => document.removeEventListener("keydown", onKey);
195
195
  }, [enabled, onEscape]);
196
196
  };
197
- var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
197
+ var useCloseWhenFocusHidden = (enabled, triggerRef, popRef, onClose) => {
198
198
  const onCloseRef = React3.useRef(onClose);
199
199
  onCloseRef.current = onClose;
200
200
  React3.useEffect(() => {
@@ -202,14 +202,17 @@ var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
202
202
  if (!enabled || !trigger) return;
203
203
  const observer = new IntersectionObserver(
204
204
  ([entry]) => {
205
- if (!entry.isIntersecting) onCloseRef.current();
205
+ if (entry.isIntersecting) return;
206
+ if (!popRef.current?.contains(document.activeElement)) return;
207
+ onCloseRef.current();
208
+ trigger.focus({ preventScroll: true });
206
209
  },
207
210
  // 그릇이 없으면 root: null → 뷰포트 기준
208
211
  { root: findScrollParent(trigger), threshold: 0 }
209
212
  );
210
213
  observer.observe(trigger);
211
214
  return () => observer.disconnect();
212
- }, [enabled, triggerRef]);
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
- useCloseWhenTriggerHidden(isOpen, triggerRef, () => setIsOpen(false));
245
+ useCloseWhenFocusHidden(isOpen, triggerRef, menuRef, () => setIsOpen(false));
243
246
  useEscapeKey(isOpen, () => {
244
247
  setIsOpen(false);
245
248
  triggerRef.current?.focus({ preventScroll: true });
@@ -230,7 +230,7 @@ var useEscapeKey = (enabled, onEscape) => {
230
230
  return () => document.removeEventListener("keydown", onKey);
231
231
  }, [enabled, onEscape]);
232
232
  };
233
- var useCloseWhenTriggerHidden = (enabled, triggerRef, 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(() => {
@@ -238,14 +238,17 @@ var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
238
238
  if (!enabled || !trigger) return;
239
239
  const observer = new IntersectionObserver(
240
240
  ([entry]) => {
241
- if (!entry.isIntersecting) onCloseRef.current();
241
+ if (entry.isIntersecting) return;
242
+ if (!popRef.current?.contains(document.activeElement)) return;
243
+ onCloseRef.current();
244
+ trigger.focus({ preventScroll: true });
242
245
  },
243
246
  // 그릇이 없으면 root: null → 뷰포트 기준
244
247
  { root: findScrollParent(trigger), threshold: 0 }
245
248
  );
246
249
  observer.observe(trigger);
247
250
  return () => observer.disconnect();
248
- }, [enabled, triggerRef]);
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
- useCloseWhenTriggerHidden(isOpen, triggerRef, onClose);
279
+ useCloseWhenFocusHidden(isOpen, triggerRef, popRef, onClose);
277
280
  useEscapeKey(isOpen, () => {
278
281
  onClose();
279
282
  triggerRef.current?.focus({ preventScroll: true });
@@ -194,7 +194,7 @@ var useEscapeKey = (enabled, onEscape) => {
194
194
  return () => document.removeEventListener("keydown", onKey);
195
195
  }, [enabled, onEscape]);
196
196
  };
197
- var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
197
+ var useCloseWhenFocusHidden = (enabled, triggerRef, popRef, onClose) => {
198
198
  const onCloseRef = React3.useRef(onClose);
199
199
  onCloseRef.current = onClose;
200
200
  React3.useEffect(() => {
@@ -202,14 +202,17 @@ var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
202
202
  if (!enabled || !trigger) return;
203
203
  const observer = new IntersectionObserver(
204
204
  ([entry]) => {
205
- if (!entry.isIntersecting) onCloseRef.current();
205
+ if (entry.isIntersecting) return;
206
+ if (!popRef.current?.contains(document.activeElement)) return;
207
+ onCloseRef.current();
208
+ trigger.focus({ preventScroll: true });
206
209
  },
207
210
  // 그릇이 없으면 root: null → 뷰포트 기준
208
211
  { root: findScrollParent(trigger), threshold: 0 }
209
212
  );
210
213
  observer.observe(trigger);
211
214
  return () => observer.disconnect();
212
- }, [enabled, triggerRef]);
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
- useCloseWhenTriggerHidden(isOpen, triggerRef, onClose);
243
+ useCloseWhenFocusHidden(isOpen, triggerRef, popRef, onClose);
241
244
  useEscapeKey(isOpen, () => {
242
245
  onClose();
243
246
  triggerRef.current?.focus({ preventScroll: true });
@@ -231,7 +231,7 @@ var useEscapeKey = (enabled, onEscape) => {
231
231
  return () => document.removeEventListener("keydown", onKey);
232
232
  }, [enabled, onEscape]);
233
233
  };
234
- var useCloseWhenTriggerHidden = (enabled, triggerRef, 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(() => {
@@ -239,14 +239,17 @@ var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
239
239
  if (!enabled || !trigger) return;
240
240
  const observer = new IntersectionObserver(
241
241
  ([entry]) => {
242
- if (!entry.isIntersecting) onCloseRef.current();
242
+ if (entry.isIntersecting) return;
243
+ if (!popRef.current?.contains(document.activeElement)) return;
244
+ onCloseRef.current();
245
+ trigger.focus({ preventScroll: true });
243
246
  },
244
247
  // 그릇이 없으면 root: null → 뷰포트 기준
245
248
  { root: findScrollParent(trigger), threshold: 0 }
246
249
  );
247
250
  observer.observe(trigger);
248
251
  return () => observer.disconnect();
249
- }, [enabled, triggerRef]);
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
- useCloseWhenTriggerHidden(isOpen, triggerRef, close);
1410
+ useCloseWhenFocusHidden(isOpen, triggerRef, contentRef, close);
1408
1411
  useEscapeKey(isOpen, () => {
1409
1412
  close();
1410
1413
  triggerRef.current?.focus({ preventScroll: true });
@@ -194,7 +194,7 @@ var useEscapeKey = (enabled, onEscape) => {
194
194
  return () => document.removeEventListener("keydown", onKey);
195
195
  }, [enabled, onEscape]);
196
196
  };
197
- var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
197
+ var useCloseWhenFocusHidden = (enabled, triggerRef, popRef, onClose) => {
198
198
  const onCloseRef = React3.useRef(onClose);
199
199
  onCloseRef.current = onClose;
200
200
  React3.useEffect(() => {
@@ -202,14 +202,17 @@ var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
202
202
  if (!enabled || !trigger) return;
203
203
  const observer = new IntersectionObserver(
204
204
  ([entry]) => {
205
- if (!entry.isIntersecting) onCloseRef.current();
205
+ if (entry.isIntersecting) return;
206
+ if (!popRef.current?.contains(document.activeElement)) return;
207
+ onCloseRef.current();
208
+ trigger.focus({ preventScroll: true });
206
209
  },
207
210
  // 그릇이 없으면 root: null → 뷰포트 기준
208
211
  { root: findScrollParent(trigger), threshold: 0 }
209
212
  );
210
213
  observer.observe(trigger);
211
214
  return () => observer.disconnect();
212
- }, [enabled, triggerRef]);
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
- useCloseWhenTriggerHidden(isOpen, triggerRef, close);
1373
+ useCloseWhenFocusHidden(isOpen, triggerRef, contentRef, close);
1371
1374
  useEscapeKey(isOpen, () => {
1372
1375
  close();
1373
1376
  triggerRef.current?.focus({ preventScroll: true });
@@ -230,7 +230,7 @@ var useEscapeKey = (enabled, onEscape) => {
230
230
  return () => document.removeEventListener("keydown", onKey);
231
231
  }, [enabled, onEscape]);
232
232
  };
233
- var useCloseWhenTriggerHidden = (enabled, triggerRef, 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(() => {
@@ -238,14 +238,17 @@ var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
238
238
  if (!enabled || !trigger) return;
239
239
  const observer = new IntersectionObserver(
240
240
  ([entry]) => {
241
- if (!entry.isIntersecting) onCloseRef.current();
241
+ if (entry.isIntersecting) return;
242
+ if (!popRef.current?.contains(document.activeElement)) return;
243
+ onCloseRef.current();
244
+ trigger.focus({ preventScroll: true });
242
245
  },
243
246
  // 그릇이 없으면 root: null → 뷰포트 기준
244
247
  { root: findScrollParent(trigger), threshold: 0 }
245
248
  );
246
249
  observer.observe(trigger);
247
250
  return () => observer.disconnect();
248
- }, [enabled, triggerRef]);
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
- useCloseWhenTriggerHidden(isOpen, triggerRef, close);
462
+ useCloseWhenFocusHidden(isOpen, triggerRef, contentRef, close);
460
463
  useEscapeKey(isOpen, () => {
461
464
  close();
462
465
  triggerRef.current?.focus({ preventScroll: true });
@@ -194,7 +194,7 @@ var useEscapeKey = (enabled, onEscape) => {
194
194
  return () => document.removeEventListener("keydown", onKey);
195
195
  }, [enabled, onEscape]);
196
196
  };
197
- var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
197
+ var useCloseWhenFocusHidden = (enabled, triggerRef, popRef, onClose) => {
198
198
  const onCloseRef = React3.useRef(onClose);
199
199
  onCloseRef.current = onClose;
200
200
  React3.useEffect(() => {
@@ -202,14 +202,17 @@ var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
202
202
  if (!enabled || !trigger) return;
203
203
  const observer = new IntersectionObserver(
204
204
  ([entry]) => {
205
- if (!entry.isIntersecting) onCloseRef.current();
205
+ if (entry.isIntersecting) return;
206
+ if (!popRef.current?.contains(document.activeElement)) return;
207
+ onCloseRef.current();
208
+ trigger.focus({ preventScroll: true });
206
209
  },
207
210
  // 그릇이 없으면 root: null → 뷰포트 기준
208
211
  { root: findScrollParent(trigger), threshold: 0 }
209
212
  );
210
213
  observer.observe(trigger);
211
214
  return () => observer.disconnect();
212
- }, [enabled, triggerRef]);
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
- useCloseWhenTriggerHidden(isOpen, triggerRef, close);
426
+ useCloseWhenFocusHidden(isOpen, triggerRef, contentRef, close);
424
427
  useEscapeKey(isOpen, () => {
425
428
  close();
426
429
  triggerRef.current?.focus({ preventScroll: true });
@@ -3441,7 +3441,7 @@ var useFocusTrap = (containerRef, enabled, options = {}) => {
3441
3441
  return () => document.removeEventListener("keydown", onKey);
3442
3442
  }, [enabled, trap, containerRef]);
3443
3443
  };
3444
- var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
3444
+ var useCloseWhenFocusHidden = (enabled, triggerRef, popRef, onClose) => {
3445
3445
  const onCloseRef = import_react11.default.useRef(onClose);
3446
3446
  onCloseRef.current = onClose;
3447
3447
  import_react11.default.useEffect(() => {
@@ -3449,14 +3449,17 @@ var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
3449
3449
  if (!enabled || !trigger) return;
3450
3450
  const observer = new IntersectionObserver(
3451
3451
  ([entry]) => {
3452
- if (!entry.isIntersecting) onCloseRef.current();
3452
+ if (entry.isIntersecting) return;
3453
+ if (!popRef.current?.contains(document.activeElement)) return;
3454
+ onCloseRef.current();
3455
+ trigger.focus({ preventScroll: true });
3453
3456
  },
3454
3457
  // 그릇이 없으면 root: null → 뷰포트 기준
3455
3458
  { root: findScrollParent(trigger), threshold: 0 }
3456
3459
  );
3457
3460
  observer.observe(trigger);
3458
3461
  return () => observer.disconnect();
3459
- }, [enabled, triggerRef]);
3462
+ }, [enabled, triggerRef, popRef]);
3460
3463
  };
3461
3464
 
3462
3465
  // src/components/Modal/Modal.tsx
@@ -4164,7 +4167,7 @@ var Dropdown = (props) => {
4164
4167
  const menuRef = import_react21.default.useRef(null);
4165
4168
  useAutoPosition_default(triggerRef, menuRef, mounted);
4166
4169
  useClickOutside_default([triggerRef, menuRef], () => setIsOpen(false), isOpen);
4167
- useCloseWhenTriggerHidden(isOpen, triggerRef, () => setIsOpen(false));
4170
+ useCloseWhenFocusHidden(isOpen, triggerRef, menuRef, () => setIsOpen(false));
4168
4171
  useEscapeKey(isOpen, () => {
4169
4172
  setIsOpen(false);
4170
4173
  triggerRef.current?.focus({ preventScroll: true });
@@ -6095,7 +6098,7 @@ var PopOver = (props) => {
6095
6098
  const [localOpen, setLocalOpen] = import_react27.default.useState(false);
6096
6099
  const [eventTrigger, setEventTrigger] = import_react27.default.useState(false);
6097
6100
  useClickOutside_default([popRef, triggerRef], onClose, isOpen);
6098
- useCloseWhenTriggerHidden(isOpen, triggerRef, onClose);
6101
+ useCloseWhenFocusHidden(isOpen, triggerRef, popRef, onClose);
6099
6102
  useEscapeKey(isOpen, () => {
6100
6103
  onClose();
6101
6104
  triggerRef.current?.focus({ preventScroll: true });
@@ -6375,7 +6378,7 @@ var SelectRoot = (props) => {
6375
6378
  }, []);
6376
6379
  const toggle = import_react31.default.useCallback(() => setOpen((prev) => !prev), []);
6377
6380
  useClickOutside_default([contentRef, triggerRef], close, isOpen);
6378
- useCloseWhenTriggerHidden(isOpen, triggerRef, close);
6381
+ useCloseWhenFocusHidden(isOpen, triggerRef, contentRef, close);
6379
6382
  useEscapeKey(isOpen, () => {
6380
6383
  close();
6381
6384
  triggerRef.current?.focus({ preventScroll: true });
@@ -7316,7 +7319,7 @@ var TimePicker = (props) => {
7316
7319
  }, [step, min, max]);
7317
7320
  const close = import_react40.default.useCallback(() => setOpen(false), []);
7318
7321
  useClickOutside_default([triggerRef, contentRef], close, isOpen);
7319
- useCloseWhenTriggerHidden(isOpen, triggerRef, close);
7322
+ useCloseWhenFocusHidden(isOpen, triggerRef, contentRef, close);
7320
7323
  useEscapeKey(isOpen, () => {
7321
7324
  close();
7322
7325
  triggerRef.current?.focus({ preventScroll: true });
@@ -3339,7 +3339,7 @@ var useFocusTrap = (containerRef, enabled, options = {}) => {
3339
3339
  return () => document.removeEventListener("keydown", onKey);
3340
3340
  }, [enabled, trap, containerRef]);
3341
3341
  };
3342
- var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
3342
+ var useCloseWhenFocusHidden = (enabled, triggerRef, popRef, onClose) => {
3343
3343
  const onCloseRef = React11.useRef(onClose);
3344
3344
  onCloseRef.current = onClose;
3345
3345
  React11.useEffect(() => {
@@ -3347,14 +3347,17 @@ var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
3347
3347
  if (!enabled || !trigger) return;
3348
3348
  const observer = new IntersectionObserver(
3349
3349
  ([entry]) => {
3350
- if (!entry.isIntersecting) onCloseRef.current();
3350
+ if (entry.isIntersecting) return;
3351
+ if (!popRef.current?.contains(document.activeElement)) return;
3352
+ onCloseRef.current();
3353
+ trigger.focus({ preventScroll: true });
3351
3354
  },
3352
3355
  // 그릇이 없으면 root: null → 뷰포트 기준
3353
3356
  { root: findScrollParent(trigger), threshold: 0 }
3354
3357
  );
3355
3358
  observer.observe(trigger);
3356
3359
  return () => observer.disconnect();
3357
- }, [enabled, triggerRef]);
3360
+ }, [enabled, triggerRef, popRef]);
3358
3361
  };
3359
3362
 
3360
3363
  // src/components/Modal/Modal.tsx
@@ -4062,7 +4065,7 @@ var Dropdown = (props) => {
4062
4065
  const menuRef = React21.useRef(null);
4063
4066
  useAutoPosition_default(triggerRef, menuRef, mounted);
4064
4067
  useClickOutside_default([triggerRef, menuRef], () => setIsOpen(false), isOpen);
4065
- useCloseWhenTriggerHidden(isOpen, triggerRef, () => setIsOpen(false));
4068
+ useCloseWhenFocusHidden(isOpen, triggerRef, menuRef, () => setIsOpen(false));
4066
4069
  useEscapeKey(isOpen, () => {
4067
4070
  setIsOpen(false);
4068
4071
  triggerRef.current?.focus({ preventScroll: true });
@@ -5993,7 +5996,7 @@ var PopOver = (props) => {
5993
5996
  const [localOpen, setLocalOpen] = React27.useState(false);
5994
5997
  const [eventTrigger, setEventTrigger] = React27.useState(false);
5995
5998
  useClickOutside_default([popRef, triggerRef], onClose, isOpen);
5996
- useCloseWhenTriggerHidden(isOpen, triggerRef, onClose);
5999
+ useCloseWhenFocusHidden(isOpen, triggerRef, popRef, onClose);
5997
6000
  useEscapeKey(isOpen, () => {
5998
6001
  onClose();
5999
6002
  triggerRef.current?.focus({ preventScroll: true });
@@ -6273,7 +6276,7 @@ var SelectRoot = (props) => {
6273
6276
  }, []);
6274
6277
  const toggle = React31.useCallback(() => setOpen((prev) => !prev), []);
6275
6278
  useClickOutside_default([contentRef, triggerRef], close, isOpen);
6276
- useCloseWhenTriggerHidden(isOpen, triggerRef, close);
6279
+ useCloseWhenFocusHidden(isOpen, triggerRef, contentRef, close);
6277
6280
  useEscapeKey(isOpen, () => {
6278
6281
  close();
6279
6282
  triggerRef.current?.focus({ preventScroll: true });
@@ -7214,7 +7217,7 @@ var TimePicker = (props) => {
7214
7217
  }, [step, min, max]);
7215
7218
  const close = React40.useCallback(() => setOpen(false), []);
7216
7219
  useClickOutside_default([triggerRef, contentRef], close, isOpen);
7217
- useCloseWhenTriggerHidden(isOpen, triggerRef, close);
7220
+ useCloseWhenFocusHidden(isOpen, triggerRef, contentRef, close);
7218
7221
  useEscapeKey(isOpen, () => {
7219
7222
  close();
7220
7223
  triggerRef.current?.focus({ preventScroll: true });
package/dist/index.cjs CHANGED
@@ -7853,7 +7853,7 @@ var useFocusTrap = (containerRef, enabled, options = {}) => {
7853
7853
  return () => document.removeEventListener("keydown", onKey);
7854
7854
  }, [enabled, trap, containerRef]);
7855
7855
  };
7856
- var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
7856
+ var useCloseWhenFocusHidden = (enabled, triggerRef, popRef, onClose) => {
7857
7857
  const onCloseRef = import_react11.default.useRef(onClose);
7858
7858
  onCloseRef.current = onClose;
7859
7859
  import_react11.default.useEffect(() => {
@@ -7861,14 +7861,17 @@ var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
7861
7861
  if (!enabled || !trigger) return;
7862
7862
  const observer = new IntersectionObserver(
7863
7863
  ([entry]) => {
7864
- if (!entry.isIntersecting) onCloseRef.current();
7864
+ if (entry.isIntersecting) return;
7865
+ if (!popRef.current?.contains(document.activeElement)) return;
7866
+ onCloseRef.current();
7867
+ trigger.focus({ preventScroll: true });
7865
7868
  },
7866
7869
  // 그릇이 없으면 root: null → 뷰포트 기준
7867
7870
  { root: findScrollParent(trigger), threshold: 0 }
7868
7871
  );
7869
7872
  observer.observe(trigger);
7870
7873
  return () => observer.disconnect();
7871
- }, [enabled, triggerRef]);
7874
+ }, [enabled, triggerRef, popRef]);
7872
7875
  };
7873
7876
 
7874
7877
  // src/components/Modal/Modal.tsx
@@ -8576,7 +8579,7 @@ var Dropdown = (props) => {
8576
8579
  const menuRef = import_react21.default.useRef(null);
8577
8580
  useAutoPosition_default(triggerRef, menuRef, mounted);
8578
8581
  useClickOutside_default([triggerRef, menuRef], () => setIsOpen(false), isOpen);
8579
- useCloseWhenTriggerHidden(isOpen, triggerRef, () => setIsOpen(false));
8582
+ useCloseWhenFocusHidden(isOpen, triggerRef, menuRef, () => setIsOpen(false));
8580
8583
  useEscapeKey(isOpen, () => {
8581
8584
  setIsOpen(false);
8582
8585
  triggerRef.current?.focus({ preventScroll: true });
@@ -10507,7 +10510,7 @@ var PopOver = (props) => {
10507
10510
  const [localOpen, setLocalOpen] = import_react27.default.useState(false);
10508
10511
  const [eventTrigger, setEventTrigger] = import_react27.default.useState(false);
10509
10512
  useClickOutside_default([popRef, triggerRef], onClose, isOpen);
10510
- useCloseWhenTriggerHidden(isOpen, triggerRef, onClose);
10513
+ useCloseWhenFocusHidden(isOpen, triggerRef, popRef, onClose);
10511
10514
  useEscapeKey(isOpen, () => {
10512
10515
  onClose();
10513
10516
  triggerRef.current?.focus({ preventScroll: true });
@@ -10787,7 +10790,7 @@ var SelectRoot = (props) => {
10787
10790
  }, []);
10788
10791
  const toggle = import_react31.default.useCallback(() => setOpen((prev) => !prev), []);
10789
10792
  useClickOutside_default([contentRef, triggerRef], close, isOpen);
10790
- useCloseWhenTriggerHidden(isOpen, triggerRef, close);
10793
+ useCloseWhenFocusHidden(isOpen, triggerRef, contentRef, close);
10791
10794
  useEscapeKey(isOpen, () => {
10792
10795
  close();
10793
10796
  triggerRef.current?.focus({ preventScroll: true });
@@ -11728,7 +11731,7 @@ var TimePicker = (props) => {
11728
11731
  }, [step, min, max]);
11729
11732
  const close = import_react40.default.useCallback(() => setOpen(false), []);
11730
11733
  useClickOutside_default([triggerRef, contentRef], close, isOpen);
11731
- useCloseWhenTriggerHidden(isOpen, triggerRef, close);
11734
+ useCloseWhenFocusHidden(isOpen, triggerRef, contentRef, close);
11732
11735
  useEscapeKey(isOpen, () => {
11733
11736
  close();
11734
11737
  triggerRef.current?.focus({ preventScroll: true });
package/dist/index.js CHANGED
@@ -7442,7 +7442,7 @@ var useFocusTrap = (containerRef, enabled, options = {}) => {
7442
7442
  return () => document.removeEventListener("keydown", onKey);
7443
7443
  }, [enabled, trap, containerRef]);
7444
7444
  };
7445
- var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
7445
+ var useCloseWhenFocusHidden = (enabled, triggerRef, popRef, onClose) => {
7446
7446
  const onCloseRef = React11.useRef(onClose);
7447
7447
  onCloseRef.current = onClose;
7448
7448
  React11.useEffect(() => {
@@ -7450,14 +7450,17 @@ var useCloseWhenTriggerHidden = (enabled, triggerRef, onClose) => {
7450
7450
  if (!enabled || !trigger) return;
7451
7451
  const observer = new IntersectionObserver(
7452
7452
  ([entry]) => {
7453
- if (!entry.isIntersecting) onCloseRef.current();
7453
+ if (entry.isIntersecting) return;
7454
+ if (!popRef.current?.contains(document.activeElement)) return;
7455
+ onCloseRef.current();
7456
+ trigger.focus({ preventScroll: true });
7454
7457
  },
7455
7458
  // 그릇이 없으면 root: null → 뷰포트 기준
7456
7459
  { root: findScrollParent(trigger), threshold: 0 }
7457
7460
  );
7458
7461
  observer.observe(trigger);
7459
7462
  return () => observer.disconnect();
7460
- }, [enabled, triggerRef]);
7463
+ }, [enabled, triggerRef, popRef]);
7461
7464
  };
7462
7465
 
7463
7466
  // src/components/Modal/Modal.tsx
@@ -8165,7 +8168,7 @@ var Dropdown = (props) => {
8165
8168
  const menuRef = React21.useRef(null);
8166
8169
  useAutoPosition_default(triggerRef, menuRef, mounted);
8167
8170
  useClickOutside_default([triggerRef, menuRef], () => setIsOpen(false), isOpen);
8168
- useCloseWhenTriggerHidden(isOpen, triggerRef, () => setIsOpen(false));
8171
+ useCloseWhenFocusHidden(isOpen, triggerRef, menuRef, () => setIsOpen(false));
8169
8172
  useEscapeKey(isOpen, () => {
8170
8173
  setIsOpen(false);
8171
8174
  triggerRef.current?.focus({ preventScroll: true });
@@ -10096,7 +10099,7 @@ var PopOver = (props) => {
10096
10099
  const [localOpen, setLocalOpen] = React27.useState(false);
10097
10100
  const [eventTrigger, setEventTrigger] = React27.useState(false);
10098
10101
  useClickOutside_default([popRef, triggerRef], onClose, isOpen);
10099
- useCloseWhenTriggerHidden(isOpen, triggerRef, onClose);
10102
+ useCloseWhenFocusHidden(isOpen, triggerRef, popRef, onClose);
10100
10103
  useEscapeKey(isOpen, () => {
10101
10104
  onClose();
10102
10105
  triggerRef.current?.focus({ preventScroll: true });
@@ -10376,7 +10379,7 @@ var SelectRoot = (props) => {
10376
10379
  }, []);
10377
10380
  const toggle = React31.useCallback(() => setOpen((prev) => !prev), []);
10378
10381
  useClickOutside_default([contentRef, triggerRef], close, isOpen);
10379
- useCloseWhenTriggerHidden(isOpen, triggerRef, close);
10382
+ useCloseWhenFocusHidden(isOpen, triggerRef, contentRef, close);
10380
10383
  useEscapeKey(isOpen, () => {
10381
10384
  close();
10382
10385
  triggerRef.current?.focus({ preventScroll: true });
@@ -11317,7 +11320,7 @@ var TimePicker = (props) => {
11317
11320
  }, [step, min, max]);
11318
11321
  const close = React40.useCallback(() => setOpen(false), []);
11319
11322
  useClickOutside_default([triggerRef, contentRef], close, isOpen);
11320
- useCloseWhenTriggerHidden(isOpen, triggerRef, close);
11323
+ useCloseWhenFocusHidden(isOpen, triggerRef, contentRef, close);
11321
11324
  useEscapeKey(isOpen, () => {
11322
11325
  close();
11323
11326
  triggerRef.current?.focus({ preventScroll: true });
@@ -284,30 +284,36 @@ Dropdown · PopOver · TimePicker 가 전부 애니메이션 없이 툭 튀어
284
284
  **올리기만 하면 된다. 쓰는 쪽에서 고칠 것은 없다.** `.repositioning` 클래스를
285
285
  직접 잡는 CSS 를 쓰고 있었다면 그 규칙만 지우면 된다.
286
286
 
287
- ## 0.14.7 ~ 0.14.14 — 안쪽 상자가 스크롤되면 닫혔다 (0.14.15 에서 되돌림)
287
+ ## 0.14.7 ~ 0.14.15 — 스크롤하면 오버레이가 닫혔다 (0.14.16 에서 되돌림)
288
288
 
289
289
  **이 구간을 쓰고 있었다면** `Select` · `Dropdown` · `PopOver` · `TimePicker`
290
- 를 열어 둔 채 페이지가 아닌 안쪽 상자를 스크롤하면 닫혔다. **0.14.15 부터는
291
- 닫지 않는다** — 따라간다.
290
+ 를 열어 둔 채 스크롤하면 닫혔다. **0.14.16 부터는 안 닫힌다** — 따라간다.
292
291
 
293
- 0.14.7 에 그 땜질을 넣은 것은 팝업이 스크롤을 **제때 못 따라가고**(JS 로
294
- 옮기느라 한 프레임 늦었다) 그릇 밖으로 나가도 안 잘려 헤더를 덮었기
295
- 때문이다. 어차피 어긋나 보이니 닫는 쪽이 나았다.
292
+ 두 번에 걸쳐 걷어냈다.
296
293
 
297
- 그 전제가 둘 다 무너졌다:
294
+ 0.14.7 스크롤이 나면 바로 닫는다
295
+ 0.14.15 트리거가 그릇 밖으로 나가면 닫는다
296
+ 0.14.16 안 닫는다 (포커스가 패널 안에 있을 때만 예외)
298
297
 
299
- 0.14.3 ~ 0.14.6 자리를 DOM 에 직접 쓰고 문서 좌표로 바꿔 지연이 사라졌다
300
- 0.14.9 ~ 0.14.14 그릇 기준으로 자리를 잡고 그릇 밖은 clip-path 로 잘라낸다
298
+ 0.14.7 의 땜질은 팝업이 스크롤을 **제때 못 따라가고**(JS 로 옮기느라 한 프레임
299
+ 늦었다) 그릇 밖으로 나가도 안 잘려 헤더를 덮었기 때문이다. 어차피 어긋나
300
+ 보이니 닫는 쪽이 나았다. 그 전제는 0.14.3~0.14.6(지연 제거)과
301
+ 0.14.9~0.14.14(그릇 기준 자리 + `clip-path`)로 무너졌다.
301
302
 
302
- 이제 따라가도 어긋나 보이지 않는다. 그리고 닫는 쪽이 손해다 — 긴 목록에서
303
- 아래를 보려고 바퀴를 조금 굴렸을 뿐인데 고르던 것이 사라진다.
303
+ 0.14.15 는 "안 보이는데 열린 상태" 를 걱정해 남겨 둔 것인데, 쓰는 사람에게는
304
+ **굴리다가 목록이 툭 사라지는 쪽**이 더 거슬렸다. 목록은 트리거에 딸린
305
+ 것이라 **트리거가 돌아오면 목록도 돌아오는 것**이 맞는 그림이다. 잠깐
306
+ 지나쳤다는 이유로 고르던 것이 없어질 이유가 없다.
304
307
 
305
- ### 다만 트리거가 그릇 밖으로 완전히 나가면 닫는다
308
+ ### 예외 하나 — 포커스가 패널 안에 있을 때
306
309
 
307
- 안 닫으면 **패널이 통째로 잘려 안 보이는데 열린 상태**가 된다. 눈에는 닫힌
308
- 것 같은데 키보드 포커스와 `Escape` 는 거기 살아 있다. 판정은
309
- `IntersectionObserver` 에 맡긴다 — 스크롤마다 재는 것보다 싸고, 그릇이
310
- 스크롤하든 페이지가 스크롤하든 같은 방식으로 걸린다.
310
+ 그때는 **보이지 않는 곳에 커서가 있는** 상태가 된다. 패널이 통째로 잘려
311
+ 있는데 거기 포커스가 살아 있고, 자리가 `absolute` 라 브라우저가 스크롤로
312
+ 되돌려 주지도 않는다. 이때만 닫고 포커스를 트리거로 돌린다
313
+ (`preventScroll: true` — 사용자가 굴린 자리를 빼앗지 않는다).
314
+
315
+ 키보드로 열어 항목을 오르내리다 굴리는 경우라 흔하지 않다. 마우스로 쓰면
316
+ 겪을 일이 없다.
311
317
 
312
318
  **쓰는 쪽에서 할 일은 없다.**
313
319
 
@@ -384,6 +390,7 @@ export { Button, Input, Select, SideBar /* 쓰는 것만 */ } from "@x-plat/desi
384
390
  | 팝업 목록 안에서 휠 → 안 닫힘 (0.14.7) | **사람이 화면에서 확인함** |
385
391
  | 긴 항목 줄바꿈 (0.14.8) | **사람이 화면에서 확인함** — 두세 줄로 떨어진다 |
386
392
  | 자리·간격·여는 동안 (0.14.14) | **프레임 단위로 측정함** — 멈춘 상태 간격 8px, 여는 동안 4→8 로 벌어짐, 포커스 링 여유가 한 프레임도 음수가 아님 |
393
+ | 열린 채 스크롤 (0.14.15) | **프레임 단위로 측정함** — 간격이 8px 에서 한 번도 안 흔들림(지연 없음), 헤더 침범 0.0px(경계에 닿되 안 넘음), 트리거가 29px 걸쳤을 때는 열려 있고 0 이 된 뒤 닫힘 |
387
394
  | 페이지(문서) 스크롤 시 안 닫힘 (0.14.7) | **확인 안 됨.** 규칙상 맞지만 그 조건의 화면에 고르개가 없어 볼 자리가 없었다 |
388
395
  | `Layout` 타입 (0.14.2) | 소비자 흉내 프로젝트로 타입 검사 통과 |
389
396
  | `ToggleChip` 커서·초점 테두리 | 마크업·CSS 만 확인. 눌러 본 사람 없음 |
@@ -398,6 +405,12 @@ export { Button, Input, Select, SideBar /* 쓰는 것만 */ } from "@x-plat/desi
398
405
  > `div[tabindex]` 라 마우스 클릭에는 `:focus-visible` 이 안 걸린다. 마우스로만
399
406
  > 열면 링이 아예 안 떠서 그 항목이 **늘 통과한다.**
400
407
  >
408
+ > **읽는 시점이 화면보다 빠르면 그것도 거짓말이다.** 스크롤 뒤 32ms 만에
409
+ > 읽었더니 헤더 침범이 −1.6 → −2.8 → −3.5 로 나왔고 끝까지 안 닫혔다.
410
+ > `clip-path` 갱신과 `IntersectionObserver` 가 아직 안 돈 것이다. 120ms 로
411
+ > 늘리니 침범 0, 제때 닫힘이었다. 재는 **범위**만이 아니라 **시점**도 맞아야
412
+ > 한다.
413
+ >
401
414
  > 확인할 때는 **배포가 앉은 뒤에** 볼 것. 한 번은 "안 닫힌다" 로 나왔다가
402
415
  > 다시 보니 닫혔다 — 반쯤 배포된 화면이었다. 그리고 버전 번호가 아니라
403
416
  > 받은 파일 안을 열어 확인하는 편이 낫다(0.14.1 은 번호만 올라가고 내용이
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@x-plat/design-system",
3
- "version": "0.14.15",
3
+ "version": "0.14.16",
4
4
  "description": "XPLAT UI Design System",
5
5
  "author": "XPLAT WOONG",
6
6
  "main": "dist/index.cjs",