soma3/public/demo.js
2024-10-20 20:04:29 -04:00

1282 lines
38 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;
}
};
}
// 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 };
}
};
// src/index.ts
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: app2 }) {
const context = {
app: app2 ? app2 : 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 child2 = node.firstChild;
while (child2) {
child2 = walk(child2, context) || child2.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;
}
// src/demo.ts
var child = {
template: html`
<div>
hello from child, food: "{{food}}" (does not inherit)
<div>
<slot />
</div>
</div>
`,
main() {
const food = ref("\u{1F354}");
return { food };
}
};
var main = {
template: html`
<div class="hero sans-serif f2">
<div :scope="{ food: '🍕' }">
<!-- <div> -->
<div>Scoped data: {{food}}</div>
<child>Child slot, food: {{food}}</child>
<div :if="food === 'nothing'">No pizza 😢</div>
<div :else-if="food === '🍕'">Pizza!</div>
</div>
</div>
`,
main() {
return { food: ref("nothing") };
}
};
var app = new App2();
app.register("child", child);
app.mount(main, "#app");
//# sourceMappingURL=demo.js.map