@deviceinsight/ng-ui-components 7.22.0 → 7.23.0

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.
Files changed (83) hide show
  1. package/README.md +104 -9
  2. package/dist/index.cjs +122 -139
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.js +122 -139
  5. package/dist/index.js.map +1 -1
  6. package/dist/types/__mocks__/i18n.d.ts.map +1 -1
  7. package/dist/types/components/chart/Chart.d.ts +11 -3
  8. package/dist/types/components/chart/Chart.d.ts.map +1 -1
  9. package/dist/types/components/chart/Chart.stories.d.ts +7 -7
  10. package/dist/types/components/chart/Chart.stories.d.ts.map +1 -1
  11. package/dist/types/components/colorpicker/ColorTextInput.d.ts.map +1 -1
  12. package/dist/types/components/colorpicker/GradientColorPicker.stories.d.ts.map +1 -1
  13. package/dist/types/components/colorpicker/GridColorPicker.stories.d.ts.map +1 -1
  14. package/dist/types/components/contentHeader/ContentHeader.d.ts.map +1 -1
  15. package/dist/types/components/contentHeader/ContentHeader.stories.d.ts.map +1 -1
  16. package/dist/types/components/infobox/InfoBox.d.ts.map +1 -1
  17. package/dist/types/components/infobox/InfoBox.stories.d.ts.map +1 -1
  18. package/dist/types/components/input/DateInput.d.ts.map +1 -1
  19. package/dist/types/components/input/DateInput.stories.d.ts +2 -1
  20. package/dist/types/components/input/DateInput.stories.d.ts.map +1 -1
  21. package/dist/types/components/input/DateTimeInput.d.ts.map +1 -1
  22. package/dist/types/components/input/DateTimeInput.stories.d.ts +12 -11
  23. package/dist/types/components/input/DateTimeInput.stories.d.ts.map +1 -1
  24. package/dist/types/components/input/SelectWithSearch.d.ts.map +1 -1
  25. package/dist/types/components/input/SelectWithSearch.stories.d.ts.map +1 -1
  26. package/dist/types/components/input/SelectWithSearchAsync.d.ts +5 -1
  27. package/dist/types/components/input/SelectWithSearchAsync.d.ts.map +1 -1
  28. package/dist/types/components/input/SelectWithSearchAsync.stories.d.ts.map +1 -1
  29. package/dist/types/components/input/UrnInput.d.ts.map +1 -1
  30. package/dist/types/components/input/UrnInput.stories.d.ts.map +1 -1
  31. package/dist/types/components/input/ValidatedTextInput.d.ts.map +1 -1
  32. package/dist/types/components/input/ValidatedTextInput.stories.d.ts.map +1 -1
  33. package/dist/types/components/link/RouteLink.stories.d.ts.map +1 -1
  34. package/dist/types/components/multiMenu/MultiMenu.stories.d.ts.map +1 -1
  35. package/dist/types/components/multiMenu/components/MultiMenuContext.d.ts.map +1 -1
  36. package/dist/types/components/multiMenu/components/MultiMenuItem.d.ts.map +1 -1
  37. package/dist/types/components/pagination/Pagination.d.ts.map +1 -1
  38. package/dist/types/components/pagination/Pagination.stories.d.ts.map +1 -1
  39. package/dist/types/components/periodpicker/PeriodPicker.stories.d.ts.map +1 -1
  40. package/dist/types/components/periodpicker/components/MonthPicker.d.ts.map +1 -1
  41. package/dist/types/components/periodpicker/periods.d.ts.map +1 -1
  42. package/dist/types/components/prompt/Prompt.stories.d.ts.map +1 -1
  43. package/dist/types/components/reactable/Reactable.d.ts.map +1 -1
  44. package/dist/types/components/reactable/Reactable.stories.d.ts.map +1 -1
  45. package/dist/types/components/reactable/helpers/ReactableTableFilterRow.d.ts.map +1 -1
  46. package/dist/types/components/reactable/helpers/ReactableTableHeader.d.ts +1 -1
  47. package/dist/types/components/reactable/helpers/ReactableTableHeader.d.ts.map +1 -1
  48. package/dist/types/components/reactable/helpers/ReactableTableRow.d.ts.map +1 -1
  49. package/dist/types/components/reactable/helpers/ReactableTextFilter.d.ts.map +1 -1
  50. package/dist/types/components/reactableWithClientSidePagination/ReactableWithClientSidePagination.d.ts +7 -1
  51. package/dist/types/components/reactableWithClientSidePagination/ReactableWithClientSidePagination.d.ts.map +1 -1
  52. package/dist/types/components/reactableWithClientSidePagination/ReactableWithClientSidePagination.stories.d.ts.map +1 -1
  53. package/dist/types/components/slider/RcSliderWithTooltip.d.ts.map +1 -1
  54. package/dist/types/components/slider/Slider.d.ts.map +1 -1
  55. package/dist/types/components/slider/Slider.stories.d.ts.map +1 -1
  56. package/dist/types/components/svg/SvgImage.d.ts.map +1 -1
  57. package/dist/types/components/svg/SvgImage.stories.d.ts.map +1 -1
  58. package/dist/types/components/svg/utils/getMutableNodeList.d.ts.map +1 -1
  59. package/dist/types/components/svg/utils/getSvgElement.d.ts +1 -1
  60. package/dist/types/components/svg/utils/getSvgElement.d.ts.map +1 -1
  61. package/dist/types/components/svg/utils/transformPositionModifications.d.ts +1 -1
  62. package/dist/types/components/svg/utils/transformPositionModifications.d.ts.map +1 -1
  63. package/dist/types/components/svg/utils/validateSvg.d.ts +1 -1
  64. package/dist/types/components/svg/utils/validateSvg.d.ts.map +1 -1
  65. package/dist/types/components/tabs/RouteTabs.stories.d.ts.map +1 -1
  66. package/dist/types/components/treeview/TreeView.d.ts +9 -4
  67. package/dist/types/components/treeview/TreeView.d.ts.map +1 -1
  68. package/dist/types/components/treeview/TreeView.stories.d.ts.map +1 -1
  69. package/dist/types/components/treeview/components/Node.d.ts +2 -5
  70. package/dist/types/components/treeview/components/Node.d.ts.map +1 -1
  71. package/dist/types/components/treeview/components/NodeInfo.d.ts.map +1 -1
  72. package/dist/types/components/upload/FileUpload.d.ts.map +1 -1
  73. package/dist/types/components/upload/FileUpload.stories.d.ts.map +1 -1
  74. package/dist/types/components/upload/ImageUpload.d.ts +2 -2
  75. package/dist/types/components/upload/ImageUpload.d.ts.map +1 -1
  76. package/dist/types/components/upload/ImageUpload.stories.d.ts +11 -11
  77. package/dist/types/components/upload/ImageUpload.stories.d.ts.map +1 -1
  78. package/dist/types/index.d.ts +1 -1
  79. package/dist/types/index.d.ts.map +1 -1
  80. package/dist/types/modules/messageBox/MessageBox.stories.d.ts.map +1 -1
  81. package/dist/types/modules/messageBox/messageBox.d.ts.map +1 -1
  82. package/dist/types/modules/utils/uuidGen.d.ts.map +1 -1
  83. package/package.json +12 -16
