- ✅ Universal: works in
vue@2
andvue@3
🚧 - ✅ Renderless/headless: no assumptions about styles or markup. You have full control.
- ✅ Accessibility first — Focus trap[1] + keyboard navigation + aria-attributes
- ✅ Fully controlled component
- ✅ Pure vue, no wrapping.
- ✅ Simplicity + size
- 🕸 Nested dialogs (questionable pattern, not recommended, but possible because it happens) and it's actually in WAI-ARIA examples so...
Detailed documentation and additional info is available at documentation site
npm i a11y-vue-dialog
# or
yarn add a11y-dialog
A renderless/headless component provides all the functionality required to build a proper Dialog
, but gives zero f*cks about your styles. As such you have full control over it and have to DYI. Here's a basic example on how to do it:
<!-- AppBaseDialog.vue -->
<template>
<a11y-dialog
v-bind="$attrs"
v-on="$listeners"
v-slot:default="{ rootRef, dialogRef, titleRef, closeRef }"
>
<div v-bind="rootRef.prop">
<!-- Bindings do the accessibility attributes for you -->
<div v-bind="dialogRef.props" v-on="dialogRef.listeners">
<h1 v-bind="titleRef.props">{{ title }}</h1>
<button v-bind="closeRef.props" v-on="closeRef.listeners">
</div>
</div>
...
<slot />
</a11y-dialog>
</template>
<script>
import { A11yDialog } from 'a11y-dialog'
export default {
name: 'AppBaseDialog',
components: { A11yDialog },
props: {
title: {
type: String,
required: true
}
}
}
</script>
<!-- At any View.vue, after import AppBaseDialog -->
<template>
<div id="page">
<button @click="isDialogOpen = true">
<app-base-dialog
title="Hello world"
:open="isDialogOpen"
@close="openMyModal = false"
@confirm="handSubmit"
>
My markup, my rules.
</app-base-dialog>
</div>
</template>
Voilá, checkout a working example on CodeSandbox.
Detailed documentation and additional info is available at documentation site
A playground is used to test the component locally. It uses vue/cli
instant prototyping feature, so the downside is that you have to install it globally.
- Clone this repo
yarn install
- Then, just run
yarn play
Thanks to all this packages for inspiration and guidance.
portal-vue|vue-simple-portal
from @LinusBorg which makes escaping overflow traps easy peasya11y-dialog
(vanilla) from@KittyGiraudel
to lead the path that ended herevue-a11y-dialog
(wrapper around ^) from@morkro
for the motivation to build a pure vue alternative to it.- All build tools used to make this a reality!
MIT © Renato de Leão
a11y-vue-dialog's People
Forkers
nskazkia11y-vue-dialog's Issues
Focus trap 3.0
After v0.4.6, we get it stable enough to not break code, but there's sill some scenarios were the calculation of what's the previous
or next
Tab-able element fails.
The good news is, focus is still trapped within dialog after content mutations, it still gets new focusable elements, so no functionality was broke.
I'll pick on this next month.
remove: closeFn scope property.
it's a duplicate of closeRef.listeners.click
and it's making the scoped slots API bigger for no extra benefit.
fix: dialogRef flaky specs
Although things are working i can't seem to write a proper test to check if methods are being called correctly.
Refactor markup version o dialog to use the renderless as base
It doesn't make sense to have the same logic twice.
Babel's helpers are needed but not included in the build
The build needs regeneratorRuntime
but doesn't include it, please either bundle it in or get rid of it. Otherwise, the package is great!
From a11y-vue-dialog/dist/a11y-vue-dialog.esm.js:218
dleOpen: function handleOpen() {
var hasRefs;
return regeneratorRuntime.async(function handleOpen$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return regeneratorRuntime.awrap(this.$nextTick());
case 2:
_context.next = 4;
return regeneratorRuntime.awrap(this.$nextTick());
case 4:
_context.next = 6;
return regeneratorRuntime.awrap(this.$nextTick());
case 6:
hasRefs = this.getDOMRefs(); // do no perform DOM actions if no DOM references
if (hasRefs) {
_context.next = 9;
break;
}
return _context.abrupt("return");
case 9:
this.toggleFocusTrap(true);
this.lookForSiblings();
this.toggleVisibilityEvents(true);
this.toggleContentAriaAttrs(true);
case 13:
case "end":
return _context.stop();
}
}
}, null, this);
},
If we remove focused element on async action, then dialog doesn't close on escape
Because there's not a focusable element within dialog selected.
We need to re-focus
after the current focus element is removed.
Not sure if the best approach is to focus first or next focusable element. Maybe binding keyboard elements to window instead would do, but then nested dialogs would require extra-logic.
Using <transition> and applying v-if to dialogRoot ref breaks focus trap
and errors... 🕵️
Add note about limitation with provide/inject while using portal
A know limitation of portal-vue
so while this depends on it we should warn users.
Gladely vue 3 new <Teleport />
won't have these issues
Related
- vuejs/rfcs#62
- vuejs/rfcs#112
- vuejs/vue#5837 (Provide/inject with functional components)
Breaks focus trap when v-if not applied to portal
And also when dialog starts open by default, focus element is not found
Remove portal-vue dependency
although I still recommend a portal solution for accessible dialogs, and speciallyportal-vue
as the one till Vue 3 comes out and we have <Teleport>
, i don't want to list it under our dependencies anymore, since we're moving renderless and any portal solution should, in theory, work.
The portal
version itself should not have any influence on this component behaviour, and any current portal version should work anyways.
Since we use <component :is="portalName" />
under the hood for the markuped, it should be fairly easy to provide any valid portal component name and props object for configuration.
Also as announced, the markup version of this component be deprecated, so it makes even more sense to do it.
This assumption needs some testing of course.
Automate packaging/publishing/releases
it's not the first time that i publish a version without actually build code on it. Let's automate the whole process so it doesn't rely on my tired human brain.
bug: breaks if using focus-visibile polyfill
Using :focus-visible polyfill breaks focus trap.
Test and it's reproducible on versions >= 0.4.5
— so probably that frankenstein engeneering to keep focus trap order after conditional content mutation affected this. The good side is debugging since it reduces the scope of causes.
We wrongly assume that backdropRef will be dialogs root
This came from the one to one match of the markup version:
<a11y-vue-dialog-renderless #default="{ backdropRef }">
<!-- i work as root but also as backdrop overlay -->
<div class="dialog" v-bind="backdropRef.props">
<div class="dialog__inner">conent</div>
<div>
</div>
But this is also a valid implementation, and currently will break the component
<a11y-vue-dialog-renderless #default="{ backdropRef }">
<!-- i work as root -->
<div class="dialog">
<div class="dialog__backdrop" v-bind="backdropRef.props" />
<div class="dialog__inner">conent</div>
<div>
</div>
Cause
Our dom "refs", are querySelected
assuming a dialogRoot exists and is the element with data-id
, but that data-id
is bound via backdropRef.props
, meaning it won't find the descendents.
a11y-vue-dialog/src/A11yVueDialogRenderless.vue
Lines 176 to 188 in 34f5985
Replace vuepress with vitepress
Note that this is not a security concern for the package at all, but I'm tired of getting those weekly security emails from github.
Vuepress
has been deprecated and Vitpress
is the new king in town.
PreventBackgroundScroll is too opinionated
This plugin shouldn't try to guess Apps root dom structure. Remove $root
overflow declaration. Make it optional to pass a scrollableElement other than body
but don't infer it.
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.