- Standardize script loading on faq, privacy, returns, shipping-info pages - Archive 14 unused JS files (cart-functions, shopping, cart.js, enhanced versions, etc.) - Archive 2 unused CSS files (responsive-enhanced, responsive-fixes) - All pages now use consistent script loading order - Eliminated shopping.js dependency (not needed after standardization)
288 lines
9.0 KiB
JavaScript
288 lines
9.0 KiB
JavaScript
/**
|
|
* Accessibility Enhancements
|
|
* WCAG 2.1 AA Compliant
|
|
*/
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
const A11y = {
|
|
init() {
|
|
this.addSkipLink();
|
|
this.enhanceFocusManagement();
|
|
this.addARIALabels();
|
|
this.improveKeyboardNav();
|
|
this.addLiveRegions();
|
|
this.enhanceFormAccessibility();
|
|
console.log("[A11y] Accessibility enhancements loaded");
|
|
},
|
|
|
|
// Add skip to main content link
|
|
addSkipLink() {
|
|
if (document.querySelector(".skip-link")) return;
|
|
|
|
const skipLink = document.createElement("a");
|
|
skipLink.href = "#main-content";
|
|
skipLink.className = "skip-link";
|
|
skipLink.textContent = "Skip to main content";
|
|
skipLink.addEventListener("click", (e) => {
|
|
e.preventDefault();
|
|
const main = document.querySelector("#main-content, main");
|
|
if (main) {
|
|
main.setAttribute("tabindex", "-1");
|
|
main.focus();
|
|
}
|
|
});
|
|
|
|
document.body.insertBefore(skipLink, document.body.firstChild);
|
|
},
|
|
|
|
// Enhance focus management
|
|
enhanceFocusManagement() {
|
|
// Trap focus in modals
|
|
document.addEventListener("keydown", (e) => {
|
|
if (e.key !== "Tab") return;
|
|
|
|
const modal = document.querySelector(
|
|
'.modal.active, .dropdown[style*="display: flex"]'
|
|
);
|
|
if (!modal) return;
|
|
|
|
const focusable = modal.querySelectorAll(
|
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
);
|
|
|
|
if (focusable.length === 0) return;
|
|
|
|
const first = focusable[0];
|
|
const last = focusable[focusable.length - 1];
|
|
|
|
if (e.shiftKey && document.activeElement === first) {
|
|
e.preventDefault();
|
|
last.focus();
|
|
} else if (!e.shiftKey && document.activeElement === last) {
|
|
e.preventDefault();
|
|
first.focus();
|
|
}
|
|
});
|
|
|
|
// Focus visible styles
|
|
const style = document.createElement("style");
|
|
style.textContent = `
|
|
*:focus-visible {
|
|
outline: 3px solid #667eea !important;
|
|
outline-offset: 2px !important;
|
|
}
|
|
|
|
button:focus-visible,
|
|
a:focus-visible,
|
|
input:focus-visible,
|
|
select:focus-visible,
|
|
textarea:focus-visible {
|
|
outline: 3px solid #667eea !important;
|
|
outline-offset: 2px !important;
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
},
|
|
|
|
// Add ARIA labels to interactive elements
|
|
addARIALabels() {
|
|
// Cart button
|
|
const cartBtn = document.querySelector("#cart-btn");
|
|
if (cartBtn && !cartBtn.hasAttribute("aria-label")) {
|
|
cartBtn.setAttribute("aria-label", "Shopping cart");
|
|
cartBtn.setAttribute("aria-haspopup", "true");
|
|
}
|
|
|
|
// Wishlist button
|
|
const wishlistBtn = document.querySelector("#wishlist-btn");
|
|
if (wishlistBtn && !wishlistBtn.hasAttribute("aria-label")) {
|
|
wishlistBtn.setAttribute("aria-label", "Wishlist");
|
|
wishlistBtn.setAttribute("aria-haspopup", "true");
|
|
}
|
|
|
|
// Mobile menu toggle
|
|
const menuToggle = document.querySelector(".mobile-menu-toggle");
|
|
if (menuToggle && !menuToggle.hasAttribute("aria-label")) {
|
|
menuToggle.setAttribute("aria-label", "Open navigation menu");
|
|
menuToggle.setAttribute("aria-expanded", "false");
|
|
}
|
|
|
|
// Add ARIA labels to product cards
|
|
document.querySelectorAll(".product-card").forEach((card, index) => {
|
|
if (!card.hasAttribute("role")) {
|
|
card.setAttribute("role", "article");
|
|
}
|
|
|
|
const title = card.querySelector("h3, .product-title");
|
|
if (title && !title.id) {
|
|
title.id = `product-title-${index}`;
|
|
card.setAttribute("aria-labelledby", title.id);
|
|
}
|
|
});
|
|
|
|
// Add labels to icon-only buttons
|
|
document.querySelectorAll("button:not([aria-label])").forEach((btn) => {
|
|
const icon = btn.querySelector('i[class*="bi-"]');
|
|
if (icon && !btn.textContent.trim()) {
|
|
const iconClass = icon.className;
|
|
let label = "Button";
|
|
|
|
if (iconClass.includes("cart")) label = "Add to cart";
|
|
else if (iconClass.includes("heart")) label = "Add to wishlist";
|
|
else if (iconClass.includes("trash")) label = "Remove";
|
|
else if (iconClass.includes("plus")) label = "Increase";
|
|
else if (iconClass.includes("minus") || iconClass.includes("dash"))
|
|
label = "Decrease";
|
|
else if (iconClass.includes("close") || iconClass.includes("x"))
|
|
label = "Close";
|
|
|
|
btn.setAttribute("aria-label", label);
|
|
}
|
|
});
|
|
},
|
|
|
|
// Improve keyboard navigation
|
|
improveKeyboardNav() {
|
|
// Dropdown keyboard support
|
|
document.querySelectorAll("[data-dropdown-toggle]").forEach((toggle) => {
|
|
toggle.addEventListener("keydown", (e) => {
|
|
if (e.key === "Enter" || e.key === " ") {
|
|
e.preventDefault();
|
|
toggle.click();
|
|
}
|
|
});
|
|
});
|
|
|
|
// Product card keyboard navigation
|
|
document.querySelectorAll(".product-card").forEach((card) => {
|
|
const link = card.querySelector("a");
|
|
if (link) {
|
|
card.addEventListener("keydown", (e) => {
|
|
if (e.key === "Enter" && e.target === card) {
|
|
link.click();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Quantity input keyboard support
|
|
document.querySelectorAll(".quantity-input").forEach((input) => {
|
|
input.addEventListener("keydown", (e) => {
|
|
if (e.key === "ArrowUp") {
|
|
e.preventDefault();
|
|
const newValue = parseInt(input.value || 1) + 1;
|
|
if (newValue <= 99) {
|
|
input.value = newValue;
|
|
input.dispatchEvent(new Event("change"));
|
|
}
|
|
} else if (e.key === "ArrowDown") {
|
|
e.preventDefault();
|
|
const newValue = parseInt(input.value || 1) - 1;
|
|
if (newValue >= 1) {
|
|
input.value = newValue;
|
|
input.dispatchEvent(new Event("change"));
|
|
}
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
// Add live regions for dynamic content
|
|
addLiveRegions() {
|
|
// Create announcement region
|
|
if (!document.querySelector("#a11y-announcements")) {
|
|
const announcer = document.createElement("div");
|
|
announcer.id = "a11y-announcements";
|
|
announcer.setAttribute("role", "status");
|
|
announcer.setAttribute("aria-live", "polite");
|
|
announcer.setAttribute("aria-atomic", "true");
|
|
announcer.className = "sr-only";
|
|
document.body.appendChild(announcer);
|
|
}
|
|
|
|
// Announce cart/wishlist updates
|
|
window.addEventListener("cart-updated", (e) => {
|
|
this.announce(`Cart updated. ${e.detail.length} items in cart.`);
|
|
});
|
|
|
|
window.addEventListener("wishlist-updated", (e) => {
|
|
this.announce(
|
|
`Wishlist updated. ${e.detail.length} items in wishlist.`
|
|
);
|
|
});
|
|
},
|
|
|
|
announce(message) {
|
|
const announcer = document.querySelector("#a11y-announcements");
|
|
if (announcer) {
|
|
announcer.textContent = "";
|
|
setTimeout(() => {
|
|
announcer.textContent = message;
|
|
}, 100);
|
|
}
|
|
},
|
|
|
|
// Enhance form accessibility
|
|
enhanceFormAccessibility() {
|
|
// Add required indicators
|
|
document
|
|
.querySelectorAll(
|
|
"input[required], select[required], textarea[required]"
|
|
)
|
|
.forEach((field) => {
|
|
const label = document.querySelector(`label[for="${field.id}"]`);
|
|
if (label && !label.querySelector(".required-indicator")) {
|
|
const indicator = document.createElement("span");
|
|
indicator.className = "required-indicator";
|
|
indicator.textContent = " *";
|
|
indicator.setAttribute("aria-label", "required");
|
|
label.appendChild(indicator);
|
|
}
|
|
});
|
|
|
|
// Add error message associations
|
|
document.querySelectorAll(".error-message").forEach((error, index) => {
|
|
if (!error.id) {
|
|
error.id = `error-${index}`;
|
|
}
|
|
|
|
const field = error.previousElementSibling;
|
|
if (
|
|
field &&
|
|
(field.tagName === "INPUT" ||
|
|
field.tagName === "SELECT" ||
|
|
field.tagName === "TEXTAREA")
|
|
) {
|
|
field.setAttribute("aria-describedby", error.id);
|
|
field.setAttribute("aria-invalid", "true");
|
|
}
|
|
});
|
|
|
|
// Add autocomplete attributes
|
|
document.querySelectorAll('input[type="email"]').forEach((field) => {
|
|
if (!field.hasAttribute("autocomplete")) {
|
|
field.setAttribute("autocomplete", "email");
|
|
}
|
|
});
|
|
|
|
document.querySelectorAll('input[type="tel"]').forEach((field) => {
|
|
if (!field.hasAttribute("autocomplete")) {
|
|
field.setAttribute("autocomplete", "tel");
|
|
}
|
|
});
|
|
},
|
|
};
|
|
|
|
// Initialize on DOM ready
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", () => A11y.init());
|
|
} else {
|
|
A11y.init();
|
|
}
|
|
|
|
// Export for external use
|
|
window.A11y = A11y;
|
|
})();
|