js-spread-grid 0.0.1
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.
- package/package.json +26 -0
- package/src/core/render.js +400 -0
- package/src/core/state.js +183 -0
- package/src/core-utils/defaults.js +4 -0
- package/src/core-utils/rect.js +49 -0
- package/src/core-utils/rect.test.js +111 -0
- package/src/core-utils/roundToPixels.js +3 -0
- package/src/core-utils/stringifyId.js +27 -0
- package/src/core-utils/stringifyId.test.js +27 -0
- package/src/index.js +595 -0
- package/src/state-utils/getActive.js +17 -0
- package/src/state-utils/getCellSection.js +22 -0
- package/src/state-utils/getCellType.js +7 -0
- package/src/state-utils/getClipboardData.js +53 -0
- package/src/state-utils/getColumnIndex.js +25 -0
- package/src/state-utils/getCombinedCells.js +6 -0
- package/src/state-utils/getDataFormatting.js +46 -0
- package/src/state-utils/getEditableCells.js +23 -0
- package/src/state-utils/getEditedCellsAndFilters.js +4 -0
- package/src/state-utils/getEdition.js +6 -0
- package/src/state-utils/getFilterFormatting.js +3 -0
- package/src/state-utils/getFiltered.js +61 -0
- package/src/state-utils/getFilteringRules.js +5 -0
- package/src/state-utils/getFixedSize.js +8 -0
- package/src/state-utils/getFormatResolver.js +5 -0
- package/src/state-utils/getFormattingRules.js +5 -0
- package/src/state-utils/getHighlightedCells.js +40 -0
- package/src/state-utils/getHoveredCell.js +43 -0
- package/src/state-utils/getInputFormatting.js +3 -0
- package/src/state-utils/getInputPlacement.js +51 -0
- package/src/state-utils/getInvoked.js +5 -0
- package/src/state-utils/getIsTextValid.js +3 -0
- package/src/state-utils/getKeys.js +3 -0
- package/src/state-utils/getLookup.js +3 -0
- package/src/state-utils/getMeasureFormatting.js +3 -0
- package/src/state-utils/getMeasured.js +113 -0
- package/src/state-utils/getMousePosition.js +8 -0
- package/src/state-utils/getNewSortBy.js +20 -0
- package/src/state-utils/getPinned.js +8 -0
- package/src/state-utils/getPlaced.js +45 -0
- package/src/state-utils/getReducedCells.js +6 -0
- package/src/state-utils/getRenderFormatting.js +122 -0
- package/src/state-utils/getResizable.js +49 -0
- package/src/state-utils/getResolved.js +42 -0
- package/src/state-utils/getResolvedFilters.js +7 -0
- package/src/state-utils/getResolvedSortBy.js +7 -0
- package/src/state-utils/getRowIndex.js +25 -0
- package/src/state-utils/getScrollRect.js +41 -0
- package/src/state-utils/getSections.js +101 -0
- package/src/state-utils/getSelection.js +5 -0
- package/src/state-utils/getSorted.js +130 -0
- package/src/state-utils/getSortingFormatting.js +3 -0
- package/src/state-utils/getSortingRules.js +5 -0
- package/src/state-utils/getTextResolver.js +5 -0
- package/src/state-utils/getToggledValue.js +11 -0
- package/src/state-utils/getTotalSize.js +6 -0
- package/src/state-utils/getUnfolded.js +86 -0
- package/src/types/Edition.js +37 -0
- package/src/types/FilteringRules.js +48 -0
- package/src/types/FormatResolver.js +19 -0
- package/src/types/FormattingRules.js +120 -0
- package/src/types/FormattingRules.test.js +90 -0
- package/src/types/RulesLookup.js +118 -0
- package/src/types/Selection.js +25 -0
- package/src/types/SortingRules.js +62 -0
- package/src/types/TextResolver.js +60 -0
- package/src/types/VisibilityResolver.js +61 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import stringifyId from "../core-utils/stringifyId.js";
|
|
2
|
+
import Selection from "../types/Selection.js";
|
|
3
|
+
|
|
4
|
+
export default function getClipboardData(selectedCells, columns, rows, formatResolver) {
|
|
5
|
+
const columnLookup = new Map(columns.map(column => [column.key, column]));
|
|
6
|
+
const rowLookup = new Map(rows.map(row => [row.key, row]));
|
|
7
|
+
const selectedCellKeys = selectedCells.map(cell => ({
|
|
8
|
+
columnKey: stringifyId(cell.columnId),
|
|
9
|
+
rowKey: stringifyId(cell.rowId)
|
|
10
|
+
})).filter(cell => columnLookup.has(cell.columnKey) && rowLookup.has(cell.rowKey));
|
|
11
|
+
const selectedColumns = new Set(selectedCellKeys.map(cell => cell.columnKey));
|
|
12
|
+
const selectedRows = new Set(selectedCellKeys.map(cell => cell.rowKey));
|
|
13
|
+
|
|
14
|
+
if (selectedCellKeys.length === 0)
|
|
15
|
+
return '';
|
|
16
|
+
|
|
17
|
+
const selection = new Selection(selectedCells);
|
|
18
|
+
const minColumnIndex = Math.min(...selectedCellKeys.map(cell => columnLookup.get(cell.columnKey).index));
|
|
19
|
+
const maxColumnIndex = Math.max(...selectedCellKeys.map(cell => columnLookup.get(cell.columnKey).index));
|
|
20
|
+
const minRowIndex = Math.min(...selectedCellKeys.map(cell => rowLookup.get(cell.rowKey).index));
|
|
21
|
+
const maxRowIndex = Math.max(...selectedCellKeys.map(cell => rowLookup.get(cell.rowKey).index));
|
|
22
|
+
|
|
23
|
+
const stringBuilder = [];
|
|
24
|
+
|
|
25
|
+
for (let rowIndex = minRowIndex; rowIndex <= maxRowIndex; rowIndex++) {
|
|
26
|
+
const row = rows[rowIndex];
|
|
27
|
+
const rowKey = row.key;
|
|
28
|
+
|
|
29
|
+
if (!selectedRows.has(rowKey))
|
|
30
|
+
continue;
|
|
31
|
+
|
|
32
|
+
for (let columnIndex = minColumnIndex; columnIndex <= maxColumnIndex; columnIndex++) {
|
|
33
|
+
const column = columns[columnIndex];
|
|
34
|
+
const columnKey = column.key;
|
|
35
|
+
|
|
36
|
+
if (!selectedColumns.has(columnKey))
|
|
37
|
+
continue;
|
|
38
|
+
|
|
39
|
+
if (selection.isKeySelected(rowKey, columnKey)) {
|
|
40
|
+
const cellData = formatResolver.resolve(row, column).text;
|
|
41
|
+
stringBuilder.push(cellData);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (columnIndex < maxColumnIndex)
|
|
45
|
+
stringBuilder.push('\t');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (rowIndex < maxRowIndex)
|
|
49
|
+
stringBuilder.push('\n');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return stringBuilder.join('');
|
|
53
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export default function getColumnIndex(columns, x) {
|
|
2
|
+
if (columns.length === 0)
|
|
3
|
+
return -1;
|
|
4
|
+
if (x < columns[0].leftWithBorder)
|
|
5
|
+
return -1;
|
|
6
|
+
if (x > columns[columns.length - 1].rightWithBorder)
|
|
7
|
+
return -1;
|
|
8
|
+
|
|
9
|
+
let iterA = 0;
|
|
10
|
+
let iterC = columns.length - 1;
|
|
11
|
+
|
|
12
|
+
while (iterA <= iterC) {
|
|
13
|
+
const iterB = Math.floor((iterA + iterC) / 2);
|
|
14
|
+
|
|
15
|
+
if (x < columns[iterB].leftWithBorder)
|
|
16
|
+
iterC = iterB - 1;
|
|
17
|
+
else if (x > columns[iterB].rightWithBorder)
|
|
18
|
+
iterA = iterB + 1;
|
|
19
|
+
|
|
20
|
+
else
|
|
21
|
+
return iterB;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return -1;
|
|
25
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import Selection from "../types/Selection.js";
|
|
2
|
+
|
|
3
|
+
export default function getCombinedCells(previousCells, newCells) {
|
|
4
|
+
const selection = new Selection(newCells);
|
|
5
|
+
return [...newCells, ...previousCells.filter(cell => !selection.isIdSelected(cell.rowId, cell.columnId))];
|
|
6
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export default function getDataFormatting(formatting, dataSelector, sortBy) {
|
|
2
|
+
return [
|
|
3
|
+
{
|
|
4
|
+
column: { match: 'DATA' },
|
|
5
|
+
row: { match: 'HEADER' },
|
|
6
|
+
value: ({ column }) => column.header === undefined ? column.id : column.header
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
column: { match: 'HEADER' },
|
|
10
|
+
row: { match: 'DATA' },
|
|
11
|
+
value: ({ row }) => row.header === undefined ? row.id : row.header
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
column: { match: 'HEADER' },
|
|
15
|
+
row: { match: 'SPECIAL' },
|
|
16
|
+
value: ''
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
column: { match: 'SPECIAL' },
|
|
20
|
+
row: { match: 'HEADER' },
|
|
21
|
+
value: ''
|
|
22
|
+
},
|
|
23
|
+
...sortBy.map(({ columnId, rowId, direction }, index) => ({
|
|
24
|
+
column: { id: columnId },
|
|
25
|
+
row: { id: rowId },
|
|
26
|
+
text: ({ column }) => `${sortBy.length > 1 ? index + 1 : ''}${direction === 'ASC' ? '⇣' : '⇡'} ${column.header}`
|
|
27
|
+
})),
|
|
28
|
+
{
|
|
29
|
+
column: { match: 'ANY' },
|
|
30
|
+
row: { match: 'FILTER' },
|
|
31
|
+
value: ({ newValue }) => newValue || '',
|
|
32
|
+
text: ({ newValue }) => newValue || 'Search...', // TODO: Move to render formatting?
|
|
33
|
+
edit: {
|
|
34
|
+
validate: () => true,
|
|
35
|
+
parse: ({ string }) => string,
|
|
36
|
+
autoCommit: true
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
column: { match: 'DATA' },
|
|
41
|
+
row: { match: 'DATA' },
|
|
42
|
+
value: dataSelector
|
|
43
|
+
},
|
|
44
|
+
...formatting
|
|
45
|
+
];
|
|
46
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import stringifyId from "../core-utils/stringifyId.js";
|
|
2
|
+
import getCellType from "./getCellType.js";
|
|
3
|
+
|
|
4
|
+
export default function getEditableCells(selectedCells, formatResolver, columnLookup, rowLookup) {
|
|
5
|
+
return selectedCells.map(cell => {
|
|
6
|
+
const columnKey = stringifyId(cell.columnId);
|
|
7
|
+
const rowKey = stringifyId(cell.rowId);
|
|
8
|
+
|
|
9
|
+
if (!columnLookup.has(columnKey))
|
|
10
|
+
return null;
|
|
11
|
+
if (!rowLookup.has(rowKey))
|
|
12
|
+
return null;
|
|
13
|
+
|
|
14
|
+
const column = columnLookup.get(columnKey);
|
|
15
|
+
const row = rowLookup.get(rowKey);
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
edit: formatResolver.resolve(row, column).edit,
|
|
19
|
+
cell: cell,
|
|
20
|
+
type: getCellType(column, row)
|
|
21
|
+
}
|
|
22
|
+
}).filter(cell => cell?.edit);
|
|
23
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import stringifyId from "../core-utils/stringifyId.js";
|
|
2
|
+
|
|
3
|
+
function getFilterLookup(filters, primaryKey, secondaryKey) {
|
|
4
|
+
const filterLookup = new Map();
|
|
5
|
+
for (const cell of filters) {
|
|
6
|
+
const primary = stringifyId(cell[primaryKey]);
|
|
7
|
+
const secondary = stringifyId(cell[secondaryKey]);
|
|
8
|
+
|
|
9
|
+
if (!filterLookup.has(primary))
|
|
10
|
+
filterLookup.set(primary, new Map());
|
|
11
|
+
|
|
12
|
+
filterLookup.get(primary).set(secondary, cell.expression);
|
|
13
|
+
}
|
|
14
|
+
return filterLookup;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getFilteredRows(filters, filteringRules, formattingRules, data, rows, columns, edition) {
|
|
18
|
+
if (filters.length === 0)
|
|
19
|
+
return rows;
|
|
20
|
+
|
|
21
|
+
const filterLookup = getFilterLookup(filters, 'columnId', 'rowId');
|
|
22
|
+
const filteredColumns = columns.filter(column => column.type !== 'FILTER' && filterLookup.has(column.key));
|
|
23
|
+
|
|
24
|
+
if (filteredColumns.length === 0)
|
|
25
|
+
return rows;
|
|
26
|
+
|
|
27
|
+
return rows.filter(row => {
|
|
28
|
+
for (const column of filteredColumns) {
|
|
29
|
+
const cell = formattingRules.resolve(data, rows, columns, row, column, edition);
|
|
30
|
+
const columnFilters = filterLookup.get(column.key);
|
|
31
|
+
const visible = filteringRules.resolve(data, rows, columns, row, column, cell.value, cell.text, columnFilters);
|
|
32
|
+
|
|
33
|
+
if (!visible)
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getFilteredColumns(filters, filteringRules, formattingRules, data, rows, columns, edition) {
|
|
41
|
+
if (filters.length === 0)
|
|
42
|
+
return columns;
|
|
43
|
+
|
|
44
|
+
const filterLookup = getFilterLookup(filters, 'rowId', 'columnId');
|
|
45
|
+
const filteredRows = rows.filter(row => row.type !== 'FILTER' && filterLookup.has(row.key));
|
|
46
|
+
|
|
47
|
+
if (filteredRows.length === 0)
|
|
48
|
+
return columns;
|
|
49
|
+
|
|
50
|
+
return columns.filter(column => {
|
|
51
|
+
for (const row of filteredRows) {
|
|
52
|
+
const cell = formattingRules.resolve(data, rows, columns, row, column, edition);
|
|
53
|
+
const rowFilters = filterLookup.get(row.key);
|
|
54
|
+
const visible = filteringRules.resolve(data, rows, columns, row, column, cell.value, cell.text, rowFilters);
|
|
55
|
+
|
|
56
|
+
if (!visible)
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import stringifyId from "../core-utils/stringifyId.js";
|
|
2
|
+
|
|
3
|
+
export default function getHighlightedCells(isMouseDown, isResizing, focusedCell, hoveredCell, columns, rows, columnLookup, rowLookup) {
|
|
4
|
+
if (!isMouseDown)
|
|
5
|
+
return [];
|
|
6
|
+
if (isResizing)
|
|
7
|
+
return [];
|
|
8
|
+
if (!hoveredCell)
|
|
9
|
+
return [];
|
|
10
|
+
if (!focusedCell)
|
|
11
|
+
return [];
|
|
12
|
+
|
|
13
|
+
const focusedColumnKey = stringifyId(focusedCell.columnId);
|
|
14
|
+
const focusedRowKey = stringifyId(focusedCell.rowId);
|
|
15
|
+
const hoveredColumnKey = stringifyId(hoveredCell.columnId);
|
|
16
|
+
const hoveredRowKey = stringifyId(hoveredCell.rowId);
|
|
17
|
+
|
|
18
|
+
if (!columnLookup.has(focusedColumnKey))
|
|
19
|
+
return [];
|
|
20
|
+
if (!rowLookup.has(focusedRowKey))
|
|
21
|
+
return [];
|
|
22
|
+
if (!columnLookup.has(hoveredColumnKey))
|
|
23
|
+
return [];
|
|
24
|
+
if (!rowLookup.has(hoveredRowKey))
|
|
25
|
+
return [];
|
|
26
|
+
|
|
27
|
+
const minColumnIndex = Math.min(columnLookup.get(focusedColumnKey).index, columnLookup.get(hoveredColumnKey).index);
|
|
28
|
+
const maxColumnIndex = Math.max(columnLookup.get(focusedColumnKey).index, columnLookup.get(hoveredColumnKey).index);
|
|
29
|
+
const minRowIndex = Math.min(rowLookup.get(focusedRowKey).index, rowLookup.get(hoveredRowKey).index);
|
|
30
|
+
const maxRowIndex = Math.max(rowLookup.get(focusedRowKey).index, rowLookup.get(hoveredRowKey).index);
|
|
31
|
+
|
|
32
|
+
return columns.slice(minColumnIndex, maxColumnIndex + 1).flatMap(column => {
|
|
33
|
+
return rows.slice(minRowIndex, maxRowIndex + 1).map(row => {
|
|
34
|
+
return {
|
|
35
|
+
rowId: row.id,
|
|
36
|
+
columnId: column.id
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import getColumnIndex from "./getColumnIndex.js";
|
|
2
|
+
import getRowIndex from "./getRowIndex.js";
|
|
3
|
+
|
|
4
|
+
export default function getHoveredCell(element, mousePosition, rows, columns, fixedSize, totalSize) {
|
|
5
|
+
// TODO: sometimes mousePosition is outside of bounds and it crashes the click events
|
|
6
|
+
|
|
7
|
+
if (!mousePosition)
|
|
8
|
+
return null;
|
|
9
|
+
if (mousePosition.x < 0 || mousePosition.y < 0 || mousePosition.x > totalSize.width || mousePosition.y > totalSize.height)
|
|
10
|
+
return null;
|
|
11
|
+
|
|
12
|
+
const scrollOffset = {
|
|
13
|
+
left: element.scrollLeft,
|
|
14
|
+
top: element.scrollTop
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const clientSize = {
|
|
18
|
+
width: element.clientWidth,
|
|
19
|
+
height: element.clientHeight
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const x = mousePosition.x <= fixedSize.left
|
|
23
|
+
? mousePosition.x
|
|
24
|
+
: mousePosition.x >= clientSize.width - fixedSize.right
|
|
25
|
+
? totalSize.width - clientSize.width + mousePosition.x
|
|
26
|
+
: mousePosition.x + scrollOffset.left;
|
|
27
|
+
const y = mousePosition.y <= fixedSize.top
|
|
28
|
+
? mousePosition.y
|
|
29
|
+
: mousePosition.y >= clientSize.height - fixedSize.bottom
|
|
30
|
+
? totalSize.height - clientSize.height + mousePosition.y
|
|
31
|
+
: mousePosition.y + scrollOffset.top;
|
|
32
|
+
|
|
33
|
+
const hoverRowIndex = getRowIndex(rows, y);
|
|
34
|
+
const hoverColumnIndex = getColumnIndex(columns, x);
|
|
35
|
+
|
|
36
|
+
if (hoverRowIndex === -1 || hoverColumnIndex === -1)
|
|
37
|
+
return null;
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
rowId: rows[hoverRowIndex].id,
|
|
41
|
+
columnId: columns[hoverColumnIndex].id
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import stringifyId from "../core-utils/stringifyId.js";
|
|
2
|
+
import getCellSection from "./getCellSection.js";
|
|
3
|
+
|
|
4
|
+
export default function getInputPlacement(editableCells, focusedCell, columnLookup, rowLookup, sections) {
|
|
5
|
+
if (!focusedCell)
|
|
6
|
+
return null;
|
|
7
|
+
|
|
8
|
+
const focusedColumnKey = stringifyId(focusedCell.columnId);
|
|
9
|
+
const focusedRowKey = stringifyId(focusedCell.rowId);
|
|
10
|
+
|
|
11
|
+
if (!columnLookup.has(focusedColumnKey))
|
|
12
|
+
return null;
|
|
13
|
+
if (!rowLookup.has(focusedRowKey))
|
|
14
|
+
return null;
|
|
15
|
+
|
|
16
|
+
const column = columnLookup.get(focusedColumnKey);
|
|
17
|
+
const row = rowLookup.get(focusedRowKey);
|
|
18
|
+
|
|
19
|
+
if (editableCells.length === 0)
|
|
20
|
+
return null;
|
|
21
|
+
|
|
22
|
+
const position = {
|
|
23
|
+
width: column.width,
|
|
24
|
+
height: row.height,
|
|
25
|
+
section: getCellSection(column, row)
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
switch (row.pinned) {
|
|
29
|
+
case "BEGIN":
|
|
30
|
+
position.top = row.top;
|
|
31
|
+
break;
|
|
32
|
+
case "END":
|
|
33
|
+
position.bottom = sections.top.height + sections.middle.height + sections.bottom.height - row.top - row.height;
|
|
34
|
+
break;
|
|
35
|
+
default:
|
|
36
|
+
position.marginTop = row.top - sections.top.height;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
switch (column.pinned) {
|
|
40
|
+
case "BEGIN":
|
|
41
|
+
position.left = column.left;
|
|
42
|
+
break;
|
|
43
|
+
case "END":
|
|
44
|
+
position.right = sections.left.width + sections.center.width + sections.right.width - column.left - column.width;
|
|
45
|
+
break;
|
|
46
|
+
default:
|
|
47
|
+
position.marginLeft = column.left - sections.left.width;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return position;
|
|
51
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { defaultPadding } from "../core-utils/defaults.js";
|
|
2
|
+
|
|
3
|
+
// TODO: add expand and expand-data
|
|
4
|
+
|
|
5
|
+
export function getMeasuredColumns(columns, rows, textResolver, formatResolver, measuringCache, presentKeys) {
|
|
6
|
+
if (columns.every(column => typeof column.width === 'number'))
|
|
7
|
+
return columns;
|
|
8
|
+
|
|
9
|
+
const measureColumn = column => {
|
|
10
|
+
const requestedWidth = 'width' in column ? column.width : 'fit-once';
|
|
11
|
+
|
|
12
|
+
if (typeof requestedWidth === 'number')
|
|
13
|
+
return requestedWidth;
|
|
14
|
+
|
|
15
|
+
if (measuringCache.has(column.key)) {
|
|
16
|
+
const cached = measuringCache.get(column.key);
|
|
17
|
+
|
|
18
|
+
if (requestedWidth === 'fit-once' && !cached.dataOnly)
|
|
19
|
+
return cached.width;
|
|
20
|
+
if (requestedWidth === 'fit-data-once' && cached.dataOnly)
|
|
21
|
+
return cached.width;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let width = 0;
|
|
25
|
+
for (const row of rows) {
|
|
26
|
+
if (row.type !== 'DATA' && requestedWidth === 'fit-data-once')
|
|
27
|
+
continue;
|
|
28
|
+
if (row.type !== 'DATA' && requestedWidth === 'fit-data')
|
|
29
|
+
continue;
|
|
30
|
+
|
|
31
|
+
const cell = formatResolver.resolve(row, column);
|
|
32
|
+
const text = cell.text;
|
|
33
|
+
const font = cell.font;
|
|
34
|
+
const padding = cell.padding.left + cell.padding.right;
|
|
35
|
+
|
|
36
|
+
const cellWidth = textResolver.measureWidth(text, font) + padding;
|
|
37
|
+
|
|
38
|
+
width = Math.max(width, cellWidth);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
measuringCache.set(column.key, {
|
|
42
|
+
width,
|
|
43
|
+
dataOnly: requestedWidth === 'fit-data-once'
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return width;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const key of measuringCache.keys()) {
|
|
50
|
+
if (!presentKeys.has(key))
|
|
51
|
+
measuringCache.delete(key);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return columns.map(column => ({
|
|
55
|
+
...column,
|
|
56
|
+
width: measureColumn(column)
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function getMeasuredRows(columns, rows, textResolver, formatResolver, measuringCache, presentKeys) {
|
|
61
|
+
if (rows.every(row => typeof row.height === 'number'))
|
|
62
|
+
return rows;
|
|
63
|
+
|
|
64
|
+
const measureRow = row => {
|
|
65
|
+
const requestedHeight = 'height' in row ? row.height : 'fit-once';
|
|
66
|
+
|
|
67
|
+
if (typeof requestedHeight === 'number')
|
|
68
|
+
return requestedHeight;
|
|
69
|
+
|
|
70
|
+
if (measuringCache.has(row.key)) {
|
|
71
|
+
const cached = measuringCache.get(row.key);
|
|
72
|
+
|
|
73
|
+
if (requestedHeight === 'fit-once' && !cached.dataOnly)
|
|
74
|
+
return cached.height;
|
|
75
|
+
if (requestedHeight === 'fit-data-once' && cached.dataOnly)
|
|
76
|
+
return cached.height;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let height = 0;
|
|
80
|
+
for (const column of columns) {
|
|
81
|
+
if (column.type !== 'DATA' && requestedHeight === 'fit-data-once')
|
|
82
|
+
continue;
|
|
83
|
+
if (column.type !== 'DATA' && requestedHeight === 'fit-data')
|
|
84
|
+
continue;
|
|
85
|
+
|
|
86
|
+
const cell = formatResolver.resolve(row, column);
|
|
87
|
+
const text = cell.text;
|
|
88
|
+
const font = cell.font;
|
|
89
|
+
const padding = cell.padding.top + cell.padding.bottom;
|
|
90
|
+
|
|
91
|
+
const cellHeight = textResolver.measureHeight(text, font) + padding;
|
|
92
|
+
|
|
93
|
+
height = Math.max(height, cellHeight);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
measuringCache.set(row.key, {
|
|
97
|
+
height,
|
|
98
|
+
dataOnly: requestedHeight === 'fit-data-once'
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return height;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
for (const key of measuringCache.keys()) {
|
|
105
|
+
if (!presentKeys.has(key))
|
|
106
|
+
measuringCache.delete(key);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return rows.map(row => ({
|
|
110
|
+
...row,
|
|
111
|
+
height: measureRow(row)
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import stringifyId from '../core-utils/stringifyId.js';
|
|
2
|
+
|
|
3
|
+
export default function getNewSortBy(sortBy, column, row, ctrlKey) {
|
|
4
|
+
// TODO: better support for multi-row sorting
|
|
5
|
+
|
|
6
|
+
function isCurrentRule(rule) {
|
|
7
|
+
return column.key === stringifyId(rule.columnId || 'HEADER') && row.key === stringifyId(rule.rowId || 'HEADER');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const directionLoop = ['ASC', 'DESC', undefined];
|
|
11
|
+
const currentRule = sortBy.find(isCurrentRule);
|
|
12
|
+
const directionIndex = directionLoop.indexOf(currentRule?.direction);
|
|
13
|
+
const newDirection = directionLoop[(directionIndex + 1) % directionLoop.length];
|
|
14
|
+
const isLastRule = sortBy.indexOf(currentRule) === sortBy.length - 1;
|
|
15
|
+
const shouldKeepOld = ctrlKey && (isLastRule || !currentRule);
|
|
16
|
+
const rulesToKeep = shouldKeepOld ? sortBy.filter(rule => !isCurrentRule(rule)) : [];
|
|
17
|
+
const newRules = newDirection ? [{ columnId: column.id, rowId: row.id, direction: newDirection }] : [];
|
|
18
|
+
|
|
19
|
+
return [...rulesToKeep, ...newRules];
|
|
20
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import roundToPixels from "../core-utils/roundToPixels.js";
|
|
2
|
+
|
|
3
|
+
export function getPlacedColumns(columns, devicePixelRatio, borderWidth) {
|
|
4
|
+
let left = borderWidth;
|
|
5
|
+
|
|
6
|
+
return columns.map((column, index) => {
|
|
7
|
+
const assignedWidth = 'width' in column ? column.width : 100;
|
|
8
|
+
const width = roundToPixels(assignedWidth, devicePixelRatio);
|
|
9
|
+
const newColumn = {
|
|
10
|
+
...column,
|
|
11
|
+
index: index,
|
|
12
|
+
width: width,
|
|
13
|
+
leftWithBorder: left - borderWidth,
|
|
14
|
+
left: left,
|
|
15
|
+
right: left + width,
|
|
16
|
+
rightWithBorder: left + width + borderWidth
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
left += newColumn.width + borderWidth;
|
|
20
|
+
|
|
21
|
+
return newColumn;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getPlacedRows(rows, devicePixelRatio, borderWidth) {
|
|
26
|
+
let top = borderWidth;
|
|
27
|
+
|
|
28
|
+
return rows.map((row, index) => {
|
|
29
|
+
const assignedHeight = 'height' in row ? row.height : 20;
|
|
30
|
+
const height = roundToPixels(assignedHeight, devicePixelRatio);
|
|
31
|
+
const newRow = {
|
|
32
|
+
...row,
|
|
33
|
+
index: index,
|
|
34
|
+
height: height,
|
|
35
|
+
topWithBorder: top - borderWidth,
|
|
36
|
+
top: top,
|
|
37
|
+
bottom: top + height,
|
|
38
|
+
bottomWithBorder: top + height + borderWidth
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
top += newRow.height + borderWidth;
|
|
42
|
+
|
|
43
|
+
return newRow;
|
|
44
|
+
});
|
|
45
|
+
}
|