@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
323 lines (305 loc) • 10.1 kB
JavaScript
/*!
* CoreUI chip.js v5.6.1 (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/selector-engine.js'), require('./util/index.js')) :
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/selector-engine', './util/index'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Chip = factory(global.BaseComponent, global.EventHandler, global.SelectorEngine, global.Index));
})(this, (function (BaseComponent, EventHandler, SelectorEngine, 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_REMOVE = '.chip-remove';
const SELECTOR_DATA_CHIP = '[data-coreui-chip]';
const SELECTOR_FOCUSABLE_ITEMS = '.chip:not(.disabled)';
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 = {
ariaRemoveLabel: 'Remove',
disabled: false,
removable: false,
removeIcon: DEFAULT_REMOVE_ICON,
selectable: false,
selected: false
};
const DefaultType = {
ariaRemoveLabel: 'string',
disabled: 'boolean',
removable: 'boolean',
removeIcon: 'string',
selectable: 'boolean',
selected: 'boolean'
};
/**
* 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
_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');
} else {
this._element.classList.remove(CLASS_NAME_ACTIVE);
if (this._element.getAttribute('aria-selected') === 'true') {
this._element.setAttribute('aria-selected', 'false');
}
}
}
_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._config.removeIcon;
return button;
}
_ensureRemoveButton() {
if (!this._config.removable) {
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();
const sibling = this._getFocusableSibling(false) || this._getFocusableSibling(true);
sibling == null || sibling.focus();
this.remove();
}
break;
}
case 'ArrowLeft':
{
event.preventDefault();
const chip = this._getFocusableSibling(false);
chip == null || chip.focus();
break;
}
case 'ArrowRight':
{
event.preventDefault();
const chip = this._getFocusableSibling(true);
chip == null || chip.focus();
break;
}
case 'Home':
{
event.preventDefault();
this._navigateToEdge(0);
break;
}
case 'End':
{
event.preventDefault();
this._navigateToEdge(-1);
break;
}
// No default
}
}
_getFocusableSibling(shouldGetNext) {
const chips = SelectorEngine.find(SELECTOR_FOCUSABLE_ITEMS, this._element.parentElement);
const sibling = index_js.getNextActiveElement(chips, this._element, shouldGetNext, !chips.includes(this._element));
return sibling === this._element ? null : sibling;
}
_navigateToEdge(targetIndex) {
const chips = SelectorEngine.find(SELECTOR_FOCUSABLE_ITEMS, this._element.parentElement);
if (chips.length === 0) {
return;
}
const targetChip = chips.at(targetIndex);
targetChip == null || targetChip.focus();
}
_destroyElement() {
EventHandler.trigger(this._element, EVENT_REMOVED);
this._element.remove();
this.dispose();
}
// 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