mirror of
https://github.com/nvms/soma3.git
synced 2025-12-13 06:40:52 +00:00
changes
This commit is contained in:
parent
02730daae5
commit
3c07b7568d
184
public/demo.js
184
public/demo.js
@ -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
169
src/demo.ts
169
src/demo.ts
@ -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");
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
147
src/index.ts
147
src/index.ts
@ -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));
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user