@energy8platform/game-engine 0.2.0 → 0.3.0

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 (59) hide show
  1. package/README.md +318 -49
  2. package/dist/animation.cjs.js +191 -1
  3. package/dist/animation.cjs.js.map +1 -1
  4. package/dist/animation.d.ts +117 -1
  5. package/dist/animation.esm.js +192 -3
  6. package/dist/animation.esm.js.map +1 -1
  7. package/dist/audio.cjs.js +66 -16
  8. package/dist/audio.cjs.js.map +1 -1
  9. package/dist/audio.d.ts +4 -0
  10. package/dist/audio.esm.js +66 -16
  11. package/dist/audio.esm.js.map +1 -1
  12. package/dist/core.cjs.js +310 -84
  13. package/dist/core.cjs.js.map +1 -1
  14. package/dist/core.d.ts +60 -1
  15. package/dist/core.esm.js +311 -85
  16. package/dist/core.esm.js.map +1 -1
  17. package/dist/debug.cjs.js +36 -68
  18. package/dist/debug.cjs.js.map +1 -1
  19. package/dist/debug.d.ts +4 -6
  20. package/dist/debug.esm.js +36 -68
  21. package/dist/debug.esm.js.map +1 -1
  22. package/dist/index.cjs.js +1250 -251
  23. package/dist/index.cjs.js.map +1 -1
  24. package/dist/index.d.ts +386 -41
  25. package/dist/index.esm.js +1250 -254
  26. package/dist/index.esm.js.map +1 -1
  27. package/dist/ui.cjs.js +757 -1
  28. package/dist/ui.cjs.js.map +1 -1
  29. package/dist/ui.d.ts +208 -2
  30. package/dist/ui.esm.js +756 -2
  31. package/dist/ui.esm.js.map +1 -1
  32. package/dist/vite.cjs.js +65 -68
  33. package/dist/vite.cjs.js.map +1 -1
  34. package/dist/vite.d.ts +17 -23
  35. package/dist/vite.esm.js +66 -68
  36. package/dist/vite.esm.js.map +1 -1
  37. package/package.json +4 -5
  38. package/src/animation/SpriteAnimation.ts +210 -0
  39. package/src/animation/Tween.ts +27 -1
  40. package/src/animation/index.ts +2 -0
  41. package/src/audio/AudioManager.ts +64 -15
  42. package/src/core/EventEmitter.ts +7 -1
  43. package/src/core/GameApplication.ts +25 -7
  44. package/src/core/SceneManager.ts +3 -1
  45. package/src/debug/DevBridge.ts +49 -80
  46. package/src/index.ts +6 -0
  47. package/src/input/InputManager.ts +26 -0
  48. package/src/loading/CSSPreloader.ts +7 -33
  49. package/src/loading/LoadingScene.ts +17 -41
  50. package/src/loading/index.ts +1 -0
  51. package/src/loading/logo.ts +95 -0
  52. package/src/types.ts +4 -0
  53. package/src/ui/BalanceDisplay.ts +14 -0
  54. package/src/ui/Button.ts +1 -1
  55. package/src/ui/Layout.ts +364 -0
  56. package/src/ui/ScrollContainer.ts +557 -0
  57. package/src/ui/index.ts +4 -0
  58. package/src/viewport/ViewportManager.ts +2 -0
  59. package/src/vite/index.ts +83 -83
