@jetbrains/ring-ui 8.0.0-beta.2 → 8.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/babel.config.js +1 -1
  2. package/components/auth-dialog-service/auth-dialog-service.js +2 -2
  3. package/components/collapse/collapse-content.js +2 -2
  4. package/components/collapse/collapse-control.js +2 -2
  5. package/components/collapse/collapse.js +2 -2
  6. package/components/confirm-service/confirm-service.js +2 -2
  7. package/components/data-list/data-list.d.ts +1 -1
  8. package/components/data-list/data-list.mock.d.ts +1 -1
  9. package/components/data-list/item.d.ts +1 -1
  10. package/components/data-list/selection.d.ts +1 -1
  11. package/components/data-list/selection.js +1 -1
  12. package/components/date-picker/months.js +2 -2
  13. package/components/date-picker/use-scroll-behavior.js +5 -6
  14. package/components/date-picker/years.js +2 -2
  15. package/components/dialog/dialog.d.ts +2 -2
  16. package/components/dialog/dialog.js +2 -2
  17. package/components/dropdown-menu/dropdown-menu.d.ts +4 -4
  18. package/components/dropdown-menu/dropdown-menu.js +4 -4
  19. package/components/editable-heading/editable-heading.d.ts +1 -2
  20. package/components/editable-heading/editable-heading.js +5 -6
  21. package/components/expand/collapsible-group.d.ts +5 -1
  22. package/components/expand/collapsible-group.js +12 -12
  23. package/components/global/create-stateful-context.js +5 -5
  24. package/components/global/intersection-observer-context.d.ts +2 -2
  25. package/components/global/intersection-observer-context.js +5 -5
  26. package/components/global/rerender-hoc.d.ts +4 -2
  27. package/components/global/rerender-hoc.js +4 -4
  28. package/components/{legacy-table/selection.d.ts → global/table-selection.d.ts} +14 -14
  29. package/components/{legacy-table/selection.js → global/table-selection.js} +1 -1
  30. package/components/global/theme.d.ts +4 -3
  31. package/components/global/theme.js +8 -8
  32. package/components/i18n/i18n-context.js +1 -1
  33. package/components/island/adaptive-island-hoc.js +4 -4
  34. package/components/island/content.d.ts +7 -2
  35. package/components/island/content.js +5 -5
  36. package/components/legacy-table/cell.js +1 -1
  37. package/components/legacy-table/header-cell.js +1 -1
  38. package/components/legacy-table/header.js +1 -1
  39. package/components/legacy-table/multitable.d.ts +1 -1
  40. package/components/legacy-table/row.js +1 -1
  41. package/components/legacy-table/selection-adapter.d.ts +3 -3
  42. package/components/legacy-table/selection-shortcuts-hoc.d.ts +5 -5
  43. package/components/legacy-table/simple-table.d.ts +2 -2
  44. package/components/legacy-table/simple-table.js +3 -3
  45. package/components/legacy-table/smart-table.d.ts +5 -5
  46. package/components/legacy-table/smart-table.js +3 -3
  47. package/components/legacy-table/table.js +1 -1
  48. package/components/login-dialog/service.js +2 -2
  49. package/components/popup/popup.target.d.ts +3 -2
  50. package/components/popup/popup.target.js +4 -6
  51. package/components/query-assist/query-assist.d.ts +3 -1
  52. package/components/query-assist/query-assist.js +2 -2
  53. package/components/radio/radio-item.d.ts +3 -3
  54. package/components/radio/radio-item.js +3 -4
  55. package/components/radio/radio.d.ts +2 -2
  56. package/components/radio/radio.js +1 -1
  57. package/components/select/select.d.ts +3 -1
  58. package/components/slider/slider.js +4 -5
  59. package/components/tab-trap/tab-trap.d.ts +3 -3
  60. package/components/tab-trap/tab-trap.js +3 -5
  61. package/components/table/default-item-renderer.d.ts +20 -9
  62. package/components/table/default-item-renderer.js +15 -36
  63. package/components/table/table-component.d.ts +43 -16
  64. package/components/table/table-component.js +66 -37
  65. package/components/table/table-primitives.d.ts +28 -0
  66. package/components/table/{table-base.js → table-primitives.js} +22 -22
  67. package/components/table/table-row-focus.d.ts +4 -0
  68. package/components/table/table-row-focus.js +42 -0
  69. package/components/table/table-virtualize.d.ts +3 -3
  70. package/components/table/table-virtualize.js +13 -14
  71. package/components/table/table.d.ts +9 -24
  72. package/components/tags-input/tags-input.d.ts +3 -1
  73. package/components/tooltip/tooltip.js +2 -2
  74. package/components/upload/upload.d.ts +4 -3
  75. package/components/upload/upload.js +3 -7
  76. package/components/user-agreement/service.js +2 -2
  77. package/package.json +20 -19
  78. package/components/global/use-event-callback.d.ts +0 -1
  79. package/components/global/use-event-callback.js +0 -15
  80. package/components/table/table-base.d.ts +0 -24
  81. /package/components/legacy-table/{table.css → legacy-table.css} +0 -0
@@ -1,44 +1,71 @@
1
- import { type ComponentPropsWithoutRef } from 'react';
1
+ import { type ComponentPropsWithRef } from 'react';
2
2
  import type { TableProps } from './table';
3
3
  /**
4
4
  * The new Table component. Use it instead of tables in the `legacy-table` folder.
5
5
  *
6
- * Minimal usage requires the following props:
6
+ * For every prop and component referenced here, see the corresponding docs
7
+ * for detailed behavior.
8
+ *
9
+ * ## Minimal usage
10
+ *
11
+ * You need the following props:
7
12
  * - `data`
8
13
  * - `getKey`
9
14
  * - `columns`
10
15
  * - `key`
11
- * - `renderCell` (not required, but usually needed)
16
+ * - `name` (not required but needed in most cases)
17
+ * - `renderCell` (not required but needed in most cases)
12
18
  *
13
19
  * ## Selection
14
20
  *
15
- * Following three props support the selection:
21
+ * Selection is handled on item level via `renderItem` (often with
22
+ * `DefaultItemRenderer`) and its props:
23
+ *
24
+ * - `clickable`
25
+ * - `selected`
26
+ * - `onClick` or `onPointerUp`, etc.
16
27
  *
17
- * - `selection`
18
- * - `isItemClickable`
19
- * - `DefaultItemRenderer.onClick`
28
+ * You may use the TableSelection from `global/table-selection.ts` for the selection control:
20
29
  *
21
- * Only `selection` is required: you can display and modify selection your way, e.g., via
22
- * checkboxes in cells.
30
+ * - `selected={tableSelection.isSelected(item)}`
31
+ * - `onClick={() => setTableSelection(tableSelection.toggle(item))}`
32
+ *
33
+ * See the stories for examples with this utility.
34
+ *
35
+ * Additionally, for accessibility, you will likely need a cell with a checkbox
36
+ * to toggle item selection.
23
37
  *
24
38
  * ## Sorting
39
+ *
25
40
  * You need the following to support sorting:
26
41
  *
27
42
  * - Include `<SortButton />` in a column header
28
- * - Set initial `Column.sortOrder` to `none`, `ascending`.
29
- * Do not leave `undefined` for the accessibility reasons.
43
+ * - Set initial `Column.sortOrder` to `none`. Do not leave `undefined`
44
+ * for accessibility reasons.
30
45
  * - Handle `TableProps.onSort` callback in the client code. It is expected
31
- * to update `columns`, by setting the new `sortOrder` value for
32
- * the corresponding column, and updating the data accordingly.
46
+ * to update `columns` by setting the new `sortOrder` value for
47
+ * the corresponding column, and updating the data accordingly.
48
+ *
49
+ * ## Focus
50
+ *
51
+ * The table supports the ["roving tabindex"](https://developer.mozilla.org/en-US/docs/Web/Accessibility/Guides/Keyboard-navigable_JavaScript_widgets#technique_1_roving_tabindex)
52
+ * technique to focus rows with the up/down arrow keys and, possibly, with the pointer.
53
+ * To support it, use the following props of the `DefaultItemRenderer`:
54
+ *
55
+ * - `keyboardFocusable`
56
+ * - Possibly `onClick` invoking `focusRow(e.currentTarget)`
57
+ *
58
+ * In your custom row renderer, use `TableRow`, which also has the `keyboardFocusable` prop.
33
59
  *
34
60
  * ## Deleting columns
61
+ *
35
62
  * You need the following to support deleting columns:
36
63
  *
37
64
  * - Make sure the `column` has a proper `name` or `key` prop, which will be
38
- * automatically included in the aria-label of `<DeleteColumnButton />`.
65
+ * automatically included in the aria-label of `<DeleteColumnButton />`.
39
66
  * - Include `<DeleteColumnButton />` in a column header
40
67
  * - Handle `TableProps.onColumnDelete` callback in the client code. It is expected
41
- * to update `columns` by removing the corresponding column.
68
+ * to update `columns` by removing the corresponding column.
42
69
  *
43
70
  * ## Row virtualization
44
71
  *
@@ -50,4 +77,4 @@ import type { TableProps } from './table';
50
77
  * the default height (e.g. multiline or custom content)
51
78
  * - Fine-tuning props: `lookaheadPx`, `retentionMarginPx`, `minScrollAndResizeDeltaPx`
52
79
  */
53
- export default function Table<T>(props: TableProps<T> & ComponentPropsWithoutRef<'table'>): import("react").JSX.Element;
80
+ export default function Table<T>(props: TableProps<T> & ComponentPropsWithRef<'table'>): import("react").JSX.Element;
@@ -1,49 +1,78 @@
1
1
  import { useRef } from 'react';
2
2
  import classNames from 'classnames';
3
+ import { mergeRefs } from 'react-merge-refs';
3
4
  import { IntersectionObserverContext } from '../global/intersection-observer-context';
4
5
  import { SpacerRow, useTableVirtualize } from './table-virtualize';
5
6
  import { DefaultItemRenderer } from './default-item-renderer';
6
7
  import { CollapseItemIntoSpacerContext, ColumnIndexContext, defaultLookaheadPx, defaultMinScrollAndResizeDeltaPx, defaultRetentionMarginPx, defaultRowHeight, TablePropsContext, } from './table-const';
8
+ import { onBlurCaptureTbody, onKeyDownTbody } from './table-row-focus';
7
9
  import styles from './table.css';
