@energy8platform/shell 0.4.0 → 0.5.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.
@@ -1,7 +1,8 @@
1
1
  import type { PixiComponentContext, ShellLayer } from '../context';
2
2
  import { CardModal } from '../primitives/card';
3
3
  import { Chip } from '../primitives/controls';
4
- import { FlexBox } from '../primitives/flex';
4
+ import { FlexBox, type Sizable } from '../primitives/flex';
5
+ import { ScrollBox } from '../primitives/scroll';
5
6
 
6
7
  interface Choice {
7
8
  id: string;
@@ -13,8 +14,15 @@ interface SheetOpts {
13
14
  title: string;
14
15
  choices: Choice[];
15
16
  selected: string;
16
- /** Chips per row — a fixed number, or `{ wide, mobile }` that reflows with the layout. */
17
+ /** Chips per row — a fixed number, or `{ wide, mobile }` that reflows with the layout.
18
+ * With `autoFit`, this is the *maximum* column count; the grid drops to fewer when the labels
19
+ * are too wide to fit that many. */
17
20
  columns: number | { wide: number; mobile: number };
21
+ /** Size the columns to the widest chip label (instead of always packing `columns`), and cap the
22
+ * grid height with a scroll region. For variable-width labels (a wide currency's bet values)
23
+ * this reflows + scrolls rather than clipping — the Pixi analogue of the HTML shell's
24
+ * `auto-fill minmax` + `overflow-y:auto`. */
25
+ autoFit?: boolean;
18
26
  /** Max card width in em (the 6-wide bet picker needs 44em vs the 28em default). */
19
27
  maxEm?: number;
20
28
  confirmLabel: string;
@@ -36,8 +44,24 @@ class PickerModal extends CardModal {
36
44
  }
37
45
 
38
46
  /** A centred picker (chips grid + accent Confirm) on the shared card modal. */
47
+ /** A ScrollBox that reports a fixed box to the FlexBox layout — so a capped, scrolling grid slots
48
+ * into the card body like any other sized child (measured/positioned by its viewport, not its
49
+ * full content bounds). */
50
+ class SizedScrollBox extends ScrollBox implements Sizable {
51
+ constructor(private readonly boxW: number, private readonly boxH: number, canvas?: HTMLCanvasElement) {
52
+ super(canvas);
53
+ this.setViewport(boxW, boxH);
54
+ }
55
+ measureSize(): { w: number; h: number } {
56
+ return { w: this.boxW, h: this.boxH };
57
+ }
58
+ setLayoutSize(): void {
59
+ /* fixed viewport — ignore layout-imposed sizing */
60
+ }
61
+ }
62
+
39
63
  function buildSheet(host: PixiComponentContext, opts: SheetOpts): ShellLayer {
40
- const columns = typeof opts.columns === 'number'
64
+ const maxColumns = typeof opts.columns === 'number'
41
65
  ? opts.columns
42
66
  : host.layout === 'mobile' ? opts.columns.mobile : opts.columns.wide;
43
67
 
@@ -91,27 +115,53 @@ function buildSheet(host: PixiComponentContext, opts: SheetOpts): ShellLayer {
91
115
  const em = modal.emSize;
92
116
  const gap = 0.65 * em;
93
117
  const innerW = modal.cardWidth - 2.4 * em;
118
+
119
+ // Build the chips first (at natural width) so autoFit can measure the widest label.
120
+ for (let i = 0; i < opts.choices.length; i++) {
121
+ const c = opts.choices[i];
122
+ const chipIndex = i;
123
+ chips.push(new Chip(host, c.id, c.label, chipIndex === focusIndex, em, (id) => {
124
+ const idx = opts.choices.findIndex((ch) => ch.id === id);
125
+ if (idx >= 0) setHighlight(idx);
126
+ }));
127
+ }
128
+
129
+ // Column count: with autoFit, drop below the max until the widest label fits. Short labels
130
+ // (a normal currency) exceed the max and clamp back to it — so the compact 6-wide layout is
131
+ // preserved and only wide currencies reflow to fewer columns.
132
+ let columns = maxColumns;
133
+ if (opts.autoFit) {
134
+ const widest = chips.reduce((m, ch) => Math.max(m, ch.measureSize().w), 0);
135
+ const fitCols = Math.floor((innerW + gap) / (widest + gap));
136
+ columns = Math.max(1, Math.min(maxColumns, fitCols));
137
+ }
94
138
  const colW = (innerW - gap * (columns - 1)) / columns;
95
139
 
96
140
  const grid = new FlexBox({ direction: 'column', align: 'start', gap });
97
- for (let i = 0; i < opts.choices.length; i += columns) {
98
- const rowChoices = opts.choices.slice(i, i + columns);
141
+ for (let i = 0; i < chips.length; i += columns) {
99
142
  const row = new FlexBox({ direction: 'row', align: 'center', gap });
100
- for (let j = 0; j < rowChoices.length; j++) {
101
- const c = rowChoices[j];
102
- const chipIndex = i + j;
103
- const chip = new Chip(host, c.id, c.label, chipIndex === focusIndex, em, (id) => {
104
- const idx = opts.choices.findIndex((ch) => ch.id === id);
105
- if (idx >= 0) setHighlight(idx);
106
- });
143
+ for (let j = 0; j < columns && i + j < chips.length; j++) {
144
+ const chip = chips[i + j];
107
145
  chip.setLayoutSize(colW, undefined);
108
- chips.push(chip);
109
146
  row.add(chip);
110
147
  }
111
- row.layout();
112
148
  grid.add(row);
113
149
  }
114
- modal.body.add(grid);
150
+
151
+ // Cap the grid height and scroll when the ladder overflows — the Pixi analogue of the HTML
152
+ // shell's `max-height:min(50vh,28em); overflow-y:auto`. (Mask hit-testing clips to the viewport
153
+ // but still lets clicks through to chips inside it — verified against pixi.js 8.16's
154
+ // EventBoundary.) When it fits, drop the scroll box entirely so nothing is masked needlessly.
155
+ const gridH = grid.measureSize().h;
156
+ const maxGridH = Math.min(host.screenH * 0.5, 28 * em);
157
+ if (opts.autoFit && gridH > maxGridH) {
158
+ const scroll = new SizedScrollBox(innerW, maxGridH, host.canvas);
159
+ scroll.content.addChild(grid);
160
+ scroll.refresh();
161
+ modal.body.add(scroll);
162
+ } else {
163
+ modal.body.add(grid);
164
+ }
115
165
 
116
166
  modal.setActions([
117
167
  {
@@ -130,6 +180,7 @@ export function openBetPicker(host: PixiComponentContext): ShellLayer {
130
180
  tag: 'bet-modal',
131
181
  title: host.t('Bet'),
132
182
  columns: { wide: 6, mobile: 3 },
183
+ autoFit: true, // reflow columns + scroll when a wide currency makes the labels grow
133
184
  maxEm: 44, // wider card to fit 6 chips/row
134
185
  confirmLabel: host.t('Confirm'),
135
186
  choices: host.state.availableBets.map((b) => ({ id: String(b), label: host.fmt(b) })),