mirror of
https://github.com/nvms/soma3.git
synced 2025-12-15 15:10:53 +00:00
neat
This commit is contained in:
parent
4e18b46c89
commit
bceca3053c
118
public/demo.js
118
public/demo.js
@ -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
1727
public/index.js
1727
public/index.js
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
142
src/demo.ts
142
src/demo.ts
@ -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");
|
||||||
|
|||||||
@ -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));
|
||||||
|
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
32
src/index.ts
32
src/index.ts
@ -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,
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user