const SMART_SELECT_STYLES_CSS = ` :host { display: block; position: relative; width: 100%; font-family: system-ui, sans-serif; } :host ::-webkit-scrollbar { height: 5px; width: 8px; } :host ::-webkit-scrollbar-track { background: transparent; } :host ::-webkit-scrollbar-thumb { background: var(--smart-select-chip-background, #475569); border-radius: 4px; } @media (hover: hover) { :host ::-webkit-scrollbar-thumb:hover { opacity: 0.7; } } /* Стили для скролла */ .trigger::-webkit-scrollbar { height: 4px; } .trigger::-webkit-scrollbar-thumb { background: var(--smart-select-chip-background, #475569); border-radius: 4px; } .wrapper { min-height: 35px; border: 1px solid var(--smart-select-border-color, #ccc); border-radius: var(--smart-select-border-radius-1, 6px); display: flex; padding: 0 6px; flex-direction: column; justify-content: center; background: var(--smart-select-background, #fff); } .trigger { display: flex; gap: 6px; width: 100%; overflow-x: auto; align-items: center; cursor: pointer; } .placeholder-text { color: var(--smart-select-chip-color); opacity: 0.4; pointer-events: none; user-select: none; font-size: var(--smart-select-font-size-2); } .chip { background: var(--smart-select-chip-background, #dbe3ea); color: var(--smart-select-chip-color, #000); padding: 4px 6px; border-radius: var(--smart-select-border-radius-2, 4px); font-size: var(--smart-select-font-size-1, 12px); display: flex; align-items: center; gap: 3px; white-space: nowrap; cursor: pointer; } .chip button { display: flex; position: relative; background: none; border: none; cursor: pointer; padding: 0; width: 20px; height: 15px; justify-content: flex-end; fill: var(--smart-select-chip-fill, #e91e63); } .chip button svg{ width: 15px; height: 15px; } .dropdown { display: none; position: absolute; top: 100%; left: 0; right: 0; background: var(--smart-select-background, #ffffff); border: 1px solid var(--smart-select-border-color, #ccc); border-radius: var(--smart-select-border-radius-1, 6px); z-index: 9999; margin-top: 4px; flex-direction: column; box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1); } :host([open]) .dropdown { display: flex; } input[type="search"] { margin: 10px; padding: 8px 10px; border-radius: var(--smart-select-border-radius-2, 4px); border: 1px solid var(--smart-select-search-border, #ccc); background: transparent; color: var(--smart-select-search-color, #000); } .options-list {max-height: 200px; overflow-y: auto; padding: 10px; display: flex; flex-direction: column; gap: 8px; } ::slotted([slot="option"]) { padding: 8px 12px !important; cursor: pointer; border-radius: var(--smart-select-border-radius-2, 4px); display: block; color: var(--smart-select-option-color, #000); font-size: var(--smart-select-font-size-2, 14px); } @media (hover: hover) { ::slotted([slot="option"]:hover) { background: var(--smart-select-hover-background, #475569); color: var(--smart-select-hover-color, #fff); } } ::slotted([slot="option"].selected) { background: var(--smart-select-selected-background, #dbe3eb) !important; color: var(--smart-select-selected-color, #000) !important; } `; // Створення об'єкта CSSStyleSheet (якщо підтримується) let SmartSelectStyles = null; if (typeof CSSStyleSheet !== 'undefined' && CSSStyleSheet.prototype.replaceSync) { SmartSelectStyles = new CSSStyleSheet(); // (2) Визначення об'єкта тут SmartSelectStyles.replaceSync(SMART_SELECT_STYLES_CSS); } class SmartSelect extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this._selectedValues = new Set(); // Додаємо стилі в конструкторі, якщо це adoptable if (this.shadowRoot.adoptedStyleSheets) { this.shadowRoot.adoptedStyleSheets = [SmartSelectStyles]; } else { // FALLBACK для старих браузерів (наприклад, iOS < 16.4) const style = document.createElement('style'); style.textContent = SMART_SELECT_STYLES_CSS; this.shadowRoot.appendChild(style); } } connectedCallback() { this.render(); const searchInput = this.shadowRoot.querySelector('input[type="search"]'); searchInput.addEventListener('input', (e) => this.handleSearch(e)); const wrapper = this.shadowRoot.querySelector('.wrapper'); wrapper.addEventListener('click', (e) => { if (e.target.closest('button')) return; this.toggleAttribute('open'); if (this.hasAttribute('open')) { setTimeout(() => searchInput.focus(), 50); } }); // Слушаем клики по элементам в слоте this.addEventListener('click', (e) => { const opt = e.target.closest('[slot="option"]'); if (opt) { this.toggleValue(opt.getAttribute('data-value'), opt); } }); // Слушаем изменение слота для инициализации (фикс Safari) this.shadowRoot.querySelector('slot').addEventListener('slotchange', () => { this.syncOptions(); }); this.syncOptions(); } _formatValue(val) { const isNumber = this.getAttribute('type') === 'number'; return isNumber ? Number(val) : String(val); } syncOptions() { const options = Array.from(this.querySelectorAll('[slot="option"]')); options.forEach(opt => { // Используем data-selected вместо атрибута selected if (opt.hasAttribute('data-selected')) { // Зберігаємо вже у потрібному форматі const val = this._formatValue(opt.getAttribute('data-value')); this._selectedValues.add(val); } }); this.updateDisplay(); } handleSearch(e) { const term = e.target.value.toLowerCase().trim(); const options = this.querySelectorAll('[slot="option"]'); options.forEach(opt => { const text = opt.textContent.toLowerCase(); opt.style.display = text.includes(term) ? '' : 'none'; }); } toggleValue(val) { const max = this.getAttribute('max') ? parseInt(this.getAttribute('max')) : null; const formattedVal = this._formatValue(val); if (this._selectedValues.has(formattedVal)) { this._selectedValues.delete(formattedVal); this._click = { value: formattedVal, state: "delete" }; } else { if (max && this._selectedValues.size >= max) return; this._selectedValues.add(formattedVal); this._click = { value: formattedVal, state: "add" }; } this.updateDisplay(); this.dispatchEvent(new Event('change', { bubbles: true })); } updateDisplay() { const container = this.shadowRoot.getElementById('tags'); const placeholder = this.shadowRoot.getElementById('placeholder'); const optionsElements = this.querySelectorAll('[slot="option"]'); // Керування видимістю плейсхолдера if (this._selectedValues.size > 0) { placeholder.style.display = 'none'; } else { placeholder.style.display = 'block'; } container.innerHTML = ''; this._selectedValues.forEach(val => { // Важливо: при пошуку елемента в DOM атрибут data-value завжди рядок, // тому використовуємо == для порівняння числа з рядком const opt = Array.from(optionsElements).find(o => o.getAttribute('data-value') == val); if (opt) { const chip = document.createElement('div'); chip.className = 'chip'; chip.innerHTML = `${opt.textContent} `; chip.querySelector('button').onclick = (e) => { e.stopPropagation(); this.toggleValue(val); }; container.appendChild(chip); } }); const max = this.getAttribute('max') ? parseInt(this.getAttribute('max')) : null; const isFull = max && this._selectedValues.size >= max; optionsElements.forEach(opt => { const optVal = this._formatValue(opt.getAttribute('data-value')); const isSelected = this._selectedValues.has(optVal); opt.classList.toggle('selected', isSelected); // Если лимит исчерпан, делаем невыбранные опции полупрозрачными if (isFull && !isSelected) { opt.style.opacity = '0.5'; opt.style.cursor = 'not-allowed'; } else { opt.style.opacity = '1'; opt.style.cursor = 'pointer'; } }); } get value() { return Array.from(this._selectedValues); } get getClick() { return this._click; } render() { this.shadowRoot.innerHTML = `