@@ -699,136 +699,169 @@ function getCalculatorHTML() {
699699 </style>
700700 ` ;
701701}
702- let calculatorKeyboardAdded = false ;
702+
703703function initCalculator ( ) {
704- const display = document . getElementById ( 'calcDisplay' ) ;
705- let currentValue = '0' ;
706- let previousValue = '' ;
707- let operation = '' ;
708-
709- document . querySelectorAll ( '.calc-btn' ) . forEach ( btn => {
710- btn . addEventListener ( 'click' , ( ) => {
711- const value = btn . getAttribute ( 'data-value' ) ;
712- const action = btn . getAttribute ( 'data-action' ) ;
704+ const display = document . getElementById ( "calcDisplay" ) ;
705+ if ( ! display ) return ;
706+ let expression = "" ;
707+
708+ function update ( ) {
709+ display . textContent = expression || "0" ;
710+ }
711+
712+ function format ( expr ) {
713+ return expr
714+ . replace ( / ÷ / g, "/" )
715+ . replace ( / × / g, "*" )
716+ . replace ( / \^ / g, "**" ) ;
717+ }
718+
719+ function safeEval ( expr ) {
720+ try {
721+ if ( ! expr ) return "" ;
722+ let result = eval ( format ( expr ) ) ;
723+ if ( result === undefined ) return "" ;
724+ if ( isNaN ( result ) ) return "Error" ;
725+ return String ( result ) ;
726+ } catch {
727+ return "Error" ;
728+ }
729+ }
730+
731+ function applyFunction ( type ) {
732+ try {
733+ let value = eval ( format ( expression || "0" ) ) ;
734+ let result ;
735+ switch ( type ) {
736+ case "sin" : result = Math . sin ( value ) ; break ;
737+ case "cos" : result = Math . cos ( value ) ; break ;
738+ case "tan" : result = Math . tan ( value ) ; break ;
739+ case "sqrt" : result = Math . sqrt ( value ) ; break ;
740+ case "square" : result = value * value ; break ;
741+ case "inv" : result = 1 / value ; break ;
742+ }
743+ if ( isNaN ( result ) ) return "Error" ;
744+ return String ( result ) ;
745+ } catch {
746+ return "Error" ;
747+ }
748+ }
749+
750+
751+ function clearIfFinished ( ) {
752+ if ( expression === "Error" || expression === "NaN" ) {
753+ expression = "" ;
754+ }
755+ }
756+
757+ document . querySelectorAll ( ".calc-btn" ) . forEach ( ( btn ) => {
758+ btn . addEventListener ( "click" , ( ) => {
759+ clearIfFinished ( ) ;
713760
714- if ( value ) {
715- handleNumber ( value ) ;
716- } else if ( action ) {
717- handleAction ( action ) ;
761+ const value = btn . dataset . value ;
762+ const action = btn . dataset . action ;
763+
764+ if ( value !== undefined ) {
765+ if ( value === "." ) {
766+
767+ const lastOperand = expression . split ( / [ \+ \- \* \/ \^ \( \) ] / ) . pop ( ) ;
768+ if ( lastOperand . includes ( "." ) ) return ;
769+ }
770+ expression += value ;
771+ update ( ) ;
772+ return ;
718773 }
719-
720- updateDisplay ( ) ;
774+
775+ if ( ! action ) return ;
776+
777+
778+ switch ( action ) {
779+ case "clear" :
780+ expression = "" ;
781+ break ;
782+ case "delete" :
783+ if ( expression === "Infinity" || expression === "-Infinity" ) {
784+ expression = "" ;
785+ } else {
786+ expression = expression . slice ( 0 , - 1 ) ;
787+ }
788+ break ;
789+ case "=" :
790+ expression = safeEval ( expression ) ;
791+ break ;
792+ case "sin" :
793+ case "cos" :
794+ case "tan" :
795+ case "sqrt" :
796+ case "square" :
797+ case "inv" :
798+ expression = applyFunction ( action ) ;
799+ break ;
800+ case "^" :
801+ case "+" :
802+ case "-" :
803+ case "*" :
804+ case "/" :
805+
806+ const lastChar = expression . slice ( - 1 ) ;
807+ if ( [ "+" , "-" , "*" , "/" , "^" ] . includes ( lastChar ) ) {
808+ expression = expression . slice ( 0 , - 1 ) + action ;
809+ } else {
810+ expression += action ;
811+ }
812+ break ;
813+ default :
814+ expression += action ;
815+ }
816+ update ( ) ;
721817 } ) ;
722818 } ) ;
723- if ( ! calculatorKeyboardAdded ) {
724- document . addEventListener ( "keydown" , ( e ) => {
725- const display = document . getElementById ( "calcDisplay" ) ;
726- if ( ! display ) return ;
727819
820+ document . addEventListener ( "keydown" , ( e ) => {
728821 const key = e . key ;
822+ if ( ! document . getElementById ( "calcDisplay" ) ) return ;
729823
730- if (
731- key === "Enter" ||
732- key === " " ||
733- key === "Backspace" ||
734- key === "Escape" ||
735- key === "=" ||
736- key === "+" ||
737- key === "-" ||
738- key === "*" ||
739- key === "/" ||
740- key === "^" ||
741- key === "." ||
742- / ^ [ 0 - 9 ] $ / . test ( key ) ||
743- key . startsWith ( "Arrow" )
744- ) {
824+ // Whitelist allowed keys to prevent typing letters
825+ const allowedKeys = [ "Enter" , "Backspace" , "Escape" , "=" , "+" , "-" , "*" , "/" , "^" , "." , "(" , ")" ] ;
826+ if ( allowedKeys . includes ( key ) || / ^ [ 0 - 9 ] $ / . test ( key ) ) {
745827 e . preventDefault ( ) ;
828+ } else {
829+ return ;
746830 }
747831
832+ clearIfFinished ( ) ;
833+
748834 if ( / ^ [ 0 - 9 ] $ / . test ( key ) ) {
749- handleNumber ( key ) ;
750- }
751- else if ( key === "." ) {
752- handleNumber ( "." ) ;
753- }
754- else if ( [ "+" , "-" , "*" , "/" ] . includes ( key ) ) {
755- handleAction ( key ) ;
756- }
757- else if ( key === "^" ) {
758- handleAction ( "**" ) ;
759- }
760- else if ( key === "Enter" || key === "=" ) {
761- handleAction ( "=" ) ;
762- }
763- else if ( key === "Backspace" ) {
764- handleAction ( "delete" ) ;
765- }
766- else if ( key === "Escape" || key . toLowerCase ( ) === "c" ) {
767- handleAction ( "clear" ) ;
835+ expression += key ;
836+ } else if ( key === "." ) {
837+ const lastOperand = expression . split ( / [ \+ \- \* \/ \^ \( \) ] / ) . pop ( ) ;
838+ if ( ! lastOperand . includes ( "." ) ) {
839+ expression += "." ;
840+ }
841+ } else if ( [ "+" , "-" , "*" , "/" , "^" ] . includes ( key ) ) {
842+ const lastChar = expression . slice ( - 1 ) ;
843+ if ( [ "+" , "-" , "*" , "/" , "^" ] . includes ( lastChar ) ) {
844+ expression = expression . slice ( 0 , - 1 ) + key ;
845+ } else {
846+ expression += key ;
847+ }
848+ } else if ( key === ")" || key === "(" ) {
849+ expression += key ;
850+ } else if ( key === "Enter" || key === "=" ) {
851+ expression = safeEval ( expression ) ;
852+ } else if ( key === "Backspace" ) {
853+ if ( expression === "Infinity" || expression === "-Infinity" ) {
854+ expression = "" ;
855+ } else {
856+ expression = expression . slice ( 0 , - 1 ) ;
857+ }
858+ } else if ( key === "Escape" || key . toLowerCase ( ) === "c" ) {
859+ expression = "" ;
768860 }
769-
770- updateDisplay ( ) ;
861+ update ( ) ;
771862 } ) ;
772863
773- calculatorKeyboardAdded = true ;
774- }
775-
776- function handleNumber ( num ) {
777- if ( currentValue === '0' || currentValue === 'Error' ) {
778- currentValue = num ;
779- } else {
780- currentValue += num ;
781- }
782- }
783-
784- function handleAction ( action ) {
785- if ( action === 'clear' ) {
786- currentValue = '0' ;
787- previousValue = '' ;
788- operation = '' ;
789- } else if ( action === 'delete' ) {
790- currentValue = currentValue . slice ( 0 , - 1 ) || '0' ;
791- } else if ( action === '=' ) {
792- calculate ( ) ;
793- } else {
794- if ( previousValue && operation ) {
795- calculate ( ) ;
796- }
797- previousValue = currentValue ;
798- currentValue = '0' ;
799- operation = action ;
800- }
801- }
802-
803- function calculate ( ) {
804- try {
805- const prev = parseFloat ( previousValue ) ;
806- const curr = parseFloat ( currentValue ) ;
807- let result ;
808-
809- switch ( operation ) {
810- case '+' : result = prev + curr ; break ;
811- case '-' : result = prev - curr ; break ;
812- case '*' : result = prev * curr ; break ;
813- case '/' : result = prev / curr ; break ;
814- case '**' : result = Math . pow ( prev , curr ) ; break ;
815- default : return ;
816- }
817-
818- currentValue = result . toString ( ) ;
819- previousValue = '' ;
820- operation = '' ;
821- } catch ( e ) {
822- currentValue = 'Error' ;
823- }
824- }
825-
826- function updateDisplay ( ) {
827- const display = document . getElementById ( 'calcDisplay' ) ;
828- if ( display ) {
829- display . textContent = currentValue ;
830- }
831- }
864+ update ( ) ;
832865}
833866
834867// ============================================
0 commit comments