@xh/hoist 80.0.0-SNAPSHOT.1767973504648 → 80.0.0-SNAPSHOT.1767982629403
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/CHANGELOG.md +10 -9
- package/build/types/cmp/filter/FilterChooserFieldSpec.d.ts +1 -1
- package/build/types/cmp/filter/FilterChooserModel.d.ts +19 -10
- package/build/types/cmp/grid/GridModel.d.ts +2 -2
- package/build/types/cmp/grid/filter/GridFilterFieldSpec.d.ts +4 -3
- package/build/types/cmp/grid/filter/GridFilterModel.d.ts +1 -1
- package/build/types/cmp/layout/Tags.d.ts +0 -2
- package/build/types/cmp/tab/TabContainerModel.d.ts +1 -1
- package/build/types/data/Store.d.ts +5 -6
- package/build/types/data/cube/View.d.ts +5 -3
- package/build/types/data/filter/BaseFilterFieldSpec.d.ts +3 -3
- package/build/types/data/filter/CompoundFilter.d.ts +1 -1
- package/build/types/data/filter/FieldFilter.d.ts +1 -1
- package/build/types/data/filter/Filter.d.ts +3 -3
- package/build/types/data/filter/FunctionFilter.d.ts +1 -1
- package/build/types/data/filter/Types.d.ts +39 -13
- package/build/types/desktop/cmp/dash/canvas/DashCanvasModel.d.ts +6 -46
- package/build/types/desktop/cmp/grid/impl/filter/headerfilter/values/ValuesTabModel.d.ts +2 -2
- package/build/types/svc/InspectorService.d.ts +2 -2
- package/cmp/filter/FilterChooserFieldSpec.ts +5 -22
- package/cmp/filter/FilterChooserModel.ts +27 -12
- package/cmp/grid/GridModel.ts +2 -2
- package/cmp/grid/filter/GridFilterFieldSpec.ts +52 -51
- package/cmp/grid/filter/GridFilterModel.ts +6 -7
- package/cmp/layout/Tags.ts +0 -2
- package/cmp/tab/TabContainerModel.ts +1 -1
- package/data/Store.ts +47 -13
- package/data/cube/View.ts +14 -3
- package/data/filter/BaseFilterFieldSpec.ts +3 -3
- package/data/filter/CompoundFilter.ts +3 -3
- package/data/filter/FieldFilter.ts +2 -2
- package/data/filter/Filter.ts +4 -4
- package/data/filter/FunctionFilter.ts +2 -2
- package/data/filter/Types.ts +53 -20
- package/desktop/cmp/dash/canvas/DashCanvas.ts +4 -21
- package/desktop/cmp/dash/canvas/DashCanvasModel.ts +24 -140
- package/desktop/cmp/grid/impl/filter/headerfilter/values/ValuesTabModel.ts +9 -9
- package/package.json +3 -3
- package/svc/InspectorService.ts +6 -5
- package/tsconfig.tsbuildinfo +1 -1
- package/build/types/desktop/cmp/button/FieldsetCollapseButton.d.ts +0 -10
- package/build/types/desktop/cmp/dash/canvas/widgetwell/DashCanvasWidgetWell.d.ts +0 -17
- package/build/types/desktop/cmp/dash/canvas/widgetwell/DashCanvasWidgetWellModel.d.ts +0 -11
- package/build/types/desktop/cmp/form/CollapsibleFieldset.d.ts +0 -11
- package/desktop/cmp/button/FieldsetCollapseButton.ts +0 -44
- package/desktop/cmp/dash/canvas/widgetwell/DashCanvasWidgetWell.scss +0 -26
- package/desktop/cmp/dash/canvas/widgetwell/DashCanvasWidgetWell.ts +0 -126
- package/desktop/cmp/dash/canvas/widgetwell/DashCanvasWidgetWellModel.ts +0 -65
- package/desktop/cmp/form/CollapsibleFieldset.scss +0 -14
- package/desktop/cmp/form/CollapsibleFieldset.ts +0 -65
package/cmp/grid/GridModel.ts
CHANGED
|
@@ -1442,9 +1442,9 @@ export class GridModel extends HoistModel {
|
|
|
1442
1442
|
|
|
1443
1443
|
/**
|
|
1444
1444
|
* Begin an inline editing session.
|
|
1445
|
-
* @param
|
|
1445
|
+
* @param record - StoreRecord/ID to edit. If unspecified, the first selected StoreRecord
|
|
1446
1446
|
* will be used, if any, or the first overall StoreRecord in the grid.
|
|
1447
|
-
* @param
|
|
1447
|
+
* @param colId - ID of column on which to start editing. If unspecified, the first
|
|
1448
1448
|
* editable column will be used.
|
|
1449
1449
|
*/
|
|
1450
1450
|
async beginEditAsync(opts: {record?: StoreRecordOrId; colId?: string} = {}) {
|
|
@@ -7,12 +7,18 @@
|
|
|
7
7
|
import {ColumnRenderer} from '@xh/hoist/cmp/grid';
|
|
8
8
|
import {HoistInputProps} from '@xh/hoist/cmp/input';
|
|
9
9
|
import {PlainObject} from '@xh/hoist/core';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
CompoundFilter,
|
|
12
|
+
FieldFilter,
|
|
13
|
+
FieldFilterOperator,
|
|
14
|
+
Filter,
|
|
15
|
+
parseFilter
|
|
16
|
+
} from '@xh/hoist/data';
|
|
11
17
|
import {
|
|
12
18
|
BaseFilterFieldSpec,
|
|
13
19
|
BaseFilterFieldSpecConfig
|
|
14
20
|
} from '@xh/hoist/data/filter/BaseFilterFieldSpec';
|
|
15
|
-
import {castArray, compact,
|
|
21
|
+
import {castArray, compact, flatMap, isDate, isEmpty, uniqBy} from 'lodash';
|
|
16
22
|
import {GridFilterModel} from './GridFilterModel';
|
|
17
23
|
|
|
18
24
|
export interface GridFilterFieldSpecConfig extends BaseFilterFieldSpecConfig {
|
|
@@ -37,14 +43,16 @@ export interface GridFilterFieldSpecConfig extends BaseFilterFieldSpecConfig {
|
|
|
37
43
|
|
|
38
44
|
/**
|
|
39
45
|
* Apps should NOT instantiate this class directly.
|
|
40
|
-
* Instead, provide a config for this object
|
|
46
|
+
* Instead, provide a config for this object via {@link GridConfig.filterModel} config.
|
|
41
47
|
*/
|
|
42
48
|
export class GridFilterFieldSpec extends BaseFilterFieldSpec {
|
|
43
49
|
filterModel: GridFilterModel;
|
|
44
50
|
renderer: ColumnRenderer;
|
|
45
51
|
inputProps: PlainObject;
|
|
46
52
|
defaultOp: FieldFilterOperator;
|
|
47
|
-
|
|
53
|
+
|
|
54
|
+
/** Total number of unique values for this field in the source, regardless of other filters. */
|
|
55
|
+
allValuesCount: number;
|
|
48
56
|
|
|
49
57
|
constructor({
|
|
50
58
|
filterModel,
|
|
@@ -71,68 +79,61 @@ export class GridFilterFieldSpec extends BaseFilterFieldSpec {
|
|
|
71
79
|
//------------------------
|
|
72
80
|
loadValuesFromSource() {
|
|
73
81
|
const {filterModel, field, source, sourceField} = this,
|
|
74
|
-
columnFilters = filterModel.getColumnFilters(field)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
//
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
columnFilters = filterModel.getColumnFilters(field);
|
|
83
|
+
|
|
84
|
+
// All possible values for this field from our source.
|
|
85
|
+
const allSrcVals = source.getValuesForFieldFilter(field).map(it => this.toDisplayValue(it));
|
|
86
|
+
|
|
87
|
+
// Values from current column filter.
|
|
88
|
+
const colFilterVals = flatMap(columnFilters, filter => {
|
|
89
|
+
return castArray(filter.value).map(val => sourceField.parseVal(val));
|
|
90
|
+
}).map(it => this.toDisplayValue(it));
|
|
91
|
+
|
|
92
|
+
// Combine + unique - these are all values that *could* be shown in the filter UI.
|
|
93
|
+
const allValues = uniqBy([...allSrcVals, ...colFilterVals], this.getUniqueValue);
|
|
86
94
|
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
this.getUniqueValue
|
|
101
|
-
);
|
|
102
|
-
let values;
|
|
103
|
-
if (cleanedFilter) {
|
|
104
|
-
values = uniqBy(
|
|
105
|
-
[
|
|
106
|
-
...flatten(filteredRecords.map(rec => this.valueFromRecord(rec))),
|
|
107
|
-
...filterValues
|
|
108
|
-
],
|
|
109
|
-
this.getUniqueValue
|
|
110
|
-
);
|
|
95
|
+
// Create a filter with other filters *not* pertaining to this field, if any.
|
|
96
|
+
// This filter will be used to get the subset of all possible values for this field that
|
|
97
|
+
// exist in records passing those other filters.
|
|
98
|
+
const otherFieldsFilter = this.cleanFilter(filterModel.filter);
|
|
99
|
+
|
|
100
|
+
let values: any[];
|
|
101
|
+
|
|
102
|
+
if (otherFieldsFilter) {
|
|
103
|
+
// If we have filters on other fields, get values from source that pass that filter.
|
|
104
|
+
// These will be the set of values shown in the filter UI.
|
|
105
|
+
const filteredSrcVals = source
|
|
106
|
+
.getValuesForFieldFilter(field, otherFieldsFilter)
|
|
107
|
+
.map(it => this.toDisplayValue(it));
|
|
108
|
+
values = uniqBy([...filteredSrcVals, ...colFilterVals], this.getUniqueValue);
|
|
111
109
|
} else {
|
|
110
|
+
// Otherwise, all possible values are shown.
|
|
112
111
|
values = allValues;
|
|
113
112
|
}
|
|
114
113
|
|
|
115
114
|
this.values = values.sort();
|
|
116
|
-
|
|
115
|
+
|
|
116
|
+
// Note count of all possible values for this field - allows ValuesTabModel
|
|
117
|
+
// to indicate to the user if some values are hidden due to other active filters.
|
|
118
|
+
this.allValuesCount = allValues.length;
|
|
117
119
|
}
|
|
118
120
|
|
|
119
121
|
// Recursively modify a Filter|CompoundFilter to remove all FieldFilters referencing this column
|
|
120
|
-
private cleanFilter(filter) {
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
const {field, filters, op} = filter;
|
|
124
|
-
if (filters) {
|
|
122
|
+
private cleanFilter(filter: Filter): Filter {
|
|
123
|
+
if (CompoundFilter.isCompoundFilter(filter)) {
|
|
124
|
+
const {filters, op} = filter;
|
|
125
125
|
const ret = compact(filters.map(it => this.cleanFilter(it)));
|
|
126
|
-
return !isEmpty(ret) ? {op, filters: ret} : null;
|
|
127
|
-
}
|
|
126
|
+
return !isEmpty(ret) ? parseFilter({op, filters: ret}) : null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (FieldFilter.isFieldFilter(filter) && filter.field === this.field) {
|
|
128
130
|
return null;
|
|
129
131
|
}
|
|
130
132
|
|
|
131
133
|
return filter;
|
|
132
134
|
}
|
|
133
135
|
|
|
134
|
-
private
|
|
135
|
-
|
|
136
|
-
return filterModel.toDisplayValue(record.get(field));
|
|
136
|
+
private toDisplayValue(val: any): any {
|
|
137
|
+
return this.filterModel.toDisplayValue(val);
|
|
137
138
|
}
|
|
138
139
|
}
|
|
@@ -117,11 +117,11 @@ export class GridFilterModel extends HoistModel {
|
|
|
117
117
|
return this.fieldSpecs.find(it => it.field === field);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
toDisplayValue(value) {
|
|
120
|
+
toDisplayValue(value: any): any {
|
|
121
121
|
return isNil(value) || value === '' ? GridFilterModel.BLANK_PLACEHOLDER : value;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
fromDisplayValue(value) {
|
|
124
|
+
fromDisplayValue(value: any): any {
|
|
125
125
|
return value === GridFilterModel.BLANK_PLACEHOLDER ? null : value;
|
|
126
126
|
}
|
|
127
127
|
|
|
@@ -135,7 +135,7 @@ export class GridFilterModel extends HoistModel {
|
|
|
135
135
|
this.dialogOpen = false;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
setFilter(filter) {
|
|
138
|
+
setFilter(filter: Filter) {
|
|
139
139
|
wait()
|
|
140
140
|
.then(() => this.bind.setFilter(filter))
|
|
141
141
|
.linkTo(this.gridModel.filterTask);
|
|
@@ -161,11 +161,10 @@ export class GridFilterModel extends HoistModel {
|
|
|
161
161
|
});
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
private getOuterCompoundFilter(filter, field) {
|
|
165
|
-
if (!filter
|
|
164
|
+
private getOuterCompoundFilter(filter: Filter, field: string) {
|
|
165
|
+
if (!CompoundFilter.isCompoundFilter(filter)) return null;
|
|
166
166
|
|
|
167
|
-
// This is the outer compound filter if all
|
|
168
|
-
// are FieldFilters on the matching field.
|
|
167
|
+
// This is the outer compound filter if all children are FieldFilters on the matching field.
|
|
169
168
|
if (every(filter.filters, {field})) {
|
|
170
169
|
return filter;
|
|
171
170
|
}
|
package/cmp/layout/Tags.ts
CHANGED
|
@@ -29,7 +29,6 @@ export const a = elementFactory('a');
|
|
|
29
29
|
export const br = elementFactory('br');
|
|
30
30
|
export const code = elementFactory('code');
|
|
31
31
|
export const div = elementFactory('div');
|
|
32
|
-
export const fieldset = elementFactory('fieldset');
|
|
33
32
|
export const form = elementFactory('form');
|
|
34
33
|
export const hr = elementFactory('hr');
|
|
35
34
|
export const h1 = elementFactory('h1');
|
|
@@ -37,7 +36,6 @@ export const h2 = elementFactory('h2');
|
|
|
37
36
|
export const h3 = elementFactory('h3');
|
|
38
37
|
export const h4 = elementFactory('h4');
|
|
39
38
|
export const label = elementFactory('label');
|
|
40
|
-
export const legend = elementFactory('legend');
|
|
41
39
|
export const li = elementFactory('li');
|
|
42
40
|
export const nav = elementFactory('nav');
|
|
43
41
|
export const ol = elementFactory('ol');
|
|
@@ -119,7 +119,7 @@ export class TabContainerModel extends HoistModel {
|
|
|
119
119
|
|
|
120
120
|
/**
|
|
121
121
|
* @param config - TabContainer configuration.
|
|
122
|
-
* @param depth - Depth in hierarchy of nested TabContainerModels. Not for application use.
|
|
122
|
+
* @param [depth] - Depth in hierarchy of nested TabContainerModels. Not for application use.
|
|
123
123
|
*/
|
|
124
124
|
constructor(
|
|
125
125
|
{
|
package/data/Store.ts
CHANGED
|
@@ -6,6 +6,18 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {HoistBase, managed, PlainObject, Some, XH} from '@xh/hoist/core';
|
|
9
|
+
import {
|
|
10
|
+
Field,
|
|
11
|
+
FieldSpec,
|
|
12
|
+
Filter,
|
|
13
|
+
FilterBindTarget,
|
|
14
|
+
FilterLike,
|
|
15
|
+
FilterValueSource,
|
|
16
|
+
parseFilter,
|
|
17
|
+
StoreRecord,
|
|
18
|
+
StoreRecordId,
|
|
19
|
+
StoreRecordOrId
|
|
20
|
+
} from '@xh/hoist/data';
|
|
9
21
|
import {action, computed, makeObservable, observable} from '@xh/hoist/mobx';
|
|
10
22
|
import {logWithDebug, throwIf, warnIf} from '@xh/hoist/utils/js';
|
|
11
23
|
import equal from 'fast-deep-equal';
|
|
@@ -13,6 +25,7 @@ import {
|
|
|
13
25
|
castArray,
|
|
14
26
|
defaultsDeep,
|
|
15
27
|
differenceBy,
|
|
28
|
+
first,
|
|
16
29
|
flatMapDeep,
|
|
17
30
|
isArray,
|
|
18
31
|
isEmpty,
|
|
@@ -20,21 +33,15 @@ import {
|
|
|
20
33
|
isNil,
|
|
21
34
|
isNull,
|
|
22
35
|
isString,
|
|
23
|
-
|
|
36
|
+
partition,
|
|
24
37
|
remove as lodashRemove,
|
|
25
|
-
uniq,
|
|
26
|
-
first,
|
|
27
38
|
some,
|
|
28
|
-
|
|
39
|
+
uniq,
|
|
40
|
+
values
|
|
29
41
|
} from 'lodash';
|
|
30
|
-
import {
|
|
31
|
-
import {parseFilter} from './filter/Utils';
|
|
42
|
+
import {instanceManager} from '../core/impl/InstanceManager';
|
|
32
43
|
import {RecordSet} from './impl/RecordSet';
|
|
33
44
|
import {StoreErrorMap, StoreValidator} from './impl/StoreValidator';
|
|
34
|
-
import {StoreRecord, StoreRecordId, StoreRecordOrId} from './StoreRecord';
|
|
35
|
-
import {instanceManager} from '../core/impl/InstanceManager';
|
|
36
|
-
import {Filter} from './filter/Filter';
|
|
37
|
-
import {FilterLike} from './filter/Types';
|
|
38
45
|
|
|
39
46
|
export interface StoreConfig {
|
|
40
47
|
/** Field names, configs, or instances. */
|
|
@@ -181,11 +188,13 @@ export type StoreRecordIdSpec = string | ((data: PlainObject) => StoreRecordId);
|
|
|
181
188
|
/**
|
|
182
189
|
* A managed and observable set of local, in-memory Records.
|
|
183
190
|
*/
|
|
184
|
-
export class Store extends HoistBase {
|
|
185
|
-
|
|
186
|
-
return
|
|
191
|
+
export class Store extends HoistBase implements FilterBindTarget, FilterValueSource {
|
|
192
|
+
static isStore(obj: unknown): obj is Store {
|
|
193
|
+
return obj instanceof Store;
|
|
187
194
|
}
|
|
188
195
|
|
|
196
|
+
readonly isFilterValueSource = true;
|
|
197
|
+
|
|
189
198
|
fields: Field[] = null;
|
|
190
199
|
idSpec: (data: PlainObject) => StoreRecordId;
|
|
191
200
|
processRawData: (raw: any) => any;
|
|
@@ -811,6 +820,31 @@ export class Store extends HoistBase {
|
|
|
811
820
|
return !this.getById(id, true) && !!this.getById(id, false);
|
|
812
821
|
}
|
|
813
822
|
|
|
823
|
+
getValuesForFieldFilter(fieldName: string, filter?: Filter): any[] {
|
|
824
|
+
const field = this.getField(fieldName);
|
|
825
|
+
if (!field) return [];
|
|
826
|
+
|
|
827
|
+
let recs = this.allRecords;
|
|
828
|
+
if (filter) {
|
|
829
|
+
const testFn = filter.getTestFn(this);
|
|
830
|
+
recs = recs.filter(testFn);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
const ret = new Set();
|
|
834
|
+
recs.forEach(rec => {
|
|
835
|
+
const val = rec.get(fieldName);
|
|
836
|
+
if (!isNil(val)) {
|
|
837
|
+
if (field.type === 'tags') {
|
|
838
|
+
val.forEach(it => ret.add(it));
|
|
839
|
+
} else {
|
|
840
|
+
ret.add(val);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
return Array.from(ret);
|
|
846
|
+
}
|
|
847
|
+
|
|
814
848
|
/**
|
|
815
849
|
* Set whether the root should be loaded as summary data in loadData().
|
|
816
850
|
*/
|
package/data/cube/View.ts
CHANGED
|
@@ -10,7 +10,9 @@ import {
|
|
|
10
10
|
Cube,
|
|
11
11
|
CubeField,
|
|
12
12
|
Filter,
|
|
13
|
+
FilterBindTarget,
|
|
13
14
|
FilterLike,
|
|
15
|
+
FilterValueSource,
|
|
14
16
|
Query,
|
|
15
17
|
QueryConfig,
|
|
16
18
|
Store,
|
|
@@ -64,11 +66,13 @@ export interface DimensionValue {
|
|
|
64
66
|
* Primary interface for consuming grouped and aggregated data from the cube.
|
|
65
67
|
* Applications should create via the {@link Cube.createView} factory.
|
|
66
68
|
*/
|
|
67
|
-
export class View extends HoistBase {
|
|
68
|
-
|
|
69
|
-
return
|
|
69
|
+
export class View extends HoistBase implements FilterBindTarget, FilterValueSource {
|
|
70
|
+
static isView(obj: unknown): obj is View {
|
|
71
|
+
return obj instanceof View;
|
|
70
72
|
}
|
|
71
73
|
|
|
74
|
+
readonly isFilterValueSource = true;
|
|
75
|
+
|
|
72
76
|
/** Query defining this View. Update via {@link updateQuery}. */
|
|
73
77
|
@observable.ref
|
|
74
78
|
query: Query = null;
|
|
@@ -222,6 +226,13 @@ export class View extends HoistBase {
|
|
|
222
226
|
}
|
|
223
227
|
}
|
|
224
228
|
|
|
229
|
+
//----------------------------
|
|
230
|
+
// FilterValueSource interface
|
|
231
|
+
//----------------------------
|
|
232
|
+
getValuesForFieldFilter(fieldName: string, filter?: Filter): any[] {
|
|
233
|
+
return this.cube.store.getValuesForFieldFilter(fieldName, filter);
|
|
234
|
+
}
|
|
235
|
+
|
|
225
236
|
//------------------------
|
|
226
237
|
// Implementation
|
|
227
238
|
//------------------------
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Copyright © 2026 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
import {HoistBase} from '@xh/hoist/core';
|
|
8
|
-
import {Field,
|
|
8
|
+
import {Field, FieldFilter, FieldType, FilterValueSource, genDisplayName} from '@xh/hoist/data';
|
|
9
9
|
import {compact, isArray, isEmpty} from 'lodash';
|
|
10
10
|
import {FieldFilterOperator} from './Types';
|
|
11
11
|
|
|
@@ -19,7 +19,7 @@ export interface BaseFilterFieldSpecConfig {
|
|
|
19
19
|
/** Operators available for filtering, will default to a supported set based on type.*/
|
|
20
20
|
ops?: FieldFilterOperator[];
|
|
21
21
|
/** Used to source matching data `Field` and extract values if configured. */
|
|
22
|
-
source?:
|
|
22
|
+
source?: FilterValueSource;
|
|
23
23
|
/**
|
|
24
24
|
* True to provide interfaces and auto-complete options
|
|
25
25
|
* with enumerated matches for creating '=' or '!=' filters. Defaults to true for
|
|
@@ -47,7 +47,7 @@ export abstract class BaseFilterFieldSpec extends HoistBase {
|
|
|
47
47
|
fieldType: FieldType;
|
|
48
48
|
displayName: string;
|
|
49
49
|
ops: FieldFilterOperator[];
|
|
50
|
-
source:
|
|
50
|
+
source: FilterValueSource;
|
|
51
51
|
enableValues: boolean;
|
|
52
52
|
forceSelection: boolean;
|
|
53
53
|
values: any[];
|
|
@@ -17,8 +17,8 @@ import {CompoundFilterSpec, CompoundFilterOperator, FilterTestFn} from './Types'
|
|
|
17
17
|
* Immutable.
|
|
18
18
|
*/
|
|
19
19
|
export class CompoundFilter extends Filter {
|
|
20
|
-
|
|
21
|
-
return
|
|
20
|
+
static isCompoundFilter(obj: unknown): obj is CompoundFilter {
|
|
21
|
+
return obj instanceof CompoundFilter;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
readonly filters: Filter[];
|
|
@@ -63,7 +63,7 @@ export class CompoundFilter extends Filter {
|
|
|
63
63
|
other instanceof CompoundFilter &&
|
|
64
64
|
other.op === this.op &&
|
|
65
65
|
isEqualWith(other.filters, this.filters, (a, b) =>
|
|
66
|
-
|
|
66
|
+
Filter.isFilter(a) && Filter.isFilter(b) ? a.equals(b) : undefined
|
|
67
67
|
)
|
|
68
68
|
);
|
|
69
69
|
}
|
|
@@ -36,8 +36,8 @@ import {FieldFilterOperator, FieldFilterSpec, FilterTestFn} from './Types';
|
|
|
36
36
|
* Immutable.
|
|
37
37
|
*/
|
|
38
38
|
export class FieldFilter extends Filter {
|
|
39
|
-
|
|
40
|
-
return
|
|
39
|
+
static isFieldFilter(obj: unknown): obj is FieldFilter {
|
|
40
|
+
return obj instanceof FieldFilter;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
readonly field: string;
|
package/data/filter/Filter.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* Copyright © 2026 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {Store} from '
|
|
9
|
-
import {FilterSpec, FilterTestFn} from './Types';
|
|
8
|
+
import {Store} from '@xh/hoist/data';
|
|
9
|
+
import type {FilterSpec, FilterTestFn} from './Types';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Base class for Hoist data package Filters.
|
|
@@ -20,8 +20,8 @@ import {FilterSpec, FilterTestFn} from './Types';
|
|
|
20
20
|
* via an `AND` or `OR` operator.
|
|
21
21
|
*/
|
|
22
22
|
export abstract class Filter {
|
|
23
|
-
|
|
24
|
-
return
|
|
23
|
+
static isFilter(obj: unknown): obj is Filter {
|
|
24
|
+
return obj instanceof Filter;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
@@ -19,8 +19,8 @@ import {FunctionFilterSpec, FilterTestFn} from './Types';
|
|
|
19
19
|
* Immutable.
|
|
20
20
|
*/
|
|
21
21
|
export class FunctionFilter extends Filter {
|
|
22
|
-
|
|
23
|
-
return
|
|
22
|
+
static isFunctionFilter(obj: unknown): obj is FunctionFilter {
|
|
23
|
+
return obj instanceof FunctionFilter;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
readonly key: string;
|
package/data/filter/Types.ts
CHANGED
|
@@ -5,17 +5,25 @@
|
|
|
5
5
|
* Copyright © 2026 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {PlainObject} from '@xh/hoist/core';
|
|
9
|
-
import {Filter} from './Filter';
|
|
10
|
-
import {StoreRecord, Field, FieldType} from '../';
|
|
8
|
+
import type {PlainObject} from '@xh/hoist/core';
|
|
9
|
+
import type {Filter} from './Filter';
|
|
10
|
+
import type {StoreRecord, Field, FieldType} from '../';
|
|
11
11
|
|
|
12
|
-
export type
|
|
13
|
-
export interface CompoundFilterSpec {
|
|
14
|
-
/** Collection of Filters or configs to create. */
|
|
15
|
-
filters: FilterLike[];
|
|
12
|
+
export type FilterLike = Filter | FilterSpec | FilterTestFn | FilterLike[];
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
export type FilterSpec = FieldFilterSpec | FunctionFilterSpec | CompoundFilterSpec;
|
|
15
|
+
|
|
16
|
+
export interface FieldFilterSpec {
|
|
17
|
+
/** Name of Field to filter or Field instance. */
|
|
18
|
+
field: string | Field;
|
|
19
|
+
|
|
20
|
+
/** One of the supported operators to use for comparison. */
|
|
21
|
+
op: FieldFilterOperator;
|
|
22
|
+
|
|
23
|
+
value: any;
|
|
24
|
+
|
|
25
|
+
/** For internal serialization only. */
|
|
26
|
+
valueType?: FieldType;
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
export type FieldFilterOperator =
|
|
@@ -33,19 +41,17 @@ export type FieldFilterOperator =
|
|
|
33
41
|
| 'not ends'
|
|
34
42
|
| 'includes'
|
|
35
43
|
| 'excludes';
|
|
36
|
-
export interface FieldFilterSpec {
|
|
37
|
-
/** Name of Field to filter or Field instance. */
|
|
38
|
-
field: string | Field;
|
|
39
44
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
value: any;
|
|
45
|
+
export interface CompoundFilterSpec {
|
|
46
|
+
/** Collection of Filters or configs to create. */
|
|
47
|
+
filters: FilterLike[];
|
|
44
48
|
|
|
45
|
-
/**
|
|
46
|
-
|
|
49
|
+
/** logical operator 'AND' (default) or 'OR'. */
|
|
50
|
+
op?: CompoundFilterOperator;
|
|
47
51
|
}
|
|
48
52
|
|
|
53
|
+
export type CompoundFilterOperator = 'AND' | 'OR' | 'and' | 'or';
|
|
54
|
+
|
|
49
55
|
export interface FunctionFilterSpec {
|
|
50
56
|
/** Key used to identify this FunctionFilter.*/
|
|
51
57
|
key: string;
|
|
@@ -61,6 +67,33 @@ export interface FunctionFilterSpec {
|
|
|
61
67
|
*/
|
|
62
68
|
export type FilterTestFn = (candidate: PlainObject | StoreRecord) => boolean;
|
|
63
69
|
|
|
64
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Target (typically a {@link Store} or Cube {@link View}) that can be used by filtering models
|
|
72
|
+
* such as {@link FilterChooserModel} and {@link GridFilterModel} to get and set filters.
|
|
73
|
+
*/
|
|
74
|
+
export interface FilterBindTarget {
|
|
75
|
+
filter: Filter;
|
|
76
|
+
setFilter(filter: FilterLike): unknown;
|
|
77
|
+
}
|
|
65
78
|
|
|
66
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Data provider (typically a {@link Store} or Cube {@link View}) that can be used by filtering
|
|
81
|
+
* models such as {@link FilterChooserModel} and {@link GridFilterModel} to source available
|
|
82
|
+
* values from within a dataset for display / suggestion to users.
|
|
83
|
+
*/
|
|
84
|
+
export interface FilterValueSource {
|
|
85
|
+
/** Names of all fields available in the source. */
|
|
86
|
+
fieldNames: string[];
|
|
87
|
+
/** @returns the Field instance for the given field name. */
|
|
88
|
+
getField(fieldName: string): Field;
|
|
89
|
+
/** @returns unique values for the given field, applying the given filter if provided. */
|
|
90
|
+
getValuesForFieldFilter(fieldName: string, filter?: Filter): any[];
|
|
91
|
+
/** Observable timestamp for the source's data, to trigger consumers to re-query values. */
|
|
92
|
+
lastUpdated: number;
|
|
93
|
+
/** For the {@link isFilterValueSource} typeguard. */
|
|
94
|
+
isFilterValueSource: true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function isFilterValueSource(v: unknown): v is FilterValueSource {
|
|
98
|
+
return (v as any)?.isFilterValueSource === true;
|
|
99
|
+
}
|
|
@@ -10,7 +10,7 @@ import ReactGridLayout, {
|
|
|
10
10
|
useContainerWidth,
|
|
11
11
|
getCompactor
|
|
12
12
|
} from 'react-grid-layout';
|
|
13
|
-
import {GridBackground, type GridBackgroundProps
|
|
13
|
+
import {GridBackground, type GridBackgroundProps} from 'react-grid-layout/extras';
|
|
14
14
|
import composeRefs from '@seznam/compose-react-refs';
|
|
15
15
|
import {div, vbox, vspacer} from '@xh/hoist/cmp/layout';
|
|
16
16
|
import {
|
|
@@ -62,11 +62,7 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
|
|
|
62
62
|
render({className, model, rglOptions, testId}, ref) {
|
|
63
63
|
const isDraggable = !model.layoutLocked,
|
|
64
64
|
isResizable = !model.layoutLocked,
|
|
65
|
-
{width, containerRef, mounted} = useContainerWidth()
|
|
66
|
-
defaultDroppedItemDims = {
|
|
67
|
-
w: Math.floor(model.columns / 3),
|
|
68
|
-
h: Math.floor(model.columns / 3)
|
|
69
|
-
};
|
|
65
|
+
{width, containerRef, mounted} = useContainerWidth();
|
|
70
66
|
|
|
71
67
|
return refreshContextView({
|
|
72
68
|
model: model.refreshContextModel,
|
|
@@ -102,20 +98,7 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
|
|
|
102
98
|
resizeConfig: {
|
|
103
99
|
enabled: isResizable
|
|
104
100
|
},
|
|
105
|
-
|
|
106
|
-
enabled: model.contentLocked ? false : model.allowsDrop,
|
|
107
|
-
defaultItem: defaultDroppedItemDims,
|
|
108
|
-
onDragOver: (evt: DragEvent) => model.onDropDragOver(evt)
|
|
109
|
-
},
|
|
110
|
-
onDrop: (
|
|
111
|
-
layout: LayoutItem[],
|
|
112
|
-
layoutItem: LayoutItem,
|
|
113
|
-
evt: Event
|
|
114
|
-
) => model.onDrop(layout, layoutItem, evt),
|
|
115
|
-
compactor:
|
|
116
|
-
model.compact === 'wrap'
|
|
117
|
-
? wrapCompactor
|
|
118
|
-
: getCompactor(model.compact, false, false),
|
|
101
|
+
compactor: getCompactor(model.compact, false, false),
|
|
119
102
|
onLayoutChange: (layout: LayoutItem[]) =>
|
|
120
103
|
model.onRglLayoutChange(layout),
|
|
121
104
|
onResizeStart: () => (model.isResizing = true),
|
|
@@ -133,7 +116,7 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
|
|
|
133
116
|
),
|
|
134
117
|
width
|
|
135
118
|
}),
|
|
136
|
-
emptyContainerOverlay({omit: !mounted
|
|
119
|
+
emptyContainerOverlay({omit: !mounted})
|
|
137
120
|
],
|
|
138
121
|
[TEST_ID]: testId
|
|
139
122
|
})
|