@iobroker/json-config 9.0.24 → 9.1.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.
package/README.md CHANGED
@@ -1503,6 +1503,7 @@ In the Settings of the Web developer tools, you can create your own devices with
1503
1503
  | `notOs` | Do not show this element on these operating systems of the host, on which the instance runs: `"win32"` or `["linux", "darwin"]` |
1504
1504
  | `docker` | Show this element only if the ioBroker runs (`true`) or does not run (`false`) in a docker container |
1505
1505
  | `disabled` | JS function that could use `native.attribute` for calculation |
1506
+ | `dependsOnStates` | ioBroker states, on which this element depends: `{"running": ".info.browsing"}`. See [Show or disable elements depending on ioBroker states](#show-or-disable-elements-depending-on-iobroker-states) |
1506
1507
  | `help` | help text (multi-language) |
1507
1508
  | `helpLink` | href to help (could be used only together with `help`) |
1508
1509
  | `style` | CSS style in ReactJS notation: `radiusBorder` and not `radius-border`. |
@@ -1558,6 +1559,42 @@ the text patterns of `label`, `help` and so on:
1558
1559
  }
1559
1560
  ```
1560
1561
 
1562
+ ### Show or disable elements depending on ioBroker states
1563
+
1564
+ With `dependsOnStates`, an element can react on the values of ioBroker states. The states are subscribed,
1565
+ so the element is updated immediately if a state changes - no reload of the configuration dialog is required.
1566
+
1567
+ ```json5
1568
+ {
1569
+ "startBrowse": {
1570
+ "type": "sendTo",
1571
+ "command": "browse",
1572
+ "label": "${_states.running?.val ? 'Stop browse' : 'Start browse'}",
1573
+ "dependsOnStates": { "running": ".info.browsing" },
1574
+ "disabled": "!!_states.running?.val"
1575
+ }
1576
+ }
1577
+ ```
1578
+
1579
+ - `dependsOnStates` is written as `{"<alias>": "<state ID>"}`. The values are available in **all** JS functions
1580
+ (`hidden`, `disabled`, `validator`, `defaultFunc`, `onChange.calculateFunc`, `confirm.condition`) and in the
1581
+ text patterns of `label`, `help`, `tooltip` and so on as `_states.<alias>`.
1582
+ - `_states.<alias>` contains the **whole state object**, so `_states.running?.val`, `_states.running?.ts`,
1583
+ `_states.running?.ack` can be used. It is `null` if the state does not exist, so always use `?.`.
1584
+ - A state ID that starts with a dot addresses the own instance: `.info.browsing` => `myAdapter.0.info.browsing`.
1585
+ Every other ID is used as it is, so the states of other adapters can be monitored too.
1586
+ - The ID may contain `${data.xxx}` patterns, e.g. `"device": "${data.deviceInstance}.info.connection"`.
1587
+ It will be resolved anew if the configuration changes. Wildcards (`*`) are **not** allowed.
1588
+ - If one of the states changes, `hidden`, `disabled`, `label`, `help`, `validator` and `defaultFunc` of this
1589
+ element will be calculated anew. Every state is subscribed only once, independent of how many elements
1590
+ (or table lines) use it.
1591
+ - The short form `"dependsOnStates": ["admin.0.info.connection"]` uses the ID itself as an alias:
1592
+ `_states['admin.0.info.connection']`.
1593
+ - The attribute may be used on every element, also on `panel`, `tabs` and table columns.
1594
+
1595
+ **Note:** old admin versions do not know `_states` and would throw an error by the evaluation of such a
1596
+ function, so the element would stay visible and enabled.
1597
+
1561
1598
  **Note:** old admin versions do not know `_os` and would evaluate `"hidden": "_os !== 'linux'"` to `true`
1562
1599
  and so hide the element everywhere. Because of that, `os`/`notOs` should be preferred, as they are simply
1563
1600
  ignored by old admin versions (the element will be shown). If a JS function must be used, write it
@@ -1708,6 +1745,7 @@ const func = new Function(
1708
1745
  '_os', // Operating system of the host, where the instance runs: 'win32', 'linux', 'darwin', ...
1709
1746
  '_arch', // Architecture of the host, where the instance runs: 'x64', 'arm64', ...
1710
1747
  '_host', // Information about the host: {id, os, osType, arch, release, nodeVersion, controllerVersion, docker, dockerVersion}
1748
+ '_states', // Values of the states from `dependsOnStates`: {<alias>: <state object or null>}
1711
1749
  myValidator.includes('return') ? myValidator : 'return ' + myValidator); // e.g. "_alive === true"
1712
1750
 
1713
1751
  const isValid = func(data, systemConfig.common, instanceAlive, adapter.common, this.props.socket);
@@ -1728,6 +1766,7 @@ The following variables are available in JS function in adapter settings:
1728
1766
  - `_os` - operating system of the host, on which the instance runs (`process.platform`), e.g. `linux`, `win32`, `darwin`. Empty string if unknown
1729
1767
  - `_arch` - architecture of the host, on which the instance runs, e.g. `x64`, `arm64`
1730
1768
  - `_host` - information about the host: `{id, os, osType, arch, release, nodeVersion, controllerVersion, docker, dockerVersion}`. `docker` is `undefined` if the docker state was not requested or the host did not answer
1769
+ - `_states` - values of the states from [`dependsOnStates`](#show-or-disable-elements-depending-on-iobroker-states): `{<alias>: <state object>}`. `null` if the state does not exist
1731
1770
 
1732
1771
  ### Custom settings dialog
1733
1772
 
@@ -1748,6 +1787,7 @@ const func = new Function(
1748
1787
  "_os",
1749
1788
  "_arch",
1750
1789
  "_host",
1790
+ "_states",
1751
1791
  myValidator.includes("return") ? myValidator : "return " + myValidator
1752
1792
  ); // e.g. "_alive === true"
1753
1793
 
@@ -1774,6 +1814,7 @@ The following variables are available in JS function in custom settings:
1774
1814
  - `_os` - operating system of the host, on which the instance runs (`process.platform`), e.g. `linux`, `win32`, `darwin`. Empty string if unknown
1775
1815
  - `_arch` - architecture of the host, on which the instance runs, e.g. `x64`, `arm64`
1776
1816
  - `_host` - information about the host: `{id, os, osType, arch, release, nodeVersion, controllerVersion, docker, dockerVersion}`. `docker` is `undefined` if the docker state was not requested or the host did not answer
1817
+ - `_states` - values of the states from [`dependsOnStates`](#show-or-disable-elements-depending-on-iobroker-states): `{<alias>: <state object>}`. `null` if the state does not exist
1777
1818
 
1778
1819
  ```json5
1779
1820
  {
@@ -1907,8 +1948,9 @@ The schema is used here: https://github.com/SchemaStore/schemastore/blob/6da29cd
1907
1948
  ### **WORK IN PROGRESS**
1908
1949
  -->
1909
1950
  ## Changelog
1910
- ### 9.0.24 (2026-08-30)
1951
+ ### 9.1.0 (2026-08-31)
1911
1952
  - (@GermanBluefox) Added progress bar to the state component
1953
+ - (@GermanBluefox) Added the possibility to show or hide elements depending on the states: `dependsOnStates` and the JS variable `_states`
1912
1954
 
1913
1955
  ### 9.0.23 (2026-08-27)
1914
1956
  - (@krobipd) Corrected: the object browser stayed empty after closing the object customization dialog if any object was changed while the dialog was open (ioBroker/ioBroker.admin#3391)
@@ -90,8 +90,60 @@ export default class ConfigGeneric<Props extends ConfigGenericProps = ConfigGene
90
90
  private reportedHidden;
91
91
  private calculateTimeout;
92
92
  private readonly AsyncFunction;
93
+ /** Resolved `dependsOnStates` of this element: alias => state ID */
94
+ protected stateAliases: Record<string, string> | null;
95
+ /** True if this element has subscribed on states, so it must unsubscribe if `dependsOnStates` disappears */
96
+ protected statesSubscribed: boolean;
93
97
  constructor(props: Props);
94
98
  protected getCachedObject(id: string): Promise<ioBroker.Object | null>;
99
+ /**
100
+ * Bring the `dependsOnStates` attribute into the form `{ alias: stateID }`.
101
+ * In the short array form the state ID is used as an alias too.
102
+ *
103
+ * @param dependsOnStates value of the `dependsOnStates` attribute
104
+ * @returns aliases with the still unresolved state IDs
105
+ */
106
+ static normalizeDependsOnStates(dependsOnStates: Record<string, string> | string[] | undefined): Record<string, string>;
107
+ /**
108
+ * Build the real state ID out of a `dependsOnStates` entry: resolve the `${data.xxx}` patterns and
109
+ * expand a leading dot (`.info.running`) to the namespace of the own instance.
110
+ *
111
+ * @param id state ID from the schema
112
+ * @returns the resolved state ID or an empty string if it cannot be used
113
+ */
114
+ protected resolveStateId(id: string): Promise<string>;
115
+ /**
116
+ * Resolve all state IDs of a `dependsOnStates` attribute.
117
+ *
118
+ * @param dependsOnStates value of the `dependsOnStates` attribute
119
+ * @returns aliases with the resolved state IDs
120
+ */
121
+ protected resolveDependsOnStates(dependsOnStates: Record<string, string> | string[] | undefined): Promise<Record<string, string>>;
122
+ /**
123
+ * Subscribe on the given state IDs. The promise is resolved as soon as all values are known,
124
+ * so the following calculation does not run with unknown states.
125
+ *
126
+ * @param ids resolved state IDs
127
+ */
128
+ protected subscribeOnStateIds(ids: string[]): Promise<void>;
129
+ /**
130
+ * Resolve the `dependsOnStates` attribute of this element and subscribe on the states.
131
+ *
132
+ * It is called before every calculation and not only once, because a state ID can contain a
133
+ * `${data.xxx}` pattern and so it can change if the user changes the configuration.
134
+ */
135
+ protected updateStateSubscriptions(): Promise<void>;
136
+ /** True if this element depends on ioBroker states */
137
+ protected usesDependsOnStates(): boolean;
138
+ /** One of the states from `dependsOnStates` changed => recalculate `hidden`, `disabled`, ... */
139
+ protected onDependsOnStateChanged: () => void;
140
+ /**
141
+ * Values of the subscribed states for the JS functions and patterns (`_states.<alias>`).
142
+ *
143
+ * @param aliases alias => state ID. If omitted, the aliases of this element are used
144
+ * @returns alias => state object. `null` if the state does not exist
145
+ */
146
+ protected getStateValues(aliases?: Record<string, string> | null): Record<string, ioBroker.State | null>;
95
147
  componentDidMount(): Promise<void>;
96
148
  private defaultSendTo;
97
149
  componentWillUnmount(): void;
@@ -104,7 +156,9 @@ export default class ConfigGeneric<Props extends ConfigGenericProps = ConfigGene
104
156
  getText(text: ioBroker.StringOrTranslated | undefined | null | {
105
157
  /** @deprecated */
106
158
  func: ioBroker.StringOrTranslated;
107
- }, noTranslation?: boolean): string;
159
+ }, noTranslation?: boolean,
160
+ /** Values for `_states`. If omitted, the states of this element are used */
161
+ states?: Record<string, ioBroker.State | null>): string;
108
162
  getTextAsync(text: ioBroker.StringOrTranslated, noTranslation?: boolean): Promise<string>;
