Story 3.2 done
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user