mirror of
https://github.com/Choices-js/Choices.git
synced 2024-05-10 17:46:35 +02:00
Highlight position function + scroll dropdown based on highlighted option
This commit is contained in:
parent
5aaf5ad117
commit
39fff5dd34
4
assets/scripts/dist/bundle.js
vendored
4
assets/scripts/dist/bundle.js
vendored
File diff suppressed because one or more lines are too long
|
@ -90,6 +90,8 @@ export class Choices {
|
|||
// Retrieve triggering element (i.e. element with 'data-choice' trigger)
|
||||
this.passedElement = isType('String', element) ? document.querySelector(element) : element;
|
||||
|
||||
this.highlightPosition = 0;
|
||||
|
||||
// Set preset items - this looks out of place
|
||||
this.presetItems = [];
|
||||
if(this.options.items.length) {
|
||||
|
@ -280,26 +282,20 @@ export class Choices {
|
|||
if(this.passedElement.type === 'select-multiple' && hasActiveDropdown) {
|
||||
|
||||
const currentEl = this.dropdown.querySelector(`.${this.options.classNames.highlightedState}`);
|
||||
const directionInt = e.keyCode === downKey ? 1 : -1;
|
||||
let nextEl;
|
||||
|
||||
if(currentEl) {
|
||||
if(e.keyCode === downKey) {
|
||||
nextEl = getAdjacentEl(currentEl, '[data-option-selectable]', 1);
|
||||
} else if(e.keyCode === upKey) {
|
||||
nextEl = getAdjacentEl(currentEl, '[data-option-selectable]', -1);
|
||||
}
|
||||
nextEl = getAdjacentEl(currentEl, '[data-option-selectable]', directionInt);
|
||||
} else {
|
||||
nextEl = this.dropdown.querySelector('[data-option-selectable]');
|
||||
}
|
||||
|
||||
if(nextEl) {
|
||||
if(currentEl) {
|
||||
currentEl.classList.remove(this.options.classNames.highlightedState);
|
||||
this.highlightOption(nextEl);
|
||||
if(!isScrolledIntoView(nextEl, this.dropdown, directionInt)) {
|
||||
this.scrollToOption(nextEl, directionInt);
|
||||
}
|
||||
if(!isScrolledIntoView(nextEl)) {
|
||||
nextEl.scrollIntoView();
|
||||
}
|
||||
nextEl.classList.add(this.options.classNames.highlightedState);
|
||||
}
|
||||
}
|
||||
break
|
||||
|
@ -432,13 +428,7 @@ export class Choices {
|
|||
// If we have a dropdown and it is either the target or one of its children is the target
|
||||
if(this.dropdown && (e.target === this.dropdown || findAncestor(e.target, this.options.classNames.listDropdown))) {
|
||||
if(e.target.hasAttribute('data-option')) {
|
||||
const highlightedOptions = this.dropdown.querySelectorAll(`.${this.options.classNames.highlightedState}`);
|
||||
// Remove any highlighted options
|
||||
Array.from(highlightedOptions).forEach((element) => {
|
||||
element.classList.remove(this.options.classNames.highlightedState);
|
||||
});
|
||||
|
||||
e.target.classList.add(this.options.classNames.highlightedState);
|
||||
this.highlightOption(e.target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -471,8 +461,8 @@ export class Choices {
|
|||
|
||||
/**
|
||||
* Tests value against a regular expression
|
||||
* @param {string} value Value to test
|
||||
* @return {Boolean} Whether test passed/failed
|
||||
* @param {string} value Value to test
|
||||
* @return {Boolean} Whether test passed/failed
|
||||
*/
|
||||
regexFilter(value) {
|
||||
const expression = new RegExp(this.options.regexFilter, 'i');
|
||||
|
@ -481,6 +471,50 @@ export class Choices {
|
|||
return passesTest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to an option element
|
||||
* @param {HTMLElement} option Option to scroll to
|
||||
* @param {Number} direction Whether option is above or below
|
||||
* @return
|
||||
*/
|
||||
scrollToOption(option, direction) {
|
||||
if(!option) return;
|
||||
// Distance from bottom of element to top of parent
|
||||
const optionPos = option.offsetTop + option.offsetHeight;
|
||||
// Scroll position from top
|
||||
const containerPos = this.dropdown.scrollTop + this.dropdown.offsetHeight;
|
||||
|
||||
if(direction > 0) {
|
||||
const scrollDiff = optionPos - containerPos;
|
||||
this.dropdown.scrollTop += scrollDiff;
|
||||
} else {
|
||||
this.dropdown.scrollTop = option.offsetTop;
|
||||
}
|
||||
}
|
||||
|
||||
highlightOption(el) {
|
||||
// Highlight first element in dropdown
|
||||
const options = Array.from(this.dropdown.querySelectorAll('[data-option-selectable]'));
|
||||
const highlightedOptions = Array.from(this.dropdown.querySelectorAll(`.${this.options.classNames.highlightedState}`));
|
||||
|
||||
// Remove any highlighted options
|
||||
highlightedOptions.forEach((el) => {
|
||||
el.classList.remove(this.options.classNames.highlightedState);
|
||||
});
|
||||
|
||||
if(el){
|
||||
this.highlightPosition = options.indexOf(el);
|
||||
el.classList.add(this.options.classNames.highlightedState);
|
||||
} else {
|
||||
let el = options[this.highlightPosition];
|
||||
if(!el) el = options[0];
|
||||
|
||||
if(el) {
|
||||
el.classList.add(this.options.classNames.highlightedState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select item (a selected item can be deleted)
|
||||
* @param {Element} item Element to select
|
||||
|
@ -951,11 +985,7 @@ export class Choices {
|
|||
optionListFragment.appendChild(dropdownItem);
|
||||
this.dropdown.appendChild(optionListFragment);
|
||||
} else {
|
||||
// Highlight first element in dropdown
|
||||
const firstEl = this.dropdown.querySelector('[data-option]');
|
||||
if(firstEl) {
|
||||
firstEl.classList.add(this.options.classNames.highlightedState);
|
||||
}
|
||||
this.highlightOption();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -320,7 +320,7 @@ export const getScrollPosition = function(position) {
|
|||
|
||||
/**
|
||||
* Determine whether an element is within the viewport
|
||||
* @param {HTMLElement} el Element to test for
|
||||
* @param {HTMLElement} el Element to test
|
||||
* @return {String} Position of scroll
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
@ -329,11 +329,25 @@ export const isInView = function(el, position, offset) {
|
|||
return this.getScrollPosition(position) > (this.getElemDistance(el) + this.getElementOffset(el, offset)) ? true : false;
|
||||
};
|
||||
|
||||
export const isScrolledIntoView = (el) => {
|
||||
const dimensions = el.getBoundingClientRect();
|
||||
const elemTop = dimensions.top;
|
||||
const elemBottom = dimensions.bottom;
|
||||
const isVisible = (elemTop >= 0) && (elemBottom <= window.innerHeight);
|
||||
/**
|
||||
* Determine whether an element is within
|
||||
* @param {HTMLElement} el Element to test
|
||||
* @param {HTMLElement} parent Scrolling parent
|
||||
* @param {Number} direction Whether element is visible from above or below
|
||||
* @return {Boolean}
|
||||
*/
|
||||
export const isScrolledIntoView = (el, parent, direction = 1) => {
|
||||
if(!el) return;
|
||||
|
||||
let isVisible;
|
||||
|
||||
if(direction > 0) {
|
||||
// In view from bottom
|
||||
isVisible = (parent.scrollTop + parent.offsetHeight) >= (el.offsetTop + el.offsetHeight) ;
|
||||
} else {
|
||||
// In view from top
|
||||
isVisible = el.offsetTop >= parent.scrollTop;
|
||||
}
|
||||
|
||||
return isVisible;
|
||||
}
|
||||
|
|
12
index.html
12
index.html
|
@ -62,7 +62,7 @@
|
|||
<option value="Lyon">Lyon</option>
|
||||
<option value="Marseille">Marseille</option>
|
||||
</optgroup>
|
||||
<optgroup label="DL" disabled>
|
||||
<optgroup label="DE" disabled>
|
||||
<option value="Hamburg">Hamburg</option>
|
||||
<option value="Munich">Munich</option>
|
||||
<option value="Berlin">Berlin</option>
|
||||
|
@ -72,6 +72,16 @@
|
|||
<option value="Washington" disabled>Washington</option>
|
||||
<option value="Michigan">Michigan</option>
|
||||
</optgroup>
|
||||
<optgroup label="SP">
|
||||
<option value="Madrid">Madrid</option>
|
||||
<option value="Barcelona">Barcelona</option>
|
||||
<option value="Malaga">Malaga</option>
|
||||
</optgroup>
|
||||
<optgroup label="CA">
|
||||
<option value="Montreal">Montreal</option>
|
||||
<option value="Toronto">Toronto</option>
|
||||
<option value="Vancouver">Vancouver</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue