Added support for Streamdeck Pedal and updated UI to better fit the Packed UI style

This commit is contained in:
2026-02-27 22:47:08 +01:00
committed by erik
parent 5a70f775f1
commit 93faae5cc8
1463 changed files with 306917 additions and 0 deletions

View 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);
}
}
});
}
}