Fix: growing textarea now working properly. Placeholder wasn't working before
Preparation for #42
This commit is contained in:
@@ -94,18 +94,17 @@ const recalculatePositions = () => {
|
|||||||
<!-- Posten -->
|
<!-- Posten -->
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Input v-model="element.title" placeholder="Posten"
|
<Input v-model="element.title" placeholder="Posten"
|
||||||
class="font-bold h-6 py-0 px-1 m-0 bg-transparent dark:bg-transparent hover:bg-background/66 dark:hover:bg-background/66 border-none hover:border-1 dark:hover:border-1 placeholder:text-muted-foreground/30 shadow-none mb-1" />
|
class="font-bold h-fit p-1 h-7! m-0 bg-transparent dark:bg-transparent hover:bg-background/66 dark:hover:bg-background/66 border-none hover:border-1 dark:hover:border-1 placeholder:text-muted-foreground/30 shadow-none mb-1" />
|
||||||
<!-- <Textarea v-model="element.description" placeholder="Beschreibung"
|
|
||||||
class="py-0 min-h-4 px-1 m-0 bg-transparent dark:bg-transparent border-none placeholder:text-muted-foreground/30 shadow-none" /> -->
|
|
||||||
<GrowingTextarea v-model="element.description" placeholder="Beschreibung"
|
<GrowingTextarea v-model="element.description" placeholder="Beschreibung"
|
||||||
class="font-light bg-transparent dark:bg-transparent hover:bg-background/66 dark:hover:bg-background/66 py-0 px-1 m-0 border-none shadow-none" />
|
class="font-light m-0 bg-transparent dark:bg-transparent hover:bg-background/66 dark:hover:bg-background/66 py-0 px-1 m-0 border-none shadow-none" />
|
||||||
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<!-- Einh. -->
|
<!-- Einh. -->
|
||||||
<TableCell class="w-1/8 text-center">
|
<TableCell class="w-1/8 text-center">
|
||||||
<Select v-model="element.unit">
|
<Select v-model="element.unit">
|
||||||
<SelectTrigger class="shadow-none bg-transparent dark:bg-transparent hover:bg-background/66 dark:hover:bg-background/66 border-none pr-0 py-0 pl-1 w-full h-6!">
|
<SelectTrigger
|
||||||
|
class="shadow-none bg-transparent p-1 h-7! dark:bg-transparent hover:bg-background/66 dark:hover:bg-background/66 border-none w-full">
|
||||||
<SelectValue placeholder="Einheit" />
|
<SelectValue placeholder="Einheit" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -122,16 +121,20 @@ const recalculatePositions = () => {
|
|||||||
<TableCell class="w-20 text-center">
|
<TableCell class="w-20 text-center">
|
||||||
<NumberField v-model="element.quantity" :step="0.5" :format-options="{}">
|
<NumberField v-model="element.quantity" :step="0.5" :format-options="{}">
|
||||||
<NumberFieldContent>
|
<NumberFieldContent>
|
||||||
<NumberFieldDecrement class="text-muted-foreground p-1 bg-transparent dark:bg-transparent hover:bg-background/66 dark:hover:bg-background/66" />
|
<NumberFieldDecrement
|
||||||
<NumberFieldInput class="h-6 border-none bg-transparent dark:bg-transparent hover:bg-background/66 dark:hover:bg-background/66" />
|
class="text-muted-foreground p-1 h-7! bg-transparent dark:bg-transparent hover:bg-background/66 dark:hover:bg-background/66" />
|
||||||
<NumberFieldIncrement class="text-muted-foreground p-1 bg-transparent dark:bg-transparent hover:bg-background/66 dark:hover:bg-background/66" />
|
<NumberFieldInput
|
||||||
|
class="h-6 border-none p-1 h-7! bg-transparent dark:bg-transparent hover:bg-background/66 dark:hover:bg-background/66" />
|
||||||
|
<NumberFieldIncrement
|
||||||
|
class="text-muted-foreground p-1 h-7! bg-transparent dark:bg-transparent hover:bg-background/66 dark:hover:bg-background/66" />
|
||||||
</NumberFieldContent>
|
</NumberFieldContent>
|
||||||
</NumberField>
|
</NumberField>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<!-- Preis -->
|
<!-- Preis -->
|
||||||
<TableCell class="w-1/8 text-right tabular-nums">
|
<TableCell class="w-1/8 text-right tabular-nums">
|
||||||
<NumberInput :modelValue="Number(element.price)" class="bg-transparent dark:bg-transparent hover:bg-background/66 dark:hover:bg-background/66 h-6 rounded" />
|
<NumberInput :modelValue="Number(element.price)"
|
||||||
|
class="bg-transparent p-1 h-7! dark:bg-transparent hover:bg-background/66 dark:hover:bg-background/66 rounded shadow-none!" />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<!-- Total -->
|
<!-- Total -->
|
||||||
@@ -168,7 +171,7 @@ const recalculatePositions = () => {
|
|||||||
|
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
<Empty v-if="items.length < 1">
|
<Empty v-if="items.length < 1" class="md:pb-0 md:pt-8">
|
||||||
<EmptyHeader>
|
<EmptyHeader>
|
||||||
<EmptyMedia variant="icon">
|
<EmptyMedia variant="icon">
|
||||||
<TextSelect class="text-muted-foreground" stroke-width="1.5" />
|
<TextSelect class="text-muted-foreground" stroke-width="1.5" />
|
||||||
|
|||||||
@@ -1,57 +1,77 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, HTMLAttributes, emit, computed } from 'vue'
|
import { watch, type HTMLAttributes, useTemplateRef, onMounted } from "vue"
|
||||||
import { cn } from '@/lib/utils'
|
import { useVModel } from "@vueuse/core"
|
||||||
import { useVModel } from '@vueuse/core'
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
defaultValue?: string | number
|
class?: HTMLAttributes["class"]
|
||||||
modelValue?: string | number
|
defaultValue?: string
|
||||||
placeholder?: string | number
|
modelValue?: string
|
||||||
class?: HTMLAttributes['class']
|
placeholder?: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const textElement = ref<HTMLDivElement | null>(null)
|
|
||||||
const placeholderVisible = ref(true)
|
|
||||||
|
|
||||||
const emits = defineEmits<{
|
const emits = defineEmits<{
|
||||||
(e: 'update:modelValue', payload: string | number): void
|
(e: "update:modelValue", payload: string | number): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const modelValue = useVModel(props, 'modelValue', emits, {
|
const wrapper = useTemplateRef('grow-wrap')
|
||||||
|
|
||||||
|
const modelValue = useVModel(props, "modelValue", emits, {
|
||||||
passive: true,
|
passive: true,
|
||||||
defaultValue: props.defaultValue,
|
defaultValue: props.defaultValue,
|
||||||
})
|
})
|
||||||
|
|
||||||
function validate(event: Event) {
|
watch(modelValue, (value) => {
|
||||||
(event.target as HTMLInputElement).blur()
|
if (!wrapper.value) return
|
||||||
|
wrapper.value.dataset.replicatedValue = value as string
|
||||||
|
})
|
||||||
|
|
||||||
modelValue.value = textElement.value?.innerText.trim()
|
onMounted(() => {
|
||||||
}
|
if (!wrapper.value) return
|
||||||
|
wrapper.value.dataset.replicatedValue = modelValue.value as string
|
||||||
|
})
|
||||||
|
|
||||||
const updatePlaceholder = () => {
|
|
||||||
placeholderVisible.value = !textElement.value?.innerText.trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
defineExpose({ titleElement: textElement })
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="relative">
|
<div ref="grow-wrap" class="grow-wrap">
|
||||||
<div ref="textElement" contenteditable spellcheck="false" @blur="validate" @update="updatePlaceholder"
|
<textarea rows="1" name="text" id="text" v-model="modelValue" data-slot="textarea" :placeholder="placeholder"
|
||||||
@keydown="updatePlaceholder" :class="cn(
|
:class="cn('border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm', props.class)" />
|
||||||
'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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.grow-wrap {
|
||||||
|
/* easy way to plop the elements on top of each other and have them both sized based on the tallest one's height */
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grow-wrap::after {
|
||||||
|
/* Note the weird space! Needed to preventy jumpy behavior */
|
||||||
|
content: attr(data-replicated-value) " ";
|
||||||
|
|
||||||
|
/* This is how textarea text behaves */
|
||||||
|
white-space: pre-wrap;
|
||||||
|
|
||||||
|
/* Hidden from view, clicks, and screen readers */
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grow-wrap>textarea {
|
||||||
|
/* You could leave this, but after a user resizes, then it ruins the auto sizing */
|
||||||
|
resize: none;
|
||||||
|
|
||||||
|
/* Firefox shows scrollbar on growth, you can hide like this. */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grow-wrap>textarea,
|
||||||
|
.grow-wrap::after {
|
||||||
|
/* Identical styling required!! */
|
||||||
|
padding: calc(var(--spacing) * 1);
|
||||||
|
font: inherit;
|
||||||
|
|
||||||
|
/* Place on top of each other */
|
||||||
|
grid-area: 1 / 1 / 2 / 2;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user