Skip to content

Commit 3d1d8b9

Browse files
rix0rrrgajus
authored andcommitted
feat: add support for wrapping with newlines (#88)
* Add support for wrapping with newlines It can be useful to more accurately control formatting in table cells by means of inserting newlines. For example, we can now put bulleted lists or pretty-printed JSON objects in table cells. This change makes it so that any '\n' characters encountered in the cell's values will be translated into line breaks. Implementation note: the cell wrapping logic that used to be duplicated between `mapDataUsingRowHeightIndex` and `calculateCellHeight` has been moved to `wrapCell` to be shared between them. * Add test with color coding * More readable way to write ANSI test
1 parent 2417ad6 commit 3d1d8b9

File tree

5 files changed

+110
-18
lines changed

5 files changed

+110
-18
lines changed

src/calculateCellHeight.js

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import _ from 'lodash';
2-
import stringWidth from 'string-width';
3-
import wrapWord from './wrapWord';
2+
import wrapCell from './wrapCell';
43

54
/**
65
* @param {string} value
@@ -21,9 +20,5 @@ export default (value, columnWidth, useWrapWord = false) => {
2120
throw new Error('Column width must be greater than 0.');
2221
}
2322

24-
if (useWrapWord) {
25-
return wrapWord(value, columnWidth).length;
26-
}
27-
28-
return Math.ceil(stringWidth(value) / columnWidth);
23+
return wrapCell(value, columnWidth, useWrapWord).length;
2924
};

src/mapDataUsingRowHeightIndex.js

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import _ from 'lodash';
2-
import wrapString from './wrapString';
3-
import wrapWord from './wrapWord';
2+
import wrapCell from './wrapCell';
43

54
/**
65
* @param {Array} unmappedRows
@@ -21,16 +20,10 @@ export default (unmappedRows, rowHeightIndex, config) => {
2120
// [{cell index within a virtual row; index1}]
2221

2322
cells.forEach((value, index1) => {
24-
let chunkedValue;
23+
const cellLines = wrapCell(value, config.columns[index1].width, config.columns[index1].wrapWord);
2524

26-
if (config.columns[index1].wrapWord) {
27-
chunkedValue = wrapWord(value, config.columns[index1].width);
28-
} else {
29-
chunkedValue = wrapString(value, config.columns[index1].width);
30-
}
31-
32-
chunkedValue.forEach((part, index2) => {
33-
rowHeight[index2][index1] = part;
25+
cellLines.forEach((cellLine, index2) => {
26+
rowHeight[index2][index1] = cellLine;
3427
});
3528
});
3629

src/wrapCell.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import wrapString from './wrapString';
2+
import wrapWord from './wrapWord';
3+
4+
/**
5+
* Wrap a single cell value into a list of lines
6+
*
7+
* Always wraps on newlines, for the remainder uses either word or string wrapping
8+
* depending on user configuration.
9+
*
10+
* @param {string} cellValue
11+
* @param {number} columnWidth
12+
* @param {boolean} useWrapWord
13+
* @returns {Array}
14+
*/
15+
export default (cellValue, columnWidth, useWrapWord) => {
16+
// First split on literal newlines
17+
const cellLines = cellValue.split('\n');
18+
19+
// Then iterate over the list and word-wrap every remaining line if necessary.
20+
for (let lineNr = 0; lineNr < cellLines.length;) {
21+
let lineChunks;
22+
23+
if (useWrapWord) {
24+
lineChunks = wrapWord(cellLines[lineNr], columnWidth);
25+
} else {
26+
lineChunks = wrapString(cellLines[lineNr], columnWidth);
27+
}
28+
29+
// Replace our original array element with whatever the wrapping returned
30+
cellLines.splice(lineNr, 1, ...lineChunks);
31+
lineNr += lineChunks.length;
32+
}
33+
34+
return cellLines;
35+
};

test/calculateCellHeight.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ describe('calculateCellHeight', () => {
1414
}).to.throw(Error, 'Value must be a string.');
1515
});
1616
});
17+
it('contains newlines', () => {
18+
expect(calculateCellHeight('a\nb\nc', 10)).to.equal(3);
19+
});
20+
it('contains newlines and will be wrapped', () => {
21+
expect(calculateCellHeight('aa\nbbb\nc', 2)).to.equal(4);
22+
});
1723
});
1824
describe('context width', () => {
1925
context('is not an integer', () => {

test/mapDataUsingRowHeightIndex.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
expect
33
} from 'chai';
4+
import chalk from 'chalk';
45
import mapDataUsingRowHeightIndex from '../src/mapDataUsingRowHeightIndex';
56

67
describe('mapDataUsingRowHeightIndex', () => {
@@ -66,6 +67,68 @@ describe('mapDataUsingRowHeightIndex', () => {
6667
});
6768
});
6869

70+
context('single cell contains newlines', () => {
71+
it('maps data to multiple rows', () => {
72+
const config = {
73+
columns: {
74+
0: {
75+
width: 100
76+
}
77+
}
78+
};
79+
80+
const rowSpanIndex = [
81+
5
82+
];
83+
84+
const data = [
85+
[
86+
'aa\nbb\ncc\ndd\nee'
87+
]
88+
];
89+
90+
const mappedData = mapDataUsingRowHeightIndex(data, rowSpanIndex, config);
91+
92+
expect(mappedData).to.deep.equal([
93+
['aa'],
94+
['bb'],
95+
['cc'],
96+
['dd'],
97+
['ee']
98+
]);
99+
});
100+
101+
it('maps data with color coding to multiple rows', () => {
102+
const config = {
103+
columns: {
104+
0: {
105+
width: 100
106+
}
107+
}
108+
};
109+
110+
const rowSpanIndex = [
111+
5
112+
];
113+
114+
const data = [
115+
[
116+
chalk.red('aa\nbb\ncc\ndd\nee')
117+
]
118+
];
119+
120+
const mappedData = mapDataUsingRowHeightIndex(data, rowSpanIndex, config);
121+
122+
expect(mappedData).to.deep.equal([
123+
[chalk.red('aa')],
124+
[chalk.red('bb')],
125+
[chalk.red('cc')],
126+
[chalk.red('dd')],
127+
[chalk.red('ee')]
128+
]);
129+
});
130+
});
131+
69132
context('multiple cells spans multiple rows', () => {
70133
it('maps data to multiple rows', () => {
71134
const config = {

0 commit comments

Comments
 (0)