Skip to content

Commit 91a784f

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

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
@@ -52,6 +63,37 @@ export default {
5263
return 'any'
5364
}
5465
},
66+
hasMin() {
67+
return this.column?.numberMin !== null && this.column?.numberMin !== undefined
68+
},
69+
hasMax() {
70+
return this.column?.numberMax !== null && this.column?.numberMax !== undefined
71+
},
72+
hasRangeError() {
73+
const value = this.parseValue(this.localValue)
74+
if (value === null) {
75+
return false
76+
}
77+
if (this.hasMin && value < this.column.numberMin) {
78+
return true
79+
}
80+
if (this.hasMax && value > this.column.numberMax) {
81+
return true
82+
}
83+
return false
84+
},
85+
rangeHintText() {
86+
const min = this.column?.numberMin
87+
const max = this.column?.numberMax
88+
if (this.hasMin && this.hasMax) {
89+
return t('tables', 'Enter a value between {min} and {max}.', { min, max })
90+
} else if (this.hasMin) {
91+
return t('tables', 'Enter a value of {min} or more.', { min })
92+
} else if (this.hasMax) {
93+
return t('tables', 'Enter a value of {max} or less.', { max })
94+
}
95+
return ''
96+
},
5597
localValue: {
5698
get() {
5799
if (this.value !== null) {
@@ -65,16 +107,13 @@ export default {
65107
}
66108
}
67109
},
68-
set(v) { this.$emit('update:value', this.parseValue(v)) },
110+
set(v) {
111+
this.$emit('update:value', v === '' ? null : v)
112+
},
69113
},
70114
},
71115
72116
watch: {
73-
localValue() {
74-
const value = this.parseValue(this.localValue)
75-
this.localValue = value
76-
this.$emit('update:value', value)
77-
},
78117
value() {
79118
this.localValue = this.value
80119
},
@@ -85,6 +124,15 @@ export default {
85124
},
86125
87126
methods: {
127+
t,
128+
// Normalise the input (decimal separator + rounding) without clamping.
129+
// Out-of-range values are surfaced via hasRangeError instead of being
130+
// silently replaced by the min/max value.
131+
formatValue() {
132+
const parsedValue = this.parseValue(this.localValue)
133+
this.localValue = parsedValue
134+
this.$emit('update:value', parsedValue)
135+
},
88136
parseValue(inputValue) {
89137
if (inputValue === null || inputValue === '') {
90138
return null
@@ -95,15 +143,11 @@ export default {
95143
} else {
96144
parsedValue = inputValue
97145
}
98-
const roundedValue = parsedValue.toFixed(this.column?.numberDecimals)
99-
let value = parseFloat(roundedValue)
100-
if ((this.column?.numberMin !== null && this.column?.numberMin !== undefined) && value < this.column?.numberMin) {
101-
value = this.column.numberMin
102-
}
103-
if ((this.column?.numberMax !== null && this.column?.numberMax !== undefined) && value > this.column?.numberMax) {
104-
value = this.column.numberMax
146+
if (isNaN(parsedValue)) {
147+
return null
105148
}
106-
return value
149+
const roundedValue = parsedValue.toFixed(this.column?.numberDecimals)
150+
return parseFloat(roundedValue)
107151
},
108152
},
109153
}
@@ -118,4 +162,25 @@ export default {
118162
padding-inline-start: calc(var(--default-grid-baseline) * 2);
119163
}
120164
165+
.number-input {
166+
display: flex;
167+
flex-direction: column;
168+
flex: 1;
169+
min-width: 0;
170+
171+
&__field {
172+
width: 100%;
173+
174+
&--error {
175+
border-color: var(--color-error) !important;
176+
}
177+
}
178+
179+
&__hint {
180+
margin-block-start: calc(var(--default-grid-baseline) * 1);
181+
color: var(--color-error);
182+
font-size: 0.9em;
183+
}
184+
}
185+
121186
</style>

0 commit comments

Comments
 (0)