Skip to content

Commit 4359820

Browse files
committed
fix: improve number input validation and update handling
Signed-off-by: Enjeck C. <patrathewhiz@gmail.com>
1 parent 146c024 commit 4359820

1 file changed

Lines changed: 85 additions & 20 deletions

File tree

src/shared/components/ncTable/partials/rowTypePartials/NumberForm.vue

Lines changed: 85 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,22 @@
77
<div v-if="column.numberPrefix" class="prefix">
88
{{ column.numberPrefix }}
99
</div>
10-
<input v-model="localValue"
11-
type="number"
12-
:min="column.numberMin"
13-
:max="column.numberMax"
14-
:readonly="column.viewColumnInformation?.readonly"
15-
:step="getStep">
10+
<div class="number-input">
11+
<input v-model="localValue"
12+
type="number"
13+
class="number-input__field"
14+
:class="{ 'number-input__field--error': hasRangeError }"
15+
:min="column.numberMin"
16+
:max="column.numberMax"
17+
:readonly="column.viewColumnInformation?.readonly"
18+
:step="getStep"
19+
:aria-invalid="hasRangeError"
20+
@blur="formatValue"
21+
@keyup.enter="formatValue">
22+
<p v-if="hasRangeError" class="number-input__hint" role="alert">
23+
{{ rangeHintText }}
24+
</p>
25+
</div>
1626
<div v-if="column.numberSuffix" class="suffix">
1727
{{ column.numberSuffix }}
1828
</div>
@@ -21,6 +31,7 @@
2131

2232
<script>
2333
import RowFormWrapper from './RowFormWrapper.vue'
34+
import { translate as t } from '@nextcloud/l10n'
2435
import rowHelper from '../../../../components/ncTable/mixins/rowHelper.js'
2536
export default {
2637
@@ -49,6 +60,37 @@ export default {
4960
return 'any'
5061
}
5162
},
63+
hasMin() {
64+
return this.column?.numberMin !== null && this.column?.numberMin !== undefined
65+
},
66+
hasMax() {
67+
return this.column?.numberMax !== null && this.column?.numberMax !== undefined
68+
},
69+
hasRangeError() {
70+
const value = this.parseValue(this.localValue)
71+
if (value === null) {
72+
return false
73+
}
74+
if (this.hasMin && value < this.column.numberMin) {
75+
return true
76+
}
77+
if (this.hasMax && value > this.column.numberMax) {
78+
return true
79+
}
80+
return false
81+
},
82+
rangeHintText() {
83+
const min = this.column?.numberMin
84+
const max = this.column?.numberMax
85+
if (this.hasMin && this.hasMax) {
86+
return t('tables', 'Enter a value between {min} and {max}.', { min, max })
87+
} else if (this.hasMin) {
88+
return t('tables', 'Enter a value of {min} or more.', { min })
89+
} else if (this.hasMax) {
90+
return t('tables', 'Enter a value of {max} or less.', { max })
91+
}
92+
return ''
93+
},
5294
localValue: {
5395
get() {
5496
if (this.value !== null) {
@@ -62,16 +104,13 @@ export default {
62104
}
63105
}
64106
},
65-
set(v) { this.$emit('update:value', this.parseValue(v)) },
107+
set(v) {
108+
this.$emit('update:value', v === '' ? null : v)
109+
},
66110
},
67111
},
68112
69113
watch: {
70-
localValue() {
71-
const value = this.parseValue(this.localValue)
72-
this.localValue = value
73-
this.$emit('update:value', value)
74-
},
75114
value() {
76115
this.localValue = this.value
77116
},
@@ -82,6 +121,15 @@ export default {
82121
},
83122
84123
methods: {
124+
t,
125+
// Normalise the input (decimal separator + rounding) without clamping.
126+
// Out-of-range values are surfaced via hasRangeError instead of being
127+
// silently replaced by the min/max value.
128+
formatValue() {
129+
const parsedValue = this.parseValue(this.localValue)
130+
this.localValue = parsedValue
131+
this.$emit('update:value', parsedValue)
132+
},
85133
parseValue(inputValue) {
86134
if (inputValue === null || inputValue === '') {
87135
return null
@@ -92,15 +140,11 @@ export default {
92140
} else {
93141
parsedValue = inputValue
94142
}
95-
const roundedValue = parsedValue.toFixed(this.column?.numberDecimals)
96-
let value = parseFloat(roundedValue)
97-
if ((this.column?.numberMin !== null && this.column?.numberMin !== undefined) && value < this.column?.numberMin) {
98-
value = this.column.numberMin
99-
}
100-
if ((this.column?.numberMax !== null && this.column?.numberMax !== undefined) && value > this.column?.numberMax) {
101-
value = this.column.numberMax
143+
if (isNaN(parsedValue)) {
144+
return null
102145
}
103-
return value
146+
const roundedValue = parsedValue.toFixed(this.column?.numberDecimals)
147+
return parseFloat(roundedValue)
104148
},
105149
},
106150
}
@@ -115,4 +159,25 @@ export default {
115159
padding-inline-start: calc(var(--default-grid-baseline) * 2);
116160
}
117161
162+
.number-input {
163+
display: flex;
164+
flex-direction: column;
165+
flex: 1;
166+
min-width: 0;
167+
168+
&__field {
169+
width: 100%;
170+
171+
&--error {
172+
border-color: var(--color-error) !important;
173+
}
174+
}
175+
176+
&__hint {
177+
margin-block-start: calc(var(--default-grid-baseline) * 1);
178+
color: var(--color-error);
179+
font-size: 0.9em;
180+
}
181+
}
182+
118183
</style>

0 commit comments

Comments
 (0)