@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/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
- | EffectFunction<S>
24
- | [effect: EffectFunction<S>, ...args: any[]]
25
- | Generator<Patch<S>, unknown, void>
26
- | AsyncGenerator<Patch<S>, unknown, void>;
24
+ | EventFunction<S>
25
+ | Generator<Patch<S>>
26
+ | AsyncGenerator<Patch<S>>;
27
27
 
28
- export type EffectFunction<S> = (state: S, ...args: any[]) => Patch<S>;
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]: ((state: S, evt: Event) => Patch<S>) | Patch<S> } // all on* events
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 type ContainerNode<S> = HTMLElement & {
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
- * delete it to clear all resources of the vode app, or remove the container itself */
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
- render: () => void,
82
- q: object | null, // next patch aggregate to be applied
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
- renderPatchCount: number,
89
- renderCount: number,
90
- lastRenderTime: number,
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 extends object | unknown>(tag: Tag | Vode<S>, props?: Props<S> | ChildVode<S>, ...children: ChildVode<S>[]): 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 extends object | unknown>(container: Element, state: Omit<S, "patch">, dom: (s: S) => Vode<S>, ...initialPatches: Patch<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.stats = { lastRenderTime: 0, renderCount: 0, liveEffectCount: 0, patchCount: 0, renderPatchCount: 0 };
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>, unknown, void>)?.next) {
132
- const generator = action as AsyncGenerator<Patch<S>, unknown, void>;
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 (typeof action[0] === "function") {
159
- if (action.length > 1)
160
- _vode.patch!(action[0](_vode.state!, ...(action as any[]).slice(1)));
161
- else _vode.patch!(action[0](_vode.state!));
162
- } else {
163
- _vode.stats.patchCount--;
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!((<EffectFunction<S>>action)(_vode.state));
188
+ _vode.patch!((<(s: S) => unknown>action)(_vode.state), isAsync);
167
189
  } else {
168
- _vode.stats.renderPatchCount++;
169
- _vode.q = mergeState(_vode.q || {}, action, false);
170
- if (!_vode.isRendering) _vode.render!();
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
- Object.defineProperty(_vode, "render", {
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: () => requestAnimationFrame(() => {
178
- if (_vode.isRendering || !_vode.q) return;
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.q, true);
183
- _vode.q = null;
184
- const vom = dom(_vode.state);
185
- _vode.vode = render(_vode.state, _vode.patch, container.parentElement as Element, 0, _vode.vode, vom)!;
186
- if ((<ContainerNode<S>>container).tagName.toUpperCase() !== (vom[0] as Tag).toUpperCase()) { //the tag name was changed during render -> update reference to vode-app-root
187
- container = _vode.vode.node as Element;
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.isRendering = false;
192
- _vode.stats.renderCount++;
193
- _vode.stats.lastRenderTime = Date.now() - sw;
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 = unknown>(element: Element | Text, prepareForRender?: boolean): Vode<S> | string | AttachedVode<S> | undefined {
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 extends object | unknown>(state: S): PatchableState<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 extends object | unknown>(p: DeepPartial<S> | Effect<S> | IgnoredPatch): typeof p { return p; }
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 EffectFunction<S>;
679
- eventHandler = (evt: Event) => patch([action, evt]);
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
  }
package/tsconfig.json CHANGED
@@ -24,7 +24,6 @@
24
24
  },
25
25
  "include": [
26
26
  "./index.ts",
27
- "./src/vode.ts",
28
- "./src/vode-tags.ts",
27
+ "./src/*.ts",
29
28
  ]
30
29
  }