@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
465 lines (445 loc) • 15.7 kB
JavaScript
/*!
* CoreUI chip-set.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('./chip.js'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./dom/selector-engine.js'), require('./util/index.js')) :
typeof define === 'function' && define.amd ? define(['./base-component', './chip', './dom/event-handler', './dom/manipulator', './dom/selector-engine', './util/index'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ChipSet = factory(global.BaseComponent, global.Chip, global.EventHandler, global.Manipulator, global.SelectorEngine, global.Index));
})(this, (function (BaseComponent, Chip, EventHandler, Manipulator, SelectorEngine, index_js) { 'use strict';
/**
* --------------------------------------------------------------------------
* CoreUI chip-set.js
* Licensed under MIT (https://github.com/coreui/coreui/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
/**
* Constants
*/
const NAME = 'chip-set';
const DATA_KEY = 'coreui.chip-set';
const EVENT_KEY = `.${DATA_KEY}`;
const DATA_API_KEY = '.data-api';
const EVENT_ADD = 'add';
const EVENT_REMOVE = 'remove';
const EVENT_CHANGE = 'change';
const EVENT_SELECT = 'select';
const EVENT_KEYDOWN = 'keydown';
const EVENT_CHIP_SELECTED = 'selected.coreui.chip';
const EVENT_CHIP_DESELECTED = 'deselected.coreui.chip';
const EVENT_CHIP_REMOVE = 'remove.coreui.chip';
const EVENT_CHIP_REMOVED = 'removed.coreui.chip';
const SELECTOR_DATA_CHIP_SET = '[data-coreui-chip-set]';
const SELECTOR_CHIP = '.chip';
const SELECTOR_CHIP_ACTIVE = `${SELECTOR_CHIP}.active`;
const SELECTOR_CHIP_REMOVE = '.chip-remove';
const SELECTOR_FOCUSABLE_ITEMS = '.chip:not(.disabled)';
const CLASS_NAME_CHIP = 'chip';
const CLASS_NAME_DISABLED = 'disabled';
const SELECTION_MODE_SINGLE = 'single';
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 Default = {
ariaRemoveLabel: 'Remove',
chipClassName: null,
disabled: false,
filter: false,
maxChips: null,
removable: false,
removeIcon: DEFAULT_REMOVE_ICON,
selectable: false,
selectedIcon: DEFAULT_SELECTED_ICON,
selectionMode: 'multiple',
unique: false
};
const DefaultType = {
ariaRemoveLabel: 'string',
chipClassName: '(string|function|null)',
disabled: 'boolean',
filter: 'boolean',
maxChips: '(number|null)',
removable: 'boolean',
removeIcon: 'string',
selectable: 'boolean',
selectedIcon: 'string',
selectionMode: 'string',
unique: 'boolean'
};
/**
* Class definition
*/
class ChipSet extends BaseComponent {
constructor(element, config) {
super(element, config);
this._disabled = this._config.disabled || this._element.classList.contains(CLASS_NAME_DISABLED);
this._pendingFocus = null;
this._chips = [];
this._initChips();
this._addEventListeners();
}
// Getters
static get Default() {
return Default;
}
static get DefaultType() {
return DefaultType;
}
static get NAME() {
return NAME;
}
// Public
add(chip) {
var _this$_input;
if (!this._canModify()) {
return null;
}
const isElement = typeof chip !== 'string';
const value = isElement ? this._getChipValue(chip) : String(chip).trim();
if (!value) {
return null;
}
if (this._config.unique && this._chips.includes(value)) {
return null;
}
if (this._config.maxChips !== null && this._chips.length >= this._config.maxChips) {
return null;
}
const addEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_ADD), {
value,
relatedTarget: (_this$_input = this._input) != null ? _this$_input : null
});
if (addEvent.defaultPrevented) {
return null;
}
const element = isElement ? chip : this._createChip(value);
this._appendChip(element);
this._setupChip(element);
this._chips.push(value);
EventHandler.trigger(this._element, this.constructor.eventName(EVENT_CHANGE), {
values: this.getValues()
});
return element;
}
remove(chipOrValue) {
var _this$_input2;
if (!this._canModify()) {
return false;
}
let chip;
let value;
if (typeof chipOrValue === 'string') {
value = chipOrValue;
chip = this._findChipByValue(value);
} else {
chip = chipOrValue;
value = this._getChipValue(chip);
}
if (!chip || !value) {
return false;
}
const removeEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_REMOVE), {
value,
chip,
relatedTarget: (_this$_input2 = this._input) != null ? _this$_input2 : null
});
if (removeEvent.defaultPrevented) {
return false;
}
const instance = Chip.getInstance(chip);
if (instance) {
instance.remove();
} else {
chip.remove();
this._handleChipRemoval(chip, value);
}
return !chip.isConnected;
}
removeSelected() {
for (const chip of this.getSelected()) {
this.remove(chip);
}
}
clear() {
for (const chip of this._getChipElements()) {
this.remove(chip);
}
}
selectChip(chip) {
var _Chip$getInstance;
if (!this._getChipElements().includes(chip)) {
return;
}
(_Chip$getInstance = Chip.getInstance(chip)) == null || _Chip$getInstance.select();
}
selectAll() {
if (!this._config.selectable) {
return;
}
for (const chip of this._getChipElements()) {
var _Chip$getInstance2;
(_Chip$getInstance2 = Chip.getInstance(chip)) == null || _Chip$getInstance2.select();
}
}
deselectAll() {
for (const chip of this.getSelected()) {
var _Chip$getInstance3;
(_Chip$getInstance3 = Chip.getInstance(chip)) == null || _Chip$getInstance3.deselect();
}
}
clearSelection() {
this.deselectAll();
EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SELECT), {
selected: this.getSelectedValues()
});
}
getSelected() {
return SelectorEngine.find(SELECTOR_CHIP_ACTIVE, this._element);
}
getValues() {
return [...this._chips];
}
getSelectedValues() {
return this.getSelected().map(chip => this._getChipValue(chip));
}
dispose() {
EventHandler.off(this._element, Chip.EVENT_KEY);
super.dispose();
}
// Private
_configAfterMerge(config) {
// Filter chips are selectable by definition.
if (config.filter) {
config.selectable = true;
}
return config;
}
_canModify() {
return !this._disabled;
}
_appendChip(chip) {
this._element.append(chip);
}
_getChipElements() {
return SelectorEngine.find(SELECTOR_CHIP, this._element);
}
_findChipByValue(value) {
return this._getChipElements().find(chip => this._getChipValue(chip) === value);
}
_getChipValue(chip) {
var _clone$textContent;
if (chip.dataset.coreuiChipValue) {
return chip.dataset.coreuiChipValue;
}
const clone = chip.cloneNode(true);
const remove = SelectorEngine.findOne(SELECTOR_CHIP_REMOVE, clone);
if (remove) {
remove.remove();
}
return ((_clone$textContent = clone.textContent) == null ? void 0 : _clone$textContent.trim()) || '';
}
_getFocusableChips() {
return SelectorEngine.find(SELECTOR_FOCUSABLE_ITEMS, this._element);
}
_initChips() {
for (const chip of this._getChipElements()) {
const value = this._getChipValue(chip);
if (value) {
this._chips.push(value);
this._applyChipClassName(chip, value);
}
this._setupChip(chip);
}
}
_setupChip(chip) {
Chip.getOrCreateInstance(chip, this._getChipConfig(chip));
}
_getChipConfig(chip) {
// A chip's own data attributes override the set's options.
return {
ariaRemoveLabel: this._config.ariaRemoveLabel,
disabled: this._disabled,
filter: this._config.filter,
removable: this._config.removable,
removeIcon: this._config.removeIcon,
selectable: this._config.selectable,
selectedIcon: this._config.selectedIcon,
...Manipulator.getDataAttributes(chip)
};
}
_createChip(value) {
const chip = document.createElement('span');
chip.className = CLASS_NAME_CHIP;
chip.dataset.coreuiChipValue = value;
chip.append(document.createTextNode(value));
this._applyChipClassName(chip, value);
return chip;
}
_applyChipClassName(chip, value) {
const className = this._resolveChipClassName(value);
if (!className) {
return;
}
chip.classList.add(...className.split(/\s+/).filter(Boolean));
}
_resolveChipClassName(value) {
const {
chipClassName
} = this._config;
if (!chipClassName) {
return '';
}
if (typeof chipClassName === 'function') {
const resolvedClassName = chipClassName(value);
return typeof resolvedClassName === 'string' ? resolvedClassName : '';
}
return typeof chipClassName === 'string' ? chipClassName : '';
}
_addEventListeners() {
EventHandler.on(this._element, this.constructor.eventName(EVENT_KEYDOWN), SELECTOR_CHIP, event => this._handleKeydown(event));
EventHandler.on(this._element, EVENT_CHIP_SELECTED, SELECTOR_CHIP, event => this._handleSelectionChange(event));
EventHandler.on(this._element, EVENT_CHIP_DESELECTED, SELECTOR_CHIP, event => this._handleSelectionChange(event));
EventHandler.on(this._element, EVENT_CHIP_REMOVE, SELECTOR_CHIP, event => this._handleChipRemove(event));
EventHandler.on(this._element, EVENT_CHIP_REMOVED, SELECTOR_CHIP, event => this._handleChipRemoved(event));
}
_handleKeydown(event) {
const chip = event.target.closest(SELECTOR_CHIP);
if (!chip || chip.classList.contains(CLASS_NAME_DISABLED)) {
return;
}
switch (event.key) {
case 'ArrowLeft':
{
event.preventDefault();
// In RTL the visual direction is mirrored, so ArrowLeft moves to the next chip.
this._focusSibling(chip, index_js.isRTL());
break;
}
case 'ArrowRight':
{
event.preventDefault();
this._focusSibling(chip, !index_js.isRTL());
break;
}
case 'Home':
{
event.preventDefault();
this._navigateToEdge(0);
break;
}
case 'End':
{
event.preventDefault();
this._navigateToEdge(-1);
break;
}
// No default
}
}
_focusSibling(chip, shouldGetNext) {
const chips = this._getFocusableChips();
if (chips.length === 0) {
return;
}
// No cycling: navigation stops at the edges.
const sibling = index_js.getNextActiveElement(chips, chip, shouldGetNext, false);
if (sibling && sibling !== chip) {
sibling.focus();
}
}
_getRemovalNeighbor(chip) {
const chips = this._getFocusableChips();
if (chips.length === 0) {
return null;
}
// Prefer the next chip, fall back to the previous one at the end.
const next = index_js.getNextActiveElement(chips, chip, true, false);
if (next && next !== chip) {
return next;
}
const previous = index_js.getNextActiveElement(chips, chip, false, false);
return previous && previous !== chip ? previous : null;
}
_navigateToEdge(targetIndex) {
var _chips;
const chips = this._getFocusableChips();
(_chips = chips[targetIndex < 0 ? chips.length + targetIndex : targetIndex]) == null || _chips.focus();
}
_handleSelectionChange(event) {
const chip = event.target.closest(SELECTOR_CHIP);
if (this._config.selectionMode === SELECTION_MODE_SINGLE && chip != null && chip.matches(SELECTOR_CHIP_ACTIVE)) {
this._enforceSingleSelection(chip);
}
EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SELECT), {
selected: this.getSelectedValues()
});
}
_enforceSingleSelection(selectedChip) {
for (const chip of this.getSelected()) {
if (chip !== selectedChip) {
var _Chip$getInstance4;
(_Chip$getInstance4 = Chip.getInstance(chip)) == null || _Chip$getInstance4.deselect();
}
}
}
_handleChipRemove(event) {
const chip = event.target.closest(SELECTOR_CHIP);
this._pendingFocus = chip ? this._getRemovalNeighbor(chip) : null;
}
_handleChipRemoved(event) {
var _this$_pendingFocus;
const chip = event.target.closest(SELECTOR_CHIP);
(_this$_pendingFocus = this._pendingFocus) == null || _this$_pendingFocus.focus();
this._pendingFocus = null;
this._handleChipRemoval(chip, this._getChipValue(chip));
}
_handleChipRemoval(chip, value) {
const index = this._chips.indexOf(value);
if (index !== -1) {
this._chips.splice(index, 1);
}
EventHandler.trigger(this._element, this.constructor.eventName(EVENT_CHANGE), {
values: this.getValues()
});
EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SELECT), {
selected: this.getSelectedValues()
});
}
// Static
static chipSetInterface(element, config) {
const data = ChipSet.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 = ChipSet.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_SET)) {
ChipSet.chipSetInterface(element);
}
});
/**
* jQuery
*/
index_js.defineJQueryPlugin(ChipSet);
return ChipSet;
}));
//# sourceMappingURL=chip-set.js.map