refatoração
This commit is contained in:
parent
2763da4108
commit
8c1d26f968
4 changed files with 271 additions and 146 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue