@virtualstate/examples
Version:
163 lines • 6.27 kB
JavaScript
import { assertFragmentVNode, ChildrenSource, h, Instance, TokenConstructor, createFragment, EnableThen, assertVNode } from "@virtualstate/fringe";
import { assertElement, isNode, setAttributes } from "@virtualstate/dom";
const ParentInstance = Symbol("Parent Instance");
const space = new WeakMap();
async function* RenderElement({ document }, input) {
assertFragmentVNode(input);
for await (const children of input.children) {
const [node, ...rest] = children;
if (rest.length)
throw new Error("Expected one child");
const element = getElement(node);
if (!element.children) {
await setElementAttributes(element, node.options);
yield element;
continue;
}
for await (const children of element.children) {
await setElementAttributes(element, element.options);
const nodeChildren = children
.filter((child) => (typeof child.source === "string" &&
isNode(child[Instance])));
// This is the "mvp" mount for a set of nodes
// It could maybe be better and check if things are mounted and shift them around
// ... or we could just remove everything and add it back
for (const child of Array.from(element[Instance].childNodes)) {
element[Instance].removeChild(child);
}
for (const child of nodeChildren) {
element[Instance].appendChild(child[Instance]);
}
// console.log({ element, nodeChildren, children });
yield {
...element,
// Resolution has been shifted to the instance, but it can still be provided
children: {
async *[Symbol.asyncIterator]() {
yield children;
}
}
};
}
}
async function setElementAttributes(element, options) {
const attributes = Object.entries(options ?? element.options ?? {})
.filter((entry) => (typeof entry[1] === "string" ||
typeof entry[1] === "boolean" ||
typeof entry[1] === "number"));
if (!attributes.length) {
if (element[Instance].attributes.length === 0) {
return;
}
}
await setAttributes({
...element,
options: {
type: "Element",
attributes: Object.fromEntries(attributes)
}
}, element[Instance]);
}
function getElement(node) {
let thing = space.get(node[TokenConstructor]);
if (!thing) {
return create(node);
}
assertVNode(thing);
assertElementVNode(thing);
const element = {
...thing,
};
if (node.children) {
element.children = {
[Symbol.asyncIterator]() {
return cycleChildren(element, node.children)[Symbol.asyncIterator]();
}
};
}
element.options = node.options ?? element.options;
return element;
}
function assertElementVNode(node) {
assertElement(node[Instance]);
}
function create(node) {
if (typeof node.source !== "string") {
throw new Error("Expected string source");
}
if (typeof node[TokenConstructor] !== "function") {
throw new Error("Expected TokenConstructor");
}
const source = node.source;
const element = {
[Instance]: document.createElement(source),
source: node.source,
reference: Symbol("Element"),
options: node.options
};
if (node.scalar) {
element.scalar = node.scalar;
}
if (node.children) {
element.children = {
[Symbol.asyncIterator]() {
return cycleChildren(element, node.children)[Symbol.asyncIterator]();
}
};
}
space.set(node[TokenConstructor], element);
return element;
}
function cycleChildrenSource(parent, source) {
return source
.filter(value => typeof value !== "undefined")
.map(value => {
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
let node;
return {
get [Instance]() {
if (node)
return node;
return node = document.createTextNode(`${value}`);
},
source: value,
scalar: true,
reference: Symbol("Text")
};
}
return h(Render, { space: space, document: document, ...{ [ParentInstance]: parent } }, value);
});
}
function isChildrenSource(children) {
function isChildrenSourceLike(value) {
return !!value;
}
return isChildrenSourceLike(children) && Array.isArray(children[ChildrenSource]);
}
async function* cycleChildren(parent, nodeChildren) {
if (isChildrenSource(nodeChildren)) {
return yield* createFragment({}, ...cycleChildrenSource(parent, nodeChildren[ChildrenSource])).children;
}
for await (const children of nodeChildren) {
yield* createFragment({}, ...children.map((child) => {
if (child[Instance])
return child;
return h(RenderElement, { space: space, document: document, ...{ [ParentInstance]: parent } }, child);
})).children;
}
}
}
export async function* Render({ document, space = new WeakMap() }, input) {
if (!input?.children)
return;
for await (const children of input.children) {
yield children.map((child) => {
return h(RenderElement, { document: document, space: space }, child);
});
}
}
Render[EnableThen] = true;
export async function render(document, site, space) {
return (h(Render, { document: document, space: space }, site));
}
//# sourceMappingURL=render.js.map