automatically focus after opening the dialog and support confirming with the enter key

This commit is contained in:
MaysWind
2026-01-11 13:24:26 +08:00
parent ee9b281919
commit ca959fb9ce
7 changed files with 66 additions and 22 deletions
+12 -1
View File
@@ -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();
+8 -6
View File
@@ -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());
+9 -7
View File
@@ -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');
+3 -1
View File
@@ -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>
+14 -2
View File
@@ -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;
} }