106 lines
2.3 KiB
Vue
106 lines
2.3 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed, watch } from 'vue'
|
|
|
|
const plate = defineModel<string>({ required: true })
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'lookup', plate: string): void
|
|
}>()
|
|
|
|
const touched = ref(false)
|
|
const lastEmitted = ref('')
|
|
|
|
const SWEDISH_PLATE_REGEX = /^[A-Z]{3}(\d{3}|\d{2}[A-Z])$/
|
|
|
|
const isValid = computed(() => SWEDISH_PLATE_REGEX.test(plate.value ?? ''))
|
|
|
|
const showError = computed(
|
|
() => touched.value && !isValid.value && (plate.value?.length ?? 0) > 0,
|
|
)
|
|
|
|
function handleInput(event: Event) {
|
|
const target = event.target as HTMLInputElement
|
|
const rawValue = target.value
|
|
const transformed = rawValue.toUpperCase().replace(/[^A-Z0-9]/g, '')
|
|
plate.value = transformed
|
|
touched.value = true
|
|
}
|
|
|
|
watch(isValid, (valid) => {
|
|
if (valid && plate.value && plate.value !== lastEmitted.value) {
|
|
lastEmitted.value = plate.value
|
|
emit('lookup', plate.value)
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="plate-input">
|
|
<label for="plate" class="plate-input__label">Registreringsnummer</label>
|
|
<input
|
|
id="plate"
|
|
type="text"
|
|
inputmode="text"
|
|
autocomplete="off"
|
|
spellcheck="false"
|
|
:value="plate"
|
|
class="plate-input__field"
|
|
:class="{ 'plate-input__field--error': showError }"
|
|
placeholder="ABC 123"
|
|
maxlength="7"
|
|
@input="handleInput"
|
|
/>
|
|
<p v-if="showError" class="plate-input__error">
|
|
Ange ett giltigt registreringsnummer
|
|
</p>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.plate-input {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
width: 100%;
|
|
}
|
|
|
|
.plate-input__label {
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
color: #4a5568;
|
|
}
|
|
|
|
.plate-input__field {
|
|
width: 100%;
|
|
padding: 0.875rem 1rem;
|
|
font-size: 1.5rem;
|
|
font-family: monospace;
|
|
letter-spacing: 0.15em;
|
|
text-transform: uppercase;
|
|
border: 2px solid #cbd5e0;
|
|
border-radius: 0.5rem;
|
|
outline: none;
|
|
transition: border-color 0.15s ease;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.plate-input__field:focus {
|
|
border-color: #4299e1;
|
|
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.25);
|
|
}
|
|
|
|
.plate-input__field--error {
|
|
border-color: #e53e3e;
|
|
}
|
|
|
|
.plate-input__field--error:focus {
|
|
border-color: #e53e3e;
|
|
box-shadow: 0 0 0 3px rgba(229, 62, 62, 0.25);
|
|
}
|
|
|
|
.plate-input__error {
|
|
margin: 0;
|
|
font-size: 0.8125rem;
|
|
color: #e53e3e;
|
|
}
|
|
</style>
|