/** * StripOverlayLayer — Single overlay container for all positioned overlays. * * Owns: DOM element with position: absolute; inset: 0; pointer-events: none; overflow: visible * Children restore pointer-events: auto * Used by: ActionPopover (Story 1.5), ConfirmationBar (Story 3.2) * * Import rule: may import from src/core/, src/contracts/, src/utils/ ONLY. * Constructors are side-effect free — call init() from module.js Hooks.once('ready'). * * Story 1.5: Original creation for ActionPopover support * Story 3.2: Extended to support ConfirmationBar * * @module ui/shared/StripOverlayLayer */ /** * Single overlay container for all positioned overlays. * Provides a common parent element with pointer-events: none that allows * children to restore pointer-events: auto for specific interactive areas. */ export class StripOverlayLayer { /** * @param {import('../../foundry/FoundryAdapter.js').FoundryAdapter} adapter * Injected FoundryAdapter surface. */ constructor(adapter) { this._adapter = adapter; /** @type {HTMLElement|null} The overlay container element */ this._element = null; /** @type {Map} Track rendered overlays by key */ this._overlays = new Map(); } /** * Initializes the StripOverlayLayer by creating the DOM element. * Side-effect: Creates and appends the overlay container to the ScryingPoolStrip. */ init() { // Create overlay container element this._element = document.createElement('div'); this._element.className = 'sp-strip__overlay-layer'; this._element.setAttribute('aria-hidden', 'true'); // Critical styles per UX-DR6 this._element.style.cssText = ` position: absolute; inset: 0; pointer-events: none; overflow: visible; `; // Try to find the ScryingPoolStrip element to append to // The strip is created in Story 1.5 as a floating ApplicationV2 window const stripElement = document.querySelector?.('.scrying-pool__roster-strip'); if (stripElement) { stripElement.appendChild(this._element); } else { // Fallback: if strip not found, append to body (shouldn't happen in normal flow) console.warn('[ScryingPool] StripOverlayLayer: ScryingPoolStrip not found, appending to body'); document.body.appendChild(this._element); } } /** * Returns the overlay container element. * @returns {HTMLElement|null} The overlay element. */ get element() { return this._element; } /** * Renders content into the overlay layer. * The content will have pointer-events: auto to allow interaction. * * @param {string|HTMLElement} content - HTML string or DOM element to render. * @param {string} [key] - Optional key to track this overlay for replacement. * @returns {HTMLElement|null} The rendered element, or null if failed. */ render(content, key = null) { if (!this._element) { console.warn('[ScryingPool] StripOverlayLayer: Cannot render, element not initialized'); return null; } // Remove previous overlay if key is provided if (key && this._overlays.has(key)) { const previous = this._overlays.get(key); if (previous && previous.parentNode) { previous.parentNode.removeChild(previous); } this._overlays.delete(key); } // Create container for the content const container = document.createElement('div'); container.style.pointerEvents = 'auto'; // Set content if (typeof content === 'string') { container.innerHTML = content; } else if (content instanceof HTMLElement) { container.appendChild(content); } else { console.warn('[ScryingPool] StripOverlayLayer: Invalid content type'); return null; } // Append to overlay layer this._element.appendChild(container); // Track by key if provided if (key) { this._overlays.set(key, container); } return container; } /** * Removes an overlay by key. * * @param {string} key - The key of the overlay to remove. */ remove(key) { if (!this._overlays.has(key)) { return; } const overlay = this._overlays.get(key); if (overlay && overlay.parentNode) { overlay.parentNode.removeChild(overlay); } this._overlays.delete(key); } /** * Removes all overlays from the layer. */ clearAll() { for (const [, overlay] of this._overlays) { if (overlay && overlay.parentNode) { overlay.parentNode.removeChild(overlay); } } this._overlays.clear(); } /** * Cleans up the StripOverlayLayer by removing the DOM element. * Safe to call multiple times. */ teardown() { this.clearAll(); if (this._element && this._element.parentNode) { this._element.parentNode.removeChild(this._element); } this._element = null; } }