@xh/hoist 80.0.0-SNAPSHOT.1767974090726 → 80.0.0-SNAPSHOT.1768003263151

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 (33) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/build/types/cmp/filter/FilterChooserFieldSpec.d.ts +1 -1
  3. package/build/types/cmp/filter/FilterChooserModel.d.ts +19 -10
  4. package/build/types/cmp/grid/filter/GridFilterFieldSpec.d.ts +4 -3
  5. package/build/types/cmp/grid/filter/GridFilterModel.d.ts +1 -1
  6. package/build/types/cmp/tab/TabContainerModel.d.ts +10 -5
  7. package/build/types/data/Store.d.ts +5 -6
  8. package/build/types/data/cube/View.d.ts +5 -3
  9. package/build/types/data/filter/BaseFilterFieldSpec.d.ts +3 -3
  10. package/build/types/data/filter/CompoundFilter.d.ts +1 -1
  11. package/build/types/data/filter/FieldFilter.d.ts +1 -1
  12. package/build/types/data/filter/Filter.d.ts +3 -3
  13. package/build/types/data/filter/FunctionFilter.d.ts +1 -1
  14. package/build/types/data/filter/Types.d.ts +39 -13
  15. package/build/types/desktop/cmp/grid/impl/filter/headerfilter/values/ValuesTabModel.d.ts +2 -2
  16. package/build/types/svc/InspectorService.d.ts +2 -2
  17. package/cmp/filter/FilterChooserFieldSpec.ts +5 -22
  18. package/cmp/filter/FilterChooserModel.ts +27 -12
  19. package/cmp/grid/filter/GridFilterFieldSpec.ts +52 -51
  20. package/cmp/grid/filter/GridFilterModel.ts +6 -7
  21. package/cmp/tab/TabContainerModel.ts +9 -5
  22. package/data/Store.ts +47 -13
  23. package/data/cube/View.ts +14 -3
  24. package/data/filter/BaseFilterFieldSpec.ts +3 -3
  25. package/data/filter/CompoundFilter.ts +3 -3
  26. package/data/filter/FieldFilter.ts +2 -2
  27. package/data/filter/Filter.ts +4 -4
  28. package/data/filter/FunctionFilter.ts +2 -2
  29. package/data/filter/Types.ts +53 -20
  30. package/desktop/cmp/grid/impl/filter/headerfilter/values/ValuesTabModel.ts +9 -9
  31. package/package.json +1 -1
  32. package/svc/InspectorService.ts +6 -5
  33. package/tsconfig.tsbuildinfo +1 -1
