Skip to content

Commit e6aa0f2

Browse files
Merge pull request #188 from Pramod-Munnoli/feat/calculator-ui
feat: add calculator UI, operations, and keyboard support
2 parents 95d0dd9 + eb027c9 commit e6aa0f2

File tree

3 files changed

+377
-44
lines changed

3 files changed

+377
-44
lines changed

projects/calculator/index.html

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,48 @@
11
<!doctype html>
22
<html lang="en">
3+
34
<head>
4-
<meta charset="utf-8" />
5-
<meta name="viewport" content="width=device-width, initial-scale=1" />
6-
<title>Calculator | Vanilla Verse</title>
7-
<link rel="stylesheet" href="styles.css" />
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>Calculator</title>
8+
<link rel="stylesheet" href="styles.css">
89
</head>
10+
911
<body>
10-
<header><h1>Calculator</h1></header>
11-
<main id="app">
12-
<!-- TODO: Implement basic calculator UI (display + buttons 0-9, ., +, -, ×, ÷, =, C, ±, %) -->
13-
<!-- TODO: Wire up interactions and keyboard support -->
14-
<!-- TODO: Handle edge cases (divide by zero, multiple decimals, operator chaining) -->
15-
</main>
16-
<script defer src="main.js"></script>
12+
<div class="calculator">
13+
<div class="display">
14+
<div class="previous-operand" id="previousOperand"></div>
15+
<div class="current-operand" id="currentOperand">0</div>
16+
</div>
17+
18+
<div class="buttons">
19+
<button class="function" aria-label="All Clear" data-action="clear">AC</button>
20+
<button class="function" aria-label="Backspace" data-action="backspace"></button>
21+
<button class="function" aria-label="Toggle Sign" data-action="toggle-sign">+/-</button>
22+
<button class="operator" aria-label="Divide" data-action="operator" data-operator="÷">÷</button>
23+
24+
<button aria-label="7" data-number="7">7</button>
25+
<button aria-label="8" data-number="8">8</button>
26+
<button aria-label="9" data-number="9">9</button>
27+
<button class="operator" aria-label="Multiply" data-action="operator" data-operator="×">×</button>
28+
29+
<button aria-label="4" data-number="4">4</button>
30+
<button aria-label="5" data-number="5">5</button>
31+
<button aria-label="6" data-number="6">6</button>
32+
<button class="operator" aria-label="Subtract" data-action="operator" data-operator=""></button>
33+
34+
<button aria-label="1" data-number="1">1</button>
35+
<button aria-label="2" data-number="2">2</button>
36+
<button aria-label="3" data-number="3">3</button>
37+
<button class="operator" aria-label="Add" data-action="operator" data-operator="+">+</button>
38+
39+
<button class="zero" aria-label="0" data-number="0">0</button>
40+
<button aria-label="Decimal" data-action="decimal">.</button>
41+
<button class="function" aria-label="Percentage" data-action="percentage">%</button>
42+
<button class="equals" aria-label="Equals" data-action="equals">=</button>
43+
</div>
44+
</div>
45+
<script src="main.js"></script>
1746
</body>
47+
1848
</html>

projects/calculator/main.js

