Code Monkey home page Code Monkey logo

vue-a11y-dialog's Introduction

Vue A11yDialog Build Status

This is a Vue.js wrapper component for [email protected] (Demo on CodeSandbox).

Installation

This library supports both Vue 3 and Vue 2. However, active maintenance is focused on Vue 3. If you still need to support Vue 2, you can stay on version 0.5.2.

Vue 3

npm install vue-a11y-dialog

Vue 2

npm install [email protected]

Usage

In your main.js application file, install the component:

import { createApp } from 'vue'
import A11yDialog from 'vue-a11y-dialog'
import App from './App.vue'

createApp(App).use(A11yDialog).mount('#app')

Then use it as follows:

<template>
  <div id="app">
    <!-- ... -->
    <button type="button" @click="openDialog">
      Open dialog
    </button>

    <a11y-dialog
      id="app-dialog"
      @dialog-ref="assignDialogRef"
    >
      <template v-slot:title>
        <span>Your dialog title</span>
      </template>
      <div>
        <p>Your content</p>
      </div>
    </a11y-dialog>
  </div>
</template>
export default {
  name: 'YourComponent',

  data: () => ({
    dialog: null
  }),

  methods: {
    openDialog() {
      if (this.dialog) {
        this.dialog.show()
      }
    },

    assignDialogRef(dialog) {
      this.dialog = dialog
    }
  }
}

It's important to assign the direct reference to the dialog instance via @dialog-ref, otherwise there is no way to call its methods.

Alternatively, you can also import the component directly into your file without installing it first:

import { A11yDialog } from 'vue-a11y-dialog'
export default {
  name: 'YourComponent',

  components: {
    'a11y-dialog': A11yDialog
  },

  methods: {
    // ...
  }
}

Multiple dialogs

It's possible to use multiple dialogs in the same component, just make sure to assign the different dialog instances separately.

In your <template>:

<template>
  <div id="app">
    <!-- First dialog -->
    <a11y-dialog
      id="first-dialog"
      @dialog-ref="dialog => assignDialogRef('first', dialog)"
    >
      <template v-slot:title>
        <span>First dialog title</span>
      </template>
      <div>
        <p>Your content</p>
      </div>
    </a11y-dialog>

    <!-- Second dialog -->
    <a11y-dialog
      id="second-dialog"
      @dialog-ref="dialog => assignDialogRef('second', dialog)"
    >
      <template v-slot:title>
        <span>Second dialog title</span>
      </template>
      <div>
        <p>Your content</p>
      </div>
    </a11y-dialog>
  </div>
</template>

In your <script>:

import { A11yDialog } from 'vue-a11y-dialog';
export default {
  name: 'YourComponent',

  data: () => ({
    dialogs: {}
  }),

  methods: {
    assignDialogRef(type, dialog) {
      this.dialogs[type] = dialog
    }
  }
}

API

All a11y-dialog instance methods are available, for further documentation see here.

id

  • Property: id
  • Type: String
  • Required: true
  • Description: The unique HTML id attribute added to the dialog element, internally used by a11y-dialog to manipulate the dialog.
  • Usage:
<a11y-dialog id="main-dialog">
  <!-- ... -->
</a11y-dialog>

dialog-root

  • Property: dialog-root
  • Type: String — CSS Selector string.
  • Required: false
  • Default: 'body'
  • Description: The container for the dialog to be rendered into.
  • Usage:
<a11y-dialog dialog-root="#dialog-root">
  <!-- ... -->
</a11y-dialog>

class-names

  • Property: class-names
  • Type: Object
  • Required: false
  • Default: {}
  • Description: Object of classes for each HTML element of the dialog element. Keys are: container, overlay, document, title, closeButton. See a11y-dialog docs for reference.
  • Usage:
<a11y-dialog :class-names="{ container: 'container-class', overlay: 'overlay-class' }">
  <!-- ... -->
</a11y-dialog>

title-id

  • Property: title-id
  • Type: String
  • Required: false
  • Default: Defaults to id + '-title'.
  • Description: The HTML id attribute of the dialog’s title element, used by assistive technologies to provide context and meaning to the dialog window.
  • Usage:
<a11y-dialog title-id="main-title">
  <!-- ... -->
</a11y-dialog>

close-button-label

  • Property: close-button-label
  • Type: String
  • Required: false
  • Default: 'Close this dialog window'
  • Description: The HTML aria-label attribute of the close button, used by assistive technologies to provide extra meaning to the usual cross-mark.
  • Usage:
<a11y-dialog close-button-label="Close dialog">
  <!-- ... -->
</a11y-dialog>

role

  • Property: role
  • Type: String
  • Required: false
  • Default: dialog
  • Description: The role attribute of the dialog element, either dialog (default) or alertdialog to make it a modal (preventing closing on click outside of ESC key).
  • Usage:
<a11y-dialog role="alertdialog">
  <!-- ... -->
</a11y-dialog>

Events

dialog-ref

  • Returns: An a11y-dialog instance or undefined.
  • Description: This event emits the a11y-dialog instance once the component has been initialised. When it gets destroyed, the event returns undefined. This is needed to call instance methods of the dialog, e.g. this.dialog.show().
  • Usage:
<a11y-dialog @dialog-ref="assignDialogRef">
  <!-- ... -->
</a11y-dialog>
export default {
  // ...
  methods: {
    assignDialogRef(dialog) {
      this.dialog = dialog
    }
  }
}

Slots

title

  • Name: title
  • Description: The title of the dialog, mandatory in the document to provide context to assistive technology. Could be hidden with CSS (while remaining accessible).
  • Usage:
<a11y-dialog>
  <template v-slot:title>
    <span>Your dialog title</span>
  </template>
  <!-- ... -->
</a11y-dialog>

closeButtonContent

  • Name: closeButtonContent
  • Default: \u00D7 (×)
  • Description: The string that is the inner HTML of the close button.
  • Usage:
<a11y-dialog>
  <template v-slot:closeButtonContent>
    <span>Close dialog</span>
  </template>
  <!-- ... -->
</a11y-dialog>

closeButtonPosition

  • Name: closeButtonPosition
  • Default: first
  • Description: One of first, last, or none
  • Usage:
<a11y-dialog close-button-position="last">
  <template v-slot:closeButtonContent>
    <span>Close dialog</span>
  </template>
  <!-- ... -->
</a11y-dialog>

Server-side Rendering

This is a client-only component; a11y-dialog won't render anything on the server and wait for your bundle to be executed on the client.

vue-a11y-dialog's People

Contributors

dependabot[bot] avatar evanleck avatar evanre avatar josephkmh avatar kittygiraudel avatar marcus-herrmann avatar morkro avatar redgluten avatar roblevintennis avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

vue-a11y-dialog's Issues

API documentation: app-root="#app" may not be the best example

In the project's readme, there is a example of the app-root prop and an explanation of what it does:

<a11y-dialog app-root="#app">
  <!-- ... -->
</a11y-dialog>
Description: The selector(s) a11y-dialog needs to disable when the dialog is open.

The problem is that, once you apply aria-hidden=true to <div id="app" />, everything inside it will be removed from the accessibility tree, including the modal itself. You can see this behaviour in your official demo: https://rj20wr1kpp.codesandbox.io/

As WAI ARIA Authoring Practices, Modal Dialogs, state (see green note box, lower part):

aria-hidden is set to true on each element containing a portion of the inert layer. ... The dialog element is not a descendant of any element that has aria-hidden set to true.

Therefore, at least the readme should be changed. If possible you should rename app-root to its actual function, maybe render-inert.

I wrote a blog post on how to use your component differently (circa last third of the article): https://marcus.io/blog/a11y-app-dialogs-modals

But nevertheless, thanks for your handy wrapper of a11y-dialog!

issue rendering two dialogs on the same page.

Thanks for the wrapper, i'm fan of the original plugin, and will be of this wrapper for sure :)

This sounds like a dumb question (and probably is), but i want to render [2..n] dialogs in one page. For demo purposes i went with 2, and currently it's only rendering the last one. For the first i'm getting the following error for the first :

DOMException: Failed to execute 'showModal' on 'HTMLDialogElement': The element is not in a Document.

Demo link

My suspicions go to the portal-target. Since we're using the "html" version of it i suspect we don't get the multiple option that comes with the component version <portal-target multiple>, but i might be wrong, so I wanted to get your input on this.

Thanks for your time! ✋

Can you provide types?

Can you provide typescript types for this component?

When importing in typescript file I get:
Could not find a declaration file for module 'vue-a11y-dialog'. '.../node_modules/vue-a11y-dialog/dist/vue-a11y-dialog.umd.js' implicitly has an 'any' type.
Try npm i --save-dev @types/vue-a11y-dialog if it exists or add a new declaration (.d.ts) file containing declare module 'vue-a11y-dialog';

