As far as I'm aware, there is no standard to apply a <datalist>
to random elements with the [contenteditable]
attribute, or anything other than a input with the [list]
attribute. At best, you'd be beholden to how individual browsers choose to implement that spec.
Obviously, the best medicine is to convert to semantically correct html, but just adding that note for future visitors to do if possible, since that's not your use case.
A possible workaround is to spin up an input
element during the focusin
event, match the styling of the surrounding span, allow the native browser events to fire, and apply the updated value when the input loses focus.
Here's how that would look in JavaScript:
document.addEventListener('focusin', function (event) {
if (event.target.matches('[contenteditable]')) {
var editable = event.target
// get text
var text = editable.innerText
// create input
var input = document.createElement("input");
input.type = "text";
input.className = "editable-mirror";
input.setAttribute("list", "browsers");
input.value = text;
editable.appendChild(input);
input.focus()
}
});
document.addEventListener('focusout', function (event) {
if (event.target.matches('.editable-mirror')) {
var input = event.target
var editable = input.closest("[contenteditable]")
// get text
var text = input.value;
// destroy input
input.parentNode.removeChild(input);
// apply value
editable.innerText = text;
}
});
And some starter styles (although Your Mileage May Vary)
[contenteditable] {
position: relative;
border: 1px solid silver;
padding: 2px 5px;
display: inline-block;
}
.editable-mirror {
position: absolute;
left: -1px;
top: -1px;
height: calc(100% + 2px);
width: calc(100% + 7px);
padding: 2px 5px;
margin: 0;
border: 0;
}
Here's a working demo in Stack Snippets & JSFiddle
<!-- begin snippet: js hide: true console: true babel: false -->
<!-- language: lang-js -->
document.addEventListener('focusin', function (event) {
if (event.target.matches('[contenteditable]')) {
console.log('focused')
var editable = event.target
// enter edit mode
editable.classList.add("editing")
// get text
var text = editable.innerText
// create input
var input = document.createElement("input");
input.type = "text";
input.className = "editable-mirror";
input.setAttribute("list", "browsers");
input.value = text;
editable.appendChild(input);
input.focus()
}
}, false);
document.addEventListener('focusout', function (event) {
if (event.target.matches('.editable-mirror')) {
console.log('blur')
var input = event.target
var editable = input.closest("[contenteditable]")
// leave edit mode
editable.classList.remove("editing")
// get text
var text = input.value;
// destroy input
input.parentNode.removeChild(input);
// apply value
editable.innerText = text;
}
}, false);
<!-- language: lang-css -->
[contenteditable] {
position: relative;
border: 1px solid silver;
padding: 2px 5px;
}
.editable-mirror {
position: absolute;
left: -1px;
top: -1px;
height: calc(100% + 2px);
width: calc(100% + 12px);
padding: 2px 5px;
margin: 0;
border: 0;
font: inherit;
}
<!-- language: lang-html -->
<h2>Regular - Input + Datalist</h2>
<label>Choose a browser from this list:
<input type="text" list="browsers" placeholder="Edit Me" id="regular" /></label>
<datalist id="browsers">
<option value="Chrome"/>
<option value="Firefox"/>
<option value="Internet Explorer"/>
<option value="Opera"/>
<option value="Safari"/>
<option value="Microsoft Edge"/>
</datalist>
<h2>Workaround - ContentEditable + Datalist</h2>
<label>Choose a browser from this list:
<span contenteditable list="browsers" name="myBrowser">edit me</span></label>
<!-- end snippet -->