@open-slot-ui/core 0.12.0 → 0.13.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.cjs +33 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +33 -15
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.d.cts
CHANGED
|
@@ -252,6 +252,15 @@ interface LayoutSpec {
|
|
|
252
252
|
scale?: number;
|
|
253
253
|
/** Rotation in degrees, clockwise. Default 0. */
|
|
254
254
|
rotation?: number;
|
|
255
|
+
/**
|
|
256
|
+
* Which rect the anchor resolves against. `'stage'` (default) = the uniformly-scaled
|
|
257
|
+
* design FRAME, so the HUD scales as one homogeneous unit and inter-control gaps never
|
|
258
|
+
* change with aspect ratio. `'screen'` = the RAW viewport, so a control hugs the true
|
|
259
|
+
* screen edge even when the frame is letterboxed off it — for the corner utility buttons
|
|
260
|
+
* (mute / fullscreen / RTP / timers), which must sit in the viewport corner at every
|
|
261
|
+
* aspect (otherwise a bottom-anchored letterboxed frame drops a "top" control mid-screen).
|
|
262
|
+
*/
|
|
263
|
+
origin?: 'stage' | 'screen';
|
|
255
264
|
}
|
|
256
265
|
interface Placement {
|
|
257
266
|
x: number;
|
|
@@ -1747,6 +1756,12 @@ type BlockSpec = {
|
|
|
1747
1756
|
payouts: string;
|
|
1748
1757
|
icon?: string;
|
|
1749
1758
|
}>;
|
|
1759
|
+
} | {
|
|
1760
|
+
kind: 'paylines';
|
|
1761
|
+
id: string;
|
|
1762
|
+
reels: number;
|
|
1763
|
+
rows: number;
|
|
1764
|
+
lines: number[][];
|
|
1750
1765
|
} | {
|
|
1751
1766
|
kind: 'image';
|
|
1752
1767
|
id: string;
|
|
@@ -1786,7 +1801,7 @@ type BlockSpec = {
|
|
|
1786
1801
|
children: BlockSpec[];
|
|
1787
1802
|
});
|
|
1788
1803
|
/** All block kinds, for runtime validation of untyped host data. */
|
|
1789
|
-
declare const BLOCK_KINDS: readonly ["slider", "toggle", "button", "select", "stepper", "value", "text", "heading", "subheading", "callout", "stat-grid", "mode-stats", "steps", "table", "paytable", "image", "media", "cards", "legal", "divider", "group"];
|
|
1804
|
+
declare const BLOCK_KINDS: readonly ["slider", "toggle", "button", "select", "stepper", "value", "text", "heading", "subheading", "callout", "stat-grid", "mode-stats", "steps", "table", "paytable", "paylines", "image", "media", "cards", "legal", "divider", "group"];
|
|
1790
1805
|
interface PanelSpec {
|
|
1791
1806
|
id: string;
|
|
1792
1807
|
variant: PanelVariant;
|
package/dist/index.d.ts
CHANGED
|
@@ -252,6 +252,15 @@ interface LayoutSpec {
|
|
|
252
252
|
scale?: number;
|
|
253
253
|
/** Rotation in degrees, clockwise. Default 0. */
|
|
254
254
|
rotation?: number;
|
|
255
|
+
/**
|
|
256
|
+
* Which rect the anchor resolves against. `'stage'` (default) = the uniformly-scaled
|
|
257
|
+
* design FRAME, so the HUD scales as one homogeneous unit and inter-control gaps never
|
|
258
|
+
* change with aspect ratio. `'screen'` = the RAW viewport, so a control hugs the true
|
|
259
|
+
* screen edge even when the frame is letterboxed off it — for the corner utility buttons
|
|
260
|
+
* (mute / fullscreen / RTP / timers), which must sit in the viewport corner at every
|
|
261
|
+
* aspect (otherwise a bottom-anchored letterboxed frame drops a "top" control mid-screen).
|
|
262
|
+
*/
|
|
263
|
+
origin?: 'stage' | 'screen';
|
|
255
264
|
}
|
|
256
265
|
interface Placement {
|
|
257
266
|
x: number;
|
|
@@ -1747,6 +1756,12 @@ type BlockSpec = {
|
|
|
1747
1756
|
payouts: string;
|
|
1748
1757
|
icon?: string;
|
|
1749
1758
|
}>;
|
|
1759
|
+
} | {
|
|
1760
|
+
kind: 'paylines';
|
|
1761
|
+
id: string;
|
|
1762
|
+
reels: number;
|
|
1763
|
+
rows: number;
|
|
1764
|
+
lines: number[][];
|
|
1750
1765
|
} | {
|
|
1751
1766
|
kind: 'image';
|
|
1752
1767
|
id: string;
|
|
@@ -1786,7 +1801,7 @@ type BlockSpec = {
|
|
|
1786
1801
|
children: BlockSpec[];
|
|
1787
1802
|
});
|
|
1788
1803
|
/** All block kinds, for runtime validation of untyped host data. */
|
|
1789
|
-
declare const BLOCK_KINDS: readonly ["slider", "toggle", "button", "select", "stepper", "value", "text", "heading", "subheading", "callout", "stat-grid", "mode-stats", "steps", "table", "paytable", "image", "media", "cards", "legal", "divider", "group"];
|
|
1804
|
+
declare const BLOCK_KINDS: readonly ["slider", "toggle", "button", "select", "stepper", "value", "text", "heading", "subheading", "callout", "stat-grid", "mode-stats", "steps", "table", "paytable", "paylines", "image", "media", "cards", "legal", "divider", "group"];
|
|
1790
1805
|
interface PanelSpec {
|
|
1791
1806
|
id: string;
|
|
1792
1807
|
variant: PanelVariant;
|
package/dist/index.js
CHANGED
|
@@ -131,10 +131,10 @@ function anchorFactors(anchor) {
|
|
|
131
131
|
function resolvePlacement(spec, screen) {
|
|
132
132
|
const [ax, ay] = anchorFactors(spec.anchor);
|
|
133
133
|
const [ox, oy] = spec.offset ?? [0, 0];
|
|
134
|
-
const
|
|
134
|
+
const rect = spec.origin === "screen" ? { x: 0, y: 0, width: screen.width, height: screen.height } : screen.stage;
|
|
135
135
|
return {
|
|
136
|
-
x:
|
|
137
|
-
y:
|
|
136
|
+
x: rect.x + ax * rect.width + ox * screen.scale,
|
|
137
|
+
y: rect.y + ay * rect.height + oy * screen.scale,
|
|
138
138
|
scale: screen.scale * (spec.scale ?? 1),
|
|
139
139
|
rotation: (spec.rotation ?? 0) * Math.PI / 180
|
|
140
140
|
};
|
|
@@ -158,11 +158,13 @@ var landscapeDefaultLayouts = Object.freeze({
|
|
|
158
158
|
// Right-anchored so a long localized caption grows LEFT and never clips the edge.
|
|
159
159
|
"total-win": { anchor: "bottom-right", offset: [-150, -300] },
|
|
160
160
|
settings: { anchor: "bottom-center", offset: [-740, -320], rotation: -5 },
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
161
|
+
// Corner utilities anchor to the SCREEN (not the letterboxed frame) so they hug the true
|
|
162
|
+
// viewport corners at every aspect — see `LayoutSpec.origin`.
|
|
163
|
+
mute: { anchor: "top-right", offset: [-122, 46], origin: "screen" },
|
|
164
|
+
fullscreen: { anchor: "top-right", offset: [-46, 46], origin: "screen" },
|
|
165
|
+
rtp: { anchor: "top-left", offset: [20, 18], origin: "screen" },
|
|
166
|
+
"session-timer": { anchor: "top-left", offset: [20, 46], origin: "screen" },
|
|
167
|
+
"net-position": { anchor: "top-left", offset: [20, 74], origin: "screen" }
|
|
166
168
|
});
|
|
167
169
|
var portraitDefaultLayouts = Object.freeze({
|
|
168
170
|
// Mobile sizes run ~1.5× the desktop base (the Figma mobile frame draws the
|
|
@@ -177,19 +179,21 @@ var portraitDefaultLayouts = Object.freeze({
|
|
|
177
179
|
settings: { anchor: "bottom-center", offset: [438, -480], scale: 1.5, rotation: -5 },
|
|
178
180
|
"bet-minus": { anchor: "bottom-center", offset: [-105, -267], scale: 1.5 },
|
|
179
181
|
"bet-plus": { anchor: "bottom-center", offset: [105, -267], scale: 1.5 },
|
|
180
|
-
|
|
181
|
-
|
|
182
|
+
// Corner utilities anchor to the SCREEN (see `LayoutSpec.origin`) so they stay in the true
|
|
183
|
+
// viewport corner even when the portrait frame is letterboxed.
|
|
184
|
+
mute: { anchor: "top-right", offset: [-159, 57], scale: 1.5, origin: "screen" },
|
|
185
|
+
fullscreen: { anchor: "top-right", offset: [-57, 57], scale: 1.5, origin: "screen" },
|
|
182
186
|
// Compliance readouts scale up ~2× on mobile with wider line spacing.
|
|
183
|
-
rtp: { anchor: "top-left", offset: [16, 16], scale: 2 },
|
|
184
|
-
"session-timer": { anchor: "top-left", offset: [16, 66], scale: 2 },
|
|
185
|
-
"net-position": { anchor: "top-left", offset: [16, 116], scale: 2 },
|
|
187
|
+
rtp: { anchor: "top-left", offset: [16, 16], scale: 2, origin: "screen" },
|
|
188
|
+
"session-timer": { anchor: "top-left", offset: [16, 66], scale: 2, origin: "screen" },
|
|
189
|
+
"net-position": { anchor: "top-left", offset: [16, 116], scale: 2, origin: "screen" },
|
|
186
190
|
balance: { anchor: "bottom-left", offset: [36, -110], rotation: -5 },
|
|
187
191
|
bet: { anchor: "bottom-right", offset: [-36, -110], rotation: 5 }
|
|
188
192
|
});
|
|
189
193
|
function defaultLayout(id) {
|
|
190
194
|
const l = landscapeDefaultLayouts[id];
|
|
191
195
|
if (!l) return { anchor: "center" };
|
|
192
|
-
return { anchor: l.anchor, offset: l.offset ? [l.offset[0], l.offset[1]] : void 0, scale: l.scale, rotation: l.rotation };
|
|
196
|
+
return { anchor: l.anchor, offset: l.offset ? [l.offset[0], l.offset[1]] : void 0, scale: l.scale, rotation: l.rotation, origin: l.origin };
|
|
193
197
|
}
|
|
194
198
|
|
|
195
199
|
// src/transition/Transition.ts
|
|
@@ -1273,6 +1277,7 @@ var BLOCK_KINDS = [
|
|
|
1273
1277
|
"steps",
|
|
1274
1278
|
"table",
|
|
1275
1279
|
"paytable",
|
|
1280
|
+
"paylines",
|
|
1276
1281
|
"image",
|
|
1277
1282
|
"media",
|
|
1278
1283
|
"cards",
|
|
@@ -1434,6 +1439,10 @@ function validateSpec(spec) {
|
|
|
1434
1439
|
if (b.kind === "paytable" && (!b.rows || b.rows.length === 0)) {
|
|
1435
1440
|
add("warn", `${p}.rows`, "empty-paytable", "a paytable block has no rows");
|
|
1436
1441
|
}
|
|
1442
|
+
if (b.kind === "paylines") {
|
|
1443
|
+
if (!(b.reels > 0) || !(b.rows > 0)) add("error", `${p}.reels`, "paylines-shape", "a paylines block needs reels > 0 and rows > 0");
|
|
1444
|
+
if (!b.lines || b.lines.length === 0) add("warn", `${p}.lines`, "empty-paylines", "a paylines block has no lines");
|
|
1445
|
+
}
|
|
1437
1446
|
if (b.kind === "image" && (!b.src || !b.src.trim())) {
|
|
1438
1447
|
add("error", `${p}.src`, "image-src", "an image block needs a src");
|
|
1439
1448
|
}
|
|
@@ -2457,7 +2466,7 @@ var OpenUI = class {
|
|
|
2457
2466
|
|
|
2458
2467
|
// src/spec/responsive.ts
|
|
2459
2468
|
function cloneLayout(l) {
|
|
2460
|
-
return { anchor: l.anchor, offset: l.offset ? [l.offset[0], l.offset[1]] : void 0, scale: l.scale, rotation: l.rotation };
|
|
2469
|
+
return { anchor: l.anchor, offset: l.offset ? [l.offset[0], l.offset[1]] : void 0, scale: l.scale, rotation: l.rotation, origin: l.origin };
|
|
2461
2470
|
}
|
|
2462
2471
|
function activeKeys(screen) {
|
|
2463
2472
|
return [screen.orientation, screen.breakpoint];
|
|
@@ -2915,6 +2924,15 @@ function renderBlocksHtml(blocks, tr, facts) {
|
|
|
2915
2924
|
case "paytable":
|
|
2916
2925
|
out.push(`<div class="ohm-grid">${renderPaytable(b.rows, tr)}</div>`);
|
|
2917
2926
|
break;
|
|
2927
|
+
case "paylines": {
|
|
2928
|
+
const masks = b.lines.map((line, i) => {
|
|
2929
|
+
const cells = [];
|
|
2930
|
+
for (let row = 0; row < b.rows; row++) for (let reel = 0; reel < b.reels; reel++) cells.push(`<i class="${line[reel] === row ? "on" : ""}"></i>`);
|
|
2931
|
+
return `<div class="ohm-line"><div class="ohm-linegrid" style="grid-template-columns:repeat(${b.reels},1fr)">${cells.join("")}</div><span>${i + 1}</span></div>`;
|
|
2932
|
+
}).join("");
|
|
2933
|
+
out.push(`<div class="ohm-lines">${masks}</div>`);
|
|
2934
|
+
break;
|
|
2935
|
+
}
|
|
2918
2936
|
case "table": {
|
|
2919
2937
|
const head = b.columns?.length ? `<thead><tr>${b.columns.map((c) => `<th>${esc(tr(c))}</th>`).join("")}</tr></thead>` : "";
|
|
2920
2938
|
const body = b.rows.map((r) => `<tr>${r.map((c) => `<td>${esc(tr(c))}</td>`).join("")}</tr>`).join("");
|