@pallasoft/ps-table-components 1.2.0 → 1.4.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.
Files changed (35) hide show
  1. package/dist/components/ps-data-table.d.ts +12 -2
  2. package/dist/components/ps-data-table.js +97 -26
  3. package/dist/components/ps-table-bulk-bar.d.ts +7 -0
  4. package/dist/components/ps-table-bulk-bar.js +7 -0
  5. package/dist/components/ps-table-column-picker.d.ts +10 -0
  6. package/dist/components/ps-table-column-picker.js +20 -0
  7. package/dist/components/ps-table-filter-field.d.ts +11 -0
  8. package/dist/components/ps-table-filter-field.js +16 -0
  9. package/dist/components/ps-table-pagination.js +2 -2
  10. package/dist/components/ps-table-sort-header.d.ts +9 -0
  11. package/dist/components/ps-table-sort-header.js +18 -0
  12. package/dist/components/ps-table-toolbar.d.ts +16 -0
  13. package/dist/components/ps-table-toolbar.js +18 -0
  14. package/dist/constants/table-config.d.ts +5 -0
  15. package/dist/constants/table-config.js +8 -0
  16. package/dist/constants/table-filter-kind.d.ts +6 -0
  17. package/dist/constants/table-filter-kind.js +9 -0
  18. package/dist/index.d.ts +8 -0
  19. package/dist/index.js +8 -0
  20. package/dist/interfaces/table-column-filter.d.ts +7 -0
  21. package/dist/interfaces/table-column-filter.js +2 -0
  22. package/dist/interfaces/table-column.d.ts +4 -2
  23. package/dist/interfaces/table-filter-option.d.ts +4 -0
  24. package/dist/interfaces/table-filter-option.js +2 -0
  25. package/dist/interfaces/table-filter-value.d.ts +6 -0
  26. package/dist/interfaces/table-filter-value.js +2 -0
  27. package/dist/interfaces/table-query.d.ts +8 -0
  28. package/dist/interfaces/table-query.js +2 -0
  29. package/dist/interfaces/table-row-action.d.ts +7 -0
  30. package/dist/interfaces/table-row-action.js +2 -0
  31. package/dist/interfaces/table-sort.d.ts +4 -0
  32. package/dist/interfaces/table-sort.js +2 -0
  33. package/dist/services/ps-table-query-service.d.ts +13 -0
  34. package/dist/services/ps-table-query-service.js +97 -0
  35. package/package.json +7 -3
@@ -1,19 +1,29 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import type { PsTableColumn } from '../interfaces/table-column';
3
+ import type { PsTableQuery } from '../interfaces/table-query';
4
+ import type { PsTableRowAction } from '../interfaces/table-row-action';
5
+ import type { PsTableSort } from '../interfaces/table-sort';
3
6
  export interface PsDataTableProps<RowData> {
4
7
  caption?: string;
5
8
  className?: string;
6
9
  columns: PsTableColumn<RowData>[];
10
+ defaultSort?: PsTableSort;
7
11
  density?: 'compact' | 'normal';
8
- /** Ids of rows whose `renderExpandedRow` content is shown beneath them. */
9
12
  expandedRowIds?: string[];
13
+ getBulkActions?: (rows: RowData[]) => PsTableRowAction[];
10
14
  getRowId: (row: RowData) => string;
15
+ getRowLabel?: (row: RowData) => string;
16
+ getSearchText?: (row: RowData) => string;
11
17
  onPageChange?: (page: number) => void;
18
+ onQueryChange?: (query: PsTableQuery) => void;
12
19
  onRowClick?: (row: RowData) => void;
13
20
  page?: number;
14
21
  pageSize?: number;
22
+ query?: PsTableQuery;
15
23
  renderExpandedRow?: (row: RowData) => ReactNode;
16
24
  rows: RowData[];
25
+ searchPlaceholder?: string;
26
+ toolbarActions?: ReactNode;
17
27
  totalRows?: number;
18
28
  }
19
- export declare const PsDataTable: <RowData>({ caption, className, columns, density, expandedRowIds, getRowId, onPageChange, onRowClick, page, pageSize, renderExpandedRow, rows, totalRows, }: PsDataTableProps<RowData>) => import("react").JSX.Element;
29
+ export declare const PsDataTable: <RowData>({ caption, className, columns, defaultSort, density, expandedRowIds, getBulkActions, getRowId, getRowLabel, getSearchText, onPageChange, onQueryChange, onRowClick, page, pageSize, query, renderExpandedRow, rows, searchPlaceholder, toolbarActions, totalRows, }: PsDataTableProps<RowData>) => import("react").JSX.Element;
@@ -4,16 +4,23 @@ exports.PsDataTable = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
6
  const ps_design_system_1 = require("@pallasoft/ps-design-system");
7
+ const ps_input_components_1 = require("@pallasoft/ps-input-components");
7
8
  const ps_motion_components_1 = require("@pallasoft/ps-motion-components");
9
+ const table_config_1 = require("../constants/table-config");
10
+ const ps_table_query_service_1 = require("../services/ps-table-query-service");
11
+ const ps_table_bulk_bar_1 = require("./ps-table-bulk-bar");
12
+ const ps_table_column_picker_1 = require("./ps-table-column-picker");
8
13
  const ps_table_pagination_1 = require("./ps-table-pagination");
14
+ const ps_table_sort_header_1 = require("./ps-table-sort-header");
15
+ const ps_table_toolbar_1 = require("./ps-table-toolbar");
9
16
  const densityClassNames = {
10
17
  compact: {
11
18
  cell: 'px-4 py-3',
12
19
  head: 'px-4 py-3',
13
20
  },
14
21
  normal: {
15
- cell: 'px-4 py-4',
16
- head: 'px-4 py-3.5',
22
+ cell: 'px-4 py-3.5',
23
+ head: 'px-4 py-3',
17
24
  },
18
25
  };
19
26
  const columnStyle = (column) => column.width || column.minWidth ? { minWidth: column.minWidth, width: column.width } : undefined;
@@ -22,44 +29,108 @@ const alignClassNames = {
22
29
  left: 'text-left',
23
30
  right: 'text-right tabular-nums',
24
31
  };
