standard header in about page (#676)
[openemr.git] / public / assets / jquery-ui-1-12-1 / ui / widgets / menu.js
blobfd86527f77a4f8e5b5239b38e6b530d4b46ea10b
1 /*!
2  * jQuery UI Menu 1.12.1
3  * http://jqueryui.com
4  *
5  * Copyright jQuery Foundation and other contributors
6  * Released under the MIT license.
7  * http://jquery.org/license
8  */
10 //>>label: Menu
11 //>>group: Widgets
12 //>>description: Creates nestable menus.
13 //>>docs: http://api.jqueryui.com/menu/
14 //>>demos: http://jqueryui.com/menu/
15 //>>css.structure: ../../themes/base/core.css
16 //>>css.structure: ../../themes/base/menu.css
17 //>>css.theme: ../../themes/base/theme.css
19 ( function( factory ) {
20         if ( typeof define === "function" && define.amd ) {
22                 // AMD. Register as an anonymous module.
23                 define( [
24                         "jquery",
25                         "../keycode",
26                         "../position",
27                         "../safe-active-element",
28                         "../unique-id",
29                         "../version",
30                         "../widget"
31                 ], factory );
32         } else {
34                 // Browser globals
35                 factory( jQuery );
36         }
37 }( function( $ ) {
39 return $.widget( "ui.menu", {
40         version: "1.12.1",
41         defaultElement: "<ul>",
42         delay: 300,
43         options: {
44                 icons: {
45                         submenu: "ui-icon-caret-1-e"
46                 },
47                 items: "> *",
48                 menus: "ul",
49                 position: {
50                         my: "left top",
51                         at: "right top"
52                 },
53                 role: "menu",
55                 // Callbacks
56                 blur: null,
57                 focus: null,
58                 select: null
59         },
61         _create: function() {
62                 this.activeMenu = this.element;
64                 // Flag used to prevent firing of the click handler
65                 // as the event bubbles up through nested menus
66                 this.mouseHandled = false;
67                 this.element
68                         .uniqueId()
69                         .attr( {
70                                 role: this.options.role,
71                                 tabIndex: 0
72                         } );
74                 this._addClass( "ui-menu", "ui-widget ui-widget-content" );
75                 this._on( {
77                         // Prevent focus from sticking to links inside menu after clicking
78                         // them (focus should always stay on UL during navigation).
79                         "mousedown .ui-menu-item": function( event ) {
80                                 event.preventDefault();
81                         },
82                         "click .ui-menu-item": function( event ) {
83                                 var target = $( event.target );
84                                 var active = $( $.ui.safeActiveElement( this.document[ 0 ] ) );
85                                 if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) {
86                                         this.select( event );
88                                         // Only set the mouseHandled flag if the event will bubble, see #9469.
89                                         if ( !event.isPropagationStopped() ) {
90                                                 this.mouseHandled = true;
91                                         }
93                                         // Open submenu on click
94                                         if ( target.has( ".ui-menu" ).length ) {
95                                                 this.expand( event );
96                                         } else if ( !this.element.is( ":focus" ) &&
97                                                         active.closest( ".ui-menu" ).length ) {
99                                                 // Redirect focus to the menu
100                                                 this.element.trigger( "focus", [ true ] );
102                                                 // If the active item is on the top level, let it stay active.
103                                                 // Otherwise, blur the active item since it is no longer visible.
104                                                 if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) {
105                                                         clearTimeout( this.timer );
106                                                 }
107                                         }
108                                 }
109                         },
110                         "mouseenter .ui-menu-item": function( event ) {
112                                 // Ignore mouse events while typeahead is active, see #10458.
113                                 // Prevents focusing the wrong item when typeahead causes a scroll while the mouse
114                                 // is over an item in the menu
115                                 if ( this.previousFilter ) {
116                                         return;
117                                 }
119                                 var actualTarget = $( event.target ).closest( ".ui-menu-item" ),
120                                         target = $( event.currentTarget );
122                                 // Ignore bubbled events on parent items, see #11641
123                                 if ( actualTarget[ 0 ] !== target[ 0 ] ) {
124                                         return;
125                                 }
127                                 // Remove ui-state-active class from siblings of the newly focused menu item
128                                 // to avoid a jump caused by adjacent elements both having a class with a border
129                                 this._removeClass( target.siblings().children( ".ui-state-active" ),
130                                         null, "ui-state-active" );
131                                 this.focus( event, target );
132                         },
133                         mouseleave: "collapseAll",
134                         "mouseleave .ui-menu": "collapseAll",
135                         focus: function( event, keepActiveItem ) {
137                                 // If there's already an active item, keep it active
138                                 // If not, activate the first item
139                                 var item = this.active || this.element.find( this.options.items ).eq( 0 );
141                                 if ( !keepActiveItem ) {
142                                         this.focus( event, item );
143                                 }
144                         },
145                         blur: function( event ) {
146                                 this._delay( function() {
147                                         var notContained = !$.contains(
148                                                 this.element[ 0 ],
149                                                 $.ui.safeActiveElement( this.document[ 0 ] )
150                                         );
151                                         if ( notContained ) {
152                                                 this.collapseAll( event );
153                                         }
154                                 } );
155                         },
156                         keydown: "_keydown"
157                 } );
159                 this.refresh();
161                 // Clicks outside of a menu collapse any open menus
162                 this._on( this.document, {
163                         click: function( event ) {
164                                 if ( this._closeOnDocumentClick( event ) ) {
165                                         this.collapseAll( event );
166                                 }
168                                 // Reset the mouseHandled flag
169                                 this.mouseHandled = false;
170                         }
171                 } );
172         },
174         _destroy: function() {
175                 var items = this.element.find( ".ui-menu-item" )
176                                 .removeAttr( "role aria-disabled" ),
177                         submenus = items.children( ".ui-menu-item-wrapper" )
178                                 .removeUniqueId()
179                                 .removeAttr( "tabIndex role aria-haspopup" );
181                 // Destroy (sub)menus
182                 this.element
183                         .removeAttr( "aria-activedescendant" )
184                         .find( ".ui-menu" ).addBack()
185                                 .removeAttr( "role aria-labelledby aria-expanded aria-hidden aria-disabled " +
186                                         "tabIndex" )
187                                 .removeUniqueId()
188                                 .show();
190                 submenus.children().each( function() {
191                         var elem = $( this );
192                         if ( elem.data( "ui-menu-submenu-caret" ) ) {
193                                 elem.remove();
194                         }
195                 } );
196         },
198         _keydown: function( event ) {
199                 var match, prev, character, skip,
200                         preventDefault = true;
202                 switch ( event.keyCode ) {
203                 case $.ui.keyCode.PAGE_UP:
204                         this.previousPage( event );
205                         break;
206                 case $.ui.keyCode.PAGE_DOWN:
207                         this.nextPage( event );
208                         break;
209                 case $.ui.keyCode.HOME:
210                         this._move( "first", "first", event );
211                         break;
212                 case $.ui.keyCode.END:
213                         this._move( "last", "last", event );
214                         break;
215                 case $.ui.keyCode.UP:
216                         this.previous( event );
217                         break;
218                 case $.ui.keyCode.DOWN:
219                         this.next( event );
220                         break;
221                 case $.ui.keyCode.LEFT:
222                         this.collapse( event );
223                         break;
224                 case $.ui.keyCode.RIGHT:
225                         if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
226                                 this.expand( event );
227                         }
228                         break;
229                 case $.ui.keyCode.ENTER:
230                 case $.ui.keyCode.SPACE:
231                         this._activate( event );
232                         break;
233                 case $.ui.keyCode.ESCAPE:
234                         this.collapse( event );
235                         break;
236                 default:
237                         preventDefault = false;
238                         prev = this.previousFilter || "";
239                         skip = false;
241                         // Support number pad values
242                         character = event.keyCode >= 96 && event.keyCode <= 105 ?
243                                 ( event.keyCode - 96 ).toString() : String.fromCharCode( event.keyCode );
245                         clearTimeout( this.filterTimer );
247                         if ( character === prev ) {
248                                 skip = true;
249                         } else {
250                                 character = prev + character;
251                         }
253                         match = this._filterMenuItems( character );
254                         match = skip && match.index( this.active.next() ) !== -1 ?
255                                 this.active.nextAll( ".ui-menu-item" ) :
256                                 match;
258                         // If no matches on the current filter, reset to the last character pressed
259                         // to move down the menu to the first item that starts with that character
260                         if ( !match.length ) {
261                                 character = String.fromCharCode( event.keyCode );
262                                 match = this._filterMenuItems( character );
263                         }
265                         if ( match.length ) {
266                                 this.focus( event, match );
267                                 this.previousFilter = character;
268                                 this.filterTimer = this._delay( function() {
269                                         delete this.previousFilter;
270                                 }, 1000 );
271                         } else {
272                                 delete this.previousFilter;
273                         }
274                 }
276                 if ( preventDefault ) {
277                         event.preventDefault();
278                 }
279         },
281         _activate: function( event ) {
282                 if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
283                         if ( this.active.children( "[aria-haspopup='true']" ).length ) {
284                                 this.expand( event );
285                         } else {
286                                 this.select( event );
287                         }
288                 }
289         },
291         refresh: function() {
292                 var menus, items, newSubmenus, newItems, newWrappers,
293                         that = this,
294                         icon = this.options.icons.submenu,
295                         submenus = this.element.find( this.options.menus );
297                 this._toggleClass( "ui-menu-icons", null, !!this.element.find( ".ui-icon" ).length );
299                 // Initialize nested menus
300                 newSubmenus = submenus.filter( ":not(.ui-menu)" )
301                         .hide()
302                         .attr( {
303                                 role: this.options.role,
304                                 "aria-hidden": "true",
305                                 "aria-expanded": "false"
306                         } )
307                         .each( function() {
308                                 var menu = $( this ),
309                                         item = menu.prev(),
310                                         submenuCaret = $( "<span>" ).data( "ui-menu-submenu-caret", true );
312                                 that._addClass( submenuCaret, "ui-menu-icon", "ui-icon " + icon );
313                                 item
314                                         .attr( "aria-haspopup", "true" )
315                                         .prepend( submenuCaret );
316                                 menu.attr( "aria-labelledby", item.attr( "id" ) );
317                         } );
319                 this._addClass( newSubmenus, "ui-menu", "ui-widget ui-widget-content ui-front" );
321                 menus = submenus.add( this.element );
322                 items = menus.find( this.options.items );
324                 // Initialize menu-items containing spaces and/or dashes only as dividers
325                 items.not( ".ui-menu-item" ).each( function() {
326                         var item = $( this );
327                         if ( that._isDivider( item ) ) {
328                                 that._addClass( item, "ui-menu-divider", "ui-widget-content" );
329                         }
330                 } );
332                 // Don't refresh list items that are already adapted
333                 newItems = items.not( ".ui-menu-item, .ui-menu-divider" );
334                 newWrappers = newItems.children()
335                         .not( ".ui-menu" )
336                                 .uniqueId()
337                                 .attr( {
338                                         tabIndex: -1,
339                                         role: this._itemRole()
340                                 } );
341                 this._addClass( newItems, "ui-menu-item" )
342                         ._addClass( newWrappers, "ui-menu-item-wrapper" );
344                 // Add aria-disabled attribute to any disabled menu item
345                 items.filter( ".ui-state-disabled" ).attr( "aria-disabled", "true" );
347                 // If the active item has been removed, blur the menu
348                 if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
349                         this.blur();
350                 }
351         },
353         _itemRole: function() {
354                 return {
355                         menu: "menuitem",
356                         listbox: "option"
357                 }[ this.options.role ];
358         },
360         _setOption: function( key, value ) {
361                 if ( key === "icons" ) {
362                         var icons = this.element.find( ".ui-menu-icon" );
363                         this._removeClass( icons, null, this.options.icons.submenu )
364                                 ._addClass( icons, null, value.submenu );
365                 }
366                 this._super( key, value );
367         },
369         _setOptionDisabled: function( value ) {
370                 this._super( value );
372                 this.element.attr( "aria-disabled", String( value ) );
373                 this._toggleClass( null, "ui-state-disabled", !!value );
374         },
376         focus: function( event, item ) {
377                 var nested, focused, activeParent;
378                 this.blur( event, event && event.type === "focus" );
380                 this._scrollIntoView( item );
382                 this.active = item.first();
384                 focused = this.active.children( ".ui-menu-item-wrapper" );
385                 this._addClass( focused, null, "ui-state-active" );
387                 // Only update aria-activedescendant if there's a role
388                 // otherwise we assume focus is managed elsewhere
389                 if ( this.options.role ) {
390                         this.element.attr( "aria-activedescendant", focused.attr( "id" ) );
391                 }
393                 // Highlight active parent menu item, if any
394                 activeParent = this.active
395                         .parent()
396                                 .closest( ".ui-menu-item" )
397                                         .children( ".ui-menu-item-wrapper" );
398                 this._addClass( activeParent, null, "ui-state-active" );
400                 if ( event && event.type === "keydown" ) {
401                         this._close();
402                 } else {
403                         this.timer = this._delay( function() {
404                                 this._close();
405                         }, this.delay );
406                 }
408                 nested = item.children( ".ui-menu" );
409                 if ( nested.length && event && ( /^mouse/.test( event.type ) ) ) {
410                         this._startOpening( nested );
411                 }
412                 this.activeMenu = item.parent();
414                 this._trigger( "focus", event, { item: item } );
415         },
417         _scrollIntoView: function( item ) {
418                 var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
419                 if ( this._hasScroll() ) {
420                         borderTop = parseFloat( $.css( this.activeMenu[ 0 ], "borderTopWidth" ) ) || 0;
421                         paddingTop = parseFloat( $.css( this.activeMenu[ 0 ], "paddingTop" ) ) || 0;
422                         offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;
423                         scroll = this.activeMenu.scrollTop();
424                         elementHeight = this.activeMenu.height();
425                         itemHeight = item.outerHeight();
427                         if ( offset < 0 ) {
428                                 this.activeMenu.scrollTop( scroll + offset );
429                         } else if ( offset + itemHeight > elementHeight ) {
430                                 this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
431                         }
432                 }
433         },
435         blur: function( event, fromFocus ) {
436                 if ( !fromFocus ) {
437                         clearTimeout( this.timer );
438                 }
440                 if ( !this.active ) {
441                         return;
442                 }
444                 this._removeClass( this.active.children( ".ui-menu-item-wrapper" ),
445                         null, "ui-state-active" );
447                 this._trigger( "blur", event, { item: this.active } );
448                 this.active = null;
449         },
451         _startOpening: function( submenu ) {
452                 clearTimeout( this.timer );
454                 // Don't open if already open fixes a Firefox bug that caused a .5 pixel
455                 // shift in the submenu position when mousing over the caret icon
456                 if ( submenu.attr( "aria-hidden" ) !== "true" ) {
457                         return;
458                 }
460                 this.timer = this._delay( function() {
461                         this._close();
462                         this._open( submenu );
463                 }, this.delay );
464         },
466         _open: function( submenu ) {
467                 var position = $.extend( {
468                         of: this.active
469                 }, this.options.position );
471                 clearTimeout( this.timer );
472                 this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) )
473                         .hide()
474                         .attr( "aria-hidden", "true" );
476                 submenu
477                         .show()
478                         .removeAttr( "aria-hidden" )
479                         .attr( "aria-expanded", "true" )
480                         .position( position );
481         },
483         collapseAll: function( event, all ) {
484                 clearTimeout( this.timer );
485                 this.timer = this._delay( function() {
487                         // If we were passed an event, look for the submenu that contains the event
488                         var currentMenu = all ? this.element :
489                                 $( event && event.target ).closest( this.element.find( ".ui-menu" ) );
491                         // If we found no valid submenu ancestor, use the main menu to close all
492                         // sub menus anyway
493                         if ( !currentMenu.length ) {
494                                 currentMenu = this.element;
495                         }
497                         this._close( currentMenu );
499                         this.blur( event );
501                         // Work around active item staying active after menu is blurred
502                         this._removeClass( currentMenu.find( ".ui-state-active" ), null, "ui-state-active" );
504                         this.activeMenu = currentMenu;
505                 }, this.delay );
506         },
508         // With no arguments, closes the currently active menu - if nothing is active
509         // it closes all menus.  If passed an argument, it will search for menus BELOW
510         _close: function( startMenu ) {
511                 if ( !startMenu ) {
512                         startMenu = this.active ? this.active.parent() : this.element;
513                 }
515                 startMenu.find( ".ui-menu" )
516                         .hide()
517                         .attr( "aria-hidden", "true" )
518                         .attr( "aria-expanded", "false" );
519         },
521         _closeOnDocumentClick: function( event ) {
522                 return !$( event.target ).closest( ".ui-menu" ).length;
523         },
525         _isDivider: function( item ) {
527                 // Match hyphen, em dash, en dash
528                 return !/[^\-\u2014\u2013\s]/.test( item.text() );
529         },
531         collapse: function( event ) {
532                 var newItem = this.active &&
533                         this.active.parent().closest( ".ui-menu-item", this.element );
534                 if ( newItem && newItem.length ) {
535                         this._close();
536                         this.focus( event, newItem );
537                 }
538         },
540         expand: function( event ) {
541                 var newItem = this.active &&
542                         this.active
543                                 .children( ".ui-menu " )
544                                         .find( this.options.items )
545                                                 .first();
547                 if ( newItem && newItem.length ) {
548                         this._open( newItem.parent() );
550                         // Delay so Firefox will not hide activedescendant change in expanding submenu from AT
551                         this._delay( function() {
552                                 this.focus( event, newItem );
553                         } );
554                 }
555         },
557         next: function( event ) {
558                 this._move( "next", "first", event );
559         },
561         previous: function( event ) {
562                 this._move( "prev", "last", event );
563         },
565         isFirstItem: function() {
566                 return this.active && !this.active.prevAll( ".ui-menu-item" ).length;
567         },
569         isLastItem: function() {
570                 return this.active && !this.active.nextAll( ".ui-menu-item" ).length;
571         },
573         _move: function( direction, filter, event ) {
574                 var next;
575                 if ( this.active ) {
576                         if ( direction === "first" || direction === "last" ) {
577                                 next = this.active
578                                         [ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" )
579                                         .eq( -1 );
580                         } else {
581                                 next = this.active
582                                         [ direction + "All" ]( ".ui-menu-item" )
583                                         .eq( 0 );
584                         }
585                 }
586                 if ( !next || !next.length || !this.active ) {
587                         next = this.activeMenu.find( this.options.items )[ filter ]();
588                 }
590                 this.focus( event, next );
591         },
593         nextPage: function( event ) {
594                 var item, base, height;
596                 if ( !this.active ) {
597                         this.next( event );
598                         return;
599                 }
600                 if ( this.isLastItem() ) {
601                         return;
602                 }
603                 if ( this._hasScroll() ) {
604                         base = this.active.offset().top;
605                         height = this.element.height();
606                         this.active.nextAll( ".ui-menu-item" ).each( function() {
607                                 item = $( this );
608                                 return item.offset().top - base - height < 0;
609                         } );
611                         this.focus( event, item );
612                 } else {
613                         this.focus( event, this.activeMenu.find( this.options.items )
614                                 [ !this.active ? "first" : "last" ]() );
615                 }
616         },
618         previousPage: function( event ) {
619                 var item, base, height;
620                 if ( !this.active ) {
621                         this.next( event );
622                         return;
623                 }
624                 if ( this.isFirstItem() ) {
625                         return;
626                 }
627                 if ( this._hasScroll() ) {
628                         base = this.active.offset().top;
629                         height = this.element.height();
630                         this.active.prevAll( ".ui-menu-item" ).each( function() {
631                                 item = $( this );
632                                 return item.offset().top - base + height > 0;
633                         } );
635                         this.focus( event, item );
636                 } else {
637                         this.focus( event, this.activeMenu.find( this.options.items ).first() );
638                 }
639         },
641         _hasScroll: function() {
642                 return this.element.outerHeight() < this.element.prop( "scrollHeight" );
643         },
645         select: function( event ) {
647                 // TODO: It should never be possible to not have an active item at this
648                 // point, but the tests don't trigger mouseenter before click.
649                 this.active = this.active || $( event.target ).closest( ".ui-menu-item" );
650                 var ui = { item: this.active };
651                 if ( !this.active.has( ".ui-menu" ).length ) {
652                         this.collapseAll( event, true );
653                 }
654                 this._trigger( "select", event, ui );
655         },
657         _filterMenuItems: function( character ) {
658                 var escapedCharacter = character.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ),
659                         regex = new RegExp( "^" + escapedCharacter, "i" );
661                 return this.activeMenu
662                         .find( this.options.items )
664                                 // Only match on items, not dividers or other content (#10571)
665                                 .filter( ".ui-menu-item" )
666                                         .filter( function() {
667                                                 return regex.test(
668                                                         $.trim( $( this ).children( ".ui-menu-item-wrapper" ).text() ) );
669                                         } );
670         }
671 } );
673 } ) );