mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-21 02:04:26 +08:00
automatically focus after opening the dialog and support confirming with the enter key
This commit is contained in:
@@ -15,11 +15,16 @@ export interface CommonNumberInputProps {
|
|||||||
modelValue: number;
|
modelValue: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CommonNumberInputEmits {
|
||||||
|
(e: 'update:modelValue', value: number): void;
|
||||||
|
(e: 'enter'): void;
|
||||||
|
}
|
||||||
|
|
||||||
export type ParseNumberFunction = (value: string) => number;
|
export type ParseNumberFunction = (value: string) => number;
|
||||||
export type FormatNumberFunction = (value: number) => string;
|
export type FormatNumberFunction = (value: number) => string;
|
||||||
export type GetValidFormattedValueFunction = (value: number, textualValue: string, hasDecimalSeparator: boolean) => string;
|
export type GetValidFormattedValueFunction = (value: number, textualValue: string, hasDecimalSeparator: boolean) => string;
|
||||||
|
|
||||||
export function useCommonNumberInputBase(props: CommonNumberInputProps, maxDecimalCount: number, initValue: string, parseNumber: ParseNumberFunction, formatNumber: FormatNumberFunction, getValidFormattedValue: GetValidFormattedValueFunction) {
|
export function useCommonNumberInputBase(props: CommonNumberInputProps, emit: CommonNumberInputEmits, maxDecimalCount: number, initValue: string, parseNumber: ParseNumberFunction, formatNumber: FormatNumberFunction, getValidFormattedValue: GetValidFormattedValueFunction) {
|
||||||
const {
|
const {
|
||||||
getCurrentNumeralSystemType,
|
getCurrentNumeralSystemType,
|
||||||
getCurrentDecimalSeparator,
|
getCurrentDecimalSeparator,
|
||||||
@@ -43,6 +48,12 @@ export function useCommonNumberInputBase(props: CommonNumberInputProps, maxDecim
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
emit('enter');
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const digitGroupingSymbol = getCurrentDigitGroupingSymbol();
|
const digitGroupingSymbol = getCurrentDigitGroupingSymbol();
|
||||||
const decimalSeparator = getCurrentDecimalSeparator();
|
const decimalSeparator = getCurrentDecimalSeparator();
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import { computed, watch } from 'vue';
|
import { computed, watch } from 'vue';
|
||||||
|
|
||||||
import { useI18n } from '@/locales/helpers.ts';
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
import { type CommonNumberInputProps, useCommonNumberInputBase } from '@/components/base/CommonNumberInputBase.ts';
|
import {
|
||||||
|
type CommonNumberInputProps,
|
||||||
|
type CommonNumberInputEmits,
|
||||||
|
useCommonNumberInputBase
|
||||||
|
} from '@/components/base/CommonNumberInputBase.ts';
|
||||||
|
|
||||||
import { isDefined, isNumber, replaceAll, removeAll } from '@/lib/common.ts';
|
import { isDefined, isNumber, replaceAll, removeAll } from '@/lib/common.ts';
|
||||||
import { NumeralSystem } from '@/core/numeral.ts';
|
import { NumeralSystem } from '@/core/numeral.ts';
|
||||||
@@ -12,11 +16,9 @@ export interface NumberInputProps extends CommonNumberInputProps {
|
|||||||
maxDecimalCount?: number;
|
maxDecimalCount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NumberInputEmits {
|
export type NumberInputEmits = CommonNumberInputEmits;
|
||||||
(e: 'update:modelValue', value: number): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useNumberInputBase(props: NumberInputProps, emit: NumberInputEmits) {
|
export function useNumberInputBase(props: NumberInputProps, emit: CommonNumberInputEmits) {
|
||||||
const {
|
const {
|
||||||
getCurrentNumeralSystemType,
|
getCurrentNumeralSystemType,
|
||||||
getCurrentDecimalSeparator,
|
getCurrentDecimalSeparator,
|
||||||
@@ -27,7 +29,7 @@ export function useNumberInputBase(props: NumberInputProps, emit: NumberInputEmi
|
|||||||
currentValue,
|
currentValue,
|
||||||
onKeyUpDown,
|
onKeyUpDown,
|
||||||
onPaste
|
onPaste
|
||||||
} = useCommonNumberInputBase(props, props.maxDecimalCount ?? -1, getFormattedValue(props.modelValue), parseNumber, getFormattedValue, getValidFormattedValue);
|
} = useCommonNumberInputBase(props, emit, props.maxDecimalCount ?? -1, getFormattedValue(props.modelValue), parseNumber, getFormattedValue, getValidFormattedValue);
|
||||||
|
|
||||||
const numeralSystem = computed<NumeralSystem>(() => getCurrentNumeralSystemType());
|
const numeralSystem = computed<NumeralSystem>(() => getCurrentNumeralSystemType());
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-text-field type="text" class="text-field-with-colored-label" :class="extraClass"
|
<v-text-field type="text" class="text-field-with-colored-label" :class="extraClass"
|
||||||
:color="color" :base-color="color"
|
:color="color" :base-color="color"
|
||||||
:density="density" :variant="variant"
|
:density="density" :variant="variant" :autofocus="autofocus"
|
||||||
:readonly="!!readonly" :disabled="!!disabled"
|
:readonly="!!readonly" :disabled="!!disabled"
|
||||||
:label="label" :placeholder="placeholder"
|
:label="label" :placeholder="placeholder"
|
||||||
:persistent-placeholder="!!persistentPlaceholder"
|
:persistent-placeholder="!!persistentPlaceholder"
|
||||||
@@ -73,7 +73,11 @@ import SnackBar from '@/components/desktop/SnackBar.vue';
|
|||||||
import { ref, computed, useTemplateRef, watch } from 'vue';
|
import { ref, computed, useTemplateRef, watch } from 'vue';
|
||||||
|
|
||||||
import { useI18n } from '@/locales/helpers.ts';
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
import { type CommonNumberInputProps, useCommonNumberInputBase } from '@/components/base/CommonNumberInputBase.ts';
|
import {
|
||||||
|
type CommonNumberInputProps,
|
||||||
|
type CommonNumberInputEmits,
|
||||||
|
useCommonNumberInputBase
|
||||||
|
} from '@/components/base/CommonNumberInputBase.ts';
|
||||||
|
|
||||||
import { NumeralSystem, DecimalSeparator } from '@/core/numeral.ts';
|
import { NumeralSystem, DecimalSeparator } from '@/core/numeral.ts';
|
||||||
import type { CurrencyPrependAndAppendText } from '@/core/currency.ts';
|
import type { CurrencyPrependAndAppendText } from '@/core/currency.ts';
|
||||||
@@ -97,6 +101,7 @@ interface DesktopAmountInputProps extends CommonNumberInputProps {
|
|||||||
color?: string;
|
color?: string;
|
||||||
density?: ComponentDensity;
|
density?: ComponentDensity;
|
||||||
variant?: InputVariant;
|
variant?: InputVariant;
|
||||||
|
autofocus?: boolean;
|
||||||
currency: string;
|
currency: string;
|
||||||
showCurrency?: boolean;
|
showCurrency?: boolean;
|
||||||
persistentPlaceholder?: boolean;
|
persistentPlaceholder?: boolean;
|
||||||
@@ -107,10 +112,7 @@ interface DesktopAmountInputProps extends CommonNumberInputProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<DesktopAmountInputProps>();
|
const props = defineProps<DesktopAmountInputProps>();
|
||||||
|
const emit = defineEmits<CommonNumberInputEmits>();
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: 'update:modelValue', value: number): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
tt,
|
tt,
|
||||||
@@ -125,7 +127,7 @@ const {
|
|||||||
currentValue,
|
currentValue,
|
||||||
onKeyUpDown,
|
onKeyUpDown,
|
||||||
onPaste
|
onPaste
|
||||||
} = useCommonNumberInputBase(props, DEFAULT_DECIMAL_NUMBER_COUNT, getInitedFormattedValue(props.modelValue, props.flipNegative), parseAmountFromLocalizedNumerals, getFormattedValue, getValidFormattedValue);
|
} = useCommonNumberInputBase(props, emit, DEFAULT_DECIMAL_NUMBER_COUNT, getInitedFormattedValue(props.modelValue, props.flipNegative), parseAmountFromLocalizedNumerals, getFormattedValue, getValidFormattedValue);
|
||||||
|
|
||||||
const snackbar = useTemplateRef<SnackBarType>('snackbar');
|
const snackbar = useTemplateRef<SnackBarType>('snackbar');
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,13 @@
|
|||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
<amount-input :persistent-placeholder="true"
|
<amount-input :persistent-placeholder="true"
|
||||||
|
:autofocus="true"
|
||||||
:currency="dialogOptions?.currency"
|
:currency="dialogOptions?.currency"
|
||||||
:show-currency="!!dialogOptions?.currency"
|
:show-currency="!!dialogOptions?.currency"
|
||||||
:label="inputLabelContent"
|
:label="inputLabelContent"
|
||||||
:placeholder="inputPlaceholderContent"
|
:placeholder="inputPlaceholderContent"
|
||||||
v-model="amount" />
|
v-model="amount"
|
||||||
|
@enter="confirm" />
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-form>
|
</v-form>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-text-field type="text" :class="extraClass" :density="density" :readonly="!!readonly" :disabled="!!disabled"
|
<v-text-field ref="textInput" type="text" :class="extraClass" :density="density" :readonly="!!readonly" :disabled="!!disabled"
|
||||||
:label="label" :placeholder="placeholder"
|
:label="label" :placeholder="placeholder"
|
||||||
:persistent-placeholder="!!persistentPlaceholder"
|
:persistent-placeholder="!!persistentPlaceholder"
|
||||||
v-model="currentValue"
|
v-model="currentValue"
|
||||||
@@ -8,7 +8,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { VTextField } from 'vuetify/components/VTextField';
|
||||||
|
|
||||||
|
import { computed, useTemplateRef } from 'vue';
|
||||||
|
|
||||||
import { type NumberInputProps, type NumberInputEmits, useNumberInputBase } from '@/components/base/NumberInputBase.ts';
|
import { type NumberInputProps, type NumberInputEmits, useNumberInputBase } from '@/components/base/NumberInputBase.ts';
|
||||||
|
|
||||||
@@ -29,7 +31,17 @@ const {
|
|||||||
onPaste
|
onPaste
|
||||||
} = useNumberInputBase(props, emit);
|
} = useNumberInputBase(props, emit);
|
||||||
|
|
||||||
|
const textInput = useTemplateRef<VTextField>('textInput');
|
||||||
|
|
||||||
const extraClass = computed<string>(() => {
|
const extraClass = computed<string>(() => {
|
||||||
return props.class || '';
|
return props.class || '';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function focus(): void {
|
||||||
|
textInput.value?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
focus
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -9,14 +9,16 @@
|
|||||||
<v-card-text class="d-flex flex-column flex-md-row mt-2">
|
<v-card-text class="d-flex flex-column flex-md-row mt-2">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<number-input :disabled="submitting"
|
<number-input :autofocus="true"
|
||||||
|
:disabled="submitting"
|
||||||
:label="tt('Amount')"
|
:label="tt('Amount')"
|
||||||
:placeholder="tt('Amount')"
|
:placeholder="tt('Amount')"
|
||||||
:persistent-placeholder="true"
|
:persistent-placeholder="true"
|
||||||
:min-value="USER_CUSTOM_EXCHANGE_RATE_MIN_VALUE"
|
:min-value="USER_CUSTOM_EXCHANGE_RATE_MIN_VALUE"
|
||||||
:max-value="USER_CUSTOM_EXCHANGE_RATE_MAX_VALUE"
|
:max-value="USER_CUSTOM_EXCHANGE_RATE_MAX_VALUE"
|
||||||
:max-decimal-count="4"
|
:max-decimal-count="4"
|
||||||
v-model="defaultCurrencyAmount"/>
|
v-model="defaultCurrencyAmount"
|
||||||
|
@keyup.enter="targetAmountInput?.focus()" />
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<currency-select :disabled="true"
|
<currency-select :disabled="true"
|
||||||
@@ -28,14 +30,15 @@
|
|||||||
<v-icon :icon="mdiSwapVertical" size="24" />
|
<v-icon :icon="mdiSwapVertical" size="24" />
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<number-input :disabled="submitting"
|
<number-input ref="targetAmountInput" :disabled="submitting"
|
||||||
:label="tt('Amount')"
|
:label="tt('Amount')"
|
||||||
:placeholder="tt('Amount')"
|
:placeholder="tt('Amount')"
|
||||||
:persistent-placeholder="true"
|
:persistent-placeholder="true"
|
||||||
:min-value="USER_CUSTOM_EXCHANGE_RATE_MIN_VALUE"
|
:min-value="USER_CUSTOM_EXCHANGE_RATE_MIN_VALUE"
|
||||||
:max-value="USER_CUSTOM_EXCHANGE_RATE_MAX_VALUE"
|
:max-value="USER_CUSTOM_EXCHANGE_RATE_MAX_VALUE"
|
||||||
:max-decimal-count="4"
|
:max-decimal-count="4"
|
||||||
v-model="targetCurrencyAmount"/>
|
v-model="targetCurrencyAmount"
|
||||||
|
@keyup.enter="confirm" />
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<currency-select :disabled="submitting"
|
<currency-select :disabled="submitting"
|
||||||
@@ -61,6 +64,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import NumberInput from '@/components/desktop/NumberInput.vue';
|
||||||
import SnackBar from '@/components/desktop/SnackBar.vue';
|
import SnackBar from '@/components/desktop/SnackBar.vue';
|
||||||
|
|
||||||
import { ref, useTemplateRef } from 'vue';
|
import { ref, useTemplateRef } from 'vue';
|
||||||
@@ -83,6 +87,7 @@ interface UserCustomExchangeRateUpdateResponse {
|
|||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NumberInputType = InstanceType<typeof NumberInput>;
|
||||||
type SnackBarType = InstanceType<typeof SnackBar>;
|
type SnackBarType = InstanceType<typeof SnackBar>;
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@@ -101,6 +106,7 @@ const defaultCurrencyAmount = ref<number>(1);
|
|||||||
const currency = ref<string>(userStore.currentUserDefaultCurrency);
|
const currency = ref<string>(userStore.currentUserDefaultCurrency);
|
||||||
const targetCurrencyAmount = ref<number>(1);
|
const targetCurrencyAmount = ref<number>(1);
|
||||||
|
|
||||||
|
const targetAmountInput = useTemplateRef<NumberInputType>('targetAmountInput');
|
||||||
const snackbar = useTemplateRef<SnackBarType>('snackbar');
|
const snackbar = useTemplateRef<SnackBarType>('snackbar');
|
||||||
|
|
||||||
let resolveFunc: ((response: UserCustomExchangeRateUpdateResponse) => void) | null = null;
|
let resolveFunc: ((response: UserCustomExchangeRateUpdateResponse) => void) | null = null;
|
||||||
@@ -119,6 +125,10 @@ function open(): Promise<UserCustomExchangeRateUpdateResponse> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function confirm(): void {
|
function confirm(): void {
|
||||||
|
if (submitting.value || !defaultCurrencyAmount.value || !currency.value || !targetCurrencyAmount.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
submitting.value = true;
|
submitting.value = true;
|
||||||
|
|
||||||
exchangeRatesStore.updateUserCustomExchangeRate({
|
exchangeRatesStore.updateUserCustomExchangeRate({
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
:autofocus="true"
|
:autofocus="true"
|
||||||
:label="tt('Explorer Name')"
|
:label="tt('Explorer Name')"
|
||||||
:placeholder="tt('Explorer Name')"
|
:placeholder="tt('Explorer Name')"
|
||||||
v-model="newExplorerName"/>
|
v-model="newExplorerName"
|
||||||
|
@keyup.enter="save" />
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<div class="w-100 d-flex justify-center flex-wrap mt-sm-1 mt-md-2 gap-4">
|
<div class="w-100 d-flex justify-center flex-wrap mt-sm-1 mt-md-2 gap-4">
|
||||||
@@ -53,6 +54,10 @@ function open(currentExplorerName: string, title?: string): Promise<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function save(): void {
|
function save(): void {
|
||||||
|
if (!newExplorerName.value || oldExplorerName.value === newExplorerName.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
resolveFunc?.(newExplorerName.value);
|
resolveFunc?.(newExplorerName.value);
|
||||||
showState.value = false;
|
showState.value = false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user