tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

arguments-ui.ts (3768B)


      1 import { literals, type Literal } from './codecs.js';
      2 import { parseLiteral, printLiteral } from './literal-input.js';
      3 
      4 export function argumentEditor(raw: HTMLTextAreaElement) {
      5   const container = document.getElementById('argument-fields')!;
      6   const add = document.getElementById('add-argument') as HTMLButtonElement;
      7   const rawMode = document.getElementById('raw-arguments') as HTMLInputElement;
      8   let snapshot = '', disabled = false;
      9   let labels: readonly string[] = [];
     10   let rows: { input: HTMLTextAreaElement; original?: Literal; text: string }[] = [];
     11   function render(): void {
     12     container.replaceChildren();
     13     if (!rows.length) { const hint = document.createElement('p'); hint.className = 'hint'; hint.textContent = 'No inputs supplied. Add an input to apply this definition.'; container.append(hint); }
     14     rows.forEach((row, i) => {
     15       const wrapper = document.createElement('div'); wrapper.className = 'argument-row';
     16       const label = document.createElement('label'); label.htmlFor = `argument-${i}`; label.textContent = labels[i] ?? `Argument ${i + 1}`;
     17       row.input.id = label.htmlFor; row.input.rows = 2; row.input.maxLength = 16000; row.input.spellcheck = false; row.input.disabled = disabled;
     18       const remove = document.createElement('button'); remove.type = 'button'; remove.textContent = 'Remove'; remove.disabled = disabled;
     19       remove.setAttribute('aria-label', `Remove argument ${i + 1}`);
     20       remove.onclick = () => { rows.splice(i, 1); render(); sync(); };
     21       row.input.oninput = sync;
     22       wrapper.append(label, row.input, remove); container.append(wrapper);
     23     });
     24     container.hidden = rawMode.checked; add.hidden = rawMode.checked; add.disabled = disabled || rows.length >= 16;
     25     raw.hidden = !rawMode.checked; rawMode.disabled = disabled;
     26   }
     27   function values(): Literal[] {
     28     return literals(rows.map(row => row.original && row.input.value === row.text ? row.original : parseLiteral(row.input.value)));
     29   }
     30   function sync(): void {
     31     try { raw.value = JSON.stringify(values()); snapshot = raw.value; } catch { /* Retain invalid field text for correction; read reports it. */ }
     32   }
     33   function refresh(reset = false): void {
     34     if (reset) rawMode.checked = false;
     35     if (!reset && raw.value === snapshot) { render(); return; }
     36     try {
     37       rows = literals(JSON.parse(raw.value) as unknown).map(original => {
     38         const input = document.createElement('textarea'), text = printLiteral(original); input.value = text; return { input, original, text };
     39       });
     40       snapshot = raw.value;
     41     } catch { rawMode.checked = true; }
     42     render();
     43   }
     44   add.onclick = () => { const input = document.createElement('textarea'); input.value = 't'; rows.push({ input, text: 't' }); render(); sync(); input.focus(); };
     45   raw.oninput = () => { snapshot = ''; };
     46   rawMode.onchange = () => {
     47     if (!rawMode.checked) refresh();
     48     else {
     49       try { raw.value = JSON.stringify(values()); snapshot = raw.value; }
     50       catch (error) {
     51         rawMode.checked = false;
     52         document.getElementById('result')!.textContent = `Correct the argument before switching editors: ${error instanceof Error ? error.message : String(error)}`;
     53       }
     54     }
     55     render();
     56   };
     57   refresh();
     58   return {
     59     refresh,
     60     labels(value: readonly string[]) { labels = value; },
     61     disable(value: boolean) { disabled = value; refresh(); },
     62     read(): Literal[] {
     63       if (raw.value.length > 16000) throw new Error('Argument text limit exceeded');
     64       if (rawMode.checked || raw.value !== snapshot) return literals(JSON.parse(raw.value) as unknown);
     65       const result = values();
     66       if (JSON.stringify(result).length > 16000) throw new Error('Argument text limit exceeded');
     67       sync(); return result;
     68     },
     69   };
     70 }