vvjf-1.0.3/js/vvjf.js
js/vvjf.js
/**
* @file
* VVJ Flipbox.
*
* Filename: vvjf.js
* Website: https://www.flashwebcenter.com
* Developer: Alaa Haddad https://www.alaahaddad.com.
*/
(function (Drupal, drupalSettings, once) {
Drupal.behaviors.VVJFlipbox = {
attach: function (context, settings) {
// Use 'once' to ensure the behavior is applied only once per element.
const flipboxes = once('vvjFlipbox', '.vvjf', context);
flipboxes.forEach(flipbox => {
const breakpoints = flipbox.getAttribute('data-breakpoints');
const trigger = flipbox.getAttribute('data-flip-trigger');
const parsedBreakpoint = parseInt(breakpoints, 10);
const isBreakpointValid = !isNaN(parsedBreakpoint);
function updateAccessibility(frontSide, backSide, isFlipped) {
if (frontSide && backSide) {
const frontAriaHidden = isFlipped ? 'true' : 'false';
const backAriaHidden = isFlipped ? 'false' : 'true';
// Set aria-hidden and tabindex based on the flipped 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 flipped state
frontSide.querySelectorAll('a, button, input, select, textarea, [tabindex]').forEach(el => {
el.setAttribute('tabindex', isFlipped ? '-1' : '0');
});
backSide.querySelectorAll('a, button, input, select, textarea, [tabindex]').forEach(el => {
if (trigger === 'click') {
el.setAttribute('tabindex', isFlipped ? '-1' : '0');
} else if (trigger === 'hover') {
el.setAttribute('tabindex', isFlipped ? '0' : '-1');
}
});
}
}
function disableLinksInsideFlipbox(flipbox, disable) {
const links = flipbox.querySelectorAll('.flipbox-front a, .flipbox-back 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 applyFlipboxBehavior() {
const currentWidth = window.innerWidth;
if (breakpoints === 'all' || (isBreakpointValid && currentWidth >= parsedBreakpoint)) {
activateFlipbox(flipbox);
disableLinksInsideFlipbox(flipbox, trigger === 'click'); // Disable links when active and triggered by click
} else {
deactivateFlipbox(flipbox);
disableLinksInsideFlipbox(flipbox, false); // Re-enable links when inactive
}
}
function activateFlipbox(flipbox) {
const flipboxItems = flipbox.querySelectorAll('.flipbox-item-inner');
if (!flipboxItems.length) return;
flipboxItems.forEach(inner => {
const frontSide = inner.querySelector('.flipbox-front');
const backSide = inner.querySelector('.flipbox-back');
if (!frontSide || !backSide) return;
function handleMouseEnter() {
inner.classList.add('flipped');
updateAccessibility(frontSide, backSide, true);
}
function handleMouseLeave() {
inner.classList.remove('flipped');
updateAccessibility(frontSide, backSide, false);
}
function handleClick() {
const isFlipped = inner.classList.toggle('flipped');
updateAccessibility(frontSide, backSide, isFlipped);
}
function handleKeyDown(event) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
const isFlipped = inner.classList.toggle('flipped');
updateAccessibility(frontSide, backSide, isFlipped);
}
}
// Remove existing event listeners to prevent duplication
inner.removeEventListener('mouseenter', inner.flipboxHandlers?.handleMouseEnter);
inner.removeEventListener('mouseleave', inner.flipboxHandlers?.handleMouseLeave);
inner.removeEventListener('click', inner.flipboxHandlers?.handleClick);
inner.removeEventListener('keydown', inner.flipboxHandlers?.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.flipboxHandlers = { handleMouseEnter, handleMouseLeave, handleClick, handleKeyDown };
});
}
function deactivateFlipbox(flipbox) {
const flipboxItems = flipbox.querySelectorAll('.flipbox-item-inner');
if (!flipboxItems.length) return;
flipboxItems.forEach(inner => {
inner.classList.remove('flipped');
if (inner.flipboxHandlers) {
const { handleMouseEnter, handleMouseLeave, handleClick, handleKeyDown } = inner.flipboxHandlers;
inner.removeEventListener('mouseenter', handleMouseEnter);
inner.removeEventListener('mouseleave', handleMouseLeave);
inner.removeEventListener('click', handleClick);
inner.removeEventListener('keydown', handleKeyDown);
}
});
}
function resetFlippedOnResize() {
const flipboxItems = flipbox.querySelectorAll('.flipbox-item-inner');
if (!flipboxItems.length) return;
flipboxItems.forEach(inner => {
if (inner.classList.contains('flipped')) {
inner.classList.remove('flipped');
const frontSide = inner.querySelector('.flipbox-front');
const backSide = inner.querySelector('.flipbox-back');
updateAccessibility(frontSide, backSide, false);
}
});
}
function updateAriaHiddenBasedOnWidth() {
const currentWidth = window.innerWidth;
const flipboxItems = flipbox.querySelectorAll('.flipbox-item-inner');
flipboxItems.forEach(inner => {
const frontSide = inner.querySelector('.flipbox-front');
const backSide = inner.querySelector('.flipbox-back');
if (currentWidth < parsedBreakpoint) {
// Small screen: both sides visible, disable flipping
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 = flipbox.querySelectorAll('a, button, input, select, textarea, [tabindex]');
allTabbables.forEach(el => el.setAttribute('tabindex', '0'));
// Remove flip classes and event listeners
if (inner.classList.contains('flipped')) {
inner.classList.remove('flipped');
}
inner.removeEventListener('mouseenter', inner.flipboxHandlers?.handleMouseEnter);
inner.removeEventListener('mouseleave', inner.flipboxHandlers?.handleMouseLeave);
inner.removeEventListener('click', inner.flipboxHandlers?.handleClick);
inner.removeEventListener('keydown', inner.flipboxHandlers?.handleKeyDown);
disableLinksInsideFlipbox(flipbox, false); // Re-enable links on small screens
} else {
// Large screen: standard behavior
const isFlipped = inner.classList.contains('flipped');
const frontAriaHidden = isFlipped ? 'true' : 'false';
const backAriaHidden = isFlipped ? '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 flipped state
frontSide.querySelectorAll('a, button, input, select, textarea, [tabindex]').forEach(el => {
el.setAttribute('tabindex', isFlipped ? '-1' : '0');
});
backSide.querySelectorAll('a, button, input, select, textarea, [tabindex]').forEach(el => {
if (trigger === 'click') {
el.setAttribute('tabindex', isFlipped ? '-1' : '0');
} else if (trigger === 'hover') {
el.setAttribute('tabindex', isFlipped ? '0' : '-1');
}
});
// Reattach event listeners
activateFlipbox(flipbox);
disableLinksInsideFlipbox(flipbox, 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
applyFlipboxBehavior();
updateAriaHiddenBasedOnWidth();
// Throttled resize event listener
const resizeHandler = throttle(() => {
resetFlippedOnResize();
applyFlipboxBehavior();
updateAriaHiddenBasedOnWidth();
}, 200);
window.addEventListener('resize', resizeHandler);
});
}
};
})(Drupal, drupalSettings, once);
