All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 36s
Bij veel widgets nam de instellingenstrook te veel hoogte in, vooral bij een smal widgetvenster waar de rij naar meerdere regels uitwaaierde. Nieuw gedeeld patroon: besturing die voortdurend nodig is (kleur, niveau, woordbron) blijft in één compacte rij zichtbaar; besturing die maar zelden wordt aangepast (thema, geluid aan/uit, invoerwijze, bereik) gaat achter een inklapbaar ⚙-paneel. Dit patroon bestond al voor klok/geld/rekengetallen/ wereld (learningShell() in learning.js) - nu als widgetGear()/gearField() in core.js beschikbaar voor de rest van de app, met dezelfde .edu-settings- opmaak zodat het overal hetzelfde oogt en werkt. - kleurplaten: twee volle rijen (thema/plaat + kwast/emmer/gum/kleur/dikte/ hulp/reset/klaar) teruggebracht naar één vaste rij (gereedschap, kleur, dikte, klaar) plus het ingeklapte paneel (thema, plaat, kleurhulp, reset). Losstaand: coloring.css gebruikte @media(max-width:620px) in plaats van de @container widget-conventie die de rest van de app gebruikt, waardoor die regels nooit afgingen bij het verkleinen van het widgetvenster zelf - nu gecorrigeerd. - letterblokken: geluid, invoerwijze en aantal kaarten in het paneel; niveau en woordbron blijven vast. - rekenwidgets (optellen/aftrekken/keer/delen/procenten): geluid en invoerwijze in het paneel; niveau blijft vast. - ballonnenwoord: geluid in het paneel. - flitswoorden: flitstijd in het paneel. - getallenlijn: het hele bereik-instelblok (van/tot/stap/nieuw) in het paneel - dat stond toch al uit voor leerlingen met een beheerd niveau. - zinsbouwer: het rijtje voor eigen woorden toevoegen in het paneel; de speelknoppen (verrassing/ongedaan/wissen/voorlezen) blijven vast. - timer: geluid en weergavevorm in het paneel; starttijden en start/reset blijven vast. - geluidsmeter: de twee drempelschuifjes plus de privacynotitie in het paneel. Widgets die dit patroon al hadden (klok, geld, breuken, plaatswaarde, woordsoorten, bomen/vogels/planten, topografie, stemming, stemming-poll, dagplanning) of waar de rij al compact genoeg was (schrijfblad, hakken en plakken, dobbelstenen, notities, naamtrekker) zijn ongewijzigd gelaten. Geverifieerd door elke aangepaste widget live via de echte makeWidget()/ REGISTRY in een headless browser te openen, het paneel open en dicht te klappen en te controleren op consolefouten - ook op de minimale widgetbreedte (coloring, minW 390). 202 tests groen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011YErxhic5w9K57zmt8Q1uS
74 lines
4.8 KiB
JavaScript
74 lines
4.8 KiB
JavaScript
/* widget: interactieve getallenlijn */
|
||
function mountNumberline(root, initState){
|
||
const S=Object.assign({level:1,yearGroup:null,managedLearningLevel:false,min:0,max:20,step:1,value:0,target:10,score:0,streak:0,solved:false},initState||{});
|
||
const rules=mathTaskRules(S.yearGroup,S.level);
|
||
if(S.managedLearningLevel&&rules){S.min=rules.numberlineMin;S.max=rules.numberlineMax;S.step=rules.numberlineStep;S.value=S.min;S.target=S.min+S.step;}
|
||
root.innerHTML=`
|
||
<div class="nlw">
|
||
<details class="edu-settings widget-gear"><summary>⚙ <span class="nlw-settings-label"></span></summary>
|
||
<div class="nlw-settings edu-controls">
|
||
<label><span class="nlw-from-label"></span><input class="nlw-min" type="number" min="-1000000" max="999999"></label>
|
||
<label><span class="nlw-to-label"></span><input class="nlw-max" type="number" min="-999999" max="1000000"></label>
|
||
<label><span class="nlw-step-label"></span><select class="nlw-step"><option>1</option><option>2</option><option>5</option><option>10</option></select></label>
|
||
<button class="tbtn ghost nlw-new" type="button"></button>
|
||
</div></details>
|
||
<div class="nlw-playtop"><div class="nlw-task"></div><output class="nlw-score"></output></div>
|
||
<div class="nlw-scroll"><div class="nlw-line"></div></div>
|
||
<div class="nlw-actions">
|
||
<button class="tbtn ghost nlw-minus" type="button">−</button>
|
||
<output class="nlw-value"></output>
|
||
<button class="tbtn ghost nlw-plus" type="button">+</button>
|
||
<button class="tbtn nlw-check" type="button"></button>
|
||
</div>
|
||
<div class="nlw-feedback" aria-live="polite"></div>
|
||
</div>`;
|
||
const q=s=>root.querySelector(s), minEl=q('.nlw-min'), maxEl=q('.nlw-max'), stepEl=q('.nlw-step'),
|
||
line=q('.nlw-line'), valueEl=q('.nlw-value'), task=q('.nlw-task'), feedback=q('.nlw-feedback'),score=q('.nlw-score');
|
||
if(![...stepEl.options].some(o=>+o.value===S.step))stepEl.add(new Option(S.step,S.step));
|
||
minEl.value=S.min; maxEl.value=S.max; stepEl.value=S.step;
|
||
[minEl,maxEl,stepEl].forEach(el=>el.disabled=!!S.managedLearningLevel);
|
||
function normalize(){
|
||
let min=Math.max(-1000000,Math.min(999999,+minEl.value||0)); let max=Math.max(-999999,Math.min(1000000,+maxEl.value||20));
|
||
if(max<=min) max=min+1; const step=+stepEl.value||1;
|
||
if((max-min)/step>60) max=min+60*step;
|
||
S.min=min; S.max=max; S.step=step;
|
||
if(S.value<min||S.value>max||(S.value-min)%step!==0) S.value=min;
|
||
if(S.target<min||S.target>max||(S.target-min)%step!==0) S.target=min;
|
||
minEl.value=min; maxEl.value=max; render();
|
||
}
|
||
function render(){
|
||
line.innerHTML="";
|
||
for(let v=S.min;v<=S.max;v+=S.step){
|
||
const b=document.createElement('button'); b.type='button'; b.className='nlw-tick'; b.dataset.v=v;
|
||
const mark=document.createElement('i'), label=document.createElement('span'); label.textContent=v;
|
||
b.append(mark,label); b.classList.toggle('selected',v===S.value);
|
||
b.addEventListener('click',()=>{S.value=v;if(!S.solved)feedback.textContent="";render();}); line.appendChild(b);
|
||
}
|
||
valueEl.textContent=S.value; task.textContent=T('numberlineTask').replace('{number}',S.target);
|
||
score.textContent=T('numberlineScore').replace('{score}',S.score).replace('{streak}',S.streak);
|
||
}
|
||
function newTarget(){
|
||
const count=Math.floor((S.max-S.min)/S.step);let target=S.target;
|
||
for(let tries=0;tries<8&&target===S.target;tries++)target=S.min+Math.floor(Math.random()*(count+1))*S.step;
|
||
S.target=target;S.solved=false;
|
||
feedback.textContent="";feedback.className='nlw-feedback';render();
|
||
}
|
||
[minEl,maxEl,stepEl].forEach(el=>el.addEventListener('change',normalize));
|
||
q('.nlw-minus').addEventListener('click',()=>{S.value=Math.max(S.min,S.value-S.step);render();});
|
||
q('.nlw-plus').addEventListener('click',()=>{S.value=Math.min(S.max,S.value+S.step);render();});
|
||
q('.nlw-new').addEventListener('click',newTarget);
|
||
q('.nlw-check').addEventListener('click',()=>{
|
||
const ok=S.value===S.target; feedback.textContent=T(ok?'numberlineGood':'numberlineTry');
|
||
feedback.className='nlw-feedback '+(ok?'ok':'bad');
|
||
if(ok){if(!S.solved){S.score++;S.streak++;S.solved=true;}sndGood();confetti(root);}else{S.streak=0;S.solved=false;sndBad();}render();
|
||
});
|
||
function labels(){
|
||
q('.nlw-settings-label').textContent=T('widgetSettings');
|
||
q('.nlw-from-label').textContent=T('numberlineFrom'); q('.nlw-to-label').textContent=T('numberlineTo');
|
||
q('.nlw-step-label').textContent=T('numberlineStep'); q('.nlw-new').textContent=T('numberlineNew');
|
||
q('.nlw-check').textContent=T('numberlineCheck'); render();
|
||
}
|
||
root.classList.toggle("managed-learning-level",!!S.managedLearningLevel);
|
||
document.addEventListener('langchange',labels); labels(); normalize();
|
||
return {getState:()=>({min:S.min,max:S.max,step:S.step,value:S.value,target:S.target,score:S.score,streak:S.streak,solved:S.solved})};
|
||
}
|