jattac.libs.web.responsive-table 0.7.5 → 0.8.0
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/README.md +58 -1095
- package/dist/Data/IGroupedRowDefinition.d.ts +10 -0
- package/dist/Data/IResponsiveTableColumnDefinition.d.ts +1 -0
- package/dist/Hooks/useResponsiveTable.d.ts +14 -0
- package/dist/Hooks/useTablePlugins.d.ts +39 -0
- package/dist/Plugins/GroupingPlugin.d.ts +17 -0
- package/dist/Plugins/HighlightPlugin.d.ts +23 -0
- package/dist/UI/DesktopView.d.ts +42 -0
- package/dist/UI/InfiniteTable.d.ts +16 -42
- package/dist/UI/MobileView.d.ts +29 -0
- package/dist/UI/ResponsiveTable.d.ts +8 -45
- package/dist/UI/ResponsiveTable.stories.d.ts +17 -0
- package/dist/UI/ResponsiveTable.test.d.ts +1 -0
- package/dist/UI/SkeletonView.d.ts +8 -0
- package/dist/index.js +709 -673
- package/dist/index.js.map +1 -1
- package/docs/api.md +65 -0
- package/docs/breaking-changes.md +28 -0
- package/docs/configuration.md +61 -0
- package/docs/custom-plugin-example.md +123 -0
- package/docs/development.md +40 -0
- package/docs/examples.md +207 -0
- package/docs/features.md +33 -0
- package/package.json +117 -71
package/dist/index.js
CHANGED
|
@@ -75,10 +75,189 @@ function styleInject(css, ref) {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
var css_248z$2 = "/* Using CSS variables for a more maintainable and themeable design */\n.ResponsiveTable-module_responsiveTable__4y-Od {\n --table-border-color: #e0e0e0;\n --table-header-bg: #f8f9fa;\n --table-row-hover-bg: #e9ecef;\n --table-row-stripe-bg: #f2f2f2;\n --card-bg: #ffffff;\n --card-border-color: #e0e0e0;\n --card-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);\n --text-color: #212529;\n --text-color-muted: #6c757d;\n --interactive-color: #0056b3;\n}\n\n/* Mobile Card View */\n.ResponsiveTable-module_card__b-U2v {\n background-color: var(--card-bg);\n border: 1px solid var(--card-border-color);\n margin-bottom: 1rem;\n border-radius: 8px;\n overflow: hidden;\n box-shadow: var(--card-shadow);\n transition: box-shadow 0.2s ease-in-out;\n}\n\n.ResponsiveTable-module_card__b-U2v:hover {\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n}\n\n/* This is not used in the component, but keeping it styled in case it's added back */\n.ResponsiveTable-module_card-header__Ttk51 {\n background-color: var(--table-header-bg);\n padding: 0.75rem 1rem;\n font-weight: 600; /* Bolder */\n border-bottom: 1px solid var(--table-border-color);\n}\n\n.ResponsiveTable-module_card-body__XIy0h {\n padding: 1rem;\n font-size: 0.9rem;\n}\n\n.ResponsiveTable-module_card-body__XIy0h p {\n margin: 0 0 0.75rem;\n display: flex;\n justify-content: space-between;\n}\n\n.ResponsiveTable-module_card-body__XIy0h p:last-child {\n margin-bottom: 0;\n}\n\n.ResponsiveTable-module_card-label__v9L71 {\n font-weight: 600;\n color: var(--text-color);\n margin-right: 0.5rem;\n}\n\n/* Desktop Table View */\n.ResponsiveTable-module_responsiveTable__4y-Od {\n width: 100%;\n border-collapse: collapse;\n color: var(--text-color);\n}\n\n.ResponsiveTable-module_responsiveTable__4y-Od thead th {\n background-color: var(--table-header-bg);\n /* position: sticky; <-- REMOVED to prevent conflict with transform */\n /* top: 0; <-- REMOVED */\n z-index: 1;\n font-weight: 600;\n text-align: left;\n padding: 1rem; /* Keep padding for the th itself */\n border-bottom: 2px solid var(--table-border-color);\n}\n\n.ResponsiveTable-module_headerInnerWrapper__3VAhD {\n display: flex; /* Use flexbox for alignment within the th */\n align-items: center; /* Vertically center content */\n justify-content: space-between; /* Push icon to the right */\n width: 100%; /* Ensure it takes full width of th */\n position: relative; /* Contain the absolutely positioned sort icon */\n}\n\n.ResponsiveTable-module_headerContent__ODMzS {\n flex-grow: 1; /* Allow content to take available space */\n overflow: hidden; /* Hide overflowing content */\n text-overflow: ellipsis; /* Show ellipsis for truncated text */\n white-space: nowrap; /* Prevent text from wrapping */\n}\n\n.ResponsiveTable-module_responsiveTable__4y-Od td {\n padding: 1rem;\n border-bottom: 1px solid var(--table-border-color);\n text-align: left;\n}\n\n.ResponsiveTable-module_responsiveTable__4y-Od tr {\n background-color: var(--card-bg);\n transition: background-color 0.2s ease-in-out;\n}\n\n/* Add a transition for the card view as well */\n.ResponsiveTable-module_card__b-U2v {\n background-color: var(--card-bg);\n border: 1px solid var(--card-border-color);\n margin-bottom: 1rem;\n border-radius: 8px;\n overflow: hidden;\n box-shadow: var(--card-shadow);\n transition: box-shadow 0.2s ease-in-out, background-color 0.2s ease-in-out;\n border-left: 5px solid transparent; /* Add transparent border for smooth transition */\n}\n\n/* Style for selected rows */\n.ResponsiveTable-module_selectedRow__-JyNW {\n /* Use a variable for the selection color to allow theming */\n --table-row-selected-bg: #ddeaff;\n background-color: var(--table-row-selected-bg) !important; /* Use important to override hover and stripe styles */\n}\n\n/* In mobile view, we use the border for selection indication */\n.ResponsiveTable-module_card__b-U2v.ResponsiveTable-module_selectedRow__-JyNW {\n border-left: 5px solid var(--interactive-color);\n background-color: var(--table-row-selected-bg);\n}\n\n/* Subtle striping for better readability */\n.ResponsiveTable-module_responsiveTable__4y-Od tr:nth-child(even) {\n background-color: var(--table-row-stripe-bg);\n}\n\n\n/* Modern hover effect */\n.ResponsiveTable-module_responsiveTable__4y-Od tr:hover {\n background-color: var(--table-row-hover-bg);\n}\n\n/* Clickable Row Style */\n.ResponsiveTable-module_clickableRow__0kjWm {\n cursor: pointer;\n}\n\n/* Clickable Header Style */\n.ResponsiveTable-module_clickableHeader__xHQhF {\n cursor: pointer;\n color: var(--interactive-color);\n}\n\n.ResponsiveTable-module_clickableHeader__xHQhF:hover {\n text-decoration: underline;\n}\n\n/* Sortable Header Styles */\n.ResponsiveTable-module_responsiveTable__4y-Od th.ResponsiveTable-module_sortable__yvA60,\n.ResponsiveTable-module_responsiveTable__4y-Od th.ResponsiveTable-module_sorted-asc__jzOIa,\n.ResponsiveTable-module_responsiveTable__4y-Od th.ResponsiveTable-module_sorted-desc__7WCFK {\n cursor: pointer;\n /* position: relative; <-- REMOVED to fix sticky header bug */\n transition: transform 0.2s ease-in-out; /* Add transition for smooth scaling */\n}\n\n.ResponsiveTable-module_responsiveTable__4y-Od th.ResponsiveTable-module_sortable__yvA60:hover,\n.ResponsiveTable-module_responsiveTable__4y-Od th.ResponsiveTable-module_sorted-asc__jzOIa:hover,\n.ResponsiveTable-module_responsiveTable__4y-Od th.ResponsiveTable-module_sorted-desc__7WCFK:hover {\n transform: scale(1.01); /* Subtle zoom effect on hover */\n}\n\n.ResponsiveTable-module_responsiveTable__4y-Od th .ResponsiveTable-module_sortIcon__A9WtD {\n width: 1rem;\n height: 1rem;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n opacity: 0.4; /* Ghosted by default */\n transition: opacity 0.2s ease-in-out;\n margin-left: 0.5rem; /* Add a small gap between text and icon */\n}\n\n/* Default unsorted icon (funnel) */\n.ResponsiveTable-module_responsiveTable__4y-Od th.ResponsiveTable-module_sortable__yvA60 .ResponsiveTable-module_sortIcon__A9WtD {\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%236c757d'%3E%3Cpath d='M10 18h4v-2h-4v2zm-6-8v2h16V8H4zm3-6h10v2H7V2z'/%3E%3C/svg%3E\");\n}\n\n.ResponsiveTable-module_responsiveTable__4y-Od th.ResponsiveTable-module_sortable__yvA60:hover .ResponsiveTable-module_sortIcon__A9WtD {\n opacity: 1;\n}\n\n/* Ensure cursor applies to content within sortable header */\n.ResponsiveTable-module_responsiveTable__4y-Od th.ResponsiveTable-module_sortable__yvA60 > * {\n cursor: inherit;\n}\n\n/* Sorted Ascending Icon */\n.ResponsiveTable-module_responsiveTable__4y-Od th.ResponsiveTable-module_sorted-asc__jzOIa .ResponsiveTable-module_sortIcon__A9WtD {\n opacity: 1; /* Always prominent when sorted */\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%230056b3'%3E%3Cpath d='M7 14l5-5 5 5z'/%3E%3C/svg%3E\");\n}\n\n/* Sorted Descending Icon */\n.ResponsiveTable-module_responsiveTable__4y-Od th.ResponsiveTable-module_sorted-desc__7WCFK .ResponsiveTable-module_sortIcon__A9WtD {\n opacity: 1; /* Always prominent when sorted */\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%230056b3'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E\");\n}\n\n.ResponsiveTable-module_responsiveTable__4y-Od tfoot {\n background-color: var(--table-header-bg);\n border-top: 2px solid var(--table-border-color);\n font-weight: 600;\n}\n\n.ResponsiveTable-module_footerCell__8H-uG {\n padding: 1rem;\n text-align: right;\n font-weight: 600;\n}\n\n.ResponsiveTable-module_clickableFooterCell__WB9Ss {\n cursor: pointer;\n color: var(--interactive-color);\n}\n\n.ResponsiveTable-module_clickableFooterCell__WB9Ss:hover {\n text-decoration: underline;\n}\n\n.ResponsiveTable-module_footerCard__-NE2M {\n background-color: var(--table-header-bg);\n border: 1px solid var(--card-border-color);\n border-top: 2px solid var(--table-border-color);\n margin-bottom: 1rem;\n border-radius: 8px;\n overflow: hidden;\n box-shadow: none;\n}\n\n.ResponsiveTable-module_footer-card-body__CtBMv {\n padding: 1rem;\n font-size: 0.9rem;\n}\n\n.ResponsiveTable-module_footer-card-row__Vv6Ur {\n margin: 0 0 0.75rem;\n display: flex;\n justify-content: space-between;\n font-weight: 600;\n}\n\n/* No Data State */\n\n/* Staggered Entrance Animation */\n.ResponsiveTable-module_animatedRow__SFjrJ {\n animation: ResponsiveTable-module_fadeInUp__jMCS7 0.5s ease-out forwards;\n opacity: 0;\n}\n\n@keyframes ResponsiveTable-module_fadeInUp__jMCS7 {\n from {\n opacity: 0;\n transform: translateY(20px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n/* Skeleton Loader */\n.ResponsiveTable-module_skeleton__XxsXW {\n background-color: #e0e0e0;\n border-radius: 4px;\n position: relative;\n overflow: hidden;\n}\n\n.ResponsiveTable-module_skeleton__XxsXW::after {\n content: '';\n position: absolute;\n top: 0;\n left: -150%;\n width: 150%;\n height: 100%;\n background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);\n animation: ResponsiveTable-module_shimmer__H8PhC 1.5s infinite;\n}\n\n.ResponsiveTable-module_skeletonText__T-Lgq {\n height: 1.2em;\n width: 100%;\n}\n\n.ResponsiveTable-module_skeletonCard__AYVwL {\n background-color: var(--card-bg);\n border: 1px solid var(--card-border-color);\n margin-bottom: 1rem;\n border-radius: 8px;\n overflow: hidden;\n padding: 1rem;\n}\n\n@keyframes ResponsiveTable-module_shimmer__H8PhC {\n 0% {\n transform: translateX(0);\n }\n 100% {\n transform: translateX(100%);\n }\n}\n.ResponsiveTable-module_noDataWrapper__Rj-k3 {\n color: var(--text-color-muted); /* Softer color */\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100%;\n gap: 10px;\n padding: 40px;\n background-color: var(--table-header-bg);\n border: 1px dashed var(--table-border-color);\n border-radius: 8px;\n}\n\n.ResponsiveTable-module_noData__IpwNq {\n text-align: center;\n font-weight: 500; /* Less aggressive than bold */\n font-size: 1rem;\n}\n\n.ResponsiveTable-module_row-exit__EVX6T {\n opacity: 0;\n transform: scaleY(0);\n transition: transform 0.3s ease-out, opacity 0.3s ease-out;\n}\n\n.ResponsiveTable-module_row-enter__YKgI4 {\n opacity: 0;\n transform: translateY(20px);\n transition: transform 0.5s ease-out, opacity 0.5s ease-out;\n}\n\n.ResponsiveTable-module_row-flash__a4NOm {\n animation: ResponsiveTable-module_flash__nxeAX 0.5s ease-out;\n}\n\n@keyframes ResponsiveTable-module_flash__nxeAX {\n 0% {\n background-color: var(--table-row-hover-bg);\n }\n 100% {\n background-color: transparent;\n }\n}\n\n.ResponsiveTable-module_stickyHeader__-jjN- th {\n position: sticky;\n top: 0;\n z-index: 1;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n}\n\n.ResponsiveTable-module_internalStickyHeader__idiJY th {\n position: sticky;\n top: 0;\n z-index: 1;\n}\n/* Spinner for Loading More */\n.ResponsiveTable-module_spinner__Pn-3D {\n border: 4px solid rgba(0, 0, 0, 0.1);\n border-left-color: var(--interactive-color); /* Use theme color */\n border-radius: 50%;\n width: 24px;\n height: 24px;\n animation: ResponsiveTable-module_spin__i3NHn 1s linear infinite;\n display: inline-block;\n vertical-align: middle;\n margin-right: 8px;\n}\n\n@keyframes ResponsiveTable-module_spin__i3NHn {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n}\n\n/* Loading/No More Data Container */\n.ResponsiveTable-module_infoContainer__b9IF5 {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 20px;\n color: var(--text-color-muted);\n font-size: 0.9rem;\n gap: 8px;\n background-color: var(--table-header-bg);\n border-top: 1px solid var(--table-border-color);\n}\n\n.ResponsiveTable-module_infoContainer__b9IF5.ResponsiveTable-module_noMoreData__he1rZ {\n font-weight: 500;\n}";
|
|
78
|
+
var css_248z$2 = "/* Using CSS variables for a more maintainable and themeable design */\r\n.ResponsiveTable-module_responsiveTable__4y-Od {\r\n --table-border-color: #e0e0e0;\r\n --table-header-bg: #f8f9fa;\r\n --table-row-hover-bg: #e9ecef;\r\n --table-row-stripe-bg: #f2f2f2;\r\n --card-bg: #ffffff;\r\n --card-border-color: #e0e0e0;\r\n --card-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);\r\n --text-color: #212529;\r\n --text-color-muted: #6c757d;\r\n --interactive-color: #0056b3;\r\n}\r\n\r\n/* Mobile Card View */\r\n.ResponsiveTable-module_card__b-U2v {\r\n background-color: var(--card-bg);\r\n border: 1px solid var(--card-border-color);\r\n margin-bottom: 1rem;\r\n border-radius: 8px;\r\n overflow: hidden;\r\n box-shadow: var(--card-shadow);\r\n transition: box-shadow 0.2s ease-in-out;\r\n}\r\n\r\n.ResponsiveTable-module_card__b-U2v:hover {\r\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n/* This is not used in the component, but keeping it styled in case it's added back */\r\n.ResponsiveTable-module_card-header__Ttk51 {\r\n background-color: var(--table-header-bg);\r\n padding: 0.75rem 1rem;\r\n font-weight: 600; /* Bolder */\r\n border-bottom: 1px solid var(--table-border-color);\r\n}\r\n\r\n.ResponsiveTable-module_card-body__XIy0h {\r\n padding: 1rem;\r\n font-size: 0.9rem;\r\n}\r\n\r\n.ResponsiveTable-module_card-body__XIy0h p {\r\n margin: 0 0 0.75rem;\r\n display: flex;\r\n justify-content: space-between;\r\n}\r\n\r\n.ResponsiveTable-module_card-body__XIy0h p:last-child {\r\n margin-bottom: 0;\r\n}\r\n\r\n.ResponsiveTable-module_card-label__v9L71 {\r\n font-weight: 600;\r\n color: var(--text-color);\r\n margin-right: 0.5rem;\r\n}\r\n\r\n/* Desktop Table View */\r\n.ResponsiveTable-module_responsiveTable__4y-Od {\r\n width: 100%;\r\n border-collapse: collapse;\r\n color: var(--text-color);\r\n}\r\n\r\n.ResponsiveTable-module_responsiveTable__4y-Od thead th {\r\n background-color: var(--table-header-bg);\r\n /* position: sticky; <-- REMOVED to prevent conflict with transform */\r\n /* top: 0; <-- REMOVED */\r\n z-index: 1;\r\n font-weight: 600;\r\n text-align: left;\r\n padding: 1rem; /* Keep padding for the th itself */\r\n border-bottom: 2px solid var(--table-border-color);\r\n}\r\n\r\n.ResponsiveTable-module_headerInnerWrapper__3VAhD {\r\n display: flex; /* Use flexbox for alignment within the th */\r\n align-items: center; /* Vertically center content */\r\n justify-content: space-between; /* Push icon to the right */\r\n width: 100%; /* Ensure it takes full width of th */\r\n position: relative; /* Contain the absolutely positioned sort icon */\r\n}\r\n\r\n.ResponsiveTable-module_headerContent__ODMzS {\r\n flex-grow: 1; /* Allow content to take available space */\r\n overflow: hidden; /* Hide overflowing content */\r\n text-overflow: ellipsis; /* Show ellipsis for truncated text */\r\n white-space: nowrap; /* Prevent text from wrapping */\r\n}\r\n\r\n.ResponsiveTable-module_responsiveTable__4y-Od td {\r\n padding: 1rem;\r\n border-bottom: 1px solid var(--table-border-color);\r\n text-align: left;\r\n}\r\n\r\n.ResponsiveTable-module_responsiveTable__4y-Od tr {\r\n background-color: var(--card-bg);\r\n transition: background-color 0.2s ease-in-out;\r\n}\r\n\r\n/* Add a transition for the card view as well */\r\n.ResponsiveTable-module_card__b-U2v {\r\n background-color: var(--card-bg);\r\n border: 1px solid var(--card-border-color);\r\n margin-bottom: 1rem;\r\n border-radius: 8px;\r\n overflow: hidden;\r\n box-shadow: var(--card-shadow);\r\n transition: box-shadow 0.2s ease-in-out, background-color 0.2s ease-in-out;\r\n border-left: 5px solid transparent; /* Add transparent border for smooth transition */\r\n}\r\n\r\n/* Style for selected rows */\r\n.ResponsiveTable-module_selectedRow__-JyNW {\r\n /* Use a variable for the selection color to allow theming */\r\n --table-row-selected-bg: #ddeaff;\r\n background-color: var(--table-row-selected-bg) !important; /* Use important to override hover and stripe styles */\r\n}\r\n\r\n/* In mobile view, we use the border for selection indication */\r\n.ResponsiveTable-module_card__b-U2v.ResponsiveTable-module_selectedRow__-JyNW {\r\n border-left: 5px solid var(--interactive-color);\r\n background-color: var(--table-row-selected-bg);\r\n}\r\n\r\n/* Subtle striping for better readability */\r\n.ResponsiveTable-module_responsiveTable__4y-Od tr:nth-child(even) {\r\n background-color: var(--table-row-stripe-bg);\r\n}\r\n\r\n\r\n/* Modern hover effect */\r\n.ResponsiveTable-module_responsiveTable__4y-Od tr:hover {\r\n background-color: var(--table-row-hover-bg);\r\n}\r\n\r\n/* Clickable Row Style */\r\n.ResponsiveTable-module_clickableRow__0kjWm {\r\n cursor: pointer;\r\n}\r\n\r\n/* Clickable Header Style */\r\n.ResponsiveTable-module_clickableHeader__xHQhF {\r\n cursor: pointer;\r\n color: var(--interactive-color);\r\n}\r\n\r\n.ResponsiveTable-module_clickableHeader__xHQhF:hover {\r\n text-decoration: underline;\r\n}\r\n\r\n/* Sortable Header Styles */\r\n.ResponsiveTable-module_responsiveTable__4y-Od th.ResponsiveTable-module_sortable__yvA60,\r\n.ResponsiveTable-module_responsiveTable__4y-Od th.ResponsiveTable-module_sorted-asc__jzOIa,\r\n.ResponsiveTable-module_responsiveTable__4y-Od th.ResponsiveTable-module_sorted-desc__7WCFK {\r\n cursor: pointer;\r\n /* position: relative; <-- REMOVED to fix sticky header bug */\r\n transition: transform 0.2s ease-in-out; /* Add transition for smooth scaling */\r\n}\r\n\r\n.ResponsiveTable-module_responsiveTable__4y-Od th.ResponsiveTable-module_sortable__yvA60:hover,\r\n.ResponsiveTable-module_responsiveTable__4y-Od th.ResponsiveTable-module_sorted-asc__jzOIa:hover,\r\n.ResponsiveTable-module_responsiveTable__4y-Od th.ResponsiveTable-module_sorted-desc__7WCFK:hover {\r\n transform: scale(1.01); /* Subtle zoom effect on hover */\r\n}\r\n\r\n.ResponsiveTable-module_responsiveTable__4y-Od th .ResponsiveTable-module_sortIcon__A9WtD {\r\n width: 1rem;\r\n height: 1rem;\r\n background-size: contain;\r\n background-repeat: no-repeat;\r\n background-position: center;\r\n opacity: 0.4; /* Ghosted by default */\r\n transition: opacity 0.2s ease-in-out;\r\n margin-left: 0.5rem; /* Add a small gap between text and icon */\r\n}\r\n\r\n/* Default unsorted icon (funnel) */\r\n.ResponsiveTable-module_responsiveTable__4y-Od th.ResponsiveTable-module_sortable__yvA60 .ResponsiveTable-module_sortIcon__A9WtD {\r\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%236c757d'%3E%3Cpath d='M10 18h4v-2h-4v2zm-6-8v2h16V8H4zm3-6h10v2H7V2z'/%3E%3C/svg%3E\");\r\n}\r\n\r\n.ResponsiveTable-module_responsiveTable__4y-Od th.ResponsiveTable-module_sortable__yvA60:hover .ResponsiveTable-module_sortIcon__A9WtD {\r\n opacity: 1;\r\n}\r\n\r\n/* Ensure cursor applies to content within sortable header */\r\n.ResponsiveTable-module_responsiveTable__4y-Od th.ResponsiveTable-module_sortable__yvA60 > * {\r\n cursor: inherit;\r\n}\r\n\r\n/* Sorted Ascending Icon */\r\n.ResponsiveTable-module_responsiveTable__4y-Od th.ResponsiveTable-module_sorted-asc__jzOIa .ResponsiveTable-module_sortIcon__A9WtD {\r\n opacity: 1; /* Always prominent when sorted */\r\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%230056b3'%3E%3Cpath d='M7 14l5-5 5 5z'/%3E%3C/svg%3E\");\r\n}\r\n\r\n/* Sorted Descending Icon */\r\n.ResponsiveTable-module_responsiveTable__4y-Od th.ResponsiveTable-module_sorted-desc__7WCFK .ResponsiveTable-module_sortIcon__A9WtD {\r\n opacity: 1; /* Always prominent when sorted */\r\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%230056b3'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E\");\r\n}\r\n\r\n.ResponsiveTable-module_responsiveTable__4y-Od tfoot {\r\n background-color: var(--table-header-bg);\r\n border-top: 2px solid var(--table-border-color);\r\n font-weight: 600;\r\n}\r\n\r\n.ResponsiveTable-module_footerCell__8H-uG {\r\n padding: 1rem;\r\n text-align: right;\r\n font-weight: 600;\r\n}\r\n\r\n.ResponsiveTable-module_clickableFooterCell__WB9Ss {\r\n cursor: pointer;\r\n color: var(--interactive-color);\r\n}\r\n\r\n.ResponsiveTable-module_clickableFooterCell__WB9Ss:hover {\r\n text-decoration: underline;\r\n}\r\n\r\n.ResponsiveTable-module_footerCard__-NE2M {\r\n background-color: var(--table-header-bg);\r\n border: 1px solid var(--card-border-color);\r\n border-top: 2px solid var(--table-border-color);\r\n margin-bottom: 1rem;\r\n border-radius: 8px;\r\n overflow: hidden;\r\n box-shadow: none;\r\n}\r\n\r\n.ResponsiveTable-module_footer-card-body__CtBMv {\r\n padding: 1rem;\r\n font-size: 0.9rem;\r\n}\r\n\r\n.ResponsiveTable-module_footer-card-row__Vv6Ur {\r\n margin: 0 0 0.75rem;\r\n display: flex;\r\n justify-content: space-between;\r\n font-weight: 600;\r\n}\r\n\r\n/* No Data State */\r\n\r\n/* Staggered Entrance Animation */\r\n.ResponsiveTable-module_animatedRow__SFjrJ {\r\n animation: ResponsiveTable-module_fadeInUp__jMCS7 0.5s ease-out forwards;\r\n opacity: 0;\r\n}\r\n\r\n@keyframes ResponsiveTable-module_fadeInUp__jMCS7 {\r\n from {\r\n opacity: 0;\r\n transform: translateY(20px);\r\n }\r\n to {\r\n opacity: 1;\r\n transform: translateY(0);\r\n }\r\n}\r\n\r\n/* Skeleton Loader */\r\n.ResponsiveTable-module_skeleton__XxsXW {\r\n background-color: #e0e0e0;\r\n border-radius: 4px;\r\n position: relative;\r\n overflow: hidden;\r\n}\r\n\r\n.ResponsiveTable-module_skeleton__XxsXW::after {\r\n content: '';\r\n position: absolute;\r\n top: 0;\r\n left: -150%;\r\n width: 150%;\r\n height: 100%;\r\n background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);\r\n animation: ResponsiveTable-module_shimmer__H8PhC 1.5s infinite;\r\n}\r\n\r\n.ResponsiveTable-module_skeletonText__T-Lgq {\r\n height: 1.2em;\r\n width: 100%;\r\n}\r\n\r\n.ResponsiveTable-module_skeletonCard__AYVwL {\r\n background-color: var(--card-bg);\r\n border: 1px solid var(--card-border-color);\r\n margin-bottom: 1rem;\r\n border-radius: 8px;\r\n overflow: hidden;\r\n padding: 1rem;\r\n}\r\n\r\n@keyframes ResponsiveTable-module_shimmer__H8PhC {\r\n 0% {\r\n transform: translateX(0);\r\n }\r\n 100% {\r\n transform: translateX(100%);\r\n }\r\n}\r\n.ResponsiveTable-module_noDataWrapper__Rj-k3 {\r\n color: var(--text-color-muted); /* Softer color */\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n height: 100%;\r\n gap: 10px;\r\n padding: 40px;\r\n background-color: var(--table-header-bg);\r\n border: 1px dashed var(--table-border-color);\r\n border-radius: 8px;\r\n}\r\n\r\n.ResponsiveTable-module_noData__IpwNq {\r\n text-align: center;\r\n font-weight: 500; /* Less aggressive than bold */\r\n font-size: 1rem;\r\n}\r\n\r\n.ResponsiveTable-module_row-exit__EVX6T {\r\n opacity: 0;\r\n transform: scaleY(0);\r\n transition: transform 0.3s ease-out, opacity 0.3s ease-out;\r\n}\r\n\r\n.ResponsiveTable-module_row-enter__YKgI4 {\r\n opacity: 0;\r\n transform: translateY(20px);\r\n transition: transform 0.5s ease-out, opacity 0.5s ease-out;\r\n}\r\n\r\n.ResponsiveTable-module_row-flash__a4NOm {\r\n animation: ResponsiveTable-module_flash__nxeAX 0.5s ease-out;\r\n}\r\n\r\n@keyframes ResponsiveTable-module_flash__nxeAX {\r\n 0% {\r\n background-color: var(--table-row-hover-bg);\r\n }\r\n 100% {\r\n background-color: transparent;\r\n }\r\n}\r\n\r\n.ResponsiveTable-module_stickyHeader__-jjN- th {\r\n position: sticky;\r\n top: 0;\r\n z-index: 1;\r\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n.ResponsiveTable-module_internalStickyHeader__idiJY th {\r\n position: sticky;\r\n top: 0;\r\n z-index: 1;\r\n}\r\n/* Spinner for Loading More */\r\n.ResponsiveTable-module_spinner__Pn-3D {\r\n border: 4px solid rgba(0, 0, 0, 0.1);\r\n border-left-color: var(--interactive-color); /* Use theme color */\r\n border-radius: 50%;\r\n width: 24px;\r\n height: 24px;\r\n animation: ResponsiveTable-module_spin__i3NHn 1s linear infinite;\r\n display: inline-block;\r\n vertical-align: middle;\r\n margin-right: 8px;\r\n}\r\n\r\n@keyframes ResponsiveTable-module_spin__i3NHn {\r\n 0% { transform: rotate(0deg); }\r\n 100% { transform: rotate(360deg); }\r\n}\r\n\r\n/* Loading/No More Data Container */\r\n.ResponsiveTable-module_infoContainer__b9IF5 {\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n padding: 20px;\r\n color: var(--text-color-muted);\r\n font-size: 0.9rem;\r\n gap: 8px;\r\n background-color: var(--table-header-bg);\r\n border-top: 1px solid var(--table-border-color);\r\n}\r\n\r\n.ResponsiveTable-module_infoContainer__b9IF5.ResponsiveTable-module_noMoreData__he1rZ {\r\n font-weight: 500;\r\n}";
|
|
79
79
|
var styles$2 = {"responsiveTable":"ResponsiveTable-module_responsiveTable__4y-Od","card":"ResponsiveTable-module_card__b-U2v","card-header":"ResponsiveTable-module_card-header__Ttk51","card-body":"ResponsiveTable-module_card-body__XIy0h","card-label":"ResponsiveTable-module_card-label__v9L71","headerInnerWrapper":"ResponsiveTable-module_headerInnerWrapper__3VAhD","headerContent":"ResponsiveTable-module_headerContent__ODMzS","selectedRow":"ResponsiveTable-module_selectedRow__-JyNW","clickableRow":"ResponsiveTable-module_clickableRow__0kjWm","clickableHeader":"ResponsiveTable-module_clickableHeader__xHQhF","sortable":"ResponsiveTable-module_sortable__yvA60","sorted-asc":"ResponsiveTable-module_sorted-asc__jzOIa","sorted-desc":"ResponsiveTable-module_sorted-desc__7WCFK","sortIcon":"ResponsiveTable-module_sortIcon__A9WtD","footerCell":"ResponsiveTable-module_footerCell__8H-uG","clickableFooterCell":"ResponsiveTable-module_clickableFooterCell__WB9Ss","footerCard":"ResponsiveTable-module_footerCard__-NE2M","footer-card-body":"ResponsiveTable-module_footer-card-body__CtBMv","footer-card-row":"ResponsiveTable-module_footer-card-row__Vv6Ur","animatedRow":"ResponsiveTable-module_animatedRow__SFjrJ","fadeInUp":"ResponsiveTable-module_fadeInUp__jMCS7","skeleton":"ResponsiveTable-module_skeleton__XxsXW","shimmer":"ResponsiveTable-module_shimmer__H8PhC","skeletonText":"ResponsiveTable-module_skeletonText__T-Lgq","skeletonCard":"ResponsiveTable-module_skeletonCard__AYVwL","noDataWrapper":"ResponsiveTable-module_noDataWrapper__Rj-k3","noData":"ResponsiveTable-module_noData__IpwNq","row-exit":"ResponsiveTable-module_row-exit__EVX6T","row-enter":"ResponsiveTable-module_row-enter__YKgI4","row-flash":"ResponsiveTable-module_row-flash__a4NOm","flash":"ResponsiveTable-module_flash__nxeAX","stickyHeader":"ResponsiveTable-module_stickyHeader__-jjN-","internalStickyHeader":"ResponsiveTable-module_internalStickyHeader__idiJY","spinner":"ResponsiveTable-module_spinner__Pn-3D","spin":"ResponsiveTable-module_spin__i3NHn","infoContainer":"ResponsiveTable-module_infoContainer__b9IF5","noMoreData":"ResponsiveTable-module_noMoreData__he1rZ"};
|
|
80
80
|
styleInject(css_248z$2);
|
|
81
81
|
|
|
82
|
+
function DesktopView(props) {
|
|
83
|
+
const { columnDefinitions, originalColumnDefinitions, currentData, maxHeight, isHeaderSticky, tableContainerRef, headerRef, getRowProps, getHeaderProps, onHeaderClickCallback, getClickableHeaderClassName, getRawColumnDefinition, getColumnDefinition, renderCell, rowClickFunction, footerRows, renderPluginFooters, animationProps, onRowClick, selectionProps, onScroll, } = props;
|
|
84
|
+
const getEffectiveColSpan = React.useCallback((footerCol, startIndex) => {
|
|
85
|
+
const originalSpan = footerCol.colSpan || 1;
|
|
86
|
+
const endIndex = startIndex + originalSpan;
|
|
87
|
+
let visibleCount = 0;
|
|
88
|
+
for (let i = startIndex; i < endIndex; i++) {
|
|
89
|
+
const col = originalColumnDefinitions[i];
|
|
90
|
+
if (col && getRawColumnDefinition(col).visible !== false) {
|
|
91
|
+
visibleCount++;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return visibleCount;
|
|
95
|
+
}, [originalColumnDefinitions, getRawColumnDefinition]);
|
|
96
|
+
const tableFooter = React.useMemo(() => {
|
|
97
|
+
if (!footerRows || footerRows.length === 0) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
return (React.createElement("tfoot", null, footerRows.map((row, rowIndex) => {
|
|
101
|
+
let currentOriginalIndex = 0;
|
|
102
|
+
return (React.createElement("tr", { key: rowIndex }, row.columns.map((col, colIndex) => {
|
|
103
|
+
const effectiveColSpan = getEffectiveColSpan(col, currentOriginalIndex);
|
|
104
|
+
currentOriginalIndex += (col.colSpan || 1);
|
|
105
|
+
if (effectiveColSpan === 0)
|
|
106
|
+
return null;
|
|
107
|
+
return (React.createElement("td", { key: colIndex, colSpan: effectiveColSpan, className: `${styles$2.footerCell} ${col.className || ''} ${col.onCellClick ? styles$2.clickableFooterCell : ''}`, onClick: col.onCellClick }, col.cellRenderer()));
|
|
108
|
+
})));
|
|
109
|
+
})));
|
|
110
|
+
}, [footerRows, getEffectiveColSpan]);
|
|
111
|
+
const useFixedHeaders = maxHeight ? true : false;
|
|
112
|
+
const isClickable = onRowClick || selectionProps;
|
|
113
|
+
const fixedHeadersStyle = useFixedHeaders
|
|
114
|
+
? { maxHeight, overflowY: 'auto' }
|
|
115
|
+
: {};
|
|
116
|
+
const headerClassName = useFixedHeaders
|
|
117
|
+
? styles$2.internalStickyHeader
|
|
118
|
+
: (isHeaderSticky ? styles$2.stickyHeader : '');
|
|
119
|
+
return (React.createElement("div", { style: fixedHeadersStyle, ref: tableContainerRef, onScroll: onScroll },
|
|
120
|
+
React.createElement("table", { className: styles$2['responsiveTable'] },
|
|
121
|
+
React.createElement("thead", { ref: headerRef, className: headerClassName },
|
|
122
|
+
React.createElement("tr", null, columnDefinitions.map((columnDefinition, colIndex) => {
|
|
123
|
+
const onHeaderClick = onHeaderClickCallback(columnDefinition);
|
|
124
|
+
const clickableHeaderClassName = getClickableHeaderClassName(onHeaderClick, columnDefinition);
|
|
125
|
+
const headerProps = getHeaderProps(columnDefinition);
|
|
126
|
+
const combinedClassName = `${clickableHeaderClassName} ${headerProps.className ? styles$2[headerProps.className] : ''}`.trim();
|
|
127
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
128
|
+
const { className } = headerProps, restHeaderProps = __rest(headerProps, ["className"]);
|
|
129
|
+
return (React.createElement("th", Object.assign({ key: colIndex, className: combinedClassName }, restHeaderProps, { onClick: onHeaderClick ? () => onHeaderClick(getRawColumnDefinition(columnDefinition).interactivity.id) : restHeaderProps.onClick }),
|
|
130
|
+
React.createElement("div", { className: styles$2.headerInnerWrapper },
|
|
131
|
+
React.createElement("div", { className: styles$2.headerContent }, getColumnDefinition(columnDefinition, 0).displayLabel),
|
|
132
|
+
React.createElement("span", { className: styles$2.sortIcon }))));
|
|
133
|
+
}))),
|
|
134
|
+
React.createElement("tbody", null, currentData.map((row, rowIndex) => {
|
|
135
|
+
const rowProps = getRowProps(row);
|
|
136
|
+
const pluginOnClick = rowProps.onClick;
|
|
137
|
+
return (React.createElement("tr", { key: rowIndex, className: `${isClickable ? styles$2.clickableRow : ''} ${(animationProps === null || animationProps === void 0 ? void 0 : animationProps.animateOnLoad) ? styles$2.animatedRow : ''} ${rowProps.className || ''}`.trim(), style: { animationDelay: `${rowIndex * 0.05}s` }, "aria-selected": rowProps['aria-selected'], onClick: (e) => {
|
|
138
|
+
if (pluginOnClick) {
|
|
139
|
+
pluginOnClick(e);
|
|
140
|
+
}
|
|
141
|
+
rowClickFunction(row);
|
|
142
|
+
} }, columnDefinitions.map((columnDefinition, colIndex) => {
|
|
143
|
+
const colDef = getColumnDefinition(columnDefinition, rowIndex);
|
|
144
|
+
const cellContent = colDef.cellRenderer(row);
|
|
145
|
+
return (React.createElement("td", { key: colIndex }, renderCell(cellContent, row, colDef)));
|
|
146
|
+
})));
|
|
147
|
+
})),
|
|
148
|
+
tableFooter),
|
|
149
|
+
renderPluginFooters()));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function MobileView(props) {
|
|
153
|
+
const { currentData, columnDefinitions, onRowClick, selectionProps, animationProps, getRowProps, getRowId, getColumnDefinition, onHeaderClickCallback, getClickableHeaderClassName, renderCell, rowClickFunction, mobileFooter, } = props;
|
|
154
|
+
const isClickable = onRowClick || selectionProps;
|
|
155
|
+
return (React.createElement("div", null,
|
|
156
|
+
currentData.map((row, rowIndex) => {
|
|
157
|
+
const rowProps = getRowProps(row);
|
|
158
|
+
const pluginOnClick = rowProps.onClick;
|
|
159
|
+
return (React.createElement("div", { key: getRowId(row, rowIndex), className: `${styles$2.card} ${isClickable ? styles$2.clickableRow : ''} ${(animationProps === null || animationProps === void 0 ? void 0 : animationProps.animateOnLoad) ? styles$2.animatedRow : ''} ${rowProps.className || ''}`.trim(), style: { animationDelay: `${rowIndex * 0.05}s` }, "aria-selected": rowProps['aria-selected'], onClick: (e) => {
|
|
160
|
+
if (pluginOnClick)
|
|
161
|
+
pluginOnClick(e);
|
|
162
|
+
rowClickFunction(row);
|
|
163
|
+
} },
|
|
164
|
+
React.createElement("div", { className: styles$2['card-header'] }, " "),
|
|
165
|
+
React.createElement("div", { className: styles$2['card-body'] }, columnDefinitions.map((columnDefinition, colIndex) => {
|
|
166
|
+
const colDef = getColumnDefinition(columnDefinition, rowIndex);
|
|
167
|
+
const onHeaderClick = onHeaderClickCallback(colDef);
|
|
168
|
+
const clickableHeaderClassName = getClickableHeaderClassName(onHeaderClick, colDef);
|
|
169
|
+
return (React.createElement("div", { key: colIndex, className: styles$2['card-row'] },
|
|
170
|
+
React.createElement("p", null,
|
|
171
|
+
React.createElement("span", { className: `${styles$2['card-label']} ${clickableHeaderClassName}`, onClick: (e) => {
|
|
172
|
+
if (onHeaderClick) {
|
|
173
|
+
e.stopPropagation();
|
|
174
|
+
onHeaderClick(colDef.interactivity.id);
|
|
175
|
+
}
|
|
176
|
+
} }, colDef.displayLabel),
|
|
177
|
+
React.createElement("span", { className: styles$2['card-value'] }, renderCell(colDef.cellRenderer(row), row, colDef)))));
|
|
178
|
+
}))));
|
|
179
|
+
}),
|
|
180
|
+
mobileFooter));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function SkeletonView(props) {
|
|
184
|
+
const { isMobile, columnDefinitions } = props;
|
|
185
|
+
const skeletonRowCount = 5;
|
|
186
|
+
const columnCount = columnDefinitions.length;
|
|
187
|
+
if (isMobile) {
|
|
188
|
+
return (React.createElement("div", null, [...Array(skeletonRowCount)].map((_, i) => (React.createElement("div", { key: i, className: styles$2.skeletonCard }, [...Array(columnCount)].map((_, j) => (React.createElement("div", { key: j, className: `${styles$2.skeleton} ${styles$2.skeletonText}`, style: { marginBottom: '0.5rem' } }))))))));
|
|
189
|
+
}
|
|
190
|
+
return (React.createElement("table", { className: styles$2.responsiveTable },
|
|
191
|
+
React.createElement("thead", null,
|
|
192
|
+
React.createElement("tr", null, [...Array(columnCount)].map((_, i) => (React.createElement("th", { key: i },
|
|
193
|
+
React.createElement("div", { className: `${styles$2.skeleton} ${styles$2.skeletonText}` })))))),
|
|
194
|
+
React.createElement("tbody", null, [...Array(skeletonRowCount)].map((_, i) => (React.createElement("tr", { key: i }, [...Array(columnCount)].map((_, j) => (React.createElement("td", { key: j },
|
|
195
|
+
React.createElement("div", { className: `${styles$2.skeleton} ${styles$2.skeletonText}` }))))))))));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
var css_248z$1 = "/* Spinner for Loading More */\r\n.LoadingSpinner-module_spinner__F9V3x {\r\n border: 4px solid rgba(0, 0, 0, 0.1);\r\n border-left-color: var(--interactive-color); /* Use theme color */\r\n border-radius: 50%;\r\n width: 24px;\r\n height: 24px;\r\n animation: LoadingSpinner-module_spin__VkBDO 1s linear infinite;\r\n display: inline-block;\r\n vertical-align: middle;\r\n margin-right: 8px;\r\n}\r\n\r\n@keyframes LoadingSpinner-module_spin__VkBDO {\r\n 0% { transform: rotate(0deg); }\r\n 100% { transform: rotate(360deg); }\r\n}";
|
|
199
|
+
var styles$1 = {"spinner":"LoadingSpinner-module_spinner__F9V3x"};
|
|
200
|
+
styleInject(css_248z$1);
|
|
201
|
+
|
|
202
|
+
var css_248z = "/* Loading/No More Data Container */\r\n.NoMoreDataMessage-module_infoContainer__dk1r5 {\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n padding: 20px;\r\n color: var(--text-color-muted);\r\n font-size: 0.9rem;\r\n gap: 8px;\r\n background-color: var(--table-header-bg);\r\n border-top: 1px solid var(--table-border-color);\r\n}\r\n\r\n.NoMoreDataMessage-module_infoContainer__dk1r5.NoMoreDataMessage-module_noMoreData__ATuIg {\r\n font-weight: 500;\r\n}";
|
|
203
|
+
var styles = {"infoContainer":"NoMoreDataMessage-module_infoContainer__dk1r5","noMoreData":"NoMoreDataMessage-module_noMoreData__ATuIg"};
|
|
204
|
+
styleInject(css_248z);
|
|
205
|
+
|
|
206
|
+
const LoadingSpinner = () => {
|
|
207
|
+
return (React.createElement("div", { className: styles.infoContainer },
|
|
208
|
+
React.createElement("div", { className: styles$1.spinner }),
|
|
209
|
+
React.createElement("span", null, "Loading more items...")));
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const NoMoreDataMessage = () => {
|
|
213
|
+
return (React.createElement("div", { className: `${styles.infoContainer} ${styles.noMoreData}` },
|
|
214
|
+
React.createElement("p", null, "You've reached the end!")));
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
function debounce(func, delay) {
|
|
218
|
+
let timeout;
|
|
219
|
+
return function (...args) {
|
|
220
|
+
clearTimeout(timeout);
|
|
221
|
+
timeout = setTimeout(() => {
|
|
222
|
+
func.apply(this, args);
|
|
223
|
+
}, delay);
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
const useResponsiveTable = (props) => {
|
|
227
|
+
const { mobileBreakpoint = 600, enablePageLevelStickyHeader = true, maxHeight, headerRef, scrollableRef } = props;
|
|
228
|
+
const [isMobile, setIsMobile] = React.useState(false);
|
|
229
|
+
const [isHeaderSticky, setIsHeaderSticky] = React.useState(false);
|
|
230
|
+
const handleResize = React.useCallback(() => {
|
|
231
|
+
setIsMobile(window.innerWidth <= mobileBreakpoint);
|
|
232
|
+
}, [mobileBreakpoint]);
|
|
233
|
+
const handleScroll = React.useCallback((currentTarget) => {
|
|
234
|
+
// Page-level sticky header logic
|
|
235
|
+
if (enablePageLevelStickyHeader && !maxHeight && (headerRef === null || headerRef === void 0 ? void 0 : headerRef.current)) {
|
|
236
|
+
const { top } = headerRef.current.getBoundingClientRect();
|
|
237
|
+
const sticky = top <= 0;
|
|
238
|
+
if (sticky !== isHeaderSticky) {
|
|
239
|
+
setIsHeaderSticky(sticky);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}, [enablePageLevelStickyHeader, maxHeight, headerRef, isHeaderSticky]);
|
|
243
|
+
const debouncedScrollHandler = React.useRef(debounce(handleScroll, 200)).current;
|
|
244
|
+
React.useEffect(() => {
|
|
245
|
+
handleResize(); // Initial check
|
|
246
|
+
window.addEventListener('resize', handleResize);
|
|
247
|
+
const scrollTarget = (scrollableRef === null || scrollableRef === void 0 ? void 0 : scrollableRef.current) || window;
|
|
248
|
+
if (enablePageLevelStickyHeader || maxHeight) { // Only add scroll listener if sticky header or internal scroll is enabled
|
|
249
|
+
scrollTarget.addEventListener('scroll', (e) => debouncedScrollHandler(e.currentTarget));
|
|
250
|
+
}
|
|
251
|
+
return () => {
|
|
252
|
+
window.removeEventListener('resize', handleResize);
|
|
253
|
+
if (enablePageLevelStickyHeader || maxHeight) {
|
|
254
|
+
scrollTarget.removeEventListener('scroll', (e) => debouncedScrollHandler(e.currentTarget));
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
}, [handleResize, debouncedScrollHandler, enablePageLevelStickyHeader, maxHeight, scrollableRef]);
|
|
258
|
+
return { isMobile, isHeaderSticky, debouncedScrollHandler: debouncedScrollHandler };
|
|
259
|
+
};
|
|
260
|
+
|
|
82
261
|
class FilterPlugin {
|
|
83
262
|
constructor() {
|
|
84
263
|
this.id = 'filter';
|
|
@@ -121,7 +300,11 @@ class FilterPlugin {
|
|
|
121
300
|
});
|
|
122
301
|
});
|
|
123
302
|
};
|
|
124
|
-
this.renderCell = (content,
|
|
303
|
+
this.renderCell = (content,
|
|
304
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
305
|
+
_row,
|
|
306
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
307
|
+
_column) => {
|
|
125
308
|
if (!this.filterText || typeof content !== 'string') {
|
|
126
309
|
return content;
|
|
127
310
|
}
|
|
@@ -224,474 +407,560 @@ class SelectionPlugin {
|
|
|
224
407
|
}
|
|
225
408
|
}
|
|
226
409
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
410
|
+
// Type-safe sort comparer helpers
|
|
411
|
+
const createSortComparers = () => ({
|
|
412
|
+
numeric: (key) => (a, b, direction) => {
|
|
413
|
+
const numA = parseFloat(String(a[key]));
|
|
414
|
+
const numB = parseFloat(String(b[key]));
|
|
415
|
+
const aIsNaN = isNaN(numA);
|
|
416
|
+
const bIsNaN = isNaN(numB);
|
|
417
|
+
if (aIsNaN && bIsNaN)
|
|
418
|
+
return 0;
|
|
419
|
+
if (aIsNaN)
|
|
420
|
+
return 1; // Put non-numbers at the end
|
|
421
|
+
if (bIsNaN)
|
|
422
|
+
return -1;
|
|
423
|
+
return direction === 'asc' ? numA - numB : numB - numA;
|
|
424
|
+
},
|
|
425
|
+
caseInsensitiveString: (key) => (a, b, direction) => {
|
|
426
|
+
var _a, _b;
|
|
427
|
+
const valA = String((_a = a[key]) !== null && _a !== void 0 ? _a : '').toLowerCase();
|
|
428
|
+
const valB = String((_b = b[key]) !== null && _b !== void 0 ? _b : '').toLowerCase();
|
|
429
|
+
if (valA < valB)
|
|
430
|
+
return direction === 'asc' ? -1 : 1;
|
|
431
|
+
if (valA > valB)
|
|
432
|
+
return direction === 'asc' ? 1 : -1;
|
|
433
|
+
return 0;
|
|
434
|
+
},
|
|
435
|
+
date: (key) => (a, b, direction) => {
|
|
436
|
+
const dateA = new Date(String(a[key])).getTime();
|
|
437
|
+
const dateB = new Date(String(b[key])).getTime();
|
|
438
|
+
const aIsNaN = isNaN(dateA);
|
|
439
|
+
const bIsNaN = isNaN(dateB);
|
|
440
|
+
if (aIsNaN && bIsNaN)
|
|
441
|
+
return 0;
|
|
442
|
+
if (aIsNaN)
|
|
443
|
+
return 1; // Put invalid dates at the end
|
|
444
|
+
if (bIsNaN)
|
|
445
|
+
return -1;
|
|
446
|
+
return direction === 'asc' ? dateA - dateB : dateB - dateA;
|
|
447
|
+
},
|
|
448
|
+
});
|
|
449
|
+
class SortPlugin {
|
|
450
|
+
constructor(options) {
|
|
451
|
+
var _a, _b;
|
|
452
|
+
this.id = 'sort';
|
|
453
|
+
this.comparers = createSortComparers();
|
|
454
|
+
this.onPluginInit = (api) => {
|
|
455
|
+
this.api = api;
|
|
255
456
|
};
|
|
256
|
-
this.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
if (newItems && newItems.length > 0) {
|
|
270
|
-
const newInternalData = [...this.state.internalData, ...newItems];
|
|
271
|
-
this.setState({ internalData: newInternalData }, () => {
|
|
272
|
-
this.processData(); // Re-process data after new items are added
|
|
273
|
-
});
|
|
457
|
+
this.processData = (data) => {
|
|
458
|
+
if (!this.sortColumn || !this.sortDirection) {
|
|
459
|
+
return data;
|
|
460
|
+
}
|
|
461
|
+
const columnDef = this.api.columnDefinitions.find((col) => (typeof col === 'object' && col.columnId === this.sortColumn));
|
|
462
|
+
if (!columnDef) {
|
|
463
|
+
return data;
|
|
464
|
+
}
|
|
465
|
+
const sortedData = [...data].sort((a, b) => {
|
|
466
|
+
if ('sortComparer' in columnDef && columnDef.sortComparer) {
|
|
467
|
+
if (columnDef.sortComparer.length < 3) {
|
|
468
|
+
console.warn(`The custom sortComparer for column '${this.sortColumn}' should accept all three parameters (a, b, direction) to ensure correct sorting behavior. You provided a function with ${columnDef.sortComparer.length} parameters.`);
|
|
274
469
|
}
|
|
470
|
+
return columnDef.sortComparer(a, b, this.sortDirection);
|
|
275
471
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
472
|
+
if ('getSortableValue' in columnDef && columnDef.getSortableValue) {
|
|
473
|
+
const aValue = columnDef.getSortableValue(a);
|
|
474
|
+
const bValue = columnDef.getSortableValue(b);
|
|
475
|
+
if (aValue < bValue)
|
|
476
|
+
return this.sortDirection === 'asc' ? -1 : 1;
|
|
477
|
+
if (aValue > bValue)
|
|
478
|
+
return this.sortDirection === 'asc' ? 1 : -1;
|
|
479
|
+
return 0;
|
|
282
480
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
481
|
+
// Fallback to dataKey if it exists and no other sorter is provided
|
|
482
|
+
if ('dataKey' in columnDef && columnDef.dataKey) {
|
|
483
|
+
const key = columnDef.dataKey;
|
|
484
|
+
const aValue = a[key];
|
|
485
|
+
const bValue = b[key];
|
|
486
|
+
if (aValue < bValue)
|
|
487
|
+
return this.sortDirection === 'asc' ? -1 : 1;
|
|
488
|
+
if (aValue > bValue)
|
|
489
|
+
return this.sortDirection === 'asc' ? 1 : -1;
|
|
490
|
+
return 0;
|
|
290
491
|
}
|
|
492
|
+
return 0;
|
|
291
493
|
});
|
|
292
|
-
|
|
293
|
-
};
|
|
294
|
-
this.getColumnDefinition = (colDef, rowIndex) => {
|
|
295
|
-
if (typeof colDef === 'function') {
|
|
296
|
-
return colDef(this.data[rowIndex], rowIndex);
|
|
297
|
-
}
|
|
298
|
-
return colDef;
|
|
494
|
+
return sortedData;
|
|
299
495
|
};
|
|
300
|
-
this.
|
|
301
|
-
|
|
302
|
-
|
|
496
|
+
this.getHeaderProps = (columnDef) => {
|
|
497
|
+
const { columnId } = columnDef;
|
|
498
|
+
const isSortable = ('sortComparer' in columnDef && !!columnDef.sortComparer) || ('getSortableValue' in columnDef && !!columnDef.getSortableValue);
|
|
499
|
+
// A column must have a columnId and a sort function to be sortable
|
|
500
|
+
if (!isSortable || !columnId) {
|
|
501
|
+
return {};
|
|
303
502
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
this.getClickableHeaderClassName = (colDef) => {
|
|
312
|
-
var _a;
|
|
313
|
-
const raw = this.getRawColumnDefinition(colDef);
|
|
314
|
-
return ((_a = raw.interactivity) === null || _a === void 0 ? void 0 : _a.onHeaderClick) ? raw.interactivity.className || styles$2.clickableHeader : '';
|
|
315
|
-
};
|
|
316
|
-
this.getHeaderProps = (colDef) => {
|
|
317
|
-
const headerProps = {};
|
|
318
|
-
this.state.activePlugins.forEach(plugin => {
|
|
319
|
-
if (plugin.getHeaderProps) {
|
|
320
|
-
Object.assign(headerProps, plugin.getHeaderProps(this.getRawColumnDefinition(colDef)));
|
|
503
|
+
const onHeaderClick = (e) => {
|
|
504
|
+
const target = e.target;
|
|
505
|
+
console.log('SortPlugin: Header clicked. Target:', target);
|
|
506
|
+
// If the click is on an interactive element, don't sort
|
|
507
|
+
if (target.closest('input, button, a, [onclick]')) {
|
|
508
|
+
console.log('SortPlugin: Interactive element clicked, ignoring sort.');
|
|
509
|
+
return;
|
|
321
510
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
511
|
+
console.log('SortPlugin: Non-interactive element clicked, proceeding with sort.');
|
|
512
|
+
if (this.sortColumn === columnId) {
|
|
513
|
+
if (this.sortDirection === 'desc') {
|
|
514
|
+
this.sortColumn = null;
|
|
515
|
+
this.sortDirection = null;
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
this.sortDirection = 'desc';
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
this.sortColumn = columnId;
|
|
523
|
+
this.sortDirection = 'asc';
|
|
524
|
+
}
|
|
525
|
+
this.api.forceUpdate();
|
|
526
|
+
};
|
|
527
|
+
let sortClassName = 'sortable';
|
|
528
|
+
if (this.sortColumn === columnId) {
|
|
529
|
+
sortClassName = `sorted-${this.sortDirection}`;
|
|
530
|
+
}
|
|
531
|
+
return {
|
|
532
|
+
onClick: onHeaderClick,
|
|
533
|
+
className: sortClassName,
|
|
534
|
+
'aria-sort': (this.sortColumn === columnId ? (this.sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'),
|
|
535
|
+
};
|
|
333
536
|
};
|
|
334
|
-
this.
|
|
335
|
-
this.
|
|
537
|
+
this.sortColumn = (_a = options === null || options === void 0 ? void 0 : options.initialSortColumn) !== null && _a !== void 0 ? _a : null;
|
|
538
|
+
this.sortDirection = (_b = options === null || options === void 0 ? void 0 : options.initialSortDirection) !== null && _b !== void 0 ? _b : null;
|
|
336
539
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const useTablePlugins = (props) => {
|
|
543
|
+
const { data, plugins, filterProps, selectionProps, sortProps, columnDefinitions, getScrollableElement, infiniteScrollProps, } = props;
|
|
544
|
+
const [processedData, setProcessedData] = React.useState(data);
|
|
545
|
+
const [activePlugins, setActivePlugins] = React.useState([]);
|
|
546
|
+
// Persist internal plugins using refs to prevent state loss
|
|
547
|
+
const filterPluginRef = React.useRef(null);
|
|
548
|
+
const selectionPluginRef = React.useRef(null);
|
|
549
|
+
const sortPluginRef = React.useRef(null);
|
|
550
|
+
const getRawColumnDefinition = React.useCallback((columnDefinition) => {
|
|
551
|
+
if (typeof columnDefinition === 'function') {
|
|
552
|
+
return columnDefinition(data[0] || {}, 0);
|
|
347
553
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
554
|
+
return columnDefinition;
|
|
555
|
+
}, [data]);
|
|
556
|
+
const visibleColumns = React.useMemo(() => {
|
|
557
|
+
return columnDefinitions.filter(col => {
|
|
558
|
+
const raw = getRawColumnDefinition(col);
|
|
559
|
+
return raw.visible !== false;
|
|
560
|
+
});
|
|
561
|
+
}, [columnDefinitions, getRawColumnDefinition]);
|
|
562
|
+
const initializePlugins = React.useCallback(() => {
|
|
563
|
+
const newActivePlugins = [];
|
|
564
|
+
// 1. Add external plugins
|
|
565
|
+
if (plugins) {
|
|
566
|
+
newActivePlugins.push(...plugins);
|
|
355
567
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
clearTimeout(timeout);
|
|
364
|
-
timeout = setTimeout(() => {
|
|
365
|
-
func.apply(this, args);
|
|
366
|
-
}, delay);
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
handleScroll(currentTarget) {
|
|
370
|
-
var _a;
|
|
371
|
-
if (!currentTarget)
|
|
372
|
-
return;
|
|
373
|
-
const { scrollHeight, scrollTop, clientHeight } = currentTarget;
|
|
374
|
-
const { isLoadingMore } = this.state;
|
|
375
|
-
const hasMore = ((_a = this.props.infiniteScrollProps) === null || _a === void 0 ? void 0 : _a.hasMore) !== undefined
|
|
376
|
-
? this.props.infiniteScrollProps.hasMore
|
|
377
|
-
: this.state.internalHasMore;
|
|
378
|
-
// When maxHeight is set, we use CSS position:sticky and don't need JS-based detection.
|
|
379
|
-
// The isHeaderSticky state is only for page-level stickiness.
|
|
380
|
-
if (this.props.enablePageLevelStickyHeader !== false && !this.props.maxHeight && this.headerRef.current) {
|
|
381
|
-
const { top } = this.headerRef.current.getBoundingClientRect();
|
|
382
|
-
const isSticky = top <= 0;
|
|
383
|
-
if (isSticky !== this.state.isHeaderSticky) {
|
|
384
|
-
this.setState({ isHeaderSticky: isSticky });
|
|
568
|
+
// 2. Manage internal FilterPlugin
|
|
569
|
+
if (filterProps === null || filterProps === void 0 ? void 0 : filterProps.showFilter) {
|
|
570
|
+
if (!filterPluginRef.current) {
|
|
571
|
+
filterPluginRef.current = new FilterPlugin();
|
|
572
|
+
}
|
|
573
|
+
if (!newActivePlugins.some(p => p.id === 'filter')) {
|
|
574
|
+
newActivePlugins.push(filterPluginRef.current);
|
|
385
575
|
}
|
|
386
576
|
}
|
|
387
|
-
|
|
388
|
-
|
|
577
|
+
else {
|
|
578
|
+
filterPluginRef.current = null;
|
|
389
579
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
580
|
+
// 3. Manage internal SelectionPlugin
|
|
581
|
+
if (selectionProps === null || selectionProps === void 0 ? void 0 : selectionProps.onSelectionChange) {
|
|
582
|
+
if (!selectionPluginRef.current) {
|
|
583
|
+
selectionPluginRef.current = new SelectionPlugin();
|
|
584
|
+
}
|
|
585
|
+
if (!newActivePlugins.some(p => p.id === 'selection')) {
|
|
586
|
+
newActivePlugins.push(selectionPluginRef.current);
|
|
587
|
+
}
|
|
398
588
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
var _a;
|
|
402
|
-
(_a = plugin.onPluginInit) === null || _a === void 0 ? void 0 : _a.call(plugin, Object.assign({ getData: () => this.state.internalData, forceUpdate: () => this.processData(), getScrollableElement: () => this.tableContainerRef.current }, this.props));
|
|
403
|
-
});
|
|
404
|
-
this.processData();
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
get data() {
|
|
408
|
-
return this.state.processedData || [];
|
|
409
|
-
}
|
|
410
|
-
get hasData() {
|
|
411
|
-
return this.data.length > 0;
|
|
412
|
-
}
|
|
413
|
-
get noDataComponent() {
|
|
414
|
-
return this.props.noDataComponent || React.createElement("div", { className: styles$2.noData }, "No data");
|
|
415
|
-
}
|
|
416
|
-
get defaultLoadingComponent() {
|
|
417
|
-
return React.createElement(LoadingSpinner, null);
|
|
418
|
-
}
|
|
419
|
-
get defaultNoMoreDataComponent() {
|
|
420
|
-
return React.createElement(NoMoreDataMessage, null);
|
|
421
|
-
}
|
|
422
|
-
get rowClickFunction() {
|
|
423
|
-
return this.props.onRowClick || (() => { });
|
|
424
|
-
}
|
|
425
|
-
get rowClickStyle() {
|
|
426
|
-
return this.props.onRowClick ? { cursor: 'pointer' } : {};
|
|
427
|
-
}
|
|
428
|
-
get mobileView() {
|
|
429
|
-
var _a;
|
|
430
|
-
const { infiniteScrollProps } = this.props;
|
|
431
|
-
const { isLoadingMore } = this.state;
|
|
432
|
-
const hasMore = ((_a = this.props.infiniteScrollProps) === null || _a === void 0 ? void 0 : _a.hasMore) !== undefined ? this.props.infiniteScrollProps.hasMore : this.state.internalHasMore;
|
|
433
|
-
return (React.createElement("div", null,
|
|
434
|
-
this.data.map((row, rowIndex) => {
|
|
435
|
-
var _a;
|
|
436
|
-
return (React.createElement("div", { key: rowIndex, className: `${styles$2['card']} ${((_a = this.props.animationProps) === null || _a === void 0 ? void 0 : _a.animateOnLoad) ? styles$2.animatedRow : ''}`, style: { animationDelay: `${rowIndex * 0.05}s` }, onClick: () => this.rowClickFunction(row) },
|
|
437
|
-
React.createElement("div", { className: styles$2['card-body'] }, this.props.columnDefinitions.map((colDef, colIndex) => {
|
|
438
|
-
const column = this.getColumnDefinition(colDef, rowIndex);
|
|
439
|
-
return (React.createElement("div", { key: colIndex, className: styles$2['card-row'] },
|
|
440
|
-
React.createElement("p", null,
|
|
441
|
-
React.createElement("span", { className: styles$2['card-label'] }, column.displayLabel),
|
|
442
|
-
React.createElement("span", { className: styles$2['card-value'] }, column.cellRenderer(row)))));
|
|
443
|
-
}))));
|
|
444
|
-
}),
|
|
445
|
-
isLoadingMore && ((infiniteScrollProps === null || infiniteScrollProps === void 0 ? void 0 : infiniteScrollProps.loadingMoreComponent) || this.defaultLoadingComponent),
|
|
446
|
-
!isLoadingMore && !hasMore && ((infiniteScrollProps === null || infiniteScrollProps === void 0 ? void 0 : infiniteScrollProps.noMoreDataComponent) || this.defaultNoMoreDataComponent)));
|
|
447
|
-
}
|
|
448
|
-
get largeScreenView() {
|
|
449
|
-
var _a;
|
|
450
|
-
const { infiniteScrollProps } = this.props;
|
|
451
|
-
const { isLoadingMore } = this.state;
|
|
452
|
-
const hasMore = ((_a = this.props.infiniteScrollProps) === null || _a === void 0 ? void 0 : _a.hasMore) !== undefined ? this.props.infiniteScrollProps.hasMore : this.state.internalHasMore;
|
|
453
|
-
const headerClassName = this.props.maxHeight
|
|
454
|
-
? styles$2.internalStickyHeader
|
|
455
|
-
: this.state.isHeaderSticky
|
|
456
|
-
? styles$2.stickyHeader
|
|
457
|
-
: '';
|
|
458
|
-
return (React.createElement("div", { ref: this.tableContainerRef, onScroll: (e) => this.debouncedScrollHandler(e.currentTarget), style: { maxHeight: this.props.maxHeight, overflowY: 'auto' } },
|
|
459
|
-
React.createElement("table", { className: styles$2['responsiveTable'] },
|
|
460
|
-
React.createElement("thead", { ref: this.headerRef, className: headerClassName },
|
|
461
|
-
React.createElement("tr", null, this.props.columnDefinitions.map((colDef, colIndex) => {
|
|
462
|
-
const rawColDef = this.getRawColumnDefinition(colDef);
|
|
463
|
-
const headerProps = this.getHeaderProps(rawColDef);
|
|
464
|
-
const onHeaderClickCallback = this.onHeaderClickCallback(rawColDef);
|
|
465
|
-
const combinedClassName = `${this.getClickableHeaderClassName(rawColDef)} ${headerProps.className ? styles$2[headerProps.className] : ''}`.trim();
|
|
466
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
467
|
-
const { className } = headerProps, restHeaderProps = __rest(headerProps, ["className"]);
|
|
468
|
-
return (React.createElement("th", Object.assign({ key: colIndex, className: combinedClassName }, restHeaderProps, { onClick: onHeaderClickCallback ? () => onHeaderClickCallback(rawColDef.interactivity.id) : undefined }),
|
|
469
|
-
React.createElement("div", { className: styles$2.headerInnerWrapper },
|
|
470
|
-
React.createElement("div", { className: styles$2.headerContent }, rawColDef.displayLabel),
|
|
471
|
-
React.createElement("span", { className: styles$2.sortIcon }))));
|
|
472
|
-
}))),
|
|
473
|
-
React.createElement("tbody", null, this.data.map((row, rowIndex) => {
|
|
474
|
-
var _a;
|
|
475
|
-
return (React.createElement("tr", { key: rowIndex, className: ((_a = this.props.animationProps) === null || _a === void 0 ? void 0 : _a.animateOnLoad) ? styles$2.animatedRow : '', style: { animationDelay: `${rowIndex * 0.05}s` } }, this.props.columnDefinitions.map((colDef, colIndex) => (React.createElement("td", { key: colIndex, onClick: () => this.rowClickFunction(row), style: this.rowClickStyle }, this.getColumnDefinition(colDef, rowIndex).cellRenderer(row))))));
|
|
476
|
-
}))),
|
|
477
|
-
isLoadingMore && ((infiniteScrollProps === null || infiniteScrollProps === void 0 ? void 0 : infiniteScrollProps.loadingMoreComponent) || this.defaultLoadingComponent),
|
|
478
|
-
!isLoadingMore && !hasMore && ((infiniteScrollProps === null || infiniteScrollProps === void 0 ? void 0 : infiniteScrollProps.noMoreDataComponent) || this.defaultNoMoreDataComponent)));
|
|
479
|
-
}
|
|
480
|
-
render() {
|
|
481
|
-
var _a;
|
|
482
|
-
if (((_a = this.props.animationProps) === null || _a === void 0 ? void 0 : _a.isLoading) && !this.hasData) {
|
|
483
|
-
return React.createElement("div", null, "Skeleton View Placeholder");
|
|
589
|
+
else {
|
|
590
|
+
selectionPluginRef.current = null;
|
|
484
591
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
this.tableContainerRef = React.createRef();
|
|
497
|
-
this.headerRef = React.createRef();
|
|
498
|
-
this.filterPlugin = null;
|
|
499
|
-
this.handleScroll = () => {
|
|
500
|
-
if (this.headerRef.current && !this.props.maxHeight) {
|
|
501
|
-
const { top } = this.headerRef.current.getBoundingClientRect();
|
|
502
|
-
const isSticky = top <= 0;
|
|
503
|
-
if (isSticky !== this.state.isHeaderSticky) {
|
|
504
|
-
this.setState({ isHeaderSticky: isSticky });
|
|
505
|
-
}
|
|
592
|
+
// 4. Manage internal SortPlugin
|
|
593
|
+
const isAnyColumnSortable = columnDefinitions.some(col => {
|
|
594
|
+
const rawCol = getRawColumnDefinition(col);
|
|
595
|
+
return rawCol.sortComparer || rawCol.getSortableValue;
|
|
596
|
+
});
|
|
597
|
+
if (isAnyColumnSortable) {
|
|
598
|
+
if (!sortPluginRef.current) {
|
|
599
|
+
sortPluginRef.current = new SortPlugin(sortProps);
|
|
600
|
+
}
|
|
601
|
+
if (!newActivePlugins.some(p => p.id === 'sort')) {
|
|
602
|
+
newActivePlugins.push(sortPluginRef.current);
|
|
506
603
|
}
|
|
507
|
-
};
|
|
508
|
-
this.handleResize = () => {
|
|
509
|
-
this.setState({
|
|
510
|
-
isMobile: window.innerWidth <= this.mobileBreakpoint,
|
|
511
|
-
});
|
|
512
|
-
};
|
|
513
|
-
if ((_a = props.filterProps) === null || _a === void 0 ? void 0 : _a.showFilter) {
|
|
514
|
-
this.filterPlugin = new FilterPlugin();
|
|
515
|
-
}
|
|
516
|
-
this.state = {
|
|
517
|
-
isMobile: false,
|
|
518
|
-
processedData: props.data,
|
|
519
|
-
isLoadingMore: false,
|
|
520
|
-
isHeaderSticky: false,
|
|
521
|
-
activePlugins: [],
|
|
522
|
-
};
|
|
523
|
-
this.debouncedResize = this.debounce(this.handleResize, 200);
|
|
524
|
-
}
|
|
525
|
-
get mobileBreakpoint() {
|
|
526
|
-
return this.props.mobileBreakpoint || 600;
|
|
527
|
-
}
|
|
528
|
-
debounce(func, delay) {
|
|
529
|
-
let timeout;
|
|
530
|
-
return () => {
|
|
531
|
-
clearTimeout(timeout);
|
|
532
|
-
timeout = setTimeout(() => func(), delay);
|
|
533
|
-
};
|
|
534
|
-
}
|
|
535
|
-
get data() {
|
|
536
|
-
if (Array.isArray(this.state.processedData) && this.state.processedData.length > 0) {
|
|
537
|
-
return this.state.processedData;
|
|
538
604
|
}
|
|
539
605
|
else {
|
|
540
|
-
|
|
606
|
+
sortPluginRef.current = null;
|
|
541
607
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
608
|
+
setActivePlugins(newActivePlugins);
|
|
609
|
+
const api = {
|
|
610
|
+
getData: () => data,
|
|
611
|
+
forceUpdate: forceUpdatePlugins,
|
|
612
|
+
getScrollableElement: getScrollableElement,
|
|
613
|
+
infiniteScrollProps: infiniteScrollProps,
|
|
614
|
+
filterProps: filterProps,
|
|
615
|
+
selectionProps: selectionProps,
|
|
616
|
+
columnDefinitions: columnDefinitions,
|
|
617
|
+
};
|
|
618
|
+
// Initialize/Refresh all active plugins with the current API
|
|
619
|
+
newActivePlugins.forEach((plugin) => {
|
|
620
|
+
if (plugin.onPluginInit) {
|
|
621
|
+
plugin.onPluginInit(api);
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
// Run the data processing pipeline
|
|
625
|
+
let currentProcessedData = [...data];
|
|
626
|
+
newActivePlugins.forEach((plugin) => {
|
|
627
|
+
if (plugin.processData) {
|
|
628
|
+
currentProcessedData = plugin.processData(currentProcessedData);
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
return currentProcessedData;
|
|
632
|
+
}, [
|
|
633
|
+
data,
|
|
634
|
+
plugins,
|
|
635
|
+
filterProps,
|
|
636
|
+
selectionProps,
|
|
637
|
+
sortProps,
|
|
638
|
+
columnDefinitions,
|
|
639
|
+
getScrollableElement,
|
|
640
|
+
infiniteScrollProps,
|
|
641
|
+
getRawColumnDefinition,
|
|
642
|
+
]);
|
|
643
|
+
const forceUpdatePlugins = React.useCallback(() => {
|
|
644
|
+
setProcessedData(initializePlugins());
|
|
645
|
+
}, [initializePlugins]);
|
|
646
|
+
// Handle re-initialization when props change
|
|
647
|
+
React.useEffect(() => {
|
|
648
|
+
setProcessedData(initializePlugins());
|
|
649
|
+
}, [initializePlugins]);
|
|
650
|
+
return { processedData, activePlugins, visibleColumns, forceUpdatePlugins };
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
function InfiniteTable(props) {
|
|
654
|
+
const { columnDefinitions, data, noDataComponent, maxHeight, onRowClick, footerRows, mobileBreakpoint, plugins, enablePageLevelStickyHeader, infiniteScrollProps, filterProps, selectionProps, animationProps, sortProps, } = props;
|
|
655
|
+
const tableContainerRef = React.useRef(null);
|
|
656
|
+
const headerRef = React.useRef(null);
|
|
657
|
+
const { isMobile, isHeaderSticky, debouncedScrollHandler } = useResponsiveTable({
|
|
658
|
+
mobileBreakpoint,
|
|
659
|
+
enablePageLevelStickyHeader,
|
|
660
|
+
maxHeight,
|
|
661
|
+
headerRef,
|
|
662
|
+
scrollableRef: tableContainerRef,
|
|
663
|
+
});
|
|
664
|
+
const [internalData, setInternalData] = React.useState(data || []);
|
|
665
|
+
const [isLoadingMore, setIsLoadingMore] = React.useState(false);
|
|
666
|
+
const [internalHasMore, setInternalHasMore] = React.useState(true);
|
|
667
|
+
const getScrollableElement = React.useCallback(() => tableContainerRef.current, []);
|
|
668
|
+
const { processedData, activePlugins, visibleColumns } = useTablePlugins({
|
|
669
|
+
data: internalData,
|
|
670
|
+
plugins,
|
|
671
|
+
filterProps,
|
|
672
|
+
selectionProps,
|
|
673
|
+
sortProps,
|
|
674
|
+
columnDefinitions,
|
|
675
|
+
getScrollableElement,
|
|
676
|
+
infiniteScrollProps,
|
|
677
|
+
});
|
|
678
|
+
const currentHasMore = (infiniteScrollProps === null || infiniteScrollProps === void 0 ? void 0 : infiniteScrollProps.hasMore) !== undefined
|
|
679
|
+
? infiniteScrollProps.hasMore
|
|
680
|
+
: internalHasMore;
|
|
681
|
+
const currentData = React.useMemo(() => {
|
|
682
|
+
if (Array.isArray(processedData) && processedData.length > 0) {
|
|
683
|
+
return processedData;
|
|
560
684
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
}
|
|
564
|
-
componentWillUnmount() {
|
|
565
|
-
window.removeEventListener('resize', this.debouncedResize);
|
|
566
|
-
if (this.props.enablePageLevelStickyHeader !== false) {
|
|
567
|
-
window.removeEventListener('scroll', this.handleScroll);
|
|
685
|
+
else {
|
|
686
|
+
return [];
|
|
568
687
|
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
688
|
+
}, [processedData]);
|
|
689
|
+
const hasData = React.useMemo(() => currentData.length > 0, [currentData]);
|
|
690
|
+
const noDataComponentNode = noDataComponent || React.createElement("div", { className: styles$2.noData }, "No data");
|
|
691
|
+
const defaultLoadingComponent = React.createElement(LoadingSpinner, null);
|
|
692
|
+
const defaultNoMoreDataComponent = React.createElement(NoMoreDataMessage, null);
|
|
693
|
+
const getRawColumnDefinition = (columnDefinition) => {
|
|
694
|
+
if (typeof columnDefinition === 'function') {
|
|
695
|
+
if (currentData.length === 0) {
|
|
696
|
+
return { displayLabel: '', cellRenderer: () => '' };
|
|
575
697
|
}
|
|
576
|
-
|
|
577
|
-
|
|
698
|
+
return columnDefinition(currentData[0], 0);
|
|
699
|
+
}
|
|
700
|
+
return columnDefinition;
|
|
701
|
+
};
|
|
702
|
+
const getColumnDefinition = (columnDefinition, rowIndex) => {
|
|
703
|
+
if (!hasData) {
|
|
704
|
+
return { displayLabel: '', cellRenderer: () => '' };
|
|
705
|
+
}
|
|
706
|
+
return columnDefinition instanceof Function ? columnDefinition(currentData[rowIndex], rowIndex) : columnDefinition;
|
|
707
|
+
};
|
|
708
|
+
const loadMoreData = React.useCallback(() => __awaiter(this, void 0, void 0, function* () {
|
|
709
|
+
var _a;
|
|
710
|
+
if (!infiniteScrollProps || isLoadingMore)
|
|
711
|
+
return;
|
|
712
|
+
setIsLoadingMore(true);
|
|
713
|
+
try {
|
|
714
|
+
const newItems = yield ((_a = infiniteScrollProps.onLoadMore) === null || _a === void 0 ? void 0 : _a.call(infiniteScrollProps, internalData));
|
|
715
|
+
if (infiniteScrollProps.hasMore === undefined) {
|
|
716
|
+
if (!newItems || newItems.length === 0) {
|
|
717
|
+
setInternalHasMore(false);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (newItems && newItems.length > 0) {
|
|
721
|
+
setInternalData(prevData => [...prevData, ...newItems]);
|
|
578
722
|
}
|
|
579
723
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
prevProps.plugins !== this.props.plugins ||
|
|
583
|
-
prevProps.filterProps !== this.props.filterProps ||
|
|
584
|
-
prevProps.selectionProps !== this.props.selectionProps) {
|
|
585
|
-
const { processedData, activePlugins } = this.initializePlugins();
|
|
586
|
-
this.setState({ processedData, activePlugins });
|
|
724
|
+
catch (error) {
|
|
725
|
+
console.error("Failed to load more items for infinite scroll:", error);
|
|
587
726
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
var _a, _b;
|
|
591
|
-
const activePlugins = [];
|
|
592
|
-
// Add explicitly provided plugins first
|
|
593
|
-
if (this.props.plugins) {
|
|
594
|
-
activePlugins.push(...this.props.plugins);
|
|
727
|
+
finally {
|
|
728
|
+
setIsLoadingMore(false);
|
|
595
729
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
730
|
+
}), [infiniteScrollProps, isLoadingMore, internalData]);
|
|
731
|
+
React.useEffect(() => {
|
|
732
|
+
setInternalData(data || []);
|
|
733
|
+
}, [data]);
|
|
734
|
+
React.useEffect(() => {
|
|
735
|
+
if (data.length === 0) {
|
|
736
|
+
loadMoreData();
|
|
599
737
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
738
|
+
}, [data.length, loadMoreData]);
|
|
739
|
+
const handleScrollForInfinite = React.useCallback((currentTarget) => {
|
|
740
|
+
if (!currentTarget)
|
|
741
|
+
return;
|
|
742
|
+
const { scrollHeight, scrollTop, clientHeight } = currentTarget;
|
|
743
|
+
if (currentHasMore && !isLoadingMore && scrollHeight - scrollTop - clientHeight < 100) {
|
|
744
|
+
loadMoreData();
|
|
603
745
|
}
|
|
746
|
+
}, [currentHasMore, isLoadingMore, loadMoreData]);
|
|
747
|
+
const onHeaderClickCallback = (colDef) => {
|
|
748
|
+
var _a;
|
|
749
|
+
const rawColumnDefinition = getRawColumnDefinition(colDef);
|
|
750
|
+
return (_a = rawColumnDefinition.interactivity) === null || _a === void 0 ? void 0 : _a.onHeaderClick;
|
|
751
|
+
};
|
|
752
|
+
const getClickableHeaderClassName = (onHeaderClickCallback, colDef) => {
|
|
753
|
+
var _a;
|
|
754
|
+
const rawColumnDefinition = getRawColumnDefinition(colDef);
|
|
755
|
+
return onHeaderClickCallback
|
|
756
|
+
? ((_a = rawColumnDefinition.interactivity) === null || _a === void 0 ? void 0 : _a.className) || styles$2.clickableHeader
|
|
757
|
+
: '';
|
|
758
|
+
};
|
|
759
|
+
const getHeaderProps = (colDef) => {
|
|
760
|
+
const headerProps = {};
|
|
604
761
|
activePlugins.forEach((plugin) => {
|
|
605
|
-
if (plugin.
|
|
606
|
-
plugin.
|
|
607
|
-
getData: () => this.props.data,
|
|
608
|
-
forceUpdate: () => {
|
|
609
|
-
const { processedData, activePlugins } = this.initializePlugins();
|
|
610
|
-
this.setState({ processedData, activePlugins });
|
|
611
|
-
},
|
|
612
|
-
getScrollableElement: () => this.tableContainerRef.current,
|
|
613
|
-
infiniteScrollProps: this.props.infiniteScrollProps,
|
|
614
|
-
filterProps: this.props.filterProps,
|
|
615
|
-
selectionProps: this.props.selectionProps,
|
|
616
|
-
columnDefinitions: this.props.columnDefinitions,
|
|
617
|
-
});
|
|
762
|
+
if (plugin.getHeaderProps) {
|
|
763
|
+
Object.assign(headerProps, plugin.getHeaderProps(getRawColumnDefinition(colDef)));
|
|
618
764
|
}
|
|
619
765
|
});
|
|
620
|
-
|
|
621
|
-
|
|
766
|
+
return headerProps;
|
|
767
|
+
};
|
|
768
|
+
const getRowId = (row, index) => {
|
|
769
|
+
if (selectionProps && selectionProps.rowIdKey) {
|
|
770
|
+
return row[selectionProps.rowIdKey];
|
|
771
|
+
}
|
|
772
|
+
return index;
|
|
773
|
+
};
|
|
774
|
+
const getRowProps = (row) => {
|
|
775
|
+
const rowProps = {};
|
|
776
|
+
const clickHandlers = [];
|
|
622
777
|
activePlugins.forEach((plugin) => {
|
|
623
|
-
if (plugin.
|
|
624
|
-
|
|
778
|
+
if (plugin.getRowProps) {
|
|
779
|
+
const props = plugin.getRowProps(row);
|
|
780
|
+
if (props.className) {
|
|
781
|
+
rowProps.className = `${rowProps.className || ''} ${props.className}`.trim();
|
|
782
|
+
}
|
|
783
|
+
if (props.onClick) {
|
|
784
|
+
clickHandlers.push(props.onClick);
|
|
785
|
+
}
|
|
786
|
+
const rest = __rest(props, []);
|
|
787
|
+
Object.assign(rowProps, rest);
|
|
625
788
|
}
|
|
626
789
|
});
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
790
|
+
if (clickHandlers.length > 0) {
|
|
791
|
+
rowProps.onClick = (e) => {
|
|
792
|
+
clickHandlers.forEach(handler => handler(e));
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
return rowProps;
|
|
796
|
+
};
|
|
797
|
+
const renderCell = (content, row, colDef) => {
|
|
798
|
+
let processedContent = content;
|
|
799
|
+
activePlugins.forEach((plugin) => {
|
|
800
|
+
if (plugin.renderCell) {
|
|
801
|
+
processedContent = plugin.renderCell(processedContent, row, colDef);
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
return processedContent;
|
|
805
|
+
};
|
|
806
|
+
const rowClickFunction = onRowClick || (() => { });
|
|
807
|
+
const mobileFooter = React.useMemo(() => {
|
|
808
|
+
if (!footerRows || footerRows.length === 0) {
|
|
809
|
+
return null;
|
|
632
810
|
}
|
|
633
|
-
return
|
|
811
|
+
return (React.createElement("div", { className: styles$2.footerCard },
|
|
812
|
+
React.createElement("div", { className: styles$2['footer-card-body'] }, footerRows.map((row, rowIndex) => {
|
|
813
|
+
let currentColumnIndex = 0;
|
|
814
|
+
return (React.createElement("div", { key: rowIndex }, row.columns.map((col, colIndex) => {
|
|
815
|
+
let label = col.displayLabel;
|
|
816
|
+
if (!label && col.colSpan === 1) {
|
|
817
|
+
const header = columnDefinitions[currentColumnIndex];
|
|
818
|
+
if (header) {
|
|
819
|
+
label = getRawColumnDefinition(header).displayLabel;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
currentColumnIndex += col.colSpan;
|
|
823
|
+
return (React.createElement("p", { key: colIndex, className: `${styles$2['footer-card-row']} ${col.className || ''} ${col.onCellClick ? styles$2.clickableFooterCell : ''}`, onClick: col.onCellClick },
|
|
824
|
+
label && React.createElement("span", { className: styles$2['card-label'] }, label),
|
|
825
|
+
React.createElement("span", { className: styles$2['card-value'] }, col.cellRenderer())));
|
|
826
|
+
})));
|
|
827
|
+
}))));
|
|
828
|
+
}, [footerRows, columnDefinitions]);
|
|
829
|
+
const renderPluginHeaders = React.useCallback(() => {
|
|
830
|
+
if (!activePlugins) {
|
|
831
|
+
return null;
|
|
832
|
+
}
|
|
833
|
+
return activePlugins.map((plugin) => {
|
|
834
|
+
if (plugin.renderHeader) {
|
|
835
|
+
if (plugin.id === 'sort' && !isMobile) {
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
return React.createElement("div", { key: plugin.id }, plugin.renderHeader());
|
|
839
|
+
}
|
|
840
|
+
return null;
|
|
841
|
+
});
|
|
842
|
+
}, [activePlugins, isMobile]);
|
|
843
|
+
const renderPluginFooters = React.useCallback(() => {
|
|
844
|
+
if (!plugins) {
|
|
845
|
+
return null;
|
|
846
|
+
}
|
|
847
|
+
return plugins.map((plugin) => {
|
|
848
|
+
if (plugin.renderFooter) {
|
|
849
|
+
return React.createElement("div", { key: plugin.id + '-footer' }, plugin.renderFooter());
|
|
850
|
+
}
|
|
851
|
+
return null;
|
|
852
|
+
});
|
|
853
|
+
}, [plugins]);
|
|
854
|
+
const infiniteStatusUI = (React.createElement(React.Fragment, null,
|
|
855
|
+
isLoadingMore && ((infiniteScrollProps === null || infiniteScrollProps === void 0 ? void 0 : infiniteScrollProps.loadingMoreComponent) || defaultLoadingComponent),
|
|
856
|
+
!isLoadingMore && !currentHasMore && ((infiniteScrollProps === null || infiniteScrollProps === void 0 ? void 0 : infiniteScrollProps.noMoreDataComponent) || defaultNoMoreDataComponent)));
|
|
857
|
+
const desktopView = (React.createElement(DesktopView, { columnDefinitions: visibleColumns, originalColumnDefinitions: columnDefinitions, currentData: currentData, maxHeight: maxHeight, isHeaderSticky: isHeaderSticky, tableContainerRef: tableContainerRef, headerRef: headerRef, getRowProps: getRowProps, getHeaderProps: getHeaderProps, onHeaderClickCallback: onHeaderClickCallback, getClickableHeaderClassName: getClickableHeaderClassName, getRawColumnDefinition: getRawColumnDefinition, getColumnDefinition: getColumnDefinition, renderCell: renderCell, rowClickFunction: rowClickFunction, footerRows: footerRows, renderPluginFooters: renderPluginFooters, animationProps: animationProps, onRowClick: onRowClick, selectionProps: selectionProps, onScroll: (e) => {
|
|
858
|
+
debouncedScrollHandler(e.currentTarget); // For sticky header
|
|
859
|
+
handleScrollForInfinite(e.currentTarget); // For infinite scroll
|
|
860
|
+
} }));
|
|
861
|
+
const mobileView = (React.createElement(MobileView, { currentData: currentData, columnDefinitions: visibleColumns, onRowClick: onRowClick, selectionProps: selectionProps, animationProps: animationProps, getRowProps: getRowProps, getRowId: getRowId, getColumnDefinition: getColumnDefinition, onHeaderClickCallback: onHeaderClickCallback, getClickableHeaderClassName: getClickableHeaderClassName, renderCell: renderCell, rowClickFunction: rowClickFunction, mobileFooter: mobileFooter }));
|
|
862
|
+
const skeletonView = (React.createElement(SkeletonView, { isMobile: isMobile, columnDefinitions: visibleColumns }));
|
|
863
|
+
if ((animationProps === null || animationProps === void 0 ? void 0 : animationProps.isLoading) && !hasData) {
|
|
864
|
+
return skeletonView;
|
|
634
865
|
}
|
|
635
|
-
|
|
866
|
+
return (React.createElement("div", null,
|
|
867
|
+
React.createElement("div", { style: { display: 'flex', justifyContent: 'flex-end' } }, renderPluginHeaders()),
|
|
868
|
+
!hasData && noDataComponentNode,
|
|
869
|
+
hasData && (isMobile ? mobileView : desktopView),
|
|
870
|
+
hasData && infiniteStatusUI));
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function ResponsiveTable(props) {
|
|
874
|
+
const { columnDefinitions, data, noDataComponent, maxHeight, onRowClick, footerRows, mobileBreakpoint, plugins, enablePageLevelStickyHeader, infiniteScrollProps, filterProps, selectionProps, animationProps, sortProps, } = props;
|
|
875
|
+
const tableContainerRef = React.useRef(null);
|
|
876
|
+
const headerRef = React.useRef(null);
|
|
877
|
+
const { isMobile, isHeaderSticky } = useResponsiveTable({
|
|
878
|
+
mobileBreakpoint,
|
|
879
|
+
enablePageLevelStickyHeader,
|
|
880
|
+
maxHeight,
|
|
881
|
+
headerRef,
|
|
882
|
+
scrollableRef: tableContainerRef,
|
|
883
|
+
});
|
|
884
|
+
const getScrollableElement = React.useCallback(() => tableContainerRef.current, []);
|
|
885
|
+
const { processedData, activePlugins, visibleColumns } = useTablePlugins({
|
|
886
|
+
data,
|
|
887
|
+
plugins,
|
|
888
|
+
filterProps,
|
|
889
|
+
selectionProps,
|
|
890
|
+
sortProps,
|
|
891
|
+
columnDefinitions,
|
|
892
|
+
getScrollableElement,
|
|
893
|
+
infiniteScrollProps,
|
|
894
|
+
});
|
|
895
|
+
const currentData = React.useMemo(() => {
|
|
896
|
+
if (Array.isArray(processedData) && processedData.length > 0) {
|
|
897
|
+
return processedData;
|
|
898
|
+
}
|
|
899
|
+
else {
|
|
900
|
+
return [];
|
|
901
|
+
}
|
|
902
|
+
}, [processedData]);
|
|
903
|
+
const hasData = React.useMemo(() => currentData.length > 0, [currentData]);
|
|
904
|
+
const noDataSvg = (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "#ccc", height: "40", width: "40", viewBox: "0 0 24 24" },
|
|
905
|
+
React.createElement("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-14h2v6h-2zm0 8h2v2h-2z" })));
|
|
906
|
+
const noDataComponentNode = noDataComponent || (React.createElement("div", { className: styles$2.noDataWrapper },
|
|
907
|
+
noDataSvg,
|
|
908
|
+
React.createElement("div", { className: styles$2.noData }, "No data")));
|
|
909
|
+
const getRawColumnDefinition = (columnDefinition) => {
|
|
636
910
|
if (typeof columnDefinition === 'function') {
|
|
637
|
-
|
|
638
|
-
// Return a placeholder, as the table body won't be rendered anyway.
|
|
639
|
-
if (this.data.length === 0) {
|
|
911
|
+
if (currentData.length === 0) {
|
|
640
912
|
return { displayLabel: '', cellRenderer: () => '' };
|
|
641
913
|
}
|
|
642
|
-
return columnDefinition(
|
|
914
|
+
return columnDefinition(currentData[0], 0);
|
|
643
915
|
}
|
|
644
916
|
return columnDefinition;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
return rawColumnDefinition.interactivity.onHeaderClick;
|
|
650
|
-
}
|
|
651
|
-
else {
|
|
652
|
-
return undefined;
|
|
917
|
+
};
|
|
918
|
+
const getColumnDefinition = (columnDefinition, rowIndex) => {
|
|
919
|
+
if (!hasData) {
|
|
920
|
+
return { displayLabel: '', cellRenderer: () => '' };
|
|
653
921
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
922
|
+
return columnDefinition instanceof Function ? columnDefinition(currentData[0], rowIndex) : columnDefinition;
|
|
923
|
+
};
|
|
924
|
+
const onHeaderClickCallback = (colDef) => {
|
|
925
|
+
var _a;
|
|
926
|
+
const rawColumnDefinition = getRawColumnDefinition(colDef);
|
|
927
|
+
return (_a = rawColumnDefinition.interactivity) === null || _a === void 0 ? void 0 : _a.onHeaderClick;
|
|
928
|
+
};
|
|
929
|
+
const getClickableHeaderClassName = (onHeaderClickCallback, colDef) => {
|
|
930
|
+
var _a;
|
|
931
|
+
const rawColumnDefinition = getRawColumnDefinition(colDef);
|
|
932
|
+
return onHeaderClickCallback
|
|
933
|
+
? ((_a = rawColumnDefinition.interactivity) === null || _a === void 0 ? void 0 : _a.className) || styles$2.clickableHeader
|
|
659
934
|
: '';
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
getHeaderProps(columnDefinition) {
|
|
935
|
+
};
|
|
936
|
+
const getHeaderProps = (colDef) => {
|
|
663
937
|
const headerProps = {};
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
});
|
|
670
|
-
}
|
|
938
|
+
activePlugins.forEach((plugin) => {
|
|
939
|
+
if (plugin.getHeaderProps) {
|
|
940
|
+
Object.assign(headerProps, plugin.getHeaderProps(getRawColumnDefinition(colDef)));
|
|
941
|
+
}
|
|
942
|
+
});
|
|
671
943
|
return headerProps;
|
|
672
|
-
}
|
|
673
|
-
getRowId(row, index) {
|
|
674
|
-
const { selectionProps } = this.props;
|
|
944
|
+
};
|
|
945
|
+
const getRowId = (row, index) => {
|
|
675
946
|
if (selectionProps && selectionProps.rowIdKey) {
|
|
676
947
|
return row[selectionProps.rowIdKey];
|
|
677
948
|
}
|
|
678
949
|
return index;
|
|
679
|
-
}
|
|
680
|
-
getRowProps(row) {
|
|
950
|
+
};
|
|
951
|
+
const getRowProps = (row) => {
|
|
681
952
|
const rowProps = {};
|
|
682
953
|
const clickHandlers = [];
|
|
683
|
-
|
|
954
|
+
activePlugins.forEach((plugin) => {
|
|
684
955
|
if (plugin.getRowProps) {
|
|
685
956
|
const props = plugin.getRowProps(row);
|
|
686
|
-
if (plugin.id === 'selection') ;
|
|
687
957
|
if (props.className) {
|
|
688
958
|
rowProps.className = `${rowProps.className || ''} ${props.className}`.trim();
|
|
689
959
|
}
|
|
690
960
|
if (props.onClick) {
|
|
691
961
|
clickHandlers.push(props.onClick);
|
|
692
962
|
}
|
|
693
|
-
|
|
694
|
-
const { className, onClick } = props, rest = __rest(props, ["className", "onClick"]);
|
|
963
|
+
const rest = __rest(props, []);
|
|
695
964
|
Object.assign(rowProps, rest);
|
|
696
965
|
}
|
|
697
966
|
});
|
|
@@ -701,43 +970,30 @@ class ResponsiveTable extends React.Component {
|
|
|
701
970
|
};
|
|
702
971
|
}
|
|
703
972
|
return rowProps;
|
|
704
|
-
}
|
|
705
|
-
renderCell(content, row, colDef) {
|
|
973
|
+
};
|
|
974
|
+
const renderCell = (content, row, colDef) => {
|
|
706
975
|
let processedContent = content;
|
|
707
|
-
|
|
976
|
+
activePlugins.forEach((plugin) => {
|
|
708
977
|
if (plugin.renderCell) {
|
|
709
978
|
processedContent = plugin.renderCell(processedContent, row, colDef);
|
|
710
979
|
}
|
|
711
980
|
});
|
|
712
981
|
return processedContent;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
}
|
|
718
|
-
else {
|
|
719
|
-
return () => { };
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
get tableFooter() {
|
|
723
|
-
if (!this.props.footerRows || this.props.footerRows.length === 0) {
|
|
724
|
-
return null;
|
|
725
|
-
}
|
|
726
|
-
return (React.createElement("tfoot", null, this.props.footerRows.map((row, rowIndex) => (React.createElement("tr", { key: rowIndex }, row.columns.map((col, colIndex) => (React.createElement("td", { key: colIndex, colSpan: col.colSpan, className: `${styles$2.footerCell} ${col.className || ''} ${col.onCellClick ? styles$2.clickableFooterCell : ''}`, onClick: col.onCellClick }, col.cellRenderer()))))))));
|
|
727
|
-
}
|
|
728
|
-
get mobileFooter() {
|
|
729
|
-
if (!this.props.footerRows || this.props.footerRows.length === 0) {
|
|
982
|
+
};
|
|
983
|
+
const rowClickFunction = onRowClick || (() => { });
|
|
984
|
+
const mobileFooter = React.useMemo(() => {
|
|
985
|
+
if (!footerRows || footerRows.length === 0) {
|
|
730
986
|
return null;
|
|
731
987
|
}
|
|
732
988
|
return (React.createElement("div", { className: styles$2.footerCard },
|
|
733
|
-
React.createElement("div", { className: styles$2['footer-card-body'] },
|
|
989
|
+
React.createElement("div", { className: styles$2['footer-card-body'] }, footerRows.map((row, rowIndex) => {
|
|
734
990
|
let currentColumnIndex = 0;
|
|
735
991
|
return (React.createElement("div", { key: rowIndex }, row.columns.map((col, colIndex) => {
|
|
736
992
|
let label = col.displayLabel;
|
|
737
993
|
if (!label && col.colSpan === 1) {
|
|
738
|
-
const header =
|
|
994
|
+
const header = columnDefinitions[currentColumnIndex];
|
|
739
995
|
if (header) {
|
|
740
|
-
label =
|
|
996
|
+
label = getRawColumnDefinition(header).displayLabel;
|
|
741
997
|
}
|
|
742
998
|
}
|
|
743
999
|
currentColumnIndex += col.colSpan;
|
|
@@ -746,134 +1002,46 @@ class ResponsiveTable extends React.Component {
|
|
|
746
1002
|
React.createElement("span", { className: styles$2['card-value'] }, col.cellRenderer())));
|
|
747
1003
|
})));
|
|
748
1004
|
}))));
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
if (this.state.isMobile) {
|
|
754
|
-
return (React.createElement("div", null, [...Array(skeletonRowCount)].map((_, i) => (React.createElement("div", { key: i, className: styles$2.skeletonCard }, [...Array(columnCount)].map((_, j) => (React.createElement("div", { key: j, className: `${styles$2.skeleton} ${styles$2.skeletonText}`, style: { marginBottom: '0.5rem' } }))))))));
|
|
755
|
-
}
|
|
756
|
-
return (React.createElement("table", { className: styles$2.responsiveTable },
|
|
757
|
-
React.createElement("thead", null,
|
|
758
|
-
React.createElement("tr", null, [...Array(columnCount)].map((_, i) => (React.createElement("th", { key: i },
|
|
759
|
-
React.createElement("div", { className: `${styles$2.skeleton} ${styles$2.skeletonText}` })))))),
|
|
760
|
-
React.createElement("tbody", null, [...Array(skeletonRowCount)].map((_, i) => (React.createElement("tr", { key: i }, [...Array(columnCount)].map((_, j) => (React.createElement("td", { key: j },
|
|
761
|
-
React.createElement("div", { className: `${styles$2.skeleton} ${styles$2.skeletonText}` }))))))))));
|
|
762
|
-
}
|
|
763
|
-
get mobileView() {
|
|
764
|
-
const isClickable = this.props.onRowClick || this.props.selectionProps;
|
|
765
|
-
return (React.createElement("div", null,
|
|
766
|
-
this.data.map((row, rowIndex) => {
|
|
767
|
-
var _a;
|
|
768
|
-
const rowProps = this.getRowProps(row);
|
|
769
|
-
const pluginOnClick = rowProps.onClick;
|
|
770
|
-
return (React.createElement("div", { key: this.getRowId(row, rowIndex), className: `${styles$2.card} ${isClickable ? styles$2.clickableRow : ''} ${((_a = this.props.animationProps) === null || _a === void 0 ? void 0 : _a.animateOnLoad) ? styles$2.animatedRow : ''} ${rowProps.className || ''}`.trim(), style: { animationDelay: `${rowIndex * 0.05}s` }, "aria-selected": rowProps['aria-selected'], onClick: (e) => {
|
|
771
|
-
if (pluginOnClick)
|
|
772
|
-
pluginOnClick(e);
|
|
773
|
-
this.rowClickFunction(row);
|
|
774
|
-
} },
|
|
775
|
-
React.createElement("div", { className: styles$2['card-header'] }, " "),
|
|
776
|
-
React.createElement("div", { className: styles$2['card-body'] }, this.props.columnDefinitions.map((columnDefinition, colIndex) => {
|
|
777
|
-
const colDef = this.getColumnDefinition(columnDefinition, rowIndex);
|
|
778
|
-
const onHeaderClickCallback = this.onHeaderClickCallback(colDef);
|
|
779
|
-
const clickableHeaderClassName = this.getClickableHeaderClassName(onHeaderClickCallback, colDef);
|
|
780
|
-
return (React.createElement("div", { key: colIndex, className: styles$2['card-row'] },
|
|
781
|
-
React.createElement("p", null,
|
|
782
|
-
React.createElement("span", { className: `${styles$2['card-label']} ${clickableHeaderClassName}`, onClick: (e) => {
|
|
783
|
-
if (onHeaderClickCallback) {
|
|
784
|
-
e.stopPropagation();
|
|
785
|
-
onHeaderClickCallback(colDef.interactivity.id);
|
|
786
|
-
}
|
|
787
|
-
} }, colDef.displayLabel),
|
|
788
|
-
React.createElement("span", { className: styles$2['card-value'] }, this.renderCell(colDef.cellRenderer(row), row, colDef)))));
|
|
789
|
-
}))));
|
|
790
|
-
}),
|
|
791
|
-
this.mobileFooter));
|
|
792
|
-
}
|
|
793
|
-
get largeScreenView() {
|
|
794
|
-
const useFixedHeaders = this.props.maxHeight ? true : false;
|
|
795
|
-
const isClickable = this.props.onRowClick || this.props.selectionProps;
|
|
796
|
-
const fixedHeadersStyle = useFixedHeaders
|
|
797
|
-
? { maxHeight: this.props.maxHeight, overflowY: 'auto' }
|
|
798
|
-
: {};
|
|
799
|
-
const headerClassName = useFixedHeaders
|
|
800
|
-
? styles$2.internalStickyHeader
|
|
801
|
-
: (this.state.isHeaderSticky ? styles$2.stickyHeader : '');
|
|
802
|
-
return (React.createElement("div", { style: fixedHeadersStyle, ref: this.tableContainerRef },
|
|
803
|
-
React.createElement("table", { className: styles$2['responsiveTable'] },
|
|
804
|
-
React.createElement("thead", { ref: this.headerRef, className: headerClassName },
|
|
805
|
-
React.createElement("tr", null, this.props.columnDefinitions.map((columnDefinition, colIndex) => {
|
|
806
|
-
const onHeaderClickCallback = this.onHeaderClickCallback(columnDefinition);
|
|
807
|
-
const clickableHeaderClassName = this.getClickableHeaderClassName(onHeaderClickCallback, columnDefinition);
|
|
808
|
-
const headerProps = this.getHeaderProps(columnDefinition);
|
|
809
|
-
// Combine class names: existing clickable, and plugin-provided (mapped to CSS Modules)
|
|
810
|
-
const combinedClassName = `${clickableHeaderClassName} ${headerProps.className ? styles$2[headerProps.className] : ''}`.trim();
|
|
811
|
-
// Remove className from headerProps to avoid duplication
|
|
812
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
813
|
-
const { className } = headerProps, restHeaderProps = __rest(headerProps, ["className"]);
|
|
814
|
-
return (React.createElement("th", Object.assign({ key: colIndex, className: combinedClassName }, restHeaderProps),
|
|
815
|
-
React.createElement("div", { className: styles$2.headerInnerWrapper },
|
|
816
|
-
React.createElement("div", { className: styles$2.headerContent }, this.getColumnDefinition(columnDefinition, 0).displayLabel),
|
|
817
|
-
React.createElement("span", { className: styles$2.sortIcon }))));
|
|
818
|
-
}))),
|
|
819
|
-
React.createElement("tbody", null, this.data.map((row, rowIndex) => {
|
|
820
|
-
var _a;
|
|
821
|
-
const rowProps = this.getRowProps(row);
|
|
822
|
-
const pluginOnClick = rowProps.onClick;
|
|
823
|
-
return (React.createElement("tr", { key: this.getRowId(row, rowIndex), className: `${isClickable ? styles$2.clickableRow : ''} ${((_a = this.props.animationProps) === null || _a === void 0 ? void 0 : _a.animateOnLoad) ? styles$2.animatedRow : ''} ${rowProps.className || ''}`.trim(), style: { animationDelay: `${rowIndex * 0.05}s` }, "aria-selected": rowProps['aria-selected'], onClick: (e) => {
|
|
824
|
-
if (pluginOnClick) {
|
|
825
|
-
pluginOnClick(e);
|
|
826
|
-
}
|
|
827
|
-
this.rowClickFunction(row);
|
|
828
|
-
} }, this.props.columnDefinitions.map((columnDefinition, colIndex) => {
|
|
829
|
-
const colDef = this.getColumnDefinition(columnDefinition, rowIndex);
|
|
830
|
-
const cellContent = colDef.cellRenderer(row);
|
|
831
|
-
return (React.createElement("td", { key: colIndex }, this.renderCell(cellContent, row, colDef)));
|
|
832
|
-
})));
|
|
833
|
-
})),
|
|
834
|
-
this.tableFooter),
|
|
835
|
-
this.renderPluginFooters()));
|
|
836
|
-
}
|
|
837
|
-
renderPluginHeaders() {
|
|
838
|
-
if (!this.state.activePlugins) {
|
|
1005
|
+
}, [footerRows, columnDefinitions]);
|
|
1006
|
+
const skeletonView = (React.createElement(SkeletonView, { isMobile: isMobile, columnDefinitions: visibleColumns }));
|
|
1007
|
+
const renderPluginHeaders = React.useCallback(() => {
|
|
1008
|
+
if (!activePlugins) {
|
|
839
1009
|
return null;
|
|
840
1010
|
}
|
|
841
|
-
return
|
|
1011
|
+
return activePlugins.map((plugin) => {
|
|
842
1012
|
if (plugin.renderHeader) {
|
|
843
|
-
|
|
844
|
-
if (plugin.id === 'sort' && !this.state.isMobile) {
|
|
1013
|
+
if (plugin.id === 'sort' && !isMobile) {
|
|
845
1014
|
return null;
|
|
846
1015
|
}
|
|
847
1016
|
return React.createElement("div", { key: plugin.id }, plugin.renderHeader());
|
|
848
1017
|
}
|
|
849
1018
|
return null;
|
|
850
1019
|
});
|
|
851
|
-
}
|
|
852
|
-
renderPluginFooters() {
|
|
853
|
-
if (!
|
|
1020
|
+
}, [activePlugins, isMobile]);
|
|
1021
|
+
const renderPluginFooters = React.useCallback(() => {
|
|
1022
|
+
if (!plugins) {
|
|
854
1023
|
return null;
|
|
855
1024
|
}
|
|
856
|
-
return
|
|
1025
|
+
return plugins.map((plugin) => {
|
|
857
1026
|
if (plugin.renderFooter) {
|
|
858
1027
|
return React.createElement("div", { key: plugin.id + '-footer' }, plugin.renderFooter());
|
|
859
1028
|
}
|
|
860
1029
|
return null;
|
|
861
1030
|
});
|
|
1031
|
+
}, [plugins]);
|
|
1032
|
+
const mobileView = (React.createElement(MobileView, { currentData: currentData, columnDefinitions: visibleColumns, onRowClick: onRowClick, selectionProps: selectionProps, animationProps: animationProps, getRowProps: getRowProps, getRowId: getRowId, getColumnDefinition: getColumnDefinition, onHeaderClickCallback: onHeaderClickCallback, getClickableHeaderClassName: getClickableHeaderClassName, renderCell: renderCell, rowClickFunction: rowClickFunction, mobileFooter: mobileFooter }));
|
|
1033
|
+
const largeScreenView = (React.createElement(DesktopView, { columnDefinitions: visibleColumns, originalColumnDefinitions: columnDefinitions, currentData: currentData, maxHeight: maxHeight, isHeaderSticky: isHeaderSticky, tableContainerRef: tableContainerRef, headerRef: headerRef, getRowProps: getRowProps, getHeaderProps: getHeaderProps, onHeaderClickCallback: onHeaderClickCallback, getClickableHeaderClassName: getClickableHeaderClassName, getRawColumnDefinition: getRawColumnDefinition, getColumnDefinition: getColumnDefinition, renderCell: renderCell, rowClickFunction: rowClickFunction, footerRows: footerRows, renderPluginFooters: renderPluginFooters, animationProps: animationProps, onRowClick: onRowClick, selectionProps: selectionProps }));
|
|
1034
|
+
if (infiniteScrollProps) {
|
|
1035
|
+
return React.createElement(InfiniteTable, Object.assign({}, props));
|
|
862
1036
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
if (this.props.infiniteScrollProps) {
|
|
866
|
-
return React.createElement(InfiniteTable, Object.assign({}, this.props));
|
|
867
|
-
}
|
|
868
|
-
if ((_a = this.props.animationProps) === null || _a === void 0 ? void 0 : _a.isLoading) {
|
|
869
|
-
return this.skeletonView;
|
|
870
|
-
}
|
|
871
|
-
return (React.createElement("div", null,
|
|
872
|
-
React.createElement("div", { style: { display: 'flex', justifyContent: 'flex-end' } }, this.renderPluginHeaders()),
|
|
873
|
-
!this.hasData && this.noDataComponent,
|
|
874
|
-
this.hasData && this.state.isMobile && this.mobileView,
|
|
875
|
-
this.hasData && !this.state.isMobile && this.largeScreenView));
|
|
1037
|
+
if (animationProps === null || animationProps === void 0 ? void 0 : animationProps.isLoading) {
|
|
1038
|
+
return skeletonView;
|
|
876
1039
|
}
|
|
1040
|
+
return (React.createElement("div", null,
|
|
1041
|
+
React.createElement("div", { style: { display: 'flex', justifyContent: 'flex-end' } }, renderPluginHeaders()),
|
|
1042
|
+
!hasData && noDataComponentNode,
|
|
1043
|
+
hasData && isMobile && mobileView,
|
|
1044
|
+
hasData && !isMobile && largeScreenView));
|
|
877
1045
|
}
|
|
878
1046
|
|
|
879
1047
|
class InfiniteScrollPlugin {
|
|
@@ -929,138 +1097,6 @@ class InfiniteScrollPlugin {
|
|
|
929
1097
|
}
|
|
930
1098
|
}
|
|
931
1099
|
|
|
932
|
-
// Type-safe sort comparer helpers
|
|
933
|
-
const createSortComparers = () => ({
|
|
934
|
-
numeric: (key) => (a, b, direction) => {
|
|
935
|
-
const numA = parseFloat(String(a[key]));
|
|
936
|
-
const numB = parseFloat(String(b[key]));
|
|
937
|
-
const aIsNaN = isNaN(numA);
|
|
938
|
-
const bIsNaN = isNaN(numB);
|
|
939
|
-
if (aIsNaN && bIsNaN)
|
|
940
|
-
return 0;
|
|
941
|
-
if (aIsNaN)
|
|
942
|
-
return 1; // Put non-numbers at the end
|
|
943
|
-
if (bIsNaN)
|
|
944
|
-
return -1;
|
|
945
|
-
return direction === 'asc' ? numA - numB : numB - numA;
|
|
946
|
-
},
|
|
947
|
-
caseInsensitiveString: (key) => (a, b, direction) => {
|
|
948
|
-
var _a, _b;
|
|
949
|
-
const valA = String((_a = a[key]) !== null && _a !== void 0 ? _a : '').toLowerCase();
|
|
950
|
-
const valB = String((_b = b[key]) !== null && _b !== void 0 ? _b : '').toLowerCase();
|
|
951
|
-
if (valA < valB)
|
|
952
|
-
return direction === 'asc' ? -1 : 1;
|
|
953
|
-
if (valA > valB)
|
|
954
|
-
return direction === 'asc' ? 1 : -1;
|
|
955
|
-
return 0;
|
|
956
|
-
},
|
|
957
|
-
date: (key) => (a, b, direction) => {
|
|
958
|
-
const dateA = new Date(String(a[key])).getTime();
|
|
959
|
-
const dateB = new Date(String(b[key])).getTime();
|
|
960
|
-
const aIsNaN = isNaN(dateA);
|
|
961
|
-
const bIsNaN = isNaN(dateB);
|
|
962
|
-
if (aIsNaN && bIsNaN)
|
|
963
|
-
return 0;
|
|
964
|
-
if (aIsNaN)
|
|
965
|
-
return 1; // Put invalid dates at the end
|
|
966
|
-
if (bIsNaN)
|
|
967
|
-
return -1;
|
|
968
|
-
return direction === 'asc' ? dateA - dateB : dateB - dateA;
|
|
969
|
-
},
|
|
970
|
-
});
|
|
971
|
-
class SortPlugin {
|
|
972
|
-
constructor(options) {
|
|
973
|
-
var _a, _b;
|
|
974
|
-
this.id = 'sort';
|
|
975
|
-
this.comparers = createSortComparers();
|
|
976
|
-
this.onPluginInit = (api) => {
|
|
977
|
-
this.api = api;
|
|
978
|
-
};
|
|
979
|
-
this.processData = (data) => {
|
|
980
|
-
if (!this.sortColumn || !this.sortDirection) {
|
|
981
|
-
return data;
|
|
982
|
-
}
|
|
983
|
-
const columnDef = this.api.columnDefinitions.find((col) => (typeof col === 'object' && col.columnId === this.sortColumn));
|
|
984
|
-
if (!columnDef) {
|
|
985
|
-
return data;
|
|
986
|
-
}
|
|
987
|
-
const sortedData = [...data].sort((a, b) => {
|
|
988
|
-
if ('sortComparer' in columnDef && columnDef.sortComparer) {
|
|
989
|
-
if (columnDef.sortComparer.length < 3) {
|
|
990
|
-
console.warn(`The custom sortComparer for column '${this.sortColumn}' should accept all three parameters (a, b, direction) to ensure correct sorting behavior. You provided a function with ${columnDef.sortComparer.length} parameters.`);
|
|
991
|
-
}
|
|
992
|
-
return columnDef.sortComparer(a, b, this.sortDirection);
|
|
993
|
-
}
|
|
994
|
-
if ('getSortableValue' in columnDef && columnDef.getSortableValue) {
|
|
995
|
-
const aValue = columnDef.getSortableValue(a);
|
|
996
|
-
const bValue = columnDef.getSortableValue(b);
|
|
997
|
-
if (aValue < bValue)
|
|
998
|
-
return this.sortDirection === 'asc' ? -1 : 1;
|
|
999
|
-
if (aValue > bValue)
|
|
1000
|
-
return this.sortDirection === 'asc' ? 1 : -1;
|
|
1001
|
-
return 0;
|
|
1002
|
-
}
|
|
1003
|
-
// Fallback to dataKey if it exists and no other sorter is provided
|
|
1004
|
-
if ('dataKey' in columnDef && columnDef.dataKey) {
|
|
1005
|
-
const key = columnDef.dataKey;
|
|
1006
|
-
const aValue = a[key];
|
|
1007
|
-
const bValue = b[key];
|
|
1008
|
-
if (aValue < bValue)
|
|
1009
|
-
return this.sortDirection === 'asc' ? -1 : 1;
|
|
1010
|
-
if (aValue > bValue)
|
|
1011
|
-
return this.sortDirection === 'asc' ? 1 : -1;
|
|
1012
|
-
return 0;
|
|
1013
|
-
}
|
|
1014
|
-
return 0;
|
|
1015
|
-
});
|
|
1016
|
-
return sortedData;
|
|
1017
|
-
};
|
|
1018
|
-
this.getHeaderProps = (columnDef) => {
|
|
1019
|
-
const { columnId } = columnDef;
|
|
1020
|
-
const isSortable = ('sortComparer' in columnDef && !!columnDef.sortComparer) || ('getSortableValue' in columnDef && !!columnDef.getSortableValue);
|
|
1021
|
-
// A column must have a columnId and a sort function to be sortable
|
|
1022
|
-
if (!isSortable || !columnId) {
|
|
1023
|
-
return {};
|
|
1024
|
-
}
|
|
1025
|
-
const onHeaderClick = (e) => {
|
|
1026
|
-
const target = e.target;
|
|
1027
|
-
console.log('SortPlugin: Header clicked. Target:', target);
|
|
1028
|
-
// If the click is on an interactive element, don't sort
|
|
1029
|
-
if (target.closest('input, button, a, [onclick]')) {
|
|
1030
|
-
console.log('SortPlugin: Interactive element clicked, ignoring sort.');
|
|
1031
|
-
return;
|
|
1032
|
-
}
|
|
1033
|
-
console.log('SortPlugin: Non-interactive element clicked, proceeding with sort.');
|
|
1034
|
-
if (this.sortColumn === columnId) {
|
|
1035
|
-
if (this.sortDirection === 'desc') {
|
|
1036
|
-
this.sortColumn = null;
|
|
1037
|
-
this.sortDirection = null;
|
|
1038
|
-
}
|
|
1039
|
-
else {
|
|
1040
|
-
this.sortDirection = 'desc';
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
else {
|
|
1044
|
-
this.sortColumn = columnId;
|
|
1045
|
-
this.sortDirection = 'asc';
|
|
1046
|
-
}
|
|
1047
|
-
this.api.forceUpdate();
|
|
1048
|
-
};
|
|
1049
|
-
let sortClassName = 'sortable';
|
|
1050
|
-
if (this.sortColumn === columnId) {
|
|
1051
|
-
sortClassName = `sorted-${this.sortDirection}`;
|
|
1052
|
-
}
|
|
1053
|
-
return {
|
|
1054
|
-
onClick: onHeaderClick,
|
|
1055
|
-
className: sortClassName,
|
|
1056
|
-
'aria-sort': (this.sortColumn === columnId ? (this.sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'),
|
|
1057
|
-
};
|
|
1058
|
-
};
|
|
1059
|
-
this.sortColumn = (_a = options === null || options === void 0 ? void 0 : options.initialSortColumn) !== null && _a !== void 0 ? _a : null;
|
|
1060
|
-
this.sortDirection = (_b = options === null || options === void 0 ? void 0 : options.initialSortDirection) !== null && _b !== void 0 ? _b : null;
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
1100
|
exports.FilterPlugin = FilterPlugin;
|
|
1065
1101
|
exports.InfiniteScrollPlugin = InfiniteScrollPlugin;
|
|
1066
1102
|
exports.SelectionPlugin = SelectionPlugin;
|