module-inspector.js (5359B)
1 import { importSites } from './import-folds.js'; 2 import { loadLibraries, renderLibraryExports } from './library-browser.js'; 3 export function moduleInspector(input, local) { 4 const card = document.createElement('section'); 5 card.className = 'definition-card module-card'; 6 card.hidden = true; 7 card.setAttribute('aria-label', 'Imported module'); 8 const heading = document.createElement('div'); 9 heading.className = 'definition-card-controls'; 10 const title = document.createElement('strong'), close = document.createElement('button'); 11 close.type = 'button'; 12 close.textContent = '×'; 13 close.className = 'definition-card-close'; 14 close.setAttribute('aria-label', 'Close module inspection'); 15 const search = document.createElement('input'); 16 search.type = 'search'; 17 search.placeholder = 'Filter definitions…'; 18 search.setAttribute('aria-label', 'Search module definitions'); 19 const list = document.createElement('div'); 20 list.className = 'module-exports'; 21 const status = document.createElement('p'); 22 status.className = 'hint'; 23 status.setAttribute('role', 'status'); 24 const identity = document.createElement('div'); 25 identity.className = 'inspection-title'; 26 const kind = document.createElement('span'); 27 kind.className = 'inspection-kind'; 28 kind.textContent = 'Imported module'; 29 const description = document.createElement('p'); 30 description.className = 'module-description'; 31 description.hidden = true; 32 identity.append(kind, title); 33 heading.append(identity, close); 34 card.append(heading, description, search, list, status); 35 input.dom.append(card); 36 let selected, names = [], revision = 0; 37 function hide() { revision++; selected = undefined; card.hidden = true; } 38 function position() { 39 if (!selected) 40 return; 41 const site = importSites(input.view.state).find(site => site.from === selected.from && site.hash === selected.hash); 42 if (!site || input.view.state.doc.lineAt(input.view.state.selection.main.head).number !== site.line) { 43 hide(); 44 return; 45 } 46 const rect = input.view.coordsAtPos(site.to), height = input.dom.clientHeight; 47 card.style.maxHeight = `${Math.max(80, Math.min(height - 16, innerHeight * .75, 400))}px`; 48 const top = rect ? rect.bottom - input.dom.getBoundingClientRect().top + 4 : 8; 49 card.style.top = `${Math.max(8, Math.min(top, height - card.offsetHeight - 8))}px`; 50 } 51 function render() { 52 renderLibraryExports(list, names, search.value, name => { 53 void navigator.clipboard.writeText(name).then(() => { status.textContent = 'Definition name copied.'; }, () => { status.textContent = name; }); 54 }); 55 position(); 56 } 57 async function show(target) { 58 const hash = target.dataset.importHash; 59 const site = importSites(input.view.state).find(site => site.hash === hash && site.line === input.view.state.doc.lineAt(input.view.state.selection.main.head).number); 60 if (!site) 61 return; 62 const current = ++revision; 63 selected = { hash: site.hash, from: site.from }; 64 names = []; 65 search.value = ''; 66 list.replaceChildren(); 67 description.hidden = true; 68 description.textContent = ''; 69 title.textContent = 'Imported module'; 70 status.textContent = 'Loading module definitions…'; 71 card.hidden = false; 72 position(); 73 search.focus({ preventScroll: true }); 74 try { 75 const nearby = local().find(row => row.manifest === hash); 76 const published = nearby ? undefined : (await loadLibraries()).find(row => row.manifest === hash); 77 if (current !== revision) 78 return; 79 title.textContent = nearby?.label ?? published?.title ?? 'Imported module'; 80 description.textContent = published?.description ?? (nearby ? 'Local module' : ''); 81 description.hidden = !description.textContent; 82 names = nearby?.names ?? published?.names ?? []; 83 status.textContent = nearby || published ? `${names.length} definitions · Click a name to copy it.` : 'This module is not in the library index or local modules.'; 84 render(); 85 } 86 catch (error) { 87 if (current === revision) 88 status.textContent = `Could not load module definitions: ${String(error)}`; 89 } 90 } 91 input.dom.addEventListener('click', event => { 92 const target = event.target.closest('.import-hash-full'); 93 if (target && event.detail <= 1) 94 void show(target); 95 }); 96 input.dom.addEventListener('keydown', event => { 97 const target = event.target.closest('.import-hash-full'); 98 if (target && (event.key === 'Enter' || event.key === ' ')) { 99 event.preventDefault(); 100 event.stopPropagation(); 101 void show(target); 102 } 103 }, true); 104 function dismiss() { hide(); input.focus(); } 105 close.onclick = dismiss; 106 card.addEventListener('keydown', event => { if (event.key === 'Escape') { 107 event.preventDefault(); 108 dismiss(); 109 } }); 110 search.oninput = render; 111 input.onLayout(position); 112 input.onChange(hide); 113 new ResizeObserver(position).observe(card); 114 return { hide }; 115 }