Fix mobile color picker popup clipping and locale update for tabs/sections
Color picker popover now uses fixed positioning on small screens to escape the header toolbar overflow container. Section titles, sub-tab labels, and filter placeholders use data-i18n attributes so they update automatically on language change. Display picker title switches to "Select a Device" for engine-owned display lists. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -86,13 +86,13 @@ export class CardSection {
|
||||
<div class="subtab-section${collapsedClass}" data-card-section="${this.sectionKey}">
|
||||
<div class="subtab-section-header cs-header" data-cs-toggle="${this.sectionKey}">
|
||||
<span class="cs-chevron"${chevronStyle}>▶</span>
|
||||
<span class="cs-title">${t(this.titleKey)}</span>
|
||||
<span class="cs-title" data-i18n="${this.titleKey}">${t(this.titleKey)}</span>
|
||||
<span class="cs-count">${count}</span>
|
||||
${this.headerExtra ? `<span class="cs-header-extra">${this.headerExtra}</span>` : ''}
|
||||
<div class="cs-filter-wrap">
|
||||
<input type="text" class="cs-filter" data-cs-filter="${this.sectionKey}"
|
||||
placeholder="${t('section.filter.placeholder')}" autocomplete="off">
|
||||
<button type="button" class="cs-filter-reset" data-cs-filter-reset="${this.sectionKey}" title="${t('section.filter.reset')}">×</button>
|
||||
data-i18n-placeholder="section.filter.placeholder" placeholder="${t('section.filter.placeholder')}" autocomplete="off">
|
||||
<button type="button" class="cs-filter-reset" data-cs-filter-reset="${this.sectionKey}" data-i18n-title="section.filter.reset" title="${t('section.filter.reset')}">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cs-content ${this.gridClass}" data-cs-content="${this.sectionKey}"${contentDisplay}>
|
||||
|
||||
@@ -73,29 +73,56 @@ window._cpToggle = function (id) {
|
||||
// Close all other pickers first (and drop their card elevation)
|
||||
document.querySelectorAll('.color-picker-popover').forEach(p => {
|
||||
if (p.id !== `cp-pop-${id}`) {
|
||||
p.style.display = 'none';
|
||||
const card = p.closest('.card, .template-card');
|
||||
if (card) card.classList.remove('cp-elevated');
|
||||
_cpClosePopover(p);
|
||||
}
|
||||
});
|
||||
const pop = document.getElementById(`cp-pop-${id}`);
|
||||
if (!pop) return;
|
||||
const show = pop.style.display === 'none';
|
||||
pop.style.display = show ? '' : 'none';
|
||||
if (!show) {
|
||||
_cpClosePopover(pop);
|
||||
return;
|
||||
}
|
||||
pop.style.display = '';
|
||||
// Elevate the card so the popover isn't clipped by sibling cards
|
||||
const card = pop.closest('.card, .template-card');
|
||||
if (card) card.classList.toggle('cp-elevated', show);
|
||||
if (show) {
|
||||
// Mark active dot
|
||||
const swatch = document.getElementById(`cp-swatch-${id}`);
|
||||
const cur = swatch ? (_rgbToHex(swatch.style.backgroundColor) || swatch.style.background) : '';
|
||||
pop.querySelectorAll('.color-picker-dot').forEach(d => {
|
||||
const dHex = _rgbToHex(d.style.backgroundColor || d.style.background);
|
||||
d.classList.toggle('active', dHex.toLowerCase() === cur.toLowerCase());
|
||||
});
|
||||
if (card) card.classList.toggle('cp-elevated', true);
|
||||
|
||||
// On small screens, if inside an overflow container (e.g. header toolbar),
|
||||
// switch to fixed positioning so the popover isn't clipped.
|
||||
const wrapper = pop.closest('.color-picker-wrapper');
|
||||
if (wrapper && window.innerWidth <= 600) {
|
||||
const rect = wrapper.getBoundingClientRect();
|
||||
pop.style.position = 'fixed';
|
||||
pop.style.top = (rect.bottom + 4) + 'px';
|
||||
pop.style.right = Math.max(4, window.innerWidth - rect.right) + 'px';
|
||||
pop.style.left = 'auto';
|
||||
pop.classList.add('cp-fixed');
|
||||
}
|
||||
|
||||
// Mark active dot
|
||||
const swatch = document.getElementById(`cp-swatch-${id}`);
|
||||
const cur = swatch ? (_rgbToHex(swatch.style.backgroundColor) || swatch.style.background) : '';
|
||||
pop.querySelectorAll('.color-picker-dot').forEach(d => {
|
||||
const dHex = _rgbToHex(d.style.backgroundColor || d.style.background);
|
||||
d.classList.toggle('active', dHex.toLowerCase() === cur.toLowerCase());
|
||||
});
|
||||
};
|
||||
|
||||
/** Reset popover positioning and close. */
|
||||
function _cpClosePopover(pop) {
|
||||
pop.style.display = 'none';
|
||||
if (pop.classList.contains('cp-fixed')) {
|
||||
pop.classList.remove('cp-fixed');
|
||||
pop.style.position = '';
|
||||
pop.style.top = '';
|
||||
pop.style.right = '';
|
||||
pop.style.left = '';
|
||||
}
|
||||
const card = pop.closest('.card, .template-card');
|
||||
if (card) card.classList.remove('cp-elevated');
|
||||
}
|
||||
|
||||
window._cpPick = function (id, hex) {
|
||||
// Update swatch
|
||||
const swatch = document.getElementById(`cp-swatch-${id}`);
|
||||
@@ -103,16 +130,14 @@ window._cpPick = function (id, hex) {
|
||||
// Update native input
|
||||
const native = document.getElementById(`cp-native-${id}`);
|
||||
if (native) native.value = hex;
|
||||
// Mark active dot
|
||||
// Mark active dot and close
|
||||
const pop = document.getElementById(`cp-pop-${id}`);
|
||||
if (pop) {
|
||||
pop.querySelectorAll('.color-picker-dot').forEach(d => {
|
||||
const dHex = _rgbToHex(d.style.backgroundColor || d.style.background);
|
||||
d.classList.toggle('active', dHex.toLowerCase() === hex.toLowerCase());
|
||||
});
|
||||
pop.style.display = 'none';
|
||||
const card = pop.closest('.card, .template-card');
|
||||
if (card) card.classList.remove('cp-elevated');
|
||||
_cpClosePopover(pop);
|
||||
}
|
||||
// Fire callback
|
||||
if (_callbacks[id]) _callbacks[id](hex);
|
||||
@@ -126,20 +151,14 @@ window._cpReset = function (id, resetColor) {
|
||||
const pop = document.getElementById(`cp-pop-${id}`);
|
||||
if (pop) {
|
||||
pop.querySelectorAll('.color-picker-dot').forEach(d => d.classList.remove('active'));
|
||||
pop.style.display = 'none';
|
||||
const card = pop.closest('.card, .template-card');
|
||||
if (card) card.classList.remove('cp-elevated');
|
||||
_cpClosePopover(pop);
|
||||
}
|
||||
// Fire callback with empty string to signal removal
|
||||
if (_callbacks[id]) _callbacks[id]('');
|
||||
};
|
||||
|
||||
export function closeAllColorPickers() {
|
||||
document.querySelectorAll('.color-picker-popover').forEach(p => {
|
||||
p.style.display = 'none';
|
||||
const card = p.closest('.card, .template-card');
|
||||
if (card) card.classList.remove('cp-elevated');
|
||||
});
|
||||
document.querySelectorAll('.color-picker-popover').forEach(p => _cpClosePopover(p));
|
||||
}
|
||||
|
||||
// Close on outside click
|
||||
|
||||
@@ -25,6 +25,12 @@ export function openDisplayPicker(callback, selectedIndex, engineType = null) {
|
||||
_pickerEngineType = engineType || null;
|
||||
const lightbox = document.getElementById('display-picker-lightbox');
|
||||
|
||||
// Use "Select a Device" title for engines with own display lists (camera, scrcpy, etc.)
|
||||
const titleEl = lightbox.querySelector('.display-picker-title');
|
||||
if (titleEl) {
|
||||
titleEl.textContent = t(_pickerEngineType ? 'displays.picker.title.device' : 'displays.picker.title');
|
||||
}
|
||||
|
||||
lightbox.classList.add('active');
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
|
||||
@@ -1295,8 +1295,8 @@ function renderPictureSourcesList(streams) {
|
||||
];
|
||||
|
||||
const tabBar = `<div class="stream-tab-bar">${tabs.map(tab =>
|
||||
`<button class="stream-tab-btn${tab.key === activeTab ? ' active' : ''}" data-stream-tab="${tab.key}" onclick="switchStreamTab('${tab.key}')">${tab.icon} ${t(tab.titleKey)} <span class="stream-tab-count">${tab.count}</span></button>`
|
||||
).join('')}<span class="cs-expand-collapse-group"><button class="btn-expand-collapse" onclick="expandAllStreamSections()" title="${t('section.expand_all')}">⊞</button><button class="btn-expand-collapse" onclick="collapseAllStreamSections()" title="${t('section.collapse_all')}">⊟</button><button class="tutorial-trigger-btn" onclick="startSourcesTutorial()" title="${t('tour.restart')}">${ICON_HELP}</button></span></div>`;
|
||||
`<button class="stream-tab-btn${tab.key === activeTab ? ' active' : ''}" data-stream-tab="${tab.key}" onclick="switchStreamTab('${tab.key}')">${tab.icon} <span data-i18n="${tab.titleKey}">${t(tab.titleKey)}</span> <span class="stream-tab-count">${tab.count}</span></button>`
|
||||
).join('')}<span class="cs-expand-collapse-group"><button class="btn-expand-collapse" onclick="expandAllStreamSections()" data-i18n-title="section.expand_all" title="${t('section.expand_all')}">⊞</button><button class="btn-expand-collapse" onclick="collapseAllStreamSections()" data-i18n-title="section.collapse_all" title="${t('section.collapse_all')}">⊟</button><button class="tutorial-trigger-btn" onclick="startSourcesTutorial()" data-i18n-title="tour.restart" title="${t('tour.restart')}">${ICON_HELP}</button></span></div>`;
|
||||
|
||||
const renderAudioSourceCard = (src) => {
|
||||
const isMono = src.source_type === 'mono';
|
||||
|
||||
@@ -569,8 +569,8 @@ export async function loadTargetsTab() {
|
||||
];
|
||||
|
||||
const tabBar = `<div class="stream-tab-bar">${subTabs.map(tab =>
|
||||
`<button class="target-sub-tab-btn stream-tab-btn${tab.key === activeSubTab ? ' active' : ''}" data-target-sub-tab="${tab.key}" onclick="switchTargetSubTab('${tab.key}')">${tab.icon} ${t(tab.titleKey)} <span class="stream-tab-count">${tab.count}</span></button>`
|
||||
).join('')}<span class="cs-expand-collapse-group"><button class="btn-expand-collapse" onclick="expandAllTargetSections()" title="${t('section.expand_all')}">⊞</button><button class="btn-expand-collapse" onclick="collapseAllTargetSections()" title="${t('section.collapse_all')}">⊟</button><button class="tutorial-trigger-btn" onclick="startTargetsTutorial()" title="${t('tour.restart')}">${ICON_HELP}</button></span></div>`;
|
||||
`<button class="target-sub-tab-btn stream-tab-btn${tab.key === activeSubTab ? ' active' : ''}" data-target-sub-tab="${tab.key}" onclick="switchTargetSubTab('${tab.key}')">${tab.icon} <span data-i18n="${tab.titleKey}">${t(tab.titleKey)}</span> <span class="stream-tab-count">${tab.count}</span></button>`
|
||||
).join('')}<span class="cs-expand-collapse-group"><button class="btn-expand-collapse" onclick="expandAllTargetSections()" data-i18n-title="section.expand_all" title="${t('section.expand_all')}">⊞</button><button class="btn-expand-collapse" onclick="collapseAllTargetSections()" data-i18n-title="section.collapse_all" title="${t('section.collapse_all')}">⊟</button><button class="tutorial-trigger-btn" onclick="startTargetsTutorial()" data-i18n-title="tour.restart" title="${t('tour.restart')}">${ICON_HELP}</button></span></div>`;
|
||||
|
||||
// Use window.createPatternTemplateCard to avoid circular import
|
||||
const createPatternTemplateCard = window.createPatternTemplateCard || (() => '');
|
||||
|
||||
Reference in New Issue
Block a user