@xh/hoist 59.2.0 → 59.3.0
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 +63 -14
- package/admin/AppComponent.ts +1 -1
- package/admin/tabs/activity/ActivityTab.ts +1 -1
- package/admin/tabs/general/GeneralTab.ts +2 -2
- package/admin/tabs/general/config/ConfigPanel.ts +1 -0
- package/admin/tabs/monitor/MonitorTab.ts +1 -1
- package/admin/tabs/server/ServerTab.ts +1 -1
- package/admin/tabs/userData/UserDataTab.ts +1 -1
- package/cmp/ag-grid/AgGrid.scss +51 -25
- package/cmp/ag-grid/AgGrid.ts +8 -2
- package/cmp/badge/Badge.ts +18 -5
- package/cmp/chart/Chart.ts +13 -11
- package/cmp/clock/Clock.ts +6 -5
- package/cmp/dataview/DataView.ts +5 -3
- package/cmp/form/Form.ts +25 -6
- package/cmp/grid/Grid.ts +41 -25
- package/cmp/grid/GridModel.ts +2 -2
- package/cmp/grid/Types.ts +1 -1
- package/cmp/grid/columns/Column.ts +45 -2
- package/cmp/grid/impl/GridHScrollbar.ts +140 -0
- package/cmp/grid/renderers/MultiFieldRenderer.ts +1 -1
- package/cmp/input/HoistInputModel.ts +4 -4
- package/cmp/input/HoistInputProps.ts +3 -1
- package/cmp/layout/Box.ts +4 -2
- package/cmp/relativetimestamp/RelativeTimestamp.ts +106 -40
- package/cmp/store/StoreFilterField.ts +2 -2
- package/cmp/tab/TabContainer.ts +1 -1
- package/cmp/zoneGrid/Types.ts +47 -0
- package/cmp/zoneGrid/ZoneGrid.ts +62 -0
- package/cmp/zoneGrid/ZoneGridModel.ts +666 -0
- package/cmp/zoneGrid/impl/ZoneGridPersistenceModel.ts +143 -0
- package/cmp/zoneGrid/impl/ZoneMapperModel.ts +335 -0
- package/cmp/zoneGrid/index.ts +3 -0
- package/core/HoistComponent.ts +23 -10
- package/core/HoistProps.ts +25 -6
- package/core/XH.ts +49 -27
- package/core/elem.ts +11 -3
- package/core/impl/InstanceManager.ts +24 -1
- package/core/model/HoistModel.ts +4 -4
- package/data/RecordAction.ts +7 -4
- package/data/StoreRecord.ts +8 -1
- package/desktop/appcontainer/AppContainer.ts +2 -0
- package/desktop/cmp/appbar/AppBar.ts +8 -6
- package/desktop/cmp/button/Button.ts +14 -3
- package/desktop/cmp/button/ButtonGroup.ts +14 -3
- package/desktop/cmp/button/ZoneMapperButton.ts +82 -0
- package/desktop/cmp/button/index.ts +1 -0
- package/desktop/cmp/dash/canvas/DashCanvas.ts +14 -4
- package/desktop/cmp/dash/container/DashContainer.ts +11 -4
- package/desktop/cmp/error/ErrorMessage.ts +9 -8
- package/desktop/cmp/form/FormField.ts +34 -10
- package/desktop/cmp/grid/columns/Actions.ts +2 -1
- package/desktop/cmp/grid/impl/colchooser/ColChooser.ts +3 -2
- package/desktop/cmp/grouping/GroupingChooser.ts +29 -29
- package/desktop/cmp/input/ButtonGroupInput.ts +1 -1
- package/desktop/cmp/input/Checkbox.ts +3 -3
- package/desktop/cmp/input/CodeInput.ts +2 -1
- package/desktop/cmp/input/DateInput.ts +128 -123
- package/desktop/cmp/input/JsonInput.ts +1 -1
- package/desktop/cmp/input/NumberInput.ts +3 -2
- package/desktop/cmp/input/RadioInput.ts +3 -1
- package/desktop/cmp/input/Select.ts +31 -4
- package/desktop/cmp/input/SwitchInput.ts +2 -1
- package/desktop/cmp/input/TextArea.ts +3 -3
- package/desktop/cmp/input/TextInput.ts +51 -47
- package/desktop/cmp/panel/Panel.ts +21 -19
- package/desktop/cmp/panel/impl/ResizeContainer.ts +3 -2
- package/desktop/cmp/pinpad/impl/PinPad.ts +4 -3
- package/desktop/cmp/record/RecordActionBar.ts +12 -3
- package/desktop/cmp/record/impl/RecordActionButton.ts +1 -0
- package/desktop/cmp/rest/Actions.ts +10 -5
- package/desktop/cmp/rest/RestGrid.ts +20 -6
- package/desktop/cmp/rest/impl/RestForm.ts +5 -4
- package/desktop/cmp/rest/impl/RestGridToolbar.ts +3 -2
- package/desktop/cmp/tab/TabSwitcher.ts +8 -3
- package/desktop/cmp/tab/impl/Tab.ts +2 -1
- package/desktop/cmp/tab/impl/TabContainer.ts +18 -15
- package/desktop/cmp/toolbar/Toolbar.ts +3 -1
- package/desktop/cmp/treemap/SplitTreeMap.ts +2 -1
- package/desktop/cmp/treemap/TreeMap.ts +5 -3
- package/desktop/cmp/zoneGrid/impl/ZoneMapper.scss +71 -0
- package/desktop/cmp/zoneGrid/impl/ZoneMapper.ts +232 -0
- package/desktop/cmp/zoneGrid/impl/ZoneMapperDialog.ts +35 -0
- package/dynamics/desktop.ts +2 -0
- package/dynamics/mobile.ts +2 -0
- package/inspector/instances/InstancesModel.ts +2 -2
- package/mobile/appcontainer/AppContainer.ts +2 -0
- package/mobile/cmp/button/ZoneMapperButton.ts +41 -0
- package/mobile/cmp/button/index.ts +1 -0
- package/mobile/cmp/error/ErrorMessage.ts +4 -4
- package/mobile/cmp/input/Select.scss +1 -0
- package/mobile/cmp/input/Select.ts +7 -0
- package/mobile/cmp/input/TextInput.ts +1 -0
- package/mobile/cmp/panel/DialogPanel.scss +18 -6
- package/mobile/cmp/panel/DialogPanel.ts +3 -1
- package/mobile/cmp/zoneGrid/impl/ZoneMapper.scss +67 -0
- package/mobile/cmp/zoneGrid/impl/ZoneMapper.ts +236 -0
- package/package.json +4 -3
- package/styles/vars.scss +3 -3
- package/svc/InspectorService.ts +1 -1
- package/utils/js/DomUtils.ts +10 -0
- package/utils/js/LangUtils.ts +10 -0
- package/utils/js/TestUtils.ts +9 -0
- package/utils/js/index.ts +1 -0
package/cmp/grid/Grid.ts
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
import composeRefs from '@seznam/compose-react-refs';
|
|
8
8
|
import {agGrid, AgGrid} from '@xh/hoist/cmp/ag-grid';
|
|
9
9
|
import {getTreeStyleClasses} from '@xh/hoist/cmp/grid';
|
|
10
|
+
import {gridHScrollbar} from '@xh/hoist/cmp/grid/impl/GridHScrollbar';
|
|
10
11
|
import {getAgGridMenuItems} from '@xh/hoist/cmp/grid/impl/MenuSupport';
|
|
11
|
-
import {div, fragment, frame} from '@xh/hoist/cmp/layout';
|
|
12
|
+
import {div, fragment, frame, vframe} from '@xh/hoist/cmp/layout';
|
|
12
13
|
import {
|
|
13
14
|
hoistCmp,
|
|
14
15
|
HoistModel,
|
|
@@ -16,29 +17,18 @@ import {
|
|
|
16
17
|
LayoutProps,
|
|
17
18
|
lookup,
|
|
18
19
|
PlainObject,
|
|
20
|
+
TestSupportProps,
|
|
19
21
|
useLocalModel,
|
|
20
22
|
uses,
|
|
21
23
|
XH
|
|
22
24
|
} from '@xh/hoist/core';
|
|
25
|
+
import {RecordSet} from '@xh/hoist/data/impl/RecordSet';
|
|
23
26
|
import {
|
|
24
27
|
colChooser as desktopColChooser,
|
|
25
28
|
gridFilterDialog,
|
|
26
29
|
ModalSupportModel
|
|
27
30
|
} from '@xh/hoist/dynamics/desktop';
|
|
28
31
|
import {colChooser as mobileColChooser} from '@xh/hoist/dynamics/mobile';
|
|
29
|
-
import {computed, observer} from '@xh/hoist/mobx';
|
|
30
|
-
import {wait} from '@xh/hoist/promise';
|
|
31
|
-
import {consumeEvent, isDisplayed, logDebug, logWithDebug} from '@xh/hoist/utils/js';
|
|
32
|
-
import {getLayoutProps} from '@xh/hoist/utils/react';
|
|
33
|
-
import classNames from 'classnames';
|
|
34
|
-
import {debounce, isEmpty, isEqual, isNil, max, maxBy, merge} from 'lodash';
|
|
35
|
-
import {createRef} from 'react';
|
|
36
|
-
import './Grid.scss';
|
|
37
|
-
import {GridModel} from './GridModel';
|
|
38
|
-
import {columnGroupHeader} from './impl/ColumnGroupHeader';
|
|
39
|
-
import {columnHeader} from './impl/ColumnHeader';
|
|
40
|
-
import {RowKeyNavSupport} from './impl/RowKeyNavSupport';
|
|
41
|
-
import {RecordSet} from '@xh/hoist/data/impl/RecordSet';
|
|
42
32
|
import {Icon} from '@xh/hoist/icon';
|
|
43
33
|
|
|
44
34
|
import type {
|
|
@@ -49,8 +39,19 @@ import type {
|
|
|
49
39
|
GridReadyEvent,
|
|
50
40
|
ProcessCellForExportParams
|
|
51
41
|
} from '@xh/hoist/kit/ag-grid';
|
|
42
|
+
import {computed, observer} from '@xh/hoist/mobx';
|
|
43
|
+
import {wait} from '@xh/hoist/promise';
|
|
44
|
+
import {consumeEvent, isDisplayed, logDebug, logWithDebug} from '@xh/hoist/utils/js';
|
|
45
|
+
import {createObservableRef, getLayoutProps} from '@xh/hoist/utils/react';
|
|
46
|
+
import classNames from 'classnames';
|
|
47
|
+
import {debounce, isEmpty, isEqual, isNil, max, maxBy, merge} from 'lodash';
|
|
48
|
+
import './Grid.scss';
|
|
49
|
+
import {GridModel} from './GridModel';
|
|
50
|
+
import {columnGroupHeader} from './impl/ColumnGroupHeader';
|
|
51
|
+
import {columnHeader} from './impl/ColumnHeader';
|
|
52
|
+
import {RowKeyNavSupport} from './impl/RowKeyNavSupport';
|
|
52
53
|
|
|
53
|
-
export interface GridProps extends HoistProps<GridModel>, LayoutProps {
|
|
54
|
+
export interface GridProps extends HoistProps<GridModel>, LayoutProps, TestSupportProps {
|
|
54
55
|
/**
|
|
55
56
|
* Options for ag-Grid's API.
|
|
56
57
|
*
|
|
@@ -90,7 +91,7 @@ export const [Grid, grid] = hoistCmp.withFactory<GridProps>({
|
|
|
90
91
|
model: uses(GridModel),
|
|
91
92
|
className: 'xh-grid',
|
|
92
93
|
|
|
93
|
-
render({model, className, ...props}, ref) {
|
|
94
|
+
render({model, className, testId, ...props}, ref) {
|
|
94
95
|
const {store, treeMode, treeStyle, highlightRowOnClick, colChooserModel, filterModel} =
|
|
95
96
|
model,
|
|
96
97
|
impl = useLocalModel(GridLocalModel),
|
|
@@ -106,14 +107,24 @@ export const [Grid, grid] = hoistCmp.withFactory<GridProps>({
|
|
|
106
107
|
highlightRowOnClick ? 'xh-grid--highlight-row-on-click' : null
|
|
107
108
|
);
|
|
108
109
|
|
|
110
|
+
const {enableFullWidthScroll} = model.experimental,
|
|
111
|
+
container = enableFullWidthScroll ? vframe : frame;
|
|
112
|
+
|
|
109
113
|
return fragment(
|
|
110
|
-
|
|
114
|
+
container({
|
|
111
115
|
className,
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
items: [
|
|
117
|
+
agGrid({
|
|
118
|
+
model: model.agGridModel,
|
|
119
|
+
...getLayoutProps(props),
|
|
120
|
+
...impl.agOptions
|
|
121
|
+
}),
|
|
122
|
+
gridHScrollbar({
|
|
123
|
+
omit: !enableFullWidthScroll,
|
|
124
|
+
gridLocalModel: impl
|
|
125
|
+
})
|
|
126
|
+
],
|
|
127
|
+
testId,
|
|
117
128
|
onKeyDown: impl.onKeyDown,
|
|
118
129
|
ref: composeRefs(impl.viewRef, ref)
|
|
119
130
|
}),
|
|
@@ -123,18 +134,18 @@ export const [Grid, grid] = hoistCmp.withFactory<GridProps>({
|
|
|
123
134
|
}
|
|
124
135
|
});
|
|
125
136
|
|
|
126
|
-
(Grid as any).MULTIFIELD_ROW_HEIGHT =
|
|
137
|
+
(Grid as any).MULTIFIELD_ROW_HEIGHT = 42;
|
|
127
138
|
|
|
128
139
|
//------------------------
|
|
129
140
|
// Implementation
|
|
130
141
|
//------------------------
|
|
131
|
-
class GridLocalModel extends HoistModel {
|
|
142
|
+
export class GridLocalModel extends HoistModel {
|
|
132
143
|
override xhImpl = true;
|
|
133
144
|
|
|
134
145
|
@lookup(GridModel)
|
|
135
146
|
private model: GridModel;
|
|
136
147
|
agOptions: GridOptions;
|
|
137
|
-
viewRef =
|
|
148
|
+
viewRef = createObservableRef<HTMLElement>();
|
|
138
149
|
private rowKeyNavSupport: RowKeyNavSupport;
|
|
139
150
|
private prevRs: RecordSet;
|
|
140
151
|
|
|
@@ -270,6 +281,11 @@ class GridLocalModel extends HoistModel {
|
|
|
270
281
|
};
|
|
271
282
|
}
|
|
272
283
|
|
|
284
|
+
// Support for FullWidthScroll
|
|
285
|
+
if (model.experimental.enableFullWidthScroll) {
|
|
286
|
+
ret.suppressHorizontalScroll = true;
|
|
287
|
+
}
|
|
288
|
+
|
|
273
289
|
return ret;
|
|
274
290
|
}
|
|
275
291
|
|
package/cmp/grid/GridModel.ts
CHANGED
|
@@ -132,7 +132,7 @@ export interface GridConfig {
|
|
|
132
132
|
/** Config with which to create a GridFilterModel, or `true` to enable default. Desktop only.*/
|
|
133
133
|
filterModel?: GridFilterModelConfig | boolean;
|
|
134
134
|
|
|
135
|
-
/** Config with which to create
|
|
135
|
+
/** Config with which to create a ColChooserModel, or boolean `true` to enable default.*/
|
|
136
136
|
colChooserModel?: ColChooserConfig | boolean;
|
|
137
137
|
|
|
138
138
|
/**
|
|
@@ -1461,7 +1461,7 @@ export class GridModel extends HoistModel {
|
|
|
1461
1461
|
if (omit) return null;
|
|
1462
1462
|
|
|
1463
1463
|
if (this.isGroupSpec(config)) {
|
|
1464
|
-
if (config.borders) borderedGroup = config;
|
|
1464
|
+
if (config.borders !== false) borderedGroup = config;
|
|
1465
1465
|
const children = compact(
|
|
1466
1466
|
config.children.map(c => this.buildColumn(c, borderedGroup))
|
|
1467
1467
|
) as Array<ColumnGroup | Column>;
|
package/cmp/grid/Types.ts
CHANGED
|
@@ -113,7 +113,7 @@ export interface GridFilterModelConfig {
|
|
|
113
113
|
fieldSpecs?: Array<string | GridFilterFieldSpecConfig>;
|
|
114
114
|
|
|
115
115
|
/** Default properties to be assigned to all fieldSpecs created by this model. */
|
|
116
|
-
fieldSpecDefaults?: GridFilterFieldSpecConfig
|
|
116
|
+
fieldSpecDefaults?: Omit<GridFilterFieldSpecConfig, 'field'>;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
/**
|
|
@@ -190,6 +190,13 @@ export interface ColumnSpec {
|
|
|
190
190
|
*/
|
|
191
191
|
sortValue?: string | ColumnSortValueFn;
|
|
192
192
|
|
|
193
|
+
/**
|
|
194
|
+
* Values to match or functions to check to determine if a value should always be sorted
|
|
195
|
+
* to the bottom, regardless of sort order. If more than one entry is provided, values will be
|
|
196
|
+
* sorted according to the order they appear here.
|
|
197
|
+
*/
|
|
198
|
+
sortToBottom?: Some<unknown | ((v: unknown) => boolean)>;
|
|
199
|
+
|
|
193
200
|
/** Function to compare cell values for sorting.*/
|
|
194
201
|
comparator?: ColumnComparator;
|
|
195
202
|
|
|
@@ -434,6 +441,7 @@ export class Column {
|
|
|
434
441
|
sortingOrder: ColumnSortSpec[];
|
|
435
442
|
absSort: boolean;
|
|
436
443
|
sortValue: string | ColumnSortValueFn;
|
|
444
|
+
sortToBottom: Array<(v: unknown) => boolean>;
|
|
437
445
|
comparator: ColumnComparator;
|
|
438
446
|
resizable: boolean;
|
|
439
447
|
sortable: boolean;
|
|
@@ -506,6 +514,7 @@ export class Column {
|
|
|
506
514
|
absSort,
|
|
507
515
|
sortingOrder,
|
|
508
516
|
sortValue,
|
|
517
|
+
sortToBottom,
|
|
509
518
|
comparator,
|
|
510
519
|
resizable,
|
|
511
520
|
movable,
|
|
@@ -600,6 +609,7 @@ export class Column {
|
|
|
600
609
|
this.absSort = withDefault(absSort, false);
|
|
601
610
|
this.sortingOrder = this.parseSortingOrder(sortingOrder);
|
|
602
611
|
this.sortValue = sortValue;
|
|
612
|
+
this.sortToBottom = this.parseSortToBottom(sortToBottom);
|
|
603
613
|
this.comparator = comparator;
|
|
604
614
|
|
|
605
615
|
this.resizable = withDefault(resizable, true);
|
|
@@ -897,12 +907,20 @@ export class Column {
|
|
|
897
907
|
if (this.comparator === undefined) {
|
|
898
908
|
// Use default comparator with appropriate inputs
|
|
899
909
|
ret.comparator = (valueA, valueB, agNodeA, agNodeB) => {
|
|
900
|
-
const
|
|
910
|
+
const {gridModel, colId} = this,
|
|
911
|
+
// Note: sortCfg and agNodes can be undefined if comparator called during show
|
|
912
|
+
// of agGrid column header set filter menu.
|
|
913
|
+
sortCfg = find(gridModel.sortBy, {colId}),
|
|
914
|
+
sortDir = sortCfg?.sort || 'asc',
|
|
915
|
+
recordA = agNodeA?.data,
|
|
901
916
|
recordB = agNodeB?.data;
|
|
902
917
|
|
|
903
918
|
valueA = this.getSortValue(valueA, recordA);
|
|
904
919
|
valueB = this.getSortValue(valueB, recordB);
|
|
905
920
|
|
|
921
|
+
const sortToBottom = this.sortToBottomComparator(valueA, valueB, sortDir);
|
|
922
|
+
if (sortToBottom !== 0) return sortToBottom;
|
|
923
|
+
|
|
906
924
|
return this.defaultComparator(valueA, valueB);
|
|
907
925
|
};
|
|
908
926
|
} else {
|
|
@@ -921,7 +939,7 @@ export class Column {
|
|
|
921
939
|
recordB,
|
|
922
940
|
column: this,
|
|
923
941
|
gridModel,
|
|
924
|
-
defaultComparator:
|
|
942
|
+
defaultComparator: this.defaultComparator,
|
|
925
943
|
agNodeA,
|
|
926
944
|
agNodeB
|
|
927
945
|
};
|
|
@@ -929,6 +947,9 @@ export class Column {
|
|
|
929
947
|
valueA = this.getSortValue(valueA, recordA);
|
|
930
948
|
valueB = this.getSortValue(valueB, recordB);
|
|
931
949
|
|
|
950
|
+
const sortToBottom = this.sortToBottomComparator(valueA, valueB, sortDir);
|
|
951
|
+
if (sortToBottom !== 0) return sortToBottom;
|
|
952
|
+
|
|
932
953
|
return this.comparator(valueA, valueB, sortDir, abs, params);
|
|
933
954
|
};
|
|
934
955
|
}
|
|
@@ -979,6 +1000,23 @@ export class Column {
|
|
|
979
1000
|
return sortCfg ? sortCfg.comparator(v1, v2) : GridSorter.defaultComparator(v1, v2);
|
|
980
1001
|
};
|
|
981
1002
|
|
|
1003
|
+
private sortToBottomComparator = (v1, v2, sortDir: 'asc' | 'desc') => {
|
|
1004
|
+
const {sortToBottom} = this;
|
|
1005
|
+
|
|
1006
|
+
if (isNil(sortToBottom)) return 0;
|
|
1007
|
+
|
|
1008
|
+
for (let fn of sortToBottom) {
|
|
1009
|
+
const v1ToBottom = fn(v1),
|
|
1010
|
+
v2ToBottom = fn(v2);
|
|
1011
|
+
const isAsc = sortDir === 'asc';
|
|
1012
|
+
if (v1ToBottom != v2ToBottom) {
|
|
1013
|
+
return v1ToBottom ? (isAsc ? 1 : -1) : isAsc ? -1 : 1;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
return 0;
|
|
1018
|
+
};
|
|
1019
|
+
|
|
982
1020
|
private defaultSetValueFn = ({value, record, store, field}) => {
|
|
983
1021
|
const data = {id: record.id};
|
|
984
1022
|
data[field] = value;
|
|
@@ -1011,6 +1049,11 @@ export class Column {
|
|
|
1011
1049
|
return sortingOrder?.map(spec => (isString(spec) || spec === null ? {sort: spec} : spec));
|
|
1012
1050
|
}
|
|
1013
1051
|
|
|
1052
|
+
private parseSortToBottom(sortToBottom): Array<(v: unknown) => boolean> {
|
|
1053
|
+
if (isNil(sortToBottom)) return null;
|
|
1054
|
+
return castArray(sortToBottom).map(v => (isFunction(v) ? v : it => it === v));
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1014
1057
|
private parseFilterable(filterable) {
|
|
1015
1058
|
if (!filterable) return false;
|
|
1016
1059
|
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import {GridLocalModel, GridModel} from '@xh/hoist/cmp/grid';
|
|
2
|
+
import {div} from '@xh/hoist/cmp/layout';
|
|
3
|
+
import {hoistCmp, HoistModel, HoistProps, useLocalModel} from '@xh/hoist/core';
|
|
4
|
+
import {makeObservable} from '@xh/hoist/mobx';
|
|
5
|
+
import {observeResize} from '@xh/hoist/utils/js';
|
|
6
|
+
import {sumBy} from 'lodash';
|
|
7
|
+
import {action, observable} from 'mobx';
|
|
8
|
+
import {createRef, RefObject} from 'react';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Implementation for Grid's full-width horizontal scrollbar, to span pinned columns
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface GridHScrollbarProps extends HoistProps<GridModel> {
|
|
16
|
+
gridLocalModel: GridLocalModel;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const gridHScrollbar = hoistCmp.factory<GridHScrollbarProps>({
|
|
20
|
+
className: 'xh-grid__grid-hscrollbar',
|
|
21
|
+
render({className}) {
|
|
22
|
+
const impl = useLocalModel(GridHScrollbarModel),
|
|
23
|
+
{scrollerRef, viewWidth, visibleColumnWidth, SCROLLBAR_SIZE} = impl;
|
|
24
|
+
|
|
25
|
+
if (viewWidth > visibleColumnWidth) return null;
|
|
26
|
+
|
|
27
|
+
return div({
|
|
28
|
+
className,
|
|
29
|
+
item: div({
|
|
30
|
+
className: `${className}__filler`,
|
|
31
|
+
style: {
|
|
32
|
+
height: SCROLLBAR_SIZE,
|
|
33
|
+
width: visibleColumnWidth
|
|
34
|
+
}
|
|
35
|
+
}),
|
|
36
|
+
onScroll: e => {
|
|
37
|
+
impl.scrollViewport((e.target as HTMLDivElement).scrollLeft);
|
|
38
|
+
},
|
|
39
|
+
ref: scrollerRef,
|
|
40
|
+
style: {
|
|
41
|
+
height: SCROLLBAR_SIZE, // TODO: make this a property on GridModel to apply to both scrollbars
|
|
42
|
+
overflowX: 'auto',
|
|
43
|
+
overflowY: 'hidden'
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
class GridHScrollbarModel extends HoistModel {
|
|
50
|
+
readonly SCROLLBAR_SIZE = 10;
|
|
51
|
+
readonly scrollerRef = createRef<HTMLDivElement>();
|
|
52
|
+
|
|
53
|
+
@observable viewWidth: number;
|
|
54
|
+
@observable private isVerticalScrollbarVisible = false;
|
|
55
|
+
|
|
56
|
+
/** Observe AG's viewport to detect when vertical scrollbar visibility changes */
|
|
57
|
+
private agViewportResizeObserver: ResizeObserver;
|
|
58
|
+
/** Observe overall view to detect when horizontal scrollbar is needed */
|
|
59
|
+
private viewResizeObserver: ResizeObserver;
|
|
60
|
+
|
|
61
|
+
get visibleColumnWidth(): number {
|
|
62
|
+
const {gridModel, SCROLLBAR_SIZE} = this;
|
|
63
|
+
return (
|
|
64
|
+
sumBy(gridModel.columnState, ({colId, hidden, width}) => {
|
|
65
|
+
if (hidden) return 0;
|
|
66
|
+
const minWidth = gridModel.getColumn(colId).minWidth ?? 0;
|
|
67
|
+
if (width) return Math.max(width, minWidth);
|
|
68
|
+
return minWidth;
|
|
69
|
+
}) + (this.isVerticalScrollbarVisible ? SCROLLBAR_SIZE : 0)
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private get agViewport(): HTMLDivElement {
|
|
74
|
+
return this.viewRef.current.querySelector('.ag-center-cols-viewport');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private get agVerticalScrollContainer(): HTMLDivElement {
|
|
78
|
+
return this.viewRef.current.querySelector('.ag-body-vertical-scroll-container');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private get gridModel(): GridModel {
|
|
82
|
+
return this.componentProps.model as GridModel;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private get viewRef(): RefObject<HTMLElement> {
|
|
86
|
+
return this.componentProps.gridLocalModel.viewRef;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
constructor() {
|
|
90
|
+
super();
|
|
91
|
+
makeObservable(this);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
scrollScroller(left: number) {
|
|
95
|
+
this.scrollerRef.current.scrollLeft = left;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
scrollViewport(left: number) {
|
|
99
|
+
this.agViewport.scrollLeft = left;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
override afterLinked() {
|
|
103
|
+
this.addReaction({
|
|
104
|
+
when: () => this.viewRef.current && this.gridModel.isReady,
|
|
105
|
+
run: () => {
|
|
106
|
+
const {agViewport, viewRef} = this;
|
|
107
|
+
this.viewWidth = viewRef.current.clientWidth;
|
|
108
|
+
agViewport.addEventListener('scroll', e =>
|
|
109
|
+
this.scrollScroller((e.target as HTMLDivElement).scrollLeft)
|
|
110
|
+
);
|
|
111
|
+
this.agViewportResizeObserver = observeResize(
|
|
112
|
+
() => this.onAgViewportResized(),
|
|
113
|
+
agViewport,
|
|
114
|
+
{debounce: 100}
|
|
115
|
+
);
|
|
116
|
+
this.viewResizeObserver = observeResize(
|
|
117
|
+
rect => this.onViewResized(rect),
|
|
118
|
+
viewRef.current,
|
|
119
|
+
{debounce: 100}
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
override destroy() {
|
|
126
|
+
super.destroy();
|
|
127
|
+
this.agViewportResizeObserver?.disconnect();
|
|
128
|
+
this.viewResizeObserver?.disconnect();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@action
|
|
132
|
+
private onAgViewportResized() {
|
|
133
|
+
this.isVerticalScrollbarVisible = !!this.agVerticalScrollContainer.clientHeight;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@action
|
|
137
|
+
private onViewResized({width}: DOMRect) {
|
|
138
|
+
this.viewWidth = width;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -98,7 +98,7 @@ function renderMainField(value, renderer, context) {
|
|
|
98
98
|
|
|
99
99
|
function renderSubField({colId, label}, context) {
|
|
100
100
|
const {record, gridModel} = context,
|
|
101
|
-
column = gridModel.
|
|
101
|
+
column = gridModel.getColumn(colId);
|
|
102
102
|
|
|
103
103
|
throwIf(!column, `Subfield ${colId} not found`);
|
|
104
104
|
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {DefaultHoistProps, HoistModel, HoistModelClass, useLocalModel} from '@xh/hoist/core';
|
|
8
7
|
import {FieldModel} from '@xh/hoist/cmp/form';
|
|
9
|
-
import {
|
|
8
|
+
import {DefaultHoistProps, HoistModel, HoistModelClass, useLocalModel} from '@xh/hoist/core';
|
|
9
|
+
import {action, computed, makeObservable, observable} from '@xh/hoist/mobx';
|
|
10
|
+
import {createObservableRef} from '@xh/hoist/utils/react';
|
|
10
11
|
import classNames from 'classnames';
|
|
11
12
|
import {isEqual} from 'lodash';
|
|
12
|
-
import {ForwardedRef, ReactElement, ReactInstance, useImperativeHandle
|
|
13
|
+
import {FocusEvent, ForwardedRef, ReactElement, ReactInstance, useImperativeHandle} from 'react';
|
|
13
14
|
import {findDOMNode} from 'react-dom';
|
|
14
|
-
import {createObservableRef} from '@xh/hoist/utils/react';
|
|
15
15
|
import './HoistInput.scss';
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
import {TestSupportProps} from '@xh/hoist/core';
|
|
9
|
+
|
|
10
|
+
export interface HoistInputProps extends TestSupportProps {
|
|
9
11
|
/**
|
|
10
12
|
* Field or model property name from which this component should read and write its value
|
|
11
13
|
* in controlled mode. Can be set by parent FormField.
|
package/cmp/layout/Box.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import {BoxProps, hoistCmp, HoistProps} from '@xh/hoist/core';
|
|
8
|
+
import {TEST_ID} from '@xh/hoist/utils/js';
|
|
8
9
|
import {splitLayoutProps} from '@xh/hoist/utils/react';
|
|
9
10
|
import {merge} from 'lodash';
|
|
10
11
|
import {div} from './Tags';
|
|
@@ -31,11 +32,12 @@ export const [Box, box] = hoistCmp.withFactory<BoxComponentProps>({
|
|
|
31
32
|
// Note `model` destructured off of non-layout props to avoid setting
|
|
32
33
|
// model as a bogus DOM attribute. This low-level component may easily be passed one from
|
|
33
34
|
// a parent that has not properly managed its own props.
|
|
34
|
-
let [layoutProps, {children, model, ...restProps}] = splitLayoutProps(props);
|
|
35
|
+
let [layoutProps, {children, model, testId, ...restProps}] = splitLayoutProps(props);
|
|
35
36
|
|
|
36
37
|
restProps = merge(
|
|
37
38
|
{style: {display: 'flex', overflow: 'hidden', position: 'relative'}},
|
|
38
39
|
{style: layoutProps},
|
|
40
|
+
{[TEST_ID]: testId},
|
|
39
41
|
restProps
|
|
40
42
|
);
|
|
41
43
|
|