fix(uxp-panel): icon controls as <div role=button> so UXP renders them

ROOT CAUSE: UXP renders native <button> chrome that ignores CSS
`background` and does not draw <svg>-only button content. The original
panel "worked" only because its buttons had TEXT (native buttons render
their text label); the redesign stripped text out, leaving empty grey
pills. Non-<button> elements (the <span> status chips, the <label>
search field + magnifier) render custom backgrounds and SVG children
correctly in this exact UXP -- proof divs are the right vehicle.

FIX: convert the 12 rail/dock/menu icon controls from <button> to
<div role="button" tabindex="0">. Divs have no native `disabled`, so
main.js installs a `disabled` accessor that reflects to a [disabled]
attribute; CSS keys disabled styling off [disabled]. Reverted the
non-working ::before cover back to direct backgrounds (divs honor them).
Text buttons (Connect, slide-panel actions, glyph close) stay <button>.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Zac Gaetano 2026-05-28 22:55:45 -04:00
parent 08a0fb1b60
commit 3430ef823e
3 changed files with 62 additions and 72 deletions

View file

@ -35,9 +35,9 @@
<span class="signal-dot"></span> <span class="signal-dot"></span>
<span id="connected-host" class="connected-host"></span> <span id="connected-host" class="connected-host"></span>
<span id="panel-version" class="panel-version" title="Plugin version"></span> <span id="panel-version" class="panel-version" title="Plugin version"></span>
<button id="menu-btn" class="iconbtn iconbtn--sm" data-tip="More" data-tip-pos="down-left" aria-label="More"> <div id="menu-btn" role="button" tabindex="0" class="iconbtn iconbtn--sm" data-tip="More" data-tip-pos="down-left" aria-label="More">
<svg width="15" height="15" viewBox="0 0 24 24"><path fill="currentColor" d="M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/></svg> <svg width="15" height="15" viewBox="0 0 24 24"><path fill="currentColor" d="M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/></svg>
</button> </div>
<div id="status-menu" class="menu hidden" role="menu"> <div id="status-menu" class="menu hidden" role="menu">
<button id="disconnect-btn" class="menu-item" role="menuitem">Disconnect</button> <button id="disconnect-btn" class="menu-item" role="menuitem">Disconnect</button>
</div> </div>
@ -47,22 +47,22 @@
<!-- Vertical icon rail: views on top, global actions below --> <!-- Vertical icon rail: views on top, global actions below -->
<nav class="rail"> <nav class="rail">
<button id="tab-library" class="rail-btn active" data-tip="Library" data-tip-pos="right"> <div id="tab-library" role="button" tabindex="0" class="rail-btn active" data-tip="Library" data-tip-pos="right">
<svg width="18" height="18" viewBox="0 0 24 24"><rect x="3" y="3" width="7.5" height="7.5" rx="1.5" fill="currentColor"/><rect x="13.5" y="3" width="7.5" height="7.5" rx="1.5" fill="currentColor"/><rect x="3" y="13.5" width="7.5" height="7.5" rx="1.5" fill="currentColor"/><rect x="13.5" y="13.5" width="7.5" height="7.5" rx="1.5" fill="currentColor"/></svg> <svg width="18" height="18" viewBox="0 0 24 24"><rect x="3" y="3" width="7.5" height="7.5" rx="1.5" fill="currentColor"/><rect x="13.5" y="3" width="7.5" height="7.5" rx="1.5" fill="currentColor"/><rect x="3" y="13.5" width="7.5" height="7.5" rx="1.5" fill="currentColor"/><rect x="13.5" y="13.5" width="7.5" height="7.5" rx="1.5" fill="currentColor"/></svg>
</button> </div>
<button id="tab-growing" class="rail-btn" data-tip="Growing" data-tip-pos="right"> <div id="tab-growing" role="button" tabindex="0" class="rail-btn" data-tip="Growing" data-tip-pos="right">
<svg width="18" height="18" viewBox="0 0 24 24"><path fill="currentColor" d="M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z"/></svg> <svg width="18" height="18" viewBox="0 0 24 24"><path fill="currentColor" d="M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z"/></svg>
<span id="growing-count" class="rail-count" style="display:none">0</span> <span id="growing-count" class="rail-count" style="display:none">0</span>
</button> </div>
<span class="rail-spacer"></span> <span class="rail-spacer"></span>
<button id="export-timeline-btn" class="rail-btn rail-btn--accent" data-tip="Export Timeline" data-tip-pos="right"> <div id="export-timeline-btn" role="button" tabindex="0" class="rail-btn rail-btn--accent" data-tip="Export Timeline" data-tip-pos="right">
<svg width="18" height="18" viewBox="0 0 24 24"><path fill="currentColor" d="M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z"/></svg> <svg width="18" height="18" viewBox="0 0 24 24"><path fill="currentColor" d="M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z"/></svg>
</button> </div>
<button id="refresh-btn" class="rail-btn" data-tip="Refresh" data-tip-pos="right"> <div id="refresh-btn" role="button" tabindex="0" class="rail-btn" data-tip="Refresh" data-tip-pos="right">
<svg width="18" height="18" viewBox="0 0 24 24"><path fill="currentColor" d="M17.65 6.35A7.96 7.96 0 0 0 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg> <svg width="18" height="18" viewBox="0 0 24 24"><path fill="currentColor" d="M17.65 6.35A7.96 7.96 0 0 0 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>
</button> </div>
</nav> </nav>
<!-- Main column --> <!-- Main column -->
@ -112,30 +112,30 @@
<!-- Contextual action dock: text buttons replaced by icon buttons <!-- Contextual action dock: text buttons replaced by icon buttons
with hover labels. Per-asset actions left, batch actions right. --> with hover labels. Per-asset actions left, batch actions right. -->
<footer class="dock"> <footer class="dock">
<button id="import-proxy-btn" class="iconbtn iconbtn--primary" data-tip="Import Proxy" data-tip-pos="up" disabled> <div id="import-proxy-btn" role="button" tabindex="0" class="iconbtn iconbtn--primary" data-tip="Import Proxy" data-tip-pos="up" disabled>
<svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/></svg> <svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/></svg>
</button> </div>
<button id="import-hires-btn" class="iconbtn" data-tip="Import Hi-Res" data-tip-pos="up" disabled> <div id="import-hires-btn" role="button" tabindex="0" class="iconbtn" data-tip="Import Hi-Res" data-tip-pos="up" disabled>
<svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M11.99 18.54l-7.37-5.73L3 14.07l9 7 9-7-1.63-1.27-7.38 5.74zM12 16l7.36-5.73L21 9l-9-7-9 7 1.63 1.27L12 16z"/></svg> <svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M11.99 18.54l-7.37-5.73L3 14.07l9 7 9-7-1.63-1.27-7.38 5.74zM12 16l7.36-5.73L21 9l-9-7-9 7 1.63 1.27L12 16z"/></svg>
</button> </div>
<button id="mount-live-btn" class="iconbtn" data-tip="Mount Live" data-tip-pos="up" disabled> <div id="mount-live-btn" role="button" tabindex="0" class="iconbtn" data-tip="Mount Live" data-tip-pos="up" disabled>
<svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M14 12c0 1.11-.89 2-2 2s-2-.89-2-2 .89-2 2-2 2 .89 2 2zm-2-6c-3.31 0-6 2.69-6 6 0 2.22 1.21 4.15 3 5.19l1-1.74A3.98 3.98 0 0 1 8 12c0-2.21 1.79-4 4-4s4 1.79 4 4c0 1.48-.81 2.75-2 3.45l1 1.74c1.79-1.04 3-2.97 3-5.19 0-3.31-2.69-6-6-6zm0-4C7.58 2 4 5.58 4 10c0 2.96 1.61 5.53 4 6.92l1-1.73C7.21 14.07 6 12.18 6 10c0-3.31 2.69-6 6-6s6 2.69 6 6c0 2.18-1.21 4.07-3 5.19l1 1.73c2.39-1.39 4-3.96 4-6.92 0-4.42-3.58-8-8-8z"/></svg> <svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M14 12c0 1.11-.89 2-2 2s-2-.89-2-2 .89-2 2-2 2 .89 2 2zm-2-6c-3.31 0-6 2.69-6 6 0 2.22 1.21 4.15 3 5.19l1-1.74A3.98 3.98 0 0 1 8 12c0-2.21 1.79-4 4-4s4 1.79 4 4c0 1.48-.81 2.75-2 3.45l1 1.74c1.79-1.04 3-2.97 3-5.19 0-3.31-2.69-6-6-6zm0-4C7.58 2 4 5.58 4 10c0 2.96 1.61 5.53 4 6.92l1-1.73C7.21 14.07 6 12.18 6 10c0-3.31 2.69-6 6-6s6 2.69 6 6c0 2.18-1.21 4.07-3 5.19l1 1.73c2.39-1.39 4-3.96 4-6.92 0-4.42-3.58-8-8-8z"/></svg>
</button> </div>
<button id="relink-btn" class="iconbtn" data-tip="Relink Hi-Res" data-tip-pos="up" disabled> <div id="relink-btn" role="button" tabindex="0" class="iconbtn" data-tip="Relink Hi-Res" data-tip-pos="up" disabled>
<svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/></svg> <svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/></svg>
</button> </div>
<span class="dock-sep"></span> <span class="dock-sep"></span>
<button id="import-all-btn" class="iconbtn" data-tip="Import All" data-tip-pos="up-left" disabled> <div id="import-all-btn" role="button" tabindex="0" class="iconbtn" data-tip="Import All" data-tip-pos="up-left" disabled>
<svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M20.54 5.23l-1.39-1.68C18.88 3.21 18.47 3 18 3H6c-.47 0-.88.21-1.16.55L3.46 5.23C3.17 5.57 3 6.02 3 6.5V19c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6.5c0-.48-.17-.93-.46-1.27zM12 17.5L6.5 12H10v-2h4v2h3.5L12 17.5zM5.12 5l.81-1h12l.94 1H5.12z"/></svg> <svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M20.54 5.23l-1.39-1.68C18.88 3.21 18.47 3 18 3H6c-.47 0-.88.21-1.16.55L3.46 5.23C3.17 5.57 3 6.02 3 6.5V19c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6.5c0-.48-.17-.93-.46-1.27zM12 17.5L6.5 12H10v-2h4v2h3.5L12 17.5zM5.12 5l.81-1h12l.94 1H5.12z"/></svg>
</button> </div>
<button id="export-conform-btn" class="iconbtn" data-tip="Export &amp; Conform" data-tip-pos="up-left" disabled> <div id="export-conform-btn" role="button" tabindex="0" class="iconbtn" data-tip="Export &amp; Conform" data-tip-pos="up-left" disabled>
<svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M3 17v2h6v-2H3zM3 5v2h10V5H3zm10 16v-2h8v-2h-8v-2h-2v6h2zM7 9v2H3v2h4v2h2V9H7zm14 4v-2H11v2h10zm-6-4h2V7h4V5h-4V3h-2v6z"/></svg> <svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M3 17v2h6v-2H3zM3 5v2h10V5H3zm10 16v-2h8v-2h-8v-2h-2v6h2zM7 9v2H3v2h4v2h2V9H7zm14 4v-2H11v2h10zm-6-4h2V7h4V5h-4V3h-2v6z"/></svg>
</button> </div>
<button id="fetch-relink-btn" class="iconbtn" data-tip="Fetch &amp; Relink All" data-tip-pos="up-left" disabled> <div id="fetch-relink-btn" role="button" tabindex="0" class="iconbtn" data-tip="Fetch &amp; Relink All" data-tip-pos="up-left" disabled>
<svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46A7.93 7.93 0 0 0 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74A7.93 7.93 0 0 0 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/></svg> <svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46A7.93 7.93 0 0 0 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74A7.93 7.93 0 0 0 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/></svg>
</button> </div>
</footer> </footer>
</div><!-- /main --> </div><!-- /main -->

