@iobroker/gui-components 10.3.15 → 10.3.16

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/README.md CHANGED
@@ -36,7 +36,7 @@ npm create vite@latest src -- --template react-ts
36
36
  "dependencies": {
37
37
  "@emotion/react": "^11.14.0",
38
38
  "@emotion/styled": "^11.14.1",
39
- "@iobroker/gui-components": "^10.3.15",
39
+ "@iobroker/gui-components": "^10.3.16",
40
40
  "@mui/icons-material": "^9.0.1",
41
41
  "@mui/material": "^9.0.1",
42
42
  "react": "^19.2.5",
@@ -877,6 +877,10 @@ You can find the migration instructions:
877
877
  -->
878
878
 
879
879
  ## Changelog
880
+ ### 10.3.16 (2026-11-04)
881
+ - (@GermanBluefox) Added: object browser shows an arrow up/down next to a changed number; a changed boolean flips
882
+ - (@GermanBluefox) Added: `SaveCloseButtons` shows a check mark after saving
883
+
880
884
  ### 10.3.15 (2026-11-03)
881
885
  - (@GermanBluefox) Added: animated icons `IconPlayPause`, `IconStatusLed`, `IconResult`, `IconRefresh`, `IconBell`, `IconMenuArrow`, `IconTrash`, `IconConnectionState`, `IconExpertMode` and `IconFolder` (CSS/SVG)
882
886
  - (@GermanBluefox) Changed: the object browser uses the animated reload and delete icons
@@ -97,6 +97,15 @@ export declare class ObjectBrowserClass extends Component<ObjectBrowserProps, Ob
97
97
  private windowLast;
98
98
  /** id -> how often the state of this row has changed. Part of the memo key of the row */
99
99
  readonly rowStateVersion: Record<string, number>;
100
+ /**
101
+ * id -> last change of a number (arrow up or down) or of a boolean (the value flips), see `renderColumnValue`.
102
+ * `shown` is the time the row showed it first, so a row that is rendered again continues the animation.
103
+ */
104
+ readonly valueTrends: Record<string, {
105
+ dir: 'up' | 'down' | 'toggle';
106
+ ts: number;
107
+ shown?: number;
108
+ }>;
100
109
  /** The running animations of an unfolded or folding node (see `rowAnimation.ts`) */
101
110
  private rowAnimations;
102
111
  /** The node whose children slide under its row at the moment; they are removed when that ends */
@@ -17,7 +17,7 @@ import { Utils } from '../Utils'; // @iobroker/gui-components/Components/Utils
17
17
  import { TabContainer } from '../TabContainer';
18
18
  import { TabContent } from '../TabContent';
19
19
  import { TabHeader } from '../TabHeader';
20
- import { ROW_HEIGHT, applyFilter, buildTree, findNode, formatValue, generateFile, getName, colVar, widthFromContainer, growVar, getSelectIdIconFromObjects, setCustomValue, } from './utils';
20
+ import { ROW_HEIGHT, TREND_DURATION, applyFilter, buildTree, findNode, formatValue, generateFile, getName, colVar, widthFromContainer, growVar, getSelectIdIconFromObjects, setCustomValue, } from './utils';
21
21
  import { HistoryChart } from './HistoryChart';
22
22
  import { styles } from './styles';
23
23
  import * as dialogs from './dialogs';
@@ -124,6 +124,11 @@ export class ObjectBrowserClass extends Component {
124
124
  windowLast = 0;
125
125
  /** id -> how often the state of this row has changed. Part of the memo key of the row */
126
126
  rowStateVersion = {};
127
+ /**
128
+ * id -> last change of a number (arrow up or down) or of a boolean (the value flips), see `renderColumnValue`.
129
+ * `shown` is the time the row showed it first, so a row that is rendered again continues the animation.
130
+ */
131
+ valueTrends = {};
127
132
  /** The running animations of an unfolded or folding node (see `rowAnimation.ts`) */
128
133
  rowAnimations = [];
129
134
  /** The node whose children slide under its row at the moment; they are removed when that ends */
@@ -1073,6 +1078,16 @@ export class ObjectBrowserClass extends Component {
1073
1078
  item.data.state = undefined;
1074
1079
  }
1075
1080
  }
1081
+ const oldVal = oldState?.val;
1082
+ const newVal = state?.val;
1083
+ if (typeof oldVal === 'number' && typeof newVal === 'number' && oldVal !== newVal) {
1084
+ if (!Number.isNaN(oldVal) && !Number.isNaN(newVal)) {
1085
+ this.valueTrends[id] = { dir: newVal > oldVal ? 'up' : 'down', ts: Date.now() };
1086
+ }
1087
+ }
1088
+ else if (typeof oldVal === 'boolean' && typeof newVal === 'boolean' && oldVal !== newVal) {
1089
+ this.valueTrends[id] = { dir: 'toggle', ts: Date.now() };
1090
+ }
1076
1091
  if (state) {
1077
1092
  this.states[id] = state;
1078
1093
  }
@@ -2892,6 +2907,59 @@ export class ObjectBrowserClass extends Component {
2892
2907
  .newValueBrowser-light {
2893
2908
  animation: newValueAnimation-light 2s ease-in-out;
2894
2909
  }
2910
+ .iob-ob-trend {
2911
+ display: inline-flex;
2912
+ align-self: center;
2913
+ flex-shrink: 0;
2914
+ width: 0.85em;
2915
+ height: 0.85em;
2916
+ margin-left: 4px;
2917
+ opacity: 0;
2918
+ animation: iob-ob-trend-up ${TREND_DURATION}ms ease-out forwards;
2919
+ }
2920
+ .iob-ob-trend svg {
2921
+ width: 100%;
2922
+ height: 100%;
2923
+ fill: currentColor;
2924
+ }
2925
+ .iob-ob-trend-up {
2926
+ color: ${this.props.themeType === 'dark' ? '#66bb6a' : '#2e7d32'};
2927
+ }
2928
+ .iob-ob-trend-down {
2929
+ color: ${this.props.themeType === 'dark' ? '#ef5350' : '#c62828'};
2930
+ animation-name: iob-ob-trend-down;
2931
+ }
2932
+ @keyframes iob-ob-trend-up {
2933
+ 0% { opacity: 0; transform: translateY(45%); }
2934
+ 12% { opacity: 1; transform: none; }
2935
+ 80% { opacity: 1; }
2936
+ 100% { opacity: 0; }
2937
+ }
2938
+ @keyframes iob-ob-trend-down {
2939
+ 0% { opacity: 0; transform: translateY(-45%); }
2940
+ 12% { opacity: 1; transform: none; }
2941
+ 80% { opacity: 1; }
2942
+ 100% { opacity: 0; }
2943
+ }
2944
+ .newValueBrowser-dark.iob-ob-flip {
2945
+ animation: iob-ob-flip 0.32s cubic-bezier(0.3, 1.4, 0.5, 1), newValueAnimation-dark 2s ease-in-out;
2946
+ }
2947
+ .newValueBrowser-light.iob-ob-flip {
2948
+ animation: iob-ob-flip 0.32s cubic-bezier(0.3, 1.4, 0.5, 1), newValueAnimation-light 2s ease-in-out;
2949
+ }
2950
+ @keyframes iob-ob-flip {
2951
+ 0% { transform: perspective(60px) rotateX(90deg); opacity: 0.3; }
2952
+ 100% { transform: none; opacity: 1; }
2953
+ }
2954
+ @media (prefers-reduced-motion: reduce) {
2955
+ .iob-ob-trend { animation-name: iob-ob-trend-fade; }
2956
+ .newValueBrowser-dark.iob-ob-flip { animation: newValueAnimation-dark 2s ease-in-out; }
2957
+ .newValueBrowser-light.iob-ob-flip { animation: newValueAnimation-light 2s ease-in-out; }
2958
+ @keyframes iob-ob-trend-fade {
2959
+ 0%, 80% { opacity: 1; }
2960
+ 100% { opacity: 0; }
2961
+ }
2962
+ }
2895
2963
  .highlight-link .iob-link {
2896
2964
  text-decoration: underline;
2897
2965
  cursor: pointer;