UNPKG

@coreui/coreui-pro

Version:

The most popular front-end framework for developing responsive, mobile-first projects on the web rewritten by the CoreUI Team

527 lines (509 loc) 17.3 kB
/*! * CoreUI chip-input.js v5.24.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/selector-engine.js'), require('./util/index.js')) : typeof define === 'function' && define.amd ? define(['./base-component', './chip', './dom/event-handler', './dom/selector-engine', './util/index'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ChipInput = factory(global.BaseComponent, global.Chip, global.EventHandler, global.SelectorEngine, global.Index)); })(this, (function (BaseComponent, Chip, EventHandler, SelectorEngine, index_js) { 'use strict'; /** * -------------------------------------------------------------------------- * CoreUI chip-input.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * * This component is a highly modified version of the Bootstrap's chip-input.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ /** * Constants */ const NAME = 'chip-input'; const DATA_KEY = 'coreui.chip-input'; const EVENT_KEY = `.${DATA_KEY}`; const DATA_API_KEY = '.data-api'; const EVENT_ADD = `add${EVENT_KEY}`; const EVENT_REMOVE = `remove${EVENT_KEY}`; const EVENT_CHANGE = `change${EVENT_KEY}`; const EVENT_SELECT = `select${EVENT_KEY}`; const EVENT_INPUT = `input${EVENT_KEY}`; const SELECTOR_DATA_CHIP_INPUT = '[data-coreui-chip-input]'; const SELECTOR_CHIP = '.chip'; const SELECTOR_CHIP_ACTIVE = `${SELECTOR_CHIP}.active`; const SELECTOR_CHIP_INPUT_LABEL = '.chip-input-label'; const SELECTOR_CHIP_REMOVE = '.chip-remove'; const SELECTOR_FOCUSABLE_ITEMS = '.chip:not(.disabled)'; const CLASS_NAME_CHIP = 'chip'; const CLASS_NAME_DISABLED = 'disabled'; const CLASS_NAME_CHIP_INPUT_FIELD = 'chip-input-field'; 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 = { chipClassName: null, createOnBlur: true, disabled: false, id: null, maxChips: null, name: null, placeholder: '', readonly: false, removable: true, removeIcon: DEFAULT_REMOVE_ICON, selectable: false, separator: ',' }; const DefaultType = { chipClassName: '(string|function|null)', createOnBlur: 'boolean', disabled: 'boolean', maxChips: '(number|null)', id: '(string|null)', name: '(string|null)', placeholder: 'string', readonly: 'boolean', removable: 'boolean', removeIcon: 'string', selectable: 'boolean', separator: '(string|null)' }; /** * Class definition */ class ChipInput extends BaseComponent { constructor(element, config) { var _this$_config$id; super(element, config); this._uniqueId = (_this$_config$id = this._config.id) != null ? _this$_config$id : index_js.getUID(`${this.constructor.NAME}`); this._disabled = this._config.disabled || this._element.classList.contains(CLASS_NAME_DISABLED); this._readonly = this._config.readonly; this._chips = []; this._input = SelectorEngine.findOne('input', this._element); this._hiddenInput = null; if (this._input) { this._setInputSize(); } else { this._createInput(); } this._applyInteractionState(); this._initializeExistingChips(); this._createHiddenInput(); this._addEventListeners(); } // Getters static get Default() { return Default; } static get DefaultType() { return DefaultType; } static get NAME() { return NAME; } // Public add(value) { if (this._disabled || this._readonly) { return null; } const trimmedValue = String(value).trim(); if (!trimmedValue) { return null; } // Chips are unique by value if (this._chips.includes(trimmedValue)) { return null; } // Check max chips limit if (this._config.maxChips !== null && this._chips.length >= this._config.maxChips) { return null; } const addEvent = EventHandler.trigger(this._element, EVENT_ADD, { value: trimmedValue, relatedTarget: this._input }); if (addEvent.defaultPrevented) { return null; } const chip = this._createChip(trimmedValue); this._element.insertBefore(chip, this._input); this._chips.push(trimmedValue); const values = this.getValues(); this._hiddenInput.value = values.join(','); EventHandler.trigger(this._element, EVENT_CHANGE, { values }); return chip; } remove(chipOrValue) { if (this._disabled || this._readonly) { 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, EVENT_REMOVE, { value, chip, relatedTarget: this._input }); if (removeEvent.defaultPrevented) { return false; } const chipInstance = Chip.getInstance(chip); if (chipInstance) { chipInstance.remove(); } else { chip.remove(); this._handleChipRemoved(chip, value); } return !chip.isConnected; } removeSelected() { var _this$_input; const chipsToRemove = this._getSelectedChipElements(); for (const chip of chipsToRemove) { this.remove(chip); } (_this$_input = this._input) == null || _this$_input.focus(); } getValues() { return [...this._chips]; } getSelectedValues() { return this._getSelectedChipElements().map(chip => this._getChipValue(chip)); } clear() { const chips = SelectorEngine.find(SELECTOR_CHIP, this._element); for (const chip of chips) { this.remove(chip); } } clearSelection() { for (const chip of this._getSelectedChipElements()) { var _Chip$getInstance; (_Chip$getInstance = Chip.getInstance(chip)) == null || _Chip$getInstance.deselect(); } EventHandler.trigger(this._element, EVENT_SELECT, { selected: [] }); } selectChip(chip) { const chipElements = this._getChipElements(); if (!chipElements.includes(chip)) { return; } const chipInstance = Chip.getInstance(chip); if (!chipInstance) { return; } chipInstance.select(); } focus() { var _this$_input2; (_this$_input2 = this._input) == null || _this$_input2.focus(); } // Private _emitSelectionChange() { EventHandler.trigger(this._element, EVENT_SELECT, { selected: this.getSelectedValues() }); } _getChipElements() { return SelectorEngine.find(SELECTOR_CHIP, this._element); } _getSelectedChipElements() { return SelectorEngine.find(SELECTOR_CHIP_ACTIVE, this._element); } _createInput() { const input = document.createElement('input'); const label = SelectorEngine.findOne(SELECTOR_CHIP_INPUT_LABEL, this._element); const labelFor = label == null ? void 0 : label.getAttribute('for'); const generatedInputId = labelFor || index_js.getUID(`${this.constructor.NAME}-input`); input.type = 'text'; input.className = CLASS_NAME_CHIP_INPUT_FIELD; input.id = generatedInputId; if (this._config.placeholder) { input.placeholder = this._config.placeholder; } if (label && !labelFor) { label.setAttribute('for', generatedInputId); } this._input = input; this._setInputSize(); this._element.append(input); } _createHiddenInput() { const hiddenInput = document.createElement('input'); hiddenInput.type = 'hidden'; hiddenInput.id = this._uniqueId; hiddenInput.name = this._config.name || this._uniqueId; this._element.append(hiddenInput); this._hiddenInput = hiddenInput; this._hiddenInput.value = this.getValues().join(','); } _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); this._setupChip(chip); return chip; } _createChipFromInput() { if (this._disabled || this._readonly) { return; } const value = this._input.value.trim(); if (value) { this.add(value); this._input.value = ''; this._setInputSize(); } } _findChipByValue(value) { const chips = this._getChipElements(); return chips.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()) || ''; } _initializeExistingChips() { const existingChips = SelectorEngine.find(SELECTOR_CHIP, this._element); for (const chip of existingChips) { const value = this._getChipValue(chip); if (value) { this._chips.push(value); this._applyChipClassName(chip, value); this._setupChip(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 : ''; } _setupChip(chip) { Chip.getOrCreateInstance(chip, { ariaRemoveLabel: `Remove ${this._getChipValue(chip)}`, disabled: this._disabled, removable: this._config.removable && !this._readonly && !this._disabled, removeIcon: this._config.removeIcon, selectable: this._config.selectable }); const removeButton = SelectorEngine.findOne(SELECTOR_CHIP_REMOVE, chip); if (removeButton) { removeButton.disabled = this._disabled || this._readonly; } } _applyInteractionState() { this._element.classList.toggle(CLASS_NAME_DISABLED, this._disabled); this._input.disabled = this._disabled; this._input.readOnly = !this._disabled && this._readonly; this._element.setAttribute('aria-disabled', this._disabled ? 'true' : 'false'); this._element.setAttribute('aria-readonly', this._readonly ? 'true' : 'false'); } _addEventListeners() { EventHandler.on(this._element, 'keydown', event => { if (event.target === this._input) { return; } if (event.key.length === 1) { this._input.focus(); } }); EventHandler.on(this._input, 'keydown', event => this._handleInputKeydown(event)); EventHandler.on(this._input, 'input', event => this._handleInput(event)); EventHandler.on(this._input, 'paste', event => this._handlePaste(event)); EventHandler.on(this._input, 'focus', () => this.clearSelection()); if (this._config.createOnBlur) { EventHandler.on(this._input, 'blur', event => { var _event$relatedTarget; // Don't create chip if clicking on a chip if (!((_event$relatedTarget = event.relatedTarget) != null && _event$relatedTarget.closest(SELECTOR_CHIP))) { this._createChipFromInput(); } }); } EventHandler.on(this._element, 'selected.coreui.chip', SELECTOR_CHIP, () => { this._emitSelectionChange(); }); EventHandler.on(this._element, 'deselected.coreui.chip', SELECTOR_CHIP, () => { this._emitSelectionChange(); }); EventHandler.on(this._element, 'remove.coreui.chip', SELECTOR_CHIP, event => { if (this._disabled || this._readonly) { event.preventDefault(); } }); EventHandler.on(this._element, 'removed.coreui.chip', SELECTOR_CHIP, event => { const chip = event.target.closest(SELECTOR_CHIP); if (chip) { this._handleChipRemoved(chip); const focusableChips = SelectorEngine.find(SELECTOR_FOCUSABLE_ITEMS, this._element); if (focusableChips.length > 0) { var _this$_input3; (_this$_input3 = this._input) == null || _this$_input3.focus(); } this._emitSelectionChange(); } }); // Focus input when clicking container background EventHandler.on(this._element, 'click', event => { if (event.target === this._element) { var _this$_input4; (_this$_input4 = this._input) == null || _this$_input4.focus(); } }); } _handleInputKeydown(event) { const { key } = event; switch (key) { case 'Enter': { event.preventDefault(); this._createChipFromInput(); break; } case 'Backspace': case 'Delete': { if (this._input.value === '') { event.preventDefault(); const chips = this._getChipElements(); if (chips.length > 0) { const lastChip = chips.at(-1); lastChip.focus(); } } break; } case 'ArrowLeft': { if (this._input.selectionStart === 0 && this._input.selectionEnd === 0) { event.preventDefault(); const chips = this._getChipElements(); if (chips.length > 0) { const lastChip = chips.at(-1); lastChip.focus(); } } break; } case 'Escape': { this._input.value = ''; this._input.blur(); break; } // No default } } _handleChipRemoved(chip, value = null) { const chipValue = value || this._getChipValue(chip); const valueIndex = this._chips.indexOf(chipValue); if (valueIndex !== -1) { this._chips.splice(valueIndex, 1); } const values = this.getValues(); this._hiddenInput.value = values.join(','); EventHandler.trigger(this._element, EVENT_CHANGE, { values }); } _handleInput(event) { if (this._disabled || this._readonly) { return; } const { value } = event.target; const { separator } = this._config; if (separator && value.includes(separator)) { const parts = value.split(separator); for (const part of parts.slice(0, -1)) { this.add(part.trim()); } this._input.value = parts.at(-1); } this._setInputSize(); EventHandler.trigger(this._element, EVENT_INPUT, { value: this._input.value, relatedTarget: this._input }); } _handlePaste(event) { if (this._disabled || this._readonly) { return; } const { separator } = this._config; if (!separator) { return; } const pastedData = (event.clipboardData || window.clipboardData).getData('text'); if (pastedData.includes(separator)) { event.preventDefault(); const parts = pastedData.split(separator); for (const part of parts) { this.add(part.trim()); } } } _setInputSize() { if (!this._input) { return; } this._input.size = Math.max(this._input.placeholder.length, this._input.value.length) || 1; } } /** * Data API implementation */ EventHandler.on(document, `DOMContentLoaded${EVENT_KEY}${DATA_API_KEY}`, () => { for (const element of SelectorEngine.find(SELECTOR_DATA_CHIP_INPUT)) { ChipInput.getOrCreateInstance(element); } }); return ChipInput; })); //# sourceMappingURL=chip-input.js.map