Skip to content

Commit 26658f9

Browse files
committed
Support spanning cells
1 parent 373785c commit 26658f9

50 files changed

Lines changed: 2084 additions & 546 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.README/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646

4747
{"gitdown": "include", "file": "./api/table/header.md"}
4848

49+
{"gitdown": "include", "file": "./api/table/spanningCells.md"}
50+
4951
{"gitdown": "include", "file": "./api/stream/index.md"}
5052

5153
{"gitdown": "include", "file": "./api/getBorderCharacters.md"}

.README/api/table/header.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ Type: `object`
44

55
Header configuration.
66

7+
*Deprecated in favor of the new spanning cells API.*
8+
79
The header configuration inherits the most of the column's, except:
810
- `content` **{string}**: the header content.
911
- `width:` calculate based on the content width automatically.

.README/api/table/spanningCells.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
##### config.spanningCells
2+
3+
Type: `SpanningCellConfig[]`
4+
5+
Spanning cells configuration.
6+
7+
The configuration should be straightforward: just specify an array of minimal cell configurations including the position of top-left cell
8+
and the number of columns and/or rows will be expanded from it.
9+
10+
The content of overlap cells will be ignored to make the `data` shape be consistent.
11+
12+
By default, the configuration of column that the top-left cell belongs to will be applied to the whole spanning cell, except:
13+
* The `width` will be summed up of all spanning columns.
14+
* The `paddingRight` will be received from the right-most column intentionally.
15+
16+
Advances customized column-like styles can be configurable to each spanning cell to overwrite the default behavior.
17+
18+
```js
19+
const data = [
20+
['Test Coverage Report', '', '', '', '', ''],
21+
['Module', 'Component', 'Test Cases', 'Failures', 'Durations', 'Success Rate'],
22+
['Services', 'User', '50', '30', '3m 7s', '60.0%'],
23+
['', 'Payment', '100', '80', '7m 15s', '80.0%'],
24+
['Subtotal', '', '150', '110', '10m 22s', '73.3%'],
25+
['Controllers', 'User', '24', '18', '1m 30s', '75.0%'],
26+
['', 'Payment', '30', '24', '50s', '80.0%'],
27+
['Subtotal', '', '54', '42', '2m 20s', '77.8%'],
28+
['Total', '', '204', '152', '12m 42s', '74.5%'],
29+
];
30+
31+
const config = {
32+
columns: [
33+
{ alignment: 'center', width: 12 },
34+
{ alignment: 'center', width: 10 },
35+
{ alignment: 'right' },
36+
{ alignment: 'right' },
37+
{ alignment: 'right' },
38+
{ alignment: 'right' }
39+
],
40+
spanningCells: [
41+
{ col: 0, row: 0, colSpan: 6 },
42+
{ col: 0, row: 2, rowSpan: 2, verticalAlignment: 'middle'},
43+
{ col: 0, row: 4, colSpan: 2, alignment: 'right'},
44+
{ col: 0, row: 5, rowSpan: 2, verticalAlignment: 'middle'},
45+
{ col: 0, row: 7, colSpan: 2, alignment: 'right' },
46+
{ col: 0, row: 8, colSpan: 2, alignment: 'right' }
47+
],
48+
};
49+
50+
console.log(table(data, config));
51+
```
52+
53+
```
54+
╔══════════════════════════════════════════════════════════════════════════════╗
55+
║ Test Coverage Report ║
56+
╟──────────────┬────────────┬────────────┬──────────┬───────────┬──────────────╢
57+
║ Module │ Component │ Test Cases │ Failures │ Durations │ Success Rate ║
58+
╟──────────────┼────────────┼────────────┼──────────┼───────────┼──────────────╢
59+
║ │ User │ 50 │ 30 │ 3m 7s │ 60.0% ║
60+
║ Services ├────────────┼────────────┼──────────┼───────────┼──────────────╢
61+
║ │ Payment │ 100 │ 80 │ 7m 15s │ 80.0% ║
62+
╟──────────────┴────────────┼────────────┼──────────┼───────────┼──────────────╢
63+
║ Subtotal │ 150 │ 110 │ 10m 22s │ 73.3% ║
64+
╟──────────────┬────────────┼────────────┼──────────┼───────────┼──────────────╢
65+
║ │ User │ 24 │ 18 │ 1m 30s │ 75.0% ║
66+
║ Controllers ├────────────┼────────────┼──────────┼───────────┼──────────────╢
67+
║ │ Payment │ 30 │ 24 │ 50s │ 80.0% ║
68+
╟──────────────┴────────────┼────────────┼──────────┼───────────┼──────────────╢
69+
║ Subtotal │ 54 │ 42 │ 2m 20s │ 77.8% ║
70+
╟───────────────────────────┼────────────┼──────────┼───────────┼──────────────╢
71+
║ Total │ 204 │ 152 │ 12m 42s │ 74.5% ║
72+
╚═══════════════════════════╧════════════╧══════════╧═══════════╧══════════════╝
73+
```

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ tsconfig.tsbuildinfo
1111
!.README
1212
!.mocharc.js
1313
!.github
14+
!.husky

