@xh/hoist 67.0.0-SNAPSHOT.1724719449021 → 67.0.0-SNAPSHOT.1724967981403

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 CHANGED
@@ -23,6 +23,8 @@
23
23
  * `GridModel` will now accept `contextMenu: false` to omit context menus.
24
24
  * New bindable `AppContainerModel.intializingLoadMaskMessage` property to allow apps to customize
25
25
  the loading mask message shown during app initialization.
26
+ * Enhanced `select` component with new `emptyValue` prop, allowing for a custom value to be returned
27
+ when the control is empty (vs default of `null`). Expected usage is `[]` when `enableMulti:true`.
26
28
 
27
29
  ### 🐞 Bug Fixes
28
30
 
@@ -14,6 +14,11 @@ export interface SelectProps extends HoistProps, HoistInputProps, LayoutProps {
14
14
  createMessageFn?: (query: string) => string;
15
15
  /** True (default) to close the menu after each selection. */
16
16
  closeMenuOnSelect?: boolean;
17
+ /**
18
+ * Value to use when the input is empty (default `null`).
19
+ * Recommended usage is `[]` when `enableMulti` is true to ensure value is always an array.
20
+ */
21
+ emptyValue?: any;
17
22
  /** True to show a "clear" button at the right of the control. */
18
23
  enableClear?: boolean;
19
24
  /** True to accept and commit input values not present in options or returned by a query. */
@@ -26,8 +31,8 @@ export interface SelectProps extends HoistProps, HoistInputProps, LayoutProps {
26
31
  /** True to allow entry/selection of multiple values - "tag picker" style. */
27
32
  enableMulti?: boolean;
28
33
  /**
29
- * True to enable tooltips on selected values. Enable when the space
30
- * available to the select component might not support showing the value's full text.
34
+ * True to enable tooltips on selected values. Enable when the space available to the
35
+ * component might not support showing the value's full text.
31
36
  */
32
37
  enableTooltips?: boolean;
33
38
  /**
@@ -39,14 +39,18 @@ const unauthorizedMessage = hoistCmp.factory<AppContainerModel>({
39
39
  render({model}) {
40
40
  const {identityService} = XH,
41
41
  {appSpec, appStateModel} = model,
42
+ {isImpersonating} = identityService,
42
43
  user = XH.getUser(),
44
+ authMsg = isImpersonating
45
+ ? `You are impersonating ${user.username}`
46
+ : `You are logged in as ${user.username}`,
43
47
  roleMsg = isEmpty(user.roles)
44
48
  ? 'no roles assigned'
45
49
  : `the roles [${user.roles.join(', ')}]`;
46
50
 
47
51
  return div(
48
52
  p(appStateModel.accessDeniedMessage ?? ''),
49
- p(`You are logged in as ${user.username} and have ${roleMsg}.`),
53
+ p(`${authMsg} and have ${roleMsg}.`),
50
54
  p({
51
55
  item: appSpec.lockoutMessage,
52
56
  omit: !appSpec.lockoutMessage
@@ -60,7 +64,7 @@ const unauthorizedMessage = hoistCmp.factory<AppContainerModel>({
60
64
  }),
61
65
  hspacer(5),
62
66
  button({
63
- omit: !identityService.isImpersonating,
67
+ omit: !isImpersonating,
64
68
  icon: Icon.impersonate(),
65
69
  text: 'End Impersonation',
66
70
  minimal: false,
@@ -28,7 +28,7 @@ import {
28
28
  } from '@xh/hoist/kit/react-select';
29
29
  import {action, bindable, makeObservable, observable, override} from '@xh/hoist/mobx';
30
30
  import {wait} from '@xh/hoist/promise';
31
- import {elemWithin, getTestId, TEST_ID, throwIf, withDefault, mergeDeep} from '@xh/hoist/utils/js';
31
+ import {elemWithin, getTestId, mergeDeep, TEST_ID, throwIf, withDefault} from '@xh/hoist/utils/js';
32
32
  import {createObservableRef, getLayoutProps} from '@xh/hoist/utils/react';
33
33
  import classNames from 'classnames';
34
34
  import debouncePromise from 'debounce-promise';
@@ -52,6 +52,12 @@ export interface SelectProps extends HoistProps, HoistInputProps, LayoutProps {
52
52
  /** True (default) to close the menu after each selection. */
53
53
  closeMenuOnSelect?: boolean;
54
54
 
55
+ /**
56
+ * Value to use when the input is empty (default `null`).
57
+ * Recommended usage is `[]` when `enableMulti` is true to ensure value is always an array.
58
+ */
59
+ emptyValue?: any;
60
+
55
61
  /** True to show a "clear" button at the right of the control. */
56
62
  enableClear?: boolean;
57
63
 
@@ -68,8 +74,8 @@ export interface SelectProps extends HoistProps, HoistInputProps, LayoutProps {
68
74
  enableMulti?: boolean;
69
75
 
70
76
  /**
71
- * True to enable tooltips on selected values. Enable when the space
72
- * available to the select component might not support showing the value's full text.
77
+ * True to enable tooltips on selected values. Enable when the space available to the
78
+ * component might not support showing the value's full text.
73
79
  */
74
80
  enableTooltips?: boolean;
75
81
 
@@ -236,6 +242,10 @@ class SelectInputModel extends HoistInputModel {
236
242
  return !!this.componentProps.enableMulti;
237
243
  }
238
244
 
245
+ get emptyValue(): any {
246
+ return this.componentProps.emptyValue ?? null;
247
+ }
248
+
239
249
  get filterMode(): boolean {
240
250
  return this.componentProps.enableFilter ?? true;
241
251
  }
@@ -424,11 +434,10 @@ class SelectInputModel extends HoistInputModel {
424
434
  // (Exception for a null value, which we will only accept if explicitly present in options.)
425
435
  override toInternal(external) {
426
436
  if (this.multiMode) {
427
- if (external == null) external = []; // avoid [null]
437
+ if (external == null || isEqual(external, this.emptyValue)) external = []; // avoid [null]
428
438
  return castArray(external).map(it => this.findOption(it, !isNil(it)));
429
439
  }
430
-
431
- return this.findOption(external, !isNil(external));
440
+ return this.findOption(external, !isNil(external) && !isEqual(external, this.emptyValue));
432
441
  }
433
442
 
434
443
  private findOption(value, createIfNotFound, options = this.internalOptions) {
@@ -446,10 +455,10 @@ class SelectInputModel extends HoistInputModel {
446
455
  }
447
456
 
448
457
  override toExternal(internal) {
449
- if (isNil(internal)) return null;
458
+ if (isNil(internal)) return this.emptyValue;
450
459
 
451
460
  if (this.multiMode) {
452
- if (isEmpty(internal)) return null;
461
+ if (isEmpty(internal)) return this.emptyValue;
453
462
  return castArray(internal).map(it => it.value);
454
463
  }
455
464
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "67.0.0-SNAPSHOT.1724719449021",
3
+ "version": "67.0.0-SNAPSHOT.1724967981403",
4
4
  "description": "Hoist add-on for building and deploying React Applications.",
5
5
  "repository": "github:xh/hoist-react",
6
6
  "homepage": "https://xh.io",