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() {