@adriansteffan/reactive 0.0.28 → 0.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/style.css CHANGED
@@ -1,4 +1,4 @@
1
- /*! tailwindcss v4.0.9 | MIT License | https://tailwindcss.com */@layer theme{:root,:host{--color-red-100:oklch(.936 .032 17.717);--color-red-200:oklch(.885 .062 18.334);--color-red-500:oklch(.637 .237 25.331);--color-red-600:oklch(.577 .245 27.325);--color-red-700:oklch(.505 .213 27.518);--color-green-500:oklch(.723 .219 149.579);--color-green-600:oklch(.627 .194 149.214);--color-green-700:oklch(.527 .154 150.069);--color-blue-400:oklch(.707 .165 254.624);--color-blue-500:oklch(.623 .214 259.815);--color-blue-600:oklch(.546 .245 262.881);--color-blue-700:oklch(.488 .243 264.376);--color-indigo-500:oklch(.585 .233 277.117);--color-indigo-600:oklch(.511 .262 276.966);--color-indigo-700:oklch(.457 .24 277.023);--color-gray-50:oklch(.985 .002 247.839);--color-gray-100:oklch(.967 .003 264.542);--color-gray-200:oklch(.928 .006 264.531);--color-gray-300:oklch(.872 .01 258.338);--color-gray-500:oklch(.551 .027 264.364);--color-gray-600:oklch(.446 .03 256.802);--color-gray-700:oklch(.373 .034 259.733);--color-gray-800:oklch(.278 .033 256.848);--color-gray-900:oklch(.21 .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-md:28rem;--container-2xl:42rem;--container-4xl:56rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-3xl:1.875rem;--text-3xl--line-height: 1.2 ;--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--leading-relaxed:1.625;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-font-feature-settings:var(--font-sans--font-feature-settings);--default-font-variation-settings:var(--font-sans--font-variation-settings);--default-mono-font-family:var(--font-mono);--default-mono-font-feature-settings:var(--font-mono--font-feature-settings);--default-mono-font-variation-settings:var(--font-mono--font-variation-settings);--font-sans:Atkinson Hyperlegible,sans-serif;--animate-slide-down:slideDown .8s ease-out forwards;--animate-fade-in:fadeIn .5s ease-out forwards}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}body{line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::-moz-placeholder{opacity:1;color:color-mix(in oklab,currentColor 50%,transparent)}::placeholder{opacity:1;color:color-mix(in oklab,currentColor 50%,transparent)}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*,:after,:before,::backdrop{border-color:var(--color-gray-200,currentColor)}::file-selector-button{border-color:var(--color-gray-200,currentColor)}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.visible\!{visibility:visible!important}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:calc(var(--spacing)*0)}.-z-10{z-index:-10}.z-50{z-index:50}.col-span-1{grid-column:span 1/span 1}.col-span-2{grid-column:span 2/span 2}.col-span-3{grid-column:span 3/span 3}.col-span-6{grid-column:span 6/span 6}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.me-4{margin-inline-end:calc(var(--spacing)*4)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-8{margin-top:calc(var(--spacing)*8)}.mt-16{margin-top:calc(var(--spacing)*16)}.mt-20{margin-top:calc(var(--spacing)*20)}.mt-auto{margin-top:auto}.mr-2{margin-right:calc(var(--spacing)*2)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.mb-8{margin-bottom:calc(var(--spacing)*8)}.mb-20{margin-bottom:calc(var(--spacing)*20)}.-ml-1{margin-left:calc(var(--spacing)*-1)}.ml-2{margin-left:calc(var(--spacing)*2)}.ml-3{margin-left:calc(var(--spacing)*3)}.ml-4{margin-left:calc(var(--spacing)*4)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-3{height:calc(var(--spacing)*3)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-8{height:calc(var(--spacing)*8)}.h-12{height:calc(var(--spacing)*12)}.h-full{height:100%}.max-h-60{max-height:calc(var(--spacing)*60)}.min-h-10{min-height:calc(var(--spacing)*10)}.w-3{width:calc(var(--spacing)*3)}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-6{width:calc(var(--spacing)*6)}.w-8{width:calc(var(--spacing)*8)}.w-11{width:calc(var(--spacing)*11)}.w-12{width:calc(var(--spacing)*12)}.w-64{width:calc(var(--spacing)*64)}.w-258{width:calc(var(--spacing)*258)}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-md{max-width:var(--container-md)}.max-w-prose{max-width:65ch}.flex-1{flex:1}.flex-shrink-0{flex-shrink:0}.flex-grow{flex-grow:1}.translate-x-0{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-1{--tw-translate-x:calc(var(--spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-5{--tw-translate-x:calc(var(--spacing)*5);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-1{--tw-translate-y:calc(var(--spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x)var(--tw-rotate-y)var(--tw-rotate-z)var(--tw-skew-x)var(--tw-skew-y)}.animate-fade-in{animation:var(--animate-fade-in)}.animate-pulse{animation:var(--animate-pulse)}.animate-slide-down{animation:var(--animate-slide-down)}.animate-spin{animation:var(--animate-spin)}.cursor-pointer{cursor:pointer}.resize{resize:both}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-6{gap:calc(var(--spacing)*6)}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*4)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-x-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-none{border-radius:0}.rounded-sm{border-radius:var(--radius-sm)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-4{border-style:var(--tw-border-style);border-width:4px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-black{border-color:var(--color-black)}.border-blue-500{border-color:var(--color-blue-500)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-transparent{border-color:#0000}.border-t-transparent{border-top-color:#0000}.bg-black{background-color:var(--color-black)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-green-600{background-color:var(--color-green-600)}.bg-indigo-600{background-color:var(--color-indigo-600)}.bg-red-100{background-color:var(--color-red-100)}.bg-red-500{background-color:var(--color-red-500)}.bg-red-600{background-color:var(--color-red-600)}.bg-white{background-color:var(--color-white)}.bg-\[radial-gradient\(\#e5e7eb_1px\,transparent_1px\)\]{background-image:radial-gradient(#e5e7eb 1px,#0000 1px)}.\[background-size\:16px_16px\]{background-size:16px 16px}.p-2{padding:calc(var(--spacing)*2)}.p-4{padding:calc(var(--spacing)*4)}.p-5{padding:calc(var(--spacing)*5)}.p-6{padding:calc(var(--spacing)*6)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-5{padding-inline:calc(var(--spacing)*5)}.px-8{padding-inline:calc(var(--spacing)*8)}.px-10{padding-inline:calc(var(--spacing)*10)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-5{padding-block:calc(var(--spacing)*5)}.pt-2{padding-top:calc(var(--spacing)*2)}.pr-10{padding-right:calc(var(--spacing)*10)}.pl-3{padding-left:calc(var(--spacing)*3)}.text-center{text-align:center}.text-left{text-align:left}.font-sans{font-family:var(--font-sans)}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.text-black{color:var(--color-black)}.text-blue-400{color:var(--color-blue-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-indigo-600{color:var(--color-indigo-600)}.text-red-500{color:var(--color-red-500)}.text-red-700{color:var(--color-red-700)}.text-white{color:var(--color-white)}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.italic{font-style:italic}.underline{text-decoration-line:underline}.opacity-0{opacity:0}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[2px_2px_0px_0px_rgba\(0\,0\,0\,1\)\]{--tw-shadow:2px 2px 0px 0px var(--tw-shadow-color,#000);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[2px_2px_0px_rgba\(0\,0\,0\,1\)\]{--tw-shadow:2px 2px 0px var(--tw-shadow-color,#000);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-0{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(0px + var(--tw-ring-offset-width))var(--tw-ring-color,currentColor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}@media (hover:hover){.hover\:translate-x-\[2px\]:hover{--tw-translate-x:2px;translate:var(--tw-translate-x)var(--tw-translate-y)}.hover\:translate-y-\[2px\]:hover{--tw-translate-y:2px;translate:var(--tw-translate-x)var(--tw-translate-y)}.hover\:bg-black:hover{background-color:var(--color-black)}.hover\:bg-blue-600:hover{background-color:var(--color-blue-600)}.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-green-700:hover{background-color:var(--color-green-700)}.hover\:bg-indigo-700:hover{background-color:var(--color-indigo-700)}.hover\:bg-red-200:hover{background-color:var(--color-red-200)}.hover\:bg-red-600:hover{background-color:var(--color-red-600)}.hover\:text-white:hover{color:var(--color-white)}.hover\:shadow-\[1px_1px_0px_0px_rgba\(0\,0\,0\,1\)\]:hover{--tw-shadow:1px 1px 0px 0px var(--tw-shadow-color,#000);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-none:hover{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.focus\:border-blue-500:focus{border-color:var(--color-blue-500)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentColor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-blue-500:focus{--tw-ring-color:var(--color-blue-500)}.focus\:ring-green-500:focus{--tw-ring-color:var(--color-green-500)}.focus\:ring-indigo-500:focus{--tw-ring-color:var(--color-indigo-500)}.focus\:ring-red-500:focus{--tw-ring-color:var(--color-red-500)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.active\:translate-x-1:active{--tw-translate-x:calc(var(--spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.active\:translate-y-1:active{--tw-translate-y:calc(var(--spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.active\:shadow-none:active{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media (width>=40rem){.sm\:mt-12{margin-top:calc(var(--spacing)*12)}.sm\:rounded-md{border-radius:var(--radius-md)}.sm\:px-6{padding-inline:calc(var(--spacing)*6)}.sm\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}@media (width>=48rem){.md\:flex-row{flex-direction:row}}}@keyframes slideDown{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}:root{--font-atkinson:"Atkinson Hyperlegible",system-ui,sans-serif}*{-webkit-touch-callout:none}@font-face{font-family:Atkinson Hyperlegible;src:url(/Atkinson_Hyperlegible/AtkinsonHyperlegible-Regular.ttf)format("truetype");font-weight:400;font-style:normal}@font-face{font-family:Atkinson Hyperlegible;src:url(/Atkinson_Hyperlegible/AtkinsonHyperlegible-Bold.ttf)format("truetype");font-weight:700;font-style:normal}@font-face{font-family:Atkinson Hyperlegible;src:url(/Atkinson_Hyperlegible/AtkinsonHyperlegible-Italic.ttf)format("truetype");font-weight:400;font-style:italic}@font-face{font-family:Atkinson Hyperlegible;src:url(/Atkinson_Hyperlegible/AtkinsonHyperlegible-BoldItalic.ttf)format("truetype");font-weight:700;font-style:italic}.sd-root-modern{background-image:radial-gradient(#e5e7eb 1px,#0000 1px);background-size:16px 16px;font-family:Atkinson Hyperlegible,sans-serif;position:relative;background-color:#fff!important}.sd-root-modern *{font-family:Atkinson Hyperlegible,sans-serif}.sd-row,.sd-clearfix{padding-bottom:20px!important}@media (width<=639px){.sd-imagepicker__item--inline{margin-top:10px;margin-left:auto;margin-right:auto}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false;initial-value:rotateX(0)}@property --tw-rotate-y{syntax:"*";inherits:false;initial-value:rotateY(0)}@property --tw-rotate-z{syntax:"*";inherits:false;initial-value:rotateZ(0)}@property --tw-skew-x{syntax:"*";inherits:false;initial-value:skewX(0)}@property --tw-skew-y{syntax:"*";inherits:false;initial-value:skewY(0)}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}/*!
1
+ /*!
2
2
  * surveyjs - Survey JavaScript library v1.12.11
3
3
  * Copyright (c) 2015-2024 Devsoft Baltic OÜ - http://surveyjs.io/
4
4
  * License: MIT (http://www.opensource.org/licenses/mit-license.php)
@@ -1,4 +1,4 @@
1
- import { W as P, b as x, E } from "./mod-CjZm1Ta9.js";
1
+ import { W as P, b as x, E } from "./mod-BY9yD0Pz.js";
2
2
  function m(w) {
3
3
  const e = w.split("/").filter((t) => t !== "."), r = [];
4
4
  return e.forEach((t) => {
@@ -1,4 +1,4 @@
1
- import { W as e } from "./mod-CjZm1Ta9.js";
1
+ import { W as e } from "./mod-BY9yD0Pz.js";
2
2
  class s extends e {
3
3
  async enable() {
4
4
  console.log("Immersive mode is only available on Android");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adriansteffan/reactive",
3
- "version": "0.0.28",
3
+ "version": "0.0.30",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite",
@@ -21,7 +21,9 @@ interface CanvasSlide {
21
21
  hideOnResponse?: boolean;
22
22
  ignoreData?: boolean;
23
23
  allowedKeys?: string[] | boolean;
24
- metadata?: Record<string, any>;
24
+ metadata?:
25
+ | Record<string, any>
26
+ | ((data?: RefinedTrialData[], store?: Store) => Record<string, any>);
25
27
  nestMetadata?: boolean;
26
28
  }
27
29
 
@@ -324,9 +326,14 @@ export default function CanvasBlock({
324
326
  reactionTime: responseData ? responseData.reactionTime : null,
325
327
  } as CanvasResultData;
326
328
 
329
+ const metadata =
330
+ typeof slide.metadata === 'function'
331
+ ? slide.metadata(dataRef.current, storeRef.current)
332
+ : slide.metadata;
333
+
327
334
  if (slide.nestMetadata) {
328
- trialData = { ...trialData, metadata: slide.metadata };
329
- } else trialData = { ...slide.metadata, ...trialData };
335
+ trialData = { ...trialData, metadata: metadata };
336
+ } else trialData = { ...metadata, ...trialData };
330
337
 
331
338
  dataRef.current.push(trialData);
332
339
  }
@@ -23,6 +23,7 @@ import CheckDevice from './checkdevice';
23
23
 
24
24
  import VoicerecorderQuestionComponent from './voicerecorder';
25
25
  import React from 'react';
26
+ import StoreUI from './storeui';
26
27
 
27
28
  type ComponentsMap = {
28
29
  [key: string]: ComponentType<any>;
@@ -40,6 +41,7 @@ const defaultComponents: ComponentsMap = {
40
41
  RequestFilePermission,
41
42
  CanvasBlock,
42
43
  CheckDevice,
44
+ StoreUI
43
45
  };
44
46
 
45
47
  const defaultCustomQuestions: ComponentsMap = {
@@ -51,7 +53,11 @@ interface RuntimeComponentContent {
51
53
  type: string;
52
54
  collectRefreshRate?: boolean;
53
55
  hideSettings?: string[] | boolean;
54
- props?: Record<string, any> | ((store: Store, data: RefinedTrialData[]) => Record<string, any>);
56
+ metadata?:
57
+ | Record<string, any>
58
+ | ((data: RefinedTrialData[], store: Store) => Record<string, any>);
59
+ nestMetadata?: boolean;
60
+ props?: Record<string, any> | ((data: RefinedTrialData[], store: Store) => Record<string, any>);
55
61
  }
56
62
 
57
63
  function isRuntimeComponentContent(content: any): content is RuntimeComponentContent {
@@ -76,7 +82,7 @@ export default function ExperimentRunner({
76
82
  }, [timeline]);
77
83
 
78
84
  const [instructionPointer, setInstructionPointer] = useState(0);
79
- const [data, setData] = useState<RefinedTrialData[]>(() => {
85
+ const dataRef = useRef<RefinedTrialData[]>((() => {
80
86
  const urlParams: Record<string, any> = {};
81
87
  const searchParams = new URLSearchParams(window.location.search);
82
88
  for (const [key, value] of searchParams.entries()) {
@@ -101,8 +107,7 @@ export default function ExperimentRunner({
101
107
  // Then add/overwrite with registry entries
102
108
  for (const param of registry) {
103
109
  params[param.name] = {
104
- value:
105
- param.value !== undefined ? param.value : urlParams[param.name],
110
+ value: param.value !== undefined ? param.value : urlParams[param.name],
106
111
  registered: true,
107
112
  defaultValue: param.defaultValue,
108
113
  type: param.type,
@@ -123,9 +128,9 @@ export default function ExperimentRunner({
123
128
  params,
124
129
  },
125
130
  };
126
-
131
+
127
132
  return [initialData];
128
- });
133
+ })());
129
134
 
130
135
  const [totalTrialsCompleted, setTotalTrialsCompleted] = useState(0);
131
136
  const lastTrialEndTimeRef = useRef(now());
@@ -163,7 +168,7 @@ export default function ExperimentRunner({
163
168
  if (currentInstruction?.type === 'ExecuteContent') {
164
169
  const content = currentInstruction.content;
165
170
  if (isRuntimeComponentContent(content)) {
166
- const trialData: ComponentResultData = {
171
+ let trialData: ComponentResultData = {
167
172
  index: instructionPointer,
168
173
  trialNumber: totalTrialsCompleted + 1,
169
174
  start: startTime,
@@ -173,7 +178,17 @@ export default function ExperimentRunner({
173
178
  name: content.name ?? '',
174
179
  responseData: componentResponseData,
175
180
  };
176
- setData((prevData) => [...prevData, trialData]);
181
+
182
+ const metadata =
183
+ typeof content.metadata === 'function'
184
+ ? content.metadata(dataRef.current, experimentStoreRef.current)
185
+ : content.metadata;
186
+
187
+ if (content.nestMetadata) {
188
+ trialData = { ...trialData, metadata: metadata };
189
+ } else trialData = { ...metadata, ...trialData };
190
+
191
+ dataRef.current = [...dataRef.current, trialData];
177
192
  setTotalTrialsCompleted((prevCount) => prevCount + 1);
178
193
  } else {
179
194
  console.log(
@@ -191,7 +206,7 @@ export default function ExperimentRunner({
191
206
 
192
207
  switch (nextInstruction.type) {
193
208
  case 'IfGoto':
194
- if (nextInstruction.cond(experimentStoreRef.current, data)) {
209
+ if (nextInstruction.cond(experimentStoreRef.current, dataRef.current)) {
195
210
  const markerIndex = trialByteCode.markers[nextInstruction.marker];
196
211
  if (markerIndex !== undefined) {
197
212
  nextPointer = markerIndex;
@@ -206,7 +221,7 @@ export default function ExperimentRunner({
206
221
  break;
207
222
 
208
223
  case 'UpdateStore':
209
- updateStore(nextInstruction.fun(experimentStoreRef.current, data));
224
+ updateStore(nextInstruction.fun(experimentStoreRef.current, dataRef.current));
210
225
  nextPointer++;
211
226
  break;
212
227
 
@@ -334,7 +349,7 @@ export default function ExperimentRunner({
334
349
  if (Component) {
335
350
  const componentProps =
336
351
  typeof content.props === 'function'
337
- ? content.props(experimentStoreRef.current, data)
352
+ ? content.props(dataRef.current, experimentStoreRef.current)
338
353
  : content.props || {};
339
354
 
340
355
  componentToRender = (
@@ -343,7 +358,7 @@ export default function ExperimentRunner({
343
358
  next={next}
344
359
  updateStore={updateStore}
345
360
  store={experimentStoreRef.current}
346
- data={data}
361
+ data={dataRef.current}
347
362
  {...(content.type === 'Quest' ? { customQuestions: customQuestionsMap } : {})}
348
363
  {...componentProps}
349
364
  />
@@ -362,7 +377,7 @@ export default function ExperimentRunner({
362
377
  }
363
378
 
364
379
  return (
365
- <div className='w-full'>
380
+ <div className='w-full h-full'>
366
381
  <div
367
382
  className={` ${
368
383
  config.showProgressBar ? '' : 'hidden '
@@ -0,0 +1,327 @@
1
+ import { BaseComponentProps } from '../mod';
2
+ import React, { useState, useEffect } from 'react';
3
+
4
+ interface BaseFieldConfig {
5
+ type: 'string' | 'integer' | 'float' | 'boolean';
6
+ storeKey: string;
7
+ label: string;
8
+ prompt?: string;
9
+ validate?: (value: any) => boolean | string;
10
+ defaultValue?: any;
11
+ component?: React.ComponentType<{ value: any }>;
12
+ }
13
+
14
+ interface StringFieldConfig extends BaseFieldConfig {
15
+ type: 'string';
16
+ minLength?: number;
17
+ maxLength?: number;
18
+ defaultValue?: string;
19
+ }
20
+
21
+ interface NumberFieldConfig extends BaseFieldConfig {
22
+ type: 'integer' | 'float';
23
+ min?: number;
24
+ max?: number;
25
+ step?: number;
26
+ defaultValue?: number;
27
+ }
28
+
29
+ interface BooleanFieldConfig extends BaseFieldConfig {
30
+ type: 'boolean';
31
+ defaultValue?: boolean;
32
+ }
33
+
34
+ type FieldConfig = StringFieldConfig | NumberFieldConfig | BooleanFieldConfig;
35
+
36
+ interface StoreUIProps extends BaseComponentProps {
37
+ title?: string;
38
+ description?: string;
39
+ fields: FieldConfig[];
40
+ saveButtonText?: string;
41
+ }
42
+
43
+ function StoreUI({
44
+ next,
45
+ updateStore,
46
+ store,
47
+ title = 'Settings',
48
+ description,
49
+ fields,
50
+ saveButtonText = 'Save Configuration',
51
+ }: StoreUIProps) {
52
+
53
+ const [values, setValues] = useState<Record<string, any>>({});
54
+ const [errors, setErrors] = useState<Record<string, string>>({});
55
+ const [touched, setTouched] = useState<Record<string, boolean>>({});
56
+
57
+
58
+ useEffect(() => {
59
+ const initialValues: Record<string, any> = {};
60
+
61
+ fields.forEach(field => {
62
+
63
+ initialValues[field.storeKey] =
64
+ store?.[field.storeKey] !== undefined
65
+ ? store[field.storeKey]
66
+ : field.defaultValue;
67
+ });
68
+
69
+ setValues(initialValues);
70
+
71
+ const initialTouched: Record<string, boolean> = {};
72
+ fields.forEach(field => {
73
+ initialTouched[field.storeKey] = false;
74
+ });
75
+ setTouched(initialTouched);
76
+ }, [store, fields]);
77
+
78
+ const validateField = (field: FieldConfig, value: any): string | null => {
79
+ if (field.type === 'integer' || field.type === 'float') {
80
+ if (value === undefined || value === null || value === '') {
81
+ return 'This field is required';
82
+ } else if (field.min !== undefined && value < field.min) {
83
+ return `Value must be at least ${field.min}`;
84
+ } else if (field.max !== undefined && value > field.max) {
85
+ return `Value must be at most ${field.max}`;
86
+ }
87
+ } else if (field.type === 'string') {
88
+ if (field.minLength !== undefined && value.length < field.minLength) {
89
+ return `Must be at least ${field.minLength} characters`;
90
+ } else if (field.maxLength !== undefined && value.length > field.maxLength) {
91
+ return `Must be at most ${field.maxLength} characters`;
92
+ }
93
+ }
94
+
95
+ if (field.validate) {
96
+ const validationResult = field.validate(value);
97
+ if (validationResult !== true && validationResult !== '') {
98
+ return typeof validationResult === 'string'
99
+ ? validationResult
100
+ : 'Invalid value';
101
+ }
102
+ }
103
+
104
+ return null;
105
+ };
106
+
107
+ useEffect(() => {
108
+ const newErrors: Record<string, string> = {};
109
+
110
+ fields.forEach(field => {
111
+ const value = values[field.storeKey];
112
+
113
+ if (!touched[field.storeKey]) return;
114
+
115
+ const error = validateField(field, value);
116
+ if (error) {
117
+ newErrors[field.storeKey] = error;
118
+ }
119
+ });
120
+
121
+ setErrors(newErrors);
122
+ }, [values, fields, touched]);
123
+
124
+ const handleChange = (storeKey: string, value: any) => {
125
+ setValues(prev => ({
126
+ ...prev,
127
+ [storeKey]: value
128
+ }));
129
+
130
+ setTouched(prev => ({
131
+ ...prev,
132
+ [storeKey]: true
133
+ }));
134
+ };
135
+
136
+ const handleSave = () => {
137
+ const allTouched: Record<string, boolean> = {};
138
+ fields.forEach(field => {
139
+ allTouched[field.storeKey] = true;
140
+ });
141
+ setTouched(allTouched);
142
+
143
+ const newErrors: Record<string, string> = {};
144
+ fields.forEach(field => {
145
+ const value = values[field.storeKey];
146
+
147
+
148
+ if (field.type === 'integer' || field.type === 'float') {
149
+ if (value === undefined || value === null || value === '') {
150
+ newErrors[field.storeKey] = 'This field is required';
151
+ } else if (field.min !== undefined && value < field.min) {
152
+ newErrors[field.storeKey] = `Value must be at least ${field.min}`;
153
+ } else if (field.max !== undefined && value > field.max) {
154
+ newErrors[field.storeKey] = `Value must be at most ${field.max}`;
155
+ }
156
+ } else if (field.type === 'string') {
157
+ if (field.minLength !== undefined && value.length < field.minLength) {
158
+ newErrors[field.storeKey] = `Must be at least ${field.minLength} characters`;
159
+ } else if (field.maxLength !== undefined && value.length > field.maxLength) {
160
+ newErrors[field.storeKey] = `Must be at most ${field.maxLength} characters`;
161
+ }
162
+ }
163
+
164
+ if (field.validate && !newErrors[field.storeKey]) {
165
+ const validationResult = field.validate(value);
166
+ if (validationResult !== true && validationResult !== '') {
167
+ newErrors[field.storeKey] = typeof validationResult === 'string'
168
+ ? validationResult
169
+ : 'Invalid value';
170
+ }
171
+ }
172
+ });
173
+
174
+ setErrors(newErrors);
175
+
176
+ if (Object.keys(newErrors).length === 0) {
177
+ updateStore(values);
178
+ next(values);
179
+ }
180
+ };
181
+
182
+ const hasErrors = Object.keys(errors).length > 0;
183
+
184
+ return (
185
+ <div className="max-w-prose mx-auto mt-20 mb-20 px-4">
186
+ <h1 className="text-4xl font-bold mb-6">{title}</h1>
187
+
188
+ {description && (
189
+ <article className="prose prose-lg mb-10">
190
+ {description}
191
+ </article>
192
+ )}
193
+
194
+ <div className="mt-12 space-y-12">
195
+ {fields.map((field) => (
196
+ <div key={field.storeKey} className="space-y-2">
197
+ <label className="block text-xl font-medium text-gray-900">
198
+ {field.label}
199
+ {field.type === 'integer' && field.min !== undefined && field.max !== undefined &&
200
+ ` (${field.min}-${field.max})`}
201
+ {field.type === 'float' && field.min !== undefined && field.max !== undefined &&
202
+ ` (${field.min}-${field.max})`}
203
+ </label>
204
+
205
+ {field.prompt && (
206
+ <p className="mb-4 text-base text-gray-700 mb-2">
207
+ {field.prompt}
208
+ </p>
209
+ )}
210
+
211
+ {field.type === 'boolean' && (
212
+ <div className="flex items-center">
213
+ <div
214
+ className={`relative cursor-pointer w-10 h-10 flex items-center justify-center border-2 border-black rounded-xl shadow-[2px_2px_0px_rgba(0,0,0,1)] hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-none ${values[field.storeKey] ? 'bg-blue-600 text-white' : 'bg-white'}`}
215
+ onClick={() => handleChange(field.storeKey, !values[field.storeKey])}
216
+ >
217
+ <input
218
+ type="checkbox"
219
+ checked={!!values[field.storeKey]}
220
+ onChange={(e) => handleChange(field.storeKey, e.target.checked)}
221
+ className="absolute opacity-0 h-0 w-0"
222
+ />
223
+ {values[field.storeKey] && (
224
+ <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
225
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
226
+ </svg>
227
+ )}
228
+ </div>
229
+ <span className="ml-3 text-lg">Enabled</span>
230
+ </div>
231
+ )}
232
+
233
+ {(field.type === 'integer' || field.type === 'float') && (
234
+ <div className="flex items-center space-x-4">
235
+ {field.min !== undefined && field.max !== undefined && (
236
+ <button
237
+ onClick={() => handleChange(
238
+ field.storeKey,
239
+ Math.max(
240
+ field.min || 0,
241
+ (values[field.storeKey] || 0) - (field.step || 1)
242
+ )
243
+ )}
244
+ className="cursor-pointer bg-white h-10 w-10 flex items-center justify-center border-2 border-black font-bold text-black text-lg rounded-full shadow-[2px_2px_0px_rgba(0,0,0,1)] hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-none"
245
+ >
246
+ -
247
+ </button>
248
+ )}
249
+
250
+ <input
251
+ type='text'
252
+ value={values[field.storeKey] || ''}
253
+ onChange={(e) => {
254
+ const rawValue = e.target.value;
255
+ let parsedValue;
256
+
257
+ if (field.type === 'integer') {
258
+ parsedValue = rawValue === '' ? '' : parseInt(rawValue, 10);
259
+ } else if (field.type === 'float') {
260
+ parsedValue = rawValue === '' ? '' : parseFloat(rawValue);
261
+ }
262
+
263
+ if (rawValue === '' || !isNaN(parsedValue as number)) {
264
+ handleChange(field.storeKey, parsedValue);
265
+ }
266
+ }}
267
+ step={field.step || (field.type === 'integer' ? 1 : 0.1)}
268
+ min={field.min}
269
+ max={field.max}
270
+ className="w-24 px-4 py-2 text-center border-2 border-black rounded-xl text-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
271
+ />
272
+
273
+ {field.min !== undefined && field.max !== undefined && (
274
+ <button
275
+ onClick={() => handleChange(
276
+ field.storeKey,
277
+ Math.min(
278
+ field.max || Infinity,
279
+ (values[field.storeKey] || 0) + (field.step || 1)
280
+ )
281
+ )}
282
+ className="cursor-pointer bg-white h-10 w-10 flex items-center justify-center border-2 border-black font-bold text-black text-lg rounded-full shadow-[2px_2px_0px_rgba(0,0,0,1)] hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-none"
283
+ >
284
+ +
285
+ </button>
286
+ )}
287
+ </div>
288
+ )}
289
+
290
+ {field.type === 'string' && (
291
+ <input
292
+ type="text"
293
+ value={values[field.storeKey] || ''}
294
+ onChange={(e) => handleChange(field.storeKey, e.target.value)}
295
+ className="w-full px-4 py-2 border-2 border-black rounded-xl text-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
296
+ />
297
+ )}
298
+
299
+ {errors[field.storeKey] && touched[field.storeKey] && (
300
+ <div className="text-red-500 text-sm mt-1">
301
+ {errors[field.storeKey]}
302
+ </div>
303
+ )}
304
+
305
+ {field.component && (
306
+ <div className="mt-4">
307
+ {React.createElement(field.component, { value: values[field.storeKey] })}
308
+ </div>
309
+ )}
310
+ </div>
311
+ ))}
312
+ </div>
313
+
314
+ <div className="mt-12 flex justify-center">
315
+ <button
316
+ onClick={handleSave}
317
+ disabled={hasErrors}
318
+ className={`bg-white cursor-pointer px-8 py-3 border-2 border-black font-bold text-black text-lg rounded-xl shadow-[2px_2px_0px_rgba(0,0,0,1)] hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-none ${hasErrors ? 'opacity-50 cursor-not-allowed' : ''}`}
319
+ >
320
+ {saveButtonText}
321
+ </button>
322
+ </div>
323
+ </div>
324
+ );
325
+ }
326
+
327
+ export default StoreUI;
package/src/mod.tsx CHANGED
@@ -1,4 +1,3 @@
1
- import './index.css';
2
1
  import { BaseComponentProps, ExperimentConfig } from './utils/common';
3
2
 
4
3
  export type { BaseComponentProps, ExperimentConfig };