@xh/hoist 76.0.0-SNAPSHOT.1754920978016 → 76.0.0-SNAPSHOT.1755111169547

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
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## 76.0.0-SNAPSHOT - unreleased
4
4
 
5
+ ### 🎁 New Features
6
+
7
+ * Added new `extraConfirmText`, `extraConfirmLabel` properties to `MessageOptions`. Use this option
8
+ to require the specified text to be re-typed by a user when confirming a potentially destructive or disruptive action.
9
+
5
10
  ## 75.0.1 - 2025-08-11
6
11
 
7
12
  ### 🎁 New Features
@@ -32,6 +37,7 @@
32
37
  which can be activated via the `Query.provideLeaves` property.
33
38
 
34
39
  ### 🐞 Bug Fixes
40
+
35
41
  * Fixed bugs where `Store.modifyRecords`, `Store.revertRecords` and `Store.revert` were not properly
36
42
  handling changes to `SummaryRecords`.
37
43
  * Fixed minor `DashCanvas` issues with `allowAdd: false`, ensuring it does not block additions made
@@ -212,7 +212,8 @@ export class InstancesTabModel extends HoistModel {
212
212
  private async shutdownInstanceAsync(instance: PlainObject) {
213
213
  if (
214
214
  !(await XH.confirm({
215
- message: `Are you sure you wish to immediately terminate instance ${instance.name}?`,
215
+ message: `Are you sure you want to immediately terminate instance ${instance.name}?`,
216
+ extraConfirmText: instance.name,
216
217
  confirmProps: {
217
218
  icon: Icon.skull(),
218
219
  text: 'Yes, kill the instance',
@@ -135,7 +135,7 @@ export class AlertBannerModel extends HoistModel {
135
135
  @action
136
136
  removePreset(preset: PlainObject) {
137
137
  XH.confirm({
138
- message: 'Are you sure you wish to delete this preset?',
138
+ message: 'Are you sure you want to delete this preset?',
139
139
  confirmProps: {
140
140
  text: 'Remove',
141
141
  intent: 'danger',
@@ -252,7 +252,7 @@ export class AlertBannerModel extends HoistModel {
252
252
  const finalConfirm = await XH.confirm({
253
253
  message: fragment(
254
254
  p('This change will modify a live banner for all users of this application.'),
255
- p('Are you sure you wish to do this?')
255
+ p('Are you sure you want to do this?')
256
256
  ),
257
257
  confirmProps: {
258
258
  text: 'Yes, modify the banner',
@@ -8,6 +8,7 @@ import {RecategorizeDialogModel} from '@xh/hoist/admin/tabs/userData/roles/recat
8
8
  import {FilterChooserModel} from '@xh/hoist/cmp/filter';
9
9
  import {GridModel, tagsRenderer, TreeStyle} from '@xh/hoist/cmp/grid';
10
10
  import * as Col from '@xh/hoist/cmp/grid/columns';
11
+ import {fragment, p} from '@xh/hoist/cmp/layout';
11
12
  import {HoistModel, LoadSpec, managed, XH} from '@xh/hoist/core';
12
13
  import {RecordActionSpec} from '@xh/hoist/data';
13
14
  import {actionCol, calcActionColWidth} from '@xh/hoist/desktop/cmp/grid';
@@ -125,10 +126,15 @@ export class RoleModel extends HoistModel {
125
126
  if (this.readonly) return false;
126
127
 
127
128
  const confirm = await XH.confirm({
128
- message: `Are you sure you want to delete "${role.name}"? This may affect access to this application.`,
129
+ message: fragment(
130
+ p(`Are you sure you want to delete the ${role.name} role?`),
131
+ p(`This may impact access to this application.`)
132
+ ),
133
+ extraConfirmText: role.name,
129
134
  confirmProps: {
130
- text: 'Yes, delete role',
135
+ text: 'Delete role',
131
136
  intent: 'danger',
137
+ outlined: true,
132
138
  autoFocus: false
133
139
  }
134
140
  });
@@ -77,13 +77,14 @@ export class RoleEditorModel extends HoistModel {
77
77
  this.doCancel();
78
78
  } else {
79
79
  XH.confirm({
80
- message: 'You have unsaved changes. Are you sure you wish to proceed?',
80
+ message: 'You have unsaved changes. Are you sure you want to proceed?',
81
81
  cancelProps: {
82
82
  text: 'Keep editing'
83
83
  },
84
84
  confirmProps: {
85
85
  text: 'Discard Changes',
86
- intent: 'danger'
86
+ intent: 'danger',
87
+ outlined: true
87
88
  },
88
89
  onConfirm: () => this.doCancel()
89
90
  });
@@ -9,6 +9,7 @@ import {HoistModel, XH, MessageSpec, managed} from '@xh/hoist/core';
9
9
  import {action, observable, makeObservable} from '@xh/hoist/mobx';
10
10
  import {warnIf} from '@xh/hoist/utils/js';
11
11
  import {isEmpty} from 'lodash';
12
+ import {ReactNode} from 'react';
12
13
 
13
14
  /**
14
15
  * Model for a single instance of a modal dialog.
@@ -26,6 +27,7 @@ export class MessageModel extends HoistModel {
26
27
  messageKey;
27
28
  className;
28
29
  input;
30
+ extraConfirmLabel: ReactNode;
29
31
  confirmProps;
30
32
  cancelProps;
31
33
  cancelAlign;
@@ -50,6 +52,8 @@ export class MessageModel extends HoistModel {
50
52
  messageKey,
51
53
  className,
52
54
  input,
55
+ extraConfirmText,
56
+ extraConfirmLabel,
53
57
  confirmProps = {},
54
58
  cancelProps = {},
55
59
  cancelAlign = 'right',
@@ -69,10 +73,24 @@ export class MessageModel extends HoistModel {
69
73
  this.dismissable = dismissable;
70
74
  this.cancelOnDismiss = cancelOnDismiss;
71
75
 
76
+ const fields = [];
77
+
72
78
  if (input) {
73
79
  this.input = input;
74
80
  const {initialValue, rules} = input;
75
- this.formModel = new FormModel({fields: [{name: 'value', initialValue, rules}]});
81
+ fields.push({name: 'value', initialValue, rules});
82
+ }
83
+
84
+ if (extraConfirmText) {
85
+ this.extraConfirmLabel = extraConfirmLabel ?? `Enter '${extraConfirmText}' to confirm:`;
86
+ fields.push({
87
+ name: 'extraConfirm',
88
+ rules: [({value}) => (value === extraConfirmText ? null : `Confirmation required`)]
89
+ });
90
+ }
91
+
92
+ if (!isEmpty(fields)) {
93
+ this.formModel = new FormModel({fields});
76
94
  }
77
95
 
78
96
  this.confirmProps = this.parseButtonProps(confirmProps, () => this.doConfirmAsync());
@@ -98,7 +116,9 @@ export class MessageModel extends HoistModel {
98
116
  if (this.formModel) {
99
117
  await this.formModel.validateAsync();
100
118
  if (!this.formModel.isValid) return;
101
- resolvedVal = this.formModel.getData().value;
119
+ if (this.formModel.getField('value')) {
120
+ resolvedVal = this.formModel.getData().value;
121
+ }
102
122
  }
103
123
 
104
124
  this.onConfirm?.();
@@ -1,5 +1,6 @@
1
1
  import { FormModel } from '@xh/hoist/cmp/form';
2
2
  import { HoistModel, MessageSpec } from '@xh/hoist/core';
3
+ import { ReactNode } from 'react';
3
4
  /**
4
5
  * Model for a single instance of a modal dialog.
5
6
  * Not intended for direct application use.
@@ -14,6 +15,7 @@ export declare class MessageModel extends HoistModel {
14
15
  messageKey: any;
15
16
  className: any;
16
17
  input: any;
18
+ extraConfirmLabel: ReactNode;
17
19
  confirmProps: any;
18
20
  cancelProps: any;
19
21
  cancelAlign: any;
@@ -25,7 +27,7 @@ export declare class MessageModel extends HoistModel {
25
27
  _resolver: any;
26
28
  formModel: FormModel;
27
29
  isOpen: boolean;
28
- constructor({ title, icon, message, messageKey, className, input, confirmProps, cancelProps, cancelAlign, onConfirm, onCancel, dismissable, cancelOnDismiss }: MessageSpec);
30
+ constructor({ title, icon, message, messageKey, className, input, extraConfirmText, extraConfirmLabel, confirmProps, cancelProps, cancelAlign, onConfirm, onCancel, dismissable, cancelOnDismiss }: MessageSpec);
29
31
  doConfirmAsync(): Promise<void>;
30
32
  doCancel(): void;
31
33
  doEscape(): void;
@@ -72,14 +72,15 @@ export interface MessageSpec {
72
72
  */
73
73
  messageKey?: string;
74
74
  /** Config for input to be displayed (as a prompt). */
75
- input?: {
76
- /** An element specifying a HoistInput, defaults to a platform appropriate TextInput. */
77
- item?: ReactElement;
78
- /** Validation constraints to apply. */
79
- rules?: RuleLike[];
80
- /** Initial value for the input. */
81
- initialValue?: any;
82
- };
75
+ input?: MessageSpecInput;
76
+ /** If specified, user will be required to type this text when confirming. */
77
+ extraConfirmText?: string;
78
+ /**
79
+ * Text/label to inform the user of the text required to confirm.
80
+ * Only used if extraConfirmText is specified.
81
+ * Defaults to `Type '${extraConfirmText}' to confirm:`.
82
+ */
83
+ extraConfirmLabel?: ReactNode;
83
84
  /**
84
85
  * Props for primary confirm button.
85
86
  * Must provide either text or icon for button to be displayed, or use a preconfigured
@@ -106,6 +107,14 @@ export interface MessageSpec {
106
107
  /** Flag to specify whether onCancel is executed when clicking out of or escaping a popup. */
107
108
  cancelOnDismiss?: boolean;
108
109
  }
110
+ export interface MessageSpecInput {
111
+ /** An element specifying a HoistInput, defaults to a platform appropriate TextInput. */
112
+ item?: ReactElement;
113
+ /** Validation constraints to apply. */
114
+ rules?: RuleLike[];
115
+ /** Initial value for the input. */
116
+ initialValue?: any;
117
+ }
109
118
  /**
110
119
  * Configuration object for an app-wide banner.
111
120
  */
@@ -91,16 +91,17 @@ export interface MessageSpec {
91
91
  messageKey?: string;
92
92
 
93
93
  /** Config for input to be displayed (as a prompt). */
94
- input?: {
95
- /** An element specifying a HoistInput, defaults to a platform appropriate TextInput. */
96
- item?: ReactElement;
94
+ input?: MessageSpecInput;
97
95
 
98
- /** Validation constraints to apply. */
99
- rules?: RuleLike[];
96
+ /** If specified, user will be required to type this text when confirming. */
97
+ extraConfirmText?: string;
100
98
 
101
- /** Initial value for the input. */
102
- initialValue?: any;
103
- };
99
+ /**
100
+ * Text/label to inform the user of the text required to confirm.
101
+ * Only used if extraConfirmText is specified.
102
+ * Defaults to `Type '${extraConfirmText}' to confirm:`.
103
+ */
104
+ extraConfirmLabel?: ReactNode;
104
105
 
105
106
  /**
106
107
  * Props for primary confirm button.
@@ -135,6 +136,17 @@ export interface MessageSpec {
135
136
  cancelOnDismiss?: boolean;
136
137
  }
137
138
 
139
+ export interface MessageSpecInput {
140
+ /** An element specifying a HoistInput, defaults to a platform appropriate TextInput. */
141
+ item?: ReactElement;
142
+
143
+ /** Validation constraints to apply. */
144
+ rules?: RuleLike[];
145
+
146
+ /** Initial value for the input. */
147
+ initialValue?: any;
148
+ }
149
+
138
150
  /**
139
151
  * Configuration object for an app-wide banner.
140
152
  */
@@ -96,12 +96,14 @@ export abstract class BaseRow {
96
96
  }
97
97
 
98
98
  private getChildrenDatas(): ViewRowData[] {
99
- let {children, view} = this;
99
+ let {children, view} = this,
100
+ {query} = view;
100
101
 
101
- if (!children) return null;
102
+ if (!children || (children[0].isLeaf && !query.includeLeaves && !query.provideLeaves))
103
+ return null;
102
104
 
103
105
  // Skip all children in a locked node
104
- if (view.query.lockFn?.(this as any)) {
106
+ if (query.lockFn?.(this as any)) {
105
107
  this.locked = true;
106
108
  return null;
107
109
  }
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import {MessageModel} from '@xh/hoist/appcontainer/MessageModel';
8
8
  import {form} from '@xh/hoist/cmp/form';
9
- import {filler} from '@xh/hoist/cmp/layout';
9
+ import {div, filler} from '@xh/hoist/cmp/layout';
10
10
  import {hoistCmp, uses} from '@xh/hoist/core';
11
11
  import {button} from '@xh/hoist/desktop/cmp/button';
12
12
  import {formField} from '@xh/hoist/desktop/cmp/form';
@@ -35,31 +35,57 @@ export const message = hoistCmp.factory({
35
35
  title: model.title,
36
36
  icon: model.icon,
37
37
  className: classNames(className, model.className),
38
- items: [dialogBody(model.message, inputCmp()), bbar()],
38
+ items: [dialogBody(model.message, inputsCmp()), bbar()],
39
39
  onClose: () => model.doEscape(),
40
40
  ...props
41
41
  });
42
42
  }
43
43
  });
44
44
 
45
- const inputCmp = hoistCmp.factory<MessageModel>(({model}) => {
46
- const {formModel, input} = model;
45
+ const inputsCmp = hoistCmp.factory<MessageModel>(({model}) => {
46
+ const {formModel, input, extraConfirmLabel} = model;
47
47
  if (!formModel) return null;
48
- return form({
49
- model: formModel,
50
- fieldDefaults: {commitOnChange: true, minimal: true, label: null},
51
- item: formField({
52
- field: 'value',
53
- item: withDefault(
54
- input.item,
55
- textInput({
48
+
49
+ const items = [];
50
+ if (formModel.getField('value')) {
51
+ items.push(
52
+ formField({
53
+ field: 'value',
54
+ label: null,
55
+ item: withDefault(
56
+ input.item,
57
+ textInput({
58
+ autoFocus: true,
59
+ selectOnFocus: true,
60
+ onKeyDown: evt => {
61
+ if (evt.key === 'Enter') model.doConfirmAsync();
62
+ }
63
+ })
64
+ )
65
+ })
66
+ );
67
+ }
68
+ if (formModel.getField('extraConfirm')) {
69
+ items.push(
70
+ formField({
71
+ label: extraConfirmLabel,
72
+ field: 'extraConfirm',
73
+ item: textInput({
56
74
  autoFocus: true,
57
75
  selectOnFocus: true,
58
76
  onKeyDown: evt => {
59
77
  if (evt.key === 'Enter') model.doConfirmAsync();
60
78
  }
61
79
  })
62
- )
80
+ })
81
+ );
82
+ }
83
+ return form({
84
+ model: formModel,
85
+ fieldDefaults: {commitOnChange: true, minimal: true},
86
+ item: div({
87
+ className: 'xh-pad',
88
+ items
63
89
  })
64
90
  });
65
91
  });
@@ -68,13 +68,29 @@ export const message = hoistCmp.factory({
68
68
  });
69
69
 
70
70
  const inputCmp = hoistCmp.factory<MessageModel>(({model}) => {
71
- const {formModel, input} = model;
71
+ const {formModel, input, extraConfirmLabel} = model;
72
72
  if (!formModel) return null;
73
+
74
+ const items = [];
75
+ if (formModel.getField('value')) {
76
+ items.push(
77
+ formField({
78
+ field: 'value',
79
+ item: withDefault(input.item, textInput())
80
+ })
81
+ );
82
+ }
83
+ if (formModel.getField('extraConfirm')) {
84
+ items.push(
85
+ formField({
86
+ label: extraConfirmLabel,
87
+ field: 'extraConfirm',
88
+ item: textInput()
89
+ })
90
+ );
91
+ }
73
92
  return form({
74
93
  fieldDefaults: {commitOnChange: true, minimal: true, label: null},
75
- item: formField({
76
- field: 'value',
77
- item: withDefault(input.item, textInput())
78
- })
94
+ items
79
95
  });
80
96
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "76.0.0-SNAPSHOT.1754920978016",
3
+ "version": "76.0.0-SNAPSHOT.1755111169547",
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",