You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

619 lines
16 KiB

1 year ago
  1. /*!
  2. * jQuery UI Accordion 1.13.2
  3. * http://jqueryui.com
  4. *
  5. * Copyright jQuery Foundation and other contributors
  6. * Released under the MIT license.
  7. * http://jquery.org/license
  8. */
  9. //>>label: Accordion
  10. //>>group: Widgets
  11. /* eslint-disable max-len */
  12. //>>description: Displays collapsible content panels for presenting information in a limited amount of space.
  13. /* eslint-enable max-len */
  14. //>>docs: http://api.jqueryui.com/accordion/
  15. //>>demos: http://jqueryui.com/accordion/
  16. //>>css.structure: ../../themes/base/core.css
  17. //>>css.structure: ../../themes/base/accordion.css
  18. //>>css.theme: ../../themes/base/theme.css
  19. ( function( factory ) {
  20. "use strict";
  21. if ( typeof define === "function" && define.amd ) {
  22. // AMD. Register as an anonymous module.
  23. define( [
  24. "jquery",
  25. "./core"
  26. ], factory );
  27. } else {
  28. // Browser globals
  29. factory( jQuery );
  30. }
  31. } )( function( $ ) {
  32. "use strict";
  33. return $.widget( "ui.accordion", {
  34. version: "1.13.2",
  35. options: {
  36. active: 0,
  37. animate: {},
  38. classes: {
  39. "ui-accordion-header": "ui-corner-top",
  40. "ui-accordion-header-collapsed": "ui-corner-all",
  41. "ui-accordion-content": "ui-corner-bottom"
  42. },
  43. collapsible: false,
  44. event: "click",
  45. header: function( elem ) {
  46. return elem.find( "> li > :first-child" ).add( elem.find( "> :not(li)" ).even() );
  47. },
  48. heightStyle: "auto",
  49. icons: {
  50. activeHeader: "ui-icon-triangle-1-s",
  51. header: "ui-icon-triangle-1-e"
  52. },
  53. // Callbacks
  54. activate: null,
  55. beforeActivate: null
  56. },
  57. hideProps: {
  58. borderTopWidth: "hide",
  59. borderBottomWidth: "hide",
  60. paddingTop: "hide",
  61. paddingBottom: "hide",
  62. height: "hide"
  63. },
  64. showProps: {
  65. borderTopWidth: "show",
  66. borderBottomWidth: "show",
  67. paddingTop: "show",
  68. paddingBottom: "show",
  69. height: "show"
  70. },
  71. _create: function() {
  72. var options = this.options;
  73. this.prevShow = this.prevHide = $();
  74. this._addClass( "ui-accordion", "ui-widget ui-helper-reset" );
  75. this.element.attr( "role", "tablist" );
  76. // Don't allow collapsible: false and active: false / null
  77. if ( !options.collapsible && ( options.active === false || options.active == null ) ) {
  78. options.active = 0;
  79. }
  80. this._processPanels();
  81. // handle negative values
  82. if ( options.active < 0 ) {
  83. options.active += this.headers.length;
  84. }
  85. this._refresh();
  86. },
  87. _getCreateEventData: function() {
  88. return {
  89. header: this.active,
  90. panel: !this.active.length ? $() : this.active.next()
  91. };
  92. },
  93. _createIcons: function() {
  94. var icon, children,
  95. icons = this.options.icons;
  96. if ( icons ) {
  97. icon = $( "<span>" );
  98. this._addClass( icon, "ui-accordion-header-icon", "ui-icon " + icons.header );
  99. icon.prependTo( this.headers );
  100. children = this.active.children( ".ui-accordion-header-icon" );
  101. this._removeClass( children, icons.header )
  102. ._addClass( children, null, icons.activeHeader )
  103. ._addClass( this.headers, "ui-accordion-icons" );
  104. }
  105. },
  106. _destroyIcons: function() {
  107. this._removeClass( this.headers, "ui-accordion-icons" );
  108. this.headers.children( ".ui-accordion-header-icon" ).remove();
  109. },
  110. _destroy: function() {
  111. var contents;
  112. // Clean up main element
  113. this.element.removeAttr( "role" );
  114. // Clean up headers
  115. this.headers
  116. .removeAttr( "role aria-expanded aria-selected aria-controls tabIndex" )
  117. .removeUniqueId();
  118. this._destroyIcons();
  119. // Clean up content panels
  120. contents = this.headers.next()
  121. .css( "display", "" )
  122. .removeAttr( "role aria-hidden aria-labelledby" )
  123. .removeUniqueId();
  124. if ( this.options.heightStyle !== "content" ) {
  125. contents.css( "height", "" );
  126. }
  127. },
  128. _setOption: function( key, value ) {
  129. if ( key === "active" ) {
  130. // _activate() will handle invalid values and update this.options
  131. this._activate( value );
  132. return;
  133. }
  134. if ( key === "event" ) {
  135. if ( this.options.event ) {
  136. this._off( this.headers, this.options.event );
  137. }
  138. this._setupEvents( value );
  139. }
  140. this._super( key, value );
  141. // Setting collapsible: false while collapsed; open first panel
  142. if ( key === "collapsible" && !value && this.options.active === false ) {
  143. this._activate( 0 );
  144. }
  145. if ( key === "icons" ) {
  146. this._destroyIcons();
  147. if ( value ) {
  148. this._createIcons();
  149. }
  150. }
  151. },
  152. _setOptionDisabled: function( value ) {
  153. this._super( value );
  154. this.element.attr( "aria-disabled", value );
  155. // Support: IE8 Only
  156. // #5332 / #6059 - opacity doesn't cascade to positioned elements in IE
  157. // so we need to add the disabled class to the headers and panels
  158. this._toggleClass( null, "ui-state-disabled", !!value );
  159. this._toggleClass( this.headers.add( this.headers.next() ), null, "ui-state-disabled",
  160. !!value );
  161. },
  162. _keydown: function( event ) {
  163. if ( event.altKey || event.ctrlKey ) {
  164. return;
  165. }
  166. var keyCode = $.ui.keyCode,
  167. length = this.headers.length,
  168. currentIndex = this.headers.index( event.target ),
  169. toFocus = false;
  170. switch ( event.keyCode ) {
  171. case keyCode.RIGHT:
  172. case keyCode.DOWN:
  173. toFocus = this.headers[ ( currentIndex + 1 ) % length ];
  174. break;
  175. case keyCode.LEFT:
  176. case keyCode.UP:
  177. toFocus = this.headers[ ( currentIndex - 1 + length ) % length ];
  178. break;
  179. case keyCode.SPACE:
  180. case keyCode.ENTER:
  181. this._eventHandler( event );
  182. break;
  183. case keyCode.HOME:
  184. toFocus = this.headers[ 0 ];
  185. break;
  186. case keyCode.END:
  187. toFocus = this.headers[ length - 1 ];
  188. break;
  189. }
  190. if ( toFocus ) {
  191. $( event.target ).attr( "tabIndex", -1 );
  192. $( toFocus ).attr( "tabIndex", 0 );
  193. $( toFocus ).trigger( "focus" );
  194. event.preventDefault();
  195. }
  196. },
  197. _panelKeyDown: function( event ) {
  198. if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) {
  199. $( event.currentTarget ).prev().trigger( "focus" );
  200. }
  201. },
  202. refresh: function() {
  203. var options = this.options;
  204. this._processPanels();
  205. // Was collapsed or no panel
  206. if ( ( options.active === false && options.collapsible === true ) ||
  207. !this.headers.length ) {
  208. options.active = false;
  209. this.active = $();
  210. // active false only when collapsible is true
  211. } else if ( options.active === false ) {
  212. this._activate( 0 );
  213. // was active, but active panel is gone
  214. } else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
  215. // all remaining panel are disabled
  216. if ( this.headers.length === this.headers.find( ".ui-state-disabled" ).length ) {
  217. options.active = false;
  218. this.active = $();
  219. // activate previous panel
  220. } else {
  221. this._activate( Math.max( 0, options.active - 1 ) );
  222. }
  223. // was active, active panel still exists
  224. } else {
  225. // make sure active index is correct
  226. options.active = this.headers.index( this.active );
  227. }
  228. this._destroyIcons();
  229. this._refresh();
  230. },
  231. _processPanels: function() {
  232. var prevHeaders = this.headers,
  233. prevPanels = this.panels;
  234. if ( typeof this.options.header === "function" ) {
  235. this.headers = this.options.header( this.element );
  236. } else {
  237. this.headers = this.element.find( this.options.header );
  238. }
  239. this._addClass( this.headers, "ui-accordion-header ui-accordion-header-collapsed",
  240. "ui-state-default" );
  241. this.panels = this.headers.next().filter( ":not(.ui-accordion-content-active)" ).hide();
  242. this._addClass( this.panels, "ui-accordion-content", "ui-helper-reset ui-widget-content" );
  243. // Avoid memory leaks (#10056)
  244. if ( prevPanels ) {
  245. this._off( prevHeaders.not( this.headers ) );
  246. this._off( prevPanels.not( this.panels ) );
  247. }
  248. },
  249. _refresh: function() {
  250. var maxHeight,
  251. options = this.options,
  252. heightStyle = options.heightStyle,
  253. parent = this.element.parent();
  254. this.active = this._findActive( options.active );
  255. this._addClass( this.active, "ui-accordion-header-active", "ui-state-active" )
  256. ._removeClass( this.active, "ui-accordion-header-collapsed" );
  257. this._addClass( this.active.next(), "ui-accordion-content-active" );
  258. this.active.next().show();
  259. this.headers
  260. .attr( "role", "tab" )
  261. .each( function() {
  262. var header = $( this ),
  263. headerId = header.uniqueId().attr( "id" ),
  264. panel = header.next(),
  265. panelId = panel.uniqueId().attr( "id" );
  266. header.attr( "aria-controls", panelId );
  267. panel.attr( "aria-labelledby", headerId );
  268. } )
  269. .next()
  270. .attr( "role", "tabpanel" );
  271. this.headers
  272. .not( this.active )
  273. .attr( {
  274. "aria-selected": "false",
  275. "aria-expanded": "false",
  276. tabIndex: -1
  277. } )
  278. .next()
  279. .attr( {
  280. "aria-hidden": "true"
  281. } )
  282. .hide();
  283. // Make sure at least one header is in the tab order
  284. if ( !this.active.length ) {
  285. this.headers.eq( 0 ).attr( "tabIndex", 0 );
  286. } else {
  287. this.active.attr( {
  288. "aria-selected": "true",
  289. "aria-expanded": "true",
  290. tabIndex: 0
  291. } )
  292. .next()
  293. .attr( {
  294. "aria-hidden": "false"
  295. } );
  296. }
  297. this._createIcons();
  298. this._setupEvents( options.event );
  299. if ( heightStyle === "fill" ) {
  300. maxHeight = parent.height();
  301. this.element.siblings( ":visible" ).each( function() {
  302. var elem = $( this ),
  303. position = elem.css( "position" );
  304. if ( position === "absolute" || position === "fixed" ) {
  305. return;
  306. }
  307. maxHeight -= elem.outerHeight( true );
  308. } );
  309. this.headers.each( function() {
  310. maxHeight -= $( this ).outerHeight( true );
  311. } );
  312. this.headers.next()
  313. .each( function() {
  314. $( this ).height( Math.max( 0, maxHeight -
  315. $( this ).innerHeight() + $( this ).height() ) );
  316. } )
  317. .css( "overflow", "auto" );
  318. } else if ( heightStyle === "auto" ) {
  319. maxHeight = 0;
  320. this.headers.next()
  321. .each( function() {
  322. var isVisible = $( this ).is( ":visible" );
  323. if ( !isVisible ) {
  324. $( this ).show();
  325. }
  326. maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() );
  327. if ( !isVisible ) {
  328. $( this ).hide();
  329. }
  330. } )
  331. .height( maxHeight );
  332. }
  333. },
  334. _activate: function( index ) {
  335. var active = this._findActive( index )[ 0 ];
  336. // Trying to activate the already active panel
  337. if ( active === this.active[ 0 ] ) {
  338. return;
  339. }
  340. // Trying to collapse, simulate a click on the currently active header
  341. active = active || this.active[ 0 ];
  342. this._eventHandler( {
  343. target: active,
  344. currentTarget: active,
  345. preventDefault: $.noop
  346. } );
  347. },
  348. _findActive: function( selector ) {
  349. return typeof selector === "number" ? this.headers.eq( selector ) : $();
  350. },
  351. _setupEvents: function( event ) {
  352. var events = {
  353. keydown: "_keydown"
  354. };
  355. if ( event ) {
  356. $.each( event.split( " " ), function( index, eventName ) {
  357. events[ eventName ] = "_eventHandler";
  358. } );
  359. }
  360. this._off( this.headers.add( this.headers.next() ) );
  361. this._on( this.headers, events );
  362. this._on( this.headers.next(), { keydown: "_panelKeyDown" } );
  363. this._hoverable( this.headers );
  364. this._focusable( this.headers );
  365. },
  366. _eventHandler: function( event ) {
  367. var activeChildren, clickedChildren,
  368. options = this.options,
  369. active = this.active,
  370. clicked = $( event.currentTarget ),
  371. clickedIsActive = clicked[ 0 ] === active[ 0 ],
  372. collapsing = clickedIsActive && options.collapsible,
  373. toShow = collapsing ? $() : clicked.next(),
  374. toHide = active.next(),
  375. eventData = {
  376. oldHeader: active,
  377. oldPanel: toHide,
  378. newHeader: collapsing ? $() : clicked,
  379. newPanel: toShow
  380. };
  381. event.preventDefault();
  382. if (
  383. // click on active header, but not collapsible
  384. ( clickedIsActive && !options.collapsible ) ||
  385. // allow canceling activation
  386. ( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
  387. return;
  388. }
  389. options.active = collapsing ? false : this.headers.index( clicked );
  390. // When the call to ._toggle() comes after the class changes
  391. // it causes a very odd bug in IE 8 (see #6720)
  392. this.active = clickedIsActive ? $() : clicked;
  393. this._toggle( eventData );
  394. // Switch classes
  395. // corner classes on the previously active header stay after the animation
  396. this._removeClass( active, "ui-accordion-header-active", "ui-state-active" );
  397. if ( options.icons ) {
  398. activeChildren = active.children( ".ui-accordion-header-icon" );
  399. this._removeClass( activeChildren, null, options.icons.activeHeader )
  400. ._addClass( activeChildren, null, options.icons.header );
  401. }
  402. if ( !clickedIsActive ) {
  403. this._removeClass( clicked, "ui-accordion-header-collapsed" )
  404. ._addClass( clicked, "ui-accordion-header-active", "ui-state-active" );
  405. if ( options.icons ) {
  406. clickedChildren = clicked.children( ".ui-accordion-header-icon" );
  407. this._removeClass( clickedChildren, null, options.icons.header )
  408. ._addClass( clickedChildren, null, options.icons.activeHeader );
  409. }
  410. this._addClass( clicked.next(), "ui-accordion-content-active" );
  411. }
  412. },
  413. _toggle: function( data ) {
  414. var toShow = data.newPanel,
  415. toHide = this.prevShow.length ? this.prevShow : data.oldPanel;
  416. // Handle activating a panel during the animation for another activation
  417. this.prevShow.add( this.prevHide ).stop( true, true );
  418. this.prevShow = toShow;
  419. this.prevHide = toHide;
  420. if ( this.options.animate ) {
  421. this._animate( toShow, toHide, data );
  422. } else {
  423. toHide.hide();
  424. toShow.show();
  425. this._toggleComplete( data );
  426. }
  427. toHide.attr( {
  428. "aria-hidden": "true"
  429. } );
  430. toHide.prev().attr( {
  431. "aria-selected": "false",
  432. "aria-expanded": "false"
  433. } );
  434. // if we're switching panels, remove the old header from the tab order
  435. // if we're opening from collapsed state, remove the previous header from the tab order
  436. // if we're collapsing, then keep the collapsing header in the tab order
  437. if ( toShow.length && toHide.length ) {
  438. toHide.prev().attr( {
  439. "tabIndex": -1,
  440. "aria-expanded": "false"
  441. } );
  442. } else if ( toShow.length ) {
  443. this.headers.filter( function() {
  444. return parseInt( $( this ).attr( "tabIndex" ), 10 ) === 0;
  445. } )
  446. .attr( "tabIndex", -1 );
  447. }
  448. toShow
  449. .attr( "aria-hidden", "false" )
  450. .prev()
  451. .attr( {
  452. "aria-selected": "true",
  453. "aria-expanded": "true",
  454. tabIndex: 0
  455. } );
  456. },
  457. _animate: function( toShow, toHide, data ) {
  458. var total, easing, duration,
  459. that = this,
  460. adjust = 0,
  461. boxSizing = toShow.css( "box-sizing" ),
  462. down = toShow.length &&
  463. ( !toHide.length || ( toShow.index() < toHide.index() ) ),
  464. animate = this.options.animate || {},
  465. options = down && animate.down || animate,
  466. complete = function() {
  467. that._toggleComplete( data );
  468. };
  469. if ( typeof options === "number" ) {
  470. duration = options;
  471. }
  472. if ( typeof options === "string" ) {
  473. easing = options;
  474. }
  475. // fall back from options to animation in case of partial down settings
  476. easing = easing || options.easing || animate.easing;
  477. duration = duration || options.duration || animate.duration;
  478. if ( !toHide.length ) {
  479. return toShow.animate( this.showProps, duration, easing, complete );
  480. }
  481. if ( !toShow.length ) {
  482. return toHide.animate( this.hideProps, duration, easing, complete );
  483. }
  484. total = toShow.show().outerHeight();
  485. toHide.animate( this.hideProps, {
  486. duration: duration,
  487. easing: easing,
  488. step: function( now, fx ) {
  489. fx.now = Math.round( now );
  490. }
  491. } );
  492. toShow
  493. .hide()
  494. .animate( this.showProps, {
  495. duration: duration,
  496. easing: easing,
  497. complete: complete,
  498. step: function( now, fx ) {
  499. fx.now = Math.round( now );
  500. if ( fx.prop !== "height" ) {
  501. if ( boxSizing === "content-box" ) {
  502. adjust += fx.now;
  503. }
  504. } else if ( that.options.heightStyle !== "content" ) {
  505. fx.now = Math.round( total - toHide.outerHeight() - adjust );
  506. adjust = 0;
  507. }
  508. }
  509. } );
  510. },
  511. _toggleComplete: function( data ) {
  512. var toHide = data.oldPanel,
  513. prev = toHide.prev();
  514. this._removeClass( toHide, "ui-accordion-content-active" );
  515. this._removeClass( prev, "ui-accordion-header-active" )
  516. ._addClass( prev, "ui-accordion-header-collapsed" );
  517. // Work around for rendering bug in IE (#5421)
  518. if ( toHide.length ) {
  519. toHide.parent()[ 0 ].className = toHide.parent()[ 0 ].className;
  520. }
  521. this._trigger( "activate", null, data );
  522. }
  523. } );
  524. } );