@energy8platform/shell 0.4.0 → 0.4.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/pixi.d.ts CHANGED
@@ -473,7 +473,7 @@ declare class ShellController extends EventEmitter<ShellEvents> implements Shell
473
473
  tokens: ShellTokens;
474
474
  layout: ShellLayoutMode;
475
475
  soundOn: boolean;
476
- readonly engineVersion = "0.4.0";
476
+ readonly engineVersion = "0.4.1";
477
477
  readonly actions: ShellActions;
478
478
  private renderer;
479
479
  private i18n;
@@ -539,7 +539,7 @@ interface I18n {
539
539
  declare function createI18n(opts: I18nOptions): I18n;
540
540
 
541
541
  /** The @energy8platform/shell package version, stamped into the game-info footer. */
542
- declare const PACKAGE_VERSION = "0.4.0";
542
+ declare const PACKAGE_VERSION = "0.4.1";
543
543
 
544
544
  /** A shell: the renderer-agnostic controller plus the surface facade (safeArea/barHeight/setVisible)
545
545
  * an embedding host reads. `createShell`, `createGameShell` and `createPixiShell` all return this. */
package/dist/pixi.esm.js CHANGED
@@ -1437,7 +1437,7 @@ class KeyboardController {
1437
1437
 
1438
1438
  // AUTO-GENERATED by scripts/gen-version.mjs — do not edit. Mirrors package.json "version".
1439
1439
  /** The @energy8platform/shell package version, stamped into the game-info footer. */
1440
- const PACKAGE_VERSION = '0.4.0';
1440
+ const PACKAGE_VERSION = '0.4.1';
1441
1441
 
1442
1442
  /** Apply defaults to the raw config (the mount target lives on the renderer, not here). */
1443
1443
  function resolveConfig(config) {
@@ -4079,28 +4079,69 @@ function sectionHotkeys(host, title, width) {
4079
4079
  visible.forEach((r, i) => {
4080
4080
  if (i > 0)
4081
4081
  sec.add(hairline$1(host, inner));
4082
- sec.add(hkRow(host, r));
4082
+ sec.add(hkRow(host, r, inner));
4083
4083
  });
4084
4084
  return sec;
4085
4085
  }
4086
- /** Render a single hotkey row: keycap chip(s) on the left, action name on the right. */
4086
+ /** Render a single hotkey row: keycap chip(s) on the left, action name on the right. The row is
4087
+ * constrained to `inner` (chips left, label right). On narrow widths the chip combos stack onto
4088
+ * their own lines and the label wraps within the leftover width, so nothing clips past the plaque
4089
+ * — the Pixi analogue of the HTML `.ge-gi-hk-chips { flex:0 1 auto; flex-wrap }` + label wrap. */
4087
4090
  function hkRow(host, r, inner) {
4088
- const row = new FlexBox({ direction: 'row', align: 'center', gap: 14, padding: { top: 8, bottom: 8 } });
4089
- // Build chips container
4090
- const chipsCol = new FlexBox({ direction: 'row', align: 'center', gap: 4 });
4091
+ const GAP = 14;
4092
+ const LABEL_MIN = 92; // reserve at least this much for the action label before wrapping the chips
4091
4093
  const chipKeys = buildChipKeys(r);
4094
+ // Lay the chips on one line and measure. If that leaves too little room for the label, stack the
4095
+ // combos (split at the '/') onto their own lines so the chips give up horizontal width.
4096
+ let chips = chipLine(host, chipKeys);
4097
+ let chipsW = chips.measureSize().w;
4098
+ if (chipsW > inner - GAP - LABEL_MIN) {
4099
+ chips = chipCombos(host, chipKeys);
4100
+ chipsW = chips.measureSize().w;
4101
+ }
4102
+ // The label takes the leftover width and WRAPS within it (never clips); right-aligned to sit
4103
+ // against the plaque edge like the DOM `.ge-gi-hk-tx`.
4104
+ const labelW = Math.max(LABEL_MIN, inner - chipsW - GAP);
4105
+ const nameText = makeText(host.t(r.name), { size: 15, weight: '700', color: '#ffffff', wrapWidth: labelW, align: 'right' });
4106
+ const row = new FlexBox({ direction: 'row', align: 'center', justify: 'space-between', gap: GAP, padding: { top: 8, bottom: 8 }, width: inner });
4107
+ row.add(chips);
4108
+ row.add(nameText);
4109
+ return row;
4110
+ }
4111
+ /** Chips on a single row, with '/' separators between the combos (as flattened by buildChipKeys). */
4112
+ function chipLine(host, chipKeys) {
4113
+ const line = new FlexBox({ direction: 'row', align: 'center', gap: 4 });
4114
+ for (const key of chipKeys) {
4115
+ if (key === '/')
4116
+ line.add(makeText('/', { size: 12, weight: '500', color: host.tokens.plaqueLabel }));
4117
+ else
4118
+ line.add(keycap(host, key));
4119
+ }
4120
+ return line;
4121
+ }
4122
+ /** Chips stacked as combos — split at the '/' separators, one combo per line (drops the inline
4123
+ * '/', which reads as a line break once the combos are stacked). */
4124
+ function chipCombos(host, chipKeys) {
4125
+ const col = new FlexBox({ direction: 'column', align: 'start', gap: 4 });
4126
+ let line = new FlexBox({ direction: 'row', align: 'center', gap: 4 });
4127
+ let filled = false;
4128
+ const flush = () => {
4129
+ if (!filled)
4130
+ return;
4131
+ col.add(line);
4132
+ line = new FlexBox({ direction: 'row', align: 'center', gap: 4 });
4133
+ filled = false;
4134
+ };
4092
4135
  for (const key of chipKeys) {
4093
4136
  if (key === '/') {
4094
- chipsCol.add(makeText('/', { size: 12, weight: '500', color: host.tokens.plaqueLabel }));
4095
- }
4096
- else {
4097
- chipsCol.add(keycap(host, key));
4137
+ flush();
4138
+ continue;
4098
4139
  }
4140
+ line.add(keycap(host, key));
4141
+ filled = true;
4099
4142
  }
4100
- const nameText = makeText(host.t(r.name), { size: 15, weight: '700', color: '#ffffff' });
4101
- row.add(chipsCol);
4102
- row.add(nameText);
4103
- return row;
4143
+ flush();
4144
+ return col;
4104
4145
  }
4105
4146
  /** Flatten a row's chip list into display tokens — for bet rows, produce: Shift ↑ / Shift = style. */
4106
4147
  function buildChipKeys(r) {
@@ -5266,8 +5307,27 @@ class PickerModal extends CardModal {
5266
5307
  }
5267
5308
  }
5268
5309
  /** A centred picker (chips grid + accent Confirm) on the shared card modal. */
5310
+ /** A ScrollBox that reports a fixed box to the FlexBox layout — so a capped, scrolling grid slots
5311
+ * into the card body like any other sized child (measured/positioned by its viewport, not its
5312
+ * full content bounds). */
5313
+ class SizedScrollBox extends ScrollBox {
5314
+ boxW;
5315
+ boxH;
5316
+ constructor(boxW, boxH, canvas) {
5317
+ super(canvas);
5318
+ this.boxW = boxW;
5319
+ this.boxH = boxH;
5320
+ this.setViewport(boxW, boxH);
5321
+ }
5322
+ measureSize() {
5323
+ return { w: this.boxW, h: this.boxH };
5324
+ }
5325
+ setLayoutSize() {
5326
+ /* fixed viewport — ignore layout-imposed sizing */
5327
+ }
5328
+ }
5269
5329
  function buildSheet(host, opts) {
5270
- const columns = typeof opts.columns === 'number'
5330
+ const maxColumns = typeof opts.columns === 'number'
5271
5331
  ? opts.columns
5272
5332
  : host.layout === 'mobile' ? opts.columns.mobile : opts.columns.wide;
5273
5333
  let selected = opts.selected;
@@ -5319,27 +5379,51 @@ function buildSheet(host, opts) {
5319
5379
  const em = modal.emSize;
5320
5380
  const gap = 0.65 * em;
5321
5381
  const innerW = modal.cardWidth - 2.4 * em;
5382
+ // Build the chips first (at natural width) so autoFit can measure the widest label.
5383
+ for (let i = 0; i < opts.choices.length; i++) {
5384
+ const c = opts.choices[i];
5385
+ const chipIndex = i;
5386
+ chips.push(new Chip(host, c.id, c.label, chipIndex === focusIndex, em, (id) => {
5387
+ const idx = opts.choices.findIndex((ch) => ch.id === id);
5388
+ if (idx >= 0)
5389
+ setHighlight(idx);
5390
+ }));
5391
+ }
5392
+ // Column count: with autoFit, drop below the max until the widest label fits. Short labels
5393
+ // (a normal currency) exceed the max and clamp back to it — so the compact 6-wide layout is
5394
+ // preserved and only wide currencies reflow to fewer columns.
5395
+ let columns = maxColumns;
5396
+ if (opts.autoFit) {
5397
+ const widest = chips.reduce((m, ch) => Math.max(m, ch.measureSize().w), 0);
5398
+ const fitCols = Math.floor((innerW + gap) / (widest + gap));
5399
+ columns = Math.max(1, Math.min(maxColumns, fitCols));
5400
+ }
5322
5401
  const colW = (innerW - gap * (columns - 1)) / columns;
5323
5402
  const grid = new FlexBox({ direction: 'column', align: 'start', gap });
5324
- for (let i = 0; i < opts.choices.length; i += columns) {
5325
- const rowChoices = opts.choices.slice(i, i + columns);
5403
+ for (let i = 0; i < chips.length; i += columns) {
5326
5404
  const row = new FlexBox({ direction: 'row', align: 'center', gap });
5327
- for (let j = 0; j < rowChoices.length; j++) {
5328
- const c = rowChoices[j];
5329
- const chipIndex = i + j;
5330
- const chip = new Chip(host, c.id, c.label, chipIndex === focusIndex, em, (id) => {
5331
- const idx = opts.choices.findIndex((ch) => ch.id === id);
5332
- if (idx >= 0)
5333
- setHighlight(idx);
5334
- });
5405
+ for (let j = 0; j < columns && i + j < chips.length; j++) {
5406
+ const chip = chips[i + j];
5335
5407
  chip.setLayoutSize(colW, undefined);
5336
- chips.push(chip);
5337
5408
  row.add(chip);
5338
5409
  }
5339
- row.layout();
5340
5410
  grid.add(row);
5341
5411
  }
5342
- modal.body.add(grid);
5412
+ // Cap the grid height and scroll when the ladder overflows — the Pixi analogue of the HTML
5413
+ // shell's `max-height:min(50vh,28em); overflow-y:auto`. (Mask hit-testing clips to the viewport
5414
+ // but still lets clicks through to chips inside it — verified against pixi.js 8.16's
5415
+ // EventBoundary.) When it fits, drop the scroll box entirely so nothing is masked needlessly.
5416
+ const gridH = grid.measureSize().h;
5417
+ const maxGridH = Math.min(host.screenH * 0.5, 28 * em);
5418
+ if (opts.autoFit && gridH > maxGridH) {
5419
+ const scroll = new SizedScrollBox(innerW, maxGridH, host.canvas);
5420
+ scroll.content.addChild(grid);
5421
+ scroll.refresh();
5422
+ modal.body.add(scroll);
5423
+ }
5424
+ else {
5425
+ modal.body.add(grid);
5426
+ }
5343
5427
  modal.setActions([
5344
5428
  {
5345
5429
  label: opts.confirmLabel,
@@ -5356,6 +5440,7 @@ function openBetPicker(host) {
5356
5440
  tag: 'bet-modal',
5357
5441
  title: host.t('Bet'),
5358
5442
  columns: { wide: 6, mobile: 3 },
5443
+ autoFit: true, // reflow columns + scroll when a wide currency makes the labels grow
5359
5444
  maxEm: 44, // wider card to fit 6 chips/row
5360
5445
  confirmLabel: host.t('Confirm'),
5361
5446
  choices: host.state.availableBets.map((b) => ({ id: String(b), label: host.fmt(b) })),