npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

time-calculator-web-component

v0.0.4

Published

A web component for time calculation with expression evaluation

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: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

// 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 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:

.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  := ("+" | "-") 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:

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

# 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 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