package/README.md CHANGED
@@ -1,18 +1,109 @@
1
+ # General
2
+
3
+ This project contains the higher-level React components shared by
4
+ [NG-UI](https://gitlab.device-insight.com/ng-ui/ng-ui) and custom projects.
5
+ It is published as `@deviceinsight/ng-ui-components` and builds on
6
+ `@deviceinsight/ng-ui-basic-components` and `@deviceinsight/ng-ui-api-client`.
7
+
8
+ The package is ESM-first (`dist/index.js`) with a CommonJS build (`dist/index.cjs`)
9
+ and type declarations (`dist/types/index.d.ts`). The bundled stylesheet is exported
10
+ separately and has to be imported by the consuming project:
11
+
12
+ ```ts
13
+ import '@deviceinsight/ng-ui-components/style.css';
14
+ ```
15
+
16
+ `react`, `react-dom`, `i18next`, `react-i18next` and `react-router-dom` are peer
17
+ dependencies and have to be provided by the consuming project.
18
+
19
+ ## Contents
20
+
21
+ `src/index.ts` is the public API. It currently exports:
22
+
23
+ - **Inputs** — `DateInput`, `DateTimeInput`, `UrnInput`, `ValidatedTextInput`,
24
+ `SelectWithSearch`, `SelectWithSearchAsync`, `PeriodPicker`, `Slider`
25
+ - **Data display** — `Reactable`, `ReactableWithClientSidePagination`, `Pagination`,
26
+ `TreeView` (plus `getTreeNodePathById`), `Chart`, `InfoBox`, `ContentHeader`
27
+ - **Color pickers** — `ColorPicker` / `GridColorPicker`, `GradientColorPicker`
28
+ - **Navigation** — `RouteLink`, `RouteTabs`, `MultiMenu`, `MultiMenuItem`
29
+ - **Upload** — `FileUpload`, `ImageUpload`
30
+ - **SVG** — `SvgImage` and the helpers `getSvgElement`, `getMutableNodeList`,
31
+ `validateSvg`
32
+ - **Prompt** — `withPrompt`
33
+ - **Message box** — `messageBoxSuccess`, `messageBoxInfo`, `messageBoxWarn`,
34
+ `messageBoxError`, `messageBoxDismissSingle`, `messageBoxDismissGroup`,
35
+ `messageBoxDismissAll`, `setGlobalMessageBoxOptions`,
36
+ `setGlobalErrorMessageBoxOptions`, `MessageBoxContainer`
37
+ - **Utilities** — `uuidV4`, `nanoid10`, `initializeTranslation`
38
+ - the accompanying types (`ReactableProps`, `ReactableColumnDef`, `Period`,
39
+ `SvgImageProps`, `PaginationSettings`, …)
40
+
41
+ Translations live in `src/i18n` and are set up by calling `initializeTranslation`.
42
+ `src/modules` holds non-component code (message box, utils), `src/styles` the
43
+ stylesheet, `src/utils` shared helpers.
44
+
45
+ # Development
46
+
47
+ Node is pinned via `.nvmrc` (see also the `engines` field in `package.json`).
48
+
49
+ ```shell
50
+ npm install
51
+ npm run storybook # dev server on http://localhost:6006
52
+ ```
53
+
54
+ Available scripts:
55
+
56
+ | Script | Description |
57
+ | ------------------------- | -------------------------------------------------------------------------- |
58
+ | `npm run build` | Cleans `dist`, bundles with Vite and emits types via `tsconfig.types.json` |
59
+ | `npm test` | Runs the Vitest suite (watch mode by default, `--run` for a single pass) |
60
+ | `npm run watch` | Rebuilds and runs `yalc push` on every successful compile |
61
+ | `npm run clean` | Removes `dist` |
62
+ | `npm run storybook` | Starts Storybook on port 6006 |
63
+ | `npm run build-storybook` | Builds the static Storybook |
64
+ | `npm run typecheck` | `tsc --noEmit` |
65
+ | `npm run lint` | Lints with oxlint and checks formatting with Prettier |
66
+ | `npm run prettier` | Formats the sources and `.mdx` files |
67
+ | `npm run scan` | Vulnerability scan via `audit-ci` using `scan-config.json5` |
68
+
69
+ Tests use Vitest with `happy-dom` and Testing Library; the setup lives in
70
+ `vitest.setup.tsx` and the config in `vitest.config.mts`. Components are additionally
71
+ exercised via Storybook stories (`*.stories.tsx`).
72
+
73
+ Commits are checked by a husky `pre-commit` hook that runs `lint-staged`
74
+ (prettier on the staged files).
75
+
76
+ `CHANGELOG.md` is maintained per version. Renovate dependency updates append their
77
+ entry automatically via the `postUpgradeTasks` hook in `renovate.json`, which calls
78
+ `./update-changelog.sh <depName> <currentVersion> <newVersion>`.
79
+
1
80
  # Linking for local development
2
81
 
3
- We recommend using `yalc` tool for linking `ng-ui-components`. It's because `yalc` generates exactly the same package as
82
+ We recommend using the `yalc` tool for linking `@deviceinsight/ng-ui-components`. It's because `yalc` generates exactly the same package as
4
83
  it would be published to the npm registry, especially it removes files that should be ignored and their existence cause
5
- problems when we use `yarn link`.
84
+ problems when we use `npm link`.
6
85
 
7
86
  Here's how to do the linking:
8
87
 
9
88
  ```shell
10
- yarn link-local
89
+ npm run build
90
+ npx yalc publish
11
91
 
12
92
  # in the project where you want to link this library to:
13
- yalc link "@deviceinsight/ng-ui-components"
93
+ npx yalc link "@deviceinsight/ng-ui-components"
14
94
  ```
15
95
 
96
+ # CI
97
+
98
+ `.gitlab-ci.yml` installs the dependencies and then runs `test`, `scan`, `lint` and
99
+ `typecheck` on every branch except `main`. On `develop` the `pages` job publishes the
100
+ static Storybook to GitLab Pages. The `publish` job runs for `develop` and for tags:
101
+ `-SNAPSHOT` versions are published to the internal Nexus registry under the `next`
102
+ tag, releases go to the public npm registry. A release older than the currently
103
+ published `latest` is published under a `hotfix-<major>.<minor>` tag so that `latest`
104
+ is not moved backwards. `publish_alpha` is a manual job that publishes an alpha
105
+ prerelease of the `VERSION` variable to the internal registry under `next`.
106
+
16
107
  # Releasing the library using git flow
17
108
 
18
109
  ## Prerequisites
@@ -24,7 +115,8 @@ The git flow tools need to be installed. This depends on your operating system.
24
115
  They can be also installed from the git repo: https://github.com/petervanderdoes/gitflow-avh
25
116
 
26
117
  After git flow is installed, the repo needs to be initialized. Run `git flow init` and answer every
27
- question with the default (press Enter/Return).
118
+ question with the default (press Enter/Return). The script verifies that the branches
119
+ are named `main` and `develop`.
28
120
 
29
121
  ## Building a release
30
122
 
@@ -34,9 +126,8 @@ To build a release of this library, execute `./release.sh`, which consists of th
34
126
 
35
127
  Vulnerabilities can occur at any time.
36
128
  To be sure, the pipeline for the branches and the tags created
37
- in the release process will not fail, it is wise to run the checks locally before building the
38
- release.
39
- To do this, run:
129
+ in the release process will not fail, the script runs the checks locally before building the
130
+ release:
40
131
 
41
132
  ```
42
133
  npm install
@@ -44,12 +135,16 @@ npm run scan
44
135
  npm run test
45
136
  ```
46
137
 
138
+ If `package-lock.json` changed during the install, the script commits it.
139
+
47
140
  ### Building the release
48
141
 
49
142
  1. Switch to develop branch and make sure both main and develop are current:
50
143
 
51
144
  `git checkout develop && git pull && git fetch origin main:main`
52
145
 
146
+ The script also aborts if the workarea is dirty or if `develop` has unpushed commits.
147
+
53
148
  2. Get the upcoming release from `package.json` and start the release:
54
149
 
55
150
  `git flow release start $RELEASE_VERSION`, e.g. `git flow release start 1.2.0`.
@@ -86,7 +181,7 @@ so that the version is e.g. `1.2.0-SNAPSHOT`.
86
181
 
87
182
  5.2. Run `npm install`.
88
183
 
89
- 5.2. Commit `package.json` and `package-lock.json`. Commit message should be `Starting $NEW_SNAPSHOT_VERSION`, e.g. `Starting 1.2.0`.
184
+ 5.3. Commit `package.json` and `package-lock.json`. Commit message should be `Starting $NEW_SNAPSHOT_VERSION`, e.g. `Starting 1.2.0`.
90
185
 
91
186
  6. Push the branches and the tag:
92
187
 
package/dist/index.cjs CHANGED
@@ -411,9 +411,11 @@ var ValidatedTextInput = class extends react.Component {
411
411
  //#region src/components/colorpicker/ColorTextInput.tsx
412
412
  function ColorTextInput({ value, pattern, onChange, placeholder, style }) {
413
413
  const [text, setText] = (0, react.useState)(value);
414
- (0, react.useEffect)(() => {
414
+ const [lastValue, setLastValue] = (0, react.useState)(value);
415
+ if (value !== lastValue) {
416
+ setLastValue(value);
415
417
  setText(value);
416
- }, [value]);
418
+ }
417
419
  function handleChange(value, valid) {
418
420
  setText(value);
419
421
  if (valid) onChange(value);
@@ -544,9 +546,7 @@ function inputToTzDate(value, pattern, timezone) {
544
546
  var DateInput = (0, react.forwardRef)(({ value, clearable, inline, readOnly, wrapperClassName, onChange, locale, timezone, ...otherProps }, ref) => {
545
547
  const [pattern, setPattern] = (0, react.useState)(dayjs.default.localeData().longDateFormat("L"));
546
548
  const { t } = useTranslation();
547
- const [valid, setValid] = (0, react.useState)(false);
548
549
  const [popupOpen, setPopupOpen] = (0, react.useState)(false);
549
- const [inputText, setInputText] = (0, react.useState)("");
550
550
  const debouncedSetPopupActive = (0, react.useMemo)(() => (0, lodash.debounce)(setPopupOpen, 30), []);
551
551
  (0, react.useEffect)(() => {
552
552
  const language = locale ?? i18next.default.language;
@@ -564,17 +564,22 @@ var DateInput = (0, react.forwardRef)(({ value, clearable, inline, readOnly, wra
564
564
  t,
565
565
  timezone
566
566
  ]);
567
- (0, react.useEffect)(() => {
568
- if (value == null) setInputText("");
569
- else setInputText(formattedDate(value));
570
- }, [formattedDate, value]);
571
- (0, react.useEffect)(() => {
567
+ const [valid, setValid] = (0, react.useState)(() => isInputValid(value, pattern));
568
+ const [inputText, setInputText] = (0, react.useState)(() => value == null ? "" : formattedDate(value));
569
+ const [lastInput, setLastInput] = (0, react.useState)({
570
+ value,
571
+ pattern,
572
+ timezone
573
+ });
574
+ if (!Object.is(value, lastInput.value) || pattern !== lastInput.pattern || timezone !== lastInput.timezone) {
575
+ setLastInput({
576
+ value,
577
+ pattern,
578
+ timezone
579
+ });
580
+ setInputText(value == null ? "" : formattedDate(value));
572
581
  setValid(isInputValid(value, pattern));
573
- }, [pattern, value]);
574
- (0, react.useEffect)(() => {
575
- dayjs.default.locale(locale ?? i18next.default.language);
576
- setPattern(dayjs.default.localeData().longDateFormat("L"));
577
- }, [locale]);
582
+ }
578
583
  const handleInputTextChange = (text) => {
579
584
  setInputText(text);
580
585
  if (text) {
@@ -1114,7 +1119,6 @@ var Node = class Node extends react.Component {
1114
1119
  super(..._args);
1115
1120
  this.state = {
1116
1121
  expanded: false,
1117
- selectionState: this.getSelectionState(),
1118
1122
  isLoading: false
1119
1123
  };
1120
1124
  this.toggleExpand = () => {
@@ -1140,28 +1144,23 @@ var Node = class Node extends react.Component {
1140
1144
  * @param {Props} prevProps
1141
1145
  * @param {State} prevState
1142
1146
  */
1143
- shouldComponentUpdate(prevProps, prevState) {
1144
- const { selectedNodes: prevSelectedNodes, treeSize: prevTreeSize } = prevProps;
1145
- const { expanded: prevExpanded, selectionState: prevSelectionState, isLoading: prevIsLoading } = prevState;
1147
+ shouldComponentUpdate(nextProps, nextState) {
1148
+ const { selectedNodes: nextSelectedNodes, treeSize: nextTreeSize } = nextProps;
1149
+ const { expanded: nextExpanded, isLoading: nextIsLoading } = nextState;
1146
1150
  const { selectedNodes, node, treeSize } = this.props;
1147
- const { expanded, selectionState, isLoading } = this.state;
1148
- if (prevIsLoading !== isLoading) return true;
1149
- if (prevTreeSize !== treeSize) return true;
1150
- if (this.isLeaf()) return prevSelectedNodes.includes(node.id) && !selectedNodes.includes(node.id) || !prevSelectedNodes.includes(node.id) && selectedNodes.includes(node.id) || prevSelectionState !== selectionState;
1151
- else return prevSelectedNodes.length !== selectedNodes.length || prevExpanded !== expanded || prevSelectionState !== selectionState;
1152
- }
1153
- componentDidMount() {
1154
- this.setState({ selectionState: this.getSelectionState() });
1155
- }
1156
- componentDidUpdate() {
1157
- this.setState({ selectionState: this.getSelectionState() });
1151
+ const { expanded, isLoading } = this.state;
1152
+ const selectionStateChanged = this.getSelectionState(this.props) !== this.getSelectionState(nextProps);
1153
+ if (isLoading !== nextIsLoading) return true;
1154
+ if (treeSize !== nextTreeSize) return true;
1155
+ if (this.isLeaf()) return selectedNodes.includes(node.id) && !nextSelectedNodes.includes(node.id) || !selectedNodes.includes(node.id) && nextSelectedNodes.includes(node.id) || selectionStateChanged;
1156
+ else return selectedNodes.length !== nextSelectedNodes.length || expanded !== nextExpanded || selectionStateChanged;
1158
1157
  }
1159
1158
  isLeaf() {
1160
1159
  const { node } = this.props;
1161
1160
  return node.children === void 0;
1162
1161
  }
1163
- getSelectionState() {
1164
- const { node, selectedNodes } = this.props;
1162
+ getSelectionState(props = this.props) {
1163
+ const { node, selectedNodes } = props;
1165
1164
  if (this.isLeaf()) return selectedNodes.includes(node.id) ? SelectionState.ALL : SelectionState.NONE;
1166
1165
  else {
1167
1166
  const leafIds = getAllLeafIds(node);
@@ -1173,7 +1172,8 @@ var Node = class Node extends react.Component {
1173
1172
  }
1174
1173
  render() {
1175
1174
  const { node, isLastChild, ...commonNodeProps } = this.props;
1176
- const { expanded, selectionState, isLoading } = this.state;
1175
+ const { expanded, isLoading } = this.state;
1176
+ const selectionState = this.getSelectionState();
1177
1177
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1178
1178
  className: "node",
1179
1179
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeInfo, {
@@ -1204,19 +1204,15 @@ var Node = class Node extends react.Component {
1204
1204
  var TreeView = class extends react.Component {
1205
1205
  constructor(..._args) {
1206
1206
  super(..._args);
1207
+ this.convert = convertToTree(this.props.dataSetConfig);
1208
+ this.initialTree = this.initTree(this.props.treeData);
1207
1209
  this.state = {
1208
1210
  treeData: this.props.treeData,
1209
- tree: {
1210
- id: "virtualRootNode",
1211
- name: "",
1212
- children: [],
1213
- path: "/",
1214
- originalNode: {}
1215
- },
1216
- treeSize: 0,
1217
- selectedNodes: this.props.selectedNodes || []
1211
+ tree: this.initialTree,
1212
+ treeSize: getTreeSize(this.initialTree),
1213
+ selectedNodes: this.props.selectedNodes || [],
1214
+ lastSelectedNodes: this.props.selectedNodes || []
1218
1215
  };
1219
- this.convert = convertToTree(this.props.dataSetConfig);
1220
1216
  this.selectNodes = (nodes) => {
1221
1217
  const { onSelect } = this.props;
1222
1218
  const { selectedNodes } = this.state;
@@ -1264,19 +1260,15 @@ var TreeView = class extends react.Component {
1264
1260
  });
1265
1261
  };
1266
1262
  }
1267
- componentDidMount() {
1268
- const { treeData } = this.props;
1269
- const tree = this.initTree(treeData);
1270
- const treeSize = getTreeSize(tree);
1271
- this.setState({
1272
- tree,
1273
- treeSize
1274
- });
1275
- }
1276
- componentDidUpdate(prevProps) {
1277
- const { selectedNodes: prevNodes } = prevProps;
1278
- const { selectedNodes } = this.props;
1279
- if (selectedNodes && prevNodes && prevNodes.length !== selectedNodes.length) this.setState({ selectedNodes });
1263
+ static {
1264
+ this.getDerivedStateFromProps = (props, state) => {
1265
+ const { selectedNodes } = props;
1266
+ if (selectedNodes && selectedNodes.length !== state.lastSelectedNodes.length) return {
1267
+ selectedNodes,
1268
+ lastSelectedNodes: selectedNodes
1269
+ };
1270
+ return null;
1271
+ };
1280
1272
  }
1281
1273
  initTree(treeData) {
1282
1274
  return {
@@ -1353,9 +1345,11 @@ function Pagination({ onChangePage, page, totalPages, onChangeSize, size }) {
1353
1345
  (0, react.useEffect)(() => {
1354
1346
  changePageDebouncedRef.current = (0, lodash.debounce)(onChangePage, 500);
1355
1347
  }, [onChangePage]);
1356
- (0, react.useEffect)(() => {
1348
+ const [lastPage, setLastPage] = (0, react.useState)(page);
1349
+ if (page !== lastPage) {
1350
+ setLastPage(page);
1357
1351
  setPageRaw(`${page + 1}`);
1358
- }, [page]);
1352
+ }
1359
1353
  function goToFirstPage() {
1360
1354
  onChangePage(0);
1361
1355
  }
@@ -1575,8 +1569,10 @@ var SelectWithSearch = class extends react.Component {
1575
1569
  value: this.state.inputValue,
1576
1570
  label: this.state.inputValue
1577
1571
  };
1578
- this.setState((prevState) => ({ options: [newItem, ...prevState.options] }));
1579
- this.state.inputValue = null;
1572
+ this.setState((prevState) => ({
1573
+ options: [newItem, ...prevState.options],
1574
+ inputValue: null
1575
+ }));
1580
1576
  onChange(newItem);
1581
1577
  });
1582
1578
  }
@@ -2100,16 +2096,27 @@ var ReactableWithClientSidePagination = class extends react.Component {
2100
2096
  this.state = {
2101
2097
  paginationSettings: paginationSettings || DEFAULT_PAGINATION_SETTINGS,
2102
2098
  filterSettings: {},
2103
- filteredItems: null
2099
+ filteredItems: null,
2100
+ lastSettings: paginationSettings
2104
2101
  };
2105
2102
  }
2106
2103
  componentDidUpdate(prevProps) {
2107
2104
  const { items } = this.props;
2108
2105
  const { items: prevItems } = prevProps;
2109
- const settings = this.props.paginationSettings || this.props.initialPaginationSettings;
2110
- const prevSettings = prevProps.paginationSettings || prevProps.initialPaginationSettings;
2111
2106
  if (items && prevItems && items.length !== prevItems.length) this.calculatePageNumber(items, this.state.paginationSettings);
2112
- if (settings && !(0, lodash.isEqual)(settings, prevSettings)) this.setState({ paginationSettings: settings }, () => this.handleChangePage(0));
2107
+ }
2108
+ static {
2109
+ this.getDerivedStateFromProps = (props, state) => {
2110
+ const settings = props.paginationSettings || props.initialPaginationSettings;
2111
+ if (settings && !(0, lodash.isEqual)(settings, state.lastSettings)) return {
2112
+ lastSettings: settings,
2113
+ paginationSettings: {
2114
+ ...settings,
2115
+ page: 0
2116
+ }
2117
+ };
2118
+ return null;
2119
+ };
2113
2120
  }
2114
2121
  render() {
2115
2122
  const { uniqueItemKey, isLoading, columnDefs, rowDef, tableName, clickable, selectable, selectedItems, onChangeSize } = this.props;
@@ -2292,20 +2299,17 @@ var ImageUpload = class extends react.Component {
2292
2299
  constructor(..._args) {
2293
2300
  super(..._args);
2294
2301
  this.state = {
2295
- dragOverPreview: false,
2302
+ dragOverUrl: void 0,
2296
2303
  file: null
2297
2304
  };
2298
2305
  this.handleDragOver = () => {
2299
- this.setState({ dragOverPreview: true });
2306
+ this.setState({ dragOverUrl: this.props.imageUrl });
2300
2307
  };
2301
2308
  }
2302
- componentDidUpdate(prevProps) {
2303
- if (prevProps.imageUrl !== this.props.imageUrl) this.setState({ dragOverPreview: false });
2304
- }
2305
2309
  render() {
2306
2310
  const { accept, imageUrl, imageSize, onSelectFile, onRemoveFile, onDownloadFile, ...otherProps } = this.props;
2307
- const { dragOverPreview, file } = this.state;
2308
- if (imageUrl && !dragOverPreview && file) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PreviewImage, {
2311
+ const { dragOverUrl, file } = this.state;
2312
+ if (imageUrl && !(dragOverUrl !== void 0 && dragOverUrl === imageUrl) && file) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PreviewImage, {
2309
2313
  url: imageUrl,
2310
2314
  imageSize,
2311
2315
  onRemoveFile: () => onRemoveFile?.(file),
@@ -2315,7 +2319,10 @@ var ImageUpload = class extends react.Component {
2315
2319
  else return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FileUpload, {
2316
2320
  ...otherProps,
2317
2321
  onRemoveFile: (file) => {
2318
- this.setState({ file: null });
2322
+ this.setState({
2323
+ file: null,
2324
+ dragOverUrl: void 0
2325
+ });
2319
2326
  onRemoveFile?.(file);
2320
2327
  },
2321
2328
  onSelectFile: (file) => {
@@ -2394,9 +2401,17 @@ var formatTime = (timezone, date) => {
2394
2401
  var DateTimeInput = (0, react.forwardRef)(({ timestamp, timezone, warning, readOnly = false, onChange, ...otherProps }, ref) => {
2395
2402
  const [time, setTime] = (0, react.useState)(formatTime(timezone, timestamp));
2396
2403
  const timeValue = time == null ? formatTime(timezone, timestamp) : time;
2397
- (0, react.useEffect)(() => {
2404
+ const [lastInput, setLastInput] = (0, react.useState)({
2405
+ timestamp,
2406
+ timezone
2407
+ });
2408
+ if (timestamp !== lastInput.timestamp || timezone !== lastInput.timezone) {
2409
+ setLastInput({
2410
+ timestamp,
2411
+ timezone
2412
+ });
2398
2413
  setTime(formatTime(timezone, timestamp));
2399
- }, [timezone, timestamp]);
2414
+ }
2400
2415
  function handleChangeDate(date, fromTextField) {
2401
2416
  const currentDate = dayjs.default.tz(timestamp, timezone);
2402
2417
  onChange(dayjs.default.tz(date, timezone).set("hour", currentDate.hour()).set("minute", currentDate.minute()).set("second", currentDate.second()).set("millisecond", currentDate.millisecond()).valueOf(), fromTextField);
@@ -2515,7 +2530,13 @@ var SelectWithSearchAsync = class extends react.Component {
2515
2530
  const { currentItem } = this.state;
2516
2531
  if (value && (!currentItem || currentItem.value !== value)) {
2517
2532
  if (!this.reloadItemFromCache()) searchById(value).then(this.addBackendItemToCache);
2518
- } else if (!value && currentItem) this.setState({ currentItem: null });
2533
+ }
2534
+ }
2535
+ static {
2536
+ this.getDerivedStateFromProps = (props, state) => {
2537
+ if (!props.value && state.currentItem) return { currentItem: null };
2538
+ return null;
2539
+ };
2519
2540
  }
2520
2541
  render() {
2521
2542
  const { clearable, noResultsMessage, disabled, menuPortalTarget } = this.props;
@@ -2801,7 +2822,7 @@ function Menu({ onChange, onShowCustomPeriod, periodName, timezone }) {
2801
2822
  const [visible, setVisible] = (0, react.useState)(false);
2802
2823
  const [showDatePicker, setShowDatePicker] = (0, react.useState)(false);
2803
2824
  const [showMonthPicker, setShowMonthPicker] = (0, react.useState)(false);
2804
- const [datePickerDate, setDatePickerDate] = (0, react.useState)(Date.now());
2825
+ const [datePickerDate, setDatePickerDate] = (0, react.useState)(() => Date.now());
2805
2826
  function setPeriod(period) {
2806
2827
  onChange(period);
2807
2828
  setVisible(false);
@@ -3227,17 +3248,18 @@ function sortModifications(modifications) {
3227
3248
  //#endregion
3228
3249
  //#region src/components/svg/SvgImage.tsx
3229
3250
  function useGlobalStyleElement() {
3230
- const styleElementRef = (0, react.useRef)(null);
3251
+ const [styleElement] = (0, react.useState)(() => {
3252
+ const element = document.createElement("style");
3253
+ element.appendChild(document.createTextNode(""));
3254
+ return element;
3255
+ });
3231
3256
  (0, react.useEffect)(() => {
3232
- const styleElement = document.createElement("style");
3233
- styleElement.appendChild(document.createTextNode(""));
3234
3257
  if (document && document.head) document.head.appendChild(styleElement);
3235
- styleElementRef.current = styleElement;
3236
3258
  return () => {
3237
- if (document && document.head && styleElement) document.head.removeChild(styleElement);
3259
+ if (document && document.head) document.head.removeChild(styleElement);
3238
3260
  };
3239
- }, []);
3240
- return styleElementRef.current;
3261
+ }, [styleElement]);
3262
+ return styleElement;
3241
3263
  }
3242
3264
  function SvgImage({ imageData, modifications, onConflict }) {
3243
3265
  const svgWrapperRef = (0, react.useRef)(null);
@@ -3326,9 +3348,10 @@ var getClickConfiguration = (onClick) => {
3326
3348
  var Chart = class extends react.Component {
3327
3349
  constructor(..._args) {
3328
3350
  super(..._args);
3329
- this.state = {};
3351
+ this.state = { titleApplied: false };
3330
3352
  this.chart = null;
3331
3353
  this.ro = null;
3354
+ this.titleTimeoutId = void 0;
3332
3355
  this.handleChart = (chart) => {
3333
3356
  this.chart = chart;
3334
3357
  this.ro = new ResizeObserver(() => {
@@ -3338,8 +3361,16 @@ var Chart = class extends react.Component {
3338
3361
  };
3339
3362
  }
3340
3363
  componentDidMount() {
3364
+ this.titleTimeoutId = setTimeout(() => this.setState({ titleApplied: true }), 0);
3365
+ }
3366
+ /**
3367
+ * Builds the Highcharts configuration from the props. Everything here is derived, so it is
3368
+ * rebuilt while rendering rather than mirrored into state.
3369
+ */
3370
+ buildOptions() {
3341
3371
  const { type, title, items, tooltipTitle, valueFormatter, onClick } = this.props;
3342
- this.setState({
3372
+ const { titleApplied } = this.state;
3373
+ return {
3343
3374
  ...initialOptions,
3344
3375
  plotOptions: {
3345
3376
  ...initialOptions.plotOptions,
@@ -3356,44 +3387,12 @@ var Chart = class extends react.Component {
3356
3387
  ...initialOptions.legend,
3357
3388
  enabled: type === "pie"
3358
3389
  },
3359
- yAxis: {
3360
- ...initialOptions.yAxis,
3361
- enabled: type === "column",
3362
- title: { text: title || "" }
3363
- },
3364
- series: [{
3365
- type,
3366
- innerSize: "50%",
3367
- data: items.map(({ label, value }) => [label, value])
3368
- }]
3369
- });
3370
- setTimeout(() => {
3371
- this.setState({ title: {
3372
- ...initialOptions.title,
3373
- text: type === "column" ? "" : title || ""
3374
- } });
3375
- }, 0);
3376
- }
3377
- componentDidUpdate(prevProps) {
3378
- const { type, title, items, tooltipTitle, valueFormatter, onClick } = this.props;
3379
- if (title !== prevProps.title) this.setState({
3380
3390
  title: {
3381
- ...this.state.title,
3382
- text: type === "column" ? "" : title || ""
3383
- },
3384
- yAxis: {
3385
- ...this.state.yAxis,
3386
- title: { text: title || "" }
3387
- }
3388
- });
3389
- if (type !== prevProps.type) this.setState({
3390
- legend: {
3391
- ...this.state.legend,
3392
- enabled: type === "pie"
3391
+ ...initialOptions.title,
3392
+ text: titleApplied && type !== "column" ? title || "" : ""
3393
3393
  },
3394
- title: { text: type === "column" ? "" : title || "" },
3395
3394
  yAxis: {
3396
- ...this.state.yAxis,
3395
+ ...initialOptions.yAxis,
3397
3396
  enabled: type === "column",
3398
3397
  title: { text: type === "column" ? title || "" : "" }
3399
3398
  },
@@ -3402,35 +3401,19 @@ var Chart = class extends react.Component {
3402
3401
  innerSize: "50%",
3403
3402
  data: items.map(({ label, value }) => [label, value])
3404
3403
  }]
3405
- });
3406
- if (!(0, lodash.isEqual)(items, prevProps.items)) this.setState({ series: [{
3407
- type,
3408
- innerSize: "50%",
3409
- data: items.map(({ label, value }) => [label, value])
3410
- }] });
3411
- if (tooltipTitle !== prevProps.tooltipTitle || valueFormatter !== prevProps.valueFormatter || onClick !== prevProps.onClick) this.setState({
3412
- tooltip: {
3413
- ...initialOptions.tooltip,
3414
- formatter() {
3415
- const format = valueFormatter || ((value) => value);
3416
- return createTooltip(tooltipTitle || "", this.point.name, format(this.point.y));
3417
- }
3418
- },
3419
- plotOptions: {
3420
- ...initialOptions.plotOptions,
3421
- ...getClickConfiguration(onClick)
3422
- }
3423
- });
3404
+ };
3424
3405
  }
3425
3406
  componentWillUnmount() {
3407
+ clearTimeout(this.titleTimeoutId);
3426
3408
  if (this.ro && this.chart?.renderTo) this.ro.unobserve(this.chart.renderTo);
3427
3409
  this.chart = null;
3428
3410
  this.ro = null;
3429
3411
  }
3430
3412
  render() {
3413
+ const built = this.buildOptions();
3431
3414
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(highcharts_react_official.default, {
3432
3415
  highcharts: highcharts.default,
3433
- options: this.props.updateOptions ? this.props.updateOptions(this.state) : this.state,
3416
+ options: this.props.updateOptions ? this.props.updateOptions(built) : built,
3434
3417
  containerProps: { className: this.props.className },
3435
3418
  callback: this.handleChart
3436
3419
  });