Skip to content

Commit 6222b6e

Browse files
committed
use custom number input box to replace the system input box
1 parent baa6850 commit 6222b6e

8 files changed

Lines changed: 230 additions & 18 deletions

File tree

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { watch } from 'vue';
2+
3+
import { useI18n } from '@/locales/helpers.ts';
4+
import { type CommonNumberInputProps, useCommonNumberInputBase } from '@/components/base/CommonNumberInputBase.ts';
5+
6+
import { isNumber, replaceAll, removeAll } from '@/lib/common.ts';
7+
8+
export interface NumberInputProps extends CommonNumberInputProps {
9+
minValue?: number;
10+
maxValue?: number;
11+
maxDecimalCount?: number;
12+
}
13+
14+
export interface NumberInputEmits {
15+
(e: 'update:modelValue', value: number): void;
16+
}
17+
18+
export function useNumberInputBase(props: NumberInputProps, emit: NumberInputEmits) {
19+
const {
20+
getCurrentDecimalSeparator,
21+
getCurrentDigitGroupingSymbol
22+
} = useI18n();
23+
24+
const {
25+
currentValue,
26+
onKeyUpDown,
27+
onPaste
28+
} = useCommonNumberInputBase(props, props.maxDecimalCount ?? -1, getFormattedValue(props.modelValue), parseNumber, getFormattedValue, getValidFormattedValue);
29+
30+
function parseNumber(value: string): number {
31+
if (!value) {
32+
return 0;
33+
}
34+
35+
const decimalSeparator = getCurrentDecimalSeparator();
36+
37+
let finalValue = '';
38+
for (let i = 0; i < value.length; i++) {
39+
if (!('0' <= value[i] && value[i] <= '9') && value[i] !== '-' && value[i] !== decimalSeparator) {
40+
break;
41+
}
42+
43+
finalValue += value[i];
44+
}
45+
46+
if (decimalSeparator !== '.') {
47+
finalValue = replaceAll(finalValue, decimalSeparator, '.');
48+
}
49+
50+
return parseFloat(finalValue);
51+
}
52+
53+
function getValidFormattedValue(value: number, textualValue: string): string {
54+
if (isNumber(props.minValue) && value < props.minValue) {
55+
return getFormattedValue(props.minValue);
56+
}
57+
58+
if (isNumber(props.maxValue) && value > props.maxValue) {
59+
return getFormattedValue(props.maxValue);
60+
}
61+
62+
const decimalSeparator = getCurrentDecimalSeparator();
63+
const digitGroupingSymbol = getCurrentDigitGroupingSymbol();
64+
return replaceAll(removeAll(textualValue, digitGroupingSymbol), '.', decimalSeparator);
65+
}
66+
67+
function getFormattedValue(value: number): string {
68+
if (!Number.isNaN(value) && Number.isFinite(value)) {
69+
const decimalSeparator = getCurrentDecimalSeparator();
70+
71+
if (isNumber(props.maxDecimalCount) && props.maxDecimalCount >= 0) {
72+
return replaceAll(value.toFixed(props.maxDecimalCount), '.', decimalSeparator);
73+
} else {
74+
return replaceAll(value.toString(), '.', decimalSeparator);
75+
}
76+
}
77+
78+
return '0';
79+
}
80+
81+
watch(() => props.modelValue, (newValue) => {
82+
const numericCurrentValue = parseNumber(currentValue.value);
83+
84+
if (newValue !== numericCurrentValue) {
85+
const newStringValue = getFormattedValue(newValue);
86+
87+
if (!(newStringValue === '0' && currentValue.value === '')) {
88+
currentValue.value = newStringValue;
89+
}
90+
}
91+
});
92+
93+
watch(currentValue, (newValue) => {
94+
let finalValue = '';
95+
96+
if (newValue) {
97+
const decimalSeparator = getCurrentDecimalSeparator();
98+
99+
for (let i = 0; i < newValue.length; i++) {
100+
if (!('0' <= newValue[i] && newValue[i] <= '9') && newValue[i] !== '-' && newValue[i] !== decimalSeparator) {
101+
break;
102+
}
103+
104+
finalValue += newValue[i];
105+
}
106+
}
107+
108+
if (finalValue !== newValue) {
109+
currentValue.value = finalValue;
110+
} else {
111+
const value: number = parseNumber(finalValue);
112+
emit('update:modelValue', value);
113+
}
114+
});
115+
116+
return {
117+
// states
118+
currentValue,
119+
// functions
120+
onKeyUpDown,
121+
onPaste
122+
}
123+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<template>
2+
<v-text-field type="text" :class="extraClass" :density="density" :readonly="!!readonly" :disabled="!!disabled"
3+
:label="label" :placeholder="placeholder"
4+
:persistent-placeholder="!!persistentPlaceholder"
5+
v-model="currentValue"
6+
@keydown="onKeyUpDown" @keyup="onKeyUpDown" @paste="onPaste">
7+
</v-text-field>
8+
</template>
9+
10+
<script setup lang="ts">
11+
import { computed } from 'vue';
12+
13+
import { type NumberInputProps, type NumberInputEmits, useNumberInputBase } from '@/components/base/NumberInputBase.ts';
14+
15+
import type { ComponentDensity } from '@/lib/ui/desktop.ts';
16+
17+
interface DesktopNumberInputProps extends NumberInputProps {
18+
class?: string;
19+
density?: ComponentDensity;
20+
persistentPlaceholder?: boolean;
21+
}
22+
23+
const props = defineProps<DesktopNumberInputProps>();
24+
const emit = defineEmits<NumberInputEmits>();
25+
26+
const {
27+
currentValue,
28+
onKeyUpDown,
29+
onPaste
30+
} = useNumberInputBase(props, emit);
31+
32+
const extraClass = computed<string>(() => {
33+
return props.class || '';
34+
});
35+
</script>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<template>
2+
<f7-list-input
3+
type="text"
4+
:readonly="!!readonly"
5+
:disabled="!!disabled"
6+
:label="label"
7+
:placeholder="placeholder"
8+
v-model:value="currentValue"
9+
@keydown="onKeyUpDown"
10+
@keyup="onKeyUpDown"
11+
@paste="onPaste"
12+
></f7-list-input>
13+
</template>
14+
15+
<script setup lang="ts">
16+
import { type NumberInputProps, type NumberInputEmits, useNumberInputBase } from '@/components/base/NumberInputBase.ts';
17+
18+
const props = defineProps<NumberInputProps>();
19+
const emit = defineEmits<NumberInputEmits>();
20+
21+
const {
22+
currentValue,
23+
onKeyUpDown,
24+
onPaste
25+
} = useNumberInputBase(props, emit);
26+
</script>

src/consts/exchange_rate.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const USER_CUSTOM_EXCHANGE_RATE_MIN_VALUE: number = 0;
2+
export const USER_CUSTOM_EXCHANGE_RATE_MAX_VALUE: number = 999999999.9999;

src/desktop-main.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ import MapView from '@/components/common/MapView.vue';
7777

7878
import ItemIcon from '@/components/desktop/ItemIcon.vue';
7979
import BtnVerticalGroup from '@/components/desktop/BtnVerticalGroup.vue';
80+
import NumberInput from '@/components/desktop/NumberInput.vue';
8081
import AmountInput from '@/components/desktop/AmountInput.vue';
8182
import LanguageSelect from '@/components/desktop/LanguageSelect.vue';
8283
import LanguageSelectButton from '@/components/desktop/LanguageSelectButton.vue';
@@ -453,6 +454,7 @@ app.component('MapView', MapView);
453454

454455
app.component('ItemIcon', ItemIcon);
455456
app.component('BtnVerticalGroup', BtnVerticalGroup);
457+
app.component('NumberInput', NumberInput);
456458
app.component('AmountInput', AmountInput);
457459
app.component('LanguageSelect', LanguageSelect);
458460
app.component('LanguageSelectButton', LanguageSelectButton);

src/mobile-main.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ import DateSelectionSheet from '@/components/mobile/DateSelectionSheet.vue';
9595
import DateRangeSelectionSheet from '@/components/mobile/DateRangeSelectionSheet.vue';
9696
import MonthSelectionSheet from '@/components/mobile/MonthSelectionSheet.vue';
9797
import MonthRangeSelectionSheet from '@/components/mobile/MonthRangeSelectionSheet.vue';
98+
import ListNumberInput from '@/components/mobile/ListNumberInput.vue';
9899
import ListItemSelectionSheet from '@/components/mobile/ListItemSelectionSheet.vue';
99100
import ListItemSelectionPopup from '@/components/mobile/ListItemSelectionPopup.vue';
100101
import TwoColumnListItemSelectionSheet from '@/components/mobile/TwoColumnListItemSelectionSheet.vue';
@@ -181,6 +182,7 @@ app.component('DateSelectionSheet', DateSelectionSheet);
181182
app.component('DateRangeSelectionSheet', DateRangeSelectionSheet);
182183
app.component('MonthSelectionSheet', MonthSelectionSheet);
183184
app.component('MonthRangeSelectionSheet', MonthRangeSelectionSheet);
185+
app.component('ListNumberInput', ListNumberInput);
184186
app.component('ListItemSelectionSheet', ListItemSelectionSheet);
185187
app.component('ListItemSelectionPopup', ListItemSelectionPopup);
186188
app.component('TwoColumnListItemSelectionSheet', TwoColumnListItemSelectionSheet);

src/views/desktop/exchangerates/list/dialogs/UpdateDialog.vue

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111
<v-card-text class="my-md-4 w-100 d-flex justify-center">
1212
<v-row>
1313
<v-col cols="12" md="6">
14-
<v-text-field type="number"
15-
:disabled="submitting"
14+
<number-input :disabled="submitting"
1615
:label="tt('Amount')"
1716
:placeholder="tt('Amount')"
1817
:persistent-placeholder="true"
18+
:min-value="USER_CUSTOM_EXCHANGE_RATE_MIN_VALUE"
19+
:max-value="USER_CUSTOM_EXCHANGE_RATE_MAX_VALUE"
20+
:max-decimal-count="4"
1921
v-model="defaultCurrencyAmount"/>
2022
</v-col>
2123
<v-col cols="12" md="6">
@@ -28,11 +30,13 @@
2830
<v-icon :icon="mdiSwapVertical" size="24" />
2931
</v-col>
3032
<v-col cols="12" md="6">
31-
<v-text-field type="number"
32-
:disabled="submitting"
33+
<number-input :disabled="submitting"
3334
:label="tt('Amount')"
3435
:placeholder="tt('Amount')"
3536
:persistent-placeholder="true"
37+
:min-value="USER_CUSTOM_EXCHANGE_RATE_MIN_VALUE"
38+
:max-value="USER_CUSTOM_EXCHANGE_RATE_MAX_VALUE"
39+
:max-decimal-count="4"
3640
v-model="targetCurrencyAmount"/>
3741
</v-col>
3842
<v-col cols="12" md="6">
@@ -68,6 +72,11 @@ import { useI18n } from '@/locales/helpers.ts';
6872
import { useUserStore } from '@/stores/user.ts';
6973
import { useExchangeRatesStore } from '@/stores/exchangeRates.ts';
7074
75+
import {
76+
USER_CUSTOM_EXCHANGE_RATE_MAX_VALUE,
77+
USER_CUSTOM_EXCHANGE_RATE_MIN_VALUE
78+
} from '@/consts/exchange_rate.ts';
79+
7180
import {
7281
mdiSwapVertical
7382
} from '@mdi/js';

src/views/mobile/exchangerates/UpdatePage.vue

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@
1010
</f7-navbar>
1111

1212
<f7-list form strong inset dividers class="margin-vertical">
13-
<f7-list-input
14-
type="number"
15-
:disabled="submitting"
16-
:label="tt('Amount')"
17-
:placeholder="tt('Amount')"
18-
v-model:value="defaultCurrencyAmount"
19-
></f7-list-input>
13+
<template #list>
14+
<list-number-input
15+
:disabled="submitting"
16+
:label="tt('Amount')"
17+
:placeholder="tt('Amount')"
18+
:min-value="USER_CUSTOM_EXCHANGE_RATE_MIN_VALUE"
19+
:max-value="USER_CUSTOM_EXCHANGE_RATE_MAX_VALUE"
20+
:max-decimal-count="4"
21+
v-model="defaultCurrencyAmount"
22+
></list-number-input>
23+
</template>
2024

2125
<f7-list-item
2226
class="list-item-with-header-and-title list-item-no-item-after"
@@ -39,13 +43,17 @@
3943
</f7-block>
4044

4145
<f7-list form strong inset dividers class="margin-vertical">
42-
<f7-list-input
43-
type="number"
44-
:disabled="submitting"
45-
:label="tt('Amount')"
46-
:placeholder="tt('Amount')"
47-
v-model:value="targetCurrencyAmount"
48-
></f7-list-input>
46+
<template #list>
47+
<list-number-input
48+
:disabled="submitting"
49+
:label="tt('Amount')"
50+
:placeholder="tt('Amount')"
51+
:min-value="USER_CUSTOM_EXCHANGE_RATE_MIN_VALUE"
52+
:max-value="USER_CUSTOM_EXCHANGE_RATE_MAX_VALUE"
53+
:max-decimal-count="4"
54+
v-model="targetCurrencyAmount"
55+
></list-number-input>
56+
</template>
4957

5058
<f7-list-item
5159
class="list-item-with-header-and-title list-item-no-item-after"
@@ -88,6 +96,11 @@ import { useExchangeRatesStore } from '@/stores/exchangeRates.ts';
8896
8997
import type { LocalizedCurrencyInfo } from '@/core/currency.ts';
9098
99+
import {
100+
USER_CUSTOM_EXCHANGE_RATE_MAX_VALUE,
101+
USER_CUSTOM_EXCHANGE_RATE_MIN_VALUE
102+
} from '@/consts/exchange_rate.ts';
103+
91104
const props = defineProps<{
92105
f7router: Router.Router;
93106
}>();

0 commit comments

Comments
 (0)