Overview
Focus the input below to view or edit the formula ((2 + 3) * 4). When blurred, the field shows the calculated result (20). Only one input is visible to the user at any time.
Click a preset formula to load it (or focus the field to inspect the formula):
Features & Component Anatomy
<calc-input> wraps three standard <input type="text"> elements in Light DOM so native HTML <form> submissions and constraint validation work automatically.
<calc-input name="size" value="2 + 3">
├─ <input type="text" name="size" hidden>
├─ <input type="text" name="size--formula" hidden>
└─ <input type="text" name="size--result">
🔄 Focus / Blur Switching
Shows the calculated result when blurred and seamlessly switches back to the original formula when focused. Never shows more than one input at a time.
📦 Configurable Form Submission
Control what gets sent to the server via submit: send only the formula (formula-only), only the result (result-only), or all values (formula or result, with your preferred format in the original input).
⚙️ Configurable Separator
Customize the -- separator used in name--formula and name--result via separator="..." (including an empty string separator="").
🛡️ Safe Expression Parser
Evaluates arithmetic (+, -, *, /, %, ^), nested parentheses, unary operators, and math functions without eval().
🚨 Native Form Validation
Unparsable formulas (like 2 + ) keep showing the formula on blur, display a red invalid border, and call setCustomValidity() to block form submission.
🔒 Clean DOM Attributes
Initial formulas can be passed via the value attribute (e.g. value="2 + 3") without ever writing user-entered values back into the DOM attributes.
Installation & Quick Start
npm install calc-input
Import the package to register the <calc-input> custom element:
import 'calc-input';
Then use <calc-input> anywhere in your HTML or forms:
<calc-input name="size" value="2 + 3"></calc-input>
The 3 Underlying Inputs
Behind the scenes, <calc-input name="size" value="2 + 3"> creates three <input type="text"> elements inside the custom element:
<calc-input name="size" value="2 + 3">
<!-- 1. Primary input: always hidden; value controlled by `submit` ("formula", "result", "formula-only", or "result-only") -->
<input type="text" name="size" hidden>
<!-- 2. Formula input: always holds "2 + 3"; visible when focused (or when invalid) -->
<input type="text" name="size--formula" hidden>
<!-- 3. Result input: always holds "5"; visible when blurred (and valid) -->
<input type="text" name="size--result">
</calc-input>
Why Three Inputs?
When a parent <form> is submitted, FormData automatically receives size (either the formula or result depending on submit), size--formula (always the raw formula), and size--result (always the calculated result). At any given time, two of the inputs have the hidden attribute so the user only ever sees one input.
Controlling what gets submitted: You can configure what gets sent to the server using the submit attribute. Set submit="formula" (default) or submit="result" to submit all three fields (with your preferred format in size), or set submit="formula-only" / submit="result-only" to omit the name attribute from the --formula and --result inputs so only the primary size field is submitted.
Attributes & Configuration
| Attribute | Default | Description |
|---|---|---|
name |
"" |
Base name used to generate the three inner inputs: <name>, <name><separator>formula, and <name><separator>result. |
value |
"" |
Initial formula string (e.g. value="2 + 3"). Parsed on load so the blurred input shows 5 and focusing shows 2 + 3. User edits update live input properties without writing back to the DOM attribute. |
submit |
"formula" |
Configures what the primary <input type="text" name="<name>"> contains and whether the extra --formula and --result fields are submitted. Accepts "formula", "result", "formula-only", or "result-only". When set to "formula-only" or "result-only", the --formula and --result fields are not submitted. |
separator |
"--" |
Configures the separator between <name> and formula / result. An empty string (separator="") is accepted (producing e.g. sizeformula and sizeresult). |
placeholder |
"" |
Placeholder text forwarded to the visible inner inputs. |
disabled / readonly / required |
false |
Standard form control states forwarded to the underlying inputs. |
Validation & Direct CSS Styling
When an unparsable formula is entered (such as 2 + or (2 + 3), blurring the field keeps showing the original formula instead of switching to the result input, applies invalid styling (red border), and sets custom validity via setCustomValidity() to prevent form submission. Because <calc-input> uses display: contents and its inner <input> elements inherit box and typography properties (border: inherit, padding: inherit, background: inherit, border-radius: inherit, etc.), any styles applied directly onto <calc-input> automatically apply to the inputs inside it.
/* Styles applied directly onto calc-input are inherited by its inner inputs */
calc-input {
border: 1px solid #d1d5db;
border-radius: 8px;
padding: 0.625rem 0.75rem;
}
calc-input:focus-within {
border-color: #2563eb;
}
calc-input:not(:focus-within):has(input:invalid) {
border-color: #ef4444;
background-color: #fef2f2;
}
JavaScript API
const el = document.querySelector('calc-input');
// Read formula, evaluated result, and configured submit value
console.log(el.formula); // "2 + 3"
console.log(el.result); // "5"
console.log(el.value); // "2 + 3" (for "formula"/"formula-only") or "5" (for "result"/"result-only")
console.log(el.isValid); // true
// Inspect structured AST & tokens
console.log(el.getEvaluation());
// Check constraint validity
console.log(el.checkValidity());
1. Live 3-Inputs & AST Inspector
Type a formula, focus and blur the input, and watch how the three underlying <input type="text"> elements update in real time without mutating the value attribute in the DOM.
Parsed Tokens & DOM Attribute Check:
Evaluation & AST (el.getEvaluation()):
2. Form Submission & submit Attribute
Compare submit="formula" (default), submit="result", submit="formula-only", and submit="result-only" in a real <form>. When formula-only or result-only is set, the extra --formula and --result fields are omitted from form submission.
3. Custom separator Attribute
The separator between name and formula / result defaults to --, and can be customized via the separator attribute (including an empty string separator="").
separator="...")
Click a separator option to update the component dynamically:
submit="...")
Configure what the primary name="price" input holds and whether extra fields are submitted:
4. Invalid Formula Handling
When the user enters an unparsable formula (e.g. 2 + ), the component keeps displaying the original formula when blurred, highlights the input with a red border, and sets setCustomValidity() so its form cannot be submitted.
Notice that even though the input above is blurred, it still displays 2 + with a red border. Focus it and type 3 at the end (making it 2 + 3), then blur to see it resolve to 5!
5. Custom Styling
Because <calc-input> uses display: contents and its inner <input> elements inherit box and typography styles, you can apply CSS properties directly onto <calc-input> and they automatically apply to the inputs inside it.
calc-input {
border: 2px solid #3b82f6;
border-radius: 9999px;
padding: 0.625rem 1.25rem;
}
calc-input {
background-color: #0f172a;
color: #4ade80;
border: 1px solid #22c55e;
border-radius: 4px;
font-family: monospace;
}
calc-input {
font-size: 1.25rem;
font-weight: 700;
text-align: right;
width: 240px;
border: 2px solid #059669;
border-radius: 2px;
font-family: monospace;
}