emel
Version:
Create DOM elements with Emmet
167 lines (140 loc) • 3.79 kB
JavaScript
import emmet from "@emmetio/abbreviation";
function format(value, placeholders, isAttrName = false) {
if (Array.isArray(value)) {
value = value[0];
}
const origValue = value;
if (value in placeholders) {
const placeholder = placeholders[value];
if (Array.isArray(placeholder)) {
if (placeholder.length > 0) {
value = placeholder.shift();
}
} else {
value = placeholder;
}
}
if (isAttrName) {
if (value === false || value === null || typeof value === "undefined") {
return false;
} else if (value === true) {
return origValue;
}
}
return value;
}
function createElementFromNode(options) {
return (node) => {
if (node.value && !node.name && !node.attributes && node.children.length === 0) {
return options.doc.createTextNode(format(node.value, options.placeholders));
}
const tag = format(node.name || "div", options.placeholders);
const el = tag instanceof Node ? tag : options.doc.createElement(tag);
if (node.attributes) {
node.attributes.forEach(attr => {
if (attr.boolean || typeof attr.value === "undefined") {
const name = format(attr.name, options.placeholders, true);
if (name !== false) {
el.setAttribute(name, "");
}
} else {
const resolveAttrVal = () => format(attr.value, options.placeholders);
switch (attr.name) {
case "id":
el.setAttribute(attr.name, resolveAttrVal());
break;
case "class":
el.classList.add(resolveAttrVal());
break;
default: {
const name = format(attr.name, options.placeholders, true);
const value = resolveAttrVal();
if (name !== false) {
el.setAttribute(name, value);
}
}
}
}
});
}
if (node.value) {
const text = format(node.value, options.placeholders);
if (text instanceof Node) {
el.appendChild(text);
} else {
el.textContent = text;
}
}
node.children
.map(createElementFromNode(options))
.forEach(child => el.appendChild(child));
return el;
};
}
const defaultOptions = {
returnSingleChild: false,
multiline: false,
placeholders: {},
};
function isPlaceholderObject(obj) {
return (
typeof obj === "string" ||
typeof obj === "number" ||
typeof obj === "boolean" ||
Array.isArray(obj) ||
obj instanceof Node
);
}
function getOptions(opts, defaults = defaultOptions) {
if (isPlaceholderObject(opts)) {
opts = { placeholders: { "?": opts } };
}
const options = { ...defaults, ...opts };
if ("placeholders" in opts) {
if (isPlaceholderObject(options.placeholders)) {
options.placeholders = {
"?": options.placeholders,
};
} else {
options.placeholders = { ...options.placeholders };
}
for (const prop in options.placeholders) {
if (Array.isArray(options.placeholders[prop])) {
options.placeholders[prop] = [...options.placeholders[prop]];
}
}
}
if (!options.doc) {
options.doc = document;
}
return options;
}
export default function emel(str = "", options = {}) {
if (this instanceof emel) {
let opts = defaultOptions;
if (str && typeof str === "object") {
opts = getOptions(str);
}
this.emel = (s = "", o = {}) => {
return emel(s, getOptions(o, opts));
};
return;
}
options = getOptions(options);
if ("?" in options.placeholders) {
// escape unescaped questionmarks
str = str.replace(/(^|[^\\])(\\\\)*\?/g, "$1$2\\?");
}
if (options.multiline) {
str = str.split("\n").map(line => line.trim()).join("");
}
const tree = emmet(str);
const children = tree.children.map(createElementFromNode(options));
if (options.returnSingleChild && children.length === 1) {
return children[0];
}
return children.reduce((el, child) => {
el.appendChild(child);
return el;
}, options.doc.createDocumentFragment());
}