• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gregnb / mui-datatables / #1864

23 Jun 2026 05:20PM UTC coverage: 73.248% (+0.7%) from 72.5%
#1864

push

265 of 396 branches covered (66.92%)

Branch coverage included in aggregate %.

425 of 546 relevant lines covered (77.84%)

41.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

2.08
/src/components/TableResize.js
1
import React from 'react';
2
import PropTypes from 'prop-types';
3
import { withStyles } from 'tss-react/mui';
4

5
const defaultResizeStyles = {
6
  root: {
7
    position: 'absolute',
1✔
8
  },
9
  resizer: {
10
    position: 'absolute',
11
    width: '1px',
12
    height: '100%',
13
    left: '100px',
14
    cursor: 'ew-resize',
15
    border: '0.1px solid rgba(224, 224, 224, 1)',
16
  },
17
};
18

19
function getParentOffsetLeft(tableEl) {
20
  let ii = 0,
21
    parentOffsetLeft = 0,
22
    offsetParent = tableEl.offsetParent;
23
  while (offsetParent) {
24
    parentOffsetLeft = parentOffsetLeft + (offsetParent.offsetLeft || 0) - (offsetParent.scrollLeft || 0);
25
    offsetParent = offsetParent.offsetParent;
26
    ii++;
27
    if (ii > 1000) break;
28
  }
29
  return parentOffsetLeft;
30
}
31