8
10
  /**
9
11
  * The new Table component. Use it instead of tables in the `legacy-table` folder.
10
12
  *
11
- * Minimal usage requires the following props:
13
+ * For every prop and component referenced here, see the corresponding docs
14
+ * for detailed behavior.
15
+ *
16
+ * ## Minimal usage
17
+ *
18
+ * You need the following props:
12
19
  * - `data`
13
20
  * - `getKey`
14
21
  * - `columns`
15
22
  * - `key`
16
- * - `renderCell` (not required, but usually needed)
23
+ * - `name` (not required but needed in most cases)
24
+ * - `renderCell` (not required but needed in most cases)
17
25
  *
18
26
  * ## Selection
19
27
  *
20
- * Following three props support the selection:
28
+ * Selection is handled on item level via `renderItem` (often with
29
+ * `DefaultItemRenderer`) and its props:
30
+ *
31
+ * - `clickable`
32
+ * - `selected`
33
+ * - `onClick` or `onPointerUp`, etc.
21
34
  *
22
- * - `selection`
23
- * - `isItemClickable`
24
- * - `DefaultItemRenderer.onClick`
35
+ * You may use the TableSelection from `global/table-selection.ts` for the selection control:
25
36
  *
26
- * Only `selection` is required: you can display and modify selection your way, e.g., via
27
- * checkboxes in cells.
37
+ * - `selected={tableSelection.isSelected(item)}`
38
+ * - `onClick={() => setTableSelection(tableSelection.toggle(item))}`
39
+ *
40
+ * See the stories for examples with this utility.
41
+ *
42
+ * Additionally, for accessibility, you will likely need a cell with a checkbox
43
+ * to toggle item selection.
28
44
  *
29
45
  * ## Sorting
46
+ *
30
47
  * You need the following to support sorting:
31
48
  *
32
49
  * - Include `<SortButton />` in a column header
33
- * - Set initial `Column.sortOrder` to `none`, `ascending`.
34
- * Do not leave `undefined` for the accessibility reasons.
50
+ * - Set initial `Column.sortOrder` to `none`. Do not leave `undefined`
51
+ * for accessibility reasons.
35
52
  * - Handle `TableProps.onSort` callback in the client code. It is expected
36
- * to update `columns`, by setting the new `sortOrder` value for
37
- * the corresponding column, and updating the data accordingly.
53
+ * to update `columns` by setting the new `sortOrder` value for
54
+ * the corresponding column, and updating the data accordingly.
55
+ *
56
+ * ## Focus
57
+ *
58
+ * The table supports the ["roving tabindex"](https://developer.mozilla.org/en-US/docs/Web/Accessibility/Guides/Keyboard-navigable_JavaScript_widgets#technique_1_roving_tabindex)
59
+ * technique to focus rows with the up/down arrow keys and, possibly, with the pointer.
60
+ * To support it, use the following props of the `DefaultItemRenderer`:
61
+ *
62
+ * - `keyboardFocusable`
63
+ * - Possibly `onClick` invoking `focusRow(e.currentTarget)`
64
+ *
65
+ * In your custom row renderer, use `TableRow`, which also has the `keyboardFocusable` prop.
38
66
  *
39
67
  * ## Deleting columns
68
+ *
40
69
  * You need the following to support deleting columns:
41
70
  *
42
71
  * - Make sure the `column` has a proper `name` or `key` prop, which will be
43
- * automatically included in the aria-label of `<DeleteColumnButton />`.
72
+ * automatically included in the aria-label of `<DeleteColumnButton />`.
44
73
  * - Include `<DeleteColumnButton />` in a column header
45
74
  * - Handle `TableProps.onColumnDelete` callback in the client code. It is expected
46
- * to update `columns` by removing the corresponding column.
75
+ * to update `columns` by removing the corresponding column.
47
76
  *
48
77
  * ## Row virtualization
49
78
  *
@@ -56,46 +85,46 @@ import styles from './table.css';
56
85
  * - Fine-tuning props: `lookaheadPx`, `retentionMarginPx`, `minScrollAndResizeDeltaPx`
57
86
  */
