canvasengine 2.0.0-beta.3 → 2.0.0-beta.30

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 (130) hide show
  1. package/dist/DebugRenderer-DcvJLrrD.js +172 -0
  2. package/dist/DebugRenderer-DcvJLrrD.js.map +1 -0
  3. package/dist/components/Button.d.ts +136 -0
  4. package/dist/components/Button.d.ts.map +1 -0
  5. package/dist/components/Canvas.d.ts +18 -0
  6. package/dist/components/Canvas.d.ts.map +1 -0
  7. package/dist/components/Container.d.ts +80 -0
  8. package/dist/components/Container.d.ts.map +1 -0
  9. package/dist/components/DOMContainer.d.ts +77 -0
  10. package/dist/components/DOMContainer.d.ts.map +1 -0
  11. package/dist/components/DOMElement.d.ts +44 -0
  12. package/dist/components/DOMElement.d.ts.map +1 -0
  13. package/dist/components/DisplayObject.d.ts +82 -0
  14. package/dist/components/DisplayObject.d.ts.map +1 -0
  15. package/dist/components/Graphic.d.ts +65 -0
  16. package/dist/components/Graphic.d.ts.map +1 -0
  17. package/dist/components/Mesh.d.ts +202 -0
  18. package/dist/components/Mesh.d.ts.map +1 -0
  19. package/dist/components/NineSliceSprite.d.ts +17 -0
  20. package/dist/components/NineSliceSprite.d.ts.map +1 -0
  21. package/dist/components/ParticleEmitter.d.ts +5 -0
  22. package/dist/components/ParticleEmitter.d.ts.map +1 -0
  23. package/dist/components/Scene.d.ts +2 -0
  24. package/dist/components/Scene.d.ts.map +1 -0
  25. package/dist/components/Sprite.d.ts +174 -0
  26. package/dist/components/Sprite.d.ts.map +1 -0
  27. package/dist/components/Text.d.ts +21 -0
  28. package/dist/components/Text.d.ts.map +1 -0
  29. package/dist/components/TilingSprite.d.ts +18 -0
  30. package/dist/components/TilingSprite.d.ts.map +1 -0
  31. package/dist/components/Video.d.ts +15 -0
  32. package/dist/components/Video.d.ts.map +1 -0
  33. package/dist/components/Viewport.d.ts +106 -0
  34. package/dist/components/Viewport.d.ts.map +1 -0
  35. package/dist/components/index.d.ts +17 -0
  36. package/dist/components/index.d.ts.map +1 -0
  37. package/dist/components/types/DisplayObject.d.ts +106 -0
  38. package/dist/components/types/DisplayObject.d.ts.map +1 -0
  39. package/dist/components/types/MouseEvent.d.ts +4 -0
  40. package/dist/components/types/MouseEvent.d.ts.map +1 -0
  41. package/dist/components/types/Spritesheet.d.ts +366 -0
  42. package/dist/components/types/Spritesheet.d.ts.map +1 -0
  43. package/dist/components/types/index.d.ts +5 -0
  44. package/dist/components/types/index.d.ts.map +1 -0
  45. package/dist/directives/Drag.d.ts +70 -0
  46. package/dist/directives/Drag.d.ts.map +1 -0
  47. package/dist/directives/KeyboardControls.d.ts +530 -0
  48. package/dist/directives/KeyboardControls.d.ts.map +1 -0
  49. package/dist/directives/Scheduler.d.ts +36 -0
  50. package/dist/directives/Scheduler.d.ts.map +1 -0
  51. package/dist/directives/Sound.d.ts +26 -0
  52. package/dist/directives/Sound.d.ts.map +1 -0
  53. package/dist/directives/Transition.d.ts +11 -0
  54. package/dist/directives/Transition.d.ts.map +1 -0
  55. package/dist/directives/ViewportCull.d.ts +12 -0
  56. package/dist/directives/ViewportCull.d.ts.map +1 -0
  57. package/dist/directives/ViewportFollow.d.ts +19 -0
  58. package/dist/directives/ViewportFollow.d.ts.map +1 -0
  59. package/dist/directives/index.d.ts +2 -0
  60. package/dist/directives/index.d.ts.map +1 -0
  61. package/dist/engine/animation.d.ts +59 -0
  62. package/dist/engine/animation.d.ts.map +1 -0
  63. package/dist/engine/bootstrap.d.ts +16 -0
  64. package/dist/engine/bootstrap.d.ts.map +1 -0
  65. package/dist/engine/directive.d.ts +14 -0
  66. package/dist/engine/directive.d.ts.map +1 -0
  67. package/dist/engine/reactive.d.ts +95 -0
  68. package/dist/engine/reactive.d.ts.map +1 -0
  69. package/dist/engine/signal.d.ts +72 -0
  70. package/dist/engine/signal.d.ts.map +1 -0
  71. package/dist/engine/trigger.d.ts +51 -0
  72. package/dist/engine/trigger.d.ts.map +1 -0
  73. package/dist/engine/utils.d.ts +90 -0
  74. package/dist/engine/utils.d.ts.map +1 -0
  75. package/dist/hooks/addContext.d.ts +2 -0
  76. package/dist/hooks/addContext.d.ts.map +1 -0
  77. package/dist/hooks/useProps.d.ts +42 -0
  78. package/dist/hooks/useProps.d.ts.map +1 -0
  79. package/dist/hooks/useRef.d.ts +5 -0
  80. package/dist/hooks/useRef.d.ts.map +1 -0
  81. package/dist/index-C-iY-lCt.js +11080 -0
  82. package/dist/index-C-iY-lCt.js.map +1 -0
  83. package/dist/index.d.ts +15 -919
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.global.js +29 -0
  86. package/dist/index.global.js.map +1 -0
  87. package/dist/index.js +63 -2950
  88. package/dist/index.js.map +1 -1
  89. package/dist/utils/Ease.d.ts +17 -0
  90. package/dist/utils/Ease.d.ts.map +1 -0
  91. package/dist/utils/RadialGradient.d.ts +58 -0
  92. package/dist/utils/RadialGradient.d.ts.map +1 -0
  93. package/dist/utils/functions.d.ts +2 -0
  94. package/dist/utils/functions.d.ts.map +1 -0
  95. package/index.d.ts +4 -0
  96. package/package.json +12 -7
  97. package/src/components/Button.ts +269 -0
  98. package/src/components/Canvas.ts +53 -45
  99. package/src/components/Container.ts +2 -2
  100. package/src/components/DOMContainer.ts +123 -0
  101. package/src/components/DOMElement.ts +421 -0
  102. package/src/components/DisplayObject.ts +283 -190
  103. package/src/components/Graphic.ts +200 -34
  104. package/src/components/Mesh.ts +222 -0
  105. package/src/components/NineSliceSprite.ts +4 -1
  106. package/src/components/ParticleEmitter.ts +12 -8
  107. package/src/components/Sprite.ts +92 -22
  108. package/src/components/Text.ts +34 -14
  109. package/src/components/Video.ts +110 -0
  110. package/src/components/Viewport.ts +59 -43
  111. package/src/components/index.ts +7 -2
  112. package/src/components/types/DisplayObject.ts +30 -0
  113. package/src/directives/Drag.ts +357 -52
  114. package/src/directives/KeyboardControls.ts +3 -1
  115. package/src/directives/Sound.ts +94 -31
  116. package/src/directives/ViewportFollow.ts +35 -7
  117. package/src/engine/animation.ts +41 -5
  118. package/src/engine/bootstrap.ts +23 -3
  119. package/src/engine/directive.ts +2 -2
  120. package/src/engine/reactive.ts +472 -172
  121. package/src/engine/signal.ts +18 -2
  122. package/src/engine/trigger.ts +65 -9
  123. package/src/engine/utils.ts +97 -9
  124. package/src/hooks/useProps.ts +1 -1
  125. package/src/index.ts +4 -1
  126. package/src/utils/RadialGradient.ts +29 -0
  127. package/src/utils/functions.ts +7 -0
  128. package/testing/index.ts +12 -0
  129. package/tsconfig.json +17 -0
  130. package/vite.config.ts +39 -0
