@xh/hoist 86.0.1 → 86.2.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 +104 -0
- package/admin/AdminJsonDisplay.ts +31 -0
- package/admin/App.scss +3 -11
- package/admin/jsonsearch/impl/JsonSearchImplModel.ts +4 -4
- package/admin/tabs/cluster/instances/connpool/ConnPoolMonitorPanel.ts +3 -11
- package/admin/tabs/cluster/instances/services/DetailsPanel.ts +2 -13
- package/admin/tabs/cluster/objects/DetailPanel.ts +3 -12
- package/admin/tabs/userData/roles/RoleModel.ts +1 -19
- package/appcontainer/RouterModel.ts +2 -0
- package/appcontainer/ThemeModel.ts +41 -0
- package/appcontainer/login/LoginPanelModel.ts +6 -2
- package/build/types/admin/AdminJsonDisplay.d.ts +7 -0
- package/build/types/admin/jsonsearch/impl/JsonSearchImplModel.d.ts +2 -2
- package/build/types/appcontainer/RouterModel.d.ts +1 -0
- package/build/types/appcontainer/ThemeModel.d.ts +9 -0
- package/build/types/cmp/grid/Grid.d.ts +6 -3
- package/build/types/cmp/grid/GridModel.d.ts +11 -0
- package/build/types/cmp/grid/filter/GridFilterFieldSpec.d.ts +24 -6
- package/build/types/cmp/input/SegmentedControlOption.d.ts +44 -0
- package/build/types/cmp/input/index.d.ts +1 -0
- package/build/types/core/AppSpec.d.ts +8 -2
- package/build/types/core/HoistAuthModel.d.ts +6 -3
- package/build/types/data/filter/BaseFilterFieldSpec.d.ts +7 -1
- package/build/types/data/filter/FieldFilter.d.ts +2 -0
- package/build/types/desktop/cmp/button/grid/ExpandToLevelButton.d.ts +2 -1
- package/build/types/desktop/cmp/filechooser/FileChooserModel.d.ts +1 -1
- package/build/types/desktop/cmp/input/CodeInput.d.ts +7 -0
- package/build/types/desktop/cmp/input/NumberInput.d.ts +1 -1
- package/build/types/desktop/cmp/input/SegmentedControl.d.ts +1 -41
- package/build/types/desktop/cmp/input/impl/CalcWindowedMenuWidth.d.ts +20 -0
- package/build/types/desktop/cmp/rest/impl/RestFormModel.d.ts +1 -1
- package/build/types/format/FormatNumber.d.ts +11 -3
- package/build/types/icon/Icon.d.ts +5 -3
- package/build/types/mobile/cmp/appOption/SizingModeAppOption.d.ts +3 -3
- package/build/types/mobile/cmp/appOption/ThemeAppOption.d.ts +3 -3
- package/build/types/mobile/cmp/button/grid/ExpandToLevelButton.d.ts +2 -1
- package/build/types/mobile/cmp/input/NumberInput.d.ts +1 -1
- package/build/types/mobile/cmp/input/SegmentedControl.d.ts +47 -0
- package/build/types/mobile/cmp/input/SwitchInput.d.ts +1 -1
- package/build/types/mobile/cmp/input/TextInput.d.ts +3 -0
- package/build/types/mobile/cmp/input/index.d.ts +1 -0
- package/build/types/svc/JsonBlobService.d.ts +7 -7
- package/cmp/error/ErrorMessage.scss +2 -0
- package/cmp/grid/Grid.ts +17 -0
- package/cmp/grid/GridModel.ts +17 -5
- package/cmp/grid/filter/GridFilterFieldSpec.ts +33 -7
- package/cmp/grid/impl/GridHScrollbar.ts +10 -3
- package/cmp/grid/impl/MenuSupport.ts +2 -4
- package/cmp/input/SegmentedControlOption.ts +61 -0
- package/cmp/input/index.ts +1 -0
- package/core/AppSpec.ts +9 -1
- package/core/HoistAuthModel.ts +13 -6
- package/data/Field.ts +3 -1
- package/data/filter/BaseFilterFieldSpec.ts +16 -2
- package/data/filter/FieldFilter.ts +90 -21
- package/desktop/appcontainer/LoginPanel.ts +5 -3
- package/desktop/cmp/button/grid/ExpandToLevelButton.ts +4 -5
- package/desktop/cmp/filechooser/FileChooserModel.ts +1 -1
- package/desktop/cmp/grid/editors/NumberEditor.ts +16 -17
- package/desktop/cmp/grid/editors/SelectEditor.ts +15 -10
- package/desktop/cmp/grid/impl/filter/GridFilterDialog.ts +2 -2
- package/desktop/cmp/grid/impl/filter/headerfilter/HeaderFilterModel.ts +9 -4
- package/desktop/cmp/grid/impl/filter/headerfilter/custom/CustomRowModel.ts +2 -2
- package/desktop/cmp/grid/impl/filter/headerfilter/values/ValuesTabModel.ts +46 -7
- package/desktop/cmp/input/CheckboxButton.ts +21 -1
- package/desktop/cmp/input/CodeInput.ts +20 -3
- package/desktop/cmp/input/NumberInput.ts +1 -1
- package/desktop/cmp/input/SegmentedControl.ts +8 -51
- package/desktop/cmp/input/Select.ts +13 -2
- package/desktop/cmp/input/impl/CalcWindowedMenuWidth.ts +106 -0
- package/desktop/cmp/rest/impl/RestFormField.ts +1 -1
- package/desktop/cmp/tab/Tabs.scss +78 -27
- package/desktop/cmp/tab/dynamic/DynamicTabSwitcher.ts +7 -3
- package/docs/error-handling.md +9 -0
- package/format/FormatNumber.ts +69 -32
- package/icon/Icon.scss +13 -0
- package/icon/Icon.ts +7 -3
- package/icon/impl/IconHtml.ts +1 -1
- package/icon/index.ts +8 -0
- package/mcp/data/ts-registry.ts +1 -1
- package/mobile/appcontainer/LoginPanel.scss +53 -11
- package/mobile/appcontainer/LoginPanel.ts +69 -44
- package/mobile/cmp/appOption/SizingModeAppOption.ts +5 -16
- package/mobile/cmp/appOption/ThemeAppOption.ts +8 -25
- package/mobile/cmp/button/grid/ExpandToLevelButton.ts +4 -5
- package/mobile/cmp/error/impl/ErrorMessage.ts +4 -8
- package/mobile/cmp/input/CheckboxButton.ts +18 -1
- package/mobile/cmp/input/NumberInput.ts +1 -1
- package/mobile/cmp/input/SegmentedControl.scss +126 -0
- package/mobile/cmp/input/SegmentedControl.ts +210 -0
- package/mobile/cmp/input/SwitchInput.ts +1 -1
- package/mobile/cmp/input/TextInput.scss +7 -0
- package/mobile/cmp/input/TextInput.ts +11 -1
- package/mobile/cmp/input/index.ts +1 -0
- package/mobile/cmp/tab/impl/Tabs.scss +8 -0
- package/mobile/cmp/toolbar/Toolbar.scss +8 -0
- package/package.json +7 -7
- package/styles/vars.scss +21 -1
- package/svc/JsonBlobService.ts +21 -20
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Intent } from '@xh/hoist/core';
|
|
2
|
+
import { LocalDate } from '@xh/hoist/utils/datetime';
|
|
3
|
+
import { ReactElement } from 'react';
|
|
4
|
+
/** Primitive value types supported as a SegmentedControl option value/label. */
|
|
5
|
+
export type OptionPrimitive = string | number | boolean | LocalDate;
|
|
6
|
+
/**
|
|
7
|
+
* Option for a SegmentedControl, shared by the desktop and mobile implementations.
|
|
8
|
+
*/
|
|
9
|
+
export interface SegmentedControlOption {
|
|
10
|
+
/** Value for this option. */
|
|
11
|
+
value: OptionPrimitive;
|
|
12
|
+
/** Display label. Defaults to `value.toString()` if omitted. */
|
|
13
|
+
label?: string;
|
|
14
|
+
/** Icon element, displayed before the label. */
|
|
15
|
+
icon?: ReactElement;
|
|
16
|
+
/** True to disable this individual option. */
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Visual intent for this option - rendered as a solid fill when selected and as a subtle
|
|
20
|
+
* text-color hint when not (e.g. to flag a destructive choice). Overrides any control-level
|
|
21
|
+
* `intent` default. Defaults to the control's `intent`.
|
|
22
|
+
*/
|
|
23
|
+
intent?: Intent;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Variant of SegmentedControlOption for representing a null/"no value" selection.
|
|
27
|
+
* Label is required to force use case to override default js 'null' toString rendering.
|
|
28
|
+
*/
|
|
29
|
+
export interface SegmentedControlNullOption {
|
|
30
|
+
/** Null value for this option. */
|
|
31
|
+
value: null;
|
|
32
|
+
/** Display label - required for null options. */
|
|
33
|
+
label: string;
|
|
34
|
+
/** Icon element, displayed before the label. */
|
|
35
|
+
icon?: ReactElement;
|
|
36
|
+
/** True to disable this individual option. */
|
|
37
|
+
disabled?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Visual intent for this option - rendered as a solid fill when selected and as a subtle
|
|
40
|
+
* text-color hint when not (e.g. to flag a destructive choice). Overrides any control-level
|
|
41
|
+
* `intent` default. Defaults to the control's `intent`.
|
|
42
|
+
*/
|
|
43
|
+
intent?: Intent;
|
|
44
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ElementFactory, HoistAppModel, HoistAuthModel, HoistProps } from '@xh/hoist/core';
|
|
2
|
-
import { Component, ComponentClass, FunctionComponent } from 'react';
|
|
2
|
+
import { Component, ComponentClass, FunctionComponent, ReactElement } from 'react';
|
|
3
3
|
/**
|
|
4
4
|
* Spec for a client-side Hoist application. A config matching this class's shape is provided
|
|
5
5
|
* to {@link XHApi.renderApp} to kick-off app rendering and is available thereafter as `XH.appSpec`.
|
|
@@ -98,6 +98,11 @@ export declare class AppSpec<T extends HoistAppModel = HoistAppModel> {
|
|
|
98
98
|
lockoutPanel?: ElementFactory | FunctionComponent | Component;
|
|
99
99
|
/** Optional message to show users when denied access to app. */
|
|
100
100
|
lockoutMessage?: string;
|
|
101
|
+
/**
|
|
102
|
+
* Icon to display on the form-based login page, if active via `enableLoginForm: true`.
|
|
103
|
+
* Defaults to `Icon.shieldHalved()`.
|
|
104
|
+
*/
|
|
105
|
+
loginPanelIcon?: ReactElement;
|
|
101
106
|
/** Optional message to show on login form, if `showLoginForm: true`. */
|
|
102
107
|
loginMessage?: string;
|
|
103
108
|
/**
|
|
@@ -116,7 +121,7 @@ export declare class AppSpec<T extends HoistAppModel = HoistAppModel> {
|
|
|
116
121
|
* initialized, including a breakdown of elapsed time throughout the init process.
|
|
117
122
|
*/
|
|
118
123
|
trackAppLoad?: boolean;
|
|
119
|
-
constructor({ authModelClass, checkAccess, clientAppCode, clientAppName, componentClass, containerClass, disableWebSockets, enableXssProtection, enableLoginForm, enableLogout, idlePanel, isMobileApp, lockoutMessage, lockoutPanel, loginMessage, modelClass, showBrowserContextMenu, trackAppLoad }: {
|
|
124
|
+
constructor({ authModelClass, checkAccess, clientAppCode, clientAppName, componentClass, containerClass, disableWebSockets, enableXssProtection, enableLoginForm, enableLogout, idlePanel, isMobileApp, lockoutMessage, lockoutPanel, loginMessage, loginPanelIcon, modelClass, showBrowserContextMenu, trackAppLoad }: {
|
|
120
125
|
authModelClass?: typeof HoistAuthModel;
|
|
121
126
|
checkAccess: any;
|
|
122
127
|
clientAppCode?: string;
|
|
@@ -132,6 +137,7 @@ export declare class AppSpec<T extends HoistAppModel = HoistAppModel> {
|
|
|
132
137
|
lockoutMessage?: any;
|
|
133
138
|
lockoutPanel?: any;
|
|
134
139
|
loginMessage?: any;
|
|
140
|
+
loginPanelIcon?: any;
|
|
135
141
|
modelClass: any;
|
|
136
142
|
showBrowserContextMenu?: boolean;
|
|
137
143
|
trackAppLoad?: boolean;
|
|
@@ -43,21 +43,24 @@ export declare class HoistAuthModel extends HoistModel {
|
|
|
43
43
|
* Process a manual login, submitted by user via form.
|
|
44
44
|
* @returns identity of the user authenticated with the server; null if not yet authenticated.
|
|
45
45
|
*/
|
|
46
|
-
loginWithCredentialsAsync(username: string, password: string): Promise<IdentityInfo>;
|
|
46
|
+
loginWithCredentialsAsync(username: string, password: string, ctx?: CallContextLike): Promise<IdentityInfo>;
|
|
47
47
|
/**
|
|
48
48
|
* Logout this user with the backend server.
|
|
49
49
|
*
|
|
50
50
|
* The default implementation will call the 'logout' endpoint on the Grails server, clearing
|
|
51
51
|
* any server-side session state there. Override to manage any client-side or third-party state.
|
|
52
52
|
*/
|
|
53
|
-
logoutAsync(): Promise<void>;
|
|
53
|
+
logoutAsync(ctx?: CallContextLike): Promise<void>;
|
|
54
54
|
/**
|
|
55
55
|
* Load auth-related config from the server via an /xh/ endpoint that is specifically
|
|
56
56
|
* whitelisted by Hoist to allow access prior to user authentication. For use in bootstrapping
|
|
57
57
|
* client-side auth solutions that require configs such as OAuth endpoint URLs and client IDs.
|
|
58
58
|
* See `BaseAuthenticationService.getClientConfig()` in hoist-core.
|
|
59
|
+
*
|
|
60
|
+
* @param ctx - tracing/load context. When called from within a `completeAuthAsync` override,
|
|
61
|
+
* forward the supplied context so this fetch nests under the bootstrap trace.
|
|
59
62
|
*/
|
|
60
|
-
loadConfigAsync(): Promise<PlainObject>;
|
|
63
|
+
loadConfigAsync(ctx?: CallContextLike): Promise<PlainObject>;
|
|
61
64
|
/**
|
|
62
65
|
* Create a client-side HoistUser.
|
|
63
66
|
*
|
|
@@ -13,7 +13,12 @@ import { FieldFilterOperator } from './Types';
|
|
|
13
13
|
export interface BaseFilterFieldSpecConfig {
|
|
14
14
|
/** Identifying field name to filter on. */
|
|
15
15
|
field: string;
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Type of field, will default from related field on source if provided, or 'auto'. A `date`
|
|
18
|
+
* (timestamp) source defaults to 'localDate' so filtering compares by calendar day (range and
|
|
19
|
+
* equality operators use full-day bounds). Set explicitly to 'date' to filter by exact
|
|
20
|
+
* timestamp instead.
|
|
21
|
+
*/
|
|
17
22
|
fieldType?: FieldType;
|
|
18
23
|
/** DisplayName, will default from related field on source if provided */
|
|
19
24
|
displayName?: string;
|
|
@@ -78,5 +83,6 @@ export declare abstract class BaseFilterFieldSpec extends HoistBase {
|
|
|
78
83
|
abstract loadValuesFromSource(): any;
|
|
79
84
|
private parseOperators;
|
|
80
85
|
private getDefaultOperators;
|
|
86
|
+
private get isLocalDateFilteringTimestamp();
|
|
81
87
|
private get isEnumerableByDefault();
|
|
82
88
|
}
|
|
@@ -26,6 +26,8 @@ export declare class FieldFilter extends Filter {
|
|
|
26
26
|
*/
|
|
27
27
|
constructor({ field, op, value, valueType }: FieldFilterSpec);
|
|
28
28
|
getTestFn(store?: Store): FilterTestFn;
|
|
29
|
+
private getOpFn;
|
|
30
|
+
private getDayBoundedOpFn;
|
|
29
31
|
equals(other: Filter): boolean;
|
|
30
32
|
toJSON(): FieldFilterSpec;
|
|
31
33
|
removeFieldFilters(field?: string): Filter;
|
|
@@ -13,6 +13,7 @@ export interface ExpandToLevelButtonProps extends Omit<ButtonProps, 'title'> {
|
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* A menu button to expand a multi-level grouped or tree grid out to a desired level.
|
|
16
|
-
* Requires {@link GridConfig.levelLabels} to be configured on the bound GridModel
|
|
16
|
+
* Requires {@link GridConfig.levelLabels} to be configured on the bound GridModel - the menu offers
|
|
17
|
+
* one entry per labelled level, so a partial array will limit the available expand-to targets.
|
|
17
18
|
*/
|
|
18
19
|
export declare const ExpandToLevelButton: import("react").FC<ExpandToLevelButtonProps>, expandToLevelButton: import("@xh/hoist/core").ElementFactory<ExpandToLevelButtonProps>;
|
|
@@ -65,7 +65,7 @@ export declare class FileChooserModel extends HoistModel {
|
|
|
65
65
|
private readonly onFileRejected;
|
|
66
66
|
private readonly rejectToastMessage;
|
|
67
67
|
private readonly rejectToastSpec;
|
|
68
|
-
constructor(config
|
|
68
|
+
constructor(config?: FileChooserConfig);
|
|
69
69
|
/** Open the file browser programmatically. Typically used in a button's onClick callback.*/
|
|
70
70
|
openFileBrowser(): void;
|
|
71
71
|
/**
|
|
@@ -4,6 +4,13 @@ import './CodeInput.scss';
|
|
|
4
4
|
export interface CodeInputProps extends HoistProps, HoistInputProps, LayoutProps {
|
|
5
5
|
/** True to focus the control on render. */
|
|
6
6
|
autoFocus?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* True to automatically format content for display using the configured `formatter`.
|
|
9
|
+
* Defaults to true for `readonly` inputs - set false to opt out. May also be enabled on
|
|
10
|
+
* editable inputs, in which case content is formatted on blur (never mid-edit, so user
|
|
11
|
+
* edits and cursor position are preserved while typing).
|
|
12
|
+
*/
|
|
13
|
+
autoFormat?: boolean;
|
|
7
14
|
/** False to not commit on every change/keystroke, default true. */
|
|
8
15
|
commitOnChange?: boolean;
|
|
9
16
|
/**
|
|
@@ -36,7 +36,7 @@ export interface NumberInputProps extends HoistProps, LayoutProps, StyleProps, H
|
|
|
36
36
|
onKeyDown?: KeyboardEventHandler<HTMLInputElement>;
|
|
37
37
|
/** Text to display when control is empty. */
|
|
38
38
|
placeholder?: string;
|
|
39
|
-
/** Max decimal precision of the value, defaults to 4. */
|
|
39
|
+
/** Max decimal precision of the value, defaults to 4. Set to null for full, unrestricted precision. */
|
|
40
40
|
precision?: NumericPrecision;
|
|
41
41
|
/** Element to display inline on the right side of the input. */
|
|
42
42
|
rightElement?: ReactNode;
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import { HoistInputProps } from '@xh/hoist/cmp/input';
|
|
1
|
+
import { HoistInputProps, OptionPrimitive, SegmentedControlNullOption, SegmentedControlOption } from '@xh/hoist/cmp/input';
|
|
2
2
|
import { HoistProps, Intent } from '@xh/hoist/core';
|
|
3
3
|
import '@xh/hoist/desktop/register';
|
|
4
|
-
import { LocalDate } from '@xh/hoist/utils/datetime';
|
|
5
|
-
import { ReactElement } from 'react';
|
|
6
4
|
import './SegmentedControl.scss';
|
|
7
|
-
type OptionPrimitive = string | number | boolean | LocalDate;
|
|
8
5
|
export interface SegmentedControlProps extends HoistProps, HoistInputProps {
|
|
9
6
|
/** True to render in a compact mode with reduced sizing for space-constrained contexts. */
|
|
10
7
|
compact?: boolean;
|
|
@@ -31,42 +28,6 @@ export interface SegmentedControlProps extends HoistProps, HoistInputProps {
|
|
|
31
28
|
*/
|
|
32
29
|
outlined?: boolean;
|
|
33
30
|
}
|
|
34
|
-
export interface SegmentedControlOption {
|
|
35
|
-
/** Value for this option. */
|
|
36
|
-
value: OptionPrimitive;
|
|
37
|
-
/** Display label. Defaults to `value.toString()` if omitted. */
|
|
38
|
-
label?: string;
|
|
39
|
-
/** Icon element, displayed before the label. */
|
|
40
|
-
icon?: ReactElement;
|
|
41
|
-
/** True to disable this individual option. */
|
|
42
|
-
disabled?: boolean;
|
|
43
|
-
/**
|
|
44
|
-
* Visual intent for this option - rendered as a solid fill when selected and as a subtle
|
|
45
|
-
* text-color hint when not (e.g. to flag a destructive choice). Overrides any control-level
|
|
46
|
-
* `intent` default. Defaults to the control's `intent`.
|
|
47
|
-
*/
|
|
48
|
-
intent?: Intent;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Variant of SegmentedControlOption for representing a null/"no value" selection.
|
|
52
|
-
* Label is required to force use case to override default js 'null' toString rendering.
|
|
53
|
-
*/
|
|
54
|
-
export interface SegmentedControlNullOption {
|
|
55
|
-
/** Null value for this option. */
|
|
56
|
-
value: null;
|
|
57
|
-
/** Display label - required for null options. */
|
|
58
|
-
label: string;
|
|
59
|
-
/** Icon element, displayed before the label. */
|
|
60
|
-
icon?: ReactElement;
|
|
61
|
-
/** True to disable this individual option. */
|
|
62
|
-
disabled?: boolean;
|
|
63
|
-
/**
|
|
64
|
-
* Visual intent for this option - rendered as a solid fill when selected and as a subtle
|
|
65
|
-
* text-color hint when not (e.g. to flag a destructive choice). Overrides any control-level
|
|
66
|
-
* `intent` default. Defaults to the control's `intent`.
|
|
67
|
-
*/
|
|
68
|
-
intent?: Intent;
|
|
69
|
-
}
|
|
70
31
|
/**
|
|
71
32
|
* An input for selecting a single value from a small set of mutually exclusive options,
|
|
72
33
|
* rendered as a group of toggle buttons with clear visual indication of the active
|
|
@@ -79,4 +40,3 @@ export interface SegmentedControlNullOption {
|
|
|
79
40
|
* Built on Blueprint's SegmentedControl component.
|
|
80
41
|
*/
|
|
81
42
|
export declare const SegmentedControl: import("react").FC<SegmentedControlProps>, segmentedControl: import("@xh/hoist/core").ElementFactory<SegmentedControlProps>;
|
|
82
|
-
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Compute an explicit pixel width for a windowed (virtualized) `Select` menu.
|
|
4
|
+
*
|
|
5
|
+
* Windowed menus can't auto-size via CSS - react-window absolutely-positions rows at width:100%,
|
|
6
|
+
* so option content never widens the menu and it collapses to the control width. This restores
|
|
7
|
+
* content-based sizing using Canvas + off-screen DOM, the same render-and-measure approach as the
|
|
8
|
+
* grid's `ColumnWidthCalculator`: render each option's actual menu markup (via the configured
|
|
9
|
+
* `optionRenderer`, if any), canvas-estimate its width to pick the widest candidates, then measure
|
|
10
|
+
* only those candidates' displayed width in a hidden probe - so custom `optionRenderer`s (icons,
|
|
11
|
+
* multi-element content, etc.) size correctly. Unlike the grid (which may autosize many columns
|
|
12
|
+
* over thousands of records), this renders one menu's options once per options change.
|
|
13
|
+
*
|
|
14
|
+
* @param options - normalized (possibly grouped) Select options.
|
|
15
|
+
* @param renderOption - renders an option to its menu `ReactNode` (e.g. model.formatOptionLabel).
|
|
16
|
+
* @param portal - shared menu portal div, used to host probes so menu CSS (font, padding) applies.
|
|
17
|
+
* @returns explicit menu width in px, or null to fall back to the control width.
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export declare function calcWindowedMenuWidth(options: any[], renderOption: (opt: any) => ReactNode, portal: HTMLElement): number;
|
|
@@ -20,7 +20,7 @@ export declare class RestFormModel extends HoistModel {
|
|
|
20
20
|
del?: import("react").ReactNode | ((recs: import("@xh/hoist/data").StoreRecord[]) => import("react").ReactNode);
|
|
21
21
|
edit?: import("react").ReactNode | ((recs: import("@xh/hoist/data").StoreRecord[]) => import("react").ReactNode);
|
|
22
22
|
};
|
|
23
|
-
get actions(): (import("@xh/hoist/data").
|
|
23
|
+
get actions(): (import("@xh/hoist/data").RecordAction | import("@xh/hoist/data").RecordActionSpec)[];
|
|
24
24
|
get editors(): RestGridEditor[];
|
|
25
25
|
get gridModel(): import("../../../../cmp/grid").GridModel;
|
|
26
26
|
get store(): import("@xh/hoist/desktop/cmp/rest").RestStore;
|
|
@@ -33,8 +33,9 @@ export interface NumberFormatOptions extends Omit<FormatOptions<number>, 'toolti
|
|
|
33
33
|
*/
|
|
34
34
|
omitFourDigitComma?: boolean;
|
|
35
35
|
/**
|
|
36
|
-
* Desired number of decimal places,
|
|
37
|
-
* automatically based on the scale of the value
|
|
36
|
+
* Desired number of decimal places, 'auto' (default) to adjust the displayed precision
|
|
37
|
+
* automatically based on the scale of the value, or null for full, unrestricted precision
|
|
38
|
+
* (capped at the max supported precision, with trailing zeros trimmed).
|
|
38
39
|
*/
|
|
39
40
|
precision?: Precision;
|
|
40
41
|
/** Prefix to prepend to value (between the number and its sign). */
|
|
@@ -65,13 +66,20 @@ export interface NumberFormatOptions extends Omit<FormatOptions<number>, 'toolti
|
|
|
65
66
|
*
|
|
66
67
|
* e.g. `{precision:4, zeroPad:2}` will format `1.2` → "1.20" and `1.234` → "1.234"
|
|
67
68
|
*
|
|
68
|
-
* Default is true if a fixed precision is set, false if precision is 'auto'.
|
|
69
|
+
* Default is true if a fixed precision is set, false if precision is 'auto' or null (full).
|
|
69
70
|
*/
|
|
70
71
|
zeroPad?: ZeroPad;
|
|
71
72
|
}
|
|
72
73
|
export interface QuantityFormatOptions extends NumberFormatOptions {
|
|
74
|
+
/** True to compact values \>= 1 million into units of millions (m). Default true. */
|
|
73
75
|
useMillions?: boolean;
|
|
76
|
+
/** True to compact values \>= 1 billion into units of billions (b). Default true. */
|
|
74
77
|
useBillions?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* True to compact to m/b units only when no precision is lost, else render the full value.
|
|
80
|
+
* Default false.
|
|
81
|
+
*/
|
|
82
|
+
lossless?: boolean;
|
|
75
83
|
}
|
|
76
84
|
/** Config for pos/neg/neutral color classes. */
|
|
77
85
|
export interface ColorSpec {
|
|
@@ -3,6 +3,7 @@ import { FontAwesomeIconProps } from '@fortawesome/react-fontawesome';
|
|
|
3
3
|
import { HoistProps, Intent, Thunkable } from '@xh/hoist/core';
|
|
4
4
|
import { ReactElement } from 'react';
|
|
5
5
|
import { SetRequired } from 'type-fest';
|
|
6
|
+
import './Icon.scss';
|
|
6
7
|
export interface IconProps extends HoistProps, Partial<Omit<FontAwesomeIconProps, 'ref'>> {
|
|
7
8
|
/** Name of the icon in FontAwesome. */
|
|
8
9
|
iconName?: IconName;
|
|
@@ -232,6 +233,7 @@ export declare const Icon: {
|
|
|
232
233
|
settings(p?: IconProps): any;
|
|
233
234
|
shield(p?: IconProps): any;
|
|
234
235
|
shieldCheck(p?: IconProps): any;
|
|
236
|
+
shieldHalved(p?: IconProps): any;
|
|
235
237
|
sigma(p?: IconProps): any;
|
|
236
238
|
skull(p?: IconProps): any;
|
|
237
239
|
slashedCircle(p?: IconProps): any;
|
|
@@ -332,9 +334,9 @@ export declare const Icon: {
|
|
|
332
334
|
filename: string;
|
|
333
335
|
}): any;
|
|
334
336
|
/**
|
|
335
|
-
* Returns an empty div
|
|
336
|
-
* where an icon might otherwise go - e.g. to align a series of menu items,
|
|
337
|
-
* not have an icon but others do.
|
|
337
|
+
* Returns an empty div sized to occupy the width of a standard icon. Can be used to take up
|
|
338
|
+
* room in a layout where an icon might otherwise go - e.g. to align a series of menu items,
|
|
339
|
+
* where some items do not have an icon but others do.
|
|
338
340
|
*/
|
|
339
341
|
placeholder(opts?: IconProps): any;
|
|
340
342
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AppOptionSpec, SizingMode } from '@xh/hoist/core';
|
|
2
|
-
import {
|
|
2
|
+
import { SegmentedControlProps } from '@xh/hoist/mobile/cmp/input';
|
|
3
3
|
import { FormFieldProps } from '../form/FormField';
|
|
4
4
|
import '@xh/hoist/mobile/register';
|
|
5
5
|
interface SizingModeAppOptionSpec {
|
|
@@ -7,8 +7,8 @@ interface SizingModeAppOptionSpec {
|
|
|
7
7
|
modes?: SizingMode[];
|
|
8
8
|
/** Props for nested FormField. */
|
|
9
9
|
formFieldProps?: Partial<FormFieldProps>;
|
|
10
|
-
/** Props for nested
|
|
11
|
-
inputProps?: Partial<
|
|
10
|
+
/** Props for nested SegmentedControl. */
|
|
11
|
+
inputProps?: Partial<SegmentedControlProps>;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
14
|
* Convenience configuration for the `sizingMode` AppOption.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { AppOptionSpec } from '@xh/hoist/core';
|
|
2
2
|
import { FormFieldProps } from '@xh/hoist/mobile/cmp/form';
|
|
3
|
-
import {
|
|
3
|
+
import { SegmentedControlProps } from '@xh/hoist/mobile/cmp/input';
|
|
4
4
|
import '@xh/hoist/mobile/register';
|
|
5
5
|
interface ThemeAppOptionSpec {
|
|
6
6
|
/** Props for nested FormField */
|
|
7
7
|
formFieldProps?: Partial<FormFieldProps>;
|
|
8
|
-
/** Props for nested
|
|
9
|
-
inputProps?: Partial<
|
|
8
|
+
/** Props for nested SegmentedControl */
|
|
9
|
+
inputProps?: Partial<SegmentedControlProps>;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* Convenience configuration for the `theme` AppOption.
|
|
@@ -10,6 +10,7 @@ export interface ExpandToLevelButtonProps extends MenuButtonProps {
|
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* A menu button to expand a multi-level grouped or tree grid out to a desired level.
|
|
13
|
-
* Requires {@link GridConfig.levelLabels} to be configured on the bound GridModel
|
|
13
|
+
* Requires {@link GridConfig.levelLabels} to be configured on the bound GridModel - the menu offers
|
|
14
|
+
* one entry per labelled level, so a partial array will limit the available expand-to targets.
|
|
14
15
|
*/
|
|
15
16
|
export declare const ExpandToLevelButton: import("react").FC<ExpandToLevelButtonProps>, expandToLevelButton: import("@xh/hoist/core").ElementFactory<ExpandToLevelButtonProps>;
|
|
@@ -26,7 +26,7 @@ export interface NumberInputProps extends HoistProps, HoistInputProps, StyleProp
|
|
|
26
26
|
modifier?: string;
|
|
27
27
|
/** Text to display when control is empty. */
|
|
28
28
|
placeholder?: string;
|
|
29
|
-
/** Max decimal precision of the value, defaults to 4. */
|
|
29
|
+
/** Max decimal precision of the value, defaults to 4. Set to null for full, unrestricted precision. */
|
|
30
30
|
precision?: NumericPrecision;
|
|
31
31
|
/**
|
|
32
32
|
* Scale factor to apply when converting between the internal and external value. Useful for
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { HoistInputProps, OptionPrimitive, SegmentedControlNullOption, SegmentedControlOption } from '@xh/hoist/cmp/input';
|
|
2
|
+
import { HoistProps, Intent } from '@xh/hoist/core';
|
|
3
|
+
import '@xh/hoist/mobile/register';
|
|
4
|
+
import './SegmentedControl.scss';
|
|
5
|
+
export interface SegmentedControlProps extends HoistProps, HoistInputProps {
|
|
6
|
+
/**
|
|
7
|
+
* True (default) to render all segments at an equal width when {@link fill} is enabled,
|
|
8
|
+
* dividing the available width into equal parts regardless of label length - the conventional
|
|
9
|
+
* segmented-control appearance. Set false to instead size each segment to its content and
|
|
10
|
+
* share only the leftover space, so a longer label yields a wider segment. No effect when
|
|
11
|
+
* `fill` is false. Either way, a label too wide for its segment truncates with an ellipsis.
|
|
12
|
+
*/
|
|
13
|
+
equalSegmentWidths?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* True (default) to stretch the control to fill available width,
|
|
16
|
+
* distributing space equally among options.
|
|
17
|
+
*/
|
|
18
|
+
fill?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Default visual intent applied to the selected option, and as a subtle text-color hint
|
|
21
|
+
* to options when not selected. Serves as the default for any option that does not specify
|
|
22
|
+
* its own `intent`. Defaults to `'none'`.
|
|
23
|
+
*/
|
|
24
|
+
intent?: 'none' | Intent;
|
|
25
|
+
/**
|
|
26
|
+
* Array of available options. Each entry may be a SegmentedControlOption object
|
|
27
|
+
* with value/label/icon/disabled properties, or a primitive value used as both
|
|
28
|
+
* the value and the display label.
|
|
29
|
+
*/
|
|
30
|
+
options: Array<SegmentedControlOption | SegmentedControlNullOption | OptionPrimitive>;
|
|
31
|
+
/**
|
|
32
|
+
* True to render with an outlined style - a border around the control tray
|
|
33
|
+
* with no inner background fill. Border color follows the current intent.
|
|
34
|
+
*/
|
|
35
|
+
outlined?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* An input for selecting a single value from a small set of mutually exclusive options,
|
|
39
|
+
* rendered as a group of toggle buttons with clear visual indication of the active
|
|
40
|
+
* selection.
|
|
41
|
+
*
|
|
42
|
+
* Similar to ButtonGroupInput but driven by an `options` prop (like Select) rather than Button
|
|
43
|
+
* children, and with stronger visual differentiation between selected and unselected states.
|
|
44
|
+
* The mobile counterpart to the desktop SegmentedControl, built on Hoist's mobile Button
|
|
45
|
+
* (no Blueprint dependency).
|
|
46
|
+
*/
|
|
47
|
+
export declare const SegmentedControl: import("react").FC<SegmentedControlProps>, segmentedControl: import("@xh/hoist/core").ElementFactory<SegmentedControlProps>;
|
|
@@ -3,7 +3,7 @@ import { HoistProps, StyleProps } from '@xh/hoist/core';
|
|
|
3
3
|
import '@xh/hoist/mobile/register';
|
|
4
4
|
import './SwitchInput.scss';
|
|
5
5
|
export interface SwitchInputProps extends HoistProps, HoistInputProps, StyleProps {
|
|
6
|
-
value?:
|
|
6
|
+
value?: boolean;
|
|
7
7
|
/** Onsen modifier string */
|
|
8
8
|
modifier?: string;
|
|
9
9
|
}
|
|
@@ -2,6 +2,7 @@ import { HoistInputProps } from '@xh/hoist/cmp/input';
|
|
|
2
2
|
import { HoistProps, LayoutProps, StyleProps } from '@xh/hoist/core';
|
|
3
3
|
import '@xh/hoist/mobile/register';
|
|
4
4
|
import type { Property } from 'csstype';
|
|
5
|
+
import { ReactElement } from 'react';
|
|
5
6
|
import './TextInput.scss';
|
|
6
7
|
export interface TextInputProps extends HoistProps, HoistInputProps, StyleProps, LayoutProps {
|
|
7
8
|
value?: string;
|
|
@@ -27,6 +28,8 @@ export interface TextInputProps extends HoistProps, HoistInputProps, StyleProps,
|
|
|
27
28
|
commitOnChange?: boolean;
|
|
28
29
|
/** True to show a "clear" button aligned to the right of the control. Defaults to false. */
|
|
29
30
|
enableClear?: boolean;
|
|
31
|
+
/** Icon to display inline on the left side of the input. */
|
|
32
|
+
leftIcon?: ReactElement;
|
|
30
33
|
/** Onsen modifier string */
|
|
31
34
|
modifier?: string;
|
|
32
35
|
/** Text to display when control is empty */
|
|
@@ -41,21 +41,21 @@ export declare class JsonBlobService extends HoistService {
|
|
|
41
41
|
telemetryPrefix: string;
|
|
42
42
|
static instance: JsonBlobService;
|
|
43
43
|
/** Retrieve a single JSONBlob by its unique token. */
|
|
44
|
-
getAsync(token: string): Promise<JsonBlob>;
|
|
44
|
+
getAsync(token: string, ctx?: CallContextLike): Promise<JsonBlob>;
|
|
45
45
|
/** Retrieve all blobs of a particular type that are visible to the current user. */
|
|
46
46
|
listAsync(spec: {
|
|
47
47
|
type: string;
|
|
48
48
|
includeValue?: boolean;
|
|
49
|
-
ctx
|
|
49
|
+
ctx?: CallContextLike;
|
|
50
50
|
}): Promise<JsonBlob[]>;
|
|
51
51
|
/** Persist a new JSONBlob back to the server. */
|
|
52
|
-
createAsync({ acl, description, type, meta, name, value }: Partial<JsonBlob
|
|
52
|
+
createAsync({ acl, description, type, meta, name, value }: Partial<JsonBlob>, ctx?: CallContextLike): Promise<JsonBlob>;
|
|
53
53
|
/** Modify mutable properties of an existing JSONBlob, as identified by its unique token. */
|
|
54
|
-
updateAsync(token: string, update: Partial<JsonBlob
|
|
54
|
+
updateAsync(token: string, update: Partial<JsonBlob>, ctx?: CallContextLike): Promise<JsonBlob>;
|
|
55
55
|
/** Create or update a blob for a user with the existing type and name. */
|
|
56
|
-
createOrUpdateAsync(type: string, name: string, data: Partial<JsonBlob
|
|
56
|
+
createOrUpdateAsync(type: string, name: string, data: Partial<JsonBlob>, ctx?: CallContextLike): Promise<JsonBlob>;
|
|
57
57
|
/** Find a blob owned by this user with a specific type and name. If none exists, return null. */
|
|
58
|
-
findAsync(type: string, name: string): Promise<JsonBlob>;
|
|
58
|
+
findAsync(type: string, name: string, ctx?: CallContextLike): Promise<JsonBlob>;
|
|
59
59
|
/** Archive (soft-delete) an existing JSONBlob, as identified by its unique token. */
|
|
60
|
-
archiveAsync(token: string): Promise<JsonBlob>;
|
|
60
|
+
archiveAsync(token: string, ctx?: CallContextLike): Promise<JsonBlob>;
|
|
61
61
|
}
|
package/cmp/grid/Grid.ts
CHANGED
|
@@ -48,6 +48,7 @@ import {consumeEvent, isDisplayed, logWithDebug} from '@xh/hoist/utils/js';
|
|
|
48
48
|
import {composeRefs, createObservableRef, getLayoutProps} from '@xh/hoist/utils/react';
|
|
49
49
|
import classNames from 'classnames';
|
|
50
50
|
import {compact, debounce, isBoolean, isEmpty, isEqual, isNil, max, maxBy, merge} from 'lodash';
|
|
51
|
+
import {type MouseEvent} from 'react';
|
|
51
52
|
import './Grid.scss';
|
|
52
53
|
import {GridModel} from './GridModel';
|
|
53
54
|
import {columnGroupHeader} from './impl/ColumnGroupHeader';
|
|
@@ -135,6 +136,7 @@ export const [Grid, grid] = hoistCmp.withFactory<GridProps>({
|
|
|
135
136
|
],
|
|
136
137
|
testId,
|
|
137
138
|
onKeyDown: impl.onKeyDown,
|
|
139
|
+
onMouseDown: impl.onViewMouseDown,
|
|
138
140
|
ref: composeRefs(impl.viewRef, model.viewRef, ref)
|
|
139
141
|
}),
|
|
140
142
|
colChooserModel ? platformColChooser({model: colChooserModel}) : null,
|
|
@@ -151,6 +153,10 @@ export const [Grid, grid] = hoistCmp.withFactory<GridProps>({
|
|
|
151
153
|
export class GridLocalModel extends HoistModel {
|
|
152
154
|
override xhImpl = true;
|
|
153
155
|
|
|
156
|
+
// Structural "empty" grid space.
|
|
157
|
+
private static EMPTY_SPACE_SELECTOR =
|
|
158
|
+
'.ag-body-viewport, .ag-center-cols-viewport, .ag-center-cols-container, .ag-row';
|
|
159
|
+
|
|
154
160
|
@lookup(GridModel)
|
|
155
161
|
private model: GridModel;
|
|
156
162
|
agOptions: GridOptions;
|
|
@@ -844,6 +850,17 @@ export class GridLocalModel extends HoistModel {
|
|
|
844
850
|
return this.rowKeyNavSupport?.navigateToNextCell(agParams);
|
|
845
851
|
};
|
|
846
852
|
|
|
853
|
+
// `stopEditingWhenCellsLoseFocus` doesn't fire on clicks in empty grid space (focus stays in
|
|
854
|
+
// the grid), so commit the active edit on those clicks ourselves. Require exact match on empty
|
|
855
|
+
// space to avoid interfering with cell editors.
|
|
856
|
+
onViewMouseDown = (evt: MouseEvent) => {
|
|
857
|
+
const {model} = this,
|
|
858
|
+
target = evt.target as HTMLElement;
|
|
859
|
+
if (model.isEditing && target.matches(GridLocalModel.EMPTY_SPACE_SELECTOR)) {
|
|
860
|
+
model.agApi?.stopEditing();
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
|
|
847
864
|
onCellMouseDown = evt => {
|
|
848
865
|
const {model} = this;
|
|
849
866
|
if (model.highlightRowOnClick) {
|
package/cmp/grid/GridModel.ts
CHANGED
|
@@ -305,6 +305,10 @@ export interface GridConfig {
|
|
|
305
305
|
* expand/collapse options in the default context menu will be enhanced to allow users to
|
|
306
306
|
* expand/collapse to a specific level. See {@link GroupingChooserModel.valueDisplayNames}
|
|
307
307
|
* for a convenient getter that will satisfy this API when a GroupingChooser is in play.
|
|
308
|
+
*
|
|
309
|
+
* Labels are matched to levels top-down and need not cover the full depth of the grid - provide
|
|
310
|
+
* a partial array to label only the top levels (e.g. when deeper levels should not be
|
|
311
|
+
* expand-to targets). Deeper, unlabelled levels are omitted from the menu.
|
|
308
312
|
*/
|
|
309
313
|
levelLabels?: Thunkable<string[]>;
|
|
310
314
|
|
|
@@ -1174,15 +1178,23 @@ export class GridModel extends HoistModel {
|
|
|
1174
1178
|
|
|
1175
1179
|
/**
|
|
1176
1180
|
* Get the resolved level labels for the current state of the grid.
|
|
1181
|
+
* An over-long array is truncated to the current `maxDepth`.
|
|
1177
1182
|
*/
|
|
1178
1183
|
get resolvedLevelLabels(): string[] {
|
|
1179
1184
|
const {maxDepth, levelLabels} = this,
|
|
1180
1185
|
ret = executeIfFunction(levelLabels);
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
+
return !isEmpty(ret) ? take(ret, maxDepth + 1) : null;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
/**
|
|
1190
|
+
* True if the given `resolvedLevelLabels` index is the grid's current expand level - used to
|
|
1191
|
+
* mark the active item in the "Expand to..." menu. The deepest labelled level counts as current
|
|
1192
|
+
* whenever the grid is expanded to or beyond it.
|
|
1193
|
+
*/
|
|
1194
|
+
isCurrentExpandLevel(idx: number): boolean {
|
|
1195
|
+
const {expandLevel, resolvedLevelLabels} = this,
|
|
1196
|
+
lastIdx = resolvedLevelLabels?.length - 1;
|
|
1197
|
+
return expandLevel === idx || (idx === lastIdx && expandLevel > lastIdx);
|
|
1186
1198
|
}
|
|
1187
1199
|
|
|
1188
1200
|
/**
|