rafatoração de componentes de entrada

This commit is contained in:
Luiz Silva 2026-01-29 11:27:08 -03:00
parent 6aedf2469f
commit 27c9e4d5e2
45 changed files with 1295 additions and 2605 deletions

2
dist/eli-vue.css vendored

File diff suppressed because one or more lines are too long

2407
dist/eli-vue.es.js vendored

File diff suppressed because it is too large Load diff

30
dist/eli-vue.umd.js vendored

File diff suppressed because one or more lines are too long

View file

@ -186,30 +186,27 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
default: undefined;
};
}>> & Readonly<{
"onUpdate:modelValue"?: ((_v: string | null) => any) | undefined;
"onUpdate:value"?: ((_v: string | null) => any) | undefined;
onInput?: ((_v: string | null) => any) | undefined;
onChange?: ((_v: string | null) => any) | undefined;
onFocus?: (() => any) | undefined;
onBlur?: (() => any) | undefined;
onInput?: ((_v: string | null) => any) | undefined;
onAlterar?: ((_v: string | null) => any) | undefined;
"onUpdate:value"?: ((_v: string | null) => any) | undefined;
"onUpdate:modelValue"?: ((_v: string | null) => any) | undefined;
onFoco?: (() => any) | undefined;
onDesfoco?: (() => any) | undefined;
}>, {
placeholder: string;
modelValue: string | null;
value: string | null | undefined;
max: string | undefined;
min: string | undefined;
erro: boolean;
rotulo: string;
modo: "data" | "dataHora" | undefined;
limpavel: boolean;
erro: boolean;
mensagensErro: string | string[];
dica: string;
dicaPersistente: boolean;
min: string | undefined;
max: string | undefined;
densidade: CampoDensidade;
variante: CampoVariante;
value: string | null | undefined;
opcoes: {
rotulo: string;
placeholder?: string;
@ -225,6 +222,9 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
densidade?: import("../../tipos").CampoDensidade;
variante?: import("../../tipos").CampoVariante;
};
placeholder: string;
rotulo: string;
modelValue: string | null;
desabilitado: boolean;
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
declare const _default: typeof __VLS_export;

View file

@ -15,7 +15,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
attrs: {
[x: string]: unknown;
};
emit: ((event: "change", _v: number | null | undefined) => void) & ((event: "focus") => void) & ((event: "blur") => void) & ((event: "input", _v: number | null | undefined) => void) & ((event: "update:value", _v: number | null | undefined) => void);
emit: ((event: "update:value", _v: number | null | undefined) => void) & ((event: "input", _v: number | null | undefined) => void) & ((event: "change", _v: number | null | undefined) => void) & ((event: "focus") => void) & ((event: "blur") => void);
displayValue: import("vue").Ref<string, string>;
isInteiro: import("vue").ComputedRef<boolean>;
onUpdateModelValue: (texto: string) => void;
@ -37,11 +37,11 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
required: true;
};
}>> & Readonly<{
"onUpdate:value"?: ((_v: number | null | undefined) => any) | undefined;
onInput?: ((_v: number | null | undefined) => any) | undefined;
onChange?: ((_v: number | null | undefined) => any) | undefined;
onFocus?: (() => any) | undefined;
onBlur?: (() => any) | undefined;
onInput?: ((_v: number | null | undefined) => any) | undefined;
"onUpdate:value"?: ((_v: number | null | undefined) => any) | undefined;
}>, {
value: number | null | undefined;
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;

View file

@ -15,8 +15,11 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
attrs: {
[x: string]: unknown;
};
emit: ((event: "change", _v: string | null | undefined) => void) & ((event: "focus") => void) & ((event: "blur") => void) & ((event: "input", _v: string | null | undefined) => void) & ((event: "update:value", _v: string | null | undefined) => void);
emit: ((event: "update:value", _v: string | null | undefined) => void) & ((event: "input", _v: string | null | undefined) => void) & ((event: "change", _v: string | null | undefined) => void) & ((event: "focus") => void) & ((event: "blur") => void);
localValue: import("vue").WritableComputedRef<string | null | undefined, string | null | undefined>;
inputHtmlType: import("vue").ComputedRef<"text" | "email" | "url">;
inputMode: import("vue").ComputedRef<string | undefined>;
onInput: (e: Event) => void;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
"update:value": (_v: EntradaTexto["value"]) => true;
/** Compat Vue2 (v-model padrão: value + input) */
@ -35,11 +38,11 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
required: true;
};
}>> & Readonly<{
"onUpdate:value"?: ((_v: string | null | undefined) => any) | undefined;
onInput?: ((_v: string | null | undefined) => any) | undefined;
onChange?: ((_v: string | null | undefined) => any) | undefined;
onFocus?: (() => any) | undefined;
onBlur?: (() => any) | undefined;
onInput?: ((_v: string | null | undefined) => any) | undefined;
"onUpdate:value"?: ((_v: string | null | undefined) => any) | undefined;
}>, {
value: string | null | undefined;
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;

View file

@ -10,6 +10,7 @@ export declare const registryTabelaCelulas: {
placeholder?: string;
} & {
limiteCaracteres?: number;
formato?: "texto" | "email" | "url" | "telefone" | "cpfCnpj" | "cep";
}>;
required: true;
};
@ -17,8 +18,11 @@ export declare const registryTabelaCelulas: {
attrs: {
[x: string]: unknown;
};
emit: ((event: "change", _v: string | null | undefined) => void) & ((event: "focus") => void) & ((event: "blur") => void) & ((event: "input", _v: string | null | undefined) => void) & ((event: "update:value", _v: string | null | undefined) => void);
emit: ((event: "update:value", _v: string | null | undefined) => void) & ((event: "input", _v: string | null | undefined) => void) & ((event: "change", _v: string | null | undefined) => void) & ((event: "focus") => void) & ((event: "blur") => void);
localValue: import("vue").WritableComputedRef<string | null | undefined, string | null | undefined>;
inputHtmlType: import("vue").ComputedRef<"text" | "email" | "url">;
inputMode: import("vue").ComputedRef<string | undefined>;
onInput: (e: Event) => void;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
"update:value": (_v: string | null | undefined) => true;
input: (_v: string | null | undefined) => true;
@ -36,15 +40,16 @@ export declare const registryTabelaCelulas: {
placeholder?: string;
} & {
limiteCaracteres?: number;
formato?: "texto" | "email" | "url" | "telefone" | "cpfCnpj" | "cep";
}>;
required: true;
};
}>> & Readonly<{
"onUpdate:value"?: ((_v: string | null | undefined) => any) | undefined;
onInput?: ((_v: string | null | undefined) => any) | undefined;
onChange?: ((_v: string | null | undefined) => any) | undefined;
onFocus?: (() => any) | undefined;
onBlur?: (() => any) | undefined;
onInput?: ((_v: string | null | undefined) => any) | undefined;
"onUpdate:value"?: ((_v: string | null | undefined) => any) | undefined;
}>, {
value: string | null | undefined;
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@ -68,7 +73,7 @@ export declare const registryTabelaCelulas: {
attrs: {
[x: string]: unknown;
};
emit: ((event: "change", _v: number | null | undefined) => void) & ((event: "focus") => void) & ((event: "blur") => void) & ((event: "input", _v: number | null | undefined) => void) & ((event: "update:value", _v: number | null | undefined) => void);
emit: ((event: "update:value", _v: number | null | undefined) => void) & ((event: "input", _v: number | null | undefined) => void) & ((event: "change", _v: number | null | undefined) => void) & ((event: "focus") => void) & ((event: "blur") => void);
displayValue: import("vue").Ref<string, string>;
isInteiro: import("vue").ComputedRef<boolean>;
onUpdateModelValue: (texto: string) => void;
@ -95,11 +100,11 @@ export declare const registryTabelaCelulas: {
required: true;
};
}>> & Readonly<{
"onUpdate:value"?: ((_v: number | null | undefined) => any) | undefined;
onInput?: ((_v: number | null | undefined) => any) | undefined;
onChange?: ((_v: number | null | undefined) => any) | undefined;
onFocus?: (() => any) | undefined;
onBlur?: (() => any) | undefined;
onInput?: ((_v: number | null | undefined) => any) | undefined;
"onUpdate:value"?: ((_v: number | null | undefined) => any) | undefined;
}>, {
value: number | null | undefined;
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@ -121,8 +126,8 @@ export declare const registryTabelaCelulas: {
dicaPersistente?: boolean;
min?: string;
max?: string;
densidade?: import("../../tipos/campo.js").CampoDensidade;
variante?: import("../../tipos/campo.js").CampoVariante;
densidade?: import("../../tipos/entrada.js").CampoDensidade;
variante?: import("../../tipos/entrada.js").CampoVariante;
}>;
required: false;
default: undefined;
@ -168,11 +173,11 @@ export declare const registryTabelaCelulas: {
default: undefined;
};
densidade: {
type: import("vue").PropType<import("../../tipos/campo.js").CampoDensidade>;
type: import("vue").PropType<import("../../tipos/entrada.js").CampoDensidade>;
default: undefined;
};
variante: {
type: import("vue").PropType<import("../../tipos/campo.js").CampoVariante>;
type: import("vue").PropType<import("../../tipos/entrada.js").CampoVariante>;
default: undefined;
};
min: {
@ -203,8 +208,8 @@ export declare const registryTabelaCelulas: {
dicaPersistente?: boolean;
min?: string;
max?: string;
densidade?: import("../../tipos/campo.js").CampoDensidade;
variante?: import("../../tipos/campo.js").CampoVariante;
densidade?: import("../../tipos/entrada.js").CampoDensidade;
variante?: import("../../tipos/entrada.js").CampoVariante;
}>;
desabilitadoEfetivo: import("vue").ComputedRef<boolean>;
emitCompatFocus: () => void;
@ -237,8 +242,8 @@ export declare const registryTabelaCelulas: {
dicaPersistente?: boolean;
min?: string;
max?: string;
densidade?: import("../../tipos/campo.js").CampoDensidade;
variante?: import("../../tipos/campo.js").CampoVariante;
densidade?: import("../../tipos/entrada.js").CampoDensidade;
variante?: import("../../tipos/entrada.js").CampoVariante;
}>;
required: false;
default: undefined;
@ -284,11 +289,11 @@ export declare const registryTabelaCelulas: {
default: undefined;
};
densidade: {
type: import("vue").PropType<import("../../tipos/campo.js").CampoDensidade>;
type: import("vue").PropType<import("../../tipos/entrada.js").CampoDensidade>;
default: undefined;
};
variante: {
type: import("vue").PropType<import("../../tipos/campo.js").CampoVariante>;
type: import("vue").PropType<import("../../tipos/entrada.js").CampoVariante>;
default: undefined;
};
min: {
@ -300,30 +305,27 @@ export declare const registryTabelaCelulas: {
default: undefined;
};
}>> & Readonly<{
"onUpdate:modelValue"?: ((_v: string | null) => any) | undefined;
"onUpdate:value"?: ((_v: string | null) => any) | undefined;
onInput?: ((_v: string | null) => any) | undefined;
onChange?: ((_v: string | null) => any) | undefined;
onFocus?: (() => any) | undefined;
onBlur?: (() => any) | undefined;
onInput?: ((_v: string | null) => any) | undefined;
onAlterar?: ((_v: string | null) => any) | undefined;
"onUpdate:value"?: ((_v: string | null) => any) | undefined;
"onUpdate:modelValue"?: ((_v: string | null) => any) | undefined;
onFoco?: (() => any) | undefined;
onDesfoco?: (() => any) | undefined;
}>, {
placeholder: string;
modelValue: string | null;
value: string | null | undefined;
max: string | undefined;
min: string | undefined;
erro: boolean;
rotulo: string;
modo: "data" | "dataHora" | undefined;
limpavel: boolean;
erro: boolean;
mensagensErro: string | string[];
dica: string;
dicaPersistente: boolean;
densidade: import("../../tipos/campo.js").CampoDensidade;
variante: import("../../tipos/campo.js").CampoVariante;
min: string | undefined;
max: string | undefined;
densidade: import("../../tipos/entrada.js").CampoDensidade;
variante: import("../../tipos/entrada.js").CampoVariante;
value: string | null | undefined;
opcoes: {
rotulo: string;
placeholder?: string;
@ -336,9 +338,12 @@ export declare const registryTabelaCelulas: {
dicaPersistente?: boolean;
min?: string;
max?: string;
densidade?: import("../../tipos/campo.js").CampoDensidade;
variante?: import("../../tipos/campo.js").CampoVariante;
densidade?: import("../../tipos/entrada.js").CampoDensidade;
variante?: import("../../tipos/entrada.js").CampoVariante;
};
placeholder: string;
rotulo: string;
modelValue: string | null;
desabilitado: boolean;
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
};

View file

@ -41,6 +41,11 @@ export type PadroesEntradas = {
texto: tipoPadraoEntrada<string | null | undefined, {
/** Limite máximo de caracteres permitidos (se definido) */
limiteCaracteres?: number;
/**
* Formato/máscara aplicada ao texto.
* Obs: o `value` SEMPRE será o texto formatado (o que aparece no input).
*/
formato?: "texto" | "email" | "url" | "telefone" | "cpfCnpj" | "cep";
}>;
numero: tipoPadraoEntrada<number | null | undefined, {
/** Unidade de medida (ex.: "kg", "m³") */

View file

@ -0,0 +1,2 @@
/** Formata CEP no padrão 00000-000 */
export declare function formatarCep(valor: string): string;

View file

@ -1,146 +0,0 @@
import { PropType } from "vue";
import type { CampoDensidade, CampoOpcao, CampoOpcaoBruta, CampoTipo, CampoValor, CampoValorMultiplo, CampoVariante } from "../../tipos";
declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
/**
* Aceita valor simples (text-like) ou lista de valores (checkbox/select multiple).
* O componente não converte tipos automaticamente: mantém o que receber.
*/
modelValue: {
type: PropType<CampoValor | CampoValorMultiplo>;
default: string;
};
type: {
type: PropType<CampoTipo>;
default: string;
};
label: StringConstructor;
placeholder: StringConstructor;
disabled: BooleanConstructor;
error: BooleanConstructor;
errorMessages: {
type: PropType<string | string[]>;
default: () => never[];
};
hint: StringConstructor;
persistentHint: BooleanConstructor;
rows: {
type: NumberConstructor;
default: number;
};
/**
* Para select/radio/checkbox.
* Aceita lista normalizada ({ label, value }) ou valores primitivos.
*/
options: {
type: PropType<Array<CampoOpcaoBruta>>;
default: () => never[];
};
clearable: BooleanConstructor;
variant: {
type: PropType<CampoVariante>;
default: string;
};
density: {
type: PropType<CampoDensidade>;
default: string;
};
color: {
type: StringConstructor;
default: string;
};
row: BooleanConstructor;
showPasswordToggle: BooleanConstructor;
multiple: BooleanConstructor;
chips: BooleanConstructor;
}>, {
attrs: {
[x: string]: unknown;
};
value: import("vue").WritableComputedRef<CampoValor | CampoValorMultiplo, CampoValor | CampoValorMultiplo>;
isTextLike: import("vue").ComputedRef<boolean>;
inputHtmlType: import("vue").ComputedRef<"text" | "password">;
inputMode: import("vue").ComputedRef<"tel" | "decimal" | "numeric" | undefined>;
internalColor: import("vue").ComputedRef<string | undefined>;
showPassword: import("vue").Ref<boolean, boolean>;
togglePassword: () => void;
onInput: (e: Event) => void;
onFocus: () => void;
onBlur: () => void;
computedItems: import("vue").ComputedRef<CampoOpcao[]>;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("update:modelValue" | "change" | "focus" | "blur")[], "update:modelValue" | "change" | "focus" | "blur", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
/**
* Aceita valor simples (text-like) ou lista de valores (checkbox/select multiple).
* O componente não converte tipos automaticamente: mantém o que receber.
*/
modelValue: {
type: PropType<CampoValor | CampoValorMultiplo>;
default: string;
};
type: {
type: PropType<CampoTipo>;
default: string;
};
label: StringConstructor;
placeholder: StringConstructor;
disabled: BooleanConstructor;
error: BooleanConstructor;
errorMessages: {
type: PropType<string | string[]>;
default: () => never[];
};
hint: StringConstructor;
persistentHint: BooleanConstructor;
rows: {
type: NumberConstructor;
default: number;
};
/**
* Para select/radio/checkbox.
* Aceita lista normalizada ({ label, value }) ou valores primitivos.
*/
options: {
type: PropType<Array<CampoOpcaoBruta>>;
default: () => never[];
};
clearable: BooleanConstructor;
variant: {
type: PropType<CampoVariante>;
default: string;
};
density: {
type: PropType<CampoDensidade>;
default: string;
};
color: {
type: StringConstructor;
default: string;
};
row: BooleanConstructor;
showPasswordToggle: BooleanConstructor;
multiple: BooleanConstructor;
chips: BooleanConstructor;
}>> & Readonly<{
"onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
onChange?: ((...args: any[]) => any) | undefined;
onFocus?: ((...args: any[]) => any) | undefined;
onBlur?: ((...args: any[]) => any) | undefined;
}>, {
color: string;
type: CampoTipo;
variant: CampoVariante;
disabled: boolean;
error: boolean;
persistentHint: boolean;
clearable: boolean;
row: boolean;
showPasswordToggle: boolean;
multiple: boolean;
chips: boolean;
modelValue: CampoValor | CampoValorMultiplo;
errorMessages: string | string[];
rows: number;
options: CampoOpcaoBruta[];
density: CampoDensidade;
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
declare const _default: typeof __VLS_export;
export default _default;

View file

@ -1 +0,0 @@
export { default as EliInput } from "./EliInput.vue";

View file

@ -1 +0,0 @@
export declare function formatarCep(v: string): string;

View file

@ -1,9 +0,0 @@
export declare function somenteNumeros(valor: string): string;
export declare function formatarDecimal(valor: string): string;
/**
* Formatação para percentual:
* - remove '%' caso venha junto (ex: colar "10%")
* - mantém apenas dígitos e vírgula (no máximo uma)
*/
export declare function formatarPorcentagem(valor: string): string;
export declare function formatarMoeda(valor: string): string;

View file

@ -22,7 +22,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
};
}>, {
rotuloStatus: import("vue").ComputedRef<CartaoStatus>;
corStatus: import("vue").ComputedRef<"primary" | "error" | "secondary" | "success">;
corStatus: import("vue").ComputedRef<"primary" | "secondary" | "success" | "error">;
classeStatus: import("vue").ComputedRef<string>;
onClick: () => void;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {

View file

@ -1 +0,0 @@
export { default as EliEntradaDataHora } from "../EliEntrada/EliEntradaDataHora.vue";

View file

@ -1,18 +1,9 @@
type Habilidade = "vue" | "react";
declare const __VLS_export: import("vue").DefineComponent<{}, {
nome: import("vue").Ref<string, string>;
email: import("vue").Ref<string, string>;
documento: import("vue").Ref<string, string>;
estado: import("vue").Ref<string[], string[]>;
telefone: import("vue").Ref<string, string>;
mensagem: import("vue").Ref<string, string>;
senha: import("vue").Ref<string, string>;
cor: import("vue").Ref<"azul" | "verde" | null, "azul" | "verde" | null>;
habilidades: import("vue").Ref<Habilidade[], Habilidade[]>;
idade: import("vue").Ref<string, string>;
altura: import("vue").Ref<string, string>;
cep: import("vue").Ref<string, string>;
valor: import("vue").Ref<string, string>;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {
EliBotao: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
color: {
@ -144,131 +135,59 @@ declare const __VLS_export: import("vue").DefineComponent<{}, {
badge: string | number | undefined;
radius: import("../../tipos/indicador.js").IndicadorPresetRaio | import("../../tipos/indicador.js").CssLength;
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
EliInput: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
modelValue: {
type: import("vue").PropType<import("../../tipos/campo.js").CampoValor | import("../../tipos/campo.js").CampoValorMultiplo>;
default: string;
EliEntradaTexto: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
value: {
type: import("vue").PropType<string | null | undefined>;
default: undefined;
};
type: {
type: import("vue").PropType<import("../../tipos/campo.js").CampoTipo>;
default: string;
opcoes: {
type: import("vue").PropType<{
rotulo: string;
placeholder?: string;
} & {
limiteCaracteres?: number;
formato?: "texto" | "email" | "url" | "telefone" | "cpfCnpj" | "cep";
}>;
required: true;
};
label: StringConstructor;
placeholder: StringConstructor;
disabled: BooleanConstructor;
error: BooleanConstructor;
errorMessages: {
type: import("vue").PropType<string | string[]>;
default: () => never[];
};
hint: StringConstructor;
persistentHint: BooleanConstructor;
rows: {
type: NumberConstructor;
default: number;
};
options: {
type: import("vue").PropType<Array<import("../../tipos/campo.js").CampoOpcaoBruta>>;
default: () => never[];
};
clearable: BooleanConstructor;
variant: {
type: import("vue").PropType<import("../../tipos/campo.js").CampoVariante>;
default: string;
};
density: {
type: import("vue").PropType<import("../../tipos/campo.js").CampoDensidade>;
default: string;
};
color: {
type: StringConstructor;
default: string;
};
row: BooleanConstructor;
showPasswordToggle: BooleanConstructor;
multiple: BooleanConstructor;
chips: BooleanConstructor;
}>, {
attrs: {
[x: string]: unknown;
};
value: import("vue").WritableComputedRef<import("../../tipos/campo.js").CampoValor | import("../../tipos/campo.js").CampoValorMultiplo, import("../../tipos/campo.js").CampoValor | import("../../tipos/campo.js").CampoValorMultiplo>;
isTextLike: import("vue").ComputedRef<boolean>;
inputHtmlType: import("vue").ComputedRef<"text" | "password">;
inputMode: import("vue").ComputedRef<"tel" | "decimal" | "numeric" | undefined>;
internalColor: import("vue").ComputedRef<string | undefined>;
showPassword: import("vue").Ref<boolean, boolean>;
togglePassword: () => void;
emit: ((event: "update:value", _v: string | null | undefined) => void) & ((event: "input", _v: string | null | undefined) => void) & ((event: "change", _v: string | null | undefined) => void) & ((event: "focus") => void) & ((event: "blur") => void);
localValue: import("vue").WritableComputedRef<string | null | undefined, string | null | undefined>;
inputHtmlType: import("vue").ComputedRef<"text" | "email" | "url">;
inputMode: import("vue").ComputedRef<string | undefined>;
onInput: (e: Event) => void;
onFocus: () => void;
onBlur: () => void;
computedItems: import("vue").ComputedRef<import("../../tipos/campo.js").CampoOpcao[]>;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("update:modelValue" | "change" | "focus" | "blur")[], "update:modelValue" | "change" | "focus" | "blur", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
modelValue: {
type: import("vue").PropType<import("../../tipos/campo.js").CampoValor | import("../../tipos/campo.js").CampoValorMultiplo>;
default: string;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
"update:value": (_v: string | null | undefined) => true;
input: (_v: string | null | undefined) => true;
change: (_v: string | null | undefined) => true;
focus: () => true;
blur: () => true;
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
value: {
type: import("vue").PropType<string | null | undefined>;
default: undefined;
};
type: {
type: import("vue").PropType<import("../../tipos/campo.js").CampoTipo>;
default: string;
opcoes: {
type: import("vue").PropType<{
rotulo: string;
placeholder?: string;
} & {
limiteCaracteres?: number;
formato?: "texto" | "email" | "url" | "telefone" | "cpfCnpj" | "cep";
}>;
required: true;
};
label: StringConstructor;
placeholder: StringConstructor;
disabled: BooleanConstructor;
error: BooleanConstructor;
errorMessages: {
type: import("vue").PropType<string | string[]>;
default: () => never[];
};
hint: StringConstructor;
persistentHint: BooleanConstructor;
rows: {
type: NumberConstructor;
default: number;
};
options: {
type: import("vue").PropType<Array<import("../../tipos/campo.js").CampoOpcaoBruta>>;
default: () => never[];
};
clearable: BooleanConstructor;
variant: {
type: import("vue").PropType<import("../../tipos/campo.js").CampoVariante>;
default: string;
};
density: {
type: import("vue").PropType<import("../../tipos/campo.js").CampoDensidade>;
default: string;
};
color: {
type: StringConstructor;
default: string;
};
row: BooleanConstructor;
showPasswordToggle: BooleanConstructor;
multiple: BooleanConstructor;
chips: BooleanConstructor;
}>> & Readonly<{
"onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
onChange?: ((...args: any[]) => any) | undefined;
onFocus?: ((...args: any[]) => any) | undefined;
onBlur?: ((...args: any[]) => any) | undefined;
"onUpdate:value"?: ((_v: string | null | undefined) => any) | undefined;
onInput?: ((_v: string | null | undefined) => any) | undefined;
onChange?: ((_v: string | null | undefined) => any) | undefined;
onFocus?: (() => any) | undefined;
onBlur?: (() => any) | undefined;
}>, {
color: string;
type: import("../../tipos/campo.js").CampoTipo;
variant: import("../../tipos/campo.js").CampoVariante;
disabled: boolean;
error: boolean;
persistentHint: boolean;
clearable: boolean;
row: boolean;
showPasswordToggle: boolean;
multiple: boolean;
chips: boolean;
modelValue: import("../../tipos/campo.js").CampoValor | import("../../tipos/campo.js").CampoValorMultiplo;
errorMessages: string | string[];
rows: number;
options: import("../../tipos/campo.js").CampoOpcaoBruta[];
density: import("../../tipos/campo.js").CampoDensidade;
value: string | null | undefined;
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
declare const _default: typeof __VLS_export;

View file

@ -3,14 +3,12 @@ import "./styles/eli-vue-fonts.css";
import { EliOlaMundo } from "./componentes/ola_mundo";
import { EliBotao } from "./componentes/botao";
import { EliBadge } from "./componentes/indicador";
import { EliInput } from "./componentes/campo";
import { EliCartao } from "./componentes/cartao";
import { EliTabela } from "./componentes/EliTabela";
import { EliEntradaTexto, EliEntradaNumero, EliEntradaDataHora } from "./componentes/EliEntrada";
export { EliOlaMundo };
export { EliBotao };
export { EliBadge };
export { EliInput };
export { EliCartao };
export { EliTabela };
export { EliEntradaTexto, EliEntradaNumero, EliEntradaDataHora };

View file

@ -1,19 +0,0 @@
/**
* Tipos do componente EliInput (campo).
*/
export type CampoValor = string | number | boolean | null;
export type CampoValorMultiplo = CampoValor[];
export type CampoOpcao<TValor extends CampoValor = CampoValor> = {
label: string;
value: TValor;
disabled?: boolean;
};
export type CampoOpcaoBruta<TValor extends CampoValor = CampoValor> = TValor | {
label?: string;
value: TValor;
disabled?: boolean;
};
export type CampoVariante = "outlined" | "filled" | "plain" | "solo" | "solo-filled" | "solo-inverted" | "underlined";
export type CampoDensidade = "default" | "comfortable" | "compact";
export type CampoTipoNumerico = "numericoInteiro" | "numericoDecimal" | "numericoMoeda" | "porcentagem";
export type CampoTipo = "text" | "password" | "email" | "search" | "url" | "textarea" | "radio" | "checkbox" | "telefone" | "cpfCnpj" | "cep" | "select" | CampoTipoNumerico;

9
dist/types/tipos/entrada.d.ts vendored Normal file
View file

@ -0,0 +1,9 @@
/**
* Tipos compartilhados para componentes de entrada (EliEntrada*).
*
* OBS: Estes tipos existiam anteriormente em `tipos/campo.ts` junto do componente
* `EliInput`. Como o `EliInput` foi removido, mantemos aqui apenas o que ainda
* é relevante para padronização visual/props do Vuetify.
*/
export type CampoVariante = "outlined" | "filled" | "plain" | "solo" | "solo-filled" | "solo-inverted" | "underlined";
export type CampoDensidade = "default" | "comfortable" | "compact";

View file

@ -1,4 +1,4 @@
export * from "./botao";
export * from "./cartao";
export * from "./campo";
export * from "./entrada";
export * from "./indicador";

View file

@ -1,6 +1,6 @@
{
"name": "eli-vue",
"version": "0.1.60",
"version": "0.1.62",
"private": false,
"main": "./dist/eli-vue.umd.js",
"module": "./dist/eli-vue.es.js",

View file

@ -1,6 +1,8 @@
<template>
<v-text-field
v-model="localValue"
:type="inputHtmlType"
:inputmode="inputMode"
:label="opcoes?.rotulo"
:placeholder="opcoes?.placeholder"
:counter="opcoes?.limiteCaracteres"
@ -8,12 +10,16 @@
v-bind="attrs"
@focus="() => emit('focus')"
@blur="() => emit('blur')"
@input="onInput"
/>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from "vue";
import type { PadroesEntradas } from "./tiposEntradas";
import { formatarCpfCnpj } from "./utils/cpfCnpj";
import { formatTelefone } from "./utils/telefone";
import { formatarCep } from "./utils/cep";
type EntradaTexto = PadroesEntradas["texto"];
@ -40,6 +46,8 @@ export default defineComponent({
blur: () => true,
},
setup(props, { attrs, emit }) {
const formato = computed(() => props.opcoes?.formato ?? "texto");
const localValue = computed<EntradaTexto["value"]>({
get: () => props.value,
set: (v) => {
@ -49,7 +57,43 @@ export default defineComponent({
},
});
return { attrs, emit, localValue };
const inputHtmlType = computed(() => {
if (formato.value === "email") return "email";
if (formato.value === "url") return "url";
return "text";
});
const inputMode = computed<string | undefined>(() => {
if (formato.value === "telefone") return "tel";
if (formato.value === "cpfCnpj" || formato.value === "cep") return "numeric";
return undefined;
});
function aplicarFormato(valor: string) {
switch (formato.value) {
case "telefone":
return formatTelefone(valor);
case "cpfCnpj":
return formatarCpfCnpj(valor);
case "cep":
return formatarCep(valor);
default:
return valor;
}
}
function onInput(e: Event) {
const target = e.target as HTMLInputElement;
const resultado = aplicarFormato(target.value);
// garante que o input mostre o valor formatado
target.value = resultado;
// regra do projeto: value sempre igual ao que aparece
localValue.value = resultado;
}
return { attrs, emit, localValue, inputHtmlType, inputMode, onInput };
},
});
</script>

View file

@ -47,18 +47,24 @@ export type PadroesEntradas = {
{
/** Limite máximo de caracteres permitidos (se definido) */
limiteCaracteres?: number
/**
* Formato/máscara aplicada ao texto.
* Obs: o `value` SEMPRE será o texto formatado (o que aparece no input).
*/
formato?: "texto" | "email" | "url" | "telefone" | "cpfCnpj" | "cep"
}
>
numero: tipoPadraoEntrada<
number | null | undefined,
{
/** Unidade de medida (ex.: "kg", "m³") */
/** Unidade de medida (ex.: "kg", "m³") */
sufixo?: string
/** Moéda (ex.: "R$") */
/** Moéda (ex.: "R$") */
prefixo?: string
/**

View file

@ -0,0 +1,10 @@
function somenteNumeros(valor: string) {
return valor.replace(/\D+/g, "");
}
/** Formata CEP no padrão 00000-000 */
export function formatarCep(valor: string) {
const digitos = somenteNumeros(valor);
if (!digitos) return "";
return digitos.replace(/^(\d{5})(\d)/, "$1-$2").slice(0, 9);
}

View file

@ -1,5 +1,3 @@
// utils/telefone.ts
/**
* Remove tudo que não é número
*/

View file

@ -1,304 +0,0 @@
<template>
<div class="eli-input">
<!-- TEXT LIKE INPUTS -->
<v-text-field
v-if="isTextLike"
v-model="value"
:type="inputHtmlType"
:label="label"
:placeholder="placeholder"
:disabled="disabled"
:clearable="clearable && type !== 'password'"
:error="error"
:error-messages="errorMessages"
:hint="hint"
:persistent-hint="persistentHint"
:density="density"
:variant="variant"
:color="internalColor"
:inputmode="inputMode"
:suffix="type === 'porcentagem' ? '%' : undefined"
v-bind="attrs"
@focus="onFocus"
@blur="onBlur"
@input="onInput"
>
<!-- PASSWORD TOGGLE -->
<template
v-if="type === 'password' && showPasswordToggle"
#append-inner
>
<v-icon
class="cursor-pointer"
@click="togglePassword"
>
{{ showPassword ? "mdi-eye-off" : "mdi-eye" }}
</v-icon>
</template>
</v-text-field>
<!-- TEXTAREA -->
<v-textarea
v-else-if="type === 'textarea'"
v-model="value"
:label="label"
:rows="rows"
:density="density"
:variant="variant"
v-bind="attrs"
/>
<!-- SELECT -->
<v-select
v-else-if="type === 'select'"
v-model="value"
:items="computedItems"
:label="label"
:placeholder="placeholder"
:multiple="multiple"
:chips="chips"
:clearable="clearable"
:disabled="disabled"
:density="density"
:variant="variant"
item-title="label"
item-value="value"
:error="error"
:error-messages="errorMessages"
v-bind="attrs"
@focus="onFocus"
@blur="onBlur"
/>
<!-- RADIO -->
<v-radio-group
v-else-if="type === 'radio'"
v-model="value"
:row="row"
>
<v-radio
v-for="opt in computedItems"
:key="String(opt.value)"
:label="opt.label"
:value="opt.value"
/>
</v-radio-group>
<!-- CHECKBOX -->
<div v-else-if="type === 'checkbox'" class="checkbox-group">
<v-checkbox
v-for="opt in computedItems"
:key="String(opt.value)"
v-model="value"
:label="opt.label"
:value="opt.value"
:density="density"
/>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed, PropType } from "vue";
import type {
CampoDensidade,
CampoOpcao,
CampoOpcaoBruta,
CampoTipo,
CampoValor,
CampoValorMultiplo,
CampoVariante,
} from "../../tipos";
import { formatarCpfCnpj } from "./utils/cpfCnpj";
import { formatTelefone } from "./utils/telefone";
import { formatarDecimal, formatarMoeda, formatarPorcentagem, somenteNumeros } from "./utils/numerico"
import { formatarCep } from "./utils/cep";
export default defineComponent({
name: "EliInput",
inheritAttrs: false,
props: {
/**
* Aceita valor simples (text-like) ou lista de valores (checkbox/select multiple).
* O componente não converte tipos automaticamente: mantém o que receber.
*/
modelValue: {
type: [String, Number, Boolean, Array] as PropType<CampoValor | CampoValorMultiplo>,
default: "",
},
type: { type: String as PropType<CampoTipo>, default: "text" },
label: String,
placeholder: String,
disabled: Boolean,
error: Boolean,
errorMessages: {
type: [String, Array] as PropType<string | string[]>,
default: () => [],
},
hint: String,
persistentHint: Boolean,
rows: { type: Number, default: 4 },
/**
* Para select/radio/checkbox.
* Aceita lista normalizada ({ label, value }) ou valores primitivos.
*/
options: {
type: Array as PropType<Array<CampoOpcaoBruta>>,
default: () => [],
},
clearable: Boolean,
variant: { type: String as PropType<CampoVariante>, default: "outlined" },
density: { type: String as PropType<CampoDensidade>, default: "comfortable" },
color: { type: String, default: "primary" },
row: Boolean,
showPasswordToggle: Boolean,
multiple: Boolean,
chips: Boolean,
},
emits: ["update:modelValue", "change", "focus", "blur"],
setup(props, { emit, attrs }) {
const focused = ref(false);
const showPassword = ref(false);
const value = computed({
get: () => props.modelValue,
set: (v: CampoValor | CampoValorMultiplo) => {
emit("update:modelValue", v);
emit("change", v);
},
});
const isTextLike = computed(() =>
[
"text",
"password",
"email",
"search",
"url",
"telefone",
"cpfCnpj",
"numericoInteiro",
"numericoDecimal",
"numericoMoeda",
"porcentagem",
"cep",
].includes(props.type)
);
const inputHtmlType = computed(() =>
props.type === "password"
? showPassword.value
? "text"
: "password"
: "text"
);
const inputMode = computed(() => {
if (props.type === "telefone") return "tel";
if (props.type === "porcentagem") return "decimal";
if (props.type.startsWith("numerico")) return "numeric";
return undefined;
});
const internalColor = computed(() =>
props.error ? "error" : focused.value ? props.color : undefined
);
function onInput(e: Event) {
const target = e.target as HTMLInputElement;
let resultado = target.value;
switch (props.type) {
case "numericoInteiro":
resultado = somenteNumeros(resultado);
break;
case "numericoDecimal":
resultado = formatarDecimal(resultado);
break;
case "numericoMoeda":
resultado = formatarMoeda(resultado);
break;
case "porcentagem":
resultado = formatarPorcentagem(resultado);
break;
case "telefone":
resultado = formatTelefone(resultado);
break;
case "cpfCnpj":
resultado = formatarCpfCnpj(resultado);
break;
case "cep":
resultado = formatarCep(resultado);
break;
}
target.value = resultado;
emit("update:modelValue", resultado);
emit("change", resultado);
}
function togglePassword() {
showPassword.value = !showPassword.value;
}
// --- Helpers para select / radio / checkbox (aceita objetos ou primitivos) ---
const computedItems = computed<Array<CampoOpcao>>(() => {
// Normaliza options para [{ label, value, disabled? }]
return (props.options || []).map((o) => {
if (o && typeof o === "object" && "value" in o) {
const valor = o.value as CampoValor;
return {
label: o.label ?? String(valor),
value: valor,
disabled: o.disabled,
};
}
const valor = o as CampoValor;
return { label: String(valor), value: valor };
});
});
return {
attrs,
value,
isTextLike,
inputHtmlType,
inputMode,
internalColor,
showPassword,
togglePassword,
onInput,
onFocus: () => emit("focus"),
onBlur: () => emit("blur"),
computedItems,
};
},
});
</script>
<style scoped>
.eli-input {
width: 100%;
}
.checkbox-group {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.cursor-pointer {
cursor: pointer;
}
</style>

View file

@ -1,118 +0,0 @@
# EliInput
**Componente base de input do design system**
O EliInput unifica vários tipos de campo (v-text-field, v-textarea, v-select, v-radio-group, v-checkbox) em uma única API consistente. Ele encapsula comportamentos, máscaras e regras comuns (CPF/CNPJ, telefone, CEP, numéricos, formatação de moeda etc.) para manter coerência visual e de lógica em toda a aplicação.
> ⚠️ Nunca use os componentes Vuetify diretamente fora do design system para esses casos.
> Utilize sempre EliInput para garantir formatação, tipagem e repasse de atributos padronizados.
---
## Visão geral
EliInput foi projetado para:
* Centralizar formatação (máscaras) para tipos de entrada comuns (telefone, CPF/CNPJ, CEP, numéricos).
* Fornecer uma API única (type) que representa diferentes controles (text, textarea, select, radio, checkbox, etc.).
* Repassar atributos/props do pai para o componente Vuetify interno (v-bind="$attrs") mantendo inheritAttrs: false.
* Emitir eventos padronizados: update:modelValue, change, focus, blur.
---
## Principais decisões de implementação
* inheritAttrs: false — o componente controla explicitamente para onde os atributos são passados (v-bind="attrs" no elemento interno).
* Uso de um computed value que faz emit("update:modelValue", v) e emit("change", v) — assim qualquer v-model no pai funciona como esperado.
* Normalização de props.options para aceitar objetos { label, value, disabled } ou primitivos ('A', 1).
* Separação clara entre lógica de formatação (aplicada em onInput) e componentes que não devem ser formatados (ex.: v-select).
---
## Tipagem (TypeScript)
```ts
type ValorCampo = string | number | boolean | null;
type Option = { label: string; value: ValorCampo; disabled?: boolean };
type InputVariant = 'outlined' | 'filled' | 'plain' | 'solo' | 'solo-filled' | 'solo-inverted' | 'underlined';
type Density = 'default' | 'comfortable' | 'compact';
type TipoNumerico = 'numericoInteiro' | 'numericoDecimal' | 'numericoMoeda' | 'porcentagem';
type InputType =
| 'text' | 'password' | 'email' | 'search' | 'url' | 'textarea'
| 'radio' | 'checkbox' | 'telefone' | 'cpfCnpj' | 'cep' | 'select'
| TipoNumerico;
```
---
## Props
| Prop | Tipo | Default | Descrição |
| ---------------- | --------------------------- | --------------- | ------------------------------------------------------ |
| `modelValue` | `string \| number \| boolean \| (string \| number \| boolean \| null)[]` | `""` | Valor controlado (use com `v-model`). |
| `type` | `InputType` | `"text"` | Tipo do controle (ver `InputType`). |
| `label` | `string` | `-` | Rótulo do campo. |
| `placeholder` | `string` | `-` | Texto exibido quando o campo está vazio. |
| `disabled` | `boolean` | `false` | Desabilita o campo. |
| `error` | `boolean` | `false` | Força estado visual de erro. |
| `errorMessages` | `string \| string[]` | `[]` | Mensagem ou lista de mensagens de erro. |
| `hint` | `string` | `-` | Texto de ajuda exibido abaixo do campo. |
| `persistentHint` | `boolean` | `false` | Mantém o hint sempre visível. |
| `variant` | `InputVariant` | `"outlined"` | Variante visual do Vuetify. |
| `density` | `Density` | `"comfortable"` | Densidade visual do campo. |
| `color` | `string` | `"primary"` | Cor do campo quando focado (ou `error`, se aplicável). |
| `clearable` | `boolean` | `false` | Permite limpar o valor do campo. |
## Notas sobre props
* options: aceita arrays como ['Frontend','Backend'] ou { label:'São Paulo', value:'SP' }. O componente normaliza para { label, value, disabled? }.
* type determina quais comportamentos internos são aplicados. Tipos numéricos e máscara (telefone, cpfCnpj, cep) passam por formatação em onInput.
* multiple/chips: úteis para type="select". O v-select interno recebe item-title="label" e item-value="value" para compatibilidade com objetos.
---
## Emissões (events)
* update:modelValue — padrão v-model.
* change — emitido sempre que o valor muda (alinha com update:modelValue).
* focus — quando o campo interno recebe foco.
* blur — quando perde o foco.
* Observação: como value é um computed com getter/setter que emite ambos, v-model e listeners de mudança no pai funcionarão normalmente.
---
## Repasso de atributos e listeners
* O componente define inheritAttrs: false e usa v-bind="attrs" nos componentes internos (v-text-field, v-select, etc.). Isso implica que:
* Atributos HTML (ex.: type, aria-label, class, style) passados para <EliInput> são aplicados ao componente Vuetify interno apropriado.
* Listeners (ex.: @click, @keydown) também fazem parte de $attrs e serão repassados para o componente interno — use o EliInput como se estivesse ouvindo eventos diretamente no input.
## Exemplo:
```vue
<EliInput type="text" v-model="nome" aria-label="Nome completo" @keydown.enter="enviar" />
```
---
## Comportamentos de formatação importantes
* numericoInteiro — remove tudo que não for dígito.
* numericoDecimal — mantém separador decimal (aplica formatarDecimal).
* numericoMoeda — formata para moeda conforme util (formatarMoeda).
* porcentagem — aplica formatação decimal e exibe sufixo `%` automaticamente.
* telefone — aplica máscara/format formatTelefone.
* cpfCnpj — aplica formatarCpfCnpj.
* cep — aplica formatarCep.
**Importante: a formatação ocorre no onInput (campos text-like). O v-select não passa por onInput — ele usa v-model="value" e o computed que emite o update. Se desejar formatação específica para itens do select (por exemplo, mostrar label formatado), trate nos options antes de passar.**
---
## Slot
* O componente não expõe slots customizados diretamente. Ele controla internamente o append para toggle de senha quando type === 'password' && showPasswordToggle (ícone de olho).
* Se você precisa de slots específicos do v-text-field/v-select, considere estender o componente ou criar uma variação que exponha os slots desejados.

View file

@ -1 +0,0 @@
export { default as EliInput } from "./EliInput.vue";

View file

@ -1,9 +0,0 @@
import { somenteNumeros } from "./numerico";
export function formatarCep(v: string): string {
const d = somenteNumeros(v).slice(0, 8);
if (d.length <= 5) return d;
return d.replace(/^(\d{5})(\d{1,3})$/, "$1-$2");
}

View file

@ -1,26 +0,0 @@
export function somenteNumeros(valor: string) {
return valor.replace(/\D+/g, "");
}
export function formatarDecimal(valor: string) {
const limpo = valor.replace(/[^\d,]/g, "");
const partes = limpo.split(",");
return partes.length > 2 ? partes[0] + "," + partes.slice(1).join("") : limpo;
}
/**
* Formatação para percentual:
* - remove '%' caso venha junto (ex: colar "10%")
* - mantém apenas dígitos e vírgula (no máximo uma)
*/
export function formatarPorcentagem(valor: string) {
return formatarDecimal(valor.replace(/%/g, ""));
}
export function formatarMoeda(valor: string) {
const numero = somenteNumeros(valor);
if (!numero) return "";
const inteiro = (parseInt(numero, 10) / 100).toFixed(2);
return inteiro.replace(".", ",").replace(/\B(?=(\d{3})+(?!\d))/g, ".");
}

View file

@ -1,108 +0,0 @@
# EliDataHora
O `EliDataHora` é um componente de **entrada de data e hora** baseado em `v-text-field` (Vuetify), usando o tipo nativo do HTML `datetime-local`.
Ele foi criado para oferecer uma solução **estável e leve** sem depender de componentes experimentais do Vuetify.
## Objetivo
- Permitir o usuário selecionar **data + hora** com UX nativa do navegador.
- Padronizar a API em português (props/eventos) no Design System.
## API
### Props
| Prop | Tipo | Padrão | Descrição |
|------|------|--------|-----------|
| `modelValue` | `string \| null` | `null` | **Sempre em ISO 8601**, aceitando UTC absoluto (`Z`) ou com offset (ex.: `2026-01-09T16:15:00Z`, `2026-01-09T13:15:00-03:00`). O componente converte para horário **local** antes de exibir. |
| `modo` | `"data" \| "dataHora"` | `"dataHora"` | Define se o campo permite selecionar apenas data (`date`) ou data+hora (`datetime-local`). |
| `rotulo` | `string` | `"Data e hora"` | Label do campo. |
| `placeholder` | `string` | `""` | Placeholder do campo. |
| `desabilitado` | `boolean` | `false` | Desabilita o campo. |
| `limpavel` | `boolean` | `false` | Habilita botão de limpar (Vuetify `clearable`). |
| `erro` | `boolean` | `false` | Estado de erro visual. |
| `mensagensErro` | `string \| string[]` | `[]` | Mensagens de erro. |
| `dica` | `string` | `""` | Hint/ajuda abaixo do campo. |
| `dicaPersistente` | `boolean` | `false` | Mantém dica sempre visível. |
| `densidade` | `CampoDensidade` | `"comfortable"` | Densidade (Vuetify). |
| `variante` | `CampoVariante` | `"outlined"` | Variante (Vuetify). |
| `min` | `string \| undefined` | `undefined` | Mínimo permitido em ISO 8601 (offset ou `Z`). |
| `max` | `string \| undefined` | `undefined` | Máximo permitido em ISO 8601 (offset ou `Z`). |
> Observação: o atributo HTML `datetime-local` **não inclui timezone**.
> Este componente resolve isso convertendo:
>
> - **entrada**: ISO 8601 (UTC/offset) → **exibição** em horário local
> - **saída**: valor selecionado → ISO 8601 com **offset local**
### Emits
| Evento | Payload | Quando dispara |
|--------|---------|---------------|
| `update:modelValue` | `string \| null` | Sempre que o valor muda (padrão do v-model). O payload é ISO 8601 com **offset local**. |
| `alterar` | `string \| null` | Alias semântico para mudanças de valor (mesmo payload do v-model). |
| `foco` | `void` | Ao focar o campo. |
| `desfoco` | `void` | Ao sair do foco. |
### Slots
Este componente não define slots próprios. Você pode usar slots do `v-text-field` via `v-bind="$attrs"` caso precise (ver exemplos abaixo).
## Exemplos
### 1) Uso básico com v-model
```vue
<template>
<EliDataHora v-model="dataHora" />
<div class="text-caption">Valor: {{ dataHora }}</div>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
import { EliDataHora } from "eli-vue";
export default defineComponent({
components: { EliDataHora },
setup() {
const dataHora = ref<string | null>("2026-01-09T16:15:00Z");
return { dataHora };
},
});
</script>
```
### 2) Com limites (min/max) e validação visual
```vue
<template>
<EliDataHora
v-model="dataHora"
rotulo="Agendar"
:min="min"
:max="max"
:erro="!dataHora"
:mensagensErro="!dataHora ? ['Obrigatório'] : []"
limpavel
/>
</template>
```
## Casos de borda / comportamento esperado
- Ao limpar o campo, o componente emite `null` (não string vazia).
- O navegador pode variar a UI do seletor (isso é esperado do `datetime-local`).
- `min/max` devem ser strings em ISO 8601 (offset ou `Z`).
- Em `modo="data"`, o componente emite ISO no **início do dia** (`00:00:00`) no fuso local.
## Acessibilidade
- O `v-text-field` do Vuetify já oferece base de acessibilidade.
- Sempre prefira passar `rotulo` significativo.
## Decisões de implementação
- Usamos `datetime-local` por ser amplamente suportado e não depender de APIs experimentais.
- O componente usa `dayjs` para converter entradas UTC/offset para local antes de exibir e para emitir ISO 8601 com offset local.
- Mantemos o valor como `string | null` para evitar conversões implícitas e permitir que cada projeto decida como persistir (UTC/local).

View file

@ -1 +0,0 @@
export { default as EliEntradaDataHora } from "../EliEntrada/EliEntradaDataHora.vue";

View file

@ -11,100 +11,31 @@
<div class="grid-example">
<!-- text normal -->
<EliInput
v-model="nome"
label="Nome"
placeholder="Digite o nome"
<EliEntradaTexto
v-model:value="nome"
:opcoes="{ rotulo: 'Nome', placeholder: 'Digite o nome' }"
density="compact"
/>
<EliInput
v-model="idade"
type="numericoInteiro"
label="Idade"
density="default"
<EliEntradaTexto
v-model:value="telefone"
:opcoes="{ rotulo: 'Telefone', formato: 'telefone' }"
/>
<EliInput
v-model="altura"
type="numericoDecimal"
label="Altura"
density="comfortable"
<EliEntradaTexto
v-model:value="cep"
:opcoes="{ rotulo: 'CEP', placeholder: '00000-000', formato: 'cep' }"
/>
<EliInput v-model="valor" type="numericoMoeda" label="Valor" />
<EliInput v-model="telefone" type="telefone" label="Telefone" />
<EliInput
v-model="cep"
type="cep"
label="CEP"
placeholder="00000-000"
<EliEntradaTexto
v-model:value="documento"
:opcoes="{ rotulo: 'CPF / CNPJ', formato: 'cpfCnpj' }"
/>
<EliInput
type="select"
label="Estado"
:options="[
{ label: 'São Paulo', value: 'SP' },
{ label: 'Rio de Janeiro', value: 'RJ' }
]"
v-model="estado"
multiple
/>
<EliInput v-model="documento" type="cpfCnpj" label="CPF / CNPJ" />
<EliInput
v-model="email"
label="Email"
placeholder="email@exemplo.com"
/>
<EliInput
v-model="senha"
label="Senha"
type="password"
:showPasswordToggle="true"
placeholder="Digite sua senha"
/>
<!-- textarea -->
<EliInput
type="textarea"
v-model="mensagem"
label="Mensagem"
:rows="5"
/>
<!-- radio -->
<EliInput
type="radio"
v-model="cor"
label="Cor favorita"
:options="[
{ label: 'Azul', value: 'azul' },
{ label: 'Verde', value: 'verde' },
]"
/>
<!-- checkbox group -->
<EliInput
type="checkbox"
v-model="habilidades"
:options="[
{ label: 'Vue', value: 'vue' },
{ label: 'React', value: 'react' },
]"
/>
<!-- erro -->
<EliInput
v-model="nome"
label="Nome"
:error="true"
:error-messages="['Obrigatório']"
<EliEntradaTexto
v-model:value="email"
:opcoes="{ rotulo: 'Email', placeholder: 'email@exemplo.com', formato: 'email' }"
/>
</div>
</v-card-text>
@ -121,45 +52,27 @@
import { defineComponent, ref } from "vue";
import EliBotao from "../botao/EliBotao.vue";
import EliBadge from "../indicador/EliBadge.vue";
import EliInput from "../campo/EliInput.vue";
type Habilidade = "vue" | "react";
import EliEntradaTexto from "../EliEntrada/EliEntradaTexto.vue";
export default defineComponent({
name: "EliOlaMundo",
components: {
EliBotao,
EliBadge,
EliInput,
EliEntradaTexto,
},
setup() {
const nome = ref("");
const estado = ref<string[]>([]);
const cep = ref("");
const telefone = ref("");
const idade = ref("");
const altura = ref("");
const valor = ref("");
const email = ref("");
const mensagem = ref("");
const senha = ref("");
const documento = ref("");
const cor = ref<"azul" | "verde" | null>(null);
const habilidades = ref<Habilidade[]>([]);
return {
nome,
email,
documento,
estado,
telefone,
mensagem,
senha,
cor,
habilidades,
idade,
altura,
cep,
valor,
};
},
});

View file

@ -2,14 +2,14 @@
O `EliOlaMundo` é um componente **de exemplo** usado para validar rapidamente o Design System no playground.
> Ele não é um componente “de produto”; ele existe para demonstrar integração com Vuetify e mostrar variações de uso de `EliInput`, `EliBotao` e `EliBadge`.
> Ele não é um componente “de produto”; ele existe para demonstrar integração com Vuetify e mostrar variações de uso de `EliEntradaTexto`, `EliEntradaNumero`, `EliBotao` e `EliBadge`.
## Estrutura do repositório (padrão)
Neste Design System:
- **Pastas e arquivos** (quando aplicável) preferem português: `botao/`, `campo/`, `indicador/`, etc.
- **Componentes** mantêm prefixo técnico `Eli` (PascalCase): `EliBotao`, `EliInput`.
- **Pastas e arquivos** (quando aplicável) preferem português: `botao/`, `EliEntrada/`, `indicador/`, etc.
- **Componentes** mantêm prefixo técnico `Eli` (PascalCase): `EliBotao`, `EliEntradaTexto`.
Exemplo:
@ -19,9 +19,10 @@ src/componentes/
EliBotao.vue
index.ts
README.md
campo/
EliInput.vue
index.ts
EliEntrada/
EliEntradaTexto.vue
EliEntradaNumero.vue
EliEntradaDataHora.vue
README.md
```

View file

@ -3,7 +3,6 @@ import "./styles/eli-vue-fonts.css";
import { EliOlaMundo } from "./componentes/ola_mundo";
import { EliBotao } from "./componentes/botao";
import { EliBadge } from "./componentes/indicador";
import { EliInput } from "./componentes/campo";
import { EliCartao } from "./componentes/cartao";
import { EliTabela } from "./componentes/EliTabela";
import { EliEntradaTexto, EliEntradaNumero, EliEntradaDataHora } from "./componentes/EliEntrada";
@ -11,7 +10,6 @@ import { EliEntradaTexto, EliEntradaNumero, EliEntradaDataHora } from "./compone
export { EliOlaMundo };
export { EliBotao };
export { EliBadge };
export { EliInput };
export { EliCartao };
export { EliTabela };
export { EliEntradaTexto, EliEntradaNumero, EliEntradaDataHora };
@ -21,7 +19,6 @@ const EliVue: Plugin = {
app.component("EliOlaMundo", EliOlaMundo);
app.component("EliBotao", EliBotao);
app.component("EliBadge", EliBadge);
app.component("EliInput", EliInput);
app.component("EliCartao", EliCartao);
app.component("EliTabela", EliTabela);
app.component("EliEntradaTexto", EliEntradaTexto);

View file

@ -8,7 +8,6 @@
<v-tab value="botao">Botão</v-tab>
<v-tab value="indicador">Indicador</v-tab>
<v-tab value="cartao">Cartão</v-tab>
<v-tab value="campo">Campo</v-tab>
<v-tab value="entradas">Entradas</v-tab>
<v-tab value="data_hora">Data e hora</v-tab>
<v-tab value="tabela">Tabela</v-tab>
@ -20,7 +19,6 @@
<BotaoPlayground v-if="aba === 'botao'" />
<IndicadorPlayground v-else-if="aba === 'indicador'" />
<CartaoPlayground v-else-if="aba === 'cartao'" />
<CampoPlayground v-else-if="aba === 'campo'" />
<EntradasPlayground v-else-if="aba === 'entradas'" />
<DataHoraPlayground v-else-if="aba === 'data_hora'" />
<TabelaPlayground v-else-if="aba === 'tabela'" />
@ -33,7 +31,6 @@ import { defineComponent } from "vue";
import BotaoPlayground from "./botao.playground.vue";
import IndicadorPlayground from "./indicador.playground.vue";
import CartaoPlayground from "./cartao.playground.vue";
import CampoPlayground from "./campo.playground.vue";
import EntradasPlayground from "./entradas.playground.vue";
import DataHoraPlayground from "./data_hora.playground.vue";
import TabelaPlayground from "./tabela.playground.vue";
@ -43,7 +40,6 @@ type AbaPlayground =
| "botao"
| "indicador"
| "cartao"
| "campo"
| "entradas"
| "data_hora"
| "tabela"
@ -53,7 +49,6 @@ const mapaHashParaAba: Record<string, AbaPlayground> = {
botao: "botao",
indicador: "indicador",
cartao: "cartao",
campo: "campo",
entradas: "entradas",
"data-hora": "data_hora",
tabela: "tabela",
@ -64,7 +59,6 @@ const mapaAbaParaHash: Record<AbaPlayground, string> = {
botao: "botao",
indicador: "indicador",
cartao: "cartao",
campo: "campo",
entradas: "entradas",
data_hora: "data-hora",
tabela: "tabela",
@ -77,7 +71,6 @@ export default defineComponent({
BotaoPlayground,
IndicadorPlayground,
CartaoPlayground,
CampoPlayground,
EntradasPlayground,
DataHoraPlayground,
TabelaPlayground,

View file

@ -1,127 +0,0 @@
<template>
<section class="stack">
<h2>EliInput (campo)</h2>
<div class="grid">
<EliInput v-model="nome" label="Nome" placeholder="Digite seu nome" />
<EliInput v-model="telefone" type="telefone" label="Telefone" />
<EliInput v-model="documento" type="cpfCnpj" label="CPF / CNPJ" />
<EliInput v-model="idade" type="numericoInteiro" label="Idade" />
<EliInput v-model="valor" type="numericoMoeda" label="Valor" />
<EliInput v-model="taxa" type="porcentagem" label="Taxa" placeholder="0,00" />
<EliInput v-model="cep" type="cep" label="CEP" placeholder="00000-000" />
<EliInput
v-model="estado"
type="select"
label="Estado"
multiple
:options="[
{ label: 'São Paulo', value: 'SP' },
{ label: 'Rio de Janeiro', value: 'RJ' },
]"
/>
<EliInput
v-model="cor"
type="radio"
label="Cor favorita"
:options="[
{ label: 'Azul', value: 'azul' },
{ label: 'Verde', value: 'verde' },
]"
/>
<EliInput
v-model="habilidades"
type="checkbox"
label="Habilidades"
:options="[
{ label: 'Vue', value: 'vue' },
{ label: 'React', value: 'react' },
]"
/>
</div>
<pre class="debug">{{ debug }}</pre>
</section>
</template>
<script lang="ts">
import { computed, defineComponent, ref } from "vue";
import { EliInput } from "@/componentes/campo";
type Cor = "azul" | "verde" | null;
type Habilidade = "vue" | "react";
export default defineComponent({
name: "CampoPlayground",
components: { EliInput },
setup() {
const nome = ref("");
const telefone = ref("");
const documento = ref("");
const idade = ref("");
const valor = ref("");
const taxa = ref("");
const cep = ref("");
const estado = ref<string[]>([]);
const cor = ref<Cor>(null);
const habilidades = ref<Habilidade[]>([]);
const debug = computed(() =>
JSON.stringify(
{
nome: nome.value,
telefone: telefone.value,
documento: documento.value,
idade: idade.value,
valor: valor.value,
taxa: taxa.value,
cep: cep.value,
estado: estado.value,
cor: cor.value,
habilidades: habilidades.value,
},
null,
2
)
);
return {
nome,
telefone,
documento,
idade,
valor,
taxa,
cep,
estado,
cor,
habilidades,
debug,
};
},
});
</script>
<style scoped>
.stack {
display: grid;
gap: 16px;
}
.grid {
display: grid;
gap: 12px;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
.debug {
padding: 12px;
background: rgba(0, 0, 0, 0.04);
border-radius: 8px;
overflow: auto;
}
</style>

View file

@ -3,10 +3,9 @@
<h2>EliCartao (pipeline)</h2>
<div class="toolbar">
<EliInput
v-model="filtro"
label="Buscar"
placeholder="Cliente, título, valor..."
<EliEntradaTexto
v-model:value="filtro"
:opcoes="{ rotulo: 'Buscar', placeholder: 'Cliente, título, valor...' }"
density="compact"
/>
@ -48,7 +47,7 @@
import { defineComponent, ref } from "vue";
import { EliBadge } from "@/componentes/indicador";
import { EliBotao } from "@/componentes/botao";
import { EliInput } from "@/componentes/campo";
import { EliEntradaTexto } from "@/componentes/EliEntrada";
import { EliCartao } from "@/componentes/cartao";
import type { CartaoStatus } from "@/tipos";
@ -68,7 +67,7 @@ type Coluna = {
export default defineComponent({
name: "CartaoPlayground",
components: { EliBadge, EliBotao, EliInput, EliCartao },
components: { EliBadge, EliBotao, EliEntradaTexto, EliCartao },
setup() {
const filtro = ref("");

View file

@ -3,7 +3,7 @@
<h2>EliOlaMundo</h2>
<p class="muted">
Demo integrada (útil para smoke-test visual). Para testes específicos,
prefira os playgrounds de <code>EliBotao</code>, <code>EliInput</code> e
prefira os playgrounds de <code>EliBotao</code>, <code>EliEntradas</code> e
<code>EliBadge</code>.
</p>

View file

@ -1,52 +0,0 @@
/**
* Tipos do componente EliInput (campo).
*/
export type CampoValor = string | number | boolean | null;
export type CampoValorMultiplo = CampoValor[];
export type CampoOpcao<TValor extends CampoValor = CampoValor> = {
label: string;
value: TValor;
disabled?: boolean;
};
export type CampoOpcaoBruta<TValor extends CampoValor = CampoValor> =
| TValor
| {
label?: string;
value: TValor;
disabled?: boolean;
};
export type CampoVariante =
| "outlined"
| "filled"
| "plain"
| "solo"
| "solo-filled"
| "solo-inverted"
| "underlined";
export type CampoDensidade = "default" | "comfortable" | "compact";
export type CampoTipoNumerico =
| "numericoInteiro"
| "numericoDecimal"
| "numericoMoeda"
| "porcentagem";
export type CampoTipo =
| "text"
| "password"
| "email"
| "search"
| "url"
| "textarea"
| "radio"
| "checkbox"
| "telefone"
| "cpfCnpj"
| "cep"
| "select"
| CampoTipoNumerico;

18
src/tipos/entrada.ts Normal file
View file

@ -0,0 +1,18 @@
/**
* Tipos compartilhados para componentes de entrada (EliEntrada*).
*
* OBS: Estes tipos existiam anteriormente em `tipos/campo.ts` junto do componente
* `EliInput`. Como o `EliInput` foi removido, mantemos aqui apenas o que ainda
* é relevante para padronização visual/props do Vuetify.
*/
export type CampoVariante =
| "outlined"
| "filled"
| "plain"
| "solo"
| "solo-filled"
| "solo-inverted"
| "underlined";
export type CampoDensidade = "default" | "comfortable" | "compact";

View file

@ -1,4 +1,4 @@
export * from "./botao";
export * from "./cartao";
export * from "./campo";
export * from "./entrada";
export * from "./indicador";