time-calculator-web-component
v0.0.4
Published
A web component for time calculation with expression evaluation
Maintainers
Readme
⏰ Time Calculator Web Component
A framework-agnostic time calculator web component built with TypeScript following Domain-Driven Design (DDD) principles. Supports complex time expressions with proper operator precedence and provides a beautiful, accessible UI.
🚀 Quick Start
Installation
# Install via npm (when published)
npm install time-calculator-web-component
# Or use directly from CDN
<script type="module" src="https://unpkg.com/time-calculator-web-component/dist/index.esm.js"></script>Basic Usage
<!DOCTYPE html>
<html>
<head>
<script type="module" src="dist/index.esm.js"></script>
</head>
<body>
<!-- Simple usage -->
<time-calculator open></time-calculator>
<!-- With configuration -->
<time-calculator
mode="popover"
locale="pt-BR"
persist-position="true">
</time-calculator>
</body>
</html>✨ Features
🎯 Core Functionality
- Multiple Time Formats: H:mm (1:30), unit format (1h 30m), pure numbers
- Arithmetic Operations: Addition, subtraction, multiplication, division
- Operator Precedence: Proper mathematical precedence with parentheses support
- Context-Aware Numbers: Numbers interpreted as minutes in +/- operations, scalars in */÷
- Unlimited Hours: Support for hours > 23 (e.g., 500:30)
- Negative Time: Full support for negative time calculations
🎨 User Interface
- Draggable Window: Fully draggable floating calculator window
- Keyboard Navigation: Complete keyboard-only operation support
- Multiple Display Modes: H:mm format or pure minutes
- Accessibility: ARIA labels, focus trap, screen reader support
- Position Persistence: Optional localStorage position saving
- Responsive Design: Works on mobile, tablet, and desktop
⚙️ Technical
- Framework Agnostic: Works with React, Vue, Angular, or vanilla HTML
- TypeScript: Full TypeScript support with complete type definitions
- Zero Dependencies: No runtime dependencies, lightweight bundle
- DDD Architecture: Clean domain-driven design with separation of concerns
- Comprehensive Testing: >90% test coverage on core functionality
📋 Expression Examples
Basic Operations
"10:00 + 0:30" // → 10:30
"10:00 + 70m" // → 11:10
"30m + 30m" // → 1:00
"1h 30m * 2" // → 3:00
"90 / 3" // → 0:30 (90 minutes ÷ 3)Complex Expressions
"(1h + 30m) * 2 - 15m" // → 2:45
"500:30 + 20:30 - 521:00" // → 0:00
"-1:30 + 2h" // → 0:30
"1h 30m × 2 ÷ 3" // → 1:00Supported Input Formats
- H:mm Format:
1:30,500:30,-2:15 - Unit Format:
1h 30m,45m,2h,90min - Mixed:
1:30 + 45m,2h - 0:15 - Pure Numbers:
90 + 30(interpreted as minutes in +/-)
🔧 API Reference
Web Component Attributes
| Attribute | Type | Default | Description |
|-----------|------|---------|-------------|
| open | boolean | false | Opens the calculator |
| mode | "popover" | "fixed" | "popover" | Window behavior mode |
| locale | "pt-BR" | "en-US" | "pt-BR" | Locale for number parsing |
| decimal-separator | "," | "." | auto | Decimal separator override |
| rounding | "floor" | "ceil" | "halfUp" | "halfUp" | Rounding mode for division |
| allow-negative | boolean | true | Allow negative results |
| max-abs-minutes | number | null | Maximum absolute minutes |
| close-on-outside-click | boolean | auto | Close when clicking outside |
| persist-position | boolean | false | Save position to localStorage |
JavaScript API
// Get the web component
const calculator = document.querySelector('time-calculator');
// Control visibility
calculator.open();
calculator.close();
calculator.toggle();
// Set input and evaluate
calculator.setInput('1h 30m + 45m');
calculator.evaluate();
// Get results
const result = calculator.getInput();
console.log(result); // "1h 30m + 45m"Programmatic Usage (Headless)
import { evaluateExpression, ExpressionEvaluator } from 'time-calculator-web-component';
// Quick evaluation
const result = evaluateExpression('1h 30m + 45m');
console.log(result.formatted); // "2:15"
console.log(result.minutes); // 135
// Advanced usage with options
const evaluator = new ExpressionEvaluator({
locale: 'pt-BR',
rounding: 'ceil',
allowNegative: false
});
const result = evaluator.evaluateExpression('1h 30m * 2,5');
console.log(result.formatted); // "3:45"🎛️ Configuration Options
Window Modes
popover(default): Closes when clicking outside, floating behaviorfixed: Stays open when clicking outside, modal-like behavior
Localization
pt-BR: Portuguese (Brazil) - comma as decimal separatoren-US: English (US) - dot as decimal separator
Rounding Modes
halfUp(default): Round to nearest minute (0.5 → 1)floor: Always round down (0.9 → 0)ceil: Always round up (0.1 → 1)
⌨️ Keyboard Shortcuts
When the calculator is focused:
| Shortcut | Action |
|----------|--------|
| Enter | Evaluate expression |
| Esc | Close calculator |
| Ctrl+M / Cmd+M | Toggle display mode |
| Ctrl+C / Cmd+C | Copy result |
| Ctrl+Shift+C | Copy result as minutes |
| Ctrl+Z / Cmd+Z | Undo |
| Ctrl+Y / Cmd+Y | Redo |
🎨 Customization
CSS Classes
The component exposes several CSS classes for styling:
.time-calc-container { /* Main container */ }
.time-calc-header { /* Header with title and buttons */ }
.time-calc-body { /* Body with input and result */ }
.time-calc-input { /* Input field */ }
.time-calc-result { /* Result display */ }
.time-calc-button { /* Header buttons */ }
.time-calc-overlay { /* Background overlay */ }Custom Styling
<time-calculator
container-class="my-custom-container"
header-class="my-custom-header"
input-class="my-custom-input">
</time-calculator>Themes
The component uses CSS custom properties for theming:
time-calculator {
--calc-bg-color: #ffffff;
--calc-border-color: #e5e7eb;
--calc-text-color: #111827;
--calc-accent-color: #3b82f6;
}🏗️ Framework Integration
React
import 'time-calculator-web-component';
function App() {
const [isOpen, setIsOpen] = useState(false);
return (
<time-calculator
open={isOpen}
onClose={() => setIsOpen(false)}
locale="en-US"
/>
);
}Vue
<template>
<time-calculator
:open="isOpen"
@close="isOpen = false"
locale="pt-BR"
/>
</template>
<script setup>
import 'time-calculator-web-component';
const isOpen = ref(false);
</script>Angular
// app.module.ts
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import 'time-calculator-web-component';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
// ...
})
// component.html
<time-calculator
[open]="isOpen"
(close)="isOpen = false">
</time-calculator>🧪 Grammar & Parsing
The calculator uses a formal grammar for expression parsing:
expr := term (("+" | "-") term)*
term := factor (("*" | "/") factor)*
factor := group | duration | scalarOrMinutes | unary
group := "(" expr ")"
duration := TIME_HMM
| NUMBER "h" [" " NUMBER "m"]
| NUMBER "m"
scalarOrMinutes := NUMBER
unary := ("+" | "-") factorToken Types
- TIME_HMM:
-?\d+:[0-5]\d(unlimited hours, valid minutes) - NUMBER: Integer or decimal (locale-aware separator)
- UNITS:
h|hora|horas|m|min|minuto|minutos(normalized) - OPERATORS:
+,-,*,/,×,÷,(,)
🔍 Error Handling
The calculator provides detailed error messages:
try {
const result = evaluateExpression('1h + ');
} catch (error) {
console.log(error.message); // "Expected time, duration, or number"
}Common errors:
- Syntax errors: Invalid expression format
- Division by zero: Attempting to divide by 0
- Overflow errors: Result exceeds
maxAbsMinuteslimit - Invalid time format: Malformed time input (e.g., "1:60")
🚀 Development
Building from Source
# Clone and install
git clone <repository-url>
cd time-calculator
yarn install
# Build
yarn build # or: npx rollup -c
# Run tests
yarn test # or: npx jest
# Serve demo
yarn serve # or: python3 -m http.server 8000Project Structure
src/
├── domain/ # Core domain logic (DDD)
│ ├── types.ts # Type definitions
│ ├── time-amount.ts # TimeAmount entity
│ ├── lexer.ts # Expression lexer
│ ├── parser.ts # Expression parser
│ ├── evaluator.ts # Expression evaluator
│ └── formatters.ts # Output formatters
├── ui/ # User interface
│ ├── controller.ts # UI controller
│ └── web-component.ts # Web component
├── utils/ # Utilities
│ ├── event-emitter.ts
│ ├── focus-trap.ts
│ └── storage.ts
└── __tests__/ # Test filesArchitecture Principles
- Domain-Driven Design: Clear separation between domain logic and UI
- Framework Agnostic: Core logic independent of UI framework
- Type Safety: Full TypeScript coverage with strict types
- Testability: High test coverage with isolated unit tests
- Accessibility: WCAG 2.1 AA compliance
📄 License
MIT License - see LICENSE file for details.
🤝 Contributing
Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and development process.
🐛 Issues
Found a bug or have a feature request? Please open an issue on our GitHub Issues page.
Made with ❤️ by gustavodamazio
