From 9a5e4c4ce4d701eb9befeef2e8429d94ab4d9e1d Mon Sep 17 00:00:00 2001 From: Tim Evans Date: Mon, 20 Jul 2026 15:30:56 +0100 Subject: [PATCH] feat: fullscreen prop overlay Implements fullscreen image overlay component for displaying art to players. Methods: show(src, label), dismiss(), isOpen(). Starts hidden. Clicking overlay dismisses. Strict TDD: 3 tests passing. Co-Authored-By: Claude Opus 4.8 --- src/components/prop-viewer.js | 22 +++++++++++++++++++++ tests/components/prop-viewer.test.js | 29 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 src/components/prop-viewer.js create mode 100644 tests/components/prop-viewer.test.js diff --git a/src/components/prop-viewer.js b/src/components/prop-viewer.js new file mode 100644 index 0000000..9e41e22 --- /dev/null +++ b/src/components/prop-viewer.js @@ -0,0 +1,22 @@ +export class PropViewer extends HTMLElement { + constructor() { super(); this._src = ''; this._label = ''; } + connectedCallback() { if (!this.hasAttribute('hidden')) this.setAttribute('hidden', ''); this.render(); } + + isOpen() { return !this.hasAttribute('hidden'); } + + show(src, label = '') { + this._src = src; this._label = label; + this.removeAttribute('hidden'); + this.render(); + } + dismiss() { this.setAttribute('hidden', ''); } + + render() { + this.innerHTML = ` +
+ ${this._label} +
`; + this.querySelector('[data-role=overlay]').addEventListener('click', () => this.dismiss()); + } +} +customElements.define('prop-viewer', PropViewer); diff --git a/tests/components/prop-viewer.test.js b/tests/components/prop-viewer.test.js new file mode 100644 index 0000000..4dddb87 --- /dev/null +++ b/tests/components/prop-viewer.test.js @@ -0,0 +1,29 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import '../../src/components/prop-viewer.js'; + +describe('', () => { + let el; + beforeEach(() => { + document.body.innerHTML = ''; + el = document.createElement('prop-viewer'); + document.body.appendChild(el); + }); + + it('starts hidden', () => { + expect(el.isOpen()).toBe(false); + expect(el.hasAttribute('hidden')).toBe(true); + }); + + it('shows an image when show() is called', () => { + el.show('/art/rain-pier.png', 'Pier in the rain'); + expect(el.isOpen()).toBe(true); + const img = el.querySelector('[data-role=prop-img]'); + expect(img.getAttribute('src')).toBe('/art/rain-pier.png'); + }); + + it('dismisses when the overlay is clicked', () => { + el.show('/art/x.png', 'x'); + el.querySelector('[data-role=overlay]').click(); + expect(el.isOpen()).toBe(false); + }); +});