mirror of
https://github.com/Choices-js/Choices.git
synced 2026-03-14 14:45:47 +01:00
Use helper function for add/remove classes, standardize adding/setting classnames for elements. Also shaves ~180 bytes off the compressed bundle
This commit is contained in:
parent
c4521b893c
commit
1e741fbdd9
7 changed files with 74 additions and 55 deletions
|
|
@ -6,7 +6,8 @@
|
|||
* Fix the rendered item list was not cleared when `clearStore` was called. This impacted the on-form-reset and `refresh` features.
|
||||
|
||||
### Chore
|
||||
* Add e2e test for 'form reset' and 'on paste & search'
|
||||
* Add e2e test for 'form reset' and 'on paste & search'.
|
||||
* Cleanup adding classes to generated elements.
|
||||
|
||||
## [11.0.0] (2024-08-28)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,13 +9,14 @@ import { InputGroup } from './interfaces/input-group';
|
|||
import { Options, ObjectsInConfig } from './interfaces/options';
|
||||
import { StateChangeSet } from './interfaces/state';
|
||||
import {
|
||||
addClassesToElement,
|
||||
diff,
|
||||
escapeForTemplate,
|
||||
generateId,
|
||||
getAdjacentEl,
|
||||
getClassNames,
|
||||
getClassNamesSelector,
|
||||
isScrolledIntoView,
|
||||
removeClassesFromElement,
|
||||
resolveNoticeFunction,
|
||||
resolveStringFunction,
|
||||
sanitise,
|
||||
|
|
@ -1995,14 +1996,14 @@ class Choices {
|
|||
}
|
||||
|
||||
let passedEl = el;
|
||||
const highlightedState = getClassNames(this.config.classNames.highlightedState);
|
||||
const { highlightedState } = this.config.classNames;
|
||||
const highlightedChoices = Array.from(
|
||||
this.dropdown.element.querySelectorAll<HTMLElement>(getClassNamesSelector(highlightedState)),
|
||||
);
|
||||
|
||||
// Remove any highlighted choices
|
||||
highlightedChoices.forEach((choice) => {
|
||||
choice.classList.remove(...highlightedState);
|
||||
removeClassesFromElement(choice, highlightedState);
|
||||
choice.setAttribute('aria-selected', 'false');
|
||||
});
|
||||
|
||||
|
|
@ -2023,7 +2024,7 @@ class Choices {
|
|||
}
|
||||
}
|
||||
|
||||
passedEl.classList.add(...highlightedState);
|
||||
addClassesToElement(passedEl, highlightedState);
|
||||
passedEl.setAttribute('aria-selected', 'true');
|
||||
this.passedElement.triggerEvent(EventType.highlightChoice, {
|
||||
el: passedEl,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { getClassNames } from '../lib/utils';
|
||||
import { addClassesToElement, removeClassesFromElement } from '../lib/utils';
|
||||
import { ClassNames } from '../interfaces/class-names';
|
||||
import { PositionOptionsType } from '../interfaces/position-options-type';
|
||||
import { PassedElementType, PassedElementTypes } from '../interfaces/passed-element-type';
|
||||
|
|
@ -69,39 +69,39 @@ export default class Container {
|
|||
}
|
||||
|
||||
open(dropdownPos: number, dropdownHeight: number): void {
|
||||
this.element.classList.add(...getClassNames(this.classNames.openState));
|
||||
addClassesToElement(this.element, this.classNames.openState);
|
||||
this.element.setAttribute('aria-expanded', 'true');
|
||||
this.isOpen = true;
|
||||
|
||||
if (this.shouldFlip(dropdownPos, dropdownHeight)) {
|
||||
this.element.classList.add(...getClassNames(this.classNames.flippedState));
|
||||
addClassesToElement(this.element, this.classNames.flippedState);
|
||||
this.isFlipped = true;
|
||||
}
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.element.classList.remove(...getClassNames(this.classNames.openState));
|
||||
removeClassesFromElement(this.element, this.classNames.openState);
|
||||
this.element.setAttribute('aria-expanded', 'false');
|
||||
this.removeActiveDescendant();
|
||||
this.isOpen = false;
|
||||
|
||||
// A dropdown flips if it does not have space within the page
|
||||
if (this.isFlipped) {
|
||||
this.element.classList.remove(...getClassNames(this.classNames.flippedState));
|
||||
removeClassesFromElement(this.element, this.classNames.flippedState);
|
||||
this.isFlipped = false;
|
||||
}
|
||||
}
|
||||
|
||||
addFocusState(): void {
|
||||
this.element.classList.add(...getClassNames(this.classNames.focusState));
|
||||
addClassesToElement(this.element, this.classNames.focusState);
|
||||
}
|
||||
|
||||
removeFocusState(): void {
|
||||
this.element.classList.remove(...getClassNames(this.classNames.focusState));
|
||||
removeClassesFromElement(this.element, this.classNames.focusState);
|
||||
}
|
||||
|
||||
enable(): void {
|
||||
this.element.classList.remove(...getClassNames(this.classNames.disabledState));
|
||||
removeClassesFromElement(this.element, this.classNames.disabledState);
|
||||
this.element.removeAttribute('aria-disabled');
|
||||
if (this.type === PassedElementTypes.SelectOne) {
|
||||
this.element.setAttribute('tabindex', '0');
|
||||
|
|
@ -110,7 +110,7 @@ export default class Container {
|
|||
}
|
||||
|
||||
disable(): void {
|
||||
this.element.classList.add(...getClassNames(this.classNames.disabledState));
|
||||
addClassesToElement(this.element, this.classNames.disabledState);
|
||||
this.element.setAttribute('aria-disabled', 'true');
|
||||
if (this.type === PassedElementTypes.SelectOne) {
|
||||
this.element.setAttribute('tabindex', '-1');
|
||||
|
|
@ -144,13 +144,13 @@ export default class Container {
|
|||
}
|
||||
|
||||
addLoadingState(): void {
|
||||
this.element.classList.add(...getClassNames(this.classNames.loadingState));
|
||||
addClassesToElement(this.element, this.classNames.loadingState);
|
||||
this.element.setAttribute('aria-busy', 'true');
|
||||
this.isLoading = true;
|
||||
}
|
||||
|
||||
removeLoadingState(): void {
|
||||
this.element.classList.remove(...getClassNames(this.classNames.loadingState));
|
||||
removeClassesFromElement(this.element, this.classNames.loadingState);
|
||||
this.element.removeAttribute('aria-busy');
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ClassNames } from '../interfaces/class-names';
|
||||
import { PassedElementType } from '../interfaces/passed-element-type';
|
||||
import { getClassNames } from '../lib/utils';
|
||||
import { addClassesToElement, removeClassesFromElement } from '../lib/utils';
|
||||
|
||||
export default class Dropdown {
|
||||
element: HTMLElement;
|
||||
|
|
@ -30,7 +30,7 @@ export default class Dropdown {
|
|||
* Show dropdown to user by adding active state class
|
||||
*/
|
||||
show(): this {
|
||||
this.element.classList.add(...getClassNames(this.classNames.activeState));
|
||||
addClassesToElement(this.element, this.classNames.activeState);
|
||||
this.element.setAttribute('aria-expanded', 'true');
|
||||
this.isActive = true;
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ export default class Dropdown {
|
|||
* Hide dropdown from user
|
||||
*/
|
||||
hide(): this {
|
||||
this.element.classList.remove(...getClassNames(this.classNames.activeState));
|
||||
removeClassesFromElement(this.element, this.classNames.activeState);
|
||||
this.element.setAttribute('aria-expanded', 'false');
|
||||
this.isActive = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ClassNames } from '../interfaces/class-names';
|
||||
import { EventTypes } from '../interfaces/event-type';
|
||||
import { dispatchEvent, getClassNames } from '../lib/utils';
|
||||
import { addClassesToElement, dispatchEvent, removeClassesFromElement } from '../lib/utils';
|
||||
import { EventMap } from '../interfaces';
|
||||
|
||||
export default class WrappedElement<T extends HTMLInputElement | HTMLSelectElement> {
|
||||
|
|
@ -36,7 +36,7 @@ export default class WrappedElement<T extends HTMLInputElement | HTMLSelectEleme
|
|||
conceal(): void {
|
||||
const el = this.element;
|
||||
// Hide passed input
|
||||
el.classList.add(...getClassNames(this.classNames.input));
|
||||
addClassesToElement(el, this.classNames.input);
|
||||
el.hidden = true;
|
||||
|
||||
// Remove element from tab index
|
||||
|
|
@ -55,7 +55,7 @@ export default class WrappedElement<T extends HTMLInputElement | HTMLSelectEleme
|
|||
reveal(): void {
|
||||
const el = this.element;
|
||||
// Reinstate passed element
|
||||
el.classList.remove(...getClassNames(this.classNames.input));
|
||||
removeClassesFromElement(el, this.classNames.input);
|
||||
el.hidden = false;
|
||||
el.removeAttribute('tabindex');
|
||||
|
||||
|
|
|
|||
|
|
@ -202,6 +202,14 @@ export const getClassNamesSelector = (option: string | Array<string> | null): st
|
|||
return `.${option}`;
|
||||
};
|
||||
|
||||
export const addClassesToElement = (element: HTMLElement, className: Array<string> | string): void => {
|
||||
element.classList.add(...getClassNames(className));
|
||||
};
|
||||
|
||||
export const removeClassesFromElement = (element: HTMLElement, className: Array<string> | string): void => {
|
||||
element.classList.remove(...getClassNames(className));
|
||||
};
|
||||
|
||||
export const parseCustomProperties = (customProperties?: string): object | string => {
|
||||
if (typeof customProperties !== 'undefined') {
|
||||
try {
|
||||
|
|
@ -217,7 +225,7 @@ export const parseCustomProperties = (customProperties?: string): object | strin
|
|||
export const updateClassList = (item: ChoiceFull, add: string | string[], remove: string | string[]): void => {
|
||||
const { itemEl } = item;
|
||||
if (itemEl) {
|
||||
itemEl.classList.remove(...getClassNames(remove));
|
||||
itemEl.classList.add(...getClassNames(add));
|
||||
removeClassesFromElement(itemEl, remove);
|
||||
addClassesToElement(itemEl, add);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import {
|
|||
resolveNoticeFunction,
|
||||
setElementHtml,
|
||||
escapeForTemplate,
|
||||
addClassesToElement,
|
||||
removeClassesFromElement,
|
||||
} from './lib/utils';
|
||||
import { NoticeType, NoticeTypes, TemplateOptions, Templates as TemplatesInterface } from './interfaces/templates';
|
||||
import { StringUntrusted } from './interfaces/string-untrusted';
|
||||
|
|
@ -69,7 +71,7 @@ const templates: TemplatesInterface = {
|
|||
labelId: string,
|
||||
): HTMLDivElement {
|
||||
const div = document.createElement('div');
|
||||
div.className = getClassNames(containerOuter).join(' ');
|
||||
addClassesToElement(div, containerOuter);
|
||||
|
||||
div.dataset.type = passedElementType;
|
||||
|
||||
|
|
@ -102,7 +104,7 @@ const templates: TemplatesInterface = {
|
|||
|
||||
containerInner({ classNames: { containerInner } }: TemplateOptions): HTMLDivElement {
|
||||
const div = document.createElement('div');
|
||||
div.className = getClassNames(containerInner).join(' ');
|
||||
addClassesToElement(div, containerInner);
|
||||
|
||||
return div;
|
||||
},
|
||||
|
|
@ -112,7 +114,8 @@ const templates: TemplatesInterface = {
|
|||
isSelectOneElement: boolean,
|
||||
): HTMLDivElement {
|
||||
const div = document.createElement('div');
|
||||
div.className = `${getClassNames(list).join(' ')} ${isSelectOneElement ? getClassNames(listSingle).join(' ') : getClassNames(listItems).join(' ')}`;
|
||||
addClassesToElement(div, list);
|
||||
addClassesToElement(div, isSelectOneElement ? listSingle : listItems);
|
||||
|
||||
if (this._isSelectElement && searchEnabled) {
|
||||
div.setAttribute('role', 'listbox');
|
||||
|
|
@ -126,7 +129,7 @@ const templates: TemplatesInterface = {
|
|||
value: StringPreEscaped | string,
|
||||
): HTMLDivElement {
|
||||
const div = document.createElement('div');
|
||||
div.className = getClassNames(placeholder).join(' ');
|
||||
addClassesToElement(div, placeholder);
|
||||
setElementHtml(div, allowHTML, value);
|
||||
|
||||
return div;
|
||||
|
|
@ -145,12 +148,12 @@ const templates: TemplatesInterface = {
|
|||
): HTMLDivElement {
|
||||
const rawValue = unwrapStringForRaw(choice.value);
|
||||
const div = document.createElement('div');
|
||||
div.className = getClassNames(item).join(' ');
|
||||
addClassesToElement(div, item);
|
||||
|
||||
if (choice.labelClass) {
|
||||
const spanLabel = document.createElement('span');
|
||||
setElementHtml(spanLabel, allowHTML, choice.label);
|
||||
spanLabel.className = getClassNames(choice.labelClass).join(' ');
|
||||
addClassesToElement(spanLabel, choice.labelClass);
|
||||
div.appendChild(spanLabel);
|
||||
} else {
|
||||
setElementHtml(div, allowHTML, choice.label);
|
||||
|
|
@ -171,21 +174,21 @@ const templates: TemplatesInterface = {
|
|||
}
|
||||
|
||||
if (choice.placeholder) {
|
||||
div.classList.add(...getClassNames(placeholder));
|
||||
addClassesToElement(div, placeholder);
|
||||
div.dataset.placeholder = '';
|
||||
}
|
||||
|
||||
div.classList.add(...(choice.highlighted ? getClassNames(highlightedState) : getClassNames(itemSelectable)));
|
||||
addClassesToElement(div, choice.highlighted ? highlightedState : itemSelectable);
|
||||
|
||||
if (removeItemButton) {
|
||||
if (choice.disabled) {
|
||||
div.classList.remove(...getClassNames(itemSelectable));
|
||||
removeClassesFromElement(div, itemSelectable);
|
||||
}
|
||||
div.dataset.deletable = '';
|
||||
|
||||
const removeButton = document.createElement('button');
|
||||
removeButton.type = 'button';
|
||||
removeButton.className = getClassNames(button).join(' ');
|
||||
addClassesToElement(removeButton, button);
|
||||
setElementHtml(removeButton, true, resolveNoticeFunction(removeItemIconText, choice.value));
|
||||
|
||||
const REMOVE_ITEM_LABEL = resolveNoticeFunction(removeItemLabelText, choice.value);
|
||||
|
|
@ -205,7 +208,7 @@ const templates: TemplatesInterface = {
|
|||
|
||||
choiceList({ classNames: { list } }: TemplateOptions, isSelectOneElement: boolean): HTMLDivElement {
|
||||
const div = document.createElement('div');
|
||||
div.className = getClassNames(list).join(' ');
|
||||
addClassesToElement(div, list);
|
||||
|
||||
if (!isSelectOneElement) {
|
||||
div.setAttribute('aria-multiselectable', 'true');
|
||||
|
|
@ -221,7 +224,10 @@ const templates: TemplatesInterface = {
|
|||
): HTMLDivElement {
|
||||
const rawLabel = unwrapStringForRaw(label);
|
||||
const div = document.createElement('div');
|
||||
div.className = `${getClassNames(group).join(' ')} ${disabled ? getClassNames(itemDisabled).join(' ') : ''}`;
|
||||
addClassesToElement(div, group);
|
||||
if (disabled) {
|
||||
addClassesToElement(div, itemDisabled);
|
||||
}
|
||||
|
||||
div.setAttribute('role', 'group');
|
||||
|
||||
|
|
@ -234,7 +240,7 @@ const templates: TemplatesInterface = {
|
|||
}
|
||||
|
||||
const heading = document.createElement('div');
|
||||
heading.className = getClassNames(groupHeading).join(' ');
|
||||
addClassesToElement(heading, groupHeading);
|
||||
setElementHtml(heading, allowHTML, label || '');
|
||||
div.appendChild(heading);
|
||||
|
||||
|
|
@ -255,7 +261,8 @@ const templates: TemplatesInterface = {
|
|||
const rawValue = unwrapStringForRaw(choice.value);
|
||||
const div = document.createElement('div');
|
||||
div.id = choice.elementId as string;
|
||||
div.className = `${getClassNames(item).join(' ')} ${getClassNames(itemChoice).join(' ')}`;
|
||||
addClassesToElement(div, item);
|
||||
addClassesToElement(div, itemChoice);
|
||||
|
||||
if (groupName && typeof label === 'string') {
|
||||
label = escapeForTemplate(allowHTML, label);
|
||||
|
|
@ -268,7 +275,7 @@ const templates: TemplatesInterface = {
|
|||
if (choice.labelClass) {
|
||||
const spanLabel = document.createElement('span');
|
||||
setElementHtml(spanLabel, allowHTML, label);
|
||||
spanLabel.className = getClassNames(choice.labelClass).join(' ');
|
||||
addClassesToElement(spanLabel, choice.labelClass);
|
||||
describedBy = spanLabel;
|
||||
div.appendChild(spanLabel);
|
||||
} else {
|
||||
|
|
@ -281,16 +288,16 @@ const templates: TemplatesInterface = {
|
|||
const spanDesc = document.createElement('span');
|
||||
setElementHtml(spanDesc, allowHTML, choice.labelDescription);
|
||||
spanDesc.id = descId;
|
||||
spanDesc.classList.add(...getClassNames(description));
|
||||
addClassesToElement(spanDesc, description);
|
||||
div.appendChild(spanDesc);
|
||||
}
|
||||
|
||||
if (choice.selected) {
|
||||
div.classList.add(...getClassNames(selectedState));
|
||||
addClassesToElement(div, selectedState);
|
||||
}
|
||||
|
||||
if (choice.placeholder) {
|
||||
div.classList.add(...getClassNames(placeholder));
|
||||
addClassesToElement(div, placeholder);
|
||||
}
|
||||
|
||||
div.setAttribute('role', choice.groupId ? 'treeitem' : 'option');
|
||||
|
|
@ -305,11 +312,11 @@ const templates: TemplatesInterface = {
|
|||
assignCustomProperties(div, choice, false);
|
||||
|
||||
if (choice.disabled) {
|
||||
div.classList.add(...getClassNames(itemDisabled));
|
||||
addClassesToElement(div, itemDisabled);
|
||||
div.dataset.choiceDisabled = '';
|
||||
div.setAttribute('aria-disabled', 'true');
|
||||
} else {
|
||||
div.classList.add(...getClassNames(itemSelectable));
|
||||
addClassesToElement(div, itemSelectable);
|
||||
div.dataset.choiceSelectable = '';
|
||||
}
|
||||
|
||||
|
|
@ -322,7 +329,8 @@ const templates: TemplatesInterface = {
|
|||
): HTMLInputElement {
|
||||
const inp = document.createElement('input');
|
||||
inp.type = 'search';
|
||||
inp.className = `${getClassNames(input).join(' ')} ${getClassNames(inputCloned).join(' ')}`;
|
||||
addClassesToElement(inp, input);
|
||||
addClassesToElement(inp, inputCloned);
|
||||
inp.autocomplete = 'off';
|
||||
inp.autocapitalize = 'off';
|
||||
inp.spellcheck = false;
|
||||
|
|
@ -341,8 +349,8 @@ const templates: TemplatesInterface = {
|
|||
dropdown({ classNames: { list, listDropdown } }: TemplateOptions): HTMLDivElement {
|
||||
const div = document.createElement('div');
|
||||
|
||||
div.classList.add(...getClassNames(list));
|
||||
div.classList.add(...getClassNames(listDropdown));
|
||||
addClassesToElement(div, list);
|
||||
addClassesToElement(div, listDropdown);
|
||||
div.setAttribute('aria-expanded', 'false');
|
||||
|
||||
return div;
|
||||
|
|
@ -353,25 +361,26 @@ const templates: TemplatesInterface = {
|
|||
innerHTML: string,
|
||||
type: NoticeType = NoticeTypes.generic,
|
||||
): HTMLDivElement {
|
||||
const classes = [...getClassNames(item), ...getClassNames(itemChoice), ...getClassNames(noticeItem)];
|
||||
const notice = document.createElement('div');
|
||||
setElementHtml(notice, true, innerHTML);
|
||||
|
||||
addClassesToElement(notice, item);
|
||||
addClassesToElement(notice, itemChoice);
|
||||
addClassesToElement(notice, noticeItem);
|
||||
|
||||
// eslint-disable-next-line default-case
|
||||
switch (type) {
|
||||
case NoticeTypes.addChoice:
|
||||
classes.push(...getClassNames(addChoice));
|
||||
addClassesToElement(notice, addChoice);
|
||||
break;
|
||||
case NoticeTypes.noResults:
|
||||
classes.push(...getClassNames(noResults));
|
||||
addClassesToElement(notice, noResults);
|
||||
break;
|
||||
case NoticeTypes.noChoices:
|
||||
classes.push(...getClassNames(noChoices));
|
||||
addClassesToElement(notice, noChoices);
|
||||
break;
|
||||
}
|
||||
|
||||
const notice = document.createElement('div');
|
||||
setElementHtml(notice, true, innerHTML);
|
||||
notice.className = classes.join(' ');
|
||||
|
||||
if (type === NoticeTypes.addChoice) {
|
||||
notice.dataset.choiceSelectable = '';
|
||||
notice.dataset.choice = '';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue