quasarframework / quasar

Quasar Framework - Build high-performance VueJS user interfaces in record time
https://quasar.dev
MIT License
25.96k stars 3.52k forks source link

Dialog Plugin: Tab doesn't focus cancel button #12505

Open TuringJest opened 2 years ago

TuringJest commented 2 years ago

What happened?

The cancel button is not accessible by forward tabbing (shift + tab works) on a dialog with only a cancel and ok button and no focusable elements in the body.

What did you expect to happen?

Hitting tab should cycle through cancel as well.

Reproduction URL

https://quasar.dev/quasar-plugins/dialog#example--basic

How to reproduce?

  1. Open the confirm dialog (or any dialog with just cancel and ok buttons)
  2. Keep pressing tab

-> The focus will cycle between adressbar and ok

Flavour

Quasar CLI (@quasar/cli | @quasar/app)

Areas

Components (quasar), Plugins (quasar)

Platforms/Browsers

Chrome

Quasar info output

Operating System - Darwin(21.3.0) - darwin/arm64
NodeJs - 16.6.1

Global packages
  NPM - 8.3.0
  yarn - 1.22.11
  @quasar/cli - 1.2.2
  @quasar/icongenie - Not installed
  cordova - Not installed

Important local packages
  quasar - 2.5.3 -- Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time
  @quasar/app - 3.3.2 -- Quasar Framework local CLI
  @quasar/extras - 1.12.4 -- Quasar Framework fonts, icons and animations
  eslint-plugin-quasar - Not installed
  vue - 3.2.29 -- The progressive JavaScript framework for building modern web UI.
  vue-router - 4.0.12
  vuex - 4.0.2 -- state management for Vue.js
  electron - 17.0.0 -- Build cross platform desktop apps with JavaScript, HTML, and CSS
  electron-packager - 15.4.0 -- Customize and package your Electron app with OS-specific bundles (.app, .exe, etc.) via JS or CLI
  electron-builder - Not installed
  @babel/core - 7.14.3 -- Babel compiler core.
  webpack - 5.64.1 -- Packs CommonJs/AMD modules for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand. Support loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.
  webpack-dev-server - 4.7.3 -- Serves a webpack app. Updates the browser on changes.
  workbox-webpack-plugin - Not installed
  register-service-worker - 1.7.2 -- Script for registering service worker, with hooks
  typescript - 4.5.5 -- TypeScript is a language for application scale JavaScript development
  @capacitor/core - Not installed
  @capacitor/cli - Not installed
  @capacitor/android - Not installed
  @capacitor/ios - Not installed

Quasar App Extensions
  @quasar/quasar-app-extension-dotenv - 1.1.0 -- Load .env variables into your quasar project

Networking
  Host - MacBook-Pro.local
  en0 - 192.168.20.5

Relevant log output

No response

Additional context

No response

github-actions[bot] commented 2 years ago

Hi @TuringJest! 👋

It looks like you provided an invalid or unsupported reproduction URL. Do not use any service other than Codepen, jsFiddle, Codesandbox, and GitHub. Make sure the URL you provided is correct and reachable. You can test it by visiting it in a private tab, another device, etc. Please edit your original post above and provide a valid reproduction URL as explained.

Without a proper reproduction, your issue will have to get closed.

Thank you for your collaboration. 👏

Saeid-Za commented 2 years ago

Hello There!

Explanation on why this is the expected behavior

The rendered HTML for a confirm dialog is something like this (simplified version):

<div class="q-dialog">
    <button> cancel </button>
    <button> ok </button>
</div

which means the cancel button is rendered as first and then ok button is rendered. quasar itself, focues on the ok button and both buttons has tabindex=0 attribute. from developer.mozilla:

tabindex="0" means that the element should be focusable in sequential keyboard navigation, after any positive tabindex values and its order is defined by the document's source order.

Which means after pressing the tab keyboard when confirm dialog is open, the focus would be returned to the first focusable element on page and skipping cancel button because there is no other html element after the ok button, which is the expected behavior.

Workaround

Again from developer.mozilla:

A positive value means the element should be focusable in sequential keyboard navigation, with its order defined by the value of the number. That is, tabindex="4" is focused before tabindex="5" and tabindex="0", but after tabindex="3". If multiple elements share the same positive tabindex value, their order relative to each other follows their position in the document source. The maximum value for tabindex is 32767. If not specified, it takes the default value 0.

what we can do is setting tabindex manually on the buttons to enforce tabbing order.

const { useQuasar } = Quasar

const app = Vue.createApp({
  setup () {
    const $q = useQuasar()

    function confirm () {
      $q.dialog({
        title: 'Confirm',
        message: 'Would you like to turn on the wifi?',
        cancel: {
          tabindex: 1
        },
        ok:{
          tabindex: 2
        },
        persistent: true
      }).onOk(() => {
        // console.log('>>>> OK')
      }).onOk(() => {
        // console.log('>>>> second OK catcher')
      }).onCancel(() => {
        // console.log('>>>> Cancel')
      }).onDismiss(() => {
        // console.log('I am triggered on both OK and Cancel')
      })
    }

    return { confirm }
  }
})

app.use(Quasar, { config: {} })
app.mount('#q-app')
<div id="q-app" style="min-height: 100vh;">
    <q-btn label="Confirm" color="primary" @click="confirm"></q-btn>
</div>

Hope this helps.

A side note

Perhaps it is better to focus on the cancel button initially to prevent the accidental confirmation of the operation (something like delete), this is why, I myself always set the initial focus on the cancel button.

const { useQuasar } = Quasar

const app = Vue.createApp({
  setup () {
    const $q = useQuasar()

    function confirm () {
      $q.dialog({
        title: 'Confirm',
        message: 'Would you like to turn on the wifi?',
        cancel: {
          autofocus: true
        },
        persistent: true
      }).onOk(() => {
        // console.log('>>>> OK')
      }).onOk(() => {
        // console.log('>>>> second OK catcher')
      }).onCancel(() => {
        // console.log('>>>> Cancel')
      }).onDismiss(() => {
        // console.log('I am triggered on both OK and Cancel')
      })
    }

    return { confirm }
  }
})

app.use(Quasar, { config: {} })
app.mount('#q-app')
<div id="q-app" style="min-height: 100vh;">
    <q-btn label="Confirm" color="primary" @click="confirm"></q-btn>
</div>