@x-plat/design-system 0.14.6 → 0.14.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Dropdown/index.cjs +41 -13
- package/dist/components/Dropdown/index.js +41 -13
- package/dist/components/PopOver/index.cjs +41 -13
- package/dist/components/PopOver/index.js +41 -13
- package/dist/components/Select/index.cjs +41 -13
- package/dist/components/Select/index.js +41 -13
- package/dist/components/TimePicker/index.cjs +41 -13
- package/dist/components/TimePicker/index.js +41 -13
- package/dist/components/index.cjs +44 -13
- package/dist/components/index.js +44 -13
- package/dist/index.cjs +44 -13
- package/dist/index.js +44 -13
- package/guidelines/MIGRATION.md +37 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7304,6 +7304,23 @@ var useFocusTrap = (containerRef, enabled, options = {}) => {
|
|
|
7304
7304
|
return () => document.removeEventListener("keydown", onKey);
|
|
7305
7305
|
}, [enabled, trap, containerRef]);
|
|
7306
7306
|
};
|
|
7307
|
+
var useCloseOnContainerScroll = (enabled, popRef, onClose) => {
|
|
7308
|
+
const onCloseRef = React10.useRef(onClose);
|
|
7309
|
+
onCloseRef.current = onClose;
|
|
7310
|
+
React10.useEffect(() => {
|
|
7311
|
+
if (!enabled) return;
|
|
7312
|
+
const onScroll = (e) => {
|
|
7313
|
+
const target = e.target;
|
|
7314
|
+
if (target === document || target === document.documentElement || target === document.body) {
|
|
7315
|
+
return;
|
|
7316
|
+
}
|
|
7317
|
+
if (target instanceof Node && popRef.current?.contains(target)) return;
|
|
7318
|
+
onCloseRef.current();
|
|
7319
|
+
};
|
|
7320
|
+
window.addEventListener("scroll", onScroll, { passive: true, capture: true });
|
|
7321
|
+
return () => window.removeEventListener("scroll", onScroll, true);
|
|
7322
|
+
}, [enabled, popRef]);
|
|
7323
|
+
};
|
|
7307
7324
|
|
|
7308
7325
|
// src/components/Modal/Modal.tsx
|
|
7309
7326
|
import { jsx as jsx313 } from "react/jsx-runtime";
|
|
@@ -7979,15 +7996,21 @@ import React19 from "react";
|
|
|
7979
7996
|
var useAutoPosition = (triggerRef, popRef, enabled = true) => {
|
|
7980
7997
|
const [direction, setDirection] = React19.useState("bottom");
|
|
7981
7998
|
const directionRef = React19.useRef("bottom");
|
|
7982
|
-
const
|
|
7999
|
+
const sizeRef = React19.useRef(null);
|
|
8000
|
+
const calculatePosition = React19.useCallback((remeasure = false) => {
|
|
7983
8001
|
const pop = popRef.current;
|
|
7984
8002
|
if (!triggerRef.current || !pop) return;
|
|
8003
|
+
if (remeasure || !sizeRef.current) {
|
|
8004
|
+
sizeRef.current = { w: pop.offsetWidth, h: pop.offsetHeight };
|
|
8005
|
+
}
|
|
8006
|
+
const { w: popW, h: popH } = sizeRef.current;
|
|
7985
8007
|
const triggerRect = triggerRef.current.getBoundingClientRect();
|
|
7986
|
-
const popW = pop.offsetWidth;
|
|
7987
|
-
const popH = pop.offsetHeight;
|
|
7988
8008
|
const viewportHeight = window.innerHeight;
|
|
7989
8009
|
const viewportWidth = window.innerWidth;
|
|
7990
|
-
if (popH === 0 || popW === 0)
|
|
8010
|
+
if (popH === 0 || popW === 0) {
|
|
8011
|
+
sizeRef.current = null;
|
|
8012
|
+
return;
|
|
8013
|
+
}
|
|
7991
8014
|
let next = "bottom";
|
|
7992
8015
|
let top;
|
|
7993
8016
|
let left;
|
|
@@ -8010,27 +8033,31 @@ var useAutoPosition = (triggerRef, popRef, enabled = true) => {
|
|
|
8010
8033
|
}
|
|
8011
8034
|
}, [triggerRef, popRef]);
|
|
8012
8035
|
React19.useLayoutEffect(() => {
|
|
8013
|
-
if (!enabled)
|
|
8014
|
-
|
|
8015
|
-
|
|
8036
|
+
if (!enabled) {
|
|
8037
|
+
sizeRef.current = null;
|
|
8038
|
+
return;
|
|
8039
|
+
}
|
|
8040
|
+
calculatePosition(true);
|
|
8041
|
+
const raf = requestAnimationFrame(() => calculatePosition(true));
|
|
8016
8042
|
return () => cancelAnimationFrame(raf);
|
|
8017
8043
|
}, [calculatePosition, enabled]);
|
|
8018
8044
|
React19.useEffect(() => {
|
|
8019
8045
|
if (!enabled || !popRef.current) return;
|
|
8020
8046
|
const observer = new ResizeObserver(() => {
|
|
8021
|
-
calculatePosition();
|
|
8047
|
+
calculatePosition(true);
|
|
8022
8048
|
});
|
|
8023
8049
|
observer.observe(popRef.current);
|
|
8024
8050
|
return () => observer.disconnect();
|
|
8025
8051
|
}, [calculatePosition, enabled, popRef]);
|
|
8026
8052
|
React19.useEffect(() => {
|
|
8027
8053
|
if (!enabled) return;
|
|
8028
|
-
const
|
|
8029
|
-
|
|
8030
|
-
window.addEventListener("
|
|
8054
|
+
const onScroll = () => calculatePosition();
|
|
8055
|
+
const onResize = () => calculatePosition(true);
|
|
8056
|
+
window.addEventListener("resize", onResize, { passive: true });
|
|
8057
|
+
window.addEventListener("scroll", onScroll, { passive: true, capture: true });
|
|
8031
8058
|
return () => {
|
|
8032
|
-
window.removeEventListener("resize",
|
|
8033
|
-
window.removeEventListener("scroll",
|
|
8059
|
+
window.removeEventListener("resize", onResize);
|
|
8060
|
+
window.removeEventListener("scroll", onScroll, true);
|
|
8034
8061
|
};
|
|
8035
8062
|
}, [calculatePosition, enabled]);
|
|
8036
8063
|
return { direction };
|
|
@@ -8073,6 +8100,7 @@ var Dropdown = (props) => {
|
|
|
8073
8100
|
const menuRef = React21.useRef(null);
|
|
8074
8101
|
const { direction } = useAutoPosition_default(triggerRef, menuRef, mounted);
|
|
8075
8102
|
useClickOutside_default([triggerRef, menuRef], () => setIsOpen(false), isOpen);
|
|
8103
|
+
useCloseOnContainerScroll(isOpen, menuRef, () => setIsOpen(false));
|
|
8076
8104
|
useEscapeKey(isOpen, () => {
|
|
8077
8105
|
setIsOpen(false);
|
|
8078
8106
|
triggerRef.current?.focus({ preventScroll: true });
|
|
@@ -10003,6 +10031,7 @@ var PopOver = (props) => {
|
|
|
10003
10031
|
const [localOpen, setLocalOpen] = React27.useState(false);
|
|
10004
10032
|
const [eventTrigger, setEventTrigger] = React27.useState(false);
|
|
10005
10033
|
useClickOutside_default([popRef, triggerRef], onClose, isOpen);
|
|
10034
|
+
useCloseOnContainerScroll(isOpen, popRef, onClose);
|
|
10006
10035
|
useEscapeKey(isOpen, () => {
|
|
10007
10036
|
onClose();
|
|
10008
10037
|
triggerRef.current?.focus({ preventScroll: true });
|
|
@@ -10283,6 +10312,7 @@ var SelectRoot = (props) => {
|
|
|
10283
10312
|
}, []);
|
|
10284
10313
|
const toggle = React31.useCallback(() => setOpen((prev) => !prev), []);
|
|
10285
10314
|
useClickOutside_default([contentRef, triggerRef], close, isOpen);
|
|
10315
|
+
useCloseOnContainerScroll(isOpen, contentRef, close);
|
|
10286
10316
|
useEscapeKey(isOpen, () => {
|
|
10287
10317
|
close();
|
|
10288
10318
|
triggerRef.current?.focus({ preventScroll: true });
|
|
@@ -11223,6 +11253,7 @@ var TimePicker = (props) => {
|
|
|
11223
11253
|
}, [step, min, max]);
|
|
11224
11254
|
const close = React40.useCallback(() => setOpen(false), []);
|
|
11225
11255
|
useClickOutside_default([triggerRef, contentRef], close, isOpen);
|
|
11256
|
+
useCloseOnContainerScroll(isOpen, contentRef, close);
|
|
11226
11257
|
useEscapeKey(isOpen, () => {
|
|
11227
11258
|
close();
|
|
11228
11259
|
triggerRef.current?.focus({ preventScroll: true });
|
package/guidelines/MIGRATION.md
CHANGED
|
@@ -284,6 +284,43 @@ Dropdown · PopOver · TimePicker 가 전부 애니메이션 없이 툭 튀어
|
|
|
284
284
|
**올리기만 하면 된다. 쓰는 쪽에서 고칠 것은 없다.** `.repositioning` 클래스를
|
|
285
285
|
직접 잡는 CSS 를 쓰고 있었다면 그 규칙만 지우면 된다.
|
|
286
286
|
|
|
287
|
+
## 0.14.6 → 0.14.7 — 안쪽 상자가 스크롤되면 오버레이가 닫힌다
|
|
288
|
+
|
|
289
|
+
**동작 변경이다.** `Select` · `Dropdown` · `PopOver` · `TimePicker` 를 열어 둔
|
|
290
|
+
채 **페이지가 아닌 안쪽 상자**(`overflow: auto`)를 스크롤하면 닫힌다.
|
|
291
|
+
|
|
292
|
+
닫지 않는 경우:
|
|
293
|
+
|
|
294
|
+
- **페이지(문서) 스크롤** — 팝업이 `absolute` + 문서 좌표라 브라우저가 함께
|
|
295
|
+
옮겨 준다. 지연도 겹침도 없으므로 닫을 이유가 없다.
|
|
296
|
+
- **팝업 자기 목록 스크롤** — 셀렉트 목록은 `max-height` 안에서 스스로
|
|
297
|
+
스크롤한다. 그걸로 닫히면 고를 수가 없다.
|
|
298
|
+
|
|
299
|
+
### 왜
|
|
300
|
+
|
|
301
|
+
팝업은 `body` 밑 포털에 있고 트리거는 상자 안에 있다. 둘을 이어 주는 것이
|
|
302
|
+
브라우저에 없으므로 JS 가 매번 다시 붙여야 하는데, **스크롤은 컴포지터가
|
|
303
|
+
먼저 그리고 `scroll` 이벤트는 그 뒤에 메인 스레드로 온다.** 그래서 JS 로
|
|
304
|
+
옮기는 한 항상 한 프레임 이상 늦는다. 0.12.0 부터 0.14.6 까지 네 번에 걸쳐
|
|
305
|
+
줄여 봤지만(rAF 묶기 → DOM 직접 쓰기 → 문서 좌표 → rAF 제거) 원리가 그대로라
|
|
306
|
+
남았다.
|
|
307
|
+
|
|
308
|
+
두 번째 증상도 같은 뿌리다. 팝업이 상자 **바깥**에 있어 그 상자에 잘리지
|
|
309
|
+
않으므로, 트리거가 헤더 밑으로 지나가도 팝업만 헤더 위에 남는다. **위치가
|
|
310
|
+
틀린 것이지 순서가 틀린 게 아니라서 `z-index` 로는 못 고친다** — 헤더를
|
|
311
|
+
올리면 이번엔 목록 윗부분이 잘린다.
|
|
312
|
+
|
|
313
|
+
닫으면 둘 다 없어진다.
|
|
314
|
+
|
|
315
|
+
> **DS `Layout` 을 쓰면 이 조건에 기본으로 들어간다.**
|
|
316
|
+
> `.lib-xplat-layout` 이 `overflow: hidden` 이고 `.lib-xplat-layout-content`
|
|
317
|
+
> 만 `overflow: auto` 라 페이지가 아예 스크롤되지 않는다.
|
|
318
|
+
|
|
319
|
+
### 쓰는 쪽에서 할 일
|
|
320
|
+
|
|
321
|
+
**없다.** 다만 「열어 둔 채 스크롤하며 고르는」 흐름이 있었다면 그것만 막힌다.
|
|
322
|
+
목록이 길어 스크롤이 필요하다면 `Select` 의 `searchable` 로 걸러내는 쪽이 낫다.
|
|
323
|
+
|
|
287
324
|
---
|
|
288
325
|
|
|
289
326
|
## Next.js App Router 에서 반드시 걸리는 것
|