173 lines
5.0 KiB
JavaScript
173 lines
5.0 KiB
JavaScript
export const createCalculator = (options = {}) => {
|
||
const state = {
|
||
currentOperand: '',
|
||
previousOperand: '',
|
||
operation: undefined,
|
||
shouldResetDisplay: false,
|
||
allowNegative: options.allowNegative ?? true,
|
||
allowDecimal: options.allowDecimal ?? true,
|
||
allowZero: options.allowZero ?? true,
|
||
decimalPlaces: options.decimalPlaces ?? null,
|
||
}
|
||
|
||
// Expose state for debugging and direct access
|
||
const getState = () => ({ ...state })
|
||
|
||
const clear = () => {
|
||
state.currentOperand = ''
|
||
state.previousOperand = ''
|
||
state.operation = undefined
|
||
state.shouldResetDisplay = false
|
||
return state.currentOperand
|
||
}
|
||
|
||
const deleteNumber = () => {
|
||
if (state.currentOperand.length <= 1) {
|
||
state.currentOperand = '0'
|
||
} else {
|
||
state.currentOperand = state.currentOperand.toString().slice(0, -1)
|
||
}
|
||
return state.currentOperand
|
||
}
|
||
|
||
const appendNumber = (number) => {
|
||
if (number === '.' && !state.allowDecimal) return state.currentOperand
|
||
if (number === '.' && state.currentOperand.includes('.')) return state.currentOperand
|
||
|
||
if (state.shouldResetDisplay) {
|
||
state.currentOperand = number.toString()
|
||
state.shouldResetDisplay = false
|
||
} else {
|
||
if (state.currentOperand === '0' && number !== '.') {
|
||
state.currentOperand = number.toString()
|
||
} else {
|
||
state.currentOperand = state.currentOperand.toString() + number.toString()
|
||
}
|
||
}
|
||
return state.currentOperand
|
||
}
|
||
|
||
const chooseOperation = (operation) => {
|
||
if (operation === '-' && state.currentOperand === '0' && state.previousOperand === '' && state.allowNegative) {
|
||
state.currentOperand = '-'
|
||
return state.currentOperand
|
||
}
|
||
|
||
if (state.currentOperand === '' || state.currentOperand === '-') return state.currentOperand
|
||
|
||
// If there's a previous operation, compute it first
|
||
if (state.previousOperand !== '') {
|
||
compute()
|
||
}
|
||
|
||
state.operation = operation
|
||
state.previousOperand = state.currentOperand
|
||
state.currentOperand = ''
|
||
return state.previousOperand + state.operation
|
||
}
|
||
|
||
const compute = () => {
|
||
// If there's no operation, return the current value
|
||
if (!state.operation) return parseFloat(state.currentOperand || '0')
|
||
|
||
// If there's no current operand but we have a previous one, use previous as current
|
||
if (state.currentOperand === '' && state.previousOperand !== '') {
|
||
state.currentOperand = state.previousOperand
|
||
}
|
||
|
||
const prev = parseFloat(state.previousOperand)
|
||
const current = parseFloat(state.currentOperand)
|
||
|
||
if (isNaN(prev) || isNaN(current)) return 0
|
||
|
||
let result
|
||
switch (state.operation) {
|
||
case '+':
|
||
result = prev + current
|
||
break
|
||
case '-':
|
||
result = prev - current
|
||
break
|
||
case '×':
|
||
result = prev * current
|
||
break
|
||
case '÷':
|
||
if (current === 0) {
|
||
state.currentOperand = 'Error'
|
||
return 0
|
||
}
|
||
result = prev / current
|
||
break
|
||
default:
|
||
return parseFloat(state.currentOperand || '0')
|
||
}
|
||
|
||
// Apply formatting and constraints
|
||
if (state.decimalPlaces !== null) {
|
||
result = Number(result.toFixed(state.decimalPlaces))
|
||
}
|
||
|
||
if (!state.allowDecimal) {
|
||
result = Math.round(result)
|
||
}
|
||
|
||
if (!state.allowNegative && result < 0) {
|
||
result = 0
|
||
}
|
||
|
||
if (!state.allowZero && result === 0) {
|
||
result = 1
|
||
}
|
||
|
||
// Update state
|
||
state.currentOperand = result.toString()
|
||
state.previousOperand = ''
|
||
state.operation = undefined
|
||
state.shouldResetDisplay = true
|
||
|
||
return result
|
||
}
|
||
|
||
// Getter methods for the calculator state
|
||
const getCurrentOperand = () => state.currentOperand
|
||
const getPreviousOperand = () => state.previousOperand
|
||
const getOperation = () => state.operation
|
||
const getDisplayValue = () => {
|
||
if (state.operation && state.previousOperand) {
|
||
return `${state.previousOperand} ${state.operation} ${state.currentOperand || ''}`.trim()
|
||
}
|
||
return state.currentOperand
|
||
}
|
||
|
||
return {
|
||
// Core calculator methods
|
||
clear,
|
||
delete: deleteNumber, // Alias for deleteNumber for compatibility
|
||
deleteNumber,
|
||
appendNumber,
|
||
chooseOperation,
|
||
compute,
|
||
|
||
// State getters
|
||
getDisplayValue,
|
||
getCurrentOperand,
|
||
getPreviousOperand,
|
||
getOperation,
|
||
|
||
// Direct state access (for debugging)
|
||
getState,
|
||
|
||
// Direct property access (for compatibility with CalcInput.jsx)
|
||
get currentOperand() { return state.currentOperand },
|
||
get previousOperand() { return state.previousOperand },
|
||
get operation() { return state.operation },
|
||
get shouldResetDisplay() { return state.shouldResetDisplay },
|
||
|
||
// Setter for direct property access (if needed)
|
||
set currentOperand(value) { state.currentOperand = value },
|
||
set previousOperand(value) { state.previousOperand = value },
|
||
set operation(value) { state.operation = value },
|
||
set shouldResetDisplay(value) { state.shouldResetDisplay = value }
|
||
}
|
||
}
|