@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.
Files changed (43) hide show
  1. package/custom-element/index.js +479 -300
  2. package/dist/cjs/debounce-e9b040d9.js +575 -0
  3. package/dist/cjs/loader.cjs.js +1 -1
  4. package/dist/cjs/revo-grid.cjs.js +1 -1
  5. package/dist/cjs/revo-grid_11.cjs.entry.js +163 -634
  6. package/dist/cjs/revogr-filter-panel.cjs.entry.js +158 -43
  7. package/dist/collection/components/revo-grid/revo-grid.js +1 -1
  8. package/dist/collection/plugins/filter/conditions/equal.js +5 -1
  9. package/dist/collection/plugins/filter/filter.button.js +10 -0
  10. package/dist/collection/plugins/filter/filter.plugin.js +93 -47
  11. package/dist/collection/plugins/filter/filter.pop.js +196 -47
  12. package/dist/collection/plugins/filter/filter.style.css +51 -4
  13. package/dist/collection/plugins/groupingColumn/grouping.col.plugin.js +1 -0
  14. package/dist/collection/plugins/sorting/sorting.plugin.js +3 -0
  15. package/dist/collection/utilsExternal/generate-data.js +1 -1
  16. package/dist/esm/debounce-8dadcda7.js +558 -0
  17. package/dist/esm/loader.js +1 -1
  18. package/dist/esm/revo-grid.js +1 -1
  19. package/dist/esm/revo-grid_11.entry.js +117 -588
  20. package/dist/esm/revogr-filter-panel.entry.js +157 -42
  21. package/dist/esm-es5/debounce-8dadcda7.js +1 -0
  22. package/dist/esm-es5/loader.js +1 -1
  23. package/dist/esm-es5/revo-grid.js +1 -1
  24. package/dist/esm-es5/revo-grid_11.entry.js +1 -1
  25. package/dist/esm-es5/revogr-filter-panel.entry.js +1 -1
  26. package/dist/revo-grid/debounce-d097578d.js +1 -0
  27. package/dist/revo-grid/debounce-f40a88f6.system.js +1 -0
  28. package/dist/revo-grid/revo-grid.esm.js +1 -1
  29. package/dist/revo-grid/revo-grid.system.js +1 -1
  30. package/dist/revo-grid/revo-grid_11.entry.js +1 -1
  31. package/dist/revo-grid/revo-grid_11.system.entry.js +1 -1
  32. package/dist/revo-grid/revogr-filter-panel.entry.js +1 -1
  33. package/dist/revo-grid/revogr-filter-panel.system.entry.js +1 -1
  34. package/dist/types/components.d.ts +4 -2
  35. package/dist/types/plugins/filter/filter.button.d.ts +4 -0
  36. package/dist/types/plugins/filter/filter.plugin.d.ts +5 -8
  37. package/dist/types/plugins/filter/filter.pop.d.ts +26 -7
  38. package/package.json +1 -1
  39. package/dist/cjs/filter.button-2396a488.js +0 -27
  40. package/dist/esm/filter.button-53ebca66.js +0 -23
  41. package/dist/esm-es5/filter.button-53ebca66.js +0 -1
  42. package/dist/revo-grid/filter.button-1509c206.js +0 -1
  43. 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: "Filter by condition",
10
- save: "Save",
11
- reset: "Reset",
12
- cancel: "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
- renderConditions(type) {
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(extra, value) {
45
- this.extraElement = undefined;
46
- switch (extra) {
47
- case 'input':
48
- return (h("input", { type: "text", value: value, onInput: (e) => this.onInput(e), onKeyDown: e => this.onKeyDown(e), ref: e => (this.extraElement = e) }));
49
- default:
50
- return '';
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 || !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("select", { class: "select-css", onChange: e => this.onFilterChange(e) }, this.renderConditions(this.changes.type)),
66
- h("div", null, this.renderExtra(this.filterEntities[this.changes.type].extra, this.changes.value)),
67
- h("div", { class: "center" },
68
- h(RevoButton, { class: { green: true }, onClick: () => this.onSave() }, capts.save),
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
- onFilterChange(e) {
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.changes = Object.assign(Object.assign({}, this.changes), { type });
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
- onInput(e) {
81
- this.changes.value = e.target.value;
82
- // prevent grid focus and other unexpected events
83
- e.preventDefault();
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
- this.onSave();
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
- onSave() {
96
- var _a, _b;
194
+ onReset() {
97
195
  this.assertChanges();
98
- this.filterChange.emit({
99
- prop: this.changes.prop,
100
- type: this.changes.type,
101
- value: (_b = (_a = this.extraElement) === null || _a === void 0 ? void 0 : _a.value) === null || _b === void 0 ? void 0 : _b.trim(),
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
- onReset() {
201
+ onRemoveFilter(id) {
106
202
  this.assertChanges();
107
- this.filterChange.emit({
108
- prop: this.changes.prop,
109
- type: "none",
110
- });
111
- this.changes = void 0;
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": "FilterItem",
249
- "resolved": "{ prop?: ColumnProp; type?: \"none\" | \"empty\" | \"notEmpty\" | \"eq\" | \"notEq\" | \"begins\" | \"contains\" | \"notContains\" | \"eqN\" | \"neqN\" | \"gt\" | \"gte\" | \"lt\" | \"lte\"; value?: any; }",
397
+ "original": "MultiFilterItem",
398
+ "resolved": "{ [prop: string]: FilterData[]; }",
250
399
  "references": {
251
- "FilterItem": {
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) {
@@ -157,6 +157,9 @@ export default class SortingPlugin extends BasePlugin {
157
157
  let sorted = 0;
158
158
  for (let prop in sortingFunc) {
159
159
  const cmp = sortingFunc[prop];
160
+ if (!cmp) {
161
+ continue;
162
+ }
160
163
  sorted = cmp(prop, a, b);
161
164
  if (sorted) {
162
165
  break;
@@ -90,7 +90,7 @@ export function generateFakeDataObject(config = {}) {
90
90
  }
91
91
  // apply config
92
92
  if (rgCol === order) {
93
- columns[rgCol].order = 'asc';
93
+ columns[rgCol].order = 'desc';
94
94
  }
95
95
  }
96
96
  // apply config