.husky/post-commit

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/sh
2+
. "$(dirname "$0")/_/husky.sh"
3+
4+
npm run create-readme
5+
git add README.md
6+
git commit -m 'docs: generate docs' --no-verify

.husky/pre-commit

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/sh
2+
. "$(dirname "$0")/_/husky.sh"
3+
4+
npm run build
5+
npm run lint
6+
npm run test

README.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,8 @@ Type: `object`
548548

549549
Header configuration.
550550

551+
*Deprecated in favor of the new spanning cells API.*
552+
551553
The header configuration inherits the most of the column's, except:
552554
- `content` **{string}**: the header content.
553555
- `width:` calculate based on the content width automatically.
@@ -589,6 +591,82 @@ console.log(table(data, config));
589591
```
590592

591593

594+
<a name="table-api-table-1-config-spanningcells"></a>
595+
##### config.spanningCells
596+
597+
Type: `SpanningCellConfig[]`
598+
599+
Spanning cells configuration.
600+
601+
The configuration should be straightforward: just specify an array of minimal cell configurations including the position of top-left cell
602+
and the number of columns and/or rows will be expanded from it.
603+
604+
The content of overlap cells will be ignored to make the `data` shape be consistent.
605+
606+
By default, the configuration of column that the top-left cell belongs to will be applied to the whole spanning cell, except:
607+
* The `width` will be summed up of all spanning columns.
608+
* The `paddingRight` will be received from the right-most column intentionally.
609+
610+
Advances customized column-like styles can be configurable to each spanning cell to overwrite the default behavior.
611+
612+
```js
613+
const data = [
614+
['Test Coverage Report', '', '', '', '', ''],
615+
['Module', 'Component', 'Test Cases', 'Failures', 'Durations', 'Success Rate'],
616+
['Services', 'User', '50', '30', '3m 7s', '60.0%'],
617+
['', 'Payment', '100', '80', '7m 15s', '80.0%'],
618+
['Subtotal', '', '150', '110', '10m 22s', '73.3%'],
619+
['Controllers', 'User', '24', '18', '1m 30s', '75.0%'],
620+
['', 'Payment', '30', '24', '50s', '80.0%'],
621+
['Subtotal', '', '54', '42', '2m 20s', '77.8%'],
622+
['Total', '', '204', '152', '12m 42s', '74.5%'],
623+
];
624+
625+
const config = {
626+
columns: [
627+
{ alignment: 'center', width: 12 },
628+
{ alignment: 'center', width: 10 },
629+
{ alignment: 'right' },
630+
{ alignment: 'right' },
631+
{ alignment: 'right' },
632+
{ alignment: 'right' }
633+
],
634+
spanningCells: [
635+
{ col: 0, row: 0, colSpan: 6 },
636+
{ col: 0, row: 2, rowSpan: 2, verticalAlignment: 'middle'},
637+
{ col: 0, row: 4, colSpan: 2, alignment: 'right'},
638+
{ col: 0, row: 5, rowSpan: 2, verticalAlignment: 'middle'},
639+
{ col: 0, row: 7, colSpan: 2, alignment: 'right' },
640+
{ col: 0, row: 8, colSpan: 2, alignment: 'right' }
641+
],
642+
};
643+
644+
console.log(table(data, config));
645+
```
646+
647+
```
648+
╔══════════════════════════════════════════════════════════════════════════════╗
649+
║ Test Coverage Report ║
650+
╟──────────────┬────────────┬────────────┬──────────┬───────────┬──────────────╢
651+
║ Module │ Component │ Test Cases │ Failures │ Durations │ Success Rate ║
652+
╟──────────────┼────────────┼────────────┼──────────┼───────────┼──────────────╢
653+
║ │ User │ 50 │ 30 │ 3m 7s │ 60.0% ║
654+
║ Services ├────────────┼────────────┼──────────┼───────────┼──────────────╢
655+
║ │ Payment │ 100 │ 80 │ 7m 15s │ 80.0% ║
656+
╟──────────────┴────────────┼────────────┼──────────┼───────────┼──────────────╢
657+
║ Subtotal │ 150 │ 110 │ 10m 22s │ 73.3% ║
658+
╟──────────────┬────────────┼────────────┼──────────┼───────────┼──────────────╢
659+
║ │ User │ 24 │ 18 │ 1m 30s │ 75.0% ║
660+
║ Controllers ├────────────┼────────────┼──────────┼───────────┼──────────────╢
661+
║ │ Payment │ 30 │ 24 │ 50s │ 80.0% ║
662+
╟──────────────┴────────────┼────────────┼──────────┼───────────┼──────────────╢
663+
║ Subtotal │ 54 │ 42 │ 2m 20s │ 77.8% ║
664+
╟───────────────────────────┼────────────┼──────────┼───────────┼──────────────╢
665+
║ Total │ 204 │ 152 │ 12m 42s │ 74.5% ║
666+
╚═══════════════════════════╧════════════╧══════════╧═══════════╧══════════════╝
667+
```
668+
669+
592670
<a name="table-api-createstream"></a>
593671
### createStream
594672

src/alignSpanningCell.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import {
2+
alignString,
3+
} from './alignString';
4+
import {
5+
padCellVertically,
6+
} from './mapDataUsingRowHeights';
7+
import {
8+
padString,
9+
} from './padTableData';
10+
import type {
11+
SpanningCellContext,
12+
} from './spanningCellManager';
13+
import {
14+
truncateString,
15+
} from './truncateTableData';
16+
import type {
17+
RangeConfig,
18+
} from './types/internal';
19+
import {
20+
sequence, sumArray,
21+
} from './utils';
22+
import {
23+
wrapCell,
24+
} from './wrapCell';
25+
26+
/**
27+
* Fill content into all cells in range in order to calculate total height
28+
*/
29+
export const wrapRangeContent = (rangeConfig: RangeConfig, rangeWidth: number, context: SpanningCellContext): string[] => {
30+
const {topLeft, paddingRight, paddingLeft, truncate, wrapWord, alignment} = rangeConfig;
31+
32+
const originalContent = context.rows[topLeft.row][topLeft.col];
33+
const contentWidth = rangeWidth - paddingLeft - paddingRight;
34+
35+
return wrapCell(truncateString(originalContent, truncate), contentWidth, wrapWord).map((line) => {
36+
const alignedLine = alignString(line, contentWidth, alignment);
37+
38+
return padString(alignedLine, paddingLeft, paddingRight);
39+
});
40+
};
41+
42+
export const alignVerticalRangeContent = (range: RangeConfig, content: string[], context: SpanningCellContext) => {
43+
const {rows, drawHorizontalLine, rowHeights} = context;
44+
const {topLeft, bottomRight, verticalAlignment} = range;
45+
46+
// They are empty before calculateRowHeights function run
47+
if (rowHeights.length === 0) {
48+
return [];
49+
}
50+
51+
const totalCellHeight = sumArray(rowHeights.slice(topLeft.row, bottomRight.row + 1));
52+
const totalBorderHeight = bottomRight.row - topLeft.row;
53+
const hiddenHorizontalBorderCount = sequence(topLeft.row + 1, bottomRight.row).filter((horizontalBorderIndex) => {
54+
return !drawHorizontalLine(horizontalBorderIndex, rows.length);
55+
}).length;
56+
57+
const availableRangeHeight = totalCellHeight + totalBorderHeight - hiddenHorizontalBorderCount;
58+
59+
return padCellVertically(content, availableRangeHeight, verticalAlignment).map((line) => {
60+
if (line.length === 0) {
61+
return ' '.repeat(content[0].length);
62+
}
63+
64+
return line;
65+
});
66+
};

src/alignTableData.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@ import type {
77
} from './types/internal';
88

99
export const alignTableData = (rows: Row[], config: BaseConfig): Row[] => {
10-
return rows.map((row) => {
10+
return rows.map((row, rowIndex) => {
1111
return row.map((cell, cellIndex) => {
1212
const {width, alignment} = config.columns[cellIndex];
1313

14+
const containingRange = config.spanningCellManager?.getContainingRange({col: cellIndex,
15+
row: rowIndex}, {mapped: true});
16+
if (containingRange) {
17+
return cell;
18+
}
19+
1420
return alignString(cell, width, alignment);
1521
});
1622
});

src/calculateCellWidths.ts

Lines changed: 0 additions & 15 deletions
This file was deleted.

0 commit comments

Comments
 (0)