canvasengine 2.0.0-beta.40 → 2.0.0-beta.42

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.js CHANGED
@@ -1,5 +1,5 @@
1
- import { h as a } from "./index-gb763Hyx.js";
2
- import { A as r, _ as o, $ as n, t as l, x as c, v as p, C as S, d as m, Y as u, Z as d, an as g, f as b, D as C, am as k, ak as h, y as j, j as T, G as w, w as D, c as E, I as f, a0 as v, J as y, K as O, M as P, X as V, O as x, P as A, al as B, R as G, H, S as M, g as F, e as J, L as N, B as R, Q as q, U as z, T as I, z as K, b as U, N as L, W as Q, V as W, aj as X, ai as Y, ag as Z, k as _, a7 as $, a5 as aa, a8 as sa, l as ea, ac as ta, ah as ia, m as ra, n as oa, a1 as na, a4 as la, o as ca, i as pa, a2 as Sa, p as ma, ad as ua, q as da, a6 as ga, aa as ba, a9 as Ca, af as ka, a3 as ha, s as ja, ab as Ta, ae as wa, r as Da, a as Ea, u as fa } from "./index-gb763Hyx.js";
1
+ import { h as a } from "./index-DNwqVzaq.js";
2
+ import { A as r, $ as o, a0 as n, v as l, y as c, w as p, C as S, d as m, Z as u, _ as g, ao as d, f as C, D as b, an as k, al as h, z as j, j as T, G as w, x as D, c as E, I as f, a1 as v, J as y, K as O, M as A, Y as P, O as V, P as x, am as B, R as G, L as H, S as M, g as F, e as J, N, H as R, U as q, W as z, T as I, B as K, b as U, Q as L, X as Q, V as W, ak as X, aj as Y, ah as Z, k as _, a8 as $, a6 as aa, a9 as sa, l as ea, ad as ta, ai as ia, m as ra, n as oa, a2 as na, a5 as la, o as ca, i as pa, a3 as Sa, p as ma, ae as ua, q as ga, a7 as da, ab as Ca, aa as ba, ag as ka, r as ha, a4 as ja, s as Ta, ac as wa, af as Da, t as Ea, a as fa, u as va } from "./index-DNwqVzaq.js";
3
3
  const e = a.Howler;
