Files
Sheep-Service/web/lib/customElements/smartSelect.js

292 lines
11 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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} <button><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg></button>`;
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 = `
<div class="wrapper">
<div class="trigger" id="tags"></div>
<div id="placeholder" class="placeholder-text">
${this.getAttribute('placeholder') || 'Оберіть значення...'}
</div>
</div>
<div class="dropdown">
<input type="search" placeholder="Пошук...">
<div class="options-list">
<slot name="option"></slot>
</div>
</div>
`;
}
}
customElements.define('smart-select', SmartSelect);