}
};
}
+
+// Focus Trap for Modals - Week 5
+function createFocusTrap(modalElement) {
+ const focusableElements = modalElement.querySelectorAll(
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
+ );
+
+ if (focusableElements.length === 0) return;
+
+ const firstElement = focusableElements[0];
+ const lastElement = focusableElements[focusableElements.length - 1];
+
+ modalElement.addEventListener('keydown', (e) => {
+ if (e.key !== 'Tab') return;
+
+ if (e.shiftKey) {
+ // Shift + Tab
+ if (document.activeElement === firstElement) {
+ e.preventDefault();
+ lastElement.focus();
+ }
+ } else {
+ // Tab
+ if (document.activeElement === lastElement) {
+ e.preventDefault();
+ firstElement.focus();
+ }
+ }
+ });
+
+ // Set initial focus
+ firstElement.focus();
+}
+
+// Export for use in Alpine.js
+window.createFocusTrap = createFocusTrap;
============================================ -->
<div class="modal" :class="{ active: showAlertModal }" x-show="showAlertModal">
- <div class="modal-backdrop" @click="showAlertModal = false"></div>
- <div class="modal-content modal-sm">
+ <div class="modal-backdrop" @click="showAlertModal = false" aria-hidden="true"></div>
+ <div class="modal-content modal-sm" role="dialog" aria-labelledby="alert-modal-title" aria-modal="true" @x-show.transition.in="createFocusTrap($el)">
<div class="modal-header">
- <h3 class="modal-title">{{ i18n "form_alert_title" | default "Alert" }}</h3>
- <div class="modal-close" @click="showAlertModal = false"></div>
+ <h3 class="modal-title" id="alert-modal-title">{{ i18n "form_alert_title" | default "Alert" }}</h3>
+ <div class="modal-close" @click="showAlertModal = false" aria-label="Close modal"></div>
</div>
<div class="modal-body">
<p>{{ i18n "form_alert_message" | default "This is an alert modal. Click OK to dismiss." }}</p>
============================================ -->
<div class="modal" :class="{ active: showConfirmModal }" x-show="showConfirmModal">
- <div class="modal-backdrop" @click="showConfirmModal = false"></div>
- <div class="modal-content modal-sm">
+ <div class="modal-backdrop" @click="showConfirmModal = false" aria-hidden="true"></div>
+ <div class="modal-content modal-sm" role="dialog" aria-labelledby="confirm-modal-title" aria-modal="true" @x-show.transition.in="createFocusTrap($el)">
<div class="modal-header">
- <h3 class="modal-title">{{ i18n "form_confirm_title" | default "Confirm Action" }}</h3>
- <div class="modal-close" @click="showConfirmModal = false"></div>
+ <h3 class="modal-title" id="confirm-modal-title">{{ i18n "form_confirm_title" | default "Confirm Action" }}</h3>
+ <div class="modal-close" @click="showConfirmModal = false" aria-label="Close modal"></div>
</div>
<div class="modal-body">
<p>{{ i18n "form_confirm_message" | default "Are you sure you want to continue?" }}</p>
============================================ -->
<div class="modal" :class="{ active: showContentModal }" x-show="showContentModal">
- <div class="modal-backdrop" @click="showContentModal = false"></div>
- <div class="modal-content modal-md">
+ <div class="modal-backdrop" @click="showContentModal = false" aria-hidden="true"></div>
+ <div class="modal-content modal-md" role="dialog" aria-labelledby="content-modal-title" aria-modal="true" @x-show.transition.in="createFocusTrap($el)">
<div class="modal-header">
- <h3 class="modal-title">{{ i18n "form_content_title" | default "Modal with Content" }}</h3>
- <div class="modal-close" @click="showContentModal = false"></div>
+ <h3 class="modal-title" id="content-modal-title">{{ i18n "form_content_title" | default "Modal with Content" }}</h3>
+ <div class="modal-close" @click="showContentModal = false" aria-label="Close modal"></div>
</div>
<div class="modal-body">
<p>{{ i18n "form_content_message" | default "This modal contains detailed content. You can add forms, lists, or any HTML here." }}</p>