Added support for Streamdeck Pedal and updated UI to better fit the Packed UI style
This commit is contained in:
184
pirp/public/assets/combobox.js
Normal file
184
pirp/public/assets/combobox.js
Normal file
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* PirpCombobox - Tippbare Dropdown-Auswahl als Ersatz für <select>
|
||||
*
|
||||
* Verwendung:
|
||||
* new PirpCombobox(containerEl, accountOptions, {
|
||||
* placeholder: '--Konto--',
|
||||
* selectedValue: 'kasse_s|soll||',
|
||||
* onSelect: function(opt) { ... }
|
||||
* });
|
||||
*
|
||||
* accountOptions: { "Gruppenname": [ {value, label, side, rev_id, exp_id}, ... ], ... }
|
||||
*/
|
||||
class PirpCombobox {
|
||||
constructor(container, options, config) {
|
||||
this.container = container;
|
||||
this.options = options; // grouped: { group: [{value, label, side, rev_id, exp_id}] }
|
||||
this.config = config || {};
|
||||
this.flatOptions = [];
|
||||
this.highlighted = -1;
|
||||
|
||||
// Flatten options for filtering
|
||||
for (const [group, opts] of Object.entries(this.options)) {
|
||||
for (const opt of opts) {
|
||||
this.flatOptions.push({ group, ...opt });
|
||||
}
|
||||
}
|
||||
|
||||
this.build();
|
||||
this.bindEvents();
|
||||
|
||||
// Set initial selection
|
||||
if (this.config.selectedValue) {
|
||||
const parts = this.config.selectedValue.split('|');
|
||||
const match = this.flatOptions.find(o =>
|
||||
o.value === parts[0] && o.side === parts[1] &&
|
||||
String(o.rev_id) === String(parts[2]) &&
|
||||
String(o.exp_id) === String(parts[3])
|
||||
);
|
||||
if (match) {
|
||||
this.input.value = match.label;
|
||||
this.selectedOpt = match;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
build() {
|
||||
this.container.classList.add('pirp-combobox');
|
||||
|
||||
this.input = document.createElement('input');
|
||||
this.input.type = 'text';
|
||||
this.input.placeholder = this.config.placeholder || '--Konto--';
|
||||
this.input.autocomplete = 'off';
|
||||
|
||||
this.dropdown = document.createElement('div');
|
||||
this.dropdown.className = 'pirp-combobox-dropdown';
|
||||
this.dropdown.style.display = 'none';
|
||||
|
||||
this.container.appendChild(this.input);
|
||||
this.container.appendChild(this.dropdown);
|
||||
|
||||
this.selectedOpt = null;
|
||||
}
|
||||
|
||||
renderDropdown(filter) {
|
||||
this.dropdown.innerHTML = '';
|
||||
this.highlighted = -1;
|
||||
const f = (filter || '').toLowerCase();
|
||||
let visibleItems = [];
|
||||
let lastGroup = null;
|
||||
|
||||
for (const opt of this.flatOptions) {
|
||||
if (f && opt.label.toLowerCase().indexOf(f) === -1) continue;
|
||||
if (opt.group !== lastGroup) {
|
||||
lastGroup = opt.group;
|
||||
const groupEl = document.createElement('div');
|
||||
groupEl.className = 'pirp-combobox-group';
|
||||
groupEl.textContent = opt.group;
|
||||
this.dropdown.appendChild(groupEl);
|
||||
}
|
||||
const optEl = document.createElement('div');
|
||||
optEl.className = 'pirp-combobox-option';
|
||||
optEl.textContent = opt.label;
|
||||
optEl.dataset.index = visibleItems.length;
|
||||
visibleItems.push({ el: optEl, opt });
|
||||
this.dropdown.appendChild(optEl);
|
||||
}
|
||||
|
||||
this.visibleItems = visibleItems;
|
||||
}
|
||||
|
||||
open() {
|
||||
this.renderDropdown(this.input.value === (this.selectedOpt ? this.selectedOpt.label : '') ? '' : this.input.value);
|
||||
this.dropdown.style.display = 'block';
|
||||
}
|
||||
|
||||
close() {
|
||||
this.dropdown.style.display = 'none';
|
||||
this.highlighted = -1;
|
||||
}
|
||||
|
||||
select(opt) {
|
||||
this.selectedOpt = opt;
|
||||
this.input.value = opt.label;
|
||||
this.close();
|
||||
if (this.config.onSelect) {
|
||||
this.config.onSelect(opt);
|
||||
}
|
||||
}
|
||||
|
||||
highlightIndex(idx) {
|
||||
if (!this.visibleItems || !this.visibleItems.length) return;
|
||||
if (this.highlighted >= 0 && this.highlighted < this.visibleItems.length) {
|
||||
this.visibleItems[this.highlighted].el.classList.remove('highlighted');
|
||||
}
|
||||
this.highlighted = Math.max(0, Math.min(idx, this.visibleItems.length - 1));
|
||||
this.visibleItems[this.highlighted].el.classList.add('highlighted');
|
||||
this.visibleItems[this.highlighted].el.scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.input.addEventListener('focus', () => {
|
||||
this.open();
|
||||
});
|
||||
|
||||
this.input.addEventListener('input', () => {
|
||||
this.renderDropdown(this.input.value);
|
||||
this.dropdown.style.display = 'block';
|
||||
if (this.visibleItems.length > 0) {
|
||||
this.highlightIndex(0);
|
||||
}
|
||||
});
|
||||
|
||||
this.input.addEventListener('keydown', (e) => {
|
||||
if (this.dropdown.style.display === 'none') {
|
||||
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
||||
this.open();
|
||||
e.preventDefault();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
this.highlightIndex(this.highlighted + 1);
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
this.highlightIndex(this.highlighted - 1);
|
||||
} else if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (this.highlighted >= 0 && this.visibleItems[this.highlighted]) {
|
||||
this.select(this.visibleItems[this.highlighted].opt);
|
||||
}
|
||||
} else if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
if (this.selectedOpt) {
|
||||
this.input.value = this.selectedOpt.label;
|
||||
}
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
|
||||
this.input.addEventListener('blur', () => {
|
||||
// Delay to allow click on option to register
|
||||
setTimeout(() => {
|
||||
if (this.selectedOpt) {
|
||||
this.input.value = this.selectedOpt.label;
|
||||
}
|
||||
this.close();
|
||||
}, 150);
|
||||
});
|
||||
|
||||
// Use mousedown to prevent blur race condition
|
||||
this.dropdown.addEventListener('mousedown', (e) => {
|
||||
e.preventDefault(); // Prevent blur
|
||||
const optEl = e.target.closest('.pirp-combobox-option');
|
||||
if (optEl) {
|
||||
const idx = parseInt(optEl.dataset.index);
|
||||
if (this.visibleItems[idx]) {
|
||||
this.select(this.visibleItems[idx].opt);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user