@@ -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 {FieldFilterOperator, parseFilter, StoreRecord, View} from '@xh/hoist/data';
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, flatten, isDate, isEmpty, uniqBy} from 'lodash';
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 to the GridModel's `filterModel` config.
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
- valueCount: number;
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
- sourceStore = source instanceof View ? source.cube.store : source,
76
- allRecords = sourceStore.allRecords;
77
-
78
- // Apply external filters *not* pertaining to this field to the sourceStore
79
- // to get the filtered set of available values to offer as options.
80
- const cleanedFilter = this.cleanFilter(filterModel.filter);
81
- let filteredRecords = allRecords;
82
- if (cleanedFilter) {
83
- const testFn = parseFilter(cleanedFilter).getTestFn(sourceStore);
84
- filteredRecords = allRecords.filter(testFn);
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
- // Get values from current column filter
88
- const filterValues = [];
89
- columnFilters.forEach(filter => {
90
- const newValues = castArray(filter.value).map(value => {
91
- value = sourceField.parseVal(value);
92
- return filterModel.toDisplayValue(value);
93
- });
94
- filterValues.push(...newValues);
95
- });
96
-
97
- // Combine unique values from record sets and column filters.
98
- const allValues = uniqBy(
99
- [...flatten(allRecords.map(rec => this.valueFromRecord(rec))), ...filterValues],
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
- this.valueCount = allValues.length;
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 (!filter) return filter;
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
- } else if (field === this.field) {
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 valueFromRecord(record: StoreRecord) {
135
- const {filterModel, field} = this;
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?.isCompoundFilter) return null;
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 its children
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
  }
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Copyright © 2026 Extremely Heavy Industries Inc.
6
6
  */
7
- import {
7
+ import type {
8
8
  TabSwitcherConfig,
9
9
  IDynamicTabSwitcherModel,
10
10
  TabContainerModelPersistOptions
@@ -14,7 +14,6 @@ import {
14
14
  managed,
15
15
  PersistableState,
16
16
  PersistenceProvider,
17
- PersistOptions,
18
17
  RefreshContextModel,
19
18
  RefreshMode,
20
19
  RenderMode,
@@ -41,7 +40,7 @@ export interface TabContainerConfig {
41
40
 
42
41
  /**
43
42
  * Base route name for this container. If set, this container will be route-enabled, with the
44
- * route for each tab being "[route]/[tab.id]". Cannot be used with `persistWith`.
43
+ * route for each tab being "[route]/[tab.id]".
45
44
  */
46
45
  route?: string;
47
46
 
@@ -69,8 +68,13 @@ export interface TabContainerConfig {
69
68
  */
70
69
  refreshMode?: RefreshMode;
71
70
 
72
- /** Options governing persistence. Cannot be used with `route`. */
73
- persistWith?: PersistOptions;
71
+ /**
72
+ * Options governing persistence. Tab containers can persist their last-active tab as well
73
+ * as favorite tabs for the dynamic `switcher` option. Note that this must be left unset or
74
+ * its nested `persistActiveTabId` option must be set to false if also using `route`, to avoid
75
+ * a possible conflict between an initial route and persisted last active tab.
76
+ */
77
+ persistWith?: TabContainerModelPersistOptions;
74
78
 
75
79
  /**
76
80
  * Placeholder to display if no tabs are provided or all tabs have been removed via
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
- values,
36
+ partition,
24
37
  remove as lodashRemove,
25
- uniq,
26
- first,
27
38
  some,
28
- partition
39
+ uniq,
40
+ values
29
41
  } from 'lodash';
30
- import {Field, FieldSpec} from './Field';
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
- get isStore() {
186
- return true;
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
- get isView() {
69
- return true;
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, Store, FieldFilter, FieldType, genDisplayName, View} from '@xh/hoist/data';
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?: Store | View;
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: Store | View;
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
- get isCompoundFilter() {
21
- return true;
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
- a.isFilter && b.isFilter ? a.equals(b) : undefined
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
- get isFieldFilter() {
40
- return true;
39
+ static isFieldFilter(obj: unknown): obj is FieldFilter {
40
+ return obj instanceof FieldFilter;
41
41
  }
42
42
 
43
43
  readonly field: string;
@@ -5,8 +5,8 @@
5
5
  * Copyright © 2026 Extremely Heavy Industries Inc.
6
6
  */
7
7
 
8
- import {Store} from '../Store';
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
- get isFilter() {
24
- return true;
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
- get isFunctionFilter() {
23
- return true;
22
+ static isFunctionFilter(obj: unknown): obj is FunctionFilter {
23
+ return obj instanceof FunctionFilter;
24
24
  }
25
25
 
26
26
  readonly key: string;
@@ -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 CompoundFilterOperator = 'AND' | 'OR' | 'and' | 'or';
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
- /** logical operator 'AND' (default) or 'OR'. */
18
- op?: CompoundFilterOperator;
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
- /** One of the supported operators to use for comparison. */
41
- op: FieldFilterOperator;
42
-
43
- value: any;
45
+ export interface CompoundFilterSpec {
46
+ /** Collection of Filters or configs to create. */
47
+ filters: FilterLike[];
44
48
 
45
- /** For internal serialization only. */
46
- valueType?: FieldType;
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
- export type FilterSpec = FieldFilterSpec | FunctionFilterSpec | CompoundFilterSpec;
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
- export type FilterLike = Filter | FilterSpec | FilterTestFn | FilterLike[];
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
+ }
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import {GridFilterModel, GridModel} from '@xh/hoist/cmp/grid';
8
8
  import {HoistModel, managed} from '@xh/hoist/core';
9
- import {FieldFilterSpec} from '@xh/hoist/data';
9
+ import type {FieldFilterOperator, FieldFilterSpec} from '@xh/hoist/data';
10
10
  import {checkbox} from '@xh/hoist/desktop/cmp/input';
11
11
  import {Icon} from '@xh/hoist/icon';
12
12
  import {action, bindable, computed, makeObservable, observable} from '@xh/hoist/mobx';
@@ -73,12 +73,12 @@ export class ValuesTabModel extends HoistModel {
73
73
  return this.fieldSpec.values;
74
74
  }
75
75
 
76
- get valueCount() {
77
- return this.fieldSpec.valueCount;
76
+ get allValuesCount() {
77
+ return this.fieldSpec.allValuesCount;
78
78
  }
79
79
 
80
80
  get hasHiddenValues() {
81
- return this.values.length < this.valueCount;
81
+ return this.values.length < this.allValuesCount;
82
82
  }
83
83
 
84
84
  get sortIcon() {
@@ -193,24 +193,24 @@ export class ValuesTabModel extends HoistModel {
193
193
  );
194
194
  }
195
195
 
196
- private getFilter() {
197
- const {gridFilterModel, pendingValues, values, valueCount, field} = this,
196
+ private getFilter(): FieldFilterSpec {
197
+ const {gridFilterModel, pendingValues, values, allValuesCount, field} = this,
198
198
  included = pendingValues.map(it => gridFilterModel.fromDisplayValue(it)),
199
199
  excluded = difference(values, pendingValues).map(it =>
200
200
  gridFilterModel.fromDisplayValue(it)
201
201
  );
202
202
 
203
- if (included.length === valueCount || excluded.length === valueCount) {
203
+ if (included.length === allValuesCount || excluded.length === allValuesCount) {
204
204
  return null;
205
205
  }
206
206
 
207
207
  const {fieldType} = this.headerFilterModel;
208
- let arr, op;
208
+ let arr: any[], op: FieldFilterOperator;
209
209
  if (fieldType === 'tags') {
210
210
  arr = included;
211
211
  op = 'includes';
212
212
  } else {
213
- const weight = valueCount <= 10 ? 2.5 : 1; // Prefer '=' for short lists
213
+ const weight = allValuesCount <= 10 ? 2.5 : 1; // Prefer '=' for short lists
214
214
  op = included.length > excluded.length * weight ? '!=' : '=';
215
215
  arr = op === '=' ? included : excluded;
216
216
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "80.0.0-SNAPSHOT.1767974090726",
3
+ "version": "80.0.0-SNAPSHOT.1768003263151",
4
4
  "description": "Hoist add-on for building and deploying React Applications.",
5
5
  "repository": "github:xh/hoist-react",
6
6
  "homepage": "https://xh.io",