@energy8platform/game-engine 0.14.10 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audio.cjs.js +6 -1
- package/dist/audio.cjs.js.map +1 -1
- package/dist/audio.esm.js +6 -1
- package/dist/audio.esm.js.map +1 -1
- package/dist/core.cjs.js +6 -1
- package/dist/core.cjs.js.map +1 -1
- package/dist/core.esm.js +6 -1
- package/dist/core.esm.js.map +1 -1
- package/dist/index.cjs.js +28 -9
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +24 -5
- package/dist/index.esm.js +28 -9
- package/dist/index.esm.js.map +1 -1
- package/dist/react-jsx.cjs.js +3 -0
- package/dist/react-jsx.cjs.js.map +1 -0
- package/dist/react-jsx.d.ts +602 -0
- package/dist/react-jsx.esm.js +2 -0
- package/dist/react-jsx.esm.js.map +1 -0
- package/dist/react.cjs.js +146 -11
- package/dist/react.cjs.js.map +1 -1
- package/dist/react.d.ts +600 -1
- package/dist/react.esm.js +146 -11
- package/dist/react.esm.js.map +1 -1
- package/dist/ui.cjs.js +114 -8
- package/dist/ui.cjs.js.map +1 -1
- package/dist/ui.d.ts +82 -7
- package/dist/ui.esm.js +114 -9
- package/dist/ui.esm.js.map +1 -1
- package/package.json +6 -1
- package/src/audio/AudioManager.ts +6 -1
- package/src/react/applyProps.ts +32 -1
- package/src/react/extendAll.ts +2 -2
- package/src/react/index.ts +6 -0
- package/src/react/jsx-runtime.ts +346 -0
- package/src/react/reconciler.ts +1 -1
- package/src/ui/FlexContainer.ts +44 -11
- package/src/ui/LabelValue.ts +122 -0
- package/src/ui/index.ts +2 -0
- package/src/react/jsx.d.ts +0 -261
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSX prop types for the PixiJS React reconciler.
|
|
3
|
+
*
|
|
4
|
+
* The engine auto-augments `react`'s module-scoped `JSX.IntrinsicElements`
|
|
5
|
+
* with every registered element (`<flexContainer>`, `<button>`, `<label>`,
|
|
6
|
+
* `<labelValue>`, etc). Because the augmentation is scoped to the `react`
|
|
7
|
+
* module (not the legacy global `JSX` namespace), it reliably wins over
|
|
8
|
+
* `@types/react`'s HTML defaults for colliding names like `<button>` / `<label>`.
|
|
9
|
+
*
|
|
10
|
+
* **Consumers don't need any shim.** Any import from `@energy8platform/game-engine/react`
|
|
11
|
+
* (e.g. `createPixiRoot`, `extendUIElements`, `ReactScene` — you're already
|
|
12
|
+
* importing these to boot the reconciler) pulls this module in and the JSX
|
|
13
|
+
* types activate automatically.
|
|
14
|
+
*
|
|
15
|
+
* If a project imports only from `/ui` or `/core` and never touches `/react`,
|
|
16
|
+
* add a one-line side-effect import to trigger the augmentation:
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* // app/src/pixi-jsx.d.ts
|
|
20
|
+
* import '@energy8platform/game-engine/react-jsx';
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* Dash-notation for nested config objects is supported:
|
|
24
|
+
* <button colors-default={0xff0000} colors-hover={0x00ff00} />
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type { Texture, TextStyle } from 'pixi.js';
|
|
28
|
+
import type { ViewInput } from '../ui/view';
|
|
29
|
+
import type { ButtonConfig, ButtonState } from '../ui/Button';
|
|
30
|
+
import type { LabelConfig } from '../ui/Label';
|
|
31
|
+
import type { LabelValueConfig } from '../ui/LabelValue';
|
|
32
|
+
import type { PanelConfig } from '../ui/Panel';
|
|
33
|
+
import type { FlexContainerConfig, AlignSelf, AlignContent } from '../ui/FlexContainer';
|
|
34
|
+
import type { ProgressBarConfig } from '../ui/ProgressBar';
|
|
35
|
+
import type { ScrollContainerConfig } from '../ui/ScrollContainer';
|
|
36
|
+
import type { ModalConfig } from '../ui/Modal';
|
|
37
|
+
import type { ToastConfig } from '../ui/Toast';
|
|
38
|
+
import type { BalanceDisplayConfig } from '../ui/BalanceDisplay';
|
|
39
|
+
import type { WinDisplayConfig } from '../ui/WinDisplay';
|
|
40
|
+
import type { LayoutConfig } from '../ui/Layout';
|
|
41
|
+
import type { SliderConfig } from '../ui/Slider';
|
|
42
|
+
import type { ToggleConfig } from '../ui/Toggle';
|
|
43
|
+
|
|
44
|
+
// ─── Event props ─────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
export interface PixiEventProps {
|
|
47
|
+
onClick?: (e: any) => void;
|
|
48
|
+
onPointerDown?: (e: any) => void;
|
|
49
|
+
onPointerUp?: (e: any) => void;
|
|
50
|
+
onPointerMove?: (e: any) => void;
|
|
51
|
+
onPointerOver?: (e: any) => void;
|
|
52
|
+
onPointerOut?: (e: any) => void;
|
|
53
|
+
onPointerEnter?: (e: any) => void;
|
|
54
|
+
onPointerLeave?: (e: any) => void;
|
|
55
|
+
onPointerCancel?: (e: any) => void;
|
|
56
|
+
onPointerTap?: (e: any) => void;
|
|
57
|
+
onPointerUpOutside?: (e: any) => void;
|
|
58
|
+
onMouseDown?: (e: any) => void;
|
|
59
|
+
onMouseUp?: (e: any) => void;
|
|
60
|
+
onMouseMove?: (e: any) => void;
|
|
61
|
+
onMouseOver?: (e: any) => void;
|
|
62
|
+
onMouseOut?: (e: any) => void;
|
|
63
|
+
onWheel?: (e: any) => void;
|
|
64
|
+
onTap?: (e: any) => void;
|
|
65
|
+
onRightClick?: (e: any) => void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ─── Base container props (shared by all elements) ───────
|
|
69
|
+
|
|
70
|
+
export interface BaseProps extends PixiEventProps {
|
|
71
|
+
key?: React.Key;
|
|
72
|
+
ref?: React.Ref<any>;
|
|
73
|
+
children?: React.ReactNode;
|
|
74
|
+
|
|
75
|
+
// Container props
|
|
76
|
+
x?: number;
|
|
77
|
+
y?: number;
|
|
78
|
+
width?: number;
|
|
79
|
+
height?: number;
|
|
80
|
+
alpha?: number;
|
|
81
|
+
visible?: boolean;
|
|
82
|
+
rotation?: number;
|
|
83
|
+
angle?: number;
|
|
84
|
+
zIndex?: number;
|
|
85
|
+
label?: string;
|
|
86
|
+
cursor?: string;
|
|
87
|
+
eventMode?: 'auto' | 'none' | 'passive' | 'static' | 'dynamic';
|
|
88
|
+
|
|
89
|
+
// Nested via dash-notation
|
|
90
|
+
'scale-x'?: number;
|
|
91
|
+
'scale-y'?: number;
|
|
92
|
+
'pivot-x'?: number;
|
|
93
|
+
'pivot-y'?: number;
|
|
94
|
+
'position-x'?: number;
|
|
95
|
+
'position-y'?: number;
|
|
96
|
+
'anchor-x'?: number;
|
|
97
|
+
'anchor-y'?: number;
|
|
98
|
+
|
|
99
|
+
// Allow scale as number (uniform)
|
|
100
|
+
scale?: number | { x: number; y: number };
|
|
101
|
+
|
|
102
|
+
// Flex item props (used when child of <flexContainer>)
|
|
103
|
+
flexGrow?: number;
|
|
104
|
+
flexShrink?: number;
|
|
105
|
+
layoutWidth?: number | string;
|
|
106
|
+
layoutHeight?: number | string;
|
|
107
|
+
alignSelf?: AlignSelf;
|
|
108
|
+
flexExclude?: boolean;
|
|
109
|
+
// Absolute positioning for flexExclude children.
|
|
110
|
+
// Values describe the visual bounding rectangle (top-left of the rendered
|
|
111
|
+
// frame), so elements with centered origin like Button/Label are positioned
|
|
112
|
+
// intuitively — (left: 100, top: 50) puts the visible corner at (100, 50).
|
|
113
|
+
top?: number;
|
|
114
|
+
right?: number;
|
|
115
|
+
bottom?: number;
|
|
116
|
+
left?: number;
|
|
117
|
+
/** X coordinate of the visual center within parent (px or `'50%'`). Overrides left/right. */
|
|
118
|
+
centerX?: number | string;
|
|
119
|
+
/** Y coordinate of the visual center within parent (px or `'50%'`). Overrides top/bottom. */
|
|
120
|
+
centerY?: number | string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ─── PixiJS primitive elements ───────────────────────────
|
|
124
|
+
|
|
125
|
+
export interface SpriteProps extends BaseProps {
|
|
126
|
+
texture?: string | Texture;
|
|
127
|
+
anchor?: number | { x: number; y: number };
|
|
128
|
+
tint?: number;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface TextProps extends BaseProps {
|
|
132
|
+
text?: string;
|
|
133
|
+
style?: Partial<TextStyle>;
|
|
134
|
+
anchor?: number | { x: number; y: number };
|
|
135
|
+
// Dash-notation for style
|
|
136
|
+
'style-fontSize'?: number;
|
|
137
|
+
'style-fill'?: number | string;
|
|
138
|
+
'style-fontFamily'?: string;
|
|
139
|
+
'style-fontWeight'?: string;
|
|
140
|
+
'style-letterSpacing'?: number;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface GraphicsProps extends BaseProps {
|
|
144
|
+
/** Draw function: receives the Graphics instance, called on mount and updates */
|
|
145
|
+
draw?: (g: any) => void;
|
|
146
|
+
/**
|
|
147
|
+
* Layout size hint (pixels). For Graphics, `width`/`height` do NOT apply a scale
|
|
148
|
+
* transform (unlike Container.width). They're forwarded to the parent
|
|
149
|
+
* FlexContainer as `layoutWidth`/`layoutHeight` so layout measures the element
|
|
150
|
+
* at the requested size while the actual geometry stays in the `draw` callback.
|
|
151
|
+
*/
|
|
152
|
+
width?: number;
|
|
153
|
+
height?: number;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface AnimatedSpriteProps extends BaseProps {
|
|
157
|
+
textures?: Texture[];
|
|
158
|
+
animationSpeed?: number;
|
|
159
|
+
loop?: boolean;
|
|
160
|
+
playing?: boolean;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface NineSliceSpriteProps extends BaseProps {
|
|
164
|
+
texture?: string | Texture;
|
|
165
|
+
leftWidth?: number;
|
|
166
|
+
topHeight?: number;
|
|
167
|
+
rightWidth?: number;
|
|
168
|
+
bottomHeight?: number;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface TilingSpriteProps extends BaseProps {
|
|
172
|
+
texture?: string | Texture;
|
|
173
|
+
tilePosition?: { x: number; y: number };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ─── Engine UI component props ───────────────────────────
|
|
177
|
+
|
|
178
|
+
export interface ButtonComponentProps extends BaseProps, Omit<ButtonConfig, 'colors' | 'textStyle'> {
|
|
179
|
+
// Full object form
|
|
180
|
+
colors?: Partial<Record<ButtonState, number>>;
|
|
181
|
+
textStyle?: Record<string, unknown>;
|
|
182
|
+
// Custom views (ViewInput: string texture name, Texture, or Container)
|
|
183
|
+
defaultView?: ViewInput;
|
|
184
|
+
hoverView?: ViewInput;
|
|
185
|
+
pressedView?: ViewInput;
|
|
186
|
+
disabledView?: ViewInput;
|
|
187
|
+
// Dash-notation for nested objects
|
|
188
|
+
'colors-default'?: number;
|
|
189
|
+
'colors-hover'?: number;
|
|
190
|
+
'colors-pressed'?: number;
|
|
191
|
+
'colors-disabled'?: number;
|
|
192
|
+
'textStyle-fontSize'?: number;
|
|
193
|
+
'textStyle-fill'?: number | string;
|
|
194
|
+
'textStyle-fontFamily'?: string;
|
|
195
|
+
'textStyle-fontWeight'?: string;
|
|
196
|
+
'textStyle-fontStyle'?: string;
|
|
197
|
+
'textStyle-letterSpacing'?: number;
|
|
198
|
+
// Component callbacks
|
|
199
|
+
onPress?: () => void;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export interface LabelComponentProps extends BaseProps, Omit<LabelConfig, 'style'> {
|
|
203
|
+
style?: Partial<TextStyle>;
|
|
204
|
+
'style-fontSize'?: number;
|
|
205
|
+
'style-fill'?: number | string;
|
|
206
|
+
'style-fontFamily'?: string;
|
|
207
|
+
'style-fontWeight'?: string;
|
|
208
|
+
'style-letterSpacing'?: number;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export interface LabelValueComponentProps extends Omit<BaseProps, 'width' | 'height' | 'label'>,
|
|
212
|
+
Omit<LabelValueConfig, 'labelStyle' | 'valueStyle'> {
|
|
213
|
+
labelStyle?: Partial<TextStyle>;
|
|
214
|
+
valueStyle?: Partial<TextStyle>;
|
|
215
|
+
width?: number | string;
|
|
216
|
+
height?: number | string;
|
|
217
|
+
// Dash-notation for nested styles
|
|
218
|
+
'labelStyle-fontSize'?: number;
|
|
219
|
+
'labelStyle-fill'?: number | string;
|
|
220
|
+
'labelStyle-fontFamily'?: string;
|
|
221
|
+
'labelStyle-fontWeight'?: string;
|
|
222
|
+
'valueStyle-fontSize'?: number;
|
|
223
|
+
'valueStyle-fill'?: number | string;
|
|
224
|
+
'valueStyle-fontFamily'?: string;
|
|
225
|
+
'valueStyle-fontWeight'?: string;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export interface PanelComponentProps extends BaseProps, Omit<PanelConfig, 'layout'> {
|
|
229
|
+
layout?: Partial<FlexContainerConfig>;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export interface FlexContainerComponentProps extends Omit<BaseProps, 'width' | 'height'>, FlexContainerConfig {
|
|
233
|
+
padding?: number | [number, number, number, number];
|
|
234
|
+
paddingTop?: number;
|
|
235
|
+
paddingRight?: number;
|
|
236
|
+
paddingBottom?: number;
|
|
237
|
+
paddingLeft?: number;
|
|
238
|
+
alignContent?: AlignContent;
|
|
239
|
+
width?: number | string;
|
|
240
|
+
height?: number | string;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export interface ProgressBarComponentProps extends BaseProps, ProgressBarConfig {
|
|
244
|
+
progress?: number;
|
|
245
|
+
/** Custom track background (string texture name, Texture, or Container) */
|
|
246
|
+
trackView?: ViewInput;
|
|
247
|
+
/** Custom fill bar */
|
|
248
|
+
fillView?: ViewInput;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export interface ScrollContainerComponentProps extends BaseProps, Omit<ScrollContainerConfig, 'width' | 'height'> {
|
|
252
|
+
width: number;
|
|
253
|
+
height: number;
|
|
254
|
+
/** Show scrollbar indicator */
|
|
255
|
+
scrollbar?: boolean;
|
|
256
|
+
/** Custom scrollbar thumb view */
|
|
257
|
+
thumbView?: ViewInput;
|
|
258
|
+
scrollbarWidth?: number;
|
|
259
|
+
scrollbarPadding?: number;
|
|
260
|
+
scrollbarColor?: number;
|
|
261
|
+
scrollbarAlpha?: number;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export interface ModalComponentProps extends BaseProps, ModalConfig {
|
|
265
|
+
onClose?: () => void;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export interface ToastComponentProps extends BaseProps, ToastConfig {
|
|
269
|
+
/** Custom background view */
|
|
270
|
+
backgroundView?: ViewInput;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export interface BalanceDisplayComponentProps extends BaseProps, BalanceDisplayConfig {
|
|
274
|
+
/** Current balance value */
|
|
275
|
+
value?: number;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export interface WinDisplayComponentProps extends BaseProps, WinDisplayConfig {}
|
|
279
|
+
|
|
280
|
+
export interface LayoutComponentProps extends BaseProps, LayoutConfig {}
|
|
281
|
+
|
|
282
|
+
export interface SliderComponentProps extends BaseProps, Omit<SliderConfig, 'width' | 'height'> {
|
|
283
|
+
width?: number;
|
|
284
|
+
height?: number;
|
|
285
|
+
onUpdate?: (value: number) => void;
|
|
286
|
+
onChange?: (value: number) => void;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export interface ToggleComponentProps extends BaseProps, Omit<ToggleConfig, 'width' | 'height'> {
|
|
290
|
+
width?: number;
|
|
291
|
+
height?: number;
|
|
292
|
+
onView?: ViewInput;
|
|
293
|
+
offView?: ViewInput;
|
|
294
|
+
onChange?: (value: boolean) => void;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ─── IntrinsicElements map ───────────────────────────────
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Complete element → prop-type map for all engine-registered JSX elements.
|
|
301
|
+
* Also consumable directly for typing custom wrappers around engine elements.
|
|
302
|
+
*/
|
|
303
|
+
export interface EngineIntrinsicElements {
|
|
304
|
+
// PixiJS primitives
|
|
305
|
+
container: BaseProps;
|
|
306
|
+
sprite: SpriteProps;
|
|
307
|
+
text: TextProps;
|
|
308
|
+
graphics: GraphicsProps;
|
|
309
|
+
animatedSprite: AnimatedSpriteProps;
|
|
310
|
+
nineSliceSprite: NineSliceSpriteProps;
|
|
311
|
+
tilingSprite: TilingSpriteProps;
|
|
312
|
+
|
|
313
|
+
// Engine UI components
|
|
314
|
+
button: ButtonComponentProps;
|
|
315
|
+
label: LabelComponentProps;
|
|
316
|
+
labelValue: LabelValueComponentProps;
|
|
317
|
+
panel: PanelComponentProps;
|
|
318
|
+
flexContainer: FlexContainerComponentProps;
|
|
319
|
+
progressBar: ProgressBarComponentProps;
|
|
320
|
+
scrollContainer: ScrollContainerComponentProps;
|
|
321
|
+
modal: ModalComponentProps;
|
|
322
|
+
toast: ToastComponentProps;
|
|
323
|
+
balanceDisplay: BalanceDisplayComponentProps;
|
|
324
|
+
winDisplay: WinDisplayComponentProps;
|
|
325
|
+
layout: LayoutComponentProps;
|
|
326
|
+
slider: SliderComponentProps;
|
|
327
|
+
toggle: ToggleComponentProps;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ─── Auto-augment React's JSX namespace ──────────────────
|
|
331
|
+
//
|
|
332
|
+
// React 19 scopes `JSX.IntrinsicElements` inside `declare module 'react'`,
|
|
333
|
+
// so we augment the same module. `extends EngineIntrinsicElements {}` adds
|
|
334
|
+
// every engine element to the interface the reconciler sees.
|
|
335
|
+
//
|
|
336
|
+
// Interface merging with `@types/react`'s HTML defaults: property names
|
|
337
|
+
// unique to the engine (`flexContainer`, `labelValue`, `button` since it
|
|
338
|
+
// carries our ButtonComponentProps, etc.) merge cleanly. For HTML-colliding
|
|
339
|
+
// names (`button`, `label`), this module-scoped augmentation takes precedence
|
|
340
|
+
// over the legacy global JSX namespace in React-19 apps.
|
|
341
|
+
|
|
342
|
+
declare module 'react' {
|
|
343
|
+
namespace JSX {
|
|
344
|
+
interface IntrinsicElements extends EngineIntrinsicElements {}
|
|
345
|
+
}
|
|
346
|
+
}
|
package/src/react/reconciler.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { FlexContainer } from '../ui/FlexContainer';
|
|
|
7
7
|
import type { FlexItemConfig } from '../ui/FlexContainer';
|
|
8
8
|
|
|
9
9
|
/** Flex item prop names that should be forwarded to _flexConfig on the child */
|
|
10
|
-
const FLEX_ITEM_PROPS = ['flexGrow', 'flexShrink', 'layoutWidth', 'layoutHeight', 'alignSelf', 'flexExclude', 'top', 'right', 'bottom', 'left'] as const;
|
|
10
|
+
const FLEX_ITEM_PROPS = ['flexGrow', 'flexShrink', 'layoutWidth', 'layoutHeight', 'alignSelf', 'flexExclude', 'top', 'right', 'bottom', 'left', 'centerX', 'centerY'] as const;
|
|
11
11
|
|
|
12
12
|
/** Any component with suspendLayout/resumeLayout (FlexContainer, Panel, etc.) */
|
|
13
13
|
interface Suspendable {
|
package/src/ui/FlexContainer.ts
CHANGED
|
@@ -20,16 +20,35 @@ export interface FlexItemConfig {
|
|
|
20
20
|
layoutHeight?: number | string;
|
|
21
21
|
/** Override parent's alignItems for this child */
|
|
22
22
|
alignSelf?: AlignSelf;
|
|
23
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Exclude from flex layout (acts like position: absolute).
|
|
25
|
+
*
|
|
26
|
+
* Positioning props (`top`/`right`/`bottom`/`left`/`centerX`/`centerY`) describe
|
|
27
|
+
* the **visual bounding rectangle**, so children with a centered internal
|
|
28
|
+
* origin (Button, Label — `anchor = 0.5`) are positioned intuitively.
|
|
29
|
+
* `left: 100` places the visible left edge at 100px, regardless of origin.
|
|
30
|
+
*/
|
|
24
31
|
flexExclude?: boolean;
|
|
25
|
-
/**
|
|
32
|
+
/** Distance from top edge of parent content area to the child's visual top */
|
|
26
33
|
top?: number;
|
|
27
|
-
/**
|
|
34
|
+
/** Distance from right edge of parent content area to the child's visual right */
|
|
28
35
|
right?: number;
|
|
29
|
-
/**
|
|
36
|
+
/** Distance from bottom edge of parent content area to the child's visual bottom */
|
|
30
37
|
bottom?: number;
|
|
31
|
-
/**
|
|
38
|
+
/** Distance from left edge of parent content area to the child's visual left */
|
|
32
39
|
left?: number;
|
|
40
|
+
/**
|
|
41
|
+
* X coordinate of the child's visual center within parent content area.
|
|
42
|
+
* Accepts pixels (`200`) or a percentage of parent content width (`'50%'`).
|
|
43
|
+
* Takes precedence over `left`/`right`.
|
|
44
|
+
*/
|
|
45
|
+
centerX?: number | string;
|
|
46
|
+
/**
|
|
47
|
+
* Y coordinate of the child's visual center within parent content area.
|
|
48
|
+
* Accepts pixels or a percentage of parent content height (`'50%'`).
|
|
49
|
+
* Takes precedence over `top`/`bottom`.
|
|
50
|
+
*/
|
|
51
|
+
centerY?: number | string;
|
|
33
52
|
}
|
|
34
53
|
|
|
35
54
|
export interface FlexContainerConfig {
|
|
@@ -679,17 +698,31 @@ export class FlexContainer extends Container {
|
|
|
679
698
|
this._computedHeight = this._explicitHeight > 0 ? this._explicitHeight : (pt + naturalMainSize + pb);
|
|
680
699
|
}
|
|
681
700
|
|
|
682
|
-
// Position flexExclude children (absolute positioning)
|
|
701
|
+
// Position flexExclude children (absolute positioning).
|
|
702
|
+
// All position props describe the visual (bounds) rectangle. We derive the
|
|
703
|
+
// visual rectangle directly from `getLocalBounds()` rather than the
|
|
704
|
+
// layout-config `w`/`h` returned by measureChild — those may differ from
|
|
705
|
+
// actual bounds when the user pins `layoutWidth`/`layoutHeight` to a value
|
|
706
|
+
// that doesn't match the child's real visual extent (e.g. a Button whose
|
|
707
|
+
// text overflows its configured `width`). Mixing layout size with real
|
|
708
|
+
// bounds-origin would shift the visual center off the requested point.
|
|
709
|
+
// `centerX/centerY` take precedence over `left/right` / `top/bottom` on each axis.
|
|
683
710
|
for (const child of this._layoutChildren) {
|
|
684
711
|
if (!child._flexConfig?.flexExclude) continue;
|
|
685
712
|
const cfg = child._flexConfig;
|
|
686
|
-
const
|
|
713
|
+
const bounds = child.getLocalBounds();
|
|
687
714
|
const cw = this._computedWidth;
|
|
688
715
|
const ch = this._computedHeight;
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
if (
|
|
692
|
-
else if (cfg.
|
|
716
|
+
|
|
717
|
+
const centerX = resolveDimension(cfg.centerX, pctRefW);
|
|
718
|
+
if (centerX !== undefined) child.x = centerX - bounds.x - bounds.width / 2;
|
|
719
|
+
else if (cfg.left !== undefined) child.x = cfg.left - bounds.x;
|
|
720
|
+
else if (cfg.right !== undefined) child.x = cw - cfg.right - bounds.x - bounds.width;
|
|
721
|
+
|
|
722
|
+
const centerY = resolveDimension(cfg.centerY, pctRefH);
|
|
723
|
+
if (centerY !== undefined) child.y = centerY - bounds.y - bounds.height / 2;
|
|
724
|
+
else if (cfg.top !== undefined) child.y = cfg.top - bounds.y;
|
|
725
|
+
else if (cfg.bottom !== undefined) child.y = ch - cfg.bottom - bounds.y - bounds.height;
|
|
693
726
|
}
|
|
694
727
|
}
|
|
695
728
|
|
|
@@ -0,0 +1,122 @@
|
|
|
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/index.ts
CHANGED
|
@@ -11,6 +11,8 @@ export { ProgressBar } from './ProgressBar';
|
|
|
11
11
|
export type { ProgressBarConfig } from './ProgressBar';
|
|
12
12
|
export { Label } from './Label';
|
|
13
13
|
export type { LabelConfig } from './Label';
|
|
14
|
+
export { LabelValue } from './LabelValue';
|
|
15
|
+
export type { LabelValueConfig, LabelValueAlign } from './LabelValue';
|
|
14
16
|
export { Panel } from './Panel';
|
|
15
17
|
export type { PanelConfig } from './Panel';
|
|
16
18
|
export { BalanceDisplay } from './BalanceDisplay';
|