218 lines
7.1 KiB
Vue
218 lines
7.1 KiB
Vue
<template>
|
|
<div class="money-input-container">
|
|
<input
|
|
ref="input"
|
|
:value="displayValue"
|
|
@input="handleInput"
|
|
@keydown="handleKeydown"
|
|
@blur="handleBlur"
|
|
v-bind="$attrs"
|
|
v-on="inputListeners"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
name: 'MoneyInput',
|
|
inheritAttrs: false,
|
|
props: {
|
|
value: {
|
|
type: [String, Number],
|
|
default: ''
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
localValue: '',
|
|
hasComma: false,
|
|
rawDisplayValue: ''
|
|
}
|
|
},
|
|
computed: {
|
|
// Format untuk tampilan
|
|
displayValue() {
|
|
// Jika pengguna baru mengetik, gunakan rawDisplayValue
|
|
if (this.rawDisplayValue) {
|
|
return this.rawDisplayValue;
|
|
}
|
|
|
|
// Format dari nilai model
|
|
return this.formatValue(this.localValue);
|
|
},
|
|
|
|
// Event listeners yang diteruskan
|
|
inputListeners() {
|
|
const listeners = { ...this.$listeners };
|
|
|
|
// Hapus event handlers yang dikelola sendiri
|
|
delete listeners.input;
|
|
delete listeners.keydown;
|
|
delete listeners.blur;
|
|
|
|
return listeners;
|
|
}
|
|
},
|
|
watch: {
|
|
// Sinkronkan props dengan data lokal
|
|
value: {
|
|
immediate: true,
|
|
handler(newValue) {
|
|
// Hindari loop jika nilai sama
|
|
if (newValue !== this.localValue) {
|
|
this.localValue = newValue;
|
|
this.rawDisplayValue = '';
|
|
}
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
// Format nilai untuk tampilan (1234.56 -> 1.234,56)
|
|
formatValue(value) {
|
|
if (!value && value !== 0) return '';
|
|
|
|
// Convert ke string
|
|
const strValue = value.toString();
|
|
|
|
// Pisahkan bagian integer dan desimal
|
|
const parts = strValue.split('.');
|
|
const integerPart = parts[0];
|
|
const decimalPart = parts.length > 1 ? parts[1] : '';
|
|
|
|
// Format bagian integer dengan titik ribuan
|
|
const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
|
|
|
// Gabungkan dengan format display
|
|
return decimalPart ? `${formattedInteger},${decimalPart}` : formattedInteger;
|
|
},
|
|
|
|
// Parse dari format display ke nilai model (1.234,56 -> 1234.56)
|
|
parseValue(displayValue) {
|
|
if (!displayValue) return '';
|
|
|
|
// Pisahkan bagian integer dan desimal
|
|
const parts = displayValue.split(',');
|
|
const integerPart = parts[0].replace(/\./g, ''); // Hapus titik ribuan
|
|
const decimalPart = parts.length > 1 ? parts[1] : '';
|
|
|
|
// Gabungkan dengan format model (titik untuk desimal)
|
|
return decimalPart ? `${integerPart}.${decimalPart}` : integerPart;
|
|
},
|
|
|
|
// Tangani event input
|
|
handleInput(event) {
|
|
const input = event.target;
|
|
const cursorPos = input.selectionStart;
|
|
const rawValue = input.value;
|
|
|
|
// Cek apakah ada koma
|
|
this.hasComma = rawValue.includes(',');
|
|
|
|
// Pisahkan bagian integer dan desimal
|
|
let integerPart, decimalPart;
|
|
|
|
if (this.hasComma) {
|
|
const parts = rawValue.split(',');
|
|
integerPart = parts[0];
|
|
decimalPart = parts.length > 1 ? parts[1] : '';
|
|
} else {
|
|
integerPart = rawValue;
|
|
decimalPart = '';
|
|
}
|
|
|
|
// Format bagian integer (hapus titik lama, tambahkan titik baru)
|
|
const cleanInteger = integerPart.replace(/\./g, '');
|
|
const formattedInteger = cleanInteger.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
|
|
|
// Buat nilai tampilan baru
|
|
this.rawDisplayValue = this.hasComma ? `${formattedInteger},${decimalPart}` : formattedInteger;
|
|
|
|
// Update model value (tanpa formatter)
|
|
const modelValue = this.hasComma ? `${cleanInteger}.${decimalPart}` : cleanInteger;
|
|
this.localValue = modelValue;
|
|
|
|
// Emit event
|
|
this.$emit('input', modelValue);
|
|
|
|
// Set cursor position setelah DOM diupdate
|
|
this.$nextTick(() => {
|
|
// Posisi kursor relatif
|
|
if (this.hasComma) {
|
|
const commaPos = rawValue.indexOf(',');
|
|
|
|
if (cursorPos <= commaPos) {
|
|
// Hitung pergeseran karena dot formatting
|
|
const oldDotsBeforeCursor = (integerPart.substring(0, cursorPos).match(/\./g) || []).length;
|
|
const newDotsBeforeCursor = (formattedInteger.substring(0, cursorPos).match(/\./g) || []).length;
|
|
const offset = newDotsBeforeCursor - oldDotsBeforeCursor;
|
|
input.setSelectionRange(cursorPos + offset, cursorPos + offset);
|
|
} else {
|
|
// Setelah koma, sesuaikan dengan posisi koma baru
|
|
const newCommaPos = formattedInteger.length;
|
|
const offsetAfterComma = cursorPos - commaPos;
|
|
input.setSelectionRange(newCommaPos + offsetAfterComma, newCommaPos + offsetAfterComma);
|
|
}
|
|
} else {
|
|
// Format tanpa koma
|
|
const oldDots = (rawValue.substring(0, cursorPos).match(/\./g) || []).length;
|
|
const newDots = (this.rawDisplayValue.substring(0, cursorPos).match(/\./g) || []).length;
|
|
input.setSelectionRange(cursorPos + (newDots - oldDots), cursorPos + (newDots - oldDots));
|
|
}
|
|
});
|
|
},
|
|
|
|
// Tangani tombol koma dan titik
|
|
handleKeydown(event) {
|
|
// Khusus untuk tombol koma dan titik
|
|
if (event.key === ',' || event.key === '.') {
|
|
// Jika sudah ada koma, cegah input
|
|
if (this.rawDisplayValue.includes(',')) {
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
|
|
event.preventDefault();
|
|
|
|
// Ambil nilai dan posisi kursor saat ini
|
|
const input = event.target;
|
|
const cursorPos = input.selectionStart;
|
|
const currentValue = this.rawDisplayValue || this.displayValue;
|
|
|
|
// Sisipkan koma
|
|
const beforeComma = currentValue.substring(0, cursorPos);
|
|
const afterComma = currentValue.substring(cursorPos);
|
|
const newDisplayValue = beforeComma + ',' + afterComma;
|
|
|
|
// Set nilai display
|
|
this.rawDisplayValue = newDisplayValue;
|
|
|
|
// Update model value
|
|
const parts = newDisplayValue.split(',');
|
|
const integerPart = parts[0].replace(/\./g, '');
|
|
const decimalPart = parts.length > 1 ? parts[1] : '';
|
|
const modelValue = integerPart + (decimalPart ? '.' + decimalPart : '');
|
|
|
|
this.localValue = modelValue;
|
|
this.hasComma = true;
|
|
|
|
// Emit event
|
|
this.$emit('input', modelValue);
|
|
|
|
// Set kursor setelah koma
|
|
this.$nextTick(() => {
|
|
input.setSelectionRange(cursorPos + 1, cursorPos + 1);
|
|
});
|
|
}
|
|
},
|
|
|
|
// Format ulang pada blur
|
|
handleBlur() {
|
|
// Reset raw display value
|
|
this.rawDisplayValue = '';
|
|
|
|
// Emit event
|
|
this.$emit('blur');
|
|
}
|
|
}
|
|
};
|
|
</script> |