Add an event that fires on close

Instead of only firing an event when the dialog is created it would be great to be able to watch for an event when the user hides the dialog. As in by adding @hide on the component, not by having to add an event listener on the dialog object.

1.1.0 build issue from esm

I'm having trouble building 1.1.0 and consuming from AgnosticUI's agnostic-vue package. Let me provide some context…

To test the 1.1.0 PR I did as mentioned here:
#31 (comment)

For posterity this was that tester's package.json:

{
  "name": "package_test",
  "version": "0.0.0",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "vue": "^3.2.25",
    "vue-a11y-dialog": "file:../vue-a11y-dialog-v1.1.0.tgz"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^2.0.0",
    "vite": "^2.7.2"
  }
}

This works fine.

However, I've tried to consume my PR's topic branch late last night and ran into an issue which appears to be some sort of UMD inside an ESM env issue.

I'm going to try to experiment with the vite.config.js build setup and package.json pointers as well. Something must be off.

Here's errors as both image and text for posterity…

image

This is caused by consumer doing:
import { Vue3A11yDialog } from "vue-a11y-dialog";

yarn dev

changed 1 package, and audited 38 packages in 961ms

3 packages are looking for funding
  run `npm fund` for details

1 moderate severity vulnerability

To address all issues, run:
  npm audit fix

Run `npm audit` for details.
yarn run v1.22.17
warning package.json: No license field
$ vite
Pre-bundling dependencies:
  vue
  agnostic-vue
