diff --git a/src/js/_enqueues/admin/user-profile.js b/src/js/_enqueues/admin/user-profile.js index ce680ef4c4298..5614078bb584a 100644 --- a/src/js/_enqueues/admin/user-profile.js +++ b/src/js/_enqueues/admin/user-profile.js @@ -20,7 +20,11 @@ $form, originalFormContent, $passwordWrapper, - successTimeout; + successTimeout, + isMac = window.navigator.platform ? window.navigator.platform.indexOf( 'Mac' ) !== -1 : false, + ua = navigator.userAgent.toLowerCase(), + isSafari = window.safari !== 'undefined' && typeof window.safari === 'object', + isFirefox = ua.indexOf( 'firefox' ) !== -1; function generatePassword() { if ( typeof zxcvbn !== 'function' ) { @@ -80,6 +84,8 @@ $pass1.removeClass( 'short bad good strong' ); showOrHideWeakPasswordCheckbox(); } ); + + bindCapsLockWarning( $pass1 ); } function resetToggle( show ) { @@ -213,6 +219,8 @@ } else { // Password field for the login form. $pass1 = $( '#user_pass' ); + + bindCapsLockWarning( $pass1 ); } /* @@ -332,6 +340,79 @@ } } + /** + * Bind Caps Lock detection to a password input field. + * + * @param {jQuery} $input The password input field. + */ + function bindCapsLockWarning( $input ) { + var $capsWarning, + $capsIcon, + $capsText, + capsLockOn = false; + + // Skip warning on macOS Safari + Firefox (they show native indicators). + if ( isMac && ( isSafari || isFirefox ) ) { + return; + } + + $capsWarning = $( '
' ); + $capsIcon = $( '' ); + $capsText = $( '', { 'class': 'caps-warning-text', text: __( 'Caps lock is on.' ) } ); + $capsWarning.append( $capsIcon, $capsText ); + + $input.parent( 'div' ).append( $capsWarning ); + + $input.on( 'keydown', function( jqEvent ) { + var event = jqEvent.originalEvent; + + // Skip if key is not a printable character. + // Key length > 1 usually means non-printable (e.g., "Enter", "Tab"). + if ( event.ctrlKey || event.metaKey || event.altKey || ! event.key || event.key.length !== 1 ) { + return; + } + + var state = isCapsLockOn( event ); + + // React when the state changes or if caps lock is on when the user starts typing. + if ( state !== capsLockOn ) { + capsLockOn = state; + + if ( capsLockOn ) { + $capsWarning.show(); + // Don't duplicate existing screen reader Caps lock notifications. + if ( event.key !== 'CapsLock' ) { + wp.a11y.speak( __( 'Caps lock is on.' ), 'assertive' ); + } + } else { + $capsWarning.hide(); + } + } + } ); + + $input.on( 'blur', function() { + if ( ! document.hasFocus() ) { + return; + } + capsLockOn = false; + $capsWarning.hide(); + } ); + } + + /** + * Determines if Caps Lock is currently enabled. + * + * On macOS Safari and Firefox, the native warning is preferred, + * so this function returns false to suppress custom warnings. + * + * @param {KeyboardEvent} e The keydown event object. + * + * @return {boolean} True if Caps Lock is on, false otherwise. + */ + function isCapsLockOn( event ) { + return event.getModifierState( 'CapsLock' ); + } + function showOrHideWeakPasswordCheckbox() { var passStrengthResult = $('#pass-strength-result'); diff --git a/src/wp-admin/css/forms.css b/src/wp-admin/css/forms.css index 151b60aa77467..31aca661db5a0 100644 --- a/src/wp-admin/css/forms.css +++ b/src/wp-admin/css/forms.css @@ -713,6 +713,37 @@ fieldset label, display: inline-block; } +/* Caps lock warning */ +.wp-pwd .caps-warning { + display: none; + position: relative; + background: #fcf9e8; + border: 1px solid #f0c33c; + color: #1d2327; + padding: 6px 10px; + top: -8px; +} + +.profile-php .wp-pwd .caps-warning { + padding: 3px 5px; + top: -4px; + border-radius: 4px; +} + +.wp-pwd .caps-icon { + display: inline-flex; + justify-content: center; + width: 20px; + height: 20px; + margin-right: 5px; + vertical-align: middle; +} + +.wp-pwd .caps-warning-text { + vertical-align: middle; +} +/* Caps lock warning */ + p.search-box { display: flex; flex-wrap: wrap; @@ -1641,6 +1672,10 @@ table.form-table td .updated p { padding: 8px; } + .profile-php .wp-pwd .caps-warning { + padding: 8px; + } + .password-input-wrapper { display: block; }