미디어위키:Common.js: 두 판 사이의 차이

편집 요약 없음
편집 요약 없음
2번째 줄: 2번째 줄:
  * MediaWiki 커스텀 펼치기 / 접기 애니메이션
  * MediaWiki 커스텀 펼치기 / 접기 애니메이션
  *
  *
  * 지원 구조:
  * 지원:
*
* 1. 일반 MediaWiki 구조
  *
  *
* 1.
  * .mw-customtoggle-XXX
  * .mw-customtoggle-XXX
  * #mw-customcollapsible-XXX
  * #mw-customcollapsible-XXX
  *     └─ .mw-collapsible-content
  * └─ .mw-collapsible-content
*
*
* 2. 동일 ID를 2개 사용하는 전환형 둘러보기 틀
*
* #mw-customcollapsible-XXX  ← 처음 보이는 버튼
* └─ .mw-customtoggle-XXX
*
* #mw-customcollapsible-XXX  ← 처음 숨겨진 실제 내용
* ├─ .mw-customtoggle-XXX
* └─ table / .sakura-table-wrapper
  *
  *
* 2.
* .mw-customtoggle-XXX
* #mw-customcollapsible-XXX
*    ├─ .mw-customtoggle-XXX
*    └─ table / .sakura-table-wrapper / 기타 콘텐츠
  *
  *
  * 동일한 mw-customcollapsible-* ID가 여러 개 존재하는
  * 클릭 시 같은 XXX를 가진 모든 collapsible을
  * 둘러보기 틀 형태도 지원.
  * 각각 현재 상태의 반대로 전환합니다.
  */
  */


24번째 줄: 30번째 줄:
var DURATION = 300;
var DURATION = 300;
var EASING = 'swing';
var EASING = 'swing';
var DATA_TARGET =
'mwAnimatedCollapseTarget';
var DATA_DISPLAY =
'mwAnimatedNaturalDisplay';


