@skirtle/vue-vnode-utils
Version:
Utilities for manipulating Vue 3 VNodes
346 lines (345 loc) • 10.1 kB
JavaScript
import { Comment, Fragment, Static, Text, cloneVNode, createCommentVNode, createTextVNode, isVNode } from "vue";
//#region src/base.ts
const isComment = (vnode) => {
return getType(vnode) === "comment";
};
const isComponent = (vnode) => {
return getType(vnode) === "component";
};
const isElement = (vnode) => {
return getType(vnode) === "element";
};
const isFragment = (vnode) => {
return getType(vnode) === "fragment";
};
const isFunctionalComponent = (vnode) => {
return isComponent(vnode) && typeof vnode.type === "function";
};
const isStatefulComponent = (vnode) => {
return isComponent(vnode) && typeof vnode.type === "object";
};
const isStatic = (vnode) => {
return getType(vnode) === "static";
};
const isText = (vnode) => {
return getType(vnode) === "text";
};
const getText = (vnode) => {
if (typeof vnode === "string") return vnode;
if (typeof vnode === "number") return String(vnode);
if (isVNode(vnode) && vnode.type === Text) return String(vnode.children);
};
const getType = (vnode) => {
const typeofVNode = typeof vnode;
if (vnode == null || typeofVNode === "boolean") return "comment";
else if (typeofVNode === "string" || typeofVNode === "number") return "text";
else if (Array.isArray(vnode)) return "fragment";
if (isVNode(vnode)) {
const { type } = vnode;
const typeofType = typeof type;
if (typeofType === "symbol") {
if (type === Fragment) return "fragment";
else if (type === Text) return "text";
else if (type === Comment) return "comment";
else if (type === Static) return "static";
} else if (typeofType === "string") return "element";
else if (typeofType === "object" || typeofType === "function") return "component";
}
};
//#endregion
//#region src/iterators.ts
const warn = (method, msg) => {
console.warn(`[${method}] ${msg}`);
};
const checkArguments = (method, passed, expected) => {
for (let index = 0; index < passed.length; ++index) {
const t = typeOf(passed[index]);
const expect = expected[index];
if (expect !== t) warn(method, `Argument ${index + 1} was ${t}, should be ${expect}`);
}
};
const isEmptyObject = (obj) => {
for (const prop in obj) return false;
return true;
};
const typeOf = (value) => {
let t = typeof value;
if (t === "object") {
if (value === null) t = "null";
else if (Array.isArray(value)) t = "array";
else if (isVNode(value)) t = "vnode";
else if (value instanceof Date) t = "date";
else if (value instanceof RegExp) t = "regexp";
}
return t;
};
const getFragmentChildren = (fragmentVNode) => {
if (Array.isArray(fragmentVNode)) return fragmentVNode;
const { children } = fragmentVNode;
if (Array.isArray(children)) return children;
warn("getFragmentChildren", `Unknown children for fragment: ${typeOf(children)}`);
return [];
};
function freeze(obj) {
return Object.freeze(obj);
}
const COMPONENTS_AND_ELEMENTS = /* @__PURE__ */ freeze({
element: true,
component: true
});
const SKIP_COMMENTS = /* @__PURE__ */ freeze({
element: true,
component: true,
text: true,
static: true
});
const ALL_VNODES = /* @__PURE__ */ freeze({
element: true,
component: true,
text: true,
static: true,
comment: true
});
const promoteToVNode = (node, options) => {
const type = getType(node);
if (!type || type === "fragment" || !options[type]) return null;
if (isVNode(node)) return node;
if (type === "text") return createTextVNode(getText(node));
return createCommentVNode();
};
const addProps = (children, callback, options = COMPONENTS_AND_ELEMENTS) => {
checkArguments("addProps", [
children,
callback,
options
], [
"array",
"function",
"object"
]);
return replaceChildrenInternal(children, (vnode) => {
const props = callback(vnode);
{
const typeofProps = typeOf(props);
if (![
"object",
"null",
"undefined"
].includes(typeofProps)) warn("addProps", `Callback returned unexpected ${typeofProps}: ${String(props)}`);
}
if (props && !isEmptyObject(props)) return cloneVNode(vnode, props, true);
}, options);
};
const replaceChildren = (children, callback, options = SKIP_COMMENTS) => {
checkArguments("replaceChildren", [
children,
callback,
options
], [
"array",
"function",
"object"
]);
return replaceChildrenInternal(children, callback, options);
};
const replaceChildrenInternal = (children, callback, options) => {
var _nc3;
let nc = null;
for (let index = 0; index < children.length; ++index) {
const child = children[index];
if (isFragment(child)) {
const oldFragmentChildren = getFragmentChildren(child);
const newFragmentChildren = replaceChildrenInternal(oldFragmentChildren, callback, options);
let newChild = child;
if (oldFragmentChildren !== newFragmentChildren) {
var _nc;
(_nc = nc) !== null && _nc !== void 0 || (nc = children.slice(0, index));
if (Array.isArray(child)) newChild = newFragmentChildren;
else {
newChild = cloneVNode(child);
newChild.children = newFragmentChildren;
}
}
nc && nc.push(newChild);
} else {
const vnode = promoteToVNode(child, options);
if (vnode) {
var _callback;
const newNodes = (_callback = callback(vnode)) !== null && _callback !== void 0 ? _callback : vnode;
{
const typeOfNewNodes = typeOf(newNodes);
if (![
"array",
"vnode",
"string",
"number",
"undefined"
].includes(typeOfNewNodes)) warn("replaceChildren", `Callback returned unexpected ${typeOfNewNodes} ${String(newNodes)}`);
}
if (newNodes !== child) {
var _nc2;
(_nc2 = nc) !== null && _nc2 !== void 0 || (nc = children.slice(0, index));
}
if (Array.isArray(newNodes)) nc && nc.push(...newNodes);
else nc && nc.push(newNodes);
} else nc && nc.push(child);
}
}
return (_nc3 = nc) !== null && _nc3 !== void 0 ? _nc3 : children;
};
const betweenChildren = (children, callback, options = SKIP_COMMENTS) => {
checkArguments("betweenChildren", [
children,
callback,
options
], [
"array",
"function",
"object"
]);
let previousVNode = null;
return replaceChildrenInternal(children, (vnode) => {
let insertedNodes = void 0;
if (previousVNode) {
insertedNodes = callback(previousVNode, vnode);
{
const typeOfInsertedNodes = typeOf(insertedNodes);
if (![
"array",
"vnode",
"string",
"number",
"undefined"
].includes(typeOfInsertedNodes)) warn("betweenChildren", `Callback returned unexpected ${typeOfInsertedNodes} ${String(insertedNodes)}`);
}
}
previousVNode = vnode;
if (insertedNodes == null || Array.isArray(insertedNodes) && insertedNodes.length === 0) return;
if (Array.isArray(insertedNodes)) return [...insertedNodes, vnode];
return [insertedNodes, vnode];
}, options);
};
const someChild = (children, callback, options = ALL_VNODES) => {
checkArguments("someChild", [
children,
callback,
options
], [
"array",
"function",
"object"
]);
return someChildInternal(children, callback, options);
};
const someChildInternal = (children, callback, options) => {
for (const child of children) if (isFragment(child)) {
if (someChild(getFragmentChildren(child), callback, options)) return true;
} else {
const vnode = promoteToVNode(child, options);
if (vnode && callback(vnode)) return true;
}
return false;
};
const everyChild = (children, callback, options = ALL_VNODES) => {
checkArguments("everyChild", [
children,
callback,
options
], [
"array",
"function",
"object"
]);
return !someChildInternal(children, (vnode) => !callback(vnode), options);
};
const eachChild = (children, callback, options = ALL_VNODES) => {
checkArguments("eachChild", [
children,
callback,
options
], [
"array",
"function",
"object"
]);
someChildInternal(children, (vnode) => {
callback(vnode);
}, options);
};
const findChild = (children, callback, options = ALL_VNODES) => {
checkArguments("findChild", [
children,
callback,
options
], [
"array",
"function",
"object"
]);
let node = void 0;
someChildInternal(children, (vnode) => {
if (callback(vnode)) {
node = vnode;
return true;
}
}, options);
return node;
};
const reduceChildren = (children, callback, initialValue, options = ALL_VNODES) => {
checkArguments("reduceChildren", [
children,
callback,
null,
options
], [
"array",
"function",
"null",
"object"
]);
someChildInternal(children, (vnode) => {
initialValue = callback(initialValue, vnode);
}, options);
return initialValue;
};
const COLLAPSIBLE_WHITESPACE_RE = /\S|\u00a0/;
const isEmpty = (children) => {
checkArguments("isEmpty", [children], ["array"]);
return !someChildInternal(children, (vnode) => {
if (isText(vnode)) {
const text = getText(vnode) || "";
return COLLAPSIBLE_WHITESPACE_RE.test(text);
}
return true;
}, SKIP_COMMENTS);
};
const extractSingleChild = (children) => {
checkArguments("extractSingleChild", [children], ["array"]);
const node = findChild(children, () => {
return true;
}, COMPONENTS_AND_ELEMENTS);
someChildInternal(children, (vnode) => {
let warning = "";
if (vnode === node) return false;
if (isElement(vnode) || isComponent(vnode)) warning = "Multiple root nodes found, only one expected";
else if (isText(vnode)) {
const text = getText(vnode) || "";
if (COLLAPSIBLE_WHITESPACE_RE.test(text)) warning = `Non-empty text node:\n'${text}'`;
} else warning = `Encountered unexpected ${getType(vnode)} VNode`;
if (warning) {
warn("extractSingleChild", warning);
return true;
}
}, SKIP_COMMENTS);
return node;
};
const countChildren = (children, options = ALL_VNODES) => {
checkArguments("count", [children, options], ["array", "object"]);
let count = 0;
someChildInternal(children, () => {
++count;
}, options);
return count;
};
//#endregion
export { ALL_VNODES, COMPONENTS_AND_ELEMENTS, SKIP_COMMENTS, addProps, betweenChildren, countChildren, eachChild, everyChild, extractSingleChild, findChild, getText, getType, isComment, isComponent, isElement, isEmpty, isFragment, isFunctionalComponent, isStatefulComponent, isStatic, isText, reduceChildren, replaceChildren, someChild };