@singularsystems/neo-react 1.3.7 → 1.3.9

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,18 @@
2
2
 
3
3
  This file details all new features and changes to functionality. If you only need to see changes that require updates to your project, see the [breaking changes](./Breaking.md) readme.
4
4
 
5
+ ## Version 1.3.9 (4 March 2026)
6
+
7
+ - Dropdown
8
+ - Added support for grouped drop downs by specifying `groupDisplayMember` and `childListMember` props.
9
+ - ValidationSummary
10
+ - Fixed component not updating when invalid rules change.
11
+
12
+ ## Version 1.3.8 (24 Nov 2025)
13
+
14
+ - Deserialiser
15
+ - Fixed plain arrays not deserialising when marked with `ChildList` attribute.
16
+
5
17
  ## Version 1.3.7 (18 Nov 2025)
6
18
 
7
19
  - Modals shown with ModalUtils will now preserve pre-formatted text if the provided content is a string.
@@ -141,6 +141,10 @@ var ReactModelCreator = /** @class */ (function (_super) {
141
141
  };
142
142
  ReactModelCreator.prototype.setupList = function (object, propertyName, originalList) {
143
143
  var obsArray = object[propertyName];
144
+ if ((originalList === null || originalList === void 0 ? void 0 : originalList.setupProxy) === undefined) {
145
+ // This is a plain array, not a neo list.
146
+ return;
147
+ }
144
148
  List.copyMethods(obsArray);
145
149
  originalList.setupProxy(obsArray, object);
146
150
  // Don't allow list to change, just change the list contents.
@@ -9,8 +9,10 @@ interface IOption {
9
9
  value: string | number;
10
10
  label: string;
11
11
  item?: any;
12
+ itemParent?: any;
13
+ options?: IOption[];
12
14
  }
13
- interface IAutoCompleteCommonProps<T> extends ICommonEditorProps, Props<IOption, true> {
15
+ interface IAutoCompleteCommonProps<T, TChild = T> extends ICommonEditorProps, Props<IOption, true> {
14
16
  /**
15
17
  * Callback for loading data.
16
18
  */
@@ -19,28 +21,28 @@ interface IAutoCompleteCommonProps<T> extends ICommonEditorProps, Props<IOption,
19
21
  * Items to display for non async drop down, or default items to display in async drop down before typing.
20
22
  */
21
23
  items?: ItemSource<T>;
22
- valueMember?: keyof T;
23
- displayMember?: keyof T;
24
+ valueMember?: keyof TChild;
25
+ displayMember?: keyof TChild;
24
26
  /**
25
27
  * Function used to filter items out of the datasource.
26
28
  * **Note:** this will only apply to the `items` prop. */
27
- filterPredicate?: (item: T) => boolean;
29
+ filterPredicate?: (item: TChild, itemParent?: T) => boolean;
28
30
  /**
29
31
  * Function used to filter items based on what the user has typed.
30
32
  * Applies after `filterPredicate`, and is ignored if `itemSource` is specified.
31
33
  * Use if you are displaying multiple columns and need to match on any of the columns.
32
34
  */
33
- inputFilter?: (input: string, item: T) => boolean;
35
+ inputFilter?: (input: string, item: TChild, parent?: T) => boolean;
34
36
  /**
35
37
  * Property of an item to use as the description.
36
38
  *
37
39
  * Description will be shown in muted text below the item name.
38
40
  */
39
- descriptionMember?: keyof T | false;
41
+ descriptionMember?: keyof TChild | false;
40
42
  /**
41
43
  * Callback used to provide a custom option component instead of displaying the display member value.
42
44
  */
43
- option?: (item: T) => JSX.Element;
45
+ option?: (item: T | TChild) => JSX.Element;
44
46
  /**
45
47
  * Text to display when the user has typed something, but no records were returned.
46
48
  */
@@ -54,7 +56,7 @@ interface IAutoCompleteCommonProps<T> extends ICommonEditorProps, Props<IOption,
54
56
  * The removedItem parameter will be provided when removing an item from a multi select drop down.
55
57
  * The addedItem parameter will be provided when an item is selected in a multi select drop down. This is the item stored in the bindItems list.
56
58
  */
57
- onItemSelected?: (item?: T, removedItem?: Model.ISelectedItem, addedItem?: Model.ISelectedItem) => void;
59
+ onItemSelected?: (item?: TChild, removedItem?: Model.ISelectedItem, addedItem?: Model.ISelectedItem) => void;
58
60
  /**
59
61
  * Millisecond delay from the last typed character, to when the itemSource function is called.
60
62
  * Default is 500ms.
@@ -65,6 +67,14 @@ interface IAutoCompleteCommonProps<T> extends ICommonEditorProps, Props<IOption,
65
67
  * Default is 1.
66
68
  */
67
69
  minCharacters?: number;
70
+ /**
71
+ * The group member to use as the display for grouped items.
72
+ */
73
+ groupDisplayMember?: keyof T;
74
+ /**
75
+ * Name of the property on the parent which contains a child list.
76
+ */
77
+ childListMember?: keyof T;
68
78
  }
69
79
  export interface IAutoCompleteProps {
70
80
  bind: Model.IPropertyInstance;
@@ -85,20 +95,31 @@ export interface IAutoCompleteMultiSelectProps {
85
95
  */
86
96
  fieldInfoLimit?: number;
87
97
  }
88
- export default class AutoCompleteDropDown<T> extends DropDownBase<T, IAutoCompleteCommonProps<T> & (IAutoCompleteProps | IAutoCompleteMultiSelectProps)> {
98
+ export default class AutoCompleteDropDown<T, TChild = T> extends DropDownBase<T, IAutoCompleteCommonProps<T, TChild> & (IAutoCompleteProps | IAutoCompleteMultiSelectProps), TChild> {
89
99
  static contextType: React.Context<import("../FormContext").FormContextParams>;
90
100
  context: React.ContextType<typeof FormContext>;
91
101
  private asyncSelectImplementation;
92
102
  private selectImplementation;
93
- constructor(props: IAutoCompleteCommonProps<T> & (IAutoCompleteProps | IAutoCompleteMultiSelectProps));
103
+ constructor(props: IAutoCompleteCommonProps<T, TChild> & (IAutoCompleteProps | IAutoCompleteMultiSelectProps));
94
104
  private bindProperty;
95
105
  private bindIdsList?;
96
106
  private bindIdsProperty?;
97
- static getBindProp<T>(props: IAutoCompleteProps | IAutoCompleteMultiSelectProps): Model.IPropertyInstance<any>;
107
+ static getBindProp<TChild>(props: IAutoCompleteProps | IAutoCompleteMultiSelectProps): Model.IPropertyInstance<any>;
98
108
  private onKeyPress;
99
109
  private setBindProperty;
100
110
  private mapTArrayToOptions;
111
+ private mapChildItemsToOptions;
112
+ private getGroupedOptionsFromItems;
113
+ /**
114
+ * Finds an option in the items list by value.
115
+ * Handles both flat lists and grouped (nested) items.
116
+ * @param items The items array to search in
117
+ * @param value The value to search for
118
+ * @returns The matching IOption or undefined if not found
119
+ */
120
+ private findOptionByValue;
101
121
  private onItemSelected;
122
+ private get isGrouped();
102
123
  render(): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | JSX.Element | null | undefined;
103
124
  private getOption;
104
125
  }
@@ -35,7 +35,7 @@ var AutoCompleteDropDown = /** @class */ (function (_super) {
35
35
  else {
36
36
  items = list;
37
37
  }
38
- dataReceived(_this.mapTArrayToOptions(items));
38
+ dataReceived(_this.mapTArrayToOptions((items).slice()));
39
39
  });
40
40
  }, (_b = this.props.delayMs) !== null && _b !== void 0 ? _b : 500);
41
41
  }
@@ -68,16 +68,69 @@ var AutoCompleteDropDown = /** @class */ (function (_super) {
68
68
  }
69
69
  };
70
70
  AutoCompleteDropDown.prototype.mapTArrayToOptions = function (items) {
71
- var _this = this;
71
+ if (this.isGrouped) {
72
+ var firstChildItem = this.getFirstChildItem(items, this.props.childListMember);
73
+ if (firstChildItem) {
74
+ this.findMembers(firstChildItem);
75
+ }
76
+ return this.getGroupedOptionsFromItems(items);
77
+ }
72
78
  if (items.length) {
73
79
  this.findMembers(items[0]);
74
80
  }
75
- return items.map(function (item) { return ({
76
- item: item,
77
- value: item[_this.valueMember],
78
- label: item[_this.displayMember]
81
+ return this.mapChildItemsToOptions(items);
82
+ };
83
+ AutoCompleteDropDown.prototype.mapChildItemsToOptions = function (childItems, parentItem) {
84
+ var _this = this;
85
+ return childItems.map(function (childItem) { return ({
86
+ item: childItem,
87
+ itemParent: parentItem,
88
+ value: childItem[_this.valueMember],
89
+ label: childItem[_this.displayMember]
79
90
  }); });
80
91
  };
92
+ AutoCompleteDropDown.prototype.getGroupedOptionsFromItems = function (groupedItems) {
93
+ var _this = this;
94
+ var groupedOptions = groupedItems.map(function (groupItem) {
95
+ var childOptions = _this.mapChildItemsToOptions(groupItem[_this.props.childListMember], groupItem);
96
+ return {
97
+ item: groupItem,
98
+ itemParent: null,
99
+ label: groupItem[_this.props.groupDisplayMember],
100
+ options: childOptions
101
+ };
102
+ });
103
+ return groupedOptions;
104
+ };
105
+ /**
106
+ * Finds an option in the items list by value.
107
+ * Handles both flat lists and grouped (nested) items.
108
+ * @param items The items array to search in
109
+ * @param value The value to search for
110
+ * @returns The matching IOption or undefined if not found
111
+ */
112
+ AutoCompleteDropDown.prototype.findOptionByValue = function (items, value) {
113
+ if (!items) {
114
+ return undefined;
115
+ }
116
+ if (this.isGrouped) {
117
+ // For grouped items, search within child options of each group
118
+ for (var _i = 0, items_1 = items; _i < items_1.length; _i++) {
119
+ var group = items_1[_i];
120
+ if (group.options) {
121
+ var childMatch = group.options.find(function (option) { return option.value === value; });
122
+ if (childMatch) {
123
+ return childMatch;
124
+ }
125
+ }
126
+ }
127
+ return undefined;
128
+ }
129
+ else {
130
+ // For flat items, search directly
131
+ return items.find(function (item) { return item.value === value; });
132
+ }
133
+ };
81
134
  AutoCompleteDropDown.prototype.onItemSelected = function (args, action) {
82
135
  var _a;
83
136
  var onItemSelected = this.props.onItemSelected, singleProps = this.props, multiProps = this.props;
@@ -129,6 +182,13 @@ var AutoCompleteDropDown = /** @class */ (function (_super) {
129
182
  }
130
183
  }
131
184
  };
185
+ Object.defineProperty(AutoCompleteDropDown.prototype, "isGrouped", {
186
+ get: function () {
187
+ return this.props.groupDisplayMember !== undefined && this.props.childListMember !== undefined;
188
+ },
189
+ enumerable: false,
190
+ configurable: true
191
+ });
132
192
  AutoCompleteDropDown.prototype.render = function () {
133
193
  var _this = this;
134
194
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
@@ -150,19 +210,40 @@ var AutoCompleteDropDown = /** @class */ (function (_super) {
150
210
  }
151
211
  var defaultItems = undefined;
152
212
  if (this.props.items) {
153
- var items_1 = this.getItems(this.props.items);
154
- defaultItems = this.mapTArrayToOptions(items_1);
213
+ var items_2 = [];
214
+ if (this.isGrouped) {
215
+ items_2 = this.getItems(this.props.items);
216
+ defaultItems = this.getGroupedOptionsFromItems(items_2);
217
+ }
218
+ else {
219
+ items_2 = this.getItems(this.props.items);
220
+ defaultItems = this.mapTArrayToOptions(items_2);
221
+ }
155
222
  if (filterPredicate !== undefined) {
156
- if (bindValue) {
157
- // Dont filter out the current selected item even if excluded by the filter.
158
- defaultItems = defaultItems.filter(function (option) { return bindValue.value === option.value || filterPredicate(option.item); });
223
+ if (this.isGrouped) {
224
+ var groupsWithFilteredChildren = defaultItems.map(function (group) { return (__assign(__assign({}, group), { options: group.options.filter(function (option) {
225
+ if (bindValue) {
226
+ return bindValue.value === option.value || filterPredicate(option.item, option.itemParent);
227
+ }
228
+ else {
229
+ return filterPredicate(option.item, option.itemParent);
230
+ }
231
+ }) })); });
232
+ // Remove groups that have no children after filtering
233
+ defaultItems = groupsWithFilteredChildren.filter(function (group) { return group.options.length > 0; });
159
234
  }
160
235
  else {
161
- defaultItems = defaultItems.filter(function (option) { return filterPredicate(option.item); });
236
+ if (bindValue) {
237
+ // Dont filter out the current selected item even if excluded by the filter.
238
+ defaultItems = defaultItems.filter(function (option) { return bindValue.value === option.value || filterPredicate(option.item, option.itemParent); });
239
+ }
240
+ else {
241
+ defaultItems = defaultItems.filter(function (option) { return filterPredicate(option.item, option.itemParent); });
242
+ }
162
243
  }
163
244
  }
164
- if (descriptionMember === undefined && items_1.length > 0) {
165
- if (EnumHelper.isEnumItem(items_1[0])) {
245
+ if (descriptionMember === undefined && items_2.length > 0) {
246
+ if (EnumHelper.isEnumItem(items_2[0])) {
166
247
  descriptionMember = "description";
167
248
  }
168
249
  }
@@ -186,7 +267,7 @@ var AutoCompleteDropDown = /** @class */ (function (_super) {
186
267
  var displayValue = bindDisplayProperty === null || bindDisplayProperty === void 0 ? void 0 : bindDisplayProperty.value;
187
268
  if (!bindDisplayProperty) {
188
269
  if (defaultItems) {
189
- displayValue = (_d = (_c = defaultItems.find(function (c) { return c.value === bindValue.value; })) === null || _c === void 0 ? void 0 : _c.label) !== null && _d !== void 0 ? _d : "";
270
+ displayValue = (_d = (_c = this.findOptionByValue(defaultItems, bindValue.value)) === null || _c === void 0 ? void 0 : _c.label) !== null && _d !== void 0 ? _d : "";
190
271
  }
191
272
  else {
192
273
  throw "bindDisplay is required when the items prop is not set.";
@@ -225,7 +306,7 @@ var AutoCompleteDropDown = /** @class */ (function (_super) {
225
306
  };
226
307
  if (this.props.inputFilter && !this.props.itemSource) {
227
308
  nativeProps.filterOption = function (option, input) {
228
- return !input || _this.props.inputFilter(input, option.data.item);
309
+ return !input || _this.props.inputFilter(input, option.data.item, option.data.itemParent);
229
310
  };
230
311
  }
231
312
  if (this.props.itemSource !== undefined) {
@@ -3,20 +3,20 @@ import { IBindableEditorProps } from '../PropTypes';
3
3
  import { Data } from '@singularsystems/neo-core';
4
4
  import { DropDownBase } from './DropDownBase';
5
5
  import { FormContext } from '../FormContext';
6
- export interface IDropDownSpecificProps<T> {
6
+ export interface IDropDownSpecificProps<T, TChild = T> {
7
7
  items?: T[] | Promise<T[]>;
8
8
  itemSource?: Data.IAsyncDataSource<T[]> | Data.IArrayCacheEntry<T>;
9
- valueMember?: keyof T;
10
- displayMember?: keyof T;
9
+ valueMember?: keyof TChild;
10
+ displayMember?: keyof TChild;
11
11
  /**
12
12
  * Set to true if your bind property is nullable, and you need to show a blank item in the list.
13
13
  * If your property value is null, a blank item will automatically be shown.
14
14
  */
15
15
  allowNulls?: boolean;
16
16
  /** Function used to filter items out of the datasource. */
17
- filterPredicate?: (item: T) => boolean;
17
+ filterPredicate?: (item: TChild, itemParent?: T) => boolean;
18
18
  /** Callback which fires when an item is selected. */
19
- onItemSelected?: (item?: T) => void;
19
+ onItemSelected?: (item?: TChild) => void;
20
20
  /**
21
21
  * True if the display member of the selected item should display as plain text.
22
22
  * Use this if you need a readonly display, and don't want a form control rendered.
@@ -28,18 +28,28 @@ export interface IDropDownSpecificProps<T> {
28
28
  deSelectText?: string;
29
29
  /** Set to true if the property should be sorted by value instead of display text. */
30
30
  sortByValue?: boolean;
31
+ /**
32
+ * The group member to use as the display for grouped items.
33
+ */
34
+ groupDisplayMember?: keyof T;
35
+ /**
36
+ * The child list containing the grouped items.
37
+ */
38
+ childListMember?: keyof T;
31
39
  }
32
- export interface IDropDownProps<T> extends IBindableEditorProps<HTMLSelectElement> {
33
- select: IDropDownSpecificProps<T>;
40
+ export interface IDropDownProps<T, TChild = T> extends IBindableEditorProps<HTMLSelectElement> {
41
+ select: IDropDownSpecificProps<T, TChild>;
34
42
  }
35
- export default class DropDown<T> extends DropDownBase<T, IDropDownProps<T>> {
43
+ export default class DropDown<T, TChild = T> extends DropDownBase<T, IDropDownProps<T, TChild>, TChild> {
36
44
  static contextType: React.Context<import("../FormContext").FormContextParams>;
37
45
  context: React.ContextType<typeof FormContext>;
38
46
  private selectedItem;
39
47
  private sortByValue?;
40
48
  private listIndexSymbol;
41
- constructor(props: IDropDownProps<T>);
49
+ private get isGrouped();
50
+ constructor(props: IDropDownProps<T, TChild>);
42
51
  private get items();
52
+ private get groupedItems();
43
53
  private createLookupIndex;
44
54
  private getSelectedOption;
45
55
  private onItemSelected;
@@ -15,17 +15,36 @@ var DropDown = /** @class */ (function (_super) {
15
15
  _this.fieldInfoContainer = Model.ModelFieldInfo.asModelFieldInfoContainer(_this.props.bind.attachedTo);
16
16
  return _this;
17
17
  }
18
+ Object.defineProperty(DropDown.prototype, "isGrouped", {
19
+ get: function () {
20
+ return this.props.select.groupDisplayMember !== undefined
21
+ && this.props.select.childListMember !== undefined;
22
+ },
23
+ enumerable: false,
24
+ configurable: true
25
+ });
18
26
  Object.defineProperty(DropDown.prototype, "items", {
19
27
  get: function () {
20
28
  var _this = this;
21
- var _a;
22
- var items = this.getItems((_a = this.props.select.items) !== null && _a !== void 0 ? _a : this.props.select.itemSource);
23
- if (items && items.length > 0) {
24
- var item = items[0];
25
- this.findMembers(item);
29
+ var _a, _b;
30
+ var items = [];
31
+ if (this.isGrouped) {
32
+ var groups = this.getItems((_a = this.props.select.items) !== null && _a !== void 0 ? _a : this.props.select.itemSource);
33
+ var firstChildItem = this.getFirstChildItem(groups, this.props.select.childListMember);
34
+ if (firstChildItem) {
35
+ this.findMembers(firstChildItem);
36
+ }
37
+ items = groups.flatMap(function (group) { return group[_this.props.select.childListMember]; });
38
+ }
39
+ else {
40
+ items = this.getItems((_b = this.props.select.items) !== null && _b !== void 0 ? _b : this.props.select.itemSource);
41
+ if (items && items.length > 0) {
42
+ var item = items[0];
43
+ this.findMembers(item);
44
+ }
26
45
  }
27
46
  if (this.props.select.filterPredicate !== undefined) {
28
- return items.filter(function (item) { return _this.props.bind.value === item[_this.valueMember] || _this.props.select.filterPredicate(item); });
47
+ return items.filter(function (item) { return _this.props.bind.value === item[_this.valueMember] || _this.props.select.filterPredicate(item, undefined); });
29
48
  }
30
49
  else {
31
50
  return items;
@@ -34,6 +53,31 @@ var DropDown = /** @class */ (function (_super) {
34
53
  enumerable: false,
35
54
  configurable: true
36
55
  });
56
+ Object.defineProperty(DropDown.prototype, "groupedItems", {
57
+ get: function () {
58
+ var _this = this;
59
+ var _a;
60
+ var groups = this.getItems((_a = this.props.select.items) !== null && _a !== void 0 ? _a : this.props.select.itemSource);
61
+ var firstChildItem = this.getFirstChildItem(groups, this.props.select.childListMember);
62
+ if (firstChildItem) {
63
+ this.findMembers(firstChildItem);
64
+ }
65
+ // Handle grouped filtering - filter children in each group
66
+ var groupsWithFilteredChildren = groups.map(function (group) {
67
+ var children = group[_this.props.select.childListMember];
68
+ if (_this.props.select.filterPredicate !== undefined) {
69
+ return __assign(__assign({}, group), { options: children.filter(function (item) { return _this.props.bind.value === (item)[_this.valueMember] || _this.props.select.filterPredicate(item, group); }) });
70
+ }
71
+ else {
72
+ return __assign(__assign({}, group), { options: children });
73
+ }
74
+ });
75
+ // Remove groups that have no children after filtering
76
+ return groupsWithFilteredChildren.filter(function (group) { return group.options.length > 0; });
77
+ },
78
+ enumerable: false,
79
+ configurable: true
80
+ });
37
81
  DropDown.prototype.createLookupIndex = function (list) {
38
82
  var _this = this;
39
83
  var index = {};
@@ -42,12 +86,12 @@ var DropDown = /** @class */ (function (_super) {
42
86
  list.forEach(function (item) { return index[item[_this.valueMember]] = item; });
43
87
  return index;
44
88
  };
45
- DropDown.prototype.getSelectedOption = function (value) {
89
+ DropDown.prototype.getSelectedOption = function (value, items) {
46
90
  if (!this.sortByValue) {
47
91
  this.props.bind.propertyInfo.hasSortValue = true;
48
92
  }
49
93
  if (!this.selectedItem || this.selectedItem[this.valueMember] != value) {
50
- var list = this.items;
94
+ var list = items !== null && items !== void 0 ? items : this.items;
51
95
  //@ts-ignore
52
96
  var index = list[this.listIndexSymbol];
53
97
  var indexJustCreated = false;
@@ -103,7 +147,7 @@ var DropDown = /** @class */ (function (_super) {
103
147
  var displayInfo = this.props.bind.validator.getDisplayState(this.context.validationDisplayMode);
104
148
  displayInfo.applyToDom(domProps, !splitProps.inGroup);
105
149
  var items = this.items;
106
- var selectedItem = this.getSelectedOption(this.props.bind.value); // This sets the sort value to allow sorting of drop downs in grids.
150
+ var selectedItem = this.getSelectedOption(this.props.bind.value, items); // This sets the sort value to allow sorting of drop downs in grids.
107
151
  var value = this.props.bind.value;
108
152
  var valueIsEmpty = !value;
109
153
  var notFound = items.length > 0 && !valueIsEmpty && selectedItem[this.valueMember] !== value;
@@ -132,10 +176,22 @@ var DropDown = /** @class */ (function (_super) {
132
176
  if (valueIsNull) {
133
177
  domProps.className = Utils.joinWithSpaces(domProps.className, "select-no-value");
134
178
  }
179
+ var childListMember = void 0;
180
+ var groupDisplayMember_1;
181
+ if (this.isGrouped) {
182
+ childListMember = this.props.select.childListMember;
183
+ groupDisplayMember_1 = this.props.select.groupDisplayMember;
184
+ }
135
185
  control =
136
186
  React.createElement("select", __assign({ value: valueIsNull ? "" : value, onChange: this.onItemSelected, ref: splitProps.editor.inputElement }, domProps),
137
187
  addBlankItem && React.createElement("option", { className: "option-default" }, notFound ? "(Not found): ".concat(value) : nullText),
138
- items.map(function (item) { return (React.createElement("option", { value: item[_this.valueMember], key: item[_this.valueMember] }, item[_this.displayMember])); }));
188
+ !this.isGrouped &&
189
+ this.items.map(function (item) { return (React.createElement("option", { value: item[_this.valueMember], key: item[_this.valueMember] }, item[_this.displayMember])); }),
190
+ this.isGrouped &&
191
+ this.groupedItems.map(function (group) {
192
+ var children = group.options;
193
+ return React.createElement("optgroup", { label: group[groupDisplayMember_1], key: group[groupDisplayMember_1] }, (children.map(function (item) { return (React.createElement("option", { value: item[_this.valueMember], key: item[_this.valueMember] }, item[_this.displayMember])); })));
194
+ }));
139
195
  }
140
196
  return (InputHelpers.surroundWithInputGroup(control, splitProps.editor, domProps));
141
197
  };
@@ -1,9 +1,9 @@
1
1
  import { Data, Model } from '@singularsystems/neo-core';
2
2
  import ComponentBase from '../ComponentBase';
3
3
  export type ItemSource<T> = T[] | Promise<T[]> | Data.IAsyncDataSource<T[]> | Data.IArrayCacheEntry<T>;
4
- export declare abstract class DropDownBase<T, TProps> extends ComponentBase<TProps> {
5
- protected valueMember: keyof T;
6
- protected displayMember: keyof T;
4
+ export declare abstract class DropDownBase<T, TProps, TChild = T> extends ComponentBase<TProps> {
5
+ protected valueMember: keyof TChild;
6
+ protected displayMember: keyof TChild;
7
7
  protected findMembers(item: any): void;
8
8
  protected isLoading: boolean;
9
9
  protected dataSource: Data.IAsyncDataSource<T[]> | null;
@@ -13,4 +13,5 @@ export declare abstract class DropDownBase<T, TProps> extends ComponentBase<TPro
13
13
  afterRender(isInitial: boolean): Promise<void>;
14
14
  private fieldDisplayInfo;
15
15
  protected setFieldInfo(bindProperty: Model.IPropertyInstance, displayValue: string, readableName?: string): void;
16
+ protected getFirstChildItem(items: T[], childListMember: keyof T): TChild | null;
16
17
  }
@@ -81,6 +81,16 @@ var DropDownBase = /** @class */ (function (_super) {
81
81
  this.fieldDisplayInfo = { bindProperty: bindProperty, displayValue: displayValue, readableName: readableName };
82
82
  }
83
83
  };
84
+ DropDownBase.prototype.getFirstChildItem = function (items, childListMember) {
85
+ if (items.length > 0) {
86
+ var firstParentItem = items[0];
87
+ var childList = firstParentItem[childListMember];
88
+ if (childList && childList.length > 0) {
89
+ return childList[0];
90
+ }
91
+ }
92
+ return null;
93
+ };
84
94
  return DropDownBase;
85
95
  }(ComponentBase));
86
96
  export { DropDownBase };
@@ -1,11 +1,11 @@
1
1
  import * as React from 'react';
2
2
  import { Components, Model } from '@singularsystems/neo-core';
3
- interface IFormProps<T extends Model.IModelBase> extends Omit<React.FormHTMLAttributes<HTMLElement>, "children">, Components.IFormOptions<T> {
3
+ interface IFormProps<T extends Model.IModelBase> extends Omit<React.FormHTMLAttributes<HTMLElement>, "children" | "onInvalid">, Components.IFormOptions<T> {
4
4
  /**
5
5
  * Called when the form is submitted, but the model is not valid.
6
6
  * The firstSubmit parameter will be true if this is the first submit attempt for the model.
7
7
  */
8
- onInvalid?: (e?: React.FormEvent, firstSubmit?: boolean) => void;
8
+ onInvalid?: (e?: React.SubmitEvent, firstSubmit?: boolean) => void;
9
9
  children: React.ReactNode | ((item: T, meta: Model.TransformMetaType<T>) => React.ReactNode);
10
10
  }
11
11
  export default class Form<T extends Model.IModelBase> extends React.Component<IFormProps<T>> {
@@ -1,7 +1,7 @@
1
1
  import { __assign, __awaiter, __decorate, __extends, __generator, __rest } from "tslib";
2
2
  import * as React from 'react';
3
- import { observer, Observer } from 'mobx-react';
4
- import { Validation, Model, ModalUtils, Misc } from '@singularsystems/neo-core';
3
+ import { observer } from 'mobx-react';
4
+ import { Model, ModalUtils, Misc } from '@singularsystems/neo-core';
5
5
  import ValidationSummary from './ValidationSummary';
6
6
  import { reaction } from 'mobx';
7
7
  import { FormContextProvider } from './FormContext';
@@ -82,7 +82,7 @@ var Form = /** @class */ (function (_super) {
82
82
  }
83
83
  codeModal_1 = ModalUtils.showModal(_this.props.invalidSummaryTitle || "Invalid data", React.createElement("div", null,
84
84
  _this.props.invalidSummaryText || "Cannot continue due to the following issues:",
85
- React.createElement(Observer, null, function () { return React.createElement(ValidationSummary, { graph: Validation.getValidationSummary(modelOrModels), hideTopLevelTitle: !(modelOrModels instanceof Array) }); })), { onClose: onClose, closeButton: closeButton });
85
+ React.createElement(ValidationSummary, { model: modelOrModels, hideTopLevelTitle: !(modelOrModels instanceof Array) })), { onClose: onClose, closeButton: closeButton });
86
86
  }
87
87
  if (someBusy()) {
88
88
  // Async rule is busy, listen for when isBusy changes to false.
@@ -112,7 +112,7 @@ var Form = /** @class */ (function (_super) {
112
112
  };
113
113
  Form.prototype.render = function () {
114
114
  var _this = this;
115
- var _a = this.props, validationDisplayMode = _a.validationDisplayMode, allReadOnly = _a.allReadOnly, allDisabled = _a.allDisabled, model = _a.model, showSummaryModal = _a.showSummaryModal, invalidSummaryTitle = _a.invalidSummaryTitle, invalidSummaryText = _a.invalidSummaryText, labelSizes = _a.labelSizes, suppressLabels = _a.suppressLabels, native = __rest(_a, ["validationDisplayMode", "allReadOnly", "allDisabled", "model", "showSummaryModal", "invalidSummaryTitle", "invalidSummaryText", "labelSizes", "suppressLabels"]);
115
+ var _a = this.props, validationDisplayMode = _a.validationDisplayMode, allReadOnly = _a.allReadOnly, allDisabled = _a.allDisabled, model = _a.model, showSummaryModal = _a.showSummaryModal, invalidSummaryTitle = _a.invalidSummaryTitle, invalidSummaryText = _a.invalidSummaryText, labelSizes = _a.labelSizes, suppressLabels = _a.suppressLabels, onInvalid = _a.onInvalid, native = __rest(_a, ["validationDisplayMode", "allReadOnly", "allDisabled", "model", "showSummaryModal", "invalidSummaryTitle", "invalidSummaryText", "labelSizes", "suppressLabels", "onInvalid"]);
116
116
  return (React.createElement("form", __assign({}, native, { onSubmit: function (e) { return _this.onSubmit(e); } }),
117
117
  React.createElement(FormContextProvider, { validationDisplayMode: validationDisplayMode !== null && validationDisplayMode !== void 0 ? validationDisplayMode : Misc.Settings.form.validationDisplayMode, allReadOnly: allReadOnly, allDisabled: allDisabled, labelSizes: labelSizes, suppressLabels: suppressLabels }, (this.props.children instanceof Function) ? this.props.children(model, model.meta) : this.props.children)));
118
118
  };
@@ -132,7 +132,7 @@ export declare class BindPropsHelper {
132
132
  * Gets the correct input control type based on the props. E.g. Input / DatePicker / Select
133
133
  * @param props Props passed to a multi type input container. E.g. FormGroup / Column
134
134
  */
135
- static getFormControl(props: IEditorNormalisedProps<any, any>): React.CElement<import("./DatePicker").INeoDatePickerProps, DatePicker> | React.CElement<import("./NumberInput").INumberInputProps, NumberInput> | React.CElement<import("./DropDown/DropDown").IDropDownProps<unknown>, DropDown<unknown>> | React.CElement<IFileEditorProps, FileInput> | React.CElement<import("./Input").IInputProps, Input> | React.CElement<import("./RadioList").IRadioListProps<unknown>, RadioList<unknown>>;
135
+ static getFormControl(props: IEditorNormalisedProps<any, any>): React.CElement<import("./DatePicker").INeoDatePickerProps, DatePicker> | React.CElement<import("./NumberInput").INumberInputProps, NumberInput> | React.CElement<import("./DropDown/DropDown").IDropDownProps<unknown, unknown>, DropDown<unknown, unknown>> | React.CElement<IFileEditorProps, FileInput> | React.CElement<import("./Input").IInputProps, Input> | React.CElement<import("./RadioList").IRadioListProps<unknown>, RadioList<unknown>>;
136
136
  static getControlType(props: IEditorNormalisedProps<any, any>): ControlType;
137
137
  static getCheckBoxType(props: IEditorNormalisedProps<any, any>, instance?: any): CheckboxType;
138
138
  /**
@@ -55,7 +55,12 @@ var RadioList = /** @class */ (function (_super) {
55
55
  return React.createElement("div", { className: className }, this.items.map(function (item, index) {
56
56
  var _a, _b;
57
57
  return React.createElement("div", { className: "custom-radio " + prefix + (radioProps.inline ? " ".concat(prefix, "-inline") : ""), key: item.id },
58
- React.createElement("input", { type: "radio", id: splitProps.uniqueID + ((_a = item.id) !== null && _a !== void 0 ? _a : ""), name: splitProps.uniqueID, checked: bindValue === item.id, disabled: domProps.disabled || domProps.readOnly, onChange: function () { return _this.onItemSelected(item); }, className: "".concat(prefix, "-input") }),
58
+ React.createElement("input", { type: "radio", id: splitProps.uniqueID + ((_a = item.id) !== null && _a !== void 0 ? _a : ""), name: splitProps.uniqueID, checked: bindValue === item.id, disabled: domProps.disabled || domProps.readOnly, onChange: function (e) {
59
+ _this.onItemSelected(item);
60
+ if (domProps.onChange) {
61
+ domProps.onChange(e);
62
+ }
63
+ }, className: "".concat(prefix, "-input") }),
59
64
  React.createElement("label", { className: "".concat(prefix, "-label"), htmlFor: splitProps.uniqueID + ((_b = item.id) !== null && _b !== void 0 ? _b : ""), "data-tip": tooltips && index < tooltips.length ? tooltips[index] : undefined }, item.name));
60
65
  }));
61
66
  };
@@ -1,7 +1,8 @@
1
- import { __extends } from "tslib";
1
+ import { __decorate, __extends } from "tslib";
2
2
  import React from 'react';
3
3
  import { Validation, Misc } from '@singularsystems/neo-core';
4
4
  import { NeoReactTypes } from '../Modules/Types';
5
+ import { observer } from "mobx-react";
5
6
  var ValidationSummary = /** @class */ (function (_super) {
6
7
  __extends(ValidationSummary, _super);
7
8
  function ValidationSummary() {
@@ -9,6 +10,7 @@ var ValidationSummary = /** @class */ (function (_super) {
9
10
  _this.iconFactory = Misc.Globals.appService.get(NeoReactTypes.Components.IconFactory);
10
11
  return _this;
11
12
  }
13
+ ValidationSummary_1 = ValidationSummary;
12
14
  ValidationSummary.prototype.getRules = function (modelInfo) {
13
15
  return modelInfo.objectDisplayInfo.concat(modelInfo.propertyRules)
14
16
  .sort(function (a, b) { return a.order === b.order ? 0 : (a.order > b.order ? 1 : -1); })
@@ -37,10 +39,14 @@ var ValidationSummary = /** @class */ (function (_super) {
37
39
  _this.iconFactory.getIconComponent(rule.displayInfo.icon, { fixedWidth: true, spin: !!rule.displayInfo.asyncBusyText }),
38
40
  " ",
39
41
  rule.displayInfo.displayText)); })),
40
- React.createElement(ValidationSummary, { graph: modelInfo.valueObjects }),
41
- React.createElement(ValidationSummary, { graph: modelInfo.childObjects }),
42
- React.createElement(ValidationSummary, { graph: modelInfo.childLists })))); })));
42
+ React.createElement(ValidationSummary_1, { graph: modelInfo.valueObjects }),
43
+ React.createElement(ValidationSummary_1, { graph: modelInfo.childObjects }),
44
+ React.createElement(ValidationSummary_1, { graph: modelInfo.childLists })))); })));
43
45
  };
46
+ var ValidationSummary_1;
47
+ ValidationSummary = ValidationSummary_1 = __decorate([
48
+ observer
49
+ ], ValidationSummary);
44
50
  return ValidationSummary;
45
51
  }(React.Component));
46
52
  export default ValidationSummary;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@singularsystems/neo-react",
3
- "version": "1.3.7",
3
+ "version": "1.3.9",
4
4
  "description": "React application logic and components for the Neo client library",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,26 +18,26 @@
18
18
  "devDependencies": {
19
19
  "@types/jest": "30.0.0",
20
20
  "@types/memoize-one": "5.1.2",
21
- "@types/node": "24.7.2",
22
- "@types/react": "19.2.2",
21
+ "@types/node": "25.3.0",
22
+ "@types/react": "19.2.14",
23
23
  "@types/react-custom-scrollbars": "4.0.13",
24
- "@types/react-dom": "19.2.2",
24
+ "@types/react-dom": "19.2.3",
25
25
  "decimal.js-light": "2.5.1",
26
26
  "react-scripts": "5.0.1",
27
- "typescript": "5.7.3"
27
+ "typescript": "5.9.3"
28
28
  },
29
29
  "dependencies": {
30
- "@singularsystems/neo-core": "1.3.4",
30
+ "@singularsystems/neo-core": "1.3.8",
31
31
  "@types/react-color": "3.0.13",
32
- "axios": "1.12.2",
32
+ "axios": "1.13.5",
33
33
  "inversify": "6.0.2",
34
34
  "memoize-one": "6.0.0",
35
35
  "mobx": "6.15.0",
36
36
  "mobx-react": "9.2.1",
37
37
  "rc-slider": "11.1.9",
38
- "react": "19.2.0",
38
+ "react": "19.2.4",
39
39
  "react-custom-scrollbars": "4.2.1",
40
- "react-dom": "19.2.0",
40
+ "react-dom": "19.2.4",
41
41
  "react-select": "5.10.2",
42
42
  "tslib": "2.8.1"
43
43
  },