1/** 2 * @file 3 * Dropbutton feature. 4 */ 5 6(function ($, Drupal) { 7 /** 8 * A DropButton presents an HTML list as a button with a primary action. 9 * 10 * All secondary actions beyond the first in the list are presented in a 11 * dropdown list accessible through a toggle arrow associated with the button. 12 * 13 * @constructor Drupal.DropButton 14 * 15 * @param {HTMLElement} dropbutton 16 * A DOM element. 17 * @param {object} settings 18 * A list of options including: 19 * @param {string} settings.title 20 * The text inside the toggle link element. This text is hidden 21 * from visual UAs. 22 */ 23 function DropButton(dropbutton, settings) { 24 // Merge defaults with settings. 25 const options = $.extend( 26 { title: Drupal.t('List additional actions') }, 27 settings, 28 ); 29 const $dropbutton = $(dropbutton); 30 31 /** 32 * @type {jQuery} 33 */ 34 this.$dropbutton = $dropbutton; 35 36 /** 37 * @type {jQuery} 38 */ 39 this.$list = $dropbutton.find('.dropbutton'); 40 41 /** 42 * Find actions and mark them. 43 * 44 * @type {jQuery} 45 */ 46 this.$actions = this.$list.find('li').addClass('dropbutton-action'); 47 48 // Add the special dropdown only if there are hidden actions. 49 if (this.$actions.length > 1) { 50 // Identify the first element of the collection. 51 const $primary = this.$actions.slice(0, 1); 52 // Identify the secondary actions. 53 const $secondary = this.$actions.slice(1); 54 $secondary.addClass('secondary-action'); 55 // Add toggle link. 56 $primary.after(Drupal.theme('dropbuttonToggle', options)); 57 // Bind mouse events. 58 this.$dropbutton.addClass('dropbutton-multiple').on({ 59 /** 60 * Adds a timeout to close the dropdown on mouseleave. 61 * 62 * @ignore 63 */ 64 'mouseleave.dropbutton': this.hoverOut.bind(this), 65 66 /** 67 * Clears timeout when mouseout of the dropdown. 68 * 69 * @ignore 70 */ 71 'mouseenter.dropbutton': this.hoverIn.bind(this), 72 73 /** 74 * Similar to mouseleave/mouseenter, but for keyboard navigation. 75 * 76 * @ignore 77 */ 78 'focusout.dropbutton': this.focusOut.bind(this), 79 80 /** 81 * @ignore 82 */ 83 'focusin.dropbutton': this.focusIn.bind(this), 84 }); 85 } else { 86 this.$dropbutton.addClass('dropbutton-single'); 87 } 88 } 89 90 /** 91 * Delegated callback for opening and closing dropbutton secondary actions. 92 * 93 * @function Drupal.DropButton~dropbuttonClickHandler 94 * 95 * @param {jQuery.Event} e 96 * The event triggered. 97 */ 98 function dropbuttonClickHandler(e) { 99 e.preventDefault(); 100 $(e.target).closest('.dropbutton-wrapper').toggleClass('open'); 101 } 102 103 /** 104 * Process elements with the .dropbutton class on page load. 105 * 106 * @type {Drupal~behavior} 107 * 108 * @prop {Drupal~behaviorAttach} attach 109 * Attaches dropButton behaviors. 110 */ 111 Drupal.behaviors.dropButton = { 112 attach(context, settings) { 113 const dropbuttons = once('dropbutton', '.dropbutton-wrapper', context); 114 if (dropbuttons.length) { 115 // Adds the delegated handler that will toggle dropdowns on click. 116 const body = once('dropbutton-click', 'body'); 117 if (body.length) { 118 $(body).on('click', '.dropbutton-toggle', dropbuttonClickHandler); 119 } 120 // Initialize all buttons. 121 dropbuttons.forEach((dropbutton) => { 122 DropButton.dropbuttons.push( 123 new DropButton(dropbutton, settings.dropbutton), 124 ); 125 }); 126 } 127 }, 128 }; 129 130 /** 131 * Extend the DropButton constructor. 132 */ 133 $.extend( 134 DropButton, 135 /** @lends Drupal.DropButton */ { 136 /** 137 * Store all processed DropButtons. 138 * 139 * @type {Array.<Drupal.DropButton>} 140 */ 141 dropbuttons: [], 142 }, 143 ); 144 145 /** 146 * Extend the DropButton prototype. 147 */ 148 $.extend( 149 DropButton.prototype, 150 /** @lends Drupal.DropButton# */ { 151 /** 152 * Toggle the dropbutton open and closed. 153 * 154 * @param {boolean} [show] 155 * Force the dropbutton to open by passing true or to close by 156 * passing false. 157 */ 158 toggle(show) { 159 const isBool = typeof show === 'boolean'; 160 show = isBool ? show : !this.$dropbutton.hasClass('open'); 161 this.$dropbutton.toggleClass('open', show); 162 }, 163 164 /** 165 * @method 166 */ 167 hoverIn() { 168 // Clear any previous timer we were using. 169 if (this.timerID) { 170 window.clearTimeout(this.timerID); 171 } 172 }, 173 174 /** 175 * @method 176 */ 177 hoverOut() { 178 // Wait half a second before closing. 179 this.timerID = window.setTimeout(this.close.bind(this), 500); 180 }, 181 182 /** 183 * @method 184 */ 185 open() { 186 this.toggle(true); 187 }, 188 189 /** 190 * @method 191 */ 192 close() { 193 this.toggle(false); 194 }, 195 196 /** 197 * @param {jQuery.Event} e 198 * The event triggered. 199 */ 200 focusOut(e) { 201 this.hoverOut.call(this, e); 202 }, 203 204 /** 205 * @param {jQuery.Event} e 206 * The event triggered. 207 */ 208 focusIn(e) { 209 this.hoverIn.call(this, e); 210 }, 211 }, 212 ); 213 214 $.extend( 215 Drupal.theme, 216 /** @lends Drupal.theme */ { 217 /** 218 * A toggle is an interactive element often bound to a click handler. 219 * 220 * @param {object} options 221 * Options object. 222 * @param {string} [options.title] 223 * The button text. 224 * 225 * @return {string} 226 * A string representing a DOM fragment. 227 */ 228 dropbuttonToggle(options) { 229 return `<li class="dropbutton-toggle"><button type="button"><span class="dropbutton-arrow"><span class="visually-hidden">${options.title}</span></span></button></li>`; 230 }, 231 }, 232 ); 233 234 // Expose constructor in the public space. 235 Drupal.DropButton = DropButton; 236})(jQuery, Drupal);
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.