node-red-contrib-knx-ultimate 6.3.7 → 6.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,9 +6,13 @@
6
6
 
7
7
  # CHANGELOG
8
8
 
9
+ **Version 6.3.8** - August 2026<br/>
10
+
11
+ - **HUE Controller**: fixed Light and grouped-light mapping tabs remaining hidden when Node-RED emits temporary empty KNX/Hue config-selector values during editor initialization; saved gateways now survive bootstrap and remount, while only an explicit user selection of `none` hides the mapping tabs.<br/>
12
+
9
13
  **Version 6.3.7** - August 2026<br/>
10
14
 
11
- - **HUE Controller**: improved gateway-dependent editor visibility, preserving all KNX mappings and the correct tab layout when gateways are deselected, reselected or temporarily unavailable; obsolete headings and decorative tab images were removed.<br/>
15
+ - **HUE Controller**: improved gateway-dependent editor visibility, preserving all KNX mappings and restoring the correct tab layout when gateways are deselected or reselected; obsolete headings and decorative images were removed.<br/>
12
16
  - **HUE Controller**: added clearer Hue Bridge loading/offline feedback, kept saved mappings visible while disabling unavailable device discovery, and restored the complete previous configuration when the editor is cancelled after previewing another Hue resource.<br/>
13
17
 
14
18
  **Version 6.3.6** - August 2026<br/>
@@ -1,5 +1,6 @@
1
1
  <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/11f26b4500.js"></script>
2
2
  <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/htmlUtils.js"></script>
3
+ <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/hueControllerEditorSelection.js"></script>
3
4
  <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/hueControllerProfiles.js"></script>
4
5
  <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/hueControllerMigration.js"></script>
5
6
  <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/hueControllerMigrationDialog.js"></script>
@@ -47,6 +48,24 @@
47
48
  };
48
49
  const TUTORIALS_URL = 'https://www.youtube.com/playlist?list=PL9Yh1bjbLAYrU8PsVhW4xzEug2WtVFv3E';
49
50
  const profileBundle = window.KNXUltimateHueControllerProfiles;
51
+ const editorSelection = window.KNXUltimateHueControllerEditorSelection;
52
+ const shouldPreserveStoredSelection = editorSelection && typeof editorSelection.shouldPreserveStoredSelection === 'function'
53
+ ? editorSelection.shouldPreserveStoredSelection
54
+ : (event, nextValue, storedValue, emptyValues) => {
55
+ const programmaticChange = !event || !event.originalEvent;
56
+ const normalizedNext = nextValue === undefined || nextValue === null ? '' : String(nextValue).trim().toLowerCase();
57
+ const normalizedStored = storedValue === undefined || storedValue === null ? '' : String(storedValue).trim().toLowerCase();
58
+ return programmaticChange && emptyValues.has(normalizedNext) && !emptyValues.has(normalizedStored);
59
+ };
60
+ const resolveSelectedOrStoredSelection = editorSelection && typeof editorSelection.resolveSelectedOrStoredSelection === 'function'
61
+ ? editorSelection.resolveSelectedOrStoredSelection
62
+ : (selectedValue, storedValue, emptyValues) => {
63
+ const selected = selectedValue === undefined || selectedValue === null ? '' : String(selectedValue).trim();
64
+ const stored = storedValue === undefined || storedValue === null ? '' : String(storedValue).trim();
65
+ return emptyValues.has(selected.toLowerCase())
66
+ ? (emptyValues.has(stored.toLowerCase()) ? '' : stored)
67
+ : selected;
68
+ };
50
69
 
