canvasengine 2.0.0-beta.60 → 2.0.0-beta.61

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.
@@ -1,22 +1,38 @@
1
1
  import {
2
2
  Observable,
3
+ Subject,
3
4
  Subscription
4
5
  } from "rxjs";
5
6
  import { isSignal } from "@signe/reactive";
6
7
  import type { Element } from "./reactive";
7
- import { isElementFrozen, waitForDependencies } from "./reactive";
8
+ import { destroyElement, isElementFrozen, waitForDependencies } from "./reactive";
8
9
  import { isPromise } from "./utils";
9
10
  import { Tick } from "../directives/Scheduler";
10
11
  import { Container } from "../components";
11
12
 
12
- type MountFunction = (fn: (element: Element) => void) => void;
13
+ type MountCallback = (element: Element) => any;
14
+ type MountFunction = (fn: MountCallback) => void;
13
15
 
14
16
  // Define ComponentFunction type
15
17
  export type ComponentFunction<P = {}> = (props: P) => Element | Promise<Element>;
18
+ type HotFlowResult = { elements: Element[] };
19
+ type HotComponentRecord = {
20
+ component: ComponentFunction<any>;
21
+ updates: Subject<void>;
22
+ wrapper?: ComponentFunction<any>;
23
+ };
16
24
 
17
25
  export let currentSubscriptionsTracker: ((subscription: Subscription) => void) | null = null;
18
26
  export let mountTracker: MountFunction | null = null;
19
27
 
