refatoração

This commit is contained in:
Luiz Silva 2026-08-08 14:57:24 -03:00
parent 2763da4108
commit 8c1d26f968
4 changed files with 271 additions and 146 deletions

View file

@ -1,14 +1,14 @@
import dayjs from "dayjs"
import "dayjs/locale/pt-br"
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue"
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue"
import type { CampoDensidade, CampoVariante } from "../../../tipos"
import type { PadroesEntradas } from "../tiposEntradas"
import {
aplicarMascaraDataHora,
converterTextoParaIso,
formatarValor,
gerarCelulas,
} from "./calendarioHelpers"
import { formatarValor } from "./calendarioHelpers"
import { useDataHoraInput } from "./useDataHoraInput"
import { useDataHoraPosicao } from "./useDataHoraPosicao"
import { diasSemana, MESES, useDataHoraProps } from "./useDataHoraProps"
export { diasSemana, MESES }
dayjs.locale("pt-br")
@ -23,13 +23,6 @@ export interface CelulaCalendario {
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
@ -58,73 +51,44 @@ let contadorUid = 0
export function useDataHora(props: Props, emit: EmitFn) {
const uid = ++contadorUid
const aberto = ref(false)
const focado = ref(false)
const textoDigitado = ref("")
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"]>(() => {
const o = props.opcoes
return {
rotulo: o?.rotulo ?? props.rotulo ?? "Data",
placeholder: o?.placeholder ?? props.placeholder ?? "",
modo: o?.modo ?? props.modo ?? "dataHora",
limpavel: o?.limpavel ?? props.limpavel,
erro: o?.erro ?? props.erro,
mensagensErro: o?.mensagensErro ?? props.mensagensErro,
dica: o?.dica ?? props.dica,
dicaPersistente: o?.dicaPersistente ?? props.dicaPersistente,
densidade: o?.densidade ?? props.densidade,
variante: o?.variante ?? props.variante,
min: o?.min ?? props.min,
max: o?.max ?? props.max,
}
const {
opcoesEfetivas,
modoEfetivo,
desabilitadoEfetivo,
effectiveModelValue,
valor,
mensagensVisiveis,
valorFormatado,
nomeMesAtual,
anoAtual,
mesAtual,
listaAnos,
celulas,
} = useDataHoraProps({ props, referencia, diaSelecionado })
const { estiloPopover, calcularPosicao, adicionarListenersPosicao, removerListenersPosicao } =
useDataHoraPosicao({ uid, modoEfetivo })
function emitirValor(iso: string | null) {
;["update:value", "input", "change", "update:modelValue", "alterar"].forEach((e) => {
emit(e, iso)
})
}
const { textoDigitado, focado, onTextInput, onInputFocus, onInputBlur } = useDataHoraInput({
modoEfetivo,
valor,
horaTemp,
minutoTemp,
emitirValor,
emit,
})
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) => {
@ -147,35 +111,6 @@ export function useDataHora(props: Props, emit: EmitFn) {
{ immediate: true },
)
function calcularPosicao() {
const el = document.getElementById(`eli-dh-campo-${uid}`)
if (!el) return
const rect = el.getBoundingClientRect()
const popoverAltura = modoEfetivo.value === "dataHora" ? 380 : 320
let top = rect.bottom + 6
if (rect.bottom + popoverAltura > window.innerHeight && rect.top - popoverAltura > 0) {
top = rect.top - popoverAltura - 6
}
let left = rect.left
if (left + 316 > window.innerWidth - 8) {
left = Math.max(8, rect.right - 316)
}
estiloPopover.value = { position: "fixed", top: `${top}px`, left: `${left}px` }
}
function adicionarListenersPosicao() {
window.addEventListener("scroll", calcularPosicao, true)
window.addEventListener("resize", calcularPosicao)
}
function removerListenersPosicao() {
window.removeEventListener("scroll", calcularPosicao, true)
window.removeEventListener("resize", calcularPosicao)
}
function abrirSeHabilitado() {
if (desabilitadoEfetivo.value) return
if (!aberto.value) {
@ -223,12 +158,6 @@ export function useDataHora(props: Props, emit: EmitFn) {
confirmar()
}
function emitirValor(iso: string | null) {
;["update:value", "input", "change", "update:modelValue", "alterar"].forEach((e) => {
emit(e, iso)
})
}
function atualizarHoraTemp(v: string) {
horaTemp.value = v
if (diaSelecionado.value) {
@ -266,43 +195,6 @@ export function useDataHora(props: Props, emit: EmitFn) {
emitirValor(null)
}
function onTextInput(e: Event) {
const rawVal = (e.target as HTMLInputElement).value
const comMascara = aplicarMascaraDataHora(rawVal, modoEfetivo.value)
textoDigitado.value = comMascara
;(e.target as HTMLInputElement).value = comMascara
if (!comMascara) {
emitirValor(null)
return
}
const iso = converterTextoParaIso(comMascara, modoEfetivo.value, {
hora: horaTemp.value,
minuto: minutoTemp.value,
})
if (iso) {
emitirValor(iso)
}
}
function onInputFocus() {
focado.value = true
emit("foco")
emit("focus")
}
function onInputBlur() {
focado.value = false
if (!valor.value) {
textoDigitado.value = ""
} else {
textoDigitado.value = formatarValor(valor.value, modoEfetivo.value)
}
emit("desfoco")
emit("blur")
}
function onDocClick(e: MouseEvent) {
if (!aberto.value) return
const alvo = e.target as Node

View file

@ -0,0 +1,71 @@
import { type Ref, ref } from "vue"
import { aplicarMascaraDataHora, converterTextoParaIso, formatarValor } from "./calendarioHelpers"
interface UseDataHoraInputOptions {
modoEfetivo: Ref<"data" | "dataHora">
valor: Ref<string | null>
horaTemp: Ref<string>
minutoTemp: Ref<string>
emitirValor: (iso: string | null) => void
emit: (event: string, payload?: unknown) => void
}
/**
* Composable responsável pelo controle do campo de texto editável com máscara e eventos de foco/blur.
*/
export function useDataHoraInput({
modoEfetivo,
valor,
horaTemp,
minutoTemp,
emitirValor,
emit,
}: UseDataHoraInputOptions) {
const textoDigitado = ref("")
const focado = ref(false)
function onTextInput(e: Event) {
const rawVal = (e.target as HTMLInputElement).value
const comMascara = aplicarMascaraDataHora(rawVal, modoEfetivo.value)
textoDigitado.value = comMascara
;(e.target as HTMLInputElement).value = comMascara
if (!comMascara) {
emitirValor(null)
return
}
const iso = converterTextoParaIso(comMascara, modoEfetivo.value, {
hora: horaTemp.value,
minuto: minutoTemp.value,
})
if (iso) {
emitirValor(iso)
}
}
function onInputFocus() {
focado.value = true
emit("foco")
emit("focus")
}
function onInputBlur() {
focado.value = false
if (!valor.value) {
textoDigitado.value = ""
} else {
textoDigitado.value = formatarValor(valor.value, modoEfetivo.value)
}
emit("desfoco")
emit("blur")
}
return {
textoDigitado,
focado,
onTextInput,
onInputFocus,
onInputBlur,
}
}

View file

@ -0,0 +1,49 @@
import { type Ref, ref } from "vue"
interface UseDataHoraPosicaoOptions {
uid: number
modoEfetivo: Ref<"data" | "dataHora">
}
/**
* Composable responsável por calcular a posição fixed do popover do calendário.
*/
export function useDataHoraPosicao({ uid, modoEfetivo }: UseDataHoraPosicaoOptions) {
const estiloPopover = ref<Record<string, string>>({})
function calcularPosicao() {
const el = document.getElementById(`eli-dh-campo-${uid}`)
if (!el) return
const rect = el.getBoundingClientRect()
const popoverAltura = modoEfetivo.value === "dataHora" ? 380 : 320
let top = rect.bottom + 6
if (rect.bottom + popoverAltura > window.innerHeight && rect.top - popoverAltura > 0) {
top = rect.top - popoverAltura - 6
}
let left = rect.left
if (left + 316 > window.innerWidth - 8) {
left = Math.max(8, rect.right - 316)
}
estiloPopover.value = { position: "fixed", top: `${top}px`, left: `${left}px` }
}
function adicionarListenersPosicao() {
window.addEventListener("scroll", calcularPosicao, true)
window.addEventListener("resize", calcularPosicao)
}
function removerListenersPosicao() {
window.removeEventListener("scroll", calcularPosicao, true)
window.removeEventListener("resize", calcularPosicao)
}
return {
estiloPopover,
calcularPosicao,
adicionarListenersPosicao,
removerListenersPosicao,
}
}

View file

@ -0,0 +1,113 @@
import dayjs from "dayjs"
import { computed, type Ref } from "vue"
import { formatarValor, gerarCelulas } from "./calendarioHelpers"
import type { EntradaDataHora } from "./useDataHora"
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"]
export interface UseDataHoraPropsOptions {
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?: EntradaDataHora["opcoes"]["densidade"]
variante?: EntradaDataHora["opcoes"]["variante"]
min?: string
max?: string
}
referencia: Ref<dayjs.Dayjs>
diaSelecionado: Ref<dayjs.Dayjs | null>
}
/**
* Composable responsável pelo cálculo de propriedades derivadas e opções do EliEntradaDataHora.
*/
export function useDataHoraProps({ props, referencia, diaSelecionado }: UseDataHoraPropsOptions) {
const opcoesEfetivas = computed<EntradaDataHora["opcoes"]>(() => {
const o = props.opcoes
return {
rotulo: o?.rotulo ?? props.rotulo ?? "Data",
placeholder: o?.placeholder ?? props.placeholder ?? "",
modo: o?.modo ?? props.modo ?? "dataHora",
limpavel: o?.limpavel ?? props.limpavel,
erro: o?.erro ?? props.erro,
mensagensErro: o?.mensagensErro ?? props.mensagensErro,
dica: o?.dica ?? props.dica,
dicaPersistente: o?.dicaPersistente ?? props.dicaPersistente,
densidade: o?.densidade ?? props.densidade,
variante: o?.variante ?? props.variante,
min: o?.min ?? props.min,
max: o?.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(() =>
gerarCelulas(
referencia.value,
diaSelecionado.value,
opcoesEfetivas.value.min,
opcoesEfetivas.value.max,
),
)
return {
opcoesEfetivas,
modoEfetivo,
desabilitadoEfetivo,
effectiveModelValue,
valor,
mensagensVisiveis,
valorFormatado,
nomeMesAtual,
anoAtual,
mesAtual,
listaAnos,
celulas,
}
}