1/** 2 * CitrusOS desktop icon selection â marquee box, multi-select, keyboard helpers. 3 */ 4(function (global) { 5 'use strict'; 6 7 var MARQUEE_THRESHOLD = 6; 8 var desktopEl = null; 9 var marqueeEl = null; 10 /** @type {Record<string, boolean>} */ 11 var selected = {}; 12 var anchorId = null; 13 14 function getOrderedShortcutIds() { 15 if (!global.CitrusDesktopShortcuts) return []; 16 var list = global.CitrusDesktopShortcuts.listVisibleShortcuts().slice(); 17 list.sort(function (a, b) { 18 if (a.y !== b.y) return a.y - b.y; 19 return a.x - b.x; 20 }); 21 var ids = []; 22 for (var i = 0; i < list.length; i++) ids.push(list[i].id); 23 return ids; 24 } 25 26 function getSelectedIds() { 27 return Object.keys(selected); 28 } 29 30 function getSelectedCount() { 31 return Object.keys(selected).length; 32 } 33 34 function isSelected(id) { 35 return !!selected[id]; 36 } 37 38 function syncDom() { 39 if (!desktopEl) return; 40 var icons = desktopEl.querySelectorAll('.citrus-desktop-icon'); 41 for (var i = 0; i < icons.length; i++) { 42 var ic = icons[i]; 43 var sid = ic.getAttribute('data-shortcut-id'); 44 ic.classList.toggle('citrus-desktop-icon--selected', !!selected[sid]); 45 } 46 } 47 48 function clearSelection() { 49 selected = {}; 50 anchorId = null; 51 syncDom(); 52 } 53 54 function setSelection(ids, opts) { 55 opts = opts || {}; 56 if (!opts.add) selected = {}; 57 for (var i = 0; i < ids.length; i++) { 58 if (ids[i]) selected[ids[i]] = true; 59 } 60 if (ids.length && !opts.keepAnchor) { 61 anchorId = ids[0]; 62 } 63 syncDom(); 64 } 65 66 function selectOnly(id) { 67 selected = {}; 68 if (id) { 69 selected[id] = true; 70 anchorId = id; 71 } else { 72 anchorId = null; 73 } 74 syncDom(); 75 } 76 77 function toggleSelection(id) { 78 if (!id) return; 79 if (selected[id]) { 80 delete selected[id]; 81 if (anchorId === id) anchorId = getSelectedIds()[0] || null; 82 } else { 83 selected[id] = true; 84 anchorId = id; 85 } 86 syncDom(); 87 } 88 89 function selectRange(toId) { 90 if (!toId) return; 91 var ordered = getOrderedShortcutIds(); 92 var fromId = anchorId || toId; 93 var fromIdx = ordered.indexOf(fromId); 94 var toIdx = ordered.indexOf(toId); 95 if (fromIdx === -1) fromIdx = toIdx; 96 if (toIdx === -1) return; 97 var lo = Math.min(fromIdx, toIdx); 98 var hi = Math.max(fromIdx, toIdx); 99 selected = {}; 100 for (var i = lo; i <= hi; i++) selected[ordered[i]] = true; 101 anchorId = fromId; 102 syncDom(); 103 } 104 105 function selectAll() { 106 setSelection(getOrderedShortcutIds()); 107 } 108 109 function handleIconPointerDown(id, ev) { 110 if (ev.button !== 0 || !id) return; 111 if (ev.shiftKey) { 112 selectRange(id); 113 return; 114 } 115 if (ev.ctrlKey || ev.metaKey) { 116 toggleSelection(id); 117 return; 118 } 119 if (!isSelected(id) || getSelectedCount() === 1) { 120 selectOnly(id); 121 } 122 } 123 124 function rectsIntersect(a, b) { 125 return a.left < b.right && a.right > b.left && a.top < b.bottom && a.bottom > b.top; 126 } 127 128 function iconsInMarquee(x1, y1, x2, y2) { 129 var selRect = { 130 left: Math.min(x1, x2), 131 top: Math.min(y1, y2), 132 right: Math.max(x1, x2), 133 bottom: Math.max(y1, y2) 134 }; 135 var hits = []; 136 if (!desktopEl) return hits; 137 var icons = desktopEl.querySelectorAll('.citrus-desktop-icon'); 138 for (var i = 0; i < icons.length; i++) { 139 var r = icons[i].getBoundingClientRect(); 140 if (rectsIntersect(selRect, r)) { 141 hits.push(icons[i].getAttribute('data-shortcut-id')); 142 } 143 } 144 return hits; 145 } 146 147 function updateMarqueeRect(x1, y1, x2, y2) { 148 if (!marqueeEl || !desktopEl) return; 149 var deskRect = desktopEl.getBoundingClientRect(); 150 var left = Math.min(x1, x2) - deskRect.left; 151 var top = Math.min(y1, y2) - deskRect.top; 152 var width = Math.abs(x2 - x1); 153 var height = Math.abs(y2 - y1); 154 marqueeEl.style.left = left + 'px'; 155 marqueeEl.style.top = top + 'px'; 156 marqueeEl.style.width = width + 'px'; 157 marqueeEl.style.height = height + 'px'; 158 marqueeEl.hidden = !(width > 0 && height > 0); 159 } 160 161 function ensureMarqueeEl() { 162 if (marqueeEl || !desktopEl) return; 163 marqueeEl = document.createElement('div'); 164 marqueeEl.className = 'citrus-desktop-marquee'; 165 marqueeEl.hidden = true; 166 marqueeEl.setAttribute('aria-hidden', 'true'); 167 desktopEl.appendChild(marqueeEl); 168 } 169 170 function bindMarquee() { 171 if (!desktopEl) return; 172 173 desktopEl.addEventListener('pointerdown', function (ev) { 174 if (ev.button !== 0) return; 175 if (ev.target.closest('.citrus-desktop-icon')) return; 176 if (global.CitrusDesktopDnD && global.CitrusDesktopDnD.isDragActive && 177 global.CitrusDesktopDnD.isDragActive()) { 178 return; 179 } 180 181 var startX = ev.clientX; 182 var startY = ev.clientY; 183 var moved = false;
184 var marqueeActive = false; 185 var addMode = ev.ctrlKey || ev.metaKey; 186 var baseSelection = addMode ? getSelectedIds().slice() : []; 187 188 function applyMarqueeHits(clientX, clientY) { 189 var hits = iconsInMarquee(startX, startY, clientX, clientY); 190 if (addMode) { 191 var merged = baseSelection.slice(); 192 for (var i = 0; i < hits.length; i++) { 193 if (merged.indexOf(hits[i]) === -1) merged.push(hits[i]); 194 } 195 setSelection(merged, { keepAnchor: true }); 196 } else { 197 setSelection(hits, { keepAnchor: true }); 198 } 199 if (hits.length) anchorId = hits[0]; 200 } 201 202 function onMove(e) { 203 var dx = e.clientX - startX; 204 var dy = e.clientY - startY; 205 if (!moved && (Math.abs(dx) > MARQUEE_THRESHOLD || Math.abs(dy) > MARQUEE_THRESHOLD)) { 206 moved = true; 207 marqueeActive = true; 208 ensureMarqueeEl(); 209 if (!addMode) clearSelection(); 210 desktopEl.classList.add('citrusos-desktop--marquee-active'); 211 if (marqueeEl) marqueeEl.hidden = false; 212 if (desktopEl.setPointerCapture) desktopEl.setPointerCapture(ev.pointerId); 213 } 214 if (!marqueeActive) return; 215 e.preventDefault(); 216 updateMarqueeRect(startX, startY, e.clientX, e.clientY); 217 applyMarqueeHits(e.clientX, e.clientY); 218 } 219 220 function onUp(e) { 221 if (desktopEl.hasPointerCapture && desktopEl.hasPointerCapture(ev.pointerId)) { 222 desktopEl.releasePointerCapture(ev.pointerId); 223 } 224 document.removeEventListener('pointermove', onMove); 225 document.removeEventListener('pointerup', onUp); 226 document.removeEventListener('pointercancel', onUp); 227 desktopEl.classList.remove('citrusos-desktop--marquee-active'); 228 if (marqueeEl) marqueeEl.hidden = true; 229 if (!moved && !addMode) clearSelection(); 230 } 231 232 document.addEventListener('pointermove', onMove); 233 document.addEventListener('pointerup', onUp); 234 document.addEventListener('pointercancel', onUp); 235 }); 236 } 237 238 function init(el) { 239 desktopEl = el; 240 ensureMarqueeEl(); 241 bindMarquee(); 242 } 243 244 function restoreSelection(ids) { 245 selected = {}; 246 for (var i = 0; i < ids.length; i++) { 247 if (ids[i]) selected[ids[i]] = true; 248 } 249 anchorId = ids[0] || null; 250 syncDom(); 251 } 252 253 global.CitrusDesktopSelection = { 254 init: init, 255 clear: clearSelection, 256 selectOnly: selectOnly, 257 toggle: toggleSelection, 258 selectRange: selectRange, 259 selectAll: selectAll, 260 getSelectedIds: getSelectedIds, 261 getSelectedCount: getSelectedCount, 262 isSelected: isSelected, 263 syncDom: syncDom, 264 handleIconPointerDown: handleIconPointerDown, 265 restoreSelection: restoreSelection, 266 getAnchorId: function () { return anchorId; } 267 }; 268})(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.