25
- const PsDataTable = ({ caption, className, columns, density = 'normal', expandedRowIds, getRowId, onPageChange, onRowClick, page, pageSize, renderExpandedRow, rows, totalRows, }) => {
32
+ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'normal', expandedRowIds, getBulkActions, getRowId, getRowLabel, getSearchText, onPageChange, onQueryChange, onRowClick, page, pageSize, query, renderExpandedRow, rows, searchPlaceholder = table_config_1.PsTableConfig.searchPlaceholder, toolbarActions, totalRows, }) => {
26
33
  const containerRef = (0, react_1.useRef)(null);
27
- const hasFooter = columns.some((column) => column.footer !== undefined);
28
34
  const [uncontrolledPage, setUncontrolledPage] = (0, react_1.useState)(1);
29
- const isPaginated = Boolean(pageSize);
35
+ const [uncontrolledQuery, setUncontrolledQuery] = (0, react_1.useState)(() => ps_table_query_service_1.PsTableQueryService.empty(defaultSort ? defaultSort : null));
36
+ const [selectedIds, setSelectedIds] = (0, react_1.useState)([]);
37
+ const currentQuery = query ? query : uncontrolledQuery;
38
+ const { filters, hiddenColumnIds, search, sort } = currentQuery;
39
+ const visibleColumns = columns.filter((column) => !hiddenColumnIds.includes(column.id));
40
+ const updateQuery = (patch) => {
41
+ const next = { ...currentQuery, ...patch };
42
+ if (!query)
43
+ setUncontrolledQuery(next);
44
+ onQueryChange?.(next);
45
+ };
30
46
  const isServerPaginated = totalRows !== undefined;
47
+ const hasFooter = columns.some((column) => column.footer !== undefined);
48
+ const isSearchable = Boolean(getSearchText) && !isServerPaginated;
49
+ const hasFilters = columns.some((column) => column.filter) && !isServerPaginated;
50
+ const hasToolbar = isSearchable || hasFilters || Boolean(toolbarActions);
51
+ const isSelectable = Boolean(getBulkActions);
52
+ const columnCount = visibleColumns.length + (isSelectable ? 1 : 0);
53
+ const queriedRows = (0, react_1.useMemo)(() => isServerPaginated
54
+ ? rows
55
+ : ps_table_query_service_1.PsTableQueryService.apply(rows, columns, filters, search, getSearchText, sort), [columns, filters, getSearchText, isServerPaginated, rows, search, sort]);
31
56
  const currentPage = page !== null && page !== undefined ? page : uncontrolledPage;
32
- const rowCount = totalRows !== null && totalRows !== undefined ? totalRows : rows.length;
57
+ const rowCount = isServerPaginated ? totalRows : queriedRows.length;
33
58
  const pageCount = pageSize ? Math.max(Math.ceil(rowCount / pageSize), 1) : 1;
34
59
  const safePage = Math.min(Math.max(currentPage, 1), pageCount);
60
+ const isQueried = search.trim() !== '' ||
61
+ Object.values(filters).some((value) => ps_table_query_service_1.PsTableQueryService.isActive(value));
35
62
  const visibleRows = (0, react_1.useMemo)(() => {
36
63
  if (!pageSize || isServerPaginated)
37
- return rows;
38
- return rows.slice((safePage - 1) * pageSize, safePage * pageSize);
39
- }, [isServerPaginated, pageSize, rows, safePage]);
64
+ return queriedRows;
65
+ return queriedRows.slice((safePage - 1) * pageSize, safePage * pageSize);
66
+ }, [isServerPaginated, pageSize, queriedRows, safePage]);
40
67
  const changePage = (next) => {
41
68
  if (onPageChange) {
42
69
  onPageChange(next);
43
70
  return;
44
71
  }
45
- containerRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
72
+ containerRef.current?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
46
73
  setUncontrolledPage(next);
47
74
  };
