canvasengine 1.3.0 → 2.0.0-beta.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.
Files changed (96) hide show
  1. package/dist/index.d.ts +1137 -0
  2. package/dist/index.js +3222 -0
  3. package/dist/index.js.map +1 -0
  4. package/index.d.ts +4 -0
  5. package/package.json +43 -17
  6. package/src/components/Canvas.ts +134 -0
  7. package/src/components/Container.ts +46 -0
  8. package/src/components/DisplayObject.ts +458 -0
  9. package/src/components/Graphic.ts +147 -0
  10. package/src/components/NineSliceSprite.ts +46 -0
  11. package/src/components/ParticleEmitter.ts +39 -0
  12. package/src/components/Scene.ts +6 -0
  13. package/src/components/Sprite.ts +514 -0
  14. package/src/components/Text.ts +145 -0
  15. package/src/components/TilingSprite.ts +39 -0
  16. package/src/components/Video.ts +110 -0
  17. package/src/components/Viewport.ts +159 -0
  18. package/src/components/index.ts +12 -0
  19. package/src/components/types/DisplayObject.ts +70 -0
  20. package/src/components/types/MouseEvent.ts +3 -0
  21. package/src/components/types/Spritesheet.ts +389 -0
  22. package/src/components/types/index.ts +4 -0
  23. package/src/directives/Drag.ts +84 -0
  24. package/src/directives/KeyboardControls.ts +922 -0
  25. package/src/directives/Scheduler.ts +101 -0
  26. package/src/directives/Sound.ts +91 -0
  27. package/src/directives/Transition.ts +45 -0
  28. package/src/directives/ViewportCull.ts +40 -0
  29. package/src/directives/ViewportFollow.ts +26 -0
  30. package/src/directives/index.ts +7 -0
  31. package/src/engine/animation.ts +149 -0
  32. package/src/engine/bootstrap.ts +19 -0
  33. package/src/engine/directive.ts +23 -0
  34. package/src/engine/reactive.ts +480 -0
  35. package/src/engine/signal.ts +138 -0
  36. package/src/engine/trigger.ts +96 -0
  37. package/src/engine/utils.ts +211 -0
  38. package/src/hooks/addContext.ts +6 -0
  39. package/src/hooks/useProps.ts +155 -0
  40. package/src/hooks/useRef.ts +21 -0
  41. package/src/index.ts +15 -0
  42. package/src/utils/Ease.ts +33 -0
  43. package/src/utils/RadialGradient.ts +115 -0
  44. package/testing/index.ts +11 -0
  45. package/.gitattributes +0 -22
  46. package/.npmignore +0 -163
  47. package/canvasengine-1.3.0.all.min.js +0 -21
  48. package/canvasengine.js +0 -5802
  49. package/core/DB.js +0 -24
  50. package/core/ModelServer.js +0 -348
  51. package/core/Users.js +0 -190
  52. package/core/engine-common.js +0 -952
  53. package/doc/cocoonjs.md +0 -36
  54. package/doc/doc-lang.yml +0 -43
  55. package/doc/doc-router.yml +0 -14
  56. package/doc/doc-tuto.yml +0 -9
  57. package/doc/doc.yml +0 -39
  58. package/doc/element.md +0 -37
  59. package/doc/entity.md +0 -90
  60. package/doc/extend.md +0 -47
  61. package/doc/get_started.md +0 -19
  62. package/doc/images/entity.png +0 -0
  63. package/doc/multitouch.md +0 -58
  64. package/doc/nodejs.md +0 -142
  65. package/doc/scene.md +0 -44
  66. package/doc/text.md +0 -156
  67. package/examples/server/client.html +0 -31
  68. package/examples/server/server.js +0 -16
  69. package/examples/tiled_server/client.html +0 -52
  70. package/examples/tiled_server/images/tiles_spritesheet.png +0 -0
  71. package/examples/tiled_server/server/map.json +0 -50
  72. package/examples/tiled_server/server/map.tmx +0 -16
  73. package/examples/tiled_server/server/server.js +0 -16
  74. package/extends/Animation.js +0 -910
  75. package/extends/Effect.js +0 -252
  76. package/extends/Gleed2d.js +0 -252
  77. package/extends/Hit.js +0 -1509
  78. package/extends/Input.js +0 -699
  79. package/extends/Marshal.js +0 -716
  80. package/extends/Scrolling.js +0 -388
  81. package/extends/Soundmanager2.js +0 -5466
  82. package/extends/Spritesheet.js +0 -196
  83. package/extends/Text.js +0 -366
  84. package/extends/Tiled.js +0 -403
  85. package/extends/Window.js +0 -575
  86. package/extends/gamepad.js +0 -397
  87. package/extends/socket.io.min.js +0 -2
  88. package/extends/swf/soundmanager2.swf +0 -0
  89. package/extends/swf/soundmanager2_debug.swf +0 -0
  90. package/extends/swf/soundmanager2_flash9.swf +0 -0
  91. package/extends/swf/soundmanager2_flash9_debug.swf +0 -0
  92. package/extends/swf/soundmanager2_flash_xdomain.zip +0 -0
  93. package/extends/workers/transition.js +0 -43
  94. package/index.js +0 -46
  95. package/license.txt +0 -19
  96. package/readme.md +0 -483
@@ -0,0 +1,480 @@
1
+ import { Signal, WritableArraySignal, WritableObjectSignal, isSignal } from "@signe/reactive";
2
+ import {
3
+ Observable,
4
+ Subject,
5
+ Subscription,
6
+ defer,
7
+ from,
8
+ map,
9
+ of,
10
+ switchMap,
11
+ } from "rxjs";
12
+ import { ComponentInstance } from "../components/DisplayObject";
13
+ import { Directive, applyDirective } from "./directive";
14
+ import { isObject, isPromise, set } from "./utils";
15
+
16
+ export interface Props {
17
+ [key: string]: any;
18
+ }
19
+
20
+ export type ArrayChange<T> = {
21
+ type: "add" | "remove" | "update" | "init" | "reset";
22
+ index?: number;
23
+ items: T[];
24
+ };
25
+
26
+ export type ObjectChange<T> = {
27
+ type: "add" | "remove" | "update" | "init" | "reset";
28
+ key?: string;
29
+ value?: T;
30
+ items: T[];
31
+ };
32
+
33
+ type ElementObservable<T> = Observable<
34
+ (ArrayChange<T> | ObjectChange<T>) & {
35
+ value: Element | Element[];
36
+ }
37
+ >;
38
+
39
+ type NestedSignalObjects = {
40
+ [Key in string]: NestedSignalObjects | Signal<any>;
41
+ };
42
+
43
+ export interface Element<T = ComponentInstance> {
44
+ tag: string;
45
+ props: Props;
46
+ componentInstance: T;
47
+ propSubscriptions: Subscription[];
48
+ effectSubscriptions: Subscription[];
49
+ effectMounts: (() => void)[];
50
+ effectUnmounts: ((element?: Element) => void)[];
51
+ propObservables: NestedSignalObjects | undefined;
52
+ parent: Element | null;
53
+ context?: {
54
+ [key: string]: any;
55
+ };
56
+ directives: {
57
+ [key: string]: Directive;
58
+ };
59
+ destroy: () => void;
60
+ allElements: Subject<void>;
61
+ }
62
+
63
+ type FlowResult = {
64
+ elements: Element[];
65
+ prev?: Element;
66
+ fullElements?: Element[];
67
+ };
68
+
69
+ type FlowObservable = Observable<FlowResult>;
70
+
71
+ const components: { [key: string]: any } = {};
72
+
73
+ export const isElement = (value: any): value is Element => {
74
+ return (
75
+ value &&
76
+ typeof value === "object" &&
77
+ "tag" in value &&
78
+ "props" in value &&
79
+ "componentInstance" in value
80
+ );
81
+ };
82
+
83
+ export const isPrimitive = (value) => {
84
+ return (
85
+ typeof value === "string" ||
86
+ typeof value === "number" ||
87
+ typeof value === "boolean" ||
88
+ value === null ||
89
+ value === undefined
90
+ );
91
+ };
92
+
93
+ export function registerComponent(name, component) {
94
+ components[name] = component;
95
+ }
96
+
97
+ function destroyElement(element: Element | Element[]) {
98
+ if (Array.isArray(element)) {
99
+ element.forEach((e) => destroyElement(e));
100
+ return;
101
+ }
102
+ if (!element) {
103
+ return;
104
+ }
105
+ element.propSubscriptions.forEach((sub) => sub.unsubscribe());
106
+ element.effectSubscriptions.forEach((sub) => sub.unsubscribe());
107
+ for (let name in element.directives) {
108
+ element.directives[name].onDestroy?.();
109
+ }
110
+ element.componentInstance.onDestroy?.(element.parent as any);
111
+ element.effectUnmounts.forEach((fn) => fn?.());
112
+ }
113
+
114
+ /**
115
+ * Creates a virtual element or a representation thereof, with properties that can be dynamically updated based on BehaviorSubjects.
116
+ *
117
+ * @param {string} tag - The tag name of the element to create.
118
+ * @param {Object} props - An object containing properties for the element. Each property can either be a direct value
119
+ * or an array where the first element is a function that returns a value based on input parameters,
120
+ * and the second element is an array of BehaviorSubjects. The property is updated dynamically
121
+ * using the combineLatest RxJS operator to wait for all BehaviorSubjects to emit.
122
+ * @returns {Object} An object representing the created element, including tag name and dynamic properties.
123
+ */
124
+ export function createComponent(tag: string, props?: Props): Element {
125
+ if (!components[tag]) {
126
+ throw new Error(`Component ${tag} is not registered`);
127
+ }
128
+ const instance = new components[tag]();
129
+ const element: Element = {
130
+ tag,
131
+ props: {},
132
+ componentInstance: instance,
133
+ propSubscriptions: [],
134
+ propObservables: props,
135
+ parent: null,
136
+ directives: {},
137
+ effectUnmounts: [],
138
+ effectSubscriptions: [],
139
+ effectMounts: [],
140
+ destroy() {
141
+ destroyElement(this);
142
+ },
143
+ allElements: new Subject(),
144
+ };
145
+
146
+ // Iterate over each property in the props object
147
+ if (props) {
148
+ const recursiveProps = (props, path = "") => {
149
+ const _set = (path, key, value) => {
150
+ if (path == "") {
151
+ element.props[key] = value;
152
+ return;
153
+ }
154
+ set(element.props, path + "." + key, value);
155
+ };
156
+
157
+ Object.entries(props).forEach(([key, value]: [string, unknown]) => {
158
+ if (isSignal(value)) {
159
+ const _value = value as Signal<any>;
160
+ if ("dependencies" in _value && _value.dependencies.size == 0) {
161
+ _set(path, key, _value());
162
+ return;
163
+ }
164
+ element.propSubscriptions.push(
165
+ _value.observable.subscribe((value) => {
166
+ _set(path, key, value);
167
+ if (element.directives[key]) {
168
+ element.directives[key].onUpdate?.(value);
169
+ }
170
+ if (key == "tick") {
171
+ return
172
+ }
173
+ instance.onUpdate?.(
174
+ path == ""
175
+ ? {
176
+ [key]: value,
177
+ }
178
+ : set({}, path + "." + key, value)
179
+ );
180
+ })
181
+ );
182
+ } else {
183
+ if (isObject(value) && key != "context" && !isElement(value)) {
184
+ recursiveProps(value, (path ? path + "." : "") + key);
185
+ } else {
186
+ _set(path, key, value);
187
+ }
188
+ }
189
+ });
190
+ };
191
+ recursiveProps(props);
192
+ }
193
+
194
+ instance.onInit?.(element.props);
195
+
196
+ const elementsListen = new Subject<any>()
197
+
198
+ if (props?.isRoot) {
199
+ element.allElements = elementsListen
200
+ element.props.context.rootElement = element;
201
+ element.componentInstance.onMount?.(element);
202
+ propagateContext(element);
203
+ }
204
+
205
+ if (props) {
206
+ for (let key in props) {
207
+ const directive = applyDirective(element, key);
208
+ if (directive) element.directives[key] = directive;
209
+ }
210
+ }
211
+
212
+ function onMount(parent: Element, element: Element, index?: number) {
213
+ element.props.context = parent.props.context;
214
+ element.parent = parent;
215
+ element.componentInstance.onMount?.(element, index);
216
+ for (let name in element.directives) {
217
+ element.directives[name].onMount?.(element);
218
+ }
219
+ element.effectMounts.forEach((fn: any) => {
220
+ element.effectUnmounts.push(fn(element));
221
+ });
222
+ };
223
+
224
+ async function propagateContext(element) {
225
+ if (element.props.attach) {
226
+ const isReactiveAttach = isSignal(element.propObservables?.attach)
227
+ if (!isReactiveAttach) {
228
+ element.props.children.push(element.props.attach)
229
+ }
230
+ else {
231
+ await new Promise((resolve) => {
232
+ let lastElement = null
233
+ element.propSubscriptions.push(element.propObservables.attach.observable.subscribe(async (args) => {
234
+ const value = args?.value ?? args
235
+ if (!value) {
236
+ throw new Error(`attach in ${element.tag} is undefined or null, add a component`)
237
+ }
238
+ if (lastElement) {
239
+ destroyElement(lastElement)
240
+ }
241
+ lastElement = value
242
+ await createElement(element, value)
243
+ resolve(undefined)
244
+ }))
245
+ })
246
+ }
247
+ }
248
+ if (!element.props.children) {
249
+ return;
250
+ }
251
+ for (let child of element.props.children) {
252
+ if (!child) continue;
253
+ await createElement(element, child)
254
+ }
255
+ };
256
+
257
+ async function createElement(parent: Element, child: Element) {
258
+ if (isPromise(child)) {
259
+ child = await child;
260
+ }
261
+ if (child instanceof Observable) {
262
+ child.subscribe(
263
+ ({
264
+ elements: comp,
265
+ prev,
266
+ }: {
267
+ elements: Element[];
268
+ prev?: Element;
269
+ }) => {
270
+ // if prev, insert element after this
271
+ const components = comp.filter((c) => c !== null);
272
+ if (prev) {
273
+ components.forEach((c) => {
274
+ const index = parent.props.children.indexOf(prev.props.key);
275
+ onMount(parent, c, index + 1);
276
+ propagateContext(c);
277
+ });
278
+ return;
279
+ }
280
+ components.forEach((component) => {
281
+ if (!Array.isArray(component)) {
282
+ onMount(parent, component);
283
+ propagateContext(component);
284
+ } else {
285
+ component.forEach((comp) => {
286
+ onMount(parent, comp);
287
+ propagateContext(comp);
288
+ });
289
+ }
290
+ });
291
+ elementsListen.next(undefined)
292
+ }
293
+ );
294
+ } else {
295
+ onMount(parent, child);
296
+ await propagateContext(child);
297
+ }
298
+ }
299
+
300
+ // Return the created element representation
301
+ return element;
302
+ }
303
+
304
+ /**
305
+ * Observes a BehaviorSubject containing an array or object of items and dynamically creates child elements for each item.
306
+ *
307
+ * @param {WritableArraySignal<T> | WritableObjectSignal<T>} itemsSubject - A signal that emits an array or object of items.
308
+ * @param {Function} createElementFn - A function that takes an item and returns an element representation.
309
+ * @returns {Observable} An observable that emits the list of created child elements.
310
+ */
311
+ export function loop<T>(
312
+ itemsSubject: WritableArraySignal<T[]> | WritableObjectSignal<T>,
313
+ createElementFn: (item: T, index: number | string) => Element | null
314
+ ): FlowObservable {
315
+ let elements: Element[] = [];
316
+ let elementMap = new Map<string | number, Element>();
317
+
318
+ return new Observable<FlowResult>(subscriber => {
319
+ const isArraySignal = (signal: any): signal is WritableArraySignal<T[]> =>
320
+ Array.isArray(signal());
321
+
322
+ const subscription = isArraySignal(itemsSubject)
323
+ ? itemsSubject.observable.subscribe(change => {
324
+ if (change.type === 'init' || change.type === 'reset') {
325
+ elements.forEach(el => el.destroy());
326
+ elements = [];
327
+ elementMap.clear();
328
+
329
+ const items = itemsSubject();
330
+ if (items) {
331
+ items.forEach((item, index) => {
332
+ const element = createElementFn(item, index);
333
+ if (element) {
334
+ elements.push(element);
335
+ elementMap.set(index, element);
336
+ }
337
+ });
338
+ }
339
+ } else if (change.type === 'add' && change.index !== undefined) {
340
+ const newElements = change.items.map((item, i) => {
341
+ const element = createElementFn(item as T, change.index! + i);
342
+ if (element) {
343
+ elementMap.set(change.index! + i, element);
344
+ }
345
+ return element;
346
+ }).filter((el): el is Element => el !== null);
347
+
348
+ elements.splice(change.index, 0, ...newElements);
349
+ } else if (change.type === 'remove' && change.index !== undefined) {
350
+ const removed = elements.splice(change.index, 1);
351
+ removed.forEach(el => {
352
+ el.destroy();
353
+ elementMap.delete(change.index!);
354
+ });
355
+ }
356
+
357
+ subscriber.next({
358
+ elements: elements
359
+ });
360
+ })
361
+ : (itemsSubject as WritableObjectSignal<T>).observable.subscribe(change => {
362
+ const key = change.key as string | number
363
+ if (change.type === 'init' || change.type === 'reset') {
364
+ elements.forEach(el => el.destroy());
365
+ elements = [];
366
+ elementMap.clear();
367
+
368
+ const items = (itemsSubject as WritableObjectSignal<T>)();
369
+ if (items) {
370
+ Object.entries(items).forEach(([key, value]) => {
371
+ const element = createElementFn(value, key);
372
+ if (element) {
373
+ elements.push(element);
374
+ elementMap.set(key, element);
375
+ }
376
+ });
377
+ }
378
+ } else if (change.type === 'add' && change.key && change.value !== undefined) {
379
+ const element = createElementFn(change.value as T, key);
380
+ if (element) {
381
+ elements.push(element);
382
+ elementMap.set(key, element);
383
+ }
384
+ } else if (change.type === 'remove' && change.key) {
385
+ const index = elements.findIndex(el => elementMap.get(key) === el);
386
+ if (index !== -1) {
387
+ const [removed] = elements.splice(index, 1);
388
+ removed.destroy();
389
+ elementMap.delete(key);
390
+ }
391
+ } else if (change.type === 'update' && change.key && change.value !== undefined) {
392
+ const index = elements.findIndex(el => elementMap.get(key) === el);
393
+ if (index !== -1) {
394
+ const oldElement = elements[index];
395
+ oldElement.destroy();
396
+ const newElement = createElementFn(change.value as T, key);
397
+ if (newElement) {
398
+ elements[index] = newElement;
399
+ elementMap.set(key, newElement);
400
+ }
401
+ }
402
+ }
403
+
404
+ subscriber.next({
405
+ elements: elements
406
+ });
407
+ });
408
+
409
+ return subscription;
410
+ });
411
+ }
412
+
413
+ /**
414
+ * Conditionally creates and destroys elements based on a condition signal.
415
+ *
416
+ * @param {Signal<boolean> | boolean} condition - A signal or boolean that determines whether to create an element.
417
+ * @param {Function} createElementFn - A function that returns an element or a promise that resolves to an element.
418
+ * @returns {Observable} An observable that emits the created or destroyed element.
419
+ */
420
+ export function cond(
421
+ condition: Signal<boolean> | boolean,
422
+ createElementFn: () => Element | Promise<Element>
423
+ ): FlowObservable {
424
+ let element: Element | null = null;
425
+
426
+ if (isSignal(condition)) {
427
+ const signalCondition = condition as WritableObjectSignal<boolean>;
428
+ return new Observable<{elements: Element[], type?: "init" | "remove"}>(subscriber => {
429
+ return signalCondition.observable.subscribe(bool => {
430
+ if (bool) {
431
+ let _el = createElementFn();
432
+ if (isPromise(_el)) {
433
+ from(_el as Promise<Element>).subscribe(el => {
434
+ element = el;
435
+ subscriber.next({
436
+ type: "init",
437
+ elements: [el],
438
+ });
439
+ });
440
+ } else {
441
+ element = _el as Element;
442
+ subscriber.next({
443
+ type: "init",
444
+ elements: [element],
445
+ });
446
+ }
447
+ } else if (element) {
448
+ destroyElement(element);
449
+ subscriber.next({
450
+ elements: [],
451
+ });
452
+ } else {
453
+ subscriber.next({
454
+ elements: [],
455
+ });
456
+ }
457
+ });
458
+ });
459
+ } else {
460
+ // Handle boolean case
461
+ if (condition) {
462
+ let _el = createElementFn();
463
+ if (isPromise(_el)) {
464
+ return from(_el as Promise<Element>).pipe(
465
+ map((el) => ({
466
+ type: "init",
467
+ elements: [el],
468
+ }))
469
+ );
470
+ }
471
+ return of({
472
+ type: "init",
473
+ elements: [_el as Element],
474
+ });
475
+ }
476
+ return of({
477
+ elements: [],
478
+ });
479
+ }
480
+ }
@@ -0,0 +1,138 @@
1
+ import {
2
+ Subscription
3
+ } from "rxjs";
4
+ import type { Element } from "./reactive";
5
+ import { Tick } from "../directives/Scheduler";
6
+
7
+ type MountFunction = (fn: (element: Element) => void) => void;
8
+
9
+ // Define ComponentFunction type
10
+ export type ComponentFunction<P = {}> = (props: P) => Element | Promise<Element>;
11
+
12
+ export let currentSubscriptionsTracker: ((subscription: Subscription) => void) | null = null;
13
+ export let mountTracker: MountFunction | null = null;
14
+
15
+ /**
16
+ * Registers a mount function to be called when the component is mounted.
17
+ * To unmount the component, the function must return a function that will be called by the engine.
18
+ *
19
+ * @param {(element: Element) => void} fn - The function to be called on mount.
20
+ * @example
21
+ * ```ts
22
+ * mount((el) => {
23
+ * console.log('mounted', el);
24
+ * });
25
+ * ```
26
+ * Unmount the component by returning a function:
27
+ * ```ts
28
+ * mount((el) => {
29
+ * console.log('mounted', el);
30
+ * return () => {
31
+ * console.log('unmounted', el);
32
+ * }
33
+ * });
34
+ * ```
35
+ */
36
+ export function mount(fn: (element: Element) => void) {
37
+ mountTracker?.(fn);
38
+ }
39
+
40
+ /**
41
+ * Registers a tick function to be called on each tick of the component's context.
42
+ * @param {(tickValue: Tick, element: Element) => void} fn - The function to be called on each tick.
43
+ * @example
44
+ * ```ts
45
+ * tick((tickValue, el) => {
46
+ * console.log('tick', tickValue, el);
47
+ * });
48
+ * ```
49
+ */
50
+ export function tick(fn: (tickValue: Tick, element: Element) => void) {
51
+ mount((el: Element) => {
52
+ const { context } = el.props
53
+ let subscription: Subscription | undefined
54
+ if (context.tick) {
55
+ subscription = context.tick.observable.subscribe(({ value }) => {
56
+ fn(value, el)
57
+ })
58
+ }
59
+ return () => {
60
+ subscription?.unsubscribe()
61
+ }
62
+ })
63
+ }
64
+
65
+ /**
66
+ * Add tracking for subscriptions and mounts, then create an element from a component function.
67
+ * @template C
68
+ * @param {C} componentFunction - The component function to create an element from.
69
+ * @param {Parameters<C>[0]} [props={}] - The props to pass to the component function.
70
+ * @param {...any[]} children - The children elements of the component.
71
+ * @returns {ReturnType<C>}
72
+ * @example
73
+ * ```ts
74
+ * const el = h(MyComponent, {
75
+ * x: 100,
76
+ * y: 100,
77
+ * });
78
+ * ```
79
+ *
80
+ * with children:
81
+ * ```ts
82
+ * const el = h(MyComponent, {
83
+ * x: 100,
84
+ * y: 100,
85
+ * },
86
+ * h(MyChildComponent, {
87
+ * x: 50,
88
+ * y: 50,
89
+ * }),
90
+ * );
91
+ * ```
92
+ */
93
+ export function h<C extends ComponentFunction<any>>(
94
+ componentFunction: C,
95
+ props: Parameters<C>[0] = {} as Parameters<C>[0],
96
+ ...children: any[]
97
+ ): ReturnType<C> {
98
+ const allSubscriptions = new Set<Subscription>();
99
+ const allMounts = new Set<MountFunction>();
100
+
101
+ currentSubscriptionsTracker = (subscription) => {
102
+ allSubscriptions.add(subscription);
103
+ };
104
+
105
+ mountTracker = (fn: any) => {
106
+ allMounts.add(fn);
107
+ };
108
+
109
+ if (children[0] instanceof Array) {
110
+ children = children[0]
111
+ }
112
+
113
+ let component = componentFunction({ ...props, children }) as Element;
114
+
115
+ if (!component) {
116
+ component = {} as any
117
+ }
118
+
119
+ component.effectSubscriptions = Array.from(allSubscriptions);
120
+ component.effectMounts = [
121
+ ...Array.from(allMounts),
122
+ ...((component as any).effectMounts ?? [])
123
+ ];
124
+
125
+ // call mount hook for root component
126
+ if (component instanceof Promise) {
127
+ component.then((component) => {
128
+ if (component.props.isRoot) {
129
+ allMounts.forEach((fn) => fn(component));
130
+ }
131
+ })
132
+ }
133
+
134
+ currentSubscriptionsTracker = null;
135
+ mountTracker = null;
136
+
137
+ return component as ReturnType<C>;
138
+ }
@@ -0,0 +1,96 @@
1
+ import { effect, signal } from "@signe/reactive";
2
+
3
+ interface Listen<T = any> {
4
+ config: T | undefined;
5
+ seed: {
6
+ config: T | undefined;
7
+ value: number;
8
+ resolve: (value: any) => void;
9
+ };
10
+ }
11
+
12
+ interface Trigger<T = any> {
13
+ start: () => Promise<void>;
14
+ listen: () => Listen<T> | undefined;
15
+ }
16
+
17
+ /**
18
+ * Checks if the given argument is a Trigger object
19
+ * @param arg - The value to check
20
+ * @returns True if the argument is a Trigger object
21
+ */
22
+ export function isTrigger(arg: any): arg is Trigger<any> {
23
+ return arg?.start && arg?.listen;
24
+ }
25
+
26
+ /**
27
+ * Creates a new trigger that can be used to pass data between components
28
+ * @param globalConfig - Optional configuration data to be passed when the trigger is activated
29
+ * @returns A Trigger object with start and listen methods
30
+ * @example
31
+ * ```ts
32
+ * const myTrigger = trigger()
33
+ *
34
+ * on(myTrigger, (data) => {
35
+ * console.log('Triggered with data:', data)
36
+ * })
37
+ *
38
+ * myTrigger.start({ message: 'Hello' })
39
+ * ```
40
+ */
41
+ export function trigger<T = any>(globalConfig?: T): Trigger<T> {
42
+ const _signal = signal({
43
+ config: globalConfig,
44
+ value: 0,
45
+ resolve: (value: any) => void 0,
46
+ });
47
+ return {
48
+ start: (config?: T) => {
49
+ return new Promise((resolve: (value: any) => void) => {
50
+ _signal.set({
51
+ config: {
52
+ ...globalConfig,
53
+ ...config,
54
+ },
55
+ resolve,
56
+ value: Math.random(),
57
+ });
58
+ });
59
+ },
60
+ listen: (): Listen<T> | undefined => {
61
+ return {
62
+ config: globalConfig,
63
+ seed: _signal(),
64
+ };
65
+ },
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Subscribes to a trigger and executes a callback when the trigger is activated
71
+ * @param triggerSignal - The trigger to subscribe to
72
+ * @param callback - Function to execute when the trigger is activated
73
+ * @throws Error if triggerSignal is not a valid trigger
74
+ * @example
75
+ * ```ts
76
+ * const click = trigger()
77
+ *
78
+ * on(click, () => {
79
+ * console.log('Click triggered')
80
+ * })
81
+ * ```
82
+ */
83
+ export function on(triggerSignal: any, callback: (config: any) => void | Promise<void>) {
84
+ if (!isTrigger(triggerSignal)) {
85
+ throw new Error("In 'on(arg)' must have a trigger signal type");
86
+ }
87
+ effect(() => {
88
+ const result = triggerSignal.listen();
89
+ if (result?.seed.value) {
90
+ const ret = callback(result?.seed.config);
91
+ if (ret && typeof ret.then === 'function') {
92
+ ret.then(result?.seed.resolve);
93
+ }
94
+ }
95
+ });
96
+ }