@pallasoft/ps-table-components 1.27.1 → 1.28.1
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.d.ts +2 -1
- package/dist/components/ps-data-table.js +15 -44
- package/dist/components/ps-table-toolbar.js +1 -1
- package/dist/constants/ps-table-sticky-config.d.ts +3 -0
- package/dist/constants/ps-table-sticky-config.js +6 -0
- package/dist/esm/components/ps-data-table.js +16 -45
- package/dist/esm/components/ps-table-toolbar.js +1 -1
- package/dist/esm/constants/ps-table-sticky-config.js +3 -0
- package/dist/esm/hooks/use-ps-sticky-head.js +98 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/interfaces/ps-scroll-timeline-options.js +1 -0
- package/dist/esm/interfaces/ps-scroll-timeline-window.js +1 -0
- package/dist/esm/interfaces/ps-sticky-head-refs.js +1 -0
- package/dist/esm/interfaces/ps-table-sticky-backdrop-keyframe.js +1 -0
- package/dist/esm/interfaces/ps-table-sticky-head-keyframe.js +1 -0
- package/dist/esm/services/ps-table-sticky-head-service.js +24 -0
- package/dist/hooks/use-ps-sticky-head.d.ts +2 -0
- package/dist/hooks/use-ps-sticky-head.js +102 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/interfaces/ps-scroll-timeline-options.d.ts +4 -0
- package/dist/interfaces/ps-scroll-timeline-options.js +2 -0
- package/dist/interfaces/ps-scroll-timeline-window.d.ts +4 -0
- package/dist/interfaces/ps-scroll-timeline-window.js +2 -0
- package/dist/interfaces/ps-sticky-head-refs.d.ts +9 -0
- package/dist/interfaces/ps-sticky-head-refs.js +2 -0
- package/dist/interfaces/ps-table-sticky-backdrop-keyframe.d.ts +4 -0
- package/dist/interfaces/ps-table-sticky-backdrop-keyframe.js +2 -0
- package/dist/interfaces/ps-table-sticky-head-keyframe.d.ts +4 -0
- package/dist/interfaces/ps-table-sticky-head-keyframe.js +2 -0
- package/dist/services/ps-table-sticky-head-service.d.ts +8 -0
- package/dist/services/ps-table-sticky-head-service.js +28 -0
- package/package.json +1 -1
|
@@ -43,8 +43,9 @@ export interface PsDataTableProps<RowData> {
|
|
|
43
43
|
searchPlaceholder?: string;
|
|
44
44
|
selectAllMatching?: PsTableSelectAllMatching;
|
|
45
45
|
stickyClassName?: string;
|
|
46
|
+
stickyLeading?: ReactNode;
|
|
46
47
|
stickyTop?: string;
|
|
47
48
|
toolbarActions?: ReactNode;
|
|
48
49
|
totalRows?: number;
|
|
49
50
|
}
|
|
50
|
-
export declare const PsDataTable: <RowData>({ caption, className, clearSelectionKey, columns, cursorPaging, defaultSort, density, expandedRowIds, mobileLayout, getBulkActions, getRowId, getRowLabel, getSearchText, grouping, isLoading, isPending, isRefreshing, layout, onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, paginationPosition, pinFirstColumn, query, renderExpandedRow, reorder, rows, searchingLabel, searchPlaceholder, selectAllMatching, stickyClassName, stickyTop, toolbarActions, totalRows, }: PsDataTableProps<RowData>) => import("react").JSX.Element;
|
|
51
|
+
export declare const PsDataTable: <RowData>({ caption, className, clearSelectionKey, columns, cursorPaging, defaultSort, density, expandedRowIds, mobileLayout, getBulkActions, getRowId, getRowLabel, getSearchText, grouping, isLoading, isPending, isRefreshing, layout, onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, paginationPosition, pinFirstColumn, query, renderExpandedRow, reorder, rows, searchingLabel, searchPlaceholder, selectAllMatching, stickyClassName, stickyLeading, stickyTop, toolbarActions, totalRows, }: PsDataTableProps<RowData>) => import("react").JSX.Element;
|
|
@@ -48,6 +48,7 @@ const table_breakpoint_1 = require("../constants/table-breakpoint");
|
|
|
48
48
|
const table_config_1 = require("../constants/table-config");
|
|
49
49
|
const use_ps_incremental_count_1 = require("../hooks/use-ps-incremental-count");
|
|
50
50
|
const use_ps_scroll_anchor_1 = require("../hooks/use-ps-scroll-anchor");
|
|
51
|
+
const use_ps_sticky_head_1 = require("../hooks/use-ps-sticky-head");
|
|
51
52
|
const use_ps_table_overflow_1 = require("../hooks/use-ps-table-overflow");
|
|
52
53
|
const use_ps_table_stream_status_1 = require("../hooks/use-ps-table-stream-status");
|
|
53
54
|
const ps_table_cell_tooltip_service_1 = require("../services/ps-table-cell-tooltip-service");
|
|
@@ -87,7 +88,7 @@ const alignClassNames = {
|
|
|
87
88
|
left: 'text-left',
|
|
88
89
|
right: 'text-right tabular-nums',
|
|
89
90
|
};
|
|
90
|
-
const PsDataTable = ({ caption, className, clearSelectionKey, columns, cursorPaging, defaultSort, density = 'normal', expandedRowIds, mobileLayout = 'cards', getBulkActions, getRowId, getRowLabel, getSearchText, grouping, isLoading = false, isPending = false, isRefreshing = false, layout = 'auto', onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, paginationPosition = ps_table_pagination_position_1.PsTablePaginationPosition.bottom, pinFirstColumn = true, query, renderExpandedRow, reorder, rows, searchingLabel = table_config_1.PsTableConfig.searchingLabel, searchPlaceholder = table_config_1.PsTableConfig.searchPlaceholder, selectAllMatching, stickyClassName, stickyTop, toolbarActions, totalRows, }) => {
|
|
91
|
+
const PsDataTable = ({ caption, className, clearSelectionKey, columns, cursorPaging, defaultSort, density = 'normal', expandedRowIds, mobileLayout = 'cards', getBulkActions, getRowId, getRowLabel, getSearchText, grouping, isLoading = false, isPending = false, isRefreshing = false, layout = 'auto', onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, paginationPosition = ps_table_pagination_position_1.PsTablePaginationPosition.bottom, pinFirstColumn = true, query, renderExpandedRow, reorder, rows, searchingLabel = table_config_1.PsTableConfig.searchingLabel, searchPlaceholder = table_config_1.PsTableConfig.searchPlaceholder, selectAllMatching, stickyClassName, stickyLeading, stickyTop, toolbarActions, totalRows, }) => {
|
|
91
92
|
const containerRef = (0, react_1.useRef)(null);
|
|
92
93
|
const [uncontrolledPage, setUncontrolledPage] = (0, react_1.useState)(1);
|
|
93
94
|
const [uncontrolledQuery, setUncontrolledQuery] = (0, react_1.useState)(() => ps_table_query_service_1.PsTableQueryService.empty(defaultSort ? defaultSort : null));
|
|
@@ -98,47 +99,13 @@ const PsDataTable = ({ caption, className, clearSelectionKey, columns, cursorPag
|
|
|
98
99
|
const [seenClearKey, setSeenClearKey] = (0, react_1.useState)(clearSelectionKey);
|
|
99
100
|
const stickyRegionRef = (0, react_1.useRef)(null);
|
|
100
101
|
const stickySentinelRef = (0, react_1.useRef)(null);
|
|
101
|
-
const
|
|
102
|
-
const
|
|
102
|
+
const stickyBackdropRef = (0, react_1.useRef)(null);
|
|
103
|
+
const stickyHeadRef = (0, react_1.useRef)(null);
|
|
103
104
|
const currentQuery = query ? query : uncontrolledQuery;
|
|
104
105
|
if (seenClearKey !== clearSelectionKey) {
|
|
105
106
|
setSeenClearKey(clearSelectionKey);
|
|
106
107
|
setSelectedIds([]);
|
|
107
108
|
}
|
|
108
|
-
(0, react_1.useLayoutEffect)(() => {
|
|
109
|
-
const region = stickyRegionRef.current;
|
|
110
|
-
if (stickyTop === undefined || region === null)
|
|
111
|
-
return;
|
|
112
|
-
const measure = () => setStickyHeight(region.offsetHeight);
|
|
113
|
-
const observer = new ResizeObserver(measure);
|
|
114
|
-
measure();
|
|
115
|
-
observer.observe(region);
|
|
116
|
-
return () => observer.disconnect();
|
|
117
|
-
}, [stickyTop]);
|
|
118
|
-
(0, react_1.useEffect)(() => {
|
|
119
|
-
const region = stickyRegionRef.current;
|
|
120
|
-
const sentinel = stickySentinelRef.current;
|
|
121
|
-
if (stickyTop === undefined || region === null || sentinel === null)
|
|
122
|
-
return;
|
|
123
|
-
let frame = 0;
|
|
124
|
-
const check = () => {
|
|
125
|
-
frame = 0;
|
|
126
|
-
setIsStuck(sentinel.getBoundingClientRect().top < Number.parseFloat(getComputedStyle(region).top));
|
|
127
|
-
};
|
|
128
|
-
const schedule = () => {
|
|
129
|
-
if (frame === 0)
|
|
130
|
-
frame = window.requestAnimationFrame(check);
|
|
131
|
-
};
|
|
132
|
-
check();
|
|
133
|
-
window.addEventListener('scroll', schedule, { capture: true, passive: true });
|
|
134
|
-
window.addEventListener('resize', schedule);
|
|
135
|
-
return () => {
|
|
136
|
-
window.removeEventListener('scroll', schedule, { capture: true });
|
|
137
|
-
window.removeEventListener('resize', schedule);
|
|
138
|
-
if (frame !== 0)
|
|
139
|
-
window.cancelAnimationFrame(frame);
|
|
140
|
-
};
|
|
141
|
-
}, [stickyTop]);
|
|
142
109
|
const { filters, hiddenColumnIds, search, sort } = currentQuery;
|
|
143
110
|
const visibleColumns = columns.filter((column) => !hiddenColumnIds.includes(column.id));
|
|
144
111
|
const fittedWidths = (0, react_1.useMemo)(() => new Map(columns
|
|
@@ -161,6 +128,14 @@ const PsDataTable = ({ caption, className, clearSelectionKey, columns, cursorPag
|
|
|
161
128
|
const pins = ps_table_pin_service_1.PsTablePinService.leading(pinFirstColumn, Boolean(reorder), isSelectable);
|
|
162
129
|
const placementOf = (column, columnIndex) => ps_design_system_1.PallasoftClassNameService.merge(column.hideBelow === undefined ? '' : table_breakpoint_1.PsTableHideBelowClasses[column.hideBelow], columnIndex === 0 ? pins.first : '', ps_table_pin_service_1.PsTablePinService.end(column.pinEnd === true));
|
|
163
130
|
const overflow = (0, use_ps_table_overflow_1.usePsTableOverflow)(`${visibleColumns.map((column) => column.id).join(',')}:${rows.length}`);
|
|
131
|
+
const isStuck = (0, use_ps_sticky_head_1.usePsStickyHead)({
|
|
132
|
+
backdropRef: stickyBackdropRef,
|
|
133
|
+
headRef: stickyHeadRef,
|
|
134
|
+
layoutKey: `${visibleColumns.map((column) => column.id).join(',')}:${rows.length}:${isLoading}`,
|
|
135
|
+
regionRef: stickyRegionRef,
|
|
136
|
+
sentinelRef: stickySentinelRef,
|
|
137
|
+
stickyTop,
|
|
138
|
+
});
|
|
164
139
|
const deferredSearch = (0, react_1.useDeferredValue)(search);
|
|
165
140
|
const queriedRows = (0, react_1.useMemo)(() => isServerPaginated
|
|
166
141
|
? rows
|
|
@@ -421,7 +396,7 @@ const PsDataTable = ({ caption, className, clearSelectionKey, columns, cursorPag
|
|
|
421
396
|
updateQuery({ search: next });
|
|
422
397
|
resetPage();
|
|
423
398
|
}, rows: rows, search: search, searchPlaceholder: searchPlaceholder })) : null, banner] }));
|
|
424
|
-
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: [stickyTop === undefined ? (topContent) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { "aria-hidden": 'true', className: '-mb-3 h-0', ref: stickySentinelRef }), (0, jsx_runtime_1.
|
|
399
|
+
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: [stickyTop === undefined ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [stickyLeading, topContent] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { "aria-hidden": 'true', className: '-mb-3 h-0', ref: stickySentinelRef }), (0, jsx_runtime_1.jsxs)("div", { className: ps_design_system_1.PallasoftClassNameService.merge('sticky z-sticky flex flex-col gap-3', stickyClassName), "data-stuck": isStuck, ref: stickyRegionRef, style: { top: stickyTop }, children: [(0, jsx_runtime_1.jsx)("div", { "aria-hidden": 'true', className: 'ps-glass-strong pointer-events-none absolute -inset-x-3 -bottom-3 rounded-b-ps-card border-t-0 opacity-0', ref: stickyBackdropRef, style: { top: `calc(-1 * ${stickyTop})` } }), (0, jsx_runtime_1.jsxs)("div", { className: 'relative flex flex-col gap-3', children: [stickyLeading, topContent] })] })] })), (0, jsx_runtime_1.jsxs)("div", { "aria-busy": isRefreshing, className: 'min-w-0 overflow-hidden rounded-[1.25rem] border border-line bg-surface', children: [showsPagination &&
|
|
425
400
|
paginationPosition !== ps_table_pagination_position_1.PsTablePaginationPosition.bottom &&
|
|
426
401
|
rowCount > ps_table_pagination_config_1.PsTablePaginationConfig.topMinRows
|
|
427
402
|
? renderPagination('border-b border-t-0')
|
|
@@ -432,13 +407,9 @@ const PsDataTable = ({ caption, className, clearSelectionKey, columns, cursorPag
|
|
|
432
407
|
? grouping.renderGroupSummary(group.rows)
|
|
433
408
|
: undefined }), (0, jsx_runtime_1.jsx)(ps_motion_components_1.PsCollapse, { isOpen: isExpanded && !closingGroupKeys.includes(group.key), children: renderCardList(group.rows, null) })] }, `group-${group.key}`));
|
|
434
409
|
})
|
|
435
|
-
: renderCardList(shouldVirtualize ? visibleRows.slice(0, cards.count) : visibleRows, visibleRows.length === 0 && isSearching ? ((0, jsx_runtime_1.jsx)(ps_table_searching_state_1.PsTableSearchingState, { label: searchingLabel, skeletonRows: table_config_1.PsTableConfig.searchingSkeletonRows })) : visibleRows.length === 0 && isQueried ? ((0, jsx_runtime_1.jsxs)("div", { className: 'px-4 py-10 text-center', 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)("button", { className: ps_design_system_1.PallasoftClassNameService.merge(ps_table_control_classes_1.PsTableControlClasses.textButton, 'mt-2 text-primary'), onClick: clearFilters, type: 'button', children: "Clear search and filters" })] })) : null), !grouping && shouldVirtualize && cards.count < visibleRows.length ? ((0, jsx_runtime_1.jsx)("div", { "aria-hidden": 'true', className: 'h-px', ref: cards.sentinelRef })) : null] })) : null, (0, jsx_runtime_1.jsx)("div", { className: ps_design_system_1.PallasoftClassNameService.merge(stickyTop === undefined
|
|
436
|
-
? 'ps-scrollbar overflow-x-auto overflow-y-hidden overscroll-x-contain'
|
|
437
|
-
: 'overflow-x-clip', 'transition-opacity duration-150', isRefreshing ? 'opacity-60' : '', 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) => ((0, jsx_runtime_1.jsxs)("table", { className: ps_design_system_1.PallasoftClassNameService.merge('w-full border-collapse text-left', layout === 'fixed' ? 'table-fixed' : ''), children: [caption ? (0, jsx_runtime_1.jsx)("caption", { className: 'sr-only', children: caption }) : null, (0, jsx_runtime_1.jsx)("thead", { className: ps_design_system_1.PallasoftClassNameService.merge('bg-mist/70 text-[0.68rem] font-semibold uppercase tracking-[0.18em] text-ink/45', stickyTop === undefined
|
|
410
|
+
: renderCardList(shouldVirtualize ? visibleRows.slice(0, cards.count) : visibleRows, visibleRows.length === 0 && isSearching ? ((0, jsx_runtime_1.jsx)(ps_table_searching_state_1.PsTableSearchingState, { label: searchingLabel, skeletonRows: table_config_1.PsTableConfig.searchingSkeletonRows })) : visibleRows.length === 0 && isQueried ? ((0, jsx_runtime_1.jsxs)("div", { className: 'px-4 py-10 text-center', 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)("button", { className: ps_design_system_1.PallasoftClassNameService.merge(ps_table_control_classes_1.PsTableControlClasses.textButton, 'mt-2 text-primary'), onClick: clearFilters, type: 'button', children: "Clear search and filters" })] })) : null), !grouping && shouldVirtualize && cards.count < visibleRows.length ? ((0, jsx_runtime_1.jsx)("div", { "aria-hidden": 'true', className: 'h-px', ref: cards.sentinelRef })) : null] })) : null, (0, jsx_runtime_1.jsx)("div", { className: ps_design_system_1.PallasoftClassNameService.merge('ps-scrollbar overflow-x-auto overflow-y-hidden overscroll-x-contain', 'transition-opacity duration-150', isRefreshing ? 'opacity-60' : '', 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) => ((0, jsx_runtime_1.jsxs)("table", { className: ps_design_system_1.PallasoftClassNameService.merge('w-full border-collapse text-left', layout === 'fixed' ? 'table-fixed' : ''), children: [caption ? (0, jsx_runtime_1.jsx)("caption", { className: 'sr-only', children: caption }) : null, (0, jsx_runtime_1.jsx)("thead", { className: ps_design_system_1.PallasoftClassNameService.merge('bg-mist/70 text-[0.68rem] font-semibold uppercase tracking-[0.18em] text-ink/45', stickyTop === undefined
|
|
438
411
|
? ''
|
|
439
|
-
: '
|
|
440
|
-
? undefined
|
|
441
|
-
: { top: `calc(${stickyTop} + ${stickyHeight}px)` }, children: (0, jsx_runtime_1.jsxs)("tr", { children: [reorder ? ((0, jsx_runtime_1.jsx)("th", { className: ps_design_system_1.PallasoftClassNameService.merge('w-9 min-w-9 pl-3 pr-0', pins.reorder), scope: 'col', children: reorder.enabled ? ((0, jsx_runtime_1.jsx)("span", { className: 'sr-only', children: reorder.columnLabel })) : reorder.disabledReason ? ((0, jsx_runtime_1.jsx)("span", { "aria-label": reorder.disabledReason, className: 'inline-flex cursor-help opacity-40', role: 'img', title: reorder.disabledReason, children: (0, jsx_runtime_1.jsx)(ps_core_components_1.PsDragGrip, {}) })) : null })) : null, isSelectable ? ((0, jsx_runtime_1.jsx)("th", { className: ps_design_system_1.PallasoftClassNameService.merge('w-10 min-w-10 pl-4 pr-0', pins.select), scope: 'col', children: (0, jsx_runtime_1.jsx)(ps_input_components_1.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) => {
|
|
412
|
+
: 'relative z-docked bg-surface will-change-transform [&_th]:border-b [&_th]:border-line [&>tr]:bg-mist/70'), ref: stickyHeadRef, children: (0, jsx_runtime_1.jsxs)("tr", { children: [reorder ? ((0, jsx_runtime_1.jsx)("th", { className: ps_design_system_1.PallasoftClassNameService.merge('w-9 min-w-9 pl-3 pr-0', pins.reorder), scope: 'col', children: reorder.enabled ? ((0, jsx_runtime_1.jsx)("span", { className: 'sr-only', children: reorder.columnLabel })) : reorder.disabledReason ? ((0, jsx_runtime_1.jsx)("span", { "aria-label": reorder.disabledReason, className: 'inline-flex cursor-help opacity-40', role: 'img', title: reorder.disabledReason, children: (0, jsx_runtime_1.jsx)(ps_core_components_1.PsDragGrip, {}) })) : null })) : null, isSelectable ? ((0, jsx_runtime_1.jsx)("th", { className: ps_design_system_1.PallasoftClassNameService.merge('w-10 min-w-10 pl-4 pr-0', pins.select), scope: 'col', children: (0, jsx_runtime_1.jsx)(ps_input_components_1.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) => {
|
|
442
413
|
const align = column.align !== null && column.align !== undefined ? column.align : 'left';
|
|
443
414
|
const isSorted = sort !== null && sort.columnId === column.id;
|
|
444
415
|
return ((0, jsx_runtime_1.jsx)("th", { "aria-sort": isSorted
|
|
@@ -13,6 +13,6 @@ const PsTableToolbar = ({ actions, columns, filters, isSearchable, onClearFilter
|
|
|
13
13
|
const filterableColumns = columns.filter((column) => column.filter);
|
|
14
14
|
const zIndex = (0, ps_core_components_1.usePsLayerZIndex)(ps_design_system_1.PallasoftZIndex.Dropdown);
|
|
15
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 basis-full sm:min-w-[12rem] sm:flex-1 sm:basis-auto 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 ps-control rounded-ps-control pl-10 text-sm', onChange: (event) => onSearchChange(event.target.value), placeholder: searchPlaceholder, type: 'search', value: search })] })) : null, (0, jsx_runtime_1.jsxs)("div", { className: '
|
|
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 basis-full sm:min-w-[12rem] sm:flex-1 sm:basis-auto 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 ps-control rounded-ps-control pl-10 text-sm', onChange: (event) => onSearchChange(event.target.value), placeholder: searchPlaceholder, type: 'search', value: search })] })) : null, (0, jsx_runtime_1.jsxs)("div", { className: 'flex max-w-full shrink-0 grow flex-wrap items-center gap-2', children: [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: ps_table_control_classes_1.PsTableControlClasses.toolbarButton, 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", { className: 'sr-only sm:not-sr-only', 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-hidden 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_design_system_1.PallasoftClassNameService.merge(ps_table_control_classes_1.PsTableControlClasses.textButton, '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_table_control_classes_1.PsTableControlClasses.textButton, '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 shrink-0 items-center gap-2', children: actions }) : null] })] }));
|
|
17
17
|
};
|
|
18
18
|
exports.PsTableToolbar = PsTableToolbar;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Fragment, Suspense, lazy, useCallback, useDeferredValue, useEffect,
|
|
2
|
+
import { Fragment, Suspense, lazy, useCallback, useDeferredValue, useEffect, useMemo, useRef, useState, } from 'react';
|
|
3
3
|
import { PsDragGrip, PsSkeletonTable } from '@pallasoft/ps-core-components';
|
|
4
4
|
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
5
5
|
import { PsCheckbox } from '@pallasoft/ps-input-components';
|
|
@@ -12,6 +12,7 @@ import { PsTableHideBelowClasses } from '../constants/table-breakpoint';
|
|
|
12
12
|
import { PsTableConfig } from '../constants/table-config';
|
|
13
13
|
import { usePsIncrementalCount } from '../hooks/use-ps-incremental-count';
|
|
14
14
|
import { usePsScrollAnchor } from '../hooks/use-ps-scroll-anchor';
|
|
15
|
+
import { usePsStickyHead } from '../hooks/use-ps-sticky-head';
|
|
15
16
|
import { usePsTableOverflow } from '../hooks/use-ps-table-overflow';
|
|
16
17
|
import { usePsTableStreamStatus } from '../hooks/use-ps-table-stream-status';
|
|
17
18
|
import { PsTableCellTooltipService } from '../services/ps-table-cell-tooltip-service';
|
|
@@ -51,7 +52,7 @@ const alignClassNames = {
|
|
|
51
52
|
left: 'text-left',
|
|
52
53
|
right: 'text-right tabular-nums',
|
|
53
54
|
};
|
|
54
|
-
export const PsDataTable = ({ caption, className, clearSelectionKey, columns, cursorPaging, defaultSort, density = 'normal', expandedRowIds, mobileLayout = 'cards', getBulkActions, getRowId, getRowLabel, getSearchText, grouping, isLoading = false, isPending = false, isRefreshing = false, layout = 'auto', onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, paginationPosition = PsTablePaginationPosition.bottom, pinFirstColumn = true, query, renderExpandedRow, reorder, rows, searchingLabel = PsTableConfig.searchingLabel, searchPlaceholder = PsTableConfig.searchPlaceholder, selectAllMatching, stickyClassName, stickyTop, toolbarActions, totalRows, }) => {
|
|
55
|
+
export const PsDataTable = ({ caption, className, clearSelectionKey, columns, cursorPaging, defaultSort, density = 'normal', expandedRowIds, mobileLayout = 'cards', getBulkActions, getRowId, getRowLabel, getSearchText, grouping, isLoading = false, isPending = false, isRefreshing = false, layout = 'auto', onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, paginationPosition = PsTablePaginationPosition.bottom, pinFirstColumn = true, query, renderExpandedRow, reorder, rows, searchingLabel = PsTableConfig.searchingLabel, searchPlaceholder = PsTableConfig.searchPlaceholder, selectAllMatching, stickyClassName, stickyLeading, stickyTop, toolbarActions, totalRows, }) => {
|
|
55
56
|
const containerRef = useRef(null);
|
|
56
57
|
const [uncontrolledPage, setUncontrolledPage] = useState(1);
|
|
57
58
|
const [uncontrolledQuery, setUncontrolledQuery] = useState(() => PsTableQueryService.empty(defaultSort ? defaultSort : null));
|
|
@@ -62,47 +63,13 @@ export const PsDataTable = ({ caption, className, clearSelectionKey, columns, cu
|
|
|
62
63
|
const [seenClearKey, setSeenClearKey] = useState(clearSelectionKey);
|
|
63
64
|
const stickyRegionRef = useRef(null);
|
|
64
65
|
const stickySentinelRef = useRef(null);
|
|
65
|
-
const
|
|
66
|
-
const
|
|
66
|
+
const stickyBackdropRef = useRef(null);
|
|
67
|
+
const stickyHeadRef = useRef(null);
|
|
67
68
|
const currentQuery = query ? query : uncontrolledQuery;
|
|
68
69
|
if (seenClearKey !== clearSelectionKey) {
|
|
69
70
|
setSeenClearKey(clearSelectionKey);
|
|
70
71
|
setSelectedIds([]);
|
|
71
72
|
}
|
|
72
|
-
useLayoutEffect(() => {
|
|
73
|
-
const region = stickyRegionRef.current;
|
|
74
|
-
if (stickyTop === undefined || region === null)
|
|
75
|
-
return;
|
|
76
|
-
const measure = () => setStickyHeight(region.offsetHeight);
|
|
77
|
-
const observer = new ResizeObserver(measure);
|
|
78
|
-
measure();
|
|
79
|
-
observer.observe(region);
|
|
80
|
-
return () => observer.disconnect();
|
|
81
|
-
}, [stickyTop]);
|
|
82
|
-
useEffect(() => {
|
|
83
|
-
const region = stickyRegionRef.current;
|
|
84
|
-
const sentinel = stickySentinelRef.current;
|
|
85
|
-
if (stickyTop === undefined || region === null || sentinel === null)
|
|
86
|
-
return;
|
|
87
|
-
let frame = 0;
|
|
88
|
-
const check = () => {
|
|
89
|
-
frame = 0;
|
|
90
|
-
setIsStuck(sentinel.getBoundingClientRect().top < Number.parseFloat(getComputedStyle(region).top));
|
|
91
|
-
};
|
|
92
|
-
const schedule = () => {
|
|
93
|
-
if (frame === 0)
|
|
94
|
-
frame = window.requestAnimationFrame(check);
|
|
95
|
-
};
|
|
96
|
-
check();
|
|
97
|
-
window.addEventListener('scroll', schedule, { capture: true, passive: true });
|
|
98
|
-
window.addEventListener('resize', schedule);
|
|
99
|
-
return () => {
|
|
100
|
-
window.removeEventListener('scroll', schedule, { capture: true });
|
|
101
|
-
window.removeEventListener('resize', schedule);
|
|
102
|
-
if (frame !== 0)
|
|
103
|
-
window.cancelAnimationFrame(frame);
|
|
104
|
-
};
|
|
105
|
-
}, [stickyTop]);
|
|
106
73
|
const { filters, hiddenColumnIds, search, sort } = currentQuery;
|
|
107
74
|
const visibleColumns = columns.filter((column) => !hiddenColumnIds.includes(column.id));
|
|
108
75
|
const fittedWidths = useMemo(() => new Map(columns
|
|
@@ -125,6 +92,14 @@ export const PsDataTable = ({ caption, className, clearSelectionKey, columns, cu
|
|
|
125
92
|
const pins = PsTablePinService.leading(pinFirstColumn, Boolean(reorder), isSelectable);
|
|
126
93
|
const placementOf = (column, columnIndex) => PallasoftClassNameService.merge(column.hideBelow === undefined ? '' : PsTableHideBelowClasses[column.hideBelow], columnIndex === 0 ? pins.first : '', PsTablePinService.end(column.pinEnd === true));
|
|
127
94
|
const overflow = usePsTableOverflow(`${visibleColumns.map((column) => column.id).join(',')}:${rows.length}`);
|
|
95
|
+
const isStuck = usePsStickyHead({
|
|
96
|
+
backdropRef: stickyBackdropRef,
|
|
97
|
+
headRef: stickyHeadRef,
|
|
98
|
+
layoutKey: `${visibleColumns.map((column) => column.id).join(',')}:${rows.length}:${isLoading}`,
|
|
99
|
+
regionRef: stickyRegionRef,
|
|
100
|
+
sentinelRef: stickySentinelRef,
|
|
101
|
+
stickyTop,
|
|
102
|
+
});
|
|
128
103
|
const deferredSearch = useDeferredValue(search);
|
|
129
104
|
const queriedRows = useMemo(() => isServerPaginated
|
|
130
105
|
? rows
|
|
@@ -385,7 +360,7 @@ export const PsDataTable = ({ caption, className, clearSelectionKey, columns, cu
|
|
|
385
360
|
updateQuery({ search: next });
|
|
386
361
|
resetPage();
|
|
387
362
|
}, rows: rows, search: search, searchPlaceholder: searchPlaceholder })) : null, banner] }));
|
|
388
|
-
return (_jsxs("div", { ref: containerRef, className: PallasoftClassNameService.merge('flex min-w-0 flex-col gap-3', className), children: [stickyTop === undefined ? (topContent) : (_jsxs(_Fragment, { children: [_jsx("div", { "aria-hidden": 'true', className: '-mb-3 h-0', ref: stickySentinelRef }),
|
|
363
|
+
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] })) : (_jsxs(_Fragment, { children: [_jsx("div", { "aria-hidden": 'true', className: '-mb-3 h-0', ref: stickySentinelRef }), _jsxs("div", { className: PallasoftClassNameService.merge('sticky z-sticky flex flex-col gap-3', stickyClassName), "data-stuck": isStuck, ref: stickyRegionRef, style: { top: stickyTop }, children: [_jsx("div", { "aria-hidden": 'true', className: 'ps-glass-strong pointer-events-none absolute -inset-x-3 -bottom-3 rounded-b-ps-card border-t-0 opacity-0', ref: stickyBackdropRef, style: { top: `calc(-1 * ${stickyTop})` } }), _jsxs("div", { className: 'relative flex flex-col gap-3', children: [stickyLeading, topContent] })] })] })), _jsxs("div", { "aria-busy": isRefreshing, className: 'min-w-0 overflow-hidden rounded-[1.25rem] border border-line bg-surface', children: [showsPagination &&
|
|
389
364
|
paginationPosition !== PsTablePaginationPosition.bottom &&
|
|
390
365
|
rowCount > PsTablePaginationConfig.topMinRows
|
|
391
366
|
? renderPagination('border-b border-t-0')
|
|
@@ -396,13 +371,9 @@ export const PsDataTable = ({ caption, className, clearSelectionKey, columns, cu
|
|
|
396
371
|
? grouping.renderGroupSummary(group.rows)
|
|
397
372
|
: undefined }), _jsx(PsCollapse, { isOpen: isExpanded && !closingGroupKeys.includes(group.key), children: renderCardList(group.rows, null) })] }, `group-${group.key}`));
|
|
398
373
|
})
|
|
399
|
-
: 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(stickyTop === undefined
|
|
400
|
-
? 'ps-scrollbar overflow-x-auto overflow-y-hidden overscroll-x-contain'
|
|
401
|
-
: 'overflow-x-clip', 'transition-opacity duration-150', isRefreshing ? 'opacity-60' : '', 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
|
|
374
|
+
: 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', 'transition-opacity duration-150', isRefreshing ? 'opacity-60' : '', 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
|
|
402
375
|
? ''
|
|
403
|
-
: '
|
|
404
|
-
? undefined
|
|
405
|
-
: { top: `calc(${stickyTop} + ${stickyHeight}px)` }, 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) => {
|
|
376
|
+
: '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) => {
|
|
406
377
|
const align = column.align !== null && column.align !== undefined ? column.align : 'left';
|
|
407
378
|
const isSorted = sort !== null && sort.columnId === column.id;
|
|
408
379
|
return (_jsx("th", { "aria-sort": isSorted
|
|
@@ -10,5 +10,5 @@ export const PsTableToolbar = ({ actions, columns, filters, isSearchable, onClea
|
|
|
10
10
|
const filterableColumns = columns.filter((column) => column.filter);
|
|
11
11
|
const zIndex = usePsLayerZIndex(PallasoftZIndex.Dropdown);
|
|
12
12
|
const activeCount = filterableColumns.filter((column) => PsTableQueryService.isActive(filters[column.id])).length;
|
|
13
|
-
return (_jsxs("div", { className: 'flex flex-wrap items-center gap-2', children: [isSearchable ? (_jsxs("label", { className: 'relative basis-full sm:min-w-[12rem] sm:flex-1 sm:basis-auto sm:max-w-xs', children: [_jsx("span", { className: 'sr-only', children: searchPlaceholder }), _jsx(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: PsIconKey.Search }), _jsx("input", { className: 'ps-input-control ps-focus-ring ps-control rounded-ps-control pl-10 text-sm', onChange: (event) => onSearchChange(event.target.value), placeholder: searchPlaceholder, type: 'search', value: search })] })) : null, _jsxs("div", { className: '
|
|
13
|
+
return (_jsxs("div", { className: 'flex flex-wrap items-center gap-2', children: [isSearchable ? (_jsxs("label", { className: 'relative basis-full sm:min-w-[12rem] sm:flex-1 sm:basis-auto sm:max-w-xs', children: [_jsx("span", { className: 'sr-only', children: searchPlaceholder }), _jsx(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: PsIconKey.Search }), _jsx("input", { className: 'ps-input-control ps-focus-ring ps-control rounded-ps-control pl-10 text-sm', onChange: (event) => onSearchChange(event.target.value), placeholder: searchPlaceholder, type: 'search', value: search })] })) : null, _jsxs("div", { className: 'flex max-w-full shrink-0 grow flex-wrap items-center gap-2', children: [filterableColumns.length > 0 ? (_jsxs(DialogTrigger, { children: [_jsxs(Button, { className: PsTableControlClasses.toolbarButton, children: [_jsx(PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4', iconKey: PsIconKey.Filter }), _jsx("span", { className: 'sr-only sm:not-sr-only', children: "Filters" }), activeCount > 0 ? (_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] }), _jsx(Popover, { className: 'ps-floating-panel ps-scrollbar-hidden max-h-[min(34rem,80vh)] w-[min(22rem,calc(100vw-2rem))] overflow-y-auto outline-none', offset: 6, placement: 'bottom start', style: { zIndex }, children: _jsx(Dialog, { "aria-label": 'Filters', className: 'flex flex-col gap-4 p-4 outline-none', children: _jsxs(PsLayer, { zIndex: zIndex, children: [filterableColumns.map((column) => (_jsx(PsTableFilterField, { kind: column.filter ? column.filter.kind : 'text', label: column.label, onChange: (value) => onFilterChange(column.id, value), options: PsTableQueryService.options(rows, column), value: filters[column.id] ? filters[column.id] : {} }, column.id))), _jsx("div", { className: 'flex justify-end border-t border-line pt-3', children: _jsx("button", { className: PallasoftClassNameService.merge(PsTableControlClasses.textButton, 'text-ink/65 hover:text-ink disabled:opacity-40'), disabled: activeCount === 0, onClick: onClearFilters, type: 'button', children: "Clear all" }) })] }) }) })] })) : null, activeCount > 0 ? (_jsx("button", { className: PallasoftClassNameService.merge(PsTableControlClasses.textButton, 'text-ink/65 hover:text-ink'), onClick: onClearFilters, type: 'button', children: "Clear filters" })) : null, actions ? _jsx("div", { className: 'ml-auto flex shrink-0 items-center gap-2', children: actions }) : null] })] }));
|
|
14
14
|
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useLayoutEffect, useState } from 'react';
|
|
2
|
+
import { PsTableStickyConfig } from '../constants/ps-table-sticky-config';
|
|
3
|
+
import { PsTableScrollService } from '../services/ps-table-scroll-service';
|
|
4
|
+
import { PsTableStickyHeadService } from '../services/ps-table-sticky-head-service';
|
|
5
|
+
export const usePsStickyHead = ({ backdropRef, headRef, layoutKey, regionRef, sentinelRef, stickyTop, }) => {
|
|
6
|
+
const [isStuck, setIsStuck] = useState(false);
|
|
7
|
+
useLayoutEffect(() => {
|
|
8
|
+
const region = regionRef.current;
|
|
9
|
+
const sentinel = sentinelRef.current;
|
|
10
|
+
const backdrop = backdropRef.current;
|
|
11
|
+
const head = headRef.current;
|
|
12
|
+
const table = head === null ? null : head.parentElement;
|
|
13
|
+
if (stickyTop === undefined ||
|
|
14
|
+
region === null ||
|
|
15
|
+
sentinel === null ||
|
|
16
|
+
backdrop === null ||
|
|
17
|
+
head === null ||
|
|
18
|
+
table === null)
|
|
19
|
+
return;
|
|
20
|
+
const scroller = PsTableScrollService.parent(region);
|
|
21
|
+
const Timeline = window.ScrollTimeline;
|
|
22
|
+
const isDriven = scroller !== null && Timeline !== undefined;
|
|
23
|
+
let animations = [];
|
|
24
|
+
let frame = 0;
|
|
25
|
+
const maxShift = () => Math.max(table.offsetHeight - head.offsetHeight, 0);
|
|
26
|
+
const drive = () => {
|
|
27
|
+
if (scroller === null || Timeline === undefined)
|
|
28
|
+
return;
|
|
29
|
+
const scrollerTop = scroller.getBoundingClientRect().top;
|
|
30
|
+
const regionTop = region.getBoundingClientRect().top;
|
|
31
|
+
const pinnedBottom = scrollerTop +
|
|
32
|
+
Number.parseFloat(getComputedStyle(region).top) +
|
|
33
|
+
backdrop.getBoundingClientRect().bottom -
|
|
34
|
+
regionTop;
|
|
35
|
+
const pinScroll = table.getBoundingClientRect().top + scroller.scrollTop - pinnedBottom;
|
|
36
|
+
const barPinScroll = sentinel.getBoundingClientRect().top -
|
|
37
|
+
scrollerTop +
|
|
38
|
+
scroller.scrollTop -
|
|
39
|
+
Number.parseFloat(getComputedStyle(region).top);
|
|
40
|
+
const scrollRange = scroller.scrollHeight - scroller.clientHeight;
|
|
41
|
+
const timeline = new Timeline({ axis: 'block', source: scroller });
|
|
42
|
+
const headFrames = PsTableStickyHeadService.keyframes(pinScroll, maxShift(), scrollRange);
|
|
43
|
+
const backdropFrames = PsTableStickyHeadService.backdropKeyframes(barPinScroll, PsTableStickyConfig.backdropFadePx, scrollRange);
|
|
44
|
+
animations.forEach((animation) => animation.cancel());
|
|
45
|
+
animations =
|
|
46
|
+
headFrames.length === 0
|
|
47
|
+
? []
|
|
48
|
+
: [
|
|
49
|
+
head.animate(headFrames.map((keyframe) => ({
|
|
50
|
+
offset: keyframe.offset,
|
|
51
|
+
transform: `translateY(${keyframe.shift}px)`,
|
|
52
|
+
})), { fill: 'both', timeline }),
|
|
53
|
+
backdrop.animate(backdropFrames.map((keyframe) => ({
|
|
54
|
+
offset: keyframe.offset,
|
|
55
|
+
opacity: keyframe.opacity,
|
|
56
|
+
})), { fill: 'both', timeline }),
|
|
57
|
+
];
|
|
58
|
+
};
|
|
59
|
+
const follow = () => (head.style.transform = `translateY(${PsTableStickyHeadService.offset(backdrop.getBoundingClientRect().bottom, table.getBoundingClientRect().top, table.offsetHeight, head.offsetHeight)}px)`);
|
|
60
|
+
const check = () => {
|
|
61
|
+
const stuck = sentinel.getBoundingClientRect().top < region.getBoundingClientRect().top - 1;
|
|
62
|
+
frame = 0;
|
|
63
|
+
setIsStuck(stuck);
|
|
64
|
+
if (isDriven)
|
|
65
|
+
return;
|
|
66
|
+
backdrop.style.opacity = stuck ? '1' : '0';
|
|
67
|
+
follow();
|
|
68
|
+
};
|
|
69
|
+
const schedule = () => {
|
|
70
|
+
if (frame === 0)
|
|
71
|
+
frame = window.requestAnimationFrame(check);
|
|
72
|
+
};
|
|
73
|
+
const measure = () => {
|
|
74
|
+
if (isDriven)
|
|
75
|
+
drive();
|
|
76
|
+
schedule();
|
|
77
|
+
};
|
|
78
|
+
const observer = new ResizeObserver(measure);
|
|
79
|
+
measure();
|
|
80
|
+
observer.observe(region);
|
|
81
|
+
observer.observe(table);
|
|
82
|
+
if (scroller !== null)
|
|
83
|
+
Array.from(scroller.children).forEach((child) => observer.observe(child));
|
|
84
|
+
window.addEventListener('scroll', schedule, { capture: true, passive: true });
|
|
85
|
+
window.addEventListener('resize', measure);
|
|
86
|
+
return () => {
|
|
87
|
+
observer.disconnect();
|
|
88
|
+
animations.forEach((animation) => animation.cancel());
|
|
89
|
+
head.style.transform = '';
|
|
90
|
+
backdrop.style.opacity = '';
|
|
91
|
+
window.removeEventListener('scroll', schedule, { capture: true });
|
|
92
|
+
window.removeEventListener('resize', measure);
|
|
93
|
+
if (frame !== 0)
|
|
94
|
+
window.cancelAnimationFrame(frame);
|
|
95
|
+
};
|
|
96
|
+
}, [backdropRef, headRef, layoutKey, regionRef, sentinelRef, stickyTop]);
|
|
97
|
+
return isStuck;
|
|
98
|
+
};
|
package/dist/esm/index.js
CHANGED
|
@@ -29,9 +29,13 @@ export * from './services/ps-table-group-service';
|
|
|
29
29
|
export * from './services/ps-table-query-service';
|
|
30
30
|
export * from './services/ps-table-search-service';
|
|
31
31
|
export * from './services/ps-table-scroll-service';
|
|
32
|
+
export * from './services/ps-table-sticky-head-service';
|
|
32
33
|
export * from './services/ps-table-pin-service';
|
|
33
34
|
export * from './services/ps-table-overflow-service';
|
|
34
35
|
export * from './hooks/use-ps-table-overflow';
|
|
36
|
+
export * from './hooks/use-ps-sticky-head';
|
|
37
|
+
export * from './interfaces/ps-sticky-head-refs';
|
|
38
|
+
export * from './interfaces/ps-table-sticky-head-keyframe';
|
|
35
39
|
export * from './interfaces/table-pin-classes';
|
|
36
40
|
export * from './interfaces/table-overflow-edges';
|
|
37
41
|
export * from './services/ps-table-stream-service';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export class PsTableStickyHeadService {
|
|
2
|
+
static offset(pinnedBottom, tableTop, tableHeight, headHeight) {
|
|
3
|
+
return Math.min(Math.max(pinnedBottom - tableTop, 0), Math.max(tableHeight - headHeight, 0));
|
|
4
|
+
}
|
|
5
|
+
static keyframes(pinScroll, maxShift, scrollRange) {
|
|
6
|
+
if (scrollRange <= 0)
|
|
7
|
+
return [];
|
|
8
|
+
const shiftAt = (scroll) => Math.min(Math.max(scroll - pinScroll, 0), maxShift);
|
|
9
|
+
return PsTableStickyHeadService.stops([0, pinScroll, pinScroll + maxShift, scrollRange], scrollRange).map((scroll) => ({
|
|
10
|
+
offset: scroll / scrollRange,
|
|
11
|
+
shift: shiftAt(scroll),
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
static backdropKeyframes(pinScroll, fade, scrollRange) {
|
|
15
|
+
if (scrollRange <= 0)
|
|
16
|
+
return [];
|
|
17
|
+
const opacityAt = (scroll) => Math.min(Math.max((scroll - pinScroll + fade) / fade, 0), 1);
|
|
18
|
+
return PsTableStickyHeadService.stops([0, pinScroll - fade, pinScroll, scrollRange], scrollRange).map((scroll) => ({ offset: scroll / scrollRange, opacity: opacityAt(scroll) }));
|
|
19
|
+
}
|
|
20
|
+
static stops(scrolls, scrollRange) {
|
|
21
|
+
const inside = scrolls.filter((scroll) => scroll >= 0 && scroll <= scrollRange);
|
|
22
|
+
return inside.filter((scroll, index) => inside.indexOf(scroll) === index);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.usePsStickyHead = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const ps_table_sticky_config_1 = require("../constants/ps-table-sticky-config");
|
|
6
|
+
const ps_table_scroll_service_1 = require("../services/ps-table-scroll-service");
|
|
7
|
+
const ps_table_sticky_head_service_1 = require("../services/ps-table-sticky-head-service");
|
|
8
|
+
const usePsStickyHead = ({ backdropRef, headRef, layoutKey, regionRef, sentinelRef, stickyTop, }) => {
|
|
9
|
+
const [isStuck, setIsStuck] = (0, react_1.useState)(false);
|
|
10
|
+
(0, react_1.useLayoutEffect)(() => {
|
|
11
|
+
const region = regionRef.current;
|
|
12
|
+
const sentinel = sentinelRef.current;
|
|
13
|
+
const backdrop = backdropRef.current;
|
|
14
|
+
const head = headRef.current;
|
|
15
|
+
const table = head === null ? null : head.parentElement;
|
|
16
|
+
if (stickyTop === undefined ||
|
|
17
|
+
region === null ||
|
|
18
|
+
sentinel === null ||
|
|
19
|
+
backdrop === null ||
|
|
20
|
+
head === null ||
|
|
21
|
+
table === null)
|
|
22
|
+
return;
|
|
23
|
+
const scroller = ps_table_scroll_service_1.PsTableScrollService.parent(region);
|
|
24
|
+
const Timeline = window.ScrollTimeline;
|
|
25
|
+
const isDriven = scroller !== null && Timeline !== undefined;
|
|
26
|
+
let animations = [];
|
|
27
|
+
let frame = 0;
|
|
28
|
+
const maxShift = () => Math.max(table.offsetHeight - head.offsetHeight, 0);
|
|
29
|
+
const drive = () => {
|
|
30
|
+
if (scroller === null || Timeline === undefined)
|
|
31
|
+
return;
|
|
32
|
+
const scrollerTop = scroller.getBoundingClientRect().top;
|
|
33
|
+
const regionTop = region.getBoundingClientRect().top;
|
|
34
|
+
const pinnedBottom = scrollerTop +
|
|
35
|
+
Number.parseFloat(getComputedStyle(region).top) +
|
|
36
|
+
backdrop.getBoundingClientRect().bottom -
|
|
37
|
+
regionTop;
|
|
38
|
+
const pinScroll = table.getBoundingClientRect().top + scroller.scrollTop - pinnedBottom;
|
|
39
|
+
const barPinScroll = sentinel.getBoundingClientRect().top -
|
|
40
|
+
scrollerTop +
|
|
41
|
+
scroller.scrollTop -
|
|
42
|
+
Number.parseFloat(getComputedStyle(region).top);
|
|
43
|
+
const scrollRange = scroller.scrollHeight - scroller.clientHeight;
|
|
44
|
+
const timeline = new Timeline({ axis: 'block', source: scroller });
|
|
45
|
+
const headFrames = ps_table_sticky_head_service_1.PsTableStickyHeadService.keyframes(pinScroll, maxShift(), scrollRange);
|
|
46
|
+
const backdropFrames = ps_table_sticky_head_service_1.PsTableStickyHeadService.backdropKeyframes(barPinScroll, ps_table_sticky_config_1.PsTableStickyConfig.backdropFadePx, scrollRange);
|
|
47
|
+
animations.forEach((animation) => animation.cancel());
|
|
48
|
+
animations =
|
|
49
|
+
headFrames.length === 0
|
|
50
|
+
? []
|
|
51
|
+
: [
|
|
52
|
+
head.animate(headFrames.map((keyframe) => ({
|
|
53
|
+
offset: keyframe.offset,
|
|
54
|
+
transform: `translateY(${keyframe.shift}px)`,
|
|
55
|
+
})), { fill: 'both', timeline }),
|
|
56
|
+
backdrop.animate(backdropFrames.map((keyframe) => ({
|
|
57
|
+
offset: keyframe.offset,
|
|
58
|
+
opacity: keyframe.opacity,
|
|
59
|
+
})), { fill: 'both', timeline }),
|
|
60
|
+
];
|
|
61
|
+
};
|
|
62
|
+
const follow = () => (head.style.transform = `translateY(${ps_table_sticky_head_service_1.PsTableStickyHeadService.offset(backdrop.getBoundingClientRect().bottom, table.getBoundingClientRect().top, table.offsetHeight, head.offsetHeight)}px)`);
|
|
63
|
+
const check = () => {
|
|
64
|
+
const stuck = sentinel.getBoundingClientRect().top < region.getBoundingClientRect().top - 1;
|
|
65
|
+
frame = 0;
|
|
66
|
+
setIsStuck(stuck);
|
|
67
|
+
if (isDriven)
|
|
68
|
+
return;
|
|
69
|
+
backdrop.style.opacity = stuck ? '1' : '0';
|
|
70
|
+
follow();
|
|
71
|
+
};
|
|
72
|
+
const schedule = () => {
|
|
73
|
+
if (frame === 0)
|
|
74
|
+
frame = window.requestAnimationFrame(check);
|
|
75
|
+
};
|
|
76
|
+
const measure = () => {
|
|
77
|
+
if (isDriven)
|
|
78
|
+
drive();
|
|
79
|
+
schedule();
|
|
80
|
+
};
|
|
81
|
+
const observer = new ResizeObserver(measure);
|
|
82
|
+
measure();
|
|
83
|
+
observer.observe(region);
|
|
84
|
+
observer.observe(table);
|
|
85
|
+
if (scroller !== null)
|
|
86
|
+
Array.from(scroller.children).forEach((child) => observer.observe(child));
|
|
87
|
+
window.addEventListener('scroll', schedule, { capture: true, passive: true });
|
|
88
|
+
window.addEventListener('resize', measure);
|
|
89
|
+
return () => {
|
|
90
|
+
observer.disconnect();
|
|
91
|
+
animations.forEach((animation) => animation.cancel());
|
|
92
|
+
head.style.transform = '';
|
|
93
|
+
backdrop.style.opacity = '';
|
|
94
|
+
window.removeEventListener('scroll', schedule, { capture: true });
|
|
95
|
+
window.removeEventListener('resize', measure);
|
|
96
|
+
if (frame !== 0)
|
|
97
|
+
window.cancelAnimationFrame(frame);
|
|
98
|
+
};
|
|
99
|
+
}, [backdropRef, headRef, layoutKey, regionRef, sentinelRef, stickyTop]);
|
|
100
|
+
return isStuck;
|
|
101
|
+
};
|
|
102
|
+
exports.usePsStickyHead = usePsStickyHead;
|
package/dist/index.d.ts
CHANGED
|
@@ -29,9 +29,13 @@ export * from './services/ps-table-group-service';
|
|
|
29
29
|
export * from './services/ps-table-query-service';
|
|
30
30
|
export * from './services/ps-table-search-service';
|
|
31
31
|
export * from './services/ps-table-scroll-service';
|
|
32
|
+
export * from './services/ps-table-sticky-head-service';
|
|
32
33
|
export * from './services/ps-table-pin-service';
|
|
33
34
|
export * from './services/ps-table-overflow-service';
|
|
34
35
|
export * from './hooks/use-ps-table-overflow';
|
|
36
|
+
export * from './hooks/use-ps-sticky-head';
|
|
37
|
+
export * from './interfaces/ps-sticky-head-refs';
|
|
38
|
+
export * from './interfaces/ps-table-sticky-head-keyframe';
|
|
35
39
|
export * from './interfaces/table-pin-classes';
|
|
36
40
|
export * from './interfaces/table-overflow-edges';
|
|
37
41
|
export * from './services/ps-table-stream-service';
|
package/dist/index.js
CHANGED
|
@@ -45,9 +45,13 @@ __exportStar(require("./services/ps-table-group-service"), exports);
|
|
|
45
45
|
__exportStar(require("./services/ps-table-query-service"), exports);
|
|
46
46
|
__exportStar(require("./services/ps-table-search-service"), exports);
|
|
47
47
|
__exportStar(require("./services/ps-table-scroll-service"), exports);
|
|
48
|
+
__exportStar(require("./services/ps-table-sticky-head-service"), exports);
|
|
48
49
|
__exportStar(require("./services/ps-table-pin-service"), exports);
|
|
49
50
|
__exportStar(require("./services/ps-table-overflow-service"), exports);
|
|
50
51
|
__exportStar(require("./hooks/use-ps-table-overflow"), exports);
|
|
52
|
+
__exportStar(require("./hooks/use-ps-sticky-head"), exports);
|
|
53
|
+
__exportStar(require("./interfaces/ps-sticky-head-refs"), exports);
|
|
54
|
+
__exportStar(require("./interfaces/ps-table-sticky-head-keyframe"), exports);
|
|
51
55
|
__exportStar(require("./interfaces/table-pin-classes"), exports);
|
|
52
56
|
__exportStar(require("./interfaces/table-overflow-edges"), exports);
|
|
53
57
|
__exportStar(require("./services/ps-table-stream-service"), exports);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RefObject } from 'react';
|
|
2
|
+
export interface PsStickyHeadRefs {
|
|
3
|
+
backdropRef: RefObject<HTMLDivElement | null>;
|
|
4
|
+
headRef: RefObject<HTMLTableSectionElement | null>;
|
|
5
|
+
layoutKey: string;
|
|
6
|
+
regionRef: RefObject<HTMLDivElement | null>;
|
|
7
|
+
sentinelRef: RefObject<HTMLDivElement | null>;
|
|
8
|
+
stickyTop?: string;
|
|
9
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PsTableStickyBackdropKeyframe } from '../interfaces/ps-table-sticky-backdrop-keyframe';
|
|
2
|
+
import type { PsTableStickyHeadKeyframe } from '../interfaces/ps-table-sticky-head-keyframe';
|
|
3
|
+
export declare class PsTableStickyHeadService {
|
|
4
|
+
static offset(pinnedBottom: number, tableTop: number, tableHeight: number, headHeight: number): number;
|
|
5
|
+
static keyframes(pinScroll: number, maxShift: number, scrollRange: number): PsTableStickyHeadKeyframe[];
|
|
6
|
+
static backdropKeyframes(pinScroll: number, fade: number, scrollRange: number): PsTableStickyBackdropKeyframe[];
|
|
7
|
+
private static stops;
|
|
8
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsTableStickyHeadService = void 0;
|
|
4
|
+
class PsTableStickyHeadService {
|
|
5
|
+
static offset(pinnedBottom, tableTop, tableHeight, headHeight) {
|
|
6
|
+
return Math.min(Math.max(pinnedBottom - tableTop, 0), Math.max(tableHeight - headHeight, 0));
|
|
7
|
+
}
|
|
8
|
+
static keyframes(pinScroll, maxShift, scrollRange) {
|
|
9
|
+
if (scrollRange <= 0)
|
|
10
|
+
return [];
|
|
11
|
+
const shiftAt = (scroll) => Math.min(Math.max(scroll - pinScroll, 0), maxShift);
|
|
12
|
+
return PsTableStickyHeadService.stops([0, pinScroll, pinScroll + maxShift, scrollRange], scrollRange).map((scroll) => ({
|
|
13
|
+
offset: scroll / scrollRange,
|
|
14
|
+
shift: shiftAt(scroll),
|
|
15
|
+
}));
|
|
16
|
+
}
|
|
17
|
+
static backdropKeyframes(pinScroll, fade, scrollRange) {
|
|
18
|
+
if (scrollRange <= 0)
|
|
19
|
+
return [];
|
|
20
|
+
const opacityAt = (scroll) => Math.min(Math.max((scroll - pinScroll + fade) / fade, 0), 1);
|
|
21
|
+
return PsTableStickyHeadService.stops([0, pinScroll - fade, pinScroll, scrollRange], scrollRange).map((scroll) => ({ offset: scroll / scrollRange, opacity: opacityAt(scroll) }));
|
|
22
|
+
}
|
|
23
|
+
static stops(scrolls, scrollRange) {
|
|
24
|
+
const inside = scrolls.filter((scroll) => scroll >= 0 && scroll <= scrollRange);
|
|
25
|
+
return inside.filter((scroll, index) => inside.indexOf(scroll) === index);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.PsTableStickyHeadService = PsTableStickyHeadService;
|