canvasengine 2.0.0-beta.35 → 2.0.0-beta.37
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/{DebugRenderer-DMqhoMfE.js → DebugRenderer-CTWPthRt.js} +2 -2
- package/dist/{DebugRenderer-DMqhoMfE.js.map → DebugRenderer-CTWPthRt.js.map} +1 -1
- package/dist/components/Container.d.ts +4 -0
- package/dist/components/Container.d.ts.map +1 -1
- package/dist/components/DOMContainer.d.ts +4 -0
- package/dist/components/DOMContainer.d.ts.map +1 -1
- package/dist/components/DisplayObject.d.ts +4 -0
- package/dist/components/DisplayObject.d.ts.map +1 -1
- package/dist/components/Mesh.d.ts +4 -0
- package/dist/components/Mesh.d.ts.map +1 -1
- package/dist/components/Sprite.d.ts +4 -0
- package/dist/components/Sprite.d.ts.map +1 -1
- package/dist/components/Viewport.d.ts +4 -0
- package/dist/components/Viewport.d.ts.map +1 -1
- package/dist/components/types/DisplayObject.d.ts +4 -0
- package/dist/components/types/DisplayObject.d.ts.map +1 -1
- package/dist/directives/Flash.d.ts +117 -0
- package/dist/directives/Flash.d.ts.map +1 -0
- package/dist/directives/Shake.d.ts +98 -0
- package/dist/directives/Shake.d.ts.map +1 -0
- package/dist/directives/index.d.ts +2 -0
- package/dist/directives/index.d.ts.map +1 -1
- package/dist/engine/reactive.d.ts.map +1 -1
- package/dist/engine/trigger.d.ts +2 -3
- package/dist/engine/trigger.d.ts.map +1 -1
- package/dist/engine/utils.d.ts.map +1 -1
- package/dist/{index-B9ATEmLe.js → index-BqwprEPH.js} +4044 -3825
- package/dist/index-BqwprEPH.js.map +1 -0
- package/dist/index.global.js +6 -6
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +51 -49
- package/package.json +1 -1
- package/src/components/Container.ts +17 -0
- package/src/components/DisplayObject.ts +45 -6
- package/src/components/types/DisplayObject.ts +4 -0
- package/src/directives/Flash.ts +409 -0
- package/src/directives/Shake.ts +282 -0
- package/src/directives/index.ts +3 -1
- package/src/engine/reactive.ts +9 -1
- package/src/engine/trigger.ts +2 -2
- package/src/engine/utils.ts +4 -0
- package/dist/index-B9ATEmLe.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { h as a } from "./index-
|
|
2
|
-
import { A as r,
|
|
1
|
+
import { h as a } from "./index-BqwprEPH.js";
|
|
2
|
+
import { A as r, X as o, Y as n, q as l, v as c, r as p, C as S, d as u, U as m, W as g, ai as d, f as b, D as C, ah as h, af as T, w as j, j as w, G as D, t as f, c as v, I as E, K as O, M as k, Q as y, O as P, P as V, ag as x, R as A, z as B, S as G, g as H, e as M, B as N, y as R, J as q, L as F, T as I, x as K, b as U, H as z, N as J, V as L, ae as Q, ad as W, ab as X, k as Y, a2 as Z, a0 as _, a3 as $, l as aa, a7 as sa, ac as ea, m as ta, n as ia, Z as ra, o as oa, i as na, _ as la, p as ca, a8 as pa, a1 as Sa, a5 as ua, a4 as ma, aa as ga, $ as da, s as ba, a6 as Ca, a9 as ha, a as Ta, u as ja } from "./index-BqwprEPH.js";
|
|
3
3
|
const e = a.Howler;
|
|
4
4
|
export {
|
|
5
5
|
r as ArraySubject,
|
|
@@ -15,61 +15,63 @@ export {
|
|
|
15
15
|
d as DisplayObject,
|
|
16
16
|
b as Drag,
|
|
17
17
|
C as Drop,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
h as EVENTS,
|
|
19
|
+
T as Easing,
|
|
20
|
+
j as Ellipse,
|
|
21
|
+
w as Flash,
|
|
21
22
|
D as GamepadControls,
|
|
22
23
|
f as Graphics,
|
|
23
24
|
v as Howl,
|
|
24
25
|
e as Howler,
|
|
25
26
|
E as Input,
|
|
26
27
|
O as KeyboardControls,
|
|
27
|
-
|
|
28
|
+
k as Mesh,
|
|
28
29
|
y as NineSliceSprite,
|
|
29
30
|
P as ObjectSubject,
|
|
30
31
|
V as ParticlesEmitter,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
I as
|
|
42
|
-
K as
|
|
43
|
-
U as
|
|
44
|
-
z as
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
Ta as
|
|
32
|
+
x as RadialGradient,
|
|
33
|
+
A as Rect,
|
|
34
|
+
B as Scene,
|
|
35
|
+
G as Scheduler,
|
|
36
|
+
H as Shake,
|
|
37
|
+
M as Sound,
|
|
38
|
+
N as Sprite,
|
|
39
|
+
R as Svg,
|
|
40
|
+
q as Text,
|
|
41
|
+
F as TilingSprite,
|
|
42
|
+
I as Transition,
|
|
43
|
+
K as Triangle,
|
|
44
|
+
U as Utils,
|
|
45
|
+
z as Video,
|
|
46
|
+
J as Viewport,
|
|
47
|
+
L as ViewportFollow,
|
|
48
|
+
Q as animatedSequence,
|
|
49
|
+
W as animatedSignal,
|
|
50
|
+
X as bootstrapCanvas,
|
|
51
|
+
Y as computed,
|
|
52
|
+
Z as cond,
|
|
53
|
+
_ as createComponent,
|
|
54
|
+
$ as currentSubscriptionsTracker,
|
|
55
|
+
aa as effect,
|
|
56
|
+
sa as h,
|
|
57
|
+
ea as isAnimatedSignal,
|
|
58
|
+
ta as isArraySubject,
|
|
59
|
+
ia as isComputed,
|
|
60
|
+
ra as isElement,
|
|
61
|
+
oa as isObjectSubject,
|
|
62
|
+
na as isObservable,
|
|
63
|
+
la as isPrimitive,
|
|
64
|
+
ca as isSignal,
|
|
65
|
+
pa as isTrigger,
|
|
66
|
+
Sa as loop,
|
|
67
|
+
ua as mount,
|
|
68
|
+
ma as mountTracker,
|
|
69
|
+
ga as on,
|
|
70
|
+
da as registerComponent,
|
|
71
|
+
ba as signal,
|
|
72
|
+
Ca as tick,
|
|
73
|
+
ha as trigger,
|
|
74
|
+
Ta as useDefineProps,
|
|
75
|
+
ja as useProps
|
|
74
76
|
};
|
|
75
77
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@ import { DisplayObject } from "./DisplayObject";
|
|
|
4
4
|
import { ComponentFunction } from "../engine/signal";
|
|
5
5
|
import { DisplayObjectProps } from "./types/DisplayObject";
|
|
6
6
|
import { setObservablePoint } from "../engine/utils";
|
|
7
|
+
import { isPercent } from "../utils/functions";
|
|
7
8
|
|
|
8
9
|
interface ContainerProps extends DisplayObjectProps {
|
|
9
10
|
sortableChildren?: boolean;
|
|
@@ -34,6 +35,22 @@ export class CanvasContainer extends DisplayObject(PixiContainer) {
|
|
|
34
35
|
componentInstance.addChild(child);
|
|
35
36
|
});
|
|
36
37
|
}
|
|
38
|
+
|
|
39
|
+
// Listen to layout events to update displayWidth and displayHeight with computed values
|
|
40
|
+
const isWidthPercentage = isPercent(props.width);
|
|
41
|
+
const isHeightPercentage = isPercent(props.height);
|
|
42
|
+
|
|
43
|
+
if (isWidthPercentage || isHeightPercentage) {
|
|
44
|
+
this.on('layout', (event) => {
|
|
45
|
+
const layoutBox = event.computedLayout;
|
|
46
|
+
if (isWidthPercentage && layoutBox.width !== undefined) {
|
|
47
|
+
this.displayWidth.set(layoutBox.width);
|
|
48
|
+
}
|
|
49
|
+
if (isHeightPercentage && layoutBox.height !== undefined) {
|
|
50
|
+
this.displayHeight.set(layoutBox.height);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
37
54
|
}
|
|
38
55
|
}
|
|
39
56
|
|
|
@@ -118,6 +118,8 @@ export function DisplayObject(extendClass) {
|
|
|
118
118
|
disableLayout: boolean = false;
|
|
119
119
|
// Store registered event listeners for cleanup
|
|
120
120
|
#registeredEvents: Map<string, Function> = new Map();
|
|
121
|
+
// Store computed layout box dimensions
|
|
122
|
+
#computedLayoutBox: { width?: number; height?: number } | null = null;
|
|
121
123
|
|
|
122
124
|
get deltaRatio() {
|
|
123
125
|
return this.#canvasContext?.scheduler?.tick.value.deltaRatio;
|
|
@@ -188,6 +190,19 @@ export function DisplayObject(extendClass) {
|
|
|
188
190
|
}
|
|
189
191
|
this.isMounted = true;
|
|
190
192
|
this.onUpdate(props);
|
|
193
|
+
|
|
194
|
+
// Listen to layout events to store computed layout dimensions
|
|
195
|
+
const layoutHandler = (event: any) => {
|
|
196
|
+
if (event.computedLayout) {
|
|
197
|
+
this.#computedLayoutBox = {
|
|
198
|
+
width: event.computedLayout.width,
|
|
199
|
+
height: event.computedLayout.height,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
this.on('layout', layoutHandler);
|
|
204
|
+
this.#registeredEvents.set('layout', layoutHandler);
|
|
205
|
+
|
|
191
206
|
if (this.onAfterMount) {
|
|
192
207
|
await this.onAfterMount();
|
|
193
208
|
}
|
|
@@ -466,12 +481,36 @@ export function DisplayObject(extendClass) {
|
|
|
466
481
|
}
|
|
467
482
|
}
|
|
468
483
|
|
|
469
|
-
getWidth() {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
484
|
+
getWidth(): number {
|
|
485
|
+
// If width is a percentage, use computed layout box
|
|
486
|
+
if (isPercent(this.fullProps.width)) {
|
|
487
|
+
if (this.#computedLayoutBox?.width !== undefined) {
|
|
488
|
+
return this.#computedLayoutBox.width;
|
|
489
|
+
}
|
|
490
|
+
// Fallback to native width if layout not yet computed
|
|
491
|
+
return typeof this.width === 'number' ? this.width : 0;
|
|
492
|
+
}
|
|
493
|
+
// For static values, use native PixiJS width or displayWidth signal
|
|
494
|
+
const staticWidth = typeof this.width === 'number' && this.width > 0
|
|
495
|
+
? this.width
|
|
496
|
+
: (typeof this.displayWidth() === 'number' ? this.displayWidth() : 0);
|
|
497
|
+
return staticWidth;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
getHeight(): number {
|
|
501
|
+
// If height is a percentage, use computed layout box
|
|
502
|
+
if (isPercent(this.fullProps.height)) {
|
|
503
|
+
if (this.#computedLayoutBox?.height !== undefined) {
|
|
504
|
+
return this.#computedLayoutBox.height;
|
|
505
|
+
}
|
|
506
|
+
// Fallback to native height if layout not yet computed
|
|
507
|
+
return typeof this.height === 'number' ? this.height : 0;
|
|
508
|
+
}
|
|
509
|
+
// For static values, use native PixiJS height or displayHeight signal
|
|
510
|
+
const staticHeight = typeof this.height === 'number' && this.height > 0
|
|
511
|
+
? this.height
|
|
512
|
+
: (typeof this.displayHeight() === 'number' ? this.displayHeight() : 0);
|
|
513
|
+
return staticHeight;
|
|
475
514
|
}
|
|
476
515
|
|
|
477
516
|
// Min/Max constraints
|
|
@@ -2,6 +2,8 @@ import * as PIXI from "pixi.js";
|
|
|
2
2
|
import { SignalOrPrimitive } from ".";
|
|
3
3
|
import { DragProps } from "../../directives/Drag";
|
|
4
4
|
import { ViewportFollowProps } from "../../directives/ViewportFollow";
|
|
5
|
+
import { ShakeProps } from "../../directives/Shake";
|
|
6
|
+
import { FlashProps } from "../../directives/Flash";
|
|
5
7
|
|
|
6
8
|
export type FlexDirection = 'row' | 'column' | 'row-reverse' | 'column-reverse';
|
|
7
9
|
export type JustifyContent = 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around';
|
|
@@ -64,6 +66,8 @@ export interface DisplayObjectProps {
|
|
|
64
66
|
// Directives
|
|
65
67
|
drag?: DragProps;
|
|
66
68
|
viewportFollow?: ViewportFollowProps;
|
|
69
|
+
shake?: ShakeProps;
|
|
70
|
+
flash?: FlashProps;
|
|
67
71
|
|
|
68
72
|
// Events
|
|
69
73
|
click?: PIXI.FederatedEventHandler;
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import { Container } from 'pixi.js';
|
|
2
|
+
import { Directive, registerDirective } from '../engine/directive';
|
|
3
|
+
import { Element } from '../engine/reactive';
|
|
4
|
+
import { effect, isSignal } from '@signe/reactive';
|
|
5
|
+
import { on, isTrigger, Trigger } from '../engine/trigger';
|
|
6
|
+
import { useProps } from '../hooks/useProps';
|
|
7
|
+
import { SignalOrPrimitive } from '../components/types';
|
|
8
|
+
import { animatedSignal, AnimatedSignal } from '../engine/animation';
|
|
9
|
+
import { Subscription } from 'rxjs';
|
|
10
|
+
|
|
11
|
+
export type FlashType = 'alpha' | 'tint' | 'both';
|
|
12
|
+
|
|
13
|
+
export type FlashProps = {
|
|
14
|
+
/**
|
|
15
|
+
* Trigger that activates the flash animation
|
|
16
|
+
* When the trigger is activated, the flash animation will start
|
|
17
|
+
*/
|
|
18
|
+
trigger?: Trigger<any>;
|
|
19
|
+
/**
|
|
20
|
+
* Type of flash effect: 'alpha' (opacity), 'tint' (color), or 'both'
|
|
21
|
+
* @default 'alpha'
|
|
22
|
+
*/
|
|
23
|
+
type?: SignalOrPrimitive<FlashType>;
|
|
24
|
+
/**
|
|
25
|
+
* Duration of the flash animation in milliseconds
|
|
26
|
+
* @default 300
|
|
27
|
+
*/
|
|
28
|
+
duration?: SignalOrPrimitive<number>;
|
|
29
|
+
/**
|
|
30
|
+
* Number of flash cycles (flash on/off)
|
|
31
|
+
* @default 1
|
|
32
|
+
*/
|
|
33
|
+
cycles?: SignalOrPrimitive<number>;
|
|
34
|
+
/**
|
|
35
|
+
* Alpha value when flashing (0 to 1)
|
|
36
|
+
* Only used when type is 'alpha' or 'both'
|
|
37
|
+
* @default 0.3
|
|
38
|
+
*/
|
|
39
|
+
alpha?: SignalOrPrimitive<number>;
|
|
40
|
+
/**
|
|
41
|
+
* Tint color when flashing (hex color value)
|
|
42
|
+
* Only used when type is 'tint' or 'both'
|
|
43
|
+
* @default 0xffffff (white)
|
|
44
|
+
*/
|
|
45
|
+
tint?: SignalOrPrimitive<number>;
|
|
46
|
+
/**
|
|
47
|
+
* Original alpha value to restore after flash
|
|
48
|
+
* If not provided, uses the current alpha value
|
|
49
|
+
*/
|
|
50
|
+
originalAlpha?: number;
|
|
51
|
+
/**
|
|
52
|
+
* Original tint value to restore after flash
|
|
53
|
+
* If not provided, uses the current tint value
|
|
54
|
+
*/
|
|
55
|
+
originalTint?: number;
|
|
56
|
+
/**
|
|
57
|
+
* Callback function called when flash starts
|
|
58
|
+
*/
|
|
59
|
+
onStart?: () => void;
|
|
60
|
+
/**
|
|
61
|
+
* Callback function called when flash completes
|
|
62
|
+
*/
|
|
63
|
+
onComplete?: () => void;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Flash directive that animates a display object's alpha and/or tint when a trigger is activated.
|
|
68
|
+
* Creates a flash effect by rapidly changing opacity or color.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* // Basic usage with trigger
|
|
73
|
+
* const flashTrigger = trigger();
|
|
74
|
+
*
|
|
75
|
+
* onMount(element) {
|
|
76
|
+
* // Element will flash when trigger is activated
|
|
77
|
+
* element.props.flash = { trigger: flashTrigger };
|
|
78
|
+
* }
|
|
79
|
+
*
|
|
80
|
+
* // Trigger the flash
|
|
81
|
+
* flashTrigger.start();
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export class Flash extends Directive {
|
|
85
|
+
private elementRef: Element<Container> | null = null;
|
|
86
|
+
private progressSignal: AnimatedSignal<number> | null = null;
|
|
87
|
+
private flashSubscription: any = null;
|
|
88
|
+
private alphaEffect: Subscription | null = null;
|
|
89
|
+
private tintEffect: Subscription | null = null;
|
|
90
|
+
private originalAlpha: number = 1;
|
|
91
|
+
private originalTint: number = 0xffffff;
|
|
92
|
+
private currentFlashConfig: {
|
|
93
|
+
type: FlashType;
|
|
94
|
+
duration: number;
|
|
95
|
+
cycles: number;
|
|
96
|
+
flashAlpha: number;
|
|
97
|
+
flashTint: number;
|
|
98
|
+
} | null = null;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Initializes the flash directive
|
|
102
|
+
* @param element - The element to attach the flash effect to
|
|
103
|
+
*/
|
|
104
|
+
onInit(element: Element<Container>) {
|
|
105
|
+
this.elementRef = element;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Mounts the flash directive and sets up trigger listener
|
|
110
|
+
* @param element - The element being mounted
|
|
111
|
+
*/
|
|
112
|
+
onMount(element: Element<Container>) {
|
|
113
|
+
const instance = element.componentInstance;
|
|
114
|
+
if (!instance) return;
|
|
115
|
+
|
|
116
|
+
const flashProps = this.flashProps;
|
|
117
|
+
|
|
118
|
+
// Check if trigger is provided
|
|
119
|
+
if (!flashProps.trigger || !isTrigger(flashProps.trigger)) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Store original values once at mount time
|
|
124
|
+
// Only set if not already stored (to preserve values from first mount)
|
|
125
|
+
if (this.originalAlpha === 1 && !flashProps.originalAlpha) {
|
|
126
|
+
this.originalAlpha = instance.alpha ?? 1;
|
|
127
|
+
} else if (flashProps.originalAlpha !== undefined) {
|
|
128
|
+
this.originalAlpha = flashProps.originalAlpha;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const currentTint = (instance as any).tint;
|
|
132
|
+
if (this.originalTint === 0xffffff && !flashProps.originalTint) {
|
|
133
|
+
this.originalTint = (isSignal(currentTint) ? currentTint() : currentTint) ?? 0xffffff;
|
|
134
|
+
} else if (flashProps.originalTint !== undefined) {
|
|
135
|
+
this.originalTint = flashProps.originalTint;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Listen to trigger activation
|
|
139
|
+
this.flashSubscription = on(flashProps.trigger, async (data) => {
|
|
140
|
+
await this.performFlash(data);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Gets the flash props with default values
|
|
146
|
+
* @returns FlashProps with defaults applied
|
|
147
|
+
*/
|
|
148
|
+
get flashProps(): FlashProps {
|
|
149
|
+
const flash = this.elementRef?.props.flash;
|
|
150
|
+
return useProps(flash?.value ?? flash, {
|
|
151
|
+
type: 'alpha',
|
|
152
|
+
duration: 300,
|
|
153
|
+
cycles: 1,
|
|
154
|
+
alpha: 0.3,
|
|
155
|
+
tint: 0xffffff,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Performs the flash animation using animatedSignal
|
|
161
|
+
* @param data - Optional data passed from the trigger that can override default options
|
|
162
|
+
*/
|
|
163
|
+
private async performFlash(data?: any): Promise<void> {
|
|
164
|
+
if (!this.elementRef?.componentInstance) return;
|
|
165
|
+
|
|
166
|
+
const instance = this.elementRef.componentInstance;
|
|
167
|
+
const flashProps = this.flashProps;
|
|
168
|
+
|
|
169
|
+
// Use data from trigger to override defaults if provided
|
|
170
|
+
const type = data?.type ?? (typeof flashProps.type === 'function' ? flashProps.type() : flashProps.type);
|
|
171
|
+
const duration = data?.duration ?? (typeof flashProps.duration === 'function' ? flashProps.duration() : flashProps.duration);
|
|
172
|
+
const cycles = data?.cycles ?? (typeof flashProps.cycles === 'function' ? flashProps.cycles() : flashProps.cycles);
|
|
173
|
+
const flashAlpha = data?.alpha ?? (typeof flashProps.alpha === 'function' ? flashProps.alpha() : flashProps.alpha);
|
|
174
|
+
const flashTint = data?.tint ?? (typeof flashProps.tint === 'function' ? flashProps.tint() : flashProps.tint);
|
|
175
|
+
|
|
176
|
+
// Stop any existing animation first
|
|
177
|
+
if (this.progressSignal) {
|
|
178
|
+
// Stop the animation immediately
|
|
179
|
+
this.progressSignal.set(0, { duration: 0 });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Clean up effects BEFORE restoring values
|
|
183
|
+
// This prevents effects from continuing to update values after we restore
|
|
184
|
+
if (this.alphaEffect) {
|
|
185
|
+
this.alphaEffect.unsubscribe();
|
|
186
|
+
this.alphaEffect = null;
|
|
187
|
+
}
|
|
188
|
+
if (this.tintEffect) {
|
|
189
|
+
this.tintEffect.unsubscribe();
|
|
190
|
+
this.tintEffect = null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Always restore to original values immediately after stopping effects
|
|
194
|
+
// This ensures that if a new flash starts before the previous one completes,
|
|
195
|
+
// we restore to the true original values, not the intermediate animation values
|
|
196
|
+
instance.alpha = this.originalAlpha;
|
|
197
|
+
const currentTint = (instance as any).tint;
|
|
198
|
+
if (currentTint !== undefined) {
|
|
199
|
+
// Ensure originalTint is a primitive value, not a signal
|
|
200
|
+
const tintValue = typeof this.originalTint === 'number' ? this.originalTint : 0xffffff;
|
|
201
|
+
// Handle both signal and primitive tint
|
|
202
|
+
if (isSignal(currentTint)) {
|
|
203
|
+
currentTint.set(tintValue);
|
|
204
|
+
} else {
|
|
205
|
+
(instance as any).tint = tintValue;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Call onStart callback
|
|
210
|
+
flashProps.onStart?.();
|
|
211
|
+
|
|
212
|
+
// Store current flash configuration for use in effect
|
|
213
|
+
this.currentFlashConfig = {
|
|
214
|
+
type,
|
|
215
|
+
duration,
|
|
216
|
+
cycles,
|
|
217
|
+
flashAlpha,
|
|
218
|
+
flashTint,
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// Create or recreate progress signal for flash animation
|
|
222
|
+
// Note: We already stopped the previous animation above, so we can reuse the signal
|
|
223
|
+
if (!this.progressSignal) {
|
|
224
|
+
this.progressSignal = animatedSignal(0, {
|
|
225
|
+
duration: duration,
|
|
226
|
+
ease: (t) => t, // Linear ease
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
// Reset to 0 immediately without animation to start fresh
|
|
230
|
+
this.progressSignal.set(0, { duration: 0 });
|
|
231
|
+
// Wait a bit to ensure the reset is complete before starting new animation
|
|
232
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
233
|
+
|
|
234
|
+
// Create effect to update alpha based on progress
|
|
235
|
+
if (type === 'alpha' || type === 'both') {
|
|
236
|
+
this.alphaEffect = effect(() => {
|
|
237
|
+
if (!instance || !this.progressSignal || !this.currentFlashConfig) return;
|
|
238
|
+
|
|
239
|
+
const progress = this.progressSignal();
|
|
240
|
+
const config = this.currentFlashConfig;
|
|
241
|
+
|
|
242
|
+
// Calculate flash value based on cycles
|
|
243
|
+
// Each cycle goes from 0 to 1, so we use modulo to repeat
|
|
244
|
+
const cycleProgress = (progress * config.cycles) % 1;
|
|
245
|
+
|
|
246
|
+
// Create flash effect: fade to flashAlpha then back to original
|
|
247
|
+
// For each cycle, we flash twice (on/off)
|
|
248
|
+
const flashPhase = cycleProgress < 0.5
|
|
249
|
+
? cycleProgress * 2 // Fade to flashAlpha (0 to 1)
|
|
250
|
+
: 1 - ((cycleProgress - 0.5) * 2); // Fade back to original (1 to 0)
|
|
251
|
+
|
|
252
|
+
// Interpolate between original and flash alpha
|
|
253
|
+
const currentAlpha = this.originalAlpha + (config.flashAlpha - this.originalAlpha) * flashPhase;
|
|
254
|
+
instance.alpha = currentAlpha;
|
|
255
|
+
}).subscription;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Create effect to update tint based on progress
|
|
259
|
+
if (type === 'tint' || type === 'both') {
|
|
260
|
+
this.tintEffect = effect(() => {
|
|
261
|
+
if (!instance || !this.progressSignal || !this.currentFlashConfig) return;
|
|
262
|
+
|
|
263
|
+
// Get current tint value - handle both signal and primitive
|
|
264
|
+
const currentTint = (instance as any).tint;
|
|
265
|
+
if (currentTint === undefined) return;
|
|
266
|
+
|
|
267
|
+
// Check if tint is a signal
|
|
268
|
+
const tintIsSignal = isSignal(currentTint);
|
|
269
|
+
|
|
270
|
+
const progress = this.progressSignal();
|
|
271
|
+
const config = this.currentFlashConfig;
|
|
272
|
+
|
|
273
|
+
// Calculate flash value based on cycles
|
|
274
|
+
const cycleProgress = (progress * config.cycles) % 1;
|
|
275
|
+
|
|
276
|
+
// Create flash effect: change to flashTint then back to original
|
|
277
|
+
const flashPhase = cycleProgress < 0.5
|
|
278
|
+
? cycleProgress * 2 // Change to flashTint (0 to 1)
|
|
279
|
+
: 1 - ((cycleProgress - 0.5) * 2); // Change back to original (1 to 0)
|
|
280
|
+
|
|
281
|
+
// Interpolate between original and flash tint
|
|
282
|
+
// Simple linear interpolation for RGB values
|
|
283
|
+
const r1 = (this.originalTint >> 16) & 0xff;
|
|
284
|
+
const g1 = (this.originalTint >> 8) & 0xff;
|
|
285
|
+
const b1 = this.originalTint & 0xff;
|
|
286
|
+
|
|
287
|
+
const r2 = (config.flashTint >> 16) & 0xff;
|
|
288
|
+
const g2 = (config.flashTint >> 8) & 0xff;
|
|
289
|
+
const b2 = config.flashTint & 0xff;
|
|
290
|
+
|
|
291
|
+
const r = Math.round(r1 + (r2 - r1) * flashPhase);
|
|
292
|
+
const g = Math.round(g1 + (g2 - g1) * flashPhase);
|
|
293
|
+
const b = Math.round(b1 + (b2 - b1) * flashPhase);
|
|
294
|
+
|
|
295
|
+
const newTintValue = (r << 16) | (g << 8) | b;
|
|
296
|
+
|
|
297
|
+
// Handle both signal and primitive tint
|
|
298
|
+
if (tintIsSignal) {
|
|
299
|
+
currentTint.set(newTintValue);
|
|
300
|
+
} else {
|
|
301
|
+
(instance as any).tint = newTintValue;
|
|
302
|
+
}
|
|
303
|
+
}).subscription;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Start animation and wait for completion
|
|
307
|
+
await this.progressSignal.set(1, {
|
|
308
|
+
duration: duration,
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Animation completed - clean up and call callbacks
|
|
312
|
+
// Restore original values
|
|
313
|
+
if (instance) {
|
|
314
|
+
instance.alpha = this.originalAlpha;
|
|
315
|
+
const currentTint = (instance as any).tint;
|
|
316
|
+
if (currentTint !== undefined) {
|
|
317
|
+
// Ensure originalTint is a primitive value, not a signal
|
|
318
|
+
const tintValue = typeof this.originalTint === 'number' ? this.originalTint : 0xffffff;
|
|
319
|
+
// Handle both signal and primitive tint
|
|
320
|
+
if (isSignal(currentTint)) {
|
|
321
|
+
currentTint.set(tintValue);
|
|
322
|
+
} else {
|
|
323
|
+
(instance as any).tint = tintValue;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Clean up effects
|
|
329
|
+
if (this.alphaEffect) {
|
|
330
|
+
this.alphaEffect.unsubscribe();
|
|
331
|
+
this.alphaEffect = null;
|
|
332
|
+
}
|
|
333
|
+
if (this.tintEffect) {
|
|
334
|
+
this.tintEffect.unsubscribe();
|
|
335
|
+
this.tintEffect = null;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Clear flash config
|
|
339
|
+
this.currentFlashConfig = null;
|
|
340
|
+
|
|
341
|
+
// Call onComplete callback
|
|
342
|
+
flashProps.onComplete?.();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Updates the flash directive when props change
|
|
347
|
+
* @param props - Updated props
|
|
348
|
+
*/
|
|
349
|
+
onUpdate(props: any) {
|
|
350
|
+
// Re-mount if props change significantly
|
|
351
|
+
if (props.type && props.type === 'reset') {
|
|
352
|
+
this.onDestroy();
|
|
353
|
+
if (this.elementRef) {
|
|
354
|
+
this.onMount(this.elementRef);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Cleans up the flash directive
|
|
361
|
+
*/
|
|
362
|
+
onDestroy() {
|
|
363
|
+
// Stop any running animation by resetting progress
|
|
364
|
+
if (this.progressSignal) {
|
|
365
|
+
this.progressSignal.set(0, { duration: 0 });
|
|
366
|
+
this.progressSignal = null;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Clean up effects
|
|
370
|
+
if (this.alphaEffect) {
|
|
371
|
+
this.alphaEffect.unsubscribe();
|
|
372
|
+
this.alphaEffect = null;
|
|
373
|
+
}
|
|
374
|
+
if (this.tintEffect) {
|
|
375
|
+
this.tintEffect.unsubscribe();
|
|
376
|
+
this.tintEffect = null;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Clear flash config
|
|
380
|
+
this.currentFlashConfig = null;
|
|
381
|
+
|
|
382
|
+
// Restore original values
|
|
383
|
+
if (this.elementRef?.componentInstance) {
|
|
384
|
+
const instance = this.elementRef.componentInstance;
|
|
385
|
+
instance.alpha = this.originalAlpha;
|
|
386
|
+
const currentTint = (instance as any).tint;
|
|
387
|
+
if (currentTint !== undefined) {
|
|
388
|
+
// Ensure originalTint is a primitive value, not a signal
|
|
389
|
+
const tintValue = typeof this.originalTint === 'number' ? this.originalTint : 0xffffff;
|
|
390
|
+
// Handle both signal and primitive tint
|
|
391
|
+
if (isSignal(currentTint)) {
|
|
392
|
+
currentTint.set(tintValue);
|
|
393
|
+
} else {
|
|
394
|
+
(instance as any).tint = tintValue;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Clean up subscription
|
|
400
|
+
if (this.flashSubscription) {
|
|
401
|
+
this.flashSubscription = null;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
this.elementRef = null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
registerDirective('flash', Flash);
|
|
409
|
+
|