Files
peptide-starter-theme/assets/js/abbr-tooltip.js
peptiderepo 2b23bb7e2a feat: RCT inline abbreviation tooltip on verdict legend strip (v2.2.1) (#53)
CI green (PHP Lint 8.1/8.2/8.3 ✓, PHPCS ✓, PHPUnit 8.1/8.2/8.3 ✓). QA: PASS.
2026-04-29 13:29:04 +07:00

135 lines
3.7 KiB
JavaScript

/**
* abbr-tooltip.js — Inline abbreviation popover for verdict legend screen.
*
* What: Opens a small popover anchored to any .pr-abbr element when tapped,
* clicked, or activated via keyboard. Closes on outside click or Escape.
* Who triggers: Enqueued by functions.php on is_front_page() requests only.
* Depends on: .pr-abbr elements in the DOM with data-def attributes; CSS in
* style.css (.pr-abbr-popover).
*
* @see inc/verdict-helpers.php — peptide_starter_render_desc() emits .pr-abbr elements
* @see style.css — .pr-abbr, .pr-abbr-popover visual styles
* @package peptide-starter
*/
( function () {
'use strict';
var POPOVER_CLASS = 'pr-abbr-popover';
var ACTIVE_CLASS = 'pr-abbr--active';
var activePopover = null;
var activeAbbr = null;
/**
* Remove the currently open popover, if any.
*
* @return {void}
*/
function closePopover() {
if ( activePopover ) {
activePopover.remove();
activePopover = null;
}
if ( activeAbbr ) {
activeAbbr.classList.remove( ACTIVE_CLASS );
activeAbbr.removeAttribute( 'aria-expanded' );
activeAbbr = null;
}
}
/**
* Create and position a popover anchored to the given abbr element.
*
* @param {HTMLElement} abbr - The .pr-abbr element that was activated.
* @return {void}
*/
function openPopover( abbr ) {
closePopover();
var def = abbr.getAttribute( 'data-def' );
if ( ! def ) {
return;
}
var popover = document.createElement( 'div' );
popover.className = POPOVER_CLASS;
popover.textContent = def;
popover.setAttribute( 'role', 'tooltip' );
popover.setAttribute( 'id', POPOVER_CLASS + '-' + Date.now() );
document.body.appendChild( popover );
// Position below the abbr, clamped to viewport width.
var rect = abbr.getBoundingClientRect();
var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
var top = rect.bottom + scrollTop + 6;
var left = rect.left + scrollLeft;
var maxLeft = window.innerWidth - popover.offsetWidth - 16;
popover.style.top = top + 'px';
popover.style.left = Math.max( 8, Math.min( left, maxLeft ) ) + 'px';
abbr.classList.add( ACTIVE_CLASS );
abbr.setAttribute( 'aria-expanded', 'true' );
abbr.setAttribute( 'aria-describedby', popover.id );
activePopover = popover;
activeAbbr = abbr;
}
/**
* Toggle the popover for an abbr element (open if closed, close if open).
*
* @param {HTMLElement} abbr - The .pr-abbr element that was activated.
* @return {void}
*/
function togglePopover( abbr ) {
if ( activeAbbr === abbr ) {
closePopover();
} else {
openPopover( abbr );
}
}
// -------------------------------------------------------------------------
// Event listeners
// -------------------------------------------------------------------------
document.addEventListener( 'click', function ( e ) {
var abbr = e.target.closest( '.pr-abbr' );
if ( abbr ) {
e.stopPropagation();
togglePopover( abbr );
return;
}
// Click outside any abbr — close.
closePopover();
} );
document.addEventListener( 'keydown', function ( e ) {
if ( e.key === 'Escape' ) {
closePopover();
return;
}
if ( e.key === 'Enter' || e.key === ' ' ) {
var abbr = e.target.closest( '.pr-abbr' );
if ( abbr ) {
e.preventDefault();
togglePopover( abbr );
}
}
} );
// Close when focus leaves the popover+abbr pair entirely.
document.addEventListener( 'focusin', function ( e ) {
if (
activeAbbr &&
! activeAbbr.contains( e.target ) &&
( ! activePopover || ! activePopover.contains( e.target ) )
) {
closePopover();
}
} );
} )();