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 >
2131
2232<script >
2333import RowFormWrapper from ' ./RowFormWrapper.vue'
34+ import { translate as t } from ' @nextcloud/l10n'
2435import rowHelper from ' ../../../../components/ncTable/mixins/rowHelper.js'
2536export 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