109
163
  renderDialogConfirm(): JSX.Element | null;
110
164
  getIcon(iconSettings?: ConfigIconType | null): JSX.Element | null;
@@ -147,8 +201,12 @@ export default class ConfigGeneric<Props extends ConfigGenericProps = ConfigGene
147
201
  * @returns false if the element must not be shown on this host
148
202
  */
149
203
  static isHostAllowed(schema: Pick<ConfigItem, 'os' | 'notOs' | 'docker'>, hostInfo?: JsonConfigHostInfo): boolean;
150
- execute(func: string | boolean | Record<string, string> | undefined, defaultValue: string | number | boolean | undefined, data: Record<string, any> | undefined, arrayIndex: number | undefined, globalData: Record<string, any> | undefined, funcName?: string): Promise<string | number | boolean | undefined>;
151
- executeCustom(func: string | boolean | Record<string, string> | undefined, data: Record<string, any>, customObj: Record<string, any> | undefined, instanceObj: ioBroker.InstanceObject | undefined, arrayIndex: number | undefined, globalData: Record<string, any> | undefined, funcName?: string): Promise<string | boolean | number | null | undefined>;
204
+ execute(func: string | boolean | Record<string, string> | undefined, defaultValue: string | number | boolean | undefined, data: Record<string, any> | undefined, arrayIndex: number | undefined, globalData: Record<string, any> | undefined, funcName?: string,
205
+ /** Values for `_states`. If omitted, the states of this element are used */
206
+ states?: Record<string, ioBroker.State | null>): Promise<string | number | boolean | undefined>;
207
+ executeCustom(func: string | boolean | Record<string, string> | undefined, data: Record<string, any>, customObj: Record<string, any> | undefined, instanceObj: ioBroker.InstanceObject | undefined, arrayIndex: number | undefined, globalData: Record<string, any> | undefined, funcName?: string,
208
+ /** Values for `_states`. If omitted, the states of this element are used */
209
+ states?: Record<string, ioBroker.State | null>): Promise<string | boolean | number | null | undefined>;
152
210
  calculate(schema: Record<string, any>): Promise<{
153
211
  error: boolean;
154
212
  disabled: boolean;
@@ -164,10 +222,14 @@ export default class ConfigGeneric<Props extends ConfigGenericProps = ConfigGene
164
222
  getObject: (id: string) => Promise<ioBroker.Object | null>;
165
223
  getPatternAsync(pattern: string | {
166
224
  func: string;
167
- }, data?: Record<string, any> | null, noTranslation?: boolean): Promise<string>;
225
+ }, data?: Record<string, any> | null, noTranslation?: boolean,
226
+ /** Values for `_states`. If omitted, the states of this element are used */
227
+ states?: Record<string, ioBroker.State | null>): Promise<string>;
168
228
  getPattern(pattern: string | {
169
229
  func: string;
170
- }, data?: Record<string, any>, noTranslation?: boolean): string;
230
+ }, data?: Record<string, any>, noTranslation?: boolean,
231
+ /** Values for `_states`. If omitted, the states of this element are used */
232
+ states?: Record<string, ioBroker.State | null>): string;
171
233
  updateCalculatedValues(): void;
