Story 3.2 done

This commit is contained in:
2026-05-23 18:23:48 +02:00
parent d175f92806
commit a1e8886fce
66 changed files with 18258 additions and 1650 deletions
+162
View File
@@ -0,0 +1,162 @@
/**
* 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<string, HTMLElement>} 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;
}
}