@open-slot-ui/pixi 0.8.3 → 0.9.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.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { ControlView, ButtonView, SliderView, buildBlockColumn, OpenUIPixi } from './chunk-PYXZIGXG.js';
2
- export { AutoplayDrawerView, AutoplayView, ButtonView, ControlView, DialogView, MenuView, OpenUIPixi, ReadoutView, SelectView, SliderView, SpinView, StepperView, TextCellRenderer, ToggleView, TurboView, Tweener, ValueDisplayView, buildBlockColumn, defaultSpinSkin, drawSpin, isDesktop, svgSpinSkin } from './chunk-PYXZIGXG.js';
1
+ import { ControlView, ButtonView, SliderView, buildBlockColumn, OpenUIPixi } from './chunk-Z7X5JH7H.js';
2
+ export { AutoplayDrawerView, AutoplayView, ButtonView, ControlView, DialogView, MenuView, OpenUIPixi, ReadoutView, SelectView, SliderView, SpinView, StepperView, TextCellRenderer, ToggleView, TurboView, Tweener, ValueDisplayView, buildBlockColumn, defaultSpinSkin, drawSpin, isDesktop, svgSpinSkin } from './chunk-Z7X5JH7H.js';
3
3
  import { Text, Graphics, Container, Rectangle } from 'pixi.js';
4
- import { LOCALE_LABELS, createUI, composeMenu, buildPanel, formatAmount } from '@open-slot-ui/core';
4
+ import { LOCALE_LABELS, createUI, composeMenu, buildPanel, modeStatsItems, formatAmountPrecise, factsVars, auditRules } from '@open-slot-ui/core';
5
5
 
6
6
  var RISE = 26;
7
7
  var STAGGER = 38;
@@ -280,7 +280,11 @@ var PanelBodyView = class extends ControlView {
280
280
  };
281
281
  var esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
282
282
  var rich = (s) => esc(s).replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
