@ryupold/vode 1.2.0 → 1.3.0
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 +30 -0
- package/dist/vode.js +220 -92
- package/dist/vode.min.js +1 -1
- package/dist/vode.min.mjs +1 -1
- package/dist/vode.mjs +219 -96
- package/index.ts +5 -1
- package/package.json +3 -3
- package/src/merge-class.ts +62 -0
- package/src/state-context.ts +228 -0
- package/src/vode.ts +140 -140
- package/tsconfig.json +1 -2
package/src/vode.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export type Vode<S> = FullVode<S> | JustTagVode | NoPropsVode<S>;
|
|
2
|
-
export type FullVode<S> = [tag: Tag, props: Props<S>, ...children: ChildVode<S>[]];
|
|
3
|
-
export type NoPropsVode<S> = [tag: Tag, ...children: ChildVode<S>[]] | (TextVode[]);
|
|
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>[]];
|
|
3
|
+
export type NoPropsVode<S = PatchableState> = [tag: Tag, ...children: ChildVode<S>[]] | (TextVode[]);
|
|
4
4
|
export type JustTagVode = [tag: Tag];
|
|
5
|
-
export type ChildVode<S> = Vode<S> | TextVode | NoVode | Component<S>;
|
|
5
|
+
export type ChildVode<S = PatchableState> = Vode<S> | TextVode | NoVode | Component<S>;
|
|
6
6
|
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 };
|
|
@@ -16,21 +16,21 @@ export type Patch<S> =
|
|
|
16
16
|
|
|
17
17
|
export type IgnoredPatch = undefined | null | number | boolean | bigint | string | symbol | void;
|
|
18
18
|
export type RenderPatch<S> = {} | DeepPartial<S>;
|
|
19
|
+
export type AnimatedPatch<S> = Array<Patch<S>>;
|
|
19
20
|
export type DeepPartial<S> = { [P in keyof S]?: S[P] extends Array<infer I> ? Array<DeepPartial<I>> : DeepPartial<S[P]> };
|
|
20
21
|
|
|
21
22
|
export type Effect<S> =
|
|
22
23
|
| (() => Patch<S>)
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
| AsyncGenerator<Patch<S>, unknown, void>;
|
|
24
|
+
| EventFunction<S>
|
|
25
|
+
| Generator<Patch<S>>
|
|
26
|
+
| AsyncGenerator<Patch<S>>;
|
|
27
27
|
|
|
28
|
-
export type
|
|
28
|
+
export type EventFunction<S> = (state: S, evt: Event) => Patch<S>;
|
|
29
29
|
|
|
30
30
|
export type Props<S> = Partial<
|
|
31
31
|
Omit<HTMLElement,
|
|
32
32
|
keyof (DocumentFragment & ElementCSSInlineStyle & GlobalEventHandlers)> &
|
|
33
|
-
{ [K in keyof EventsMap]:
|
|
33
|
+
{ [K in keyof EventsMap]: EventFunction<S> | Patch<S> } // all on* events
|
|
34
34
|
> & {
|
|
35
35
|
[_: string]: unknown,
|
|
36
36
|
class?: ClassProp,
|
|
@@ -68,26 +68,40 @@ export type PropertyValue<S> =
|
|
|
68
68
|
| Patch<S>;
|
|
69
69
|
|
|
70
70
|
export type Dispatch<S> = (action: Patch<S>) => void;
|
|
71
|
-
export type PatchableState<S> = S & { patch: Dispatch<S> };
|
|
71
|
+
export type PatchableState<S = object> = S & { patch: Dispatch<S> };
|
|
72
72
|
|
|
73
|
-
export
|
|
73
|
+
export const globals = {
|
|
74
|
+
currentViewTransition: <ViewTransition | null | undefined>undefined,
|
|
75
|
+
requestAnimationFrame: !!window.requestAnimationFrame ? window.requestAnimationFrame.bind(window) : ((cb: () => void) => cb()),
|
|
76
|
+
startViewTransition: !!document.startViewTransition ? document.startViewTransition.bind(document) : null,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export type ContainerNode<S = PatchableState> = HTMLElement & {
|
|
74
80
|
/** the `_vode` property is added to the container in `app()`.
|
|
75
81
|
* it contains all necessary stuff for the vode app to function.
|
|
76
|
-
*
|
|
82
|
+
* remove the container node to clear vodes resources */
|
|
77
83
|
_vode: {
|
|
78
84
|
state: PatchableState<S>,
|
|
79
85
|
vode: AttachedVode<S>,
|
|
80
86
|
patch: Dispatch<S>,
|
|
81
|
-
|
|
82
|
-
|
|
87
|
+
renderSync: () => void,
|
|
88
|
+
renderAsync: () => Promise<unknown>,
|
|
89
|
+
syncRenderer: (cb: () => void) => void,
|
|
90
|
+
asyncRenderer: ((cb: () => void) => ViewTransition) | null | undefined,
|
|
91
|
+
qSync: {} | undefined | null, // next patch aggregate to be applied
|
|
92
|
+
qAsync: {} | undefined | null, // next render-patches to be animated after another
|
|
83
93
|
isRendering: boolean,
|
|
94
|
+
isAnimating: boolean,
|
|
84
95
|
/** stats about the overall patches & last render time */
|
|
85
96
|
stats: {
|
|
86
97
|
patchCount: number,
|
|
87
98
|
liveEffectCount: number,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
99
|
+
syncRenderPatchCount: number,
|
|
100
|
+
asyncRenderPatchCount: number,
|
|
101
|
+
syncRenderCount: number,
|
|
102
|
+
asyncRenderCount: number,
|
|
103
|
+
lastSyncRenderTime: number,
|
|
104
|
+
lastAsyncRenderTime: number,
|
|
91
105
|
},
|
|
92
106
|
}
|
|
93
107
|
};
|
|
@@ -99,7 +113,7 @@ export type ContainerNode<S> = HTMLElement & {
|
|
|
99
113
|
* - tag, props and children: `vode("div", { class: "foo" }, ["span", "bar"])` => `["div", { class: "foo" }, ["span", "bar"]]` --*rendered*-> `<div class="foo"><span>bar</span></div>`
|
|
100
114
|
* - identity: `vode(["div", ["span", "bar"]])` => `["div", ["span", "bar"]]` --*rendered*-> `<div><span>bar</span></div>`
|
|
101
115
|
*/
|
|
102
|
-
export function vode<S
|
|
116
|
+
export function vode<S = PatchableState>(tag: Tag | Vode<S>, props?: Props<S> | ChildVode<S>, ...children: ChildVode<S>[]): Vode<S> {
|
|
103
117
|
if (!tag) throw new Error("first argument to vode() must be a tag name or a vode");
|
|
104
118
|
|
|
105
119
|
if (Array.isArray(tag)) return tag;
|
|
@@ -114,35 +128,39 @@ export function vode<S extends object | unknown>(tag: Tag | Vode<S>, props?: Pro
|
|
|
114
128
|
* @param initialPatches variadic list of patches that are applied after the first render
|
|
115
129
|
* @returns a patch function that can be used to update the state
|
|
116
130
|
*/
|
|
117
|
-
export function app<S
|
|
131
|
+
export function app<S = PatchableState>(container: Element, state: Omit<S, "patch">, dom: (s: S) => Vode<S>, ...initialPatches: Patch<S>[]) {
|
|
118
132
|
if (!container?.parentElement) throw new Error("first argument to app() must be a valid HTMLElement inside the <html></html> document");
|
|
119
133
|
if (!state || typeof state !== "object") throw new Error("second argument to app() must be a state object");
|
|
120
134
|
if (typeof dom !== "function") throw new Error("third argument to app() must be a function that returns a vode");
|
|
121
135
|
|
|
122
|
-
const _vode = {} as ContainerNode<S>["_vode"];
|
|
123
|
-
_vode.
|
|
136
|
+
const _vode = {} as ContainerNode<S>["_vode"] & { patch: (action: Patch<S>, animate?: boolean) => void };
|
|
137
|
+
_vode.syncRenderer = globals.requestAnimationFrame;
|
|
138
|
+
_vode.asyncRenderer = globals.startViewTransition;
|
|
139
|
+
_vode.qSync = null;
|
|
140
|
+
_vode.qAsync = null;
|
|
141
|
+
_vode.stats = { lastSyncRenderTime: 0, lastAsyncRenderTime: 0, syncRenderCount: 0, asyncRenderCount: 0, liveEffectCount: 0, patchCount: 0, syncRenderPatchCount: 0, asyncRenderPatchCount: 0 };
|
|
124
142
|
|
|
125
143
|
Object.defineProperty(state, "patch", {
|
|
126
144
|
enumerable: false, configurable: true,
|
|
127
|
-
writable: false, value: async (action: Patch<S
|
|
145
|
+
writable: false, value: async (action: Patch<S>, isAsync?: boolean) => {
|
|
128
146
|
if (!action || (typeof action !== "function" && typeof action !== "object")) return;
|
|
129
147
|
_vode.stats.patchCount++;
|
|
130
148
|
|
|
131
|
-
if ((action as AsyncGenerator<Patch<S
|
|
132
|
-
const generator = action as AsyncGenerator<Patch<S
|
|
149
|
+
if ((action as AsyncGenerator<Patch<S>>)?.next) {
|
|
150
|
+
const generator = action as AsyncGenerator<Patch<S>>;
|
|
133
151
|
_vode.stats.liveEffectCount++;
|
|
134
152
|
try {
|
|
135
153
|
let v = await generator.next();
|
|
136
154
|
while (v.done === false) {
|
|
137
155
|
_vode.stats.liveEffectCount++;
|
|
138
156
|
try {
|
|
139
|
-
_vode.patch!(v.value);
|
|
157
|
+
_vode.patch!(v.value, isAsync);
|
|
140
158
|
v = await generator.next();
|
|
141
159
|
} finally {
|
|
142
160
|
_vode.stats.liveEffectCount--;
|
|
143
161
|
}
|
|
144
162
|
}
|
|
145
|
-
_vode.patch!(v.value as Patch<S
|
|
163
|
+
_vode.patch!(v.value as Patch<S>, isAsync);
|
|
146
164
|
} finally {
|
|
147
165
|
_vode.stats.liveEffectCount--;
|
|
148
166
|
}
|
|
@@ -150,57 +168,99 @@ export function app<S extends object | unknown>(container: Element, state: Omit<
|
|
|
150
168
|
_vode.stats.liveEffectCount++;
|
|
151
169
|
try {
|
|
152
170
|
const nextState = await (action as Promise<S>);
|
|
153
|
-
_vode.patch!(<Patch<S>>nextState);
|
|
171
|
+
_vode.patch!(<Patch<S>>nextState, isAsync);
|
|
154
172
|
} finally {
|
|
155
173
|
_vode.stats.liveEffectCount--;
|
|
156
174
|
}
|
|
157
175
|
} else if (Array.isArray(action)) {
|
|
158
|
-
if (
|
|
159
|
-
|
|
160
|
-
_vode.patch!
|
|
161
|
-
|
|
162
|
-
} else {
|
|
163
|
-
_vode.
|
|
176
|
+
if (action.length > 0) {
|
|
177
|
+
for (const p of action) {
|
|
178
|
+
_vode.patch(p, !document.hidden && !!_vode.asyncRenderer);
|
|
179
|
+
}
|
|
180
|
+
} else { //when [] is patched: 1. skip current animation 2. merge all queued async patches into synced queue
|
|
181
|
+
_vode.qSync = mergeState(_vode.qSync || {}, _vode.qAsync, false);
|
|
182
|
+
_vode.qAsync = null;
|
|
183
|
+
globals.currentViewTransition?.skipTransition();
|
|
184
|
+
_vode.stats.syncRenderPatchCount++;
|
|
185
|
+
_vode.renderSync();
|
|
164
186
|
}
|
|
165
187
|
} else if (typeof action === "function") {
|
|
166
|
-
_vode.patch!((<
|
|
188
|
+
_vode.patch!((<(s: S) => unknown>action)(_vode.state), isAsync);
|
|
167
189
|
} else {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
190
|
+
if (isAsync) {
|
|
191
|
+
_vode.stats.asyncRenderPatchCount++;
|
|
192
|
+
_vode.qAsync = mergeState(_vode.qAsync || {}, action, false);
|
|
193
|
+
await _vode.renderAsync();
|
|
194
|
+
} else {
|
|
195
|
+
_vode.stats.syncRenderPatchCount++;
|
|
196
|
+
_vode.qSync = mergeState(_vode.qSync || {}, action, false);
|
|
197
|
+
_vode.renderSync();
|
|
198
|
+
}
|
|
171
199
|
}
|
|
172
200
|
}
|
|
173
201
|
});
|
|
174
202
|
|
|
175
|
-
|
|
203
|
+
function renderDom(isAsync: boolean) {
|
|
204
|
+
const sw = Date.now();
|
|
205
|
+
const vom = dom(_vode.state);
|
|
206
|
+
_vode.vode = render(_vode.state, _vode.patch, container.parentElement as Element, 0, _vode.vode, vom)!;
|
|
207
|
+
|
|
208
|
+
if ((<ContainerNode<S>>container).tagName.toUpperCase() !== (vom[0] as Tag).toUpperCase()) { //the tag name was changed during render -> update reference to vode-app-root
|
|
209
|
+
container = _vode.vode.node as Element;
|
|
210
|
+
(<ContainerNode<S>>container)._vode = _vode
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (!isAsync) {
|
|
214
|
+
_vode.stats.lastSyncRenderTime = Date.now() - sw;
|
|
215
|
+
_vode.stats.syncRenderCount++;
|
|
216
|
+
_vode.isRendering = false;
|
|
217
|
+
if (_vode.qSync) _vode.renderSync();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const sr = renderDom.bind(null, false);
|
|
221
|
+
const ar = renderDom.bind(null, true);
|
|
222
|
+
|
|
223
|
+
Object.defineProperty(_vode, "renderSync", {
|
|
176
224
|
enumerable: false, configurable: true,
|
|
177
|
-
writable: false, value: () =>
|
|
178
|
-
if (_vode.isRendering || !_vode.
|
|
225
|
+
writable: false, value: () => {
|
|
226
|
+
if (_vode.isRendering || !_vode.qSync) return;
|
|
227
|
+
|
|
179
228
|
_vode.isRendering = true;
|
|
229
|
+
|
|
230
|
+
_vode.state = mergeState(_vode.state, _vode.qSync, true);
|
|
231
|
+
_vode.qSync = null;
|
|
232
|
+
|
|
233
|
+
_vode.syncRenderer(sr);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
Object.defineProperty(_vode, "renderAsync", {
|
|
238
|
+
enumerable: false, configurable: true,
|
|
239
|
+
writable: false, value: async () => {
|
|
240
|
+
if (_vode.isAnimating || !_vode.qAsync) return;
|
|
241
|
+
await globals.currentViewTransition?.updateCallbackDone; //sandwich
|
|
242
|
+
if (_vode.isAnimating || !_vode.qAsync || document.hidden) return;
|
|
243
|
+
|
|
244
|
+
_vode.isAnimating = true;
|
|
180
245
|
const sw = Date.now();
|
|
181
246
|
try {
|
|
182
|
-
_vode.state = mergeState(_vode.state, _vode.
|
|
183
|
-
_vode.
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
(<ContainerNode<S>>container)._vode = _vode
|
|
189
|
-
}
|
|
247
|
+
_vode.state = mergeState(_vode.state, _vode.qAsync, true);
|
|
248
|
+
_vode.qAsync = null;
|
|
249
|
+
|
|
250
|
+
globals.currentViewTransition = _vode.asyncRenderer!(ar) as ViewTransition | undefined;
|
|
251
|
+
|
|
252
|
+
await globals.currentViewTransition?.updateCallbackDone;
|
|
190
253
|
} finally {
|
|
191
|
-
_vode.
|
|
192
|
-
_vode.stats.
|
|
193
|
-
_vode.
|
|
194
|
-
if (_vode.q) {
|
|
195
|
-
_vode.render();
|
|
196
|
-
}
|
|
254
|
+
_vode.stats.lastAsyncRenderTime = Date.now() - sw;
|
|
255
|
+
_vode.stats.asyncRenderCount++;
|
|
256
|
+
_vode.isAnimating = false;
|
|
197
257
|
}
|
|
198
|
-
|
|
258
|
+
if (_vode.qAsync) _vode.renderAsync();
|
|
259
|
+
}
|
|
199
260
|
});
|
|
200
261
|
|
|
201
262
|
_vode.patch = (<PatchableState<S>>state).patch;
|
|
202
263
|
_vode.state = <PatchableState<S>>state;
|
|
203
|
-
_vode.q = null;
|
|
204
264
|
|
|
205
265
|
const root = container as ContainerNode<S>;
|
|
206
266
|
root._vode = _vode;
|
|
@@ -222,7 +282,7 @@ export function app<S extends object | unknown>(container: Element, state: Omit<
|
|
|
222
282
|
}
|
|
223
283
|
|
|
224
284
|
/** return vode representation of given DOM node */
|
|
225
|
-
export function hydrate<S =
|
|
285
|
+
export function hydrate<S = PatchableState>(element: Element | Text, prepareForRender?: boolean): Vode<S> | string | AttachedVode<S> | undefined {
|
|
226
286
|
if ((element as Text)?.nodeType === Node.TEXT_NODE) {
|
|
227
287
|
if ((element as Text).nodeValue?.trim() !== "")
|
|
228
288
|
return prepareForRender ? element as Text : (element as Text).nodeValue!;
|
|
@@ -263,7 +323,7 @@ export function hydrate<S = unknown>(element: Element | Text, prepareForRender?:
|
|
|
263
323
|
|
|
264
324
|
/** memoizes the resulting component or props by comparing element by element (===) with the
|
|
265
325
|
* `compare` of the previous render. otherwise skips the render step (not calling `componentOrProps`)*/
|
|
266
|
-
export function memo<S>(compare: any[], componentOrProps: Component<S> | ((s: S) => Props<S>)): typeof componentOrProps extends ((s: S) => Props<S>) ? ((s: S) => Props<S>) : Component<S> {
|
|
326
|
+
export function memo<S = PatchableState>(compare: any[], componentOrProps: Component<S> | ((s: S) => Props<S>)): typeof componentOrProps extends ((s: S) => Props<S>) ? ((s: S) => Props<S>) : Component<S> {
|
|
267
327
|
if (!compare || !Array.isArray(compare)) throw new Error("first argument to memo() must be an array of values to compare");
|
|
268
328
|
if (typeof componentOrProps !== "function") throw new Error("second argument to memo() must be a function that returns a vode or props object");
|
|
269
329
|
|
|
@@ -272,17 +332,17 @@ export function memo<S>(compare: any[], componentOrProps: Component<S> | ((s: S)
|
|
|
272
332
|
}
|
|
273
333
|
|
|
274
334
|
/** create a state object used as state for `app()`. it is updated with `PatchableState.patch()` using `merge()` */
|
|
275
|
-
export function createState<S
|
|
335
|
+
export function createState<S = PatchableState>(state: S): PatchableState<S> {
|
|
276
336
|
if (!state || typeof state !== "object") throw new Error("createState() must be called with a state object");
|
|
277
337
|
|
|
278
338
|
return state as PatchableState<S>;
|
|
279
339
|
}
|
|
280
340
|
|
|
281
341
|
/** type safe way to create a patch. useful for type inference and autocompletion. */
|
|
282
|
-
export function createPatch<S
|
|
342
|
+
export function createPatch<S = PatchableState>(p: DeepPartial<S> | Effect<S> | IgnoredPatch): typeof p { return p; }
|
|
283
343
|
|
|
284
344
|
/** html tag of the vode or `#text` if it is a text node */
|
|
285
|
-
export function tag<S>(v: Vode<S> | TextVode | NoVode | AttachedVode<S>): Tag | "#text" | undefined {
|
|
345
|
+
export function tag<S = PatchableState>(v: Vode<S> | TextVode | NoVode | AttachedVode<S>): Tag | "#text" | undefined {
|
|
286
346
|
return !!v ? (Array.isArray(v)
|
|
287
347
|
? v[0] : (typeof v === "string" || (<any>v).nodeType === Node.TEXT_NODE)
|
|
288
348
|
? "#text" : undefined) as Tag
|
|
@@ -290,7 +350,7 @@ export function tag<S>(v: Vode<S> | TextVode | NoVode | AttachedVode<S>): Tag |
|
|
|
290
350
|
}
|
|
291
351
|
|
|
292
352
|
/** get properties object of a vode, if there is any */
|
|
293
|
-
export function props<S>(vode: ChildVode<S> | AttachedVode<S>): Props<S> | undefined {
|
|
353
|
+
export function props<S = PatchableState>(vode: ChildVode<S> | AttachedVode<S>): Props<S> | undefined {
|
|
294
354
|
if (Array.isArray(vode)
|
|
295
355
|
&& vode.length > 1
|
|
296
356
|
&& vode[1]
|
|
@@ -307,59 +367,8 @@ export function props<S>(vode: ChildVode<S> | AttachedVode<S>): Props<S> | undef
|
|
|
307
367
|
return undefined;
|
|
308
368
|
}
|
|
309
369
|
|
|
310
|
-
/** merge `ClassProp`s regardless of structure */
|
|
311
|
-
export function mergeClass(a: ClassProp, b: ClassProp): ClassProp {
|
|
312
|
-
if (!a) return b;
|
|
313
|
-
if (!b) return a;
|
|
314
|
-
|
|
315
|
-
if (typeof a === "string" && typeof b === "string") {
|
|
316
|
-
const aSplit = a.split(" ");
|
|
317
|
-
const bSplit = b.split(" ");
|
|
318
|
-
const classSet = new Set([...aSplit, ...bSplit]);
|
|
319
|
-
return Array.from(classSet).join(" ").trim();
|
|
320
|
-
}
|
|
321
|
-
else if (typeof a === "string" && Array.isArray(b)) {
|
|
322
|
-
const classSet = new Set([...b, ...a.split(" ")]);
|
|
323
|
-
return Array.from(classSet).join(" ").trim();
|
|
324
|
-
}
|
|
325
|
-
else if (Array.isArray(a) && typeof b === "string") {
|
|
326
|
-
const classSet = new Set([...a, ...b.split(" ")]);
|
|
327
|
-
return Array.from(classSet).join(" ").trim();
|
|
328
|
-
}
|
|
329
|
-
else if (Array.isArray(a) && Array.isArray(b)) {
|
|
330
|
-
const classSet = new Set([...a, ...b]);
|
|
331
|
-
return Array.from(classSet).join(" ").trim();
|
|
332
|
-
}
|
|
333
|
-
else if (typeof a === "string" && typeof b === "object") {
|
|
334
|
-
return { [a]: true, ...b };
|
|
335
|
-
}
|
|
336
|
-
else if (typeof a === "object" && typeof b === "string") {
|
|
337
|
-
return { ...a, [b]: true };
|
|
338
|
-
}
|
|
339
|
-
else if (typeof a === "object" && typeof b === "object") {
|
|
340
|
-
return { ...a, ...b };
|
|
341
|
-
} else if (typeof a === "object" && Array.isArray(b)) {
|
|
342
|
-
const aa = { ...a };
|
|
343
|
-
for (const item of b as string[]) {
|
|
344
|
-
(<Record<string, boolean | null | undefined>>aa)[item] = true;
|
|
345
|
-
}
|
|
346
|
-
return aa;
|
|
347
|
-
} else if (Array.isArray(a) && typeof b === "object") {
|
|
348
|
-
const aa: Record<string, any> = {};
|
|
349
|
-
for (const item of a as string[]) {
|
|
350
|
-
aa[item] = true;
|
|
351
|
-
}
|
|
352
|
-
for (const bKey of Object.keys(b)) {
|
|
353
|
-
aa[bKey] = (<Record<string, boolean | null | undefined>>b)[bKey];
|
|
354
|
-
}
|
|
355
|
-
return aa;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
throw new Error(`cannot merge classes of ${a} (${typeof a}) and ${b} (${typeof b})`);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
370
|
/** get a slice of all children of a vode, if there are any */
|
|
362
|
-
export function children<S>(vode: ChildVode<S> | AttachedVode<S>): ChildVode<S>[] | null {
|
|
371
|
+
export function children<S = PatchableState>(vode: ChildVode<S> | AttachedVode<S>): ChildVode<S>[] | null {
|
|
363
372
|
const start = childrenStart(vode);
|
|
364
373
|
if (start > 0) {
|
|
365
374
|
return (<Vode<S>>vode).slice(start) as Vode<S>[];
|
|
@@ -368,14 +377,14 @@ export function children<S>(vode: ChildVode<S> | AttachedVode<S>): ChildVode<S>[
|
|
|
368
377
|
return null;
|
|
369
378
|
}
|
|
370
379
|
|
|
371
|
-
export function childCount<S>(vode: Vode<S>) { return vode.length - childrenStart(vode); }
|
|
380
|
+
export function childCount<S = PatchableState>(vode: Vode<S>) { return vode.length - childrenStart(vode); }
|
|
372
381
|
|
|
373
|
-
export function child<S>(vode: Vode<S>, index: number): ChildVode<S> | undefined {
|
|
382
|
+
export function child<S = PatchableState>(vode: Vode<S>, index: number): ChildVode<S> | undefined {
|
|
374
383
|
return vode[index + childrenStart(vode)] as ChildVode<S>;
|
|
375
384
|
}
|
|
376
385
|
|
|
377
386
|
/** index in vode at which child-vodes start */
|
|
378
|
-
export function childrenStart<S>(vode: ChildVode<S> | AttachedVode<S>): number {
|
|
387
|
+
export function childrenStart<S = PatchableState>(vode: ChildVode<S> | AttachedVode<S>): number {
|
|
379
388
|
return props(vode) ? 2 : 1;
|
|
380
389
|
}
|
|
381
390
|
|
|
@@ -473,12 +482,12 @@ function render<S>(state: S, patch: Dispatch<S>, parent: Element, childIndex: nu
|
|
|
473
482
|
// falsy|text|element(A) -> element(B)
|
|
474
483
|
if (
|
|
475
484
|
(isNode && (!oldNode || oldIsText || (<Vode<S>>oldVode)[0] !== (<Vode<S>>newVode)[0]))
|
|
476
|
-
) {
|
|
485
|
+
) {
|
|
477
486
|
const newvode = <Vode<S>>newVode;
|
|
478
487
|
if (1 in newvode) {
|
|
479
488
|
newvode[1] = remember(state, newvode[1], undefined) as Vode<S>;
|
|
480
489
|
}
|
|
481
|
-
|
|
490
|
+
|
|
482
491
|
const properties = props(newVode);
|
|
483
492
|
|
|
484
493
|
xmlns = properties?.xmlns as string || xmlns;
|
|
@@ -487,7 +496,7 @@ function render<S>(state: S, patch: Dispatch<S>, parent: Element, childIndex: nu
|
|
|
487
496
|
: document.createElement((<Vode<S>>newVode)[0]);
|
|
488
497
|
(<AttachedVode<S>>newVode).node = newNode;
|
|
489
498
|
|
|
490
|
-
patchProperties(patch, newNode, undefined, properties);
|
|
499
|
+
patchProperties(state, patch, newNode, undefined, properties);
|
|
491
500
|
|
|
492
501
|
if (oldNode) {
|
|
493
502
|
(<any>oldNode).onUnmount && patch((<any>oldNode).onUnmount(oldNode));
|
|
@@ -526,13 +535,13 @@ function render<S>(state: S, patch: Dispatch<S>, parent: Element, childIndex: nu
|
|
|
526
535
|
newvode[1] = remember(state, newvode[1], oldvode[1]) as Vode<S>;
|
|
527
536
|
if (prev !== newvode[1]) {
|
|
528
537
|
const properties = props(newVode);
|
|
529
|
-
patchProperties(patch, oldNode!, props(oldVode), properties);
|
|
538
|
+
patchProperties(state, patch, oldNode!, props(oldVode), properties);
|
|
530
539
|
hasProps = !!properties;
|
|
531
540
|
}
|
|
532
541
|
}
|
|
533
542
|
else {
|
|
534
543
|
const properties = props(newVode);
|
|
535
|
-
patchProperties(patch, oldNode!, props(oldVode), properties);
|
|
544
|
+
patchProperties(state, patch, oldNode!, props(oldVode), properties);
|
|
536
545
|
hasProps = !!properties;
|
|
537
546
|
}
|
|
538
547
|
|
|
@@ -611,7 +620,7 @@ function unwrap<S>(c: Component<S> | ChildVode<S>, s: S): ChildVode<S> {
|
|
|
611
620
|
}
|
|
612
621
|
}
|
|
613
622
|
|
|
614
|
-
function patchProperties<S>(patch: Dispatch<S>, node: ChildNode, oldProps?: Props<S>, newProps?: Props<S>) {
|
|
623
|
+
function patchProperties<S>(s: S, patch: Dispatch<S>, node: ChildNode, oldProps?: Props<S>, newProps?: Props<S>) {
|
|
615
624
|
if (!newProps && !oldProps) return;
|
|
616
625
|
|
|
617
626
|
// match existing properties
|
|
@@ -621,8 +630,8 @@ function patchProperties<S>(patch: Dispatch<S>, node: ChildNode, oldProps?: Prop
|
|
|
621
630
|
const newValue = newProps?.[key as keyof Props<S>] as PropertyValue<S>;
|
|
622
631
|
|
|
623
632
|
if (oldValue !== newValue) {
|
|
624
|
-
if (newProps) newProps[key as keyof Props<S>] = patchProperty(patch, node, key, oldValue, newValue);
|
|
625
|
-
else patchProperty(patch, node, key, oldValue, undefined);
|
|
633
|
+
if (newProps) newProps[key as keyof Props<S>] = patchProperty(s, patch, node, key, oldValue, newValue);
|
|
634
|
+
else patchProperty(s, patch, node, key, oldValue, undefined);
|
|
626
635
|
}
|
|
627
636
|
}
|
|
628
637
|
}
|
|
@@ -632,7 +641,7 @@ function patchProperties<S>(patch: Dispatch<S>, node: ChildNode, oldProps?: Prop
|
|
|
632
641
|
for (const key in newProps) {
|
|
633
642
|
if (!(key in oldProps)) {
|
|
634
643
|
const newValue = newProps[key as keyof Props<S>] as PropertyValue<S>;
|
|
635
|
-
newProps[key as keyof Props<S>] = patchProperty(patch, <Element>node, key, undefined, newValue);
|
|
644
|
+
newProps[key as keyof Props<S>] = patchProperty(s, patch, <Element>node, key, undefined, newValue);
|
|
636
645
|
}
|
|
637
646
|
}
|
|
638
647
|
}
|
|
@@ -640,12 +649,12 @@ function patchProperties<S>(patch: Dispatch<S>, node: ChildNode, oldProps?: Prop
|
|
|
640
649
|
else if (newProps) {
|
|
641
650
|
for (const key in newProps) {
|
|
642
651
|
const newValue = newProps[key as keyof Props<S>] as PropertyValue<S>;
|
|
643
|
-
newProps[key as keyof Props<S>] = patchProperty(patch, <Element>node, key, undefined, newValue);
|
|
652
|
+
newProps[key as keyof Props<S>] = patchProperty(s, patch, <Element>node, key, undefined, newValue);
|
|
644
653
|
}
|
|
645
654
|
}
|
|
646
655
|
}
|
|
647
656
|
|
|
648
|
-
function patchProperty<S>(patch: Dispatch<S>, node: ChildNode, key: string | keyof ElementEventMap, oldValue?: PropertyValue<S>, newValue?: PropertyValue<S>) {
|
|
657
|
+
function patchProperty<S>(s: S, patch: Dispatch<S>, node: ChildNode, key: string | keyof ElementEventMap, oldValue?: PropertyValue<S>, newValue?: PropertyValue<S>) {
|
|
649
658
|
if (key === "style") {
|
|
650
659
|
if (!newValue) {
|
|
651
660
|
(node as HTMLElement).style.cssText = "";
|
|
@@ -675,17 +684,8 @@ function patchProperty<S>(patch: Dispatch<S>, node: ChildNode, key: string | key
|
|
|
675
684
|
if (newValue) {
|
|
676
685
|
let eventHandler: Function | null = null;
|
|
677
686
|
if (typeof newValue === "function") {
|
|
678
|
-
const action = newValue as
|
|
679
|
-
eventHandler = (evt: Event) => patch(
|
|
680
|
-
} else if (Array.isArray(newValue)) {
|
|
681
|
-
const arr = (newValue as Array<any>);
|
|
682
|
-
const action = newValue[0] as EffectFunction<S>;
|
|
683
|
-
if (arr.length > 1) {
|
|
684
|
-
eventHandler = () => patch([action, ...arr.slice(1)]);
|
|
685
|
-
}
|
|
686
|
-
else {
|
|
687
|
-
eventHandler = (evt: Event) => patch([action, evt]);
|
|
688
|
-
}
|
|
687
|
+
const action = newValue as EventFunction<S>;
|
|
688
|
+
eventHandler = (evt: Event) => patch(action(s, evt));
|
|
689
689
|
} else if (typeof newValue === "object") {
|
|
690
690
|
eventHandler = () => patch(newValue as Patch<S>);
|
|
691
691
|
}
|