@@ -0,0 +1,421 @@
1
+ import { DOMContainer as PixiDOMContainer } from "pixi.js";
2
+ import {
3
+ createComponent,
4
+ Element,
5
+ registerComponent,
6
+ } from "../engine/reactive";
7
+ import { ComponentInstance, DisplayObject, OnHook } from "./DisplayObject";
8
+ import { ComponentFunction } from "../engine/signal";
9
+ import { DisplayObjectProps } from "./types/DisplayObject";
10
+ import { isObservable } from "../engine/utils";
11
+ import { isSignal } from "@signe/reactive";
12
+
13
+ interface DOMContainerProps extends DisplayObjectProps {
14
+ element:
15
+ | string
16
+ | {
17
+ value: HTMLElement;
18
+ };
19
+ textContent?: string;
20
+ attrs?: Record<string, any> & {
21
+ class?:
22
+ | string
23
+ | string[]
24
+ | Record<string, boolean>
25
+ | { items?: string[] }
26
+ | { value?: string | string[] | Record<string, boolean> };
27
+ style?:
28
+ | string
29
+ | Record<string, string | number>
30
+ | { value?: string | Record<string, string | number> };
31
+ };
32
+ onBeforeDestroy?: OnHook;
33
+ }
34
+
35
+ /**
36
+ * DOMContainer class for managing DOM elements within the canvas engine
37
+ *
38
+ * This class extends the DisplayObject functionality to handle DOM elements using
39
+ * PixiJS's native DOMContainer. It provides a bridge between the canvas rendering
40
+ * system and traditional DOM manipulation with proper transform hierarchy and visibility.
41
+ *
42
+ * The DOMContainer is especially useful for rendering standard DOM elements that handle
43
+ * user input, such as `<input>` or `<textarea>`. This is often simpler and more flexible
44
+ * than trying to implement text input directly in PixiJS.
45
+ *
46
+ * ## Form Elements with Reactive Signals
47
+ *
48
+ * For form elements (`input`, `textarea`, `select`), the component supports reactive
49
+ * two-way data binding using signals. When the `value` attribute is a signal, the
50
+ * component automatically:
51
+ * - Sets the initial value from the signal
52
+ * - Listens for `input` events and updates the signal with the new value
53
+ * - Updates the DOM element when the signal value changes programmatically
54
+ *
55
+ * ## Form Submission Handling
56
+ *
57
+ * When a `form` element has a `submit` event handler, the component automatically:
58
+ * - Prevents the default form submission behavior (stops propagation)
59
+ * - Collects all form data from input elements within the form
60
+ * - Passes both the event and the collected form data as parameters to the submit handler
61
+ * - Handles multiple values for the same field name (e.g., checkboxes with same name)
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * import { signal } from '@signe/reactive';
66
+ *
67
+ * // Basic usage with input element
68
+ * const element = document.createElement('input');
69
+ * element.type = 'text';
70
+ * element.placeholder = 'Enter text...';
71
+ *
72
+ * const domContainer = new DOMContainer({
73
+ * element,
74
+ * x: 100,
75
+ * y: 50,
76
+ * anchor: { x: 0.5, y: 0.5 }
77
+ * });
78
+ *
79
+ * // Reactive form input with signal
80
+ * const inputValue = signal('');
81
+ *
82
+ * const reactiveInput = new DOMContainer({
83
+ * element: 'input',
84
+ * attrs: {
85
+ * type: 'text',
86
+ * placeholder: 'Type something...',
87
+ * value: inputValue // Signal for two-way binding
88
+ * }
89
+ * });
90
+ *
91
+ * // The signal will automatically update when user types
92
+ * inputValue.subscribe(value => {
93
+ * console.log('Input value changed:', value);
94
+ * });
95
+ *
96
+ * // You can also update the input programmatically
97
+ * inputValue.set('New value');
98
+ *
99
+ * // Form submission with automatic data collection
100
+ * const loginForm = new DOMContainer({
101
+ * element: 'form',
102
+ * attrs: {
103
+ * submit: (event, formData) => {
104
+ * // event: the submit event (already prevented)
105
+ * // formData: object containing all form field values
106
+ * console.log('Form submitted with data:', formData);
107
+ * // Example formData: { username: 'john', password: 'secret', remember: 'on' }
108
+ * }
109
+ * },
110
+ * children: [
111
+ * new DOMContainer({
112
+ * element: 'input',
113
+ * attrs: { name: 'username', type: 'text', placeholder: 'Username' }
114
+ * }),
115
+ * new DOMContainer({
116
+ * element: 'input',
117
+ * attrs: { name: 'password', type: 'password', placeholder: 'Password' }
118
+ * }),
119
+ * new DOMContainer({
120
+ * element: 'input',
121
+ * attrs: { name: 'remember', type: 'checkbox', value: 'on' }
122
+ * }),
123
+ * new DOMContainer({
124
+ * element: 'button',
125
+ * attrs: { type: 'submit' },
126
+ * textContent: 'Login'
127
+ * })
128
+ * ]
129
+ * });
130
+ *
131
+ * // Using different class and style formats
132
+ * const containerWithClasses = new DOMContainer({
133
+ * element: 'div',
134
+ * attrs: {
135
+ * // String format: space-separated classes
136
+ * class: 'container primary-theme',
137
+ *
138
+ * // Array format: array of class names
139
+ * // class: ['container', 'primary-theme'],
140
+ *
141
+ * // Object format: conditional classes
142
+ * // class: {
143
+ * // 'container': true,
144
+ * // 'primary-theme': true,
145
+ * // 'disabled': false
146
+ * // }
147
+ *
148
+ * // String format: CSS style string
149
+ * style: 'background-color: red; padding: 10px;',
150
+ *
151
+ * // Object format: style properties
152
+ * // style: {
153
+ * // backgroundColor: 'red',
154
+ * // padding: '10px',
155
+ * // fontSize: 16
156
+ * // }
157
+ * }
158
+ * });
159
+ * ```
160
+ */
161
+ const EVENTS = [
162
+ "click",
163
+ "mouseover",
164
+ "mouseout",
165
+ "mouseenter",
166
+ "mouseleave",
167
+ "mousemove",
168
+ "mouseup",
169
+ "mousedown",
170
+ "touchstart",
171
+ "touchend",
172
+ "touchmove",
173
+ "touchcancel",
174
+ "wheel",
175
+ "scroll",
176
+ "resize",
177
+ "focus",
178
+ "blur",
179
+ "change",
180
+ "input",
181
+ "submit",
182
+ "reset",
183
+ "keydown",
184
+ "keyup",
185
+ "keypress",
186
+ "contextmenu",
187
+ "drag",
188
+ "dragend",
189
+ "dragenter",
190
+ "dragleave",
191
+ "dragover",
192
+ "drop",
193
+ "dragstart",
194
+ "select",
195
+ "selectstart",
196
+ "selectend",
197
+ "selectall",
198
+ "selectnone",
199
+ ];
200
+
201
+ export class CanvasDOMElement {
202
+ public element: HTMLElement;
203
+ private eventListeners: Map<string, (e: Event) => void> = new Map();
204
+ private onBeforeDestroy: OnHook | null = null;
205
+ private valueSignal: any = null;
206
+ private isFormElementType: boolean = false;
207
+
208
+ /**
209
+ * Checks if the element is a form element that supports the value attribute
210
+ * @param elementType - The element type string from props
211
+ * @returns true if the element is a form element with value support
212
+ */
213
+ private isFormElement(elementType: string): boolean {
214
+ const formElements = ["input", "textarea", "select"];
215
+ return formElements.includes(elementType.toLowerCase());
216
+ }
217
+
218
+ onInit(props: DOMContainerProps) {
219
+ if (typeof props.element === "string") {
220
+ this.element = document.createElement(props.element);
221
+ this.isFormElementType = this.isFormElement(props.element);
222
+ } else {
223
+ this.element = props.element.value;
224
+ this.isFormElementType = this.isFormElement(this.element.tagName);
225
+ }
226
+ if (props.onBeforeDestroy || props["on-before-destroy"]) {
227
+ this.onBeforeDestroy =
228
+ props.onBeforeDestroy || props["on-before-destroy"];
229
+ }
230
+
231
+ for (const event of EVENTS) {
232
+ if (props.attrs?.[event]) {
233
+ const eventHandler = (e: Event) => {
234
+ // Special handling for form submit events
235
+ if (event === "submit" && this.element.tagName.toLowerCase() === "form") {
236
+ e.preventDefault(); // Stop form submission propagation
237
+
238
+ // Collect all form data
239
+ const formData = new FormData(this.element as HTMLFormElement);
240
+ const formObject: Record<string, any> = {};
241
+
242
+ // Convert FormData to plain object
243
+ formData.forEach((value, key) => {
244
+ if (formObject[key]) {
245
+ // Handle multiple values for same key (like checkboxes)
246
+ if (Array.isArray(formObject[key])) {
247
+ formObject[key].push(value);
248
+ } else {
249
+ formObject[key] = [formObject[key], value];
250
+ }
251
+ } else {
252
+ formObject[key] = value;
253
+ }
254
+ });
255
+
256
+ // Call the event handler with event and form data
257
+ props.attrs[event]?.(e, formObject);
258
+ } else {
259
+ props.attrs[event]?.(e);
260
+ }
261
+ };
262
+ this.eventListeners.set(event, eventHandler);
263
+ this.element.addEventListener(event, eventHandler, false);
264
+ }
265
+ }
266
+ if (props.children) {
267
+ for (const child of props.children) {
268
+ if (isObservable(child)) {
269
+ child.subscribe(({ elements }) => {
270
+ for (const element of elements) {
271
+ this.element.appendChild(element.componentInstance.element);
272
+ }
273
+ });
274
+ } else {
275
+ this.element.appendChild(child.componentInstance.element);
276
+ }
277
+ }
278
+ }
279
+ this.onUpdate(props);
280
+ }
281
+
282
+ onMount(context: Element<CanvasDOMElement>) {
283
+ const props = context.propObservables;
284
+ const attrs = props.attrs as any;
285
+ // Handle form elements with signal value
286
+ if (
287
+ this.isFormElementType &&
288
+ attrs?.value &&
289
+ isSignal(attrs.value)
290
+ ) {
291
+ this.valueSignal = attrs.value;
292
+ // Set initial value from signal
293
+ (
294
+ this.element as
295
+ | HTMLInputElement
296
+ | HTMLTextAreaElement
297
+ | HTMLSelectElement
298
+ ).value = this.valueSignal();
299
+
300
+ // Listen for input events and update the signal
301
+ const inputHandler = (e: Event) => {
302
+ const target = e.target as
303
+ | HTMLInputElement
304
+ | HTMLTextAreaElement
305
+ | HTMLSelectElement;
306
+ this.valueSignal.set(target.value);
307
+ };
308
+
309
+ this.eventListeners.set("input", inputHandler);
310
+ this.element.addEventListener("input", inputHandler, false);
311
+ }
312
+ }
313
+
314
+ onUpdate(props: DOMContainerProps) {
315
+ if (!this.element) return;
316
+ for (const [key, value] of Object.entries(props.attrs || {})) {
317
+ if (key === "class") {
318
+ const classList = value.items || value.value || value;
319
+
320
+ // Clear existing classes first
321
+ this.element.className = "";
322
+
323
+ if (typeof classList === "string") {
324
+ // String: space-separated class names
325
+ this.element.className = classList;
326
+ } else if (Array.isArray(classList)) {
327
+ // Array: array of class names
328
+ this.element.classList.add(...classList);
329
+ } else if (typeof classList === "object" && classList !== null) {
330
+ // Object: { className: boolean }
331
+ for (const [className, shouldAdd] of Object.entries(classList)) {
332
+ if (shouldAdd) {
333
+ this.element.classList.add(className);
334
+ }
335
+ }
336
+ }
337
+ } else if (key === "style") {
338
+ const styleValue = value.items || value.value || value;
339
+
340
+ if (typeof styleValue === "string") {
341
+ // String: CSS style string
342
+ this.element.setAttribute("style", styleValue);
343
+ } else if (typeof styleValue === "object" && styleValue !== null) {
344
+ // Object: { property: value }
345
+ for (const [styleProp, styleVal] of Object.entries(styleValue)) {
346
+ if (styleVal !== null && styleVal !== undefined) {
347
+ (this.element.style as any)[styleProp] = styleVal;
348
+ }
349
+ }
350
+ }
351
+ } else if (key === "value" && this.isFormElementType) {
352
+ // Handle value attribute for form elements
353
+ if (isSignal(value)) {
354
+ // If it's a signal, the value is already handled in onInit
355
+ // Update the DOM element value if the signal value changed
356
+ const currentValue = (
357
+ this.element as
358
+ | HTMLInputElement
359
+ | HTMLTextAreaElement
360
+ | HTMLSelectElement
361
+ ).value;
362
+ const signalValue = value();
363
+ if (currentValue !== signalValue) {
364
+ (
365
+ this.element as
366
+ | HTMLInputElement
367
+ | HTMLTextAreaElement
368
+ | HTMLSelectElement
369
+ ).value = signalValue;
370
+ }
371
+ } else {
372
+ // If it's not a signal, set the value directly
373
+ (
374
+ this.element as
375
+ | HTMLInputElement
376
+ | HTMLTextAreaElement
377
+ | HTMLSelectElement
378
+ ).value = value;
379
+ }
380
+ } else if (!EVENTS.includes(key)) {
381
+ this.element.setAttribute(key, value);
382
+ }
383
+ }
384
+ if (props.textContent) {
385
+ this.element.textContent = props.textContent;
386
+ }
387
+ }
388
+
389
+ async onDestroy(
390
+ parent: Element<CanvasDOMElement>,
391
+ afterDestroy: () => void
392
+ ): Promise<void> {
393
+ // Remove all event listeners from the DOM element
394
+
395
+ if (this.element) {
396
+ if (this.onBeforeDestroy) {
397
+ await this.onBeforeDestroy();
398
+ }
399
+
400
+ for (const [event, handler] of this.eventListeners) {
401
+ this.element.removeEventListener(event, handler, false);
402
+ }
403
+
404
+ this.eventListeners.clear();
405
+
406
+ this.element.remove();
407
+
408
+ if (afterDestroy) {
409
+ afterDestroy();
410
+ }
411
+ }
412
+ }
413
+ }
414
+
415
+ export interface CanvasDOMElement extends DisplayObjectProps {}
416
+
417
+ registerComponent("DOMElement", CanvasDOMElement);
418
+
419
+ export const DOMElement: ComponentFunction<DOMContainerProps> = (props) => {
420
+ return createComponent("DOMElement", props);
421
+ };