UNPKG

time-calculator-web-component

Version:
367 lines (290 loc) โ€ข 10.1 kB
# โฐ 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 ```bash # 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 ```html <!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 ```javascript "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 ```javascript "(1h + 30m) * 2 - 15m" // โ†’ 2:45 "500:30 + 20:30 - 521:00" // โ†’ 0:00 "-1:30 + 2h" // โ†’ 0:30 "1h 30m ร— 2 รท 3" // โ†’ 1:00 ``` ### Supported 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 ```javascript // 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) ```javascript 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 behavior - **`fixed`**: Stays open when clicking outside, modal-like behavior ### Localization - **`pt-BR`**: Portuguese (Brazil) - comma as decimal separator - **`en-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: ```css .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 ```html <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: ```css time-calculator { --calc-bg-color: #ffffff; --calc-border-color: #e5e7eb; --calc-text-color: #111827; --calc-accent-color: #3b82f6; } ``` ## ๐Ÿ—๏ธ Framework Integration ### React ```jsx import 'time-calculator-web-component'; function App() { const [isOpen, setIsOpen] = useState(false); return ( <time-calculator open={isOpen} onClose={() => setIsOpen(false)} locale="en-US" /> ); } ``` ### Vue ```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 ```typescript // 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: ```ebnf 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 := ("+" | "-") factor ``` ### Token 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: ```javascript 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 `maxAbsMinutes` limit - **Invalid time format**: Malformed time input (e.g., "1:60") ## ๐Ÿš€ Development ### Building from Source ```bash # 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 8000 ``` ### Project 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 files ``` ### Architecture 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](LICENSE) file for details. ## ๐Ÿค Contributing Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) 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](https://github.com/your-username/time-calculator/issues) page. --- Made with โค๏ธ by gustavodamazio