Skip to content

Commit 88e8f52

Browse files
committed
feat: iif and dom text diff
1 parent 067576a commit 88e8f52

File tree

8 files changed

+258
-153
lines changed

8 files changed

+258
-153
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {
2+
ReadSignal,
3+
Signal,
4+
signal as createSignal,
5+
} from "../signals/index.ts";
6+
7+
export function iif<T = any, C = any>(
8+
condition: Signal<C> | ReadSignal<C>,
9+
first: (val?: C) => T,
10+
second: (val?: C) => T | null = () => null,
11+
) {
12+
const val = condition.value;
13+
const result = createSignal<T | null>(val ? first(val) : second(val));
14+
condition.subscribe((newValue) => {
15+
// console.log("iif", condition.value, first(), second());
16+
result.value = newValue ? first(newValue) : second(newValue);
17+
});
18+
// Remove old value if it exists
19+
result.subscribe((newValue, oldValue: any) => {
20+
// console.log("iif result", newValue, oldValue);
21+
if (oldValue && oldValue?.remove && oldValue?.isConnected) {
22+
// TODO: handle dom elements
23+
oldValue.remove();
24+
}
25+
});
26+
return result;
27+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
export * from "./iif.ts";
12
export * from "./cls.ts";
23
export * from "./render.ts";

packages/async-framework/jsx-runtime.ts

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Define types for JSX elements and children
22
type Signal<T> = {
3-
subscribe: (callback: (value: T) => void) => void;
3+
subscribe: (callback: (newValue: T, oldValue: T) => void) => void;
44
value: T;
55
};
66
type JSXChild =
@@ -18,17 +18,55 @@ type Component = (props: any) => JSXElement | Signal<any>;
1818
function renderValueBasedOnType(
1919
parent: HTMLElement | DocumentFragment,
2020
type: string,
21-
value: any,
21+
newValue: any,
22+
oldValue: any,
2223
) {
2324
// TODO: render based on value type being a DOM element
2425
switch (type) {
2526
case "number":
2627
case "string":
2728
case "boolean":
28-
parent.appendChild(document.createTextNode(String(value)));
29+
const oldValueString = String(oldValue);
30+
const newValueString = String(newValue);
31+
const textNode = document.createTextNode(newValueString);
32+
if (parent && !parent.firstChild) {
33+
parent.appendChild(textNode);
34+
return;
35+
}
36+
let replaced = false;
37+
Array.from(parent.childNodes).forEach((child) => {
38+
if (child.textContent === oldValueString) {
39+
// console.log("replaced", oldValueString, newValueString);
40+
parent.replaceChild(textNode, child);
41+
replaced = true;
42+
}
43+
});
44+
if (!replaced) {
45+
// console.log("appendChild", newValueString);
46+
parent.appendChild(textNode);
47+
}
2948
break;
49+
case "function":
50+
// handle iif
51+
console.log("renderValueBasedOnType function", newValue);
52+
const result = newValue();
53+
return renderValueBasedOnType(parent, typeof result, result, oldValue);
3054
default:
31-
parent.appendChild(value);
55+
if (parent.firstElementChild === oldValue && parent.firstElementChild) {
56+
console.log("replace child", newValue, oldValue);
57+
parent.replaceChild(newValue, parent.firstElementChild);
58+
} else if (parent.firstChild === oldValue && parent.firstChild) {
59+
console.log("replace child", newValue, oldValue);
60+
parent.replaceChild(newValue, parent.firstChild);
61+
} else if (Array.isArray(newValue)) {
62+
console.log("appendChild array", newValue);
63+
for (const child of newValue) {
64+
appendChild(parent, child);
65+
}
66+
} else {
67+
console.log("appendChild", newValue);
68+
parent.appendChild(newValue);
69+
}
3270
}
3371
}
3472

@@ -49,11 +87,15 @@ export function jsx(
4987
// Handle case where component returns a signal
5088
if ((result as Signal<any>)?.subscribe) {
5189
const signal = result as Signal<any>;
52-
const placeholder = document.createTextNode(String(signal.value));
90+
const value = signal.value;
91+
// const innerParent = document.createDocumentFragment();
92+
const parent = document.createElement("div");
93+
renderValueBasedOnType(parent, typeof value, value, null);
5394
signal.subscribe((newValue: any) => {
54-
placeholder.textContent = String(newValue);
95+
console.log("jsx.signal.subscribe", newValue);
96+
renderValueBasedOnType(parent, typeof newValue, newValue, value);
5597
});
56-
return placeholder as unknown as JSXElement;
98+
return parent as unknown as JSXElement;
5799
}
58100
return result as JSXElement;
59101
}
@@ -114,10 +156,10 @@ function appendChild(
114156
if (value === undefined || value === null) {
115157
value = "";
116158
}
117-
signal.subscribe((newValue: any) => {
118-
renderValueBasedOnType(parent, typeof newValue, newValue);
159+
signal.subscribe((newValue: any, oldValue: any) => {
160+
renderValueBasedOnType(parent, typeof newValue, newValue, oldValue);
119161
});
120-
renderValueBasedOnType(parent, typeof value, value);
162+
renderValueBasedOnType(parent, typeof value, value, null);
121163
return;
122164
}
123165

packages/async-framework/router/router.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,33 @@ function mergeUrl(base: string, path: string) {
1212
}
1313
return uri;
1414
}
15+
function removeBase(base: string, path: string) {
16+
let uri = path.replace(base, "");
17+
if (!uri.startsWith("/")) {
18+
uri = "/" + uri;
19+
}
20+
if (!uri.endsWith("/")) {
21+
uri = uri + "/";
22+
}
23+
uri = uri.replace(/\/\//g, "/");
24+
return uri;
25+
}
1526

1627
export function createRouter({ base }: { base: string }) {
17-
console.log("Router.base", base);
28+
base = base.endsWith("/") ? base : base + "/";
29+
const currentPath = removeBase(base, window.location.pathname);
30+
console.log("Router.base", base, currentPath);
1831
const currentRoute = signal<Route>({
19-
path: window.location.pathname.replace(base, ""),
32+
path: currentPath,
2033
params: {},
2134
});
2235

2336
// Handle browser back/forward
2437
window.addEventListener("popstate", () => {
38+
const currentPath = removeBase(base, window.location.pathname);
39+
console.log("Router.popstate", currentPath);
2540
currentRoute.value = {
26-
path: window.location.pathname.replace(base, ""),
41+
path: currentPath,
2742
params: {},
2843
};
2944
});
@@ -36,8 +51,15 @@ export function createRouter({ base }: { base: string }) {
3651
console.log("Router.initialize", base);
3752
},
3853
navigate(path: string, params: Record<string, string> = {}) {
39-
window.history.pushState({}, "", mergeUrl(base, path));
40-
currentRoute.value = { path, params };
54+
if (!path.endsWith("/")) {
55+
path = path + "/";
56+
}
57+
const uri = mergeUrl(base, path);
58+
console.log("Router.navigate", uri);
59+
window.history.pushState({}, "", uri);
60+
const routeUrl = removeBase(base, uri);
61+
console.log("Router.navigate.routeUrl", routeUrl);
62+
currentRoute.value = { path: routeUrl, params };
4163
},
4264
};
4365
}

packages/examples/resource-manager/App.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ export function App({ router }: { router: ReturnType<typeof createRouter> }) {
99

1010
// Simple router logic
1111
router.current.subscribe(({ path, params }) => {
12-
if (path === "/" || path === "/resources") {
12+
console.log("App.router.current", path);
13+
if (path === "/" || path === "/resources/") {
1314
currentView.value = <ResourceList router={router} />;
1415
} else if (path.startsWith("/resources/")) {
1516
const id = path.split("/")[2];
1617
currentView.value = <ResourceDetails id={id} router={router} />;
17-
} else if (path === "/dashboard") {
18-
currentView.value = <Dashboard router={router} />;
18+
} else if (path === "/dashboard/") {
19+
const dashboard = <Dashboard router={router} />;
20+
currentView.value = dashboard;
1921
} else {
2022
currentView.value = <div>404 - Not Found</div>;
2123
}

0 commit comments

Comments
 (0)