@ehfuse/mui-virtual-data-table 1.0.3 → 1.0.5

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
@@ -421,7 +421,7 @@ const DEFAULT_TRACK_CONFIG = {};
421
421
  const DEFAULT_ARROWS_CONFIG = {};
422
422
  const DEFAULT_DRAG_SCROLL_CONFIG = {};
423
423
  const DEFAULT_AUTO_HIDE_CONFIG = {};
424
- const OverlayScrollbar = forwardRef(({ className = "", style = {}, children, onScroll,
424
+ const OverlayScrollbar = forwardRef(({ className = "", style = {}, containerStyle = {}, contentStyle = {}, children, onScroll,
425
425
  // 그룹화된 설정 객체들
426
426
  thumb = DEFAULT_THUMB_CONFIG, track = DEFAULT_TRACK_CONFIG, arrows = DEFAULT_ARROWS_CONFIG, dragScroll = DEFAULT_DRAG_SCROLL_CONFIG, autoHide = DEFAULT_AUTO_HIDE_CONFIG,
427
427
  // 기타 설정들
@@ -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(() => {
@@ -1129,24 +1169,11 @@ showScrollbar = true, }, ref) => {
1129
1169
  }
1130
1170
  };
1131
1171
  }, []);
1132
- return (jsxs("div", { className: `overlay-scrollbar-wrapper ${className}`, style: Object.assign({ display: "flex", flexDirection: "column", position: "relative", minHeight: 0, height: "100%", flex: "1 1 0%" }, style), children: [jsx("div", { ref: containerRef, className: "overlay-scrollbar-container", tabIndex: -1, onMouseDown: handleDragScrollStart, style: {
1133
- width: "100%", // 명시적 너비 설정
1134
- height: "100%", // 부모의 전체 높이 사용
1135
- flex: "1 1 auto", // flex item으로 설정하여 높이를 자동으로 계산
1136
- minHeight: 0, // 최소 높이 보장
1137
- overflow: "auto", // 네이티브 스크롤 기능 유지
1172
+ return (jsxs("div", { className: `overlay-scrollbar-wrapper ${className}`, style: Object.assign({ display: "flex", flexDirection: "column", position: "relative", minHeight: 0, height: "100%", flex: "1 1 0%" }, style), children: [jsx("div", { ref: containerRef, className: "overlay-scrollbar-container", tabIndex: -1, onMouseDown: handleDragScrollStart, style: Object.assign({ width: "100%", height: "100%", flex: "1 1 auto", minHeight: 0, overflow: "auto",
1138
1173
  // 브라우저 기본 스크롤바만 숨기기
1139
- scrollbarWidth: "none", // Firefox
1140
- msOverflowStyle: "none", // IE/Edge
1174
+ scrollbarWidth: "none", msOverflowStyle: "none",
1141
1175
  // 키보드 포커스 스타일 (접근성)
1142
- outline: "none", // 기본 아웃라인 제거
1143
- userSelect: isDragScrolling ? "none" : "auto", // 드래그 중 텍스트 선택 방지
1144
- }, children: jsx("div", { ref: contentRef, className: "overlay-scrollbar-content", style: {
1145
- height: "100%", // min-height 대신 height 사용
1146
- minHeight: 0, // flex shrink 허용
1147
- display: "flex", // flex 컨테이너로 설정
1148
- flexDirection: "column", // 세로 방향 정렬
1149
- }, children: children }) }), showScrollbar && (jsxs("div", { ref: scrollbarRef, className: "overlay-scrollbar-track", onMouseEnter: () => {
1176
+ outline: "none", userSelect: isDragScrolling ? "none" : "auto" }, containerStyle), children: jsx("div", { ref: contentRef, className: "overlay-scrollbar-content", style: Object.assign({ minHeight: 0, display: "flex", flexDirection: "column" }, contentStyle), children: children }) }), showScrollbar && hasScrollableContent && (jsxs("div", { ref: scrollbarRef, className: "overlay-scrollbar-track", onMouseEnter: () => {
1150
1177
  clearHideTimer();
1151
1178
  setScrollbarVisible(true);
1152
1179
  }, onMouseLeave: () => {
@@ -1209,7 +1236,7 @@ showScrollbar = true, }, ref) => {
1209
1236
  borderRadius: `${finalThumbConfig.radius}px`,
1210
1237
  cursor: "pointer",
1211
1238
  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: {
1239
+ } })] })), showScrollbar && hasScrollableContent && showArrows && (jsx("div", { className: "overlay-scrollbar-up-arrow", onClick: handleUpArrowClick, onMouseEnter: () => setHoveredArrow("up"), onMouseLeave: () => setHoveredArrow(null), style: {
1213
1240
  position: "absolute",
1214
1241
  top: `${finalTrackConfig.margin}px`,
1215
1242
  right: finalTrackConfig.alignment === "right"
@@ -1235,7 +1262,7 @@ showScrollbar = true, }, ref) => {
1235
1262
  : finalArrowsConfig.opacity
1236
1263
  : 0,
1237
1264
  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: {
1265
+ }, children: "\u25B2" })), showScrollbar && hasScrollableContent && showArrows && (jsx("div", { className: "overlay-scrollbar-down-arrow", onClick: handleDownArrowClick, onMouseEnter: () => setHoveredArrow("down"), onMouseLeave: () => setHoveredArrow(null), style: {
1239
1266
  position: "absolute",
1240
1267
  bottom: `${finalTrackConfig.margin}px`,
1241
1268
  right: finalTrackConfig.alignment === "right"
@@ -1284,7 +1311,7 @@ function VirtualDataTableComponent({ data, loading = false, columns, onRowClick,
1284
1311
  // 각 테이블 인스턴스별로 Scroller 컴포넌트 생성 (scrollbars, paddingX를 초기값으로 고정)
1285
1312
  const VirtuosoScroller = useMemo(() => forwardRef((props, ref) => {
1286
1313
  const scrollContainerRef = useRef(null);
1287
- return (jsx(OverlayScrollbar, { track: OVERLAY_SCROLLBAR_TRACK_CONFIG, ...scrollbars, children: jsx(TableContainer, { component: Box, ...props, ref: (node) => {
1314
+ return (jsx(OverlayScrollbar, { track: OVERLAY_SCROLLBAR_TRACK_CONFIG, contentStyle: { height: "100%" }, ...scrollbars, children: jsx(TableContainer, { component: Box, ...props, ref: (node) => {
1288
1315
  scrollContainerRef.current =
1289
1316
  node;
1290
1317
  if (typeof ref === "function") {