vvjr-1.0.3/js/vvjr.js
js/vvjr.js
/**
* @file
* VVJ Reveal.
*
* Filename: vvjr.js
* Website: https://www.flashwebcenter.com
* Developer: Alaa Haddad https://www.alaahaddad.com.
*/
(function (Drupal, drupalSettings, once) {
Drupal.behaviors.VVJReveal = {
attach: function (context, settings) {
// Scoped selection for '.vvjr' elements within the context
const reveals = once('vvjReveal', '.vvjr', context);
reveals.forEach(reveal => {
const breakpoints = reveal.getAttribute('data-breakpoints');
const trigger = reveal.getAttribute('data-reveal-method');
const parsedBreakpoint = parseInt(breakpoints, 10);
const isBreakpointValid = !isNaN(parsedBreakpoint);
function updateAccessibility(frontSide, backSide, isRevealed) {
if (frontSide && backSide) {
const frontAriaHidden = isRevealed ? 'true' : 'false';
const backAriaHidden = isRevealed ? 'false' : 'true';
// Set aria-hidden and tabindex based on the revealed state
frontSide.setAttribute('aria-hidden', frontAriaHidden);
backSide.setAttribute('aria-hidden', backAriaHidden);
frontSide.setAttribute('tabindex', frontAriaHidden === 'true' ? '-1' : '0');
backSide.setAttribute('tabindex', backAriaHidden === 'true' ? '-1' : '0');
// Adjust tabbable elements based on trigger and revealed state
frontSide.querySelectorAll('a, button, input, select, textarea, [tabindex]').forEach(el => {
el.setAttribute('tabindex', isRevealed ? '-1' : '0');
});
backSide.querySelectorAll('a, button, input, select, textarea, [tabindex]').forEach(el => {
if (trigger === 'click') {
el.setAttribute('tabindex', isRevealed ? '-1' : '0');
} else if (trigger === 'hover') {
el.setAttribute('tabindex', isRevealed ? '0' : '-1');
}
});
}
}
function disableLinksInsideReveal(reveal, disable) {
const links = reveal.querySelectorAll('.front-box a, .back-box a');
links.forEach(link => {
if (disable) {
link.setAttribute('tabindex', '-1');
link.addEventListener('click', preventDefaultHandler);
} else {
link.removeAttribute('tabindex');
link.removeEventListener('click', preventDefaultHandler);
}
});
}
function preventDefaultHandler(event) {
event.preventDefault();
}
function applyRevealBehavior() {
const currentWidth = window.innerWidth;
if (breakpoints === 'all' || (isBreakpointValid && currentWidth >= parsedBreakpoint)) {
activateReveal(reveal);
disableLinksInsideReveal(reveal, trigger === 'click'); // Disable links when active and triggered by click
} else {
deactivateReveal(reveal);
disableLinksInsideReveal(reveal, false); // Re-enable links when inactive
}
}
function activateReveal(reveal) {
const revealItems = reveal.querySelectorAll('.reveal-item');
if (!revealItems.length) return;
revealItems.forEach(inner => {
const frontSide = inner.querySelector('.front-box');
const backSide = inner.querySelector('.back-box');
if (!frontSide || !backSide) return;
function handleMouseEnter() {
inner.classList.add('revealed');
updateAccessibility(frontSide, backSide, true);
}
function handleMouseLeave() {
inner.classList.remove('revealed');
updateAccessibility(frontSide, backSide, false);
}
function handleClick() {
const isRevealed = inner.classList.toggle('revealed');
updateAccessibility(frontSide, backSide, isRevealed);
if (isRevealed) {
applyRevealEffect(inner, backSide);
} else {
resetRevealEffect(inner, backSide);
}
}
function handleKeyDown(event) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
const isRevealed = inner.classList.toggle('revealed');
updateAccessibility(frontSide, backSide, isRevealed);
if (isRevealed) {
applyRevealEffect(inner, backSide);
} else {
resetRevealEffect(inner, backSide);
}
}
}
// Remove existing event listeners to prevent duplication
inner.removeEventListener('mouseenter', inner.revealHandlers?.handleMouseEnter);
inner.removeEventListener('mouseleave', inner.revealHandlers?.handleMouseLeave);
inner.removeEventListener('click', inner.revealHandlers?.handleClick);
inner.removeEventListener('keydown', inner.revealHandlers?.handleKeyDown);
// Attach event listeners based on the trigger type
if (trigger === 'hover') {
inner.addEventListener('mouseenter', handleMouseEnter);
inner.addEventListener('mouseleave', handleMouseLeave);
}
if (trigger === 'click') {
inner.addEventListener('click', handleClick);
}
inner.addEventListener('keydown', handleKeyDown);
inner.revealHandlers = { handleMouseEnter, handleMouseLeave, handleClick, handleKeyDown };
});
}
function applyRevealEffect(inner, backSide) {
backSide.style.opacity = '1';
if (inner.classList.contains('a-fade')) {
backSide.style.opacity = '1';
} else if (inner.classList.contains('a-zoom')) {
backSide.style.transform = 'scale(1)';
} else if (inner.classList.contains('a-top')) {
backSide.style.transform = 'translateY(0)';
} else if (inner.classList.contains('a-right')) {
backSide.style.transform = 'translateX(0)';
} else if (inner.classList.contains('a-bottom')) {
backSide.style.transform = 'translateY(0)';
} else if (inner.classList.contains('a-left')) {
backSide.style.transform = 'translateX(0)';
}
}
function resetRevealEffect(inner, backSide) {
backSide.style.opacity = '0';
if (inner.classList.contains('a-fade')) {
backSide.style.opacity = '0';
} else if (inner.classList.contains('a-zoom')) {
backSide.style.transform = 'scale(0)';
} else if (inner.classList.contains('a-top')) {
backSide.style.transform = 'translateY(-100%)';
} else if (inner.classList.contains('a-right')) {
backSide.style.transform = 'translateX(100%)';
} else if (inner.classList.contains('a-bottom')) {
backSide.style.transform = 'translateY(100%)';
} else if (inner.classList.contains('a-left')) {
backSide.style.transform = 'translateX(-100%)';
}
}
function deactivateReveal(reveal) {
const revealItems = reveal.querySelectorAll('.reveal-item');
if (!revealItems.length) return;
revealItems.forEach(inner => {
inner.classList.remove('revealed');
if (inner.revealHandlers) {
const { handleMouseEnter, handleMouseLeave, handleClick, handleKeyDown } = inner.revealHandlers;
inner.removeEventListener('mouseenter', handleMouseEnter);
inner.removeEventListener('mouseleave', handleMouseLeave);
inner.removeEventListener('click', handleClick);
inner.removeEventListener('keydown', handleKeyDown);
}
});
}
function resetRevealedOnResize() {
const revealItems = reveal.querySelectorAll('.reveal-item');
if (!revealItems.length) return;
revealItems.forEach(inner => {
if (inner.classList.contains('revealed')) {
inner.classList.remove('revealed');
const frontSide = inner.querySelector('.front-box');
const backSide = inner.querySelector('.back-box');
updateAccessibility(frontSide, backSide, false);
resetRevealEffect(inner, backSide);
}
});
}
function updateAriaHiddenBasedOnWidth() {
const currentWidth = window.innerWidth;
const revealItems = reveal.querySelectorAll('.reveal-item');
revealItems.forEach(inner => {
const frontSide = inner.querySelector('.front-box');
const backSide = inner.querySelector('.back-box');
if (currentWidth < parsedBreakpoint && breakpoints !== 'all') {
// Small screen: both sides visible, disable revealing
frontSide.setAttribute('aria-hidden', 'false');
backSide.setAttribute('aria-hidden', 'false');
// Remove tabindex from both sides
frontSide.removeAttribute('tabindex');
backSide.removeAttribute('tabindex');
// Ensure all elements are tabbable
const allTabbables = reveal.querySelectorAll('a, button, input, select, textarea, [tabindex]');
allTabbables.forEach(el => el.setAttribute('tabindex', '0'));
// Remove reveal classes and event listeners
if (inner.classList.contains('revealed')) {
inner.classList.remove('revealed');
}
inner.removeEventListener('mouseenter', inner.revealHandlers?.handleMouseEnter);
inner.removeEventListener('mouseleave', inner.revealHandlers?.handleMouseLeave);
inner.removeEventListener('click', inner.revealHandlers?.handleClick);
inner.removeEventListener('keydown', inner.revealHandlers?.handleKeyDown);
disableLinksInsideReveal(reveal, false); // Re-enable links on small screens
} else {
// Large screen: standard behavior
const isRevealed = inner.classList.contains('revealed');
const frontAriaHidden = isRevealed ? 'true' : 'false';
const backAriaHidden = isRevealed ? 'false' : 'true';
frontSide.setAttribute('aria-hidden', frontAriaHidden);
backSide.setAttribute('aria-hidden', backAriaHidden);
frontSide.setAttribute('tabindex', frontAriaHidden === 'true' ? '-1' : '0');
backSide.setAttribute('tabindex', backAriaHidden === 'true' ? '-1' : '0');
// Adjust tabbable elements based on trigger and revealed state
frontSide.querySelectorAll('a, button, input, select, textarea, [tabindex]').forEach(el => {
el.setAttribute('tabindex', isRevealed ? '-1' : '0');
});
backSide.querySelectorAll('a, button, input, select, textarea, [tabindex]').forEach(el => {
if (trigger === 'click') {
el.setAttribute('tabindex', isRevealed ? '-1' : '0');
} else if (trigger === 'hover') {
el.setAttribute('tabindex', isRevealed ? '0' : '-1');
}
});
// Reattach event listeners
activateReveal(reveal);
disableLinksInsideReveal(reveal, trigger === 'click'); // Disable links on large screens when clicked
}
});
}
// Throttle the resize event handler
function throttle(func, limit) {
let lastFunc;
let lastRan;
return function() {
const context = this;
const args = arguments;
if (!lastRan) {
func.apply(context, args);
lastRan = Date.now();
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(function() {
if (Date.now() - lastRan >= limit) {
func.apply(context, args);
lastRan = Date.now();
}
}, limit - (Date.now() - lastRan));
}
};
}
// Initial check on load
applyRevealBehavior();
updateAriaHiddenBasedOnWidth();
// Throttled resize event listener
const resizeHandler = throttle(() => {
resetRevealedOnResize();
applyRevealBehavior();
updateAriaHiddenBasedOnWidth();
}, 200);
window.addEventListener('resize', resizeHandler);
});
}
};
})(Drupal, drupalSettings, once);
