Contextmenu bij rechtsklik Nieuw
Eigen contextmenu voor een lijst offertes: rechtsklik, de menutoets, Shift+F10 of op een telefoon lang vasthouden opent het menu op de plek van de cursor of onder de regel, altijd binnen het vlak. Pijltjes, Home, End en Esc werken en de focus gaat terug naar de regel. De acties doen echt iets: verstuurd markeren, dupliceren en verwijderen. Gebruik dit in een webapp of portaal waar mensen veel met dezelfde lijst werken; zet de acties ook ergens zichtbaar, want niet iedereen kent rechtsklikken.
Zo gebruik je het
- Plak de HTML op de plek waar het onderdeel moet komen.
- Zet de CSS in je stylesheet. Alle klassen hebben een eigen voorvoegsel, dus niets botst met je eigen opmaak.
- Voeg de JavaScript toe en roep init() aan met het element waar de HTML in staat.
<!-- Contextmenu bij rechtsklik · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="cmr">
<p class="cmr__hint">Rechtsklik op een offerte, of houd hem op je telefoon even vast.</p>
<ul class="cmr__list" aria-label="Offertes">
<li><button class="cmr__row" type="button" aria-haspopup="menu"><span class="cmr__nm">Dakkapel Van Leeuwen, Gouda</span><span class="cmr__amt">€ 8.450</span><span class="cmr__tag">concept</span></button></li>
<li><button class="cmr__row" type="button" aria-haspopup="menu"><span class="cmr__nm">Badkamer Smits, Woerden</span><span class="cmr__amt">€ 14.900</span><span class="cmr__tag is-sent">verstuurd</span></button></li>
<li><button class="cmr__row" type="button" aria-haspopup="menu"><span class="cmr__nm">Kozijnen De Graaf, Bodegraven</span><span class="cmr__amt">€ 6.275</span><span class="cmr__tag">concept</span></button></li>
<li><button class="cmr__row" type="button" aria-haspopup="menu"><span class="cmr__nm">Schilderwerk Yilmaz, Alphen</span><span class="cmr__amt">€ 3.180</span><span class="cmr__tag">concept</span></button></li>
</ul>
<div class="cmr__menu" role="menu" aria-label="Acties voor offerte" hidden>
<button class="cmr__mi" type="button" role="menuitem" tabindex="-1" data-a="open">Openen</button>
<button class="cmr__mi" type="button" role="menuitem" tabindex="-1" data-a="sent">Markeer als verstuurd</button>
<button class="cmr__mi" type="button" role="menuitem" tabindex="-1" data-a="dup">Dupliceren</button>
<span class="cmr__sep" role="separator"></span>
<button class="cmr__mi cmr__mi--del" type="button" role="menuitem" tabindex="-1" data-a="del">Verwijderen</button>
</div>
<p class="cmr__st" aria-live="polite"></p>
</div>
</div>/* Contextmenu bij rechtsklik · NedDev · eigen werk. */
.cmr{position:absolute;inset:0;display:flex;flex-direction:column;padding:12px 14px 8px;background:#f4f3f1;color:#0a0a0a;font-family:'Switzer',sans-serif;text-align:left;overflow:hidden}
.cmr *,.cmr *::before,.cmr *::after{box-sizing:border-box}
.cmr__hint{margin:0 0 8px;font:400 13px/1.35 'Switzer',sans-serif;color:#57534e}
.cmr__list{list-style:none;margin:0;padding:0;background:#fff;border:1px solid rgba(10,10,10,.12);border-radius:8px;overflow:auto;min-height:0}
.cmr__list li+li{border-top:1px solid rgba(10,10,10,.08)}
.cmr__row{display:flex;align-items:center;gap:10px;width:100%;min-height:36px;margin:0;padding:6px 10px;border:0;background:none;color:inherit;font:400 14px/1.2 'Switzer',sans-serif;text-align:left;cursor:default;-webkit-touch-callout:none;user-select:none}
.cmr__row:hover{background:#f4f3f1}
.cmr__row.is-ctx{background:#0a0a0a;color:#fff}
.cmr__row:focus-visible{outline:2px solid #F5480F;outline-offset:-2px}
.cmr__nm{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cmr__amt{font-variant-numeric:tabular-nums;white-space:nowrap}
.cmr__tag{flex:none;padding:3px 6px;border-radius:4px;background:#f4f3f1;color:#57534e;font:500 12px/1 'Switzer',sans-serif}
.cmr__tag.is-sent{background:#0a0a0a;color:#fff}
.cmr__row.is-ctx .cmr__tag{background:#F5480F;color:#0a0a0a}
.cmr__row.is-new{animation:cmrIn .3s ease}
@keyframes cmrIn{from{background:#fde3d9}to{background:transparent}}
.cmr__menu{position:absolute;z-index:3;min-width:190px;padding:4px;background:#fff;border:1px solid rgba(10,10,10,.14);border-radius:8px;box-shadow:0 14px 30px -12px rgba(10,10,10,.4);transform-origin:var(--ox,0) var(--oy,0);animation:cmrMenu .14s ease-out}
.cmr__menu[hidden]{display:none}
@keyframes cmrMenu{from{opacity:0;transform:scale(.96)}to{opacity:1;transform:none}}
.cmr__mi{display:block;width:100%;min-height:30px;margin:0;padding:6px 10px;border:0;border-radius:4px;background:none;color:#0a0a0a;font:400 14px/1.2 'Switzer',sans-serif;text-align:left;cursor:pointer}
.cmr__mi:hover,.cmr__mi:focus{background:#f4f3f1;outline:none}
.cmr__mi:focus-visible{box-shadow:inset 0 0 0 2px #F5480F}
.cmr__mi--del{color:#b42318}
.cmr__sep{display:block;height:1px;margin:4px 6px;background:rgba(10,10,10,.1)}
.cmr__st{margin:6px 0 0;min-height:17px;font:400 13px/1.3 'Switzer',sans-serif;color:#0a0a0a}
@media (max-width:420px){.cmr__amt{display:none}}
@media (prefers-reduced-motion:reduce){.cmr__menu,.cmr__row.is-new{animation:none}}// Contextmenu bij rechtsklik · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
var w=root.querySelector('.cmr');if(!w)return;
var list=w.querySelector('.cmr__list'),menu=w.querySelector('.cmr__menu'),st=w.querySelector('.cmr__st');
var mis=[].slice.call(menu.querySelectorAll('.cmr__mi')),row=null,lp=null,lpFired=false,openedAt=0;
function nm(r){return r.querySelector('.cmr__nm').textContent;}
function close(back){if(menu.hidden)return;menu.hidden=true;if(row){row.classList.remove('is-ctx');row.removeAttribute('aria-expanded');if(back)row.focus();}}
function open(r,x,y){close(false);row=r;openedAt=Date.now();r.classList.add('is-ctx');r.setAttribute('aria-expanded','true');
var tag=r.querySelector('.cmr__tag');mis[1].textContent=tag.classList.contains('is-sent')?'Terugzetten naar concept':'Markeer als verstuurd';
menu.hidden=false;var wr=w.getBoundingClientRect(),rr=r.getBoundingClientRect(),mw=menu.offsetWidth,mh=menu.offsetHeight;
if(x==null){x=rr.left-wr.left+16;y=rr.bottom-wr.top-4;}
var nx=Math.max(4,Math.min(x,wr.width-mw-4)),ny=Math.max(4,Math.min(y,wr.height-mh-4));
menu.style.left=nx+'px';menu.style.top=ny+'px';menu.style.setProperty('--ox',(x-nx)+'px');menu.style.setProperty('--oy',(y-ny)+'px');mis[0].focus();}
list.addEventListener('contextmenu',function(e){var r=e.target.closest('.cmr__row');if(!r)return;e.preventDefault();clearTimeout(lp);if(lpFired){lpFired=false;return;}if(!menu.hidden&&row===r&&Date.now()-openedAt<400)return;
var wr=w.getBoundingClientRect(),kb=e.button!==2&&e.pointerType!=='touch'&&e.pointerType!=='pen';
if(kb)open(r);else open(r,e.clientX-wr.left,e.clientY-wr.top);});
list.addEventListener('keydown',function(e){var r=e.target.closest('.cmr__row');if(!r)return;if(e.key==='ContextMenu'||(e.shiftKey&&e.key==='F10')){e.preventDefault();open(r);}
else if(e.key==='ArrowDown'||e.key==='ArrowUp'){var rs=[].slice.call(list.querySelectorAll('.cmr__row')),i=rs.indexOf(r),n=rs[i+(e.key==='ArrowDown'?1:-1)];if(n){e.preventDefault();n.focus();}}});
list.addEventListener('click',function(e){var r=e.target.closest('.cmr__row');if(!r||lpFired){lpFired=false;return;}st.textContent=nm(r)+': rechtsklik of Shift+F10 voor acties.';});
list.addEventListener('pointerdown',function(e){if(e.pointerType==='mouse')return;var r=e.target.closest('.cmr__row');if(!r)return;var wr=w.getBoundingClientRect(),x=e.clientX-wr.left,y=e.clientY-wr.top;clearTimeout(lp);lp=setTimeout(function(){lpFired=true;open(r,x,y);},520);});
['pointerup','pointercancel','pointermove'].forEach(function(t){list.addEventListener(t,function(e){if(t==='pointermove'&&e.pointerType==='mouse')return;clearTimeout(lp);});});
menu.addEventListener('keydown',function(e){var i=mis.indexOf(document.activeElement),k=e.key;
if(k==='ArrowDown'){e.preventDefault();mis[(i+1)%mis.length].focus();}else if(k==='ArrowUp'){e.preventDefault();mis[(i-1+mis.length)%mis.length].focus();}
else if(k==='Home'){e.preventDefault();mis[0].focus();}else if(k==='End'){e.preventDefault();mis[mis.length-1].focus();}
else if(k==='Escape'){e.preventDefault();close(true);}else if(k==='Tab'){e.preventDefault();close(true);}});
menu.addEventListener('click',function(e){var b=e.target.closest('.cmr__mi');if(!b||!row)return;var a=b.dataset.a,r=row,n=nm(r);
if(a==='open'){close(true);st.textContent='Offerte '+n+' geopend.';}
else if(a==='sent'){var t=r.querySelector('.cmr__tag'),on=!t.classList.contains('is-sent');t.classList.toggle('is-sent',on);t.textContent=on?'verstuurd':'concept';close(true);st.textContent=n+(on?' staat nu op verstuurd.':' staat weer op concept.');}
else if(a==='dup'){var li=r.parentElement,c=li.cloneNode(true),cr=c.querySelector('.cmr__row');cr.classList.remove('is-ctx');cr.removeAttribute('aria-expanded');cr.querySelector('.cmr__nm').textContent=n+' (kopie)';var ct=cr.querySelector('.cmr__tag');ct.classList.remove('is-sent');ct.textContent='concept';cr.classList.add('is-new');li.after(c);close(false);cr.focus();st.textContent='Kopie gemaakt van '+n+'.';}
else if(a==='del'){var li2=r.parentElement,nx=li2.nextElementSibling||li2.previousElementSibling;close(false);li2.remove();st.textContent='Offerte '+n+' verwijderd.';if(nx)nx.querySelector('.cmr__row').focus();}});
document.addEventListener('pointerdown',function(e){if(!menu.hidden&&!menu.contains(e.target))close(false);});
w.addEventListener('scroll',function(){if(Date.now()-openedAt>400)close(false);},true);
}
init(document.querySelector('.jouw-container'));
Meer uit navigatie & menu's
Iets gezien dat je mooi vindt?
Zet het in je selectie en stuur hem op. Dan bouwen we je site eromheen, in jouw huisstijl.
Neem contact op →