baseui 0.0.0-next-a3efedf → 0.0.0-next-cefa45f

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.
@@ -12,46 +12,69 @@ import {useStyletron} from '../styles/index.js';
12
12
 
13
13
  import HeaderCell from './header-cell.js';
14
14
  import type {ColumnT, RowT} from './types.js';
15
+ import {useRef} from 'react';
15
16
 
16
- // https://github.com/Swizec/useDimensions
17
- function useDimensions() {
18
- const [dimensions, setDimensions] = React.useState({});
19
- const [node, setNode] = React.useState(null);
17
+ // Measures the column header + sampled data
18
+ function MeasureColumn({
19
+ sampleIndexes,
20
+ column,
21
+ columnIndex,
22
+ rows,
23
+ isSelectable,
24
+ onLayout,
25
+ }) {
26
+ const [css] = useStyletron();
20
27
 
21
- const ref = React.useCallback(node => {
22
- setNode(node);
23
- }, []);
28
+ const ref = useRef();
24
29
 
25
30
  React.useEffect(() => {
26
- if (__BROWSER__) {
27
- if (node) {
28
- window.requestAnimationFrame(() => {
29
- setDimensions(node.getBoundingClientRect());
30
- });
31
- }
31
+ if (ref.current) {
32
+ onLayout(columnIndex, ref.current.getBoundingClientRect());
32
33
  }
33
- }, [node]);
34
-
35
- return [ref, dimensions];
36
- }
37
-
38
- type ElementMeasurerPropsT = {
39
- onDimensionsChange: (dimensions: {width: number}) => void,
40
- // eslint-disable-next-line flowtype/no-weak-types
41
- item: React.Element<any>,
42
- };
43
-
44
- function ElementMeasurer(props: ElementMeasurerPropsT) {
45
- const {onDimensionsChange} = props;
46
- const [ref, dimensions] = useDimensions();
47
-
48
- React.useEffect(() => {
49
- onDimensionsChange(dimensions);
50
- }, [dimensions, onDimensionsChange]);
34
+ }, []);
51
35
 
52
- return React.cloneElement(props.item, {ref});
36
+ return (
37
+ <div
38
+ ref={ref}
39
+ className={css({
40
+ display: 'flex',
41
+ flexDirection: 'column',
42
+ width: 'fit-content',
43
+ })}
44
+ >
45
+ <HeaderCell
46
+ index={columnIndex}
47
+ isHovered
48
+ isMeasured
49
+ isSelectedAll={false}
50
+ isSelectedIndeterminate={false}
51
+ onMouseEnter={() => {}}
52
+ onMouseLeave={() => {}}
53
+ onSelectAll={() => {}}
54
+ onSelectNone={() => {}}
55
+ onSort={i => {}}
56
+ sortable={column.sortable}
57
+ sortDirection={null}
58
+ title={column.title}
59
+ isSelectable={isSelectable}
60
+ />
61
+ {sampleIndexes.map((rowIndex, i) => {
62
+ const Cell = column.renderCell;
63
+ return (
64
+ <Cell
65
+ key={`measure-${i}`}
66
+ value={column.mapDataToValue(rows[rowIndex].data)}
67
+ isSelectable={isSelectable}
68
+ isMeasured
69
+ sortable={column.sortable}
70
+ x={0}
71
+ y={rowIndex}
72
+ />
73
+ );
74
+ })}
75
+ </div>
76
+ );
53
77
  }