Lines changed: 224 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,224 @@
1-
/**
2-
* TODO: Calculator logic
3-
* - Render/display current input and result
4-
* - Support operations: +, -, *, /, %, ±, decimal
5-
* - Keyboard support (numbers, operators, Enter for =, Backspace)
6-
* - Prevent invalid inputs (multiple decimals, divide by zero)
7-
* Optional:
8-
* - Expression history
9-
* - Theme toggle
10-
*/
11-
12-
document.addEventListener('DOMContentLoaded', () => {
13-
console.log('Calculator app ready');
14-
// TODO: Build UI or select existing DOM nodes and attach event listeners
15-
});
1+
2+
const currentOperandElement = document.getElementById('currentOperand');
3+
const previousOperandElement = document.getElementById('previousOperand');
4+
const buttons = document.querySelectorAll('button');
5+
6+
let currentOperand = '0';
7+
let previousOperand = '';
8+
let operation = null;
9+
let shouldResetScreen = false;
10+
11+
function init() {
12+
buttons.forEach(button => {
13+
button.addEventListener('click', () => {
14+
button.classList.add('press-animation');
15+
setTimeout(() => button.classList.remove('press-animation'), 200);
16+
17+
handleButtonClick(button);
18+
});
19+
});
20+
21+
document.addEventListener('keydown', handleKeyboardInput);
22+
23+
updateDisplay();
24+
}
25+
26+
function handleButtonClick(button) {
27+
if (button.dataset.number !== undefined) {
28+
appendNumber(button.dataset.number);
29+
} else if (button.dataset.action === 'operator') {
30+
chooseOperation(button.dataset.operator);
31+
} else if (button.dataset.action === 'decimal') {
32+
appendDecimal();
33+
} else if (button.dataset.action === 'equals') {
34+
compute();
35+
} else if (button.dataset.action === 'clear') {
36+
clear();
37+
} else if (button.dataset.action === 'backspace') {
38+
backspace();
39+
} else if (button.dataset.action === 'toggle-sign') {
40+
toggleSign();
41+
} else if (button.dataset.action === 'percentage') {
42+
percentage();
43+
}
44+
}
45+
46+
function handleKeyboardInput(e) {
47+
if (e.key === 'Enter' || e.key === 'Escape' || e.key === 'Backspace') {
48+
e.preventDefault();
49+
}
50+
51+
if (e.key >= '0' && e.key <= '9') {
52+
appendNumber(e.key);
53+
}
54+
else if (e.key === '.') {
55+
appendDecimal();
56+
}
57+
else if (e.key === '+' || e.key === '-' || e.key === '*' || e.key === '/') {
58+
const operatorMap = {
59+
'+': '+',
60+
'-': '−',
61+
'*': '×',
62+
'/': '÷'
63+
};
64+
chooseOperation(operatorMap[e.key]);
65+
}
66+
else if (e.key === 'Enter' || e.key === '=') {
67+
compute();
68+
}
69+
else if (e.key === 'Escape') {
70+
clear();
71+
}
72+
else if (e.key === 'Backspace') {
73+
backspace();
74+
}
75+
}
76+
77+
function appendNumber(number) {
78+
if (shouldResetScreen) {
79+
currentOperand = '';
80+
shouldResetScreen = false;
81+
}
82+
83+
if (currentOperand === '0') {
84+
currentOperand = number;
85+
} else if (currentOperand.length < 12) {
86+
currentOperand += number;
87+
}
88+
89+
updateDisplay();
90+
}
91+
92+
function appendDecimal() {
93+
if (shouldResetScreen) {
94+
currentOperand = '0';
95+
shouldResetScreen = false;
96+
}
97+
98+
if (!currentOperand.includes('.')) {
99+
currentOperand += '.';
100+
}
101+
102+
updateDisplay();
103+
}
104+
105+
function chooseOperation(op) {
106+
if (currentOperand === '') return;
107+
108+
if (previousOperand !== '') {
109+
compute();
110+
}
111+
112+
operation = op;
113+
previousOperand = currentOperand;
114+
shouldResetScreen = true;
115+
updateDisplay();
116+
}
117+
118+
function compute() {
119+
if (operation === null || previousOperand === '') return;
120+
121+
let computation;
122+
const prev = parseFloat(previousOperand);
123+
const current = parseFloat(currentOperand);
124+
125+
if (isNaN(prev) || isNaN(current)) {
126+
clear();
127+
return;
128+
}
129+
130+
switch (operation) {
131+
case '+': computation = prev + current; break;
132+
case '−': computation = prev - current; break;
133+
case '×': computation = prev * current; break;
134+
case '÷':
135+
if (current === 0) {
136+
currentOperand = 'Error';
137+
previousOperand = '';
138+
operation = null;
139+
shouldResetScreen = true;
140+
updateDisplay();
141+
return;
142+
}
143+
computation = prev / current;
144+
break;
145+
default: return;
146+
}
147+
148+
currentOperand = roundResult(computation);
149+
operation = null;
150+
previousOperand = '';
151+
shouldResetScreen = true;
152+
updateDisplay();
153+
}
154+
155+
function clear() {
156+
currentOperand = '0';
157+
previousOperand = '';
158+
operation = null;
159+
shouldResetScreen = false;
160+
updateDisplay();
161+
}
162+
163+
function backspace() {
164+
if (currentOperand.length > 1) {
165+
currentOperand = currentOperand.slice(0, -1);
166+
} else {
167+
currentOperand = '0';
168+
}
169+
updateDisplay();
170+
}
171+
172+
function toggleSign() {
173+
if (currentOperand !== '0') {
174+
currentOperand = currentOperand.startsWith('-')
175+
? currentOperand.slice(1)
176+
: '-' + currentOperand;
177+
}
178+
updateDisplay();
179+
}
180+
181+
function percentage() {
182+
currentOperand = (parseFloat(currentOperand) / 100).toString();
183+
shouldResetScreen = true;
184+
updateDisplay();
185+
}
186+
187+
function roundResult(number) {
188+
const strNumber = number.toExponential(12);
189+
const roundedNumber = parseFloat(strNumber);
190+
191+
return Number.isInteger(roundedNumber)
192+
? roundedNumber.toString()
193+
: roundedNumber.toString();
194+
}
195+
196+
function formatNumber(number) {
197+
if (number === 'Error') return number;
198+
199+
const stringNumber = number.toString();
200+
201+
if (stringNumber.length > 12) {
202+
return parseFloat(number).toExponential(6);
203+
}
204+
205+
return stringNumber;
206+
}
207+
208+
function updateDisplay() {
209+
currentOperandElement.textContent = formatNumber(currentOperand);
210+
211+
if (operation != null) {
212+
previousOperandElement.textContent = `${previousOperand} ${operation}`;
213+
} else {
214+
previousOperandElement.textContent = '';
215+
}
216+
217+
if (currentOperand === 'Error') {
218+
currentOperandElement.classList.add('error');
219+
} else {
220+
currentOperandElement.classList.remove('error');
221+
}
222+
}
223+
224+
init();

0 commit comments

Comments
 (0)