@ryupold/vode 1.8.7 → 1.8.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -56
- package/dist/vode.cjs.min.js +2 -2
- package/dist/vode.d.ts +10 -10
- package/dist/vode.es5.min.js +7 -7
- package/dist/vode.js +97 -113
- package/dist/vode.min.js +1 -1
- package/dist/vode.min.mjs +1 -1
- package/dist/vode.mjs +97 -113
- package/dist/vode.tests.mjs +5303 -0
- package/package.json +5 -5
- package/src/state-context.ts +6 -4
- package/src/vode.ts +114 -126
- package/test/helper.ts +304 -113
- package/test/index.ts +10 -47
- package/test/mocks.ts +199 -43
- package/test/run-tests.ts +61 -0
- package/test/tests-app.ts +154 -38
- package/test/tests-catch.ts +160 -0
- package/test/tests-children.ts +31 -31
- package/test/tests-createPatch.ts +12 -12
- package/test/tests-createState.ts +11 -11
- package/test/tests-defuse.ts +35 -14
- package/test/tests-examples.ts +991 -0
- package/test/tests-hydrate.ts +59 -25
- package/test/tests-memo.ts +106 -64
- package/test/tests-mergeClass.ts +31 -31
- package/test/tests-mergeProps.ts +19 -19
- package/test/tests-mergeStyle.ts +28 -14
- package/test/tests-mount-unmount.ts +177 -154
- package/test/tests-patch-advanced.ts +86 -0
- package/test/tests-patch-merge.ts +66 -0
- package/test/tests-props.ts +15 -15
- package/test/tests-state-context.ts +56 -25
- package/test/tests-tag.ts +14 -14
- package/test/tests-vode.ts +6 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ryupold/vode",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.10",
|
|
4
4
|
"description": "a minimalist web framework",
|
|
5
5
|
"author": "Michael Scherbakow (ryupold)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,11 +25,10 @@
|
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://github.com/ryupold/vode#readme",
|
|
27
27
|
"type": "module",
|
|
28
|
-
"main": "./dist/vode.mjs",
|
|
29
|
-
"types": "./dist/vode.d.ts",
|
|
30
28
|
"exports": {
|
|
31
29
|
".": {
|
|
32
30
|
"types": "./dist/vode.d.ts",
|
|
31
|
+
"tests": "./dist/vode.tests.mjs",
|
|
33
32
|
"import": "./dist/vode.min.mjs",
|
|
34
33
|
"require": "./dist/vode.cjs.min.js",
|
|
35
34
|
"default": "./dist/vode.min.js"
|
|
@@ -43,11 +42,12 @@
|
|
|
43
42
|
"build-classic-min": "esbuild index.ts --outfile=dist/vode.min.js --bundle --format=iife --global-name=V --minify",
|
|
44
43
|
"babel": "npx babel dist/vode.mjs --out-file dist/vode.cjs.min.js",
|
|
45
44
|
"babel-classic": "npx babel dist/vode.js --out-file dist/vode.es5.min.js",
|
|
46
|
-
"release": "npm run build && npm run build-min && npm run build-classic && npm run build-classic-min && npm run babel && npm run babel-classic && npm run types",
|
|
45
|
+
"release": "npm run build && npm run build-min && npm run build-classic && npm run build-classic-min && npm run babel && npm run babel-classic && npm run types && npm run build-tests",
|
|
47
46
|
"publish": "npm publish --access public",
|
|
48
47
|
"clean": "tsc -b --clean && rm dist/*",
|
|
49
48
|
"watch": "tsc -b -w",
|
|
50
|
-
"test": "tsc -p tsconfig.test.json && esbuild test/
|
|
49
|
+
"test": "tsc -p tsconfig.test.json && esbuild test/run-tests.ts --bundle --outfile=test/run-tests.js --platform=node && node test/run-tests.js",
|
|
50
|
+
"build-tests": "esbuild test/index.ts --bundle --format=esm --outfile=dist/vode.tests.mjs"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@babel/cli": "7.28.6",
|
package/src/state-context.ts
CHANGED
|
@@ -41,13 +41,13 @@ export interface SubContext<SubState> {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
export type ProxyStateContext<S extends PatchableState, SubState> = StateContext<S, SubState> & {
|
|
44
|
-
[K in keyof SubState]-?: SubState[K] extends object
|
|
44
|
+
[K in keyof SubState]-?: SubState[K] extends object | null
|
|
45
45
|
? ProxyStateContext<S, SubState[K]>
|
|
46
46
|
: StateContext<S, SubState[K]>
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
export type ProxySubContext<SubState> = SubContext<SubState> & {
|
|
50
|
-
[K in keyof SubState]-?: SubState[K] extends object
|
|
50
|
+
[K in keyof SubState]-?: SubState[K] extends object | null
|
|
51
51
|
? ProxySubContext<SubState[K]>
|
|
52
52
|
: SubContext<SubState[K]>
|
|
53
53
|
};
|
|
@@ -107,10 +107,12 @@ class ProxyStateContextImpl<S extends PatchableState, SubState>
|
|
|
107
107
|
}
|
|
108
108
|
raw[keys[i]] = value;
|
|
109
109
|
} else if (keys.length === 1) {
|
|
110
|
-
if (typeof (<any>target)[keys[0]] === "object" && typeof value === "object")
|
|
110
|
+
if (typeof (<any>target)[keys[0]] === "object" && typeof value === "object" && value !== null) {
|
|
111
111
|
Object.assign((<any>target)[keys[0]], value);
|
|
112
|
-
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
113
114
|
(<any>target)[keys[0]] = value;
|
|
115
|
+
}
|
|
114
116
|
} else {
|
|
115
117
|
Object.assign(target, value as DeepPartial<S>);
|
|
116
118
|
}
|
package/src/vode.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type Vode<S = PatchableState> = FullVode<S> | JustTagVode | NoPropsVode<S>;
|
|
2
|
-
export type FullVode<S = PatchableState> = [tag: Tag, props: Props<S>, ...children: ChildVode<S>[]];
|
|
2
|
+
export type FullVode<S = PatchableState> = [tag: Tag, props: Props<S> | ChildVode<S>, ...children: ChildVode<S>[]];
|
|
3
3
|
export type NoPropsVode<S = PatchableState> = [tag: Tag, ...children: ChildVode<S>[]] | (TextVode[]);
|
|
4
4
|
export type JustTagVode = [tag: Tag];
|
|
5
5
|
export type ChildVode<S = PatchableState> = Vode<S> | TextVode | NoVode | Component<S>;
|
|
@@ -7,7 +7,7 @@ export type TextVode = string & {};
|
|
|
7
7
|
export type NoVode = undefined | null | number | boolean | bigint | void;
|
|
8
8
|
export type AttachedVode<S> = Vode<S> & { node: ChildNode } | Text & { node?: never };
|
|
9
9
|
export type Tag = keyof (HTMLElementTagNameMap & SVGElementTagNameMap & MathMLElementTagNameMap) | (string & {});
|
|
10
|
-
export type Component<S> = (s: S) => ChildVode<S>;
|
|
10
|
+
export type Component<S = PatchableState> = (s: S) => ChildVode<S>;
|
|
11
11
|
|
|
12
12
|
export type Patch<S> =
|
|
13
13
|
| IgnoredPatch // ignored
|
|
@@ -37,9 +37,9 @@ export interface Props<S = PatchableState> extends Partial<
|
|
|
37
37
|
class?: ClassProp,
|
|
38
38
|
style?: StyleProp,
|
|
39
39
|
/** called after the element was attached */
|
|
40
|
-
onMount?: MountFunction<S
|
|
40
|
+
onMount?: MountFunction<S> | null | false,
|
|
41
41
|
/** called before the element is detached */
|
|
42
|
-
onUnmount?: MountFunction<S
|
|
42
|
+
onUnmount?: MountFunction<S> | null | false,
|
|
43
43
|
/** used instead of original vode when an error occurs during rendering */
|
|
44
44
|
catch?: ((s: S, error: Error) => ChildVode<S>) | ChildVode<S>;
|
|
45
45
|
};
|
|
@@ -93,9 +93,8 @@ export interface ContainerNode<S = PatchableState> extends HTMLElement {
|
|
|
93
93
|
renderAsync: () => Promise<unknown>,
|
|
94
94
|
syncRenderer: (cb: () => void) => void,
|
|
95
95
|
asyncRenderer: ((cb: () => void) => ViewTransition) | null | undefined,
|
|
96
|
-
qSync: {} | undefined | null, // next patch aggregate to be applied
|
|
97
96
|
qAsync: {} | undefined | null, // next render-patches to be animated after another
|
|
98
|
-
isRendering:
|
|
97
|
+
isRendering: number,
|
|
99
98
|
isAnimating: boolean,
|
|
100
99
|
/** stats about the overall patches & last render time */
|
|
101
100
|
stats: {
|
|
@@ -146,7 +145,7 @@ export function app<S extends PatchableState = PatchableState>(
|
|
|
146
145
|
const _vode = {} as ContainerNode<S>["_vode"];
|
|
147
146
|
_vode.syncRenderer = globals.requestAnimationFrame;
|
|
148
147
|
_vode.asyncRenderer = globals.startViewTransition;
|
|
149
|
-
_vode.
|
|
148
|
+
_vode.isRendering = 0;
|
|
150
149
|
_vode.qAsync = null;
|
|
151
150
|
_vode.stats = { lastSyncRenderTime: 0, lastAsyncRenderTime: 0, syncRenderCount: 0, asyncRenderCount: 0, liveEffectCount: 0, patchCount: 0, syncRenderPatchCount: 0, asyncRenderPatchCount: 0 };
|
|
152
151
|
|
|
@@ -156,68 +155,79 @@ export function app<S extends PatchableState = PatchableState>(
|
|
|
156
155
|
initialPatches = [...(state as any).patch.initialPatches, ...initialPatches];
|
|
157
156
|
}
|
|
158
157
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
158
|
+
async function promisePatch(action: Promise<Patch<S>>, isAnimated?: boolean) {
|
|
159
|
+
_vode.stats.liveEffectCount++;
|
|
160
|
+
try {
|
|
161
|
+
const resolvedPatch = await (action as Promise<S>);
|
|
162
|
+
patchableState.patch(<Patch<S>>resolvedPatch, isAnimated);
|
|
163
|
+
} finally {
|
|
164
|
+
_vode.stats.liveEffectCount--;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
164
167
|
|
|
165
|
-
|
|
166
|
-
|
|
168
|
+
async function generatorPatch(action: AsyncGenerator<Patch<S>>, isAnimated?: boolean) {
|
|
169
|
+
const generator = action as AsyncGenerator<Patch<S>>;
|
|
170
|
+
_vode.stats.liveEffectCount++;
|
|
171
|
+
try {
|
|
172
|
+
let v = await generator.next();
|
|
173
|
+
while (v.done === false) {
|
|
167
174
|
_vode.stats.liveEffectCount++;
|
|
168
175
|
try {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
_vode.stats.liveEffectCount++;
|
|
172
|
-
try {
|
|
173
|
-
patchableState.patch(v.value, isAsync);
|
|
174
|
-
v = await generator.next();
|
|
175
|
-
} finally {
|
|
176
|
-
_vode.stats.liveEffectCount--;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
patchableState.patch(v.value as Patch<S>, isAsync);
|
|
176
|
+
patchableState.patch(v.value, isAnimated);
|
|
177
|
+
v = await generator.next();
|
|
180
178
|
} finally {
|
|
181
179
|
_vode.stats.liveEffectCount--;
|
|
182
180
|
}
|
|
181
|
+
}
|
|
182
|
+
patchableState.patch(v.value as Patch<S>, isAnimated);
|
|
183
|
+
} finally {
|
|
184
|
+
_vode.stats.liveEffectCount--;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
Object.defineProperty(state, "patch", {
|
|
189
|
+
enumerable: false, configurable: true,
|
|
190
|
+
writable: false, value: (action: Patch<S>, isAnimated?: boolean) => {
|
|
191
|
+
while (typeof action === "function") {
|
|
192
|
+
action = (<(s: S) => unknown>action)(_vode.state);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!action || typeof action !== "object") return;
|
|
196
|
+
|
|
197
|
+
_vode.stats.patchCount++;
|
|
198
|
+
|
|
199
|
+
if ((action as AsyncGenerator<Patch<S>>)?.next) {
|
|
200
|
+
generatorPatch(action as AsyncGenerator<Patch<S>>, isAnimated);
|
|
183
201
|
} else if ((action as Promise<S>).then) {
|
|
184
|
-
|
|
185
|
-
try {
|
|
186
|
-
const resolvedPatch = await (action as Promise<S>);
|
|
187
|
-
patchableState.patch(<Patch<S>>resolvedPatch, isAsync);
|
|
188
|
-
} finally {
|
|
189
|
-
_vode.stats.liveEffectCount--;
|
|
190
|
-
}
|
|
202
|
+
promisePatch(action as Promise<S>, isAnimated);
|
|
191
203
|
} else if (Array.isArray(action)) {
|
|
192
204
|
if (action.length > 0) {
|
|
193
205
|
for (const p of action) {
|
|
194
206
|
patchableState.patch(p, !document.hidden && !!_vode.asyncRenderer);
|
|
195
207
|
}
|
|
196
208
|
} else { //when [] is patched: 1. skip current animation 2. merge all queued async patches into synced queue
|
|
197
|
-
|
|
209
|
+
mergeState(_vode.state, _vode.qAsync, true);
|
|
198
210
|
_vode.qAsync = null;
|
|
199
211
|
try { globals.currentViewTransition?.skipTransition(); } catch { }
|
|
200
212
|
_vode.stats.syncRenderPatchCount++;
|
|
201
213
|
_vode.renderSync();
|
|
202
214
|
}
|
|
203
|
-
} else if (typeof action === "function") {
|
|
204
|
-
patchableState.patch((<(s: S) => unknown>action)(_vode.state), isAsync);
|
|
205
215
|
} else {
|
|
206
|
-
if (
|
|
216
|
+
if (isAnimated) {
|
|
207
217
|
_vode.stats.asyncRenderPatchCount++;
|
|
208
218
|
_vode.qAsync = mergeState(_vode.qAsync || {}, action, false);
|
|
209
|
-
|
|
219
|
+
_vode.renderAsync();
|
|
210
220
|
} else {
|
|
211
221
|
_vode.stats.syncRenderPatchCount++;
|
|
212
|
-
|
|
222
|
+
mergeState(_vode.state, action, true);
|
|
213
223
|
_vode.renderSync();
|
|
214
224
|
}
|
|
215
225
|
}
|
|
216
226
|
}
|
|
217
227
|
});
|
|
218
228
|
|
|
219
|
-
function renderDom(
|
|
220
|
-
const sw =
|
|
229
|
+
function renderDom(isAnimated: boolean) {
|
|
230
|
+
const sw = performance.now();
|
|
221
231
|
const vom = dom(_vode.state);
|
|
222
232
|
_vode.vode = render<S>(_vode.state, container.parentElement as Element, 0, 0, _vode.vode, vom)!;
|
|
223
233
|
|
|
@@ -226,11 +236,13 @@ export function app<S extends PatchableState = PatchableState>(
|
|
|
226
236
|
(<ContainerNode<S>>container)._vode = _vode
|
|
227
237
|
}
|
|
228
238
|
|
|
229
|
-
if (!
|
|
230
|
-
_vode.stats.lastSyncRenderTime =
|
|
239
|
+
if (!isAnimated) {
|
|
240
|
+
_vode.stats.lastSyncRenderTime = performance.now() - sw;
|
|
241
|
+
const changesSinceRender = _vode.isRendering !== _vode.stats.syncRenderPatchCount;
|
|
231
242
|
_vode.stats.syncRenderCount++;
|
|
232
|
-
_vode.isRendering =
|
|
233
|
-
if (
|
|
243
|
+
_vode.isRendering = 0;
|
|
244
|
+
if (changesSinceRender)
|
|
245
|
+
_vode.renderSync();
|
|
234
246
|
}
|
|
235
247
|
}
|
|
236
248
|
const sr = renderDom.bind(null, false);
|
|
@@ -239,13 +251,9 @@ export function app<S extends PatchableState = PatchableState>(
|
|
|
239
251
|
Object.defineProperty(_vode, "renderSync", {
|
|
240
252
|
enumerable: false, configurable: true,
|
|
241
253
|
writable: false, value: () => {
|
|
242
|
-
if (_vode.isRendering
|
|
243
|
-
|
|
244
|
-
_vode.isRendering = true;
|
|
245
|
-
|
|
246
|
-
_vode.state = mergeState(_vode.state, _vode.qSync, true);
|
|
247
|
-
_vode.qSync = null;
|
|
254
|
+
if (_vode.isRendering) return;
|
|
248
255
|
|
|
256
|
+
_vode.isRendering = _vode.stats.syncRenderPatchCount;
|
|
249
257
|
_vode.syncRenderer(sr);
|
|
250
258
|
}
|
|
251
259
|
});
|
|
@@ -258,7 +266,7 @@ export function app<S extends PatchableState = PatchableState>(
|
|
|
258
266
|
if (_vode.isAnimating || !_vode.qAsync || document.hidden) return;
|
|
259
267
|
|
|
260
268
|
_vode.isAnimating = true;
|
|
261
|
-
const sw =
|
|
269
|
+
const sw = performance.now();
|
|
262
270
|
try {
|
|
263
271
|
_vode.state = mergeState(_vode.state, _vode.qAsync, true);
|
|
264
272
|
_vode.qAsync = null;
|
|
@@ -267,7 +275,7 @@ export function app<S extends PatchableState = PatchableState>(
|
|
|
267
275
|
|
|
268
276
|
await globals.currentViewTransition?.updateCallbackDone;
|
|
269
277
|
} finally {
|
|
270
|
-
_vode.stats.lastAsyncRenderTime =
|
|
278
|
+
_vode.stats.lastAsyncRenderTime = performance.now() - sw;
|
|
271
279
|
_vode.stats.asyncRenderCount++;
|
|
272
280
|
_vode.isAnimating = false;
|
|
273
281
|
}
|
|
@@ -281,7 +289,8 @@ export function app<S extends PatchableState = PatchableState>(
|
|
|
281
289
|
root._vode = _vode;
|
|
282
290
|
const indexInParent = Array.from(container.parentElement.children).indexOf(container);
|
|
283
291
|
|
|
284
|
-
|
|
292
|
+
const patchCountBefore = _vode.stats.syncRenderPatchCount;
|
|
293
|
+
_vode.isRendering = _vode.stats.syncRenderPatchCount;
|
|
285
294
|
_vode.vode = render(
|
|
286
295
|
<S>state,
|
|
287
296
|
container.parentElement,
|
|
@@ -290,8 +299,10 @@ export function app<S extends PatchableState = PatchableState>(
|
|
|
290
299
|
hydrate<S>(container, true) as AttachedVode<S>,
|
|
291
300
|
dom(<S>state)
|
|
292
301
|
)!;
|
|
293
|
-
_vode.
|
|
294
|
-
|
|
302
|
+
const continueRendering = _vode.stats.syncRenderPatchCount !== patchCountBefore;
|
|
303
|
+
_vode.isRendering = 0;
|
|
304
|
+
_vode.stats.syncRenderCount++;
|
|
305
|
+
if (continueRendering) _vode.renderSync();
|
|
295
306
|
|
|
296
307
|
for (const effect of initialPatches) {
|
|
297
308
|
patchableState.patch(effect);
|
|
@@ -384,13 +395,20 @@ export function hydrate<S = PatchableState>(element: Element | Text, prepareForR
|
|
|
384
395
|
}
|
|
385
396
|
|
|
386
397
|
/** memoizes the resulting component or props by comparing element by element (===) with the
|
|
387
|
-
* `compare` of the previous render. otherwise skips the render step (not calling `componentOrProps`)
|
|
388
|
-
|
|
398
|
+
* `compare` of the previous render. otherwise skips the render step (not calling `componentOrProps`)
|
|
399
|
+
*/
|
|
400
|
+
export function memo<S = PatchableState>(compare: any[], component: Component<S>): Component<S> {
|
|
389
401
|
if (!compare || !Array.isArray(compare)) throw new Error("first argument to memo() must be an array of values to compare");
|
|
390
|
-
if (typeof
|
|
402
|
+
if (typeof component !== "function") throw new Error("second argument to memo() must be a function that returns a child vode");
|
|
403
|
+
|
|
404
|
+
if ((<any>component).__memo) { // wrap to prevent double memoization
|
|
405
|
+
const comp = component;
|
|
406
|
+
component = (s: S) => comp(s);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
(<any>component).__memo = compare;
|
|
391
410
|
|
|
392
|
-
|
|
393
|
-
return componentOrProps as typeof componentOrProps extends ((s: S) => Props<S>) ? ((s: S) => Props<S>) : Component<S>;
|
|
411
|
+
return component;
|
|
394
412
|
}
|
|
395
413
|
|
|
396
414
|
/**
|
|
@@ -559,7 +577,7 @@ function render<S extends PatchableState>(state: S, parent: Element, childIndex:
|
|
|
559
577
|
for (let i = indexInParent; i < parent.childNodes.length; i++) {
|
|
560
578
|
const nextSibling = parent.childNodes[i];
|
|
561
579
|
if (nextSibling) {
|
|
562
|
-
nextSibling.before(text
|
|
580
|
+
nextSibling.before(text);
|
|
563
581
|
inserted = true;
|
|
564
582
|
break;
|
|
565
583
|
}
|
|
@@ -605,7 +623,7 @@ function render<S extends PatchableState>(state: S, parent: Element, childIndex:
|
|
|
605
623
|
for (let i = indexInParent; i < parent.childNodes.length; i++) {
|
|
606
624
|
const nextSibling = parent.childNodes[i];
|
|
607
625
|
if (nextSibling) {
|
|
608
|
-
nextSibling.before(newNode
|
|
626
|
+
nextSibling.before(newNode);
|
|
609
627
|
inserted = true;
|
|
610
628
|
break;
|
|
611
629
|
}
|
|
@@ -615,12 +633,12 @@ function render<S extends PatchableState>(state: S, parent: Element, childIndex:
|
|
|
615
633
|
}
|
|
616
634
|
}
|
|
617
635
|
|
|
618
|
-
const
|
|
619
|
-
if (
|
|
636
|
+
const newStart = childrenStart(newVode);
|
|
637
|
+
if (newStart > 0) {
|
|
620
638
|
const childOffset = !!properties ? 2 : 1;
|
|
621
639
|
let indexP = 0;
|
|
622
|
-
for (let i = 0; i <
|
|
623
|
-
const child =
|
|
640
|
+
for (let i = 0; i < (<Vode<S>>newVode).length - newStart; i++) {
|
|
641
|
+
const child = (<Vode<S>>newVode)[i + newStart] as ChildVode<S>;
|
|
624
642
|
// render child in xml mode to prevent using the dom properties
|
|
625
643
|
const attached = render(state, newNode as Element, i, indexP, undefined, child, xmlns ?? null);
|
|
626
644
|
(<Vode<S>>newVode!)[i + childOffset] = <Vode<S>>attached;
|
|
@@ -639,49 +657,37 @@ function render<S extends PatchableState>(state: S, parent: Element, childIndex:
|
|
|
639
657
|
if (!oldIsText && isNode && (<Vode<S>>oldVode)[0] === (<Vode<S>>newVode)[0]) {
|
|
640
658
|
(<AttachedVode<S>>newVode).node = oldNode;
|
|
641
659
|
|
|
642
|
-
const newvode = <Vode<S>>newVode;
|
|
643
|
-
const oldvode = <Vode<S>>oldVode;
|
|
644
|
-
|
|
645
660
|
const properties = props(newVode);
|
|
646
661
|
const oldProps = props(oldVode);
|
|
647
662
|
|
|
648
|
-
if (properties?.xmlns !== undefined)
|
|
663
|
+
if (properties?.xmlns !== undefined)
|
|
664
|
+
xmlns = properties.xmlns;
|
|
649
665
|
|
|
650
|
-
|
|
651
|
-
const prev = newvode[1] as any;
|
|
652
|
-
newvode[1] = remember(state, newvode[1], oldvode[1]) as Vode<S>;
|
|
653
|
-
if (prev !== newvode[1]) {
|
|
654
|
-
patchProperties(state, oldNode!, oldProps, properties, xmlns);
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
else {
|
|
658
|
-
patchProperties(state, oldNode!, oldProps, properties, xmlns);
|
|
659
|
-
}
|
|
666
|
+
patchProperties(state, oldNode!, oldProps, properties, xmlns);
|
|
660
667
|
|
|
661
668
|
if (!!properties?.catch && oldProps?.catch !== properties.catch) {
|
|
662
669
|
(<any>newVode).node['catch'] = null;
|
|
663
670
|
(<any>newVode).node.removeAttribute('catch');
|
|
664
671
|
}
|
|
665
672
|
|
|
666
|
-
const
|
|
667
|
-
const
|
|
668
|
-
if (
|
|
669
|
-
const childOffset = !!properties ? 2 : 1;
|
|
673
|
+
const newStart = childrenStart(newVode);
|
|
674
|
+
const oldStart = childrenStart(oldVode);
|
|
675
|
+
if (newStart > 0) {
|
|
670
676
|
let indexP = 0;
|
|
671
|
-
for (let i = 0; i <
|
|
672
|
-
const child =
|
|
673
|
-
const oldChild =
|
|
677
|
+
for (let i = 0; i < (<Vode<S>>newVode).length - newStart; i++) {
|
|
678
|
+
const child = (<Vode<S>>newVode)[i + newStart] as ChildVode<S>;
|
|
679
|
+
const oldChild = oldStart > 0 ? (<Vode<S>>oldVode)[i + oldStart] as AttachedVode<S> : undefined;
|
|
674
680
|
|
|
675
681
|
const attached = render(state, oldNode as Element, i, indexP, oldChild, child, xmlns);
|
|
676
|
-
(<
|
|
682
|
+
(<any>newVode)[i + newStart] = attached;
|
|
677
683
|
if (attached) indexP++;
|
|
678
684
|
}
|
|
679
685
|
}
|
|
680
686
|
|
|
681
|
-
if (
|
|
682
|
-
const newKidsCount =
|
|
683
|
-
for (let i =
|
|
684
|
-
render(state, oldNode as Element, i, i,
|
|
687
|
+
if (oldStart > 0) {
|
|
688
|
+
const newKidsCount = newStart > 0 ? (<Vode<S>>newVode).length - newStart : 0;
|
|
689
|
+
for (let i = (<Vode<S>>oldVode).length - 1 - oldStart; i >= newKidsCount; i--) {
|
|
690
|
+
render(state, oldNode as Element, i, i, (<any>oldVode)[i + oldStart], undefined, xmlns);
|
|
685
691
|
}
|
|
686
692
|
}
|
|
687
693
|
|
|
@@ -745,14 +751,19 @@ function isTextVode<S>(x: ChildVode<S>): x is TextVode {
|
|
|
745
751
|
}
|
|
746
752
|
|
|
747
753
|
function remember<S>(state: S, present: any, past: any): ChildVode<S> | AttachedVode<S> {
|
|
754
|
+
while (typeof present === "function" && !present.__memo) {
|
|
755
|
+
present = present(state);
|
|
756
|
+
}
|
|
757
|
+
|
|
748
758
|
if (typeof present !== "function")
|
|
749
759
|
return present;
|
|
750
760
|
|
|
751
|
-
const presentMemo = present?.__memo;
|
|
752
|
-
const pastMemo = past?.__memo;
|
|
753
761
|
|
|
754
|
-
|
|
755
|
-
|
|
762
|
+
const presentMemo: unknown[] = present?.__memo;
|
|
763
|
+
const pastMemo: unknown[] = past?.__memo;
|
|
764
|
+
|
|
765
|
+
if (
|
|
766
|
+
Array.isArray(presentMemo) && Array.isArray(pastMemo)
|
|
756
767
|
&& presentMemo.length === pastMemo.length
|
|
757
768
|
) {
|
|
758
769
|
let same = true;
|
|
@@ -765,40 +776,17 @@ function remember<S>(state: S, present: any, past: any): ChildVode<S> | Attached
|
|
|
765
776
|
if (same) return past;
|
|
766
777
|
}
|
|
767
778
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
const resultMemo = result.__memo;
|
|
772
|
-
if (Array.isArray(resultMemo) && Array.isArray(pastMemo) && resultMemo.length === pastMemo.length) {
|
|
773
|
-
let same = true;
|
|
774
|
-
for (let i = 0; i < resultMemo.length; i++) {
|
|
775
|
-
if (resultMemo[i] !== pastMemo[i]) {
|
|
776
|
-
same = false;
|
|
777
|
-
break;
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
if (same) return past;
|
|
781
|
-
}
|
|
782
|
-
const innerRender = result(state);
|
|
783
|
-
if (typeof innerRender === "object") {
|
|
784
|
-
innerRender.__memo = resultMemo;
|
|
785
|
-
}
|
|
786
|
-
return innerRender;
|
|
779
|
+
// memos are not equal so we unwrap the present
|
|
780
|
+
while (typeof present === "function") {
|
|
781
|
+
present = present(state);
|
|
787
782
|
}
|
|
788
783
|
|
|
789
|
-
|
|
790
|
-
if (typeof
|
|
791
|
-
(<any>
|
|
784
|
+
// attach memo to the unwrapped present for future comparisons
|
|
785
|
+
if (typeof present === "object") {
|
|
786
|
+
(<any>present).__memo = presentMemo;
|
|
792
787
|
}
|
|
793
|
-
return newRender;
|
|
794
|
-
}
|
|
795
788
|
|
|
796
|
-
|
|
797
|
-
if (typeof c === "function") {
|
|
798
|
-
return unwrap(c(s), s);
|
|
799
|
-
} else {
|
|
800
|
-
return c;
|
|
801
|
-
}
|
|
789
|
+
return present;
|
|
802
790
|
}
|
|
803
791
|
|
|
804
792
|
function patchProperties<S extends PatchableState>(
|