@energy8platform/game-engine 0.21.0 → 0.23.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 (65) hide show
  1. package/dist/host.d.ts +3 -5
  2. package/dist/index.cjs.js +0 -2532
  3. package/dist/index.cjs.js.map +1 -1
  4. package/dist/index.d.ts +3 -1020
  5. package/dist/index.esm.js +2 -2522
  6. package/dist/index.esm.js.map +1 -1
  7. package/dist/slot.cjs.js +0 -30
  8. package/dist/slot.cjs.js.map +1 -1
  9. package/dist/slot.d.ts +2 -25
  10. package/dist/slot.esm.js +1 -30
  11. package/dist/slot.esm.js.map +1 -1
  12. package/dist/vite.cjs.js +0 -5
  13. package/dist/vite.cjs.js.map +1 -1
  14. package/dist/vite.esm.js +0 -5
  15. package/dist/vite.esm.js.map +1 -1
  16. package/package.json +4 -37
  17. package/src/host/index.ts +0 -1
  18. package/src/host/types.ts +0 -3
  19. package/src/index.ts +0 -28
  20. package/src/slot/index.ts +0 -2
  21. package/src/vite/index.ts +0 -5
  22. package/dist/react-jsx.cjs.js +0 -3
  23. package/dist/react-jsx.cjs.js.map +0 -1
  24. package/dist/react-jsx.d.ts +0 -602
  25. package/dist/react-jsx.esm.js +0 -2
  26. package/dist/react-jsx.esm.js.map +0 -1
  27. package/dist/react.cjs.js +0 -3802
  28. package/dist/react.cjs.js.map +0 -1
  29. package/dist/react.d.ts +0 -1467
  30. package/dist/react.esm.js +0 -3786
  31. package/dist/react.esm.js.map +0 -1
  32. package/dist/ui.cjs.js +0 -3014
  33. package/dist/ui.cjs.js.map +0 -1
  34. package/dist/ui.d.ts +0 -1116
  35. package/dist/ui.esm.js +0 -2998
  36. package/dist/ui.esm.js.map +0 -1
  37. package/src/react/EngineContext.ts +0 -26
  38. package/src/react/ReactScene.ts +0 -88
  39. package/src/react/applyProps.ts +0 -271
  40. package/src/react/catalogue.ts +0 -17
  41. package/src/react/createPixiRoot.ts +0 -31
  42. package/src/react/extendAll.ts +0 -74
  43. package/src/react/hooks.ts +0 -46
  44. package/src/react/index.ts +0 -29
  45. package/src/react/jsx-runtime.ts +0 -346
  46. package/src/react/reconciler.ts +0 -338
  47. package/src/slot/freeSpins/FreeSpinsSession.ts +0 -40
  48. package/src/state/StateMachine.ts +0 -231
  49. package/src/state/index.ts +0 -1
  50. package/src/ui/BalanceDisplay.ts +0 -159
  51. package/src/ui/Button.ts +0 -304
  52. package/src/ui/FlexContainer.ts +0 -775
  53. package/src/ui/Label.ts +0 -124
  54. package/src/ui/LabelValue.ts +0 -122
  55. package/src/ui/Layout.ts +0 -291
  56. package/src/ui/Modal.ts +0 -170
  57. package/src/ui/Panel.ts +0 -204
  58. package/src/ui/ProgressBar.ts +0 -170
  59. package/src/ui/ScrollContainer.ts +0 -478
  60. package/src/ui/Slider.ts +0 -241
  61. package/src/ui/Toast.ts +0 -150
  62. package/src/ui/Toggle.ts +0 -201
  63. package/src/ui/WinDisplay.ts +0 -145
  64. package/src/ui/index.ts +0 -33
  65. package/src/ui/view.ts +0 -28
