This commit is contained in:
Luiz Silva 2026-01-29 18:31:52 -03:00
parent 8c5a31ef30
commit a693081023
34 changed files with 14887 additions and 1146 deletions

View file

@ -0,0 +1,66 @@
<template>
<v-textarea
v-model="localValue"
:label="opcoes?.rotulo"
:placeholder="opcoes?.placeholder"
:rows="opcoes?.linhas ?? 4"
:counter="opcoes?.limiteCaracteres"
:maxlength="opcoes?.limiteCaracteres"
:clearable="Boolean(opcoes?.limpavel)"
:error="Boolean(opcoes?.erro)"
:error-messages="opcoes?.mensagensErro"
:hint="opcoes?.dica"
:persistent-hint="Boolean(opcoes?.dicaPersistente)"
:density="opcoes?.densidade ?? 'comfortable'"
:variant="opcoes?.variante ?? 'outlined'"
auto-grow
v-bind="attrs"
@focus="() => emit('focus')"
@blur="() => emit('blur')"
/>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from "vue";
import { VTextarea } from "vuetify/components";
import type { PadroesEntradas } from "./tiposEntradas";
type EntradaParagrafo = PadroesEntradas["paragrafo"];
export default defineComponent({
name: "EliEntradaParagrafo",
components: { VTextarea },
inheritAttrs: false,
props: {
value: {
type: [String, null] as unknown as PropType<EntradaParagrafo["value"]>,
default: undefined,
},
opcoes: {
type: Object as PropType<EntradaParagrafo["opcoes"]>,
required: true,
},
},
emits: {
"update:value": (_v: EntradaParagrafo["value"]) => true,
input: (_v: EntradaParagrafo["value"]) => true,
change: (_v: EntradaParagrafo["value"]) => true,
focus: () => true,
blur: () => true,
},
setup(props, { attrs, emit }) {
const localValue = computed<EntradaParagrafo["value"]>({
get: () => props.value,
set: (v) => {
emit("update:value", v);
emit("input", v);
emit("change", v);
},
});
return { attrs, emit, localValue, opcoes: props.opcoes };
},
});
</script>
<style scoped></style>

View file

@ -0,0 +1,93 @@
<template>
<v-select
v-model="localValue"
:label="opcoes?.rotulo"
:placeholder="opcoes?.placeholder"
:items="itens"
item-title="rotulo"
item-value="chave"
:loading="carregando"
:disabled="carregando"
:clearable="Boolean(opcoes?.limpavel)"
:error="Boolean(opcoes?.erro)"
:error-messages="opcoes?.mensagensErro"
:hint="opcoes?.dica"
:persistent-hint="Boolean(opcoes?.dicaPersistente)"
:density="opcoes?.densidade ?? 'comfortable'"
:variant="opcoes?.variante ?? 'outlined'"
v-bind="attrs"
@focus="() => emit('focus')"
@blur="() => emit('blur')"
/>
</template>
<script lang="ts">
import { computed, defineComponent, onMounted, PropType, ref, watch } from "vue";
import { VSelect } from "vuetify/components";
import type { PadroesEntradas } from "./tiposEntradas";
type EntradaSelecao = PadroesEntradas["selecao"];
type ItemSelecao = { chave: string; rotulo: string };
export default defineComponent({
name: "EliEntradaSelecao",
components: { VSelect },
inheritAttrs: false,
props: {
value: {
type: [String, null] as unknown as PropType<EntradaSelecao["value"]>,
default: undefined,
},
opcoes: {
type: Object as PropType<EntradaSelecao["opcoes"]>,
required: true,
},
},
emits: {
"update:value": (_v: EntradaSelecao["value"]) => true,
input: (_v: EntradaSelecao["value"]) => true,
change: (_v: EntradaSelecao["value"]) => true,
focus: () => true,
blur: () => true,
},
setup(props, { attrs, emit }) {
const itens = ref<ItemSelecao[]>([]);
const carregando = ref(false);
const localValue = computed<EntradaSelecao["value"]>({
get: () => props.value,
set: (v) => {
emit("update:value", v);
emit("input", v);
emit("change", v);
},
});
async function carregarItens() {
carregando.value = true;
try {
const resultado = await props.opcoes.itens();
itens.value = Array.isArray(resultado) ? resultado : [];
} finally {
carregando.value = false;
}
}
// Recarrega quando muda a função (caso o consumidor troque dinamicamente).
watch(
() => props.opcoes.itens,
() => {
void carregarItens();
}
);
onMounted(() => {
void carregarItens();
});
return { attrs, emit, localValue, opcoes: props.opcoes, itens, carregando };
},
});
</script>
<style scoped></style>

View file

