This commit is contained in:
Luiz Silva 2026-01-27 17:11:13 -03:00
parent 67dc4c465a
commit 92662a0b13
20 changed files with 1005 additions and 774 deletions

2
dist/eli-vue.css vendored

File diff suppressed because one or more lines are too long

1432
dist/eli-vue.es.js vendored

File diff suppressed because it is too large Load diff

24
dist/eli-vue.umd.js vendored

File diff suppressed because one or more lines are too long

View file

@ -1,6 +0,0 @@
import type { VNodeChild } from "vue";
export type EliCelulaTextoSimples = {
tipo: "texto-simples";
texto: string;
};
export declare const renderEliCelulaTextoSimples: (celula: EliCelulaTextoSimples) => VNodeChild;

View file

@ -0,0 +1,25 @@
import type { Component } from "vue";
import { PropType } from "vue";
import type { ComponenteCelula } from "../types-eli-tabela";
declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
celula: {
type: PropType<ComponenteCelula>;
required: true;
};
}>, {
Componente: import("vue").ComputedRef<Component>;
dadosParaComponente: import("vue").ComputedRef<{
texto: string;
acao?: () => void;
} | {
numero: number;
acao?: () => void;
}>;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
celula: {
type: PropType<ComponenteCelula>;
required: true;
};
}>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
declare const _default: typeof __VLS_export;
export default _default;

View file

@ -0,0 +1,18 @@
import { PropType } from "vue";
import type { TiposTabelaCelulas } from "./tiposTabelaCelulas";
declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
dados: {
type: PropType<TiposTabelaCelulas["numero"]>;
};
}>, {
dados: {
numero: number;
acao?: () => void;
} | undefined;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
dados: {
type: PropType<TiposTabelaCelulas["numero"]>;
};
}>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
declare const _default: typeof __VLS_export;
export default _default;

View file

@ -0,0 +1,18 @@
import { PropType } from "vue";
import type { TiposTabelaCelulas } from "./tiposTabelaCelulas";
declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
dados: {
type: PropType<TiposTabelaCelulas["textoSimples"]>;
};
}>, {
dados: {
texto: string;
acao?: () => void;
} | undefined;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
dados: {
type: PropType<TiposTabelaCelulas["textoSimples"]>;
};
}>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
declare const _default: typeof __VLS_export;
export default _default;

View file

@ -0,0 +1,30 @@
export declare const registryTabelaCelulas: {
readonly textoSimples: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
dados: {
type: import("vue").PropType<import("./tiposTabelaCelulas").TiposTabelaCelulas["textoSimples"]>;
};
}>, {
dados: {
texto: string;
acao?: () => void;
} | undefined;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
dados: {
type: import("vue").PropType<import("./tiposTabelaCelulas").TiposTabelaCelulas["textoSimples"]>;
};
}>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
readonly numero: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
dados: {
type: import("vue").PropType<import("./tiposTabelaCelulas").TiposTabelaCelulas["numero"]>;
};
}>, {
dados: {
numero: number;
acao?: () => void;
} | undefined;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
dados: {
type: import("vue").PropType<import("./tiposTabelaCelulas").TiposTabelaCelulas["numero"]>;
};
}>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
};

View file

@ -0,0 +1,14 @@
/**
* Tipagem dos dados de entrada dos componentes de celulas
*/
export type TiposTabelaCelulas = {
textoSimples: {
texto: string;
acao?: () => void;
};
numero: {
numero: number;
acao?: () => void;
};
};
export type TipoTabelaCelula = keyof TiposTabelaCelulas;

View file

@ -1,3 +1,4 @@
export { default as EliTabela } from "./EliTabela.vue";
export * from "./types-eli-tabela";
export * from "./celulas/EliCelulaTextoSimples";
export * from "./celulas/tiposTabelaCelulas";
export { celulaTabela } from "./types-eli-tabela";

View file

