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.
Files changed (67) hide show
  1. package/package.json +26 -0
  2. package/src/core/render.js +400 -0
  3. package/src/core/state.js +183 -0
  4. package/src/core-utils/defaults.js +4 -0
  5. package/src/core-utils/rect.js +49 -0
  6. package/src/core-utils/rect.test.js +111 -0
  7. package/src/core-utils/roundToPixels.js +3 -0
  8. package/src/core-utils/stringifyId.js +27 -0
  9. package/src/core-utils/stringifyId.test.js +27 -0
  10. package/src/index.js +595 -0
  11. package/src/state-utils/getActive.js +17 -0
  12. package/src/state-utils/getCellSection.js +22 -0
  13. package/src/state-utils/getCellType.js +7 -0
  14. package/src/state-utils/getClipboardData.js +53 -0
  15. package/src/state-utils/getColumnIndex.js +25 -0
  16. package/src/state-utils/getCombinedCells.js +6 -0
  17. package/src/state-utils/getDataFormatting.js +46 -0
  18. package/src/state-utils/getEditableCells.js +23 -0
  19. package/src/state-utils/getEditedCellsAndFilters.js +4 -0
  20. package/src/state-utils/getEdition.js +6 -0
  21. package/src/state-utils/getFilterFormatting.js +3 -0
  22. package/src/state-utils/getFiltered.js +61 -0
  23. package/src/state-utils/getFilteringRules.js +5 -0
  24. package/src/state-utils/getFixedSize.js +8 -0
  25. package/src/state-utils/getFormatResolver.js +5 -0
  26. package/src/state-utils/getFormattingRules.js +5 -0
  27. package/src/state-utils/getHighlightedCells.js +40 -0
  28. package/src/state-utils/getHoveredCell.js +43 -0
  29. package/src/state-utils/getInputFormatting.js +3 -0
  30. package/src/state-utils/getInputPlacement.js +51 -0
  31. package/src/state-utils/getInvoked.js +5 -0
  32. package/src/state-utils/getIsTextValid.js +3 -0
  33. package/src/state-utils/getKeys.js +3 -0
  34. package/src/state-utils/getLookup.js +3 -0
  35. package/src/state-utils/getMeasureFormatting.js +3 -0
  36. package/src/state-utils/getMeasured.js +113 -0
  37. package/src/state-utils/getMousePosition.js +8 -0
  38. package/src/state-utils/getNewSortBy.js +20 -0
  39. package/src/state-utils/getPinned.js +8 -0
  40. package/src/state-utils/getPlaced.js +45 -0
  41. package/src/state-utils/getReducedCells.js +6 -0
  42. package/src/state-utils/getRenderFormatting.js +122 -0
  43. package/src/state-utils/getResizable.js +49 -0
  44. package/src/state-utils/getResolved.js +42 -0
  45. package/src/state-utils/getResolvedFilters.js +7 -0
  46. package/src/state-utils/getResolvedSortBy.js +7 -0
  47. package/src/state-utils/getRowIndex.js +25 -0
  48. package/src/state-utils/getScrollRect.js +41 -0
  49. package/src/state-utils/getSections.js +101 -0
  50. package/src/state-utils/getSelection.js +5 -0
  51. package/src/state-utils/getSorted.js +130 -0
  52. package/src/state-utils/getSortingFormatting.js +3 -0
  53. package/src/state-utils/getSortingRules.js +5 -0
  54. package/src/state-utils/getTextResolver.js +5 -0
  55. package/src/state-utils/getToggledValue.js +11 -0
  56. package/src/state-utils/getTotalSize.js +6 -0
  57. package/src/state-utils/getUnfolded.js +86 -0
  58. package/src/types/Edition.js +37 -0
  59. package/src/types/FilteringRules.js +48 -0
  60. package/src/types/FormatResolver.js +19 -0
  61. package/src/types/FormattingRules.js +120 -0
  62. package/src/types/FormattingRules.test.js +90 -0
  63. package/src/types/RulesLookup.js +118 -0
  64. package/src/types/Selection.js +25 -0
  65. package/src/types/SortingRules.js +62 -0
  66. package/src/types/TextResolver.js +60 -0
  67. package/src/types/VisibilityResolver.js +61 -0
