@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 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
- elementsToWatch.push(child);
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
- const timer = setTimeout(() => {
1039
- setIsInitialized(true);
1040
- // 초기화 후 스크롤바 업데이트 (썸 높이 정확하게 계산)
1041
- updateScrollbar();
1042
- // 자동 숨김이 비활성화되어 있으면 스크롤바를 항상 표시
1043
- if (!finalAutoHideConfig.enabled && isScrollable()) {
1044
- setScrollbarVisible(true);
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
- // 더 보수적인 조건: 85% 지점에서 로드 (기존 VirtualDataTable 방식)
1477
- const bufferSize = Math.max(10, Math.floor(data.length * 0.15)); // 데이터의 15% 또는 최소 10개
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
- }, onMouseMove: () => {
1805
- // 마우스가 눌린 상태로 움직이면 드래그로 간주
1806
- isScrollDraggingRef.current = true;
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 &&