mirror of
https://github.com/Choices-js/Choices.git
synced 2026-03-14 22:55:46 +01:00
Replace 2nd argument to of callbackOnCreateTemplates with an escaping function, and fix aria-label could be set to a bad value
This commit is contained in:
parent
fba3bbdcd4
commit
1699018ad8
6 changed files with 30 additions and 17 deletions
|
|
@ -21,7 +21,7 @@
|
|||
* Add `removeItemButtonAlignLeft` option, to control if the remove item button is at the start or the end of the item.
|
||||
* Add `removeChoice` method.
|
||||
* Improve rendering performance by batching changes.
|
||||
* Provide original templates via a new argument to the `callbackOnCreateTemplates` callback. #1149
|
||||
* `escapeForTemplate` function is passed to the 2nd method of the `callbackOnCreateTemplates` callback.
|
||||
* When `allowHtml` is false, default templates now render escaped html to `innerHtml` writing to `innerText`.
|
||||
* This provides consistent rendering performance as `innerText` is quirky and slower than escaped html into `innerHtml`
|
||||
|
||||
|
|
@ -42,3 +42,4 @@
|
|||
* Fix clearInput function did not clear the last search.
|
||||
* Fix `addItemFilter` would allow empty strings as input to be added for items.
|
||||
* Fix various issues with double escaping when displaying items/choices depending on allowHTML mode.
|
||||
* Fix `aria-label` for placeholders was set to the string `null`
|
||||
|
|
|
|||
|
|
@ -746,7 +746,7 @@
|
|||
var singleNoSearch = new Choices('#choices-single-no-search', {
|
||||
allowHTML: true,
|
||||
searchEnabled: false,
|
||||
removeItemButton: true,
|
||||
removeItemButton: true,
|
||||
choices: [
|
||||
{ value: 'One', label: 'Label One' },
|
||||
{ value: 'Two', label: 'Label Two', disabled: true },
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ import {
|
|||
} from './lib/utils';
|
||||
import { defaultState } from './reducers';
|
||||
import Store from './store/store';
|
||||
import templates from './templates';
|
||||
import templates, { escapeForTemplate } from './templates';
|
||||
import { mapInputToChoice } from './lib/choice-input';
|
||||
import { ChoiceFull } from './interfaces/choice-full';
|
||||
import { GroupFull } from './interfaces/group-full';
|
||||
|
|
@ -2245,7 +2245,7 @@ class Choices implements Choices {
|
|||
userTemplates = callbackOnCreateTemplates.call(
|
||||
this,
|
||||
strToEl,
|
||||
defaultTemplates,
|
||||
escapeForTemplate,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { InputChoice } from './input-choice';
|
|||
import { ClassNames } from './class-names';
|
||||
import { PositionOptionsType } from './position-options-type';
|
||||
import { Types } from './types';
|
||||
import Templates from '../templates';
|
||||
import { RecordToCompare } from '../lib/utils';
|
||||
|
||||
/**
|
||||
|
|
@ -601,7 +600,10 @@ export interface Options {
|
|||
* @default null
|
||||
*/
|
||||
callbackOnCreateTemplates:
|
||||
| ((template: Types.StrToEl, defaultTemplates: typeof Templates) => void)
|
||||
| ((
|
||||
template: Types.StrToEl,
|
||||
escapeForTemplate: Types.EscapeForTemplateFn,
|
||||
) => void)
|
||||
| null;
|
||||
|
||||
appendGroupInSearch: false;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,14 @@
|
|||
import { StringUntrusted } from './string-untrusted';
|
||||
import { StringPreEscaped } from './string-pre-escaped';
|
||||
|
||||
export namespace Types {
|
||||
export type StrToEl = (
|
||||
str: string,
|
||||
) => HTMLElement | HTMLInputElement | HTMLOptionElement;
|
||||
export type EscapeForTemplateFn = (
|
||||
allowHTML: boolean,
|
||||
s: StringUntrusted | StringPreEscaped | string,
|
||||
) => string;
|
||||
export type StringFunction = () => string;
|
||||
export type NoticeStringFunction = (
|
||||
value: string,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
/**
|
||||
* Helpers to create HTML elements used by Choices
|
||||
* Can be overridden by providing `callbackOnCreateTemplates` option
|
||||
* Can be overridden by providing `callbackOnCreateTemplates` option.
|
||||
* `Choices.defaults.templates` allows access to the default template methods from `callbackOnCreateTemplates`
|
||||
*/
|
||||
|
||||
import { ChoiceFull } from './interfaces/choice-full';
|
||||
|
|
@ -22,7 +23,7 @@ type TemplateOptions = Record<
|
|||
any
|
||||
>;
|
||||
|
||||
const unwrapForTemplate = (
|
||||
export const escapeForTemplate = (
|
||||
allowHTML: boolean,
|
||||
s: StringUntrusted | StringPreEscaped | string,
|
||||
): string => (allowHTML ? unwrapStringForEscaped(s) : (sanitise(s) as string));
|
||||
|
|
@ -94,7 +95,7 @@ const templates = {
|
|||
): HTMLDivElement {
|
||||
return Object.assign(document.createElement('div'), {
|
||||
className: getClassNames(placeholder).join(' '),
|
||||
innerHTML: unwrapForTemplate(allowHTML, value),
|
||||
innerHTML: escapeForTemplate(allowHTML, value),
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -130,12 +131,12 @@ const templates = {
|
|||
|
||||
if (typeof labelClass === 'string' || Array.isArray(labelClass)) {
|
||||
const spanLabel = Object.assign(document.createElement('span'), {
|
||||
innerHTML: unwrapForTemplate(allowHTML, label),
|
||||
innerHTML: escapeForTemplate(allowHTML, label),
|
||||
className: getClassNames(labelClass).join(' '),
|
||||
});
|
||||
div.appendChild(spanLabel);
|
||||
} else {
|
||||
div.innerHTML = unwrapForTemplate(allowHTML, label);
|
||||
div.innerHTML = escapeForTemplate(allowHTML, label);
|
||||
}
|
||||
|
||||
Object.assign(div.dataset, {
|
||||
|
|
@ -245,7 +246,7 @@ const templates = {
|
|||
div.appendChild(
|
||||
Object.assign(document.createElement('div'), {
|
||||
className: getClassNames(groupHeading).join(' '),
|
||||
innerHTML: unwrapForTemplate(allowHTML, label),
|
||||
innerHTML: escapeForTemplate(allowHTML, label),
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
@ -290,19 +291,19 @@ const templates = {
|
|||
|
||||
if (typeof labelClass === 'string' || Array.isArray(labelClass)) {
|
||||
const spanLabel = Object.assign(document.createElement('span'), {
|
||||
innerHTML: unwrapForTemplate(allowHTML, label),
|
||||
innerHTML: escapeForTemplate(allowHTML, label),
|
||||
className: getClassNames(labelClass).join(' '),
|
||||
});
|
||||
spanLabel.setAttribute('aria-describedby', descId);
|
||||
div.appendChild(spanLabel);
|
||||
} else {
|
||||
div.innerHTML = unwrapForTemplate(allowHTML, label);
|
||||
div.innerHTML = escapeForTemplate(allowHTML, label);
|
||||
div.setAttribute('aria-describedby', descId);
|
||||
}
|
||||
|
||||
if (typeof labelDescription === 'string') {
|
||||
const spanDesc = Object.assign(document.createElement('span'), {
|
||||
innerHTML: unwrapForTemplate(allowHTML, labelDescription),
|
||||
innerHTML: escapeForTemplate(allowHTML, labelDescription),
|
||||
id: descId,
|
||||
});
|
||||
spanDesc.classList.add(...getClassNames(description));
|
||||
|
|
@ -360,7 +361,9 @@ const templates = {
|
|||
|
||||
inp.setAttribute('role', 'textbox');
|
||||
inp.setAttribute('aria-autocomplete', 'list');
|
||||
inp.setAttribute('aria-label', placeholderValue);
|
||||
if (placeholderValue) {
|
||||
inp.setAttribute('aria-label', placeholderValue);
|
||||
}
|
||||
|
||||
return inp;
|
||||
},
|
||||
|
|
@ -394,7 +397,7 @@ const templates = {
|
|||
}
|
||||
|
||||
return Object.assign(document.createElement('div'), {
|
||||
innerHTML: unwrapForTemplate(allowHTML, innerText),
|
||||
innerHTML: escapeForTemplate(allowHTML, innerText),
|
||||
className: classes.join(' '),
|
||||
});
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue