Phase 1: Remove obsolete files and standardize all pages
- 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)
This commit is contained in:
287
website/public/assets/js/archive/accessibility-enhanced.js
Normal file
287
website/public/assets/js/archive/accessibility-enhanced.js
Normal file
@@ -0,0 +1,287 @@
|
||||
/**
|
||||
* 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;
|
||||
})();
|
||||
Reference in New Issue
Block a user