|
15 | 15 | ref="input" |
16 | 16 | :aria-labelledby="titleId" |
17 | 17 | :aria-describedby="description ? descriptionId : undefined" |
| 18 | + :aria-errormessage="hasError ? errorId : undefined" |
| 19 | + :aria-invalid="hasError ? 'true' : undefined" |
18 | 20 | :placeholder="submissionInputPlaceholder" |
19 | 21 | :disabled="!readOnly" |
20 | 22 | :name="name || undefined" |
|
78 | 80 |
|
79 | 81 | <script> |
80 | 82 | import IconRegex from '@material-symbols/svg-400/outlined/regular_expression.svg?raw' |
| 83 | +import debounce from 'debounce' |
81 | 84 | import NcActionInput from '@nextcloud/vue/components/NcActionInput' |
82 | 85 | import NcActionRadio from '@nextcloud/vue/components/NcActionRadio' |
83 | 86 | import NcActions from '@nextcloud/vue/components/NcActions' |
84 | 87 | import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper' |
85 | 88 | import Question from './Question.vue' |
86 | 89 | import QuestionMixin from '../../mixins/QuestionMixin.js' |
| 90 | +import { INPUT_DEBOUNCE_MS } from '../../models/Constants.ts' |
87 | 91 | import validationTypes from '../../models/ValidationTypes.js' |
88 | 92 | import { splitRegex, validateExpression } from '../../utils/RegularExpression.js' |
89 | 93 |
|
@@ -159,43 +163,45 @@ export default { |
159 | 163 |
|
160 | 164 | methods: { |
161 | 165 | async validate() { |
162 | | - if ( |
163 | | - this.isRequired |
164 | | - && (this.values.length === 0 || this.values[0] === '') |
165 | | - ) { |
| 166 | + /** @type {HTMLInputElement} */ |
| 167 | + const input = this.$refs.input |
| 168 | + const value = input.value |
| 169 | +
|
| 170 | + // Clear the previous custom error before checking native validity. |
| 171 | + input.setCustomValidity('') |
| 172 | +
|
| 173 | + if (this.isRequired && input.validity.valueMissing) { |
166 | 174 | this.errorMessage = t('forms', 'You must answer this question') |
167 | 175 | return false |
168 | 176 | } |
169 | 177 |
|
| 178 | + const isCustomValid = |
| 179 | + !value |
| 180 | + || this.validationObject.validate( |
| 181 | + value, |
| 182 | + splitRegex(this.validationRegex), |
| 183 | + ) |
| 184 | +
|
| 185 | + if (!input.validity.valid || !isCustomValid) { |
| 186 | + input.setCustomValidity(this.validationObject.errorMessage) |
| 187 | + this.errorMessage = this.validationObject.errorMessage |
| 188 | + return false |
| 189 | + } |
| 190 | +
|
170 | 191 | this.errorMessage = null |
171 | 192 | return true |
172 | 193 | }, |
173 | 194 |
|
| 195 | + debounceValidate: debounce(async function () { |
| 196 | + this.validate() |
| 197 | + }, INPUT_DEBOUNCE_MS), |
| 198 | +
|
174 | 199 | onInput() { |
175 | | - /** @type {HTMLObjectElement} */ |
| 200 | + /** @type {HTMLInputElement} */ |
176 | 201 | const input = this.$refs.input |
177 | | - /** @type {string} */ |
178 | 202 | const value = input.value |
179 | | -
|
180 | | - input.setCustomValidity('') |
181 | | -
|
182 | | - // Only check non empty values, this question might not be required, if not already invalid |
183 | | - if (value) { |
184 | | - // Then check native browser validation (might be better then our) |
185 | | - // If the browsers validation succeeds either the browser does not implement a validation |
186 | | - // or it is valid, so we double check by running our custom validation. |
187 | | - if ( |
188 | | - !input.checkValidity() |
189 | | - || !this.validationObject.validate( |
190 | | - value, |
191 | | - splitRegex(this.validationRegex), |
192 | | - ) |
193 | | - ) { |
194 | | - input.setCustomValidity(this.validationObject.errorMessage) |
195 | | - } |
196 | | - } |
197 | | -
|
198 | 203 | this.$emit('update:values', [value]) |
| 204 | + this.debounceValidate() |
199 | 205 | }, |
200 | 206 |
|
201 | 207 | /** |
|
0 commit comments