Here is a simple autocomplete using The French Government Design system.
You can see a demo here Demo
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Multiple Autocomplete Components with GUIDs</title>
<!-- Include DSFR CSS using CDN (ensure you are using the latest version) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@gouvfr/dsfr/dist/dsfr/dsfr.min.css">
</head>
<body>
<h4>
Autocomplete DSFR
</h4>
<!-- Multiple autocomplete-input components -->
<div class="container">
<div class="autocomple-container">
<autocomplete-input></autocomplete-input>
<autocomplete-input></autocomplete-input>
</div>
<ul class="fr-btns-group" style="max-width:415px;width:100%;margin:0 auto;">
<li>
<button class="fr-btn">Calculer le prix en vigueur</button>
</li>
</ul>
</div>
<script>
class AutocompleteInput extends HTMLElement {
constructor() {
super();
// Attach Shadow DOM
this.attachShadow({ mode: 'open' });
// Generate a unique ID for this component instance
this.uniqueId = this.generateGUID();
// Template for the component
const template = document.createElement('template');
template.innerHTML = `
<!-- Import DSFR stylesheet into the shadow DOM -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@gouvfr/dsfr/dist/dsfr/dsfr.min.css">
<style>
/* Custom scoped styles for the component */
.fr-input-group {
position: relative;
margin-bottom:1rem !important;
margin:0 auto;
}
ul {
position: absolute;
top: 100%;
left: 0;
right: 0;
border: 1px solid #ccc;
max-height: 150px;
overflow-y: auto;
background: white;
list-style: none;
padding: 0;
margin: 0;
z-index: 1000;
display: none;
}
li {
padding: 8px;
cursor: pointer;
}
li:hover, .highlight {
background-color: #ddd;
}
.container {
width:100%;
max-width:400px;
margin:0 auto;
}
</style>
<div class="container">
<div class="fr-input-group">
<!-- DSFR styled input -->
<label for="${this.uniqueId}" class="fr-label"></label>
<input id="${this.uniqueId}" class="fr-input" type="text" placeholder="Sélectionner"/>
<ul></ul>
</div>
</div>
`;
// Attach the template to the shadow DOM
this.shadowRoot.appendChild(template.content.cloneNode(true));
// Internal component variables
this.inputElement = this.shadowRoot.querySelector('input');
this.listElement = this.shadowRoot.querySelector('ul');
this.suggestions = [];
this.filteredSuggestions = [];
this.highlightedIndex = -1;
// Event listeners
this.inputElement.addEventListener('input', this.onInput.bind(this));
this.inputElement.addEventListener('keydown', this.onKeyDown.bind(this));
this.listElement.addEventListener('click', this.onSelect.bind(this));
}
// Method to generate a GUID for each instance
generateGUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
// Method to update suggestions from outside the component
setSuggestions(suggestions) {
this.suggestions = suggestions;
}
// Method called when user types in the input field
onInput() {
const inputValue = this.inputElement.value.toLowerCase().trim(); // Trim input value
this.filteredSuggestions = this.suggestions.filter(suggestion =>
suggestion.toLowerCase().includes(inputValue)
);
this.highlightedIndex = -1; // Reset highlighted index
// Hide the list if input is empty or no matches
if (inputValue === '' || this.filteredSuggestions.length === 0) {
this.listElement.style.display = 'none';
} else {
this.updateList();
}
}
// Method to update the suggestion list
updateList() {
this.listElement.innerHTML = '';
if (this.filteredSuggestions.length > 0) {
this.filteredSuggestions.forEach((suggestion, index) => {
const listItem = document.createElement('li');
listItem.textContent = suggestion;
if (index === this.highlightedIndex) {
listItem.classList.add('highlight');
}
this.listElement.appendChild(listItem);
});
this.listElement.style.display = 'block';
} else {
this.listElement.style.display = 'none'; // Ensure it's hidden when no matches
}
}
// Handle keyboard navigation (arrow keys, Enter)
onKeyDown(event) {
const key = event.key;
if (key === 'ArrowDown') {
this.highlightedIndex = Math.min(this.highlightedIndex + 1, this.filteredSuggestions.length - 1);
this.updateList();
} else if (key === 'ArrowUp') {
this.highlightedIndex = Math.max(this.highlightedIndex - 1, 0);
this.updateList();
} else if (key === 'Enter') {
if (this.highlightedIndex > -1) {
this.inputElement.value = this.filteredSuggestions[this.highlightedIndex];
this.listElement.style.display = 'none';
}
}
}
// Method called when the user selects a suggestion
onSelect(event) {
if (event.target.tagName === 'LI') {
this.inputElement.value = event.target.textContent;
this.listElement.style.display = 'none';
}
}
}
// Register the custom element
customElements.define('autocomplete-input', AutocompleteInput);
// Example of using the component with multiple instances
const autocompleteInputs = document.querySelectorAll('autocomplete-input');
// Set different suggestions for each instance
autocompleteInputs[0].setSuggestions(['Table', 'Chaise', 'Armoire', 'Date']);
autocompleteInputs[1].setSuggestions(['Paris', 'Rome', 'Naples']);
</script>
</body>
</html>
Top comments (0)