@singularsystems/neo-react 1.6.2 → 1.6.4

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,25 @@
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.6.4 (20 July 2026)
6
+
7
+ - Neo.Card
8
+ - Added `description` prop to neo card.
9
+ - Additional root classes will be rendered when description, or headerElements is specified for more styling options.
10
+ - Drop down
11
+ - Added parent value to `onItemSelected`.
12
+ - Added `nullValue` to better control when a blank item renders.
13
+ - With this change, the basic `Neo.DropDown` component is more explicit with how it decides whether or not to show a blank item.
14
+ Previously if the items list contained an item with any falsy value, then a blank item would not be shown, and the item with the falsy value would be shown if the bound value was null.
15
+ This has changed so that only items with a value of `null` are considered blank items.
16
+ If you need a different value to be considered null, then set the `nullValue` prop.
17
+ - Grid
18
+ - Added `headerProps` and `cellProps` to allow customisation at header / cell level.
19
+
20
+ ## Version 1.6.3 (14 July 2026)
21
+
22
+ - Allow `ICardProps` to be referenced.
23
+
5
24
  ## Version 1.6.2 (10 July 2026)
6
25
 
7
26
  - Routing fix
@@ -1,10 +1,12 @@
1
1
  import React, { JSX } from 'react';
2
2
  import { Model } from '@singularsystems/neo-core';
3
- interface ICardProps {
3
+ export interface ICardProps {
4
4
  /** Icon to show next to title in header. */
5
5
  icon?: string;
6
6
  /** Title to show in header. */
7
7
  title?: any;
8
+ /** Description to show below the title. */
9
+ description?: string;
8
10
  /** Header elements to show on right hand side of header. */
9
11
  headerElements?: JSX.Element;
10
12
  /** Footer elements to show on bottom of the card. */
@@ -24,5 +26,5 @@ export default class Card extends React.Component<ICardProps & React.HTMLProps<H
24
26
  private iconFactory;
25
27
  private expandState;
26
28
  render(): JSX.Element;
29
+ private renderTitle;
27
30
  }
28
- export {};
@@ -15,7 +15,7 @@ var Card = /** @class */ (function (_super) {
15
15
  return _this;
16
16
  }
17
17
  Card.prototype.render = function () {
18
- var _a = this.props, icon = _a.icon, title = _a.title, children = _a.children, headerElements = _a.headerElements, footerContent = _a.footerContent, useCustomScrollbar = _a.useCustomScrollbar, collapsible = _a.collapsible, initiallyCollapsed = _a.initiallyCollapsed, isExpanded = _a.isExpanded, expandIconPosition = _a.expandIconPosition, domProps = __rest(_a, ["icon", "title", "children", "headerElements", "footerContent", "useCustomScrollbar", "collapsible", "initiallyCollapsed", "isExpanded", "expandIconPosition"]);
18
+ var _a = this.props, icon = _a.icon, title = _a.title, description = _a.description, children = _a.children, headerElements = _a.headerElements, footerContent = _a.footerContent, useCustomScrollbar = _a.useCustomScrollbar, collapsible = _a.collapsible, initiallyCollapsed = _a.initiallyCollapsed, isExpanded = _a.isExpanded, expandIconPosition = _a.expandIconPosition, domProps = __rest(_a, ["icon", "title", "description", "children", "headerElements", "footerContent", "useCustomScrollbar", "collapsible", "initiallyCollapsed", "isExpanded", "expandIconPosition"]);
19
19
  var className = Utils.joinWithSpaces("neo-card card", domProps.className);
20
20
  expandIconPosition !== null && expandIconPosition !== void 0 ? expandIconPosition : (expandIconPosition = "right");
21
21
  var isCollapsible = collapsible || isExpanded !== undefined || initiallyCollapsed !== undefined;
@@ -39,6 +39,12 @@ var Card = /** @class */ (function (_super) {
39
39
  }
40
40
  className += " card-collapsible";
41
41
  }
42
+ if (headerElements) {
43
+ className += " card--with-header-elements";
44
+ }
45
+ if (description) {
46
+ className += " card--with-description";
47
+ }
42
48
  if (icon && Misc.Settings.icons.shouldSuppress({ icon: icon, component: "card", props: this.props })) {
43
49
  icon = undefined;
44
50
  }
@@ -59,10 +65,20 @@ var Card = /** @class */ (function (_super) {
59
65
  React.createElement("div", { className: "card-header-title" },
60
66
  expandIconPosition === "left" && expandIndicator,
61
67
  this.iconFactory.getIconComponent(icon),
62
- React.createElement("span", null, title)),
68
+ this.renderTitle(title, description)),
63
69
  headerElements && React.createElement("div", { className: "card-header-right-section", onClick: function (e) { return e.stopPropagation(); } }, headerElements)),
64
70
  content));
65
71
  };
72
+ Card.prototype.renderTitle = function (title, description) {
73
+ if (description) {
74
+ return (React.createElement("div", { className: "card-title-section" },
75
+ React.createElement("div", { className: "card-title-text" }, title),
76
+ React.createElement("div", { className: "card-title-description" }, description)));
77
+ }
78
+ else {
79
+ return typeof title === "string" ? React.createElement("span", null, title) : title;
80
+ }
81
+ };
66
82
  Card = __decorate([
67
83
  observer
68
84
  ], Card);
@@ -1,18 +1,11 @@
1
1
  import React, { JSX } from 'react';
2
2
  import { Model } from '@singularsystems/neo-core';
3
3
  import { AxiosPromise } from 'axios';
4
- import { DropDownBase, ItemSource } from './DropDownBase';
4
+ import { DropDownBase, ICommonDropDownProps, IOption, ItemSource } from './DropDownBase';
5
5
  import type { Props, SelectInstance } from 'react-select';
6
6
  import { ICommonEditorProps } from '../PropTypes';
7
7
  import { FormContext } from '../FormContext';