58
87
  export default function Table(props) {
59
- const { data, columns, getKey, selection, isItemKeyboardFocusable, onItemFocus, onItemMove, onSort, onColumnDelete, onColumnMove, renderItem, virtualizeRows = false, scrollerRef, estimateHeight = () => defaultRowHeight, lookaheadPx = defaultLookaheadPx, retentionMarginPx = defaultRetentionMarginPx, minScrollAndResizeDeltaPx = defaultMinScrollAndResizeDeltaPx, columnEditButton, ref: userRef, className, theadClassName, theadTrClassName, tbodyClassName, ...restProps } = props;
60
- const selfRef = useRef(null);
61
- const tableRef = userRef ?? selfRef;
88
+ const { data, columns, getKey, noHeader, onItemMove, onSort, onColumnDelete, onColumnMove, renderItem, virtualizeRows = false, scrollerRef, estimateHeight = () => defaultRowHeight, lookaheadPx = defaultLookaheadPx, retentionMarginPx = defaultRetentionMarginPx, minScrollAndResizeDeltaPx = defaultMinScrollAndResizeDeltaPx, columnEditButton, theadClassName, theadTrClassName, tbodyClassName, ref: userRef, className, ...restProps } = props;
89
+ const localRef = useRef(null);
62
90
  const { virtualItems, intersectionObserverHandle, collapseItemIntoSpacer } = useTableVirtualize({
63
91
  enabled: virtualizeRows,
64
- length: data.length,
92
+ data,
65
93
  scrollerRef,
66
- tableRef,
94
+ tableRef: localRef,
67
95
  estimateHeight,
68
96
  lookaheadPx,
69
97
  retentionMarginPx,
70
98
  minScrollAndResizeDeltaPx,
71
99
  });
72
- return (<TablePropsContext.Provider value={props}>
73
- <IntersectionObserverContext.Provider value={intersectionObserverHandle}>
74
- <table className={classNames(styles.table, className)} ref={tableRef} {...restProps}>
75
- <thead className={theadClassName}>
76
- <tr className={classNames(styles.headerRow, theadTrClassName)}>
77
- {columns.map((column, columnIndex) => (<th key={column.key} className={classNames(styles.headerCell, column.thClassName)} aria-sort={column.sortOrder}>
78
- <ColumnIndexContext.Provider value={columnIndex}>
79
- {column.renderHeader?.() ?? column.name ?? String(column.key)}
80
- </ColumnIndexContext.Provider>
81
- </th>))}
82
- </tr>
83
- </thead>
100
+ return (<TablePropsContext value={props}>
101
+ <IntersectionObserverContext value={intersectionObserverHandle}>
102
+ <table className={classNames(styles.table, className)} ref={mergeRefs([userRef, localRef])} {...restProps}>
103
+ {!noHeader && (<thead className={theadClassName}>
104
+ <tr className={classNames(styles.headerRow, theadTrClassName)}>
105
+ {columns.map((column, columnIndex) => (<th key={column.key} className={classNames(styles.headerCell, column.thClassName)} aria-sort={column.sortOrder}>
106
+ <ColumnIndexContext value={columnIndex}>
107
+ {column.renderHeader?.() ?? column.name ?? String(column.key)}
108
+ </ColumnIndexContext>
109
+ </th>))}
110
+ </tr>
111
+ </thead>)}
84
112
 
85
- <tbody className={tbodyClassName}>
113
+ {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */}
114
+ <tbody className={tbodyClassName} onKeyDown={onKeyDownTbody} onBlurCapture={onBlurCaptureTbody}>
86
115
  {virtualItems.map(virtualItem => {
87
116
  if (virtualItem.type === 'spacer') {
88
117
  return <SpacerRow key={virtualItem.key} spacer={virtualItem} colSpan={columns.length}/>;
89
118
  }
90
119
  const index = virtualItem.index;
91
120
  const item = data[index];
92
- const key = props.getKey(item, index);
93
- return (<CollapseItemIntoSpacerContext.Provider value={height => collapseItemIntoSpacer(index, height)} key={key}>
121
+ const key = getKey(item, index, data);
122
+ return (<CollapseItemIntoSpacerContext value={height => collapseItemIntoSpacer(index, height)} key={key}>
94
123
  {renderItem ? renderItem(item, index, data) : <DefaultItemRenderer index={index}/>}
95
- </CollapseItemIntoSpacerContext.Provider>);
124
+ </CollapseItemIntoSpacerContext>);
96
125
  })}
97
126
  </tbody>
98
127
  </table>
99
- </IntersectionObserverContext.Provider>
100
- </TablePropsContext.Provider>);
128
+ </IntersectionObserverContext>
129
+ </TablePropsContext>);
101
130
  }
@@ -0,0 +1,28 @@
1
+ import { type ComponentPropsWithRef } from 'react';
2
+ /**
3
+ * Include it in a column header to make the column sortable.
4
+ * Handle clicks with {@link TableProps.onSort}.
5
+ */
6
+ export declare function SortButton<T>({ className, children, onClick, ...restProps }: ComponentPropsWithRef<'button'>): import("react").JSX.Element | null;
7
+ /**
8
+ * Include it in a column header to make the column deletable.
9
+ * Beware that `column.name ?? String(column.key)` is used in the aria-label.
10
+ * Handle clicks with {@link TableProps.onColumnDelete}.
11
+ */
12
+ export declare function DeleteColumnButton<T>({ className, onClick, ...restProps }: ComponentPropsWithRef<'button'>): import("react").JSX.Element | null;
13
+ export interface TableRowProps {
14
+ /**
15
+ * @see DefaultItemRendererProps.keyboardFocusable
16
+ */
17
+ keyboardFocusable?: boolean;
18
+ }
19
+ /**
20
+ * A helper `<tr>` component for a custom {@link TableProps.renderItem} implementations.
21
+ * Applies the standard row classnames.
22
+ */
23
+ export declare function TableRow(props: TableRowProps & ComponentPropsWithRef<'tr'>): import("react").JSX.Element;
24
+ /**
25
+ * A helper `<td>` component for a custom {@link TableProps.renderItem} implementations.
26
+ * Applies the standard cell classnames, but not data-dependent `tdClassName`.
27
+ */
28
+ export declare function TableCell(props: ComponentPropsWithRef<'td'>): import("react").JSX.Element;
@@ -1,4 +1,4 @@
1
- import { useContext } from 'react';
1
+ import { use, useCallback } from 'react';
2
2
  import classNames from 'classnames';