283
- function renderBlocks(blocks, tr) {
283
+ function statGridHtml(items, tr) {
284
+ const rows = items.map((it) => `<div><dt>${esc(tr(it.label))}</dt><dd>${esc(tr(it.value))}</dd></div>`).join("");
285
+ return `<dl class="ohm-stats">${rows}</dl>`;
286
+ }
287
+ function renderBlocks(blocks, tr, facts) {
284
288
  const out = [];
285
289
  for (const b of blocks) {
286
290
  switch (b.kind) {
@@ -322,9 +326,16 @@ function renderBlocks(blocks, tr) {
322
326
  out.push(`<table class="ohm-table">${head}<tbody>${body}</tbody></table>`);
323
327
  break;
324
328
  }
325
- case "stat-grid": {
326
- const rows = b.items.map((it) => `<div><dt>${esc(tr(it.label))}</dt><dd>${esc(tr(it.value))}</dd></div>`).join("");
327
- out.push(`<dl class="ohm-stats">${rows}</dl>`);
329
+ case "stat-grid":
330
+ out.push(statGridHtml(b.items, tr));
331
+ break;
332
+ // The AUTO per-mode RTP / Max-win grid — rendered straight from the declared
333
+ // game facts (+ any host extras), so the table can never drift from the config.
334
+ // `modeStatsItems` localizes the label PARTS itself ("Max win" · the mode name),
335
+ // so the grid renderer gets identity-tr — no double translation.
336
+ case "mode-stats": {
337
+ const extras = (b.extras ?? []).map((e) => ({ label: tr(e.label), value: tr(e.value) }));
338
+ out.push(statGridHtml([...modeStatsItems(facts, tr), ...extras], (s) => s));
328
339
  break;
329
340
  }
330
341
  case "steps": {
@@ -394,8 +405,30 @@ function mountInfoMenu(app, ui) {
394
405
  const soundMode = menu.sound ?? "sliders";
395
406
  const volRow = (id, label, hint, val) => `<div class="ohm-setting"><label class="ohm-row"><span>${esc(tr(label))}</span><input class="ohm-slider" data-vol="${id}" type="range" min="0" max="1" step="0.01" value="${val}"></label><div class="ohm-hint">${esc(tr(hint))}</div></div>`;
396
407
  const soundSliders = soundMode === "master" ? volRow("master", "Volume", "Adjust the overall game volume.", ui.sfxSlider.value.get()) : soundMode === "sliders" ? volRow("music", "Music", "Adjust the background music volume.", ui.musicSlider.value.get()) + volRow("sfx", "Effects", "Adjust the sound-effects volume.", ui.sfxSlider.value.get()) : "";
397
- const paytableHtml = menu.paytable ? renderBlocks(menu.paytable, tr) : "";
398
- const rulesHtml = menu.rules ? renderBlocks(menu.rules, tr) : "";
408
+ const rulesTr = () => {
409
+ const vars2 = factsVars(ui.facts.get(), { "game.name": ui.gameInfo.name ?? "", "game.version": ui.gameInfo.version ?? "" });
410
+ return (s) => ui.t(s, vars2);
411
+ };
412
+ const auditCardHtml = (trr) => {
413
+ if (!menu.rules?.length) return "";
414
+ const issues = auditRules(ui.facts.get(), menu.rules, { resolve: trr });
415
+ if (!issues.length) return "";
416
+ const req = issues.filter((i) => i.level === "required");
417
+ const rec = issues.filter((i) => i.level === "recommended");
418
+ const list = (items) => `<ul>${items.map((i) => `<li>${esc(tr(i.message))}</li>`).join("")}</ul>`;
419
+ return `<div class="ohm-audit" role="alert">
420
+ <div class="ohm-audit-title">\u26A0 ${esc(tr("openui.rulesAudit.title"))}</div>
421
+ ${req.length ? `<div class="ohm-audit-sub">${esc(tr("openui.rulesAudit.required"))}</div>${list(req)}` : ""}
422
+ ${rec.length ? `<div class="ohm-audit-sub ohm-audit-sub--rec">${esc(tr("openui.rulesAudit.recommended"))}</div><div class="ohm-audit-rec">${list(rec)}</div>` : ""}
423
+ </div>`;
424
+ };
425
+ const rulesInnerHtml = () => {
426
+ if (!menu.rules) return "";
427
+ const trr = rulesTr();
428
+ return auditCardHtml(trr) + renderBlocks(menu.rules, trr, ui.facts.get());
429
+ };
430
+ const paytableHtml = menu.paytable ? renderBlocks(menu.paytable, tr, ui.facts.get()) : "";
431
+ const rulesHtml = rulesInnerHtml();
399
432
  const banner = menu.banner ? `<img class="ohm-logo" alt="" src="${menu.banner.src}"${menu.banner.width ? ` width="${menu.banner.width}"` : ""}${menu.banner.height ? ` height="${menu.banner.height}"` : ""}>` : ui.gameInfo.name ? `<h1 class="ohm-logo-text">${esc(ui.gameInfo.name)}</h1>` : "";
400
433
  host2.innerHTML = `
401
434
  <div class="ohm-backdrop" data-close></div>
@@ -467,19 +500,40 @@ function mountInfoMenu(app, ui) {
467
500
  else if (el instanceof HTMLSelectElement) el.addEventListener("change", () => ui.bus.emit("optionSelected", { id, value: el.value, index: el.selectedIndex }));
468
501
  });
469
502
  host2.querySelectorAll("[data-close]").forEach((b) => b.addEventListener("click", () => ui.settingsPanel.closePanel()));
503
+ const refreshRules = () => {
504
+ const rulesEl = host2.querySelector("#ohm-rules");
505
+ if (rulesEl && menu.rules) rulesEl.innerHTML = rulesInnerHtml();
506
+ };
470
507
  disposers.push(
471
508
  ui.locale.subscribe(() => {
472
509
  const body = host2.querySelector(".ohm-body");
473
510
  if (body) {
474
- const rulesEl = host2.querySelector("#ohm-rules");
475
- if (rulesEl && menu.rules) rulesEl.innerHTML = renderBlocks(menu.rules, tr);
511
+ refreshRules();
476
512
  if (lang) lang.value = ui.locale.get();
477
513
  }
514
+ }),
515
+ ui.facts.subscribe(refreshRules)
516
+ );
517
+ let menuLocked = false;
518
+ disposers.push(
519
+ ui.settingsPanel.state.subscribe(() => {
520
+ const isOpen = ui.settingsPanel.isOpen;
521
+ host2.classList.toggle("open", isOpen);
522
+ if (isOpen && !menuLocked) {
523
+ ui.lock();
524
+ menuLocked = true;
525
+ } else if (!isOpen && menuLocked) {
526
+ ui.unlock();
527
+ menuLocked = false;
528
+ }
478
529
  })
479
530
  );
480
- disposers.push(ui.settingsPanel.state.subscribe(() => host2.classList.toggle("open", ui.settingsPanel.isOpen)));
481
531
  return () => {
482
532
  for (const d of disposers.splice(0)) d();
533
+ if (menuLocked) {
534
+ ui.unlock();
535
+ menuLocked = false;
536
+ }
483
537
  host2.remove();
484
538
  };
485
539
  }
@@ -533,6 +587,13 @@ var OHM_CSS = `
533
587
  .ohm-stats { margin: 12px 0; display: grid; grid-template-columns: 1fr 1fr; gap: 0 28px; }
534
588
  .ohm-stats > div { display: flex; justify-content: space-between; padding: 9px 0; border-bottom: 1px solid color-mix(in srgb, var(--text-dim) 20%, transparent); }
535
589
  .ohm-stats dt { color: var(--text-dim); margin: 0; } .ohm-stats dd { margin: 0; font-weight: 700; }
590
+ .ohm-audit { margin: 12px 0 18px; padding: 14px 16px; border-radius: 12px; border: 2px solid #d03131; background: color-mix(in srgb, #d03131 8%, transparent); }
591
+ .ohm-audit-title { font-weight: 900; letter-spacing: .5px; color: #b31d1d; }
592
+ .ohm-audit-sub { margin-top: 8px; font-size: 13px; font-weight: 800; color: #b31d1d; }
593
+ .ohm-audit-sub--rec { color: #b07d09; }
594
+ .ohm-audit ul { margin: 6px 0 0; padding-left: 20px; color: var(--text); font-size: 13.5px; line-height: 1.55; }
595
+ .ohm-audit li { margin: 3px 0; }
596
+ .ohm-audit-rec ul { color: var(--text-dim); }
536
597
  .ohm-callout { margin: 16px 0 4px; padding: 14px 16px; border-radius: 12px; border-left: 4px solid var(--accent); background: color-mix(in srgb, var(--accent) 9%, transparent); }
537
598
  .ohm-callout b { color: var(--accent); } .ohm-callout p { margin: 4px 0 0; color: var(--text); }
538
599
  .ohm-callout--warning { border-left-color: #e0a106; background: color-mix(in srgb, #e0a106 10%, transparent); }
@@ -622,7 +683,7 @@ var CONFIRM_CSS = `
622
683
  // src/mountHud.ts
623
684
  function showReplayModal(ui, info, buttonKey, onSelect) {
624
685
  const cur = info.currency ?? ui.balance.currency.get();
625
- const money2 = (n) => formatAmount(n, cur);
686
+ const money2 = (n) => formatAmountPrecise(n, cur);
626
687
  ui.showNotice(
627
688
  [
628
689
  { kind: "heading", id: "openui-replay-h", text: "openui.replay.title" },
@@ -760,13 +821,14 @@ function mountHud(app, spec = {}, opts = {}) {
760
821
  };
761
822
  }
762
823
  function money(amount, cur) {
763
- return formatAmount(amount, cur);
824
+ return formatAmountPrecise(amount, cur);
764
825
  }
765
826
  var esc3 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
766
827
  function mountBuyFeatureModal(_app, hud, features, opts = {}) {
767
828
  const ui = hud.ui;
768
829
  const tr = (k) => ui.t(k);
769
830
  const list = features.slice(0, 4);
831
+ ui.declareFacts({ modes: list.map((f) => ({ id: f.id, name: f.name, kind: f.variant, cost: f.cost })) });
770
832
  const boosts = /* @__PURE__ */ new Set();
771
833
  const activation = opts.activation ?? "multi";
772
834
  const blocksBuy = opts.activationBlocksBuy ?? false;
@@ -889,10 +951,16 @@ function mountBuyFeatureModal(_app, hud, features, opts = {}) {
889
951
  $("#bfm-plus").addEventListener("click", () => ui.betStepper.inc());
890
952
  disposers.push(ui.bet.value.subscribe(() => renderCards()));
891
953
  const open = () => {
954
+ if (host2.classList.contains("open")) return;
892
955
  renderCards();
893
956
  host2.classList.add("open");
957
+ ui.lock();
958
+ };
959
+ const close = () => {
960
+ if (!host2.classList.contains("open")) return;
961
+ host2.classList.remove("open");
962
+ ui.unlock();
894
963
  };
895
- const close = () => host2.classList.remove("open");
896
964
  host2.querySelectorAll("[data-close]").forEach((b) => b.addEventListener("click", close));
897
965
  disposers.push(ui.on("buttonActivated", ({ id }) => {
898
966
  if (id === "bonus") open();
@@ -910,6 +978,7 @@ function mountBuyFeatureModal(_app, hud, features, opts = {}) {
910
978
  renderCards();
911
979
  return () => {
912
980
  window.removeEventListener("resize", onResize);
981
+ if (host2.classList.contains("open")) ui.unlock();
913
982
  for (const d of disposers.splice(0)) d();
914
983
  host2.remove();
915
984
  };