fix: keep source selector stable
This commit is contained in:
@@ -1481,6 +1481,7 @@
|
|||||||
let priceRefreshTimer = null;
|
let priceRefreshTimer = null;
|
||||||
let pendingVoteOptionId = null;
|
let pendingVoteOptionId = null;
|
||||||
let pendingVoteRemove = false;
|
let pendingVoteRemove = false;
|
||||||
|
let pendingStableOptionOrder = null;
|
||||||
|
|
||||||
// ── Init ───────────────────────────────────────────────────
|
// ── Init ───────────────────────────────────────────────────
|
||||||
function init() {
|
function init() {
|
||||||
@@ -1760,6 +1761,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setOptionSource(optionId, sourceKey) {
|
function setOptionSource(optionId, sourceKey) {
|
||||||
|
pendingStableOptionOrder = {
|
||||||
|
tabId: activeTab,
|
||||||
|
optionIds: getRenderedOptionIds(),
|
||||||
|
};
|
||||||
state.priceSourceSelections = {
|
state.priceSourceSelections = {
|
||||||
...state.priceSourceSelections,
|
...state.priceSourceSelections,
|
||||||
[optionId]: normalizeSourceKey(sourceKey),
|
[optionId]: normalizeSourceKey(sourceKey),
|
||||||
@@ -1769,12 +1774,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setSortMode(sortMode) {
|
function setSortMode(sortMode) {
|
||||||
|
pendingStableOptionOrder = null;
|
||||||
const allowedModes = new Set(['vote-desc', 'vote-asc', 'price-asc', 'price-desc']);
|
const allowedModes = new Set(['vote-desc', 'vote-asc', 'price-asc', 'price-desc']);
|
||||||
state.sortMode = allowedModes.has(sortMode) ? sortMode : 'vote-desc';
|
state.sortMode = allowedModes.has(sortMode) ? sortMode : 'vote-desc';
|
||||||
localStorage.setItem('cabo_sort_mode', state.sortMode);
|
localStorage.setItem('cabo_sort_mode', state.sortMode);
|
||||||
render();
|
render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getRenderedOptionIds() {
|
||||||
|
return [...document.querySelectorAll('#optionsList .option-card[data-option-id]')]
|
||||||
|
.map(card => card.dataset.optionId)
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
function getOptionOrderIndexMap() {
|
function getOptionOrderIndexMap() {
|
||||||
return new Map(state.options.map((opt, index) => [opt.id, index]));
|
return new Map(state.options.map((opt, index) => [opt.id, index]));
|
||||||
}
|
}
|
||||||
@@ -1817,6 +1829,24 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyPendingStableOrder(sortedOptions) {
|
||||||
|
if (!pendingStableOptionOrder || pendingStableOptionOrder.tabId !== activeTab) {
|
||||||
|
pendingStableOptionOrder = null;
|
||||||
|
return sortedOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const order = new Map(pendingStableOptionOrder.optionIds.map((optionId, index) => [optionId, index]));
|
||||||
|
pendingStableOptionOrder = null;
|
||||||
|
if (!order.size) return sortedOptions;
|
||||||
|
|
||||||
|
return [...sortedOptions].sort((a, b) => {
|
||||||
|
const aIndex = order.has(a.id) ? order.get(a.id) : Number.MAX_SAFE_INTEGER;
|
||||||
|
const bIndex = order.has(b.id) ? order.get(b.id) : Number.MAX_SAFE_INTEGER;
|
||||||
|
if (aIndex !== bIndex) return aIndex - bIndex;
|
||||||
|
return sortedOptions.indexOf(a) - sortedOptions.indexOf(b);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function escapeHtml(value) {
|
function escapeHtml(value) {
|
||||||
return String(value ?? '')
|
return String(value ?? '')
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, '&')
|
||||||
@@ -2308,7 +2338,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sorted = sortOptionsByMode(opts, getOptionOrderIndexMap());
|
const sorted = applyPendingStableOrder(sortOptionsByMode(opts, getOptionOrderIndexMap()));
|
||||||
const maxVotes = sorted[0] ? getVoteEntries(sorted[0]).length : 1;
|
const maxVotes = sorted[0] ? getVoteEntries(sorted[0]).length : 1;
|
||||||
const budgetBoard = activeTab === 'budget' ? renderBudgetBoard() : '';
|
const budgetBoard = activeTab === 'budget' ? renderBudgetBoard() : '';
|
||||||
|
|
||||||
@@ -2325,7 +2355,7 @@
|
|||||||
: (opt.url ? `<a href="${opt.url}" target="_blank" rel="noopener noreferrer" class="option-link" onclick="event.stopPropagation()">🔗 ${opt.url.replace(/^https?:\/\//, '').split('/')[0]}</a>` : '');
|
: (opt.url ? `<a href="${opt.url}" target="_blank" rel="noopener noreferrer" class="option-link" onclick="event.stopPropagation()">🔗 ${opt.url.replace(/^https?:\/\//, '').split('/')[0]}</a>` : '');
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="option-card${hasVoted ? ' voted' : ''}">
|
<div class="option-card${hasVoted ? ' voted' : ''}" data-option-id="${escapeHtml(opt.id)}">
|
||||||
<div class="option-top">
|
<div class="option-top">
|
||||||
<div class="option-name">${opt.name}</div>
|
<div class="option-name">${opt.name}</div>
|
||||||
<div class="option-votes">${voteEntries.length} vote${voteEntries.length !== 1 ? 's' : ''}</div>
|
<div class="option-votes">${voteEntries.length} vote${voteEntries.length !== 1 ? 's' : ''}</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user