@singularsystems/neo-react 1.2.4 → 1.3.1

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
@@ -1,171 +1,50 @@
1
- # Version 1.2
1
+ # Neo react changelog
2
2
 
3
- Version 1.2 upgrades React to v19. A few of the other dependencies were also updated, which will need to be updated in each project.
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
- Please take the following steps when upgrading:
5
+ ## Version 1.3.1
6
6
 
7
- - Update `react`, `react-dom` and their types packages to the latest version.
8
- - Update `react-select` to at least 5.10.0.
9
- - Update `react-custom-scrollbars` and its types to the latest version.
10
- - If you are using `rc-slider`.
11
- - Remove the `@types/rc-slider` package, and update `rc-slider` to the latest version.
12
- - In your app module, remove the `Range` import, and remove this line: `container.bind(Types.Neo.Components.Range).toConstantValue(Range);`
13
- - If you are using `react-color`.
14
- - Update the package, and types to the latest version.
15
- - Under React 19, JSX is no longer a global import. You may receive errors such as "Cannot find namespace 'JSX'."
16
- - To rectify, change `JSX.Element` to `React.ReactNode`.
17
- - If that doesn't work, you can import JSX from React. E.g. `import React, { JSX } from 'React';`
18
- - Finally, run `yarn dedupe` to make sure all old transitive packages reference the project version.
7
+ - Routing - Fixed an issue where the base route would take precedence over other routes if it had parameters, and was declared before other routes.
19
8
 
20
- # Version 1.1
9
+ ## Version 1.3.0
21
10
 
22
- Version 1.1 removes the `react-router` dependency. This requires updates to your project which are explained in [this guide](./docs/RemoveReactRouter.md).
11
+ - ViewBase and ViewComponentBase now require that the linked view model has a `TaskRunner` as the first argument. See breaking changes for more info.
12
+ - GridLayout
13
+ - Added `arrangeVertically` prop. Allows items to flow from top to bottom instead of left to right. Useful the height of each item differs.
14
+ - Neo.Card
15
+ - Now has a `footerContent` prop. This allows a footer to be added to the card. The footer will be anchored to the bottom of the card, and will be collapsed if the card is collapsed.
16
+ - Settings
17
+ - Added `buttons.backButton` setting to replace the `icons.backIcon` global setting. This allows more customisation of back buttons used in built in neo screens.
18
+ - Added `icons.unDelete` to allow changing the undo delete icon in neo grids.
19
+ - Deferred deleting: see below.
23
20
 
24
- It also fixes some issues preventing projects from upgrading to `mobx-react` v9.x.
21
+ ### Deferred deleting
25
22
 
26
- ## Version 1.0.22
23
+ Previously when clicking the default delete button in a neo grid, the item would be removed from the bound list, and tracked under the `deletedList` property. When serialising the list (usually on clicking save), the deleted items would be included with a tracking state of "deleted" so that they could be deleted from the database.
27
24
 
28
- React-select dependency was updated which has a breaking change in its types. You will need to update the version of this package in your project to at least:
25
+ This was slightly confusing for a user, since they might not know that a save is required to actually commit the delete. Additionally, if the save failed, the item would not re-appear in the grid and the user would be stuck, having to reload the screen.
29
26
 
30
- ```json
31
- "react-select": "5.8.1",
32
- ```
27
+ In this version, a new option has been added to allow the grid to keep displaying the row being deleted, but displayed with ~~strikethrough~~ text and 50% transparency. The delete button will show a different icon when the item is marked as deleted, allowing the user to undo the delete before saving. A tooltip will also show when hovering over the row.
33
28
 
34
- # Version 1.0
29
+ To make this work, 3 main changes were made to `ModelBase` and `List`.
30
+ - `ModelBase` now has an `isSelfDeleting` property.
31
+ - This causes the grid to display the item differently.
32
+ - Models with this property set to true will be serialised with a tracking state of deleted.
33
+ - This will cause `isDirty` to return true, however `isSelfDirty` will remain false.
34
+ - `List` has new methods: `beginRemove` and `toggleRemove`.
35
+ - These new methods check whether the list is in "soft delete" mode (see below), and if so, sets `isSelfDeleting` to true on the item being removed.
36
+ - The existing `remove` and `removeWithoutTracking` methods will always remove the item from the main list regardless of the delete mode.
37
+ - `List` now has an `activeLength` property.
38
+ - Use this if you need to check if the list is required to have a certain amount of items. E.g. in validation rules.
35
39
 
36
- Version 1 of neo-react upgrades react to version 18, and mobX to version 6. Both of these are major updates and will require you to make code changes to your project.
37
40
 
38
- ## Testing
41
+ #### Settings:
39
42
 
40
- In addition to requiring code / syntax changes, both React 18, and Mobx 6 behave differently enough to their previous versions that runtime bugs may be introduced into your project. You should only update to this version when you and the client have enough time to test the project thoroughly. If you find any issues, please report them so this document can be updated.
43
+ To control the behavior when removing an item, you use the global setting, or you can configure each list individually by passing in options to the list constructor.
41
44
 
