wai-website-theme 0.1.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.
Files changed (173) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +52 -0
  4. data/_data/lang.json +730 -0
  5. data/_data/techniques.yml +180 -0
  6. data/_data/wcag.yml +125 -0
  7. data/_includes/.DS_Store +0 -0
  8. data/_includes/body-class.html +1 -0
  9. data/_includes/box.html +10 -0
  10. data/_includes/excol.html +13 -0
  11. data/_includes/footer.html +40 -0
  12. data/_includes/head.html +23 -0
  13. data/_includes/header.html +59 -0
  14. data/_includes/icon.html +6 -0
  15. data/_includes/img.html +17 -0
  16. data/_includes/multilang-list-policy-links.html +29 -0
  17. data/_includes/multilang-list.html +35 -0
  18. data/_includes/multilang-policy-title.html +5 -0
  19. data/_includes/multilang-title-full.html +1 -0
  20. data/_includes/multilang-title.html +1 -0
  21. data/_includes/navlist.html +22 -0
  22. data/_includes/notes.html +2 -0
  23. data/_includes/prevnext.html +34 -0
  24. data/_includes/resources.html +19 -0
  25. data/_includes/sidenav.html +65 -0
  26. data/_includes/sidenote.html +14 -0
  27. data/_includes/toc.html +10 -0
  28. data/_includes/video-player.html +99 -0
  29. data/_layouts/default.html +26 -0
  30. data/_layouts/home.html +14 -0
  31. data/_layouts/news.html +21 -0
  32. data/_layouts/none.html +1 -0
  33. data/_layouts/policy.html +72 -0
  34. data/_layouts/sidenav.html +27 -0
  35. data/_layouts/sidenavsidebar.html +22 -0
  36. data/assets/ableplayer/.gitattributes +14 -0
  37. data/assets/ableplayer/.gitignore +7 -0
  38. data/assets/ableplayer/Gruntfile.js +105 -0
  39. data/assets/ableplayer/LICENSE +26 -0
  40. data/assets/ableplayer/README.md +656 -0
  41. data/assets/ableplayer/build/ableplayer.dist.js +12157 -0
  42. data/assets/ableplayer/build/ableplayer.js +12157 -0
  43. data/assets/ableplayer/build/ableplayer.min.css +2 -0
  44. data/assets/ableplayer/build/ableplayer.min.js +8 -0
  45. data/assets/ableplayer/button-icons/able-icons.svg +116 -0
  46. data/assets/ableplayer/button-icons/black/captions.png +0 -0
  47. data/assets/ableplayer/button-icons/black/chapters.png +0 -0
  48. data/assets/ableplayer/button-icons/black/close.png +0 -0
  49. data/assets/ableplayer/button-icons/black/descriptions.png +0 -0
  50. data/assets/ableplayer/button-icons/black/ellipsis.png +0 -0
  51. data/assets/ableplayer/button-icons/black/faster.png +0 -0
  52. data/assets/ableplayer/button-icons/black/forward.png +0 -0
  53. data/assets/ableplayer/button-icons/black/fullscreen-collapse.png +0 -0
  54. data/assets/ableplayer/button-icons/black/fullscreen-expand.png +0 -0
  55. data/assets/ableplayer/button-icons/black/help.png +0 -0
  56. data/assets/ableplayer/button-icons/black/next.png +0 -0
  57. data/assets/ableplayer/button-icons/black/pause.png +0 -0
  58. data/assets/ableplayer/button-icons/black/pipe.png +0 -0
  59. data/assets/ableplayer/button-icons/black/play.png +0 -0
  60. data/assets/ableplayer/button-icons/black/preferences.png +0 -0
  61. data/assets/ableplayer/button-icons/black/previous.png +0 -0
  62. data/assets/ableplayer/button-icons/black/rabbit.png +0 -0
  63. data/assets/ableplayer/button-icons/black/restart.png +0 -0
  64. data/assets/ableplayer/button-icons/black/rewind.png +0 -0
  65. data/assets/ableplayer/button-icons/black/sign.png +0 -0
  66. data/assets/ableplayer/button-icons/black/slower.png +0 -0
  67. data/assets/ableplayer/button-icons/black/stop.png +0 -0
  68. data/assets/ableplayer/button-icons/black/transcript.png +0 -0
  69. data/assets/ableplayer/button-icons/black/turtle.png +0 -0
  70. data/assets/ableplayer/button-icons/black/volume-loud.png +0 -0
  71. data/assets/ableplayer/button-icons/black/volume-medium.png +0 -0
  72. data/assets/ableplayer/button-icons/black/volume-mute.png +0 -0
  73. data/assets/ableplayer/button-icons/black/volume-soft.png +0 -0
  74. data/assets/ableplayer/button-icons/fonts/able.eot +0 -0
  75. data/assets/ableplayer/button-icons/fonts/able.svg +40 -0
  76. data/assets/ableplayer/button-icons/fonts/able.ttf +0 -0
  77. data/assets/ableplayer/button-icons/fonts/able.woff +0 -0
  78. data/assets/ableplayer/button-icons/white/captions.png +0 -0
  79. data/assets/ableplayer/button-icons/white/chapters.png +0 -0
  80. data/assets/ableplayer/button-icons/white/close.png +0 -0
  81. data/assets/ableplayer/button-icons/white/descriptions.png +0 -0
  82. data/assets/ableplayer/button-icons/white/ellipsis.png +0 -0
  83. data/assets/ableplayer/button-icons/white/faster.png +0 -0
  84. data/assets/ableplayer/button-icons/white/forward.png +0 -0
  85. data/assets/ableplayer/button-icons/white/fullscreen-collapse.png +0 -0
  86. data/assets/ableplayer/button-icons/white/fullscreen-expand.png +0 -0
  87. data/assets/ableplayer/button-icons/white/help.png +0 -0
  88. data/assets/ableplayer/button-icons/white/next.png +0 -0
  89. data/assets/ableplayer/button-icons/white/pause.png +0 -0
  90. data/assets/ableplayer/button-icons/white/pipe.png +0 -0
  91. data/assets/ableplayer/button-icons/white/play.png +0 -0
  92. data/assets/ableplayer/button-icons/white/preferences.png +0 -0
  93. data/assets/ableplayer/button-icons/white/previous.png +0 -0
  94. data/assets/ableplayer/button-icons/white/rabbit.png +0 -0
  95. data/assets/ableplayer/button-icons/white/restart.png +0 -0
  96. data/assets/ableplayer/button-icons/white/rewind.png +0 -0
  97. data/assets/ableplayer/button-icons/white/sign.png +0 -0
  98. data/assets/ableplayer/button-icons/white/slower.png +0 -0
  99. data/assets/ableplayer/button-icons/white/stop.png +0 -0
  100. data/assets/ableplayer/button-icons/white/transcript.png +0 -0
  101. data/assets/ableplayer/button-icons/white/turtle.png +0 -0
  102. data/assets/ableplayer/button-icons/white/volume-loud.png +0 -0
  103. data/assets/ableplayer/button-icons/white/volume-medium.png +0 -0
  104. data/assets/ableplayer/button-icons/white/volume-mute.png +0 -0
  105. data/assets/ableplayer/button-icons/white/volume-soft.png +0 -0
  106. data/assets/ableplayer/images/wingrip.png +0 -0
  107. data/assets/ableplayer/package.json +22 -0
  108. data/assets/ableplayer/scripts/JQuery.doWhen.js +113 -0
  109. data/assets/ableplayer/scripts/ableplayer-base.js +440 -0
  110. data/assets/ableplayer/scripts/browser.js +162 -0
  111. data/assets/ableplayer/scripts/buildplayer.js +1609 -0
  112. data/assets/ableplayer/scripts/caption.js +385 -0
  113. data/assets/ableplayer/scripts/chapters.js +242 -0
  114. data/assets/ableplayer/scripts/control.js +1514 -0
  115. data/assets/ableplayer/scripts/description.js +283 -0
  116. data/assets/ableplayer/scripts/dialog.js +147 -0
  117. data/assets/ableplayer/scripts/dragdrop.js +766 -0
  118. data/assets/ableplayer/scripts/event.js +595 -0
  119. data/assets/ableplayer/scripts/initialize.js +725 -0
  120. data/assets/ableplayer/scripts/langs.js +750 -0
  121. data/assets/ableplayer/scripts/metadata.js +134 -0
  122. data/assets/ableplayer/scripts/misc.js +72 -0
  123. data/assets/ableplayer/scripts/preference.js +909 -0
  124. data/assets/ableplayer/scripts/search.js +171 -0
  125. data/assets/ableplayer/scripts/sign.js +92 -0
  126. data/assets/ableplayer/scripts/slider.js +454 -0
  127. data/assets/ableplayer/scripts/track.js +296 -0
  128. data/assets/ableplayer/scripts/transcript.js +590 -0
  129. data/assets/ableplayer/scripts/translation.js +66 -0
  130. data/assets/ableplayer/scripts/volume.js +383 -0
  131. data/assets/ableplayer/scripts/webvtt.js +765 -0
  132. data/assets/ableplayer/scripts/youtube.js +471 -0
  133. data/assets/ableplayer/styles/ableplayer.css +1241 -0
  134. data/assets/ableplayer/thirdparty/js.cookie.js +145 -0
  135. data/assets/ableplayer/thirdparty/modernizr.custom.js +4 -0
  136. data/assets/ableplayer/translations/ca.js +1 -0
  137. data/assets/ableplayer/translations/de.js +1 -0
  138. data/assets/ableplayer/translations/en.js +305 -0
  139. data/assets/ableplayer/translations/es.js +305 -0
  140. data/assets/ableplayer/translations/fr.js +305 -0
  141. data/assets/ableplayer/translations/it.js +303 -0
  142. data/assets/ableplayer/translations/ja.js +305 -0
  143. data/assets/ableplayer/translations/nl.js +305 -0
  144. data/assets/css/style.css +4360 -0
  145. data/assets/css/style.css.map +1 -0
  146. data/assets/fonts/anonymouspro-bold.woff +0 -0
  147. data/assets/fonts/anonymouspro-bold.woff2 +0 -0
  148. data/assets/fonts/anonymouspro-bolditalic.woff +0 -0
  149. data/assets/fonts/anonymouspro-bolditalic.woff2 +0 -0
  150. data/assets/fonts/anonymouspro-italic.woff +0 -0
  151. data/assets/fonts/anonymouspro-italic.woff2 +0 -0
  152. data/assets/fonts/anonymouspro-regular.woff +0 -0
  153. data/assets/fonts/anonymouspro-regular.woff2 +0 -0
  154. data/assets/fonts/notosans-bold.woff +0 -0
  155. data/assets/fonts/notosans-bold.woff2 +0 -0
  156. data/assets/fonts/notosans-bolditalic.woff +0 -0
  157. data/assets/fonts/notosans-bolditalic.woff2 +0 -0
  158. data/assets/fonts/notosans-italic.woff +0 -0
  159. data/assets/fonts/notosans-italic.woff2 +0 -0
  160. data/assets/fonts/notosans-regular.woff +0 -0
  161. data/assets/fonts/notosans-regular.woff2 +0 -0
  162. data/assets/images/.DS_Store +0 -0
  163. data/assets/images/Shape.svg +10 -0
  164. data/assets/images/icon-related-content.svg +14 -0
  165. data/assets/images/icons.svg +126 -0
  166. data/assets/images/teaser-image@1x.jpg +0 -0
  167. data/assets/images/teaser-image@2x.jpg +0 -0
  168. data/assets/images/w3c.sketch +0 -0
  169. data/assets/images/w3c.svg +10 -0
  170. data/assets/scripts/jquery.min.js +4 -0
  171. data/assets/scripts/main.js +208 -0
  172. data/assets/scripts/svg4everybody.js +1 -0
  173. metadata +257 -0
