diff options
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/theme-studio/README.md | 4 | ||||
| -rw-r--r-- | scripts/theme-studio/app-core.js | 20 | ||||
| -rw-r--r-- | scripts/theme-studio/app.js | 23 | ||||
| -rw-r--r-- | scripts/theme-studio/browser-gates.js | 26 | ||||
| -rw-r--r-- | scripts/theme-studio/styles.css | 8 | ||||
| -rw-r--r-- | scripts/theme-studio/test-app-core.mjs | 32 | ||||
| -rw-r--r-- | scripts/theme-studio/theme-studio.html | 75 |
7 files changed, 158 insertions, 30 deletions
diff --git a/scripts/theme-studio/README.md b/scripts/theme-studio/README.md index 2b4acc6c..e319a324 100644 --- a/scripts/theme-studio/README.md +++ b/scripts/theme-studio/README.md @@ -152,6 +152,10 @@ derived from hue, chroma, lightness, or the visible color name. - **Dropdown order.** Color dropdowns show the default entry, then `bg` and `fg`, then palette columns from left to right. Within each column's dropdown group, colors are ordered lightest to darkest. +- **Dropdown arrows.** Color dropdowns in the syntax, UI, and package face tables + have left/right arrows. Left steps to the next darker color in the selected + color's column; right steps to the next lighter color. The arrows are disabled + for defaults, gone colors, locked rows, and column ends. The standalone ramp generator is gone; fanning a color into a ramp is now "add the color, then raise its column's count." diff --git a/scripts/theme-studio/app-core.js b/scripts/theme-studio/app-core.js index 4f1eee16..af90f13a 100644 --- a/scripts/theme-studio/app-core.js +++ b/scripts/theme-studio/app-core.js @@ -303,5 +303,23 @@ function paletteOptionList(cur,palette,ground){ sortColumns(grouped.columns).forEach(f=>lightestFirstMembers(f.members).forEach(m=>add(m.hex,m.name))); return out; } +function spanNeighborHex(cur,palette,ground,dir){ + if(!cur)return null; + const wanted=(cur||'').toLowerCase(),groups=[],byLight=(a,b)=>oklchOf(a.hex).L-oklchOf(b.hex).L; + const addGroup=members=>{ + const seen=new Set(),g=[]; + members.filter(m=>m&&m.hex).sort(byLight).forEach(m=>{const h=m.hex.toLowerCase();if(!seen.has(h)){seen.add(h);g.push(m);}}); + if(g.length)groups.push(g); + }; + addGroup(groundColumnMembersFromPalette(palette,ground||{})); + sortColumns(columnsFromPalette(palette,ground||{}).columns).forEach(f=>addGroup(f.members)); + for(const g of groups){ + const i=g.findIndex(m=>(m.hex||'').toLowerCase()===wanted); + if(i<0)continue; + const next=g[i+(dir>0?1:-1)]; + return next?next.hex:null; + } + return null; +} -export { nameToHex, normalizePkgFace, buildPkgmap, packagesForExport, mergePackagesInto, effResolve, optList, paletteOptionList, 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, 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 14c94f4b..88ddbaa9 100644 --- a/scripts/theme-studio/app.js +++ b/scripts/theme-studio/app.js @@ -47,12 +47,24 @@ let _ddPop=null; function closeColorDropdown(){if(_ddPop){_ddPop.remove();_ddPop=null;}} document.addEventListener('pointerdown',e=>{if(_ddPop&&!e.target.closest('.cdd')&&!e.target.closest('.cddpop'))closeColorDropdown();}); function mkColorDropdown(options,cur,onPick){ + const wrap=document.createElement('div');wrap.className='cstep'; + const left=document.createElement('button'),right=document.createElement('button'); + left.className='cstepbtn';right.className='cstepbtn';left.type=right.type='button'; + left.textContent='‹';right.textContent='›';left.title='move to next darker color in this column';right.title='move to next lighter color in this column'; const t=document.createElement('div');t.className='cdd';t.tabIndex=0; const nameOf=h=>{const o=options.find(p=>p[0]===h);return o?o[1]:(h||'none');}; + function step(dir){if(wrap.dataset.locked==='1')return;const next=spanNeighborHex(cur,PALETTE,{bg:MAP['bg'],fg:MAP['p']},dir);if(!next)return;cur=next;paint();onPick(next);} + function paintStepButtons(){ + const locked=wrap.dataset.locked==='1'; + left.disabled=locked||!spanNeighborHex(cur,PALETTE,{bg:MAP['bg'],fg:MAP['p']},-1); + right.disabled=locked||!spanNeighborHex(cur,PALETTE,{bg:MAP['bg'],fg:MAP['p']},1); + } function paint(){t.style.background=cur||'#161412';t.style.color=cur?textOn(cur):'#b4b1a2';t.dataset.val=cur||''; - t.innerHTML=`<span class="cddsw" style="background:${cur||'transparent'}"></span>${esc(nameOf(cur))}`;} + t.innerHTML=`<span class="cddsw" style="background:${cur||'transparent'}"></span>${esc(nameOf(cur))}`;paintStepButtons();} paint(); - t.onclick=(e)=>{e.stopPropagation();if(t.dataset.locked==='1')return;if(_ddPop){closeColorDropdown();return;} + left.onclick=e=>{e.stopPropagation();step(-1);}; + right.onclick=e=>{e.stopPropagation();step(1);}; + t.onclick=(e)=>{e.stopPropagation();if(wrap.dataset.locked==='1')return;if(_ddPop){closeColorDropdown();return;} 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':''); row.innerHTML=`<span class="cddsw" style="background:${hex||'transparent'}"></span><span class="cddnm">${esc(name)}</span><span class="cddhx">${hex||''}</span>`; @@ -65,7 +77,10 @@ function mkColorDropdown(options,cur,onPick){ if(r.bottom+ph>window.innerHeight-6)pop.style.top=Math.max(6,r.top-ph-2)+'px'; _ddPop=pop;}; t.setValue=h=>{cur=h;paint();}; - return t;} + wrap.setValue=h=>{cur=h;paint();}; + wrap.syncLocked=paintStepButtons; + wrap.appendChild(left);wrap.appendChild(t);wrap.appendChild(right);paintStepButtons(); + return wrap;} // Standard option list for a swatch dropdown: a "default" entry, then the // palette in the same ground/column order as the palette panel. If cur is set // but no longer in the palette, surface it as a "(gone)" entry so the row still @@ -82,7 +97,7 @@ function mkLockCell(lockKey,els){ lk.title=on?'locked — click to unlock':'click to lock this decision'; (els||[]).forEach(el=>{if(!el)return; if(el.tagName==='SELECT'||el.tagName==='BUTTON'||el.tagName==='INPUT')el.disabled=on; - else{el.dataset.locked=on?'1':'';el.classList.toggle('locked',on);}});} + else{el.dataset.locked=on?'1':'';el.classList.toggle('locked',on);if(el.syncLocked)el.syncLocked();}});} lk.onclick=()=>{LOCKED.has(lockKey)?LOCKED.delete(lockKey):LOCKED.add(lockKey);paint();updateLockToggles();}; paint();td.appendChild(lk);return td;} // B/I/U/S style buttons shared by the UI and package tables. isOn(attr) reads the diff --git a/scripts/theme-studio/browser-gates.js b/scripts/theme-studio/browser-gates.js index 1e47b5ee..cf45c317 100644 --- a/scripts/theme-studio/browser-gates.js +++ b/scripts/theme-studio/browser-gates.js @@ -28,16 +28,26 @@ if(location.hash==='#selftest')pkgSelftest(); if(location.hash==='#locktest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c){ok=false;notes.push(n);}}; LOCKED.clear();buildTable(); {const k=CATS.map(c=>c[0]).filter(k=>k!=='bg'&&k!=='p')[0]; - const tr=document.querySelector('#legbody tr[data-kind="'+k+'"]'),dd=tr.querySelector('.cdd'),lb=tr.querySelector('.lockbtn'); - A(dd.dataset.locked!=='1','syntax-dd-starts-unlocked');lb.click(); - A(dd.dataset.locked==='1'&&dd.classList.contains('locked'),'syntax-lock-disables-dd');lb.click(); - A(dd.dataset.locked!=='1','syntax-unlock-reenables-dd');} + const tr=document.querySelector('#legbody tr[data-kind="'+k+'"]'),step=tr.querySelector('.cstep'),dd=tr.querySelector('.cdd'),lb=tr.querySelector('.lockbtn'); + A(step.dataset.locked!=='1','syntax-dd-starts-unlocked');lb.click(); + A(step.dataset.locked==='1'&&step.classList.contains('locked')&&step.querySelector('.cstepbtn').disabled,'syntax-lock-disables-dd');lb.click(); + A(step.dataset.locked!=='1'&&!step.classList.contains('locked'),'syntax-unlock-reenables-dd');} LOCKED.clear();buildUITable(); {const f=UI_FACES[0][0]; - const tr=document.querySelector('#uibody tr[data-face="'+f+'"]'),dd=tr.querySelector('.cdd'),lb=tr.querySelector('.lockbtn'); - A(dd.dataset.locked!=='1','ui-dd-starts-unlocked');lb.click(); - A(dd.dataset.locked==='1'&&dd.classList.contains('locked'),'ui-lock-disables-dd');lb.click(); - A(dd.dataset.locked!=='1','ui-unlock-reenables-dd');} + const tr=document.querySelector('#uibody tr[data-face="'+f+'"]'),step=tr.querySelector('.cstep'),dd=tr.querySelector('.cdd'),lb=tr.querySelector('.lockbtn'); + A(step.dataset.locked!=='1','ui-dd-starts-unlocked');lb.click(); + A(step.dataset.locked==='1'&&step.classList.contains('locked')&&step.querySelector('.cstepbtn').disabled,'ui-lock-disables-dd');lb.click(); + A(step.dataset.locked!=='1'&&!step.classList.contains('locked'),'ui-unlock-reenables-dd');} + {PALETTE=[['#000000','bg','ground'],['#ffffff','fg','ground'],['#222222','gray-dark','gray'],['#888888','gray-mid','gray'],['#dddddd','gray-light','gray']];MAP['bg']='#000000';MAP['p']='#ffffff';MAP['kw']='#888888';LOCKED.clear();buildTable(); + const tr=document.querySelector('#legbody tr[data-kind="kw"]'),btns=tr.querySelectorAll('.cstepbtn');btns[1].click(); + A(MAP['kw']==='#dddddd'&&tr.querySelector('.cdd').dataset.val==='#dddddd','syntax right arrow steps to lighter color');btns[0].click(); + A(MAP['kw']==='#888888','syntax left arrow steps to darker color');} + {UIMAP['region'].bg='#888888';LOCKED.clear();buildUITable();const tr=document.querySelector('#uibody tr[data-face="region"]'),btns=tr.cells[3].querySelectorAll('.cstepbtn');btns[1].click(); + A(UIMAP['region'].bg==='#dddddd','ui right arrow steps to lighter color');btns[0].click(); + A(UIMAP['region'].bg==='#888888','ui left arrow steps to darker color');} + {const app=curApp(),face=APPS[app].faces[0][0];PKGMAP[app][face].fg='#888888';LOCKED.clear();buildPkgTable();const tr=document.querySelector('#pkgbody tr[data-face="'+face+'"]'),btns=tr.cells[2].querySelectorAll('.cstepbtn');btns[1].click(); + A(PKGMAP[app][face].fg==='#dddddd','pkg right arrow steps to lighter color');btns[0].click(); + A(PKGMAP[app][face].fg==='#888888','pkg left arrow steps to darker color');} {const ks=CATS.map(c=>c[0]).filter(k=>k!=='bg'&&k!=='p'),k1=ks[0],k2=ks[1]; MAP[k1]='#111111';MAP[k2]='#222222';LOCKED.clear();LOCKED.add(k1);clearUnlocked(); A(MAP[k1]==='#111111','syntax-clear-keeps-locked');A(MAP[k2]==='','syntax-clear-wipes-unlocked');} diff --git a/scripts/theme-studio/styles.css b/scripts/theme-studio/styles.css index 21c4030e..78feb4ce 100644 --- a/scripts/theme-studio/styles.css +++ b/scripts/theme-studio/styles.css @@ -8,7 +8,11 @@ table.leg th{cursor:pointer;color:#b4b1a2;text-align:left;padding:4px 12px;user-select:none;font-weight:normal} table.leg th:hover{color:#e8bd30} select.chip{appearance:none;border:1px solid #00000060;border-radius:5px;padding:5px 10px;font:bold 14px monospace;width:160px;cursor:pointer} - .cdd{display:inline-flex;align-items:center;gap:7px;border:1px solid #00000060;border-radius:5px;padding:5px 10px;font:bold 13px monospace;width:160px;cursor:pointer;box-sizing:border-box;overflow:hidden;white-space:nowrap} + .cstep{display:inline-flex;align-items:center;gap:4px} + .cstepbtn{width:22px;height:28px;padding:0;border:1px solid #3a3a3a;border-radius:4px;background:#1f1c19;color:#e8bd30;font:bold 14px monospace;cursor:pointer} + .cstepbtn:disabled{opacity:.28;cursor:default;color:#8f8977} + .cstep.locked .cstepbtn{opacity:.28;cursor:default} + .cdd{display:inline-flex;align-items:center;gap:7px;border:1px solid #00000060;border-radius:5px;padding:5px 10px;font:bold 13px monospace;width:150px;cursor:pointer;box-sizing:border-box;overflow:hidden;white-space:nowrap} .cddsw{display:inline-block;width:13px;height:13px;border-radius:3px;border:1px solid #0007;flex:none} .cddpop{position:fixed;z-index:200;background:#161412;border:1px solid #3a3a3a;border-radius:6px;box-shadow:0 12px 34px #000c;max-height:60vh;overflow:auto;padding:4px} .cddrow{display:flex;align-items:center;gap:9px;padding:4px 9px;cursor:pointer;color:#cdced1;font:12px monospace;border-radius:4px;white-space:nowrap} @@ -16,7 +20,7 @@ .cddrow.sel{outline:1px solid #e8bd30;outline-offset:-1px} .cddrow .cddnm{flex:1} .cddrow .cddhx{opacity:.55;margin-left:10px} - .cdd.locked{cursor:default;opacity:.85;box-shadow:inset 0 0 0 2px #e8bd3088} + .cstep.locked .cdd{cursor:default;opacity:.85;box-shadow:inset 0 0 0 2px #e8bd3088} .lockbtn{background:none;border:none;cursor:pointer;font-size:15px;line-height:1;padding:2px 4px;opacity:.5;filter:grayscale(1)} .lockbtn.on{opacity:1;filter:none} .legctl{margin:0 0 8px;display:flex;gap:8px;align-items:center} diff --git a/scripts/theme-studio/test-app-core.mjs b/scripts/theme-studio/test-app-core.mjs index 48318305..ded3da94 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, optList, paletteOptionList, slugify, + nameToHex, normalizePkgFace, buildPkgmap, packagesForExport, mergePackagesInto, effResolve, optList, paletteOptionList, spanNeighborHex, slugify, clearPalettePlan, deletePaletteColumnPlan, groundColumnMembersFromPalette, areAllLocked, lockToggleLabel, toggleLockSet, } from './app-core.js'; @@ -100,6 +100,36 @@ test('paletteOptionList: Error — a cur outside palette and ground is surfaced assert.deepEqual(list[1], ['#123456', '(gone) #123456']); }); +test('spanNeighborHex: Normal — steps lighter and darker within the current column', () => { + const pal = [ + ['#222222', 'gray-dark', 'gray'], + ['#888888', 'gray-mid', 'gray'], + ['#dddddd', 'gray-light', 'gray'], + ['#330000', 'red-dark', 'red'], + ]; + const ground = { bg: '#000000', fg: '#ffffff' }; + assert.equal(spanNeighborHex('#888888', pal, ground, 1), '#dddddd'); + assert.equal(spanNeighborHex('#888888', pal, ground, -1), '#222222'); + assert.equal(spanNeighborHex('#dddddd', pal, ground, 1), null); + assert.equal(spanNeighborHex('#222222', pal, ground, -1), null); +}); + +test('spanNeighborHex: Normal — ground steps by lightness too', () => { + const pal = [ + ['#ffffff', 'bg', 'ground'], + ['#777777', 'ground+1', 'ground'], + ['#000000', 'fg', 'ground'], + ]; + const ground = { bg: '#ffffff', fg: '#000000' }; + assert.equal(spanNeighborHex('#777777', pal, ground, 1), '#ffffff'); + assert.equal(spanNeighborHex('#777777', pal, ground, -1), '#000000'); +}); + +test('spanNeighborHex: Boundary — default and gone colors cannot step', () => { + assert.equal(spanNeighborHex('', PAL, { bg: '#000000', fg: '#ffffff' }, 1), null); + assert.equal(spanNeighborHex('#123456', PAL, { bg: '#000000', fg: '#ffffff' }, 1), null); +}); + test('clearPalettePlan: Normal — removes non-ground colors and records recoverable names', () => { const plan = clearPalettePlan([ ['#0d0b0a', 'bg', 'ground'], diff --git a/scripts/theme-studio/theme-studio.html b/scripts/theme-studio/theme-studio.html index 51c358b8..ef439993 100644 --- a/scripts/theme-studio/theme-studio.html +++ b/scripts/theme-studio/theme-studio.html @@ -10,7 +10,11 @@ table.leg th{cursor:pointer;color:#b4b1a2;text-align:left;padding:4px 12px;user-select:none;font-weight:normal} table.leg th:hover{color:#e8bd30} select.chip{appearance:none;border:1px solid #00000060;border-radius:5px;padding:5px 10px;font:bold 14px monospace;width:160px;cursor:pointer} - .cdd{display:inline-flex;align-items:center;gap:7px;border:1px solid #00000060;border-radius:5px;padding:5px 10px;font:bold 13px monospace;width:160px;cursor:pointer;box-sizing:border-box;overflow:hidden;white-space:nowrap} + .cstep{display:inline-flex;align-items:center;gap:4px} + .cstepbtn{width:22px;height:28px;padding:0;border:1px solid #3a3a3a;border-radius:4px;background:#1f1c19;color:#e8bd30;font:bold 14px monospace;cursor:pointer} + .cstepbtn:disabled{opacity:.28;cursor:default;color:#8f8977} + .cstep.locked .cstepbtn{opacity:.28;cursor:default} + .cdd{display:inline-flex;align-items:center;gap:7px;border:1px solid #00000060;border-radius:5px;padding:5px 10px;font:bold 13px monospace;width:150px;cursor:pointer;box-sizing:border-box;overflow:hidden;white-space:nowrap} .cddsw{display:inline-block;width:13px;height:13px;border-radius:3px;border:1px solid #0007;flex:none} .cddpop{position:fixed;z-index:200;background:#161412;border:1px solid #3a3a3a;border-radius:6px;box-shadow:0 12px 34px #000c;max-height:60vh;overflow:auto;padding:4px} .cddrow{display:flex;align-items:center;gap:9px;padding:4px 9px;cursor:pointer;color:#cdced1;font:12px monospace;border-radius:4px;white-space:nowrap} @@ -18,7 +22,7 @@ .cddrow.sel{outline:1px solid #e8bd30;outline-offset:-1px} .cddrow .cddnm{flex:1} .cddrow .cddhx{opacity:.55;margin-left:10px} - .cdd.locked{cursor:default;opacity:.85;box-shadow:inset 0 0 0 2px #e8bd3088} + .cstep.locked .cdd{cursor:default;opacity:.85;box-shadow:inset 0 0 0 2px #e8bd3088} .lockbtn{background:none;border:none;cursor:pointer;font-size:15px;line-height:1;padding:2px 4px;opacity:.5;filter:grayscale(1)} .lockbtn.on{opacity:1;filter:none} .legctl{margin:0 0 8px;display:flex;gap:8px;align-items:center} @@ -723,6 +727,24 @@ function paletteOptionList(cur,palette,ground){ sortColumns(grouped.columns).forEach(f=>lightestFirstMembers(f.members).forEach(m=>add(m.hex,m.name))); return out; } +function spanNeighborHex(cur,palette,ground,dir){ + if(!cur)return null; + const wanted=(cur||'').toLowerCase(),groups=[],byLight=(a,b)=>oklchOf(a.hex).L-oklchOf(b.hex).L; + const addGroup=members=>{ + const seen=new Set(),g=[]; + members.filter(m=>m&&m.hex).sort(byLight).forEach(m=>{const h=m.hex.toLowerCase();if(!seen.has(h)){seen.add(h);g.push(m);}}); + if(g.length)groups.push(g); + }; + addGroup(groundColumnMembersFromPalette(palette,ground||{})); + sortColumns(columnsFromPalette(palette,ground||{}).columns).forEach(f=>addGroup(f.members)); + for(const g of groups){ + const i=g.findIndex(m=>(m.hex||'').toLowerCase()===wanted); + if(i<0)continue; + const next=g[i+(dir>0?1:-1)]; + return next?next.hex:null; + } + return null; +} // Pure color/UI-boundary helpers (normHex, ratingColor, textOn), inlined from // app-util.js. textOn uses rl from the colormath core above. // Pure color/UI-boundary helpers: hex-input parsing, the contrast-rating status @@ -770,12 +792,24 @@ let _ddPop=null; function closeColorDropdown(){if(_ddPop){_ddPop.remove();_ddPop=null;}} document.addEventListener('pointerdown',e=>{if(_ddPop&&!e.target.closest('.cdd')&&!e.target.closest('.cddpop'))closeColorDropdown();}); function mkColorDropdown(options,cur,onPick){ + const wrap=document.createElement('div');wrap.className='cstep'; + const left=document.createElement('button'),right=document.createElement('button'); + left.className='cstepbtn';right.className='cstepbtn';left.type=right.type='button'; + left.textContent='‹';right.textContent='›';left.title='move to next darker color in this column';right.title='move to next lighter color in this column'; const t=document.createElement('div');t.className='cdd';t.tabIndex=0; const nameOf=h=>{const o=options.find(p=>p[0]===h);return o?o[1]:(h||'none');}; + function step(dir){if(wrap.dataset.locked==='1')return;const next=spanNeighborHex(cur,PALETTE,{bg:MAP['bg'],fg:MAP['p']},dir);if(!next)return;cur=next;paint();onPick(next);} + function paintStepButtons(){ + const locked=wrap.dataset.locked==='1'; + left.disabled=locked||!spanNeighborHex(cur,PALETTE,{bg:MAP['bg'],fg:MAP['p']},-1); + right.disabled=locked||!spanNeighborHex(cur,PALETTE,{bg:MAP['bg'],fg:MAP['p']},1); + } function paint(){t.style.background=cur||'#161412';t.style.color=cur?textOn(cur):'#b4b1a2';t.dataset.val=cur||''; - t.innerHTML=`<span class="cddsw" style="background:${cur||'transparent'}"></span>${esc(nameOf(cur))}`;} + t.innerHTML=`<span class="cddsw" style="background:${cur||'transparent'}"></span>${esc(nameOf(cur))}`;paintStepButtons();} paint(); - t.onclick=(e)=>{e.stopPropagation();if(t.dataset.locked==='1')return;if(_ddPop){closeColorDropdown();return;} + left.onclick=e=>{e.stopPropagation();step(-1);}; + right.onclick=e=>{e.stopPropagation();step(1);}; + t.onclick=(e)=>{e.stopPropagation();if(wrap.dataset.locked==='1')return;if(_ddPop){closeColorDropdown();return;} 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':''); row.innerHTML=`<span class="cddsw" style="background:${hex||'transparent'}"></span><span class="cddnm">${esc(name)}</span><span class="cddhx">${hex||''}</span>`; @@ -788,7 +822,10 @@ function mkColorDropdown(options,cur,onPick){ if(r.bottom+ph>window.innerHeight-6)pop.style.top=Math.max(6,r.top-ph-2)+'px'; _ddPop=pop;}; t.setValue=h=>{cur=h;paint();}; - return t;} + wrap.setValue=h=>{cur=h;paint();}; + wrap.syncLocked=paintStepButtons; + wrap.appendChild(left);wrap.appendChild(t);wrap.appendChild(right);paintStepButtons(); + return wrap;} // Standard option list for a swatch dropdown: a "default" entry, then the // palette in the same ground/column order as the palette panel. If cur is set // but no longer in the palette, surface it as a "(gone)" entry so the row still @@ -805,7 +842,7 @@ function mkLockCell(lockKey,els){ lk.title=on?'locked — click to unlock':'click to lock this decision'; (els||[]).forEach(el=>{if(!el)return; if(el.tagName==='SELECT'||el.tagName==='BUTTON'||el.tagName==='INPUT')el.disabled=on; - else{el.dataset.locked=on?'1':'';el.classList.toggle('locked',on);}});} + else{el.dataset.locked=on?'1':'';el.classList.toggle('locked',on);if(el.syncLocked)el.syncLocked();}});} lk.onclick=()=>{LOCKED.has(lockKey)?LOCKED.delete(lockKey):LOCKED.add(lockKey);paint();updateLockToggles();}; paint();td.appendChild(lk);return td;} // B/I/U/S style buttons shared by the UI and package tables. isOn(attr) reads the @@ -1800,16 +1837,26 @@ if(location.hash==='#selftest')pkgSelftest(); if(location.hash==='#locktest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c){ok=false;notes.push(n);}}; LOCKED.clear();buildTable(); {const k=CATS.map(c=>c[0]).filter(k=>k!=='bg'&&k!=='p')[0]; - const tr=document.querySelector('#legbody tr[data-kind="'+k+'"]'),dd=tr.querySelector('.cdd'),lb=tr.querySelector('.lockbtn'); - A(dd.dataset.locked!=='1','syntax-dd-starts-unlocked');lb.click(); - A(dd.dataset.locked==='1'&&dd.classList.contains('locked'),'syntax-lock-disables-dd');lb.click(); - A(dd.dataset.locked!=='1','syntax-unlock-reenables-dd');} + const tr=document.querySelector('#legbody tr[data-kind="'+k+'"]'),step=tr.querySelector('.cstep'),dd=tr.querySelector('.cdd'),lb=tr.querySelector('.lockbtn'); + A(step.dataset.locked!=='1','syntax-dd-starts-unlocked');lb.click(); + A(step.dataset.locked==='1'&&step.classList.contains('locked')&&step.querySelector('.cstepbtn').disabled,'syntax-lock-disables-dd');lb.click(); + A(step.dataset.locked!=='1'&&!step.classList.contains('locked'),'syntax-unlock-reenables-dd');} LOCKED.clear();buildUITable(); {const f=UI_FACES[0][0]; - const tr=document.querySelector('#uibody tr[data-face="'+f+'"]'),dd=tr.querySelector('.cdd'),lb=tr.querySelector('.lockbtn'); - A(dd.dataset.locked!=='1','ui-dd-starts-unlocked');lb.click(); - A(dd.dataset.locked==='1'&&dd.classList.contains('locked'),'ui-lock-disables-dd');lb.click(); - A(dd.dataset.locked!=='1','ui-unlock-reenables-dd');} + const tr=document.querySelector('#uibody tr[data-face="'+f+'"]'),step=tr.querySelector('.cstep'),dd=tr.querySelector('.cdd'),lb=tr.querySelector('.lockbtn'); + A(step.dataset.locked!=='1','ui-dd-starts-unlocked');lb.click(); + A(step.dataset.locked==='1'&&step.classList.contains('locked')&&step.querySelector('.cstepbtn').disabled,'ui-lock-disables-dd');lb.click(); + A(step.dataset.locked!=='1'&&!step.classList.contains('locked'),'ui-unlock-reenables-dd');} + {PALETTE=[['#000000','bg','ground'],['#ffffff','fg','ground'],['#222222','gray-dark','gray'],['#888888','gray-mid','gray'],['#dddddd','gray-light','gray']];MAP['bg']='#000000';MAP['p']='#ffffff';MAP['kw']='#888888';LOCKED.clear();buildTable(); + const tr=document.querySelector('#legbody tr[data-kind="kw"]'),btns=tr.querySelectorAll('.cstepbtn');btns[1].click(); + A(MAP['kw']==='#dddddd'&&tr.querySelector('.cdd').dataset.val==='#dddddd','syntax right arrow steps to lighter color');btns[0].click(); + A(MAP['kw']==='#888888','syntax left arrow steps to darker color');} + {UIMAP['region'].bg='#888888';LOCKED.clear();buildUITable();const tr=document.querySelector('#uibody tr[data-face="region"]'),btns=tr.cells[3].querySelectorAll('.cstepbtn');btns[1].click(); + A(UIMAP['region'].bg==='#dddddd','ui right arrow steps to lighter color');btns[0].click(); + A(UIMAP['region'].bg==='#888888','ui left arrow steps to darker color');} + {const app=curApp(),face=APPS[app].faces[0][0];PKGMAP[app][face].fg='#888888';LOCKED.clear();buildPkgTable();const tr=document.querySelector('#pkgbody tr[data-face="'+face+'"]'),btns=tr.cells[2].querySelectorAll('.cstepbtn');btns[1].click(); + A(PKGMAP[app][face].fg==='#dddddd','pkg right arrow steps to lighter color');btns[0].click(); + A(PKGMAP[app][face].fg==='#888888','pkg left arrow steps to darker color');} {const ks=CATS.map(c=>c[0]).filter(k=>k!=='bg'&&k!=='p'),k1=ks[0],k2=ks[1]; MAP[k1]='#111111';MAP[k2]='#222222';LOCKED.clear();LOCKED.add(k1);clearUnlocked(); A(MAP[k1]==='#111111','syntax-clear-keeps-locked');A(MAP[k2]==='','syntax-clear-wipes-unlocked');} |