172
234
  render(): string | JSX.Element | null;
173
235
  }
@@ -65,6 +65,10 @@ export default class ConfigGeneric extends Component {
65
65
  reportedHidden = false;
66
66
  calculateTimeout = null;
67
67
  AsyncFunction;
68
+ /** Resolved `dependsOnStates` of this element: alias => state ID */
69
+ stateAliases = null;
70
+ /** True if this element has subscribed on states, so it must unsubscribe if `dependsOnStates` disappears */
71
+ statesSubscribed = false;
68
72
  constructor(props) {
69
73
  super(props);
70
74
  this.AsyncFunction = Object.getPrototypeOf(async function () { }).constructor;
@@ -98,7 +102,128 @@ export default class ConfigGeneric extends Component {
98
102
  }
99
103
  return (await this.props.oContext.socket.getObject(id)) || null;
100
104
  }
105
+ /**
106
+ * Bring the `dependsOnStates` attribute into the form `{ alias: stateID }`.
107
+ * In the short array form the state ID is used as an alias too.
108
+ *
109
+ * @param dependsOnStates value of the `dependsOnStates` attribute
110
+ * @returns aliases with the still unresolved state IDs
111
+ */
112
+ static normalizeDependsOnStates(dependsOnStates) {
113
+ const result = {};
114
+ if (!dependsOnStates) {
115
+ return result;
116
+ }
117
+ if (Array.isArray(dependsOnStates)) {
118
+ dependsOnStates.forEach(id => {
119
+ if (id && typeof id === 'string') {
120
+ result[id] = id;
121
+ }
122
+ });
123
+ }
124
+ else if (isObject(dependsOnStates)) {
125
+ Object.keys(dependsOnStates).forEach(alias => {
126
+ if (dependsOnStates[alias] && typeof dependsOnStates[alias] === 'string') {
127
+ result[alias] = dependsOnStates[alias];
128
+ }
129
+ });
130
+ }
131
+ else {
132
+ console.error(`[JsonConfigComponent] Invalid dependsOnStates: ${JSON.stringify(dependsOnStates)}`);
133
+ }
134
+ return result;
135
+ }
136
+ /**
137
+ * Build the real state ID out of a `dependsOnStates` entry: resolve the `${data.xxx}` patterns and
138
+ * expand a leading dot (`.info.running`) to the namespace of the own instance.
139
+ *
140
+ * @param id state ID from the schema
141
+ * @returns the resolved state ID or an empty string if it cannot be used
142
+ */
143
+ async resolveStateId(id) {
144
+ let result = (id || '').toString();
145
+ if (result.includes('${')) {
146
+ result = await this.getPatternAsync(result, null, true);
147
+ }
148
+ result = result.trim();
149
+ if (result.startsWith('.')) {
150
+ result = `${this.props.oContext.adapterName}.${this.props.oContext.instance || 0}${result}`;
151
+ }
152
+ if (result.includes('*')) {
153
+ console.error(`[JsonConfigComponent] Wildcards are not allowed in dependsOnStates: ${result}`);
154
+ return '';
155
+ }
156
+ return result;
157
+ }
158
+ /**
159
+ * Resolve all state IDs of a `dependsOnStates` attribute.
160
+ *
161
+ * @param dependsOnStates value of the `dependsOnStates` attribute
162
+ * @returns aliases with the resolved state IDs
163
+ */
164
+ async resolveDependsOnStates(dependsOnStates) {
165
+ const aliases = ConfigGeneric.normalizeDependsOnStates(dependsOnStates);
166
+ const resolved = {};
167
+ for (const alias of Object.keys(aliases)) {
168
+ const id = await this.resolveStateId(aliases[alias]);
169
+ if (id) {
170
+ resolved[alias] = id;
171
+ }
172
+ }
173
+ return resolved;
174
+ }
175
+ /**
176
+ * Subscribe on the given state IDs. The promise is resolved as soon as all values are known,
177
+ * so the following calculation does not run with unknown states.
178
+ *
179
+ * @param ids resolved state IDs
180
+ */
181
+ async subscribeOnStateIds(ids) {
182
+ if (!ids.length && !this.statesSubscribed) {
183
+ return;
184
+ }
185
+ this.statesSubscribed = !!ids.length;
186
+ await this.props.oContext.subscribeStates?.(this, ids, this.onDependsOnStateChanged);
187
+ }
188
+ /**
189
+ * Resolve the `dependsOnStates` attribute of this element and subscribe on the states.
190
+ *
191
+ * It is called before every calculation and not only once, because a state ID can contain a
192
+ * `${data.xxx}` pattern and so it can change if the user changes the configuration.
193
+ */
194
+ async updateStateSubscriptions() {
195
+ this.stateAliases = await this.resolveDependsOnStates(this.props.schema?.dependsOnStates);
196
+ await this.subscribeOnStateIds(Object.values(this.stateAliases));
197
+ }
198
+ /** True if this element depends on ioBroker states */
199
+ usesDependsOnStates() {
200
+ return !!this.props.schema?.dependsOnStates || this.statesSubscribed;
201
+ }
202
+ /** One of the states from `dependsOnStates` changed => recalculate `hidden`, `disabled`, ... */
203
+ onDependsOnStateChanged = () => {
204
+ this.forceUpdate();
205
+ };
206
+ /**
207
+ * Values of the subscribed states for the JS functions and patterns (`_states.<alias>`).
208
+ *
209
+ * @param aliases alias => state ID. If omitted, the aliases of this element are used
210
+ * @returns alias => state object. `null` if the state does not exist
211
+ */
212
+ getStateValues(aliases) {
213
+ const map = aliases === undefined ? this.stateAliases : aliases;
214
+ const result = {};
215
+ if (map) {
216
+ Object.keys(map).forEach(alias => {
217
+ result[alias] = this.props.oContext.getStateValue?.(map[alias]) ?? null;
218
+ });
219
+ }
220
+ return result;
221
+ }
101
222
  async componentDidMount() {
223
+ if (this.usesDependsOnStates()) {
224
+ // Subscribe before the first calculation, so `defaultFunc` and `hidden` already see the values
225
+ await this.updateStateSubscriptions();
226
+ }
102
227
  if (this.props.schema?.defaultFunc) {
103
228
  if (this.props.custom) {
104
229
  this.defaultValue = await this.executeCustom(this.props.schema.defaultFunc, this.props.data, this.props.customObj, this.props.oContext.instanceObj, this.props.arrayIndex, this.props.globalData, 'defaultFunc');
@@ -195,6 +320,10 @@ export default class ConfigGeneric extends Component {
195
320
  if (this.props.attr) {
196
321
  this.props.oContext.registerOnForceUpdate?.(this.props.attr);
197
322
  }
323
+ // Not only if `statesSubscribed` is true: the element could have released its states before (an ID
324
+ // with a `${data.xxx}` pattern was resolved to nothing), but it is still registered in the pool
325
+ this.props.oContext.unsubscribeStates?.(this);
326
+ this.statesSubscribed = false;
198
327
  if (this.sendToTimeout) {
199
328
  clearTimeout(this.sendToTimeout);
200
329
  this.sendToTimeout = null;
@@ -259,14 +388,16 @@ export default class ConfigGeneric extends Component {
259
388
  ConfigGeneric.setValue(data[part], attr, value);
260
389
  }
261
390
  }
262
- getText(text, noTranslation) {
391
+ getText(text, noTranslation,
392
+ /** Values for `_states`. If omitted, the states of this element are used */
393
+ states) {
263
394
  if (!text) {
264
395
  return '';
265
396
  }
266
397
  if (typeof text === 'string') {
267
398
  const strText = noTranslation ? text : I18n.t(text);
268
399
  if (strText.includes('${')) {
269
- return this.getPattern(strText, undefined, noTranslation);
400
+ return this.getPattern(strText, undefined, noTranslation, states);
270
401
  }
271
402
  return strText;
272
403
  }
@@ -277,9 +408,9 @@ export default class ConfigGeneric extends Component {
277
408
  if (typeof funcText.func === 'object') {
278
409
  return this.getPattern(funcText.func[this.lang] ||
279
410
  funcText.func.en ||
280
- '', undefined, true);
411
+ '', undefined, true, states);
281
412
  }
282
- return this.getPattern(funcText.func, undefined, noTranslation);
413
+ return this.getPattern(funcText.func, undefined, noTranslation, states);
283
414
  }
284
415
  return text[this.lang] || text.en || '';
285
416
  }
@@ -604,7 +735,9 @@ export default class ConfigGeneric extends Component {
604
735
  }
605
736
  return true;
606
737
  }
607
- async execute(func, defaultValue, data, arrayIndex, globalData, funcName) {
738
+ async execute(func, defaultValue, data, arrayIndex, globalData, funcName,
739
+ /** Values for `_states`. If omitted, the states of this element are used */
740
+ states) {
608
741
  let fun;
609
742
  if (isObject(func)) {
610
743
  fun = func.func;
@@ -623,8 +756,8 @@ export default class ConfigGeneric extends Component {
623
756
  fun = ConfigGeneric.ensureAwaitGetObject(fun);
624
757
  }
625
758
  try {
626
- const f = new this.AsyncFunction('data', 'originalData', '_system', '_alive', '_common', '_socket', '_instance', 'arrayIndex', 'globalData', '_changed', '_href', 'getObject', '_os', '_arch', '_host', fun.includes('return') ? fun : `return ${fun}`);
627
- const result = await f(data || this.props.data || {}, this.props.originalData, this.props.oContext.systemConfig, this.props.alive, this.props.common, this.props.oContext.socket, this.props.oContext.instance, arrayIndex, globalData || {}, this.props.changed, window.location.href, this.getObject, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {});
759
+ const f = new this.AsyncFunction('data', 'originalData', '_system', '_alive', '_common', '_socket', '_instance', 'arrayIndex', 'globalData', '_changed', '_href', 'getObject', '_os', '_arch', '_host', '_states', fun.includes('return') ? fun : `return ${fun}`);
760
+ const result = await f(data || this.props.data || {}, this.props.originalData, this.props.oContext.systemConfig, this.props.alive, this.props.common, this.props.oContext.socket, this.props.oContext.instance, arrayIndex, globalData || {}, this.props.changed, window.location.href, this.getObject, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {}, states || this.getStateValues());
628
761
  this.debugLog(funcName || 'JS function', fun, result, data);
629
762
  return result;
630
763
  }
@@ -634,7 +767,9 @@ export default class ConfigGeneric extends Component {
634
767
  return defaultValue;
635
768
  }
636
769
  }
637
- async executeCustom(func, data, customObj, instanceObj, arrayIndex, globalData, funcName) {
770
+ async executeCustom(func, data, customObj, instanceObj, arrayIndex, globalData, funcName,
771
+ /** Values for `_states`. If omitted, the states of this element are used */
772
+ states) {
638
773
  let fun;
639
774
  if (isObject(func)) {
640
775
  fun = func.func;
@@ -653,8 +788,8 @@ export default class ConfigGeneric extends Component {
653
788
  fun = ConfigGeneric.ensureAwaitGetObject(fun);
654
789
  }
655
790
  try {
656
- const f = new this.AsyncFunction('data', 'originalData', '_system', 'instanceObj', 'customObj', '_socket', 'arrayIndex', 'globalData', '_changed', '_href', 'getObject', '_os', '_arch', '_host', fun.includes('return') ? fun : `return ${fun}`);
657
- const result = await f(data || this.props.data, this.props.originalData, this.props.oContext.systemConfig, instanceObj || {}, customObj || {}, this.props.oContext.socket, arrayIndex, globalData || {}, this.props.changed, window.location.href, this.getObject, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {});
791
+ const f = new this.AsyncFunction('data', 'originalData', '_system', 'instanceObj', 'customObj', '_socket', 'arrayIndex', 'globalData', '_changed', '_href', 'getObject', '_os', '_arch', '_host', '_states', fun.includes('return') ? fun : `return ${fun}`);
792
+ const result = await f(data || this.props.data, this.props.originalData, this.props.oContext.systemConfig, instanceObj || {}, customObj || {}, this.props.oContext.socket, arrayIndex, globalData || {}, this.props.changed, window.location.href, this.getObject, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {}, states || this.getStateValues());
658
793
  this.debugLog(funcName || 'JS function', fun, result, data);
659
794
  return result;
660
795
  }
@@ -801,7 +936,9 @@ export default class ConfigGeneric extends Component {
801
936
  return null;
802
937
  }
803
938
  };
804
- async getPatternAsync(pattern, data, noTranslation) {
939
+ async getPatternAsync(pattern, data, noTranslation,
940
+ /** Values for `_states`. If omitted, the states of this element are used */
941
+ states) {
805
942
  data ||= this.props.data;
806
943
  if (!pattern) {
807
944
  return '';
@@ -825,15 +962,15 @@ export default class ConfigGeneric extends Component {
825
962
  }
826
963
  try {
827
964
  if (this.props.custom) {
828
- const f = new this.AsyncFunction('data', 'originalData', 'arrayIndex', 'globalData', '_system', 'instanceObj', 'customObj', '_socket', '_changed', '_href', 'getObject', '_os', '_arch', '_host', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
829
- const text = await f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.oContext.instanceObj, this.props.customObj, this.props.oContext.socket, this.props.changed, window.location.href, this.getObject, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {});
965
+ const f = new this.AsyncFunction('data', 'originalData', 'arrayIndex', 'globalData', '_system', 'instanceObj', 'customObj', '_socket', '_changed', '_href', 'getObject', '_os', '_arch', '_host', '_states', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
966
+ const text = await f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.oContext.instanceObj, this.props.customObj, this.props.oContext.socket, this.props.changed, window.location.href, this.getObject, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {}, states || this.getStateValues());
830
967
  if (noTranslation) {
831
968
  return text;
832
969
  }
833
970
  return I18n.t(text);
834
971
  }
835
- const f = new this.AsyncFunction('data', 'originalData', 'arrayIndex', 'globalData', '_system', '_alive', '_common', '_socket', '_changed', '_href', 'getObject', '_os', '_arch', '_host', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
836
- const text = await f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.alive, this.props.common, this.props.oContext.socket, this.props.changed, window.location.href, this.getObject, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {});
972
+ const f = new this.AsyncFunction('data', 'originalData', 'arrayIndex', 'globalData', '_system', '_alive', '_common', '_socket', '_changed', '_href', 'getObject', '_os', '_arch', '_host', '_states', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
973
+ const text = await f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.alive, this.props.common, this.props.oContext.socket, this.props.changed, window.location.href, this.getObject, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {}, states || this.getStateValues());
837
974
  if (noTranslation) {
838
975
  return text;
839
976
  }
@@ -844,7 +981,9 @@ export default class ConfigGeneric extends Component {
844
981
  return patternStr;
845
982
  }
846
983
  }
847
- getPattern(pattern, data, noTranslation) {
984
+ getPattern(pattern, data, noTranslation,
985
+ /** Values for `_states`. If omitted, the states of this element are used */
986
+ states) {
848
987
  data ||= this.props.data;
849
988
  if (!pattern) {
850
989
  return '';
@@ -868,15 +1007,15 @@ export default class ConfigGeneric extends Component {
868
1007
  }
869
1008
  try {
870
1009
  if (this.props.custom) {
871
- const f = new Function('data', 'originalData', 'arrayIndex', 'globalData', '_system', 'instanceObj', 'customObj', '_socket', '_changed', '_href', '_os', '_arch', '_host', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
872
- const text = f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.oContext.instanceObj, this.props.customObj, this.props.oContext.socket, this.props.changed, window.location.href, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {});
1010
+ const f = new Function('data', 'originalData', 'arrayIndex', 'globalData', '_system', 'instanceObj', 'customObj', '_socket', '_changed', '_href', '_os', '_arch', '_host', '_states', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
1011
+ const text = f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.oContext.instanceObj, this.props.customObj, this.props.oContext.socket, this.props.changed, window.location.href, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {}, states || this.getStateValues());
873
1012
  if (noTranslation) {
874
1013
  return text;
875
1014
  }
876
1015
  return I18n.t(text);
877
1016
  }
878
- const f = new Function('data', 'originalData', 'arrayIndex', 'globalData', '_system', '_alive', '_common', '_socket', '_changed', '_href', '_os', '_arch', '_host', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
879
- const text = f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.alive, this.props.common, this.props.oContext.socket, this.props.changed, window.location.href, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {});
1017
+ const f = new Function('data', 'originalData', 'arrayIndex', 'globalData', '_system', '_alive', '_common', '_socket', '_changed', '_href', '_os', '_arch', '_host', '_states', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
1018
+ const text = f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.alive, this.props.common, this.props.oContext.socket, this.props.changed, window.location.href, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {}, states || this.getStateValues());
880
1019
  if (noTranslation) {
881
1020
  return text;
882
1021
  }
@@ -897,6 +1036,11 @@ export default class ConfigGeneric extends Component {
897
1036
  if (!schema) {
898
1037
  return;
899
1038
  }
1039
+ if (this.usesDependsOnStates()) {
1040
+ // The state IDs can contain `${data.xxx}` patterns, so they must be resolved anew if the data changed.
1041
+ // It waits for the first values, so the element is not calculated with unknown states.
1042
+ await this.updateStateSubscriptions();
1043
+ }
900
1044
  const { error, disabled, hidden, defaultValue } = await this.calculate(schema);
901
1045
  if (!this.state.calculatedValues ||
902
1046
  this.state.calculatedValues.error !== error ||