@energy8platform/game-engine 0.20.0 → 0.22.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 (93) hide show
  1. package/README.md +121 -0
  2. package/dist/animation.cjs.js +18 -3
  3. package/dist/animation.cjs.js.map +1 -1
  4. package/dist/animation.esm.js +18 -3
  5. package/dist/animation.esm.js.map +1 -1
  6. package/dist/core.cjs.js +18 -3
  7. package/dist/core.cjs.js.map +1 -1
  8. package/dist/core.esm.js +18 -3
  9. package/dist/core.esm.js.map +1 -1
  10. package/dist/host.cjs.js +18 -3
  11. package/dist/host.cjs.js.map +1 -1
  12. package/dist/host.d.ts +3 -5
  13. package/dist/host.esm.js +18 -3
  14. package/dist/host.esm.js.map +1 -1
  15. package/dist/index.cjs.js +18 -2535
  16. package/dist/index.cjs.js.map +1 -1
  17. package/dist/index.d.ts +3 -1020
  18. package/dist/index.esm.js +20 -2525
  19. package/dist/index.esm.js.map +1 -1
  20. package/dist/slot.cjs.js +1872 -61
  21. package/dist/slot.cjs.js.map +1 -1
  22. package/dist/slot.d.ts +555 -28
  23. package/dist/slot.esm.js +1857 -62
  24. package/dist/slot.esm.js.map +1 -1
  25. package/dist/vite.cjs.js +0 -5
  26. package/dist/vite.cjs.js.map +1 -1
  27. package/dist/vite.esm.js +0 -5
  28. package/dist/vite.esm.js.map +1 -1
  29. package/package.json +2 -35
  30. package/src/animation/Tween.ts +14 -3
  31. package/src/host/index.ts +0 -1
  32. package/src/host/types.ts +0 -3
  33. package/src/index.ts +0 -28
  34. package/src/slot/anim/easing-map.ts +16 -2
  35. package/src/slot/cascade/TumbleController.ts +230 -0
  36. package/src/slot/config/ReelSystemConfig.ts +559 -0
  37. package/src/slot/config/presets.ts +222 -0
  38. package/src/slot/features/extra.ts +189 -0
  39. package/src/slot/features/index.ts +44 -0
  40. package/src/slot/features/symbols.ts +183 -0
  41. package/src/slot/features/types.ts +136 -0
  42. package/src/slot/features/wilds.ts +151 -0
  43. package/src/slot/grid/ReelGrid.ts +93 -24
  44. package/src/slot/grid/SymbolCell.ts +45 -11
  45. package/src/slot/index.ts +61 -2
  46. package/src/slot/motion/AnticipationController.ts +96 -0
  47. package/src/slot/motion/SpinEngine.ts +384 -0
  48. package/src/slot/system/ReelSystem.ts +295 -0
  49. package/src/vite/index.ts +0 -5
  50. package/dist/react-jsx.cjs.js +0 -3
  51. package/dist/react-jsx.cjs.js.map +0 -1
  52. package/dist/react-jsx.d.ts +0 -602
  53. package/dist/react-jsx.esm.js +0 -2
  54. package/dist/react-jsx.esm.js.map +0 -1
  55. package/dist/react.cjs.js +0 -3787
  56. package/dist/react.cjs.js.map +0 -1
  57. package/dist/react.d.ts +0 -1467
  58. package/dist/react.esm.js +0 -3771
  59. package/dist/react.esm.js.map +0 -1
  60. package/dist/ui.cjs.js +0 -2999
  61. package/dist/ui.cjs.js.map +0 -1
  62. package/dist/ui.d.ts +0 -1116
  63. package/dist/ui.esm.js +0 -2983
  64. package/dist/ui.esm.js.map +0 -1
  65. package/src/react/EngineContext.ts +0 -26
  66. package/src/react/ReactScene.ts +0 -88
  67. package/src/react/applyProps.ts +0 -271
  68. package/src/react/catalogue.ts +0 -17
  69. package/src/react/createPixiRoot.ts +0 -31
  70. package/src/react/extendAll.ts +0 -74
  71. package/src/react/hooks.ts +0 -46
  72. package/src/react/index.ts +0 -29
  73. package/src/react/jsx-runtime.ts +0 -346
  74. package/src/react/reconciler.ts +0 -338
  75. package/src/slot/freeSpins/FreeSpinsSession.ts +0 -40
  76. package/src/state/StateMachine.ts +0 -231
  77. package/src/state/index.ts +0 -1
  78. package/src/ui/BalanceDisplay.ts +0 -159
  79. package/src/ui/Button.ts +0 -304
  80. package/src/ui/FlexContainer.ts +0 -775
  81. package/src/ui/Label.ts +0 -124
  82. package/src/ui/LabelValue.ts +0 -122
  83. package/src/ui/Layout.ts +0 -291
  84. package/src/ui/Modal.ts +0 -170
  85. package/src/ui/Panel.ts +0 -204
  86. package/src/ui/ProgressBar.ts +0 -170
  87. package/src/ui/ScrollContainer.ts +0 -478
  88. package/src/ui/Slider.ts +0 -241
  89. package/src/ui/Toast.ts +0 -150
  90. package/src/ui/Toggle.ts +0 -201
  91. package/src/ui/WinDisplay.ts +0 -145
  92. package/src/ui/index.ts +0 -33
  93. package/src/ui/view.ts +0 -28
