@singularsystems/neo-react 1.0.13 → 1.0.15

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.
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import ComponentBase from './ComponentBase';
3
3
  import { Components } from '@singularsystems/neo-core';
4
- type IButtonProps = Components.IButtonOptions & Omit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & React.RefAttributes<Button>;
4
+ type IButtonProps = Components.IButtonOptions & Omit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref" | "onClick"> & React.RefAttributes<Button>;
5
5
  interface IButtonState {
6
6
  isExpanded: boolean;
7
7
  }
@@ -13,12 +13,14 @@ export default class Button extends ComponentBase<IButtonProps, IButtonState> {
13
13
  menuDom: React.RefObject<HTMLDivElement>;
14
14
  private iconFactory;
15
15
  private windowClickHandler;
16
+ private positionHelper?;
16
17
  constructor(props: IButtonProps);
17
18
  showMenu(): void;
18
19
  hideMenu(): void;
19
20
  private menuItemClicked;
20
21
  private windowClicked;
21
22
  afterRender(): void;
23
+ componentWillUnmount(): void;
22
24
  render(): JSX.Element;
23
25
  }
24
26
  export {};
@@ -25,10 +25,12 @@ var Button = /** @class */ (function (_super) {
25
25
  }
26
26
  };
27
27
  Button.prototype.hideMenu = function () {
28
+ var _a;
28
29
  if (this.state.isExpanded) {
29
30
  this.setState({ isExpanded: false });
30
31
  }
31
32
  window.removeEventListener("click", this.windowClickHandler);
33
+ (_a = this.positionHelper) === null || _a === void 0 ? void 0 : _a.dispose();
32
34
  };
