Compare commits
2 commits
master
...
fix/tab_na
Author | SHA1 | Date | |
---|---|---|---|
d7c469956e | |||
7057722772 |
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -11689,6 +11689,11 @@
|
|||
"integrity": "sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=",
|
||||
"dev": true
|
||||
},
|
||||
"tabbable": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-4.0.0.tgz",
|
||||
"integrity": "sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ=="
|
||||
},
|
||||
"table": {
|
||||
"version": "5.4.6",
|
||||
"resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
|
||||
|
|
|
@ -42,7 +42,8 @@
|
|||
"v-clipboard": "^2.2.3",
|
||||
"vue": "^2.6.11",
|
||||
"vue-router": "^3.3.4",
|
||||
"vuedraggable": "^2.23.2"
|
||||
"vuedraggable": "^2.23.2",
|
||||
"tabbable": "^4.0.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"extends @nextcloud/browserslist-config"
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
@keydown.enter.prevent="addNewEntry">
|
||||
|
||||
<!-- Delete answer -->
|
||||
<Actions>
|
||||
<Actions @keydown.tab.exact.prevent="onActionsTab">
|
||||
<ActionButton icon="icon-close" @click="deleteEntry">
|
||||
{{ t('forms', 'Delete answer') }}
|
||||
</ActionButton>
|
||||
|
@ -79,6 +79,11 @@ export default {
|
|||
this.$refs.input.focus()
|
||||
},
|
||||
|
||||
onActionsTab(event) {
|
||||
console.debug('keydownTab')
|
||||
// this.$emit('focus-next', event.target)
|
||||
},
|
||||
|
||||
/**
|
||||
* Option changed, processing the data
|
||||
*/
|
||||
|
|
|
@ -25,9 +25,7 @@
|
|||
:class="{ 'question--edit': edit }"
|
||||
:aria-label="t('forms', 'Question number {index}', {index})"
|
||||
class="question"
|
||||
@click="enableEdit"
|
||||
@focusin="onFocusIn"
|
||||
@focusout="onFocusOut">
|
||||
@click="enableEdit">
|
||||
<!-- Drag handle -->
|
||||
<!-- TODO: implement arrow key mapping to reorder question -->
|
||||
<div v-if="!readOnly"
|
||||
|
@ -47,7 +45,8 @@
|
|||
minlength="1"
|
||||
:maxlength="maxQuestionLength"
|
||||
required
|
||||
@input="onTitleChange">
|
||||
@input="onTitleChange"
|
||||
@keydown.shift.tab.prevent="onTitleShiftTab">
|
||||
<h3 v-else
|
||||
class="question__header-title"
|
||||
:tabindex="computedTitleTabIndex"
|
||||
|
@ -57,7 +56,10 @@
|
|||
v-tooltip.auto="warningInvalid"
|
||||
class="question__header-warning icon-error-color"
|
||||
tabindex="0" />
|
||||
<Actions v-if="!readOnly" class="question__header-menu" :force-menu="true">
|
||||
<Actions v-if="!readOnly"
|
||||
class="question__header-menu"
|
||||
:force-menu="true"
|
||||
@keydown.tab.exact.prevent="onActionsTab">
|
||||
<ActionCheckbox :checked="mandatory"
|
||||
@update:checked="onMandatoryChange">
|
||||
{{ t('forms', 'Required') }}
|
||||
|
@ -168,6 +170,16 @@ export default {
|
|||
},
|
||||
|
||||
methods: {
|
||||
onTitleShiftTab(event) {
|
||||
console.debug('this', event.target)
|
||||
this.$emit('focus-prev', event.target)
|
||||
this.disableEdit()
|
||||
},
|
||||
onActionsTab(event) {
|
||||
console.debug('actionsTab', event)
|
||||
this.$emit('focus-next', event.target)
|
||||
},
|
||||
|
||||
onTitleChange({ target }) {
|
||||
this.$emit('update:text', target.value)
|
||||
},
|
||||
|
@ -188,26 +200,11 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Enable & disable resp. edit, if focus jumps to next question (e.g. by tab-navigation)
|
||||
* @param {Object} event The triggered focusIn/focusOut event
|
||||
*/
|
||||
onFocusIn(event) {
|
||||
if (event.target.closest('.question') !== event.relatedTarget?.closest('.question')) {
|
||||
this.enableEdit()
|
||||
}
|
||||
},
|
||||
|
||||
onFocusOut(event) {
|
||||
if (event.target.closest('.question') !== event.relatedTarget?.closest('.question')) {
|
||||
this.disableEdit()
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Enable the edit mode
|
||||
*/
|
||||
enableEdit() {
|
||||
console.debug('enableEdit')
|
||||
if (!this.readOnly) {
|
||||
this.$emit('update:edit', true)
|
||||
}
|
||||
|
@ -217,6 +214,7 @@ export default {
|
|||
* Disable the edit mode
|
||||
*/
|
||||
disableEdit() {
|
||||
console.debug('disableEdit')
|
||||
if (!this.readOnly) {
|
||||
this.$emit('update:edit', false)
|
||||
}
|
||||
|
|
|
@ -32,7 +32,9 @@
|
|||
:warning-invalid="answerType.warningInvalid"
|
||||
@update:text="onTitleChange"
|
||||
@update:mandatory="onMandatoryChange"
|
||||
@delete="onDelete">
|
||||
@delete="onDelete"
|
||||
@focus-prev="onFocusPrev"
|
||||
@focus-next="FocusNextOutside">
|
||||
<div class="question__content">
|
||||
<textarea ref="textarea"
|
||||
:aria-label="t('forms', 'A long answer for the question “{text}”', { text })"
|
||||
|
|
|
@ -34,13 +34,14 @@
|
|||
:shift-drag-handle="shiftDragHandle"
|
||||
@update:text="onTitleChange"
|
||||
@update:mandatory="onMandatoryChange"
|
||||
@delete="onDelete">
|
||||
@delete="onDelete"
|
||||
@focus-prev="onFocusPrev"
|
||||
@focus-next="onFocusNext">
|
||||
<ul class="question__content">
|
||||
<template v-for="(answer, index) in options">
|
||||
<li v-if="!edit"
|
||||
:key="answer.id"
|
||||
class="question__item"
|
||||
:class="{'question__item--last': (index === options.length-1),}">
|
||||
class="question__item">
|
||||
<!-- Answer radio/checkbox + label -->
|
||||
<!-- TODO: migrate to radio/checkbox component once available -->
|
||||
<input :id="`${id}-answer-${answer.id}`"
|
||||
|
@ -55,7 +56,8 @@
|
|||
:required="isRequired(answer.id)"
|
||||
:type="isUnique ? 'radio' : 'checkbox'"
|
||||
@change="onChange($event, answer.id)"
|
||||
@keydown.enter.exact.prevent="onKeydownEnter">
|
||||
@keydown.enter.exact.prevent="onKeydownEnter"
|
||||
@focus="onFocusRadioInput">
|
||||
<label v-if="!edit"
|
||||
ref="label"
|
||||
:for="`${id}-answer-${answer.id}`"
|
||||
|
@ -72,7 +74,8 @@
|
|||
:max-option-length="maxStringLengths.optionText"
|
||||
@add="addNewEntry"
|
||||
@delete="deleteOption"
|
||||
@update:answer="updateAnswer" />
|
||||
@update:answer="updateAnswer"
|
||||
@focus-next="onFocusNext" />
|
||||
</template>
|
||||
|
||||
<li v-if="(edit && !isLastEmpty) || hasNoAnswer" class="question__item">
|
||||
|
@ -153,15 +156,6 @@ export default {
|
|||
|
||||
// update parent
|
||||
this.updateOptions(options)
|
||||
} else {
|
||||
// If edit becomes true by tabbing to last element, focus newAnswer-input
|
||||
if (document.activeElement?.parentNode?.classList.contains('question__item--last')) {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.inputNewAnswer) {
|
||||
this.$refs.inputNewAnswer.focus()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -188,6 +182,20 @@ export default {
|
|||
this.$emit('update:values', [...new Set(values)])
|
||||
},
|
||||
|
||||
/**
|
||||
* If the focus gets onto one of the radio/checkbox inputs and readOnly is false, activate Edit and focus inputNewAnswer
|
||||
* Necessary for Tab-Navigation on editing forms.
|
||||
*/
|
||||
onFocusRadioInput() {
|
||||
console.debug('Inputfocus!')
|
||||
if (!this.readOnly) {
|
||||
this.enableEdit()
|
||||
this.$nextTick(() => {
|
||||
this.$refs.inputNewAnswer.focus()
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Is the provided answer checked ?
|
||||
* @param {number} id the answer id
|
||||
|
|
|
@ -32,7 +32,10 @@
|
|||
:warning-invalid="answerType.warningInvalid"
|
||||
@update:text="onTitleChange"
|
||||
@update:mandatory="onMandatoryChange"
|
||||
@delete="onDelete">
|
||||
@delete="onDelete"
|
||||
@focus-prev="onFocusPrev"
|
||||
@focus-next="FocusNextOutside"
|
||||
@keydown.tab.exact="onKeydownTab">
|
||||
<div class="question__content">
|
||||
<input ref="input"
|
||||
:aria-label="t('forms', 'A short answer for the question “{text}”', { text })"
|
||||
|
|
|
@ -22,6 +22,7 @@ import { debounce } from 'debounce'
|
|||
import { generateUrl } from '@nextcloud/router'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import axios from '@nextcloud/axios'
|
||||
import tabbable from 'tabbable'
|
||||
|
||||
import Question from '../components/Questions/Question'
|
||||
|
||||
|
@ -181,5 +182,42 @@ export default {
|
|||
console.error(error)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Enable the edit mode
|
||||
*/
|
||||
enableEdit() {
|
||||
console.debug('enableEdit')
|
||||
if (!this.readOnly) {
|
||||
this.edit = true
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Disable the edit mode
|
||||
*/
|
||||
disableEdit() {
|
||||
console.debug('disableEdit')
|
||||
if (!this.readOnly) {
|
||||
this.edit = false
|
||||
}
|
||||
},
|
||||
|
||||
onFocusPrev(elem) {
|
||||
console.debug('TabPrev Mixin', elem)
|
||||
this.$emit('focus-prev', elem)
|
||||
},
|
||||
onFocusNext(elem) {
|
||||
console.debug('TabNext', elem)
|
||||
this.$emit('focus-next', elem)
|
||||
},
|
||||
FocusNextOutside(elem) {
|
||||
console.debug('focusnextoutside', elem)
|
||||
this.$emit('focus-next', elem)
|
||||
this.disableEdit()
|
||||
},
|
||||
onKeydownTab(event) {
|
||||
console.debug('keydownTab')
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -94,7 +94,9 @@
|
|||
:index="index + 1"
|
||||
:max-string-lengths="maxStringLengths"
|
||||
v-bind.sync="question"
|
||||
@delete="deleteQuestion(question)" />
|
||||
@delete="deleteQuestion(question)"
|
||||
@focus-prev="onFocusPrev"
|
||||
@focus-next="onFocusNext" />
|
||||
</Draggable>
|
||||
|
||||
<!-- Add new questions toolbar -->
|
||||
|
@ -126,6 +128,7 @@ import { loadState } from '@nextcloud/initial-state'
|
|||
import { showError } from '@nextcloud/dialogs'
|
||||
import axios from '@nextcloud/axios'
|
||||
import debounce from 'debounce'
|
||||
import tabbable from 'tabbable'
|
||||
import Draggable from 'vuedraggable'
|
||||
|
||||
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
|
||||
|
@ -230,6 +233,22 @@ export default {
|
|||
},
|
||||
|
||||
methods: {
|
||||
onFocusPrev(elem) {
|
||||
console.debug('Create tabPrev', elem)
|
||||
const tabelements = tabbable(this.$el)
|
||||
console.debug(tabelements)
|
||||
const ind = tabelements.findIndex(tabbableElement => tabbableElement === elem)
|
||||
console.debug(tabelements[ind - 1])
|
||||
tabelements[ind - 1].focus()
|
||||
},
|
||||
onFocusNext(elem) {
|
||||
console.debug('Create tabNext', elem)
|
||||
const tabelements = tabbable(this.$el)
|
||||
console.debug(tabelements)
|
||||
const ind = tabelements.findIndex(tabbableElement => tabbableElement === elem)
|
||||
console.debug(tabelements[ind + 1])
|
||||
tabelements[ind + 1].focus()
|
||||
},
|
||||
/**
|
||||
* Fetch the full form data and update parent
|
||||
*
|
||||
|
|
Loading…
Reference in a new issue