mirror of
https://github.com/Choices-js/Choices.git
synced 2024-05-04 23:03:11 +02:00
feat: allowHTML
This commit is contained in:
parent
82b94228f9
commit
7f727480e8
|
@ -109,6 +109,7 @@ Or include Choices directly:
|
|||
removeItems: true,
|
||||
removeItemButton: false,
|
||||
editItems: false,
|
||||
allowHTML: true
|
||||
duplicateItemsAllowed: true,
|
||||
delimiter: ',',
|
||||
paste: true,
|
||||
|
@ -314,6 +315,14 @@ Pass an array of objects:
|
|||
|
||||
**Usage:** Whether a user can edit items. An item's value can be edited by pressing the backspace.
|
||||
|
||||
### allowHTML
|
||||
|
||||
**Type:** `Boolean` **Default:** `true`
|
||||
|
||||
**Input types affected:** `text`, `select-one`, `select-multiple
|
||||
|
||||
**Usage:** Whether HTML should be shown properly when showing choices. (Can be used to perform XSS attacks if not disabled or handled correctly)
|
||||
|
||||
### duplicateItemsAllowed
|
||||
|
||||
**Type:** `Boolean` **Default:** `true`
|
||||
|
|
|
@ -151,6 +151,10 @@ class Choices implements Choices {
|
|||
| HTMLSelectElement = '[data-choice]',
|
||||
userConfig: Partial<Options> = {},
|
||||
) {
|
||||
if (userConfig.allowHTML === undefined) {
|
||||
console.warn('Deprecation warning: allowHTML in the future will be defaulted to false. You must explicitly set it to true to properly display html tags in choices.');
|
||||
}
|
||||
|
||||
this.config = merge.all<Options>(
|
||||
[DEFAULT_CONFIG, Choices.defaults.options, userConfig],
|
||||
// When merging array configs, replace with a copy of the userConfig array,
|
||||
|
@ -2105,9 +2109,7 @@ class Choices implements Choices {
|
|||
}
|
||||
|
||||
_getTemplate(template: string, ...args: any): any {
|
||||
const { classNames } = this.config;
|
||||
|
||||
return this._templates[template].call(this, classNames, ...args);
|
||||
return this._templates[template].call(this, this.config, ...args);
|
||||
}
|
||||
|
||||
_createTemplates(): void {
|
||||
|
|
|
@ -54,6 +54,7 @@ describe('constants', () => {
|
|||
expect(DEFAULT_CONFIG.removeItems).to.be.a('boolean');
|
||||
expect(DEFAULT_CONFIG.removeItemButton).to.be.a('boolean');
|
||||
expect(DEFAULT_CONFIG.editItems).to.be.a('boolean');
|
||||
expect(DEFAULT_CONFIG.allowHTML).to.be.a('boolean');
|
||||
expect(DEFAULT_CONFIG.duplicateItemsAllowed).to.be.a('boolean');
|
||||
expect(DEFAULT_CONFIG.delimiter).to.be.a('string');
|
||||
expect(DEFAULT_CONFIG.paste).to.be.a('boolean');
|
||||
|
|
|
@ -42,6 +42,7 @@ export const DEFAULT_CONFIG: Options = {
|
|||
removeItems: true,
|
||||
removeItemButton: false,
|
||||
editItems: false,
|
||||
allowHTML: true,
|
||||
duplicateItemsAllowed: true,
|
||||
delimiter: ',',
|
||||
paste: true,
|
||||
|
|
|
@ -159,6 +159,16 @@ export interface Options {
|
|||
*/
|
||||
editItems: boolean;
|
||||
|
||||
/**
|
||||
* Whether HTML should be shown properly when showing choices.
|
||||
* (Can be used to perform XSS attacks if not disabled or handled correctly)
|
||||
*
|
||||
* **Input types affected:** text, select-one, select-multiple
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
allowHTML: boolean;
|
||||
|
||||
/**
|
||||
* Whether each inputted/chosen item should be unique.
|
||||
*
|
||||
|
|
|
@ -4,14 +4,14 @@
|
|||
*/
|
||||
|
||||
import { Choice } from './interfaces/choice';
|
||||
import { ClassNames } from './interfaces/class-names';
|
||||
import { Group } from './interfaces/group';
|
||||
import { Item } from './interfaces/item';
|
||||
import { Options } from './interfaces/options';
|
||||
import { PassedElementType } from './interfaces/passed-element-type';
|
||||
|
||||
const templates = {
|
||||
containerOuter(
|
||||
{ containerOuter }: Pick<ClassNames, 'containerOuter'>,
|
||||
{ classNames: { containerOuter } }: Options,
|
||||
dir: HTMLElement['dir'],
|
||||
isSelectElement: boolean,
|
||||
isSelectOneElement: boolean,
|
||||
|
@ -46,19 +46,19 @@ const templates = {
|
|||
},
|
||||
|
||||
containerInner({
|
||||
containerInner,
|
||||
}: Pick<ClassNames, 'containerInner'>): HTMLDivElement {
|
||||
classNames: { containerInner },
|
||||
}: Options): HTMLDivElement {
|
||||
return Object.assign(document.createElement('div'), {
|
||||
className: containerInner,
|
||||
});
|
||||
},
|
||||
|
||||
itemList(
|
||||
{
|
||||
itemList({
|
||||
classNames: {
|
||||
list,
|
||||
listSingle,
|
||||
listItems,
|
||||
}: Pick<ClassNames, 'list' | 'listSingle' | 'listItems'>,
|
||||
}}: Options,
|
||||
isSelectOneElement: boolean,
|
||||
): HTMLDivElement {
|
||||
return Object.assign(document.createElement('div'), {
|
||||
|
@ -67,26 +67,22 @@ const templates = {
|
|||
},
|
||||
|
||||
placeholder(
|
||||
{ placeholder }: Pick<ClassNames, 'placeholder'>,
|
||||
{ allowHTML, classNames: { placeholder } }: Options,
|
||||
value: string,
|
||||
): HTMLDivElement {
|
||||
return Object.assign(document.createElement('div'), {
|
||||
className: placeholder,
|
||||
innerHTML: value,
|
||||
[allowHTML ? 'innerHTML' : 'innerText']: value,
|
||||
});
|
||||
},
|
||||
|
||||
item(
|
||||
{
|
||||
item({ allowHTML, classNames: {
|
||||
item,
|
||||
button,
|
||||
highlightedState,
|
||||
itemSelectable,
|
||||
placeholder,
|
||||
}: Pick<
|
||||
ClassNames,
|
||||
'item' | 'button' | 'highlightedState' | 'itemSelectable' | 'placeholder'
|
||||
>,
|
||||
} }: Options,
|
||||
{
|
||||
id,
|
||||
value,
|
||||
|
@ -101,7 +97,7 @@ const templates = {
|
|||
): HTMLDivElement {
|
||||
const div = Object.assign(document.createElement('div'), {
|
||||
className: item,
|
||||
innerHTML: label,
|
||||
[allowHTML ? 'innerHTML' : 'innerText']: label,
|
||||
});
|
||||
|
||||
Object.assign(div.dataset, {
|
||||
|
@ -135,7 +131,7 @@ const templates = {
|
|||
const removeButton = Object.assign(document.createElement('button'), {
|
||||
type: 'button',
|
||||
className: button,
|
||||
innerHTML: REMOVE_ITEM_TEXT,
|
||||
[allowHTML ? 'innerHTML' : 'innerText']: REMOVE_ITEM_TEXT,
|
||||
});
|
||||
removeButton.setAttribute(
|
||||
'aria-label',
|
||||
|
@ -149,7 +145,7 @@ const templates = {
|
|||
},
|
||||
|
||||
choiceList(
|
||||
{ list }: Pick<ClassNames, 'list'>,
|
||||
{ classNames: { list } }: Options,
|
||||
isSelectOneElement: boolean,
|
||||
): HTMLDivElement {
|
||||
const div = Object.assign(document.createElement('div'), {
|
||||
|
@ -164,12 +160,13 @@ const templates = {
|
|||
return div;
|
||||
},
|
||||
|
||||
choiceGroup(
|
||||
{
|
||||
choiceGroup({
|
||||
allowHTML,
|
||||
classNames: {
|
||||
group,
|
||||
groupHeading,
|
||||
itemDisabled,
|
||||
}: Pick<ClassNames, 'group' | 'groupHeading' | 'itemDisabled'>,
|
||||
} }: Options,
|
||||
{ id, value, disabled }: Group,
|
||||
): HTMLDivElement {
|
||||
const div = Object.assign(document.createElement('div'), {
|
||||
|
@ -191,30 +188,24 @@ const templates = {
|
|||
div.appendChild(
|
||||
Object.assign(document.createElement('div'), {
|
||||
className: groupHeading,
|
||||
innerHTML: value,
|
||||
[allowHTML ? 'innerHTML' : 'innerText']: value,
|
||||
}),
|
||||
);
|
||||
|
||||
return div;
|
||||
},
|
||||
|
||||
choice(
|
||||
{
|
||||
choice({
|
||||
allowHTML,
|
||||
classNames: {
|
||||
item,
|
||||
itemChoice,
|
||||
itemSelectable,
|
||||
selectedState,
|
||||
itemDisabled,
|
||||
placeholder,
|
||||
}: Pick<
|
||||
ClassNames,
|
||||
| 'item'
|
||||
| 'itemChoice'
|
||||
| 'itemSelectable'
|
||||
| 'selectedState'
|
||||
| 'itemDisabled'
|
||||
| 'placeholder'
|
||||
>,
|
||||
}
|
||||
}: Options,
|
||||
{
|
||||
id,
|
||||
value,
|
||||
|
@ -229,7 +220,7 @@ const templates = {
|
|||
): HTMLDivElement {
|
||||
const div = Object.assign(document.createElement('div'), {
|
||||
id: elementId,
|
||||
innerHTML: label,
|
||||
[allowHTML ? 'innerHTML' : 'innerText']: label,
|
||||
className: `${item} ${itemChoice}`,
|
||||
});
|
||||
|
||||
|
@ -263,7 +254,7 @@ const templates = {
|
|||
},
|
||||
|
||||
input(
|
||||
{ input, inputCloned }: Pick<ClassNames, 'input' | 'inputCloned'>,
|
||||
{ classNames: { input, inputCloned } }: Options,
|
||||
placeholderValue: string,
|
||||
): HTMLInputElement {
|
||||
const inp = Object.assign(document.createElement('input'), {
|
||||
|
@ -283,9 +274,11 @@ const templates = {
|
|||
},
|
||||
|
||||
dropdown({
|
||||
list,
|
||||
listDropdown,
|
||||
}: Pick<ClassNames, 'list' | 'listDropdown'>): HTMLDivElement {
|
||||
classNames: {
|
||||
list,
|
||||
listDropdown,
|
||||
}
|
||||
}: Options): HTMLDivElement {
|
||||
const div = document.createElement('div');
|
||||
|
||||
div.classList.add(list, listDropdown);
|
||||
|
@ -294,14 +287,16 @@ const templates = {
|
|||
return div;
|
||||
},
|
||||
|
||||
notice(
|
||||
{
|
||||
notice({
|
||||
allowHTML,
|
||||
classNames: {
|
||||
item,
|
||||
itemChoice,
|
||||
noResults,
|
||||
noChoices,
|
||||
}: Pick<ClassNames, 'item' | 'itemChoice' | 'noResults' | 'noChoices'>,
|
||||
innerHTML: string,
|
||||
}
|
||||
}: Options,
|
||||
innerText: string,
|
||||
type: 'no-choices' | 'no-results' | '' = '',
|
||||
): HTMLDivElement {
|
||||
const classes = [item, itemChoice];
|
||||
|
@ -313,7 +308,7 @@ const templates = {
|
|||
}
|
||||
|
||||
return Object.assign(document.createElement('div'), {
|
||||
innerHTML,
|
||||
[allowHTML ? 'innerHTML' : 'innerText']: innerText,
|
||||
className: classes.join(' '),
|
||||
});
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue