# Numeric Mask Directive

Diretiva Angular para formatação de valores numéricos com suporte completo a internacionalização e validação. Funciona com moedas, números decimais, notação científica e muito mais.

## Recursos

- ✅ Suporte completo a internacionalização (todos os locales)
- ✅ Formatação automática com separadores de milhar e decimal
- ✅ Validação integrada com Angular Forms
- ✅ Suporte a valores negativos
- ✅ Controle de casas decimais configurável (mínimas e máximas)
- ✅ Truncamento inteligente de decimais (nunca arredonda)
- ✅ Precisão arbitrária com BigNumber
- ✅ Suporte a notação científica com expoentes negativos
- ✅ Tratamento inteligente de colagem
- ✅ Teclado numérico em dispositivos móveis
- ✅ Acessibilidade (ARIA)
- ✅ Standalone component compatível

## Instalação

```bash
npm install @seniorsistemas/angular-components
```

## Uso Básico

### Standalone Components (Recomendado)

```typescript
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { NumericMaskDirective } from '@seniorsistemas/angular-components/numeric-mask';

@Component({
  selector: 'app-example',
  standalone: true,
  imports: [FormsModule, NumericMaskDirective],
  template: `
    <input
      type="text"
      sNumericMask
      [locale]="'pt-BR'"
      [minDecimalPlaces]="2"
      [maxDecimalPlaces]="2"
      [(ngModel)]="value"
    />
  `
})
export class ExampleComponent {
  value: string | null = null;
}
```

## API

### Inputs

| Input | Tipo | Padrão | Descrição |
|-------|------|--------|-----------|
| `locale` | `string \| undefined` | `undefined` | Locale para formatação (ex: 'pt-BR', 'en-US', 'de-DE', 'fr-FR'). Se não fornecido, usa automaticamente o locale do `LocaleService` |
| `minDecimalPlaces` | `number` | `0` | Quantidade mínima de casas decimais a exibir. Na formatação (blur), ajusta o valor para ter no mínimo esse número de casas decimais |
| `maxDecimalPlaces` | `number` | `10` | Quantidade máxima de casas decimais permitidas. Na formatação (blur), limita a exibição a esse número de casas decimais |
| `allowNegative` | `boolean` | `false` | Permite valores negativos. Use `+` e `-` para alternar o sinal |
| `allowScientificNotation` | `boolean` | `true` | Habilita suporte a notação científica (ex: `1e-6`). Quando `false`, bloqueia digitação e colagem de valores com `e`/`E` |
| `textAlign` | `'left' \| 'right'` | `'right'` | Alinhamento do texto dentro do input |
| `min` | `number \| undefined` | `undefined` | Valor mínimo permitido. Quando definido, adiciona validação nativa |
| `max` | `number \| undefined` | `undefined` | Valor máximo permitido. Quando definido, adiciona validação nativa |

## Comportamento

### Truncamento de Decimais

Ao contrário de outras máscaras numéricas que **arredondam**, a `numeric-mask` **trunca** valores que excedem `maxDecimalPlaces`. Isso é crucial para precisão em valores financeiros e científicos.

Exemplo com `maxDecimalPlaces="2"`:
- Valor digitado: `1,999`
- Valor no modelo: `1.999` (sem truncamento)
- Valor exibido (blur): `1,99` (truncado, não arredondado para `2,00`)
- Valor ao receber foco: `1,999` (mostra completo)

### Bloqueio de Caracteres Inválidos

A diretiva bloqueia automaticamente a digitação de caracteres que não sejam:
- Dígitos (0-9)
- Separador decimal (`,` ou `.` conforme o locale)
- Caracteres de controle (Tab, Backspace, Delete, etc.)
- Teclas de seta
- Ctrl/Cmd/Alt

Para **valores negativos**, você pode:
- Pressionar `-` no início do valor para ativar/desativar o sinal negativo
- Pressionar `+` para remover o sinal negativo

### Focus

Quando o input recebe foco:
- Remove a formatação (separadores de milhar)
- Exibe o valor com o separador decimal do locale
- Auto-seleciona todo o valor para fácil substituição
- Mostra o valor **completo** do modelo (sem truncamento)

Exemplo:
- Valor formatado: `1.234,56`
- No focus: `1234,56` (apenas separador decimal, valor completo)

### Modelo vs Formatação

A diretiva separa claramente o valor armazenado do valor exibido:

| Situação | Modelo | Display |
|----------|--------|---------|
| Digitando | `1.999` | `1,999` |
| Após blur | `1.999` | `1,99` (truncado) |
| Com foco | `1.999` | `1,999` (completo) |
| Notação científica | `0.000000000000000000001` | `0,00000000000000000000` (truncado) |

**Garantia:** O modelo **sempre** armazena o valor completo, sem truncamento. As propriedades `minDecimalPlaces` e `maxDecimalPlaces` afetam **apenas** a formatação visual.

### Blur

Quando o input perde foco:
- Reaplica a formatação completa com separadores de milhar
- Valida o valor conforme as regras configuradas

### Colagem (Paste)

- Processa automaticamente valores copiados
- Converte notação científica (ex: `1e-6`) em decimal
- Normaliza separadores para o locale configurado

## Exemplos

### Exemplo Básico

```typescript
@Component({
  selector: 'app-basic',
  standalone: true,
  imports: [FormsModule, NumericMaskDirective],
  template: `
    <input
      sNumericMask
      [locale]="'pt-BR'"
      [minDecimalPlaces]="2"
      [maxDecimalPlaces]="2"
      [(ngModel)]="value"
    />
    <p>Valor: {{ value }}</p>
  `
})
export class BasicComponent {
  value: string | null = null;
}
```

### Exemplo com Valores Negativos

```typescript
@Component({
  selector: 'app-negative',
  standalone: true,
  imports: [FormsModule, NumericMaskDirective],
  template: `
    <input
      sNumericMask
      [allowNegative]="true"
      [locale]="'pt-BR'"
      [minDecimalPlaces]="2"
      [maxDecimalPlaces]="2"
      [(ngModel)]="value"
    />
    <p>Pressione - para alternar o sinal</p>
  `
})
export class NegativeComponent {
  value: string | null = null;
}
```

### Exemplo com Notação Científica

```typescript
@Component({
  selector: 'app-scientific',
  standalone: true,
  imports: [FormsModule, NumericMaskDirective],
  template: `
    <input
      sNumericMask
      [allowScientificNotation]="true"
      [minDecimalPlaces]="0"
      [maxDecimalPlaces]="15"
      [(ngModel)]="value"
      placeholder="Ex: 1e-6, 1.5e-10"
    />
    <p>Valor: {{ value }}</p>
  `
})
export class ScientificComponent {
  value: string | null = null;
}
```

### Exemplo sem Notação Científica

```typescript
@Component({
  selector: 'app-no-scientific',
  standalone: true,
  imports: [FormsModule, NumericMaskDirective],
  template: `
    <input
      sNumericMask
      [allowScientificNotation]="false"
      [locale]="'pt-BR'"
      [minDecimalPlaces]="2"
      [maxDecimalPlaces]="2"
      [(ngModel)]="value"
    />
  `
})
export class NoScientificComponent {
  value: string | null = null;
}
```

### Exemplo com Validação Min/Max

```typescript
@Component({
  selector: 'app-min-max',
  standalone: true,
  imports: [FormsModule, NumericMaskDirective],
  template: `
    <input
      sNumericMask
      [minDecimalPlaces]="2"
      [maxDecimalPlaces]="2"
      [min]="0"
      [max]="1000"
      [(ngModel)]="price"
      #priceInput="ngModel"
    />
    
    @if (priceInput.errors?.['min']) {
      <span class="error">
        Valor mínimo: {{ priceInput.errors?.['min'].min }}
      </span>
    }
    
    @if (priceInput.errors?.['max']) {
      <span class="error">
        Valor máximo: {{ priceInput.errors?.['max'].max }}
      </span>
    }
  `
})
export class MinMaxComponent {
  price: string | null = null;
}
```

### Exemplo com Alta Precisão

```typescript
@Component({
  selector: 'app-high-precision',
  standalone: true,
  imports: [FormsModule, NumericMaskDirective],
  template: `
    <input
      sNumericMask
      [locale]="'pt-BR'"
      [minDecimalPlaces]="0"
      [maxDecimalPlaces]="8"
      [(ngModel)]="cryptoValue"
    />
    <p>Valor da criptomoeda: {{ cryptoValue }}</p>
  `
})
export class HighPrecisionComponent {
  cryptoValue: string | null = null;
}
```

## Integração com LocaleService

A diretiva se integra automaticamente com o `LocaleService` do `@seniorsistemas/angular-components/locale`. Quando o input `locale` não é fornecido, a diretiva:

1. Injeta opcionalmente o `LocaleService`
2. Chama `localeService.get()` para carregar as configurações de locale
3. Usa `getLocaleOptions()?.locale` para obter o locale do usuário
4. Usa 'pt-BR' como fallback se o serviço não estiver disponível

### Com LocaleService

```typescript
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { NumericMaskDirective } from '@seniorsistemas/angular-components/numeric-mask';
import { LocaleModule } from '@seniorsistemas/angular-components/locale';

@Component({
  selector: 'app-with-locale-service',
  standalone: true,
  imports: [FormsModule, NumericMaskDirective, LocaleModule],
  template: `
    <!-- Locale automático do usuário -->
    <input sNumericMask [minDecimalPlaces]="2"
      [maxDecimalPlaces]="2" [(ngModel)]="value" />
  `
})
export class WithLocaleServiceComponent {
  value: string | null = null;
}
```

### Sem LocaleService (Locale Explícito)

```typescript
@Component({
  selector: 'app-explicit-locale',
  standalone: true,
  imports: [FormsModule, NumericMaskDirective],
  template: `
    <!-- Locale explícito, não usa LocaleService -->
    <input sNumericMask [locale]="'en-US'" [minDecimalPlaces]="2"
      [maxDecimalPlaces]="2" [(ngModel)]="value" />
  `
})
export class ExplicitLocaleComponent {
  value: string | null = null;
}
```

## Acessibilidade

A diretiva configura automaticamente:

- `inputmode="decimal"` - Teclado numérico em dispositivos móveis
- `autocomplete="off"` - Desabilita autocomplete

Para melhor acessibilidade, adicione manualmente:

```html
<label for="price">Preço (até 2 casas decimais)</label>
<input
  id="price"
  type="text"
  sNumericMask
  [locale]="'pt-BR'"
  [minDecimalPlaces]="2"
      [maxDecimalPlaces]="2"
  [(ngModel)]="price"
  aria-label="Digite um número com até 2 casas decimais"
/>
```

## Compatibilidade

- Angular 18+
- Todos os navegadores modernos com suporte a `Intl.NumberFormat`

## Licença

MIT
