diff --git a/src/registrar/assets/js/uswds-edited.js b/src/registrar/assets/js/uswds-edited.js index ae246b05c..d8664d5bf 100644 --- a/src/registrar/assets/js/uswds-edited.js +++ b/src/registrar/assets/js/uswds-edited.js @@ -5284,7 +5284,10 @@ const setUpModal = baseComponent => { overlayDiv.classList.add(OVERLAY_CLASSNAME); // Set attributes - modalWrapper.setAttribute("role", "dialog"); + // DOTGOV + // Removes the dialog role as this causes a double readout bug with screenreaders + // modalWrapper.setAttribute("role", "dialog"); + // END DOTGOV modalWrapper.setAttribute("id", modalID); if (ariaLabelledBy) { modalWrapper.setAttribute("aria-labelledby", ariaLabelledBy); diff --git a/src/registrar/assets/src/js/getgov-admin/domain-request-form.js b/src/registrar/assets/src/js/getgov-admin/domain-request-form.js index b3d14839e..db6467875 100644 --- a/src/registrar/assets/src/js/getgov-admin/domain-request-form.js +++ b/src/registrar/assets/src/js/getgov-admin/domain-request-form.js @@ -1,4 +1,4 @@ -import { hideElement, showElement, addOrRemoveSessionBoolean } from './helpers-admin.js'; +import { hideElement, showElement, addOrRemoveSessionBoolean, announceForScreenReaders } from './helpers-admin.js'; import { handlePortfolioSelection } from './helpers-portfolio-dynamic-fields.js'; function displayModalOnDropdownClick(linkClickedDisplaysModal, statusDropdown, actionButton, valueToCheck){ @@ -684,3 +684,33 @@ export function initDynamicDomainRequestFields(){ handleSuborgFieldsAndButtons(); } } + +export function initFilterFocusListeners() { + document.addEventListener("DOMContentLoaded", function() { + let filters = document.querySelectorAll("#changelist-filter li a"); // Get list of all filter links + let clickedFilter = false; // Used to determine if we are truly navigating away or not + + // Restore focus from localStorage + let lastClickedFilterId = localStorage.getItem("admin_filter_focus_id"); + if (lastClickedFilterId) { + let focusedElement = document.getElementById(lastClickedFilterId); + if (focusedElement) { + //Focus the element + focusedElement.setAttribute("tabindex", "0"); + focusedElement.focus({ preventScroll: true }); + + // Announce focus change for screen readers + announceForScreenReaders("Filter refocused on " + focusedElement.textContent); + localStorage.removeItem("admin_filter_focus_id"); + } + } + + // Capture clicked filter and store its ID + filters.forEach(filter => { + filter.addEventListener("click", function() { + localStorage.setItem("admin_filter_focus_id", this.id); + clickedFilter = true; // Mark that a filter was clicked + }); + }); + }); +} \ No newline at end of file diff --git a/src/registrar/assets/src/js/getgov-admin/helpers-admin.js b/src/registrar/assets/src/js/getgov-admin/helpers-admin.js index 8055e29d3..5ec78f6b0 100644 --- a/src/registrar/assets/src/js/getgov-admin/helpers-admin.js +++ b/src/registrar/assets/src/js/getgov-admin/helpers-admin.js @@ -32,3 +32,22 @@ export function getParameterByName(name, url) { if (!results[2]) return ''; return decodeURIComponent(results[2].replace(/\+/g, ' ')); } + +/** + * Creates a temporary live region to announce messages for screen readers. + */ +export function announceForScreenReaders(message) { + let liveRegion = document.createElement("div"); + liveRegion.setAttribute("aria-live", "assertive"); + liveRegion.setAttribute("role", "alert"); + liveRegion.setAttribute("class", "usa-sr-only"); + document.body.appendChild(liveRegion); + + // Delay the update slightly to ensure it's recognized + setTimeout(() => { + liveRegion.textContent = message; + setTimeout(() => { + document.body.removeChild(liveRegion); + }, 1000); + }, 100); +} \ No newline at end of file diff --git a/src/registrar/assets/src/js/getgov-admin/main.js b/src/registrar/assets/src/js/getgov-admin/main.js index 7eb1fc8cd..112740dd9 100644 --- a/src/registrar/assets/src/js/getgov-admin/main.js +++ b/src/registrar/assets/src/js/getgov-admin/main.js @@ -10,7 +10,8 @@ import { initRejectedEmail, initApprovedDomain, initCopyRequestSummary, - initDynamicDomainRequestFields } from './domain-request-form.js'; + initDynamicDomainRequestFields, + initFilterFocusListeners } from './domain-request-form.js'; import { initDomainFormTargetBlankButtons } from './domain-form.js'; import { initDynamicPortfolioFields } from './portfolio-form.js'; import { initDynamicDomainInformationFields } from './domain-information-form.js'; @@ -34,6 +35,7 @@ initRejectedEmail(); initApprovedDomain(); initCopyRequestSummary(); initDynamicDomainRequestFields(); +initFilterFocusListeners(); // Domain initDomainFormTargetBlankButtons(); diff --git a/src/registrar/assets/src/js/getgov/main.js b/src/registrar/assets/src/js/getgov/main.js index 637488187..0529d3614 100644 --- a/src/registrar/assets/src/js/getgov/main.js +++ b/src/registrar/assets/src/js/getgov/main.js @@ -16,6 +16,7 @@ import { initDomainManagersPage } from './domain-managers.js'; import { initDomainDSData } from './domain-dsdata.js'; import { initDomainDNSSEC } from './domain-dnssec.js'; import { initFormErrorHandling } from './form-errors.js'; +import { initButtonLinks } from '../getgov-admin/button-utils.js'; initDomainValidators(); @@ -50,3 +51,5 @@ initFormErrorHandling(); initPortfolioMemberPageRadio(); initPortfolioNewMemberPageToggle(); initAddNewMemberPageListeners(); + +initButtonLinks(); diff --git a/src/registrar/templates/admin/filter.html b/src/registrar/templates/admin/filter.html new file mode 100644 index 000000000..abe3ad282 --- /dev/null +++ b/src/registrar/templates/admin/filter.html @@ -0,0 +1,13 @@ +{% comment %} Override of this file: https://github.com/django/django/blob/main/django/contrib/admin/templates/admin/filter.html {% endcomment %} +{% load i18n %} +
+ + {% blocktranslate with filter_title=title %} By {{ filter_title }} {% endblocktranslate %} + + +
\ No newline at end of file diff --git a/src/registrar/templates/django/admin/multiple_choice_list_filter.html b/src/registrar/templates/django/admin/multiple_choice_list_filter.html index c64fa1be1..27b8d9969 100644 --- a/src/registrar/templates/django/admin/multiple_choice_list_filter.html +++ b/src/registrar/templates/django/admin/multiple_choice_list_filter.html @@ -9,16 +9,12 @@ {% for choice in choices %} {% if choice.reset %} - {{ choice.display }} + {{ choice.display }} - {% endif %} - {% endfor %} - - {% for choice in choices %} - {% if not choice.reset %} - + {% else %} + {% if choice.selected and choice.exclude_query_string %} - {{ choice.display }} + {{ choice.display }} @@ -26,9 +22,8 @@ - {% endif %} - {% if not choice.selected and choice.include_query_string %} - {{ choice.display }} + {% elif not choice.selected and choice.include_query_string %} + {{ choice.display }} @@ -38,4 +33,4 @@ {% endif %} {% endfor %} - + \ No newline at end of file diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index de4d9e712..3c96016eb 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -18,10 +18,10 @@

Manage your domains

- Start a new domain request - +

{% include "includes/domains_table.html" with user_domain_count=user_domain_count %} diff --git a/src/registrar/templates/portfolio_requests.html b/src/registrar/templates/portfolio_requests.html index 58fbde10c..4f366d0c6 100644 --- a/src/registrar/templates/portfolio_requests.html +++ b/src/registrar/templates/portfolio_requests.html @@ -26,10 +26,10 @@ {% else %} diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index ccd0e6cc2..0cb99c5c6 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -215,14 +215,14 @@ class TestDomainInvitationAdmin(WebTest): ) # Assert that the filters are added - self.assertContains(response, "invited", count=5) + self.assertContains(response, "invited", count=6) self.assertContains(response, "Invited", count=2) - self.assertContains(response, "retrieved", count=2) + self.assertContains(response, "retrieved", count=3) self.assertContains(response, "Retrieved", count=2) # Check for the HTML context specificially - invited_html = 'Invited' - retrieved_html = 'Retrieved' + invited_html = 'Invited' + retrieved_html = 'Retrieved' self.assertContains(response, invited_html, count=1) self.assertContains(response, retrieved_html, count=1) @@ -1269,14 +1269,14 @@ class TestPortfolioInvitationAdmin(TestCase): ) # Assert that the filters are added - self.assertContains(response, "invited", count=4) + self.assertContains(response, "invited", count=5) self.assertContains(response, "Invited", count=2) - self.assertContains(response, "retrieved", count=2) + self.assertContains(response, "retrieved", count=3) self.assertContains(response, "Retrieved", count=2) # Check for the HTML context specificially - invited_html = 'Invited' - retrieved_html = 'Retrieved' + invited_html = 'Invited' + retrieved_html = 'Retrieved' self.assertContains(response, invited_html, count=1) self.assertContains(response, retrieved_html, count=1)