I recently had a requirement to enforce Proper-Case in a HTML input field. CSS Text-Transform: Capitalize was 90% there but didn't down-case multiple caps. So I wrote the Javascript below.
To my surprise you'll see that autocomplete input (user clicking one of the autocomplete options) results in keyup and keydown events that are bereft of any "key" properties :-( As you can also see in the code, I'm having to search for "key" in the event and if it's not there I assume it's an autocomplete.
Is there a better/documented way? Surely an autocomplete is more of a "Paste" event? Does someone have pointers to a specification(s)?
<!DOCTYPE html>
<html>
<head>
<script type="application/javascript">
'use strict';
document.addEventListener("DOMContentLoaded", isDOMIsGood);
function isDOMIsGood()
{
// Handle autocomplete
document.body.addEventListener('keyup',
(e) => {
if (e.target.tagName.toUpperCase() != "INPUT" ||
e.target.type.toUpperCase() != "TEXT" ||
getComputedStyle(e.target).textTransform.toUpperCase()
!= "CAPITALIZE" ||
(
"transform" in e.target.dataset &&
e.target.dataset.transform.toLowerCase()
== "standard"
))
return true;
if ("key" in e) // not autocomplete
return true;
e.target.value = e.target.value.toLowerCase();
return true;
}
);
// Handle normal keyboard input
document.body.addEventListener('keydown',
(e) => {
const WORD_DELIMS = ".,& -:"; // Needs work
if (e.target.tagName.toUpperCase() != "INPUT" ||
e.target.type.toUpperCase() != "TEXT")
return true;
if (!("key" in e)) // autocomplete
return true;
if (e.key.length > 1 ||
e.altKey ||
e.ctrlKey ||
e.isComposing ||
e.metaKey ||
e.target.value.length == 0 ||
e.target.selectionStart == 0 ||
e.key > "Z" ||
e.key < "A" ||
(
"transform" in e.target.dataset &&
e.target.dataset.transform.toLowerCase()
== "standard"
) ||
getComputedStyle(e.target).textTransform.toUpperCase()
!= "CAPITALIZE" ||
WORD_DELIMS.indexOf(e.target.value.substr(e.target.selectionStart - 1, 1)) != -1)
return true;
let cursorPos = e.target.selectionStart;
e.target.value = e.target.value.substring(0, cursorPos) +
String.fromCharCode(e.key.charCodeAt(0) + 32) +
e.target.value.substring(cursorPos);
e.target.selectionStart = ++cursorPos;
e.target.selectionEnd = cursorPos;
e.preventDefault();
e.stopPropagation();
return false;
}
);
// Handle paste
document.body.addEventListener("paste",
(e) => {
const WORD_DELIMS = ".,& -:"; // Needs work
if (e.target.tagName.toUpperCase() != "INPUT" ||
e.target.type.toUpperCase() != "TEXT" ||
getComputedStyle(e.target).textTransform.toUpperCase()
!= "CAPITALIZE" ||
(
"transform" in e.target.dataset &&
e.target.dataset.transform.toLowerCase()
== "standard"
))
return true;
let paste = (e.clipboardData || window.clipboardData).getData('text');
if (paste.length == 0)
return true;
e.target.value = (e.target.value.substring(0, e.target.selectionStart) +
paste + e.target.value.substring(e.target.selectionStart)).toLowerCase();
e.preventDefault();
e.stopPropagation();
return true;
}
);
// Allow the user, config file, etc set the enforcement level
document.getElementById("properRules").addEventListener("click",
(e) => {
if (e.target.tagName.toLowerCase() == "input" &&
e.target.type.toLowerCase() == "radio" &&
e.target.name == "properCase")
{
var enhanced = document.getElementById("enhanced");
enhanced.dataset.transform = e.target.value;
if (enhanced.dataset.transform == "none")
enhanced.style.textTransform = "none";
else
enhanced.style.textTransform = "capitalize";
}
return true;
}
);
}
</script>
<style type="text/css">
input[type=radio]:checked {
accent-color: lightblue;;
}
</style>
</head>
<body>
<h1>Supplementing CSS Text-Transform: Capitalize</h1>
<h2>How to enforce "Proper Case" on HTML input?</h2>
<p>CSS Text-Transform will upcase each word if all lower case but not downcase multiple consecutive caps</p>
<div id="outer">
<div>
<label for="enhanced">Enhanced Capitalize:</label>
<input id="enhanced" type="text" style="text-transform: capitalize; background-color: lightblue;"
autocomplete="new-password" autofocus spellcheck="false" >
<br><br>
</div>
<div id="properRules">
<p>"Proper Case" enforcement level:</p>
<input type="radio" id="pc1" name="properCase" value="strict" checked=checked >
<label for="pc1">Strict</label><br>
<input type="radio" id="pc2" name="properCase" value="standard">
<label for="pc2">Relaxed</label><br>
<input type="radio" id="pc3" name="properCase" value="none">
<label for="pc3">None</label><br>
</div>
</div>
</body>
</html>
Here is how you can do it without changing the html. But I would suggest a better way would be to have an input for the user to enter or paste their text and an output where you transform that text based on a radio button selection.
HTML
<h1>Supplementing CSS Text-Transform: Capitalize</h1>
<h2>How to enforce "Proper Case" on HTML input?</h2>
<p>CSS Text-Transform will upcase each word if all lower case but not downcase multiple consecutive caps</p>
<div id="outer">
<div>
<label for="enhanced">Enhanced Capitalize:</label>
<input id="enhanced" type="text" style="background-color: lightblue;" autocomplete="new-password" autofocus spellcheck="false">
<button id="copy">Copy</button>
<br><br>
</div>
<div id="properRules">
<p>"Proper Case" enforcement level:</p>
<input type="radio" id="pc1" name="properCase" value="strict" checked=checked>
<label for="pc1">Strict</label><br>
<input type="radio" id="pc2" name="properCase" value="standard">
<label for="pc2">Relaxed</label><br>
<input type="radio" id="pc3" name="properCase" value="none">
<label for="pc3">None</label><br>
</div>
</div>
CSS
input[type=radio]:checked {
accent-color: lightblue;
}
.capitalize {
text-transform: capitalize;
}
JS
const textCopy = document.getElementById("copy");
const textInput = document.getElementById("enhanced");
const radioStrict = document.getElementById("pc1");
const radioRelaxed = document.getElementById("pc2");
const radioNone = document.getElementById("pc3");
textCopy.addEventListener('click', () => {
textInput.select();
const range = document.getSelection();
const value = range.toString();
navigator.clipboard.writeText( value );
console.log( value );
alert( value );
});
textInput.addEventListener('input', () => {
if (radioStrict.checked) {
textInput.value = textInput.value.toLowerCase();
textInput.classList.add('capitalize');
return true;
}
if (radioRelaxed.checked) {
textInput.classList.add('capitalize');
return true;
}
if (radioNone.checked) {
textInput.classList.remove('capitalize');
return true;
}
});
radioStrict.addEventListener('input', () => {
textInput.value = textInput.value.toLowerCase();
textInput.classList.add('capitalize');
});
radioRelaxed.addEventListener('input', () => {
textInput.classList.add('capitalize');
});
radioNone.addEventListener('input', () => {
textInput.classList.remove('capitalize');
});