48
- return ((0, jsx_runtime_1.jsxs)("div", { ref: containerRef, className: ps_design_system_1.PallasoftClassNameService.merge('ps-scrollbar overflow-x-auto rounded-[1.25rem] border border-line bg-surface', className), children: [(0, jsx_runtime_1.jsxs)("table", { className: 'w-full border-collapse text-left', children: [caption ? (0, jsx_runtime_1.jsx)("caption", { className: 'sr-only', children: caption }) : null, (0, jsx_runtime_1.jsx)("thead", { className: 'bg-mist/70 text-[0.68rem] font-semibold uppercase tracking-[0.18em] text-ink/45', children: (0, jsx_runtime_1.jsx)("tr", { children: columns.map((column) => ((0, jsx_runtime_1.jsx)("th", { className: ps_design_system_1.PallasoftClassNameService.merge(densityClassNames[density].head, alignClassNames[column.align !== null && column.align !== undefined ? column.align : 'left'], 'whitespace-nowrap'), scope: 'col', style: columnStyle(column), children: column.label }, column.id))) }) }), (0, jsx_runtime_1.jsx)("tbody", { className: 'divide-y divide-line', children: visibleRows.map((row) => {
49
- const rowId = getRowId(row);
50
- const isExpanded = expandedRowIds ? expandedRowIds.includes(rowId) : false;
51
- return ((0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("tr", { className: ps_design_system_1.PallasoftClassNameService.merge('align-middle transition', onRowClick
52
- ? 'ps-focus-ring cursor-pointer hover:bg-mist/60'
53
- : 'hover:bg-mist/40'), onClick: onRowClick ? () => onRowClick(row) : undefined, onKeyDown: onRowClick
54
- ? (event) => {
55
- if (event.key !== 'Enter' && event.key !== ' ')
56
- return;
57
- event.preventDefault();
58
- onRowClick(row);
59
- }
60
- : undefined, tabIndex: onRowClick ? 0 : undefined, children: columns.map((column) => ((0, jsx_runtime_1.jsx)("td", { className: ps_design_system_1.PallasoftClassNameService.merge(densityClassNames[density].cell, alignClassNames[column.align !== null && column.align !== undefined
61
- ? column.align
62
- : 'left'], column.wrap ? '' : 'whitespace-nowrap', 'text-ink'), style: columnStyle(column), children: column.render(row) }, column.id))) }), renderExpandedRow ? ((0, jsx_runtime_1.jsx)("tr", { className: isExpanded ? '' : 'border-t-0', children: (0, jsx_runtime_1.jsx)("td", { className: 'p-0', colSpan: columns.length, children: (0, jsx_runtime_1.jsx)(ps_motion_components_1.PsCollapse, { isOpen: isExpanded, children: (0, jsx_runtime_1.jsx)("div", { className: 'bg-mist/40 px-4 py-4', children: renderExpandedRow(row) }) }) }) })) : null] }, rowId));
63
- }) }), hasFooter ? ((0, jsx_runtime_1.jsx)("tfoot", { className: 'border-t-2 border-line bg-mist/50 font-semibold text-ink', children: (0, jsx_runtime_1.jsx)("tr", { children: columns.map((column) => ((0, jsx_runtime_1.jsx)("td", { className: ps_design_system_1.PallasoftClassNameService.merge(densityClassNames[density].cell, alignClassNames[column.align !== null && column.align !== undefined ? column.align : 'left']), children: column.footer }, column.id))) }) })) : null] }), isPaginated && pageSize ? ((0, jsx_runtime_1.jsx)(ps_table_pagination_1.PsTablePagination, { onPageChange: changePage, page: safePage, pageCount: pageCount, pageSize: pageSize, totalRows: rowCount })) : null] }));
75
+ const resetPage = () => {
76
+ if (safePage === 1)
77
+ return;
78
+ if (onPageChange)
79
+ onPageChange(1);
80
+ else
81
+ setUncontrolledPage(1);
82
+ };
83
+ const updateSort = (columnId) => {
84
+ updateQuery({ sort: ps_table_query_service_1.PsTableQueryService.nextSort(sort, columnId) });
85
+ resetPage();
86
+ };
87
+ const clearFilters = () => {
88
+ updateQuery({ filters: {}, search: '' });
89
+ resetPage();
90
+ };
91
+ const selectedRows = queriedRows.filter((row) => selectedIds.includes(getRowId(row)));
92
+ const pageIds = visibleRows.map((row) => getRowId(row));
93
+ const isPageSelected = pageIds.length > 0 && pageIds.every((rowId) => selectedIds.includes(rowId));
94
+ const togglePage = () => setSelectedIds((current) => isPageSelected
95
+ ? current.filter((rowId) => !pageIds.includes(rowId))
96
+ : [...new Set([...current, ...pageIds])]);
97
+ const toggleRow = (rowId) => setSelectedIds((current) => current.includes(rowId) ? current.filter((id) => id !== rowId) : [...current, rowId]);
98
+ return ((0, jsx_runtime_1.jsxs)("div", { ref: containerRef, className: ps_design_system_1.PallasoftClassNameService.merge('flex min-w-0 flex-col gap-3', className), children: [isSelectable && selectedRows.length > 0 && getBulkActions ? ((0, jsx_runtime_1.jsx)(ps_table_bulk_bar_1.PsTableBulkBar, { actions: getBulkActions(selectedRows), onClear: () => setSelectedIds([]), selectedCount: selectedRows.length })) : hasToolbar || columns.some((column) => column.canHide) ? ((0, jsx_runtime_1.jsx)(ps_table_toolbar_1.PsTableToolbar, { actions: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [toolbarActions, columns.some((column) => column.canHide) ? ((0, jsx_runtime_1.jsx)(ps_table_column_picker_1.PsTableColumnPicker, { columns: columns
99
+ .filter((column) => column.canHide)
100
+ .map((column) => ({ id: column.id, label: column.label })), hiddenColumnIds: hiddenColumnIds, onChange: (nextHidden) => updateQuery({ hiddenColumnIds: nextHidden }) })) : null] }), columns: hasFilters ? columns : [], filters: filters, isSearchable: isSearchable, onClearFilters: clearFilters, onFilterChange: (columnId, value) => {
101
+ updateQuery({ filters: { ...filters, [columnId]: value } });
102
+ resetPage();
103
+ }, onSearchChange: (next) => {
104
+ updateQuery({ search: next });
105
+ resetPage();
106
+ }, rows: rows, search: search, searchPlaceholder: searchPlaceholder })) : null, (0, jsx_runtime_1.jsxs)("div", { className: 'min-w-0 overflow-hidden rounded-[1.25rem] border border-line bg-surface', children: [(0, jsx_runtime_1.jsx)("div", { className: 'ps-scrollbar overflow-x-auto', children: (0, jsx_runtime_1.jsxs)("table", { className: 'w-full border-collapse text-left', children: [caption ? (0, jsx_runtime_1.jsx)("caption", { className: 'sr-only', children: caption }) : null, (0, jsx_runtime_1.jsx)("thead", { className: 'bg-mist/70 text-[0.68rem] font-semibold uppercase tracking-[0.18em] text-ink/45', children: (0, jsx_runtime_1.jsxs)("tr", { children: [isSelectable ? ((0, jsx_runtime_1.jsx)("th", { className: 'w-10 pl-4 pr-0', scope: 'col', children: (0, jsx_runtime_1.jsx)(ps_input_components_1.PsCheckbox, { isIndeterminate: !isPageSelected && pageIds.some((rowId) => selectedIds.includes(rowId)), isSelected: isPageSelected, label: 'Select all rows on this page', onChange: togglePage }) })) : null, visibleColumns.map((column) => {
107
+ const align = column.align !== null && column.align !== undefined ? column.align : 'left';
108
+ const isSorted = sort !== null && sort.columnId === column.id;
109
+ return ((0, jsx_runtime_1.jsx)("th", { "aria-sort": isSorted
110
+ ? sort.direction
111
+ : column.sortValue && !isServerPaginated
112
+ ? 'none'
113
+ : undefined, className: ps_design_system_1.PallasoftClassNameService.merge(densityClassNames[density].head, alignClassNames[align], 'whitespace-nowrap'), scope: 'col', style: columnStyle(column), children: column.sortValue && !isServerPaginated ? ((0, jsx_runtime_1.jsx)(ps_table_sort_header_1.PsTableSortHeader, { align: align, columnId: column.id, label: column.label, onSort: updateSort, sort: sort })) : (column.label) }, column.id));
114
+ })] }) }), (0, jsx_runtime_1.jsxs)("tbody", { className: 'divide-y divide-line', children: [visibleRows.length === 0 && isQueried ? ((0, jsx_runtime_1.jsx)("tr", { children: (0, jsx_runtime_1.jsxs)("td", { className: 'px-4 py-12 text-center', colSpan: columnCount, children: [(0, jsx_runtime_1.jsx)("p", { className: 'text-sm font-semibold text-ink', children: table_config_1.PsTableConfig.noMatchesTitle }), (0, jsx_runtime_1.jsx)("p", { className: 'mt-1 text-sm text-ink/55', children: table_config_1.PsTableConfig.noMatchesDescription }), (0, jsx_runtime_1.jsx)("button", { className: 'ps-focus-ring mt-3 rounded-full px-3 py-1.5 text-sm font-semibold text-primary hover:underline', onClick: clearFilters, type: 'button', children: "Clear search and filters" })] }) })) : null, visibleRows.map((row) => {
115
+ const rowId = getRowId(row);
116
+ const isExpanded = expandedRowIds ? expandedRowIds.includes(rowId) : false;
117
+ const rowLabel = getRowLabel ? getRowLabel(row) : rowId;
118
+ return ((0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("tr", { "aria-label": onRowClick ? rowLabel : undefined, className: ps_design_system_1.PallasoftClassNameService.merge('align-middle transition-colors', onRowClick
119
+ ? 'ps-focus-ring cursor-pointer hover:bg-mist/60'
120
+ : 'hover:bg-mist/40'), onClick: onRowClick ? () => onRowClick(row) : undefined, onKeyDown: onRowClick
121
+ ? (event) => {
122
+ if (event.target !== event.currentTarget)
123
+ return;
124
+ if (event.key !== 'Enter' && event.key !== ' ')
125
+ return;
126
+ event.preventDefault();
127
+ onRowClick(row);
128
+ }
129
+ : undefined, tabIndex: onRowClick ? 0 : undefined, children: [isSelectable ? ((0, jsx_runtime_1.jsx)("td", { className: 'w-10 pl-4 pr-0', onClick: (event) => event.stopPropagation(), onKeyDown: (event) => event.stopPropagation(), children: (0, jsx_runtime_1.jsx)(ps_input_components_1.PsCheckbox, { isSelected: selectedIds.includes(rowId), label: `Select ${rowLabel}`, onChange: () => toggleRow(rowId) }) })) : null, visibleColumns.map((column) => ((0, jsx_runtime_1.jsx)("td", { className: ps_design_system_1.PallasoftClassNameService.merge(densityClassNames[density].cell, alignClassNames[column.align !== null && column.align !== undefined
130
+ ? column.align
131
+ : 'left'], column.wrap ? '' : 'whitespace-nowrap', 'text-sm text-ink'), style: columnStyle(column), children: column.render(row) }, column.id)))] }), renderExpandedRow ? ((0, jsx_runtime_1.jsx)("tr", { className: isExpanded ? '' : 'border-t-0', children: (0, jsx_runtime_1.jsx)("td", { className: 'p-0', colSpan: columnCount, children: (0, jsx_runtime_1.jsx)(ps_motion_components_1.PsCollapse, { isOpen: isExpanded, children: (0, jsx_runtime_1.jsx)("div", { className: 'bg-mist/40 px-4 py-4', children: renderExpandedRow(row) }) }) }) })) : null] }, rowId));
132
+ })] }), hasFooter ? ((0, jsx_runtime_1.jsx)("tfoot", { className: 'border-t-2 border-line bg-mist/50 text-sm font-semibold text-ink', children: (0, jsx_runtime_1.jsxs)("tr", { children: [isSelectable ? (0, jsx_runtime_1.jsx)("td", {}) : null, visibleColumns.map((column) => ((0, jsx_runtime_1.jsx)("td", { className: ps_design_system_1.PallasoftClassNameService.merge(densityClassNames[density].cell, alignClassNames[column.align !== null && column.align !== undefined
133
+ ? column.align
134
+ : 'left'], 'whitespace-nowrap'), children: column.footer }, column.id)))] }) })) : null] }) }), pageSize ? ((0, jsx_runtime_1.jsx)(ps_table_pagination_1.PsTablePagination, { onPageChange: changePage, page: safePage, pageCount: pageCount, pageSize: pageSize, totalRows: rowCount })) : null] })] }));
64
135
  };
