PageSourceSearch

https://prgi.gov.in/themes/custom/rni/js/accessiblity-fix.js?v=1.x

js prgi.gov.in collected 2026-10-04 23:58:03 UTC 11,024 bytes, 455 lines download raw bytes

1/**
2 * accessibility-fix.js
3 * WCAG 2.1 AA / GIGW / STQC Accessibility Helper
4 */
5
6(function ($, Drupal, once) {
7
8  Drupal.behaviors.accessibilityFix = {
9    attach: function (context) {
10
11      /* =====================================
12       * 1. FIX LANDMARKS
13       * ===================================== */
14
15      if (!$('header', context).length) {
16        $('.site-header, .navbar, #header', context).first().wrap('<header role="banner"></header>');
17      }
18
19      if (!$('main', context).length) {
20        $('#main, .main-content, #content', context).first().wrap('<main id="main-content" role="main"></main>');
21      }
22
23      if (!$('footer', context).length) {
24        $('.site-footer, #footer', context).first().wrap('<footer role="contentinfo"></footer>');
25      }
26
27      /* =====================================
28       * 2. HEADING STRUCTURE
29       * ===================================== */
30
31      if (!$('body').hasClass('user-logged-in')) {
32
33        const $headings = $('h1,h2,h3,h4,h5,h6', context);
34
35        const $h1s = $('h1', context);
36
37        if ($h1s.length > 1) {
38          $h1s.slice(1).each(function () {
39            $(this).replaceWith('<h2>' + $(this).html() + '</h2>');
40          });
41        }
42
43        if (!$('h1', context).length && $('h2', context).length) {
44          $('h2', context).first().replaceWith(function () {
45            return '<h1>' + $(this).html() + '</h1>';
46          });
47        }
48
49        let lastLevel = 0;
50
51        $headings.each(function () {
52
53          let level = parseInt(this.tagName.substring(1), 10);
54
55          if (lastLevel && level > lastLevel + 1) {
56
57            let fixed = lastLevel + 1;
58
59            $(this).replaceWith(
60              '<h' + fixed + '>' + $(this).html() + '</h' + fixed + '>'
61            );
62
63            lastLevel = fixed;
64
65          }
66          else {
67            lastLevel = level;
68          }
69
70        });
71
72      }
73
74      /* =====================================
75       * 3. REMOVE FAKE HEADINGS
76       * ===================================== */
77
78      $('.heading,.title,.section-title', context).each(function () {
79
80        if (!$(this).is('h1,h2,h3,h4,h5,h6')) {
81          $(this).replaceWith('<p>' + $(this).html() + '</p>');
82        }
83
84      });
85
86      /* =====================================
87       * 4. FORM LABEL FIX
88       * ===================================== */
89
90      $('input,textarea,select', context).each(function () {
91
92        const $field = $(this);
93
94        if (
95          !$field.attr('aria-label') &&
96          !$field.attr('aria-labelledby')
97        ) {
98
99          let label = '';
100
101          const id = $field.attr('id');
102
103          if (id && $('label[for="' + id + '"]').length) {
104            label = $('label[for="' + id + '"]').text();
105          }
106
107          if (!label && $field.attr('placeholder')) {
108            label = $field.attr('placeholder');
109          }
110
111          if (!label && $field.attr('title')) {
112            label = $field.attr('title');
113          }
114
115          if (!label) {
116            label = $field.closest('.form-item').find('label').text();
117          }
118
119          if (label) {
120            $field.attr('aria-label', $.trim(label));
121          }
122
123        }
124
125      });
126
127$('.menuparent > span[aria-expanded="false"]').removeAttr('aria-expanded');
128
129      /* =====================================
130       * 5. EMPTY LINK FIX
131       * ===================================== */
132
133      $('a,button', context).each(function () {
134
135        const $el = $(this);
136
137        if (
138          !$el.text().trim() &&
139          !$el.find('img').length &&
140          !$el.attr('aria-label')
141        ) {
142
143          if ($el.attr('title')) {
144            $el.attr('aria-label', $.trim($el.attr('title')));
145          }
146
147        }
148
149      });
150
151    /* =====================================
152 * 6. IMAGE ACCESSIBILITY
153 * STQC / GIGW / WCAG
154 * ===================================== */
155
156$('a:has(img), img', context).each(function () {
157
158  var $img, $link;
159
160  if ($(this).is('img')) {
161    $img = $(this);
162    $link = $img.closest('a');
163  }
164  else {
165    $link = $(this);
166    $img = $link.find('img').first();
167  }
168
169  if (!$img.length) {
170    return;
171  }
172
173  // Remove invalid role
174  if ($link.length && $link.attr('role') === 'button') {
175    $link.removeAttr('role');
176  }
177
178  // Lazy loading
179  if (!$img.attr('loading')) {
180    $img.attr('loading', 'lazy');
181  }
182
183  // Async decoding
184  if (!$img.attr('decoding')) {
185    $img.attr('decoding', 'async');
186  }
187
188  var alt = $.trim($img.attr('alt') || '');
189  var title = $.trim($img.attr('title') || '');
190  var aria = $.trim($link.attr('aria-label') || '');
191
192  /* ---------------------------------
193   * Case 1 : Alt already exists
194   * --------------------------------- */
195
196  if (alt !== '') {
197
198    if ($link.length) {
199      $link.removeAttr('aria-label');
200      $link.removeAttr('title');
201    }
202
203    $img.removeAttr('title');
204    return;
205  }
206
207  /* ---------------------------------
208   * Case 2 : Title exists but Alt missing
209   * --------------------------------- */
210
211  if (title !== '') {
212
213    $img.attr('alt', title);
214
215    if ($link.length) {
216      $link.removeAttr('aria-label');
217      $link.removeAttr('title');
218    }
219
220    $img.removeAttr('title');
221    return;
222  }
223
224  /* ---------------------------------
225   * Case 3 : Alt & Title both missing
226   * --------------------------------- */
227
228  if (alt === '' && title === '') {
229
230    var label = '';
231
232    if ($link.length && $.trim($link.text()) !== '') {
233      label = $.trim($link.text());
234    }
235
236    if (!label) {
237
238      var cardTitle = $img.closest('.card').find('.card-title').first().text();
239
240      if (cardTitle) {
241        label = $.trim(cardTitle);
242      }
243
244    }
245
246    if (!label) {
247      label = 'Image';
248    }
249
250    if ($link.length && aria === '') {
251      $link.attr('aria-label', label);
252    }
253
254  }
255
256  // Width & Height
257  if (!$img.attr('width') && this.naturalWidth) {
258    $img.attr('width', this.naturalWidth);
259  }
260
261  if (!$img.attr('height') && this.naturalHeight) {
262    $img.attr('height', this.naturalHeight);
263  }
264
265});
266
267      /* =====================================
268       * 7. IMAGE LINK FIX
269       * ===================================== */
270
271      $('a:has(img)', context).each(function () {
272
273        const $link = $(this);
274        const $img = $link.find('img').first();
275
276        // Remove invalid role
277        if ($link.attr('role') === 'button') {
278          $link.removeAttr('role');
279        }
280
281        // If image already has alt
282        // don't duplicate aria-label
283        if ($img.attr('alt') && $.trim($img.attr('alt')) !== '') {
284          $link.removeAttr('aria-label');
285        }
286
287      });
288
289      /* =====================================
290       * 8. DECORATIVE IMAGES
291       * ===================================== */
292
293      $('img.decorative', context).each(function () {
294
295        $(this)
296          .attr('alt', '')
297          .attr('role', 'presentation');
298
299      });
300
301      /* =====================================
302       * 9. REMOVE EMPTY TITLE
303       * ===================================== */
304
305      $('[title]', context).each(function () {
306
307        if (!$.trim($(this).attr('title'))) {
308          $(this).removeAttr('title');
309        }
310
311      });
312
313    }
314
315  };
316
317})(jQuery, Drupal, once);
318
319
320
321(function ($, Drupal) {
322
323  Drupal.behaviors.fixDuplicateIds = {
324    attach: function (context) {
325
326      var ids = {};
327
328      $('[id]', context).each(function () {
329
330        var $el = $(this);
331        var id = $el.attr('id');
332
333        if (!id) {
334          return;
335        }
336
337        if (ids[id]) {
338
339          ids[id]++;
340
341          var newId = id + '-' + ids[id];
342
343          $el.attr('id', newId);
344
345          // Update labels
346          $('label[for="' + id + '"]', context).attr('for', newId);
347
348          // Update anchor links
349          $('a[href="#' + id + '"]', context).attr('href', '#' + newId);
350
351          // Update ARIA references
352          $('[aria-labelledby="' + id + '"]', context).attr('aria-labelledby', newId);
353          $('[aria-describedby="' + id + '"]', context).attr('aria-describedby', newId);
354          $('[aria-controls="' + id + '"]', context).attr('aria-controls', newId);
355
356        } else {
357
358          ids[id] = 1;
359
360        }
361
362      });
363
364    }
365  };
366
367})(jQuery, Drupal);
368
369
370/**
371 * WCAG 2.1 - Identify Input Purpose
372 * Add autocomplete attributes automatically
373 */
374
375(function ($, Drupal) {
376
377  Drupal.behaviors.autoCompleteFix = {
378    attach: function (context) {
379
380      $('input:not([autocomplete])', context).each(function () {
381
382        var $field = $(this);
383
384        var text = (
385          ($field.attr('name') || '') + ' ' +
386          ($field.attr('id') || '') + ' ' +
387          ($field.attr('placeholder') || '') + ' ' +
388          ($field.closest('.form-item').find('label').text() || '')
389        ).toLowerCase();
390
391        var value = '';
392
393        if (text.match(/first.?name|fname|given.?name/)) {
394          value = 'given-name';
395        }
396        else if (text.match(/last.?name|surname|lname|family.?name/)) {
397          value = 'family-name';
398        }
399        else if (text.match(/full.?name|your.?name|name/)) {
400          value = 'name';
401        }
402        else if (text.match(/email|e-mail/)) {
403          value = 'email';
404        }
405        else if (text.match(/phone|mobile|telephone|contact/)) {
406          value = 'tel';
407        }
408        else if (text.match(/organisation|organization|company/)) {
409          value = 'organization';
410        }
411        else if (text.match(/address.?line.?1|address1/)) {
412          value = 'address-line1';
413        }
414        else if (text.match(/address.?line.?2|address2/)) {
415          value = 'address-line2';
416        }
417        else if (text.match(/city|town/)) {
418          value = 'address-level2';
419        }
420        else if (text.match(/district/)) {
421          value = 'address-level2';
422        }
423        else if (text.match(/state/)) {
424          value = 'address-level1';
425        }
426        else if (text.match(/postal|zip|pincode|pin.?code/)) {
427          value = 'postal-code';
428        }
429        else if (text.match(/country/)) {
430          value = 'country';
431        }
432        else if (text.match(/username|user.?name|login/)) {
433          value = 'username';
434        }
435        else if (text.match(/password/) && !$field.attr('autocomplete')) {
436          value = 'current-password';
437        }
438        else if (text.match(/search|keyword|title|owner|publisher|language|registration|publication/)) {
439          // Search/filter fields
440          value = 'off';
441        }
442
443        if (value !== '') {
444          $field.attr('autocomplete', value);
445        }
446
447      });
448
449    }
450  };
451
452})(jQuery, Drupal);
453
454
455

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.