diff --git a/agent/includes/inc_client_top_head.php b/agent/includes/inc_client_top_head.php index e45662a9c..bdf5b8340 100644 --- a/agent/includes/inc_client_top_head.php +++ b/agent/includes/inc_client_top_head.php @@ -1,9 +1,19 @@ +
-

+

= 2) { ?> @@ -64,7 +74,7 @@
-
" id="clientHeader"> +
diff --git a/css/itflow_custom.css b/css/itflow_custom.css index ebcc66a62..b32383943 100644 --- a/css/itflow_custom.css +++ b/css/itflow_custom.css @@ -1532,3 +1532,26 @@ html { max-width: 100%; } + +/* Client header disclosure - agent/includes/inc_client_top_head.php + + The client name is the collapse trigger for #clientHeader, but links carry no + underline in this app and the h4 is text-dark, so nothing said the heading was + clickable or that a panel was folded behind it. Bootstrap maintains + aria-expanded on the trigger itself, so the chevron reads the real state + rather than a second copy of it kept somewhere in JS. */ +.client-header-chevron { + font-size: .7em; + margin-right: .25rem; + vertical-align: middle; + color: var(--bs-secondary-color); + transition: transform .15s ease-in-out; +} + +.client-header-toggle:hover .client-header-chevron { + color: var(--bs-body-color); +} + +.client-header-toggle[aria-expanded="true"] .client-header-chevron { + transform: rotate(90deg); +} diff --git a/js/app.js b/js/app.js index 116e11af2..7cdc0ea16 100644 --- a/js/app.js +++ b/js/app.js @@ -588,23 +588,49 @@ function itflowInit() { } }); - // Password reveal. Replaces Show-Hide-Passwords-Bootstrap-4, which has no - // Bootstrap 5 release. Same data-toggle="password" contract as before. - document.querySelectorAll('[data-toggle="password"]').forEach(function (btn) { - btn.addEventListener('click', function () { - var group = btn.closest('.input-group'); - var input = group && group.querySelector('input'); - if (!input) { + // Password reveal. Replaces Show-Hide-Passwords-Bootstrap-4, which has no Bootstrap 5 + // release. The data-toggle="password" contract stays on the INPUT, which is where all + // 15 call sites write it and where the old plugin expected it - but the plugin built + // its own toggle button, and the click has to land on the eye SPAN beside the field. + // Binding it to the input instead meant the eye did nothing at all, and clicking into + // the field to type your password switched it to type="text" while you typed it. + itflowStep('password-reveal', function () { + document.querySelectorAll('input[data-toggle="password"]').forEach(function (input) { + var group = input.closest('.input-group'); + var icon = group && group.querySelector('.fa-eye, .fa-eye-slash'); + var toggle = icon && icon.closest('span, button, a'); + + // includes/modal_footer.php re-executes this file on every ajax modal open, so + // without a marker anything already on the page collects a second handler and + // the next click toggles twice, which looks exactly like nothing happening. + if (!toggle || toggle.dataset.itflowPasswordToggle) { return; } - var hidden = input.type === 'password'; - input.type = hidden ? 'text' : 'password'; - var icon = btn.querySelector('i'); - if (icon) { + toggle.dataset.itflowPasswordToggle = '1'; + + // The eye is a plain , so it needs the role to get a pointer cursor and + // the tabindex to be reachable at all without a mouse. + toggle.setAttribute('role', 'button'); + toggle.setAttribute('tabindex', '0'); + toggle.setAttribute('aria-pressed', 'false'); + toggle.setAttribute('aria-label', 'Show password'); + + function toggleReveal() { + var hidden = input.type === 'password'; + input.type = hidden ? 'text' : 'password'; icon.classList.toggle('fa-eye', !hidden); icon.classList.toggle('fa-eye-slash', hidden); + toggle.setAttribute('aria-pressed', hidden ? 'true' : 'false'); + toggle.setAttribute('aria-label', hidden ? 'Hide password' : 'Show password'); } - btn.setAttribute('aria-pressed', hidden ? 'true' : 'false'); + + toggle.addEventListener('click', toggleReveal); + toggle.addEventListener('keydown', function (e) { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + toggleReveal(); + } + }); }); });