This commit is contained in:
nvms 2024-10-19 11:14:33 -04:00
parent 2c4e66bd8d
commit 0019cd64e5
3 changed files with 321 additions and 219 deletions

View File

@ -279,10 +279,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;
@ -352,7 +352,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(() => {
@ -385,7 +385,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);
}
}
}
@ -399,28 +399,28 @@ 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) {
parent.insertBefore(anchor, block.element);
parent2.insertBefore(anchor, block.element);
block.remove();
block = void 0;
}
@ -432,8 +432,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;
@ -1015,14 +1015,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;
}
@ -1031,12 +1031,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() {
@ -1047,12 +1047,12 @@ var Block = class {
}
}
if (this.start) {
const parent = this.start.parentNode;
const parent2 = this.start.parentNode;
let node = this.start;
let next;
while (node) {
next = node.nextSibling;
parent.removeChild(node);
parent2.removeChild(node);
if (node === this.end) {
break;
}
@ -1080,9 +1080,39 @@ function walk(node, context) {
}
if (isElement(node)) {
let exp;
if (isComponent(node, context)) {
const component = context.app.getComponent(node.tagName.toLowerCase());
const allProps = Array.from(node.attributes).filter((attr) => isSpreadProp(attr.name) || isMirrorProp(attr.name) || isRegularProp(attr.name) && componentHasPropByName(extractPropName(attr.name), component)).map((attr) => ({
const handleDirectives = (node2, context2, component, componentProps, allProps) => {
if (exp = checkAndRemoveAttribute(node2, ":teleport")) {
return _teleport(node2, exp, context2);
}
if (exp = checkAndRemoveAttribute(node2, ":if")) {
return _if(node2, exp, context2, component, componentProps, allProps);
}
if (exp = checkAndRemoveAttribute(node2, ":for")) {
return _for(node2, exp, context2, component, componentProps, allProps);
}
if (exp = checkAndRemoveAttribute(node2, ":show")) {
new ShowDirective({ element: node2, context: context2, expression: exp });
}
if (exp = checkAndRemoveAttribute(node2, ":ref")) {
context2.scope[exp].value = node2;
}
if (exp = checkAndRemoveAttribute(node2, ":value")) {
new ValueDirective({ element: node2, context: context2, expression: exp });
}
if (exp = checkAndRemoveAttribute(node2, ":html")) {
context2.effect(() => {
const result = evalGet(context2.scope, exp);
if (result instanceof Element) {
node2.replaceChildren();
node2.append(result);
} else {
node2.innerHTML = result;
}
});
}
};
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"),
@ -1091,6 +1121,10 @@ function walk(node, context) {
exp: attr.value,
value: isMirrorProp(attr.name) ? evalGet(context.scope, extractPropName(attr.name)) : attr.value ? evalGet(context.scope, attr.value) : 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);
@ -1102,20 +1136,10 @@ function walk(node, context) {
}
return acc;
}, {});
if (exp = checkAndRemoveAttribute(node, ":teleport")) {
return _teleport(node, exp, 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, ":show")) {
new ShowDirective({ element: node, context, expression: exp });
}
const next2 = handleDirectives(node, context, component, componentProps, allProps);
if (next2) return next2;
const templates = findTemplateNodes(node);
const block = new Block({
return new Block({
element: node,
parentContext: context,
component,
@ -1124,27 +1148,10 @@ function walk(node, context) {
templates,
componentProps,
allProps
});
return block.element;
}
if (exp = checkAndRemoveAttribute(node, ":teleport")) {
return _teleport(node, exp, context);
}
if (exp = checkAndRemoveAttribute(node, ":if")) {
return _if(node, exp, context);
}
if (exp = checkAndRemoveAttribute(node, ":for")) {
return _for(node, exp, context);
}
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, ":show")) {
new ShowDirective({ element: node, context, expression: exp });
}).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 });
@ -1157,9 +1164,9 @@ function walk(node, context) {
}
}
function walkChildren(node, context) {
let child = node.firstChild;
while (child) {
child = walk(child, context) || child.nextSibling;
let child2 = node.firstChild;
while (child2) {
child2 = walk(child2, context) || child2.nextSibling;
}
}
var evalFuncCache = {};
@ -1203,28 +1210,84 @@ function flattenRefs(scope) {
}
return mapped;
}
var counter = {
var child = {
template: html`<div>Animal: {{animal}} {{index}}</div>`,
props: { animal: { default: "cat" }, index: { default: 0 } },
main({ animal, index }) {
return { animal, index };
}
};
var parent = {
template: html`
<div>
<div :teleport="body" :if="style.color === 'gray'">true</div>
<h3 {style:bind}>Count: {{count}}{{count >= 2 ? '!!!' : ''}}</h3>
<button @click="increment">+</button>
<button @click="decrement">-</button>
mirror, no bind:
<child {animal} />
<hr />
mirror, bind:
<child {animal:bind} />
<hr />
spread, no bind:
<child ...spread />
<hr />
spread, bind:
<child ...spread:bind />
<hr />
regular prop:
<child .animal="animal" />
<hr />
regular prop, bind:
<child .animal:bind="animal" />
<hr />
<div .id="animal">div has "id" set to animal.value</div>
<hr />
<div .id:bind="animal">div has "id" set and bound to animal.value</div>
<hr />
<div {animal}>div has "animal" set to animal.value</div>
<hr />
<div {animal:bind}>div has "animal" set and bound to animal.value</div>
<hr />
<div ...spread>div has "animal" spread</div>
<hr />
<div ...spread:bind>div has "animal" spread and bound</div>
<hr />
<hr />
<hr />
<hr />
if bool, mirror, no bind:
<child :if="bool" {animal} />
if bool, mirror, bind:
<child :if="bool" {animal:bind} />
<hr />
for list, mirror, no bind:
<child :for="item in list" {animal} />
<hr />
for list, mirror, bind:
<child :for="item in list" {animal:bind} />
if bool, for list, mirror, no bind: these have the value "DOG!" because by the time for :for directive is evaluated, animal.value is "DOG!", and no longer "dog".
<div :if="bool">
<child :for="item in list" {animal} />
</div>
</div>
`,
main() {
const count = ref(0);
const style = reactive({ color: "gray" });
const increment = () => count.value++;
const decrement = () => count.value--;
setInterval(() => {
style.color = style.color === "gray" ? "white" : "gray";
const bool = ref(false);
const animal = ref("dog");
const spread = reactive({ animal: "panther" });
const list = reactive([1, 2, 3]);
setTimeout(() => {
spread.animal = "PANTHER!";
animal.value = "DOG!";
bool.value = true;
}, 500);
return { count, increment, decrement, style };
setTimeout(() => {
animal.value = "DOG!!!!!";
}, 1e3);
return { animal, spread, bool, list };
}
};
var app = new App();
app.mount(counter, "#app");
app.register("child", child);
app.mount(parent, "#app");
export {
App,
Block,

File diff suppressed because one or more lines are too long

View File

@ -417,10 +417,40 @@ function walk(node: Node, context: Context) {
if (isElement(node)) {
let exp: string | null;
if (isComponent(node, context)) {
const component = context.app.getComponent(node.tagName.toLowerCase());
const handleDirectives = (node: Element, context: Context, component?: Component, componentProps?: Record<string, any>, allProps?: any[]) => {
if ((exp = checkAndRemoveAttribute(node, ":teleport"))) {
return _teleport(node, exp, 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, ":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"))) {
context.effect(() => {
const result = evalGet(context.scope, exp);
if (result instanceof Element) {
node.replaceChildren();
node.append(result);
} else {
node.innerHTML = result;
}
});
}
};
const allProps = Array.from(node.attributes)
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),
@ -431,6 +461,11 @@ function walk(node: Node, context: Context) {
exp: attr.value,
value: isMirrorProp(attr.name) ? evalGet(context.scope, extractPropName(attr.name)) : attr.value ? evalGet(context.scope, attr.value) : 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) {
@ -444,25 +479,12 @@ function walk(node: Node, context: Context) {
return acc;
}, {});
if ((exp = checkAndRemoveAttribute(node, ":teleport"))) {
return _teleport(node, exp, 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, ":show"))) {
new ShowDirective({ element: node, context, expression: exp });
}
const next = handleDirectives(node, context, component, componentProps, allProps);
if (next) return next;
const templates = findTemplateNodes(node);
const block = new Block({
return new Block({
element: node,
parentContext: context,
component,
@ -471,34 +493,11 @@ function walk(node: Node, context: Context) {
templates,
componentProps,
allProps,
});
return block.element;
}).element;
}
if ((exp = checkAndRemoveAttribute(node, ":teleport"))) {
return _teleport(node, exp, context);
}
if ((exp = checkAndRemoveAttribute(node, ":if"))) {
return _if(node, exp, context);
}
if ((exp = checkAndRemoveAttribute(node, ":for"))) {
return _for(node, exp, context);
}
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, ":show"))) {
new ShowDirective({ element: node, context, expression: exp });
}
const next = handleDirectives(node, context);
if (next) return next;
Array.from(node.attributes).forEach((attr) => {
if (isPropAttribute(attr.name)) {
@ -650,115 +649,155 @@ function flattenRefs(scope: any): any {
// ------------------------------------------------
// Component pros, mirror and spread, bind and no bind
// const child = {
// template: html`<div>Animal: {{animal}}</div>`,
// props: { animal: { default: "cat" } },
// main({ animal }) {
// return { animal };
// },
// };
const child = {
template: html`<div>Animal: {{animal}} {{index}}</div>`,
props: { animal: { default: "cat" }, index: { default: 0 } },
main({ animal, index }) {
return { animal, index };
},
};
// const parent = {
// template: html`
// <div>
// <div>asdf</div>
// mirror, no bind:
// <child {animal} />
// <hr />
// mirror, bind:
// <child {animal:bind} />
// <hr />
// spread, no bind:
// <child ...spread />
// <hr />
// spread, bind:
// <child ...spread:bind />
// <hr />
// regular prop:
// <child .animal="animal" />
// <hr />
// regular prop, bind:
// <child .animal:bind="animal" />
// <hr />
// <div .id="animal">div has "id" set to animal.value</div>
// <hr />
// <div .id:bind="animal">div has "id" set and bound to animal.value</div>
// <hr />
// <div {animal}>div has "animal" set to animal.value</div>
// <hr />
// <div {animal:bind}>div has "animal" set and bound to animal.value</div>
// <hr />
// <div ...spread>div has "animal" spread</div>
// <hr />
// <div ...spread:bind>div has "animal" spread and bound</div>
// <hr />
// <hr />
// <hr />
// <hr />
// if bool, mirror, no bind:
// <child :if="bool" {animal} />
// if bool, mirror, bind:
// <child :if="bool" {animal:bind} />
// <hr />
// for list, mirror, no bind:
// <child :for="item in list" {animal} />
// <hr />
// for list, mirror, bind:
// <child :for="item in list" {animal:bind} />
// if bool, for list, mirror, no bind: these have the value "DOG!" because by the time for :for directive is evaluated, animal.value is "DOG!", and no longer "dog".
// <div :if="bool">
// <child :for="item in list" {animal} />
// </div>
// </div>
// `,
// main() {
// const bool = ref(false);
// const animal = ref("dog");
// const spread = reactive({ animal: "panther" });
// const list = reactive([1, 2, 3]);
// setTimeout(() => {
// spread.animal = "PANTHER!";
// animal.value = "DOG!";
// bool.value = true;
// }, 500);
// setTimeout(() => {
// animal.value = "DOG!!!!!";
// }, 1000);
// return { animal, spread, bool, list };
// },
// };
// const app = new App();
// app.register("child", child);
// app.mount(parent, "#app");
// ------------------------------------------------
// Event directive
const counter = {
const parent = {
template: html`
<div>
<div :teleport="body" :if="style.color === 'gray'">true</div>
<h3 {style:bind}>Count: {{count}}{{count >= 2 ? '!!!' : ''}}</h3>
<button @click="increment">+</button>
<button @click="decrement">-</button>
mirror, no bind:
<child {animal} />
<hr />
mirror, bind:
<child {animal:bind} />
<hr />
spread, no bind:
<child ...spread />
<hr />
spread, bind:
<child ...spread:bind />
<hr />
regular prop:
<child .animal="animal" />
<hr />
regular prop, bind:
<child .animal:bind="animal" />
<hr />
<div .id="animal">div has "id" set to animal.value</div>
<hr />
<div .id:bind="animal">div has "id" set and bound to animal.value</div>
<hr />
<div {animal}>div has "animal" set to animal.value</div>
<hr />
<div {animal:bind}>div has "animal" set and bound to animal.value</div>
<hr />
<div ...spread>div has "animal" spread</div>
<hr />
<div ...spread:bind>div has "animal" spread and bound</div>
<hr />
<hr />
<hr />
<hr />
if bool, mirror, no bind:
<child :if="bool" {animal} />
if bool, mirror, bind:
<child :if="bool" {animal:bind} />
<hr />
for list, mirror, no bind:
<child :for="item in list" {animal} />
<hr />
for list, mirror, bind:
<child :for="item in list" {animal:bind} />
if bool, for list, mirror, no bind: these have the value "DOG!" because by the time for :for directive is evaluated, animal.value is "DOG!", and no longer "dog".
<div :if="bool">
<child :for="item in list" {animal} />
</div>
</div>
`,
main() {
const count = ref(0);
const style = reactive({ color: "gray" });
const increment = () => count.value++;
const decrement = () => count.value--;
const bool = ref(false);
const animal = ref("dog");
const spread = reactive({ animal: "panther" });
const list = reactive([1, 2, 3]);
setInterval(() => {
style.color = style.color === "gray" ? "white" : "gray";
setTimeout(() => {
spread.animal = "PANTHER!";
animal.value = "DOG!";
bool.value = true;
}, 500);
return { count, increment, decrement, style };
setTimeout(() => {
animal.value = "DOG!!!!!";
}, 1000);
return { animal, spread, bool, list };
},
};
const app = new App();
app.mount(counter, "#app");
app.register("child", child);
app.mount(parent, "#app");
// ------------------------------------------------
// Event directive
// const counter = {
// template: html`
// <div>
// <div :teleport="body" :if="style.color === 'gray'">true</div>
// <h3 {style:bind}>Count: {{count}}{{count >= 2 ? '!!!' : ''}}</h3>
// <button @click="increment">+</button>
// <button @click="decrement">-</button>
// </div>
// `,
// main() {
// const count = ref(0);
// const style = reactive({ color: "gray" });
// const increment = () => count.value++;
// const decrement = () => count.value--;
// setInterval(() => {
// style.color = style.color === "gray" ? "white" : "gray";
// }, 500);
// return { count, increment, decrement, style };
// },
// };
// const app = new App();
// app.mount(counter, "#app");
// ------------------------------------------------
// Template
// const main = {
// template: html`
// <div>
// <div :if="bool">
// <div :for="item in items">{{item}}</div>
// </div>
// </div>
// `,
// main() {
// const items = reactive([1, 2, 3, 4, 5]);
// const bool = ref(true);
// setInterval(() => (bool.value = !bool.value), 250);
// return { items, bool };
// },
// };
// const app = new App();
// app.mount(main, "#app");
// ------------------------------------------------
// :html
// const main = {
// template: html`<div :html="html"></div>`,
// main() {
// const html = ref("<h1>hello</h1>");
// setTimeout(() => {
// if (html.value === "<h1>hello</h1>") {
// html.value = "<h1>world</h1>";
// }
// }, 1000);
// return { html };
// },
// };
// const app = new App();
// app.mount(main, "#app");