/**
* @module
* Editing UI for `html:` blocks — a key/value attribute table.
*
* Previously this lived inside `createHtmlHandler(emmetAbbr)` and could have
* closed over the parsed Emmet abbreviation (tag/type). It never did: the
* editor only ever read `attrs` and reported back through `onCommit`, so
* moving it out to a single standalone editor shared by every `html:*` block
* type loses no behaviour. If a future revision needs the element tag, note
* that a `BlockEditor` is not given the block type string — it would have to
* come from `attrs` or from registering a distinct editor per type.
*/
import type { BlockEditor } from "./editor.type.js";
/** Editing UI for `html:` blocks — an editable attribute key/value table. */
export const htmlEditor: BlockEditor = (container, _raw, attrs, onCommit, onCancel, _toolbar) => {
const entries = Object.entries(attrs);
const rows: { key: string; valueInput: HTMLInputElement }[] = [];
const wrapper = document.createElement("div");
function addRow(key: string, value: string) {
const row = document.createElement("div");
row.style.display = "flex";
row.style.gap = "6px";
row.style.marginBottom = "4px";
const keyInput = document.createElement("input");
keyInput.type = "text";
keyInput.classList.add("edit-input");
keyInput.style.minHeight = "auto";
keyInput.style.flex = "0 0 35%";
keyInput.placeholder = "attribute";
keyInput.value = key;
const valueInput = document.createElement("input");
valueInput.type = "text";
valueInput.classList.add("edit-input");
valueInput.style.minHeight = "auto";
valueInput.placeholder = "value";
valueInput.value = value;
const entry = { key, valueInput };
rows.push(entry);
keyInput.addEventListener("input", () => {
entry.key = keyInput.value;
});
const onKey = (e: KeyboardEvent) => {
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
commit();
}
if (e.key === "Escape") {
e.preventDefault();
onCancel();
}
};
keyInput.addEventListener("keydown", onKey);
valueInput.addEventListener("keydown", onKey);
row.appendChild(keyInput);
row.appendChild(valueInput);
wrapper.appendChild(row);
}
for (const [k, v] of entries) {
addRow(k, v);
}
if (entries.length === 0) {
addRow("name", "");
}
const addBtn = document.createElement("button");
addBtn.textContent = "+ Add attribute";
addBtn.style.fontSize = "0.8em";
addBtn.style.cursor = "pointer";
addBtn.style.background = "none";
addBtn.style.border = "none";
addBtn.style.padding = "2px 0";
addBtn.style.color = "inherit";
addBtn.style.opacity = "0.7";
addBtn.addEventListener("click", (e) => {
e.stopPropagation();
addRow("", "");
const inputs = wrapper.querySelectorAll("input");
inputs[inputs.length - 2]?.focus();
});
function commit() {
const newAttrs: Record = {};
for (const row of rows) {
const k = row.key.trim();
if (k) newAttrs[k] = row.valueInput.value;
}
onCommit("", newAttrs);
}
container.innerHTML = "";
container.appendChild(wrapper);
container.appendChild(addBtn);
const firstValue = wrapper.querySelector("input:nth-child(2)");
firstValue?.focus();
};