canvasengine 2.0.0-beta.6 → 2.0.0-beta.8
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/dist/index.d.ts +37 -9
- package/dist/index.js +106 -30
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/DisplayObject.ts +3 -3
- package/src/components/Text.ts +2 -2
- package/src/engine/animation.ts +41 -5
- package/src/engine/reactive.ts +99 -23
package/package.json
CHANGED
|
@@ -170,7 +170,7 @@ export function DisplayObject(extendClass) {
|
|
|
170
170
|
effect(() => {
|
|
171
171
|
setter(parentSize() * (parseInt(size) / 100));
|
|
172
172
|
if (this.isFlex) {
|
|
173
|
-
this
|
|
173
|
+
this.applyFlexLayout();
|
|
174
174
|
}
|
|
175
175
|
});
|
|
176
176
|
} else {
|
|
@@ -188,7 +188,7 @@ export function DisplayObject(extendClass) {
|
|
|
188
188
|
);
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
|
|
191
|
+
applyFlexLayout() {
|
|
192
192
|
this.calculateLayout();
|
|
193
193
|
for (let child of this.children) {
|
|
194
194
|
const { left, top } = child.node.getComputedLayout();
|
|
@@ -201,7 +201,7 @@ export function DisplayObject(extendClass) {
|
|
|
201
201
|
if (!this.parent) return;
|
|
202
202
|
if (props.flexDirection || props.justifyContent) {
|
|
203
203
|
this.isFlex = true;
|
|
204
|
-
this
|
|
204
|
+
this.applyFlexLayout();
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
|
package/src/components/Text.ts
CHANGED
|
@@ -65,8 +65,8 @@ class CanvasText extends DisplayObject(PixiText) {
|
|
|
65
65
|
this.typewriterOptions = props.typewriter;
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
-
if (props.text) {
|
|
69
|
-
this.text = props.text;
|
|
68
|
+
if (props.text !== undefined) {
|
|
69
|
+
this.text = ''+props.text;
|
|
70
70
|
}
|
|
71
71
|
if (props.text !== undefined && props.text !== this.fullText && this.fullProps.typewriter) {
|
|
72
72
|
this.text = "";
|
package/src/engine/animation.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { effect, signal, type WritableSignal } from "@signe/reactive";
|
|
2
2
|
import { animate as animatePopmotion } from "popmotion";
|
|
3
3
|
|
|
4
|
-
interface AnimateOptions<T> {
|
|
4
|
+
export interface AnimateOptions<T> {
|
|
5
5
|
duration?: number;
|
|
6
6
|
ease?: (t: number) => number;
|
|
7
7
|
onUpdate?: (value: T) => void;
|
|
8
|
+
onComplete?: () => void;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export interface AnimatedState<T> {
|
|
@@ -15,7 +16,7 @@ export interface AnimatedState<T> {
|
|
|
15
16
|
|
|
16
17
|
export interface AnimatedSignal<T> extends Omit<WritableSignal<T>, 'set'> {
|
|
17
18
|
(): T;
|
|
18
|
-
set: (newValue: T) => void
|
|
19
|
+
set: (newValue: T, options?: AnimateOptions<T>) => Promise<void>;
|
|
19
20
|
animatedState: WritableSignal<AnimatedState<T>>;
|
|
20
21
|
update: (updater: (value: T) => T) => void;
|
|
21
22
|
}
|
|
@@ -60,7 +61,8 @@ export function animatedSignal<T>(initialValue: T, options: AnimateOptions<T> =
|
|
|
60
61
|
|
|
61
62
|
function animatedSignal(): AnimatedState<T>;
|
|
62
63
|
function animatedSignal(newValue: T): void;
|
|
63
|
-
function animatedSignal(newValue
|
|
64
|
+
function animatedSignal(newValue: T, animationConfig: AnimateOptions<T>): void;
|
|
65
|
+
function animatedSignal(newValue?: T, animationConfig: AnimateOptions<T> = {}): AnimatedState<T> | void {
|
|
64
66
|
if (newValue === undefined) {
|
|
65
67
|
return privateSignal();
|
|
66
68
|
}
|
|
@@ -82,6 +84,7 @@ export function animatedSignal<T>(initialValue: T, options: AnimateOptions<T> =
|
|
|
82
84
|
// TODO
|
|
83
85
|
duration: 20,
|
|
84
86
|
...options,
|
|
87
|
+
...animationConfig,
|
|
85
88
|
from: prevState.current,
|
|
86
89
|
to: newValue,
|
|
87
90
|
onUpdate: (value) => {
|
|
@@ -105,9 +108,42 @@ export function animatedSignal<T>(initialValue: T, options: AnimateOptions<T> =
|
|
|
105
108
|
fn.update = (updater: (value: T) => any) => {
|
|
106
109
|
animatedSignal(updater(privateSignal().current));
|
|
107
110
|
}
|
|
108
|
-
fn.set = (newValue: T) => {
|
|
109
|
-
|
|
111
|
+
fn.set = async (newValue: T, animationConfig: AnimateOptions<T> = {}) => {
|
|
112
|
+
return new Promise<void>((resolve) => {
|
|
113
|
+
animatedSignal(newValue, {
|
|
114
|
+
...animationConfig,
|
|
115
|
+
onComplete: resolve
|
|
116
|
+
});
|
|
117
|
+
})
|
|
110
118
|
}
|
|
111
119
|
|
|
112
120
|
return fn as any
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Executes a sequence of animations. If an array is provided as an element in the sequence,
|
|
125
|
+
* those animations will be executed in parallel.
|
|
126
|
+
*
|
|
127
|
+
* @param sequence Array of animation functions or arrays of animation functions for parallel execution
|
|
128
|
+
* @returns Promise that resolves when all animations are complete
|
|
129
|
+
* @example
|
|
130
|
+
* ```ts
|
|
131
|
+
* await animatedSequence([
|
|
132
|
+
* () => value1.set(10),
|
|
133
|
+
* [
|
|
134
|
+
* () => value2.set(20),
|
|
135
|
+
* () => value3.set(30)
|
|
136
|
+
* ],
|
|
137
|
+
* () => value1.set(0)
|
|
138
|
+
* ])
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export async function animatedSequence(sequence: ((() => Promise<void>) | (() => Promise<void>)[])[]) {
|
|
142
|
+
for (const item of sequence) {
|
|
143
|
+
if (Array.isArray(item)) {
|
|
144
|
+
await Promise.all(item.map(fn => fn()));
|
|
145
|
+
} else {
|
|
146
|
+
await item();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
113
149
|
}
|
package/src/engine/reactive.ts
CHANGED
|
@@ -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
|
|
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,58 @@ 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 {
|
|
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
|
|
302
|
-
itemsSubject: WritableArraySignal<T>,
|
|
303
|
-
createElementFn: (item:
|
|
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
|
|
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
|
|
310
|
-
|
|
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
|
+
|
|
315
347
|
return defer(() => {
|
|
316
|
-
|
|
348
|
+
const { items, keys } = getInitialItems();
|
|
349
|
+
let initialItems = [...items];
|
|
350
|
+
let initialKeys = keys ? [...keys] : undefined;
|
|
317
351
|
let init = true;
|
|
318
|
-
return itemsSubject.observable.pipe(
|
|
319
|
-
map((event: ArrayChange<T>) => {
|
|
320
|
-
const { type, items
|
|
352
|
+
return (itemsSubject.observable as Observable<ArrayChange<T> | ObjectChange<T>>).pipe(
|
|
353
|
+
map((event: ArrayChange<T> | ObjectChange<T>): FlowResult => {
|
|
354
|
+
const { type, items } = event;
|
|
355
|
+
const index = 'index' in event ? event.index : (event as ObjectChange<T>).key;
|
|
356
|
+
|
|
321
357
|
if (init) {
|
|
322
358
|
if (elements.length > 0) {
|
|
323
359
|
return {
|
|
@@ -325,8 +361,9 @@ export function loop<T = any>(
|
|
|
325
361
|
fullElements: elements,
|
|
326
362
|
};
|
|
327
363
|
}
|
|
328
|
-
const newElements = addAt(initialItems, 0);
|
|
364
|
+
const newElements = addAt(initialItems, 0, initialKeys);
|
|
329
365
|
initialItems = [];
|
|
366
|
+
initialKeys = undefined;
|
|
330
367
|
init = false;
|
|
331
368
|
return {
|
|
332
369
|
elements: newElements,
|
|
@@ -339,25 +376,64 @@ export function loop<T = any>(
|
|
|
339
376
|
});
|
|
340
377
|
elements = [];
|
|
341
378
|
}
|
|
342
|
-
|
|
379
|
+
if (!isArraySignal) {
|
|
380
|
+
const entries = Object.entries((itemsSubject as any)._subject.value.value) as [string, T][];
|
|
381
|
+
const newElements = addAt(
|
|
382
|
+
entries.map(([_, value]) => value),
|
|
383
|
+
0,
|
|
384
|
+
entries.map(([key]) => key)
|
|
385
|
+
);
|
|
386
|
+
return {
|
|
387
|
+
elements: newElements,
|
|
388
|
+
fullElements: elements,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
const newElements = addAt(items as T[], 0);
|
|
343
392
|
return {
|
|
344
393
|
elements: newElements,
|
|
345
394
|
fullElements: elements,
|
|
346
395
|
};
|
|
347
396
|
} else if (type == "add" && index != undefined) {
|
|
348
|
-
const lastElement = elements[index - 1];
|
|
349
|
-
|
|
397
|
+
const lastElement = typeof index === 'number' ? elements[index - 1] : elements[elements.length - 1];
|
|
398
|
+
let newElements: Element[];
|
|
399
|
+
if (!isArraySignal && typeof index === 'string') {
|
|
400
|
+
// For object updates, create a single element with the new value
|
|
401
|
+
const value = (event as ObjectChange<T>).value;
|
|
402
|
+
if (value !== undefined) {
|
|
403
|
+
newElements = [createElementFn(value, index)];
|
|
404
|
+
elements.push(newElements[0]);
|
|
405
|
+
} else {
|
|
406
|
+
newElements = [];
|
|
407
|
+
}
|
|
408
|
+
} else {
|
|
409
|
+
// For array updates, use addAt with the items array
|
|
410
|
+
newElements = addAt(items as T[], index);
|
|
411
|
+
}
|
|
350
412
|
return {
|
|
351
413
|
prev: lastElement,
|
|
352
414
|
elements: newElements,
|
|
353
415
|
fullElements: elements,
|
|
354
416
|
};
|
|
355
|
-
} else if (
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
417
|
+
} else if (type == "remove") {
|
|
418
|
+
if (!isArraySignal && typeof index === 'string') {
|
|
419
|
+
// For object property deletion
|
|
420
|
+
const elementIndex = elements.findIndex(el => {
|
|
421
|
+
return el.props.text === index || el.props.key === index;
|
|
422
|
+
});
|
|
423
|
+
if (elementIndex !== -1) {
|
|
424
|
+
const currentElement = elements[elementIndex];
|
|
425
|
+
destroyElement(currentElement);
|
|
426
|
+
elements.splice(elementIndex, 1);
|
|
427
|
+
}
|
|
428
|
+
} else if (typeof index === 'number') {
|
|
429
|
+
// For array element deletion
|
|
430
|
+
const currentElement = elements[index];
|
|
431
|
+
destroyElement(currentElement);
|
|
432
|
+
elements.splice(index, 1);
|
|
433
|
+
}
|
|
359
434
|
return {
|
|
360
435
|
elements: [],
|
|
436
|
+
fullElements: elements,
|
|
361
437
|
};
|
|
362
438
|
}
|
|
363
439
|
return {
|