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