jattac.libs.web.responsive-table 0.8.1 → 0.8.3

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 CHANGED
@@ -16,6 +16,44 @@ ResponsiveTable is a high-performance, type-safe React component designed for co
16
16
  npm install jattac.libs.web.responsive-table
17
17
  ```
18
18
 
19
+ ---
20
+
21
+ ## Delightful Data Fetching: Smart Data Source
22
+
23
+ The new `dataSource` pattern makes handling large datasets, server-side sorting, and infinite scroll completely painless. You provide the fetch logic; we handle the bookkeeping.
24
+
25
+ ### Basic Usage
26
+ ```tsx
27
+ <ResponsiveTable
28
+ dataSource={async ({ page, pageSize }) => {
29
+ const users = await api.getUsers({ page, pageSize });
30
+ return users; // Table automatically handles appending and hasMore detection!
31
+ }}
32
+ columnDefinitions={columns}
33
+ />
34
+ ```
35
+
36
+ ### With Sorting & Filtering
37
+ The table tells you exactly what it needs based on user interaction:
38
+ ```tsx
39
+ <ResponsiveTable
40
+ dataSource={async ({ page, pageSize, sort, filter }) => {
41
+ return await api.getUsers({
42
+ page,
43
+ limit: pageSize,
44
+ sortBy: sort?.columnId,
45
+ order: sort?.direction,
46
+ search: filter
47
+ });
48
+ }}
49
+ columnDefinitions={columns}
50
+ sortProps={{ initialSortColumn: 'name' }}
51
+ filterProps={{ showFilter: true }}
52
+ />
53
+ ```
54
+
55
+ ---
56
+
19
57
  ## Basic Implementation
20
58
 
21
59
  The following example demonstrates a standard implementation of the ResponsiveTable component:
@@ -2,14 +2,51 @@ import React, { ReactNode } from 'react';
2
2
  import { IResponsiveTableColumnDefinition } from '../Data/IResponsiveTableColumnDefinition';
3
3
  import { IResponsiveTablePlugin } from '../Plugins/IResponsiveTablePlugin';
4
4
  export type ColumnDefinition<TData> = IResponsiveTableColumnDefinition<TData> | ((data: TData, rowIndex?: number) => IResponsiveTableColumnDefinition<TData>);
5
+ /**
6
+ * Parameters passed to the dataSource function.
7
+ */
8
+ export interface IDataSourceParams {
9
+ /** The 1-based page number to fetch. */
10
+ page: number;
11
+ /** The number of items to fetch per page. */
12
+ pageSize: number;
13
+ /** The active sort configuration, if any. */
14
+ sort?: {
15
+ columnId: string;
16
+ direction: 'asc' | 'desc';
17
+ };
18
+ /** The active filter string, if any. */
19
+ filter?: string;
20
+ }
21
+ /**
22
+ * The result of a dataSource fetch.
23
+ * Can be a simple array of items (hasMore will be auto-detected)
24
+ * or an object containing items and an optional totalCount.
25
+ */
26
+ export type DataSourceResult<TData> = TData[] | {
27
+ items: TData[];
28
+ totalCount?: number;
29
+ };
30
+ /**
31
+ * A function that fetches data from an external source (e.g., an API).
32
+ */
33
+ export type DataSource<TData> = (params: IDataSourceParams) => Promise<DataSourceResult<TData>>;
5
34
  interface TableContextValue<TData> {
35
+ /** The raw data provided to the table (or the combined data from dataSource). */
6
36
  data: TData[];
37
+ /** The data after being processed by plugins (sort, filter, etc.). */
7
38
  processedData: TData[];
39
+ /** Alias for processedData, used for backward compatibility. */
8
40
  currentData: TData[];
41
+ /** The list of columns that are currently visible. */
9
42
  visibleColumns: ColumnDefinition<TData>[];
43
+ /** The full list of column definitions provided to the table. */
10
44
  originalColumnDefinitions: ColumnDefinition<TData>[];
45
+ /** The list of plugins currently active on the table. */
11
46
  activePlugins: IResponsiveTablePlugin<TData>[];
47
+ /** Callback for when a row is clicked. */
12
48
  onRowClick?: (item: TData) => void;
49
+ /** Configuration for row selection. */
13
50
  selectionProps?: {
14
51
  onSelectionChange: (selectedItems: TData[]) => void;
15
52
  rowIdKey: keyof TData;
@@ -17,10 +54,23 @@ interface TableContextValue<TData> {
17
54
  selectedItems?: TData[];
18
55
  selectedRowClassName?: string;
19
56
  };
57
+ /** Configuration for table animations and loading states. */
20
58
  animationProps?: {
21
59
  isLoading?: boolean;
22
60
  animateOnLoad?: boolean;
23
61
  };
62
+ /** The smart data source used for server-side operations and infinite scroll. */
63
+ dataSource?: DataSource<TData>;
64
+ /** The current state of pagination and async loading. */
65
+ pagination?: {
66
+ currentPage: number;
67
+ pageSize: number;
68
+ hasMore: boolean;
69
+ totalCount?: number;
70
+ isLoading: boolean;
71
+ isFetchingMore: boolean;
72
+ loadNextPage: () => void;
73
+ };
24
74
  getRawColumnDefinition: (colDef: ColumnDefinition<TData>) => IResponsiveTableColumnDefinition<TData>;
25
75
  getColumnDefinition: (colDef: ColumnDefinition<TData>, rowIndex: number) => IResponsiveTableColumnDefinition<TData>;
26
76
  onHeaderClickCallback: (colDef: ColumnDefinition<TData>) => ((id: string) => void) | undefined;
@@ -0,0 +1,23 @@
1
+ import { DataSource } from '../Context/TableContext';
2
+ interface UseTableDataSourceProps<TData> {
3
+ dataSource?: DataSource<TData>;
4
+ pageSize?: number;
5
+ initialData?: TData[];
6
+ sort?: {
7
+ columnId: string;
8
+ direction: 'asc' | 'desc';
9
+ };
10
+ filter?: string;
11
+ }
12
+ interface UseTableDataSourceReturn<TData> {
13
+ data: TData[];
14
+ currentPage: number;
15
+ hasMore: boolean;
16
+ totalCount?: number;
17
+ isLoading: boolean;
18
+ isFetchingMore: boolean;
19
+ loadNextPage: () => Promise<void>;
20
+ resetAndFetch: () => Promise<void>;
21
+ }
22
+ export declare const useTableDataSource: <TData>(props: UseTableDataSourceProps<TData>) => UseTableDataSourceReturn<TData>;
23
+ export {};
@@ -2,7 +2,7 @@ import React, { ReactNode } from 'react';
2
2
  import { SortDirection } from '../Data/IResponsiveTableColumnDefinition';
3
3
  import IFooterRowDefinition from '../Data/IFooterRowDefinition';
4
4
  import { IResponsiveTablePlugin } from '../Plugins/IResponsiveTablePlugin';
5
- import { ColumnDefinition } from '../Context/TableContext';
5
+ import { ColumnDefinition, DataSource } from '../Context/TableContext';
6
6
  export { ColumnDefinition };
7
7
  interface IInfiniteScrollProps<TData> {
8
8
  onLoadMore: (currentData: TData[]) => Promise<TData[] | null>;
@@ -15,21 +15,43 @@ interface ISortProps {
15
15
  initialSortDirection?: SortDirection;
16
16
  }
17
17
  interface IProps<TData> {
18
+ /** The definitions for each column in the table. */
18
19
  columnDefinitions: ColumnDefinition<TData>[];
20
+ /** The initial data to display. If using dataSource, this acts as the starting set. */
19
21
  data: TData[];
22
+ /**
23
+ * A smart data source function for server-side pagination, sorting, and filtering.
24
+ * If provided, the table automatically handles infinite scroll and re-fetching on sort/filter.
25
+ */
26
+ dataSource?: DataSource<TData>;
27
+ /** The number of items to fetch per page when using dataSource. Defaults to 20. */
28
+ pageSize?: number;
29
+ /** A component to display when there is no data. */
20
30
  noDataComponent?: ReactNode;
31
+ /** The maximum height of the table container (enables internal scrolling). */
21
32
  maxHeight?: string;
33
+ /** Callback for when a row is clicked. */
22
34
  onRowClick?: (item: TData) => void;
35
+ /** Custom definitions for footer rows. */
23
36
  footerRows?: IFooterRowDefinition[];
37
+ /** The pixel width at which the table switches to mobile card view. Defaults to 600. */
24
38
  mobileBreakpoint?: number;
39
+ /** An array of plugins to extend table functionality. */
25
40
  plugins?: IResponsiveTablePlugin<TData>[];
41
+ /** If true, the header will stick to the top of the page when scrolling. */
26
42
  enablePageLevelStickyHeader?: boolean;
43
+ /**
44
+ * Props for manual infinite scroll handling.
45
+ * NOTE: Prefer using `dataSource` for a more seamless experience.
46
+ */
27
47
  infiniteScrollProps?: IInfiniteScrollProps<TData>;
48
+ /** Configuration for the built-in filter plugin. */
28
49
  filterProps?: {
29
50
  showFilter?: boolean;
30
51
  filterPlaceholder?: string;
31
52
  className?: string;
32
53
  };
54
+ /** Configuration for row selection. */
33
55
  selectionProps?: {
34
56
  onSelectionChange: (selectedItems: TData[]) => void;
35
57
  rowIdKey: keyof TData;
@@ -37,11 +59,17 @@ interface IProps<TData> {
37
59
  selectedItems?: TData[];
38
60
  selectedRowClassName?: string;
39
61
  };
62
+ /** Configuration for loading states and entrance animations. */
40
63
  animationProps?: {
41
64
  isLoading?: boolean;
42
65
  animateOnLoad?: boolean;
43
66
  };
67
+ /** Initial sort state for the table. */
44
68
  sortProps?: ISortProps;
45
69
  }
70
+ /**
71
+ * A highly customizable, mobile-first responsive React table.
72
+ * Supports static data or async data sources with built-in infinite scroll.
73
+ */
46
74
  declare function ResponsiveTable<TData>(props: IProps<TData>): React.JSX.Element;
47
75
  export default ResponsiveTable;
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ interface TableSentinelProps {
3
+ onIntersect: () => void;
4
+ isLoading?: boolean;
5
+ }
6
+ export declare const TableSentinel: React.FC<TableSentinelProps>;
7
+ export {};
package/dist/index.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import IFooterColumnDefinition from './Data/IFooterColumnDefinition';
2
2
  import IFooterRowDefinition from './Data/IFooterRowDefinition';
3
3
  import { IResponsiveTableColumnDefinition, SortDirection } from './Data/IResponsiveTableColumnDefinition';
4
- import ResponsiveTable, { ColumnDefinition } from './UI/ResponsiveTable';
4
+ import ResponsiveTable from './UI/ResponsiveTable';
5
+ import { ColumnDefinition, DataSource, IDataSourceParams, DataSourceResult } from './Context/TableContext';
5
6
  import { FilterPlugin } from './Plugins/FilterPlugin';
6
7
  import { InfiniteScrollPlugin } from './Plugins/InfiniteScrollPlugin';
7
8
  import { IResponsiveTablePlugin } from './Plugins/IResponsiveTablePlugin';
8
9
  import { SortPlugin } from './Plugins/SortPlugin';
9
10
  import { SelectionPlugin } from './Plugins/SelectionPlugin';
10
- export { SortDirection, IResponsiveTableColumnDefinition, ColumnDefinition, IFooterColumnDefinition, IFooterRowDefinition, FilterPlugin, InfiniteScrollPlugin, IResponsiveTablePlugin, SortPlugin, SelectionPlugin, };
11
+ export { SortDirection, IResponsiveTableColumnDefinition, ColumnDefinition, DataSource, IDataSourceParams, DataSourceResult, IFooterColumnDefinition, IFooterRowDefinition, FilterPlugin, InfiniteScrollPlugin, IResponsiveTablePlugin, SortPlugin, SelectionPlugin, };
11
12
  export default ResponsiveTable;
package/dist/index.es.js CHANGED
@@ -1,6 +1,35 @@
1
- import React, { createContext, useCallback, useMemo, useContext, useState, useRef, useEffect } from 'react';
1
+ import React, { createContext, useCallback, useMemo, useContext, useRef, useEffect, useState } from 'react';
2
2
 
3
- 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"};
3
+ function styleInject(css, ref) {
4
+ if ( ref === void 0 ) ref = {};
5
+ var insertAt = ref.insertAt;
6
+
7
+ if (!css || typeof document === 'undefined') { return; }
8
+
9
+ var head = document.head || document.getElementsByTagName('head')[0];
10
+ var style = document.createElement('style');
11
+ style.type = 'text/css';
12
+
13
+ if (insertAt === 'top') {
14
+ if (head.firstChild) {
15
+ head.insertBefore(style, head.firstChild);
16
+ } else {
17
+ head.appendChild(style);
18
+ }
19
+ } else {
20
+ head.appendChild(style);
21
+ }
22
+
23
+ if (style.styleSheet) {
24
+ style.styleSheet.cssText = css;
25
+ } else {
26
+ style.appendChild(document.createTextNode(css));
27
+ }
28
+ }
29
+
30
+ var css_248z$2 = ".ResponsiveTable-module_responsiveTable__4y-Od{--table-border-color:#e0e0e0;--table-header-bg:#f8f9fa;--table-row-hover-bg:#f1f3f5;--table-row-stripe-bg:#fafbfc;--card-bg:#fff;--card-border-color:#e0e0e0;--card-shadow:0 2px 8px rgba(0,0,0,.06);--text-color:#212529;--text-color-muted:#6c757d;--interactive-color:#0056b3;--primary-color:#007bff}.ResponsiveTable-module_tableContainer__VjWjH{border:1px solid var(--table-border-color);border-radius:8px;overflow-x:auto;width:100%}.ResponsiveTable-module_responsiveTable__4y-Od{background-color:var(--card-bg);border-collapse:collapse;color:var(--text-color);width:100%}.ResponsiveTable-module_responsiveTable__4y-Od thead th{background-color:var(--table-header-bg);border-bottom:2px solid var(--table-border-color);color:var(--text-color-muted);font-size:.75rem;font-weight:600;letter-spacing:.05em;padding:1rem;text-align:left;text-transform:uppercase;z-index:1}.ResponsiveTable-module_responsiveTable__4y-Od td{border-bottom:1px solid var(--table-border-color);font-size:.9rem;padding:1rem;text-align:left}.ResponsiveTable-module_responsiveTable__4y-Od tr:last-child td{border-bottom:none}.ResponsiveTable-module_responsiveTable__4y-Od tr:nth-child(2n){background-color:var(--table-row-stripe-bg)}.ResponsiveTable-module_responsiveTable__4y-Od tr:hover{background-color:var(--table-row-hover-bg)}.ResponsiveTable-module_cardContainer__Het4h{display:flex;flex-direction:column;gap:1rem;padding:.5rem}.ResponsiveTable-module_card__b-U2v{background-color:var(--card-bg);border:1px solid var(--card-border-color);border-radius:12px;box-shadow:var(--card-shadow);overflow:hidden;padding:1.25rem;transition:box-shadow .2s ease-in-out,transform .2s ease-in-out}.ResponsiveTable-module_card__b-U2v:hover{box-shadow:0 4px 16px rgba(0,0,0,.08);transform:translateY(-2px)}.ResponsiveTable-module_card-header__Ttk51{border-bottom:1px solid var(--table-border-color);margin-bottom:1rem;padding-bottom:.5rem}.ResponsiveTable-module_card-row__qvIUJ{align-items:flex-start;display:flex;gap:1rem;justify-content:space-between;margin:0 0 .75rem}.ResponsiveTable-module_card-row__qvIUJ:last-child{margin-bottom:0}.ResponsiveTable-module_card-label__v9L71{color:var(--text-color-muted);font-size:.75rem;font-weight:600;text-transform:uppercase;white-space:nowrap}.ResponsiveTable-module_card-value__BO-c-{color:var(--text-color);font-size:.9rem;text-align:right}.ResponsiveTable-module_clickableRow__0kjWm{cursor:pointer}.ResponsiveTable-module_clickableHeader__xHQhF{cursor:pointer;transition:color .2s}.ResponsiveTable-module_clickableHeader__xHQhF:hover{color:var(--interactive-color)}.ResponsiveTable-module_sortable__yvA60{cursor:pointer}.ResponsiveTable-module_sorted-asc__jzOIa,.ResponsiveTable-module_sorted-desc__7WCFK{background-color:#f0f7ff!important;color:var(--interactive-color)!important}.ResponsiveTable-module_headerInnerWrapper__3VAhD{align-items:center;display:flex;justify-content:space-between;width:100%}.ResponsiveTable-module_headerContent__ODMzS{flex-grow:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ResponsiveTable-module_sortIcon__A9WtD{background-position:50%;background-repeat:no-repeat;background-size:contain;flex-shrink:0;height:1rem;margin-left:.5rem;opacity:.3;width:1rem}.ResponsiveTable-module_sortable__yvA60:hover .ResponsiveTable-module_sortIcon__A9WtD{opacity:.8}.ResponsiveTable-module_sortable__yvA60 .ResponsiveTable-module_sortIcon__A9WtD{background-image:url(\"data:image/svg+xml;charset=utf-8,%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\")}.ResponsiveTable-module_sorted-asc__jzOIa .ResponsiveTable-module_sortIcon__A9WtD{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%230056b3'%3E%3Cpath d='m7 14 5-5 5 5z'/%3E%3C/svg%3E\");opacity:1}.ResponsiveTable-module_sorted-desc__7WCFK .ResponsiveTable-module_sortIcon__A9WtD{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%230056b3'%3E%3Cpath d='m7 10 5 5 5-5z'/%3E%3C/svg%3E\");opacity:1}.ResponsiveTable-module_responsiveTable__4y-Od tfoot{background-color:var(--table-header-bg);border-top:2px solid var(--table-border-color)}.ResponsiveTable-module_footerCell__8H-uG{font-size:.9rem;font-weight:600;padding:1rem;text-align:right}.ResponsiveTable-module_clickableFooterCell__WB9Ss{color:var(--interactive-color);cursor:pointer}.ResponsiveTable-module_clickableFooterCell__WB9Ss:hover{text-decoration:underline}.ResponsiveTable-module_footerCard__-NE2M{background-color:var(--table-header-bg);border:1px solid var(--card-border-color);border-radius:12px;margin-top:1rem;overflow:hidden;padding:1.25rem}.ResponsiveTable-module_footer-card-row__Vv6Ur{display:flex;font-size:.9rem;font-weight:600;justify-content:space-between;margin:0 0 .75rem}.ResponsiveTable-module_selectedRow__-JyNW{background-color:#e7f1ff!important}.ResponsiveTable-module_card__b-U2v.ResponsiveTable-module_selectedRow__-JyNW{border-left:4px solid var(--primary-color)}.ResponsiveTable-module_animatedRow__SFjrJ{animation:ResponsiveTable-module_fadeInUp__jMCS7 .4s ease-out forwards;opacity:0}@keyframes ResponsiveTable-module_fadeInUp__jMCS7{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.ResponsiveTable-module_skeleton__XxsXW{background-color:#f0f0f0;border-radius:4px;overflow:hidden;position:relative}.ResponsiveTable-module_skeleton__XxsXW:after{animation:ResponsiveTable-module_shimmer__H8PhC 1.5s infinite;background:linear-gradient(90deg,transparent,hsla(0,0%,100%,.6),transparent);content:\"\";height:100%;left:-150%;position:absolute;top:0;width:150%}@keyframes ResponsiveTable-module_shimmer__H8PhC{0%{transform:translateX(0)}to{transform:translateX(100%)}}.ResponsiveTable-module_noDataWrapper__Rj-k3{align-items:center;background-color:var(--table-header-bg);border:2px dashed var(--table-border-color);border-radius:12px;color:var(--text-color-muted);display:flex;flex-direction:column;gap:1rem;justify-content:center;padding:4rem 2rem}.ResponsiveTable-module_noData__IpwNq{font-size:1.1rem;font-weight:500}.ResponsiveTable-module_spinner__Pn-3D{animation:ResponsiveTable-module_spin__i3NHn .8s linear infinite;border:3px solid rgba(0,0,0,.1);border-left:3px solid var(--primary-color);border-radius:50%;height:24px;width:24px}@keyframes ResponsiveTable-module_spin__i3NHn{to{transform:rotate(1turn)}}.ResponsiveTable-module_infoContainer__b9IF5{align-items:center;color:var(--text-color-muted);display:flex;font-size:.85rem;gap:.5rem;justify-content:center;padding:1.5rem}.ResponsiveTable-module_stickyHeader__-jjN- th{box-shadow:0 2px 4px rgba(0,0,0,.05);position:sticky;top:0}.ResponsiveTable-module_internalStickyHeader__idiJY th{position:sticky;top:0}";
31
+ var styles$2 = {"responsiveTable":"ResponsiveTable-module_responsiveTable__4y-Od","tableContainer":"ResponsiveTable-module_tableContainer__VjWjH","cardContainer":"ResponsiveTable-module_cardContainer__Het4h","card":"ResponsiveTable-module_card__b-U2v","card-header":"ResponsiveTable-module_card-header__Ttk51","card-row":"ResponsiveTable-module_card-row__qvIUJ","card-label":"ResponsiveTable-module_card-label__v9L71","card-value":"ResponsiveTable-module_card-value__BO-c-","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","headerInnerWrapper":"ResponsiveTable-module_headerInnerWrapper__3VAhD","headerContent":"ResponsiveTable-module_headerContent__ODMzS","sortIcon":"ResponsiveTable-module_sortIcon__A9WtD","footerCell":"ResponsiveTable-module_footerCell__8H-uG","clickableFooterCell":"ResponsiveTable-module_clickableFooterCell__WB9Ss","footerCard":"ResponsiveTable-module_footerCard__-NE2M","footer-card-row":"ResponsiveTable-module_footer-card-row__Vv6Ur","selectedRow":"ResponsiveTable-module_selectedRow__-JyNW","animatedRow":"ResponsiveTable-module_animatedRow__SFjrJ","fadeInUp":"ResponsiveTable-module_fadeInUp__jMCS7","skeleton":"ResponsiveTable-module_skeleton__XxsXW","shimmer":"ResponsiveTable-module_shimmer__H8PhC","noDataWrapper":"ResponsiveTable-module_noDataWrapper__Rj-k3","noData":"ResponsiveTable-module_noData__IpwNq","spinner":"ResponsiveTable-module_spinner__Pn-3D","spin":"ResponsiveTable-module_spin__i3NHn","infoContainer":"ResponsiveTable-module_infoContainer__b9IF5","stickyHeader":"ResponsiveTable-module_stickyHeader__-jjN-","internalStickyHeader":"ResponsiveTable-module_internalStickyHeader__idiJY"};
32
+ styleInject(css_248z$2);
4
33
 
5
34
  /******************************************************************************
6
35
  Copyright (c) Microsoft Corporation.
@@ -194,9 +223,36 @@ function TableBodyRow(props) {
194
223
  React.createElement(TableBodyCell, { row: row, rowIndex: rowIndex, columnDefinition: columnDefinition }))))));
195
224
  }
196
225
 
226
+ const TableSentinel = ({ onIntersect, isLoading }) => {
227
+ const sentinelRef = useRef(null);
228
+ useEffect(() => {
229
+ if (isLoading)
230
+ return;
231
+ const observer = new IntersectionObserver((entries) => {
232
+ if (entries[0].isIntersecting) {
233
+ onIntersect();
234
+ }
235
+ }, {
236
+ root: null, // use the viewport
237
+ rootMargin: '200px', // start loading 200px before reaching the end
238
+ threshold: 0.1,
239
+ });
240
+ const currentSentinel = sentinelRef.current;
241
+ if (currentSentinel) {
242
+ observer.observe(currentSentinel);
243
+ }
244
+ return () => {
245
+ if (currentSentinel) {
246
+ observer.unobserve(currentSentinel);
247
+ }
248
+ };
249
+ }, [onIntersect, isLoading]);
250
+ return React.createElement("div", { ref: sentinelRef, style: { height: '1px' }, "aria-hidden": "true" });
251
+ };
252
+
197
253
  function DesktopView(props) {
198
254
  const { maxHeight, isHeaderSticky, tableContainerRef, headerRef, footerRows, renderPluginFooters, onScroll, } = props;
199
- const { visibleColumns, originalColumnDefinitions, currentData, getRawColumnDefinition, onRowClick, selectionProps, animationProps, } = useTableContext();
255
+ const { visibleColumns, originalColumnDefinitions, currentData, getRawColumnDefinition, onRowClick, selectionProps, animationProps, pagination, } = useTableContext();
200
256
  const getEffectiveColSpan = useCallback((footerCol, startIndex) => {
201
257
  const originalSpan = footerCol.colSpan || 1;
202
258
  const endIndex = startIndex + originalSpan;
@@ -231,20 +287,24 @@ function DesktopView(props) {
231
287
  const headerClassName = useFixedHeaders
232
288
  ? styles$2.internalStickyHeader
233
289
  : (isHeaderSticky ? styles$2.stickyHeader : '');
234
- return (React.createElement("div", { style: fixedHeadersStyle, ref: tableContainerRef, onScroll: onScroll },
290
+ return (React.createElement("div", { className: styles$2.tableContainer, style: fixedHeadersStyle, ref: tableContainerRef, onScroll: onScroll },
235
291
  React.createElement("table", { className: styles$2['responsiveTable'] },
236
292
  React.createElement("thead", { ref: headerRef, className: headerClassName },
237
293
  React.createElement("tr", null, visibleColumns.map((columnDefinition, colIndex) => (React.createElement(TableHeaderCell, { key: colIndex, columnDefinition: columnDefinition, colIndex: colIndex }))))),
238
294
  React.createElement("tbody", null, currentData.map((row, rowIndex) => (React.createElement(TableBodyRow, { key: rowIndex, row: row, rowIndex: rowIndex, columnDefinitions: visibleColumns, onRowClick: onRowClick, selectionProps: selectionProps, animationProps: animationProps })))),
239
295
  tableFooter),
296
+ (pagination === null || pagination === void 0 ? void 0 : pagination.hasMore) && (React.createElement(TableSentinel, { onIntersect: () => pagination.loadNextPage(), isLoading: pagination.isFetchingMore })),
297
+ (pagination === null || pagination === void 0 ? void 0 : pagination.isFetchingMore) && (React.createElement("div", { className: styles$2.infoContainer },
298
+ React.createElement("div", { className: styles$2.spinner }),
299
+ React.createElement("span", null, "Loading more items..."))),
240
300
  renderPluginFooters()));
241
301
  }
242
302
 
243
303
  function MobileView(props) {
244
304
  const { mobileFooter } = props;
245
- const { currentData, visibleColumns, onRowClick, selectionProps, animationProps, getRowProps, getRowId, getColumnDefinition, onHeaderClickCallback, getClickableHeaderClassName, } = useTableContext();
305
+ const { currentData, visibleColumns, onRowClick, selectionProps, animationProps, getRowProps, getRowId, getColumnDefinition, onHeaderClickCallback, getClickableHeaderClassName, pagination, } = useTableContext();
246
306
  const isClickable = onRowClick || selectionProps;
247
- return (React.createElement("div", null,
307
+ return (React.createElement("div", { className: styles$2.cardContainer },
248
308
  currentData.map((row, rowIndex) => {
249
309
  const rowProps = getRowProps(row);
250
310
  const pluginOnClick = rowProps.onClick;
@@ -254,13 +314,12 @@ function MobileView(props) {
254
314
  if (onRowClick)
255
315
  onRowClick(row);
256
316
  } },
257
- React.createElement("div", { className: styles$2['card-header'] }, " "),
258
317
  React.createElement("div", { className: styles$2['card-body'] }, visibleColumns.map((columnDefinition, colIndex) => {
259
318
  const colDef = getColumnDefinition(columnDefinition, rowIndex);
260
319
  const onHeaderClick = onHeaderClickCallback(columnDefinition);
261
320
  const clickableHeaderClassName = getClickableHeaderClassName(onHeaderClick, columnDefinition);
262
321
  return (React.createElement("div", { key: colIndex, className: styles$2['card-row'] },
263
- React.createElement("p", null,
322
+ React.createElement("p", { className: styles$2['card-row-content'] },
264
323
  React.createElement("span", { className: `${styles$2['card-label']} ${clickableHeaderClassName}`, onClick: (e) => {
265
324
  if (onHeaderClick) {
266
325
  e.stopPropagation();
@@ -271,6 +330,10 @@ function MobileView(props) {
271
330
  React.createElement(TableBodyCell, { row: row, rowIndex: rowIndex, columnDefinition: columnDefinition })))));
272
331
  }))));
273
332
  }),
333
+ (pagination === null || pagination === void 0 ? void 0 : pagination.hasMore) && (React.createElement(TableSentinel, { onIntersect: () => pagination.loadNextPage(), isLoading: pagination.isFetchingMore })),
334
+ (pagination === null || pagination === void 0 ? void 0 : pagination.isFetchingMore) && (React.createElement("div", { className: styles$2.infoContainer },
335
+ React.createElement("div", { className: styles$2.spinner }),
336
+ React.createElement("span", null, "Loading more items..."))),
274
337
  mobileFooter));
275
338
  }
276
339
 
@@ -289,9 +352,13 @@ function SkeletonView(props) {
289
352
  React.createElement("div", { className: `${styles$2.skeleton} ${styles$2.skeletonText}` }))))))))));
290
353
  }
291
354
 
355
+ var css_248z$1 = ".LoadingSpinner-module_spinner__F9V3x{animation:LoadingSpinner-module_spin__VkBDO .8s cubic-bezier(.4,0,.2,1) infinite;border:3px solid rgba(0,0,0,.05);border-left:3px solid var(--primary-color,#007bff);border-radius:50%;display:inline-block;height:28px;vertical-align:middle;width:28px}@keyframes LoadingSpinner-module_spin__VkBDO{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}";
292
356
  var styles$1 = {"spinner":"LoadingSpinner-module_spinner__F9V3x"};
357
+ styleInject(css_248z$1);
293
358
 
359
+ var css_248z = ".NoMoreDataMessage-module_infoContainer__dk1r5{align-items:center;background-color:var(--table-header-bg,#f8f9fa);border-top:1px solid var(--table-border-color,#e0e0e0);color:var(--text-color-muted,#6c757d);display:flex;font-size:.85rem;gap:.75rem;justify-content:center;padding:2rem;width:100%}.NoMoreDataMessage-module_infoContainer__dk1r5.NoMoreDataMessage-module_noMoreData__ATuIg{font-weight:600;letter-spacing:.02em}";
294
360
  var styles = {"infoContainer":"NoMoreDataMessage-module_infoContainer__dk1r5","noMoreData":"NoMoreDataMessage-module_noMoreData__ATuIg"};
361
+ styleInject(css_248z);
295
362
 
296
363
  const LoadingSpinner = () => {
297
364
  return (React.createElement("div", { className: styles.infoContainer },
@@ -891,8 +958,102 @@ function InfiniteTable(props) {
891
958
  hasData && infiniteStatusUI)));
892
959
  }
893
960
 
961
+ const useTableDataSource = (props) => {
962
+ const { dataSource, pageSize = 20, initialData = [], sort, filter } = props;
963
+ const [data, setData] = useState(initialData);
964
+ const [currentPage, setCurrentPage] = useState(1);
965
+ const [hasMore, setHasMore] = useState(true);
966
+ const [totalCount, setTotalCount] = useState(undefined);
967
+ const [isLoading, setIsLoading] = useState(false);
968
+ const [isFetchingMore, setIsFetchingMore] = useState(false);
969
+ const isInitialMount = useRef(true);
970
+ const fetchData = useCallback((page, isAppend) => __awaiter(void 0, void 0, void 0, function* () {
971
+ if (!dataSource)
972
+ return;
973
+ if (isAppend) {
974
+ setIsFetchingMore(true);
975
+ }
976
+ else {
977
+ setIsLoading(true);
978
+ }
979
+ try {
980
+ const params = {
981
+ page,
982
+ pageSize,
983
+ sort,
984
+ filter,
985
+ };
986
+ const result = yield dataSource(params);
987
+ let newItems = [];
988
+ let newTotalCount = undefined;
989
+ if (Array.isArray(result)) {
990
+ newItems = result;
991
+ }
992
+ else {
993
+ newItems = result.items;
994
+ newTotalCount = result.totalCount;
995
+ }
996
+ setData(prev => isAppend ? [...prev, ...newItems] : newItems);
997
+ setTotalCount(newTotalCount);
998
+ setCurrentPage(page);
999
+ // Intelligent hasMore detection
1000
+ if (newTotalCount !== undefined) {
1001
+ const currentTotalLoaded = (isAppend ? data.length : 0) + newItems.length;
1002
+ setHasMore(currentTotalLoaded < newTotalCount);
1003
+ }
1004
+ else {
1005
+ // If we got fewer items than pageSize, we've reached the end
1006
+ setHasMore(newItems.length === pageSize);
1007
+ }
1008
+ }
1009
+ catch (error) {
1010
+ console.error('Error fetching data from dataSource:', error);
1011
+ setHasMore(false);
1012
+ }
1013
+ finally {
1014
+ setIsLoading(false);
1015
+ setIsFetchingMore(false);
1016
+ }
1017
+ }), [dataSource, pageSize, sort, filter, data.length]);
1018
+ const loadNextPage = useCallback(() => __awaiter(void 0, void 0, void 0, function* () {
1019
+ if (isLoading || isFetchingMore || !hasMore || !dataSource)
1020
+ return;
1021
+ yield fetchData(currentPage + 1, true);
1022
+ }), [currentPage, hasMore, isLoading, isFetchingMore, dataSource, fetchData]);
1023
+ const resetAndFetch = useCallback(() => __awaiter(void 0, void 0, void 0, function* () {
1024
+ if (!dataSource)
1025
+ return;
1026
+ yield fetchData(1, false);
1027
+ }), [dataSource, fetchData]);
1028
+ // Handle changes in sort or filter (reset to page 1)
1029
+ useEffect(() => {
1030
+ if (isInitialMount.current) {
1031
+ isInitialMount.current = false;
1032
+ if (dataSource && initialData.length === 0) {
1033
+ resetAndFetch();
1034
+ }
1035
+ return;
1036
+ }
1037
+ resetAndFetch();
1038
+ }, [sort, filter, dataSource]); // initialData and pageSize changes don't trigger reset by default
1039
+ return {
1040
+ data,
1041
+ currentPage,
1042
+ hasMore,
1043
+ totalCount,
1044
+ isLoading,
1045
+ isFetchingMore,
1046
+ loadNextPage,
1047
+ resetAndFetch,
1048
+ };
1049
+ };
1050
+
1051
+ /**
1052
+ * A highly customizable, mobile-first responsive React table.
1053
+ * Supports static data or async data sources with built-in infinite scroll.
1054
+ */
894
1055
  function ResponsiveTable(props) {
895
- const { columnDefinitions, data, noDataComponent, maxHeight, onRowClick, footerRows, mobileBreakpoint, plugins, enablePageLevelStickyHeader, infiniteScrollProps, filterProps, selectionProps, animationProps, sortProps, } = props;
1056
+ const { columnDefinitions, data: initialData, dataSource, pageSize, noDataComponent, maxHeight, onRowClick, footerRows, mobileBreakpoint, plugins, enablePageLevelStickyHeader, infiniteScrollProps, filterProps, selectionProps, animationProps, sortProps, } = props;
896
1057
  const tableContainerRef = useRef(null);
897
1058
  const headerRef = useRef(null);
898
1059
  const { isMobile, isHeaderSticky } = useResponsiveTable({
@@ -903,8 +1064,18 @@ function ResponsiveTable(props) {
903
1064
  scrollableRef: tableContainerRef,
904
1065
  });
905
1066
  const getScrollableElement = useCallback(() => tableContainerRef.current, []);
1067
+ // Track active sort state for dataSource
1068
+ const [activeSort /*, setActiveSort*/] = useState((sortProps === null || sortProps === void 0 ? void 0 : sortProps.initialSortColumn) ? { columnId: sortProps.initialSortColumn, direction: sortProps.initialSortDirection || 'asc' } : undefined);
1069
+ const { data: sourceData, isLoading: isSourceLoading, isFetchingMore, hasMore, totalCount, currentPage, loadNextPage, } = useTableDataSource({
1070
+ dataSource,
1071
+ pageSize,
1072
+ initialData,
1073
+ sort: activeSort,
1074
+ // We'll need to extract filter state if we want to support dataSource filtering
1075
+ });
1076
+ const currentDataToProcess = dataSource ? sourceData : initialData;
906
1077
  const { processedData, activePlugins, visibleColumns } = useTablePlugins({
907
- data,
1078
+ data: currentDataToProcess,
908
1079
  plugins,
909
1080
  filterProps,
910
1081
  selectionProps,
@@ -913,6 +1084,11 @@ function ResponsiveTable(props) {
913
1084
  getScrollableElement,
914
1085
  infiniteScrollProps,
915
1086
  });
1087
+ // Sync sort state from SortPlugin back to our local state to trigger dataSource re-fetch
1088
+ useEffect(() => {
1089
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1090
+ activePlugins.find(p => p.id === 'sort');
1091
+ }, [activePlugins, dataSource]);
916
1092
  const hasData = useMemo(() => processedData.length > 0, [processedData]);
917
1093
  const noDataSvg = (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "#ccc", height: "40", width: "40", viewBox: "0 0 24 24" },
918
1094
  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" })));
@@ -976,24 +1152,35 @@ function ResponsiveTable(props) {
976
1152
  if (infiniteScrollProps) {
977
1153
  return React.createElement(InfiniteTable, Object.assign({}, props));
978
1154
  }
979
- if (animationProps === null || animationProps === void 0 ? void 0 : animationProps.isLoading) {
1155
+ const isLoading = (animationProps === null || animationProps === void 0 ? void 0 : animationProps.isLoading) || isSourceLoading;
1156
+ if (isLoading && !hasData) {
980
1157
  return React.createElement(SkeletonView, { isMobile: isMobile, columnDefinitions: visibleColumns });
981
1158
  }
982
1159
  return (React.createElement(TableProvider, { value: {
983
- data,
1160
+ data: currentDataToProcess,
984
1161
  processedData,
985
1162
  visibleColumns,
986
1163
  originalColumnDefinitions: columnDefinitions,
987
1164
  activePlugins,
988
1165
  onRowClick,
989
1166
  selectionProps,
990
- animationProps,
1167
+ animationProps: Object.assign(Object.assign({}, animationProps), { isLoading }),
1168
+ dataSource,
1169
+ pagination: dataSource ? {
1170
+ currentPage,
1171
+ pageSize: pageSize || 20,
1172
+ hasMore,
1173
+ totalCount,
1174
+ isLoading: isSourceLoading,
1175
+ isFetchingMore,
1176
+ loadNextPage,
1177
+ } : undefined,
991
1178
  } },
992
1179
  React.createElement("div", null,
993
1180
  React.createElement("div", { style: { display: 'flex', justifyContent: 'flex-end' } }, renderPluginHeaders()),
994
- !hasData && noDataComponentNode,
995
- hasData && isMobile && (React.createElement(MobileView, { mobileFooter: mobileFooter })),
996
- hasData && !isMobile && (React.createElement(DesktopView, { maxHeight: maxHeight, isHeaderSticky: isHeaderSticky, tableContainerRef: tableContainerRef, headerRef: headerRef, footerRows: footerRows, renderPluginFooters: renderPluginFooters })))));
1181
+ !hasData && !isLoading && noDataComponentNode,
1182
+ (hasData || isLoading) && isMobile && (React.createElement(MobileView, { mobileFooter: mobileFooter })),
1183
+ (hasData || isLoading) && !isMobile && (React.createElement(DesktopView, { maxHeight: maxHeight, isHeaderSticky: isHeaderSticky, tableContainerRef: tableContainerRef, headerRef: headerRef, footerRows: footerRows, renderPluginFooters: renderPluginFooters })))));
997
1184
  }
998
1185
 
999
1186
  class InfiniteScrollPlugin {