1/** 2 * CitrusOS tool lifecycle: render â bind â destroy inside window mounts. 3 * Tools export init(scopeEl) / destroy(scopeEl); init calls render then bind. 4 */ 5(function (global) { 6 'use strict'; 7 8 function findRouteMount(root) { 9 if (!root || !root.querySelector) return null; 10 return root.querySelector('[data-citrus-mount]') || 11 root.querySelector('#content') || 12 null; 13 } 14 15 function findRouteContainer(scopeEl) { 16 var mount = getMount(scopeEl); 17 if (!mount) return null; 18 if (mount.classList && mount.classList.contains('citrus-window__content')) return mount; 19 return mount.closest ? mount.closest('.citrus-window__content') : null; 20 } 21 22 function attachPrefs(refs, scopeEl) { 23 if (!refs) return; 24 if (!global.CitrusPrefs) { 25 refs.prefs = { settings: {}, save: function () { return refs.prefs.settings; } }; 26 return; 27 } 28 var routeId = null; 29 if (global.CitrusWindowLoader && typeof global.CitrusWindowLoader.getRouteIdForContainer === 'function') { 30 routeId = global.CitrusWindowLoader.getRouteIdForContainer(findRouteContainer(scopeEl)); 31 } 32 refs.prefs = global.CitrusPrefs.createAppContext(routeId); 33 } 34 35 function getMount(scopeEl) { 36 if (!scopeEl) { 37 if (global.CitrusWindowLoader && typeof global.CitrusWindowLoader.getFocusedContainer === 'function') { 38 scopeEl = global.CitrusWindowLoader.getFocusedContainer(); 39 } 40 if (!scopeEl && global.CitrusRouteScope && typeof global.CitrusRouteScope.getActiveScope === 'function') { 41 scopeEl = global.CitrusRouteScope.getActiveScope(); 42 } 43 } 44 if (!scopeEl) return null; 45 46 if (scopeEl.matches && scopeEl.matches('[data-citrus-mount]')) return scopeEl; 47 if (scopeEl.id === 'content') return scopeEl; 48 49 var appRoot = scopeEl.querySelector('[data-citrus-app-root]'); 50 if (appRoot) { 51 var nested = findRouteMount(appRoot); 52 return nested || appRoot; 53 } 54 55 var content = findRouteMount(scopeEl); 56 return content || scopeEl; 57 } 58 59 function queryById(id, scopeEl) { 60 if (!id) return null; 61 var mount = getMount(scopeEl); 62 if (mount && mount.querySelector) { 63 var scoped = mount.querySelector('#' + id); 64 if (scoped) return scoped; 65 } 66 return document.getElementById(id); 67 } 68 69 /** 70 * @param {object} spec 71 * @param {function(HTMLElement): object} spec.render 72 * @param {function(object, HTMLElement): void} [spec.bind] 73 * @param {function(object): void} [spec.teardown] 74 */ 75 function create(spec) { 76 var state = { refs: null, mount: null, bound: false, bindInFlight: null }; 77 78 function destroy(scopeEl) { 79 if (spec.teardown && state.refs) { 80 try { spec.teardown(state.refs); } catch (e) { console.warn('[CitrusToolContract] teardown', e); } 81 } 82 var mount = state.mount || getMount(scopeEl); 83 if (mount) mount.innerHTML = ''; 84 state.refs = null; 85 state.mount = null; 86 state.bound = false; 87 state.bindInFlight = null; 88 } 89 90 function init(scopeEl) { 91 var mount = getMount(scopeEl); 92 if (!mount) return Promise.resolve(); 93 if (state.bindInFlight) return state.bindInFlight; 94 if (state.bound) destroy(scopeEl); 95 96 mount.innerHTML = ''; 97 state.mount = mount; 98 state.refs = spec.render(mount) || {}; 99 attachPrefs(state.refs, scopeEl); 100 if (typeof spec.bind === 'function') { 101 var bindResult = spec.bind(state.refs, mount); 102 if (bindResult && typeof bindResult.then === 'function') { 103 state.bindInFlight = bindResult.then(function () { 104 state.bound = true; 105 }).catch(function (e) { 106 state.bound = false;
107 console.warn('[CitrusToolContract] bind failed', e); 108 }).finally(function () { 109 state.bindInFlight = null; 110 }); 111 return state.bindInFlight; 112 } 113 state.bound = true; 114 return Promise.resolve(); 115 } 116 state.bound = true; 117 return Promise.resolve(); 118 } 119 120 return { 121 init: init, 122 destroy: destroy, 123 render: spec.render, 124 bind: spec.bind, 125 getMount: getMount 126 }; 127 } 128 129 global.CitrusToolContract = { 130 create: create, 131 getMount: getMount, 132 queryById: queryById 133 }; 134})(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.