mirror of
https://github.com/Choices-js/Choices.git
synced 2024-05-22 23:42:16 +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)
|
// Retrieve triggering element (i.e. element with 'data-choice' trigger)
|
||||||
this.passedElement = isType('String', element) ? document.querySelector(element) : element;
|
this.passedElement = isType('String', element) ? document.querySelector(element) : element;
|
||||||
|
|
||||||
|
this.highlightPosition = 0;
|
||||||
|
|
||||||
// Set preset items - this looks out of place
|
// Set preset items - this looks out of place
|
||||||
this.presetItems = [];
|
this.presetItems = [];
|
||||||
if(this.options.items.length) {
|
if(this.options.items.length) {
|
||||||
|
@ -280,26 +282,20 @@ export class Choices {
|
||||||
if(this.passedElement.type === 'select-multiple' && hasActiveDropdown) {
|
if(this.passedElement.type === 'select-multiple' && hasActiveDropdown) {
|
||||||
|
|
||||||
const currentEl = this.dropdown.querySelector(`.${this.options.classNames.highlightedState}`);
|
const currentEl = this.dropdown.querySelector(`.${this.options.classNames.highlightedState}`);
|
||||||
|
const directionInt = e.keyCode === downKey ? 1 : -1;
|
||||||
let nextEl;
|
let nextEl;
|
||||||
|
|
||||||
if(currentEl) {
|
if(currentEl) {
|
||||||
if(e.keyCode === downKey) {
|
nextEl = getAdjacentEl(currentEl, '[data-option-selectable]', directionInt);
|
||||||
nextEl = getAdjacentEl(currentEl, '[data-option-selectable]', 1);
|
|
||||||
} else if(e.keyCode === upKey) {
|
|
||||||
nextEl = getAdjacentEl(currentEl, '[data-option-selectable]', -1);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
nextEl = this.dropdown.querySelector('[data-option-selectable]');
|
nextEl = this.dropdown.querySelector('[data-option-selectable]');
|
||||||
}
|
}
|
||||||
|
|
||||||
if(nextEl) {
|
if(nextEl) {
|
||||||
if(currentEl) {
|
this.highlightOption(nextEl);
|
||||||
currentEl.classList.remove(this.options.classNames.highlightedState);
|
if(!isScrolledIntoView(nextEl, this.dropdown, directionInt)) {
|
||||||
|
this.scrollToOption(nextEl, directionInt);
|
||||||
}
|
}
|
||||||
if(!isScrolledIntoView(nextEl)) {
|
|
||||||
nextEl.scrollIntoView();
|
|
||||||
}
|
|
||||||
nextEl.classList.add(this.options.classNames.highlightedState);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
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 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(this.dropdown && (e.target === this.dropdown || findAncestor(e.target, this.options.classNames.listDropdown))) {
|
||||||
if(e.target.hasAttribute('data-option')) {
|
if(e.target.hasAttribute('data-option')) {
|
||||||
const highlightedOptions = this.dropdown.querySelectorAll(`.${this.options.classNames.highlightedState}`);
|
this.highlightOption(e.target);
|
||||||
// Remove any highlighted options
|
|
||||||
Array.from(highlightedOptions).forEach((element) => {
|
|
||||||
element.classList.remove(this.options.classNames.highlightedState);
|
|
||||||
});
|
|
||||||
|
|
||||||
e.target.classList.add(this.options.classNames.highlightedState);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -471,8 +461,8 @@ export class Choices {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests value against a regular expression
|
* Tests value against a regular expression
|
||||||
* @param {string} value Value to test
|
* @param {string} value Value to test
|
||||||
* @return {Boolean} Whether test passed/failed
|
* @return {Boolean} Whether test passed/failed
|
||||||
*/
|
*/
|
||||||
regexFilter(value) {
|
regexFilter(value) {
|
||||||
const expression = new RegExp(this.options.regexFilter, 'i');
|
const expression = new RegExp(this.options.regexFilter, 'i');
|
||||||
|
@ -481,6 +471,50 @@ export class Choices {
|
||||||
return passesTest;
|
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)
|
* Select item (a selected item can be deleted)
|
||||||
* @param {Element} item Element to select
|
* @param {Element} item Element to select
|
||||||
|
@ -951,11 +985,7 @@ export class Choices {
|
||||||
optionListFragment.appendChild(dropdownItem);
|
optionListFragment.appendChild(dropdownItem);
|
||||||
this.dropdown.appendChild(optionListFragment);
|
this.dropdown.appendChild(optionListFragment);
|
||||||
} else {
|
} else {
|
||||||
// Highlight first element in dropdown
|
this.highlightOption();
|
||||||
const firstEl = this.dropdown.querySelector('[data-option]');
|
|
||||||
if(firstEl) {
|
|
||||||
firstEl.classList.add(this.options.classNames.highlightedState);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -320,7 +320,7 @@ export const getScrollPosition = function(position) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether an element is within the viewport
|
* 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 {String} Position of scroll
|
||||||
* @return {Boolean}
|
* @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;
|
return this.getScrollPosition(position) > (this.getElemDistance(el) + this.getElementOffset(el, offset)) ? true : false;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isScrolledIntoView = (el) => {
|
/**
|
||||||
const dimensions = el.getBoundingClientRect();
|
* Determine whether an element is within
|
||||||
const elemTop = dimensions.top;
|
* @param {HTMLElement} el Element to test
|
||||||
const elemBottom = dimensions.bottom;
|
* @param {HTMLElement} parent Scrolling parent
|
||||||
const isVisible = (elemTop >= 0) && (elemBottom <= window.innerHeight);
|
* @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;
|
return isVisible;
|
||||||
}
|
}
|
||||||
|
|
12
index.html
12
index.html
|
@ -62,7 +62,7 @@
|
||||||
<option value="Lyon">Lyon</option>
|
<option value="Lyon">Lyon</option>
|
||||||
<option value="Marseille">Marseille</option>
|
<option value="Marseille">Marseille</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
<optgroup label="DL" disabled>
|
<optgroup label="DE" disabled>
|
||||||
<option value="Hamburg">Hamburg</option>
|
<option value="Hamburg">Hamburg</option>
|
||||||
<option value="Munich">Munich</option>
|
<option value="Munich">Munich</option>
|
||||||
<option value="Berlin">Berlin</option>
|
<option value="Berlin">Berlin</option>
|
||||||
|
@ -72,6 +72,16 @@
|
||||||
<option value="Washington" disabled>Washington</option>
|
<option value="Washington" disabled>Washington</option>
|
||||||
<option value="Michigan">Michigan</option>
|
<option value="Michigan">Michigan</option>
|
||||||
</optgroup>
|
</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>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue