canvasengine 2.0.0-beta.5 → 2.0.0-beta.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canvasengine",
3
- "version": "2.0.0-beta.5",
3
+ "version": "2.0.0-beta.7",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -5,7 +5,7 @@ import { Sprite } from "./Sprite";
5
5
  import { effect, Signal, signal } from "@signe/reactive";
6
6
 
7
7
  interface VideoProps {
8
- source: string;
8
+ src: string;
9
9
  paused?: boolean;
10
10
  loop?: boolean;
11
11
  muted?: boolean;
@@ -87,7 +87,7 @@ export function Video(props: VideoProps) {
87
87
 
88
88
  return h(Sprite, {
89
89
  ...props,
90
- image: props.source,
90
+ image: props.src,
91
91
  loader: {
92
92
  onComplete: (texture) => {
93
93
  const source = texture.source.resource
@@ -8,6 +8,7 @@ export type Size = number | `${number}%`
8
8
  export type EdgeSize = SignalOrPrimitive<Size | [Size, Size] | [Size, Size, Size, Size]>
9
9
 
10
10
  export interface DisplayObjectProps {
11
+ attach?: any;
11
12
  ref?: string;
12
13
  x?: SignalOrPrimitive<number>;
13
14
  y?: SignalOrPrimitive<number>;
@@ -1,4 +1,4 @@
1
- import { Signal, WritableArraySignal, isSignal } from "@signe/reactive";
1
+ import { Signal, WritableArraySignal, WritableObjectSignal, isSignal } from "@signe/reactive";
2
2
  import {
3
3
  Observable,
4
4
  Subject,
@@ -23,8 +23,15 @@ export type ArrayChange<T> = {
23
23
  items: T[];
24
24
  };
25
25
 
26
+ export type ObjectChange<T> = {
27
+ type: "add" | "remove" | "update" | "init" | "reset";
28
+ key?: string;
29
+ value?: T;
30
+ items: T[];
31
+ };
32
+
26
33
  type ElementObservable<T> = Observable<
27
- ArrayChange<T> & {
34
+ (ArrayChange<T> | ObjectChange<T>) & {
28
35
  value: Element | Element[];
29
36
  }
30
37
  >;
@@ -53,10 +60,13 @@ export interface Element<T = ComponentInstance> {
53
60
  allElements: Subject<void>;
54
61
  }
55
62
 
56
- type FlowObservable = Observable<{
63
+ type FlowResult = {
57
64
  elements: Element[];
58
65
  prev?: Element;
59
- }>;
66
+ fullElements?: Element[];
67
+ };
68
+
69
+ type FlowObservable = Observable<FlowResult>;
60
70
 
61
71
  const components: { [key: string]: any } = {};
62
72
 
@@ -292,32 +302,59 @@ export function createComponent(tag: string, props?: Props): Element {
292
302
  }
293
303
 
294
304
  /**
295
- * Observes a BehaviorSubject containing an array of items and dynamically creates child elements for each item.
305
+ * Observes a BehaviorSubject containing an array or object of items and dynamically creates child elements for each item.
296
306
  *
297
- * @param {BehaviorSubject<Array>} itemsSubject - A BehaviorSubject that emits an array of items.
307
+ * @param {WritableArraySignal<T> | WritableObjectSignal<T>} itemsSubject - A signal that emits an array or object of items.
298
308
  * @param {Function} createElementFn - A function that takes an item and returns an element representation.
299
309
  * @returns {Observable} An observable that emits the list of created child elements.
300
310
  */
301
- export function loop<T = any>(
302
- itemsSubject: WritableArraySignal<T>,
303
- createElementFn: (item: any, index: number) => Element | Promise<Element>
311
+ export function loop<T>(
312
+ itemsSubject: WritableArraySignal<T[]> | WritableObjectSignal<T>,
313
+ createElementFn: (item: T, index: number | string) => Element | null
304
314
  ): FlowObservable {
305
315
  let elements: Element[] = [];
306
316
 
307
- const addAt = (items, insertIndex: number) => {
317
+ const isArraySignal = '_subject' in itemsSubject && 'items' in (itemsSubject as any)._subject;
318
+
319
+ const addAt = (items: T[], insertIndex: number | string, keys?: string[]): Element[] => {
308
320
  return items.map((item, index) => {
309
- const element = createElementFn(item, insertIndex + index);
310
- elements.splice(insertIndex + index, 0, element as Element);
321
+ const key = keys ? keys[index] : (typeof insertIndex === 'number' ? insertIndex + index : insertIndex);
322
+ const element = createElementFn(item, key);
323
+ if (typeof insertIndex === 'number') {
324
+ elements.splice(insertIndex + index, 0, element);
325
+ } else {
326
+ elements.push(element);
327
+ }
311
328
  return element;
312
329
  });
313
330
  };
314
331
 
332
+ const getInitialItems = () => {
333
+ if (isArraySignal) {
334
+ return {
335
+ items: (itemsSubject as any)._subject.items as T[],
336
+ keys: undefined as string[] | undefined
337
+ };
338
+ } else {
339
+ const entries = Object.entries((itemsSubject as any)._subject.value.value) as [string, T][];
340
+ return {
341
+ items: entries.map(([_, value]) => value),
342
+ keys: entries.map(([key]) => key)
343
+ };
344
+ }
345
+ };
346
+
347
+ const { items, keys } = getInitialItems();
348
+
315
349
  return defer(() => {
316
- let initialItems = [...itemsSubject._subject.items];
350
+ let initialItems = [...items];
351
+ let initialKeys = keys ? [...keys] : undefined;
317
352
  let init = true;
318
- return itemsSubject.observable.pipe(
319
- map((event: ArrayChange<T>) => {
320
- const { type, items, index } = event;
353
+ return (itemsSubject.observable as Observable<ArrayChange<T> | ObjectChange<T>>).pipe(
354
+ map((event: ArrayChange<T> | ObjectChange<T>): FlowResult => {
355
+ const { type, items } = event;
356
+ const index = 'index' in event ? event.index : (event as ObjectChange<T>).key;
357
+
321
358
  if (init) {
322
359
  if (elements.length > 0) {
323
360
  return {
@@ -325,8 +362,9 @@ export function loop<T = any>(
325
362
  fullElements: elements,
326
363
  };
327
364
  }
328
- const newElements = addAt(initialItems, 0);
365
+ const newElements = addAt(initialItems, 0, initialKeys);
329
366
  initialItems = [];
367
+ initialKeys = undefined;
330
368
  init = false;
331
369
  return {
332
370
  elements: newElements,
@@ -339,25 +377,64 @@ export function loop<T = any>(
339
377
  });
340
378
  elements = [];
341
379
  }
342
- const newElements = addAt(items, 0);
380
+ if (!isArraySignal) {
381
+ const entries = Object.entries((itemsSubject as any)._subject.value.value) as [string, T][];
382
+ const newElements = addAt(
383
+ entries.map(([_, value]) => value),
384
+ 0,
385
+ entries.map(([key]) => key)
386
+ );
387
+ return {
388
+ elements: newElements,
389
+ fullElements: elements,
390
+ };
391
+ }
392
+ const newElements = addAt(items as T[], 0);
343
393
  return {
344
394
  elements: newElements,
345
395
  fullElements: elements,
346
396
  };
347
397
  } else if (type == "add" && index != undefined) {
348
- const lastElement = elements[index - 1];
349
- const newElements = addAt(items, index);
398
+ const lastElement = typeof index === 'number' ? elements[index - 1] : elements[elements.length - 1];
399
+ let newElements: Element[];
400
+ if (!isArraySignal && typeof index === 'string') {
401
+ // For object updates, create a single element with the new value
402
+ const value = (event as ObjectChange<T>).value;
403
+ if (value !== undefined) {
404
+ newElements = [createElementFn(value, index)];
405
+ elements.push(newElements[0]);
406
+ } else {
407
+ newElements = [];
408
+ }
409
+ } else {
410
+ // For array updates, use addAt with the items array
411
+ newElements = addAt(items as T[], index);
412
+ }
350
413
  return {
351
414
  prev: lastElement,
352
415
  elements: newElements,
353
416
  fullElements: elements,
354
417
  };
355
- } else if (index != undefined && type == "remove") {
356
- const currentElement = elements[index];
357
- destroyElement(currentElement);
358
- elements.splice(index, 1);
418
+ } else if (type == "remove") {
419
+ if (!isArraySignal && typeof index === 'string') {
420
+ // For object property deletion
421
+ const elementIndex = elements.findIndex(el => {
422
+ return el.props.text === index || el.props.key === index;
423
+ });
424
+ if (elementIndex !== -1) {
425
+ const currentElement = elements[elementIndex];
426
+ destroyElement(currentElement);
427
+ elements.splice(elementIndex, 1);
428
+ }
429
+ } else if (typeof index === 'number') {
430
+ // For array element deletion
431
+ const currentElement = elements[index];
432
+ destroyElement(currentElement);
433
+ elements.splice(index, 1);
434
+ }
359
435
  return {
360
436
  elements: [],
437
+ fullElements: elements,
361
438
  };
362
439
  }
363
440
  return {
@@ -369,37 +446,71 @@ export function loop<T = any>(
369
446
  });
370
447
  }
371
448
 
449
+ /**
450
+ * Conditionally creates and destroys elements based on a condition signal.
451
+ *
452
+ * @param {Signal<boolean> | boolean} condition - A signal or boolean that determines whether to create an element.
453
+ * @param {Function} createElementFn - A function that returns an element or a promise that resolves to an element.
454
+ * @returns {Observable} An observable that emits the created or destroyed element.
455
+ */
372
456
  export function cond(
373
- condition: Signal,
457
+ condition: Signal<boolean> | boolean,
374
458
  createElementFn: () => Element | Promise<Element>
375
459
  ): FlowObservable {
376
460
  let element: Element | null = null;
377
- return (condition.observable as Observable<boolean>).pipe(
378
- switchMap((bool) => {
379
- if (bool) {
380
- let _el = createElementFn();
381
- if (isPromise(_el)) {
382
- return from(_el as Promise<Element>).pipe(
383
- map((el) => {
384
- element = _el as Element;
385
- return {
461
+
462
+ if (isSignal(condition)) {
463
+ const signalCondition = condition as WritableObjectSignal<boolean>;
464
+ return new Observable<{elements: Element[], type?: "init" | "remove"}>(subscriber => {
465
+ return signalCondition.observable.subscribe(bool => {
466
+ if (bool) {
467
+ let _el = createElementFn();
468
+ if (isPromise(_el)) {
469
+ from(_el as Promise<Element>).subscribe(el => {
470
+ element = el;
471
+ subscriber.next({
386
472
  type: "init",
387
473
  elements: [el],
388
- };
389
- })
390
- );
474
+ });
475
+ });
476
+ } else {
477
+ element = _el as Element;
478
+ subscriber.next({
479
+ type: "init",
480
+ elements: [element],
481
+ });
482
+ }
483
+ } else if (element) {
484
+ destroyElement(element);
485
+ subscriber.next({
486
+ elements: [],
487
+ });
488
+ } else {
489
+ subscriber.next({
490
+ elements: [],
491
+ });
391
492
  }
392
- element = _el as Element;
393
- return of({
394
- type: "init",
395
- elements: [element],
396
- });
397
- } else if (element) {
398
- destroyElement(element);
493
+ });
494
+ });
495
+ } else {
496
+ // Handle boolean case
497
+ if (condition) {
498
+ let _el = createElementFn();
499
+ if (isPromise(_el)) {
500
+ return from(_el as Promise<Element>).pipe(
501
+ map((el) => ({
502
+ type: "init",
503
+ elements: [el],
504
+ }))
505
+ );
399
506
  }
400
507
  return of({
401
- elements: [],
508
+ type: "init",
509
+ elements: [_el as Element],
402
510
  });
403
- })
404
- );
511
+ }
512
+ return of({
513
+ elements: [],
514
+ });
515
+ }
405
516
  }