33
35
  Button.prototype.menuItemClicked = function (e, data, userEvent) {
34
36
  this.hideMenu();
@@ -48,9 +50,14 @@ var Button = /** @class */ (function (_super) {
48
50
  };
49
51
  Button.prototype.afterRender = function () {
50
52
  if (this.btnGroupDom.current && this.menuDom.current) {
51
- Components.PositionHelper.checkScreenBounds(this.btnGroupDom.current, this.menuDom.current, undefined, this.props.menuAlignment === "right");
53
+ this.menuDom.current.style.position = "fixed"; // override bootstrap style.
54
+ this.positionHelper = new Components.FixedPositionHelper(this.btnGroupDom.current, this.menuDom.current, this.props.menuAlignment === "right");
52
55
  }
53
56
  };
57
+ Button.prototype.componentWillUnmount = function () {
58
+ var _a;
59
+ (_a = this.positionHelper) === null || _a === void 0 ? void 0 : _a.dispose();
60
+ };
54
61
  Button.prototype.render = function () {
55
62
  var _this = this;
56
63
  var _a;
@@ -3,15 +3,17 @@ import { Components, Model } from '@singularsystems/neo-core';
3
3
  interface IFormProps<T extends Model.IModelBase> extends Omit<React.FormHTMLAttributes<HTMLElement>, "children">, Components.IFormOptions<T> {
4
4
  /**
5
5
  * Called when the form is submitted, but the model is not valid.
6
- * The justValidated parameter will be true if the validationDisplayMode is 'AfterSubmit', and this is the first submit attempt.
6
+ * The firstSubmit parameter will be true if this is the first submit attempt for the model.
7
7
  */
8
- onInvalid?: (e: React.FormEvent, justValidated?: boolean) => void;
8
+ onInvalid?: (e?: React.FormEvent, firstSubmit?: boolean) => void;
9
9
  children: React.ReactNode | ((item: T, meta: Model.TransformMetaType<T>) => React.ReactNode);
10
10
  }
11
11
  export default class Form<T extends Model.IModelBase> extends React.Component<IFormProps<T>> {
12
12
  private reactionDisposer?;
13
- private firstSubmit;
13
+ private getModelOrModels;
14
14
  private onSubmit;
15
+ /** Checks if the model is valid, waiting for any async rules to complete. Returns true if the model is valid. */
16
+ validateAsync(): Promise<boolean>;
15
17
  private disposeReaction;
16
18
  componentWillUnmount(): void;
17
19
  render(): React.JSX.Element;
@@ -1,4 +1,4 @@
1
- import { __assign, __decorate, __extends, __rest } from "tslib";
1
+ import { __assign, __awaiter, __decorate, __extends, __generator, __rest } from "tslib";
2
2
  import * as React from 'react';
3
3
  import { observer, Observer } from 'mobx-react';
4
4
  import { Validation, Model, ModalUtils, Misc } from '@singularsystems/neo-core';
@@ -8,59 +8,100 @@ import { FormContextProvider } from './FormContext';
8
8
  var Form = /** @class */ (function (_super) {
9
9
  __extends(Form, _super);
10
10
  function Form() {
11
- var _this = _super !== null && _super.apply(this, arguments) || this;
12
- _this.firstSubmit = true;
13
- return _this;
11
+ return _super !== null && _super.apply(this, arguments) || this;
14
12
  }
15
- Form.prototype.onSubmit = function (e, model) {
16
- var _this = this;
17
- var _a;
18
- e.preventDefault();
19
- e.stopPropagation();
20
- this.disposeReaction();
21
- var canSubmit = true;
22
- // TODO: if the model and none of its children have blurred, justValidated should be true
23
- var justValidated = this.props.validationDisplayMode === Validation.DisplayMode.AfterSubmit && this.firstSubmit;
24
- this.firstSubmit = false;
13
+ Form.prototype.getModelOrModels = function () {
14
+ var model = this.props.model;
25
15
  if (model) {
26
- if (!model.isValid) {
27
- canSubmit = false;
28
- var container = void 0;
29
- if (model instanceof Array) {
30
- container = model;
31
- }
32
- else {
33
- container = [model];
16
+ // Allow binding to ObservableProperty
17
+ if (model.attachedTo && !(model instanceof Array)) {
18
+ return model.attachedTo;
19
+ }
20
+ else {
21
+ return model;
22
+ }
23
+ }
24
+ return undefined;
25
+ };
26
+ Form.prototype.onSubmit = function (e) {
27
+ return __awaiter(this, void 0, void 0, function () {
28
+ var models, isFirstSubmit, isValid;
29
+ return __generator(this, function (_a) {
30
+ switch (_a.label) {
31
+ case 0:
32
+ e.preventDefault();
33
+ e.stopPropagation();
34
+ models = this.getModelOrModels();
35
+ isFirstSubmit = true;
36
+ if (models) {
37
+ Model.GraphTraverser.traverseItems(models, new Model.GraphTraverser.RecursionState(), function (item, state) {
38
+ if (item.validator.forceShowErrors) {
39
+ isFirstSubmit = false;
40
+ }
41
+ else {
42
+ item.validator.forceShowErrors = true;
43
+ }
44
+ });
45
+ }
46
+ return [4 /*yield*/, this.validateAsync()];
47
+ case 1:
48
+ isValid = _a.sent();
49
+ if (isValid) {
50
+ if (this.props.onSubmit)
51
+ this.props.onSubmit(e);
52
+ }
53
+ else {
54
+ if (this.props.onInvalid)
55
+ this.props.onInvalid(e, isFirstSubmit);
56
+ }
57
+ return [2 /*return*/];
34
58
  }
35
- Model.GraphTraverser.traverseItems(container, new Model.GraphTraverser.RecursionState(), function (item, state) {
36
- item.validator.forceShowErrors = true;
37
- });
38
- if (this.props.onInvalid)
39
- this.props.onInvalid(e, justValidated);
40
- if (this.props.showSummaryModal || this.props.invalidSummaryText || this.props.invalidSummaryTitle) {
41
- var onClose = this.disposeReaction.bind(this);
42
- var closeButton = (_a = Misc.Settings.form.validationModalCloseButton) !== null && _a !== void 0 ? _a : { text: "Ok" };
43
- if (closeButton) {
44
- closeButton.onClick = onClose;
59
+ });
60
+ });
61
+ };
62
+ /** Checks if the model is valid, waiting for any async rules to complete. Returns true if the model is valid. */
63
+ Form.prototype.validateAsync = function () {
64
+ var _this = this;
65
+ return new Promise(function (resolve, reject) {
66
+ var _a;
67
+ _this.disposeReaction();
68
+ var isValid = true;
69
+ var modelOrModels = _this.getModelOrModels();
70
+ var models = modelOrModels === undefined ? [] : (modelOrModels instanceof Array ? modelOrModels : [modelOrModels]);
71
+ if (models) {
72
+ var allValid_1 = function () { return models.every(function (c) { return c.isValid; }); };
73
+ var someBusy = function () { return models.some(function (c) { return c.validator.isBusy; }); };
74
+ if (!allValid_1()) {
75
+ isValid = false;
76
+ var codeModal_1 = null;
77
+ if (_this.props.showSummaryModal || _this.props.invalidSummaryText || _this.props.invalidSummaryTitle) {
78
+ var onClose = _this.disposeReaction.bind(_this);
79
+ var closeButton = (_a = Misc.Settings.form.validationModalCloseButton) !== null && _a !== void 0 ? _a : { text: "Ok" };
80
+ if (closeButton) {
81
+ closeButton.onClick = onClose;
82
+ }
83
+ codeModal_1 = ModalUtils.showModal(_this.props.invalidSummaryTitle || "Invalid data", React.createElement(Observer, null, function () {
84
+ return React.createElement("div", null,
85
+ _this.props.invalidSummaryText || "Cannot continue due to the following issues:",
86
+ React.createElement(ValidationSummary, { graph: Validation.getValidationSummary(modelOrModels), hideTopLevelTitle: !(modelOrModels instanceof Array) }));
87
+ }), { onClose: onClose, closeButton: closeButton });
88
+ }
89
+ if (someBusy()) {
90
+ // Async rule is busy, listen for when isBusy changes to false.
91
+ _this.reactionDisposer = reaction(someBusy, function () {
92
+ _this.disposeReaction();
93
+ isValid = allValid_1();
94
+ if (codeModal_1 !== null && isValid) {
95
+ codeModal_1.close();
96
+ }
97
+ resolve(isValid);
98
+ });
99
+ return;
45
100
  }
46
- var codeModal_1 = ModalUtils.showModal(this.props.invalidSummaryTitle || "Invalid data", React.createElement(Observer, null, function () {
47
- return React.createElement("div", null,
48
- _this.props.invalidSummaryText || "Cannot continue due to the following issues:",
49
- React.createElement(ValidationSummary, { graph: Validation.getValidationSummary(model), hideTopLevelTitle: !(model instanceof Array) }));
50
- }), { onClose: onClose, closeButton: closeButton });
51
- // Listen for when isValid changes to true, in case there is an async rule running.
52
- this.reactionDisposer = reaction(function () { return model.isValid; }, function () {
53
- _this.disposeReaction();
54
- codeModal_1.close();
55
- if (model.isValid && _this.props.onSubmit)
56
- _this.props.onSubmit(e);
57
- });
58
101
  }
59
102
  }
60
- }
61
- if (canSubmit && this.props.onSubmit) {
62
- this.props.onSubmit(e);
63
- }
103
+ resolve(isValid);
104
+ });
64
105
  };
65
106
  Form.prototype.disposeReaction = function () {
66
107
  if (this.reactionDisposer) {
@@ -74,17 +115,7 @@ var Form = /** @class */ (function (_super) {
74
115
  Form.prototype.render = function () {
75
116
  var _this = this;
76
117
  var _a = this.props, validationDisplayMode = _a.validationDisplayMode, allReadOnly = _a.allReadOnly, allDisabled = _a.allDisabled, model = _a.model, showSummaryModal = _a.showSummaryModal, invalidSummaryTitle = _a.invalidSummaryTitle, invalidSummaryText = _a.invalidSummaryText, native = __rest(_a, ["validationDisplayMode", "allReadOnly", "allDisabled", "model", "showSummaryModal", "invalidSummaryTitle", "invalidSummaryText"]);
77
- var formModel;
78
- if (model) {
79
- // Allow binding to ObservableProperty
80
- if (model.attachedTo && !(model instanceof Array)) {
81
- formModel = model.attachedTo;
82
- }
83
- else {
84
- formModel = model;
85
- }
86
- }
87
- return (React.createElement("form", __assign({}, native, { onSubmit: function (e) { return _this.onSubmit(e, formModel); } }),
118
+ return (React.createElement("form", __assign({}, native, { onSubmit: function (e) { return _this.onSubmit(e); } }),
88
119
  React.createElement(FormContextProvider, { validationDisplayMode: validationDisplayMode !== null && validationDisplayMode !== void 0 ? validationDisplayMode : Misc.Settings.form.validationDisplayMode, allReadOnly: allReadOnly, allDisabled: allDisabled }, (this.props.children instanceof Function) ? this.props.children(model, model.meta) : this.props.children)));
89
120
  };
90
121
  Form = __decorate([
@@ -11,7 +11,10 @@ export declare class StickyTableHeader {
11
11
  * @param fixedElement A fixed element at the top of the screen to use as an anchor, rather than the top of the screen itself.
12
12
  */
13
13
  constructor(table: HTMLTableElement, fixedElement?: HTMLElement | undefined);
14
- private scrollParent?;
14
+ /** Parent element that may scroll, requiring a position update. */
15
+ private scrollElement?;
16
+ /** Fixed height parent element that the grid scrolls within. */
17
+ private relativeParent?;
15
18
  private header;
16
19
  private headerCopy?;
17
20
  private headerHeight;
@@ -16,12 +16,17 @@ var StickyTableHeader = /** @class */ (function () {
16
16
  throw Error("Table must have a header section.");
17
17
  }
18
18
  this.header = table.tHead;
19
- this.scrollParent = StickyTableHeader.getParentScrollElement(table);
19
+ this.scrollElement = StickyTableHeader.getParentScrollElement(table);
20
+ if (this.scrollElement) {
21
+ if (this.scrollElement.style.height || this.scrollElement.style.maxHeight) {
22
+ this.relativeParent = this.scrollElement;
23
+ }
24
+ }
20
25
  this.handleScroll = this.handleScroll.bind(this);
21
26
  this.onPossibleContentChange = this.onPossibleContentChange.bind(this);
22
27
  document.addEventListener("scroll", this.handleScroll);
23
- if (this.scrollParent) {
24
- this.scrollParent.addEventListener("scroll", this.handleScroll);
28
+ if (this.scrollElement) {
29
+ this.scrollElement.addEventListener("scroll", this.handleScroll);
25
30
  }
26
31
  table.addEventListener("keydown", this.onPossibleContentChange);
27
32
  table.addEventListener("mouseup", this.onPossibleContentChange);
@@ -31,10 +36,10 @@ var StickyTableHeader = /** @class */ (function () {
31
36
  // otherwise if there is a fixed (non scrolling) element at the top of the page, then the headers must appear below that.
32
37
  // otherwise the headers will stick to the top of the screen.
33
38
  var fixedElement = null;
34
- if (!this.scrollParent && this.fixedElement && getComputedStyle(this.fixedElement).position === "fixed") {
39
+ if (!this.relativeParent && this.fixedElement && getComputedStyle(this.fixedElement).position === "fixed") {
35
40
  fixedElement = this.fixedElement;
36
41
  }
37
- var topBoundary = this.scrollParent ? this.scrollParent.getBoundingClientRect().top : (fixedElement ? fixedElement.getBoundingClientRect().bottom : 0);
42
+ var topBoundary = this.relativeParent ? this.relativeParent.getBoundingClientRect().top : (fixedElement ? fixedElement.getBoundingClientRect().bottom : 0);
38
43
  var tableBounds = this.table.getBoundingClientRect();
39
44
  var tbodyBounds = this.table.tBodies[this.table.tBodies.length - 1].getBoundingClientRect();
40
45
  if (tableBounds.top < topBoundary && tbodyBounds.bottom - this.headerHeight > topBoundary) {
@@ -50,6 +55,11 @@ var StickyTableHeader = /** @class */ (function () {
50
55
  }
51
56
  this.header.style.left = tableBounds.left + "px";
52
57
  this.header.style.top = topBoundary + "px";
58
+ if (this.scrollElement) {
59
+ var left = this.scrollElement.scrollLeft;
60
+ var width = this.scrollElement.clientWidth + left;
61
+ this.header.style.clipPath = "rect(0px ".concat(width, "px 100% ").concat(left, "px)");
62
+ }
53
63
  }
54
64
  else {
55
65
  this.setColumnWidths(true);
@@ -59,6 +69,7 @@ var StickyTableHeader = /** @class */ (function () {
59
69
  StickyTableHeader.prototype.removeCopy = function () {
60
70
  if (this.headerCopy) {
61
71
  this.header.classList.remove("neo-grid-fixed-header");
72
+ this.header.style.clipPath = "";
62
73
  this.table.removeChild(this.headerCopy);
63
74
  this.headerCopy = undefined;
64
75
  }
@@ -81,8 +92,8 @@ var StickyTableHeader = /** @class */ (function () {
81
92
  };
82
93
  StickyTableHeader.prototype.dispose = function () {
83
94
  document.removeEventListener("scroll", this.handleScroll);
84
- if (this.scrollParent) {
85
- this.scrollParent.removeEventListener("scroll", this.handleScroll);
95
+ if (this.scrollElement) {
96
+ this.scrollElement.removeEventListener("scroll", this.handleScroll);
86
97
  }
87
98
  this.table.removeEventListener("keydown", this.onPossibleContentChange);
88
99
  this.table.removeEventListener("mouseup", this.onPossibleContentChange);
@@ -92,10 +103,11 @@ var StickyTableHeader = /** @class */ (function () {
92
103
  * Returns the first parent element that has a scrollbar.
93
104
  */
94
105
  StickyTableHeader.getParentScrollElement = function (element) {
95
- var current = element;
96
- while (current) {
97
- if ((current.style.height || current.style.maxHeight) &&
98
- (current.style.overflow === "scroll" || current.style.overflow === "auto" || current.style.overflowY === "scroll" || current.style.overflowY === "auto")) {
106
+ var current = element.parentElement;
107
+ var depth = 0;
108
+ while (current && depth < 5) {
109
+ var computedStyle = window.getComputedStyle(current);
110
+ if (computedStyle.overflowX === "auto" || computedStyle.overflowX === "scroll" || computedStyle.overflowY === "auto" || computedStyle.overflowY === "scroll") {
99
111
  return current;
100
112
  }
101
113
  current = current.parentElement;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@singularsystems/neo-react",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
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",
@@ -31,7 +31,7 @@
31
31
  "typescript": "5.4.2"
32
32
  },
33
33
  "dependencies": {
34
- "@singularsystems/neo-core": "1.0.10",
34
+ "@singularsystems/neo-core": "1.0.12",
35
35
  "@types/rc-slider": "^8.6.5",
36
36
  "@types/react-color": "^3.0.1",
37
37
  "axios": "1.6.7",
@@ -49,7 +49,7 @@
49
49
  "tslib": "2.5.0"
50
50
  },
51
51
  "peerDependencies": {
52
- "@singularsystems/neo-core": ">=1.0.10",
52
+ "@singularsystems/neo-core": ">=1.0.12",
53
53
  "axios": ">=1.6.2",
54
54
  "inversify": ">=5.0.1",
55
55
  "mobx": ">=6.9.0",