@ -1,7 +1,12 @@
import type { tipoResposta } from "p-respostas";
import type { LucideIcon } from "lucide-vue-next";
import type { VNodeChild } from "vue";
export type ComponenteCelula = VNodeChild;
import type { TipoTabelaCelula, TiposTabelaCelulas } from "./celulas/tiposTabelaCelulas";
export type ComponenteCelulaBase<T extends TipoTabelaCelula> = readonly [T, TiposTabelaCelulas[T]];
export type ComponenteCelula = {
[K in TipoTabelaCelula]: ComponenteCelulaBase<K>;
}[TipoTabelaCelula];
export declare const celulaTabela: <T extends TipoTabelaCelula>(tipo: T, dados: TiposTabelaCelulas[T]) => ComponenteCelulaBase<T>;
export type { TipoTabelaCelula, TiposTabelaCelulas };
export type EliAlinhamentoColuna = "esquerda" | "centro" | "direita";
export type EliColuna<T> = {
/** Texto exibido no cabeçalho da coluna. */

View file

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

View file

@ -10,6 +10,7 @@ import { ArrowDown, ArrowUp, MoreVertical } from "lucide-vue-next";
import { codigosResposta } from "p-respostas";
import EliTabelaCaixaDeBusca from "./EliTabelaCaixaDeBusca.vue";
import EliTabelaPaginacao from "./EliTabelaPaginacao.vue";
import EliTabelaCelula from "./celulas/EliTabelaCelula.vue";
import type { EliTabelaConsulta } from "./types-eli-tabela";
export default defineComponent({
@ -131,15 +132,6 @@ export default defineComponent({
menuAberto.value = null;
}
function normalizarFilhos(filhos: unknown) {
// `VNodeChild` pode ser null/undefined/boolean.
// Para a assinatura de `h()`, normalizamos para string vazia.
if (filhos === null || filhos === undefined || filhos === false) {
return "";
}
return filhos as never;
}
function obterClasseAlinhamento(alinhamento?: string) {
if (alinhamento === "direita") {
return "eli-tabela__celula--direita";
@ -157,34 +149,23 @@ export default defineComponent({
return typeof largura === "number" ? `${largura}px` : String(largura);
}
function renderConteudoCelula(valor: unknown, coluna: (typeof props.tabela.colunas)[number]) {
const filhos = normalizarFilhos(valor);
const truncar = Boolean(coluna.truncar);
if (!truncar) {
return filhos;
function obterTooltipCelula(celula: unknown) {
if (!Array.isArray(celula)) {
return undefined;
}
// Só truncamos de forma segura quando o conteúdo é textual.
if (typeof filhos !== "string" && typeof filhos !== "number") {
return filhos;
const tipo = celula[0];
const dados = celula[1] as any;
if (tipo === "textoSimples") {
return typeof dados?.texto === "string" ? dados.texto : undefined;
}
const tooltip = String(filhos);
if (tipo === "numero") {
return typeof dados?.numero === "number" ? String(dados.numero) : undefined;
}
return h(
"span",
{
class: "eli-tabela__celula-conteudo",
style: coluna.largura_maxima
? {
maxWidth: obterMaxWidth(coluna.largura_maxima),
}
: undefined,
title: tooltip,
},
tooltip
);
return undefined;
}
function renderErro(mensagem: string) {
@ -557,19 +538,45 @@ export default defineComponent({
{ class: "eli-tabela__tbody" },
linhas.value.map((linha, i) => {
const celulas = colunas.map((coluna, j) =>
h(
"td",
{
class: [
"eli-tabela__td",
coluna.acao ? "eli-tabela__td--clicavel" : undefined,
obterClasseAlinhamento(coluna.alinhamento),
],
key: `${i}-${j}`,
onClick: coluna.acao ? () => coluna.acao?.() : undefined,
},
renderConteudoCelula(coluna.celula(linha as never), coluna)
)
(() => {
const celula = coluna.celula(linha as never);
const truncar = Boolean(coluna.truncar);
const tooltip = truncar ? obterTooltipCelula(celula) : undefined;
const conteudo = h(EliTabelaCelula, {
celula: celula as never,
});
const conteudoFinal = truncar
? h(
"span",
{
class: "eli-tabela__celula-conteudo",
style: coluna.largura_maxima
? {
maxWidth: obterMaxWidth(coluna.largura_maxima),
}
: undefined,
title: tooltip,
},
conteudo
)
: conteudo;
return h(
"td",
{
class: [
"eli-tabela__td",
coluna.acao ? "eli-tabela__td--clicavel" : undefined,
obterClasseAlinhamento(coluna.alinhamento),
],
key: `${i}-${j}`,
onClick: coluna.acao ? () => coluna.acao?.() : undefined,
},
conteudoFinal
);
})()
);
if (temAcoes) {

View file

@ -0,0 +1,39 @@
<template>
<!--
Os componentes de célula possuem props tipadas por `tipo`.
Aqui o TS do template não consegue inferir o narrowing do union do `dados`,
então normalizamos para `unknown` e deixamos a validação de runtime do Vue.
-->
<component :is="Componente" :dados="dadosParaComponente" />
</template>
<script lang="ts">
import type { Component } from "vue";
import { computed, defineComponent, PropType } from "vue";
import type { ComponenteCelula, TipoTabelaCelula, TiposTabelaCelulas } from "../types-eli-tabela";
import { registryTabelaCelulas } from "./registryTabelaCelulas";
export default defineComponent({
name: "EliTabelaCelula",
props: {
celula: {
// `ComponenteCelula` é uma tupla `readonly [tipo, dados]`.
type: Array as unknown as PropType<ComponenteCelula>,
required: true,
},
},
setup(props) {
const tipo = computed(() => props.celula[0] as TipoTabelaCelula);
const dados = computed(() => props.celula[1] as TiposTabelaCelulas[TipoTabelaCelula]);
// Observação: mantemos o registry tipado, mas o TS do template não consegue
// fazer narrowing do componente com base em `tipo`, então tipamos como `Component`.
const Componente = computed(() => registryTabelaCelulas[tipo.value] as unknown as Component);
const dadosParaComponente = computed(() => dados.value);
return { Componente, dadosParaComponente };
},
});
</script>

View file

@ -4,14 +4,14 @@
<script lang="ts">
import { defineComponent, PropType } from "vue"
import { tiposTabelaCelulas } from "./tiposTabelaCelulas";
import type { TiposTabelaCelulas } from "./tiposTabelaCelulas";
export default defineComponent({
name: "EliTabelaCelulaNumero",
components: {},
props: {
dados: {
type: Object as PropType<tiposTabelaCelulas['numero']>,
type: Object as PropType<TiposTabelaCelulas["numero"]>,
},
},
data() {

View file

@ -4,14 +4,14 @@
<script lang="ts">
import { defineComponent, PropType } from "vue"
import { tiposTabelaCelulas } from "./tiposTabelaCelulas";
import type { TiposTabelaCelulas } from "./tiposTabelaCelulas";
export default defineComponent({
name: "EliTabelaCelulaTextoSimples",
components: {},
props: {
dados: {
type: Object as PropType<tiposTabelaCelulas['textoSimples']>,
type: Object as PropType<TiposTabelaCelulas["textoSimples"]>,
},
},
data() {

View file

@ -0,0 +1,10 @@
import type { Component } from "vue";
import EliTabelaCelulaTextoSimples from "./EliTabelaCelulaTextoSimples.vue";
import EliTabelaCelulaNumero from "./EliTabelaCelulaNumero.vue";
import type { TipoTabelaCelula } from "./tiposTabelaCelulas";
export const registryTabelaCelulas = {
textoSimples: EliTabelaCelulaTextoSimples,
numero: EliTabelaCelulaNumero,
} as const satisfies Record<TipoTabelaCelula, Component>;

View file

@ -2,14 +2,15 @@
* Tipagem dos dados de entrada dos componentes de celulas
*/
export type tiposTabelaCelulas = {
"textoSimples": {
texto: string,
acao?: ()=>{}
}
export type TiposTabelaCelulas = {
textoSimples: {
texto: string;
acao?: () => void;
};
numero: {
numero: number;
acao?: () => void;
};
};
"numero": {
numero: number,
acao?: ()=>{}
}
}
export type TipoTabelaCelula = keyof TiposTabelaCelulas;

View file

@ -1,4 +1,7 @@
export { default as EliTabela } from "./EliTabela.vue";
export * from "./types-eli-tabela";
export * from "./celulas/EliCelulaTextoSimples";
export * from "./celulas/tiposTabelaCelulas";
// Helper para construção de células tipadas.
export { celulaTabela } from "./types-eli-tabela";

View file

@ -1,25 +1,27 @@
import type { tipoResposta } from "p-respostas";
import type { LucideIcon } from "lucide-vue-next";
import { tiposTabelaCelulas } from "./celulas/tiposTabelaCelulas";
import type { TipoTabelaCelula, TiposTabelaCelulas } from "./celulas/tiposTabelaCelulas";
export type ComponenteCelulaBase<T extends keyof tiposTabelaCelulas> =
readonly [T, tiposTabelaCelulas[T]]
export type ComponenteCelulaBase<T extends TipoTabelaCelula> =
readonly [T, TiposTabelaCelulas[T]]
export type ComponenteCelula = {
[K in keyof tiposTabelaCelulas]: ComponenteCelulaBase<K>
}[keyof tiposTabelaCelulas]
[K in TipoTabelaCelula]: ComponenteCelulaBase<K>
}[TipoTabelaCelula]
export const celulaTabela = <T extends keyof tiposTabelaCelulas>(
export const celulaTabela = <T extends TipoTabelaCelula>(
tipo: T,
dados: tiposTabelaCelulas[T],
dados: TiposTabelaCelulas[T],
): ComponenteCelulaBase<T> => {
return [tipo, dados] as const
}
export type { TipoTabelaCelula, TiposTabelaCelulas };
export type EliAlinhamentoColuna = "esquerda" | "centro" | "direita";