@@ -1,478 +0,0 @@
1
- import { Container, Graphics, type ColorSource, Ticker } from 'pixi.js';
2
- import { resolveView } from './view';
3
- import type { ViewInput } from './view';
4
-
5
- // ─── Types ───────────────────────────────────────────────
6
-
7
- export type ScrollDirection = 'vertical' | 'horizontal' | 'both';
8
-
9
- export interface ScrollContainerConfig {
10
- /** Visible viewport width */
11
- width: number;
12
-
13
- /** Visible viewport height */
14
- height: number;
15
-
16
- /** Scroll direction (default: 'vertical') */
17
- direction?: ScrollDirection;
18
-
19
- /** Background color (undefined = transparent) */
20
- backgroundColor?: ColorSource;
21
-
22
- /** Border radius for the mask (default: 0) */
23
- borderRadius?: number;
24
-
25
- /** Gap between items (default: 0) */
26
- elementsMargin?: number;
27
-
28
- /** Padding */
29
- padding?: number;
30
-
31
- /** Disable easing/inertia */
32
- disableEasing?: boolean;
33
-
34
- /** Show scrollbar indicator (default: false) */
35
- scrollbar?: boolean;
36
-
37
- /** Custom scrollbar thumb view (string texture name, Texture, or Container) */
38
- thumbView?: ViewInput;
39
-
40
- /** Scrollbar width in px (default: 6) */
41
- scrollbarWidth?: number;
42
-
43
- /** Scrollbar padding from edge (default: 4) */
44
- scrollbarPadding?: number;
45
-
46
- /** Scrollbar color (when no thumbView, default: 0xaaaaaa) */
47
- scrollbarColor?: number;
48
-
49
- /** Scrollbar alpha (default: 0.5) */
50
- scrollbarAlpha?: number;
51
- }
52
-
53
- const DECELERATION = 0.95;
54
- const MIN_VELOCITY = 0.5;
55
-
56
- /**
57
- * Scrollable container with touch/drag, mouse wheel, and inertia.
58
- *
59
- * @example
60
- * ```ts
61
- * const scroll = new ScrollContainer({
62
- * width: 600,
63
- * height: 400,
64
- * direction: 'vertical',
65
- * elementsMargin: 8,
66
- * });
67
- *
68
- * for (let i = 0; i < 50; i++) {
69
- * scroll.addItem(createRow(i));
70
- * }
71
- *
72
- * scene.container.addChild(scroll);
73
- * ```
74
- */
75
- export class ScrollContainer extends Container {
76
- readonly __uiComponent = true as const;
77
-
78
- private _viewport: { width: number; height: number };
79
- private _internalSetup = true;
80
- private _content: Container;
81
- private _maskGfx: Graphics;
82
- private _bg: Graphics | null = null;
83
- private _scrollConfig: Required<Pick<ScrollContainerConfig, 'direction' | 'elementsMargin' | 'padding' | 'borderRadius' | 'disableEasing'>>;
84
- private _items: Container[] = [];
85
-
86
- // Scrollbar
87
- private _scrollbar: Container | null = null;
88
- private _scrollbarConfig: { width: number; padding: number };
89
-
90
- // Drag state
91
- private _dragging = false;
92
- private _dragStart = { x: 0, y: 0 };
93
- private _contentStart = { x: 0, y: 0 };
94
- private _velocity = { x: 0, y: 0 };
95
- private _lastDragPos = { x: 0, y: 0 };
96
- private _lastDragTime = 0;
97
- private _inertiaActive = false;
98
-
99
- // Bound handlers for cleanup
100
- private _onTickBound: ((ticker: Ticker) => void) | null = null;
101
- private _onWheelBound: ((e: WheelEvent) => void) | null = null;
102
-
103
- constructor(config: ScrollContainerConfig) {
104
- super();
105
-
106
- this._viewport = { width: config.width, height: config.height };
107
- this._scrollConfig = {
108
- direction: config.direction ?? 'vertical',
109
- elementsMargin: config.elementsMargin ?? 0,
110
- padding: config.padding ?? 0,
111
- borderRadius: config.borderRadius ?? 0,
112
- disableEasing: config.disableEasing ?? false,
113
- };
114
-
115
- // Background
116
- if (config.backgroundColor !== undefined) {
117
- this._bg = new Graphics();
118
- this._bg.roundRect(0, 0, config.width, config.height, this._scrollConfig.borderRadius)
119
- .fill(config.backgroundColor);
120
- this.addChild(this._bg);
121
- }
122
-
123
- // Mask
124
- this._maskGfx = new Graphics();
125
- this._maskGfx.roundRect(0, 0, config.width, config.height, this._scrollConfig.borderRadius)
126
- .fill(0xffffff);
127
- this.addChild(this._maskGfx);
128
-
129
- // Content container
130
- this._content = new Container();
131
- this._content.mask = this._maskGfx;
132
- this.addChild(this._content);
133
-
134
- // Interaction
135
- this.eventMode = 'static';
136
- this.hitArea = { contains: (x: number, y: number) => x >= 0 && x <= config.width && y >= 0 && y <= config.height };
137
-
138
- this.on('pointerdown', this._onPointerDown, this);
139
- this.on('pointermove', this._onPointerMove, this);
140
- this.on('pointerup', this._onPointerUp, this);
141
- this.on('pointerupoutside', this._onPointerUp, this);
142
-
143
- // Mouse wheel
144
- this._onWheelBound = this._onWheel.bind(this);
145
-
146
- // Scrollbar
147
- const sbWidth = config.scrollbarWidth ?? 6;
148
- const sbPadding = config.scrollbarPadding ?? 4;
149
- this._scrollbarConfig = { width: sbWidth, padding: sbPadding };
150
-
151
- if (config.scrollbar) {
152
- const customThumb = resolveView(config.thumbView);
153
- if (customThumb) {
154
- this._scrollbar = customThumb;
155
- } else {
156
- const g = new Graphics();
157
- g.roundRect(0, 0, sbWidth, 40, sbWidth / 2).fill(config.scrollbarColor ?? 0xaaaaaa);
158
- g.alpha = config.scrollbarAlpha ?? 0.5;
159
- this._scrollbar = g;
160
- }
161
- this._scrollbar.visible = false;
162
- super.addChild(this._scrollbar);
163
- }
164
-
165
- this._internalSetup = false;
166
- }
167
-
168
- /**
169
- * Override addChild so external children are routed to scroll content.
170
- * Enables `<scrollContainer><label /><panel /></scrollContainer>` in React JSX.
171
- */
172
- override addChild<T extends Container>(...children: T[]): T {
173
- if (this._internalSetup) {
174
- return super.addChild(...children);
175
- }
176
- for (const child of children) {
177
- this.addItem(child as any);
178
- }
179
- return children[0];
180
- }
181
-
182
- override removeChild<T extends Container>(...children: T[]): T {
183
- if (this._internalSetup) {
184
- return super.removeChild(...children);
185
- }
186
- for (const child of children) {
187
- const idx = this._items.indexOf(child as any);
188
- if (idx !== -1) {
189
- this._items.splice(idx, 1);
190
- this._content.removeChild(child);
191
- }
192
- }
193
- this.layoutItems();
194
- return children[0];
195
- }
196
-
197
- /** React reconciler update hook */
198
- updateConfig(changed: Record<string, any>): void {
199
- if ('width' in changed || 'height' in changed) {
200
- this.setViewportSize(
201
- changed.width ?? this._viewport.width,
202
- changed.height ?? this._viewport.height,
203
- );
204
- }
205
- }
206
-
207
- /** Enable mouse wheel scrolling (call after adding to stage) */
208
- enableWheel(canvas: HTMLCanvasElement): void {
209
- if (this._onWheelBound) {
210
- canvas.addEventListener('wheel', this._onWheelBound, { passive: false });
211
- }
212
- }
213
-
214
- /** Set scrollable content. Replaces any existing items. */
215
- setContent(content: Container): void {
216
- this.clearItems();
217
- const children = [...content.children] as Container[];
218
- for (const child of children) {
219
- this.addItem(child);
220
- }
221
- }
222
-
223
- /** Add a single item */
224
- addItem(child: Container): this {
225
- this._items.push(child);
226
- this._content.addChild(child);
227
- this.layoutItems();
228
- return this;
229
- }
230
-
231
- /** Remove all items */
232
- clearItems(): void {
233
- for (const item of this._items) {
234
- this._content.removeChild(item);
235
- }
236
- this._items.length = 0;
237
- }
238
-
239
- /** Get items */
240
- get items(): readonly Container[] {
241
- return this._items;
242
- }
243
-
244
- /** Scroll to make a specific item index visible */
245
- scrollToItem(index: number): void {
246
- if (index < 0 || index >= this._items.length) return;
247
- const item = this._items[index];
248
- const isVert = this._scrollConfig.direction !== 'horizontal';
249
-
250
- if (isVert) {
251
- this._content.y = -item.y + this._scrollConfig.padding;
252
- } else {
253
- this._content.x = -item.x + this._scrollConfig.padding;
254
- }
255
- this.clampScroll();
256
- }
257
-
258
- /** Current scroll position */
259
- get scrollPosition(): { x: number; y: number } {
260
- return { x: this._content.x, y: this._content.y };
261
- }
262
-
263
- /** Resize the scroll viewport */
264
- setViewportSize(width: number, height: number): void {
265
- this._viewport.width = width;
266
- this._viewport.height = height;
267
-
268
- this._maskGfx.clear();
269
- this._maskGfx.roundRect(0, 0, width, height, this._scrollConfig.borderRadius).fill(0xffffff);
270
-
271
- if (this._bg) {
272
- this._bg.clear();
273
- this._bg.roundRect(0, 0, width, height, this._scrollConfig.borderRadius)
274
- .fill(0xffffff); // color will be overridden if needed
275
- }
276
-
277
- this.clampScroll();
278
- }
279
-
280
- // ─── Layout ──────────────────────────────────────────
281
-
282
- private layoutItems(): void {
283
- const { direction, elementsMargin, padding } = this._scrollConfig;
284
- const isVert = direction !== 'horizontal';
285
- let pos = padding;
286
-
287
- for (const item of this._items) {
288
- if (isVert) {
289
- item.x = padding;
290
- item.y = pos;
291
- pos += item.height + elementsMargin;
292
- } else {
293
- item.x = pos;
294
- item.y = padding;
295
- pos += item.width + elementsMargin;
296
- }
297
- }
298
- }
299
-
300
- // ─── Drag handling ───────────────────────────────────
301
-
302
- private _onPointerDown(e: import('pixi.js').FederatedPointerEvent): void {
303
- this._dragging = true;
304
- this._inertiaActive = false;
305
- this._dragStart.x = e.globalX;
306
- this._dragStart.y = e.globalY;
307
- this._contentStart.x = this._content.x;
308
- this._contentStart.y = this._content.y;
309
- this._lastDragPos.x = e.globalX;
310
- this._lastDragPos.y = e.globalY;
311
- this._lastDragTime = Date.now();
312
- this._velocity.x = 0;
313
- this._velocity.y = 0;
314
-
315
- this.stopInertia();
316
- }
317
-
318
- private _onPointerMove(e: import('pixi.js').FederatedPointerEvent): void {
319
- if (!this._dragging) return;
320
-
321
- const dx = e.globalX - this._dragStart.x;
322
- const dy = e.globalY - this._dragStart.y;
323
- const { direction } = this._scrollConfig;
324
-
325
- if (direction !== 'horizontal') {
326
- this._content.y = this._contentStart.y + dy;
327
- }
328
- if (direction !== 'vertical') {
329
- this._content.x = this._contentStart.x + dx;
330
- }
331
-
332
- // Track velocity
333
- const now = Date.now();
334
- const dt = now - this._lastDragTime;
335
- if (dt > 0) {
336
- this._velocity.x = (e.globalX - this._lastDragPos.x) / dt * 16;
337
- this._velocity.y = (e.globalY - this._lastDragPos.y) / dt * 16;
338
- }
339
- this._lastDragPos.x = e.globalX;
340
- this._lastDragPos.y = e.globalY;
341
- this._lastDragTime = now;
342
-
343
- this.clampScroll();
344
- }
345
-
346
- private _onPointerUp(): void {
347
- if (!this._dragging) return;
348
- this._dragging = false;
349
-
350
- if (!this._scrollConfig.disableEasing &&
351
- (Math.abs(this._velocity.x) > MIN_VELOCITY || Math.abs(this._velocity.y) > MIN_VELOCITY)) {
352
- this.startInertia();
353
- }
354
- }
355
-
356
- // ─── Inertia ─────────────────────────────────────────
357
-
358
- private startInertia(): void {
359
- this._inertiaActive = true;
360
- this._onTickBound = this._inertiaTick.bind(this);
361
- Ticker.shared.add(this._onTickBound);
362
- }
363
-
364
- private stopInertia(): void {
365
- if (this._onTickBound && this._inertiaActive) {
366
- Ticker.shared.remove(this._onTickBound);
367
- this._inertiaActive = false;
368
- }
369
- }
370
-
371
- private _inertiaTick(): void {
372
- const { direction } = this._scrollConfig;
373
-
374
- if (direction !== 'horizontal') {
375
- this._content.y += this._velocity.y;
376
- this._velocity.y *= DECELERATION;
377
- }
378
- if (direction !== 'vertical') {
379
- this._content.x += this._velocity.x;
380
- this._velocity.x *= DECELERATION;
381
- }
382
-
383
- this.clampScroll();
384
-
385
- if (Math.abs(this._velocity.x) < MIN_VELOCITY && Math.abs(this._velocity.y) < MIN_VELOCITY) {
386
- this.stopInertia();
387
- }
388
- }
389
-
390
- // ─── Mouse wheel ─────────────────────────────────────
391
-
392
- private _onWheel(e: WheelEvent): void {
393
- const { direction } = this._scrollConfig;
394
- e.preventDefault();
395
-
396
- if (direction !== 'horizontal') {
397
- this._content.y -= e.deltaY;
398
- }
399
- if (direction !== 'vertical') {
400
- this._content.x -= e.deltaX;
401
- }
402
-
403
- this.clampScroll();
404
- }
405
-
406
- // ─── Scroll bounds ───────────────────────────────────
407
-
408
- private clampScroll(): void {
409
- const { direction } = this._scrollConfig;
410
- const bounds = this._content.getLocalBounds();
411
-
412
- if (direction !== 'horizontal') {
413
- const contentHeight = bounds.height + bounds.y;
414
- const maxScroll = Math.min(0, this._viewport.height - contentHeight);
415
- this._content.y = Math.max(maxScroll, Math.min(0, this._content.y));
416
- }
417
-
418
- if (direction !== 'vertical') {
419
- const contentWidth = bounds.width + bounds.x;
420
- const maxScroll = Math.min(0, this._viewport.width - contentWidth);
421
- this._content.x = Math.max(maxScroll, Math.min(0, this._content.x));
422
- }
423
-
424
- this.updateScrollbar();
425
- }
426
-
427
- private updateScrollbar(): void {
428
- if (!this._scrollbar) return;
429
- const { direction } = this._scrollConfig;
430
- const { width: sbW, padding: sbPad } = this._scrollbarConfig;
431
- const bounds = this._content.getLocalBounds();
432
- const isVert = direction !== 'horizontal';
433
-
434
- if (isVert) {
435
- const contentH = bounds.height + bounds.y;
436
- if (contentH <= this._viewport.height) {
437
- this._scrollbar.visible = false;
438
- return;
439
- }
440
- this._scrollbar.visible = true;
441
- const ratio = this._viewport.height / contentH;
442
- const thumbH = Math.max(20, this._viewport.height * ratio);
443
- const scrollRange = this._viewport.height - thumbH;
444
- const scrollProgress = -this._content.y / (contentH - this._viewport.height);
445
-
446
- this._scrollbar.x = this._viewport.width - sbW - sbPad;
447
- this._scrollbar.y = scrollProgress * scrollRange;
448
- this._scrollbar.height = thumbH;
449
- this._scrollbar.width = sbW;
450
- } else {
451
- const contentW = bounds.width + bounds.x;
452
- if (contentW <= this._viewport.width) {
453
- this._scrollbar.visible = false;
454
- return;
455
- }
456
- this._scrollbar.visible = true;
457
- const ratio = this._viewport.width / contentW;
458
- const thumbW = Math.max(20, this._viewport.width * ratio);
459
- const scrollRange = this._viewport.width - thumbW;
460
- const scrollProgress = -this._content.x / (contentW - this._viewport.width);
461
-
462
- this._scrollbar.y = this._viewport.height - sbW - sbPad;
463
- this._scrollbar.x = scrollProgress * scrollRange;
464
- this._scrollbar.width = thumbW;
465
- this._scrollbar.height = sbW;
466
- }
467
- }
468
-
469
- override destroy(options?: boolean | { children?: boolean; texture?: boolean; textureSource?: boolean }): void {
470
- this.stopInertia();
471
- this.off('pointerdown', this._onPointerDown, this);
472
- this.off('pointermove', this._onPointerMove, this);
473
- this.off('pointerup', this._onPointerUp, this);
474
- this.off('pointerupoutside', this._onPointerUp, this);
475
- this._items.length = 0;
476
- super.destroy(options);
477
- }
478
- }
package/src/ui/Slider.ts DELETED
@@ -1,241 +0,0 @@
1
- import { Container, Graphics, FederatedPointerEvent } from 'pixi.js';
2
- import { resolveView } from './view';
3
- import type { ViewInput } from './view';
4
-
5
- export interface SliderConfig {
6
- /** Minimum value (default: 0) */
7
- min?: number;
8
- /** Maximum value (default: 1) */
9
- max?: number;
10
- /** Step increment (0 = continuous, default: 0) */
11
- step?: number;
12
- /** Initial value (default: min) */
13
- value?: number;
14
- /** Track width in pixels (default: 200) */
15
- width?: number;
16
- /** Track height in pixels (default: 8) */
17
- height?: number;
18
- /** Corner radius for Graphics-based track/fill (default: 4) */
19
- borderRadius?: number;
20
- /** Track background color (ignored when trackView provided) */
21
- trackColor?: number;
22
- /** Fill bar color (ignored when fillView provided) */
23
- fillColor?: number;
24
- /** Handle radius (for Graphics-based handle, default: 12) */
25
- handleRadius?: number;
26
- /** Handle color (for Graphics-based handle, ignored when handleView provided) */
27
- handleColor?: number;
28
-
29
- /** Custom track background view */
30
- trackView?: ViewInput;
31
- /** Custom fill bar view */
32
- fillView?: ViewInput;
33
- /** Custom handle view */
34
- handleView?: ViewInput;
35
-
36
- /** Called when value changes during drag */
37
- onUpdate?: (value: number) => void;
38
- /** Called when drag ends */
39
- onChange?: (value: number) => void;
40
- }
41
-
42
- /**
43
- * Draggable slider with customizable track, fill, and handle views.
44
- *
45
- * @example
46
- * ```ts
47
- * const volume = new Slider({
48
- * min: 0, max: 1, value: 0.5,
49
- * width: 200, height: 8,
50
- * fillColor: 0xffd700,
51
- * onUpdate: (v) => console.log('Volume:', v),
52
- * });
53
- * ```
54
- */
55
- export class Slider extends Container {
56
- readonly __uiComponent = true as const;
57
-
58
- private _track: Container;
59
- private _fill: Container;
60
- private _fillMask: Graphics;
61
- private _handle: Container;
62
- private _config: Required<Pick<SliderConfig, 'min' | 'max' | 'step' | 'width' | 'height' | 'borderRadius' | 'trackColor' | 'fillColor' | 'handleRadius' | 'handleColor'>>;
63
- private _value: number;
64
- private _dragging = false;
65
-
66
- onUpdate: ((value: number) => void) | null = null;
67
- onChange: ((value: number) => void) | null = null;
68
-
69
- constructor(config: SliderConfig = {}) {
70
- super();
71
-
72
- this._config = {
73
- min: config.min ?? 0,
74
- max: config.max ?? 1,
75
- step: config.step ?? 0,
76
- width: config.width ?? 200,
77
- height: config.height ?? 8,
78
- borderRadius: config.borderRadius ?? 4,
79
- trackColor: config.trackColor ?? 0x333333,
80
- fillColor: config.fillColor ?? 0xffd700,
81
- handleRadius: config.handleRadius ?? 12,
82
- handleColor: config.handleColor ?? 0xffffff,
83
- };
84
-
85
- this._value = config.value ?? this._config.min;
86
- this.onUpdate = config.onUpdate ?? null;
87
- this.onChange = config.onChange ?? null;
88
-
89
- const { width, height, borderRadius, trackColor, fillColor, handleRadius, handleColor } = this._config;
90
-
91
- // Track
92
- const customTrack = resolveView(config.trackView);
93
- if (customTrack) {
94
- customTrack.width = width;
95
- customTrack.height = height;
96
- this._track = customTrack;
97
- } else {
98
- const g = new Graphics();
99
- g.roundRect(0, 0, width, height, borderRadius).fill(trackColor);
100
- this._track = g;
101
- }
102
- this.addChild(this._track);
103
-
104
- // Fill
105
- const customFill = resolveView(config.fillView);
106
- if (customFill) {
107
- customFill.width = width;
108
- customFill.height = height;
109
- this._fill = customFill;
110
- } else {
111
- const g = new Graphics();
112
- g.roundRect(0, 0, width, height, borderRadius).fill(fillColor);
113
- this._fill = g;
114
- }
115
- this.addChild(this._fill);
116
-
117
- // Fill mask
118
- this._fillMask = new Graphics();
119
- this.addChild(this._fillMask);
120
- this._fill.mask = this._fillMask;
121
-
122
- // Handle
123
- const customHandle = resolveView(config.handleView);
124
- if (customHandle) {
125
- this._handle = customHandle;
126
- } else {
127
- const g = new Graphics();
128
- g.circle(0, 0, handleRadius).fill(handleColor);
129
- this._handle = g;
130
- }
131
- this._handle.y = height / 2;
132
- this.addChild(this._handle);
133
-
134
- // Interaction
135
- this.eventMode = 'static';
136
- this.cursor = 'pointer';
137
-
138
- // Hit area covers track + handle overflow
139
- const hitPad = Math.max(handleRadius - height / 2, 0);
140
- this.hitArea = { contains: (x: number, y: number) => x >= -hitPad && x <= width + hitPad && y >= -hitPad && y <= height + hitPad };
141
-
142
- this.on('pointerdown', this._onPointerDown, this);
143
- this.on('globalpointermove', this._onPointerMove, this);
144
- this.on('pointerup', this._onPointerUp, this);
145
- this.on('pointerupoutside', this._onPointerUp, this);
146
-
147
- this._updateVisuals();
148
- }
149
-
150
- /** Current value */
151
- get value(): number {
152
- return this._value;
153
- }
154
-
155
- set value(v: number) {
156
- const clamped = this._applyStep(Math.max(this._config.min, Math.min(this._config.max, v)));
157
- if (clamped === this._value) return;
158
- this._value = clamped;
159
- this._updateVisuals();
160
- }
161
-
162
- get min(): number { return this._config.min; }
163
- get max(): number { return this._config.max; }
164
-
165
- /** React reconciler update hook */
166
- updateConfig(changed: Record<string, any>): void {
167
- if ('value' in changed) this.value = changed.value;
168
- if ('min' in changed) { this._config.min = changed.min; this._updateVisuals(); }
169
- if ('max' in changed) { this._config.max = changed.max; this._updateVisuals(); }
170
- if ('step' in changed) this._config.step = changed.step;
171
- if ('onUpdate' in changed) this.onUpdate = changed.onUpdate;
172
- if ('onChange' in changed) this.onChange = changed.onChange;
173
- }
174
-
175
- private _fraction(): number {
176
- const { min, max } = this._config;
177
- return max === min ? 0 : (this._value - min) / (max - min);
178
- }
179
-
180
- private _applyStep(v: number): number {
181
- const { step, min } = this._config;
182
- if (step <= 0) return v;
183
- return min + Math.round((v - min) / step) * step;
184
- }
185
-
186
- private _updateVisuals(): void {
187
- const frac = this._fraction();
188
- const w = this._config.width;
189
- const h = this._config.height;
190
-
191
- // Update fill mask
192
- this._fillMask.clear();
193
- this._fillMask.rect(0, 0, w * frac, h).fill(0xffffff);
194
-
195
- // Update handle position
196
- this._handle.x = w * frac;
197
- }
198
-
199
- private _valueFromPointer(e: FederatedPointerEvent): number {
200
- const local = this.toLocal(e.global);
201
- const frac = Math.max(0, Math.min(1, local.x / this._config.width));
202
- const { min, max } = this._config;
203
- return this._applyStep(min + frac * (max - min));
204
- }
205
-
206
- private _onPointerDown(e: FederatedPointerEvent): void {
207
- this._dragging = true;
208
- const newValue = this._valueFromPointer(e);
209
- if (newValue !== this._value) {
210
- this._value = newValue;
211
- this._updateVisuals();
212
- this.onUpdate?.(this._value);
213
- }
214
- }
215
-
216
- private _onPointerMove(e: FederatedPointerEvent): void {
217
- if (!this._dragging) return;
218
- const newValue = this._valueFromPointer(e);
219
- if (newValue !== this._value) {
220
- this._value = newValue;
221
- this._updateVisuals();
222
- this.onUpdate?.(this._value);
223
- }
224
- }
225
-
226
- private _onPointerUp(_e: FederatedPointerEvent): void {
227
- if (!this._dragging) return;
228
- this._dragging = false;
229
- this.onChange?.(this._value);
230
- }
231
-
232
- override destroy(options?: boolean | { children?: boolean; texture?: boolean; textureSource?: boolean }): void {
233
- this.off('pointerdown', this._onPointerDown, this);
234
- this.off('globalpointermove', this._onPointerMove, this);
235
- this.off('pointerup', this._onPointerUp, this);
236
- this.off('pointerupoutside', this._onPointerUp, this);
237
- this.onUpdate = null;
238
- this.onChange = null;
239
- super.destroy(options);
240
- }
241
- }