This commit is contained in:
nvms 2025-05-12 13:03:52 -04:00
parent 02730daae5
commit 3c07b7568d
7 changed files with 338 additions and 195 deletions

View File

@ -253,7 +253,9 @@ var EventDirective = class {
this.eventCount++;
const handler = evalGet(context.scope, attr.value, element);
if (typeof handler === "function") {
handler(event);
handler.call(context.scope, event);
} else {
execute(context.scope, this.expression, element, false);
}
});
element.removeAttribute(attr.name);
@ -272,10 +274,10 @@ var _for = (el, exp, ctx, component, componentProps, allProps) => {
return;
}
const nextNode = el.nextSibling;
const parent = el.parentElement;
const parent2 = el.parentElement;
const anchor = new Text("");
parent.insertBefore(anchor, el);
parent.removeChild(el);
parent2.insertBefore(anchor, el);
parent2.removeChild(el);
const sourceExp = inMatch[2].trim();
let valueExp = inMatch[1].trim().replace(stripParensRE, "").trim();
let destructureBindings;
@ -345,7 +347,7 @@ var _for = (el, exp, ctx, component, componentProps, allProps) => {
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);
block.insert(parent2, ref2);
return block;
};
ctx.effect(() => {
@ -378,7 +380,7 @@ var _for = (el, exp, ctx, component, componentProps, allProps) => {
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);
block.insert(parent2, nextBlock ? nextBlock.element : anchor);
}
}
}
@ -392,30 +394,30 @@ var _for = (el, exp, ctx, component, componentProps, allProps) => {
// src/directives/if.ts
function _if(el, exp, ctx, component, componentProps, allProps) {
const parent = el.parentElement;
const parent2 = el.parentElement;
const anchor = new Comment(":if");
parent.insertBefore(anchor, el);
parent2.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);
parent2.removeChild(elseEl);
branches.push({ exp: elseExp, el: elseEl });
} else {
break;
}
}
const nextNode = el.nextSibling;
parent.removeChild(el);
parent2.removeChild(el);
let block;
let activeBranchIndex = -1;
const removeActiveBlock = () => {
if (!block) {
return;
}
parent.insertBefore(anchor, block.element);
parent2.insertBefore(anchor, block.element);
block.remove();
block = void 0;
};
@ -426,8 +428,8 @@ function _if(el, exp, ctx, component, componentProps, allProps) {
if (i !== activeBranchIndex) {
removeActiveBlock();
block = new Block({ element: el2, parentContext: ctx, replacementType: "replace", component, componentProps, allProps });
block.insert(parent, anchor);
parent.removeChild(anchor);
block.insert(parent2, anchor);
parent2.removeChild(anchor);
activeBranchIndex = i;
}
return;
@ -518,6 +520,7 @@ var ShowDirective = class {
// src/directives/teleport.ts
function _teleport(el, exp, ctx, component, componentProps, allProps) {
console.log("_teleport", ctx.scope);
const anchor = new Comment(":teleport anchor");
insertBefore(anchor, el);
const observed = new Comment(":teleport");
@ -532,17 +535,19 @@ function _teleport(el, exp, ctx, component, componentProps, allProps) {
el.style.display = "none";
let block;
target.appendChild(el);
console.log("appended", el, "to", target);
const observer = new MutationObserver((mutationsList) => {
mutationsList.forEach((mutation) => {
mutation.removedNodes.forEach((removedNode) => {
if (removedNode.contains(observed)) {
console.log("removedNode", removedNode, "observed:", observed);
if (removedNode.contains(anchor)) {
if (block.element) block.remove();
observer.disconnect();
}
});
});
});
observer.observe(document.body, { childList: true, subtree: true });
observer.observe(document.querySelector(exp) || document.body, { childList: true, subtree: true });
el.style.display = originalDisplay;
block = new Block({
element: el,
@ -832,6 +837,25 @@ var link = {
};
// src/index.ts
function provide(key, value) {
if (!current2.componentBlock) {
console.warn("Can't provide: no current component block");
}
current2.componentBlock.provides.set(key, value);
}
function inject(key) {
if (!current2.componentBlock) {
console.warn("Can't inject: no current component block");
}
let c = current2.componentBlock;
while (c) {
if (c.provides.has(key)) {
return c.provides.get(key);
}
c = c.parentComponentBlock;
}
return void 0;
}
var App2 = class {
root;
registry = /* @__PURE__ */ new Map();
@ -892,6 +916,14 @@ function createContext({ parentContext, app: app2 }) {
};
return context;
}
var extendScopedContext = (ctx, data = {}) => {
const parentScope = ctx.scope;
const mergedScope = { ...parentScope };
Object.defineProperties(mergedScope, Object.getOwnPropertyDescriptors(data));
const reactiveScope = reactive(mergedScope);
ctx.scope = reactiveScope;
return ctx;
};
var createScopedContext = (ctx, data = {}) => {
const parentScope = ctx.scope;
const mergedScope = Object.create(parentScope);
@ -970,11 +1002,24 @@ var Block = class {
}
if (opts.component) {
this.componentProps = mergeProps(opts.componentProps ?? {}, opts.component.props ?? {});
if (opts.component.main) {
this.context.scope = {
...opts.component.main(this.componentProps) || {}
};
const componentScope = opts.component.main ? opts.component.main(this.componentProps) || {} : {};
const providedKeys = /* @__PURE__ */ new Set();
let currentBlock = this.parentComponentBlock;
while (currentBlock) {
currentBlock.provides.forEach((_, key) => providedKeys.add(key));
currentBlock = currentBlock.parentComponentBlock;
}
for (const key of providedKeys) {
if (componentScope[key] === void 0) {
const providedValue = inject(key);
if (providedValue !== void 0) {
componentScope[key] = providedValue;
}
}
}
this.context.scope = {
...componentScope
};
}
opts.allProps?.forEach((prop) => {
if (prop.isBind) {
@ -1024,14 +1069,14 @@ var Block = class {
this.componentProps[name] = value;
}
}
insert(parent, anchor = null) {
insert(parent2, anchor = null) {
if (this.isFragment) {
if (this.start) {
let node = this.start;
let next;
while (node) {
next = node.nextSibling;
parent.insertBefore(node, anchor);
parent2.insertBefore(node, anchor);
if (node === this.end) {
break;
}
@ -1040,12 +1085,12 @@ var Block = class {
} 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);
parent2.insertBefore(this.end, anchor);
parent2.insertBefore(this.start, this.end);
parent2.insertBefore(this.element, this.end);
}
} else {
parent.insertBefore(this.element, anchor);
parent2.insertBefore(this.element, anchor);
}
}
remove() {
@ -1056,12 +1101,15 @@ var Block = class {
}
}
if (this.start) {
const parent = this.start.parentNode;
const parent2 = this.start.parentNode;
if (!parent2) {
throw new Error("Parent node is null");
}
let node = this.start;
let next;
while (node) {
next = node.nextSibling;
parent.removeChild(node);
parent2.removeChild(node);
if (node === this.end) {
break;
}
@ -1076,7 +1124,7 @@ var Block = class {
this.context.blocks.forEach((block) => {
block.teardown();
});
this.context.effects.forEach((e) => e.stop());
this.context.effects.forEach((e) => void e.stop());
}
};
function isComponent(element, context) {
@ -1084,7 +1132,7 @@ function isComponent(element, context) {
}
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("These directives cannot be used together on the same node:", directives);
console.warn("Node ignored:", node);
return true;
}
@ -1104,12 +1152,23 @@ function walk(node, context) {
if (warnInvalidDirectives(node2, [":for", ":teleport"])) return;
if (warnInvalidDirectives(node2, [":if", ":teleport"])) return;
if (exp = checkAndRemoveAttribute(node2, ":scope")) {
const scope = evalGet(context2.scope, exp, node2);
context2.scope = extendScopedContext(context2, scope).scope;
}
if (exp = checkAndRemoveAttribute(node2, ":scope:provide")) {
const scope = evalGet(context2.scope, exp, node2);
if (typeof scope === "object") {
Object.assign(context2.scope, scope);
Object.entries(scope).forEach(([key, value]) => {
provide(key, value);
});
}
}
if (exp = checkAndRemoveAttribute(node2, ":if")) {
if (current2.componentBlock) {
const merged = extendScopedContext(context2, current2.componentBlock?.parentComponentBlock?.context.scope);
return _if(node2, exp, merged, component, componentProps, allProps);
}
return _if(node2, exp, context2, component, componentProps, allProps);
}
if (exp = checkAndRemoveAttribute(node2, ":for")) {
@ -1175,7 +1234,7 @@ function walk(node, context) {
return new Block({
element: node,
app: current2.componentBlock.context.app,
// parentContext: context,
parentContext: context,
component,
replacementType: "replace",
parentComponentBlock: current2.componentBlock,
@ -1197,9 +1256,9 @@ function walk(node, context) {
walkChildren(node, context);
}
function walkChildren(node, context) {
let child2 = node.firstChild;
while (child2) {
child2 = walk(child2, context) || child2.nextSibling;
let child = node.firstChild;
while (child) {
child = walk(child, context) || child.nextSibling;
}
}
var evalFuncCache = {};
@ -1241,34 +1300,49 @@ function flattenRefs(scope) {
}
// src/demo.ts
var child = {
var app = new App2();
var parent = {
template: html`
<div>
I am child and I have a cheeseburger: "{{food}}" (does not inherit)
<div>
<slot />
</div>
<h1>parent, bool: {{bool}}</h1>
<card>
<!-- default -->
<div :if="bool">
<div :teleport="body" id="teleported">
<div>showing 1-3 because bool is true</div>
<div>1</div>
<div>2</div>
<div>3</div>
</div>
</div>
content 1 always shown
<div :if="bool">
content 2, animals:
<div :for="animal in animals">animal: {{animal}}</div>
</div>
<!-- body -->
<template slot="body">card body from parent</template>
</card>
</div>
`,
main() {
const food = ref("\u{1F354}");
return { food };
const bool = ref(true);
const animals = reactive(["dog", "cat", "bear"]);
setInterval(() => {
bool.value = !bool.value;
}, 2e3);
return { bool, animals };
}
};
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 card = {
template: html`<div>
<h2>card</h2>
<h3><slot /></h3>
<slot name="body" />
</div>`
};
var app = new App2();
app.register("child", child);
app.mount(main, "#app");
app.register("card", card);
var parentBlock = app.mount(parent, "body");
var cardBlock = parentBlock.context.blocks[0];
//# sourceMappingURL=demo.js.map

File diff suppressed because one or more lines are too long

View File

@ -31,53 +31,54 @@ import { html } from "./util";
// ------------------------------------------------
// Slots, multiple default and named, :if and :for
// const app = new App();
const app = new App();
// const parent = {
// template: html`
// <div>
// <h1>parent</h1>
// <card>
// <!-- default -->
// <div :if="bool">
// <div :teleport="body" id="teleported">
// <div>1</div>
// <div>2</div>
// <div>3</div>
// </div>
// </div>
// content 1 always shown
// <div :if="bool">
// content 2, animals:
// <div :for="animal in animals">animal: {{animal}}</div>
// </div>
const parent = {
template: html`
<div>
<h1>parent, bool: {{bool}}</h1>
<card>
<!-- default -->
<div :if="bool">
<div :teleport="body" id="teleported">
<div>showing 1-3 because bool is true</div>
<div>1</div>
<div>2</div>
<div>3</div>
</div>
</div>
content 1 always shown
<div :if="bool">
content 2, animals:
<div :for="animal in animals">animal: {{animal}}</div>
</div>
// <!-- body -->
// <template slot="body">card body from parent</template>
// </card>
// </div>
// `,
// main() {
// const bool = ref(true);
// const animals = reactive(["dog", "cat", "bear"]);
<!-- body -->
<template slot="body">card body from parent</template>
</card>
</div>
`,
main() {
const bool = ref(true);
const animals = reactive(["dog", "cat", "bear"]);
// setInterval(() => {
// bool.value = !bool.value;
// }, 2000);
setInterval(() => {
bool.value = !bool.value;
}, 2000);
// return { bool, animals };
// },
// };
// const card = {
// template: html`<div>
// <h2>card</h2>
// <h3><slot /></h3>
// <slot name="body" />
// </div>`,
// };
// app.register("card", card);
// const parentBlock = app.mount(parent, "body");
// const cardBlock = parentBlock.context.blocks[0];
return { bool, animals };
},
};
const card = {
template: html`<div>
<h2>card</h2>
<h3><slot /></h3>
<slot name="body" />
</div>`,
};
app.register("card", card);
const parentBlock = app.mount(parent, "body");
const cardBlock = parentBlock.context.blocks[0];
// ------------------------------------------------
// Component pros, mirror and spread, bind and no bind
@ -268,48 +269,50 @@ import { html } from "./util";
// 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 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");
// const child = {
// template: html`
// <div>
// I am child and I have a cheeseburger: "{{food}}" (does not inherit)
// <div>
// <slot />
// </div>
// </div>
// `,
// main() {
// // Comment this out to implicitly inherit the
// // provided :scope value from the parent.
// const food = ref("🍔");
// return { food };
// },
// };
//
// const main = {
// template: html`
// <div class="hero sans-serif f2" :scope:provide="{ drink: '🍹' }">
// <div :scope:provide="{ 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
@ -318,18 +321,16 @@ app.mount(main, "#app");
// <div class="hero sans-serif f2" :scope="{ visible: true }">
// <div :show="visible">ON</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-0_25">Toggle</button>
// <div>visible: {{visible}} / foo: {{foo}}</div>
// </div>
// `,
// main() {
// const onClick = () => {
// console.log("ok");
// };
//
// return { onClick };
// const foo = ref("bar");
// return { foo };
// },
// };
//
// const app = new App();
// app.mount(main, "#app");

View File

@ -1,4 +1,4 @@
import { Context, evalGet } from "..";
import { Context, evalGet, execute } from "..";
interface EventDirectiveOptions {
element: Element;
@ -31,7 +31,9 @@ export class EventDirective {
const handler = evalGet(context.scope, attr.value, element);
if (typeof handler === "function") {
handler(event);
handler.call(context.scope, event);
} else {
execute(context.scope, this.expression, element, false);
}
});

View File

@ -7,6 +7,7 @@ interface Branch {
}
export function _if(el: Element, exp: string, ctx: Context, component?: Component, componentProps?: Record<string, any>, allProps?: Record<string, any>) {
// console.log("_if received scope", ctx.scope);
const parent = el.parentElement!;
const anchor = new Comment(":if");
@ -45,6 +46,7 @@ export function _if(el: Element, exp: string, ctx: Context, component?: Componen
ctx.effect(() => {
for (let i = 0; i < branches.length; i++) {
const { exp, el } = branches[i];
// console.log("scope", ctx.scope);
if (!exp || evalGet(ctx.scope, exp, el)) {
if (i !== activeBranchIndex) {

View File

@ -1,7 +1,8 @@
import { Block, Component, Context } from "..";
import { insertBefore } from "../util";
import { Block, Component, Context, evalGet } from "..";
import { insertBefore, nextTick } from "../util";
export function _teleport(el: Element, exp: string, ctx: Context, component?: Component, componentProps?: Record<string, any>, allProps?: Record<string, any>) {
console.log("_teleport", ctx.scope);
const anchor = new Comment(":teleport anchor");
insertBefore(anchor, el);
const observed = new Comment(":teleport");
@ -23,12 +24,19 @@ export function _teleport(el: Element, exp: string, ctx: Context, component?: Co
let block: Block;
target.appendChild(el);
// nextTick(() => {
target.appendChild(el);
console.log("appended", el, "to", target);
// })
// setTimeout(() => {
// });
const observer = new MutationObserver((mutationsList) => {
mutationsList.forEach((mutation) => {
mutation.removedNodes.forEach((removedNode) => {
if (removedNode.contains(observed)) {
console.log("removedNode", removedNode, "observed:", observed);
// if (removedNode.contains(observed)) {
if (removedNode.contains(anchor)) {
if (block.element) block.remove();
observer.disconnect();
}
@ -36,7 +44,8 @@ export function _teleport(el: Element, exp: string, ctx: Context, component?: Co
});
});
observer.observe(document.body, { childList: true, subtree: true });
// observer.observe(document.body, { childList: true, subtree: true });
observer.observe(document.querySelector(exp) || document.body, { childList: true, subtree: true });
// @ts-ignore
el.style.display = originalDisplay;

View File

@ -6,30 +6,12 @@ import { InterpolationDirective } from "./directives/interpolation";
import { ShowDirective } from "./directives/show";
import { _teleport } from "./directives/teleport";
import { ValueDirective } from "./directives/value";
import { Plugin } from "./plugins";
import type { Plugin } from "./plugins";
import { isComputed } from "./reactivity/computed";
import { effect as _effect } from "./reactivity/effect";
import { reactive } from "./reactivity/reactive";
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, isElement, isEventAttribute, isMirrorProp, isObject, isPropAttribute, isRegularProp, isSpreadProp, isText, type Slot, stringToElement, type Template, toDisplayString } from "./util";
export * from "./plugins";
export * from "./plugins/router";
@ -145,9 +127,29 @@ export function createContext({ parentContext, app }: CreateContextOptions): Con
},
};
// console.log('createContext, scope:', context.scope);
return context;
}
export const extendScopedContext = (ctx: Context, data = {}): Context => {
const parentScope = ctx.scope;
// Create a new object that inherits from parentScope
const mergedScope = { ...parentScope };
// Add the new properties from data
Object.defineProperties(mergedScope, Object.getOwnPropertyDescriptors(data));
// Make it reactive
const reactiveScope = reactive(mergedScope);
// Assign it back to the context
ctx.scope = reactiveScope;
return ctx;
};
export const createScopedContext = (ctx: Context, data = {}): Context => {
const parentScope = ctx.scope;
const mergedScope = Object.create(parentScope);
@ -232,15 +234,15 @@ export class Block {
context: Context;
parentContext: Context;
component: Component;
provides = new Map<string, any>();
provides = new Map<string, unknown>();
parentComponentBlock: Block | undefined;
componentProps: Record<string, any>;
allProps: Record<string, any>;
componentProps: Record<string, unknown>;
allProps: Record<string, unknown>;
isFragment: boolean;
start?: Text;
end?: Text;
key?: any;
key?: unknown;
constructor(opts: BlockOptions) {
this.isFragment = opts.element instanceof HTMLTemplateElement;
@ -265,16 +267,39 @@ export class Block {
this.parentContext = opts.parentContext ? opts.parentContext : createContext({});
this.parentContext.blocks.push(this);
this.context = createContext({ parentContext: opts.parentContext, app: opts.app });
// console.log('created context', this.context.scope);
// console.log('opts.parentContext was', opts.parentContext.scope);
}
if (opts.component) {
this.componentProps = mergeProps(opts.componentProps ?? {}, opts.component.props ?? {});
if (opts.component.main) {
this.context.scope = {
...(opts.component.main(this.componentProps) || {}),
};
// Create the component scope
const componentScope = opts.component.main ? opts.component.main(this.componentProps) || {} : {};
// Check for provided values from parent components
// First, get all provided keys from parent components
const providedKeys = new Set<string>();
let currentBlock = this.parentComponentBlock;
while (currentBlock) {
currentBlock.provides.forEach((_, key) => providedKeys.add(key));
currentBlock = currentBlock.parentComponentBlock;
}
// Then inject values for each provided key
for (const key of providedKeys) {
// Only inject if the component doesn't already define this value
if (componentScope[key] === undefined) {
const providedValue = inject(key);
if (providedValue !== undefined) {
componentScope[key] = providedValue;
}
}
}
this.context.scope = {
...componentScope,
};
}
opts.allProps?.forEach((prop: any) => {
@ -376,7 +401,10 @@ export class Block {
}
if (this.start) {
const parent = this.start.parentNode!;
const parent = this.start.parentNode;
if (!parent) {
throw new Error("Parent node is null");
}
let node: Node | null = this.start;
let next: Node | null;
@ -402,7 +430,7 @@ export class Block {
block.teardown();
});
this.context.effects.forEach((e) => e.stop());
this.context.effects.forEach((e) => void e.stop());
}
}
@ -412,7 +440,7 @@ function isComponent(element: Element, context: Context) {
function warnInvalidDirectives(node: Element, directives: string[]): boolean {
if (directives.every((d) => node.hasAttribute(d))) {
console.warn(`These directives cannot be used together on the same node:`, directives);
console.warn("These directives cannot be used together on the same node:", directives);
console.warn("Node ignored:", node);
return true;
}
@ -426,7 +454,9 @@ function walk(node: Node, context: Context) {
return;
}
if (!isElement(node)) { return; }
if (!isElement(node)) {
return;
}
let exp: string | null;
@ -438,15 +468,39 @@ function walk(node: Node, context: Context) {
// e.g. <div :scope="{ open: true }" />
// In this case, the scope is merged into context.scope and will overwrite
// 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, ":scope"))) {
const scope = evalGet(context.scope, exp, node);
context.scope = extendScopedContext(context, scope).scope;
}
// Handle :scope:provide directive - provides values to all descendants
if ((exp = checkAndRemoveAttribute(node, ":scope:provide"))) {
const scope = evalGet(context.scope, exp, node);
if (typeof scope === "object") {
// First, merge into the current scope
Object.assign(context.scope, scope);
// context = createScopedContext(context, scope);
// Then provide each key-value pair to descendants
Object.entries(scope).forEach(([key, value]) => {
provide(key, value);
});
}
}
if ((exp = checkAndRemoveAttribute(node, ":if"))) {
// TODO: Uh
if (current.componentBlock) {
const merged = extendScopedContext(context, current.componentBlock?.parentComponentBlock?.context.scope);
return _if(node, exp, merged, component, componentProps, allProps);
}
return _if(node, exp, context, component, componentProps, allProps);
}
if ((exp = checkAndRemoveAttribute(node, ":for"))) {
@ -486,17 +540,18 @@ function walk(node: Node, context: Context) {
}
};
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,
}));
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());
@ -522,7 +577,7 @@ function walk(node: Node, context: Context) {
return new Block({
element: node,
app: current.componentBlock.context.app,
// parentContext: context,
parentContext: context,
component,
replacementType: "replace",
parentComponentBlock: current.componentBlock,
@ -568,7 +623,7 @@ export function evalSet(scope: any, exp: string, value: unknown) {
return execute(scope, `const ___target = (${exp.trim()}); return $isRef(___target) ? ___target.value = ${value} : ___target = ${value};`, null, false);
}
function execute(scope: any, exp: string, el?: Node, flatRefs = true) {
export function execute(scope: any, exp: string, el?: Node, flatRefs = true) {
const newScope = flatRefs ? flattenRefs(scope) : scope;
const fn = evalFuncCache[exp] || (evalFuncCache[exp] = toFunction(exp));