mirror of
https://github.com/nvms/soma3.git
synced 2025-12-15 15:10:53 +00:00
ok
This commit is contained in:
parent
e0f4945b1b
commit
02730daae5
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
node_modules
|
node_modules
|
||||||
|
.aider*
|
||||||
|
|||||||
300
public/demo.js
300
public/demo.js
@ -412,11 +412,12 @@ function _if(el, exp, ctx, component, componentProps, allProps) {
|
|||||||
let block;
|
let block;
|
||||||
let activeBranchIndex = -1;
|
let activeBranchIndex = -1;
|
||||||
const removeActiveBlock = () => {
|
const removeActiveBlock = () => {
|
||||||
if (block) {
|
if (!block) {
|
||||||
parent.insertBefore(anchor, block.element);
|
return;
|
||||||
block.remove();
|
|
||||||
block = void 0;
|
|
||||||
}
|
}
|
||||||
|
parent.insertBefore(anchor, block.element);
|
||||||
|
block.remove();
|
||||||
|
block = void 0;
|
||||||
};
|
};
|
||||||
ctx.effect(() => {
|
ctx.effect(() => {
|
||||||
for (let i = 0; i < branches.length; i++) {
|
for (let i = 0; i < branches.length; i++) {
|
||||||
@ -730,18 +731,6 @@ var $computed = Symbol("computed");
|
|||||||
function isComputed(value) {
|
function isComputed(value) {
|
||||||
return isObject(value) && value[$computed];
|
return isObject(value) && value[$computed];
|
||||||
}
|
}
|
||||||
function computed(getter) {
|
|
||||||
const ref2 = {
|
|
||||||
get value() {
|
|
||||||
return getter();
|
|
||||||
},
|
|
||||||
[$computed]: true
|
|
||||||
};
|
|
||||||
effect(() => {
|
|
||||||
getter();
|
|
||||||
});
|
|
||||||
return ref2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// src/reactivity/ref.ts
|
// src/reactivity/ref.ts
|
||||||
var $ref = Symbol("ref");
|
var $ref = Symbol("ref");
|
||||||
@ -859,7 +848,7 @@ var App2 = class {
|
|||||||
}
|
}
|
||||||
mount(component, target = "body", props = {}) {
|
mount(component, target = "body", props = {}) {
|
||||||
const root = typeof target === "string" ? document.querySelector(target) : target;
|
const root = typeof target === "string" ? document.querySelector(target) : target;
|
||||||
const display = root.style.display;
|
const { display } = root.style;
|
||||||
root.style.display = "none";
|
root.style.display = "none";
|
||||||
this._mount(component, root, props);
|
this._mount(component, root, props);
|
||||||
root.style.display = display;
|
root.style.display = display;
|
||||||
@ -873,7 +862,7 @@ var App2 = class {
|
|||||||
}
|
}
|
||||||
parentContext.scope.$isRef = isRef;
|
parentContext.scope.$isRef = isRef;
|
||||||
parentContext.scope.$isComputed = isComputed;
|
parentContext.scope.$isComputed = isComputed;
|
||||||
const block = new Block({
|
return new Block({
|
||||||
app: this,
|
app: this,
|
||||||
element: target,
|
element: target,
|
||||||
parentContext,
|
parentContext,
|
||||||
@ -882,7 +871,6 @@ var App2 = class {
|
|||||||
componentProps: props,
|
componentProps: props,
|
||||||
replacementType: "replaceChildren"
|
replacementType: "replaceChildren"
|
||||||
});
|
});
|
||||||
return block;
|
|
||||||
}
|
}
|
||||||
unmount() {
|
unmount() {
|
||||||
this.root.teardown();
|
this.root.teardown();
|
||||||
@ -890,7 +878,7 @@ var App2 = class {
|
|||||||
};
|
};
|
||||||
function createContext({ parentContext, app: app2 }) {
|
function createContext({ parentContext, app: app2 }) {
|
||||||
const context = {
|
const context = {
|
||||||
app: app2 ? app2 : parentContext && parentContext.app ? parentContext.app : null,
|
app: app2 ? app2 : parentContext?.app ? parentContext.app : null,
|
||||||
scope: parentContext ? parentContext.scope : reactive({}),
|
scope: parentContext ? parentContext.scope : reactive({}),
|
||||||
blocks: [],
|
blocks: [],
|
||||||
effects: [],
|
effects: [],
|
||||||
@ -944,7 +932,7 @@ function mergeProps(props, defaultProps) {
|
|||||||
});
|
});
|
||||||
return merged;
|
return merged;
|
||||||
}
|
}
|
||||||
var current = { componentBlock: void 0 };
|
var current2 = { componentBlock: void 0 };
|
||||||
var Block = class {
|
var Block = class {
|
||||||
element;
|
element;
|
||||||
context;
|
context;
|
||||||
@ -962,17 +950,15 @@ var Block = class {
|
|||||||
this.isFragment = opts.element instanceof HTMLTemplateElement;
|
this.isFragment = opts.element instanceof HTMLTemplateElement;
|
||||||
this.parentComponentBlock = opts.parentComponentBlock;
|
this.parentComponentBlock = opts.parentComponentBlock;
|
||||||
if (opts.component) {
|
if (opts.component) {
|
||||||
current.componentBlock = this;
|
current2.componentBlock = this;
|
||||||
this.element = stringToElement(opts.component.template);
|
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 {
|
} else {
|
||||||
if (this.isFragment) {
|
this.element = opts.element.cloneNode(true);
|
||||||
this.element = opts.element.content.cloneNode(true);
|
opts.element.replaceWith(this.element);
|
||||||
} 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) {
|
if (opts.isRoot) {
|
||||||
this.context = opts.parentContext;
|
this.context = opts.parentContext;
|
||||||
@ -1026,10 +1012,8 @@ var Block = class {
|
|||||||
if (opts.element instanceof HTMLElement) {
|
if (opts.element instanceof HTMLElement) {
|
||||||
opts.element.replaceWith(this.element);
|
opts.element.replaceWith(this.element);
|
||||||
}
|
}
|
||||||
} else {
|
} else if (opts.element instanceof HTMLElement) {
|
||||||
if (opts.element instanceof HTMLElement) {
|
opts.element.replaceChildren(this.element);
|
||||||
opts.element.replaceChildren(this.element);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1111,112 +1095,111 @@ function walk(node, context) {
|
|||||||
new InterpolationDirective({ element: node, context });
|
new InterpolationDirective({ element: node, context });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isElement(node)) {
|
if (!isElement(node)) {
|
||||||
let exp;
|
return;
|
||||||
const handleDirectives = (node2, context2, component, componentProps, allProps) => {
|
|
||||||
if (warnInvalidDirectives(node2, [":if", ":for"])) return;
|
|
||||||
if (warnInvalidDirectives(node2, [":for", ":teleport"])) return;
|
|
||||||
if (warnInvalidDirectives(node2, [":if", ":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, ":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, ":teleport")) {
|
|
||||||
return _teleport(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")) {
|
|
||||||
const htmlExp = exp;
|
|
||||||
context2.effect(() => {
|
|
||||||
const result = evalGet(context2.scope, htmlExp, node2);
|
|
||||||
if (result instanceof Element) {
|
|
||||||
node2.replaceChildren();
|
|
||||||
node2.append(result);
|
|
||||||
} else {
|
|
||||||
node2.innerHTML = result;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (exp = checkAndRemoveAttribute(node2, ":text")) {
|
|
||||||
const textExp = exp;
|
|
||||||
context2.effect(() => {
|
|
||||||
node2.textContent = toDisplayString(evalGet(context2.scope, textExp, 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,
|
|
||||||
app: current.componentBlock.context.app,
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
let exp;
|
||||||
|
const handleDirectives = (node2, context2, component, componentProps, allProps) => {
|
||||||
|
if (warnInvalidDirectives(node2, [":if", ":for"])) return;
|
||||||
|
if (warnInvalidDirectives(node2, [":for", ":teleport"])) return;
|
||||||
|
if (warnInvalidDirectives(node2, [":if", ":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, ":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, ":teleport")) {
|
||||||
|
return _teleport(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")) {
|
||||||
|
const htmlExp = exp;
|
||||||
|
context2.effect(() => {
|
||||||
|
const result = evalGet(context2.scope, htmlExp, node2);
|
||||||
|
if (result instanceof Element) {
|
||||||
|
node2.replaceChildren();
|
||||||
|
node2.append(result);
|
||||||
|
} else {
|
||||||
|
node2.innerHTML = result;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (exp = checkAndRemoveAttribute(node2, ":text")) {
|
||||||
|
const textExp = exp;
|
||||||
|
context2.effect(() => {
|
||||||
|
node2.textContent = toDisplayString(evalGet(context2.scope, textExp, node2));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const processAttributes = (node2, component) => 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,
|
||||||
|
app: current2.componentBlock.context.app,
|
||||||
|
// parentContext: context,
|
||||||
|
component,
|
||||||
|
replacementType: "replace",
|
||||||
|
parentComponentBlock: current2.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) {
|
function walkChildren(node, context) {
|
||||||
let child = node.firstChild;
|
let child2 = node.firstChild;
|
||||||
while (child) {
|
while (child2) {
|
||||||
child = walk(child, context) || child.nextSibling;
|
child2 = walk(child2, context) || child2.nextSibling;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var evalFuncCache = {};
|
var evalFuncCache = {};
|
||||||
@ -1251,38 +1234,41 @@ function flattenRefs(scope) {
|
|||||||
const mapped = {};
|
const mapped = {};
|
||||||
for (const key in scope) {
|
for (const key in scope) {
|
||||||
if (scope.hasOwnProperty(key)) {
|
if (scope.hasOwnProperty(key)) {
|
||||||
if (isRef(scope[key])) {
|
mapped[key] = isRef(scope[key]) ? scope[key].value : scope[key];
|
||||||
mapped[key] = scope[key].value;
|
|
||||||
} else {
|
|
||||||
mapped[key] = scope[key];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return mapped;
|
return mapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/demo.ts
|
// src/demo.ts
|
||||||
var main = {
|
var child = {
|
||||||
template: html`
|
template: html`
|
||||||
<div class="sans-serif margin-y-3 container" style="--column-gap: .5rem; --row-gap: .5rem;">
|
<div>
|
||||||
<h1 class="f1 margin-bottom-1 color-60">phase</h1>
|
I am child and I have a cheeseburger: "{{food}}" (does not inherit)
|
||||||
<h2 class="f3 margin-bottom-1 color-peach-50">Colors</h2>
|
<div>
|
||||||
<grid columns="6" class="f6 white-space-nowrap">
|
<slot />
|
||||||
<div :for="variant in ['base', 'accent', 'red', 'rose', 'orange', 'amber', 'yellow', 'lime', 'green', 'emerald', 'teal', 'cyan', 'sky', 'blue', 'indigo', 'lavender', 'violet', 'purple', 'plum', 'fuchsia', 'pink', 'peach']" class="border-color-30 border-2px">
|
</div>
|
||||||
<div :for="rank, index in ranks">
|
|
||||||
<div .style:bind="bg(variant, rank, index)" class="padding-1">{{variant}}-{{rank}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</grid>
|
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
main() {
|
main() {
|
||||||
const ranks = reactive(["5", "10", "20", "30", "40", "50", "60", "70", "80", "90"]);
|
const food = ref("\u{1F354}");
|
||||||
const basesReverse = computed(() => Array.from(ranks).reverse());
|
return { food };
|
||||||
const bg = (variant, rank, index) => ({ backgroundColor: `var(--${variant}-${rank})`, color: `var(--${variant}-${basesReverse.value[index]})` });
|
}
|
||||||
return { ranks, bg };
|
};
|
||||||
|
var main = {
|
||||||
|
template: html`
|
||||||
|
<div class="hero sans-serif f2" :scope="{ drink: '🍹' }">
|
||||||
|
<div :scope="{ food: '🍕' }">
|
||||||
|
<div>Parent has pizza: {{food}} and scoped drink: {{drink}}</div>
|
||||||
|
<child>Child slot, food: {{food}} {{drink}}</child>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
main() {
|
||||||
|
return { food: ref("nothing") };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var app = new App2();
|
var app = new App2();
|
||||||
|
app.register("child", child);
|
||||||
app.mount(main, "#app");
|
app.mount(main, "#app");
|
||||||
//# sourceMappingURL=demo.js.map
|
//# sourceMappingURL=demo.js.map
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
111
src/demo.ts
111
src/demo.ts
@ -1,6 +1,7 @@
|
|||||||
import { App } from ".";
|
import { App } from ".";
|
||||||
import { computed } from "./reactivity/computed";
|
import { computed } from "./reactivity/computed";
|
||||||
import { reactive } from "./reactivity/reactive";
|
import { reactive } from "./reactivity/reactive";
|
||||||
|
import { ref } from "./reactivity/ref";
|
||||||
import { html } from "./util";
|
import { html } from "./util";
|
||||||
|
|
||||||
// ------------------------------------------------
|
// ------------------------------------------------
|
||||||
@ -188,15 +189,15 @@ import { html } from "./util";
|
|||||||
// const style = reactive({ color: "gray" });
|
// const style = reactive({ color: "gray" });
|
||||||
// const increment = () => count.value++;
|
// const increment = () => count.value++;
|
||||||
// const decrement = () => count.value--;
|
// const decrement = () => count.value--;
|
||||||
//
|
|
||||||
// setInterval(() => {
|
// setInterval(() => {
|
||||||
// style.color = style.color === "gray" ? "white" : "gray";
|
// style.color = style.color === "gray" ? "white" : "gray";
|
||||||
// }, 500);
|
// }, 500);
|
||||||
//
|
|
||||||
// return { count, increment, decrement, style };
|
// return { count, increment, decrement, style };
|
||||||
// },
|
// },
|
||||||
// };
|
// };
|
||||||
//
|
|
||||||
// const app = new App();
|
// const app = new App();
|
||||||
// app.mount(counter, "#app");
|
// app.mount(counter, "#app");
|
||||||
|
|
||||||
@ -219,11 +220,11 @@ import { html } from "./util";
|
|||||||
// main() {
|
// main() {
|
||||||
// const items = reactive([1, 2, 3, 4, 5]);
|
// const items = reactive([1, 2, 3, 4, 5]);
|
||||||
// const bool = ref(true);
|
// const bool = ref(true);
|
||||||
// setInterval(() => (bool.value = !bool.value), 250);
|
// setInterval(() => (bool.value = !bool.value), 2050);
|
||||||
// return { items, bool };
|
// return { items, bool };
|
||||||
// },
|
// },
|
||||||
// };
|
// };
|
||||||
//
|
|
||||||
// const app = new App();
|
// const app = new App();
|
||||||
// app.mount(main, "#app");
|
// app.mount(main, "#app");
|
||||||
|
|
||||||
@ -249,67 +250,67 @@ import { html } from "./util";
|
|||||||
|
|
||||||
// ------------------------------------------------
|
// ------------------------------------------------
|
||||||
// Colors from css framework
|
// Colors from css framework
|
||||||
const main = {
|
|
||||||
template: html`
|
|
||||||
<div class="sans-serif margin-y-3 container" style="--column-gap: .5rem; --row-gap: .5rem;">
|
|
||||||
<h1 class="f1 margin-bottom-1 color-60">phase</h1>
|
|
||||||
<h2 class="f3 margin-bottom-1 color-peach-50">Colors</h2>
|
|
||||||
<grid columns="6" class="f6 white-space-nowrap">
|
|
||||||
<div :for="variant in ['base', 'accent', 'red', 'rose', 'orange', 'amber', 'yellow', 'lime', 'green', 'emerald', 'teal', 'cyan', 'sky', 'blue', 'indigo', 'lavender', 'violet', 'purple', 'plum', 'fuchsia', 'pink', 'peach']" class="border-color-30 border-2px">
|
|
||||||
<div :for="rank, index in ranks">
|
|
||||||
<div .style:bind="bg(variant, rank, index)" class="padding-1">{{variant}}-{{rank}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</grid>
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
main() {
|
|
||||||
const ranks = reactive(["5", "10", "20", "30", "40", "50", "60", "70", "80", "90"]);
|
|
||||||
const basesReverse = computed(() => Array.from(ranks).reverse());
|
|
||||||
const bg = (variant: string, rank: string, index: number) => ({ backgroundColor: `var(--${variant}-${rank})`, color: `var(--${variant}-${basesReverse.value[index]})` });
|
|
||||||
|
|
||||||
return { ranks, bg };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const app = new App();
|
|
||||||
app.mount(main, "#app");
|
|
||||||
|
|
||||||
// ------------------------------------------------
|
|
||||||
// :scope
|
|
||||||
// const child = {
|
|
||||||
// template: html`
|
|
||||||
// <div>
|
|
||||||
// I am child and I have food: "{{food}}" (does not inherit)
|
|
||||||
// <div>
|
|
||||||
// <slot />
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// `,
|
|
||||||
// main() {
|
|
||||||
// const food = ref("🍔");
|
|
||||||
// return { food };
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const main = {
|
// const main = {
|
||||||
// template: html`
|
// template: html`
|
||||||
// <div class="hero sans-serif f2" :scope="{ drink: '🍹' }">
|
// <div class="sans-serif margin-y-3 container" style="--column-gap: .5rem; --row-gap: .5rem;">
|
||||||
// <div :scope="{ food: '🍕' }">
|
// <h1 class="f1 margin-bottom-1 color-60">phase</h1>
|
||||||
// <div>Scoped food: {{food}} and scoped drink: {{drink}}</div>
|
// <h2 class="f3 margin-bottom-1 color-peach-50">Colors</h2>
|
||||||
// <child>Child slot, food: {{food}} {{drink}}</child>
|
// <grid columns="6" class="f6 white-space-nowrap">
|
||||||
// </div>
|
// <div :for="variant in ['base', 'accent', 'red', 'rose', 'orange', 'amber', 'yellow', 'lime', 'green', 'emerald', 'teal', 'cyan', 'sky', 'blue', 'indigo', 'lavender', 'violet', 'purple', 'plum', 'fuchsia', 'pink', 'peach']" class="border-color-30 border-2px">
|
||||||
|
// <div :for="rank, index in ranks">
|
||||||
|
// <div .style:bind="bg(variant, rank, index)" class="padding-1">{{variant}}-{{rank}}</div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </grid>
|
||||||
// </div>
|
// </div>
|
||||||
// `,
|
// `,
|
||||||
// main() {
|
// main() {
|
||||||
// return { food: ref("nothing") };
|
// const ranks = reactive(["5", "10", "20", "30", "40", "50", "60", "70", "80", "90"]);
|
||||||
|
// const basesReverse = computed(() => Array.from(ranks).reverse());
|
||||||
|
// const bg = (variant: string, rank: string, index: number) => ({ backgroundColor: `var(--${variant}-${rank})`, color: `var(--${variant}-${basesReverse.value[index]})` });
|
||||||
|
|
||||||
|
// return { ranks, bg };
|
||||||
// },
|
// },
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// const app = new App();
|
// const app = new App();
|
||||||
// app.register("child", child);
|
|
||||||
// app.mount(main, "#app");
|
// app.mount(main, "#app");
|
||||||
|
|
||||||
|
// ------------------------------------------------
|
||||||
|
// :scope
|
||||||
|
const child = {
|
||||||
|
template: html`
|
||||||
|
<div>
|
||||||
|
I am child and I have a cheeseburger: "{{food}}" (does not inherit)
|
||||||
|
<div>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
main() {
|
||||||
|
const food = ref("🍔");
|
||||||
|
return { food };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const main = {
|
||||||
|
template: html`
|
||||||
|
<div class="hero sans-serif f2" :scope="{ drink: '🍹' }">
|
||||||
|
<div :scope="{ food: '🍕' }">
|
||||||
|
<div>Parent has pizza: {{food}} and scoped drink: {{drink}}</div>
|
||||||
|
<child>Child slot, food: {{food}} {{drink}}</child>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
main() {
|
||||||
|
return { food: ref("nothing") };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const app = new App();
|
||||||
|
app.register("child", child);
|
||||||
|
app.mount(main, "#app");
|
||||||
|
|
||||||
// ------------------------------------------------
|
// ------------------------------------------------
|
||||||
// Practical :scope demo
|
// Practical :scope demo
|
||||||
// const main = {
|
// const main = {
|
||||||
|
|||||||
@ -35,11 +35,11 @@ export function _if(el: Element, exp: string, ctx: Context, component?: Componen
|
|||||||
let activeBranchIndex = -1;
|
let activeBranchIndex = -1;
|
||||||
|
|
||||||
const removeActiveBlock = () => {
|
const removeActiveBlock = () => {
|
||||||
if (block) {
|
if (!block) { return; }
|
||||||
parent.insertBefore(anchor, block.element);
|
|
||||||
block.remove();
|
parent.insertBefore(anchor, block.element);
|
||||||
block = undefined;
|
block.remove();
|
||||||
}
|
block = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.effect(() => {
|
ctx.effect(() => {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Context, evalGet } from "../";
|
import { Context, current, evalGet } from "../";
|
||||||
import { insertAfter, toDisplayString } from "../util";
|
import { insertAfter, toDisplayString } from "../util";
|
||||||
|
|
||||||
interface InterpolationDirectiveOptions {
|
interface InterpolationDirectiveOptions {
|
||||||
|
|||||||
258
src/index.ts
258
src/index.ts
@ -80,7 +80,7 @@ export class App {
|
|||||||
|
|
||||||
mount(component: Component, target: string | HTMLElement = "body", props: Record<string, any> = {}) {
|
mount(component: Component, target: string | HTMLElement = "body", props: Record<string, any> = {}) {
|
||||||
const root = typeof target === "string" ? (document.querySelector(target) as HTMLElement) : target;
|
const root = typeof target === "string" ? (document.querySelector(target) as HTMLElement) : target;
|
||||||
const display = root.style.display;
|
const { display } = root.style;
|
||||||
root.style.display = "none";
|
root.style.display = "none";
|
||||||
this._mount(component, root, props);
|
this._mount(component, root, props);
|
||||||
root.style.display = display;
|
root.style.display = display;
|
||||||
@ -98,7 +98,7 @@ export class App {
|
|||||||
parentContext.scope.$isRef = isRef;
|
parentContext.scope.$isRef = isRef;
|
||||||
parentContext.scope.$isComputed = isComputed;
|
parentContext.scope.$isComputed = isComputed;
|
||||||
|
|
||||||
const block = new Block({
|
return new Block({
|
||||||
app: this,
|
app: this,
|
||||||
element: target,
|
element: target,
|
||||||
parentContext,
|
parentContext,
|
||||||
@ -107,8 +107,6 @@ export class App {
|
|||||||
componentProps: props,
|
componentProps: props,
|
||||||
replacementType: "replaceChildren",
|
replacementType: "replaceChildren",
|
||||||
});
|
});
|
||||||
|
|
||||||
return block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unmount() {
|
unmount() {
|
||||||
@ -134,7 +132,7 @@ interface CreateContextOptions {
|
|||||||
|
|
||||||
export function createContext({ parentContext, app }: CreateContextOptions): Context {
|
export function createContext({ parentContext, app }: CreateContextOptions): Context {
|
||||||
const context: Context = {
|
const context: Context = {
|
||||||
app: app ? app : parentContext && parentContext.app ? parentContext.app : null,
|
app: app ? app : parentContext?.app ? parentContext.app : null,
|
||||||
scope: parentContext ? parentContext.scope : reactive({}),
|
scope: parentContext ? parentContext.scope : reactive({}),
|
||||||
blocks: [],
|
blocks: [],
|
||||||
effects: [],
|
effects: [],
|
||||||
@ -251,15 +249,13 @@ export class Block {
|
|||||||
if (opts.component) {
|
if (opts.component) {
|
||||||
current.componentBlock = this;
|
current.componentBlock = this;
|
||||||
this.element = stringToElement(opts.component.template);
|
this.element = stringToElement(opts.component.template);
|
||||||
|
} else if (this.isFragment) {
|
||||||
|
this.element = (opts.element as HTMLTemplateElement).content.cloneNode(true) as Element;
|
||||||
|
} else if (typeof opts.element === "string") {
|
||||||
|
this.element = stringToElement(opts.element);
|
||||||
} else {
|
} else {
|
||||||
if (this.isFragment) {
|
this.element = opts.element.cloneNode(true) as Element;
|
||||||
this.element = (opts.element as HTMLTemplateElement).content.cloneNode(true) as Element;
|
opts.element.replaceWith(this.element);
|
||||||
} else if (typeof opts.element === "string") {
|
|
||||||
this.element = stringToElement(opts.element);
|
|
||||||
} else {
|
|
||||||
this.element = opts.element.cloneNode(true) as Element;
|
|
||||||
opts.element.replaceWith(this.element);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.isRoot) {
|
if (opts.isRoot) {
|
||||||
@ -326,10 +322,8 @@ export class Block {
|
|||||||
if (opts.element instanceof HTMLElement) {
|
if (opts.element instanceof HTMLElement) {
|
||||||
opts.element.replaceWith(this.element);
|
opts.element.replaceWith(this.element);
|
||||||
}
|
}
|
||||||
} else {
|
} else if (opts.element instanceof HTMLElement) {
|
||||||
if (opts.element instanceof HTMLElement) {
|
opts.element.replaceChildren(this.element);
|
||||||
opts.element.replaceChildren(this.element);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -432,128 +426,126 @@ function walk(node: Node, context: Context) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isElement(node)) {
|
if (!isElement(node)) { return; }
|
||||||
let exp: string | null;
|
|
||||||
|
|
||||||
const handleDirectives = (node: Element, context: Context, component?: Component, componentProps?: Record<string, any>, allProps?: any[]) => {
|
let exp: string | null;
|
||||||
if (warnInvalidDirectives(node, [":if", ":for"])) return;
|
|
||||||
if (warnInvalidDirectives(node, [":for", ":teleport"])) return;
|
|
||||||
if (warnInvalidDirectives(node, [":if", ":teleport"])) return;
|
|
||||||
|
|
||||||
// e.g. <div :scope="{ open: true }" />
|
const handleDirectives = (node: Element, context: Context, component?: Component, componentProps?: Record<string, any>, allProps?: any[]) => {
|
||||||
// In this case, the scope is merged into context.scope and will overwrite
|
if (warnInvalidDirectives(node, [":if", ":for"])) return;
|
||||||
// anything returned from `main`.
|
if (warnInvalidDirectives(node, [":for", ":teleport"])) return;
|
||||||
if ((exp = checkAndRemoveAttribute(node, ":scope"))) {
|
if (warnInvalidDirectives(node, [":if", ":teleport"])) return;
|
||||||
const scope = evalGet(context.scope, exp, node);
|
|
||||||
if (typeof scope === "object") {
|
// e.g. <div :scope="{ open: true }" />
|
||||||
Object.assign(context.scope, scope);
|
// In this case, the scope is merged into context.scope and will overwrite
|
||||||
// context = createScopedContext(context, scope);
|
// anything returned from `main`.
|
||||||
}
|
if ((exp = checkAndRemoveAttribute(node, ":scope"))) {
|
||||||
|
const scope = evalGet(context.scope, exp, node);
|
||||||
|
if (typeof scope === "object") {
|
||||||
|
Object.assign(context.scope, scope);
|
||||||
|
// context = createScopedContext(context, scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((exp = checkAndRemoveAttribute(node, ":if"))) {
|
|
||||||
return _if(node, exp, context, component, componentProps, allProps);
|
|
||||||
}
|
|
||||||
if ((exp = checkAndRemoveAttribute(node, ":for"))) {
|
|
||||||
return _for(node, exp, context, component, componentProps, allProps);
|
|
||||||
}
|
|
||||||
if ((exp = checkAndRemoveAttribute(node, ":teleport"))) {
|
|
||||||
return _teleport(node, exp, context, component, componentProps, allProps);
|
|
||||||
}
|
|
||||||
if ((exp = checkAndRemoveAttribute(node, ":show"))) {
|
|
||||||
new ShowDirective({ element: node, context, expression: exp });
|
|
||||||
}
|
|
||||||
if ((exp = checkAndRemoveAttribute(node, ":ref"))) {
|
|
||||||
context.scope[exp].value = node;
|
|
||||||
}
|
|
||||||
if ((exp = checkAndRemoveAttribute(node, ":value"))) {
|
|
||||||
new ValueDirective({ element: node, context, expression: exp });
|
|
||||||
}
|
|
||||||
if ((exp = checkAndRemoveAttribute(node, ":html"))) {
|
|
||||||
const htmlExp = exp;
|
|
||||||
|
|
||||||
context.effect(() => {
|
|
||||||
const result = evalGet(context.scope, htmlExp, node);
|
|
||||||
if (result instanceof Element) {
|
|
||||||
node.replaceChildren();
|
|
||||||
node.append(result);
|
|
||||||
} else {
|
|
||||||
node.innerHTML = result;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if ((exp = checkAndRemoveAttribute(node, ":text"))) {
|
|
||||||
const textExp = exp;
|
|
||||||
|
|
||||||
context.effect(() => {
|
|
||||||
node.textContent = toDisplayString(evalGet(context.scope, textExp, node));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const processAttributes = (node: Element, component?: Component) => {
|
|
||||||
return Array.from(node.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), node) : attr.value ? evalGet(context.scope, attr.value, node) : undefined,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
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 next = handleDirectives(node, context, component, componentProps, allProps);
|
|
||||||
if (next) return next;
|
|
||||||
|
|
||||||
const templates = findTemplateNodes(node);
|
|
||||||
|
|
||||||
return new Block({
|
|
||||||
element: node,
|
|
||||||
app: current.componentBlock.context.app,
|
|
||||||
// parentContext: context,
|
|
||||||
component,
|
|
||||||
replacementType: "replace",
|
|
||||||
parentComponentBlock: current.componentBlock,
|
|
||||||
templates,
|
|
||||||
componentProps,
|
|
||||||
allProps,
|
|
||||||
}).element;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const next = handleDirectives(node, context);
|
if ((exp = checkAndRemoveAttribute(node, ":if"))) {
|
||||||
|
return _if(node, exp, context, component, componentProps, allProps);
|
||||||
|
}
|
||||||
|
if ((exp = checkAndRemoveAttribute(node, ":for"))) {
|
||||||
|
return _for(node, exp, context, component, componentProps, allProps);
|
||||||
|
}
|
||||||
|
if ((exp = checkAndRemoveAttribute(node, ":teleport"))) {
|
||||||
|
return _teleport(node, exp, context, component, componentProps, allProps);
|
||||||
|
}
|
||||||
|
if ((exp = checkAndRemoveAttribute(node, ":show"))) {
|
||||||
|
new ShowDirective({ element: node, context, expression: exp });
|
||||||
|
}
|
||||||
|
if ((exp = checkAndRemoveAttribute(node, ":ref"))) {
|
||||||
|
context.scope[exp].value = node;
|
||||||
|
}
|
||||||
|
if ((exp = checkAndRemoveAttribute(node, ":value"))) {
|
||||||
|
new ValueDirective({ element: node, context, expression: exp });
|
||||||
|
}
|
||||||
|
if ((exp = checkAndRemoveAttribute(node, ":html"))) {
|
||||||
|
const htmlExp = exp;
|
||||||
|
|
||||||
|
context.effect(() => {
|
||||||
|
const result = evalGet(context.scope, htmlExp, node);
|
||||||
|
if (result instanceof Element) {
|
||||||
|
node.replaceChildren();
|
||||||
|
node.append(result);
|
||||||
|
} else {
|
||||||
|
node.innerHTML = result;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if ((exp = checkAndRemoveAttribute(node, ":text"))) {
|
||||||
|
const textExp = exp;
|
||||||
|
|
||||||
|
context.effect(() => {
|
||||||
|
node.textContent = toDisplayString(evalGet(context.scope, textExp, node));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const processAttributes = (node: Element, component?: Component) => Array.from(node.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), node) : attr.value ? evalGet(context.scope, attr.value, node) : undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
|
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 next = handleDirectives(node, context, component, componentProps, allProps);
|
||||||
if (next) return next;
|
if (next) return next;
|
||||||
|
|
||||||
Array.from(node.attributes).forEach((attr) => {
|
const templates = findTemplateNodes(node);
|
||||||
if (isPropAttribute(attr.name)) {
|
|
||||||
new AttributeDirective({ element: node, context, attr });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isEventAttribute(attr.name)) {
|
return new Block({
|
||||||
new EventDirective({ element: node, context, attr });
|
element: node,
|
||||||
}
|
app: current.componentBlock.context.app,
|
||||||
});
|
// parentContext: context,
|
||||||
|
component,
|
||||||
walkChildren(node, context);
|
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: Node, context: Context) {
|
function walkChildren(node: Node, context: Context) {
|
||||||
@ -605,11 +597,7 @@ function flattenRefs(scope: any): any {
|
|||||||
for (const key in scope) {
|
for (const key in scope) {
|
||||||
if (scope.hasOwnProperty(key)) {
|
if (scope.hasOwnProperty(key)) {
|
||||||
// Check if the value is a Ref
|
// Check if the value is a Ref
|
||||||
if (isRef(scope[key])) {
|
mapped[key] = isRef(scope[key]) ? scope[key].value : scope[key];
|
||||||
mapped[key] = scope[key].value;
|
|
||||||
} else {
|
|
||||||
mapped[key] = scope[key];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return mapped;
|
return mapped;
|
||||||
|
|||||||
26
tests/app.test.ts
Normal file
26
tests/app.test.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { App, Component } from "../src/index";
|
||||||
|
import { reactive } from "../src/reactivity/reactive";
|
||||||
|
|
||||||
|
describe("App", () => {
|
||||||
|
it("should mount a simple component and update reactive data", () => {
|
||||||
|
const app = new App();
|
||||||
|
const component: Component = {
|
||||||
|
template: "<div>{{ message }}</div>",
|
||||||
|
props: { message: { default: "Hello" } },
|
||||||
|
main(props) {
|
||||||
|
return reactive({ message: props.message });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const root = document.createElement("div");
|
||||||
|
app.mount(component, root);
|
||||||
|
|
||||||
|
expect(root.innerHTML).toBe("<div>Hello</div>");
|
||||||
|
|
||||||
|
const { scope } = app.root.context;
|
||||||
|
scope.message = "World";
|
||||||
|
|
||||||
|
expect(root.innerHTML).toBe("<div>World</div>");
|
||||||
|
});
|
||||||
|
});
|
||||||
38
tests/router.test.ts
Normal file
38
tests/router.test.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { describe, it, expect, beforeEach } from "vitest";
|
||||||
|
import { App } from "../src/index";
|
||||||
|
import { RouterPlugin } from "../src/plugins/router";
|
||||||
|
|
||||||
|
describe("RouterPlugin", () => {
|
||||||
|
let app: App;
|
||||||
|
let router: RouterPlugin;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
app = new App();
|
||||||
|
router = new RouterPlugin([
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
component: { template: "<div>Home</div>" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/about",
|
||||||
|
component: { template: "<div>About</div>" },
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
app.use(router);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render the correct component on route change", () => {
|
||||||
|
const root = document.createElement("div");
|
||||||
|
root.innerHTML = "<router-view></router-view>";
|
||||||
|
document.body.appendChild(root);
|
||||||
|
|
||||||
|
router.compile(root.firstElementChild as Element);
|
||||||
|
|
||||||
|
router.doRouteChange("/");
|
||||||
|
expect(root.innerHTML).toContain("<div>Home</div>");
|
||||||
|
|
||||||
|
router.doRouteChange("/about");
|
||||||
|
expect(root.innerHTML).toContain("<div>About</div>");
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user