8
- interface IOption<T = any> {
9
- value: string | number;
10
- label: string;
11
- item?: T;
12
- itemParent?: any;
13
- options?: IOption[];
14
- }
15
- interface IAutoCompleteCommonProps<T, TChild = T> extends ICommonEditorProps<SelectInstance>, Props<IOption, true> {
8
+ interface IAutoCompleteCommonProps<T, TChild = T> extends ICommonEditorProps<SelectInstance>, Props<IOption, true>, ICommonDropDownProps<T, TChild> {
16
9
  /**
17
10
  * Callback for loading data.
18
11
  */
@@ -56,7 +49,7 @@ interface IAutoCompleteCommonProps<T, TChild = T> extends ICommonEditorProps<Sel
56
49
  * The removedItem parameter will be provided when removing an item from a multi select drop down.
57
50
  * 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.
58
51
  */
59
- onItemSelected?: (item?: TChild, removedItem?: Model.ISelectedItem, addedItem?: Model.ISelectedItem) => void;
52
+ onItemSelected?: (item?: TChild, removedItem?: Model.ISelectedItem, addedItem?: Model.ISelectedItem, parent?: T) => void;
60
53
  /**
61
54
  * Millisecond delay from the last typed character, to when the itemSource function is called.
62
55
  * Default is 500ms.
@@ -67,20 +60,12 @@ interface IAutoCompleteCommonProps<T, TChild = T> extends ICommonEditorProps<Sel
67
60
  * Default is 1.
68
61
  */
69
62
  minCharacters?: number;
70
- /**
71
- * The group member to use as the display for grouped items.
72
- */
73
- groupDisplayMember?: NoInfer<keyof T>;
74
- /**
75
- * Name of the property on the parent which contains a child list.
76
- */
77
- childListMember?: NoInfer<keyof T>;
78
63
  }
79
- export interface IAutoCompleteProps {
64
+ export interface IAutoCompleteProps<T, TChild> extends IAutoCompleteCommonProps<T, TChild> {
80
65
  bind: Model.IPropertyInstance;
81
66
  bindDisplay?: Model.IPropertyInstance;
82
67
  }
83
- export interface IAutoCompleteMultiSelectProps {
68
+ export interface IAutoCompleteMultiSelectProps<T, TChild = T> extends IAutoCompleteCommonProps<T, TChild> {
84
69
  /**
85
70
  * A list of ids and labels to use as the selected list.
86
71
  */
@@ -95,31 +80,20 @@ export interface IAutoCompleteMultiSelectProps {
95
80
  */
96
81
  fieldInfoLimit?: number;
97
82
  }
98
- export default class AutoCompleteDropDown<T, TChild = T> extends DropDownBase<T, IAutoCompleteCommonProps<T, TChild> & (IAutoCompleteProps | IAutoCompleteMultiSelectProps), TChild> {
83
+ export default class AutoCompleteDropDown<T, TChild = T> extends DropDownBase<T, TChild, IAutoCompleteProps<T, TChild> | IAutoCompleteMultiSelectProps<T, TChild>> {
99
84
  static contextType: React.Context<import("../FormContext").FormContextParams>;
100
85
  context: React.ContextType<typeof FormContext>;
101
86
  private asyncSelectImplementation;
102
87
  private selectImplementation;
103
- constructor(props: IAutoCompleteCommonProps<T, TChild> & (IAutoCompleteProps | IAutoCompleteMultiSelectProps));
88
+ constructor(props: IAutoCompleteProps<T, TChild> | IAutoCompleteMultiSelectProps<T, TChild>);
104
89
  private bindProperty;
105
90
  private bindIdsList?;
106
91
  private bindIdsProperty?;
107
- static getBindProp<TChild>(props: IAutoCompleteProps | IAutoCompleteMultiSelectProps): Model.IPropertyInstance<any>;
92
+ static getBindProp<T, TChild>(props: IAutoCompleteProps<T, TChild> | IAutoCompleteMultiSelectProps<T, TChild>): Model.IPropertyInstance<any>;
93
+ protected get commonProps(): ICommonDropDownProps<T, TChild>;
108
94
  private onKeyPress;
109
95
  private setBindProperty;
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;
121
96
  private onItemSelected;
122
- private get isGrouped();
123
97
  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;
124
98
  private getOption;
125
99
  }
@@ -22,6 +22,13 @@ var AutoCompleteDropDown = /** @class */ (function (_super) {
22
22
  var _a;
23
23
  return (_a = props.bind) !== null && _a !== void 0 ? _a : props.bindItems;
24
24
  };
25
+ Object.defineProperty(AutoCompleteDropDown.prototype, "commonProps", {
26
+ get: function () {
27
+ return this.props;
28
+ },
29
+ enumerable: false,
30
+ configurable: true
31
+ });
25
32
  AutoCompleteDropDown.prototype.onKeyPress = function (searchValue, dataReceived) {
26
33
  var _this = this;
27
34
  var _a, _b;
@@ -35,7 +42,7 @@ var AutoCompleteDropDown = /** @class */ (function (_super) {
35
42
  else {
36
43
  items = list;
37
44
  }
38
- dataReceived(_this.mapTArrayToOptions((items).slice()));
45
+ dataReceived(_this.mapItemsToOptions((items).slice()));
39
46
  });
40
47
  }, (_b = this.props.delayMs) !== null && _b !== void 0 ? _b : 500);
41
48
  }
@@ -67,73 +74,9 @@ var AutoCompleteDropDown = /** @class */ (function (_super) {
67
74
  }
68
75
  }
69
76
  };
70
- AutoCompleteDropDown.prototype.mapTArrayToOptions = function (items) {
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
- }
78
- if (items.length) {
79
- this.findMembers(items[0]);
80
- }
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]
90
- }); });
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
- };
134
77
  AutoCompleteDropDown.prototype.onItemSelected = function (args, action) {
135
- var _a;
136
- var onItemSelected = this.props.onItemSelected, singleProps = this.props, multiProps = this.props;
78
+ var _a, _b;
79
+ var singleProps = this.props, multiProps = this.props;
137
80
  if (multiProps.bindItems) {
138
81
  var itemsProperty = multiProps.bindItems;
139
82
  // Multi select
@@ -144,8 +87,8 @@ var AutoCompleteDropDown = /** @class */ (function (_super) {
144
87
  if (this.bindIdsList) {
145
88
  this.bindIdsList.push(action.option.value);
146
89
  }
147
- if (onItemSelected) {
148
- onItemSelected((_a = action.option) === null || _a === void 0 ? void 0 : _a.item, undefined, newItem);
90
+ if (multiProps.onItemSelected) {
91
+ multiProps.onItemSelected((_a = action.option) === null || _a === void 0 ? void 0 : _a.item, undefined, newItem, (_b = action.option) === null || _b === void 0 ? void 0 : _b.itemParent);
149
92
  }
150
93
  }
151
94
  else if (action.action === "remove-value") {
@@ -156,8 +99,8 @@ var AutoCompleteDropDown = /** @class */ (function (_super) {
156
99
  this.bindIdsList.remove(removeItem.id);
157
100
  }
158
101
  }
159
- if (onItemSelected) {
160
- onItemSelected(undefined, removeItem);
102
+ if (multiProps.onItemSelected) {
103
+ multiProps.onItemSelected(undefined, removeItem);
161
104
  }
162
105
  }
163
106
  else if (action.action === "clear") {
@@ -165,8 +108,8 @@ var AutoCompleteDropDown = /** @class */ (function (_super) {
165
108
  if (this.bindIdsList) {
166
109
  this.bindIdsList.length = 0;
167
110
  }
168
- if (onItemSelected) {
169
- onItemSelected(undefined);
111
+ if (multiProps.onItemSelected) {
112
+ multiProps.onItemSelected(undefined);
170
113
  }
171
114
  }
172
115
  itemsProperty.attachedTo.isSelfDirty = true;
@@ -177,18 +120,11 @@ var AutoCompleteDropDown = /** @class */ (function (_super) {
177
120
  if (singleProps.bindDisplay) {
178
121
  singleProps.bindDisplay.value = args ? args.label : "";
179
122
  }
180
- if (onItemSelected) {
181
- onItemSelected(args === null || args === void 0 ? void 0 : args.item);
123
+ if (singleProps.onItemSelected) {
124
+ singleProps.onItemSelected(args === null || args === void 0 ? void 0 : args.item, undefined, undefined, args === null || args === void 0 ? void 0 : args.itemParent);
182
125
  }
183
126
  }
184
127
  };
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
- });
192
128
  AutoCompleteDropDown.prototype.render = function () {
193
129
  var _this = this;
194
130
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
@@ -210,40 +146,20 @@ var AutoCompleteDropDown = /** @class */ (function (_super) {
210
146
  }
211
147
  var defaultItems = undefined;
212
148
  if (this.props.items) {
213
- var items_2 = [];
149
+ var items_1 = [];
214
150
  if (this.isGrouped) {
215
- items_2 = this.getItems(this.props.items);
216
- defaultItems = this.getGroupedOptionsFromItems(items_2);
151
+ items_1 = this.getItems(this.props.items);
152
+ defaultItems = this.mapGroupedItemsToOptions(items_1);
217
153
  }
218
154
  else {
219
- items_2 = this.getItems(this.props.items);
220
- defaultItems = this.mapTArrayToOptions(items_2);
155
+ items_1 = this.getItems(this.props.items);
156
+ defaultItems = this.mapItemsToOptions(items_1);
221
157
  }
222
158
  if (filterPredicate !== undefined) {
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; });
234
- }
235
- else {
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
- }
243
- }
159
+ defaultItems = this.filterOptions(defaultItems, filterPredicate, bindValue);
244
160
  }
245
- if (descriptionMember === undefined && items_2.length > 0) {
246
- if (EnumHelper.isEnumItem(items_2[0])) {
161
+ if (descriptionMember === undefined && items_1.length > 0) {
162
+ if (EnumHelper.isEnumItem(items_1[0])) {
247
163
  descriptionMember = "description";
248
164
  }
249
165
  }
@@ -1,56 +1,53 @@
1
1
  import * as React from 'react';
2
2
  import { IBindableEditorProps } from '../PropTypes';
3
3
  import { Data } from '@singularsystems/neo-core';
4
- import { DropDownBase } from './DropDownBase';
4
+ import { DropDownBase, ICommonDropDownProps } from './DropDownBase';
5
5
  import { FormContext } from '../FormContext';
6
- export interface IDropDownSpecificProps<T, TChild = T> {
6
+ export interface IDropDownSpecificProps<T, TChild = T> extends ICommonDropDownProps<T, TChild> {
7
7
  items?: T[] | Promise<T[]>;
8
8
  itemSource?: Data.IAsyncDataSource<T[]> | Data.IArrayCacheEntry<T>;
9
9
  valueMember?: NoInfer<keyof TChild>;
10
10
  displayMember?: NoInfer<keyof TChild>;
11
11
  /**
12
- * Set to true if your bind property is nullable, and you need to show a blank item in the list.
13
- * If your property value is null, a blank item will automatically be shown.
12
+ * Same as `allowDefault`.
13
+ * Set to true if the user should be able to de-select an item, and set the bound value to the `defaultValue`. This will show a default item in the list.
14
14
  */
15
15
  allowNulls?: boolean;
16
+ /**
17
+ * Set to true if the user should be able to de-select an item, and set the bound value to the `defaultValue`. This will show a default item in the list.
18
+ */
19
+ allowDefault?: boolean;
20
+ /**
21
+ * Value considered default. E.g. if allowDefault is true, then selecting the default item will set the bound value to this value. Defaults to `null`.
22
+ */
23
+ defaultValue?: any;
16
24
  /** Function used to filter items out of the datasource. */
17
25
  filterPredicate?: (item: TChild, itemParent?: T) => boolean;
18
26
  /** Callback which fires when an item is selected. */
19
- onItemSelected?: (item?: TChild) => void;
27
+ onItemSelected?: (item?: TChild, parent?: T) => void;
20
28
  /**
21
29
  * True if the display member of the selected item should display as plain text.
22
30
  * Use this if you need a readonly display, and don't want a form control rendered.
23
31
  */
24
32
  renderAsText?: boolean;
25
- /** Text to display when the bound value is null, and there is no item with null as it's value member. */
33
+ /** Text to display when the bound value is the default value, and there is no item with this value. */
26
34
  nullText?: string;
27
- /** Text to display as the first item in the drop down list when the user is allowed to select a blank item. */
35
+ /** Text to display as the first item in the drop down list when the user is allowed to select a default item. */
28
36
  deSelectText?: string;
29
37
  /** Set to true if the property should be sorted by value instead of display text. */
30
38
  sortByValue?: boolean;
31
- /**
32
- * The group member to use as the display for grouped items.
33
- */
34
- groupDisplayMember?: NoInfer<keyof T>;
35
- /**
36
- * The child list containing the grouped items.
37
- */
38
- childListMember?: NoInfer<keyof T>;
39
39
  }
40
40
  export interface IDropDownProps<T, TChild = T> extends IBindableEditorProps<HTMLSelectElement> {
41
41
  select: IDropDownSpecificProps<T, TChild>;
42
42
  }
43
- export default class DropDown<T, TChild = T> extends DropDownBase<T, IDropDownProps<T, TChild>, TChild> {
43
+ export default class DropDown<T, TChild = T> extends DropDownBase<T, TChild, IDropDownProps<T, TChild>> {
44
44
  static contextType: React.Context<import("../FormContext").FormContextParams>;
45
45
  context: React.ContextType<typeof FormContext>;
46
- private selectedItem;
46
+ private allOptions;
47
+ private selectedOption;
47
48
  private sortByValue?;
48
- private listIndexSymbol;
49
- private get isGrouped();
50
49
  constructor(props: IDropDownProps<T, TChild>);
51
- private get items();
52
- private get groupedItems();
53
- private createLookupIndex;
50
+ protected get commonProps(): ICommonDropDownProps<T, TChild>;
54
51
  private getSelectedOption;
55
52
  private onItemSelected;
56
53
  render(): React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | React.JSX.Element;
@@ -10,130 +10,61 @@ var DropDown = /** @class */ (function (_super) {
10
10
  __extends(DropDown, _super);
11
11
  function DropDown(props) {
12
12
  var _this = _super.call(this, props) || this;
13
- _this.listIndexSymbol = Symbol("ValueIndex");
13
+ _this.allOptions = [];
14
+ _this.selectedOption = null;
14
15
  _this.onItemSelected = _this.onItemSelected.bind(_this);
15
16
  _this.fieldInfoContainer = Model.ModelFieldInfo.asModelFieldInfoContainer(_this.props.bind.attachedTo);
16
17
  return _this;
17
18
  }
18
- Object.defineProperty(DropDown.prototype, "isGrouped", {
19
+ Object.defineProperty(DropDown.prototype, "commonProps", {
19
20
  get: function () {
20
- return this.props.select.groupDisplayMember !== undefined
21
- && this.props.select.childListMember !== undefined;
21
+ return this.props.select;
22
22
  },
23
23
  enumerable: false,
24
24
  configurable: true
25
25
  });
26
- Object.defineProperty(DropDown.prototype, "items", {
27
- get: function () {
28
- var _this = this;
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
- }
45
- }
46
- if (this.props.select.filterPredicate !== undefined) {
47
- return items.filter(function (item) { return _this.props.bind.value === item[_this.valueMember] || _this.props.select.filterPredicate(item, undefined); });
48
- }
49
- else {
50
- return items;
51
- }
52
- },
53
- enumerable: false,
54
- configurable: true
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
- });
81
- DropDown.prototype.createLookupIndex = function (list) {
82
- var _this = this;
83
- var index = {};
84
- //@ts-ignore
85
- list[this.listIndexSymbol] = index;
86
- list.forEach(function (item) { return index[item[_this.valueMember]] = item; });
87
- return index;
88
- };
89
- DropDown.prototype.getSelectedOption = function (value, items) {
26
+ DropDown.prototype.getSelectedOption = function (value) {
27
+ var _a, _b, _c;
90
28
  if (!this.sortByValue) {
91
29
  this.props.bind.propertyInfo.hasSortValue = true;
92
30
  }
93
- if (!this.selectedItem || this.selectedItem[this.valueMember] != value) {
94
- var list = items !== null && items !== void 0 ? items : this.items;
95
- //@ts-ignore
96
- var index = list[this.listIndexSymbol];
97
- var indexJustCreated = false;
98
- if (!index) {
99
- index = this.createLookupIndex(list);
100
- indexJustCreated = true;
101
- }
102
- this.selectedItem = index[value];
103
- if (value && !this.selectedItem && !indexJustCreated) {
104
- // Index may be out of date.
105
- index = this.createLookupIndex(list);
106
- this.selectedItem = index[value];
107
- }
108
- if (!this.selectedItem) {
109
- // create default item.
110
- this.selectedItem = {};
111
- this.selectedItem[this.displayMember] = "";
112
- this.selectedItem[this.valueMember] = null;
113
- }
114
- var displayValue = this.selectedItem[this.displayMember];
31
+ if (!this.selectedOption || this.selectedOption.value != value) {
32
+ this.selectedOption = (_a = this.findOptionByValue(this.allOptions, value)) !== null && _a !== void 0 ? _a : null;
33
+ var displayValue = (_c = (_b = this.selectedOption) === null || _b === void 0 ? void 0 : _b.label) !== null && _c !== void 0 ? _c : "";
115
34
  this.setFieldInfo(this.props.bind, displayValue);
116
35
  this.props.bind.sortValue = this.sortByValue ? value : displayValue;
117
36
  }
118
- return this.selectedItem;
37
+ return this.selectedOption;
119
38
  };
120
39
  DropDown.prototype.onItemSelected = function (e) {
121
- this.selectedItem = null;
122
- var boundItem = this.getSelectedOption(e.target.value);
123
- if (boundItem && boundItem[this.valueMember] !== undefined) {
40
+ this.selectedOption = null;
41
+ var value = e.target.value;
42
+ var parsedValue = parseInt(e.target.value);
43
+ if (!isNaN(parsedValue)) {
44
+ value = parsedValue;
45
+ }
46
+ var selectedOption = this.getSelectedOption(value);
47
+ if (!selectedOption && (value === "false" || value === "true")) {
48
+ selectedOption = this.getSelectedOption(value === "true");
49
+ }
50
+ if (selectedOption) {
124
51
  // Use the bound item to get the correct data type of the value member;
125
- this.props.bind.value = boundItem[this.valueMember];
52
+ this.props.bind.value = selectedOption.value;
126
53
  }
127
54
  else {
128
- this.props.bind.value = e.target.value;
55
+ this.props.bind.value = this.defaultItemValue;
129
56
  }
130
57
  if (this.props.select.onItemSelected !== undefined) {
131
- this.props.select.onItemSelected((boundItem[this.valueMember] === undefined ? undefined : boundItem));
58
+ if (selectedOption) {
59
+ this.props.select.onItemSelected(selectedOption.item, selectedOption.itemParent);
60
+ }
61
+ else {
62
+ this.props.select.onItemSelected(undefined, undefined);
63
+ }
132
64
  }
133
65
  };
134
66
  DropDown.prototype.render = function () {
135
- var _this = this;
136
- var _a, _b, _c, _d, _e, _f, _g;
67
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
137
68
  var splitProps = BindPropsHelper.normaliseEditorProps(this, this.props);
138
69
  var domProps = BindPropsHelper.autoSetDomProperties(splitProps, this.context);
139
70
  domProps.className = Utils.joinWithSpaces(domProps.className, splitProps.isBootstrap ? "form-control form-select" : "neo-forms-editor");
@@ -146,51 +77,44 @@ var DropDown = /** @class */ (function (_super) {
146
77
  }
147
78
  var displayInfo = this.props.bind.validator.getDisplayState(this.context.validationDisplayMode);
148
79
  displayInfo.applyToDom(domProps, !splitProps.inGroup);
149
- var items = this.items;
150
- var selectedItem = this.getSelectedOption(this.props.bind.value, items); // This sets the sort value to allow sorting of drop downs in grids.
80
+ this.defaultItemValue = (_c = (_b = splitProps.select) === null || _b === void 0 ? void 0 : _b.defaultValue) !== null && _c !== void 0 ? _c : null;
81
+ this.allOptions = this.mapItemsToOptions(this.getItems((_d = this.props.select.items) !== null && _d !== void 0 ? _d : this.props.select.itemSource));
82
+ var options = this.allOptions;
83
+ var selectedOption = this.getSelectedOption(this.props.bind.value); // This sets the sort value to allow sorting of drop downs in grids.
151
84
  var value = this.props.bind.value;
152
- var valueIsEmpty = !value;
153
- var notFound = items.length > 0 && !valueIsEmpty && selectedItem[this.valueMember] !== value;
154
- var addBlankItem = ((_c = (_b = splitProps.select) === null || _b === void 0 ? void 0 : _b.allowNulls) !== null && _c !== void 0 ? _c : this.props.bind.propertyInfo.allowNulls) || valueIsEmpty || notFound;
155
- if (addBlankItem && items.find(function (item) { return item[_this.valueMember] === null || item[_this.valueMember] === "" || (valueIsEmpty && item[_this.valueMember] === value); })) {
156
- // If there is an item with a null value, don't add a blank drop down item.
157
- addBlankItem = false;
158
- }
85
+ var valueIsEmpty = ((_e = splitProps.select) === null || _e === void 0 ? void 0 : _e.defaultValue) !== undefined ? (value === splitProps.select.defaultValue) : !value;
86
+ var notFound = options.length > 0 && !valueIsEmpty && (selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.value) !== value;
87
+ var allowDefault = (_j = (_g = (_f = splitProps.select) === null || _f === void 0 ? void 0 : _f.allowDefault) !== null && _g !== void 0 ? _g : (_h = splitProps.select) === null || _h === void 0 ? void 0 : _h.allowNulls) !== null && _j !== void 0 ? _j : this.props.bind.propertyInfo.allowNulls;
88
+ var addDefaultItem = (!this.hasDefaultItem && (allowDefault || valueIsEmpty)) || notFound;
159
89
  var control;
160
90
  if (this.isLoading) {
161
91
  control = React.createElement("div", { className: "form-control form-control-loading" },
162
92
  React.createElement("div", { className: "spinner-border" }));
163
93
  }
164
- else if ((_d = splitProps.select) === null || _d === void 0 ? void 0 : _d.renderAsText) {
165
- control = React.createElement("span", null, selectedItem ? selectedItem[this.displayMember] : "");
94
+ else if ((_k = splitProps.select) === null || _k === void 0 ? void 0 : _k.renderAsText) {
95
+ control = React.createElement("span", null, selectedOption ? selectedOption.label : "");
166
96
  }
167
97
  else if (splitProps.editor.isReadOnly) {
168
- control = React.createElement("input", __assign({ type: "text", value: selectedItem ? selectedItem[this.displayMember] : "", ref: splitProps.editor.inputElement }, domProps));
98
+ control = React.createElement("input", __assign({ type: "text", value: selectedOption ? selectedOption.label : "", ref: splitProps.editor.inputElement }, domProps));
169
99
  }
170
100
  else {
171
101
  if (domProps.readOnly) {
172
102
  domProps.disabled = true;
173
103
  }
174
- var valueIsNull = value === undefined || value === null;
175
- var nullText = (valueIsNull || ((_e = splitProps.select) === null || _e === void 0 ? void 0 : _e.deSelectText) === undefined) ? (_f = splitProps.select) === null || _f === void 0 ? void 0 : _f.nullText : (_g = splitProps.select) === null || _g === void 0 ? void 0 : _g.deSelectText;
176
- if (valueIsNull) {
104
+ var valueIsDefault = value === undefined || value === this.defaultItemValue;
105
+ var nullText = (valueIsDefault || ((_l = splitProps.select) === null || _l === void 0 ? void 0 : _l.deSelectText) === undefined) ? (_m = splitProps.select) === null || _m === void 0 ? void 0 : _m.nullText : (_o = splitProps.select) === null || _o === void 0 ? void 0 : _o.deSelectText;
106
+ if (valueIsDefault) {
177
107
  domProps.className = Utils.joinWithSpaces(domProps.className, "select-no-value");
178
108
  }
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
- }
109
+ var filteredOptions = ((_p = splitProps.select) === null || _p === void 0 ? void 0 : _p.filterPredicate) === undefined ? options : this.filterOptions(options, splitProps.select.filterPredicate, this.props.bind);
185
110
  control =
186
- React.createElement("select", __assign({ value: valueIsNull ? "" : value, onChange: this.onItemSelected, ref: splitProps.editor.inputElement }, domProps),
187
- addBlankItem && React.createElement("option", { className: "option-default" }, notFound ? "(Not found): ".concat(value) : nullText),
111
+ React.createElement("select", __assign({ value: value !== null && value !== void 0 ? value : "", onChange: this.onItemSelected, ref: splitProps.editor.inputElement }, domProps),
112
+ addDefaultItem && React.createElement("option", { className: "option-default" }, notFound ? "(Not found): ".concat(value) : nullText),
188
113
  !this.isGrouped &&
189
- this.items.map(function (item) { return (React.createElement("option", { value: item[_this.valueMember], key: item[_this.valueMember] }, item[_this.displayMember])); }),
114
+ filteredOptions.map(function (option) { return (React.createElement("option", { value: option.value, key: option.value }, option.label)); }),
190
115
  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])); })));
116
+ filteredOptions.map(function (group) {
117
+ return React.createElement("optgroup", { label: group.label, key: group.label }, (group.options.map(function (item) { return (React.createElement("option", { value: item.value, key: item.value }, item.label)); })));
194
118
  }));
195
119
  }
196
120
  return (InputHelpers.surroundWithInputGroup(control, splitProps.editor, domProps));
@@ -1,15 +1,49 @@
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, TChild = T> extends ComponentBase<TProps> {
4
+ export interface IOption<T = any> {
5
+ value: string | number;
6
+ label: string;
7
+ item?: T;
8
+ itemParent?: any;
9
+ options?: IOption[];
10
+ }
11
+ export interface ICommonDropDownProps<T, TChild = T> {
12
+ /**
13
+ * The group member to use as the display for grouped items.
14
+ */
15
+ groupDisplayMember?: NoInfer<keyof T>;
16
+ /**
17
+ * Name of the property on the parent which contains a child list.
18
+ */
19
+ childListMember?: NoInfer<keyof T>;
20
+ }
21
+ export declare abstract class DropDownBase<T, TChild, TProps> extends ComponentBase<TProps> {
5
22
  protected valueMember: keyof TChild;
6
23
  protected displayMember: keyof TChild;
24
+ protected defaultItemValue: any;
25
+ protected hasDefaultItem: boolean;
7
26
  protected findMembers(item: any): void;
8
27
  protected isLoading: boolean;
9
28
  protected dataSource: Data.IAsyncDataSource<T[]> | null;
10
29
  protected cachedDataSource: Data.IDataCacheEntry | null;
11
30
  protected fieldInfoContainer: Model.IModelFieldInfoContainer | null;
12
31
  protected getItems(itemSource: ItemSource<T>): T[];
32
+ /** Returns either a flat list, or grouped options based on the provided props. */
33
+ protected mapItemsToOptions(items: T[]): IOption[];
34
+ protected mapGroupedItemsToOptions(groupedItems: T[]): IOption<any>[];
35
+ private mapChildItemsToOptions;
36
+ /**
37
+ * Finds an option in the items list by value.
38
+ * Handles both flat lists and grouped (nested) items.
39
+ * @param items The items array to search in
40
+ * @param value The value to search for
41
+ * @returns The matching IOption or undefined if not found
42
+ */
43
+ protected findOptionByValue(items: IOption[] | undefined, value: string | number | boolean): IOption | undefined;
44
+ protected filterOptions(options: IOption[], filterPredicate: (item: TChild, itemParent?: T) => boolean, bindProperty: Model.IPropertyInstance | undefined): IOption<any>[];
45
+ protected abstract get commonProps(): ICommonDropDownProps<T, TChild>;
46
+ protected get isGrouped(): boolean;
13
47
  afterRender(isInitial: boolean): Promise<void>;
14
48
  private fieldDisplayInfo;
15
49
  protected setFieldInfo(bindProperty: Model.IPropertyInstance, displayValue: string, readableName?: string): void;
@@ -1,4 +1,4 @@
1
- import { __awaiter, __extends, __generator } from "tslib";
1
+ import { __assign, __awaiter, __extends, __generator } from "tslib";
2
2
  import { Data } from '@singularsystems/neo-core';
3
3
  import ComponentBase from '../ComponentBase';
4
4
  import LookupHelper from './LookupHelper';
@@ -6,6 +6,8 @@ var DropDownBase = /** @class */ (function (_super) {
6
6
  __extends(DropDownBase, _super);
7
7
  function DropDownBase() {
8
8
  var _this = _super !== null && _super.apply(this, arguments) || this;
9
+ _this.defaultItemValue = null;
10
+ _this.hasDefaultItem = false;
9
11
  _this.isLoading = false;
10
12
  _this.dataSource = null;
11
13
  _this.cachedDataSource = null;
@@ -52,6 +54,103 @@ var DropDownBase = /** @class */ (function (_super) {
52
54
  }
53
55
  return items;
54
56
  };
57
+ /** Returns either a flat list, or grouped options based on the provided props. */
58
+ DropDownBase.prototype.mapItemsToOptions = function (items) {
59
+ this.hasDefaultItem = false;
60
+ if (this.isGrouped) {
61
+ var firstChildItem = this.getFirstChildItem(items, this.commonProps.childListMember);
62
+ if (firstChildItem) {
63
+ this.findMembers(firstChildItem);
64
+ }
65
+ return this.mapGroupedItemsToOptions(items);
66
+ }
67
+ if (items.length) {
68
+ this.findMembers(items[0]);
69
+ }
70
+ return this.mapChildItemsToOptions(items);
71
+ };
72
+ DropDownBase.prototype.mapGroupedItemsToOptions = function (groupedItems) {
73
+ var _this = this;
74
+ var groupedOptions = groupedItems.map(function (groupItem) {
75
+ var childOptions = _this.mapChildItemsToOptions(groupItem[_this.commonProps.childListMember], groupItem);
76
+ return {
77
+ item: groupItem,
78
+ itemParent: null,
79
+ label: groupItem[_this.commonProps.groupDisplayMember],
80
+ options: childOptions
81
+ };
82
+ });
83
+ return groupedOptions;
84
+ };
85
+ DropDownBase.prototype.mapChildItemsToOptions = function (childItems, parentItem) {
86
+ var _this = this;
87
+ return childItems.map(function (childItem) {
88
+ var value = childItem[_this.valueMember];
89
+ if (value === _this.defaultItemValue) {
90
+ _this.hasDefaultItem = true;
91
+ }
92
+ return {
93
+ item: childItem,
94
+ itemParent: parentItem,
95
+ value: value,
96
+ label: childItem[_this.displayMember]
97
+ };
98
+ });
99
+ };
100
+ /**
101
+ * Finds an option in the items list by value.
102
+ * Handles both flat lists and grouped (nested) items.
103
+ * @param items The items array to search in
104
+ * @param value The value to search for
105
+ * @returns The matching IOption or undefined if not found
106
+ */
107
+ DropDownBase.prototype.findOptionByValue = function (items, value) {
108
+ if (!items) {
109
+ return undefined;
110
+ }
111
+ if (this.isGrouped) {
112
+ // For grouped items, search within child options of each group
113
+ for (var _i = 0, items_1 = items; _i < items_1.length; _i++) {
114
+ var group = items_1[_i];
115
+ if (group.options) {
116
+ var childMatch = group.options.find(function (option) { return option.value === value; });
117
+ if (childMatch) {
118
+ return childMatch;
119
+ }
120
+ }
121
+ }
122
+ return undefined;
123
+ }
124
+ else {
125
+ // For flat items, search directly
126
+ return items.find(function (item) { return item.value === value; });
127
+ }
128
+ };
129
+ DropDownBase.prototype.filterOptions = function (options, filterPredicate, bindProperty) {
130
+ var filterPredicateIncludeSelected;
131
+ if (bindProperty) {
132
+ var bindValue_1 = bindProperty.value;
133
+ filterPredicateIncludeSelected = function (option) { return bindValue_1 === option.value || filterPredicate(option.item, option.itemParent); };
134
+ }
135
+ else {
136
+ filterPredicateIncludeSelected = function (option) { return filterPredicate(option.item, option.itemParent); };
137
+ }
138
+ if (this.isGrouped) {
139
+ var groupsWithFilteredChildren = options.map(function (group) { return (__assign(__assign({}, group), { options: group.options.filter(filterPredicateIncludeSelected) })); });
140
+ // Remove groups that have no children after filtering
141
+ return groupsWithFilteredChildren.filter(function (group) { return group.options.length > 0; });
142
+ }
143
+ else {
144
+ return options.filter(filterPredicateIncludeSelected);
145
+ }
146
+ };
147
+ Object.defineProperty(DropDownBase.prototype, "isGrouped", {
148
+ get: function () {
149
+ return this.commonProps.groupDisplayMember !== undefined && this.commonProps.childListMember !== undefined;
150
+ },
151
+ enumerable: false,
152
+ configurable: true
153
+ });
55
154
  DropDownBase.prototype.afterRender = function (isInitial) {
56
155
  return __awaiter(this, void 0, void 0, function () {
57
156
  var _a, bindProperty, displayValue, readableName, fieldInfo;
@@ -13,7 +13,7 @@ interface IDeleteButtonOptions extends IButtonOptions {
13
13
  */
14
14
  onDeleteExisting?: (item: any) => Promise<boolean>;
15
15
  }
16
- export interface IButtonColumnProps extends INeoCommonColumnProps, Omit<React.HTMLProps<HTMLTableCellElement>, "children"> {
16
+ export interface IButtonColumnProps extends INeoCommonColumnProps {
17
17
  /** Item to pass to edit and delete callbacks. Defaults to the current rows item */
18
18
  item?: any;
19
19
  /** Text to show in the header. */
@@ -34,16 +34,20 @@ export interface IButtonColumnProps extends INeoCommonColumnProps, Omit<React.HT
34
34
  childAlignment?: "left" | "right";
35
35
  children?: React.ReactNode | (() => React.ReactNode);
36
36
  }
37
- export declare class ButtonColumn extends ComponentBase<IButtonColumnProps> {
37
+ type TableCellProps = Omit<React.HTMLProps<HTMLTableCellElement>, "children">;
38
+ export declare class ButtonColumn extends ComponentBase<IButtonColumnProps & TableCellProps> {
38
39
  static isColumnComponent: boolean;
39
40
  static contextType: React.Context<import("./Grid").GridContextParams<unknown>>;
40
41
  context: React.ContextType<typeof GridContext>;
41
- constructor(props: IButtonColumnProps);
42
+ constructor(props: IButtonColumnProps & TableCellProps);
42
43
  private item;
43
44
  private section;
44
45
  protected onFirstRender(): void;
45
46
  private get itemAsModel();
46
47
  onDelete(): Promise<void>;
47
- render(): React.JSX.Element;
48
+ render(): React.JSX.Element | null;
49
+ private renderHeader;
50
+ private renderBody;
51
+ private renderFooter;
48
52
  }
49
53
  export {};
@@ -60,28 +60,41 @@ var ButtonColumn = /** @class */ (function (_super) {
60
60
  });
61
61
  };
62
62
  ButtonColumn.prototype.render = function () {
63
- var _a, _b;
64
63
  this.onRender();
65
- var _c = this.props, item = _c.item, label = _c.label, showDefaultButtonText = _c.showDefaultButtonText, buttonSize = _c.buttonSize, showDelete = _c.showDelete, editButton = _c.editButton, deleteButton = _c.deleteButton, viewButton = _c.viewButton, childAlignment = _c.childAlignment, hideBelow = _c.hideBelow, hideAbove = _c.hideAbove, alignment = _c.alignment, domProps = __rest(_c, ["item", "label", "showDefaultButtonText", "buttonSize", "showDelete", "editButton", "deleteButton", "viewButton", "childAlignment", "hideBelow", "hideAbove", "alignment"]);
64
+ var _a = this.props, item = _a.item, label = _a.label, showDefaultButtonText = _a.showDefaultButtonText, buttonSize = _a.buttonSize, showDelete = _a.showDelete, editButton = _a.editButton, deleteButton = _a.deleteButton, viewButton = _a.viewButton, childAlignment = _a.childAlignment, hideBelow = _a.hideBelow, hideAbove = _a.hideAbove, alignment = _a.alignment, headerProps = _a.headerProps, cellProps = _a.cellProps, suppressHeader = _a.suppressHeader, domProps = __rest(_a, ["item", "label", "showDefaultButtonText", "buttonSize", "showDelete", "editButton", "deleteButton", "viewButton", "childAlignment", "hideBelow", "hideAbove", "alignment", "headerProps", "cellProps", "suppressHeader"]);
66
65
  domProps.className = Utils.joinWithSpaces("neo-grid-button-column", domProps.className);
67
66
  domProps.className = ColumnHelper.addCommonClasses(domProps.className, this.props);
67
+ return (this.section !== "body" ?
68
+ (this.section === "header" ?
69
+ (suppressHeader ? null : this.renderHeader(label, domProps, headerProps)) :
70
+ (suppressHeader ? null : this.renderFooter(domProps, headerProps))) :
71
+ this.renderBody(this.props, domProps, cellProps));
72
+ };
73
+ ButtonColumn.prototype.renderHeader = function (label, domProps, headerProps) {
74
+ if (headerProps) {
75
+ Object.assign(domProps, headerProps);
76
+ }
77
+ return React.createElement("th", __assign({}, domProps), label);
78
+ };
79
+ ButtonColumn.prototype.renderBody = function (props, domProps, cellProps) {
80
+ var _a, _b;
68
81
  var buttonColumnDefaults = Misc.Settings.grid.buttonColumnButtons;
69
82
  var editButtonDefaults = __assign(__assign({}, buttonColumnDefaults), Misc.Settings.grid.editButton);
70
83
  var deleteButtonDefaults = __assign(__assign({}, buttonColumnDefaults), Misc.Settings.grid.deleteButton);
71
84
  var viewButtonDefaults = __assign(__assign({}, buttonColumnDefaults), Misc.Settings.grid.viewButton);
72
- if (!showDefaultButtonText) {
85
+ if (!props.showDefaultButtonText) {
73
86
  editButtonDefaults.text = "";
74
87
  deleteButtonDefaults.text = "";
75
88
  viewButtonDefaults.text = "";
76
89
  }
77
- if (buttonSize) {
78
- editButtonDefaults.size = buttonSize;
79
- deleteButtonDefaults.size = buttonSize;
80
- viewButtonDefaults.size = buttonSize;
90
+ if (props.buttonSize) {
91
+ editButtonDefaults.size = props.buttonSize;
92
+ deleteButtonDefaults.size = props.buttonSize;
93
+ viewButtonDefaults.size = props.buttonSize;
81
94
  }
82
- var editButtonOptions = editButton ? Utils.populatePartialWithDefaults(editButton, editButtonDefaults) : null;
83
- var deleteButtonOptions = (deleteButton || showDelete) ? Utils.populatePartialWithDefaults(deleteButton, deleteButtonDefaults) : null;
84
- var viewButtonOptions = viewButton ? Utils.populatePartialWithDefaults(viewButton, viewButtonDefaults) : null;
95
+ var editButtonOptions = props.editButton ? Utils.populatePartialWithDefaults(props.editButton, editButtonDefaults) : null;
96
+ var deleteButtonOptions = (props.deleteButton || props.showDelete) ? Utils.populatePartialWithDefaults(props.deleteButton, deleteButtonDefaults) : null;
97
+ var viewButtonOptions = props.viewButton ? Utils.populatePartialWithDefaults(props.viewButton, viewButtonDefaults) : null;
85
98
  if (deleteButtonOptions) {
86
99
  if (!deleteButtonOptions.onClick) {
87
100
  deleteButtonOptions.onClick = this.onDelete;
@@ -95,13 +108,20 @@ var ButtonColumn = /** @class */ (function (_super) {
95
108
  viewButtonOptions && React.createElement(NeoButton, __assign({}, viewButtonOptions)),
96
109
  editButtonOptions && React.createElement(NeoButton, __assign({}, editButtonOptions)),
97
110
  deleteButtonOptions && React.createElement(NeoButton, __assign({}, deleteButtonOptions)));
98
- var children = this.props.children instanceof Function ? this.props.children() : this.props.children;
99
- return (this.section !== "body" ?
100
- React.createElement("th", __assign({}, domProps), this.section === "header" && label) :
101
- React.createElement("td", __assign({}, domProps),
102
- childAlignment === "right" ? buttons : undefined,
103
- React.Children.map(children, function (c) { return setButtonProps(c); }),
104
- childAlignment !== "right" ? buttons : undefined));
111
+ var children = props.children instanceof Function ? props.children() : props.children;
112
+ if (cellProps) {
113
+ Object.assign(domProps, cellProps);
114
+ }
115
+ return (React.createElement("td", __assign({}, domProps),
116
+ props.childAlignment === "right" ? buttons : undefined,
117
+ React.Children.map(children, function (c) { return setButtonProps(c); }),
118
+ props.childAlignment !== "right" ? buttons : undefined));
119
+ };
120
+ ButtonColumn.prototype.renderFooter = function (domProps, headerProps) {
121
+ if (headerProps) {
122
+ Object.assign(domProps, headerProps);
123
+ }
124
+ return React.createElement("th", __assign({}, domProps));
105
125
  };
106
126
  ButtonColumn.isColumnComponent = true;
107
127
  ButtonColumn.contextType = GridContext;
@@ -43,7 +43,7 @@ export default class Column<TData> extends ComponentBase<INeoColumnProps<TData>>
43
43
  private headerClick;
44
44
  refreshStateObserver(props: IBindableEditorProps): void;
45
45
  componentWillUnmount(): void;
46
- render(): React.JSX.Element;
46
+ render(): React.JSX.Element | null;
47
47
  private renderHeader;
48
48
  private renderBody;
49
49
  private renderFooter;
@@ -91,7 +91,7 @@ var Column = /** @class */ (function (_super) {
91
91
  Column.prototype.render = function () {
92
92
  this.onRender();
93
93
  var allProps = BindPropsHelper.normaliseContainerProps(this, this.props, false);
94
- var _a = allProps.containerDom, width = _a.width, hideBelow = _a.hideBelow, hideAbove = _a.hideAbove, headerTooltip = _a.headerTooltip, cellTooltip = _a.cellTooltip, alignment = _a.alignment, sort = _a.sort, sum = _a.sum, footerContent = _a.footerContent, headerClassName = _a.headerClassName, cellClassName = _a.cellClassName, onHeaderClick = _a.onHeaderClick, domProps = __rest(_a, ["width", "hideBelow", "hideAbove", "headerTooltip", "cellTooltip", "alignment", "sort", "sum", "footerContent", "headerClassName", "cellClassName", "onHeaderClick"]);
94
+ var _a = allProps.containerDom, width = _a.width, hideBelow = _a.hideBelow, hideAbove = _a.hideAbove, headerTooltip = _a.headerTooltip, cellTooltip = _a.cellTooltip, alignment = _a.alignment, sort = _a.sort, invertSortIcon = _a.invertSortIcon, sum = _a.sum, footerContent = _a.footerContent, headerClassName = _a.headerClassName, cellClassName = _a.cellClassName, headerProps = _a.headerProps, cellProps = _a.cellProps, suppressHeader = _a.suppressHeader, onHeaderClick = _a.onHeaderClick, domProps = __rest(_a, ["width", "hideBelow", "hideAbove", "headerTooltip", "cellTooltip", "alignment", "sort", "invertSortIcon", "sum", "footerContent", "headerClassName", "cellClassName", "headerProps", "cellProps", "suppressHeader", "onHeaderClick"]);
95
95
  if (allProps.bind !== undefined) {
96
96
  if (!this.controlType || this.isHeader) {
97
97
  this.controlType = BindPropsHelper.getControlType(allProps);
@@ -131,14 +131,17 @@ var Column = /** @class */ (function (_super) {
131
131
  }
132
132
  }
133
133
  this.refreshStateObserver(allProps.editor);
134
- return this.isHeader ? this.renderHeader(allProps, domProps, headerTooltip, headerClassName) :
135
- this.isFooter ? this.renderFooter(allProps, domProps) :
136
- this.renderBody(allProps, domProps, cellTooltip, cellClassName);
134
+ return this.isHeader ? (suppressHeader ? null : this.renderHeader(allProps, domProps, headerTooltip, headerClassName, headerProps)) :
135
+ this.isFooter ? (suppressHeader ? null : this.renderFooter(allProps, domProps, headerProps)) :
136
+ this.renderBody(allProps, domProps, cellTooltip, cellClassName, cellProps);
137
137
  };
138
- Column.prototype.renderHeader = function (props, domProps, tooltip, headerClassName) {
138
+ Column.prototype.renderHeader = function (props, domProps, tooltip, headerClassName, headerProps) {
139
139
  var _this = this;
140
140
  var sortIcon = null;
141
141
  var sortProperty = this.getSortProperty();
142
+ if (headerProps) {
143
+ Object.assign(domProps, headerProps);
144
+ }
142
145
  domProps.className = Utils.joinWithSpaces(domProps.className, headerClassName);
143
146
  if (sortProperty && !this.suppressSort) {
144
147
  var sortInfo = this.context.dataView.sortInfo.find(function (c) { return c.column === sortProperty.propertyInfo; });
@@ -154,7 +157,7 @@ var Column = /** @class */ (function (_super) {
154
157
  sortIcon &&
155
158
  React.createElement("i", { className: "column-sort-icon fa fa-" + sortIcon })));
156
159
  };
157
- Column.prototype.renderBody = function (props, domProps, cellTooltip, cellClassName) {
160
+ Column.prototype.renderBody = function (props, domProps, cellTooltip, cellClassName, cellProps) {
158
161
  var _this = this;
159
162
  var editorProps = props.editor;
160
163
  var containerProps = props.container;
@@ -188,6 +191,9 @@ var Column = /** @class */ (function (_super) {
188
191
  editor = BindPropsHelper.getFormControl(props);
189
192
  }
190
193
  }
194
+ if (cellProps) {
195
+ Object.assign(domProps, cellProps);
196
+ }
191
197
  domProps.className = Utils.joinWithSpaces(domProps.className, cellClassName);
192
198
  return (React.createElement("td", __assign({ "data-tip": cellTooltip }, domProps),
193
199
  !containerProps.suppressDefaultContent && (editor !== undefined ?
@@ -197,10 +203,13 @@ var Column = /** @class */ (function (_super) {
197
203
  containerProps.bindContext &&
198
204
  React.createElement(FormContext.Consumer, null, function (value) { return (React.createElement(FormContext.Provider, { value: __assign(__assign({}, value), { parentType: EditorParentType.Column, bindProperty: containerProps.bindContext }) }, _this.props.children instanceof Function ? _this.props.children() : _this.props.children)); })));
199
205
  };
200
- Column.prototype.renderFooter = function (props, domProps) {
206
+ Column.prototype.renderFooter = function (props, domProps, headerProps) {
201
207
  var _this = this;
202
208
  var colProps = props.containerDom;
203
209
  var hasSummary = (colProps.sum && !!props.bind) || colProps.footerContent;
210
+ if (headerProps) {
211
+ Object.assign(domProps, headerProps);
212
+ }
204
213
  return (React.createElement("th", __assign({}, domProps), hasSummary &&
205
214
  React.createElement(Observer, null, function () { return colProps.footerContent ? colProps.footerContent(_this.context.dataView.filteredItems) :
206
215
  _this.numFormatState(props.bind.propertyInfo, props.bind.propertyInfo.propertyType, props.numProps).getFormattedValue(_this.context.dataView.filteredItems.sum(props.bind.propertyInfo)); })));
@@ -6,7 +6,14 @@ export interface INeoCommonColumnProps {
6
6
  hideAbove?: Components.breakpoint;
7
7
  /** Set the alignment of the column. */
8
8
  alignment?: "left" | "center" | "right";
9
+ /** Props to apply to the header cell. */
10
+ headerProps?: TableCellSectionProps;
11
+ /** Props to apply to body cells. */
12
+ cellProps?: TableCellSectionProps;
13
+ /** If set, the header cell for this column will be hidden. */
14
+ suppressHeader?: boolean;
9
15
  }
16
+ export type TableCellSectionProps = Omit<React.HTMLProps<HTMLTableCellElement>, "className" | "onClick">;
10
17
  export declare class ColumnHelper {
11
18
  static addCommonClasses(className: string | undefined, props: INeoCommonColumnProps): string | undefined;
12
19
  }
@@ -21,9 +21,10 @@ var Row = /** @class */ (function (_super) {
21
21
  if (!this.context.doneStaticSetup) {
22
22
  this.context.doneStaticSetup = true;
23
23
  React.Children.forEach(this.props.children, function (child) {
24
+ var _a, _b, _c;
24
25
  if (child) {
25
26
  var childProps = child.props;
26
- _this.context.columnCount += (childProps ? childProps.colSpan || 1 : 1);
27
+ _this.context.columnCount += (childProps ? ((_c = (_b = (_a = childProps.cellProps) === null || _a === void 0 ? void 0 : _a.colSpan) !== null && _b !== void 0 ? _b : childProps.colSpan) !== null && _c !== void 0 ? _c : 1) : 1);
27
28
  if (childProps && (childProps.sum || childProps.footerContent)) {
28
29
  _this.context.hasSummaries = true;
29
30
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@singularsystems/neo-react",
3
- "version": "1.6.2",
3
+ "version": "1.6.4",
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",
@@ -27,7 +27,7 @@
27
27
  "typescript": "5.9.3"
28
28
  },
29
29
  "dependencies": {
30
- "@singularsystems/neo-core": "1.6.1",
30
+ "@singularsystems/neo-core": "1.6.3",
31
31
  "@types/react-color": "3.0.13",
32
32
  "axios": "1.13.5",
33
33
  "inversify": "6.0.2",
package/styles/Card.scss CHANGED
@@ -37,4 +37,23 @@
37
37
  border-bottom: 0;
38
38
  }
39
39
  }
40
+
41
+ &.card--with-description {
42
+ .card-header {
43
+ .card-header-title {
44
+ display: flex;
45
+ align-items: center;
46
+
47
+ .neo-icon {
48
+ margin-right: 1rem;
49
+ }
50
+ }
51
+ }
52
+
53
+ .card-title-description {
54
+ font-size: 14px;
55
+ line-height: 1.2;
56
+ color: $text-muted;
57
+ }
58
+ }
40
59
  }