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 from "dayjs"
|
||||||
import "dayjs/locale/pt-br"
|
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 { CampoDensidade, CampoVariante } from "../../../tipos"
|
||||||
import type { PadroesEntradas } from "../tiposEntradas"
|
import type { PadroesEntradas } from "../tiposEntradas"
|
||||||
import {
|
import { formatarValor } from "./calendarioHelpers"
|
||||||
aplicarMascaraDataHora,
|
import { useDataHoraInput } from "./useDataHoraInput"
|
||||||
converterTextoParaIso,
|
import { useDataHoraPosicao } from "./useDataHoraPosicao"
|
||||||
formatarValor,
|
import { diasSemana, MESES, useDataHoraProps } from "./useDataHoraProps"
|
||||||
gerarCelulas,
|
|
||||||
} from "./calendarioHelpers"
|
export { diasSemana, MESES }
|
||||||
|
|
||||||
dayjs.locale("pt-br")
|
dayjs.locale("pt-br")
|
||||||
|
|
||||||
|
|
@ -23,13 +23,6 @@ export interface CelulaCalendario {
|
||||||
desabilitado: 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) */
|
/** Função de emit genérica do Vue (compatível com defineComponent) */
|
||||||
type EmitFn = (event: string, payload?: unknown) => void
|
type EmitFn = (event: string, payload?: unknown) => void
|
||||||
|
|
||||||
|
|
@ -58,73 +51,44 @@ let contadorUid = 0
|
||||||
export function useDataHora(props: Props, emit: EmitFn) {
|
export function useDataHora(props: Props, emit: EmitFn) {
|
||||||
const uid = ++contadorUid
|
const uid = ++contadorUid
|
||||||
const aberto = ref(false)
|
const aberto = ref(false)
|
||||||
const focado = ref(false)
|
|
||||||
const textoDigitado = ref("")
|
|
||||||
const estiloPopover = ref<Record<string, string>>({})
|
|
||||||
const referencia = ref(dayjs())
|
const referencia = ref(dayjs())
|
||||||
const diaSelecionado = ref<dayjs.Dayjs | null>(null)
|
const diaSelecionado = ref<dayjs.Dayjs | null>(null)
|
||||||
const horaTemp = ref("00")
|
const horaTemp = ref("00")
|
||||||
const minutoTemp = ref("00")
|
const minutoTemp = ref("00")
|
||||||
|
|
||||||
const opcoesEfetivas = computed<EntradaDataHora["opcoes"]>(() => {
|
const {
|
||||||
const o = props.opcoes
|
opcoesEfetivas,
|
||||||
return {
|
modoEfetivo,
|
||||||
rotulo: o?.rotulo ?? props.rotulo ?? "Data",
|
desabilitadoEfetivo,
|
||||||
placeholder: o?.placeholder ?? props.placeholder ?? "",
|
effectiveModelValue,
|
||||||
modo: o?.modo ?? props.modo ?? "dataHora",
|
valor,
|
||||||
limpavel: o?.limpavel ?? props.limpavel,
|
mensagensVisiveis,
|
||||||
erro: o?.erro ?? props.erro,
|
valorFormatado,
|
||||||
mensagensErro: o?.mensagensErro ?? props.mensagensErro,
|
nomeMesAtual,
|
||||||
dica: o?.dica ?? props.dica,
|
anoAtual,
|
||||||
dicaPersistente: o?.dicaPersistente ?? props.dicaPersistente,
|
mesAtual,
|
||||||
densidade: o?.densidade ?? props.densidade,
|
listaAnos,
|
||||||
variante: o?.variante ?? props.variante,
|
celulas,
|
||||||
min: o?.min ?? props.min,
|
} = useDataHoraProps({ props, referencia, diaSelecionado })
|
||||||
max: o?.max ?? props.max,
|
|
||||||
|
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(
|
watch(
|
||||||
effectiveModelValue,
|
effectiveModelValue,
|
||||||
(v) => {
|
(v) => {
|
||||||
|
|
@ -147,35 +111,6 @@ export function useDataHora(props: Props, emit: EmitFn) {
|
||||||
{ immediate: true },
|
{ 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() {
|
function abrirSeHabilitado() {
|
||||||
if (desabilitadoEfetivo.value) return
|
if (desabilitadoEfetivo.value) return
|
||||||
if (!aberto.value) {
|
if (!aberto.value) {
|
||||||
|
|
@ -223,12 +158,6 @@ export function useDataHora(props: Props, emit: EmitFn) {
|
||||||
confirmar()
|
confirmar()
|
||||||
}
|
}
|
||||||
|
|
||||||
function emitirValor(iso: string | null) {
|
|
||||||
;["update:value", "input", "change", "update:modelValue", "alterar"].forEach((e) => {
|
|
||||||
emit(e, iso)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function atualizarHoraTemp(v: string) {
|
function atualizarHoraTemp(v: string) {
|
||||||
horaTemp.value = v
|
horaTemp.value = v
|
||||||
if (diaSelecionado.value) {
|
if (diaSelecionado.value) {
|
||||||
|
|
@ -266,43 +195,6 @@ export function useDataHora(props: Props, emit: EmitFn) {
|
||||||
emitirValor(null)
|
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) {
|
function onDocClick(e: MouseEvent) {
|
||||||
if (!aberto.value) return
|
if (!aberto.value) return
|
||||||
const alvo = e.target as Node
|
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