65
136
  exports.PsDataTable = PsDataTable;
@@ -0,0 +1,7 @@
1
+ import type { PsTableRowAction } from '../interfaces/table-row-action';
2
+ export interface PsTableBulkBarProps {
3
+ actions: PsTableRowAction[];
4
+ onClear: () => void;
5
+ selectedCount: number;
6
+ }
7
+ export declare const PsTableBulkBar: ({ actions, onClear, selectedCount }: PsTableBulkBarProps) => import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsTableBulkBar = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const ps_design_system_1 = require("@pallasoft/ps-design-system");
6
+ const PsTableBulkBar = ({ actions, onClear, selectedCount }) => ((0, jsx_runtime_1.jsxs)("div", { "aria-live": 'polite', className: 'flex flex-wrap items-center gap-2 rounded-full border border-primary/30 bg-primary/10 px-2 py-1.5', children: [(0, jsx_runtime_1.jsxs)("span", { className: 'px-2 text-sm font-semibold text-ink', children: [selectedCount, " selected"] }), actions.map((action) => ((0, jsx_runtime_1.jsx)("button", { className: ps_design_system_1.PallasoftClassNameService.merge('ps-focus-ring inline-flex min-h-8 items-center rounded-full px-3 text-sm font-semibold transition-colors disabled:opacity-40', action.isDanger ? 'text-danger hover:bg-danger/10' : 'text-ink hover:bg-ink/5'), disabled: action.isDisabled, onClick: action.onSelect, type: 'button', children: action.label }, action.id))), (0, jsx_runtime_1.jsx)("button", { className: 'ps-focus-ring ml-auto inline-flex min-h-8 items-center rounded-full px-3 text-sm font-semibold text-ink/65 hover:text-ink', onClick: onClear, type: 'button', children: "Clear selection" })] }));
7
+ exports.PsTableBulkBar = PsTableBulkBar;
@@ -0,0 +1,10 @@
1
+ export interface PsTableColumnPickerOption {
2
+ id: string;
3
+ label: string;
4
+ }
5
+ export interface PsTableColumnPickerProps {
6
+ columns: PsTableColumnPickerOption[];
7
+ hiddenColumnIds: string[];
8
+ onChange: (hiddenColumnIds: string[]) => void;
9
+ }
10
+ export declare const PsTableColumnPicker: ({ columns, hiddenColumnIds, onChange, }: PsTableColumnPickerProps) => import("react").JSX.Element;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsTableColumnPicker = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const ps_core_components_1 = require("@pallasoft/ps-core-components");
6
+ const ps_design_system_1 = require("@pallasoft/ps-design-system");
7
+ const ps_icons_1 = require("@pallasoft/ps-icons");
8
+ const ps_input_components_1 = require("@pallasoft/ps-input-components");
9
+ const react_aria_components_1 = require("react-aria-components");
10
+ const PsTableColumnPicker = ({ columns, hiddenColumnIds, onChange, }) => {
11
+ const zIndex = (0, ps_core_components_1.usePsLayerZIndex)(ps_design_system_1.PallasoftZIndex.Dropdown);
12
+ const visibleCount = columns.filter((column) => !hiddenColumnIds.includes(column.id)).length;
13
+ return ((0, jsx_runtime_1.jsxs)(react_aria_components_1.DialogTrigger, { children: [(0, jsx_runtime_1.jsxs)(react_aria_components_1.Button, { className: 'ps-focus-ring ps-liquid ps-liquid-control inline-flex min-h-10 items-center gap-2 rounded-full px-4 text-sm font-semibold text-ink outline-none transition-colors hover:border-primary/40', children: [(0, jsx_runtime_1.jsx)("span", { children: "Columns" }), (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4', iconKey: ps_icons_1.PsIconKey.Chevron })] }), (0, jsx_runtime_1.jsx)(react_aria_components_1.Popover, { className: 'ps-floating-panel w-60 outline-none', offset: 6, placement: 'bottom end', style: { zIndex }, children: (0, jsx_runtime_1.jsx)(react_aria_components_1.Dialog, { "aria-label": 'Columns', className: 'flex flex-col gap-0.5 p-1.5 outline-none', children: (0, jsx_runtime_1.jsx)(ps_core_components_1.PsLayer, { zIndex: zIndex, children: columns.map((column) => {
14
+ const isVisible = !hiddenColumnIds.includes(column.id);
15
+ return ((0, jsx_runtime_1.jsx)(ps_input_components_1.PsCheckbox, { className: 'ps-floating-option w-full', isDisabled: isVisible && visibleCount <= 1, isSelected: isVisible, label: column.label, onChange: () => onChange(isVisible
16
+ ? [...hiddenColumnIds, column.id]
17
+ : hiddenColumnIds.filter((hiddenId) => hiddenId !== column.id)), children: column.label }, column.id));
18
+ }) }) }) })] }));
19
+ };
20
+ exports.PsTableColumnPicker = PsTableColumnPicker;
@@ -0,0 +1,11 @@
1
+ import { PsTableFilterKind } from '../constants/table-filter-kind';
2
+ import type { PsTableFilterOption } from '../interfaces/table-filter-option';
3
+ import type { PsTableFilterValue } from '../interfaces/table-filter-value';
4
+ export interface PsTableFilterFieldProps {
5
+ kind: (typeof PsTableFilterKind)[keyof typeof PsTableFilterKind];
6
+ label: string;
7
+ onChange: (value: PsTableFilterValue) => void;
8
+ options: PsTableFilterOption[];
9
+ value: PsTableFilterValue;
10
+ }
11
+ export declare const PsTableFilterField: ({ kind, label, onChange, options, value, }: PsTableFilterFieldProps) => import("react").JSX.Element;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsTableFilterField = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const ps_input_components_1 = require("@pallasoft/ps-input-components");
6
+ const table_filter_kind_1 = require("../constants/table-filter-kind");
7
+ const PsTableFilterField = ({ kind, label, onChange, options, value, }) => {
8
+ if (kind === table_filter_kind_1.PsTableFilterKind.select)
9
+ return ((0, jsx_runtime_1.jsx)(ps_input_components_1.PsMultiSelectInput, { label: label, onChange: (values) => onChange({ values }), options: options, placeholder: 'Any', value: value.values ? value.values : [] }));
10
+ if (kind === table_filter_kind_1.PsTableFilterKind.text)
11
+ return ((0, jsx_runtime_1.jsx)(ps_input_components_1.PsTextInput, { label: label, onChange: (event) => onChange({ text: event.target.value }), placeholder: 'Contains', value: value.text ? value.text : '' }));
12
+ if (kind === table_filter_kind_1.PsTableFilterKind.number)
13
+ return ((0, jsx_runtime_1.jsxs)("fieldset", { className: 'grid grid-cols-2 gap-2', children: [(0, jsx_runtime_1.jsx)("legend", { className: 'ps-input-label col-span-2 mb-2', children: label }), (0, jsx_runtime_1.jsx)(ps_input_components_1.PsTextInput, { hideLabel: true, inputMode: 'decimal', label: `${label} minimum`, onChange: (event) => onChange({ ...value, from: event.target.value }), placeholder: 'Min', value: value.from ? value.from : '' }), (0, jsx_runtime_1.jsx)(ps_input_components_1.PsTextInput, { hideLabel: true, inputMode: 'decimal', label: `${label} maximum`, onChange: (event) => onChange({ ...value, to: event.target.value }), placeholder: 'Max', value: value.to ? value.to : '' })] }));
14
+ return ((0, jsx_runtime_1.jsxs)("fieldset", { className: 'grid grid-cols-2 gap-2', children: [(0, jsx_runtime_1.jsx)("legend", { className: 'ps-input-label col-span-2 mb-2', children: label }), (0, jsx_runtime_1.jsx)(ps_input_components_1.PsDateInput, { hideLabel: true, label: `${label} from`, onChange: (from) => onChange({ ...value, from }), placeholder: 'From', value: value.from ? value.from : '' }), (0, jsx_runtime_1.jsx)(ps_input_components_1.PsDateInput, { hideLabel: true, label: `${label} to`, onChange: (to) => onChange({ ...value, to }), placeholder: 'To', value: value.to ? value.to : '' })] }));
15
+ };
16
+ exports.PsTableFilterField = PsTableFilterField;
@@ -6,7 +6,7 @@ const ps_design_system_1 = require("@pallasoft/ps-design-system");
6
6
  const PsTablePagination = ({ className, onPageChange, page, pageCount, pageSize, totalRows, }) => {
7
7
  const firstRow = totalRows === 0 ? 0 : (page - 1) * pageSize + 1;
8
8
  const lastRow = Math.min(page * pageSize, totalRows);
9
- const buttonClassName = 'ps-focus-ring rounded-ps-control border border-line px-3 py-1.5 text-sm font-semibold text-ink transition disabled:cursor-not-allowed disabled:opacity-40 enabled:hover:border-primary/40 enabled:hover:text-primary';
10
- return ((0, jsx_runtime_1.jsxs)("nav", { "aria-label": 'Pagination', className: ps_design_system_1.PallasoftClassNameService.merge('flex flex-wrap items-center justify-between gap-3 border-t border-line px-4 py-3', className), children: [(0, jsx_runtime_1.jsx)("p", { className: 'text-sm text-muted', children: totalRows === 0 ? 'No rows' : `Showing ${firstRow}-${lastRow} of ${totalRows}` }), (0, jsx_runtime_1.jsxs)("div", { className: 'flex items-center gap-2', children: [(0, jsx_runtime_1.jsx)("button", { className: buttonClassName, disabled: page <= 1, onClick: () => onPageChange(page - 1), type: 'button', children: "Previous" }), (0, jsx_runtime_1.jsxs)("span", { className: 'text-sm font-semibold text-ink', children: ["Page ", Math.min(page, pageCount), " of ", pageCount] }), (0, jsx_runtime_1.jsx)("button", { className: buttonClassName, disabled: page >= pageCount, onClick: () => onPageChange(page + 1), type: 'button', children: "Next" })] })] }));
9
+ const buttonClassName = 'ps-focus-ring inline-flex min-h-9 items-center rounded-full border border-line px-3.5 text-sm font-semibold text-ink transition-colors disabled:cursor-not-allowed disabled:opacity-40 enabled:hover:border-primary/40';
10
+ return ((0, jsx_runtime_1.jsxs)("nav", { "aria-label": 'Pagination', className: ps_design_system_1.PallasoftClassNameService.merge('flex flex-wrap items-center justify-between gap-3 border-t border-line px-4 py-3', className), children: [(0, jsx_runtime_1.jsx)("p", { className: 'text-sm text-ink/55', children: totalRows === 0 ? 'No rows' : `${firstRow}–${lastRow} of ${totalRows}` }), (0, jsx_runtime_1.jsxs)("div", { className: 'flex items-center gap-2', children: [(0, jsx_runtime_1.jsx)("button", { className: buttonClassName, disabled: page <= 1, onClick: () => onPageChange(page - 1), type: 'button', children: "Previous" }), (0, jsx_runtime_1.jsxs)("span", { className: 'text-sm text-ink/65', children: ["Page ", Math.min(page, pageCount), " of ", pageCount] }), (0, jsx_runtime_1.jsx)("button", { className: buttonClassName, disabled: page >= pageCount, onClick: () => onPageChange(page + 1), type: 'button', children: "Next" })] })] }));
11
11
  };