28
+ const getHotComponentRegistry = (): Map<string, HotComponentRecord> => {
29
+ const hotGlobal = globalThis as any;
30
+ if (!hotGlobal.__CANVAS_ENGINE_HOT_COMPONENTS__) {
31
+ hotGlobal.__CANVAS_ENGINE_HOT_COMPONENTS__ = new Map<string, HotComponentRecord>();
32
+ }
33
+ return hotGlobal.__CANVAS_ENGINE_HOT_COMPONENTS__;
34
+ };
35
+
20
36
  /**
21
37
  * Registers a mount function to be called when the component is mounted.
22
38
  * To unmount the component, the function must return a function that will be called by the engine.
@@ -57,7 +73,7 @@ export function tick(fn: (tickValue: Tick, element: Element) => void) {
57
73
  const { context } = el.props
58
74
  let subscription: Subscription | undefined
59
75
  if (context.tick) {
60
- subscription = context.tick.observable.subscribe(({ value }) => {
76
+ subscription = context.tick.observable.subscribe(({ value }: { value: Tick }) => {
61
77
  // Block tick if element is frozen
62
78
  if (isElementFrozen(el)) {
63
79
  return;
@@ -104,17 +120,6 @@ function _h<C extends ComponentFunction<any>>(
104
120
  props: Parameters<C>[0] = {} as Parameters<C>[0],
105
121
  children: any[]
106
122
  ): ReturnType<C> {
107
- const allSubscriptions = new Set<Subscription>();
108
- const allMounts = new Set<MountFunction>();
109
-
110
- currentSubscriptionsTracker = (subscription) => {
111
- allSubscriptions.add(subscription);
112
- };
113
-
114
- mountTracker = (fn: any) => {
115
- allMounts.add(fn);
116
- };
117
-
118
123
  if (children[0] instanceof Array) {
119
124
  children = children[0]
120
125
  }
@@ -136,38 +141,139 @@ function _h<C extends ComponentFunction<any>>(
136
141
  component = componentFunction as any
137
142
  }
138
143
  else {
139
- component = componentFunction({ ...props, children }) as Element;
144
+ component = createTrackedComponent(componentFunction, { ...props, children }) as Element;
140
145
  }
141
146
 
142
147
  if (!component) {
143
148
  component = {} as any
144
149
  }
145
150
 
146
- component.effectSubscriptions = Array.from(allSubscriptions);
147
- component.effectMounts = [
148
- ...Array.from(allMounts),
149
- ...((component as any).effectMounts ?? [])
150
- ];
151
-
152
151
  // Copy dependencies prop to the returned element so it can be used for delayed mounting
153
152
  if (props?.dependencies) {
154
153
  component.props = component.props || {};
155
154
  component.props.dependencies = props.dependencies;
156
155
  }
157
156
 
158
- // call mount hook for root component
157
+ return component as ReturnType<C>;
158
+ }
159
+
160
+ function createTrackedComponent<C extends ComponentFunction<any>>(
161
+ componentFunction: C,
162
+ props: Parameters<C>[0]
163
+ ): ReturnType<C> {
164
+ const allSubscriptions = new Set<Subscription>();
165
+ const allMounts = new Set<MountCallback>();
166
+
167
+ currentSubscriptionsTracker = (subscription) => {
168
+ allSubscriptions.add(subscription);
169
+ };
170
+
171
+ mountTracker = (fn: any) => {
172
+ allMounts.add(fn);
173
+ };
174
+
175
+ let component: ReturnType<C> = undefined as any;
176
+ try {
177
+ component = componentFunction(props) as ReturnType<C>;
178
+ } finally {
179
+ currentSubscriptionsTracker = null;
180
+ mountTracker = null;
181
+ }
182
+
183
+ const applyTrackedEffects = (element: Element) => {
184
+ if (!element) return;
185
+ element.effectSubscriptions = [
186
+ ...Array.from(allSubscriptions),
187
+ ...((element as any).effectSubscriptions ?? [])
188
+ ];
189
+ element.effectMounts = [
190
+ ...Array.from(allMounts),
191
+ ...((element as any).effectMounts ?? [])
192
+ ];
193
+ };
194
+
159
195
  if (component instanceof Promise) {
160
- component.then((component) => {
161
- if (component.props.isRoot) {
162
- allMounts.forEach((fn) => fn(component));
196
+ component.then((element) => {
197
+ applyTrackedEffects(element);
198
+ if (element?.props?.isRoot) {
199
+ allMounts.forEach((fn) => fn(element));
163
200
  }
164
- })
201
+ });
202
+ } else if (component instanceof Observable) {
203
+ (component as any).effectSubscriptions = [
204
+ ...Array.from(allSubscriptions),
205
+ ...((component as any).effectSubscriptions ?? [])
206
+ ];
207
+ (component as any).effectMounts = [
208
+ ...Array.from(allMounts),
209
+ ...((component as any).effectMounts ?? [])
210
+ ];
211
+ } else {
212
+ applyTrackedEffects(component as Element);
165
213
  }
166
214
 
167
- currentSubscriptionsTracker = null;
168
- mountTracker = null;
215
+ return component;
216
+ }
169
217
 
170
- return component as ReturnType<C>;
218
+ export function createHotComponent<P>(
219
+ id: string,
220
+ component: ComponentFunction<P>
221
+ ): ComponentFunction<P> {
222
+ const registry = getHotComponentRegistry();
223
+ let record = registry.get(id);
224
+
225
+ if (!record) {
226
+ record = {
227
+ component,
228
+ updates: new Subject<void>(),
229
+ };
230
+ registry.set(id, record);
231
+ } else {
232
+ record.component = component;
233
+ record.updates.next();
234
+ }
235
+
236
+ if (!record.wrapper) {
237
+ record.wrapper = ((props: P) => {
238
+ return new Observable<HotFlowResult>((subscriber) => {
239
+ let disposed = false;
240
+ let currentElement: Element | null = null;
241
+
242
+ const emit = () => {
243
+ const rendered = createTrackedComponent(record!.component, props);
244
+ const next = (element: Element | null | undefined) => {
245
+ if (!disposed) {
246
+ subscriber.next({ elements: element ? [element] : [] });
247
+ if (currentElement && currentElement !== element) {
248
+ destroyElement(currentElement);
249
+ }
250
+ currentElement = element ?? null;
251
+ }
252
+ };
253
+
254
+ if (rendered instanceof Promise) {
255
+ rendered.then(next).catch((error) => subscriber.error(error));
256
+ } else {
257
+ next(rendered as Element);
258
+ }
259
+ };
260
+
261
+ emit();
262
+ const subscription = record!.updates.subscribe(emit);
263
+
264
+ return () => {
265
+ disposed = true;
266
+ if (currentElement) {
267
+ destroyElement(currentElement);
268
+ currentElement = null;
269
+ }
270
+ subscription.unsubscribe();
271
+ };
272
+ }) as any;
273
+ }) as ComponentFunction<any>;
274
+ }
275
+
276
+ return record.wrapper as ComponentFunction<P>;
171
277
  }
172
278
 
173
279
  /**
@@ -206,7 +312,7 @@ export function h<C extends ComponentFunction<any>>(
206
312
  if (props?.dependencies) {
207
313
  const hasPromise = props.dependencies.some(isPromise);
208
314
  if (!hasPromise) {
209
- const allReady = props.dependencies.every(dep => {
315
+ const allReady = props.dependencies.every((dep: any) => {
210
316
  if (isSignal(dep)) return dep() !== undefined;
211
317
  return dep !== undefined;
212
318
  });