/**
/**
* 토글 클래스에서 이름 추출
* 토글 이름 추출
*
*
* mw-customtoggle-Flora_LACTEA_01
* mw-customtoggle-Flora_LACTEA_01
59번째 줄: 58번째 줄:


/**
/**
* CSS attribute selector escape
* attribute selector escape
*/
*/
function escapeAttribute( value ) {
function escapeAttribute( value ) {
69번째 줄: 68번째 줄:


/**
/**
* 해당 요소의 직계 mw-collapsible-content 찾기
* 동일한 이름을 사용하는
* 모든 collapsible 대상 가져오기
*/
function getTargets( name ) {
var id =
'mw-customcollapsible-' + name;
 
return document.querySelectorAll(
'[id="' +
escapeAttribute( id ) +
'"]'
);
}
 
 
/**
* 직계 .mw-collapsible-content 찾기
*
*
* 중첩된 collapsible을 잘못 잡지 않도록
* 중첩 collapsible까지 잘못 선택하지 않도록
* querySelector가 아니라 children 검사
* children만 검사합니다.
*/
*/
function getDirectCollapsibleContent( target ) {
function getDirectContent( target ) {
var children = target.children;
var children = target.children;
var i;
var i;
93번째 줄: 108번째 줄:


/**
/**
* 실제로 펼치고 접을 요소 결정
* 실제 애니메이션 대상
*
*
* .mw-collapsible-content가 있으면 그것을 사용.
* MediaWiki 표준:
* .mw-collapsible-content
*
*
* 없으면 target 자체가 display:none 되는 구조이므로
* 현재 LACTEA 구조:
* target 자체를 애니메이션.
* target 자체
*/
*/
function getAnimatedElement( target ) {
function getAnimatedElement( target ) {
var content =
var content =
getDirectCollapsibleContent( target );
getDirectContent( target );


if ( content ) {
if ( content ) {
113번째 줄: 129번째 줄:


/**
/**
* candidate가 단순한 버튼 wrapper인지 판단
* 현재 접혀 있는지 확인
*/
*/
function isToggleOnlyWrapper( candidate ) {
function isCollapsed( target, element ) {
var children = candidate.children;


if ( children.length !== 1 ) {
if (
return false;
target.classList.contains(
'mw-collapsed'
)
) {
return true;
}
}


return !!getToggleName(
return (
children[ 0 ]
window.getComputedStyle(
element
).display === 'none'
);
);
}
}
129번째 줄: 150번째 줄:


/**
/**
* 동일한 ID가 여러 개 있어도 실제 펼침 영역 선택
* 펼칠 때 사용할 display 계산
*/
*/
function findTarget( name ) {
function getOpenDisplay( element ) {
if ( !name ) {
return null;
}
 
var id =
'mw-customcollapsible-' + name;
 
var candidates =
document.querySelectorAll(
'[id="' +
escapeAttribute( id ) +
'"]'
);
 
if ( !candidates.length ) {
return null;
}
 


/*
/*
* 이전에 실제 target으로 판정한 요소 우선
* 이전에 저장된 display가 있으면 사용
*/
*/
var i;
var stored =
 
for ( i = 0; i < candidates.length; i++ ) {
if (
$( candidates[ i ] ).data(
DATA_TARGET
)
) {
return candidates[ i ];
}
}
 
 
if ( candidates.length === 1 ) {
$( candidates[ 0 ] ).data(
DATA_TARGET,
true
);
 
return candidates[ 0 ];
}
 
 
var best = null;
var bestScore = -Infinity;
 
 
for ( i = 0; i < candidates.length; i++ ) {
var candidate = candidates[ i ];
var score = 0;
 
 
/*
* 초기 상태가 mw-collapsed라면
* 실제 내용 영역일 가능성이 매우 높음
*/
if (
candidate.classList.contains(
'mw-collapsed'
)
) {
score += 10000;
}
 
 
/*
* MediaWiki 표준 content wrapper
*/
if (
getDirectCollapsibleContent(
candidate
)
) {
score += 8000;
}
 
 
/*
* Sakura 표 wrapper
*/
if (
candidate.querySelector(
':scope > .sakura-table-wrapper'
)
) {
score += 6000;
}
 
 
/*
* 실제 표 포함
*/
if (
candidate.querySelector(
'table'
)
) {
score += 3000;
}
 
 
if (
candidate.querySelector(
'.wikitable'
)
) {
score += 1000;
}
 
 
/*
* 버튼 하나만 들어간 wrapper는 감점
*/
if (
isToggleOnlyWrapper(
candidate
)
) {
score -= 10000;
}
 
 
/*
* 내부 콘텐츠가 많을수록 가산
*/
score +=
candidate.querySelectorAll( '*' )
.length;
 
 
if ( score > bestScore ) {
bestScore = score;
best = candidate;
}
}
 
 
if ( best ) {
$( best ).data(
DATA_TARGET,
true
);
}
 
 
return best;
}
 
 
/**
* 자연스러운 display 값 계산
*
* 현재 display:none인 경우 직접 태그별 기본값 사용
*/
function getNaturalDisplay( element ) {
 
var saved =
$( element ).data(
$( element ).data(
DATA_DISPLAY
'mwAnimatedDisplay'
);
);


if ( saved ) {
if ( stored ) {
return saved;
return stored;
}
}


306번째 줄: 173번째 줄:




/*
* 현재 보이는 상태라면 저장
*/
if (
if (
display &&
display &&
display !== 'none'
display !== 'none'
) {
) {
$( element ).data(
$( element ).data(
DATA_DISPLAY,
'mwAnimatedDisplay',
display
display
);
);
319번째 줄: 190번째 줄:




switch ( element.tagName ) {
/*
* mw-collapsible 자체를 펼치는 구조
*/
if (
element.classList.contains(
'mw-collapsible'
)
) {
 
/*
* width가 100%인 내용 영역은 block이
* 가장 자연스럽습니다.
*/
if (
element.style.width === '100%'
) {
return 'block';
}


case 'TABLE':
return 'inline-block';
display = 'table';
}
break;


case 'TBODY':
display = 'table-row-group';
break;


case 'THEAD':
switch ( element.tagName ) {
display = 'table-header-group';
break;


case 'TFOOT':
case 'TABLE':
display = 'table-footer-group';
return 'table';
break;


case 'TR':
case 'TR':
display = 'table-row';
return 'table-row';
break;


case 'TD':
case 'TD':
case 'TH':
case 'TH':
display = 'table-cell';
return 'table-cell';
break;


case 'LI':
case 'LI':
display = 'list-item';
return 'list-item';
break;


default:
default:
 
return 'block';
/*
* 인라인 스타일상 inline-block인
* collapsible도 많으므로 먼저 확인
*/
if (
element.style.display &&
element.style.display !== 'none'
) {
display =
element.style.display;
} else {
display = 'block';
}
 
break;
}
}
$( element ).data(
DATA_DISPLAY,
display
);
return display;
}
}




/**
/**
* 현재 접힘 상태
* 현재 display 값을 기억
*
* 닫기 전에 호출해야 함
*/
*/
function isCollapsed(
function rememberDisplay( element ) {
target,
 
animatedElement
var display =
) {
window.getComputedStyle(
element
).display;


if (
if (
target.classList.contains(
display &&
'mw-collapsed'
display !== 'none'
)
) {
) {
return true;
$( element ).data(
'mwAnimatedDisplay',
display
);
}
}
return (
window.getComputedStyle(
animatedElement
).display === 'none'
);
}
}




/**
/**
* 같은 name을 가진 모든 toggle의 aria-expanded 변경
* 같은 name의 모든 토글에
* aria-expanded 설정
*/
*/
function updateToggleState(
function updateToggleAria(
name,
name,
expanded
expanded
) {
) {


var prefix =
var className =
'mw-customtoggle-' + name;
'mw-customtoggle-' + name;


var toggles =
var toggles =
document.getElementsByClassName(
document.getElementsByClassName(
prefix
className
);
);


var i;
var i;


for (
for ( i = 0; i < toggles.length; i++ ) {
i = 0;
i < toggles.length;
i++
) {
toggles[ i ].setAttribute(
toggles[ i ].setAttribute(
'aria-expanded',
'aria-expanded',
expanded ? 'true' : 'false'
expanded ?
'true' :
'false'
);
);
}
}
438번째 줄: 292번째 줄:
*/
*/
function expand(
function expand(
name,
target,
target,
animatedElement
element
) {
) {


var $element =
var $element =
$( animatedElement );
$( element );




/*
* 기존 애니메이션 즉시 중단
*/
$element.stop(
$element.stop(
true,
true,
462번째 줄: 312번째 줄:


/*
/*
* target 자체가 애니메이션 대상인 경우
* 표준 MediaWiki 방식
*
*
* 기존 inline style="display:none"을
* target 안의
* slideDown이 처리할 수 있도록 복구
* .mw-collapsible-content만 처리
*/
*/
if (
if ( element !== target ) {
animatedElement === target
) {


var naturalDisplay =
$element
getNaturalDisplay(
.stop( true, false )
target
.slideDown(
DURATION
);
);


/*
return;
* jQuery slideDown은 inline-block 같은
}
* display 복원에서 상황에 따라 문제가 있으므로
 
* display를 직접 설정한 다음 height를 애니메이션
 
*/
/*
$element.css(
* LACTEA 같은 구조
'display',
*
naturalDisplay
* mw-collapsible 자체가 display:none이므로
);
* 직접 높이를 애니메이션
*/


$element.css(
var display =
{
getOpenDisplay(
overflow: 'hidden',
element
height: 0,
opacity: 0
}
);
);




var targetHeight =
$element.css(
animatedElement.scrollHeight;
{
display: display,
height: 'auto',
overflow: 'hidden'
}
);




$element.animate(
var targetHeight =
{
element.scrollHeight;
height:
targetHeight,
opacity: 1
},
{
duration:
DURATION,


easing:
EASING,


complete:
$element.css(
function () {
{
height: 0,
opacity: 0
}
);


$element.css(
{
height: '',
overflow: '',
opacity: ''
}
);


/*
* reflow
*/
void element.offsetHeight;


updateToggleState(
name,
true
);
}
}
);


} else {
$element.animate(
{
height:
targetHeight,


/*
opacity:
* 표준 MediaWiki 구조
1
*
},
* mw-collapsible-content만 펼침
{
*/
duration:
$element
.stop( true, true )
.slideDown(
DURATION,
DURATION,
easing:
EASING,
complete:
function () {
function () {


548번째 줄: 390번째 줄:
{
{
height: '',
height: '',
overflow: ''
overflow: '',
opacity: ''
}
}
);
updateToggleState(
name,
true
);
);
}
}
);
}
}
);
}
}


567번째 줄: 404번째 줄:
*/
*/
function collapse(
function collapse(
name,
target,
target,
animatedElement
element
) {
) {


var $element =
var $element =
$( animatedElement );
$( element );




583번째 줄: 419번째 줄:


/*
/*
* target 자체가 display:none 되는 구조
* 현재 display를 반드시 먼저 기억
*/
*/
if (
rememberDisplay(
animatedElement === target
element
) {
);
 


var startHeight =
/*
animatedElement
* 표준 MediaWiki 구조
.getBoundingClientRect()
*/
.height;
if ( element !== target ) {


$element.slideUp(
DURATION,
function () {


$element.css(
target.classList.add(
{
'mw-collapsed'
height:
);
startHeight,
overflow:
'hidden',
opacity:
1
}
}
);
);


return;
}


$element.animate(
{
height: 0,
opacity: 0
},
{
duration:
DURATION,


easing:
/*
EASING,
* mw-collapsible 자체가 접히는 구조
*/


complete:
var startHeight =
function () {
element
.getBoundingClientRect()
.height;


target.classList.add(
'mw-collapsed'
);


$element.css(
{
height:
startHeight,


$element.css(
overflow:
{
'hidden',
display:
'none',
height:
'',
overflow:
'',
opacity:
''
}
);


opacity:
1
}
);


updateToggleState(
name,
false
);
}
}
);


} else {
/*
* reflow
*/
void element.offsetHeight;
 
 
$element.animate(
{
height: 0,
opacity: 0
},
{
duration:
DURATION,
 
easing:
EASING,
 
complete:
function () {
 
target.classList.add(
'mw-collapsed'
);
 


/*
$element.css(
* 표준 mw-collapsible-content
{
*/
display:
$element.slideUp(
'none',
DURATION,
function () {


target.classList.add(
height:
'mw-collapsed'
'',
);


overflow:
'',


updateToggleState(
opacity:
name,
''
false
}
);
);
}
}
);
}
}
);
}
}




/**
/**
* 토글 실행
* 하나의 name에 연결된 대상 전체 토글
*/
*/
function toggleCollapsible(
function toggleGroup( toggle ) {
toggle
) {


var name =
var name =
690번째 줄: 531번째 줄:




var target =
var targets =
findTarget(
getTargets(
name
name
);
);


if ( !target ) {
if ( !targets.length ) {
return false;
return false;
}
}




var animatedElement =
var i;
getAnimatedElement(
 
target
/*
);
* 핵심:
*
* 하나만 선택하지 않고
* 같은 이름을 가진 모든 target을
* 각자 반대 상태로 변경
*/
for (
i = 0;
i < targets.length;
i++
) {
 
var target =
targets[ i ];
 
var element =
getAnimatedElement(
target
);




if (
if (
isCollapsed(
isCollapsed(
target,
target,
animatedElement
element
)
)
) {
) {


expand(
expand(
name,
target,
target,
element
animatedElement
);
);


} else {
} else {


collapse(
collapse(
name,
target,
target,
element
animatedElement
);
);
}
}
}
/*
* 이 형태에서는 보이는 토글과 숨겨진 토글이
* 교대하므로 aria-expanded를 특정 하나에
* 일괄 적용하는 것은 생략합니다.
*/




737번째 줄: 602번째 줄:
*
*
* MediaWiki 기본 customtoggle보다
* MediaWiki 기본 customtoggle보다
* 먼저 처리
* 먼저 실행
*/
*/
document.addEventListener(
document.addEventListener(
769번째 줄: 634번째 줄:




toggleCollapsible(
toggleGroup(
toggle
toggle
);
);
818번째 줄: 683번째 줄:




toggleCollapsible(
toggleGroup(
toggle
toggle
);
);
824번째 줄: 689번째 줄:
true
true
);
);


}( jQuery, mediaWiki ) );
}( jQuery, mediaWiki ) );

2026년 8월 20일 (목) 14:08 판

/**
 * MediaWiki 커스텀 펼치기 / 접기 애니메이션
 *
 * 지원:
 *
 * 1. 일반 MediaWiki 구조
 *
 * .mw-customtoggle-XXX
 * #mw-customcollapsible-XXX
 * └─ .mw-collapsible-content
 *
 *
 * 2. 동일 ID를 2개 사용하는 전환형 둘러보기 틀
 *
 * #mw-customcollapsible-XXX   ← 처음 보이는 버튼
 * └─ .mw-customtoggle-XXX
 *
 * #mw-customcollapsible-XXX   ← 처음 숨겨진 실제 내용
 * ├─ .mw-customtoggle-XXX
 * └─ table / .sakura-table-wrapper
 *
 *
 * 클릭 시 같은 XXX를 가진 모든 collapsible을
 * 각각 현재 상태의 반대로 전환합니다.
 */

( function ( $, mw ) {
	'use strict';

	var DURATION = 300;
	var EASING = 'swing';

	/**
	 * 토글 이름 추출
	 *
	 * mw-customtoggle-Flora_LACTEA_01
	 * →
	 * Flora_LACTEA_01
	 */
	function getToggleName( toggle ) {
		var prefix = 'mw-customtoggle-';
		var classes = toggle.classList;
		var i;

		for ( i = 0; i < classes.length; i++ ) {
			if (
				classes[ i ].indexOf( prefix ) === 0
			) {
				return classes[ i ].substring(
					prefix.length
				);
			}
		}

		return null;
	}


	/**
	 * attribute selector escape
	 */
	function escapeAttribute( value ) {
		return String( value )
			.replace( /\\/g, '\\\\' )
			.replace( /"/g, '\\"' );
	}


	/**
	 * 동일한 이름을 사용하는
	 * 모든 collapsible 대상 가져오기
	 */
	function getTargets( name ) {
		var id =
			'mw-customcollapsible-' + name;

		return document.querySelectorAll(
			'[id="' +
			escapeAttribute( id ) +
			'"]'
		);
	}


	/**
	 * 직계 .mw-collapsible-content 찾기
	 *
	 * 중첩 collapsible까지 잘못 선택하지 않도록
	 * children만 검사합니다.
	 */
	function getDirectContent( target ) {
		var children = target.children;
		var i;

		for ( i = 0; i < children.length; i++ ) {
			if (
				children[ i ].classList.contains(
					'mw-collapsible-content'
				)
			) {
				return children[ i ];
			}
		}

		return null;
	}


	/**
	 * 실제 애니메이션 대상
	 *
	 * MediaWiki 표준:
	 * .mw-collapsible-content
	 *
	 * 현재 LACTEA 구조:
	 * target 자체
	 */
	function getAnimatedElement( target ) {
		var content =
			getDirectContent( target );

		if ( content ) {
			return content;
		}

		return target;
	}


	/**
	 * 현재 접혀 있는지 확인
	 */
	function isCollapsed( target, element ) {

		if (
			target.classList.contains(
				'mw-collapsed'
			)
		) {
			return true;
		}

		return (
			window.getComputedStyle(
				element
			).display === 'none'
		);
	}


	/**
	 * 펼칠 때 사용할 display 계산
	 */
	function getOpenDisplay( element ) {

		/*
		 * 이전에 저장된 display가 있으면 사용
		 */
		var stored =
			$( element ).data(
				'mwAnimatedDisplay'
			);

		if ( stored ) {
			return stored;
		}


		var display =
			window.getComputedStyle(
				element
			).display;


		/*
		 * 현재 보이는 상태라면 저장
		 */
		if (
			display &&
			display !== 'none'
		) {

			$( element ).data(
				'mwAnimatedDisplay',
				display
			);

			return display;
		}


		/*
		 * mw-collapsible 자체를 펼치는 구조
		 */
		if (
			element.classList.contains(
				'mw-collapsible'
			)
		) {

			/*
			 * width가 100%인 내용 영역은 block이
			 * 가장 자연스럽습니다.
			 */
			if (
				element.style.width === '100%'
			) {
				return 'block';
			}

			return 'inline-block';
		}


		switch ( element.tagName ) {

			case 'TABLE':
				return 'table';

			case 'TR':
				return 'table-row';

			case 'TD':
			case 'TH':
				return 'table-cell';

			case 'LI':
				return 'list-item';

			default:
				return 'block';
		}
	}


	/**
	 * 현재 display 값을 기억
	 *
	 * 닫기 전에 호출해야 함
	 */
	function rememberDisplay( element ) {

		var display =
			window.getComputedStyle(
				element
			).display;

		if (
			display &&
			display !== 'none'
		) {
			$( element ).data(
				'mwAnimatedDisplay',
				display
			);
		}
	}


	/**
	 * 같은 name의 모든 토글에
	 * aria-expanded 설정
	 */
	function updateToggleAria(
		name,
		expanded
	) {

		var className =
			'mw-customtoggle-' + name;

		var toggles =
			document.getElementsByClassName(
				className
			);

		var i;

		for ( i = 0; i < toggles.length; i++ ) {
			toggles[ i ].setAttribute(
				'aria-expanded',
				expanded ?
					'true' :
					'false'
			);
		}
	}


	/**
	 * 펼치기
	 */
	function expand(
		target,
		element
	) {

		var $element =
			$( element );


		$element.stop(
			true,
			false
		);


		target.classList.remove(
			'mw-collapsed'
		);


		/*
		 * 표준 MediaWiki 방식
		 *
		 * target 안의
		 * .mw-collapsible-content만 처리
		 */
		if ( element !== target ) {

			$element
				.stop( true, false )
				.slideDown(
					DURATION
				);

			return;
		}


		/*
		 * LACTEA 같은 구조
		 *
		 * mw-collapsible 자체가 display:none이므로
		 * 직접 높이를 애니메이션
		 */

		var display =
			getOpenDisplay(
				element
			);


		$element.css(
			{
				display: display,
				height: 'auto',
				overflow: 'hidden'
			}
		);


		var targetHeight =
			element.scrollHeight;


		$element.css(
			{
				height: 0,
				opacity: 0
			}
		);


		/*
		 * reflow
		 */
		void element.offsetHeight;


		$element.animate(
			{
				height:
					targetHeight,

				opacity:
					1
			},
			{
				duration:
					DURATION,

				easing:
					EASING,

				complete:
					function () {

						$element.css(
							{
								height: '',
								overflow: '',
								opacity: ''
							}
						);
					}
			}
		);
	}


	/**
	 * 접기
	 */
	function collapse(
		target,
		element
	) {

		var $element =
			$( element );


		$element.stop(
			true,
			false
		);


		/*
		 * 현재 display를 반드시 먼저 기억
		 */
		rememberDisplay(
			element
		);


		/*
		 * 표준 MediaWiki 구조
		 */
		if ( element !== target ) {

			$element.slideUp(
				DURATION,
				function () {

					target.classList.add(
						'mw-collapsed'
					);
				}
			);

			return;
		}


		/*
		 * mw-collapsible 자체가 접히는 구조
		 */

		var startHeight =
			element
				.getBoundingClientRect()
				.height;


		$element.css(
			{
				height:
					startHeight,

				overflow:
					'hidden',

				opacity:
					1
			}
		);


		/*
		 * reflow
		 */
		void element.offsetHeight;


		$element.animate(
			{
				height: 0,
				opacity: 0
			},
			{
				duration:
					DURATION,

				easing:
					EASING,

				complete:
					function () {

						target.classList.add(
							'mw-collapsed'
						);


						$element.css(
							{
								display:
									'none',

								height:
									'',

								overflow:
									'',

								opacity:
									''
							}
						);
					}
			}
		);
	}


	/**
	 * 하나의 name에 연결된 대상 전체 토글
	 */
	function toggleGroup( toggle ) {

		var name =
			getToggleName(
				toggle
			);

		if ( !name ) {
			return false;
		}


		var targets =
			getTargets(
				name
			);

		if ( !targets.length ) {
			return false;
		}


		var i;

		/*
		 * 핵심:
		 *
		 * 하나만 선택하지 않고
		 * 같은 이름을 가진 모든 target을
		 * 각자 반대 상태로 변경
		 */
		for (
			i = 0;
			i < targets.length;
			i++
		) {

			var target =
				targets[ i ];

			var element =
				getAnimatedElement(
					target
				);


			if (
				isCollapsed(
					target,
					element
				)
			) {

				expand(
					target,
					element
				);

			} else {

				collapse(
					target,
					element
				);
			}
		}


		/*
		 * 이 형태에서는 보이는 토글과 숨겨진 토글이
		 * 교대하므로 aria-expanded를 특정 하나에
		 * 일괄 적용하는 것은 생략합니다.
		 */


		return true;
	}


	/**
	 * 클릭
	 *
	 * MediaWiki 기본 customtoggle보다
	 * 먼저 실행
	 */
	document.addEventListener(
		'click',
		function ( event ) {

			var toggle =
				event.target.closest(
					'.mw-customtoggle'
				);

			if ( !toggle ) {
				return;
			}


			if (
				!getToggleName(
					toggle
				)
			) {
				return;
			}


			event.preventDefault();

			event.stopPropagation();

			event.stopImmediatePropagation();


			toggleGroup(
				toggle
			);
		},
		true
	);


	/**
	 * Enter / Space
	 */
	document.addEventListener(
		'keydown',
		function ( event ) {

			if (
				event.key !== 'Enter' &&
				event.key !== ' '
			) {
				return;
			}


			var toggle =
				event.target.closest(
					'.mw-customtoggle'
				);

			if ( !toggle ) {
				return;
			}


			if (
				!getToggleName(
					toggle
				)
			) {
				return;
			}


			event.preventDefault();

			event.stopPropagation();

			event.stopImmediatePropagation();


			toggleGroup(
				toggle
			);
		},
		true
	);

}( jQuery, mediaWiki ) );