@xh/hoist 75.0.0-SNAPSHOT.1753369138152 → 75.0.0-SNAPSHOT.1753454795132
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 +6 -0
- package/build/types/cmp/filter/FilterChooserModel.d.ts +3 -1
- package/build/types/cmp/grid/GridModel.d.ts +4 -0
- package/build/types/data/filter/FieldFilter.d.ts +3 -0
- package/cmp/filter/FilterChooserModel.ts +106 -38
- package/cmp/grid/GridModel.ts +18 -3
- package/cmp/grid/impl/MenuSupport.ts +5 -17
- package/data/filter/FieldFilter.ts +4 -0
- package/desktop/cmp/button/grid/ExpandToLevelButton.ts +6 -17
- package/mobile/cmp/button/grid/ExpandToLevelButton.ts +7 -21
- package/package.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,12 @@
|
|
|
16
16
|
which `dimensions` are provided to the model.
|
|
17
17
|
* The usage of the `RelativeTimestamp` component has been streamlined by deprecating the `options`
|
|
18
18
|
prop. All `RelativeTimestampOptions` are now supported by this component as top-level props.
|
|
19
|
+
* `FilterChooserModel` has been enhanced to better handle multiple simultaneous filters with
|
|
20
|
+
different `op`s on the same field. "Inclusive" ops (e.g. `=`, `like`) will be OR'ed together,
|
|
21
|
+
"Exclusive" ops (e.g. `!=`, `not like`) will be AND'ed together and range ops (e.g. `<`, `>` )
|
|
22
|
+
will use a heuristic to create a meaningful query that will actually return results. This
|
|
23
|
+
behavior is consistent with current behavior and user intuition, and should maximize the ability
|
|
24
|
+
to create useful queries.
|
|
19
25
|
|
|
20
26
|
### 🐞 Bug Fixes
|
|
21
27
|
|
|
@@ -95,7 +95,6 @@ export declare class FilterChooserModel extends HoistModel {
|
|
|
95
95
|
*/
|
|
96
96
|
setValue(rawValue: FilterLike): void;
|
|
97
97
|
setSelectValue(selectValue: string[]): void;
|
|
98
|
-
toDisplayFilters(filter: Filter): any[];
|
|
99
98
|
queryAsync(query: string): Promise<FilterChooserOption[]>;
|
|
100
99
|
autoComplete(value: any): void;
|
|
101
100
|
createFilterOption(filter: Filter): FilterChooserOption;
|
|
@@ -114,6 +113,9 @@ export declare class FilterChooserModel extends HoistModel {
|
|
|
114
113
|
getFieldSpec(fieldName: string): FilterChooserFieldSpec;
|
|
115
114
|
validateFilter(f: Filter): f is FilterChooserFilter;
|
|
116
115
|
getDefaultIntroHelpText(): string;
|
|
116
|
+
private toValueFilter;
|
|
117
|
+
private toDisplayFilters;
|
|
118
|
+
private implicitOrFieldFilters;
|
|
117
119
|
private initPersist;
|
|
118
120
|
private setValueInternal;
|
|
119
121
|
/**
|
|
@@ -437,6 +437,10 @@ export declare class GridModel extends HoistModel {
|
|
|
437
437
|
collapseAll(): void;
|
|
438
438
|
/** Expand all parent rows in grouped or tree grid to the specified level. */
|
|
439
439
|
expandToLevel(level: number): void;
|
|
440
|
+
/**
|
|
441
|
+
* Get the resolved level labels for the current state of the grid.
|
|
442
|
+
*/
|
|
443
|
+
get resolvedLevelLabels(): string[];
|
|
440
444
|
/**
|
|
441
445
|
* Sort this grid.
|
|
442
446
|
* This method is a no-op if provided any sorters without a corresponding column.
|
|
@@ -17,6 +17,9 @@ export declare class FieldFilter extends Filter {
|
|
|
17
17
|
readonly value: any;
|
|
18
18
|
static OPERATORS: string[];
|
|
19
19
|
static ARRAY_OPERATORS: string[];
|
|
20
|
+
static INCLUDE_LIKE_OPERATORS: string[];
|
|
21
|
+
static EXCLUDE_LIKE_OPERATORS: string[];
|
|
22
|
+
static RANGE_LIKE_OPERATORS: string[];
|
|
20
23
|
/**
|
|
21
24
|
* Constructor - not typically called by apps - create via {@link parseFilter} instead.
|
|
22
25
|
* @internal
|
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
XH
|
|
16
16
|
} from '@xh/hoist/core';
|
|
17
17
|
import {
|
|
18
|
-
combineValueFilters,
|
|
19
18
|
CompoundFilter,
|
|
20
19
|
FieldFilter,
|
|
21
20
|
Filter,
|
|
@@ -32,6 +31,8 @@ import {createObservableRef} from '@xh/hoist/utils/react';
|
|
|
32
31
|
import {
|
|
33
32
|
cloneDeep,
|
|
34
33
|
compact,
|
|
34
|
+
every,
|
|
35
|
+
first,
|
|
35
36
|
flatMap,
|
|
36
37
|
flatten,
|
|
37
38
|
forEach,
|
|
@@ -41,6 +42,7 @@ import {
|
|
|
41
42
|
isFinite,
|
|
42
43
|
isObject,
|
|
43
44
|
isString,
|
|
45
|
+
map,
|
|
44
46
|
partition,
|
|
45
47
|
sortBy,
|
|
46
48
|
uniq,
|
|
@@ -222,48 +224,12 @@ export class FilterChooserModel extends HoistModel {
|
|
|
222
224
|
const [filters, suggestions] = partition(parsedValues, 'op');
|
|
223
225
|
|
|
224
226
|
// Round-trip actual filters through main value setter above.
|
|
225
|
-
this.setValue(
|
|
227
|
+
this.setValue(this.toValueFilter(filters));
|
|
226
228
|
|
|
227
229
|
// And then programmatically re-enter any suggestion
|
|
228
230
|
if (suggestions.length === 1) this.autoComplete(suggestions[0]);
|
|
229
231
|
}
|
|
230
232
|
|
|
231
|
-
// Transfer the value filter to the canonical set of individual filters for display.
|
|
232
|
-
// Filters with arrays values will be split.
|
|
233
|
-
toDisplayFilters(filter: Filter) {
|
|
234
|
-
if (!filter) return [];
|
|
235
|
-
|
|
236
|
-
let ret;
|
|
237
|
-
const unsupported = s => {
|
|
238
|
-
throw XH.exception(`Unsupported Filter in FilterChooserModel: ${s}`);
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
// 1) Flatten CompoundFilters across disparate fields to FieldFilters.
|
|
242
|
-
if (filter instanceof CompoundFilter && !filter.field) {
|
|
243
|
-
ret = filter.filters;
|
|
244
|
-
} else {
|
|
245
|
-
ret = [filter];
|
|
246
|
-
}
|
|
247
|
-
if (ret.some(f => !(f instanceof FieldFilter) && !(f instanceof CompoundFilter))) {
|
|
248
|
-
unsupported('Filters must be FieldFilters or CompoundFilters.');
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// 2) Recognize unsupported multiple filters for array-based filters.
|
|
252
|
-
const groupMap = groupBy(ret, ({op, field}) => `${op}|${field}`);
|
|
253
|
-
forEach(groupMap, filters => {
|
|
254
|
-
const {op} = filters[0];
|
|
255
|
-
if (filters.length > 1 && FieldFilter.ARRAY_OPERATORS.includes(op)) {
|
|
256
|
-
unsupported(`Multiple filters cannot be provided with ${op} operator`);
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
// 3) Finally unroll multi-value filters to one value per filter.
|
|
261
|
-
// The multiple values will later be restored.
|
|
262
|
-
return flatMap(ret, f => {
|
|
263
|
-
return isArray(f.value) ? f.value.map(value => new FieldFilter({...f, value})) : f;
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
|
|
267
233
|
//-------------
|
|
268
234
|
// Querying
|
|
269
235
|
//---------------
|
|
@@ -411,6 +377,108 @@ export class FilterChooserModel extends HoistModel {
|
|
|
411
377
|
// -------------------------------
|
|
412
378
|
// Implementation
|
|
413
379
|
// -------------------------------
|
|
380
|
+
|
|
381
|
+
// Take the raw flat displayed FieldFilter specs and combine them with appropriate semantics
|
|
382
|
+
// into a proper Filter for the value of ths model. Field Filters on the same field are going
|
|
383
|
+
// to be combined into a FieldFilter with array values as well as potentially appropriate
|
|
384
|
+
// compound filters to combine different ops. See toDisplayFilters() for the inverse of this
|
|
385
|
+
// operation.
|
|
386
|
+
private toValueFilter(specs: FieldFilterSpec[] = []): FilterLike {
|
|
387
|
+
const ret: FilterLike[] = [];
|
|
388
|
+
|
|
389
|
+
// group filters by field -- we'll produce up to two ANDable filters per field
|
|
390
|
+
const fieldMap = groupBy(specs, 'field');
|
|
391
|
+
forEach(fieldMap, specs => {
|
|
392
|
+
// a) combine filters with SAME operator in to a single FieldFilter
|
|
393
|
+
const opMap = groupBy(specs, 'op');
|
|
394
|
+
specs = flatMap(opMap, specs => {
|
|
395
|
+
const firstSpec = first(specs);
|
|
396
|
+
return specs.length > 1 && FieldFilter.ARRAY_OPERATORS.includes(firstSpec.op)
|
|
397
|
+
? {...firstSpec, value: map(specs, 'value')}
|
|
398
|
+
: specs;
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// Process like operators together, potentially creating sub-OR clauses.
|
|
402
|
+
[
|
|
403
|
+
FieldFilter.INCLUDE_LIKE_OPERATORS,
|
|
404
|
+
FieldFilter.EXCLUDE_LIKE_OPERATORS,
|
|
405
|
+
FieldFilter.RANGE_LIKE_OPERATORS
|
|
406
|
+
].forEach(type => {
|
|
407
|
+
const filters = specs.filter(s => type.includes(s.op));
|
|
408
|
+
if (this.implicitOrFieldFilters(filters)) {
|
|
409
|
+
ret.push([{op: 'OR', filters}]);
|
|
410
|
+
} else {
|
|
411
|
+
ret.push(...filters);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
return ret;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Transfer the value filter to the canonical set of individual filters for display.
|
|
420
|
+
// See toValueFilter() for the inverse of this operation.
|
|
421
|
+
private toDisplayFilters(filter: Filter): Filter[] {
|
|
422
|
+
if (!filter) return [];
|
|
423
|
+
const unsupported = s => {
|
|
424
|
+
throw XH.exception(`Unsupported Filter in FilterChooserModel: ${s}`);
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
let ret: Filter[] = [filter];
|
|
428
|
+
|
|
429
|
+
// 0) Can always unwind the top level AND -- its implicit.
|
|
430
|
+
if (filter instanceof CompoundFilter && filter.op == 'AND') {
|
|
431
|
+
ret = filter.filters;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// 1) Further flatten 2nd-Level CompoundFilters to FieldFilters.
|
|
435
|
+
// OR'ed filters on the same field can be decomposed if they will later be re-combined
|
|
436
|
+
ret = ret.flatMap(f => {
|
|
437
|
+
return f instanceof CompoundFilter &&
|
|
438
|
+
(f.op == 'AND' ||
|
|
439
|
+
(f.field && this.implicitOrFieldFilters(f.filters as FieldFilter[])))
|
|
440
|
+
? f.filters
|
|
441
|
+
: f;
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// 2) Recognize misc unsupported filters.
|
|
445
|
+
if (!ret.every(f => f instanceof FieldFilter || f instanceof CompoundFilter)) {
|
|
446
|
+
unsupported('Filters must be FieldFilters or CompoundFilters.');
|
|
447
|
+
}
|
|
448
|
+
const fieldFilters = ret.filter(it => it instanceof FieldFilter) as FieldFilter[];
|
|
449
|
+
const groupMap = groupBy(fieldFilters, ({op, field}) => `${op}|${field}`);
|
|
450
|
+
forEach(groupMap, filters => {
|
|
451
|
+
const {op} = filters[0];
|
|
452
|
+
if (filters.length > 1 && FieldFilter.ARRAY_OPERATORS.includes(op)) {
|
|
453
|
+
unsupported(`Multiple filters cannot be provided with ${op} operator`);
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// 3) Finally unroll multi-value filters to one filter per value.
|
|
458
|
+
return flatMap(ret, f => {
|
|
459
|
+
return f instanceof FieldFilter && isArray(f.value)
|
|
460
|
+
? f.value.map(value => new FieldFilter({...f, value}))
|
|
461
|
+
: f;
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Should Field Filters on a particular Field be implicitly OR'ed together?
|
|
466
|
+
private implicitOrFieldFilters(filters: Array<FieldFilterSpec | FieldFilter>): boolean {
|
|
467
|
+
const {INCLUDE_LIKE_OPERATORS, RANGE_LIKE_OPERATORS} = FieldFilter;
|
|
468
|
+
if (filters.length < 2) return false;
|
|
469
|
+
|
|
470
|
+
// For INCLUDE_LIKE, treat them like "equals" and OR them
|
|
471
|
+
if (every(filters, f => INCLUDE_LIKE_OPERATORS.includes(f.op))) return true;
|
|
472
|
+
|
|
473
|
+
// For RANGE_LIKE, recognize simple "exterior" bifurcated range as an OR, otherwise AND
|
|
474
|
+
if (filters.length == 2 && every(filters, f => RANGE_LIKE_OPERATORS.includes(f.op))) {
|
|
475
|
+
const [a, b] = sortBy(filters, 'op');
|
|
476
|
+
return a.op.startsWith('<') && b.op.startsWith('>') && a.value <= b.value;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
|
|
414
482
|
private initPersist({
|
|
415
483
|
persistValue = true,
|
|
416
484
|
persistFavorites = true,
|
package/cmp/grid/GridModel.ts
CHANGED
|
@@ -92,7 +92,8 @@ import {
|
|
|
92
92
|
min,
|
|
93
93
|
omit,
|
|
94
94
|
pick,
|
|
95
|
-
pull
|
|
95
|
+
pull,
|
|
96
|
+
take
|
|
96
97
|
} from 'lodash';
|
|
97
98
|
import {ReactNode} from 'react';
|
|
98
99
|
import {GridAutosizeOptions} from './GridAutosizeOptions';
|
|
@@ -552,7 +553,7 @@ export class GridModel extends HoistModel {
|
|
|
552
553
|
|
|
553
554
|
this.xhImpl = xhImpl;
|
|
554
555
|
|
|
555
|
-
this._defaultState = {columns, sortBy, groupBy};
|
|
556
|
+
this._defaultState = {columns, sortBy, groupBy, expandLevel};
|
|
556
557
|
|
|
557
558
|
this.treeMode = treeMode;
|
|
558
559
|
this.treeStyle = treeStyle;
|
|
@@ -679,10 +680,11 @@ export class GridModel extends HoistModel {
|
|
|
679
680
|
if (!confirmed) return false;
|
|
680
681
|
}
|
|
681
682
|
|
|
682
|
-
const {columns, sortBy, groupBy, filter} = this._defaultState;
|
|
683
|
+
const {columns, sortBy, groupBy, filter, expandLevel} = this._defaultState;
|
|
683
684
|
this.setColumns(columns);
|
|
684
685
|
this.setSortBy(sortBy);
|
|
685
686
|
this.setGroupBy(groupBy);
|
|
687
|
+
this.expandToLevel(expandLevel);
|
|
686
688
|
|
|
687
689
|
this.filterModel?.setFilter(filter);
|
|
688
690
|
|
|
@@ -1043,6 +1045,19 @@ export class GridModel extends HoistModel {
|
|
|
1043
1045
|
this.expandLevel = level;
|
|
1044
1046
|
}
|
|
1045
1047
|
|
|
1048
|
+
/**
|
|
1049
|
+
* Get the resolved level labels for the current state of the grid.
|
|
1050
|
+
*/
|
|
1051
|
+
get resolvedLevelLabels(): string[] {
|
|
1052
|
+
const {maxDepth, levelLabels} = this,
|
|
1053
|
+
ret = executeIfFunction(levelLabels);
|
|
1054
|
+
if (ret && ret.length < maxDepth + 1) {
|
|
1055
|
+
this.logError('Value produced by `GridModel.levelLabels` has insufficient length.');
|
|
1056
|
+
return null;
|
|
1057
|
+
}
|
|
1058
|
+
return ret ? take(ret, maxDepth + 1) : null;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1046
1061
|
/**
|
|
1047
1062
|
* Sort this grid.
|
|
1048
1063
|
* This method is a no-op if provided any sorters without a corresponding column.
|
|
@@ -9,7 +9,6 @@ import {Column, GridModel} from '@xh/hoist/cmp/grid';
|
|
|
9
9
|
import {RecordAction, Store, StoreRecord} from '@xh/hoist/data';
|
|
10
10
|
import {convertIconToHtml, Icon} from '@xh/hoist/icon';
|
|
11
11
|
import {filterConsecutiveMenuSeparators} from '@xh/hoist/utils/impl';
|
|
12
|
-
import {executeIfFunction} from '@xh/hoist/utils/js';
|
|
13
12
|
import copy from 'clipboard-copy';
|
|
14
13
|
import {isEmpty, isFunction, isNil, isString, uniq} from 'lodash';
|
|
15
14
|
import {isValidElement} from 'react';
|
|
@@ -282,26 +281,15 @@ function levelExpandAction(gridModel: GridModel): RecordAction {
|
|
|
282
281
|
return new RecordAction({
|
|
283
282
|
text: 'Expand to ...',
|
|
284
283
|
displayFn: () => {
|
|
285
|
-
|
|
286
|
-
const {maxDepth, expandLevel} = gridModel;
|
|
287
|
-
if (maxDepth <= 1) return {hidden: true};
|
|
284
|
+
const {maxDepth, expandLevel, resolvedLevelLabels} = gridModel;
|
|
288
285
|
|
|
289
|
-
//
|
|
290
|
-
|
|
291
|
-
if (!levelLabels) {
|
|
292
|
-
return {hidden: true};
|
|
293
|
-
}
|
|
294
|
-
if (levelLabels.length < maxDepth + 1) {
|
|
295
|
-
gridModel.logDebug(
|
|
296
|
-
'Value produced by `GridModel.levelLabels` has insufficient length. No menu items shown.'
|
|
297
|
-
);
|
|
298
|
-
return {hidden: true};
|
|
299
|
-
}
|
|
286
|
+
// Don't show for flat grid models or if we don't have labels
|
|
287
|
+
if (!maxDepth || !resolvedLevelLabels) return {hidden: true};
|
|
300
288
|
|
|
301
|
-
const items =
|
|
289
|
+
const items = resolvedLevelLabels.map((label, idx) => {
|
|
302
290
|
const isCurrLevel =
|
|
303
291
|
expandLevel === idx ||
|
|
304
|
-
(expandLevel > maxDepth && idx ===
|
|
292
|
+
(expandLevel > maxDepth && idx === resolvedLevelLabels.length - 1);
|
|
305
293
|
|
|
306
294
|
return {
|
|
307
295
|
icon: isCurrLevel ? Icon.check() : null,
|
|
@@ -69,6 +69,10 @@ export class FieldFilter extends Filter {
|
|
|
69
69
|
'excludes'
|
|
70
70
|
];
|
|
71
71
|
|
|
72
|
+
static INCLUDE_LIKE_OPERATORS = ['=', 'like', 'begins', 'ends', 'includes'];
|
|
73
|
+
static EXCLUDE_LIKE_OPERATORS = ['!=', 'not like', 'excludes'];
|
|
74
|
+
static RANGE_LIKE_OPERATORS = ['>', '>=', '<', '<='];
|
|
75
|
+
|
|
72
76
|
/**
|
|
73
77
|
* Constructor - not typically called by apps - create via {@link parseFilter} instead.
|
|
74
78
|
* @internal
|
|
@@ -11,7 +11,7 @@ import '@xh/hoist/desktop/register';
|
|
|
11
11
|
import {Icon} from '@xh/hoist/icon';
|
|
12
12
|
import {menu, popover, Position} from '@xh/hoist/kit/blueprint';
|
|
13
13
|
import {parseMenuItems} from '@xh/hoist/utils/impl';
|
|
14
|
-
import {
|
|
14
|
+
import {logError, withDefault} from '@xh/hoist/utils/js';
|
|
15
15
|
import {ReactNode} from 'react';
|
|
16
16
|
import {button, ButtonProps} from '../Button';
|
|
17
17
|
|
|
@@ -52,25 +52,14 @@ export const [ExpandToLevelButton, expandToLevelButton] =
|
|
|
52
52
|
return disabledButton();
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
// Render a disabled button if requested
|
|
56
|
-
const {maxDepth, expandLevel} = gridModel;
|
|
57
|
-
if (disabled || !maxDepth) return disabledButton();
|
|
55
|
+
// Render a disabled button if requested, if we have a flat grid, or no level labels
|
|
56
|
+
const {maxDepth, expandLevel, resolvedLevelLabels} = gridModel;
|
|
57
|
+
if (disabled || !maxDepth || !resolvedLevelLabels) return disabledButton();
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
const levelLabels = executeIfFunction(gridModel.levelLabels);
|
|
61
|
-
if (!levelLabels) return disabledButton();
|
|
62
|
-
if (levelLabels.length < maxDepth + 1) {
|
|
63
|
-
logDebug(
|
|
64
|
-
'Value produced by `GridModel.levelLabels` has insufficient length - button will be disabled.',
|
|
65
|
-
ExpandToLevelButton
|
|
66
|
-
);
|
|
67
|
-
return disabledButton();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const menuItems: MenuItemLike[] = levelLabels.map((label, idx) => {
|
|
59
|
+
const menuItems: MenuItemLike[] = resolvedLevelLabels.map((label, idx) => {
|
|
71
60
|
const isCurrLevel =
|
|
72
61
|
expandLevel === idx ||
|
|
73
|
-
(expandLevel > maxDepth && idx ===
|
|
62
|
+
(expandLevel > maxDepth && idx === resolvedLevelLabels.length - 1);
|
|
74
63
|
|
|
75
64
|
return {
|
|
76
65
|
icon: isCurrLevel ? Icon.check() : Icon.placeholder(),
|
|
@@ -8,7 +8,7 @@ import {GridModel} from '@xh/hoist/cmp/grid';
|
|
|
8
8
|
import {hoistCmp, MenuItemLike, useContextModel} from '@xh/hoist/core';
|
|
9
9
|
import {Icon} from '@xh/hoist/icon';
|
|
10
10
|
import '@xh/hoist/mobile/register';
|
|
11
|
-
import {
|
|
11
|
+
import {logError, withDefault} from '@xh/hoist/utils/js';
|
|
12
12
|
import {menuButton, MenuButtonProps} from '../../menu';
|
|
13
13
|
|
|
14
14
|
export interface ExpandToLevelButtonProps extends MenuButtonProps {
|
|
@@ -29,7 +29,7 @@ export const [ExpandToLevelButton, expandToLevelButton] =
|
|
|
29
29
|
className: 'xh-expand-to-level-button',
|
|
30
30
|
model: false,
|
|
31
31
|
|
|
32
|
-
render({gridModel, className, icon, ...rest}) {
|
|
32
|
+
render({gridModel, className, icon, disabled, ...rest}) {
|
|
33
33
|
gridModel = withDefault(gridModel, useContextModel(GridModel));
|
|
34
34
|
icon = withDefault(icon, Icon.treeList());
|
|
35
35
|
|
|
@@ -50,28 +50,14 @@ export const [ExpandToLevelButton, expandToLevelButton] =
|
|
|
50
50
|
return disabledButton();
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (!maxDepth) return disabledButton();
|
|
53
|
+
// Render a disabled button if requested or if we have a flat grid, or no level labels
|
|
54
|
+
const {maxDepth, expandLevel, resolvedLevelLabels} = gridModel;
|
|
55
|
+
if (disabled || !maxDepth || !resolvedLevelLabels) return disabledButton();
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
const levelLabels = executeIfFunction(gridModel.levelLabels);
|
|
60
|
-
if (!levelLabels) {
|
|
61
|
-
return disabledButton();
|
|
62
|
-
}
|
|
63
|
-
if (levelLabels.length < maxDepth + 1) {
|
|
64
|
-
logDebug(
|
|
65
|
-
'Value produced by `GridModel.levelLabels` has insufficient length. No menu items shown.',
|
|
66
|
-
ExpandToLevelButton
|
|
67
|
-
);
|
|
68
|
-
return disabledButton();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const menuItems: MenuItemLike[] = levelLabels.map((label, idx) => {
|
|
57
|
+
const menuItems: MenuItemLike[] = resolvedLevelLabels.map((label, idx) => {
|
|
72
58
|
const isCurrLevel =
|
|
73
59
|
expandLevel === idx ||
|
|
74
|
-
(expandLevel > maxDepth && idx ===
|
|
60
|
+
(expandLevel > maxDepth && idx === resolvedLevelLabels.length - 1);
|
|
75
61
|
|
|
76
62
|
return {
|
|
77
63
|
icon: isCurrLevel ? Icon.check() : Icon.placeholder(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xh/hoist",
|
|
3
|
-
"version": "75.0.0-SNAPSHOT.
|
|
3
|
+
"version": "75.0.0-SNAPSHOT.1753454795132",
|
|
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",
|