feat: melhoria componentes de data

This commit is contained in:
MarcioJRGodoi 2026-06-24 13:51:05 -03:00
parent 84383c3bbf
commit 642ccf4c1a
21 changed files with 6888 additions and 4607 deletions

View file

@ -0,0 +1,248 @@
import dayjs from "dayjs"
import "dayjs/locale/pt-br"
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue"
import type { CampoDensidade, CampoVariante } from "../../../tipos"
import type { PadroesEntradas } from "../tiposEntradas"
import { formatarValor, gerarCelulas } from "./calendarioHelpers"
dayjs.locale("pt-br")
export type EntradaDataHora = PadroesEntradas["dataHora"]
export interface CelulaCalendario {
dia: number
data: dayjs.Dayjs
atual: boolean
hoje: boolean
selecionado: boolean
desabilitado: boolean
}
export const MESES = Array.from({ length: 12 }, (_, i) => {
const s = dayjs().month(i).format("MMMM")
return s.charAt(0).toUpperCase() + s.slice(1)
})
export const diasSemana = ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"]
/** Função de emit genérica do Vue (compatível com defineComponent) */
type EmitFn = (event: string, payload?: unknown) => void
type Props = {
value?: string | null
opcoes?: EntradaDataHora["opcoes"]
modelValue?: string | null
modo?: "data" | "dataHora"
rotulo?: string
placeholder?: string
desabilitado?: boolean
limpavel?: boolean
erro?: boolean
mensagensErro?: string | string[]
dica?: string
dicaPersistente?: boolean
densidade?: CampoDensidade
variante?: CampoVariante
min?: string
max?: string
}
let contadorUid = 0
/** Composable principal do EliEntradaDataHora. */
export function useDataHora(props: Props, emit: EmitFn) {
const uid = ++contadorUid
const aberto = ref(false)
const estiloPopover = ref<Record<string, string>>({})
const referencia = ref(dayjs())
const diaSelecionado = ref<dayjs.Dayjs | null>(null)
const horaTemp = ref("00")
const minutoTemp = ref("00")
const opcoesEfetivas = computed<EntradaDataHora["opcoes"]>(
() =>
props.opcoes ?? {
rotulo: props.rotulo ?? "Data",
placeholder: props.placeholder ?? "",
modo: props.modo ?? "dataHora",
limpavel: props.limpavel,
erro: props.erro,
mensagensErro: props.mensagensErro,
dica: props.dica,
dicaPersistente: props.dicaPersistente,
densidade: props.densidade,
variante: props.variante,
min: props.min,
max: props.max,
},
)
const modoEfetivo = computed<"data" | "dataHora">(() => opcoesEfetivas.value.modo ?? "dataHora")
const desabilitadoEfetivo = computed<boolean>(() => Boolean(props.desabilitado))
const effectiveModelValue = computed<string | null>(() =>
props.value !== undefined ? (props.value ?? null) : (props.modelValue ?? null),
)
const valor = computed(() => effectiveModelValue.value)
const mensagensVisiveis = computed<string[]>(() => {
const m = opcoesEfetivas.value.erro ? opcoesEfetivas.value.mensagensErro : null
return m ? (Array.isArray(m) ? m : [m]) : []
})
const valorFormatado = computed<string>(() => formatarValor(valor.value, modoEfetivo.value))
const nomeMesAtual = computed(() => MESES[referencia.value.month()])
const anoAtual = computed(() => referencia.value.year())
const mesAtual = computed(() => referencia.value.month())
const listaAnos = computed(() => {
const y = referencia.value.year()
const min = opcoesEfetivas.value.min
? dayjs(opcoesEfetivas.value.min).year()
: dayjs().year() - 80
const max = opcoesEfetivas.value.max
? dayjs(opcoesEfetivas.value.max).year()
: dayjs().year() + 20
const anos: number[] = []
for (let i = Math.min(min, y); i <= Math.max(max, y); i++) anos.push(i)
return anos
})
const celulas = computed<CelulaCalendario[]>(() =>
gerarCelulas(
referencia.value,
diaSelecionado.value,
opcoesEfetivas.value.min,
opcoesEfetivas.value.max,
),
)
watch(
effectiveModelValue,
(v) => {
if (v) {
const d = dayjs(v)
diaSelecionado.value = d.startOf("day")
referencia.value = d.startOf("month")
horaTemp.value = d.format("HH")
minutoTemp.value = d.format("mm")
} else {
diaSelecionado.value = null
horaTemp.value = "00"
minutoTemp.value = "00"
}
},
{ immediate: true },
)
function calcularPosicao() {
const el = document.getElementById(`eli-dh-campo-${uid}`)
if (!el) return
const rect = el.getBoundingClientRect()
const top = rect.bottom + window.scrollY + 6
let left = rect.left + window.scrollX
if (left + 316 > window.innerWidth + window.scrollX - 8)
left = rect.right + window.scrollX - 316
estiloPopover.value = { position: "absolute", top: `${top}px`, left: `${left}px` }
}
function abrirSeHabilitado() {
if (desabilitadoEfetivo.value) return
aberto.value = !aberto.value
if (aberto.value) {
emit("foco")
emit("focus")
nextTick(calcularPosicao)
}
}
function fechar() {
if (!aberto.value) return
aberto.value = false
emit("desfoco")
emit("blur")
}
function navegarMes(delta: number) {
referencia.value = referencia.value.add(delta, "month")
}
function mudarMes(mes: number) {
referencia.value = referencia.value.month(mes)
}
function mudarAno(ano: number) {
referencia.value = referencia.value.year(ano)
}
function selecionarDia(cell: CelulaCalendario) {
if (cell.desabilitado) return
diaSelecionado.value = cell.data.startOf("day")
if (modoEfetivo.value === "data") confirmar()
}
function emitirValor(iso: string | null) {
;["update:value", "input", "change", "update:modelValue", "alterar"].forEach((e) => {
emit(e, iso)
})
}
function confirmar() {
if (!diaSelecionado.value) return
const h = Number.parseInt(horaTemp.value, 10) || 0
const m = Number.parseInt(minutoTemp.value, 10) || 0
const r =
modoEfetivo.value === "data"
? diaSelecionado.value.startOf("day")
: diaSelecionado.value.hour(h).minute(m).second(0)
emitirValor(r.format())
fechar()
}
function limpar() {
emitirValor(null)
}
function onDocClick(e: MouseEvent) {
if (!aberto.value) return
const alvo = e.target as Node
if (
!document.getElementById(`eli-dh-campo-${uid}`)?.contains(alvo) &&
!document.getElementById(`eli-dh-popover-${uid}`)?.contains(alvo)
)
fechar()
}
onMounted(() => document.addEventListener("click", onDocClick, true))
onBeforeUnmount(() => document.removeEventListener("click", onDocClick, true))
return {
uid,
aberto,
estiloPopover,
referencia,
diaSelecionado,
horaTemp,
minutoTemp,
opcoesEfetivas,
modoEfetivo,
desabilitadoEfetivo,
valor,
valorFormatado,
mensagensVisiveis,
nomeMesAtual,
anoAtual,
mesAtual,
listaAnos,
mudarMes,
mudarAno,
celulas,
abrirSeHabilitado,
fechar,
navegarMes,
selecionarDia,
confirmar,
limpar,
}
}