@@ -0,0 +1,122 @@
1
+ import stringifyId from '../core-utils/stringifyId.js';
2
+
3
+ function getHighlightColor(baseColor, isStrong) {
4
+ if (isStrong)
5
+ return baseColor + '99';
6
+ else
7
+ return baseColor + '33';
8
+ }
9
+
10
+ export default function getRenderFormatting(formatting, hoveredCell, focusedCell, selection, highlight, edition, resizableColumn, resizableRow) {
11
+ const focusedColumnKey = focusedCell ? stringifyId(focusedCell.columnId) : null;
12
+ const focusedRowKey = focusedCell ? stringifyId(focusedCell.rowId) : null;
13
+ const isResizingColumn = resizableColumn !== null;
14
+ const isResizingRow = resizableRow !== null;
15
+ const isResizing = isResizingColumn || isResizingRow;
16
+
17
+ const isSelected = (rows, columns, rowIndex, columnIndex) => {
18
+ if (rowIndex < 0 || rowIndex >= rows.length)
19
+ return false;
20
+ if (columnIndex < 0 || columnIndex >= columns.length)
21
+ return false;
22
+
23
+ const rowKey = rows[rowIndex].key;
24
+ const columnKey = columns[columnIndex].key;
25
+
26
+ return selection.isKeySelected(rowKey, columnKey);
27
+ };
28
+
29
+ const optional = (condition, style) => {
30
+ if (condition)
31
+ return [style];
32
+ else
33
+ return [];
34
+ }
35
+
36
+ return [
37
+ {
38
+ column: { match: 'ANY' },
39
+ row: { match: 'FILTER' },
40
+ style: ({ newValue }) => ({ background: '#FBFBFB', foreground: newValue ? 'black' : '#cccccc', border: { width: 1, color: 'gray' } })
41
+ },
42
+ {
43
+ column: { match: 'ANY' },
44
+ row: { match: 'HEADER' },
45
+ style: { background: '#F5F5F5', border: { width: 1, color: 'gray' } }
46
+ },
47
+ {
48
+ column: { match: 'HEADER' },
49
+ row: { match: 'ANY' },
50
+ style: { background: '#F5F5F5', border: { width: 1, color: 'gray' } }
51
+ },
52
+ {
53
+ column: { match: 'HEADER' },
54
+ row: { match: 'HEADER' },
55
+ style: { background: '#E0E0E0' }
56
+ },
57
+ ...formatting,
58
+ // TODO: optionally re-enable
59
+ // ...optional(hoveredCell && !isResizingColumn, {
60
+ // column: { id: hoveredCell?.columnId },
61
+ // row: { match: 'ANY' },
62
+ // style: { highlight: '#81948133' },
63
+ // }),
64
+ ...optional(hoveredCell && !isResizingRow, {
65
+ column: { match: 'ANY' },
66
+ row: { id: hoveredCell?.rowId },
67
+ style: { highlight: '#81948133' },
68
+ }),
69
+ ...optional(hoveredCell && !isResizing, {
70
+ column: { id: hoveredCell?.columnId },
71
+ row: { id: hoveredCell?.rowId },
72
+ style: { highlight: '#81948188' },
73
+ }),
74
+ {
75
+ column: { match: 'ANY' },
76
+ row: { match: 'ANY' },
77
+ condition: ({ rows, columns, row, column }) => isSelected(rows, columns, row.index, column.index),
78
+ style: ({ rows, columns, row, column, edit }) => ({
79
+ ...(!isSelected(rows, columns, row.index - 1, column.index) ? { borderTop: { width: 3, color: '#596959', index: Number.MAX_SAFE_INTEGER } } : {}),
80
+ ...(!isSelected(rows, columns, row.index + 1, column.index) ? { borderBottom: { width: 3, color: '#596959', index: Number.MAX_SAFE_INTEGER } } : {}),
81
+ ...(!isSelected(rows, columns, row.index, column.index - 1) ? { borderLeft: { width: 3, color: '#596959', index: Number.MAX_SAFE_INTEGER } } : {}),
82
+ ...(!isSelected(rows, columns, row.index, column.index + 1) ? { borderRight: { width: 3, color: '#596959', index: Number.MAX_SAFE_INTEGER } } : {}),
83
+ highlight: getHighlightColor(edit ? '#798d9c' : '#819481', focusedColumnKey !== column.key || focusedRowKey !== row.key)
84
+ }),
85
+ },
86
+ {
87
+ column: { match: 'ANY' },
88
+ row: { match: 'ANY' },
89
+ condition: ({ row, column }) => highlight.isKeySelected(row.key, column.key),
90
+ style: ({ row, column }) => ({
91
+ highlight: getHighlightColor('#93a8b8', focusedColumnKey !== column.key || focusedRowKey !== row.key)
92
+ })
93
+ },
94
+ ...optional(focusedCell, {
95
+ column: { id: focusedCell?.columnId },
96
+ row: { id: focusedCell?.rowId },
97
+ style: { highlight: '#ffffffaa' }
98
+ }),
99
+ {
100
+ column: { match: 'ANY' },
101
+ row: { match: 'ANY' },
102
+ condition: ({ edit }) => edit,
103
+ style: { corner: '#77777720' },
104
+ },
105
+ {
106
+ column: { match: 'ANY' },
107
+ row: { match: 'ANY' },
108
+ condition: ({ row, column }) => edition.hasValueByKey(row.key, column.key),
109
+ style: { corner: 'darkgreen' },
110
+ },
111
+ ...optional(resizableColumn, {
112
+ column: { id: resizableColumn },
113
+ row: { match: 'HEADER' },
114
+ style: { borderRight: { width: 5, color: 'cornflowerblue' } }
115
+ }),
116
+ ...optional(resizableRow, {
117
+ column: { match: 'HEADER' },
118
+ row: { id: resizableRow },
119
+ style: { borderBottom: { width: 5, color: 'cornflowerblue' } }
120
+ }),
121
+ ];
122
+ }
@@ -0,0 +1,49 @@
1
+ import stringifyId from "../core-utils/stringifyId.js";
2
+
3
+ const grabOffset = 5;
4
+
5
+ // TODO: not working when scrolled
6
+
7
+ export function getResizableColumn(columns, columnLookup, rowLookup, hoveredCell, mousePosition) {
8
+ if (!hoveredCell)
9
+ return null;
10
+
11
+ const column = columnLookup.get(stringifyId(hoveredCell.columnId));
12
+ const row = rowLookup.get(stringifyId(hoveredCell.rowId));
13
+ const x = mousePosition.x;
14
+
15
+ if (row.type !== 'HEADER')
16
+ return null;
17
+
18
+ if (x >= column.right - grabOffset && x <= column.right + grabOffset)
19
+ return column.id;
20
+
21
+ if (column.index === 0)
22
+ return null;
23
+ if (x < column.left - grabOffset || x > column.left + grabOffset)
24
+ return null;
25
+
26
+ return columns[column.index - 1].id;
27
+ }
28
+
29
+ export function getResizableRow(rows, columnLookup, rowLookup, hoveredCell, mousePosition) {
30
+ if (!hoveredCell)
31
+ return null;
32
+
33
+ const column = columnLookup.get(stringifyId(hoveredCell.columnId));
34
+ const row = rowLookup.get(stringifyId(hoveredCell.rowId));
35
+ const y = mousePosition.y;
36
+
37
+ if (column.type !== 'HEADER')
38
+ return null;
39
+
40
+ if (y >= row.bottom - grabOffset && y <= row.bottom + grabOffset)
41
+ return row.id;
42
+
43
+ if (row.index === 0)
44
+ return null;
45
+ if (y < row.top - grabOffset || y > row.top + grabOffset)
46
+ return null;
47
+
48
+ return rows[row.index - 1].id;
49
+ }
@@ -0,0 +1,42 @@
1
+ import stringifyId from "../core-utils/stringifyId.js";
2
+ import getPinned from "./getPinned.js";
3
+
4
+ function getResolved(elements, pinnedBegin, pinnedEnd) {
5
+ return elements.map((element, index) => {
6
+ const id = 'id' in element ? element.id : element.type;
7
+ return {
8
+ ...element,
9
+ id: id,
10
+ type: element.type || "DATA",
11
+ index: index,
12
+ key: stringifyId(id),
13
+ pinned: getPinned(index, elements.length, pinnedBegin, pinnedEnd),
14
+ header: 'header' in element ? element.header : id,
15
+ labels: element.labels || []
16
+ };
17
+ });
18
+ }
19
+
20
+ export function getResolvedColumns(columns, pinnedBegin, pinnedEnd, columnWidths) {
21
+ const widthsLookup = new Map(columnWidths.map(({ columnId, width }) => [stringifyId(columnId), width]));
22
+ const resolvedColumns = getResolved(columns, pinnedBegin, pinnedEnd);
23
+
24
+ return resolvedColumns.map(column => ({
25
+ ...column,
26
+ width: widthsLookup.has(column.key)
27
+ ? widthsLookup.get(column.key)
28
+ : column.width
29
+ }));
30
+ }
31
+
32
+ export function getResolvedRows(rows, pinnedBegin, pinnedEnd, rowHeights) {
33
+ const heightsLookup = new Map(rowHeights.map(({ rowId, height }) => [stringifyId(rowId), height]));
34
+ const resolvedRows = getResolved(rows, pinnedBegin, pinnedEnd);
35
+
36
+ return resolvedRows.map(row => ({
37
+ ...row,
38
+ height: heightsLookup.has(row.key)
39
+ ? heightsLookup.get(row.key)
40
+ : row.height
41
+ }));
42
+ }
@@ -0,0 +1,7 @@
1
+ export default function getResolvedFilters(filters) {
2
+ return filters.map(filter => ({
3
+ columnId: 'columnId' in filter ? filter.columnId : 'FILTER',
4
+ rowId: 'rowId' in filter ? filter.rowId : 'FILTER',
5
+ expression: filter.expression
6
+ }));
7
+ }
@@ -0,0 +1,7 @@
1
+ export default function getResolvedSortBy(sortBy) {
2
+ return sortBy.map(sort => ({
3
+ columnId: 'columnId' in sort ? sort.columnId : 'HEADER',
4
+ rowId: 'rowId' in sort ? sort.rowId : 'HEADER',
5
+ direction: sort.direction
6
+ }));
7
+ }
@@ -0,0 +1,25 @@
1
+ export default function getRowIndex(rows, y) {
2
+ if (rows.length === 0)
3
+ return -1;
4
+ if (y < rows[0].topWithBorder)
5
+ return -1;
6
+ if (y > rows[rows.length - 1].bottomWithBorder)
7
+ return -1;
8
+
9
+ let iterA = 0;
10
+ let iterC = rows.length - 1;
11
+
12
+ while (iterA <= iterC) {
13
+ const iterB = Math.floor((iterA + iterC) / 2);
14
+
15
+ if (y < rows[iterB].topWithBorder)
16
+ iterC = iterB - 1;
17
+ else if (y > rows[iterB].bottomWithBorder)
18
+ iterA = iterB + 1;
19
+
20
+ else
21
+ return iterB;
22
+ }
23
+
24
+ return -1;
25
+ }
@@ -0,0 +1,41 @@
1
+ import { area, clip, contains, expand, subtract } from "../core-utils/rect.js";
2
+
3
+ const requiredMargin = 200;
4
+ const preloadedMargin = 400;
5
+ const emptyRect = {
6
+ left: 0,
7
+ top: 0,
8
+ width: 0,
9
+ height: 0
10
+ };
11
+
12
+ export default function getScrollRect(previous, totalSize, fixedSize, element) {
13
+ // TODO: Is it optimal to use getBoundingClientRect()?
14
+ const size = {
15
+ width: element.getBoundingClientRect().width,
16
+ height: element.getBoundingClientRect().height
17
+ };
18
+ const scrollOffset = {
19
+ left: element.scrollLeft,
20
+ top: element.scrollTop
21
+ };
22
+
23
+ const prevScrollRect = previous || emptyRect;
24
+
25
+ const totalRect = { left: 0, top: 0, ...totalSize };
26
+ const bounds = subtract(totalRect, fixedSize);
27
+ const scrollRect = subtract({ ...scrollOffset, ...size }, fixedSize);
28
+ const requiredScrollRect = clip(bounds, expand(scrollRect, requiredMargin));
29
+ const preloadedScrollRect = clip(bounds, expand(scrollRect, preloadedMargin));
30
+
31
+ if (!contains(bounds, prevScrollRect))
32
+ return preloadedScrollRect;
33
+
34
+ if (!contains(prevScrollRect, requiredScrollRect))
35
+ return preloadedScrollRect;
36
+
37
+ if (area(prevScrollRect) > 2 * area(preloadedScrollRect))
38
+ return preloadedScrollRect;
39
+
40
+ return prevScrollRect;
41
+ }
@@ -0,0 +1,101 @@
1
+ export default function getSections(columns, rows) {
2
+ const topLength = rows.filter(row => row.pinned === 'BEGIN').length; // TODO: optimize
3
+ const bottomLength = rows.filter(row => row.pinned === 'END').length;
4
+ const middleLength = rows.length - topLength - bottomLength;
5
+ const leftLength = columns.filter(column => column.pinned === 'BEGIN').length;
6
+ const rightLength = columns.filter(column => column.pinned === 'END').length;
7
+ const centerLength = columns.length - leftLength - rightLength;
8
+
9
+ const topRows = rows.slice(0, topLength);
10
+ const bottomRows = rows.slice(rows.length - bottomLength, rows.length);
11
+ const middleRows = rows.slice(topLength, rows.length - bottomLength);
12
+ const leftColumns = columns.slice(0, leftLength);
13
+ const rightColumns = columns.slice(columns.length - rightLength, columns.length);
14
+ const centerColumns = columns.slice(leftLength, columns.length - rightLength);
15
+
16
+ const hasTopRows = topLength > 0;
17
+ const hasBottomRows = bottomLength > 0;
18
+ const hasMiddleRows = middleLength > 0;
19
+ const hasLeftColumns = leftLength > 0;
20
+ const hasRightColumns = rightLength > 0;
21
+ const hasCenterColumns = centerLength > 0;
22
+
23
+ const topShowTopBorder = true;
24
+ const topShowBottomBorder = true;
25
+ const bottomShowTopBorder = hasMiddleRows || !hasTopRows;
26
+ const bottomShowBottomBorder = true;
27
+ const middleShowTopBorder = !hasTopRows;
28
+ const middleShowBottomBorder = !hasBottomRows;
29
+ const leftShowLeftBorder = true;
30
+ const leftShowRightBorder = true;
31
+ const rightShowLeftBorder = hasCenterColumns || !hasLeftColumns;
32
+ const rightShowRightBorder = true;
33
+ const centerShowLeftBorder = !hasLeftColumns;
34
+ const centerShowRightBorder = !hasRightColumns;
35
+
36
+ const getHeight = (rows, showTopBorder, showBottomBorder) => {
37
+ if (rows.length === 0)
38
+ return 0;
39
+
40
+ const top = showTopBorder ? rows.at(0).topWithBorder : rows.at(0).top;
41
+ const bottom = showBottomBorder ? rows.at(-1).bottomWithBorder : rows.at(-1).bottom;
42
+
43
+ return bottom - top;
44
+ }
45
+
46
+ const getWidth = (columns, showLeftBorder, showRightBorder) => {
47
+ if (columns.length === 0)
48
+ return 0;
49
+
50
+ const left = showLeftBorder ? columns.at(0).leftWithBorder : columns.at(0).left;
51
+ const right = showRightBorder ? columns.at(-1).rightWithBorder : columns.at(-1).right;
52
+
53
+ return right - left;
54
+ }
55
+
56
+ const topHeight = getHeight(topRows, topShowTopBorder, topShowBottomBorder);
57
+ const bottomHeight = getHeight(bottomRows, bottomShowTopBorder, bottomShowBottomBorder);
58
+ const middleHeight = getHeight(middleRows, middleShowTopBorder, middleShowBottomBorder);
59
+ const leftWidth = getWidth(leftColumns, leftShowLeftBorder, leftShowRightBorder);
60
+ const rightWidth = getWidth(rightColumns, rightShowLeftBorder, rightShowRightBorder);
61
+ const centerWidth = getWidth(centerColumns, centerShowLeftBorder, centerShowRightBorder);
62
+
63
+ return {
64
+ top: {
65
+ rows: topRows,
66
+ showTopBorder: topShowTopBorder,
67
+ showBottomBorder: topShowBottomBorder,
68
+ height: topHeight
69
+ },
70
+ bottom: {
71
+ rows: bottomRows,
72
+ showTopBorder: bottomShowTopBorder,
73
+ showBottomBorder: bottomShowBottomBorder,
74
+ height: bottomHeight
75
+ },
76
+ middle: {
77
+ rows: middleRows,
78
+ showTopBorder: middleShowTopBorder,
79
+ showBottomBorder: middleShowBottomBorder,
80
+ height: middleHeight
81
+ },
82
+ left: {
83
+ columns: leftColumns,
84
+ showLeftBorder: leftShowLeftBorder,
85
+ showRightBorder: leftShowRightBorder,
86
+ width: leftWidth
87
+ },
88
+ right: {
89
+ columns: rightColumns,
90
+ showLeftBorder: rightShowLeftBorder,
91
+ showRightBorder: rightShowRightBorder,
92
+ width: rightWidth
93
+ },
94
+ center: {
95
+ columns: centerColumns,
96
+ showLeftBorder: centerShowLeftBorder,
97
+ showRightBorder: centerShowRightBorder,
98
+ width: centerWidth
99
+ }
100
+ };
101
+ }
@@ -0,0 +1,5 @@
1
+ import Selection from "../types/Selection.js";
2
+
3
+ export default function getSelection(selectedCells) {
4
+ return new Selection(selectedCells);
5
+ }
@@ -0,0 +1,130 @@
1
+ import stringifyId from "../core-utils/stringifyId.js";
2
+
3
+ function getSortByLookup(sortBy, primaryKey, secondaryKey) {
4
+ const sortByLookup = new Map();
5
+ for (const cell of sortBy) {
6
+ const primary = stringifyId(cell[primaryKey]);
7
+ const secondary = stringifyId(cell[secondaryKey]);
8
+
9
+ if (!sortByLookup.has(primary))
10
+ sortByLookup.set(primary, new Map());
11
+
12
+ sortByLookup.get(primary).set(secondary, cell.direction);
13
+ }
14
+ return sortByLookup;
15
+ }
16
+
17
+ function flush(temp, result) {
18
+ temp.sort((lhs, rhs) => {
19
+ const result = lhs.comparator(lhs.cell, rhs.cell);
20
+ if (typeof result === 'number')
21
+ return result;
22
+ return result ? -1 : 1;
23
+ });
24
+ result.push(...temp.map(entry => entry.entity));
25
+ temp.length = 0;
26
+ }
27
+
28
+ export function getSortedRows(sortBy, sortingRules, formattingRules, data, rows, columns, edition) {
29
+ if (sortBy.length === 0)
30
+ return rows;
31
+
32
+ const sortByLookup = getSortByLookup(sortBy, 'columnId', 'rowId');
33
+ const columnLookup = new Map(columns.map(column => [column.key, column]));
34
+ const sortedColumns = sortBy
35
+ .map(cell => stringifyId(cell.columnId))
36
+ .filter(key => columnLookup.has(key))
37
+ .map(key => columnLookup.get(key))
38
+ .reverse();
39
+
40
+ if (sortedColumns.length === 0)
41
+ return rows;
42
+
43
+ for (const column of sortedColumns) {
44
+ const result = [];
45
+ const temp = [];
46
+
47
+ for (const row of rows) {
48
+ const comparator = sortingRules.resolve(column, row, sortByLookup.get(column.key));
49
+
50
+ if (!comparator) {
51
+ flush(temp, result);
52
+ result.push(row);
53
+ continue;
54
+ }
55
+
56
+ const cell = formattingRules.resolve(data, rows, columns, row, column, edition);
57
+ const entry = { entity: row, comparator, cell };
58
+
59
+ if (temp.length === 0) {
60
+ temp.push(entry);
61
+ continue;
62
+ }
63
+
64
+ if (temp[0].comparator === comparator) {
65
+ temp.push(entry);
66
+ continue;
67
+ }
68
+
69
+ flush(temp, result);
70
+ temp.push(entry);
71
+ }
72
+
73
+ flush(temp, result);
74
+ rows = result;
75
+ }
76
+
77
+ return rows;
78
+ }
79
+
80
+ export function getSortedColumns(sortBy, sortingRules, formattingRules, data, rows, columns, edition) {
81
+ if (sortBy.length === 0)
82
+ return columns;
83
+
84
+ const sortByLookup = getSortByLookup(sortBy, 'rowId', 'columnId');
85
+ const rowLookup = new Map(rows.map(row => [row.key, row]));
86
+ const sortedRows = sortBy
87
+ .map(cell => stringifyId(cell.rowId))
88
+ .filter(key => rowLookup.has(key))
89
+ .map(key => rowLookup.get(key))
90
+ .reverse();
91
+
92
+ if (sortedRows.length === 0)
93
+ return columns;
94
+
95
+ for (const row of sortedRows) {
96
+ const result = [];
97
+ const temp = [];
98
+
99
+ for (const column of columns) {
100
+ const comparator = sortingRules.resolve(column, row, sortByLookup.get(row.key));
101
+
102
+ if (!comparator) {
103
+ flush(temp, result);
104
+ result.push(column);
105
+ continue;
106
+ }
107
+
108
+ const cell = formattingRules.resolve(data, rows, columns, row, column, edition);
109
+ const entry = { entity: column, comparator, cell };
110
+
111
+ if (temp.length === 0) {
112
+ temp.push(entry);
113
+ continue;
114
+ }
115
+
116
+ if (temp[0].comparator === comparator) {
117
+ temp.push(entry);
118
+ continue;
119
+ }
120
+
121
+ flush(temp, result);
122
+ temp.push(entry);
123
+ }
124
+
125
+ flush(temp, result);
126
+ columns = result;
127
+ }
128
+
129
+ return columns;
130
+ }
@@ -0,0 +1,3 @@
1
+ export default function getSortingFormatting(formatting) {
2
+ return formatting;
3
+ }
@@ -0,0 +1,5 @@
1
+ import SortingRules from "../types/SortingRules.js";
2
+
3
+ export default function getSortingRules(sorting) {
4
+ return new SortingRules(sorting);
5
+ }
@@ -0,0 +1,5 @@
1
+ import TextResolver from "../types/TextResolver.js";
2
+
3
+ export default function getTextResolver() {
4
+ return new TextResolver();
5
+ }
@@ -0,0 +1,11 @@
1
+ export default function getToggledValue(cell, column, row, edition) {
2
+ const value = edition.hasValueByKey(row.key, column.key)
3
+ ? edition.getValueByKey(row.key, column.key)
4
+ : cell.value;
5
+ const toggle = cell.edit.toggle;
6
+
7
+ if (typeof toggle === 'function')
8
+ return toggle({value}); // TODO: expand by context
9
+
10
+ return toggle[(toggle.indexOf(value) + 1) % toggle.length];
11
+ }
@@ -0,0 +1,6 @@
1
+ export default function getTotalSize(columns, rows) {
2
+ return {
3
+ width: columns.length ? columns.at(-1).rightWithBorder : 0,
4
+ height: rows.length ? rows.at(-1).bottomWithBorder : 0
5
+ };
6
+ }
@@ -0,0 +1,86 @@
1
+ function getDefaultIds(elements) {
2
+ if (!Array.isArray(elements))
3
+ return Object.keys(elements);
4
+
5
+ return elements.map((_, index) => index);
6
+ }
7
+
8
+ function getDefaultRowIds(data) {
9
+ return getDefaultIds(data);
10
+ }
11
+
12
+ function getDefaultColumnIds(data) {
13
+ const keys = new Set();
14
+
15
+ if (Array.isArray(data)) {
16
+ for (const element of data) {
17
+ for (const id of getDefaultIds(element))
18
+ keys.add(id);
19
+ }
20
+ } else {
21
+ for (const key in data) {
22
+ for (const id of getDefaultIds(data[key]))
23
+ keys.add(id);
24
+ }
25
+ }
26
+
27
+ return [...keys];
28
+ }
29
+
30
+ export function getUnfoldedColumns(columns, data) {
31
+ const hasDataBlocks = columns.some(column => column.type === 'DATA-BLOCK');
32
+
33
+ if (!hasDataBlocks)
34
+ return columns;
35
+
36
+ const unfoldedColumns = [];
37
+
38
+ for (const column of columns) {
39
+ if (column.type === 'DATA-BLOCK') {
40
+ const ids = 'selector' in column
41
+ ? column.selector(data)
42
+ : getDefaultColumnIds(data);
43
+
44
+ for (const id of ids) {
45
+ unfoldedColumns.push({
46
+ ...column,
47
+ id,
48
+ type: 'DATA'
49
+ });
50
+ }
51
+ } else {
52
+ unfoldedColumns.push(column);
53
+ }
54
+ }
55
+
56
+ return unfoldedColumns;
57
+ }
58
+
59
+ export function getUnfoldedRows(rows, data) {
60
+ const hasDataBlocks = rows.some(row => row.type === 'DATA-BLOCK');
61
+
62
+ if (!hasDataBlocks)
63
+ return rows;
64
+
65
+ const unfoldedRows = [];
66
+
67
+ for (const row of rows) {
68
+ if (row.type === 'DATA-BLOCK') {
69
+ const ids = 'selector' in row
70
+ ? row.selector(data)
71
+ : getDefaultRowIds(data);
72
+
73
+ for (const id of ids) {
74
+ unfoldedRows.push({
75
+ ...row,
76
+ id,
77
+ type: 'DATA'
78
+ });
79
+ }
80
+ } else {
81
+ unfoldedRows.push(row);
82
+ }
83
+ }
84
+
85
+ return unfoldedRows;
86
+ }