baseui 0.0.0-alpha-2158d09 → 0.0.0-alpha-bcb33da

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,69 +12,46 @@ 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';
16
15
 
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();
16
+ // https://github.com/Swizec/useDimensions
17
+ function useDimensions() {
18
+ const [dimensions, setDimensions] = React.useState({});
19
+ const [node, setNode] = React.useState(null);
27
20
 
28
- const ref = useRef();
21
+ const ref = React.useCallback(node => {
22
+ setNode(node);
23
+ }, []);
29
24
 
30
25
  React.useEffect(() => {
31
- if (ref.current) {
32
- onLayout(columnIndex, ref.current.getBoundingClientRect());
26
+ if (__BROWSER__) {
27
+ if (node) {
28
+ window.requestAnimationFrame(() => {
29
+ setDimensions(node.getBoundingClientRect());
30
+ });
31
+ }
33
32
  }
34
- }, []);
33
+ }, [node]);
35
34
 
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
- );
35
+ return [ref, dimensions];
77
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]);
51
+
52
+ return React.cloneElement(props.item, {ref});
53
+ }
54
+
78
55
  type MeasureColumnWidthsPropsT = {
79
56
  columns: ColumnT<>[],
80
57
  // if selectable, measure the first column with checkbox included
@@ -84,6 +61,7 @@ type MeasureColumnWidthsPropsT = {
84
61
  widths: number[],
85
62
  };
86
63
 
64
+ // sample size could likely be generated based on row count, to have higher confidence
87
65
  const MAX_SAMPLE_SIZE = 50;
88
66
 
89
67
  function generateSampleIndices(inputMin, inputMax, maxSamples) {
@@ -118,41 +96,51 @@ export default function MeasureColumnWidths({
118
96
  }: MeasureColumnWidthsPropsT) {
119
97
  const [css] = useStyletron();
120
98
 
121
- const widthMap = React.useMemo(() => {
122
- return new Map();
123
- }, []);
99
+ const measurementCount = React.useRef(0);
100
+ const dimensionsCache = React.useRef(widths);
124
101
 
125
102
  const sampleSize =
126
103
  rows.length < MAX_SAMPLE_SIZE ? rows.length : MAX_SAMPLE_SIZE;
127
104
  const finishedMeasurementCount = (sampleSize + 1) * columns.length;
128
105
 
129
- const sampleIndexes = React.useMemo<number[]>(() => {
130
- return generateSampleIndices(0, rows.length - 1, sampleSize);
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);
131
112
  }, [columns, rows, widths, sampleSize]);
132
113
 
133
114
  const handleDimensionsChange = React.useCallback(
134
- (columnIndex, dimensions) => {
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
+
135
127
  const nextWidth = Math.min(
136
128
  Math.max(
137
129
  columns[columnIndex].minWidth || 0,
138
- widthMap.get(columnIndex) || 0,
130
+ dimensionsCache.current[columnIndex],
139
131
  dimensions.width + 1,
140
132
  ),
141
133
  columns[columnIndex].maxWidth || Infinity,
142
134
  );
143
135
 
144
- if (nextWidth !== widthMap.get(columnIndex)) {
145
- widthMap.set(columnIndex, nextWidth);
136
+ if (nextWidth !== dimensionsCache.current[columnIndex]) {
137
+ const nextWidths = [...dimensionsCache.current];
138
+ nextWidths[columnIndex] = nextWidth;
139
+ dimensionsCache.current = nextWidths;
146
140
  }
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()));
141
+
142
+ if (measurementCount.current >= finishedMeasurementCount) {
143
+ onWidthsChange(dimensionsCache.current);
156
144
  }
157
145
  },
158
146
  [columns, finishedMeasurementCount, onWidthsChange],
@@ -164,27 +152,61 @@ export default function MeasureColumnWidths({
164
152
  height: 0,
165
153
  });
166
154
 
167
- // Remove the measurement nodes after we are done updating our column width
168
- if (widthMap.size === columns.length) {
155
+ if (measurementCount.current >= finishedMeasurementCount) {
169
156
  return null;
170
157
  }
171
158
 
172
159
  return (
173
160
  // eslint-disable-next-line jsx-a11y/role-supports-aria-props
174
161
  <div className={hiddenStyle} aria-hidden role="none">
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}
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
+ }
185
181
  />
186
- );
182
+ ));
187
183
  })}
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
+ ))}
188
210
  </div>
