@jetbrains/ring-ui 8.0.0-beta.4 → 8.0.0-beta.5
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/components/alert/alert-actions.d.ts +4 -0
- package/components/alert/alert-actions.js +5 -0
- package/components/alert/alert-heading.d.ts +4 -0
- package/components/alert/alert-heading.js +6 -0
- package/components/alert/alert.css +63 -22
- package/components/alert/alert.d.ts +14 -1
- package/components/alert/alert.js +34 -19
- package/components/alert/container.css +1 -2
- package/components/alert-service/alert-service.d.ts +2 -1
- package/components/alert-service/alert-service.js +8 -4
- package/components/auth/auth-core.d.ts +5 -9
- package/components/auth/auth-core.js +56 -15
- package/components/button-group/button-group.css +5 -5
- package/components/date-picker/consts.d.ts +1 -0
- package/components/date-picker/date-input.js +6 -2
- package/components/header/header.css +3 -3
- package/components/http/http.d.ts +2 -2
- package/components/http/http.js +2 -2
- package/components/i18n/i18n.d.ts +1 -0
- package/components/i18n/messages.json +1 -0
- package/components/table/default-item-renderer.d.ts +7 -1
- package/components/table/default-item-renderer.js +18 -5
- package/components/table/internal/reorder-animation-context.d.ts +21 -0
- package/components/table/internal/reorder-animation-context.js +60 -0
- package/components/table/internal/reorder-handle.d.ts +9 -0
- package/components/table/internal/reorder-handle.js +334 -0
- package/components/table/internal/reorder-layout-context.d.ts +16 -0
- package/components/table/internal/reorder-layout-context.js +69 -0
- package/components/table/internal/table-header.d.ts +1 -4
- package/components/table/internal/table-header.js +32 -246
- package/components/table/internal/{virtual-items.d.ts → virtualization.d.ts} +10 -3
- package/components/table/internal/{virtual-items.js → virtualization.js} +8 -6
- package/components/table/item-virtualization.d.ts +5 -3
- package/components/table/item-virtualization.js +4 -6
- package/components/table/reorder-animation.d.ts +37 -0
- package/components/table/reorder-animation.js +11 -0
- package/components/table/reorder-item-layout.d.ts +32 -0
- package/components/table/reorder-item-layout.js +23 -0
- package/components/table/table-const.d.ts +0 -32
- package/components/table/table-const.js +0 -7
- package/components/table/table-primitives.d.ts +43 -0
- package/components/table/table-primitives.js +9 -0
- package/components/table/table-props.d.ts +29 -5
- package/components/table/table.css +17 -10
- package/components/table/table.d.ts +31 -5
- package/components/table/table.js +64 -35
- package/components/user-card/smart-user-card-tooltip.d.ts +1 -1
- package/components/util-stories.d.ts +2 -2
- package/package.json +12 -12
- package/components/table/internal/column-animation.d.ts +0 -16
- package/components/table/internal/column-animation.js +0 -45
|
@@ -1,4 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export declare function TableHeader<T>({ expectColumnReorder }: {
|
|
3
|
-
expectColumnReorder: ExpectColumnReorder;
|
|
4
|
-
}): import("react").JSX.Element | null;
|
|
1
|
+
export declare function TableHeader<T>(): import("react").JSX.Element | null;
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
/* eslint-disable no-nested-ternary
|
|
1
|
+
/* eslint-disable no-nested-ternary */
|
|
2
2
|
import { use, useCallback, useRef, useState } from 'react';
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
import arrowDownIcon from '@jetbrains/icons/arrow-12px-down';
|
|
5
5
|
import arrowUpIcon from '@jetbrains/icons/arrow-12px-up';
|
|
6
|
-
import dragIcon from '@jetbrains/icons/drag-12px';
|
|
7
6
|
import settingsIcon from '@jetbrains/icons/settings-12px';
|
|
8
7
|
import trashIcon from '@jetbrains/icons/trash-12px';
|
|
9
8
|
import unsortedIcon from '@jetbrains/icons/unsorted-12px';
|
|
10
|
-
import {
|
|
9
|
+
import { TablePropsContext } from '../table-const';
|
|
11
10
|
import Icon from '../../icon';
|
|
12
|
-
import { useComposedRef } from '../../global/compose-refs';
|
|
13
11
|
import { isWithinInteractiveElement } from '../../global/is-within-interactive-element';
|
|
14
|
-
import {
|
|
12
|
+
import { ReorderHandle } from './reorder-handle';
|
|
13
|
+
import { ReorderAnimationContext } from './reorder-animation-context';
|
|
14
|
+
import { ReorderLayoutContext, useReorderLayoutContextValue } from './reorder-layout-context';
|
|
15
|
+
import { useReorderItemLayout } from '../reorder-item-layout';
|
|
15
16
|
import styles from '../table.css';
|
|
16
|
-
export function TableHeader(
|
|
17
|
+
export function TableHeader() {
|
|
17
18
|
const { columns, noHeader, stickyHeader, columnEditing, onColumnEditingRequest, theadClassName, theadTrClassName } = use(TablePropsContext);
|
|
18
19
|
const [localColumnEditing, setLocalColumnEditing] = useState(false);
|
|
19
20
|
const effectiveColumnEditing = columnEditing ?? localColumnEditing;
|
|
@@ -36,25 +37,38 @@ export function TableHeader({ expectColumnReorder }) {
|
|
|
36
37
|
const handleEditColumnsButtonClick = useCallback(() => {
|
|
37
38
|
toggleColumnEditing('edit-button');
|
|
38
39
|
}, [toggleColumnEditing]);
|
|
40
|
+
const reorderContextValue = useReorderLayoutContextValue();
|
|
39
41
|
if (noHeader)
|
|
40
42
|
return null;
|
|
41
43
|
return (
|
|
42
44
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events
|
|
43
45
|
<thead className={classNames(theadClassName, effectiveColumnEditing && styles.theadColumnEditing, stickyHeader && styles.stickyHeader)} onClick={handleTheadClick}>
|
|
44
46
|
<tr className={theadTrClassName}>
|
|
45
|
-
|
|
47
|
+
<ReorderLayoutContext value={reorderContextValue}>
|
|
48
|
+
{columns.map((column, columnIndex) => (<TableHeaderCell key={column.key} columnIndex={columnIndex} columnEditing={effectiveColumnEditing} handleEditColumnsButtonClick={handleEditColumnsButtonClick}/>))}
|
|
49
|
+
</ReorderLayoutContext>
|
|
46
50
|
</tr>
|
|
47
51
|
</thead>);
|
|
48
52
|
}
|
|
49
|
-
function TableHeaderCell({ columnIndex, columnEditing, handleEditColumnsButtonClick,
|
|
53
|
+
function TableHeaderCell({ columnIndex, columnEditing, handleEditColumnsButtonClick, }) {
|
|
54
|
+
const ref = useRef(null);
|
|
50
55
|
const { columns, columnEditButton } = use(TablePropsContext);
|
|
51
56
|
const { key, name, renderHeader, sortOrder, deletable, canReorder, thClassName } = columns[columnIndex];
|
|
52
|
-
const
|
|
57
|
+
const { reorderAnimation } = use(ReorderAnimationContext);
|
|
53
58
|
const children = renderHeader ? renderHeader() : (name ?? String(key));
|
|
54
|
-
|
|
59
|
+
useReorderItemLayout({
|
|
60
|
+
index: columnIndex,
|
|
61
|
+
getBounds: () => {
|
|
62
|
+
const r = ref.current?.getBoundingClientRect();
|
|
63
|
+
return { start: r?.left ?? 0, end: r?.right ?? 0 };
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
return (<th ref={ref} className={classNames(styles.headerCell, reorderAnimation?.direction === 'columns' &&
|
|
67
|
+
reorderAnimation.index === columnIndex &&
|
|
68
|
+
reorderAnimation.className, thClassName)} scope='col' aria-sort={sortOrder}>
|
|
55
69
|
<div className={styles.headerCellInnerWrapper}>
|
|
56
70
|
<div className={styles.sortAndHeader}>
|
|
57
|
-
{canReorder && <ColumnReorderHandle columnIndex={columnIndex}
|
|
71
|
+
{canReorder && <ColumnReorderHandle columnIndex={columnIndex}/>}
|
|
58
72
|
{sortOrder ? (<SortButton columnIndex={columnIndex} aria-description={`Sort order: ${sortOrder}`}>
|
|
59
73
|
{children}
|
|
60
74
|
</SortButton>) : (children)}
|
|
@@ -89,7 +103,7 @@ function SortButton({ columnIndex, className, children, onClick, ...restProps })
|
|
|
89
103
|
if (!tableProps || !column) {
|
|
90
104
|
return null;
|
|
91
105
|
}
|
|
92
|
-
return (<button type='button' className={classNames(styles.
|
|
106
|
+
return (<button type='button' className={classNames(styles.tableButton, className)} onClick={handleClick} {...restProps}>
|
|
93
107
|
{children} <Icon glyph={glyph} aria-hidden/>
|
|
94
108
|
</button>);
|
|
95
109
|
}
|
|
@@ -99,255 +113,27 @@ function DeleteColumnButton({ columnIndex, className, onClick, ...restProps }) {
|
|
|
99
113
|
const handleClick = useCallback((e) => {
|
|
100
114
|
onClick?.(e);
|
|
101
115
|
if (!e.defaultPrevented) {
|
|
102
|
-
|
|
116
|
+
const columns = tableProps.columns;
|
|
117
|
+
tableProps.onColumnDelete?.(columns[columnIndex], columnIndex, columns);
|
|
103
118
|
}
|
|
104
119
|
}, [columnIndex, onClick, tableProps]);
|
|
105
120
|
if (!tableProps || !column) {
|
|
106
121
|
return null;
|
|
107
122
|
}
|
|
108
123
|
const hint = `Delete column ${column.name ?? String(column.key)}.`;
|
|
109
|
-
return (<button type='button' className={classNames(styles.
|
|
124
|
+
return (<button type='button' className={classNames(styles.tableButton, styles.deleteColumnButton, className)} onClick={handleClick} aria-label={hint} title={hint} {...restProps}>
|
|
110
125
|
<Icon glyph={trashIcon} aria-hidden='true'/>
|
|
111
126
|
</button>);
|
|
112
127
|
}
|
|
113
128
|
function EditColumnsButton({ columnEditing, ...props }) {
|
|
114
129
|
const { className, ...restProps } = props;
|
|
115
130
|
const hint = columnEditing ? 'Hide column controls.' : 'Show column controls.';
|
|
116
|
-
return (<button type='button' className={classNames(styles.
|
|
131
|
+
return (<button type='button' className={classNames(styles.tableButton, styles.editColumnsButton, className)} aria-pressed={columnEditing} aria-label={hint} title={hint} {...restProps}>
|
|
117
132
|
<Icon glyph={settingsIcon} aria-hidden='true'/>
|
|
118
133
|
</button>);
|
|
119
134
|
}
|
|
120
|
-
function ColumnReorderHandle({ columnIndex,
|
|
121
|
-
|
|
122
|
-
const column = tableProps?.columns[columnIndex];
|
|
123
|
-
const canReorder = column?.canReorder;
|
|
124
|
-
const canReorderCb = useCallback((index) => canReorder === true || (typeof canReorder === 'function' && canReorder(index, tableProps.columns)), [canReorder, tableProps]);
|
|
125
|
-
const localRef = useRef(null);
|
|
126
|
-
const composedRef = useComposedRef(localRef, userRef);
|
|
127
|
-
const activeDragRef = useRef(null);
|
|
128
|
-
const getDragFrame = useCallback(() => {
|
|
129
|
-
return document.body.querySelector(`.${styles.columnDragFrame}`);
|
|
130
|
-
}, []);
|
|
131
|
-
const renderDragFrame = useCallback((clientX) => {
|
|
132
|
-
const { startColumnIndex, startClientX, headerTopClientY, columnsClientX, indicatorHeight } = activeDragRef.current;
|
|
133
|
-
const { l, r } = columnsClientX[startColumnIndex];
|
|
134
|
-
let dragFrame = getDragFrame();
|
|
135
|
-
if (!dragFrame) {
|
|
136
|
-
dragFrame = document.createElement('div');
|
|
137
|
-
dragFrame.className = styles.columnDragFrame;
|
|
138
|
-
dragFrame.style.setProperty('top', `calc(max(0px, ${headerTopClientY - 2}px))`);
|
|
139
|
-
dragFrame.style.setProperty('width', `${r - l}px`);
|
|
140
|
-
dragFrame.style.setProperty('height', indicatorHeight);
|
|
141
|
-
document.body.appendChild(dragFrame);
|
|
142
|
-
}
|
|
143
|
-
dragFrame.style.setProperty('left', `${l + clientX - startClientX}px`);
|
|
144
|
-
}, [getDragFrame]);
|
|
145
|
-
const translateXButton = useCallback((clientX) => {
|
|
146
|
-
if (!localRef.current || !activeDragRef.current)
|
|
147
|
-
return;
|
|
148
|
-
const { startClientX } = activeDragRef.current;
|
|
149
|
-
localRef.current.style.setProperty('transform', `translateX(${clientX - startClientX}px)`);
|
|
150
|
-
}, []);
|
|
151
|
-
const getClosestInsertionPoint = useCallback((clientX) => {
|
|
152
|
-
let bestDistance = Infinity;
|
|
153
|
-
let index = -1;
|
|
154
|
-
let after = false;
|
|
155
|
-
activeDragRef.current?.columnsClientX.forEach(({ l, r }, i) => {
|
|
156
|
-
const distanceToLeft = Math.abs(l - clientX);
|
|
157
|
-
const distanceToRight = Math.abs(r - clientX);
|
|
158
|
-
if (distanceToLeft < bestDistance && canReorderCb(i)) {
|
|
159
|
-
bestDistance = distanceToLeft;
|
|
160
|
-
index = i;
|
|
161
|
-
after = false;
|
|
162
|
-
}
|
|
163
|
-
if (distanceToRight < bestDistance && canReorderCb(i + 1)) {
|
|
164
|
-
bestDistance = distanceToRight;
|
|
165
|
-
index = i;
|
|
166
|
-
after = true;
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
return { index, after };
|
|
170
|
-
}, [canReorderCb]);
|
|
171
|
-
const getInsertionIndicator = useCallback(() => {
|
|
172
|
-
return document.body.querySelector(`.${styles.columnInsertionIndicator}`);
|
|
173
|
-
}, []);
|
|
174
|
-
const renderInsertionIndicator = useCallback((insertionPoint) => {
|
|
175
|
-
const { index, after } = insertionPoint;
|
|
176
|
-
const { headerTopClientY, columnsClientX, indicatorHeight } = activeDragRef.current;
|
|
177
|
-
const { l, r } = columnsClientX[index];
|
|
178
|
-
let indicator = getInsertionIndicator();
|
|
179
|
-
if (!indicator) {
|
|
180
|
-
indicator = document.createElement('div');
|
|
181
|
-
indicator.className = styles.columnInsertionIndicator;
|
|
182
|
-
indicator.style.setProperty('top', `${headerTopClientY}px`);
|
|
183
|
-
indicator.style.setProperty('height', indicatorHeight);
|
|
184
|
-
document.body.appendChild(indicator);
|
|
185
|
-
}
|
|
186
|
-
indicator.style.setProperty('left', `${(after ? r : l) - 1}px`);
|
|
187
|
-
}, [getInsertionIndicator]);
|
|
188
|
-
const cleanupDrag = useCallback(() => {
|
|
189
|
-
if (activeDragRef.current) {
|
|
190
|
-
activeDragRef.current.cleanup();
|
|
191
|
-
activeDragRef.current = null;
|
|
192
|
-
}
|
|
193
|
-
const btn = localRef.current;
|
|
194
|
-
if (btn) {
|
|
195
|
-
btn.style.removeProperty('transform');
|
|
196
|
-
btn.style.removeProperty('transition');
|
|
197
|
-
}
|
|
198
|
-
getDragFrame()?.remove();
|
|
199
|
-
getInsertionIndicator()?.remove();
|
|
200
|
-
}, [getDragFrame, getInsertionIndicator]);
|
|
201
|
-
const animateNoChangeThenCleanup = useCallback(() => {
|
|
202
|
-
if (activeDragRef.current?.state === 'is-dragging') {
|
|
203
|
-
activeDragRef.current.state = 'ended-with-no-change';
|
|
204
|
-
activeDragRef.current.cleanup();
|
|
205
|
-
activeDragRef.current.cleanup = () => { };
|
|
206
|
-
const dragFrame = getDragFrame();
|
|
207
|
-
if (dragFrame) {
|
|
208
|
-
const { columnsClientX, startColumnIndex } = activeDragRef.current;
|
|
209
|
-
dragFrame.style.left = `${columnsClientX[startColumnIndex].l}px`;
|
|
210
|
-
dragFrame.style.opacity = '0';
|
|
211
|
-
dragFrame.style.transition = 'left var(--ring-ease), opacity var(--ring-ease)';
|
|
212
|
-
}
|
|
213
|
-
const indicator = getInsertionIndicator();
|
|
214
|
-
if (indicator) {
|
|
215
|
-
indicator.style.opacity = '0';
|
|
216
|
-
indicator.style.transition = 'opacity var(--ring-ease)';
|
|
217
|
-
}
|
|
218
|
-
const btn = localRef.current;
|
|
219
|
-
if (btn) {
|
|
220
|
-
btn.style.transform = 'translateX(0)';
|
|
221
|
-
btn.style.transition = 'transform var(--ring-ease)';
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
const ringEaseMs = parseCssDuration(window.getComputedStyle(document.documentElement).getPropertyValue('--ring-ease'));
|
|
225
|
-
setTimeout(cleanupDrag, ringEaseMs);
|
|
226
|
-
}, [cleanupDrag, getDragFrame, getInsertionIndicator]);
|
|
227
|
-
const handlePointerDown = useCallback((e) => {
|
|
228
|
-
onPointerDown?.(e);
|
|
229
|
-
if (e.defaultPrevented)
|
|
230
|
-
return;
|
|
231
|
-
const { clientX: startClientX, pointerId, currentTarget } = e;
|
|
232
|
-
const thead = currentTarget.closest('thead');
|
|
233
|
-
const table = thead?.closest('table');
|
|
234
|
-
if (!thead || !table)
|
|
235
|
-
return;
|
|
236
|
-
const headerTopClientY = thead.getBoundingClientRect().top;
|
|
237
|
-
const columnsClientX = [...thead.querySelectorAll('th')].map(th => {
|
|
238
|
-
const rect = th.getBoundingClientRect();
|
|
239
|
-
return { l: rect.x, r: rect.x + rect.width };
|
|
240
|
-
});
|
|
241
|
-
const { bottom } = table.getBoundingClientRect();
|
|
242
|
-
const visibleTableHeight = bottom - headerTopClientY;
|
|
243
|
-
const viewportBottomRelativeToHeaderTop = window.innerHeight - headerTopClientY;
|
|
244
|
-
const indicatorHeight = `min(${visibleTableHeight}px, calc(${viewportBottomRelativeToHeaderTop}px - .5rem))`;
|
|
245
|
-
currentTarget.setPointerCapture(pointerId);
|
|
246
|
-
function keydownListener(keyEvent) {
|
|
247
|
-
if (keyEvent.key === 'Escape') {
|
|
248
|
-
animateNoChangeThenCleanup();
|
|
249
|
-
keyEvent.stopPropagation();
|
|
250
|
-
keyEvent.preventDefault();
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
document.addEventListener('keydown', keydownListener); // In Safari, the button is not focused
|
|
254
|
-
currentTarget.style.cursor = 'grabbing';
|
|
255
|
-
activeDragRef.current = {
|
|
256
|
-
state: 'is-dragging',
|
|
257
|
-
startColumnIndex: columnIndex,
|
|
258
|
-
startClientX,
|
|
259
|
-
headerTopClientY,
|
|
260
|
-
columnsClientX,
|
|
261
|
-
indicatorHeight,
|
|
262
|
-
cleanup: () => {
|
|
263
|
-
document.removeEventListener('keydown', keydownListener);
|
|
264
|
-
currentTarget.releasePointerCapture(pointerId);
|
|
265
|
-
currentTarget.style.removeProperty('cursor');
|
|
266
|
-
},
|
|
267
|
-
};
|
|
268
|
-
renderDragFrame(startClientX);
|
|
269
|
-
e.preventDefault();
|
|
270
|
-
}, [animateNoChangeThenCleanup, columnIndex, onPointerDown, renderDragFrame]);
|
|
271
|
-
const handlePointerMove = useCallback((e) => {
|
|
272
|
-
onPointerMove?.(e);
|
|
273
|
-
if (e.defaultPrevented || activeDragRef.current?.state !== 'is-dragging')
|
|
274
|
-
return;
|
|
275
|
-
const { clientX } = e;
|
|
276
|
-
renderDragFrame(clientX);
|
|
277
|
-
translateXButton(clientX);
|
|
278
|
-
const insertionPoint = getClosestInsertionPoint(clientX);
|
|
279
|
-
if (insertionPoint)
|
|
280
|
-
renderInsertionIndicator(insertionPoint);
|
|
281
|
-
}, [getClosestInsertionPoint, translateXButton, onPointerMove, renderDragFrame, renderInsertionIndicator]);
|
|
282
|
-
const handlePointerUp = useCallback((e) => {
|
|
283
|
-
onPointerUp?.(e);
|
|
284
|
-
if (e.defaultPrevented || activeDragRef.current?.state !== 'is-dragging')
|
|
285
|
-
return;
|
|
286
|
-
const table = e.currentTarget.closest('table');
|
|
287
|
-
if (!table) {
|
|
288
|
-
cleanupDrag();
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
const { index, after } = getClosestInsertionPoint(e.clientX);
|
|
292
|
-
const insertionIndex = after ? index + 1 : index;
|
|
293
|
-
if (insertionIndex === columnIndex || insertionIndex === columnIndex + 1) {
|
|
294
|
-
animateNoChangeThenCleanup();
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
cleanupDrag();
|
|
298
|
-
expectColumnReorder({ fromIndex: columnIndex, insertionIndex });
|
|
299
|
-
tableProps.onColumnReorder?.(columnIndex, insertionIndex, tableProps.columns);
|
|
300
|
-
}, [
|
|
301
|
-
animateNoChangeThenCleanup,
|
|
302
|
-
cleanupDrag,
|
|
303
|
-
columnIndex,
|
|
304
|
-
expectColumnReorder,
|
|
305
|
-
getClosestInsertionPoint,
|
|
306
|
-
onPointerUp,
|
|
307
|
-
tableProps,
|
|
308
|
-
]);
|
|
309
|
-
const handlePointerCancel = useCallback((e) => {
|
|
310
|
-
onPointerCancel?.(e);
|
|
311
|
-
if (e.defaultPrevented)
|
|
312
|
-
return;
|
|
313
|
-
animateNoChangeThenCleanup();
|
|
314
|
-
}, [animateNoChangeThenCleanup, onPointerCancel]);
|
|
315
|
-
const handleLostPointerCapture = useCallback((e) => {
|
|
316
|
-
onLostPointerCapture?.(e);
|
|
317
|
-
if (e.defaultPrevented)
|
|
318
|
-
return;
|
|
319
|
-
animateNoChangeThenCleanup();
|
|
320
|
-
}, [animateNoChangeThenCleanup, onLostPointerCapture]);
|
|
321
|
-
const handleKeyDown = useCallback((e) => {
|
|
322
|
-
onKeyDown?.(e);
|
|
323
|
-
if (e.defaultPrevented || !tableProps)
|
|
324
|
-
return;
|
|
325
|
-
const left = e.key === 'ArrowLeft';
|
|
326
|
-
const right = e.key === 'ArrowRight';
|
|
327
|
-
if (!left && !right)
|
|
328
|
-
return;
|
|
329
|
-
const initialInsertionIndex = left ? columnIndex - 1 : columnIndex + 2;
|
|
330
|
-
const step = left ? -1 : 1;
|
|
331
|
-
// eslint-disable-next-line yoda
|
|
332
|
-
for (let i = initialInsertionIndex; 0 <= i && i <= tableProps.columns.length; i += step) {
|
|
333
|
-
if (canReorderCb(i)) {
|
|
334
|
-
expectColumnReorder({ fromIndex: columnIndex, insertionIndex: i });
|
|
335
|
-
tableProps.onColumnReorder?.(columnIndex, i, tableProps.columns);
|
|
336
|
-
e.preventDefault();
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}, [canReorderCb, columnIndex, expectColumnReorder, onKeyDown, tableProps]);
|
|
341
|
-
if (!tableProps || !column) {
|
|
342
|
-
return null;
|
|
343
|
-
}
|
|
344
|
-
const hint = `Reorder column ${column.name ?? String(column.key)}.`;
|
|
345
|
-
const description = 'Use Left and Right arrow keys to move the column.';
|
|
346
|
-
return (
|
|
347
|
-
// eslint-disable-next-line jsx-a11y/role-supports-aria-props
|
|
348
|
-
<button ref={composedRef} type='button' className={classNames(styles.headerButton, styles.columnReorderHandle, className)} aria-label={hint} aria-description={description} aria-keyshortcuts='ArrowLeft ArrowRight' onKeyDown={handleKeyDown} onPointerDown={handlePointerDown} onPointerMove={handlePointerMove} onPointerUp={handlePointerUp} onPointerCancel={handlePointerCancel} onLostPointerCapture={handleLostPointerCapture} {...restProps}>
|
|
349
|
-
<Icon glyph={dragIcon} aria-hidden='true'/>
|
|
350
|
-
</button>);
|
|
135
|
+
function ColumnReorderHandle({ columnIndex, ...restProps }) {
|
|
136
|
+
return <ReorderHandle direction='columns' index={columnIndex} {...restProps}/>;
|
|
351
137
|
}
|
|
352
138
|
/**
|
|
353
139
|
* Reserves the space for the reorder handle and prevents layout shift when the handle appears on hover.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type RefObject } from 'react';
|
|
2
|
+
import { type IntersectionObserverHandle } from '../../global/intersection-observer-context';
|
|
2
3
|
export type VirtualItem = MaterializedItem | Spacer;
|
|
3
4
|
interface MaterializedItem {
|
|
4
5
|
type: 'materialized';
|
|
@@ -12,7 +13,11 @@ interface Spacer {
|
|
|
12
13
|
key: string;
|
|
13
14
|
}
|
|
14
15
|
type CollapseItemIntoSpacerCallback = (index: number, height: number) => void;
|
|
15
|
-
|
|
16
|
+
interface VirtualizationContextValue {
|
|
17
|
+
intersectionObserverHandle: IntersectionObserverHandle;
|
|
18
|
+
collapseItemIntoSpacer: CollapseItemIntoSpacerCallback;
|
|
19
|
+
}
|
|
20
|
+
export declare const VirtualizationContext: import("react").Context<VirtualizationContextValue>;
|
|
16
21
|
export declare function useVirtualItems<T>({ enabled, data, data: { length }, scrollerRef, tableRef, estimateHeight, lookaheadPx, retentionMarginPx, minScrollAndResizeDeltaPx, }: {
|
|
17
22
|
enabled: boolean;
|
|
18
23
|
data: readonly T[];
|
|
@@ -24,8 +29,10 @@ export declare function useVirtualItems<T>({ enabled, data, data: { length }, sc
|
|
|
24
29
|
minScrollAndResizeDeltaPx: number;
|
|
25
30
|
}): {
|
|
26
31
|
virtualItems: VirtualItem[];
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
virtualizationContextValue: {
|
|
33
|
+
intersectionObserverHandle: IntersectionObserverHandle;
|
|
34
|
+
collapseItemIntoSpacer: (index: number, height: number) => void;
|
|
35
|
+
};
|
|
29
36
|
};
|
|
30
37
|
export declare function SpacerRow({ spacer: { from, to, height }, colSpan }: {
|
|
31
38
|
spacer: Spacer;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { createContext, useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
-
import { useIntersectionObserverHandle } from '../../global/intersection-observer-context';
|
|
2
|
+
import { useIntersectionObserverHandle, } from '../../global/intersection-observer-context';
|
|
3
3
|
import { setTimeoutWithCleanup } from '../../global/schedule-with-cleanup';
|
|
4
4
|
import styles from '../table.css';
|
|
5
|
-
export const
|
|
5
|
+
export const VirtualizationContext = createContext({
|
|
6
|
+
intersectionObserverHandle: { observe: () => () => { } },
|
|
7
|
+
collapseItemIntoSpacer: () => { },
|
|
8
|
+
});
|
|
6
9
|
/**
|
|
7
10
|
* RAF is somewhat too frequent. Most updates happen on virtualization boundaries
|
|
8
11
|
* within the invisible overscroll area, so they don't require such a high update rate.
|
|
@@ -151,16 +154,15 @@ export function useVirtualItems({ enabled, data, data: { length }, scrollerRef,
|
|
|
151
154
|
throttle,
|
|
152
155
|
]);
|
|
153
156
|
const intersectionObserverHandle = useIntersectionObserverHandle(scrollerRef, scrollerRef ? retentionMarginPx : undefined, !scrollerRef ? retentionMarginPx : undefined);
|
|
154
|
-
|
|
157
|
+
function collapseItemIntoSpacer(index, height) {
|
|
155
158
|
if (!enabled)
|
|
156
159
|
return;
|
|
157
160
|
itemsMaterialization.current[index] = height;
|
|
158
161
|
throttle(recomputeVirtualItems);
|
|
159
|
-
}
|
|
162
|
+
}
|
|
160
163
|
return {
|
|
161
164
|
virtualItems,
|
|
162
|
-
intersectionObserverHandle,
|
|
163
|
-
collapseItemIntoSpacer,
|
|
165
|
+
virtualizationContextValue: { intersectionObserverHandle, collapseItemIntoSpacer },
|
|
164
166
|
};
|
|
165
167
|
}
|
|
166
168
|
function getAllVirtualizedItems(data, estimateHeight) {
|
|
@@ -13,13 +13,15 @@ export declare function useItemVirtualization({ index, refs, onIntersectionChang
|
|
|
13
13
|
* When multiple elements are provided, the virtualization callback receives
|
|
14
14
|
* the intersection state of all of them.
|
|
15
15
|
*
|
|
16
|
-
* If you pass multiple refs,
|
|
17
|
-
*
|
|
16
|
+
* If you pass multiple refs, pass a stable array reference to avoid restarting
|
|
17
|
+
* observation on every render — for example, memoize it with `useMemo()`,
|
|
18
|
+
* unless you use the React Compiler.
|
|
18
19
|
*/
|
|
19
20
|
refs: RefObject<HTMLElement | null> | RefObject<HTMLElement | null>[];
|
|
20
21
|
/**
|
|
21
22
|
* Invoked when the `isIntersecting` state of the observed elements changes.
|
|
22
|
-
*
|
|
23
|
+
* Pass a stable function reference to avoid restarting observation on every render —
|
|
24
|
+
* for example, wrap it with `useCallback()`, unless you use the React Compiler.
|
|
23
25
|
*
|
|
24
26
|
* @param intersectionStates - Current intersection state of every observed element.
|
|
25
27
|
* Entries are initially `undefined` until the corresponding element
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { use, useEffect } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { CollapseItemIntoSpacerContext } from './internal/virtual-items';
|
|
2
|
+
import { VirtualizationContext } from './internal/virtualization';
|
|
4
3
|
/**
|
|
5
4
|
* Use in an item renderer to control item virtualization.
|
|
6
5
|
*/
|
|
7
6
|
export function useItemVirtualization({ index, refs, onIntersectionChange, }) {
|
|
8
|
-
const
|
|
9
|
-
const collapseItemIntoSpacer = use(CollapseItemIntoSpacerContext);
|
|
7
|
+
const { intersectionObserverHandle, collapseItemIntoSpacer } = use(VirtualizationContext);
|
|
10
8
|
useEffect(() => {
|
|
11
9
|
const intersectionStates = Array.isArray(refs) ? refs.map(() => undefined) : [undefined];
|
|
12
10
|
const elements = Array.isArray(refs) ? refs.map(r => r.current) : [refs.current];
|
|
@@ -14,7 +12,7 @@ export function useItemVirtualization({ index, refs, onIntersectionChange, }) {
|
|
|
14
12
|
elements.forEach((element, elementIndex) => {
|
|
15
13
|
if (!element)
|
|
16
14
|
return;
|
|
17
|
-
const cleanup =
|
|
15
|
+
const cleanup = intersectionObserverHandle.observe(element, isIntersecting => {
|
|
18
16
|
intersectionStates[elementIndex] = isIntersecting;
|
|
19
17
|
const height = onIntersectionChange(intersectionStates, elementIndex, elements);
|
|
20
18
|
if (height != null) {
|
|
@@ -24,5 +22,5 @@ export function useItemVirtualization({ index, refs, onIntersectionChange, }) {
|
|
|
24
22
|
cleanups.push(cleanup);
|
|
25
23
|
});
|
|
26
24
|
return () => cleanups.forEach(cleanup => cleanup());
|
|
27
|
-
}, [collapseItemIntoSpacer,
|
|
25
|
+
}, [collapseItemIntoSpacer, intersectionObserverHandle, index, onIntersectionChange, refs]);
|
|
28
26
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Information about a column or item currently being animated after reorder.
|
|
3
|
+
*
|
|
4
|
+
* Available through {@link useReorderAnimation} to allow custom cell
|
|
5
|
+
* renderers to animate reordered columns and items consistently with the default
|
|
6
|
+
* table renderer.
|
|
7
|
+
*/
|
|
8
|
+
export interface ReorderAnimation {
|
|
9
|
+
/**
|
|
10
|
+
* What is animated: columns or items (rows).
|
|
11
|
+
*/
|
|
12
|
+
direction: 'columns' | 'items';
|
|
13
|
+
/**
|
|
14
|
+
* Index of the column or item being animated.
|
|
15
|
+
*/
|
|
16
|
+
index: number;
|
|
17
|
+
/**
|
|
18
|
+
* Current animation phase.
|
|
19
|
+
*/
|
|
20
|
+
phase: 'initial' | 'fade-out';
|
|
21
|
+
/**
|
|
22
|
+
* CSS class to apply to the animated row, cell, or another element used to
|
|
23
|
+
* render the animation on this phase.
|
|
24
|
+
*
|
|
25
|
+
* The class sets `background-color` and `transition`. If your custom styles
|
|
26
|
+
* also define `transition` on the same element, they may conflict with the
|
|
27
|
+
* animation.
|
|
28
|
+
*/
|
|
29
|
+
className: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Provides information about the currently animated column or item after reorder.
|
|
33
|
+
*
|
|
34
|
+
* Use in a custom cell renderer to animate reordered columns and items
|
|
35
|
+
* consistently with the default table renderer.
|
|
36
|
+
*/
|
|
37
|
+
export declare function useReorderAnimation(): ReorderAnimation | null;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { use } from 'react';
|
|
2
|
+
import { ReorderAnimationContext } from './internal/reorder-animation-context';
|
|
3
|
+
/**
|
|
4
|
+
* Provides information about the currently animated column or item after reorder.
|
|
5
|
+
*
|
|
6
|
+
* Use in a custom cell renderer to animate reordered columns and items
|
|
7
|
+
* consistently with the default table renderer.
|
|
8
|
+
*/
|
|
9
|
+
export function useReorderAnimation() {
|
|
10
|
+
return use(ReorderAnimationContext).reorderAnimation;
|
|
11
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registers the physical boundaries of a custom-rendered item with the reorder
|
|
3
|
+
* system, so that the insertion indicator and insertion point calculation are
|
|
4
|
+
* correct for items that span multiple rows or have non-standard sizing.
|
|
5
|
+
*
|
|
6
|
+
* `DefaultItemRenderer` calls this automatically. Use this hook in a custom item
|
|
7
|
+
* renderer when the default boundary measurement is insufficient — for example,
|
|
8
|
+
* when the item spans multiple rows, or when you don't use
|
|
9
|
+
* the `DefaultItemRenderer` at all.
|
|
10
|
+
*
|
|
11
|
+
* If you include `DefaultItemRenderer` inside your custom renderer, set its
|
|
12
|
+
* `noReorderLayout` prop to `true` to prevent double registration.
|
|
13
|
+
*/
|
|
14
|
+
export declare function useReorderItemLayout({ disabled, index, getBounds, }: {
|
|
15
|
+
/**
|
|
16
|
+
* When `true`, the item is not registered. Use to disable without violating
|
|
17
|
+
* the rules of hooks.
|
|
18
|
+
*/
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Index of the item in the `data` array.
|
|
22
|
+
*/
|
|
23
|
+
index: number;
|
|
24
|
+
/**
|
|
25
|
+
* Returns the item's top and bottom client coordinates (in pixels, relative
|
|
26
|
+
* to the viewport).
|
|
27
|
+
*/
|
|
28
|
+
getBounds: () => {
|
|
29
|
+
start: number;
|
|
30
|
+
end: number;
|
|
31
|
+
};
|
|
32
|
+
}): void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { use, useEffect } from 'react';
|
|
2
|
+
import { ReorderLayoutContext } from './internal/reorder-layout-context';
|
|
3
|
+
/**
|
|
4
|
+
* Registers the physical boundaries of a custom-rendered item with the reorder
|
|
5
|
+
* system, so that the insertion indicator and insertion point calculation are
|
|
6
|
+
* correct for items that span multiple rows or have non-standard sizing.
|
|
7
|
+
*
|
|
8
|
+
* `DefaultItemRenderer` calls this automatically. Use this hook in a custom item
|
|
9
|
+
* renderer when the default boundary measurement is insufficient — for example,
|
|
10
|
+
* when the item spans multiple rows, or when you don't use
|
|
11
|
+
* the `DefaultItemRenderer` at all.
|
|
12
|
+
*
|
|
13
|
+
* If you include `DefaultItemRenderer` inside your custom renderer, set its
|
|
14
|
+
* `noReorderLayout` prop to `true` to prevent double registration.
|
|
15
|
+
*/
|
|
16
|
+
export function useReorderItemLayout({ disabled, index, getBounds, }) {
|
|
17
|
+
const { registerReorderItem } = use(ReorderLayoutContext);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (disabled)
|
|
20
|
+
return;
|
|
21
|
+
return registerReorderItem(index, getBounds);
|
|
22
|
+
}, [index, getBounds, registerReorderItem, disabled]);
|
|
23
|
+
}
|
|
@@ -4,38 +4,6 @@ import type { TableProps } from './table-props';
|
|
|
4
4
|
* Cast to `Context<TableProps<T>>` in usage place.
|
|
5
5
|
*/
|
|
6
6
|
export declare const TablePropsContext: import("react").Context<TableProps<unknown> | null>;
|
|
7
|
-
/**
|
|
8
|
-
* Information about a column reorder animation.
|
|
9
|
-
*
|
|
10
|
-
* Available through {@link ColumnAnimationContext} to allow custom cell
|
|
11
|
-
* renderers to animate reordered columns consistently with the default
|
|
12
|
-
* table renderer.
|
|
13
|
-
*/
|
|
14
|
-
export interface ColumnAnimation {
|
|
15
|
-
/**
|
|
16
|
-
* Index of the column being animated.
|
|
17
|
-
*/
|
|
18
|
-
columnIndex: number;
|
|
19
|
-
/**
|
|
20
|
-
* Current animation phase.
|
|
21
|
-
*/
|
|
22
|
-
phase: 'initial' | 'fade-out';
|
|
23
|
-
/**
|
|
24
|
-
* CSS class to apply to the animated cell or another element used to
|
|
25
|
-
* render the animation.
|
|
26
|
-
*
|
|
27
|
-
* The class only defines the `background-color` and `transition`
|
|
28
|
-
* properties.
|
|
29
|
-
*/
|
|
30
|
-
cellClassName: string;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Provides information about the currently animated column.
|
|
34
|
-
*
|
|
35
|
-
* Use in a custom cell renderer to animate reordered columns
|
|
36
|
-
* consistently with the default table renderer.
|
|
37
|
-
*/
|
|
38
|
-
export declare const ColumnAnimationContext: import("react").Context<ColumnAnimation | null>;
|
|
39
7
|
/**
|
|
40
8
|
* When a row only contains unformatted single-line text, it will be exactly of this height.
|
|
41
9
|
*/
|
|
@@ -4,13 +4,6 @@ import { createContext } from 'react';
|
|
|
4
4
|
* Cast to `Context<TableProps<T>>` in usage place.
|
|
5
5
|
*/
|
|
6
6
|
export const TablePropsContext = createContext(null);
|
|
7
|
-
/**
|
|
8
|
-
* Provides information about the currently animated column.
|
|
9
|
-
*
|
|
10
|
-
* Use in a custom cell renderer to animate reordered columns
|
|
11
|
-
* consistently with the default table renderer.
|
|
12
|
-
*/
|
|
13
|
-
export const ColumnAnimationContext = createContext(null);
|
|
14
7
|
/**
|
|
15
8
|
* When a row only contains unformatted single-line text, it will be exactly of this height.
|
|
16
9
|
*/
|