tricu

A lazy functional language based on Tree Calculus
Log | Files | Refs | README | LICENSE

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 }