mirror of
https://github.com/nvms/soma3.git
synced 2025-12-13 14:40:52 +00:00
1727 lines
52 KiB
JavaScript
Executable File
1727 lines
52 KiB
JavaScript
Executable File
// src/util.ts
|
|
function stringToElement(template) {
|
|
const parser = new DOMParser();
|
|
const doc = parser.parseFromString(template, "text/html");
|
|
return doc.body.firstChild;
|
|
}
|
|
var isText = (node) => {
|
|
return node.nodeType === Node.TEXT_NODE;
|
|
};
|
|
var isTemplate = (node) => {
|
|
return node.nodeName === "TEMPLATE";
|
|
};
|
|
var isElement = (node) => {
|
|
return node.nodeType === Node.ELEMENT_NODE;
|
|
};
|
|
function isObject(value) {
|
|
return value !== null && typeof value === "object";
|
|
}
|
|
function isArray(value) {
|
|
return Array.isArray(value);
|
|
}
|
|
function checkAndRemoveAttribute(el, attrName) {
|
|
const attributeValue = el.getAttribute(attrName);
|
|
if (attributeValue !== null) {
|
|
el.removeAttribute(attrName);
|
|
}
|
|
return attributeValue;
|
|
}
|
|
function findSlotNodes(element) {
|
|
const slots = [];
|
|
const findSlots = (node) => {
|
|
Array.from(node.childNodes).forEach((node2) => {
|
|
if (isElement(node2)) {
|
|
if (node2.nodeName === "SLOT") {
|
|
slots.push({ node: node2, name: node2.getAttribute("name") || "default" });
|
|
}
|
|
if (node2.hasChildNodes()) {
|
|
findSlots(node2);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
findSlots(element);
|
|
return slots;
|
|
}
|
|
function findTemplateNodes(element) {
|
|
const templates = [];
|
|
const findTemplates = (element2) => {
|
|
let defaultContentNodes = [];
|
|
Array.from(element2.childNodes).forEach((node) => {
|
|
if (isElement(node) || isText(node)) {
|
|
if (isElement(node) && node.nodeName === "TEMPLATE" && isTemplate(node)) {
|
|
templates.push({ targetSlotName: node.getAttribute("slot") || "", node });
|
|
} else {
|
|
defaultContentNodes.push(node);
|
|
}
|
|
}
|
|
});
|
|
if (defaultContentNodes.length > 0) {
|
|
const defaultTemplate = document.createElement("template");
|
|
defaultTemplate.setAttribute("slot", "default");
|
|
defaultContentNodes.forEach((node) => {
|
|
defaultTemplate.content.appendChild(node);
|
|
});
|
|
templates.push({ targetSlotName: "default", node: defaultTemplate });
|
|
}
|
|
};
|
|
findTemplates(element);
|
|
return templates;
|
|
}
|
|
var nextTick = async (f) => {
|
|
await new Promise(
|
|
(r) => setTimeout(
|
|
(_) => requestAnimationFrame((_2) => {
|
|
f && f();
|
|
r();
|
|
})
|
|
)
|
|
);
|
|
};
|
|
function html(strings, ...values) {
|
|
const selfClosingTags = ["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"];
|
|
let result = strings.reduce((acc, str, i) => acc + str + (values[i] || ""), "");
|
|
result = result.replace(/<([a-zA-Z][^\s/>]*)\s*([^>]*?)\/>/g, (match, tagName, attributes) => {
|
|
if (selfClosingTags.includes(tagName.toLowerCase())) {
|
|
return match;
|
|
}
|
|
return `<${tagName} ${attributes}></${tagName}>`;
|
|
});
|
|
return result;
|
|
}
|
|
function toDisplayString(value) {
|
|
return value == null ? "" : isObject(value) ? JSON.stringify(value, null, 2) : String(value);
|
|
}
|
|
function insertAfter(newNode, existingNode) {
|
|
if (existingNode.nextSibling) {
|
|
existingNode.parentNode.insertBefore(newNode, existingNode.nextSibling);
|
|
} else {
|
|
existingNode?.parentNode?.appendChild(newNode);
|
|
}
|
|
}
|
|
function isPropAttribute(attrName) {
|
|
if (attrName.startsWith(".")) {
|
|
return true;
|
|
}
|
|
if (attrName.startsWith("{") && attrName.endsWith("}")) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
function isSpreadProp(attr) {
|
|
return attr.startsWith("...");
|
|
}
|
|
function isMirrorProp(attr) {
|
|
return attr.startsWith("{") && attr.endsWith("}");
|
|
}
|
|
function isRegularProp(attr) {
|
|
return attr.startsWith(".");
|
|
}
|
|
function isEventAttribute(attrName) {
|
|
return attrName.startsWith("@");
|
|
}
|
|
function componentHasPropByName(name, component) {
|
|
return Object.keys(component?.props ?? {}).some((prop) => prop === name);
|
|
}
|
|
function extractAttributeName(attrName) {
|
|
return attrName.replace(/^\.\.\./, "").replace(/^\./, "").replace(/^{/, "").replace(/}$/, "").replace(/:bind$/, "");
|
|
}
|
|
function dashToCamel(str) {
|
|
return str.toLowerCase().replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
}
|
|
function extractPropName(attrName) {
|
|
return dashToCamel(extractAttributeName(attrName));
|
|
}
|
|
function classNames(_) {
|
|
const classes = [];
|
|
for (let i = 0; i < arguments.length; i++) {
|
|
const arg = arguments[i];
|
|
if (!arg) continue;
|
|
const argType = typeof arg;
|
|
if (argType === "string" || argType === "number") {
|
|
classes.push(arg);
|
|
} else if (Array.isArray(arg)) {
|
|
if (arg.length) {
|
|
const inner = classNames.apply(null, arg);
|
|
if (inner) {
|
|
classes.push(inner);
|
|
}
|
|
}
|
|
} else if (argType === "object") {
|
|
if (arg.toString === Object.prototype.toString) {
|
|
for (let key in arg) {
|
|
if (Object.hasOwnProperty.call(arg, key) && arg[key]) {
|
|
classes.push(key);
|
|
}
|
|
}
|
|
} else {
|
|
classes.push(arg.toString());
|
|
}
|
|
}
|
|
}
|
|
return classes.join(" ");
|
|
}
|
|
|
|
// src/directives/attribute.ts
|
|
var AttributeDirective = class {
|
|
element;
|
|
context;
|
|
expression;
|
|
attr;
|
|
extractedAttributeName;
|
|
previousClasses = [];
|
|
previousStyles = {};
|
|
is = {
|
|
sameNameProperty: false,
|
|
bound: false,
|
|
spread: false,
|
|
componentProp: false
|
|
};
|
|
constructor({ element, context, attr }) {
|
|
this.element = element;
|
|
this.context = context;
|
|
this.expression = attr.value;
|
|
this.attr = attr;
|
|
this.extractedAttributeName = extractAttributeName(attr.name);
|
|
this.is = {
|
|
sameNameProperty: attr.name.startsWith("{") && attr.name.endsWith("}"),
|
|
bound: attr.name.includes(":bind"),
|
|
spread: attr.name.startsWith("..."),
|
|
componentProp: false
|
|
};
|
|
if (this.is.sameNameProperty) {
|
|
this.expression = this.extractedAttributeName;
|
|
}
|
|
if (this.is.spread) {
|
|
this.expression = this.extractedAttributeName;
|
|
}
|
|
element.removeAttribute(attr.name);
|
|
if (this.is.bound) {
|
|
context.effect(this.update.bind(this));
|
|
} else {
|
|
this.update();
|
|
}
|
|
}
|
|
update() {
|
|
let value = evalGet(this.context.scope, this.expression, this.element);
|
|
if (this.is.spread && typeof value === "object") {
|
|
for (const [key, val] of Object.entries(value)) {
|
|
this.element.setAttribute(key, String(val));
|
|
}
|
|
} else if ((typeof value === "object" || Array.isArray(value)) && this.extractedAttributeName === "class") {
|
|
value = classNames(value);
|
|
const next = value.split(" ");
|
|
const diff = next.filter((c) => !this.previousClasses.includes(c)).filter(Boolean);
|
|
const rm = this.previousClasses.filter((c) => !next.includes(c));
|
|
diff.forEach((c) => {
|
|
this.previousClasses.push(c);
|
|
this.element.classList.add(c);
|
|
});
|
|
rm.forEach((c) => {
|
|
this.previousClasses = this.previousClasses.filter((addedClass) => addedClass !== c);
|
|
this.element.classList.remove(c);
|
|
});
|
|
} else if (typeof value === "object" && this.extractedAttributeName === "style") {
|
|
console.log("value is object", value);
|
|
const next = Object.keys(value);
|
|
const rm = Object.keys(this.previousStyles).filter((style) => !next.includes(style));
|
|
next.forEach((style) => {
|
|
this.previousStyles[style] = value[style];
|
|
this.element.style[style] = value[style];
|
|
});
|
|
rm.forEach((style) => {
|
|
this.previousStyles[style] = "";
|
|
this.element.style[style] = "";
|
|
});
|
|
this.previousStyles = value;
|
|
} else {
|
|
this.element.setAttribute(this.extractedAttributeName, value);
|
|
}
|
|
}
|
|
};
|
|
|
|
// src/directives/event.ts
|
|
var EventDirective = class {
|
|
element;
|
|
context;
|
|
expression;
|
|
attr;
|
|
eventCount = 0;
|
|
constructor({ element, context, attr }) {
|
|
this.element = element;
|
|
this.context = context;
|
|
this.expression = attr.value;
|
|
this.attr = attr;
|
|
const eventName = attr.name.replace(/^@/, "");
|
|
const parts = eventName.split(".");
|
|
this.element.addEventListener(parts[0], (event) => {
|
|
if (parts.includes("prevent")) event.preventDefault();
|
|
if (parts.includes("stop")) event.stopPropagation();
|
|
if (parts.includes("once") && this.eventCount > 0) return;
|
|
this.eventCount++;
|
|
const handler = evalGet(context.scope, attr.value, element);
|
|
if (typeof handler === "function") {
|
|
handler(event);
|
|
}
|
|
});
|
|
element.removeAttribute(attr.name);
|
|
}
|
|
};
|
|
|
|
// src/directives/for.ts
|
|
var forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/;
|
|
var forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/;
|
|
var stripParensRE = /^\(|\)$/g;
|
|
var destructureRE = /^[{[]\s*((?:[\w_$]+\s*,?\s*)+)[\]}]$/;
|
|
var _for = (el, exp, ctx, component, componentProps, allProps) => {
|
|
const inMatch = exp.match(forAliasRE);
|
|
if (!inMatch) {
|
|
console.warn(`invalid :for expression: ${exp}`);
|
|
return;
|
|
}
|
|
const nextNode = el.nextSibling;
|
|
const parent = el.parentElement;
|
|
const anchor = new Text("");
|
|
parent.insertBefore(anchor, el);
|
|
parent.removeChild(el);
|
|
const sourceExp = inMatch[2].trim();
|
|
let valueExp = inMatch[1].trim().replace(stripParensRE, "").trim();
|
|
let destructureBindings;
|
|
let isArrayDestructure = false;
|
|
let indexExp;
|
|
let objIndexExp;
|
|
let keyAttr = "key";
|
|
let keyExp = el.getAttribute(keyAttr) || el.getAttribute(keyAttr = ":key") || el.getAttribute(keyAttr = ":key:bind");
|
|
if (keyExp) {
|
|
el.removeAttribute(keyAttr);
|
|
if (keyAttr === "key") keyExp = JSON.stringify(keyExp);
|
|
}
|
|
let match;
|
|
if (match = valueExp.match(forIteratorRE)) {
|
|
valueExp = valueExp.replace(forIteratorRE, "").trim();
|
|
indexExp = match[1].trim();
|
|
if (match[2]) {
|
|
objIndexExp = match[2].trim();
|
|
}
|
|
}
|
|
if (match = valueExp.match(destructureRE)) {
|
|
destructureBindings = match[1].split(",").map((s) => s.trim());
|
|
isArrayDestructure = valueExp[0] === "[";
|
|
}
|
|
let mounted = false;
|
|
let blocks;
|
|
let childCtxs;
|
|
let keyToIndexMap;
|
|
const createChildContexts = (source) => {
|
|
const map = /* @__PURE__ */ new Map();
|
|
const ctxs = [];
|
|
if (isArray(source)) {
|
|
for (let i = 0; i < source.length; i++) {
|
|
ctxs.push(createChildContext(map, source[i], i));
|
|
}
|
|
} else if (typeof source === "number") {
|
|
for (let i = 0; i < source; i++) {
|
|
ctxs.push(createChildContext(map, i + 1, i));
|
|
}
|
|
} else if (isObject(source)) {
|
|
let i = 0;
|
|
for (const key in source) {
|
|
ctxs.push(createChildContext(map, source[key], i++, key));
|
|
}
|
|
}
|
|
return [ctxs, map];
|
|
};
|
|
const createChildContext = (map, value, index, objKey) => {
|
|
const data = {};
|
|
if (destructureBindings) {
|
|
destructureBindings.forEach((b, i) => data[b] = value[isArrayDestructure ? i : b]);
|
|
} else {
|
|
data[valueExp] = value;
|
|
}
|
|
if (objKey) {
|
|
indexExp && (data[indexExp] = objKey);
|
|
objIndexExp && (data[objIndexExp] = index);
|
|
} else {
|
|
indexExp && (data[indexExp] = index);
|
|
}
|
|
const childCtx = createScopedContext(ctx, data);
|
|
const key = keyExp ? evalGet(childCtx.scope, keyExp, el) : index;
|
|
map.set(key, index);
|
|
childCtx.key = key;
|
|
return childCtx;
|
|
};
|
|
const mountBlock = (ctx2, ref2) => {
|
|
const block = new Block({ element: el, parentContext: ctx2, replacementType: "replace", component, componentProps, allProps });
|
|
block.key = ctx2.key;
|
|
block.insert(parent, ref2);
|
|
return block;
|
|
};
|
|
ctx.effect(() => {
|
|
const source = evalGet(ctx.scope, sourceExp, el);
|
|
const prevKeyToIndexMap = keyToIndexMap;
|
|
[childCtxs, keyToIndexMap] = createChildContexts(source);
|
|
if (!mounted) {
|
|
blocks = childCtxs.map((s) => mountBlock(s, anchor));
|
|
mounted = true;
|
|
} else {
|
|
for (let i2 = 0; i2 < blocks.length; i2++) {
|
|
if (!keyToIndexMap.has(blocks[i2].key)) {
|
|
blocks[i2].remove();
|
|
}
|
|
}
|
|
const nextBlocks = [];
|
|
let i = childCtxs.length;
|
|
let nextBlock;
|
|
let prevMovedBlock;
|
|
while (i--) {
|
|
const childCtx = childCtxs[i];
|
|
const oldIndex = prevKeyToIndexMap.get(childCtx.key);
|
|
let block;
|
|
if (oldIndex == null) {
|
|
block = mountBlock(childCtx, nextBlock ? nextBlock.element : anchor);
|
|
} else {
|
|
block = blocks[oldIndex];
|
|
Object.assign(block.context.scope, childCtx.scope);
|
|
if (oldIndex !== i) {
|
|
if (blocks[oldIndex + 1] !== nextBlock || // If the next has moved, it must move too
|
|
prevMovedBlock === nextBlock) {
|
|
prevMovedBlock = block;
|
|
block.insert(parent, nextBlock ? nextBlock.element : anchor);
|
|
}
|
|
}
|
|
}
|
|
nextBlocks.unshift(nextBlock = block);
|
|
}
|
|
blocks = nextBlocks;
|
|
}
|
|
});
|
|
return nextNode;
|
|
};
|
|
|
|
// src/directives/if.ts
|
|
function _if(el, exp, ctx, component, componentProps, allProps) {
|
|
const parent = el.parentElement;
|
|
const anchor = new Comment(":if");
|
|
parent.insertBefore(anchor, el);
|
|
const branches = [{ exp, el }];
|
|
let elseEl;
|
|
let elseExp;
|
|
while (elseEl = el.nextElementSibling) {
|
|
elseExp = null;
|
|
if (checkAndRemoveAttribute(elseEl, ":else") === "" || (elseExp = checkAndRemoveAttribute(elseEl, ":else-if"))) {
|
|
parent.removeChild(elseEl);
|
|
branches.push({ exp: elseExp, el: elseEl });
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
const nextNode = el.nextSibling;
|
|
parent.removeChild(el);
|
|
let block;
|
|
let activeBranchIndex = -1;
|
|
const removeActiveBlock = () => {
|
|
if (block) {
|
|
parent.insertBefore(anchor, block.element);
|
|
block.remove();
|
|
block = void 0;
|
|
}
|
|
};
|
|
ctx.effect(() => {
|
|
for (let i = 0; i < branches.length; i++) {
|
|
const { exp: exp2, el: el2 } = branches[i];
|
|
if (!exp2 || evalGet(ctx.scope, exp2, el2)) {
|
|
if (i !== activeBranchIndex) {
|
|
removeActiveBlock();
|
|
block = new Block({ element: el2, parentContext: ctx, replacementType: "replace", component, componentProps, allProps });
|
|
block.insert(parent, anchor);
|
|
parent.removeChild(anchor);
|
|
activeBranchIndex = i;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
activeBranchIndex = -1;
|
|
removeActiveBlock();
|
|
});
|
|
return nextNode;
|
|
}
|
|
|
|
// src/directives/interpolation.ts
|
|
var delims = /{{\s?(.*?)\s?}}/g;
|
|
var InterpolationDirective = class {
|
|
element;
|
|
context;
|
|
textNodes = /* @__PURE__ */ new Map();
|
|
constructor({ element, context }) {
|
|
this.element = element;
|
|
this.context = context;
|
|
this.findNodes();
|
|
this.textNodes.forEach((nodes, expression) => {
|
|
const trimmedExpression = expression.slice(2, -2).trim();
|
|
nodes.forEach((node) => {
|
|
const getter = (exp = trimmedExpression) => evalGet(this.context.scope, exp, element);
|
|
context.effect(() => {
|
|
node.textContent = toDisplayString(getter());
|
|
});
|
|
});
|
|
});
|
|
}
|
|
findNodes() {
|
|
const textContent = this.element.textContent.trim();
|
|
if (textContent?.match(delims)) {
|
|
const textNodes = textContent.split(/(\{\{\s?[^}]+\s?\}\})/g).filter(Boolean);
|
|
if (textNodes) {
|
|
let previousNode = this.element;
|
|
for (let i = 0; i < textNodes.length; i++) {
|
|
const textNode = textNodes[i];
|
|
if (textNode.match(/\{\{\s?.+\s?\}\}/)) {
|
|
const newNode = document.createTextNode(textNode);
|
|
if (i === 0) {
|
|
this.element.replaceWith(newNode);
|
|
} else {
|
|
insertAfter(newNode, previousNode);
|
|
}
|
|
previousNode = newNode;
|
|
if (this.textNodes.has(textNode)) {
|
|
this.textNodes.get(textNode).push(newNode);
|
|
} else {
|
|
this.textNodes.set(textNode, [newNode]);
|
|
}
|
|
} else {
|
|
const newNode = document.createTextNode(textNodes[i]);
|
|
if (i === 0) {
|
|
this.element.replaceWith(newNode);
|
|
} else {
|
|
insertAfter(newNode, previousNode);
|
|
}
|
|
previousNode = newNode;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
update() {
|
|
}
|
|
};
|
|
|
|
// src/directives/show.ts
|
|
var ShowDirective = class {
|
|
element;
|
|
context;
|
|
expression;
|
|
originalDisplay;
|
|
constructor({ element, context, expression }) {
|
|
this.element = element;
|
|
this.context = context;
|
|
this.expression = expression;
|
|
this.originalDisplay = getComputedStyle(this.element).display;
|
|
context.effect(this.update.bind(this));
|
|
}
|
|
update() {
|
|
const shouldShow = Boolean(evalGet(this.context.scope, this.expression, this.element));
|
|
this.element.style.display = shouldShow ? this.originalDisplay : "none";
|
|
}
|
|
};
|
|
|
|
// src/directives/teleport.ts
|
|
function _teleport(el, exp, ctx) {
|
|
const anchor = new Comment(":teleport");
|
|
el.replaceWith(anchor);
|
|
const target = document.querySelector(exp);
|
|
if (!target) {
|
|
console.warn(`teleport target not found: ${exp}`);
|
|
return;
|
|
}
|
|
nextTick(() => {
|
|
target.appendChild(el);
|
|
const observer = new MutationObserver((mutationsList) => {
|
|
mutationsList.forEach((mutation) => {
|
|
mutation.removedNodes.forEach((removedNode) => {
|
|
if (removedNode.contains(anchor)) {
|
|
el.remove();
|
|
observer.disconnect();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
new Block({
|
|
element: el,
|
|
parentContext: ctx
|
|
});
|
|
});
|
|
return anchor;
|
|
}
|
|
|
|
// src/directives/value.ts
|
|
function isInput(element) {
|
|
return element instanceof HTMLInputElement;
|
|
}
|
|
function isTextarea(element) {
|
|
return element instanceof HTMLTextAreaElement;
|
|
}
|
|
function isSelect(element) {
|
|
return element instanceof HTMLSelectElement;
|
|
}
|
|
var ValueDirective = class {
|
|
element;
|
|
context;
|
|
expression;
|
|
inputType;
|
|
constructor({ element, context, expression }) {
|
|
this.element = element;
|
|
this.context = context;
|
|
this.expression = expression;
|
|
this.inputType = element.getAttribute("type");
|
|
if (isInput(element)) {
|
|
switch (this.inputType) {
|
|
case "text":
|
|
case "password":
|
|
case "number":
|
|
case "color":
|
|
element.addEventListener("input", () => {
|
|
const value = this.inputType === "number" ? element.value ? parseFloat(element.value) : 0 : element.value;
|
|
evalSet(this.context.scope, expression, value);
|
|
});
|
|
break;
|
|
case "checkbox":
|
|
element.addEventListener("change", (e) => {
|
|
evalSet(this.context.scope, expression, !!e.currentTarget.checked);
|
|
});
|
|
break;
|
|
case "radio":
|
|
element.addEventListener("change", (e) => {
|
|
if (e.currentTarget.checked) {
|
|
evalSet(this.context.scope, expression, element.getAttribute("value"));
|
|
}
|
|
});
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (isTextarea(element)) {
|
|
element.addEventListener("input", () => {
|
|
evalSet(this.context.scope, expression, element.value);
|
|
});
|
|
}
|
|
if (isSelect(element)) {
|
|
element.addEventListener("change", () => {
|
|
evalSet(this.context.scope, expression, element.value);
|
|
});
|
|
}
|
|
context.effect(this.updateElementValue.bind(this));
|
|
}
|
|
updateElementValue() {
|
|
const value = evalGet(this.context.scope, this.expression, this.element);
|
|
if (isInput(this.element)) {
|
|
switch (this.inputType) {
|
|
case "text":
|
|
case "password":
|
|
case "number":
|
|
case "color":
|
|
this.element.value = value;
|
|
break;
|
|
case "checkbox":
|
|
this.element.checked = !!value;
|
|
break;
|
|
case "radio":
|
|
this.element.checked = this.element.value === value;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (isTextarea(this.element)) {
|
|
this.element.value = value;
|
|
}
|
|
if (isSelect(this.element)) {
|
|
this.element.value = value;
|
|
}
|
|
}
|
|
};
|
|
|
|
// src/reactivity/effect.ts
|
|
var targetMap = /* @__PURE__ */ new WeakMap();
|
|
var effectStack = [];
|
|
function track(target, key) {
|
|
const activeEffect = effectStack[effectStack.length - 1];
|
|
if (!activeEffect) return;
|
|
let effectsMap = targetMap.get(target);
|
|
if (!effectsMap)
|
|
targetMap.set(target, effectsMap = /* @__PURE__ */ new Map());
|
|
let effects = effectsMap.get(key);
|
|
if (!effects) effectsMap.set(key, effects = /* @__PURE__ */ new Set());
|
|
if (!effects.has(activeEffect)) {
|
|
effects.add(activeEffect);
|
|
activeEffect.refs.push(effects);
|
|
}
|
|
}
|
|
function trigger(target, key) {
|
|
const effectsMap = targetMap.get(target);
|
|
if (!effectsMap) return;
|
|
const scheduled = /* @__PURE__ */ new Set();
|
|
effectsMap.get(key)?.forEach((effect2) => {
|
|
scheduled.add(effect2);
|
|
});
|
|
scheduled.forEach(run);
|
|
}
|
|
function stop2(effect2) {
|
|
if (effect2.active) cleanup(effect2);
|
|
effect2.active = false;
|
|
}
|
|
function start(effect2) {
|
|
if (!effect2.active) {
|
|
effect2.active = true;
|
|
run(effect2);
|
|
}
|
|
}
|
|
function run(effect2) {
|
|
if (!effect2.active) return;
|
|
if (effectStack.includes(effect2)) return;
|
|
cleanup(effect2);
|
|
let val;
|
|
try {
|
|
effectStack.push(effect2);
|
|
val = effect2.handler();
|
|
} finally {
|
|
effectStack.pop();
|
|
}
|
|
return val;
|
|
}
|
|
function cleanup(effect2) {
|
|
const { refs } = effect2;
|
|
if (refs.length) {
|
|
for (const ref2 of refs) {
|
|
ref2.delete(effect2);
|
|
}
|
|
}
|
|
refs.length = 0;
|
|
}
|
|
function effect(handler, opts = {}) {
|
|
const { lazy } = opts;
|
|
const newEffect = {
|
|
active: !lazy,
|
|
handler,
|
|
refs: []
|
|
};
|
|
run(newEffect);
|
|
return {
|
|
start: () => {
|
|
start(newEffect);
|
|
},
|
|
stop: () => {
|
|
stop2(newEffect);
|
|
},
|
|
toggle: () => {
|
|
if (newEffect.active) {
|
|
stop2(newEffect);
|
|
} else {
|
|
start(newEffect);
|
|
}
|
|
return newEffect.active;
|
|
}
|
|
};
|
|
}
|
|
|
|
// src/reactivity/computed.ts
|
|
var $computed = Symbol("computed");
|
|
function isComputed(value) {
|
|
return isObject(value) && value[$computed];
|
|
}
|
|
|
|
// src/reactivity/ref.ts
|
|
var $ref = Symbol("ref");
|
|
function isRef(value) {
|
|
return isObject(value) && !!value[$ref];
|
|
}
|
|
function ref(value = null) {
|
|
if (isObject(value)) {
|
|
return isRef(value) ? value : reactive(value);
|
|
}
|
|
const result = { value, [$ref]: true };
|
|
return new Proxy(result, {
|
|
get(target, key, receiver) {
|
|
const val = Reflect.get(target, key, receiver);
|
|
track(result, "value");
|
|
return val;
|
|
},
|
|
set(target, key, value2) {
|
|
const oldValue = target[key];
|
|
if (oldValue !== value2) {
|
|
const success = Reflect.set(target, key, value2);
|
|
if (success) {
|
|
trigger(result, "value");
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
|
|
// src/reactivity/reactive.ts
|
|
var $reactive = Symbol("reactive");
|
|
function reactive(value) {
|
|
if (!isObject(value)) return ref(value);
|
|
if (value[$reactive]) return value;
|
|
value[$reactive] = true;
|
|
Object.keys(value).forEach((key) => {
|
|
if (isObject(value[key])) {
|
|
value[key] = reactive(value[key]);
|
|
}
|
|
});
|
|
return new Proxy(value, reactiveProxyHandler());
|
|
}
|
|
function reactiveProxyHandler() {
|
|
return {
|
|
deleteProperty(target, key) {
|
|
const had = Reflect.has(target, key);
|
|
const result = Reflect.deleteProperty(target, key);
|
|
if (had) trigger(target, key);
|
|
return result;
|
|
},
|
|
get(target, key) {
|
|
track(target, key);
|
|
return Reflect.get(target, key);
|
|
},
|
|
set(target, key, value) {
|
|
if (target[key] === value) return true;
|
|
let newObj = false;
|
|
if (isObject(value) && !isObject(target[key])) {
|
|
newObj = true;
|
|
}
|
|
if (Reflect.set(target, key, value)) {
|
|
trigger(target, key);
|
|
}
|
|
if (newObj) {
|
|
target[key] = reactive(target[key]);
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
}
|
|
|
|
// node_modules/path-to-regexp/dist.es2015/index.js
|
|
function lexer(str) {
|
|
var tokens = [];
|
|
var i = 0;
|
|
while (i < str.length) {
|
|
var char = str[i];
|
|
if (char === "*" || char === "+" || char === "?") {
|
|
tokens.push({ type: "MODIFIER", index: i, value: str[i++] });
|
|
continue;
|
|
}
|
|
if (char === "\\") {
|
|
tokens.push({ type: "ESCAPED_CHAR", index: i++, value: str[i++] });
|
|
continue;
|
|
}
|
|
if (char === "{") {
|
|
tokens.push({ type: "OPEN", index: i, value: str[i++] });
|
|
continue;
|
|
}
|
|
if (char === "}") {
|
|
tokens.push({ type: "CLOSE", index: i, value: str[i++] });
|
|
continue;
|
|
}
|
|
if (char === ":") {
|
|
var name = "";
|
|
var j = i + 1;
|
|
while (j < str.length) {
|
|
var code = str.charCodeAt(j);
|
|
if (
|
|
// `0-9`
|
|
code >= 48 && code <= 57 || // `A-Z`
|
|
code >= 65 && code <= 90 || // `a-z`
|
|
code >= 97 && code <= 122 || // `_`
|
|
code === 95
|
|
) {
|
|
name += str[j++];
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if (!name)
|
|
throw new TypeError("Missing parameter name at " + i);
|
|
tokens.push({ type: "NAME", index: i, value: name });
|
|
i = j;
|
|
continue;
|
|
}
|
|
if (char === "(") {
|
|
var count = 1;
|
|
var pattern = "";
|
|
var j = i + 1;
|
|
if (str[j] === "?") {
|
|
throw new TypeError('Pattern cannot start with "?" at ' + j);
|
|
}
|
|
while (j < str.length) {
|
|
if (str[j] === "\\") {
|
|
pattern += str[j++] + str[j++];
|
|
continue;
|
|
}
|
|
if (str[j] === ")") {
|
|
count--;
|
|
if (count === 0) {
|
|
j++;
|
|
break;
|
|
}
|
|
} else if (str[j] === "(") {
|
|
count++;
|
|
if (str[j + 1] !== "?") {
|
|
throw new TypeError("Capturing groups are not allowed at " + j);
|
|
}
|
|
}
|
|
pattern += str[j++];
|
|
}
|
|
if (count)
|
|
throw new TypeError("Unbalanced pattern at " + i);
|
|
if (!pattern)
|
|
throw new TypeError("Missing pattern at " + i);
|
|
tokens.push({ type: "PATTERN", index: i, value: pattern });
|
|
i = j;
|
|
continue;
|
|
}
|
|
tokens.push({ type: "CHAR", index: i, value: str[i++] });
|
|
}
|
|
tokens.push({ type: "END", index: i, value: "" });
|
|
return tokens;
|
|
}
|
|
function parse(str, options) {
|
|
if (options === void 0) {
|
|
options = {};
|
|
}
|
|
var tokens = lexer(str);
|
|
var _a = options.prefixes, prefixes = _a === void 0 ? "./" : _a;
|
|
var defaultPattern = "[^" + escapeString(options.delimiter || "/#?") + "]+?";
|
|
var result = [];
|
|
var key = 0;
|
|
var i = 0;
|
|
var path = "";
|
|
var tryConsume = function(type) {
|
|
if (i < tokens.length && tokens[i].type === type)
|
|
return tokens[i++].value;
|
|
};
|
|
var mustConsume = function(type) {
|
|
var value2 = tryConsume(type);
|
|
if (value2 !== void 0)
|
|
return value2;
|
|
var _a2 = tokens[i], nextType = _a2.type, index = _a2.index;
|
|
throw new TypeError("Unexpected " + nextType + " at " + index + ", expected " + type);
|
|
};
|
|
var consumeText = function() {
|
|
var result2 = "";
|
|
var value2;
|
|
while (value2 = tryConsume("CHAR") || tryConsume("ESCAPED_CHAR")) {
|
|
result2 += value2;
|
|
}
|
|
return result2;
|
|
};
|
|
while (i < tokens.length) {
|
|
var char = tryConsume("CHAR");
|
|
var name = tryConsume("NAME");
|
|
var pattern = tryConsume("PATTERN");
|
|
if (name || pattern) {
|
|
var prefix = char || "";
|
|
if (prefixes.indexOf(prefix) === -1) {
|
|
path += prefix;
|
|
prefix = "";
|
|
}
|
|
if (path) {
|
|
result.push(path);
|
|
path = "";
|
|
}
|
|
result.push({
|
|
name: name || key++,
|
|
prefix,
|
|
suffix: "",
|
|
pattern: pattern || defaultPattern,
|
|
modifier: tryConsume("MODIFIER") || ""
|
|
});
|
|
continue;
|
|
}
|
|
var value = char || tryConsume("ESCAPED_CHAR");
|
|
if (value) {
|
|
path += value;
|
|
continue;
|
|
}
|
|
if (path) {
|
|
result.push(path);
|
|
path = "";
|
|
}
|
|
var open = tryConsume("OPEN");
|
|
if (open) {
|
|
var prefix = consumeText();
|
|
var name_1 = tryConsume("NAME") || "";
|
|
var pattern_1 = tryConsume("PATTERN") || "";
|
|
var suffix = consumeText();
|
|
mustConsume("CLOSE");
|
|
result.push({
|
|
name: name_1 || (pattern_1 ? key++ : ""),
|
|
pattern: name_1 && !pattern_1 ? defaultPattern : pattern_1,
|
|
prefix,
|
|
suffix,
|
|
modifier: tryConsume("MODIFIER") || ""
|
|
});
|
|
continue;
|
|
}
|
|
mustConsume("END");
|
|
}
|
|
return result;
|
|
}
|
|
function escapeString(str) {
|
|
return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1");
|
|
}
|
|
function flags(options) {
|
|
return options && options.sensitive ? "" : "i";
|
|
}
|
|
function regexpToRegexp(path, keys) {
|
|
if (!keys)
|
|
return path;
|
|
var groupsRegex = /\((?:\?<(.*?)>)?(?!\?)/g;
|
|
var index = 0;
|
|
var execResult = groupsRegex.exec(path.source);
|
|
while (execResult) {
|
|
keys.push({
|
|
// Use parenthesized substring match if available, index otherwise
|
|
name: execResult[1] || index++,
|
|
prefix: "",
|
|
suffix: "",
|
|
modifier: "",
|
|
pattern: ""
|
|
});
|
|
execResult = groupsRegex.exec(path.source);
|
|
}
|
|
return path;
|
|
}
|
|
function arrayToRegexp(paths, keys, options) {
|
|
var parts = paths.map(function(path) {
|
|
return pathToRegexp(path, keys, options).source;
|
|
});
|
|
return new RegExp("(?:" + parts.join("|") + ")", flags(options));
|
|
}
|
|
function stringToRegexp(path, keys, options) {
|
|
return tokensToRegexp(parse(path, options), keys, options);
|
|
}
|
|
function tokensToRegexp(tokens, keys, options) {
|
|
if (options === void 0) {
|
|
options = {};
|
|
}
|
|
var _a = options.strict, strict = _a === void 0 ? false : _a, _b = options.start, start2 = _b === void 0 ? true : _b, _c = options.end, end = _c === void 0 ? true : _c, _d = options.encode, encode = _d === void 0 ? function(x) {
|
|
return x;
|
|
} : _d;
|
|
var endsWith = "[" + escapeString(options.endsWith || "") + "]|$";
|
|
var delimiter = "[" + escapeString(options.delimiter || "/#?") + "]";
|
|
var route = start2 ? "^" : "";
|
|
for (var _i = 0, tokens_1 = tokens; _i < tokens_1.length; _i++) {
|
|
var token = tokens_1[_i];
|
|
if (typeof token === "string") {
|
|
route += escapeString(encode(token));
|
|
} else {
|
|
var prefix = escapeString(encode(token.prefix));
|
|
var suffix = escapeString(encode(token.suffix));
|
|
if (token.pattern) {
|
|
if (keys)
|
|
keys.push(token);
|
|
if (prefix || suffix) {
|
|
if (token.modifier === "+" || token.modifier === "*") {
|
|
var mod = token.modifier === "*" ? "?" : "";
|
|
route += "(?:" + prefix + "((?:" + token.pattern + ")(?:" + suffix + prefix + "(?:" + token.pattern + "))*)" + suffix + ")" + mod;
|
|
} else {
|
|
route += "(?:" + prefix + "(" + token.pattern + ")" + suffix + ")" + token.modifier;
|
|
}
|
|
} else {
|
|
route += "(" + token.pattern + ")" + token.modifier;
|
|
}
|
|
} else {
|
|
route += "(?:" + prefix + suffix + ")" + token.modifier;
|
|
}
|
|
}
|
|
}
|
|
if (end) {
|
|
if (!strict)
|
|
route += delimiter + "?";
|
|
route += !options.endsWith ? "$" : "(?=" + endsWith + ")";
|
|
} else {
|
|
var endToken = tokens[tokens.length - 1];
|
|
var isEndDelimited = typeof endToken === "string" ? delimiter.indexOf(endToken[endToken.length - 1]) > -1 : (
|
|
// tslint:disable-next-line
|
|
endToken === void 0
|
|
);
|
|
if (!strict) {
|
|
route += "(?:" + delimiter + "(?=" + endsWith + "))?";
|
|
}
|
|
if (!isEndDelimited) {
|
|
route += "(?=" + delimiter + "|" + endsWith + ")";
|
|
}
|
|
}
|
|
return new RegExp(route, flags(options));
|
|
}
|
|
function pathToRegexp(path, keys, options) {
|
|
if (path instanceof RegExp)
|
|
return regexpToRegexp(path, keys);
|
|
if (Array.isArray(path))
|
|
return arrayToRegexp(path, keys, options);
|
|
return stringToRegexp(path, keys, options);
|
|
}
|
|
|
|
// src/reactivity/unwrap.ts
|
|
function unwrap(value) {
|
|
if (isRef(value) || isComputed(value)) {
|
|
return value.value;
|
|
}
|
|
if (typeof value === "function") {
|
|
return value();
|
|
}
|
|
return value;
|
|
}
|
|
|
|
// src/plugins/router/plugin.ts
|
|
var activeRouters = /* @__PURE__ */ new Set();
|
|
var link = {
|
|
template: html`<a {href:bind} @click="go" .class:bind="classes"><slot /></a>`,
|
|
props: { href: { default: "#" } },
|
|
main({ href }) {
|
|
const go = (e) => {
|
|
e.preventDefault();
|
|
activeRouters.forEach((router) => {
|
|
router.doRouteChange(unwrap(href));
|
|
});
|
|
};
|
|
const classes = reactive({ "router-link": true });
|
|
return { go, classes, href };
|
|
}
|
|
};
|
|
async function runEnterTransition(enter) {
|
|
return await enter();
|
|
}
|
|
var canEnterRoute = async (route) => {
|
|
if (route.beforeEnter) {
|
|
return await runEnterTransition(route.beforeEnter);
|
|
}
|
|
return true;
|
|
};
|
|
var maybeRedirectRoute = (route) => {
|
|
if (route.redirectTo) {
|
|
activeRouters.forEach((plugin) => plugin.doRouteChange(route.redirectTo));
|
|
}
|
|
};
|
|
var RouterPlugin = class {
|
|
app;
|
|
routes = [];
|
|
pathExpressions = /* @__PURE__ */ new Map();
|
|
lastPath = "/";
|
|
knownRouterViews = /* @__PURE__ */ new Map();
|
|
knownRouterViewNames = /* @__PURE__ */ new Map();
|
|
populatedRouterViews = /* @__PURE__ */ new Map();
|
|
constructor(routes = []) {
|
|
this.routes = routes;
|
|
}
|
|
use(app, ..._) {
|
|
this.app = app;
|
|
this.app.register("router-link", link);
|
|
window.addEventListener("popstate", this.onHistoryEvent.bind(this));
|
|
window.addEventListener("pushstate", this.onHistoryEvent.bind(this));
|
|
window.addEventListener("load", this.onHistoryEvent.bind(this));
|
|
for (const route of this.routes) {
|
|
this.cacheRouteExpression(route);
|
|
}
|
|
this.lastPath = `${location.pathname}${location.search}`;
|
|
window.history.replaceState({}, "", this.lastPath);
|
|
activeRouters.add(this);
|
|
}
|
|
compile(element) {
|
|
if (element.nodeType === Node.ELEMENT_NODE && element.nodeName === "ROUTER-VIEW" && !this.knownRouterViews.has(element) && current.componentBlock) {
|
|
this.knownRouterViews.set(element, current.componentBlock);
|
|
this.knownRouterViewNames.set(element.getAttribute("name")?.trim() || "", element);
|
|
}
|
|
}
|
|
onHistoryEvent(e) {
|
|
e.preventDefault();
|
|
e.stopImmediatePropagation();
|
|
const path = new URL(e.currentTarget.location.href).pathname;
|
|
if (e.type === "load") {
|
|
window.history.replaceState({}, "", this.lastPath);
|
|
} else if (e.type === "pushstate") {
|
|
window.history.replaceState({}, "", path);
|
|
} else if (e.type === "popstate") {
|
|
window.history.replaceState({}, "", path);
|
|
}
|
|
this.lastPath = path;
|
|
const matches = this.getMatchesForURL(path);
|
|
this.applyMatches(matches);
|
|
}
|
|
doRouteChange(to) {
|
|
window.history.pushState({}, "", to);
|
|
const matches = this.getMatchesForURL(`${location.pathname}${location.search}`);
|
|
this.applyMatches(matches);
|
|
}
|
|
getMatchesForURL(url) {
|
|
let matches = [];
|
|
const matchRoutes = (routes, parentPath = "", previousParents = []) => {
|
|
let parents = [];
|
|
for (const route of routes) {
|
|
parents.push(route);
|
|
const path = `${parentPath}${route.path}`.replace(/\/\//g, "/");
|
|
const match = this.getPathMatch(path, url);
|
|
if (match) matches.push({ match, parents: [...previousParents, ...parents] });
|
|
if (route.children?.length) {
|
|
matchRoutes(route.children, path, [...previousParents, ...parents]);
|
|
parents = [];
|
|
}
|
|
}
|
|
return matches;
|
|
};
|
|
matches = matchRoutes(this.routes);
|
|
return matches;
|
|
}
|
|
/**
|
|
* getRouteExpression takes a path like "/users/:id" and returns a regex
|
|
* and an array of params that match the path.
|
|
* "/users/:id" => { regex: /^\/users\/([^\/]+)\?jwt=(\w)$/, params: ["id"], query: ["jwt"] }
|
|
*/
|
|
getRouteExpression(path, route) {
|
|
if (this.pathExpressions.has(path)) return this.pathExpressions.get(path);
|
|
const params = [];
|
|
const regex = pathToRegexp(path, params, { strict: false, sensitive: false, end: true });
|
|
const expression = { regex, params, path, route };
|
|
this.pathExpressions.set(path, expression);
|
|
return expression;
|
|
}
|
|
/**
|
|
*
|
|
* @param path A path like /foo/bar/:id
|
|
* @param url A url like /foo/bar/1234
|
|
* @returns A RouteExpression if the URL matches the regex cached for @param path, null otherwise.
|
|
*/
|
|
getPathMatch(path, url) {
|
|
if (this.pathExpressions.get(path)) {
|
|
const match = this.pathExpressions.get(path).regex.exec(url);
|
|
if (match) {
|
|
return this.pathExpressions.get(path);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
async applyMatches(matches) {
|
|
if (!matches) return;
|
|
const usedRouterViews = /* @__PURE__ */ new Set();
|
|
const renderRoutes = async (routeChain, rootNode) => {
|
|
for (const route of routeChain) {
|
|
if (route.view) {
|
|
const viewNode = this.knownRouterViewNames.get(route.view);
|
|
if (viewNode && await canEnterAndRenderRoute(viewNode, route)) {
|
|
continue;
|
|
}
|
|
} else if (rootNode && await canEnterAndRenderRoute(rootNode, route)) {
|
|
continue;
|
|
}
|
|
}
|
|
};
|
|
const canEnterAndRenderRoute = async (node, route) => {
|
|
const canEnter = await canEnterRoute(route);
|
|
if (canEnter) {
|
|
renderRouteAtNode(node, route);
|
|
return true;
|
|
} else {
|
|
if (route.componentFallback) {
|
|
renderRouteAtNode(node, route, route.componentFallback);
|
|
} else {
|
|
maybeRedirectRoute(route);
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
const renderRouteAtNode = (node, route, component) => {
|
|
if (!usedRouterViews.has(node) || this.populatedRouterViews.get(node)?.route !== route) {
|
|
const div = document.createElement("div");
|
|
node.replaceChildren(div);
|
|
const target = div.parentElement;
|
|
const block = new Block({
|
|
element: div,
|
|
component: component ? component : route.component,
|
|
replacementType: "replaceChildren",
|
|
parentContext: current.componentBlock.context
|
|
});
|
|
target.replaceChild(block.element, div);
|
|
this.populatedRouterViews.set(node, { block, route });
|
|
usedRouterViews.add(node);
|
|
}
|
|
};
|
|
for (const match of matches) {
|
|
const routeChain = [...match.parents, match.match.route];
|
|
const uniqueRouteChain = routeChain.filter((route, index, self) => index === self.findIndex((r) => r.path === route.path));
|
|
const rootNode = this.knownRouterViewNames.get("") ?? null;
|
|
await renderRoutes(uniqueRouteChain, rootNode);
|
|
}
|
|
for (const node of this.knownRouterViews.keys()) {
|
|
if (!usedRouterViews.has(node) && this.populatedRouterViews.has(node)) {
|
|
const entry = this.populatedRouterViews.get(node);
|
|
if (entry) {
|
|
entry.block.teardown();
|
|
this.populatedRouterViews.delete(node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
cacheRouteExpression(route, parentPath = "") {
|
|
const path = `${parentPath}${route.path}`.replace(/\/\//g, "/");
|
|
this.getRouteExpression(path, route);
|
|
if (route.children?.length) {
|
|
route.children.forEach((child) => {
|
|
this.cacheRouteExpression(child, path);
|
|
});
|
|
}
|
|
}
|
|
destroy() {
|
|
window.removeEventListener("popstate", this.onHistoryEvent.bind(this));
|
|
window.removeEventListener("pushstate", this.onHistoryEvent.bind(this));
|
|
window.removeEventListener("load", this.onHistoryEvent.bind(this));
|
|
}
|
|
};
|
|
|
|
// src/index.ts
|
|
function provide(key, value) {
|
|
if (!current.componentBlock) {
|
|
console.warn("Can't provide: no current component block");
|
|
}
|
|
current.componentBlock.provides.set(key, value);
|
|
}
|
|
function inject(key) {
|
|
if (!current.componentBlock) {
|
|
console.warn("Can't inject: no current component block");
|
|
}
|
|
let c = current.componentBlock;
|
|
while (c) {
|
|
if (c.provides.has(key)) {
|
|
return c.provides.get(key);
|
|
}
|
|
c = c.parentComponentBlock;
|
|
}
|
|
return void 0;
|
|
}
|
|
var App2 = class {
|
|
root;
|
|
registry = /* @__PURE__ */ new Map();
|
|
plugins = /* @__PURE__ */ new Set();
|
|
register(name, component) {
|
|
this.registry.set(name, component);
|
|
}
|
|
use(plugin, ...config) {
|
|
this.plugins.add(plugin);
|
|
plugin.use(this, ...config);
|
|
}
|
|
getComponent(tag) {
|
|
return this.registry.get(tag);
|
|
}
|
|
mount(component, target = "body", props = {}) {
|
|
const root = typeof target === "string" ? document.querySelector(target) : target;
|
|
const display = root.style.display;
|
|
root.style.display = "none";
|
|
this.root = this._mount(component, root, props);
|
|
root.style.display = display;
|
|
return this.root;
|
|
}
|
|
_mount(component, target, props) {
|
|
const parentContext = createContext({ app: this });
|
|
if (props) {
|
|
parentContext.scope = reactive(props);
|
|
bindContextMethods(parentContext.scope);
|
|
}
|
|
parentContext.scope.$isRef = isRef;
|
|
parentContext.scope.$isComputed = isComputed;
|
|
const block = new Block({
|
|
element: target,
|
|
parentContext,
|
|
component,
|
|
isRoot: true,
|
|
componentProps: props,
|
|
replacementType: "replaceChildren"
|
|
});
|
|
return block;
|
|
}
|
|
unmount() {
|
|
this.root.teardown();
|
|
}
|
|
};
|
|
function createContext({ parentContext, app }) {
|
|
const context = {
|
|
app: app ? app : parentContext && parentContext.app ? parentContext.app : null,
|
|
scope: parentContext ? parentContext.scope : reactive({}),
|
|
// scope: reactive({}),
|
|
blocks: [],
|
|
effects: [],
|
|
slots: [],
|
|
templates: parentContext ? parentContext.templates : [],
|
|
effect: (handler) => {
|
|
const e = effect(handler);
|
|
context.effects.push(e);
|
|
return e;
|
|
}
|
|
};
|
|
return context;
|
|
}
|
|
var createScopedContext = (ctx, data = {}) => {
|
|
const parentScope = ctx.scope;
|
|
const mergedScope = Object.create(parentScope);
|
|
Object.defineProperties(mergedScope, Object.getOwnPropertyDescriptors(data));
|
|
let proxy;
|
|
proxy = reactive(
|
|
new Proxy(mergedScope, {
|
|
set(target, key, val, receiver) {
|
|
if (receiver === proxy && !target.hasOwnProperty(key)) {
|
|
return Reflect.set(parentScope, key, val);
|
|
}
|
|
return Reflect.set(target, key, val, receiver);
|
|
}
|
|
})
|
|
);
|
|
bindContextMethods(proxy);
|
|
const out = {
|
|
...ctx,
|
|
scope: {
|
|
...ctx.scope,
|
|
...proxy
|
|
}
|
|
};
|
|
return out;
|
|
};
|
|
function bindContextMethods(scope) {
|
|
for (const key of Object.keys(scope)) {
|
|
if (typeof scope[key] === "function") {
|
|
scope[key] = scope[key].bind(scope);
|
|
}
|
|
}
|
|
}
|
|
function mergeProps(props, defaultProps) {
|
|
const merged = {};
|
|
Object.keys(defaultProps).forEach((defaultProp) => {
|
|
const propValue = props.hasOwnProperty(defaultProp) ? props[defaultProp] : defaultProps[defaultProp]?.default;
|
|
merged[defaultProp] = reactive(typeof propValue === "function" ? propValue() : propValue);
|
|
});
|
|
return merged;
|
|
}
|
|
var current = { componentBlock: void 0 };
|
|
var Block = class {
|
|
element;
|
|
context;
|
|
parentContext;
|
|
component;
|
|
provides = /* @__PURE__ */ new Map();
|
|
parentComponentBlock;
|
|
componentProps;
|
|
allProps;
|
|
isFragment;
|
|
start;
|
|
end;
|
|
key;
|
|
constructor(opts) {
|
|
this.isFragment = opts.element instanceof HTMLTemplateElement;
|
|
this.parentComponentBlock = opts.parentComponentBlock;
|
|
if (opts.component) {
|
|
current.componentBlock = this;
|
|
this.element = stringToElement(opts.component.template);
|
|
} else {
|
|
if (this.isFragment) {
|
|
this.element = opts.element.content.cloneNode(true);
|
|
} else if (typeof opts.element === "string") {
|
|
this.element = stringToElement(opts.element);
|
|
} else {
|
|
this.element = opts.element.cloneNode(true);
|
|
opts.element.replaceWith(this.element);
|
|
}
|
|
}
|
|
if (opts.isRoot) {
|
|
this.context = opts.parentContext;
|
|
} else {
|
|
this.parentContext = opts.parentContext ? opts.parentContext : createContext({});
|
|
this.parentContext.blocks.push(this);
|
|
this.context = createContext({ parentContext: opts.parentContext });
|
|
}
|
|
if (opts.component) {
|
|
this.componentProps = mergeProps(opts.componentProps ?? {}, opts.component.props ?? {});
|
|
if (opts.component.main) {
|
|
this.context.scope = {
|
|
...opts.component.main(this.componentProps) || {}
|
|
};
|
|
}
|
|
}
|
|
opts.allProps?.forEach((prop) => {
|
|
if (prop.isBind) {
|
|
this.context.effect(() => {
|
|
let newValue;
|
|
if (prop.isSpread) {
|
|
const spreadProps = evalGet(this.parentContext.scope, prop.extractedName);
|
|
if (isObject(spreadProps)) {
|
|
Object.keys(spreadProps).forEach((key) => {
|
|
newValue = spreadProps[key];
|
|
this.setProp(key, newValue);
|
|
});
|
|
}
|
|
} else {
|
|
newValue = prop.isMirror ? evalGet(this.parentContext.scope, prop.extractedName) : evalGet(this.parentContext.scope, prop.exp);
|
|
this.setProp(prop.extractedName, newValue);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
this.context.slots = findSlotNodes(this.element);
|
|
this.context.templates = opts.templates ?? [];
|
|
this.context.slots.forEach((slot) => {
|
|
const template = this.context.templates.find((t) => t.targetSlotName === slot.name);
|
|
if (template) {
|
|
const templateContents = template.node.content.cloneNode(true);
|
|
slot.node.replaceWith(templateContents);
|
|
}
|
|
});
|
|
this.context.scope.$isRef = isRef;
|
|
this.context.scope.$isComputed = isComputed;
|
|
walk(this.element, this.context);
|
|
if (opts.component) {
|
|
if (opts.replacementType === "replace") {
|
|
if (opts.element instanceof HTMLElement) {
|
|
opts.element.replaceWith(this.element);
|
|
}
|
|
} else {
|
|
if (opts.element instanceof HTMLElement) {
|
|
opts.element.replaceChildren(this.element);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
setProp(name, value) {
|
|
if (isRef(this.componentProps[name])) {
|
|
this.componentProps[name].value = value;
|
|
} else {
|
|
this.componentProps[name] = value;
|
|
}
|
|
}
|
|
insert(parent, anchor = null) {
|
|
if (this.isFragment) {
|
|
if (this.start) {
|
|
let node = this.start;
|
|
let next;
|
|
while (node) {
|
|
next = node.nextSibling;
|
|
parent.insertBefore(node, anchor);
|
|
if (node === this.end) {
|
|
break;
|
|
}
|
|
node = next;
|
|
}
|
|
} else {
|
|
this.start = new Text("");
|
|
this.end = new Text("");
|
|
parent.insertBefore(this.end, anchor);
|
|
parent.insertBefore(this.start, this.end);
|
|
parent.insertBefore(this.element, this.end);
|
|
}
|
|
} else {
|
|
parent.insertBefore(this.element, anchor);
|
|
}
|
|
}
|
|
remove() {
|
|
if (this.parentContext) {
|
|
const i = this.parentContext.blocks.indexOf(this);
|
|
if (i > -1) {
|
|
this.parentContext.blocks.splice(i, 1);
|
|
}
|
|
}
|
|
if (this.start) {
|
|
const parent = this.start.parentNode;
|
|
let node = this.start;
|
|
let next;
|
|
while (node) {
|
|
next = node.nextSibling;
|
|
parent.removeChild(node);
|
|
if (node === this.end) {
|
|
break;
|
|
}
|
|
node = next;
|
|
}
|
|
} else {
|
|
this.element.remove();
|
|
}
|
|
this.teardown();
|
|
}
|
|
teardown() {
|
|
this.context.blocks.forEach((block) => {
|
|
block.teardown();
|
|
});
|
|
this.context.effects.forEach(stop);
|
|
}
|
|
};
|
|
function isComponent(element, context) {
|
|
return !!context.app.getComponent(element.tagName.toLowerCase());
|
|
}
|
|
function warnInvalidDirectives(node, directives) {
|
|
if (directives.every((d) => node.hasAttribute(d))) {
|
|
console.warn(`These directives cannot be used together on the same node:`, directives);
|
|
console.warn("Node ignored:", node);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
function walk(node, context) {
|
|
if (isText(node)) {
|
|
new InterpolationDirective({ element: node, context });
|
|
return;
|
|
}
|
|
if (isElement(node)) {
|
|
let exp;
|
|
const handleDirectives = (node2, context2, component, componentProps, allProps) => {
|
|
if (warnInvalidDirectives(node2, [":if", ":for"])) return;
|
|
if (warnInvalidDirectives(node2, [":if", ":teleport"])) return;
|
|
if (warnInvalidDirectives(node2, [":for", ":teleport"])) return;
|
|
if (exp = checkAndRemoveAttribute(node2, ":scope")) {
|
|
const scope = evalGet(context2.scope, exp, node2);
|
|
if (typeof scope === "object") {
|
|
Object.assign(context2.scope, scope);
|
|
}
|
|
}
|
|
if (exp = checkAndRemoveAttribute(node2, ":teleport")) {
|
|
return _teleport(node2, exp, context2);
|
|
}
|
|
if (exp = checkAndRemoveAttribute(node2, ":if")) {
|
|
return _if(node2, exp, context2, component, componentProps, allProps);
|
|
}
|
|
if (exp = checkAndRemoveAttribute(node2, ":for")) {
|
|
return _for(node2, exp, context2, component, componentProps, allProps);
|
|
}
|
|
if (exp = checkAndRemoveAttribute(node2, ":show")) {
|
|
new ShowDirective({ element: node2, context: context2, expression: exp });
|
|
}
|
|
if (exp = checkAndRemoveAttribute(node2, ":ref")) {
|
|
context2.scope[exp].value = node2;
|
|
}
|
|
if (exp = checkAndRemoveAttribute(node2, ":value")) {
|
|
new ValueDirective({ element: node2, context: context2, expression: exp });
|
|
}
|
|
if (exp = checkAndRemoveAttribute(node2, ":html")) {
|
|
context2.effect(() => {
|
|
const result = evalGet(context2.scope, exp, node2);
|
|
if (result instanceof Element) {
|
|
node2.replaceChildren();
|
|
node2.append(result);
|
|
} else {
|
|
node2.innerHTML = result;
|
|
}
|
|
});
|
|
}
|
|
if (exp = checkAndRemoveAttribute(node2, ":text")) {
|
|
context2.effect(() => {
|
|
node2.textContent = toDisplayString(evalGet(context2.scope, exp, node2));
|
|
});
|
|
}
|
|
};
|
|
const processAttributes = (node2, component) => {
|
|
return Array.from(node2.attributes).filter((attr) => isSpreadProp(attr.name) || isMirrorProp(attr.name) || isRegularProp(attr.name) && componentHasPropByName(extractPropName(attr.name), component)).map((attr) => ({
|
|
isMirror: isMirrorProp(attr.name),
|
|
isSpread: isSpreadProp(attr.name),
|
|
isBind: attr.name.includes("bind"),
|
|
originalName: attr.name,
|
|
extractedName: extractPropName(attr.name),
|
|
exp: attr.value,
|
|
value: isMirrorProp(attr.name) ? evalGet(context.scope, extractPropName(attr.name), node2) : attr.value ? evalGet(context.scope, attr.value, node2) : void 0
|
|
}));
|
|
};
|
|
if (isComponent(node, context)) {
|
|
const component = context.app.getComponent(node.tagName.toLowerCase());
|
|
const allProps = processAttributes(node, component);
|
|
const componentProps = allProps.reduce((acc, { isSpread, isMirror, extractedName, value }) => {
|
|
if (isSpread) {
|
|
const spread = evalGet(context.scope, extractedName, node);
|
|
if (isObject(spread)) Object.assign(acc, spread);
|
|
} else if (isMirror) {
|
|
acc[extractedName] = evalGet(context.scope, extractedName, node);
|
|
} else {
|
|
acc[extractedName] = value;
|
|
}
|
|
return acc;
|
|
}, {});
|
|
const next2 = handleDirectives(node, context, component, componentProps, allProps);
|
|
if (next2) return next2;
|
|
const templates = findTemplateNodes(node);
|
|
return new Block({
|
|
element: node,
|
|
parentContext: context,
|
|
component,
|
|
replacementType: "replace",
|
|
parentComponentBlock: current.componentBlock,
|
|
templates,
|
|
componentProps,
|
|
allProps
|
|
}).element;
|
|
}
|
|
const next = handleDirectives(node, context);
|
|
if (next) return next;
|
|
Array.from(node.attributes).forEach((attr) => {
|
|
if (isPropAttribute(attr.name)) {
|
|
new AttributeDirective({ element: node, context, attr });
|
|
}
|
|
if (isEventAttribute(attr.name)) {
|
|
new EventDirective({ element: node, context, attr });
|
|
}
|
|
});
|
|
walkChildren(node, context);
|
|
}
|
|
}
|
|
function walkChildren(node, context) {
|
|
let child = node.firstChild;
|
|
while (child) {
|
|
child = walk(child, context) || child.nextSibling;
|
|
}
|
|
}
|
|
var evalFuncCache = {};
|
|
function evalGet(scope, exp, el) {
|
|
if (!exp.trim()) return void 0;
|
|
return execute(scope, `const ___value = (${exp.trim()}); return ___value;`, el);
|
|
}
|
|
function evalSet(scope, exp, value) {
|
|
value = typeof value === "string" ? `"${value}"` : value;
|
|
return execute(scope, `const ___target = (${exp.trim()}); return $isRef(___target) ? ___target.value = ${value} : ___target = ${value};`, null, false);
|
|
}
|
|
function execute(scope, exp, el, flatRefs = true) {
|
|
const newScope = flatRefs ? flattenRefs(scope) : scope;
|
|
const fn = evalFuncCache[exp] || (evalFuncCache[exp] = toFunction(exp));
|
|
try {
|
|
return fn(newScope, el);
|
|
} catch (e) {
|
|
console.warn(`Error evaluating expression: "${exp}":`);
|
|
console.error(e);
|
|
}
|
|
}
|
|
function toFunction(exp) {
|
|
try {
|
|
return new Function("$data", "$el", `with($data){${exp}}`);
|
|
} catch (e) {
|
|
console.error(`${e.message} in expression: ${exp}`);
|
|
return () => {
|
|
};
|
|
}
|
|
}
|
|
function flattenRefs(scope) {
|
|
const mapped = {};
|
|
for (const key in scope) {
|
|
if (scope.hasOwnProperty(key)) {
|
|
if (isRef(scope[key])) {
|
|
mapped[key] = scope[key].value;
|
|
} else {
|
|
mapped[key] = scope[key];
|
|
}
|
|
}
|
|
}
|
|
return mapped;
|
|
}
|
|
export {
|
|
App2 as App,
|
|
Block,
|
|
RouterPlugin,
|
|
createContext,
|
|
createScopedContext,
|
|
current,
|
|
evalGet,
|
|
evalSet,
|
|
inject,
|
|
provide
|
|
};
|
|
//# sourceMappingURL=index.js.map
|