canvasengine 2.0.0-beta.13 → 2.0.0-beta.15
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 +35 -26
- package/dist/index.js +280 -157
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/Graphic.ts +15 -13
- package/src/components/Sprite.ts +8 -2
- package/src/components/types/DisplayObject.ts +7 -0
- package/src/directives/Drag.ts +118 -11
- package/src/directives/Sound.ts +5 -4
- package/src/directives/ViewportFollow.ts +26 -4
- package/src/engine/reactive.ts +41 -22
- package/src/index.ts +2 -1
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canvasengine",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"@barvynkoa/particle-emitter": "^0.0.1",
|
|
9
|
-
"@signe/reactive": "^
|
|
9
|
+
"@signe/reactive": "^2.3.3",
|
|
10
10
|
"howler": "^2.2.4",
|
|
11
11
|
"pixi-filters": "^6.0.5",
|
|
12
12
|
"pixi-viewport": "^6.0.3",
|
|
@@ -1,35 +1,31 @@
|
|
|
1
|
-
import { effect, Signal } from "@signe/reactive";
|
|
1
|
+
import { Effect, effect, Signal } from "@signe/reactive";
|
|
2
2
|
import { Graphics as PixiGraphics } from "pixi.js";
|
|
3
3
|
import { createComponent, registerComponent } from "../engine/reactive";
|
|
4
4
|
import { DisplayObject } from "./DisplayObject";
|
|
5
5
|
import { DisplayObjectProps } from "./types/DisplayObject";
|
|
6
6
|
import { useProps } from "../hooks/useProps";
|
|
7
|
+
import { SignalOrPrimitive } from "./types";
|
|
7
8
|
|
|
8
9
|
interface GraphicsProps extends DisplayObjectProps {
|
|
9
10
|
draw?: (graphics: PixiGraphics) => void;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
interface RectProps extends DisplayObjectProps {
|
|
13
|
-
|
|
14
|
-
height: number;
|
|
15
|
-
color: string;
|
|
14
|
+
color: SignalOrPrimitive<string>;
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
interface CircleProps extends DisplayObjectProps {
|
|
19
|
-
radius: number
|
|
20
|
-
color: string
|
|
18
|
+
radius: SignalOrPrimitive<number>;
|
|
19
|
+
color: SignalOrPrimitive<string>;
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
interface EllipseProps extends DisplayObjectProps {
|
|
24
|
-
|
|
25
|
-
height: number;
|
|
26
|
-
color: string;
|
|
23
|
+
color: SignalOrPrimitive<string>;
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
interface TriangleProps extends DisplayObjectProps {
|
|
30
|
-
base: number
|
|
31
|
-
|
|
32
|
-
color: string;
|
|
27
|
+
base: SignalOrPrimitive<number>;
|
|
28
|
+
color: SignalOrPrimitive<string>;
|
|
33
29
|
}
|
|
34
30
|
|
|
35
31
|
interface SvgProps extends DisplayObjectProps {
|
|
@@ -37,15 +33,21 @@ interface SvgProps extends DisplayObjectProps {
|
|
|
37
33
|
}
|
|
38
34
|
|
|
39
35
|
class CanvasGraphics extends DisplayObject(PixiGraphics) {
|
|
36
|
+
clearEffect: Effect;
|
|
40
37
|
onInit(props) {
|
|
41
38
|
super.onInit(props);
|
|
42
39
|
if (props.draw) {
|
|
43
|
-
effect(() => {
|
|
40
|
+
this.clearEffect = effect(() => {
|
|
44
41
|
this.clear();
|
|
45
42
|
props.draw?.(this);
|
|
46
43
|
});
|
|
47
44
|
}
|
|
48
45
|
}
|
|
46
|
+
|
|
47
|
+
onDestroy() {
|
|
48
|
+
this.clearEffect.subscription.unsubscribe();
|
|
49
|
+
super.onDestroy();
|
|
50
|
+
}
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
interface CanvasGraphics extends PixiGraphics {}
|
package/src/components/Sprite.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Howl } from 'howler';
|
|
2
|
+
import { computed, effect, isSignal, Signal } from "@signe/reactive";
|
|
2
3
|
import {
|
|
3
4
|
Assets,
|
|
4
5
|
Container,
|
|
@@ -338,7 +339,12 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
338
339
|
const sound = this.currentAnimation.data.sound;
|
|
339
340
|
|
|
340
341
|
if (sound) {
|
|
341
|
-
|
|
342
|
+
new Howl({
|
|
343
|
+
src: sound,
|
|
344
|
+
autoplay: true,
|
|
345
|
+
loop: false,
|
|
346
|
+
volume: 1,
|
|
347
|
+
})
|
|
342
348
|
}
|
|
343
349
|
|
|
344
350
|
// Updates immediately to avoid flickering
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as PIXI from "pixi.js";
|
|
2
2
|
import { SignalOrPrimitive } from ".";
|
|
3
|
+
import { DragProps } from "../../directives/Drag";
|
|
4
|
+
import { ViewportFollowProps } from "../../directives/ViewportFollow";
|
|
3
5
|
|
|
4
6
|
export type FlexDirection = 'row' | 'column' | 'row-reverse' | 'column-reverse';
|
|
5
7
|
export type JustifyContent = 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around';
|
|
@@ -37,6 +39,11 @@ export interface DisplayObjectProps {
|
|
|
37
39
|
blendMode?: SignalOrPrimitive<PIXI.BLEND_MODES>;
|
|
38
40
|
blur?: SignalOrPrimitive<number>;
|
|
39
41
|
|
|
42
|
+
// Directives
|
|
43
|
+
drag?: DragProps;
|
|
44
|
+
viewportFollow?: ViewportFollowProps;
|
|
45
|
+
|
|
46
|
+
// Events
|
|
40
47
|
click?: PIXI.FederatedEventHandler;
|
|
41
48
|
mousedown?: PIXI.FederatedEventHandler;
|
|
42
49
|
mouseenter?: PIXI.FederatedEventHandler;
|
package/src/directives/Drag.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { effect, isSignal, signal } from '@signe/reactive';
|
|
1
|
+
import { effect, isComputed, isSignal, signal } from '@signe/reactive';
|
|
2
2
|
import { Container, Rectangle, Point, FederatedPointerEvent } from 'pixi.js';
|
|
3
3
|
import { Directive, registerDirective } from '../engine/directive';
|
|
4
4
|
import { Element } from '../engine/reactive';
|
|
@@ -6,6 +6,20 @@ import { snap } from 'popmotion';
|
|
|
6
6
|
import { addContext } from '../hooks/addContext';
|
|
7
7
|
import { Subscription } from 'rxjs';
|
|
8
8
|
import { useProps } from '../hooks/useProps';
|
|
9
|
+
import { SignalOrPrimitive } from '../components/types';
|
|
10
|
+
|
|
11
|
+
export type DragProps = {
|
|
12
|
+
move?: (event: FederatedPointerEvent) => void;
|
|
13
|
+
start?: () => void;
|
|
14
|
+
end?: () => void;
|
|
15
|
+
snap?: SignalOrPrimitive<number>;
|
|
16
|
+
direction?: SignalOrPrimitive<'x' | 'y' | 'all'>;
|
|
17
|
+
keyToPress?: SignalOrPrimitive<string[]>;
|
|
18
|
+
viewport?: {
|
|
19
|
+
edgeThreshold?: SignalOrPrimitive<number>;
|
|
20
|
+
maxSpeed?: SignalOrPrimitive<number>;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
9
23
|
|
|
10
24
|
export class Drop extends Directive {
|
|
11
25
|
private elementRef: Element<Container> | null = null;
|
|
@@ -33,10 +47,14 @@ export class Drag extends Directive {
|
|
|
33
47
|
private viewport: any | null = null;
|
|
34
48
|
private animationFrameId: number | null = null;
|
|
35
49
|
private lastPointerPosition: Point = new Point();
|
|
50
|
+
private pressedKeys: Set<string> = new Set();
|
|
51
|
+
private pointerIsDown = false;
|
|
36
52
|
|
|
37
53
|
private onDragMoveHandler: (event: FederatedPointerEvent) => void = () => {};
|
|
38
54
|
private onDragEndHandler: () => void = () => {};
|
|
39
55
|
private onDragStartHandler: (event: FederatedPointerEvent) => void = () => {};
|
|
56
|
+
private onKeyDownHandler: (event: KeyboardEvent) => void = () => {};
|
|
57
|
+
private onKeyUpHandler: (event: KeyboardEvent) => void = () => {};
|
|
40
58
|
|
|
41
59
|
private subscriptions: Subscription[] = [];
|
|
42
60
|
|
|
@@ -45,6 +63,8 @@ export class Drag extends Directive {
|
|
|
45
63
|
this.onDragMoveHandler = this.onDragMove.bind(this);
|
|
46
64
|
this.onDragEndHandler = this.onDragEnd.bind(this);
|
|
47
65
|
this.onDragStartHandler = this.onPointerDown.bind(this);
|
|
66
|
+
this.onKeyDownHandler = this.onKeyDown.bind(this);
|
|
67
|
+
this.onKeyUpHandler = this.onKeyUp.bind(this);
|
|
48
68
|
}
|
|
49
69
|
|
|
50
70
|
onMount(element: Element<Container>) {
|
|
@@ -76,6 +96,12 @@ export class Drag extends Directive {
|
|
|
76
96
|
this.stageRef.on('pointerup', this.onDragEndHandler);
|
|
77
97
|
this.stageRef.on('pointerupoutside', this.onDragEndHandler);
|
|
78
98
|
|
|
99
|
+
const keysToPress = dragProps.keyToPress ? dragProps.keyToPress : [];
|
|
100
|
+
|
|
101
|
+
// Always add keyboard event listeners to track pressed keys
|
|
102
|
+
window.addEventListener('keydown', this.onKeyDownHandler);
|
|
103
|
+
window.addEventListener('keyup', this.onKeyUpHandler);
|
|
104
|
+
|
|
79
105
|
this.subscriptions = [
|
|
80
106
|
tick.observable.subscribe(() => {
|
|
81
107
|
if (this.isDragging && this.viewport) {
|
|
@@ -91,7 +117,8 @@ export class Drag extends Directive {
|
|
|
91
117
|
const options = useProps(drag?.value ?? drag, {
|
|
92
118
|
snap: 0,
|
|
93
119
|
viewport: {},
|
|
94
|
-
direction: 'all'
|
|
120
|
+
direction: 'all',
|
|
121
|
+
keyToPress: []
|
|
95
122
|
});
|
|
96
123
|
options.viewport = useProps(options.viewport, {
|
|
97
124
|
edgeThreshold: 300,
|
|
@@ -147,12 +174,19 @@ export class Drag extends Directive {
|
|
|
147
174
|
this.lastPointerPosition.copyFrom(event.global);
|
|
148
175
|
|
|
149
176
|
const { x: xProp, y: yProp } = propObservables as any;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
177
|
+
|
|
178
|
+
const updatePosition = (prop: any, value: number) => {
|
|
179
|
+
if (isComputed(prop)) {
|
|
180
|
+
prop.dependencies.forEach(dependency => {
|
|
181
|
+
dependency.set(value)
|
|
182
|
+
})
|
|
183
|
+
} else if (isSignal(prop)) {
|
|
184
|
+
prop.set(value)
|
|
185
|
+
}
|
|
155
186
|
}
|
|
187
|
+
|
|
188
|
+
if (xProp !== undefined) updatePosition(xProp, instance.position.x)
|
|
189
|
+
if (yProp !== undefined) updatePosition(yProp, instance.position.y)
|
|
156
190
|
}
|
|
157
191
|
|
|
158
192
|
/**
|
|
@@ -223,10 +257,13 @@ export class Drag extends Directive {
|
|
|
223
257
|
* Handles drag end event and stops viewport movement
|
|
224
258
|
*/
|
|
225
259
|
private onDragEnd() {
|
|
226
|
-
|
|
260
|
+
this.pointerIsDown = false;
|
|
261
|
+
|
|
262
|
+
if (!this.isDragging) return;
|
|
227
263
|
|
|
228
264
|
const dragProps = this.dragProps;
|
|
229
265
|
this.isDragging = false;
|
|
266
|
+
|
|
230
267
|
dragProps?.end?.();
|
|
231
268
|
|
|
232
269
|
if (this.stageRef) {
|
|
@@ -234,23 +271,86 @@ export class Drag extends Directive {
|
|
|
234
271
|
}
|
|
235
272
|
}
|
|
236
273
|
|
|
274
|
+
onKeyDown(event: KeyboardEvent) {
|
|
275
|
+
this.pressedKeys.add(event.code);
|
|
276
|
+
this.pressedKeys.add(event.key.toLowerCase());
|
|
277
|
+
|
|
278
|
+
if (this.pointerIsDown && !this.isDragging && this.areRequiredKeysPressed()) {
|
|
279
|
+
this.startDrag();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
onKeyUp(event: KeyboardEvent) {
|
|
284
|
+
this.pressedKeys.delete(event.code);
|
|
285
|
+
this.pressedKeys.delete(event.key.toLowerCase());
|
|
286
|
+
if (this.isDragging && !this.areRequiredKeysPressed()) {
|
|
287
|
+
this.onDragEnd();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private areRequiredKeysPressed(): boolean {
|
|
292
|
+
const keyToPress = this.dragProps.keyToPress ? this.dragProps.keyToPress : [];
|
|
293
|
+
if (!keyToPress || keyToPress.length === 0) {
|
|
294
|
+
return true; // No keys required, always return true
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return keyToPress.some(key => {
|
|
298
|
+
// Check if the key is pressed directly
|
|
299
|
+
if (this.pressedKeys.has(key)) {
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Check common alternative formats
|
|
304
|
+
// Space key can be "Space", " ", or "space"
|
|
305
|
+
if (key.toLowerCase() === 'space') {
|
|
306
|
+
return this.pressedKeys.has('Space') || this.pressedKeys.has(' ');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Shift key can be "ShiftLeft", "ShiftRight", or "shift"
|
|
310
|
+
if (key.toLowerCase() === 'shift') {
|
|
311
|
+
return this.pressedKeys.has('ShiftLeft') || this.pressedKeys.has('ShiftRight');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Control key can be "ControlLeft", "ControlRight", or "control"
|
|
315
|
+
if (key.toLowerCase() === 'control' || key.toLowerCase() === 'ctrl') {
|
|
316
|
+
return this.pressedKeys.has('ControlLeft') || this.pressedKeys.has('ControlRight');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Alt key can be "AltLeft", "AltRight", or "alt"
|
|
320
|
+
if (key.toLowerCase() === 'alt') {
|
|
321
|
+
return this.pressedKeys.has('AltLeft') || this.pressedKeys.has('AltRight');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return false;
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
237
328
|
private onPointerDown(event: FederatedPointerEvent) {
|
|
238
329
|
if (!this.elementRef?.componentInstance || !this.stageRef || !this.elementRef.componentInstance.parent) return;
|
|
330
|
+
|
|
331
|
+
this.pointerIsDown = true;
|
|
239
332
|
|
|
240
333
|
const instance = this.elementRef.componentInstance;
|
|
241
334
|
const parent = instance.parent;
|
|
242
|
-
const dragProps = this.dragProps;
|
|
243
335
|
|
|
244
336
|
const parentLocalPointer = parent.toLocal(event.global);
|
|
245
337
|
|
|
246
338
|
this.offsetInParent.x = parentLocalPointer.x - instance.position.x;
|
|
247
339
|
this.offsetInParent.y = parentLocalPointer.y - instance.position.y;
|
|
248
|
-
|
|
249
|
-
this.isDragging = true;
|
|
250
340
|
|
|
251
341
|
// Store initial pointer position
|
|
252
342
|
this.lastPointerPosition.copyFrom(event.global);
|
|
253
343
|
|
|
344
|
+
if (this.areRequiredKeysPressed()) {
|
|
345
|
+
this.startDrag();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private startDrag() {
|
|
350
|
+
if (this.isDragging || !this.stageRef) return;
|
|
351
|
+
|
|
352
|
+
this.isDragging = true;
|
|
353
|
+
const dragProps = this.dragProps;
|
|
254
354
|
dragProps?.start?.();
|
|
255
355
|
this.stageRef.on('pointermove', this.onDragMoveHandler);
|
|
256
356
|
}
|
|
@@ -273,8 +373,15 @@ export class Drag extends Directive {
|
|
|
273
373
|
this.stageRef.off('pointerup', this.onDragEndHandler);
|
|
274
374
|
this.stageRef.off('pointerupoutside', this.onDragEndHandler);
|
|
275
375
|
}
|
|
376
|
+
|
|
377
|
+
// Remove keyboard event listeners
|
|
378
|
+
window.removeEventListener('keydown', this.onKeyDownHandler);
|
|
379
|
+
window.removeEventListener('keyup', this.onKeyUpHandler);
|
|
380
|
+
|
|
276
381
|
this.stageRef = null;
|
|
277
382
|
this.viewport = null;
|
|
383
|
+
this.pressedKeys.clear();
|
|
384
|
+
this.pointerIsDown = false;
|
|
278
385
|
}
|
|
279
386
|
}
|
|
280
387
|
|
package/src/directives/Sound.ts
CHANGED
|
@@ -20,7 +20,8 @@ export class Sound extends Directive {
|
|
|
20
20
|
onMount(element: Element<Container>) {
|
|
21
21
|
const { props } = element
|
|
22
22
|
const tick = props.context.tick
|
|
23
|
-
const
|
|
23
|
+
const propsSound = props.sound.value ?? props.sound
|
|
24
|
+
const { src, autoplay, loop, volume, spatial } = propsSound
|
|
24
25
|
this.sound = new Howl({
|
|
25
26
|
src,
|
|
26
27
|
autoplay,
|
|
@@ -28,8 +29,8 @@ export class Sound extends Directive {
|
|
|
28
29
|
volume
|
|
29
30
|
})
|
|
30
31
|
for (let event of EVENTS) {
|
|
31
|
-
if (!
|
|
32
|
-
const fn =
|
|
32
|
+
if (!propsSound[event]) continue
|
|
33
|
+
const fn = propsSound[event]
|
|
33
34
|
this.eventsFn.push(fn)
|
|
34
35
|
this.sound.on(event, fn);
|
|
35
36
|
}
|
|
@@ -51,7 +52,7 @@ export class Sound extends Directive {
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
onUpdate(props: any) {
|
|
54
|
-
const { volume, loop, mute, seek, playing, rate, spatial } = props
|
|
55
|
+
const { volume, loop, mute, seek, playing, rate, spatial } = props.value ?? props
|
|
55
56
|
if (volume != undefined) this.sound.volume(volume)
|
|
56
57
|
if (loop != undefined) this.sound.loop(loop)
|
|
57
58
|
if (mute != undefined) this.sound.mute(mute)
|
|
@@ -1,23 +1,45 @@
|
|
|
1
1
|
import { ComponentInstance } from '../components/DisplayObject';
|
|
2
|
+
import { SignalOrPrimitive } from '../components/types';
|
|
2
3
|
import { Directive, registerDirective } from '../engine/directive';
|
|
3
4
|
import { Element } from '../engine/reactive';
|
|
4
5
|
import { error } from '../engine/utils';
|
|
6
|
+
import { useProps } from '../hooks/useProps';
|
|
7
|
+
|
|
8
|
+
export type ViewportFollowProps = {
|
|
9
|
+
viewportFollow?: boolean | {
|
|
10
|
+
speed?: SignalOrPrimitive<number>;
|
|
11
|
+
acceleration?: SignalOrPrimitive<number>;
|
|
12
|
+
radius?: SignalOrPrimitive<number>;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
5
15
|
|
|
6
16
|
export class ViewportFollow extends Directive {
|
|
7
17
|
onInit(element: Element<ComponentInstance>) {
|
|
8
18
|
|
|
9
19
|
}
|
|
10
20
|
onMount(element: Element) {
|
|
11
|
-
this.onUpdate(element.props, element)
|
|
21
|
+
this.onUpdate(element.props.viewportFollow, element)
|
|
12
22
|
}
|
|
13
|
-
onUpdate(
|
|
14
|
-
const { viewportFollow } = element.props
|
|
23
|
+
onUpdate(viewportFollow: any, element: Element) {
|
|
15
24
|
const { viewport } = element.props.context
|
|
16
25
|
if (!viewport) {
|
|
17
26
|
throw error('ViewportFollow directive requires a Viewport component to be mounted in the same context')
|
|
18
27
|
}
|
|
19
28
|
if (viewportFollow) {
|
|
20
|
-
|
|
29
|
+
if (viewportFollow === true) {
|
|
30
|
+
viewport.follow(element.componentInstance)
|
|
31
|
+
} else {
|
|
32
|
+
const options = useProps(viewportFollow, {
|
|
33
|
+
speed: undefined,
|
|
34
|
+
acceleration: undefined,
|
|
35
|
+
radius: undefined
|
|
36
|
+
})
|
|
37
|
+
viewport.follow(element.componentInstance, {
|
|
38
|
+
speed: options.speed(),
|
|
39
|
+
acceleration: options.acceleration(),
|
|
40
|
+
radius: options.radius()
|
|
41
|
+
})
|
|
42
|
+
}
|
|
21
43
|
} else {
|
|
22
44
|
viewport.plugins.remove('follow')
|
|
23
45
|
}
|
package/src/engine/reactive.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Signal, WritableArraySignal, WritableObjectSignal, isSignal } from "@signe/reactive";
|
|
1
|
+
import { ArrayChange, ObjectChange, Signal, WritableArraySignal, WritableObjectSignal, isComputed, isSignal, signal } from "@signe/reactive";
|
|
2
2
|
import {
|
|
3
3
|
Observable,
|
|
4
4
|
Subject,
|
|
@@ -17,25 +17,6 @@ export interface Props {
|
|
|
17
17
|
[key: string]: any;
|
|
18
18
|
}
|
|
19
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
20
|
type NestedSignalObjects = {
|
|
40
21
|
[Key in string]: NestedSignalObjects | Signal<any>;
|
|
41
22
|
};
|
|
@@ -104,11 +85,11 @@ function destroyElement(element: Element | Element[]) {
|
|
|
104
85
|
}
|
|
105
86
|
element.propSubscriptions.forEach((sub) => sub.unsubscribe());
|
|
106
87
|
element.effectSubscriptions.forEach((sub) => sub.unsubscribe());
|
|
88
|
+
element.effectUnmounts.forEach((fn) => fn?.());
|
|
107
89
|
for (let name in element.directives) {
|
|
108
90
|
element.directives[name].onDestroy?.(element);
|
|
109
91
|
}
|
|
110
92
|
element.componentInstance.onDestroy?.(element.parent as any);
|
|
111
|
-
element.effectUnmounts.forEach((fn) => fn?.());
|
|
112
93
|
}
|
|
113
94
|
|
|
114
95
|
/**
|
|
@@ -309,9 +290,17 @@ export function createComponent(tag: string, props?: Props): Element {
|
|
|
309
290
|
* @returns {Observable} An observable that emits the list of created child elements.
|
|
310
291
|
*/
|
|
311
292
|
export function loop<T>(
|
|
312
|
-
itemsSubject:
|
|
293
|
+
itemsSubject: any,
|
|
313
294
|
createElementFn: (item: T, index: number | string) => Element | null
|
|
314
295
|
): FlowObservable {
|
|
296
|
+
|
|
297
|
+
if (isComputed(itemsSubject) && itemsSubject.dependencies.size == 0) {
|
|
298
|
+
itemsSubject = signal(itemsSubject());
|
|
299
|
+
}
|
|
300
|
+
else if (!isSignal(itemsSubject)) {
|
|
301
|
+
itemsSubject = signal(itemsSubject);
|
|
302
|
+
}
|
|
303
|
+
|
|
315
304
|
return defer(() => {
|
|
316
305
|
let elements: Element[] = [];
|
|
317
306
|
let elementMap = new Map<string | number, Element>();
|
|
@@ -376,6 +365,36 @@ export function loop<T>(
|
|
|
376
365
|
el.destroy();
|
|
377
366
|
elementMap.delete(change.index!);
|
|
378
367
|
});
|
|
368
|
+
} else if (change.type === 'update' && change.index !== undefined && change.items.length === 1) {
|
|
369
|
+
const index = change.index;
|
|
370
|
+
const newItem = change.items[0];
|
|
371
|
+
|
|
372
|
+
// Check if the previous item at this index was effectively undefined or non-existent
|
|
373
|
+
if (index >= elements.length || elements[index] === undefined || !elementMap.has(index)) {
|
|
374
|
+
// Treat as add operation
|
|
375
|
+
const newElement = createElementFn(newItem as T, index);
|
|
376
|
+
if (newElement) {
|
|
377
|
+
elements.splice(index, 0, newElement); // Insert at the correct index
|
|
378
|
+
elementMap.set(index, newElement);
|
|
379
|
+
// Adjust indices in elementMap for subsequent elements might be needed if map relied on exact indices
|
|
380
|
+
// This simple implementation assumes keys are stable or createElementFn handles context correctly
|
|
381
|
+
} else {
|
|
382
|
+
console.warn(`Element creation returned null for index ${index} during add-like update.`);
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
// Treat as a standard update operation
|
|
386
|
+
const oldElement = elements[index];
|
|
387
|
+
oldElement.destroy();
|
|
388
|
+
const newElement = createElementFn(newItem as T, index);
|
|
389
|
+
if (newElement) {
|
|
390
|
+
elements[index] = newElement;
|
|
391
|
+
elementMap.set(index, newElement);
|
|
392
|
+
} else {
|
|
393
|
+
// Handle case where new element creation returns null
|
|
394
|
+
elements.splice(index, 1);
|
|
395
|
+
elementMap.delete(index);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
379
398
|
}
|
|
380
399
|
|
|
381
400
|
subscriber.next({
|
package/src/index.ts
CHANGED
|
@@ -12,4 +12,5 @@ export * from './utils/Ease'
|
|
|
12
12
|
export * from './utils/RadialGradient'
|
|
13
13
|
export * from './components/DisplayObject'
|
|
14
14
|
export { isObservable } from 'rxjs'
|
|
15
|
-
export * as Utils from './engine/utils'
|
|
15
|
+
export * as Utils from './engine/utils'
|
|
16
|
+
export * as Howl from 'howler'
|