Skip to content

Commit ffde0e7

Browse files
authored
Merge pull request #5318 from nextcloud/backport/5281/stable28
[stable28] fix: Fix tab focus when other elements are displayed next to text that are within a focus trap
2 parents 2ee29bc + 299840c commit ffde0e7

File tree

6 files changed

+49
-11
lines changed

6 files changed

+49
-11
lines changed

src/EditorFactory.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { lowlight } from 'lowlight/lib/core.js'
2929
import hljs from 'highlight.js/lib/core'
3030

3131
import { logger } from './helpers/logger.js'
32-
import { Mention, PlainText, RichText } from './extensions/index.js'
32+
import { FocusTrap, Mention, PlainText, RichText } from './extensions/index.js'
3333
// eslint-disable-next-line import/no-named-as-default
3434
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
3535

@@ -64,6 +64,7 @@ const createEditor = ({ language, onCreate, onUpdate = () => {}, extensions, ena
6464
}),
6565
],
6666
}),
67+
FocusTrap,
6768
]
6869
} else {
6970
defaultExtensions = [PlainText, CodeBlockLowlight.configure({ lowlight, defaultLanguage: language })]

src/components/Editor.vue

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -643,16 +643,10 @@ export default {
643643
644644
onFocus() {
645645
this.emit('focus')
646-
647-
// Make sure that the focus trap doesn't catch tab keys while being in the contenteditable
648-
window._nc_focus_trap?.[0]?.pause()
649646
},
647+
650648
onBlur() {
651649
this.emit('blur')
652-
653-
// Hand back focus to the previous trap
654-
window._nc_focus_trap?.[0]?.unpause()
655-
this.$el.focus()
656650
},
657651
658652
onAddImageNode() {
@@ -750,6 +744,7 @@ export default {
750744
this.$editor.commands.insertContent('\t')
751745
this.$editor.commands.focus()
752746
event.preventDefault()
747+
event.stopPropagation()
753748
return
754749
}
755750

src/components/Editor/ContentContainer.vue

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@
3030
<EditorOutline />
3131
</div>
3232
<slot />
33-
<EditorContent tabindex="0"
34-
role="document"
33+
<EditorContent role="document"
3534
class="editor__content text-editor__content"
3635
:editor="$editor" />
3736
<div class="text-editor__content-wrapper__right" />

src/extensions/FocusTrap.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Extension } from '@tiptap/core'
2+
3+
const toggleFocusTrap = ({ editor }) => {
4+
const trapStack = window._nc_focus_trap ?? []
5+
const activeTrap = trapStack[trapStack.length - 1]
6+
7+
const possibleEditorTabCommand = editor.can().sinkListItem('listItem')
8+
|| editor.can().goToNextCell()
9+
|| editor.can().goToPreviousCell()
10+
11+
if (possibleEditorTabCommand) {
12+
activeTrap?.pause()
13+
} else {
14+
activeTrap?.unpause()
15+
}
16+
}
17+
18+
const unpauseFocusTrap = ({ editor }) => {
19+
const trapStack = window._nc_focus_trap ?? []
20+
const activeTrap = trapStack[trapStack.length - 1]
21+
22+
activeTrap?.unpause()
23+
}
24+
25+
/**
26+
* The viewer focus trap needs to be paused on the fly in order to properly handle tab commands in the editor,
27+
* as we have no control over if a tab key event is reaching the editor otherwise. This is because the focus trap
28+
* registeres a capture listener (https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#capture), so whenever we reach a tab command in the editor the focus trap will already have captured the event.
29+
*
30+
* We also cannot work around this by pushing our own focus trap to the stack, as the focus trap package does not offer any reliable way to programmatically focus the next element of the parent trap if we allow tabbing out of the editor.
31+
*/
32+
const FocusTrap = Extension.create({
33+
name: 'focustrap',
34+
onFocus: toggleFocusTrap,
35+
onBlur: unpauseFocusTrap,
36+
onSelectionUpdate: toggleFocusTrap,
37+
onTransaction: toggleFocusTrap,
38+
onUpdate: toggleFocusTrap,
39+
})
40+
41+
export default FocusTrap

src/extensions/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import CollaborationCursor from './CollaborationCursor.js'
2424
import Emoji from './Emoji.js'
25+
import FocusTrap from './FocusTrap.js'
2526
import Keymap from './Keymap.js'
2627
import UserColor from './UserColor.js'
2728
import Markdown from './Markdown.js'
@@ -33,6 +34,7 @@ import Mention from './Mention.js'
3334
export {
3435
CollaborationCursor,
3536
Emoji,
37+
FocusTrap,
3638
Keymap,
3739
UserColor,
3840
Markdown,

src/nodes/CodeBlockView.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
</div>
4747
</div>
4848
<div :class="{'split-view': showCode && showPreview }">
49-
<pre v-show="showCode" class="split-view__code"><NodeViewContent as="code" :contenteditable="isEditable" /></pre>
49+
<pre v-show="showCode" class="split-view__code"><NodeViewContent as="code" tabindex="-1" :contenteditable="isEditable" /></pre>
5050
<div v-show="showPreview"
5151
ref="preview"
5252
class="split-view__preview"

0 commit comments

Comments
 (0)