canvasengine 2.0.1-beta.1 → 2.0.1

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.
Files changed (212) hide show
  1. package/dist/DebugRenderer-DkjTAc48.js +1384 -0
  2. package/dist/DebugRenderer-DkjTAc48.js.map +1 -0
  3. package/dist/components/Button.d.ts +185 -0
  4. package/dist/components/Button.d.ts.map +1 -0
  5. package/dist/components/Canvas.d.ts +17 -0
  6. package/dist/components/Canvas.d.ts.map +1 -0
  7. package/dist/components/Container.d.ts +25 -0
  8. package/dist/components/Container.d.ts.map +1 -0
  9. package/dist/components/DOMContainer.d.ts +28 -0
  10. package/dist/components/DOMContainer.d.ts.map +1 -0
  11. package/dist/components/DOMElement.d.ts +54 -0
  12. package/dist/components/DOMElement.d.ts.map +1 -0
  13. package/dist/components/DOMSprite.d.ts +127 -0
  14. package/dist/components/DOMSprite.d.ts.map +1 -0
  15. package/dist/components/DisplayObject.d.ts +18 -0
  16. package/dist/components/DisplayObject.d.ts.map +1 -0
  17. package/dist/components/FocusContainer.d.ts +129 -0
  18. package/dist/components/FocusContainer.d.ts.map +1 -0
  19. package/dist/components/Graphic.d.ts +64 -0
  20. package/dist/components/Graphic.d.ts.map +1 -0
  21. package/dist/components/Joystick.d.ts +37 -0
  22. package/dist/components/Joystick.d.ts.map +1 -0
  23. package/dist/components/Mesh.d.ts +138 -0
  24. package/dist/components/Mesh.d.ts.map +1 -0
  25. package/dist/components/NineSliceSprite.d.ts +16 -0
  26. package/dist/components/NineSliceSprite.d.ts.map +1 -0
  27. package/dist/components/ParticleEmitter.d.ts +4 -0
  28. package/dist/components/ParticleEmitter.d.ts.map +1 -0
  29. package/dist/components/Scene.d.ts +2 -0
  30. package/dist/components/Scene.d.ts.map +1 -0
  31. package/dist/components/Sprite.d.ts +182 -0
  32. package/dist/components/Sprite.d.ts.map +1 -0
  33. package/dist/components/Text.d.ts +24 -0
  34. package/dist/components/Text.d.ts.map +1 -0
  35. package/dist/components/TilingSprite.d.ts +17 -0
  36. package/dist/components/TilingSprite.d.ts.map +1 -0
  37. package/dist/components/Video.d.ts +14 -0
  38. package/dist/components/Video.d.ts.map +1 -0
  39. package/dist/components/Viewport.d.ts +52 -0
  40. package/dist/components/Viewport.d.ts.map +1 -0
  41. package/dist/components/index.d.ts +20 -0
  42. package/dist/components/index.d.ts.map +1 -0
  43. package/dist/components/types/DisplayObject.d.ts +127 -0
  44. package/dist/components/types/DisplayObject.d.ts.map +1 -0
  45. package/dist/components/types/MouseEvent.d.ts +4 -0
  46. package/dist/components/types/MouseEvent.d.ts.map +1 -0
  47. package/dist/components/types/Spritesheet.d.ts +248 -0
  48. package/dist/components/types/Spritesheet.d.ts.map +1 -0
  49. package/dist/components/types/index.d.ts +4 -0
  50. package/dist/components/types/index.d.ts.map +1 -0
  51. package/dist/directives/Controls.d.ts +112 -0
  52. package/dist/directives/Controls.d.ts.map +1 -0
  53. package/dist/directives/ControlsBase.d.ts +199 -0
  54. package/dist/directives/ControlsBase.d.ts.map +1 -0
  55. package/dist/directives/Drag.d.ts +69 -0
  56. package/dist/directives/Drag.d.ts.map +1 -0
  57. package/dist/directives/Flash.d.ts +116 -0
  58. package/dist/directives/Flash.d.ts.map +1 -0
  59. package/dist/directives/FocusNavigation.d.ts +52 -0
  60. package/dist/directives/FocusNavigation.d.ts.map +1 -0
  61. package/dist/directives/FogVisibility.d.ts +47 -0
  62. package/dist/directives/FogVisibility.d.ts.map +1 -0
  63. package/dist/directives/GamepadControls.d.ts +224 -0
  64. package/dist/directives/GamepadControls.d.ts.map +1 -0
  65. package/dist/directives/JoystickControls.d.ts +171 -0
  66. package/dist/directives/JoystickControls.d.ts.map +1 -0
  67. package/dist/directives/KeyboardControls.d.ts +219 -0
  68. package/dist/directives/KeyboardControls.d.ts.map +1 -0
  69. package/dist/directives/Scheduler.d.ts +36 -0
  70. package/dist/directives/Scheduler.d.ts.map +1 -0
  71. package/dist/directives/Shake.d.ts +98 -0
  72. package/dist/directives/Shake.d.ts.map +1 -0
  73. package/dist/directives/Sound.d.ts +25 -0
  74. package/dist/directives/Sound.d.ts.map +1 -0
  75. package/dist/directives/SpriteEffects.d.ts +70 -0
  76. package/dist/directives/SpriteEffects.d.ts.map +1 -0
  77. package/dist/directives/Transition.d.ts +10 -0
  78. package/dist/directives/Transition.d.ts.map +1 -0
  79. package/dist/directives/ViewportCull.d.ts +11 -0
  80. package/dist/directives/ViewportCull.d.ts.map +1 -0
  81. package/dist/directives/ViewportFollow.d.ts +18 -0
  82. package/dist/directives/ViewportFollow.d.ts.map +1 -0
  83. package/dist/directives/index.d.ts +15 -0
  84. package/dist/directives/index.d.ts.map +1 -0
  85. package/dist/dist-BOOc43Qm.js +778 -0
  86. package/dist/dist-BOOc43Qm.js.map +1 -0
  87. package/dist/engine/FocusManager.d.ts +174 -0
  88. package/dist/engine/FocusManager.d.ts.map +1 -0
  89. package/dist/engine/animation.d.ts +72 -0
  90. package/dist/engine/animation.d.ts.map +1 -0
  91. package/dist/engine/bootstrap.d.ts +52 -0
  92. package/dist/engine/bootstrap.d.ts.map +1 -0
  93. package/dist/engine/directive.d.ts +13 -0
  94. package/dist/engine/directive.d.ts.map +1 -0
  95. package/dist/engine/reactive.d.ts +139 -0
  96. package/dist/engine/reactive.d.ts.map +1 -0
  97. package/dist/engine/signal.d.ts +74 -0
  98. package/dist/engine/signal.d.ts.map +1 -0
  99. package/dist/engine/trigger.d.ts +54 -0
  100. package/dist/engine/trigger.d.ts.map +1 -0
  101. package/dist/engine/utils.d.ts +89 -0
  102. package/dist/engine/utils.d.ts.map +1 -0
  103. package/dist/hooks/addContext.d.ts +2 -0
  104. package/dist/hooks/addContext.d.ts.map +1 -0
  105. package/dist/hooks/useFocus.d.ts +60 -0
  106. package/dist/hooks/useFocus.d.ts.map +1 -0
  107. package/dist/hooks/useProps.d.ts +53 -0
  108. package/dist/hooks/useProps.d.ts.map +1 -0
  109. package/dist/hooks/useRef.d.ts +4 -0
  110. package/dist/hooks/useRef.d.ts.map +1 -0
  111. package/dist/index.d.ts +19 -844
  112. package/dist/index.d.ts.map +1 -0
  113. package/dist/index.global.js +189 -0
  114. package/dist/index.global.js.map +1 -0
  115. package/dist/index.js +15156 -3287
  116. package/dist/index.js.map +1 -1
  117. package/dist/utils/Ease.d.ts +17 -0
  118. package/dist/utils/Ease.d.ts.map +1 -0
  119. package/dist/utils/GlobalAssetLoader.d.ts +141 -0
  120. package/dist/utils/GlobalAssetLoader.d.ts.map +1 -0
  121. package/dist/utils/RadialGradient.d.ts +57 -0
  122. package/dist/utils/RadialGradient.d.ts.map +1 -0
  123. package/dist/utils/functions.d.ts +2 -0
  124. package/dist/utils/functions.d.ts.map +1 -0
  125. package/dist/utils/tabindex.d.ts +16 -0
  126. package/dist/utils/tabindex.d.ts.map +1 -0
  127. package/index.d.ts +1 -3
  128. package/package.json +19 -52
  129. package/src/components/Button.ts +406 -0
  130. package/src/components/Canvas.ts +82 -51
  131. package/src/components/Container.ts +31 -4
  132. package/src/components/DOMContainer.ts +387 -0
  133. package/src/components/DOMElement.ts +556 -0
  134. package/src/components/DOMSprite.ts +1044 -0
  135. package/src/components/DisplayObject.ts +423 -202
  136. package/src/components/FocusContainer.ts +368 -0
  137. package/src/components/Graphic.ts +255 -68
  138. package/src/components/Joystick.ts +269 -0
  139. package/src/components/Mesh.ts +239 -0
  140. package/src/components/NineSliceSprite.ts +4 -1
  141. package/src/components/ParticleEmitter.ts +12 -8
  142. package/src/components/Sprite.ts +441 -54
  143. package/src/components/Text.ts +270 -26
  144. package/src/components/Video.ts +110 -0
  145. package/src/components/Viewport.ts +122 -63
  146. package/src/components/index.ts +12 -5
  147. package/src/components/types/DisplayObject.ts +64 -5
  148. package/src/components/types/Spritesheet.ts +0 -118
  149. package/src/directives/Controls.ts +254 -0
  150. package/src/directives/ControlsBase.ts +267 -0
  151. package/src/directives/Drag.ts +357 -52
  152. package/src/directives/Flash.ts +419 -0
  153. package/src/directives/FocusNavigation.ts +113 -0
  154. package/src/directives/FogVisibility.ts +273 -0
  155. package/src/directives/GamepadControls.ts +537 -0
  156. package/src/directives/JoystickControls.ts +396 -0
  157. package/src/directives/KeyboardControls.ts +86 -431
  158. package/src/directives/Scheduler.ts +21 -16
  159. package/src/directives/Shake.ts +298 -0
  160. package/src/directives/Sound.ts +94 -31
  161. package/src/directives/SpriteEffects.ts +461 -0
  162. package/src/directives/ViewportFollow.ts +40 -9
  163. package/src/directives/index.ts +14 -6
  164. package/src/engine/FocusManager.ts +510 -0
  165. package/src/engine/animation.ts +175 -21
  166. package/src/engine/bootstrap.ts +140 -6
  167. package/src/engine/directive.ts +4 -4
  168. package/src/engine/reactive.ts +1266 -167
  169. package/src/engine/signal.ts +335 -47
  170. package/src/engine/trigger.ts +95 -12
  171. package/src/engine/utils.ts +101 -9
  172. package/src/hooks/useFocus.ts +91 -0
  173. package/src/hooks/useProps.ts +92 -7
  174. package/src/index.ts +13 -6
  175. package/src/types/pixi-cull.d.ts +7 -0
  176. package/src/utils/GlobalAssetLoader.ts +257 -0
  177. package/src/utils/RadialGradient.ts +29 -0
  178. package/src/utils/functions.ts +7 -0
  179. package/src/utils/tabindex.ts +70 -0
  180. package/testing/index.ts +42 -0
  181. package/tsconfig.json +15 -25
  182. package/vite.config.ts +39 -0
  183. package/.cursorrules +0 -0
  184. package/.github/workflows/ci.yml +0 -29
  185. package/README.md +0 -79
  186. package/dist/compiler/vite.js +0 -119
  187. package/dist/compiler/vite.js.map +0 -1
  188. package/logo.png +0 -0
  189. package/src/compiler/grammar.pegjs +0 -180
  190. package/src/compiler/vite.ts +0 -166
  191. package/src/components/DrawMap/index.ts +0 -65
  192. package/src/components/Tilemap/Tile.ts +0 -79
  193. package/src/components/Tilemap/TileGroup.ts +0 -207
  194. package/src/components/Tilemap/TileLayer.ts +0 -163
  195. package/src/components/Tilemap/TileSet.ts +0 -41
  196. package/src/components/Tilemap/index.ts +0 -80
  197. package/src/presets/Bar.ts +0 -89
  198. package/src/presets/Button.ts +0 -0
  199. package/src/presets/Joystick.ts +0 -286
  200. package/src/presets/NightAmbiant.ts +0 -122
  201. package/src/presets/Particle.ts +0 -53
  202. package/starter/assets/logo.png +0 -0
  203. package/starter/components/app.ce +0 -18
  204. package/starter/components/hello.ce +0 -35
  205. package/starter/index.html +0 -21
  206. package/starter/main.ts +0 -6
  207. package/starter/package.json +0 -20
  208. package/starter/tsconfig.json +0 -32
  209. package/starter/vite.config.ts +0 -12
  210. package/tsconfig.node.json +0 -10
  211. package/tsup.config.ts +0 -28
  212. package/vitest.config.ts +0 -12