3
3
  import unsortedIcon from '@jetbrains/icons/unsorted-12px';
4
4
  import arrowDownIcon from '@jetbrains/icons/arrow-12px-down';
@@ -6,31 +6,31 @@ import arrowUpIcon from '@jetbrains/icons/arrow-12px-up';
6
6
  import trashIcon from '@jetbrains/icons/trash-12px';
7
7
  import Icon from '../icon/icon';
8
8
  import { ColumnIndexContext, TablePropsContext } from './table-const';
9
+ import { keyboardFocusableAttrName } from './table-row-focus';
9
10
  import styles from './table.css';
10
11
  /**
11
12
  * Include it in a column header to make the column sortable.
12
13
  * Handle clicks with {@link TableProps.onSort}.
13
14
  */
14
- export function SortButton(props) {
15
- const tableProps = useContext(TablePropsContext);
16
- const columnIndex = useContext(ColumnIndexContext);
15
+ export function SortButton({ className, children, onClick, ...restProps }) {
16
+ const tableProps = use(TablePropsContext);
17
+ const columnIndex = use(ColumnIndexContext);
17
18
  const column = tableProps?.columns[columnIndex];
18
- if (!tableProps || !column) {
19
- return null;
20
- }
21
- const sortOrder = column.sortOrder ?? 'none';
19
+ const sortOrder = column?.sortOrder ?? 'none';
22
20
  // eslint-disable-next-line no-nested-ternary, prettier/prettier
23
21
  const glyph = sortOrder === 'none' ? unsortedIcon
24
22
  : sortOrder === 'ascending' ? arrowUpIcon
25
23
  : arrowDownIcon;
26
- const { className, children, onClick, ...restProps } = props;
27
- function handleClick(e) {
24
+ const handleClick = useCallback((e) => {
28
25
  onClick?.(e);
29
26
  if (!e.defaultPrevented) {
30
27
  const sequence = ['none', 'ascending', 'descending'];
31
28
  const nextOrder = sequence[(sequence.indexOf(sortOrder) + 1) % sequence.length];
32
- tableProps.onSort?.(columnIndex, nextOrder);
29
+ tableProps.onSort?.(columnIndex, nextOrder, tableProps.columns);
33
30
  }
31
+ }, [columnIndex, onClick, sortOrder, tableProps]);
32
+ if (!tableProps || !column) {
33
+ return null;
34
34
  }
35
35
  return (<button type='button' className={classNames(styles.headerButton, className)} onClick={handleClick} {...restProps}>
36
36
  {children} <Icon glyph={glyph} aria-hidden/>
@@ -41,19 +41,18 @@ export function SortButton(props) {
41
41
  * Beware that `column.name ?? String(column.key)` is used in the aria-label.
42
42
  * Handle clicks with {@link TableProps.onColumnDelete}.
43
43
  */
44
- export function DeleteColumnButton(props) {
45
- const tableProps = useContext(TablePropsContext);
46
- const columnIndex = useContext(ColumnIndexContext);
44
+ export function DeleteColumnButton({ className, onClick, ...restProps }) {
45
+ const tableProps = use(TablePropsContext);
46
+ const columnIndex = use(ColumnIndexContext);
47
47
  const column = tableProps?.columns[columnIndex];
48
- if (!tableProps || !column) {
49
- return null;
50
- }
51
- const { className, onClick, ...restProps } = props;
52
- function handleClick(e) {
48
+ const handleClick = useCallback((e) => {
53
49
  onClick?.(e);
54
50
  if (!e.defaultPrevented) {
55
- tableProps.onColumnDelete?.(columnIndex);
51
+ tableProps.onColumnDelete?.(columnIndex, tableProps.columns);
56
52
  }
53
+ }, [columnIndex, onClick, tableProps]);
54
+ if (!tableProps || !column) {
55
+ return null;
57
56
  }
58
57
  return (<button type='button' className={classNames(styles.headerButton, styles.deleteColumnButton, className)} onClick={handleClick} aria-label={`Delete column ${column.name ?? String(column.key)}`} {...restProps}>
59
58
  <Icon glyph={trashIcon}/>
@@ -64,9 +63,10 @@ export function DeleteColumnButton(props) {
64
63
  * Applies the standard row classnames.
65
64
  */
66
65
  export function TableRow(props) {
67
- const { ref, className, ...restProps } = props;
66
+ const { keyboardFocusable, className, ...restProps } = props;
68
67
  const classes = classNames(styles.row, className);
69
- return <tr ref={ref} className={classes} {...restProps}/>;
68
+ const trRestProps = keyboardFocusable ? { [keyboardFocusableAttrName]: '', ...restProps } : restProps;
69
+ return <tr className={classes} {...trRestProps}/>;
70
70
  }
71
71
  /**
72
72
  * A helper `<td>` component for a custom {@link TableProps.renderItem} implementations.
@@ -0,0 +1,4 @@
1
+ export declare const keyboardFocusableAttrName = "data-keyboard-focusable";
2
+ export declare function focusRow(row: HTMLTableRowElement): void;
3
+ export declare function onKeyDownTbody(e: React.KeyboardEvent<HTMLTableSectionElement>): void;
4
+ export declare function onBlurCaptureTbody(e: React.FocusEvent<HTMLTableSectionElement>): void;
@@ -0,0 +1,42 @@
1
+ export const keyboardFocusableAttrName = 'data-keyboard-focusable';
2
+ const temporaryTabIndexAttrName = 'data-temporary-tabindex';
3
+ export function focusRow(row) {
4
+ if (!row.hasAttribute('tabindex')) {
5
+ row.tabIndex = 0;
6
+ row.setAttribute(temporaryTabIndexAttrName, '');
7
+ }
8
+ row.focus();
9
+ }
10
+ export function onKeyDownTbody(e) {
11
+ if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') {
12
+ return;
13
+ }
14
+ const tbody = e.currentTarget;
15
+ const currentRow = e.target.closest('tr');
16
+ if (!(currentRow instanceof HTMLTableRowElement) || currentRow.parentElement !== tbody) {
17
+ return;
18
+ }
19
+ let candidate = currentRow;
20
+ while (candidate) {
21
+ candidate =
22
+ e.key === 'ArrowUp'
23
+ ? candidate.previousElementSibling
24
+ : candidate.nextElementSibling;
25
+ if (candidate?.hasAttribute(keyboardFocusableAttrName)) {
26
+ focusRow(candidate);
27
+ e.preventDefault();
28
+ return;
29
+ }
30
+ }
31
+ }
32
+ export function onBlurCaptureTbody(e) {
33
+ const tbody = e.currentTarget;
34
+ if (!(e.target instanceof HTMLTableRowElement) || e.target.parentElement !== tbody) {
35
+ return;
36
+ }
37
+ const row = e.target;
38
+ if (row.hasAttribute(temporaryTabIndexAttrName)) {
39
+ row.removeAttribute('tabindex');
40
+ row.removeAttribute(temporaryTabIndexAttrName);
41
+ }
42
+ }
@@ -11,12 +11,12 @@ interface Spacer {
11
11
  height: number;
12
12
  key: string;
13
13
  }
14
- export declare function useTableVirtualize({ enabled, length, scrollerRef, tableRef, estimateHeight, lookaheadPx, retentionMarginPx, minScrollAndResizeDeltaPx, }: {
14
+ export declare function useTableVirtualize<T>({ enabled, data, data: { length }, scrollerRef, tableRef, estimateHeight, lookaheadPx, retentionMarginPx, minScrollAndResizeDeltaPx, }: {
15
15
  enabled: boolean;
16
- length: number;
16
+ data: T[];
17
17
  scrollerRef: RefObject<HTMLElement | null> | undefined;
18
18
  tableRef: RefObject<HTMLTableElement | null>;
19
- estimateHeight: (index: number) => number;
19
+ estimateHeight: (item: T, index: number, items: T[]) => number;
20
20
  lookaheadPx: number;
21
21
  retentionMarginPx: number;
22
22
  minScrollAndResizeDeltaPx: number;
@@ -1,5 +1,4 @@
1
- import { useEffect, useMemo, useRef, useState } from 'react';
2
- import useEventCallback from '../global/use-event-callback';
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
2
  import { useIntersectionObserverHandle } from '../global/intersection-observer-context';
4
3
  import styles from './table.css';
5
4
  /**
@@ -8,18 +7,18 @@ import styles from './table.css';
8
7
  * Therefore, we throttle with a custom delay.
9
8
  */
10
9
  const virtualizationThrottleDelay = 50;
11
- export function useTableVirtualize({ enabled, length, scrollerRef, tableRef, estimateHeight, lookaheadPx, retentionMarginPx, minScrollAndResizeDeltaPx, }) {
10
+ export function useTableVirtualize({ enabled, data, data: { length }, scrollerRef, tableRef, estimateHeight, lookaheadPx, retentionMarginPx, minScrollAndResizeDeltaPx, }) {
12
11
  const itemsMaterialization = useRef([]);
13
12
  const [virtualItems, setVirtualItems] = useState(() => [
14
13
  {
15
14
  type: 'spacer',
16
15
  from: 0,
17
16
  to: length,
18
- height: Array.from({ length }, (_, i) => estimateHeight(i)).reduce((a, b) => a + b, 0),
17
+ height: data.reduce((acc, item, index, items) => acc + estimateHeight(item, index, items), 0),
19
18
  key: `${styles.spacerRow}-0`,
20
19
  },
21
20
  ]);
22
- const materializeVisibleSpacerItems = useEventCallback(() => {
21
+ const materializeVisibleSpacerItems = useCallback(() => {
23
22
  if (!tableRef.current)
24
23
  return;
25
24
  const containerHeight = scrollerRef?.current?.clientHeight ?? window.innerHeight;
@@ -38,7 +37,7 @@ export function useTableVirtualize({ enabled, length, scrollerRef, tableRef, est
38
37
  const to = Number(spacerRow.dataset.to);
39
38
  for (let i = from; i < to; i++) {
40
39
  const itemMaterialization = itemsMaterialization.current[i];
41
- const itemHeight = typeof itemMaterialization === 'number' ? itemMaterialization : estimateHeight(i);
40
+ const itemHeight = typeof itemMaterialization === 'number' ? itemMaterialization : estimateHeight(data[i], i, data);
42
41
  const itemOffsetStart = offsetInSpacer;
43
42
  const itemOffsetEnd = offsetInSpacer + itemHeight;
44
43
  if (itemOffsetStart < materializeOffsetEnd && itemOffsetEnd > materializeOffsetStart) {
@@ -50,8 +49,8 @@ export function useTableVirtualize({ enabled, length, scrollerRef, tableRef, est
50
49
  offsetInSpacer += itemHeight;
51
50
  }
52
51
  }
53
- });
54
- const recomputeVirtualItems = useEventCallback(() => {
52
+ }, [data, estimateHeight, lookaheadPx, scrollerRef, tableRef]);
53
+ const recomputeVirtualItems = useCallback(() => {
55
54
  const newVirtualItems = [];
56
55
  let spacerCounter = 0;
57
56
  for (let i = 0; i < length; i++) {
@@ -62,7 +61,7 @@ export function useTableVirtualize({ enabled, length, scrollerRef, tableRef, est
62
61
  else {
63
62
  const lastItemOrSpacer = newVirtualItems[newVirtualItems.length - 1];
64
63
  const lastSpacer = lastItemOrSpacer?.type === 'spacer' ? lastItemOrSpacer : undefined;
65
- const height = typeof itemMaterialization === 'number' ? itemMaterialization : estimateHeight(i);
64
+ const height = typeof itemMaterialization === 'number' ? itemMaterialization : estimateHeight(data[i], i, data);
66
65
  if (lastSpacer) {
67
66
  lastSpacer.to = i + 1;
68
67
  lastSpacer.height += height;
@@ -79,10 +78,10 @@ export function useTableVirtualize({ enabled, length, scrollerRef, tableRef, est
79
78
  }
80
79
  }
81
80
  setVirtualItems(newVirtualItems);
82
- });
81
+ }, [data, estimateHeight, length]);
83
82
  const timerIdRef = useRef(null);
84
83
  const callbacksRef = useRef(null);
85
- const throttle = useEventCallback((...callbacks) => {
84
+ const throttle = useCallback((...callbacks) => {
86
85
  if (timerIdRef.current != null) {
87
86
  callbacks.forEach(cb => {
88
87
  callbacksRef.current.delete(cb);
@@ -96,7 +95,7 @@ export function useTableVirtualize({ enabled, length, scrollerRef, tableRef, est
96
95
  timerIdRef.current = null;
97
96
  callbacksRef.current = null;
98
97
  }, virtualizationThrottleDelay);
99
- });
98
+ }, []);
100
99
  useEffect(() => {
101
100
  if (!enabled)
102
101
  return;
@@ -130,12 +129,12 @@ export function useTableVirtualize({ enabled, length, scrollerRef, tableRef, est
130
129
  };
131
130
  }, [enabled, materializeVisibleSpacerItems, minScrollAndResizeDeltaPx, recomputeVirtualItems, scrollerRef, throttle]);
132
131
  const intersectionObserverHandle = useIntersectionObserverHandle(scrollerRef, scrollerRef ? retentionMarginPx : undefined, !scrollerRef ? retentionMarginPx : undefined);
133
- const collapseItemIntoSpacer = useEventCallback((index, height) => {
132
+ const collapseItemIntoSpacer = useCallback((index, height) => {
134
133
  if (!enabled)
135
134
  return;
136
135
  itemsMaterialization.current[index] = height;
137
136
  throttle(recomputeVirtualItems);
138
- });
137
+ }, [enabled, throttle, recomputeVirtualItems]);
139
138
  const allVisibleVirtualItems = useMemo(() => Array.from({ length: enabled ? 0 : length }, (_, index) => ({ type: 'rendered', index })), [enabled, length]);
140
139
  return {
141
140
  virtualItems: enabled ? virtualItems : allVisibleVirtualItems,
@@ -1,6 +1,5 @@
1
1
  import Table from './table-component';
2
2
  import type { ReactNode, RefObject } from 'react';
3
- import type Selection from '../legacy-table/selection';
4
3
  export default Table;
5
4
  export interface TableProps<T> {
6
5
  /**
@@ -15,21 +14,11 @@ export interface TableProps<T> {
15
14
  /**
16
15
  * Used to render a row key, e.g. `<tr key={getKey(item, index)}>`.
17
16
  */
18
- getKey: (item: T, index: number) => React.Key;
17
+ getKey: (item: T, index: number, items: T[]) => React.Key;
19
18
  /**
20
- * Displays the selection and focus state.
19
+ * If true, the table header will not be rendered.
21
20
  */
22
- selection?: Selection<T>;
23
- /**
24
- * If true, the item can be focused by keyboard up/down arrows.
25
- * Note that `false` doesn't prevent from `selection.focus()`.
26
- */
27
- isItemKeyboardFocusable?: (item: T, index: number, items: T[]) => boolean;
28
- /**
29
- * When the item should get focused by keyboard navigation.
30
- * The client is expected to update `selection`.
31
- */
32
- onItemFocus?: (item: T | null, index: number, items: T[]) => void;
21
+ noHeader?: boolean;
33
22
  /**
34
23
  * Called when the client moves a row by dragging it.
35
24
  */
@@ -37,15 +26,15 @@ export interface TableProps<T> {
37
26
  /**
38
27
  * Called when the client clicks on SortButton in a column header.
39
28
  */
40
- onSort?: (columnIndex: number, newOrder: SortOrder) => void;
29
+ onSort?: (columnIndex: number, newOrder: SortOrder, columns: Column<T>[]) => void;
41
30
  /**
42
31
  * Called when the client clicks on a column delete button in the header.
43
32
  */
44
- onColumnDelete?: (columnIndex: number) => void;
33
+ onColumnDelete?: (columnIndex: number, columns: Column<T>[]) => void;
45
34
  /**
46
35
  * Called when the client moves a column.
47
36
  */
48
- onColumnMove?: (fromIndex: number, toIndex: number) => void;
37
+ onColumnMove?: (fromIndex: number, toIndex: number, columns: Column<T>[]) => void;
49
38
  /**
50
39
  * Implement to specify props like `clickable`, handlers like `onClick`,
51
40
  * a custom `className`, a `ref` etc., or to provide a custom renderer.
@@ -89,7 +78,7 @@ export interface TableProps<T> {
89
78
  * - ResizeObserver observes `document.body`
90
79
  * - IntersectionObserver has no root (i.e. the viewport is used)
91
80
  */
92
- scrollerRef?: React.RefObject<HTMLElement | null>;
81
+ scrollerRef?: RefObject<HTMLElement | null>;
93
82
  /**
94
83
  * Used with `virtualizeRows` to estimate the height of items that have not been rendered yet.
95
84
  * The function should be fast and side-effect free. Do not measure the DOM here.
@@ -105,7 +94,7 @@ export interface TableProps<T> {
105
94
  *
106
95
  * Default: 37px = 16px padding + 20px line height + 1px border.
107
96
  */
108
- estimateHeight?: (index: number) => number;
97
+ estimateHeight?: (item: T, index: number, items: T[]) => number;
109
98
  /**
110
99
  * When using `virtualizeRows`, the number of pixels above and below the viewport
111
100
  * to materialize in advance.
@@ -151,10 +140,6 @@ export interface TableProps<T> {
151
140
  * Whether to show a small gear button at the top right corner.
152
141
  */
153
142
  columnEditButton?: 'everywhere' | 'mobileOnly';
154
- /**
155
- * Optional ref to install on the table element.
156
- */
157
- ref?: RefObject<HTMLTableElement | null>;
158
143
  /**
159
144
  * Applied to the `<thead>` element.
160
145
  */
@@ -174,7 +159,7 @@ export type SortOrder = 'none' | 'ascending' | 'descending';
174
159
  */
175
160
  export interface Column<T> {
176
161
  /**
177
- * Used to render a row key, e.g. `<thead><tr><td key={getKey(item, index)}...</td></tr></thead>`.
162
+ * Used to render a row key, e.g. `<thead><tr><td key={getKey(item, index, items)}...</td></tr></thead>`.
178
163
  */
179
164
  key: React.Key;
180
165
  /**
@@ -110,6 +110,8 @@ export default class TagsInput extends PureComponent<TagsInputProps, TagsInputSt
110
110
  selectRef: (el: Select | null) => void;
111
111
  render(): React.JSX.Element;
112
112
  }
113
- export declare const RerenderableTagsInput: React.ForwardRefExoticComponent<TagsInputProps & React.RefAttributes<TagsInput>>;
113
+ export declare const RerenderableTagsInput: ({ ref, ...props }: TagsInputProps & {
114
+ ref?: React.Ref<TagsInput> | undefined;
115
+ }) => React.JSX.Element;
114
116
  export type TagsInputAttrs = React.JSX.LibraryManagedAttributes<typeof TagsInput, TagsInputProps>;
115
117
  export {};
@@ -135,13 +135,13 @@ export default class Tooltip extends Component {
135
135
  const popup = (<Popup trapFocus={false} anchorElement={this.containerNode} hidden={!this.state.showPopup || this.state.showNestedPopup} onCloseAttempt={this.hidePopup} maxHeight={400} attached={false} onMouseOut={this.hideIfMovedOutsidePopup} top={4} dontCloseOnAnchorClick ref={this.popupRef} {...popupProps} className={classNames(styles.tooltip, { [styles.long]: long, [styles.inheritedTheme]: theme === 'inherit' }, popupProps?.className)}>
136
136
  {title}
137
137
  </Popup>);
138
- return (<TooltipContext.Provider value={{ onNestedTooltipShow, onNestedTooltipHide }}>
138
+ return (<TooltipContext value={{ onNestedTooltipShow, onNestedTooltipHide }}>
139
139
  <span {...ariaProps} {...restProps} ref={this.containerRef} data-test={dataTests('ring-tooltip', dataTest)} data-test-title={typeof title === 'string' ? title : undefined}>
140
140
  {children}
141
141
  {theme === 'inherit' ? (popup) : (<ThemeProvider theme={theme} passToPopups WrapperComponent={props => <span {...props}/>}>
142
142
  {popup}
143
143
  </ThemeProvider>)}
144
144
  </span>
145
- </TooltipContext.Provider>);
145
+ </TooltipContext>);
146
146
  }
147
147
  }