274-transect: copy object name on click; stop clobbering text selection

Row clicks called draw(), which rebuilt the whole tbody — so any attempt
to drag-select text in the roster was wiped on mousedown. Selection was
never blocked, just destroyed. Row clicks now call select(), which swaps
the highlight class and repaints the SVG marks in place.

Object names sit in a bare button with a dotted underline and cursor:copy;
clicking copies the name and flashes "copied" beside it. Propagation is
stopped so copying and row-selecting stay independent.

Clipboard uses navigator.clipboard with a hidden-textarea fallback, since
the vendored copy opens over file:// where the async API is often absent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S6MpcBisZYhX45oDGRRJoz
This commit is contained in:
2026-09-12 20:35:12 +09:00
parent 4fd56a12fd
commit 9daca9e3fb
+59 -11
View File
@@ -83,6 +83,15 @@ footer{margin-top:38px;padding-top:16px;border-top:1px solid var(--rule);
} }
.frow.oaza .chip{font-size:11px} .frow.oaza .chip{font-size:11px}
button.copy{font:inherit;color:inherit;background:none;border:0;padding:0;margin:0;
cursor:copy;text-align:left;border-bottom:1px dotted var(--rule)}
button.copy:hover{border-bottom-color:var(--ink-3)}
button.copy:focus-visible{outline:2px solid var(--ochre);outline-offset:2px}
button.copy.copied{background:color-mix(in srgb,var(--ochre) 22%,transparent);
border-bottom-color:transparent}
button.copy.copied::after{content:' copied';font-size:10px;letter-spacing:.08em;
color:var(--ink-3);text-transform:uppercase}
.tblwrap{user-select:text}
.chip.quick{font-size:10px;padding:3px 8px;letter-spacing:.08em;text-transform:uppercase; .chip.quick{font-size:10px;padding:3px 8px;letter-spacing:.08em;text-transform:uppercase;
color:var(--ink-3);border-style:dashed;margin-right:2px} color:var(--ink-3);border-style:dashed;margin-right:2px}
.chip.quick:hover{color:var(--ink);border-style:solid} .chip.quick:hover{color:var(--ink);border-style:solid}
@@ -126,8 +135,8 @@ north to south, so the road reads nearly straight apart from the swing south thr
<h2>Roster</h2> <h2>Roster</h2>
<div class="tblwrap"><table id="tbl"> <div class="tblwrap"><table id="tbl">
<thead><tr><th style="text-align:right">km</th><th>Name</th><th>Object</th><th>Kana</th><th>Romaji</th> <thead><tr><th style="text-align:right">km</th><th>Object</th><th>Kana</th>
<th>Kind</th><th>大字</th><th style="text-align:right">Built</th><th>Grade</th></tr></thead> <th>Kind</th><th style="text-align:right">Offset (m)</th><th>Address</th></tr></thead>
<tbody id="tbody"></tbody> <tbody id="tbody"></tbody>
</table></div> </table></div>
@@ -207,6 +216,15 @@ document.getElementById('legend').innerHTML=
.map(function(g){return '<span><i style="background:'+FAM[g[0]]+'"></i>'+g[1]+'</span>'}).join(''); .map(function(g){return '<span><i style="background:'+FAM[g[0]]+'"></i>'+g[1]+'</span>'}).join('');
var NS='http://www.w3.org/2000/svg'; var NS='http://www.w3.org/2000/svg';
var rowOf=null, markOf=new Map();
function paintMarks(){
markOf.forEach(function(m,k){
var on=(k===sel);
if(m.c){m.c.setAttribute('r',on?6:m.r0);}
if(m.t){m.t.setAttribute('font-weight',on?'700':'400');}
if(m.g){m.g.setAttribute('r',on?6:3);m.g.setAttribute('opacity',on?1:.85);}
});
}
function el(n,a){var e=document.createElementNS(NS,n);for(var k in a)e.setAttribute(k,a[k]);return e} function el(n,a){var e=document.createElementNS(NS,n);for(var k in a)e.setAttribute(k,a[k]);return e}
function draw(){ function draw(){
var W=Math.max(2600,(K1-K0)*36),H=450,AX=272,PADL=54,PADR=54; var W=Math.max(2600,(K1-K0)*36),H=450,AX=272,PADL=54,PADR=54;
@@ -227,6 +245,7 @@ function draw(){
t.textContent=k;s.appendChild(t); t.textContent=k;s.appendChild(t);
} }
s.appendChild(el('line',{x1:PADL,y1:AX,x2:W-PADR,y2:AX,stroke:'var(--road)','stroke-width':2.5})); s.appendChild(el('line',{x1:PADL,y1:AX,x2:W-PADR,y2:AX,stroke:'var(--road)','stroke-width':2.5}));
markOf=new Map();
var vis=items.filter(function(o){return onK[o.kind]&&onO[o.oaza]}); var vis=items.filter(function(o){return onK[o.kind]&&onO[o.oaza]});
document.getElementById('count').innerHTML='showing <b>'+vis.length+'</b> of '+items.length+' entries'; document.getElementById('count').innerHTML='showing <b>'+vis.length+'</b> of '+items.length+' entries';
var lu=[],ld=[]; var lu=[],ld=[];
@@ -242,7 +261,8 @@ function draw(){
tx.textContent=o.obj; tx.textContent=o.obj;
if(sel===o)tx.setAttribute('font-weight','700'); if(sel===o)tx.setAttribute('font-weight','700');
tx.style.cursor='pointer';r.style.cursor='pointer'; tx.style.cursor='pointer';r.style.cursor='pointer';
var pick=function(){sel=o;draw()};tx.onclick=pick;r.onclick=pick; var pick=function(){select(o)};tx.onclick=pick;r.onclick=pick;
var e0=markOf.get(o)||{};e0.c=r;e0.t=tx;e0.r0=(up?4:3);markOf.set(o,e0);
s.appendChild(r);s.appendChild(tx); s.appendChild(r);s.appendChild(tx);
var km=el('text',{x:px,y:up?y-13:y+16,fill:'var(--ink-3)','font-size':9.5,'text-anchor':'middle'}); var km=el('text',{x:px,y:up?y-13:y+16,fill:'var(--ink-3)','font-size':9.5,'text-anchor':'middle'});
km.textContent=o.km.toFixed(2)+(o.name&&o.name!==o.obj?' \u00b7 '+o.name:''); km.textContent=o.km.toFixed(2)+(o.name&&o.name!==o.obj?' \u00b7 '+o.name:'');
@@ -271,23 +291,51 @@ function draw(){
vis.forEach(function(o){ vis.forEach(function(o){
var cir=el('circle',{cx:gx(o.lon),cy:gy(o.lat),r:(sel===o?6:3),fill:FAM[o._f], var cir=el('circle',{cx:gx(o.lon),cy:gy(o.lat),r:(sel===o?6:3),fill:FAM[o._f],
opacity:(sel===o?1:.85),class:'mark'}); opacity:(sel===o?1:.85),class:'mark'});
cir.style.cursor='pointer';cir.onclick=function(){sel=o;draw()}; cir.style.cursor='pointer';cir.onclick=function(){select(o)};
var e1=markOf.get(o)||{};e1.g=cir;markOf.set(o,e1);
var t=el('title');t.textContent=o.obj+' \u00b7 '+o.km.toFixed(2)+' km \u00b7 '+o.oaza;cir.appendChild(t); var t=el('title');t.textContent=o.obj+' \u00b7 '+o.km.toFixed(2)+' km \u00b7 '+o.oaza;cir.appendChild(t);
g.appendChild(cir); g.appendChild(cir);
}); });
var tb=document.getElementById('tbody');tb.innerHTML=''; var tb=document.getElementById('tbody');tb.innerHTML='';rowOf=new Map();
vis.forEach(function(o){ vis.forEach(function(o){
var tr=document.createElement('tr'); var tr=document.createElement('tr');
if(sel===o)tr.className='hit'; if(sel===o)tr.className='hit';
tr.innerHTML='<td class="km">'+o.km.toFixed(2)+'</td><td class="jp">'+o.name+'</td>'+ tr.innerHTML='<td class="km">'+o.km.toFixed(2)+'</td>'+
'<td class="jp" style="color:var(--ink-2)">'+o.obj+'</td><td class="jp" style="color:var(--ink-3)">'+(o.kana||'')+'</td>'+ '<td class="jp"><button type="button" class="copy" title="Copy name">'+o.obj+'</button></td>'+
'<td class="kind">'+o.kind+'</td><td class="jp">'+o.oaza+'</td>'+ '<td class="jp" style="color:var(--ink-3)">'+(o.kana||'')+'</td>'+
'<td class="num">'+(o.y||'')+'</td><td class="kind">'+(o.g||'')+'</td>'; '<td class="kind">'+o.kind+'</td>'+
tr.style.cursor='pointer';tr.onclick=function(){sel=o;draw()}; '<td class="num">'+Math.round(o.off)+'</td>'+
tb.appendChild(tr); '<td class="jp" style="color:var(--ink-2)">'+(o.addr||'')+'</td>';
tr.querySelector('.copy').onclick=function(e){e.stopPropagation();copyName(o,this)};
tr.onclick=function(){select(o)};
rowOf.set(o,tr);tb.appendChild(tr);
}); });
} }
function select(o){
sel=o;
if(rowOf)rowOf.forEach(function(tr,k){tr.className=(k===o?'hit':'')});
paintMarks();
}
function copyName(o,btn){
var t=o.obj, done=function(){
btn.classList.add('copied');
setTimeout(function(){btn.classList.remove('copied')},900);
};
if(navigator.clipboard&&navigator.clipboard.writeText){
navigator.clipboard.writeText(t).then(done,function(){fallback(t,done)});
} else fallback(t,done);
}
function fallback(t,done){
var ta=document.createElement('textarea');
ta.value=t;ta.setAttribute('readonly','');
ta.style.position='fixed';ta.style.left='-9999px';
document.body.appendChild(ta);ta.select();
try{document.execCommand('copy');done()}catch(e){}
document.body.removeChild(ta);
}
draw(); draw();
})(); })();
</script> </script>