42
- The project changes required are detailed below:
45
+ - To configure globally, set `Misc.Settings.list.removeItemBehavior` to `RemoveItemBehavior.MarkDeleted`.
46
+ - The tooltip to show that the item is being deleted can be customised by setting the `isDeletingTooltip` prop on the grid row, or by using the `Misc.Settings.grid.isDeletingTooltip` global setting.
43
47
 
44
- ## Package changes:
48
+ By default this setting will be enabled for catalogue grids, but is not enabled globally.
45
49
 
46
- * Update all neo packages to the latest 1.x version.
47
- * Update the following packages to the latest version (these were the latest at time of writing):
48
- * dependencies
49
- * `"axios": "1.6.7"`,
50
- * `"decimal.js-light": "2.5.1"`, - See below for alternative
51
- * `"mobx": "6.11.0"`,
52
- * `"mobx-react": "7.6.0"`,
53
- * `"react": "18.2.0"`,
54
- * `"react-dom": "18.2.0"`,
55
- * `"react-router": "5.2.1"`, - Must be version 5.x
56
- * `"react-router-dom": "5.3.0"`, - Must be version 5.x
57
- * `"react-select": "5.7.3"`,
58
-
59
- * devDependencies
60
- * `"@types/react": "18.2.37"`,
61
- * `"@types/react-dom": "18.2.15"`,
62
- * `"@types/react-router": "5.1.17"`, - Must be version 5.x
63
- * `"@types/react-router-dom": "5.3.3"`, - Must be version 5.x
64
- * resolutions
65
- * remove all entries
66
- * delete yarn.lock, and run `yarn install`
67
-
68
- ## Fix errors
69
-
70
- If any of your components allow children, React 18 requires to you have an explicit `children` prop. Add this to the props definition of the component:
71
-
72
- ```typescript
73
- interface MyProps {
74
- ...
75
- children: React.ReactNode;
76
- }
77
-
78
- // or if the component has no other props:
79
-
80
- MyComponent extends React.Component<{ children: React.ReactNode }> {
81
- ...
82
- }
83
- ```
84
-
85
- The React root api has also changed in version 18. Make the following changes to your `index.tsx` file:
86
-
87
- Your existing code will look something like this:
88
-
89
- ```typescript
90
- import ReactDOM from 'react-dom';
91
- ...
92
- // React init
93
- ReactDOM.render(
94
- renderComponent,
95
- document.getElementById('root') as HTMLElement
96
- );
97
- ```
98
-
99
- Change the `ReactDOM` import, and the react init code:
100
-
101
- ```typescript
102
- import ReactDOM from 'react-dom/client';
103
- ...
104
- // React init
105
- const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
106
- root.render(renderComponent);
107
- ```
108
-
109
- ## Mobx
110
-
111
- Before mobx 6, you could mark a property as being observable by adding an `@observable` decorator to the property. In mobx 6 this no longer works ([read more](https://mobx.js.org/migrating-from-4-or-5.html)).
112
-
113
- For any class that extends `ModelBase`, `ValueObject`, `ViewModelBase` etc, this change is handled by neo, and you will not need to change any code.
114
-
115
- For other classes, you will need to add initialisation code to the constructor.
116
-
117
- * Search for `@observable` in your project. (In most projects this will be in the Sidebar and AppLayout components)
118
- * In the constructor of the class that contains the property, add:
119
-
120
- ```typescript
121
- makeObservable(this);
122
- ```
123
-
124
- ### Decorators on neo models
125
-
126
- If you try use mobx decorators on any models that extend `ModelBase`, `ValueObject` or `ViewModelBase`, you will receive this error:
127
-
128
- > [MobX] makeObservable second arg must be nullish when using decorators. Mixing @decorator syntax with annotations is not supported.
129
-
130
- This is most likely because you have used an `@action` decorator on a method. Neo now marks all methods as actions, so you can remove the `@action` decorator.
131
-
132
- * Search for `@action` in your project. Remove the decorator **only** if the class extends `ModelBase` (and its decendents e.g. `ValueObject`) or `ViewModelBase`.
133
-
134
- ### Array sorting
135
-
136
- Mobx no longer allows you to call `sort` on an observable array. You first need to call `slice` to create a copy of the array. This change has been made in the neo `sortBy` method, but will cause unexpected behaviour if you relied on the list being sorted in-place, as this no longer happens.
137
-
138
- * Search for `.sort` in your project. Change to use the neo `sortBy` method.
139
- * Search for `.sortBy`. Ensure you use the array returned by this method, and not the array passed in.
140
-
141
- ### Viewmodels
142
-
143
- You may receive errors such as:
144
-
145
- ```
146
- Argument of type 'typeof MyVM' is not assignable to parameter of type 'VMConstructor<MyVM>'.
147
- ```
148
-
149
- The `registerViewModel` method on the `ViewModelBase` class now requires the view model passed to it to have a `taskRunner` as the first argument. This task runner instance must be passed to the base constructor.
150
-
151
- ## Decimals
152
-
153
- Neo no longer includes the `decimal.js-light` package by default. This is to allow for other decimal packages to be used. You will now need to manually register a decimal service in your app module.
154
-
155
- The easiest would be to continue using `decimal.js-light`:
156
-
157
- * Ensure your project references `decimal.js-light` directly.
158
- * In your main app module, add the following code:
159
- ```typescript
160
- import Decimal from "decimal.js-light";
161
- ...
162
- container.bind(Types.Neo.Core.DecimalService).toConstantValue(new Numeric.DecimalService(Decimal, Decimal.ROUND_HALF_UP, Decimal.ROUND_DOWN));
163
- ```
164
-
165
- An alternative is to use `big.js`. This package is slightly smaller than the above, but has less features.
166
-
167
- ```typescript
168
- import Big from "big.js";
169
- ...
170
- container.bind(Types.Neo.Core.DecimalService).toConstantValue(new Numeric.DecimalService(Big, Big.roundHalfUp, Big.roundDown))
171
- ```
50
+ To change the styling of deleted rows, target: `tr.neo-item-deleting`.
@@ -1,6 +1,8 @@
1
+ import { Components } from "@singularsystems/neo-core";
1
2
  import React, { MouseEventHandler } from "react";
2
3
  declare class BootstrapUtils {
3
4
  constructor();
5
+ private breakPoints;
4
6
  /** Bootstrap version is before v5 */
5
7
  get isPre5(): boolean;
6
8
  /** Class prefix (without -) for start margin / left margin. */
@@ -12,6 +14,8 @@ declare class BootstrapUtils {
12
14
  * @param pre5ClassName Class to add if bootstrap version is <5.
13
15
  */
14
16
  getCloseButton(onClick: MouseEventHandler<HTMLButtonElement>, pre5ClassName?: string): React.JSX.Element;
17
+ getBreakpoints(): Components.IBootstrapSizes<number>;
18
+ currentDimension<T>(props: Partial<Components.IBootstrapSizes<T>>): T | undefined;
15
19
  }
16
20
  declare const _default: BootstrapUtils;
17
21
  export default _default;
@@ -2,6 +2,7 @@ import { Misc, Utils } from "@singularsystems/neo-core";
2
2
  import React from "react";
3
3
  var BootstrapUtils = /** @class */ (function () {
4
4
  function BootstrapUtils() {
5
+ this.breakPoints = null;
5
6
  }
6
7
  Object.defineProperty(BootstrapUtils.prototype, "isPre5", {
7
8
  /** Bootstrap version is before v5 */
@@ -38,6 +39,41 @@ var BootstrapUtils = /** @class */ (function () {
38
39
  React.createElement("span", { "aria-hidden": "true" }, "\u00D7")) :
39
40
  React.createElement("button", { type: "button", className: "btn-close", "aria-label": "Close", onClick: onClick });
40
41
  };
42
+ BootstrapUtils.prototype.getBreakpoints = function () {
43
+ if (!this.breakPoints) {
44
+ var styles = window.getComputedStyle(document.body);
45
+ this.breakPoints = {
46
+ xs: parseInt(styles.getPropertyValue('--bs-breakpoint-xs')),
47
+ sm: parseInt(styles.getPropertyValue('--bs-breakpoint-sm')),
48
+ md: parseInt(styles.getPropertyValue('--bs-breakpoint-md')),
49
+ lg: parseInt(styles.getPropertyValue('--bs-breakpoint-lg')),
50
+ xl: parseInt(styles.getPropertyValue('--bs-breakpoint-xl')),
51
+ xxl: parseInt(styles.getPropertyValue('--bs-breakpoint-xxl'))
52
+ };
53
+ }
54
+ return this.breakPoints;
55
+ };
56
+ BootstrapUtils.prototype.currentDimension = function (props) {
57
+ var _a, _b, _c, _d, _e;
58
+ var width = window.innerWidth;
59
+ var breakpoints = this.getBreakpoints();
60
+ var found = props.xs;
61
+ if (width < breakpoints.sm)
62
+ return found;
63
+ found = (_a = props.sm) !== null && _a !== void 0 ? _a : found;
64
+ if (width < breakpoints.md)
65
+ return found;
66
+ found = (_b = props.md) !== null && _b !== void 0 ? _b : found;
67
+ if (width < breakpoints.lg)
68
+ return found;
69
+ found = (_c = props.lg) !== null && _c !== void 0 ? _c : found;
70
+ if (width < breakpoints.xl)
71
+ return found;
72
+ found = (_d = props.xl) !== null && _d !== void 0 ? _d : found;
73
+ if (width < breakpoints.xxl)
74
+ return found;
75
+ return (_e = props.xxl) !== null && _e !== void 0 ? _e : found;
76
+ };
41
77
  return BootstrapUtils;
42
78
  }());
43
79
  export default new BootstrapUtils();
@@ -7,6 +7,8 @@ interface ICardProps {
7
7
  title?: any;
8
8
  /** Header elements to show on right hand side of header. */
9
9
  headerElements?: JSX.Element;
10
+ /** Footer elements to show on bottom of the card. */
11
+ footerContent?: JSX.Element;
10
12
  /** Use a custom scrollbar instead of the browser scrollbar. */
11
13
  useCustomScrollbar?: boolean;
12
14
  /** Causes the card to be collapsible. Adds a collapse icon to the header. */
@@ -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, useCustomScrollbar = _a.useCustomScrollbar, collapsible = _a.collapsible, initiallyCollapsed = _a.initiallyCollapsed, isExpanded = _a.isExpanded, expandIconPosition = _a.expandIconPosition, domProps = __rest(_a, ["icon", "title", "children", "headerElements", "useCustomScrollbar", "collapsible", "initiallyCollapsed", "isExpanded", "expandIconPosition"]);
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"]);
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;
@@ -45,9 +45,13 @@ var Card = /** @class */ (function (_super) {
45
45
  var body = React.createElement("div", { className: "card-body" },
46
46
  useCustomScrollbar && React.createElement(Scrollbar, null, children),
47
47
  !useCustomScrollbar && children);
48
+ var footer = footerContent ? (React.createElement("div", { className: 'card-footer' }, footerContent)) : null;
49
+ var content = React.createElement(React.Fragment, null,
50
+ body,
51
+ footer);
48
52
  if (isCollapsible) {
49
- body =
50
- React.createElement(Collapsable, { show: expandState.value }, body);
53
+ content =
54
+ React.createElement(Collapsable, { show: expandState.value }, content);
51
55
  }
52
56
  return (React.createElement("div", __assign({}, domProps, { className: className }),
53
57
  (icon || title) &&
@@ -57,7 +61,7 @@ var Card = /** @class */ (function (_super) {
57
61
  this.iconFactory.getIconComponent(icon),
58
62
  React.createElement("span", null, title)),
59
63
  headerElements && React.createElement("div", { className: "card-header-right-section", onClick: function (e) { return e.stopPropagation(); } }, headerElements)),
60
- body));
64
+ content));
61
65
  };
62
66
  Card = __decorate([
63
67
  observer
@@ -42,6 +42,7 @@ export declare class ButtonColumn extends ComponentBase<IButtonColumnProps> {
42
42
  private item;
43
43
  private section;
44
44
  protected onFirstRender(): void;
45
+ private get itemAsModel();
45
46
  onDelete(): Promise<void>;
46
47
  render(): React.JSX.Element;
47
48
  }
@@ -1,10 +1,11 @@
1
- import { __assign, __awaiter, __extends, __generator, __rest } from "tslib";
1
+ import { __assign, __awaiter, __decorate, __extends, __generator, __metadata, __rest } from "tslib";
2
2
  import * as React from 'react';
3
3
  import { GridContext } from './Grid';
4
4
  import NeoButton from '../Button';
5
5
  import { Misc, Utils } from '@singularsystems/neo-core';
6
6
  import ComponentBase from '../ComponentBase';
7
7
  import { ColumnHelper } from './ColumnHelper';
8
+ import { observer } from "mobx-react";
8
9
  var ButtonColumn = /** @class */ (function (_super) {
9
10
  __extends(ButtonColumn, _super);
10
11
  function ButtonColumn(props) {
@@ -16,33 +17,52 @@ var ButtonColumn = /** @class */ (function (_super) {
16
17
  this.section = this.context.section;
17
18
  this.item = this.context.currentItem;
18
19
  };
20
+ Object.defineProperty(ButtonColumn.prototype, "itemAsModel", {
21
+ get: function () {
22
+ var _a;
23
+ var item = (_a = this.props.item) !== null && _a !== void 0 ? _a : this.item;
24
+ if ((item === null || item === void 0 ? void 0 : item.entityIdentifier) !== undefined) {
25
+ return item;
26
+ }
27
+ return undefined;
28
+ },
29
+ enumerable: false,
30
+ configurable: true
31
+ });
19
32
  ButtonColumn.prototype.onDelete = function () {
20
33
  return __awaiter(this, void 0, void 0, function () {
21
- var item, mustRemove;
22
- var _a, _b;
23
- return __generator(this, function (_c) {
24
- switch (_c.label) {
34
+ var item, list, mustRemove;
35
+ var _a;
36
+ return __generator(this, function (_b) {
37
+ switch (_b.label) {
25
38
  case 0:
26
- item = (_a = this.props.item) !== null && _a !== void 0 ? _a : this.item;
27
- if (!(item.isNew === false && ((_b = this.props.deleteButton) === null || _b === void 0 ? void 0 : _b.onDeleteExisting) !== undefined)) return [3 /*break*/, 2];
39
+ item = this.itemAsModel;
40
+ list = this.context.dataView.rawItems;
41
+ if (!(item.isNew === false && ((_a = this.props.deleteButton) === null || _a === void 0 ? void 0 : _a.onDeleteExisting) !== undefined)) return [3 /*break*/, 2];
28
42
  return [4 /*yield*/, this.props.deleteButton.onDeleteExisting(item)];
29
43
  case 1:
30
- mustRemove = _c.sent();
44
+ mustRemove = _b.sent();
31
45
  if (mustRemove) {
32
- this.context.dataView.rawItems.removeWithoutTracking(item);
46
+ list.removeWithoutTracking(item);
33
47
  }
34
48
  return [3 /*break*/, 3];
35
49
  case 2:
36
- this.context.dataView.rawItems.remove(item);
37
- _c.label = 3;
50
+ if (list.toggleRemove !== undefined) {
51
+ list.toggleRemove(item);
52
+ }
53
+ else {
54
+ this.context.dataView.rawItems.remove(item);
55
+ }
56
+ _b.label = 3;
38
57
  case 3: return [2 /*return*/];
39
58
  }
40
59
  });
41
60
  });
42
61
  };
43
62
  ButtonColumn.prototype.render = function () {
63
+ var _a, _b;
44
64
  this.onRender();
45
- 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, domProps = __rest(_a, ["item", "label", "showDefaultButtonText", "buttonSize", "showDelete", "editButton", "deleteButton", "viewButton", "childAlignment", "hideBelow", "hideAbove", "alignment"]);
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"]);
46
66
  domProps.className = Utils.joinWithSpaces("neo-grid-button-column", domProps.className);
47
67
  domProps.className = ColumnHelper.addCommonClasses(domProps.className, this.props);
48
68
  var buttonColumnDefaults = Misc.Settings.grid.buttonColumnButtons;
@@ -62,8 +82,14 @@ var ButtonColumn = /** @class */ (function (_super) {
62
82
  var editButtonOptions = editButton ? Utils.populatePartialWithDefaults(editButton, editButtonDefaults) : null;
63
83
  var deleteButtonOptions = (deleteButton || showDelete) ? Utils.populatePartialWithDefaults(deleteButton, deleteButtonDefaults) : null;
64
84
  var viewButtonOptions = viewButton ? Utils.populatePartialWithDefaults(viewButton, viewButtonDefaults) : null;
65
- if (deleteButtonOptions && !deleteButtonOptions.onClick) {
66
- deleteButtonOptions.onClick = this.onDelete;
85
+ if (deleteButtonOptions) {
86
+ if (!deleteButtonOptions.onClick) {
87
+ deleteButtonOptions.onClick = this.onDelete;
88
+ }
89
+ if ((_a = this.itemAsModel) === null || _a === void 0 ? void 0 : _a.isSelfDeleting) {
90
+ deleteButtonOptions.icon = Misc.Settings.icons.unDelete;
91
+ (_b = deleteButtonOptions.tooltip) !== null && _b !== void 0 ? _b : (deleteButtonOptions.tooltip = "Undo delete");
92
+ }
67
93
  }
68
94
  var buttons = React.createElement(React.Fragment, null,
69
95
  viewButtonOptions && React.createElement(NeoButton, __assign({}, viewButtonOptions)),
@@ -79,6 +105,10 @@ var ButtonColumn = /** @class */ (function (_super) {
79
105
  };
80
106
  ButtonColumn.isColumnComponent = true;
81
107
  ButtonColumn.contextType = GridContext;
108
+ ButtonColumn = __decorate([
109
+ observer,
110
+ __metadata("design:paramtypes", [Object])
111
+ ], ButtonColumn);
82
112
  return ButtonColumn;
83
113
  }(ComponentBase));
84
114
  export { ButtonColumn };
@@ -9,7 +9,7 @@ export interface INeoColumnProps<TData> extends INeoCommonColumnProps, IEditorCo
9
9
  width?: number | string;
10
10
  /** Tooltip to show in header. If you want the tooltip to show on the cells as well, use 'data-tip'. */
11
11
  headerTooltip?: string;
12
- /** Tooltip to show in cells. If you want the tooltip to show on the cells as well, use 'data-tip'. */
12
+ /** Tooltip to show in cells. If you want the tooltip to show on the headers as well, use 'data-tip'. */
13
13
  cellTooltip?: string;
14
14
  /** Allows you to override the default allow sorting setting of the datasource, or make clicking a column heading sort a specific property. */
15
15
  sort?: Model.IPropertyInstance | boolean;
@@ -1,7 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { GridContext, GridSection } from './Grid';
3
3
  import { Model } from '@singularsystems/neo-core';
4
- import { StateObserver } from '../StateObserver';
5
4
  import ComponentBase from '../ComponentBase';
6
5
  interface IRowProps extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLTableRowElement>, HTMLTableRowElement> {
7
6
  showInHeader?: boolean;
@@ -15,9 +14,8 @@ interface IRowProps extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLTab
15
14
  onHeaderClick?: (section: GridSection, e: React.MouseEvent<HTMLTableRowElement, MouseEvent>) => void;
16
15
  /** Class name that will not be applied to headers. */
17
16
  bodyClassName?: string;
18
- }
19
- interface IState {
20
- isExpanded: boolean;
17
+ /** Allows overriding the tooltip shown when an item is marked for deletion. */
18
+ isDeletingTooltip?: string;
21
19
  }
22
20
  export default class Row<TData> extends ComponentBase<IRowProps> {
23
21
  static contextType: React.Context<import("./Grid").GridContextParams<unknown>>;
@@ -27,10 +25,11 @@ export default class Row<TData> extends ComponentBase<IRowProps> {
27
25
  showInHeader: boolean;
28
26
  };
29
27
  private setGridContext;
30
- section: GridSection;
31
- stateObserver: StateObserver<IState>;
32
- expandProperty?: Model.IPropertyInstance;
33
- firstRowInGroup: boolean;
28
+ private section;
29
+ private stateObserver;
30
+ private expandProperty?;
31
+ private firstRowInGroup;
32
+ private item?;
34
33
  onFirstRender(): void;
35
34
  componentWillUnmount(): void;
36
35
  onExpandClick(): void;
@@ -7,6 +7,7 @@ import Column from './Column';
7
7
  import NeoButton from '../Button';
8
8
  import { StateObserver } from '../StateObserver';
9
9
  import ComponentBase from '../ComponentBase';
10
+ import Tooltip from "../Tooltip";
10
11
  var Row = /** @class */ (function (_super) {
11
12
  __extends(Row, _super);
12
13
  function Row(props) {
@@ -40,6 +41,9 @@ var Row = /** @class */ (function (_super) {
40
41
  return { isExpanded: _this.expandProperty.value };
41
42
  });
42
43
  }
44
+ if (this.context.currentItem && this.context.currentItem.isSelfDeleting !== undefined) {
45
+ this.item = this.context.currentItem;
46
+ }
43
47
  };
44
48
  Row.prototype.componentWillUnmount = function () {
45
49
  if (this.stateObserver)
@@ -52,8 +56,9 @@ var Row = /** @class */ (function (_super) {
52
56
  };
53
57
  Row.prototype.render = function () {
54
58
  var _this = this;
59
+ var _a, _b;
55
60
  _super.prototype.onRender.call(this);
56
- var _a = this.props, showInHeader = _a.showInHeader, onHeaderClick = _a.onHeaderClick, bodyClassName = _a.bodyClassName, domProps = __rest(_a, ["showInHeader", "onHeaderClick", "bodyClassName"]);
61
+ var _c = this.props, showInHeader = _c.showInHeader, onHeaderClick = _c.onHeaderClick, bodyClassName = _c.bodyClassName, domProps = __rest(_c, ["showInHeader", "onHeaderClick", "bodyClassName"]);
57
62
  var inBody = this.section === "body";
58
63
  if (this.stateObserver)
59
64
  this.stateObserver.observe();
@@ -69,6 +74,10 @@ var Row = /** @class */ (function (_super) {
69
74
  if (bodyClassName) {
70
75
  domProps.className = Utils.joinWithSpaces(domProps.className, bodyClassName);
71
76
  }
77
+ if ((_a = this.item) === null || _a === void 0 ? void 0 : _a.isSelfDeleting) {
78
+ domProps.className = Utils.joinWithSpaces(domProps.className, "neo-item-deleting");
79
+ Tooltip.setDomProperties(domProps, (_b = this.props.isDeletingTooltip) !== null && _b !== void 0 ? _b : Misc.Settings.grid.isDeletingTooltip);
80
+ }
72
81
  }
73
82
  else {
74
83
  if (onHeaderClick) {
@@ -91,6 +100,10 @@ var Row = /** @class */ (function (_super) {
91
100
  Row.defaultProps = {
92
101
  showInHeader: true
93
102
  };
103
+ Row = __decorate([
104
+ observer,
105
+ __metadata("design:paramtypes", [Object])
106
+ ], Row);
94
107
  return Row;
95
108
  }(ComponentBase));
96
109
  export default Row;
@@ -12,6 +12,8 @@ interface ILayoutGridProps extends Components.IColumnCountBreakpoints {
12
12
  /** Sets dynamic horizontal spacing. If this is set, normal column spacing is ignored. */
13
13
  justifyContent?: Components.flexJustifyContent;
14
14
  formGroupProps?: Components.IColumnWidthBreakpoints;
15
+ /** Arranges child elements vertically rather than horizontally. */
16
+ arrangeVertically?: boolean;
15
17
  }
16
18
  export declare class FormGroupLayoutContextParams {
17
19
  formGroupProps?: Components.IColumnWidthBreakpoints;
@@ -15,7 +15,8 @@ var GridLayout = /** @class */ (function (_super) {
15
15
  return _super !== null && _super.apply(this, arguments) || this;
16
16
  }
17
17
  GridLayout.prototype.render = function () {
18
- var _a = this.props, gutterSize = _a.gutterSize, verticalSpace = _a.verticalSpace, withGaps = _a.withGaps, alignItems = _a.alignItems, justifyContent = _a.justifyContent, formGroupProps = _a.formGroupProps, xs = _a.xs, sm = _a.sm, md = _a.md, lg = _a.lg, xl = _a.xl, xxl = _a.xxl, domProps = __rest(_a, ["gutterSize", "verticalSpace", "withGaps", "alignItems", "justifyContent", "formGroupProps", "xs", "sm", "md", "lg", "xl", "xxl"]);
18
+ var _a;
19
+ var _b = this.props, gutterSize = _b.gutterSize, verticalSpace = _b.verticalSpace, withGaps = _b.withGaps, alignItems = _b.alignItems, justifyContent = _b.justifyContent, formGroupProps = _b.formGroupProps, arrangeVertically = _b.arrangeVertically, xs = _b.xs, sm = _b.sm, md = _b.md, lg = _b.lg, xl = _b.xl, xxl = _b.xxl, domProps = __rest(_b, ["gutterSize", "verticalSpace", "withGaps", "alignItems", "justifyContent", "formGroupProps", "arrangeVertically", "xs", "sm", "md", "lg", "xl", "xxl"]);
19
20
  var isBsPre5 = BootstrapUtils.isPre5;
20
21
  var hasColumns = (xs || sm || md || lg || xl || xxl);
21
22
  var columnClassName = undefined;
@@ -44,9 +45,20 @@ var GridLayout = /** @class */ (function (_super) {
44
45
  containerStyle.marginTop = "-" + verticalSpace;
45
46
  columnStyle = { marginTop: verticalSpace, marginLeft: "auto" };
46
47
  }
47
- var childCount = React.Children.count(this.props.children);
48
+ var childItems = React.Children.toArray(this.props.children).filter(function (c) { return !!c; });
49
+ var childCount = childItems.length;
50
+ if (arrangeVertically) {
51
+ var maxColumns = (_a = BootstrapUtils.currentDimension(this.props)) !== null && _a !== void 0 ? _a : 2;
52
+ var itemsPerRow = Math.ceil(childCount / maxColumns);
53
+ var rows = [];
54
+ for (var i = 0; i < maxColumns; i++) {
55
+ BootstrapUtils;
56
+ rows.push(React.createElement("div", { key: i }, childItems.splice(0, itemsPerRow)));
57
+ }
58
+ childItems = rows;
59
+ }
48
60
  return (React.createElement(FormGroupLayoutContext.Provider, { value: { formGroupProps: formGroupProps } },
49
- React.createElement("div", __assign({}, domProps, { className: rowClassName, style: containerStyle }), React.Children.map(this.props.children, function (child, index) { return (child && React.createElement("div", { className: columnClassName, style: __assign(__assign({}, columnStyle), { marginLeft: childCount === 2 && index === 1 && isJustifyBetween ? "auto" : undefined }) }, child)); }))));
61
+ React.createElement("div", __assign({}, domProps, { className: rowClassName, style: containerStyle }), childItems.map(function (child, index) { return (child && React.createElement("div", { key: index, className: columnClassName, style: __assign(__assign({}, columnStyle), { marginLeft: childCount === 2 && index === 1 && isJustifyBetween ? "auto" : undefined }) }, child)); }))));
50
62
  };
51
63
  return GridLayout;
52
64
  }(React.Component));
@@ -85,6 +85,7 @@ var neoFontAwesomeMappings = [
85
85
  ["angle-down", "keyboard_arrow_down"],
86
86
  ["caret-left", "arrow_left"],
87
87
  ["trash", "delete"],
88
+ ["trash-restore", "delete_forever"],
88
89
  ["user", "person"],
89
90
  ["users", "groups"],
90
91
  ["user-shield", "admin_panel_settings"],
@@ -117,5 +118,6 @@ var neoFontAwesomeMappings = [
117
118
  ["greater-than-equal", ""],
118
119
  ["stamp", "approval"],
119
120
  ["circle-notch", "progress_activity"],
120
- ["cube", "deployed_code"]
121
+ ["cube", "deployed_code"],
122
+ ["paper-plane", "send"]
121
123
  ];
@@ -10,6 +10,7 @@ export interface IProcessedRoute extends Routing.IRoute {
10
10
  path: string;
11
11
  /** Route params in order, excluding query parameters. */
12
12
  params: INamedRouteParameter[];
13
+ order: number;
13
14
  }
14
15
  type ParamsObject = {
15
16
  [index: string]: string | undefined;
@@ -22,9 +22,7 @@ var RouteProvider = /** @class */ (function () {
22
22
  this.staticRoutes = this.allRoutes.filter(function (c) { return c.params.length === 0; });
23
23
  this.parameterisedRoutes = this.allRoutes
24
24
  .filter(function (c) { return c.params.length > 0; })
25
- .map(function (route) { return ({ route: route, order: route.path.split("/").length }); })
26
- .sortBy(function (c) { return c.order; }, "desc")
27
- .map(function (c) { return c.route; });
25
+ .sortBy(function (c) { return c.order; }, "desc");
28
26
  }
29
27
  /** Gets the path the component is bound to. If the component is bound to multiple paths, the first path is returned. */
30
28
  RouteProvider.prototype.getPathForComponent = function (component) {
@@ -108,7 +106,7 @@ var RouteProvider = /** @class */ (function () {
108
106
  * Adds a unique key, and adds route parameters extracted from the component static params object.
109
107
  */
110
108
  RouteProvider.prototype.processRoute = function (into, route) {
111
- var processed = __assign(__assign({}, route), { path: route.path, params: [], key: this.key++ });
109
+ var processed = __assign(__assign({}, route), { path: route.path, params: [], key: this.key++, order: 0 });
112
110
  if (route instanceof MenuRoute) {
113
111
  processed.path = route.basePath;
114
112
  }
@@ -116,6 +114,9 @@ var RouteProvider = /** @class */ (function () {
116
114
  if (processed.path.length > 1 && processed.path.endsWith("/")) {
117
115
  processed.path = processed.path.substring(0, processed.path.length - 1);
118
116
  }
117
+ if (processed.path.length > 1) {
118
+ processed.order = processed.path.split("/").length;
119
+ }
119
120
  var viewBaseComponent = route.component;
120
121
  if (viewBaseComponent.params) {
121
122
  for (var param in viewBaseComponent.params) {
@@ -5,7 +5,7 @@ import { AxiosPromise } from 'axios';
5
5
  import { WaitForTaskRunnerOptions } from '@singularsystems/neo-core/dist/Components/TaskRunner/TaskRunner';
6
6
  import { BindableBase } from '@singularsystems/neo-core/dist/Model/BindableBase';
7
7
  type VMConstructor<T> = (new (taskRunner?: ITaskRunner) => T);
8
- export type valueOrLazy<T> = T | (() => T) | (new () => T);
8
+ export type valueOrLazy<T> = T | (() => T) | VMConstructor<T>;
9
9
  export default abstract class ViewModelBase extends ModelBase implements IViewModel {
10
10
  taskRunner: ITaskRunner;
11
11
  private disposeHelper;
@@ -1,5 +1,5 @@
1
1
  import { __awaiter, __extends, __generator } from "tslib";
2
- import { ModelBase, Misc } from '@singularsystems/neo-core';
2
+ import { ModelBase, Misc, AppServices } from '@singularsystems/neo-core';
3
3
  import { NeoReactTypes } from '../Modules/Types';
4
4
  var ViewModelBase = /** @class */ (function (_super) {
5
5
  __extends(ViewModelBase, _super);
@@ -106,7 +106,11 @@ var ViewModelBase = /** @class */ (function (_super) {
106
106
  }
107
107
  else if (viewModel.neo) {
108
108
  // Constructor
109
- return new viewModel(taskRunner);
109
+ var instance = new viewModel(taskRunner);
110
+ if (taskRunner && instance.taskRunner !== taskRunner) {
111
+ AppServices.ConfigService.tryShowError("".concat(viewModel.name, " must pass the task runner parameter to the base constructor."));
112
+ }
113
+ return instance;
110
114
  }
111
115
  else {
112
116
  // Callback
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@singularsystems/neo-react",
3
- "version": "1.2.4",
3
+ "version": "1.3.1",
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",
@@ -20,7 +20,7 @@
20
20
  "@testing-library/react": "16.2.0",
21
21
  "@types/jest": "29.5.14",
22
22
  "@types/memoize-one": "5.1.2",
23
- "@types/node": "22.13.4",
23
+ "@types/node": "22.15.30",
24
24
  "@types/react": "19.0.10",
25
25
  "@types/react-custom-scrollbars": "4.0.13",
26
26
  "@types/react-dom": "19.0.4",
@@ -44,7 +44,7 @@
44
44
  "tslib": "2.8.1"
45
45
  },
46
46
  "peerDependencies": {
47
- "@singularsystems/neo-core": ">=1.2.0",
47
+ "@singularsystems/neo-core": ">=1.3.0",
48
48
  "axios": ">=1.6.2",
49
49
  "inversify": ">=5.0.1",
50
50
  "mobx": ">=6.9.0",
package/styles/Card.scss CHANGED
@@ -26,4 +26,15 @@
26
26
  }
27
27
  }
28
28
  }
29
+
30
+ .card-footer {
31
+ border-top: 0;
32
+ background-color: inherit;
33
+ }
34
+
35
+ .card-body {
36
+ &:has(+ .card-footer) {
37
+ border-bottom: 0;
38
+ }
39
+ }
29
40
  }
package/styles/Grid.scss CHANGED
@@ -21,6 +21,18 @@ table.neo-grid {
21
21
  right: 5px;
22
22
  }
23
23
  }
24
+
25
+ tr.neo-item-deleting {
26
+ td {
27
+ text-decoration: line-through;
28
+ opacity: 0.5;
29
+
30
+ .form-control {
31
+ text-decoration: line-through;
32
+ }
33
+ }
34
+ }
35
+
24
36
  .number-col {
25
37
  text-align: right;
26
38
  }