diff options
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/theme-studio/app-core.js | 13 | ||||
| -rw-r--r-- | scripts/theme-studio/app.js | 2 | ||||
| -rw-r--r-- | scripts/theme-studio/test-app-core.mjs | 20 |
3 files changed, 32 insertions, 3 deletions
diff --git a/scripts/theme-studio/app-core.js b/scripts/theme-studio/app-core.js index 2761031b9..d99e5e364 100644 --- a/scripts/theme-studio/app-core.js +++ b/scripts/theme-studio/app-core.js @@ -78,6 +78,17 @@ function resolveUiAttr(face,attr,uimap){ return null; } +// Text color for a swatch-dropdown popup row. A row showing a real palette color +// sits on the popup's own fixed background, so its name/hex text must inherit the +// popup foreground (return '' to use the CSS color). Coloring it for contrast +// against the swatch instead picks near-black text for a mid/dark swatch, which +// is unreadable on the dark popup. Only the "default" row, filled solid with +// SHOWN, uses a contrast color computed against that fill. +function dropdownRowTextColor(hex,shown,textOnFn){ + if(hex)return ''; + return shown?textOnFn(shown):''; +} + // Standard swatch-dropdown option list: a default entry, then the palette. When // cur is set but no longer in the palette, surface it as a "(gone)" entry first. function optList(cur,palette){const have=cur===''||palette.some(p=>p[0]===cur);return [['','— default —'],...(have?palette:[[cur,'(gone)'],...palette])];} @@ -367,4 +378,4 @@ function spanNeighborHex(cur,palette,ground,dir){ return null; } -export { nameToHex, normalizePkgFace, buildPkgmap, packagesForExport, mergePackagesInto, effResolve, resolveSyntaxFg, resolveUiAttr, optList, paletteOptionList, spanNeighborHex, slugify, ramp, fgSetFor, floor, lMax, COVERED_FACES, columnsFromPalette, regenColumn, rankByLightness, stepRepointPlan, sortColumns, sortColumnMembers, groundRoleOfEntry, groundColumnMembersFromPalette, clearPalettePlan, deletePaletteColumnPlan, areAllLocked, lockToggleLabel, toggleLockSet }; +export { nameToHex, normalizePkgFace, buildPkgmap, packagesForExport, mergePackagesInto, effResolve, resolveSyntaxFg, resolveUiAttr, dropdownRowTextColor, optList, paletteOptionList, spanNeighborHex, slugify, ramp, fgSetFor, floor, lMax, COVERED_FACES, columnsFromPalette, regenColumn, rankByLightness, stepRepointPlan, sortColumns, sortColumnMembers, groundRoleOfEntry, groundColumnMembersFromPalette, clearPalettePlan, deletePaletteColumnPlan, areAllLocked, lockToggleLabel, toggleLockSet }; diff --git a/scripts/theme-studio/app.js b/scripts/theme-studio/app.js index 4b331c555..5056a7be8 100644 --- a/scripts/theme-studio/app.js +++ b/scripts/theme-studio/app.js @@ -83,7 +83,7 @@ function mkColorDropdown(options,cur,onPick,opts={}){ const pop=document.createElement('div');pop.className='cddpop'; for(const [hex,name] of options){const row=document.createElement('div');row.className='cddrow'+(hex===cur?' sel':''); const shown=displayHex(hex),nm=hex?name:(opts.defaultName||name); - row.style.background=hex?'':shown;row.style.color=shown?textOn(shown):''; + row.style.background=hex?'':shown;row.style.color=dropdownRowTextColor(hex,shown,textOn); row.innerHTML=`<span class="cddsw" style="background:${shown||'transparent'}"></span><span class="cddnm">${esc(nm)}</span><span class="cddhx">${hex||shown||''}</span>`; row.onclick=(ev)=>{ev.stopPropagation();cur=hex;paint();closeColorDropdown();onPick(hex);}; pop.appendChild(row);} diff --git a/scripts/theme-studio/test-app-core.mjs b/scripts/theme-studio/test-app-core.mjs index 63e79a95c..e98e511e5 100644 --- a/scripts/theme-studio/test-app-core.mjs +++ b/scripts/theme-studio/test-app-core.mjs @@ -7,7 +7,7 @@ import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { - nameToHex, normalizePkgFace, buildPkgmap, packagesForExport, mergePackagesInto, effResolve, resolveSyntaxFg, resolveUiAttr, optList, paletteOptionList, spanNeighborHex, slugify, + nameToHex, normalizePkgFace, buildPkgmap, packagesForExport, mergePackagesInto, effResolve, resolveSyntaxFg, resolveUiAttr, dropdownRowTextColor, optList, paletteOptionList, spanNeighborHex, slugify, clearPalettePlan, deletePaletteColumnPlan, groundColumnMembersFromPalette, areAllLocked, lockToggleLabel, toggleLockSet, } from './app-core.js'; import { planPaletteGenerator, entriesForGeneratedColumn } from './palette-generator-core.js'; @@ -769,3 +769,21 @@ test('resolveUiAttr: returns null when nothing up the chain is set', () => { test('resolveUiAttr: a face with no inherit and an unset attribute returns null', () => { assert.equal(resolveUiAttr('region', 'bg', { 'region': { bg: null } }), null); }); + +// dropdownRowTextColor: a popup row showing a real palette color inherits the +// popup foreground (legible on the fixed dark popup); only the filled default +// row uses a contrast color against its own background. textOn is stubbed so the +// test asserts the decision, not the contrast math. +const stubTextOn = (h) => (h === '#000000' ? '#fff' : '#000'); +test('dropdownRowTextColor: a real palette color inherits the popup fg (empty)', () => { + assert.equal(dropdownRowTextColor('#2a3a5a', '#2a3a5a', stubTextOn), ''); +}); +test('dropdownRowTextColor: a dark swatch still inherits (regression: blues were unreadable)', () => { + assert.equal(dropdownRowTextColor('#000000', '#000000', stubTextOn), ''); +}); +test('dropdownRowTextColor: the filled default row contrasts against its fill', () => { + assert.equal(dropdownRowTextColor('', '#cdced1', stubTextOn), '#000'); +}); +test('dropdownRowTextColor: a default row with no fill inherits (empty)', () => { + assert.equal(dropdownRowTextColor('', '', stubTextOn), ''); +}); |