(this will be run only when your dependencies or config have changed)
 > node_modules/agnostic-vue/dist/agnostic-vue.esm.js:1:7: error: No matching export in "node_modules/vue/dist/vue.runtime.esm-bundler.js" for import "default"
    1 │ import require$$0, { openBlock, createElementBlock, normalizeClass, rend...
      ╵        ~~~~~~~~~~

error when starting dev server:
Error: Build failed with 1 error:
node_modules/agnostic-vue/dist/agnostic-vue.esm.js:1:7: error: No matching export in "node_modules/vue/dist/vue.runtime.esm-bundler.js" for import "default"
    at failureErrorWithLog (/Users/roblevin/workspace/opensource/agnosticui/agnostic-vue/examples/node_modules/esbuild/lib/main.js:1493:15)
    at /Users/roblevin/workspace/opensource/agnosticui/agnostic-vue/examples/node_modules/esbuild/lib/main.js:1151:28
    at runOnEndCallbacks (/Users/roblevin/workspace/opensource/agnosticui/agnostic-vue/examples/node_modules/esbuild/lib/main.js:941:63)
    at buildResponseToResult (/Users/roblevin/workspace/opensource/agnosticui/agnostic-vue/examples/node_modules/esbuild/lib/main.js:1149:7)
    at /Users/roblevin/workspace/opensource/agnosticui/agnostic-vue/examples/node_modules/esbuild/lib/main.js:1258:14
    at /Users/roblevin/workspace/opensource/agnosticui/agnostic-vue/examples/node_modules/esbuild/lib/main.js:629:9
    at handleIncomingPacket (/Users/roblevin/workspace/opensource/agnosticui/agnostic-vue/examples/node_modules/esbuild/lib/main.js:726:9)
    at Socket.readFromStdout (/Users/roblevin/workspace/opensource/agnosticui/agnostic-vue/examples/node_modules/esbuild/lib/main.js:596:7)
    at Socket.emit (node:events:390:28)
    at addChunk (node:internal/streams/readable:315:12)
error Command failed with exit code 1.

odd issue with dialog root element not existing

my app uses vue-a11y-dialog in a nested component while the #dialog-root is in the main app. i am getting a [portal-vue]: Mount Point '#dialog-root' not found in document error only in circumstances where the dialog component is being rendered straight away. it works if i wait for the component to be created (eg put a v-if="$el" in the dialog component). is this the expected behavior?

app.vue:

<template>
  <div id="app">
    <MyDialog v-if="$el" />
    <div id="dialog-root"></div>
  </div>
</template>

<script>
import MyDialog from 'components/MyDialog'

export default {
  components: { MyDialog }
}
</script>

components/MyDialog.vue:

<template>
  <div>
    <button type="button" @click="openDialog">
      Show dialog
    </button>
    <a11y-dialog
      id="my-dialog"
      app-root="#app"
      dialog-root="#dialog-root"
      @dialog-ref="assignDialogRef"
    >
    ... more stuff here 
    </a11y-dialog>
  </div>
</template>

<script>
export default {
  data() {
    return { dialog: null }
  },
  methods: {
    assignDialogRef(dialog) {
      this.dialog = dialog
    },
    openDialog() {
      if (this.dialog) {
        this.dialog.show()
      }
    },
  }
}
</script>

in the code above, removing v-if="$el" will produce (error slightly edited):

[portal-vue]: Mount Point '#dialog-root' not found in document vue-a11y-dialog.js:499
[portal-vue] Target wasn't mounted vue-a11y-dialog.js:528
[Vue warn]: Error in nextTick: "TypeError: a is undefined"

found in

---> <VueA11yDialog>
       <MyDialog> at app/javascript/src/components/MyDialog.vue
           <App> at app/javascript/src/app.vue
             <Root> vue.runtime.esm.js:640
TypeError: "a is undefined"
    b vue-a11y-dialog.js:79
    mounted vue-a11y-dialog.js:614
    VueJS 2
[portal-vue] Target wasn't mounted

in case it is relevant, Vue itself is invoked in JS via:

import Vue from 'vue'
import VueA11yDialog from 'vue-a11y-dialog'
Vue.use(VueA11yDialog)

import App from 'app'

document.addEventListener('DOMContentLoaded', () => {
  const app = new Vue({
    render: (h) => h(App)
  }).$mount()
  document.body.appendChild(app.$el)
})

right now my app works but it seems odd that i need to wait for $el to be available since i did not see it mentioned anywhere in the docs.

Version 1.0.0 seems to be broken: a._self in render function is undefined

See: https://codesandbox.io/s/compassionate-dewdney-nrsxs?file=/src/App.vue (minimal demo)

Vue: 3.0.7
Vue-A11y-Dialog: 1.0.0

Which really confuses me, because back when I added PR #25 it was working (otherwise my PR would not have happened).

Error stack:

Uncaught TypeError: a._self is undefined
    render vue-a11y-dialog.js:1
    renderComponentRoot runtime-core.esm-bundler.js:732
    componentEffect runtime-core.esm-bundler.js:4225
    reactiveEffect reactivity.esm-bundler.js:42
    effect reactivity.esm-bundler.js:17
    setupRenderEffect runtime-core.esm-bundler.js:4208
    mountComponent runtime-core.esm-bundler.js:4167
    processComponent runtime-core.esm-bundler.js:4127
    patch runtime-core.esm-bundler.js:3745
    mountChildren runtime-core.esm-bundler.js:3927
    processFragment runtime-core.esm-bundler.js:4087
    patch runtime-core.esm-bundler.js:3738
    componentEffect runtime-core.esm-bundler.js:4243
    reactiveEffect reactivity.esm-bundler.js:42
    effect reactivity.esm-bundler.js:17
    setupRenderEffect runtime-core.esm-bundler.js:4208
    mountComponent runtime-core.esm-bundler.js:4167
    processComponent runtime-core.esm-bundler.js:4127
    patch runtime-core.esm-bundler.js:3745
    render runtime-core.esm-bundler.js:4828
    mount runtime-core.esm-bundler.js:3046
    mount runtime-dom.esm-bundler.js:1234
    <anonymous> main.js:5
    js app.js:1159
    __webpack_require__ app.js:849
    fn app.js:151
    1 app.js:1172
    __webpack_require__ app.js:849
    checkDeferredModules app.js:46
    <anonymous> app.js:925
    <anonymous> app.js:928

On top of that, my local setup seems to be broken somehow. I've been fighting with Cannot find module 'eslint-plugin-import' when trying to debug this problem with npm link locally

Dialog title implementation and README example mismatched

Hi Moritz! Thanks for this excellent implementation — really works well.

The current example in the README suggests the following use inside <a11y-dialog>:

<h1 slot="title">Your dialog title</h1>

But this will actually render:

<h1>
  <h1>
    Your dialog title
  </h1>
<h1>

Because the title slot is defined as an <h1> element already:

<h1 :id="fullTitleId" :class="classNames.title">
<slot name="title" />
</h1>

I can imagine that an <h1> tag is a semantically good idea inside the dialog, in which case perhaps it should be enforced and the README example changed (e.g. to <span slot="title">).

On the other hand, given that a title isn’t required by the dialog, perhaps it would be better to give users more flexibility? For example, <header> could be used for the slot, which would usually bring less style defaults with it, while still maintaining decent semantics.

regeneratorRuntime is missing

When checking on the published package, I noticed that the build expects regeneratorRuntime to be defined globally, which will be true in 99% due to the wide adoption of Babel, but for some folks it might cause trouble. Please either bundle it up or target the modern browsers to solve the issue.

vue-a11y-dialog not working in MS Edge

I note the demo referenced in this document -> https://codesandbox.io/s/rj20wr1kpp is not working as expected in MS Edge (see attached screenshot). Noticed the error after failing to implement the component, and tried to see weather the demo in this project worked as expected. It does not, however seem to be a problem with the package a11y-dialog itself, and I currently don´t have the answer why this is failing. Therefor opening an issue instead of a PR here. I´ll be happy to contribute, btw.

skjermbilde 2018-09-28 kl 11 11 45

IE11 issue

Hi there,

File vue-a11y-dialog.js has 2 arrow functions and IE11 doesn't understand them. When I replace them with normal functions everything works perfect. Can you pleace fix this in repo? Thanks

Vue i18n functions not available inside dialog

I use this plugin to display a form that use various calls to Vue i18n $t function. It systematically throws the following error:

TypeError: i18n is undefined

If I move my code outside of the Dialog, it works just fine. I've found related issues here and there about Vue i18n and Portal Vue mounting-portal component (LinusBorg/portal-vue#199, kazupon/vue-i18n#184 (comment)) so I decided to try adapting it and see if it works.

I was surprised to see that just by copying your source A11yDialog component within my project solved the issue. Could it be something in the compiled version that cause this?

I don't know if you have an idea about what it could be? In any case providing the source component into the npm package would actually be enough for me and avoid having to copy it manually within my project.

Alert dialog overlay

I think I may have made a mistake in the alert dialog implementation for alert dialog role case…

See:
https://github.com/morkro/vue-a11y-dialog/blob/main/src/A11yDialog.vue#L12

and
https://github.com/morkro/vue-a11y-dialog/blob/main/src/A11yDialog.vue#L12

Notice we only omit the close handler but not the attribute.

And then if we read @KittyGiraudel instructions for this:
https://a11y-dialog.netlify.app/advanced/alert-dialog

I suppose the fix might look like :data-a11y-dialog-hide="role === 'alertdialog' ? undefined : true" or in context:

    >
      <div
        :data-a11y-dialog-hide="role === 'alertdialog' ? undefined : true"
        tabIndex="-1"
        :class="classNames.overlay"
        @click="role === 'alertdialog' ? undefined : close"
      />

I'm happy to make the change next spare cycle I get @morkro

remarks on implementation

  1. You shouldn't be using a function to display your dialog, rather use a v-if which is less verbose and more optimized : your way of implementating will result in the dialog being rendered and inserted to the dom wether or not it's shown, killing the entire benefit of Vue's vdom diffing logic. On a side effect, it will probably make it harder to implement custom transitions also, since "appear/disappear" cycles won't be triggered. A simple way to fix this would be to remove some logic and using v-if on the consumer side instead of using your own flow.

  2. A span for title is arguable, but it's very neat that you made it a <slot /> so it's consumer's discretion to choose what to use.

  3. In case you want to have a more granular separation of concern, you may want to leave the discretion of insertion point to the consumer of your component - meaning that the portal should not be included inside your component ; a dialog is a dialog, the way to use it, the place to insert it can depend on each project. That last point is very debattable since it could be also an explicit implementation constraint but it seems to be that the benefit of embedding portal is not a lot compared to the modularity brought by leaving this choice to your end user.

An ideal implementation should be :

<template>
  <div id="app">
    <!-- ... -->
    <button type="button" @click="dialog = true">
      Open dialog
    </button>

    <portal to="the-dialog-place">
      <a11y-dialog ref="dialog" v-if="dialog">
        <span slot="title">Your dialog title</span>
        <div>
          <p>Your content</p>
        </div>
      </a11y-dialog>
    </portal>
  </div>
</template>

<script>
export default {
  name: 'YourComponent',

  data: () => ({
    dialog: false
  }),
}
</script>

Migrate to version 7 of a11y-dialog

Version 7 is out and based on the changelog, adds some fundamental improvement regarding the fallback to the native dialog element (by removing it).

(btw. the README currently mentions that the vue wrapper is based on version 5 of the a11y-dialog but seems the code uses v6 already)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.