Choices/src/scripts/templates.js
Josh Johnson 84b952e115
Resolve bug #637 (#754)
* Add is-selected class when item is selected

* Remove console.log

* Update README
2019-11-13 14:49:23 +00:00

332 lines
7.6 KiB
JavaScript

/**
* Helpers to create HTML elements used by Choices
* Can be overridden by providing `callbackOnCreateTemplates` option
* @typedef {import('../../types/index').Choices.Templates} Templates
* @typedef {import('../../types/index').Choices.ClassNames} ClassNames
* @typedef {import('../../types/index').Choices.Options} Options
* @typedef {import('../../types/index').Choices.Item} Item
* @typedef {import('../../types/index').Choices.Choice} Choice
* @typedef {import('../../types/index').Choices.Group} Group
*/
export const TEMPLATES = /** @type {Templates} */ ({
/**
* @param {Partial<ClassNames>} classNames
* @param {"ltr" | "rtl" | "auto"} dir
* @param {boolean} isSelectElement
* @param {boolean} isSelectOneElement
* @param {boolean} searchEnabled
* @param {"select-one" | "select-multiple" | "text"} passedElementType
*/
containerOuter(
{ containerOuter },
dir,
isSelectElement,
isSelectOneElement,
searchEnabled,
passedElementType,
) {
const div = Object.assign(document.createElement('div'), {
className: containerOuter,
});
div.dataset.type = passedElementType;
if (dir) {
div.dir = dir;
}
if (isSelectOneElement) {
div.tabIndex = 0;
}
if (isSelectElement) {
div.setAttribute('role', searchEnabled ? 'combobox' : 'listbox');
if (searchEnabled) {
div.setAttribute('aria-autocomplete', 'list');
}
}
div.setAttribute('aria-haspopup', 'true');
div.setAttribute('aria-expanded', 'false');
return div;
},
/**
* @param {Partial<ClassNames>} classNames
*/
containerInner({ containerInner }) {
return Object.assign(document.createElement('div'), {
className: containerInner,
});
},
/**
* @param {Partial<ClassNames>} classNames
* @param {boolean} isSelectOneElement
*/
itemList({ list, listSingle, listItems }, isSelectOneElement) {
return Object.assign(document.createElement('div'), {
className: `${list} ${isSelectOneElement ? listSingle : listItems}`,
});
},
/**
* @param {Partial<ClassNames>} classNames
* @param {string} value
*/
placeholder({ placeholder }, value) {
return Object.assign(document.createElement('div'), {
className: placeholder,
innerHTML: value,
});
},
/**
* @param {Partial<ClassNames>} classNames
* @param {Item} item
* @param {boolean} removeItemButton
*/
item(
{ item, button, highlightedState, itemSelectable, placeholder },
{
id,
value,
label,
customProperties,
active,
disabled,
highlighted,
placeholder: isPlaceholder,
},
removeItemButton,
) {
const div = Object.assign(document.createElement('div'), {
className: item,
innerHTML: label,
});
Object.assign(div.dataset, {
item: '',
id,
value,
customProperties,
});
if (active) {
div.setAttribute('aria-selected', 'true');
}
if (disabled) {
div.setAttribute('aria-disabled', 'true');
}
if (isPlaceholder) {
div.classList.add(placeholder);
}
div.classList.add(highlighted ? highlightedState : itemSelectable);
if (removeItemButton) {
if (disabled) {
div.classList.remove(itemSelectable);
}
div.dataset.deletable = '';
/** @todo This MUST be localizable, not hardcoded! */
const REMOVE_ITEM_TEXT = 'Remove item';
const removeButton = Object.assign(document.createElement('button'), {
type: 'button',
className: button,
innerHTML: REMOVE_ITEM_TEXT,
});
removeButton.setAttribute(
'aria-label',
`${REMOVE_ITEM_TEXT}: '${value}'`,
);
removeButton.dataset.button = '';
div.appendChild(removeButton);
}
return div;
},
/**
* @param {Partial<ClassNames>} classNames
* @param {boolean} isSelectOneElement
*/
choiceList({ list }, isSelectOneElement) {
const div = Object.assign(document.createElement('div'), {
className: list,
});
if (!isSelectOneElement) {
div.setAttribute('aria-multiselectable', 'true');
}
div.setAttribute('role', 'listbox');
return div;
},
/**
* @param {Partial<ClassNames>} classNames
* @param {Group} group
*/
choiceGroup({ group, groupHeading, itemDisabled }, { id, value, disabled }) {
const div = Object.assign(document.createElement('div'), {
className: `${group} ${disabled ? itemDisabled : ''}`,
});
div.setAttribute('role', 'group');
Object.assign(div.dataset, {
group: '',
id,
value,
});
if (disabled) {
div.setAttribute('aria-disabled', 'true');
}
div.appendChild(
Object.assign(document.createElement('div'), {
className: groupHeading,
innerHTML: value,
}),
);
return div;
},
/**
* @param {Partial<ClassNames>} classNames
* @param {Choice} choice
* @param {Options['itemSelectText']} selectText
*/
choice(
{
item,
itemChoice,
itemSelectable,
selectedState,
itemDisabled,
placeholder,
},
{
id,
value,
label,
groupId,
elementId,
disabled: isDisabled,
selected: isSelected,
placeholder: isPlaceholder,
},
selectText,
) {
const div = Object.assign(document.createElement('div'), {
id: elementId,
innerHTML: label,
className: `${item} ${itemChoice}`,
});
if (isSelected) {
div.classList.add(selectedState);
}
if (isPlaceholder) {
div.classList.add(placeholder);
}
div.setAttribute('role', groupId > 0 ? 'treeitem' : 'option');
Object.assign(div.dataset, {
choice: '',
id,
value,
selectText,
});
if (isDisabled) {
div.classList.add(itemDisabled);
div.dataset.choiceDisabled = '';
div.setAttribute('aria-disabled', 'true');
} else {
div.classList.add(itemSelectable);
div.dataset.choiceSelectable = '';
}
return div;
},
/**
* @param {Partial<ClassNames>} classNames
* @param {string} placeholderValue
*/
input({ input, inputCloned }, placeholderValue) {
const inp = Object.assign(document.createElement('input'), {
type: 'text',
className: `${input} ${inputCloned}`,
autocomplete: 'off',
autocapitalize: 'off',
spellcheck: false,
});
inp.setAttribute('role', 'textbox');
inp.setAttribute('aria-autocomplete', 'list');
inp.setAttribute('aria-label', placeholderValue);
return inp;
},
/**
* @param {Partial<ClassNames>} classNames
*/
dropdown({ list, listDropdown }) {
const div = document.createElement('div');
div.classList.add(list, listDropdown);
div.setAttribute('aria-expanded', 'false');
return div;
},
/**
*
* @param {Partial<ClassNames>} classNames
* @param {string} innerHTML
* @param {"no-choices" | "no-results" | ""} type
*/
notice({ item, itemChoice, noResults, noChoices }, innerHTML, type = '') {
const classes = [item, itemChoice];
if (type === 'no-choices') {
classes.push(noChoices);
} else if (type === 'no-results') {
classes.push(noResults);
}
return Object.assign(document.createElement('div'), {
innerHTML,
className: classes.join(' '),
});
},
/**
* @param {Item} option
*/
option({ label, value, customProperties, active, disabled }) {
const opt = new Option(label, value, false, active);
if (customProperties) {
opt.dataset.customProperties = customProperties;
}
opt.disabled = disabled;
return opt;
},
});
export default TEMPLATES;