# NumericMask

![Status](https://img.shields.io/badge/status-stable-brightgreen)

Diretiva Angular para formatação de valores numéricos em campos de input com suporte a internacionalização. Aplica separadores de milhar e decimal conforme o locale configurado, com controle de casas decimais, suporte a valores negativos e notação científica.

## Quando usar

- Campos de entrada de valores numéricos que precisam de formatação automática conforme o locale do usuário
- Campos monetários, de quantidade ou de medidas que exijam separadores de milhar e decimal corretos
- Formulários com validação de valor mínimo e máximo em campos numéricos

## Quando não usar

- Para exibição de valores formatados somente leitura — use o `NumericPipe` do pacote `numeric` com `| async`
- Para inputs do tipo `number` do HTML — a diretiva opera sobre `<input type="text">`
- Quando a entrada é livre (texto, datas, etc.) — use as diretivas de máscara específicas para cada tipo

## Instalação

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

@Component({
    standalone: true,
    imports: [NumericMaskDirective, FormsModule],
})
export class MeuComponent {
    valor: string | null = null;
}
```

## Uso básico

```html
<input
    type="text"
    sNumericMask
    [locale]="'pt-BR'"
    [minDecimalPlaces]="2"
    [maxDecimalPlaces]="2"
    [(ngModel)]="valor"
/>
```

## API

### Inputs

| Propriedade               | Tipo                                                   | Padrão                    | Obrigatório | Descrição                                                                                                                                                         |
| ------------------------- | ------------------------------------------------------ | ------------------------- | :---------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `locale`                  | `string \| undefined`                                  | Locale do `LocaleService` |     Não     | Locale para formatação (ex: `'pt-BR'`, `'en-US'`). Se omitido, usa o locale do `LocaleService`                                                                    |
| `minDecimalPlaces`        | `number`                                               | `0`                       |     Não     | Número mínimo de casas decimais a exibir                                                                                                                          |
| `maxDecimalPlaces`        | `number`                                               | `10`                      |     Não     | Número máximo de casas decimais permitidas                                                                                                                        |
| `allowNegative`           | `boolean`                                              | `false`                   |     Não     | Permite valores negativos. Use as teclas `+` ou `-` para alternar o sinal                                                                                         |
| `allowScientificNotation` | `boolean`                                              | `true`                    |     Não     | Habilita suporte a notação científica (ex: `1.5e10`)                                                                                                              |
| `min`                     | `number \| undefined`                                  | `undefined`               |     Não     | Valor mínimo permitido (validação de formulário)                                                                                                                  |
| `max`                     | `number \| undefined`                                  | `undefined`               |     Não     | Valor máximo permitido (validação de formulário)                                                                                                                  |
| `blockDecimalOverflow`    | `boolean`                                              | `true`                    |     Não     | Bloqueia a digitação e a colagem de casas decimais além de `maxDecimalPlaces`. Quando `maxDecimalPlaces` é `0`, o próprio separador decimal não pode ser digitado |
| `decimalRoundingMode`     | `'preserve' \| 'truncate' \| 'roundUp' \| 'roundDown'` | `'preserve'`              |     Não     | Estratégia aplicada no `blur` quando `blockDecimalOverflow` é `false`. Sem efeito enquanto `blockDecimalOverflow` for `true`                                      |

### Tipos

```typescript
// O valor do modelo (ngModel / FormControl) é sempre string no formato decimal internacional
// Exemplo: "1234567.89" para o valor 1.234.567,89 em pt-BR
type NumericMaskModelValue = string | null;
```

### Erros de validação emitidos

```typescript
// Valor negativo quando allowNegative = false
{
    negativeNotAllowed: true;
}

// Valor abaixo do mínimo
{
    min: {
        min: number;
        actual: number;
    }
}

// Valor acima do máximo
{
    max: {
        max: number;
        actual: number;
    }
}

// Casas decimais acima do máximo
{
    excessiveDecimalPlaces: {
        max: number;
        actual: number;
    }
}
```

## Comportamento de excesso de casas decimais

Por padrão (`blockDecimalOverflow = true`), a digitação e a colagem de valores com mais casas decimais que `maxDecimalPlaces` são bloqueadas no momento da entrada: dígitos excedentes são ignorados imediatamente (sem esperar o `blur`) e, quando `maxDecimalPlaces` é `0`, o próprio separador decimal não pode ser digitado.

Desligando o bloqueio (`[blockDecimalOverflow]="false"`), a digitação volta a ser livre e o input `decimalRoundingMode` passa a controlar o que acontece ao perder o foco (`blur`):

| `decimalRoundingMode` | Comportamento no `blur`                                                                                                                                     |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `'preserve'` (padrão) | Comportamento legado: a exibição é truncada, mas o modelo mantém a precisão total do que foi digitado                                                       |
| `'truncate'`          | Corta as casas excedentes tanto na exibição quanto no modelo, sem arredondar (sempre em direção a zero)                                                     |
| `'roundUp'`           | Arredonda sempre para o próximo valor representável **acima** (ceiling), mesmo que o excedente seja mínimo — não é arredondamento para o valor mais próximo |
| `'roundDown'`         | Arredonda sempre para o próximo valor representável **abaixo** (floor), pelo mesmo motivo                                                                   |

```html
<!-- Padrão: bloqueia a digitação acima de 2 casas decimais -->
<input
    type="text"
    sNumericMask
    [maxDecimalPlaces]="2"
    [(ngModel)]="valor"
/>

<!-- Comportamento legado explícito (digitação livre, modelo com precisão total) -->
<input
    type="text"
    sNumericMask
    [maxDecimalPlaces]="2"
    [blockDecimalOverflow]="false"
    decimalRoundingMode="preserve"
    [(ngModel)]="valor"
/>

<!-- Trunca no blur (corta sem arredondar) -->
<input
    type="text"
    sNumericMask
    [maxDecimalPlaces]="2"
    [blockDecimalOverflow]="false"
    decimalRoundingMode="truncate"
    [(ngModel)]="valor"
/>

<!-- Arredonda sempre para cima no blur -->
<input
    type="text"
    sNumericMask
    [maxDecimalPlaces]="2"
    [blockDecimalOverflow]="false"
    decimalRoundingMode="roundUp"
    [(ngModel)]="valor"
/>

<!-- Arredonda sempre para baixo no blur -->
<input
    type="text"
    sNumericMask
    [maxDecimalPlaces]="2"
    [blockDecimalOverflow]="false"
    decimalRoundingMode="roundDown"
    [(ngModel)]="valor"
/>
```

## Exemplos

### Campo monetário com 2 casas decimais fixas

```html
<input
    type="text"
    sNumericMask
    locale="pt-BR"
    [minDecimalPlaces]="2"
    [maxDecimalPlaces]="2"
    placeholder="0,00"
    [(ngModel)]="valorMonetario"
/>
```

### Campo com valores negativos

```html
<input
    type="text"
    sNumericMask
    locale="pt-BR"
    [minDecimalPlaces]="2"
    [maxDecimalPlaces]="2"
    [allowNegative]="true"
    placeholder="-0,00"
    [(ngModel)]="variacao"
/>
<!-- Pressione + ou - para alternar entre positivo e negativo -->
```

### Campo com validação de intervalo (porcentagem)

```html
<input
    type="text"
    sNumericMask
    locale="pt-BR"
    [minDecimalPlaces]="2"
    [maxDecimalPlaces]="2"
    [min]="0"
    [max]="100"
    placeholder="0,00"
    [(ngModel)]="percentual"
/>
```

### Campo com locale en-US

```html
<input
    type="text"
    sNumericMask
    locale="en-US"
    [minDecimalPlaces]="2"
    [maxDecimalPlaces]="2"
    placeholder="0.00"
    [(ngModel)]="valor"
/>
```

### Campo com casas decimais variáveis

```html
<input
    type="text"
    sNumericMask
    locale="pt-BR"
    [minDecimalPlaces]="0"
    [maxDecimalPlaces]="4"
    placeholder="0"
    [(ngModel)]="quantidade"
/>
```

## Acessibilidade

- Define automaticamente `inputmode="decimal"` no elemento host para exibir teclado numérico em dispositivos móveis
- Define `autocomplete="off"` para evitar sugestões incorretas do navegador
- Suporte a `Backspace`, `Tab`, `Enter`, `Escape`, `ArrowLeft`, `ArrowRight`, `Delete`, `Home` e `End`
- As teclas `+` e `-` alternam o sinal quando `allowNegative` está ativo

## Componentes relacionados

- [`NumericPipe`](../numeric/README.md) — formatação de valores numéricos para exibição somente leitura
- [`NumberInput`](../number-input/src/lib/number-input/README.md) — diretiva descontinuada, use `sNumericMask` em seu lugar

## Migração

### Mudança de comportamento padrão

A partir desta versão, `blockDecimalOverflow` passa a ser `true` por padrão: a digitação e a colagem de valores com mais casas decimais que `maxDecimalPlaces` passam a ser bloqueadas no momento da entrada, em vez de aceitas livremente e truncadas apenas na exibição ao perder o foco.

Para restaurar o comportamento anterior (digitação livre, modelo com precisão total, exibição truncada no `blur`):

```html
<input
    type="text"
    sNumericMask
    [maxDecimalPlaces]="2"
    [blockDecimalOverflow]="false"
    decimalRoundingMode="preserve"
    [(ngModel)]="valor"
/>
```

`decimalRoundingMode="preserve"` já é o padrão — pode ser omitido, mas deixá-lo explícito reforça a intenção do comportamento legado.