package/src/ui/Label.ts DELETED
@@ -1,124 +0,0 @@
1
- import { Container, Text, TextStyle } from 'pixi.js';
2
-
3
- export interface LabelConfig {
4
- text?: string;
5
- style?: Partial<TextStyle>;
6
- /** Maximum width — text will be scaled down to fit */
7
- maxWidth?: number;
8
- /** Auto-fit: scale text to fit maxWidth */
9
- autoFit?: boolean;
10
- }
11
-
12
- /**
13
- * Enhanced text label with auto-fit scaling and currency formatting.
14
- *
15
- * @example
16
- * ```ts
17
- * const label = new Label({
18
- * text: 'BALANCE',
19
- * style: { fontSize: 24, fill: 0xffd700 },
20
- * maxWidth: 200,
21
- * autoFit: true,
22
- * });
23
- * ```
24
- */
25
- export class Label extends Container {
26
- readonly __uiComponent = true as const;
27
-
28
- private _text: Text;
29
- private _maxWidth: number;
30
- private _autoFit: boolean;
31
-
32
- constructor(config: LabelConfig = {}) {
33
- super();
34
-
35
- this._maxWidth = config.maxWidth ?? Infinity;
36
- this._autoFit = config.autoFit ?? false;
37
-
38
- this._text = new Text({
39
- text: config.text ?? '',
40
- style: {
41
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
42
- fontSize: 24,
43
- fill: 0xffffff,
44
- ...config.style,
45
- },
46
- });
47
- this._text.anchor.set(0.5);
48
- this.addChild(this._text);
49
-
50
- this.fitText();
51
- }
52
-
53
- /** Get/set the displayed text */
54
- get text(): string {
55
- return this._text.text;
56
- }
57
-
58
- set text(value: string) {
59
- this._text.text = value;
60
- this.fitText();
61
- }
62
-
63
- /** Get/set the text style */
64
- get style(): TextStyle {
65
- return this._text.style as TextStyle;
66
- }
67
-
68
- /** Set max width constraint */
69
- set maxWidth(value: number) {
70
- this._maxWidth = value;
71
- this.fitText();
72
- }
73
-
74
- /**
75
- * Format and display a number as currency.
76
- *
77
- * @param amount - The numeric amount
78
- * @param currency - Currency code (e.g., 'USD', 'EUR')
79
- * @param locale - Locale string (default: 'en-US')
80
- */
81
- setCurrency(amount: number, currency: string, locale = 'en-US'): void {
82
- try {
83
- this.text = new Intl.NumberFormat(locale, {
84
- style: 'currency',
85
- currency,
86
- minimumFractionDigits: 2,
87
- maximumFractionDigits: 2,
88
- }).format(amount);
89
- } catch {
90
- this.text = `${amount.toFixed(2)} ${currency}`;
91
- }
92
- }
93
-
94
- /**
95
- * Format a number with thousands separators.
96
- */
97
- setNumber(value: number, decimals = 0, locale = 'en-US'): void {
98
- this.text = new Intl.NumberFormat(locale, {
99
- minimumFractionDigits: decimals,
100
- maximumFractionDigits: decimals,
101
- }).format(value);
102
- }
103
-
104
- /** React reconciler update hook */
105
- updateConfig(changed: Record<string, any>): void {
106
- if ('text' in changed) this.text = changed.text;
107
- if ('maxWidth' in changed) this.maxWidth = changed.maxWidth;
108
- if ('autoFit' in changed) { this._autoFit = changed.autoFit; this.fitText(); }
109
- if ('style' in changed && typeof changed.style === 'object') {
110
- Object.assign(this._text.style, changed.style);
111
- this.fitText();
112
- }
113
- }
114
-
115
- private fitText(): void {
116
- if (!this._autoFit || this._maxWidth === Infinity) return;
117
-
118
- this._text.scale.set(1);
119
- if (this._text.width > this._maxWidth) {
120
- const scale = this._maxWidth / this._text.width;
121
- this._text.scale.set(scale);
122
- }
123
- }
124
- }
@@ -1,122 +0,0 @@
1
- import type { TextStyle } from 'pixi.js';
2
- import { FlexContainer, type FlexContainerConfig } from './FlexContainer';
3
- import { Label } from './Label';
4
-
5
- export type LabelValueAlign = 'start' | 'center' | 'end';
6
-
7
- export interface LabelValueConfig {
8
- /** Caption text (top row, typically smaller/muted) */
9
- label?: string;
10
- /** Value text (bottom row, typically larger/prominent) */
11
- value?: string;
12
- /** Style for the caption Label */
13
- labelStyle?: Partial<TextStyle>;
14
- /** Style for the value Label */
15
- valueStyle?: Partial<TextStyle>;
16
- /** Gap in pixels between caption and value (default: 4) */
17
- gap?: number;
18
- /** Horizontal alignment of both rows (default: 'center') */
19
- align?: LabelValueAlign;
20
- /** Maximum width — value will be auto-scaled to fit when exceeded */
21
- maxWidth?: number;
22
- /** Optional padding (forwarded to the underlying FlexContainer) */
23
- padding?: FlexContainerConfig['padding'];
24
- }
25
-
26
- /**
27
- * Two-row text cell with a muted caption above and a prominent value below —
28
- * the archetypal casino UI pattern (BALANCE/€500, BET/€1, WIN/€0.00).
29
- *
30
- * Extends FlexContainer, so it participates in parent flex layouts directly
31
- * (you can pass `flexGrow`, `alignSelf`, etc. when it's a child of another
32
- * FlexContainer) and auto-sizes to its contents when no explicit size is set.
33
- *
34
- * @example
35
- * ```tsx
36
- * <labelValue
37
- * label="BALANCE"
38
- * value={`€${balance.toFixed(2)}`}
39
- * labelStyle={{ fontSize: 12, fill: 0x888888 }}
40
- * valueStyle={{ fontSize: 22, fill: 0xffffff, fontWeight: 'bold' }}
41
- * gap={6}
42
- * align="center"
43
- * />
44
- * ```
45
- */
46
- export class LabelValue extends FlexContainer {
47
- private readonly _labelEl: Label;
48
- private readonly _valueEl: Label;
49
- private _valueMaxWidth: number;
50
-
51
- constructor(config: LabelValueConfig = {}) {
52
- super({
53
- direction: 'column',
54
- alignItems: toAlignItems(config.align ?? 'center'),
55
- gap: config.gap ?? 4,
56
- padding: config.padding,
57
- });
58
-
59
- this._valueMaxWidth = config.maxWidth ?? Infinity;
60
-
61
- this._labelEl = new Label({
62
- text: config.label ?? '',
63
- style: config.labelStyle,
64
- });
65
- this._valueEl = new Label({
66
- text: config.value ?? '',
67
- style: config.valueStyle,
68
- maxWidth: config.maxWidth,
69
- autoFit: config.maxWidth !== undefined,
70
- });
71
-
72
- this.addFlexChild(this._labelEl);
73
- this.addFlexChild(this._valueEl);
74
- }
75
-
76
- /** Get the caption Label (for advanced styling / animation) */
77
- get labelElement(): Label {
78
- return this._labelEl;
79
- }
80
-
81
- /** Get the value Label */
82
- get valueElement(): Label {
83
- return this._valueEl;
84
- }
85
-
86
- /** Update caption text */
87
- setLabel(text: string): void {
88
- this._labelEl.text = text;
89
- this.updateLayout();
90
- }
91
-
92
- /** Update value text */
93
- setValue(text: string): void {
94
- this._valueEl.text = text;
95
- this.updateLayout();
96
- }
97
-
98
- /** React reconciler update hook */
99
- override updateConfig(changed: Record<string, any>): void {
100
- if ('label' in changed) this._labelEl.text = changed.label ?? '';
101
- if ('value' in changed) this._valueEl.text = changed.value ?? '';
102
- if ('labelStyle' in changed && typeof changed.labelStyle === 'object') {
103
- Object.assign(this._labelEl.style, changed.labelStyle);
104
- }
105
- if ('valueStyle' in changed && typeof changed.valueStyle === 'object') {
106
- Object.assign(this._valueEl.style, changed.valueStyle);
107
- }
108
- if ('maxWidth' in changed) {
109
- this._valueMaxWidth = changed.maxWidth ?? Infinity;
110
- this._valueEl.maxWidth = this._valueMaxWidth;
111
- }
112
- if ('gap' in changed) this.setGap(changed.gap);
113
- if ('align' in changed) this.setAlignItems(toAlignItems(changed.align));
114
-
115
- // Forward remaining FlexContainer props (e.g. padding, width, height)
116
- super.updateConfig(changed);
117
- }
118
- }
119
-
120
- function toAlignItems(align: LabelValueAlign): 'start' | 'center' | 'end' {
121
- return align;
122
- }
package/src/ui/Layout.ts DELETED
@@ -1,291 +0,0 @@
1
- import { Container } from 'pixi.js';
2
- import { FlexContainer } from './FlexContainer';
3
- import type { FlexDirection, AlignItems, FlexItemConfig } from './FlexContainer';
4
-
5
- // ─── Types ───────────────────────────────────────────────
6
-
7
- export type LayoutDirection = 'horizontal' | 'vertical' | 'grid' | 'wrap';
8
- export type LayoutAlignment = 'start' | 'center' | 'end' | 'stretch';
9
- export type LayoutAnchor =
10
- | 'top-left' | 'top-center' | 'top-right'
11
- | 'center-left' | 'center' | 'center-right'
12
- | 'bottom-left' | 'bottom-center' | 'bottom-right';
13
-
14
- export interface LayoutConfig {
15
- /** Layout direction (default: 'vertical') */
16
- direction?: LayoutDirection;
17
-
18
- /** Gap between children in pixels (default: 0) */
19
- gap?: number;
20
-
21
- /** Padding inside the layout container [top, right, bottom, left] or a single number */
22
- padding?: number | [number, number, number, number];
23
-
24
- /** Alignment of children on the cross axis (default: 'start') */
25
- alignment?: LayoutAlignment;
26
-
27
- /** Anchor point determining where the layout is positioned relative to its coordinates */
28
- anchor?: LayoutAnchor;
29
-
30
- /** Number of columns (only for 'grid' direction) */
31
- columns?: number;
32
-
33
- /** Maximum width for 'wrap' direction before wrapping to next line */
34
- maxWidth?: number;
35
-
36
- /** Whether to auto-layout when children change (default: true) */
37
- autoLayout?: boolean;
38
-
39
- /** Breakpoints: map of max viewport widths to override configs */
40
- breakpoints?: Record<number, Partial<LayoutConfig>>;
41
- }
42
-
43
- // ─── Helpers ─────────────────────────────────────────────
44
-
45
- function directionToFlex(direction: LayoutDirection): { direction: FlexDirection; wrap: boolean } {
46
- switch (direction) {
47
- case 'horizontal': return { direction: 'row', wrap: false };
48
- case 'vertical': return { direction: 'column', wrap: false };
49
- case 'grid': return { direction: 'row', wrap: true };
50
- case 'wrap': return { direction: 'row', wrap: true };
51
- }
52
- }
53
-
54
- /**
55
- * Responsive layout container powered by a lightweight built-in flex layout solver.
56
- *
57
- * Supports horizontal, vertical, grid, and wrap layout modes with
58
- * alignment, padding, gap, and viewport-anchor positioning.
59
- * Breakpoints allow different layouts for different screen sizes.
60
- *
61
- * @example
62
- * ```ts
63
- * const toolbar = new Layout({
64
- * direction: 'horizontal',
65
- * gap: 20,
66
- * alignment: 'center',
67
- * anchor: 'bottom-center',
68
- * padding: 16,
69
- * breakpoints: {
70
- * 768: { direction: 'vertical', gap: 10 },
71
- * },
72
- * });
73
- *
74
- * toolbar.addItem(spinButton);
75
- * toolbar.addItem(betLabel);
76
- * scene.container.addChild(toolbar);
77
- *
78
- * toolbar.updateViewport(width, height);
79
- * ```
80
- */
81
- export class Layout extends Container {
82
- readonly __uiComponent = true as const;
83
-
84
- private _layoutConfig: Required<Pick<LayoutConfig, 'direction' | 'gap' | 'alignment' | 'autoLayout' | 'columns'>>;
85
- private _padding: number | [number, number, number, number];
86
- private _anchor: LayoutAnchor;
87
- private _maxWidth: number;
88
- private _breakpoints: [number, Partial<LayoutConfig>][];
89
- private _items: Container[] = [];
90
- private _viewportWidth = 0;
91
- private _viewportHeight = 0;
92
- private _flex: FlexContainer;
93
-
94
- constructor(config: LayoutConfig = {}) {
95
- super();
96
-
97
- this._layoutConfig = {
98
- direction: config.direction ?? 'vertical',
99
- gap: config.gap ?? 0,
100
- alignment: config.alignment ?? 'start',
101
- autoLayout: config.autoLayout ?? true,
102
- columns: config.columns ?? 2,
103
- };
104
-
105
- this._padding = config.padding ?? 0;
106
- this._anchor = config.anchor ?? 'top-left';
107
- this._maxWidth = config.maxWidth ?? Infinity;
108
-
109
- this._breakpoints = config.breakpoints
110
- ? Object.entries(config.breakpoints)
111
- .map(([w, cfg]) => [Number(w), cfg] as [number, Partial<LayoutConfig>])
112
- .sort((a, b) => a[0] - b[0])
113
- : [];
114
-
115
- // Create internal FlexContainer
116
- this._flex = new FlexContainer();
117
- super.addChild(this._flex);
118
-
119
- this.applyLayoutStyles();
120
- }
121
-
122
- /** Add an item to the layout */
123
- addItem(child: Container): this {
124
- this._items.push(child);
125
-
126
- const flexConfig = this.buildFlexItemConfig(child);
127
- this._flex.addFlexChild(child, flexConfig);
128
-
129
- if (this._layoutConfig.autoLayout) {
130
- this.applyLayoutStyles();
131
- }
132
-
133
- return this;
134
- }
135
-
136
- /** Remove an item from the layout */
137
- removeItem(child: Container): this {
138
- const idx = this._items.indexOf(child);
139
- if (idx !== -1) {
140
- this._items.splice(idx, 1);
141
- this._flex.removeFlexChild(child);
142
- }
143
- return this;
144
- }
145
-
146
- /** Remove all items */
147
- clearItems(): this {
148
- this._flex.clearFlexChildren();
149
- this._items.length = 0;
150
- return this;
151
- }
152
-
153
- /** Get all layout items */
154
- get items(): readonly Container[] {
155
- return this._items;
156
- }
157
-
158
- /**
159
- * Update the viewport size and recalculate layout.
160
- * Should be called from `Scene.onResize()`.
161
- */
162
- updateViewport(width: number, height: number): void {
163
- this._viewportWidth = width;
164
- this._viewportHeight = height;
165
- this.applyLayoutStyles();
166
- this.applyAnchor();
167
- }
168
-
169
- private applyLayoutStyles(): void {
170
- const effective = this.resolveConfig();
171
- const direction = effective.direction ?? this._layoutConfig.direction;
172
- const gap = effective.gap ?? this._layoutConfig.gap;
173
- const alignment = effective.alignment ?? this._layoutConfig.alignment;
174
- const padding = effective.padding ?? this._padding;
175
- const maxWidth = effective.maxWidth ?? this._maxWidth;
176
-
177
- const { direction: flexDir, wrap } = directionToFlex(direction);
178
-
179
- this._flex.setDirection(flexDir);
180
- this._flex.setJustifyContent('start');
181
- this._flex.setAlignItems(alignment as AlignItems);
182
- this._flex.setGap(gap);
183
- this._flex.setPadding(padding);
184
-
185
- // Wrap and maxWidth
186
- if (wrap) {
187
- (this._flex as any)._config.flexWrap = true;
188
- if (direction === 'grid' && maxWidth < Infinity) {
189
- (this._flex as any)._maxWidth = maxWidth;
190
- } else if (direction === 'grid') {
191
- // For grid without explicit maxWidth, we don't constrain wrapping —
192
- // each child gets a proportional width via flexConfig
193
- }
194
- if (maxWidth < Infinity) {
195
- (this._flex as any)._maxWidth = maxWidth;
196
- }
197
- } else {
198
- (this._flex as any)._config.flexWrap = false;
199
- }
200
-
201
- // Update grid child widths
202
- if (direction === 'grid') {
203
- for (const item of this._items) {
204
- const flexConfig = this.buildFlexItemConfig(item);
205
- (item as any)._flexConfig = flexConfig;
206
- }
207
- }
208
-
209
- // Set explicit size if we have viewport dimensions
210
- if (this._viewportWidth > 0 && this._viewportHeight > 0) {
211
- this._flex.resize(this._viewportWidth, this._viewportHeight);
212
- } else {
213
- this._flex.updateLayout();
214
- }
215
- }
216
-
217
- private buildFlexItemConfig(_child: Container): FlexItemConfig | undefined {
218
- const effective = this.resolveConfig();
219
- const direction = effective.direction ?? this._layoutConfig.direction;
220
- const columns = effective.columns ?? this._layoutConfig.columns;
221
-
222
- if (direction === 'grid' && columns > 0) {
223
- // For grid, give each item a proportional width
224
- // The actual pixel width will be computed during layout
225
- return { flexGrow: 1 };
226
- }
227
-
228
- return undefined;
229
- }
230
-
231
- private applyAnchor(): void {
232
- const anchor = this.resolveConfig().anchor ?? this._anchor;
233
- if (this._viewportWidth === 0 || this._viewportHeight === 0) return;
234
-
235
- const { width: contentW, height: contentH } = this._flex.getContentSize();
236
- const vw = this._viewportWidth;
237
- const vh = this._viewportHeight;
238
-
239
- let anchorX = 0;
240
- let anchorY = 0;
241
-
242
- if (anchor.includes('left')) {
243
- anchorX = 0;
244
- } else if (anchor.includes('right')) {
245
- anchorX = vw - contentW;
246
- } else {
247
- anchorX = (vw - contentW) / 2;
248
- }
249
-
250
- if (anchor.startsWith('top')) {
251
- anchorY = 0;
252
- } else if (anchor.startsWith('bottom')) {
253
- anchorY = vh - contentH;
254
- } else {
255
- anchorY = (vh - contentH) / 2;
256
- }
257
-
258
- this.x = anchorX;
259
- this.y = anchorY;
260
- }
261
-
262
- private resolveConfig(): Partial<LayoutConfig> {
263
- if (this._breakpoints.length === 0 || this._viewportWidth === 0) {
264
- return {};
265
- }
266
-
267
- for (const [maxWidth, overrides] of this._breakpoints) {
268
- if (this._viewportWidth <= maxWidth) {
269
- return overrides;
270
- }
271
- }
272
- return {};
273
- }
274
-
275
- /** React reconciler update hook */
276
- updateConfig(changed: Record<string, any>): void {
277
- if ('direction' in changed) this._layoutConfig.direction = changed.direction;
278
- if ('gap' in changed) this._layoutConfig.gap = changed.gap;
279
- if ('alignment' in changed) this._layoutConfig.alignment = changed.alignment;
280
- if ('anchor' in changed) this._anchor = changed.anchor;
281
- if ('padding' in changed) this._padding = changed.padding;
282
- if ('columns' in changed) this._layoutConfig.columns = changed.columns;
283
- this.applyLayoutStyles();
284
- if (this._viewportWidth > 0) this.applyAnchor();
285
- }
286
-
287
- override destroy(options?: boolean | { children?: boolean; texture?: boolean; textureSource?: boolean }): void {
288
- this._items.length = 0;
289
- super.destroy(options);
290
- }
291
- }
package/src/ui/Modal.ts DELETED
@@ -1,170 +0,0 @@
1
- import { Container, Graphics } from 'pixi.js';
2
- import { Tween } from '../animation/Tween';
3
- import { Easing } from '../animation/Easing';
4
-
5
- export interface ModalConfig {
6
- /** Overlay color */
7
- overlayColor?: number;
8
- /** Overlay alpha */
9
- overlayAlpha?: number;
10
- /** Close on overlay tap */
11
- closeOnOverlay?: boolean;
12
- /** Animation duration */
13
- animationDuration?: number;
14
- }
15
-
16
- /**
17
- * Modal overlay component.
18
- * Shows content on top of a dark overlay with enter/exit animations.
19
- *
20
- * Content is automatically centered via position calculations.
21
- *
22
- * @example
23
- * ```ts
24
- * const modal = new Modal({ closeOnOverlay: true });
25
- * modal.content.addChild(settingsPanel);
26
- * modal.onClose = () => console.log('Closed');
27
- * await modal.show(1920, 1080);
28
- * ```
29
- */
30
- export class Modal extends Container {
31
- readonly __uiComponent = true as const;
32
-
33
- private _overlay!: Graphics;
34
- private _contentContainer!: Container;
35
- private _config!: Required<ModalConfig>;
36
- private _showing = false;
37
- private _internalSetup = true;
38
-
39
- /** Called when the modal is closed */
40
- public onClose?: () => void;
41
-
42
- constructor(config: ModalConfig = {}) {
43
- super();
44
-
45
- this._config = {
46
- overlayColor: config.overlayColor ?? 0x000000,
47
- overlayAlpha: config.overlayAlpha ?? 0.7,
48
- closeOnOverlay: config.closeOnOverlay ?? true,
49
- animationDuration: config.animationDuration ?? 300,
50
- };
51
-
52
- // Overlay
53
- this._overlay = new Graphics();
54
- this._overlay.eventMode = 'static';
55
- this._overlay.on('pointertap', () => {
56
- if (this._config.closeOnOverlay) this.hide();
57
- });
58
- this.addChild(this._overlay);
59
-
60
- // Content container
61
- this._contentContainer = new Container();
62
- this.addChild(this._contentContainer);
63
-
64
- this.visible = false;
65
- this._internalSetup = false;
66
- }
67
-
68
- /** Content container — add your UI here */
69
- get content(): Container {
70
- return this._contentContainer;
71
- }
72
-
73
- /**
74
- * Override addChild so external children are routed to _contentContainer.
75
- * Enables `<modal><flexContainer>...</flexContainer></modal>` in React JSX.
76
- */
77
- override addChild<T extends Container>(...children: T[]): T {
78
- if (this._internalSetup) {
79
- return super.addChild(...children);
80
- }
81
- for (const child of children) {
82
- this._contentContainer.addChild(child);
83
- }
84
- return children[0];
85
- }
86
-
87
- override removeChild<T extends Container>(...children: T[]): T {
88
- if (this._internalSetup) {
89
- return super.removeChild(...children);
90
- }
91
- for (const child of children) {
92
- this._contentContainer.removeChild(child);
93
- }
94
- return children[0];
95
- }
96
-
97
- /** Whether the modal is currently showing */
98
- get isShowing(): boolean {
99
- return this._showing;
100
- }
101
-
102
- /**
103
- * Show the modal with animation.
104
- */
105
- async show(viewWidth: number, viewHeight: number): Promise<void> {
106
- this._showing = true;
107
- this.visible = true;
108
-
109
- // Draw overlay to cover full screen
110
- this._overlay.clear();
111
- this._overlay.rect(0, 0, viewWidth, viewHeight).fill(this._config.overlayColor);
112
- this._overlay.alpha = 0;
113
-
114
- // Center content
115
- this._contentContainer.x = viewWidth / 2;
116
- this._contentContainer.y = viewHeight / 2;
117
- this._contentContainer.alpha = 0;
118
- this._contentContainer.scale.set(0.8);
119
-
120
- // Animate in
121
- await Promise.all([
122
- Tween.to(
123
- this._overlay,
124
- { alpha: this._config.overlayAlpha },
125
- this._config.animationDuration,
126
- Easing.easeOutCubic,
127
- ),
128
- Tween.to(
129
- this._contentContainer,
130
- { alpha: 1, 'scale.x': 1, 'scale.y': 1 },
131
- this._config.animationDuration,
132
- Easing.easeOutBack,
133
- ),
134
- ]);
135
- }
136
-
137
- /**
138
- * Hide the modal with animation.
139
- */
140
- async hide(): Promise<void> {
141
- if (!this._showing) return;
142
-
143
- await Promise.all([
144
- Tween.to(
145
- this._overlay,
146
- { alpha: 0 },
147
- this._config.animationDuration * 0.7,
148
- Easing.easeInCubic,
149
- ),
150
- Tween.to(
151
- this._contentContainer,
152
- { alpha: 0, 'scale.x': 0.8, 'scale.y': 0.8 },
153
- this._config.animationDuration * 0.7,
154
- Easing.easeInCubic,
155
- ),
156
- ]);
157
-
158
- this.visible = false;
159
- this._showing = false;
160
- this.onClose?.();
161
- }
162
-
163
- /** React reconciler update hook */
164
- updateConfig(changed: Record<string, any>): void {
165
- if ('overlayAlpha' in changed) this._config.overlayAlpha = changed.overlayAlpha;
166
- if ('closeOnOverlay' in changed) this._config.closeOnOverlay = changed.closeOnOverlay;
167
- if ('animationDuration' in changed) this._config.animationDuration = changed.animationDuration;
168
- if ('onClose' in changed) this.onClose = changed.onClose;
169
- }
170
- }