@coreui/coreui
Version:
The most popular front-end framework for developing responsive, mobile-first projects on the web rewritten and maintained by the CoreUI Team
340 lines (322 loc) • 11.2 kB
JavaScript
/*!
* CoreUI chip.js v5.9.0 (https://coreui.io)
* Copyright 2026 The CoreUI Team (https://github.com/orgs/coreui/people)
* Licensed under MIT (https://github.com/coreui/coreui/blob/main/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./dom/selector-engine.js'), require('./util/sanitizer.js'), require('./util/index.js')) :
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/manipulator', './dom/selector-engine', './util/sanitizer', './util/index'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Chip = factory(global.BaseComponent, global.EventHandler, global.Manipulator, global.SelectorEngine, global.Sanitizer, global.Index));
})(this, (function (BaseComponent, EventHandler, Manipulator, SelectorEngine, sanitizer_js, index_js) { 'use strict';
/**
* --------------------------------------------------------------------------
* CoreUI chip.js
* Licensed under MIT (https://github.com/coreui/coreui/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
/**
* Constants
*/
const NAME = 'chip';
const DATA_KEY = 'coreui.chip';
const EVENT_KEY = `.${DATA_KEY}`;
const DATA_API_KEY = '.data-api';
const EVENT_REMOVE = `remove${EVENT_KEY}`;
const EVENT_REMOVED = `removed${EVENT_KEY}`;
const EVENT_SELECT = `select${EVENT_KEY}`;
const EVENT_SELECTED = `selected${EVENT_KEY}`;
const EVENT_DESELECT = `deselect${EVENT_KEY}`;
const EVENT_DESELECTED = `deselected${EVENT_KEY}`;
const SELECTOR_CHIP_CHECK = '.chip-check';
const SELECTOR_CHIP_REMOVE = '.chip-remove';
const SELECTOR_DATA_CHIP = '[data-coreui-chip]';
const CLASS_NAME_CHIP_CHECK = 'chip-check';
const CLASS_NAME_CHIP_CLICKABLE = 'chip-clickable';
const CLASS_NAME_CHIP_REMOVE = 'chip-remove';
const CLASS_NAME_ACTIVE = 'active';
const CLASS_NAME_DISABLED = 'disabled';
const DEFAULT_REMOVE_ICON = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="4" y1="4" x2="12" y2="12"/><line x1="12" y1="4" x2="4" y2="12"/></svg>';
const DEFAULT_SELECTED_ICON = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 512 512" fill="currentColor"><path d="M425.373 89.373 196 318.745 86.627 209.373l-45.254 45.254L196 409.255l274.627-274.628z"/></svg>';
const DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn']);
const Default = {
allowList: sanitizer_js.SVGAllowlist,
ariaRemoveLabel: 'Remove',
disabled: false,
filter: false,
removable: false,
removeIcon: DEFAULT_REMOVE_ICON,
sanitize: true,
sanitizeFn: null,
selectable: false,
selected: false,
selectedIcon: DEFAULT_SELECTED_ICON
};
const DefaultType = {
allowList: 'object',
ariaRemoveLabel: 'string',
disabled: 'boolean',
filter: 'boolean',
removable: 'boolean',
removeIcon: 'string',
sanitize: 'boolean',
sanitizeFn: '(null|function)',
selectable: 'boolean',
selected: 'boolean',
selectedIcon: 'string'
};
/**
* Class definition
*/
class Chip extends BaseComponent {
constructor(element, config) {
super(element, config);
this._disabled = this._config.disabled || this._element.classList.contains(CLASS_NAME_DISABLED);
this._selected = this._config.selected || this._element.classList.contains(CLASS_NAME_ACTIVE);
this._ensureRemoveButton();
this._applyState();
if (this._config.selectable || this._config.removable) {
this._makeFocusable();
}
this._addEventListeners();
}
// Getters
static get Default() {
return Default;
}
static get DefaultType() {
return DefaultType;
}
static get NAME() {
return NAME;
}
// Public
remove() {
const removeEvent = EventHandler.trigger(this._element, EVENT_REMOVE);
if (removeEvent.defaultPrevented) {
return;
}
this._destroyElement();
}
toggle() {
if (!this._config.selectable) {
return;
}
if (this._selected) {
this.deselect();
return;
}
this.select();
}
select() {
if (!this._config.selectable) {
return;
}
if (this._selected) {
return;
}
const selectEvent = EventHandler.trigger(this._element, EVENT_SELECT);
if (selectEvent.defaultPrevented) {
return;
}
this._selected = true;
this._applyState();
EventHandler.trigger(this._element, EVENT_SELECTED);
}
deselect() {
if (!this._config.selectable) {
return;
}
if (!this._selected) {
return;
}
const deselectEvent = EventHandler.trigger(this._element, EVENT_DESELECT);
if (deselectEvent.defaultPrevented) {
return;
}
this._selected = false;
this._applyState();
EventHandler.trigger(this._element, EVENT_DESELECTED);
}
// Private
_configAfterMerge(config) {
// A filter chip is selectable by definition.
if (config.filter) {
config.selectable = true;
}
return config;
}
_addEventListeners() {
EventHandler.on(this._element, 'keydown', event => this._handleKeydown(event));
EventHandler.on(this._element, 'click', event => {
if (this._disabled) {
return;
}
if (event.target.closest(SELECTOR_CHIP_REMOVE)) {
return;
}
this.toggle();
});
EventHandler.on(this._element, 'click', SELECTOR_CHIP_REMOVE, event => {
event.stopPropagation();
this.remove();
});
}
_applyState() {
if (!this._disabled && (this._config.clickable || this._config.selectable)) {
this._element.classList.add(CLASS_NAME_CHIP_CLICKABLE);
}
if (this._disabled) {
this._element.classList.add(CLASS_NAME_DISABLED);
this._element.setAttribute('aria-disabled', 'true');
} else {
this._element.classList.remove(CLASS_NAME_DISABLED);
if (this._element.getAttribute('aria-disabled') === 'true') {
this._element.setAttribute('aria-disabled', 'false');
}
}
if (this._config.selectable) {
this._element.classList.toggle(CLASS_NAME_ACTIVE, this._selected);
this._element.setAttribute('aria-selected', this._selected ? 'true' : 'false');
if (this._config.filter) {
if (this._selected) {
this._ensureCheckIcon();
} else {
var _SelectorEngine$findO;
(_SelectorEngine$findO = SelectorEngine.findOne(SELECTOR_CHIP_CHECK, this._element)) == null || _SelectorEngine$findO.remove();
}
}
} else {
this._element.classList.remove(CLASS_NAME_ACTIVE);
if (this._element.getAttribute('aria-selected') === 'true') {
this._element.setAttribute('aria-selected', 'false');
}
}
}
_ensureCheckIcon() {
if (SelectorEngine.findOne(SELECTOR_CHIP_CHECK, this._element)) {
return;
}
const check = document.createElement('span');
check.className = CLASS_NAME_CHIP_CHECK;
check.setAttribute('aria-hidden', 'true');
check.innerHTML = this._sanitizeIcon(this._config.selectedIcon);
this._element.prepend(check);
}
_createRemoveButton() {
const button = document.createElement('button');
button.type = 'button';
button.className = CLASS_NAME_CHIP_REMOVE;
button.setAttribute('aria-label', this._config.ariaRemoveLabel);
button.setAttribute('tabindex', '-1'); // Not in tab order, chips handle keyboard
button.innerHTML = this._sanitizeIcon(this._config.removeIcon);
return button;
}
_ensureRemoveButton() {
// A disabled chip is not interactive, so it never shows a remove button.
if (!this._config.removable || this._disabled) {
return;
}
if (SelectorEngine.findOne(SELECTOR_CHIP_REMOVE, this._element)) {
return;
}
this._element.append(this._createRemoveButton());
}
_makeFocusable() {
if (this._element.hasAttribute('tabindex') || this._disabled) {
return;
}
this._element.setAttribute('tabindex', '0');
}
_handleKeydown(event) {
const {
key
} = event;
if (this._disabled) {
return;
}
switch (key) {
case 'Enter':
case ' ':
case 'Spacebar':
{
if (!this._config.selectable) {
return;
}
event.preventDefault();
this.toggle();
break;
}
case 'Backspace':
case 'Delete':
{
if (this._config.removable) {
event.preventDefault();
this.remove();
}
break;
}
// No default
}
}
_destroyElement() {
EventHandler.trigger(this._element, EVENT_REMOVED);
this._element.remove();
this.dispose();
}
_sanitizeIcon(icon) {
return this._config.sanitize ? sanitizer_js.sanitizeHtml(icon, this._config.allowList, this._config.sanitizeFn) : icon;
}
_getConfig(config) {
const dataAttributes = Manipulator.getDataAttributes(this._element);
for (const dataAttribute of Object.keys(dataAttributes)) {
if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {
delete dataAttributes[dataAttribute];
}
}
config = {
...dataAttributes,
...(typeof config === 'object' && config ? config : {})
};
config = this._mergeConfigObj(config);
config = this._configAfterMerge(config);
this._typeCheckConfig(config);
return config;
}
// Static
static chipInterface(element, config) {
const data = Chip.getOrCreateInstance(element, config);
if (typeof config === 'string') {
if (typeof data[config] === 'undefined') {
throw new TypeError(`No method named "${config}"`);
}
data[config]();
}
}
static jQueryInterface(config) {
return this.each(function () {
const data = Chip.getOrCreateInstance(this);
if (typeof config !== 'string') {
return;
}
if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {
throw new TypeError(`No method named "${config}"`);
}
data[config](this);
});
}
}
/**
* Data API implementation
*/
EventHandler.on(document, `DOMContentLoaded${EVENT_KEY}${DATA_API_KEY}`, () => {
for (const element of SelectorEngine.find(SELECTOR_DATA_CHIP)) {
Chip.chipInterface(element);
}
});
/**
* jQuery
*/
index_js.defineJQueryPlugin(Chip);
return Chip;
}));
//# sourceMappingURL=chip.js.map