1/** 2 * Shared DOM builders for CitrusOS tool UIs (retro panels, toolbars, forms). 3 */ 4(function (global) { 5 'use strict'; 6 7 function el(tag, className, attrs) { 8 var node = document.createElement(tag); 9 if (className) node.className = className; 10 if (attrs) { 11 Object.keys(attrs).forEach(function (key) { 12 if (key === 'text') node.textContent = attrs[key]; 13 else if (key === 'html') node.innerHTML = attrs[key]; 14 else if (key === 'id') node.id = attrs[key]; 15 else node.setAttribute(key, attrs[key]); 16 }); 17 } 18 return node; 19 } 20 21 function icon(name, label) { 22 var i = el('i', 'fa-solid ' + name, { 'aria-hidden': 'true' }); 23 if (label) { 24 var wrap = el('span', 'citrus-tool-icon-label'); 25 wrap.appendChild(i); 26 wrap.appendChild(document.createTextNode(' ' + label)); 27 return wrap; 28 } 29 return i; 30 } 31 32 function appShell(mount, className) { 33 var root = el('div', 'citrus-tool-app' + (className ? ' ' + className : '')); 34 mount.appendChild(root); 35 return root; 36 } 37 38 function header(title, iconClass) { 39 var h = el('header', 'citrus-tool-header'); 40 var h1 = el('h1', 'citrus-tool-title'); 41 if (iconClass) h1.appendChild(icon(iconClass)); 42 h1.appendChild(document.createTextNode(title)); 43 h.appendChild(h1); 44 return h; 45 } 46 47 function panel(className) { 48 return el('section', 'citrus-tool-panel tool-widget' + (className ? ' ' + className : '')); 49 } 50 51 function sectionTitle(text, iconClass) { 52 var h = el('h2', 'citrus-tool-section-title tool-section-title'); 53 if (iconClass) h.appendChild(icon(iconClass)); 54 h.appendChild(document.createTextNode(text)); 55 return h; 56 } 57 58 function toolbar(className) { 59 return el('div', 'citrus-tool-toolbar' + (className ? ' ' + className : '')); 60 } 61 62 function scrollRegion(className) { 63 return el('div', 'citrus-tool-scroll' + (className ? ' ' + className : '')); 64 } 65 66 function btn(label, opts) { 67 opts = opts || {}; 68 var tag = opts.href ? 'a' : 'button'; 69 var node = el(tag, 'citrus-tool-btn tool-btn' + (opts.className ? ' ' + opts.className : '')); 70 if (tag === 'button') node.type = opts.type || 'button';
71 if (opts.href) node.href = opts.href; 72 if (opts.id) node.id = opts.id; 73 if (opts.ariaLabel) node.setAttribute('aria-label', opts.ariaLabel); 74 if (opts.icon) node.appendChild(icon(opts.icon)); 75 node.appendChild(document.createTextNode(label)); 76 return node; 77 } 78 79 function tabBar(tabs, activeId) { 80 var bar = el('div', 'citrus-tool-tabs', { role: 'tablist' }); 81 tabs.forEach(function (tab) { 82 var b = el('button', 'citrus-tool-tab tab-button' + (tab.id === activeId ? ' active' : ''), { 83 type: 'button', 84 role: 'tab', 85 'aria-selected': tab.id === activeId ? 'true' : 'false', 86 'data-tab': tab.id 87 }); 88 if (tab.icon) b.appendChild(icon(tab.icon)); 89 b.appendChild(document.createTextNode(tab.label)); 90 bar.appendChild(b); 91 }); 92 return bar; 93 } 94 95 function formRow(labelText, control) { 96 var row = el('label', 'citrus-tool-form-row'); 97 var lbl = el('span', 'citrus-tool-form-label tool-label', { text: labelText }); 98 row.appendChild(lbl); 99 row.appendChild(control); 100 return row; 101 } 102 103 function fieldInput(type, id, attrs) { 104 var input = el('input', 'citrus-tool-input tool-select', attrs || {}); 105 input.type = type || 'text'; 106 if (id) input.id = id; 107 return input; 108 } 109 110 function hint(text) { 111 return el('p', 'citrus-tool-hint tool-hint', { text: text }); 112 } 113 114 function grid(className) { 115 return el('div', 'citrus-tool-grid tool-grid' + (className ? ' ' + className : '')); 116 } 117 118 function layoutSplit(sidebar, main) { 119 var wrap = el('div', 'citrus-tool-split'); 120 if (sidebar) { 121 sidebar.classList.add('citrus-tool-split__side'); 122 sidebar.id = sidebar.id || 'sidebar'; 123 wrap.appendChild(sidebar); 124 } 125 if (main) { 126 main.classList.add('citrus-tool-split__main'); 127 if (main.tagName === 'MAIN' || !main.tagName) { 128 if (main.tagName !== 'MAIN') { 129 var m = el('main'); 130 m.appendChild(main); 131 main = m; 132 } 133 } 134 wrap.appendChild(main); 135 } 136 return wrap; 137 } 138 139 global.CitrusToolUi = { 140 el: el, 141 icon: icon, 142 appShell: appShell, 143 header: header, 144 panel: panel, 145 sectionTitle: sectionTitle, 146 toolbar: toolbar, 147 scrollRegion: scrollRegion, 148 btn: btn, 149 tabBar: tabBar, 150 formRow: formRow, 151 fieldInput: fieldInput, 152 hint: hint, 153 grid: grid, 154 layoutSplit: layoutSplit 155 }; 156})(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.