1/** 2 * CitrusOS lifecycle adapter for Virtual Keyboard â MIDI routes to focused window only. 3 */ 4(function (global) { 5 'use strict'; 6 7 var focusObserver = null; 8 var observedShell = null; 9 var fitTimer = null; 10 var fitResizeObserver = null; 11 var fitScopeEl = null; 12 13 function setMidiForFocus(focused) { 14 if (global.DawEngine) { 15 global.DawEngine.setPhysicalMidiEnabled(!!focused); 16 } 17 } 18 19 function watchWindowFocus(containerEl) { 20 if (!containerEl) return; 21 var shell = containerEl.closest('.citrus-window'); 22 if (!shell) { 23 setMidiForFocus(true); 24 return; 25 } 26 27 observedShell = shell; 28 setMidiForFocus(shell.classList.contains('citrus-window--focused')); 29 30 if (focusObserver) { 31 focusObserver.disconnect(); 32 focusObserver = null; 33 } 34 35 focusObserver = new MutationObserver(function () { 36 if (!observedShell || !observedShell.isConnected) return; 37 setMidiForFocus(observedShell.classList.contains('citrus-window--focused')); 38 }); 39 focusObserver.observe(shell, { attributes: true, attributeFilter: ['class'] }); 40 } 41 42 function clearFocusWatch() { 43 if (focusObserver) { 44 focusObserver.disconnect(); 45 focusObserver = null; 46 } 47 observedShell = null; 48 } 49 50 function measureDawShellHeight(scopeEl, shell) { 51 var titlebar = shell.querySelector('.citrus-window__titlebar'); 52 var root = scopeEl.querySelector ? scopeEl.querySelector('#daw-app-root') : null; 53 if (!titlebar || !root) return 0; 54 55 var contentH = Math.ceil(root.getBoundingClientRect().height); 56 if (!contentH) { 57 contentH = root.offsetHeight || root.scrollHeight; 58 } 59 60 return titlebar.offsetHeight + contentH + 2; 61 } 62 63 function fitCitrusWindow(scopeEl) { 64 if (!scopeEl) return; 65 var shell = scopeEl.closest ? scopeEl.closest('.citrus-window') : null; 66 if (!shell || shell.classList.contains('citrus-window--maximized') || 67 shell.classList.contains('citrus-window--immersive')) { 68 return; 69 } 70 71 var nextH = measureDawShellHeight(scopeEl, shell); 72 if (!nextH) return; 73 74 var appId = shell.getAttribute('data-app-id'); 75 76 if (global.CitrusDesktop && typeof global.CitrusDesktop.setWindowSize === 'function' && appId) { 77 global.CitrusDesktop.setWindowSize(appId, null, Math.max(320, nextH)); 78 return; 79 } 80 81 shell.style.height = Math.max(320, nextH) + 'px'; 82 } 83 84 function scheduleFitWindow(scopeEl) { 85 if (fitTimer) global.clearTimeout(fitTimer); 86 fitTimer = global.setTimeout(function () { 87 fitTimer = null; 88 global.requestAnimationFrame(function () { 89 global.requestAnimationFrame(function () { 90 fitCitrusWindow(scopeEl); 91 }); 92 }); 93 }, 0); 94 } 95 96 function watchContentResize(scopeEl) { 97 if (!scopeEl || typeof global.ResizeObserver !== 'function') return; 98 if (fitResizeObserver) { 99 fitResizeObserver.disconnect(); 100 fitResizeObserver = null; 101 } 102 fitScopeEl = scopeEl; 103 var root = scopeEl.querySelector('#daw-app-root'); 104 if (!root) return; 105 106 fitResizeObserver = new global.ResizeObserver(function () { 107 scheduleFitWindow(fitScopeEl); 108 }); 109 fitResizeObserver.observe(root); 110 } 111 112 function clearContentResizeWatch() { 113 if (fitResizeObserver) { 114 fitResizeObserver.disconnect(); 115 fitResizeObserver = null; 116 } 117 fitScopeEl = null; 118 } 119 120 function init(scopeEl) { 121 if (!global.DawPage) return Promise.resolve(); 122 return global.DawPage.init(scopeEl).then(function () { 123 watchWindowFocus(scopeEl); 124 watchContentResize(scopeEl); 125 scheduleFitWindow(scopeEl); 126 }); 127 } 128 129 function destroy(scopeEl) { 130 if (fitTimer) { 131 global.clearTimeout(fitTimer); 132 fitTimer = null; 133 } 134 clearContentResizeWatch(); 135 clearFocusWatch(); 136 if (!global.DawPage) return Promise.resolve(); 137 return global.DawPage.destroy(scopeEl); 138 } 139 140 global.DawPageCitrus = { 141 init: init, 142 destroy: destroy, 143 scheduleFitWindow: scheduleFitWindow 144 }; 145})(typeof window !== 'undefined' ? window : this);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.