@pallasoft/ps-table-components 1.28.3 → 1.29.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/dist/components/ps-data-table.js +52 -50
- package/dist/components/ps-report-table-skeleton.d.ts +10 -0
- package/dist/components/ps-report-table-skeleton.js +29 -0
- package/dist/components/ps-report-table.d.ts +28 -0
- package/dist/components/ps-report-table.js +45 -0
- package/dist/constants/ps-report-column-align.d.ts +6 -0
- package/dist/constants/ps-report-column-align.js +8 -0
- package/dist/constants/ps-report-row-kind.d.ts +7 -0
- package/dist/constants/ps-report-row-kind.js +9 -0
- package/dist/constants/ps-report-row-tone.d.ts +7 -0
- package/dist/constants/ps-report-row-tone.js +9 -0
- package/dist/constants/ps-report-table-classes.d.ts +52 -0
- package/dist/constants/ps-report-table-classes.js +59 -0
- package/dist/constants/ps-report-table-config.d.ts +9 -0
- package/dist/constants/ps-report-table-config.js +9 -0
- package/dist/constants/ps-report-table-density.d.ts +5 -0
- package/dist/constants/ps-report-table-density.js +7 -0
- package/dist/constants/ps-table-sticky-config.d.ts +1 -1
- package/dist/constants/ps-table-sticky-config.js +1 -1
- package/dist/esm/components/ps-data-table.js +52 -50
- package/dist/esm/components/ps-report-table-skeleton.js +25 -0
- package/dist/esm/components/ps-report-table.js +41 -0
- package/dist/esm/constants/ps-report-column-align.js +5 -0
- package/dist/esm/constants/ps-report-row-kind.js +6 -0
- package/dist/esm/constants/ps-report-row-tone.js +6 -0
- package/dist/esm/constants/ps-report-table-classes.js +56 -0
- package/dist/esm/constants/ps-report-table-config.js +6 -0
- package/dist/esm/constants/ps-report-table-density.js +4 -0
- package/dist/esm/constants/ps-table-sticky-config.js +1 -1
- package/dist/esm/hooks/use-ps-sticky-head.js +34 -22
- package/dist/esm/index.js +8 -0
- package/dist/esm/interfaces/ps-report-cell-plan.js +1 -0
- package/dist/esm/interfaces/ps-report-column.js +1 -0
- package/dist/esm/interfaces/ps-table-sticky-head-inputs.js +1 -0
- package/dist/esm/services/ps-report-table-service.js +67 -0
- package/dist/esm/services/ps-table-sticky-head-service.js +11 -3
- package/dist/hooks/use-ps-sticky-head.d.ts +1 -1
- package/dist/hooks/use-ps-sticky-head.js +33 -21
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/interfaces/ps-report-cell-plan.d.ts +6 -0
- package/dist/interfaces/ps-report-cell-plan.js +2 -0
- package/dist/interfaces/ps-report-column.d.ts +12 -0
- package/dist/interfaces/ps-report-column.js +2 -0
- package/dist/interfaces/ps-sticky-head-refs.d.ts +1 -1
- package/dist/interfaces/ps-table-sticky-head-inputs.d.ts +7 -0
- package/dist/interfaces/ps-table-sticky-head-inputs.js +2 -0
- package/dist/services/ps-report-table-service.d.ts +17 -0
- package/dist/services/ps-report-table-service.js +71 -0
- package/dist/services/ps-table-sticky-head-service.d.ts +3 -1
- package/dist/services/ps-table-sticky-head-service.js +11 -3
- package/package.json +1 -1
|
@@ -63,8 +63,8 @@ export const PsDataTable = ({ caption, className, clearSelectionKey, columns, cu
|
|
|
63
63
|
const [openedGroupKeys, setOpenedGroupKeys] = useState([]);
|
|
64
64
|
const [seenClearKey, setSeenClearKey] = useState(clearSelectionKey);
|
|
65
65
|
const stickyRegionRef = useRef(null);
|
|
66
|
-
const stickySentinelRef = useRef(null);
|
|
67
66
|
const stickyCardRef = useRef(null);
|
|
67
|
+
const stickyContentRef = useRef(null);
|
|
68
68
|
const stickyHeadRef = useRef(null);
|
|
69
69
|
const currentQuery = query ? query : uncontrolledQuery;
|
|
70
70
|
if (seenClearKey !== clearSelectionKey) {
|
|
@@ -93,12 +93,12 @@ export const PsDataTable = ({ caption, className, clearSelectionKey, columns, cu
|
|
|
93
93
|
const pins = PsTablePinService.leading(pinFirstColumn, Boolean(reorder), isSelectable);
|
|
94
94
|
const placementOf = (column, columnIndex) => PallasoftClassNameService.merge(column.hideBelow === undefined ? '' : PsTableHideBelowClasses[column.hideBelow], columnIndex === 0 ? pins.first : '', PsTablePinService.end(column.pinEnd === true));
|
|
95
95
|
const overflow = usePsTableOverflow(`${visibleColumns.map((column) => column.id).join(',')}:${rows.length}`);
|
|
96
|
-
|
|
96
|
+
usePsStickyHead({
|
|
97
97
|
cardRef: stickyCardRef,
|
|
98
|
+
contentRef: stickyContentRef,
|
|
98
99
|
headRef: stickyHeadRef,
|
|
99
100
|
layoutKey: `${visibleColumns.map((column) => column.id).join(',')}:${rows.length}:${isLoading}`,
|
|
100
101
|
regionRef: stickyRegionRef,
|
|
101
|
-
sentinelRef: stickySentinelRef,
|
|
102
102
|
stickyTop,
|
|
103
103
|
});
|
|
104
104
|
const deferredSearch = useDeferredValue(search);
|
|
@@ -361,51 +361,53 @@ export const PsDataTable = ({ caption, className, clearSelectionKey, columns, cu
|
|
|
361
361
|
updateQuery({ search: next });
|
|
362
362
|
resetPage();
|
|
363
363
|
}, rows: rows, search: search, searchPlaceholder: searchPlaceholder })) : null, banner] }));
|
|
364
|
-
return (_jsxs("div", { ref: containerRef, className: PallasoftClassNameService.merge('flex min-w-0 flex-col gap-3', className), children: [stickyTop === undefined ? (_jsxs(_Fragment, { children: [stickyLeading, topContent] })) : (
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
364
|
+
return (_jsxs("div", { ref: containerRef, className: PallasoftClassNameService.merge('flex min-w-0 flex-col gap-3', className), children: [stickyTop === undefined ? (_jsxs(_Fragment, { children: [stickyLeading, topContent] })) : (_jsx(_Fragment, { children: _jsxs("div", { className: PallasoftClassNameService.merge('sticky z-sticky flex flex-col gap-3', stickyClassName), ref: stickyRegionRef, style: { top: stickyTop }, children: [stickyLeading, topContent] }) })), _jsx("div", { className: 'min-w-0 overflow-clip rounded-[1.25rem]', children: _jsx("div", { "aria-busy": isRefreshing, className: 'min-w-0 overflow-hidden rounded-[1.25rem] border border-line bg-surface', ref: stickyCardRef, children: _jsxs("div", { ref: stickyContentRef, children: [showsPagination &&
|
|
365
|
+
paginationPosition !== PsTablePaginationPosition.bottom &&
|
|
366
|
+
rowCount > PsTablePaginationConfig.topMinRows
|
|
367
|
+
? renderPagination('border-b border-t-0')
|
|
368
|
+
: null, mobileLayout === 'cards' ? (_jsxs("div", { className: PallasoftClassNameService.merge('sm:hidden', isRefreshing ? PsTableBodyClasses.refreshing : ''), children: [reorder && reorder.cardsReason ? (_jsx("p", { className: 'border-b border-line px-4 py-2 text-xs text-ink/55', children: reorder.cardsReason })) : null, grouping
|
|
369
|
+
? groups.map((group, groupIndex) => {
|
|
370
|
+
const isExpanded = isGroupExpanded(group.key, groupIndex);
|
|
371
|
+
return (_jsxs("div", { className: 'border-b border-line last:border-b-0', children: [_jsx(PsTableGroupHeader, { count: group.rows.length, isExpanded: isExpanded, label: grouping.getGroupLabel(group.key, group.rows), onToggle: () => toggleGroup(group.key, isExpanded), summary: grouping.renderGroupSummary
|
|
372
|
+
? grouping.renderGroupSummary(group.rows)
|
|
373
|
+
: undefined }), _jsx(PsCollapse, { isOpen: isExpanded && !closingGroupKeys.includes(group.key), children: renderCardList(group.rows, null) })] }, `group-${group.key}`));
|
|
374
|
+
})
|
|
375
|
+
: renderCardList(shouldVirtualize ? visibleRows.slice(0, cards.count) : visibleRows, visibleRows.length === 0 && isSearching ? (_jsx(PsTableSearchingState, { label: searchingLabel, skeletonRows: PsTableConfig.searchingSkeletonRows })) : visibleRows.length === 0 && isQueried ? (_jsxs("div", { className: 'px-4 py-10 text-center', children: [_jsx("p", { className: 'text-sm font-semibold text-ink', children: PsTableConfig.noMatchesTitle }), _jsx("button", { className: PallasoftClassNameService.merge(PsTableControlClasses.textButton, 'mt-2 text-primary'), onClick: clearFilters, type: 'button', children: "Clear search and filters" })] })) : null), !grouping && shouldVirtualize && cards.count < visibleRows.length ? (_jsx("div", { "aria-hidden": 'true', className: 'h-px', ref: cards.sentinelRef })) : null] })) : null, _jsx("div", { className: PallasoftClassNameService.merge('ps-scrollbar overflow-x-auto overflow-y-hidden overscroll-x-contain', isRefreshing ? PsTableBodyClasses.refreshing : '', mobileLayout === 'cards' ? 'hidden sm:block' : ''), "data-overflow-end": overflow.edges.end, "data-overflow-start": overflow.edges.start, onScroll: overflow.update, ref: overflow.ref, children: renderReorder((api) => (_jsxs("table", { className: PallasoftClassNameService.merge('w-full border-collapse text-left', layout === 'fixed' ? 'table-fixed' : ''), children: [caption ? _jsx("caption", { className: 'sr-only', children: caption }) : null, _jsx("thead", { className: PallasoftClassNameService.merge('bg-mist/70 text-[0.68rem] font-semibold uppercase tracking-[0.18em] text-ink/45', stickyTop === undefined
|
|
376
|
+
? ''
|
|
377
|
+
: 'relative z-docked bg-surface will-change-transform [&_th]:border-b [&_th]:border-line [&>tr]:bg-mist/70'), ref: stickyHeadRef, children: _jsxs("tr", { children: [reorder ? (_jsx("th", { className: PallasoftClassNameService.merge('w-9 min-w-9 pl-3 pr-0', pins.reorder), scope: 'col', children: reorder.enabled ? (_jsx("span", { className: 'sr-only', children: reorder.columnLabel })) : reorder.disabledReason ? (_jsx("span", { "aria-label": reorder.disabledReason, className: 'inline-flex cursor-help opacity-40', role: 'img', title: reorder.disabledReason, children: _jsx(PsDragGrip, {}) })) : null })) : null, isSelectable ? (_jsx("th", { className: PallasoftClassNameService.merge('w-10 min-w-10 pl-4 pr-0', pins.select), scope: 'col', children: _jsx(PsCheckbox, { isIndeterminate: !isPageSelected && pageIds.some((rowId) => selectedIdSet.has(rowId)), isSelected: isPageSelected, label: 'Select all rows on this page', onChange: togglePage }) })) : null, visibleColumns.map((column, columnIndex) => {
|
|
378
|
+
const align = column.align !== null && column.align !== undefined
|
|
379
|
+
? column.align
|
|
380
|
+
: 'left';
|
|
381
|
+
const isSorted = sort !== null && sort.columnId === column.id;
|
|
382
|
+
return (_jsx("th", { "aria-sort": isSorted
|
|
383
|
+
? sort.direction
|
|
384
|
+
: column.sortValue && isQueryable
|
|
385
|
+
? 'none'
|
|
386
|
+
: undefined, className: PallasoftClassNameService.merge(densityClassNames[density].head, alignClassNames[align], placementOf(column, columnIndex), 'whitespace-nowrap'), scope: 'col', style: columnStyle(column, fittedWidths.get(column.id)), children: column.sortValue && isQueryable ? (_jsx(PsTableSortHeader, { align: align, columnId: column.id, label: column.label, onSort: updateSort, sort: sort })) : (column.label) }, column.id));
|
|
387
|
+
})] }) }), _jsxs("tbody", { className: 'divide-y divide-line', ref: bodyRef, children: [visibleRows.length === 0 && isSearching ? (_jsx("tr", { children: _jsx("td", { className: 'p-0', colSpan: columnCount, children: _jsx(PsTableSearchingState, { label: searchingLabel, skeletonRows: PsTableConfig.searchingSkeletonRows }) }) })) : null, visibleRows.length === 0 && isQueried && !isSearching ? (_jsx("tr", { children: _jsxs("td", { className: 'px-4 py-12 text-center', colSpan: columnCount, children: [_jsx("p", { className: 'text-sm font-semibold text-ink', children: PsTableConfig.noMatchesTitle }), _jsx("p", { className: 'mt-1 text-sm text-ink/55', children: PsTableConfig.noMatchesDescription }), _jsx("button", { className: PallasoftClassNameService.merge(PsTableControlClasses.textButton, 'mt-3 text-primary hover:underline'), onClick: clearFilters, type: 'button', children: "Clear search and filters" })] }) })) : null, grouping
|
|
388
|
+
? groups.map((group, groupIndex) => {
|
|
389
|
+
const isExpanded = isGroupExpanded(group.key, groupIndex);
|
|
390
|
+
return (_jsxs(Fragment, { children: [_jsx("tr", { children: _jsx("td", { className: 'p-0', colSpan: columnCount, children: _jsx(PsTableGroupHeader, { count: group.rows.length, isExpanded: isExpanded, label: grouping.getGroupLabel(group.key, group.rows), onToggle: () => toggleGroup(group.key, isExpanded), summary: grouping.renderGroupSummary
|
|
391
|
+
? grouping.renderGroupSummary(group.rows)
|
|
392
|
+
: undefined }) }) }), isExpanded
|
|
393
|
+
? renderEntries(api, group.rows.map((row, rowIndex) => ({
|
|
394
|
+
motion: groupRowMotion(group.key, rowIndex),
|
|
395
|
+
row,
|
|
396
|
+
})))
|
|
397
|
+
: null] }, `group-${group.key}`));
|
|
398
|
+
})
|
|
399
|
+
: shouldVirtualize
|
|
400
|
+
? [
|
|
401
|
+
_jsx("tr", { "aria-hidden": 'true', children: _jsx("td", { className: 'p-0', colSpan: columnCount, style: { height: virtualTop } }) }, 'virtual-top'),
|
|
402
|
+
...renderEntries(api, virtualItems.map((item) => ({
|
|
403
|
+
row: visibleRows[item.index],
|
|
404
|
+
virtualItem: item,
|
|
405
|
+
}))),
|
|
406
|
+
_jsx("tr", { "aria-hidden": 'true', children: _jsx("td", { className: 'p-0', colSpan: columnCount, style: { height: virtualBottom } }) }, 'virtual-bottom'),
|
|
407
|
+
]
|
|
408
|
+
: renderEntries(api, visibleRows.map((row) => ({ row })))] }), hasFooter ? (_jsx("tfoot", { className: 'border-t-2 border-line bg-mist/50 text-sm font-semibold text-ink', children: _jsxs("tr", { children: [reorder ? _jsx("td", { className: pins.reorder }) : null, isSelectable ? _jsx("td", { className: pins.select }) : null, visibleColumns.map((column, columnIndex) => (_jsx("td", { className: PallasoftClassNameService.merge(densityClassNames[density].cell, placementOf(column, columnIndex), alignClassNames[column.align !== null && column.align !== undefined
|
|
409
|
+
? column.align
|
|
410
|
+
: 'left'], 'whitespace-nowrap'), children: column.footer }, column.id)))] }) })) : null] }))) }), visibleRows.length > 0 && isSearching ? (_jsx(PsTableSearchingState, { className: 'border-t border-line py-3', label: searchingLabel })) : null, showsPagination && paginationPosition !== PsTablePaginationPosition.top
|
|
411
|
+
? renderPagination('')
|
|
412
|
+
: null] }) }) })] }));
|
|
411
413
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PsSkeleton, PsSkeletonGroup } from '@pallasoft/ps-core-components';
|
|
3
|
+
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
4
|
+
import { PsReportColumnAlign } from '../constants/ps-report-column-align';
|
|
5
|
+
import { PsReportTableClasses } from '../constants/ps-report-table-classes';
|
|
6
|
+
import { PsReportTableConfig } from '../constants/ps-report-table-config';
|
|
7
|
+
import { PsReportTableDensity } from '../constants/ps-report-table-density';
|
|
8
|
+
import { PsReportTableService } from '../services/ps-report-table-service';
|
|
9
|
+
export const PsReportTableSkeleton = ({ className, columns, density = PsReportTableDensity.normal, isFramed = true, rows = PsReportTableConfig.skeletonRows, }) => {
|
|
10
|
+
const isEnd = (column) => PsReportTableService.alignOf(column) === PsReportColumnAlign.right;
|
|
11
|
+
const headClass = (column, index) => {
|
|
12
|
+
if (isEnd(column))
|
|
13
|
+
return PsReportTableClasses.skeleton.headEnd;
|
|
14
|
+
return index === 0
|
|
15
|
+
? PsReportTableClasses.skeleton.headFirst
|
|
16
|
+
: PsReportTableClasses.skeleton.headOther;
|
|
17
|
+
};
|
|
18
|
+
const cellClass = (column, rowIndex, index) => isEnd(column)
|
|
19
|
+
? PsReportTableClasses.skeleton.cellEnd
|
|
20
|
+
: PsReportTableClasses.skeleton.cellWidths[(rowIndex + index) % PsReportTableClasses.skeleton.cellWidths.length];
|
|
21
|
+
return (_jsx(PsSkeletonGroup, { className: PallasoftClassNameService.merge(isFramed ? PsReportTableClasses.frame : PsReportTableClasses.skeleton.plain, className), children: _jsxs("table", { className: PsReportTableClasses.table, children: [_jsx("thead", { className: PsReportTableClasses.head, children: _jsx("tr", { children: columns.map((column, index) => (_jsx("th", { className: PsReportTableClasses.headCell[density], style: PsReportTableService.indent(0, density), children: _jsx(PsSkeleton, { className: headClass(column, index) }) }, column.id))) }) }), _jsx("tbody", { className: PsReportTableClasses.body, children: Array.from({ length: rows }, (_, rowIndex) => (_jsx("tr", { children: columns.map((column, index) => (_jsx("td", { className: PsReportTableClasses.cell[density], style: {
|
|
22
|
+
...PsReportTableService.columnStyle(column),
|
|
23
|
+
...PsReportTableService.indent(0, density),
|
|
24
|
+
}, children: _jsx(PsSkeleton, { className: cellClass(column, rowIndex, index) }) }, column.id))) }, rowIndex))) })] }) }));
|
|
25
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
3
|
+
import { PsReportRowKind } from '../constants/ps-report-row-kind';
|
|
4
|
+
import { PsReportRowTone } from '../constants/ps-report-row-tone';
|
|
5
|
+
import { PsReportTableClasses } from '../constants/ps-report-table-classes';
|
|
6
|
+
import { PsReportTableConfig } from '../constants/ps-report-table-config';
|
|
7
|
+
import { PsReportTableDensity } from '../constants/ps-report-table-density';
|
|
8
|
+
import { usePsTableOverflow } from '../hooks/use-ps-table-overflow';
|
|
9
|
+
import { PsReportTableService } from '../services/ps-report-table-service';
|
|
10
|
+
import { PsReportTableSkeleton } from './ps-report-table-skeleton';
|
|
11
|
+
export const PsReportTable = ({ caption, className, columns, density = PsReportTableDensity.normal, emptyState, getLeadSpan, getRowId, getRowKind, getRowLevel, getRowTone, isFirstColumnSticky = false, isFramed = true, isHeaderHidden = false, isHeaderSticky = false, isLoading = false, isRefreshing = false, maxHeight, minWidth, renderLead, rows, }) => {
|
|
12
|
+
const overflow = usePsTableOverflow(String(rows.length));
|
|
13
|
+
if (isLoading)
|
|
14
|
+
return (_jsx(PsReportTableSkeleton, { className: className, columns: columns, density: density, isFramed: isFramed }));
|
|
15
|
+
const kindOf = (row) => getRowKind !== undefined ? getRowKind(row) : PsReportRowKind.data;
|
|
16
|
+
const toneOf = (row) => getRowTone !== undefined ? getRowTone(row) : PsReportRowTone.neutral;
|
|
17
|
+
const levelOf = (row) => PsReportTableService.levelOf(getRowLevel !== undefined ? getRowLevel(row) : undefined);
|
|
18
|
+
const leadOf = (row) => (getLeadSpan !== undefined ? getLeadSpan(row) : 1);
|
|
19
|
+
const renderRow = (row) => {
|
|
20
|
+
const kind = kindOf(row);
|
|
21
|
+
const tone = toneOf(row);
|
|
22
|
+
const level = levelOf(row);
|
|
23
|
+
return (_jsx("tr", { className: PsReportTableClasses.kind[kind], children: PsReportTableService.cells(columns, leadOf(row)).map((plan) => {
|
|
24
|
+
const className = PallasoftClassNameService.merge(PsReportTableService.cellClass(plan.column, plan.index, density, tone, isFirstColumnSticky), plan.index === 0 ? PsReportTableClasses.rowHeader : '');
|
|
25
|
+
const style = {
|
|
26
|
+
...PsReportTableService.columnStyle(plan.column),
|
|
27
|
+
...(plan.index === 0 ? PsReportTableService.indent(level, density) : {}),
|
|
28
|
+
};
|
|
29
|
+
return plan.index === 0 ? (_jsx("th", { className: className, colSpan: plan.colSpan, scope: kind === PsReportRowKind.group && plan.colSpan === columns.length
|
|
30
|
+
? 'colgroup'
|
|
31
|
+
: 'row', style: style, children: plan.colSpan > 1 && renderLead !== undefined
|
|
32
|
+
? renderLead(row)
|
|
33
|
+
: plan.column.render(row) }, plan.column.id)) : (_jsx("td", { className: className, colSpan: plan.colSpan, style: style, children: plan.column.render(row) }, plan.column.id));
|
|
34
|
+
}) }, getRowId(row)));
|
|
35
|
+
};
|
|
36
|
+
const content = (_jsx("div", { className: PallasoftClassNameService.merge(PsReportTableClasses.scroll, maxHeight !== undefined ? PsReportTableClasses.scrollBounded : '', isRefreshing ? PsReportTableClasses.refreshing : ''), "data-overflow-end": overflow.edges.end, "data-overflow-start": overflow.edges.start, onScroll: overflow.update, ref: overflow.ref, style: { maxHeight }, children: _jsxs("table", { className: PsReportTableClasses.table, style: { minWidth }, children: [_jsx("caption", { className: PsReportTableClasses.caption, children: caption }), _jsx("thead", { className: PallasoftClassNameService.merge(PsReportTableClasses.head, isHeaderHidden ? PsReportTableClasses.headHidden : ''), children: _jsx("tr", { children: columns.map((column, index) => (_jsx("th", { className: PsReportTableService.headClass(column, index, density, isHeaderSticky, isFirstColumnSticky), scope: 'col', style: {
|
|
37
|
+
...PsReportTableService.columnStyle(column),
|
|
38
|
+
...PsReportTableService.indent(0, density),
|
|
39
|
+
}, children: column.label }, column.id))) }) }), _jsx("tbody", { className: PsReportTableClasses.body, children: rows.length === 0 ? (_jsx("tr", { children: _jsx("td", { className: PsReportTableClasses.empty, colSpan: columns.length, children: emptyState !== undefined ? emptyState : PsReportTableConfig.emptyLabel }) })) : (rows.map(renderRow)) })] }) }));
|
|
40
|
+
return (_jsx("div", { "aria-busy": isRefreshing, className: PallasoftClassNameService.merge(isFramed ? PsReportTableClasses.frame : PsReportTableClasses.framePlain, className), children: content }));
|
|
41
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { PsReportColumnAlign } from './ps-report-column-align';
|
|
2
|
+
import { PsReportRowKind } from './ps-report-row-kind';
|
|
3
|
+
import { PsReportRowTone } from './ps-report-row-tone';
|
|
4
|
+
import { PsReportTableDensity } from './ps-report-table-density';
|
|
5
|
+
export const PsReportTableClasses = {
|
|
6
|
+
align: {
|
|
7
|
+
[PsReportColumnAlign.center]: 'text-center',
|
|
8
|
+
[PsReportColumnAlign.left]: 'text-left',
|
|
9
|
+
[PsReportColumnAlign.right]: 'text-right tabular-nums',
|
|
10
|
+
},
|
|
11
|
+
body: 'divide-y divide-line/60',
|
|
12
|
+
caption: 'sr-only',
|
|
13
|
+
cell: {
|
|
14
|
+
[PsReportTableDensity.compact]: 'py-1.5 pr-3 text-[0.8125rem] leading-5',
|
|
15
|
+
[PsReportTableDensity.normal]: 'py-3 pr-5 text-sm',
|
|
16
|
+
},
|
|
17
|
+
empty: 'px-4 py-10 text-center text-sm text-ink/55',
|
|
18
|
+
framePlain: 'min-w-0',
|
|
19
|
+
frame: 'min-w-0 overflow-hidden rounded-[1.25rem] border border-line bg-surface',
|
|
20
|
+
head: 'bg-mist/70 text-[0.68rem] font-semibold uppercase tracking-[0.18em] text-ink/45',
|
|
21
|
+
headCell: {
|
|
22
|
+
[PsReportTableDensity.compact]: 'py-2 pr-3 whitespace-nowrap',
|
|
23
|
+
[PsReportTableDensity.normal]: 'py-3.5 pr-5 whitespace-nowrap',
|
|
24
|
+
},
|
|
25
|
+
headHidden: 'sr-only',
|
|
26
|
+
kind: {
|
|
27
|
+
[PsReportRowKind.data]: 'text-ink',
|
|
28
|
+
[PsReportRowKind.group]: 'bg-mist/40 font-semibold text-ink [--ps-table-pin-fill:inset_0_0_0_100vmax_rgb(var(--ps-color-mist-rgb)/0.4)]',
|
|
29
|
+
[PsReportRowKind.subtotal]: 'border-t border-line font-semibold text-ink',
|
|
30
|
+
[PsReportRowKind.total]: 'border-t-2 border-line bg-mist/50 font-semibold text-ink [--ps-table-pin-fill:inset_0_0_0_100vmax_rgb(var(--ps-color-mist-rgb)/0.5)]',
|
|
31
|
+
},
|
|
32
|
+
nowrap: 'whitespace-nowrap',
|
|
33
|
+
refreshing: 'opacity-60',
|
|
34
|
+
rowHeader: '[font-weight:inherit]',
|
|
35
|
+
scroll: 'ps-scrollbar overflow-x-auto overscroll-x-contain',
|
|
36
|
+
scrollBounded: 'overflow-y-auto overscroll-contain',
|
|
37
|
+
skeleton: {
|
|
38
|
+
cellEnd: 'ml-auto w-16',
|
|
39
|
+
cellWidths: ['w-3/4', 'w-1/2'],
|
|
40
|
+
headEnd: 'ml-auto h-2.5 w-12',
|
|
41
|
+
headFirst: 'h-2.5 w-20',
|
|
42
|
+
headOther: 'h-2.5 w-14',
|
|
43
|
+
plain: 'min-w-0 overflow-hidden',
|
|
44
|
+
},
|
|
45
|
+
stickyFirstBody: 'ps-table-pin left-0',
|
|
46
|
+
stickyFirstHead: 'ps-table-pin left-0 top-0 z-[3]',
|
|
47
|
+
stickyHead: 'ps-table-pin top-0 z-[2]',
|
|
48
|
+
table: 'w-full border-collapse text-left',
|
|
49
|
+
tone: {
|
|
50
|
+
[PsReportRowTone.danger]: 'bg-danger-soft/60 text-danger [--ps-table-pin-fill:inset_0_0_0_100vmax_rgb(var(--ps-color-danger-soft-rgb)/0.6)]',
|
|
51
|
+
[PsReportRowTone.muted]: 'text-ink/60',
|
|
52
|
+
[PsReportRowTone.neutral]: '',
|
|
53
|
+
[PsReportRowTone.primary]: 'bg-primary/10 text-primary [--ps-table-pin-fill:inset_0_0_0_100vmax_rgb(var(--ps-color-primary-rgb)/0.1)]',
|
|
54
|
+
},
|
|
55
|
+
truncate: 'truncate',
|
|
56
|
+
};
|
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
import { useLayoutEffect
|
|
1
|
+
import { useLayoutEffect } from 'react';
|
|
2
2
|
import { PsTableStickyConfig } from '../constants/ps-table-sticky-config';
|
|
3
3
|
import { PsTableScrollService } from '../services/ps-table-scroll-service';
|
|
4
4
|
import { PsTableStickyHeadService } from '../services/ps-table-sticky-head-service';
|
|
5
|
-
export const usePsStickyHead = ({ cardRef, headRef, layoutKey, regionRef,
|
|
6
|
-
const [isStuck, setIsStuck] = useState(false);
|
|
5
|
+
export const usePsStickyHead = ({ cardRef, contentRef, headRef, layoutKey, regionRef, stickyTop, }) => {
|
|
7
6
|
useLayoutEffect(() => {
|
|
8
7
|
const region = regionRef.current;
|
|
9
|
-
const sentinel = sentinelRef.current;
|
|
10
8
|
const card = cardRef.current;
|
|
9
|
+
const content = contentRef.current;
|
|
11
10
|
const head = headRef.current;
|
|
12
11
|
const table = head === null ? null : head.parentElement;
|
|
13
12
|
if (stickyTop === undefined ||
|
|
14
13
|
region === null ||
|
|
15
|
-
sentinel === null ||
|
|
16
14
|
card === null ||
|
|
15
|
+
content === null ||
|
|
17
16
|
head === null ||
|
|
18
17
|
table === null)
|
|
19
18
|
return;
|
|
@@ -21,6 +20,7 @@ export const usePsStickyHead = ({ cardRef, headRef, layoutKey, regionRef, sentin
|
|
|
21
20
|
const Timeline = window.ScrollTimeline;
|
|
22
21
|
const isDriven = scroller !== null && Timeline !== undefined;
|
|
23
22
|
let animations = [];
|
|
23
|
+
let measured = null;
|
|
24
24
|
let frame = 0;
|
|
25
25
|
const headTravel = () => Math.max(table.offsetHeight - head.offsetHeight, 0);
|
|
26
26
|
const drive = () => {
|
|
@@ -30,10 +30,19 @@ export const usePsStickyHead = ({ cardRef, headRef, layoutKey, regionRef, sentin
|
|
|
30
30
|
Number.parseFloat(getComputedStyle(region).top) +
|
|
31
31
|
region.offsetHeight +
|
|
32
32
|
PsTableStickyConfig.cardGapPx;
|
|
33
|
-
const
|
|
33
|
+
const inputs = {
|
|
34
|
+
cardPin: content.getBoundingClientRect().top + scroller.scrollTop - pinnedLine,
|
|
35
|
+
cardTravel: card.offsetHeight,
|
|
36
|
+
headPin: table.getBoundingClientRect().top + scroller.scrollTop - pinnedLine,
|
|
37
|
+
headTravel: headTravel(),
|
|
38
|
+
scrollRange: scroller.scrollHeight - scroller.clientHeight,
|
|
39
|
+
};
|
|
40
|
+
if (PsTableStickyHeadService.sameInputs(measured, inputs))
|
|
41
|
+
return;
|
|
42
|
+
measured = inputs;
|
|
34
43
|
const timeline = new Timeline({ axis: 'block', source: scroller });
|
|
35
|
-
const headFrames = PsTableStickyHeadService.keyframes(
|
|
36
|
-
const cardFrames = PsTableStickyHeadService.keyframes(
|
|
44
|
+
const headFrames = PsTableStickyHeadService.keyframes(inputs.headPin, inputs.headTravel, inputs.scrollRange);
|
|
45
|
+
const cardFrames = PsTableStickyHeadService.keyframes(inputs.cardPin, inputs.cardTravel, inputs.scrollRange);
|
|
37
46
|
animations.forEach((animation) => animation.cancel());
|
|
38
47
|
animations = [
|
|
39
48
|
head.animate(headFrames.map((keyframe) => ({
|
|
@@ -41,30 +50,32 @@ export const usePsStickyHead = ({ cardRef, headRef, layoutKey, regionRef, sentin
|
|
|
41
50
|
transform: `translateY(${keyframe.shift}px)`,
|
|
42
51
|
})), { fill: 'both', timeline }),
|
|
43
52
|
card.animate(cardFrames.map((keyframe) => ({
|
|
44
|
-
clipPath: PsTableStickyHeadService.clip(keyframe.shift),
|
|
45
53
|
offset: keyframe.offset,
|
|
54
|
+
transform: `translateY(${keyframe.shift}px)`,
|
|
55
|
+
})), { fill: 'both', timeline }),
|
|
56
|
+
content.animate(cardFrames.map((keyframe) => ({
|
|
57
|
+
offset: keyframe.offset,
|
|
58
|
+
transform: `translateY(${-keyframe.shift}px)`,
|
|
46
59
|
})), { fill: 'both', timeline }),
|
|
47
60
|
];
|
|
48
61
|
};
|
|
49
62
|
const follow = () => {
|
|
63
|
+
frame = 0;
|
|
50
64
|
const pinnedLine = region.getBoundingClientRect().bottom + PsTableStickyConfig.cardGapPx;
|
|
51
65
|
head.style.transform = `translateY(${PsTableStickyHeadService.offset(pinnedLine, table.getBoundingClientRect().top, table.offsetHeight, head.offsetHeight)}px)`;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
frame = 0;
|
|
56
|
-
setIsStuck(sentinel.getBoundingClientRect().top < region.getBoundingClientRect().top - 1);
|
|
57
|
-
if (!isDriven)
|
|
58
|
-
follow();
|
|
66
|
+
const cut = PsTableStickyHeadService.offset(pinnedLine, content.getBoundingClientRect().top, card.offsetHeight, 0);
|
|
67
|
+
card.style.transform = `translateY(${cut}px)`;
|
|
68
|
+
content.style.transform = `translateY(${-cut}px)`;
|
|
59
69
|
};
|
|
60
70
|
const schedule = () => {
|
|
61
71
|
if (frame === 0)
|
|
62
|
-
frame = window.requestAnimationFrame(
|
|
72
|
+
frame = window.requestAnimationFrame(follow);
|
|
63
73
|
};
|
|
64
74
|
const measure = () => {
|
|
65
75
|
if (isDriven)
|
|
66
76
|
drive();
|
|
67
|
-
|
|
77
|
+
else
|
|
78
|
+
schedule();
|
|
68
79
|
};
|
|
69
80
|
const observer = new ResizeObserver(measure);
|
|
70
81
|
measure();
|
|
@@ -72,18 +83,19 @@ export const usePsStickyHead = ({ cardRef, headRef, layoutKey, regionRef, sentin
|
|
|
72
83
|
observer.observe(card);
|
|
73
84
|
if (scroller !== null)
|
|
74
85
|
Array.from(scroller.children).forEach((child) => observer.observe(child));
|
|
75
|
-
window.addEventListener('scroll', schedule, { capture: true, passive: true });
|
|
76
86
|
window.addEventListener('resize', measure);
|
|
87
|
+
if (!isDriven)
|
|
88
|
+
window.addEventListener('scroll', schedule, { capture: true, passive: true });
|
|
77
89
|
return () => {
|
|
78
90
|
observer.disconnect();
|
|
79
91
|
animations.forEach((animation) => animation.cancel());
|
|
80
92
|
head.style.transform = '';
|
|
81
|
-
card.style.
|
|
93
|
+
card.style.transform = '';
|
|
94
|
+
content.style.transform = '';
|
|
82
95
|
window.removeEventListener('scroll', schedule, { capture: true });
|
|
83
96
|
window.removeEventListener('resize', measure);
|
|
84
97
|
if (frame !== 0)
|
|
85
98
|
window.cancelAnimationFrame(frame);
|
|
86
99
|
};
|
|
87
|
-
}, [cardRef, headRef, layoutKey, regionRef,
|
|
88
|
-
return isStuck;
|
|
100
|
+
}, [cardRef, contentRef, headRef, layoutKey, regionRef, stickyTop]);
|
|
89
101
|
};
|
package/dist/esm/index.js
CHANGED
|
@@ -64,3 +64,11 @@ export * from './interfaces/ps-server-rows-result';
|
|
|
64
64
|
export * from './interfaces/ps-table-cursor-paging';
|
|
65
65
|
export * from './interfaces/ps-table-select-all-matching';
|
|
66
66
|
export * from './services/ps-server-rows-service';
|
|
67
|
+
export * from './components/ps-report-table';
|
|
68
|
+
export * from './components/ps-report-table-skeleton';
|
|
69
|
+
export * from './constants/ps-report-column-align';
|
|
70
|
+
export * from './constants/ps-report-row-kind';
|
|
71
|
+
export * from './constants/ps-report-row-tone';
|
|
72
|
+
export * from './constants/ps-report-table-density';
|
|
73
|
+
export * from './interfaces/ps-report-column';
|
|
74
|
+
export * from './services/ps-report-table-service';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { PsReportColumnAlign } from '../constants/ps-report-column-align';
|
|
2
|
+
import { PsReportTableClasses } from '../constants/ps-report-table-classes';
|
|
3
|
+
import { PsReportTableConfig } from '../constants/ps-report-table-config';
|
|
4
|
+
import { PsTablePinClasses } from '../constants/ps-table-pin-classes';
|
|
5
|
+
export class PsReportTableService {
|
|
6
|
+
static cells(columns, leadSpan) {
|
|
7
|
+
const span = PsReportTableService.span(columns.length, leadSpan);
|
|
8
|
+
const plan = [{ colSpan: span, column: columns[0], index: 0 }];
|
|
9
|
+
for (let index = span; index < columns.length; index += 1)
|
|
10
|
+
plan.push({ colSpan: 1, column: columns[index], index });
|
|
11
|
+
return plan;
|
|
12
|
+
}
|
|
13
|
+
static span(count, leadSpan) {
|
|
14
|
+
return Math.min(Math.max(leadSpan, 1), count);
|
|
15
|
+
}
|
|
16
|
+
static indent(level, density) {
|
|
17
|
+
const base = PsReportTableConfig.baseIndentRem[density];
|
|
18
|
+
return { paddingLeft: `${base + level * PsReportTableConfig.indentRem}rem` };
|
|
19
|
+
}
|
|
20
|
+
static columnStyle(column) {
|
|
21
|
+
return {
|
|
22
|
+
maxWidth: column.maxWidth,
|
|
23
|
+
minWidth: column.minWidth,
|
|
24
|
+
width: column.width,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
static levelOf(level) {
|
|
28
|
+
return level !== undefined && level > 0 ? level : 0;
|
|
29
|
+
}
|
|
30
|
+
static headClass(column, index, density, isHeaderSticky, isFirstColumnSticky) {
|
|
31
|
+
const isPinnedFirst = index === 0 && isFirstColumnSticky;
|
|
32
|
+
const pin = PsReportTableService.headPin(isHeaderSticky, isPinnedFirst);
|
|
33
|
+
return [
|
|
34
|
+
PsReportTableClasses.headCell[density],
|
|
35
|
+
PsReportTableClasses.align[PsReportTableService.alignOf(column)],
|
|
36
|
+
pin,
|
|
37
|
+
isPinnedFirst ? PsTablePinClasses.edge : '',
|
|
38
|
+
]
|
|
39
|
+
.filter((name) => name !== '')
|
|
40
|
+
.join(' ');
|
|
41
|
+
}
|
|
42
|
+
static headPin(isHeaderSticky, isPinnedFirst) {
|
|
43
|
+
if (isHeaderSticky && isPinnedFirst)
|
|
44
|
+
return PsReportTableClasses.stickyFirstHead;
|
|
45
|
+
if (isHeaderSticky)
|
|
46
|
+
return PsReportTableClasses.stickyHead;
|
|
47
|
+
if (isPinnedFirst)
|
|
48
|
+
return PsReportTableClasses.stickyFirstBody;
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
static cellClass(column, index, density, tone, isFirstColumnSticky) {
|
|
52
|
+
const isPinnedFirst = index === 0 && isFirstColumnSticky;
|
|
53
|
+
return [
|
|
54
|
+
PsReportTableClasses.cell[density],
|
|
55
|
+
PsReportTableClasses.align[PsReportTableService.alignOf(column)],
|
|
56
|
+
PsReportTableClasses.tone[tone],
|
|
57
|
+
column.wrap === true ? '' : PsReportTableClasses.nowrap,
|
|
58
|
+
column.maxWidth !== undefined ? PsReportTableClasses.truncate : '',
|
|
59
|
+
isPinnedFirst ? `${PsReportTableClasses.stickyFirstBody} ${PsTablePinClasses.edge}` : '',
|
|
60
|
+
]
|
|
61
|
+
.filter((name) => name !== '')
|
|
62
|
+
.join(' ');
|
|
63
|
+
}
|
|
64
|
+
static alignOf(column) {
|
|
65
|
+
return column.align !== undefined ? column.align : PsReportColumnAlign.left;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -3,6 +3,15 @@ export class PsTableStickyHeadService {
|
|
|
3
3
|
static offset(pinnedBottom, tableTop, tableHeight, headHeight) {
|
|
4
4
|
return Math.min(Math.max(pinnedBottom - tableTop, 0), Math.max(tableHeight - headHeight, 0));
|
|
5
5
|
}
|
|
6
|
+
static sameInputs(previous, next) {
|
|
7
|
+
if (previous === null)
|
|
8
|
+
return false;
|
|
9
|
+
return (PsTableStickyHeadService.close(previous.headPin, next.headPin) &&
|
|
10
|
+
PsTableStickyHeadService.close(previous.headTravel, next.headTravel) &&
|
|
11
|
+
PsTableStickyHeadService.close(previous.cardPin, next.cardPin) &&
|
|
12
|
+
PsTableStickyHeadService.close(previous.cardTravel, next.cardTravel) &&
|
|
13
|
+
PsTableStickyHeadService.close(previous.scrollRange, next.scrollRange));
|
|
14
|
+
}
|
|
6
15
|
static keyframes(pinScroll, maxShift, scrollRange) {
|
|
7
16
|
if (scrollRange <= 0)
|
|
8
17
|
return [];
|
|
@@ -12,9 +21,8 @@ export class PsTableStickyHeadService {
|
|
|
12
21
|
shift: shiftAt(scroll),
|
|
13
22
|
}));
|
|
14
23
|
}
|
|
15
|
-
static
|
|
16
|
-
|
|
17
|
-
return `inset(${shift}px 0 0 0 round ${radius} ${radius} 0 0)`;
|
|
24
|
+
static close(first, second) {
|
|
25
|
+
return Math.abs(first - second) <= PsTableStickyConfig.inputTolerancePx;
|
|
18
26
|
}
|
|
19
27
|
static stops(scrolls, scrollRange) {
|
|
20
28
|
const inside = scrolls.filter((scroll) => scroll >= 0 && scroll <= scrollRange);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { PsStickyHeadRefs } from '../interfaces/ps-sticky-head-refs';
|
|
2
|
-
export declare const usePsStickyHead: ({ cardRef, headRef, layoutKey, regionRef,
|
|
2
|
+
export declare const usePsStickyHead: ({ cardRef, contentRef, headRef, layoutKey, regionRef, stickyTop, }: PsStickyHeadRefs) => void;
|