@@ -0,0 +1,283 @@
1
+ (function ($) {
2
+ AblePlayer.prototype.initDescription = function() {
3
+
4
+ // set default mode for delivering description (open vs closed)
5
+ // based on availability and user preference
6
+
7
+ // called when player is being built, or when a user
8
+ // toggles the Description button or changes a description-related preference
9
+ // In the latter two scendarios, this.refreshingDesc == true via control.js > handleDescriptionToggle()
10
+
11
+ // The following variables are applicable to delivery of description:
12
+ // prefDesc == 1 if user wants description (i.e., Description button is on); else 0
13
+ // prefDescFormat == either 'video' or 'text'
14
+ // prefDescPause == 1 to pause video when description starts; else 0
15
+ // prefVisibleDesc == 1 to visibly show text-based description area; else 0
16
+ // hasOpenDesc == true if a described version of video is available via data-desc-src attribute
17
+ // hasClosedDesc == true if a description text track is available
18
+ // this.useDescFormat == either 'video' or 'text'; the format ultimately delivered
19
+ // descOn == true if description of either type is on
20
+ if (!this.refreshingDesc) {
21
+ // this is the initial build
22
+ // first, check to see if there's an open-described version of this video
23
+ // checks only the first source since if a described version is provided,
24
+ // it must be provided for all sources
25
+ this.descFile = this.$sources.first().attr('data-desc-src');
26
+ if (typeof this.descFile !== 'undefined') {
27
+ this.hasOpenDesc = true;
28
+ }
29
+ else {
30
+ // there's no open-described version via data-desc-src, but what about data-youtube-desc-src?
31
+ if (this.youTubeDescId) {
32
+ this.hasOpenDesc = true;
33
+ }
34
+ else { // there are no open-described versions from any source
35
+ this.hasOpenDesc = false;
36
+ }
37
+ }
38
+ }
39
+ // update this.useDescFormat based on media availability & user preferences
40
+ if (this.prefDesc) {
41
+ if (this.hasOpenDesc && this.hasClosedDesc) {
42
+ // both formats are available. Use whichever one user prefers
43
+ this.useDescFormat = this.prefDescFormat;
44
+ this.descOn = true;
45
+ }
46
+ else if (this.hasOpenDesc) {
47
+ this.useDescFormat = 'video';
48
+ this.descOn = true;
49
+ }
50
+ else if (this.hasClosedDesc) {
51
+ this.useDescFormat = 'text';
52
+ this.descOn = true;
53
+ }
54
+ }
55
+ else { // description button is off
56
+ if (this.refreshingDesc) { // user just now toggled it off
57
+ this.prevDescFormat = this.useDescFormat;
58
+ this.useDescFormat = false;
59
+ this.descOn = false;
60
+ }
61
+ else { // desc has always been off
62
+ this.useDescFormat = false;
63
+ }
64
+ }
65
+
66
+ if (this.descOn) {
67
+
68
+ if (this.useDescFormat === 'video') {
69
+
70
+ if (!this.usingAudioDescription()) {
71
+ // switched from non-described to described version
72
+ this.swapDescription();
73
+ }
74
+ // hide description div
75
+ this.$descDiv.hide();
76
+ this.$descDiv.removeClass('able-clipped');
77
+ }
78
+ else if (this.useDescFormat === 'text') {
79
+ this.$descDiv.show();
80
+ if (this.prefVisibleDesc) { // make it visible to everyone
81
+ this.$descDiv.removeClass('able-clipped');
82
+ }
83
+ else { // keep it visible to screen readers, but hide from everyone else
84
+ this.$descDiv.addClass('able-clipped');
85
+ }
86
+ if (!this.swappingSrc) {
87
+ this.showDescription(this.getElapsed());
88
+ }
89
+ }
90
+ }
91
+ else { // description is off.
92
+
93
+ if (this.prevDescFormat === 'video') { // user was previously using description via video
94
+ if (this.usingAudioDescription()) {
95
+ this.swapDescription();
96
+ }
97
+ }
98
+ else if (this.prevDescFormat === 'text') { // user was previously using text description
99
+ // hide description div from everyone, including screen reader users
100
+ this.$descDiv.hide();
101
+ this.$descDiv.removeClass('able-clipped');
102
+ }
103
+ }
104
+ this.refreshingDesc = false;
105
+ };
106
+
107
+ // Returns true if currently using audio description, false otherwise.
108
+ AblePlayer.prototype.usingAudioDescription = function () {
109
+
110
+ if (this.player === 'youtube') {
111
+ return (this.activeYouTubeId === this.youTubeDescId);
112
+ }
113
+ else {
114
+ return (this.$sources.first().attr('data-desc-src') === this.$sources.first().attr('src'));
115
+ }
116
+ };
117
+
118
+ AblePlayer.prototype.swapDescription = function() {
119
+ // swap described and non-described source media, depending on which is playing
120
+ // this function is only called in two circumstances:
121
+ // 1. Swapping to described version when initializing player (based on user prefs & availability)
122
+ // 2. User is toggling description
123
+ var thisObj, i, origSrc, descSrc, srcType, jwSourceIndex, newSource;
124
+
125
+ thisObj = this;
126
+
127
+ // get current time, and start new video at the same time
128
+ // NOTE: There is some risk in resuming playback at the same start time
129
+ // since the described version might include extended audio description (with pauses)
130
+ // and might therefore be longer than the non-described version
131
+ // The benefits though would seem to outweigh this risk
132
+ this.swapTime = this.getElapsed(); // video will scrub to this time after loaded (see event.js)
133
+
134
+ if (this.descOn) {
135
+ // user has requested the described version
136
+ this.showAlert(this.tt.alertDescribedVersion);
137
+ }
138
+ else {
139
+ // user has requested the non-described version
140
+ this.showAlert(this.tt.alertNonDescribedVersion);
141
+ }
142
+
143
+ if (this.player === 'html5') {
144
+
145
+ if (this.usingAudioDescription()) {
146
+ // the described version is currently playing. Swap to non-described
147
+ for (i=0; i < this.$sources.length; i++) {
148
+ // for all <source> elements, replace src with data-orig-src
149
+ origSrc = this.$sources[i].getAttribute('data-orig-src');
150
+ srcType = this.$sources[i].getAttribute('type');
151
+ if (origSrc) {
152
+ this.$sources[i].setAttribute('src',origSrc);
153
+ }
154
+ if (srcType === 'video/mp4') {
155
+ jwSourceIndex = i;
156
+ }
157
+ }
158
+ // No need to check for this.initializing
159
+ // This function is only called during initialization
160
+ // if swapping from non-described to described
161
+ this.swappingSrc = true;
162
+ }
163
+ else {
164
+ // the non-described version is currently playing. Swap to described.
165
+ for (i=0; i < this.$sources.length; i++) {
166
+ // for all <source> elements, replace src with data-desc-src (if one exists)
167
+ // then store original source in a new data-orig-src attribute
168
+ origSrc = this.$sources[i].getAttribute('src');
169
+ descSrc = this.$sources[i].getAttribute('data-desc-src');
170
+ srcType = this.$sources[i].getAttribute('type');
171
+ if (descSrc) {
172
+ this.$sources[i].setAttribute('src',descSrc);
173
+ this.$sources[i].setAttribute('data-orig-src',origSrc);
174
+ }
175
+ if (srcType === 'video/mp4') {
176
+ jwSourceIndex = i;
177
+ }
178
+ }
179
+ this.swappingSrc = true;
180
+ }
181
+
182
+ // now reload the source file.
183
+ if (this.player === 'html5') {
184
+ this.media.load();
185
+ }
186
+ else if (this.player === 'youtube') {
187
+ // TODO: Load new youTubeId
188
+ }
189
+ else if (this.player === 'jw' && this.jwPlayer) {
190
+ newSource = this.$sources[jwSourceIndex].getAttribute('src');
191
+ this.jwPlayer.load({file: newSource});
192
+ }
193
+ }
194
+ else if (this.player === 'youtube') {
195
+
196
+ if (this.usingAudioDescription()) {
197
+ // the described version is currently playing. Swap to non-described
198
+ this.activeYouTubeId = this.youTubeId;
199
+ this.showAlert(this.tt.alertNonDescribedVersion);
200
+ }
201
+ else {
202
+ // the non-described version is currently playing. Swap to described.
203
+ this.activeYouTubeId = this.youTubeDescId;
204
+ this.showAlert(this.tt.alertDescribedVersion);
205
+ }
206
+ if (typeof this.youTubePlayer !== 'undefined') {
207
+
208
+ // retrieve/setup captions for the new video from YouTube
209
+ this.setupAltCaptions().then(function() {
210
+
211
+ if (thisObj.playing) {
212
+ // loadVideoById() loads and immediately plays the new video at swapTime
213
+ thisObj.youTubePlayer.loadVideoById(thisObj.activeYouTubeId,thisObj.swapTime);
214
+ }
215
+ else {
216
+ // cueVideoById() loads the new video and seeks to swapTime, but does not play
217
+ thisObj.youTubePlayer.cueVideoById(thisObj.activeYouTubeId,thisObj.swapTime);
218
+ }
219
+ });
220
+ }
221
+ }
222
+ };
223
+
224
+ AblePlayer.prototype.showDescription = function(now) {
225
+
226
+ // there's a lot of redundancy between this function and showCaptions
227
+ // Trying to combine them ended up in a mess though. Keeping as is for now.
228
+
229
+ if (this.swappingSrc) {
230
+ return;
231
+ }
232
+
233
+ var d, thisDescription;
234
+ var flattenComponentForDescription = function (component) {
235
+ var result = [];
236
+ if (component.type === 'string') {
237
+ result.push(component.value);
238
+ }
239
+ else {
240
+ for (var ii = 0; ii < component.children.length; ii++) {
241
+ result.push(flattenComponentForDescription(component.children[ii]));
242
+ }
243
+ }
244
+ return result.join('');
245
+ };
246
+
247
+ var cues;
248
+ if (this.selectedDescriptions) {
249
+ cues = this.selectedDescriptions.cues;
250
+ }
251
+ else if (this.descriptions.length >= 1) {
252
+ cues = this.descriptions[0].cues;
253
+ }
254
+ else {
255
+ cues = [];
256
+ }
257
+ for (d = 0; d < cues.length; d++) {
258
+ if ((cues[d].start <= now) && (cues[d].end > now)) {
259
+ thisDescription = d;
260
+ break;
261
+ }
262
+ }
263
+ if (typeof thisDescription !== 'undefined') {
264
+ if (this.currentDescription !== thisDescription) {
265
+ // temporarily remove aria-live from $status in order to prevent description from being interrupted
266
+ this.$status.removeAttr('aria-live');
267
+ // load the new description into the container div
268
+ this.$descDiv.html(flattenComponentForDescription(cues[thisDescription].components));
269
+ if (this.prefDescPause) {
270
+ this.pauseMedia();
271
+ }
272
+ this.currentDescription = thisDescription;
273
+ }
274
+ }
275
+ else {
276
+ this.$descDiv.html('');
277
+ this.currentDescription = -1;
278
+ // restore aria-live to $status
279
+ this.$status.attr('aria-live','polite');
280
+ }
281
+ };
282
+
283
+ })(jQuery);
@@ -0,0 +1,147 @@
1
+ (function ($) {
2
+ var focusableElementsSelector = "a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]";
3
+
4
+ // Based on the incredible accessible modal dialog.
5
+ window.AccessibleDialog = function(modalDiv, $returnElement, dialogRole, title, $descDiv, closeButtonLabel, width, fullscreen, escapeHook) {
6
+
7
+ this.title = title;
8
+ this.closeButtonLabel = closeButtonLabel;
9
+ this.focusedElementBeforeModal = $returnElement;
10
+ this.escapeHook = escapeHook;
11
+ this.baseId = $(modalDiv).attr('id') || Math.floor(Math.random() * 1000000000).toString();
12
+ var thisObj = this;
13
+ var modal = modalDiv;
14
+ this.modal = modal;
15
+ modal.css({
16
+ 'width': width || '50%',
17
+ 'top': (fullscreen ? '0' : '5%')
18
+ });
19
+ modal.addClass('able-modal-dialog');
20
+
21
+ if (!fullscreen) {
22
+ var closeButton = $('<button>',{
23
+ 'class': 'modalCloseButton',
24
+ 'title': thisObj.closeButtonLabel,
25
+ 'aria-label': thisObj.closeButtonLabel
26
+ }).text('X');
27
+ closeButton.keydown(function (event) {
28
+ // Space key down
29
+ if (event.which === 32) {
30
+ thisObj.hide();
31
+ }
32
+ }).click(function () {
33
+ thisObj.hide();
34
+ });
35
+
36
+ var titleH1 = $('<h1></h1>');
37
+ titleH1.attr('id', 'modalTitle-' + this.baseId);
38
+ titleH1.css('text-align', 'center');
39
+ titleH1.text(title);
40
+
41
+ $descDiv.attr('id', 'modalDesc-' + this.baseId);
42
+
43
+ modal.attr({
44
+ 'aria-labelledby': 'modalTitle-' + this.baseId,
45
+ 'aria-describedby': 'modalDesc-' + this.baseId
46
+ });
47
+ modal.prepend(titleH1);
48
+ modal.prepend(closeButton);
49
+ }
50
+
51
+ modal.attr({
52
+ 'aria-hidden': 'true',
53
+ 'role': dialogRole
54
+ });
55
+
56
+ modal.keydown(function (event) {
57
+ // Escape
58
+ if (event.which === 27) {
59
+ if (thisObj.escapeHook) {
60
+ thisObj.escapeHook(event, this);
61
+ }
62
+ else {
63
+ thisObj.hide();
64
+ event.preventDefault();
65
+ }
66
+ }
67
+ // Tab
68
+ else if (event.which === 9) {
69
+ // Manually loop tab navigation inside the modal.
70
+ var parts = modal.find('*');
71
+ var focusable = parts.filter(focusableElementsSelector).filter(':visible');
72
+
73
+ if (focusable.length === 0) {
74
+ return;
75
+ }
76
+
77
+ var focused = $(':focus');
78
+ var currentIndex = focusable.index(focused);
79
+ if (event.shiftKey) {
80
+ // If backwards from first element, go to last.
81
+ if (currentIndex === 0) {
82
+ focusable.get(focusable.length - 1).focus();
83
+ event.preventDefault();
84
+ }
85
+ }
86
+ else {
87
+ if (currentIndex === focusable.length - 1) {
88
+ focusable.get(0).focus();
89
+ event.preventDefault();
90
+ }
91
+ }
92
+ }
93
+ event.stopPropagation();
94
+ });
95
+
96
+ $('body > *').not('.able-modal-overlay').not('.able-modal-dialog').attr('aria-hidden', 'false');
97
+ };
98
+
99
+ AccessibleDialog.prototype.show = function () {
100
+ if (!this.overlay) {
101
+ // Generate overlay.
102
+ var overlay = $('<div></div>').attr({
103
+ 'class': 'able-modal-overlay',
104
+ 'tabindex': '-1'
105
+ });
106
+ this.overlay = overlay;
107
+ $('body').append(overlay);
108
+
109
+ // Keep from moving focus out of dialog when clicking outside of it.
110
+ overlay.on('mousedown.accessibleModal', function (event) {
111
+ event.preventDefault();
112
+ });
113
+ }
114
+
115
+ $('body > *').not('.able-modal-overlay').not('.able-modal-dialog').attr('aria-hidden', 'true');
116
+
117
+ this.overlay.css('display', 'block');
118
+ this.modal.css('display', 'block');
119
+ this.modal.attr({
120
+ 'aria-hidden': 'false',
121
+ 'tabindex': '-1'
122
+ });
123
+
124
+ var focusable = this.modal.find("*").filter(focusableElementsSelector).filter(':visible');
125
+ if (focusable.length === 0) {
126
+ this.focusedElementBeforeModal.blur();
127
+ }
128
+ var thisObj = this;
129
+ setTimeout(function () {
130
+ // originally set focus on the first focusable element
131
+ // thisObj.modal.find('button.modalCloseButton').first().focus();
132
+ // but setting focus on dialog seems to provide more reliable access to ALL content within
133
+ thisObj.modal.focus();
134
+ }, 300);
135
+ };
136
+
137
+ AccessibleDialog.prototype.hide = function () {
138
+ if (this.overlay) {
139
+ this.overlay.css('display', 'none');
140
+ }
141
+ this.modal.css('display', 'none');
142
+ this.modal.attr('aria-hidden', 'true');
143
+ $('body > *').not('.able-modal-overlay').not('.able-modal-dialog').attr('aria-hidden', 'false');
144
+
145
+ this.focusedElementBeforeModal.focus();
146
+ };
147
+ })(jQuery);