4
4
  export {
5
5
  r as ArraySubject,
@@ -11,10 +11,10 @@ export {
11
11
  S as ControlsBase,
12
12
  m as ControlsDirective,
13
13
  u as DOMContainer,
14
- d as DOMElement,
15
- g as DisplayObject,
16
- b as Drag,
17
- C as Drop,
14
+ g as DOMElement,
15
+ d as DisplayObject,
16
+ C as Drag,
17
+ b as Drop,
18
18
  k as EVENTS,
19
19
  h as Easing,
20
20
  j as Ellipse,
@@ -27,10 +27,10 @@ export {
27
27
  v as Joystick,
28
28
  y as JoystickControls,
29
29
  O as KeyboardControls,
30
- P as Mesh,
31
- V as NineSliceSprite,
32
- x as ObjectSubject,
33
- A as ParticlesEmitter,
30
+ A as Mesh,
31
+ P as NineSliceSprite,
32
+ V as ObjectSubject,
33
+ x as ParticlesEmitter,
34
34
  B as RadialGradient,
35
35
  G as Rect,
36
36
  H as Scene,
@@ -66,17 +66,18 @@ export {
66
66
  Sa as isPrimitive,
67
67
  ma as isSignal,
68
68
  ua as isTrigger,
69
- da as linkedSignal,
70
- ga as loop,
71
- ba as mount,
72
- Ca as mountTracker,
69
+ ga as linkedSignal,
70
+ da as loop,
71
+ Ca as mount,
72
+ ba as mountTracker,
73
73
  ka as on,
74
- ha as registerComponent,
75
- ja as signal,
76
- Ta as tick,
77
- wa as trigger,
78
- Da as untracked,
79
- Ea as useDefineProps,
80
- fa as useProps
74
+ ha as registerAllComponents,
75
+ ja as registerComponent,
76
+ Ta as signal,
77
+ wa as tick,
78
+ Da as trigger,
79
+ Ea as untracked,
80
+ fa as useDefineProps,
81
+ va as useProps
81
82
  };
82
83
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canvasengine",
3
- "version": "2.0.0-beta.40",
3
+ "version": "2.0.0-beta.42",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -100,7 +100,8 @@ export function Joystick(opts: JoystickSettings = {}) {
100
100
  }
101
101
 
102
102
  function handleDragStart(event: any) {
103
- startPosition = event.getLocalPosition(this);
103
+ const target = event.currentTarget || event.target;
104
+ startPosition = event.getLocalPosition(target);
104
105
  dragging = true;
105
106
  innerAlpha.set(1);
106
107
  settings.onStart?.();
@@ -146,7 +147,8 @@ export function Joystick(opts: JoystickSettings = {}) {
146
147
  return;
147
148
  }
148
149
 
149
- let newPosition = event.getLocalPosition(this);
150
+ const target = event.currentTarget || event.target;
151
+ let newPosition = event.getLocalPosition(target);
150
152
 
151
153
  let sideX = newPosition.x - (startPosition?.x ?? 0);
152
154
  let sideY = newPosition.y - (startPosition?.y ?? 0);
@@ -173,14 +173,8 @@ export class Flash extends Directive {
173
173
  const flashAlpha = data?.alpha ?? (typeof flashProps.alpha === 'function' ? flashProps.alpha() : flashProps.alpha);
174
174
  const flashTint = data?.tint ?? (typeof flashProps.tint === 'function' ? flashProps.tint() : flashProps.tint);
175
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
176
+ // Clean up effects BEFORE stopping animation
177
+ // This prevents effects from continuing to update values
184
178
  if (this.alphaEffect) {
185
179
  this.alphaEffect.unsubscribe();
186
180
  this.alphaEffect = null;
@@ -190,33 +184,44 @@ export class Flash extends Directive {
190
184
  this.tintEffect = null;
191
185
  }
192
186
 
187
+ // DO NOT update original values here if a flash is in progress
188
+ // Only restore to the already-stored original values to avoid overwriting
189
+ // with intermediate animation values when multiple flashes trigger quickly
190
+
193
191
  // Always restore to original values immediately after stopping effects
194
192
  // This ensures that if a new flash starts before the previous one completes,
195
193
  // we restore to the true original values, not the intermediate animation values
196
194
  instance.alpha = this.originalAlpha;
197
- const currentTint = (instance as any).tint;
198
- if (currentTint !== undefined) {
195
+ const currentInstanceTint = (instance as any).tint;
196
+ if (currentInstanceTint !== undefined) {
199
197
  // Ensure originalTint is a primitive value, not a signal
200
198
  const tintValue = typeof this.originalTint === 'number' ? this.originalTint : 0xffffff;
201
199
  // Handle both signal and primitive tint
202
- if (isSignal(currentTint)) {
203
- currentTint.set(tintValue);
200
+ if (isSignal(currentInstanceTint)) {
201
+ currentInstanceTint.set(tintValue);
204
202
  } else {
205
203
  (instance as any).tint = tintValue;
206
204
  }
207
205
  }
208
206
 
209
- // Call onStart callback
207
+ // Call onStart callback early, before async operations
210
208
  flashProps.onStart?.();
211
209
 
210
+ // Stop any existing animation after cleanup and callback
211
+ if (this.progressSignal) {
212
+ // Stop the animation immediately and wait for completion
213
+ await this.progressSignal.set(0, { duration: 0 });
214
+ }
215
+
212
216
  // Store current flash configuration for use in effect
213
- this.currentFlashConfig = {
217
+ const flashConfig = {
214
218
  type,
215
219
  duration,
216
220
  cycles,
217
221
  flashAlpha,
218
222
  flashTint,
219
223
  };
224
+ this.currentFlashConfig = flashConfig;
220
225
 
221
226
  // Create or recreate progress signal for flash animation
222
227
  // Note: We already stopped the previous animation above, so we can reuse the signal
@@ -226,10 +231,11 @@ export class Flash extends Directive {
226
231
  ease: (t) => t, // Linear ease
227
232
  });
228
233
  }
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));
234
+ // Signal is already reset to 0 above if it existed, no need to reset again
235
+
236
+ // Store references to the effects we're creating to verify they're still active later
237
+ const expectedAlphaEffect = type === 'alpha' || type === 'both';
238
+ const expectedTintEffect = type === 'tint' || type === 'both';
233
239
 
234
240
  // Create effect to update alpha based on progress
235
241
  if (type === 'alpha' || type === 'both') {
@@ -309,8 +315,12 @@ export class Flash extends Directive {
309
315
  });
310
316
 
311
317
  // Animation completed - clean up and call callbacks
312
- // Restore original values
313
- if (instance) {
318
+ // Only restore and clean up if this flash is still the active one
319
+ // If currentFlashConfig has changed, it means a new flash has started
320
+ const isStillActive = this.currentFlashConfig === flashConfig;
321
+
322
+ if (isStillActive && instance) {
323
+ // Restore original values
314
324
  instance.alpha = this.originalAlpha;
315
325
  const currentTint = (instance as any).tint;
316
326
  if (currentTint !== undefined) {
@@ -323,21 +333,21 @@ export class Flash extends Directive {
323
333
  (instance as any).tint = tintValue;
324
334
  }
325
335
  }
336
+
337
+ // Clean up effects
338
+ if (this.alphaEffect) {
339
+ this.alphaEffect.unsubscribe();
340
+ this.alphaEffect = null;
341
+ }
342
+ if (this.tintEffect) {
343
+ this.tintEffect.unsubscribe();
344
+ this.tintEffect = null;
345
+ }
346
+
347
+ // Clear flash config
348
+ this.currentFlashConfig = null;
326
349
  }
327
350
 
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
351
  // Call onComplete callback
342
352
  flashProps.onComplete?.();
343
353
  }
@@ -102,6 +102,12 @@ export class Shake extends Directive {
102
102
  // Store original position
103
103
  this.originalPosition.set(instance.position.x, instance.position.y);
104
104
 
105
+ // Clean up previous subscription if it exists
106
+ if (this.shakeSubscription) {
107
+ this.shakeSubscription.unsubscribe();
108
+ this.shakeSubscription = null;
109
+ }
110
+
105
111
  // Listen to trigger activation
106
112
  this.shakeSubscription = on(shakeProps.trigger, async (data) => {
107
113
  await this.performShake(data);
@@ -144,8 +150,16 @@ export class Shake extends Directive {
144
150
  this.positionEffect = null;
145
151
  }
146
152
 
153
+ // Only update originalPosition if it hasn't been properly initialized (still at 0,0 and no parent was present when saved)
154
+ // OR if the current position is different from the stored original (element was moved)
155
+ const isOriginalUninitialized = this.originalPosition.x === 0 && this.originalPosition.y === 0 && instance.parent;
156
+ const hasPositionChanged = instance.position.x !== this.originalPosition.x || instance.position.y !== this.originalPosition.y;
157
+
158
+ if (isOriginalUninitialized || hasPositionChanged) {
159
+ this.originalPosition.set(instance.position.x, instance.position.y);
160
+ }
161
+
147
162
  // Reset position to original before starting new shake
148
- this.originalPosition.set(instance.position.x, instance.position.y);
149
163
  instance.position.x = this.originalPosition.x;
150
164
  instance.position.y = this.originalPosition.y;
151
165
 
@@ -163,10 +177,8 @@ export class Shake extends Directive {
163
177
  // Create or recreate progress signal for shake animation
164
178
  // We recreate it to ensure a fresh animation state
165
179
  if (this.progressSignal) {
166
- // Reset to 0 immediately without animation
167
- this.progressSignal.set(0, { duration: 0 });
168
- // Wait a bit to ensure the reset is complete
169
- await new Promise(resolve => setTimeout(resolve, 0));
180
+ // Reset to 0 immediately without animation and wait for completion
181
+ await this.progressSignal.set(0, { duration: 0 });
170
182
  } else {
171
183
  this.progressSignal = animatedSignal(0, {
172
184
  duration: duration,
@@ -271,6 +283,7 @@ export class Shake extends Directive {
271
283
 
272
284
  // Clean up subscription
273
285
  if (this.shakeSubscription) {
286
+ this.shakeSubscription.unsubscribe();
274
287
  this.shakeSubscription = null;
275
288
  }
276
289
 
@@ -2,23 +2,90 @@ import '@pixi/layout';
2
2
  import { Application, ApplicationOptions } from "pixi.js";
3
3
  import { ComponentFunction, h } from "./signal";
4
4
  import { useProps } from '../hooks/useProps';
5
+ import { registerAllComponents, registerComponent } from './reactive';
6
+
7
+ // Import all components to ensure they are registered
8
+ // This is done here (not in reactive.ts) to avoid circular dependencies
9
+ // Components register themselves when their modules are imported
10
+ import '../components/Canvas';
11
+ import '../components/Container';
12
+ import '../components/Sprite';
13
+ import '../components/Text';
14
+ import '../components/Graphic';
15
+ import '../components/Mesh';
16
+ import '../components/Viewport';
17
+ import '../components/TilingSprite';
18
+ import '../components/NineSliceSprite';
19
+ import '../components/DOMContainer';
20
+ import '../components/DOMElement';
21
+ import '../components/ParticleEmitter';
22
+
23
+ /**
24
+ * Extended options for bootstrapCanvas that includes component registration configuration.
25
+ *
26
+ * @property components - Optional mapping of component names to their classes (can include mocks for testing)
27
+ * @property autoRegister - If true (default), registers all default components before applying custom components. If false, only registers the specified components.
28
+ */
29
+ export interface BootstrapOptions extends ApplicationOptions {
30
+ components?: {
31
+ [name: string]: any; // ComponentClass
32
+ };
33
+ autoRegister?: boolean; // true by default if components is not provided
34
+ }
5
35
 
6
36
  /**
7
37
  * Bootstraps a canvas element and renders it to the DOM.
8
38
  *
9
39
  * @param rootElement - The HTML element where the canvas will be rendered. Can be null.
10
40
  * @param canvas - A Promise that resolves to an Element representing the canvas component.
41
+ * @param options - Optional bootstrap options including ApplicationOptions and component registration configuration.
11
42
  * @returns A Promise that resolves to the rendered canvas element.
12
43
  * @throws {Error} If the provided element is not a Canvas component.
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * // Default: all components registered automatically
48
+ * await bootstrapCanvas(rootElement, MyComponent, {
49
+ * width: 800,
50
+ * height: 600
51
+ * });
52
+ * ```
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * // With mocks for testing
57
+ * import { mockComponents } from '@canvasengine/testing';
58
+ * await bootstrapCanvas(rootElement, MyComponent, {
59
+ * components: mockComponents,
60
+ * autoRegister: false
61
+ * });
62
+ * ```
13
63
  */
14
- export const bootstrapCanvas = async (rootElement: HTMLElement | null, canvas: ComponentFunction<any>, options?: ApplicationOptions) => {
64
+ export const bootstrapCanvas = async (rootElement: HTMLElement | null, canvas: ComponentFunction<any>, options?: BootstrapOptions) => {
65
+ // Extract component registration options
66
+ const { components, autoRegister, ...appOptions } = options ?? {};
67
+
68
+ // Handle component registration
69
+ if (components) {
70
+ if (autoRegister !== false) {
71
+ // Register all default components first, then override with custom ones
72
+ registerAllComponents();
73
+ }
74
+ // Register the specified components (overriding defaults if autoRegister is true)
75
+ Object.entries(components).forEach(([name, componentClass]) => {
76
+ registerComponent(name, componentClass);
77
+ });
78
+ } else {
79
+ // Default behavior: register all components
80
+ registerAllComponents();
81
+ }
15
82
 
16
83
  const app = new Application();
17
84
  await app.init({
18
85
  resizeTo: rootElement,
19
86
  autoStart: false,
20
87
  antialias: true,
21
- ...(options ?? {})
88
+ ...appOptions
22
89
  });
23
90
  const canvasElement = await h(canvas);
24
91
  if (canvasElement.tag != 'Canvas') {
@@ -84,6 +84,41 @@ export function registerComponent(name, component) {
84
84
  components[name] = component;
85
85
  }
86
86
 
87
+ // Track if components have been registered to avoid duplicate imports
88
+ let componentsRegistered = false;
89
+
90
+ /**
91
+ * Registers all default CanvasEngine components.
92
+ *
93
+ * This function imports and registers all core components that are available by default.
94
+ * It's called automatically by bootstrapCanvas() if no custom component configuration is provided.
95
+ *
96
+ * Components register themselves when their modules are imported, so this function ensures
97
+ * all component modules are loaded. Since components call registerComponent() at module load time,
98
+ * importing them will automatically register them synchronously.
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * // Register all default components manually
103
+ * registerAllComponents();
104
+ *
105
+ * // Now you can use any component
106
+ * const sprite = createComponent('Sprite', { image: 'hero.png' });
107
+ * ```
108
+ */
109
+ export function registerAllComponents() {
110
+ if (componentsRegistered) {
111
+ return;
112
+ }
113
+
114
+ // Components are registered when their modules are imported
115
+ // Since bootstrap.ts imports all components, they should already be registered
116
+ // when bootstrapCanvas() is called. This function just marks that registration
117
+ // has been attempted. If components aren't registered yet, they will be when
118
+ // bootstrap.ts imports them (which happens before bootstrapCanvas() is called).
119
+ componentsRegistered = true;
120
+ }
121
+
87
122
  /**
88
123
  * Checks if an element is currently frozen.
89
124
  * An element is frozen when the `freeze` prop is set to `true` (either as a boolean or Signal<boolean>),
@@ -47,13 +47,14 @@ export function trigger<T = any>(globalConfig?: T): Trigger<T> {
47
47
  return {
48
48
  start: (config?: T) => {
49
49
  return new Promise((resolve: (value: any) => void) => {
50
+ const newValue = Math.random();
50
51
  _signal.set({
51
52
  config: {
52
53
  ...globalConfig,
53
54
  ...config,
54
55
  },
55
56
  resolve,
56
- value: Math.random(),
57
+ value: newValue,
57
58
  });
58
59
  });
59
60
  },
@@ -70,14 +71,18 @@ export function trigger<T = any>(globalConfig?: T): Trigger<T> {
70
71
  * Subscribes to a trigger and executes a callback when the trigger is activated
71
72
  * @param triggerSignal - The trigger to subscribe to
72
73
  * @param callback - Function to execute when the trigger is activated
74
+ * @returns Subscription that can be unsubscribed to stop listening
73
75
  * @throws Error if triggerSignal is not a valid trigger
74
76
  * @example
75
77
  * ```ts
76
78
  * const click = trigger()
77
79
  *
78
- * on(click, () => {
80
+ * const subscription = on(click, () => {
79
81
  * console.log('Click triggered')
80
82
  * })
83
+ *
84
+ * // Later, to stop listening:
85
+ * subscription.unsubscribe()
81
86
  * ```
82
87
  */
83
88
  export function on(triggerSignal: any, callback: (config: any) => void | Promise<void>) {
@@ -86,9 +91,10 @@ export function on(triggerSignal: any, callback: (config: any) => void | Promise
86
91
  }
87
92
  let lastValue: number | undefined;
88
93
 
89
- effect(() => {
94
+ const effectResult = effect(() => {
90
95
  const result = triggerSignal.listen();
91
96
  const seed = result?.seed;
97
+
92
98
  if (!seed) return;
93
99
 
94
100
  // Only run callback when the trigger value actually changes
@@ -96,7 +102,9 @@ export function on(triggerSignal: any, callback: (config: any) => void | Promise
96
102
  lastValue = seed.value;
97
103
  return;
98
104
  }
99
- if (seed.value === lastValue) return;
105
+ if (seed.value === lastValue) {
106
+ return;
107
+ }
100
108
  lastValue = seed.value;
101
109
 
102
110
  try {
@@ -110,4 +118,6 @@ export function on(triggerSignal: any, callback: (config: any) => void | Promise
110
118
  seed.resolve(undefined);
111
119
  }
112
120
  });
121
+
122
+ return effectResult.subscription;
113
123
  }
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ export * from '@signe/reactive'
4
4
  export { Howler } from 'howler'
5
5
  export * from './components'
6
6
  export * from './engine/reactive'
7
+ export { registerAllComponents } from './engine/reactive'
7
8
  export * from './engine/signal'
8
9
  export * from './engine/trigger'
9
10
  export * from './engine/bootstrap'