@ -121,6 +121,84 @@ Exemplo:
/>
```
---
#### 4) `EliEntradaParagrafo`
Entrada de texto multi-linha (equivalente a um **textarea**).
**Value**: `string | null | undefined`
**Opções** (além de `rotulo`/`placeholder`):
- `linhas?: number` (default: `4`)
- `limiteCaracteres?: number`
- `limpavel?: boolean`
- `erro?: boolean`
- `mensagensErro?: string | string[]`
- `dica?: string`
- `dicaPersistente?: boolean`
- `densidade?: CampoDensidade`
- `variante?: CampoVariante`
Exemplo:
```vue
<EliEntradaParagrafo
v-model:value="descricao"
:opcoes="{
rotulo: 'Descrição',
placeholder: 'Digite uma descrição mais longa...',
linhas: 5,
limiteCaracteres: 300,
limpavel: true,
}"
/>
```
---
#### 5) `EliEntradaSelecao`
Entrada de seleção (select) com carregamento de itens via função.
**Value**: `string | null | undefined` (chave do item selecionado)
**Opções** (além de `rotulo`/`placeholder`):
- `itens: () => {chave: string; rotulo: string}[] | Promise<{chave: string; rotulo: string}[]>`
- `limpavel?: boolean`
- `erro?: boolean`
- `mensagensErro?: string | string[]`
- `dica?: string`
- `dicaPersistente?: boolean`
- `densidade?: CampoDensidade`
- `variante?: CampoVariante`
Comportamento:
- Ao montar, o componente chama `opcoes.itens()`.
- Enquanto carrega, o select fica em `loading` e desabilitado.
Exemplo:
```vue
<EliEntradaSelecao
v-model:value="categoria"
:opcoes="{
rotulo: 'Categoria',
placeholder: 'Selecione...',
limpavel: true,
itens: async () => {
await new Promise((r) => setTimeout(r, 300));
return [
{ chave: 'a', rotulo: 'Categoria A' },
{ chave: 'b', rotulo: 'Categoria B' },
];
},
}"
/>
```
### Compatibilidade Vue 2 / Vue 3
Padrão recomendado (Vue 3):

View file

@ -1,6 +1,8 @@
import EliEntradaTexto from "./EliEntradaTexto.vue";
import EliEntradaNumero from "./EliEntradaNumero.vue";
import EliEntradaDataHora from "./EliEntradaDataHora.vue";
import EliEntradaParagrafo from "./EliEntradaParagrafo.vue";
import EliEntradaSelecao from "./EliEntradaSelecao.vue";
export { EliEntradaTexto, EliEntradaNumero, EliEntradaDataHora };
export { EliEntradaTexto, EliEntradaNumero, EliEntradaDataHora, EliEntradaParagrafo, EliEntradaSelecao };
export type { PadroesEntradas, TipoEntrada } from "./tiposEntradas";

View file

@ -3,6 +3,8 @@ import type { Component } from "vue";
import EliEntradaTexto from "./EliEntradaTexto.vue";
import EliEntradaNumero from "./EliEntradaNumero.vue";
import EliEntradaDataHora from "./EliEntradaDataHora.vue";
import EliEntradaParagrafo from "./EliEntradaParagrafo.vue";
import EliEntradaSelecao from "./EliEntradaSelecao.vue";
import type { TipoEntrada } from "./tiposEntradas";
@ -10,4 +12,6 @@ export const registryTabelaCelulas = {
texto: EliEntradaTexto,
numero: EliEntradaNumero,
dataHora: EliEntradaDataHora,
paragrafo: EliEntradaParagrafo,
selecao: EliEntradaSelecao,
} as const satisfies Record<TipoEntrada, Component>;

View file

@ -113,6 +113,72 @@ export type PadroesEntradas = {
variante?: import("../../tipos").CampoVariante
}
>
paragrafo: tipoPadraoEntrada<
string | null | undefined,
{
/** Quantidade de linhas visíveis no textarea (Vuetify `rows`). */
linhas?: number
/** Limite máximo de caracteres permitidos (se definido). */
limiteCaracteres?: number
/** Se true, mostra ícone para limpar o valor (Vuetify clearable). */
limpavel?: boolean
/** Estado de erro (visual). */
erro?: boolean
/** Mensagens de erro. */
mensagensErro?: string | string[]
/** Texto de apoio. */
dica?: string
/** Mantém a dica sempre visível. */
dicaPersistente?: boolean
/** Densidade do campo (Vuetify). */
densidade?: import("../../tipos").CampoDensidade
/** Variante do v-text-field (Vuetify). */
variante?: import("../../tipos").CampoVariante
}
>
selecao: tipoPadraoEntrada<
string | null | undefined,
{
/**
* Carrega os itens da seleção (sincrono ou async).
* - Cada item precisa ter uma chave estável (value) e um rótulo (title).
*/
itens: () =>
| { chave: string; rotulo: string }[]
| Promise<{ chave: string; rotulo: string }[]>
/** Se true, mostra ícone para limpar o valor (Vuetify clearable). */
limpavel?: boolean
/** Estado de erro (visual). */
erro?: boolean
/** Mensagens de erro. */
mensagensErro?: string | string[]
/** Texto de apoio. */
dica?: string
/** Mantém a dica sempre visível. */
dicaPersistente?: boolean
/** Densidade do campo (Vuetify). */
densidade?: import("../../tipos").CampoDensidade
/** Variante do v-text-field (Vuetify). */
variante?: import("../../tipos").CampoVariante
}
>
}
/**