@@ -1,28 +1,52 @@
1
+ import { layout as layoutPretext, prepare as preparePretext, type PreparedText, type PrepareOptions } from "@chenglou/pretext";
1
2
  import { Text as PixiText, TextStyle } from "pixi.js";
2
- import { createComponent, registerComponent } from "../engine/reactive";
3
+ import { createComponent, registerComponent, Element } from "../engine/reactive";
3
4
  import { DisplayObject } from "./DisplayObject";
4
5
  import { DisplayObjectProps } from "./types/DisplayObject";
5
6
  import { Signal } from "@signe/reactive";
6
- import { on } from "../engine/trigger";
7
+ import { on, isTrigger } from "../engine/trigger";
8
+ import { Howl } from "howler";
7
9
 
8
10
  enum TextEffect {
9
11
  Typewriter = "typewriter",
10
12
  }
11
13
 
12
- interface TextProps extends DisplayObjectProps {
14
+ export interface TextProps extends DisplayObjectProps {
13
15
  text?: string;
14
16
  style?: Partial<TextStyle>;
15
17
  color?: string;
16
- size?: string;
18
+ size?: string | number;
17
19
  fontFamily?: string;
18
20
  typewriter?: {
19
21
  speed?: number;
20
22
  start?: () => void;
21
23
  onComplete?: () => void;
22
24
  skip?: () => void;
25
+ sound?: {
26
+ src: string;
27
+ volume?: number;
28
+ rate?: number;
29
+ };
23
30
  };
31
+ context?: any; // Ensure context is available, ideally typed from a base prop or injected
24
32
  }
25
33
 
34
+ type PretextMeasurement = {
35
+ width: number;
36
+ height: number;
37
+ };
38
+
39
+ const toFiniteNumber = (value: unknown): number | null => {
40
+ if (typeof value === "number") {
41
+ return Number.isFinite(value) ? value : null;
42
+ }
43
+ if (typeof value === "string") {
44
+ const parsed = Number.parseFloat(value);
45
+ return Number.isFinite(parsed) ? parsed : null;
46
+ }
47
+ return null;
48
+ };
49
+
26
50
  class CanvasText extends DisplayObject(PixiText) {
27
51
  private subscriptionTick: any;
28
52
  private fullText: string = "";
@@ -31,10 +55,22 @@ class CanvasText extends DisplayObject(PixiText) {
31
55
  private _wordWrapWidth: number = 0;
32
56
  private typewriterOptions: any = {};
33
57
  private skipSignal?: () => void;
58
+ private typewriterSound?: Howl;
59
+ private lastSoundTime: number = 0;
60
+ private soundDuration: number = 0; // Duration of the sound in milliseconds
61
+ private pretextPrepared: PreparedText | null = null;
62
+ private pretextPrepareKey: string = "";
63
+ private measuredLayout: PretextMeasurement | null = null;
34
64
 
35
- onMount(args) {
36
- super.onMount(args);
37
- const { props } = args;
65
+ /**
66
+ * Called when the component is mounted to the scene graph.
67
+ * Initializes the typewriter effect if configured.
68
+ * @param {Element<CanvasText>} element - The element being mounted with parent and props.
69
+ * @param {number} [index] - The index of the component among its siblings.
70
+ */
71
+ async onMount(element: Element<any>, index?: number): Promise<void> {
72
+ const { props } = element;
73
+ await super.onMount(element, index);
38
74
  const tick: Signal = props.context.tick;
39
75
 
40
76
  if (props.text && props.typewriter) {
@@ -44,12 +80,18 @@ class CanvasText extends DisplayObject(PixiText) {
44
80
  // Set typewriter options
45
81
  if (props.typewriter) {
46
82
  this.typewriterOptions = props.typewriter;
47
- if (this.typewriterOptions.skip) {
83
+ if (this.typewriterOptions.skip && isTrigger(this.typewriterOptions.skip)) {
48
84
  on(this.typewriterOptions.skip, () => {
49
85
  this.skipTypewriter();
50
86
  });
51
87
  }
88
+ // Initialize typewriter sound if configured
89
+ if (this.typewriterOptions.sound) {
90
+ this.initializeTypewriterSound();
91
+ }
52
92
  }
93
+ // Update layout after initializing typewriter
94
+ this.updateLayout();
53
95
  }
54
96
  this.subscriptionTick = tick.observable.subscribe(() => {
55
97
  if (props.typewriter) {
@@ -63,23 +105,24 @@ class CanvasText extends DisplayObject(PixiText) {
63
105
  if (props.typewriter) {
64
106
  if (props.typewriter) {
65
107
  this.typewriterOptions = props.typewriter;
108
+ // Reinitialize sound if sound configuration changed
109
+ if (props.typewriter.sound) {
110
+ this.initializeTypewriterSound();
111
+ }
66
112
  }
67
113
  }
68
- if (props.text) {
69
- this.text = props.text;
114
+ if (props.text !== undefined) {
115
+ this.text = ''+props.text;
70
116
  }
71
117
  if (props.text !== undefined && props.text !== this.fullText && this.fullProps.typewriter) {
72
118
  this.text = "";
73
119
  this.currentIndex = 0;
74
120
  this.fullText = props.text;
121
+ // Update layout after resetting typewriter
122
+ this.updateLayout();
75
123
  }
76
124
  if (props.style) {
77
- for (const key in props.style) {
78
- this.style[key] = props.style[key];
79
- }
80
- if (props.style.wordWrapWidth) {
81
- this._wordWrapWidth = props.style.wordWrapWidth;
82
- }
125
+ this.applyTextStyle(props.style);
83
126
  }
84
127
  if (props.color) {
85
128
  this.style.fill = props.color;
@@ -90,18 +133,186 @@ class CanvasText extends DisplayObject(PixiText) {
90
133
  if (props.fontFamily) {
91
134
  this.style.fontFamily = props.fontFamily;
92
135
  }
93
- if (this._wordWrapWidth) {
94
- this.setWidth(this._wordWrapWidth);
95
- } else {
96
- this.setWidth(this.width);
97
- }
98
- this.setHeight(this.height);
136
+ this.updateWordWrapWidth();
137
+
138
+ // Use the centralized layout update method
139
+ this.updateLayout();
99
140
  }
100
141
 
101
142
  get onCompleteCallback() {
102
143
  return this.typewriterOptions.onComplete;
103
144
  }
104
145
 
146
+ /**
147
+ * Initializes the typewriter sound effect using Howler.
148
+ * Creates a Howl instance with the configured sound settings.
149
+ * Calculates the sound duration to prevent overlapping sounds.
150
+ */
151
+ private initializeTypewriterSound() {
152
+ if (!this.typewriterOptions.sound?.src) return;
153
+
154
+ this.typewriterSound = new Howl({
155
+ src: [this.typewriterOptions.sound.src],
156
+ volume: this.typewriterOptions.sound.volume ?? 0.5,
157
+ rate: this.typewriterOptions.sound.rate ?? 1.0,
158
+ preload: true,
159
+ onload: () => {
160
+ // Calculate sound duration in milliseconds
161
+ if (this.typewriterSound) {
162
+ const duration = this.typewriterSound.duration();
163
+ const rate = this.typewriterOptions.sound?.rate ?? 1.0;
164
+ this.soundDuration = (duration / rate) * 1000;
165
+ }
166
+ }
167
+ });
168
+ }
169
+
170
+ /**
171
+ * Plays the typewriter sound with duration-based cooldown to prevent overlapping sounds.
172
+ * @param {number} currentTime - The current timestamp to check against sound duration.
173
+ */
174
+ private playTypewriterSound(currentTime: number) {
175
+ if (!this.typewriterSound || !this.typewriterOptions.sound) return;
176
+
177
+ // Check if enough time has passed since the last sound play
178
+ // Use the actual sound duration to prevent overlap
179
+ if (this.soundDuration > 0 && currentTime - this.lastSoundTime < this.soundDuration) return;
180
+
181
+ this.typewriterSound.play();
182
+ this.lastSoundTime = currentTime;
183
+ }
184
+
185
+ /**
186
+ * Updates the layout properties of the text component.
187
+ * This method ensures consistent width, height and word wrap behavior.
188
+ */
189
+ private updateLayout() {
190
+ const measured = this.measurePretextLayout();
191
+ const width = measured?.width ?? this.width;
192
+ const height = measured?.height ?? this.height;
193
+
194
+ this.measuredLayout = measured ?? { width, height };
195
+ this.setMeasuredLayout(width, height);
196
+ }
197
+
198
+ private applyTextStyle(style: Partial<TextStyle>) {
199
+ const assign = (this.style as TextStyle & { assign?: (values: any) => TextStyle }).assign;
200
+ if (typeof assign === "function") {
201
+ assign.call(this.style, style);
202
+ return;
203
+ }
204
+
205
+ for (const key in style) {
206
+ (this.style as any)[key] = (style as any)[key];
207
+ }
208
+ }
209
+
210
+ private updateWordWrapWidth() {
211
+ if (!this.style.wordWrap) {
212
+ this._wordWrapWidth = 0;
213
+ return;
214
+ }
215
+ const wordWrapWidth = toFiniteNumber(this.style.wordWrapWidth);
216
+ this._wordWrapWidth = wordWrapWidth !== null && wordWrapWidth > 0 ? wordWrapWidth : 0;
217
+ }
218
+
219
+ private measurePretextLayout(): PretextMeasurement | null {
220
+ if (!this.style.wordWrap || this._wordWrapWidth <= 0) {
221
+ this.pretextPrepared = null;
222
+ this.pretextPrepareKey = "";
223
+ return null;
224
+ }
225
+
226
+ const text = `${this.text ?? ""}`;
227
+ const font = this.resolvePretextFont();
228
+ const lineHeight = this.resolveLineHeight();
229
+ const options = this.resolvePretextOptions();
230
+ const prepareKey = JSON.stringify([text, font, options.whiteSpace, options.wordBreak, options.letterSpacing]);
231
+
232
+ try {
233
+ if (this.pretextPrepareKey !== prepareKey || !this.pretextPrepared) {
234
+ this.pretextPrepared = preparePretext(text, font, options);
235
+ this.pretextPrepareKey = prepareKey;
236
+ }
237
+
238
+ const result = layoutPretext(this.pretextPrepared, this._wordWrapWidth, lineHeight);
239
+ return {
240
+ width: this._wordWrapWidth,
241
+ height: result.height,
242
+ };
243
+ } catch {
244
+ this.pretextPrepared = null;
245
+ this.pretextPrepareKey = "";
246
+ return null;
247
+ }
248
+ }
249
+
250
+ private resolvePretextFont(): string {
251
+ const fontString = (this.style as TextStyle & { _fontString?: string })._fontString;
252
+ if (fontString) return fontString;
253
+
254
+ const fontSize = this.resolveFontSize();
255
+ const fontFamily = Array.isArray(this.style.fontFamily)
256
+ ? this.style.fontFamily.join(",")
257
+ : this.style.fontFamily;
258
+
259
+ return `${this.style.fontStyle} ${this.style.fontVariant} ${this.style.fontWeight} ${fontSize}px ${fontFamily}`;
260
+ }
261
+
262
+ private resolvePretextOptions(): PrepareOptions {
263
+ return {
264
+ whiteSpace: this.style.whiteSpace === "normal" ? "normal" : "pre-wrap",
265
+ letterSpacing: this.style.letterSpacing || undefined,
266
+ };
267
+ }
268
+
269
+ private resolveLineHeight(): number {
270
+ const lineHeight = toFiniteNumber(this.style.lineHeight);
271
+ if (lineHeight !== null && lineHeight > 0) return lineHeight;
272
+ return this.resolveFontSize();
273
+ }
274
+
275
+ private resolveFontSize(): number {
276
+ const fontSize = toFiniteNumber(this.style.fontSize);
277
+ return fontSize !== null && fontSize > 0 ? fontSize : 16;
278
+ }
279
+
280
+ private setMeasuredLayout(width: number, height: number) {
281
+ const layout: { width?: number; height?: number } = {};
282
+
283
+ if (this.fullProps.width === undefined) {
284
+ this.displayWidth.set(width);
285
+ if (this.parentIsFlex) {
286
+ layout.width = width;
287
+ }
288
+ }
289
+
290
+ if (this.fullProps.height === undefined) {
291
+ this.displayHeight.set(height);
292
+ if (this.parentIsFlex) {
293
+ layout.height = height;
294
+ }
295
+ }
296
+
297
+ if (this.parentIsFlex && (layout.width !== undefined || layout.height !== undefined)) {
298
+ (this as any).layout = layout;
299
+ }
300
+ }
301
+
302
+ getWidth(): number {
303
+ if (this.fullProps.width === undefined && this.measuredLayout) {
304
+ return this.measuredLayout.width;
305
+ }
306
+ return super.getWidth();
307
+ }
308
+
309
+ getHeight(): number {
310
+ if (this.fullProps.height === undefined && this.measuredLayout) {
311
+ return this.measuredLayout.height;
312
+ }
313
+ return super.getHeight();
314
+ }
315
+
105
316
  private typewriterEffect() {
106
317
  if (this.currentIndex < this.fullText.length) {
107
318
  const nextIndex = Math.min(
@@ -111,6 +322,14 @@ class CanvasText extends DisplayObject(PixiText) {
111
322
  this.text = this.fullText.slice(0, nextIndex);
112
323
  this.currentIndex = nextIndex;
113
324
 
325
+ // Play typewriter sound if configured
326
+ if (this.typewriterOptions.sound) {
327
+ this.playTypewriterSound(Date.now());
328
+ }
329
+
330
+ // Update layout after text change to maintain proper word wrap and dimensions
331
+ this.updateLayout();
332
+
114
333
  // Check if typewriter effect is complete
115
334
  if (
116
335
  this.currentIndex === this.fullText.length &&
@@ -128,15 +347,40 @@ class CanvasText extends DisplayObject(PixiText) {
128
347
  }
129
348
  this.text = this.fullText;
130
349
  this.currentIndex = this.fullText.length;
350
+
351
+ // Update layout after setting full text to maintain proper word wrap and dimensions
352
+ this.updateLayout();
131
353
  }
132
354
 
133
- onDestroy(): void {
134
- super.onDestroy();
135
- this.subscriptionTick.unsubscribe();
355
+ /**
356
+ * Called when the component is about to be destroyed.
357
+ * Unsubscribes from the tick observable and cleans up sound resources.
358
+ * @param {Element<any>} parent - The parent element.
359
+ * @param {() => void} [afterDestroy] - An optional callback function to be executed after the component's own destruction logic.
360
+ */
361
+ async onDestroy(parent: Element<any>, afterDestroy?: () => void): Promise<void> {
362
+ const _afterDestroy = async () => {
363
+ if (this.subscriptionTick) {
364
+ this.subscriptionTick.unsubscribe();
365
+ }
366
+ // Clean up typewriter sound
367
+ if (this.typewriterSound) {
368
+ this.typewriterSound.stop();
369
+ this.typewriterSound.unload();
370
+ this.typewriterSound = undefined;
371
+ }
372
+ this.pretextPrepared = null;
373
+ this.pretextPrepareKey = "";
374
+ this.measuredLayout = null;
375
+ if (afterDestroy) {
376
+ afterDestroy();
377
+ }
378
+ }
379
+ await super.onDestroy(parent, _afterDestroy);
136
380
  }
137
381
  }
138
382
 
139
- interface CanvasText extends PixiText {}
383
+ // interface CanvasText extends PixiText {} // Removed as it's redundant and causes type conflicts
140
384
 
141
385
  registerComponent("Text", CanvasText);
142
386
 
@@ -0,0 +1,110 @@
1
+ import { Texture } from "pixi.js";
2
+ import { h, mount } from "../engine/signal";
3
+ import { useDefineProps } from "../hooks/useProps";
4
+ import { Sprite } from "./Sprite";
5
+ import { effect, Signal, signal } from "@signe/reactive";
6
+
7
+ interface VideoProps {
8
+ src: string;
9
+ paused?: boolean;
10
+ loop?: boolean;
11
+ muted?: boolean;
12
+ loader?: {
13
+ onComplete?: (texture: Texture) => void;
14
+ onProgress?: (progress: number) => void;
15
+ };
16
+ }
17
+
18
+ export function Video(props: VideoProps) {
19
+ const eventsMap = {
20
+ audioprocess: null,
21
+ canplay: null,
22
+ canplaythrough: null,
23
+ complete: null,
24
+ durationchange: null,
25
+ emptied: null,
26
+ ended: null,
27
+ loadeddata: null,
28
+ loadedmetadata: null,
29
+ pause: null,
30
+ play: null,
31
+ playing: null,
32
+ progress: null,
33
+ ratechange: null,
34
+ seeked: null,
35
+ seeking: null,
36
+ stalled: null,
37
+ suspend: null,
38
+ timeupdate: null,
39
+ volumechange: null,
40
+ waiting: null
41
+ }
42
+
43
+ const video: Signal<HTMLVideoElement | null> = signal(null)
44
+ const defineProps = useDefineProps(props)
45
+ const { play, loop, muted } = defineProps({
46
+ play: {
47
+ type: Boolean,
48
+ default: true
49
+ },
50
+ loop: {
51
+ type: Boolean,
52
+ default: false
53
+ },
54
+ muted: {
55
+ type: Boolean,
56
+ default: false
57
+ }
58
+ })
59
+
60
+ effect(() => {
61
+ const _video = video()
62
+ const state = play()
63
+ if (_video && state !== undefined) {
64
+ if (state) {
65
+ _video.play()
66
+ } else {
67
+ _video.pause()
68
+ }
69
+ }
70
+ if (_video && loop()) {
71
+ _video.loop = loop()
72
+ }
73
+ if (_video && muted()) {
74
+ _video.muted = muted()
75
+ }
76
+ })
77
+
78
+ mount(() => {
79
+ return () => {
80
+ for (let event in eventsMap) {
81
+ if (eventsMap[event]) {
82
+ video().removeEventListener(event, eventsMap[event])
83
+ }
84
+ }
85
+ }
86
+ })
87
+
88
+ return h(Sprite, {
89
+ ...props,
90
+ image: props.src,
91
+ loader: {
92
+ onComplete: (texture) => {
93
+ const source = texture.source.resource
94
+ video.set(source)
95
+ if (props?.loader?.onComplete) {
96
+ props.loader.onComplete(texture)
97
+ }
98
+ for (let event in eventsMap) {
99
+ if (props[event]) {
100
+ const cb = (ev) => {
101
+ props[event](ev)
102
+ }
103
+ eventsMap[event] = cb
104
+ source.addEventListener(event, cb)
105
+ }
106
+ }
107
+ }
108
+ }
109
+ })
110
+ }