32
class TableResize extends React.Component {
33
  static propTypes = {
34
    /** Extend the style applied to components */
35
    classes: PropTypes.object,
×
36
  };
×
37

×
38
  state = {
39
    resizeCoords: {},
40
    priorPosition: {},
41
    tableWidth: '100%',
42
    tableHeight: '100%',
×
43
  };
×
44

×
45
  handleResize = () => {
46
    if (window.innerWidth !== this.windowWidth) {
47
      this.windowWidth = window.innerWidth;
48
      this.setDividers();
×
49
    }
50
  };
51

52
  componentDidMount() {
×
53
    this.minWidths = [];
×
54
    this.windowWidth = null;
×
55
    this.props.setResizeable(this.setCellRefs);
56
    this.props.updateDividers(() => this.setState({ updateCoords: true }, () => this.updateWidths));
57
    window.addEventListener('resize', this.handleResize, false);
58
  }
×
59

×
60
  componentWillUnmount() {
61
    window.removeEventListener('resize', this.handleResize, false);
×
62
  }
×
63

64
  setCellRefs = (cellsRef, tableRef) => {
×
65
    this.cellsRef = cellsRef;
×
66
    this.tableRef = tableRef;
×
67
    this.setDividers();
68
  };
×
69

70
  setDividers = () => {
71
    const tableEl = this.tableRef;
72
    const { width: tableWidth, height: tableHeight } = tableEl.getBoundingClientRect();
73
    const { resizeCoords } = this.state;
×
74

75
    for (let prop in resizeCoords) {
76
      delete resizeCoords[prop];
77
    }
×
78

×
79
    let parentOffsetLeft = getParentOffsetLeft(tableEl);
80
    let finalCells = Object.entries(this.cellsRef);
×
81
    let cellMinusOne = finalCells.filter((_item, ix) => ix + 1 < finalCells.length);
×
82

×
83
    cellMinusOne.forEach(([key, item], idx) => {
×
84
      if (!item) return;
85
      let elRect = item.getBoundingClientRect();
×
86
      let left = elRect.left;
×
87
      left = (left || 0) - parentOffsetLeft;
88
      const elStyle = window.getComputedStyle(item, null);
89
      resizeCoords[key] = { left: left + item.offsetWidth };
90
    });
91
    this.setState({ tableWidth, tableHeight, resizeCoords }, this.updateWidths);
×
92
  };
93

94
  updateWidths = () => {
95
    let lastPosition = 0;
×
96
    const { resizeCoords, tableWidth } = this.state;
97

×
98
    Object.entries(resizeCoords).forEach(([key, item]) => {
×
99
      let newWidth = Number(((item.left - lastPosition) / tableWidth) * 100);
100

×
101
      /*
×
102
        Using .toFixed(2) causes the columns to jitter when resized. On all browsers I (patrojk) have tested,
103
        a width with a floating point decimal works fine. It's unclear to me why the numbers were being rouned.
×
104
        However, I'm putting in an undocumented escape hatch to use toFixed in case the change introduces a bug.
105
        The below code will be removed in a later release if no problems with non-rounded widths are reported.
106
      */
107
      if (typeof this.props.resizableColumns === 'object' && this.props.resizableColumns.roundWidthPercentages) {
108
        newWidth = newWidth.toFixed(2);
×
109
      }
110

111
      lastPosition = item.left;
112

×
113
      const thCell = this.cellsRef[key];
×
114
      if (thCell) thCell.style.width = newWidth + '%';
115
    });
×
116
  };
117

118
  onResizeStart = (id, e) => {
×
119
    const tableEl = this.tableRef;
120
    const originalWidth = tableEl.style.width;
121
    let lastColumn = 0;
122
    tableEl.style.width = '1px';
123

124
    let finalCells = Object.entries(this.cellsRef);
125
    finalCells.forEach(([key, item], idx) => {
×
126
      let elRect = item ? item.getBoundingClientRect() : { width: 0, left: 0 };
127
      this.minWidths[key] = elRect.width;
128
      lastColumn = Math.max(key, lastColumn);
129
    });
130
    tableEl.style.width = originalWidth;
131

132
    this.setState({ isResize: true, id, lastColumn });
133
  };
134

135
  onResizeMove = (id, e) => {
136
    const { isResize, resizeCoords, lastColumn } = this.state;
137

138
    const prevCol = id => {
139
      let nextId = id - 1;
140
      while (typeof resizeCoords[nextId] === 'undefined' && nextId >= 0) {
141
        nextId--;
142
      }
143
      return nextId;
144
    };
145
    const nextCol = id => {
146
      let nextId = id + 1;
147
      let tries = 0;
148
      while (typeof resizeCoords[nextId] === 'undefined' && tries < 20) {
149
        nextId++;
150
        tries++;
151
      }
152
      return nextId;
153
    };
154

155
    const fixedMinWidth1 = this.minWidths[id];
156
    const fixedMinWidth2 = this.minWidths[nextCol(parseInt(id, 10))] || this.minWidths[id];
157
    const idNumber = parseInt(id, 10);
158
    const finalCells = Object.entries(this.cellsRef);
159
    const tableEl = this.tableRef;
160
    const { width: tableWidth, height: tableHeight } = tableEl.getBoundingClientRect();
161
    const { selectableRows } = this.props.options;
162

163
    let parentOffsetLeft = getParentOffsetLeft(tableEl);
164

165
    const nextCoord = id => {
166
      let nextId = id + 1;
167
      let tries = 0;
168
      while (typeof resizeCoords[nextId] === 'undefined' && tries < 20) {
169
        nextId++;
170
        tries++;
171
      }
172
      return resizeCoords[nextId];
173
    };
174
    const prevCoord = id => {
175
      let nextId = id - 1;
176
      while (typeof resizeCoords[nextId] === 'undefined' && nextId >= 0) {
177
        nextId--;
178
      }
179
      return resizeCoords[nextId];
180
    };
181

182
    if (isResize) {
183
      let leftPos = e.clientX - parentOffsetLeft;
184

185
      const handleMoveRightmostBoundary = (leftPos, tableWidth, fixedMinWidth) => {
186
        if (leftPos > tableWidth - fixedMinWidth) {
187
          return tableWidth - fixedMinWidth;
188
        }
189
        return leftPos;
190
      };
191

192
      const handleMoveLeftmostBoundary = (leftPos, fixedMinWidth) => {
193
        if (leftPos < fixedMinWidth) {
194
          return fixedMinWidth;
195
        }
196
        return leftPos;
197
      };
198

199
      const handleMoveRight = (leftPos, resizeCoords, id, fixedMinWidth) => {
200
        if (typeof nextCoord(id) === 'undefined') return leftPos;
201
        if (leftPos > nextCoord(id).left - fixedMinWidth) {
202
          return nextCoord(id).left - fixedMinWidth;
203
        }
204
        return leftPos;
205
      };
206

207
      const handleMoveLeft = (leftPos, resizeCoords, id, fixedMinWidth) => {
208
        if (typeof prevCoord(id) === 'undefined') return leftPos;
209
        if (leftPos < prevCoord(id).left + fixedMinWidth) {
210
          return prevCoord(id).left + fixedMinWidth;
211
        }
212
        return leftPos;
213
      };
214

215
      const isFirstColumn = (selectableRows, id) => {
216
        let firstColumn = 1;
217
        while (!resizeCoords[firstColumn] && firstColumn < 20) {
218
          firstColumn++;
219
        }
220

221
        return (selectableRows !== 'none' && id === 0) || (selectableRows === 'none' && id === firstColumn);
222
      };
223

224
      const isLastColumn = (id, finalCells) => {
225
        return id === prevCol(lastColumn);
226
      };
227

228
      if (isFirstColumn(selectableRows, idNumber) && isLastColumn(idNumber, finalCells)) {
229
        leftPos = handleMoveLeftmostBoundary(leftPos, fixedMinWidth1);
230
        leftPos = handleMoveRightmostBoundary(leftPos, tableWidth, fixedMinWidth2);
231
      } else if (!isFirstColumn(selectableRows, idNumber) && isLastColumn(idNumber, finalCells)) {
232
        leftPos = handleMoveRightmostBoundary(leftPos, tableWidth, fixedMinWidth2);
233
        leftPos = handleMoveLeft(leftPos, resizeCoords, idNumber, fixedMinWidth1);
234
      } else if (isFirstColumn(selectableRows, idNumber) && !isLastColumn(idNumber, finalCells)) {
235
        leftPos = handleMoveLeftmostBoundary(leftPos, fixedMinWidth1);
236
        leftPos = handleMoveRight(leftPos, resizeCoords, idNumber, fixedMinWidth2);
237
      } else if (!isFirstColumn(selectableRows, idNumber) && !isLastColumn(idNumber, finalCells)) {
238
        leftPos = handleMoveLeft(leftPos, resizeCoords, idNumber, fixedMinWidth1);
239
        leftPos = handleMoveRight(leftPos, resizeCoords, idNumber, fixedMinWidth2);
240
      }
241

242
      const curCoord = { ...resizeCoords[id], left: leftPos };
243
      const newResizeCoords = { ...resizeCoords, [id]: curCoord };
244
      this.setState({ resizeCoords: newResizeCoords, tableHeight }, this.updateWidths);
245
    }
246
  };
247

248
  onResizeEnd = (id, e) => {
249
    this.setState({ isResize: false, id: null });
250
  };
251

252
  render() {
253
    const { classes, tableId } = this.props;
254
    const { id, isResize, resizeCoords, tableWidth, tableHeight } = this.state;
255

256
    return (
257
      <div className={classes.root} style={{ width: tableWidth }}>
258
        {Object.entries(resizeCoords).map(([key, val]) => {
259
          return (
260
            <div
261
              data-divider-index={key}
262
              data-tableid={tableId}
263
              aria-hidden="true"
264
              key={key}
265
              onMouseMove={this.onResizeMove.bind(null, key)}
266
              onMouseUp={this.onResizeEnd.bind(null, key)}
267
              style={{
268
                width: isResize && id == key ? tableWidth : 'auto',
269
                position: 'absolute',
270
                height: tableHeight - 2,
271
                cursor: 'ew-resize',
272
                zIndex: 1000,
273
              }}>
274
              <div
275
                aria-hidden="true"
276
                onMouseDown={this.onResizeStart.bind(null, key)}
277
                className={classes.resizer}
278
                style={{ left: val.left }}
279
              />
280
            </div>
281
          );
282
        })}
283
      </div>
284
    );
285
  }
286
}
287

288
export default withStyles(TableResize, defaultResizeStyles, { name: 'MUIDataTableResize' });
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc