diff --git a/shopping_app/static/css/style.css b/shopping_app/static/css/style.css index 3b88f37..a2eeeee 100644 --- a/shopping_app/static/css/style.css +++ b/shopping_app/static/css/style.css @@ -5162,3 +5162,100 @@ body:not(.sorting-active) .drag-handle { min-width: 44px !important; } } + + +/* final hotfix 2026-03-17b: password toggle parity on login/system-auth/admin-users */ +.ui-password-group { + display: flex !important; + flex-wrap: nowrap !important; + align-items: stretch !important; + width: 100% !important; +} + +.ui-password-group > .form-control { + flex: 1 1 auto !important; + width: 1% !important; + min-width: 0 !important; + min-height: 42px !important; + border-top-right-radius: 0 !important; + border-bottom-right-radius: 0 !important; + border-right: 0 !important; +} + +.ui-password-group > .ui-password-toggle { + appearance: none !important; + -webkit-appearance: none !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + flex: 0 0 46px !important; + width: 46px !important; + min-width: 46px !important; + min-height: 42px !important; + padding: 0 !important; + margin: 0 !important; + cursor: pointer !important; + background-color: var(--dark-700) !important; + background-image: none !important; + color: var(--text-strong) !important; + border: 1px solid var(--dark-300) !important; + border-left: 0 !important; + border-top-left-radius: 0 !important; + border-bottom-left-radius: 0 !important; + border-top-right-radius: 14px !important; + border-bottom-right-radius: 14px !important; + box-shadow: none !important; + line-height: 1 !important; +} + +.ui-password-group > .ui-password-toggle:hover, +.ui-password-group > .ui-password-toggle:focus, +.ui-password-group > .ui-password-toggle:focus-visible { + background-color: var(--dark-800) !important; + color: #fff !important; + border-color: var(--primary) !important; + box-shadow: 0 0 0 .25rem rgba(24, 64, 118, .18) !important; + outline: none !important; +} + +.ui-password-group > .ui-password-toggle.is-active { + background-color: var(--dark-800) !important; + color: #fff !important; +} + +.ui-password-group > .ui-password-toggle > * { + pointer-events: none !important; +} + +.endpoint-login .ui-password-group > .ui-password-toggle, +.endpoint-system_auth .ui-password-group > .ui-password-toggle, +.endpoint-user_management .ui-password-group > .ui-password-toggle, +.endpoint-user_management .modal .ui-password-group > .ui-password-toggle { + background-color: var(--dark-700) !important; + color: var(--text-strong) !important; + border-color: var(--dark-300) !important; +} + +.endpoint-login .ui-password-group > .ui-password-toggle:hover, +.endpoint-login .ui-password-group > .ui-password-toggle:focus, +.endpoint-login .ui-password-group > .ui-password-toggle:focus-visible, +.endpoint-system_auth .ui-password-group > .ui-password-toggle:hover, +.endpoint-system_auth .ui-password-group > .ui-password-toggle:focus, +.endpoint-system_auth .ui-password-group > .ui-password-toggle:focus-visible, +.endpoint-user_management .ui-password-group > .ui-password-toggle:hover, +.endpoint-user_management .ui-password-group > .ui-password-toggle:focus, +.endpoint-user_management .ui-password-group > .ui-password-toggle:focus-visible, +.endpoint-user_management .modal .ui-password-group > .ui-password-toggle:hover, +.endpoint-user_management .modal .ui-password-group > .ui-password-toggle:focus, +.endpoint-user_management .modal .ui-password-group > .ui-password-toggle:focus-visible { + background-color: var(--dark-800) !important; + border-color: var(--primary) !important; +} + +@media (max-width: 575.98px) { + .ui-password-group > .ui-password-toggle { + flex-basis: 44px !important; + width: 44px !important; + min-width: 44px !important; + } +} diff --git a/shopping_app/static/js/app_ui.js b/shopping_app/static/js/app_ui.js index f5bf746..db3caea 100644 --- a/shopping_app/static/js/app_ui.js +++ b/shopping_app/static/js/app_ui.js @@ -1,5 +1,6 @@ document.addEventListener('DOMContentLoaded', function () { enhancePasswordFields(); + observePasswordFields(); enhanceSearchableTables(); wireCopyButtons(); wireUnsavedWarnings(); @@ -8,55 +9,78 @@ document.addEventListener('DOMContentLoaded', function () { initResponsiveCategoryBadges(); }); -function enhancePasswordFields() { - document.querySelectorAll('input[type="password"]').forEach(function (input) { - if (input.dataset.uiPasswordReady === '1') return; - if (input.closest('[data-ui-skip-toggle="true"]')) return; +function initPasswordField(input) { + if (!input || input.dataset.uiPasswordReady === '1') return; + if (input.closest('[data-ui-skip-toggle="true"]')) return; - const btn = document.createElement('button'); - btn.type = 'button'; - btn.className = 'ui-password-toggle'; - btn.setAttribute('aria-label', 'Pokaż lub ukryj hasło'); - btn.setAttribute('aria-pressed', 'false'); - btn.title = 'Pokaż hasło'; - btn.innerHTML = ''; + const btn = document.createElement('button'); + btn.type = 'button'; + btn.className = 'ui-password-toggle'; + btn.setAttribute('aria-label', 'Pokaż lub ukryj hasło'); + btn.setAttribute('aria-pressed', 'false'); + btn.title = 'Pokaż hasło'; + btn.innerHTML = ''; - const syncState = function () { - const visible = input.type === 'text'; - btn.innerHTML = visible ? '' : ''; - btn.classList.toggle('is-active', visible); - btn.setAttribute('aria-pressed', visible ? 'true' : 'false'); - btn.title = visible ? 'Ukryj hasło' : 'Pokaż hasło'; - }; + const syncState = function () { + const visible = input.type === 'text'; + btn.innerHTML = visible ? '' : ''; + btn.classList.toggle('is-active', visible); + btn.setAttribute('aria-pressed', visible ? 'true' : 'false'); + btn.title = visible ? 'Ukryj hasło' : 'Pokaż hasło'; + }; - btn.addEventListener('click', function () { - const selectionStart = input.selectionStart; - const selectionEnd = input.selectionEnd; - input.type = input.type === 'password' ? 'text' : 'password'; - syncState(); - input.focus({ preventScroll: true }); - if (typeof selectionStart === 'number' && typeof selectionEnd === 'number') { - try { - input.setSelectionRange(selectionStart, selectionEnd); - } catch (err) {} - } - }); - - const parent = input.parentElement; - if (parent && parent.classList.contains('input-group')) { - parent.classList.add('ui-password-group'); - parent.appendChild(btn); - } else { - const wrapper = document.createElement('div'); - wrapper.className = 'input-group ui-password-group'; - input.parentNode.insertBefore(wrapper, input); - wrapper.appendChild(input); - wrapper.appendChild(btn); - } - - input.dataset.uiPasswordReady = '1'; + btn.addEventListener('click', function () { + const selectionStart = input.selectionStart; + const selectionEnd = input.selectionEnd; + input.type = input.type === 'password' ? 'text' : 'password'; syncState(); + input.focus({ preventScroll: true }); + if (typeof selectionStart === 'number' && typeof selectionEnd === 'number') { + try { + input.setSelectionRange(selectionStart, selectionEnd); + } catch (err) {} + } }); + + const parent = input.parentElement; + if (parent && parent.classList.contains('input-group')) { + parent.classList.add('ui-password-group'); + if (!parent.querySelector(':scope > .ui-password-toggle')) { + parent.appendChild(btn); + } + } else { + const wrapper = document.createElement('div'); + wrapper.className = 'input-group ui-password-group'; + input.parentNode.insertBefore(wrapper, input); + wrapper.appendChild(input); + wrapper.appendChild(btn); + } + + input.dataset.uiPasswordReady = '1'; + syncState(); +} + +function enhancePasswordFields(root) { + const scope = root && root.querySelectorAll ? root : document; + if (scope.matches && scope.matches('input[type="password"]')) { + initPasswordField(scope); + } + scope.querySelectorAll('input[type="password"]').forEach(initPasswordField); +} + +function observePasswordFields() { + if (window.__uiPasswordObserverReady) return; + const observer = new MutationObserver(function (mutations) { + mutations.forEach(function (mutation) { + mutation.addedNodes.forEach(function (node) { + if (!(node instanceof HTMLElement)) return; + enhancePasswordFields(node); + }); + }); + }); + + observer.observe(document.body, { childList: true, subtree: true }); + window.__uiPasswordObserverReady = true; } function enhanceSearchableTables() {