UNPKG

psytask

Version:

JavaScript Framework for Psychology task

9 lines (8 loc) 26.2 kB
/** * Psytask v1.1.1 * @author cubxx * @license MIT */ var QQ=Object.create;var{getPrototypeOf:ZQ,defineProperty:c,getOwnPropertyNames:$Q}=Object;var qQ=Object.prototype.hasOwnProperty;var n=(Q,Z,q)=>{q=Q!=null?QQ(ZQ(Q)):{};let $=Z||!Q||!Q.__esModule?c(q,"default",{value:Q,enumerable:!0}):q;for(let G of $Q(Q))if(!qQ.call($,G))c($,G,{get:()=>Q[G],enumerable:!0});return $};var GQ=(Q,Z)=>()=>(Z||Q((Z={exports:{}}).exports,Z),Z.exports);var y=GQ((xQ,o)=>{var MQ=(Q)=>{let Z=new Set;do for(let q of Reflect.ownKeys(Q))Z.add([Q,q]);while((Q=Reflect.getPrototypeOf(Q))&&Q!==Object.prototype);return Z};o.exports=(Q,{include:Z,exclude:q}={})=>{let $=(G)=>{let X=(H)=>typeof H==="string"?G===H:H.test(G);if(Z)return Z.some(X);if(q)return!q.some(X);return!0};for(let[G,X]of MQ(Q.constructor.prototype)){if(X==="constructor"||!$(X))continue;let H=Reflect.getOwnPropertyDescriptor(G,X);if(H&&typeof H.value==="function")Q[X]=Q[X].bind(Q)}return Q}});var U=(Q,Z,q)=>{let $=document.createElement(Q);if(Z!=null)for(let G of Object.keys(Z)){if(G==="style"){for(let X of Object.keys(Z.style))$.style.setProperty(X,Z.style[X]);continue}if(G==="dataset"){for(let X of Object.keys(Z.dataset))$.dataset[X]=Z.dataset[X];continue}$[G]=Z[G]}if(q!=null)Array.isArray(q)?$.append(...q):$.append(q);return $};function b(Q,Z){return Object.prototype.hasOwnProperty.call(Q,Z)}var p=b(Promise,"withResolvers")&&typeof Promise.withResolvers==="function"?Promise.withResolvers.bind(Promise):function(){let Q,Z;return{promise:new Promise(($,G)=>(Q=$,Z=G)),resolve:Q,reject:Z}};function F(Q,Z,q,$){return Q.addEventListener(Z,q,$),()=>Q.removeEventListener(Z,q,$)}Symbol.dispose??=Symbol.for("Symbol.dispose");class j{listeners={};[Symbol.dispose](){this.emit("cleanup",null)}on(Q,Z){return(this.listeners[Q]??=new Set).add(Z),this}off(Q,Z){return this.listeners[Q]?.delete(Z),this}once(Q,Z){let q=($)=>{try{Z($)}finally{this.off(Q,q)}};return this.on(Q,q),this}emit(Q,Z){let q=this.listeners[Q];if(!q)return 0;for(let $ of q)$(Z);return q.size}}var S=null,T=new Set;var k=(Q,Z)=>{if(S)(Q[Z]??=new Set).add(S)},_=(Q,Z)=>{let q=Q[Z];if(!q||q.size===0)return;for(let $ of q)if(S!==$){if(T.size===0)globalThis.queueMicrotask(()=>{for(let G of T)try{G()}catch(X){console.error("Error in reactive effect:",X)}T.clear()});T.add($)}},v=Symbol("Object.iterator"),XQ=(Q)=>{let Z={};return{proxy:new Proxy(Q,{_effectMap:Z,get($,G,X){return k(Z,G),Reflect.get($,G,X)},has($,G){return k(Z,G),Reflect.has($,G)},ownKeys($){return k(Z,v),Reflect.ownKeys($)},set($,G,X,H){if(Object.is($[G],X))return!0;let K=Reflect.set($,G,X,H);return _(Z,G),_(Z,v),K},deleteProperty($,G){let X=b($,G),H=Reflect.deleteProperty($,G);if(H&&X)_(Z,G),_(Z,v);return H}}),map:Z}},C=(Q)=>XQ(Q).proxy,J=(Q)=>{S=Q;try{S()}finally{S=null}};var E=(Q,Z,q)=>{Q.style.width="100%",J(()=>Q.id=Z.id),J(()=>Q.name=Z.id),J(()=>Q.required=Z.required??!0);let $;J(()=>{if($?.(),Z.validate){let H=()=>{let K=Z.validate(q());Q.setCustomValidity(K===!0?"":K)};H(),$=F(Q,"change",H)}});let G=U("label");J(()=>G.htmlFor=Z.id),J(()=>{let{label:H}=Z;G.replaceChildren(H instanceof HTMLElement?H:H??Z.id+(Z.required??!0?"*":""))});let X=U("div",{},[G," ",Q]);return J(()=>Z.setup?.(X)),{node:X,data:()=>({value:q()})}},zQ=function(Q){let Z=U("input");return J(()=>Z.type=Q.inputType??"text"),J(()=>Z.value=Q.defaultValue??""),E(Z,Q,()=>Z.value)},UQ=function(Q){let Z=U("input",{type:"number"});return J(()=>Z.value=Q.defaultValue?.toString()??""),J(()=>Z.min=Q.min?.toString()??"0"),J(()=>Z.max=Q.max?.toString()??""),J(()=>Z.step=Q.step?.toString()??""),E(Z,Q,()=>+Z.value)},HQ=function(Q){let Z=U("textarea",{style:{resize:"vertical"}});return J(()=>Z.value=Q.defaultValue??""),E(Z,Q,()=>Z.value)},YQ=function(Q){let Z=U("select");return J(()=>Z.value=Q.defaultValue??""),J(()=>{Z.replaceChildren(...Q.options.map((q)=>U("option",{value:q.value,selected:q.value===Q.defaultValue},q.label)))}),E(Z,Q,()=>Z.value)},BQ=function(Q){let Z=U("input",{type:"checkbox",style:{"margin-right":"0.5rem",width:"auto"}});return J(()=>Z.checked=!!Q.defaultValue),E(Z,Q,()=>Z.checked)},JQ=function(Q){let Z=U("input",{type:"radio"});return J(()=>Z.checked=!!Q.defaultValue),E(Z,Q,()=>Z.checked)},LQ={TextField:zQ,NumberField:UQ,TextArea:HQ,Select:YQ,Checkbox:BQ,Radio:JQ},TQ=function(Q,Z){let q=U("h2",{style:{"margin-bottom":"0.5rem"}});J(()=>q.textContent=Q.title??"");let $=U("p");J(()=>$.textContent=Q.subtitle??"");let G=U("button",{type:"button",style:{width:"100%","margin-top":"1rem"},onclick(){H.checkValidity()?Z.close():H.reportValidity()}});J(()=>G.textContent=Q.submitLabel??"OK");let X=U("div",{style:{display:"grid",gap:"0.5rem","grid-template-columns":"repeat(auto-fit, minmax(min(100%, 8rem), 1fr))"}}),H=U("form",{style:{margin:"2rem","min-width":"60%"},onsubmit:(N)=>N.preventDefault()},[q,$,X,G]),K;return Z.on("scene:show",()=>K=[]),J(()=>{if(!Q.fields)return;K=Object.entries(Q.fields).reduce((N,[B,{type:W,...M}])=>{let z=LQ[W];if(!z)throw new Error(`Unknown field type: ${W}`);return N.push({id:B,...z({id:B,...M})}),N},[]),X.replaceChildren(...K.map((N)=>N.node))}),{node:U("div",{className:"psytask-center",style:{"white-space":"pre-line","line-height":1.5}},H),data(){let N=K.reduce((B,{id:W,data:M})=>({...B,[W]:M().value}),{});return console.info("Form",N),N}}};var i=n(y(),1);var l=n(y(),1);class h{getRootElement;areResponsesCaseSensitive;minimumValidRt;constructor(Q,Z=!1,q=0){this.getRootElement=Q;this.areResponsesCaseSensitive=Z;this.minimumValidRt=q;l.default(this),this.registerRootListeners()}listeners=new Set;heldKeys=new Set;areRootListenersRegistered=!1;registerRootListeners(){if(!this.areRootListenersRegistered){let Q=this.getRootElement();if(Q)Q.addEventListener("keydown",this.rootKeydownListener),Q.addEventListener("keyup",this.rootKeyupListener),this.areRootListenersRegistered=!0}}rootKeydownListener(Q){for(let Z of[...this.listeners])Z(Q);this.heldKeys.add(this.toLowerCaseIfInsensitive(Q.key))}toLowerCaseIfInsensitive(Q){return this.areResponsesCaseSensitive?Q:Q.toLowerCase()}rootKeyupListener(Q){this.heldKeys.delete(this.toLowerCaseIfInsensitive(Q.key))}isResponseValid(Q,Z,q){if(!Z&&this.heldKeys.has(q))return!1;if(Q==="ALL_KEYS")return!0;if(Q==="NO_KEYS")return!1;return Q.includes(q)}getKeyboardResponse({callback_function:Q,valid_responses:Z="ALL_KEYS",rt_method:q="performance",persist:$,audio_context:G,audio_context_start_time:X,allow_held_key:H=!1,minimum_valid_rt:K=this.minimumValidRt}){if(q!=="performance"&&q!=="audio")console.log('Invalid RT method specified in getKeyboardResponse. Defaulting to "performance" method.'),q="performance";let B=q==="performance"?performance.now():X*1000;if(this.registerRootListeners(),!this.areResponsesCaseSensitive&&typeof Z!=="string")Z=Z.map((M)=>M.toLowerCase());let W=(M)=>{let z=Math.round((q=="performance"?performance.now():G.currentTime*1000)-B);if(z<K)return;let L=this.toLowerCaseIfInsensitive(M.key);if(this.isResponseValid(Z,H,L)){if(M.preventDefault(),!$)this.cancelKeyboardResponse(W);Q({key:M.key,rt:z})}};return this.listeners.add(W),W}cancelKeyboardResponse(Q){this.listeners.delete(Q)}cancelAllKeyboardResponses(){this.listeners.clear()}compareKeys(Q,Z){if(typeof Q!=="string"&&Q!==null||typeof Z!=="string"&&Z!==null){console.error("Error in jsPsych.pluginAPI.compareKeys: arguments must be key strings or null.");return}if(typeof Q==="string"&&typeof Z==="string")return this.areResponsesCaseSensitive?Q===Z:Q.toLowerCase()===Z.toLowerCase();return Q===null&&Z===null}}class f{timeout_handlers=[];setTimeout(Q,Z){let q=window.setTimeout(Q,Z);return this.timeout_handlers.push(q),q}clearAllTimeouts(){for(let Q of this.timeout_handlers)clearTimeout(Q);this.timeout_handlers=[]}}var u;((Y)=>{Y[Y.BOOL=0]="BOOL";Y[Y.STRING=1]="STRING";Y[Y.INT=2]="INT";Y[Y.FLOAT=3]="FLOAT";Y[Y.FUNCTION=4]="FUNCTION";Y[Y.KEY=5]="KEY";Y[Y.KEYS=6]="KEYS";Y[Y.SELECT=7]="SELECT";Y[Y.HTML_STRING=8]="HTML_STRING";Y[Y.IMAGE=9]="IMAGE";Y[Y.AUDIO=10]="AUDIO";Y[Y.VIDEO=11]="VIDEO";Y[Y.OBJECT=12]="OBJECT";Y[Y.COMPLEX=13]="COMPLEX";Y[Y.TIMELINE=14]="TIMELINE"})(u||={});var cQ=function(Q,Z){window.jsPsychModule??={ParameterType:u};let q,$=U("div",{id:"jspsych-content",className:"jspsych-content"});return J(()=>{let G=Q.type;if(typeof G!=="function"||typeof G.prototype==="undefined"||typeof G.info==="undefined")throw new Error(`jsPsych trial.type only supports jsPsych class plugins, but got ${G}`);for(let B in G.info.parameters)if(!b(Q,B))Q[B]=G.info.parameters[B].default;let X=[new h(()=>Z.root),new f].reduce((B,W)=>Object.assign(B,i.default(W)),{}),H={finishTrial(B){if(q=Object.assign({},Q.data,B),Q.on_finish?.(q),typeof Q.post_trial_gap==="number")window.setTimeout(()=>Z.close(),Q.post_trial_gap);else Z.close()},pluginAPI:X};Q.on_start?.(Q),$.className="jspsych-content";let K=Q.css_classes;if(typeof K==="string")$.classList.add(K);else if(Array.isArray(K))$.classList.add(...K);$.innerHTML="",new G(H).trial($,Q,()=>{Q.on_load?.()})}),{node:U("div",{className:"jspsych-display-element",style:{height:"100%",width:"100%"}},U("div",{className:"jspsych-content-wrapper"},$)),data:()=>q}};var lQ=(Q)=>(Z,q)=>Math.exp(-(Z**2+q**2)/(2*Q**2)),x=function(Q){let Z=U("div",{className:"psytask-center",style:{"white-space":"pre-line","line-height":1.5,padding:"2rem"}});J(()=>{let q=Q.children??"Hello Word";Array.isArray(q)?Z.replaceChildren(...q):Z.replaceChildren(q)});for(let q of Object.keys(Q))if(q!=="children")J(()=>Z.style[q]=Q[q]);return{node:Z}},NQ=function(Q){let Z=U("canvas"),q=Z.getContext("2d");if(!q)throw new Error("Failed to get canvas 2d context");return J(()=>{q.clearRect(0,0,Z.width,Z.height);let $=Q.image;if($)if([Z.width,Z.height]=[$.width,$.height],$ instanceof ImageData)q.putImageData($,0,0);else q.drawImage($,0,0);Q.draw?.(q)}),{node:Z}},KQ={sin:Math.sin,square:(Q)=>Math.sin(Q)>=0?1:-1,triangle:(Q)=>2/Math.PI*Math.asin(Math.sin(Q)),sawtooth:(Q)=>2/Math.PI*(Q%(2*Math.PI)-Math.PI)},VQ=(Q,Z,q)=>Math.max(Z,Math.min(q,Q)),iQ=function(Q,Z){let q=Z.use(NQ,{});return J(()=>{let $={ori:0,phase:0,color:[0,0,0],...Q},[G,X]=typeof $.size==="number"?[$.size,$.size]:$.size,H=Math.cos($.ori),K=Math.sin($.ori),N=G/2,B=X/2,W=new ImageData(G,X);for(let M=0;M<X;M++)for(let z=0;z<G;z++){let L=z-N,V=M-B,A=(L*H+V*K)*$.sf*2*Math.PI+$.phase,O=((typeof $.type==="string"?KQ[$.type](A):$.type(A))+1)/2,I=$.color.length===2?[...$.color[1].map((D,e)=>D+O*($.color[0][e]-D)),255]:[...$.color,255*O];if(I[3]>0&&$.mask)I[3]*=$.mask(L/N,V/B);let R=(M*G+z)*4;for(let D of I)W.data[R++]=VQ(Math.round(D),0,255)}q.props.image=W}),q},sQ=function(Q,Z){let q=Object.assign({i18n:{confirm:"Use previous chinrest data?",yes:"Yes",no:"No",screen_width:"Screen Width",line_spacing:"Line Spacing",distance:"Distance",SWT_guide:"Move the lower right line and measure the spacing (cm) between the two lines.",DT_guide:"Close right eye, focus left eye on square, keep head still.",DT_start:"\uD83D\uDC46\uD83C\uDFFB Click here to start",DT_stop:"\uD83D\uDC46\uD83C\uDFFB Click again when red circle disappears"},blindspotDegree:13.5},Q),$=C({line_spacing_pix:Math.floor(Z.app.data.window_wh_pix[0]/2),line_spacing_cm:0,pix_per_cm:0,screen_width_cm:0,move_width_pix:0,move_widths:[],distance_cm:0});J(()=>{$.pix_per_cm=$.line_spacing_pix/$.line_spacing_cm}),J(()=>{$.line_spacing_cm=$.line_spacing_pix/$.pix_per_cm}),J(()=>{$.pix_per_cm=Z.app.data.screen_wh_pix[0]/$.screen_width_cm}),J(()=>{$.screen_width_cm=Z.app.data.screen_wh_pix[0]/$.pix_per_cm}),J(()=>{let{move_widths:z}=$,L=z.length&&z.reduce((V,Y)=>V+Y.cm,0)/z.length;$.distance_cm=L/2/Math.tan(q.blindspotDegree/2*(Math.PI/180))});let G=(z)=>{let L=U("input",{id:z.id,type:"number",min:"1",step:"any",required:!0,onchange(V){let Y=+V.target.value;if(!Number.isNaN(Y))$[z.key]=Y;else console.warn(`Invalid ${z.id}:`,V)}});return J(()=>{L.value=$[z.key]+""}),U("div",null,[U("label",{htmlFor:z.id},z.label+" "),L])},X=(z)=>{return U("form",{style:{margin:"2rem"},onsubmit:(L)=>L.preventDefault()},[U("h2",null,z.title),z.content,G(z),U("button",{type:"button",style:{width:"100%","margin-top":"0.5rem"},onclick(L){let V=L.target.form;V.checkValidity()?z.onSuccess():V.reportValidity()}},"OK")])},H=(z,L)=>{return U("div",{style:{position:"absolute",left:"-5.5px",width:"0",height:"0","border-style":"solid","border-width":L==="up"?"0 6px 6px 6px":"6px 6px 0 6px","border-color":L==="up"?`transparent transparent ${z} transparent`:`${z} transparent transparent transparent`,[L==="up"?"bottom":"top"]:"-5.5px"}})},K=()=>{let z={position:"absolute","background-color":"#000","pointer-events":"none"};return[U("div",{style:{...z,left:"50%",width:"1px",height:"100%"}}),U("div",{style:{...z,top:"50%",width:"100%",height:"1px"}})]},N=X({title:"\uD83D\uDC40 "+q.i18n.screen_width,id:"screen-width-input",label:q.i18n.screen_width+" (cm):",key:"screen_width_cm",content:(()=>{let z={position:"absolute",width:"1px",height:"10rem"},L=U("div",{style:{background:"#fff",...z,left:0}},[H("#fff","up"),H("#fff","down")]),V=!1,Y=U("div",{style:{background:"#f00",...z,cursor:"ew-resize"},onpointerdown:()=>V=!0},[H("#f00","up"),H("#f00","down")]);J(()=>{Y.style.left=$.line_spacing_pix/Z.app.data.dpr+"px"}),Z.on("pointerup",()=>V=!1).on("pointermove",(w)=>{if(!V)return;let O=L.getBoundingClientRect().x;$.line_spacing_pix=(w.clientX-O)*Z.app.data.dpr});let A=U("p");return J(()=>{A.textContent=q.i18n.line_spacing+" (pix): "+$.line_spacing_pix}),U("div",null,[q.i18n.SWT_guide,U("div",{style:{position:"relative",margin:"2rem",height:z.height}},[L,Y]),A,G({id:"line-spacing-input",label:q.i18n.line_spacing+" (cm):",key:"line_spacing_cm"})])})(),onSuccess(){console.info("screen width:",$.screen_width_cm),M.props.children=B}}),B=X({title:"\uD83D\uDC40 "+q.i18n.distance,id:"distance-input",label:q.i18n.distance+" (cm):",key:"distance_cm",content:(()=>{let L={position:"absolute",width:"24px",height:"24px","user-select":"none"},V=U("div",{style:{position:"absolute",top:"100%",width:"max-content"}},q.i18n.DT_start),Y=U("div",{style:{...L,right:0,"background-color":"#fff",cursor:"pointer"},onpointerup(){if(!A){A=!0,Y.style.cursor="progress",V.textContent=q.i18n.DT_stop;return}A=!1,Y.style.cursor="pointer",V.textContent=q.i18n.DT_start;let R=$.move_width_pix;$.move_width_pix=0,$.move_widths=[...$.move_widths,{pix:R,cm:R/$.pix_per_cm}]}},[...K(),V]),A=!1,w=U("div",{style:{...L,"background-color":"#f00","border-radius":"50%"}},K());J(()=>{w.style.right=24+$.move_width_pix*Z.app.data.dpr+"px"}),Z.on("scene:frame",()=>{if(A)$.move_width_pix+=1});let O=U("div"),I=4;return J(()=>{O.replaceChildren(...$.move_widths.map(({pix:R,cm:D})=>U("span",{title:D+" cm",style:{position:"absolute",top:(24-I)/2+"px",right:24+R*Z.app.data.dpr+"px","background-color":"#fff5",width:I+"px",height:I+"px","border-radius":"50%"}})))}),U("div",null,[q.i18n.DT_guide,U("div",{style:{position:"relative",margin:"2rem",height:"24px"}},[O,Y,w])])})(),onSuccess(){let{screen_width_cm:z,distance_cm:L}=$;localStorage.setItem(W,JSON.stringify({screen_width_cm:z,distance_cm:L},null,2)),Z.close()}}),W="psytask:VirtualChinrest:store",M=Z.use(x,{children:""});return J(()=>{let{usePreviousData:z}=Q,L=localStorage.getItem(W);if(!L||z===!1){M.props.children=N;return}if(z===!0){Object.assign($,JSON.parse(L)),Z.on("scene:show",()=>Z.close());return}M.props.children=[U("h3",null,q.i18n.confirm),U("pre",null,L),U("div",{style:{display:"grid","grid-template-columns":"1fr 1fr",gap:"1rem"}},[{style:{width:"5rem"},textContent:q.i18n.yes,onclick(){Object.assign($,JSON.parse(L)),Z.close()}},{textContent:q.i18n.no,onclick:()=>M.props.children=N}].map((V)=>U("button",V)))]}),{node:M.node,data(){console.info("VirtualChinrest",{...$});let{pix_per_cm:z,distance_cm:L}=$,V=(A)=>2*L*Math.tan(A/2*(Math.PI/180)),Y=(A)=>V(A)*z;return{pix_per_cm:z,distance_cm:L,deg2cm:V,deg2pix:Y,deg2csspix:(A)=>Y(A)/Z.app.data.dpr}}}};class g{value=""}class s extends g{keys=[];normalize(Q){if(Q==null)return"";return Q=""+Q,/[,"\n\r]/.test(Q)?`"${Q.replaceAll('"','""')}"`:Q}transform(Q){let Z="",q=this.keys.length;if(q===0)this.keys=Object.keys(Q),q=this.keys.length,Z=this.keys.reduce(($,G,X)=>$+this.normalize(G)+(X<q-1?",":""),"");return Z+=this.keys.reduce(($,G,X)=>$+this.normalize(Q[G])+(X<q-1?",":""),` `),this.value+=Z,Z}final(){return""}}class r extends g{transform(Q){let Z=(this.value===""?"[":",")+JSON.stringify(Q);return this.value+=Z,Z}final(){let Q=this.value===""?"[]":"]";return this.value+=Q,Q}}class P extends j{filename;static stringifiers={csv:s,json:r};#Q=!1;rows=[];stringifier;constructor(Q=`data-${Date.now()}.csv`,Z){super();this.filename=Q;let q=Q.match(/\.([^\.]+)$/),$="csv",G=q?q[1]:(console.warn("Please specify the file extension in the filename"),$);if(Z instanceof g)this.stringifier=Z;else{let X=Object.keys(P.stringifiers);if(X.includes(G))this.stringifier=new P.stringifiers[G];else console.warn(`Please specify a valid file extension: ${X.join(", ")}, but got "${G}". Or, add your DataStringifier class to DataCollector.stringifiers.`),this.stringifier=new P.stringifiers[$]}this.on("cleanup",F(document,"visibilitychange",()=>{if(document.visibilityState==="hidden")this.download(`-${Date.now()}.backup`)})).on("cleanup",()=>this.save())}add(Q){console.info("data",Q),this.rows.push(Q);let Z=this.stringifier.transform(Q);return this.emit("add",{row:Q,chunk:Z}),Z}save(){if(this.#Q){console.warn("Repeated save is not allowed");return}this.#Q=!0;let Q=this.stringifier.final(),Z=!1;if(this.emit("save",{chunk:Q,preventDefault:()=>Z=!0}),!Z)this.download()}download(Q=""){if(this.rows.length===0)return;let Z=URL.createObjectURL(new Blob([this.stringifier.value],{type:"text/plain"})),q=U("a",{download:this.filename+Q,href:Z});document.body.appendChild(q),q.click(),URL.revokeObjectURL(Z),document.body.removeChild(q)}}var WQ=()=>({start_time:0,frame_times:[]}),AQ=["mouse:left","mouse:middle","mouse:right"],FQ=(Q)=>Q;class m extends j{app;defaultOptions;root=U("div",{className:"psytask-scene",tabIndex:-1,oncontextmenu:(Q)=>Q.preventDefault(),style:{transform:"scale(0)"}});props;data;show=this.#Z;options;#Q=null;constructor(Q,Z,q){super();this.app=Q;this.defaultOptions=q;this.options=q;let{node:$,data:G,props:X}=this.use(Z,{...q.defaultProps});this.data=G,this.props=X,Array.isArray($)?this.root.append(...$):this.root.append($),Q.root.appendChild(this.root),this.on("cleanup",()=>Q.root.removeChild(this.root))}use(Q,Z){let q=C(Z);return{...Q(q,this),props:q}}config(Q){return this.options={...this.defaultOptions,...Q},this}close(){if(!this.#Q)throw new Error("Scene hasn't been shown");this.root.style.transform="scale(0)",this.#Q.resolve(null)}async#Z(Q){if(this.#Q)throw new Error("Scene has been shown");this.root.focus(),this.root.style.transform="scale(1)",this.#Q=p();let{defaultProps:Z,duration:q,close_on:$,frame_times:G}=this.options;if(Object.assign(this.props,Z,Q),this.emit("scene:show",null),typeof $!=="undefined"){let M=Array.isArray($)?$:[$],z=()=>this.close();for(let L of M)this.on(L,z),this.once("scene:close",()=>this.off(L,z))}let X=Object.keys(this.listeners),H=[!1,!1];for(let M of X){if(!H[0]&&M.startsWith("mouse:")){H[0]=!0,this.once("scene:close",F(this.root,"mousedown",(z)=>this.emit(AQ[z.button]??"mouse:unknown",z)));continue}if(!H[1]&&M.startsWith("key:")){H[1]=!0,this.once("scene:close",F(this.root,"keydown",(z)=>this.emit(`key:${z.key}`,z)));continue}if(!M.startsWith("scene:"))this.once("scene:close",F(this.root,M,(z)=>this.emit(M,z)))}let K=this.app.data.frame_ms,N,B=WQ(),W=(M)=>{if(G&&B.frame_times.push(M),typeof q!=="undefined"&&M-B.start_time>=q-K*1.5){this.close();return}this.emit("scene:frame",{lastFrameTime:M}),N=window.requestAnimationFrame(W)};return N=window.requestAnimationFrame((M)=>{B.start_time=M,W(M)}),await this.#Q.promise,this.emit("scene:close",null),window.cancelAnimationFrame(N),this.options=this.defaultOptions,this.#Q=null,Object.assign(this.data?.()??{},B)}}class a extends j{root;data={frame_ms:16.67,leave_count:0,dpr:window.devicePixelRatio,screen_wh_pix:[window.screen.width,window.screen.height],window_wh_pix:[window.innerWidth,window.innerHeight]};constructor(Q){super();this.root=Q;if(this.data=C(this.data),J(()=>{let Z=this.data.dpr;Object.assign(this.data,{screen_wh_pix:[window.screen.width*Z,window.screen.height*Z],window_wh_pix:[window.innerWidth*Z,window.innerHeight*Z]})}),window.getComputedStyle(this.root).getPropertyValue("--psytask")==="")throw new Error("Please import psytask CSS file in your HTML file");this.on("cleanup",F(window,"beforeunload",(Z)=>{return Z.preventDefault(),Z.returnValue="Leaving the page will discard progress. Are you sure?"})).on("cleanup",F(document,"visibilitychange",()=>{if(document.visibilityState==="hidden")this.data.leave_count++,window.setTimeout(()=>alert("Please keep the page visible on the screen during the task running"))})).on("cleanup",(()=>{let Z;return J(()=>{Z?.(),Z=F(window.matchMedia(`(resolution: ${this.data.dpr}dppx)`),"change",()=>this.data.dpr=window.devicePixelRatio)}),()=>Z()})()).on("cleanup",F(window,"resize",()=>{let Z=this.data.dpr;this.data.window_wh_pix=[window.innerWidth*Z,window.innerHeight*Z]})).on("cleanup",()=>{this.root.appendChild(U("div",{className:"psytask-center"},"Thanks for participating!"))})}async load(Q,Z){let q=this.root.appendChild(x({children:""}).node),$=Q.map(async(X)=>{let H=U("a",{href:X,target:"_blank"},X),K=q.appendChild(U("p",{title:X},["Fetch ",H,"..."]));try{let N=await fetch(X);if(N.body==null)throw new Error("no response body");let B=N.headers.get("Content-Length");if(B==null)return console.warn(`Failed to get content length for ${X}`),K.replaceChildren("Loading",H,"..."),N.blob();let W=+B,M=N.body.getReader(),z=[];for(let V=0;;){let{done:Y,value:A}=await M.read();if(Y)break;V+=A.length,K.replaceChildren("Loading",H,`... ${(V/W*100).toFixed(2)}%`),z.push(A)}let L=new Blob(z);return Z?Z(L,X):L}catch(N){K.style.color="#000",K.replaceChildren("Failed to load",H,`: ${N}`),await new Promise(()=>{})}}),G=await Promise.all($);return this.root.removeChild(q),G}collector(...Q){return new P(...Q)}scene(...Q){return new m(this,...Q)}text(Q,Z){return this.scene(x,{defaultProps:{children:Q,...Z?.defaultProps},...Z})}}function OQ(Q){let Z=Q.length,q=Q.reduce((G,X)=>G+X)/Z,$=Math.sqrt(Q.reduce((G,X)=>G+Math.pow(X-q,2),0)/(Z-1));return{mean:q,std:$}}function IQ(Q){function Z(){if(document.visibilityState==="hidden")alert("Please keep the page visible on the screen during the FPS detection"),location.reload()}document.addEventListener("visibilitychange",Z);let q=0,$=[],G=Q.root.appendChild(U("p"));return new Promise((X)=>{window.requestAnimationFrame(function H(K){if(q!==0)$.push(K-q);q=K;let N=$.length/Q.framesCount;if(G.textContent=`test fps ${Math.floor(N*100)}%`,N<1){window.requestAnimationFrame(H);return}document.removeEventListener("visibilitychange",Z);let{mean:B,std:W}=OQ($),M=$.filter((L)=>B-W*2<=L&&L<=B+W*2);if(M.length<1)throw new Error("No valid frames found");let z=M.reduce((L,V)=>L+V)/M.length;console.info("detectFPS",{mean:B,std:W,valids:M,raws:$,frame_ms:z}),X(z)})})}var jQ=async(Q)=>{let Z={root:document.body,framesCount:60,...Q};if(!Z.root.isConnected)console.warn("Root element is not connected to the document, it will be mounted to document.body"),document.body.appendChild(Z.root);let q=new a(Z.root),$=U("div",{className:"psytask-center"});return Z.root.appendChild($),q.data.frame_ms=await IQ({...Z,root:$}),Z.root.removeChild($),q};class d{options;#Q=!1;#Z=!1;constructor(Q){this.options=Q}[Symbol.iterator](){if(this.#Z)throw new Error("Please create a new trial iterator, it can only be used once.");return this}next(){if(this.#Q)throw new Error("Unexpected call to next() after the iterator is done");this.#Z=!0;let Q=this.nextValue();if(typeof Q==="undefined")return this.#Q=!0,{value:void 0,done:!0};return{value:Q,done:!1}}}class t extends d{}class CQ extends d{#Q=0;constructor(Q){super({candidates:[...Q.candidates],sampleSize:Q.sampleSize??Q.candidates.length,replace:Q.replace??!0});if(this.options.candidates.length===0){console.warn("No candidates provided, iterator will not yield any values");return}if(!this.options.replace&&this.options.sampleSize>this.options.candidates.length)this.options.sampleSize=this.options.candidates.length,console.warn("Sample size should be <= the number of candidates when not replacing")}nextValue(){if(this.options.candidates.length===0)return;if(this.#Q>=this.options.sampleSize)return;this.#Q++;let Q=Math.floor(Math.random()*this.options.candidates.length),Z=this.options.candidates[Q];if(!this.options.replace)this.options.candidates.splice(Q,1);return Z}}class RQ extends t{options;data=[];constructor(Q){super(Q);this.options=Q}nextValue(){let Q=this.data.length;if(Q===0){let B=this.options.start;return this.data.push({value:B,response:!1,isReversal:!1}),B}let Z=this.data.filter((B)=>B.isReversal).length;if(Z>=this.options.reversal)return;let{step:q,down:$,up:G,max:X,min:H}=this.options,K=this.data.at(-1),N=K.value;if(Z===0)N+=K.response?-q:q;else{if(Q>=$&&this.data.slice(-$).every((B)=>B.value===K.value&&B.response===!0))N-=q;if(Q>=G&&this.data.slice(-G).every((B)=>B.value===K.value&&B.response===!1))N+=q}if(typeof H==="number"&&N<H)N=H;if(typeof X==="number"&&N>X)N=X;return this.data.push({value:N,response:!1,isReversal:!1}),N}response(Q){if(this.data.length===0){console.warn("Please iterate first to get a value");return}let Z=this.data.at(-1);if(Z.response=Q,this.data.length>1){let q=this.data.at(-2);if(Q!==q.response)Z.isReversal=!0}}getThreshold(Q=this.options.reversal){let Z=this.data.filter((X)=>X.isReversal),q=Z.length;if(q<Q)console.warn(`Not enough reversals, only ${q} found, but requested ${Q}`);let $=Z.slice(-Q);return $.reduce((X,H)=>X+H.value,0)/$.length}}export{C as reactive,F as on,cQ as jsPsychStim,U as h,FQ as generic,J as effect,jQ as createApp,sQ as VirtualChinrest,d as TrialIterator,x as TextStim,zQ as TextField,HQ as TextArea,RQ as StairCase,YQ as Select,t as ResponsiveTrialIterator,CQ as RandomSampling,JQ as Radio,UQ as NumberField,r as JSONStringifier,NQ as ImageStim,iQ as Grating,lQ as GaussianMask,TQ as Form,g as DataStringifier,P as DataCollector,BQ as Checkbox,s as CSVStringifier};