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 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">&times;</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
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+
3
+ module.exports = {
4
+ name: require("./package").name,
5
+ isDevelopingAddon() {
6
+ return true;
7
+ },
8
+
9
+ included() {
10
+ this._super.included.apply(this, arguments);
11
+ },
12
+ };
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
+ }