diff --git a/src/views/Tasks.vue b/src/views/Tasks.vue index ad581fe..8ffd8b7 100644 --- a/src/views/Tasks.vue +++ b/src/views/Tasks.vue @@ -3,7 +3,7 @@

Pages

- +
+

Task {{index+1}}: {{task.name}}

- +

{{task.description}}

-
- - +
+
- +

Task {{index+1}}: {{task.name}}

- +
@@ -202,6 +203,31 @@ export default { } }, methods: { + // This method is for event handlers (which are synchronous) that need to + // focus on conditionally-rendered elements enabled inside the handler. + // Because the handler blocks the process, the element won't be rendered + // until after the handler returns, but elements can't be focused if they + // are hidden. + // This is kind of a hack, but sadly, I couldn't think of a better and more + // or less universal solution. + deferredFocus(elementId) { + let targetElement = document.getElementById(elementId); + (async () => { + // Conditional rendering may be based on `v-if` or `v-show`. In the + // former case, the element isn't part of the DOM, and so the second + // loop (which is valid for `v-show`) would fail because `targetElement` + // is null. + while (!targetElement) { + targetElement = document.getElementById(elementId); + await new Promise(resolve => setTimeout(resolve, 50)); + } + + while (targetElement.offsetParent === null) { + await new Promise(resolve => setTimeout(resolve, 50)); + } + targetElement.focus(); + })(); + }, getGeneratorOptionInputId(optionId) { return `generator-option-${this.generatorModalChosenGenerator}-${optionId}`; }, @@ -222,9 +248,11 @@ export default { }, expandTask(internalId) { this.expandedTasks[internalId] = true; + this.deferredFocus(`task-collapse-${internalId}`); }, collapseTask(internalId) { this.expandedTasks[internalId] = false; + this.deferredFocus(`task-expand-${internalId}`); }, generatePages() { const pages = pageGenerators.generatePages(this.generatorModalChosenGenerator, this.generatorOptionValues); @@ -296,22 +324,7 @@ export default { openGeneratorModal(event) { this.generatorModalOpen = true; document.addEventListener("keydown", this.onGeneratorModalKeydown); - - // It seems handling events (which is how this method would be called) - // blocks the process, which means the modal won't appear until the event - // handler exits. And until the modal appears, an invisible input inside - // it can't be focused. As this produces no event (that I know of), - // and a MutationObserver on the `style` attribute felt more hacky and - // less reliable than just asynchronously waiting for the modal to appear, - // this is how I handled it. - const targetElement = document.getElementById(`page-generator-selector-${this.generatorModalChosenGenerator}`); - (async () => { - while (targetElement.offsetParent === null) { - await new Promise(resolve => setTimeout(resolve, 50)); - } - targetElement.focus(); - })(); - + this.deferredFocus(`page-generator-selector-${this.generatorModalChosenGenerator}`); event.preventDefault(); }, closeGeneratorModal() { @@ -326,16 +339,7 @@ export default { openAddTaskModal(event) { this.addTaskModalOpen = true; document.addEventListener("keydown", this.onAddTaskModalKeydown); - - const targetElement = document.getElementById(`page-task-selector-${this.addTaskModalChosenTask}`); - (async () => { - while (targetElement.offsetParent === null) { - await new Promise(resolve => setTimeout(resolve, 50)); - } - targetElement.focus(); - })(); - - // see the note in openGeneratorModal() + this.deferredFocus(`page-task-selector-${this.addTaskModalChosenTask}`); event.preventDefault(); }, closeAddTaskModal() { @@ -349,6 +353,7 @@ export default { .svg-icon-button { @apply flex-shrink-0; @apply hover:bg-gray-300 dark:hover:bg-gray-600; + @apply focus:bg-gray-300 dark:focus:bg-gray-600 focus:shadow-inner-focus; } .modal-wrapper {