cx 24.4.3 → 24.4.5
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/dist/manifest.js +587 -587
- package/dist/util.js +7 -0
- package/dist/widgets.js +165 -72
- package/package.json +1 -1
- package/src/charts/PieChart.js +527 -527
- package/src/data/Grouper.js +144 -144
- package/src/data/Grouper.spec.js +57 -57
- package/src/ui/adapter/GroupAdapter.js +141 -141
- package/src/util/Format.js +8 -0
- package/src/widgets/grid/Grid.d.ts +4 -2
- package/src/widgets/grid/Grid.js +131 -60
- package/src/widgets/grid/Grid.scss +680 -680
- package/src/widgets/grid/GridCell.d.ts +9 -0
- package/src/widgets/grid/GridCell.js +9 -8
- package/src/widgets/grid/GridRow.js +21 -21
|
@@ -1,141 +1,141 @@
|
|
|
1
|
-
import { ArrayAdapter } from "./ArrayAdapter";
|
|
2
|
-
import { ReadOnlyDataView } from "../../data/ReadOnlyDataView";
|
|
3
|
-
import { Grouper } from "../../data/Grouper";
|
|
4
|
-
import { isArray } from "../../util/isArray";
|
|
5
|
-
import { isDefined } from "../../util/isDefined";
|
|
6
|
-
import { getComparer } from "../../data/comparer";
|
|
7
|
-
import { Culture } from "../Culture";
|
|
8
|
-
import { isObject } from "../../util/isObject";
|
|
9
|
-
|
|
10
|
-
export class GroupAdapter extends ArrayAdapter {
|
|
11
|
-
init() {
|
|
12
|
-
super.init();
|
|
13
|
-
|
|
14
|
-
if (this.groupRecordsAlias) this.groupRecordsName = this.groupRecordsAlias;
|
|
15
|
-
|
|
16
|
-
if (this.groupings) this.groupBy(this.groupings);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
getRecords(context, instance, records, parentStore) {
|
|
20
|
-
let result = super.getRecords(context, instance, records, parentStore);
|
|
21
|
-
|
|
22
|
-
if (this.groupings) {
|
|
23
|
-
let groupedResults = [];
|
|
24
|
-
this.processLevel([], result, groupedResults, parentStore);
|
|
25
|
-
result = groupedResults;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return result;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
processLevel(keys, records, result, parentStore) {
|
|
32
|
-
let level = keys.length;
|
|
33
|
-
let inverseLevel = this.groupings.length - level;
|
|
34
|
-
|
|
35
|
-
if (inverseLevel == 0) {
|
|
36
|
-
for (let i = 0; i < records.length; i++) {
|
|
37
|
-
records[i].store.setStore(parentStore);
|
|
38
|
-
result.push(records[i]);
|
|
39
|
-
}
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
let grouping = this.groupings[level];
|
|
44
|
-
let { grouper } = grouping;
|
|
45
|
-
grouper.reset();
|
|
46
|
-
grouper.processAll(records);
|
|
47
|
-
let results = grouper.getResults();
|
|
48
|
-
if (grouping.comparer) results.sort(grouping.comparer);
|
|
49
|
-
|
|
50
|
-
results.forEach((gr) => {
|
|
51
|
-
keys.push(gr.key);
|
|
52
|
-
|
|
53
|
-
let key = keys.map(serializeKey).join("|");
|
|
54
|
-
|
|
55
|
-
let $group = {
|
|
56
|
-
...gr.key,
|
|
57
|
-
...gr.aggregates,
|
|
58
|
-
$name: gr.name,
|
|
59
|
-
$level: inverseLevel,
|
|
60
|
-
$records: gr.records || [],
|
|
61
|
-
$key: key,
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
let data = {
|
|
65
|
-
[this.recordName]: gr.records.length > 0 ? gr.records[0].data : null,
|
|
66
|
-
[this.groupName]: $group,
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
let groupStore = new ReadOnlyDataView({
|
|
70
|
-
store: parentStore,
|
|
71
|
-
data,
|
|
72
|
-
immutable: this.immutable,
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
let group = {
|
|
76
|
-
key,
|
|
77
|
-
data,
|
|
78
|
-
group: $group,
|
|
79
|
-
grouping,
|
|
80
|
-
store: groupStore,
|
|
81
|
-
level: inverseLevel,
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
if (grouping.includeHeader !== false)
|
|
85
|
-
result.push({
|
|
86
|
-
...group,
|
|
87
|
-
type: "group-header",
|
|
88
|
-
key: "header:" + group.key,
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
this.processLevel(keys, gr.records, result, groupStore);
|
|
92
|
-
|
|
93
|
-
if (grouping.includeFooter !== false)
|
|
94
|
-
result.push({
|
|
95
|
-
...group,
|
|
96
|
-
type: "group-footer",
|
|
97
|
-
key: "footer:" + group.key,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
keys.pop();
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
groupBy(groupings) {
|
|
105
|
-
if (!groupings) this.groupings = null;
|
|
106
|
-
else if (isArray(groupings)) {
|
|
107
|
-
this.groupings = groupings;
|
|
108
|
-
this.groupings.forEach((g) => {
|
|
109
|
-
let groupSorters = [];
|
|
110
|
-
let key = {};
|
|
111
|
-
for (let name in g.key) {
|
|
112
|
-
if (!g.key[name] || !isDefined(g.key[name].direction) || !isDefined(g.key[name].value))
|
|
113
|
-
g.key[name] = { value: g.key[name], direction: "ASC" };
|
|
114
|
-
key[name] = g.key[name].value;
|
|
115
|
-
groupSorters.push({
|
|
116
|
-
field: name,
|
|
117
|
-
direction: g.key[name].direction,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
g.grouper = new Grouper(key, { ...this.aggregates, ...g.aggregates }, (r) => r.store.getData(), g.text);
|
|
121
|
-
g.comparer = null;
|
|
122
|
-
if (groupSorters.length > 0)
|
|
123
|
-
g.comparer = getComparer(
|
|
124
|
-
groupSorters,
|
|
125
|
-
(x) => x.key,
|
|
126
|
-
this.sortOptions ? Culture.getComparer(this.sortOptions) : null,
|
|
127
|
-
);
|
|
128
|
-
});
|
|
129
|
-
} else throw new Error("Invalid grouping provided.");
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
GroupAdapter.prototype.groupName = "$group";
|
|
134
|
-
|
|
135
|
-
function serializeKey(data) {
|
|
136
|
-
if (isObject(data))
|
|
137
|
-
return Object.keys(data)
|
|
138
|
-
.map((k) => serializeKey(data[k]))
|
|
139
|
-
.join(":");
|
|
140
|
-
return data?.toString() ?? "";
|
|
141
|
-
}
|
|
1
|
+
import { ArrayAdapter } from "./ArrayAdapter";
|
|
2
|
+
import { ReadOnlyDataView } from "../../data/ReadOnlyDataView";
|
|
3
|
+
import { Grouper } from "../../data/Grouper";
|
|
4
|
+
import { isArray } from "../../util/isArray";
|
|
5
|
+
import { isDefined } from "../../util/isDefined";
|
|
6
|
+
import { getComparer } from "../../data/comparer";
|
|
7
|
+
import { Culture } from "../Culture";
|
|
8
|
+
import { isObject } from "../../util/isObject";
|
|
9
|
+
|
|
10
|
+
export class GroupAdapter extends ArrayAdapter {
|
|
11
|
+
init() {
|
|
12
|
+
super.init();
|
|
13
|
+
|
|
14
|
+
if (this.groupRecordsAlias) this.groupRecordsName = this.groupRecordsAlias;
|
|
15
|
+
|
|
16
|
+
if (this.groupings) this.groupBy(this.groupings);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getRecords(context, instance, records, parentStore) {
|
|
20
|
+
let result = super.getRecords(context, instance, records, parentStore);
|
|
21
|
+
|
|
22
|
+
if (this.groupings) {
|
|
23
|
+
let groupedResults = [];
|
|
24
|
+
this.processLevel([], result, groupedResults, parentStore);
|
|
25
|
+
result = groupedResults;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
processLevel(keys, records, result, parentStore) {
|
|
32
|
+
let level = keys.length;
|
|
33
|
+
let inverseLevel = this.groupings.length - level;
|
|
34
|
+
|
|
35
|
+
if (inverseLevel == 0) {
|
|
36
|
+
for (let i = 0; i < records.length; i++) {
|
|
37
|
+
records[i].store.setStore(parentStore);
|
|
38
|
+
result.push(records[i]);
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let grouping = this.groupings[level];
|
|
44
|
+
let { grouper } = grouping;
|
|
45
|
+
grouper.reset();
|
|
46
|
+
grouper.processAll(records);
|
|
47
|
+
let results = grouper.getResults();
|
|
48
|
+
if (grouping.comparer) results.sort(grouping.comparer);
|
|
49
|
+
|
|
50
|
+
results.forEach((gr) => {
|
|
51
|
+
keys.push(gr.key);
|
|
52
|
+
|
|
53
|
+
let key = keys.map(serializeKey).join("|");
|
|
54
|
+
|
|
55
|
+
let $group = {
|
|
56
|
+
...gr.key,
|
|
57
|
+
...gr.aggregates,
|
|
58
|
+
$name: gr.name,
|
|
59
|
+
$level: inverseLevel,
|
|
60
|
+
$records: gr.records || [],
|
|
61
|
+
$key: key,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
let data = {
|
|
65
|
+
[this.recordName]: gr.records.length > 0 ? gr.records[0].data : null,
|
|
66
|
+
[this.groupName]: $group,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
let groupStore = new ReadOnlyDataView({
|
|
70
|
+
store: parentStore,
|
|
71
|
+
data,
|
|
72
|
+
immutable: this.immutable,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
let group = {
|
|
76
|
+
key,
|
|
77
|
+
data,
|
|
78
|
+
group: $group,
|
|
79
|
+
grouping,
|
|
80
|
+
store: groupStore,
|
|
81
|
+
level: inverseLevel,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (grouping.includeHeader !== false)
|
|
85
|
+
result.push({
|
|
86
|
+
...group,
|
|
87
|
+
type: "group-header",
|
|
88
|
+
key: "header:" + group.key,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
this.processLevel(keys, gr.records, result, groupStore);
|
|
92
|
+
|
|
93
|
+
if (grouping.includeFooter !== false)
|
|
94
|
+
result.push({
|
|
95
|
+
...group,
|
|
96
|
+
type: "group-footer",
|
|
97
|
+
key: "footer:" + group.key,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
keys.pop();
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
groupBy(groupings) {
|
|
105
|
+
if (!groupings) this.groupings = null;
|
|
106
|
+
else if (isArray(groupings)) {
|
|
107
|
+
this.groupings = groupings;
|
|
108
|
+
this.groupings.forEach((g) => {
|
|
109
|
+
let groupSorters = [];
|
|
110
|
+
let key = {};
|
|
111
|
+
for (let name in g.key) {
|
|
112
|
+
if (!g.key[name] || !isDefined(g.key[name].direction) || !isDefined(g.key[name].value))
|
|
113
|
+
g.key[name] = { value: g.key[name], direction: "ASC" };
|
|
114
|
+
key[name] = g.key[name].value;
|
|
115
|
+
groupSorters.push({
|
|
116
|
+
field: name,
|
|
117
|
+
direction: g.key[name].direction,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
g.grouper = new Grouper(key, { ...this.aggregates, ...g.aggregates }, (r) => r.store.getData(), g.text);
|
|
121
|
+
g.comparer = null;
|
|
122
|
+
if (groupSorters.length > 0)
|
|
123
|
+
g.comparer = getComparer(
|
|
124
|
+
groupSorters,
|
|
125
|
+
(x) => x.key,
|
|
126
|
+
this.sortOptions ? Culture.getComparer(this.sortOptions) : null,
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
} else throw new Error("Invalid grouping provided.");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
GroupAdapter.prototype.groupName = "$group";
|
|
134
|
+
|
|
135
|
+
function serializeKey(data) {
|
|
136
|
+
if (isObject(data))
|
|
137
|
+
return Object.keys(data)
|
|
138
|
+
.map((k) => serializeKey(data[k]))
|
|
139
|
+
.join(":");
|
|
140
|
+
return data?.toString() ?? "";
|
|
141
|
+
}
|
package/src/util/Format.js
CHANGED
|
@@ -124,6 +124,13 @@ let formatFactory = {
|
|
|
124
124
|
};
|
|
125
125
|
}
|
|
126
126
|
},
|
|
127
|
+
|
|
128
|
+
zeroPad: function (part0, length) {
|
|
129
|
+
return (value) => {
|
|
130
|
+
let s = String(value);
|
|
131
|
+
return s.padStart(length, "0");
|
|
132
|
+
};
|
|
133
|
+
},
|
|
127
134
|
};
|
|
128
135
|
|
|
129
136
|
formatFactory.s = formatFactory.str = formatFactory.string;
|
|
@@ -134,6 +141,7 @@ formatFactory.ps = formatFactory.percentageSign;
|
|
|
134
141
|
formatFactory.d = formatFactory.date;
|
|
135
142
|
formatFactory.t = formatFactory.time;
|
|
136
143
|
formatFactory.dt = formatFactory.datetime;
|
|
144
|
+
formatFactory.zeropad = formatFactory.zeroPad;
|
|
137
145
|
|
|
138
146
|
function buildFormatter(format) {
|
|
139
147
|
let formatter = defaultFormatter,
|
|
@@ -139,6 +139,8 @@ interface GridColumnConfig {
|
|
|
139
139
|
/** Options for data sorting. See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Collator */
|
|
140
140
|
sortOptions?: CollatorOptions;
|
|
141
141
|
colSpan?: NumberProp;
|
|
142
|
+
|
|
143
|
+
mergeCells?: Prop<null | false | "same-value" | "always">;
|
|
142
144
|
}
|
|
143
145
|
|
|
144
146
|
interface GridRowLineConfig {
|
|
@@ -321,7 +323,7 @@ interface GridProps<T = unknown> extends StyledContainerProps {
|
|
|
321
323
|
sortField?: string;
|
|
322
324
|
sortDirection?: string;
|
|
323
325
|
},
|
|
324
|
-
instance?: Instance
|
|
326
|
+
instance?: Instance,
|
|
325
327
|
) => FetchRecordsResult | Promise<FetchRecordsResult>;
|
|
326
328
|
|
|
327
329
|
/** Callback function to be executed when a row is double-clicked. */
|
|
@@ -354,7 +356,7 @@ interface GridProps<T = unknown> extends StyledContainerProps {
|
|
|
354
356
|
/** Callback to create a function that can be used to check whether a record is selectable. */
|
|
355
357
|
onCreateIsRecordSelectable?: (
|
|
356
358
|
params: any,
|
|
357
|
-
instance: Instance
|
|
359
|
+
instance: Instance,
|
|
358
360
|
) => (record: T, options?: { range?: boolean; toggle?: boolean }) => boolean;
|
|
359
361
|
|
|
360
362
|
/** Parameters whose change will cause scroll to be reset. */
|
package/src/widgets/grid/Grid.js
CHANGED
|
@@ -128,6 +128,9 @@ export class Grid extends Container {
|
|
|
128
128
|
|
|
129
129
|
row.hasSortableColumns = false;
|
|
130
130
|
row.hasResizableColumns = false;
|
|
131
|
+
row.hasMergedCells = false;
|
|
132
|
+
row.mergedColumns = [];
|
|
133
|
+
|
|
131
134
|
let aggregates = {};
|
|
132
135
|
let showFooter = false;
|
|
133
136
|
let lines = [];
|
|
@@ -146,9 +149,18 @@ export class Grid extends Container {
|
|
|
146
149
|
});
|
|
147
150
|
|
|
148
151
|
row.header.items.forEach((line) => {
|
|
149
|
-
line.items.forEach((c) => {
|
|
152
|
+
line.items.forEach((c, index) => {
|
|
150
153
|
if (c.sortable) row.hasSortableColumns = true;
|
|
151
154
|
|
|
155
|
+
if (c.mergeCells) {
|
|
156
|
+
if (row.header.items.length > 1)
|
|
157
|
+
Console.warn("Merged columns are only supported in grids in which rows have only one line of cells.");
|
|
158
|
+
else {
|
|
159
|
+
row.hasMergedCells = true;
|
|
160
|
+
row.mergedColumns.push({ uniqueColumnId: c.uniqueColumnId, index, mode: c.mergeCells });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
152
164
|
if (
|
|
153
165
|
c.resizable ||
|
|
154
166
|
(c.header && c.header.resizable) ||
|
|
@@ -609,15 +621,16 @@ export class Grid extends Container {
|
|
|
609
621
|
if (!header) return null;
|
|
610
622
|
|
|
611
623
|
let skip = {};
|
|
624
|
+
let lineIndex = 0;
|
|
612
625
|
|
|
613
|
-
header.children.forEach((line
|
|
626
|
+
header.children.forEach((line) => {
|
|
614
627
|
let empty = [true, true, true];
|
|
615
628
|
let result = [[], [], []];
|
|
616
629
|
|
|
617
630
|
line.children.forEach((hdinst, colIndex) => {
|
|
618
631
|
let hdwidget = hdinst.widget;
|
|
619
632
|
for (let l = 0; l < 3; l++) {
|
|
620
|
-
let colKey = `${lineIndex}-${colIndex}
|
|
633
|
+
let colKey = `${lineIndex + l}-${colIndex}`;
|
|
621
634
|
|
|
622
635
|
if (skip[colKey]) continue;
|
|
623
636
|
|
|
@@ -690,7 +703,7 @@ export class Grid extends Container {
|
|
|
690
703
|
|
|
691
704
|
for (let r = 0; r < header.data.rowSpan; r++)
|
|
692
705
|
for (let c = 0; c < header.data.colSpan; c++)
|
|
693
|
-
skip[`${lineIndex
|
|
706
|
+
skip[`${lineIndex + l + r}-${colIndex + c}`] = true;
|
|
694
707
|
}
|
|
695
708
|
|
|
696
709
|
if ((hdwidget.resizable || header.data.resizable) && header.data.colSpan < 2) {
|
|
@@ -745,6 +758,7 @@ export class Grid extends Container {
|
|
|
745
758
|
});
|
|
746
759
|
|
|
747
760
|
result = result.filter((_, i) => !empty[i]);
|
|
761
|
+
lineIndex += result.length;
|
|
748
762
|
|
|
749
763
|
if (result[0]) {
|
|
750
764
|
if (fixed && !fixedColumns) {
|
|
@@ -1054,9 +1068,7 @@ export class Grid extends Container {
|
|
|
1054
1068
|
|
|
1055
1069
|
for (let i = 0; i < records.length; i++) {
|
|
1056
1070
|
record = records[i];
|
|
1057
|
-
if (record.type == "data")
|
|
1058
|
-
record.vdom = record.row.render(context, record.key);
|
|
1059
|
-
}
|
|
1071
|
+
if (record.type == "data") record.vdom = record.row.render(context, record.key);
|
|
1060
1072
|
|
|
1061
1073
|
if (record.type == "group-header") {
|
|
1062
1074
|
record.vdom = [];
|
|
@@ -1311,11 +1323,12 @@ class GridComponent extends VDOM.Component {
|
|
|
1311
1323
|
|
|
1312
1324
|
createRowRenderer(cellWrap) {
|
|
1313
1325
|
let { instance, data } = this.props;
|
|
1314
|
-
let { widget, isRecordSelectable, visibleColumns, isRecordDraggable } = instance;
|
|
1326
|
+
let { widget, isRecordSelectable, visibleColumns, isRecordDraggable, row } = instance;
|
|
1315
1327
|
let { CSS, baseClass } = widget;
|
|
1316
1328
|
let { dragSource } = data;
|
|
1317
1329
|
let { dragged, cursor, cursorCellIndex, cellEdit, dropInsertionIndex, dropTarget } = this.state;
|
|
1318
1330
|
let { colWidth, dimensionsVersion } = instance.state;
|
|
1331
|
+
let { hasMergedCells } = row;
|
|
1319
1332
|
|
|
1320
1333
|
return (record, index, standalone, fixed) => {
|
|
1321
1334
|
let { store, key, row } = record;
|
|
@@ -1341,49 +1354,58 @@ class GridComponent extends VDOM.Component {
|
|
|
1341
1354
|
mod["draggable"] = draggable;
|
|
1342
1355
|
mod["non-draggable"] = !draggable;
|
|
1343
1356
|
|
|
1344
|
-
let wrap = (children) =>
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1357
|
+
let wrap = (children) => {
|
|
1358
|
+
let skipCells = {};
|
|
1359
|
+
return (
|
|
1360
|
+
<GridRowComponent
|
|
1361
|
+
key={key}
|
|
1362
|
+
className={CSS.state(mod)}
|
|
1363
|
+
store={store}
|
|
1364
|
+
dragSource={dragSource}
|
|
1365
|
+
instance={row}
|
|
1366
|
+
grid={instance}
|
|
1367
|
+
record={record}
|
|
1368
|
+
parent={this}
|
|
1369
|
+
cursorIndex={index}
|
|
1370
|
+
selected={row.selected}
|
|
1371
|
+
isBeingDragged={dragged}
|
|
1372
|
+
isDraggedOver={mod.over}
|
|
1373
|
+
cursor={mod.cursor}
|
|
1374
|
+
cursorCellIndex={index == cursor && cursorCellIndex}
|
|
1375
|
+
cellEdit={index == cursor && cursorCellIndex != null && cellEdit}
|
|
1376
|
+
shouldUpdate={row.shouldUpdate}
|
|
1377
|
+
dimensionsVersion={dimensionsVersion}
|
|
1378
|
+
fixed={fixed}
|
|
1379
|
+
useTrTag={hasMergedCells}
|
|
1380
|
+
>
|
|
1381
|
+
{children.content.map(({ key, data, content }, line) => {
|
|
1382
|
+
var cells = content.map(({ key, data, content, uniqueColumnId, merged, mergeRowSpan }, cellIndex) => {
|
|
1383
|
+
if (Boolean(data.fixed) !== fixed) return null;
|
|
1384
|
+
if (merged) return null;
|
|
1385
|
+
let cellected =
|
|
1386
|
+
index == cursor && cellIndex == cursorCellIndex && widget.cellEditable && line == 0;
|
|
1387
|
+
let className = cellected ? CSS.expand(data.classNames, CSS.state("cellected")) : data.classNames;
|
|
1388
|
+
if (cellected && cellEdit) {
|
|
1389
|
+
let column = visibleColumns[cursorCellIndex];
|
|
1390
|
+
if (column && column.editor && data.editable)
|
|
1391
|
+
return this.renderCellEditor(key, CSS, baseClass, row, column);
|
|
1392
|
+
}
|
|
1393
|
+
let width = colWidth[uniqueColumnId];
|
|
1394
|
+
let style = data.style;
|
|
1395
|
+
if (width) {
|
|
1396
|
+
style = {
|
|
1397
|
+
...style,
|
|
1398
|
+
maxWidth: `${width}px`,
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
if (skipCells[`${line}-${cellIndex}`]) return null;
|
|
1403
|
+
|
|
1404
|
+
if (data.colSpan > 1 || data.rowSpan > 1) {
|
|
1405
|
+
for (let r = line; r < line + (data.rowSpan ?? 1); r++)
|
|
1406
|
+
for (let c = cellIndex; c < cellIndex + (data.colSpan ?? 1); c++)
|
|
1407
|
+
skipCells[`${r}-${c}`] = true;
|
|
1408
|
+
}
|
|
1387
1409
|
|
|
1388
1410
|
if (cellWrap) content = cellWrap(content);
|
|
1389
1411
|
|
|
@@ -1393,16 +1415,22 @@ class GridComponent extends VDOM.Component {
|
|
|
1393
1415
|
className={className}
|
|
1394
1416
|
style={style}
|
|
1395
1417
|
colSpan={data.colSpan}
|
|
1396
|
-
rowSpan={data.rowSpan}
|
|
1418
|
+
rowSpan={mergeRowSpan ?? data.rowSpan}
|
|
1397
1419
|
>
|
|
1398
1420
|
{content}
|
|
1399
1421
|
</td>
|
|
1400
1422
|
);
|
|
1401
|
-
})
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1423
|
+
});
|
|
1424
|
+
if (hasMergedCells) return cells;
|
|
1425
|
+
return (
|
|
1426
|
+
<tr key={key} className={data.classNames} style={data.style}>
|
|
1427
|
+
{cells}
|
|
1428
|
+
</tr>
|
|
1429
|
+
);
|
|
1430
|
+
})}
|
|
1431
|
+
</GridRowComponent>
|
|
1432
|
+
);
|
|
1433
|
+
};
|
|
1406
1434
|
|
|
1407
1435
|
if (!standalone) return wrap(record.vdom);
|
|
1408
1436
|
|
|
@@ -1570,10 +1598,53 @@ class GridComponent extends VDOM.Component {
|
|
|
1570
1598
|
});
|
|
1571
1599
|
instance.recordInstanceCache.sweep();
|
|
1572
1600
|
} else {
|
|
1601
|
+
let { row } = instance;
|
|
1602
|
+
let { hasMergedCells, mergedColumns } = row;
|
|
1603
|
+
if (hasMergedCells) {
|
|
1604
|
+
// merge adjacent cells with the same value in columns that are marked as merged
|
|
1605
|
+
let rowSpan = {};
|
|
1606
|
+
let getCellRenderInfo = (vdom, cellIndex) => vdom.content[0]?.content[cellIndex];
|
|
1607
|
+
for (let index = instance.records.length - 1; index >= 0; index--) {
|
|
1608
|
+
let row = instance.records[index];
|
|
1609
|
+
let prevRow = instance.records[index - 1];
|
|
1610
|
+
if (row.type == "data") {
|
|
1611
|
+
let stopMerge = false;
|
|
1612
|
+
for (let mi = 0; mi < mergedColumns.length; mi++) {
|
|
1613
|
+
let mergedCol = mergedColumns[mi];
|
|
1614
|
+
let cellInfo = getCellRenderInfo(row.vdom, mergedCol.index);
|
|
1615
|
+
cellInfo.merged = false;
|
|
1616
|
+
delete cellInfo.mergeRowSpan;
|
|
1617
|
+
if (prevRow?.type == "data") {
|
|
1618
|
+
let shouldMerge = false;
|
|
1619
|
+
switch (mergedCol.mode) {
|
|
1620
|
+
case "always":
|
|
1621
|
+
shouldMerge = true;
|
|
1622
|
+
break;
|
|
1623
|
+
case "same-value":
|
|
1624
|
+
let prevCellInfo = getCellRenderInfo(prevRow.vdom, mergedCol.index);
|
|
1625
|
+
shouldMerge = !stopMerge && cellInfo.data.value === prevCellInfo.data.value;
|
|
1626
|
+
break;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
if (shouldMerge) {
|
|
1630
|
+
cellInfo.merged = true;
|
|
1631
|
+
rowSpan[mergedCol.uniqueColumnId] = (rowSpan[mergedCol.uniqueColumnId] ?? 1) + 1;
|
|
1632
|
+
} else {
|
|
1633
|
+
if (mergedCol.mode == "same-value") stopMerge = true;
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
if (!cellInfo.merged && rowSpan[mergedCol.uniqueColumnId] > 1) {
|
|
1637
|
+
cellInfo.mergeRowSpan = rowSpan[mergedCol.uniqueColumnId];
|
|
1638
|
+
rowSpan[mergedCol.uniqueColumnId] = 1;
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
} else rowSpan = {};
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1573
1645
|
instance.records.forEach((record, i) => {
|
|
1574
|
-
if (record.type == "data")
|
|
1575
|
-
|
|
1576
|
-
} else {
|
|
1646
|
+
if (record.type == "data") addRow(record, i, false);
|
|
1647
|
+
else {
|
|
1577
1648
|
children.push(record.vdom);
|
|
1578
1649
|
if (hasFixedColumns) fixedChildren.push(record.fixedVdom);
|
|
1579
1650
|
}
|