189
211
  );
190
212
  }
@@ -17,8 +17,6 @@ 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
-
22
20
  function CellPlacement({
23
21
  columnIndex,
24
22
  rowIndex,
@@ -257,7 +255,7 @@ function Header(props) {
257
255
  }))));
258
256
  }
259
257
 
260
- function Headers() {
258
+ function Headers(props) {
261
259
  const [css, theme] = useStyletron();
262
260
  const locale = React.useContext(LocaleContext);
263
261
  const ctx = React.useContext(HeaderContext);
@@ -267,7 +265,7 @@ function Headers() {
267
265
  position: 'sticky',
268
266
  top: 0,
269
267
  left: 0,
270
- width: `${sum(ctx.widths)}px`,
268
+ width: `${ctx.widths.reduce((sum, w) => sum + w, 0)}px`,
271
269
  height: `${HEADER_ROW_HEIGHT}px`,
272
270
  display: 'flex',
273
271
  // this feels bad.. the absolutely positioned children elements
@@ -469,10 +467,8 @@ export function DataTable({
469
467
  }
470
468
 
471
469
  return rowHeight;
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);
470
+ }, [rowHeight]);
471
+ const gridRef = React.useRef(null);
476
472
  const [measuredWidths, setMeasuredWidths] = React.useState(columns.map(() => 0));
477
473
  const [resizeDeltas, setResizeDeltas] = React.useState(columns.map(() => 0));
478
474
  React.useEffect(() => {
@@ -484,11 +480,11 @@ export function DataTable({
484
480
  });
485
481
  }, [columns]);
486
482
  const resetAfterColumnIndex = React.useCallback(columnIndex => {
487
- if (gridRef) {
483
+ if (gridRef.current) {
488
484
  // $FlowFixMe trigger react-window to layout the elements again
489
- gridRef.resetAfterColumnIndex(columnIndex, true);
485
+ gridRef.current.resetAfterColumnIndex(columnIndex, true);
490
486
  }
491
- }, [gridRef]);
487
+ }, [gridRef.current]);
492
488
  const handleWidthsChange = React.useCallback(nextWidths => {
493
489
  setMeasuredWidths(nextWidths);
494
490
  resetAfterColumnIndex(0);
@@ -598,10 +594,13 @@ export function DataTable({
598
594
  }, [sortedIndices, filteredIndices, onIncludedRowsChange, allRows]);
599
595
  const [browserScrollbarWidth, setBrowserScrollbarWidth] = React.useState(0);
600
596
  const normalizedWidths = React.useMemo(() => {
597
+ const sum = ns => ns.reduce((s, n) => s + n, 0);
598
+
601
599
  const resizedWidths = measuredWidths.map((w, i) => Math.floor(w) + Math.floor(resizeDeltas[i]));
602
600
 
603
- if (gridRef) {
604
- const gridProps = gridRef.props;
601
+ if (gridRef.current) {
602
+ // $FlowFixMe
603
+ const gridProps = gridRef.current.props;
605
604
  let isContentTallerThanContainer = false;
606
605
  let visibleRowHeight = 0;
607
606
 
@@ -636,7 +635,7 @@ export function DataTable({
636
635
  }
637
636
 
638
637
  return resizedWidths;
639
- }, [gridRef, measuredWidths, resizeDeltas, browserScrollbarWidth, rows.length, columns]);
638
+ }, [measuredWidths, resizeDeltas, browserScrollbarWidth, rows.length, columns]);
640
639
  const isSelectable = batchActions ? !!batchActions.length : false;
641
640
  const isSelectedAll = React.useMemo(() => {
642
641
  if (!selectedRowIds) {
@@ -685,10 +684,10 @@ export function DataTable({
685
684
  function handleRowHighlightIndexChange(nextIndex) {
686
685
  setRowHighlightIndex(nextIndex);
687
686
 
688
- if (gridRef) {
687
+ if (gridRef.current) {
689
688
  if (nextIndex >= 0) {
690
689
  // $FlowFixMe - unable to get react-window types
691
- gridRef.scrollToItem({
690
+ gridRef.current.scrollToItem({
692
691
  rowIndex: nextIndex
693
692
  });
694
693
  }
@@ -777,9 +776,8 @@ export function DataTable({
777
776
  }
778
777
  }, /*#__PURE__*/React.createElement(VariableSizeGrid // eslint-disable-next-line flowtype/no-weak-types
779
778
  , {
780
- ref: setGridRef,
779
+ ref: gridRef,
781
780
  overscanRowCount: 10,
782
- overscanColumnCount: 5,
783
781
  innerElementType: InnerTableElement,
784
782
  columnCount: columns.length,
785
783
  columnWidth: columnIndex => normalizedWidths[columnIndex],
@@ -7,59 +7,40 @@ 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
11
10
 
12
- function MeasureColumn({
13
- sampleIndexes,
14
- column,
15
- columnIndex,
16
- rows,
17
- isSelectable,
18
- onLayout
19
- }) {
20
- const [css] = useStyletron();
21
- const ref = useRef();
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
+ }, []);
22
18
  React.useEffect(() => {
23
- if (ref.current) {
24
- onLayout(columnIndex, ref.current.getBoundingClientRect());
19
+ if (typeof document !== 'undefined') {
20
+ if (node) {
21
+ window.requestAnimationFrame(() => {
22
+ setDimensions(node.getBoundingClientRect());
23
+ });
24
+ }
25
25
  }
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
- }));
26
+ }, [node]);
27
+ return [ref, dimensions];
61
28
  }
62
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
+ });
41
+ }
42
+
43
+ // sample size could likely be generated based on row count, to have higher confidence
63
44
  const MAX_SAMPLE_SIZE = 50;
64
45
 
65
46
  function generateSampleIndices(inputMin, inputMax, maxSamples) {
@@ -96,35 +77,43 @@ export default function MeasureColumnWidths({
96
77
  onWidthsChange
97
78
  }) {
98
79
  const [css] = useStyletron();
99
- const widthMap = React.useMemo(() => {
100
- return new Map();
101
- }, []);
80
+ const measurementCount = React.useRef(0);
81
+ const dimensionsCache = React.useRef(widths);
102
82
  const sampleSize = rows.length < MAX_SAMPLE_SIZE ? rows.length : MAX_SAMPLE_SIZE;
103
83
  const finishedMeasurementCount = (sampleSize + 1) * columns.length;
104
- const sampleIndexes = React.useMemo(() => {
105
- return generateSampleIndices(0, rows.length - 1, sampleSize);
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);
106
89
  }, [columns, rows, widths, sampleSize]);
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);
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);
109
99
 
110
- if (nextWidth !== widthMap.get(columnIndex)) {
111
- widthMap.set(columnIndex, nextWidth);
100
+ if (nextWidth !== dimensionsCache.current[columnIndex]) {
101
+ const nextWidths = [...dimensionsCache.current];
102
+ nextWidths[columnIndex] = nextWidth;
103
+ dimensionsCache.current = nextWidths;
112
104
  }
113
105
 
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()));
106
+ if (measurementCount.current >= finishedMeasurementCount) {
107
+ onWidthsChange(dimensionsCache.current);
119
108
  }
120
109
  }, [columns, finishedMeasurementCount, onWidthsChange]);
121
110
  const hiddenStyle = css({
122
111
  position: 'absolute',
123
112
  overflow: 'hidden',
124
113
  height: 0
125
- }); // Remove the measurement nodes after we are done updating our column width
114
+ });
126
115
 
127
- if (widthMap.size === columns.length) {
116
+ if (measurementCount.current >= finishedMeasurementCount) {
128
117
  return null;
129
118
  }
130
119
 
@@ -135,16 +124,38 @@ export default function MeasureColumnWidths({
135
124
  className: hiddenStyle,
136
125
  "aria-hidden": true,
137
126
  role: "none"
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
- }))
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
+ })))
149
160
  );
150
161
  }