@dbcdk/react-components 0.0.25 → 0.0.26
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/forms/checkbox/Checkbox.js +1 -1
- package/dist/components/forms/checkbox/Checkbox.module.css +16 -3
- package/dist/components/forms/select/Select.js +1 -1
- package/dist/components/menu/Menu.js +1 -1
- package/dist/components/sidebar/components/sidebar-container/SidebarContainer.module.css +0 -1
- package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.module.css +1 -0
- package/dist/components/table/Table.d.ts +1 -2
- package/dist/components/table/Table.js +2 -11
- package/dist/components/table/Table.module.css +4 -3
- package/dist/components/tabs/Tabs.js +1 -1
- package/dist/utils/date/formatDate.d.ts +11 -3
- package/dist/utils/date/formatDate.js +35 -10
- package/package.json +1 -1
- package/dist/components/table/hooks/useAnimatedRowIds.d.ts +0 -9
- package/dist/components/table/hooks/useAnimatedRowIds.js +0 -76
|
@@ -4,7 +4,7 @@ import { Check } from 'lucide-react';
|
|
|
4
4
|
import { useId, useState } from 'react';
|
|
5
5
|
import styles from './Checkbox.module.css';
|
|
6
6
|
import { InputContainer } from '../input-container/InputContainer';
|
|
7
|
-
export function Checkbox({ checked: controlled, onChange, variant = '
|
|
7
|
+
export function Checkbox({ checked: controlled, onChange, variant = 'default', disabled, label, size = 'md', modified, containerLabel, error, helpText, orientation = 'horizontal', labelWidth = '160px', fullWidth = false, required = false, noContainer = false, id, 'data-cy': dataCy, }) {
|
|
8
8
|
const [internal, setInternal] = useState(false);
|
|
9
9
|
const isChecked = controlled !== null && controlled !== void 0 ? controlled : internal;
|
|
10
10
|
const generatedId = useId();
|
|
@@ -50,10 +50,11 @@
|
|
|
50
50
|
pointer-events: none;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
.
|
|
54
|
-
background-color: var(--
|
|
53
|
+
.default {
|
|
54
|
+
background-color: var(--color-bg-toolbar);
|
|
55
|
+
border-color: var(--color-bg-toolbar);
|
|
55
56
|
&:not(:hover) {
|
|
56
|
-
border-color:
|
|
57
|
+
border-color: var(--color-bg-toolbar);
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
.icon {
|
|
@@ -61,6 +62,18 @@
|
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
.outlined.checked {
|
|
66
|
+
background-color: transparent;
|
|
67
|
+
border-color: var(--color-border-default);
|
|
68
|
+
&:not(:hover) {
|
|
69
|
+
border-color: var(--color-brand);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.icon {
|
|
73
|
+
color: var(--color-brand);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
64
77
|
.success.checked {
|
|
65
78
|
background-color: var(--color-status-success);
|
|
66
79
|
&:not(:hover) {
|
|
@@ -112,7 +112,7 @@ export function Select({ label, error, helpText, orientation = 'vertical', label
|
|
|
112
112
|
returnFocus: true, trigger: (toggle, icon, isOpen) => (_jsx(Button, { disabled: disabled, ...(tooltipEnabled ? triggerProps : {}), id: controlId, "data-cy": dataCy !== null && dataCy !== void 0 ? dataCy : 'select-button', onKeyDown: handleKeyDown, fullWidth: fullWidth, variant: variant, onClick: e => {
|
|
113
113
|
resetActiveToSelected();
|
|
114
114
|
toggle(e);
|
|
115
|
-
}, size: size, type: "button", "data-forminput": true, "aria-haspopup": "listbox", "aria-expanded": !!isOpen, "aria-controls": listboxId, "aria-invalid": Boolean(error) || undefined, "aria-describedby": describedBy, children: _jsxs("span", { className: "dbc-flex dbc-justify-between dbc-items-center dbc-gap-xxs", style: { width: '100%' }, children: [_jsx("span", { children: selected ? selected.label : placeholder }), onClear && selected && _jsx(ClearButton, { onClick: onClear }), icon] }) })), children: _jsx(Menu, { onKeyDown: handleKeyDown, role: "listbox", children: options.map((opt, index) => {
|
|
115
|
+
}, size: size, type: "button", "data-forminput": true, "aria-haspopup": "listbox", "aria-expanded": !!isOpen, "aria-controls": listboxId, "aria-invalid": Boolean(error) || undefined, "aria-describedby": describedBy, children: _jsxs("span", { className: "dbc-flex dbc-justify-between dbc-items-center dbc-gap-xxs", style: { width: '100%' }, children: [_jsx("span", { children: selected ? selected.label : _jsx("span", { className: "dbc-muted-text", children: placeholder }) }), onClear && selected && _jsx(ClearButton, { onClick: onClear }), icon] }) })), children: _jsx(Menu, { onKeyDown: handleKeyDown, role: "listbox", children: options.map((opt, index) => {
|
|
116
116
|
const isSelected = typeof opt.value === 'object' && typeof selectedValue === 'object' && datakey
|
|
117
117
|
? (selectedValue === null || selectedValue === void 0 ? void 0 : selectedValue[datakey]) === opt.value[datakey]
|
|
118
118
|
: opt.value === selectedValue;
|
|
@@ -57,7 +57,7 @@ const MenuItem = React.forwardRef(({ children, active, disabled, className, item
|
|
|
57
57
|
});
|
|
58
58
|
MenuItem.displayName = 'Menu.Item';
|
|
59
59
|
const MenuCheckItem = React.forwardRef(({ label, checked, disabled, onCheckedChange, className, ...liProps }, ref) => {
|
|
60
|
-
return (_jsx("li", { ref: ref, role: "none", className: [styles.row, className].filter(Boolean).join(' '), ...liProps, children: _jsx("div", { className: styles.interactiveChild, children: _jsx(Checkbox, { noContainer: true, checked: checked, disabled: disabled, label: label, onChange: (next, _e) => onCheckedChange === null || onCheckedChange === void 0 ? void 0 : onCheckedChange(next) }) }) }));
|
|
60
|
+
return (_jsx("li", { ref: ref, role: "none", className: [styles.row, className].filter(Boolean).join(' '), ...liProps, children: _jsx("div", { className: styles.interactiveChild, children: _jsx(Checkbox, { variant: "default", size: "md", noContainer: true, checked: checked, disabled: disabled, label: label, onChange: (next, _e) => onCheckedChange === null || onCheckedChange === void 0 ? void 0 : onCheckedChange(next) }) }) }));
|
|
61
61
|
});
|
|
62
62
|
MenuCheckItem.displayName = 'Menu.CheckItem';
|
|
63
63
|
const MenuRadioItem = React.forwardRef(({ name, value, checked, disabled, label, onValueChange, className, ...liProps }, ref) => {
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
inline-size: var(--sidebar-width);
|
|
10
10
|
box-sizing: border-box;
|
|
11
11
|
border-inline-end: var(--border-width-thin) solid var(--color-border-default);
|
|
12
|
-
|
|
13
12
|
transition:
|
|
14
13
|
width var(--transition-fast) var(--ease-standard),
|
|
15
14
|
inline-size var(--transition-fast) var(--ease-standard);
|
|
@@ -59,7 +59,6 @@ export type TableProps<T extends Record<string, any>> = Omit<HTMLAttributes<HTML
|
|
|
59
59
|
showFirstLast?: boolean;
|
|
60
60
|
viewMode?: ViewMode;
|
|
61
61
|
emptyConfig?: TableEmptyConfig;
|
|
62
|
-
animateNewRows?: boolean;
|
|
63
62
|
};
|
|
64
|
-
export declare function Table<T extends Record<string, any>>({ data, columns, selectedRows, onRowSelect, selectionMode, onSortChange, onRowClick, onRowMouseEnter, sortById, sortDirection, dataKey, headerExtras, gridTemplateColumns, toolbar, striped, fillViewport, viewportBottomOffset, viewportMin, viewportIncludeMarginTop, take, skip, paginationPlacement, totalItemsCount, onPageChange, loading, variant, size, getRowSeverity, showFirstLast, allRowsSelected, onSelectAllRows, viewMode, emptyConfig,
|
|
63
|
+
export declare function Table<T extends Record<string, any>>({ data, columns, selectedRows, onRowSelect, selectionMode, onSortChange, onRowClick, onRowMouseEnter, sortById, sortDirection, dataKey, headerExtras, gridTemplateColumns, toolbar, striped, fillViewport, viewportBottomOffset, viewportMin, viewportIncludeMarginTop, take, skip, paginationPlacement, totalItemsCount, onPageChange, loading, variant, size, getRowSeverity, showFirstLast, allRowsSelected, onSelectAllRows, viewMode, emptyConfig, ...rest }: TableProps<T>): JSX.Element;
|
|
65
64
|
export {};
|
|
@@ -7,7 +7,6 @@ import { Pagination } from '../../components/pagination/Pagination';
|
|
|
7
7
|
import { SkeletonLoaderItem } from '../../components/skeleton-loader/skeleton-loader-item/SkeletonLoaderItem';
|
|
8
8
|
import { SeverityBgColor } from '../../constants/severity';
|
|
9
9
|
import { TableEmptyState } from './components/empty-state/EmptyState';
|
|
10
|
-
import { useAnimatedNewRowIds } from './hooks/useAnimatedRowIds';
|
|
11
10
|
import styles from './Table.module.css';
|
|
12
11
|
import { getAriaSort, getCellDisplayValue, getHeaderLabel, getNextSortDirection, getVisibleColumns, isModifierClick, shouldAllowWrap, shouldToggleOnKey, isActiveSort, } from './table.utils';
|
|
13
12
|
function buildDefaultGridTemplate(args) {
|
|
@@ -19,19 +18,13 @@ function buildDefaultGridTemplate(args) {
|
|
|
19
18
|
parts.push('minmax(120px, 1fr)');
|
|
20
19
|
return parts.join(' ');
|
|
21
20
|
}
|
|
22
|
-
export function Table({ data, columns, selectedRows, onRowSelect, selectionMode = 'single', onSortChange, onRowClick, onRowMouseEnter, sortById, sortDirection, dataKey, headerExtras, gridTemplateColumns, toolbar, striped, fillViewport = false, viewportBottomOffset, viewportMin, viewportIncludeMarginTop, take, skip, paginationPlacement = 'bottom', totalItemsCount, onPageChange, loading, variant = 'primary', size = 'md', getRowSeverity, showFirstLast = false, allRowsSelected, onSelectAllRows, viewMode, emptyConfig,
|
|
21
|
+
export function Table({ data, columns, selectedRows, onRowSelect, selectionMode = 'single', onSortChange, onRowClick, onRowMouseEnter, sortById, sortDirection, dataKey, headerExtras, gridTemplateColumns, toolbar, striped, fillViewport = false, viewportBottomOffset, viewportMin, viewportIncludeMarginTop, take, skip, paginationPlacement = 'bottom', totalItemsCount, onPageChange, loading, variant = 'primary', size = 'md', getRowSeverity, showFirstLast = false, allRowsSelected, onSelectAllRows, viewMode, emptyConfig, ...rest }) {
|
|
23
22
|
void viewportBottomOffset;
|
|
24
23
|
void viewportMin;
|
|
25
24
|
void viewportIncludeMarginTop;
|
|
26
25
|
const filteredColumns = useMemo(() => getVisibleColumns(columns), [columns]);
|
|
27
26
|
const handlePageChange = useCallback((e) => onPageChange === null || onPageChange === void 0 ? void 0 : onPageChange(e), [onPageChange]);
|
|
28
27
|
const hasSelection = Boolean(selectedRows && onRowSelect && dataKey);
|
|
29
|
-
const newRowIds = useAnimatedNewRowIds({
|
|
30
|
-
data,
|
|
31
|
-
dataKey,
|
|
32
|
-
enabled: animateNewRows,
|
|
33
|
-
animationDurationMs: 1000,
|
|
34
|
-
});
|
|
35
28
|
const template = useMemo(() => {
|
|
36
29
|
return (gridTemplateColumns !== null && gridTemplateColumns !== void 0 ? gridTemplateColumns : buildDefaultGridTemplate({ hasSelection, colCount: filteredColumns.length }));
|
|
37
30
|
}, [gridTemplateColumns, hasSelection, filteredColumns.length]);
|
|
@@ -101,15 +94,13 @@ export function Table({ data, columns, selectedRows, onRowSelect, selectionMode
|
|
|
101
94
|
})] }, `loading-row-${rowIndex}`))) }));
|
|
102
95
|
const dataBodyEl = (_jsx("div", { className: `${styles.body} ${striped ? styles.striped : ''}`, role: "rowgroup", children: data.map(row => {
|
|
103
96
|
const rowSeverity = getRowSeverity === null || getRowSeverity === void 0 ? void 0 : getRowSeverity(row);
|
|
104
|
-
const rowId =
|
|
105
|
-
const isNewRow = animateNewRows && newRowIds.has(rowId);
|
|
97
|
+
const rowId = row[dataKey];
|
|
106
98
|
const isSelected = Boolean(selectedRows === null || selectedRows === void 0 ? void 0 : selectedRows.has(rowId));
|
|
107
99
|
return (_jsxs("div", { className: [
|
|
108
100
|
styles.row,
|
|
109
101
|
onRowClick ? styles.clickableRow : '',
|
|
110
102
|
isSelected ? styles.selectedRow : '',
|
|
111
103
|
rowSeverity ? styles.severity : '',
|
|
112
|
-
isNewRow ? styles.newRow : '',
|
|
113
104
|
]
|
|
114
105
|
.filter(Boolean)
|
|
115
106
|
.join(' '), style: {
|
|
@@ -187,15 +187,16 @@
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
.selectionCell {
|
|
190
|
-
padding-block: inherit !important;
|
|
191
|
-
padding-inline: 0 !important;
|
|
192
190
|
overflow: visible !important;
|
|
193
191
|
display: flex;
|
|
194
|
-
align-items:
|
|
192
|
+
align-items: flex-start;
|
|
195
193
|
justify-content: flex-start;
|
|
196
194
|
min-width: 0;
|
|
197
195
|
}
|
|
198
196
|
|
|
197
|
+
.headerCell.selectionCell {
|
|
198
|
+
align-items: center;
|
|
199
|
+
}
|
|
199
200
|
.clickableRow {
|
|
200
201
|
cursor: pointer;
|
|
201
202
|
}
|
|
@@ -117,7 +117,7 @@ export function Tabs({ header, variant, panelStyle = false, tabs, value, default
|
|
|
117
117
|
const selected = index === activeIndex;
|
|
118
118
|
const tabDomId = `${uid}-tab-${String(tab.id)}`;
|
|
119
119
|
const panelDomId = `${uid}-panel-${String(tab.id)}`;
|
|
120
|
-
return (_jsx("div", { className: `${styles.tab} ${selected ? styles.active : ''}`, children: _jsxs("button", { id: tabDomId, type: "button", className: styles.tabButton, role: "tab", "aria-selected": selected, "aria-controls": panelDomId, tabIndex: selected ? 0 : -1, disabled: tab.disabled, onClick: () => setValue(tab.id), onKeyDown: e => onKeyDownTab(e, index), children: [tab.headerIcon ? _jsx("span", { className: styles.icon, children: tab.headerIcon }) : null, _jsx("span", { className: styles.label, children: tab.header }), tab.badge !== undefined && tab.badge > 0 ? (_jsx("span", { className: styles.badge, children: _jsx(Chip, {
|
|
120
|
+
return (_jsx("div", { className: `${styles.tab} ${selected ? styles.active : ''}`, children: _jsxs("button", { id: tabDomId, type: "button", className: styles.tabButton, role: "tab", "aria-selected": selected, "aria-controls": panelDomId, tabIndex: selected ? 0 : -1, disabled: tab.disabled, onClick: () => setValue(tab.id), onKeyDown: e => onKeyDownTab(e, index), children: [tab.headerIcon ? _jsx("span", { className: styles.icon, children: tab.headerIcon }) : null, _jsx("span", { className: styles.label, children: tab.header }), tab.badge !== undefined && tab.badge > 0 ? (_jsx("span", { className: styles.badge, children: _jsx(Chip, { type: "subtle", severity: null, size: "sm", children: tab.badge.toLocaleString('da-DK') }) })) : null] }) }, tab.id));
|
|
121
121
|
}) }), _jsx("div", { id: activeTab ? `${uid}-panel-${String(activeTab.id)}` : undefined, role: "tabpanel", "aria-labelledby": activeTab ? `${uid}-tab-${String(activeTab.id)}` : undefined, className: styles.tabContent, children: activeTab === null || activeTab === void 0 ? void 0 : activeTab.content })] })] }));
|
|
122
122
|
}
|
|
123
123
|
Tabs.Item = TabsItem;
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Formats a Date as "dd
|
|
3
|
-
*
|
|
2
|
+
* Formats a Date as "dd/mm/yyyy" or "dd/mm/yyyy hh:mm:ss".
|
|
3
|
+
* Optionally returns user-friendly Danish labels like "I dag" or "I går".
|
|
4
|
+
*
|
|
5
|
+
* Examples:
|
|
6
|
+
* - formatDate(new Date(), { userFriendlyLabel: true }) => "I dag"
|
|
7
|
+
* - formatDate(new Date(), { userFriendlyLabel: true, showTime: true }) => "I dag, kl. 14:30"
|
|
8
|
+
* - formatDate(date, { showTime: true }) => "10/03/2026 14:30"
|
|
4
9
|
*
|
|
5
10
|
* @param date - The Date or date-parsable value to format
|
|
6
|
-
* @param options.
|
|
11
|
+
* @param options.showTime - If true, append time "hh:mm" or "hh:mm:ss"
|
|
12
|
+
* @param options.showSeconds - If true, append seconds when showTime is enabled
|
|
13
|
+
* @param options.userFriendlyLabel - If true, use "I dag" / "I går" when applicable
|
|
7
14
|
* @returns A formatted string
|
|
8
15
|
*/
|
|
9
16
|
export declare function formatDate(date: Date | string | number, options?: {
|
|
10
17
|
showTime?: boolean;
|
|
11
18
|
showSeconds?: boolean;
|
|
19
|
+
userFriendlyLabel?: boolean;
|
|
12
20
|
}): string;
|
|
@@ -1,26 +1,51 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Formats a Date as "dd
|
|
3
|
-
*
|
|
2
|
+
* Formats a Date as "dd/mm/yyyy" or "dd/mm/yyyy hh:mm:ss".
|
|
3
|
+
* Optionally returns user-friendly Danish labels like "I dag" or "I går".
|
|
4
|
+
*
|
|
5
|
+
* Examples:
|
|
6
|
+
* - formatDate(new Date(), { userFriendlyLabel: true }) => "I dag"
|
|
7
|
+
* - formatDate(new Date(), { userFriendlyLabel: true, showTime: true }) => "I dag, kl. 14:30"
|
|
8
|
+
* - formatDate(date, { showTime: true }) => "10/03/2026 14:30"
|
|
4
9
|
*
|
|
5
10
|
* @param date - The Date or date-parsable value to format
|
|
6
|
-
* @param options.
|
|
11
|
+
* @param options.showTime - If true, append time "hh:mm" or "hh:mm:ss"
|
|
12
|
+
* @param options.showSeconds - If true, append seconds when showTime is enabled
|
|
13
|
+
* @param options.userFriendlyLabel - If true, use "I dag" / "I går" when applicable
|
|
7
14
|
* @returns A formatted string
|
|
8
15
|
*/
|
|
9
16
|
export function formatDate(date, options = {}) {
|
|
10
17
|
const d = date instanceof Date ? date : new Date(date);
|
|
11
18
|
if (isNaN(d.getTime()))
|
|
12
19
|
return '';
|
|
20
|
+
const { showTime = false, showSeconds = false, userFriendlyLabel = false } = options;
|
|
13
21
|
const pad = (n) => n.toString().padStart(2, '0');
|
|
22
|
+
const formatTime = () => {
|
|
23
|
+
const hours = pad(d.getHours());
|
|
24
|
+
const minutes = pad(d.getMinutes());
|
|
25
|
+
if (!showSeconds)
|
|
26
|
+
return `${hours}:${minutes}`;
|
|
27
|
+
const seconds = pad(d.getSeconds());
|
|
28
|
+
return `${hours}:${minutes}:${seconds}`;
|
|
29
|
+
};
|
|
30
|
+
const isSameCalendarDay = (a, b) => a.getDate() === b.getDate() &&
|
|
31
|
+
a.getMonth() === b.getMonth() &&
|
|
32
|
+
a.getFullYear() === b.getFullYear();
|
|
33
|
+
if (userFriendlyLabel) {
|
|
34
|
+
const now = new Date();
|
|
35
|
+
const yesterday = new Date(now);
|
|
36
|
+
yesterday.setDate(now.getDate() - 1);
|
|
37
|
+
if (isSameCalendarDay(d, now)) {
|
|
38
|
+
return showTime ? `I dag, kl. ${formatTime()}` : 'I dag';
|
|
39
|
+
}
|
|
40
|
+
if (isSameCalendarDay(d, yesterday)) {
|
|
41
|
+
return showTime ? `I går, kl. ${formatTime()}` : 'I går';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
14
44
|
const day = pad(d.getDate());
|
|
15
45
|
const month = pad(d.getMonth() + 1);
|
|
16
46
|
const year = d.getFullYear();
|
|
17
47
|
const base = `${day}/${month}/${year}`;
|
|
18
|
-
if (!
|
|
48
|
+
if (!showTime)
|
|
19
49
|
return base;
|
|
20
|
-
|
|
21
|
-
const minutes = pad(d.getMinutes());
|
|
22
|
-
if (!options.showSeconds)
|
|
23
|
-
return `${base} ${hours}:${minutes}`;
|
|
24
|
-
const seconds = pad(d.getSeconds());
|
|
25
|
-
return `${base} ${hours}:${minutes}:${seconds}`;
|
|
50
|
+
return `${base} ${formatTime()}`;
|
|
26
51
|
}
|
package/package.json
CHANGED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
type RowId = string;
|
|
2
|
-
type UseAnimatedNewRowIdsArgs<T> = {
|
|
3
|
-
data: T[];
|
|
4
|
-
dataKey: keyof T;
|
|
5
|
-
enabled?: boolean;
|
|
6
|
-
animationDurationMs?: number;
|
|
7
|
-
};
|
|
8
|
-
export declare function useAnimatedNewRowIds<T extends Record<string, any>>({ data, dataKey, enabled, animationDurationMs, }: UseAnimatedNewRowIdsArgs<T>): Set<RowId>;
|
|
9
|
-
export {};
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { useEffect, useRef, useState } from 'react';
|
|
3
|
-
export function useAnimatedNewRowIds({ data, dataKey, enabled = true, animationDurationMs = 1000, }) {
|
|
4
|
-
const prevRowIdsRef = useRef(new Set());
|
|
5
|
-
const hasEstablishedBaselineRef = useRef(false);
|
|
6
|
-
const removeTimersRef = useRef(new Map());
|
|
7
|
-
const [newRowIds, setNewRowIds] = useState(new Set());
|
|
8
|
-
useEffect(() => {
|
|
9
|
-
const currentIds = new Set();
|
|
10
|
-
for (const row of data) {
|
|
11
|
-
currentIds.add(String(row[dataKey]));
|
|
12
|
-
}
|
|
13
|
-
if (!enabled) {
|
|
14
|
-
for (const timer of removeTimersRef.current.values()) {
|
|
15
|
-
window.clearTimeout(timer);
|
|
16
|
-
}
|
|
17
|
-
removeTimersRef.current.clear();
|
|
18
|
-
setNewRowIds(new Set());
|
|
19
|
-
prevRowIdsRef.current = currentIds;
|
|
20
|
-
hasEstablishedBaselineRef.current = currentIds.size > 0;
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
if (currentIds.size === 0) {
|
|
24
|
-
prevRowIdsRef.current = currentIds;
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
if (!hasEstablishedBaselineRef.current) {
|
|
28
|
-
prevRowIdsRef.current = currentIds;
|
|
29
|
-
hasEstablishedBaselineRef.current = true;
|
|
30
|
-
setNewRowIds(new Set());
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
const addedIds = [];
|
|
34
|
-
for (const id of currentIds) {
|
|
35
|
-
if (!prevRowIdsRef.current.has(id)) {
|
|
36
|
-
addedIds.push(id);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
if (addedIds.length > 0) {
|
|
40
|
-
setNewRowIds(prev => {
|
|
41
|
-
const next = new Set(prev);
|
|
42
|
-
for (const id of addedIds)
|
|
43
|
-
next.add(id);
|
|
44
|
-
return next;
|
|
45
|
-
});
|
|
46
|
-
for (const id of addedIds) {
|
|
47
|
-
const existingTimer = removeTimersRef.current.get(id);
|
|
48
|
-
if (existingTimer) {
|
|
49
|
-
window.clearTimeout(existingTimer);
|
|
50
|
-
}
|
|
51
|
-
const timer = window.setTimeout(() => {
|
|
52
|
-
setNewRowIds(prev => {
|
|
53
|
-
if (!prev.has(id))
|
|
54
|
-
return prev;
|
|
55
|
-
const next = new Set(prev);
|
|
56
|
-
next.delete(id);
|
|
57
|
-
return next;
|
|
58
|
-
});
|
|
59
|
-
removeTimersRef.current.delete(id);
|
|
60
|
-
}, animationDurationMs);
|
|
61
|
-
removeTimersRef.current.set(id, timer);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
prevRowIdsRef.current = currentIds;
|
|
65
|
-
}, [data, dataKey, enabled, animationDurationMs]);
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
const timers = removeTimersRef.current;
|
|
68
|
-
return () => {
|
|
69
|
-
for (const timer of timers.values()) {
|
|
70
|
-
window.clearTimeout(timer);
|
|
71
|
-
}
|
|
72
|
-
timers.clear();
|
|
73
|
-
};
|
|
74
|
-
}, []);
|
|
75
|
-
return newRowIds;
|
|
76
|
-
}
|