Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions playwright/support/sections/SubmitSection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export class SubmitSection {
): Promise<void> {
const question = this.getQuestion(questionName)
await question.getByRole('textbox').fill(value)
// Wait for the debounced input handler to fire (INPUT_DEBOUNCE_MS = 400ms)
await this.page.waitForTimeout(500)
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/components/Questions/QuestionColor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
:modelValue="pickedColor"
advancedFields
:aria-required="isRequired"
:aria-errormessage="hasError ? errorId : undefined"
:aria-invalid="hasError ? 'true' : undefined"
@update:modelValue="onUpdatePickedColor">
<NcButton :disabled="!readOnly">
{{ colorPickerPlaceholder }}
Expand Down
2 changes: 2 additions & 0 deletions src/components/Questions/QuestionDate.vue
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@
:disabledDate="disabledDates"
:disabledTime="disabledTimes"
:aria-required="isRequired"
:aria-errormessage="hasError ? errorId : undefined"
:aria-invalid="hasError ? 'true' : undefined"
clearable
@update:modelValue="onValueChange" />
</div>
Expand Down
2 changes: 2 additions & 0 deletions src/components/Questions/QuestionDropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
:searchable="false"
label="text"
:aria-label-combobox="selectOptionPlaceholder"
:aria-errormessage="hasError ? errorId : undefined"
:aria-invalid="hasError ? 'true' : undefined"
@invalid.prevent="validate"
@update:modelValue="onInput" />
</div>
Expand All @@ -67,7 +69,7 @@
<AnswerInput
v-for="(answer, index) in choices"
:key="answer.local ? 'option-local' : answer.id"
ref="input"

Check warning on line 72 in src/components/Questions/QuestionDropdown.vue

View workflow job for this annotation

GitHub Actions / NPM lint

'input' is defined as ref, but never used
:answer="answer"
:formId="formId"
isDropdown
Expand Down
4 changes: 3 additions & 1 deletion src/components/Questions/QuestionFile.vue
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,9 @@
class="question__input-wrapper"
role="group"
:aria-labelledby="titleId"
:aria-describedby="description ? descriptionId : undefined">
:aria-describedby="description ? descriptionId : undefined"
:aria-errormessage="hasError ? errorId : undefined"
:aria-invalid="hasError ? 'true' : undefined">
<label>
{{ t('forms', 'Add new file as answer') }}
<input
Expand Down
2 changes: 2 additions & 0 deletions src/components/Questions/QuestionLinearScale.vue
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@
<NcCheckboxRadioSwitch
:id="`linear-scale-${id}-${option}`"
:aria-describedby="index === 0 ? labelId : undefined"
:aria-errormessage="hasError ? errorId : undefined"
:aria-invalid="hasError ? 'true' : undefined"
:disabled="!readOnly"
:modelValue="questionValues"
:value="option.toString()"
Expand Down
2 changes: 2 additions & 0 deletions src/components/Questions/QuestionLong.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
ref="textarea"
:aria-labelledby="titleId"
:aria-describedby="description ? descriptionId : undefined"
:aria-errormessage="hasError ? errorId : undefined"
:aria-invalid="hasError ? 'true' : undefined"
:placeholder="submissionInputPlaceholder"
:disabled="!readOnly"
:required="isRequired"
Expand Down
4 changes: 3 additions & 1 deletion src/components/Questions/QuestionRanking.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
class="question__content"
role="list"
:aria-labelledby="titleId"
:aria-describedby="description ? descriptionId : undefined">
:aria-describedby="description ? descriptionId : undefined"
:aria-errormessage="hasError ? errorId : undefined"
:aria-invalid="hasError ? 'true' : undefined">
<!-- Unranked pool -->
<div class="ranking-unranked">
<Draggable
Expand Down
56 changes: 29 additions & 27 deletions src/components/Questions/QuestionShort.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
ref="input"
:aria-labelledby="titleId"
:aria-describedby="description ? descriptionId : undefined"
:aria-errormessage="hasError ? errorId : undefined"
:aria-invalid="hasError ? 'true' : undefined"
:placeholder="submissionInputPlaceholder"
:disabled="!readOnly"
:name="name || undefined"
Expand Down Expand Up @@ -78,12 +80,14 @@

<script>
import IconRegex from '@material-symbols/svg-400/outlined/regular_expression.svg?raw'
import debounce from 'debounce'
import NcActionInput from '@nextcloud/vue/components/NcActionInput'
import NcActionRadio from '@nextcloud/vue/components/NcActionRadio'
import NcActions from '@nextcloud/vue/components/NcActions'
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
import Question from './Question.vue'
import QuestionMixin from '../../mixins/QuestionMixin.js'
import { INPUT_DEBOUNCE_MS } from '../../models/Constants.ts'
import validationTypes from '../../models/ValidationTypes.js'
import { splitRegex, validateExpression } from '../../utils/RegularExpression.js'

Expand Down Expand Up @@ -159,44 +163,42 @@ export default {

methods: {
async validate() {
if (
this.isRequired
&& (this.values.length === 0 || this.values[0] === '')
) {
/** @type {HTMLInputElement} */
const input = this.$refs.input
const value = input.value

// Clear the previous custom error before checking native validity.
input.setCustomValidity('')

if (this.isRequired && input.validity.valueMissing) {
this.errorMessage = t('forms', 'You must answer this question')
return false
}

const isCustomValid =
!value
|| this.validationObject.validate(
value,
splitRegex(this.validationRegex),
)

if (!input.validity.valid || !isCustomValid) {
input.setCustomValidity(this.validationObject.errorMessage)
this.errorMessage = this.validationObject.errorMessage
return false
}

this.errorMessage = null
return true
},

onInput() {
/** @type {HTMLObjectElement} */
onInput: debounce(async function () {
/** @type {HTMLInputElement} */
const input = this.$refs.input
/** @type {string} */
const value = input.value

input.setCustomValidity('')

// Only check non empty values, this question might not be required, if not already invalid
if (value) {
// Then check native browser validation (might be better then our)
// If the browsers validation succeeds either the browser does not implement a validation
// or it is valid, so we double check by running our custom validation.
if (
!input.checkValidity()
|| !this.validationObject.validate(
value,
splitRegex(this.validationRegex),
)
) {
input.setCustomValidity(this.validationObject.errorMessage)
}
}

this.$emit('update:values', [value])
},
this.validate()
}, INPUT_DEBOUNCE_MS),

/**
* Change input type
Expand Down
Loading