src/domains/fill-engine.ts (view raw)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
export type FillOptions = {
readonly rejectSelect: boolean;
readonly focusFirst: boolean;
};
const ENGINE = `
const el = this;
if (!el || el.nodeType !== 1) return { ok: false, reason: "ref does not point to an element" };
const tagName = el.tagName || "";
const tag = tagName.toLowerCase();
const type = (el.type || "").toLowerCase();
if (__rejectSelect && tag === "select") {
return { ok: false, reason: "element is a <select>", kind: "select", tag: tagName };
}
if (el.disabled) return { ok: false, reason: "element is disabled", tag: tagName };
const fire = function (t) { el.dispatchEvent(new Event(t, { bubbles: true })); };
if (__focusFirst) { try { el.focus(); } catch (e) {} }
if (tag === "select") {
const want = String(value);
let matched = null;
for (let i = 0; i < el.options.length; i++) {
const o = el.options[i];
if (o.value === want || o.label === want || o.text === want) { matched = o; break; }
}
if (!matched) {
const opts = [];
for (let i = 0; i < el.options.length; i++) opts.push({ value: el.options[i].value, text: el.options[i].text });
return { ok: false, reason: "no matching option", kind: "select", options: opts, tag: tagName };
}
el.value = matched.value;
fire("input"); fire("change");
return { ok: true, kind: "select", value: el.value, text: matched.text, tag: tagName };
}
if (tag === "input" && (type === "checkbox" || type === "radio")) {
const want = value === true || value === "true" || value === "on" || value === 1;
let changed = false;
if (el.checked !== want) {
try { el.click(); } catch (e) {}
if (el.checked !== want) { el.checked = want; fire("input"); fire("change"); }
changed = true;
}
return { ok: true, kind: type, checked: el.checked, changed: changed, tag: tagName };
}
if (el.isContentEditable) {
try { el.focus(); } catch (e) {}
let done = false;
try {
const sel = window.getSelection();
if (sel) { sel.removeAllRanges(); const rng = document.createRange(); rng.selectNodeContents(el); sel.addRange(rng); }
done = document.execCommand("insertText", false, String(value));
} catch (e) { done = false; }
if (!done) {
el.textContent = String(value);
el.dispatchEvent(new InputEvent("input", { bubbles: true }));
}
return { ok: true, kind: "contenteditable", value: el.textContent || "", tag: tagName };
}
if (tag === "input" || tag === "textarea") {
const proto = tag === "textarea" ? window.HTMLTextAreaElement.prototype : window.HTMLInputElement.prototype;
const desc = Object.getOwnPropertyDescriptor(proto, "value");
if (desc && desc.set) desc.set.call(el, String(value));
else el.value = String(value);
fire("input"); fire("change");
return { ok: true, kind: el.type || tag, value: el.value, tag: tagName };
}
return { ok: false, reason: "element is not a fillable field (tag=" + tag + ")", tag: tagName };
`;
export const fillBody = (opts: FillOptions): string =>
`const __rejectSelect = ${opts.rejectSelect}; const __focusFirst = ${opts.focusFirst};\n${ENGINE}`;
export const fillDeclaration = (opts: FillOptions): string =>
`function (value) { ${fillBody(opts)} }`;
|