51
70
  const getProfileDefinition = (controllerType) => {
52
71
  // The bundle captures a private definition through a RED facade. It does
@@ -373,9 +392,12 @@
373
392
  const draft = {};
374
393
  Object.keys(definition?.defaults || {}).forEach((key) => {
375
394
  const fieldValue = readFieldValue($(`#node-input-${key}`), controllerNode[key]);
376
- const value = key === 'server'
377
- ? normalizeKnxServerSelection(fieldValue)
378
- : (key === 'serverHue' ? normalizeHueServerSelection(fieldValue) : fieldValue);
395
+ let value = fieldValue;
396
+ if (key === 'server') {
397
+ value = resolveSelectedOrStoredSelection(fieldValue, controllerNode.server, KNX_EMPTY_SERVER_VALUES);
398
+ } else if (key === 'serverHue') {
399
+ value = resolveSelectedOrStoredSelection(fieldValue, controllerNode.serverHue, HUE_EMPTY_SERVER_VALUES);
400
+ }
379
401
  controllerNode[key] = value;
380
402
  legacyContext[key] = value;
381
403
  draft[key] = cloneEditorValue(value);
@@ -764,6 +786,7 @@
764
786
  $container.find('.form-tips').remove();
765
787
 
766
788
  const $knxFlowOnlyNotice = $container.find('.hue-controller-knx-flow-only-notice');
789
+ const $lightTabs = $container.find('#tabs');
767
790
  let knxFlowOnlyNoticeText = 'To display the group-address mappings, select a KNX gateway. Without one, this node can only be controlled by input messages from the flow.';
768
791
  try {
769
792
  const noticeKey = 'node-red-contrib-knx-ultimate/knxUltimateHueController:knxUltimateHueController.knx_gateway_required_for_mappings';
@@ -789,12 +812,30 @@
789
812
  const knxDisabled = !hasDomServer && !hasStoredServer;
790
813
  $container.toggleClass('hue-controller-no-knx', knxDisabled);
791
814
  $knxFlowOnlyNotice.toggle(controllerType === 'light' && knxDisabled);
815
+ if (controllerType === 'light' && $lightTabs.length) {
816
+ // Device availability controls the outer profile container;
817
+ // KNX selection alone controls the nested mapping tabs.
818
+ const shouldShowLightTabs = !knxDisabled;
819
+ // The mature Light editor also manages this inline style. Make
820
+ // the wrapper authoritative after every lifecycle transition so
821
+ // a transient bootstrap value cannot leave display:none behind
822
+ // after the saved/selected KNX gateway has been restored.
823
+ $lightTabs.css('display', shouldShowLightTabs ? 'flex' : 'none');
824
+ if (shouldShowLightTabs) {
825
+ try { $lightTabs.tabs('refresh'); } catch (error) { /* raw tabs remain visible */ }
826
+ }
827
+ }
792
828
  };
793
- const handleControllerKnxSelection = function () {
829
+ const handleControllerKnxSelection = function (event) {
794
830
  // An explicit user choice must supersede the persisted bootstrap
795
831
  // fallback. Node-RED can represent "none" as `_ADD_`, so normalize
796
832
  // it before both editors recalculate their visibility.
797
- const selectedServer = normalizeKnxServerSelection($(this).val());
833
+ const selectedValue = $(this).val();
834
+ if (shouldPreserveStoredSelection(event, selectedValue, controllerNode.server, KNX_EMPTY_SERVER_VALUES)) {
835
+ updateControllerKnxVisibility();
836
+ return;
837
+ }
838
+ const selectedServer = normalizeKnxServerSelection(selectedValue);
798
839
  controllerNode.server = selectedServer;
799
840
  if (legacyContext) legacyContext.server = selectedServer;
800
841
  updateControllerKnxVisibility();
@@ -820,8 +861,14 @@
820
861
  });
821
862
  $(document)
822
863
  .off('change.knxUltimateHueControllerHueServer', '#node-input-serverHue')
823
- .on('change.knxUltimateHueControllerHueServer', '#node-input-serverHue', () => {
824
- const nextServerId = normalizeHueServerSelection($('#node-input-serverHue').val());
864
+ .on('change.knxUltimateHueControllerHueServer', '#node-input-serverHue', (event) => {
865
+ const selectedValue = $('#node-input-serverHue').val();
866
+ if (shouldPreserveStoredSelection(event, selectedValue, controllerNode.serverHue, HUE_EMPTY_SERVER_VALUES)) {
867
+ updateHueDeviceControlVisibility();
868
+ updateSelectedDevicePresentation();
869
+ return;
870
+ }
871
+ const nextServerId = normalizeHueServerSelection(selectedValue);
825
872
  if (nextServerId === activeHueServerId) return;
826
873
  activeHueServerId = nextServerId;
827
874
  controllerNode.serverHue = nextServerId;
@@ -1021,6 +1068,8 @@ Dedicated legacy HUE nodes remain registered for existing flows, but are frozen
1021
1068
 
1022
1069
  When no **KNX Gateway** is selected (including **none** and **Add new...**), only the Light mapping tabs are hidden. Their mounted GA, DPT and Name fields remain unchanged and reappear immediately with the correct vertical tab layout when the gateway is selected again. Hue resource selection and flow-only options remain available.
1023
1070
 
1071
+ Temporary placeholder values emitted programmatically by Node-RED while the KNX and Hue configuration selectors initialize do not replace saved gateways. Only an explicit user selection of **none** hides the Light mapping tabs.
1072
+
1024
1073
  The Light mapping tabs contain only configuration controls; the decorative Dim, Tunable White and RGB images have been removed.
1025
1074
 
1026
1075
  For a single light, the **Dim**, **Tunable White**, **RGB/HSV**, and native-effects sections are enabled from the live capabilities reported by the selected Hue API v2 light resource. A light that exposes `dimming`, `color_temperature`, or `color` therefore shows the corresponding KNX mappings automatically.
@@ -10,6 +10,7 @@
10
10
  <p>KNX-Zuordnungszeilen halten GA, DPT und Name in einer Zeile. DPT-Auswahl und Namensfeld verwenden kompakte Breiten; das Namensfeld kann sich in schmalen Editoren weiter verkleinern, ohne den gespeicherten Text zu ändern. Gespeicherte DPT-Werte bleiben erhalten, während die Auswahloptionen asynchron geladen werden.</p>
11
11
  <p>Unterstützt werden Leuchten und Leuchtengruppen, Steckdosen, Taster, Tap dial, Bewegungs- und Kamerabewegungsressourcen, Kontakte, Lichtstärke, Temperatur, Luftfeuchtigkeit, Szenen, Batterie, Zigbee-Verbindung und Softwareupdate.</p>
12
12
  <p>Wenn kein <strong>KNX-Gateway</strong> ausgewählt ist (einschließlich <code>none</code> und <em>Neu hinzufügen...</em>), werden die Registerkarten der Leuchtenzuordnung ausgeblendet; die Auswahl der Hue-Ressource und reine Flow-Optionen bleiben verfügbar. Die zugehörigen Felder GA, DPT und Name bleiben eingebunden und behalten ihre Werte, wenn das Gateway erneut ausgewählt wird; dabei wird auch das vertikale Registerkartenlayout unverändert wiederhergestellt.</p>
13
+ <p>Temporäre Platzhalterwerte, die Node-RED während der Initialisierung der KNX- und Hue-Konfigurationsauswahl programmgesteuert auslöst, ersetzen keine gespeicherten Gateways. Nur die ausdrückliche Benutzerauswahl von <code>none</code> blendet die Leuchtenzuordnungsregister aus.</p>
13
14
  <p>Die Registerkarten der Leuchtenzuordnung enthalten nur Konfigurationsfelder; die dekorativen Bilder für Dimmen, abstimmbares Weiß und RGB wurden entfernt.</p>
14
15
  <p>Bei einer einzelnen Leuchte richten sich die Bereiche <strong>Dimmen</strong>, <strong>Abstimmbares Weiß</strong>, <strong>RGB/HSV</strong> und native Effekte nach den Live-Fähigkeiten der ausgewählten Hue-API-v2-Ressource. Die zugehörigen KNX-Zuordnungen erscheinen automatisch, wenn die Leuchte <code>dimming</code>, <code>color_temperature</code> oder <code>color</code> bereitstellt.</p>
15
16
  <p>Der Leuchteneditor zeigt gespeicherte Zuordnungen sofort an, ohne auf den Hue-Ressourcencache der Runtime zu warten. Aktuelle Fähigkeiten werden im Hintergrund geladen; schlägt die Anfrage fehl, bleiben Zuordnungen und Pin-Auswahl verfügbar und eine feste rote Node-RED-Fehlermeldung meldet den Fehler.</p>
@@ -10,6 +10,7 @@
10
10
  <p>KNX mapping rows keep GA, DPT and Name on one line. DPT selectors and Name fields use compact widths, and the Name field can contract further on narrow editor trays without changing its stored text. Saved DPT values are retained while the selector options load asynchronously.</p>
11
11
  <p>Supported functions: lights and grouped lights, plugs, buttons, Tap dial, motion and camera motion, contacts, light level, temperature, humidity, scenes, battery, Zigbee connectivity, and device software update.</p>
12
12
  <p>When no <strong>KNX Gateway</strong> is selected (including <code>none</code> and <em>Add new...</em>), the Light mapping tabs are hidden while Hue resource selection and flow-only options remain available. Their GA, DPT and Name fields stay mounted and retain their values when the gateway is selected again, with the vertical tab layout restored unchanged.</p>
13
+ <p>Temporary placeholder values emitted programmatically by Node-RED while the KNX and Hue configuration selectors initialize do not replace saved gateways. Only an explicit user selection of <code>none</code> hides the Light mapping tabs.</p>
13
14
  <p>The Light mapping tabs contain only configuration controls; the decorative Dim, Tunable White and RGB images have been removed.</p>
14
15
  <p>For a single light, the <strong>Dim</strong>, <strong>Tunable White</strong>, <strong>RGB/HSV</strong>, and native-effects sections follow the live capabilities reported by the selected Hue API v2 resource. The corresponding KNX mappings appear automatically when the light exposes <code>dimming</code>, <code>color_temperature</code>, or <code>color</code>.</p>
15
16
  <p>The Light editor renders saved mappings immediately without waiting for the runtime Hue resource cache. Current capabilities load in the background; if that request fails, the saved mappings and pin selector remain available and a fixed red Node-RED error reports the failure.</p>
@@ -10,6 +10,7 @@
10
10
  <p>Las filas de mapeo KNX mantienen GA, DPT y Nombre en una sola línea. Los campos DPT y Nombre usan anchos compactos; Nombre puede reducirse aún más en editores estrechos sin modificar el texto guardado. Los valores DPT guardados se conservan mientras las opciones del selector se cargan de forma asíncrona.</p>
11
11
  <p>Admite luces y grupos de luces, enchufes, botones, Tap dial, movimiento y movimiento de cámara, contactos, nivel de luz, temperatura, humedad, escenas, batería, conectividad Zigbee y actualización de software.</p>
12
12
  <p>Cuando no hay ningún <strong>Gateway KNX</strong> seleccionado (incluidos <code>none</code> y <em>Añadir nuevo...</em>), se ocultan las pestañas de mapeo de Luz; la selección del recurso Hue y las opciones exclusivas del flujo siguen disponibles. Sus campos GA, DPT y Nombre permanecen montados y conservan sus valores cuando se vuelve a seleccionar el gateway, restaurando también sin cambios el diseño vertical de las pestañas.</p>
13
+ <p>Los valores temporales emitidos mediante programación por Node-RED mientras se inicializan los selectores de configuración KNX y Hue no sustituyen los gateways guardados. Solo una selección explícita de <code>none</code> oculta las pestañas de mapeo de Luz.</p>
13
14
  <p>Las pestañas de mapeo de Luz contienen únicamente los controles de configuración; se han eliminado las imágenes decorativas de Regulación, Blanco regulable y RGB.</p>
14
15
  <p>Para una luz individual, las secciones <strong>Regulación</strong>, <strong>Blanco regulable</strong>, <strong>RGB/HSV</strong> y efectos nativos siguen las capacidades en vivo declaradas por el recurso Hue API v2 seleccionado. Los mapeos KNX correspondientes aparecen automáticamente cuando la luz expone <code>dimming</code>, <code>color_temperature</code> o <code>color</code>.</p>
15
16
  <p>El editor de Luz muestra inmediatamente los mapeos guardados sin esperar a la caché de recursos Hue del runtime. Las capacidades actuales se cargan en segundo plano; si la solicitud falla, los mapeos y el selector de pines siguen disponibles y un error rojo fijo de Node-RED informa del fallo.</p>
@@ -10,6 +10,7 @@
10
10
  <p>Les lignes de mappage KNX conservent GA, DPT et Nom sur une seule ligne. Les champs DPT et Nom utilisent des largeurs compactes ; le champ Nom peut encore se réduire dans un éditeur étroit sans modifier le texte enregistré. Les valeurs DPT enregistrées sont conservées pendant le chargement asynchrone des options du sélecteur.</p>
11
11
  <p>Fonctions prises en charge : lampes et groupes, prises, boutons, Tap dial, mouvement et mouvement de caméra, contacts, niveau de lumière, température, humidité, scènes, batterie, connectivité Zigbee et mise à jour logicielle.</p>
12
12
  <p>Lorsqu'aucune <strong>Passerelle KNX</strong> n'est sélectionnée (y compris <code>none</code> et <em>Ajouter...</em>), les onglets de mappage Lampe sont masqués ; la sélection de la ressource Hue et les options réservées au flow restent disponibles. Leurs champs GA, DPT et Nom restent montés et conservent leurs valeurs lorsque la passerelle est à nouveau sélectionnée, tout comme la disposition verticale des onglets.</p>
13
+ <p>Les valeurs temporaires émises par programmation par Node-RED pendant l'initialisation des sélecteurs de configuration KNX et Hue ne remplacent pas les passerelles enregistrées. Seule une sélection explicite de <code>none</code> masque les onglets de mappage Lampe.</p>
13
14
  <p>Les onglets de mappage Lampe contiennent uniquement les contrôles de configuration ; les images décoratives Variation, Blanc réglable et RGB ont été supprimées.</p>
14
15
  <p>Pour une lampe individuelle, les sections <strong>Variation</strong>, <strong>Blanc réglable</strong>, <strong>RGB/HSV</strong> et effets natifs suivent les capacités live déclarées par la ressource Hue API v2 sélectionnée. Les mappages KNX correspondants apparaissent automatiquement lorsque la lampe expose <code>dimming</code>, <code>color_temperature</code> ou <code>color</code>.</p>
15
16
  <p>L'éditeur Lampe affiche immédiatement les mappages enregistrés sans attendre le cache de ressources Hue de la runtime. Les capacités actuelles sont chargées en arrière-plan ; si la requête échoue, les mappages et le sélecteur de broches restent disponibles et une erreur Node-RED rouge et fixe signale l'échec.</p>
@@ -10,6 +10,7 @@
10
10
  <p>Le righe di mappatura KNX mantengono GA, DPT e Nome sulla stessa linea. I campi DPT e Nome usano larghezze compatte e Nome può restringersi ulteriormente negli editor più stretti senza modificare il testo memorizzato. I valori DPT salvati vengono conservati mentre le opzioni del selettore si caricano in modo asincrono.</p>
11
11
  <p>Funzioni supportate: luci e gruppi di luci, prese, pulsanti, Tap dial, movimento e movimento telecamera, contatti, livello luce, temperatura, umidità, scene, batteria, connettività Zigbee e aggiornamento software.</p>
12
12
  <p>Quando non è selezionato alcun <strong>Gateway KNX</strong> (inclusi <code>none</code> e <em>Aggiungi nuovo...</em>), vengono nascoste le TAB delle mappature Luce, mentre la selezione della risorsa Hue e le opzioni per il solo flow restano disponibili. I relativi campi GA, DPT e Nome rimangono montati e conservano i valori quando il gateway viene riselezionato, ripristinando invariato anche il layout verticale delle TAB.</p>
13
+ <p>I valori segnaposto temporanei emessi programmaticamente da Node-RED durante l'inizializzazione dei selettori KNX e Hue non sostituiscono i gateway salvati. Soltanto la selezione esplicita di <code>none</code> nasconde le TAB delle mappature Luce.</p>
13
14
  <p>Le TAB delle mappature Luce contengono soltanto i controlli di configurazione; le immagini decorative Dimmer, Bianco regolabile e RGB sono state eliminate.</p>
14
15
  <p>Per una luce singola, le sezioni <strong>Dimmer</strong>, <strong>Bianco regolabile</strong>, <strong>RGB/HSV</strong> ed effetti nativi seguono le capacità live dichiarate dalla risorsa Hue API v2 selezionata. Le relative mappature KNX appaiono automaticamente quando la luce espone <code>dimming</code>, <code>color_temperature</code> o <code>color</code>.</p>
15
16
  <p>L'editor Luce mostra subito le mappature salvate senza attendere la cache delle risorse Hue del runtime. Le capability correnti vengono caricate in background; se la richiesta fallisce, mappature e selettore dei pin restano disponibili e un errore Node-RED rosso e fisso segnala il problema.</p>
@@ -10,6 +10,7 @@
10
10
  <p>KNX 映射行会将 GA、DPT 和名称保持在同一行。DPT 与名称字段采用紧凑宽度;在较窄的编辑器中,名称字段还可继续收缩,而不会更改已保存的文本。选择器选项异步加载时,已保存的 DPT 值会被保留。</p>
11
11
  <p>支持灯和灯组、插座、按钮、Tap dial、运动和摄像机运动、接触、光照度、温度、湿度、场景、电池、Zigbee 连接和设备软件更新。</p>
12
12
  <p>未选择<strong>KNX 网关</strong>时(包括 <code>none</code> 和“新增...”),灯光映射选项卡会隐藏,而 Hue 资源选择和仅用于流程的选项仍然可用。相关的 GA、DPT 和名称字段会保持挂载,并在重新选择网关时保留原值,同时完整恢复垂直选项卡布局。</p>
13
+ <p>Node-RED 在初始化 KNX 和 Hue 配置选择器时以编程方式产生的临时占位值不会覆盖已保存的网关。只有用户明确选择 <code>none</code> 时才会隐藏灯光映射选项卡。</p>
13
14
  <p>灯光映射选项卡现在只包含配置控件;调光、可调白光和 RGB 装饰图片已移除。</p>
14
15
  <p>对于单灯,<strong>调光</strong>、<strong>可调白光</strong>、<strong>RGB/HSV</strong> 和原生效果部分会根据所选 Hue API v2 资源实时报告的能力显示。当灯具提供 <code>dimming</code>、<code>color_temperature</code> 或 <code>color</code> 时,相应的 KNX 映射会自动出现。</p>
15
16
  <p>灯光编辑器会立即显示已保存的映射,而无需等待运行时 Hue 资源缓存。当前能力在后台加载;如果请求失败,映射和引脚选择器仍然可用,并由固定的红色 Node-RED 错误消息报告失败。</p>
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "engines": {
4
4
  "node": ">=20.18.1"
5
5
  },
6
- "version": "6.3.7",
6
+ "version": "6.3.8",
7
7
  "description": "KNX Ultimate is the most advanced KNX integration for Node-RED, providing secure KNX/IP communication, routing, ETS project import, Philips Hue, Matter Controller and Matter Bridge (control matter device via KNX and expose KNX GA via Matter), MQTT, diagnostics with AI, virtual devices, and powerful automation nodes. Build professional, reliable, and scalable smart home and building automation projects with minimal effort.",
8
8
  "files": [
9
9
  "nodes/",
@@ -0,0 +1,32 @@
1
+ (function (root, factory) {
2
+ const api = factory()
3
+ if (typeof module === 'object' && module.exports) module.exports = api
4
+ if (root) root.KNXUltimateHueControllerEditorSelection = api
5
+ }(typeof window !== 'undefined' ? window : globalThis, function () {
6
+ const DEFAULT_EMPTY_VALUES = new Set(['', 'none', '_add_', '__none__'])
7
+
8
+ function normalizeSelection (value, emptyValues = DEFAULT_EMPTY_VALUES) {
9
+ const normalized = value === undefined || value === null ? '' : String(value).trim()
10
+ const normalizedEmptyValues = emptyValues instanceof Set
11
+ ? emptyValues
12
+ : new Set(Array.isArray(emptyValues) ? emptyValues : DEFAULT_EMPTY_VALUES)
13
+ return normalizedEmptyValues.has(normalized.toLowerCase()) ? '' : normalized
14
+ }
15
+
16
+ function shouldPreserveStoredSelection (event, nextValue, storedValue, emptyValues = DEFAULT_EMPTY_VALUES) {
17
+ const programmaticChange = !event || !event.originalEvent
18
+ return programmaticChange &&
19
+ normalizeSelection(nextValue, emptyValues) === '' &&
20
+ normalizeSelection(storedValue, emptyValues) !== ''
21
+ }
22
+
23
+ function resolveSelectedOrStoredSelection (selectedValue, storedValue, emptyValues = DEFAULT_EMPTY_VALUES) {
24
+ return normalizeSelection(selectedValue, emptyValues) || normalizeSelection(storedValue, emptyValues)
25
+ }
26
+
27
+ return {
28
+ normalizeSelection,
29
+ resolveSelectedOrStoredSelection,
30
+ shouldPreserveStoredSelection
31
+ }
32
+ }))
@@ -510,14 +510,17 @@
510
510
  return resolveKnxServerValue() !== '';
511
511
  };
512
512
 
513
- const $tabs = $("#tabs");
513
+ // Scope the generic legacy ID to this Controller profile. Other
514
+ // Node-RED/config dialogs may contain their own #tabs element.
515
+ const $tabs = $('#hue-controller-profile-editor').find('#tabs');
514
516
  const $pinSectionRow = $("#node-input-enableNodePINS").closest('.form-row');
515
517
  const $pinSelect = $("#node-input-enableNodePINS");
516
518
  const $pinInfoRow = $pinSectionRow.next('.form-tips');
517
519
  const updateTabsVisibility = () => {
518
520
  const knxSelected = hasKnxServerSelected();
519
- const hueDeviceSelected = getHueDeviceValue() !== '';
520
- const shouldShowTabs = knxSelected && hueDeviceSelected;
521
+ // The unified wrapper owns device/profile visibility. Inside the
522
+ // mounted Light profile, only KNX selection controls its GA tabs.
523
+ const shouldShowTabs = knxSelected;
521
524
 
522
525
  if (shouldShowTabs) {
523
526
  // jQuery.show() restores a hidden div as display:block, which
@@ -1139,12 +1142,18 @@
1139
1142
 
1140
1143
  $(document)
1141
1144
  .off('change.knxUltimateHueLightGateway', '#node-input-server')
1142
- .on('change.knxUltimateHueLightGateway', '#node-input-server', function () {
1145
+ .on('change.knxUltimateHueLightGateway', '#node-input-server', function (event) {
1143
1146
  const selectedValue = $(this).val();
1144
1147
  const normalizedValue = selectedValue === undefined || selectedValue === null
1145
1148
  ? ''
1146
1149
  : String(selectedValue).trim();
1147
- node.server = KNX_EMPTY_VALUES.has(normalizedValue.toLowerCase()) ? '' : normalizedValue;
1150
+ const selectionApi = window.KNXUltimateHueControllerEditorSelection;
1151
+ const preserveStoredSelection = selectionApi && typeof selectionApi.shouldPreserveStoredSelection === 'function'
1152
+ ? selectionApi.shouldPreserveStoredSelection(event, selectedValue, node.server, KNX_EMPTY_VALUES)
1153
+ : ((!event || !event.originalEvent) && KNX_EMPTY_VALUES.has(normalizedValue.toLowerCase()) && resolveKnxServerValue() !== '');
1154
+ if (!preserveStoredSelection) {
1155
+ node.server = KNX_EMPTY_VALUES.has(normalizedValue.toLowerCase()) ? '' : normalizedValue;
1156
+ }
1148
1157
  // Selecting none is a visibility change only. Keep every GA,
1149
1158
  // DPT option and name mounted exactly as-is so selecting the
1150
1159
  // gateway again cannot expose a half-empty mapping editor.
@@ -1281,9 +1290,9 @@
1281
1290
  setAvailableEffects(effects);
1282
1291
  // Check if grouped, to hide/show the "Get current" buttons
1283
1292
  if (oLight.type === "grouped_light") {
1284
- $("#tabs").tabs("enable", "#tabs-4");
1285
- $("#tabs").tabs("enable", "#tabs-3");
1286
- $("#tabs").tabs("enable", "#tabs-2");
1293
+ $tabs.tabs("enable", "#tabs-4");
1294
+ $tabs.tabs("enable", "#tabs-3");
1295
+ $tabs.tabs("enable", "#tabs-2");
1287
1296
  $("#getColorAtSwitchOnDayTimeButton").show();
1288
1297
  $("#getColorAtSwitchOnNightTimeButton").show();
1289
1298
  $("#node-input-specifySwitchOnBrightness").empty().append(
@@ -1331,9 +1340,9 @@
1331
1340
  );
1332
1341
  }
1333
1342
 
1334
- $("#tabs").tabs("disable", "#tabs-4");
1335
- $("#tabs").tabs("disable", "#tabs-3");
1336
- $("#tabs").tabs("disable", "#tabs-2");
1343
+ $tabs.tabs("disable", "#tabs-4");
1344
+ $tabs.tabs("disable", "#tabs-3");
1345
+ $tabs.tabs("disable", "#tabs-2");
1337
1346
  $("#divColorsAtSwitchOn").hide();
1338
1347
  $("#divColorsAtSwitchOnNightTime").hide();
1339
1348
  $("#divTemperatureAtSwitchOn").hide();
@@ -1346,11 +1355,11 @@
1346
1355
 
1347
1356
  // Enable options/tabs one by one
1348
1357
  if (oLight.dimming !== undefined) {
1349
- $("#tabs").tabs("enable", "#tabs-2");
1358
+ $tabs.tabs("enable", "#tabs-2");
1350
1359
  $("#divBehaviourBrightness").show();
1351
1360
  }
1352
1361
  if (oLight.color !== undefined) {
1353
- $("#tabs").tabs("enable", "#tabs-4");
1362
+ $tabs.tabs("enable", "#tabs-4");
1354
1363
  $("#divColorsAtSwitchOn").show();
1355
1364
  $("#divColorsAtSwitchOnNightTime").show();
1356
1365
  $("#divColorCycle").show();
@@ -1367,7 +1376,7 @@
1367
1376
  }
1368
1377
  // Check temperature (if the light supports temperature, it support dimming as well)
1369
1378
  if (oLight.color_temperature !== undefined) {
1370
- $("#tabs").tabs("enable", "#tabs-3");
1379
+ $tabs.tabs("enable", "#tabs-3");
1371
1380
  //$("#tabs").tabs("enable", "#tabs-2");
1372
1381
  $("#node-input-specifySwitchOnBrightness").append(
1373
1382
  $("<option>")