Here’s an example of a custom text editor that uses an <input> and a <datalist>:

<!DOCTYPE html>
<html>
<head>
<title>Text Editor using datalist</title>
<!-- Copyright 1998-2024 by Northwoods Software Corporation. -->
</head>
<body>
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:300px"></div>
<textarea id="mySavedModel" style="width:100%;height:250px"></textarea>
<datalist id="CountryDatalist">
<option>Afghanistan</option>
<option>Åland Islands</option>
<option>Albania</option>
<option>Algeria</option>
<option>American Samoa</option>
<option>Andorra</option>
<option>Angola</option>
<option>Anguilla</option>
<option>Antarctica</option>
<option>Antigua and Barbuda</option>
<option>Argentina</option>
<option>Armenia</option>
<option>Aruba</option>
<option>Australia</option>
<option>Austria</option>
<option>Azerbaijan</option>
<option>Bahamas</option>
<option>Bahrain</option>
<option>Bangladesh</option>
<option>Barbados</option>
<option>Belarus</option>
<option>Belgium</option>
<option>Belize</option>
<option>Benin</option>
<option>Bermuda</option>
<option>Bhutan</option>
<option>Bolivia (Plurinational State of)</option>
<option>Bonaire, Sint Eustatius and Saba</option>
<option>Bosnia and Herzegovina</option>
<option>Botswana</option>
<option>Bouvet Island</option>
<option>Brazil</option>
<option>British Indian Ocean Territory</option>
<option>Brunei Darussalam</option>
<option>Bulgaria</option>
<option>Burkina Faso</option>
<option>Burundi</option>
<option>Cabo Verde</option>
<option>Cambodia</option>
<option>Cameroon</option>
<option>Canada</option>
<option>Cayman Islands</option>
<option>Central African Republic</option>
<option>Chad</option>
<option>Chile</option>
<option>China</option>
<option>Christmas Island</option>
<option>Cocos (Keeling) Islands</option>
<option>Colombia</option>
<option>Comoros</option>
<option>Congo</option>
<option>Congo, Democratic Republic of the</option>
<option>Cook Islands</option>
<option>Costa Rica</option>
<option>Côte d'Ivoire</option>
<option>Croatia</option>
<option>Cuba</option>
<option>Curaçao</option>
<option>Cyprus</option>
<option>Czechia</option>
<option>Denmark</option>
<option>Djibouti</option>
<option>Dominica</option>
<option>Dominican Republic</option>
<option>Ecuador</option>
<option>Egypt</option>
<option>El Salvador</option>
<option>Equatorial Guinea</option>
<option>Eritrea</option>
<option>Estonia</option>
<option>Eswatini</option>
<option>Ethiopia</option>
<option>Falkland Islands (Malvinas)</option>
<option>Faroe Islands</option>
<option>Fiji</option>
<option>Finland</option>
<option>France</option>
<option>French Guiana</option>
<option>French Polynesia</option>
<option>French Southern Territories</option>
<option>Gabon</option>
<option>Gambia</option>
<option>Georgia</option>
<option>Germany</option>
<option>Ghana</option>
<option>Gibraltar</option>
<option>Greece</option>
<option>Greenland</option>
<option>Grenada</option>
<option>Guadeloupe</option>
<option>Guam</option>
<option>Guatemala</option>
<option>Guernsey</option>
<option>Guinea</option>
<option>Guinea-Bissau</option>
<option>Guyana</option>
<option>Haiti</option>
<option>Heard Island and McDonald Islands</option>
<option>Holy See</option>
<option>Honduras</option>
<option>Hong Kong</option>
<option>Hungary</option>
<option>Iceland</option>
<option>India</option>
<option>Indonesia</option>
<option>Iran (Islamic Republic of)</option>
<option>Iraq</option>
<option>Ireland</option>
<option>Isle of Man</option>
<option>Israel</option>
<option>Italy</option>
<option>Jamaica</option>
<option>Japan</option>
<option>Jersey</option>
<option>Jordan</option>
<option>Kazakhstan</option>
<option>Kenya</option>
<option>Kiribati</option>
<option>Korea (Democratic People's Republic of)</option>
<option>Korea, Republic of</option>
<option>Kuwait</option>
<option>Kyrgyzstan</option>
<option>Lao People's Democratic Republic</option>
<option>Latvia</option>
<option>Lebanon</option>
<option>Lesotho</option>
<option>Liberia</option>
<option>Libya</option>
<option>Liechtenstein</option>
<option>Lithuania</option>
<option>Luxembourg</option>
<option>Macao</option>
<option>Madagascar</option>
<option>Malawi</option>
<option>Malaysia</option>
<option>Maldives</option>
<option>Mali</option>
<option>Malta</option>
<option>Marshall Islands</option>
<option>Martinique</option>
<option>Mauritania</option>
<option>Mauritius</option>
<option>Mayotte</option>
<option>Mexico</option>
<option>Micronesia (Federated States of)</option>
<option>Moldova, Republic of</option>
<option>Monaco</option>
<option>Mongolia</option>
<option>Montenegro</option>
<option>Montserrat</option>
<option>Morocco</option>
<option>Mozambique</option>
<option>Myanmar</option>
<option>Namibia</option>
<option>Nauru</option>
<option>Nepal</option>
<option>Netherlands</option>
<option>New Caledonia</option>
<option>New Zealand</option>
<option>Nicaragua</option>
<option>Niger</option>
<option>Nigeria</option>
<option>Niue</option>
<option>Norfolk Island</option>
<option>North Macedonia</option>
<option>Northern Mariana Islands</option>
<option>Norway</option>
<option>Oman</option>
<option>Pakistan</option>
<option>Palau</option>
<option>Palestine, State of</option>
<option>Panama</option>
<option>Papua New Guinea</option>
<option>Paraguay</option>
<option>Peru</option>
<option>Philippines</option>
<option>Pitcairn</option>
<option>Poland</option>
<option>Portugal</option>
<option>Puerto Rico</option>
<option>Qatar</option>
<option>Réunion</option>
<option>Romania</option>
<option>Russian Federation</option>
<option>Rwanda</option>
<option>Saint Barthélemy</option>
<option>Saint Helena, Ascension and Tristan da Cunha</option>
<option>Saint Kitts and Nevis</option>
<option>Saint Lucia</option>
<option>Saint Martin (French part)</option>
<option>Saint Pierre and Miquelon</option>
<option>Saint Vincent and the Grenadines</option>
<option>Samoa</option>
<option>San Marino</option>
<option>Sao Tome and Principe</option>
<option>Saudi Arabia</option>
<option>Senegal</option>
<option>Serbia</option>
<option>Seychelles</option>
<option>Sierra Leone</option>
<option>Singapore</option>
<option>Sint Maarten (Dutch part)</option>
<option>Slovakia</option>
<option>Slovenia</option>
<option>Solomon Islands</option>
<option>Somalia</option>
<option>South Africa</option>
<option>South Georgia and the South Sandwich Islands</option>
<option>South Sudan</option>
<option>Spain</option>
<option>Sri Lanka</option>
<option>Sudan</option>
<option>Suriname</option>
<option>Svalbard and Jan Mayen</option>
<option>Sweden</option>
<option>Switzerland</option>
<option>Syrian Arab Republic</option>
<option>Taiwan, Province of China</option>
<option>Tajikistan</option>
<option>Tanzania, United Republic of</option>
<option>Thailand</option>
<option>Timor-Leste</option>
<option>Togo</option>
<option>Tokelau</option>
<option>Tonga</option>
<option>Trinidad and Tobago</option>
<option>Tunisia</option>
<option>Turkey</option>
<option>Turkmenistan</option>
<option>Turks and Caicos Islands</option>
<option>Tuvalu</option>
<option>Uganda</option>
<option>Ukraine</option>
<option>United Arab Emirates</option>
<option>United Kingdom of Great Britain and Northern Ireland</option>
<option>United States Minor Outlying Islands</option>
<option>United States of America</option>
<option>Uruguay</option>
<option>Uzbekistan</option>
<option>Vanuatu</option>
<option>Venezuela (Bolivarian Republic of)</option>
<option>Viet Nam</option>
<option>Virgin Islands (British)</option>
<option>Virgin Islands (U.S.)</option>
<option>Wallis and Futuna</option>
<option>Western Sahara</option>
<option>Yemen</option>
<option>Zambia</option>
<option>Zimbabwe</option>
</datalist>
<script src="https://unpkg.com/gojs"></script>
<script id="code">
// Define a custom text editor, an instance of go.HTMLInfo,
// that uses an <input> with a <datalist>.
const TextEditor = new go.HTMLInfo();
const _editor = document.createElement('div');
const _input = document.createElement('input');
_editor.appendChild(_input);
_input.setAttribute("list", "CountryDatalist");
_input.setAttribute("autocomplete", "off");
_input.style["width"] = "200px";
_input.addEventListener('keydown', (e) => {
if (e.isComposing)
return;
const tool = TextEditor.tool;
if (tool.textBlock === null)
return;
const key = e.key;
if (key === 'Enter') {
// Enter
if (tool.textBlock.isMultiline === false)
e.preventDefault();
tool.acceptText(go.TextEditingAccept.Enter);
}
else if (key === 'Tab') {
// Tab
tool.acceptText(go.TextEditingAccept.Tab);
e.preventDefault();
}
else if (key === 'Escape') {
// Esc
tool.doCancel();
if (tool.diagram !== null)
tool.diagram.doFocus();
}
}, false);
// handle focus:
_input.addEventListener('focus', (e) => {
const tool = TextEditor.tool;
if (!tool ||
tool.currentTextEditor === null ||
tool.state === go.TextEditingState.None)
return;
if (tool.state === go.TextEditingState.Active) {
tool.state = go.TextEditingState.Editing;
}
}, false);
// Disallow blur.
// If the textEditingTool blurs and the text is not valid,
// we do not want focus taken off the element just because a user clicked elsewhere.
_input.addEventListener('blur', (e) => {
const tool = TextEditor.tool;
if (!tool ||
tool.currentTextEditor === null ||
tool.state === go.TextEditingState.None)
return;
_input.focus();
}, false);
TextEditor.valueFunction = () => _input.value;
TextEditor.mainElement = _editor; // to reference it more easily
TextEditor.tool = null; // Initialize
// used to be in doActivate
TextEditor.show = (textBlock, diagram, tool) => {
if (!diagram || !diagram.div) return;
if (!(textBlock instanceof go.TextBlock)) return;
if (TextEditor.tool !== null) return; // Only one at a time.
TextEditor.tool = tool; // remember the TextEditingTool for use by listeners
// This is called during validation, if validation failed:
if (tool.state === go.TextEditingState.Invalid) {
_input.style.border = '3px solid red';
_input.focus();
return;
}
// This part is called during initalization:
const loc = textBlock.getDocumentPoint(go.Spot.TopLeft);
const pos = diagram.position;
const sc = diagram.scale;
let textscale = textBlock.getDocumentScale() * sc;
if (textscale < tool.minimumEditorScale)
textscale = tool.minimumEditorScale;
// Add slightly more width/height to stop scrollbars and line wrapping on some browsers
// +6 is firefox minimum, otherwise lines will be wrapped improperly
const left = (loc.x - pos.x) * sc;
const top = (loc.y - pos.y) * sc;
_input.value = textBlock.text;
// the only way you can mix font and fontSize is if the font inherits and the fontSize overrides
// in the future maybe have input contained in its own div
diagram.div.style['font'] = textBlock.font;
const paddingsize = 1;
_editor.style['position'] = 'absolute';
_editor.style['zIndex'] = '100';
_input.style['font'] = 'inherit';
_input.style['fontSize'] = textscale * 100 + '%';
_editor.style['left'] = (left | 0) - paddingsize + 'px';
_editor.style['top'] = (top | 0) - paddingsize + 'px';
// Show:
diagram.div.appendChild(_editor);
// After adding, focus:
_input.focus();
};
TextEditor.hide = (diagram, tool) => {
TextEditor.tool = null; // forget reference to TextEditingTool
if (diagram.div) diagram.div.removeChild(_editor);
};
// Set up the example Diagram and model
const myDiagram =
new go.Diagram("myDiagramDiv",
{
"undoManager.isEnabled": true,
"ModelChanged": e => { // just for demonstration purposes,
if (e.isTransactionFinished) { // show the model data in the page's Input
document.getElementById("mySavedModel").textContent = e.model.toJson();
}
}
});
myDiagram.nodeTemplate =
new go.Node("Auto")
.add(
new go.Shape({ fill: "white" })
.bind("fill", "color"),
new go.TextBlock({ margin: 8, editable: true, textEditor: TextEditor, isMultiline: false })
.bindTwoWay("text")
);
myDiagram.model = new go.GraphLinksModel(
[
{ key: 1, text: "Austria", color: "lightblue" },
{ key: 2, text: "Belgium", color: "orange" },
{ key: 3, text: "Germany", color: "lightgreen" },
{ key: 4, text: "Denmark", color: "pink" }
],
[
{ from: 1, to: 2 },
{ from: 1, to: 3 },
{ from: 2, to: 2 },
{ from: 3, to: 4 },
{ from: 4, to: 1 }
]);
</script>
</body>
</html>