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