57 lines
1.8 KiB
Vue
57 lines
1.8 KiB
Vue
<script setup lang="ts">
|
|
import { ref, HTMLAttributes, emit, computed } from 'vue'
|
|
import { cn } from '@/lib/utils'
|
|
import { useVModel } from '@vueuse/core'
|
|
|
|
const props = defineProps<{
|
|
defaultValue?: string | number
|
|
modelValue?: string | number
|
|
placeholder?: string | number
|
|
class?: HTMLAttributes['class']
|
|
}>()
|
|
|
|
const textElement = ref<HTMLDivElement | null>(null)
|
|
const placeholderVisible = ref(true)
|
|
|
|
const emits = defineEmits<{
|
|
(e: 'update:modelValue', payload: string | number): void
|
|
}>()
|
|
|
|
const modelValue = useVModel(props, 'modelValue', emits, {
|
|
passive: true,
|
|
defaultValue: props.defaultValue,
|
|
})
|
|
|
|
function validate(event: Event) {
|
|
(event.target as HTMLInputElement).blur()
|
|
|
|
modelValue.value = textElement.value?.innerText.trim()
|
|
}
|
|
|
|
const updatePlaceholder = () => {
|
|
placeholderVisible.value = !textElement.value?.innerText.trim()
|
|
}
|
|
|
|
|
|
|
|
|
|
defineExpose({ titleElement: textElement })
|
|
</script>
|
|
|
|
<template>
|
|
<div class="relative">
|
|
<div ref="textElement" contenteditable spellcheck="false" @blur="validate" @update="updatePlaceholder"
|
|
@keydown="updatePlaceholder" :class="cn(
|
|
'whitespace-pre-line inline-block w-full',
|
|
'dark:bg-input/30 border-input w-full min-w-0 rounded-md border bg-input px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
|
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
|
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
|
props.class,
|
|
)">
|
|
{{ modelValue }}
|
|
</div>
|
|
<span v-if="placeholderVisible" class="pointer-events-none absolute left-1 text-muted-foreground/30">
|
|
{{ placeholder }}
|
|
</span>
|
|
</div>
|
|
</template> |