12
12
  exports.PsTablePagination = PsTablePagination;
@@ -0,0 +1,9 @@
1
+ import type { PsTableSort } from '../interfaces/table-sort';
2
+ export interface PsTableSortHeaderProps {
3
+ align: 'center' | 'left' | 'right';
4
+ columnId: string;
5
+ label: string;
6
+ onSort: (columnId: string) => void;
7
+ sort: PsTableSort | null;
8
+ }
9
+ export declare const PsTableSortHeader: ({ align, columnId, label, onSort, sort, }: PsTableSortHeaderProps) => import("react").JSX.Element;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsTableSortHeader = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const ps_design_system_1 = require("@pallasoft/ps-design-system");
6
+ const ps_icons_1 = require("@pallasoft/ps-icons");
7
+ const PsTableSortHeader = ({ align, columnId, label, onSort, sort, }) => {
8
+ const isSorted = sort !== null && sort.columnId === columnId;
9
+ const iconKey = !isSorted
10
+ ? ps_icons_1.PsIconKey.Sort
11
+ : sort.direction === 'ascending'
12
+ ? ps_icons_1.PsIconKey.SortAscending
13
+ : ps_icons_1.PsIconKey.SortDescending;
14
+ return ((0, jsx_runtime_1.jsxs)("button", { className: ps_design_system_1.PallasoftClassNameService.merge('ps-focus-ring group inline-flex items-center gap-1 rounded-md uppercase tracking-[0.18em] transition-colors hover:text-ink', align === 'right' ? 'flex-row-reverse' : '', isSorted ? 'text-ink' : ''), onClick: () => onSort(columnId), type: 'button', children: [(0, jsx_runtime_1.jsx)("span", { children: label }), (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: ps_design_system_1.PallasoftClassNameService.merge('block h-3.5 w-3.5 shrink-0', isSorted
15
+ ? 'text-primary'
16
+ : 'opacity-0 group-hover:opacity-60 group-focus-visible:opacity-60'), iconKey: iconKey })] }));
17
+ };
18
+ exports.PsTableSortHeader = PsTableSortHeader;
@@ -0,0 +1,16 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { PsTableColumn } from '../interfaces/table-column';
3
+ import type { PsTableFilterValue } from '../interfaces/table-filter-value';
4
+ export interface PsTableToolbarProps<RowData> {
5
+ actions?: ReactNode;
6
+ columns: PsTableColumn<RowData>[];
7
+ filters: Record<string, PsTableFilterValue>;
8
+ isSearchable: boolean;
9
+ onClearFilters: () => void;
10
+ onFilterChange: (columnId: string, value: PsTableFilterValue) => void;
11
+ onSearchChange: (search: string) => void;
12
+ rows: RowData[];
13
+ search: string;
14
+ searchPlaceholder: string;
15
+ }
16
+ export declare const PsTableToolbar: <RowData>({ actions, columns, filters, isSearchable, onClearFilters, onFilterChange, onSearchChange, rows, search, searchPlaceholder, }: PsTableToolbarProps<RowData>) => import("react").JSX.Element;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsTableToolbar = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const ps_core_components_1 = require("@pallasoft/ps-core-components");
6
+ const ps_design_system_1 = require("@pallasoft/ps-design-system");
7
+ const ps_icons_1 = require("@pallasoft/ps-icons");
8
+ const react_aria_components_1 = require("react-aria-components");
9
+ const ps_table_query_service_1 = require("../services/ps-table-query-service");
10
+ const ps_table_filter_field_1 = require("./ps-table-filter-field");
11
+ const toolbarButtonClassName = 'ps-focus-ring ps-liquid ps-liquid-control inline-flex min-h-10 items-center gap-2 rounded-full px-4 text-sm font-semibold text-ink outline-none transition-colors hover:border-primary/40';
12
+ const PsTableToolbar = ({ actions, columns, filters, isSearchable, onClearFilters, onFilterChange, onSearchChange, rows, search, searchPlaceholder, }) => {
13
+ const filterableColumns = columns.filter((column) => column.filter);
14
+ const zIndex = (0, ps_core_components_1.usePsLayerZIndex)(ps_design_system_1.PallasoftZIndex.Dropdown);
15
+ const activeCount = filterableColumns.filter((column) => ps_table_query_service_1.PsTableQueryService.isActive(filters[column.id])).length;
16
+ return ((0, jsx_runtime_1.jsxs)("div", { className: 'flex flex-wrap items-center gap-2', children: [isSearchable ? ((0, jsx_runtime_1.jsxs)("label", { className: 'relative min-w-[12rem] flex-1 sm:max-w-xs', children: [(0, jsx_runtime_1.jsx)("span", { className: 'sr-only', children: searchPlaceholder }), (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'pointer-events-none absolute left-3.5 top-1/2 h-4 w-4 -translate-y-1/2 text-ink/45', iconKey: ps_icons_1.PsIconKey.Search }), (0, jsx_runtime_1.jsx)("input", { className: 'ps-input-control ps-focus-ring min-h-10 rounded-full pl-10 text-sm', onChange: (event) => onSearchChange(event.target.value), placeholder: searchPlaceholder, type: 'search', value: search })] })) : null, filterableColumns.length > 0 ? ((0, jsx_runtime_1.jsxs)(react_aria_components_1.DialogTrigger, { children: [(0, jsx_runtime_1.jsxs)(react_aria_components_1.Button, { className: toolbarButtonClassName, children: [(0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4', iconKey: ps_icons_1.PsIconKey.Filter }), (0, jsx_runtime_1.jsx)("span", { children: "Filters" }), activeCount > 0 ? ((0, jsx_runtime_1.jsx)("span", { className: 'inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1.5 text-xs font-semibold text-[color:var(--ps-color-primary-contrast)]', children: activeCount })) : null] }), (0, jsx_runtime_1.jsx)(react_aria_components_1.Popover, { className: 'ps-floating-panel ps-scrollbar max-h-[min(34rem,80vh)] w-[min(22rem,calc(100vw-2rem))] overflow-y-auto outline-none', offset: 6, placement: 'bottom start', style: { zIndex }, children: (0, jsx_runtime_1.jsx)(react_aria_components_1.Dialog, { "aria-label": 'Filters', className: 'flex flex-col gap-4 p-4 outline-none', children: (0, jsx_runtime_1.jsxs)(ps_core_components_1.PsLayer, { zIndex: zIndex, children: [filterableColumns.map((column) => ((0, jsx_runtime_1.jsx)(ps_table_filter_field_1.PsTableFilterField, { kind: column.filter ? column.filter.kind : 'text', label: column.label, onChange: (value) => onFilterChange(column.id, value), options: ps_table_query_service_1.PsTableQueryService.options(rows, column), value: filters[column.id] ? filters[column.id] : {} }, column.id))), (0, jsx_runtime_1.jsx)("div", { className: 'flex justify-end border-t border-line pt-3', children: (0, jsx_runtime_1.jsx)("button", { className: 'ps-focus-ring rounded-lg px-2 py-1 text-sm font-semibold text-ink/65 hover:text-ink disabled:opacity-40', disabled: activeCount === 0, onClick: onClearFilters, type: 'button', children: "Clear all" }) })] }) }) })] })) : null, activeCount > 0 ? ((0, jsx_runtime_1.jsx)("button", { className: ps_design_system_1.PallasoftClassNameService.merge('ps-focus-ring rounded-full px-3 py-2 text-sm font-semibold text-ink/65 hover:text-ink'), onClick: onClearFilters, type: 'button', children: "Clear filters" })) : null, actions ? (0, jsx_runtime_1.jsx)("div", { className: 'ml-auto flex flex-wrap items-center gap-2', children: actions }) : null] }));
17
+ };
18
+ exports.PsTableToolbar = PsTableToolbar;
@@ -0,0 +1,5 @@
1
+ export declare const PsTableConfig: {
2
+ readonly noMatchesDescription: "Try a different search or clear the filters.";
3
+ readonly noMatchesTitle: "Nothing matches";
4
+ readonly searchPlaceholder: "Search";
5
+ };
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsTableConfig = void 0;
4
+ exports.PsTableConfig = {
5
+ noMatchesDescription: 'Try a different search or clear the filters.',
6
+ noMatchesTitle: 'Nothing matches',
7
+ searchPlaceholder: 'Search',
8
+ };
@@ -0,0 +1,6 @@
1
+ export declare const PsTableFilterKind: {
2
+ readonly date: "date";
3
+ readonly number: "number";
4
+ readonly select: "select";
5
+ readonly text: "text";
6
+ };
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsTableFilterKind = void 0;
4
+ exports.PsTableFilterKind = {
5
+ date: 'date',
6
+ number: 'number',
7
+ select: 'select',
8
+ text: 'text',
9
+ };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,11 @@
1
1
  export * from './components/ps-data-table';
2
2
  export * from './components/ps-table-pagination';
3
+ export * from './constants/table-filter-kind';
3
4
  export * from './interfaces/table-column';
5
+ export * from './interfaces/table-column-filter';
6
+ export * from './interfaces/table-filter-option';
7
+ export * from './interfaces/table-filter-value';
8
+ export * from './interfaces/table-query';
9
+ export * from './interfaces/table-row-action';
10
+ export * from './interfaces/table-sort';
11
+ export * from './services/ps-table-query-service';
package/dist/index.js CHANGED
@@ -16,4 +16,12 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./components/ps-data-table"), exports);
18
18
  __exportStar(require("./components/ps-table-pagination"), exports);
19
+ __exportStar(require("./constants/table-filter-kind"), exports);
19
20
  __exportStar(require("./interfaces/table-column"), exports);
21
+ __exportStar(require("./interfaces/table-column-filter"), exports);
22
+ __exportStar(require("./interfaces/table-filter-option"), exports);
23
+ __exportStar(require("./interfaces/table-filter-value"), exports);
24
+ __exportStar(require("./interfaces/table-query"), exports);
25
+ __exportStar(require("./interfaces/table-row-action"), exports);
26
+ __exportStar(require("./interfaces/table-sort"), exports);
27
+ __exportStar(require("./services/ps-table-query-service"), exports);
@@ -0,0 +1,7 @@
1
+ import type { PsTableFilterKind } from '../constants/table-filter-kind';
2
+ import type { PsTableFilterOption } from './table-filter-option';
3
+ export interface PsTableColumnFilter<RowData> {
4
+ getValue: (row: RowData) => string | number | null;
5
+ kind: (typeof PsTableFilterKind)[keyof typeof PsTableFilterKind];
6
+ options?: PsTableFilterOption[];
7
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,13 +1,15 @@
1
1
  import type { ReactNode } from 'react';
2
+ import type { PsTableColumnFilter } from './table-column-filter';
2
3
  export interface PsTableColumn<RowData> {
3
4
  align?: 'center' | 'left' | 'right';
5
+ canHide?: boolean;
6
+ filter?: PsTableColumnFilter<RowData>;
4
7
  footer?: ReactNode;
5
8
  id: string;
6
9
  label: string;
7
- /** Stops the table squeezing this column below the given CSS width. */
8
10
  minWidth?: string;
9
11
  render: (row: RowData) => ReactNode;
12
+ sortValue?: (row: RowData) => string | number | null;
10
13
  width?: string;
11
- /** Lets long cell content wrap onto several lines. Cells stay on one line by default. */
12
14
  wrap?: boolean;
13
15
  }
@@ -0,0 +1,4 @@
1
+ export interface PsTableFilterOption {
2
+ label: string;
3
+ value: string;
4
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,6 @@
1
+ export interface PsTableFilterValue {
2
+ from?: string;
3
+ text?: string;
4
+ to?: string;
5
+ values?: string[];
6
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,8 @@
1
+ import type { PsTableFilterValue } from './table-filter-value';
2
+ import type { PsTableSort } from './table-sort';
3
+ export interface PsTableQuery {
4
+ filters: Record<string, PsTableFilterValue>;
5
+ hiddenColumnIds: string[];
6
+ search: string;
7
+ sort: PsTableSort | null;
8
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,7 @@
1
+ export interface PsTableRowAction {
2
+ id: string;
3
+ isDanger?: boolean;
4
+ isDisabled?: boolean;
5
+ label: string;
6
+ onSelect: () => void;
7
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,4 @@
1
+ export interface PsTableSort {
2
+ columnId: string;
3
+ direction: 'ascending' | 'descending';
4
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,13 @@
1
+ import type { PsTableColumn } from '../interfaces/table-column';
2
+ import type { PsTableFilterValue } from '../interfaces/table-filter-value';
3
+ import type { PsTableQuery } from '../interfaces/table-query';
4
+ import type { PsTableSort } from '../interfaces/table-sort';
5
+ export declare class PsTableQueryService {
6
+ static isActive(value: PsTableFilterValue | undefined): boolean;
7
+ static matchesFilter<RowData>(row: RowData, column: PsTableColumn<RowData>, value: PsTableFilterValue): boolean;
8
+ static apply<RowData>(rows: RowData[], columns: PsTableColumn<RowData>[], filters: Record<string, PsTableFilterValue>, search: string, getSearchText: ((row: RowData) => string) | undefined, sort: PsTableSort | null): RowData[];
9
+ static options<RowData>(rows: RowData[], column: PsTableColumn<RowData>): import("..").PsTableFilterOption[];
10
+ static empty(sort: PsTableSort | null): PsTableQuery;
11
+ static isEqual(left: PsTableQuery, right: PsTableQuery): boolean;
12
+ static nextSort(current: PsTableSort | null, columnId: string): PsTableSort | null;
13
+ }
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsTableQueryService = void 0;
4
+ const table_filter_kind_1 = require("../constants/table-filter-kind");
5
+ class PsTableQueryService {
6
+ static isActive(value) {
7
+ if (!value)
8
+ return false;
9
+ return Boolean((value.text && value.text.trim() !== '') ||
10
+ (value.values && value.values.length > 0) ||
11
+ (value.from && value.from !== '') ||
12
+ (value.to && value.to !== ''));
13
+ }
14
+ static matchesFilter(row, column, value) {
15
+ if (!column.filter || !PsTableQueryService.isActive(value))
16
+ return true;
17
+ const raw = column.filter.getValue(row);
18
+ const cell = raw === null ? '' : String(raw);
19
+ if (column.filter.kind === table_filter_kind_1.PsTableFilterKind.text)
20
+ return cell.toLowerCase().includes((value.text ? value.text : '').trim().toLowerCase());
21
+ if (column.filter.kind === table_filter_kind_1.PsTableFilterKind.select)
22
+ return value.values ? value.values.includes(cell) : true;
23
+ if (column.filter.kind === table_filter_kind_1.PsTableFilterKind.number) {
24
+ const amount = Number(cell);
25
+ if (cell === '' || Number.isNaN(amount))
26
+ return false;
27
+ if (value.from && amount < Number(value.from))
28
+ return false;
29
+ if (value.to && amount > Number(value.to))
30
+ return false;
31
+ return true;
32
+ }
33
+ if (cell === '')
34
+ return false;
35
+ if (value.from && cell < value.from)
36
+ return false;
37
+ if (value.to && cell > value.to)
38
+ return false;
39
+ return true;
40
+ }
41
+ static apply(rows, columns, filters, search, getSearchText, sort) {
42
+ const query = search.trim().toLowerCase();
43
+ const filtered = rows.filter((row) => (query === '' || !getSearchText || getSearchText(row).toLowerCase().includes(query)) &&
44
+ columns.every((column) => PsTableQueryService.matchesFilter(row, column, filters[column.id] ? filters[column.id] : {})));
45
+ const sortColumn = sort ? columns.find((column) => column.id === sort.columnId) : undefined;
46
+ if (!sort || !sortColumn || !sortColumn.sortValue)
47
+ return filtered;
48
+ const getValue = sortColumn.sortValue;
49
+ const direction = sort.direction === 'ascending' ? 1 : -1;
50
+ return [...filtered].sort((left, right) => {
51
+ const a = getValue(left);
52
+ const b = getValue(right);
53
+ if (a === b)
54
+ return 0;
55
+ if (a === null)
56
+ return 1;
57
+ if (b === null)
58
+ return -1;
59
+ if (typeof a === 'number' && typeof b === 'number')
60
+ return (a - b) * direction;
61
+ return String(a).localeCompare(String(b), undefined, { numeric: true }) * direction;
62
+ });
63
+ }
64
+ static options(rows, column) {
65
+ if (!column.filter)
66
+ return [];
67
+ if (column.filter.options)
68
+ return column.filter.options;
69
+ const getValue = column.filter.getValue;
70
+ const values = new Set(rows.map((row) => getValue(row)).filter((value) => value !== null));
71
+ return [...values]
72
+ .map((value) => String(value))
73
+ .sort((a, b) => a.localeCompare(b))
74
+ .map((value) => ({ label: value, value }));
75
+ }
76
+ static empty(sort) {
77
+ return { filters: {}, hiddenColumnIds: [], search: '', sort };
78
+ }
79
+ static isEqual(left, right) {
80
+ const activeFilters = (query) => Object.entries(query.filters)
81
+ .filter(([, value]) => PsTableQueryService.isActive(value))
82
+ .sort(([a], [b]) => a.localeCompare(b));
83
+ return (left.search.trim() === right.search.trim() &&
84
+ JSON.stringify(left.sort) === JSON.stringify(right.sort) &&
85
+ JSON.stringify([...left.hiddenColumnIds].sort()) ===
86
+ JSON.stringify([...right.hiddenColumnIds].sort()) &&
87
+ JSON.stringify(activeFilters(left)) === JSON.stringify(activeFilters(right)));
88
+ }
89
+ static nextSort(current, columnId) {
90
+ if (!current || current.columnId !== columnId)
91
+ return { columnId, direction: 'ascending' };
92
+ if (current.direction === 'ascending')
93
+ return { columnId, direction: 'descending' };
94
+ return null;
95
+ }
96
+ }
97
+ exports.PsTableQueryService = PsTableQueryService;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pallasoft/ps-table-components",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "private": false,
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -68,7 +68,11 @@
68
68
  "url": "git+https://github.com/pallasoft/ps-table-components.git"
69
69
  },
70
70
  "dependencies": {
71
- "@pallasoft/ps-design-system": "^1.1.0",
72
- "@pallasoft/ps-motion-components": "^1.1.0"
71
+ "@pallasoft/ps-core-components": "^2.2.0",
72
+ "@pallasoft/ps-design-system": "^1.3.0",
73
+ "@pallasoft/ps-icons": "^1.3.0",
74
+ "@pallasoft/ps-input-components": "^1.4.1",
75
+ "@pallasoft/ps-motion-components": "^1.1.0",
76
+ "react-aria-components": "^1.21.1"
73
77
  }
74
78
  }