View file

@ -6,6 +6,28 @@
const $ = id => document.getElementById(id); const $ = id => document.getElementById(id);
// UXP renders native <button> chrome that ignores CSS `background` and does
// not draw <svg>-only button content, so the rail/dock icon controls are
// <div role="button"> (divs render custom backgrounds + SVG children fine).
// Divs have no native `disabled`, so reflect the `.disabled` property the
// rest of the code sets onto a [disabled] attribute the stylesheet keys off.
const ICON_CONTROLS = [
'menu-btn', 'tab-library', 'tab-growing', 'export-timeline-btn', 'refresh-btn',
'import-proxy-btn', 'import-hires-btn', 'mount-live-btn', 'relink-btn',
'import-all-btn', 'export-conform-btn', 'fetch-relink-btn'
];
function enableDivDisabled() {
ICON_CONTROLS.forEach(id => {
const el = document.getElementById(id);
if (!el || Object.getOwnPropertyDescriptor(el, 'disabled')) return;
Object.defineProperty(el, 'disabled', {
configurable: true,
get() { return this.hasAttribute('disabled'); },
set(v) { if (v) this.setAttribute('disabled', ''); else this.removeAttribute('disabled'); }
});
});
}
function syncConnectBtn() { function syncConnectBtn() {
$('connect-btn').disabled = !$('server-url').value.trim() || !$('api-token').value.trim(); $('connect-btn').disabled = !$('server-url').value.trim() || !$('api-token').value.trim();
} }
@ -465,6 +487,7 @@
} }
function init() { function init() {
enableDivDisabled();
wireConnectPane(); wireLibraryPane(); wireConnectPane(); wireLibraryPane();
wireExportPanel(); wireConformPanel(); wireRelinkPanel(); wireExportPanel(); wireConformPanel(); wireRelinkPanel();
showVersion(); showVersion();

View file

@ -255,43 +255,28 @@ input[type="search"]::-webkit-search-cancel-button { display: none; }
padding: 8px 0; padding: 8px 0;
gap: 2px; gap: 2px;
} }
/* Rail/dock controls are <div role="button"> (not <button>): UXP renders
native button chrome that ignores `background` and won't draw SVG-only
content. Divs honor backgrounds and render SVG children correctly. */
.rail-btn { .rail-btn {
position: relative; position: relative;
width: 34px; width: 34px;
height: 34px; height: 34px;
border: none;
/* Leave background transparent UXP native chrome cannot be overridden via
`background` on icon-only buttons. Cover it with a ::before pseudo-element
instead; authored content renders above native chrome. */
background: transparent; background: transparent;
appearance: none;
-webkit-appearance: none;
border-radius: 8px; border-radius: 8px;
color: var(--text-3); color: var(--text-3);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
transition: color var(--t-fast) var(--ease); transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
} }
/* Full-face cover painted above native chrome */ .rail-btn:hover { background: var(--bg-hover); color: var(--text-1); }
.rail-btn::before { .rail-btn.active { background: var(--accent-subtle); color: var(--accent-bright); }
content: ''; .rail-btn svg { width: 18px; height: 18px; }
position: absolute; .rail-btn[disabled] { opacity: 0.32; cursor: default; pointer-events: none; }
inset: 0;
border-radius: 8px;
background: var(--bg-base);
transition: background var(--t-fast) var(--ease);
pointer-events: none;
}
.rail-btn:hover { color: var(--text-1); }
.rail-btn:hover::before { background: var(--bg-hover); }
.rail-btn.active { color: var(--accent-bright); }
.rail-btn.active::before { background: var(--accent-subtle); }
/* SVG and badge sit above the ::before cover */
.rail-btn svg { position: relative; z-index: 1; width: 18px; height: 18px; }
.rail-btn--accent { color: var(--accent-bright); } .rail-btn--accent { color: var(--accent-bright); }
.rail-btn--accent:hover::before { background: var(--accent-subtle); } .rail-btn--accent:hover { background: var(--accent-subtle); }
.rail-spacer { flex: 1; } .rail-spacer { flex: 1; }
.rail-count { .rail-count {
@ -493,40 +478,22 @@ input[type="search"]::-webkit-search-cancel-button { display: none; }
width: 30px; width: 30px;
height: 30px; height: 30px;
flex-shrink: 0; flex-shrink: 0;
border: none;
/* Same ::before strategy as .rail-btn authored pseudo-element covers
UXP's native button chrome which ignores explicit background rules. */
background: transparent; background: transparent;
appearance: none;
-webkit-appearance: none;
border-radius: 7px; border-radius: 7px;
color: var(--text-2); color: var(--text-2);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
transition: color var(--t-fast) var(--ease); transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
} }
.iconbtn::before { .iconbtn:hover { background: var(--bg-hover); color: var(--text-1); }
content: ''; .iconbtn svg { width: 16px; height: 16px; }
position: absolute; .iconbtn[disabled] { opacity: 0.32; cursor: default; pointer-events: none; }
inset: 0;
border-radius: 7px;
background: var(--bg-base);
transition: background var(--t-fast) var(--ease);
pointer-events: none;
}
.iconbtn:hover { color: var(--text-1); }
.iconbtn:hover::before { background: var(--bg-hover); }
/* SVG sits above the ::before cover */
.iconbtn svg { position: relative; z-index: 1; width: 16px; height: 16px; }
.iconbtn:disabled { opacity: 0.32; cursor: default; pointer-events: none; }
.iconbtn--sm { width: 24px; height: 24px; border-radius: 6px; color: var(--text-3); } .iconbtn--sm { width: 24px; height: 24px; border-radius: 6px; color: var(--text-3); }
.iconbtn--sm::before { border-radius: 6px; }
.iconbtn--sm svg { width: 15px; height: 15px; } .iconbtn--sm svg { width: 15px; height: 15px; }
.iconbtn--primary::before { background: var(--accent); } .iconbtn--primary { background: var(--accent); color: #fff; }
.iconbtn--primary { color: #fff; } .iconbtn--primary:not([disabled]):hover { background: var(--accent-hover); }
.iconbtn--primary:not(:disabled):hover::before { background: var(--accent-hover); }
/* ── progress ───────────────────────────────────────────────────── */ /* ── progress ───────────────────────────────────────────────────── */
.progress-row { display: flex; flex-direction: column; gap: 4px; padding: 8px 10px 0; flex-shrink: 0; } .progress-row { display: flex; flex-direction: column; gap: 4px; padding: 8px 10px 0; flex-shrink: 0; }