@ehfuse/mui-virtual-data-table 1.0.2 → 1.0.4
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/index.esm.js +75 -24
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +75 -24
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.esm.js
CHANGED
|
@@ -455,6 +455,7 @@ showScrollbar = true, }, ref) => {
|
|
|
455
455
|
const [dragStart, setDragStart] = useState({ y: 0, scrollTop: 0 });
|
|
456
456
|
const [thumbHeight, setThumbHeight] = useState(0);
|
|
457
457
|
const [thumbTop, setThumbTop] = useState(0);
|
|
458
|
+
const [hasScrollableContent, setHasScrollableContent] = useState(false);
|
|
458
459
|
// 드래그 스크롤 상태
|
|
459
460
|
const [isDragScrolling, setIsDragScrolling] = useState(false);
|
|
460
461
|
const [dragScrollStart, setDragScrollStart] = useState({
|
|
@@ -591,9 +592,33 @@ showScrollbar = true, }, ref) => {
|
|
|
591
592
|
return containerRef.current;
|
|
592
593
|
}
|
|
593
594
|
// children 요소에서 스크롤 가능한 요소 찾기
|
|
595
|
+
// 중첩된 OverlayScrollbar의 영역은 제외 (다른 OverlayScrollbar의 container는 스킵)
|
|
594
596
|
const childScrollableElements = containerRef.current.querySelectorAll('[data-virtuoso-scroller], [style*="overflow"], .virtuoso-scroller, [style*="overflow: auto"], [style*="overflow:auto"]');
|
|
595
597
|
for (const child of childScrollableElements) {
|
|
596
598
|
const element = child;
|
|
599
|
+
// 이 요소가 다른 OverlayScrollbar의 container인지 확인
|
|
600
|
+
// (자신의 containerRef는 아니어야 하고, overlay-scrollbar-container 클래스를 가진 경우)
|
|
601
|
+
if (element !== containerRef.current &&
|
|
602
|
+
element.classList.contains("overlay-scrollbar-container")) {
|
|
603
|
+
// 중첩된 OverlayScrollbar의 container이므로 스킵
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
// 이 요소의 부모 중에 다른 OverlayScrollbar container가 있는지 확인
|
|
607
|
+
let parent = element.parentElement;
|
|
608
|
+
let isNestedInAnotherScrollbar = false;
|
|
609
|
+
while (parent && parent !== containerRef.current) {
|
|
610
|
+
if (parent.classList.contains("overlay-scrollbar-container") &&
|
|
611
|
+
parent !== containerRef.current) {
|
|
612
|
+
// 다른 OverlayScrollbar 내부의 요소이므로 스킵
|
|
613
|
+
isNestedInAnotherScrollbar = true;
|
|
614
|
+
break;
|
|
615
|
+
}
|
|
616
|
+
parent = parent.parentElement;
|
|
617
|
+
}
|
|
618
|
+
if (isNestedInAnotherScrollbar) {
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
// 스크롤 가능한 요소인지 확인
|
|
597
622
|
if (element.scrollHeight > element.clientHeight + 2) {
|
|
598
623
|
cachedScrollContainerRef.current = element;
|
|
599
624
|
return element;
|
|
@@ -626,15 +651,18 @@ showScrollbar = true, }, ref) => {
|
|
|
626
651
|
}, [clearHideTimer, finalAutoHideConfig.enabled]);
|
|
627
652
|
// 스크롤바 위치 및 크기 업데이트
|
|
628
653
|
const updateScrollbar = useCallback(() => {
|
|
629
|
-
if (!scrollbarRef.current)
|
|
630
|
-
return;
|
|
631
654
|
const scrollableElement = findScrollableElement();
|
|
632
655
|
if (!scrollableElement) {
|
|
633
656
|
// 스크롤 불가능하면 숨김
|
|
634
657
|
setScrollbarVisible(false);
|
|
658
|
+
setHasScrollableContent(false);
|
|
635
659
|
clearHideTimer();
|
|
636
660
|
return;
|
|
637
661
|
}
|
|
662
|
+
// 스크롤 가능한 콘텐츠가 있음을 표시
|
|
663
|
+
setHasScrollableContent(true);
|
|
664
|
+
if (!scrollbarRef.current)
|
|
665
|
+
return;
|
|
638
666
|
// 자동 숨김이 비활성화되어 있으면 스크롤바를 항상 표시
|
|
639
667
|
if (!finalAutoHideConfig.enabled) {
|
|
640
668
|
setScrollbarVisible(true);
|
|
@@ -892,10 +920,25 @@ showScrollbar = true, }, ref) => {
|
|
|
892
920
|
const container = containerRef.current;
|
|
893
921
|
if (container && !scrollableElement) {
|
|
894
922
|
elementsToWatch.push(container);
|
|
895
|
-
// children 요소들의 스크롤도 감지
|
|
923
|
+
// children 요소들의 스크롤도 감지 (중첩된 OverlayScrollbar 제외)
|
|
896
924
|
const childScrollableElements = container.querySelectorAll('[data-virtuoso-scroller], [style*="overflow"], .virtuoso-scroller, [style*="overflow: auto"], [style*="overflow:auto"]');
|
|
897
925
|
childScrollableElements.forEach((child) => {
|
|
898
|
-
|
|
926
|
+
const element = child;
|
|
927
|
+
// 다른 OverlayScrollbar의 container는 제외
|
|
928
|
+
if (element !== container &&
|
|
929
|
+
element.classList.contains("overlay-scrollbar-container")) {
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
// 부모 중에 다른 OverlayScrollbar container가 있으면 제외
|
|
933
|
+
let parent = element.parentElement;
|
|
934
|
+
while (parent && parent !== container) {
|
|
935
|
+
if (parent.classList.contains("overlay-scrollbar-container") &&
|
|
936
|
+
parent !== container) {
|
|
937
|
+
return; // 중첩된 OverlayScrollbar 내부이므로 제외
|
|
938
|
+
}
|
|
939
|
+
parent = parent.parentElement;
|
|
940
|
+
}
|
|
941
|
+
elementsToWatch.push(element);
|
|
899
942
|
});
|
|
900
943
|
}
|
|
901
944
|
// 모든 요소에 이벤트 리스너 등록
|
|
@@ -1035,16 +1078,13 @@ showScrollbar = true, }, ref) => {
|
|
|
1035
1078
|
}, [updateScrollbar]);
|
|
1036
1079
|
// 컴포넌트 초기화 완료 표시 (hover 이벤트 활성화용)
|
|
1037
1080
|
useLayoutEffect(() => {
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
}
|
|
1046
|
-
}, 0);
|
|
1047
|
-
return () => clearTimeout(timer);
|
|
1081
|
+
setIsInitialized(true);
|
|
1082
|
+
// 초기화 직후 스크롤바 업데이트 (썸 높이 정확하게 계산)
|
|
1083
|
+
updateScrollbar();
|
|
1084
|
+
// 자동 숨김이 비활성화되어 있으면 스크롤바를 항상 표시
|
|
1085
|
+
if (!finalAutoHideConfig.enabled && isScrollable()) {
|
|
1086
|
+
setScrollbarVisible(true);
|
|
1087
|
+
}
|
|
1048
1088
|
}, [isScrollable, updateScrollbar, finalAutoHideConfig.enabled]);
|
|
1049
1089
|
// Resize observer로 크기 변경 감지
|
|
1050
1090
|
useEffect(() => {
|
|
@@ -1146,7 +1186,7 @@ showScrollbar = true, }, ref) => {
|
|
|
1146
1186
|
minHeight: 0, // flex shrink 허용
|
|
1147
1187
|
display: "flex", // flex 컨테이너로 설정
|
|
1148
1188
|
flexDirection: "column", // 세로 방향 정렬
|
|
1149
|
-
}, children: children }) }), showScrollbar && (jsxs("div", { ref: scrollbarRef, className: "overlay-scrollbar-track", onMouseEnter: () => {
|
|
1189
|
+
}, children: children }) }), showScrollbar && hasScrollableContent && (jsxs("div", { ref: scrollbarRef, className: "overlay-scrollbar-track", onMouseEnter: () => {
|
|
1150
1190
|
clearHideTimer();
|
|
1151
1191
|
setScrollbarVisible(true);
|
|
1152
1192
|
}, onMouseLeave: () => {
|
|
@@ -1209,7 +1249,7 @@ showScrollbar = true, }, ref) => {
|
|
|
1209
1249
|
borderRadius: `${finalThumbConfig.radius}px`,
|
|
1210
1250
|
cursor: "pointer",
|
|
1211
1251
|
transition: "background-color 0.2s ease-in-out, opacity 0.2s ease-in-out",
|
|
1212
|
-
} })] })), showScrollbar && showArrows && (jsx("div", { className: "overlay-scrollbar-up-arrow", onClick: handleUpArrowClick, onMouseEnter: () => setHoveredArrow("up"), onMouseLeave: () => setHoveredArrow(null), style: {
|
|
1252
|
+
} })] })), showScrollbar && hasScrollableContent && showArrows && (jsx("div", { className: "overlay-scrollbar-up-arrow", onClick: handleUpArrowClick, onMouseEnter: () => setHoveredArrow("up"), onMouseLeave: () => setHoveredArrow(null), style: {
|
|
1213
1253
|
position: "absolute",
|
|
1214
1254
|
top: `${finalTrackConfig.margin}px`,
|
|
1215
1255
|
right: finalTrackConfig.alignment === "right"
|
|
@@ -1235,7 +1275,7 @@ showScrollbar = true, }, ref) => {
|
|
|
1235
1275
|
: finalArrowsConfig.opacity
|
|
1236
1276
|
: 0,
|
|
1237
1277
|
transition: "opacity 0.2s ease-in-out, color 0.15s ease-in-out",
|
|
1238
|
-
}, children: "\u25B2" })), showScrollbar && showArrows && (jsx("div", { className: "overlay-scrollbar-down-arrow", onClick: handleDownArrowClick, onMouseEnter: () => setHoveredArrow("down"), onMouseLeave: () => setHoveredArrow(null), style: {
|
|
1278
|
+
}, children: "\u25B2" })), showScrollbar && hasScrollableContent && showArrows && (jsx("div", { className: "overlay-scrollbar-down-arrow", onClick: handleDownArrowClick, onMouseEnter: () => setHoveredArrow("down"), onMouseLeave: () => setHoveredArrow(null), style: {
|
|
1239
1279
|
position: "absolute",
|
|
1240
1280
|
bottom: `${finalTrackConfig.margin}px`,
|
|
1241
1281
|
right: finalTrackConfig.alignment === "right"
|
|
@@ -1363,6 +1403,7 @@ function VirtualDataTableComponent({ data, loading = false, columns, onRowClick,
|
|
|
1363
1403
|
const initialScrollTopRef = useRef(0);
|
|
1364
1404
|
const totalDragDistanceRef = useRef(0);
|
|
1365
1405
|
const isScrollDraggingRef = useRef(false); // OverlayScrollbar 드래그 스크롤 감지용
|
|
1406
|
+
const mouseDownPositionRef = useRef({ x: 0, y: 0 }); // 마우스 다운 시작 위치
|
|
1366
1407
|
useRef(null);
|
|
1367
1408
|
/**
|
|
1368
1409
|
* 마우스 버튼 누름 이벤트 핸들러
|
|
@@ -1473,8 +1514,8 @@ function VirtualDataTableComponent({ data, loading = false, columns, onRowClick,
|
|
|
1473
1514
|
return;
|
|
1474
1515
|
}
|
|
1475
1516
|
window.lastRangeChangeTime = now;
|
|
1476
|
-
// 더 보수적인 조건:
|
|
1477
|
-
const bufferSize = Math.max(10, Math.floor(data.length * 0.
|
|
1517
|
+
// 더 보수적인 조건: 90% 지점에서 로드 (기존 VirtualDataTable 방식)
|
|
1518
|
+
const bufferSize = Math.max(10, Math.floor(data.length * 0.1)); // 데이터의 10% 또는 최소 10개
|
|
1478
1519
|
const shouldLoadMore = range.endIndex >= data.length - bufferSize;
|
|
1479
1520
|
// 추가 조건: 최소 30개 이상의 데이터에서만 더 가져오기 실행
|
|
1480
1521
|
const hasMinimumData = data.length >= 30;
|
|
@@ -1798,12 +1839,22 @@ function VirtualDataTableComponent({ data, loading = false, columns, onRowClick,
|
|
|
1798
1839
|
// react-virtuoso는 'data-index' 속성으로 index를 전달합니다
|
|
1799
1840
|
const rowIndex = rest["data-index"] ?? 0;
|
|
1800
1841
|
const isOddRow = rowIndex % 2 === 1;
|
|
1801
|
-
return (jsx(TableRow, { ...rest, onMouseDown: () => {
|
|
1802
|
-
// 마우스 다운 시 드래그 플래그 초기화
|
|
1842
|
+
return (jsx(TableRow, { ...rest, onMouseDown: (e) => {
|
|
1843
|
+
// 마우스 다운 시 드래그 플래그 초기화 및 시작 위치 저장
|
|
1803
1844
|
isScrollDraggingRef.current = false;
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1845
|
+
mouseDownPositionRef.current = {
|
|
1846
|
+
x: e.clientX,
|
|
1847
|
+
y: e.clientY,
|
|
1848
|
+
};
|
|
1849
|
+
}, onMouseMove: (e) => {
|
|
1850
|
+
// 마우스가 5px 이상 움직였을 때만 드래그로 간주
|
|
1851
|
+
const deltaX = Math.abs(e.clientX - mouseDownPositionRef.current.x);
|
|
1852
|
+
const deltaY = Math.abs(e.clientY - mouseDownPositionRef.current.y);
|
|
1853
|
+
const dragThreshold = 5; // 5px 임계값
|
|
1854
|
+
if (deltaX > dragThreshold ||
|
|
1855
|
+
deltaY > dragThreshold) {
|
|
1856
|
+
isScrollDraggingRef.current = true;
|
|
1857
|
+
}
|
|
1807
1858
|
}, onClick: () => {
|
|
1808
1859
|
// 드래그 스크롤이 아니고, 아이템이 있고, onRowClick이 있을 때만 실행
|
|
1809
1860
|
if (!isScrollDraggingRef.current &&
|