@revolist/revogrid 3.0.98 → 3.1.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 (56) hide show
  1. package/custom-element/index.js +519 -301
  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 +195 -645
  6. package/dist/cjs/revogr-filter-panel.cjs.entry.js +166 -33
  7. package/dist/collection/components/data/revogr-data-style.css +8 -0
  8. package/dist/collection/components/header/revogr-header-style.css +8 -0
  9. package/dist/collection/components/overlay/editors/text.js +1 -1
  10. package/dist/collection/components/overlay/revogr-edit-style.css +8 -0
  11. package/dist/collection/components/overlay/revogr-overlay-style.css +8 -0
  12. package/dist/collection/components/revo-grid/revo-grid-style.css +8 -0
  13. package/dist/collection/components/revo-grid/revo-grid.js +30 -3
  14. package/dist/collection/components/revo-grid/viewport.service.js +3 -0
  15. package/dist/collection/components/scroll/revogr-viewport-scroll-style.css +8 -0
  16. package/dist/collection/components/scrollable/revogr-scroll-style.css +8 -0
  17. package/dist/collection/components/selection-focus/revogr-focus-style.css +8 -0
  18. package/dist/collection/components/selection-temp-range/revogr-temp-range-style.css +8 -0
  19. package/dist/collection/plugins/filter/conditions/equal.js +5 -1
  20. package/dist/collection/plugins/filter/filter.button.js +10 -0
  21. package/dist/collection/plugins/filter/filter.plugin.js +103 -47
  22. package/dist/collection/plugins/filter/filter.pop.js +227 -38
  23. package/dist/collection/plugins/filter/filter.style.css +61 -1
  24. package/dist/collection/plugins/groupingColumn/grouping.col.plugin.js +1 -0
  25. package/dist/collection/services/selection.store.connector.js +4 -0
  26. package/dist/esm/debounce-8dadcda7.js +558 -0
  27. package/dist/esm/loader.js +1 -1
  28. package/dist/esm/revo-grid.js +1 -1
  29. package/dist/esm/revo-grid_11.entry.js +149 -599
  30. package/dist/esm/revogr-filter-panel.entry.js +165 -32
  31. package/dist/esm-es5/debounce-8dadcda7.js +1 -0
  32. package/dist/esm-es5/loader.js +1 -1
  33. package/dist/esm-es5/revo-grid.js +1 -1
  34. package/dist/esm-es5/revo-grid_11.entry.js +1 -1
  35. package/dist/esm-es5/revogr-filter-panel.entry.js +1 -1
  36. package/dist/revo-grid/debounce-d097578d.js +1 -0
  37. package/dist/revo-grid/debounce-f40a88f6.system.js +1 -0
  38. package/dist/revo-grid/revo-grid.esm.js +1 -1
  39. package/dist/revo-grid/revo-grid.system.js +1 -1
  40. package/dist/revo-grid/revo-grid_11.entry.js +1 -1
  41. package/dist/revo-grid/revo-grid_11.system.entry.js +1 -1
  42. package/dist/revo-grid/revogr-filter-panel.entry.js +1 -1
  43. package/dist/revo-grid/revogr-filter-panel.system.entry.js +1 -1
  44. package/dist/types/components/revo-grid/revo-grid.d.ts +5 -1
  45. package/dist/types/components/revo-grid/viewport.service.d.ts +1 -0
  46. package/dist/types/components.d.ts +12 -4
  47. package/dist/types/plugins/filter/filter.button.d.ts +4 -0
  48. package/dist/types/plugins/filter/filter.plugin.d.ts +17 -8
  49. package/dist/types/plugins/filter/filter.pop.d.ts +31 -7
  50. package/dist/types/services/selection.store.connector.d.ts +1 -0
  51. package/package.json +1 -1
  52. package/dist/cjs/filter.button-2396a488.js +0 -27
  53. package/dist/esm/filter.button-53ebca66.js +0 -23
  54. package/dist/esm-es5/filter.button-53ebca66.js +0 -1
  55. package/dist/revo-grid/filter.button-1509c206.js +0 -1
  56. package/dist/revo-grid/filter.button-4bd87101.system.js +0 -1
