diff --git a/src/core/BTAPI.test.ts b/src/core/BTAPI.test.ts index 3d11d0b..71b16d6 100644 --- a/src/core/BTAPI.test.ts +++ b/src/core/BTAPI.test.ts @@ -58,7 +58,13 @@ function makeMockCanvas(): HTMLCanvasElement { return { width: 0, height: 0, - style: { width: '', height: '', touchAction: '' }, + style: { + width: '', + height: '', + touchAction: '', + setProperty: vi.fn(), + getPropertyValue: vi.fn(() => ''), + }, getContext: (type: string) => (type === 'webgpu' ? createMockGPUCanvasContext() : null), addEventListener: vi.fn(), removeEventListener: vi.fn(), diff --git a/src/core/BTAPI.ts b/src/core/BTAPI.ts index 3bee7df..f7dcad6 100644 --- a/src/core/BTAPI.ts +++ b/src/core/BTAPI.ts @@ -18,6 +18,7 @@ import type { IRenderer } from '../render/IRenderer'; import { SoftwareRenderer } from '../render/SoftwareRenderer'; import { SoftwareTicker } from '../render/SoftwareTicker'; import { WebGpuRenderer } from '../render/WebGpuRenderer'; +import { applyCanvasLayoutStyles, DEFAULT_MAX_CANVAS_DISPLAY_SIZE } from '../utils/CanvasLayoutStyles'; import type { Color32 } from '../utils/Color32'; import type { EasingFunction } from '../utils/Easing'; import { @@ -859,6 +860,14 @@ export class BTAPI { * @returns `true` when the renderer is ready; `false` on failure. */ private async initRenderer(canvas: HTMLCanvasElement, hw: HardwareSettings): Promise { + applyCanvasLayoutStyles(canvas, { + displaySize: hw.displaySize, + maxCanvasDisplaySize: + hw.maxCanvasDisplaySize ?? + new Vector2i(DEFAULT_MAX_CANVAS_DISPLAY_SIZE.x, DEFAULT_MAX_CANVAS_DISPLAY_SIZE.y), + ...(hw.canvasDisplaySize !== undefined ? { canvasDisplaySize: hw.canvasDisplaySize } : {}), + }); + const requestedBackend = hw.renderer ?? 'webgpu'; if (requestedBackend !== 'software') { diff --git a/src/core/IBlitTechDemo.test.ts b/src/core/IBlitTechDemo.test.ts index 3dd0511..a640ae3 100644 --- a/src/core/IBlitTechDemo.test.ts +++ b/src/core/IBlitTechDemo.test.ts @@ -32,6 +32,13 @@ describe('defaultConfig', () => { expect(settings.canvasDisplaySize?.y).toBe(480); }); + it('should include 960x720 maxCanvasDisplaySize by default', () => { + const settings = defaultConfig(); + + expect(settings.maxCanvasDisplaySize?.x).toBe(960); + expect(settings.maxCanvasDisplaySize?.y).toBe(720); + }); + it("should default outputUpscaleFilter to 'nearest'", () => { const settings = defaultConfig(); @@ -45,5 +52,6 @@ describe('defaultConfig', () => { expect(a).not.toBe(b); expect(a.displaySize).not.toBe(b.displaySize); expect(a.canvasDisplaySize).not.toBe(b.canvasDisplaySize); + expect(a.maxCanvasDisplaySize).not.toBe(b.maxCanvasDisplaySize); }); }); diff --git a/src/core/IBlitTechDemo.ts b/src/core/IBlitTechDemo.ts index 0f0edc7..9cdcdcd 100644 --- a/src/core/IBlitTechDemo.ts +++ b/src/core/IBlitTechDemo.ts @@ -41,6 +41,13 @@ export interface HardwareSettings { */ canvasDisplaySize?: Vector2i; + /** + * Maximum on-screen canvas size in CSS pixels. The demos layout scales the + * canvas up to the viewport (preserving aspect ratio) but not beyond this + * size. Defaults to `960x720` in {@link defaultConfig}. + */ + maxCanvasDisplaySize?: Vector2i; + /** * Magnification filter applied between the pixel chain and the display * chain. `'nearest'` preserves crisp pixel edges (default); `'linear'` @@ -147,6 +154,7 @@ export function defaultConfig(): HardwareSettings { return { displaySize: new Vector2i(320, 240), canvasDisplaySize: new Vector2i(640, 480), + maxCanvasDisplaySize: new Vector2i(960, 720), targetFPS: 60, outputUpscaleFilter: 'nearest', }; diff --git a/src/core/WebGPUContext.test.ts b/src/core/WebGPUContext.test.ts index 2baad49..1358c78 100644 --- a/src/core/WebGPUContext.test.ts +++ b/src/core/WebGPUContext.test.ts @@ -143,7 +143,7 @@ describe('initWebGPU', () => { expect(canvas.height).toBe(240); }); - it('should set CSS size when canvasDisplaySize is provided', async () => { + it('should set drawing-buffer size from canvasDisplaySize when provided', async () => { installMockNavigatorGPU(); const canvas = createMockCanvas(); @@ -151,19 +151,8 @@ describe('initWebGPU', () => { await initWebGPU(canvas, displaySize, canvasDisplaySize); - expect(canvas.style.width).toBe('640px'); - expect(canvas.style.height).toBe('480px'); - }); - - it('should not set CSS size when canvasDisplaySize is not provided', async () => { - installMockNavigatorGPU(); - - const canvas = createMockCanvas(); - - await initWebGPU(canvas, displaySize); - - expect(canvas.style.width).toBe(''); - expect(canvas.style.height).toBe(''); + expect(canvas.width).toBe(640); + expect(canvas.height).toBe(480); }); // #endregion diff --git a/src/core/WebGPUContext.ts b/src/core/WebGPUContext.ts index 8306824..9dda80b 100644 --- a/src/core/WebGPUContext.ts +++ b/src/core/WebGPUContext.ts @@ -90,9 +90,6 @@ export async function initWebGPU( // Only touch CSS when an explicit canvasDisplaySize was supplied. Demos // that omit it can style the canvas via HTML/CSS without us overriding. if (canvasDisplaySize) { - canvas.style.width = `${canvasDisplaySize.x}px`; - canvas.style.height = `${canvasDisplaySize.y}px`; - console.log( `[BT] Canvas drawing buffer: ${drawingBufferSize.x}x${drawingBufferSize.y} ` + `(logical render: ${displaySize.x}x${displaySize.y})`, diff --git a/src/render/SoftwareRenderer.ts b/src/render/SoftwareRenderer.ts index 9e1c29e..aa7cda4 100644 --- a/src/render/SoftwareRenderer.ts +++ b/src/render/SoftwareRenderer.ts @@ -93,7 +93,6 @@ export class SoftwareRenderer implements IRenderer { private readonly canvas: HTMLCanvasElement; private readonly displaySize: Vector2i; private readonly outputSize: Vector2i; - private readonly explicitOutputSize: boolean; private outputCtx: Canvas2D | null = null; private logicalCanvas: OffscreenCanvas | HTMLCanvasElement | null = null; @@ -121,7 +120,6 @@ export class SoftwareRenderer implements IRenderer { constructor(canvas: HTMLCanvasElement, displaySize: Vector2i, outputSize?: Vector2i) { this.canvas = canvas; this.displaySize = displaySize.clone(); - this.explicitOutputSize = outputSize !== undefined; this.outputSize = (outputSize ?? displaySize).clone(); this.framePixels = new Uint8ClampedArray(this.displaySize.x * this.displaySize.y * 4); } @@ -139,11 +137,6 @@ export class SoftwareRenderer implements IRenderer { this.canvas.width = this.outputSize.x; this.canvas.height = this.outputSize.y; - if (this.explicitOutputSize) { - this.canvas.style.width = `${this.outputSize.x}px`; - this.canvas.style.height = `${this.outputSize.y}px`; - } - this.outputCtx = this.canvas.getContext('2d') as Canvas2D | null; if (!this.outputCtx) { return false; diff --git a/src/utils/CanvasLayoutStyles.test.ts b/src/utils/CanvasLayoutStyles.test.ts new file mode 100644 index 0000000..ca82f00 --- /dev/null +++ b/src/utils/CanvasLayoutStyles.test.ts @@ -0,0 +1,51 @@ +// @vitest-environment happy-dom + +/** + * Unit tests for {@link applyCanvasLayoutStyles}. + */ + +import { describe, expect, it } from 'vitest'; + +import { applyCanvasLayoutStyles } from './CanvasLayoutStyles'; +import { Vector2i } from './Vector2i'; + +describe('applyCanvasLayoutStyles', () => { + it('sets display aspect and max size CSS variables on #canvas-container', () => { + const container = document.createElement('div'); + const canvas = document.createElement('canvas'); + + container.id = 'canvas-container'; + container.appendChild(canvas); + document.body.appendChild(container); + + applyCanvasLayoutStyles(canvas, { + displaySize: new Vector2i(320, 240), + canvasDisplaySize: new Vector2i(640, 480), + maxCanvasDisplaySize: new Vector2i(960, 720), + }); + + expect(container.style.getPropertyValue('--canvas-display-w')).toBe('640'); + expect(container.style.getPropertyValue('--canvas-display-h')).toBe('480'); + expect(container.style.getPropertyValue('--canvas-max-w')).toBe('960px'); + expect(container.style.getPropertyValue('--canvas-max-h')).toBe('720px'); + expect(canvas.style.getPropertyValue('max-width')).toBe('960px'); + expect(canvas.style.getPropertyValue('max-height')).toBe('720px'); + }); + + it('uses displaySize for aspect when canvasDisplaySize is omitted', () => { + const container = document.createElement('div'); + const canvas = document.createElement('canvas'); + + container.id = 'canvas-container'; + container.appendChild(canvas); + + applyCanvasLayoutStyles(canvas, { + displaySize: new Vector2i(400, 300), + maxCanvasDisplaySize: new Vector2i(800, 600), + }); + + expect(container.style.getPropertyValue('--canvas-display-w')).toBe('400'); + expect(container.style.getPropertyValue('--canvas-display-h')).toBe('300'); + expect(canvas.style.width).toBe(''); + }); +}); diff --git a/src/utils/CanvasLayoutStyles.ts b/src/utils/CanvasLayoutStyles.ts new file mode 100644 index 0000000..8abd7af --- /dev/null +++ b/src/utils/CanvasLayoutStyles.ts @@ -0,0 +1,72 @@ +import { DEFAULT_CONTAINER_ID } from './BootstrapHelpers'; +import type { Vector2i } from './Vector2i'; + +// #region Constants + +/** Default cap for on-screen canvas CSS size (demos layout uses these as max bounds). */ +export const DEFAULT_MAX_CANVAS_DISPLAY_SIZE = { x: 960, y: 720 } as const; + +// #endregion + +// #region Types + +/** Inputs for {@link applyCanvasLayoutStyles}. */ +export interface CanvasLayoutStyleOptions { + /** Logical render resolution (used for aspect ratio when `canvasDisplaySize` is omitted). */ + displaySize: Vector2i; + /** Optional output / drawing-buffer size (drives aspect ratio when set). */ + canvasDisplaySize?: Vector2i; + /** Maximum on-screen canvas size in CSS pixels (letterboxing still applies below this). */ + maxCanvasDisplaySize: Vector2i; +} + +// #endregion + +// #region Exported Helpers + +/** + * Resolves the layout root element for CSS custom property injection. + * + * @param canvas - The canvas element to check. + * @returns The parent container when it carries the expected ID, otherwise the canvas itself. + */ +function resolveLayoutRoot(canvas: HTMLCanvasElement): HTMLElement { + const parent = canvas.parentElement; + + if (parent?.id === DEFAULT_CONTAINER_ID) { + return parent; + } + + return canvas; +} + +/** + * Publishes CSS custom properties used by demo page layout to size the canvas. + * + * The demos `layout.html` reads `--canvas-display-w/h` for aspect ratio and + * `--canvas-max-w/h` for the largest allowed display size. + * + * @param canvas - Target canvas element. + * @param options - Display and max sizes from {@link HardwareSettings}. + */ +export function applyCanvasLayoutStyles(canvas: HTMLCanvasElement, options: CanvasLayoutStyleOptions): void { + const aspectSource = options.canvasDisplaySize ?? options.displaySize; + const layoutRoot = resolveLayoutRoot(canvas); + + layoutRoot.style.setProperty('--canvas-display-w', String(aspectSource.x)); + layoutRoot.style.setProperty('--canvas-display-h', String(aspectSource.y)); + const maxW = `${options.maxCanvasDisplaySize.x}px`; + const maxH = `${options.maxCanvasDisplaySize.y}px`; + + layoutRoot.style.setProperty('--canvas-max-w', maxW); + layoutRoot.style.setProperty('--canvas-max-h', maxH); + canvas.style.setProperty('max-width', maxW, 'important'); + canvas.style.setProperty('max-height', maxH, 'important'); + + if (options.canvasDisplaySize) { + canvas.style.width = `${options.canvasDisplaySize.x}px`; + canvas.style.height = `${options.canvasDisplaySize.y}px`; + } +} + +// #endregion -- 2.51.2 From 0ec3d8fbe85aae81dc8463862c5a4da103da35c8 Mon Sep 17 00:00:00 2001 From: Vaclav Vancura Date: Sat, 16 May 2026 21:07:40 +0200 Subject: [PATCH 2/3] chore: upgrade biome from 2.4.13 to 2.4.15 Update @biomejs/biome in package.json and align the biome.json schema URL to the matching version, removing the schema-mismatch info warning emitted during preflight. Signed-off-by: Vaclav Vancura --- biome.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/biome.json b/biome.json index 2f78dc5..c7fe2b1 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.4.13/schema.json", + "$schema": "https://biomejs.dev/schemas/2.4.15/schema.json", "vcs": { "enabled": true, "clientKind": "git", diff --git a/package.json b/package.json index ce185e9..427b8f1 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ } }, "devDependencies": { - "@biomejs/biome": "^2.4.13", + "@biomejs/biome": "^2.4.15", "@commitlint/cli": "^20.5.0", "@commitlint/config-conventional": "^20.5.0", "@eslint/js": "^10.0.1", -- 2.51.2 From 4744512fba931fc245042cd5279482d4669177cc Mon Sep 17 00:00:00 2001 From: Vaclav Vancura Date: Sat, 16 May 2026 21:32:55 +0200 Subject: [PATCH 3/3] fix(utils): clear stale canvas inline styles and sync lockfile applyCanvasLayoutStyles now explicitly resets canvas.style.width and canvas.style.height to empty strings when canvasDisplaySize is omitted, preventing stale CSS from a prior call persisting. defaultConfig() now derives maxCanvasDisplaySize from the exported DEFAULT_MAX_CANVAS_DISPLAY_SIZE constant instead of a hardcoded Vector2i(960, 720), establishing a single source of truth. Added a regression test that calls applyCanvasLayoutStyles twice on the same canvas element and asserts inline dimensions are cleared when canvasDisplaySize is removed on the second call. Synced pnpm-lock.yaml to reflect the biome 2.4.15 specifier from the prior commit; the missing lockfile update was the root cause of all four CI failures on PR #152. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Vaclav Vancura --- pnpm-lock.yaml | 74 ++++++++++++++-------------- src/core/IBlitTechDemo.ts | 3 +- src/utils/CanvasLayoutStyles.test.ts | 24 +++++++++ src/utils/CanvasLayoutStyles.ts | 3 ++ 4 files changed, 66 insertions(+), 38 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3e52552..4038a90 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,8 +13,8 @@ importers: .: devDependencies: '@biomejs/biome': - specifier: ^2.4.13 - version: 2.4.13 + specifier: ^2.4.15 + version: 2.4.15 '@commitlint/cli': specifier: ^20.5.0 version: 20.5.0(@types/node@25.6.0)(conventional-commits-parser@6.4.0)(typescript@5.9.3) @@ -179,55 +179,55 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} - '@biomejs/biome@2.4.13': - resolution: {integrity: sha512-gLXOwkOBBg0tr7bDsqlkIh4uFeKuMjxvqsrb1Tukww1iDmHcfr4Uu8MoQxp0Rcte+69+osRNWXwHsu/zxT6XqA==} + '@biomejs/biome@2.4.15': + resolution: {integrity: sha512-j5VH3a/h/HXTKBM50MDMxRCzkeLv9S2XJcW2WgnZT1+xyisi+0bISrXR82gCX+8S9lvK0skEvHJRN+3Ktr2hlw==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@2.4.13': - resolution: {integrity: sha512-2KImO1jhNFBa2oWConyr0x6flxbQpGKv6902uGXpYM62Xyem8U80j441SyUJ8KyngsmKbQjeIv1q2CQfDkNnYg==} + '@biomejs/cli-darwin-arm64@2.4.15': + resolution: {integrity: sha512-rF3PPqLq1yoST79zaQbDjVJwsuIeci/O+9bgNmC5QpgOqz6aqYuzA4abyAGx+mgyiDXn4A049xAN8gijbuR1Qg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@2.4.13': - resolution: {integrity: sha512-BKrJklbaFN4p1Ts4kPBczo+PkbsHQg57kmJ+vON9u2t6uN5okYHaSr7h/MutPCWQgg2lglaWoSmm+zhYW+oOkg==} + '@biomejs/cli-darwin-x64@2.4.15': + resolution: {integrity: sha512-/5KHXYMfSJs1fNXiX30xFtI8JcCFV6zaVVLxOa0M2sfqBKHkpQhRTv94yxQWxeTY2lzo2OuTlNvPC+hDQt2wcQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@2.4.13': - resolution: {integrity: sha512-U5MsuBQW25dXaYtqWWSPM3P96H6Y+fHuja3TQpMNnylocHW0tEbtFTDlUj6oM+YJLntvEkQy4grBvQNUD4+RCg==} + '@biomejs/cli-linux-arm64-musl@2.4.15': + resolution: {integrity: sha512-ZPcxznxm0pogHBLZhYntyR3sR+MrZjqJIKEr7ZqVen0Rl+P/4upVmfYXjftizi9RoqZntg33fv/1fbdhbYXpEQ==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-arm64@2.4.13': - resolution: {integrity: sha512-NzkUDSqfvMBrPplKgVr3aXLHZ2NEELvvF4vZxXulEylKWIGqlvNEcwUcj9OLrn75TD3lJ/GIqCVlBwd1MZCuYQ==} + '@biomejs/cli-linux-arm64@2.4.15': + resolution: {integrity: sha512-owaAMZD/T4LrD0ELNCk0Km3qrRHuM0X6EAyVE1FSqGY0rbLoiDLrO4Us2tllm6cAeB2Ioa9C2C08NZPdr8+0Ug==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-x64-musl@2.4.13': - resolution: {integrity: sha512-Z601MienRgTBDza/+u2CH3RSrWoXo9rtr8NK6A4KJzqGgfxx+H3VlyLgTJ4sRo40T3pIsqpTmiOQEvYzQvBRvQ==} + '@biomejs/cli-linux-x64-musl@2.4.15': + resolution: {integrity: sha512-CNq/9W38SYSH023lfcQ4KKU8K0YX8T//FZUhcgtMMRABDojx5XsMV7jlweAvGSl389wJQB29Qo6Zb/a+jdvt+w==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-linux-x64@2.4.13': - resolution: {integrity: sha512-Az3ZZedYRBo9EQzNnD9SxFcR1G5QsGo6VEc2hIyVPZ1rdKwee/7E9oeBBZFpE8Z44ekxsDQBqbiWGW5ShOhUSQ==} + '@biomejs/cli-linux-x64@2.4.15': + resolution: {integrity: sha512-0jj7THz12GbUOLmMibktK6DZjqz2zV64KFxyBtcFTKPiiOIY0a7vns1elpO1dERvxpsZ5ik0oFfz0oGwFde1+g==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-win32-arm64@2.4.13': - resolution: {integrity: sha512-Px9PS2B5/Q183bUwy/5VHqp3J2lzdOCeVGzMpphYfl8oSa7VDCqenBdqWpy6DCy/en4Rbf/Y1RieZF6dJPcc9A==} + '@biomejs/cli-win32-arm64@2.4.15': + resolution: {integrity: sha512-ouhkYdlhp/1GghEJPdWwD/Vi3gQ1nFxuSpMolWsbq3Lsq3QUR4jl6UdhhscdCugKU5vOEuMiJhvKj66O0OCq+w==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@2.4.13': - resolution: {integrity: sha512-tTcMkXyBrmHi9BfrD2VNHs/5rYIUKETqsBlYOvSAABwBkJhSDVb5e7wPukftsQbO3WzQkXe6kaztC6WtUOXSoQ==} + '@biomejs/cli-win32-x64@2.4.15': + resolution: {integrity: sha512-zBrGq5mx5wwpnow4+2BxUvleDM+GNd4sLbPaMapsSLQLD0NGRCquqPBTgN+7XkUteHvj7M+BstuI8tmnV7+HgQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] @@ -3222,39 +3222,39 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} - '@biomejs/biome@2.4.13': + '@biomejs/biome@2.4.15': optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.4.13 - '@biomejs/cli-darwin-x64': 2.4.13 - '@biomejs/cli-linux-arm64': 2.4.13 - '@biomejs/cli-linux-arm64-musl': 2.4.13 - '@biomejs/cli-linux-x64': 2.4.13 - '@biomejs/cli-linux-x64-musl': 2.4.13 - '@biomejs/cli-win32-arm64': 2.4.13 - '@biomejs/cli-win32-x64': 2.4.13 + '@biomejs/cli-darwin-arm64': 2.4.15 + '@biomejs/cli-darwin-x64': 2.4.15 + '@biomejs/cli-linux-arm64': 2.4.15 + '@biomejs/cli-linux-arm64-musl': 2.4.15 + '@biomejs/cli-linux-x64': 2.4.15 + '@biomejs/cli-linux-x64-musl': 2.4.15 + '@biomejs/cli-win32-arm64': 2.4.15 + '@biomejs/cli-win32-x64': 2.4.15 - '@biomejs/cli-darwin-arm64@2.4.13': + '@biomejs/cli-darwin-arm64@2.4.15': optional: true - '@biomejs/cli-darwin-x64@2.4.13': + '@biomejs/cli-darwin-x64@2.4.15': optional: true - '@biomejs/cli-linux-arm64-musl@2.4.13': + '@biomejs/cli-linux-arm64-musl@2.4.15': optional: true - '@biomejs/cli-linux-arm64@2.4.13': + '@biomejs/cli-linux-arm64@2.4.15': optional: true - '@biomejs/cli-linux-x64-musl@2.4.13': + '@biomejs/cli-linux-x64-musl@2.4.15': optional: true - '@biomejs/cli-linux-x64@2.4.13': + '@biomejs/cli-linux-x64@2.4.15': optional: true - '@biomejs/cli-win32-arm64@2.4.13': + '@biomejs/cli-win32-arm64@2.4.15': optional: true - '@biomejs/cli-win32-x64@2.4.13': + '@biomejs/cli-win32-x64@2.4.15': optional: true '@commitlint/cli@20.5.0(@types/node@25.6.0)(conventional-commits-parser@6.4.0)(typescript@5.9.3)': diff --git a/src/core/IBlitTechDemo.ts b/src/core/IBlitTechDemo.ts index 9cdcdcd..92b3186 100644 --- a/src/core/IBlitTechDemo.ts +++ b/src/core/IBlitTechDemo.ts @@ -1,3 +1,4 @@ +import { DEFAULT_MAX_CANVAS_DISPLAY_SIZE } from '../utils/CanvasLayoutStyles'; import { Vector2i } from '../utils/Vector2i'; // #region Type Definitions @@ -154,7 +155,7 @@ export function defaultConfig(): HardwareSettings { return { displaySize: new Vector2i(320, 240), canvasDisplaySize: new Vector2i(640, 480), - maxCanvasDisplaySize: new Vector2i(960, 720), + maxCanvasDisplaySize: new Vector2i(DEFAULT_MAX_CANVAS_DISPLAY_SIZE.x, DEFAULT_MAX_CANVAS_DISPLAY_SIZE.y), targetFPS: 60, outputUpscaleFilter: 'nearest', }; diff --git a/src/utils/CanvasLayoutStyles.test.ts b/src/utils/CanvasLayoutStyles.test.ts index ca82f00..0641c8f 100644 --- a/src/utils/CanvasLayoutStyles.test.ts +++ b/src/utils/CanvasLayoutStyles.test.ts @@ -48,4 +48,28 @@ describe('applyCanvasLayoutStyles', () => { expect(container.style.getPropertyValue('--canvas-display-h')).toBe('300'); expect(canvas.style.width).toBe(''); }); + + it('clears inline width/height when canvasDisplaySize is removed on second call', () => { + const container = document.createElement('div'); + const canvas = document.createElement('canvas'); + + container.id = 'canvas-container'; + container.appendChild(canvas); + + applyCanvasLayoutStyles(canvas, { + displaySize: new Vector2i(320, 240), + canvasDisplaySize: new Vector2i(640, 480), + maxCanvasDisplaySize: new Vector2i(960, 720), + }); + + applyCanvasLayoutStyles(canvas, { + displaySize: new Vector2i(320, 240), + maxCanvasDisplaySize: new Vector2i(960, 720), + }); + + expect(container.style.getPropertyValue('--canvas-display-w')).toBe('320'); + expect(container.style.getPropertyValue('--canvas-display-h')).toBe('240'); + expect(canvas.style.width).toBe(''); + expect(canvas.style.height).toBe(''); + }); }); diff --git a/src/utils/CanvasLayoutStyles.ts b/src/utils/CanvasLayoutStyles.ts index 8abd7af..ed23339 100644 --- a/src/utils/CanvasLayoutStyles.ts +++ b/src/utils/CanvasLayoutStyles.ts @@ -66,6 +66,9 @@ export function applyCanvasLayoutStyles(canvas: HTMLCanvasElement, options: Canv if (options.canvasDisplaySize) { canvas.style.width = `${options.canvasDisplaySize.x}px`; canvas.style.height = `${options.canvasDisplaySize.y}px`; + } else { + canvas.style.width = ''; + canvas.style.height = ''; } }