PageSourceSearch

https://ndri.res.in/themes/custom_theme/assets/js/sf-no-focus-open.js?tlzsjk

js ndri.res.in collected 2026-10-02 04:10:46 UTC 4,583 bytes, 128 lines download raw bytes

1(function (Drupal, $, once) {
2  "use strict";
3
4  function ensureId($el, prefix) {
5    if (!$el.attr('id')) {
6      $el.attr('id', (prefix || 'sf-') + Math.random().toString(36).slice(2));
7    }
8    return $el.attr('id');
9  }
10
11  Drupal.behaviors.sfNoFocusOpen = {
12    attach(context) {
13      $(once('sf-no-focus-open', '.sf-menu', context)).each(function () {
14        const $menu = $(this);
15
16        const closeSiblings = ($li) => {
17          $li.siblings('.menuparent').each(function () {
18            $(this).data('sf-close')?.();
19          });
20        };
21
22        $menu.find('li.menuparent').each(function () {
23          const $li  = $(this);
24          const $sub = $li.children('ul').first();
25          if (!$sub.length) return;
26
27          const $trigger = $li.children('a, span.sf-with-ul, span.nolink').first();
28          if (!$trigger.length) return;
29
30          if ($trigger.is('span')) {
31            $trigger.attr({ tabindex: '0', role: 'button' });
32          }
33
34          // IDs for relationships
35          const subId  = ensureId($sub, 'sf-sub-');
36          const trigId = ensureId($trigger, 'sf-trig-');
37
38          // Baseline ARIA
39          $li.attr({ 'aria-haspopup': 'true', 'aria-expanded': 'false' });
40          $trigger.attr({
41            'aria-haspopup': 'true',
42            'aria-expanded': 'false',
43            'aria-controls': subId,
44            'id': trigId
45          });
46
47          // submenu: aria-labelledby + aria-expanded mirror + aria-hidden
48          $sub
49            .attr({
50              'id': subId,
51              'aria-labelledby': trigId,
52              'aria-hidden': 'true',
53              'aria-expanded': 'false'   // <-- mirror state here per your ask
54            })
55            .addClass('sf-hidden')
56            .hide();
57
58          // helpers
59          const close = () => {
60            $li.removeClass('sfHover');
61            $sub.addClass('sf-hidden').hide()
62                .attr({'aria-hidden': 'true', 'aria-expanded': 'false'});
63            $li.attr('aria-expanded', 'false');
64            $trigger.attr('aria-expanded', 'false');
65          };
66          const open = () => {
67            closeSiblings($li);
68            $li.addClass('sfHover');
69            $sub.removeClass('sf-hidden').show()
70                .attr({'aria-hidden': 'false', 'aria-expanded': 'true'});
71            $li.attr('aria-expanded', 'true');
72            $trigger.attr('aria-expanded', 'true');
73          };
74
75          $li.data('sf-close', close);
76          close();
77
78          // prevent open-on-focus
79          $trigger.on('focusin', function (e) {
80            e.stopImmediatePropagation();
81            setTimeout(close, 0);
82          });
83
84          // click/tap
85          $trigger.on('click', function (e) {
86            if ($trigger.is('span') || $trigger.attr('href') === '#' || $trigger.hasClass('sf-with-ul')) {
87              e.preventDefault();
88              ($trigger.attr('aria-expanded') === 'true' ? close() : open());
89            }
90          });
91
92          // hover sync
93          $li.on('mouseenter', open).on('mouseleave', close);
94
95          // keyboard
96          $trigger.on('keydown', function (e) {
97            const k = e.key;
98            if (k === 'Enter' || k === ' ') {
99              e.preventDefault();
100              ($trigger.attr('aria-expanded') === 'true' ? close() : open());
101            } else if (k === 'ArrowDown') {
102              e.preventDefault();
103              if ($trigger.attr('aria-expanded') !== 'true') open();
104              const $first = $sub.find('a,button,[tabindex]:not([tabindex="-1"])').filter(':visible').first();
105              if ($first.length) $first.focus();
106            } else if (k === 'Escape') {
107              e.preventDefault();
108              close();
109              $trigger.focus();
110            } else if (k === 'ArrowLeft') {
111              const $prev = $li.prevAll('li:visible').first().children('a,span').first();
112              if ($prev.length) { e.preventDefault(); $prev.focus(); }
113            } else if (k === 'ArrowRight') {
114              const $next = $li.nextAll('li:visible').first().children('a,span').first();
115              if ($next.length) { e.preventDefault(); $next.focus(); }
116            }
117          });
118
119          // close when leaving subtree
120          $li.on('focusout', function (e) {
121            const next = e.relatedTarget;
122            if (!next || !$.contains($li.get(0), next)) close();
123          });
124        });
125      });
126    }
127  };
128})(Drupal, jQuery, once);

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.