ember-a11y-dialog 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -0
- package/addon/components/a11y-dialog.hbs +22 -0
- package/addon/components/a11y-dialog.js +79 -0
- package/addon/modifiers/a11y-dialog-setup.js +56 -0
- package/addon/styles/ember-a11y-dialog.css +52 -0
- package/app/components/a11y-dialog.js +1 -0
- package/app/modifiers/a11y-dialog-setup.js +1 -0
- package/index.js +12 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# ember-a11y-dialog
|
|
2
|
+
|
|
3
|
+
An Ember 3.28 wrapper for [a11y-dialog](https://a11y-dialog.netlify.app/) v8.1.5 using [ember-wormhole](https://github.com/yapplabs/ember-wormhole) for the portal pattern.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
ember install ember-a11y-dialog
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
Add the wormhole destination to your `application.hbs` (typically at the end):
|
|
14
|
+
|
|
15
|
+
```hbs
|
|
16
|
+
{{! app/templates/application.hbs }}
|
|
17
|
+
{{outlet}}
|
|
18
|
+
<A11yDialogWormhole />
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### Basic Dialog
|
|
24
|
+
|
|
25
|
+
```hbs
|
|
26
|
+
<A11yDialogOpener @dialogId="my-dialog" class="btn">
|
|
27
|
+
Open Dialog
|
|
28
|
+
</A11yDialogOpener>
|
|
29
|
+
|
|
30
|
+
<A11yDialog @dialogId="my-dialog" @title="Hello World">
|
|
31
|
+
<p>Dialog content here.</p>
|
|
32
|
+
</A11yDialog>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Controlled Dialog (programmatic open/close)
|
|
36
|
+
|
|
37
|
+
```hbs
|
|
38
|
+
<button {{on "click" this.openDialog}}>Open</button>
|
|
39
|
+
|
|
40
|
+
<A11yDialog
|
|
41
|
+
@dialogId="my-dialog"
|
|
42
|
+
@title="Controlled"
|
|
43
|
+
@isOpen={{this.isOpen}}
|
|
44
|
+
@onReady={{this.onDialogReady}}
|
|
45
|
+
@onShow={{this.onShow}}
|
|
46
|
+
@onHide={{this.onHide}}
|
|
47
|
+
>
|
|
48
|
+
<p>Content</p>
|
|
49
|
+
</A11yDialog>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Named Blocks (custom layout)
|
|
53
|
+
|
|
54
|
+
```hbs
|
|
55
|
+
<A11yDialog @dialogId="custom-dialog" @isOpen={{this.isOpen}} @onHide={{this.close}}>
|
|
56
|
+
<:header as |h|>
|
|
57
|
+
<h2 id={{h.titleId}}>Custom Title</h2>
|
|
58
|
+
<button data-a11y-dialog-hide aria-label="Close">×</button>
|
|
59
|
+
</:header>
|
|
60
|
+
<:body>
|
|
61
|
+
<p>Custom body content</p>
|
|
62
|
+
</:body>
|
|
63
|
+
<:footer>
|
|
64
|
+
<button data-a11y-dialog-hide>Cancel</button>
|
|
65
|
+
<button {{on "click" this.confirm}}>OK</button>
|
|
66
|
+
</:footer>
|
|
67
|
+
</A11yDialog>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Alert Dialog
|
|
71
|
+
|
|
72
|
+
```hbs
|
|
73
|
+
<A11yDialog
|
|
74
|
+
@dialogId="confirm-delete"
|
|
75
|
+
@title="Are you sure?"
|
|
76
|
+
@alertDialog={{true}}
|
|
77
|
+
@isOpen={{this.showConfirm}}
|
|
78
|
+
@onHide={{this.cancelDelete}}
|
|
79
|
+
>
|
|
80
|
+
<p>This cannot be undone.</p>
|
|
81
|
+
</A11yDialog>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Render In Place (no portal)
|
|
85
|
+
|
|
86
|
+
```hbs
|
|
87
|
+
<A11yDialog @dialogId="inline" @title="Inline" @renderInPlace={{true}}>
|
|
88
|
+
<p>No wormhole — renders inline.</p>
|
|
89
|
+
</A11yDialog>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## API
|
|
93
|
+
|
|
94
|
+
### `<A11yDialog>`
|
|
95
|
+
|
|
96
|
+
| Argument | Type | Default | Description |
|
|
97
|
+
|----------|------|---------|-------------|
|
|
98
|
+
| `@dialogId` | string | `'a11y-dialog'` | Unique ID for the dialog element |
|
|
99
|
+
| `@title` | string | — | Dialog title (used with default header) |
|
|
100
|
+
| `@isOpen` | boolean | — | Controlled open state |
|
|
101
|
+
| `@alertDialog` | boolean | `false` | Use `role="alertdialog"` (overlay doesn't close) |
|
|
102
|
+
| `@renderInPlace` | boolean | `false` | Skip ember-wormhole, render inline |
|
|
103
|
+
| `@destinationId` | string | `'a11y-dialog-wormhole'` | Wormhole destination element ID |
|
|
104
|
+
| `@closeButtonLabel` | string | `'Close dialog'` | Aria label for default close button |
|
|
105
|
+
| `@class` | string | — | Additional CSS class on the dialog container |
|
|
106
|
+
| `@onReady` | function | — | Called with the a11y-dialog instance after setup |
|
|
107
|
+
| `@onShow` | function | — | Called when dialog opens |
|
|
108
|
+
| `@onHide` | function | — | Called when dialog closes |
|
|
109
|
+
|
|
110
|
+
### `<A11yDialogOpener>`
|
|
111
|
+
|
|
112
|
+
| Argument | Type | Default | Description |
|
|
113
|
+
|----------|------|---------|-------------|
|
|
114
|
+
| `@dialogId` | string | `'a11y-dialog'` | ID of dialog to open |
|
|
115
|
+
|
|
116
|
+
### `<A11yDialogWormhole>`
|
|
117
|
+
|
|
118
|
+
| Argument | Type | Default | Description |
|
|
119
|
+
|----------|------|---------|-------------|
|
|
120
|
+
| `@destinationId` | string | `'a11y-dialog-wormhole'` | ID of the wormhole destination div |
|
|
121
|
+
|
|
122
|
+
## Playground
|
|
123
|
+
|
|
124
|
+
A standalone Vite playground is available for quick testing of the dialog patterns:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
cd playground
|
|
128
|
+
nvm use 22
|
|
129
|
+
npm install
|
|
130
|
+
npm run dev
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Compatibility
|
|
134
|
+
|
|
135
|
+
- Ember.js 3.28+
|
|
136
|
+
- Node.js 14+ (16+ for playground)
|
|
137
|
+
- a11y-dialog 8.1.5
|
|
138
|
+
- ember-wormhole 0.6.x
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{{#if this.destinationElement}}
|
|
2
|
+
{{#in-element this.destinationElement}}
|
|
3
|
+
<div
|
|
4
|
+
class="ember-modal-wrapper {{this.wrapperClassValue}}"
|
|
5
|
+
>
|
|
6
|
+
<div
|
|
7
|
+
class="ember-modal-overlay modal-overlay hf-slide-in-up {{this.overlayClassValue}} {{if this.translucentOverlay 'translucent'}}"
|
|
8
|
+
{{on "click" this.handleOverlayClick}}
|
|
9
|
+
>
|
|
10
|
+
<div
|
|
11
|
+
id={{@dialogId}}
|
|
12
|
+
class="modal {{this.containerClassValue}}"
|
|
13
|
+
aria-hidden="true"
|
|
14
|
+
{{a11y-dialog-setup onClose=this.closeAction onKeyDown=this.handleKeyDown}}
|
|
15
|
+
...attributes
|
|
16
|
+
>
|
|
17
|
+
{{yield}}
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
{{/in-element}}
|
|
22
|
+
{{/if}}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import Component from "@glimmer/component";
|
|
2
|
+
import { action } from "@ember/object";
|
|
3
|
+
|
|
4
|
+
export default class A11yDialogComponent extends Component {
|
|
5
|
+
get destinationElement() {
|
|
6
|
+
if (this.args.renderInPlace) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const id = this.args.destinationId || "modal-overlays";
|
|
11
|
+
let element = document.getElementById(id);
|
|
12
|
+
|
|
13
|
+
if (!element) {
|
|
14
|
+
element = document.createElement("div");
|
|
15
|
+
element.id = id;
|
|
16
|
+
document.body.appendChild(element);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return element;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get closeAction() {
|
|
23
|
+
return this.args.onClose || this.args.close;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get containerClassValue() {
|
|
27
|
+
return [
|
|
28
|
+
this.args.containerClass,
|
|
29
|
+
this.args["container-class"],
|
|
30
|
+
this.args.containerClassNames,
|
|
31
|
+
]
|
|
32
|
+
.filter(Boolean)
|
|
33
|
+
.join(" ");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get overlayClassValue() {
|
|
37
|
+
return [
|
|
38
|
+
this.args.overlayClass,
|
|
39
|
+
this.args["overlay-class"],
|
|
40
|
+
this.args.overlayClassNames,
|
|
41
|
+
]
|
|
42
|
+
.filter(Boolean)
|
|
43
|
+
.join(" ");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get wrapperClassValue() {
|
|
47
|
+
return this.args.wrapperClass || this.args["wrapper-class"] || "";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get hasOverlay() {
|
|
51
|
+
return this.args.hasOverlay !== false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get translucentOverlay() {
|
|
55
|
+
return this.args.translucentOverlay !== false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get dataTestId() {
|
|
59
|
+
return this.args["data-test-id"] || this.args.dataTestId;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@action
|
|
63
|
+
handleOverlayClick(event) {
|
|
64
|
+
if (event.target !== event.currentTarget) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (this.args.onClickOverlay) {
|
|
68
|
+
this.args.onClickOverlay();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@action
|
|
73
|
+
handleKeyDown(event) {
|
|
74
|
+
console.log("handleKeyDown", event);
|
|
75
|
+
if (this.args.onKeyDown) {
|
|
76
|
+
this.args.onKeyDown(event);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import Modifier from "ember-modifier";
|
|
2
|
+
import { registerDestructor } from "@ember/destroyable";
|
|
3
|
+
import { schedule } from "@ember/runloop";
|
|
4
|
+
import A11yDialog from "a11y-dialog/dist/a11y-dialog.esm.js";
|
|
5
|
+
|
|
6
|
+
function cleanup(instance) {
|
|
7
|
+
if (instance._keyDownHandler) {
|
|
8
|
+
document.body.removeEventListener("keydown", instance._keyDownHandler);
|
|
9
|
+
instance._keyDownHandler = null;
|
|
10
|
+
}
|
|
11
|
+
if (instance.dialogInstance) {
|
|
12
|
+
instance.dialogInstance.destroy();
|
|
13
|
+
instance.dialogInstance = null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default class A11yDialogSetupModifier extends Modifier {
|
|
18
|
+
dialogInstance = null;
|
|
19
|
+
namedArgs = null;
|
|
20
|
+
_keyDownHandler = null;
|
|
21
|
+
|
|
22
|
+
constructor(owner, args) {
|
|
23
|
+
console.log('A11yDialogSetupModifier constructor 666 @@@');
|
|
24
|
+
super(owner, args);
|
|
25
|
+
registerDestructor(this, cleanup);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
modify(element, positional, named) {
|
|
29
|
+
this.namedArgs = named;
|
|
30
|
+
|
|
31
|
+
console.log('modify 777 @@@');
|
|
32
|
+
if (!this.dialogInstance) {
|
|
33
|
+
console.log('modify 888 @@@');
|
|
34
|
+
this.dialogInstance = new A11yDialog(element);
|
|
35
|
+
|
|
36
|
+
this.dialogInstance.on("hide", () => {
|
|
37
|
+
const { onClose } = this.namedArgs;
|
|
38
|
+
if (typeof onClose === "function") onClose();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
this._keyDownHandler = (event) => {
|
|
42
|
+
const { onKeyDown } = this.namedArgs;
|
|
43
|
+
console.log("modifier keydown event", event);
|
|
44
|
+
if (typeof onKeyDown === "function") onKeyDown(event);
|
|
45
|
+
};
|
|
46
|
+
console.log("modifier keydown handler", this._keyDownHandler);
|
|
47
|
+
document.body.addEventListener("keydown", this._keyDownHandler);
|
|
48
|
+
|
|
49
|
+
schedule("afterRender", () => {
|
|
50
|
+
if (this.dialogInstance) {
|
|
51
|
+
this.dialogInstance.show();
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
.modal {
|
|
2
|
+
position: fixed;
|
|
3
|
+
top: 0;
|
|
4
|
+
left: 0;
|
|
5
|
+
right: 0;
|
|
6
|
+
bottom: 0;
|
|
7
|
+
z-index: 1000;
|
|
8
|
+
align-items: center;
|
|
9
|
+
justify-content: center;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.modal-overlay {
|
|
13
|
+
display: flex;
|
|
14
|
+
justify-content: center;
|
|
15
|
+
position: fixed;
|
|
16
|
+
top: 0;
|
|
17
|
+
left: 0;
|
|
18
|
+
right: 0;
|
|
19
|
+
bottom: 0;
|
|
20
|
+
overflow-y: auto;
|
|
21
|
+
outline: 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.modal-overlay.translucent {
|
|
25
|
+
background: rgba(0, 0, 0, 0.7);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@keyframes ember-modal-fade-in {
|
|
29
|
+
from { opacity: 0; }
|
|
30
|
+
to { opacity: 1; }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@keyframes ember-modal-slide-up {
|
|
34
|
+
from {
|
|
35
|
+
opacity: 0;
|
|
36
|
+
transform: translateY(20px);
|
|
37
|
+
}
|
|
38
|
+
to {
|
|
39
|
+
opacity: 1;
|
|
40
|
+
transform: translateY(0);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#basic-dialog {
|
|
45
|
+
position: relative;
|
|
46
|
+
margin: 60px 0 30px;
|
|
47
|
+
width: 1140px;
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
background: yellow;
|
|
51
|
+
color: #000;
|
|
52
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from 'ember-a11y-dialog/components/a11y-dialog';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "ember-a11y-dialog/modifiers/a11y-dialog-setup";
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ember-a11y-dialog",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Ember 3.28 wrapper for a11y-dialog using in-element for portals",
|
|
5
|
+
"files": [
|
|
6
|
+
"addon",
|
|
7
|
+
"app",
|
|
8
|
+
"index.js",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"keywords": [
|
|
12
|
+
"ember-addon",
|
|
13
|
+
"a11y-dialog",
|
|
14
|
+
"modal",
|
|
15
|
+
"accessible"
|
|
16
|
+
],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"author": "",
|
|
19
|
+
"directories": {
|
|
20
|
+
"doc": "doc",
|
|
21
|
+
"test": "tests"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "ember build",
|
|
25
|
+
"lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"",
|
|
26
|
+
"start": "ember serve",
|
|
27
|
+
"test": "ember test"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@glimmer/component": "^1.1.2",
|
|
31
|
+
"@glimmer/tracking": "^1.1.2",
|
|
32
|
+
"a11y-dialog": "8.1.5",
|
|
33
|
+
"ember-auto-import": "2.7.0",
|
|
34
|
+
"ember-cli-babel": "^7.26.11",
|
|
35
|
+
"ember-cli-htmlbars": "^5.1.1",
|
|
36
|
+
"ember-modifier": "^3.2.7"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@ember/optional-features": "^2.0.0",
|
|
40
|
+
"ember-cli": "~4.12.1",
|
|
41
|
+
"ember-load-initializers": "^2.1.2",
|
|
42
|
+
"ember-resolver": "^8.0.0",
|
|
43
|
+
"ember-source": "~3.28.0",
|
|
44
|
+
"ember-template-lint": "^5.0.0",
|
|
45
|
+
"loader.js": "^4.7.0",
|
|
46
|
+
"webpack": "^5.0.0"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": "14.* || 16.* || >= 18"
|
|
50
|
+
},
|
|
51
|
+
"ember": {
|
|
52
|
+
"edition": "octane"
|
|
53
|
+
},
|
|
54
|
+
"ember-addon": {
|
|
55
|
+
"configPath": "tests/dummy/config",
|
|
56
|
+
"demoURL": null
|
|
57
|
+
}
|
|
58
|
+
}
|