@@ -5,9 +5,11 @@ import { filterEntities, filterNames, filterTypes } from './filter.service';
5
5
  export const FILTER_TRIMMED_TYPE = 'filter';
6
6
  export default class FilterPlugin extends BasePlugin {
7
7
  constructor(revogrid, uiid, config) {
8
+ var _a;
8
9
  super(revogrid);
9
10
  this.revogrid = revogrid;
10
11
  this.filterCollection = {};
12
+ this.multiFilterItems = {};
11
13
  this.possibleFilters = Object.assign({}, filterTypes);
12
14
  this.possibleFilterNames = Object.assign({}, filterNames);
13
15
  this.possibleFilterEntities = Object.assign({}, filterEntities);
@@ -15,21 +17,38 @@ export default class FilterPlugin extends BasePlugin {
15
17
  this.initConfig(config);
16
18
  }
17
19
  const headerclick = (e) => this.headerclick(e);
18
- const aftersourceset = () => {
19
- if (Object.keys(this.filterCollection).length) {
20
- this.filterByProps(this.filterCollection);
20
+ const aftersourceset = async () => {
21
+ const filterCollectionProps = Object.keys(this.filterCollection);
22
+ if (filterCollectionProps.length > 0) {
23
+ // handle old way of filtering by reworking FilterCollection to new MultiFilterItem
24
+ filterCollectionProps.forEach((prop, index) => {
25
+ if (!this.multiFilterItems[prop]) {
26
+ this.multiFilterItems[prop] = [
27
+ {
28
+ id: index,
29
+ type: this.filterCollection[prop].type,
30
+ value: this.filterCollection[prop].value,
31
+ relation: 'and',
32
+ },
33
+ ];
34
+ }
35
+ });
21
36
  }
37
+ await this.runFiltering();
22
38
  };
23
39
  this.addEventListener('headerclick', headerclick);
24
40
  this.addEventListener('aftersourceset', aftersourceset);
25
41
  this.revogrid.registerVNode([
26
- h("revogr-filter-panel", { uuid: `filter-${uiid}`, filterNames: this.possibleFilterNames, filterEntities: this.possibleFilterEntities, onFilterChange: e => this.onFilterChange(e.detail), ref: e => (this.pop = e) }),
42
+ h("revogr-filter-panel", { uuid: `filter-${uiid}`, filterItems: this.multiFilterItems, filterNames: this.possibleFilterNames, filterEntities: this.possibleFilterEntities, filterCaptions: (_a = config === null || config === void 0 ? void 0 : config.localization) === null || _a === void 0 ? void 0 : _a.captions, onFilterChange: e => this.onFilterChange(e.detail), ref: e => (this.pop = e) }),
27
43
  ]);
28
44
  }
29
45
  initConfig(config) {
30
46
  if (config.collection) {
31
47
  this.filterCollection = Object.assign({}, config.collection);
32
48
  }
49
+ if (config.multiFilterItems) {
50
+ this.multiFilterItems = Object.assign({}, config.multiFilterItems);
51
+ }
33
52
  if (config.customFilters) {
34
53
  for (let cType in config.customFilters) {
35
54
  const cFilter = config.customFilters[cType];
@@ -59,6 +78,15 @@ export default class FilterPlugin extends BasePlugin {
59
78
  this.possibleFilters = filters;
60
79
  }
61
80
  }
81
+ if (config.localization) {
82
+ if (config.localization.filterNames) {
83
+ Object.entries(config.localization.filterNames).forEach(([k, v]) => {
84
+ if (this.possibleFilterNames[k] != void 0) {
85
+ this.possibleFilterNames[k] = v;
86
+ }
87
+ });
88
+ }
89
+ }
62
90
  }
63
91
  async headerclick(e) {
64
92
  var _a;
@@ -104,43 +132,18 @@ export default class FilterPlugin extends BasePlugin {
104
132
  return !!(typeof type === 'string' && this.possibleFilters[type]);
105
133
  }
106
134
  // called on internal component change
107
- async onFilterChange(filterItem) {
108
- this.filterByProps({ [filterItem.prop]: filterItem });
109
- }
110
- /**
111
- * Apply filters collection to extend existing one or override
112
- * @method
113
- * @param conditions - list of filters to apply
114
- */
115
- async filterByProps(conditions, override = false) {
116
- if (override) {
117
- this.filterCollection = {};
118
- }
119
- for (const prop in conditions) {
120
- const { type, value } = conditions[prop];
121
- if (type === 'none') {
122
- delete this.filterCollection[prop];
123
- }
124
- else {
125
- const filter = this.possibleFilterEntities[type];
126
- this.filterCollection[prop] = {
127
- filter,
128
- value,
129
- type,
130
- };
131
- }
132
- }
133
- await this.runFiltering();
135
+ async onFilterChange(filterItems) {
136
+ this.multiFilterItems = filterItems;
137
+ this.runFiltering();
134
138
  }
135
139
  /**
136
140
  * Triggers grid filtering
137
141
  */
138
- async doFiltering(collection, items, columns) {
142
+ async doFiltering(collection, items, columns, filterItems) {
139
143
  const columnsToUpdate = [];
140
- // todo improvement: loop through collection of props
141
144
  columns.forEach(rgCol => {
142
145
  const column = Object.assign({}, rgCol);
143
- const hasFilter = collection[column.prop];
146
+ const hasFilter = filterItems[column.prop];
144
147
  if (column[FILTER_PROP] && !hasFilter) {
145
148
  delete column[FILTER_PROP];
146
149
  columnsToUpdate.push(column);
@@ -150,9 +153,9 @@ export default class FilterPlugin extends BasePlugin {
150
153
  column[FILTER_PROP] = true;
151
154
  }
152
155
  });
153
- const itemsToFilter = this.getRowFilter(items, collection);
156
+ const itemsToFilter = this.getRowFilter(items, filterItems);
154
157
  // check is filter event prevented
155
- const { defaultPrevented, detail } = this.emit('beforefiltertrimmed', { collection, itemsToFilter, source: items });
158
+ const { defaultPrevented, detail } = this.emit('beforefiltertrimmed', { collection, itemsToFilter, source: items, filterItems });
156
159
  if (defaultPrevented) {
157
160
  return;
158
161
  }
@@ -161,39 +164,92 @@ export default class FilterPlugin extends BasePlugin {
161
164
  if (isAddedEvent.defaultPrevented) {
162
165
  return;
163
166
  }
167
+ // applies the hasFilter to the columns to show filter icon
164
168
  await this.revogrid.updateColumns(columnsToUpdate);
165
169
  this.emit('afterFilterApply');
166
170
  }
167
171
  async clearFiltering() {
168
- this.filterCollection = {};
172
+ this.multiFilterItems = {};
169
173
  await this.runFiltering();
170
174
  }
171
175
  async runFiltering() {
176
+ const collection = {};
177
+ // handle old filterCollection to return the first filter only (if any) from multiFilterItems
178
+ const filterProps = Object.keys(this.multiFilterItems);
179
+ for (const prop of filterProps) {
180
+ // check if we have any filter for a column
181
+ if (this.multiFilterItems[prop].length > 0) {
182
+ const firstFilterItem = this.multiFilterItems[prop][0];
183
+ collection[prop] = {
184
+ filter: filterEntities[firstFilterItem.type],
185
+ type: firstFilterItem.type,
186
+ value: firstFilterItem.value,
187
+ };
188
+ }
189
+ }
190
+ this.filterCollection = collection;
172
191
  const { source, columns } = await this.getData();
173
- const { defaultPrevented, detail } = this.emit('beforefilterapply', { collection: this.filterCollection, source, columns });
192
+ const { defaultPrevented, detail } = this.emit('beforefilterapply', { collection: this.filterCollection, source, columns, filterItems: this.multiFilterItems });
174
193
  if (defaultPrevented) {
175
194
  return;
176
195
  }
177
- this.doFiltering(detail.collection, detail.source, detail.columns);
196
+ this.doFiltering(detail.collection, detail.source, detail.columns, detail.filterItems);
178
197
  }
179
198
  async getData() {
180
199
  const source = await this.revogrid.getSource();
181
200
  const columns = await this.revogrid.getColumns();
182
201
  return {
183
202
  source,
184
- columns
203
+ columns,
185
204
  };
186
205
  }
187
- getRowFilter(rows, collection) {
206
+ getRowFilter(rows, filterItems) {
207
+ const propKeys = Object.keys(filterItems);
188
208
  const trimmed = {};
209
+ let propFilterSatisfiedCount = 0;
210
+ let lastFilterResults = [];
211
+ // each rows
189
212
  rows.forEach((model, rowIndex) => {
190
- for (const prop in collection) {
191
- const filterItem = collection[prop];
192
- const filter = filterItem.filter;
193
- if (!filter(model[prop], filterItem.value)) {
213
+ // working on all props
214
+ for (const prop of propKeys) {
215
+ const propFilters = filterItems[prop];
216
+ propFilterSatisfiedCount = 0;
217
+ lastFilterResults = [];
218
+ // testing each filter for a prop
219
+ for (const [filterIndex, filterData] of propFilters.entries()) {
220
+ // the filter LogicFunction based on the type
221
+ const filter = filterEntities[filterData.type];
222
+ // THE MAGIC OF FILTERING IS HERE
223
+ if (filterData.relation === 'or') {
224
+ lastFilterResults = [];
225
+ if (filter(model[prop], filterData.value)) {
226
+ continue;
227
+ }
228
+ propFilterSatisfiedCount++;
229
+ }
230
+ else {
231
+ // 'and' relation will need to know the next filter
232
+ // so we save this current filter to include it in the next filter
233
+ lastFilterResults.push(!filter(model[prop], filterData.value));
234
+ // check first if we have a filter on the next index to pair it with this current filter
235
+ const nextFilterData = propFilters[filterIndex + 1];
236
+ // stop the sequence if there is no next filter or if the next filter is not an 'and' relation
237
+ if (!nextFilterData || nextFilterData.relation !== 'and') {
238
+ // let's just continue since for sure propFilterSatisfiedCount cannot be satisfied
239
+ if (lastFilterResults.indexOf(true) === -1) {
240
+ lastFilterResults = [];
241
+ continue;
242
+ }
243
+ // we need to add all of the lastFilterResults since we need to satisfy all
244
+ propFilterSatisfiedCount += lastFilterResults.length;
245
+ lastFilterResults = [];
246
+ }
247
+ }
248
+ } // end of propFilters forEach
249
+ // add to the list of removed/trimmed rows of filter condition is satisfied
250
+ if (propFilterSatisfiedCount === propFilters.length)
194
251
  trimmed[rowIndex] = true;
195
- }
196
- }
252
+ } // end of for-of propKeys
197
253
  });
198
254
  return trimmed;
199
255
  }
@@ -1,13 +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() {
11
+ this.filterCaptionsInternal = {
12
+ title: 'Filter by condition',
13
+ save: 'Save',
14
+ reset: 'Reset',
15
+ cancel: 'Close',
16
+ };
17
+ this.isFilterIdSet = false;
18
+ this.filterId = 0;
19
+ this.currentFilterId = -1;
20
+ this.currentFilterType = defaultType;
21
+ this.filterItems = {};
8
22
  this.filterTypes = {};
9
23
  this.filterNames = {};
10
24
  this.filterEntities = {};
25
+ this.debouncedApplyFilter = debounce(() => {
26
+ this.filterChange.emit(this.filterItems);
27
+ }, 400);
11
28
  }
12
29
  onMouseDown(e) {
13
30
  if (this.changes && !e.defaultPrevented) {
@@ -26,26 +43,63 @@ export class FilterPanel {
26
43
  async getChanges() {
27
44
  return this.changes;
28
45
  }
29
- 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;
30
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
+ }
31
63
  for (let gIndex in this.filterTypes) {
32
- options.push(h("option", { value: defaultType }, this.filterNames[defaultType]));
33
64
  options.push(...this.filterTypes[gIndex].map(k => (h("option", { value: k, selected: type === k }, this.filterNames[k]))));
34
65
  options.push(h("option", { disabled: true }));
35
66
  }
36
67
  return options;
37
68
  }
38
- renderExtra(extra, value) {
39
- this.extraElement = undefined;
40
- switch (extra) {
41
- case 'input':
42
- return (h("input", { type: "text", value: value, onInput: (e) => this.onInput(e), onKeyDown: e => this.onKeyDown(e), ref: e => (this.extraElement = e) }));
43
- default:
44
- return '';
45
- }
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" }) : ''));
46
100
  }
47
101
  render() {
48
- if (!this.changes || !this.changes) {
102
+ if (!this.changes) {
49
103
  return h(Host, { style: { display: 'none' } });
50
104
  }
51
105
  const style = {
@@ -53,29 +107,83 @@ export class FilterPanel {
53
107
  left: `${this.changes.x}px`,
54
108
  top: `${this.changes.y}px`,
55
109
  };
110
+ const capts = Object.assign(this.filterCaptionsInternal, this.filterCaptions);
56
111
  return (h(Host, { style: style },
57
- h("label", null, "Filter by condition"),
58
- h("select", { class: "select-css", onChange: e => this.onFilterChange(e) }, this.renderConditions(this.changes.type)),
59
- h("div", null, this.renderExtra(this.filterEntities[this.changes.type].extra, this.changes.value)),
60
- h(RevoButton, { class: { green: true }, onClick: () => this.onSave() }, "Save"),
61
- h(RevoButton, { class: { light: true }, onClick: () => this.onCancel() }, "Cancel")));
112
+ h("label", null, capts.title),
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" },
117
+ h(RevoButton, { class: { red: true }, onClick: () => this.onReset() }, capts.reset),
118
+ h(RevoButton, { class: { light: true }, onClick: () => this.onCancel() }, capts.cancel))));
62
119
  }
63
- onFilterChange(e) {
64
- if (!this.changes) {
65
- throw new Error('Changes required per edit');
66
- }
120
+ onFilterTypeChange(e, prop, index) {
121
+ const el = e.target;
122
+ const type = el.value;
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();
133
+ }
134
+ onAddNewFilter(e) {
67
135
  const el = e.target;
68
136
  const type = el.value;
69
- this.changes = Object.assign(Object.assign({}, this.changes), { type });
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();
70
146
  }
71
- onInput(e) {
72
- this.changes.value = e.target.value;
73
- // prevent grid focus and other unexpected events
74
- e.preventDefault();
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();
75
176
  }
76
177
  onKeyDown(e) {
77
178
  if (e.key.toLowerCase() === 'enter') {
78
- 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;
79
187
  }
80
188
  // keep event local, don't escalate farther to dom
81
189
  e.stopPropagation();
@@ -83,19 +191,56 @@ export class FilterPanel {
83
191
  onCancel() {
84
192
  this.changes = undefined;
85
193
  }
86
- onSave() {
87
- var _a, _b;
194
+ onReset() {
195
+ this.assertChanges();
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);
200
+ }
201
+ onRemoveFilter(id) {
202
+ this.assertChanges();
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();
231
+ }
232
+ assertChanges() {
88
233
  if (!this.changes) {
89
234
  throw new Error('Changes required per edit');
90
235
  }
91
- this.filterChange.emit({
92
- prop: this.changes.prop,
93
- type: this.changes.type,
94
- value: (_b = (_a = this.extraElement) === null || _a === void 0 ? void 0 : _a.value) === null || _b === void 0 ? void 0 : _b.trim(),
95
- });
96
- this.changes = undefined;
97
236
  }
98
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;
99
244
  if (e.classList.contains(`[uuid="${this.uuid}"]`)) {
100
245
  return false;
101
246
  }
@@ -126,6 +271,26 @@ export class FilterPanel {
126
271
  "attribute": "uuid",
127
272
  "reflect": true
128
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
+ },
129
294
  "filterTypes": {
130
295
  "type": "unknown",
131
296
  "mutable": false,
@@ -189,9 +354,33 @@ export class FilterPanel {
189
354
  "text": ""
190
355
  },
191
356
  "defaultValue": "{}"
357
+ },
358
+ "filterCaptions": {
359
+ "type": "unknown",
360
+ "mutable": false,
361
+ "complexType": {
362
+ "original": "FilterCaptions | undefined",
363
+ "resolved": "{ title: string; save: string; reset: string; cancel: string; }",
364
+ "references": {
365
+ "FilterCaptions": {
366
+ "location": "import",
367
+ "path": "./filter.plugin"
368
+ }
369
+ }
370
+ },
371
+ "required": false,
372
+ "optional": false,
373
+ "docs": {
374
+ "tags": [],
375
+ "text": ""
376
+ }
192
377
  }
193
378
  }; }
194
379
  static get states() { return {
380
+ "isFilterIdSet": {},
381
+ "filterId": {},
382
+ "currentFilterId": {},
383
+ "currentFilterType": {},
195
384
  "changes": {}
196
385
  }; }
197
386
  static get events() { return [{
@@ -205,10 +394,10 @@ export class FilterPanel {
205
394
  "text": ""
206
395
  },
207
396
  "complexType": {
208
- "original": "FilterItem",
209
- "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[]; }",
210
399
  "references": {
211
- "FilterItem": {
400
+ "MultiFilterItem": {
212
401
  "location": "local"
213
402
  }
214
403
  }
@@ -52,6 +52,14 @@
52
52
  background-color: #2ee072;
53
53
  border: 1px solid #20d565;
54
54
  }
55
+ .revo-button.red {
56
+ background-color: #E0662E;
57
+ border: 1px solid #d55920;
58
+ }
59
+ .revo-button:disabled, .revo-button[disabled] {
60
+ cursor: not-allowed !important;
61
+ filter: opacity(0.35) !important;
62
+ }
55
63
  .revo-button.light {
56
64
  border: 2px solid #cedefa;
57
65
  line-height: 32px;
@@ -73,9 +81,13 @@ revogr-filter-panel {
73
81
  box-shadow: 0 5px 18px -2px rgba(0, 0, 0, 0.2);
74
82
  padding: 10px;
75
83
  border-radius: 4px;
76
- min-width: 150px;
84
+ min-width: 220px;
77
85
  text-align: left;
78
86
  }
87
+ revogr-filter-panel .filter-holder > div {
88
+ display: flex;
89
+ flex-direction: column;
90
+ }
79
91
  revogr-filter-panel label {
80
92
  color: gray;
81
93
  font-size: 13px;
@@ -94,11 +106,16 @@ revogr-filter-panel input[type=text] {
94
106
  border-radius: 5px;
95
107
  padding: 0 10px;
96
108
  box-sizing: border-box;
109
+ width: 100%;
97
110
  }
98
111
  revogr-filter-panel button {
99
112
  margin-top: 10px;
100
113
  margin-right: 5px;
101
114
  }
115
+ revogr-filter-panel .filter-actions {
116
+ text-align: right;
117
+ margin-right: -5px;
118
+ }
102
119
 
103
120
  .rgHeaderCell:hover .rv-filter {
104
121
  transition: opacity 267ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, transform 178ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
@@ -176,4 +193,47 @@ revogr-filter-panel button {
176
193
  }
177
194
  .select-css:disabled:hover, .select-css[aria-disabled=true] {
178
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;
179
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) {