@@ -0,0 +1,557 @@
1
+ import { Container, Graphics } from 'pixi.js';
2
+
3
+ // ─── Types ───────────────────────────────────────────────
4
+
5
+ export type ScrollDirection = 'vertical' | 'horizontal' | 'both';
6
+
7
+ export interface ScrollContainerConfig {
8
+ /** Visible viewport width */
9
+ width: number;
10
+
11
+ /** Visible viewport height */
12
+ height: number;
13
+
14
+ /** Scroll direction (default: 'vertical') */
15
+ direction?: ScrollDirection;
16
+
17
+ /** Show scrollbar(s) (default: true) */
18
+ showScrollbar?: boolean;
19
+
20
+ /** Scrollbar width in pixels (default: 6) */
21
+ scrollbarWidth?: number;
22
+
23
+ /** Scrollbar color (default: 0xffffff) */
24
+ scrollbarColor?: number;
25
+
26
+ /** Scrollbar opacity (default: 0.4) */
27
+ scrollbarAlpha?: number;
28
+
29
+ /** Elasticity factor for overscroll bounce (0 = none, 1 = infinite, default: 0.3) */
30
+ elasticity?: number;
31
+
32
+ /** Inertia deceleration factor (0 = instant stop, 1 = infinite drift, default: 0.92) */
33
+ inertia?: number;
34
+
35
+ /** Snap to items of fixed height/width (0 = no snap) */
36
+ snapSize?: number;
37
+
38
+ /** Background color (undefined = transparent) */
39
+ backgroundColor?: number;
40
+
41
+ /** Background alpha (default: 1) */
42
+ backgroundAlpha?: number;
43
+
44
+ /** Border radius for the mask (default: 0) */
45
+ borderRadius?: number;
46
+ }
47
+
48
+ /**
49
+ * Scrollable container with touch/drag, mouse wheel, inertia, and optional scrollbar.
50
+ *
51
+ * Perfect for paytables, settings panels, bet history, and any scrollable content
52
+ * that doesn't fit on screen.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * const scroll = new ScrollContainer({
57
+ * width: 600,
58
+ * height: 400,
59
+ * direction: 'vertical',
60
+ * showScrollbar: true,
61
+ * elasticity: 0.3,
62
+ * });
63
+ *
64
+ * // Add content taller than 400px
65
+ * const list = new Container();
66
+ * for (let i = 0; i < 50; i++) {
67
+ * const row = createRow(i);
68
+ * row.y = i * 40;
69
+ * list.addChild(row);
70
+ * }
71
+ * scroll.setContent(list);
72
+ *
73
+ * scene.container.addChild(scroll);
74
+ * ```
75
+ */
76
+ export class ScrollContainer extends Container {
77
+ private _config: Required<
78
+ Pick<ScrollContainerConfig, 'width' | 'height' | 'direction' | 'showScrollbar' |
79
+ 'scrollbarWidth' | 'scrollbarColor' | 'scrollbarAlpha' | 'elasticity' | 'inertia' |
80
+ 'snapSize' | 'borderRadius'>
81
+ >;
82
+
83
+ private _viewport: Container;
84
+ private _content: Container | null = null;
85
+ private _mask: Graphics;
86
+ private _bg: Graphics;
87
+ private _scrollbarV: Graphics | null = null;
88
+ private _scrollbarH: Graphics | null = null;
89
+ private _scrollbarFadeTimeout: number | null = null;
90
+
91
+ // Scroll state
92
+ private _scrollX = 0;
93
+ private _scrollY = 0;
94
+ private _velocityX = 0;
95
+ private _velocityY = 0;
96
+ private _isDragging = false;
97
+ private _dragStart = { x: 0, y: 0 };
98
+ private _scrollStart = { x: 0, y: 0 };
99
+ private _lastDragPos = { x: 0, y: 0 };
100
+ private _lastDragTime = 0;
101
+ private _isAnimating = false;
102
+ private _animationFrame: number | null = null;
103
+
104
+ constructor(config: ScrollContainerConfig) {
105
+ super();
106
+
107
+ this._config = {
108
+ width: config.width,
109
+ height: config.height,
110
+ direction: config.direction ?? 'vertical',
111
+ showScrollbar: config.showScrollbar ?? true,
112
+ scrollbarWidth: config.scrollbarWidth ?? 6,
113
+ scrollbarColor: config.scrollbarColor ?? 0xffffff,
114
+ scrollbarAlpha: config.scrollbarAlpha ?? 0.4,
115
+ elasticity: config.elasticity ?? 0.3,
116
+ inertia: config.inertia ?? 0.92,
117
+ snapSize: config.snapSize ?? 0,
118
+ borderRadius: config.borderRadius ?? 0,
119
+ };
120
+
121
+ // Background
122
+ this._bg = new Graphics();
123
+ if (config.backgroundColor !== undefined) {
124
+ this._bg.roundRect(0, 0, config.width, config.height, this._config.borderRadius)
125
+ .fill({ color: config.backgroundColor, alpha: config.backgroundAlpha ?? 1 });
126
+ }
127
+ this.addChild(this._bg);
128
+
129
+ // Viewport (masked area)
130
+ this._viewport = new Container();
131
+ this.addChild(this._viewport);
132
+
133
+ // Mask
134
+ this._mask = new Graphics();
135
+ this._mask.roundRect(0, 0, config.width, config.height, this._config.borderRadius)
136
+ .fill(0xffffff);
137
+ this.addChild(this._mask);
138
+ this._viewport.mask = this._mask;
139
+
140
+ // Scrollbars
141
+ if (this._config.showScrollbar) {
142
+ if (this._config.direction !== 'horizontal') {
143
+ this._scrollbarV = new Graphics();
144
+ this._scrollbarV.alpha = 0;
145
+ this.addChild(this._scrollbarV);
146
+ }
147
+ if (this._config.direction !== 'vertical') {
148
+ this._scrollbarH = new Graphics();
149
+ this._scrollbarH.alpha = 0;
150
+ this.addChild(this._scrollbarH);
151
+ }
152
+ }
153
+
154
+ // Interaction
155
+ this.eventMode = 'static';
156
+ this.cursor = 'grab';
157
+ this.hitArea = { contains: (x: number, y: number) =>
158
+ x >= 0 && x <= config.width && y >= 0 && y <= config.height };
159
+
160
+ this.on('pointerdown', this.onPointerDown);
161
+ this.on('pointermove', this.onPointerMove);
162
+ this.on('pointerup', this.onPointerUp);
163
+ this.on('pointerupoutside', this.onPointerUp);
164
+ this.on('wheel', this.onWheel);
165
+ }
166
+
167
+ /** Set scrollable content. Replaces any existing content. */
168
+ setContent(content: Container): void {
169
+ if (this._content) {
170
+ this._viewport.removeChild(this._content);
171
+ }
172
+ this._content = content;
173
+ this._viewport.addChild(content);
174
+ this._scrollX = 0;
175
+ this._scrollY = 0;
176
+ this.applyScroll();
177
+ }
178
+
179
+ /** Get the content container */
180
+ get content(): Container | null {
181
+ return this._content;
182
+ }
183
+
184
+ /** Scroll to a specific position (in content coordinates) */
185
+ scrollTo(x: number, y: number, animate = true): void {
186
+ if (!animate) {
187
+ this._scrollX = x;
188
+ this._scrollY = y;
189
+ this.clampScroll();
190
+ this.applyScroll();
191
+ return;
192
+ }
193
+
194
+ this.animateScrollTo(x, y);
195
+ }
196
+
197
+ /** Scroll to make a specific item/child visible */
198
+ scrollToItem(index: number): void {
199
+ if (this._config.snapSize > 0) {
200
+ const pos = index * this._config.snapSize;
201
+ if (this._config.direction === 'horizontal') {
202
+ this.scrollTo(pos, this._scrollY);
203
+ } else {
204
+ this.scrollTo(this._scrollX, pos);
205
+ }
206
+ }
207
+ }
208
+
209
+ /** Current scroll position */
210
+ get scrollPosition(): { x: number; y: number } {
211
+ return { x: this._scrollX, y: this._scrollY };
212
+ }
213
+
214
+ /** Resize the scroll viewport */
215
+ resize(width: number, height: number): void {
216
+ this._config.width = width;
217
+ this._config.height = height;
218
+
219
+ // Redraw mask and background
220
+ this._mask.clear();
221
+ this._mask.roundRect(0, 0, width, height, this._config.borderRadius).fill(0xffffff);
222
+
223
+ this._bg.clear();
224
+
225
+ this.hitArea = { contains: (x: number, y: number) =>
226
+ x >= 0 && x <= width && y >= 0 && y <= height };
227
+
228
+ this.clampScroll();
229
+ this.applyScroll();
230
+ }
231
+
232
+ /** Destroy and clean up */
233
+ override destroy(options?: any): void {
234
+ this.stopAnimation();
235
+ if (this._scrollbarFadeTimeout !== null) {
236
+ clearTimeout(this._scrollbarFadeTimeout);
237
+ }
238
+ this.off('pointerdown', this.onPointerDown);
239
+ this.off('pointermove', this.onPointerMove);
240
+ this.off('pointerup', this.onPointerUp);
241
+ this.off('pointerupoutside', this.onPointerUp);
242
+ this.off('wheel', this.onWheel);
243
+ super.destroy(options);
244
+ }
245
+
246
+ // ─── Scroll mechanics ─────────────────────────────────
247
+
248
+ private get contentWidth(): number {
249
+ if (!this._content) return 0;
250
+ const bounds = this._content.getBounds();
251
+ return bounds.width;
252
+ }
253
+
254
+ private get contentHeight(): number {
255
+ if (!this._content) return 0;
256
+ const bounds = this._content.getBounds();
257
+ return bounds.height;
258
+ }
259
+
260
+ private get maxScrollX(): number {
261
+ return Math.max(0, this.contentWidth - this._config.width);
262
+ }
263
+
264
+ private get maxScrollY(): number {
265
+ return Math.max(0, this.contentHeight - this._config.height);
266
+ }
267
+
268
+ private canScrollX(): boolean {
269
+ return this._config.direction === 'horizontal' || this._config.direction === 'both';
270
+ }
271
+
272
+ private canScrollY(): boolean {
273
+ return this._config.direction === 'vertical' || this._config.direction === 'both';
274
+ }
275
+
276
+ private clampScroll(): void {
277
+ if (this.canScrollX()) {
278
+ this._scrollX = Math.max(0, Math.min(this._scrollX, this.maxScrollX));
279
+ } else {
280
+ this._scrollX = 0;
281
+ }
282
+ if (this.canScrollY()) {
283
+ this._scrollY = Math.max(0, Math.min(this._scrollY, this.maxScrollY));
284
+ } else {
285
+ this._scrollY = 0;
286
+ }
287
+ }
288
+
289
+ private applyScroll(): void {
290
+ if (!this._content) return;
291
+ this._content.x = -this._scrollX;
292
+ this._content.y = -this._scrollY;
293
+ this.updateScrollbars();
294
+ }
295
+
296
+ // ─── Input handlers ────────────────────────────────────
297
+
298
+ private onPointerDown = (e: any): void => {
299
+ this._isDragging = true;
300
+ this._isAnimating = false;
301
+ this.stopAnimation();
302
+ this.cursor = 'grabbing';
303
+
304
+ const local = e.getLocalPosition(this);
305
+ this._dragStart = { x: local.x, y: local.y };
306
+ this._scrollStart = { x: this._scrollX, y: this._scrollY };
307
+ this._lastDragPos = { x: local.x, y: local.y };
308
+ this._lastDragTime = Date.now();
309
+ this._velocityX = 0;
310
+ this._velocityY = 0;
311
+
312
+ this.showScrollbars();
313
+ };
314
+
315
+ private onPointerMove = (e: any): void => {
316
+ if (!this._isDragging) return;
317
+
318
+ const local = e.getLocalPosition(this);
319
+ const dx = local.x - this._dragStart.x;
320
+ const dy = local.y - this._dragStart.y;
321
+ const now = Date.now();
322
+ const dt = Math.max(1, now - this._lastDragTime);
323
+
324
+ // Calculate velocity for inertia
325
+ this._velocityX = (local.x - this._lastDragPos.x) / dt * 16; // normalize to ~60fps
326
+ this._velocityY = (local.y - this._lastDragPos.y) / dt * 16;
327
+
328
+ this._lastDragPos = { x: local.x, y: local.y };
329
+ this._lastDragTime = now;
330
+
331
+ // Apply scroll with elasticity for overscroll
332
+ let newX = this._scrollStart.x - dx;
333
+ let newY = this._scrollStart.y - dy;
334
+
335
+ const elasticity = this._config.elasticity;
336
+ if (this.canScrollX()) {
337
+ if (newX < 0) newX *= elasticity;
338
+ else if (newX > this.maxScrollX) newX = this.maxScrollX + (newX - this.maxScrollX) * elasticity;
339
+ this._scrollX = newX;
340
+ }
341
+ if (this.canScrollY()) {
342
+ if (newY < 0) newY *= elasticity;
343
+ else if (newY > this.maxScrollY) newY = this.maxScrollY + (newY - this.maxScrollY) * elasticity;
344
+ this._scrollY = newY;
345
+ }
346
+
347
+ this.applyScroll();
348
+ };
349
+
350
+ private onPointerUp = (): void => {
351
+ if (!this._isDragging) return;
352
+ this._isDragging = false;
353
+ this.cursor = 'grab';
354
+
355
+ // Start inertia
356
+ if (Math.abs(this._velocityX) > 0.5 || Math.abs(this._velocityY) > 0.5) {
357
+ this.startInertia();
358
+ } else {
359
+ this.snapAndBounce();
360
+ }
361
+ };
362
+
363
+ private onWheel = (e: any): void => {
364
+ e.preventDefault?.();
365
+ const delta = e.deltaY ?? 0;
366
+ const deltaX = e.deltaX ?? 0;
367
+
368
+ if (this.canScrollY()) {
369
+ this._scrollY += delta * 0.5;
370
+ }
371
+ if (this.canScrollX()) {
372
+ this._scrollX += deltaX * 0.5;
373
+ }
374
+
375
+ this.clampScroll();
376
+ this.applyScroll();
377
+ this.showScrollbars();
378
+ this.scheduleScrollbarFade();
379
+ };
380
+
381
+ // ─── Inertia & snap ───────────────────────────────────
382
+
383
+ private startInertia(): void {
384
+ this._isAnimating = true;
385
+
386
+ const tick = () => {
387
+ if (!this._isAnimating) return;
388
+
389
+ this._velocityX *= this._config.inertia;
390
+ this._velocityY *= this._config.inertia;
391
+
392
+ if (this.canScrollX()) this._scrollX -= this._velocityX;
393
+ if (this.canScrollY()) this._scrollY -= this._velocityY;
394
+
395
+ // Bounce back if overscrolled
396
+ let bounced = false;
397
+ if (this.canScrollX()) {
398
+ if (this._scrollX < 0) { this._scrollX *= 0.8; bounced = true; }
399
+ else if (this._scrollX > this.maxScrollX) {
400
+ this._scrollX = this.maxScrollX + (this._scrollX - this.maxScrollX) * 0.8;
401
+ bounced = true;
402
+ }
403
+ }
404
+ if (this.canScrollY()) {
405
+ if (this._scrollY < 0) { this._scrollY *= 0.8; bounced = true; }
406
+ else if (this._scrollY > this.maxScrollY) {
407
+ this._scrollY = this.maxScrollY + (this._scrollY - this.maxScrollY) * 0.8;
408
+ bounced = true;
409
+ }
410
+ }
411
+
412
+ this.applyScroll();
413
+
414
+ const speed = Math.abs(this._velocityX) + Math.abs(this._velocityY);
415
+ if (speed < 0.1 && !bounced) {
416
+ this._isAnimating = false;
417
+ this.snapAndBounce();
418
+ return;
419
+ }
420
+
421
+ this._animationFrame = requestAnimationFrame(tick);
422
+ };
423
+
424
+ this._animationFrame = requestAnimationFrame(tick);
425
+ }
426
+
427
+ private snapAndBounce(): void {
428
+ // Clamp first
429
+ let targetX = Math.max(0, Math.min(this._scrollX, this.maxScrollX));
430
+ let targetY = Math.max(0, Math.min(this._scrollY, this.maxScrollY));
431
+
432
+ // Snap
433
+ if (this._config.snapSize > 0) {
434
+ if (this.canScrollY()) {
435
+ targetY = Math.round(targetY / this._config.snapSize) * this._config.snapSize;
436
+ targetY = Math.max(0, Math.min(targetY, this.maxScrollY));
437
+ }
438
+ if (this.canScrollX()) {
439
+ targetX = Math.round(targetX / this._config.snapSize) * this._config.snapSize;
440
+ targetX = Math.max(0, Math.min(targetX, this.maxScrollX));
441
+ }
442
+ }
443
+
444
+ if (Math.abs(targetX - this._scrollX) < 0.5 && Math.abs(targetY - this._scrollY) < 0.5) {
445
+ this._scrollX = targetX;
446
+ this._scrollY = targetY;
447
+ this.applyScroll();
448
+ this.scheduleScrollbarFade();
449
+ return;
450
+ }
451
+
452
+ this.animateScrollTo(targetX, targetY);
453
+ }
454
+
455
+ private animateScrollTo(targetX: number, targetY: number): void {
456
+ this._isAnimating = true;
457
+ const startX = this._scrollX;
458
+ const startY = this._scrollY;
459
+ const startTime = Date.now();
460
+ const duration = 300;
461
+
462
+ const tick = () => {
463
+ if (!this._isAnimating) return;
464
+
465
+ const elapsed = Date.now() - startTime;
466
+ const t = Math.min(elapsed / duration, 1);
467
+ // easeOutCubic
468
+ const eased = 1 - Math.pow(1 - t, 3);
469
+
470
+ this._scrollX = startX + (targetX - startX) * eased;
471
+ this._scrollY = startY + (targetY - startY) * eased;
472
+ this.applyScroll();
473
+
474
+ if (t < 1) {
475
+ this._animationFrame = requestAnimationFrame(tick);
476
+ } else {
477
+ this._isAnimating = false;
478
+ this.scheduleScrollbarFade();
479
+ }
480
+ };
481
+
482
+ this._animationFrame = requestAnimationFrame(tick);
483
+ }
484
+
485
+ private stopAnimation(): void {
486
+ this._isAnimating = false;
487
+ if (this._animationFrame !== null) {
488
+ cancelAnimationFrame(this._animationFrame);
489
+ this._animationFrame = null;
490
+ }
491
+ }
492
+
493
+ // ─── Scrollbars ────────────────────────────────────────
494
+
495
+ private updateScrollbars(): void {
496
+ const { width, height, scrollbarWidth, scrollbarColor, scrollbarAlpha } = this._config;
497
+
498
+ if (this._scrollbarV && this.canScrollY() && this.contentHeight > height) {
499
+ const ratio = height / this.contentHeight;
500
+ const barH = Math.max(20, height * ratio);
501
+ const barY = (this._scrollY / this.maxScrollY) * (height - barH);
502
+
503
+ this._scrollbarV.clear();
504
+ this._scrollbarV.roundRect(
505
+ width - scrollbarWidth - 2,
506
+ Math.max(0, barY),
507
+ scrollbarWidth,
508
+ barH,
509
+ scrollbarWidth / 2,
510
+ ).fill({ color: scrollbarColor, alpha: scrollbarAlpha });
511
+ }
512
+
513
+ if (this._scrollbarH && this.canScrollX() && this.contentWidth > width) {
514
+ const ratio = width / this.contentWidth;
515
+ const barW = Math.max(20, width * ratio);
516
+ const barX = (this._scrollX / this.maxScrollX) * (width - barW);
517
+
518
+ this._scrollbarH.clear();
519
+ this._scrollbarH.roundRect(
520
+ Math.max(0, barX),
521
+ height - scrollbarWidth - 2,
522
+ barW,
523
+ scrollbarWidth,
524
+ scrollbarWidth / 2,
525
+ ).fill({ color: scrollbarColor, alpha: scrollbarAlpha });
526
+ }
527
+ }
528
+
529
+ private showScrollbars(): void {
530
+ if (this._scrollbarV) this._scrollbarV.alpha = 1;
531
+ if (this._scrollbarH) this._scrollbarH.alpha = 1;
532
+ }
533
+
534
+ private scheduleScrollbarFade(): void {
535
+ if (this._scrollbarFadeTimeout !== null) {
536
+ clearTimeout(this._scrollbarFadeTimeout);
537
+ }
538
+ this._scrollbarFadeTimeout = window.setTimeout(() => {
539
+ this.fadeScrollbars();
540
+ }, 1000);
541
+ }
542
+
543
+ private fadeScrollbars(): void {
544
+ const duration = 300;
545
+ const startTime = Date.now();
546
+ const startAlphaV = this._scrollbarV?.alpha ?? 0;
547
+ const startAlphaH = this._scrollbarH?.alpha ?? 0;
548
+
549
+ const tick = () => {
550
+ const t = Math.min((Date.now() - startTime) / duration, 1);
551
+ if (this._scrollbarV) this._scrollbarV.alpha = startAlphaV * (1 - t);
552
+ if (this._scrollbarH) this._scrollbarH.alpha = startAlphaH * (1 - t);
553
+ if (t < 1) requestAnimationFrame(tick);
554
+ };
555
+ requestAnimationFrame(tick);
556
+ }
557
+ }
package/src/ui/index.ts CHANGED
@@ -14,3 +14,7 @@ export { Modal } from './Modal';
14
14
  export type { ModalConfig } from './Modal';
15
15
  export { Toast } from './Toast';
16
16
  export type { ToastConfig, ToastType } from './Toast';
17
+ export { Layout } from './Layout';
18
+ export type { LayoutConfig, LayoutDirection, LayoutAlignment, LayoutAnchor } from './Layout';
19
+ export { ScrollContainer } from './ScrollContainer';
20
+ export type { ScrollContainerConfig, ScrollDirection } from './ScrollContainer';
@@ -205,6 +205,8 @@ export class ViewportManager extends EventEmitter<ViewportEvents> {
205
205
  this._destroyed = true;
206
206
  this._resizeObserver?.disconnect();
207
207
  this._resizeObserver = null;
208
+ // Remove fallback window resize listener if it was used
209
+ window.removeEventListener('resize', this.onWindowResize);
208
210
  if (this._resizeTimeout !== null) {
209
211
  clearTimeout(this._resizeTimeout);
210
212
  }