This commit is contained in:
nvms 2024-10-21 10:10:08 -04:00
parent 4e18b46c89
commit bceca3053c
9 changed files with 1953 additions and 143 deletions

View File

@ -68,16 +68,6 @@ function findTemplateNodes(element) {
findTemplates(element); findTemplates(element);
return templates; return templates;
} }
var nextTick = async (f) => {
await new Promise(
(r) => setTimeout(
(_) => requestAnimationFrame((_2) => {
f && f();
r();
})
)
);
};
function html(strings, ...values) { function html(strings, ...values) {
const selfClosingTags = ["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"]; 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] || ""), ""); let result = strings.reduce((acc, str, i) => acc + str + (values[i] || ""), "");
@ -99,6 +89,9 @@ function insertAfter(newNode, existingNode) {
existingNode?.parentNode?.appendChild(newNode); existingNode?.parentNode?.appendChild(newNode);
} }
} }
function insertBefore(newNode, existingNode) {
existingNode.parentNode?.insertBefore(newNode, existingNode);
}
function isPropAttribute(attrName) { function isPropAttribute(attrName) {
if (attrName.startsWith(".")) { if (attrName.startsWith(".")) {
return true; return true;
@ -222,7 +215,6 @@ var AttributeDirective = class {
this.element.classList.remove(c); this.element.classList.remove(c);
}); });
} else if (typeof value === "object" && this.extractedAttributeName === "style") { } else if (typeof value === "object" && this.extractedAttributeName === "style") {
console.log("value is object", value);
const next = Object.keys(value); const next = Object.keys(value);
const rm = Object.keys(this.previousStyles).filter((style) => !next.includes(style)); const rm = Object.keys(this.previousStyles).filter((style) => !next.includes(style));
next.forEach((style) => { next.forEach((style) => {
@ -524,31 +516,40 @@ var ShowDirective = class {
}; };
// src/directives/teleport.ts // src/directives/teleport.ts
function _teleport(el, exp, ctx) { function _teleport(el, exp, ctx, component, componentProps, allProps) {
const anchor = new Comment(":teleport"); const anchor = new Comment(":teleport anchor");
el.replaceWith(anchor); insertBefore(anchor, el);
const observed = new Comment(":teleport");
el.replaceWith(observed);
console.log("Creating new block with allProps", component);
const target = document.querySelector(exp); const target = document.querySelector(exp);
if (!target) { if (!target) {
console.warn(`teleport target not found: ${exp}`); console.warn(`teleport target not found: ${exp}`);
return; return;
} }
nextTick(() => { const originalDisplay = el.style.display;
target.appendChild(el); el.style.display = "none";
const observer = new MutationObserver((mutationsList) => { let block;
mutationsList.forEach((mutation) => { target.appendChild(el);
mutation.removedNodes.forEach((removedNode) => { const observer = new MutationObserver((mutationsList) => {
if (removedNode.contains(anchor)) { mutationsList.forEach((mutation) => {
el.remove(); mutation.removedNodes.forEach((removedNode) => {
observer.disconnect(); if (removedNode.contains(observed)) {
} if (block.element) block.remove();
}); observer.disconnect();
}
}); });
}); });
observer.observe(document.body, { childList: true, subtree: true }); });
new Block({ observer.observe(document.body, { childList: true, subtree: true });
element: el, el.style.display = originalDisplay;
parentContext: ctx block = new Block({
}); element: el,
parentContext: ctx,
replacementType: "replace",
component,
componentProps,
allProps
}); });
return anchor; return anchor;
} }
@ -729,6 +730,18 @@ 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");
@ -848,7 +861,7 @@ var App2 = class {
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.display;
root.style.display = "none"; root.style.display = "none";
this.root = this._mount(component, root, props); this._mount(component, root, props);
root.style.display = display; root.style.display = display;
return this.root; return this.root;
} }
@ -861,6 +874,7 @@ var App2 = class {
parentContext.scope.$isRef = isRef; parentContext.scope.$isRef = isRef;
parentContext.scope.$isComputed = isComputed; parentContext.scope.$isComputed = isComputed;
const block = new Block({ const block = new Block({
app: this,
element: target, element: target,
parentContext, parentContext,
component, component,
@ -877,8 +891,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 && parentContext.app ? parentContext.app : null,
// scope: parentContext ? parentContext.scope : reactive({}), scope: parentContext ? parentContext.scope : reactive({}),
scope: reactive({}),
blocks: [], blocks: [],
effects: [], effects: [],
slots: [], slots: [],
@ -963,10 +976,11 @@ var Block = class {
} }
if (opts.isRoot) { if (opts.isRoot) {
this.context = opts.parentContext; this.context = opts.parentContext;
opts.app.root = this;
} else { } else {
this.parentContext = opts.parentContext ? opts.parentContext : createContext({}); this.parentContext = opts.parentContext ? opts.parentContext : createContext({});
this.parentContext.blocks.push(this); this.parentContext.blocks.push(this);
this.context = createContext({ parentContext: opts.parentContext }); this.context = createContext({ parentContext: opts.parentContext, app: opts.app });
} }
if (opts.component) { if (opts.component) {
this.componentProps = mergeProps(opts.componentProps ?? {}, opts.component.props ?? {}); this.componentProps = mergeProps(opts.componentProps ?? {}, opts.component.props ?? {});
@ -1101,23 +1115,23 @@ function walk(node, context) {
let exp; let exp;
const handleDirectives = (node2, context2, component, componentProps, allProps) => { const handleDirectives = (node2, context2, component, componentProps, allProps) => {
if (warnInvalidDirectives(node2, [":if", ":for"])) return; if (warnInvalidDirectives(node2, [":if", ":for"])) return;
if (warnInvalidDirectives(node2, [":if", ":teleport"])) return;
if (warnInvalidDirectives(node2, [":for", ":teleport"])) return; if (warnInvalidDirectives(node2, [":for", ":teleport"])) return;
if (warnInvalidDirectives(node2, [":if", ":teleport"])) return;
if (exp = checkAndRemoveAttribute(node2, ":scope")) { if (exp = checkAndRemoveAttribute(node2, ":scope")) {
const scope = evalGet(context2.scope, exp, node2); const scope = evalGet(context2.scope, exp, node2);
if (typeof scope === "object") { if (typeof scope === "object") {
Object.assign(context2.scope, scope); Object.assign(context2.scope, scope);
} }
} }
if (exp = checkAndRemoveAttribute(node2, ":teleport")) {
return _teleport(node2, exp, context2);
}
if (exp = checkAndRemoveAttribute(node2, ":if")) { if (exp = checkAndRemoveAttribute(node2, ":if")) {
return _if(node2, exp, context2, component, componentProps, allProps); return _if(node2, exp, context2, component, componentProps, allProps);
} }
if (exp = checkAndRemoveAttribute(node2, ":for")) { if (exp = checkAndRemoveAttribute(node2, ":for")) {
return _for(node2, exp, context2, component, componentProps, allProps); 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")) { if (exp = checkAndRemoveAttribute(node2, ":show")) {
new ShowDirective({ element: node2, context: context2, expression: exp }); new ShowDirective({ element: node2, context: context2, expression: exp });
} }
@ -1128,8 +1142,9 @@ function walk(node, context) {
new ValueDirective({ element: node2, context: context2, expression: exp }); new ValueDirective({ element: node2, context: context2, expression: exp });
} }
if (exp = checkAndRemoveAttribute(node2, ":html")) { if (exp = checkAndRemoveAttribute(node2, ":html")) {
const htmlExp = exp;
context2.effect(() => { context2.effect(() => {
const result = evalGet(context2.scope, exp, node2); const result = evalGet(context2.scope, htmlExp, node2);
if (result instanceof Element) { if (result instanceof Element) {
node2.replaceChildren(); node2.replaceChildren();
node2.append(result); node2.append(result);
@ -1139,8 +1154,9 @@ function walk(node, context) {
}); });
} }
if (exp = checkAndRemoveAttribute(node2, ":text")) { if (exp = checkAndRemoveAttribute(node2, ":text")) {
const textExp = exp;
context2.effect(() => { context2.effect(() => {
node2.textContent = toDisplayString(evalGet(context2.scope, exp, node2)); node2.textContent = toDisplayString(evalGet(context2.scope, textExp, node2));
}); });
} }
}; };
@ -1174,7 +1190,8 @@ function walk(node, context) {
const templates = findTemplateNodes(node); const templates = findTemplateNodes(node);
return new Block({ return new Block({
element: node, element: node,
parentContext: context, app: current.componentBlock.context.app,
// parentContext: context,
component, component,
replacementType: "replace", replacementType: "replace",
parentComponentBlock: current.componentBlock, parentComponentBlock: current.componentBlock,
@ -1247,17 +1264,22 @@ function flattenRefs(scope) {
// src/demo.ts // src/demo.ts
var main = { var main = {
template: html` template: html`
<div class="hero sans-serif f2" :scope="{ visible: true }"> <div class="sans-serif margin-y-3 container" style="--column-gap: .5rem; --row-gap: .5rem;">
<div :show="visible">ON</div> <h1 class="f1 margin-bottom-1 color-5">Colors</h1>
<div :show="!visible">OFF</div> <grid columns="6" class="f6 white-space-nowrap">
<button @click="() => visible = !visible" class="padding-x-1 padding-y-0_5 border-radius-1">Toggle</button> <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() {
const onClick = () => { const ranks = reactive(["5", "10", "20", "30", "40", "50", "60", "70", "80", "90"]);
console.log("ok"); const basesReverse = computed(() => Array.from(ranks).reverse());
}; const bg = (variant, rank, index) => ({ backgroundColor: `var(--${variant}-${rank})`, color: `var(--${variant}-${basesReverse.value[index]})` });
return { onClick }; return { ranks, bg };
} }
}; };
var app = new App2(); var app = new App2();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,5 @@
import { App } from "."; import { App } from ".";
import { computed } from "./reactivity/computed";
import { reactive } from "./reactivity/reactive"; import { reactive } from "./reactivity/reactive";
import { ref } from "./reactivity/ref"; import { ref } from "./reactivity/ref";
import { html } from "./util"; import { html } from "./util";
@ -169,11 +170,16 @@ import { html } from "./util";
// Event directive // Event directive
// const counter = { // const counter = {
// template: html` // template: html`
// <div> // <div class="sans-serif">
// <div :teleport="body" :if="style.color === 'gray'">true</div> // <div :teleport="body">
// <div :if="style.color === 'gray'">true</div>
// </div>
// <h3 {style:bind}>Count: {{count}}{{count >= 2 ? '!!!' : ''}}</h3> // <h3 {style:bind}>Count: {{count}}{{count >= 2 ? '!!!' : ''}}</h3>
// <button @click="increment">+</button> // <button @click="increment" class="padding-x-1 padding-y-0_5">+</button>
// <button @click="decrement">-</button> // <button @click="decrement" class="padding-x-1 padding-y-0_5">-</button>
// <div :teleport="body">
// <div :for="color in ['red', 'green', 'blue']">{{color}}</div>
// </div>
// </div> // </div>
// `, // `,
// main() { // main() {
@ -194,12 +200,18 @@ import { html } from "./util";
// app.mount(counter, "#app"); // app.mount(counter, "#app");
// ------------------------------------------------ // ------------------------------------------------
// Template // :if above :for
// :if with :teleport
// const main = { // const main = {
// template: html` // template: html`
// <div> // <div>
// <div :if="bool"> // <div :if="bool">
// <div :for="item in items">{{item}}</div> // <div :teleport="body">
// <div :for="item in items">{{item}}</div>
// </div>
// </div>
// <div :if="bool">
// <div :teleport="body">if bool teleported! {{items}}</div>
// </div> // </div>
// </div> // </div>
// `, // `,
@ -236,37 +248,37 @@ import { html } from "./util";
// ------------------------------------------------ // ------------------------------------------------
// Colors from css framework // Colors from css framework
// const main = { const main = {
// template: html` template: html`
// <div class="sans-serif margin-y-3 container" style="--column-gap: .5rem; --row-gap: .5rem;"> <div class="sans-serif margin-y-3 container" style="--column-gap: .5rem; --row-gap: .5rem;">
// <h1 class="f1 margin-bottom-1 color-5">Colors</h1> <h1 class="f1 margin-bottom-1 color-5">Colors</h1>
// <grid columns="6" class="f6 white-space-nowrap"> <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="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 :for="rank, index in ranks">
// <div .style:bind="bg(variant, rank, index)" class="padding-1">{{variant}}-{{rank}}</div> <div .style:bind="bg(variant, rank, index)" class="padding-1">{{variant}}-{{rank}}</div>
// </div> </div>
// </div> </div>
// </grid> </grid>
// </div> </div>
// `, `,
// main() { main() {
// const ranks = reactive(["5", "10", "20", "30", "40", "50", "60", "70", "80", "90"]); const ranks = reactive(["5", "10", "20", "30", "40", "50", "60", "70", "80", "90"]);
// const basesReverse = computed(() => Array.from(ranks).reverse()); 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]})` }); const bg = (variant: string, rank: string, index: number) => ({ backgroundColor: `var(--${variant}-${rank})`, color: `var(--${variant}-${basesReverse.value[index]})` });
// return { ranks, bg }; return { ranks, bg };
// }, },
// }; };
// const app = new App(); const app = new App();
// app.mount(main, "#app"); app.mount(main, "#app");
// ------------------------------------------------ // ------------------------------------------------
// :scope // :scope
// const child = { // const child = {
// template: html` // template: html`
// <div> // <div>
// hello from child, food: "{{food}}" (does not inherit) // I am child and I have food: "{{food}}" (does not inherit)
// <div> // <div>
// <slot /> // <slot />
// </div> // </div>
@ -277,16 +289,13 @@ import { html } from "./util";
// return { food }; // return { food };
// }, // },
// }; // };
//
// const main = { // const main = {
// template: html` // template: html`
// <div class="hero sans-serif f2" :scope="{ drink: '🍹' }"> // <div class="hero sans-serif f2" :scope="{ drink: '🍹' }">
// <div :scope="{ food: '🍕' }"> // <div :scope="{ food: '🍕' }">
// <!-- <div> --> // <div>Scoped food: {{food}} and scoped drink: {{drink}}</div>
// <div>Scoped data: {{food}}</div>
// <child>Child slot, food: {{food}} {{drink}}</child> // <child>Child slot, food: {{food}} {{drink}}</child>
// <div :if="food === 'nothing'">No pizza 😢</div>
// <div :else-if="food === '🍕'">Pizza!</div>
// </div> // </div>
// </div> // </div>
// `, // `,
@ -294,29 +303,58 @@ import { html } from "./util";
// return { food: ref("nothing") }; // return { food: ref("nothing") };
// }, // },
// }; // };
//
// const app = new App(); // const app = new App();
// app.register("child", child); // app.register("child", child);
// app.mount(main, "#app"); // app.mount(main, "#app");
// ------------------------------------------------ // ------------------------------------------------
// Practical :scope demo // Practical :scope demo
const main = { // const main = {
template: html` // template: html`
<div class="hero sans-serif f2" :scope="{ visible: true }"> // <div class="hero sans-serif f2" :scope="{ visible: true }">
<div :show="visible">ON</div> // <div :show="visible">ON</div>
<div :show="!visible">OFF</div> // <div :show="!visible">OFF</div>
<button @click="() => visible = !visible" class="padding-x-1 padding-y-0_5 border-radius-1">Toggle</button> // <button @click="() => visible = !visible" class="padding-x-1 padding-y-0_5 border-radius-1">Toggle</button>
</div> // </div>
`, // `,
main() { // main() {
const onClick = () => { // const onClick = () => {
console.log("ok"); // console.log("ok");
}; // };
//
// return { onClick };
// },
// };
//
// const app = new App();
// app.mount(main, "#app");
return { onClick }; // --------
}, // weird issue
}; // const child = {
// template: html`<div>child{{thing}}</div>`,
// props: { thing: { default: 1 }},
// main({ thing }) {
// return { thing };
// }
// };
const app = new App(); // const counter = {
app.mount(main, "#app"); // template: html`
// <div class="sans-serif">
// <div :teleport="body">
// <div :for="color in colors">{{color}}</div>
// </div>
// <child :teleport="body" .thing="5" />
// </div>
// `,
// main() {
// const colors = reactive(["red", "green"]);
// return { colors };
// },
// };
// const app = new App();
// app.register("child", child);
// app.mount(counter, "#app");

View File

@ -88,7 +88,6 @@ export class AttributeDirective {
this.element.classList.remove(c); this.element.classList.remove(c);
}); });
} else if (typeof value === "object" && this.extractedAttributeName === "style") { } else if (typeof value === "object" && this.extractedAttributeName === "style") {
console.log("value is object", value)
const next = Object.keys(value); const next = Object.keys(value);
const rm = Object.keys(this.previousStyles).filter((style) => !next.includes(style)); const rm = Object.keys(this.previousStyles).filter((style) => !next.includes(style));

View File

@ -1,9 +1,12 @@
import { Block, Context } from ".."; import { Block, Component, Context } from "..";
import { nextTick } from "../util"; import { insertBefore } from "../util";
export function _teleport(el: Element, exp: string, ctx: Context) { export function _teleport(el: Element, exp: string, ctx: Context, component?: Component, componentProps?: Record<string, any>, allProps?: Record<string, any>) {
const anchor = new Comment(":teleport"); const anchor = new Comment(":teleport anchor");
el.replaceWith(anchor); insertBefore(anchor, el);
const observed = new Comment(":teleport");
el.replaceWith(observed);
console.log("Creating new block with allProps", component);
const target = document.querySelector(exp); const target = document.querySelector(exp);
if (!target) { if (!target) {
@ -11,27 +14,40 @@ export function _teleport(el: Element, exp: string, ctx: Context) {
return; return;
} }
nextTick(() => { // Prevent interpolations flashing before they can be compiled due to nextTick,
target.appendChild(el); // which is apparently required.
// @ts-ignore
const originalDisplay = el.style.display;
// @ts-ignore
el.style.display = "none";
const observer = new MutationObserver((mutationsList) => { let block: Block;
mutationsList.forEach((mutation) => {
mutation.removedNodes.forEach((removedNode) => { target.appendChild(el);
if (removedNode.contains(anchor)) {
el.remove(); const observer = new MutationObserver((mutationsList) => {
observer.disconnect(); mutationsList.forEach((mutation) => {
} mutation.removedNodes.forEach((removedNode) => {
}); if (removedNode.contains(observed)) {
if (block.element) block.remove();
observer.disconnect();
}
}); });
}); });
});
observer.observe(document.body, { childList: true, subtree: true }); observer.observe(document.body, { childList: true, subtree: true });
// Walks the tree of this teleported element. // @ts-ignore
new Block({ el.style.display = originalDisplay;
element: el,
parentContext: ctx, block = new Block({
}); element: el,
parentContext: ctx,
replacementType: "replace",
component,
componentProps,
allProps,
}); });
// Return the anchor so walk continues down the tree in the right order. // Return the anchor so walk continues down the tree in the right order.

View File

@ -11,7 +11,7 @@ import { isComputed } from "./reactivity/computed";
import { effect as _effect } from "./reactivity/effect"; import { effect as _effect } from "./reactivity/effect";
import { reactive } from "./reactivity/reactive"; import { reactive } from "./reactivity/reactive";
import { isRef } from "./reactivity/ref"; import { isRef } from "./reactivity/ref";
import { checkAndRemoveAttribute, componentHasPropByName, extractPropName, findSlotNodes, findTemplateNodes, isElement, isEventAttribute, isMirrorProp, isObject, isPropAttribute, isRegularProp, isSpreadProp, isText, Slot, stringToElement, Template, toDisplayString } from "./util"; import { checkAndRemoveAttribute, componentHasPropByName, extractPropName, findSlotNodes, findTemplateNodes, insertBefore, isElement, isEventAttribute, isMirrorProp, isObject, isPropAttribute, isRegularProp, isSpreadProp, isText, nextTick, Slot, stringToElement, Template, toDisplayString } from "./util";
export * from "./plugins"; export * from "./plugins";
export * from "./plugins/router"; export * from "./plugins/router";
@ -64,7 +64,7 @@ export class App {
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.display;
root.style.display = "none"; root.style.display = "none";
this.root = this._mount(component, root, props); this._mount(component, root, props);
root.style.display = display; root.style.display = display;
return this.root; return this.root;
} }
@ -81,6 +81,7 @@ export class App {
parentContext.scope.$isComputed = isComputed; parentContext.scope.$isComputed = isComputed;
const block = new Block({ const block = new Block({
app: this,
element: target, element: target,
parentContext, parentContext,
component, component,
@ -116,8 +117,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 && parentContext.app ? parentContext.app : null,
// scope: parentContext ? parentContext.scope : reactive({}), scope: parentContext ? parentContext.scope : reactive({}),
scope: reactive({}),
blocks: [], blocks: [],
effects: [], effects: [],
slots: [], slots: [],
@ -202,6 +202,7 @@ interface BlockOptions {
componentProps?: Record<string, any>; componentProps?: Record<string, any>;
allProps?: Record<string, any>; allProps?: Record<string, any>;
parentContext?: Context; parentContext?: Context;
app?: App;
component?: Component; component?: Component;
parentComponentBlock?: Block; parentComponentBlock?: Block;
templates?: Template[]; templates?: Template[];
@ -242,10 +243,11 @@ export class Block {
if (opts.isRoot) { if (opts.isRoot) {
this.context = opts.parentContext; this.context = opts.parentContext;
opts.app.root = this;
} else { } else {
this.parentContext = opts.parentContext ? opts.parentContext : createContext({}); this.parentContext = opts.parentContext ? opts.parentContext : createContext({});
this.parentContext.blocks.push(this); this.parentContext.blocks.push(this);
this.context = createContext({ parentContext: opts.parentContext }); this.context = createContext({ parentContext: opts.parentContext, app: opts.app });
} }
if (opts.component) { if (opts.component) {
@ -374,7 +376,6 @@ export class Block {
node = next; node = next;
} }
} else { } else {
// this.element.parentNode!.removeChild(this.element);
this.element.remove(); this.element.remove();
} }
@ -415,8 +416,8 @@ function walk(node: Node, context: Context) {
const handleDirectives = (node: Element, context: Context, component?: Component, componentProps?: Record<string, any>, allProps?: any[]) => { const handleDirectives = (node: Element, context: Context, component?: Component, componentProps?: Record<string, any>, allProps?: any[]) => {
if (warnInvalidDirectives(node, [":if", ":for"])) return; if (warnInvalidDirectives(node, [":if", ":for"])) return;
if (warnInvalidDirectives(node, [":if", ":teleport"])) return;
if (warnInvalidDirectives(node, [":for", ":teleport"])) return; if (warnInvalidDirectives(node, [":for", ":teleport"])) return;
if (warnInvalidDirectives(node, [":if", ":teleport"])) return;
// e.g. <div :scope="{ open: true }" /> // e.g. <div :scope="{ open: true }" />
// In this case, the scope is merged into context.scope and will overwrite // In this case, the scope is merged into context.scope and will overwrite
@ -429,15 +430,15 @@ function walk(node: Node, context: Context) {
} }
} }
if ((exp = checkAndRemoveAttribute(node, ":teleport"))) {
return _teleport(node, exp, context);
}
if ((exp = checkAndRemoveAttribute(node, ":if"))) { if ((exp = checkAndRemoveAttribute(node, ":if"))) {
return _if(node, exp, context, component, componentProps, allProps); return _if(node, exp, context, component, componentProps, allProps);
} }
if ((exp = checkAndRemoveAttribute(node, ":for"))) { if ((exp = checkAndRemoveAttribute(node, ":for"))) {
return _for(node, exp, context, component, componentProps, allProps); 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"))) { if ((exp = checkAndRemoveAttribute(node, ":show"))) {
new ShowDirective({ element: node, context, expression: exp }); new ShowDirective({ element: node, context, expression: exp });
} }
@ -448,8 +449,10 @@ function walk(node: Node, context: Context) {
new ValueDirective({ element: node, context, expression: exp }); new ValueDirective({ element: node, context, expression: exp });
} }
if ((exp = checkAndRemoveAttribute(node, ":html"))) { if ((exp = checkAndRemoveAttribute(node, ":html"))) {
const htmlExp = exp;
context.effect(() => { context.effect(() => {
const result = evalGet(context.scope, exp, node); const result = evalGet(context.scope, htmlExp, node);
if (result instanceof Element) { if (result instanceof Element) {
node.replaceChildren(); node.replaceChildren();
node.append(result); node.append(result);
@ -459,8 +462,10 @@ function walk(node: Node, context: Context) {
}); });
} }
if ((exp = checkAndRemoveAttribute(node, ":text"))) { if ((exp = checkAndRemoveAttribute(node, ":text"))) {
const textExp = exp;
context.effect(() => { context.effect(() => {
node.textContent = toDisplayString(evalGet(context.scope, exp, node)); node.textContent = toDisplayString(evalGet(context.scope, textExp, node));
}); });
} }
}; };
@ -502,7 +507,8 @@ function walk(node: Node, context: Context) {
return new Block({ return new Block({
element: node, element: node,
parentContext: context, app: current.componentBlock.context.app,
// parentContext: context,
component, component,
replacementType: "replace", replacementType: "replace",
parentComponentBlock: current.componentBlock, parentComponentBlock: current.componentBlock,

View File

@ -150,6 +150,10 @@ export function insertAfter(newNode: Node, existingNode: Node) {
} }
} }
export function insertBefore(newNode: Node, existingNode: Node) {
existingNode.parentNode?.insertBefore(newNode, existingNode);
}
export function isPropAttribute(attrName: string) { export function isPropAttribute(attrName: string) {
if (attrName.startsWith(".")) { if (attrName.startsWith(".")) {
return true; return true;