@revolist/revogrid 3.0.99 → 3.1.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/custom-element/index.js +479 -300
- package/dist/cjs/debounce-e9b040d9.js +575 -0
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/revo-grid.cjs.js +1 -1
- package/dist/cjs/revo-grid_11.cjs.entry.js +163 -634
- package/dist/cjs/revogr-filter-panel.cjs.entry.js +158 -43
- package/dist/collection/components/revo-grid/revo-grid.js +1 -1
- package/dist/collection/plugins/filter/conditions/equal.js +5 -1
- package/dist/collection/plugins/filter/filter.button.js +10 -0
- package/dist/collection/plugins/filter/filter.plugin.js +93 -47
- package/dist/collection/plugins/filter/filter.pop.js +196 -47
- package/dist/collection/plugins/filter/filter.style.css +51 -4
- package/dist/collection/plugins/groupingColumn/grouping.col.plugin.js +1 -0
- package/dist/collection/plugins/sorting/sorting.plugin.js +3 -0
- package/dist/collection/utilsExternal/generate-data.js +1 -1
- package/dist/esm/debounce-8dadcda7.js +558 -0
- package/dist/esm/loader.js +1 -1
- package/dist/esm/revo-grid.js +1 -1
- package/dist/esm/revo-grid_11.entry.js +117 -588
- package/dist/esm/revogr-filter-panel.entry.js +157 -42
- package/dist/esm-es5/debounce-8dadcda7.js +1 -0
- package/dist/esm-es5/loader.js +1 -1
- package/dist/esm-es5/revo-grid.js +1 -1
- package/dist/esm-es5/revo-grid_11.entry.js +1 -1
- package/dist/esm-es5/revogr-filter-panel.entry.js +1 -1
- package/dist/revo-grid/debounce-d097578d.js +1 -0
- package/dist/revo-grid/debounce-f40a88f6.system.js +1 -0
- package/dist/revo-grid/revo-grid.esm.js +1 -1
- package/dist/revo-grid/revo-grid.system.js +1 -1
- package/dist/revo-grid/revo-grid_11.entry.js +1 -1
- package/dist/revo-grid/revo-grid_11.system.entry.js +1 -1
- package/dist/revo-grid/revogr-filter-panel.entry.js +1 -1
- package/dist/revo-grid/revogr-filter-panel.system.entry.js +1 -1
- package/dist/types/components.d.ts +4 -2
- package/dist/types/plugins/filter/filter.button.d.ts +4 -0
- package/dist/types/plugins/filter/filter.plugin.d.ts +5 -8
- package/dist/types/plugins/filter/filter.pop.d.ts +26 -7
- package/package.json +1 -1
- package/dist/cjs/filter.button-2396a488.js +0 -27
- package/dist/esm/filter.button-53ebca66.js +0 -23
- package/dist/esm-es5/filter.button-53ebca66.js +0 -1
- package/dist/revo-grid/filter.button-1509c206.js +0 -1
- package/dist/revo-grid/filter.button-4bd87101.system.js +0 -1
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
import { Component, h, Host, Listen, Prop, State, Event, Method } from '@stencil/core';
|
|
2
|
-
import { isFilterBtn } from './filter.button';
|
|
2
|
+
import { AndOrButton, isFilterBtn, TrashButton } from './filter.button';
|
|
3
3
|
import { RevoButton } from '../../components/button/button';
|
|
4
4
|
import '../../utils/closestPolifill';
|
|
5
|
+
import debounce from 'lodash/debounce';
|
|
5
6
|
const defaultType = 'none';
|
|
7
|
+
const FILTER_LIST_CLASS = 'multi-filter-list';
|
|
8
|
+
const FILTER_LIST_CLASS_ACTION = 'multi-filter-list-action';
|
|
6
9
|
export class FilterPanel {
|
|
7
10
|
constructor() {
|
|
8
11
|
this.filterCaptionsInternal = {
|
|
9
|
-
title:
|
|
10
|
-
save:
|
|
11
|
-
reset:
|
|
12
|
-
cancel:
|
|
12
|
+
title: 'Filter by condition',
|
|
13
|
+
save: 'Save',
|
|
14
|
+
reset: 'Reset',
|
|
15
|
+
cancel: 'Close',
|
|
13
16
|
};
|
|
17
|
+
this.isFilterIdSet = false;
|
|
18
|
+
this.filterId = 0;
|
|
19
|
+
this.currentFilterId = -1;
|
|
20
|
+
this.currentFilterType = defaultType;
|
|
21
|
+
this.filterItems = {};
|
|
14
22
|
this.filterTypes = {};
|
|
15
23
|
this.filterNames = {};
|
|
16
24
|
this.filterEntities = {};
|
|
25
|
+
this.debouncedApplyFilter = debounce(() => {
|
|
26
|
+
this.filterChange.emit(this.filterItems);
|
|
27
|
+
}, 400);
|
|
17
28
|
}
|
|
18
29
|
onMouseDown(e) {
|
|
19
30
|
if (this.changes && !e.defaultPrevented) {
|
|
@@ -32,26 +43,63 @@ export class FilterPanel {
|
|
|
32
43
|
async getChanges() {
|
|
33
44
|
return this.changes;
|
|
34
45
|
}
|
|
35
|
-
|
|
46
|
+
componentWillRender() {
|
|
47
|
+
if (!this.isFilterIdSet) {
|
|
48
|
+
this.isFilterIdSet = true;
|
|
49
|
+
const filterItems = Object.keys(this.filterItems);
|
|
50
|
+
for (const prop of filterItems) {
|
|
51
|
+
// we set the proper filterId so there won't be any conflict when removing filters
|
|
52
|
+
this.filterId += this.filterItems[prop].length;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
renderSelectOptions(type, isDefaultTypeRemoved = false) {
|
|
57
|
+
var _a;
|
|
36
58
|
const options = [];
|
|
59
|
+
const prop = (_a = this.changes) === null || _a === void 0 ? void 0 : _a.prop;
|
|
60
|
+
if (!isDefaultTypeRemoved) {
|
|
61
|
+
options.push(h("option", { selected: this.currentFilterType === defaultType, value: defaultType }, prop && this.filterItems[prop] && this.filterItems[prop].length > 0 ? 'Add more condition...' : this.filterNames[defaultType]));
|
|
62
|
+
}
|
|
37
63
|
for (let gIndex in this.filterTypes) {
|
|
38
|
-
options.push(h("option", { value: defaultType }, this.filterNames[defaultType]));
|
|
39
64
|
options.push(...this.filterTypes[gIndex].map(k => (h("option", { value: k, selected: type === k }, this.filterNames[k]))));
|
|
40
65
|
options.push(h("option", { disabled: true }));
|
|
41
66
|
}
|
|
42
67
|
return options;
|
|
43
68
|
}
|
|
44
|
-
renderExtra(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
69
|
+
renderExtra(prop, index) {
|
|
70
|
+
const currentFilter = this.filterItems[prop];
|
|
71
|
+
if (!currentFilter)
|
|
72
|
+
return '';
|
|
73
|
+
if (this.filterEntities[currentFilter[index].type].extra !== 'input')
|
|
74
|
+
return '';
|
|
75
|
+
return (h("input", { id: `filter-input-${currentFilter[index].id}`, placeholder: "Enter value...", type: "text", value: currentFilter[index].value, onInput: this.onUserInput.bind(this, index, prop), onKeyDown: e => this.onKeyDown(e) }));
|
|
76
|
+
}
|
|
77
|
+
getFilterItemsList() {
|
|
78
|
+
var _a;
|
|
79
|
+
const prop = (_a = this.changes) === null || _a === void 0 ? void 0 : _a.prop;
|
|
80
|
+
if (!(prop || prop === 0))
|
|
81
|
+
return '';
|
|
82
|
+
const propFilters = this.filterItems[prop] || [];
|
|
83
|
+
return (h("div", { key: this.filterId },
|
|
84
|
+
propFilters.map((d, index) => {
|
|
85
|
+
let andOrButton;
|
|
86
|
+
// hide toggle button if there is only one filter and the last one
|
|
87
|
+
if (index !== this.filterItems[prop].length - 1) {
|
|
88
|
+
andOrButton = (h("div", { onClick: () => this.toggleFilterAndOr(d.id) },
|
|
89
|
+
h(AndOrButton, { isAnd: d.relation === 'and' })));
|
|
90
|
+
}
|
|
91
|
+
return (h("div", { key: d.id, class: FILTER_LIST_CLASS },
|
|
92
|
+
h("div", { class: { 'select-input': true } },
|
|
93
|
+
h("select", { class: "select-css select-filter", onChange: e => this.onFilterTypeChange(e, prop, index) }, this.renderSelectOptions(this.filterItems[prop][index].type, true)),
|
|
94
|
+
h("div", { class: FILTER_LIST_CLASS_ACTION }, andOrButton),
|
|
95
|
+
h("div", { onClick: () => this.onRemoveFilter(d.id) },
|
|
96
|
+
h(TrashButton, null))),
|
|
97
|
+
h("div", null, this.renderExtra(prop, index))));
|
|
98
|
+
}),
|
|
99
|
+
propFilters.length > 0 ? h("div", { class: "add-filter-divider" }) : ''));
|
|
52
100
|
}
|
|
53
101
|
render() {
|
|
54
|
-
if (!this.changes
|
|
102
|
+
if (!this.changes) {
|
|
55
103
|
return h(Host, { style: { display: 'none' } });
|
|
56
104
|
}
|
|
57
105
|
const style = {
|
|
@@ -62,29 +110,80 @@ export class FilterPanel {
|
|
|
62
110
|
const capts = Object.assign(this.filterCaptionsInternal, this.filterCaptions);
|
|
63
111
|
return (h(Host, { style: style },
|
|
64
112
|
h("label", null, capts.title),
|
|
65
|
-
h("
|
|
66
|
-
h("div",
|
|
67
|
-
|
|
68
|
-
|
|
113
|
+
h("div", { class: "filter-holder" }, this.getFilterItemsList()),
|
|
114
|
+
h("div", { class: "add-filter" },
|
|
115
|
+
h("select", { id: "add-filter", class: "select-css", onChange: e => this.onAddNewFilter(e) }, this.renderSelectOptions(this.currentFilterType))),
|
|
116
|
+
h("div", { class: "filter-actions" },
|
|
69
117
|
h(RevoButton, { class: { red: true }, onClick: () => this.onReset() }, capts.reset),
|
|
70
118
|
h(RevoButton, { class: { light: true }, onClick: () => this.onCancel() }, capts.cancel))));
|
|
71
119
|
}
|
|
72
|
-
|
|
73
|
-
if (!this.changes) {
|
|
74
|
-
throw new Error('Changes required per edit');
|
|
75
|
-
}
|
|
120
|
+
onFilterTypeChange(e, prop, index) {
|
|
76
121
|
const el = e.target;
|
|
77
122
|
const type = el.value;
|
|
78
|
-
this.
|
|
123
|
+
this.filterItems[prop][index].type = type;
|
|
124
|
+
// this re-renders the input to know if we need extra input
|
|
125
|
+
this.filterId++;
|
|
126
|
+
// adding setTimeout will wait for the next tick DOM update then focus on input
|
|
127
|
+
setTimeout(() => {
|
|
128
|
+
const input = document.getElementById('filter-input-' + this.filterItems[prop][index].id);
|
|
129
|
+
if (input)
|
|
130
|
+
input.focus();
|
|
131
|
+
}, 0);
|
|
132
|
+
this.debouncedApplyFilter();
|
|
79
133
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
134
|
+
onAddNewFilter(e) {
|
|
135
|
+
const el = e.target;
|
|
136
|
+
const type = el.value;
|
|
137
|
+
this.currentFilterType = type;
|
|
138
|
+
this.addNewFilterToProp();
|
|
139
|
+
// reset value after adding new filter
|
|
140
|
+
const select = document.getElementById('add-filter');
|
|
141
|
+
if (select) {
|
|
142
|
+
select.value = defaultType;
|
|
143
|
+
this.currentFilterType = defaultType;
|
|
144
|
+
}
|
|
145
|
+
this.debouncedApplyFilter();
|
|
146
|
+
}
|
|
147
|
+
addNewFilterToProp() {
|
|
148
|
+
var _a;
|
|
149
|
+
const prop = (_a = this.changes) === null || _a === void 0 ? void 0 : _a.prop;
|
|
150
|
+
if (!(prop || prop === 0))
|
|
151
|
+
return;
|
|
152
|
+
if (!this.filterItems[prop]) {
|
|
153
|
+
this.filterItems[prop] = [];
|
|
154
|
+
}
|
|
155
|
+
if (this.currentFilterType === 'none')
|
|
156
|
+
return;
|
|
157
|
+
this.filterId++;
|
|
158
|
+
this.currentFilterId = this.filterId;
|
|
159
|
+
this.filterItems[prop].push({
|
|
160
|
+
id: this.currentFilterId,
|
|
161
|
+
type: this.currentFilterType,
|
|
162
|
+
value: '',
|
|
163
|
+
relation: 'and',
|
|
164
|
+
});
|
|
165
|
+
// adding setTimeout will wait for the next tick DOM update then focus on input
|
|
166
|
+
setTimeout(() => {
|
|
167
|
+
const input = document.getElementById('filter-input-' + this.currentFilterId);
|
|
168
|
+
if (input)
|
|
169
|
+
input.focus();
|
|
170
|
+
}, 0);
|
|
171
|
+
}
|
|
172
|
+
onUserInput(index, prop, event) {
|
|
173
|
+
// update the value of the filter item
|
|
174
|
+
this.filterItems[prop][index].value = event.target.value;
|
|
175
|
+
this.debouncedApplyFilter();
|
|
84
176
|
}
|
|
85
177
|
onKeyDown(e) {
|
|
86
178
|
if (e.key.toLowerCase() === 'enter') {
|
|
87
|
-
|
|
179
|
+
const select = document.getElementById('add-filter');
|
|
180
|
+
if (select) {
|
|
181
|
+
select.value = defaultType;
|
|
182
|
+
this.currentFilterType = defaultType;
|
|
183
|
+
this.addNewFilterToProp();
|
|
184
|
+
select.focus();
|
|
185
|
+
}
|
|
186
|
+
return;
|
|
88
187
|
}
|
|
89
188
|
// keep event local, don't escalate farther to dom
|
|
90
189
|
e.stopPropagation();
|
|
@@ -92,23 +191,43 @@ export class FilterPanel {
|
|
|
92
191
|
onCancel() {
|
|
93
192
|
this.changes = undefined;
|
|
94
193
|
}
|
|
95
|
-
|
|
96
|
-
var _a, _b;
|
|
194
|
+
onReset() {
|
|
97
195
|
this.assertChanges();
|
|
98
|
-
this.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
});
|
|
103
|
-
this.changes = undefined;
|
|
196
|
+
delete this.filterItems[this.changes.prop];
|
|
197
|
+
// this updates the DOM which is used by getFilterItemsList() key
|
|
198
|
+
this.filterId++;
|
|
199
|
+
this.filterChange.emit(this.filterItems);
|
|
104
200
|
}
|
|
105
|
-
|
|
201
|
+
onRemoveFilter(id) {
|
|
106
202
|
this.assertChanges();
|
|
107
|
-
this
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
203
|
+
// this is for reactivity issues for getFilterItemsList()
|
|
204
|
+
this.filterId++;
|
|
205
|
+
const prop = this.changes.prop;
|
|
206
|
+
const items = this.filterItems[prop];
|
|
207
|
+
if (!items)
|
|
208
|
+
return;
|
|
209
|
+
const index = items.findIndex(d => d.id === id);
|
|
210
|
+
if (index === -1)
|
|
211
|
+
return;
|
|
212
|
+
items.splice(index, 1);
|
|
213
|
+
// let's remove the prop if no more filters so the filter icon will be removed
|
|
214
|
+
if (items.length === 0)
|
|
215
|
+
delete this.filterItems[prop];
|
|
216
|
+
this.debouncedApplyFilter();
|
|
217
|
+
}
|
|
218
|
+
toggleFilterAndOr(id) {
|
|
219
|
+
this.assertChanges();
|
|
220
|
+
// this is for reactivity issues for getFilterItemsList()
|
|
221
|
+
this.filterId++;
|
|
222
|
+
const prop = this.changes.prop;
|
|
223
|
+
const items = this.filterItems[prop];
|
|
224
|
+
if (!items)
|
|
225
|
+
return;
|
|
226
|
+
const index = items.findIndex(d => d.id === id);
|
|
227
|
+
if (index === -1)
|
|
228
|
+
return;
|
|
229
|
+
items[index].relation = items[index].relation === 'and' ? 'or' : 'and';
|
|
230
|
+
this.debouncedApplyFilter();
|
|
112
231
|
}
|
|
113
232
|
assertChanges() {
|
|
114
233
|
if (!this.changes) {
|
|
@@ -116,6 +235,12 @@ export class FilterPanel {
|
|
|
116
235
|
}
|
|
117
236
|
}
|
|
118
237
|
isOutside(e) {
|
|
238
|
+
const select = document.getElementById('add-filter');
|
|
239
|
+
if (select)
|
|
240
|
+
select.value = defaultType;
|
|
241
|
+
this.currentFilterType = defaultType;
|
|
242
|
+
this.changes.type = defaultType;
|
|
243
|
+
this.currentFilterId = -1;
|
|
119
244
|
if (e.classList.contains(`[uuid="${this.uuid}"]`)) {
|
|
120
245
|
return false;
|
|
121
246
|
}
|
|
@@ -146,6 +271,26 @@ export class FilterPanel {
|
|
|
146
271
|
"attribute": "uuid",
|
|
147
272
|
"reflect": true
|
|
148
273
|
},
|
|
274
|
+
"filterItems": {
|
|
275
|
+
"type": "unknown",
|
|
276
|
+
"mutable": false,
|
|
277
|
+
"complexType": {
|
|
278
|
+
"original": "MultiFilterItem",
|
|
279
|
+
"resolved": "{ [prop: string]: FilterData[]; }",
|
|
280
|
+
"references": {
|
|
281
|
+
"MultiFilterItem": {
|
|
282
|
+
"location": "local"
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
"required": false,
|
|
287
|
+
"optional": false,
|
|
288
|
+
"docs": {
|
|
289
|
+
"tags": [],
|
|
290
|
+
"text": ""
|
|
291
|
+
},
|
|
292
|
+
"defaultValue": "{}"
|
|
293
|
+
},
|
|
149
294
|
"filterTypes": {
|
|
150
295
|
"type": "unknown",
|
|
151
296
|
"mutable": false,
|
|
@@ -232,6 +377,10 @@ export class FilterPanel {
|
|
|
232
377
|
}
|
|
233
378
|
}; }
|
|
234
379
|
static get states() { return {
|
|
380
|
+
"isFilterIdSet": {},
|
|
381
|
+
"filterId": {},
|
|
382
|
+
"currentFilterId": {},
|
|
383
|
+
"currentFilterType": {},
|
|
235
384
|
"changes": {}
|
|
236
385
|
}; }
|
|
237
386
|
static get events() { return [{
|
|
@@ -245,10 +394,10 @@ export class FilterPanel {
|
|
|
245
394
|
"text": ""
|
|
246
395
|
},
|
|
247
396
|
"complexType": {
|
|
248
|
-
"original": "
|
|
249
|
-
"resolved": "{ prop
|
|
397
|
+
"original": "MultiFilterItem",
|
|
398
|
+
"resolved": "{ [prop: string]: FilterData[]; }",
|
|
250
399
|
"references": {
|
|
251
|
-
"
|
|
400
|
+
"MultiFilterItem": {
|
|
252
401
|
"location": "local"
|
|
253
402
|
}
|
|
254
403
|
}
|
|
@@ -84,6 +84,10 @@ revogr-filter-panel {
|
|
|
84
84
|
min-width: 220px;
|
|
85
85
|
text-align: left;
|
|
86
86
|
}
|
|
87
|
+
revogr-filter-panel .filter-holder > div {
|
|
88
|
+
display: flex;
|
|
89
|
+
flex-direction: column;
|
|
90
|
+
}
|
|
87
91
|
revogr-filter-panel label {
|
|
88
92
|
color: gray;
|
|
89
93
|
font-size: 13px;
|
|
@@ -108,6 +112,10 @@ revogr-filter-panel button {
|
|
|
108
112
|
margin-top: 10px;
|
|
109
113
|
margin-right: 5px;
|
|
110
114
|
}
|
|
115
|
+
revogr-filter-panel .filter-actions {
|
|
116
|
+
text-align: right;
|
|
117
|
+
margin-right: -5px;
|
|
118
|
+
}
|
|
111
119
|
|
|
112
120
|
.rgHeaderCell:hover .rv-filter {
|
|
113
121
|
transition: opacity 267ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, transform 178ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
|
|
@@ -134,10 +142,6 @@ revogr-filter-panel button {
|
|
|
134
142
|
width: 11px;
|
|
135
143
|
}
|
|
136
144
|
|
|
137
|
-
.center {
|
|
138
|
-
text-align: center;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
145
|
.select-css {
|
|
142
146
|
display: block;
|
|
143
147
|
font-family: sans-serif;
|
|
@@ -189,4 +193,47 @@ revogr-filter-panel button {
|
|
|
189
193
|
}
|
|
190
194
|
.select-css:disabled:hover, .select-css[aria-disabled=true] {
|
|
191
195
|
border-color: #f1f1f1;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.multi-filter-list {
|
|
199
|
+
margin-top: 5px;
|
|
200
|
+
margin-bottom: 5px;
|
|
201
|
+
}
|
|
202
|
+
.multi-filter-list div {
|
|
203
|
+
white-space: nowrap;
|
|
204
|
+
}
|
|
205
|
+
.multi-filter-list .multi-filter-list-action {
|
|
206
|
+
display: flex;
|
|
207
|
+
justify-content: space-between;
|
|
208
|
+
align-items: center;
|
|
209
|
+
}
|
|
210
|
+
.multi-filter-list .and-or-button {
|
|
211
|
+
margin: 0 0 0 10px;
|
|
212
|
+
min-width: 58px;
|
|
213
|
+
cursor: pointer;
|
|
214
|
+
}
|
|
215
|
+
.multi-filter-list .trash-button {
|
|
216
|
+
margin: 0 0 -2px 6px;
|
|
217
|
+
cursor: pointer;
|
|
218
|
+
width: 22px;
|
|
219
|
+
height: 22px;
|
|
220
|
+
color: gray;
|
|
221
|
+
font-size: 18px;
|
|
222
|
+
}
|
|
223
|
+
.multi-filter-list .trash-button .trash-img {
|
|
224
|
+
width: 1em;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.add-filter-divider {
|
|
228
|
+
display: block;
|
|
229
|
+
margin: 0 -10px 10px -10px;
|
|
230
|
+
border-bottom: 1px solid #d9d9d9;
|
|
231
|
+
height: 10px;
|
|
232
|
+
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.05);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.select-input {
|
|
236
|
+
display: flex;
|
|
237
|
+
justify-content: space-between;
|
|
238
|
+
align-items: center;
|
|
192
239
|
}
|
|
@@ -27,6 +27,7 @@ export default class GroupingColumnPlugin extends BasePlugin {
|
|
|
27
27
|
res.columnGrouping[key].push(...collectionItem);
|
|
28
28
|
}
|
|
29
29
|
res.maxLevel = Math.max(res.maxLevel, collection.maxLevel);
|
|
30
|
+
res.sort = Object.assign(Object.assign({}, res.sort), collection.sort);
|
|
30
31
|
return res;
|
|
31
32
|
}
|
|
32
33
|
static isColGrouping(colData) {
|