1- import { useEffect , useState } from "react" ;
1+ import {
2+ forwardRef ,
3+ useEffect ,
4+ useImperativeHandle ,
5+ useRef ,
6+ useState ,
7+ } from "react" ;
28
39import {
410 StyledButton ,
@@ -8,99 +14,145 @@ import {
814 StyledInputOutput ,
915 StyledInputOutputContents ,
1016} from "./styles" ;
17+ import { BorderedAppContentHandles } from "../../components/BorderedApp/BorderedApp" ;
18+
19+ export type CalculatorHandles = BorderedAppContentHandles < HTMLDivElement > ;
1120
1221interface CalculatorProps { }
1322
14- // eslint-disable-next-line no-empty-pattern
15- function Calculator ( { } : CalculatorProps ) {
16- const [ input , setInput ] = useState < string > ( "" ) ;
17- const [ output , setOutput ] = useState < string > ( "" ) ;
18-
19- // Whenever the input changes we'll check if it forms a valid
20- // calculation, and if so, add it to the output
21- useEffect ( ( ) => {
22- try {
23- const res = eval ( input ) ;
24- setOutput ( res ) ;
25- } catch {
26- /* empty */
27- }
28- } , [ input ] ) ;
29-
30- function Button ( {
31- displayChar,
32- mathematicalChar,
33- action,
34- } : {
35- mathematicalChar : string | number ;
36- displayChar ?: string ;
37- action : "append-to-end" | "remove-from-end" | "evaluate" | "clear" ;
38- } ) {
39- if ( displayChar === undefined ) {
40- displayChar = mathematicalChar . toString ( ) ;
41- }
23+ const isDigit = ( key : string ) => / ^ [ 0 - 9 ] $ / . test ( key ) ;
24+ const operators = new Set ( [ "+" , "-" , "*" , "/" , "%" ] ) ;
25+
26+ export const Calculator = forwardRef < CalculatorHandles , CalculatorProps > (
27+ ( _props , ref ) => {
28+ const elementRef = useRef < HTMLDivElement > ( null ) ;
29+ const [ input , setInput ] = useState < string > ( "" ) ;
30+ const [ output , setOutput ] = useState < string > ( "" ) ;
31+ const appendToInput = ( value : string ) =>
32+ setInput ( ( current ) => current + value ) ;
33+ const removeFromEnd = ( count : number = 1 ) =>
34+ setInput ( ( current ) => current . slice ( 0 , - count ) ) ;
35+ const clear = ( ) => setInput ( "" ) ;
36+ const evaluate = ( ) => {
37+ try {
38+ setInput ( eval ( input ) ) ;
39+ setOutput ( "" ) ;
40+ } catch {
41+ setOutput ( "Invalid" ) ;
42+ }
43+ } ;
44+
45+ useImperativeHandle ( ref , ( ) => ( {
46+ onParentKeyDown ( e ) {
47+ const { key } = e ;
48+
49+ if ( isDigit ( key ) || operators . has ( key ) ) {
50+ appendToInput ( key ) ;
51+ return ;
52+ }
53+
54+ switch ( key ) {
55+ case "." :
56+ case "(" :
57+ case ")" :
58+ appendToInput ( key ) ;
59+ break ;
4260
43- function handleClick ( ) {
44- switch ( action ) {
45- case "append-to-end" :
46- setInput ( input + mathematicalChar . toString ( ) ) ;
47- break ;
48- case "remove-from-end" :
49- setInput ( String ( input ) . slice ( 0 , - 1 ) ) ;
50- break ;
51- case "evaluate" :
52- try {
53- setInput ( eval ( input ) ) ;
54- setOutput ( "" ) ;
55- } catch {
56- setOutput ( "Invalid" ) ;
57- }
58- break ;
59- case "clear" :
60- setInput ( "" ) ;
61+ case "=" :
62+ case "Enter" :
63+ evaluate ( ) ;
64+ break ;
65+
66+ case "Backspace" :
67+ removeFromEnd ( ) ;
68+ break ;
69+
70+ case "Escape" :
71+ clear ( ) ;
72+ break ;
73+ }
74+ } ,
75+ element : elementRef . current ,
76+ } ) ) ;
77+
78+ // Whenever the input changes we'll check if it forms a valid
79+ // calculation, and if so, add it to the output
80+ useEffect ( ( ) => {
81+ try {
82+ const res = eval ( input ) ;
83+ setOutput ( res ) ;
84+ } catch {
85+ /* empty */
86+ }
87+ } , [ input ] ) ;
88+
89+ function Button ( {
90+ displayChar,
91+ mathematicalChar,
92+ action,
93+ } : {
94+ mathematicalChar : string | number ;
95+ displayChar ?: string ;
96+ action : "append-to-end" | "remove-from-end" | "evaluate" | "clear" ;
97+ } ) {
98+ if ( displayChar === undefined ) {
99+ displayChar = mathematicalChar . toString ( ) ;
61100 }
101+
102+ function handleClick ( ) {
103+ switch ( action ) {
104+ case "append-to-end" :
105+ return appendToInput ( mathematicalChar . toString ( ) ) ;
106+ break ;
107+ case "remove-from-end" :
108+ return removeFromEnd ( ) ;
109+ break ;
110+ case "evaluate" :
111+ return evaluate ( ) ;
112+ case "clear" :
113+ return clear ( ) ;
114+ }
115+ }
116+
117+ return (
118+ < StyledButton onClick = { handleClick } >
119+ < StyledButtonContent > { displayChar } </ StyledButtonContent >
120+ </ StyledButton >
121+ ) ;
62122 }
63123
64124 return (
65- < StyledButton onClick = { handleClick } >
66- < StyledButtonContent > { displayChar } </ StyledButtonContent >
67- </ StyledButton >
125+ < StyledCalc ref = { elementRef } >
126+ < StyledInputOutput direction = "input" >
127+ < StyledInputOutputContents > { input } </ StyledInputOutputContents >
128+ </ StyledInputOutput >
129+ < StyledInputOutput direction = "output" >
130+ < StyledInputOutputContents > { output } </ StyledInputOutputContents >
131+ </ StyledInputOutput >
132+ < StyledButtons >
133+ < Button mathematicalChar = { "AC" } action = "clear" />
134+ < div />
135+ < div />
136+ < Button mathematicalChar = "/" displayChar = "÷" action = "append-to-end" />
137+
138+ < Button mathematicalChar = { 7 } action = "append-to-end" />
139+ < Button mathematicalChar = { 8 } action = "append-to-end" />
140+ < Button mathematicalChar = { 9 } action = "append-to-end" />
141+ < Button mathematicalChar = "*" displayChar = "x" action = "append-to-end" />
142+ < Button mathematicalChar = { 4 } action = "append-to-end" />
143+ < Button mathematicalChar = { 5 } action = "append-to-end" />
144+ < Button mathematicalChar = { 6 } action = "append-to-end" />
145+ < Button mathematicalChar = "-" action = "append-to-end" />
146+ < Button mathematicalChar = { 1 } action = "append-to-end" />
147+ < Button mathematicalChar = { 2 } action = "append-to-end" />
148+ < Button mathematicalChar = { 3 } action = "append-to-end" />
149+ < Button mathematicalChar = "+" action = "append-to-end" />
150+ < Button mathematicalChar = { 0 } action = "append-to-end" />
151+ < Button mathematicalChar = "." action = "append-to-end" />
152+ < Button mathematicalChar = "←" action = "remove-from-end" />
153+ < Button mathematicalChar = "=" action = "evaluate" />
154+ </ StyledButtons >
155+ </ StyledCalc >
68156 ) ;
69- }
70-
71- return (
72- < StyledCalc >
73- < StyledInputOutput direction = "input" >
74- < StyledInputOutputContents > { input } </ StyledInputOutputContents >
75- </ StyledInputOutput >
76- < StyledInputOutput direction = "output" >
77- < StyledInputOutputContents > { output } </ StyledInputOutputContents >
78- </ StyledInputOutput >
79- < StyledButtons >
80- < Button mathematicalChar = { "AC" } action = "clear" />
81- < div />
82- < div />
83- < Button mathematicalChar = "/" displayChar = "÷" action = "append-to-end" />
84-
85- < Button mathematicalChar = { 7 } action = "append-to-end" />
86- < Button mathematicalChar = { 8 } action = "append-to-end" />
87- < Button mathematicalChar = { 9 } action = "append-to-end" />
88- < Button mathematicalChar = "*" displayChar = "x" action = "append-to-end" />
89- < Button mathematicalChar = { 4 } action = "append-to-end" />
90- < Button mathematicalChar = { 5 } action = "append-to-end" />
91- < Button mathematicalChar = { 6 } action = "append-to-end" />
92- < Button mathematicalChar = "-" action = "append-to-end" />
93- < Button mathematicalChar = { 1 } action = "append-to-end" />
94- < Button mathematicalChar = { 2 } action = "append-to-end" />
95- < Button mathematicalChar = { 3 } action = "append-to-end" />
96- < Button mathematicalChar = "+" action = "append-to-end" />
97- < Button mathematicalChar = { 0 } action = "append-to-end" />
98- < Button mathematicalChar = "." action = "append-to-end" />
99- < Button mathematicalChar = "←" action = "remove-from-end" />
100- < Button mathematicalChar = "=" action = "evaluate" />
101- </ StyledButtons >
102- </ StyledCalc >
103- ) ;
104- }
105-
106- export default Calculator ;
157+ } ,
158+ ) ;
0 commit comments