@xh/hoist 86.0.1 → 86.1.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 +58 -0
- 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/appcontainer/RouterModel.d.ts +1 -0
- package/build/types/appcontainer/ThemeModel.d.ts +9 -0
- 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/filechooser/FileChooserModel.d.ts +1 -1
- package/build/types/desktop/cmp/input/SegmentedControl.d.ts +1 -41
- package/build/types/icon/Icon.d.ts +1 -0
- 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/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/filter/GridFilterFieldSpec.ts +5 -2
- package/cmp/grid/impl/GridHScrollbar.ts +10 -3
- 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/filechooser/FileChooserModel.ts +1 -1
- 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/input/SegmentedControl.ts +8 -51
- package/desktop/cmp/tab/Tabs.scss +6 -2
- package/desktop/cmp/tab/dynamic/DynamicTabSwitcher.ts +7 -3
- package/docs/error-handling.md +9 -0
- package/icon/Icon.ts +3 -0
- package/icon/index.ts +8 -0
- 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/error/impl/ErrorMessage.ts +4 -8
- 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 +2 -2
- package/styles/vars.scss +6 -1
- package/svc/JsonBlobService.ts +21 -20
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,59 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 86.1.0 - 2026-06-22
|
|
4
|
+
|
|
5
|
+
### 🎁 New Features
|
|
6
|
+
|
|
7
|
+
* Added a mobile `SegmentedControl` input - the mobile counterpart to the desktop component, with a
|
|
8
|
+
matching `options`-driven API for selecting a single value from a set of mutually exclusive
|
|
9
|
+
choices. Built on Hoist's mobile `Button` (no Blueprint dependency).
|
|
10
|
+
* The shared `SegmentedControlOption` option types now export from `@xh/hoist/cmp/input` rather
|
|
11
|
+
than the desktop `SegmentedControl` module; update any direct type imports.
|
|
12
|
+
* Added a `leftIcon` prop to mobile `TextInput`.
|
|
13
|
+
* `FilterChooser` and grid column filters on a timestamp (`date`) field now filter by calendar day,
|
|
14
|
+
comparing against full-day bounds for range and equality operators rather than midnight - e.g.
|
|
15
|
+
`> 2023-05-31` excludes the 31st and `= 2023-05-31` matches any time that day. Filter specs now
|
|
16
|
+
default a `date` source field to `fieldType: 'localDate'`; set `fieldType: 'date'` for exact
|
|
17
|
+
timestamps. Applications using workarounds to provide similar behavior may be able to unwind that
|
|
18
|
+
behavior and rely on Hoist default behavior.
|
|
19
|
+
|
|
20
|
+
### 🐞 Bug Fixes
|
|
21
|
+
|
|
22
|
+
* Fixed the "Is blank" / "Is not blank" grid column filters for `tags`-typed fields - empty tag
|
|
23
|
+
arrays now correctly match "Is blank", and such filters are edited on the Custom tab rather than
|
|
24
|
+
producing a phantom blank entry in the Values list.
|
|
25
|
+
* Fixed an issue where `Grid` column headers could fall out of sync with body content during
|
|
26
|
+
horizontal scrolling when both `enableFullWidthScroll` and `useVirtualColumns` were enabled.
|
|
27
|
+
* Updated `DynamicTabSwitcher` to properly apply `testId` passed down by `TabContainer`.
|
|
28
|
+
* Ensure publication of `router5-plugin-browser` TS module augmentation.
|
|
29
|
+
* Set an explicit `%` unit on the `flex-basis: 0` of `TabContainer`'s flex shorthand to ensure
|
|
30
|
+
that the `0` is not interpreted as a `0px` basis and that the container sizes as expected.
|
|
31
|
+
* ⚠️Apps that upgrade to `hoist-dev-utils v13.x` and use `flex: 1 1 0` or `flex-basis: 0` should
|
|
32
|
+
verify that their flex layouts continue to work as expected and add an explicit unit if
|
|
33
|
+
not (e.g. `flex: 1 1 0%` or `flex-basis: 0%`).
|
|
34
|
+
|
|
35
|
+
### ⚙️ Technical
|
|
36
|
+
|
|
37
|
+
* `JsonBlobService` and `HoistAuthModel` now accept an optional `CallContextLike` argument on all of
|
|
38
|
+
their public methods, allowing callers to nest their fetches within an existing trace.
|
|
39
|
+
|
|
40
|
+
### ✨ Styles
|
|
41
|
+
|
|
42
|
+
* Mobile `Button` now defaults to a more touch-friendly height (40px, up from 28px) and rounder
|
|
43
|
+
corners for buttons in body content. Toolbar buttons derive their height from the toolbar size so
|
|
44
|
+
a single token drives both, landing slightly taller than before (34px, up from 28px).
|
|
45
|
+
* `SegmentedControl` adopts the same standard height for a consistent control row.
|
|
46
|
+
* ⚠️ Mobile buttons outside toolbars are now taller - verify body / form / panel layouts that
|
|
47
|
+
pair tightly with button dimensions.
|
|
48
|
+
* Mobile tab content (`TabContainer`) now takes the themed app background, so content reads
|
|
49
|
+
correctly in dark mode rather than showing through to Onsen's light default page background.
|
|
50
|
+
* Applied the active theme's `color-scheme` onto `html` and added a `theme-color` meta tag matching
|
|
51
|
+
the active theme's app-bar color, so browser chrome and overscroll / safe-area regions might
|
|
52
|
+
better match the theme.
|
|
53
|
+
|
|
54
|
+
### 📚 Libraries
|
|
55
|
+
|
|
56
|
+
* @azure/msal-browser `5.13 → 5.14`
|
|
3
57
|
|
|
4
58
|
## 86.0.1 - 2026-06-16
|
|
5
59
|
|
|
@@ -14,6 +68,10 @@
|
|
|
14
68
|
the page is hidden or unloaded, reacting to `XH.pageState` and issuing the flush via
|
|
15
69
|
`fetch({keepalive: true})` (replacing the less reliable `beforeunload` + normal-fetch approach).
|
|
16
70
|
|
|
71
|
+
### 📚 Libraries
|
|
72
|
+
|
|
73
|
+
* @azure/msal-browser `5.11 → 5.13`
|
|
74
|
+
* mobx `6.15 → 6.16`
|
|
17
75
|
|
|
18
76
|
## 86.0.0 - 2026-06-12
|
|
19
77
|
|
|
@@ -12,12 +12,10 @@ import {fragment, p} from '@xh/hoist/cmp/layout';
|
|
|
12
12
|
import {CallContext, HoistModel, LoadSpec, managed, XH} from '@xh/hoist/core';
|
|
13
13
|
import {RecordActionSpec} from '@xh/hoist/data';
|
|
14
14
|
import {actionCol, calcActionColWidth} from '@xh/hoist/desktop/cmp/grid';
|
|
15
|
-
import {fmtDate} from '@xh/hoist/format';
|
|
16
15
|
import {Icon} from '@xh/hoist/icon';
|
|
17
16
|
import {action, bindable, makeObservable, observable, runInAction} from '@xh/hoist/mobx';
|
|
18
17
|
import {wait} from '@xh/hoist/promise';
|
|
19
18
|
import {compact, groupBy, mapValues} from 'lodash';
|
|
20
|
-
import moment from 'moment/moment';
|
|
21
19
|
import {RoleEditorModel} from './editor/RoleEditorModel';
|
|
22
20
|
import {HoistRole, RoleModuleConfig} from './Types';
|
|
23
21
|
|
|
@@ -397,23 +395,7 @@ export class RoleModel extends HoistModel {
|
|
|
397
395
|
config.directoryGroupsSupported && 'effectiveDirectoryGroupNames',
|
|
398
396
|
'effectiveRoleNames',
|
|
399
397
|
'lastUpdatedBy',
|
|
400
|
-
|
|
401
|
-
field: 'lastUpdated',
|
|
402
|
-
example: 'YYYY-MM-DD',
|
|
403
|
-
valueParser: (v, op) => {
|
|
404
|
-
let ret = moment(v, ['YYYY-MM-DD', 'YYYYMMDD'], true);
|
|
405
|
-
if (!ret.isValid()) return null;
|
|
406
|
-
|
|
407
|
-
// Note special handling for '>' & '<=' queries.
|
|
408
|
-
if (['>', '<='].includes(op)) {
|
|
409
|
-
ret = moment(ret).endOf('day');
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
return ret.toDate();
|
|
413
|
-
},
|
|
414
|
-
valueRenderer: v => fmtDate(v),
|
|
415
|
-
ops: ['>', '>=', '<', '<=']
|
|
416
|
-
}
|
|
398
|
+
'lastUpdated'
|
|
417
399
|
]),
|
|
418
400
|
persistWith: {...RoleModel.PERSIST_WITH, path: 'mainFilterChooser'}
|
|
419
401
|
});
|
|
@@ -10,6 +10,8 @@ import {mergeDeep} from '@xh/hoist/utils/js';
|
|
|
10
10
|
import {isOmitted} from '@xh/hoist/utils/impl';
|
|
11
11
|
import {createRouter, Router, State} from 'router5';
|
|
12
12
|
import browserPlugin from 'router5-plugin-browser';
|
|
13
|
+
// Required so downstream consumers pick up Router TS augmentation (buildUrl, etc.).
|
|
14
|
+
import 'router5-plugin-browser';
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Top-level model for managing application routing in Hoist.
|
|
@@ -31,7 +31,14 @@ export class ThemeModel extends HoistModel {
|
|
|
31
31
|
const classList = document.body.classList;
|
|
32
32
|
classList.toggle('xh-dark', value);
|
|
33
33
|
classList.toggle('bp6-dark', value);
|
|
34
|
+
|
|
35
|
+
// Set color-scheme on the document root (<html>) so browser chrome and overscroll / safe-area
|
|
36
|
+
// regions match the theme - the theme class only reaches <body>, leaving the root light.
|
|
37
|
+
// See https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme
|
|
38
|
+
document.documentElement.style.colorScheme = value ? 'dark' : 'light';
|
|
39
|
+
|
|
34
40
|
this.darkTheme = value;
|
|
41
|
+
this.syncThemeColorMeta();
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
@action
|
|
@@ -64,4 +71,38 @@ export class ThemeModel extends HoistModel {
|
|
|
64
71
|
}
|
|
65
72
|
});
|
|
66
73
|
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Sync the `<meta name="theme-color">` tag to the active theme's app-bar color, creating it if
|
|
77
|
+
* needed. This tints browser chrome to match the app - e.g. Android Chrome's status bar and task
|
|
78
|
+
* switcher, and desktop installed PWAs. Hoist's theme is independent of the OS color scheme, so
|
|
79
|
+
* the tag's content must be updated on each theme change rather than relying on the static
|
|
80
|
+
* `media="(prefers-color-scheme)"` form. Note Safari 26+ ignores `theme-color`, instead deriving
|
|
81
|
+
* its chrome color from the page background (see the companion `color-scheme` handling above).
|
|
82
|
+
*/
|
|
83
|
+
private syncThemeColorMeta() {
|
|
84
|
+
// Resolve --xh-appbar-bg (a chain of var() fallbacks) to a concrete color via a throwaway
|
|
85
|
+
// probe - reading computed `color` fully substitutes the var chain.
|
|
86
|
+
const probe = document.createElement('div');
|
|
87
|
+
probe.style.cssText = 'display: none; color: var(--xh-appbar-bg)';
|
|
88
|
+
document.body.appendChild(probe);
|
|
89
|
+
const color = window.getComputedStyle(probe).color;
|
|
90
|
+
probe.remove();
|
|
91
|
+
|
|
92
|
+
// Bail if the color failed to resolve (e.g. styles not yet applied) rather than write a
|
|
93
|
+
// bad value - the next theme change will sync it.
|
|
94
|
+
if (!color) return;
|
|
95
|
+
|
|
96
|
+
// Maintain our own tag, marked so we can find it again on subsequent theme changes. If an
|
|
97
|
+
// app has hand-authored any theme-color tag, defer to it entirely.
|
|
98
|
+
let meta = document.querySelector('meta[name=theme-color][data-xh-managed]');
|
|
99
|
+
if (!meta) {
|
|
100
|
+
if (document.querySelector('meta[name=theme-color]')) return;
|
|
101
|
+
meta = document.createElement('meta');
|
|
102
|
+
meta.setAttribute('name', 'theme-color');
|
|
103
|
+
meta.setAttribute('data-xh-managed', '');
|
|
104
|
+
document.head.appendChild(meta);
|
|
105
|
+
}
|
|
106
|
+
meta.setAttribute('content', color);
|
|
107
|
+
}
|
|
67
108
|
}
|
|
@@ -34,8 +34,12 @@ export class LoginPanelModel extends HoistModel {
|
|
|
34
34
|
// Debounce to defend against double-click fast enough to get through masking + button disable.
|
|
35
35
|
@debounced(300)
|
|
36
36
|
async submitAsync() {
|
|
37
|
-
const {username, password, loginTask, isValid} = this;
|
|
38
|
-
if (
|
|
37
|
+
const {username, password, loginTask, isValid, loginInProgress} = this;
|
|
38
|
+
if (loginInProgress) return;
|
|
39
|
+
if (!isValid) {
|
|
40
|
+
this.warning = 'Please enter a username and password.';
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
39
43
|
|
|
40
44
|
try {
|
|
41
45
|
this.loginInProgress = true;
|
|
@@ -10,4 +10,13 @@ export declare class ThemeModel extends HoistModel {
|
|
|
10
10
|
setDarkTheme(value: boolean): void;
|
|
11
11
|
setTheme(value: Theme, persist?: boolean): void;
|
|
12
12
|
init(): void;
|
|
13
|
+
/**
|
|
14
|
+
* Sync the `<meta name="theme-color">` tag to the active theme's app-bar color, creating it if
|
|
15
|
+
* needed. This tints browser chrome to match the app - e.g. Android Chrome's status bar and task
|
|
16
|
+
* switcher, and desktop installed PWAs. Hoist's theme is independent of the OS color scheme, so
|
|
17
|
+
* the tag's content must be updated on each theme change rather than relying on the static
|
|
18
|
+
* `media="(prefers-color-scheme)"` form. Note Safari 26+ ignores `theme-color`, instead deriving
|
|
19
|
+
* its chrome color from the page background (see the companion `color-scheme` handling above).
|
|
20
|
+
*/
|
|
21
|
+
private syncThemeColorMeta;
|
|
13
22
|
}
|
|
@@ -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;
|
|
@@ -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
|
/**
|
|
@@ -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 {};
|
|
@@ -232,6 +232,7 @@ export declare const Icon: {
|
|
|
232
232
|
settings(p?: IconProps): any;
|
|
233
233
|
shield(p?: IconProps): any;
|
|
234
234
|
shieldCheck(p?: IconProps): any;
|
|
235
|
+
shieldHalved(p?: IconProps): any;
|
|
235
236
|
sigma(p?: IconProps): any;
|
|
236
237
|
skull(p?: IconProps): any;
|
|
237
238
|
slashedCircle(p?: IconProps): any;
|
|
@@ -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.
|
|
@@ -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
|
}
|
|
@@ -87,10 +87,13 @@ export class GridFilterFieldSpec extends BaseFilterFieldSpec {
|
|
|
87
87
|
// Values from current column filter. `flatMap` here unwraps the array `parseVal` returns
|
|
88
88
|
// for `tags`-typed fields, lining those values up with the scalar values produced by
|
|
89
89
|
// `Store.getValuesForFieldFilter` above. Without this, a filter value like `'foo'` would
|
|
90
|
-
// survive as `['foo']` and dedupe incorrectly.
|
|
90
|
+
// survive as `['foo']` and dedupe incorrectly. The `filter` drops the non-selectable null
|
|
91
|
+
// value carried by a blank `tags` filter.
|
|
91
92
|
const colFilterVals = flatMap(columnFilters, filter => {
|
|
92
93
|
return castArray(filter.value).flatMap(val => sourceField.parseVal(val));
|
|
93
|
-
})
|
|
94
|
+
})
|
|
95
|
+
.filter(it => sourceField.type !== 'tags' || it != null)
|
|
96
|
+
.map(it => this.toDisplayValue(it));
|
|
94
97
|
|
|
95
98
|
// Combine + unique - these are all values that *could* be shown in the filter UI.
|
|
96
99
|
const allValues = uniqBy([...allSrcVals, ...colFilterVals], this.getUniqueValue);
|