54
-
55
78
  type MeasureColumnWidthsPropsT = {
56
79
  columns: ColumnT<>[],
57
80
  // if selectable, measure the first column with checkbox included
@@ -61,7 +84,6 @@ type MeasureColumnWidthsPropsT = {
61
84
  widths: number[],
62
85
  };
63
86
 
64
- // sample size could likely be generated based on row count, to have higher confidence
65
87
  const MAX_SAMPLE_SIZE = 50;
66
88
 
67
89
  function generateSampleIndices(inputMin, inputMax, maxSamples) {
@@ -96,51 +118,41 @@ export default function MeasureColumnWidths({
96
118
  }: MeasureColumnWidthsPropsT) {
97
119
  const [css] = useStyletron();
98
120
 
99
- const measurementCount = React.useRef(0);
100
- const dimensionsCache = React.useRef(widths);
121
+ const widthMap = React.useMemo(() => {
122
+ return new Map();
123
+ }, []);
101
124
 
102
125
  const sampleSize =
103
126
  rows.length < MAX_SAMPLE_SIZE ? rows.length : MAX_SAMPLE_SIZE;
104
127
  const finishedMeasurementCount = (sampleSize + 1) * columns.length;
105
128
 
106
- const sampleRowIndicesByColumn = React.useMemo<number[][]>(() => {
107
- measurementCount.current = 0;
108
- dimensionsCache.current = widths;
109
-
110
- const indices = generateSampleIndices(0, rows.length - 1, sampleSize);
111
- return columns.map(() => indices);
129
+ const sampleIndexes = React.useMemo<number[]>(() => {
130
+ return generateSampleIndices(0, rows.length - 1, sampleSize);
112
131
  }, [columns, rows, widths, sampleSize]);
113
132
 
114
133
  const handleDimensionsChange = React.useCallback(
115
- (columnIndex, rowIndex, dimensions) => {
116
- if (dimensions.width === undefined) return;
117
-
118
- if (
119
- columns[columnIndex] === undefined ||
120
- dimensionsCache.current[columnIndex] === undefined
121
- ) {
122
- return;
123
- }
124
-
125
- measurementCount.current += 1;
126
-
134
+ (columnIndex, dimensions) => {
127
135
  const nextWidth = Math.min(
128
136
  Math.max(
129
137
  columns[columnIndex].minWidth || 0,
130
- dimensionsCache.current[columnIndex],
138
+ widthMap.get(columnIndex) || 0,
131
139
  dimensions.width + 1,
132
140
  ),
133
141
  columns[columnIndex].maxWidth || Infinity,
134
142
  );
135
143
 
136
- if (nextWidth !== dimensionsCache.current[columnIndex]) {
137
- const nextWidths = [...dimensionsCache.current];
138
- nextWidths[columnIndex] = nextWidth;
139
- dimensionsCache.current = nextWidths;
144
+ if (nextWidth !== widthMap.get(columnIndex)) {
145
+ widthMap.set(columnIndex, nextWidth);
140
146
  }
141
-
142
- if (measurementCount.current >= finishedMeasurementCount) {
143
- onWidthsChange(dimensionsCache.current);
147
+ if (
148
+ // Refresh at 100% of done
149
+ widthMap.size === columns.length ||
150
+ // ...50%
151
+ widthMap.size === Math.floor(columns.length / 2) ||
152
+ // ...25%
153
+ widthMap.size === Math.floor(columns.length / 4)
154
+ ) {
155
+ onWidthsChange(Array.from(widthMap.values()));
144
156
  }
145
157
  },
146
158
  [columns, finishedMeasurementCount, onWidthsChange],
@@ -152,61 +164,27 @@ export default function MeasureColumnWidths({
152
164
  height: 0,
153
165
  });
154
166
 
155
- if (measurementCount.current >= finishedMeasurementCount) {
167
+ // Remove the measurement nodes after we are done updating our column width
168
+ if (widthMap.size === columns.length) {
156
169
  return null;
157
170
  }
158
171
 
159
172
  return (
160
173
  // eslint-disable-next-line jsx-a11y/role-supports-aria-props
161
174
  <div className={hiddenStyle} aria-hidden role="none">
162
- {sampleRowIndicesByColumn.map((rowIndices, columnIndex) => {
163
- const Cell = columns[columnIndex].renderCell;
164
- return rowIndices.map(rowIndex => (
165
- <ElementMeasurer
166
- key={`measure-${columnIndex}-${rowIndex}`}
167
- onDimensionsChange={dimensions =>
168
- handleDimensionsChange(columnIndex, rowIndex, dimensions)
169
- }
170
- item={
171
- <Cell
172
- value={columns[columnIndex].mapDataToValue(rows[rowIndex].data)}
173
- isMeasured
174
- onSelect={
175
- isSelectable && columnIndex === 0 ? () => {} : undefined
176
- }
177
- x={columnIndex}
178
- y={rowIndex}
179
- />
180
- }
175
+ {columns.map((column, i) => {
176
+ return (
177
+ <MeasureColumn
178
+ key={column.title + i}
179
+ column={column}
180
+ rows={rows}
181
+ isSelectable={isSelectable}
182
+ onLayout={handleDimensionsChange}
183
+ columnIndex={i}
184
+ sampleIndexes={sampleIndexes}
181
185
  />
182
- ));
186
+ );
183
187
  })}
184
- {columns.map((column, columnIndex) => (
185
- <ElementMeasurer
186
- key={`measure-column-${columnIndex}`}
187
- onDimensionsChange={dimensions =>
188
- handleDimensionsChange(columnIndex, -1, dimensions)
189
- }
190
- item={
191
- <HeaderCell
192
- index={columnIndex}
193
- isHovered
194
- isMeasured
195
- isSelectable={isSelectable && columnIndex === 0}
196
- isSelectedAll={false}
197
- isSelectedIndeterminate={false}
198
- onMouseEnter={() => {}}
199
- onMouseLeave={() => {}}
200
- onSelectAll={() => {}}
201
- onSelectNone={() => {}}
202
- onSort={i => {}}
203
- sortable={column.sortable}
204
- sortDirection={null}
205
- title={column.title}
206
- />
207
- }
208
- />
209
- ))}
210
188
  </div>
211
189
  );
212
190
  }
@@ -17,6 +17,8 @@ import { LocaleContext } from '../locale/index.js'; // consider pulling this out
17
17
 
18
18
  const HEADER_ROW_HEIGHT = 48;
19
19
 
20
+ const sum = ns => ns.reduce((s, n) => s + n, 0);
21
+
20
22
  function CellPlacement({
21
23
  columnIndex,
22
24
  rowIndex,
@@ -255,7 +257,7 @@ function Header(props) {
255
257
  }))));
256
258
  }
257
259
 
258
- function Headers(props) {
260
+ function Headers() {
259
261
  const [css, theme] = useStyletron();
260
262
  const locale = React.useContext(LocaleContext);
261
263
  const ctx = React.useContext(HeaderContext);
@@ -265,7 +267,7 @@ function Headers(props) {
265
267
  position: 'sticky',
266
268
  top: 0,
267
269
  left: 0,
268
- width: `${ctx.widths.reduce((sum, w) => sum + w, 0)}px`,
270
+ width: `${sum(ctx.widths)}px`,
269
271
  height: `${HEADER_ROW_HEIGHT}px`,
270
272
  display: 'flex',
271
273
  // this feels bad.. the absolutely positioned children elements
@@ -467,8 +469,10 @@ export function DataTable({
467
469
  }
468
470
 
469
471
  return rowHeight;
470
- }, [rowHeight]);
471
- const gridRef = React.useRef(null);
472
+ }, [rowHeight]); // We use state for our ref, to allow hooks to update when the ref changes.
473
+ // eslint-disable-next-line flowtype/no-weak-types
474
+
475
+ const [gridRef, setGridRef] = React.useState(null);
472
476
  const [measuredWidths, setMeasuredWidths] = React.useState(columns.map(() => 0));
473
477
  const [resizeDeltas, setResizeDeltas] = React.useState(columns.map(() => 0));
474
478
  React.useEffect(() => {
@@ -480,11 +484,11 @@ export function DataTable({
480
484
  });
481
485
  }, [columns]);
482
486
  const resetAfterColumnIndex = React.useCallback(columnIndex => {
483
- if (gridRef.current) {
487
+ if (gridRef) {
484
488
  // $FlowFixMe trigger react-window to layout the elements again
485
- gridRef.current.resetAfterColumnIndex(columnIndex, true);
489
+ gridRef.resetAfterColumnIndex(columnIndex, true);
486
490
  }
487
- }, [gridRef.current]);
491
+ }, [gridRef]);
488
492
  const handleWidthsChange = React.useCallback(nextWidths => {
489
493
  setMeasuredWidths(nextWidths);
490
494
  resetAfterColumnIndex(0);
@@ -594,13 +598,10 @@ export function DataTable({
594
598
  }, [sortedIndices, filteredIndices, onIncludedRowsChange, allRows]);
595
599
  const [browserScrollbarWidth, setBrowserScrollbarWidth] = React.useState(0);
596
600
  const normalizedWidths = React.useMemo(() => {
597
- const sum = ns => ns.reduce((s, n) => s + n, 0);
598
-
599
601
  const resizedWidths = measuredWidths.map((w, i) => Math.floor(w) + Math.floor(resizeDeltas[i]));
600
602
 
601
- if (gridRef.current) {
602
- // $FlowFixMe
603
- const gridProps = gridRef.current.props;
603
+ if (gridRef) {
604
+ const gridProps = gridRef.props;
604
605
  let isContentTallerThanContainer = false;
605
606
  let visibleRowHeight = 0;
606
607
 
@@ -635,7 +636,7 @@ export function DataTable({
635
636
  }
636
637
 
637
638
  return resizedWidths;
638
- }, [measuredWidths, resizeDeltas, browserScrollbarWidth, rows.length, columns]);
639
+ }, [gridRef, measuredWidths, resizeDeltas, browserScrollbarWidth, rows.length, columns]);
639
640
  const isSelectable = batchActions ? !!batchActions.length : false;
640
641
  const isSelectedAll = React.useMemo(() => {
641
642
  if (!selectedRowIds) {
@@ -684,10 +685,10 @@ export function DataTable({
684
685
  function handleRowHighlightIndexChange(nextIndex) {
685
686
  setRowHighlightIndex(nextIndex);
686
687
 
687
- if (gridRef.current) {
688
+ if (gridRef) {
688
689
  if (nextIndex >= 0) {
689
690
  // $FlowFixMe - unable to get react-window types
690
- gridRef.current.scrollToItem({
691
+ gridRef.scrollToItem({
691
692
  rowIndex: nextIndex
692
693
  });
693
694
  }
@@ -776,8 +777,9 @@ export function DataTable({
776
777
  }
777
778
  }, /*#__PURE__*/React.createElement(VariableSizeGrid // eslint-disable-next-line flowtype/no-weak-types
778
779
  , {
779
- ref: gridRef,
780
+ ref: setGridRef,
780
781
  overscanRowCount: 10,
782
+ overscanColumnCount: 5,
781
783
  innerElementType: InnerTableElement,
782
784
  columnCount: columns.length,
783
785
  columnWidth: columnIndex => normalizedWidths[columnIndex],
@@ -7,40 +7,59 @@ LICENSE file in the root directory of this source tree.
7
7
  import * as React from 'react';
8
8
  import { useStyletron } from '../styles/index.js';
9
9
  import HeaderCell from './header-cell.js';
10
+ import { useRef } from 'react'; // Measures the column header + sampled data
10
11
 
11
- // https://github.com/Swizec/useDimensions
12
- function useDimensions() {
13
- const [dimensions, setDimensions] = React.useState({});
14
- const [node, setNode] = React.useState(null);
15
- const ref = React.useCallback(node => {
16
- setNode(node);
17
- }, []);
12
+ function MeasureColumn({
13
+ sampleIndexes,
14
+ column,
15
+ columnIndex,
16
+ rows,
17
+ isSelectable,
18
+ onLayout
19
+ }) {
20
+ const [css] = useStyletron();
21
+ const ref = useRef();
18
22
  React.useEffect(() => {
19
- if (typeof document !== 'undefined') {
20
- if (node) {
21
- window.requestAnimationFrame(() => {
22
- setDimensions(node.getBoundingClientRect());
23
- });
24
- }
23
+ if (ref.current) {
24
+ onLayout(columnIndex, ref.current.getBoundingClientRect());
25
25
  }
26
- }, [node]);
27
- return [ref, dimensions];
28
- }
29
-
30
- function ElementMeasurer(props) {
31
- const {
32
- onDimensionsChange
33
- } = props;
34
- const [ref, dimensions] = useDimensions();
35
- React.useEffect(() => {
36
- onDimensionsChange(dimensions);
37
- }, [dimensions, onDimensionsChange]);
38
- return /*#__PURE__*/React.cloneElement(props.item, {
39
- ref
40
- });
26
+ }, []);
27
+ return /*#__PURE__*/React.createElement("div", {
28
+ ref: ref,
29
+ className: css({
30
+ display: 'flex',
31
+ flexDirection: 'column',
32
+ width: 'fit-content'
33
+ })
34
+ }, /*#__PURE__*/React.createElement(HeaderCell, {
35
+ index: columnIndex,
36
+ isHovered: true,
37
+ isMeasured: true,
38
+ isSelectedAll: false,
39
+ isSelectedIndeterminate: false,
40
+ onMouseEnter: () => {},
41
+ onMouseLeave: () => {},
42
+ onSelectAll: () => {},
43
+ onSelectNone: () => {},
44
+ onSort: i => {},
45
+ sortable: column.sortable,
46
+ sortDirection: null,
47
+ title: column.title,
48
+ isSelectable: isSelectable
49
+ }), sampleIndexes.map((rowIndex, i) => {
50
+ const Cell = column.renderCell;
51
+ return /*#__PURE__*/React.createElement(Cell, {
52
+ key: `measure-${i}`,
53
+ value: column.mapDataToValue(rows[rowIndex].data),
54
+ isSelectable: isSelectable,
55
+ isMeasured: true,
56
+ sortable: column.sortable,
57
+ x: 0,
58
+ y: rowIndex
59
+ });
60
+ }));
41
61
  }
42
62
 
43
- // sample size could likely be generated based on row count, to have higher confidence
44
63
  const MAX_SAMPLE_SIZE = 50;
45
64
 
46
65
  function generateSampleIndices(inputMin, inputMax, maxSamples) {
@@ -77,43 +96,35 @@ export default function MeasureColumnWidths({
77
96
  onWidthsChange
78
97
  }) {
79
98
  const [css] = useStyletron();
80
- const measurementCount = React.useRef(0);
81
- const dimensionsCache = React.useRef(widths);
99
+ const widthMap = React.useMemo(() => {
100
+ return new Map();
101
+ }, []);
82
102
  const sampleSize = rows.length < MAX_SAMPLE_SIZE ? rows.length : MAX_SAMPLE_SIZE;
83
103
  const finishedMeasurementCount = (sampleSize + 1) * columns.length;
84
- const sampleRowIndicesByColumn = React.useMemo(() => {
85
- measurementCount.current = 0;
86
- dimensionsCache.current = widths;
87
- const indices = generateSampleIndices(0, rows.length - 1, sampleSize);
88
- return columns.map(() => indices);
104
+ const sampleIndexes = React.useMemo(() => {
105
+ return generateSampleIndices(0, rows.length - 1, sampleSize);
89
106
  }, [columns, rows, widths, sampleSize]);
90
- const handleDimensionsChange = React.useCallback((columnIndex, rowIndex, dimensions) => {
91
- if (dimensions.width === undefined) return;
92
-
93
- if (columns[columnIndex] === undefined || dimensionsCache.current[columnIndex] === undefined) {
94
- return;
95
- }
96
-
97
- measurementCount.current += 1;
98
- const nextWidth = Math.min(Math.max(columns[columnIndex].minWidth || 0, dimensionsCache.current[columnIndex], dimensions.width + 1), columns[columnIndex].maxWidth || Infinity);
107
+ const handleDimensionsChange = React.useCallback((columnIndex, dimensions) => {
108
+ const nextWidth = Math.min(Math.max(columns[columnIndex].minWidth || 0, widthMap.get(columnIndex) || 0, dimensions.width + 1), columns[columnIndex].maxWidth || Infinity);
99
109
 
100
- if (nextWidth !== dimensionsCache.current[columnIndex]) {
101
- const nextWidths = [...dimensionsCache.current];
102
- nextWidths[columnIndex] = nextWidth;
103
- dimensionsCache.current = nextWidths;
110
+ if (nextWidth !== widthMap.get(columnIndex)) {
111
+ widthMap.set(columnIndex, nextWidth);
104
112
  }
105
113
 
106
- if (measurementCount.current >= finishedMeasurementCount) {
107
- onWidthsChange(dimensionsCache.current);
114
+ if ( // Refresh at 100% of done
115
+ widthMap.size === columns.length || // ...50%
116
+ widthMap.size === Math.floor(columns.length / 2) || // ...25%
117
+ widthMap.size === Math.floor(columns.length / 4)) {
118
+ onWidthsChange(Array.from(widthMap.values()));
108
119
  }
109
120
  }, [columns, finishedMeasurementCount, onWidthsChange]);
110
121
  const hiddenStyle = css({
111
122
  position: 'absolute',
112
123
  overflow: 'hidden',
113
124
  height: 0
114
- });
125
+ }); // Remove the measurement nodes after we are done updating our column width
115
126
 
116
- if (measurementCount.current >= finishedMeasurementCount) {
127
+ if (widthMap.size === columns.length) {
117
128
  return null;
118
129
  }
119
130
 
@@ -124,38 +135,16 @@ export default function MeasureColumnWidths({
124
135
  className: hiddenStyle,
125
136
  "aria-hidden": true,
126
137
  role: "none"
127
- }, sampleRowIndicesByColumn.map((rowIndices, columnIndex) => {
128
- const Cell = columns[columnIndex].renderCell;
129
- return rowIndices.map(rowIndex => /*#__PURE__*/React.createElement(ElementMeasurer, {
130
- key: `measure-${columnIndex}-${rowIndex}`,
131
- onDimensionsChange: dimensions => handleDimensionsChange(columnIndex, rowIndex, dimensions),
132
- item: /*#__PURE__*/React.createElement(Cell, {
133
- value: columns[columnIndex].mapDataToValue(rows[rowIndex].data),
134
- isMeasured: true,
135
- onSelect: isSelectable && columnIndex === 0 ? () => {} : undefined,
136
- x: columnIndex,
137
- y: rowIndex
138
- })
139
- }));
140
- }), columns.map((column, columnIndex) => /*#__PURE__*/React.createElement(ElementMeasurer, {
141
- key: `measure-column-${columnIndex}`,
142
- onDimensionsChange: dimensions => handleDimensionsChange(columnIndex, -1, dimensions),
143
- item: /*#__PURE__*/React.createElement(HeaderCell, {
144
- index: columnIndex,
145
- isHovered: true,
146
- isMeasured: true,
147
- isSelectable: isSelectable && columnIndex === 0,
148
- isSelectedAll: false,
149
- isSelectedIndeterminate: false,
150
- onMouseEnter: () => {},
151
- onMouseLeave: () => {},
152
- onSelectAll: () => {},
153
- onSelectNone: () => {},
154
- onSort: i => {},
155
- sortable: column.sortable,
156
- sortDirection: null,
157
- title: column.title
158
- })
159
- })))
138
+ }, columns.map((column, i) => {
139
+ return /*#__PURE__*/React.createElement(MeasureColumn, {
140
+ key: column.title + i,
141
+ column: column,
142
+ rows: rows,
143
+ isSelectable: isSelectable,
144
+ onLayout: handleDimensionsChange,
145
+ columnIndex: i,
146
+ sampleIndexes: sampleIndexes
147
+ });
148
+ }))
160
149
  );
161
150
  }
@@ -0,0 +1,14 @@
1
+ /*
2
+ Copyright (c) Uber Technologies, Inc.
3
+
4
+ This source code is licensed under the MIT license found in the
5
+ LICENSE file in the root directory of this source tree.
6
+ */
7
+ // https://en.wikipedia.org/wiki/Linear_congruential_generator#Parameters_in_common_use
8
+ const mul = 0x19660d;
9
+ const inc = 0x3c6ef35f;
10
+ const eps = 1 / 0x100000000;
11
+ export default function lcg(seed) {
12
+ let state = (0 <= seed && seed < 1 ? seed / eps : Math.abs(seed)) | 0;
13
+ return () => (state = mul * state + inc | 0, eps * (state >>> 0));
14
+ }